aslmp 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aslmp-0.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +192 -0
- aslmp-0.1.0/.github/ISSUE_TEMPLATE/config.yml +32 -0
- aslmp-0.1.0/.github/ISSUE_TEMPLATE/cpu_behaviour.yml +159 -0
- aslmp-0.1.0/.github/PULL_REQUEST_TEMPLATE.md +63 -0
- aslmp-0.1.0/.github/dependabot.yml +25 -0
- aslmp-0.1.0/.github/workflows/ci.yml +111 -0
- aslmp-0.1.0/.github/workflows/publish.yml +94 -0
- aslmp-0.1.0/.gitignore +38 -0
- aslmp-0.1.0/CHANGELOG.md +575 -0
- aslmp-0.1.0/CODE_OF_CONDUCT.md +132 -0
- aslmp-0.1.0/CONTRIBUTING.md +349 -0
- aslmp-0.1.0/LICENSE +202 -0
- aslmp-0.1.0/NOTICE +26 -0
- aslmp-0.1.0/PKG-INFO +892 -0
- aslmp-0.1.0/README.md +859 -0
- aslmp-0.1.0/SECURITY.md +189 -0
- aslmp-0.1.0/bench/_report.py +136 -0
- aslmp-0.1.0/bench/access_patterns.py +127 -0
- aslmp-0.1.0/bench/soak.py +727 -0
- aslmp-0.1.0/bench/transports.py +158 -0
- aslmp-0.1.0/docs/architecture.md +152 -0
- aslmp-0.1.0/docs/benchmarking.md +220 -0
- aslmp-0.1.0/docs/cli.md +219 -0
- aslmp-0.1.0/docs/errors.md +124 -0
- aslmp-0.1.0/docs/hardware.md +884 -0
- aslmp-0.1.0/docs/unverified.md +286 -0
- aslmp-0.1.0/pyproject.toml +112 -0
- aslmp-0.1.0/src/aslmp/__init__.py +955 -0
- aslmp-0.1.0/src/aslmp/__main__.py +21 -0
- aslmp-0.1.0/src/aslmp/_clock.py +37 -0
- aslmp-0.1.0/src/aslmp/_version.py +9 -0
- aslmp-0.1.0/src/aslmp/blocks/__init__.py +91 -0
- aslmp-0.1.0/src/aslmp/blocks/fields.py +911 -0
- aslmp-0.1.0/src/aslmp/blocks/layout.py +624 -0
- aslmp-0.1.0/src/aslmp/blocks/plan.py +1587 -0
- aslmp-0.1.0/src/aslmp/client.py +2623 -0
- aslmp-0.1.0/src/aslmp/commands/__init__.py +122 -0
- aslmp-0.1.0/src/aslmp/commands/base.py +924 -0
- aslmp-0.1.0/src/aslmp/commands/batch.py +390 -0
- aslmp-0.1.0/src/aslmp/commands/block.py +351 -0
- aslmp-0.1.0/src/aslmp/commands/info.py +376 -0
- aslmp-0.1.0/src/aslmp/commands/monitor.py +290 -0
- aslmp-0.1.0/src/aslmp/commands/ondemand.py +152 -0
- aslmp-0.1.0/src/aslmp/commands/password.py +165 -0
- aslmp-0.1.0/src/aslmp/commands/random.py +810 -0
- aslmp-0.1.0/src/aslmp/commands/registry.py +205 -0
- aslmp-0.1.0/src/aslmp/commands/remote.py +455 -0
- aslmp-0.1.0/src/aslmp/connection.py +1097 -0
- aslmp-0.1.0/src/aslmp/data/__init__.py +215 -0
- aslmp-0.1.0/src/aslmp/data/ambiguities.tsv +54 -0
- aslmp-0.1.0/src/aslmp/data/devices.tsv +62 -0
- aslmp-0.1.0/src/aslmp/data/end_codes.tsv +126 -0
- aslmp-0.1.0/src/aslmp/data/limits.tsv +138 -0
- aslmp-0.1.0/src/aslmp/data/manuals.tsv +22 -0
- aslmp-0.1.0/src/aslmp/data/model_codes.tsv +105 -0
- aslmp-0.1.0/src/aslmp/data/ranges_iqf.tsv +181 -0
- aslmp-0.1.0/src/aslmp/data/ranges_iqr.tsv +91 -0
- aslmp-0.1.0/src/aslmp/entries.py +237 -0
- aslmp-0.1.0/src/aslmp/errors/__init__.py +1380 -0
- aslmp-0.1.0/src/aslmp/errors/endcodes.py +2130 -0
- aslmp-0.1.0/src/aslmp/errors/routing.py +468 -0
- aslmp-0.1.0/src/aslmp/health.py +437 -0
- aslmp-0.1.0/src/aslmp/identity.py +190 -0
- aslmp-0.1.0/src/aslmp/loop.py +343 -0
- aslmp-0.1.0/src/aslmp/observability.py +951 -0
- aslmp-0.1.0/src/aslmp/profile.py +1311 -0
- aslmp-0.1.0/src/aslmp/profiles/__init__.py +179 -0
- aslmp-0.1.0/src/aslmp/profiles/fx5s.py +46 -0
- aslmp-0.1.0/src/aslmp/profiles/fx5u.py +912 -0
- aslmp-0.1.0/src/aslmp/profiles/fx5uc.py +44 -0
- aslmp-0.1.0/src/aslmp/profiles/fx5uj.py +87 -0
- aslmp-0.1.0/src/aslmp/profiles/iq_r.py +572 -0
- aslmp-0.1.0/src/aslmp/profiles/l.py +38 -0
- aslmp-0.1.0/src/aslmp/profiles/q.py +239 -0
- aslmp-0.1.0/src/aslmp/py.typed +0 -0
- aslmp-0.1.0/src/aslmp/resilience.py +569 -0
- aslmp-0.1.0/src/aslmp/results.py +362 -0
- aslmp-0.1.0/src/aslmp/sync.py +1026 -0
- aslmp-0.1.0/src/aslmp/testing/__init__.py +167 -0
- aslmp-0.1.0/src/aslmp/testing/conformance.py +855 -0
- aslmp-0.1.0/src/aslmp/testing/dispatch.py +1204 -0
- aslmp-0.1.0/src/aslmp/testing/memory.py +492 -0
- aslmp-0.1.0/src/aslmp/testing/pathology.py +401 -0
- aslmp-0.1.0/src/aslmp/testing/pytest_plugin.py +111 -0
- aslmp-0.1.0/src/aslmp/testing/scenario.py +142 -0
- aslmp-0.1.0/src/aslmp/testing/server.py +1075 -0
- aslmp-0.1.0/src/aslmp/testing/targets.py +711 -0
- aslmp-0.1.0/src/aslmp/testing/vectors.py +225 -0
- aslmp-0.1.0/src/aslmp/timed.py +788 -0
- aslmp-0.1.0/src/aslmp/timing.py +605 -0
- aslmp-0.1.0/src/aslmp/tools/__init__.py +82 -0
- aslmp-0.1.0/src/aslmp/tools/__main__.py +103 -0
- aslmp-0.1.0/src/aslmp/tools/_common.py +307 -0
- aslmp-0.1.0/src/aslmp/tools/ambiguities.py +255 -0
- aslmp-0.1.0/src/aslmp/tools/bench.py +521 -0
- aslmp-0.1.0/src/aslmp/tools/capabilities.py +150 -0
- aslmp-0.1.0/src/aslmp/tools/cite.py +175 -0
- aslmp-0.1.0/src/aslmp/tools/identify.py +100 -0
- aslmp-0.1.0/src/aslmp/tools/probe.py +134 -0
- aslmp-0.1.0/src/aslmp/tools/proxy.py +313 -0
- aslmp-0.1.0/src/aslmp/tools/read.py +258 -0
- aslmp-0.1.0/src/aslmp/tools/serve.py +188 -0
- aslmp-0.1.0/src/aslmp/tools/verify_ranges.py +266 -0
- aslmp-0.1.0/src/aslmp/tools/write.py +252 -0
- aslmp-0.1.0/src/aslmp/transport/__init__.py +65 -0
- aslmp-0.1.0/src/aslmp/transport/base.py +451 -0
- aslmp-0.1.0/src/aslmp/transport/inflight.py +292 -0
- aslmp-0.1.0/src/aslmp/transport/tcp.py +562 -0
- aslmp-0.1.0/src/aslmp/transport/udp.py +701 -0
- aslmp-0.1.0/src/aslmp/wire/__init__.py +13 -0
- aslmp-0.1.0/src/aslmp/wire/address.py +666 -0
- aslmp-0.1.0/src/aslmp/wire/citations.py +268 -0
- aslmp-0.1.0/src/aslmp/wire/codec.py +853 -0
- aslmp-0.1.0/src/aslmp/wire/devicetable.py +1195 -0
- aslmp-0.1.0/src/aslmp/wire/devspec.py +287 -0
- aslmp-0.1.0/src/aslmp/wire/frames.py +777 -0
- aslmp-0.1.0/src/aslmp/wire/raw.py +482 -0
- aslmp-0.1.0/src/aslmp/wire/reader.py +232 -0
- aslmp-0.1.0/src/aslmp/wire/route.py +195 -0
- aslmp-0.1.0/src/aslmp/wire/subcommand.py +144 -0
- aslmp-0.1.0/tests/hardware/test_fx5u.py +1013 -0
- aslmp-0.1.0/tests/hardware/test_remote_control.py +530 -0
- aslmp-0.1.0/tests/integration/test_blocks_against_simulator.py +469 -0
- aslmp-0.1.0/tests/integration/test_client_against_simulator.py +960 -0
- aslmp-0.1.0/tests/integration/test_conformance.py +605 -0
- aslmp-0.1.0/tests/typing/consumer.py +201 -0
- aslmp-0.1.0/tests/unit/test_address.py +489 -0
- aslmp-0.1.0/tests/unit/test_blocks_bounds.py +695 -0
- aslmp-0.1.0/tests/unit/test_blocks_fields.py +374 -0
- aslmp-0.1.0/tests/unit/test_blocks_layout.py +411 -0
- aslmp-0.1.0/tests/unit/test_blocks_plan.py +848 -0
- aslmp-0.1.0/tests/unit/test_citations.py +1628 -0
- aslmp-0.1.0/tests/unit/test_client.py +966 -0
- aslmp-0.1.0/tests/unit/test_codec.py +720 -0
- aslmp-0.1.0/tests/unit/test_commands_base.py +319 -0
- aslmp-0.1.0/tests/unit/test_commands_identity.py +196 -0
- aslmp-0.1.0/tests/unit/test_commands_random.py +368 -0
- aslmp-0.1.0/tests/unit/test_commands_refusals.py +626 -0
- aslmp-0.1.0/tests/unit/test_commands_registry.py +242 -0
- aslmp-0.1.0/tests/unit/test_commands_vectors.py +575 -0
- aslmp-0.1.0/tests/unit/test_connection.py +879 -0
- aslmp-0.1.0/tests/unit/test_devicetable.py +441 -0
- aslmp-0.1.0/tests/unit/test_devspec.py +413 -0
- aslmp-0.1.0/tests/unit/test_entries.py +268 -0
- aslmp-0.1.0/tests/unit/test_errors.py +867 -0
- aslmp-0.1.0/tests/unit/test_frames.py +760 -0
- aslmp-0.1.0/tests/unit/test_generated_is_current.py +241 -0
- aslmp-0.1.0/tests/unit/test_health.py +488 -0
- aslmp-0.1.0/tests/unit/test_inflight.py +240 -0
- aslmp-0.1.0/tests/unit/test_layering.py +659 -0
- aslmp-0.1.0/tests/unit/test_loop.py +338 -0
- aslmp-0.1.0/tests/unit/test_observability.py +791 -0
- aslmp-0.1.0/tests/unit/test_profile.py +588 -0
- aslmp-0.1.0/tests/unit/test_profiles.py +542 -0
- aslmp-0.1.0/tests/unit/test_public_surface.py +651 -0
- aslmp-0.1.0/tests/unit/test_read_write_symmetry.py +658 -0
- aslmp-0.1.0/tests/unit/test_reader.py +324 -0
- aslmp-0.1.0/tests/unit/test_resilience.py +353 -0
- aslmp-0.1.0/tests/unit/test_results.py +262 -0
- aslmp-0.1.0/tests/unit/test_simulator_dispatch.py +885 -0
- aslmp-0.1.0/tests/unit/test_simulator_lifecycle.py +94 -0
- aslmp-0.1.0/tests/unit/test_simulator_memory.py +213 -0
- aslmp-0.1.0/tests/unit/test_simulator_pathology.py +678 -0
- aslmp-0.1.0/tests/unit/test_simulator_scenario.py +100 -0
- aslmp-0.1.0/tests/unit/test_simulator_state.py +414 -0
- aslmp-0.1.0/tests/unit/test_simulator_switchboard.py +994 -0
- aslmp-0.1.0/tests/unit/test_simulator_targets.py +225 -0
- aslmp-0.1.0/tests/unit/test_simulator_vectors.py +244 -0
- aslmp-0.1.0/tests/unit/test_subcommand.py +142 -0
- aslmp-0.1.0/tests/unit/test_suite_hygiene.py +304 -0
- aslmp-0.1.0/tests/unit/test_sync.py +380 -0
- aslmp-0.1.0/tests/unit/test_timing.py +573 -0
- aslmp-0.1.0/tests/unit/test_tools.py +1000 -0
- aslmp-0.1.0/tests/unit/test_transport_base.py +192 -0
- aslmp-0.1.0/tests/unit/test_transport_tcp.py +782 -0
- aslmp-0.1.0/tests/unit/test_transport_udp.py +654 -0
- aslmp-0.1.0/tests/unit/test_wheel_contents.py +287 -0
- aslmp-0.1.0/tests/vectors/codec_vectors.jsonl +50 -0
- aslmp-0.1.0/tests/vectors/command_vectors.jsonl +33 -0
- aslmp-0.1.0/tests/vectors/frame_vectors.jsonl +39 -0
- aslmp-0.1.0/tools/gen_devicetable.py +294 -0
- aslmp-0.1.0/tools/gen_endcodes.py +255 -0
- aslmp-0.1.0/tools/gen_timed.py +508 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
name: Bug report
|
|
2
|
+
description: Something in aslmp behaves differently from what it says it does.
|
|
3
|
+
title: "[bug] "
|
|
4
|
+
labels: ["bug"]
|
|
5
|
+
body:
|
|
6
|
+
- type: markdown
|
|
7
|
+
attributes:
|
|
8
|
+
value: |
|
|
9
|
+
**Before anything else.** If your CPU is attached to something that can move,
|
|
10
|
+
do not reproduce a bug on it. This library writes to PLCs.
|
|
11
|
+
|
|
12
|
+
Two reports that belong somewhere else:
|
|
13
|
+
|
|
14
|
+
- *"My CPU answers differently from your table."* That is not a bug, it is the
|
|
15
|
+
most valuable report this project receives — use the **CPU behaviour
|
|
16
|
+
disagreement** template instead.
|
|
17
|
+
- A security vulnerability in `aslmp` itself: see `SECURITY.md`, not here.
|
|
18
|
+
|
|
19
|
+
Everything below the first two boxes is asking for the conditions of your
|
|
20
|
+
observation. That is not bureaucracy: this project has twice had to withdraw a
|
|
21
|
+
published claim because a measurement was recorded without them, and a report
|
|
22
|
+
about a PLC with no firmware version behind it cannot be acted on.
|
|
23
|
+
|
|
24
|
+
- type: textarea
|
|
25
|
+
id: what-happened
|
|
26
|
+
attributes:
|
|
27
|
+
label: What happened, and what you expected instead
|
|
28
|
+
description: >
|
|
29
|
+
If an exception was raised, paste the whole diagnostic block — the target,
|
|
30
|
+
request, sent, received, routes, timing, observed, action and manual lines are
|
|
31
|
+
each load-bearing. Redact the address or the device values first if they matter
|
|
32
|
+
to you; say where you redacted.
|
|
33
|
+
placeholder: |
|
|
34
|
+
aslmp.SlmpUnsupportedCommandError: end code 0xC059 — "..."
|
|
35
|
+
target ...
|
|
36
|
+
request ...
|
|
37
|
+
validations:
|
|
38
|
+
required: true
|
|
39
|
+
|
|
40
|
+
- type: textarea
|
|
41
|
+
id: reproduction
|
|
42
|
+
attributes:
|
|
43
|
+
label: The smallest code that shows it
|
|
44
|
+
description: >
|
|
45
|
+
Against the simulator (`aslmp serve`, or `aslmp.testing`) if you can reproduce
|
|
46
|
+
it there — a simulator repro is one we can fix without your PLC. Say so if it
|
|
47
|
+
only reproduces against real silicon.
|
|
48
|
+
render: python
|
|
49
|
+
validations:
|
|
50
|
+
required: true
|
|
51
|
+
|
|
52
|
+
- type: input
|
|
53
|
+
id: aslmp-version
|
|
54
|
+
attributes:
|
|
55
|
+
label: aslmp version
|
|
56
|
+
description: "`aslmp --version`, or the commit SHA if you are on a checkout."
|
|
57
|
+
placeholder: 0.1.0.dev0
|
|
58
|
+
validations:
|
|
59
|
+
required: true
|
|
60
|
+
|
|
61
|
+
- type: input
|
|
62
|
+
id: python-and-os
|
|
63
|
+
attributes:
|
|
64
|
+
label: Python version and OS
|
|
65
|
+
description: >
|
|
66
|
+
`python -VV` and the OS. Both matter here more than usual: clock resolution and
|
|
67
|
+
socket behaviour differ across them, and one shipped bug in this library existed
|
|
68
|
+
only on Windows below CPython 3.13.
|
|
69
|
+
placeholder: "3.11.15, Windows 11"
|
|
70
|
+
validations:
|
|
71
|
+
required: true
|
|
72
|
+
|
|
73
|
+
- type: input
|
|
74
|
+
id: cpu-model
|
|
75
|
+
attributes:
|
|
76
|
+
label: CPU model
|
|
77
|
+
description: The exact model as printed on the unit, not the family.
|
|
78
|
+
placeholder: FX5U-32MT/DS
|
|
79
|
+
validations:
|
|
80
|
+
required: true
|
|
81
|
+
|
|
82
|
+
- type: input
|
|
83
|
+
id: firmware
|
|
84
|
+
attributes:
|
|
85
|
+
label: CPU firmware version
|
|
86
|
+
description: >
|
|
87
|
+
GX Works3 → Diagnostics → Module Diagnostics, the firmware version field. A
|
|
88
|
+
measurement names one piece of silicon on one firmware; a report without this is
|
|
89
|
+
not actionable, and "unknown" is a worse answer than a delayed one.
|
|
90
|
+
placeholder: "1.065"
|
|
91
|
+
validations:
|
|
92
|
+
required: true
|
|
93
|
+
|
|
94
|
+
- type: dropdown
|
|
95
|
+
id: profile
|
|
96
|
+
attributes:
|
|
97
|
+
label: Profile key
|
|
98
|
+
description: The string passed to `Plc(profile=...)`. `aslmp identify HOST` prints it.
|
|
99
|
+
options:
|
|
100
|
+
- "melsec:iq-f/fx5u"
|
|
101
|
+
- "melsec:iq-f/fx5uc"
|
|
102
|
+
- "melsec:iq-f/fx5uj"
|
|
103
|
+
- "melsec:iq-f/fx5s"
|
|
104
|
+
- "melsec:iq-r"
|
|
105
|
+
- "melsec:iq-r/r00"
|
|
106
|
+
- "melsec:q"
|
|
107
|
+
- "melsec:l"
|
|
108
|
+
- "no PLC involved (simulator, unit test, CLI)"
|
|
109
|
+
validations:
|
|
110
|
+
required: true
|
|
111
|
+
|
|
112
|
+
- type: dropdown
|
|
113
|
+
id: transport
|
|
114
|
+
attributes:
|
|
115
|
+
label: Transport
|
|
116
|
+
options:
|
|
117
|
+
- TCP
|
|
118
|
+
- UDP
|
|
119
|
+
- not applicable
|
|
120
|
+
validations:
|
|
121
|
+
required: true
|
|
122
|
+
|
|
123
|
+
- type: dropdown
|
|
124
|
+
id: frame
|
|
125
|
+
attributes:
|
|
126
|
+
label: Frame type
|
|
127
|
+
options:
|
|
128
|
+
- 3E
|
|
129
|
+
- 4E
|
|
130
|
+
- not applicable
|
|
131
|
+
validations:
|
|
132
|
+
required: true
|
|
133
|
+
|
|
134
|
+
- type: dropdown
|
|
135
|
+
id: data-code
|
|
136
|
+
attributes:
|
|
137
|
+
label: Communication Data Code
|
|
138
|
+
description: >
|
|
139
|
+
On iQ-F this is an **Own Node Setting for the whole Ethernet port**, not a
|
|
140
|
+
per-entry one. If you are not sure, that is itself worth saying — a coding
|
|
141
|
+
mismatch produces no response at all rather than an error.
|
|
142
|
+
options:
|
|
143
|
+
- Binary
|
|
144
|
+
- ASCII
|
|
145
|
+
- not sure
|
|
146
|
+
- not applicable
|
|
147
|
+
validations:
|
|
148
|
+
required: true
|
|
149
|
+
|
|
150
|
+
- type: textarea
|
|
151
|
+
id: connection-entry
|
|
152
|
+
attributes:
|
|
153
|
+
label: The connection entry, as GX Works3 has it
|
|
154
|
+
description: >
|
|
155
|
+
Port number, and anything unusual: a UDP entry's destination IP, how many entries
|
|
156
|
+
the CPU has configured, whether anything else was connected at the time. One TCP
|
|
157
|
+
connection is served per entry, so "something else was on it" explains a whole
|
|
158
|
+
class of report.
|
|
159
|
+
placeholder: |
|
|
160
|
+
TCP, PLC-side port 5002, one of six entries. Nothing else connected.
|
|
161
|
+
validations:
|
|
162
|
+
required: true
|
|
163
|
+
|
|
164
|
+
- type: input
|
|
165
|
+
id: host-and-link
|
|
166
|
+
attributes:
|
|
167
|
+
label: Host and link
|
|
168
|
+
description: >
|
|
169
|
+
Required if your report quotes a latency figure, and useful otherwise. Where the
|
|
170
|
+
client ran, what is between it and the CPU, and the median RTT if you know it.
|
|
171
|
+
This project published a transport conclusion that turned out to be a property of
|
|
172
|
+
its Wi-Fi link; every timing table in this repository now names these.
|
|
173
|
+
placeholder: "laptop at 192.168.10.41, Wi-Fi, ~7 ms median RTT"
|
|
174
|
+
validations:
|
|
175
|
+
required: false
|
|
176
|
+
|
|
177
|
+
- type: checkboxes
|
|
178
|
+
id: confirmations
|
|
179
|
+
attributes:
|
|
180
|
+
label: Before you post
|
|
181
|
+
options:
|
|
182
|
+
- label: >
|
|
183
|
+
This is not a security vulnerability report. Those follow `SECURITY.md` —
|
|
184
|
+
private reporting first, never a public issue.
|
|
185
|
+
required: true
|
|
186
|
+
- label: >
|
|
187
|
+
If I quoted a latency number, I named the host and the link it came from.
|
|
188
|
+
required: false
|
|
189
|
+
- label: >
|
|
190
|
+
I have read `docs/unverified.md` and this is not a path that already ships
|
|
191
|
+
labelled unverified.
|
|
192
|
+
required: false
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Blank issues are off because both templates exist to collect the conditions of an
|
|
2
|
+
# observation, and a report without them cannot be acted on. Anything that is not a bug
|
|
3
|
+
# and not a CPU disagreement is a question, and questions are better in Discussions,
|
|
4
|
+
# where the answer stays findable.
|
|
5
|
+
blank_issues_enabled: false
|
|
6
|
+
|
|
7
|
+
contact_links:
|
|
8
|
+
- name: Question, or "how do I ...?"
|
|
9
|
+
url: https://github.com/AcaysiaChem/aslmp/discussions
|
|
10
|
+
about: >
|
|
11
|
+
Setup, GX Works3 parameters, which profile to pass, why a read came back silent.
|
|
12
|
+
Ask here — a question is not a defect, and the README's PLC-side section grew out
|
|
13
|
+
of exactly these.
|
|
14
|
+
|
|
15
|
+
- name: Security vulnerability in aslmp
|
|
16
|
+
url: https://github.com/AcaysiaChem/aslmp/security/policy
|
|
17
|
+
about: >
|
|
18
|
+
Do not open a public issue. The reporting address and what we can and cannot fix
|
|
19
|
+
are in SECURITY.md.
|
|
20
|
+
|
|
21
|
+
- name: "SLMP has no authentication and no encryption"
|
|
22
|
+
url: https://github.com/AcaysiaChem/aslmp/blob/main/SECURITY.md
|
|
23
|
+
about: >
|
|
24
|
+
That is a property of the protocol, not a defect in this library, and it is not
|
|
25
|
+
something we can fix. Read SECURITY.md before reporting it.
|
|
26
|
+
|
|
27
|
+
- name: Is this path verified?
|
|
28
|
+
url: https://github.com/AcaysiaChem/aslmp/blob/main/docs/unverified.md
|
|
29
|
+
about: >
|
|
30
|
+
Seven of the eight shipped profiles have never been connected to, and the ASCII
|
|
31
|
+
path has never been on a wire. If what surprised you is on that page, an issue
|
|
32
|
+
saying so is still welcome — but the page may already answer it.
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
name: CPU behaviour disagreement
|
|
2
|
+
description: Your CPU answers differently from a shipped table, or you can close an unverified row.
|
|
3
|
+
title: "[cpu] "
|
|
4
|
+
labels: ["hardware", "provenance"]
|
|
5
|
+
body:
|
|
6
|
+
- type: markdown
|
|
7
|
+
attributes:
|
|
8
|
+
value: |
|
|
9
|
+
**This is the report this project most wants.** One CPU is the whole of our
|
|
10
|
+
evidence: a MELSEC iQ-F FX5U-32MT/DS on firmware 1.065. Seven of the eight
|
|
11
|
+
shipped profiles have never been connected to, the ASCII path has never been on a
|
|
12
|
+
wire, and `docs/unverified.md` lists every one of them with the experiment that
|
|
13
|
+
would settle it. An iQ-R, a Q, an L, an FX5UC/FX5UJ/FX5S or an ASCII-coded port
|
|
14
|
+
turns a row of that page from "the manual says" into "we measured".
|
|
15
|
+
|
|
16
|
+
The data files are built to receive this: every row of every table in
|
|
17
|
+
`src/aslmp/data/` carries a provenance tail — cpu, firmware, measured, host,
|
|
18
|
+
medium, samples — and a row cannot be marked measured without them. The fields
|
|
19
|
+
below **are** those columns. Filling them in is most of the work of the change.
|
|
20
|
+
|
|
21
|
+
Safety first, as ever: do not probe a CPU that is attached to anything that can
|
|
22
|
+
move, and note that `aslmp verify-ranges` reads devices the profile would
|
|
23
|
+
normally refuse (it writes nothing and changes no CPU state).
|
|
24
|
+
|
|
25
|
+
- type: textarea
|
|
26
|
+
id: disagreement
|
|
27
|
+
attributes:
|
|
28
|
+
label: What we say, and what your CPU said
|
|
29
|
+
description: >
|
|
30
|
+
Name the table or the claim — a device range, a point limit, an end code, a model
|
|
31
|
+
code, a capability, a line in `docs/unverified.md` or `docs/hardware.md` — and put
|
|
32
|
+
the CPU's actual answer beside it. End codes and raw bytes are better than prose.
|
|
33
|
+
placeholder: |
|
|
34
|
+
ranges_iqr.tsv says melsec:iq-r R has 0 points. On this R04CPU, R32767 reads
|
|
35
|
+
0x0000 and R32768 returns 0xC056, so the real boundary is 32768 points.
|
|
36
|
+
validations:
|
|
37
|
+
required: true
|
|
38
|
+
|
|
39
|
+
- type: input
|
|
40
|
+
id: cpu
|
|
41
|
+
attributes:
|
|
42
|
+
label: CPU model
|
|
43
|
+
description: The exact model, as printed on the unit. `Measurement.cpu`.
|
|
44
|
+
placeholder: R04CPU
|
|
45
|
+
validations:
|
|
46
|
+
required: true
|
|
47
|
+
|
|
48
|
+
- type: input
|
|
49
|
+
id: firmware
|
|
50
|
+
attributes:
|
|
51
|
+
label: Firmware version
|
|
52
|
+
description: >
|
|
53
|
+
`Measurement.firmware`. GX Works3 → Diagnostics → Module Diagnostics. A firmware
|
|
54
|
+
update can invalidate any row in this repository, which is why no row exists
|
|
55
|
+
without this.
|
|
56
|
+
placeholder: "1.065"
|
|
57
|
+
validations:
|
|
58
|
+
required: true
|
|
59
|
+
|
|
60
|
+
- type: input
|
|
61
|
+
id: date
|
|
62
|
+
attributes:
|
|
63
|
+
label: Date measured
|
|
64
|
+
description: "`Measurement.date`. ISO `YYYY-MM-DD`; the type refuses anything else."
|
|
65
|
+
placeholder: "2026-09-07"
|
|
66
|
+
validations:
|
|
67
|
+
required: true
|
|
68
|
+
|
|
69
|
+
- type: input
|
|
70
|
+
id: host
|
|
71
|
+
attributes:
|
|
72
|
+
label: Host
|
|
73
|
+
description: >
|
|
74
|
+
`Measurement.host` — where the client ran, precisely enough to go back to it: an
|
|
75
|
+
address, a name, or both.
|
|
76
|
+
placeholder: "192.168.10.36 (argus-bench)"
|
|
77
|
+
validations:
|
|
78
|
+
required: true
|
|
79
|
+
|
|
80
|
+
- type: input
|
|
81
|
+
id: medium
|
|
82
|
+
attributes:
|
|
83
|
+
label: Link
|
|
84
|
+
description: >
|
|
85
|
+
`Measurement.medium` — what ran between that host and the CPU, with the median RTT
|
|
86
|
+
if you know it. Leaving this out is the specific omission that cost this project a
|
|
87
|
+
published claim: a transport conclusion that was a property of one Wi-Fi link.
|
|
88
|
+
placeholder: "wired, 3.64 ms median RTT"
|
|
89
|
+
validations:
|
|
90
|
+
required: true
|
|
91
|
+
|
|
92
|
+
- type: input
|
|
93
|
+
id: samples
|
|
94
|
+
attributes:
|
|
95
|
+
label: n
|
|
96
|
+
description: >
|
|
97
|
+
`Measurement.samples` — how many observations the answer rests on. `1` is a fine
|
|
98
|
+
answer for "this end code came back"; a percentile without its n cannot be given an
|
|
99
|
+
error bar by a reader. Leave blank when a count is not what the claim rests on.
|
|
100
|
+
placeholder: "300"
|
|
101
|
+
validations:
|
|
102
|
+
required: false
|
|
103
|
+
|
|
104
|
+
- type: dropdown
|
|
105
|
+
id: what-it-touches
|
|
106
|
+
attributes:
|
|
107
|
+
label: What this would change
|
|
108
|
+
description: Best guess is fine; we will work it out in the thread.
|
|
109
|
+
multiple: true
|
|
110
|
+
options:
|
|
111
|
+
- "a device range (ranges_iqf.tsv / ranges_iqr.tsv)"
|
|
112
|
+
- "a point limit (limits.tsv)"
|
|
113
|
+
- "an end code (end_codes.tsv)"
|
|
114
|
+
- "a model code (model_codes.tsv)"
|
|
115
|
+
- "a profile capability"
|
|
116
|
+
- "an open ambiguity (ambiguities.tsv, an A-... key)"
|
|
117
|
+
- "a row of docs/unverified.md"
|
|
118
|
+
- "a figure in docs/hardware.md"
|
|
119
|
+
- "not sure"
|
|
120
|
+
validations:
|
|
121
|
+
required: true
|
|
122
|
+
|
|
123
|
+
- type: input
|
|
124
|
+
id: ambiguity-key
|
|
125
|
+
attributes:
|
|
126
|
+
label: Ambiguity key, if this closes one
|
|
127
|
+
description: >
|
|
128
|
+
`aslmp ambiguities` prints the table, each row with the experiment that would
|
|
129
|
+
settle it. If you ran one of those probes, name its key.
|
|
130
|
+
placeholder: "A-ZR-RADIX"
|
|
131
|
+
validations:
|
|
132
|
+
required: false
|
|
133
|
+
|
|
134
|
+
- type: textarea
|
|
135
|
+
id: evidence
|
|
136
|
+
attributes:
|
|
137
|
+
label: The evidence, as your tools printed it
|
|
138
|
+
description: >
|
|
139
|
+
Whatever you have: `aslmp identify`, `aslmp capabilities <key>`,
|
|
140
|
+
`aslmp verify-ranges HOST --profile KEY --python` (which emits a paste-ready
|
|
141
|
+
`with_ranges()` call), `aslmp.testing.run_conformance`, `aslmp proxy` frames, or
|
|
142
|
+
raw hex. Unedited output is worth more than a summary of it.
|
|
143
|
+
render: text
|
|
144
|
+
validations:
|
|
145
|
+
required: true
|
|
146
|
+
|
|
147
|
+
- type: checkboxes
|
|
148
|
+
id: confirmations
|
|
149
|
+
attributes:
|
|
150
|
+
label: Before you post
|
|
151
|
+
options:
|
|
152
|
+
- label: >
|
|
153
|
+
Nothing was written to a machine that can move something, and I did not issue
|
|
154
|
+
remote RUN/STOP/PAUSE/RESET/Latch Clear on a CPU I could not see.
|
|
155
|
+
required: true
|
|
156
|
+
- label: >
|
|
157
|
+
The CPU model and firmware above are the ones this observation came from, not
|
|
158
|
+
the family or a sibling unit.
|
|
159
|
+
required: true
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Thank you. CONTRIBUTING.md has the long version of everything below; this is the short
|
|
3
|
+
list that gets a change merged. Delete the sections that do not apply, but do not delete
|
|
4
|
+
the provenance section if your diff touches a number.
|
|
5
|
+
-->
|
|
6
|
+
|
|
7
|
+
## What this changes, and why
|
|
8
|
+
|
|
9
|
+
<!-- One paragraph. If it is a fix, say what the wrong behaviour was, not just what the
|
|
10
|
+
new behaviour is. -->
|
|
11
|
+
|
|
12
|
+
## Checks
|
|
13
|
+
|
|
14
|
+
Run all three locally — CI runs them on 3.11, 3.12 and 3.13 across Linux, Windows and
|
|
15
|
+
macOS, and the matrix exists because a bug in this library was visible on exactly one of
|
|
16
|
+
those nine cells.
|
|
17
|
+
|
|
18
|
+
- [ ] `python -m ruff check src tests tools bench`
|
|
19
|
+
- [ ] `python -m mypy`
|
|
20
|
+
- [ ] `python -m pytest tests -q`
|
|
21
|
+
|
|
22
|
+
## Does this touch a hardware claim?
|
|
23
|
+
|
|
24
|
+
A hardware claim is any number, limit, range, end code, capability or behaviour that is
|
|
25
|
+
asserted about real silicon — in a `.tsv`, in a docstring, in a doc, in a comment, or in
|
|
26
|
+
a test's expected value. **This project does not accept one without its conditions.**
|
|
27
|
+
Two published claims have been withdrawn here, and both were prose figures whose
|
|
28
|
+
conditions were never written down.
|
|
29
|
+
|
|
30
|
+
If your diff has one, fill this in; if it does not, write "no hardware claim" and move on.
|
|
31
|
+
|
|
32
|
+
- **CPU model:**
|
|
33
|
+
- **Firmware version:**
|
|
34
|
+
- **Date measured (ISO):**
|
|
35
|
+
- **Host:** <!-- where the client ran; an address, a name, or both -->
|
|
36
|
+
- **Link:** <!-- wired / Wi-Fi, with median RTT if known -->
|
|
37
|
+
- **n:** <!-- samples behind the figure, or "not a sample-based claim" -->
|
|
38
|
+
|
|
39
|
+
- [ ] The claim is expressed as a `Measurement(...)` / `Evidence.measured(...)`, not as a
|
|
40
|
+
bare string, so the provenance and the source cannot drift apart.
|
|
41
|
+
- [ ] Provenance is `LIVE` only where silicon was actually observed. A manual reading is
|
|
42
|
+
`MANUAL`; a value carried across from a sibling model is `INFERRED`, and says so.
|
|
43
|
+
- [ ] If this converts an unverified row to a measured one, `docs/unverified.md` was
|
|
44
|
+
updated in the same commit. That page is billed as complete and a test enforces it.
|
|
45
|
+
|
|
46
|
+
## Everything else
|
|
47
|
+
|
|
48
|
+
- [ ] No new runtime dependency. The package has zero, deliberately: the wheel has to
|
|
49
|
+
install on a Jetson's aarch64 and on a plant PC with no compiler.
|
|
50
|
+
- [ ] Nothing retries, clamps, substitutes a default, or returns a stale value. An
|
|
51
|
+
AST-level test enforces this over the whole package; if you had to work around it,
|
|
52
|
+
say so here rather than widening the exemption.
|
|
53
|
+
- [ ] No new path from the CLI to remote RUN / STOP / PAUSE / RESET / Latch Clear. A
|
|
54
|
+
shell history is not an interlock, and a test walks the AST of `aslmp/tools` to
|
|
55
|
+
prove there is no such path.
|
|
56
|
+
- [ ] Public surface changes are reflected in `aslmp.__all__` and `CHANGELOG.md`.
|
|
57
|
+
- [ ] If this makes an unverified path look verified, or reads as a promise about safety,
|
|
58
|
+
certification or conformance, it does not go in. This library is not a safety
|
|
59
|
+
system, holds no conformance certification, and has no affiliation with Mitsubishi
|
|
60
|
+
Electric or the CC-Link Partner Association.
|
|
61
|
+
|
|
62
|
+
By opening this pull request you are offering the contribution under the project's
|
|
63
|
+
Apache-2.0 licence (see section 5 of `LICENSE`).
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# GitHub Actions only, and that is not an oversight.
|
|
2
|
+
#
|
|
3
|
+
# `aslmp` has zero runtime dependencies -- deliberately and load-bearing: the wheel has
|
|
4
|
+
# to install on a Jetson's aarch64 and on a locked-down plant PC with no compiler and no
|
|
5
|
+
# proxy to PyPI's transitive graph. So there is no `pip` ecosystem entry here, because
|
|
6
|
+
# there is nothing in it to watch. The `dev` extra (pytest, hypothesis, mypy, ruff) is
|
|
7
|
+
# declared as unpinned floors rather than pins, so a bump would have nothing to change;
|
|
8
|
+
# CI installs whatever is current on each run, which is the honest way to find out that a
|
|
9
|
+
# new mypy disagrees with us.
|
|
10
|
+
#
|
|
11
|
+
# The actions in `.github/workflows/` are pinned by major version, which is the one place
|
|
12
|
+
# this repository does carry a supply chain -- a workflow action runs with a checkout of
|
|
13
|
+
# our source and, in publish.yml, beside an OIDC token.
|
|
14
|
+
|
|
15
|
+
version: 2
|
|
16
|
+
|
|
17
|
+
updates:
|
|
18
|
+
- package-ecosystem: github-actions
|
|
19
|
+
directory: "/"
|
|
20
|
+
schedule:
|
|
21
|
+
interval: weekly
|
|
22
|
+
commit-message:
|
|
23
|
+
prefix: "ci"
|
|
24
|
+
labels:
|
|
25
|
+
- dependencies
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# The three checks this project ships under, on every interpreter and every OS it claims.
|
|
2
|
+
#
|
|
3
|
+
# Why the matrix is nine jobs and not one
|
|
4
|
+
# ---------------------------------------
|
|
5
|
+
# This project shipped a real bug that exactly one cell of this matrix would have caught.
|
|
6
|
+
# `time.monotonic()` is backed by `GetTickCount64()` on Windows before CPython 3.13 and
|
|
7
|
+
# steps at 15.625 ms, so on the oldest interpreter this package claims, on Windows, every
|
|
8
|
+
# stamp it took landed on a ~16 ms grid: a 6 ms round trip was recorded as 0.0 or 16.0.
|
|
9
|
+
# Measured 2026-09-12 on Python 3.11.15, Windows 11, against `perf_counter_ns` on the same
|
|
10
|
+
# machine and the same sleep. It was invisible during development, which happens on 3.13,
|
|
11
|
+
# where `monotonic` is `QueryPerformanceCounter` and resolves to 100 ns; it surfaced only
|
|
12
|
+
# when the library was driven from a 3.11 environment. The fix is `src/aslmp/_clock.py`.
|
|
13
|
+
#
|
|
14
|
+
# "Latency as data" is this library's whole argument, so a quantised latency figure is
|
|
15
|
+
# worse than an absent one. A single-version, single-OS CI would not have found that and
|
|
16
|
+
# will not find the next one. The matrix is here for that reason and not for tidiness.
|
|
17
|
+
#
|
|
18
|
+
# Note that `mypy` pins `python_version = "3.11"` in `pyproject.toml`, so the interpreter
|
|
19
|
+
# axis does not change what it type-checks -- the OS axis does, wherever behaviour is
|
|
20
|
+
# platform-conditional, and that is the half of this matrix that earns its keep for mypy.
|
|
21
|
+
|
|
22
|
+
name: CI
|
|
23
|
+
|
|
24
|
+
on:
|
|
25
|
+
push:
|
|
26
|
+
branches: [main]
|
|
27
|
+
pull_request:
|
|
28
|
+
workflow_dispatch:
|
|
29
|
+
|
|
30
|
+
permissions:
|
|
31
|
+
contents: read
|
|
32
|
+
|
|
33
|
+
concurrency:
|
|
34
|
+
group: ci-${{ github.workflow }}-${{ github.ref }}
|
|
35
|
+
cancel-in-progress: true
|
|
36
|
+
|
|
37
|
+
jobs:
|
|
38
|
+
checks:
|
|
39
|
+
name: ${{ matrix.os }} / Python ${{ matrix.python-version }}
|
|
40
|
+
runs-on: ${{ matrix.os }}
|
|
41
|
+
|
|
42
|
+
# A healthy cell finishes in about two minutes. This bound is here because four
|
|
43
|
+
# cells of this matrix once sat INSIDE the pytest step for fourteen minutes and were
|
|
44
|
+
# still going when an unrelated push happened to cancel the run (2026-09-24, commit
|
|
45
|
+
# d78ef84: macos/3.12, macos/3.13, ubuntu/3.12 and windows/3.12 hung while
|
|
46
|
+
# windows/3.13 passed the same step in 109 s). Nothing stopped them but that
|
|
47
|
+
# accident. GitHub's default job timeout is 360 minutes, so each of those cells was
|
|
48
|
+
# entitled to six hours, and a macOS minute bills at ten times a Linux one.
|
|
49
|
+
#
|
|
50
|
+
# Fifteen minutes is roughly seven times the honest run and still bounds a hung
|
|
51
|
+
# macOS cell to about a dollar instead of twenty-two.
|
|
52
|
+
timeout-minutes: 15
|
|
53
|
+
strategy:
|
|
54
|
+
# Never fail fast. The point of the matrix is to see WHICH cells disagree; a
|
|
55
|
+
# cancelled run hides exactly the pattern that identifies a clock or a path bug.
|
|
56
|
+
fail-fast: false
|
|
57
|
+
matrix:
|
|
58
|
+
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
59
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
60
|
+
|
|
61
|
+
defaults:
|
|
62
|
+
run:
|
|
63
|
+
# bash on all three runners, so the quoting in these steps is written once.
|
|
64
|
+
shell: bash
|
|
65
|
+
|
|
66
|
+
steps:
|
|
67
|
+
- uses: actions/checkout@v7
|
|
68
|
+
|
|
69
|
+
- uses: actions/setup-python@v7
|
|
70
|
+
with:
|
|
71
|
+
python-version: ${{ matrix.python-version }}
|
|
72
|
+
# There is nothing to lock -- this package has zero runtime dependencies -- but
|
|
73
|
+
# the dev extra pulls a toolchain on every one of nine cells, and two of those
|
|
74
|
+
# runners bill by the minute.
|
|
75
|
+
cache: pip
|
|
76
|
+
cache-dependency-path: pyproject.toml
|
|
77
|
+
|
|
78
|
+
- name: Install
|
|
79
|
+
run: |
|
|
80
|
+
python -m pip install --upgrade pip
|
|
81
|
+
python -m pip install -e ".[dev]"
|
|
82
|
+
|
|
83
|
+
# Hardware tests are excluded on purpose, and this step says so rather than
|
|
84
|
+
# leaving it to the marker. tests/hardware/ is gated on ASLMP_TEST_HOST and drives
|
|
85
|
+
# a real FX5U: it writes to scratch registers, and tests/hardware/
|
|
86
|
+
# test_remote_control.py issues Remote STOP, which halts a CPU. A CI runner must
|
|
87
|
+
# never be able to reach a PLC, so the variable is not set here and must not be
|
|
88
|
+
# added as a repository or environment secret.
|
|
89
|
+
- name: Assert the hardware gate is not set
|
|
90
|
+
run: |
|
|
91
|
+
if [ -n "${ASLMP_TEST_HOST:-}" ]; then
|
|
92
|
+
echo "ASLMP_TEST_HOST is set in CI. Hardware tests write to a real PLC and"
|
|
93
|
+
echo "can halt a CPU. Remove it from the repository/environment settings."
|
|
94
|
+
exit 1
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
- name: ruff
|
|
98
|
+
run: python -m ruff check src tests tools bench
|
|
99
|
+
|
|
100
|
+
- name: mypy --strict
|
|
101
|
+
run: python -m mypy
|
|
102
|
+
|
|
103
|
+
# `-m "not hardware"` is belt and braces beside the ASLMP_TEST_HOST gate: the
|
|
104
|
+
# marker is the declared contract, the missing variable is what actually makes the
|
|
105
|
+
# tests skip, and neither one alone is worth relying on for something that can stop
|
|
106
|
+
# a machine.
|
|
107
|
+
# `faulthandler_timeout` in pyproject.toml is what makes a hang here legible: the
|
|
108
|
+
# stacks land in this step's log about a minute in, long before `timeout-minutes`
|
|
109
|
+
# kills the job, so the log names the test and the await instead of just stopping.
|
|
110
|
+
- name: pytest
|
|
111
|
+
run: python -m pytest tests -q -m "not hardware" --durations=10
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Build a release and publish it to PyPI with trusted publishing (OIDC).
|
|
2
|
+
#
|
|
3
|
+
# There is no API token anywhere in this file and there must never be one. Trusted
|
|
4
|
+
# publishing mints a short-lived, workflow-scoped credential at run time; a long-lived
|
|
5
|
+
# token in a repository secret is a credential that can publish `aslmp` from anywhere,
|
|
6
|
+
# forever, and this package installs on machines that talk to PLCs.
|
|
7
|
+
#
|
|
8
|
+
# WHAT THE OWNER STILL HAS TO DO -- none of it can be done from this repository
|
|
9
|
+
# ----------------------------------------------------------------------------
|
|
10
|
+
# 1. On PyPI, add a *pending* trusted publisher before the first release
|
|
11
|
+
# (https://pypi.org/manage/account/publishing/), because the project name is not
|
|
12
|
+
# registered yet -- README.md says so in its Install section. The four fields are:
|
|
13
|
+
# PyPI project name: aslmp
|
|
14
|
+
# Owner: AcaysiaChem
|
|
15
|
+
# Repository name: aslmp
|
|
16
|
+
# Workflow name: publish.yml
|
|
17
|
+
# Environment name: pypi
|
|
18
|
+
# All five must match exactly, including the environment, or the publish step is
|
|
19
|
+
# rejected with an OIDC error rather than a permissions one.
|
|
20
|
+
# 2. Create a GitHub environment named `pypi` (Settings -> Environments) and, if you
|
|
21
|
+
# want a human in the loop, add yourself as a required reviewer there. That is the
|
|
22
|
+
# only gate between "someone pushed a release" and "a new aslmp is on PyPI".
|
|
23
|
+
# 3. Publish a GitHub release whose tag is the version, e.g. `v0.1.0`. This workflow
|
|
24
|
+
# refuses to publish if that tag disagrees with `src/aslmp/_version.py`.
|
|
25
|
+
#
|
|
26
|
+
# Two jobs on purpose: `build` needs no privileges at all, and `publish` holds the OIDC
|
|
27
|
+
# token and runs nothing but the upload. Nothing that touches the internet or the
|
|
28
|
+
# project's own code executes in the job that can sign.
|
|
29
|
+
|
|
30
|
+
name: Publish
|
|
31
|
+
|
|
32
|
+
on:
|
|
33
|
+
release:
|
|
34
|
+
types: [published]
|
|
35
|
+
|
|
36
|
+
permissions:
|
|
37
|
+
contents: read
|
|
38
|
+
|
|
39
|
+
jobs:
|
|
40
|
+
build:
|
|
41
|
+
name: Build sdist and wheel
|
|
42
|
+
runs-on: ubuntu-latest
|
|
43
|
+
steps:
|
|
44
|
+
- uses: actions/checkout@v7
|
|
45
|
+
|
|
46
|
+
- uses: actions/setup-python@v7
|
|
47
|
+
with:
|
|
48
|
+
python-version: "3.12"
|
|
49
|
+
|
|
50
|
+
- name: Check the tag against src/aslmp/_version.py
|
|
51
|
+
run: |
|
|
52
|
+
set -euo pipefail
|
|
53
|
+
version=$(python -c "import runpy; print(runpy.run_path('src/aslmp/_version.py')['__version__'])")
|
|
54
|
+
tag="${GITHUB_REF_NAME#v}"
|
|
55
|
+
echo "tag=$tag _version.py=$version"
|
|
56
|
+
if [ "$tag" != "$version" ]; then
|
|
57
|
+
echo "Release tag $GITHUB_REF_NAME does not match __version__ $version."
|
|
58
|
+
echo "The version literal exists once, in src/aslmp/_version.py. Fix one of them."
|
|
59
|
+
exit 1
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
- name: Build
|
|
63
|
+
run: |
|
|
64
|
+
python -m pip install --upgrade pip build twine
|
|
65
|
+
python -m build
|
|
66
|
+
python -m twine check --strict dist/*
|
|
67
|
+
|
|
68
|
+
- uses: actions/upload-artifact@v7
|
|
69
|
+
with:
|
|
70
|
+
name: dist
|
|
71
|
+
path: dist/
|
|
72
|
+
if-no-files-found: error
|
|
73
|
+
|
|
74
|
+
publish:
|
|
75
|
+
name: Publish to PyPI
|
|
76
|
+
needs: build
|
|
77
|
+
runs-on: ubuntu-latest
|
|
78
|
+
# The environment is half of the trusted-publisher match configured on PyPI, and it
|
|
79
|
+
# is where a required reviewer goes if you want one.
|
|
80
|
+
environment:
|
|
81
|
+
name: pypi
|
|
82
|
+
url: https://pypi.org/p/aslmp
|
|
83
|
+
permissions:
|
|
84
|
+
# The OIDC token. This is the only privilege this job has, and the only job that
|
|
85
|
+
# has it.
|
|
86
|
+
id-token: write
|
|
87
|
+
steps:
|
|
88
|
+
- uses: actions/download-artifact@v8
|
|
89
|
+
with:
|
|
90
|
+
name: dist
|
|
91
|
+
path: dist/
|
|
92
|
+
|
|
93
|
+
# `release/v1` is PyPA's own major-version ref for this action.
|
|
94
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|