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