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