26
26
# THE SOFTWARE.
27
27
28
28
from __future__ import print_function
29
+ import contextlib
29
30
import os
30
31
import sys
31
32
import glob
33
+ import tempfile
34
+ from collections import namedtuple
32
35
33
36
__all__ = ["ManifestFileError" , "ManifestFile" ]
34
37
@@ -63,6 +66,37 @@ class ManifestFileError(Exception):
63
66
pass
64
67
65
68
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
+
66
100
# Turns a dict of options into a object with attributes used to turn the
67
101
# kwargs passed to include() and require into the "options" global in the
68
102
# included manifest.
@@ -87,11 +121,12 @@ def __init__(self, mode, path_vars=None):
87
121
self ._mode = mode
88
122
# Path substition variables.
89
123
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.
92
125
self ._manifest_files = []
93
126
# Don't allow including the same file twice.
94
127
self ._visited = set ()
128
+ # Stack of metadata for each level.
129
+ self ._metadata = [ManifestMetadata ()]
95
130
96
131
def _resolve_path (self , path ):
97
132
# Convert path to an absolute path, applying variable substitutions.
@@ -121,15 +156,15 @@ def files(self):
121
156
def execute (self , manifest_file ):
122
157
if manifest_file .endswith (".py" ):
123
158
# Execute file from filesystem.
124
- self .include (manifest_file )
159
+ self .include (manifest_file , top_level = True )
125
160
else :
126
161
# Execute manifest code snippet.
127
162
try :
128
163
exec (manifest_file , self ._manifest_globals ({}))
129
164
except Exception as er :
130
165
raise ManifestFileError ("Error in manifest: {}" .format (er ))
131
166
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 ):
133
168
# Check file exists and get timestamp.
134
169
try :
135
170
stat = os .stat (full_path )
@@ -156,7 +191,9 @@ def _add_file(self, full_path, target_path, kind=KIND_AUTO, version=None, opt=No
156
191
kind = KIND_COMPILE_AS_MPY
157
192
158
193
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
+ )
160
197
)
161
198
162
199
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
167
204
for file in files :
168
205
if package_path :
169
206
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 )
173
208
else :
174
209
if base_path :
175
210
prev_cwd = os .getcwd ()
@@ -185,7 +220,6 @@ def _search(self, base_path, package_path, files, exts, kind, opt=None, strict=F
185
220
os .path .join (base_path , file ),
186
221
file ,
187
222
kind = kind ,
188
- version = None ,
189
223
opt = opt ,
190
224
)
191
225
elif strict :
@@ -194,11 +228,19 @@ def _search(self, base_path, package_path, files, exts, kind, opt=None, strict=F
194
228
if base_path :
195
229
os .chdir (prev_cwd )
196
230
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 ]
200
242
201
- def include (self , manifest_path , ** kwargs ):
243
+ def include (self , manifest_path , top_level = False , ** kwargs ):
202
244
"""
203
245
Include another manifest.
204
246
@@ -235,6 +277,8 @@ def include(self, manifest_path, **kwargs):
235
277
if manifest_path in self ._visited :
236
278
return
237
279
self ._visited .add (manifest_path )
280
+ if not top_level :
281
+ self ._metadata .append (ManifestMetadata ())
238
282
with open (manifest_path ) as f :
239
283
# Make paths relative to this manifest file while processing it.
240
284
# Applies to includes and input files.
@@ -247,6 +291,8 @@ def include(self, manifest_path, **kwargs):
247
291
"Error in manifest file: {}: {}" .format (manifest_path , er )
248
292
)
249
293
os .chdir (prev_cwd )
294
+ if not top_level :
295
+ self ._metadata .pop ()
250
296
251
297
def require (self , name , version = None , unix_ffi = False , ** kwargs ):
252
298
"""
@@ -308,7 +354,7 @@ def module(self, module_path, base_path=".", opt=None):
308
354
if ext .lower () != ".py" :
309
355
raise ManifestFileError ("module must be .py file" )
310
356
# 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 )
312
358
313
359
def _freeze_internal (self , path , script , exts , kind , opt ):
314
360
if script is None :
@@ -372,6 +418,24 @@ def freeze_mpy(self, path, script=None, opt=None):
372
418
self ._freeze_internal (path , script , exts = (".mpy" ), kind = KIND_FREEZE_MPY , opt = opt )
373
419
374
420
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
+
375
439
def main ():
376
440
import argparse
377
441
0 commit comments