36
36
import subprocess
37
37
import tempfile
38
38
39
+ # pyproject.toml `py_modules` values that are incorrect. These should all have PRs filed!
40
+ # and should be removed when the fixed version is incorporated in its respective bundle.
41
+
42
+ pyproject_py_modules_blacklist = set ((
43
+ # adafruit bundle
44
+ "adafruit_colorsys" ,
45
+
46
+ # community bundle
47
+ "at24mac_eeprom" ,
48
+ "circuitpython_Candlesticks" ,
49
+ "CircuitPython_Color_Picker" ,
50
+ "CircuitPython_Equalizer" ,
51
+ "CircuitPython_Scales" ,
52
+ "circuitPython_Slider" ,
53
+ "circuitpython_uboxplot" ,
54
+ "P1AM" ,
55
+ "p1am_200_helpers" ,
56
+ ))
57
+
58
+ if sys .version_info >= (3 , 11 ):
59
+ from tomllib import loads as load_toml
60
+ else :
61
+ from tomli import loads as load_toml
62
+
63
+ def load_pyproject_toml (lib_path : pathlib .Path ):
64
+ try :
65
+ return load_toml ((lib_path / "pyproject.toml" ) .read_text (encoding = "utf-8" ))
66
+ except FileNotFoundError :
67
+ print (f"No pyproject.toml in { lib_path } " )
68
+ return {}
69
+
70
+ def get_nested (doc , * args , default = None ):
71
+ for a in args :
72
+ if doc is None : return default
73
+ try :
74
+ doc = doc [a ]
75
+ except (KeyError , IndexError ) as e :
76
+ return default
77
+ return doc
78
+
39
79
IGNORE_PY = ["setup.py" , "conf.py" , "__init__.py" ]
40
- GLOB_PATTERNS = ["*.py" , "font5x8 .bin" ]
80
+ GLOB_PATTERNS = ["*.py" , "* .bin" ]
41
81
S3_MPY_PREFIX = "https://adafruit-circuit-python.s3.amazonaws.com/bin/mpy-cross"
42
82
43
83
def version_string (path = None , * , valid_semver = False ):
@@ -131,17 +171,13 @@ def mpy_cross(mpy_cross_filename, circuitpython_tag, quiet=False):
131
171
shutil .copy ("build_deps/circuitpython/mpy-cross/mpy-cross" , mpy_cross_filename )
132
172
133
173
def _munge_to_temp (original_path , temp_file , library_version ):
134
- with open (original_path , "rb " ) as original_file :
174
+ with open (original_path , "r" , encoding = "utf-8 " ) as original_file :
135
175
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 " )
176
+ line = line .strip ("\n " )
177
+ if line .startswith ("__version__" ):
178
+ line = line .replace ("0.0.0-auto.0" , library_version )
179
+ line = line .replace ("0.0.0+auto.0" , library_version )
180
+ print (line , file = temp_file )
145
181
temp_file .flush ()
146
182
147
183
def get_package_info (library_path , package_folder_prefix ):
@@ -154,61 +190,52 @@ def get_package_info(library_path, package_folder_prefix):
154
190
for pattern in GLOB_PATTERNS :
155
191
glob_search .extend (list (lib_path .rglob (pattern )))
156
192
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 )))
193
+ pyproject_toml = load_pyproject_toml (lib_path )
194
+ py_modules = get_nested (pyproject_toml , "tool" , "setuptools" , "py-modules" , default = [])
195
+ packages = get_nested (pyproject_toml , "tool" , "setuptools" , "packages" , default = [])
196
+
197
+ blacklisted = [name for name in py_modules if name in pyproject_py_modules_blacklist ]
198
+
199
+ if blacklisted :
200
+ print (f"{ lib_path } /settings.toml:1: { blacklisted [0 ]} blacklisted: not using metadata from pyproject.toml" )
201
+ py_modules = packages = ()
202
+
203
+ example_files = [sub_path for sub_path in (lib_path / "examples" ).rglob ("*" )
204
+ if sub_path .is_file ()]
205
+
206
+ if packages and py_modules :
207
+ raise ValueError ("Cannot specify both tool.setuptools.py-modules and .packages" )
208
+
209
+ elif packages :
210
+ if len (packages ) > 1 :
211
+ raise ValueError ("Only a single package is supported" )
212
+ package_name = packages [0 ]
213
+ #print(f"Using package name from pyproject.toml: {package_name}")
214
+ package_info ["is_package" ] = True
215
+ package_info ["module_name" ] = package_name
216
+ package_files = [sub_path for sub_path in (lib_path / package_name ).rglob ("*" )
217
+ if sub_path .is_file ()]
218
+
219
+ elif py_modules :
220
+ if len (py_modules ) > 1 :
221
+ raise ValueError ("Only a single module is supported" )
222
+ py_module = py_modules [0 ]
223
+ #print(f"Using module name from pyproject.toml: {py_module}")
224
+ package_name = py_module
225
+ package_info ["is_package" ] = False
226
+ package_info ["module_name" ] = py_module
227
+ py_files = [lib_path / f"{ py_module } .py" ]
199
228
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
229
+ else :
230
+ print (f"{ lib_path } : Using legacy autodetection" )
231
+ package_info ["is_package" ] = False
232
+ for file in glob_search :
233
+ if file .parts [parent_idx ] != "examples" :
206
234
if len (file .parts ) > parent_idx + 1 :
207
235
for prefix in package_folder_prefix :
208
236
if file .parts [parent_idx ].startswith (prefix ):
209
- is_package = True
210
-
211
- if is_package :
237
+ package_info ["is_package" ] = True
238
+ if package_info ["is_package" ]:
212
239
package_files .append (file )
213
240
else :
214
241
if file .name in IGNORE_PY :
@@ -217,91 +244,78 @@ def library(library_path, output_directory, package_folder_prefix,
217
244
if file .parent == lib_path :
218
245
py_files .append (file )
219
246
247
+ if package_files :
248
+ package_info ["module_name" ] = package_files [0 ].relative_to (library_path ).parent .name
249
+ elif py_files :
250
+ package_info ["module_name" ] = py_files [0 ].relative_to (library_path ).name [:- 3 ]
251
+ else :
252
+ package_info ["module_name" ] = None
253
+
220
254
if len (py_files ) > 1 :
221
255
raise ValueError ("Multiple top level py files not allowed. Please put "
222
256
"them in a package or combine them into a single file." )
223
257
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
258
+ package_info ["package_files" ] = package_files
259
+ package_info ["py_files" ] = py_files
260
+ package_info ["example_files" ] = example_files
261
+
262
+ try :
263
+ package_info ["version" ] = version_string (library_path , valid_semver = True )
264
+ except ValueError as e :
265
+ print (library_path + " has version that doesn't follow SemVer (semver.org)" )
266
+ print (e )
267
+ package_info ["version" ] = version_string (library_path )
268
+
269
+ return package_info
270
+
271
+ def library (library_path , output_directory , package_folder_prefix ,
272
+ mpy_cross = None , example_bundle = False ):
273
+ lib_path = pathlib .Path (library_path )
274
+ package_info = get_package_info (library_path , package_folder_prefix )
275
+ py_package_files = package_info ["package_files" ] + package_info ["py_files" ]
276
+ example_files = package_info ["example_files" ]
277
+ module_name = package_info ["module_name" ]
230
278
231
279
for fn in example_files :
232
280
base_dir = os .path .join (output_directory .replace ("/lib" , "/" ),
233
281
fn .relative_to (library_path ).parent )
234
282
if not os .path .isdir (base_dir ):
235
283
os .makedirs (base_dir )
236
- total_size += 512
237
284
238
- for fn in package_files :
285
+ for fn in py_package_files :
239
286
base_dir = os .path .join (output_directory ,
240
287
fn .relative_to (library_path ).parent )
241
288
if not os .path .isdir (base_dir ):
242
289
os .makedirs (base_dir )
243
- total_size += 512
244
290
245
- new_extension = ".py"
246
- if mpy_cross :
247
- new_extension = ".mpy"
291
+ library_version = package_info ['version' ]
248
292
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
-
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 )
278
-
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 )
293
+ if not example_bundle :
294
+ for filename in py_package_files :
295
+ full_path = os .path .join (library_path , filename )
296
+ output_file = output_directory / filename .relative_to (library_path )
297
+ if filename .suffix == ".py" :
298
+ with tempfile .NamedTemporaryFile (delete = False , mode = "w+" ) as temp_file :
299
+ temp_file_name = temp_file .name
300
+ try :
301
+ _munge_to_temp (full_path , temp_file , library_version )
302
+ temp_file .close ()
303
+ if mpy_cross and os .stat (temp_file .name ).st_size != 0 :
304
+ output_file = output_file .with_suffix (".mpy" )
305
+ mpy_success = subprocess .call ([
306
+ mpy_cross ,
307
+ "-o" , output_file ,
308
+ "-s" , str (filename .relative_to (library_path )),
309
+ temp_file .name
310
+ ])
311
+ if mpy_success != 0 :
312
+ raise RuntimeError ("mpy-cross failed on" , full_path )
313
+ else :
314
+ shutil .copyfile (full_path , output_file )
315
+ finally :
316
+ os .remove (temp_file_name )
317
+ else :
318
+ shutil .copyfile (full_path , output_file )
305
319
306
320
requirements_files = lib_path .glob ("requirements.txt*" )
307
321
requirements_files = [f for f in requirements_files if f .stat ().st_size > 0 ]
@@ -314,11 +328,9 @@ def library(library_path, output_directory, package_folder_prefix,
314
328
requirements_dir = pathlib .Path (output_directory ).parent / "requirements"
315
329
if not os .path .isdir (requirements_dir ):
316
330
os .makedirs (requirements_dir , exist_ok = True )
317
- total_size += 512
318
331
requirements_subdir = f"{ requirements_dir } /{ module_name } "
319
332
if not os .path .isdir (requirements_subdir ):
320
333
os .makedirs (requirements_subdir , exist_ok = True )
321
- total_size += 512
322
334
for filename in requirements_files :
323
335
full_path = os .path .join (library_path , filename )
324
336
output_file = os .path .join (requirements_subdir , filename .name )
@@ -328,9 +340,4 @@ def library(library_path, output_directory, package_folder_prefix,
328
340
full_path = os .path .join (library_path , filename )
329
341
output_file = os .path .join (output_directory .replace ("/lib" , "/" ),
330
342
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 )
343
+ shutil .copyfile (full_path , output_file )
0 commit comments