8000 tools/manifestfile.py: Allow manifests to set metadata. · lowfatcode/micropython@5852fd7 · GitHub
[go: up one dir, main page]

Skip to content

Commit 5852fd7

Browse files
committed
tools/manifestfile.py: Allow manifests to set metadata.
The metadata can be version, description, and license. After executing a manifest, the top-level metadata can be queried, and also each file output from the manifest will have the metadata of the containing manifest. Use the version metadata to "tag" files before freezing such that they have __version__ available.
1 parent bc23f20 commit 5852fd7

File tree

2 files changed

+102
-36
lines changed

2 files changed

+102
-36
lines changed

tools/makemanifest.py

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -176,34 +176,36 @@ def main():
176176
str_paths = []
177177
mpy_files = []
178178
ts_newest = 0
179-
for _file_type, full_path, target_path, timestamp, kind, version, opt in manifest.files():
180-
if kind == manifestfile.KIND_FREEZE_AS_STR:
179+
for result in manifest.files():
180+
if result.kind == manifestfile.KIND_FREEZE_AS_STR:
181181
str_paths.append(
182182
(
183-
full_path,
184-
target_path,
183+
result.full_path,
184+
result.target_path,
185185
)
186186
)
187-
ts_outfile = timestamp
188-
elif kind == manifestfile.KIND_FREEZE_AS_MPY:
189-
outfile = "{}/frozen_mpy/{}.mpy".format(args.build_dir, target_path[:-3])
187+
ts_outfile = result.timestamp
188+
elif result.kind == manifestfile.KIND_FREEZE_AS_MPY:
189+
outfile = "{}/frozen_mpy/{}.mpy".format(args.build_dir, result.target_path[:-3])
190190
ts_outfile = get_timestamp(outfile, 0)
191-
if timestamp >= ts_outfile:
192-
print("MPY", target_path)
191+
if result.timestamp >= ts_outfile:
192+
print("MPY", result.target_path)
193193
mkdir(outfile)
194-
try:
195-
mpy_cross.compile(
196-
full_path,
197-
dest=outfile,
198-
src_path=target_path,
199-
opt=opt,
200-
mpy_cross=MPY_CROSS,
201-
extra_args=args.mpy_cross_flags.split(),
202-
)
203-
except mpy_cross.CrossCompileError as ex:
204-
print("error compiling {}:".format(target_path))
205-
print(ex.args[0])
206-
raise SystemExit(1)
194+
# Add __version__ to the end of the file before compiling.
195+
with manifestfile.tagged_py_file(result.full_path, result.metadata) as tagged_path:
196+
try:
197+
mpy_cross.compile(
198+
tagged_path,
199+
dest=outfile,
200+
src_path=result.target_path,
201+
opt=result.opt,
202+
mpy_cross=MPY_CROSS,
203+
extra_args=args.mpy_cross_flags.split(),
204+
)
205+
except mpy_cross.CrossCompileError as ex:
206+
print("error compiling {}:".format(target_path))
207+
print(ex.args[0])
208+
raise SystemExit(1)
207209
ts_outfile = get_timestamp(outfile)
208210
mpy_files.append(outfile)
209211
else:

tools/manifestfile.py

Lines changed: 78 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,12 @@
2626
# THE SOFTWARE.
2727

2828
from __future__ import print_function
29+
import contextlib
2930
import os
3031
import sys
3132
import glob
33+
import tempfile
34+
from collections import namedtuple
3235

3336
__all__ = ["ManifestFileError", "ManifestFile"]
3437

@@ -63,6 +66,37 @@ class ManifestFileError(Exception):
6366
pass
6467

6568

69+
# The set of files that this manifest references.
70+
ManifestOutput = namedtuple(
71+
"ManifestOutput",
72+
[
73+
"file_type", # FILE_TYPE_*.
74+
"full_path", # The input file full path.
75+
"target_path", # The target path on the device.
76+
"timestamp", # Last modified date of the input file.
77+
"kind", # KIND_*.
78+
"metadata", # Metadata for the containing package.
79+
"opt", # Optimisation level (or None).
80+
],
81+
)
82+
83+
84+
# Represent the metadata for a package.
85+
class ManifestMetadata:
86+
def __init__(self):
87+
self.version = None
88+
self.description = None
89+
self.license = None
90+
91+
def update(self, description=None, version=None, license=None):
92+
if description:
93+
self.description = description
94+
if version:
95+
self.version = version
96+
if license:
97+
self.license = version
98+
99+
66100
# Turns a dict of options into a object with attributes used to turn the
67101
# kwargs passed to include() and require into the "options" global in the
68102
# included manifest.
@@ -87,11 +121,12 @@ def __init__(self, mode, path_vars=None):
87121
self._mode = mode
88122
# Path substition variables.
89123
self._path_vars = path_vars or {}
90-
# List of files references by this manifest.
91-
# Tuple of (file_type, full_path, target_path, timestamp, kind, version, opt)
124+
# List of files (as ManifestFileResult) references by this manifest.
92125
self._manifest_files = []
93126
# Don't allow including the same file twice.
94127
self._visited = set()
128+
# Stack of metadata for each level.
129+
self._metadata = [ManifestMetadata()]
95130

96131
def _resolve_path(self, path):
97132
# Convert path to an absolute path, applying variable substitutions.
@@ -121,15 +156,15 @@ def files(self):
121156
def execute(self, manifest_file):
122157
if manifest_file.endswith(".py"):
123158
# Execute file from filesystem.
124-
self.include(manifest_file)
159+
self.include(manifest_file, top_level=True)
125160
else:
126161
# Execute manifest code snippet.
127162
try:
128163
exec(manifest_file, self._manifest_globals({}))
129164
except Exception as er:
130165
raise ManifestFileError("Error in manifest: {}".format(er))
131166

132-
def _add_file(self, full_path, target_path, kind=KIND_AUTO, version=None, opt=None):
167+
def _add_file(self, full_path, target_path, kind=KIND_AUTO, opt=None):
133168
# Check file exists and get timestamp.
134169
try:
135170
stat = os.stat(full_path)
@@ -156,7 +191,9 @@ def _add_file(self, full_path, target_path, kind=KIND_AUTO, version=None, opt=No
156191
kind = KIND_COMPILE_AS_MPY
157192

158193
self._manifest_files.append(
159-
(FILE_TYPE_LOCAL, full_path, target_path, timestamp, kind, version, opt)
194+
ManifestOutput(
195+
FILE_TYPE_LOCAL, full_path, target_path, timestamp, kind, self._metadata[-1], opt
196+
)
160197
)
161198

162199
def _search(self, base_path, package_path, files, exts, kind, opt=None, strict=False):
@@ -167,9 +204,7 @@ def _search(self, base_path, package_path, files, exts, kind, opt=None, strict=F
167204
for file in files:
168205
if package_path:
169206
file = os.path.join(package_path, file)
170-
self._add_file(
171-
os.path.join(base_path, file), file, kind=kind, version=None, opt=opt
172-
)
207+
self._add_file(os.path.join(base_path, file), file, kind=kind, opt=opt)
173208
else:
174209
if base_path:
175210
prev_cwd = os.getcwd()
@@ -185,7 +220,6 @@ def _search(self, base_path, package_path, files, exts, kind, opt=None, strict=F
185220
os.path.join(base_path, file),
186221
file,
187222
kind=kind,
188-
version=None,
189223
opt=opt,
190224
)
191225
elif strict:
@@ -194,11 +228,19 @@ def _search(self, base_path, package_path, files, exts, kind, opt=None, strict=F
194228
if base_path:
195229
os.chdir(prev_cwd)
196230

197-
def metadata(self, description=None, version=None):
198-
# TODO
199-
pass
231+
def metadata(self, description=None, version=None, license=None):
232+
"""
233+
From within a manifest file, use this to set the metadata for the
234+
package described by current manifest.
235+
236+
After executing a manifest file (via execute()), call this
237+
to obtain the metadata for the top-level manifest file.
238+
"""
239+
240+
self._metadata[-1].update(description, version, license)
241+
return self._metadata[-1]
200242

201-
def include(self, manifest_path, **kwargs):
243+
def include(self, manifest_path, top_level=False, **kwargs):
202244
"""
203245
Include another manifest.
204246
@@ -235,6 +277,8 @@ def include(self, manifest_path, **kwargs):
235277
if manifest_path in self._visited:
236278
return
237279
self._visited.add(manifest_path)
280+
if not top_level:
281+
self._metadata.append(ManifestMetadata())
238282
with open(manifest_path) as f:
239283
# Make paths relative to this manifest file while processing it.
240284
# Applies to includes and input files.
@@ -247,6 +291,8 @@ def include(self, manifest_path, **kwargs):
247291
"Error in manifest file: {}: {}".format(manifest_path, er)
248292
)
249293
os.chdir(prev_cwd)
294+
if not top_level:
295+
self._metadata.pop()
250296

251297
def require(self, name, version=None, unix_ffi=False, **kwargs):
252298
"""
@@ -308,7 +354,7 @@ def module(self, module_path, base_path=".", opt=None):
308354
if ext.lower() != ".py":
309355
raise ManifestFileError("module must be .py file")
310356
# TODO: version None
311-
self._add_file(os.path.join(base_path, module_path), module_path, version=None, opt=opt)
357+
self._add_file(os.path.join(base_path, module_path), module_path, opt=opt)
312358

313359
def _freeze_internal(self, path, script, exts, kind, opt):
314360
if script is None:
@@ -372,6 +418,24 @@ def freeze_mpy(self, path, script=None, opt=None):
372418
self._freeze_internal(path, script, exts=(".mpy"), kind=KIND_FREEZE_MPY, opt=opt)
373419

374420

421+
# Generate a temporary file with a line appended to the end that adds __version__.
422+
@contextlib.contextmanager
423+
def tagged_py_file(path, metadata):
424+
dest_fd, dest_path = tempfile.mkstemp(suffix=".py", text=True)
425+
try:
426+
with os.fdopen(dest_fd, "w") as dest:
427+
with open(path, "r") as src:
428+
contents = src.read()
429+
dest.write(contents)
430+
431+
# Don't overwrite a version definition if the file already has one in it.
432+
if metadata.version and "__version__ =" not in contents:
433+
dest.write("\n\n__version__ = {}\n".format(repr(metadata.version)))
434+
yield dest_path
435+
finally:
436+
os.unlink(dest_path)
437+
438+
375439
def main():
376440
import argparse
377441

0 commit comments

Comments
 (0)
0