rosetta-build 0.1.0__py3-none-any.whl
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/__init__.py +3 -0
- rosetta_build/__main__.py +4 -0
- rosetta_build/backend.py +182 -0
- rosetta_build/cli.py +217 -0
- rosetta_build/collect.py +402 -0
- rosetta_build/collect_graphviz.py +160 -0
- rosetta_build/compiler.py +180 -0
- rosetta_build/compilers/__init__.py +77 -0
- rosetta_build/compilers/_base.py +117 -0
- rosetta_build/compilers/_gnu.py +200 -0
- rosetta_build/compilers/_msvc.py +155 -0
- rosetta_build/compilers/_process.py +25 -0
- rosetta_build/compilers/_rust.py +84 -0
- rosetta_build/compilers/clang_c.py +48 -0
- rosetta_build/compilers/clang_cxx.py +60 -0
- rosetta_build/compilers/gcc_c.py +48 -0
- rosetta_build/compilers/gcc_cxx.py +98 -0
- rosetta_build/compilers/gccrs.py +81 -0
- rosetta_build/compilers/msvc_c.py +66 -0
- rosetta_build/compilers/msvc_cxx.py +77 -0
- rosetta_build/compilers/rustc.py +81 -0
- rosetta_build/dependency.py +159 -0
- rosetta_build/execute.py +84 -0
- rosetta_build/graph.py +73 -0
- rosetta_build/language.py +24 -0
- rosetta_build/metadata.py +464 -0
- rosetta_build/options.py +84 -0
- rosetta_build/p1689.py +339 -0
- rosetta_build/plan.py +457 -0
- rosetta_build/schema.py +198 -0
- rosetta_build/target.py +85 -0
- rosetta_build/wheel.py +316 -0
- rosetta_build-0.1.0.dist-info/METADATA +44 -0
- rosetta_build-0.1.0.dist-info/RECORD +37 -0
- rosetta_build-0.1.0.dist-info/WHEEL +4 -0
- rosetta_build-0.1.0.dist-info/entry_points.txt +3 -0
- rosetta_build-0.1.0.dist-info/licenses/LICENSE +21 -0
rosetta_build/backend.py
ADDED
|
@@ -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
|
rosetta_build/cli.py
ADDED
|
@@ -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__
|