##// END OF EJS Templates
Refactor with Pathlib for setupbase.py (#14543)...
M Bussonnier -
r28928:25703b5b merge
parent child Browse files
Show More
@@ -1,214 +1,212
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 from pathlib import Path
16 17 import re
17 18 import sys
18 19 from glob import glob
19 20 from logging import log
20 21
21 22 from setuptools import Command
22 23 from setuptools.command.build_py import build_py
23 24
24 25 from setuptools.command.install import install
25 26 from setuptools.command.install_scripts import install_scripts
26 27
27 28
28 29 #-------------------------------------------------------------------------------
29 30 # Useful globals and utility functions
30 31 #-------------------------------------------------------------------------------
31 32
32 33 # A few handy globals
33 isfile = os.path.isfile
34 pjoin = os.path.join
35 repo_root = os.path.dirname(os.path.abspath(__file__))
34 repo_root = Path(__file__).resolve().parent
36 35
37 def execfile(fname, globs, locs=None):
36 def execfile(path, globs, locs=None):
38 37 locs = locs or globs
39 with open(fname, encoding="utf-8") as f:
40 exec(compile(f.read(), fname, "exec"), globs, locs)
38 with path.open(encoding="utf-8") as f:
39 exec(compile(f.read(), str(path), "exec"), globs, locs)
41 40
42 41 #---------------------------------------------------------------------------
43 42 # Basic project information
44 43 #---------------------------------------------------------------------------
45 44
46 45 # release.py contains version, authors, license, url, keywords, etc.
47 execfile(pjoin(repo_root, 'IPython','core','release.py'), globals())
46 execfile(Path(repo_root, "IPython", "core", "release.py"), globals())
48 47
49 48 # Create a dict with the basic information
50 49 # This dict is eventually passed to setup after additional keys are added.
51 50 setup_args = dict(
52 51 author = author,
53 52 author_email = author_email,
54 53 license = license,
55 54 )
56 55
57 56 #---------------------------------------------------------------------------
58 57 # Check package data
59 58 #---------------------------------------------------------------------------
60 59
61 60 def check_package_data(package_data):
62 61 """verify that package_data globs make sense"""
63 62 print("checking package data")
64 63 for pkg, data in package_data.items():
65 pkg_root = pjoin(*pkg.split('.'))
64 pkg_root = Path(*pkg.split("."))
66 65 for d in data:
67 path = pjoin(pkg_root, d)
68 if '*' in path:
69 assert len(glob(path)) > 0, "No files match pattern %s" % path
66 path = pkg_root / d
67 if "*" in str(path):
68 assert len(glob(str(path))) > 0, "No files match pattern %s" % path
70 69 else:
71 assert os.path.exists(path), "Missing package data: %s" % path
70 assert path.exists(), f"Missing package data: {path}"
72 71
73 72
74 73 def check_package_data_first(command):
75 74 """decorator for checking package_data before running a given command
76 75
77 76 Probably only needs to wrap build_py
78 77 """
79 78 class DecoratedCommand(command):
80 79 def run(self):
81 80 check_package_data(self.package_data)
82 81 command.run(self)
83 82 return DecoratedCommand
84 83
85 84
86 85 #---------------------------------------------------------------------------
87 86 # Find data files
88 87 #---------------------------------------------------------------------------
89 88
90 89 def find_data_files():
91 90 """
92 91 Find IPython's data_files.
93 92
94 93 Just man pages at this point.
95 94 """
96 95
97 96 if "freebsd" in sys.platform:
98 manpagebase = pjoin('man', 'man1')
97 manpagebase = Path("man") / "man1"
99 98 else:
100 manpagebase = pjoin('share', 'man', 'man1')
99 manpagebase = Path("share") / "man" / "man1"
101 100
102 101 # Simple file lists can be made by hand
103 manpages = [f for f in glob(pjoin('docs','man','*.1.gz')) if isfile(f)]
102 manpages = [f for f in Path("docs/man").glob("*.1.gz") if f.is_file()]
104 103 if not manpages:
105 104 # When running from a source tree, the manpages aren't gzipped
106 manpages = [f for f in glob(pjoin('docs','man','*.1')) if isfile(f)]
105 manpages = [f for f in Path("docs/man").glob("*.1") if f.is_file()]
107 106
108 107 # And assemble the entire output list
109 data_files = [ (manpagebase, manpages) ]
108 data_files = [(str(manpagebase), [str(f) for f in manpages])]
110 109
111 110 return data_files
112 111
113 112
114 113 # The two functions below are copied from IPython.utils.path, so we don't need
115 114 # to import IPython during setup, which fails on Python 3.
116 115
117 116 def target_outdated(target,deps):
118 117 """Determine whether a target is out of date.
119 118
120 119 target_outdated(target,deps) -> 1/0
121 120
122 121 deps: list of filenames which MUST exist.
123 122 target: single filename which may or may not exist.
124 123
125 124 If target doesn't exist or is older than any file listed in deps, return
126 125 true, otherwise return false.
127 126 """
128 127 try:
129 target_time = os.path.getmtime(target)
130 except os.error:
128 target_time = Path(target).stat().st_mtime
129 except FileNotFoundError:
131 130 return 1
132 131 for dep in deps:
133 dep_time = os.path.getmtime(dep)
132 dep_time = Path(dep).stat().st_mtime
134 133 if dep_time > target_time:
135 134 # print("For target",target,"Dep failed:",dep) # dbg
136 135 # print("times (dep,tar):",dep_time,target_time) # dbg
137 136 return 1
138 137 return 0
139 138
140 139
141 140 def target_update(target,deps,cmd):
142 141 """Update a target with a given command given a list of dependencies.
143 142
144 143 target_update(target,deps,cmd) -> runs cmd if target is outdated.
145 144
146 145 This is just a wrapper around target_outdated() which calls the given
147 146 command if target is outdated."""
148 147
149 148 if target_outdated(target,deps):
150 149 os.system(cmd)
151 150
152 151 #---------------------------------------------------------------------------
153 152 # VCS related
154 153 #---------------------------------------------------------------------------
155 154
156 155 def git_prebuild(pkg_dir, build_cmd=build_py):
157 156 """Return extended build or sdist command class for recording commit
158 157
159 158 records git commit in IPython.utils._sysinfo.commit
160 159
161 160 for use in IPython.utils.sysinfo.sys_info() calls after installation.
162 161 """
163 162
164 163 class MyBuildPy(build_cmd):
165 164 ''' Subclass to write commit data into installation tree '''
166 165 def run(self):
167 166 # loose as `.dev` is suppose to be invalid
168 167 print("check version number")
169 168 loose_pep440re = re.compile(r'^(\d+)\.(\d+)\.(\d+((a|b|rc)\d+)?)(\.post\d+)?(\.dev\d*)?$')
170 169 if not loose_pep440re.match(version):
171 170 raise ValueError("Version number '%s' is not valid (should match [N!]N(.N)*[{a|b|rc}N][.postN][.devN])" % version)
172 171
173 172
174 173 build_cmd.run(self)
175 174 # this one will only fire for build commands
176 175 if hasattr(self, 'build_lib'):
177 176 self._record_commit(self.build_lib)
178 177
179 178 def make_release_tree(self, base_dir, files):
180 179 # this one will fire for sdist
181 180 build_cmd.make_release_tree(self, base_dir, files)
182 181 self._record_commit(base_dir)
183 182
184 183 def _record_commit(self, base_dir):
185 184 import subprocess
186 185 proc = subprocess.Popen('git rev-parse --short HEAD',
187 186 stdout=subprocess.PIPE,
188 187 stderr=subprocess.PIPE,
189 188 shell=True)
190 189 repo_commit, _ = proc.communicate()
191 190 repo_commit = repo_commit.strip().decode("ascii")
192 191
193 out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py')
194 if os.path.isfile(out_pth) and not repo_commit:
192 out_pth = Path(base_dir) / pkg_dir / "utils" / "_sysinfo.py"
193 if out_pth.is_file() and not repo_commit:
195 194 # nothing to write, don't clobber
196 195 return
197 196
198 print("writing git commit '%s' to %s" % (repo_commit, out_pth))
197 print(f"writing git commit '{repo_commit}' to {out_pth}")
199 198
200 199 # remove to avoid overwriting original via hard link
201 200 try:
202 os.remove(out_pth)
203 except (IOError, OSError):
201 out_pth.unlink()
202 except FileNotFoundError:
204 203 pass
205 with open(out_pth, "w", encoding="utf-8") as out_file:
204 with out_pth.open("w", encoding="utf-8") as out_file:
206 205 out_file.writelines(
207 206 [
208 207 "# GENERATED BY setup.py\n",
209 'commit = "%s"\n' % repo_commit,
208 f'commit = "{repo_commit}"\n',
210 209 ]
211 210 )
212 211
213 212 return MyBuildPy
214
General Comments 0
You need to be logged in to leave comments. Login now