Skip to content

Port colcon-python-project wheel installation & uninstallation utilities to colcon-core - #741

Open
KmoM88 wants to merge 7 commits into
colcon:masterfrom
KmoM88:federico-rossi/install_wheel
Open

Port colcon-python-project wheel installation & uninstallation utilities to colcon-core#741
KmoM88 wants to merge 7 commits into
colcon:masterfrom
KmoM88:federico-rossi/install_wheel

Conversation

@KmoM88

@KmoM88 KmoM88 commented Jun 26, 2026

Copy link
Copy Markdown

Overview

This PR ports the core Python wheel installation and uninstallation engine from the prototype extension colcon-python-project into colcon-core.

Previously, colcon-core only supported legacy setuptools-centric installations that required spawning setup.py scripts. As Python platforms move away from direct setup.py invocations in favor of standard PEP 517/518 packaging structures, this PR introduces the native utilities required to install PEP 427 wheel archives (.whl) and safely clean up existing package distributions from the installation prefix.

By implementing this metadata-driven layout manager directly inside colcon-core, we establish the foundation for native PEP 517 build task execution and modern dependency resolution.

Key Features

1. Metadata-Driven Uninstallation (remove_distributions)

  • Live Discovery: Instead of depending on ephemeral local build-cache files (such as install.log or .egg-info symlinks in the build folder), the uninstallation utility queries the target install_base prefix using InstalledDistribution.discover.
  • Clean Deletions: Traverses the registered package files and deletes them from the filesystem. It unlinks residual .egg-link files and recursively removes any empty parent directories up to the install_base.
  • Platform Resilience: Deletions are guarded against file lock exceptions (particularly on Windows) by wrapping unlink and rmdir operations in exceptions handlers, logging warnings instead of crashing.

2. Wheel Layout Extraction & Mapping (install_wheel)

  • Wheel Extraction: Extracts standard PEP 427 wheel packages and determines the target library destination (purelib or platlib) based on Root-Is-Purelib metadata inside the wheel's WHEEL file.
  • Data-Files Mapping: Decodes .data/ directory segments (e.g. package-1.0.data/data/...) inside the wheel ZIP, translating them to their platform-agnostic target locations dynamically via _get_install_path.
  • Console Scripts Wrap: Parses entry point mappings defined in entry_points.txt and uses distlib.scripts.ScriptMaker to generate executable script wrappers under the scripts folder.
  • Standard Packaging Metadata: Writes the PEP 376 compliant metadata files:
    • INSTALLER: Marked as installed by colcon-core.
    • RECORD: Finalizes and writes the cryptographic hash signatures (sha256) and sizes of all extracted and generated files.

Verification & Testing

This PR introduces comprehensive unit tests under the test suite:

  • test_remove_wheel.py:
    • Sets up a mock workspace and dynamically copies distributions (typical-dist-info, typical-egg-info, typical-egg-link).
    • Asserts that remove_distributions successfully uninstalls standard, legacy, and egg-linked packages, deletes all associated files, and cleans up empty parent directories while leaving non-empty folders untouched.
  • test_install_wheel.py:
    • Programmatically mocks pure Python wheels, platform-specific wheels, wheels with console scripts, and wheels with data files.
    • Asserts that install_wheel extracts files into correct library paths, builds CLI scripts, maps data files to share/, and writes valid INSTALLER and RECORD files.
    • Verifies that the installer cleans up older package versions prior to installation.

Automated Tests

Run the newly added test suites locally:

pytest test/test_remove_wheel.py test/test_install_wheel.py

@KmoM88
KmoM88 marked this pull request as draft June 26, 2026 19:27
@cottsay
cottsay marked this pull request as ready for review June 30, 2026 14:40
@cottsay cottsay self-assigned this Jun 30, 2026
@cottsay
cottsay self-requested a review July 17, 2026 17:02
@codecov

codecov Bot commented Jul 21, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.00000% with 15 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.69%. Comparing base (dbc75ee) to head (8d3b4de).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
colcon_core/python_project/wheel.py 88.00% 8 Missing and 7 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #741      +/-   ##
==========================================
+ Coverage   87.66%   87.69%   +0.03%     
==========================================
  Files          74       75       +1     
  Lines        4442     4567     +125     
  Branches      771      795      +24     
==========================================
+ Hits         3894     4005     +111     
- Misses        433      441       +8     
- Partials      115      121       +6     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@cottsay cottsay left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll probably have more feedback but I've been sitting on these two comments for a while so I thought I'd get them posted so they can be discussed now.

Hoping I'll have time to finish the review soon 🤞

Comment thread colcon_core/python_project/wheel.py Outdated
Comment on lines +69 to +83
# Explicitly track and clean up residual egg-links in case the
# distribution files list did not fully cover them.
for libdir in libdirs:
for n in (name, name.replace('_', '-')):
egg_link = libdir / f'{n}.egg-link'
if egg_link.is_file():
logger.debug(f'Removing egg-link {egg_link}')
try:
egg_link.unlink()
deleted_files.append(egg_link)
except OSError as e:
logger.warning(
f"Could not remove egg-link '{egg_link}': {e}"
)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be handled already by colcon_core.python_project.distribution.InstalledDistribution

Suggested change
# Explicitly track and clean up residual egg-links in case the
# distribution files list did not fully cover them.
for libdir in libdirs:
for n in (name, name.replace('_', '-')):
egg_link = libdir / f'{n}.egg-link'
if egg_link.is_file():
logger.debug(f'Removing egg-link {egg_link}')
try:
egg_link.unlink()
deleted_files.append(egg_link)
except OSError as e:
logger.warning(
f"Could not remove egg-link '{egg_link}': {e}"
)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Removed the redundant .egg-link deletion block since the link path is already tracked and uninstalled by InstalledDistribution.

yield base.joinpath(*rel.parts[:i])


def _get_script_maker(script_dir, dry_run=False):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like the counterpart in colcon_core.python_project.distribution, we should cache these. (needs from functools import lru_cache as well)

Suggested change
def _get_script_maker(script_dir, dry_run=False):
@lru_cache(maxsize=32)
def _get_script_maker(script_dir, dry_run=False):

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Imported lru_cache and decorated _get_script_maker with @lru_cache(maxsize=32).

@KmoM88
KmoM88 requested a review from cottsay August 6, 2026 16:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants