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.
- ghostbuild-0.1.0/LICENSE +21 -0
- ghostbuild-0.1.0/PKG-INFO +198 -0
- ghostbuild-0.1.0/README.md +164 -0
- ghostbuild-0.1.0/ghostbuild/__init__.py +3 -0
- ghostbuild-0.1.0/ghostbuild/__main__.py +6 -0
- ghostbuild-0.1.0/ghostbuild/adapters/__init__.py +19 -0
- ghostbuild-0.1.0/ghostbuild/adapters/base.py +68 -0
- ghostbuild-0.1.0/ghostbuild/adapters/python.py +232 -0
- ghostbuild-0.1.0/ghostbuild/cli.py +341 -0
- ghostbuild-0.1.0/ghostbuild/config.py +229 -0
- ghostbuild-0.1.0/ghostbuild/detection.py +27 -0
- ghostbuild-0.1.0/ghostbuild/engine/__init__.py +1 -0
- ghostbuild-0.1.0/ghostbuild/engine/archives.py +158 -0
- ghostbuild-0.1.0/ghostbuild/engine/artifacts.py +56 -0
- ghostbuild-0.1.0/ghostbuild/engine/comparator.py +395 -0
- ghostbuild-0.1.0/ghostbuild/engine/executor.py +152 -0
- ghostbuild-0.1.0/ghostbuild/engine/isolation.py +99 -0
- ghostbuild-0.1.0/ghostbuild/engine/verifier.py +310 -0
- ghostbuild-0.1.0/ghostbuild/engine/workspace.py +116 -0
- ghostbuild-0.1.0/ghostbuild/errors.py +47 -0
- ghostbuild-0.1.0/ghostbuild/models.py +216 -0
- ghostbuild-0.1.0/ghostbuild/py.typed +0 -0
- ghostbuild-0.1.0/ghostbuild/reports/__init__.py +29 -0
- ghostbuild-0.1.0/ghostbuild/reports/html_report.py +223 -0
- ghostbuild-0.1.0/ghostbuild/reports/json_report.py +46 -0
- ghostbuild-0.1.0/ghostbuild/reports/terminal.py +186 -0
- ghostbuild-0.1.0/ghostbuild/utils/__init__.py +1 -0
- ghostbuild-0.1.0/ghostbuild/utils/environment.py +78 -0
- ghostbuild-0.1.0/ghostbuild/utils/filesystem.py +35 -0
- ghostbuild-0.1.0/ghostbuild/utils/hashing.py +44 -0
- ghostbuild-0.1.0/ghostbuild.egg-info/PKG-INFO +198 -0
- ghostbuild-0.1.0/ghostbuild.egg-info/SOURCES.txt +45 -0
- ghostbuild-0.1.0/ghostbuild.egg-info/dependency_links.txt +1 -0
- ghostbuild-0.1.0/ghostbuild.egg-info/entry_points.txt +2 -0
- ghostbuild-0.1.0/ghostbuild.egg-info/requires.txt +10 -0
- ghostbuild-0.1.0/ghostbuild.egg-info/top_level.txt +1 -0
- ghostbuild-0.1.0/pyproject.toml +62 -0
- ghostbuild-0.1.0/setup.cfg +4 -0
- ghostbuild-0.1.0/tests/test_artifacts.py +59 -0
- ghostbuild-0.1.0/tests/test_cli.py +136 -0
- ghostbuild-0.1.0/tests/test_comparator.py +198 -0
- ghostbuild-0.1.0/tests/test_config.py +87 -0
- ghostbuild-0.1.0/tests/test_detection.py +134 -0
- ghostbuild-0.1.0/tests/test_hashing.py +31 -0
- ghostbuild-0.1.0/tests/test_reports.py +133 -0
- ghostbuild-0.1.0/tests/test_safety.py +362 -0
- ghostbuild-0.1.0/tests/test_verifier.py +211 -0
ghostbuild-0.1.0/LICENSE
ADDED
|
@@ -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,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 []
|