#!/usr/bin/env python3 """ Create rpm packages of pyi2ncommon for various python versions. Usage:: make_dist.py [RELEASE [PY_VERSION_1 [PY_VERSION_2 ...]]] RELEASE defaults to current git commit hash, default versions to build for are 3.6 for avocado and 3.3 for i2n system. All python versions used must be installed on local machine. Calls setup.py with different args. Adds %check section to .spec file. Runs unittests first. This script relies on one feature I accidentally stumbled over: it appears that setting install-lib to /usr/lib/python{VERSION}/site-packages is translated by setup.py to installation requirement python(abi) = {VERSION} .. codeauthor:: Intra2net AG """ import os from os.path import join, isfile import sys from subprocess import call, check_output from tempfile import mkstemp from configparser import ConfigParser from glob import iglob import time INSTALL_DIR_PATTERN = '/usr/lib/python{}/site-packages' RPM_OPTIONS = dict(packager='Intra2net', group='Intra2net', vendor='Intra2net AG') DIST_DIR = 'dist' SPEC_FILE = join(DIST_DIR, 'pyi2ncommon.spec') CFG_FILE = 'setup.cfg' SPEC_START = b""" # spec file created automatically by make_dist.sh -- do not modify! """ SPEC_CHECK_SECTION = b"""%check PYTHONPATH=./src:$PYTHONPATH && python3 -m unittest discover test """ def check_py_version(py_version): """Test that python version is installed.""" try: result = call(['python{}'.format(py_version), '--version']) except Exception: result = 1 if result != 0: raise RuntimeError('Python version {} not installed, ' 'run {} VERSION'.format(py_version, sys.argv[0])) def run_unittests(py_version): """Run unittests with given python version. Re-Run with LANG=C.""" # Run twice: first with environment copied from call (env=None) and one # in empoverished environment without unicode capability for env in None, dict(LANG='C', PATH=os.environ['PATH']): try: print('Running unittests with python {} and env={}' .format(py_version, env)) result = call(['python{}'.format(py_version), '-m', 'unittest', 'discover'], env=env) except Exception as exc: raise RuntimeError('Unittests with python {} and env={} failed. {}' .format(py_version, env, exc)) if result != 0: raise RuntimeError('Unittests with python {} and env={} failed ' '(command returned {}).' .format(py_version, env, result)) def run_setup(command, cmd_line_args=None, install_options=None, need_zip35=False): """ Run python3 setup.py with command and options. Need this function since setup.py bdist_rpm does not accept --prefix and --install-lib options. Need to write them into cfg file from which they are then read ... Grrr! """ config = ConfigParser() rpm_options = dict(RPM_OPTIONS.items()) if need_zip35: rpm_options['requires'] = 'python3-zipfile35' if rpm_options: config['bdist_rpm'] = rpm_options if install_options: config['install'] = install_options need_delete = False try: with open(CFG_FILE, mode='xt') as write_handle: need_delete = True config.write(write_handle) cmd = ['python3', 'setup.py', command] if cmd_line_args: cmd += cmd_line_args if call(cmd) != 0: raise RuntimeError('Running setup.py failed (cmd: {})' .format(' '.join(cmd))) finally: if need_delete: os.unlink(CFG_FILE) need_delete = False def create_rpm(py_version, release): """Create rpm that will install pyi2ncommon for given python version.""" print('Creating RPM for python version {}'.format(py_version)) # define options for where to install library; # It appears this is automatically translated into requirement # python(abi) = version ! install_options = {'prefix': '/'} install_options['install-lib'] = INSTALL_DIR_PATTERN.format(py_version) # if py_version is smaller than 3.5, need zipfile35 as extra dependency need_zip35 = py_version.startswith('2') or py_version.startswith('3.0') or\ py_version.startswith('3.1') or py_version.startswith('3.2') or \ py_version.startswith('3.3') or py_version.startswith('3.4') # create rpm start_time = time.time() run_setup('bdist_rpm', install_options=install_options, cmd_line_args=['--release', release], need_zip35=need_zip35) # find rpm and rename it newest_names = [] newest_time = 0 for filename in iglob(join(DIST_DIR, 'pyi2ncommon-*.noarch.rpm')): filetime = os.stat(filename).st_mtime if filetime > newest_time: newest_time = filetime newest_names = [filename, ] elif filetime == newest_time: newest_names.append(filename) if not newest_names: raise RuntimeError('No pyi2ncommon rpm file found in {}' .format(DIST_DIR)) elif newest_time < start_time: raise RuntimeError('Newest pyi2ncommon rpm file in {} is too old' .format(DIST_DIR)) elif len(newest_names) > 1: raise RuntimeError('Multiple newest pyi2ncommon rpm files: {}' .format(newest_names)) newest_name = newest_names[0] mod_name = newest_name[:-11] + '.py' + py_version.replace('.', '') + \ newest_name[-11:] os.rename(newest_name, mod_name) return mod_name def create_spec(release): """Create .spec file and modify it.""" print('adapting spec file') # create spec run_setup('bdist_rpm', cmd_line_args=['--spec-only', '--release', release]) # adapt temp_handle = None temp_name = None try: temp_handle, temp_file = mkstemp(dir=DIST_DIR, text=False, prefix='pyi2ncommon-adapt-', suffix='.spec') os.write(temp_handle, SPEC_START) did_write_check = False with open(SPEC_FILE, 'rb') as reader: for line in reader: print('spec file: {}'.format(line.rstrip())) if line.strip() == b'%install': os.write(temp_handle, SPEC_CHECK_SECTION) did_write_check = True os.write(temp_handle, line) os.close(temp_handle) temp_handle = None if not did_write_check: raise RuntimeError('Could not find place to write %check section') # replace os.unlink(SPEC_FILE) os.rename(temp_file, SPEC_FILE) temp_file = None finally: # clean up temp if temp_handle is not None: os.close(temp_handle) if temp_name is not None and isfile(temp_file): os.unlink(temp_file) return SPEC_FILE def main(): """ Main function, called when running file as script see module doc for more info """ release = None py_versions = ['3.7', ] if len(sys.argv) > 1: if sys.argv[1] in ('--help', '-h'): print('make_dist.py [releasename [py-ver1 [py-ver2...]]]') print(' e.g.: make_dist.py featuretest 3.7') return 2 release = sys.argv[1].replace('-', '_') else: release = check_output(['git', 'log', '--pretty=format:%h', '-n', '1'], universal_newlines=True) if len(sys.argv) > 2: py_versions = sys.argv[2:] files_created = [] for py_version in py_versions: check_py_version(py_version) run_unittests(py_version) files_created.append(create_rpm(py_version, release)) files_created.append(create_spec(release)) for filename in files_created: print('Created {}'.format(filename)) print('(+ probably source rpm and tar.gz)') return 0 if __name__ == '__main__': sys.exit(main())