From 5955c8689e43341ddec30f1f771f0bd763cb9bd4 Mon Sep 17 00:00:00 2001 From: Andrei Aristarkhov Date: Wed, 8 Jul 2026 23:14:49 +0000 Subject: [PATCH 1/2] Updated devcontainer, seamless switching between build environments --- .devcontainer/devcontainer-lock.json | 34 +++++++++++++++++++++ .devcontainer/devcontainer.json | 5 +++- .gitignore | 1 + cmake/SetupPython.cmake | 45 ++++++++++++++++++++++------ 4 files changed, 75 insertions(+), 10 deletions(-) create mode 100644 .devcontainer/devcontainer-lock.json diff --git a/.devcontainer/devcontainer-lock.json b/.devcontainer/devcontainer-lock.json new file mode 100644 index 000000000..344ca1841 --- /dev/null +++ b/.devcontainer/devcontainer-lock.json @@ -0,0 +1,34 @@ +{ + "features": { + "ghcr.io/devcontainers-extra/features/apt-packages:1": { + "version": "1.0.6", + "resolved": "ghcr.io/devcontainers-extra/features/apt-packages@sha256:55c54412112da81b9381e470cdbbe55278564950d1ff536ce925b1e8e096babd", + "integrity": "sha256:55c54412112da81b9381e470cdbbe55278564950d1ff536ce925b1e8e096babd" + }, + "ghcr.io/devcontainers-extra/features/cmake:1": { + "version": "1.0.0", + "resolved": "ghcr.io/devcontainers-extra/features/cmake@sha256:86e25502f6b1213a68d88e4e55c969eba0b5016b9666d216ab65f5047808bc90", + "integrity": "sha256:86e25502f6b1213a68d88e4e55c969eba0b5016b9666d216ab65f5047808bc90" + }, + "ghcr.io/devcontainers-extra/features/uv:1": { + "version": "1.0.2", + "resolved": "ghcr.io/devcontainers-extra/features/uv@sha256:1ac5b9f17a9e9e745933d0ac2ecf758e06ed3da4423cd22c94cce4d482fd2dd8", + "integrity": "sha256:1ac5b9f17a9e9e745933d0ac2ecf758e06ed3da4423cd22c94cce4d482fd2dd8" + }, + "ghcr.io/devcontainers/features/docker-outside-of-docker:1": { + "version": "1.10.0", + "resolved": "ghcr.io/devcontainers/features/docker-outside-of-docker@sha256:c2c2cf829505ead8e4892c88c31b6594ae94a2bbb209e16e1fac456c1a3a624e", + "integrity": "sha256:c2c2cf829505ead8e4892c88c31b6594ae94a2bbb209e16e1fac456c1a3a624e" + }, + "ghcr.io/devcontainers/features/git-lfs:1": { + "version": "1.2.5", + "resolved": "ghcr.io/devcontainers/features/git-lfs@sha256:71c2b371cf12ab7fcec47cf17369c6f59156100dad9abf9e4c593049d789de72", + "integrity": "sha256:71c2b371cf12ab7fcec47cf17369c6f59156100dad9abf9e4c593049d789de72" + }, + "ghcr.io/devcontainers/features/git:1": { + "version": "1.3.8", + "resolved": "ghcr.io/devcontainers/features/git@sha256:fd75977de13a9979000e0e78baf949adb0ca71d2398995fa22e0a36d7e7e7fe2", + "integrity": "sha256:fd75977de13a9979000e0e78baf949adb0ca71d2398995fa22e0a36d7e7e7fe2" + } + } +} diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index f88e4bb1f..f8507ba98 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -6,7 +6,7 @@ "ghcr.io/devcontainers/features/git-lfs:1": {}, "ghcr.io/devcontainers/features/docker-outside-of-docker:1": {}, "ghcr.io/devcontainers-extra/features/apt-packages:1": { - "packages": "clang-format-14,libx11-dev,libxrandr-dev,libxxf86vm-dev,libgl1-mesa-dev" + "packages": "clang-format-14,libx11-dev,libxrandr-dev,libxxf86vm-dev,libgl1-mesa-dev,patchelf" }, "ghcr.io/devcontainers-extra/features/cmake:1": {}, "ghcr.io/devcontainers-extra/features/uv:1": {} @@ -14,6 +14,9 @@ "postCreateCommand": "uv tool install pre-commit && pre-commit install", "customizations": { "vscode": { + "settings": { + "cmake.buildDirectory": "${workspaceFolder}/build-devcontainer" + }, "extensions": [ "ms-vscode.cpptools", "ms-vscode.cmake-tools", diff --git a/.gitignore b/.gitignore index f3d14020a..543342f3a 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ package-lock.json # Build directories build/ +build-devcontainer/ _build/ **/build/ install/ diff --git a/cmake/SetupPython.cmake b/cmake/SetupPython.cmake index 853b6d43f..7ec2ad243 100644 --- a/cmake/SetupPython.cmake +++ b/cmake/SetupPython.cmake @@ -150,15 +150,44 @@ if(BUILD_PYTHON_BINDINGS) else() set(_venv_python "${_build_venv}/bin/python") endif() - # Reuse an existing venv when possible. If the directory exists but the - # interpreter is missing, fail fast and require explicit cleanup. - if(EXISTS "${_build_venv}" AND NOT EXISTS "${_venv_python}") - message(FATAL_ERROR - "Found stale build venv directory at ${_build_venv}, but no interpreter at ${_venv_python}. " - "Please remove ${_build_venv} and reconfigure." + # Reuse the venv when its interpreter still runs. bin/python is an absolute + # symlink into a uv-managed Python, so it is only valid in the environment + # that created the venv. + set(_venv_usable FALSE) + if(EXISTS "${_venv_python}") + execute_process( + COMMAND "${_venv_python}" --version + RESULT_VARIABLE _venv_python_ok + OUTPUT_QUIET + ERROR_QUIET ) + if(_venv_python_ok EQUAL 0) + set(_venv_usable TRUE) + endif() endif() - if(NOT EXISTS "${_venv_python}") + + if(_venv_usable) + message(STATUS "Reusing existing build venv at ${_build_venv}") + elseif(EXISTS "${_build_venv}") + # Directory is present but its interpreter is gone. The usual cause is a + # dangling bin/python symlink: the venv was created in a different + # environment (e.g. a dev container vs the host) whose uv-managed Python + # lives under a different path. Report the broken link so the fix is + # obvious rather than failing with a generic error later. + set(_venv_symlink_detail "") + if(IS_SYMLINK "${_venv_python}") + file(READ_SYMLINK "${_venv_python}" _venv_link_target) + set(_venv_symlink_detail + "\n ${_venv_python} -> ${_venv_link_target} (target does not exist)") + endif() + message(FATAL_ERROR + "Build venv at ${_build_venv} has no working Python interpreter." + "${_venv_symlink_detail}\n" + "This build venv was created in a different environment (its interpreter " + "symlink points into a uv-managed Python that is not present here). Remove " + "the build directory ${CMAKE_BINARY_DIR} (or run 'cmake --fresh') and " + "reconfigure to recreate the venv for this environment.") + else() execute_process( COMMAND "${UV_EXECUTABLE}" venv --python "${Python3_EXECUTABLE}" "${_build_venv}" RESULT_VARIABLE _venv_ok @@ -167,8 +196,6 @@ if(BUILD_PYTHON_BINDINGS) if(NOT _venv_ok EQUAL 0) message(FATAL_ERROR "Failed to create build venv: ${_venv_err}") endif() - else() - message(STATUS "Reusing existing build venv at ${_build_venv}") endif() execute_process( COMMAND "${UV_EXECUTABLE}" pip install --python "${_venv_python}" "numpy>=2.0" From 4142ca2b479e5753a881b915c09e8cef57b5c484 Mon Sep 17 00:00:00 2001 From: Andrei Aristarkhov Date: Thu, 9 Jul 2026 15:13:09 -0700 Subject: [PATCH 2/2] Fix "target does not exist" message when it does --- cmake/SetupPython.cmake | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/cmake/SetupPython.cmake b/cmake/SetupPython.cmake index 7ec2ad243..3b1390915 100644 --- a/cmake/SetupPython.cmake +++ b/cmake/SetupPython.cmake @@ -169,24 +169,26 @@ if(BUILD_PYTHON_BINDINGS) if(_venv_usable) message(STATUS "Reusing existing build venv at ${_build_venv}") elseif(EXISTS "${_build_venv}") - # Directory is present but its interpreter is gone. The usual cause is a - # dangling bin/python symlink: the venv was created in a different - # environment (e.g. a dev container vs the host) whose uv-managed Python - # lives under a different path. Report the broken link so the fix is - # obvious rather than failing with a generic error later. - set(_venv_symlink_detail "") - if(IS_SYMLINK "${_venv_python}") + # Directory is present but its interpreter is not usable. Describe the + # actual failure: a dangling bin/python symlink (the common case when the + # venv was created in a different environment, e.g. a dev container vs the + # host, whose uv-managed Python lives elsewhere) versus an interpreter that + # is present but does not run. Only the former means the target is missing. + if(IS_SYMLINK "${_venv_python}" AND NOT EXISTS "${_venv_python}") file(READ_SYMLINK "${_venv_python}" _venv_link_target) - set(_venv_symlink_detail - "\n ${_venv_python} -> ${_venv_link_target} (target does not exist)") + string(CONCAT _venv_detail + "Its interpreter symlink is broken: ${_venv_python} -> " + "${_venv_link_target} (target does not exist). This build venv was " + "created in a different environment whose uv-managed Python lives " + "elsewhere.") + else() + set(_venv_detail "Its interpreter ${_venv_python} is missing or does not run.") endif() message(FATAL_ERROR - "Build venv at ${_build_venv} has no working Python interpreter." - "${_venv_symlink_detail}\n" - "This build venv was created in a different environment (its interpreter " - "symlink points into a uv-managed Python that is not present here). Remove " - "the build directory ${CMAKE_BINARY_DIR} (or run 'cmake --fresh') and " - "reconfigure to recreate the venv for this environment.") + "Build venv at ${_build_venv} has no working Python interpreter. " + "${_venv_detail}\n" + "Remove the build directory ${CMAKE_BINARY_DIR} (or run 'cmake --fresh') " + "and reconfigure to recreate the venv for this environment.") else() execute_process( COMMAND "${UV_EXECUTABLE}" venv --python "${Python3_EXECUTABLE}" "${_build_venv}"