8000 mpy-cross/mpy_cross: Multiple backend support for mpy-cross. · micropython/micropython@db01cdf · GitHub
[go: up one dir, main page]

Skip to content

Commit db01cdf

Browse files
committed
mpy-cross/mpy_cross: Multiple backend support for mpy-cross.
This adds support for using either a native or WASM binary in the Python wrapper for mpy-cross. This work was funded through GitHub Sponsors. Signed-off-by: Jim Mussared <jim.mussared@gmail.com>
1 parent a4a6047 commit db01cdf

File tree

5 files changed

+390
-126
lines changed

5 files changed

+390
-126
lines changed

mpy-cross/mpy_cross/__init__.py

Lines changed: 9 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
#!/usr/bin/env python3
2-
#
31
# This file is part of the MicroPython project, http://micropython.org/
42
#
53
# The MIT License (MIT)
@@ -25,127 +23,18 @@
2523
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2624
# THE SOFTWARE.
2725

28-
from __future__ import print_function
29-
import os
30-
import re
31-
import stat
32-
import subprocess
33-
34-
NATIVE_ARCHS = {
35-
"NATIVE_ARCH_NONE": "",
36-
"NATIVE_ARCH_X86": "x86",
37-
"NATIVE_ARCH_X64": "x64",
38-
"NATIVE_ARCH_ARMV6": "armv6",
39-
"NATIVE_ARCH_ARMV6M": "armv6m",
40-
"NATIVE_ARCH_ARMV7M": "armv7m",
41-
"NATIVE_ARCH_ARMV7EM": "armv7em",
42-
"NATIVE_ARCH_ARMV7EMSP": "armv7emsp",
43-
"NATIVE_ARCH_ARMV7EMDP": "armv7emdp",
44-
"NATIVE_ARCH_XTENSA": "xtensa",
45-
"NATIVE_ARCH_XTENSAWIN": "xtensawin",
46-
}
47-
48-
globals().update(NATIVE_ARCHS)
49-
50-
__all__ = ["version", "compile", "run", "CrossCompileError"] + list(NATIVE_ARCHS.keys())
51-
52-
53-
class CrossCompileError(Exception):
54-
pass
55-
56-
57-
_VERSION_RE = re.compile("mpy-cross emitting mpy v([0-9]+)(?:.([0-9]+))?")
58-
59-
60-
def _find_mpy_cross_binary(mpy_cross):
61-
if mpy_cross:
62-
return mpy_cross
63-
return os.path.abspath(os.path.join(os.path.dirname(__file__), "../build/mpy-cross"))
64-
65-
66-
def mpy_version(mpy_cross=None):
67-
"""
68-
Get the version and sub-version of the .mpy file format generated by this version of mpy-cross.
69-
70-
Returns: A tuple of `(mpy_version, mpy_sub_version)`
71-
Optional keyword arguments:
72-
- mpy_cross: Specific mpy-cross binary to use
73-
"""
74-
version_info = run(["--version"], mpy_cross=mpy_cross)
75-
match = re.search(_VERSION_RE, version_info)
76-
mpy_version, mpy_sub_version = int(match.group(1)), int(match.group(2) or "0")
77-
return (
78-
mpy_version,
79-
mpy_sub_version,
80-
)
81-
82-
83-
def compile(src, dest=None, src_path=None, opt=None, march=None, mpy_cross=None, extra_args=None):
84-
"""
85-
Compile the specified .py file with mpy-cross.
86-
87-
Returns: Standard output from mpy-cross as a string.
88-
89-
Required arguments:
90-
- src: The path to the .py file
91-
92-
Optional keyword arguments:
93-
- dest: The output .mpy file. Defaults to `src` (with .mpy extension)
94-
- src_path: The path to embed in the .mpy file (defaults to `src`)
95-
- opt: Optimisation level (0-3, default 0)
96-
- march: One of the `NATIVE_ARCH_*` constants (defaults to NATIVE_ARCH_NONE)
97-
- mpy_cross: Specific mpy-cross binary to use
98-
- extra_args: Additional arguments to pass to mpy-cross (e.g. `["-X", "emit=native"]`)
99-
"""
100-
if not src:
101-
raise ValueError("src is required")
102-
if not os.path.exists(src):
103-
raise CrossCompileError("Input .py file not found: {}.".format(src_py))
104-
105-
args = []
106-
107-
if src_path:
108-
args += ["-s", src_path]
109-
110-
if dest:
111-
args += ["-o", dest]
112-
113-
if march:
114-
args += ["-march=" + march]
115-
116-
if opt is not None:
117-
args += ["-O{}".format(opt)]
118-
119-
if extra_args:
120-
args += extra_args
121-
122-
args += [src]
123-
124-
run(args, mpy_cross)
125-
12626

127-
def run(args, mpy_cross=None):
128-
"""
129-
Run mpy-cross with the specified command line arguments.
130-
Prefer to use `compile()` instead.
27+
from .compiler import *
13128

132-
Returns: Standard output from mpy-cross as a string.
13329

134-
Optional keyword arguments:
135-
- mpy_cross: Specific mpy-cross binary to use
136-
"""
137-
mpy_cross = _find_mpy_cross_binary(mpy_cross)
30+
def mpy_version():
31+
return default_compiler().mpy_version()
13832

139-
if not os.path.exists(mpy_cross):
140-
raise CrossCompileError("mpy-cross binary not found at {}.".format(mpy_cross))
33+
def version():
34+
return default_compiler().version()
14135

142-
try:
143-
st = os.stat(mpy_cross)
144-
os.chmod(mpy_cross, st.st_mode | stat.S_IEXEC)
145-
except OSError:
146-
pass
36+
def compile(*args, **kwargs):
37+
return default_compiler().compile(*args, **kwargs)
14738

148-
try:
149-
return subprocess.check_output([mpy_cross] + args, stderr=subprocess.STDOUT).decode()
150-
except subprocess.CalledProcessError as er:
151-
raise CrossCompileError(er.output.decode())
39+
def description():
40+
return default_compiler().description()

mpy-cross/mpy_cross/__main__.py

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
#!/usr/bin/env python3
2-
#
31
# This file is part of the MicroPython project, http://micropython.org/
42
#
53
# The MIT License (MIT)
64
#
75
# Copyright (c) 2022 Andrew Leech
8-
# Copyright (c) 2022 Jim Mussared
6+
# Copyright (c) 2023 Jim Mussared
97
#
108
# Permission is hereby granted, free of charge, to any person obtaining a copy
119
# of this software and associated documentation files (the "Software"), to deal
@@ -25,14 +23,49 @@
2523
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2624
# THE SOFTWARE.
2725

28-
from __future__ import print_function
2926
import argparse
3027
import sys
3128

32-
from . import run, CrossCompileError
29+
from .compiler import default_compiler, CrossCompileError, EMIT_BYTECODE, EMIT_NATIVE, EMIT_VIPER, NATIVE_ARCHS
30+
31+
march_opts = [v[0] for v in NATIVE_ARCHS.values() if v[0]]
32+
emit_opts = [EMIT_BYTECODE, EMIT_NATIVE, EMIT_VIPER]
33+
34+
cmd_parser = argparse.ArgumentParser(description="MicroPython cross-compiler")
35+
cmd_parser.add_argument("-v", "--version", default=False, action="store_true", help="Display version information")
36+
cmd_parser.add_argument("-o", "--output", type=str, help="output file for compiled bytecode (defaults to input with .mpy extension)")
37+
cmd_parser.add_argument("-s", "--source", type=str, help="source filename to embed in the compiled bytecode (defaults to input file)")
38+
cmd_parser.add_argument("-march", "--march", type=str, choices=march_opts, metavar="MARCH", help="set architecture for native emitter; " + ", ".join(march_opts))
39+
cmd_parser.add_argument("-O", "--opt", type=int, choices=range(0, 4), metavar="N", help="apply bytecode optimizations of level N")
40+
cmd_parser.add_argument("-X", metavar="emit=EMIT", choices=["emit=" + x for x in emit_opts], help="set the default code emitter; " + ", ".join(emit_opts))
41+
cmd_parser.add_argument("file", nargs="?", help="input filename")
42+
args = cmd_parser.parse_args()
43+
44+
try:
45+
c = default_compiler()
46+
except RuntimeError as e:
47+
print("Error: " + str(e), file=sys.stderr)
48+
sys.exit(1)
49+
50+
if args.version:
51+
print("{} ({})".format(c.version(), c.description()))
52+
v, s = c.mpy_version()
53+
print("mpy-cross emitting mpy v{}.{}".format(v, s))
54+
sys.exit(0)
55+
56+
if not args.file:
57+
print("Error: Input filename is required", file=sys.stderr)
58+
sys.exit(1)
3359

3460
try:
35-
print(run(sys.argv[1:]))
61+
kwargs = {}
62+
if args.march:
63+
kwargs.update(march=[v for v in NATIVE_ARCHS.values() if v[0] == args.march][0])
64+
if args.X:
65+
kwargs.update(emit=args.X.split("=")[1])
66+
if args.opt:
67+
kwargs.update(opt=args.opt)
68+
c.compile(args.file, src_path=args.source, dest=args.output, **kwargs)
3669
except CrossCompileError as er:
3770
print(er.args[0], file=sys.stderr)
3871
raise SystemExit(1)

mpy-cross/mpy_cross/compiler.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# This file is part of the MicroPython project, http://micropython.org/
2+
#
3+
# The MIT License (MIT)
4+
#
5+
# Copyright (c) 2022 Andrew Leech
6+
# Copyright (c) 2023 Jim Mussared
7+
#
8+
# Permission is hereby granted, free of charge, to any person obtaining a copy
9+
# of this software and associated documentation files (the "Software"), to deal
10+
# in the Software without restriction, including without limitation the rights
11+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
# copies of the Software, and to permit persons to whom the Software is
13+
# furnished to do so, subject to the following conditions:
14+
#
15+
# The above copyright notice and this permission notice shall be included in
16+
# all copies or substantial portions of the Software.
17+
#
18+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+
# THE SOFTWARE.
25+
26+
27+
class CrossCompileError(Exception):
28+
pass
29+
30+
EMIT_BYTECODE = "bytecode"
31+
EMIT_NATIVE = "native"
32+
EMIT_VIPER = "viper"
33+
34+
NATIVE_ARCHS = {
35+
"NATIVE_ARCH_NONE": ("", 0,),
36+
"NATIVE_ARCH_X86": ("x86", 1),
37+
"NATIVE_ARCH_X64": ("x64", 2),
38+
"NATIVE_ARCH_ARMV6": ("armv6", 3),
39+
"NATIVE_ARCH_ARMV6M": ("armv6m", 4),
40+
"NATIVE_ARCH_ARMV7M": ("armv7m", 5),
41+
"NATIVE_ARCH_ARMV7EM": ("armv7em", 6),
42+
"NATIVE_ARCH_ARMV7EMSP": ("armv7emsp", 7),
43+
"NATIVE_ARCH_ARMV7EMDP": ("armv7emdp", 8),
44+
"NATIVE_ARCH_XTENSA": ("xtensa", 9),
45+
"NATIVE_ARCH_XTENSAWIN": ("xtensawin", 10),
46+
}
47+
48+
globals().update(NATIVE_ARCHS)
49+
50+
51+
class Compiler:
52+
def mpy_version(self):
53+
"""
54+
Get the version and sub-version of the .mpy file format generated by this compiler.
55+
56+
Returns: A tuple of `(mpy_version, mpy_sub_version)`
57+
"""
58+
raise NotImplementedError()
59+
60+
def version(self):
61+
"""
62+
Get the version string from this compiler.
63+
64+
Returns: A string, typically "MicroPython <git tag> on <build date>".
65+
"""
66+
raise NotImplementedError()
67+
68+
def compile(self, src, dest=None, src_path=None, opt=0, march=NATIVE_ARCH_NONE, emit=EMIT_BYTECODE):
69+
"""
70+
Compile the specified .py file with mpy-cross.
71+
72+
Returns: Standard output from mpy-cross as a string.
73+
74+
Required arguments:
75+
- src: The path to the .py file
76+
77+
Optional keyword arguments:
78+
- dest: The output .mpy file. Defaults to `src` (with .mpy extension)
79+
- src_path: The path to embed in the .mpy file (defaults to `src`)
80+
- opt: Optimisation level (0-3, default 0)
81+
- march: One of the `NATIVE_ARCH_*` constants (defaults to
82+
NATIVE_ARCH_NONE). Architecture to use when generating code
83+
for @native/@viper functions.
84+
- emit: One of the `EMIT_*` constants (defaults to
85+
EMIT_BYTECODE). This sets the default emitter to use for
86+
functions that do not have a decorator.
87+
"""
88+
raise NotImplementedError()
89+
90+
def description(self):
91+
"""
92+
Returns: A string containing a description of this compiler.
93+
"""
94+
raise NotImplementedError()
95+
96+
97+
def default_compiler():
98+
try:
99+
from .compiler_native import CompilerNative
100+
return CompilerNative()
101+
except Exception:
102+
pass
103+
104+
try:
105+
from .compiler_pywasm3 import CompilerPywasm3
106+
return CompilerPywasm3()
107+
except Exception:
108+
pass
109+
110+
raise RuntimeError("No compiler installed/available.")
111+
112+
113+
__all__ = ["CrossCompileError", "Compiler", "default_compiler", "EMIT_BYTECODE", "EMIT_NATIVE", "EMIT_VIPER",] + list(NATIVE_ARCHS.keys())

0 commit comments

Comments
 (0)
0