Skip to content

Commit 040cf28

Browse files
pipcl.py: various additions.
* run(): also show <env_extra>. * show_system(): new, shows platform info, sys.argv and os.environ. * cpu_bits(): new, uses int.bit_length(). * number_sep(): removed because python already supports this with `,` operator. * git_get(): new fn for cloning/updating shallow checkout. * NewFiles: new class for detecting new/updated/modified files.
1 parent 6ff93bf commit 040cf28

File tree

1 file changed

+195
-48
lines changed

1 file changed

+195
-48
lines changed

pipcl.py

Lines changed: 195 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import os
2222
import platform
2323
import re
24+
import shlex
2425
import shutil
2526
import site
2627
import subprocess
@@ -654,12 +655,12 @@ def add_str(content, to_):
654655
z.writestr(f'{dist_info_dir}/RECORD', record.get(f'{dist_info_dir}/RECORD'))
655656

656657
st = os.stat(path)
657-
log1( f'Have created wheel size={st.st_size}: {path}')
658+
log1( f'Have created wheel size={st.st_size:,}: {path}')
658659
if g_verbose >= 2:
659660
with zipfile.ZipFile(path, compression=self.wheel_compression) as z:
660661
log2(f'Contents are:')
661662
for zi in sorted(z.infolist(), key=lambda z: z.filename):
662-
log2(f' {zi.file_size: 10d} {zi.filename}')
663+
log2(f' {zi.file_size: 10,d} {zi.filename}')
663664

664665
return os.path.basename(path)
665666

@@ -1901,6 +1902,105 @@ def git_items( directory, submodules=False):
19011902
return ret
19021903

19031904

1905+
def git_get(
1906+
remote,
1907+
local,
1908+
*,
1909+
branch=None,
1910+
depth=1,
1911+
env_extra=None,
1912+
tag=None,
1913+
update=True,
1914+
submodules=True,
1915+
):
1916+
'''
1917+
Ensures that <local> is a git checkout (at either <tag>, or <branch> HEAD)
1918+
of a remote repository.
1919+
1920+
Exactly one of <branch> and <tag> must be specified.
1921+
1922+
Args:
1923+
remote:
1924+
Remote git repostitory, for example
1925+
'https://github.com/ArtifexSoftware/mupdf.git'.
1926+
local:
1927+
Local directory. If <local>/.git exists, we attempt to run `git
1928+
update` in it.
1929+
branch:
1930+
Branch to use.
1931+
depth:
1932+
Depth of local checkout when cloning and fetching, or None.
1933+
env_extra:
1934+
Dict of extra name=value environment variables to use whenever we
1935+
run git.
1936+
tag:
1937+
Tag to use.
1938+
update:
1939+
If false we do not update existing repository. Might be useful if
1940+
testing without network access.
1941+
submodules:
1942+
If true, we clone with `--recursive --shallow-submodules` and run
1943+
`git submodule update --init --recursive` before returning.
1944+
'''
1945+
log0(f'{remote=} {local=} {branch=} {tag=}')
1946+
assert (branch and not tag) or (not branch and tag), f'Must specify exactly one of <branch> and <tag>.'
1947+
1948+
depth_arg = f' --depth {depth}' if depth else ''
1949+
1950+
def do_update():
1951+
# This seems to pull in the entire repository.
1952+
log0(f'do_update(): attempting to update {local=}.')
1953+
# Remove any local changes.
1954+
run(f'cd {local} && git checkout .', env_extra=env_extra)
1955+
if tag:
1956+
# `-u` avoids `fatal: Refusing to fetch into current branch`.
1957+
# Using '+' and `revs/tags/` prefix seems to avoid errors like:
1958+
# error: cannot update ref 'refs/heads/v3.16.44':
1959+
# trying to write non-commit object
1960+
# 06c4ae5fe39a03b37a25a8b95214d9f8f8a867b8 to branch
1961+
# 'refs/heads/v3.16.44'
1962+
#
1963+
run(f'cd {local} && git fetch -fuv{depth_arg} {remote} +refs/tags/{tag}:refs/tags/{tag}', env_extra=env_extra)
1964+
run(f'cd {local} && git checkout {tag}', env_extra=env_extra)
1965+
if branch:
1966+
# `-u` avoids `fatal: Refusing to fetch into current branch`.
1967+
run(f'cd {local} && git fetch -fuv{depth_arg} {remote} {branch}:{branch}', env_extra=env_extra)
1968+
run(f'cd {local} && git checkout {branch}', env_extra=env_extra)
1969+
1970+
do_clone = True
1971+
if os.path.isdir(f'{local}/.git'):
1972+
if update:
1973+
# Try to update existing checkout.
1974+
try:
1975+
do_update()
1976+
do_clone = False
1977+
except Exception as e:
1978+
log0(f'Failed to update existing checkout {local}: {e}')
1979+
else:
1980+
do_clone = False
1981+
1982+
if do_clone:
1983+
# No existing git checkout, so do a fresh clone.
1984+
#_fs_remove(local)
1985+
log0(f'Cloning to: {local}')
1986+
command = f'git clone --config core.longpaths=true{depth_arg}'
1987+
if submodules:
1988+
command += f' --recursive --shallow-submodules'
1989+
if branch:
1990+
command += f' -b {branch}'
1991+
if tag:
1992+
command += f' -b {tag}'
1993+
command += f' {remote} {local}'
1994+
run(command, env_extra=env_extra)
1995+
do_update()
1996+
1997+
if submodules:
1998+
run(f'cd {local} && git submodule update --init --recursive', env_extra=env_extra)
1999+
2000+
# Show sha of checkout.
2001+
run( f'cd {local} && git show --pretty=oneline|head -n 1', check=False)
2002+
2003+
19042004
def run(
19052005
command,
19062006
*,
@@ -1951,9 +2051,14 @@ def run(
19512051
env = os.environ.copy()
19522052
env.update(env_extra)
19532053
lines = _command_lines( command)
1954-
nl = '\n'
19552054
if verbose:
1956-
log1( f'Running: {nl.join(lines)}', caller=caller+1)
2055+
text = f'Running:'
2056+
if env_extra:
2057+
for k in sorted(env_extra.keys()):
2058+
text += f' {k}={shlex.quote(env_extra[k])}'
2059+
nl = '\n'
2060+
text += f' {nl.join(lines)}'
2061+
log1(text, caller=caller+1)
19572062
sep = ' ' if windows() else ' \\\n'
19582063
command2 = sep.join( lines)
19592064
cp = subprocess.run(
@@ -1990,6 +2095,39 @@ def linux():
19902095
def openbsd():
19912096
return platform.system() == 'OpenBSD'
19922097

2098+
2099+
def show_system():
2100+
'''
2101+
Show useful information about the system plus argv and environ.
2102+
'''
2103+
def log(text):
2104+
log0(text, caller=3)
2105+
2106+
#log(f'{__file__=}')
2107+
#log(f'{__name__=}')
2108+
log(f'{os.getcwd()=}')
2109+
log(f'{platform.machine()=}')
2110+
log(f'{platform.platform()=}')
2111+
log(f'{platform.python_version()=}')
2112+
log(f'{platform.system()=}')
2113+
log(f'{platform.uname()=}')
2114+
log(f'{sys.executable=}')
2115+
log(f'{sys.version=}')
2116+
log(f'{sys.version_info=}')
2117+
log(f'{list(sys.version_info)=}')
2118+
2119+
log(f'CPU bits: {cpu_bits()}')
2120+
2121+
log(f'sys.argv ({len(sys.argv)}):')
2122+
for i, arg in enumerate(sys.argv):
2123+
log(f' {i}: {arg!r}')
2124+
2125+
log(f'os.environ ({len(os.environ)}):')
2126+
for k in sorted( os.environ.keys()):
2127+
v = os.environ[ k]
2128+
log( f' {k}: {v!r}')
2129+
2130+
19932131
class PythonFlags:
19942132
'''
19952133
Compile/link flags for the current python, for example the include path
@@ -2164,6 +2302,10 @@ def _command_lines( command):
21642302
return lines
21652303

21662304

2305+
def cpu_bits():
2306+
return int.bit_length(sys.maxsize+1)
2307+
2308+
21672309
def _cpu_name():
21682310
'''
21692311
Returns `x32` or `x64` depending on Python build.
@@ -2418,7 +2560,7 @@ def log2(text='', caller=1):
24182560

24192561
def _log(text, level, caller):
24202562
'''
2421-
Logs lines with prefix.
2563+
Logs lines with prefix, if <level> is lower than <g_verbose>.
24222564
'''
24232565
if level <= g_verbose:
24242566
fr = inspect.stack(context=0)[caller]
@@ -2445,49 +2587,6 @@ def relpath(path, start=None):
24452587
return os.path.relpath(path, start)
24462588

24472589

2448-
def number_sep( s):
2449-
'''
2450-
Simple number formatter, adds commas in-between thousands. `s` can be a
2451-
number or a string. Returns a string.
2452-
2453-
>>> number_sep(1)
2454-
'1'
2455-
>>> number_sep(12)
2456-
'12'
2457-
>>> number_sep(123)
2458-
'123'
2459-
>>> number_sep(1234)
2460-
'1,234'
2461-
>>> number_sep(12345)
2462-
'12,345'
2463-
>>> number_sep(123456)
2464-
'123,456'
2465-
>>> number_sep(1234567)
2466-
'1,234,567'
2467-
>>> number_sep(-131072)
2468-
'-131,072'
2469-
'''
2470-
if not isinstance( s, str):
2471-
s = str( s)
2472-
ret = ''
2473-
if s.startswith('-'):
2474-
ret += '-'
2475-
s = s[1:]
2476-
c = s.find( '.')
2477-
if c==-1: c = len(s)
2478-
end = s.find('e')
2479-
if end == -1: end = s.find('E')
2480-
if end == -1: end = len(s)
2481-
for i in range( end):
2482-
ret += s[i]
2483-
if i<c-1 and (c-i-1)%3==0:
2484-
ret += ','
2485-
elif i>c and i<end-1 and (i-c)%3==0:
2486-
ret += ','
2487-
ret += s[end:]
2488-
return ret
2489-
2490-
24912590
def _so_suffix(use_so_versioning=True):
24922591
'''
24932592
Filename suffix for shared libraries is defined in pep-3149. The
@@ -2619,3 +2718,51 @@ def get(self, record_path=None):
26192718
if record_path:
26202719
ret += f'{record_path},,\n'
26212720
return ret
2721+
2722+
2723+
class NewFiles:
2724+
'''
2725+
Detects new/modified/updated files matching a glob pattern. Useful for
2726+
detecting wheels created by pip or cubuildwheel etc.
2727+
'''
2728+
def __init__(self, glob_pattern):
2729+
# Find current matches of <glob_pattern>.
2730+
self.glob_pattern = glob_pattern
2731+
self.items0 = self._items()
2732+
def get(self):
2733+
'''
2734+
Returns list of new matches of <glob_pattern> - paths of files that
2735+
were not present previously, or have different mtimes or have different
2736+
contents.
2737+
'''
2738+
ret = list()
2739+
items = self._items()
2740+
for path, id_ in items.items():
2741+
id0 = self.items0.get(path)
2742+
if id0 != id_:
2743+
#mtime0, hash0 = id0
2744+
#mtime1, hash1 = id_
2745+
#log0(f'New/modified file {path=}.')
2746+
#log0(f' {mtime0=} {"==" if mtime0==mtime1 else "!="} {mtime1=}.')
2747+
#log0(f' {hash0=} {"==" if hash0==hash1 else "!="} {hash1=}.')
2748+
ret.append(path)
2749+
return ret
2750+
def get_one(self):
2751+
'''
2752+
Returns new match of <glob_pattern>, asserting that there is exactly
2753+
one.
2754+
'''
2755+
ret = self.get()
2756+
assert len(ret) == 1, f'{len(ret)=}'
2757+
return ret[0]
2758+
def _file_id(self, path):
2759+
mtime = os.stat(path).st_mtime
2760+
with open(path, 'rb') as f:
2761+
hash_ = hashlib.file_digest(f, hashlib.md5).digest()
2762+
return mtime, hash_
2763+
def _items(self):
2764+
ret = dict()
2765+
for path in glob.glob(self.glob_pattern):
2766+
if os.path.isfile(path):
2767+
ret[path] = self._file_id(path)
2768+
return ret

0 commit comments

Comments
 (0)