ghostbuild 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 (47) hide show
  1. ghostbuild-0.1.0/LICENSE +21 -0
  2. ghostbuild-0.1.0/PKG-INFO +198 -0
  3. ghostbuild-0.1.0/README.md +164 -0
  4. ghostbuild-0.1.0/ghostbuild/__init__.py +3 -0
  5. ghostbuild-0.1.0/ghostbuild/__main__.py +6 -0
  6. ghostbuild-0.1.0/ghostbuild/adapters/__init__.py +19 -0
  7. ghostbuild-0.1.0/ghostbuild/adapters/base.py +68 -0
  8. ghostbuild-0.1.0/ghostbuild/adapters/python.py +232 -0
  9. ghostbuild-0.1.0/ghostbuild/cli.py +341 -0
  10. ghostbuild-0.1.0/ghostbuild/config.py +229 -0
  11. ghostbuild-0.1.0/ghostbuild/detection.py +27 -0
  12. ghostbuild-0.1.0/ghostbuild/engine/__init__.py +1 -0
  13. ghostbuild-0.1.0/ghostbuild/engine/archives.py +158 -0
  14. ghostbuild-0.1.0/ghostbuild/engine/artifacts.py +56 -0
  15. ghostbuild-0.1.0/ghostbuild/engine/comparator.py +395 -0
  16. ghostbuild-0.1.0/ghostbuild/engine/executor.py +152 -0
  17. ghostbuild-0.1.0/ghostbuild/engine/isolation.py +99 -0
  18. ghostbuild-0.1.0/ghostbuild/engine/verifier.py +310 -0
  19. ghostbuild-0.1.0/ghostbuild/engine/workspace.py +116 -0
  20. ghostbuild-0.1.0/ghostbuild/errors.py +47 -0
  21. ghostbuild-0.1.0/ghostbuild/models.py +216 -0
  22. ghostbuild-0.1.0/ghostbuild/py.typed +0 -0
  23. ghostbuild-0.1.0/ghostbuild/reports/__init__.py +29 -0
  24. ghostbuild-0.1.0/ghostbuild/reports/html_report.py +223 -0
  25. ghostbuild-0.1.0/ghostbuild/reports/json_report.py +46 -0
  26. ghostbuild-0.1.0/ghostbuild/reports/terminal.py +186 -0
  27. ghostbuild-0.1.0/ghostbuild/utils/__init__.py +1 -0
  28. ghostbuild-0.1.0/ghostbuild/utils/environment.py +78 -0
  29. ghostbuild-0.1.0/ghostbuild/utils/filesystem.py +35 -0
  30. ghostbuild-0.1.0/ghostbuild/utils/hashing.py +44 -0
  31. ghostbuild-0.1.0/ghostbuild.egg-info/PKG-INFO +198 -0
  32. ghostbuild-0.1.0/ghostbuild.egg-info/SOURCES.txt +45 -0
  33. ghostbuild-0.1.0/ghostbuild.egg-info/dependency_links.txt +1 -0
  34. ghostbuild-0.1.0/ghostbuild.egg-info/entry_points.txt +2 -0
  35. ghostbuild-0.1.0/ghostbuild.egg-info/requires.txt +10 -0
  36. ghostbuild-0.1.0/ghostbuild.egg-info/top_level.txt +1 -0
  37. ghostbuild-0.1.0/pyproject.toml +62 -0
  38. ghostbuild-0.1.0/setup.cfg +4 -0
  39. ghostbuild-0.1.0/tests/test_artifacts.py +59 -0
  40. ghostbuild-0.1.0/tests/test_cli.py +136 -0
  41. ghostbuild-0.1.0/tests/test_comparator.py +198 -0
  42. ghostbuild-0.1.0/tests/test_config.py +87 -0
  43. ghostbuild-0.1.0/tests/test_detection.py +134 -0
  44. ghostbuild-0.1.0/tests/test_hashing.py +31 -0
  45. ghostbuild-0.1.0/tests/test_reports.py +133 -0
  46. ghostbuild-0.1.0/tests/test_safety.py +362 -0
  47. ghostbuild-0.1.0/tests/test_verifier.py +211 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sam3360
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,198 @@
1
+ Metadata-Version: 2.4
2
+ Name: ghostbuild
3
+ Version: 0.1.0
4
+ Summary: Reproducible build verification: build it twice, know what changed.
5
+ Author: LocalGhosters
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/LocalGhosters/ghostbuild
8
+ Project-URL: Documentation, https://github.com/LocalGhosters/ghostbuild/tree/main/docs
9
+ Project-URL: Issues, https://github.com/LocalGhosters/ghostbuild/issues
10
+ Project-URL: Changelog, https://github.com/LocalGhosters/ghostbuild/blob/main/CHANGELOG.md
11
+ Keywords: reproducible-builds,build,verification,supply-chain,packaging
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: POSIX
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Software Development :: Build Tools
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.10
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: typer>=0.12
27
+ Requires-Dist: rich>=13.0
28
+ Requires-Dist: tomli>=2.0; python_version < "3.11"
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest>=7.4; extra == "dev"
31
+ Requires-Dist: pytest-cov>=4.1; extra == "dev"
32
+ Requires-Dist: build>=1.0; extra == "dev"
33
+ Dynamic: license-file
34
+
35
+ # GhostBuild
36
+
37
+ **Build it twice. Know what changed.**
38
+
39
+ GhostBuild is a local-first command-line tool that independently rebuilds a software
40
+ project and compares the resulting artifacts, so you can see whether your build is
41
+ reproducible and, if not, which files differ.
42
+
43
+ > **Status: 0.1.0, early.** Only Python projects are supported. Not yet published on PyPI.
44
+
45
+ ## Why it exists
46
+
47
+ A build that succeeds is not necessarily a reproducible one. Timestamps, tool versions,
48
+ unpinned dependencies and file ordering can all change the bytes of a wheel or sdist
49
+ without changing its behaviour. If you can't rebuild an artifact and get the same bytes,
50
+ it is hard to confirm that what you published came from the source you reviewed.
51
+ GhostBuild automates the "build it again and compare" step and explains differences.
52
+
53
+ ## How it works
54
+
55
+ 1. Inspect the project (reads files only; nothing is executed) and pick an adapter.
56
+ 2. Show you a plan and ask for consent, because builds run project code.
57
+ 3. Build the project in a temporary copy (the original build).
58
+ 4. Build a second, fresh copy in Docker (preferred) or, if you approve, on the same host.
59
+ 5. Compare artifacts by exact SHA-256 and inspect wheel/sdist members for the cause of any difference.
60
+ 6. Print a report and write `result.json` (and optionally `report.html`).
61
+
62
+ Verdicts:
63
+
64
+ - **reproducible**: every artifact matches exactly under the comparison policy.
65
+ - **non-reproducible**: at least one artifact differs, is missing, or is extra.
66
+ - **inconclusive**: a build failed or timed out, no artifacts were found, or the comparison could not be completed.
67
+
68
+ ## Features
69
+
70
+ - Python projects: setuptools, Hatch, Poetry, PDM (and Flit) detected via `pyproject.toml`; legacy `setup.py` projects too.
71
+ - Wheel and sdist artifacts, compared by SHA-256.
72
+ - For differing archives: per-member differences, timestamp/mode metadata, wheel `WHEEL`/`METADATA` fields, and hypotheses clearly labelled as unconfirmed.
73
+ - Docker isolation, with a same-host fallback that needs explicit approval and is labelled in every report.
74
+ - Terminal, JSON and self-contained HTML reports.
75
+ - Optional `ghostbuild.toml`. No accounts, no API keys, no telemetry.
76
+
77
+ ## Installation
78
+
79
+ Once published:
80
+
81
+ ```bash
82
+ pip install ghostbuild
83
+ ```
84
+
85
+ From a checkout today:
86
+
87
+ ```bash
88
+ git clone https://github.com/LocalGhosters/ghostbuild.git
89
+ cd ghostbuild
90
+ pip install .
91
+ ghostbuild --version
92
+ ```
93
+
94
+ Requires Python 3.10+. Docker is optional but recommended for independent rebuilds.
95
+
96
+ ## Quick start
97
+
98
+ ```bash
99
+ ghostbuild scan ./my-project # inspect only, runs nothing
100
+ ghostbuild verify ./my-project # asks before running the build
101
+ ghostbuild verify ./my-project --format html --output ./results
102
+ ghostbuild compare ./build-one ./build-two
103
+ ghostbuild report ./ghostbuild-results/
104
+ ```
105
+
106
+ ## Commands
107
+
108
+ | Command | Purpose |
109
+ | --- | --- |
110
+ | `ghostbuild` | Banner, version and examples. |
111
+ | `ghostbuild scan [DIR]` | Show name, version, build system, config files, build requirements, available tools. Never builds. |
112
+ | `ghostbuild verify [DIR]` | Full workflow. Options: `--output`, `--format terminal\|json\|html`, `--keep-artifacts`, `--isolation auto\|docker\|local`, `--yes`, `--timeout`, `--config`, `--verbose`, `--no-color`. |
113
+ | `ghostbuild compare A B` | Compare two existing artifact directories. Runs no builds. |
114
+ | `ghostbuild report [PATH]` | Display or regenerate a report from a stored `result.json`. |
115
+ | `ghostbuild init [PATH]` | Write an example `ghostbuild.toml`. |
116
+ | `ghostbuild version` | Print the version. |
117
+
118
+ Exit codes: `0` reproducible, `1` non-reproducible, `2` usage error, `3` inconclusive,
119
+ `4` error, `5` user declined.
120
+
121
+ ## Example output
122
+
123
+ The shape of a report (values depend on your project):
124
+
125
+ ```
126
+ RESULT: NON-REPRODUCIBLE
127
+
128
+ Artifact Original Rebuilt Status
129
+ pkg-1.0-py3-none-any.whl 9f2c41ab07e1… 3be8d0c5a912… different
130
+
131
+ Internal differences:
132
+ pkg-1.0.dist-info/RECORD metadata (timestamp)
133
+ Hypotheses (unconfirmed):
134
+ - Archive member timestamps differ; the build may embed the current time.
135
+ ```
136
+
137
+ ## Configuration
138
+
139
+ Everything is optional. See [docs/configuration.md](docs/configuration.md) and
140
+ [examples/ghostbuild.example.toml](examples/ghostbuild.example.toml). Generate a file with `ghostbuild init`.
141
+
142
+ ## Supported ecosystems
143
+
144
+ | Ecosystem | Status |
145
+ | --- | --- |
146
+ | Python (wheel, sdist) | Implemented |
147
+ | Node.js, Cargo, Go, Java | Planned, **not implemented** |
148
+
149
+ Details: [docs/supported-ecosystems.md](docs/supported-ecosystems.md).
150
+
151
+ ## Isolation and safety
152
+
153
+ Building runs arbitrary project code (build backends, `setup.py`, hooks). Only verify projects you trust.
154
+
155
+ - Nothing is built without consent (`--yes` or an interactive confirmation). `scan` and `compare` never build.
156
+ - The plan states what runs on the host and what runs in Docker.
157
+ - Docker mode mounts the source read-only, drops capabilities and sets `no-new-privileges`.
158
+ The container downloads the build frontend and build requirements.
159
+ - If Docker is unavailable, `--isolation auto` stops and asks. A local rebuild uses the same
160
+ interpreter and tools as the original build, so it only shows repeatability on this machine.
161
+ A match is labelled "same-host check only". `--isolation docker` never falls back.
162
+ - Your project directory is only read. Builds run in temporary copies. GhostBuild deletes only its own temporary directories.
163
+ - No `shell=True`; every subprocess has a timeout; output is captured.
164
+ - Builds get an allowlisted environment. Reports record variable names only, and secret-looking values are redacted from logs.
165
+ - Archives are never extracted to disk; unsafe member names are rejected.
166
+
167
+ ## Limitations
168
+
169
+ - Python only.
170
+ - Only the `exact` comparison policy exists. There is no normalised comparison.
171
+ - Hypotheses are hints from patterns, not proof of cause.
172
+ - The Docker rebuild installs build tools from the network, so differing package versions between builds are possible; unpinned build requirements are flagged as a warning.
173
+ - Python-version, OS and architecture differences are reported but not controlled.
174
+ - Tested on Linux. Windows and macOS are not verified.
175
+
176
+ **Disclaimer:** reproducibility results depend on the environments and comparison policy used.
177
+ A "reproducible" result means these two builds matched, not that every build everywhere will.
178
+
179
+ ## Development
180
+
181
+ ```bash
182
+ pip install -e ".[dev]"
183
+ pytest
184
+ python -m build
185
+ ```
186
+
187
+ Tests never run real project builds; subprocesses and Docker are faked. See
188
+ [docs/architecture.md](docs/architecture.md).
189
+
190
+ ## Contributing
191
+
192
+ Contributions are welcome: see [CONTRIBUTING.md](CONTRIBUTING.md) and the
193
+ [Code of Conduct](CODE_OF_CONDUCT.md). Open areas include new ecosystem adapters, better
194
+ diagnostics, other isolation providers and more test scenarios.
195
+
196
+ ## License
197
+
198
+ MIT. Copyright (c) 2026 LocalGhosters. See [LICENSE](LICENSE).
@@ -0,0 +1,164 @@
1
+ # GhostBuild
2
+
3
+ **Build it twice. Know what changed.**
4
+
5
+ GhostBuild is a local-first command-line tool that independently rebuilds a software
6
+ project and compares the resulting artifacts, so you can see whether your build is
7
+ reproducible and, if not, which files differ.
8
+
9
+ > **Status: 0.1.0, early.** Only Python projects are supported. Not yet published on PyPI.
10
+
11
+ ## Why it exists
12
+
13
+ A build that succeeds is not necessarily a reproducible one. Timestamps, tool versions,
14
+ unpinned dependencies and file ordering can all change the bytes of a wheel or sdist
15
+ without changing its behaviour. If you can't rebuild an artifact and get the same bytes,
16
+ it is hard to confirm that what you published came from the source you reviewed.
17
+ GhostBuild automates the "build it again and compare" step and explains differences.
18
+
19
+ ## How it works
20
+
21
+ 1. Inspect the project (reads files only; nothing is executed) and pick an adapter.
22
+ 2. Show you a plan and ask for consent, because builds run project code.
23
+ 3. Build the project in a temporary copy (the original build).
24
+ 4. Build a second, fresh copy in Docker (preferred) or, if you approve, on the same host.
25
+ 5. Compare artifacts by exact SHA-256 and inspect wheel/sdist members for the cause of any difference.
26
+ 6. Print a report and write `result.json` (and optionally `report.html`).
27
+
28
+ Verdicts:
29
+
30
+ - **reproducible**: every artifact matches exactly under the comparison policy.
31
+ - **non-reproducible**: at least one artifact differs, is missing, or is extra.
32
+ - **inconclusive**: a build failed or timed out, no artifacts were found, or the comparison could not be completed.
33
+
34
+ ## Features
35
+
36
+ - Python projects: setuptools, Hatch, Poetry, PDM (and Flit) detected via `pyproject.toml`; legacy `setup.py` projects too.
37
+ - Wheel and sdist artifacts, compared by SHA-256.
38
+ - For differing archives: per-member differences, timestamp/mode metadata, wheel `WHEEL`/`METADATA` fields, and hypotheses clearly labelled as unconfirmed.
39
+ - Docker isolation, with a same-host fallback that needs explicit approval and is labelled in every report.
40
+ - Terminal, JSON and self-contained HTML reports.
41
+ - Optional `ghostbuild.toml`. No accounts, no API keys, no telemetry.
42
+
43
+ ## Installation
44
+
45
+ Once published:
46
+
47
+ ```bash
48
+ pip install ghostbuild
49
+ ```
50
+
51
+ From a checkout today:
52
+
53
+ ```bash
54
+ git clone https://github.com/LocalGhosters/ghostbuild.git
55
+ cd ghostbuild
56
+ pip install .
57
+ ghostbuild --version
58
+ ```
59
+
60
+ Requires Python 3.10+. Docker is optional but recommended for independent rebuilds.
61
+
62
+ ## Quick start
63
+
64
+ ```bash
65
+ ghostbuild scan ./my-project # inspect only, runs nothing
66
+ ghostbuild verify ./my-project # asks before running the build
67
+ ghostbuild verify ./my-project --format html --output ./results
68
+ ghostbuild compare ./build-one ./build-two
69
+ ghostbuild report ./ghostbuild-results/
70
+ ```
71
+
72
+ ## Commands
73
+
74
+ | Command | Purpose |
75
+ | --- | --- |
76
+ | `ghostbuild` | Banner, version and examples. |
77
+ | `ghostbuild scan [DIR]` | Show name, version, build system, config files, build requirements, available tools. Never builds. |
78
+ | `ghostbuild verify [DIR]` | Full workflow. Options: `--output`, `--format terminal\|json\|html`, `--keep-artifacts`, `--isolation auto\|docker\|local`, `--yes`, `--timeout`, `--config`, `--verbose`, `--no-color`. |
79
+ | `ghostbuild compare A B` | Compare two existing artifact directories. Runs no builds. |
80
+ | `ghostbuild report [PATH]` | Display or regenerate a report from a stored `result.json`. |
81
+ | `ghostbuild init [PATH]` | Write an example `ghostbuild.toml`. |
82
+ | `ghostbuild version` | Print the version. |
83
+
84
+ Exit codes: `0` reproducible, `1` non-reproducible, `2` usage error, `3` inconclusive,
85
+ `4` error, `5` user declined.
86
+
87
+ ## Example output
88
+
89
+ The shape of a report (values depend on your project):
90
+
91
+ ```
92
+ RESULT: NON-REPRODUCIBLE
93
+
94
+ Artifact Original Rebuilt Status
95
+ pkg-1.0-py3-none-any.whl 9f2c41ab07e1… 3be8d0c5a912… different
96
+
97
+ Internal differences:
98
+ pkg-1.0.dist-info/RECORD metadata (timestamp)
99
+ Hypotheses (unconfirmed):
100
+ - Archive member timestamps differ; the build may embed the current time.
101
+ ```
102
+
103
+ ## Configuration
104
+
105
+ Everything is optional. See [docs/configuration.md](docs/configuration.md) and
106
+ [examples/ghostbuild.example.toml](examples/ghostbuild.example.toml). Generate a file with `ghostbuild init`.
107
+
108
+ ## Supported ecosystems
109
+
110
+ | Ecosystem | Status |
111
+ | --- | --- |
112
+ | Python (wheel, sdist) | Implemented |
113
+ | Node.js, Cargo, Go, Java | Planned, **not implemented** |
114
+
115
+ Details: [docs/supported-ecosystems.md](docs/supported-ecosystems.md).
116
+
117
+ ## Isolation and safety
118
+
119
+ Building runs arbitrary project code (build backends, `setup.py`, hooks). Only verify projects you trust.
120
+
121
+ - Nothing is built without consent (`--yes` or an interactive confirmation). `scan` and `compare` never build.
122
+ - The plan states what runs on the host and what runs in Docker.
123
+ - Docker mode mounts the source read-only, drops capabilities and sets `no-new-privileges`.
124
+ The container downloads the build frontend and build requirements.
125
+ - If Docker is unavailable, `--isolation auto` stops and asks. A local rebuild uses the same
126
+ interpreter and tools as the original build, so it only shows repeatability on this machine.
127
+ A match is labelled "same-host check only". `--isolation docker` never falls back.
128
+ - Your project directory is only read. Builds run in temporary copies. GhostBuild deletes only its own temporary directories.
129
+ - No `shell=True`; every subprocess has a timeout; output is captured.
130
+ - Builds get an allowlisted environment. Reports record variable names only, and secret-looking values are redacted from logs.
131
+ - Archives are never extracted to disk; unsafe member names are rejected.
132
+
133
+ ## Limitations
134
+
135
+ - Python only.
136
+ - Only the `exact` comparison policy exists. There is no normalised comparison.
137
+ - Hypotheses are hints from patterns, not proof of cause.
138
+ - The Docker rebuild installs build tools from the network, so differing package versions between builds are possible; unpinned build requirements are flagged as a warning.
139
+ - Python-version, OS and architecture differences are reported but not controlled.
140
+ - Tested on Linux. Windows and macOS are not verified.
141
+
142
+ **Disclaimer:** reproducibility results depend on the environments and comparison policy used.
143
+ A "reproducible" result means these two builds matched, not that every build everywhere will.
144
+
145
+ ## Development
146
+
147
+ ```bash
148
+ pip install -e ".[dev]"
149
+ pytest
150
+ python -m build
151
+ ```
152
+
153
+ Tests never run real project builds; subprocesses and Docker are faked. See
154
+ [docs/architecture.md](docs/architecture.md).
155
+
156
+ ## Contributing
157
+
158
+ Contributions are welcome: see [CONTRIBUTING.md](CONTRIBUTING.md) and the
159
+ [Code of Conduct](CODE_OF_CONDUCT.md). Open areas include new ecosystem adapters, better
160
+ diagnostics, other isolation providers and more test scenarios.
161
+
162
+ ## License
163
+
164
+ MIT. Copyright (c) 2026 LocalGhosters. See [LICENSE](LICENSE).
@@ -0,0 +1,3 @@
1
+ """GhostBuild: reproducible build verification. Build it twice. Know what changed."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,6 @@
1
+ """Allow ``python -m ghostbuild``."""
2
+
3
+ from ghostbuild.cli import app
4
+
5
+ if __name__ == "__main__":
6
+ app()
@@ -0,0 +1,19 @@
1
+ """Registry of ecosystem adapters.
2
+
3
+ To add an ecosystem, implement ``BuildAdapter`` and append an instance here.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from ghostbuild.adapters.base import BuildAdapter
9
+ from ghostbuild.adapters.python import PythonAdapter
10
+
11
+ ADAPTERS: tuple[BuildAdapter, ...] = (PythonAdapter(),)
12
+
13
+
14
+ def get_adapters() -> tuple[BuildAdapter, ...]:
15
+ """Return all registered adapters, in detection order."""
16
+ return ADAPTERS
17
+
18
+
19
+ __all__ = ["ADAPTERS", "BuildAdapter", "PythonAdapter", "get_adapters"]
@@ -0,0 +1,68 @@
1
+ """The interface an ecosystem adapter implements.
2
+
3
+ The engine only talks to this interface. To add an ecosystem (Node.js, Cargo,
4
+ Go, ...), subclass ``BuildAdapter`` and register it in ``adapters/__init__.py``.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from abc import ABC, abstractmethod
10
+ from pathlib import Path
11
+
12
+ from ghostbuild.config import Config
13
+ from ghostbuild.models import ProjectInfo
14
+
15
+
16
+ class BuildAdapter(ABC):
17
+ """Knows how to recognise, inspect and build one kind of project."""
18
+
19
+ #: Short identifier used in reports and configuration, e.g. ``"python"``.
20
+ id: str
21
+ #: Human-readable language name.
22
+ language: str
23
+
24
+ @abstractmethod
25
+ def detect(self, project_dir: Path) -> bool:
26
+ """Return True if this adapter can handle the project. Must not run project code."""
27
+
28
+ @abstractmethod
29
+ def inspect(self, project_dir: Path) -> ProjectInfo:
30
+ """Read project metadata from files. Must not run project code."""
31
+
32
+ @abstractmethod
33
+ def available_tools(self) -> dict[str, str | None]:
34
+ """Map tool name to a version/path, or ``None`` if it is not available."""
35
+
36
+ @abstractmethod
37
+ def missing_prerequisites(self, config: Config) -> list[str]:
38
+ """Return human-readable problems that would stop a local build."""
39
+
40
+ @abstractmethod
41
+ def build_command(self, source_dir: Path, output_dir: Path, config: Config) -> list[str]:
42
+ """Return the argument list (never a shell string) for a local build."""
43
+
44
+ @abstractmethod
45
+ def has_custom_build_command(self, config: Config) -> bool:
46
+ """Return True if the user overrode the default build command."""
47
+
48
+ @abstractmethod
49
+ def container_image(self, config: Config) -> str:
50
+ """Return the container image used for an independent rebuild."""
51
+
52
+ @abstractmethod
53
+ def container_script(self, config: Config) -> str:
54
+ """Return the ``sh`` script run inside the container.
55
+
56
+ The container mounts the prepared source read-only at ``/src`` and a
57
+ writable output directory at ``/out``. The script must write artifacts
58
+ to ``/out`` and print one ``GHOSTBUILD_ENV <json>`` line describing the
59
+ container environment.
60
+ """
61
+
62
+ def describe_build_tool(self) -> str | None:
63
+ """Return the local build tool and version, if known."""
64
+ return None
65
+
66
+ def collect_warnings(self, project: ProjectInfo) -> list[str]:
67
+ """Return reproducibility risks visible from project metadata."""
68
+ return []