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