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