Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
a2e4494
Ignore the tests outdir.
ericsnowcurrently Oct 26, 2021
9675b69
Add test helpers for the "freeze" tool.
ericsnowcurrently Oct 26, 2021
a34dca4
Add a test for the "freeze" tool.
ericsnowcurrently Oct 26, 2021
77f334d
Create the outdir.
ericsnowcurrently Oct 26, 2021
561f8e1
Build in the source tree for tests.
ericsnowcurrently Oct 26, 2021
bd54ddd
Copy the repo during tests.
ericsnowcurrently Oct 26, 2021
5d0ec9e
Force the git pull.
ericsnowcurrently Oct 26, 2021
b879a3c
Show the commands.
ericsnowcurrently Oct 26, 2021
5639939
Fix the relfile.
ericsnowcurrently Oct 26, 2021
172a259
Only print some of the run commands.
ericsnowcurrently Oct 26, 2021
875cbe3
Actually print the config var.
ericsnowcurrently Oct 26, 2021
8e4e52e
Fix a kwarg.
ericsnowcurrently Oct 26, 2021
bf95b31
Fall back to Makefile if sysconfig fails.
ericsnowcurrently Oct 26, 2021
a8e2cf0
Do not clean up first if there is not Makefile.
ericsnowcurrently Oct 26, 2021
fab3dad
Clean up get_config_var().
ericsnowcurrently Oct 26, 2021
ef579ac
Do not duplicate configure args.
ericsnowcurrently Oct 26, 2021
7338d60
Clean up the copied repo first.
ericsnowcurrently Oct 26, 2021
f6a8755
Hide error text in get_config_var().
ericsnowcurrently Oct 26, 2021
b12477e
Skip the freeze tests if the tool is missing.
ericsnowcurrently Oct 26, 2021
2dd674d
Be explicit about the -j option.
ericsnowcurrently Oct 26, 2021
cc68b86
Do not use the -C option with git.
ericsnowcurrently Oct 26, 2021
9b6aee8
Simplify.
ericsnowcurrently Oct 26, 2021
4b315b0
Skip the test if running with "-u -cpu".
ericsnowcurrently Oct 27, 2021
aa56cfc
Do not run the test on buildbots.
ericsnowcurrently Oct 27, 2021
4c18103
Add test.support.skip_if_buildbot().
ericsnowcurrently Oct 27, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Simplify.
  • Loading branch information
ericsnowcurrently committed Oct 26, 2021
commit 9b6aee843fc67834b2e7b1ba2530ef18f0229da2
12 changes: 2 additions & 10 deletions Lib/test/test_tools/test_freeze.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
"""Sanity-check tests for the "freeze" tool."""

import os
import os.path
import subprocess
import sys
import textwrap
import unittest
Expand All @@ -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...')
282 changes: 77 additions & 205 deletions Tools/freeze/test/freeze.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -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):
Expand Down Expand Up @@ -67,256 +65,130 @@ 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:
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 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)
return executable


def run(executable):
proc = subprocess.run(
[executable],
capture_output=True,
text=True,
check=True,
)
return proc.stdout.strip()
return _run_stdout([executable])