Skip to content

Commit a472349

Browse files
pipcl.py: fix for package names containing '-'. fixed doctests.
1 parent 87fe4f0 commit a472349

File tree

1 file changed

+96
-38
lines changed

1 file changed

+96
-38
lines changed

pipcl.py

Lines changed: 96 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,32 @@
22
Python packaging operations, including PEP-517 support, for use by a `setup.py`
33
script.
44
5-
The intention is to take care of as many packaging details as possible so that
6-
setup.py contains only project-specific information, while also giving as much
7-
flexibility as possible.
5+
Overview:
86
9-
For example we provide a function `build_extension()` that can be used to build
10-
a SWIG extension, but we also give access to the located compiler/linker so
11-
that a `setup.py` script can take over the details itself.
7+
The intention is to take care of as many packaging details as possible so
8+
that setup.py contains only project-specific information, while also giving
9+
as much flexibility as possible.
1210
13-
Run doctests with: `python -m doctest pipcl.py`
11+
For example we provide a function `build_extension()` that can be used
12+
to build a SWIG extension, but we also give access to the located
13+
compiler/linker so that a `setup.py` script can take over the details
14+
itself.
1415
15-
For Graal we require that PIPCL_GRAAL_PYTHON is set to non-graal Python (we
16-
build for non-graal except with Graal Python's include paths and library
17-
directory).
16+
Doctests:
17+
Doctest strings are provided in some comments.
18+
19+
Test in the usual way with:
20+
python -m doctest pipcl.py
21+
22+
Test specific functions/classes with:
23+
python pipcl.py --doctest run_if ...
24+
25+
If no functions or classes are specified, this tests everything.
26+
27+
Graal:
28+
For Graal we require that PIPCL_GRAAL_PYTHON is set to non-graal Python (we
29+
build for non-graal except with Graal Python's include paths and library
30+
directory).
1831
'''
1932

2033
import base64
@@ -532,6 +545,12 @@ def assert_str_or_multi( v):
532545
assert_str_or_multi( requires_external)
533546
assert_str_or_multi( project_url)
534547
assert_str_or_multi( provides_extra)
548+
549+
assert re.match('^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])\\Z', name, re.IGNORECASE), (
550+
f'Invalid package name'
551+
f' (https://packaging.python.org/en/latest/specifications/name-normalization/)'
552+
f': {name!r}'
553+
)
535554

536555
# https://packaging.python.org/en/latest/specifications/core-metadata/.
537556
assert re.match('([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$', name, re.IGNORECASE), \
@@ -761,7 +780,7 @@ def build_sdist(self,
761780
else:
762781
items = self.fn_sdist()
763782

764-
prefix = f'{_normalise(self.name)}-{self.version}'
783+
prefix = f'{_normalise2(self.name)}-{self.version}'
765784
os.makedirs(sdist_directory, exist_ok=True)
766785
tarpath = f'{sdist_directory}/{prefix}.tar.gz'
767786
log2(f'Creating sdist: {tarpath}')
@@ -833,9 +852,11 @@ def tag_python(self):
833852
Get two-digit python version, e.g. 'cp3.8' for python-3.8.6.
834853
'''
835854
if self.tag_python_:
836-
return self.tag_python_
855+
ret = self.tag_python_
837856
else:
838-
return 'cp' + ''.join(platform.python_version().split('.')[:2])
857+
ret = 'cp' + ''.join(platform.python_version().split('.')[:2])
858+
assert '-' not in ret
859+
return ret
839860

840861
def tag_abi(self):
841862
'''
@@ -891,10 +912,13 @@ def tag_platform(self):
891912
ret = ret2
892913

893914
log0( f'tag_platform(): returning {ret=}.')
915+
assert '-' not in ret
894916
return ret
895917

896918
def wheel_name(self):
897-
return f'{_normalise(self.name)}-{self.version}-{self.tag_python()}-{self.tag_abi()}-{self.tag_platform()}.whl'
919+
ret = f'{_normalise2(self.name)}-{self.version}-{self.tag_python()}-{self.tag_abi()}-{self.tag_platform()}.whl'
920+
assert ret.count('-') == 4, f'Expected 4 dash characters in {ret=}.'
921+
return ret
898922

899923
def wheel_name_match(self, wheel):
900924
'''
@@ -923,7 +947,7 @@ def wheel_name_match(self, wheel):
923947
log2(f'py_limited_api; {tag_python=} compatible with {self.tag_python()=}.')
924948
py_limited_api_compatible = True
925949

926-
log2(f'{_normalise(self.name) == name=}')
950+
log2(f'{_normalise2(self.name) == name=}')
927951
log2(f'{self.version == version=}')
928952
log2(f'{self.tag_python() == tag_python=} {self.tag_python()=} {tag_python=}')
929953
log2(f'{py_limited_api_compatible=}')
@@ -932,7 +956,7 @@ def wheel_name_match(self, wheel):
932956
log2(f'{self.tag_platform()=}')
933957
log2(f'{tag_platform.split(".")=}')
934958
ret = (1
935-
and _normalise(self.name) == name
959+
and _normalise2(self.name) == name
936960
and self.version == version
937961
and (self.tag_python() == tag_python or py_limited_api_compatible)
938962
and self.tag_abi() == tag_abi
@@ -1059,7 +1083,7 @@ def _argv_dist_info(self, root):
10591083
it writes to a slightly different directory.
10601084
'''
10611085
if root is None:
1062-
root = f'{self.name}-{self.version}.dist-info'
1086+
root = f'{normalise2(self.name)}-{self.version}.dist-info'
10631087
self._write_info(f'{root}/METADATA')
10641088
if self.license:
10651089
with open( f'{root}/COPYING', 'w') as f:
@@ -1347,7 +1371,7 @@ def __str__(self):
13471371
)
13481372

13491373
def _dist_info_dir( self):
1350-
return f'{_normalise(self.name)}-{self.version}.dist-info'
1374+
return f'{_normalise2(self.name)}-{self.version}.dist-info'
13511375

13521376
def _metainfo(self):
13531377
'''
@@ -1487,7 +1511,7 @@ def _fromto(self, p):
14871511
to_ = f'{self._dist_info_dir()}/{to_[ len(prefix):]}'
14881512
prefix = '$data/'
14891513
if to_.startswith( prefix):
1490-
to_ = f'{self.name}-{self.version}.data/{to_[ len(prefix):]}'
1514+
to_ = f'{_normalise2(self.name)}-{self.version}.data/{to_[ len(prefix):]}'
14911515
if isinstance(from_, str):
14921516
from_, _ = self._path_relative_to_root( from_, assert_within_root=False)
14931517
to_ = self._path_relative_to_root(to_)
@@ -2569,7 +2593,7 @@ def _cpu_name():
25692593
return f'x{32 if sys.maxsize == 2**31 - 1 else 64}'
25702594

25712595

2572-
def run_if( command, out, *prerequisites):
2596+
def run_if( command, out, *prerequisites, caller=1):
25732597
'''
25742598
Runs a command only if the output file is not up to date.
25752599
@@ -2599,21 +2623,26 @@ def run_if( command, out, *prerequisites):
25992623
... os.remove( out)
26002624
>>> if os.path.exists( f'{out}.cmd'):
26012625
... os.remove( f'{out}.cmd')
2602-
>>> run_if( f'touch {out}', out)
2626+
>>> run_if( f'touch {out}', out, caller=0)
26032627
pipcl.py:run_if(): Running command because: File does not exist: 'run_if_test_out'
26042628
pipcl.py:run_if(): Running: touch run_if_test_out
26052629
True
26062630
26072631
If we repeat, the output file will be up to date so the command is not run:
26082632
2609-
>>> run_if( f'touch {out}', out)
2633+
>>> run_if( f'touch {out}', out, caller=0)
26102634
pipcl.py:run_if(): Not running command because up to date: 'run_if_test_out'
26112635
26122636
If we change the command, the command is run:
26132637
2614-
>>> run_if( f'touch {out}', out)
2615-
pipcl.py:run_if(): Running command because: Command has changed
2616-
pipcl.py:run_if(): Running: touch run_if_test_out
2638+
>>> run_if( f'touch {out};', out, caller=0)
2639+
pipcl.py:run_if(): Running command because: Command has changed:
2640+
pipcl.py:run_if(): @@ -1,2 +1,2 @@
2641+
pipcl.py:run_if(): touch
2642+
pipcl.py:run_if(): -run_if_test_out
2643+
pipcl.py:run_if(): +run_if_test_out;
2644+
pipcl.py:run_if():
2645+
pipcl.py:run_if(): Running: touch run_if_test_out;
26172646
True
26182647
26192648
If we add a prerequisite that is newer than the output, the command is run:
@@ -2622,15 +2651,20 @@ def run_if( command, out, *prerequisites):
26222651
>>> prerequisite = 'run_if_test_prerequisite'
26232652
>>> run( f'touch {prerequisite}', caller=0)
26242653
pipcl.py:run(): Running: touch run_if_test_prerequisite
2625-
>>> run_if( f'touch {out}', out, prerequisite)
2626-
pipcl.py:run_if(): Running command because: Prerequisite is new: 'run_if_test_prerequisite'
2654+
>>> run_if( f'touch {out}', out, prerequisite, caller=0)
2655+
pipcl.py:run_if(): Running command because: Command has changed:
2656+
pipcl.py:run_if(): @@ -1,2 +1,2 @@
2657+
pipcl.py:run_if(): touch
2658+
pipcl.py:run_if(): -run_if_test_out;
2659+
pipcl.py:run_if(): +run_if_test_out
2660+
pipcl.py:run_if():
26272661
pipcl.py:run_if(): Running: touch run_if_test_out
26282662
True
26292663
26302664
If we repeat, the output will be newer than the prerequisite, so the
26312665
command is not run:
26322666
2633-
>>> run_if( f'touch {out}', out, prerequisite)
2667+
>>> run_if( f'touch {out}', out, prerequisite, caller=0)
26342668
pipcl.py:run_if(): Not running command because up to date: 'run_if_test_out'
26352669
'''
26362670
doit = False
@@ -2687,9 +2721,9 @@ def _make_prerequisites(p):
26872721
for p in prerequisites:
26882722
prerequisites_all += _make_prerequisites( p)
26892723
if 0:
2690-
log2( 'prerequisites_all:')
2724+
log2( 'prerequisites_all:', caller=caller+1)
26912725
for i in prerequisites_all:
2692-
log2( f' {i!r}')
2726+
log2( f' {i!r}', caller=caller+1)
26932727
pre_mtime = 0
26942728
pre_path = None
26952729
for prerequisite in prerequisites_all:
@@ -2715,16 +2749,16 @@ def _make_prerequisites(p):
27152749
os.remove( cmd_path)
27162750
except Exception:
27172751
pass
2718-
log1( f'Running command because: {doit}', caller=2)
2752+
log1( f'Running command because: {doit}', caller=caller+1)
27192753

2720-
run( command, caller=2)
2754+
run( command, caller=caller+1)
27212755

27222756
# Write the command we ran, into `cmd_path`.
27232757
with open( cmd_path, 'w') as f:
27242758
f.write( command)
27252759
return True
27262760
else:
2727-
log1( f'Not running command because up to date: {out!r}', caller=2)
2761+
log1( f'Not running command because up to date: {out!r}', caller=caller+1)
27282762

27292763
if 0:
27302764
log2( f'out_mtime={time.ctime(out_mtime)} pre_mtime={time.ctime(pre_mtime)}.'
@@ -2796,6 +2830,11 @@ def _normalise(name):
27962830
return re.sub(r"[-_.]+", "-", name).lower()
27972831

27982832

2833+
def _normalise2(name):
2834+
# https://packaging.python.org/en/latest/specifications/binary-distribution-format/
2835+
return _normalise(name).replace('-', '_')
2836+
2837+
27992838
def _assert_version_pep_440(version):
28002839
assert re.match(
28012840
r'^([1-9][0-9]*!)?(0|[1-9][0-9]*)(\.(0|[1-9][0-9]*))*((a|b|rc)(0|[1-9][0-9]*))?(\.post(0|[1-9][0-9]*))?(\.dev(0|[1-9][0-9]*))?$',
@@ -2848,19 +2887,30 @@ def _log(text, level, caller):
28482887
print(f'{filename}:{fr.function}(): {line}', file=sys.stdout, flush=1)
28492888

28502889

2851-
def relpath(path, start=None):
2890+
def relpath(path, start=None, allow_up=True):
28522891
'''
28532892
A safe alternative to os.path.relpath(), avoiding an exception on Windows
28542893
if the drive needs to change - in this case we use os.path.abspath().
2894+
2895+
Args:
2896+
path:
2897+
Path to be processed.
2898+
start:
2899+
Start directory or current directory if None.
2900+
allow_up:
2901+
If false we return absolute path is <path> is not within <start>.
28552902
'''
28562903
if windows():
28572904
try:
2858-
return os.path.relpath(path, start)
2905+
ret = os.path.relpath(path, start)
28592906
except ValueError:
28602907
# os.path.relpath() fails if trying to change drives.
2861-
return os.path.abspath(path)
2908+
ret = os.path.abspath(path)
28622909
else:
2863-
return os.path.relpath(path, start)
2910+
ret = os.path.relpath(path, start)
2911+
if not allow_up and ret.startswith('../') or ret.startswith('..\\'):
2912+
ret = os.path.abspath(path)
2913+
return ret
28642914

28652915

28662916
def _so_suffix(use_so_versioning=True):
@@ -3218,7 +3268,15 @@ def venv_run(args, path, recreate=True, clean=False):
32183268
# graal_legacy_python_config is true.
32193269
#
32203270
includes, ldflags = sysconfig_python_flags()
3221-
if sys.argv[1:] == ['--graal-legacy-python-config', '--includes']:
3271+
if sys.argv[1] == '--doctest':
3272+
import doctest
3273+
if sys.argv[2:]:
3274+
for f in sys.argv[2:]:
3275+
ff = globals()[f]
3276+
doctest.run_docstring_examples(ff, globals())
3277+
else:
3278+
doctest.testmod(None)
3279+
elif sys.argv[1:] == ['--graal-legacy-python-config', '--includes']:
32223280
print(includes)
32233281
elif sys.argv[1:] == ['--graal-legacy-python-config', '--ldflags']:
32243282
print(ldflags)

0 commit comments

Comments
 (0)