cronstable 1.2.11__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.
Files changed (264) hide show
  1. cronstable-1.2.11/.dockerignore +27 -0
  2. cronstable-1.2.11/.editorconfig +15 -0
  3. cronstable-1.2.11/.gitattributes +6 -0
  4. cronstable-1.2.11/.github/CODEOWNERS +3 -0
  5. cronstable-1.2.11/.github/ISSUE_TEMPLATE.md +15 -0
  6. cronstable-1.2.11/.github/dependabot.yml +59 -0
  7. cronstable-1.2.11/.github/workflows/build.yml +517 -0
  8. cronstable-1.2.11/.github/workflows/docker.yml +260 -0
  9. cronstable-1.2.11/.github/workflows/release.yml +842 -0
  10. cronstable-1.2.11/.github/workflows/tox.yml +103 -0
  11. cronstable-1.2.11/.gitignore +70 -0
  12. cronstable-1.2.11/.pre-commit-config.yaml +17 -0
  13. cronstable-1.2.11/CONTRIBUTING.md +156 -0
  14. cronstable-1.2.11/Dockerfile +102 -0
  15. cronstable-1.2.11/HISTORY.md +1891 -0
  16. cronstable-1.2.11/LICENSE +21 -0
  17. cronstable-1.2.11/MANIFEST.in +8 -0
  18. cronstable-1.2.11/PKG-INFO +1957 -0
  19. cronstable-1.2.11/README.md +1898 -0
  20. cronstable-1.2.11/codecov.yml +23 -0
  21. cronstable-1.2.11/cronstable/__init__.py +0 -0
  22. cronstable-1.2.11/cronstable/__main__.py +278 -0
  23. cronstable-1.2.11/cronstable/_json.py +127 -0
  24. cronstable-1.2.11/cronstable/backends/__init__.py +55 -0
  25. cronstable-1.2.11/cronstable/backends/etcd.py +1113 -0
  26. cronstable-1.2.11/cronstable/backends/filesystem.py +882 -0
  27. cronstable-1.2.11/cronstable/backends/kubernetes.py +1577 -0
  28. cronstable-1.2.11/cronstable/cluster.py +3402 -0
  29. cronstable-1.2.11/cronstable/config.py +3136 -0
  30. cronstable-1.2.11/cronstable/cron.py +7709 -0
  31. cronstable-1.2.11/cronstable/cronexpr.py +512 -0
  32. cronstable-1.2.11/cronstable/crontabs.py +277 -0
  33. cronstable-1.2.11/cronstable/dag.py +1201 -0
  34. cronstable-1.2.11/cronstable/dagrun.py +1773 -0
  35. cronstable-1.2.11/cronstable/fingerprint.py +284 -0
  36. cronstable-1.2.11/cronstable/job.py +1022 -0
  37. cronstable-1.2.11/cronstable/jobapi.py +838 -0
  38. cronstable-1.2.11/cronstable/jobcli.py +805 -0
  39. cronstable-1.2.11/cronstable/jobstate.py +422 -0
  40. cronstable-1.2.11/cronstable/leadership.py +600 -0
  41. cronstable-1.2.11/cronstable/platform.py +416 -0
  42. cronstable-1.2.11/cronstable/prometheus.py +1122 -0
  43. cronstable-1.2.11/cronstable/redact.py +235 -0
  44. cronstable-1.2.11/cronstable/resources.py +989 -0
  45. cronstable-1.2.11/cronstable/state.py +2847 -0
  46. cronstable-1.2.11/cronstable/state_admin.py +666 -0
  47. cronstable-1.2.11/cronstable/statsd.py +82 -0
  48. cronstable-1.2.11/cronstable/version.py +24 -0
  49. cronstable-1.2.11/cronstable/web/__init__.py +3 -0
  50. cronstable-1.2.11/cronstable/web/index.html +7107 -0
  51. cronstable-1.2.11/cronstable.egg-info/PKG-INFO +1957 -0
  52. cronstable-1.2.11/cronstable.egg-info/SOURCES.txt +262 -0
  53. cronstable-1.2.11/cronstable.egg-info/dependency_links.txt +1 -0
  54. cronstable-1.2.11/cronstable.egg-info/entry_points.txt +2 -0
  55. cronstable-1.2.11/cronstable.egg-info/requires.txt +29 -0
  56. cronstable-1.2.11/cronstable.egg-info/top_level.txt +1 -0
  57. cronstable-1.2.11/docker/Dockerfile.alpine +85 -0
  58. cronstable-1.2.11/docker/Dockerfile.amazonlinux +80 -0
  59. cronstable-1.2.11/docker/Dockerfile.distroless +83 -0
  60. cronstable-1.2.11/docker/Dockerfile.fedora +86 -0
  61. cronstable-1.2.11/docker/Dockerfile.opensuse +93 -0
  62. cronstable-1.2.11/docker/Dockerfile.rhel +88 -0
  63. cronstable-1.2.11/docker/Dockerfile.ubuntu +95 -0
  64. cronstable-1.2.11/docker-compose-acme.yml +104 -0
  65. cronstable-1.2.11/docker-compose-cluster-large.yml +95 -0
  66. cronstable-1.2.11/docker-compose-cluster.yml +138 -0
  67. cronstable-1.2.11/docker-compose-grand-tour.yml +174 -0
  68. cronstable-1.2.11/docker-compose-pulse-cluster.yml +98 -0
  69. cronstable-1.2.11/docker-compose-pulse.yml +23 -0
  70. cronstable-1.2.11/docker-compose-zen.yml +26 -0
  71. cronstable-1.2.11/docker-compose.yml +20 -0
  72. cronstable-1.2.11/docs/img/dashboard-boot.png +0 -0
  73. cronstable-1.2.11/docs/img/dashboard-cluster.png +0 -0
  74. cronstable-1.2.11/docs/img/dashboard-dag-approval.png +0 -0
  75. cronstable-1.2.11/docs/img/dashboard-dag-graph.png +0 -0
  76. cronstable-1.2.11/docs/img/dashboard-fleet.png +0 -0
  77. cronstable-1.2.11/docs/img/dashboard-heatmap.png +0 -0
  78. cronstable-1.2.11/docs/img/dashboard-history.png +0 -0
  79. cronstable-1.2.11/docs/img/dashboard-incident-timeline.png +0 -0
  80. cronstable-1.2.11/docs/img/dashboard-logs.png +0 -0
  81. cronstable-1.2.11/docs/img/dashboard-multitail.png +0 -0
  82. cronstable-1.2.11/docs/img/dashboard-overview.png +0 -0
  83. cronstable-1.2.11/docs/img/dashboard-palette.png +0 -0
  84. cronstable-1.2.11/docs/img/dashboard-schedule.png +0 -0
  85. cronstable-1.2.11/docs/img/dashboard-settings.png +0 -0
  86. cronstable-1.2.11/docs/img/dashboard-shortcuts.png +0 -0
  87. cronstable-1.2.11/docs/img/dashboard-state.png +0 -0
  88. cronstable-1.2.11/docs/img/dashboard-theme-amber.png +0 -0
  89. cronstable-1.2.11/docs/img/dashboard-theme-carolina-light.png +0 -0
  90. cronstable-1.2.11/docs/img/dashboard-theme-green.png +0 -0
  91. cronstable-1.2.11/docs/img/dashboard-theme-modern.png +0 -0
  92. cronstable-1.2.11/docs/img/dashboard-wallboard.png +0 -0
  93. cronstable-1.2.11/docs/img/logo-spin-light.gif +0 -0
  94. cronstable-1.2.11/docs/img/logo-spin.gif +0 -0
  95. cronstable-1.2.11/docs/img/social-preview.png +0 -0
  96. cronstable-1.2.11/docs/screenshots/README.md +78 -0
  97. cronstable-1.2.11/docs/screenshots/capture_dashboard.py +528 -0
  98. cronstable-1.2.11/docs/screenshots/capture_logo_gif.py +135 -0
  99. cronstable-1.2.11/docs/screenshots/capture_logs_closeup.py +67 -0
  100. cronstable-1.2.11/docs/screenshots/capture_social_card.py +55 -0
  101. cronstable-1.2.11/docs/screenshots/logs-demo.yaml +56 -0
  102. cronstable-1.2.11/docs/screenshots/shots/dashboard-boot.png +0 -0
  103. cronstable-1.2.11/docs/screenshots/shots/dashboard-cluster.png +0 -0
  104. cronstable-1.2.11/docs/screenshots/shots/dashboard-dag-approval.png +0 -0
  105. cronstable-1.2.11/docs/screenshots/shots/dashboard-dag-graph.png +0 -0
  106. cronstable-1.2.11/docs/screenshots/shots/dashboard-fleet.png +0 -0
  107. cronstable-1.2.11/docs/screenshots/shots/dashboard-heatmap.png +0 -0
  108. cronstable-1.2.11/docs/screenshots/shots/dashboard-history.png +0 -0
  109. cronstable-1.2.11/docs/screenshots/shots/dashboard-incident-timeline.png +0 -0
  110. cronstable-1.2.11/docs/screenshots/shots/dashboard-logs.png +0 -0
  111. cronstable-1.2.11/docs/screenshots/shots/dashboard-multitail.png +0 -0
  112. cronstable-1.2.11/docs/screenshots/shots/dashboard-overview.png +0 -0
  113. cronstable-1.2.11/docs/screenshots/shots/dashboard-palette.png +0 -0
  114. cronstable-1.2.11/docs/screenshots/shots/dashboard-schedule.png +0 -0
  115. cronstable-1.2.11/docs/screenshots/shots/dashboard-settings.png +0 -0
  116. cronstable-1.2.11/docs/screenshots/shots/dashboard-shortcuts.png +0 -0
  117. cronstable-1.2.11/docs/screenshots/shots/dashboard-state.png +0 -0
  118. cronstable-1.2.11/docs/screenshots/shots/dashboard-theme-amber.png +0 -0
  119. cronstable-1.2.11/docs/screenshots/shots/dashboard-theme-carolina-light.png +0 -0
  120. cronstable-1.2.11/docs/screenshots/shots/dashboard-theme-green.png +0 -0
  121. cronstable-1.2.11/docs/screenshots/shots/dashboard-theme-modern.png +0 -0
  122. cronstable-1.2.11/docs/screenshots/shots/dashboard-wallboard.png +0 -0
  123. cronstable-1.2.11/docs/screenshots/shots/logo-spin-light.gif +0 -0
  124. cronstable-1.2.11/docs/screenshots/shots/logo-spin.gif +0 -0
  125. cronstable-1.2.11/docs/screenshots/shots/social-preview.png +0 -0
  126. cronstable-1.2.11/docs/screenshots/social-card.html +89 -0
  127. cronstable-1.2.11/example/acme-platform/README.md +84 -0
  128. cronstable-1.2.11/example/acme-platform/acme.env +6 -0
  129. cronstable-1.2.11/example/acme-platform/gen-certs.sh +61 -0
  130. cronstable-1.2.11/example/acme-platform/jobs.yaml +450 -0
  131. cronstable-1.2.11/example/acme-platform/node-entrypoint.sh +67 -0
  132. cronstable-1.2.11/example/adhoc.cronstable.d/_inc.yaml +66 -0
  133. cronstable-1.2.11/example/adhoc.cronstable.d/test-tz.yaml +16 -0
  134. cronstable-1.2.11/example/adhoc.cronstable.d/test-utc.yaml +16 -0
  135. cronstable-1.2.11/example/adhoc.cronstable.d/test.yaml +64 -0
  136. cronstable-1.2.11/example/cluster/README +29 -0
  137. cronstable-1.2.11/example/cluster/gen-certs.sh +77 -0
  138. cronstable-1.2.11/example/cluster/jobs.yaml +68 -0
  139. cronstable-1.2.11/example/cluster/node-a.yaml +28 -0
  140. cronstable-1.2.11/example/cluster/node-b.yaml +18 -0
  141. cronstable-1.2.11/example/cluster/node-c.yaml +18 -0
  142. cronstable-1.2.11/example/cluster-large/README +63 -0
  143. cronstable-1.2.11/example/cluster-large/gen-certs.sh +64 -0
  144. cronstable-1.2.11/example/cluster-large/jobs.yaml +139 -0
  145. cronstable-1.2.11/example/cluster-large/node-entrypoint.sh +72 -0
  146. cronstable-1.2.11/example/crontab/README +44 -0
  147. cronstable-1.2.11/example/crontab/dashboard.yaml +18 -0
  148. cronstable-1.2.11/example/crontab/legacy.crontab +19 -0
  149. cronstable-1.2.11/example/dag/cronstable.yaml +95 -0
  150. cronstable-1.2.11/example/dag-cluster/README +84 -0
  151. cronstable-1.2.11/example/dag-cluster/cronstable.yaml +107 -0
  152. cronstable-1.2.11/example/dag-cluster/docker-compose.yml +91 -0
  153. cronstable-1.2.11/example/demo/cronstable.yaml +100 -0
  154. cronstable-1.2.11/example/docker/Dockerfile +7 -0
  155. cronstable-1.2.11/example/docker/README +28 -0
  156. cronstable-1.2.11/example/docker/cronstable.yaml +10 -0
  157. cronstable-1.2.11/example/docker/k8s-deploy.yaml +29 -0
  158. cronstable-1.2.11/example/etcd/README +64 -0
  159. cronstable-1.2.11/example/etcd/cronstable.yaml +47 -0
  160. cronstable-1.2.11/example/etcd/docker-compose.yml +69 -0
  161. cronstable-1.2.11/example/grand-tour/README.md +270 -0
  162. cronstable-1.2.11/example/grand-tour/_defaults.yaml +69 -0
  163. cronstable-1.2.11/example/grand-tour/gen-certs.sh +58 -0
  164. cronstable-1.2.11/example/grand-tour/legacy.crontab +20 -0
  165. cronstable-1.2.11/example/grand-tour/node-entrypoint.sh +132 -0
  166. cronstable-1.2.11/example/grand-tour/platform.env +7 -0
  167. cronstable-1.2.11/example/grand-tour/platform.yaml +1178 -0
  168. cronstable-1.2.11/example/grand-tour/secrets/signing.key +1 -0
  169. cronstable-1.2.11/example/job-state/cronstable.yaml +97 -0
  170. cronstable-1.2.11/example/kubernetes/README +63 -0
  171. cronstable-1.2.11/example/kubernetes/cronstable.yaml +50 -0
  172. cronstable-1.2.11/example/kubernetes/deployment.yaml +109 -0
  173. cronstable-1.2.11/example/pulse-cluster/README.md +89 -0
  174. cronstable-1.2.11/example/pulse-cluster/gen-certs.sh +68 -0
  175. cronstable-1.2.11/example/pulse-cluster/jobs.yaml +133 -0
  176. cronstable-1.2.11/example/pulse-cluster/node-a.yaml +23 -0
  177. cronstable-1.2.11/example/pulse-cluster/node-b.yaml +16 -0
  178. cronstable-1.2.11/example/pulse-cluster/node-c.yaml +20 -0
  179. cronstable-1.2.11/example/pulse-monitor/README.md +96 -0
  180. cronstable-1.2.11/example/pulse-monitor/cronstable.yaml +138 -0
  181. cronstable-1.2.11/example/zen-demo/cronstable.yaml +74 -0
  182. cronstable-1.2.11/packaging/homebrew/cronstable.rb.tmpl +45 -0
  183. cronstable-1.2.11/packaging/homebrew/render-formula.sh +40 -0
  184. cronstable-1.2.11/pyinstaller/Dockerfile +43 -0
  185. cronstable-1.2.11/pyinstaller/Makefile +9 -0
  186. cronstable-1.2.11/pyinstaller/cronstable +3 -0
  187. cronstable-1.2.11/pyinstaller/cronstable.spec +85 -0
  188. cronstable-1.2.11/pyinstaller/entitlements.plist +30 -0
  189. cronstable-1.2.11/pyinstaller/install_orjson.sh +53 -0
  190. cronstable-1.2.11/pyinstaller/verify_orjson.py +42 -0
  191. cronstable-1.2.11/pyinstaller/verify_uvloop.py +32 -0
  192. cronstable-1.2.11/pyproject.toml +206 -0
  193. cronstable-1.2.11/requirements_dev.txt +14 -0
  194. cronstable-1.2.11/setup.cfg +4 -0
  195. cronstable-1.2.11/tests/__init__.py +0 -0
  196. cronstable-1.2.11/tests/_commands.py +108 -0
  197. cronstable-1.2.11/tests/data/cron_golden.json +8796 -0
  198. cronstable-1.2.11/tests/fixtures/.testenv +7 -0
  199. cronstable-1.2.11/tests/fixtures/.testenv-invalid +1 -0
  200. cronstable-1.2.11/tests/gen_cron_golden.py +398 -0
  201. cronstable-1.2.11/tests/test_backend_etcd.py +957 -0
  202. cronstable-1.2.11/tests/test_backend_filesystem.py +764 -0
  203. cronstable-1.2.11/tests/test_backend_kubernetes.py +1200 -0
  204. cronstable-1.2.11/tests/test_cluster.py +5433 -0
  205. cronstable-1.2.11/tests/test_config.py +1086 -0
  206. cronstable-1.2.11/tests/test_config_backends.py +1104 -0
  207. cronstable-1.2.11/tests/test_cron.py +4568 -0
  208. cronstable-1.2.11/tests/test_cron_state_hardening.py +937 -0
  209. cronstable-1.2.11/tests/test_cronexpr.py +163 -0
  210. cronstable-1.2.11/tests/test_crontabs.py +325 -0
  211. cronstable-1.2.11/tests/test_fingerprint.py +759 -0
  212. cronstable-1.2.11/tests/test_include_parent.yaml +10 -0
  213. cronstable-1.2.11/tests/test_included_child.yaml +12 -0
  214. cronstable-1.2.11/tests/test_job.py +1585 -0
  215. cronstable-1.2.11/tests/test_json_portability.py +77 -0
  216. cronstable-1.2.11/tests/test_leadership.py +486 -0
  217. cronstable-1.2.11/tests/test_main.py +74 -0
  218. cronstable-1.2.11/tests/test_platform.py +153 -0
  219. cronstable-1.2.11/tests/test_prometheus.py +1344 -0
  220. cronstable-1.2.11/tests/test_redact.py +236 -0
  221. cronstable-1.2.11/tests/test_resources.py +743 -0
  222. cronstable-1.2.11/tests/test_state.py +1438 -0
  223. cronstable-1.2.11/tests/test_state_admin.py +824 -0
  224. cronstable-1.2.11/tests/test_state_dag.py +1480 -0
  225. cronstable-1.2.11/tests/test_state_dag_run.py +1343 -0
  226. cronstable-1.2.11/tests/test_state_fleet_ha.py +1048 -0
  227. cronstable-1.2.11/tests/test_state_hardening.py +899 -0
  228. cronstable-1.2.11/tests/test_state_job_api.py +811 -0
  229. cronstable-1.2.11/tests/test_state_job_cli.py +476 -0
  230. cronstable-1.2.11/tests/test_state_job_primitives.py +656 -0
  231. cronstable-1.2.11/tests/test_state_lifecycle_hardening.py +345 -0
  232. cronstable-1.2.11/tests/test_state_scheduler_durability.py +1286 -0
  233. cronstable-1.2.11/tests/test_ui_endpoints.py +431 -0
  234. cronstable-1.2.11/tests/testbrokenconfig.yaml +3 -0
  235. cronstable-1.2.11/tests/testconfig.yaml +15 -0
  236. cronstable-1.2.11/tox.ini +57 -0
  237. cronstable-1.2.11/wiki/Architecture-and-Internals.md +797 -0
  238. cronstable-1.2.11/wiki/CLI-Reference.md +594 -0
  239. cronstable-1.2.11/wiki/Classic-Crontabs.md +219 -0
  240. cronstable-1.2.11/wiki/Clustering-and-Leader-Election.md +1480 -0
  241. cronstable-1.2.11/wiki/Commands-and-Environment.md +304 -0
  242. cronstable-1.2.11/wiki/Concurrency-and-Timeouts.md +437 -0
  243. cronstable-1.2.11/wiki/Configuration-Reference.md +676 -0
  244. cronstable-1.2.11/wiki/Contributing-and-Releasing.md +190 -0
  245. cronstable-1.2.11/wiki/Durable-State.md +1029 -0
  246. cronstable-1.2.11/wiki/Failure-Detection-and-Retries.md +206 -0
  247. cronstable-1.2.11/wiki/HTTP-API.md +1191 -0
  248. cronstable-1.2.11/wiki/Home.md +43 -0
  249. cronstable-1.2.11/wiki/Includes-and-Defaults.md +268 -0
  250. cronstable-1.2.11/wiki/Installation.md +290 -0
  251. cronstable-1.2.11/wiki/Logging-Configuration.md +163 -0
  252. cronstable-1.2.11/wiki/Metrics-with-Prometheus.md +197 -0
  253. cronstable-1.2.11/wiki/Metrics-with-Statsd.md +115 -0
  254. cronstable-1.2.11/wiki/Migration-from-yacron.md +215 -0
  255. cronstable-1.2.11/wiki/Orchestration-and-DAGs.md +265 -0
  256. cronstable-1.2.11/wiki/Output-Capturing.md +206 -0
  257. cronstable-1.2.11/wiki/Production-Deployment.md +411 -0
  258. cronstable-1.2.11/wiki/Reporting.md +459 -0
  259. cronstable-1.2.11/wiki/Running-on-Windows.md +330 -0
  260. cronstable-1.2.11/wiki/Schedules-and-Timezones.md +212 -0
  261. cronstable-1.2.11/wiki/Troubleshooting.md +608 -0
  262. cronstable-1.2.11/wiki/Web-Dashboard.md +488 -0
  263. cronstable-1.2.11/wiki/_Footer.md +3 -0
  264. cronstable-1.2.11/wiki/_Sidebar.md +32 -0
@@ -0,0 +1,27 @@
1
+ # Keep the build context small. NOTE: .git MUST be included — setuptools_scm
2
+ # derives the version from git tags during `pip install .`. README.md and
3
+ # LICENSE must also stay: pyproject.toml references them at build time.
4
+ .venv
5
+ .venv314
6
+ .tox
7
+ .mypy_cache
8
+ .ruff_cache
9
+ .pytest_cache
10
+ dist
11
+ build
12
+ *.egg-info
13
+ __pycache__
14
+ *.pyc
15
+
16
+ # Not needed to build the package; excluded to shrink the build context.
17
+ .claude
18
+ .github
19
+ docker
20
+ example
21
+ pyinstaller
22
+ scripts
23
+ target
24
+ tests
25
+ .editorconfig
26
+ .gitignore
27
+ .pre-commit-config.yaml
@@ -0,0 +1,15 @@
1
+ # http://editorconfig.org
2
+
3
+ root = true
4
+
5
+ [*]
6
+ indent_style = space
7
+ indent_size = 4
8
+ trim_trailing_whitespace = true
9
+ insert_final_newline = true
10
+ charset = utf-8
11
+ end_of_line = lf
12
+
13
+
14
+ [LICENSE]
15
+ insert_final_newline = false
@@ -0,0 +1,6 @@
1
+ # The example shell scripts (example/*/gen-certs.sh, node-entrypoint.sh) are
2
+ # bind-mounted from the working tree and executed by `sh` inside Linux
3
+ # containers, so they must check out with LF line endings everywhere. Without
4
+ # this, a default Windows clone (core.autocrlf=true) converts them to CRLF and
5
+ # every cluster demo dies at its first line ("set: illegal option -").
6
+ *.sh text eol=lf
@@ -0,0 +1,3 @@
1
+ # Default owner for everything in the repo
2
+
3
+ * @ptweezy
@@ -0,0 +1,15 @@
1
+ * cronstable version:
2
+ * Python version:
3
+ * Operating System:
4
+
5
+ ### Description
6
+
7
+ Describe what you were trying to get done.
8
+ Tell us what happened, what went wrong, and what you expected to happen.
9
+
10
+ ### What I Did
11
+
12
+ ```
13
+ Paste the command(s) you ran and the output.
14
+ If there was a crash, please include the traceback here.
15
+ ```
@@ -0,0 +1,59 @@
1
+ # Dependabot keeps the CI/CD supply chain current: it opens PRs to bump the
2
+ # pinned versions as new releases land, so the exact pins in .github/workflows
3
+ # and the Dockerfiles don't silently rot back into known-vulnerable versions.
4
+ #
5
+ # Two mechanisms, one file:
6
+ # - VERSION updates (configured below) run on the weekly schedule and bump a
7
+ # dependency simply because a newer release exists.
8
+ # - SECURITY updates -- the ones that matter most here, where Dependabot raises
9
+ # a PR the moment a dependency has a published advisory, regardless of
10
+ # schedule -- are switched on in the repo UI, not here: Settings -> Code
11
+ # security -> enable "Dependabot alerts" AND "Dependabot security updates".
12
+ # They reuse the ecosystems declared below to know which manifests to patch,
13
+ # so keep this file in sync with what the pipeline actually uses.
14
+ #
15
+ # Updates are grouped per ecosystem so a quiet week brings at most one PR per
16
+ # ecosystem to review, not one per dependency.
17
+ version: 2
18
+ updates:
19
+ # The GitHub Actions pinned in .github/workflows/*.yml. They are exact-pinned
20
+ # (e.g. actions/checkout@v7.0.0), which is precisely what lets Dependabot see
21
+ # each one as outdated and open a reviewable bump -- a moving major tag (@v7)
22
+ # would hide minor/patch drift behind a mutable ref instead.
23
+ - package-ecosystem: github-actions
24
+ directory: "/" # scans .github/workflows/ (and any composite actions)
25
+ schedule:
26
+ interval: weekly
27
+ open-pull-requests-limit: 10
28
+ groups:
29
+ github-actions:
30
+ patterns: ["*"]
31
+
32
+ # Python dependencies: the pyproject.toml optional-dependency groups (incl. the
33
+ # dev extra) and requirements_dev.txt. Catches CVEs in the aiohttp/ruamel stack
34
+ # and the dev toolchain (tox, ruff, mypy, pytest, uv-via-tox-uv).
35
+ - package-ecosystem: pip
36
+ directory: "/"
37
+ schedule:
38
+ interval: weekly
39
+ open-pull-requests-limit: 10
40
+ groups:
41
+ python:
42
+ patterns: ["*"]
43
+
44
+ # Base images in the Dockerfiles: the published multi-distro image matrix
45
+ # (/Dockerfile is the default Debian image; docker/Dockerfile.* are the
46
+ # variants) plus the local PyInstaller build image (pyinstaller/Dockerfile,
47
+ # which also carries the uv COPY --from pin). Base-image bumps can change a
48
+ # distro's default Python, so review these rather than automerging.
49
+ - package-ecosystem: docker
50
+ directories:
51
+ - "/"
52
+ - "/docker"
53
+ - "/pyinstaller"
54
+ schedule:
55
+ interval: weekly
56
+ open-pull-requests-limit: 10
57
+ groups:
58
+ docker:
59
+ patterns: ["*"]
@@ -0,0 +1,517 @@
1
+ name: build
2
+
3
+ # Per-commit build verification. Runs EVERY build that release.yml performs --
4
+ # the wheel + sdist and the self-contained PyInstaller binaries for Linux
5
+ # (amd64, arm64, i686, armv7, ppc64le, s390x and riscv64 in both glibc and
6
+ # musl/Alpine flavors, plus armv6 in musl only), macOS (arm64 + amd64) and
7
+ # Windows (amd64 + arm64) -- on
8
+ # every push, but WITHOUT publishing,
9
+ # tagging,
10
+ # signing, or pushing anything. A broken build (or a broken bundle the
11
+ # `--version` smoke test
12
+ # catches) fails the commit early instead of only surfacing at release time.
13
+ #
14
+ # This is the package/binary analog of docker.yml's per-commit build-only job:
15
+ # - no PyPI publish, no git tag, no GitHub Release, no image push;
16
+ # - the macOS binaries are built and smoke-tested but NOT signed/notarized --
17
+ # signing needs secrets and is a release-time concern (see release.yml's
18
+ # binaries-macos "Sign and notarize" step);
19
+ # - binaries build at the natural setuptools_scm version derived from git
20
+ # (e.g. 1.0.9.devN+g<sha>) rather than a pinned release version.
21
+ # The real, version-pinned, signed, *published* builds still live in
22
+ # release.yml; this workflow only proves every commit can still build them.
23
+ #
24
+ # The build steps below are kept deliberately identical to their release.yml
25
+ # counterparts (same Python, same pyinstaller==6.21.0 pin, same `--version`
26
+ # smoke test, same matrices) so a change there and a drift here are easy to
27
+ # spot -- only the publish/sign/version-pin differences and the macOS 26
28
+ # coverage are intentional.
29
+ #
30
+ # armv6 note: it is musl-only -- Debian/glibc has no arm32v6 image (it ships
31
+ # arm32v5), so armv6 appears only in binaries-musl (no binaries-glibc-extra
32
+ # entry) and the published container image does not cover it (the Debian-based
33
+ # Dockerfile has no arm32v6 base). riscv64 and armv6 have no precompiled
34
+ # PyInstaller bootloader, so pip compiles it from the sdist via waf under QEMU.
35
+
36
+ on:
37
+ push:
38
+ branches: ["**"] # build on every commit, on every branch
39
+ workflow_dispatch:
40
+
41
+ concurrency:
42
+ # One in-flight build per ref; a newer commit supersedes an older build.
43
+ group: build-${{ github.workflow }}-${{ github.ref }}
44
+ cancel-in-progress: true
45
+
46
+ # Build-only: nothing in this workflow writes anywhere (no PyPI/OIDC, no
47
+ # contents:write, no packages:write). Read is all any job needs.
48
+ permissions:
49
+ contents: read
50
+
51
+ # Network hardening for a transient PyPI/network blip. The runner-native jobs
52
+ # (dist, native binaries, macOS, Windows, version) install with uv, which reads
53
+ # UV_HTTP_TIMEOUT for its per-request socket timeout (and retries internally);
54
+ # UV_PYTHON_DOWNLOADS=never keeps uv on the setup-python interpreter rather than
55
+ # fetching a managed one, so the build Python stays exactly the one CI pinned.
56
+ # The emulated foreign-arch binary jobs (binaries-musl, binaries-glibc-extra)
57
+ # stay on pip inside their `docker run` containers -- uv's official image is
58
+ # amd64/arm64 only and its musl ppc64le/s390x wheels don't exist, so pip is the
59
+ # arch-portable choice there -- and read PIP_RETRIES/PIP_TIMEOUT, forwarded into
60
+ # `docker run` with `-e`.
61
+ env:
62
+ PIP_RETRIES: "5"
63
+ PIP_TIMEOUT: "60"
64
+ UV_HTTP_TIMEOUT: "60"
65
+ UV_PYTHON_DOWNLOADS: "never"
66
+
67
+ jobs:
68
+ # Compute the version once on the host (which has git + full history) and feed
69
+ # it to every build via SETUPTOOLS_SCM_PRETEND_VERSION -- mirroring release.yml,
70
+ # which pins all its builds to one number. Here the number is the natural
71
+ # setuptools_scm dev version, not a bumped release version. Pinning it also
72
+ # lets the musl build run inside an Alpine container with no git installed
73
+ # (the container can't run `git describe`, so it relies on the pretend value).
74
+ version:
75
+ runs-on: ubuntu-latest
76
+ outputs:
77
+ new: ${{ steps.ver.outputs.new }}
78
+ steps:
79
+ - uses: actions/checkout@v7.0.0
80
+ with:
81
+ fetch-depth: 0 # full history + tags so setuptools_scm resolves a version
82
+ - uses: actions/setup-python@v6.3.0
83
+ with:
84
+ python-version: '3.14'
85
+ - uses: astral-sh/setup-uv@v8.3.0
86
+ with:
87
+ version: "0.11.26"
88
+ - name: Compute build version
89
+ id: ver
90
+ run: |
91
+ set -euo pipefail
92
+ # Resolve setuptools_scm into a throwaway env and read the version off
93
+ # git -- no venv to create, no install to leave behind.
94
+ new="$(uv run --no-project --with setuptools-scm python -m setuptools_scm)"
95
+ echo "new=$new" >> "$GITHUB_OUTPUT"
96
+ echo "::notice::Build-only at version $new (no release)."
97
+
98
+ # Wheel + sdist, built and `twine check`ed exactly as release.yml does for
99
+ # PyPI -- but never uploaded to PyPI. The artifact is kept only for download.
100
+ dist:
101
+ needs: version
102
+ runs-on: ubuntu-latest
103
+ steps:
104
+ - uses: actions/checkout@v7.0.0
105
+ with:
106
+ fetch-depth: 0 # full history so setuptools_scm has a base (version is pinned below)
107
+ - uses: actions/setup-python@v6.3.0
108
+ with:
109
+ python-version: '3.14'
110
+ - uses: astral-sh/setup-uv@v8.3.0
111
+ with:
112
+ version: "0.11.26"
113
+ - name: Build distributions
114
+ env:
115
+ SETUPTOOLS_SCM_PRETEND_VERSION: ${{ needs.version.outputs.new }}
116
+ run: |
117
+ set -euo pipefail
118
+ uv build # wheel + sdist into dist/
119
+ uvx twine check dist/*.whl dist/*.tar.gz # twine, run ephemerally
120
+ - uses: actions/upload-artifact@v7.0.1
121
+ with:
122
+ name: dist
123
+ path: |
124
+ dist/*.whl
125
+ dist/*.tar.gz
126
+ if-no-files-found: error
127
+ retention-days: 7
128
+
129
+ # glibc PyInstaller binaries, one per arch on a native runner. Mirrors
130
+ # release.yml's `binaries` job (same pyinstaller pin, same `--version` smoke
131
+ # test); the only difference is the version is the dev version, not a release.
132
+ binaries:
133
+ needs: version
134
+ strategy:
135
+ fail-fast: false
136
+ matrix:
137
+ include:
138
+ - arch: amd64
139
+ runner: ubuntu-24.04
140
+ - arch: arm64
141
+ runner: ubuntu-24.04-arm
142
+ runs-on: ${{ matrix.runner }}
143
+ steps:
144
+ - uses: actions/checkout@v7.0.0
145
+ with:
146
+ fetch-depth: 0 # full history so setuptools_scm has a base (version is pinned below)
147
+ - uses: actions/setup-python@v6.3.0
148
+ with:
149
+ python-version: '3.14'
150
+ - uses: astral-sh/setup-uv@v8.3.0
151
+ with:
152
+ version: "0.11.26"
153
+ - name: Build binary
154
+ env:
155
+ SETUPTOOLS_SCM_PRETEND_VERSION: ${{ needs.version.outputs.new }}
156
+ run: |
157
+ set -euo pipefail
158
+ # Runner-native, all-wheels arch: install into a throwaway uv venv and
159
+ # freeze from it with `uv run` (uv discovers the setup-python 3.14).
160
+ uv venv --python 3.14
161
+ uv pip install pyinstaller==6.21.0 # pin matches pyinstaller/Dockerfile
162
+ uv pip install . # bakes the pinned version into cronstable/version.py
163
+ # Bundle uvloop (the `speedups` extra) so the binary runs the faster
164
+ # event loop. Install a wheel where one exists, else build it from
165
+ # sdist (ship uvloop as broadly as it compiles); a failed install
166
+ # falls back to asyncio. verify_uvloop.py then proves the result
167
+ # actually runs a loop (a source build can miscompile under emulation)
168
+ # and uninstalls it if not, so a broken uvloop is never frozen in and
169
+ # the binary cleanly uses asyncio.
170
+ uv pip install "uvloop>=0.19" || echo "uvloop unavailable; using asyncio"
171
+ uv run python pyinstaller/verify_uvloop.py || uv pip uninstall uvloop || true
172
+ # Bundle orjson (the `speedups` extra) to accelerate the durable-state
173
+ # and cluster JSON paths; amd64/arm64 and macOS ship wheels (no Rust
174
+ # needed). The shared script round-trip-verifies it and drops a broken
175
+ # build, so the binary falls back to stdlib json (cronstable/_json).
176
+ PIP="uv pip" PIPUNINST="uv pip uninstall" PY="uv run python" \
177
+ sh pyinstaller/install_orjson.sh
178
+ uv run pyinstaller pyinstaller/cronstable.spec
179
+ dist/cronstable --version # smoke test: fails the job if the bundle is broken
180
+ mv dist/cronstable "dist/cronstable-linux-${{ matrix.arch }}"
181
+ - uses: actions/upload-artifact@v7.0.1
182
+ with:
183
+ name: cronstable-linux-${{ matrix.arch }}
184
+ path: dist/cronstable-linux-${{ matrix.arch }}
185
+ if-no-files-found: error
186
+ retention-days: 7
187
+
188
+ # musl/Alpine PyInstaller binaries, built inside an Alpine container (see
189
+ # release.yml's binaries-musl for why it's `docker run`, not a job container).
190
+ # Mirrors release.yml's binaries-musl; the pinned version is what lets the
191
+ # no-git Alpine image resolve a setuptools_scm version. Covers all eight Linux
192
+ # musl arches: amd64/arm64 build natively on a matching runner; i686 (linux/386)
193
+ # runs natively on the amd64 runner (x86-64 executes 32-bit x86 directly);
194
+ # armv7, armv6, ppc64le, s390x and riscv64 have no native execution on the
195
+ # runners, so they run under QEMU emulation. musllinux wheels exist for the
196
+ # whole stack on armv7/ppc64le/s390x, but NOT for the i686 aiohttp family
197
+ # (aiohttp/yarl/frozenlist/propcache), nothing at all for armv6 (no musllinux
198
+ # armv6 wheels exist), or multidict/frozenlist/ruamel.yaml.clib on riscv64 --
199
+ # build-base compiles those from sdist.
200
+ binaries-musl:
201
+ needs: version
202
+ strategy:
203
+ fail-fast: false
204
+ matrix:
205
+ include:
206
+ - arch: amd64
207
+ runner: ubuntu-24.04
208
+ platform: linux/amd64
209
+ - arch: arm64
210
+ runner: ubuntu-24.04-arm
211
+ platform: linux/arm64
212
+ - arch: i686
213
+ runner: ubuntu-24.04
214
+ platform: linux/386
215
+ - arch: armv7
216
+ runner: ubuntu-24.04
217
+ platform: linux/arm/v7
218
+ qemu: true # no native 32-bit ARM on the runners -> emulate
219
+ - arch: ppc64le
220
+ runner: ubuntu-24.04
221
+ platform: linux/ppc64le
222
+ qemu: true # no native ppc64le on the runners -> emulate
223
+ - arch: s390x
224
+ runner: ubuntu-24.04
225
+ platform: linux/s390x
226
+ qemu: true # no native s390x on the runners -> emulate
227
+ - arch: riscv64
228
+ runner: ubuntu-24.04
229
+ platform: linux/riscv64
230
+ qemu: true # no native riscv64 on the runners -> emulate
231
+ # armv6 is musl-only: Debian/glibc ships no arm32v6 image (it ships
232
+ # arm32v5), so there is no binaries-glibc-extra counterpart. Entire
233
+ # C-ext stack + PyInstaller bootloader compile from sdist (no musllinux
234
+ # armv6 wheels) -> slowest leg.
235
+ - arch: armv6
236
+ runner: ubuntu-24.04
237
+ platform: linux/arm/v6
238
+ qemu: true # no native armv6 on the runners -> emulate
239
+ runs-on: ${{ matrix.runner }}
240
+ steps:
241
+ - uses: actions/checkout@v7.0.0
242
+ with:
243
+ fetch-depth: 0 # full history so setuptools_scm has a base (version is pinned below)
244
+ # Register binfmt/QEMU so `docker run --platform` can execute a foreign-arch
245
+ # container (armv7, armv6, ppc64le, s390x, riscv64). amd64/arm64/i686 run
246
+ # natively and skip this.
247
+ - if: ${{ matrix.qemu }}
248
+ uses: docker/setup-qemu-action@v4.2.0
249
+ - name: Build binary (Alpine/musl)
250
+ env:
251
+ SETUPTOOLS_SCM_PRETEND_VERSION: ${{ needs.version.outputs.new }}
252
+ run: |
253
+ set -euo pipefail
254
+ docker run --rm --platform ${{ matrix.platform }} \
255
+ -e SETUPTOOLS_SCM_PRETEND_VERSION \
256
+ -e PIP_RETRIES -e PIP_TIMEOUT \
257
+ -v "$PWD:/src" -w /src \
258
+ python:3.14-alpine sh -euc '
259
+ # build-base brings the C toolchain + strip (the spec sets
260
+ # strip=True); libffi/zlib headers cover any dep that lacks a
261
+ # musllinux wheel and falls back to compiling from sdist (notably
262
+ # the i686 aiohttp stack, which ships no musllinux_i686 wheels).
263
+ apk add --no-cache build-base libffi-dev zlib-dev
264
+ pip install --upgrade pip
265
+ pip install pyinstaller==6.21.0 # pin matches the glibc job (supports 3.14)
266
+ pip install . # bakes the pinned version into cronstable/version.py
267
+ # Bundle uvloop (the `speedups` extra): a musllinux wheel where one
268
+ # exists (amd64/arm64), else a source build (ship it as broadly as
269
+ # it compiles -- build-base above supplies the toolchain).
270
+ # verify_uvloop.py proves the result runs a loop (a source build can
271
+ # miscompile under QEMU) and uninstalls it otherwise, so a broken
272
+ # uvloop is never frozen in; the binary then cleanly falls back to
273
+ # asyncio.
274
+ pip install "uvloop>=0.19" || echo "uvloop unavailable; using asyncio"
275
+ python pyinstaller/verify_uvloop.py || pip uninstall -y uvloop || true
276
+ # Bundle orjson (the `speedups` extra): a musllinux wheel on amd64/
277
+ # arm64, else a Rust source build. RUST_SETUP installs a CURRENT
278
+ # Rust via rustup (distro rustc is older than the orjson MSRV) only
279
+ # when the wheel is missing; the shared script round-trip-verifies
280
+ # it and drops a broken QEMU build -> stdlib json fallback.
281
+ RUST_SETUP="apk add --no-cache curl && curl --proto =https --tlsv1.2 -sSf https://sh.rustup.rs | env CARGO_HOME=/opt/cargo RUSTUP_HOME=/opt/rustup sh -s -- -y --default-toolchain stable --profile minimal --no-modify-path" sh pyinstaller/install_orjson.sh
282
+ pyinstaller pyinstaller/cronstable.spec
283
+ dist/cronstable --version # smoke test: a musl binary on a musl host
284
+ mv dist/cronstable dist/cronstable-linux-${{ matrix.arch }}-musl
285
+ '
286
+ - uses: actions/upload-artifact@v7.0.1
287
+ with:
288
+ name: cronstable-linux-${{ matrix.arch }}-musl
289
+ path: dist/cronstable-linux-${{ matrix.arch }}-musl
290
+ if-no-files-found: error
291
+ retention-days: 7
292
+
293
+ # glibc PyInstaller binaries for the arches with no native GitHub runner
294
+ # (i686, armv7, ppc64le, s390x, riscv64). The native `binaries` job covers only
295
+ # amd64 and arm64 (the runners GitHub hosts) and PyInstaller is not a
296
+ # cross-compiler, so -- like binaries-musl -- these build inside a container via
297
+ # `docker run --platform`, here on python:3.14-slim (Debian glibc). i686
298
+ # (linux/386) runs natively on the amd64 runner; armv7, ppc64le, s390x and
299
+ # riscv64 run under QEMU. glibc wheel coverage varies: ppc64le/s390x have full
300
+ # manylinux wheels, while the i686 aiohttp stack, propcache-on-armv7, and
301
+ # multidict/frozenlist/ruamel.yaml.clib on riscv64 ship no wheel and compile
302
+ # from sdist -- so build-essential is installed either way. Assets carry no
303
+ # -musl suffix and sit beside the 64-bit glibc binaries.
304
+ binaries-glibc-extra:
305
+ needs: version
306
+ strategy:
307
+ fail-fast: false
308
+ matrix:
309
+ include:
310
+ - arch: i686
311
+ runner: ubuntu-24.04
312
+ platform: linux/386
313
+ - arch: armv7
314
+ runner: ubuntu-24.04
315
+ platform: linux/arm/v7
316
+ qemu: true
317
+ - arch: ppc64le
318
+ runner: ubuntu-24.04
319
+ platform: linux/ppc64le
320
+ qemu: true
321
+ - arch: s390x
322
+ runner: ubuntu-24.04
323
+ platform: linux/s390x
324
+ qemu: true
325
+ - arch: riscv64
326
+ runner: ubuntu-24.04
327
+ platform: linux/riscv64
328
+ qemu: true
329
+ runs-on: ${{ matrix.runner }}
330
+ steps:
331
+ - uses: actions/checkout@v7.0.0
332
+ with:
333
+ fetch-depth: 0 # full history so setuptools_scm has a base (version is pinned below)
334
+ - if: ${{ matrix.qemu }}
335
+ uses: docker/setup-qemu-action@v4.2.0
336
+ - name: Build binary (Debian/glibc container)
337
+ env:
338
+ SETUPTOOLS_SCM_PRETEND_VERSION: ${{ needs.version.outputs.new }}
339
+ run: |
340
+ set -euo pipefail
341
+ docker run --rm --platform ${{ matrix.platform }} \
342
+ -e SETUPTOOLS_SCM_PRETEND_VERSION \
343
+ -e PIP_RETRIES -e PIP_TIMEOUT \
344
+ -v "$PWD:/src" -w /src \
345
+ python:3.14-slim sh -euc '
346
+ # build-essential brings gcc + strip (the spec sets strip=True);
347
+ # libffi/zlib headers cover the C-extension deps that ship no glibc
348
+ # wheel and compile from sdist (the i686 aiohttp stack; propcache on
349
+ # armv7). ppc64le/s390x have full manylinux wheels, so the toolchain
350
+ # goes unused there.
351
+ apt-get update
352
+ apt-get install -y --no-install-recommends build-essential libffi-dev zlib1g-dev
353
+ pip install --upgrade pip
354
+ pip install pyinstaller==6.21.0 # pin matches the other binary jobs
355
+ pip install . # bakes the pinned version into cronstable/version.py
356
+ # Bundle uvloop (the `speedups` extra): a manylinux wheel where one
357
+ # exists (amd64/arm64), else a source build (ship it as broadly as
358
+ # it compiles -- build-essential above supplies the toolchain).
359
+ # verify_uvloop.py proves the result runs a loop (a source build can
360
+ # miscompile under QEMU) and uninstalls it otherwise, so a broken
361
+ # uvloop is never frozen in; the binary then cleanly falls back to
362
+ # asyncio.
363
+ pip install "uvloop>=0.19" || echo "uvloop unavailable; using asyncio"
364
+ python pyinstaller/verify_uvloop.py || pip uninstall -y uvloop || true
365
+ # Bundle orjson (the `speedups` extra): a manylinux wheel on amd64/
366
+ # arm64, else a Rust source build. RUST_SETUP installs a CURRENT
367
+ # Rust via rustup (distro rustc is older than the orjson MSRV) only
368
+ # when the wheel is missing; the shared script round-trip-verifies
369
+ # it and drops a broken QEMU build -> stdlib json fallback.
370
+ RUST_SETUP="apt-get -o Acquire::Retries=5 update && apt-get -o Acquire::Retries=5 install -y --no-install-recommends curl ca-certificates && curl --proto =https --tlsv1.2 -sSf https://sh.rustup.rs | env CARGO_HOME=/opt/cargo RUSTUP_HOME=/opt/rustup sh -s -- -y --default-toolchain stable --profile minimal --no-modify-path" sh pyinstaller/install_orjson.sh
371
+ pyinstaller pyinstaller/cronstable.spec
372
+ dist/cronstable --version # smoke test: native (i686) or emulated (armv7/ppc64le/s390x)
373
+ mv dist/cronstable dist/cronstable-linux-${{ matrix.arch }}
374
+ '
375
+ - uses: actions/upload-artifact@v7.0.1
376
+ with:
377
+ name: cronstable-linux-${{ matrix.arch }}
378
+ path: dist/cronstable-linux-${{ matrix.arch }}
379
+ if-no-files-found: error
380
+ retention-days: 7
381
+
382
+ # macOS PyInstaller binaries. Per-commit we build on BOTH macOS 15 and macOS
383
+ # 26 (Tahoe), each on a native runner, arm64 + Intel -- four jobs -- so OS-
384
+ # version-specific build breakage surfaces early. (release.yml still ships
385
+ # only the macOS 15 pair; this extra macOS 26 coverage is CI-only.) Mirrors
386
+ # release.yml's binaries-macos -- including the arch assertion that guards
387
+ # against Rosetta silently building the wrong arch -- but deliberately OMITS
388
+ # the "Sign and notarize" step (that needs secrets and only matters for a
389
+ # published release). The matrix carries an `os` (15/26) discriminator so the
390
+ # four binaries/artifacts get unique names instead of colliding on arch.
391
+ binaries-macos:
392
+ needs: version
393
+ strategy:
394
+ fail-fast: false
395
+ matrix:
396
+ include:
397
+ - arch: arm64
398
+ runner: macos-15 # macOS 15, Apple Silicon
399
+ os: '15'
400
+ - arch: amd64
401
+ runner: macos-15-intel # macOS 15, Intel
402
+ os: '15'
403
+ - arch: arm64
404
+ runner: macos-26 # macOS 26 (Tahoe), Apple Silicon
405
+ os: '26'
406
+ - arch: amd64
407
+ runner: macos-26-intel # macOS 26 (Tahoe), Intel
408
+ os: '26'
409
+ runs-on: ${{ matrix.runner }}
410
+ steps:
411
+ - uses: actions/checkout@v7.0.0
412
+ with:
413
+ fetch-depth: 0 # full history so setuptools_scm has a base (version is pinned below)
414
+ - uses: actions/setup-python@v6.3.0
415
+ with:
416
+ python-version: '3.14' # set explicitly; don't rely on the runner's preinstalled patch level
417
+ - uses: astral-sh/setup-uv@v8.3.0
418
+ with:
419
+ version: "0.11.26"
420
+ - name: Build binary
421
+ env:
422
+ SETUPTOOLS_SCM_PRETEND_VERSION: ${{ needs.version.outputs.new }}
423
+ run: |
424
+ set -euo pipefail
425
+ # Runner-native, all-wheels arch: install into a throwaway uv venv and
426
+ # freeze from it with `uv run` (uv discovers the setup-python 3.14).
427
+ uv venv --python 3.14
428
+ uv pip install pyinstaller==6.21.0 # pin matches the Linux jobs
429
+ uv pip install . # bakes the pinned version into cronstable/version.py
430
+ # Bundle uvloop (the `speedups` extra) for the faster event loop; macOS
431
+ # ships wheels for both arches. verify_uvloop.py confirms it imports and
432
+ # runs a loop, else uninstalls it so the binary falls back to asyncio
433
+ # (cronstable/__main__._new_event_loop).
434
+ uv pip install "uvloop>=0.19" || echo "uvloop unavailable; using asyncio"
435
+ uv run python pyinstaller/verify_uvloop.py || uv pip uninstall uvloop || true
436
+ # Bundle orjson (the `speedups` extra) to accelerate the durable-state
437
+ # and cluster JSON paths; amd64/arm64 and macOS ship wheels (no Rust
438
+ # needed). The shared script round-trip-verifies it and drops a broken
439
+ # build, so the binary falls back to stdlib json (cronstable/_json).
440
+ PIP="uv pip" PIPUNINST="uv pip uninstall" PY="uv run python" \
441
+ sh pyinstaller/install_orjson.sh
442
+ uv run pyinstaller pyinstaller/cronstable.spec
443
+ dist/cronstable --version # smoke test: fails the job if the bundle is broken
444
+ # Assert the binary is the intended native arch: Rosetta would happily
445
+ # run an x86_64 build on the arm64 runner and let the smoke test pass,
446
+ # silently producing a mislabelled asset. `file` reports x86_64/arm64.
447
+ case "${{ matrix.arch }}" in
448
+ amd64) want=x86_64 ;;
449
+ arm64) want=arm64 ;;
450
+ esac
451
+ file dist/cronstable | grep -q "$want"
452
+ mv dist/cronstable "dist/cronstable-macos${{ matrix.os }}-${{ matrix.arch }}"
453
+ - uses: actions/upload-artifact@v7.0.1
454
+ with:
455
+ name: cronstable-macos${{ matrix.os }}-${{ matrix.arch }}
456
+ path: dist/cronstable-macos${{ matrix.os }}-${{ matrix.arch }}
457
+ if-no-files-found: error
458
+ retention-days: 7
459
+
460
+ # Windows PyInstaller binaries: amd64 on the hosted runner, arm64 on the
461
+ # windows-11-arm runner. Mirrors release.yml's binaries-windows. Any C-
462
+ # extension dep without a win_arm64 wheel (parts of the aiohttp stack --
463
+ # multidict/yarl/frozenlist/propcache -- and ruamel.yaml.clib) compiles from
464
+ # sdist using the runner's Visual Studio ARM64 toolchain, the same way the
465
+ # QEMU Linux legs compile from sdist for riscv64/armv6. Same pyinstaller pin
466
+ # and `--version` smoke test as the other binary jobs; the bundle is a single
467
+ # self-contained cronstable-windows-<arch>.exe. Steps use bash (git bash ships on
468
+ # the Windows runners) so they read identically to the Linux/macOS jobs.
469
+ binaries-windows:
470
+ needs: version
471
+ strategy:
472
+ fail-fast: false
473
+ matrix:
474
+ include:
475
+ - arch: amd64
476
+ runner: windows-latest
477
+ - arch: arm64
478
+ runner: windows-11-arm
479
+ runs-on: ${{ matrix.runner }}
480
+ defaults:
481
+ run:
482
+ shell: bash
483
+ steps:
484
+ - uses: actions/checkout@v7.0.0
485
+ with:
486
+ fetch-depth: 0 # full history so setuptools_scm has a base (version is pinned below)
487
+ - uses: actions/setup-python@v6.3.0
488
+ with:
489
+ python-version: '3.14'
490
+ - uses: astral-sh/setup-uv@v8.3.0
491
+ with:
492
+ version: "0.11.26"
493
+ - name: Build binary
494
+ env:
495
+ SETUPTOOLS_SCM_PRETEND_VERSION: ${{ needs.version.outputs.new }}
496
+ run: |
497
+ set -euo pipefail
498
+ # Runner-native (uv ships a win_arm64 build too); install into a
499
+ # throwaway uv venv and freeze from it with `uv run`.
500
+ uv venv --python 3.14
501
+ uv pip install pyinstaller==6.21.0 # pin matches the Linux/macOS jobs
502
+ uv pip install . # bakes the pinned version into cronstable/version.py
503
+ # Bundle orjson (the `speedups` extra) for the durable-state / cluster
504
+ # JSON paths; Windows amd64 ships a wheel (arm64 falls back if none).
505
+ # The shared script round-trip-verifies it and drops a broken build,
506
+ # so the binary falls back to stdlib json (cronstable/_json).
507
+ PIP="uv pip" PIPUNINST="uv pip uninstall" PY="uv run python" \
508
+ sh pyinstaller/install_orjson.sh
509
+ uv run pyinstaller pyinstaller/cronstable.spec
510
+ ./dist/cronstable.exe --version # smoke test: fails the job if the bundle is broken
511
+ mv dist/cronstable.exe "dist/cronstable-windows-${{ matrix.arch }}.exe"
512
+ - uses: actions/upload-artifact@v7.0.1
513
+ with:
514
+ name: cronstable-windows-${{ matrix.arch }}
515
+ path: dist/cronstable-windows-${{ matrix.arch }}.exe
516
+ if-no-files-found: error
517
+ retention-days: 7