36
36
import subprocess
37
37
import tempfile
38
38
39
+ if sys .version_info >= (3 , 11 ):
40
+ from tomllib import loads as load_toml
41
+ else :
42
+ from tomli import loads as load_toml
43
+
44
+ def load_settings_toml (lib_path : pathlib .Path ):
45
+ try :
46
+ return load_toml ((lib_path / "pyproject.toml" ) .read_text (encoding = "utf-8" ))
47
+ except FileNotFoundError :
48
+ print (f"No settings.toml in { lib_path } " )
49
+ return {}
50
+
51
+ def get_nested (doc , * args , default = None ):
52
+ for a in args :
53
+ if doc is None : return default
54
+ try :
55
+ doc = doc [a ]
56
+ except (KeyError , IndexError ) as e :
57
+ return default
58
+ return doc
59
+
39
60
IGNORE_PY = ["setup.py" , "conf.py" , "__init__.py" ]
40
- GLOB_PATTERNS = ["*.py" , "font5x8 .bin" ]
61
+ GLOB_PATTERNS = ["*.py" , "* .bin" ]
41
62
S3_MPY_PREFIX = "https://adafruit-circuit-python.s3.amazonaws.com/bin/mpy-cross"
42
63
43
64
def version_string (path = None , * , valid_semver = False ):
@@ -131,17 +152,13 @@ def mpy_cross(mpy_cross_filename, circuitpython_tag, quiet=False):
131
152
shutil .copy ("build_deps/circuitpython/mpy-cross/mpy-cross" , mpy_cross_filename )
132
153
133
154
def _munge_to_temp (original_path , temp_file , library_version ):
134
- with open (original_path , "rb " ) as original_file :
155
+ with open (original_path , "r" , encoding = "utf-8 " ) as original_file :
135
156
for line in original_file :
136
- if original_path .endswith (".bin" ):
137
- # this is solely for adafruit_framebuf/examples/font5x8.bin
138
- temp_file .write (line )
139
- else :
140
- line = line .decode ("utf-8" ).strip ("\n " )
141
- if line .startswith ("__version__" ):
142
- line = line .replace ("0.0.0-auto.0" , library_version )
143
- line = line .replace ("0.0.0+auto.0" , library_version )
144
- temp_file .write (line .encode ("utf-8" ) + b"\r \n " )
157
+ line = line .strip ("\n " )
158
+ if line .startswith ("__version__" ):
159
+ line = line .replace ("0.0.0-auto.0" , library_version )
160
+ line = line .replace ("0.0.0+auto.0" , library_version )
161
+ print (line , file = temp_file )
145
162
temp_file .flush ()
146
163
147
164
def get_package_info (library_path , package_folder_prefix ):
@@ -154,61 +171,46 @@ def get_package_info(library_path, package_folder_prefix):
154
171
for pattern in GLOB_PATTERNS :
155
172
glob_search .extend (list (lib_path .rglob (pattern )))
156
173
157
- package_info ["is_package" ] = False
158
- for file in glob_search :
159
- if file .parts [parent_idx ] != "examples" :
160
- if len (file .parts ) > parent_idx + 1 :
161
- for prefix in package_folder_prefix :
162
- if file .parts [parent_idx ].startswith (prefix ):
163
- package_info ["is_package" ] = True
164
- if package_info ["is_package" ]:
165
- package_files .append (file )
166
- else :
167
- if file .name in IGNORE_PY :
168
- #print("Ignoring:", file.resolve())
169
- continue
170
- if file .parent == lib_path :
171
- py_files .append (file )
172
-
173
- if package_files :
174
- package_info ["module_name" ] = package_files [0 ].relative_to (library_path ).parent .name
175
- elif py_files :
176
- package_info ["module_name" ] = py_files [0 ].relative_to (library_path ).name [:- 3 ]
177
- else :
178
- package_info ["module_name" ] = None
179
-
180
- try :
181
- package_info ["version" ] = version_string (library_path , valid_semver = True )
182
- except ValueError as e :
183
- package_info ["version" ] = version_string (library_path )
184
-
185
- return package_info
186
-
187
- def library (library_path , output_directory , package_folder_prefix ,
188
- mpy_cross = None , example_bundle = False ):
189
- py_files = []
190
- package_files = []
191
- example_files = []
192
- total_size = 512
193
-
194
- lib_path = pathlib .Path (library_path )
195
- parent_idx = len (lib_path .parts )
196
- glob_search = []
197
- for pattern in GLOB_PATTERNS :
198
- glob_search .extend (list (lib_path .rglob (pattern )))
199
-
200
- for file in glob_search :
201
- if file .parts [parent_idx ] == "examples" :
202
- example_files .append (file )
203
- else :
204
- if not example_bundle :
205
- is_package = False
174
+ settings_toml = load_settings_toml (lib_path )
175
+ py_modules = get_nested (settings_toml , "tool" , "setuptools" , "py-modules" , default = [])
176
+ packages = get_nested (settings_toml , "tool" , "setuptools" , "packages" , default = [])
177
+
178
+ example_files = [sub_path for sub_path in (lib_path / "examples" ).rglob ("*" )
179
+ if sub_path .is_file ()]
180
+
181
+ if packages and py_modules :
182
+ raise ValueError ("Cannot specify both tool.setuptools.py-modules and .packages" )
183
+
184
+ elif packages :
185
+ if len (packages ) > 1 :
186
+ raise ValueError ("Only a single package is supported" )
187
+ package_name = packages [0 ]
188
+ print (f"Using package name from settings.toml: { package_name } " )
189
+ package_info ["is_package" ] = True
190
+ package_info ["module_name" ] = package_name
191
+ package_files = [sub_path for sub_path in (lib_path / package_name ).rglob ("*" )
192
+ if sub_path .is_file ()]
193
+
194
+ elif py_modules :
195
+ if len (py_modules ) > 1 :
196
+ raise ValueError ("Only a single module is supported" )
197
+ print ("Using module name from settings.toml" )
198
+ py_module = py_modules [0 ]
199
+ package_name = py_module
200
+ package_info ["is_package" ] = False
201
+ package_info ["module_name" ] = py_module
202
+ py_files = [lib_path / f"{ py_module } .py" ]
203
+
204
+ if not packages and not py_modules :
205
+ print ("Using legacy autodetection" )
206
+ package_info ["is_package" ] = False
207
+ for file in glob_search :
208
+ if file .parts [parent_idx ] != "examples" :
206
209
if len (file .parts ) > parent_idx + 1 :
207
210
for prefix in package_folder_prefix :
208
211
if file .parts [parent_idx ].startswith (prefix ):
209
- is_package = True
210
-
211
- if is_package :
212
+ package_info ["is_package" ] = True
213
+ if package_info ["is_package" ]:
212
214
package_files .append (file )
213
215
else :
214
216
if file .name in IGNORE_PY :
@@ -217,91 +219,78 @@ def library(library_path, output_directory, package_folder_prefix,
217
219
if file .parent == lib_path :
218
220
py_files .append (file )
219
221
222
+ if package_files :
223
+ package_info ["module_name" ] = package_files [0 ].relative_to (library_path ).parent .name
224
+ elif py_files :
225
+ package_info ["module_name" ] = py_files [0 ].relative_to (library_path ).name [:- 3 ]
226
+ else :
227
+ package_info ["module_name" ] = None
228
+
220
229
if len (py_files ) > 1 :
221
230
raise ValueError ("Multiple top level py files not allowed. Please put "
222
231
"them in a package or combine them into a single file." )
223
232
224
- if package_files :
225
- module_name = package_files [0 ].relative_to (library_path ).parent .name
226
- elif py_files :
227
- module_name = py_files [0 ].relative_to (library_path ).name [:- 3 ]
228
- else :
229
- module_name = None
233
+ package_info ["package_files" ] = package_files
234
+ package_info ["py_files" ] = py_files
235
+ package_info ["example_files" ] = example_files
236
+
237
+ try :
238
+ package_info ["version" ] = version_string (library_path , valid_semver = True )
239
+ except ValueError as e :
240
+ print (library_path + " has version that doesn't follow SemVer (semver.org)" )
241
+ print (e )
242
+ package_info ["version" ] = version_string (library_path )
243
+
244
+ return package_info
245
+
246
+ def library (library_path , output_directory , package_folder_prefix ,
247
+ mpy_cross = None , example_bundle = False ):
248
+ lib_path = pathlib .Path (library_path )
249
+ package_info = get_package_info (library_path , package_folder_prefix )
250
+ py_package_files = package_info ["package_files" ] + package_info ["py_files" ]
251
+ example_files = package_info ["example_files" ]
252
+ module_name = package_info ["module_name" ]
230
253
231
254
for fn in example_files :
232
255
base_dir = os .path .join (output_directory .replace ("/lib" , "/" ),
233
256
fn .relative_to (library_path ).parent )
234
257
if not os .path .isdir (base_dir ):
235
258
os .makedirs (base_dir )
236
- total_size += 512
237
259
238
- for fn in package_files :
260
+ for fn in py_package_files :
239
261
base_dir = os .path .join (output_directory ,
240
262
fn .relative_to (library_path ).parent )
241
263
if not os .path .isdir (base_dir ):
242
264
os .makedirs (base_dir )
243
- total_size += 512
244
-
245
- new_extension = ".py"
246
- if mpy_cross :
247
- new_extension = ".mpy"
248
-
249
- try :
250
- library_version = version_string (library_path , valid_semver = True )
251
- except ValueError as e :
252
- print (library_path + " has version that doesn't follow SemVer (semver.org)" )
253
- print (e )
254
- library_version = version_string (library_path )
255
265
256
- for filename in py_files :
257
- full_path = os .path .join (library_path , filename )
258
- output_file = os .path .join (
259
- output_directory ,
260
- filename .relative_to (library_path ).with_suffix (new_extension )
261
- )
262
- with tempfile .NamedTemporaryFile (delete = False ) as temp_file :
263
- _munge_to_temp (full_path , temp_file , library_version )
264
- temp_filename = temp_file .name
265
- # Windows: close the temp file before it can be read or copied by name
266
- if mpy_cross :
267
- mpy_success = subprocess .call ([
268
- mpy_cross ,
269
- "-o" , output_file ,
270
- "-s" , str (filename .relative_to (library_path )),
271
- temp_filename
272
- ])
273
- if mpy_success != 0 :
274
- raise RuntimeError ("mpy-cross failed on" , full_path )
275
- else :
276
- shutil .copyfile (temp_filename , output_file )
277
- os .remove (temp_filename )
266
+ library_version = package_info ['version' ]
278
267
279
- for filename in package_files :
280
- full_path = os . path . join ( library_path , filename )
281
- output_file = ""
282
- with tempfile . NamedTemporaryFile ( delete = False ) as temp_file :
283
- _munge_to_temp ( full_path , temp_file , library_version )
284
- temp_filename = temp_file . name
285
- # Windows: close the temp file before it can be read or copied by name
286
- if not mpy_cross or os . stat ( full_path ). st_size == 0 :
287
- output_file = os . path . join ( output_directory ,
288
- filename . relative_to ( library_path ) )
289
- shutil . copyfile ( temp_filename , output_file )
290
- else :
291
- output_file = os . path . join (
292
- output_directory ,
293
- filename . relative_to ( library_path ). with_suffix ( new_extension )
294
- )
295
-
296
- mpy_success = subprocess . call ([
297
- mpy_cross ,
298
- "-o " , output_file ,
299
- "-s" , str ( filename . relative_to ( library_path )),
300
- temp_filename
301
- ])
302
- if mpy_success != 0 :
303
- raise RuntimeError ( "mpy-cross failed on" , full_path )
304
- os . remove ( temp_filename )
268
+ if not example_bundle :
269
+ for filename in py_package_files :
270
+ full_path = os . path . join ( library_path , filename )
271
+ output_file = output_directory / filename . relative_to ( library_path )
272
+ if filename . suffix == ".py" :
273
+ with tempfile . NamedTemporaryFile ( delete = False , mode = "w+" ) as temp_file :
274
+ temp_file_name = temp_file . name
275
+ try :
276
+ _munge_to_temp ( full_path , temp_file , library_version )
277
+ temp_file . close ( )
278
+ if mpy_cross :
279
+ output_file = output_file . with_suffix ( ".mpy" )
280
+ mpy_success = subprocess . call ([
281
+ mpy_cross ,
282
+ "-o" , output_file ,
283
+ "-s" , str ( filename . relative_to ( library_path )),
284
+ temp_file . name
285
+ ])
286
+ if mpy_success != 0 :
287
+ raise RuntimeError ( "mpy-cross failed on " , full_path )
288
+ else :
289
+ shutil . copyfile ( full_path , output_file )
290
+ finally :
291
+ os . remove ( temp_file_name )
292
+ else :
293
+ shutil . copyfile ( full_path , output_file )
305
294
306
295
requirements_files = lib_path .glob ("requirements.txt*" )
307
296
requirements_files = [f for f in requirements_files if f .stat ().st_size > 0 ]
@@ -314,11 +303,9 @@ def library(library_path, output_directory, package_folder_prefix,
314
303
requirements_dir = pathlib .Path (output_directory ).parent / "requirements"
315
304
if not os .path .isdir (requirements_dir ):
316
305
os .makedirs (requirements_dir , exist_ok = True )
317
- total_size += 512
318
306
requirements_subdir = f"{ requirements_dir } /{ module_name } "
319
307
if not os .path .isdir (requirements_subdir ):
320
308
os .makedirs (requirements_subdir , exist_ok = True )
321
- total_size += 512
322
309
for filename in requirements_files :
323
310
full_path = os .path .join (library_path , filename )
324
311
output_file = os .path .join (requirements_subdir , filename .name )
@@ -328,9 +315,4 @@ def library(library_path, output_directory, package_folder_prefix,
328
315
full_path = os .path .join (library_path , filename )
329
316
output_file = os .path .join (output_directory .replace ("/lib" , "/" ),
330
317
filename .relative_to (library_path ))
331
- temp_filename = ""
332
- with tempfile .NamedTemporaryFile (delete = False ) as temp_file :
333
- _munge_to_temp (full_path , temp_file , library_version )
334
- temp_filename = temp_file .name
335
- shutil .copyfile (temp_filename , output_file )
336
- os .remove (temp_filename )
318
+ shutil .copyfile (full_path , output_file )
0 commit comments