From a2e44948e289879ef6721ee78346a9802a458e51 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 25 Oct 2021 18:29:12 -0600 Subject: [PATCH 01/25] Ignore the tests outdir. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index c3fc748ea97c33..b2ad76689f12bf 100644 --- a/.gitignore +++ b/.gitignore @@ -120,6 +120,7 @@ Tools/unicode/data/ Tools/msi/obj Tools/ssl/amd64 Tools/ssl/win32 +Tools/freeze/test/outdir # The frozen modules are always generated by the build so we don't # keep them in the repo. Also see Tools/scripts/freeze_modules.py. From 9675b6924a0854cd78c18a7937649a470109699b Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 25 Oct 2021 18:51:42 -0600 Subject: [PATCH 02/25] Add test helpers for the "freeze" tool. --- Tools/freeze/test/freeze.py | 154 ++++++++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 Tools/freeze/test/freeze.py diff --git a/Tools/freeze/test/freeze.py b/Tools/freeze/test/freeze.py new file mode 100644 index 00000000000000..52ed4eae8d7e4d --- /dev/null +++ b/Tools/freeze/test/freeze.py @@ -0,0 +1,154 @@ +import os +import os.path +import shutil +import subprocess + + +TESTS_DIR = os.path.dirname(__file__) +TOOL_ROOT = os.path.dirname(TESTS_DIR) +SRCDIR = os.path.dirname(os.path.dirname(TOOL_ROOT)) + +CONFIGURE = os.path.join(SRCDIR, 'configure') +MAKE = shutil.which('make') +FREEZE = os.path.join(TOOL_ROOT, 'freeze.py') +OUTDIR = os.path.join(TESTS_DIR, 'outdir') + + +class UnsupportedError(Exception): + """The operation isn't supported.""" + + +def _run_cmd(cmd, cwd, verbose=True): + proc = subprocess.run( + cmd, + cwd=cwd, + capture_output=not verbose, + text=True, + ) + if proc.returncode != 0: + print(proc.stderr, file=sys.stderr) + proc.check_returncode() + return proc.stdout + + +################################## +# building Python + +def configure_python(outdir, prefix=None, cachefile=None, *, verbose=True): + if not prefix: + prefix = os.path.join(outdir, 'python-installation') + builddir = os.path.join(outdir, 'python-build') + print(f'configuring python in {builddir}...') + os.makedirs(builddir, exist_ok=True) + cmd = [CONFIGURE, f'--prefix={prefix}'] + if cachefile: + cmd.extend(['--cache-file', cachefile]) + _run_cmd(cmd, builddir, verbose) + return builddir + + +def get_prefix(build=None): + if build: + if os.path.isfile(build): + return _run_cmd( + [build, '-c' 'import sys; print(sys.prefix)'], + cwd=os.path.dirname(build), + ) + + builddir = build + else: + builddir = os.path.abspath('.') + # We have to read it out of Makefile. + makefile = os.path.join(builddir, 'Makefile') + try: + infile = open(makefile) + except FileNotFoundError: + return None + with infile: + for line in infile: + if line.startswith('prefix='): + return line.partition('=')[-1].strip() + return None + + +def build_python(builddir=None, *, verbose=True): + if not MAKE: + raise UnsupportedError('make') + + if builddir: + builddir = os.path.abspath(builddir) + else: + builddir = SRCDIR + if builddir != SRCDIR: + _run_cmd([MAKE, 'clean'], SRCDIR, verbose=False) + + print(f'building python in {builddir}...') + _run_cmd([MAKE, '-j'], builddir, verbose) + + return os.path.join(builddir, 'python') + + +def install_python(builddir=None, *, verbose=True): + if not MAKE: + raise UnsupportedError('make') + + if not builddir: + builddir = '.' + prefix = get_prefix(builddir) + + print(f'installing python into {prefix}...') + _run_cmd([MAKE, '-j', 'install'], builddir, verbose) + + if not prefix: + return None + return os.path.join(prefix, 'bin', 'python3') + + +################################## +# freezing + +def pre_freeze(outdir=None, *, verbose=True): + if not outdir: + outdir = OUTDIR + cachefile = os.path.join(outdir, 'python-config.cache') + builddir = configure_python(outdir, cachefile=cachefile, verbose=verbose) + build_python(builddir, verbose=verbose) + return install_python(builddir, verbose=verbose) + + +def freeze(python, scriptfile, outdir=None, *, verbose=True): + if not MAKE: + raise UnsupportedError('make') + + if not outdir: + outdir = OUTDIR + + print(f'freezing {scriptfile}...') + os.makedirs(outdir, exist_ok=True) + subprocess.run( + [python, FREEZE, '-o', outdir, scriptfile], + cwd=outdir, + capture_output=not verbose, + check=True, + ) + + subprocess.run( + [MAKE], + cwd=os.path.dirname(scriptfile), + capture_output=not verbose, + check=True, + ) + + name = os.path.basename(scriptfile).rpartition('.')[0] + executable = os.path.join(outdir, name) + return executable + + +def run(executable): + proc = subprocess.run( + [executable], + capture_output=True, + text=True, + check=True, + ) + return proc.stdout.strip() From a34dca450d2f8f1249547828588f19327c5d9258 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 25 Oct 2021 18:52:33 -0600 Subject: [PATCH 03/25] Add a test for the "freeze" tool. --- Lib/test/test_tools/test_freeze.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 Lib/test/test_tools/test_freeze.py diff --git a/Lib/test/test_tools/test_freeze.py b/Lib/test/test_tools/test_freeze.py new file mode 100644 index 00000000000000..23b565d2ce8f32 --- /dev/null +++ b/Lib/test/test_tools/test_freeze.py @@ -0,0 +1,29 @@ +"""Sanity-check tests for the "freeze" tool.""" + +import os.path +import subprocess +import sys +import textwrap +import unittest + +from . import imports_under_tool +with imports_under_tool('freeze', 'test'): + import freeze as helper + + +@unittest.skipIf(sys.platform.startswith('win'), 'not supported on Windows') +class TestFreeze(unittest.TestCase): + + def test_freeze_simple_script(self): + script = textwrap.dedent(""" + import sys + print('running...') + sys.exit(0) + """) + scriptfile = os.path.join(helper.OUTDIR, 'app.py') + with open(scriptfile, 'w') as outfile: + outfile.write(script) + python = helper.pre_freeze(verbose=True) + executable = helper.freeze(python, scriptfile, verbose=True) + text = helper.run(executable) + self.assertEqual(text, 'running...') From 77f334ddac9a8490022a499ff8ca9c952c471364 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 25 Oct 2021 19:15:32 -0600 Subject: [PATCH 04/25] Create the outdir. --- Lib/test/test_tools/test_freeze.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_tools/test_freeze.py b/Lib/test/test_tools/test_freeze.py index 23b565d2ce8f32..d8bb552a4de755 100644 --- a/Lib/test/test_tools/test_freeze.py +++ b/Lib/test/test_tools/test_freeze.py @@ -1,5 +1,6 @@ """Sanity-check tests for the "freeze" tool.""" +import os import os.path import subprocess import sys @@ -15,6 +16,7 @@ class TestFreeze(unittest.TestCase): def test_freeze_simple_script(self): + os.makedirs(helper.OUTDIR, exist_ok=True) script = textwrap.dedent(""" import sys print('running...') @@ -23,7 +25,7 @@ def test_freeze_simple_script(self): scriptfile = os.path.join(helper.OUTDIR, 'app.py') with open(scriptfile, 'w') as outfile: outfile.write(script) - python = helper.pre_freeze(verbose=True) - executable = helper.freeze(python, scriptfile, verbose=True) + python = helper.pre_freeze(verbose=False) + executable = helper.freeze(python, scriptfile, verbose=False) text = helper.run(executable) self.assertEqual(text, 'running...') From 561f8e1b8f46359d49bd9d141d4b462f3045c596 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 26 Oct 2021 09:55:27 -0600 Subject: [PATCH 05/25] Build in the source tree for tests. --- Lib/test/test_tools/test_freeze.py | 9 +- Tools/freeze/test/freeze.py | 167 +++++++++++++++++++++-------- 2 files changed, 128 insertions(+), 48 deletions(-) diff --git a/Lib/test/test_tools/test_freeze.py b/Lib/test/test_tools/test_freeze.py index d8bb552a4de755..e81f619d98fedf 100644 --- a/Lib/test/test_tools/test_freeze.py +++ b/Lib/test/test_tools/test_freeze.py @@ -16,16 +16,13 @@ class TestFreeze(unittest.TestCase): def test_freeze_simple_script(self): - os.makedirs(helper.OUTDIR, exist_ok=True) script = textwrap.dedent(""" import sys print('running...') sys.exit(0) """) - scriptfile = os.path.join(helper.OUTDIR, 'app.py') - with open(scriptfile, 'w') as outfile: - outfile.write(script) - python = helper.pre_freeze(verbose=False) - executable = helper.freeze(python, scriptfile, verbose=False) + outdir, scriptfile, python = helper.prepare(script, outoftree=False, verbose=False) + + executable = helper.freeze(python, scriptfile, outdir, verbose=False) text = helper.run(executable) self.assertEqual(text, 'running...') diff --git a/Tools/freeze/test/freeze.py b/Tools/freeze/test/freeze.py index 52ed4eae8d7e4d..e7232b1107dd72 100644 --- a/Tools/freeze/test/freeze.py +++ b/Tools/freeze/test/freeze.py @@ -1,7 +1,10 @@ import os import os.path +import re +import shlex import shutil import subprocess +import sys TESTS_DIR = os.path.dirname(__file__) @@ -18,7 +21,7 @@ class UnsupportedError(Exception): """The operation isn't supported.""" -def _run_cmd(cmd, cwd, verbose=True): +def _run_cmd(cmd, cwd=None, verbose=True): proc = subprocess.run( cmd, cwd=cwd, @@ -31,55 +34,115 @@ def _run_cmd(cmd, cwd, verbose=True): return proc.stdout -################################## -# building Python - -def configure_python(outdir, prefix=None, cachefile=None, *, verbose=True): - if not prefix: - prefix = os.path.join(outdir, 'python-installation') - builddir = os.path.join(outdir, 'python-build') - print(f'configuring python in {builddir}...') - os.makedirs(builddir, exist_ok=True) - cmd = [CONFIGURE, f'--prefix={prefix}'] - if cachefile: - cmd.extend(['--cache-file', cachefile]) - _run_cmd(cmd, builddir, verbose) - return builddir +def find_opt(args, name): + opt = f'--{name}' + optstart = f'{opt}=' + for i, arg in enumerate(args): + if arg == opt or arg.startswith(optstart): + return i + return -1 -def get_prefix(build=None): - if build: - if os.path.isfile(build): - return _run_cmd( - [build, '-c' 'import sys; print(sys.prefix)'], - cwd=os.path.dirname(build), - ) +################################## +# build queries - builddir = build - else: - builddir = os.path.abspath('.') - # We have to read it out of Makefile. - makefile = os.path.join(builddir, 'Makefile') +def get_makefile_var(builddir, name, *, fail=True): + regex = re.compile(rf'^{name} *=\s*(.*?)\s*$') + filename = os.path.join(builddir, 'Makefile') try: - infile = open(makefile) + infile = open(filename) except FileNotFoundError: + if fail: + raise # re-raise return None with infile: for line in infile: - if line.startswith('prefix='): - return line.partition('=')[-1].strip() + m = regex.match(line) + if m: + value, = m.groups() + return value or '' + if fail: + raise KeyError(f'{name!r} not in Makefile', name=name) return None -def build_python(builddir=None, *, verbose=True): +def get_config_var(build, name, *, fail=True): + if os.path.isfile(build): + python = build + else: + builddir = build + python = os.path.join(builddir, 'python') + if not os.path.isfile(python): + return get_makefile_var(builddir, 'CONFIG_ARGS', fail=fail) + + text = _run_cmd([python, '-c', + 'import sysconfig', + 'sysconfig.get_config_var("CONFIG_ARGS")']) + return text + + +def get_configure_args(build, *, fail=True): + text = get_config_var(build, 'CONFIG_ARGS', fail=fail) + if not text: + return None + return shlex.split(text) + + +def get_prefix(build=None): + if build and os.path.isfile(build): + return _run_cmd( + [build, '-c' 'import sys; print(sys.prefix)'], + cwd=os.path.dirname(build), + ) + else: + return get_makefile_var(build or '.', 'prefix', fail=False) + + +################################## +# building Python + +def configure_python(builddir=None, prefix=None, cachefile=None, args=None, *, + srcdir=None, + inherit=False, + verbose=True, + ): + if not builddir: + builddir = '.' + if not srcdir: + configure = os.path.join(builddir, 'configure') + if not os.path.isfile(configure): + srcdir = SRCDIR + configure = CONFIGURE + else: + configure = os.path.join(srcdir, 'configure') + cmd = [configure] + if inherit: + oldargs = get_configure_args(builddir) + if oldargs: + cmd.extend(oldargs) + if cachefile: + if args and find_opt(args, 'cache-file') >= 0: + raise ValueError('unexpected --cache-file') + cmd.extend(['--cache-file', os.path.abspath(cachefile)]) + if prefix: + if args and find_opt(args, 'prefix') >= 0: + raise ValueError('unexpected --prefix') + cmd.extend(['--prefix', os.path.abspath(prefix)]) + if args: + cmd.extend(args) + print(f'configuring python in {builddir}...') + os.makedirs(builddir, exist_ok=True) + _run_cmd(cmd, builddir, verbose) + return builddir + + +def build_python(builddir, *, verbose=True): if not MAKE: raise UnsupportedError('make') - if builddir: - builddir = os.path.abspath(builddir) - else: - builddir = SRCDIR - if builddir != SRCDIR: + if not builddir: + builddir = '.' + if os.path.abspath(builddir) != SRCDIR: _run_cmd([MAKE, 'clean'], SRCDIR, verbose=False) print(f'building python in {builddir}...') @@ -88,7 +151,7 @@ def build_python(builddir=None, *, verbose=True): return os.path.join(builddir, 'python') -def install_python(builddir=None, *, verbose=True): +def install_python(builddir, *, verbose=True): if not MAKE: raise UnsupportedError('make') @@ -104,16 +167,36 @@ def install_python(builddir=None, *, verbose=True): return os.path.join(prefix, 'bin', 'python3') +def ensure_python_installed(outdir, *, outoftree=True, verbose=True): + cachefile = os.path.join(outdir, 'python-config.cache') + prefix = os.path.join(outdir, 'python-installation') + if outoftree: + builddir = os.path.join(outdir, 'python-build') + else: + builddir = SRCDIR + configure_python(builddir, prefix, cachefile, inherit=True, verbose=verbose) + build_python(builddir, verbose=verbose) + return install_python(builddir, verbose=verbose) + + ################################## # freezing -def pre_freeze(outdir=None, *, verbose=True): +def prepare(script=None, outdir=None, *, outoftree=True, verbose=True): if not outdir: outdir = OUTDIR - cachefile = os.path.join(outdir, 'python-config.cache') - builddir = configure_python(outdir, cachefile=cachefile, verbose=verbose) - build_python(builddir, verbose=verbose) - return install_python(builddir, verbose=verbose) + os.makedirs(outdir, exist_ok=True) + if script: + if script.splitlines()[0] == script and os.path.isfile(script): + scriptfile = script + else: + scriptfile = os.path.join(outdir, 'app.py') + with open(scriptfile, 'w') as outfile: + outfile.write(script) + else: + scriptfile = None + python = ensure_python_installed(outdir, outoftree=outoftree, verbose=verbose) + return outdir, scriptfile, python def freeze(python, scriptfile, outdir=None, *, verbose=True): From bd54ddd8bfcf2d63ed624fcb78c17f330f181eec Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 26 Oct 2021 10:40:40 -0600 Subject: [PATCH 06/25] Copy the repo during tests. --- Lib/test/test_tools/test_freeze.py | 7 +++- Tools/freeze/test/freeze.py | 58 +++++++++++++++++++++++++----- 2 files changed, 56 insertions(+), 9 deletions(-) diff --git a/Lib/test/test_tools/test_freeze.py b/Lib/test/test_tools/test_freeze.py index e81f619d98fedf..0d044c8b87ea03 100644 --- a/Lib/test/test_tools/test_freeze.py +++ b/Lib/test/test_tools/test_freeze.py @@ -21,7 +21,12 @@ def test_freeze_simple_script(self): print('running...') sys.exit(0) """) - outdir, scriptfile, python = helper.prepare(script, outoftree=False, verbose=False) + outdir, scriptfile, python = helper.prepare( + script, + outoftree=True, + copy=True, + verbose=False, + ) executable = helper.freeze(python, scriptfile, outdir, verbose=False) text = helper.run(executable) diff --git a/Tools/freeze/test/freeze.py b/Tools/freeze/test/freeze.py index e7232b1107dd72..96b90bd074006e 100644 --- a/Tools/freeze/test/freeze.py +++ b/Tools/freeze/test/freeze.py @@ -13,6 +13,7 @@ CONFIGURE = os.path.join(SRCDIR, 'configure') MAKE = shutil.which('make') +GIT = shutil.which('git') FREEZE = os.path.join(TOOL_ROOT, 'freeze.py') OUTDIR = os.path.join(TESTS_DIR, 'outdir') @@ -43,6 +44,29 @@ def find_opt(args, name): return -1 +def git_copy_repo(newroot, remote=None, *, verbose=True): + if not GIT: + raise UnsupportedError('git') + if not remote: + remote = SRCDIR + if os.path.exists(newroot): + print(f'updating repo {newroot}...') + _run_cmd([GIT, 'pull', remote], newroot, verbose=verbose) + else: + print(f'updating repo into {newroot}...') + _run_cmd([GIT, 'clone', remote, newroot], verbose=verbose) + if os.path.exists(remote): + # Copy over any uncommited files. + reporoot = remote + text = _run_cmd([GIT, 'status', '-s'], reporoot, verbose=verbose) + for line in text.splitlines(): + _, _, relfile = line.strip().partition(' ') + srcfile = os.path.join(reporoot, relfile) + dstfile = os.path.join(newroot, relfile) + os.makedirs(os.path.dirname(dstfile), exist_ok=True) + shutil.copy2(srcfile, dstfile) + + ################################## # build queries @@ -107,7 +131,7 @@ def configure_python(builddir=None, prefix=None, cachefile=None, args=None, *, verbose=True, ): if not builddir: - builddir = '.' + builddir = srcdir or SRCDIR if not srcdir: configure = os.path.join(builddir, 'configure') if not os.path.isfile(configure): @@ -115,6 +139,7 @@ def configure_python(builddir=None, prefix=None, cachefile=None, args=None, *, configure = CONFIGURE else: configure = os.path.join(srcdir, 'configure') + cmd = [configure] if inherit: oldargs = get_configure_args(builddir) @@ -130,6 +155,7 @@ def configure_python(builddir=None, prefix=None, cachefile=None, args=None, *, cmd.extend(['--prefix', os.path.abspath(prefix)]) if args: cmd.extend(args) + print(f'configuring python in {builddir}...') os.makedirs(builddir, exist_ok=True) _run_cmd(cmd, builddir, verbose) @@ -142,8 +168,10 @@ def build_python(builddir, *, verbose=True): if not builddir: builddir = '.' - if os.path.abspath(builddir) != SRCDIR: - _run_cmd([MAKE, 'clean'], SRCDIR, verbose=False) + + srcdir = get_config_var(builddir, 'srcdir', fail=False) or SRCDIR + if os.path.abspath(builddir) != srcdir: + _run_cmd([MAKE, 'clean'], srcdir, verbose=False) print(f'building python in {builddir}...') _run_cmd([MAKE, '-j'], builddir, verbose) @@ -167,14 +195,18 @@ def install_python(builddir, *, verbose=True): return os.path.join(prefix, 'bin', 'python3') -def ensure_python_installed(outdir, *, outoftree=True, verbose=True): +def ensure_python_installed(outdir, srcdir=None, *, + outoftree=True, + verbose=True, + ): cachefile = os.path.join(outdir, 'python-config.cache') prefix = os.path.join(outdir, 'python-installation') if outoftree: builddir = os.path.join(outdir, 'python-build') else: - builddir = SRCDIR - configure_python(builddir, prefix, cachefile, inherit=True, verbose=verbose) + builddir = srcdir or SRCDIR + configure_python(builddir, prefix, cachefile, + srcdir=srcdir, inherit=True, verbose=verbose) build_python(builddir, verbose=verbose) return install_python(builddir, verbose=verbose) @@ -182,7 +214,11 @@ def ensure_python_installed(outdir, *, outoftree=True, verbose=True): ################################## # freezing -def prepare(script=None, outdir=None, *, outoftree=True, verbose=True): +def prepare(script=None, outdir=None, *, + outoftree=True, + copy=False, + verbose=True, + ): if not outdir: outdir = OUTDIR os.makedirs(outdir, exist_ok=True) @@ -195,7 +231,13 @@ def prepare(script=None, outdir=None, *, outoftree=True, verbose=True): outfile.write(script) else: scriptfile = None - python = ensure_python_installed(outdir, outoftree=outoftree, verbose=verbose) + if copy: + srcdir = os.path.join(outdir, 'cpython') + git_copy_repo(srcdir, SRCDIR, verbose=verbose) + else: + srcdir = SRCDIR + python = ensure_python_installed(outdir, srcdir, + outoftree=outoftree, verbose=verbose) return outdir, scriptfile, python From 5d0ec9e4a396d2b0c0060a401bff3105c401f71c Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 26 Oct 2021 10:47:46 -0600 Subject: [PATCH 07/25] Force the git pull. --- Tools/freeze/test/freeze.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tools/freeze/test/freeze.py b/Tools/freeze/test/freeze.py index 96b90bd074006e..a91844484beeac 100644 --- a/Tools/freeze/test/freeze.py +++ b/Tools/freeze/test/freeze.py @@ -50,10 +50,10 @@ def git_copy_repo(newroot, remote=None, *, verbose=True): if not remote: remote = SRCDIR if os.path.exists(newroot): - print(f'updating repo {newroot}...') - _run_cmd([GIT, 'pull', remote], newroot, verbose=verbose) + print(f'updating copied repo {newroot}...') + _run_cmd([GIT, 'pull', '-f', remote], newroot, verbose=verbose) else: - print(f'updating repo into {newroot}...') + print(f'copying repo into {newroot}...') _run_cmd([GIT, 'clone', remote, newroot], verbose=verbose) if os.path.exists(remote): # Copy over any uncommited files. From b879a3c950b600ca3e0a32d5845f1a7eba911ae2 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 26 Oct 2021 11:01:00 -0600 Subject: [PATCH 08/25] Show the commands. --- Tools/freeze/test/freeze.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Tools/freeze/test/freeze.py b/Tools/freeze/test/freeze.py index a91844484beeac..b84d472462f18f 100644 --- a/Tools/freeze/test/freeze.py +++ b/Tools/freeze/test/freeze.py @@ -23,6 +23,7 @@ class UnsupportedError(Exception): def _run_cmd(cmd, cwd=None, verbose=True): + print(f'# {" ".join(shlex.quote(a) for a in cmd)}') proc = subprocess.run( cmd, cwd=cwd, @@ -51,14 +52,16 @@ def git_copy_repo(newroot, remote=None, *, verbose=True): remote = SRCDIR if os.path.exists(newroot): print(f'updating copied repo {newroot}...') - _run_cmd([GIT, 'pull', '-f', remote], newroot, verbose=verbose) + _run_cmd([GIT, '-C', newroot, 'reset'], verbose=verbose) + _run_cmd([GIT, '-C', newroot, 'checkout', '.'], verbose=verbose) + _run_cmd([GIT, '-C', newroot, 'pull', '-f', remote], verbose=verbose) else: print(f'copying repo into {newroot}...') _run_cmd([GIT, 'clone', remote, newroot], verbose=verbose) if os.path.exists(remote): # Copy over any uncommited files. reporoot = remote - text = _run_cmd([GIT, 'status', '-s'], reporoot, verbose=verbose) + text = _run_cmd([GIT, '-C', reporoot, 'status', '-s'], verbose=verbose) for line in text.splitlines(): _, _, relfile = line.strip().partition(' ') srcfile = os.path.join(reporoot, relfile) @@ -171,10 +174,9 @@ def build_python(builddir, *, verbose=True): srcdir = get_config_var(builddir, 'srcdir', fail=False) or SRCDIR if os.path.abspath(builddir) != srcdir: - _run_cmd([MAKE, 'clean'], srcdir, verbose=False) + _run_cmd([MAKE, '-C', srcdir, 'clean'], verbose=False) - print(f'building python in {builddir}...') - _run_cmd([MAKE, '-j'], builddir, verbose) + _run_cmd([MAKE, '-C', builddir, '-j'], verbose) return os.path.join(builddir, 'python') @@ -188,7 +190,7 @@ def install_python(builddir, *, verbose=True): prefix = get_prefix(builddir) print(f'installing python into {prefix}...') - _run_cmd([MAKE, '-j', 'install'], builddir, verbose) + _run_cmd([MAKE, '-C', builddir, '-j', 'install'], verbose) if not prefix: return None From 56399397b172f74a9351f046c32b638f56d24331 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 26 Oct 2021 11:05:20 -0600 Subject: [PATCH 09/25] Fix the relfile. --- Tools/freeze/test/freeze.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Tools/freeze/test/freeze.py b/Tools/freeze/test/freeze.py index b84d472462f18f..b64694c05120f9 100644 --- a/Tools/freeze/test/freeze.py +++ b/Tools/freeze/test/freeze.py @@ -64,6 +64,7 @@ def git_copy_repo(newroot, remote=None, *, verbose=True): text = _run_cmd([GIT, '-C', reporoot, 'status', '-s'], verbose=verbose) for line in text.splitlines(): _, _, relfile = line.strip().partition(' ') + relfile = relfile.strip() srcfile = os.path.join(reporoot, relfile) dstfile = os.path.join(newroot, relfile) os.makedirs(os.path.dirname(dstfile), exist_ok=True) From 172a259c794f7b95a436c79e43d9c9db04b6e9b7 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 26 Oct 2021 11:12:16 -0600 Subject: [PATCH 10/25] Only print some of the run commands. --- Tools/freeze/test/freeze.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Tools/freeze/test/freeze.py b/Tools/freeze/test/freeze.py index b64694c05120f9..b1293d5bfff32e 100644 --- a/Tools/freeze/test/freeze.py +++ b/Tools/freeze/test/freeze.py @@ -22,8 +22,9 @@ class UnsupportedError(Exception): """The operation isn't supported.""" -def _run_cmd(cmd, cwd=None, verbose=True): - print(f'# {" ".join(shlex.quote(a) for a in cmd)}') +def _run_cmd(cmd, cwd=None, verbose=True, showcmd=True): + if showcmd: + print(f'# {" ".join(shlex.quote(a) for a in cmd)}') proc = subprocess.run( cmd, cwd=cwd, @@ -103,9 +104,11 @@ def get_config_var(build, name, *, fail=True): if not os.path.isfile(python): return get_makefile_var(builddir, 'CONFIG_ARGS', fail=fail) - text = _run_cmd([python, '-c', - 'import sysconfig', - 'sysconfig.get_config_var("CONFIG_ARGS")']) + text = _run_cmd( + [python, '-c', + 'import sysconfig', 'sysconfig.get_config_var("CONFIG_ARGS")'], + showcmd=False, + ) return text @@ -121,6 +124,7 @@ def get_prefix(build=None): return _run_cmd( [build, '-c' 'import sys; print(sys.prefix)'], cwd=os.path.dirname(build), + showcmd=False, ) else: return get_makefile_var(build or '.', 'prefix', fail=False) From 875cbe33beb3eee308b4040bdc4bc9b089b9da97 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 26 Oct 2021 11:16:29 -0600 Subject: [PATCH 11/25] Actually print the config var. --- Tools/freeze/test/freeze.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/freeze/test/freeze.py b/Tools/freeze/test/freeze.py index b1293d5bfff32e..75418f520ec429 100644 --- a/Tools/freeze/test/freeze.py +++ b/Tools/freeze/test/freeze.py @@ -106,7 +106,7 @@ def get_config_var(build, name, *, fail=True): text = _run_cmd( [python, '-c', - 'import sysconfig', 'sysconfig.get_config_var("CONFIG_ARGS")'], + 'import sysconfig', 'print(sysconfig.get_config_var("CONFIG_ARGS"))'], showcmd=False, ) return text From 8e4e52e423d0e42e541ca7c45c1987eddd171593 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 26 Oct 2021 11:16:42 -0600 Subject: [PATCH 12/25] Fix a kwarg. --- Tools/freeze/test/freeze.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tools/freeze/test/freeze.py b/Tools/freeze/test/freeze.py index 75418f520ec429..ad6ddb0c700575 100644 --- a/Tools/freeze/test/freeze.py +++ b/Tools/freeze/test/freeze.py @@ -181,7 +181,7 @@ def build_python(builddir, *, verbose=True): if os.path.abspath(builddir) != srcdir: _run_cmd([MAKE, '-C', srcdir, 'clean'], verbose=False) - _run_cmd([MAKE, '-C', builddir, '-j'], verbose) + _run_cmd([MAKE, '-C', builddir, '-j'], verbose=verbose) return os.path.join(builddir, 'python') @@ -195,7 +195,7 @@ def install_python(builddir, *, verbose=True): prefix = get_prefix(builddir) print(f'installing python into {prefix}...') - _run_cmd([MAKE, '-C', builddir, '-j', 'install'], verbose) + _run_cmd([MAKE, '-C', builddir, '-j', 'install'], verbose=verbose) if not prefix: return None From bf95b315df94c280983e2ddfa30f80ec11dfe583 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 26 Oct 2021 11:25:00 -0600 Subject: [PATCH 13/25] Fall back to Makefile if sysconfig fails. --- Tools/freeze/test/freeze.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Tools/freeze/test/freeze.py b/Tools/freeze/test/freeze.py index ad6ddb0c700575..704acc0a7b17be 100644 --- a/Tools/freeze/test/freeze.py +++ b/Tools/freeze/test/freeze.py @@ -98,18 +98,22 @@ def get_makefile_var(builddir, name, *, fail=True): def get_config_var(build, name, *, fail=True): if os.path.isfile(build): python = build + builddir = os.path.dirname(build) else: builddir = build python = os.path.join(builddir, 'python') if not os.path.isfile(python): return get_makefile_var(builddir, 'CONFIG_ARGS', fail=fail) - text = _run_cmd( - [python, '-c', - 'import sysconfig', 'print(sysconfig.get_config_var("CONFIG_ARGS"))'], - showcmd=False, - ) - return text + try: + text = _run_cmd( + [python, '-c', + 'import sysconfig', 'print(sysconfig.get_config_var("CONFIG_ARGS"))'], + showcmd=False, + ) + return text + except subprocess.CalledProcessError: + return get_makefile_var(builddir, 'CONFIG_ARGS', fail=fail) def get_configure_args(build, *, fail=True): From a8e2cf0f2579ac70170fd40ffd97e7889841a896 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 26 Oct 2021 13:49:40 -0600 Subject: [PATCH 14/25] Do not clean up first if there is not Makefile. --- Tools/freeze/test/freeze.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Tools/freeze/test/freeze.py b/Tools/freeze/test/freeze.py index 704acc0a7b17be..c978ebe5c974ea 100644 --- a/Tools/freeze/test/freeze.py +++ b/Tools/freeze/test/freeze.py @@ -181,10 +181,11 @@ def build_python(builddir, *, verbose=True): if not builddir: builddir = '.' + print('building python...') srcdir = get_config_var(builddir, 'srcdir', fail=False) or SRCDIR if os.path.abspath(builddir) != srcdir: - _run_cmd([MAKE, '-C', srcdir, 'clean'], verbose=False) - + if os.path.exists(os.path.join(srcdir, 'Makefile')): + _run_cmd([MAKE, '-C', srcdir, 'clean'], verbose=False) _run_cmd([MAKE, '-C', builddir, '-j'], verbose=verbose) return os.path.join(builddir, 'python') From fab3dad0ce07b536f65b0c8a5965379c6c2d8d5f Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 26 Oct 2021 13:51:29 -0600 Subject: [PATCH 15/25] Clean up get_config_var(). --- Tools/freeze/test/freeze.py | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/Tools/freeze/test/freeze.py b/Tools/freeze/test/freeze.py index c978ebe5c974ea..0949e09de759a6 100644 --- a/Tools/freeze/test/freeze.py +++ b/Tools/freeze/test/freeze.py @@ -22,7 +22,7 @@ class UnsupportedError(Exception): """The operation isn't supported.""" -def _run_cmd(cmd, cwd=None, verbose=True, showcmd=True): +def _run_cmd(cmd, cwd=None, verbose=True, showcmd=True, showerr=True): if showcmd: print(f'# {" ".join(shlex.quote(a) for a in cmd)}') proc = subprocess.run( @@ -32,7 +32,8 @@ def _run_cmd(cmd, cwd=None, verbose=True, showcmd=True): text=True, ) if proc.returncode != 0: - print(proc.stderr, file=sys.stderr) + if showerr: + print(proc.stderr, file=sys.stderr) proc.check_returncode() return proc.stdout @@ -102,18 +103,19 @@ def get_config_var(build, name, *, fail=True): else: builddir = build python = os.path.join(builddir, 'python') - if not os.path.isfile(python): - return get_makefile_var(builddir, 'CONFIG_ARGS', fail=fail) - try: - text = _run_cmd( - [python, '-c', - 'import sysconfig', 'print(sysconfig.get_config_var("CONFIG_ARGS"))'], - showcmd=False, - ) - return text - except subprocess.CalledProcessError: - return get_makefile_var(builddir, 'CONFIG_ARGS', fail=fail) + if os.path.isfile(python): + try: + text = _run_cmd( + [python, '-c', + f'import sysconfig; print(sysconfig.get_config_var("{name}"))'], + showcmd=False, + showerr=False, + ) + return text + except subprocess.CalledProcessError: + pass + return get_makefile_var(builddir, name, fail=fail) def get_configure_args(build, *, fail=True): From ef579acce3349e7137b4f279f2dd0137310472e4 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 26 Oct 2021 13:52:02 -0600 Subject: [PATCH 16/25] Do not duplicate configure args. --- Tools/freeze/test/freeze.py | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/Tools/freeze/test/freeze.py b/Tools/freeze/test/freeze.py index 0949e09de759a6..a6905cacbf3317 100644 --- a/Tools/freeze/test/freeze.py +++ b/Tools/freeze/test/freeze.py @@ -47,6 +47,26 @@ def find_opt(args, name): return -1 +def ensure_opt(args, name, value): + opt = f'--{name}' + pos = find_opt(args, name) + if value is None: + if pos < 0: + args.append(opt) + else: + args[pos] = opt + elif pos < 0: + args.extend([opt, value]) + else: + arg = args[pos] + if arg == opt: + if pos == len(args) - 1: + raise NotImplementedError((args, opt)) + args[pos + 1] = value + else: + args[pos] = f'{opt}={value}' + + def git_copy_repo(newroot, remote=None, *, verbose=True): if not GIT: raise UnsupportedError('git') @@ -156,17 +176,17 @@ def configure_python(builddir=None, prefix=None, cachefile=None, args=None, *, cmd = [configure] if inherit: - oldargs = get_configure_args(builddir) + oldargs = get_configure_args(builddir, fail=False) if oldargs: cmd.extend(oldargs) if cachefile: if args and find_opt(args, 'cache-file') >= 0: raise ValueError('unexpected --cache-file') - cmd.extend(['--cache-file', os.path.abspath(cachefile)]) + ensure_opt(cmd, 'cache-file', os.path.abspath(cachefile)) if prefix: if args and find_opt(args, 'prefix') >= 0: raise ValueError('unexpected --prefix') - cmd.extend(['--prefix', os.path.abspath(prefix)]) + ensure_opt(cmd, 'prefix', os.path.abspath(prefix)) if args: cmd.extend(args) From 7338d60cce03cd3fc73a6b238e11f6fbfddf7128 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 26 Oct 2021 14:09:37 -0600 Subject: [PATCH 17/25] Clean up the copied repo first. --- Tools/freeze/test/freeze.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Tools/freeze/test/freeze.py b/Tools/freeze/test/freeze.py index a6905cacbf3317..ee8257479936c0 100644 --- a/Tools/freeze/test/freeze.py +++ b/Tools/freeze/test/freeze.py @@ -74,6 +74,9 @@ def git_copy_repo(newroot, remote=None, *, verbose=True): remote = SRCDIR if os.path.exists(newroot): print(f'updating copied repo {newroot}...') + if newroot == SRCDIR: + raise Exception('this probably isn\'t what you wanted') + _run_cmd([GIT, '-C', newroot, 'clean', '-d', '-f'], verbose=verbose) _run_cmd([GIT, '-C', newroot, 'reset'], verbose=verbose) _run_cmd([GIT, '-C', newroot, 'checkout', '.'], verbose=verbose) _run_cmd([GIT, '-C', newroot, 'pull', '-f', remote], verbose=verbose) @@ -87,10 +90,15 @@ def git_copy_repo(newroot, remote=None, *, verbose=True): for line in text.splitlines(): _, _, relfile = line.strip().partition(' ') relfile = relfile.strip() + isdir = relfile.endswith(os.path.sep) + relfile = relfile.rstrip(os.path.sep) srcfile = os.path.join(reporoot, relfile) dstfile = os.path.join(newroot, relfile) os.makedirs(os.path.dirname(dstfile), exist_ok=True) - shutil.copy2(srcfile, dstfile) + if isdir: + shutil.copytree(srcfile, dstfile, dirs_exist_ok=True) + else: + shutil.copy2(srcfile, dstfile) ################################## From f6a8755336512b0de57f47ccde21e3abf827e6c2 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 26 Oct 2021 14:12:22 -0600 Subject: [PATCH 18/25] Hide error text in get_config_var(). --- Tools/freeze/test/freeze.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Tools/freeze/test/freeze.py b/Tools/freeze/test/freeze.py index ee8257479936c0..97134b50d2ff99 100644 --- a/Tools/freeze/test/freeze.py +++ b/Tools/freeze/test/freeze.py @@ -139,6 +139,7 @@ def get_config_var(build, name, *, fail=True): f'import sysconfig; print(sysconfig.get_config_var("{name}"))'], showcmd=False, showerr=False, + verbose=False, ) return text except subprocess.CalledProcessError: From b12477eba0801b697d0aaef03feac261525d86fe Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 26 Oct 2021 15:34:16 -0600 Subject: [PATCH 19/25] Skip the freeze tests if the tool is missing. --- Lib/test/test_tools/test_freeze.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_tools/test_freeze.py b/Lib/test/test_tools/test_freeze.py index 0d044c8b87ea03..086f5a2b7e29e9 100644 --- a/Lib/test/test_tools/test_freeze.py +++ b/Lib/test/test_tools/test_freeze.py @@ -7,7 +7,8 @@ import textwrap import unittest -from . import imports_under_tool +from . import imports_under_tool, skip_if_missing +skip_if_missing('freeze') with imports_under_tool('freeze', 'test'): import freeze as helper From 2dd674df4b05c116f3fa1ba57c55b082a8238ebc Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 26 Oct 2021 15:37:22 -0600 Subject: [PATCH 20/25] Be explicit about the -j option. --- Tools/freeze/test/freeze.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/freeze/test/freeze.py b/Tools/freeze/test/freeze.py index 97134b50d2ff99..72970705f8320f 100644 --- a/Tools/freeze/test/freeze.py +++ b/Tools/freeze/test/freeze.py @@ -217,7 +217,7 @@ def build_python(builddir, *, verbose=True): if os.path.abspath(builddir) != srcdir: if os.path.exists(os.path.join(srcdir, 'Makefile')): _run_cmd([MAKE, '-C', srcdir, 'clean'], verbose=False) - _run_cmd([MAKE, '-C', builddir, '-j'], verbose=verbose) + _run_cmd([MAKE, '-C', builddir, '-j8'], verbose=verbose) return os.path.join(builddir, 'python') From cc68b8643251b2cdd515eb1909df772cae796352 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 26 Oct 2021 15:44:23 -0600 Subject: [PATCH 21/25] Do not use the -C option with git. --- Tools/freeze/test/freeze.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Tools/freeze/test/freeze.py b/Tools/freeze/test/freeze.py index 72970705f8320f..949bff1fb19470 100644 --- a/Tools/freeze/test/freeze.py +++ b/Tools/freeze/test/freeze.py @@ -76,17 +76,17 @@ def git_copy_repo(newroot, remote=None, *, verbose=True): print(f'updating copied repo {newroot}...') if newroot == SRCDIR: raise Exception('this probably isn\'t what you wanted') - _run_cmd([GIT, '-C', newroot, 'clean', '-d', '-f'], verbose=verbose) - _run_cmd([GIT, '-C', newroot, 'reset'], verbose=verbose) - _run_cmd([GIT, '-C', newroot, 'checkout', '.'], verbose=verbose) - _run_cmd([GIT, '-C', newroot, 'pull', '-f', remote], verbose=verbose) + _run_cmd([GIT, 'clean', '-d', '-f'], newroot, verbose) + _run_cmd([GIT, 'reset'], newroot, verbose) + _run_cmd([GIT, 'checkout', '.'], newroot, verbose) + _run_cmd([GIT, 'pull', '-f', remote], newroot, verbose) else: print(f'copying repo into {newroot}...') _run_cmd([GIT, 'clone', remote, newroot], verbose=verbose) if os.path.exists(remote): # Copy over any uncommited files. reporoot = remote - text = _run_cmd([GIT, '-C', reporoot, 'status', '-s'], verbose=verbose) + text = _run_cmd([GIT, 'status', '-s'], reporoot, verbose, showcmd=False) for line in text.splitlines(): _, _, relfile = line.strip().partition(' ') relfile = relfile.strip() From 9b6aee843fc67834b2e7b1ba2530ef18f0229da2 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 26 Oct 2021 17:42:14 -0600 Subject: [PATCH 22/25] Simplify. --- Lib/test/test_tools/test_freeze.py | 12 +- Tools/freeze/test/freeze.py | 282 ++++++++--------------------- 2 files changed, 79 insertions(+), 215 deletions(-) diff --git a/Lib/test/test_tools/test_freeze.py b/Lib/test/test_tools/test_freeze.py index 086f5a2b7e29e9..6f10c3bb4784bb 100644 --- a/Lib/test/test_tools/test_freeze.py +++ b/Lib/test/test_tools/test_freeze.py @@ -1,8 +1,5 @@ """Sanity-check tests for the "freeze" tool.""" -import os -import os.path -import subprocess import sys import textwrap import unittest @@ -22,13 +19,8 @@ def test_freeze_simple_script(self): print('running...') sys.exit(0) """) - outdir, scriptfile, python = helper.prepare( - script, - outoftree=True, - copy=True, - verbose=False, - ) + outdir, scriptfile, python = helper.prepare(script) - executable = helper.freeze(python, scriptfile, outdir, verbose=False) + executable = helper.freeze(python, scriptfile, outdir) text = helper.run(executable) self.assertEqual(text, 'running...') diff --git a/Tools/freeze/test/freeze.py b/Tools/freeze/test/freeze.py index 949bff1fb19470..18a5d27cebf2ed 100644 --- a/Tools/freeze/test/freeze.py +++ b/Tools/freeze/test/freeze.py @@ -4,14 +4,12 @@ import shlex import shutil import subprocess -import sys TESTS_DIR = os.path.dirname(__file__) TOOL_ROOT = os.path.dirname(TESTS_DIR) SRCDIR = os.path.dirname(os.path.dirname(TOOL_ROOT)) -CONFIGURE = os.path.join(SRCDIR, 'configure') MAKE = shutil.which('make') GIT = shutil.which('git') FREEZE = os.path.join(TOOL_ROOT, 'freeze.py') @@ -22,20 +20,20 @@ class UnsupportedError(Exception): """The operation isn't supported.""" -def _run_cmd(cmd, cwd=None, verbose=True, showcmd=True, showerr=True): - if showcmd: - print(f'# {" ".join(shlex.quote(a) for a in cmd)}') - proc = subprocess.run( +def _run_quiet(cmd, cwd=None): + #print(f'# {" ".join(shlex.quote(a) for a in cmd)}') + return subprocess.run( cmd, cwd=cwd, - capture_output=not verbose, + capture_output=True, text=True, + check=True, ) - if proc.returncode != 0: - if showerr: - print(proc.stderr, file=sys.stderr) - proc.check_returncode() - return proc.stdout + + +def _run_stdout(cmd, cwd=None): + proc = _run_quiet(cmd, cwd) + return proc.stdout.strip() def find_opt(args, name): @@ -67,51 +65,44 @@ def ensure_opt(args, name, value): args[pos] = f'{opt}={value}' -def git_copy_repo(newroot, remote=None, *, verbose=True): +def git_copy_repo(newroot, oldroot): if not GIT: raise UnsupportedError('git') - if not remote: - remote = SRCDIR + if os.path.exists(newroot): print(f'updating copied repo {newroot}...') if newroot == SRCDIR: raise Exception('this probably isn\'t what you wanted') - _run_cmd([GIT, 'clean', '-d', '-f'], newroot, verbose) - _run_cmd([GIT, 'reset'], newroot, verbose) - _run_cmd([GIT, 'checkout', '.'], newroot, verbose) - _run_cmd([GIT, 'pull', '-f', remote], newroot, verbose) + _run_quiet([GIT, 'clean', '-d', '-f'], newroot) + _run_quiet([GIT, 'reset'], newroot) + _run_quiet([GIT, 'checkout', '.'], newroot) + _run_quiet([GIT, 'pull', '-f', oldroot], newroot) else: print(f'copying repo into {newroot}...') - _run_cmd([GIT, 'clone', remote, newroot], verbose=verbose) - if os.path.exists(remote): - # Copy over any uncommited files. - reporoot = remote - text = _run_cmd([GIT, 'status', '-s'], reporoot, verbose, showcmd=False) - for line in text.splitlines(): - _, _, relfile = line.strip().partition(' ') - relfile = relfile.strip() - isdir = relfile.endswith(os.path.sep) - relfile = relfile.rstrip(os.path.sep) - srcfile = os.path.join(reporoot, relfile) - dstfile = os.path.join(newroot, relfile) - os.makedirs(os.path.dirname(dstfile), exist_ok=True) - if isdir: - shutil.copytree(srcfile, dstfile, dirs_exist_ok=True) - else: - shutil.copy2(srcfile, dstfile) + _run_quiet([GIT, 'clone', oldroot, newroot]) + + # Copy over any uncommited files. + text = _run_stdout([GIT, 'status', '-s'], oldroot) + for line in text.splitlines(): + _, _, relfile = line.strip().partition(' ') + relfile = relfile.strip() + isdir = relfile.endswith(os.path.sep) + relfile = relfile.rstrip(os.path.sep) + srcfile = os.path.join(oldroot, relfile) + dstfile = os.path.join(newroot, relfile) + os.makedirs(os.path.dirname(dstfile), exist_ok=True) + if isdir: + shutil.copytree(srcfile, dstfile, dirs_exist_ok=True) + else: + shutil.copy2(srcfile, dstfile) -################################## -# build queries - -def get_makefile_var(builddir, name, *, fail=True): +def get_makefile_var(builddir, name): regex = re.compile(rf'^{name} *=\s*(.*?)\s*$') filename = os.path.join(builddir, 'Makefile') try: infile = open(filename) except FileNotFoundError: - if fail: - raise # re-raise return None with infile: for line in infile: @@ -119,193 +110,80 @@ def get_makefile_var(builddir, name, *, fail=True): if m: value, = m.groups() return value or '' - if fail: - raise KeyError(f'{name!r} not in Makefile', name=name) return None -def get_config_var(build, name, *, fail=True): - if os.path.isfile(build): - python = build - builddir = os.path.dirname(build) - else: - builddir = build - python = os.path.join(builddir, 'python') - +def get_config_var(builddir, name): + python = os.path.join(builddir, 'python') if os.path.isfile(python): + cmd = [python, '-c', + f'import sysconfig; print(sysconfig.get_config_var("{name}"))'] try: - text = _run_cmd( - [python, '-c', - f'import sysconfig; print(sysconfig.get_config_var("{name}"))'], - showcmd=False, - showerr=False, - verbose=False, - ) - return text + return _run_stdout(cmd) except subprocess.CalledProcessError: pass - return get_makefile_var(builddir, name, fail=fail) + return get_makefile_var(builddir, name) -def get_configure_args(build, *, fail=True): - text = get_config_var(build, 'CONFIG_ARGS', fail=fail) - if not text: - return None - return shlex.split(text) - +################################## +# freezing -def get_prefix(build=None): - if build and os.path.isfile(build): - return _run_cmd( - [build, '-c' 'import sys; print(sys.prefix)'], - cwd=os.path.dirname(build), - showcmd=False, - ) - else: - return get_makefile_var(build or '.', 'prefix', fail=False) +def prepare(script=None, outdir=None): + if not outdir: + outdir = OUTDIR + os.makedirs(outdir, exist_ok=True) + # Write the script to disk. + if script: + scriptfile = os.path.join(outdir, 'app.py') + with open(scriptfile, 'w') as outfile: + outfile.write(script) -################################## -# building Python - -def configure_python(builddir=None, prefix=None, cachefile=None, args=None, *, - srcdir=None, - inherit=False, - verbose=True, - ): - if not builddir: - builddir = srcdir or SRCDIR - if not srcdir: - configure = os.path.join(builddir, 'configure') - if not os.path.isfile(configure): - srcdir = SRCDIR - configure = CONFIGURE - else: - configure = os.path.join(srcdir, 'configure') - - cmd = [configure] - if inherit: - oldargs = get_configure_args(builddir, fail=False) - if oldargs: - cmd.extend(oldargs) - if cachefile: - if args and find_opt(args, 'cache-file') >= 0: - raise ValueError('unexpected --cache-file') - ensure_opt(cmd, 'cache-file', os.path.abspath(cachefile)) - if prefix: - if args and find_opt(args, 'prefix') >= 0: - raise ValueError('unexpected --prefix') - ensure_opt(cmd, 'prefix', os.path.abspath(prefix)) - if args: - cmd.extend(args) + # Make a copy of the repo to avoid affecting the current build. + srcdir = os.path.join(outdir, 'cpython') + git_copy_repo(srcdir, SRCDIR) - print(f'configuring python in {builddir}...') + # We use an out-of-tree build (instead of srcdir). + builddir = os.path.join(outdir, 'python-build') os.makedirs(builddir, exist_ok=True) - _run_cmd(cmd, builddir, verbose) - return builddir + # Run configure. + print(f'configuring python in {builddir}...') + cmd = [ + os.path.join(srcdir, 'configure'), + *shlex.split(get_config_var(builddir, 'CONFIG_ARGS') or ''), + ] + ensure_opt(cmd, 'cache-file', os.path.join(outdir, 'python-config.cache')) + prefix = os.path.join(outdir, 'python-installation') + ensure_opt(cmd, 'prefix', prefix) + _run_quiet(cmd, builddir) -def build_python(builddir, *, verbose=True): if not MAKE: raise UnsupportedError('make') - if not builddir: - builddir = '.' - + # Build python. print('building python...') - srcdir = get_config_var(builddir, 'srcdir', fail=False) or SRCDIR - if os.path.abspath(builddir) != srcdir: - if os.path.exists(os.path.join(srcdir, 'Makefile')): - _run_cmd([MAKE, '-C', srcdir, 'clean'], verbose=False) - _run_cmd([MAKE, '-C', builddir, '-j8'], verbose=verbose) - - return os.path.join(builddir, 'python') - - -def install_python(builddir, *, verbose=True): - if not MAKE: - raise UnsupportedError('make') - - if not builddir: - builddir = '.' - prefix = get_prefix(builddir) + if os.path.exists(os.path.join(srcdir, 'Makefile')): + # Out-of-tree builds require a clean srcdir. + _run_quiet([MAKE, '-C', srcdir, 'clean']) + _run_quiet([MAKE, '-C', builddir, '-j8']) + # Install the build. print(f'installing python into {prefix}...') - _run_cmd([MAKE, '-C', builddir, '-j', 'install'], verbose=verbose) - - if not prefix: - return None - return os.path.join(prefix, 'bin', 'python3') - - -def ensure_python_installed(outdir, srcdir=None, *, - outoftree=True, - verbose=True, - ): - cachefile = os.path.join(outdir, 'python-config.cache') - prefix = os.path.join(outdir, 'python-installation') - if outoftree: - builddir = os.path.join(outdir, 'python-build') - else: - builddir = srcdir or SRCDIR - configure_python(builddir, prefix, cachefile, - srcdir=srcdir, inherit=True, verbose=verbose) - build_python(builddir, verbose=verbose) - return install_python(builddir, verbose=verbose) + _run_quiet([MAKE, '-C', builddir, '-j8', 'install']) + python = os.path.join(prefix, 'bin', 'python3') - -################################## -# freezing - -def prepare(script=None, outdir=None, *, - outoftree=True, - copy=False, - verbose=True, - ): - if not outdir: - outdir = OUTDIR - os.makedirs(outdir, exist_ok=True) - if script: - if script.splitlines()[0] == script and os.path.isfile(script): - scriptfile = script - else: - scriptfile = os.path.join(outdir, 'app.py') - with open(scriptfile, 'w') as outfile: - outfile.write(script) - else: - scriptfile = None - if copy: - srcdir = os.path.join(outdir, 'cpython') - git_copy_repo(srcdir, SRCDIR, verbose=verbose) - else: - srcdir = SRCDIR - python = ensure_python_installed(outdir, srcdir, - outoftree=outoftree, verbose=verbose) return outdir, scriptfile, python -def freeze(python, scriptfile, outdir=None, *, verbose=True): +def freeze(python, scriptfile, outdir): if not MAKE: raise UnsupportedError('make') - if not outdir: - outdir = OUTDIR - print(f'freezing {scriptfile}...') os.makedirs(outdir, exist_ok=True) - subprocess.run( - [python, FREEZE, '-o', outdir, scriptfile], - cwd=outdir, - capture_output=not verbose, - check=True, - ) - - subprocess.run( - [MAKE], - cwd=os.path.dirname(scriptfile), - capture_output=not verbose, - check=True, - ) + _run_quiet([python, FREEZE, '-o', outdir, scriptfile], outdir) + _run_quiet([MAKE, '-C', os.path.dirname(scriptfile)]) name = os.path.basename(scriptfile).rpartition('.')[0] executable = os.path.join(outdir, name) @@ -313,10 +191,4 @@ def freeze(python, scriptfile, outdir=None, *, verbose=True): def run(executable): - proc = subprocess.run( - [executable], - capture_output=True, - text=True, - check=True, - ) - return proc.stdout.strip() + return _run_stdout([executable]) From 4b315b0dd9b899574a52a09cb3057b30fb9175d2 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 26 Oct 2021 18:04:21 -0600 Subject: [PATCH 23/25] Skip the test if running with "-u -cpu". --- Lib/test/test_tools/test_freeze.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Lib/test/test_tools/test_freeze.py b/Lib/test/test_tools/test_freeze.py index 6f10c3bb4784bb..2b057f350dbae4 100644 --- a/Lib/test/test_tools/test_freeze.py +++ b/Lib/test/test_tools/test_freeze.py @@ -4,6 +4,8 @@ import textwrap import unittest +from test import support + from . import imports_under_tool, skip_if_missing skip_if_missing('freeze') with imports_under_tool('freeze', 'test'): @@ -14,6 +16,8 @@ class TestFreeze(unittest.TestCase): def test_freeze_simple_script(self): + if support.use_resources: + support.requires('cpu', 'test re-builds Python out-of-tree') script = textwrap.dedent(""" import sys print('running...') From aa56cfc6462a23f8f173c788412e0b64d186b2d0 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 27 Oct 2021 08:45:50 -0600 Subject: [PATCH 24/25] Do not run the test on buildbots. --- Lib/test/test_tools/test_freeze.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_tools/test_freeze.py b/Lib/test/test_tools/test_freeze.py index 2b057f350dbae4..c1810804c83592 100644 --- a/Lib/test/test_tools/test_freeze.py +++ b/Lib/test/test_tools/test_freeze.py @@ -1,11 +1,10 @@ """Sanity-check tests for the "freeze" tool.""" +import os import sys import textwrap import unittest -from test import support - from . import imports_under_tool, skip_if_missing skip_if_missing('freeze') with imports_under_tool('freeze', 'test'): @@ -13,11 +12,11 @@ @unittest.skipIf(sys.platform.startswith('win'), 'not supported on Windows') +@unittest.skipIf(os.environ.get('USER') == 'buildbot', + 'not all buildbots have enough space') class TestFreeze(unittest.TestCase): def test_freeze_simple_script(self): - if support.use_resources: - support.requires('cpu', 'test re-builds Python out-of-tree') script = textwrap.dedent(""" import sys print('running...') From 4c1810312372515b5a51c87042c4aabd29559b99 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 27 Oct 2021 09:08:26 -0600 Subject: [PATCH 25/25] Add test.support.skip_if_buildbot(). --- Lib/test/support/__init__.py | 11 +++++++++++ Lib/test/test_tools/test_freeze.py | 6 +++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 85fd74126b5f47..fc3c99ec0ecc35 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -372,6 +372,17 @@ def wrapper(*args, **kw): return decorator +def skip_if_buildbot(reason=None): + """Decorator raising SkipTest if running on a buildbot.""" + if not reason: + reason = 'not suitable for buildbots' + if sys.platform == 'win32': + isbuildbot = os.environ.get('USERNAME') == 'Buildbot' + else: + isbuildbot = os.environ.get('USER') == 'buildbot' + return unittest.skipIf(isbuildbot, reason) + + def system_must_validate_cert(f): """Skip the test on TLS certificate validation failures.""" @functools.wraps(f) diff --git a/Lib/test/test_tools/test_freeze.py b/Lib/test/test_tools/test_freeze.py index c1810804c83592..392a776f042e4f 100644 --- a/Lib/test/test_tools/test_freeze.py +++ b/Lib/test/test_tools/test_freeze.py @@ -1,10 +1,11 @@ """Sanity-check tests for the "freeze" tool.""" -import os import sys import textwrap import unittest +from test import support + from . import imports_under_tool, skip_if_missing skip_if_missing('freeze') with imports_under_tool('freeze', 'test'): @@ -12,8 +13,7 @@ @unittest.skipIf(sys.platform.startswith('win'), 'not supported on Windows') -@unittest.skipIf(os.environ.get('USER') == 'buildbot', - 'not all buildbots have enough space') +@support.skip_if_buildbot('not all buildbots have enough space') class TestFreeze(unittest.TestCase): def test_freeze_simple_script(self):