##// END OF EJS Templates
setupbase.py: Remove duplication of package_data
Matthias Koeppe -
Show More
@@ -1,48 +1,47
1 1 include README.rst
2 2 include COPYING.rst
3 3 include LICENSE
4 4 include setupbase.py
5 5 include _build_meta.py
6 6 include MANIFEST.in
7 include py.typed
8 7 include .mailmap
9 8 include .flake8
10 9 include .pre-commit-config.yaml
11 10 include long_description.rst
12 11
13 12 recursive-exclude tools *
14 13 exclude tools
15 14 exclude CONTRIBUTING.md
16 15 exclude .editorconfig
17 16 exclude SECURITY.md
18 17 exclude .readthedocs.yaml
19 18
20 19 graft scripts
21 20
22 21 # Load main dir but exclude things we don't want in the distro
23 22 graft IPython
24 23
25 24 # Documentation
26 25 graft docs
27 26 exclude docs/\#*
28 27 exclude docs/man/*.1.gz
29 28
30 29 exclude .git-blame-ignore-revs
31 30
32 31 # Examples
33 32 graft examples
34 33
35 34 # docs subdirs we want to skip
36 35 prune docs/build
37 36 prune docs/gh-pages
38 37 prune docs/dist
39 38
40 39 # Patterns to exclude from any directory
41 40 global-exclude *~
42 41 global-exclude *.flc
43 42 global-exclude *.yml
44 43 global-exclude *.pyc
45 44 global-exclude *.pyo
46 45 global-exclude .dircopy.log
47 46 global-exclude .git
48 47 global-exclude .ipynb_checkpoints
@@ -1,205 +1,204
1 1 [build-system]
2 2 requires = ["setuptools>=61.2"]
3 3 # We need access to the 'setupbase' module at build time.
4 4 # Hence we declare a custom build backend.
5 5 build-backend = "_build_meta" # just re-exports setuptools.build_meta definitions
6 6 backend-path = ["."]
7 7
8 8 [project]
9 9 name = "ipython"
10 10 description = "IPython: Productive Interactive Computing"
11 11 keywords = ["Interactive", "Interpreter", "Shell", "Embedding"]
12 12 classifiers = [
13 13 "Framework :: IPython",
14 14 "Framework :: Jupyter",
15 15 "Intended Audience :: Developers",
16 16 "Intended Audience :: Science/Research",
17 17 "License :: OSI Approved :: BSD License",
18 18 "Programming Language :: Python",
19 19 "Programming Language :: Python :: 3",
20 20 "Programming Language :: Python :: 3 :: Only",
21 21 "Topic :: System :: Shells",
22 22 ]
23 23 requires-python = ">=3.10"
24 24 dependencies = [
25 25 'colorama; sys_platform == "win32"',
26 26 "decorator",
27 27 "exceptiongroup; python_version<'3.11'",
28 28 "jedi>=0.16",
29 29 "matplotlib-inline",
30 30 'pexpect>4.3; sys_platform != "win32" and sys_platform != "emscripten"',
31 31 "prompt_toolkit>=3.0.41,<3.1.0",
32 32 "pygments>=2.4.0",
33 33 "stack_data",
34 34 "traitlets>=5",
35 35 "typing_extensions; python_version<'3.10'",
36 36 ]
37 37 dynamic = ["authors", "entry-points", "license", "scripts", "version"]
38 38
39 39 [project.readme]
40 40 file = "long_description.rst"
41 41 content-type = "text/x-rst"
42 42
43 43 [project.urls]
44 44 Homepage = "https://ipython.org"
45 45 Documentation = "https://ipython.readthedocs.io/"
46 46 Funding = "https://numfocus.org/"
47 47 Source = "https://github.com/ipython/ipython"
48 48 Tracker = "https://github.com/ipython/ipython/issues"
49 49
50 50 [project.optional-dependencies]
51 51 black = [
52 52 "black",
53 53 ]
54 54 doc = [
55 55 "ipykernel",
56 56 "setuptools>=18.5",
57 57 "sphinx>=1.3",
58 58 "sphinx-rtd-theme",
59 59 "docrepr",
60 60 "matplotlib",
61 61 "stack_data",
62 62 "typing_extensions",
63 63 "exceptiongroup",
64 64 "ipython[test]",
65 65 ]
66 66 kernel = [
67 67 "ipykernel",
68 68 ]
69 69 nbconvert = [
70 70 "nbconvert",
71 71 ]
72 72 nbformat = [
73 73 "nbformat",
74 74 ]
75 75 notebook = [
76 76 "ipywidgets",
77 77 "notebook",
78 78 ]
79 79 parallel = [
80 80 "ipyparallel",
81 81 ]
82 82 qtconsole = [
83 83 "qtconsole",
84 84 ]
85 85 terminal = []
86 86 test = [
87 87 "pytest<8",
88 88 "pytest-asyncio<0.22",
89 89 "testpath",
90 90 "pickleshare",
91 91 ]
92 92 test_extra = [
93 93 "ipython[test]",
94 94 "curio",
95 95 "matplotlib!=3.2.0",
96 96 "nbformat",
97 97 "numpy>=1.23",
98 98 "pandas",
99 99 "trio",
100 100 ]
101 101 all = [
102 102 "ipython[black,doc,kernel,nbconvert,nbformat,notebook,parallel,qtconsole,terminal]",
103 103 "ipython[test,test_extra]",
104 104 ]
105 105
106 106 [tool.mypy]
107 107 python_version = "3.10"
108 108 ignore_missing_imports = true
109 109 follow_imports = 'silent'
110 110 exclude = [
111 111 'test_\.+\.py',
112 112 'IPython.utils.tests.test_wildcard',
113 113 'testing',
114 114 'tests',
115 115 'PyColorize.py',
116 116 '_process_win32_controller.py',
117 117 'IPython/core/application.py',
118 118 'IPython/core/completerlib.py',
119 119 'IPython/core/displaypub.py',
120 120 'IPython/core/historyapp.py',
121 121 #'IPython/core/interactiveshell.py',
122 122 'IPython/core/magic.py',
123 123 'IPython/core/profileapp.py',
124 124 # 'IPython/core/ultratb.py',
125 125 'IPython/lib/deepreload.py',
126 126 'IPython/lib/pretty.py',
127 127 'IPython/sphinxext/ipython_directive.py',
128 128 'IPython/terminal/ipapp.py',
129 129 'IPython/utils/_process_win32.py',
130 130 'IPython/utils/path.py',
131 131 'IPython/utils/timing.py',
132 132 'IPython/utils/text.py'
133 133 ]
134 134
135 135 [tool.pytest.ini_options]
136 136 addopts = [
137 137 "--durations=10",
138 138 "-pIPython.testing.plugin.pytest_ipdoctest",
139 139 "--ipdoctest-modules",
140 140 "--ignore=docs",
141 141 "--ignore=examples",
142 142 "--ignore=htmlcov",
143 143 "--ignore=ipython_kernel",
144 144 "--ignore=ipython_parallel",
145 145 "--ignore=results",
146 146 "--ignore=tmp",
147 147 "--ignore=tools",
148 148 "--ignore=traitlets",
149 149 "--ignore=IPython/core/tests/daft_extension",
150 150 "--ignore=IPython/sphinxext",
151 151 "--ignore=IPython/terminal/pt_inputhooks",
152 152 "--ignore=IPython/__main__.py",
153 153 "--ignore=IPython/external/qt_for_kernel.py",
154 154 "--ignore=IPython/html/widgets/widget_link.py",
155 155 "--ignore=IPython/html/widgets/widget_output.py",
156 156 "--ignore=IPython/terminal/console.py",
157 157 "--ignore=IPython/utils/_process_cli.py",
158 158 "--ignore=IPython/utils/_process_posix.py",
159 159 "--ignore=IPython/utils/_process_win32.py",
160 160 "--ignore=IPython/utils/_process_win32_controller.py",
161 161 "--ignore=IPython/utils/daemonize.py",
162 162 "--ignore=IPython/utils/eventful.py",
163 163 "--ignore=IPython/kernel",
164 164 "--ignore=IPython/consoleapp.py",
165 165 "--ignore=IPython/core/inputsplitter.py",
166 166 "--ignore=IPython/lib/kernel.py",
167 167 "--ignore=IPython/utils/jsonutil.py",
168 168 "--ignore=IPython/utils/localinterfaces.py",
169 169 "--ignore=IPython/utils/log.py",
170 170 "--ignore=IPython/utils/signatures.py",
171 171 "--ignore=IPython/utils/traitlets.py",
172 172 "--ignore=IPython/utils/version.py"
173 173 ]
174 174 doctest_optionflags = [
175 175 "NORMALIZE_WHITESPACE",
176 176 "ELLIPSIS"
177 177 ]
178 178 ipdoctest_optionflags = [
179 179 "NORMALIZE_WHITESPACE",
180 180 "ELLIPSIS"
181 181 ]
182 182 asyncio_mode = "strict"
183 183
184 184 [tool.pyright]
185 185 pythonPlatform="All"
186 186
187 187 [tool.setuptools]
188 188 zip-safe = false
189 189 platforms = ["Linux", "Mac OSX", "Windows"]
190 190 license-files = ["LICENSE"]
191 include-package-data = false
192 191
193 192 [tool.setuptools.packages.find]
194 193 exclude = ["setupext"]
195 194 namespaces = false
196 195
197 196 [tool.setuptools.package-data]
198 197 ipython = ["py.typed"]
199 198 "ipython.core" = ["profile/README*"]
200 199 "ipython.core.tests" = ["*.png", "*.jpg", "daft_extension/*.py"]
201 200 "ipython.lib.tests" = ["*.wav"]
202 201 "ipython.testing.plugin" = ["*.txt"]
203 202
204 203 [tool.setuptools.dynamic]
205 204 version = {attr = "IPython.core.release.__version__"}
@@ -1,349 +1,332
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 # Find package data
92 # Check package data
93 93 #---------------------------------------------------------------------------
94 94
95 def find_package_data():
96 """
97 Find IPython's package_data.
98 """
99 # This is not enough for these things to appear in an sdist.
100 # We need to muck with the MANIFEST to get this to work
101
102 package_data = {
103 'IPython.core' : ['profile/README*'],
104 'IPython.core.tests' : ['*.png', '*.jpg', 'daft_extension/*.py'],
105 'IPython.lib.tests' : ['*.wav'],
106 'IPython.testing.plugin' : ['*.txt'],
107 }
108
109 return package_data
110
111
112 95 def check_package_data(package_data):
113 96 """verify that package_data globs make sense"""
114 97 print("checking package data")
115 98 for pkg, data in package_data.items():
116 99 pkg_root = pjoin(*pkg.split('.'))
117 100 for d in data:
118 101 path = pjoin(pkg_root, d)
119 102 if '*' in path:
120 103 assert len(glob(path)) > 0, "No files match pattern %s" % path
121 104 else:
122 105 assert os.path.exists(path), "Missing package data: %s" % path
123 106
124 107
125 108 def check_package_data_first(command):
126 109 """decorator for checking package_data before running a given command
127 110
128 111 Probably only needs to wrap build_py
129 112 """
130 113 class DecoratedCommand(command):
131 114 def run(self):
132 115 check_package_data(self.package_data)
133 116 command.run(self)
134 117 return DecoratedCommand
135 118
136 119
137 120 #---------------------------------------------------------------------------
138 121 # Find data files
139 122 #---------------------------------------------------------------------------
140 123
141 124 def find_data_files():
142 125 """
143 126 Find IPython's data_files.
144 127
145 128 Just man pages at this point.
146 129 """
147 130
148 131 if "freebsd" in sys.platform:
149 132 manpagebase = pjoin('man', 'man1')
150 133 else:
151 134 manpagebase = pjoin('share', 'man', 'man1')
152 135
153 136 # Simple file lists can be made by hand
154 137 manpages = [f for f in glob(pjoin('docs','man','*.1.gz')) if isfile(f)]
155 138 if not manpages:
156 139 # When running from a source tree, the manpages aren't gzipped
157 140 manpages = [f for f in glob(pjoin('docs','man','*.1')) if isfile(f)]
158 141
159 142 # And assemble the entire output list
160 143 data_files = [ (manpagebase, manpages) ]
161 144
162 145 return data_files
163 146
164 147
165 148 # The two functions below are copied from IPython.utils.path, so we don't need
166 149 # to import IPython during setup, which fails on Python 3.
167 150
168 151 def target_outdated(target,deps):
169 152 """Determine whether a target is out of date.
170 153
171 154 target_outdated(target,deps) -> 1/0
172 155
173 156 deps: list of filenames which MUST exist.
174 157 target: single filename which may or may not exist.
175 158
176 159 If target doesn't exist or is older than any file listed in deps, return
177 160 true, otherwise return false.
178 161 """
179 162 try:
180 163 target_time = os.path.getmtime(target)
181 164 except os.error:
182 165 return 1
183 166 for dep in deps:
184 167 dep_time = os.path.getmtime(dep)
185 168 if dep_time > target_time:
186 169 #print "For target",target,"Dep failed:",dep # dbg
187 170 #print "times (dep,tar):",dep_time,target_time # dbg
188 171 return 1
189 172 return 0
190 173
191 174
192 175 def target_update(target,deps,cmd):
193 176 """Update a target with a given command given a list of dependencies.
194 177
195 178 target_update(target,deps,cmd) -> runs cmd if target is outdated.
196 179
197 180 This is just a wrapper around target_outdated() which calls the given
198 181 command if target is outdated."""
199 182
200 183 if target_outdated(target,deps):
201 184 os.system(cmd)
202 185
203 186 #---------------------------------------------------------------------------
204 187 # Find scripts
205 188 #---------------------------------------------------------------------------
206 189
207 190 def find_entry_points():
208 191 """Defines the command line entry points for IPython
209 192
210 193 This always uses setuptools-style entry points. When setuptools is not in
211 194 use, our own build_scripts_entrypt class below parses these and builds
212 195 command line scripts.
213 196
214 197 Each of our entry points gets a plain name, e.g. ipython, and a name
215 198 suffixed with the Python major version number, e.g. ipython3.
216 199 """
217 200 ep = [
218 201 'ipython%s = IPython:start_ipython',
219 202 ]
220 203 major_suffix = str(sys.version_info[0])
221 204 return [e % "" for e in ep] + [e % major_suffix for e in ep]
222 205
223 206
224 207 class install_lib_symlink(Command):
225 208 user_options = [
226 209 ('install-dir=', 'd', "directory to install to"),
227 210 ]
228 211
229 212 def initialize_options(self):
230 213 self.install_dir = None
231 214
232 215 def finalize_options(self):
233 216 self.set_undefined_options('symlink',
234 217 ('install_lib', 'install_dir'),
235 218 )
236 219
237 220 def run(self):
238 221 if sys.platform == 'win32':
239 222 raise Exception("This doesn't work on Windows.")
240 223 pkg = os.path.join(os.getcwd(), 'IPython')
241 224 dest = os.path.join(self.install_dir, 'IPython')
242 225 if os.path.islink(dest):
243 226 print('removing existing symlink at %s' % dest)
244 227 os.unlink(dest)
245 228 print('symlinking %s -> %s' % (pkg, dest))
246 229 os.symlink(pkg, dest)
247 230
248 231 class unsymlink(install):
249 232 def run(self):
250 233 dest = os.path.join(self.install_lib, 'IPython')
251 234 if os.path.islink(dest):
252 235 print('removing symlink at %s' % dest)
253 236 os.unlink(dest)
254 237 else:
255 238 print('No symlink exists at %s' % dest)
256 239
257 240 class install_symlinked(install):
258 241 def run(self):
259 242 if sys.platform == 'win32':
260 243 raise Exception("This doesn't work on Windows.")
261 244
262 245 # Run all sub-commands (at least those that need to be run)
263 246 for cmd_name in self.get_sub_commands():
264 247 self.run_command(cmd_name)
265 248
266 249 # 'sub_commands': a list of commands this command might have to run to
267 250 # get its work done. See cmd.py for more info.
268 251 sub_commands = [('install_lib_symlink', lambda self:True),
269 252 ('install_scripts_sym', lambda self:True),
270 253 ]
271 254
272 255 class install_scripts_for_symlink(install_scripts):
273 256 """Redefined to get options from 'symlink' instead of 'install'.
274 257
275 258 I love distutils almost as much as I love setuptools.
276 259 """
277 260 def finalize_options(self):
278 261 self.set_undefined_options('build', ('build_scripts', 'build_dir'))
279 262 self.set_undefined_options('symlink',
280 263 ('install_scripts', 'install_dir'),
281 264 ('force', 'force'),
282 265 ('skip_build', 'skip_build'),
283 266 )
284 267
285 268
286 269 #---------------------------------------------------------------------------
287 270 # VCS related
288 271 #---------------------------------------------------------------------------
289 272
290 273
291 274 def git_prebuild(pkg_dir, build_cmd=build_py):
292 275 """Return extended build or sdist command class for recording commit
293 276
294 277 records git commit in IPython.utils._sysinfo.commit
295 278
296 279 for use in IPython.utils.sysinfo.sys_info() calls after installation.
297 280 """
298 281
299 282 class MyBuildPy(build_cmd):
300 283 ''' Subclass to write commit data into installation tree '''
301 284 def run(self):
302 285 # loose as `.dev` is suppose to be invalid
303 286 print("check version number")
304 287 loose_pep440re = re.compile(r'^(\d+)\.(\d+)\.(\d+((a|b|rc)\d+)?)(\.post\d+)?(\.dev\d*)?$')
305 288 if not loose_pep440re.match(version):
306 289 raise ValueError("Version number '%s' is not valid (should match [N!]N(.N)*[{a|b|rc}N][.postN][.devN])" % version)
307 290
308 291
309 292 build_cmd.run(self)
310 293 # this one will only fire for build commands
311 294 if hasattr(self, 'build_lib'):
312 295 self._record_commit(self.build_lib)
313 296
314 297 def make_release_tree(self, base_dir, files):
315 298 # this one will fire for sdist
316 299 build_cmd.make_release_tree(self, base_dir, files)
317 300 self._record_commit(base_dir)
318 301
319 302 def _record_commit(self, base_dir):
320 303 import subprocess
321 304 proc = subprocess.Popen('git rev-parse --short HEAD',
322 305 stdout=subprocess.PIPE,
323 306 stderr=subprocess.PIPE,
324 307 shell=True)
325 308 repo_commit, _ = proc.communicate()
326 309 repo_commit = repo_commit.strip().decode("ascii")
327 310
328 311 out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py')
329 312 if os.path.isfile(out_pth) and not repo_commit:
330 313 # nothing to write, don't clobber
331 314 return
332 315
333 316 print("writing git commit '%s' to %s" % (repo_commit, out_pth))
334 317
335 318 # remove to avoid overwriting original via hard link
336 319 try:
337 320 os.remove(out_pth)
338 321 except (IOError, OSError):
339 322 pass
340 323 with open(out_pth, "w", encoding="utf-8") as out_file:
341 324 out_file.writelines(
342 325 [
343 326 "# GENERATED BY setup.py\n",
344 327 'commit = "%s"\n' % repo_commit,
345 328 ]
346 329 )
347 330
348 331 return MyBuildPy
349 332
General Comments 0
You need to be logged in to leave comments. Login now