pio-lock 0.3.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.
pio_lock-0.3.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mat McGowan
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,264 @@
1
+ Metadata-Version: 2.4
2
+ Name: pio-lock
3
+ Version: 0.3.0
4
+ Summary: PlatformIO dependency lockfile tool for reproducible builds
5
+ Author: Mat McGowan
6
+ License-Expression: MIT
7
+ Project-URL: Repository, https://github.com/m-mcgowan/pio-lock
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Topic :: Software Development :: Build Tools
12
+ Classifier: Topic :: Software Development :: Embedded Systems
13
+ Requires-Python: >=3.9
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Dynamic: license-file
17
+
18
+ # pio-lock
19
+
20
+ Dependency lockfile for [PlatformIO](https://platformio.org/) — reproducible builds for embedded projects.
21
+
22
+ PlatformIO resolves library versions at install time but has no lockfile to record what was actually installed. If your `platformio.ini` uses semver ranges (`^1.8.3`), git branches, or unpinned git URLs, a fresh install can silently pick up different versions. `pio-lock` fixes this.
23
+
24
+ ## Status
25
+
26
+ | Feature | Design | Docs | Impl | Tests | Examples | Since | Updated |
27
+ |---------|--------|------|------|-------|----------|-------|---------|
28
+ | **Dependency scanning** | [pio_lock.py](pio_lock.py) | [README](README.md) | [pio_lock.py](pio_lock.py) | [test_scan](tests/test_scan.py) | [fixtures/](tests/fixtures/) | | |
29
+ | **Lockfile generation** | [pio_lock.py](pio_lock.py) | [README](README.md) | [pio_lock.py](pio_lock.py) | [test_commands](tests/test_commands.py) | [minimal-native](tests/fixtures/minimal-native/) | | |
30
+ | **Build snapshot** | [pio_lock.py](pio_lock.py) | [README](README.md) | [pio_lock.py](pio_lock.py) | [test_snapshot](tests/test_snapshot.py) | | | |
31
+ | **Outdated detection** | [pio_lock.py](pio_lock.py) | [README](README.md) | [pio_lock.py](pio_lock.py) | [test_outdated](tests/test_outdated.py) | | | |
32
+ | **Update commands** | [pio_lock.py](pio_lock.py) | [README](README.md) | [pio_lock.py](pio_lock.py) | [test_commands](tests/test_commands.py) | | | |
33
+ | **PlatformIO integration** | [pio_lock.py](pio_lock.py) | [README](README.md) | [pio_lock.py](pio_lock.py) | [test_integration](tests/test_integration.py) | | | |
34
+ | **GitHub Actions integration** | | [README](README.md) | | | | | |
35
+ | **Config source tracking** | [pio_lock.py](pio_lock.py) | | [pio_lock.py](pio_lock.py) | [test_commands](tests/test_commands.py) | | | |
36
+
37
+ ## How it works
38
+
39
+ `pio-lock` scans your installed PlatformIO dependencies and records their exact versions in a `pio.lock.json` file that you commit alongside `platformio.ini`. Later, `restore` reinstalls those exact versions, and `check` verifies nothing has drifted.
40
+
41
+ It captures:
42
+ - **Registry libraries** — exact resolved version from `.piopm` metadata
43
+ - **Git libraries** — exact commit SHA from the installed repo
44
+ - **Local libraries** — recorded for completeness, skipped during restore
45
+ - **Global packages** — framework, toolchain, and tool versions from `~/.platformio/packages/`
46
+ - **Platform URL** — the resolved platform specification
47
+
48
+ ## Quick start
49
+
50
+ ```bash
51
+ # 1. Install your dependencies normally
52
+ pio pkg install -e my_env
53
+
54
+ # 2. Capture the resolved state
55
+ python pio_lock.py capture -e my_env
56
+
57
+ # 3. Commit the lockfile
58
+ git add pio.lock.json
59
+
60
+ # 4. On another machine (or in CI), restore exact versions
61
+ python pio_lock.py restore -e my_env
62
+
63
+ # 5. Build as usual
64
+ pio run -e my_env
65
+ ```
66
+
67
+ ## Usage
68
+
69
+ ### Standalone CLI
70
+
71
+ ```bash
72
+ python pio_lock.py capture -e esp32s3-idf
73
+ python pio_lock.py capture -e esp32s3-idf -e esp32s3 # multiple envs
74
+ python pio_lock.py capture -e esp32s3-idf --output artifacts/pio.lock.json
75
+ python pio_lock.py restore -e esp32s3-idf
76
+ python pio_lock.py check -e esp32s3-idf # exit 0 = match, 1 = drift
77
+ ```
78
+
79
+ | Flag | Description |
80
+ |------|-------------|
81
+ | `-d, --project-dir` | PlatformIO project directory (default: `.`) |
82
+ | `-e, --env` | Environment name (repeatable) |
83
+ | `--output` | Output path for capture (default: `pio.lock.json` in project dir) |
84
+
85
+ ### PlatformIO custom targets
86
+
87
+ Add `pio-lock` as a library dependency and reference its script:
88
+
89
+ ```ini
90
+ [env:myenv]
91
+ lib_deps =
92
+ https://github.com/m-mcgowan/pio-lock.git
93
+
94
+ extra_scripts =
95
+ pre:${PROJECT_LIBDEPS_DIR}/${PIOENV}/pio-lock/pio_lock.py
96
+ ```
97
+
98
+ PlatformIO downloads pio-lock into `.pio/libdeps/<env>/` and the
99
+ `extra_scripts` path references it there.
100
+
101
+ Alternatively, if you prefer not to use `lib_deps`, copy `pio_lock.py` into
102
+ your project and reference it directly:
103
+
104
+ ```ini
105
+ [env:myenv]
106
+ extra_scripts = pre:scripts/pio_lock.py
107
+ ```
108
+
109
+ This registers custom targets:
110
+
111
+ ```bash
112
+ pio run -t lock-capture # capture dependency versions
113
+ pio run -t lock-check # verify no drift
114
+ pio run -t lock-restore # restore from lockfile
115
+ ```
116
+
117
+ ## GitHub Actions
118
+
119
+ When `pio-lock` is configured via `lib_deps` and `extra_scripts` in your
120
+ `platformio.ini`, the custom targets are available directly:
121
+
122
+ ```yaml
123
+ - name: Restore locked dependencies
124
+ run: pio run -t lock-restore -e esp32s3-idf
125
+
126
+ - name: Verify lockfile
127
+ run: pio run -t lock-check -e esp32s3-idf
128
+ ```
129
+
130
+ Or integrate into an existing cache strategy:
131
+
132
+ ```yaml
133
+ - name: Cache PlatformIO Dependencies
134
+ id: cache
135
+ uses: actions/cache@v4
136
+ with:
137
+ path: |
138
+ ~/.platformio/platforms
139
+ ~/.platformio/packages
140
+ .pio/libdeps
141
+ key: pio-${{ hashFiles('pio.lock.json', 'platformio.ini') }}
142
+ restore-keys: pio-
143
+
144
+ - name: Restore locked dependencies
145
+ if: steps.cache.outputs.cache-hit != 'true'
146
+ run: pio run -t lock-restore -e esp32s3-idf
147
+
148
+ - name: Verify lockfile consistency
149
+ run: pio run -t lock-check -e esp32s3-idf
150
+ ```
151
+
152
+ ## Lockfile format
153
+
154
+ ```json
155
+ {
156
+ "_comment": "Generated by pio-lock. Do not edit manually.",
157
+ "generated_at": "2026-02-19T10:00:00Z",
158
+ "generated_from_commit": "abc1234",
159
+ "pio_core_version": "6.1.18",
160
+ "platform_url": "https://github.com/.../platform-espressif32.zip",
161
+ "global_packages": {
162
+ "framework-arduinoespressif32": "3.3.6",
163
+ "toolchain-xtensa-esp-elf": "14.2.0+20251107"
164
+ },
165
+ "envs": {
166
+ "esp32s3-idf": {
167
+ "libraries": [
168
+ {
169
+ "name": "Blues Wireless Notecard",
170
+ "type": "registry",
171
+ "version": "1.8.4",
172
+ "owner": "blues"
173
+ },
174
+ {
175
+ "name": "esp-nimble-cpp",
176
+ "type": "git",
177
+ "url": "https://github.com/user/esp-nimble-cpp.git",
178
+ "sha": "32e7ca2d2d387d74eaeac09751049778faea4040"
179
+ },
180
+ {
181
+ "name": "my-local-lib",
182
+ "type": "local",
183
+ "path": "file://lib/my-local-lib"
184
+ }
185
+ ]
186
+ }
187
+ }
188
+ }
189
+ ```
190
+
191
+ ## What this solves
192
+
193
+ | Problem | How pio-lock helps |
194
+ |---------|-------------------|
195
+ | `lib @ ^1.8.3` resolves to 1.8.4 today, 1.8.5 tomorrow | Locks to exact version (`==1.8.4`) |
196
+ | Git dep on a branch (`#fix/something`) | Locks to exact commit SHA |
197
+ | Unpinned git URL (no `#ref` at all) | Locks to exact commit SHA |
198
+ | Transitive deps resolved at install time | All resolved deps captured in lockfile |
199
+ | "What libraries were in release v1.2.3?" | Lockfile committed with the release tag |
200
+ | CI cache hit with stale deps | `check` command detects drift |
201
+
202
+ ## What this doesn't solve
203
+
204
+ - ~~**Bit-for-bit identical binaries** — ESP-IDF embeds timestamps; same source doesn't guarantee identical bytes~~ The build snapshot feature now sets `SOURCE_DATE_EPOCH` to produce deterministic `__DATE__`/`__TIME__` macros, enabling reproducible builds. The epoch is the HEAD commit's time on a clean tree, so the same commit gives the same epoch everywhere. The snapshot's epoch overrides any `SOURCE_DATE_EPOCH` already set in the environment
205
+ - **Global toolchain pinning** — Recorded for auditing; the platform URL already pins these in practice
206
+ - **PlatformIO Core version enforcement** — Recorded in lockfile for reference
207
+
208
+ ## Contributing
209
+
210
+ ### Developer setup
211
+
212
+ ```bash
213
+ ./scripts/dev-setup.sh
214
+ source .venv/bin/activate
215
+ ```
216
+
217
+ This creates a venv, installs dev dependencies, and sets up git hooks:
218
+ - **pre-commit** — ruff lint/format + unit tests
219
+ - **pre-push** — unit tests on regular pushes, full suite (unit + integration) on tag pushes
220
+
221
+ ### Running tests
222
+
223
+ ```bash
224
+ # Unit tests only (fast, no PIO needed)
225
+ pytest -m "not integration"
226
+
227
+ # All tests including integration (needs PIO installed)
228
+ pytest
229
+
230
+ # Integration tests only
231
+ pytest -m integration
232
+ ```
233
+
234
+ ### Reproducing a bug
235
+
236
+ To add a test case from a bug report:
237
+
238
+ 1. Create `tests/fixtures/<issue-name>/`
239
+ 2. Add a minimal `platformio.ini`
240
+ 3. Add `.pio/libdeps/<env>/` with the library state that triggers the bug
241
+ 4. Optionally add `expected.json` with expected output
242
+ 5. Add a `README.md` describing the scenario
243
+
244
+ See `tests/fixtures/README.md` for the full format.
245
+
246
+ ### Code quality
247
+
248
+ ```bash
249
+ ruff check . # lint
250
+ ruff format --check . # format
251
+ mypy pio_lock.py # type check
252
+ ```
253
+
254
+ Pre-commit hooks run lint + unit tests automatically. Integration tests run on tags.
255
+
256
+ ## Requirements
257
+
258
+ - Python 3.8+
259
+ - PlatformIO Core CLI (`pio`)
260
+ - git CLI
261
+
262
+ ## License
263
+
264
+ MIT
@@ -0,0 +1,247 @@
1
+ # pio-lock
2
+
3
+ Dependency lockfile for [PlatformIO](https://platformio.org/) — reproducible builds for embedded projects.
4
+
5
+ PlatformIO resolves library versions at install time but has no lockfile to record what was actually installed. If your `platformio.ini` uses semver ranges (`^1.8.3`), git branches, or unpinned git URLs, a fresh install can silently pick up different versions. `pio-lock` fixes this.
6
+
7
+ ## Status
8
+
9
+ | Feature | Design | Docs | Impl | Tests | Examples | Since | Updated |
10
+ |---------|--------|------|------|-------|----------|-------|---------|
11
+ | **Dependency scanning** | [pio_lock.py](pio_lock.py) | [README](README.md) | [pio_lock.py](pio_lock.py) | [test_scan](tests/test_scan.py) | [fixtures/](tests/fixtures/) | | |
12
+ | **Lockfile generation** | [pio_lock.py](pio_lock.py) | [README](README.md) | [pio_lock.py](pio_lock.py) | [test_commands](tests/test_commands.py) | [minimal-native](tests/fixtures/minimal-native/) | | |
13
+ | **Build snapshot** | [pio_lock.py](pio_lock.py) | [README](README.md) | [pio_lock.py](pio_lock.py) | [test_snapshot](tests/test_snapshot.py) | | | |
14
+ | **Outdated detection** | [pio_lock.py](pio_lock.py) | [README](README.md) | [pio_lock.py](pio_lock.py) | [test_outdated](tests/test_outdated.py) | | | |
15
+ | **Update commands** | [pio_lock.py](pio_lock.py) | [README](README.md) | [pio_lock.py](pio_lock.py) | [test_commands](tests/test_commands.py) | | | |
16
+ | **PlatformIO integration** | [pio_lock.py](pio_lock.py) | [README](README.md) | [pio_lock.py](pio_lock.py) | [test_integration](tests/test_integration.py) | | | |
17
+ | **GitHub Actions integration** | | [README](README.md) | | | | | |
18
+ | **Config source tracking** | [pio_lock.py](pio_lock.py) | | [pio_lock.py](pio_lock.py) | [test_commands](tests/test_commands.py) | | | |
19
+
20
+ ## How it works
21
+
22
+ `pio-lock` scans your installed PlatformIO dependencies and records their exact versions in a `pio.lock.json` file that you commit alongside `platformio.ini`. Later, `restore` reinstalls those exact versions, and `check` verifies nothing has drifted.
23
+
24
+ It captures:
25
+ - **Registry libraries** — exact resolved version from `.piopm` metadata
26
+ - **Git libraries** — exact commit SHA from the installed repo
27
+ - **Local libraries** — recorded for completeness, skipped during restore
28
+ - **Global packages** — framework, toolchain, and tool versions from `~/.platformio/packages/`
29
+ - **Platform URL** — the resolved platform specification
30
+
31
+ ## Quick start
32
+
33
+ ```bash
34
+ # 1. Install your dependencies normally
35
+ pio pkg install -e my_env
36
+
37
+ # 2. Capture the resolved state
38
+ python pio_lock.py capture -e my_env
39
+
40
+ # 3. Commit the lockfile
41
+ git add pio.lock.json
42
+
43
+ # 4. On another machine (or in CI), restore exact versions
44
+ python pio_lock.py restore -e my_env
45
+
46
+ # 5. Build as usual
47
+ pio run -e my_env
48
+ ```
49
+
50
+ ## Usage
51
+
52
+ ### Standalone CLI
53
+
54
+ ```bash
55
+ python pio_lock.py capture -e esp32s3-idf
56
+ python pio_lock.py capture -e esp32s3-idf -e esp32s3 # multiple envs
57
+ python pio_lock.py capture -e esp32s3-idf --output artifacts/pio.lock.json
58
+ python pio_lock.py restore -e esp32s3-idf
59
+ python pio_lock.py check -e esp32s3-idf # exit 0 = match, 1 = drift
60
+ ```
61
+
62
+ | Flag | Description |
63
+ |------|-------------|
64
+ | `-d, --project-dir` | PlatformIO project directory (default: `.`) |
65
+ | `-e, --env` | Environment name (repeatable) |
66
+ | `--output` | Output path for capture (default: `pio.lock.json` in project dir) |
67
+
68
+ ### PlatformIO custom targets
69
+
70
+ Add `pio-lock` as a library dependency and reference its script:
71
+
72
+ ```ini
73
+ [env:myenv]
74
+ lib_deps =
75
+ https://github.com/m-mcgowan/pio-lock.git
76
+
77
+ extra_scripts =
78
+ pre:${PROJECT_LIBDEPS_DIR}/${PIOENV}/pio-lock/pio_lock.py
79
+ ```
80
+
81
+ PlatformIO downloads pio-lock into `.pio/libdeps/<env>/` and the
82
+ `extra_scripts` path references it there.
83
+
84
+ Alternatively, if you prefer not to use `lib_deps`, copy `pio_lock.py` into
85
+ your project and reference it directly:
86
+
87
+ ```ini
88
+ [env:myenv]
89
+ extra_scripts = pre:scripts/pio_lock.py
90
+ ```
91
+
92
+ This registers custom targets:
93
+
94
+ ```bash
95
+ pio run -t lock-capture # capture dependency versions
96
+ pio run -t lock-check # verify no drift
97
+ pio run -t lock-restore # restore from lockfile
98
+ ```
99
+
100
+ ## GitHub Actions
101
+
102
+ When `pio-lock` is configured via `lib_deps` and `extra_scripts` in your
103
+ `platformio.ini`, the custom targets are available directly:
104
+
105
+ ```yaml
106
+ - name: Restore locked dependencies
107
+ run: pio run -t lock-restore -e esp32s3-idf
108
+
109
+ - name: Verify lockfile
110
+ run: pio run -t lock-check -e esp32s3-idf
111
+ ```
112
+
113
+ Or integrate into an existing cache strategy:
114
+
115
+ ```yaml
116
+ - name: Cache PlatformIO Dependencies
117
+ id: cache
118
+ uses: actions/cache@v4
119
+ with:
120
+ path: |
121
+ ~/.platformio/platforms
122
+ ~/.platformio/packages
123
+ .pio/libdeps
124
+ key: pio-${{ hashFiles('pio.lock.json', 'platformio.ini') }}
125
+ restore-keys: pio-
126
+
127
+ - name: Restore locked dependencies
128
+ if: steps.cache.outputs.cache-hit != 'true'
129
+ run: pio run -t lock-restore -e esp32s3-idf
130
+
131
+ - name: Verify lockfile consistency
132
+ run: pio run -t lock-check -e esp32s3-idf
133
+ ```
134
+
135
+ ## Lockfile format
136
+
137
+ ```json
138
+ {
139
+ "_comment": "Generated by pio-lock. Do not edit manually.",
140
+ "generated_at": "2026-02-19T10:00:00Z",
141
+ "generated_from_commit": "abc1234",
142
+ "pio_core_version": "6.1.18",
143
+ "platform_url": "https://github.com/.../platform-espressif32.zip",
144
+ "global_packages": {
145
+ "framework-arduinoespressif32": "3.3.6",
146
+ "toolchain-xtensa-esp-elf": "14.2.0+20251107"
147
+ },
148
+ "envs": {
149
+ "esp32s3-idf": {
150
+ "libraries": [
151
+ {
152
+ "name": "Blues Wireless Notecard",
153
+ "type": "registry",
154
+ "version": "1.8.4",
155
+ "owner": "blues"
156
+ },
157
+ {
158
+ "name": "esp-nimble-cpp",
159
+ "type": "git",
160
+ "url": "https://github.com/user/esp-nimble-cpp.git",
161
+ "sha": "32e7ca2d2d387d74eaeac09751049778faea4040"
162
+ },
163
+ {
164
+ "name": "my-local-lib",
165
+ "type": "local",
166
+ "path": "file://lib/my-local-lib"
167
+ }
168
+ ]
169
+ }
170
+ }
171
+ }
172
+ ```
173
+
174
+ ## What this solves
175
+
176
+ | Problem | How pio-lock helps |
177
+ |---------|-------------------|
178
+ | `lib @ ^1.8.3` resolves to 1.8.4 today, 1.8.5 tomorrow | Locks to exact version (`==1.8.4`) |
179
+ | Git dep on a branch (`#fix/something`) | Locks to exact commit SHA |
180
+ | Unpinned git URL (no `#ref` at all) | Locks to exact commit SHA |
181
+ | Transitive deps resolved at install time | All resolved deps captured in lockfile |
182
+ | "What libraries were in release v1.2.3?" | Lockfile committed with the release tag |
183
+ | CI cache hit with stale deps | `check` command detects drift |
184
+
185
+ ## What this doesn't solve
186
+
187
+ - ~~**Bit-for-bit identical binaries** — ESP-IDF embeds timestamps; same source doesn't guarantee identical bytes~~ The build snapshot feature now sets `SOURCE_DATE_EPOCH` to produce deterministic `__DATE__`/`__TIME__` macros, enabling reproducible builds. The epoch is the HEAD commit's time on a clean tree, so the same commit gives the same epoch everywhere. The snapshot's epoch overrides any `SOURCE_DATE_EPOCH` already set in the environment
188
+ - **Global toolchain pinning** — Recorded for auditing; the platform URL already pins these in practice
189
+ - **PlatformIO Core version enforcement** — Recorded in lockfile for reference
190
+
191
+ ## Contributing
192
+
193
+ ### Developer setup
194
+
195
+ ```bash
196
+ ./scripts/dev-setup.sh
197
+ source .venv/bin/activate
198
+ ```
199
+
200
+ This creates a venv, installs dev dependencies, and sets up git hooks:
201
+ - **pre-commit** — ruff lint/format + unit tests
202
+ - **pre-push** — unit tests on regular pushes, full suite (unit + integration) on tag pushes
203
+
204
+ ### Running tests
205
+
206
+ ```bash
207
+ # Unit tests only (fast, no PIO needed)
208
+ pytest -m "not integration"
209
+
210
+ # All tests including integration (needs PIO installed)
211
+ pytest
212
+
213
+ # Integration tests only
214
+ pytest -m integration
215
+ ```
216
+
217
+ ### Reproducing a bug
218
+
219
+ To add a test case from a bug report:
220
+
221
+ 1. Create `tests/fixtures/<issue-name>/`
222
+ 2. Add a minimal `platformio.ini`
223
+ 3. Add `.pio/libdeps/<env>/` with the library state that triggers the bug
224
+ 4. Optionally add `expected.json` with expected output
225
+ 5. Add a `README.md` describing the scenario
226
+
227
+ See `tests/fixtures/README.md` for the full format.
228
+
229
+ ### Code quality
230
+
231
+ ```bash
232
+ ruff check . # lint
233
+ ruff format --check . # format
234
+ mypy pio_lock.py # type check
235
+ ```
236
+
237
+ Pre-commit hooks run lint + unit tests automatically. Integration tests run on tags.
238
+
239
+ ## Requirements
240
+
241
+ - Python 3.8+
242
+ - PlatformIO Core CLI (`pio`)
243
+ - git CLI
244
+
245
+ ## License
246
+
247
+ MIT