Skip to content

Commit 09a7153

Browse files
committed
Simplify setup.py and automatic version generation
There was a lot of unnecessarily complex logic in setup.py, taken from numpy, about whether to use setuptools or distutils. In addition, the procedure for generating the version information was only partially automatic (one still needed to change the version manually in setup.py), and was somewhat unreliable (see issue #37). This commit simplifies setup.py, always using setuptools, as recommended in the Python Packaging Guide: http://python-packaging-user-guide.readthedocs.org/en/latest/current.html The version information is now generated directly from tags in the git repository. Now, *before* running setup.py, one runs python make_version.py and this generates a file with the version information. This is copied from binstar (https://github.com/Binstar/binstar) and seems to work well.
1 parent 86ebb1c commit 09a7153

5 files changed

Lines changed: 109 additions & 250 deletions

File tree

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ build/
33
dist/
44
.ropeproject/
55
MANIFEST
6-
control/version.py
6+
control/_version.py
77
build.log
88
*.egg-info/
99
.coverage

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ before_install:
2222
- conda config --set always_yes yes --set changeps1 no
2323
- conda update -q conda
2424
- conda info -a
25+
- python make_version.py
2526

2627
# Install packages
2728
install:

control/__init__.py

Lines changed: 47 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -56,65 +56,56 @@
5656
lqe linear quadratic estimator
5757
"""
5858

59-
try:
60-
__CONTROL_SETUP__
61-
except NameError:
62-
__CONTROL_SETUP__ = False
63-
64-
if __CONTROL_SETUP__:
65-
import sys as _sys
66-
_sys.stderr.write('Running from control source directory.\n')
67-
del _sys
68-
else:
59+
# Import functions from within the control system library
60+
# Should probably only import the exact functions we use...
61+
from .bdalg import series, parallel, negate, feedback
62+
from .delay import pade
63+
from .dtime import sample_system
64+
from .freqplot import bode_plot, nyquist_plot, gangof4_plot
65+
from .freqplot import bode, nyquist, gangof4
66+
from .lti import issiso, timebase, timebaseEqual, isdtime, isctime
67+
from .margins import stability_margins, phase_crossover_frequencies
68+
from .mateqn import lyap, dlyap, care, dare
69+
from .modelsimp import hsvd, modred, balred, era, markov
70+
from .nichols import nichols_plot, nichols
71+
from .phaseplot import phase_plot, box_grid
72+
from .rlocus import root_locus
73+
from .statefbk import place, lqr, ctrb, obsv, gram, acker
74+
from .statesp import StateSpace
75+
from .timeresp import forced_response, initial_response, step_response, \
76+
impulse_response
77+
from .xferfcn import TransferFunction
78+
from .ctrlutil import unwrap, issys
79+
from .frdata import FRD
80+
from .canonical import canonical_form, reachable_form
6981

70-
# Import functions from within the control system library
71-
# Should probably only import the exact functions we use...
72-
from .bdalg import series, parallel, negate, feedback
73-
from .delay import pade
74-
from .dtime import sample_system
75-
from .freqplot import bode_plot, nyquist_plot, gangof4_plot
76-
from .freqplot import bode, nyquist, gangof4
77-
from .lti import issiso, timebase, timebaseEqual, isdtime, isctime
78-
from .margins import stability_margins, phase_crossover_frequencies
79-
from .mateqn import lyap, dlyap, care, dare
80-
from .modelsimp import hsvd, modred, balred, era, markov
81-
from .nichols import nichols_plot, nichols
82-
from .phaseplot import phase_plot, box_grid
83-
from .rlocus import root_locus
84-
from .statefbk import place, lqr, ctrb, obsv, gram, acker
85-
from .statesp import StateSpace
86-
from .timeresp import forced_response, initial_response, step_response, \
87-
impulse_response
88-
from .xferfcn import TransferFunction
89-
from .ctrlutil import unwrap, issys
90-
from .frdata import FRD
91-
from .canonical import canonical_form, reachable_form
82+
# Exceptions
83+
from .exception import *
9284

93-
# Exceptions
94-
from .exception import *
95-
96-
# Version information
97-
from control.version import full_version as __version__
98-
from control.version import git_revision as __git_revision__
85+
# Version information
86+
try:
87+
from ._version import __version__, __commit__
88+
except ImportError:
89+
__version__ = "dev"
9990

100-
# Import some of the more common (and benign) MATLAB shortcuts
101-
# By default, don't import conflicting commands here
102-
#! TODO (RMM, 4 Nov 2012): remove MATLAB dependencies from __init__.py
103-
#!
104-
#! Eventually, all functionality should be in modules *other* than matlab.
105-
#! This will allow inclusion of the matlab module to set up a different set
106-
#! of defaults from the main package. At that point, the matlab module will
107-
#! allow provide compatibility with MATLAB but no package functionality.
108-
#!
109-
from .matlab import ss, tf, ss2tf, tf2ss, drss
110-
from .matlab import pole, zero, evalfr, freqresp, dcgain
111-
from .matlab import nichols, rlocus, margin
112-
# bode and nyquist come directly from freqplot.py
113-
from .matlab import step, impulse, initial, lsim
114-
from .matlab import ssdata, tfdata
91+
# Import some of the more common (and benign) MATLAB shortcuts
92+
# By default, don't import conflicting commands here
93+
#! TODO (RMM, 4 Nov 2012): remove MATLAB dependencies from __init__.py
94+
#!
95+
#! Eventually, all functionality should be in modules *other* than matlab.
96+
#! This will allow inclusion of the matlab module to set up a different set
97+
#! of defaults from the main package. At that point, the matlab module will
98+
#! allow provide compatibility with MATLAB but no package functionality.
99+
#!
100+
from .matlab import ss, tf, ss2tf, tf2ss, drss
101+
from .matlab import pole, zero, evalfr, freqresp, dcgain
102+
from .matlab import nichols, rlocus, margin
103+
# bode and nyquist come directly from freqplot.py
104+
from .matlab import step, impulse, initial, lsim
105+
from .matlab import ssdata, tfdata
115106

116107
# The following is to use Numpy's testing framework
117108
# Tests go under directory tests/, benchmarks under directory benchmarks/
118-
from numpy.testing import Tester
119-
test = Tester().test
120-
bench = Tester().bench
109+
from numpy.testing import Tester
110+
test = Tester().test
111+
bench = Tester().bench

make_version.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from subprocess import check_output
2+
import os
3+
4+
def main():
5+
cmd = 'git describe --always --long'
6+
output = check_output(cmd.split()).decode('utf-8').strip().split('-')
7+
if len(output) == 3:
8+
version, build, commit = output
9+
else:
10+
raise Exception("Could not git describe, (got %s)" % output)
11+
12+
print("Version: %s" % version)
13+
print("Build: %s" % build)
14+
print("Commit: %s\n" % commit)
15+
16+
filename = "control/_version.py"
17+
print("Writing %s" % filename)
18+
with open(filename, 'w') as fd:
19+
if build == '0':
20+
fd.write('__version__ = "%s"\n' % (version))
21+
else:
22+
fd.write('__version__ = "%s.post%s"\n' % (version, build))
23+
fd.write('__commit__ = "%s"\n' % (commit))
24+
25+
26+
if __name__ == '__main__':
27+
main()

setup.py

Lines changed: 33 additions & 193 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,26 @@
1-
#!/usr/bin/env python
2-
descr = """Python Control Systems Library
1+
from setuptools import setup, find_packages
32

4-
The Python Control Systems Library, python-control,
5-
is a python module that implements basic operations
6-
for analysis and design of feedback control systems.
3+
ver = {}
4+
try:
5+
with open('control/_version.py') as fd:
6+
exec(fd.read(), ver)
7+
version = ver.get('__version__', 'dev')
8+
except IOError:
9+
version = 'dev'
710

8-
Features:
9-
Linear input/output systems in state space and frequency domain
10-
Block diagram algebra: serial, parallel and feedback interconnections
11-
Time response: initial, step, impulse
12-
Frequency response: Bode and Nyquist plots
13-
Control analysis: stability, reachability, observability, stability margins
14-
Control design: eigenvalue placement, linear quadratic regulator
15-
Estimator design: linear quadratic estimator (Kalman filter)
11+
with open('README.rst') as fp:
12+
long_description = fp.read()
1613

17-
"""
18-
19-
MAJOR = 0
20-
MINOR = 6
21-
MICRO = 5
22-
ISRELEASED = True
23-
DISTNAME = 'control'
24-
DESCRIPTION = 'Python control systems library'
25-
LONG_DESCRIPTION = descr
26-
AUTHOR = 'Richard Murray'
27-
AUTHOR_EMAIL = 'murray@cds.caltech.edu'
28-
MAINTAINER = AUTHOR
29-
MAINTAINER_EMAIL = AUTHOR_EMAIL
30-
URL = 'http://python-control.sourceforge.net'
31-
LICENSE = 'BSD'
32-
DOWNLOAD_URL = URL
33-
PACKAGE_NAME = 'control'
34-
EXTRA_INFO = dict(
35-
install_requires=['numpy', 'scipy', 'matplotlib'],
36-
tests_require=['scipy', 'matplotlib', 'nose']
37-
)
38-
39-
VERSION = '%d.%d.%d' % (MAJOR, MINOR, MICRO)
40-
41-
import os
42-
import sys
43-
import subprocess
44-
45-
46-
CLASSIFIERS = """\
14+
CLASSIFIERS = """
4715
Development Status :: 3 - Alpha
4816
Intended Audience :: Science/Research
4917
Intended Audience :: Developers
5018
License :: OSI Approved :: BSD License
51-
Programming Language :: Python
19+
Programming Language :: Python :: 2
20+
Programming Language :: Python :: 2.7
5221
Programming Language :: Python :: 3
22+
Programming Language :: Python :: 3.3
23+
Programming Language :: Python :: 3.4
5324
Topic :: Software Development
5425
Topic :: Scientific/Engineering
5526
Operating System :: Microsoft :: Windows
@@ -58,152 +29,21 @@
5829
Operating System :: MacOS
5930
"""
6031

61-
62-
# Return the git revision as a string
63-
def git_version():
64-
def _minimal_ext_cmd(cmd):
65-
# construct minimal environment
66-
env = {}
67-
for k in ['SYSTEMROOT', 'PATH']:
68-
v = os.environ.get(k)
69-
if v is not None:
70-
env[k] = v
71-
# LANGUAGE is used on win32
72-
env['LANGUAGE'] = 'C'
73-
env['LANG'] = 'C'
74-
env['LC_ALL'] = 'C'
75-
out = subprocess.Popen(
76-
cmd,
77-
stdout=subprocess.PIPE,
78-
env=env).communicate()[0]
79-
return out
80-
81-
try:
82-
out = _minimal_ext_cmd(['git', 'rev-parse', 'HEAD'])
83-
GIT_REVISION = out.strip().decode('ascii')
84-
except OSError:
85-
GIT_REVISION = "Unknown"
86-
87-
return GIT_REVISION
88-
89-
90-
def get_version_info():
91-
# Adding the git rev number needs to be done inside write_version_py(),
92-
# otherwise the import of package.version messes up
93-
# the build under Python 3.
94-
FULLVERSION = VERSION
95-
if os.path.exists('.git'):
96-
GIT_REVISION = git_version()
97-
elif os.path.exists('control/version.py'):
98-
# must be a source distribution, use existing version file
99-
try:
100-
from control.version import git_revision as GIT_REVISION
101-
except ImportError:
102-
raise ImportError("Unable to import git_revision. Try removing "
103-
"control/version.py and the build directory "
104-
"before building.")
105-
else:
106-
GIT_REVISION = "Unknown"
107-
108-
if not ISRELEASED:
109-
FULLVERSION += '.dev-' + GIT_REVISION[:7]
110-
111-
return FULLVERSION, GIT_REVISION
112-
113-
114-
def write_version_py(filename='control/version.py'):
115-
cnt = """
116-
# THIS FILE IS GENERATED FROM SETUP.PY
117-
short_version = '%(version)s'
118-
version = '%(version)s'
119-
full_version = '%(full_version)s'
120-
git_revision = '%(git_revision)s'
121-
release = %(isrelease)s
122-
123-
if not release:
124-
version = full_version
125-
"""
126-
FULLVERSION, GIT_REVISION = get_version_info()
127-
128-
a = open(filename, 'w')
129-
try:
130-
a.write(cnt % {'version': VERSION,
131-
'full_version': FULLVERSION,
132-
'git_revision': GIT_REVISION,
133-
'isrelease': str(ISRELEASED)})
134-
finally:
135-
a.close()
136-
137-
def configuration(parent_package='',top_path=None):
138-
from numpy.distutils.misc_util import Configuration
139-
140-
config = Configuration(None, parent_package, top_path)
141-
config.set_options(ignore_setup_xxx_py=True,
142-
assume_default_configuration=True,
143-
delegate_options_to_subpackages=True,
144-
quiet=True)
145-
146-
config.add_subpackage(PACKAGE_NAME)
147-
148-
config.get_version(PACKAGE_NAME + '/version.py') # sets config.version
149-
150-
return config
151-
152-
def setup_package():
153-
src_path = os.path.dirname(os.path.abspath(sys.argv[0]))
154-
old_path = os.getcwd()
155-
os.chdir(src_path)
156-
sys.path.insert(0, src_path)
157-
158-
# Rewrite the version file everytime
159-
write_version_py()
160-
161-
metadata = dict(
162-
name=DISTNAME,
163-
author=AUTHOR,
164-
author_email=AUTHOR_EMAIL,
165-
maintainer=MAINTAINER,
166-
maintainer_email=MAINTAINER_EMAIL,
167-
description=DESCRIPTION,
168-
license=LICENSE,
169-
url=URL,
170-
download_url=DOWNLOAD_URL,
171-
long_description=LONG_DESCRIPTION,
172-
classifiers=[_f for _f in CLASSIFIERS.split('\n') if _f],
173-
platforms=["Windows", "Linux", "Solaris", "Mac OS-X", "Unix"],
174-
install_requires=['numpy', 'scipy'],
175-
tests_require=['nose'],
176-
test_suite='nose.collector',
177-
packages=[PACKAGE_NAME],
178-
)
179-
180-
# Run build
181-
if len(sys.argv) >= 2 and ('--help' in sys.argv[1:] or
182-
sys.argv[1] in ('--help-commands', 'egg_info', '--version',
183-
'clean', 'test')):
184-
# Use setuptools for these commands (they don't work well or at all
185-
# with distutils). For normal builds use distutils.
186-
try:
187-
from setuptools import setup
188-
except ImportError:
189-
from distutils.core import setup
190-
191-
FULLVERSION, GIT_REVISION = get_version_info()
192-
metadata['version'] = FULLVERSION
193-
else:
194-
if len(sys.argv) >= 2 and sys.argv[1] == 'bdist_wheel':
195-
# bdist_wheel needs setuptools
196-
import setuptools
197-
from numpy.distutils.core import setup
198-
cwd = os.path.abspath(os.path.dirname(__file__))
199-
metadata['configuration'] = configuration
200-
201-
try:
202-
setup(**metadata)
203-
finally:
204-
del sys.path[0]
205-
os.chdir(old_path)
206-
return
207-
208-
if __name__ == '__main__':
209-
setup_package()
32+
setup(
33+
name='control',
34+
version=version,
35+
author='Richard Murray',
36+
author_email='murray@cds.caltech.edu',
37+
url='http://python-control.sourceforge.net',
38+
description='Python control systems library',
39+
long_description=long_description,
40+
packages=find_packages(),
41+
classifiers=[f for f in CLASSIFIERS.split('\n') if f],
42+
install_requires=['numpy',
43+
'scipy',
44+
'matplotlib'],
45+
tests_require=['scipy',
46+
'matplotlib',
47+
'nose'],
48+
test_suite = 'nose.collector',
49+
)

0 commit comments

Comments
 (0)