rosetta-build 0.1.0__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.
Files changed (37) hide show
  1. rosetta_build-0.1.0/LICENSE +21 -0
  2. rosetta_build-0.1.0/PKG-INFO +44 -0
  3. rosetta_build-0.1.0/README.md +21 -0
  4. rosetta_build-0.1.0/pyproject.toml +79 -0
  5. rosetta_build-0.1.0/pyproject.toml.orig +76 -0
  6. rosetta_build-0.1.0/src/rosetta_build/__init__.py +3 -0
  7. rosetta_build-0.1.0/src/rosetta_build/__main__.py +4 -0
  8. rosetta_build-0.1.0/src/rosetta_build/backend.py +182 -0
  9. rosetta_build-0.1.0/src/rosetta_build/cli.py +217 -0
  10. rosetta_build-0.1.0/src/rosetta_build/collect.py +402 -0
  11. rosetta_build-0.1.0/src/rosetta_build/collect_graphviz.py +160 -0
  12. rosetta_build-0.1.0/src/rosetta_build/compiler.py +180 -0
  13. rosetta_build-0.1.0/src/rosetta_build/compilers/__init__.py +77 -0
  14. rosetta_build-0.1.0/src/rosetta_build/compilers/_base.py +117 -0
  15. rosetta_build-0.1.0/src/rosetta_build/compilers/_gnu.py +200 -0
  16. rosetta_build-0.1.0/src/rosetta_build/compilers/_msvc.py +155 -0
  17. rosetta_build-0.1.0/src/rosetta_build/compilers/_process.py +25 -0
  18. rosetta_build-0.1.0/src/rosetta_build/compilers/_rust.py +84 -0
  19. rosetta_build-0.1.0/src/rosetta_build/compilers/clang_c.py +48 -0
  20. rosetta_build-0.1.0/src/rosetta_build/compilers/clang_cxx.py +60 -0
  21. rosetta_build-0.1.0/src/rosetta_build/compilers/gcc_c.py +48 -0
  22. rosetta_build-0.1.0/src/rosetta_build/compilers/gcc_cxx.py +98 -0
  23. rosetta_build-0.1.0/src/rosetta_build/compilers/gccrs.py +81 -0
  24. rosetta_build-0.1.0/src/rosetta_build/compilers/msvc_c.py +66 -0
  25. rosetta_build-0.1.0/src/rosetta_build/compilers/msvc_cxx.py +77 -0
  26. rosetta_build-0.1.0/src/rosetta_build/compilers/rustc.py +81 -0
  27. rosetta_build-0.1.0/src/rosetta_build/dependency.py +159 -0
  28. rosetta_build-0.1.0/src/rosetta_build/execute.py +84 -0
  29. rosetta_build-0.1.0/src/rosetta_build/graph.py +73 -0
  30. rosetta_build-0.1.0/src/rosetta_build/language.py +24 -0
  31. rosetta_build-0.1.0/src/rosetta_build/metadata.py +464 -0
  32. rosetta_build-0.1.0/src/rosetta_build/options.py +84 -0
  33. rosetta_build-0.1.0/src/rosetta_build/p1689.py +339 -0
  34. rosetta_build-0.1.0/src/rosetta_build/plan.py +457 -0
  35. rosetta_build-0.1.0/src/rosetta_build/schema.py +198 -0
  36. rosetta_build-0.1.0/src/rosetta_build/target.py +85 -0
  37. rosetta_build-0.1.0/src/rosetta_build/wheel.py +316 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Will Bowers (ralian)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,44 @@
1
+ Metadata-Version: 2.4
2
+ Name: rosetta-build
3
+ Version: 0.1.0
4
+ Summary: Flat (non-meta) multilanguage build tool with a Python planner
5
+ Keywords: build,c++,modules,packaging,pep517,rust
6
+ Author: ralian
7
+ Author-email: ralian <wbowers314@gmail.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.14
15
+ Classifier: Topic :: Software Development :: Build Tools
16
+ Classifier: Typing :: Typed
17
+ Requires-Dist: pydantic>=2.13.5
18
+ Requires-Python: >=3.14
19
+ Project-URL: Homepage, https://github.com/ralian/rosetta-build
20
+ Project-URL: Repository, https://github.com/ralian/rosetta-build
21
+ Project-URL: Issues, https://github.com/ralian/rosetta-build/issues
22
+ Description-Content-Type: text/markdown
23
+
24
+ # rosetta-build: experimental python-based direct multilanguage build tool
25
+
26
+ The vision:
27
+ - Abstract compiler and language-independent build planner and executor
28
+ - Rosetta will handle build configuration, planning, and build execution
29
+ - Bring Your Own Compiler: As long as you have a compiler in your environment, Rosetta will handle everything else.
30
+
31
+ The roadmap:
32
+ - Start with a Python API, types, and build tests which implementations should pass.
33
+ - Each compiler/linker/tool will implement an interace Rosetta defines. We will target the big three C++ compilers and rustc first.
34
+ - Support C++ modules early on, shortly after the basic Big 3 implementation. We should encourage modules-first. Strike while the iron is hot.
35
+ - Add some packaging and dependency resolution niceities after. Implement common package spec?
36
+
37
+ ## Development Checks
38
+
39
+ ```
40
+ uv sync --all-groups
41
+ uv run ruff check --fix
42
+ uv run ruff format --preview
43
+ uv run mypy --strict .
44
+ ```
@@ -0,0 +1,21 @@
1
+ # rosetta-build: experimental python-based direct multilanguage build tool
2
+
3
+ The vision:
4
+ - Abstract compiler and language-independent build planner and executor
5
+ - Rosetta will handle build configuration, planning, and build execution
6
+ - Bring Your Own Compiler: As long as you have a compiler in your environment, Rosetta will handle everything else.
7
+
8
+ The roadmap:
9
+ - Start with a Python API, types, and build tests which implementations should pass.
10
+ - Each compiler/linker/tool will implement an interace Rosetta defines. We will target the big three C++ compilers and rustc first.
11
+ - Support C++ modules early on, shortly after the basic Big 3 implementation. We should encourage modules-first. Strike while the iron is hot.
12
+ - Add some packaging and dependency resolution niceities after. Implement common package spec?
13
+
14
+ ## Development Checks
15
+
16
+ ```
17
+ uv sync --all-groups
18
+ uv run ruff check --fix
19
+ uv run ruff format --preview
20
+ uv run mypy --strict .
21
+ ```
@@ -0,0 +1,79 @@
1
+ [project]
2
+ name = "rosetta-build"
3
+ version = "0.1.0"
4
+ description = "Flat (non-meta) multilanguage build tool with a Python planner"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ requires-python = ">=3.14"
9
+ keywords = [
10
+ "build",
11
+ "c++",
12
+ "modules",
13
+ "packaging",
14
+ "pep517",
15
+ "rust",
16
+ ]
17
+ classifiers = [
18
+ "Development Status :: 3 - Alpha",
19
+ "Environment :: Console",
20
+ "Intended Audience :: Developers",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.14",
23
+ "Topic :: Software Development :: Build Tools",
24
+ "Typing :: Typed",
25
+ ]
26
+ dependencies = ["pydantic>=2.13.5"]
27
+
28
+ [[project.authors]]
29
+ name = "ralian"
30
+ email = "wbowers314@gmail.com"
31
+
32
+ [project.urls]
33
+ Homepage = "https://github.com/ralian/rosetta-build"
34
+ Repository = "https://github.com/ralian/rosetta-build"
35
+ Issues = "https://github.com/ralian/rosetta-build/issues"
36
+
37
+ [project.scripts]
38
+ rosetta-build = "rosetta_build.cli:main"
39
+
40
+ [build-system]
41
+ requires = ["uv_build>=0.12.5,<0.13.0"]
42
+ build-backend = "uv_build"
43
+
44
+ [dependency-groups]
45
+ dev = [
46
+ "mypy>=2.3.1",
47
+ "pytest>=9.1.1",
48
+ "ruff>=0.16.8",
49
+ ]
50
+
51
+ [tool.uv.build-backend]
52
+ module-root = "src"
53
+ module-name = "rosetta_build"
54
+ source-exclude = [
55
+ "tests/**",
56
+ "docs/**",
57
+ ]
58
+ wheel-exclude = [
59
+ "tests/**",
60
+ "docs/**",
61
+ ]
62
+
63
+ [tool.mypy]
64
+ strict = true
65
+ files = [
66
+ "src",
67
+ "tests",
68
+ ]
69
+ exclude = ["^tests/trees/"]
70
+
71
+ [tool.ruff]
72
+ target-version = "py314"
73
+ src = [
74
+ "src",
75
+ "tests",
76
+ ]
77
+
78
+ [tool.pytest.ini_options]
79
+ testpaths = ["tests"]
@@ -0,0 +1,76 @@
1
+ [project]
2
+ name = "rosetta-build"
3
+ version = "0.1.0"
4
+ description = "Flat (non-meta) multilanguage build tool with a Python planner"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ authors = [
9
+ { name = "ralian", email = "wbowers314@gmail.com" }
10
+ ]
11
+ requires-python = ">=3.14"
12
+ keywords = [
13
+ "build",
14
+ "c++",
15
+ "modules",
16
+ "packaging",
17
+ "pep517",
18
+ "rust",
19
+ ]
20
+ classifiers = [
21
+ "Development Status :: 3 - Alpha",
22
+ "Environment :: Console",
23
+ "Intended Audience :: Developers",
24
+ "Programming Language :: Python :: 3",
25
+ "Programming Language :: Python :: 3.14",
26
+ "Topic :: Software Development :: Build Tools",
27
+ "Typing :: Typed",
28
+ ]
29
+ dependencies = [
30
+ "pydantic>=2.13.5",
31
+ ]
32
+
33
+ [project.urls]
34
+ Homepage = "https://github.com/ralian/rosetta-build"
35
+ Repository = "https://github.com/ralian/rosetta-build"
36
+ Issues = "https://github.com/ralian/rosetta-build/issues"
37
+
38
+ [project.scripts]
39
+ rosetta-build = "rosetta_build.cli:main"
40
+
41
+ [build-system]
42
+ requires = ["uv_build>=0.12.5,<0.13.0"]
43
+ build-backend = "uv_build"
44
+
45
+ [dependency-groups]
46
+ dev = [
47
+ "mypy>=2.3.1",
48
+ "pytest>=9.1.1",
49
+ "ruff>=0.16.8",
50
+ ]
51
+
52
+ [tool.uv.build-backend]
53
+ module-root = "src"
54
+ module-name = "rosetta_build"
55
+ source-exclude = [
56
+ "tests/**",
57
+ "docs/**",
58
+ ]
59
+ wheel-exclude = [
60
+ "tests/**",
61
+ "docs/**",
62
+ ]
63
+
64
+ [tool.mypy]
65
+ strict = true
66
+ files = ["src", "tests"]
67
+ exclude = [
68
+ "^tests/trees/",
69
+ ]
70
+
71
+ [tool.ruff]
72
+ target-version = "py314"
73
+ src = ["src", "tests"]
74
+
75
+ [tool.pytest.ini_options]
76
+ testpaths = ["tests"]
@@ -0,0 +1,3 @@
1
+ from rosetta_build.cli import main
2
+
3
+ __all__ = ["main"]
@@ -0,0 +1,4 @@
1
+ from rosetta_build.cli import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
@@ -0,0 +1,182 @@
1
+ """PEP 517 build-backend hooks for rosetta-build."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import shutil
7
+ import tempfile
8
+ from collections.abc import Mapping
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+ from rosetta_build.collect import Collection, CollectionError, collect
13
+ from rosetta_build.execute import BuildError, run_build
14
+ from rosetta_build.language import CompilerFamily
15
+ from rosetta_build.metadata import (
16
+ MetadataError,
17
+ ProjectMetadata,
18
+ load_project_metadata,
19
+ names_match,
20
+ )
21
+ from rosetta_build.plan import BuildPlan, PlanError, plan_build
22
+ from rosetta_build.target import WheelTarget
23
+ from rosetta_build.wheel import write_dist_info
24
+
25
+ __all__ = [
26
+ "BackendError",
27
+ "build_sdist",
28
+ "build_wheel",
29
+ "get_requires_for_build_sdist",
30
+ "get_requires_for_build_wheel",
31
+ "prepare_metadata_for_build_wheel",
32
+ ]
33
+
34
+
35
+ class BackendError(Exception):
36
+ """Raised when a PEP 517 hook cannot complete."""
37
+
38
+
39
+ def get_requires_for_build_wheel(
40
+ config_settings: dict[str, Any] | None = None,
41
+ ) -> list[str]:
42
+ del config_settings
43
+ return []
44
+
45
+
46
+ def get_requires_for_build_sdist(
47
+ config_settings: dict[str, Any] | None = None,
48
+ ) -> list[str]:
49
+ del config_settings
50
+ return []
51
+
52
+
53
+ def prepare_metadata_for_build_wheel(
54
+ metadata_directory: str,
55
+ config_settings: dict[str, Any] | None = None,
56
+ ) -> str:
57
+ del config_settings
58
+ try:
59
+ metadata = _distribution_metadata(Path.cwd())
60
+ return write_dist_info(metadata, Path(metadata_directory))
61
+ except (CollectionError, MetadataError, PlanError, BackendError) as exc:
62
+ raise BackendError(str(exc)) from exc
63
+
64
+
65
+ def build_wheel(
66
+ wheel_directory: str,
67
+ config_settings: dict[str, Any] | None = None,
68
+ metadata_directory: str | None = None,
69
+ ) -> str:
70
+ del metadata_directory
71
+ try:
72
+ plan = _plan_for_cwd(config_settings)
73
+ asyncio.run(run_build(plan))
74
+ target = _primary_wheel_target_name(plan)
75
+ built = plan.wheel_artifact_by_target[target]
76
+ return _copy_artifact(built, Path(wheel_directory))
77
+ except (CollectionError, MetadataError, PlanError, BuildError, BackendError) as exc:
78
+ raise BackendError(str(exc)) from exc
79
+
80
+
81
+ def build_sdist(
82
+ sdist_directory: str,
83
+ config_settings: dict[str, Any] | None = None,
84
+ ) -> str:
85
+ try:
86
+ plan = _plan_for_cwd(config_settings)
87
+ asyncio.run(run_build(plan))
88
+ target = _primary_wheel_target_name(plan)
89
+ built = plan.sdist_artifact_by_target[target]
90
+ return _copy_artifact(built, Path(sdist_directory))
91
+ except (CollectionError, MetadataError, PlanError, BuildError, BackendError) as exc:
92
+ raise BackendError(str(exc)) from exc
93
+
94
+
95
+ def _plan_for_cwd(config_settings: dict[str, Any] | None) -> BuildPlan:
96
+ source_tree = Path.cwd()
97
+ collection = collect(source_tree)
98
+ _primary_wheel_target(collection, load_project_metadata(source_tree))
99
+ build_dir = _build_dir(source_tree, config_settings)
100
+ family = _family(config_settings)
101
+ return plan_build(collection, build_dir=build_dir, family=family)
102
+
103
+
104
+ def _distribution_metadata(source_tree: Path) -> ProjectMetadata:
105
+ collection = collect(source_tree)
106
+ metadata = load_project_metadata(source_tree)
107
+ target = _primary_wheel_target(collection, metadata)
108
+ if names_match(target.name, metadata.name):
109
+ return metadata
110
+ return metadata.with_name(target.name)
111
+
112
+
113
+ def _primary_wheel_target(
114
+ collection: Collection, metadata: ProjectMetadata
115
+ ) -> WheelTarget:
116
+ wheels = [
117
+ target
118
+ for target in collection.targets.values()
119
+ if isinstance(target, WheelTarget)
120
+ ]
121
+ if not wheels:
122
+ raise BackendError(
123
+ 'PEP 517 backend requires at least one type = "wheel" target'
124
+ )
125
+ matching = [target for target in wheels if names_match(target.name, metadata.name)]
126
+ if len(matching) == 1:
127
+ return matching[0]
128
+ if len(wheels) == 1:
129
+ return wheels[0]
130
+ names = ", ".join(sorted(target.name for target in wheels))
131
+ raise BackendError(
132
+ f"ambiguous wheel targets for distribution {metadata.name!r}: {names}"
133
+ )
134
+
135
+
136
+ def _primary_wheel_target_name(plan: BuildPlan) -> str:
137
+ if len(plan.wheel_steps) == 1:
138
+ return plan.wheel_steps[0].target
139
+ if not plan.wheel_steps:
140
+ raise BackendError("planned build has no wheel targets")
141
+ names = ", ".join(step.target for step in plan.wheel_steps)
142
+ raise BackendError(f"planned build has multiple wheel targets: {names}")
143
+
144
+
145
+ def _build_dir(source_tree: Path, config_settings: dict[str, Any] | None) -> Path:
146
+ configured = _config_value(config_settings, "build-dir", "build_dir")
147
+ if configured is not None:
148
+ path = Path(configured)
149
+ if not path.is_absolute():
150
+ path = source_tree / path
151
+ return path
152
+ return Path(tempfile.mkdtemp(prefix="rosetta-build-"))
153
+
154
+
155
+ def _family(config_settings: dict[str, Any] | None) -> CompilerFamily:
156
+ configured = _config_value(config_settings, "family") or CompilerFamily.GCC.value
157
+ try:
158
+ return CompilerFamily(configured)
159
+ except ValueError as exc:
160
+ raise BackendError(f"unknown compiler family: {configured!r}") from exc
161
+
162
+
163
+ def _config_value(config_settings: Mapping[str, Any] | None, *keys: str) -> str | None:
164
+ if not config_settings:
165
+ return None
166
+ for key in keys:
167
+ if key not in config_settings:
168
+ continue
169
+ value = config_settings[key]
170
+ if isinstance(value, list):
171
+ if not value:
172
+ return None
173
+ return str(value[-1])
174
+ return str(value)
175
+ return None
176
+
177
+
178
+ def _copy_artifact(built: Path, destination_dir: Path) -> str:
179
+ destination_dir.mkdir(parents=True, exist_ok=True)
180
+ dest = destination_dir / built.name
181
+ shutil.copy2(built, dest)
182
+ return dest.name
@@ -0,0 +1,217 @@
1
+ """Command-line interface for rosetta-build."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import asyncio
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ from rosetta_build.collect import Collection, CollectionError, collect
11
+ from rosetta_build.collect_graphviz import write_collection_dot
12
+ from rosetta_build.execute import BuildError, run_build, run_link
13
+ from rosetta_build.language import CompilerFamily
14
+ from rosetta_build.plan import BuildPlan, PlanError, plan_build
15
+ from rosetta_build.target import (
16
+ DynamicLibraryTarget,
17
+ ExecutableTarget,
18
+ ModuleImplementationTarget,
19
+ ModuleInterfaceTarget,
20
+ ModulePartitionTarget,
21
+ StaticLibraryTarget,
22
+ Target,
23
+ WheelTarget,
24
+ )
25
+
26
+
27
+ def main(argv: list[str] | None = None) -> None:
28
+ parser = _build_parser()
29
+ args = parser.parse_args(argv)
30
+ try:
31
+ raise SystemExit(args.handler(args))
32
+ except CollectionError as exc:
33
+ print(f"error: {exc}", file=sys.stderr)
34
+ raise SystemExit(1) from exc
35
+ except PlanError as exc:
36
+ print(f"error: {exc}", file=sys.stderr)
37
+ raise SystemExit(1) from exc
38
+ except BuildError as exc:
39
+ print(f"error: {exc}", file=sys.stderr)
40
+ if exc.stderr:
41
+ print(exc.stderr, file=sys.stderr, end="")
42
+ raise SystemExit(exc.returncode or 1) from exc
43
+
44
+
45
+ def _build_parser() -> argparse.ArgumentParser:
46
+ parser = argparse.ArgumentParser(
47
+ prog="rosetta-build",
48
+ description="Experimental multilanguage build tool.",
49
+ )
50
+ subparsers = parser.add_subparsers(dest="command", required=True)
51
+
52
+ collect_parser = subparsers.add_parser(
53
+ "collect",
54
+ help=(
55
+ "Load and validate targets, then build source, usage, link, "
56
+ "and module graphs."
57
+ ),
58
+ )
59
+ collect_parser.add_argument(
60
+ "source_tree",
61
+ type=Path,
62
+ help="Source tree root containing pyproject.toml.",
63
+ )
64
+ collect_parser.add_argument(
65
+ "--graphviz",
66
+ type=Path,
67
+ metavar="OUT",
68
+ help="Write Graphviz DOT for the collected graphs to OUT.",
69
+ )
70
+ collect_parser.set_defaults(handler=_cmd_collect)
71
+
72
+ build_parser = subparsers.add_parser(
73
+ "build",
74
+ help=(
75
+ "Compile native/module targets and build wheel/sdist targets "
76
+ "(GCC/g++ for native for now)."
77
+ ),
78
+ )
79
+ _add_build_args(build_parser)
80
+ build_parser.set_defaults(handler=_cmd_build)
81
+
82
+ link_parser = subparsers.add_parser(
83
+ "link",
84
+ help="Link compiled objects into libraries and executables.",
85
+ )
86
+ _add_build_args(link_parser)
87
+ link_parser.set_defaults(handler=_cmd_link)
88
+
89
+ install_parser = subparsers.add_parser(
90
+ "install",
91
+ help="Install build artifacts (not yet implemented).",
92
+ )
93
+ install_parser.set_defaults(handler=_cmd_not_implemented, step_name="install")
94
+
95
+ return parser
96
+
97
+
98
+ def _add_build_args(parser: argparse.ArgumentParser) -> None:
99
+ parser.add_argument(
100
+ "source_tree",
101
+ type=Path,
102
+ help="Source tree root containing pyproject.toml.",
103
+ )
104
+ parser.add_argument(
105
+ "--build-dir",
106
+ type=Path,
107
+ default=None,
108
+ help="Build output directory (default: <source_tree>/build).",
109
+ )
110
+ parser.add_argument(
111
+ "--family",
112
+ type=str,
113
+ default=CompilerFamily.GCC.value,
114
+ choices=[family.value for family in CompilerFamily],
115
+ help="Compiler family (default: gcc).",
116
+ )
117
+
118
+
119
+ def _cmd_collect(args: argparse.Namespace) -> int:
120
+ collection = collect(args.source_tree)
121
+ _print_collection(collection)
122
+ if args.graphviz is not None:
123
+ write_collection_dot(collection, args.graphviz)
124
+ print(f"wrote graphviz: {args.graphviz}")
125
+ return 0
126
+
127
+
128
+ def _cmd_build(args: argparse.Namespace) -> int:
129
+ plan = _plan_from_args(args)
130
+ asyncio.run(run_build(plan))
131
+ print(f"compiled {len(plan.compile_steps)} translation unit(s)")
132
+ print(f"built {len(plan.wheel_steps)} wheel/sdist target(s)")
133
+ for name, path in sorted(plan.wheel_artifact_by_target.items()):
134
+ print(f" wheel {name}: {path}")
135
+ for name, path in sorted(plan.sdist_artifact_by_target.items()):
136
+ print(f" sdist {name}: {path}")
137
+ return 0
138
+
139
+
140
+ def _cmd_link(args: argparse.Namespace) -> int:
141
+ plan = _plan_from_args(args)
142
+ asyncio.run(run_link(plan))
143
+ print(f"linked {len(plan.link_steps)} artifact(s)")
144
+ for name, path in sorted(plan.link_artifact_by_target.items()):
145
+ print(f" {name}: {path}")
146
+ return 0
147
+
148
+
149
+ def _plan_from_args(args: argparse.Namespace) -> BuildPlan:
150
+ collection = collect(args.source_tree)
151
+ build_dir = args.build_dir
152
+ if build_dir is None:
153
+ build_dir = collection.source_tree / "build"
154
+ return plan_build(
155
+ collection,
156
+ build_dir=build_dir,
157
+ family=CompilerFamily(args.family),
158
+ )
159
+
160
+
161
+ def _cmd_not_implemented(args: argparse.Namespace) -> int:
162
+ print(f"error: '{args.step_name}' is not implemented yet", file=sys.stderr)
163
+ return 1
164
+
165
+
166
+ def _print_collection(collection: Collection) -> None:
167
+ print(f"source tree: {collection.source_tree}")
168
+ print(f"targets: {len(collection.targets)}")
169
+ for name in sorted(collection.targets):
170
+ target = collection.targets[name]
171
+ sources = collection.source_graph.sources_for(name)
172
+ usage = collection.usage_graph.dependencies_of(name)
173
+ links = collection.link_graph.dependencies_of(name)
174
+ module_imports = collection.module_graph.dependencies_of(name)
175
+ print(f" {name} ({_target_kind(target)})")
176
+ print(f" config: {target.config_path}")
177
+ print(f" sources ({len(sources)}):")
178
+ for source in sorted(sources):
179
+ print(f" {source.relative_to(collection.source_tree)}")
180
+ print(f" usage ({len(usage)}):")
181
+ for dep in sorted(usage):
182
+ print(f" {dep}")
183
+ print(f" dynamic link libraries ({len(links)}):")
184
+ for lib in sorted(links):
185
+ print(f" {lib}")
186
+ print(f" module deps ({len(module_imports)}):")
187
+ for dep in sorted(module_imports):
188
+ print(f" {dep}")
189
+ exports = collection.module_exports.get(name, frozenset())
190
+ if exports:
191
+ print(f" module exports ({len(exports)}):")
192
+ for exported in sorted(exports):
193
+ print(f" {exported}")
194
+ print("dynamic link order:")
195
+ for name in collection.link_graph.topological_order(kind="dynamic link"):
196
+ print(f" {name}")
197
+ print("module order:")
198
+ for name in collection.module_graph.topological_order(kind="module"):
199
+ print(f" {name}")
200
+
201
+
202
+ def _target_kind(target: Target) -> str:
203
+ if isinstance(target, ExecutableTarget):
204
+ return "executable"
205
+ if isinstance(target, StaticLibraryTarget):
206
+ return "static_library"
207
+ if isinstance(target, DynamicLibraryTarget):
208
+ return "dynamic_library"
209
+ if isinstance(target, ModuleInterfaceTarget):
210
+ return "module_interface"
211
+ if isinstance(target, ModulePartitionTarget):
212
+ return "module_partition"
213
+ if isinstance(target, ModuleImplementationTarget):
214
+ return "module_implementation"
215
+ if isinstance(target, WheelTarget):
216
+ return "wheel"
217
+ return type(target).__name__