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