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