##// END OF EJS Templates
setup3k: use getattr instead of hasattr...
Augie Fackler -
r14972:bcba68e8 default
parent child Browse files
Show More
@@ -1,373 +1,373 b''
1 1 #
2 2 # This is an experimental py3k-enabled 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 from distutils.command.build_py import build_py_2to3
8 8 from lib2to3.refactor import get_fixers_from_package as getfixers
9 9
10 10 import sys
11 if not hasattr(sys, 'version_info') or sys.version_info < (2, 4, 0, 'final'):
11 if getattr(sys, 'version_info', (0, 0, 0)) < (2, 4, 0, 'final'):
12 12 raise SystemExit("Mercurial requires Python 2.4 or later.")
13 13
14 14 if sys.version_info[0] >= 3:
15 15 def b(s):
16 16 '''A helper function to emulate 2.6+ bytes literals using string
17 17 literals.'''
18 18 return s.encode('latin1')
19 19 else:
20 20 def b(s):
21 21 '''A helper function to emulate 2.6+ bytes literals using string
22 22 literals.'''
23 23 return s
24 24
25 25 # Solaris Python packaging brain damage
26 26 try:
27 27 import hashlib
28 28 sha = hashlib.sha1()
29 29 except:
30 30 try:
31 31 import sha
32 32 except:
33 33 raise SystemExit(
34 34 "Couldn't import standard hashlib (incomplete Python install).")
35 35
36 36 try:
37 37 import zlib
38 38 except:
39 39 raise SystemExit(
40 40 "Couldn't import standard zlib (incomplete Python install).")
41 41
42 42 try:
43 43 import bz2
44 44 except:
45 45 raise SystemExit(
46 46 "Couldn't import standard bz2 (incomplete Python install).")
47 47
48 48 import os, subprocess, time
49 49 import shutil
50 50 import tempfile
51 51 from distutils import log
52 52 from distutils.core import setup, Extension
53 53 from distutils.dist import Distribution
54 54 from distutils.command.build import build
55 55 from distutils.command.build_ext import build_ext
56 56 from distutils.command.build_py import build_py
57 57 from distutils.spawn import spawn, find_executable
58 58 from distutils.ccompiler import new_compiler
59 59 from distutils.errors import CCompilerError
60 60
61 61 scripts = ['hg']
62 62 if os.name == 'nt':
63 63 scripts.append('contrib/win32/hg.bat')
64 64
65 65 # simplified version of distutils.ccompiler.CCompiler.has_function
66 66 # that actually removes its temporary files.
67 67 def hasfunction(cc, funcname):
68 68 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
69 69 devnull = oldstderr = None
70 70 try:
71 71 try:
72 72 fname = os.path.join(tmpdir, 'funcname.c')
73 73 f = open(fname, 'w')
74 74 f.write('int main(void) {\n')
75 75 f.write(' %s();\n' % funcname)
76 76 f.write('}\n')
77 77 f.close()
78 78 # Redirect stderr to /dev/null to hide any error messages
79 79 # from the compiler.
80 80 # This will have to be changed if we ever have to check
81 81 # for a function on Windows.
82 82 devnull = open('/dev/null', 'w')
83 83 oldstderr = os.dup(sys.stderr.fileno())
84 84 os.dup2(devnull.fileno(), sys.stderr.fileno())
85 85 objects = cc.compile([fname], output_dir=tmpdir)
86 86 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
87 87 except:
88 88 return False
89 89 return True
90 90 finally:
91 91 if oldstderr is not None:
92 92 os.dup2(oldstderr, sys.stderr.fileno())
93 93 if devnull is not None:
94 94 devnull.close()
95 95 shutil.rmtree(tmpdir)
96 96
97 97 # py2exe needs to be installed to work
98 98 try:
99 99 import py2exe
100 100 py2exeloaded = True
101 101
102 102 # Help py2exe to find win32com.shell
103 103 try:
104 104 import modulefinder
105 105 import win32com
106 106 for p in win32com.__path__[1:]: # Take the path to win32comext
107 107 modulefinder.AddPackagePath("win32com", p)
108 108 pn = "win32com.shell"
109 109 __import__(pn)
110 110 m = sys.modules[pn]
111 111 for p in m.__path__[1:]:
112 112 modulefinder.AddPackagePath(pn, p)
113 113 except ImportError:
114 114 pass
115 115
116 116 except ImportError:
117 117 py2exeloaded = False
118 118 pass
119 119
120 120 def runcmd(cmd, env):
121 121 p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
122 122 stderr=subprocess.PIPE, env=env)
123 123 out, err = p.communicate()
124 124 # If root is executing setup.py, but the repository is owned by
125 125 # another user (as in "sudo python setup.py install") we will get
126 126 # trust warnings since the .hg/hgrc file is untrusted. That is
127 127 # fine, we don't want to load it anyway. Python may warn about
128 128 # a missing __init__.py in mercurial/locale, we also ignore that.
129 129 err = [e for e in err.splitlines()
130 130 if not e.startswith(b('Not trusting file')) \
131 131 and not e.startswith(b('warning: Not importing'))]
132 132 if err:
133 133 return ''
134 134 return out
135 135
136 136 version = ''
137 137
138 138 if os.path.isdir('.hg'):
139 139 # Execute hg out of this directory with a custom environment which
140 140 # includes the pure Python modules in mercurial/pure. We also take
141 141 # care to not use any hgrc files and do no localization.
142 142 pypath = ['mercurial', os.path.join('mercurial', 'pure')]
143 143 env = {'PYTHONPATH': os.pathsep.join(pypath),
144 144 'HGRCPATH': '',
145 145 'LANGUAGE': 'C'}
146 146 if 'LD_LIBRARY_PATH' in os.environ:
147 147 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
148 148 if 'SystemRoot' in os.environ:
149 149 # Copy SystemRoot into the custom environment for Python 2.6
150 150 # under Windows. Otherwise, the subprocess will fail with
151 151 # error 0xc0150004. See: http://bugs.python.org/issue3440
152 152 env['SystemRoot'] = os.environ['SystemRoot']
153 153 cmd = [sys.executable, 'hg', 'id', '-i', '-t']
154 154 l = runcmd(cmd, env).split()
155 155 while len(l) > 1 and l[-1][0].isalpha(): # remove non-numbered tags
156 156 l.pop()
157 157 if len(l) > 1: # tag found
158 158 version = l[-1]
159 159 if l[0].endswith('+'): # propagate the dirty status to the tag
160 160 version += '+'
161 161 elif len(l) == 1: # no tag found
162 162 cmd = [sys.executable, 'hg', 'parents', '--template',
163 163 '{latesttag}+{latesttagdistance}-']
164 164 version = runcmd(cmd, env) + l[0]
165 165 if version.endswith('+'):
166 166 version += time.strftime('%Y%m%d')
167 167 elif os.path.exists('.hg_archival.txt'):
168 168 kw = dict([[t.strip() for t in l.split(':', 1)]
169 169 for l in open('.hg_archival.txt')])
170 170 if 'tag' in kw:
171 171 version = kw['tag']
172 172 elif 'latesttag' in kw:
173 173 version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
174 174 else:
175 175 version = kw.get('node', '')[:12]
176 176
177 177 if version:
178 178 f = open("mercurial/__version__.py", "w")
179 179 f.write('# this file is autogenerated by setup.py\n')
180 180 f.write('version = "%s"\n' % version)
181 181 f.close()
182 182
183 183
184 184 try:
185 185 from mercurial import __version__
186 186 version = __version__.version
187 187 except ImportError:
188 188 version = 'unknown'
189 189
190 190 class hgbuildmo(build):
191 191
192 192 description = "build translations (.mo files)"
193 193
194 194 def run(self):
195 195 if not find_executable('msgfmt'):
196 196 self.warn("could not find msgfmt executable, no translations "
197 197 "will be built")
198 198 return
199 199
200 200 podir = 'i18n'
201 201 if not os.path.isdir(podir):
202 202 self.warn("could not find %s/ directory" % podir)
203 203 return
204 204
205 205 join = os.path.join
206 206 for po in os.listdir(podir):
207 207 if not po.endswith('.po'):
208 208 continue
209 209 pofile = join(podir, po)
210 210 modir = join('locale', po[:-3], 'LC_MESSAGES')
211 211 mofile = join(modir, 'hg.mo')
212 212 mobuildfile = join('mercurial', mofile)
213 213 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
214 214 if sys.platform != 'sunos5':
215 215 # msgfmt on Solaris does not know about -c
216 216 cmd.append('-c')
217 217 self.mkpath(join('mercurial', modir))
218 218 self.make_file([pofile], mobuildfile, spawn, (cmd,))
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 # We also need build_ext before build_py. Otherwise, when 2to3 is called (in
224 224 # build_py), it will not find osutil & friends, thinking that those modules are
225 225 # global and, consequently, making a mess, now that all module imports are
226 226 # global.
227 227 build.sub_commands.insert(1, ('build_ext', None))
228 228
229 229 Distribution.pure = 0
230 230 Distribution.global_options.append(('pure', None, "use pure (slow) Python "
231 231 "code instead of C extensions"))
232 232
233 233 class hgbuildext(build_ext):
234 234
235 235 def build_extension(self, ext):
236 236 try:
237 237 build_ext.build_extension(self, ext)
238 238 except CCompilerError:
239 if not hasattr(ext, 'optional') or not ext.optional:
239 if getattr(ext, 'optional', False):
240 240 raise
241 241 log.warn("Failed to build optional extension '%s' (skipping)",
242 242 ext.name)
243 243
244 244 class hgbuildpy(build_py_2to3):
245 245 fixer_names = sorted(set(getfixers("lib2to3.fixes") +
246 246 getfixers("hgfixes")))
247 247
248 248 def finalize_options(self):
249 249 build_py.finalize_options(self)
250 250
251 251 if self.distribution.pure:
252 252 if self.py_modules is None:
253 253 self.py_modules = []
254 254 for ext in self.distribution.ext_modules:
255 255 if ext.name.startswith("mercurial."):
256 256 self.py_modules.append("mercurial.pure.%s" % ext.name[10:])
257 257 self.distribution.ext_modules = []
258 258
259 259 def find_modules(self):
260 260 modules = build_py.find_modules(self)
261 261 for module in modules:
262 262 if module[0] == "mercurial.pure":
263 263 if module[1] != "__init__":
264 264 yield ("mercurial", module[1], module[2])
265 265 else:
266 266 yield module
267 267
268 268 def run(self):
269 269 # In the build_py_2to3 class, self.updated_files = [], but I couldn't
270 270 # see when that variable was updated to point to the updated files, as
271 271 # its names suggests. Thus, I decided to just find_all_modules and feed
272 272 # them to 2to3. Unfortunately, subsequent calls to setup3k.py will
273 273 # incur in 2to3 analysis overhead.
274 274 self.updated_files = [i[2] for i in self.find_all_modules()]
275 275
276 276 # Base class code
277 277 if self.py_modules:
278 278 self.build_modules()
279 279 if self.packages:
280 280 self.build_packages()
281 281 self.build_package_data()
282 282
283 283 # 2to3
284 284 self.run_2to3(self.updated_files)
285 285
286 286 # Remaining base class code
287 287 self.byte_compile(self.get_outputs(include_bytecode=0))
288 288
289 289 cmdclass = {'build_mo': hgbuildmo,
290 290 'build_ext': hgbuildext,
291 291 'build_py': hgbuildpy}
292 292
293 293 packages = ['mercurial', 'mercurial.hgweb', 'hgext', 'hgext.convert',
294 294 'hgext.highlight', 'hgext.zeroconf']
295 295
296 296 pymodules = []
297 297
298 298 extmodules = [
299 299 Extension('mercurial.base85', ['mercurial/base85.c']),
300 300 Extension('mercurial.bdiff', ['mercurial/bdiff.c']),
301 301 Extension('mercurial.diffhelpers', ['mercurial/diffhelpers.c']),
302 302 Extension('mercurial.mpatch', ['mercurial/mpatch.c']),
303 303 Extension('mercurial.parsers', ['mercurial/parsers.c']),
304 304 ]
305 305
306 306 # disable osutil.c under windows + python 2.4 (issue1364)
307 307 if sys.platform == 'win32' and sys.version_info < (2, 5, 0, 'final'):
308 308 pymodules.append('mercurial.pure.osutil')
309 309 else:
310 310 extmodules.append(Extension('mercurial.osutil', ['mercurial/osutil.c']))
311 311
312 312 if sys.platform == 'linux2' and os.uname()[2] > '2.6':
313 313 # The inotify extension is only usable with Linux 2.6 kernels.
314 314 # You also need a reasonably recent C library.
315 315 # In any case, if it fails to build the error will be skipped ('optional').
316 316 cc = new_compiler()
317 317 if hasfunction(cc, 'inotify_add_watch'):
318 318 inotify = Extension('hgext.inotify.linux._inotify',
319 319 ['hgext/inotify/linux/_inotify.c'],
320 320 ['mercurial'])
321 321 inotify.optional = True
322 322 extmodules.append(inotify)
323 323 packages.extend(['hgext.inotify', 'hgext.inotify.linux'])
324 324
325 325 packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo',
326 326 'help/*.txt']}
327 327
328 328 def ordinarypath(p):
329 329 return p and p[0] != '.' and p[-1] != '~'
330 330
331 331 for root in ('templates',):
332 332 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
333 333 curdir = curdir.split(os.sep, 1)[1]
334 334 dirs[:] = filter(ordinarypath, dirs)
335 335 for f in filter(ordinarypath, files):
336 336 f = os.path.join(curdir, f)
337 337 packagedata['mercurial'].append(f)
338 338
339 339 datafiles = []
340 340 setupversion = version
341 341 extra = {}
342 342
343 343 if py2exeloaded:
344 344 extra['console'] = [
345 345 {'script':'hg',
346 346 'copyright':'Copyright (C) 2005-2010 Matt Mackall and others',
347 347 'product_version':version}]
348 348
349 349 if os.name == 'nt':
350 350 # Windows binary file versions for exe/dll files must have the
351 351 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
352 352 setupversion = version.split('+', 1)[0]
353 353
354 354 setup(name='mercurial',
355 355 version=setupversion,
356 356 author='Matt Mackall',
357 357 author_email='mpm@selenic.com',
358 358 url='http://mercurial.selenic.com/',
359 359 description='Scalable distributed SCM',
360 360 license='GNU GPLv2+',
361 361 scripts=scripts,
362 362 packages=packages,
363 363 py_modules=pymodules,
364 364 ext_modules=extmodules,
365 365 data_files=datafiles,
366 366 package_data=packagedata,
367 367 cmdclass=cmdclass,
368 368 options=dict(py2exe=dict(packages=['hgext', 'email']),
369 369 bdist_mpkg=dict(zipdist=True,
370 370 license='COPYING',
371 371 readme='contrib/macosx/Readme.html',
372 372 welcome='contrib/macosx/Welcome.html')),
373 373 **extra)
General Comments 0
You need to be logged in to leave comments. Login now