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