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