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