##// END OF EJS Templates
setup.py: Remove commands symlink, unsymlink
Matthias Koeppe -
Show More
@@ -1,158 +1,150 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Setup script for IPython.
3 3
4 4 Under Posix environments it works like a typical setup.py script.
5 5 Under Windows, the command sdist is not supported, since IPython
6 6 requires utilities which are not available under Windows."""
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (c) 2008-2011, IPython Development Team.
10 10 # Copyright (c) 2001-2007, Fernando Perez <fernando.perez@colorado.edu>
11 11 # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
12 12 # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
13 13 #
14 14 # Distributed under the terms of the Modified BSD License.
15 15 #
16 16 # The full license is in the file COPYING.rst, distributed with this software.
17 17 #-----------------------------------------------------------------------------
18 18
19 19 import os
20 20 import sys
21 21
22 22 # **Python version check**
23 23 #
24 24 # This check is also made in IPython/__init__, don't forget to update both when
25 25 # changing Python version requirements.
26 26 if sys.version_info < (3, 10):
27 27 pip_message = 'This may be due to an out of date pip. Make sure you have pip >= 9.0.1.'
28 28 try:
29 29 import pip
30 30 pip_version = tuple([int(x) for x in pip.__version__.split('.')[:3]])
31 31 if pip_version < (9, 0, 1) :
32 32 pip_message = 'Your pip version is out of date, please install pip >= 9.0.1. '\
33 33 'pip {} detected.'.format(pip.__version__)
34 34 else:
35 35 # pip is new enough - it must be something else
36 36 pip_message = ''
37 37 except Exception:
38 38 pass
39 39
40 40
41 41 error = """
42 42 IPython 8.19+ supports Python 3.10 and above, following SPEC0
43 43 IPython 8.13+ supports Python 3.9 and above, following NEP 29.
44 44 IPython 8.0-8.12 supports Python 3.8 and above, following NEP 29.
45 45 When using Python 2.7, please install IPython 5.x LTS Long Term Support version.
46 46 Python 3.3 and 3.4 were supported up to IPython 6.x.
47 47 Python 3.5 was supported with IPython 7.0 to 7.9.
48 48 Python 3.6 was supported with IPython up to 7.16.
49 49 Python 3.7 was still supported with the 7.x branch.
50 50
51 51 See IPython `README.rst` file for more information:
52 52
53 53 https://github.com/ipython/ipython/blob/main/README.rst
54 54
55 55 Python {py} detected.
56 56 {pip}
57 57 """.format(
58 58 py=sys.version_info, pip=pip_message
59 59 )
60 60
61 61 print(error, file=sys.stderr)
62 62 sys.exit(1)
63 63
64 64 # At least we're on the python version we need, move on.
65 65
66 66 from setuptools import setup
67 67
68 68 # Our own imports
69 69
70 70 from setupbase import target_update, find_entry_points
71 71
72 72 from setupbase import (
73 73 setup_args,
74 74 check_package_data_first,
75 75 find_data_files,
76 76 git_prebuild,
77 install_symlinked,
78 install_lib_symlink,
79 install_scripts_for_symlink,
80 unsymlink,
81 77 )
82 78
83 79 #-------------------------------------------------------------------------------
84 80 # Handle OS specific things
85 81 #-------------------------------------------------------------------------------
86 82
87 83 if os.name in ('nt','dos'):
88 84 os_name = 'windows'
89 85 else:
90 86 os_name = os.name
91 87
92 88 # Under Windows, 'sdist' has not been supported. Now that the docs build with
93 89 # Sphinx it might work, but let's not turn it on until someone confirms that it
94 90 # actually works.
95 91 if os_name == 'windows' and 'sdist' in sys.argv:
96 92 print('The sdist command is not available under Windows. Exiting.')
97 93 sys.exit(1)
98 94
99 95
100 96 #-------------------------------------------------------------------------------
101 97 # Things related to the IPython documentation
102 98 #-------------------------------------------------------------------------------
103 99
104 100 # update the manuals when building a source dist
105 101 if len(sys.argv) >= 2 and sys.argv[1] in ('sdist','bdist_rpm'):
106 102
107 103 # List of things to be updated. Each entry is a triplet of args for
108 104 # target_update()
109 105 to_update = [
110 106 (
111 107 "docs/man/ipython.1.gz",
112 108 ["docs/man/ipython.1"],
113 109 "cd docs/man && python -m gzip --best ipython.1",
114 110 ),
115 111 ]
116 112
117 113
118 114 [ target_update(*t) for t in to_update ]
119 115
120 116 #---------------------------------------------------------------------------
121 117 # Find all the packages, package data, and data_files
122 118 #---------------------------------------------------------------------------
123 119
124 120 data_files = find_data_files()
125 121
126 122 setup_args['data_files'] = data_files
127 123
128 124 #---------------------------------------------------------------------------
129 125 # custom distutils commands
130 126 #---------------------------------------------------------------------------
131 127 # imports here, so they are after setuptools import if there was one
132 128 from setuptools.command.sdist import sdist
133 129
134 130 setup_args['cmdclass'] = {
135 131 'build_py': \
136 132 check_package_data_first(git_prebuild('IPython')),
137 133 'sdist' : git_prebuild('IPython', sdist),
138 'symlink': install_symlinked,
139 'install_lib_symlink': install_lib_symlink,
140 'install_scripts_sym': install_scripts_for_symlink,
141 'unsymlink': unsymlink,
142 134 }
143 135
144 136 setup_args["entry_points"] = {
145 137 "console_scripts": find_entry_points(),
146 138 "pygments.lexers": [
147 139 "ipythonconsole = IPython.lib.lexers:IPythonConsoleLexer",
148 140 "ipython = IPython.lib.lexers:IPythonLexer",
149 141 "ipython3 = IPython.lib.lexers:IPython3Lexer",
150 142 ],
151 143 }
152 144
153 145 #---------------------------------------------------------------------------
154 146 # Do the actual setup now
155 147 #---------------------------------------------------------------------------
156 148
157 149 if __name__ == "__main__":
158 150 setup(**setup_args)
@@ -1,349 +1,285 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 214 Each of our entry points gets a plain name, e.g. ipython, and a name
215 215 suffixed with the Python major version number, e.g. ipython3.
216 216 """
217 217 ep = [
218 218 'ipython%s = IPython:start_ipython',
219 219 ]
220 220 major_suffix = str(sys.version_info[0])
221 221 return [e % "" for e in ep] + [e % major_suffix for e in ep]
222 222
223
224 class install_lib_symlink(Command):
225 user_options = [
226 ('install-dir=', 'd', "directory to install to"),
227 ]
228
229 def initialize_options(self):
230 self.install_dir = None
231
232 def finalize_options(self):
233 self.set_undefined_options('symlink',
234 ('install_lib', 'install_dir'),
235 )
236
237 def run(self):
238 if sys.platform == 'win32':
239 raise Exception("This doesn't work on Windows.")
240 pkg = os.path.join(os.getcwd(), 'IPython')
241 dest = os.path.join(self.install_dir, 'IPython')
242 if os.path.islink(dest):
243 print('removing existing symlink at %s' % dest)
244 os.unlink(dest)
245 print('symlinking %s -> %s' % (pkg, dest))
246 os.symlink(pkg, dest)
247
248 class unsymlink(install):
249 def run(self):
250 dest = os.path.join(self.install_lib, 'IPython')
251 if os.path.islink(dest):
252 print('removing symlink at %s' % dest)
253 os.unlink(dest)
254 else:
255 print('No symlink exists at %s' % dest)
256
257 class install_symlinked(install):
258 def run(self):
259 if sys.platform == 'win32':
260 raise Exception("This doesn't work on Windows.")
261
262 # Run all sub-commands (at least those that need to be run)
263 for cmd_name in self.get_sub_commands():
264 self.run_command(cmd_name)
265
266 # 'sub_commands': a list of commands this command might have to run to
267 # get its work done. See cmd.py for more info.
268 sub_commands = [('install_lib_symlink', lambda self:True),
269 ('install_scripts_sym', lambda self:True),
270 ]
271
272 class install_scripts_for_symlink(install_scripts):
273 """Redefined to get options from 'symlink' instead of 'install'.
274
275 I love distutils almost as much as I love setuptools.
276 """
277 def finalize_options(self):
278 self.set_undefined_options('build', ('build_scripts', 'build_dir'))
279 self.set_undefined_options('symlink',
280 ('install_scripts', 'install_dir'),
281 ('force', 'force'),
282 ('skip_build', 'skip_build'),
283 )
284
285
286 223 #---------------------------------------------------------------------------
287 224 # VCS related
288 225 #---------------------------------------------------------------------------
289 226
290
291 227 def git_prebuild(pkg_dir, build_cmd=build_py):
292 228 """Return extended build or sdist command class for recording commit
293 229
294 230 records git commit in IPython.utils._sysinfo.commit
295 231
296 232 for use in IPython.utils.sysinfo.sys_info() calls after installation.
297 233 """
298 234
299 235 class MyBuildPy(build_cmd):
300 236 ''' Subclass to write commit data into installation tree '''
301 237 def run(self):
302 238 # loose as `.dev` is suppose to be invalid
303 239 print("check version number")
304 240 loose_pep440re = re.compile(r'^(\d+)\.(\d+)\.(\d+((a|b|rc)\d+)?)(\.post\d+)?(\.dev\d*)?$')
305 241 if not loose_pep440re.match(version):
306 242 raise ValueError("Version number '%s' is not valid (should match [N!]N(.N)*[{a|b|rc}N][.postN][.devN])" % version)
307 243
308 244
309 245 build_cmd.run(self)
310 246 # this one will only fire for build commands
311 247 if hasattr(self, 'build_lib'):
312 248 self._record_commit(self.build_lib)
313 249
314 250 def make_release_tree(self, base_dir, files):
315 251 # this one will fire for sdist
316 252 build_cmd.make_release_tree(self, base_dir, files)
317 253 self._record_commit(base_dir)
318 254
319 255 def _record_commit(self, base_dir):
320 256 import subprocess
321 257 proc = subprocess.Popen('git rev-parse --short HEAD',
322 258 stdout=subprocess.PIPE,
323 259 stderr=subprocess.PIPE,
324 260 shell=True)
325 261 repo_commit, _ = proc.communicate()
326 262 repo_commit = repo_commit.strip().decode("ascii")
327 263
328 264 out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py')
329 265 if os.path.isfile(out_pth) and not repo_commit:
330 266 # nothing to write, don't clobber
331 267 return
332 268
333 269 print("writing git commit '%s' to %s" % (repo_commit, out_pth))
334 270
335 271 # remove to avoid overwriting original via hard link
336 272 try:
337 273 os.remove(out_pth)
338 274 except (IOError, OSError):
339 275 pass
340 276 with open(out_pth, "w", encoding="utf-8") as out_file:
341 277 out_file.writelines(
342 278 [
343 279 "# GENERATED BY setup.py\n",
344 280 'commit = "%s"\n' % repo_commit,
345 281 ]
346 282 )
347 283
348 284 return MyBuildPy
349 285
General Comments 0
You need to be logged in to leave comments. Login now