Commit | Line | Data |
---|---|---|
4ce926b3 CH |
1 | #!/usr/bin/env python3 |
2 | ||
3 | """ | |
12c2db72 | 4 | Create rpm packages of pyi2ncommon for various python versions. |
4ce926b3 | 5 | |
783a19b7 | 6 | Usage:: |
4ce926b3 | 7 | |
783a19b7 | 8 | make_dist.py [RELEASE [PY_VERSION_1 [PY_VERSION_2 ...]]] |
12c2db72 | 9 | |
783a19b7 CH |
10 | RELEASE defaults to current git commit hash, default versions to build for are |
11 | 3.6 for avocado and 3.3 for i2n system. All python versions used must be | |
12 | installed on local machine. | |
13 | ||
14 | Calls setup.py with different args. Adds %check section to .spec file. Runs | |
15 | unittests first. | |
4ce926b3 | 16 | |
783a19b7 CH |
17 | This script relies on one feature I accidentally stumbled over: it appears that |
18 | setting install-lib to /usr/lib/python{VERSION}/site-packages is translated by | |
19 | setup.py to installation requirement python(abi) = {VERSION} | |
36eadbed | 20 | |
4ce926b3 CH |
21 | .. codeauthor:: Intra2net AG <info@intra2net.com> |
22 | """ | |
23 | ||
24 | import os | |
25 | from os.path import join, isfile | |
26 | import sys | |
e98c622f | 27 | from subprocess import call, check_output |
4ce926b3 CH |
28 | from tempfile import mkstemp |
29 | from configparser import ConfigParser | |
30 | from glob import iglob | |
36eadbed | 31 | import time |
4ce926b3 CH |
32 | |
33 | INSTALL_DIR_PATTERN = '/usr/lib/python{}/site-packages' | |
34 | ||
35 | RPM_OPTIONS = dict(packager='Intra2net', group='Intra2net', | |
40a4ce5f | 36 | vendor='Intra2net AG') |
4ce926b3 CH |
37 | |
38 | DIST_DIR = 'dist' | |
39 | SPEC_FILE = join(DIST_DIR, 'pyi2ncommon.spec') | |
40 | ||
41 | CFG_FILE = 'setup.cfg' | |
42 | ||
43 | SPEC_START = b""" | |
44 | # spec file created automatically by make_dist.sh -- do not modify! | |
45 | ||
46 | """ | |
47 | ||
48 | SPEC_CHECK_SECTION = b"""%check | |
49 | PYTHONPATH=./src:$PYTHONPATH && python3 -m unittest discover test | |
50 | ||
51 | """ | |
52 | ||
783a19b7 CH |
53 | |
54 | def 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 | ||
65 | def 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 |
84 | def 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 | 118 | def 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 | 165 | def 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 | ||
206 | def 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 | ||
238 | if __name__ == '__main__': | |
239 | sys.exit(main()) |