2121import os
2222import platform
2323import re
24+ import shlex
2425import shutil
2526import site
2627import 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+
19042004def 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():
19902095def 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+
19932131class 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+
21672309def _cpu_name ():
21682310 '''
21692311 Returns `x32` or `x64` depending on Python build.
@@ -2418,7 +2560,7 @@ def log2(text='', caller=1):
24182560
24192561def _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-
24912590def _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