3636import subprocess
3737import tempfile
3838
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+
3960IGNORE_PY = ["setup.py" , "conf.py" , "__init__.py" ]
40- GLOB_PATTERNS = ["*.py" , "font5x8 .bin" ]
61+ GLOB_PATTERNS = ["*.py" , "* .bin" ]
4162S3_MPY_PREFIX = "https://adafruit-circuit-python.s3.amazonaws.com/bin/mpy-cross"
4263
4364def version_string (path = None , * , valid_semver = False ):
@@ -131,17 +152,13 @@ def mpy_cross(mpy_cross_filename, circuitpython_tag, quiet=False):
131152 shutil .copy ("build_deps/circuitpython/mpy-cross/mpy-cross" , mpy_cross_filename )
132153
133154def _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 :
135156 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 )
145162 temp_file .flush ()
146163
147164def get_package_info (library_path , package_folder_prefix ):
@@ -154,61 +171,46 @@ def get_package_info(library_path, package_folder_prefix):
154171 for pattern in GLOB_PATTERNS :
155172 glob_search .extend (list (lib_path .rglob (pattern )))
156173
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" :
206209 if len (file .parts ) > parent_idx + 1 :
207210 for prefix in package_folder_prefix :
208211 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" ]:
212214 package_files .append (file )
213215 else :
214216 if file .name in IGNORE_PY :
@@ -217,91 +219,78 @@ def library(library_path, output_directory, package_folder_prefix,
217219 if file .parent == lib_path :
218220 py_files .append (file )
219221
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+
220229 if len (py_files ) > 1 :
221230 raise ValueError ("Multiple top level py files not allowed. Please put "
222231 "them in a package or combine them into a single file." )
223232
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" ]
230253
231254 for fn in example_files :
232255 base_dir = os .path .join (output_directory .replace ("/lib" , "/" ),
233256 fn .relative_to (library_path ).parent )
234257 if not os .path .isdir (base_dir ):
235258 os .makedirs (base_dir )
236- total_size += 512
237259
238- for fn in package_files :
260+ for fn in py_package_files :
239261 base_dir = os .path .join (output_directory ,
240262 fn .relative_to (library_path ).parent )
241263 if not os .path .isdir (base_dir ):
242264 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 )
255265
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' ]
278267
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 )
305294
306295 requirements_files = lib_path .glob ("requirements.txt*" )
307296 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,
314303 requirements_dir = pathlib .Path (output_directory ).parent / "requirements"
315304 if not os .path .isdir (requirements_dir ):
316305 os .makedirs (requirements_dir , exist_ok = True )
317- total_size += 512
318306 requirements_subdir = f"{ requirements_dir } /{ module_name } "
319307 if not os .path .isdir (requirements_subdir ):
320308 os .makedirs (requirements_subdir , exist_ok = True )
321- total_size += 512
322309 for filename in requirements_files :
323310 full_path = os .path .join (library_path , filename )
324311 output_file = os .path .join (requirements_subdir , filename .name )
@@ -328,9 +315,4 @@ def library(library_path, output_directory, package_folder_prefix,
328315 full_path = os .path .join (library_path , filename )
329316 output_file = os .path .join (output_directory .replace ("/lib" , "/" ),
330317 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