cpython/Tools/freeze/test/freeze.py

167 lines
4.7 KiB
Python
Raw Normal View History

import os
import os.path
import shlex
import shutil
import subprocess
import sysconfig
from test import support
TESTS_DIR = os.path.dirname(__file__)
TOOL_ROOT = os.path.dirname(TESTS_DIR)
SRCDIR = os.path.abspath(sysconfig.get_config_var('srcdir'))
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_quiet(cmd, cwd=None):
#print(f'# {" ".join(shlex.quote(a) for a in cmd)}')
try:
return subprocess.run(
cmd,
cwd=cwd,
capture_output=True,
text=True,
check=True,
)
except subprocess.CalledProcessError as err:
# Don't be quiet if things fail
print(f"{err.__class__.__name__}: {err}")
print("--- STDOUT ---")
print(err.stdout)
print("--- STDERR ---")
print(err.stderr)
print("---- END ----")
raise
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 copy_source_tree(newroot, oldroot):
print(f'copying the source tree from {oldroot} to {newroot}...')
if os.path.exists(newroot):
if newroot == SRCDIR:
raise Exception('this probably isn\'t what you wanted')
shutil.rmtree(newroot)
shutil.copytree(oldroot, newroot, ignore=support.copy_python_src_ignore)
if os.path.exists(os.path.join(newroot, 'Makefile')):
_run_quiet([MAKE, 'clean'], newroot)
##################################
# 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')
print(f'creating the script to be frozen at {scriptfile}')
with open(scriptfile, 'w', encoding='utf-8') as outfile:
outfile.write(script)
# Make a copy of the repo to avoid affecting the current build
# (e.g. changing PREFIX).
srcdir = os.path.join(outdir, 'cpython')
copy_source_tree(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}...')
config_args = shlex.split(sysconfig.get_config_var('CONFIG_ARGS') or '')
cmd = [os.path.join(srcdir, 'configure'), *config_args]
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')
cores = os.cpu_count()
if cores and cores >= 3:
# this test is most often run as part of the whole suite with a lot
# of other tests running in parallel, from 1-2 vCPU systems up to
# people's NNN core beasts. Don't attempt to use it all.
parallel = f'-j{cores*2//3}'
else:
parallel = '-j2'
# Build python.
print(f'building python {parallel=} in {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, parallel])
# Install the build.
print(f'installing python into {prefix}...')
_run_quiet([MAKE, '-C', builddir, '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)
# Use -E to ignore PYTHONSAFEPATH
_run_quiet([python, '-E', 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])