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