##// END OF EJS Templates
Fix manpage path in FreeBSD...
Po-Chuan Hsieh -
Show More
@@ -1,404 +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 15
16 16 import re
17 17 import os
18 18 import sys
19 19
20 20 from distutils import log
21 21 from distutils.command.build_py import build_py
22 22 from distutils.command.build_scripts import build_scripts
23 23 from distutils.command.install import install
24 24 from distutils.command.install_scripts import install_scripts
25 25 from distutils.cmd import Command
26 26 from glob import glob
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 manpagebase = pjoin('share', 'man', 'man1')
165 if "freebsd" in sys.platform:
166 manpagebase = pjoin('man', 'man1')
167 else:
168 manpagebase = pjoin('share', 'man', 'man1')
166 169
167 170 # Simple file lists can be made by hand
168 171 manpages = [f for f in glob(pjoin('docs','man','*.1.gz')) if isfile(f)]
169 172 if not manpages:
170 173 # When running from a source tree, the manpages aren't gzipped
171 174 manpages = [f for f in glob(pjoin('docs','man','*.1')) if isfile(f)]
172 175
173 176 # And assemble the entire output list
174 177 data_files = [ (manpagebase, manpages) ]
175 178
176 179 return data_files
177 180
178 181
179 182 # The two functions below are copied from IPython.utils.path, so we don't need
180 183 # to import IPython during setup, which fails on Python 3.
181 184
182 185 def target_outdated(target,deps):
183 186 """Determine whether a target is out of date.
184 187
185 188 target_outdated(target,deps) -> 1/0
186 189
187 190 deps: list of filenames which MUST exist.
188 191 target: single filename which may or may not exist.
189 192
190 193 If target doesn't exist or is older than any file listed in deps, return
191 194 true, otherwise return false.
192 195 """
193 196 try:
194 197 target_time = os.path.getmtime(target)
195 198 except os.error:
196 199 return 1
197 200 for dep in deps:
198 201 dep_time = os.path.getmtime(dep)
199 202 if dep_time > target_time:
200 203 #print "For target",target,"Dep failed:",dep # dbg
201 204 #print "times (dep,tar):",dep_time,target_time # dbg
202 205 return 1
203 206 return 0
204 207
205 208
206 209 def target_update(target,deps,cmd):
207 210 """Update a target with a given command given a list of dependencies.
208 211
209 212 target_update(target,deps,cmd) -> runs cmd if target is outdated.
210 213
211 214 This is just a wrapper around target_outdated() which calls the given
212 215 command if target is outdated."""
213 216
214 217 if target_outdated(target,deps):
215 218 os.system(cmd)
216 219
217 220 #---------------------------------------------------------------------------
218 221 # Find scripts
219 222 #---------------------------------------------------------------------------
220 223
221 224 def find_entry_points():
222 225 """Defines the command line entry points for IPython
223 226
224 227 This always uses setuptools-style entry points. When setuptools is not in
225 228 use, our own build_scripts_entrypt class below parses these and builds
226 229 command line scripts.
227 230
228 231 Each of our entry points gets both a plain name, e.g. ipython, and one
229 232 suffixed with the Python major version number, e.g. ipython3.
230 233 """
231 234 ep = [
232 235 'ipython%s = IPython:start_ipython',
233 236 'iptest%s = IPython.testing.iptestcontroller:main',
234 237 ]
235 238 suffix = str(sys.version_info[0])
236 239 return [e % '' for e in ep] + [e % suffix for e in ep]
237 240
238 241 script_src = """#!{executable}
239 242 # This script was automatically generated by setup.py
240 243 if __name__ == '__main__':
241 244 from {mod} import {func}
242 245 {func}()
243 246 """
244 247
245 248 class build_scripts_entrypt(build_scripts):
246 249 """Build the command line scripts
247 250
248 251 Parse setuptools style entry points and write simple scripts to run the
249 252 target functions.
250 253
251 254 On Windows, this also creates .cmd wrappers for the scripts so that you can
252 255 easily launch them from a command line.
253 256 """
254 257 def run(self):
255 258 self.mkpath(self.build_dir)
256 259 outfiles = []
257 260 for script in find_entry_points():
258 261 name, entrypt = script.split('=')
259 262 name = name.strip()
260 263 entrypt = entrypt.strip()
261 264 outfile = os.path.join(self.build_dir, name)
262 265 outfiles.append(outfile)
263 266 print('Writing script to', outfile)
264 267
265 268 mod, func = entrypt.split(':')
266 269 with open(outfile, 'w') as f:
267 270 f.write(script_src.format(executable=sys.executable,
268 271 mod=mod, func=func))
269 272
270 273 if sys.platform == 'win32':
271 274 # Write .cmd wrappers for Windows so 'ipython' etc. work at the
272 275 # command line
273 276 cmd_file = os.path.join(self.build_dir, name + '.cmd')
274 277 cmd = r'@"{python}" "%~dp0\{script}" %*\r\n'.format(
275 278 python=sys.executable, script=name)
276 279 log.info("Writing %s wrapper script" % cmd_file)
277 280 with open(cmd_file, 'w') as f:
278 281 f.write(cmd)
279 282
280 283 return outfiles, outfiles
281 284
282 285 class install_lib_symlink(Command):
283 286 user_options = [
284 287 ('install-dir=', 'd', "directory to install to"),
285 288 ]
286 289
287 290 def initialize_options(self):
288 291 self.install_dir = None
289 292
290 293 def finalize_options(self):
291 294 self.set_undefined_options('symlink',
292 295 ('install_lib', 'install_dir'),
293 296 )
294 297
295 298 def run(self):
296 299 if sys.platform == 'win32':
297 300 raise Exception("This doesn't work on Windows.")
298 301 pkg = os.path.join(os.getcwd(), 'IPython')
299 302 dest = os.path.join(self.install_dir, 'IPython')
300 303 if os.path.islink(dest):
301 304 print('removing existing symlink at %s' % dest)
302 305 os.unlink(dest)
303 306 print('symlinking %s -> %s' % (pkg, dest))
304 307 os.symlink(pkg, dest)
305 308
306 309 class unsymlink(install):
307 310 def run(self):
308 311 dest = os.path.join(self.install_lib, 'IPython')
309 312 if os.path.islink(dest):
310 313 print('removing symlink at %s' % dest)
311 314 os.unlink(dest)
312 315 else:
313 316 print('No symlink exists at %s' % dest)
314 317
315 318 class install_symlinked(install):
316 319 def run(self):
317 320 if sys.platform == 'win32':
318 321 raise Exception("This doesn't work on Windows.")
319 322
320 323 # Run all sub-commands (at least those that need to be run)
321 324 for cmd_name in self.get_sub_commands():
322 325 self.run_command(cmd_name)
323 326
324 327 # 'sub_commands': a list of commands this command might have to run to
325 328 # get its work done. See cmd.py for more info.
326 329 sub_commands = [('install_lib_symlink', lambda self:True),
327 330 ('install_scripts_sym', lambda self:True),
328 331 ]
329 332
330 333 class install_scripts_for_symlink(install_scripts):
331 334 """Redefined to get options from 'symlink' instead of 'install'.
332 335
333 336 I love distutils almost as much as I love setuptools.
334 337 """
335 338 def finalize_options(self):
336 339 self.set_undefined_options('build', ('build_scripts', 'build_dir'))
337 340 self.set_undefined_options('symlink',
338 341 ('install_scripts', 'install_dir'),
339 342 ('force', 'force'),
340 343 ('skip_build', 'skip_build'),
341 344 )
342 345
343 346
344 347 #---------------------------------------------------------------------------
345 348 # VCS related
346 349 #---------------------------------------------------------------------------
347 350
348 351
349 352 def git_prebuild(pkg_dir, build_cmd=build_py):
350 353 """Return extended build or sdist command class for recording commit
351 354
352 355 records git commit in IPython.utils._sysinfo.commit
353 356
354 357 for use in IPython.utils.sysinfo.sys_info() calls after installation.
355 358 """
356 359
357 360 class MyBuildPy(build_cmd):
358 361 ''' Subclass to write commit data into installation tree '''
359 362 def run(self):
360 363 # loose as `.dev` is suppose to be invalid
361 364 print("check version number")
362 365 loose_pep440re = re.compile(r'^(\d+)\.(\d+)\.(\d+((a|b|rc)\d+)?)(\.post\d+)?(\.dev\d*)?$')
363 366 if not loose_pep440re.match(version):
364 367 raise ValueError("Version number '%s' is not valid (should match [N!]N(.N)*[{a|b|rc}N][.postN][.devN])" % version)
365 368
366 369
367 370 build_cmd.run(self)
368 371 # this one will only fire for build commands
369 372 if hasattr(self, 'build_lib'):
370 373 self._record_commit(self.build_lib)
371 374
372 375 def make_release_tree(self, base_dir, files):
373 376 # this one will fire for sdist
374 377 build_cmd.make_release_tree(self, base_dir, files)
375 378 self._record_commit(base_dir)
376 379
377 380 def _record_commit(self, base_dir):
378 381 import subprocess
379 382 proc = subprocess.Popen('git rev-parse --short HEAD',
380 383 stdout=subprocess.PIPE,
381 384 stderr=subprocess.PIPE,
382 385 shell=True)
383 386 repo_commit, _ = proc.communicate()
384 387 repo_commit = repo_commit.strip().decode("ascii")
385 388
386 389 out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py')
387 390 if os.path.isfile(out_pth) and not repo_commit:
388 391 # nothing to write, don't clobber
389 392 return
390 393
391 394 print("writing git commit '%s' to %s" % (repo_commit, out_pth))
392 395
393 396 # remove to avoid overwriting original via hard link
394 397 try:
395 398 os.remove(out_pth)
396 399 except (IOError, OSError):
397 400 pass
398 401 with open(out_pth, 'w') as out_file:
399 402 out_file.writelines([
400 403 '# GENERATED BY setup.py\n',
401 404 'commit = u"%s"\n' % repo_commit,
402 405 ])
403 406 return MyBuildPy
404 407
General Comments 0
You need to be logged in to leave comments. Login now