You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Hi everyone, after some investigation/experimentation I have compiled a detailed, phased proposal to bring native PEP 517/518/621/660 support to colcon. As platforms move away from direct setup.py invocations, modernizing python builds is essential. This proposal aims to bridge the standards gap while fully preserving backward compatibility, bootstrapping safety, and the rapid iteration loops expected. I would love to get your thoughts and feedback on the proposed stages and data-linking designs below! I don't claim to be the foremost expert on colcon and this proposal was done with gemini as a help to understand some internal logic, so please feel free to call out any errors or oversights in my analysis, your feedback is incredibly welcome!
1. Context & Legacy Workflow Analysis
1.1 setup.cfg Package Identification and Discovery
colcon-core natively handles Python package discovery and metadata extraction through static parsing of configuration files. The core modules colcon_core.package_identification.python and colcon_core.package_augmentation.python expect the presence of a standard setup.cfg file along with a companion setup.py shim.
Discovery: The discovery step imports read_configuration from setuptools.config.setupcfg (with fallbacks to setuptools.config depending on the installed version of setuptools) to parse the INI-style settings in setup.cfg directly on the main thread, resolving the package type as python and extracting its name.
Dependency Resolution: Augmentation maps configuration keys in setup.cfg to unified colcon dependencies:
setup_requires is mapped to build dependencies.
install_requires is mapped to run dependencies.
tests_require / extras_require (specifically test-related keys) are mapped to test dependencies.
The Static vs. Dynamic Dichotomy: This core design enforces a strict separation between static configuration (fast, safe, and easily parsed on the main thread without side effects) and dynamic script execution (which can contain arbitrary Python code). Consequently, colcon-core natively ignores packages that rely on custom dynamic code inside setup.py files. All dynamic configurations are offloaded to the external colcon-python-setup-py extension, which executes the script in an isolated subprocess running a dry-run invocation of distutils.core.run_setup to prevent execution of installation routines.
1.2 Legacy Build Execution
During the execution phase, PythonBuildTask in colcon_core.task.python.build spawns direct command-line invocations of the setup.py script:
Static Install: Spawns python setup.py build followed by python setup.py install --single-version-externally-managed.
Symlink (Editable) Install: Mirrors the module directories from the source tree into the build base using physical OS symlinks, and then runs python setup.py develop --editable pointing to the build base directory. This is augmented by symlink_data (a custom distutils command class) to symlink non-Python data files directly into the target prefix (install/).
During a legacy editable installation (--symlink-install), the build task coordinates three distinct categories of filesystem link operations:
temp_symlinks (Build-Time Source Mirroring): Temporary filesystem links created in the build base directory (e.g. /build/my_package/my_module$\rightarrow$/src/my_package/my_module). These mirror the source package modules so setuptools can execute the develop command inside the isolated build space, keeping the raw source repository clean of .egg-info directories and caches. These symlinks are unlinked immediately after the subprocess execution completes.
Editable Install Link (Python .pth Link): A persistent link registered in the target python environment (e.g. /install/my_package/lib/python3.10/site-packages/easy-install.pth) containing the path to the build directory (e.g. /build/my_package), directing Python's sys.path resolver to load modules dynamically.
symlink_data (Target Data-File Symlinking): Persistent links created inside the target installation share directory pointing back to assets in the source tree (e.g. /install/my_package/share/my_package/launch/my_launch.py$\rightarrow$/src/my_package/launch/my_launch.py). Injected via a custom distutils command class, it bypasses standard static data copying so launch configurations, models, and markers remain dynamically editable.
Symlink Category
Scope / Created In
Target / Points To
Lifetime
Operational Purpose
temp_symlinks
Temporary Build Base (build/)
Source Tree (src/)
Temporary (Build-time only)
Isolates setuptools execution to keep source repositories clean of build artifacts.
Editable Install Link
Target site-packages
Temporary Build Base (build/)
Persistent
Registers the package path inside the Python interpreter's sys.path.
To allow developing colcon using its own source repositories without depending on pre-installed instances, colcon employs a bootstrapping script at src/colcon-core/bin/colcon.
Because colcon is not yet installed in the development virtual environment, standard importlib.metadata entry point hooks are unavailable.
To bypass this, bin/colcon manipulates sys.path and overrides the module-level function load_extension_points in the colcon_core.extension_point module. It directly registers the local checkouts of verbs, discovery extensions, and task classes.
During this bootstrapping phase, colcon-core relies entirely on its native static setup.cfg package discovery logic to identify, sort, and build its own checkouts from source.
1.4 Why Upgrade to standard PEP 517/518/621/660 Environments?
Maintaining this legacy model poses immediate operational risks to the colcon and ROS developer communities:
Upstream Deprecations: Mainstream Python packaging tools are deprecating direct invocations of setup.py install and setup.py develop. Modern Linux distributions are removing legacy macros, rendering support for legacy setup.py installations in their package managers incompatible with newer Python interpreters.
Standards Adoption: Modern Python developers are adopting PEP 517/518 packaging setups using standard backends like Hatchling or Flit. Currently, colcon cannot compile these packages natively.
Modern Metadata (PEP 621): Standards specify that package metadata and dependencies should be statically declared in pyproject.toml under the [project] and [build-system] tables. colcon must be updated to parse this modern, standardized layout.
The colcon-core PR #732 introduces the foundational building blocks inside colcon_core/python_project/ to support modern PEP 517 build backend hooks:
AsyncHookCaller: Implements the subprocess execution transport needed to interface with standard build backends (e.g. setuptools.build_meta, hatchling.build, flit_core.buildapi) defined in pyproject.toml.
Subprocess IPC Channels: Uses unidirectional OS pipes and standard pickle serialization to transmit kwargs and return structures (like requirement arrays) between the parent colcon thread and the isolated child script _call_hook.py. This ensures high reliability on both Linux and Windows (using msvcrt handles).
TOML Parsing Gating (PR Add function for reading and caching PEP 518 spec #668 ): Introduces a unified TOML parser loader (toml_loads in colcon_core/python_project/spec.py). It dynamically queries the python environment, using the standard library's tomllib in Python 3.11+ while safely falling back to third-party packages tomli or toml in older runtime environments.
HookCallerDecoratorExtensionPoint: Registers a new extension point allowing downstream plugins to intercept and wrap hook invocations, enabling backend-specific configurations.
While PR #732 establishes the underlying hook caller execution and decorator registries, it does not integrate them into the colcon runtime workflow.
As it stands, PR #732 is purely foundational and does not modify:
Package Identification: Core discovery still ignores packages that lack a setup.cfg/setup.py pair.
Package Augmentation: Core still parses dependencies exclusively from setup.cfg fields.
Build Task Execution: PythonBuildTask still executes legacy setup.py command arrays, causing builds of pure PEP 517 packages to fail.
3. Key Migration Pitfall: Editable Installs and the Data File Gap
The most significant risk during this migration revolves around the --symlink-install (editable) workflow, which is heavily relied upon by the ROS developer community.
Editable Package Installation
│
┌──────────────────────┴──────────────────────┐
▼ ▼
[Python Source Code] [Non-Python Data Files]
(e.g., my_node/subscriber.py) (e.g., launch.py, package.xml)
│ │
Dynamic Linking Supported! Static Copies Packaged!
Resolved by PEP 660 (.pth) ambiguous in PEP 660 standard.
│ │
▼ ▼
Developer edits take effect Developer edits are lost
without rebuilding. until next manual build.
3.1 Python Code Editability under PEP 660
PEP 660 establishes standardized hooks (build_editable) for generating editable wheels. Backends successfully resolve Python code editability by inserting redirection files (such as .pth files pointing to the source directory) or dynamic path hooks into sys.path. Changes to Python source modules take effect instantly without rebuilding.
3.2 The Non-Python Data File Specification Gap & Metadata Loss
The PEP 660 standard is designed with Python source executables in mind. It does not specify any standard mechanism to dynamically link or live-update non-Python data files (such as XML/YAML launch files, URDF robot description models, shared parameters, or custom package index markers).
The Backend Fallback: Standard modern backends (like Setuptools, Hatch, or Flit) treat these data files as static resources. When compiling an editable wheel, they package these shared resources directly inside the wheel's ZIP archive.
The PEP 427 ZIP Packaging Barrier: According to the standard wheel packaging format (PEP 427), the packaging backend translates files declared in data_files and places them under a dedicated <package>-<version>.data/data/ or share/ directory structure inside the ZIP archive. During this compilation, all context regarding the original source repository paths is completely lost. The ZIP metadata only preserves the target installation path.
The Static Installation: When colcon installs the wheel, the installer tool unzips the archive and copies the data files statically from .data/data/ into the target directory (install/share/...). Since the installation engine receives the wheel as a standalone ZIP archive, it is structurally unable to trace these files back to the developer's original source repository.
The Broken Workflow: Any edits made to a launch file or configuration script inside the source directory will not take effect inside the active workspace environment. The workspace continues to execute the stale static copy inside the install space, forcing developers to run colcon build after every single non-Python file modification. This introduces substantial friction and destroys the rapid iteration loops expected by developers.
4. Proposed Phases of Development & Adoption
To execute this migration without breaking the active ROS 2 and wider developer communities, development and adoption must be structured in sequential phases that guarantee backward compatibility and bootstrapping safety at every single step.
Phase 1: Dual Identification, Augmentation, and Gating
Objective: Update colcon-core to discover and augment modern PEP 517/518 packages, while maintaining absolute backward compatibility with legacy setup.cfg packages.
Implementation Steps:
Update PythonPackageIdentification to inspect the directory for pyproject.toml. If found, parse it using standard TOML loaders. If the [build-system] or [project] tables are defined, identify it as a python package.
Update PythonPackageAugmentation to resolve modern PEP 518/621 dependency groups (mapping build-system.requires to build dependencies, project.dependencies to run dependencies, and project.optional-dependencies.test to test dependencies).
The Legacy Feature Gate: Introduce an environment variable (COLCON_ENABLE_LEGACY_SETUP_CFG, defaulting to true). If set to false, the legacy setup.cfg/setup.py static identification fallback is bypassed. This provides developers a clean path to opt-out and test modern-only environments.
Bootstrapping Safety: Ensure that the developer bootstrap script (bin/colcon) is updated to register the new PEP 517 identification and augmentation classes alongside the legacy classes. This guarantees that colcon-core can safely discover, sort, and build its own source tree during development bootstrapping.
Phase 2: Standard PEP 517 Build Task Adoption
Objective: Implement standard, backend-agnostic wheel compilation and target installation inside the core build task, bypassing legacy commands.
Implementation Steps:
Refactor PythonBuildTask to branch into a PEP 517 execution path (_build_pep517) when pyproject.toml is present and setup.py is absent.
Initialize the build backend hook caller. Invoke the build_wheel hook of the declared backend to compile the package into a standard wheel (.whl) within the temporary <build_base>/wheel workspace directory.
Predictable Target Installations: Execute the active virtual environment's interpreter inside a subprocess to target-install the wheel:
Why --target is highly preferred over --prefix: Under standard --prefix installations, installers are subject to operating system and packaging scheme overrides (such as Ubuntu/Debian overriding python install layouts to place files in dist-packages instead of site-packages). By redirecting standard installations with the --target flag, colcon completely bypasses these platform-specific layout overrides, ensuring that files are unpacked into a predictable, platform-agnostic directory structure which can be cleanly sourced by environmental setup scripts.
Entry Point Script Wrapping: Parse the unpacked package's .dist-info/entry_points.txt metadata and dynamically generate standard executable python scripts inside the target bin/ directory with 0o755 permissions, ensuring console script executables work natively.
Phase 3: PEP 660 Editable Symlink Restorations
Objective: Integrate build_editable editable installation hooks and restore the dynamic --symlink-install experience for non-Python data files.
Implementation Steps:
Update the build task to check if the backend implements the build_editable hook. If not, log a warning and fall back to a standard wheel build.
If editable installations are requested and supported, invoke build_editable to get an editable wheel, and install it.
Deploy a symlinking strategy (such as backend-specific decorators or frontend out-of-band linking) to replace the statically copied files inside install/share/ with dynamic symlinks pointing straight back to the original files in the source tree.
Objective: Purge all historical configuration code from colcon-core, rendering the core package 100% standards-compliant and highly maintainable.
Implementation Steps:
Create a new, retroactive standalone extension package: colcon-python-setup-cfg.
Extract the static setup.cfg package identification, augmentation, and dependency parsing logic out of colcon-core and place it inside colcon-python-setup-cfg.
Remove the COLCON_ENABLE_LEGACY_SETUP_CFG feature gate and all associated legacy static code from colcon-core.
At this point, colcon-core becomes purely standards-compliant (PEP 517/518/621/660). Users requiring legacy configurations can seamlessly maintain compatibility by installing the standalone colcon-python-setup-py and the new colcon-python-setup-cfg extensions.
5. Proposed Build Pipeline Modifications
5.1 Package Identification & Augmentation
The package identification step must inspect packages in priority order:
Check for Modern Specification: If pyproject.toml is present, parse the file. Verify that a [build-system] table is present. Extract the package name from the [project] static metadata table, and mark the descriptor type as python. Caches the parsed TOML structure inside desc.metadata['python_project_spec'] to prevent redundant disk I/O in the downstream augmentation step.
Legacy Fallback: If pyproject.toml is absent, and the environment gate COLCON_ENABLE_LEGACY_SETUP_CFG is active, check for the presence of the setup.cfg/setup.py pair and fall back to legacy static parsing.
The package augmentation step must extract dependencies in a unified format:
Static modern resolution: If the metadata was pre-cached from pyproject.toml, read and resolve standard dependency arrays under the [project] table directly on the parent thread.
Dynamic hook fallback: If dependencies are declared as dynamic (e.g. dynamic = ["dependencies"] under the [project] table), use the AsyncHookCaller subprocess to invoke get_requires_for_build_wheel to retrieve requirements dynamically.
5.2 Build Task (PythonBuildTask)
The build task execution block will be updated to execute the following logic:
6. Options for Implementing Editable Data-File Linking under PEP 660
To resolve the data file specification gap during Phase 3, two distinct architectural options can be considered.
Option A: Hook Caller Decorator Interception
This option utilizes the HookCallerDecoratorExtensionPoint plugin registry introduced in PR #732 to intercept the backend.
Implementation: A decorator tailored specifically for the target backend (e.g. setuptools.build_meta) intercepts the build_editable execution. It inspects the package's configuration files to parse source-to-destination mappings for data_files. Once the wheel is generated and installed, the decorator intercepts the install directory, deletes the static data files, and replaces them with direct OS symlinks pointing back to the source tree.
Pros: Highly modular. Avoids polluting colcon-core with backend-specific code. Standard Python installers remain unaffected.
Cons: Backend-specific. A decorator must be written and maintained for every PEP 517 backend (Setuptools, Hatch, Flit) used in the workspace. Introspecting non-setuptools configs to resolve original source paths might be complex.
Option B: Frontend-Driven Out-of-Band Symlinking
This option resolves symlinking entirely within colcon's core build task, completely independent of the backend hook execution.
Implementation: During metadata augmentation, colcon parses the package's metadata and caches a backend-agnostic, unified mapping of all declared data files in PackageDescriptor.metadata. During build execution, the task runs standard PEP 660 editable installations and target-installs the wheel. In the post-install phase, the task reads the cached mapping, locates the target files in the install space, deletes the static copies, and replaces them with OS symlinks pointing back to the recorded source files.
Pros: Unified, single-point implementation inside colcon-core. The backend remains a clean "black box" during hook execution.
Cons: Shifting the responsibility of parsing proprietary backend TOML configurations (Hatchling, Flit, etc.) to the colcon core discovery system increases code complexity.
Some planning and milestones surely still need to be discussed, but this summary should bring everyone up to speed. I welcome any feedback, corrections, thoughts, or suggestions on the proposed next phases.
Hi everyone, after some investigation/experimentation I have compiled a detailed, phased proposal to bring native PEP 517/518/621/660 support to
colcon. As platforms move away from directsetup.pyinvocations, modernizing python builds is essential. This proposal aims to bridge the standards gap while fully preserving backward compatibility, bootstrapping safety, and the rapid iteration loops expected. I would love to get your thoughts and feedback on the proposed stages and data-linking designs below! I don't claim to be the foremost expert oncolconand this proposal was done with gemini as a help to understand some internal logic, so please feel free to call out any errors or oversights in my analysis, your feedback is incredibly welcome!1. Context & Legacy Workflow Analysis
1.1
setup.cfgPackage Identification and Discoverycolcon-corenatively handles Python package discovery and metadata extraction through static parsing of configuration files. The core modulescolcon_core.package_identification.pythonandcolcon_core.package_augmentation.pythonexpect the presence of a standardsetup.cfgfile along with a companionsetup.pyshim.read_configurationfromsetuptools.config.setupcfg(with fallbacks tosetuptools.configdepending on the installed version ofsetuptools) to parse the INI-style settings insetup.cfgdirectly on the main thread, resolving the package type aspythonand extracting its name.setup.cfgto unifiedcolcondependencies:setup_requiresis mapped tobuilddependencies.install_requiresis mapped torundependencies.tests_require/extras_require(specifically test-related keys) are mapped totestdependencies.colcon-corenatively ignores packages that rely on custom dynamic code insidesetup.pyfiles. All dynamic configurations are offloaded to the externalcolcon-python-setup-pyextension, which executes the script in an isolated subprocess running a dry-run invocation ofdistutils.core.run_setupto prevent execution of installation routines.1.2 Legacy Build Execution
During the execution phase,
PythonBuildTaskincolcon_core.task.python.buildspawns direct command-line invocations of thesetup.pyscript:python setup.py buildfollowed bypython setup.py install --single-version-externally-managed.python setup.py develop --editablepointing to the build base directory. This is augmented bysymlink_data(a custom distutils command class) to symlink non-Python data files directly into the target prefix (install/).During a legacy editable installation (
--symlink-install), the build task coordinates three distinct categories of filesystem link operations:temp_symlinks(Build-Time Source Mirroring): Temporary filesystem links created in the build base directory (e.g./build/my_package/my_module/src/my_package/my_module). These mirror the source package modules sosetuptoolscan execute thedevelopcommand inside the isolated build space, keeping the raw source repository clean of.egg-infodirectories and caches. These symlinks are unlinked immediately after the subprocess execution completes..pthLink): A persistent link registered in the target python environment (e.g./install/my_package/lib/python3.10/site-packages/easy-install.pth) containing the path to the build directory (e.g./build/my_package), directing Python'ssys.pathresolver to load modules dynamically.symlink_data(Target Data-File Symlinking): Persistent links created inside the target installation share directory pointing back to assets in the source tree (e.g./install/my_package/share/my_package/launch/my_launch.py/src/my_package/launch/my_launch.py). Injected via a custom distutils command class, it bypasses standard static data copying so launch configurations, models, and markers remain dynamically editable.temp_symlinksbuild/)src/)setuptoolsexecution to keep source repositories clean of build artifacts.build/)sys.path.symlink_datainstall/share/)src/)1.3 The Bootstrapping Mechanism
To allow developing
colconusing its own source repositories without depending on pre-installed instances,colconemploys a bootstrapping script atsrc/colcon-core/bin/colcon.colconis not yet installed in the development virtual environment, standardimportlib.metadataentry point hooks are unavailable.bin/colconmanipulatessys.pathand overrides the module-level functionload_extension_pointsin thecolcon_core.extension_pointmodule. It directly registers the local checkouts of verbs, discovery extensions, and task classes.colcon-corerelies entirely on its native staticsetup.cfgpackage discovery logic to identify, sort, and build its own checkouts from source.1.4 Why Upgrade to standard PEP 517/518/621/660 Environments?
Maintaining this legacy model poses immediate operational risks to the
colconand ROS developer communities:setup.py installandsetup.py develop. Modern Linux distributions are removing legacy macros, rendering support for legacysetup.pyinstallations in their package managers incompatible with newer Python interpreters.HatchlingorFlit. Currently,colconcannot compile these packages natively.pyproject.tomlunder the[project]and[build-system]tables.colconmust be updated to parse this modern, standardized layout.2. Current Status of the Upstream Migration
2.1 Upstream PR #732: Core Contributions
The colcon-core PR #732 introduces the foundational building blocks inside
colcon_core/python_project/to support modern PEP 517 build backend hooks:AsyncHookCaller: Implements the subprocess execution transport needed to interface with standard build backends (e.g.setuptools.build_meta,hatchling.build,flit_core.buildapi) defined inpyproject.toml.pickleserialization to transmitkwargsand return structures (like requirement arrays) between the parentcolconthread and the isolated child script_call_hook.py. This ensures high reliability on both Linux and Windows (usingmsvcrthandles).toml_loadsincolcon_core/python_project/spec.py). It dynamically queries the python environment, using the standard library'stomllibin Python 3.11+ while safely falling back to third-party packagestomliortomlin older runtime environments.HookCallerDecoratorExtensionPoint: Registers a new extension point allowing downstream plugins to intercept and wrap hook invocations, enabling backend-specific configurations.2.2 What comes after PR #732?
While PR #732 establishes the underlying hook caller execution and decorator registries, it does not integrate them into the colcon runtime workflow.
As it stands, PR #732 is purely foundational and does not modify:
setup.cfg/setup.pypair.setup.cfgfields.PythonBuildTaskstill executes legacysetup.pycommand arrays, causing builds of pure PEP 517 packages to fail.3. Key Migration Pitfall: Editable Installs and the Data File Gap
The most significant risk during this migration revolves around the
--symlink-install(editable) workflow, which is heavily relied upon by the ROS developer community.3.1 Python Code Editability under PEP 660
PEP 660 establishes standardized hooks (
build_editable) for generating editable wheels. Backends successfully resolve Python code editability by inserting redirection files (such as.pthfiles pointing to the source directory) or dynamic path hooks intosys.path. Changes to Python source modules take effect instantly without rebuilding.3.2 The Non-Python Data File Specification Gap & Metadata Loss
The PEP 660 standard is designed with Python source executables in mind. It does not specify any standard mechanism to dynamically link or live-update non-Python data files (such as XML/YAML launch files, URDF robot description models, shared parameters, or custom package index markers).
data_filesand places them under a dedicated<package>-<version>.data/data/orshare/directory structure inside the ZIP archive. During this compilation, all context regarding the original source repository paths is completely lost. The ZIP metadata only preserves the target installation path.colconinstalls the wheel, the installer tool unzips the archive and copies the data files statically from.data/data/into the target directory (install/share/...). Since the installation engine receives the wheel as a standalone ZIP archive, it is structurally unable to trace these files back to the developer's original source repository.colcon buildafter every single non-Python file modification. This introduces substantial friction and destroys the rapid iteration loops expected by developers.4. Proposed Phases of Development & Adoption
To execute this migration without breaking the active ROS 2 and wider developer communities, development and adoption must be structured in sequential phases that guarantee backward compatibility and bootstrapping safety at every single step.
Phase 1: Dual Identification, Augmentation, and Gating
colcon-coreto discover and augment modern PEP 517/518 packages, while maintaining absolute backward compatibility with legacysetup.cfgpackages.PythonPackageIdentificationto inspect the directory forpyproject.toml. If found, parse it using standard TOML loaders. If the[build-system]or[project]tables are defined, identify it as apythonpackage.PythonPackageAugmentationto resolve modern PEP 518/621 dependency groups (mappingbuild-system.requirestobuilddependencies,project.dependenciestorundependencies, andproject.optional-dependencies.testtotestdependencies).COLCON_ENABLE_LEGACY_SETUP_CFG, defaulting totrue). If set tofalse, the legacysetup.cfg/setup.pystatic identification fallback is bypassed. This provides developers a clean path to opt-out and test modern-only environments.bin/colcon) is updated to register the new PEP 517 identification and augmentation classes alongside the legacy classes. This guarantees thatcolcon-corecan safely discover, sort, and build its own source tree during development bootstrapping.Phase 2: Standard PEP 517 Build Task Adoption
PythonBuildTaskto branch into a PEP 517 execution path (_build_pep517) whenpyproject.tomlis present andsetup.pyis absent.build_wheelhook of the declared backend to compile the package into a standard wheel (.whl) within the temporary<build_base>/wheelworkspace directory.--targetis highly preferred over--prefix: Under standard--prefixinstallations, installers are subject to operating system and packaging scheme overrides (such as Ubuntu/Debian overriding python install layouts to place files indist-packagesinstead ofsite-packages). By redirecting standard installations with the--targetflag,colconcompletely bypasses these platform-specific layout overrides, ensuring that files are unpacked into a predictable, platform-agnostic directory structure which can be cleanly sourced by environmental setup scripts..dist-info/entry_points.txtmetadata and dynamically generate standard executable python scripts inside the targetbin/directory with0o755permissions, ensuring console script executables work natively.Phase 3: PEP 660 Editable Symlink Restorations
build_editableeditable installation hooks and restore the dynamic--symlink-installexperience for non-Python data files.build_editablehook. If not, log a warning and fall back to a standard wheel build.build_editableto get an editable wheel, and install it.install/share/with dynamic symlinks pointing straight back to the original files in the source tree.Phase 4: Strict Core Separation & Legacy Extension Extraction (Target State)
colcon-core, rendering the core package 100% standards-compliant and highly maintainable.colcon-python-setup-cfg.setup.cfgpackage identification, augmentation, and dependency parsing logic out ofcolcon-coreand place it insidecolcon-python-setup-cfg.COLCON_ENABLE_LEGACY_SETUP_CFGfeature gate and all associated legacy static code fromcolcon-core.colcon-corebecomes purely standards-compliant (PEP 517/518/621/660). Users requiring legacy configurations can seamlessly maintain compatibility by installing the standalonecolcon-python-setup-pyand the newcolcon-python-setup-cfgextensions.5. Proposed Build Pipeline Modifications
5.1 Package Identification & Augmentation
The package identification step must inspect packages in priority order:
pyproject.tomlis present, parse the file. Verify that a[build-system]table is present. Extract the package name from the[project]static metadata table, and mark the descriptor type aspython. Caches the parsed TOML structure insidedesc.metadata['python_project_spec']to prevent redundant disk I/O in the downstream augmentation step.pyproject.tomlis absent, and the environment gateCOLCON_ENABLE_LEGACY_SETUP_CFGis active, check for the presence of thesetup.cfg/setup.pypair and fall back to legacy static parsing.The package augmentation step must extract dependencies in a unified format:
pyproject.toml, read and resolve standard dependency arrays under the[project]table directly on the parent thread.dynamic = ["dependencies"]under the[project]table), use theAsyncHookCallersubprocess to invokeget_requires_for_build_wheelto retrieve requirements dynamically.5.2 Build Task (
PythonBuildTask)The build task execution block will be updated to execute the following logic:
graph TD Start[Start Build Task] --> Check{Is pyproject.toml present and setup.py absent?} Check --> |Yes| PEP517[PEP 517 Build Pipeline] Check --> |No| Legacy[Legacy setup.cfg Build Pipeline] PEP517 --> Hook[Invoke backend build_wheel hook via AsyncHookCaller] Hook --> Install[Target-install wheel into install_base via pip subprocess] Install --> Scripts[Parse .dist-info/entry_points.txt and generate bin/ console wrappers] Scripts --> Environment[Create colcon environment hooks & setup scripts] Legacy --> RunLegacy[Spawn setup.py build, install, or develop] RunLegacy --> Environment Environment --> End[End Build Task]6. Options for Implementing Editable Data-File Linking under PEP 660
To resolve the data file specification gap during Phase 3, two distinct architectural options can be considered.
Option A: Hook Caller Decorator Interception
This option utilizes the
HookCallerDecoratorExtensionPointplugin registry introduced in PR #732 to intercept the backend.setuptools.build_meta) intercepts thebuild_editableexecution. It inspects the package's configuration files to parse source-to-destination mappings fordata_files. Once the wheel is generated and installed, the decorator intercepts the install directory, deletes the static data files, and replaces them with direct OS symlinks pointing back to the source tree.colcon-corewith backend-specific code. Standard Python installers remain unaffected.Option B: Frontend-Driven Out-of-Band Symlinking
This option resolves symlinking entirely within
colcon's core build task, completely independent of the backend hook execution.colconparses the package's metadata and caches a backend-agnostic, unified mapping of all declared data files inPackageDescriptor.metadata. During build execution, the task runs standard PEP 660 editable installations and target-installs the wheel. In the post-install phase, the task reads the cached mapping, locates the target files in the install space, deletes the static copies, and replaces them with OS symlinks pointing back to the recorded source files.colcon-core. The backend remains a clean "black box" during hook execution.colconcore discovery system increases code complexity.Some planning and milestones surely still need to be discussed, but this summary should bring everyone up to speed. I welcome any feedback, corrections, thoughts, or suggestions on the proposed next phases.