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