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.
- rosetta_build-0.1.0/LICENSE +21 -0
- rosetta_build-0.1.0/PKG-INFO +44 -0
- rosetta_build-0.1.0/README.md +21 -0
- rosetta_build-0.1.0/pyproject.toml +79 -0
- rosetta_build-0.1.0/pyproject.toml.orig +76 -0
- rosetta_build-0.1.0/src/rosetta_build/__init__.py +3 -0
- rosetta_build-0.1.0/src/rosetta_build/__main__.py +4 -0
- rosetta_build-0.1.0/src/rosetta_build/backend.py +182 -0
- rosetta_build-0.1.0/src/rosetta_build/cli.py +217 -0
- rosetta_build-0.1.0/src/rosetta_build/collect.py +402 -0
- rosetta_build-0.1.0/src/rosetta_build/collect_graphviz.py +160 -0
- rosetta_build-0.1.0/src/rosetta_build/compiler.py +180 -0
- rosetta_build-0.1.0/src/rosetta_build/compilers/__init__.py +77 -0
- rosetta_build-0.1.0/src/rosetta_build/compilers/_base.py +117 -0
- rosetta_build-0.1.0/src/rosetta_build/compilers/_gnu.py +200 -0
- rosetta_build-0.1.0/src/rosetta_build/compilers/_msvc.py +155 -0
- rosetta_build-0.1.0/src/rosetta_build/compilers/_process.py +25 -0
- rosetta_build-0.1.0/src/rosetta_build/compilers/_rust.py +84 -0
- rosetta_build-0.1.0/src/rosetta_build/compilers/clang_c.py +48 -0
- rosetta_build-0.1.0/src/rosetta_build/compilers/clang_cxx.py +60 -0
- rosetta_build-0.1.0/src/rosetta_build/compilers/gcc_c.py +48 -0
- rosetta_build-0.1.0/src/rosetta_build/compilers/gcc_cxx.py +98 -0
- rosetta_build-0.1.0/src/rosetta_build/compilers/gccrs.py +81 -0
- rosetta_build-0.1.0/src/rosetta_build/compilers/msvc_c.py +66 -0
- rosetta_build-0.1.0/src/rosetta_build/compilers/msvc_cxx.py +77 -0
- rosetta_build-0.1.0/src/rosetta_build/compilers/rustc.py +81 -0
- rosetta_build-0.1.0/src/rosetta_build/dependency.py +159 -0
- rosetta_build-0.1.0/src/rosetta_build/execute.py +84 -0
- rosetta_build-0.1.0/src/rosetta_build/graph.py +73 -0
- rosetta_build-0.1.0/src/rosetta_build/language.py +24 -0
- rosetta_build-0.1.0/src/rosetta_build/metadata.py +464 -0
- rosetta_build-0.1.0/src/rosetta_build/options.py +84 -0
- rosetta_build-0.1.0/src/rosetta_build/p1689.py +339 -0
- rosetta_build-0.1.0/src/rosetta_build/plan.py +457 -0
- rosetta_build-0.1.0/src/rosetta_build/schema.py +198 -0
- rosetta_build-0.1.0/src/rosetta_build/target.py +85 -0
- 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,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__
|