Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ tests part of the derived-project contract.
included in a wheel.
- Keep CMake Python install destinations relative to
`CMAKE_INSTALL_PREFIX`; pip owns installation into an active environment.
- Wrapper checkout updates, submodule initialization, and submodule creation
are explicit maintenance operations. Ordinary configure and build commands
must not move the wrapper checkout or change the parent repository gitlinks.

For MATLAB: Use classes a lot also in MATLAB, with a python style, but do it only when it makes sense. Functions in MATLAB are often more efficient. Evaluate whether it makes sense to have stateful implementation. Use "self" instead of "obj". All variables names must specify the datatype of the variable since MATLAB does not (hungarian notation). The following list applies: d for double, f for float, b for bool, str for struct and not for strings, char for strings and chars, ui8 for uint8, i8 for int8. All the other integers are similar to the latter. Specify "obj" as prefix if an object, cell if a cell, table if a table; "bus_" if a Simulink bus. The names are always in Pascal case including the prefix, for instance ui8MyVariable. Never nest functions definitions within other functions, always do them separate or at most in the same file (after the main function implementation). Add them as local in the same function file only when not re-used elsewhere, otherwise prefer a single implementation. Function names and static methods of classes starts with Capital letter. Local functions names ends with underscore meaning "private". Names of variables must be explicative and tell what the variable does. Short names are not allowed unless "very local in scope". Use underscore for those variables and preferably Tmp within the name. For codes that are intended to be algorithms of some kind (e.g. not plots or things to run on the host PC), make them always MATLAB codegen safe (especially if codegen directive is used). In that case names should be limited to 31 chars. Add the same template of doc to functions as below and always specify arguments-end block for input and output:
%% SIGNATURE
Expand Down
194 changes: 81 additions & 113 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,63 @@ set(PROJECT_MAINTAINER_NAME "Pietro Califano" CACHE STRING "Project maintainer n
set(PROJECT_MAINTAINER_EMAIL "petercalifano.gs@gmail.com" CACHE STRING "Project maintainer email")
set(PROJECT_LICENSE "MIT" CACHE STRING "Project SPDX license identifier")

# Determine ownership before project() so pre-language feature selection cannot
# consume unrelated generic cache options from an add_subdirectory() parent.
set(BUILD_AS_MAIN_PROJECT OFF)
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
set(BUILD_AS_MAIN_PROJECT ON)
endif()

# Set install default directory if not specified
if(NOT DEFINED CMAKE_INSTALL_PREFIX)
set(CMAKE_INSTALL_PREFIX ${CMAKE_SOURCE_DIR}/install CACHE PATH "Install path" FORCE)
endif()

option(PROJECT_METADATA_ONLY "Configure only project identity and version metadata" OFF)
option(ENABLE_OPTIX "Enable OptiX" OFF)
option(ENABLE_CUDA "Enable CUDA" OFF)
# Project-qualified options are canonical in nested builds. Preserve the
# historical generic spellings only as one-config top-level input aliases.
set(METADATA_ONLY_OPTION_NAME "${project_name}_METADATA_ONLY")
set(ENABLE_OPTIX_OPTION_NAME "${project_name}_ENABLE_OPTIX")
set(ENABLE_CUDA_OPTION_NAME "${project_name}_ENABLE_CUDA")

function(_template_project_migrate_top_level_bool_option_alias legacy_option
canonical_option option_help)
if(NOT BUILD_AS_MAIN_PROJECT)
return()
endif()

get_property(_legacy_alias_is_cached
CACHE "${legacy_option}" PROPERTY TYPE SET)
if(NOT _legacy_alias_is_cached)
return()
endif()

# Consume legacy command-line/cache input on every configure so an ON-to-OFF
# transition updates the canonical value instead of remaining stuck.
set(_legacy_alias_value "${${legacy_option}}")
set("${canonical_option}" "${_legacy_alias_value}"
CACHE BOOL "${option_help}" FORCE)
unset("${legacy_option}" CACHE)
endfunction()

_template_project_migrate_top_level_bool_option_alias(
PROJECT_METADATA_ONLY "${METADATA_ONLY_OPTION_NAME}"
"Configure only project identity and version metadata")
_template_project_migrate_top_level_bool_option_alias(
ENABLE_OPTIX "${ENABLE_OPTIX_OPTION_NAME}" "Enable OptiX")
_template_project_migrate_top_level_bool_option_alias(
ENABLE_CUDA "${ENABLE_CUDA_OPTION_NAME}" "Enable CUDA")

option(${METADATA_ONLY_OPTION_NAME}
"Configure only project identity and version metadata"
OFF)
option(${ENABLE_OPTIX_OPTION_NAME} "Enable OptiX" OFF)
option(${ENABLE_CUDA_OPTION_NAME} "Enable CUDA" OFF)

# Retained modules consume the historical local variable names. Normal
# directory-scope assignments isolate them from a parent's cache entries.
set(PROJECT_METADATA_ONLY "${${METADATA_ONLY_OPTION_NAME}}")
set(ENABLE_OPTIX "${${ENABLE_OPTIX_OPTION_NAME}}")
set(ENABLE_CUDA "${${ENABLE_CUDA_OPTION_NAME}}")

if(PROJECT_METADATA_ONLY)
set(languages NONE)
Expand Down Expand Up @@ -85,12 +134,6 @@ endif()
write_build_VERSION_file()
install(FILES "${PROJECT_BINARY_DIR}/VERSION" DESTINATION ".")

# Define variable for build as subproject (compare source dir)
set(BUILD_AS_MAIN_PROJECT ON)
if(NOT CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR)
set(BUILD_AS_MAIN_PROJECT OFF)
endif()

# Define variable for namespacing (required to prevent global target clashes!)
set(LIB_NAMESPACE "${PROJECT_NAME}")
if (DEFINED LIB_NAMESPACE_OVERRIDE)
Expand Down Expand Up @@ -545,122 +588,47 @@ if(DEFINED FULL_VERSION AND NOT "${FULL_VERSION}" STREQUAL "")
endif()
set(CPACK_GENERATOR "TGZ")
set(CPACK_SOURCE_GENERATOR "TGZ")

# VERSION is generated in the build tree so configured package metadata remains
# authoritative even when the ignored source-tree fallback is stale. Stage that
# exact file through CPack's private install prefix for binary and source TGZs.
set(_cpack_stage_version_script
"${PROJECT_BINARY_DIR}/StagePackageVersion.cmake")
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/StagePackageVersion.cmake.in"
"${_cpack_stage_version_script}"
@ONLY)
if(CMAKE_VERSION VERSION_LESS "3.16")
set(CPACK_INSTALL_SCRIPT "${_cpack_stage_version_script}")
else()
list(APPEND CPACK_INSTALL_SCRIPTS "${_cpack_stage_version_script}")
endif()

# Preserve source-ignore regexes verbatim and anchor known generated outputs
# beneath this checkout rather than matching adjacent directories.
# beneath this checkout rather than matching adjacent directories. The ignored
# source VERSION must not overwrite the generated file staged above.
set(CPACK_VERBATIM_VARIABLES YES)
set(_cpack_source_root_regex "${CMAKE_CURRENT_SOURCE_DIR}")
string(
REGEX REPLACE "([][+.*^$()|?\\\\])" "\\\\\\1"
_cpack_source_root_regex "${_cpack_source_root_regex}")
set(CPACK_SOURCE_IGNORE_FILES
"^${_cpack_source_root_regex}/(.*/)?\\.git(/|$)"
"^${_cpack_source_root_regex}/VERSION$"
"^${_cpack_source_root_regex}/install/"
"^${_cpack_source_root_regex}/ros2/(build|install|log)/"
"^${_cpack_source_root_regex}/(.*/)?\\.pytest_cache/"
"^${_cpack_source_root_regex}/(.*/)?__pycache__/"
"^${_cpack_source_root_regex}/.*\\.py[cod]$")

# Exclude the active nested binary tree and other nested CMake builds whose
# caches prove that this exact checkout owns them. Directory names alone are
# insufficient because paths such as tools/build_helpers may be real sources.
get_filename_component(
_cpack_source_root_real
"${CMAKE_CURRENT_SOURCE_DIR}"
REALPATH)
get_filename_component(
_cpack_binary_root_real
"${CMAKE_BINARY_DIR}"
REALPATH)
file(
RELATIVE_PATH
_cpack_binary_relative_to_source
"${_cpack_source_root_real}"
"${_cpack_binary_root_real}")
set(_cpack_owned_build_directories)
if(NOT IS_ABSOLUTE "${_cpack_binary_relative_to_source}"
AND NOT "${_cpack_binary_relative_to_source}" MATCHES "^\\.\\.(/|$)"
AND NOT "${_cpack_binary_relative_to_source}" STREQUAL "")
list(APPEND
_cpack_owned_build_directories
"${CMAKE_BINARY_DIR}")
endif()

file(
GLOB_RECURSE
_cpack_cache_candidates
LIST_DIRECTORIES FALSE
"${CMAKE_CURRENT_SOURCE_DIR}/*/CMakeCache.txt")
foreach(_cpack_cache_candidate IN LISTS _cpack_cache_candidates)
file(
RELATIVE_PATH
_cpack_cache_relative_to_source
"${CMAKE_CURRENT_SOURCE_DIR}"
"${_cpack_cache_candidate}")
if("${_cpack_cache_relative_to_source}" MATCHES "^install/"
OR "${_cpack_cache_relative_to_source}"
MATCHES "^ros2/(build|install|log)/")
continue()
endif()

set(_cpack_cache_is_within_owned_build FALSE)
foreach(_cpack_known_build IN LISTS _cpack_owned_build_directories)
file(
RELATIVE_PATH
_cpack_cache_relative_to_build
"${_cpack_known_build}"
"${_cpack_cache_candidate}")
if(NOT IS_ABSOLUTE "${_cpack_cache_relative_to_build}"
AND NOT "${_cpack_cache_relative_to_build}" MATCHES "^\\.\\.(/|$)")
set(_cpack_cache_is_within_owned_build TRUE)
break()
endif()
endforeach()
if(_cpack_cache_is_within_owned_build
OR NOT EXISTS "${_cpack_cache_candidate}")
continue()
endif()

file(
STRINGS
"${_cpack_cache_candidate}"
_cpack_cache_home_entries
REGEX "^CMAKE_HOME_DIRECTORY:INTERNAL="
LIMIT_COUNT 1)
if(NOT _cpack_cache_home_entries)
continue()
endif()

list(GET _cpack_cache_home_entries 0 _cpack_cache_home_entry)
string(REGEX MATCH
"^CMAKE_HOME_DIRECTORY:INTERNAL=(.*)$"
_cpack_cache_home_match
"${_cpack_cache_home_entry}")
set(_cpack_cache_home "${CMAKE_MATCH_1}")
get_filename_component(
_cpack_cache_home_real
"${_cpack_cache_home}"
REALPATH)
if(NOT "${_cpack_cache_home_real}" STREQUAL "${_cpack_source_root_real}")
continue()
endif()

get_filename_component(
_cpack_owned_build_directory
"${_cpack_cache_candidate}"
DIRECTORY)
list(APPEND
_cpack_owned_build_directories
"${_cpack_owned_build_directory}")
endforeach()

list(REMOVE_DUPLICATES _cpack_owned_build_directories)
foreach(_cpack_owned_build_directory IN LISTS _cpack_owned_build_directories)
string(
REGEX REPLACE "([][+.*^$()|?\\\\])" "\\\\\\1"
_cpack_owned_build_regex "${_cpack_owned_build_directory}")
list(
APPEND
CPACK_SOURCE_IGNORE_FILES
"^${_cpack_owned_build_regex}(/|$)")
endforeach()
# Refresh checkout-owned build exclusions when CPack runs. A generated build
# can appear after this configure, while path names alone remain insufficient
# evidence because build-prefixed directories may be legitimate sources.
set(_cpack_refresh_source_ignores_script
"${PROJECT_BINARY_DIR}/RefreshCPackSourceIgnores.cmake")
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/RefreshCPackSourceIgnores.cmake.in"
"${_cpack_refresh_source_ignores_script}"
@ONLY)
set(CPACK_PROJECT_CONFIG_FILE "${_cpack_refresh_source_ignores_script}")
include(CPack)
88 changes: 70 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ All options are passed via `build_lib.sh` flags or directly as `-D<VAR>=<VAL>` t
### `build_lib.sh` reference

```
-B, --buildpath <dir> Build directory (default: ./build)
-B, --buildpath <dir> Build directory (default: <checkout>/build)
-t, --type <type> debug | release | relwithdebinfo | minsizerel
-j, --jobs <N> Parallel jobs (default: nproc or 4)
-r, --rebuild-only Skip CMake configure; rebuild sources only
Expand All @@ -177,9 +177,11 @@ All options are passed via `build_lib.sh` flags or directly as `-D<VAR>=<VAL>` t
--ctest-extra-args <args>
Simple whitespace-split arguments appended to CTest
--gtwrap-root <dir> Path to local wrap checkout root
--no-wrap-update Disable auto-update of local wrap checkout to latest master
--wrap-update Explicitly update a local wrap checkout to latest master
--no-wrap-update Keep the local wrap checkout unchanged (default)
--wrap-submodule-init Explicitly initialize a declared wrap submodule fallback
--no-wrap-submodule-init
Disable wrap submodule initialization fallback
Do not initialize a wrap submodule (default)
--toolchain <file> CMake toolchain file
-h, --help Show full help
```
Expand All @@ -188,14 +190,17 @@ See [`doc/build_script_doc.md`](doc/build_script_doc.md) for a detailed option r

`--clean` accepts only conventional in-repository `build`, `build*`, or
`out/*` paths. An existing directory must contain a CMake cache owned by this
checkout. The option is ignored with `--rebuild-only`.
checkout. Relative paths remain anchored to the checkout containing the script,
including when it is invoked from another working directory. The option is
ignored with `--rebuild-only`.

### CMake feature flags

| Option | Default | Description |
|---|---|---|
| `ENABLE_CUDA` | OFF | CUDA GPU acceleration |
| `ENABLE_OPTIX` | OFF | NVIDIA OptiX (enables CUDA automatically) |
| `template_project_ENABLE_CUDA` | OFF | CUDA GPU acceleration |
| `template_project_ENABLE_OPTIX` | OFF | NVIDIA OptiX (enables CUDA automatically) |
| `template_project_METADATA_ONLY` | OFF | Configure project identity/version without compiler languages |
| `ENABLE_TBB` | OFF | Intel oneTBB support (`find_package(TBB)`) |
| `ENABLE_OPENGL` | OFF | OpenGL support |
| `ENABLE_TESTS` | ON | Register and run CTest tests |
Expand Down Expand Up @@ -226,6 +231,14 @@ checkout. The option is ignored with `--rebuild-only`.
| `NO_OPTIMIZATION` | OFF | Force profiler-friendly `-O0 -g3`, frame pointers, and assertions regardless of build type |
| `WARNINGS_ARE_ERRORS` | OFF | Treat all warnings as errors (`-Werror`) |

Replace the `template_project` prefix during tailoring. The historical
`ENABLE_CUDA`, `ENABLE_OPTIX`, and `PROJECT_METADATA_ONLY` options remain
top-level compatibility aliases; nested consumers must use the project-qualified
forms so parent cache options cannot change the library configuration. A legacy
alias supplied to a top-level configure wins for that invocation, is copied to
the canonical option, and is then removed from the cache so later reconfigures
cannot retain two conflicting sources of truth.

### Build type compiler flags

| Build type | Flags | Notes |
Expand Down Expand Up @@ -322,10 +335,14 @@ When `-p` and/or `-m` is used, wrapper resolution now follows this order:
3. If still unresolved and `GTWRAP_INIT_SUBMODULE_IF_MISSING=ON`, initialize a
declared `wrap` or `lib/wrap` git submodule and use that checkout.

Existing local wrap roots are updated to latest `origin/master` by default. This
includes detached/tag states by switching/creating local `master` from
`origin/master`. Pass `--no-wrap-update` to disable that update step, or
`--no-wrap-submodule-init` to disable the submodule fallback entirely.
Wrapper checkout maintenance is disabled by default. Pass `--wrap-update` to
explicitly advance a resolved local checkout to `origin/master`, or
`--wrap-submodule-init` to initialize a declared submodule after local and
installed discovery fail. Direct CMake callers must grant checkout maintenance
with `GTWRAP_MAINTENANCE_UPDATE=ON` as well as requesting
`GTWRAP_SYNC_TO_MASTER=ON`. Submodule initialization applies only to a `wrap`
or `lib/wrap` entry already declared in `.gitmodules`; adding a new submodule is
a separate Git maintenance operation.

### Prerequisites

Expand Down Expand Up @@ -371,7 +388,7 @@ Wrapper generators produce different C++ files by design:
### Python package install workflow

Python package metadata is owned by `python/pyproject.toml.in` and configured
into `python/pyproject.toml` when Python wrapping is requested.
into `<build>/python/pyproject.toml` when Python wrapping is requested.
The optional `setup.py.in` augments installation behavior without duplicating
package name/version metadata.

Expand All @@ -388,18 +405,18 @@ The checked-in `python/<project>/__init__.py` is the public package entrypoint:
- `HAS_WRAPPER` is `False` when the pure-Python package imports without the wrapper.
- `WRAPPER_IMPORT_ERROR` stores the wrapper import exception when fallback is active.

When Python wrapping is requested, the source package becomes the public install
entrypoint. CMake updates it with:
When Python wrapping is requested, CMake assembles a disposable package root
without updating the source checkout:

- generated `python/pyproject.toml`
- generated `python/setup.py`
- build-time `python/<project>/_wrapper_build.py` linking the latest
- generated `<build>/python/pyproject.toml`
- generated `<build>/python/setup.py`
- build-time `<build>/python/<project>/_wrapper_build.py` linking the latest
successfully staged wrapper configuration

Install from the source Python package directory:
Install from the configured build package directory:

```bash
cd python
cd build/python
python -m pip install .
```

Expand Down Expand Up @@ -559,6 +576,41 @@ The image in `.devcontainer/Dockerfile` can be built and used outside the DevCon
docker build --build-arg INSTALL_CUDA=on --build-arg CUDA_VERSION=12.9 -t my-dev .devcontainer
```

Command mode runs with the host numeric UID and GID and uses `/tmp` as its
writable home. Files created through the `/workspace` bind mount therefore
remain owned by the host user instead of root. Rootless Podman additionally
uses its `keep-id` user namespace.

### Attach VS Code to a launcher-managed container

Use `--vscode` to start a stable container before selecting
`Dev Containers: Attach to Running Container...`:

```bash
./run_in_container.sh --vscode --engine podman
```

Attachment mode mounts the repository under `/workspaces/<repository>`,
preserves bind-mount ownership, and forwards a live SSH-agent socket when one
is available. The launcher prints the `workspaceFolder` and `remoteUser`
values for the first attachment. This mode builds the Dockerfile directly, so
features declared only in `devcontainer.json` are not applied; use the normal
Dev Containers create/reopen workflow when those features are required.
Docker attachment mode also requires the image's `vscode` UID and GID to match
the host user; the launcher rejects a mismatch rather than creating files with
ambiguous ownership.

Expose a host MATLAB installation to wrapper configuration with:

```bash
./run_in_container.sh --vscode --engine podman \
--matlab-root /usr/local/MATLAB/R2024b
```

The installation is mounted read-only at the same absolute path and exported
as `MATLAB_ROOT_DIR`. Because mounts are fixed at container creation, stop and
recreate an existing attachment container before changing the MATLAB root.

---

## Documentation
Expand Down
Loading
Loading