colcon-uv 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,165 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ pip-wheel-metadata/
24
+ share/python-wheels/
25
+ *.egg-info/
26
+ .installed.cfg
27
+ *.egg
28
+ MANIFEST
29
+
30
+ # PyInstaller
31
+ # Usually these files are written by a python script from a template
32
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
33
+ *.manifest
34
+ *.spec
35
+
36
+ # Installer logs
37
+ pip-log.txt
38
+ pip-delete-this-directory.txt
39
+
40
+ # Unit test / coverage reports
41
+ htmlcov/
42
+ .tox/
43
+ .nox/
44
+ .coverage
45
+ .coverage.*
46
+ .cache
47
+ nosetests.xml
48
+ coverage.xml
49
+ *.cover
50
+ *.py,cover
51
+ .hypothesis/
52
+ .pytest_cache/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ target/
76
+
77
+ # Jupyter Notebook
78
+ .ipynb_checkpoints
79
+
80
+ # IPython
81
+ profile_default/
82
+ ipython_config.py
83
+
84
+ # pyenv
85
+ .python-version
86
+
87
+ # pipenv
88
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
90
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
91
+ # install all needed dependencies.
92
+ #Pipfile.lock
93
+
94
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow
95
+ __pypackages__/
96
+
97
+ # Celery stuff
98
+ celerybeat-schedule
99
+ celerybeat.pid
100
+
101
+ # SageMath parsed files
102
+ *.sage.py
103
+
104
+ # Environments
105
+ .env
106
+ .venv
107
+ env/
108
+ venv/
109
+ ENV/
110
+ env.bak/
111
+ venv.bak/
112
+
113
+ # Spyder project settings
114
+ .spyderproject
115
+ .spyproject
116
+
117
+ # Rope project settings
118
+ .ropeproject
119
+
120
+ # mkdocs documentation
121
+ /site
122
+
123
+ # mypy
124
+ .mypy_cache/
125
+ .dmypy.json
126
+ dmypy.json
127
+
128
+ # Pyre type checker
129
+ .pyre/
130
+
131
+ # ROS specific
132
+ install/
133
+ build/
134
+ log/
135
+ devel/
136
+
137
+ # Colcon specific
138
+ .colcon_install_layout
139
+ .colcon_build_layout
140
+
141
+ # UV specific
142
+ .uv/
143
+ uv.lock
144
+
145
+ # Docker
146
+ .dockerignore
147
+
148
+ # IDE
149
+ .vscode/
150
+ .idea/
151
+ *.swp
152
+ *.swo
153
+ *~
154
+
155
+ # OS
156
+ .DS_Store
157
+ .DS_Store?
158
+ ._*
159
+ .Spotlight-V100
160
+ .Trashes
161
+ ehthumbs.db
162
+ Thumbs.db
163
+
164
+ # Github Actions
165
+ act*
@@ -0,0 +1,72 @@
1
+ Metadata-Version: 2.4
2
+ Name: colcon-uv
3
+ Version: 0.0.1
4
+ Summary: A Colcon extension providing support for Python projects that use UV
5
+ Project-URL: Homepage, https://github.com/nzlz/colcon-uv
6
+ Author-email: Nestor Gonzalez <nestor94gonzalez@gmail.com>
7
+ Maintainer-email: Nestor Gonzalez <nestor94gonzalez@gmail.com>
8
+ License: BSD-3-Clause
9
+ Keywords: colcon
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Environment :: Plugins
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: BSD License
14
+ Classifier: Programming Language :: Python
15
+ Classifier: Topic :: Software Development :: Build Tools
16
+ Requires-Dist: colcon-core~=0.6
17
+ Requires-Dist: tomli>=1.2.0
18
+ Description-Content-Type: text/markdown
19
+
20
+ # colcon-uv
21
+
22
+ [![CI](https://github.com/nzlz/colcon-uv/actions/workflows/ci.yml/badge.svg)](https://github.com/nzlz/colcon-uv/actions/workflows/ci.yml)
23
+
24
+ A **colcon extension** for building and testing Python packages that use **[uv](https://github.com/astral-sh/uv)** for dependency management.
25
+
26
+ ## Features
27
+
28
+ - **Fast Dependency Management**: Leverages UV's lightning-fast dependency resolution and installation
29
+ - **Modern Python Packaging**: Support for `pyproject.toml`-based packages following PEP 517/518 standards
30
+ - **ROS Integration**: Seamless integration with colcon build system and ROS package management
31
+ - **Dependency Isolation**: Prevents dependency conflicts between packages
32
+
33
+ ## Installation
34
+
35
+ ```bash
36
+ pip install colcon-uv
37
+ ```
38
+
39
+ ## Configuration
40
+
41
+ ### Data Files
42
+
43
+ Similar to [colcon-poetry-ros](https://github.com/UrbanMachine/colcon-poetry-ros), you can specify data files using the `[tool.colcon-uv-ros.data-files]` section:
44
+
45
+ ```toml
46
+ [tool.colcon-uv-ros.data-files]
47
+ "share/ament_index/resource_index/packages" = ["resource/{package_name}"]
48
+ "share/{package_name}" = ["package.xml", "launch/", "config/"]
49
+ "lib/{package_name}" = ["scripts/"]
50
+ ```
51
+
52
+ **Required entries** for all ROS packages:
53
+
54
+ ```toml
55
+ [tool.colcon-uv-ros.data-files]
56
+ "share/ament_index/resource_index/packages" = ["resource/{package_name}"]
57
+ "share/{package_name}" = ["package.xml"]
58
+ ```
59
+
60
+ ### Package Dependencies
61
+
62
+ Specify package dependencies for build ordering and to use system libraries (fetched from system paths, not installed in virtual environment):
63
+
64
+ ```toml
65
+ [tool.colcon-uv-ros.dependencies]
66
+ depend = ["rclpy", "geometry_msgs"] # System packages (adds to both build_depend and exec_depend)
67
+ build_depend = ["bar_package"] # Build-time only dependency
68
+ exec_depend = ["std_msgs"] # Runtime system library
69
+ test_depend = ["qux_package"] # Test-time only dependency
70
+ ```
71
+
72
+ **Important**: ROS system libraries like `rclpy`, `geometry_msgs`, `std_msgs`, etc. should be listed here so they are resolved from the system installation rather than being installed into the virtual environment.
@@ -0,0 +1,53 @@
1
+ # colcon-uv
2
+
3
+ [![CI](https://github.com/nzlz/colcon-uv/actions/workflows/ci.yml/badge.svg)](https://github.com/nzlz/colcon-uv/actions/workflows/ci.yml)
4
+
5
+ A **colcon extension** for building and testing Python packages that use **[uv](https://github.com/astral-sh/uv)** for dependency management.
6
+
7
+ ## Features
8
+
9
+ - **Fast Dependency Management**: Leverages UV's lightning-fast dependency resolution and installation
10
+ - **Modern Python Packaging**: Support for `pyproject.toml`-based packages following PEP 517/518 standards
11
+ - **ROS Integration**: Seamless integration with colcon build system and ROS package management
12
+ - **Dependency Isolation**: Prevents dependency conflicts between packages
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ pip install colcon-uv
18
+ ```
19
+
20
+ ## Configuration
21
+
22
+ ### Data Files
23
+
24
+ Similar to [colcon-poetry-ros](https://github.com/UrbanMachine/colcon-poetry-ros), you can specify data files using the `[tool.colcon-uv-ros.data-files]` section:
25
+
26
+ ```toml
27
+ [tool.colcon-uv-ros.data-files]
28
+ "share/ament_index/resource_index/packages" = ["resource/{package_name}"]
29
+ "share/{package_name}" = ["package.xml", "launch/", "config/"]
30
+ "lib/{package_name}" = ["scripts/"]
31
+ ```
32
+
33
+ **Required entries** for all ROS packages:
34
+
35
+ ```toml
36
+ [tool.colcon-uv-ros.data-files]
37
+ "share/ament_index/resource_index/packages" = ["resource/{package_name}"]
38
+ "share/{package_name}" = ["package.xml"]
39
+ ```
40
+
41
+ ### Package Dependencies
42
+
43
+ Specify package dependencies for build ordering and to use system libraries (fetched from system paths, not installed in virtual environment):
44
+
45
+ ```toml
46
+ [tool.colcon-uv-ros.dependencies]
47
+ depend = ["rclpy", "geometry_msgs"] # System packages (adds to both build_depend and exec_depend)
48
+ build_depend = ["bar_package"] # Build-time only dependency
49
+ exec_depend = ["std_msgs"] # Runtime system library
50
+ test_depend = ["qux_package"] # Test-time only dependency
51
+ ```
52
+
53
+ **Important**: ROS system libraries like `rclpy`, `geometry_msgs`, `std_msgs`, etc. should be listed here so they are resolved from the system installation rather than being installed into the virtual environment.
@@ -0,0 +1,3 @@
1
+ """Colcon extension for UV-based Python packages."""
2
+
3
+ __version__ = "0.0.1"
@@ -0,0 +1,102 @@
1
+ """Command-line interface for colcon-uv."""
2
+
3
+ import argparse
4
+ import logging
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ from colcon_core.plugin_system import satisfies_version
9
+ from colcon_core.verb import VerbExtensionPoint
10
+
11
+ from .dependencies.install import discover_packages, install_dependencies
12
+
13
+
14
+ class UvVerb(VerbExtensionPoint):
15
+ """UV command for colcon."""
16
+
17
+ def __init__(self): # noqa: D107
18
+ super().__init__()
19
+ satisfies_version(VerbExtensionPoint.EXTENSION_POINT_VERSION, "^1.0")
20
+
21
+ def add_arguments(self, *, parser): # noqa: D102
22
+ subparsers = parser.add_subparsers(dest="uv_command", help="UV commands")
23
+
24
+ # Install subcommand
25
+ install_parser = subparsers.add_parser(
26
+ "install", help="Install dependencies for UV-based packages"
27
+ )
28
+ install_parser.add_argument(
29
+ "--base-paths",
30
+ nargs="*",
31
+ help="The base paths to recursively crawl for packages",
32
+ )
33
+ install_parser.add_argument(
34
+ "--uv-args", nargs="*", help="Additional arguments to pass to UV"
35
+ )
36
+
37
+ def main(self, *, context): # noqa: D102
38
+ args = context.args
39
+ logger = logging.getLogger("colcon.uv.cli")
40
+
41
+ if not hasattr(args, "uv_command") or args.uv_command != "install":
42
+ logger.error("Usage: colcon uv install [options]")
43
+ return 1
44
+
45
+ # Get base paths
46
+ base_paths = args.base_paths
47
+ if not base_paths:
48
+ base_paths = [Path(".")]
49
+ else:
50
+ base_paths = [Path(p) for p in base_paths]
51
+
52
+ # Discover UV packages using the install.py logic
53
+ try:
54
+ packages = discover_packages(base_paths)
55
+ except SystemExit:
56
+ logger.warning("No UV packages found")
57
+ return 0
58
+
59
+ # Install dependencies for each package using install.py logic
60
+ install_base = Path("install") # Default install base
61
+ merge_install = False # Default to separate installs
62
+
63
+ for package in packages:
64
+ try:
65
+ install_dependencies(package, install_base, merge_install)
66
+ except Exception as e:
67
+ logger.error(
68
+ f"Failed to install dependencies for '{package.name}': {e}"
69
+ )
70
+ return 1
71
+
72
+ return 0
73
+
74
+
75
+ def main():
76
+ """Run the CLI."""
77
+ parser = argparse.ArgumentParser(
78
+ description="Install dependencies for UV-based packages"
79
+ )
80
+ parser.add_argument(
81
+ "--base-paths",
82
+ nargs="*",
83
+ help="The base paths to recursively crawl for packages",
84
+ )
85
+ parser.add_argument(
86
+ "--uv-args", nargs="*", help="Additional arguments to pass to UV"
87
+ )
88
+
89
+ args = parser.parse_args()
90
+
91
+ # Set up logging
92
+ logging.basicConfig(
93
+ level=logging.INFO, format="%(levelname)s:%(name)s: %(message)s"
94
+ )
95
+
96
+ # Run the UV verb
97
+ verb = UvVerb()
98
+ return verb.main(context=argparse.Namespace(args=args))
99
+
100
+
101
+ if __name__ == "__main__":
102
+ sys.exit(main())
File without changes
@@ -0,0 +1,187 @@
1
+ """Install dependencies for UV packages."""
2
+
3
+ import argparse
4
+ import logging
5
+ import subprocess
6
+ import sys
7
+ from pathlib import Path
8
+ from typing import List
9
+
10
+ import tomli
11
+
12
+ logger = logging.getLogger("colcon.uv.dependencies")
13
+
14
+
15
+ class NotAUvPackageError(Exception):
16
+ """Raised when a directory is not a UV package."""
17
+
18
+ pass
19
+
20
+
21
+ class UvPackage:
22
+ """Represents a UV package."""
23
+
24
+ def __init__(self, path: Path, logger=None):
25
+ """Initialize UV package."""
26
+ self.path = path
27
+ self.logger = logger or logging.getLogger(__name__)
28
+
29
+ self.pyproject_file = path / "pyproject.toml"
30
+ if not self.pyproject_file.exists():
31
+ raise NotAUvPackageError(f"No pyproject.toml found in {path}")
32
+
33
+ # Load pyproject.toml
34
+ with open(self.pyproject_file, "rb") as f:
35
+ self.pyproject_data = tomli.load(f)
36
+
37
+ # Check if it's a UV package
38
+ if (
39
+ "tool" not in self.pyproject_data
40
+ or "colcon-uv-ros" not in self.pyproject_data["tool"]
41
+ ):
42
+ raise NotAUvPackageError(
43
+ f"No [tool.colcon-uv-ros] section found in {self.pyproject_file}"
44
+ )
45
+
46
+ # Get package name
47
+ if (
48
+ "project" in self.pyproject_data
49
+ and "name" in self.pyproject_data["project"]
50
+ ):
51
+ self.name = self.pyproject_data["project"]["name"]
52
+ else:
53
+ self.name = path.name
54
+
55
+
56
+ def main():
57
+ """Main entry point for UV dependency installation."""
58
+ args = _parse_args()
59
+ logging.basicConfig(
60
+ level=logging.DEBUG if args.verbose else logging.INFO,
61
+ format="%(levelname)s:%(name)s: %(message)s",
62
+ )
63
+
64
+ for project in discover_packages(args.base_paths):
65
+ logger.info(f"Installing dependencies for {project.path.name}...")
66
+ install_dependencies(project, args.install_base, args.merge_install)
67
+
68
+ logger.info("Dependencies installed!")
69
+
70
+
71
+ def discover_packages(base_paths: List[Path]) -> List[UvPackage]:
72
+ """Discover UV packages in the given base paths."""
73
+ projects: List[UvPackage] = []
74
+
75
+ potential_packages = []
76
+ for path in base_paths:
77
+ potential_packages += list(path.glob("*"))
78
+
79
+ for path in potential_packages:
80
+ if path.is_dir():
81
+ try:
82
+ project = UvPackage(path)
83
+ except NotAUvPackageError:
84
+ continue
85
+ else:
86
+ projects.append(project)
87
+
88
+ if len(projects) == 0:
89
+ base_paths_str = ", ".join([str(p) for p in base_paths])
90
+ logger.error(
91
+ f"No UV packages were found in the following paths: {base_paths_str}"
92
+ )
93
+ sys.exit(1)
94
+
95
+ return projects
96
+
97
+
98
+ def install_dependencies(
99
+ project: UvPackage, install_base: Path, merge_install: bool
100
+ ) -> None:
101
+ """Install dependencies for a UV package using UV."""
102
+ # Handle both contexts:
103
+ # 1. Direct install: install_base = /install, need to add package name
104
+ # 2. Build task: install_base = /install/package_name, already included
105
+
106
+ if not merge_install:
107
+ # Check if install_base already ends with the package name
108
+ if install_base.name != project.name:
109
+ install_base /= project.name
110
+
111
+ # Create the install directory first
112
+ install_base.mkdir(parents=True, exist_ok=True)
113
+
114
+ # Venv path - this should be /install/PACKAGE_NAME/venv/
115
+ venv_path = install_base / "venv"
116
+
117
+ # Create virtual environment at the target location with system packages access
118
+ # --system-site-packages is needed because ROS 2 packages like rclpy are installed
119
+ # system-wide (not available on PyPI) and our nodes need access to them
120
+ subprocess.run(["uv", "venv", "--system-site-packages", str(venv_path)], check=True)
121
+
122
+ # Install dependencies and the package itself to the target venv
123
+ # Use --python to specify the target venv's python
124
+ python_exe = venv_path / "bin" / "python"
125
+ subprocess.run(
126
+ ["uv", "pip", "install", "--python", str(python_exe), "-e", str(project.path)],
127
+ check=True,
128
+ )
129
+
130
+
131
+ def install_dependencies_from_descriptor(
132
+ pkg_descriptor, install_base: Path, merge_install: bool
133
+ ):
134
+ """Install dependencies from a PackageDescriptor object.
135
+
136
+ This is a convenience function for use by colcon build tasks.
137
+ """
138
+ try:
139
+ uv_package = UvPackage(pkg_descriptor.path)
140
+ install_dependencies(uv_package, install_base, merge_install)
141
+ except NotAUvPackageError as e:
142
+ # Skip packages that aren't UV packages
143
+ logger.debug(f"Skipping non-UV package {pkg_descriptor.name}: {e}")
144
+ return
145
+
146
+
147
+ def _parse_args() -> argparse.Namespace:
148
+ """Parse command line arguments."""
149
+ parser = argparse.ArgumentParser(
150
+ description="Searches for UV packages and installs their dependencies "
151
+ "to a configurable install base"
152
+ )
153
+
154
+ parser.add_argument(
155
+ "--base-paths",
156
+ nargs="+",
157
+ type=Path,
158
+ default=[Path.cwd()],
159
+ help="The paths to start looking for UV projects in. Defaults to the "
160
+ "current directory.",
161
+ )
162
+
163
+ parser.add_argument(
164
+ "--install-base",
165
+ type=Path,
166
+ default=Path("install"),
167
+ help="The base path for all install prefixes (default: install)",
168
+ )
169
+
170
+ parser.add_argument(
171
+ "--merge-install",
172
+ action="store_true",
173
+ help="Merge all install prefixes into a single location",
174
+ )
175
+
176
+ parser.add_argument(
177
+ "-v",
178
+ "--verbose",
179
+ action="store_true",
180
+ help="If provided, debug logs will be printed",
181
+ )
182
+
183
+ return parser.parse_args()
184
+
185
+
186
+ if __name__ == "__main__":
187
+ main()
@@ -0,0 +1,5 @@
1
+ """Package augmentation for UV-based Python packages."""
2
+
3
+ from colcon_uv.package_augmentation.uv import UvPackageAugmentation
4
+
5
+ __all__ = ["UvPackageAugmentation"]