From 5e08ec603a3684ff3b5e83bd2e6c5d3d33a90a39 Mon Sep 17 00:00:00 2001 From: John Richards Date: Sun, 10 Oct 2021 18:34:11 -0400 Subject: [PATCH] Adds container support to pymultic Using the '-c' argument will - if possible - fetch and use a container for each version of Python specified in the arguments list. --- scripts/pymultic | 139 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 96 insertions(+), 43 deletions(-) diff --git a/scripts/pymultic b/scripts/pymultic index c595548..12141c1 100755 --- a/scripts/pymultic +++ b/scripts/pymultic @@ -33,12 +33,17 @@ PYVERS = { '3.7': '3.7.10', '3.8': '3.8.9', '3.9': '3.9.4', + '3.10': '3.10.0', } OLD_PYTHONS = ('1.0', '1.1', '1.2', '1.3', '1.4', '1.5') OLD_PYURL = 'https://legacy.python.org/download/releases/src' PYURL = 'https://www.python.org/ftp/python' +# Minimum version of Python with a pre-built container. +PYVER_CONTAINER_MIN = '2.7' +USE_CONTAINERS = False + def fetch_python(snekdir, version): realver = PYVERS[version] @@ -122,58 +127,19 @@ def acquire_python(snekdir, version): return pyexe -if len(sys.argv) < 2: - print('Usage: {} [versions] input.py'.format(sys.argv[0])) - print('Compile input.py for one or more python versions') - print('Output is written to input..pyc for each version successfully compiled') - print() - print('Version is X.Y (e.g. 3.4), not including the patch version') - sys.exit(1) - -RE_PYVER = re.compile(r'\d\.\d') - -pythons = [] -infile = None -for arg in sys.argv[1:]: - if RE_PYVER.match(arg): - if arg in PYVERS.keys(): - pythons.append(arg) - else: - print('Unknown Python version: {}'.format(arg)) - sys.exit(1) - elif arg.startswith('-'): - print("WARNING: Unrecognized argument '{}'".format(arg)) - else: - infile = arg - -if infile is None: - print('No input file specified') - sys.exit(1) -elif not os.path.exists(infile): - print('Error: Input file {} does not exist'.format(infile)) - sys.exit(1) - -if len(pythons) == 0: - print('At least one Python version is required') - sys.exit(1) - -snekdir = os.path.dirname(os.path.realpath(__file__)) -result = 0 -for ver in pythons: +def local_compile(snekdir, ver, infile): pyexe = acquire_python(snekdir, ver) proc = subprocess.Popen([pyexe, '-c', 'import sys; print(sys.version)'], stdout=subprocess.PIPE) out, _ = proc.communicate() if proc.returncode != 0: print('Could not determine Python version for {}'.format(ver)) - result = 1 - continue + return None bcver = str(out, 'iso-8859-1').split(' ', 1)[0] if not bcver.startswith(ver): print('Python {} reported itself as version {}!'.format(ver, bcver)) - result = 1 - continue + return None if infile.endswith('.py'): outfile = os.path.basename(infile)[:-3] @@ -210,7 +176,94 @@ for ver in pythons: .format(infile, outfile)]) proc.communicate() - if not os.path.exists(outfile): + return outfile + + +def container_compile(snekdir, ver, infile): + if ver < PYVER_CONTAINER_MIN: + print('Container compilation requires a Python version >= {}'.format(PYVER_CONTAINER_MIN)) + return None + + if shutil.which('docker') is None: + print('Cannot find docker in $PATH') + return None + + fullver = PYVERS[ver] + + if infile.endswith('.py'): + outfile = os.path.basename(infile)[:-3] + else: + outfile = os.path.basename(infile) + outfile += '.{}.pyc'.format(ver) + if os.path.exists(outfile): + os.unlink(outfile) + + print('*** Compiling for Python {}'.format(fullver)) + # The easy way + proc = subprocess.Popen(['docker', 'run', '--privileged', '--rm', '--name', '{}-{}'.format(infile, ver), + '-v', '{}:/src'.format(snekdir), '-w', '/src', 'python:{}'.format(fullver), + 'python', '-c', "import py_compile; py_compile.compile('{}', '{}')".format(infile, outfile)]) + proc.communicate() + + return outfile + + +if len(sys.argv) < 2: + print('Usage: {} [-c] [versions] input.py'.format(sys.argv[0])) + print('Compile input.py for one or more python versions') + print() + print('-c\tuse prebuilt containers for running different versions of Python (only available for versions >= {})' + .format(PYVER_CONTAINER_MIN)) + print() + print('Output is written to input..pyc for each version successfully compiled') + print() + print('Version is X.Y (e.g. 3.4), not including the patch version') + sys.exit(1) + +RE_PYVER = re.compile(r'\d\.\d') + +pythons = [] +infile = None +for arg in sys.argv[1:]: + if RE_PYVER.match(arg): + if arg in PYVERS.keys(): + pythons.append(arg) + else: + print('Unknown Python version: {}'.format(arg)) + sys.exit(1) + elif arg == '-c': + USE_CONTAINERS = True + elif arg.startswith('-'): + print("WARNING: Unrecognized argument '{}'".format(arg)) + else: + infile = arg + +if infile is None: + print('No input file specified') + sys.exit(1) +elif not os.path.exists(infile): + print('Error: Input file {} does not exist'.format(infile)) + sys.exit(1) + +if len(pythons) == 0: + print('At least one Python version is required') + sys.exit(1) + +snekdir = os.path.dirname(os.path.realpath(__file__)) +result = 0 +for ver in pythons: + compile_with_container = USE_CONTAINERS + if USE_CONTAINERS and ver < PYVER_CONTAINER_MIN: + print('Warning: No supported container for {} - using local build'.format(ver)) + compile_with_container = False + + outfile = None + if compile_with_container: + outfile = container_compile(snekdir, ver, infile) + else: + outfile = local_compile(snekdir, ver, infile) + + if outfile is None or not os.path.exists(outfile): result = 1 sys.exit(result)