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.
Files changed (166) hide show
  1. picolet/__init__.py +26 -0
  2. picolet/__main__.py +125 -0
  3. picolet/_bridge/__init__.py +0 -0
  4. picolet/_bridge/picolet-bridge.js +1 -0
  5. picolet/_runtime_data/RUNTIME_TAG +1 -0
  6. picolet/_runtime_data/__init__.py +0 -0
  7. picolet/_runtime_data/mbm.toml +71 -0
  8. picolet/_runtime_data/sbom/__init__.py +0 -0
  9. picolet/_runtime_data/sbom/runtime.toml +288 -0
  10. picolet/cli/RUNTIME_TAG +1 -0
  11. picolet/cli/__init__.py +0 -0
  12. picolet/cli/_paths.py +144 -0
  13. picolet/cli/_targets.py +92 -0
  14. picolet/cli/_trailer.py +56 -0
  15. picolet/cli/build_cmd.py +981 -0
  16. picolet/cli/dev_cmd.py +324 -0
  17. picolet/cli/init_cmd.py +225 -0
  18. picolet/cli/run_cmd.py +129 -0
  19. picolet/cli/runtime_resolver.py +810 -0
  20. picolet/cli/sbom_gen.py +873 -0
  21. picolet/cli/test_cmd.py +640 -0
  22. picolet/cli/validate_cmd.py +61 -0
  23. picolet/cli/validator.py +469 -0
  24. picolet/templates/__init__.py +0 -0
  25. picolet/templates/config-editor/package.json +22 -0
  26. picolet/templates/config-editor/picolet.toml +20 -0
  27. picolet/templates/config-editor/src/config_store.py +302 -0
  28. picolet/templates/config-editor/src/config_validator.py +148 -0
  29. picolet/templates/config-editor/src/difflib.py +171 -0
  30. picolet/templates/config-editor/src/main.py +74 -0
  31. picolet/templates/config-editor/src/micro_yaml.py +265 -0
  32. picolet/templates/config-editor/src/tomllib.py +490 -0
  33. picolet/templates/config-editor/tsconfig.json +19 -0
  34. picolet/templates/config-editor/tsconfig.node.json +10 -0
  35. picolet/templates/config-editor/ui/index.html +12 -0
  36. picolet/templates/config-editor/ui/public/fonts/JetBrainsMono-Regular.woff2 +0 -0
  37. picolet/templates/config-editor/ui/src/App.vue +27 -0
  38. picolet/templates/config-editor/ui/src/assets/fonts.css +12 -0
  39. picolet/templates/config-editor/ui/src/assets/main.css +367 -0
  40. picolet/templates/config-editor/ui/src/env.d.ts +2 -0
  41. picolet/templates/config-editor/ui/src/main.ts +7 -0
  42. picolet/templates/config-editor/ui/src/picolet.d.ts +26 -0
  43. picolet/templates/config-editor/ui/src/router/index.ts +17 -0
  44. picolet/templates/config-editor/ui/src/store.ts +46 -0
  45. picolet/templates/config-editor/ui/src/views/DiffView.vue +78 -0
  46. picolet/templates/config-editor/ui/src/views/EditView.vue +409 -0
  47. picolet/templates/config-editor/ui/src/views/PickerView.vue +234 -0
  48. picolet/templates/config-editor/vite.config.ts +18 -0
  49. picolet/templates/dashboard/package.json +22 -0
  50. picolet/templates/dashboard/picolet.toml +20 -0
  51. picolet/templates/dashboard/src/main.py +71 -0
  52. picolet/templates/dashboard/src/metrics_reader.py +463 -0
  53. picolet/templates/dashboard/tsconfig.json +19 -0
  54. picolet/templates/dashboard/tsconfig.node.json +10 -0
  55. picolet/templates/dashboard/ui/index.html +12 -0
  56. picolet/templates/dashboard/ui/public/fonts/Antonio-Bold.woff2 +0 -0
  57. picolet/templates/dashboard/ui/public/fonts/Antonio-Regular.woff2 +0 -0
  58. picolet/templates/dashboard/ui/public/fonts/DMSans-Medium.woff2 +0 -0
  59. picolet/templates/dashboard/ui/public/fonts/DMSans-Regular.woff2 +0 -0
  60. picolet/templates/dashboard/ui/public/fonts/JetBrainsMono-Regular.woff2 +0 -0
  61. picolet/templates/dashboard/ui/src/App.vue +55 -0
  62. picolet/templates/dashboard/ui/src/assets/fonts.css +43 -0
  63. picolet/templates/dashboard/ui/src/assets/main.css +401 -0
  64. picolet/templates/dashboard/ui/src/components/CpuChart.vue +87 -0
  65. picolet/templates/dashboard/ui/src/components/DiskChart.vue +91 -0
  66. picolet/templates/dashboard/ui/src/components/MemoryGauge.vue +96 -0
  67. picolet/templates/dashboard/ui/src/components/NetworkChart.vue +91 -0
  68. picolet/templates/dashboard/ui/src/components/ProcessList.vue +53 -0
  69. picolet/templates/dashboard/ui/src/components/SparklineStrip.vue +81 -0
  70. picolet/templates/dashboard/ui/src/components/TopStrip.vue +33 -0
  71. picolet/templates/dashboard/ui/src/env.d.ts +2 -0
  72. picolet/templates/dashboard/ui/src/main.ts +7 -0
  73. picolet/templates/dashboard/ui/src/picolet.d.ts +25 -0
  74. picolet/templates/dashboard/ui/src/router/index.ts +13 -0
  75. picolet/templates/dashboard/ui/src/store.ts +45 -0
  76. picolet/templates/dashboard/ui/src/utils/format.ts +51 -0
  77. picolet/templates/dashboard/ui/src/utils/svg.ts +84 -0
  78. picolet/templates/dashboard/ui/src/views/DashboardView.vue +56 -0
  79. picolet/templates/dashboard/vite.config.ts +18 -0
  80. picolet/templates/hello-cli/picolet.toml +4 -0
  81. picolet/templates/hello-cli/src/main.py +19 -0
  82. picolet/templates/hello-lvgl/picolet.toml +15 -0
  83. picolet/templates/hello-lvgl/src/main.py +52 -0
  84. picolet/templates/hello-vue/package.json +21 -0
  85. picolet/templates/hello-vue/picolet.toml +20 -0
  86. picolet/templates/hello-vue/src/main.py +40 -0
  87. picolet/templates/hello-vue/tsconfig.json +19 -0
  88. picolet/templates/hello-vue/tsconfig.node.json +10 -0
  89. picolet/templates/hello-vue/ui/index.html +12 -0
  90. picolet/templates/hello-vue/ui/src/App.vue +127 -0
  91. picolet/templates/hello-vue/ui/src/env.d.ts +2 -0
  92. picolet/templates/hello-vue/ui/src/main.ts +4 -0
  93. picolet/templates/hello-vue/ui/src/picolet.d.ts +28 -0
  94. picolet/templates/hello-vue/vite.config.ts +17 -0
  95. picolet/templates/hello-webview/picolet.toml +17 -0
  96. picolet/templates/hello-webview/src/main.py +22 -0
  97. picolet/templates/hello-webview/ui/app.js +24 -0
  98. picolet/templates/hello-webview/ui/index.html +17 -0
  99. picolet/templates/hello-webview/ui/style.css +15 -0
  100. picolet/templates/notes/package.json +23 -0
  101. picolet/templates/notes/picolet.toml +20 -0
  102. picolet/templates/notes/src/main.py +70 -0
  103. picolet/templates/notes/src/notes_store.py +178 -0
  104. picolet/templates/notes/tsconfig.json +19 -0
  105. picolet/templates/notes/tsconfig.node.json +10 -0
  106. picolet/templates/notes/ui/index.html +12 -0
  107. picolet/templates/notes/ui/public/fonts/source-sans-3.woff2 +0 -0
  108. picolet/templates/notes/ui/public/fonts/source-serif-4-italic.woff2 +0 -0
  109. picolet/templates/notes/ui/public/fonts/source-serif-4-roman.woff2 +0 -0
  110. picolet/templates/notes/ui/src/App.vue +15 -0
  111. picolet/templates/notes/ui/src/assets/fonts.css +34 -0
  112. picolet/templates/notes/ui/src/assets/main.css +459 -0
  113. picolet/templates/notes/ui/src/env.d.ts +2 -0
  114. picolet/templates/notes/ui/src/main.ts +7 -0
  115. picolet/templates/notes/ui/src/picolet.d.ts +25 -0
  116. picolet/templates/notes/ui/src/router/index.ts +17 -0
  117. picolet/templates/notes/ui/src/views/AboutView.vue +25 -0
  118. picolet/templates/notes/ui/src/views/EditView.vue +173 -0
  119. picolet/templates/notes/ui/src/views/ListView.vue +108 -0
  120. picolet/templates/notes/vite.config.ts +18 -0
  121. picolet/templates/pydfu/README.md +109 -0
  122. picolet/templates/pydfu/package.json +22 -0
  123. picolet/templates/pydfu/picolet.toml +29 -0
  124. picolet/templates/pydfu/src/_crc32.py +28 -0
  125. picolet/templates/pydfu/src/_dfu_file.py +79 -0
  126. picolet/templates/pydfu/src/_pydfu/__init__.py +2 -0
  127. picolet/templates/pydfu/src/_pydfu/pydfu.py +462 -0
  128. picolet/templates/pydfu/src/_usb/__init__.py +2 -0
  129. picolet/templates/pydfu/src/_usb/control.py +10 -0
  130. picolet/templates/pydfu/src/_usb/core.py +339 -0
  131. picolet/templates/pydfu/src/_usb/libusb-1.0.dll +0 -0
  132. picolet/templates/pydfu/src/_usb/util.py +21 -0
  133. picolet/templates/pydfu/src/main.py +379 -0
  134. picolet/templates/pydfu/src/pydfu_adapter.py +190 -0
  135. picolet/templates/pydfu/src/pydfu_mock.py +51 -0
  136. picolet/templates/pydfu/tsconfig.json +19 -0
  137. picolet/templates/pydfu/tsconfig.node.json +10 -0
  138. picolet/templates/pydfu/ui/index.html +12 -0
  139. picolet/templates/pydfu/ui/public/fonts/IBMPlexSans-Regular.woff2 +0 -0
  140. picolet/templates/pydfu/ui/public/fonts/IBMPlexSans-SemiBold.woff2 +0 -0
  141. picolet/templates/pydfu/ui/public/fonts/MonaspaceNeon-Bold.woff2 +0 -0
  142. picolet/templates/pydfu/ui/public/fonts/MonaspaceNeon-Regular.woff2 +0 -0
  143. picolet/templates/pydfu/ui/src/App.vue +111 -0
  144. picolet/templates/pydfu/ui/src/assets/fonts.css +27 -0
  145. picolet/templates/pydfu/ui/src/assets/main.css +156 -0
  146. picolet/templates/pydfu/ui/src/components/AuditStrip.vue +114 -0
  147. picolet/templates/pydfu/ui/src/components/DeviceDetail.vue +202 -0
  148. picolet/templates/pydfu/ui/src/components/DeviceList.vue +181 -0
  149. picolet/templates/pydfu/ui/src/components/HeaderRail.vue +131 -0
  150. picolet/templates/pydfu/ui/src/components/LedDot.vue +54 -0
  151. picolet/templates/pydfu/ui/src/env.d.ts +2 -0
  152. picolet/templates/pydfu/ui/src/main.ts +7 -0
  153. picolet/templates/pydfu/ui/src/picolet.d.ts +24 -0
  154. picolet/templates/pydfu/ui/src/router/index.ts +17 -0
  155. picolet/templates/pydfu/ui/src/views/FlashView.vue +429 -0
  156. picolet/templates/pydfu/ui/src/views/HomeView.vue +29 -0
  157. picolet/templates/pydfu/ui/src/views/LogView.vue +68 -0
  158. picolet/templates/pydfu/vite.config.ts +18 -0
  159. picolet/testing/__init__.py +17 -0
  160. picolet/testing/_chromium.py +90 -0
  161. picolet/testing/_harness.py +651 -0
  162. picolet/testing/_webkit.py +314 -0
  163. picolet-0.0.1.dist-info/METADATA +250 -0
  164. picolet-0.0.1.dist-info/RECORD +166 -0
  165. picolet-0.0.1.dist-info/WHEEL +4 -0
  166. 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."
@@ -0,0 +1 @@
1
+ runtime-v0.1.0
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