Completed
Push — master ( 4bf886...d5e78a )
by Thomas
31:34 queued 16:34
created

setup.main()   C

Complexity

Conditions 10

Size

Total Lines 33
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 23
nop 0
dl 0
loc 33
rs 5.9999
c 0
b 0
f 0

How to fix   Complexity   

Complexity

Complex classes like setup.main() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
#!/usr/bin/env python3
2
# encoding: utf-8
3
"""
4
setup.py
5
6
Created by Thomas Mangin on 2011-01-24.
7
Copyright (c) 2009-2017 Exa Networks. All rights reserved.
8
"""
9
10
import os
11
import sys
12
13
class path:
14
	root = os.path.join(os.getcwd(), os.path.dirname(sys.argv[0]))
15
	changelog = os.path.join(root,'CHANGELOG')
16
	lib_exa = os.path.join(root, 'lib/exabgp')
17
	version = os.path.join(root, 'lib/exabgp/version.py')
18
	debian = os.path.join(root, 'debian/changelog')
19
	egg = os.path.join(root, 'lib/exabgp.egg-info')
20
	build_exabgp = os.path.join(root, 'build/lib/exabgp')
21
	build_root = os.path.join(root, 'build')
22
23
	@staticmethod
24
	def remove_egg():
25
		from shutil import rmtree
26
		print('removing left-over egg')
27
		if os.path.exists(path.egg):
28
			rmtree(path.egg)
29
		if os.path.exists(path.build_exabgp):
30
			rmtree(path.build_root)
31
		return 0
32
33
34
class version:
35
	JSON = '4.0.1'
36
	TEXT = '4.0.1'
37
38
	template = """\
39
import os
40
41
commit = "%s"
42
release = "%s"
43
json = "%s"
44
text = "%s"
45
version = os.environ.get('EXABGP_VERSION',release)
46
47
# Do not change the first line as it is parsed by scripts
48
49
if __name__ == '__main__':
50
	import sys
51
	sys.stdout.write(version)
52
"""
53
54
	@staticmethod
55
	def get():
56
		sys.path.append(path.lib_exa)
57
		from version import version as release
58
59
		# transitional fix
60
		if "-" in release:
61
			release = release.split("-")[0]
62
63
		if version.changelog() != release:
64
			release += ".post1"
65
66
		return release
67
68
	@staticmethod
69
	def changelog():
70
		with open(path.changelog) as f:
71
			f.readline()
72
			for line in f:
73
				if line.lower().startswith('version '):
74
					return line.split()[1].rstrip().rstrip(':').strip()
75
		return ''
76
77
	@staticmethod
78
	def set(tag,commit):
79
		with open(path.version, 'w') as f:
80
			f.write(version.template % (
81
				commit, tag,
82
				version.JSON,
83
				version.TEXT
84
			))
85
		return version.get() == tag
86
87
	@staticmethod
88
	def latest (tags):
89
		valid = [
90
			[int(_) for _ in tag.split('.')] for tag in tags
91
			if version.valid(tag)
92
		]
93
		return '.'.join(str(_) for _ in sorted(valid)[-1])
94
95
	@staticmethod
96
	def valid (tag):
97
		parts = tag.split('.')
98
		return len(parts) == 3 \
99
			and parts[0].isdigit() \
100
			and parts[1].isdigit() \
101
			and parts[2].isdigit()
102
103
	@staticmethod
104
	def candidates(tag):
105
		latest = [int(_) for _ in tag.split('.')]
106
		return [
107
			'.'.join([str(_) for _ in (latest[0], latest[1], latest[2] + 1)]),
108
			'.'.join([str(_) for _ in (latest[0], latest[1] + 1, 0)]),
109
			'.'.join([str(_) for _ in (latest[0] + 1, 0, 0)]),
110
		]
111
112
113
class debian:
114
	template = """\
115
exabgp (%s-0) unstable; urgency=low
116
117
  * Latest ExaBGP release.
118
119
 -- Vincent Bernat <[email protected]>  %s
120
121
"""
122
	@staticmethod
123
	def set(version):
124
		from email.utils import formatdate
125
		with open(path.debian, 'w') as w:
126
			w.write(debian.template % (version, formatdate()))
127
		print('updated debian/changelog')
128
129
130
class command:
131
	dryrun = 'dry-run' if os.environ.get('DRY', os.environ.get('DRYRUN', os.environ.get('DRY_RUN', False))) else ''
132
133
	@staticmethod
134
	def run(cmd):
135
		print('>', cmd)
136
		return git.dryrun or os.system(cmd)
137
138
139
class git (command):
140
	@staticmethod
141
	def commit (comment):
142
		return git.run('git commit -a -m "%s"' % comment)
143
144
	@staticmethod
145
	def push(tag=False,repo=''):
146
		command = 'git push'
147
		if tag:
148
			command += ' --tags'
149
		if repo:
150
			command += ' %s' % repo
151
		return git.run(command)
152
153
	@staticmethod
154
	def head_commit():
155
		return os.popen('git rev-parse --short HEAD').read().strip()
156
157
	@staticmethod
158
	def tags():
159
		return os.popen('git tag').read().split('-')[0].strip().split('\n')
160
161
	@staticmethod
162
	def tag(release):
163
		return git.run('git tag -a %s -m "release %s"' % (release, release))
164
165
	@staticmethod
166
	def pending():
167
		commit = None
168
		for line in os.popen('git status').read().split('\n'):
169
			if 'modified:' in line:
170
				if 'lib/exabgp/version.py' in line or 'debian/changelog' in line:
171
					if commit is not False:
172
						commit = True
173
				else:
174
					return False
175
			elif 'renamed:' in line:
176
				return False
177
		return commit
178
179
class repo:
180
	def update_version():
181
		if not version.set(version.changelog(), git.head_commit()):
182
			print('failed to set version in python code')
183
			return False
184
185
		if not git.commit('updating version to %s' % version.get()):
186
			print('failed to commit the change')
187
			return False
188
189
		if not git.push():
190
			print('failed to push the change')
191
			return False
192
		return True
193
194
195
#
196
# Check that that there is no version inconsistancy before any pypi action
197
#
198
199
def release_github():
200
	print()
201
	print('updating Github')
202
	release = version.changelog()
203
	tags = git.tags()
204
205
	if not version.valid(release):
206
		print('invalid new version in CHANGELOG (%s)' % release)
207
		return 1
208
209
	candidates = version.candidates(version.latest(tags))
210
211
	print('valid versions are:', ', '.join(candidates))
212
	print('checking the CHANGELOG uses one of them')
213
214
	print('ok, next release is %s' % release)
215
	print('checking that this release is not already tagged')
216
217
	if release in tags:
218
		print('this tag was already released')
219
		return 1
220
221
	print('ok, this is a new release')
222
	print('rewriting lib/exabgp/version.py')
223
	version.set(release, git.head_commit())
224
	print('rewriting debian/changelog')
225
	debian.set(release)
226
227
	print('checking if we need to commit a version.py change')
228
	status = git.pending()
229
	if status is None:
230
		print('all is already set for release')
231
	elif status is False:
232
		print('more than one file is modified and need updating, aborting')
233
		return 1
234
	else:
235
		if git.commit('updating version to %s' % release):
236
			print('could not commit version change (%s)' % release)
237
			return 1
238
		print('version was updated')
239
240
	print('tagging the new version')
241
	if git.tag(release):
242
		print('could not tag version (%s)' % release)
243
		return 1
244
245
	print('pushing the new tag to local repo')
246
	if git.push(tag=True, repo='upstream'):
247
		print('could not push release version')
248
		return 1
249
	return 0
250
251
252
def release_pypi():
253
	print()
254
	print('updating PyPI')
255
256
	path.remove_egg()
257
258
	if command.run('python setup.py sdist bdist_wheel'):
259
		print('could not generate egg')
260
		return 1
261
262
	# keyring used to save credential
263
	# https://pypi.org/project/twine/
264
265
	release = version.changelog()
266
267
	if command.run('twine upload dist/exabgp-%s.tar.gz' % release):
268
		print('could not upload with twine')
269
		return 1
270
271
	print('all done.')
272
	return 0
273
274
275
def st():
276
	import platform
277
	from distutils.util import get_platform
278
	from setuptools import setup
279
280
	def packages(lib):
281
		def dirs(*path):
282
			for location, _, _ in os.walk(os.path.join(*path)):
283
				yield location
284
285
		def modules(lib):
286
			return next(os.walk(lib))[1]
287
288
		r = []
289
		for module in modules(lib):
290
			for d in dirs(lib, module):
291
				r.append(d.replace('/', '.').replace('\\', '.')[len(lib) + 1:])
292
		return r
293
294
295
	def filesOf(directory):
296
		files = []
297
		for l, d, fs in os.walk(directory):
298
			if not d:
299
				for f in fs:
300
					files.append(os.path.join(l, f))
301
		return files
302
303
304
	def testFilesOf(directory):
305
		files = []
306
		for l, d, fs in os.walk(directory):
307
			if not d:
308
				for f in fs:
309
					if f.endswith('.run') or f.endswith('.conf'):
310
						files.append(os.path.join(l, f))
311
		return files
312
313
314
	files_definition = [
315
		('share/exabgp/processes', filesOf('etc/exabgp')),
316
		('share/exabgp/etc', testFilesOf('qa/conf')),
317
	]
318
319
	if platform.system() != 'NetBSD':
320
		if sys.argv[-1] == 'systemd':
321
			files_definition.append(('/usr/lib/systemd/system', filesOf('etc/systemd')))
322
323
	try:
324
		description_rst = open('PYPI.rst').read() % {'version': version.get()}
325
	except IOError:
326
		print('failed to open PYPI.rst')
327
		return 1
328
329
	if command.dryrun:
330
		return 1
331
332
	setup(
333
		name='exabgp',
334
		version=version.get(),
335
		description='BGP swiss army knife',
336
		long_description=description_rst,
337
		author='Thomas Mangin',
338
		author_email='[email protected]',
339
		url='https://github.com/Exa-Networks/exabgp',
340
		license='BSD',
341
		keywords='BGP routing SDN FlowSpec HA',
342
		platforms=[get_platform(), ],
343
		package_dir={'': 'lib'},
344
		packages=packages('lib'),
345
		package_data={'': ['PYPI.rst']},
346
		download_url='https://github.com/Exa-Networks/exabgp/archive/%s.tar.gz' % version.get(),
347
		data_files=files_definition,
348
		setup_requires=['setuptools'],
349
		classifiers=[
350
			'Development Status :: 5 - Production/Stable',
351
			'Environment :: Console',
352
			'Intended Audience :: System Administrators',
353
			'Intended Audience :: Telecommunications Industry',
354
			'License :: OSI Approved :: BSD License',
355
			'Operating System :: POSIX',
356
			'Operating System :: MacOS :: MacOS X',
357
			'Programming Language :: Python',
358
			'Programming Language :: Python :: 3.7',
359
			'Topic :: Internet',
360
		],
361
		entry_points={
362
			'console_scripts': [
363
				'exabgp = exabgp.application:run_exabgp',
364
				'exabgpcli = exabgp.application:run_cli',
365
			],
366
		},
367
	)
368
	return 0
369
370
371
def help():
372
	print("""\
373
python3 setup.py help     this help
374
python3 setup.py cleanup  delete left-over file from release
375
python3 setup.py release  tag a new version on github, and update pypi
376
python3 setup.py pypi     create egg/wheel
377
python3 setup.py install  local installation
378
python3 setup.py build    local build
379
""")
380
381
def main ():
382
	if os.environ.get("SCRUTINIZER", "") == "true":
383
		sys.exit(0)
384
385
	if sys.argv[-1] == 'cleanup':
386
		sys.exit(path.remove_egg())
387
388
	if sys.argv[-1] == 'release':
389
		sys.exit(release_github())
390
391
	if sys.argv[-1] == 'pypi':
392
		sys.exit(release_pypi())
393
394
	# "internal" commands
395
396
	if sys.argv[-1] == 'version':
397
		sys.stdout.write("%s\n" % version.get())
398
		sys.exit(0)
399
400
	if sys.argv[-1] == 'current':
401
		sys.stdout.write("%s\n" % version.changelog())
402
		sys.exit(0)
403
404
	if '--help' in sys.argv or 'help' in sys.argv:
405
		help()
406
		sys.exit(1)
407
408
	if sys.argv[-1] == 'debian':
409
		release = version.changelog()
410
		debian.set(release)
411
		sys.exit(0)
412
413
	sys.exit(st())
414
415
416
if __name__ == '__main__':
417
	main()
418