summaryrefslogtreecommitdiffstats
path: root/Tools/freeze
diff options
context:
space:
mode:
authorEric Snow <ericsnowcurrently@gmail.com>2021-10-28 16:14:37 (GMT)
committerGitHub <noreply@github.com>2021-10-28 16:14:37 (GMT)
commit13d9205f4057eeeef80a25d410ad123876dc60cd (patch)
tree18cbd8603e3f3a29d9f40a0bd8a4648356c205c9 /Tools/freeze
parent7f61d9d84843e3445f62eb00c47902f0daa30a72 (diff)
downloadcpython-13d9205f4057eeeef80a25d410ad123876dc60cd.zip
cpython-13d9205f4057eeeef80a25d410ad123876dc60cd.tar.gz
cpython-13d9205f4057eeeef80a25d410ad123876dc60cd.tar.bz2
bpo-45629: Add a test for the "freeze" tool. (gh-29222)
The "freeze" tool has been part of the repo for a long time. However, it hasn't had any tests in the test suite to guard against regressions. We add such a test here. This is especially important as there has been a lot of change recently related to frozen modules, with more to come. Note that as part of the test we build Python out-of-tree and install it in a temp dir. https://bugs.python.org/issue45629
Diffstat (limited to 'Tools/freeze')
-rw-r--r--Tools/freeze/test/freeze.py194
1 files changed, 194 insertions, 0 deletions
diff --git a/Tools/freeze/test/freeze.py b/Tools/freeze/test/freeze.py
new file mode 100644
index 0000000..18a5d27
--- /dev/null
+++ b/Tools/freeze/test/freeze.py
@@ -0,0 +1,194 @@
+import os
+import os.path
+import re
+import shlex
+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))
+
+MAKE = shutil.which('make')
+GIT = shutil.which('git')
+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_quiet(cmd, cwd=None):
+ #print(f'# {" ".join(shlex.quote(a) for a in cmd)}')
+ return subprocess.run(
+ cmd,
+ cwd=cwd,
+ capture_output=True,
+ text=True,
+ check=True,
+ )
+
+
+def _run_stdout(cmd, cwd=None):
+ proc = _run_quiet(cmd, cwd)
+ return proc.stdout.strip()
+
+
+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 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, oldroot):
+ if not GIT:
+ raise UnsupportedError('git')
+
+ if os.path.exists(newroot):
+ print(f'updating copied repo {newroot}...')
+ if newroot == SRCDIR:
+ raise Exception('this probably isn\'t what you wanted')
+ _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_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)
+
+
+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:
+ return None
+ with infile:
+ for line in infile:
+ m = regex.match(line)
+ if m:
+ value, = m.groups()
+ return value or ''
+ return None
+
+
+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:
+ return _run_stdout(cmd)
+ except subprocess.CalledProcessError:
+ pass
+ return get_makefile_var(builddir, name)
+
+
+##################################
+# freezing
+
+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)
+
+ # Make a copy of the repo to avoid affecting the current build.
+ srcdir = os.path.join(outdir, 'cpython')
+ git_copy_repo(srcdir, SRCDIR)
+
+ # We use an out-of-tree build (instead of srcdir).
+ builddir = os.path.join(outdir, 'python-build')
+ os.makedirs(builddir, exist_ok=True)
+
+ # 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)
+
+ if not MAKE:
+ raise UnsupportedError('make')
+
+ # Build python.
+ print('building python...')
+ 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_quiet([MAKE, '-C', builddir, '-j8', 'install'])
+ python = os.path.join(prefix, 'bin', 'python3')
+
+ return outdir, scriptfile, python
+
+
+def freeze(python, scriptfile, outdir):
+ if not MAKE:
+ raise UnsupportedError('make')
+
+ print(f'freezing {scriptfile}...')
+ os.makedirs(outdir, exist_ok=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)
+ return executable
+
+
+def run(executable):
+ return _run_stdout([executable])