Skip to content

Commit fcddea1

Browse files
committed
mip: Support optional per-entry native code compatibility tags.
Related to micropython#1140 / micropython#19478 / micropython#19479: those add URL-templating for single-file natmod installs, but don't help when a single manifest needs to serve different files per architecture. Adds an optional third element to hashes/urls entries: a raw sys.implementation._mpy-shaped integer. Entries without it install unconditionally (backward compatible); entries with it only install if _mpy_tag_ok() matches - exact match on version/sub-version/arch, subset match on arch-flags (a variant that doesn't require an extension must still install on hardware that supports it). Signed-off-by: o-murphy <thehelixpg@gmail.com>
1 parent 0400c56 commit fcddea1

3 files changed

Lines changed: 49 additions & 2 deletions

File tree

micropython/mip/mip/__init__.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,21 @@
2424

2525
_ALLOWED_MIP_URL_PREFIXES = const(("http://", "https://", "codeberg:", "github:", "gitlab:"))
2626

27+
# Bits 0-15 of sys.implementation._mpy: version/sub-version/arch (exact
28+
# match required). Bits 16+: optional arch-flags (e.g. RV32 extensions) -
29+
# a tag's flags must be a subset of what this device supports, not equal.
30+
_MPY_BASE_MASK = const(0xFFFF)
31+
32+
33+
def _mpy_tag_ok(tag, device_mpy=None):
34+
if device_mpy is None:
35+
device_mpy = getattr(sys.implementation, "_mpy", None)
36+
if device_mpy is None:
37+
return False
38+
if (tag & _MPY_BASE_MASK) != (device_mpy & _MPY_BASE_MASK):
39+
return False
40+
return (tag >> 16) & (device_mpy >> 16) == (tag >> 16)
41+
2742

2843
# This implements os.makedirs(os.dirname(path))
2944
def _ensure_path_exists(path):
@@ -112,7 +127,10 @@ def _install_json(package_json_url, index, target, version, mpy):
112127
package_json = response.json()
113128
finally:
114129
response.close()
115-
for target_path, short_hash in package_json.get("hashes", ()):
130+
for entry in package_json.get("hashes", ()):
131+
target_path, short_hash = entry[0], entry[1]
132+
if len(entry) > 2 and not _mpy_tag_ok(entry[2]):
133+
continue
116134
fs_target_path = target + "/" + target_path
117135
if _check_exists(fs_target_path, short_hash):
118136
print("Exists:", fs_target_path)
@@ -122,7 +140,10 @@ def _install_json(package_json_url, index, target, version, mpy):
122140
print("File not found: {} {}".format(target_path, short_hash))
123141
return False
124142
base_url = package_json_url.rpartition("/")[0]
125-
for target_path, url in package_json.get("urls", ()):
143+
for entry in package_json.get("urls", ()):
144+
target_path, url = entry[0], entry[1]
145+
if len(entry) > 2 and not _mpy_tag_ok(entry[2]):
146+
continue
126147
fs_target_path = target + "/" + target_path
127148
is_full_url = any(url.startswith(p) for p in _ALLOWED_MIP_URL_PREFIXES)
128149
if base_url and not is_full_url:

micropython/mip/test_mip_tag.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from mip import _mpy_tag_ok
2+
3+
# Exact match on version/sub-version/arch (bits 0-15), no arch-flags.
4+
assert _mpy_tag_ok(0x0A06, 0x0A06) is True
5+
6+
# Different version/sub-version/arch -> reject.
7+
assert _mpy_tag_ok(0x0A06, 0x0B06) is False
8+
assert _mpy_tag_ok(0x0A06, 0x0A07) is False
9+
10+
# Arch-flags (bits 16+): tag's required flags must be a subset of the
11+
# device's, not an exact match.
12+
assert _mpy_tag_ok(0x0A06 | (0b001 << 16), 0x0A06 | (0b011 << 16)) is True
13+
assert _mpy_tag_ok(0x0A06 | (0b011 << 16), 0x0A06 | (0b001 << 16)) is False
14+
assert _mpy_tag_ok(0x0A06, 0x0A06 | (0b111 << 16)) is True # tag needs nothing
15+
assert _mpy_tag_ok(0x0A06 | (0b111 << 16), 0x0A06) is False # device supports nothing
16+
17+
# No _mpy support on the device (e.g. bytecode-only build).
18+
assert _mpy_tag_ok(0x0A06, None) is False
19+
20+
# Omitting device_mpy falls back to sys.implementation._mpy; just check it
21+
# runs without raising, since that value depends on the build running the test.
22+
_mpy_tag_ok(0x0A06)
23+
24+
print("PASS")

tools/ci.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,15 @@ function ci_package_tests_setup_lib {
5050
$CP -r python-stdlib/unittest/unittest "${VIRTUAL_ENV}/lib/"
5151
$CP -r python-stdlib/unittest-discover/unittest "${VIRTUAL_ENV}/lib/"
5252
$CP unix-ffi/ffilib/ffilib.py "${VIRTUAL_ENV}/lib/"
53+
$CP -r python-ecosys/requests/requests "${VIRTUAL_ENV}/lib/"
5354
tree "${VIRTUAL_ENV}"
5455
}
5556

5657
function ci_package_tests_run {
5758
export MICROPYPATH
5859
for test in \
5960
micropython/drivers/storage/sdcard/sdtest.py \
61+
micropython/mip/test_mip_tag.py \
6062
micropython/xmltok/test_xmltok.py \
6163
python-ecosys/requests/test_requests.py \
6264
python-stdlib/argparse/test_argparse.py \

0 commit comments

Comments
 (0)