##// END OF EJS Templates
Set version in setup.cfg
James Morris -
Show More
@@ -1,12 +1,13 b''
1 [metadata]
1 [metadata]
2 version = attr: IPython.core.release.__version__
2 license_file = LICENSE
3 license_file = LICENSE
3 project_urls =
4 project_urls =
4 Documentation = https://ipython.readthedocs.io/
5 Documentation = https://ipython.readthedocs.io/
5 Funding = https://numfocus.org/
6 Funding = https://numfocus.org/
6 Source = https://github.com/ipython/ipython
7 Source = https://github.com/ipython/ipython
7 Tracker = https://github.com/ipython/ipython/issues
8 Tracker = https://github.com/ipython/ipython/issues
8
9
9 [velin]
10 [velin]
10 ignore_patterns =
11 ignore_patterns =
11 IPython/core/tests,
12 IPython/core/tests,
12 IPython/testing
13 IPython/testing
@@ -1,355 +1,354 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 This module defines the things that are used in setup.py for building IPython
3 This module defines the things that are used in setup.py for building IPython
4
4
5 This includes:
5 This includes:
6
6
7 * The basic arguments to setup
7 * The basic arguments to setup
8 * Functions for finding things like packages, package data, etc.
8 * Functions for finding things like packages, package data, etc.
9 * A function for checking dependencies.
9 * A function for checking dependencies.
10 """
10 """
11
11
12 # Copyright (c) IPython Development Team.
12 # Copyright (c) IPython Development Team.
13 # Distributed under the terms of the Modified BSD License.
13 # Distributed under the terms of the Modified BSD License.
14
14
15 import os
15 import os
16 import re
16 import re
17 import sys
17 import sys
18 from glob import glob
18 from glob import glob
19 from logging import log
19 from logging import log
20
20
21 from setuptools import Command
21 from setuptools import Command
22 from setuptools.command.build_py import build_py
22 from setuptools.command.build_py import build_py
23
23
24 from setuptools.command.install import install
24 from setuptools.command.install import install
25 from setuptools.command.install_scripts import install_scripts
25 from setuptools.command.install_scripts import install_scripts
26
26
27 from setupext import install_data_ext
27 from setupext import install_data_ext
28
28
29 #-------------------------------------------------------------------------------
29 #-------------------------------------------------------------------------------
30 # Useful globals and utility functions
30 # Useful globals and utility functions
31 #-------------------------------------------------------------------------------
31 #-------------------------------------------------------------------------------
32
32
33 # A few handy globals
33 # A few handy globals
34 isfile = os.path.isfile
34 isfile = os.path.isfile
35 pjoin = os.path.join
35 pjoin = os.path.join
36 repo_root = os.path.dirname(os.path.abspath(__file__))
36 repo_root = os.path.dirname(os.path.abspath(__file__))
37
37
38 def execfile(fname, globs, locs=None):
38 def execfile(fname, globs, locs=None):
39 locs = locs or globs
39 locs = locs or globs
40 with open(fname) as f:
40 with open(fname) as f:
41 exec(compile(f.read(), fname, "exec"), globs, locs)
41 exec(compile(f.read(), fname, "exec"), globs, locs)
42
42
43 # A little utility we'll need below, since glob() does NOT allow you to do
43 # A little utility we'll need below, since glob() does NOT allow you to do
44 # exclusion on multiple endings!
44 # exclusion on multiple endings!
45 def file_doesnt_endwith(test,endings):
45 def file_doesnt_endwith(test,endings):
46 """Return true if test is a file and its name does NOT end with any
46 """Return true if test is a file and its name does NOT end with any
47 of the strings listed in endings."""
47 of the strings listed in endings."""
48 if not isfile(test):
48 if not isfile(test):
49 return False
49 return False
50 for e in endings:
50 for e in endings:
51 if test.endswith(e):
51 if test.endswith(e):
52 return False
52 return False
53 return True
53 return True
54
54
55 #---------------------------------------------------------------------------
55 #---------------------------------------------------------------------------
56 # Basic project information
56 # Basic project information
57 #---------------------------------------------------------------------------
57 #---------------------------------------------------------------------------
58
58
59 # release.py contains version, authors, license, url, keywords, etc.
59 # release.py contains version, authors, license, url, keywords, etc.
60 execfile(pjoin(repo_root, 'IPython','core','release.py'), globals())
60 execfile(pjoin(repo_root, 'IPython','core','release.py'), globals())
61
61
62 # Create a dict with the basic information
62 # Create a dict with the basic information
63 # This dict is eventually passed to setup after additional keys are added.
63 # This dict is eventually passed to setup after additional keys are added.
64 setup_args = dict(
64 setup_args = dict(
65 name = name,
65 name = name,
66 version = version,
67 description = description,
66 description = description,
68 long_description = long_description,
67 long_description = long_description,
69 author = author,
68 author = author,
70 author_email = author_email,
69 author_email = author_email,
71 url = url,
70 url = url,
72 license = license,
71 license = license,
73 platforms = platforms,
72 platforms = platforms,
74 keywords = keywords,
73 keywords = keywords,
75 classifiers = classifiers,
74 classifiers = classifiers,
76 cmdclass = {'install_data': install_data_ext},
75 cmdclass = {'install_data': install_data_ext},
77 )
76 )
78
77
79
78
80 #---------------------------------------------------------------------------
79 #---------------------------------------------------------------------------
81 # Find packages
80 # Find packages
82 #---------------------------------------------------------------------------
81 #---------------------------------------------------------------------------
83
82
84 def find_packages():
83 def find_packages():
85 """
84 """
86 Find all of IPython's packages.
85 Find all of IPython's packages.
87 """
86 """
88 excludes = ['deathrow', 'quarantine']
87 excludes = ['deathrow', 'quarantine']
89 packages = []
88 packages = []
90 for directory, subdirs, files in os.walk("IPython"):
89 for directory, subdirs, files in os.walk("IPython"):
91 package = directory.replace(os.path.sep, ".")
90 package = directory.replace(os.path.sep, ".")
92 if any(package.startswith("IPython." + exc) for exc in excludes):
91 if any(package.startswith("IPython." + exc) for exc in excludes):
93 # package is to be excluded (e.g. deathrow)
92 # package is to be excluded (e.g. deathrow)
94 continue
93 continue
95 if '__init__.py' not in files:
94 if '__init__.py' not in files:
96 # not a package
95 # not a package
97 continue
96 continue
98 packages.append(package)
97 packages.append(package)
99 return packages
98 return packages
100
99
101 #---------------------------------------------------------------------------
100 #---------------------------------------------------------------------------
102 # Find package data
101 # Find package data
103 #---------------------------------------------------------------------------
102 #---------------------------------------------------------------------------
104
103
105 def find_package_data():
104 def find_package_data():
106 """
105 """
107 Find IPython's package_data.
106 Find IPython's package_data.
108 """
107 """
109 # This is not enough for these things to appear in an sdist.
108 # This is not enough for these things to appear in an sdist.
110 # We need to muck with the MANIFEST to get this to work
109 # We need to muck with the MANIFEST to get this to work
111
110
112 package_data = {
111 package_data = {
113 'IPython.core' : ['profile/README*'],
112 'IPython.core' : ['profile/README*'],
114 'IPython.core.tests' : ['*.png', '*.jpg', 'daft_extension/*.py'],
113 'IPython.core.tests' : ['*.png', '*.jpg', 'daft_extension/*.py'],
115 'IPython.lib.tests' : ['*.wav'],
114 'IPython.lib.tests' : ['*.wav'],
116 'IPython.testing.plugin' : ['*.txt'],
115 'IPython.testing.plugin' : ['*.txt'],
117 }
116 }
118
117
119 return package_data
118 return package_data
120
119
121
120
122 def check_package_data(package_data):
121 def check_package_data(package_data):
123 """verify that package_data globs make sense"""
122 """verify that package_data globs make sense"""
124 print("checking package data")
123 print("checking package data")
125 for pkg, data in package_data.items():
124 for pkg, data in package_data.items():
126 pkg_root = pjoin(*pkg.split('.'))
125 pkg_root = pjoin(*pkg.split('.'))
127 for d in data:
126 for d in data:
128 path = pjoin(pkg_root, d)
127 path = pjoin(pkg_root, d)
129 if '*' in path:
128 if '*' in path:
130 assert len(glob(path)) > 0, "No files match pattern %s" % path
129 assert len(glob(path)) > 0, "No files match pattern %s" % path
131 else:
130 else:
132 assert os.path.exists(path), "Missing package data: %s" % path
131 assert os.path.exists(path), "Missing package data: %s" % path
133
132
134
133
135 def check_package_data_first(command):
134 def check_package_data_first(command):
136 """decorator for checking package_data before running a given command
135 """decorator for checking package_data before running a given command
137
136
138 Probably only needs to wrap build_py
137 Probably only needs to wrap build_py
139 """
138 """
140 class DecoratedCommand(command):
139 class DecoratedCommand(command):
141 def run(self):
140 def run(self):
142 check_package_data(self.package_data)
141 check_package_data(self.package_data)
143 command.run(self)
142 command.run(self)
144 return DecoratedCommand
143 return DecoratedCommand
145
144
146
145
147 #---------------------------------------------------------------------------
146 #---------------------------------------------------------------------------
148 # Find data files
147 # Find data files
149 #---------------------------------------------------------------------------
148 #---------------------------------------------------------------------------
150
149
151 def find_data_files():
150 def find_data_files():
152 """
151 """
153 Find IPython's data_files.
152 Find IPython's data_files.
154
153
155 Just man pages at this point.
154 Just man pages at this point.
156 """
155 """
157
156
158 if "freebsd" in sys.platform:
157 if "freebsd" in sys.platform:
159 manpagebase = pjoin('man', 'man1')
158 manpagebase = pjoin('man', 'man1')
160 else:
159 else:
161 manpagebase = pjoin('share', 'man', 'man1')
160 manpagebase = pjoin('share', 'man', 'man1')
162
161
163 # Simple file lists can be made by hand
162 # Simple file lists can be made by hand
164 manpages = [f for f in glob(pjoin('docs','man','*.1.gz')) if isfile(f)]
163 manpages = [f for f in glob(pjoin('docs','man','*.1.gz')) if isfile(f)]
165 if not manpages:
164 if not manpages:
166 # When running from a source tree, the manpages aren't gzipped
165 # When running from a source tree, the manpages aren't gzipped
167 manpages = [f for f in glob(pjoin('docs','man','*.1')) if isfile(f)]
166 manpages = [f for f in glob(pjoin('docs','man','*.1')) if isfile(f)]
168
167
169 # And assemble the entire output list
168 # And assemble the entire output list
170 data_files = [ (manpagebase, manpages) ]
169 data_files = [ (manpagebase, manpages) ]
171
170
172 return data_files
171 return data_files
173
172
174
173
175 # The two functions below are copied from IPython.utils.path, so we don't need
174 # The two functions below are copied from IPython.utils.path, so we don't need
176 # to import IPython during setup, which fails on Python 3.
175 # to import IPython during setup, which fails on Python 3.
177
176
178 def target_outdated(target,deps):
177 def target_outdated(target,deps):
179 """Determine whether a target is out of date.
178 """Determine whether a target is out of date.
180
179
181 target_outdated(target,deps) -> 1/0
180 target_outdated(target,deps) -> 1/0
182
181
183 deps: list of filenames which MUST exist.
182 deps: list of filenames which MUST exist.
184 target: single filename which may or may not exist.
183 target: single filename which may or may not exist.
185
184
186 If target doesn't exist or is older than any file listed in deps, return
185 If target doesn't exist or is older than any file listed in deps, return
187 true, otherwise return false.
186 true, otherwise return false.
188 """
187 """
189 try:
188 try:
190 target_time = os.path.getmtime(target)
189 target_time = os.path.getmtime(target)
191 except os.error:
190 except os.error:
192 return 1
191 return 1
193 for dep in deps:
192 for dep in deps:
194 dep_time = os.path.getmtime(dep)
193 dep_time = os.path.getmtime(dep)
195 if dep_time > target_time:
194 if dep_time > target_time:
196 #print "For target",target,"Dep failed:",dep # dbg
195 #print "For target",target,"Dep failed:",dep # dbg
197 #print "times (dep,tar):",dep_time,target_time # dbg
196 #print "times (dep,tar):",dep_time,target_time # dbg
198 return 1
197 return 1
199 return 0
198 return 0
200
199
201
200
202 def target_update(target,deps,cmd):
201 def target_update(target,deps,cmd):
203 """Update a target with a given command given a list of dependencies.
202 """Update a target with a given command given a list of dependencies.
204
203
205 target_update(target,deps,cmd) -> runs cmd if target is outdated.
204 target_update(target,deps,cmd) -> runs cmd if target is outdated.
206
205
207 This is just a wrapper around target_outdated() which calls the given
206 This is just a wrapper around target_outdated() which calls the given
208 command if target is outdated."""
207 command if target is outdated."""
209
208
210 if target_outdated(target,deps):
209 if target_outdated(target,deps):
211 os.system(cmd)
210 os.system(cmd)
212
211
213 #---------------------------------------------------------------------------
212 #---------------------------------------------------------------------------
214 # Find scripts
213 # Find scripts
215 #---------------------------------------------------------------------------
214 #---------------------------------------------------------------------------
216
215
217 def find_entry_points():
216 def find_entry_points():
218 """Defines the command line entry points for IPython
217 """Defines the command line entry points for IPython
219
218
220 This always uses setuptools-style entry points. When setuptools is not in
219 This always uses setuptools-style entry points. When setuptools is not in
221 use, our own build_scripts_entrypt class below parses these and builds
220 use, our own build_scripts_entrypt class below parses these and builds
222 command line scripts.
221 command line scripts.
223
222
224 Each of our entry points gets both a plain name, e.g. ipython, and one
223 Each of our entry points gets both a plain name, e.g. ipython, and one
225 suffixed with the Python major version number, e.g. ipython3.
224 suffixed with the Python major version number, e.g. ipython3.
226 """
225 """
227 ep = [
226 ep = [
228 'ipython%s = IPython:start_ipython',
227 'ipython%s = IPython:start_ipython',
229 ]
228 ]
230 suffix = str(sys.version_info[0])
229 suffix = str(sys.version_info[0])
231 return [e % '' for e in ep] + [e % suffix for e in ep]
230 return [e % '' for e in ep] + [e % suffix for e in ep]
232
231
233 class install_lib_symlink(Command):
232 class install_lib_symlink(Command):
234 user_options = [
233 user_options = [
235 ('install-dir=', 'd', "directory to install to"),
234 ('install-dir=', 'd', "directory to install to"),
236 ]
235 ]
237
236
238 def initialize_options(self):
237 def initialize_options(self):
239 self.install_dir = None
238 self.install_dir = None
240
239
241 def finalize_options(self):
240 def finalize_options(self):
242 self.set_undefined_options('symlink',
241 self.set_undefined_options('symlink',
243 ('install_lib', 'install_dir'),
242 ('install_lib', 'install_dir'),
244 )
243 )
245
244
246 def run(self):
245 def run(self):
247 if sys.platform == 'win32':
246 if sys.platform == 'win32':
248 raise Exception("This doesn't work on Windows.")
247 raise Exception("This doesn't work on Windows.")
249 pkg = os.path.join(os.getcwd(), 'IPython')
248 pkg = os.path.join(os.getcwd(), 'IPython')
250 dest = os.path.join(self.install_dir, 'IPython')
249 dest = os.path.join(self.install_dir, 'IPython')
251 if os.path.islink(dest):
250 if os.path.islink(dest):
252 print('removing existing symlink at %s' % dest)
251 print('removing existing symlink at %s' % dest)
253 os.unlink(dest)
252 os.unlink(dest)
254 print('symlinking %s -> %s' % (pkg, dest))
253 print('symlinking %s -> %s' % (pkg, dest))
255 os.symlink(pkg, dest)
254 os.symlink(pkg, dest)
256
255
257 class unsymlink(install):
256 class unsymlink(install):
258 def run(self):
257 def run(self):
259 dest = os.path.join(self.install_lib, 'IPython')
258 dest = os.path.join(self.install_lib, 'IPython')
260 if os.path.islink(dest):
259 if os.path.islink(dest):
261 print('removing symlink at %s' % dest)
260 print('removing symlink at %s' % dest)
262 os.unlink(dest)
261 os.unlink(dest)
263 else:
262 else:
264 print('No symlink exists at %s' % dest)
263 print('No symlink exists at %s' % dest)
265
264
266 class install_symlinked(install):
265 class install_symlinked(install):
267 def run(self):
266 def run(self):
268 if sys.platform == 'win32':
267 if sys.platform == 'win32':
269 raise Exception("This doesn't work on Windows.")
268 raise Exception("This doesn't work on Windows.")
270
269
271 # Run all sub-commands (at least those that need to be run)
270 # Run all sub-commands (at least those that need to be run)
272 for cmd_name in self.get_sub_commands():
271 for cmd_name in self.get_sub_commands():
273 self.run_command(cmd_name)
272 self.run_command(cmd_name)
274
273
275 # 'sub_commands': a list of commands this command might have to run to
274 # 'sub_commands': a list of commands this command might have to run to
276 # get its work done. See cmd.py for more info.
275 # get its work done. See cmd.py for more info.
277 sub_commands = [('install_lib_symlink', lambda self:True),
276 sub_commands = [('install_lib_symlink', lambda self:True),
278 ('install_scripts_sym', lambda self:True),
277 ('install_scripts_sym', lambda self:True),
279 ]
278 ]
280
279
281 class install_scripts_for_symlink(install_scripts):
280 class install_scripts_for_symlink(install_scripts):
282 """Redefined to get options from 'symlink' instead of 'install'.
281 """Redefined to get options from 'symlink' instead of 'install'.
283
282
284 I love distutils almost as much as I love setuptools.
283 I love distutils almost as much as I love setuptools.
285 """
284 """
286 def finalize_options(self):
285 def finalize_options(self):
287 self.set_undefined_options('build', ('build_scripts', 'build_dir'))
286 self.set_undefined_options('build', ('build_scripts', 'build_dir'))
288 self.set_undefined_options('symlink',
287 self.set_undefined_options('symlink',
289 ('install_scripts', 'install_dir'),
288 ('install_scripts', 'install_dir'),
290 ('force', 'force'),
289 ('force', 'force'),
291 ('skip_build', 'skip_build'),
290 ('skip_build', 'skip_build'),
292 )
291 )
293
292
294
293
295 #---------------------------------------------------------------------------
294 #---------------------------------------------------------------------------
296 # VCS related
295 # VCS related
297 #---------------------------------------------------------------------------
296 #---------------------------------------------------------------------------
298
297
299
298
300 def git_prebuild(pkg_dir, build_cmd=build_py):
299 def git_prebuild(pkg_dir, build_cmd=build_py):
301 """Return extended build or sdist command class for recording commit
300 """Return extended build or sdist command class for recording commit
302
301
303 records git commit in IPython.utils._sysinfo.commit
302 records git commit in IPython.utils._sysinfo.commit
304
303
305 for use in IPython.utils.sysinfo.sys_info() calls after installation.
304 for use in IPython.utils.sysinfo.sys_info() calls after installation.
306 """
305 """
307
306
308 class MyBuildPy(build_cmd):
307 class MyBuildPy(build_cmd):
309 ''' Subclass to write commit data into installation tree '''
308 ''' Subclass to write commit data into installation tree '''
310 def run(self):
309 def run(self):
311 # loose as `.dev` is suppose to be invalid
310 # loose as `.dev` is suppose to be invalid
312 print("check version number")
311 print("check version number")
313 loose_pep440re = re.compile(r'^(\d+)\.(\d+)\.(\d+((a|b|rc)\d+)?)(\.post\d+)?(\.dev\d*)?$')
312 loose_pep440re = re.compile(r'^(\d+)\.(\d+)\.(\d+((a|b|rc)\d+)?)(\.post\d+)?(\.dev\d*)?$')
314 if not loose_pep440re.match(version):
313 if not loose_pep440re.match(version):
315 raise ValueError("Version number '%s' is not valid (should match [N!]N(.N)*[{a|b|rc}N][.postN][.devN])" % version)
314 raise ValueError("Version number '%s' is not valid (should match [N!]N(.N)*[{a|b|rc}N][.postN][.devN])" % version)
316
315
317
316
318 build_cmd.run(self)
317 build_cmd.run(self)
319 # this one will only fire for build commands
318 # this one will only fire for build commands
320 if hasattr(self, 'build_lib'):
319 if hasattr(self, 'build_lib'):
321 self._record_commit(self.build_lib)
320 self._record_commit(self.build_lib)
322
321
323 def make_release_tree(self, base_dir, files):
322 def make_release_tree(self, base_dir, files):
324 # this one will fire for sdist
323 # this one will fire for sdist
325 build_cmd.make_release_tree(self, base_dir, files)
324 build_cmd.make_release_tree(self, base_dir, files)
326 self._record_commit(base_dir)
325 self._record_commit(base_dir)
327
326
328 def _record_commit(self, base_dir):
327 def _record_commit(self, base_dir):
329 import subprocess
328 import subprocess
330 proc = subprocess.Popen('git rev-parse --short HEAD',
329 proc = subprocess.Popen('git rev-parse --short HEAD',
331 stdout=subprocess.PIPE,
330 stdout=subprocess.PIPE,
332 stderr=subprocess.PIPE,
331 stderr=subprocess.PIPE,
333 shell=True)
332 shell=True)
334 repo_commit, _ = proc.communicate()
333 repo_commit, _ = proc.communicate()
335 repo_commit = repo_commit.strip().decode("ascii")
334 repo_commit = repo_commit.strip().decode("ascii")
336
335
337 out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py')
336 out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py')
338 if os.path.isfile(out_pth) and not repo_commit:
337 if os.path.isfile(out_pth) and not repo_commit:
339 # nothing to write, don't clobber
338 # nothing to write, don't clobber
340 return
339 return
341
340
342 print("writing git commit '%s' to %s" % (repo_commit, out_pth))
341 print("writing git commit '%s' to %s" % (repo_commit, out_pth))
343
342
344 # remove to avoid overwriting original via hard link
343 # remove to avoid overwriting original via hard link
345 try:
344 try:
346 os.remove(out_pth)
345 os.remove(out_pth)
347 except (IOError, OSError):
346 except (IOError, OSError):
348 pass
347 pass
349 with open(out_pth, 'w') as out_file:
348 with open(out_pth, 'w') as out_file:
350 out_file.writelines([
349 out_file.writelines([
351 '# GENERATED BY setup.py\n',
350 '# GENERATED BY setup.py\n',
352 'commit = u"%s"\n' % repo_commit,
351 'commit = u"%s"\n' % repo_commit,
353 ])
352 ])
354 return MyBuildPy
353 return MyBuildPy
355
354
General Comments 0
You need to be logged in to leave comments. Login now