##// END OF EJS Templates
setup: subclass distribution instead of overwriting original
Simon Heimberg -
r15458:c3a6ec30 default
parent child Browse files
Show More
@@ -1,443 +1,448 b''
1 1 #
2 2 # This is the mercurial setup script.
3 3 #
4 4 # 'python setup.py install', or
5 5 # 'python setup.py --help' for more options
6 6
7 7 import sys, platform
8 8 if getattr(sys, 'version_info', (0, 0, 0)) < (2, 4, 0, 'final'):
9 9 raise SystemExit("Mercurial requires Python 2.4 or later.")
10 10
11 11 if sys.version_info[0] >= 3:
12 12 def b(s):
13 13 '''A helper function to emulate 2.6+ bytes literals using string
14 14 literals.'''
15 15 return s.encode('latin1')
16 16 else:
17 17 def b(s):
18 18 '''A helper function to emulate 2.6+ bytes literals using string
19 19 literals.'''
20 20 return s
21 21
22 22 # Solaris Python packaging brain damage
23 23 try:
24 24 import hashlib
25 25 sha = hashlib.sha1()
26 26 except:
27 27 try:
28 28 import sha
29 29 except:
30 30 raise SystemExit(
31 31 "Couldn't import standard hashlib (incomplete Python install).")
32 32
33 33 try:
34 34 import zlib
35 35 except:
36 36 raise SystemExit(
37 37 "Couldn't import standard zlib (incomplete Python install).")
38 38
39 39 # The base IronPython distribution (as of 2.7.1) doesn't support bz2
40 40 isironpython = False
41 41 try:
42 42 isironpython = platform.python_implementation().lower().find("ironpython") != -1
43 43 except:
44 44 pass
45 45
46 46 if isironpython:
47 47 print "warning: IronPython detected (no bz2 support)"
48 48 else:
49 49 try:
50 50 import bz2
51 51 except:
52 52 raise SystemExit(
53 53 "Couldn't import standard bz2 (incomplete Python install).")
54 54
55 55 import os, subprocess, time
56 56 import shutil
57 57 import tempfile
58 58 from distutils import log
59 59 from distutils.core import setup, Command, Extension
60 60 from distutils.dist import Distribution
61 61 from distutils.command.build import build
62 62 from distutils.command.build_ext import build_ext
63 63 from distutils.command.build_py import build_py
64 64 from distutils.command.install_scripts import install_scripts
65 65 from distutils.spawn import spawn, find_executable
66 66 from distutils.ccompiler import new_compiler
67 67 from distutils.errors import CCompilerError, DistutilsExecError
68 68 from distutils.sysconfig import get_python_inc
69 69 from distutils.version import StrictVersion
70 70
71 71 scripts = ['hg']
72 72 if os.name == 'nt':
73 73 scripts.append('contrib/win32/hg.bat')
74 74
75 75 # simplified version of distutils.ccompiler.CCompiler.has_function
76 76 # that actually removes its temporary files.
77 77 def hasfunction(cc, funcname):
78 78 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
79 79 devnull = oldstderr = None
80 80 try:
81 81 try:
82 82 fname = os.path.join(tmpdir, 'funcname.c')
83 83 f = open(fname, 'w')
84 84 f.write('int main(void) {\n')
85 85 f.write(' %s();\n' % funcname)
86 86 f.write('}\n')
87 87 f.close()
88 88 # Redirect stderr to /dev/null to hide any error messages
89 89 # from the compiler.
90 90 # This will have to be changed if we ever have to check
91 91 # for a function on Windows.
92 92 devnull = open('/dev/null', 'w')
93 93 oldstderr = os.dup(sys.stderr.fileno())
94 94 os.dup2(devnull.fileno(), sys.stderr.fileno())
95 95 objects = cc.compile([fname], output_dir=tmpdir)
96 96 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
97 97 except:
98 98 return False
99 99 return True
100 100 finally:
101 101 if oldstderr is not None:
102 102 os.dup2(oldstderr, sys.stderr.fileno())
103 103 if devnull is not None:
104 104 devnull.close()
105 105 shutil.rmtree(tmpdir)
106 106
107 107 # py2exe needs to be installed to work
108 108 try:
109 109 import py2exe
110 110 py2exeloaded = True
111 111 except ImportError:
112 112 py2exeloaded = False
113 113
114 114 def runcmd(cmd, env):
115 115 p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
116 116 stderr=subprocess.PIPE, env=env)
117 117 out, err = p.communicate()
118 118 return out, err
119 119
120 120 def runhg(cmd, env):
121 121 out, err = runcmd(cmd, env)
122 122 # If root is executing setup.py, but the repository is owned by
123 123 # another user (as in "sudo python setup.py install") we will get
124 124 # trust warnings since the .hg/hgrc file is untrusted. That is
125 125 # fine, we don't want to load it anyway. Python may warn about
126 126 # a missing __init__.py in mercurial/locale, we also ignore that.
127 127 err = [e for e in err.splitlines()
128 128 if not e.startswith(b('Not trusting file')) \
129 129 and not e.startswith(b('warning: Not importing'))]
130 130 if err:
131 131 return ''
132 132 return out
133 133
134 134 version = ''
135 135
136 136 # Execute hg out of this directory with a custom environment which
137 137 # includes the pure Python modules in mercurial/pure. We also take
138 138 # care to not use any hgrc files and do no localization.
139 139 pypath = ['mercurial', os.path.join('mercurial', 'pure')]
140 140 env = {'PYTHONPATH': os.pathsep.join(pypath),
141 141 'HGRCPATH': '',
142 142 'LANGUAGE': 'C'}
143 143 if 'LD_LIBRARY_PATH' in os.environ:
144 144 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
145 145 if 'SystemRoot' in os.environ:
146 146 # Copy SystemRoot into the custom environment for Python 2.6
147 147 # under Windows. Otherwise, the subprocess will fail with
148 148 # error 0xc0150004. See: http://bugs.python.org/issue3440
149 149 env['SystemRoot'] = os.environ['SystemRoot']
150 150
151 151 if os.path.isdir('.hg'):
152 152 cmd = [sys.executable, 'hg', 'id', '-i', '-t']
153 153 l = runhg(cmd, env).split()
154 154 while len(l) > 1 and l[-1][0].isalpha(): # remove non-numbered tags
155 155 l.pop()
156 156 if len(l) > 1: # tag found
157 157 version = l[-1]
158 158 if l[0].endswith('+'): # propagate the dirty status to the tag
159 159 version += '+'
160 160 elif len(l) == 1: # no tag found
161 161 cmd = [sys.executable, 'hg', 'parents', '--template',
162 162 '{latesttag}+{latesttagdistance}-']
163 163 version = runhg(cmd, env) + l[0]
164 164 if version.endswith('+'):
165 165 version += time.strftime('%Y%m%d')
166 166 elif os.path.exists('.hg_archival.txt'):
167 167 kw = dict([[t.strip() for t in l.split(':', 1)]
168 168 for l in open('.hg_archival.txt')])
169 169 if 'tag' in kw:
170 170 version = kw['tag']
171 171 elif 'latesttag' in kw:
172 172 version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
173 173 else:
174 174 version = kw.get('node', '')[:12]
175 175
176 176 if version:
177 177 f = open("mercurial/__version__.py", "w")
178 178 f.write('# this file is autogenerated by setup.py\n')
179 179 f.write('version = "%s"\n' % version)
180 180 f.close()
181 181
182 182
183 183 try:
184 184 from mercurial import __version__
185 185 version = __version__.version
186 186 except ImportError:
187 187 version = 'unknown'
188 188
189 189 class hgbuildmo(build):
190 190
191 191 description = "build translations (.mo files)"
192 192
193 193 def run(self):
194 194 if not find_executable('msgfmt'):
195 195 self.warn("could not find msgfmt executable, no translations "
196 196 "will be built")
197 197 return
198 198
199 199 podir = 'i18n'
200 200 if not os.path.isdir(podir):
201 201 self.warn("could not find %s/ directory" % podir)
202 202 return
203 203
204 204 join = os.path.join
205 205 for po in os.listdir(podir):
206 206 if not po.endswith('.po'):
207 207 continue
208 208 pofile = join(podir, po)
209 209 modir = join('locale', po[:-3], 'LC_MESSAGES')
210 210 mofile = join(modir, 'hg.mo')
211 211 mobuildfile = join('mercurial', mofile)
212 212 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
213 213 if sys.platform != 'sunos5':
214 214 # msgfmt on Solaris does not know about -c
215 215 cmd.append('-c')
216 216 self.mkpath(join('mercurial', modir))
217 217 self.make_file([pofile], mobuildfile, spawn, (cmd,))
218 218
219 219
220 220 # Insert hgbuildmo first so that files in mercurial/locale/ are found
221 221 # when build_py is run next.
222 222 build.sub_commands.insert(0, ('build_mo', None))
223 223
224 Distribution.pure = 0
225 Distribution.global_options.append(('pure', None, "use pure (slow) Python "
226 "code instead of C extensions"))
224 class hgdist(Distribution):
225 pure = 0
226
227 global_options = Distribution.global_options + \
228 [('pure', None, "use pure (slow) Python "
229 "code instead of C extensions"),
230 ]
227 231
228 232 class hgbuildext(build_ext):
229 233
230 234 def build_extension(self, ext):
231 235 try:
232 236 build_ext.build_extension(self, ext)
233 237 except CCompilerError:
234 238 if not getattr(ext, 'optional', False):
235 239 raise
236 240 log.warn("Failed to build optional extension '%s' (skipping)",
237 241 ext.name)
238 242
239 243 class hgbuildpy(build_py):
240 244
241 245 def finalize_options(self):
242 246 build_py.finalize_options(self)
243 247
244 248 if self.distribution.pure:
245 249 if self.py_modules is None:
246 250 self.py_modules = []
247 251 for ext in self.distribution.ext_modules:
248 252 if ext.name.startswith("mercurial."):
249 253 self.py_modules.append("mercurial.pure.%s" % ext.name[10:])
250 254 self.distribution.ext_modules = []
251 255 else:
252 256 if not os.path.exists(os.path.join(get_python_inc(), 'Python.h')):
253 257 raise SystemExit("Python headers are required to build Mercurial")
254 258
255 259 def find_modules(self):
256 260 modules = build_py.find_modules(self)
257 261 for module in modules:
258 262 if module[0] == "mercurial.pure":
259 263 if module[1] != "__init__":
260 264 yield ("mercurial", module[1], module[2])
261 265 else:
262 266 yield module
263 267
264 268 class buildhgextindex(Command):
265 269 description = 'generate prebuilt index of hgext (for frozen package)'
266 270 user_options = []
267 271 _indexfilename = 'hgext/__index__.py'
268 272
269 273 def initialize_options(self):
270 274 pass
271 275
272 276 def finalize_options(self):
273 277 pass
274 278
275 279 def run(self):
276 280 if os.path.exists(self._indexfilename):
277 281 os.unlink(self._indexfilename)
278 282
279 283 # here no extension enabled, disabled() lists up everything
280 284 code = ('import pprint; from mercurial import extensions; '
281 285 'pprint.pprint(extensions.disabled())')
282 286 out, err = runcmd([sys.executable, '-c', code], env)
283 287 if err:
284 288 raise DistutilsExecError(err)
285 289
286 290 f = open(self._indexfilename, 'w')
287 291 f.write('# this file is autogenerated by setup.py\n')
288 292 f.write('docs = ')
289 293 f.write(out)
290 294 f.close()
291 295
292 296 class hginstallscripts(install_scripts):
293 297 '''
294 298 This is a specialization of install_scripts that replaces the @LIBDIR@ with
295 299 the configured directory for modules. If possible, the path is made relative
296 300 to the directory for scripts.
297 301 '''
298 302
299 303 def initialize_options(self):
300 304 install_scripts.initialize_options(self)
301 305
302 306 self.install_lib = None
303 307
304 308 def finalize_options(self):
305 309 install_scripts.finalize_options(self)
306 310 self.set_undefined_options('install',
307 311 ('install_lib', 'install_lib'))
308 312
309 313 def run(self):
310 314 install_scripts.run(self)
311 315
312 316 if (os.path.splitdrive(self.install_dir)[0] !=
313 317 os.path.splitdrive(self.install_lib)[0]):
314 318 # can't make relative paths from one drive to another, so use an
315 319 # absolute path instead
316 320 libdir = self.install_lib
317 321 else:
318 322 common = os.path.commonprefix((self.install_dir, self.install_lib))
319 323 rest = self.install_dir[len(common):]
320 324 uplevel = len([n for n in os.path.split(rest) if n])
321 325
322 326 libdir = uplevel * ('..' + os.sep) + self.install_lib[len(common):]
323 327
324 328 for outfile in self.outfiles:
325 329 fp = open(outfile, 'rb')
326 330 data = fp.read()
327 331 fp.close()
328 332
329 333 # skip binary files
330 334 if '\0' in data:
331 335 continue
332 336
333 337 data = data.replace('@LIBDIR@', libdir.encode('string_escape'))
334 338 fp = open(outfile, 'wb')
335 339 fp.write(data)
336 340 fp.close()
337 341
338 342 cmdclass = {'build_mo': hgbuildmo,
339 343 'build_ext': hgbuildext,
340 344 'build_py': hgbuildpy,
341 345 'build_hgextindex': buildhgextindex,
342 346 'install_scripts': hginstallscripts}
343 347
344 348 packages = ['mercurial', 'mercurial.hgweb',
345 349 'mercurial.httpclient', 'mercurial.httpclient.tests',
346 350 'hgext', 'hgext.convert', 'hgext.highlight', 'hgext.zeroconf',
347 351 'hgext.largefiles']
348 352
349 353 pymodules = []
350 354
351 355 extmodules = [
352 356 Extension('mercurial.base85', ['mercurial/base85.c']),
353 357 Extension('mercurial.bdiff', ['mercurial/bdiff.c']),
354 358 Extension('mercurial.diffhelpers', ['mercurial/diffhelpers.c']),
355 359 Extension('mercurial.mpatch', ['mercurial/mpatch.c']),
356 360 Extension('mercurial.parsers', ['mercurial/parsers.c']),
357 361 ]
358 362
359 363 osutil_ldflags = []
360 364
361 365 if sys.platform == 'darwin':
362 366 osutil_ldflags += ['-framework', 'ApplicationServices']
363 367
364 368 # disable osutil.c under windows + python 2.4 (issue1364)
365 369 if sys.platform == 'win32' and sys.version_info < (2, 5, 0, 'final'):
366 370 pymodules.append('mercurial.pure.osutil')
367 371 else:
368 372 extmodules.append(Extension('mercurial.osutil', ['mercurial/osutil.c'],
369 373 extra_link_args=osutil_ldflags))
370 374
371 375 if sys.platform.startswith('linux') and os.uname()[2] > '2.6':
372 376 # The inotify extension is only usable with Linux 2.6 kernels.
373 377 # You also need a reasonably recent C library.
374 378 # In any case, if it fails to build the error will be skipped ('optional').
375 379 cc = new_compiler()
376 380 if hasfunction(cc, 'inotify_add_watch'):
377 381 inotify = Extension('hgext.inotify.linux._inotify',
378 382 ['hgext/inotify/linux/_inotify.c'],
379 383 ['mercurial'])
380 384 inotify.optional = True
381 385 extmodules.append(inotify)
382 386 packages.extend(['hgext.inotify', 'hgext.inotify.linux'])
383 387
384 388 packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo',
385 389 'help/*.txt']}
386 390
387 391 def ordinarypath(p):
388 392 return p and p[0] != '.' and p[-1] != '~'
389 393
390 394 for root in ('templates',):
391 395 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
392 396 curdir = curdir.split(os.sep, 1)[1]
393 397 dirs[:] = filter(ordinarypath, dirs)
394 398 for f in filter(ordinarypath, files):
395 399 f = os.path.join(curdir, f)
396 400 packagedata['mercurial'].append(f)
397 401
398 402 datafiles = []
399 403 setupversion = version
400 404 extra = {}
401 405
402 406 if py2exeloaded:
403 407 extra['console'] = [
404 408 {'script':'hg',
405 409 'copyright':'Copyright (C) 2005-2010 Matt Mackall and others',
406 410 'product_version':version}]
407 411 # sub command of 'build' because 'py2exe' does not handle sub_commands
408 412 build.sub_commands.insert(0, ('build_hgextindex', None))
409 413
410 414 if os.name == 'nt':
411 415 # Windows binary file versions for exe/dll files must have the
412 416 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
413 417 setupversion = version.split('+', 1)[0]
414 418
415 419 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
416 420 # XCode 4.0 dropped support for ppc architecture, which is hardcoded in
417 421 # distutils.sysconfig
418 422 version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[0].splitlines()[0]
419 423 # Also parse only first digit, because 3.2.1 can't be parsed nicely
420 424 if (version.startswith('Xcode') and
421 425 StrictVersion(version.split()[1]) >= StrictVersion('4.0')):
422 426 os.environ['ARCHFLAGS'] = ''
423 427
424 428 setup(name='mercurial',
425 429 version=setupversion,
426 430 author='Matt Mackall',
427 431 author_email='mpm@selenic.com',
428 432 url='http://mercurial.selenic.com/',
429 433 description='Scalable distributed SCM',
430 434 license='GNU GPLv2+',
431 435 scripts=scripts,
432 436 packages=packages,
433 437 py_modules=pymodules,
434 438 ext_modules=extmodules,
435 439 data_files=datafiles,
436 440 package_data=packagedata,
437 441 cmdclass=cmdclass,
442 distclass=hgdist,
438 443 options=dict(py2exe=dict(packages=['hgext', 'email']),
439 444 bdist_mpkg=dict(zipdist=True,
440 445 license='COPYING',
441 446 readme='contrib/macosx/Readme.html',
442 447 welcome='contrib/macosx/Welcome.html')),
443 448 **extra)
General Comments 0
You need to be logged in to leave comments. Login now