Skip to content

Commit b6861d8

Browse files
committed
Support standalone uv workflow and direct pybind builds
1 parent c9839a9 commit b6861d8

5 files changed

Lines changed: 107 additions & 61 deletions

File tree

CMakeLists.txt

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,37 @@
1+
cmake_minimum_required(VERSION 3.15)
2+
13
include(FetchContent)
2-
project(_lbug)
4+
project(_lbug LANGUAGES CXX C)
35

46
set(CMAKE_CXX_STANDARD 20)
7+
set(LBUG_SOURCE_DIR "" CACHE PATH "Path to the Ladybug source tree used for pybind builds")
8+
9+
if(NOT TARGET pybind11::module)
10+
if(LBUG_SOURCE_DIR)
11+
add_subdirectory("${LBUG_SOURCE_DIR}/third_party/pybind11" "${CMAKE_BINARY_DIR}/third_party/pybind11" EXCLUDE_FROM_ALL)
12+
else()
13+
find_package(pybind11 CONFIG REQUIRED)
14+
endif()
15+
endif()
16+
17+
if(NOT LBUG_API_USE_PRECOMPILED_LIB AND NOT TARGET lbug)
18+
if(NOT LBUG_SOURCE_DIR)
19+
message(FATAL_ERROR "LBUG_SOURCE_DIR must be set when building the pybind extension from Ladybug sources.")
20+
endif()
21+
22+
set(BUILD_BENCHMARK FALSE CACHE BOOL "" FORCE)
23+
set(BUILD_EXAMPLES FALSE CACHE BOOL "" FORCE)
24+
set(BUILD_EXTENSION_TESTS FALSE CACHE BOOL "" FORCE)
25+
set(BUILD_JAVA FALSE CACHE BOOL "" FORCE)
26+
set(BUILD_NODEJS FALSE CACHE BOOL "" FORCE)
27+
set(BUILD_PYTHON FALSE CACHE BOOL "" FORCE)
28+
set(BUILD_SHELL FALSE CACHE BOOL "" FORCE)
29+
set(BUILD_TESTS FALSE CACHE BOOL "" FORCE)
30+
set(BUILD_WAL_DUMP FALSE CACHE BOOL "" FORCE)
31+
set(BUILD_WASM FALSE CACHE BOOL "" FORCE)
32+
33+
add_subdirectory("${LBUG_SOURCE_DIR}" "${CMAKE_BINARY_DIR}/lbug-source" EXCLUDE_FROM_ALL)
34+
endif()
535

636
file(GLOB SOURCE_PY
737
"src_py/*")
@@ -60,6 +90,23 @@ target_include_directories(
6090
PUBLIC
6191
src_cpp/include)
6292

93+
if(TARGET lbug)
94+
get_target_property(LBUG_INCLUDE_DIRECTORIES lbug INCLUDE_DIRECTORIES)
95+
if(LBUG_INCLUDE_DIRECTORIES)
96+
target_include_directories(_lbug PRIVATE ${LBUG_INCLUDE_DIRECTORIES})
97+
endif()
98+
99+
get_target_property(LBUG_COMPILE_DEFINITIONS lbug COMPILE_DEFINITIONS)
100+
if(LBUG_COMPILE_DEFINITIONS)
101+
target_compile_definitions(_lbug PRIVATE ${LBUG_COMPILE_DEFINITIONS})
102+
endif()
103+
104+
get_target_property(LBUG_COMPILE_OPTIONS lbug COMPILE_OPTIONS)
105+
if(LBUG_COMPILE_OPTIONS)
106+
target_compile_options(_lbug PRIVATE ${LBUG_COMPILE_OPTIONS})
107+
endif()
108+
endif()
109+
63110
get_target_property(PYTHON_DEST _lbug LIBRARY_OUTPUT_DIRECTORY)
64111

65112
file(COPY ${SOURCE_PY} DESTINATION ${PYTHON_DEST})

Makefile

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
.DEFAULT_GOAL := help
22
# Explicit targets to avoid conflict with files of the same name.
33
.PHONY: \
4-
requirements \
4+
requirements sync \
55
lint check format \
66
build bootstrap-capi build-pybind-subdir test test-pybind-subdir \
77
help
88

99
PYTHONPATH=
1010
SHELL=/usr/bin/env bash
1111
VENV=.venv
12-
LBUG_SOURCE_DIR?=ladybug
12+
UV_CACHE_DIR?=$(CURDIR)/.cache/uv
13+
LBUG_SOURCE_DIR?=$(abspath ../ladybug)
1314

1415
ifeq ($(OS),Windows_NT)
1516
VENV_BIN=$(VENV)/Scripts
@@ -18,11 +19,14 @@ else
1819
endif
1920

2021
.venv: ## Set up a Python virtual environment and install dev packages
21-
uv venv $(VENV)
22+
UV_CACHE_DIR="$(UV_CACHE_DIR)" uv venv $(VENV)
2223

2324
requirements: .venv ## Install/update Python dev packages
2425
@unset CONDA_PREFIX \
25-
&& uv pip install -e .[dev]
26+
&& UV_CACHE_DIR="$(UV_CACHE_DIR)" uv pip install -e .[dev]
27+
28+
sync: bootstrap-capi ## Sync project + dev dependencies for uv run / pytest
29+
UV_CACHE_DIR="$(UV_CACHE_DIR)" uv sync --extra dev
2630

2731
pytest: requirements
2832
ifeq ($(OS),Windows_NT)
@@ -45,11 +49,10 @@ format: requirements
4549

4650
CAPI_ENV_FILE=.cache/lbug-capi.env
4751

48-
build: bootstrap-capi ## Prepare C-API backend package in ./build
49-
mkdir -p build/ladybug
50-
cp src_py/*.py build/ladybug/
52+
build: bootstrap-capi ## Prepare standalone C-API runtime assets
53+
@echo "Standalone package loads from src_py via editable install; shared lib cached under .cache/lbug-prebuilt."
5154

52-
build-pybind-subdir: requirements ## Build pybind via ./ladybug checkout (inverted layout)
55+
build-pybind-subdir: requirements ## Build pybind from this repo using Ladybug sources at LBUG_SOURCE_DIR
5356
bash scripts/build_pybind_from_subdir.sh "$(LBUG_SOURCE_DIR)"
5457

5558
test-pybind-subdir: build-pybind-subdir ## Run tests against pybind build produced from ./ladybug
@@ -59,8 +62,8 @@ test-pybind-subdir: build-pybind-subdir ## Run tests against pybind build produc
5962
bootstrap-capi: ## Download latest shared C-API binary and emit runtime env file
6063
LBUG_LIB_KIND=shared bash scripts/download_lbug.sh $(CAPI_ENV_FILE)
6164

62-
test: requirements build ## Run the Python unit tests
63-
cd build && $(VENV_BIN)/pytest test
65+
test: requirements build ## Run the standalone Python unit tests
66+
$(VENV_BIN)/pytest -q
6467

6568
help: ## Display this help information
6669
@echo -e "\033[1mAvailable commands:\033[0m"

README.md

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,36 +5,45 @@
55
### C-API backend (default)
66

77
```bash
8-
make build
8+
make sync
99
```
1010

1111
This downloads the latest shared `liblbug` binary (via upstream
12-
`download-liblbug.sh`) and stages Python sources in `./build/ladybug`.
12+
`download-liblbug.sh`) and syncs the project with dev dependencies.
13+
The Python package is installed directly from `src_py/`, so the standalone
14+
workflow no longer depends on `./build/ladybug`.
1315

1416
Run tests with:
1517

1618
```bash
17-
make test
19+
uv run pytest
1820
```
1921

2022
### Pybind backend from inverted layout
2123

2224
If your checkout layout is:
2325

24-
- `ladybug-python/` (this repo, top-level)
25-
- `ladybug-python/ladybug/` (main Ladybug repo as subdir)
26+
- `ladybug-python/` (this repo)
27+
- `../ladybug/` (main Ladybug repo as a sibling checkout)
2628

2729
then build the pybind extension through the Ladybug top-level build with:
2830

2931
```bash
3032
make build-pybind-subdir
3133
```
3234

33-
This creates a symlink at `ladybug/tools/python_api -> <this repo>`, runs
34-
`make python` in `./ladybug`, and copies `_lbug*` into `./build/ladybug`.
35+
This uses `LBUG_SOURCE_DIR` (default: `../ladybug`) to configure this repo's
36+
CMake build against the Ladybug source checkout and writes `_lbug*` into
37+
`./build/ladybug`.
3538

3639
Run tests against that pybind build with:
3740

3841
```bash
3942
make test-pybind-subdir
4043
```
44+
45+
Override the source tree location when needed:
46+
47+
```bash
48+
make build-pybind-subdir LBUG_SOURCE_DIR=/path/to/ladybug
49+
```

pyproject.toml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,6 @@ url = "https://data.pyg.org/whl/torch-2.5.0+cpu/"
4545
[tool.uv.sources]
4646
torch = { index = "pytorch-cpu" }
4747

48-
[tool.uv.workspace]
49-
members = [
50-
"t1",
51-
]
52-
5348
[tool.uv]
5449
index-strategy = "unsafe-best-match"
5550

@@ -133,9 +128,14 @@ strict = true
133128
[tool.ruff.format]
134129
docstring-code-format = true
135130

136-
[tool.setuptools.packages.find]
137-
where = ["src_py", "build"]
138-
exclude = ["src_cpp*"]
131+
[tool.setuptools]
132+
packages = ["ladybug"]
133+
134+
[tool.setuptools.package-dir]
135+
ladybug = "src_py"
136+
137+
[tool.setuptools.package-data]
138+
ladybug = ["py.typed"]
139139

140140
[build-system]
141141
requires = ["setuptools", "wheel"]

scripts/build_pybind_from_subdir.sh

Lines changed: 22 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,18 @@
22
set -euo pipefail
33

44
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
5-
LBUG_DIR="${1:-${ROOT_DIR}/ladybug}"
5+
LBUG_DIR="${1:-$(cd "${ROOT_DIR}/.." && pwd)/ladybug}"
6+
BUILD_DIR="${ROOT_DIR}/build/pybind"
7+
CCACHE_DIR="${ROOT_DIR}/.cache/ccache"
8+
CCACHE_TEMPDIR="${CCACHE_DIR}/tmp"
69

710
if [[ ! -d "${LBUG_DIR}" ]]; then
811
echo "ladybug source checkout not found: ${LBUG_DIR}" >&2
9-
echo "Expected inverted layout: <repo>/ladybug" >&2
12+
echo "Set LBUG_SOURCE_DIR to your Ladybug source tree checkout." >&2
1013
exit 1
1114
fi
1215

13-
TOOLS_DIR="${LBUG_DIR}/tools"
14-
API_LINK="${TOOLS_DIR}/python_api"
15-
16-
mkdir -p "${TOOLS_DIR}"
17-
18-
if [[ -e "${API_LINK}" && ! -L "${API_LINK}" ]]; then
19-
echo "Refusing to overwrite non-symlink path: ${API_LINK}" >&2
20-
echo "Please remove it manually or convert it to a symlink to ${ROOT_DIR}" >&2
21-
exit 1
22-
fi
23-
24-
rm -f "${API_LINK}"
25-
ln -s "${ROOT_DIR}" "${API_LINK}"
26-
27-
echo "[pybind] Building via ${LBUG_DIR} (target: make python)"
16+
echo "[pybind] Building ${ROOT_DIR} with Ladybug sources from ${LBUG_DIR}"
2817
PYTHON_BIN="${PYTHON_BIN:-${ROOT_DIR}/.venv/bin/python}"
2918
if [[ ! -x "${PYTHON_BIN}" ]]; then
3019
PYTHON_BIN="$(command -v python3)"
@@ -37,31 +26,29 @@ export PATH="$(dirname "${PYTHON_BIN}"):${PATH}"
3726
export PYTHON_EXECUTABLE="${PYTHON_BIN}"
3827
export Python_EXECUTABLE="${PYTHON_BIN}"
3928
export Python3_EXECUTABLE="${PYTHON_BIN}"
29+
export CCACHE_DIR
30+
export CCACHE_TEMPDIR
4031

41-
make -C "${LBUG_DIR}" clean-python-api || true
42-
rm -rf "${LBUG_DIR}/build/release"
32+
mkdir -p "${CCACHE_TEMPDIR}"
4333

44-
EXTRA_CMAKE_FLAGS="-DPYTHON_EXECUTABLE=${PYTHON_BIN} -DPython_EXECUTABLE=${PYTHON_BIN} -DPython3_EXECUTABLE=${PYTHON_BIN} -DPYBIND11_PYTHON_VERSION=${PYTHON_VERSION}" \
45-
make -C "${LBUG_DIR}" python
34+
rm -rf "${BUILD_DIR}"
4635

47-
mkdir -p "${ROOT_DIR}/build/ladybug"
48-
cp "${ROOT_DIR}"/src_py/*.py "${ROOT_DIR}/build/ladybug/"
36+
cmake \
37+
-S "${ROOT_DIR}" \
38+
-B "${BUILD_DIR}" \
39+
-DCMAKE_BUILD_TYPE=Release \
40+
-DLBUG_SOURCE_DIR="${LBUG_DIR}" \
41+
-DPYTHON_EXECUTABLE="${PYTHON_BIN}" \
42+
-DPython_EXECUTABLE="${PYTHON_BIN}" \
43+
-DPython3_EXECUTABLE="${PYTHON_BIN}" \
44+
-DPYBIND11_PYTHON_VERSION="${PYTHON_VERSION}"
4945

50-
# Copy extension artifact(s) to local build package.
51-
shopt -s nullglob
52-
for ext in "${API_LINK}/build/ladybug"/_lbug*.so "${API_LINK}/build/ladybug"/_lbug*.pyd "${API_LINK}/build/ladybug"/_lbug*.dylib; do
53-
src_real="$(realpath "${ext}")"
54-
dst_real="$(realpath "${ROOT_DIR}/build/ladybug/$(basename "${ext}")" 2>/dev/null || true)"
55-
if [[ -n "${dst_real}" && "${src_real}" == "${dst_real}" ]]; then
56-
continue
57-
fi
58-
cp "${ext}" "${ROOT_DIR}/build/ladybug/"
59-
done
46+
cmake --build "${BUILD_DIR}" --config Release --target _lbug
6047

6148
if compgen -G "${ROOT_DIR}/build/ladybug/_lbug*" > /dev/null; then
62-
echo "[pybind] Copied extension into ${ROOT_DIR}/build/ladybug"
49+
echo "[pybind] Built extension into ${ROOT_DIR}/build/ladybug"
6350
else
6451
echo "[pybind] Build finished, but no _lbug extension artifact was found." >&2
65-
echo "Checked: ${API_LINK}/build/ladybug" >&2
52+
echo "Checked: ${ROOT_DIR}/build/ladybug" >&2
6653
exit 1
6754
fi

0 commit comments

Comments
 (0)