##// END OF EJS Templates
setup: make Xcode 5.1 check less specific...
Matt Mackall -
r21558:8b482d49 default
parent child Browse files
Show More
@@ -1,588 +1,588 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 printf = eval('print')
17 17 libdir_escape = 'unicode_escape'
18 18 else:
19 19 libdir_escape = 'string_escape'
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 def printf(*args, **kwargs):
25 25 f = kwargs.get('file', sys.stdout)
26 26 end = kwargs.get('end', '\n')
27 27 f.write(b(' ').join(args) + end)
28 28
29 29 # Solaris Python packaging brain damage
30 30 try:
31 31 import hashlib
32 32 sha = hashlib.sha1()
33 33 except ImportError:
34 34 try:
35 35 import sha
36 36 except ImportError:
37 37 raise SystemExit(
38 38 "Couldn't import standard hashlib (incomplete Python install).")
39 39
40 40 try:
41 41 import zlib
42 42 except ImportError:
43 43 raise SystemExit(
44 44 "Couldn't import standard zlib (incomplete Python install).")
45 45
46 46 # The base IronPython distribution (as of 2.7.1) doesn't support bz2
47 47 isironpython = False
48 48 try:
49 49 isironpython = (platform.python_implementation()
50 50 .lower().find("ironpython") != -1)
51 51 except AttributeError:
52 52 pass
53 53
54 54 if isironpython:
55 55 sys.stderr.write("warning: IronPython detected (no bz2 support)\n")
56 56 else:
57 57 try:
58 58 import bz2
59 59 except ImportError:
60 60 raise SystemExit(
61 61 "Couldn't import standard bz2 (incomplete Python install).")
62 62
63 63 import os, subprocess, time
64 64 import re
65 65 import shutil
66 66 import tempfile
67 67 from distutils import log
68 68 from distutils.core import setup, Command, Extension
69 69 from distutils.dist import Distribution
70 70 from distutils.command.build import build
71 71 from distutils.command.build_ext import build_ext
72 72 from distutils.command.build_py import build_py
73 73 from distutils.command.install_scripts import install_scripts
74 74 from distutils.spawn import spawn, find_executable
75 75 from distutils import cygwinccompiler
76 76 from distutils.errors import CCompilerError, DistutilsExecError
77 77 from distutils.sysconfig import get_python_inc, get_config_var
78 78 from distutils.version import StrictVersion
79 79
80 80 convert2to3 = '--c2to3' in sys.argv
81 81 if convert2to3:
82 82 try:
83 83 from distutils.command.build_py import build_py_2to3 as build_py
84 84 from lib2to3.refactor import get_fixers_from_package as getfixers
85 85 except ImportError:
86 86 if sys.version_info[0] < 3:
87 87 raise SystemExit("--c2to3 is only compatible with python3.")
88 88 raise
89 89 sys.path.append('contrib')
90 90 elif sys.version_info[0] >= 3:
91 91 raise SystemExit("setup.py with python3 needs --c2to3 (experimental)")
92 92
93 93 scripts = ['hg']
94 94 if os.name == 'nt':
95 95 scripts.append('contrib/win32/hg.bat')
96 96
97 97 # simplified version of distutils.ccompiler.CCompiler.has_function
98 98 # that actually removes its temporary files.
99 99 def hasfunction(cc, funcname):
100 100 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
101 101 devnull = oldstderr = None
102 102 try:
103 103 try:
104 104 fname = os.path.join(tmpdir, 'funcname.c')
105 105 f = open(fname, 'w')
106 106 f.write('int main(void) {\n')
107 107 f.write(' %s();\n' % funcname)
108 108 f.write('}\n')
109 109 f.close()
110 110 # Redirect stderr to /dev/null to hide any error messages
111 111 # from the compiler.
112 112 # This will have to be changed if we ever have to check
113 113 # for a function on Windows.
114 114 devnull = open('/dev/null', 'w')
115 115 oldstderr = os.dup(sys.stderr.fileno())
116 116 os.dup2(devnull.fileno(), sys.stderr.fileno())
117 117 objects = cc.compile([fname], output_dir=tmpdir)
118 118 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
119 119 except Exception:
120 120 return False
121 121 return True
122 122 finally:
123 123 if oldstderr is not None:
124 124 os.dup2(oldstderr, sys.stderr.fileno())
125 125 if devnull is not None:
126 126 devnull.close()
127 127 shutil.rmtree(tmpdir)
128 128
129 129 # py2exe needs to be installed to work
130 130 try:
131 131 import py2exe
132 132 py2exeloaded = True
133 133 # import py2exe's patched Distribution class
134 134 from distutils.core import Distribution
135 135 except ImportError:
136 136 py2exeloaded = False
137 137
138 138 def runcmd(cmd, env):
139 139 if sys.platform == 'plan9':
140 140 # subprocess kludge to work around issues in half-baked Python
141 141 # ports, notably bichued/python:
142 142 _, out, err = os.popen3(cmd)
143 143 return str(out), str(err)
144 144 else:
145 145 p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
146 146 stderr=subprocess.PIPE, env=env)
147 147 out, err = p.communicate()
148 148 return out, err
149 149
150 150 def runhg(cmd, env):
151 151 out, err = runcmd(cmd, env)
152 152 # If root is executing setup.py, but the repository is owned by
153 153 # another user (as in "sudo python setup.py install") we will get
154 154 # trust warnings since the .hg/hgrc file is untrusted. That is
155 155 # fine, we don't want to load it anyway. Python may warn about
156 156 # a missing __init__.py in mercurial/locale, we also ignore that.
157 157 err = [e for e in err.splitlines()
158 158 if not e.startswith(b('not trusting file')) \
159 159 and not e.startswith(b('warning: Not importing')) \
160 160 and not e.startswith(b('obsolete feature not enabled'))]
161 161 if err:
162 162 printf("stderr from '%s':" % (' '.join(cmd)), file=sys.stderr)
163 163 printf(b('\n').join([b(' ') + e for e in err]), file=sys.stderr)
164 164 return ''
165 165 return out
166 166
167 167 version = ''
168 168
169 169 # Execute hg out of this directory with a custom environment which
170 170 # includes the pure Python modules in mercurial/pure. We also take
171 171 # care to not use any hgrc files and do no localization.
172 172 pypath = ['mercurial', os.path.join('mercurial', 'pure')]
173 173 env = {'PYTHONPATH': os.pathsep.join(pypath),
174 174 'HGRCPATH': '',
175 175 'LANGUAGE': 'C'}
176 176 if 'LD_LIBRARY_PATH' in os.environ:
177 177 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
178 178 if 'SystemRoot' in os.environ:
179 179 # Copy SystemRoot into the custom environment for Python 2.6
180 180 # under Windows. Otherwise, the subprocess will fail with
181 181 # error 0xc0150004. See: http://bugs.python.org/issue3440
182 182 env['SystemRoot'] = os.environ['SystemRoot']
183 183
184 184 if os.path.isdir('.hg'):
185 185 cmd = [sys.executable, 'hg', 'log', '-r', '.', '--template', '{tags}\n']
186 186 numerictags = [t for t in runhg(cmd, env).split() if t[0].isdigit()]
187 187 hgid = runhg([sys.executable, 'hg', 'id', '-i'], env).strip()
188 188 if numerictags: # tag(s) found
189 189 version = numerictags[-1]
190 190 if hgid.endswith('+'): # propagate the dirty status to the tag
191 191 version += '+'
192 192 else: # no tag found
193 193 cmd = [sys.executable, 'hg', 'parents', '--template',
194 194 '{latesttag}+{latesttagdistance}-']
195 195 version = runhg(cmd, env) + hgid
196 196 if version.endswith('+'):
197 197 version += time.strftime('%Y%m%d')
198 198 elif os.path.exists('.hg_archival.txt'):
199 199 kw = dict([[t.strip() for t in l.split(':', 1)]
200 200 for l in open('.hg_archival.txt')])
201 201 if 'tag' in kw:
202 202 version = kw['tag']
203 203 elif 'latesttag' in kw:
204 204 version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
205 205 else:
206 206 version = kw.get('node', '')[:12]
207 207
208 208 if version:
209 209 f = open("mercurial/__version__.py", "w")
210 210 f.write('# this file is autogenerated by setup.py\n')
211 211 f.write('version = "%s"\n' % version)
212 212 f.close()
213 213
214 214
215 215 try:
216 216 from mercurial import __version__
217 217 version = __version__.version
218 218 except ImportError:
219 219 version = 'unknown'
220 220
221 221 class hgbuild(build):
222 222 # Insert hgbuildmo first so that files in mercurial/locale/ are found
223 223 # when build_py is run next.
224 224 sub_commands = [('build_mo', None),
225 225
226 226 # We also need build_ext before build_py. Otherwise, when 2to3 is
227 227 # called (in build_py), it will not find osutil & friends,
228 228 # thinking that those modules are global and, consequently, making
229 229 # a mess, now that all module imports are global.
230 230
231 231 ('build_ext', build.has_ext_modules),
232 232 ] + build.sub_commands
233 233
234 234 class hgbuildmo(build):
235 235
236 236 description = "build translations (.mo files)"
237 237
238 238 def run(self):
239 239 if not find_executable('msgfmt'):
240 240 self.warn("could not find msgfmt executable, no translations "
241 241 "will be built")
242 242 return
243 243
244 244 podir = 'i18n'
245 245 if not os.path.isdir(podir):
246 246 self.warn("could not find %s/ directory" % podir)
247 247 return
248 248
249 249 join = os.path.join
250 250 for po in os.listdir(podir):
251 251 if not po.endswith('.po'):
252 252 continue
253 253 pofile = join(podir, po)
254 254 modir = join('locale', po[:-3], 'LC_MESSAGES')
255 255 mofile = join(modir, 'hg.mo')
256 256 mobuildfile = join('mercurial', mofile)
257 257 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
258 258 if sys.platform != 'sunos5':
259 259 # msgfmt on Solaris does not know about -c
260 260 cmd.append('-c')
261 261 self.mkpath(join('mercurial', modir))
262 262 self.make_file([pofile], mobuildfile, spawn, (cmd,))
263 263
264 264
265 265 class hgdist(Distribution):
266 266 pure = 0
267 267
268 268 global_options = Distribution.global_options + \
269 269 [('pure', None, "use pure (slow) Python "
270 270 "code instead of C extensions"),
271 271 ('c2to3', None, "(experimental!) convert "
272 272 "code with 2to3"),
273 273 ]
274 274
275 275 def has_ext_modules(self):
276 276 # self.ext_modules is emptied in hgbuildpy.finalize_options which is
277 277 # too late for some cases
278 278 return not self.pure and Distribution.has_ext_modules(self)
279 279
280 280 class hgbuildext(build_ext):
281 281
282 282 def build_extension(self, ext):
283 283 try:
284 284 build_ext.build_extension(self, ext)
285 285 except CCompilerError:
286 286 if not getattr(ext, 'optional', False):
287 287 raise
288 288 log.warn("Failed to build optional extension '%s' (skipping)",
289 289 ext.name)
290 290
291 291 class hgbuildpy(build_py):
292 292 if convert2to3:
293 293 fixer_names = sorted(set(getfixers("lib2to3.fixes") +
294 294 getfixers("hgfixes")))
295 295
296 296 def finalize_options(self):
297 297 build_py.finalize_options(self)
298 298
299 299 if self.distribution.pure:
300 300 if self.py_modules is None:
301 301 self.py_modules = []
302 302 for ext in self.distribution.ext_modules:
303 303 if ext.name.startswith("mercurial."):
304 304 self.py_modules.append("mercurial.pure.%s" % ext.name[10:])
305 305 self.distribution.ext_modules = []
306 306 else:
307 307 h = os.path.join(get_python_inc(), 'Python.h')
308 308 if not os.path.exists(h):
309 309 raise SystemExit('Python headers are required to build '
310 310 'Mercurial but weren\'t found in %s' % h)
311 311
312 312 def find_modules(self):
313 313 modules = build_py.find_modules(self)
314 314 for module in modules:
315 315 if module[0] == "mercurial.pure":
316 316 if module[1] != "__init__":
317 317 yield ("mercurial", module[1], module[2])
318 318 else:
319 319 yield module
320 320
321 321 class buildhgextindex(Command):
322 322 description = 'generate prebuilt index of hgext (for frozen package)'
323 323 user_options = []
324 324 _indexfilename = 'hgext/__index__.py'
325 325
326 326 def initialize_options(self):
327 327 pass
328 328
329 329 def finalize_options(self):
330 330 pass
331 331
332 332 def run(self):
333 333 if os.path.exists(self._indexfilename):
334 334 f = open(self._indexfilename, 'w')
335 335 f.write('# empty\n')
336 336 f.close()
337 337
338 338 # here no extension enabled, disabled() lists up everything
339 339 code = ('import pprint; from mercurial import extensions; '
340 340 'pprint.pprint(extensions.disabled())')
341 341 out, err = runcmd([sys.executable, '-c', code], env)
342 342 if err:
343 343 raise DistutilsExecError(err)
344 344
345 345 f = open(self._indexfilename, 'w')
346 346 f.write('# this file is autogenerated by setup.py\n')
347 347 f.write('docs = ')
348 348 f.write(out)
349 349 f.close()
350 350
351 351 class buildhgexe(build_ext):
352 352 description = 'compile hg.exe from mercurial/exewrapper.c'
353 353
354 354 def build_extensions(self):
355 355 if os.name != 'nt':
356 356 return
357 357 if isinstance(self.compiler, HackedMingw32CCompiler):
358 358 self.compiler.compiler_so = self.compiler.compiler # no -mdll
359 359 self.compiler.dll_libraries = [] # no -lmsrvc90
360 360 hv = sys.hexversion
361 361 pythonlib = 'python%d%d' % (hv >> 24, (hv >> 16) & 0xff)
362 362 f = open('mercurial/hgpythonlib.h', 'wb')
363 363 f.write('/* this file is autogenerated by setup.py */\n')
364 364 f.write('#define HGPYTHONLIB "%s"\n' % pythonlib)
365 365 f.close()
366 366 objects = self.compiler.compile(['mercurial/exewrapper.c'],
367 367 output_dir=self.build_temp)
368 368 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
369 369 target = os.path.join(dir, 'hg')
370 370 self.compiler.link_executable(objects, target,
371 371 libraries=[],
372 372 output_dir=self.build_temp)
373 373
374 374 class hginstallscripts(install_scripts):
375 375 '''
376 376 This is a specialization of install_scripts that replaces the @LIBDIR@ with
377 377 the configured directory for modules. If possible, the path is made relative
378 378 to the directory for scripts.
379 379 '''
380 380
381 381 def initialize_options(self):
382 382 install_scripts.initialize_options(self)
383 383
384 384 self.install_lib = None
385 385
386 386 def finalize_options(self):
387 387 install_scripts.finalize_options(self)
388 388 self.set_undefined_options('install',
389 389 ('install_lib', 'install_lib'))
390 390
391 391 def run(self):
392 392 install_scripts.run(self)
393 393
394 394 if (os.path.splitdrive(self.install_dir)[0] !=
395 395 os.path.splitdrive(self.install_lib)[0]):
396 396 # can't make relative paths from one drive to another, so use an
397 397 # absolute path instead
398 398 libdir = self.install_lib
399 399 else:
400 400 common = os.path.commonprefix((self.install_dir, self.install_lib))
401 401 rest = self.install_dir[len(common):]
402 402 uplevel = len([n for n in os.path.split(rest) if n])
403 403
404 404 libdir = uplevel * ('..' + os.sep) + self.install_lib[len(common):]
405 405
406 406 for outfile in self.outfiles:
407 407 fp = open(outfile, 'rb')
408 408 data = fp.read()
409 409 fp.close()
410 410
411 411 # skip binary files
412 412 if b('\0') in data:
413 413 continue
414 414
415 415 data = data.replace(b('@LIBDIR@'), libdir.encode(libdir_escape))
416 416 fp = open(outfile, 'wb')
417 417 fp.write(data)
418 418 fp.close()
419 419
420 420 cmdclass = {'build': hgbuild,
421 421 'build_mo': hgbuildmo,
422 422 'build_ext': hgbuildext,
423 423 'build_py': hgbuildpy,
424 424 'build_hgextindex': buildhgextindex,
425 425 'install_scripts': hginstallscripts,
426 426 'build_hgexe': buildhgexe,
427 427 }
428 428
429 429 packages = ['mercurial', 'mercurial.hgweb', 'mercurial.httpclient',
430 430 'hgext', 'hgext.convert', 'hgext.highlight', 'hgext.zeroconf',
431 431 'hgext.largefiles']
432 432
433 433 pymodules = []
434 434
435 435 common_depends = ['mercurial/util.h']
436 436
437 437 extmodules = [
438 438 Extension('mercurial.base85', ['mercurial/base85.c'],
439 439 depends=common_depends),
440 440 Extension('mercurial.bdiff', ['mercurial/bdiff.c'],
441 441 depends=common_depends),
442 442 Extension('mercurial.diffhelpers', ['mercurial/diffhelpers.c'],
443 443 depends=common_depends),
444 444 Extension('mercurial.mpatch', ['mercurial/mpatch.c'],
445 445 depends=common_depends),
446 446 Extension('mercurial.parsers', ['mercurial/dirs.c',
447 447 'mercurial/parsers.c',
448 448 'mercurial/pathencode.c'],
449 449 depends=common_depends),
450 450 ]
451 451
452 452 osutil_ldflags = []
453 453
454 454 if sys.platform == 'darwin':
455 455 osutil_ldflags += ['-framework', 'ApplicationServices']
456 456
457 457 # disable osutil.c under windows + python 2.4 (issue1364)
458 458 if sys.platform == 'win32' and sys.version_info < (2, 5, 0, 'final'):
459 459 pymodules.append('mercurial.pure.osutil')
460 460 else:
461 461 extmodules.append(Extension('mercurial.osutil', ['mercurial/osutil.c'],
462 462 extra_link_args=osutil_ldflags,
463 463 depends=common_depends))
464 464
465 465 # the -mno-cygwin option has been deprecated for years
466 466 Mingw32CCompiler = cygwinccompiler.Mingw32CCompiler
467 467
468 468 class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
469 469 def __init__(self, *args, **kwargs):
470 470 Mingw32CCompiler.__init__(self, *args, **kwargs)
471 471 for i in 'compiler compiler_so linker_exe linker_so'.split():
472 472 try:
473 473 getattr(self, i).remove('-mno-cygwin')
474 474 except ValueError:
475 475 pass
476 476
477 477 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
478 478
479 479 packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo',
480 480 'help/*.txt']}
481 481
482 482 def ordinarypath(p):
483 483 return p and p[0] != '.' and p[-1] != '~'
484 484
485 485 for root in ('templates',):
486 486 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
487 487 curdir = curdir.split(os.sep, 1)[1]
488 488 dirs[:] = filter(ordinarypath, dirs)
489 489 for f in filter(ordinarypath, files):
490 490 f = os.path.join(curdir, f)
491 491 packagedata['mercurial'].append(f)
492 492
493 493 datafiles = []
494 494 setupversion = version
495 495 extra = {}
496 496
497 497 if py2exeloaded:
498 498 extra['console'] = [
499 499 {'script':'hg',
500 500 'copyright':'Copyright (C) 2005-2010 Matt Mackall and others',
501 501 'product_version':version}]
502 502 # sub command of 'build' because 'py2exe' does not handle sub_commands
503 503 build.sub_commands.insert(0, ('build_hgextindex', None))
504 504
505 505 if os.name == 'nt':
506 506 # Windows binary file versions for exe/dll files must have the
507 507 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
508 508 setupversion = version.split('+', 1)[0]
509 509
510 510 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
511 511 version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[0].splitlines()
512 512 if version:
513 513 version = version[0]
514 514 xcode4 = (version.startswith('Xcode') and
515 515 StrictVersion(version.split()[1]) >= StrictVersion('4.0'))
516 xcode51 = re.match(r'^Xcode\s+5\.1\.', version) is not None
516 xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None
517 517 else:
518 518 # xcodebuild returns empty on OS X Lion with XCode 4.3 not
519 519 # installed, but instead with only command-line tools. Assume
520 520 # that only happens on >= Lion, thus no PPC support.
521 521 xcode4 = True
522 522 xcode51 = False
523 523
524 524 # XCode 4.0 dropped support for ppc architecture, which is hardcoded in
525 525 # distutils.sysconfig
526 526 if xcode4:
527 527 os.environ['ARCHFLAGS'] = ''
528 528
529 529 # XCode 5.1 changes clang such that it now fails to compile if the
530 530 # -mno-fused-madd flag is passed, but the version of Python shipped with
531 531 # OS X 10.9 Mavericks includes this flag. This causes problems in all
532 532 # C extension modules, and a bug has been filed upstream at
533 533 # http://bugs.python.org/issue21244. We also need to patch this here
534 534 # so Mercurial can continue to compile in the meantime.
535 535 if xcode51:
536 536 cflags = get_config_var('CFLAGS')
537 537 if re.search(r'-mno-fused-madd\b', cflags) is not None:
538 538 os.environ['CFLAGS'] = (
539 539 os.environ.get('CFLAGS', '') + ' -Qunused-arguments')
540 540
541 541 setup(name='mercurial',
542 542 version=setupversion,
543 543 author='Matt Mackall and many others',
544 544 author_email='mercurial@selenic.com',
545 545 url='http://mercurial.selenic.com/',
546 546 download_url='http://mercurial.selenic.com/release/',
547 547 description=('Fast scalable distributed SCM (revision control, version '
548 548 'control) system'),
549 549 long_description=('Mercurial is a distributed SCM tool written in Python.'
550 550 ' It is used by a number of large projects that require'
551 551 ' fast, reliable distributed revision control, such as '
552 552 'Mozilla.'),
553 553 license='GNU GPLv2 or any later version',
554 554 classifiers=[
555 555 'Development Status :: 6 - Mature',
556 556 'Environment :: Console',
557 557 'Intended Audience :: Developers',
558 558 'Intended Audience :: System Administrators',
559 559 'License :: OSI Approved :: GNU General Public License (GPL)',
560 560 'Natural Language :: Danish',
561 561 'Natural Language :: English',
562 562 'Natural Language :: German',
563 563 'Natural Language :: Italian',
564 564 'Natural Language :: Japanese',
565 565 'Natural Language :: Portuguese (Brazilian)',
566 566 'Operating System :: Microsoft :: Windows',
567 567 'Operating System :: OS Independent',
568 568 'Operating System :: POSIX',
569 569 'Programming Language :: C',
570 570 'Programming Language :: Python',
571 571 'Topic :: Software Development :: Version Control',
572 572 ],
573 573 scripts=scripts,
574 574 packages=packages,
575 575 py_modules=pymodules,
576 576 ext_modules=extmodules,
577 577 data_files=datafiles,
578 578 package_data=packagedata,
579 579 cmdclass=cmdclass,
580 580 distclass=hgdist,
581 581 options={'py2exe': {'packages': ['hgext', 'email']},
582 582 'bdist_mpkg': {'zipdist': True,
583 583 'license': 'COPYING',
584 584 'readme': 'contrib/macosx/Readme.html',
585 585 'welcome': 'contrib/macosx/Welcome.html',
586 586 },
587 587 },
588 588 **extra)
General Comments 0
You need to be logged in to leave comments. Login now