@@ -69,6 +69,9 @@ class Package:
6969 by legacy distutils/setuptools and described in:
7070 https://pip.pypa.io/en/stable/reference/build-system/setup-py/
7171
72+ The file pyproject.toml must exist; this is checked if/when fn_build() is
73+ called.
74+
7275 Here is a `doctest` example of using pipcl to create a SWIG extension
7376 module. Requires `swig`.
7477
@@ -335,63 +338,86 @@ def __init__(self,
335338 wheel_compresslevel = None ,
336339 ):
337340 '''
338- The initial args before `root ` define the package
339- metadata and closely follow the definitions in:
341+ The initial args before `entry_points ` define the
342+ package metadata and closely follow the definitions in:
340343 https://packaging.python.org/specifications/core-metadata/
341344
342345 Args:
343346
344347 name:
348+ Used for metadata `Name`.
345349 A string, the name of the Python package.
346350 version:
351+ Used for metadata `Version`.
347352 A string, the version of the Python package. Also see PEP-440
348353 `Version Identification and Dependency Specification`.
349354 platform:
355+ Used for metadata `Platform`.
350356 A string or list of strings.
351357 supported_platform:
358+ Used for metadata `Supported-Platform`.
352359 A string or list of strings.
353360 summary:
361+ Used for metadata `Summary`.
354362 A string, short description of the package.
355363 description:
364+ Used for metadata `Description`.
356365 A string. If contains newlines, a detailed description of the
357366 package. Otherwise the path of a file containing the detailed
358367 description of the package.
359368 description_content_type:
369+ Used for metadata `Description-Content-Type`.
360370 A string describing markup of `description` arg. For example
361371 `text/markdown; variant=GFM`.
362372 keywords:
373+ Used for metadata `Keywords`.
363374 A string containing comma-separated keywords.
364375 home_page:
376+ Used for metadata `Home-page`.
365377 URL of home page.
366378 download_url:
379+ Used for metadata `Download-URL`.
367380 Where this version can be downloaded from.
368381 author:
382+ Used for metadata `Author`.
369383 Author.
370384 author_email:
385+ Used for metadata `Author-email`.
371386 Author email.
372387 maintainer:
388+ Used for metadata `Maintainer`.
373389 Maintainer.
374390 maintainer_email:
391+ Used for metadata `Maintainer-email`.
375392 Maintainer email.
376393 license:
394+ Used for metadata `License`.
377395 A string containing the license text. Written into metadata
378396 file `COPYING`. Is also written into metadata itself if not
379397 multi-line.
380398 classifier:
399+ Used for metadata `Classifier`.
381400 A string or list of strings. Also see:
382401
383402 * https://pypi.org/pypi?%3Aaction=list_classifiers
384403 * https://pypi.org/classifiers/
385404
386405 requires_dist:
387- A string or list of strings. None items are ignored. Also see PEP-508.
406+ Used for metadata `Requires-Dist`.
407+ A string or list of strings, Python packages required
408+ at runtime. None items are ignored.
388409 requires_python:
410+ Used for metadata `Requires-Python`.
389411 A string or list of strings.
390412 requires_external:
413+ Used for metadata `Requires-External`.
391414 A string or list of strings.
392415 project_url:
393- A string or list of strings, each of the form: `{name}, {url}`.
416+ Used for metadata `Project-URL`.
417+ A string or list of strings, each of the form: `{name},
418+ {url}`.
394419 provides_extra:
420+ Used for metadata `Provides-Extra`.
395421 A string or list of strings.
396422
397423 entry_points:
@@ -456,6 +482,11 @@ def __init__(self,
456482 default being `sysconfig.get_path('platlib')` e.g.
457483 `myvenv/lib/python3.9/site-packages/`.
458484
485+ When calling this function, we assert that the file
486+ pyproject.toml exists in the current directory. (We do this
487+ here rather than in pipcl.Package's constructor, as otherwise
488+ importing setup.py from non-package-related code could fail.)
489+
459490 fn_clean:
460491 A function taking a single arg `all_` that cleans generated
461492 files. `all_` is true iff `--all` is in argv.
@@ -474,8 +505,7 @@ def __init__(self,
474505 It can be convenient to use `pipcl.git_items()`.
475506
476507 The specification for sdists requires that the list contains
477- `pyproject.toml`; we enforce this with a diagnostic rather than
478- raising an exception, to allow legacy command-line usage.
508+ `pyproject.toml`; we enforce this with a Python assert.
479509
480510 tag_python:
481511 First element of wheel tag defined in PEP-425. If None we use
@@ -822,12 +852,11 @@ def add_string(text, name):
822852 assert 0 , f'Path is inside sdist_directory={ sdist_directory } : { from_ !r} '
823853 assert os .path .exists (from_ ), f'Path does not exist: { from_ !r} '
824854 assert os .path .isfile (from_ ), f'Path is not a file: { from_ !r} '
825- if to_rel == 'pyproject.toml' :
826- found_pyproject_toml = True
827855 add (from_ , to_rel )
856+ if to_rel == 'pyproject.toml' :
857+ found_pyproject_toml = True
828858
829- if not found_pyproject_toml :
830- log0 (f'Warning: no pyproject.toml specified.' )
859+ assert found_pyproject_toml , f'Cannot create sdist because file not specified: pyproject.toml'
831860
832861 # Always add a PKG-INFO file.
833862 add_string (self ._metainfo (), 'PKG-INFO' )
@@ -978,13 +1007,38 @@ def _entry_points_text(self):
9781007
9791008 def _call_fn_build ( self , config_settings = None ):
9801009 assert self .fn_build
1010+ assert os .path .isfile ('pyproject.toml' ), (
1011+ 'Cannot create package because file does not exist: pyproject.toml'
1012+ )
9811013 log2 (f'calling self.fn_build={ self .fn_build } ' )
9821014 if inspect .signature (self .fn_build ).parameters :
9831015 ret = self .fn_build (config_settings )
9841016 else :
9851017 ret = self .fn_build ()
9861018 assert isinstance ( ret , (list , tuple )), \
9871019 f'Expected list/tuple from { self .fn_build } but got: { ret !r} '
1020+
1021+ # Check that any extensions that we have built, have same
1022+ # py_limited_api value. If package is marked with py_limited_api=True
1023+ # then non-py_limited_api extensions seem to fail at runtime on
1024+ # Windows.
1025+ #
1026+ # (We could possibly allow package py_limited_api=False and extensions
1027+ # py_limited_api=True, but haven't tested this, and it seems simpler to
1028+ # be strict.)
1029+ for item in ret :
1030+ from_ , (to_abs , to_rel ) = self ._fromto (item )
1031+ from_abs = os .path .abspath (from_ )
1032+ is_py_limited_api = _extensions_to_py_limited_api .get (from_abs )
1033+ if is_py_limited_api is not None :
1034+ assert bool (self .py_limited_api ) == bool (is_py_limited_api ), (
1035+ f'Extension was built with'
1036+ f' py_limited_api={ is_py_limited_api } but pipcl.Package'
1037+ f' name={ self .name !r} has'
1038+ f' py_limited_api={ self .py_limited_api } :'
1039+ f' { from_abs !r} '
1040+ )
1041+
9881042 return ret
9891043
9901044
@@ -1519,6 +1573,7 @@ def _fromto(self, p):
15191573 log2 (f'returning { from_ = } { to_ = } ' )
15201574 return from_ , to_
15211575
1576+ _extensions_to_py_limited_api = dict ()
15221577
15231578def build_extension (
15241579 name ,
@@ -1629,6 +1684,11 @@ def build_extension(
16291684 py_limited_api:
16301685 If true we build for current Python's limited API / stable ABI.
16311686
1687+ Note that we will assert false if this extension is added to a
1688+ pipcl.Package that has a different <py_limited_api>, because
1689+ on Windows importing a non-py_limited_api extension inside a
1690+ py_limited=True package fails.
1691+
16321692 Returns the leafname of the generated library file within `outdir`, e.g.
16331693 `_{name}.so` on Unix or `_{name}.cp311-win_amd64.pyd` on Windows.
16341694 '''
@@ -1886,6 +1946,8 @@ def build_extension(
18861946 #run(f'ls -l {path_so}', check=0)
18871947 #run(f'file {path_so}', check=0)
18881948
1949+ _extensions_to_py_limited_api [os .path .abspath (path_so )] = py_limited_api
1950+
18891951 return path_so_leaf
18901952
18911953
@@ -2864,6 +2926,9 @@ def log_line_numbers(yes):
28642926 global g_log_line_numbers
28652927 g_log_line_numbers = bool (yes )
28662928
2929+ def log (text = '' , caller = 1 ):
2930+ _log (text , 0 , caller + 1 )
2931+
28672932def log0 (text = '' , caller = 1 ):
28682933 _log (text , 0 , caller + 1 )
28692934
@@ -3146,11 +3211,8 @@ def swig_get(swig, quick, swig_local='pipcl-swig-git'):
31463211 # > If you need to have bison first in your PATH, run:
31473212 # > echo 'export PATH="/opt/homebrew/opt/bison/bin:$PATH"' >> ~/.zshrc
31483213 #
3149- run (f'brew install bison' )
3150- PATH = os .environ ['PATH' ]
3151- prefix_bison = run ('brew --prefix bison' , capture = 1 ).strip ()
3152- PATH = f'{ prefix_bison } /bin:{ PATH } '
3153- swig_env_extra = dict (PATH = PATH )
3214+ swig_env_extra = dict ()
3215+ macos_add_brew_path ('bison' , swig_env_extra )
31543216 run (f'which bison' )
31553217 run (f'which bison' , env_extra = swig_env_extra )
31563218 # Build swig.
@@ -3164,6 +3226,38 @@ def swig_get(swig, quick, swig_local='pipcl-swig-git'):
31643226 return swig
31653227
31663228
3229+ def macos_add_brew_path (package , env = None , gnubin = True ):
3230+ '''
3231+ Adds path(s) for Brew <package>'s binaries to env['PATH'].
3232+
3233+ Args:
3234+ package:
3235+ Name of package. We get <package_root> of installed package by
3236+ running `brew --prefix <package>`.
3237+ env:
3238+ The environment dict to modify. If None we use os.environ. If PATH
3239+ is not in <env>, we first copy os.environ['PATH'] into <env>.
3240+ gnubin:
3241+ If true, we also add path to gnu binaries if it exists,
3242+ <package_root>/libexe/gnubin.
3243+ '''
3244+ if not darwin ():
3245+ return
3246+ if env is None :
3247+ env = os .environ
3248+ if 'PATH' not in env :
3249+ env ['PATH' ] = os .environ ['PATH' ]
3250+ package_root = run (f'brew --prefix { package } ' , capture = 1 ).strip ()
3251+ def add (path ):
3252+ if os .path .isdir (path ):
3253+ log1 (f'Adding to $PATH: { path } ' )
3254+ PATH = env ['PATH' ]
3255+ env ['PATH' ] = f'{ path } :{ PATH } '
3256+ add (f'{ package_root } /bin' )
3257+ if gnubin :
3258+ add (f'{ package_root } /libexec/gnubin' )
3259+
3260+
31673261def _show_dict (d ):
31683262 ret = ''
31693263 for n in sorted (d .keys ()):
0 commit comments