picolet 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- picolet/__init__.py +26 -0
- picolet/__main__.py +125 -0
- picolet/_bridge/__init__.py +0 -0
- picolet/_bridge/picolet-bridge.js +1 -0
- picolet/_runtime_data/RUNTIME_TAG +1 -0
- picolet/_runtime_data/__init__.py +0 -0
- picolet/_runtime_data/mbm.toml +71 -0
- picolet/_runtime_data/sbom/__init__.py +0 -0
- picolet/_runtime_data/sbom/runtime.toml +288 -0
- picolet/cli/RUNTIME_TAG +1 -0
- picolet/cli/__init__.py +0 -0
- picolet/cli/_paths.py +144 -0
- picolet/cli/_targets.py +92 -0
- picolet/cli/_trailer.py +56 -0
- picolet/cli/build_cmd.py +981 -0
- picolet/cli/dev_cmd.py +324 -0
- picolet/cli/init_cmd.py +225 -0
- picolet/cli/run_cmd.py +129 -0
- picolet/cli/runtime_resolver.py +810 -0
- picolet/cli/sbom_gen.py +873 -0
- picolet/cli/test_cmd.py +640 -0
- picolet/cli/validate_cmd.py +61 -0
- picolet/cli/validator.py +469 -0
- picolet/templates/__init__.py +0 -0
- picolet/templates/config-editor/package.json +22 -0
- picolet/templates/config-editor/picolet.toml +20 -0
- picolet/templates/config-editor/src/config_store.py +302 -0
- picolet/templates/config-editor/src/config_validator.py +148 -0
- picolet/templates/config-editor/src/difflib.py +171 -0
- picolet/templates/config-editor/src/main.py +74 -0
- picolet/templates/config-editor/src/micro_yaml.py +265 -0
- picolet/templates/config-editor/src/tomllib.py +490 -0
- picolet/templates/config-editor/tsconfig.json +19 -0
- picolet/templates/config-editor/tsconfig.node.json +10 -0
- picolet/templates/config-editor/ui/index.html +12 -0
- picolet/templates/config-editor/ui/public/fonts/JetBrainsMono-Regular.woff2 +0 -0
- picolet/templates/config-editor/ui/src/App.vue +27 -0
- picolet/templates/config-editor/ui/src/assets/fonts.css +12 -0
- picolet/templates/config-editor/ui/src/assets/main.css +367 -0
- picolet/templates/config-editor/ui/src/env.d.ts +2 -0
- picolet/templates/config-editor/ui/src/main.ts +7 -0
- picolet/templates/config-editor/ui/src/picolet.d.ts +26 -0
- picolet/templates/config-editor/ui/src/router/index.ts +17 -0
- picolet/templates/config-editor/ui/src/store.ts +46 -0
- picolet/templates/config-editor/ui/src/views/DiffView.vue +78 -0
- picolet/templates/config-editor/ui/src/views/EditView.vue +409 -0
- picolet/templates/config-editor/ui/src/views/PickerView.vue +234 -0
- picolet/templates/config-editor/vite.config.ts +18 -0
- picolet/templates/dashboard/package.json +22 -0
- picolet/templates/dashboard/picolet.toml +20 -0
- picolet/templates/dashboard/src/main.py +71 -0
- picolet/templates/dashboard/src/metrics_reader.py +463 -0
- picolet/templates/dashboard/tsconfig.json +19 -0
- picolet/templates/dashboard/tsconfig.node.json +10 -0
- picolet/templates/dashboard/ui/index.html +12 -0
- picolet/templates/dashboard/ui/public/fonts/Antonio-Bold.woff2 +0 -0
- picolet/templates/dashboard/ui/public/fonts/Antonio-Regular.woff2 +0 -0
- picolet/templates/dashboard/ui/public/fonts/DMSans-Medium.woff2 +0 -0
- picolet/templates/dashboard/ui/public/fonts/DMSans-Regular.woff2 +0 -0
- picolet/templates/dashboard/ui/public/fonts/JetBrainsMono-Regular.woff2 +0 -0
- picolet/templates/dashboard/ui/src/App.vue +55 -0
- picolet/templates/dashboard/ui/src/assets/fonts.css +43 -0
- picolet/templates/dashboard/ui/src/assets/main.css +401 -0
- picolet/templates/dashboard/ui/src/components/CpuChart.vue +87 -0
- picolet/templates/dashboard/ui/src/components/DiskChart.vue +91 -0
- picolet/templates/dashboard/ui/src/components/MemoryGauge.vue +96 -0
- picolet/templates/dashboard/ui/src/components/NetworkChart.vue +91 -0
- picolet/templates/dashboard/ui/src/components/ProcessList.vue +53 -0
- picolet/templates/dashboard/ui/src/components/SparklineStrip.vue +81 -0
- picolet/templates/dashboard/ui/src/components/TopStrip.vue +33 -0
- picolet/templates/dashboard/ui/src/env.d.ts +2 -0
- picolet/templates/dashboard/ui/src/main.ts +7 -0
- picolet/templates/dashboard/ui/src/picolet.d.ts +25 -0
- picolet/templates/dashboard/ui/src/router/index.ts +13 -0
- picolet/templates/dashboard/ui/src/store.ts +45 -0
- picolet/templates/dashboard/ui/src/utils/format.ts +51 -0
- picolet/templates/dashboard/ui/src/utils/svg.ts +84 -0
- picolet/templates/dashboard/ui/src/views/DashboardView.vue +56 -0
- picolet/templates/dashboard/vite.config.ts +18 -0
- picolet/templates/hello-cli/picolet.toml +4 -0
- picolet/templates/hello-cli/src/main.py +19 -0
- picolet/templates/hello-lvgl/picolet.toml +15 -0
- picolet/templates/hello-lvgl/src/main.py +52 -0
- picolet/templates/hello-vue/package.json +21 -0
- picolet/templates/hello-vue/picolet.toml +20 -0
- picolet/templates/hello-vue/src/main.py +40 -0
- picolet/templates/hello-vue/tsconfig.json +19 -0
- picolet/templates/hello-vue/tsconfig.node.json +10 -0
- picolet/templates/hello-vue/ui/index.html +12 -0
- picolet/templates/hello-vue/ui/src/App.vue +127 -0
- picolet/templates/hello-vue/ui/src/env.d.ts +2 -0
- picolet/templates/hello-vue/ui/src/main.ts +4 -0
- picolet/templates/hello-vue/ui/src/picolet.d.ts +28 -0
- picolet/templates/hello-vue/vite.config.ts +17 -0
- picolet/templates/hello-webview/picolet.toml +17 -0
- picolet/templates/hello-webview/src/main.py +22 -0
- picolet/templates/hello-webview/ui/app.js +24 -0
- picolet/templates/hello-webview/ui/index.html +17 -0
- picolet/templates/hello-webview/ui/style.css +15 -0
- picolet/templates/notes/package.json +23 -0
- picolet/templates/notes/picolet.toml +20 -0
- picolet/templates/notes/src/main.py +70 -0
- picolet/templates/notes/src/notes_store.py +178 -0
- picolet/templates/notes/tsconfig.json +19 -0
- picolet/templates/notes/tsconfig.node.json +10 -0
- picolet/templates/notes/ui/index.html +12 -0
- picolet/templates/notes/ui/public/fonts/source-sans-3.woff2 +0 -0
- picolet/templates/notes/ui/public/fonts/source-serif-4-italic.woff2 +0 -0
- picolet/templates/notes/ui/public/fonts/source-serif-4-roman.woff2 +0 -0
- picolet/templates/notes/ui/src/App.vue +15 -0
- picolet/templates/notes/ui/src/assets/fonts.css +34 -0
- picolet/templates/notes/ui/src/assets/main.css +459 -0
- picolet/templates/notes/ui/src/env.d.ts +2 -0
- picolet/templates/notes/ui/src/main.ts +7 -0
- picolet/templates/notes/ui/src/picolet.d.ts +25 -0
- picolet/templates/notes/ui/src/router/index.ts +17 -0
- picolet/templates/notes/ui/src/views/AboutView.vue +25 -0
- picolet/templates/notes/ui/src/views/EditView.vue +173 -0
- picolet/templates/notes/ui/src/views/ListView.vue +108 -0
- picolet/templates/notes/vite.config.ts +18 -0
- picolet/templates/pydfu/README.md +109 -0
- picolet/templates/pydfu/package.json +22 -0
- picolet/templates/pydfu/picolet.toml +29 -0
- picolet/templates/pydfu/src/_crc32.py +28 -0
- picolet/templates/pydfu/src/_dfu_file.py +79 -0
- picolet/templates/pydfu/src/_pydfu/__init__.py +2 -0
- picolet/templates/pydfu/src/_pydfu/pydfu.py +462 -0
- picolet/templates/pydfu/src/_usb/__init__.py +2 -0
- picolet/templates/pydfu/src/_usb/control.py +10 -0
- picolet/templates/pydfu/src/_usb/core.py +339 -0
- picolet/templates/pydfu/src/_usb/libusb-1.0.dll +0 -0
- picolet/templates/pydfu/src/_usb/util.py +21 -0
- picolet/templates/pydfu/src/main.py +379 -0
- picolet/templates/pydfu/src/pydfu_adapter.py +190 -0
- picolet/templates/pydfu/src/pydfu_mock.py +51 -0
- picolet/templates/pydfu/tsconfig.json +19 -0
- picolet/templates/pydfu/tsconfig.node.json +10 -0
- picolet/templates/pydfu/ui/index.html +12 -0
- picolet/templates/pydfu/ui/public/fonts/IBMPlexSans-Regular.woff2 +0 -0
- picolet/templates/pydfu/ui/public/fonts/IBMPlexSans-SemiBold.woff2 +0 -0
- picolet/templates/pydfu/ui/public/fonts/MonaspaceNeon-Bold.woff2 +0 -0
- picolet/templates/pydfu/ui/public/fonts/MonaspaceNeon-Regular.woff2 +0 -0
- picolet/templates/pydfu/ui/src/App.vue +111 -0
- picolet/templates/pydfu/ui/src/assets/fonts.css +27 -0
- picolet/templates/pydfu/ui/src/assets/main.css +156 -0
- picolet/templates/pydfu/ui/src/components/AuditStrip.vue +114 -0
- picolet/templates/pydfu/ui/src/components/DeviceDetail.vue +202 -0
- picolet/templates/pydfu/ui/src/components/DeviceList.vue +181 -0
- picolet/templates/pydfu/ui/src/components/HeaderRail.vue +131 -0
- picolet/templates/pydfu/ui/src/components/LedDot.vue +54 -0
- picolet/templates/pydfu/ui/src/env.d.ts +2 -0
- picolet/templates/pydfu/ui/src/main.ts +7 -0
- picolet/templates/pydfu/ui/src/picolet.d.ts +24 -0
- picolet/templates/pydfu/ui/src/router/index.ts +17 -0
- picolet/templates/pydfu/ui/src/views/FlashView.vue +429 -0
- picolet/templates/pydfu/ui/src/views/HomeView.vue +29 -0
- picolet/templates/pydfu/ui/src/views/LogView.vue +68 -0
- picolet/templates/pydfu/vite.config.ts +18 -0
- picolet/testing/__init__.py +17 -0
- picolet/testing/_chromium.py +90 -0
- picolet/testing/_harness.py +651 -0
- picolet/testing/_webkit.py +314 -0
- picolet-0.0.1.dist-info/METADATA +250 -0
- picolet-0.0.1.dist-info/RECORD +166 -0
- picolet-0.0.1.dist-info/WHEEL +4 -0
- picolet-0.0.1.dist-info/entry_points.txt +2 -0
picolet/__init__.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Picolet — compile Python programs to small self-contained native binaries.
|
|
2
|
+
|
|
3
|
+
Public surface from the host-installed CLI:
|
|
4
|
+
|
|
5
|
+
picolet.cli — command implementations (build, dev, init, run, test, ...)
|
|
6
|
+
picolet.testing — AppHarness + webview/CDP drivers for autonomous testing
|
|
7
|
+
(requires the [testing] extra: ``pip install picolet[testing]``)
|
|
8
|
+
picolet.templates — starter-app templates discoverable via importlib.resources
|
|
9
|
+
|
|
10
|
+
The ``picolet`` CLI command is exposed via the entry point declared in
|
|
11
|
+
pyproject.toml; ``python -m picolet`` invokes the same main().
|
|
12
|
+
|
|
13
|
+
Note: the in-binary ``import picolet`` inside a compiled MicroPython runtime
|
|
14
|
+
refers to a separate set of frozen modules (the dispatcher, IPC transport,
|
|
15
|
+
system event facade, etc.) that live under packages/picolet-runtime/python/
|
|
16
|
+
and never ship in this wheel.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
from importlib.metadata import PackageNotFoundError, version as _pkg_version
|
|
21
|
+
|
|
22
|
+
__version__ = _pkg_version("picolet")
|
|
23
|
+
except Exception:
|
|
24
|
+
__version__ = "0.0.0-dev"
|
|
25
|
+
|
|
26
|
+
__all__ = ("__version__",)
|
picolet/__main__.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# /// script
|
|
2
|
+
# requires-python = ">=3.11"
|
|
3
|
+
# dependencies = ["mpremote"]
|
|
4
|
+
# ///
|
|
5
|
+
"""
|
|
6
|
+
picolet — the Picolet framework CLI.
|
|
7
|
+
|
|
8
|
+
Invocation paths:
|
|
9
|
+
uv run packages/picolet/picolet/__main__.py <args> # zero-install
|
|
10
|
+
picolet <args> # after pip install
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import argparse
|
|
15
|
+
import sys
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
# When invoked via `uv run picolet/__main__.py`, the parent of the picolet
|
|
19
|
+
# package directory (packages/picolet/) is not automatically on sys.path.
|
|
20
|
+
# Insert it so `from picolet.cli import ...` resolves correctly in both the
|
|
21
|
+
# uv-run and installed invocation paths.
|
|
22
|
+
_PKG_PARENT = Path(__file__).parent.parent # packages/picolet/
|
|
23
|
+
if str(_PKG_PARENT) not in sys.path:
|
|
24
|
+
sys.path.insert(0, str(_PKG_PARENT))
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
from importlib.metadata import PackageNotFoundError, version as _pkg_version
|
|
28
|
+
|
|
29
|
+
VERSION = _pkg_version("picolet")
|
|
30
|
+
except Exception:
|
|
31
|
+
VERSION = "0.0.1-dev"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
_TOP_DESCRIPTION = """\
|
|
35
|
+
Picolet builds single-binary MicroPython apps with optional webview or LVGL GUI.
|
|
36
|
+
Each app is scaffolded from a template, compiled to .mpy bytecode, and packed
|
|
37
|
+
with a pre-built runtime into one self-contained executable.
|
|
38
|
+
|
|
39
|
+
Quick start:
|
|
40
|
+
picolet init my-app --template hello-vue
|
|
41
|
+
cd my-app
|
|
42
|
+
picolet dev
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
_TOP_EPILOG = """\
|
|
46
|
+
Run `picolet <command> --help` for command-specific options and examples.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
51
|
+
parser = argparse.ArgumentParser(
|
|
52
|
+
prog="picolet",
|
|
53
|
+
description=_TOP_DESCRIPTION,
|
|
54
|
+
epilog=_TOP_EPILOG,
|
|
55
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
56
|
+
)
|
|
57
|
+
parser.add_argument(
|
|
58
|
+
"--version", "-V",
|
|
59
|
+
action="version",
|
|
60
|
+
version=f"picolet {VERSION}",
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
subparsers = parser.add_subparsers(
|
|
64
|
+
title="subcommands",
|
|
65
|
+
dest="subcommand",
|
|
66
|
+
metavar="<command>",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Register subcommands.
|
|
70
|
+
from picolet.cli import build_cmd, dev_cmd, init_cmd, run_cmd, test_cmd, validate_cmd
|
|
71
|
+
|
|
72
|
+
init_cmd.add_parser(subparsers)
|
|
73
|
+
validate_cmd.add_parser(subparsers)
|
|
74
|
+
build_cmd.add_parser(subparsers)
|
|
75
|
+
run_cmd.add_parser(subparsers)
|
|
76
|
+
dev_cmd.add_parser(subparsers)
|
|
77
|
+
test_cmd.add_parser(subparsers)
|
|
78
|
+
|
|
79
|
+
# 'picolet help <subcommand>' as an alias for 'picolet <subcommand> --help'.
|
|
80
|
+
help_parser = subparsers.add_parser(
|
|
81
|
+
"help",
|
|
82
|
+
help="show help for a subcommand",
|
|
83
|
+
)
|
|
84
|
+
help_parser.add_argument(
|
|
85
|
+
"topic",
|
|
86
|
+
nargs="?",
|
|
87
|
+
default=None,
|
|
88
|
+
metavar="<command>",
|
|
89
|
+
help="subcommand to show help for",
|
|
90
|
+
)
|
|
91
|
+
help_parser.set_defaults(func=_help_cmd)
|
|
92
|
+
|
|
93
|
+
return parser
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _help_cmd(args) -> None:
|
|
97
|
+
"""Handle `picolet help [<subcommand>]`."""
|
|
98
|
+
if args.topic:
|
|
99
|
+
# Re-parse with --help appended so argparse prints the subcommand help.
|
|
100
|
+
sys.argv = ["picolet", args.topic, "--help"]
|
|
101
|
+
_build_parser().parse_args()
|
|
102
|
+
else:
|
|
103
|
+
_build_parser().print_help()
|
|
104
|
+
sys.exit(0)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def main() -> None:
|
|
108
|
+
parser = _build_parser()
|
|
109
|
+
args = parser.parse_args()
|
|
110
|
+
|
|
111
|
+
if args.subcommand is None:
|
|
112
|
+
parser.print_help()
|
|
113
|
+
sys.exit(0)
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
rc = args.func(args)
|
|
117
|
+
except NotImplementedError as exc:
|
|
118
|
+
print(f"error: not implemented: {exc}", file=sys.stderr)
|
|
119
|
+
sys.exit(1)
|
|
120
|
+
if rc is not None:
|
|
121
|
+
sys.exit(rc)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
if __name__ == "__main__":
|
|
125
|
+
main()
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var __picolet_iife__=(()=>{var w=1,i=new Map,d=new Map;function g(t){let e=JSON.stringify(t),n=window;if(n.webkit&&n.webkit.messageHandlers&&n.webkit.messageHandlers.picolet){n.webkit.messageHandlers.picolet.postMessage(e);return}if(n.chrome&&n.chrome.webview&&typeof n.chrome.webview.postMessage=="function"){n.chrome.webview.postMessage(e);return}throw new Error("[picolet] no host postMessage channel available")}window.__picolet_recv=function(t){var n,r;let e;try{e=JSON.parse(t)}catch(o){console.warn("[picolet] malformed inbound JSON:",o);return}if(Number.isInteger(e.id)&&e.id>0&&"ok"in e){let o=i.get(e.id);if(!o){console.warn("[picolet] no pending invoke for id",e.id);return}if(i.delete(e.id),e.ok)o.resolve(e.result);else{let s=e.error,a=new Error((n=s==null?void 0:s.message)!=null?n:String(e.error));a.name=(r=s==null?void 0:s.type)!=null?r:"Error",o.reject(a)}}else if(typeof e.event=="string"){let o=d.get(e.event);if(o)for(let s of Array.from(o))try{s(e.data)}catch(a){console.error("[picolet] on() handler threw:",a)}}else console.warn("[picolet] unrecognised inbound message:",e)};window.picolet={invoke(t,e=null,n){let r=w++,o=new Promise((s,a)=>{if(i.set(r,{resolve:s,reject:a}),n!=null&&n.timeout){let u=setTimeout(()=>{i.delete(r)&&a(new Error("invoke timeout"))},n.timeout),c=i.get(r);i.set(r,{resolve:l=>{clearTimeout(u),c.resolve(l)},reject:l=>{clearTimeout(u),c.reject(l)}})}});return g({id:r,cmd:t,args:e}),o},on(t,e){return d.has(t)||d.set(t,new Set),d.get(t).add(e),function(){var r;(r=d.get(t))==null||r.delete(e)}},emit(t,e=null){g({event:t,data:e})},_drainPending(t){let e=new Error(t);for(let{reject:n}of i.values())n(e);i.clear()}};})();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
runtime-v0.0.1
|
|
File without changes
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# micropython-branch-manager (mbm)
|
|
2
|
+
# Install: pip install micropython-branch-manager
|
|
3
|
+
# or: uv tool install micropython-branch-manager
|
|
4
|
+
# Usage: mbm --help
|
|
5
|
+
|
|
6
|
+
[[submodules]]
|
|
7
|
+
path = "micropython"
|
|
8
|
+
integration_branch = "integration"
|
|
9
|
+
|
|
10
|
+
# --- Base set inherited from pydfu-win ---
|
|
11
|
+
|
|
12
|
+
[[submodules.branches]]
|
|
13
|
+
name = "pr/lib-pyusb-windows"
|
|
14
|
+
pr_url = "https://github.com/andrewleech/micropython/pull/38"
|
|
15
|
+
pr_number = 38
|
|
16
|
+
title = "lib/micropython-lib: Update pyusb with Windows support."
|
|
17
|
+
|
|
18
|
+
[[submodules.branches]]
|
|
19
|
+
name = "pr/mkrules-exe-fix"
|
|
20
|
+
pr_url = "https://github.com/andrewleech/micropython/pull/39"
|
|
21
|
+
pr_number = 39
|
|
22
|
+
title = "py/mkrules.mk: Allow PROG to be set on the command line for .exe targets."
|
|
23
|
+
|
|
24
|
+
[[submodules.branches]]
|
|
25
|
+
name = "pr/mkrules-frozen-str"
|
|
26
|
+
pr_url = "https://github.com/andrewleech/micropython/pull/40"
|
|
27
|
+
pr_number = 40
|
|
28
|
+
title = "py/mkrules.mk: Make MICROPY_MODULE_FROZEN_STR conditional on compiler."
|
|
29
|
+
|
|
30
|
+
[[submodules.branches]]
|
|
31
|
+
name = "pr/gc-add-heap"
|
|
32
|
+
pr_url = "https://github.com/andrewleech/micropython/pull/41"
|
|
33
|
+
pr_number = 41
|
|
34
|
+
title = "py/modgc: Add gc.add_heap() function for runtime heap expansion."
|
|
35
|
+
|
|
36
|
+
[[submodules.branches]]
|
|
37
|
+
name = "pr/ports-windows-ffi"
|
|
38
|
+
pr_url = "https://github.com/andrewleech/micropython/pull/42"
|
|
39
|
+
pr_number = 42
|
|
40
|
+
title = "ports/windows: Add FFI module support with libffi."
|
|
41
|
+
|
|
42
|
+
[[submodules.branches]]
|
|
43
|
+
name = "pr/unix-windows-romfs"
|
|
44
|
+
pr_url = "https://github.com/andrewleech/micropython/pull/43"
|
|
45
|
+
pr_number = 43
|
|
46
|
+
title = "ports/unix,windows: Add embedded romfs support with auto-mount."
|
|
47
|
+
|
|
48
|
+
[[submodules.branches]]
|
|
49
|
+
name = "pr/ports-windows-variant-overrides"
|
|
50
|
+
pr_url = "https://github.com/andrewleech/micropython/pull/44"
|
|
51
|
+
pr_number = 44
|
|
52
|
+
title = "ports/windows: Allow variant override of common config defines."
|
|
53
|
+
|
|
54
|
+
[[submodules.branches]]
|
|
55
|
+
name = "manifest_c_module"
|
|
56
|
+
pr_url = "https://github.com/micropython/micropython/pull/18229"
|
|
57
|
+
pr_number = 18229
|
|
58
|
+
title = "py: Add c_module() manifest function for user C modules."
|
|
59
|
+
|
|
60
|
+
[[submodules.branches]]
|
|
61
|
+
name = "pr/windows-extra-src-c"
|
|
62
|
+
pr_url = "https://github.com/andrewleech/micropython/pull/PLACEHOLDER"
|
|
63
|
+
pr_number = 0
|
|
64
|
+
title = "ports/windows: Add EXTRA_SRC_C hook for variant-supplied source files."
|
|
65
|
+
|
|
66
|
+
# --- Renderer-specific PRs added here as they're written ---
|
|
67
|
+
# - picolet webview bridge bootstrap
|
|
68
|
+
# - LVGL binding integration
|
|
69
|
+
# - picolet IPC C module
|
|
70
|
+
# - picolet window C module
|
|
71
|
+
# - macOS port (when added)
|
|
File without changes
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
# packages/picolet-runtime/sbom/runtime.toml
|
|
2
|
+
#
|
|
3
|
+
# Hand-curated component declarations for every native dependency that
|
|
4
|
+
# ships inside a built picolet runtime. This is the authoritative source
|
|
5
|
+
# for components not discoverable from Python package metadata (C
|
|
6
|
+
# libraries, vendored DLLs, submodule-pinned bindings).
|
|
7
|
+
#
|
|
8
|
+
# Schema
|
|
9
|
+
# ------
|
|
10
|
+
# Each entry in the [[component]] array MUST have:
|
|
11
|
+
# name string human-readable component name
|
|
12
|
+
# version string version string (SemVer, git SHA, or "picolet-authored")
|
|
13
|
+
# licence string SPDX expression (or LicenseRef-* for non-SPDX)
|
|
14
|
+
# source_url string canonical source / homepage URL
|
|
15
|
+
# link_type string "static" | "dynamic" | "build-time-only"
|
|
16
|
+
#
|
|
17
|
+
# Optional keys:
|
|
18
|
+
# variants list inclusion filter — entry applies only to listed variants
|
|
19
|
+
# omit entry applies to all variants
|
|
20
|
+
# targets list inclusion filter — entry applies only to listed targets
|
|
21
|
+
# omit entry applies to all targets
|
|
22
|
+
# purl string package URL (pkg:type/namespace/name@version)
|
|
23
|
+
# notes string free-text; citations, caveats, PR lists, etc.
|
|
24
|
+
#
|
|
25
|
+
# link_type meanings:
|
|
26
|
+
# static object code is part of the runtime binary
|
|
27
|
+
# dynamic loaded at runtime; binary carries no copy
|
|
28
|
+
# build-time-only used only during the build; absent from the shipped artifact
|
|
29
|
+
#
|
|
30
|
+
# Supported variants: cli | webview | lvgl
|
|
31
|
+
# Supported targets: linux-x64 | windows-x64 | macos-x64 | macos-arm64
|
|
32
|
+
|
|
33
|
+
# ---------------------------------------------------------------------------
|
|
34
|
+
# Universal components — present in every runtime artifact
|
|
35
|
+
# ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
[[component]]
|
|
38
|
+
name = "MicroPython"
|
|
39
|
+
version = "1.24.0"
|
|
40
|
+
licence = "MIT"
|
|
41
|
+
source_url = "https://github.com/micropython/micropython"
|
|
42
|
+
link_type = "static"
|
|
43
|
+
purl = "pkg:github/micropython/micropython@1.24.0"
|
|
44
|
+
notes = "Integration PRs are MicroPython-derived patches sharing the MicroPython MIT licence; the generator injects the full PR list from mbm.toml automatically."
|
|
45
|
+
|
|
46
|
+
[[component]]
|
|
47
|
+
name = "libffi"
|
|
48
|
+
version = "3.4.6"
|
|
49
|
+
licence = "MIT"
|
|
50
|
+
source_url = "https://github.com/libffi/libffi"
|
|
51
|
+
link_type = "static"
|
|
52
|
+
purl = "pkg:github/libffi/libffi@3.4.6"
|
|
53
|
+
notes = "Statically linked into all runtime variants to support ctypes / ffi module."
|
|
54
|
+
|
|
55
|
+
# ---------------------------------------------------------------------------
|
|
56
|
+
# LVGL-specific components (variants = ["lvgl"])
|
|
57
|
+
# ---------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
[[component]]
|
|
60
|
+
name = "lv_binding_micropython"
|
|
61
|
+
version = "4a569cd"
|
|
62
|
+
licence = "MIT"
|
|
63
|
+
source_url = "https://github.com/lvgl/lv_binding_micropython"
|
|
64
|
+
link_type = "static"
|
|
65
|
+
variants = ["lvgl"]
|
|
66
|
+
purl = "pkg:github/lvgl/lv_binding_micropython@4a569cd"
|
|
67
|
+
notes = "MicroPython binding for LVGL; git SHA pinned via submodule."
|
|
68
|
+
|
|
69
|
+
[[component]]
|
|
70
|
+
name = "LVGL"
|
|
71
|
+
version = "9.x"
|
|
72
|
+
licence = "MIT"
|
|
73
|
+
source_url = "https://github.com/lvgl/lvgl"
|
|
74
|
+
link_type = "static"
|
|
75
|
+
variants = ["lvgl"]
|
|
76
|
+
purl = "pkg:github/lvgl/lvgl@9.x"
|
|
77
|
+
notes = "Pulled in as a nested submodule of lv_binding_micropython."
|
|
78
|
+
|
|
79
|
+
# SDL2 — dynamic on Linux (system package) and Windows (upstream MinGW release); dynamic via brew on macOS.
|
|
80
|
+
|
|
81
|
+
[[component]]
|
|
82
|
+
name = "SDL2"
|
|
83
|
+
version = "2.0.20"
|
|
84
|
+
licence = "Zlib"
|
|
85
|
+
source_url = "https://www.libsdl.org/"
|
|
86
|
+
link_type = "dynamic"
|
|
87
|
+
variants = ["lvgl"]
|
|
88
|
+
targets = ["linux-x64"]
|
|
89
|
+
purl = "pkg:github/libsdl-org/SDL@release-2.0.20"
|
|
90
|
+
notes = "System-provided SDL2 on Ubuntu 22.04; loaded dynamically via dlopen at runtime."
|
|
91
|
+
|
|
92
|
+
[[component]]
|
|
93
|
+
name = "SDL2"
|
|
94
|
+
version = "2.30.10"
|
|
95
|
+
licence = "Zlib"
|
|
96
|
+
source_url = "https://www.libsdl.org/"
|
|
97
|
+
link_type = "dynamic"
|
|
98
|
+
variants = ["lvgl"]
|
|
99
|
+
targets = ["windows-x64"]
|
|
100
|
+
purl = "pkg:github/libsdl-org/SDL@release-2.30.10"
|
|
101
|
+
notes = "Official upstream MinGW devel release (SDL2-devel-2.30.10-mingw.tar.gz). SDL2.dll redistributed alongside the runtime binary. Linked via libSDL2.dll.a import library at build time. The NFR-3 ceiling for windows-x64/lvgl is relaxed to 3 MiB to accommodate the unstripped DLL; a future pass targeting < 2 MiB via custom from-source build (-ffunction-sections + --gc-sections) is on the roadmap."
|
|
102
|
+
|
|
103
|
+
[[component]]
|
|
104
|
+
name = "SDL2"
|
|
105
|
+
version = "2.x.x (brew)"
|
|
106
|
+
licence = "Zlib"
|
|
107
|
+
source_url = "https://www.libsdl.org/"
|
|
108
|
+
link_type = "dynamic"
|
|
109
|
+
variants = ["lvgl"]
|
|
110
|
+
targets = ["macos-x64", "macos-arm64"]
|
|
111
|
+
notes = "Installed via 'brew install sdl2'. SDL2 Cocoa backend provides the macOS window and event loop. Headers resolved from brew --prefix sdl2; dylib loaded dynamically at runtime. NFR-MAC-4 accepts dynamic SDL2 on macOS (equivalent to system libSDL2.so on Linux)."
|
|
112
|
+
|
|
113
|
+
# ---------------------------------------------------------------------------
|
|
114
|
+
# WebView components (variants = ["webview"])
|
|
115
|
+
# ---------------------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
[[component]]
|
|
118
|
+
name = "WebKitGTK"
|
|
119
|
+
version = "4.1"
|
|
120
|
+
licence = "LGPL-2.1-or-later"
|
|
121
|
+
source_url = "https://webkitgtk.org/"
|
|
122
|
+
link_type = "dynamic"
|
|
123
|
+
variants = ["webview"]
|
|
124
|
+
targets = ["linux-x64"]
|
|
125
|
+
purl = "pkg:deb/ubuntu/libwebkit2gtk-4.1-0"
|
|
126
|
+
notes = "LGPL-2.1-or-later; loaded dynamically — the Picolet runtime binary does not statically incorporate any LGPL object code. Relinking is possible by replacing the system WebKitGTK package. NFR-5 is satisfied."
|
|
127
|
+
|
|
128
|
+
[[component]]
|
|
129
|
+
name = "WebKit.framework (Apple system framework)"
|
|
130
|
+
version = "bundled-with-macos"
|
|
131
|
+
licence = "LicenseRef-Apple-System-Framework"
|
|
132
|
+
source_url = "https://developer.apple.com/documentation/webkit"
|
|
133
|
+
link_type = "dynamic"
|
|
134
|
+
variants = ["webview"]
|
|
135
|
+
targets = ["macos-x64", "macos-arm64"]
|
|
136
|
+
notes = "Apple system framework; not redistributed. Reached via -framework WebKit at link time; all ObjC calls go through objc_msgSend (libobjc.A.dylib) at runtime — no static ObjC++ object code is incorporated. Redistribution permitted under the Apple Developer Programme licence."
|
|
137
|
+
|
|
138
|
+
[[component]]
|
|
139
|
+
name = "Cocoa / AppKit (Apple system framework)"
|
|
140
|
+
version = "bundled-with-macos"
|
|
141
|
+
licence = "LicenseRef-Apple-System-Framework"
|
|
142
|
+
source_url = "https://developer.apple.com/documentation/appkit"
|
|
143
|
+
link_type = "dynamic"
|
|
144
|
+
variants = ["webview"]
|
|
145
|
+
targets = ["macos-x64", "macos-arm64"]
|
|
146
|
+
notes = "Apple system framework; not redistributed. Linked as -framework Cocoa for NSApplication, NSWindow, NSView. All calls go through objc_msgSend at runtime. Redistribution permitted under the Apple Developer Programme licence."
|
|
147
|
+
|
|
148
|
+
[[component]]
|
|
149
|
+
name = "Foundation (Apple system framework)"
|
|
150
|
+
version = "bundled-with-macos"
|
|
151
|
+
licence = "LicenseRef-Apple-System-Framework"
|
|
152
|
+
source_url = "https://developer.apple.com/documentation/foundation"
|
|
153
|
+
link_type = "dynamic"
|
|
154
|
+
variants = ["webview"]
|
|
155
|
+
targets = ["macos-x64", "macos-arm64"]
|
|
156
|
+
notes = "Apple system framework; not redistributed. Linked as -framework Foundation for NSString, NSData, NSURL, NSDictionary helpers used in picolet_webview_mac.c. Redistribution permitted under the Apple Developer Programme licence."
|
|
157
|
+
|
|
158
|
+
[[component]]
|
|
159
|
+
name = "Microsoft.Web.WebView2"
|
|
160
|
+
version = "1.0.2210.55"
|
|
161
|
+
licence = "LicenseRef-MS-WebView2-Fixed"
|
|
162
|
+
source_url = "https://aka.ms/webview2"
|
|
163
|
+
link_type = "dynamic"
|
|
164
|
+
variants = ["webview"]
|
|
165
|
+
targets = ["windows-x64"]
|
|
166
|
+
purl = "pkg:nuget/Microsoft.Web.WebView2@1.0.2210.55"
|
|
167
|
+
notes = "Redistributable under Microsoft's fixed-terms licence; not an OSI-approved SPDX identifier. Governing licence: https://www.nuget.org/packages/Microsoft.Web.WebView2/1.0.2210.55/License"
|
|
168
|
+
|
|
169
|
+
[[component]]
|
|
170
|
+
name = "WebView2Loader.dll"
|
|
171
|
+
version = "1.0.2210.55"
|
|
172
|
+
licence = "LicenseRef-MS-WebView2-Fixed"
|
|
173
|
+
source_url = "https://aka.ms/webview2"
|
|
174
|
+
link_type = "dynamic"
|
|
175
|
+
variants = ["webview"]
|
|
176
|
+
targets = ["windows-x64"]
|
|
177
|
+
purl = "pkg:nuget/Microsoft.Web.WebView2@1.0.2210.55#WebView2Loader.dll"
|
|
178
|
+
notes = "Loader DLL bundled inside the app romfs at picolet/WebView2Loader.dll; extracted to %LOCALAPPDATA%\\picolet\\<pid>\\ at runtime. Governing licence: https://www.nuget.org/packages/Microsoft.Web.WebView2/1.0.2210.55/License"
|
|
179
|
+
|
|
180
|
+
[[component]]
|
|
181
|
+
name = "WebView2_min.h (derived)"
|
|
182
|
+
version = "picolet-authored"
|
|
183
|
+
licence = "MIT"
|
|
184
|
+
source_url = "https://github.com/andrewleech/picolet"
|
|
185
|
+
link_type = "build-time-only"
|
|
186
|
+
variants = ["webview"]
|
|
187
|
+
targets = ["windows-x64"]
|
|
188
|
+
notes = "Picolet-authored minimal header derived from the public Microsoft WebView2 C API; used only at build time. NFR-6: all Picolet-authored code is MIT."
|
|
189
|
+
|
|
190
|
+
# ---------------------------------------------------------------------------
|
|
191
|
+
# pydfu example — libusb-1.0 (loaded at runtime; platform-specific)
|
|
192
|
+
# ---------------------------------------------------------------------------
|
|
193
|
+
|
|
194
|
+
[[component]]
|
|
195
|
+
name = "libusb-1.0"
|
|
196
|
+
version = "1.0.27"
|
|
197
|
+
licence = "LGPL-2.1-or-later"
|
|
198
|
+
source_url = "https://github.com/libusb/libusb"
|
|
199
|
+
link_type = "dynamic"
|
|
200
|
+
targets = ["linux-x64"]
|
|
201
|
+
purl = "pkg:deb/ubuntu/libusb-1.0-0@1.0.27-1"
|
|
202
|
+
notes = "LGPL-2.1-or-later; loaded dynamically via ffi.open('libusb-1.0.so.0') at runtime by the pydfu example app. Version tracks the Ubuntu package libusb-1.0-0 (2:1.0.27-1), not the libusb API series. The Picolet runtime binary does not statically incorporate any LGPL object code. Relinking is possible by replacing the system libusb-1.0-0 package. NFR-5 is satisfied."
|
|
203
|
+
|
|
204
|
+
[[component]]
|
|
205
|
+
name = "libusb-1.0 (macOS dylib)"
|
|
206
|
+
version = "1.0.27"
|
|
207
|
+
licence = "LGPL-2.1-or-later"
|
|
208
|
+
source_url = "https://github.com/libusb/libusb"
|
|
209
|
+
link_type = "dynamic"
|
|
210
|
+
targets = ["macos-x64", "macos-arm64"]
|
|
211
|
+
purl = "pkg:brew/libusb@1.0.27"
|
|
212
|
+
notes = "LGPL-2.1-or-later; loaded dynamically via ffi.open('libusb-1.0.dylib') at runtime by the pydfu example app (FR-EX-MAC-2). Installed via 'brew install libusb'. On x64 the dylib resolves from /usr/local/lib; on arm64 from /opt/homebrew/lib. The Picolet runtime binary does not statically incorporate any LGPL object code. NFR-MAC-8 is satisfied."
|
|
213
|
+
|
|
214
|
+
[[component]]
|
|
215
|
+
name = "libusb-1.0 (Windows DLL)"
|
|
216
|
+
version = "1.0.26"
|
|
217
|
+
licence = "LGPL-2.1-or-later"
|
|
218
|
+
source_url = "https://github.com/libusb/libusb"
|
|
219
|
+
link_type = "dynamic"
|
|
220
|
+
targets = ["windows-x64"]
|
|
221
|
+
purl = "pkg:github/libusb/libusb@v1.0.26"
|
|
222
|
+
notes = "LGPL-2.1-or-later; vendored Windows x64 DLL bundled in the pydfu example romfs at /rom/src/_usb/libusb-1.0.dll. Loaded dynamically via ffi.open() at runtime; the Picolet runtime binary contains no LGPL object code. LGPL-2.1-or-later compliance: dynamic linkage only; users can replace the DLL with their own build. DLL sourced from the pydfu-win submodule (nickel-org/libusb-package build pipeline). NFR-5 is satisfied."
|
|
223
|
+
|
|
224
|
+
# ---------------------------------------------------------------------------
|
|
225
|
+
# pydfu example fonts — bundled in romfs/ui/fonts/ (PH19)
|
|
226
|
+
# ---------------------------------------------------------------------------
|
|
227
|
+
|
|
228
|
+
[[component]]
|
|
229
|
+
name = "Monaspace Neon"
|
|
230
|
+
version = "1.101"
|
|
231
|
+
licence = "OFL-1.1"
|
|
232
|
+
source_url = "https://github.com/githubnext/monaspace"
|
|
233
|
+
link_type = "static"
|
|
234
|
+
purl = "pkg:github/githubnext/monaspace@v1.101"
|
|
235
|
+
notes = "OFL-1.1 (SIL Open Font Licence). Used as display/monospace font in the pydfu example UI (square-tech variant of the Monaspace family; industrial-aesthetic replacement for JetBrains Mono per NFR-EX-AESTHETIC). Woff2 files bundled in romfs at /rom/ui/fonts/MonaspaceNeon-Regular.woff2 and /rom/ui/fonts/MonaspaceNeon-Bold.woff2 (sourced from githubnext/monaspace v1.101)."
|
|
236
|
+
|
|
237
|
+
[[component]]
|
|
238
|
+
name = "IBM Plex Sans"
|
|
239
|
+
version = "6.4.0"
|
|
240
|
+
licence = "OFL-1.1"
|
|
241
|
+
source_url = "https://github.com/IBM/plex"
|
|
242
|
+
link_type = "static"
|
|
243
|
+
purl = "pkg:github/IBM/plex@v6.4.0"
|
|
244
|
+
notes = "OFL-1.1 (SIL Open Font Licence). Used as body font in the pydfu example UI. Latin subset woff2 files (Regular + SemiBold) bundled in romfs at /rom/ui/fonts/. Sourced from @fontsource/ibm-plex-sans npm package."
|
|
245
|
+
|
|
246
|
+
# ---------------------------------------------------------------------------
|
|
247
|
+
# notes example fonts — bundled in romfs/ui/fonts/ (PH20)
|
|
248
|
+
# ---------------------------------------------------------------------------
|
|
249
|
+
|
|
250
|
+
[[component]]
|
|
251
|
+
name = "Source Serif 4"
|
|
252
|
+
version = "4.005"
|
|
253
|
+
licence = "OFL-1.1"
|
|
254
|
+
source_url = "https://github.com/adobe-fonts/source-serif"
|
|
255
|
+
link_type = "static"
|
|
256
|
+
purl = "pkg:github/adobe-fonts/source-serif@4.005R"
|
|
257
|
+
notes = "OFL-1.1 (SIL Open Font Licence). Used as display/heading font in the notes example UI. Variable woff2 files (Roman + Italic, weight range 200-900) bundled in romfs at /rom/ui/fonts/. Renamed from canonical axis-tag filenames to avoid Vite bracket-character issues (O4 in PH20 plan)."
|
|
258
|
+
|
|
259
|
+
[[component]]
|
|
260
|
+
name = "Source Sans 3"
|
|
261
|
+
version = "3.052"
|
|
262
|
+
licence = "OFL-1.1"
|
|
263
|
+
source_url = "https://github.com/adobe-fonts/source-sans"
|
|
264
|
+
link_type = "static"
|
|
265
|
+
purl = "pkg:github/adobe-fonts/source-sans@3.052R"
|
|
266
|
+
notes = "OFL-1.1 (SIL Open Font Licence). Used as body/UI font in the notes example UI. Variable woff2 (Upright, weight range 200-900) bundled in romfs at /rom/ui/fonts/source-sans-3.woff2."
|
|
267
|
+
|
|
268
|
+
# ---------------------------------------------------------------------------
|
|
269
|
+
# dashboard example fonts — bundled in romfs/ui/fonts/ (PH22)
|
|
270
|
+
# ---------------------------------------------------------------------------
|
|
271
|
+
|
|
272
|
+
[[component]]
|
|
273
|
+
name = "Antonio"
|
|
274
|
+
version = "22"
|
|
275
|
+
licence = "OFL-1.1"
|
|
276
|
+
source_url = "https://fonts.google.com/specimen/Antonio"
|
|
277
|
+
link_type = "static"
|
|
278
|
+
purl = "pkg:generic/google-fonts/Antonio@v22"
|
|
279
|
+
notes = "OFL-1.1 (SIL Open Font Licence). Used as condensed display font in the dashboard example UI (OFL fallback for GT America Condensed per spec aesthetic). Variable font: a single latin-subset woff2 covers the full weight axis; Antonio-Bold.woff2 is byte-identical to Antonio-Regular.woff2 (Google Fonts serves one file for all weights). Both files bundled in romfs at /rom/ui/fonts/. Downloaded from fonts.gstatic.com."
|
|
280
|
+
|
|
281
|
+
[[component]]
|
|
282
|
+
name = "DM Sans"
|
|
283
|
+
version = "17"
|
|
284
|
+
licence = "OFL-1.1"
|
|
285
|
+
source_url = "https://fonts.google.com/specimen/DM+Sans"
|
|
286
|
+
link_type = "static"
|
|
287
|
+
purl = "pkg:generic/google-fonts/DM+Sans@v17"
|
|
288
|
+
notes = "OFL-1.1 (SIL Open Font Licence). Used as body/label font in the dashboard example UI (OFL fallback for GT Walsheim per spec aesthetic). Variable font: a single latin-subset woff2 covers the full weight axis; DMSans-Medium.woff2 is byte-identical to DMSans-Regular.woff2 (Google Fonts serves one file for all weights). Both files bundled in romfs at /rom/ui/fonts/. Downloaded from fonts.gstatic.com."
|
picolet/cli/RUNTIME_TAG
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
runtime-v0.1.0
|
picolet/cli/__init__.py
ADDED
|
File without changes
|
picolet/cli/_paths.py
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared path / project-resolution helpers for the picolet CLI.
|
|
3
|
+
|
|
4
|
+
Used by run_cmd, dev_cmd, and build_cmd. Plain str constants only —
|
|
5
|
+
no enum, since the framework is intended to be self-hostable on
|
|
6
|
+
MicroPython.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import stat
|
|
11
|
+
import sys
|
|
12
|
+
import tomllib
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
from picolet.cli._targets import host_target, target_exe_suffix
|
|
16
|
+
from picolet.cli.validator import validate_toml
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Directory components that should never be watched or scanned for source
|
|
20
|
+
# freshness — build outputs, caches, compiled artifacts.
|
|
21
|
+
_IGNORE_DIRS = frozenset({"target", ".picolet-cache", "__pycache__", "node_modules", "dist"})
|
|
22
|
+
_IGNORE_SUFFIXES = frozenset({".pyc", ".mpy"})
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def find_picolet_toml(start: Path) -> Path | None:
|
|
26
|
+
"""Walk up from ``start`` looking for ``picolet.toml``.
|
|
27
|
+
|
|
28
|
+
Returns the located path, or ``None`` when the filesystem root is reached
|
|
29
|
+
without finding one. The walk is iterative to avoid stack-depth issues
|
|
30
|
+
on pathological paths (and to keep the implementation MicroPython-safe
|
|
31
|
+
by avoiding deep recursion).
|
|
32
|
+
"""
|
|
33
|
+
current = start
|
|
34
|
+
while True:
|
|
35
|
+
candidate = current / "picolet.toml"
|
|
36
|
+
if candidate.is_file():
|
|
37
|
+
return candidate
|
|
38
|
+
parent = current.parent
|
|
39
|
+
if parent == current:
|
|
40
|
+
return None
|
|
41
|
+
current = parent
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def resolve_app(args) -> tuple[Path, dict, str, Path]:
|
|
45
|
+
"""Validate picolet.toml and resolve the binary path.
|
|
46
|
+
|
|
47
|
+
Returns ``(toml_path, data, target, binary_path)``. Exits 1 on any
|
|
48
|
+
validation error.
|
|
49
|
+
"""
|
|
50
|
+
toml_path = find_picolet_toml(Path.cwd())
|
|
51
|
+
if toml_path is None:
|
|
52
|
+
print(
|
|
53
|
+
"error: picolet.toml not found in current directory or any ancestor",
|
|
54
|
+
file=sys.stderr,
|
|
55
|
+
)
|
|
56
|
+
sys.exit(1)
|
|
57
|
+
|
|
58
|
+
_all_results = validate_toml(toml_path)
|
|
59
|
+
_hard_errors = [e for e in _all_results if e.level != "warn"]
|
|
60
|
+
for e in _all_results:
|
|
61
|
+
if e.level == "warn":
|
|
62
|
+
print(str(e), file=sys.stderr)
|
|
63
|
+
if _hard_errors:
|
|
64
|
+
for e in _hard_errors:
|
|
65
|
+
print(str(e), file=sys.stderr)
|
|
66
|
+
sys.exit(1)
|
|
67
|
+
|
|
68
|
+
with open(toml_path, "rb") as fh:
|
|
69
|
+
data = tomllib.load(fh)
|
|
70
|
+
|
|
71
|
+
target = args.target if args.target else host_target()
|
|
72
|
+
app_name: str = data["app"]["name"]
|
|
73
|
+
app_root: Path = toml_path.parent
|
|
74
|
+
|
|
75
|
+
binary_path = app_root / "target" / target / (app_name + target_exe_suffix(target))
|
|
76
|
+
|
|
77
|
+
return toml_path, data, target, binary_path
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def should_ignore(path: Path) -> bool:
|
|
81
|
+
"""Return True if path is in a watch-ignored location."""
|
|
82
|
+
for part in path.parts:
|
|
83
|
+
if part in _IGNORE_DIRS or part.startswith("."):
|
|
84
|
+
return True
|
|
85
|
+
return path.suffix in _IGNORE_SUFFIXES
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def collect_watch_paths(app_root: Path, data: dict) -> list[Path]:
|
|
89
|
+
"""Return the list of paths/dirs to watch (may not all exist)."""
|
|
90
|
+
src_dir = app_root / Path(data["app"]["entry"]).parent
|
|
91
|
+
paths = [app_root / "picolet.toml", src_dir]
|
|
92
|
+
|
|
93
|
+
ui = data.get("ui", {})
|
|
94
|
+
if ui:
|
|
95
|
+
ui_root = ui.get("root")
|
|
96
|
+
if ui_root:
|
|
97
|
+
paths.append(app_root / ui_root)
|
|
98
|
+
else:
|
|
99
|
+
fallback = app_root / "ui"
|
|
100
|
+
if fallback.is_dir():
|
|
101
|
+
paths.append(fallback)
|
|
102
|
+
|
|
103
|
+
return paths
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def sources_newer_than(
|
|
107
|
+
toml_path: Path, data: dict, binary: Path
|
|
108
|
+
) -> bool:
|
|
109
|
+
"""Return True if any watched source is newer than the binary."""
|
|
110
|
+
binary_mtime = binary.stat().st_mtime
|
|
111
|
+
watch_paths = collect_watch_paths(toml_path.parent, data)
|
|
112
|
+
for _path, mtime, _size in iter_watched_files(watch_paths):
|
|
113
|
+
if mtime > binary_mtime:
|
|
114
|
+
return True
|
|
115
|
+
return False
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def iter_watched_files(watch_paths):
|
|
119
|
+
"""Yield ``(path, mtime, size)`` for each watched non-ignored regular file.
|
|
120
|
+
|
|
121
|
+
Single ``stat()`` per file via ``stat.S_ISREG`` — no separate
|
|
122
|
+
``is_file()`` pass.
|
|
123
|
+
"""
|
|
124
|
+
for watch_path in watch_paths:
|
|
125
|
+
if should_ignore(watch_path):
|
|
126
|
+
continue
|
|
127
|
+
try:
|
|
128
|
+
st = watch_path.stat()
|
|
129
|
+
except OSError:
|
|
130
|
+
continue
|
|
131
|
+
if stat.S_ISREG(st.st_mode):
|
|
132
|
+
yield watch_path, st.st_mtime, st.st_size
|
|
133
|
+
continue
|
|
134
|
+
if not stat.S_ISDIR(st.st_mode):
|
|
135
|
+
continue
|
|
136
|
+
for f in watch_path.rglob("*"):
|
|
137
|
+
if should_ignore(f):
|
|
138
|
+
continue
|
|
139
|
+
try:
|
|
140
|
+
fst = f.stat()
|
|
141
|
+
except OSError:
|
|
142
|
+
continue
|
|
143
|
+
if stat.S_ISREG(fst.st_mode):
|
|
144
|
+
yield f, fst.st_mtime, fst.st_size
|