Fix typo in Group CnfVar builder
[pyi2ncommon] / make_dist.py
CommitLineData
4ce926b3
CH
1#!/usr/bin/env python3
2
3"""
12c2db72 4Create rpm packages of pyi2ncommon for various python versions.
4ce926b3 5
783a19b7 6Usage::
4ce926b3 7
783a19b7 8 make_dist.py [RELEASE [PY_VERSION_1 [PY_VERSION_2 ...]]]
12c2db72 9
783a19b7
CH
10RELEASE defaults to current git commit hash, default versions to build for are
113.6 for avocado and 3.3 for i2n system. All python versions used must be
12installed on local machine.
13
14Calls setup.py with different args. Adds %check section to .spec file. Runs
15unittests first.
4ce926b3 16
783a19b7
CH
17This script relies on one feature I accidentally stumbled over: it appears that
18setting install-lib to /usr/lib/python{VERSION}/site-packages is translated by
19setup.py to installation requirement python(abi) = {VERSION}
36eadbed 20
4ce926b3
CH
21.. codeauthor:: Intra2net AG <info@intra2net.com>
22"""
23
24import os
25from os.path import join, isfile
26import sys
e98c622f 27from subprocess import call, check_output
4ce926b3
CH
28from tempfile import mkstemp
29from configparser import ConfigParser
30from glob import iglob
36eadbed 31import time
4ce926b3
CH
32
33INSTALL_DIR_PATTERN = '/usr/lib/python{}/site-packages'
34
35RPM_OPTIONS = dict(packager='Intra2net', group='Intra2net',
40a4ce5f 36 vendor='Intra2net AG')
4ce926b3
CH
37
38DIST_DIR = 'dist'
39SPEC_FILE = join(DIST_DIR, 'pyi2ncommon.spec')
40
41CFG_FILE = 'setup.cfg'
42
43SPEC_START = b"""
44# spec file created automatically by make_dist.sh -- do not modify!
45
46"""
47
48SPEC_CHECK_SECTION = b"""%check
49PYTHONPATH=./src:$PYTHONPATH && python3 -m unittest discover test
50
51"""
52
783a19b7
CH
53
54def check_py_version(py_version):
55 """Test that python version is installed."""
56 try:
57 result = call(['python{}'.format(py_version), '--version'])
58 except Exception:
59 result = 1
60 if result != 0:
61 raise RuntimeError('Python version {} not installed, '
62 'run {} VERSION'.format(py_version, sys.argv[0]))
63
64
65def run_unittests(py_version):
66 """Run unittests with given python version. Re-Run with LANG=C."""
67 # Run twice: first with environment copied from call (env=None) and one
68 # in empoverished environment without unicode capability
69 for env in None, dict(LANG='C', PATH=os.environ['PATH']):
70 try:
71 print('Running unittests with python {} and env={}'
72 .format(py_version, env))
73 result = call(['python{}'.format(py_version), '-m', 'unittest',
74 'discover'], env=env)
75 except Exception as exc:
76 raise RuntimeError('Unittests with python {} and env={} failed. {}'
77 .format(py_version, env, exc))
78 if result != 0:
79 raise RuntimeError('Unittests with python {} and env={} failed '
80 '(command returned {}).'
81 .format(py_version, env, result))
82
83
40a4ce5f
CH
84def run_setup(command, cmd_line_args=None, install_options=None,
85 need_zip35=False):
4ce926b3
CH
86 """
87 Run python3 setup.py with command and options.
88
89 Need this function since setup.py bdist_rpm does not accept --prefix and
90 --install-lib options. Need to write them into cfg file from which they
91 are then read ... Grrr!
92 """
93 config = ConfigParser()
40a4ce5f
CH
94 rpm_options = dict(RPM_OPTIONS.items())
95 if need_zip35:
96 rpm_options['requires'] = 'python3-zipfile35'
97 if rpm_options:
98 config['bdist_rpm'] = rpm_options
4ce926b3
CH
99 if install_options:
100 config['install'] = install_options
101 need_delete = False
102 try:
103 with open(CFG_FILE, mode='xt') as write_handle:
104 need_delete = True
105 config.write(write_handle)
106 cmd = ['python3', 'setup.py', command]
107 if cmd_line_args:
108 cmd += cmd_line_args
109 if call(cmd) != 0:
110 raise RuntimeError('Running setup.py failed (cmd: {})'
111 .format(' '.join(cmd)))
112 finally:
113 if need_delete:
114 os.unlink(CFG_FILE)
115 need_delete = False
116
117
e98c622f 118def create_rpm(py_version, release):
4ce926b3
CH
119 """Create rpm that will install pyi2ncommon for given python version."""
120 print('Creating RPM for python version {}'.format(py_version))
121
122 # define options for where to install library;
123 # It appears this is automatically translated into requirement
124 # python(abi) = version !
125 install_options = {'prefix': '/'}
126 install_options['install-lib'] = INSTALL_DIR_PATTERN.format(py_version)
127
40a4ce5f
CH
128 # if py_version is smaller than 3.5, need zipfile35 as extra dependency
129 need_zip35 = py_version.startswith('2') or py_version.startswith('3.0') or\
130 py_version.startswith('3.1') or py_version.startswith('3.2') or \
131 py_version.startswith('3.3') or py_version.startswith('3.4')
132
4ce926b3 133 # create rpm
36eadbed 134 start_time = time.time()
40a4ce5f 135 run_setup('bdist_rpm', install_options=install_options,
e98c622f 136 cmd_line_args=['--release', release], need_zip35=need_zip35)
4ce926b3
CH
137
138 # find rpm and rename it
36eadbed 139 newest_names = []
4ce926b3
CH
140 newest_time = 0
141 for filename in iglob(join(DIST_DIR, 'pyi2ncommon-*.noarch.rpm')):
142 filetime = os.stat(filename).st_mtime
143 if filetime > newest_time:
144 newest_time = filetime
36eadbed
CH
145 newest_names = [filename, ]
146 elif filetime == newest_time:
147 newest_names.append(filename)
4ce926b3 148
36eadbed 149 if not newest_names:
4ce926b3
CH
150 raise RuntimeError('No pyi2ncommon rpm file found in {}'
151 .format(DIST_DIR))
36eadbed
CH
152 elif newest_time < start_time:
153 raise RuntimeError('Newest pyi2ncommon rpm file in {} is too old'
154 .format(DIST_DIR))
155 elif len(newest_names) > 1:
156 raise RuntimeError('Multiple newest pyi2ncommon rpm files: {}'
157 .format(newest_names))
158 newest_name = newest_names[0]
4ce926b3
CH
159 mod_name = newest_name[:-11] + '.py' + py_version.replace('.', '') + \
160 newest_name[-11:]
161 os.rename(newest_name, mod_name)
162 return mod_name
163
164
e98c622f 165def create_spec(release):
4ce926b3
CH
166 """Create .spec file and modify it."""
167 print('adapting spec file')
168 # create spec
e98c622f 169 run_setup('bdist_rpm', cmd_line_args=['--spec-only', '--release', release])
4ce926b3
CH
170
171 # adapt
172 temp_handle = None
173 temp_name = None
174 try:
175 temp_handle, temp_file = mkstemp(dir=DIST_DIR, text=False,
176 prefix='pyi2ncommon-adapt-',
177 suffix='.spec')
178 os.write(temp_handle, SPEC_START)
179 did_write_check = False
180 with open(SPEC_FILE, 'rb') as reader:
181 for line in reader:
182 print('spec file: {}'.format(line.rstrip()))
183 if line.strip() == b'%install':
184 os.write(temp_handle, SPEC_CHECK_SECTION)
185 did_write_check = True
186 os.write(temp_handle, line)
187 os.close(temp_handle)
188 temp_handle = None
189
190 if not did_write_check:
191 raise RuntimeError('Could not find place to write %check section')
192
193 # replace
194 os.unlink(SPEC_FILE)
195 os.rename(temp_file, SPEC_FILE)
196 temp_file = None
197 finally:
198 # clean up temp
199 if temp_handle is not None:
200 os.close(temp_handle)
201 if temp_name is not None and isfile(temp_file):
202 os.unlink(temp_file)
203 return SPEC_FILE
204
205
206def main():
207 """
208 Main function, called when running file as script
209
210 see module doc for more info
211 """
e98c622f 212 release = None
c3baf75b 213 py_versions = ['3.7', ]
12c2db72 214 if len(sys.argv) > 1:
c3baf75b
CH
215 if sys.argv[1] in ('--help', '-h'):
216 print('make_dist.py [releasename [py-ver1 [py-ver2...]]]')
217 print(' e.g.: make_dist.py featuretest 3.7')
99cb8c43 218 return 2
e98c622f 219 release = sys.argv[1].replace('-', '_')
12c2db72 220 else:
e98c622f
CH
221 release = check_output(['git', 'log', '--pretty=format:%h', '-n', '1'],
222 universal_newlines=True)
223 if len(sys.argv) > 2:
224 py_versions = sys.argv[2:]
12c2db72 225 files_created = []
e98c622f 226 for py_version in py_versions:
783a19b7
CH
227 check_py_version(py_version)
228 run_unittests(py_version)
e98c622f
CH
229 files_created.append(create_rpm(py_version, release))
230 files_created.append(create_spec(release))
12c2db72
CH
231
232 for filename in files_created:
4ce926b3 233 print('Created {}'.format(filename))
e98c622f 234 print('(+ probably source rpm and tar.gz)')
4ce926b3
CH
235 return 0
236
237
238if __name__ == '__main__':
239 sys.exit(main())