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 +21 -0
- pio_lock-0.3.0/PKG-INFO +264 -0
- pio_lock-0.3.0/README.md +247 -0
- pio_lock-0.3.0/pio_lock.egg-info/PKG-INFO +264 -0
- pio_lock-0.3.0/pio_lock.egg-info/SOURCES.txt +14 -0
- pio_lock-0.3.0/pio_lock.egg-info/dependency_links.txt +1 -0
- pio_lock-0.3.0/pio_lock.egg-info/entry_points.txt +2 -0
- pio_lock-0.3.0/pio_lock.egg-info/top_level.txt +1 -0
- pio_lock-0.3.0/pio_lock.py +1710 -0
- pio_lock-0.3.0/pyproject.toml +78 -0
- pio_lock-0.3.0/setup.cfg +4 -0
- pio_lock-0.3.0/tests/test_commands.py +533 -0
- pio_lock-0.3.0/tests/test_integration.py +614 -0
- pio_lock-0.3.0/tests/test_outdated.py +1177 -0
- pio_lock-0.3.0/tests/test_scan.py +146 -0
- pio_lock-0.3.0/tests/test_snapshot.py +440 -0
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.
|
pio_lock-0.3.0/PKG-INFO
ADDED
|
@@ -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
|
pio_lock-0.3.0/README.md
ADDED
|
@@ -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
|