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