##// END OF EJS Templates
building: build inotify for sys.platform='linux*'...
Nikolaj Sjujskij -
r15163:f4bc0b9e stable
parent child Browse files
Show More
@@ -1,373 +1,373 b''
1 #
1 #
2 # This is an experimental py3k-enabled mercurial setup script.
2 # This is an experimental py3k-enabled mercurial setup script.
3 #
3 #
4 # 'python setup.py install', or
4 # 'python setup.py install', or
5 # 'python setup.py --help' for more options
5 # 'python setup.py --help' for more options
6
6
7 from distutils.command.build_py import build_py_2to3
7 from distutils.command.build_py import build_py_2to3
8 from lib2to3.refactor import get_fixers_from_package as getfixers
8 from lib2to3.refactor import get_fixers_from_package as getfixers
9
9
10 import sys
10 import sys
11 if not hasattr(sys, 'version_info') or sys.version_info < (2, 4, 0, 'final'):
11 if not hasattr(sys, 'version_info') or sys.version_info < (2, 4, 0, 'final'):
12 raise SystemExit("Mercurial requires Python 2.4 or later.")
12 raise SystemExit("Mercurial requires Python 2.4 or later.")
13
13
14 if sys.version_info[0] >= 3:
14 if sys.version_info[0] >= 3:
15 def b(s):
15 def b(s):
16 '''A helper function to emulate 2.6+ bytes literals using string
16 '''A helper function to emulate 2.6+ bytes literals using string
17 literals.'''
17 literals.'''
18 return s.encode('latin1')
18 return s.encode('latin1')
19 else:
19 else:
20 def b(s):
20 def b(s):
21 '''A helper function to emulate 2.6+ bytes literals using string
21 '''A helper function to emulate 2.6+ bytes literals using string
22 literals.'''
22 literals.'''
23 return s
23 return s
24
24
25 # Solaris Python packaging brain damage
25 # Solaris Python packaging brain damage
26 try:
26 try:
27 import hashlib
27 import hashlib
28 sha = hashlib.sha1()
28 sha = hashlib.sha1()
29 except:
29 except:
30 try:
30 try:
31 import sha
31 import sha
32 except:
32 except:
33 raise SystemExit(
33 raise SystemExit(
34 "Couldn't import standard hashlib (incomplete Python install).")
34 "Couldn't import standard hashlib (incomplete Python install).")
35
35
36 try:
36 try:
37 import zlib
37 import zlib
38 except:
38 except:
39 raise SystemExit(
39 raise SystemExit(
40 "Couldn't import standard zlib (incomplete Python install).")
40 "Couldn't import standard zlib (incomplete Python install).")
41
41
42 try:
42 try:
43 import bz2
43 import bz2
44 except:
44 except:
45 raise SystemExit(
45 raise SystemExit(
46 "Couldn't import standard bz2 (incomplete Python install).")
46 "Couldn't import standard bz2 (incomplete Python install).")
47
47
48 import os, subprocess, time
48 import os, subprocess, time
49 import shutil
49 import shutil
50 import tempfile
50 import tempfile
51 from distutils import log
51 from distutils import log
52 from distutils.core import setup, Extension
52 from distutils.core import setup, Extension
53 from distutils.dist import Distribution
53 from distutils.dist import Distribution
54 from distutils.command.build import build
54 from distutils.command.build import build
55 from distutils.command.build_ext import build_ext
55 from distutils.command.build_ext import build_ext
56 from distutils.command.build_py import build_py
56 from distutils.command.build_py import build_py
57 from distutils.spawn import spawn, find_executable
57 from distutils.spawn import spawn, find_executable
58 from distutils.ccompiler import new_compiler
58 from distutils.ccompiler import new_compiler
59 from distutils.errors import CCompilerError
59 from distutils.errors import CCompilerError
60
60
61 scripts = ['hg']
61 scripts = ['hg']
62 if os.name == 'nt':
62 if os.name == 'nt':
63 scripts.append('contrib/win32/hg.bat')
63 scripts.append('contrib/win32/hg.bat')
64
64
65 # simplified version of distutils.ccompiler.CCompiler.has_function
65 # simplified version of distutils.ccompiler.CCompiler.has_function
66 # that actually removes its temporary files.
66 # that actually removes its temporary files.
67 def hasfunction(cc, funcname):
67 def hasfunction(cc, funcname):
68 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
68 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
69 devnull = oldstderr = None
69 devnull = oldstderr = None
70 try:
70 try:
71 try:
71 try:
72 fname = os.path.join(tmpdir, 'funcname.c')
72 fname = os.path.join(tmpdir, 'funcname.c')
73 f = open(fname, 'w')
73 f = open(fname, 'w')
74 f.write('int main(void) {\n')
74 f.write('int main(void) {\n')
75 f.write(' %s();\n' % funcname)
75 f.write(' %s();\n' % funcname)
76 f.write('}\n')
76 f.write('}\n')
77 f.close()
77 f.close()
78 # Redirect stderr to /dev/null to hide any error messages
78 # Redirect stderr to /dev/null to hide any error messages
79 # from the compiler.
79 # from the compiler.
80 # This will have to be changed if we ever have to check
80 # This will have to be changed if we ever have to check
81 # for a function on Windows.
81 # for a function on Windows.
82 devnull = open('/dev/null', 'w')
82 devnull = open('/dev/null', 'w')
83 oldstderr = os.dup(sys.stderr.fileno())
83 oldstderr = os.dup(sys.stderr.fileno())
84 os.dup2(devnull.fileno(), sys.stderr.fileno())
84 os.dup2(devnull.fileno(), sys.stderr.fileno())
85 objects = cc.compile([fname], output_dir=tmpdir)
85 objects = cc.compile([fname], output_dir=tmpdir)
86 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
86 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
87 except:
87 except:
88 return False
88 return False
89 return True
89 return True
90 finally:
90 finally:
91 if oldstderr is not None:
91 if oldstderr is not None:
92 os.dup2(oldstderr, sys.stderr.fileno())
92 os.dup2(oldstderr, sys.stderr.fileno())
93 if devnull is not None:
93 if devnull is not None:
94 devnull.close()
94 devnull.close()
95 shutil.rmtree(tmpdir)
95 shutil.rmtree(tmpdir)
96
96
97 # py2exe needs to be installed to work
97 # py2exe needs to be installed to work
98 try:
98 try:
99 import py2exe
99 import py2exe
100 py2exeloaded = True
100 py2exeloaded = True
101
101
102 # Help py2exe to find win32com.shell
102 # Help py2exe to find win32com.shell
103 try:
103 try:
104 import modulefinder
104 import modulefinder
105 import win32com
105 import win32com
106 for p in win32com.__path__[1:]: # Take the path to win32comext
106 for p in win32com.__path__[1:]: # Take the path to win32comext
107 modulefinder.AddPackagePath("win32com", p)
107 modulefinder.AddPackagePath("win32com", p)
108 pn = "win32com.shell"
108 pn = "win32com.shell"
109 __import__(pn)
109 __import__(pn)
110 m = sys.modules[pn]
110 m = sys.modules[pn]
111 for p in m.__path__[1:]:
111 for p in m.__path__[1:]:
112 modulefinder.AddPackagePath(pn, p)
112 modulefinder.AddPackagePath(pn, p)
113 except ImportError:
113 except ImportError:
114 pass
114 pass
115
115
116 except ImportError:
116 except ImportError:
117 py2exeloaded = False
117 py2exeloaded = False
118 pass
118 pass
119
119
120 def runcmd(cmd, env):
120 def runcmd(cmd, env):
121 p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
121 p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
122 stderr=subprocess.PIPE, env=env)
122 stderr=subprocess.PIPE, env=env)
123 out, err = p.communicate()
123 out, err = p.communicate()
124 # If root is executing setup.py, but the repository is owned by
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
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
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
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.
128 # a missing __init__.py in mercurial/locale, we also ignore that.
129 err = [e for e in err.splitlines()
129 err = [e for e in err.splitlines()
130 if not e.startswith(b('Not trusting file')) \
130 if not e.startswith(b('Not trusting file')) \
131 and not e.startswith(b('warning: Not importing'))]
131 and not e.startswith(b('warning: Not importing'))]
132 if err:
132 if err:
133 return ''
133 return ''
134 return out
134 return out
135
135
136 version = ''
136 version = ''
137
137
138 if os.path.isdir('.hg'):
138 if os.path.isdir('.hg'):
139 # Execute hg out of this directory with a custom environment which
139 # Execute hg out of this directory with a custom environment which
140 # includes the pure Python modules in mercurial/pure. We also take
140 # includes the pure Python modules in mercurial/pure. We also take
141 # care to not use any hgrc files and do no localization.
141 # care to not use any hgrc files and do no localization.
142 pypath = ['mercurial', os.path.join('mercurial', 'pure')]
142 pypath = ['mercurial', os.path.join('mercurial', 'pure')]
143 env = {'PYTHONPATH': os.pathsep.join(pypath),
143 env = {'PYTHONPATH': os.pathsep.join(pypath),
144 'HGRCPATH': '',
144 'HGRCPATH': '',
145 'LANGUAGE': 'C'}
145 'LANGUAGE': 'C'}
146 if 'LD_LIBRARY_PATH' in os.environ:
146 if 'LD_LIBRARY_PATH' in os.environ:
147 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
147 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
148 if 'SystemRoot' in os.environ:
148 if 'SystemRoot' in os.environ:
149 # Copy SystemRoot into the custom environment for Python 2.6
149 # Copy SystemRoot into the custom environment for Python 2.6
150 # under Windows. Otherwise, the subprocess will fail with
150 # under Windows. Otherwise, the subprocess will fail with
151 # error 0xc0150004. See: http://bugs.python.org/issue3440
151 # error 0xc0150004. See: http://bugs.python.org/issue3440
152 env['SystemRoot'] = os.environ['SystemRoot']
152 env['SystemRoot'] = os.environ['SystemRoot']
153 cmd = [sys.executable, 'hg', 'id', '-i', '-t']
153 cmd = [sys.executable, 'hg', 'id', '-i', '-t']
154 l = runcmd(cmd, env).split()
154 l = runcmd(cmd, env).split()
155 while len(l) > 1 and l[-1][0].isalpha(): # remove non-numbered tags
155 while len(l) > 1 and l[-1][0].isalpha(): # remove non-numbered tags
156 l.pop()
156 l.pop()
157 if len(l) > 1: # tag found
157 if len(l) > 1: # tag found
158 version = l[-1]
158 version = l[-1]
159 if l[0].endswith('+'): # propagate the dirty status to the tag
159 if l[0].endswith('+'): # propagate the dirty status to the tag
160 version += '+'
160 version += '+'
161 elif len(l) == 1: # no tag found
161 elif len(l) == 1: # no tag found
162 cmd = [sys.executable, 'hg', 'parents', '--template',
162 cmd = [sys.executable, 'hg', 'parents', '--template',
163 '{latesttag}+{latesttagdistance}-']
163 '{latesttag}+{latesttagdistance}-']
164 version = runcmd(cmd, env) + l[0]
164 version = runcmd(cmd, env) + l[0]
165 if version.endswith('+'):
165 if version.endswith('+'):
166 version += time.strftime('%Y%m%d')
166 version += time.strftime('%Y%m%d')
167 elif os.path.exists('.hg_archival.txt'):
167 elif os.path.exists('.hg_archival.txt'):
168 kw = dict([[t.strip() for t in l.split(':', 1)]
168 kw = dict([[t.strip() for t in l.split(':', 1)]
169 for l in open('.hg_archival.txt')])
169 for l in open('.hg_archival.txt')])
170 if 'tag' in kw:
170 if 'tag' in kw:
171 version = kw['tag']
171 version = kw['tag']
172 elif 'latesttag' in kw:
172 elif 'latesttag' in kw:
173 version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
173 version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
174 else:
174 else:
175 version = kw.get('node', '')[:12]
175 version = kw.get('node', '')[:12]
176
176
177 if version:
177 if version:
178 f = open("mercurial/__version__.py", "w")
178 f = open("mercurial/__version__.py", "w")
179 f.write('# this file is autogenerated by setup.py\n')
179 f.write('# this file is autogenerated by setup.py\n')
180 f.write('version = "%s"\n' % version)
180 f.write('version = "%s"\n' % version)
181 f.close()
181 f.close()
182
182
183
183
184 try:
184 try:
185 from mercurial import __version__
185 from mercurial import __version__
186 version = __version__.version
186 version = __version__.version
187 except ImportError:
187 except ImportError:
188 version = 'unknown'
188 version = 'unknown'
189
189
190 class hgbuildmo(build):
190 class hgbuildmo(build):
191
191
192 description = "build translations (.mo files)"
192 description = "build translations (.mo files)"
193
193
194 def run(self):
194 def run(self):
195 if not find_executable('msgfmt'):
195 if not find_executable('msgfmt'):
196 self.warn("could not find msgfmt executable, no translations "
196 self.warn("could not find msgfmt executable, no translations "
197 "will be built")
197 "will be built")
198 return
198 return
199
199
200 podir = 'i18n'
200 podir = 'i18n'
201 if not os.path.isdir(podir):
201 if not os.path.isdir(podir):
202 self.warn("could not find %s/ directory" % podir)
202 self.warn("could not find %s/ directory" % podir)
203 return
203 return
204
204
205 join = os.path.join
205 join = os.path.join
206 for po in os.listdir(podir):
206 for po in os.listdir(podir):
207 if not po.endswith('.po'):
207 if not po.endswith('.po'):
208 continue
208 continue
209 pofile = join(podir, po)
209 pofile = join(podir, po)
210 modir = join('locale', po[:-3], 'LC_MESSAGES')
210 modir = join('locale', po[:-3], 'LC_MESSAGES')
211 mofile = join(modir, 'hg.mo')
211 mofile = join(modir, 'hg.mo')
212 mobuildfile = join('mercurial', mofile)
212 mobuildfile = join('mercurial', mofile)
213 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
213 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
214 if sys.platform != 'sunos5':
214 if sys.platform != 'sunos5':
215 # msgfmt on Solaris does not know about -c
215 # msgfmt on Solaris does not know about -c
216 cmd.append('-c')
216 cmd.append('-c')
217 self.mkpath(join('mercurial', modir))
217 self.mkpath(join('mercurial', modir))
218 self.make_file([pofile], mobuildfile, spawn, (cmd,))
218 self.make_file([pofile], mobuildfile, spawn, (cmd,))
219
219
220 # Insert hgbuildmo first so that files in mercurial/locale/ are found
220 # Insert hgbuildmo first so that files in mercurial/locale/ are found
221 # when build_py is run next.
221 # when build_py is run next.
222 build.sub_commands.insert(0, ('build_mo', None))
222 build.sub_commands.insert(0, ('build_mo', None))
223 # We also need build_ext before build_py. Otherwise, when 2to3 is called (in
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
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
225 # global and, consequently, making a mess, now that all module imports are
226 # global.
226 # global.
227 build.sub_commands.insert(1, ('build_ext', None))
227 build.sub_commands.insert(1, ('build_ext', None))
228
228
229 Distribution.pure = 0
229 Distribution.pure = 0
230 Distribution.global_options.append(('pure', None, "use pure (slow) Python "
230 Distribution.global_options.append(('pure', None, "use pure (slow) Python "
231 "code instead of C extensions"))
231 "code instead of C extensions"))
232
232
233 class hgbuildext(build_ext):
233 class hgbuildext(build_ext):
234
234
235 def build_extension(self, ext):
235 def build_extension(self, ext):
236 try:
236 try:
237 build_ext.build_extension(self, ext)
237 build_ext.build_extension(self, ext)
238 except CCompilerError:
238 except CCompilerError:
239 if not hasattr(ext, 'optional') or not ext.optional:
239 if not hasattr(ext, 'optional') or not ext.optional:
240 raise
240 raise
241 log.warn("Failed to build optional extension '%s' (skipping)",
241 log.warn("Failed to build optional extension '%s' (skipping)",
242 ext.name)
242 ext.name)
243
243
244 class hgbuildpy(build_py_2to3):
244 class hgbuildpy(build_py_2to3):
245 fixer_names = sorted(set(getfixers("lib2to3.fixes") +
245 fixer_names = sorted(set(getfixers("lib2to3.fixes") +
246 getfixers("hgfixes")))
246 getfixers("hgfixes")))
247
247
248 def finalize_options(self):
248 def finalize_options(self):
249 build_py.finalize_options(self)
249 build_py.finalize_options(self)
250
250
251 if self.distribution.pure:
251 if self.distribution.pure:
252 if self.py_modules is None:
252 if self.py_modules is None:
253 self.py_modules = []
253 self.py_modules = []
254 for ext in self.distribution.ext_modules:
254 for ext in self.distribution.ext_modules:
255 if ext.name.startswith("mercurial."):
255 if ext.name.startswith("mercurial."):
256 self.py_modules.append("mercurial.pure.%s" % ext.name[10:])
256 self.py_modules.append("mercurial.pure.%s" % ext.name[10:])
257 self.distribution.ext_modules = []
257 self.distribution.ext_modules = []
258
258
259 def find_modules(self):
259 def find_modules(self):
260 modules = build_py.find_modules(self)
260 modules = build_py.find_modules(self)
261 for module in modules:
261 for module in modules:
262 if module[0] == "mercurial.pure":
262 if module[0] == "mercurial.pure":
263 if module[1] != "__init__":
263 if module[1] != "__init__":
264 yield ("mercurial", module[1], module[2])
264 yield ("mercurial", module[1], module[2])
265 else:
265 else:
266 yield module
266 yield module
267
267
268 def run(self):
268 def run(self):
269 # In the build_py_2to3 class, self.updated_files = [], but I couldn't
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
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
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
272 # them to 2to3. Unfortunately, subsequent calls to setup3k.py will
273 # incur in 2to3 analysis overhead.
273 # incur in 2to3 analysis overhead.
274 self.updated_files = [i[2] for i in self.find_all_modules()]
274 self.updated_files = [i[2] for i in self.find_all_modules()]
275
275
276 # Base class code
276 # Base class code
277 if self.py_modules:
277 if self.py_modules:
278 self.build_modules()
278 self.build_modules()
279 if self.packages:
279 if self.packages:
280 self.build_packages()
280 self.build_packages()
281 self.build_package_data()
281 self.build_package_data()
282
282
283 # 2to3
283 # 2to3
284 self.run_2to3(self.updated_files)
284 self.run_2to3(self.updated_files)
285
285
286 # Remaining base class code
286 # Remaining base class code
287 self.byte_compile(self.get_outputs(include_bytecode=0))
287 self.byte_compile(self.get_outputs(include_bytecode=0))
288
288
289 cmdclass = {'build_mo': hgbuildmo,
289 cmdclass = {'build_mo': hgbuildmo,
290 'build_ext': hgbuildext,
290 'build_ext': hgbuildext,
291 'build_py': hgbuildpy}
291 'build_py': hgbuildpy}
292
292
293 packages = ['mercurial', 'mercurial.hgweb', 'hgext', 'hgext.convert',
293 packages = ['mercurial', 'mercurial.hgweb', 'hgext', 'hgext.convert',
294 'hgext.highlight', 'hgext.zeroconf']
294 'hgext.highlight', 'hgext.zeroconf']
295
295
296 pymodules = []
296 pymodules = []
297
297
298 extmodules = [
298 extmodules = [
299 Extension('mercurial.base85', ['mercurial/base85.c']),
299 Extension('mercurial.base85', ['mercurial/base85.c']),
300 Extension('mercurial.bdiff', ['mercurial/bdiff.c']),
300 Extension('mercurial.bdiff', ['mercurial/bdiff.c']),
301 Extension('mercurial.diffhelpers', ['mercurial/diffhelpers.c']),
301 Extension('mercurial.diffhelpers', ['mercurial/diffhelpers.c']),
302 Extension('mercurial.mpatch', ['mercurial/mpatch.c']),
302 Extension('mercurial.mpatch', ['mercurial/mpatch.c']),
303 Extension('mercurial.parsers', ['mercurial/parsers.c']),
303 Extension('mercurial.parsers', ['mercurial/parsers.c']),
304 ]
304 ]
305
305
306 # disable osutil.c under windows + python 2.4 (issue1364)
306 # disable osutil.c under windows + python 2.4 (issue1364)
307 if sys.platform == 'win32' and sys.version_info < (2, 5, 0, 'final'):
307 if sys.platform == 'win32' and sys.version_info < (2, 5, 0, 'final'):
308 pymodules.append('mercurial.pure.osutil')
308 pymodules.append('mercurial.pure.osutil')
309 else:
309 else:
310 extmodules.append(Extension('mercurial.osutil', ['mercurial/osutil.c']))
310 extmodules.append(Extension('mercurial.osutil', ['mercurial/osutil.c']))
311
311
312 if sys.platform == 'linux2' and os.uname()[2] > '2.6':
312 if sys.platform.startswith('linux') and os.uname()[2] > '2.6':
313 # The inotify extension is only usable with Linux 2.6 kernels.
313 # The inotify extension is only usable with Linux 2.6 kernels.
314 # You also need a reasonably recent C library.
314 # You also need a reasonably recent C library.
315 # In any case, if it fails to build the error will be skipped ('optional').
315 # In any case, if it fails to build the error will be skipped ('optional').
316 cc = new_compiler()
316 cc = new_compiler()
317 if hasfunction(cc, 'inotify_add_watch'):
317 if hasfunction(cc, 'inotify_add_watch'):
318 inotify = Extension('hgext.inotify.linux._inotify',
318 inotify = Extension('hgext.inotify.linux._inotify',
319 ['hgext/inotify/linux/_inotify.c'],
319 ['hgext/inotify/linux/_inotify.c'],
320 ['mercurial'])
320 ['mercurial'])
321 inotify.optional = True
321 inotify.optional = True
322 extmodules.append(inotify)
322 extmodules.append(inotify)
323 packages.extend(['hgext.inotify', 'hgext.inotify.linux'])
323 packages.extend(['hgext.inotify', 'hgext.inotify.linux'])
324
324
325 packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo',
325 packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo',
326 'help/*.txt']}
326 'help/*.txt']}
327
327
328 def ordinarypath(p):
328 def ordinarypath(p):
329 return p and p[0] != '.' and p[-1] != '~'
329 return p and p[0] != '.' and p[-1] != '~'
330
330
331 for root in ('templates',):
331 for root in ('templates',):
332 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
332 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
333 curdir = curdir.split(os.sep, 1)[1]
333 curdir = curdir.split(os.sep, 1)[1]
334 dirs[:] = filter(ordinarypath, dirs)
334 dirs[:] = filter(ordinarypath, dirs)
335 for f in filter(ordinarypath, files):
335 for f in filter(ordinarypath, files):
336 f = os.path.join(curdir, f)
336 f = os.path.join(curdir, f)
337 packagedata['mercurial'].append(f)
337 packagedata['mercurial'].append(f)
338
338
339 datafiles = []
339 datafiles = []
340 setupversion = version
340 setupversion = version
341 extra = {}
341 extra = {}
342
342
343 if py2exeloaded:
343 if py2exeloaded:
344 extra['console'] = [
344 extra['console'] = [
345 {'script':'hg',
345 {'script':'hg',
346 'copyright':'Copyright (C) 2005-2010 Matt Mackall and others',
346 'copyright':'Copyright (C) 2005-2010 Matt Mackall and others',
347 'product_version':version}]
347 'product_version':version}]
348
348
349 if os.name == 'nt':
349 if os.name == 'nt':
350 # Windows binary file versions for exe/dll files must have the
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
351 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
352 setupversion = version.split('+', 1)[0]
352 setupversion = version.split('+', 1)[0]
353
353
354 setup(name='mercurial',
354 setup(name='mercurial',
355 version=setupversion,
355 version=setupversion,
356 author='Matt Mackall',
356 author='Matt Mackall',
357 author_email='mpm@selenic.com',
357 author_email='mpm@selenic.com',
358 url='http://mercurial.selenic.com/',
358 url='http://mercurial.selenic.com/',
359 description='Scalable distributed SCM',
359 description='Scalable distributed SCM',
360 license='GNU GPLv2+',
360 license='GNU GPLv2+',
361 scripts=scripts,
361 scripts=scripts,
362 packages=packages,
362 packages=packages,
363 py_modules=pymodules,
363 py_modules=pymodules,
364 ext_modules=extmodules,
364 ext_modules=extmodules,
365 data_files=datafiles,
365 data_files=datafiles,
366 package_data=packagedata,
366 package_data=packagedata,
367 cmdclass=cmdclass,
367 cmdclass=cmdclass,
368 options=dict(py2exe=dict(packages=['hgext', 'email']),
368 options=dict(py2exe=dict(packages=['hgext', 'email']),
369 bdist_mpkg=dict(zipdist=True,
369 bdist_mpkg=dict(zipdist=True,
370 license='COPYING',
370 license='COPYING',
371 readme='contrib/macosx/Readme.html',
371 readme='contrib/macosx/Readme.html',
372 welcome='contrib/macosx/Welcome.html')),
372 welcome='contrib/macosx/Welcome.html')),
373 **extra)
373 **extra)
@@ -1,492 +1,492 b''
1 # server.py - common entry point for inotify status server
1 # server.py - common entry point for inotify status server
2 #
2 #
3 # Copyright 2009 Nicolas Dumazet <nicdumz@gmail.com>
3 # Copyright 2009 Nicolas Dumazet <nicdumz@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from mercurial.i18n import _
8 from mercurial.i18n import _
9 from mercurial import cmdutil, osutil, util
9 from mercurial import cmdutil, osutil, util
10 import common
10 import common
11
11
12 import errno
12 import errno
13 import os
13 import os
14 import socket
14 import socket
15 import stat
15 import stat
16 import struct
16 import struct
17 import sys
17 import sys
18 import tempfile
18 import tempfile
19
19
20 class AlreadyStartedException(Exception):
20 class AlreadyStartedException(Exception):
21 pass
21 pass
22 class TimeoutException(Exception):
22 class TimeoutException(Exception):
23 pass
23 pass
24
24
25 def join(a, b):
25 def join(a, b):
26 if a:
26 if a:
27 if a[-1] == '/':
27 if a[-1] == '/':
28 return a + b
28 return a + b
29 return a + '/' + b
29 return a + '/' + b
30 return b
30 return b
31
31
32 def split(path):
32 def split(path):
33 c = path.rfind('/')
33 c = path.rfind('/')
34 if c == -1:
34 if c == -1:
35 return '', path
35 return '', path
36 return path[:c], path[c + 1:]
36 return path[:c], path[c + 1:]
37
37
38 walk_ignored_errors = (errno.ENOENT, errno.ENAMETOOLONG)
38 walk_ignored_errors = (errno.ENOENT, errno.ENAMETOOLONG)
39
39
40 def walk(dirstate, absroot, root):
40 def walk(dirstate, absroot, root):
41 '''Like os.walk, but only yields regular files.'''
41 '''Like os.walk, but only yields regular files.'''
42
42
43 # This function is critical to performance during startup.
43 # This function is critical to performance during startup.
44
44
45 def walkit(root, reporoot):
45 def walkit(root, reporoot):
46 files, dirs = [], []
46 files, dirs = [], []
47
47
48 try:
48 try:
49 fullpath = join(absroot, root)
49 fullpath = join(absroot, root)
50 for name, kind in osutil.listdir(fullpath):
50 for name, kind in osutil.listdir(fullpath):
51 if kind == stat.S_IFDIR:
51 if kind == stat.S_IFDIR:
52 if name == '.hg':
52 if name == '.hg':
53 if not reporoot:
53 if not reporoot:
54 return
54 return
55 else:
55 else:
56 dirs.append(name)
56 dirs.append(name)
57 path = join(root, name)
57 path = join(root, name)
58 if dirstate._ignore(path):
58 if dirstate._ignore(path):
59 continue
59 continue
60 for result in walkit(path, False):
60 for result in walkit(path, False):
61 yield result
61 yield result
62 elif kind in (stat.S_IFREG, stat.S_IFLNK):
62 elif kind in (stat.S_IFREG, stat.S_IFLNK):
63 files.append(name)
63 files.append(name)
64 yield fullpath, dirs, files
64 yield fullpath, dirs, files
65
65
66 except OSError, err:
66 except OSError, err:
67 if err.errno == errno.ENOTDIR:
67 if err.errno == errno.ENOTDIR:
68 # fullpath was a directory, but has since been replaced
68 # fullpath was a directory, but has since been replaced
69 # by a file.
69 # by a file.
70 yield fullpath, dirs, files
70 yield fullpath, dirs, files
71 elif err.errno not in walk_ignored_errors:
71 elif err.errno not in walk_ignored_errors:
72 raise
72 raise
73
73
74 return walkit(root, root == '')
74 return walkit(root, root == '')
75
75
76 class directory(object):
76 class directory(object):
77 """
77 """
78 Representing a directory
78 Representing a directory
79
79
80 * path is the relative path from repo root to this directory
80 * path is the relative path from repo root to this directory
81 * files is a dict listing the files in this directory
81 * files is a dict listing the files in this directory
82 - keys are file names
82 - keys are file names
83 - values are file status
83 - values are file status
84 * dirs is a dict listing the subdirectories
84 * dirs is a dict listing the subdirectories
85 - key are subdirectories names
85 - key are subdirectories names
86 - values are directory objects
86 - values are directory objects
87 """
87 """
88 def __init__(self, relpath=''):
88 def __init__(self, relpath=''):
89 self.path = relpath
89 self.path = relpath
90 self.files = {}
90 self.files = {}
91 self.dirs = {}
91 self.dirs = {}
92
92
93 def dir(self, relpath):
93 def dir(self, relpath):
94 """
94 """
95 Returns the directory contained at the relative path relpath.
95 Returns the directory contained at the relative path relpath.
96 Creates the intermediate directories if necessary.
96 Creates the intermediate directories if necessary.
97 """
97 """
98 if not relpath:
98 if not relpath:
99 return self
99 return self
100 l = relpath.split('/')
100 l = relpath.split('/')
101 ret = self
101 ret = self
102 while l:
102 while l:
103 next = l.pop(0)
103 next = l.pop(0)
104 try:
104 try:
105 ret = ret.dirs[next]
105 ret = ret.dirs[next]
106 except KeyError:
106 except KeyError:
107 d = directory(join(ret.path, next))
107 d = directory(join(ret.path, next))
108 ret.dirs[next] = d
108 ret.dirs[next] = d
109 ret = d
109 ret = d
110 return ret
110 return ret
111
111
112 def walk(self, states, visited=None):
112 def walk(self, states, visited=None):
113 """
113 """
114 yield (filename, status) pairs for items in the trees
114 yield (filename, status) pairs for items in the trees
115 that have status in states.
115 that have status in states.
116 filenames are relative to the repo root
116 filenames are relative to the repo root
117 """
117 """
118 for file, st in self.files.iteritems():
118 for file, st in self.files.iteritems():
119 if st in states:
119 if st in states:
120 yield join(self.path, file), st
120 yield join(self.path, file), st
121 for dir in self.dirs.itervalues():
121 for dir in self.dirs.itervalues():
122 if visited is not None:
122 if visited is not None:
123 visited.add(dir.path)
123 visited.add(dir.path)
124 for e in dir.walk(states):
124 for e in dir.walk(states):
125 yield e
125 yield e
126
126
127 def lookup(self, states, path, visited):
127 def lookup(self, states, path, visited):
128 """
128 """
129 yield root-relative filenames that match path, and whose
129 yield root-relative filenames that match path, and whose
130 status are in states:
130 status are in states:
131 * if path is a file, yield path
131 * if path is a file, yield path
132 * if path is a directory, yield directory files
132 * if path is a directory, yield directory files
133 * if path is not tracked, yield nothing
133 * if path is not tracked, yield nothing
134 """
134 """
135 if path[-1] == '/':
135 if path[-1] == '/':
136 path = path[:-1]
136 path = path[:-1]
137
137
138 paths = path.split('/')
138 paths = path.split('/')
139
139
140 # we need to check separately for last node
140 # we need to check separately for last node
141 last = paths.pop()
141 last = paths.pop()
142
142
143 tree = self
143 tree = self
144 try:
144 try:
145 for dir in paths:
145 for dir in paths:
146 tree = tree.dirs[dir]
146 tree = tree.dirs[dir]
147 except KeyError:
147 except KeyError:
148 # path is not tracked
148 # path is not tracked
149 visited.add(tree.path)
149 visited.add(tree.path)
150 return
150 return
151
151
152 try:
152 try:
153 # if path is a directory, walk it
153 # if path is a directory, walk it
154 target = tree.dirs[last]
154 target = tree.dirs[last]
155 visited.add(target.path)
155 visited.add(target.path)
156 for file, st in target.walk(states, visited):
156 for file, st in target.walk(states, visited):
157 yield file
157 yield file
158 except KeyError:
158 except KeyError:
159 try:
159 try:
160 if tree.files[last] in states:
160 if tree.files[last] in states:
161 # path is a file
161 # path is a file
162 visited.add(tree.path)
162 visited.add(tree.path)
163 yield path
163 yield path
164 except KeyError:
164 except KeyError:
165 # path is not tracked
165 # path is not tracked
166 pass
166 pass
167
167
168 class repowatcher(object):
168 class repowatcher(object):
169 """
169 """
170 Watches inotify events
170 Watches inotify events
171 """
171 """
172 statuskeys = 'almr!?'
172 statuskeys = 'almr!?'
173
173
174 def __init__(self, ui, dirstate, root):
174 def __init__(self, ui, dirstate, root):
175 self.ui = ui
175 self.ui = ui
176 self.dirstate = dirstate
176 self.dirstate = dirstate
177
177
178 self.wprefix = join(root, '')
178 self.wprefix = join(root, '')
179 self.prefixlen = len(self.wprefix)
179 self.prefixlen = len(self.wprefix)
180
180
181 self.tree = directory()
181 self.tree = directory()
182 self.statcache = {}
182 self.statcache = {}
183 self.statustrees = dict([(s, directory()) for s in self.statuskeys])
183 self.statustrees = dict([(s, directory()) for s in self.statuskeys])
184
184
185 self.ds_info = self.dirstate_info()
185 self.ds_info = self.dirstate_info()
186
186
187 self.last_event = None
187 self.last_event = None
188
188
189
189
190 def handle_timeout(self):
190 def handle_timeout(self):
191 pass
191 pass
192
192
193 def dirstate_info(self):
193 def dirstate_info(self):
194 try:
194 try:
195 st = os.lstat(self.wprefix + '.hg/dirstate')
195 st = os.lstat(self.wprefix + '.hg/dirstate')
196 return st.st_mtime, st.st_ino
196 return st.st_mtime, st.st_ino
197 except OSError, err:
197 except OSError, err:
198 if err.errno != errno.ENOENT:
198 if err.errno != errno.ENOENT:
199 raise
199 raise
200 return 0, 0
200 return 0, 0
201
201
202 def filestatus(self, fn, st):
202 def filestatus(self, fn, st):
203 try:
203 try:
204 type_, mode, size, time = self.dirstate._map[fn][:4]
204 type_, mode, size, time = self.dirstate._map[fn][:4]
205 except KeyError:
205 except KeyError:
206 type_ = '?'
206 type_ = '?'
207 if type_ == 'n':
207 if type_ == 'n':
208 st_mode, st_size, st_mtime = st
208 st_mode, st_size, st_mtime = st
209 if size == -1:
209 if size == -1:
210 return 'l'
210 return 'l'
211 if size and (size != st_size or (mode ^ st_mode) & 0100):
211 if size and (size != st_size or (mode ^ st_mode) & 0100):
212 return 'm'
212 return 'm'
213 if time != int(st_mtime):
213 if time != int(st_mtime):
214 return 'l'
214 return 'l'
215 return 'n'
215 return 'n'
216 if type_ == '?' and self.dirstate._dirignore(fn):
216 if type_ == '?' and self.dirstate._dirignore(fn):
217 # we must check not only if the file is ignored, but if any part
217 # we must check not only if the file is ignored, but if any part
218 # of its path match an ignore pattern
218 # of its path match an ignore pattern
219 return 'i'
219 return 'i'
220 return type_
220 return type_
221
221
222 def updatefile(self, wfn, osstat):
222 def updatefile(self, wfn, osstat):
223 '''
223 '''
224 update the file entry of an existing file.
224 update the file entry of an existing file.
225
225
226 osstat: (mode, size, time) tuple, as returned by os.lstat(wfn)
226 osstat: (mode, size, time) tuple, as returned by os.lstat(wfn)
227 '''
227 '''
228
228
229 self._updatestatus(wfn, self.filestatus(wfn, osstat))
229 self._updatestatus(wfn, self.filestatus(wfn, osstat))
230
230
231 def deletefile(self, wfn, oldstatus):
231 def deletefile(self, wfn, oldstatus):
232 '''
232 '''
233 update the entry of a file which has been deleted.
233 update the entry of a file which has been deleted.
234
234
235 oldstatus: char in statuskeys, status of the file before deletion
235 oldstatus: char in statuskeys, status of the file before deletion
236 '''
236 '''
237 if oldstatus == 'r':
237 if oldstatus == 'r':
238 newstatus = 'r'
238 newstatus = 'r'
239 elif oldstatus in 'almn':
239 elif oldstatus in 'almn':
240 newstatus = '!'
240 newstatus = '!'
241 else:
241 else:
242 newstatus = None
242 newstatus = None
243
243
244 self.statcache.pop(wfn, None)
244 self.statcache.pop(wfn, None)
245 self._updatestatus(wfn, newstatus)
245 self._updatestatus(wfn, newstatus)
246
246
247 def _updatestatus(self, wfn, newstatus):
247 def _updatestatus(self, wfn, newstatus):
248 '''
248 '''
249 Update the stored status of a file.
249 Update the stored status of a file.
250
250
251 newstatus: - char in (statuskeys + 'ni'), new status to apply.
251 newstatus: - char in (statuskeys + 'ni'), new status to apply.
252 - or None, to stop tracking wfn
252 - or None, to stop tracking wfn
253 '''
253 '''
254 root, fn = split(wfn)
254 root, fn = split(wfn)
255 d = self.tree.dir(root)
255 d = self.tree.dir(root)
256
256
257 oldstatus = d.files.get(fn)
257 oldstatus = d.files.get(fn)
258 # oldstatus can be either:
258 # oldstatus can be either:
259 # - None : fn is new
259 # - None : fn is new
260 # - a char in statuskeys: fn is a (tracked) file
260 # - a char in statuskeys: fn is a (tracked) file
261
261
262 if self.ui.debugflag and oldstatus != newstatus:
262 if self.ui.debugflag and oldstatus != newstatus:
263 self.ui.note(_('status: %r %s -> %s\n') %
263 self.ui.note(_('status: %r %s -> %s\n') %
264 (wfn, oldstatus, newstatus))
264 (wfn, oldstatus, newstatus))
265
265
266 if oldstatus and oldstatus in self.statuskeys \
266 if oldstatus and oldstatus in self.statuskeys \
267 and oldstatus != newstatus:
267 and oldstatus != newstatus:
268 del self.statustrees[oldstatus].dir(root).files[fn]
268 del self.statustrees[oldstatus].dir(root).files[fn]
269
269
270 if newstatus in (None, 'i'):
270 if newstatus in (None, 'i'):
271 d.files.pop(fn, None)
271 d.files.pop(fn, None)
272 elif oldstatus != newstatus:
272 elif oldstatus != newstatus:
273 d.files[fn] = newstatus
273 d.files[fn] = newstatus
274 if newstatus != 'n':
274 if newstatus != 'n':
275 self.statustrees[newstatus].dir(root).files[fn] = newstatus
275 self.statustrees[newstatus].dir(root).files[fn] = newstatus
276
276
277 def check_deleted(self, key):
277 def check_deleted(self, key):
278 # Files that had been deleted but were present in the dirstate
278 # Files that had been deleted but were present in the dirstate
279 # may have vanished from the dirstate; we must clean them up.
279 # may have vanished from the dirstate; we must clean them up.
280 nuke = []
280 nuke = []
281 for wfn, ignore in self.statustrees[key].walk(key):
281 for wfn, ignore in self.statustrees[key].walk(key):
282 if wfn not in self.dirstate:
282 if wfn not in self.dirstate:
283 nuke.append(wfn)
283 nuke.append(wfn)
284 for wfn in nuke:
284 for wfn in nuke:
285 root, fn = split(wfn)
285 root, fn = split(wfn)
286 del self.statustrees[key].dir(root).files[fn]
286 del self.statustrees[key].dir(root).files[fn]
287 del self.tree.dir(root).files[fn]
287 del self.tree.dir(root).files[fn]
288
288
289 def update_hgignore(self):
289 def update_hgignore(self):
290 # An update of the ignore file can potentially change the
290 # An update of the ignore file can potentially change the
291 # states of all unknown and ignored files.
291 # states of all unknown and ignored files.
292
292
293 # XXX If the user has other ignore files outside the repo, or
293 # XXX If the user has other ignore files outside the repo, or
294 # changes their list of ignore files at run time, we'll
294 # changes their list of ignore files at run time, we'll
295 # potentially never see changes to them. We could get the
295 # potentially never see changes to them. We could get the
296 # client to report to us what ignore data they're using.
296 # client to report to us what ignore data they're using.
297 # But it's easier to do nothing than to open that can of
297 # But it's easier to do nothing than to open that can of
298 # worms.
298 # worms.
299
299
300 if '_ignore' in self.dirstate.__dict__:
300 if '_ignore' in self.dirstate.__dict__:
301 delattr(self.dirstate, '_ignore')
301 delattr(self.dirstate, '_ignore')
302 self.ui.note(_('rescanning due to .hgignore change\n'))
302 self.ui.note(_('rescanning due to .hgignore change\n'))
303 self.handle_timeout()
303 self.handle_timeout()
304 self.scan()
304 self.scan()
305
305
306 def getstat(self, wpath):
306 def getstat(self, wpath):
307 try:
307 try:
308 return self.statcache[wpath]
308 return self.statcache[wpath]
309 except KeyError:
309 except KeyError:
310 try:
310 try:
311 return self.stat(wpath)
311 return self.stat(wpath)
312 except OSError, err:
312 except OSError, err:
313 if err.errno != errno.ENOENT:
313 if err.errno != errno.ENOENT:
314 raise
314 raise
315
315
316 def stat(self, wpath):
316 def stat(self, wpath):
317 try:
317 try:
318 st = os.lstat(join(self.wprefix, wpath))
318 st = os.lstat(join(self.wprefix, wpath))
319 ret = st.st_mode, st.st_size, st.st_mtime
319 ret = st.st_mode, st.st_size, st.st_mtime
320 self.statcache[wpath] = ret
320 self.statcache[wpath] = ret
321 return ret
321 return ret
322 except OSError:
322 except OSError:
323 self.statcache.pop(wpath, None)
323 self.statcache.pop(wpath, None)
324 raise
324 raise
325
325
326 class socketlistener(object):
326 class socketlistener(object):
327 """
327 """
328 Listens for client queries on unix socket inotify.sock
328 Listens for client queries on unix socket inotify.sock
329 """
329 """
330 def __init__(self, ui, root, repowatcher, timeout):
330 def __init__(self, ui, root, repowatcher, timeout):
331 self.ui = ui
331 self.ui = ui
332 self.repowatcher = repowatcher
332 self.repowatcher = repowatcher
333 self.sock = socket.socket(socket.AF_UNIX)
333 self.sock = socket.socket(socket.AF_UNIX)
334 self.sockpath = join(root, '.hg/inotify.sock')
334 self.sockpath = join(root, '.hg/inotify.sock')
335
335
336 self.realsockpath = self.sockpath
336 self.realsockpath = self.sockpath
337 if os.path.islink(self.sockpath):
337 if os.path.islink(self.sockpath):
338 if os.path.exists(self.sockpath):
338 if os.path.exists(self.sockpath):
339 self.realsockpath = os.readlink(self.sockpath)
339 self.realsockpath = os.readlink(self.sockpath)
340 else:
340 else:
341 raise util.Abort('inotify-server: cannot start: '
341 raise util.Abort('inotify-server: cannot start: '
342 '.hg/inotify.sock is a broken symlink')
342 '.hg/inotify.sock is a broken symlink')
343 try:
343 try:
344 self.sock.bind(self.realsockpath)
344 self.sock.bind(self.realsockpath)
345 except socket.error, err:
345 except socket.error, err:
346 if err.args[0] == errno.EADDRINUSE:
346 if err.args[0] == errno.EADDRINUSE:
347 raise AlreadyStartedException(_('cannot start: socket is '
347 raise AlreadyStartedException(_('cannot start: socket is '
348 'already bound'))
348 'already bound'))
349 if err.args[0] == "AF_UNIX path too long":
349 if err.args[0] == "AF_UNIX path too long":
350 tempdir = tempfile.mkdtemp(prefix="hg-inotify-")
350 tempdir = tempfile.mkdtemp(prefix="hg-inotify-")
351 self.realsockpath = os.path.join(tempdir, "inotify.sock")
351 self.realsockpath = os.path.join(tempdir, "inotify.sock")
352 try:
352 try:
353 self.sock.bind(self.realsockpath)
353 self.sock.bind(self.realsockpath)
354 os.symlink(self.realsockpath, self.sockpath)
354 os.symlink(self.realsockpath, self.sockpath)
355 except (OSError, socket.error), inst:
355 except (OSError, socket.error), inst:
356 try:
356 try:
357 os.unlink(self.realsockpath)
357 os.unlink(self.realsockpath)
358 except:
358 except:
359 pass
359 pass
360 os.rmdir(tempdir)
360 os.rmdir(tempdir)
361 if inst.errno == errno.EEXIST:
361 if inst.errno == errno.EEXIST:
362 raise AlreadyStartedException(_('cannot start: tried '
362 raise AlreadyStartedException(_('cannot start: tried '
363 'linking .hg/inotify.sock to a temporary socket but'
363 'linking .hg/inotify.sock to a temporary socket but'
364 ' .hg/inotify.sock already exists'))
364 ' .hg/inotify.sock already exists'))
365 raise
365 raise
366 else:
366 else:
367 raise
367 raise
368 self.sock.listen(5)
368 self.sock.listen(5)
369 self.fileno = self.sock.fileno
369 self.fileno = self.sock.fileno
370
370
371 def answer_stat_query(self, cs):
371 def answer_stat_query(self, cs):
372 names = cs.read().split('\0')
372 names = cs.read().split('\0')
373
373
374 states = names.pop()
374 states = names.pop()
375
375
376 self.ui.note(_('answering query for %r\n') % states)
376 self.ui.note(_('answering query for %r\n') % states)
377
377
378 visited = set()
378 visited = set()
379 if not names:
379 if not names:
380 def genresult(states, tree):
380 def genresult(states, tree):
381 for fn, state in tree.walk(states):
381 for fn, state in tree.walk(states):
382 yield fn
382 yield fn
383 else:
383 else:
384 def genresult(states, tree):
384 def genresult(states, tree):
385 for fn in names:
385 for fn in names:
386 for f in tree.lookup(states, fn, visited):
386 for f in tree.lookup(states, fn, visited):
387 yield f
387 yield f
388
388
389 return ['\0'.join(r) for r in [
389 return ['\0'.join(r) for r in [
390 genresult('l', self.repowatcher.statustrees['l']),
390 genresult('l', self.repowatcher.statustrees['l']),
391 genresult('m', self.repowatcher.statustrees['m']),
391 genresult('m', self.repowatcher.statustrees['m']),
392 genresult('a', self.repowatcher.statustrees['a']),
392 genresult('a', self.repowatcher.statustrees['a']),
393 genresult('r', self.repowatcher.statustrees['r']),
393 genresult('r', self.repowatcher.statustrees['r']),
394 genresult('!', self.repowatcher.statustrees['!']),
394 genresult('!', self.repowatcher.statustrees['!']),
395 '?' in states
395 '?' in states
396 and genresult('?', self.repowatcher.statustrees['?'])
396 and genresult('?', self.repowatcher.statustrees['?'])
397 or [],
397 or [],
398 [],
398 [],
399 'c' in states and genresult('n', self.repowatcher.tree) or [],
399 'c' in states and genresult('n', self.repowatcher.tree) or [],
400 visited
400 visited
401 ]]
401 ]]
402
402
403 def answer_dbug_query(self):
403 def answer_dbug_query(self):
404 return ['\0'.join(self.repowatcher.debug())]
404 return ['\0'.join(self.repowatcher.debug())]
405
405
406 def accept_connection(self):
406 def accept_connection(self):
407 sock, addr = self.sock.accept()
407 sock, addr = self.sock.accept()
408
408
409 cs = common.recvcs(sock)
409 cs = common.recvcs(sock)
410 version = ord(cs.read(1))
410 version = ord(cs.read(1))
411
411
412 if version != common.version:
412 if version != common.version:
413 self.ui.warn(_('received query from incompatible client '
413 self.ui.warn(_('received query from incompatible client '
414 'version %d\n') % version)
414 'version %d\n') % version)
415 try:
415 try:
416 # try to send back our version to the client
416 # try to send back our version to the client
417 # this way, the client too is informed of the mismatch
417 # this way, the client too is informed of the mismatch
418 sock.sendall(chr(common.version))
418 sock.sendall(chr(common.version))
419 except:
419 except:
420 pass
420 pass
421 return
421 return
422
422
423 type = cs.read(4)
423 type = cs.read(4)
424
424
425 if type == 'STAT':
425 if type == 'STAT':
426 results = self.answer_stat_query(cs)
426 results = self.answer_stat_query(cs)
427 elif type == 'DBUG':
427 elif type == 'DBUG':
428 results = self.answer_dbug_query()
428 results = self.answer_dbug_query()
429 else:
429 else:
430 self.ui.warn(_('unrecognized query type: %s\n') % type)
430 self.ui.warn(_('unrecognized query type: %s\n') % type)
431 return
431 return
432
432
433 try:
433 try:
434 try:
434 try:
435 v = chr(common.version)
435 v = chr(common.version)
436
436
437 sock.sendall(v + type + struct.pack(common.resphdrfmts[type],
437 sock.sendall(v + type + struct.pack(common.resphdrfmts[type],
438 *map(len, results)))
438 *map(len, results)))
439 sock.sendall(''.join(results))
439 sock.sendall(''.join(results))
440 finally:
440 finally:
441 sock.shutdown(socket.SHUT_WR)
441 sock.shutdown(socket.SHUT_WR)
442 except socket.error, err:
442 except socket.error, err:
443 if err.args[0] != errno.EPIPE:
443 if err.args[0] != errno.EPIPE:
444 raise
444 raise
445
445
446 if sys.platform == 'linux2':
446 if sys.platform.startswith('linux'):
447 import linuxserver as _server
447 import linuxserver as _server
448 else:
448 else:
449 raise ImportError
449 raise ImportError
450
450
451 master = _server.master
451 master = _server.master
452
452
453 def start(ui, dirstate, root, opts):
453 def start(ui, dirstate, root, opts):
454 timeout = opts.get('idle_timeout')
454 timeout = opts.get('idle_timeout')
455 if timeout:
455 if timeout:
456 timeout = float(timeout) * 60000
456 timeout = float(timeout) * 60000
457 else:
457 else:
458 timeout = None
458 timeout = None
459
459
460 class service(object):
460 class service(object):
461 def init(self):
461 def init(self):
462 try:
462 try:
463 self.master = master(ui, dirstate, root, timeout)
463 self.master = master(ui, dirstate, root, timeout)
464 except AlreadyStartedException, inst:
464 except AlreadyStartedException, inst:
465 raise util.Abort("inotify-server: %s" % inst)
465 raise util.Abort("inotify-server: %s" % inst)
466
466
467 def run(self):
467 def run(self):
468 try:
468 try:
469 try:
469 try:
470 self.master.run()
470 self.master.run()
471 except TimeoutException:
471 except TimeoutException:
472 pass
472 pass
473 finally:
473 finally:
474 self.master.shutdown()
474 self.master.shutdown()
475
475
476 if 'inserve' not in sys.argv:
476 if 'inserve' not in sys.argv:
477 runargs = util.hgcmd() + ['inserve', '-R', root]
477 runargs = util.hgcmd() + ['inserve', '-R', root]
478 else:
478 else:
479 runargs = util.hgcmd() + sys.argv[1:]
479 runargs = util.hgcmd() + sys.argv[1:]
480
480
481 pidfile = ui.config('inotify', 'pidfile')
481 pidfile = ui.config('inotify', 'pidfile')
482 if opts['daemon'] and pidfile is not None and 'pid-file' not in runargs:
482 if opts['daemon'] and pidfile is not None and 'pid-file' not in runargs:
483 runargs.append("--pid-file=%s" % pidfile)
483 runargs.append("--pid-file=%s" % pidfile)
484
484
485 service = service()
485 service = service()
486 logfile = ui.config('inotify', 'log')
486 logfile = ui.config('inotify', 'log')
487
487
488 appendpid = ui.configbool('inotify', 'appendpid', False)
488 appendpid = ui.configbool('inotify', 'appendpid', False)
489
489
490 ui.debug('starting inotify server: %s\n' % ' '.join(runargs))
490 ui.debug('starting inotify server: %s\n' % ' '.join(runargs))
491 cmdutil.service(opts, initfn=service.init, runfn=service.run,
491 cmdutil.service(opts, initfn=service.init, runfn=service.run,
492 logfile=logfile, runargs=runargs, appendpid=appendpid)
492 logfile=logfile, runargs=runargs, appendpid=appendpid)
@@ -1,441 +1,441 b''
1 #
1 #
2 # This is the mercurial setup script.
2 # This is the mercurial setup script.
3 #
3 #
4 # 'python setup.py install', or
4 # 'python setup.py install', or
5 # 'python setup.py --help' for more options
5 # 'python setup.py --help' for more options
6
6
7 import sys, platform
7 import sys, platform
8 if not hasattr(sys, 'version_info') or sys.version_info < (2, 4, 0, 'final'):
8 if not hasattr(sys, 'version_info') or sys.version_info < (2, 4, 0, 'final'):
9 raise SystemExit("Mercurial requires Python 2.4 or later.")
9 raise SystemExit("Mercurial requires Python 2.4 or later.")
10
10
11 if sys.version_info[0] >= 3:
11 if sys.version_info[0] >= 3:
12 def b(s):
12 def b(s):
13 '''A helper function to emulate 2.6+ bytes literals using string
13 '''A helper function to emulate 2.6+ bytes literals using string
14 literals.'''
14 literals.'''
15 return s.encode('latin1')
15 return s.encode('latin1')
16 else:
16 else:
17 def b(s):
17 def b(s):
18 '''A helper function to emulate 2.6+ bytes literals using string
18 '''A helper function to emulate 2.6+ bytes literals using string
19 literals.'''
19 literals.'''
20 return s
20 return s
21
21
22 # Solaris Python packaging brain damage
22 # Solaris Python packaging brain damage
23 try:
23 try:
24 import hashlib
24 import hashlib
25 sha = hashlib.sha1()
25 sha = hashlib.sha1()
26 except:
26 except:
27 try:
27 try:
28 import sha
28 import sha
29 except:
29 except:
30 raise SystemExit(
30 raise SystemExit(
31 "Couldn't import standard hashlib (incomplete Python install).")
31 "Couldn't import standard hashlib (incomplete Python install).")
32
32
33 try:
33 try:
34 import zlib
34 import zlib
35 except:
35 except:
36 raise SystemExit(
36 raise SystemExit(
37 "Couldn't import standard zlib (incomplete Python install).")
37 "Couldn't import standard zlib (incomplete Python install).")
38
38
39 # The base IronPython distribution (as of 2.7.1) doesn't support bz2
39 # The base IronPython distribution (as of 2.7.1) doesn't support bz2
40 isironpython = False
40 isironpython = False
41 try:
41 try:
42 isironpython = platform.python_implementation().lower().find("ironpython") != -1
42 isironpython = platform.python_implementation().lower().find("ironpython") != -1
43 except:
43 except:
44 pass
44 pass
45
45
46 if isironpython:
46 if isironpython:
47 print "warning: IronPython detected (no bz2 support)"
47 print "warning: IronPython detected (no bz2 support)"
48 else:
48 else:
49 try:
49 try:
50 import bz2
50 import bz2
51 except:
51 except:
52 raise SystemExit(
52 raise SystemExit(
53 "Couldn't import standard bz2 (incomplete Python install).")
53 "Couldn't import standard bz2 (incomplete Python install).")
54
54
55 import os, subprocess, time
55 import os, subprocess, time
56 import shutil
56 import shutil
57 import tempfile
57 import tempfile
58 from distutils import log
58 from distutils import log
59 from distutils.core import setup, Command, Extension
59 from distutils.core import setup, Command, Extension
60 from distutils.dist import Distribution
60 from distutils.dist import Distribution
61 from distutils.command.build import build
61 from distutils.command.build import build
62 from distutils.command.build_ext import build_ext
62 from distutils.command.build_ext import build_ext
63 from distutils.command.build_py import build_py
63 from distutils.command.build_py import build_py
64 from distutils.command.install_scripts import install_scripts
64 from distutils.command.install_scripts import install_scripts
65 from distutils.spawn import spawn, find_executable
65 from distutils.spawn import spawn, find_executable
66 from distutils.ccompiler import new_compiler
66 from distutils.ccompiler import new_compiler
67 from distutils.errors import CCompilerError, DistutilsExecError
67 from distutils.errors import CCompilerError, DistutilsExecError
68 from distutils.sysconfig import get_python_inc
68 from distutils.sysconfig import get_python_inc
69 from distutils.version import StrictVersion
69 from distutils.version import StrictVersion
70
70
71 scripts = ['hg']
71 scripts = ['hg']
72 if os.name == 'nt':
72 if os.name == 'nt':
73 scripts.append('contrib/win32/hg.bat')
73 scripts.append('contrib/win32/hg.bat')
74
74
75 # simplified version of distutils.ccompiler.CCompiler.has_function
75 # simplified version of distutils.ccompiler.CCompiler.has_function
76 # that actually removes its temporary files.
76 # that actually removes its temporary files.
77 def hasfunction(cc, funcname):
77 def hasfunction(cc, funcname):
78 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
78 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
79 devnull = oldstderr = None
79 devnull = oldstderr = None
80 try:
80 try:
81 try:
81 try:
82 fname = os.path.join(tmpdir, 'funcname.c')
82 fname = os.path.join(tmpdir, 'funcname.c')
83 f = open(fname, 'w')
83 f = open(fname, 'w')
84 f.write('int main(void) {\n')
84 f.write('int main(void) {\n')
85 f.write(' %s();\n' % funcname)
85 f.write(' %s();\n' % funcname)
86 f.write('}\n')
86 f.write('}\n')
87 f.close()
87 f.close()
88 # Redirect stderr to /dev/null to hide any error messages
88 # Redirect stderr to /dev/null to hide any error messages
89 # from the compiler.
89 # from the compiler.
90 # This will have to be changed if we ever have to check
90 # This will have to be changed if we ever have to check
91 # for a function on Windows.
91 # for a function on Windows.
92 devnull = open('/dev/null', 'w')
92 devnull = open('/dev/null', 'w')
93 oldstderr = os.dup(sys.stderr.fileno())
93 oldstderr = os.dup(sys.stderr.fileno())
94 os.dup2(devnull.fileno(), sys.stderr.fileno())
94 os.dup2(devnull.fileno(), sys.stderr.fileno())
95 objects = cc.compile([fname], output_dir=tmpdir)
95 objects = cc.compile([fname], output_dir=tmpdir)
96 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
96 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
97 except:
97 except:
98 return False
98 return False
99 return True
99 return True
100 finally:
100 finally:
101 if oldstderr is not None:
101 if oldstderr is not None:
102 os.dup2(oldstderr, sys.stderr.fileno())
102 os.dup2(oldstderr, sys.stderr.fileno())
103 if devnull is not None:
103 if devnull is not None:
104 devnull.close()
104 devnull.close()
105 shutil.rmtree(tmpdir)
105 shutil.rmtree(tmpdir)
106
106
107 # py2exe needs to be installed to work
107 # py2exe needs to be installed to work
108 try:
108 try:
109 import py2exe
109 import py2exe
110 py2exeloaded = True
110 py2exeloaded = True
111 except ImportError:
111 except ImportError:
112 py2exeloaded = False
112 py2exeloaded = False
113
113
114 def runcmd(cmd, env):
114 def runcmd(cmd, env):
115 p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
115 p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
116 stderr=subprocess.PIPE, env=env)
116 stderr=subprocess.PIPE, env=env)
117 out, err = p.communicate()
117 out, err = p.communicate()
118 return out, err
118 return out, err
119
119
120 def runhg(cmd, env):
120 def runhg(cmd, env):
121 out, err = runcmd(cmd, env)
121 out, err = runcmd(cmd, env)
122 # If root is executing setup.py, but the repository is owned by
122 # If root is executing setup.py, but the repository is owned by
123 # another user (as in "sudo python setup.py install") we will get
123 # another user (as in "sudo python setup.py install") we will get
124 # trust warnings since the .hg/hgrc file is untrusted. That is
124 # trust warnings since the .hg/hgrc file is untrusted. That is
125 # fine, we don't want to load it anyway. Python may warn about
125 # fine, we don't want to load it anyway. Python may warn about
126 # a missing __init__.py in mercurial/locale, we also ignore that.
126 # a missing __init__.py in mercurial/locale, we also ignore that.
127 err = [e for e in err.splitlines()
127 err = [e for e in err.splitlines()
128 if not e.startswith(b('Not trusting file')) \
128 if not e.startswith(b('Not trusting file')) \
129 and not e.startswith(b('warning: Not importing'))]
129 and not e.startswith(b('warning: Not importing'))]
130 if err:
130 if err:
131 return ''
131 return ''
132 return out
132 return out
133
133
134 version = ''
134 version = ''
135
135
136 if os.path.isdir('.hg'):
136 if os.path.isdir('.hg'):
137 # Execute hg out of this directory with a custom environment which
137 # Execute hg out of this directory with a custom environment which
138 # includes the pure Python modules in mercurial/pure. We also take
138 # includes the pure Python modules in mercurial/pure. We also take
139 # care to not use any hgrc files and do no localization.
139 # care to not use any hgrc files and do no localization.
140 pypath = ['mercurial', os.path.join('mercurial', 'pure')]
140 pypath = ['mercurial', os.path.join('mercurial', 'pure')]
141 env = {'PYTHONPATH': os.pathsep.join(pypath),
141 env = {'PYTHONPATH': os.pathsep.join(pypath),
142 'HGRCPATH': '',
142 'HGRCPATH': '',
143 'LANGUAGE': 'C'}
143 'LANGUAGE': 'C'}
144 if 'LD_LIBRARY_PATH' in os.environ:
144 if 'LD_LIBRARY_PATH' in os.environ:
145 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
145 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
146 if 'SystemRoot' in os.environ:
146 if 'SystemRoot' in os.environ:
147 # Copy SystemRoot into the custom environment for Python 2.6
147 # Copy SystemRoot into the custom environment for Python 2.6
148 # under Windows. Otherwise, the subprocess will fail with
148 # under Windows. Otherwise, the subprocess will fail with
149 # error 0xc0150004. See: http://bugs.python.org/issue3440
149 # error 0xc0150004. See: http://bugs.python.org/issue3440
150 env['SystemRoot'] = os.environ['SystemRoot']
150 env['SystemRoot'] = os.environ['SystemRoot']
151 cmd = [sys.executable, 'hg', 'id', '-i', '-t']
151 cmd = [sys.executable, 'hg', 'id', '-i', '-t']
152 l = runhg(cmd, env).split()
152 l = runhg(cmd, env).split()
153 while len(l) > 1 and l[-1][0].isalpha(): # remove non-numbered tags
153 while len(l) > 1 and l[-1][0].isalpha(): # remove non-numbered tags
154 l.pop()
154 l.pop()
155 if len(l) > 1: # tag found
155 if len(l) > 1: # tag found
156 version = l[-1]
156 version = l[-1]
157 if l[0].endswith('+'): # propagate the dirty status to the tag
157 if l[0].endswith('+'): # propagate the dirty status to the tag
158 version += '+'
158 version += '+'
159 elif len(l) == 1: # no tag found
159 elif len(l) == 1: # no tag found
160 cmd = [sys.executable, 'hg', 'parents', '--template',
160 cmd = [sys.executable, 'hg', 'parents', '--template',
161 '{latesttag}+{latesttagdistance}-']
161 '{latesttag}+{latesttagdistance}-']
162 version = runhg(cmd, env) + l[0]
162 version = runhg(cmd, env) + l[0]
163 if version.endswith('+'):
163 if version.endswith('+'):
164 version += time.strftime('%Y%m%d')
164 version += time.strftime('%Y%m%d')
165 elif os.path.exists('.hg_archival.txt'):
165 elif os.path.exists('.hg_archival.txt'):
166 kw = dict([[t.strip() for t in l.split(':', 1)]
166 kw = dict([[t.strip() for t in l.split(':', 1)]
167 for l in open('.hg_archival.txt')])
167 for l in open('.hg_archival.txt')])
168 if 'tag' in kw:
168 if 'tag' in kw:
169 version = kw['tag']
169 version = kw['tag']
170 elif 'latesttag' in kw:
170 elif 'latesttag' in kw:
171 version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
171 version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
172 else:
172 else:
173 version = kw.get('node', '')[:12]
173 version = kw.get('node', '')[:12]
174
174
175 if version:
175 if version:
176 f = open("mercurial/__version__.py", "w")
176 f = open("mercurial/__version__.py", "w")
177 f.write('# this file is autogenerated by setup.py\n')
177 f.write('# this file is autogenerated by setup.py\n')
178 f.write('version = "%s"\n' % version)
178 f.write('version = "%s"\n' % version)
179 f.close()
179 f.close()
180
180
181
181
182 try:
182 try:
183 from mercurial import __version__
183 from mercurial import __version__
184 version = __version__.version
184 version = __version__.version
185 except ImportError:
185 except ImportError:
186 version = 'unknown'
186 version = 'unknown'
187
187
188 class hgbuildmo(build):
188 class hgbuildmo(build):
189
189
190 description = "build translations (.mo files)"
190 description = "build translations (.mo files)"
191
191
192 def run(self):
192 def run(self):
193 if not find_executable('msgfmt'):
193 if not find_executable('msgfmt'):
194 self.warn("could not find msgfmt executable, no translations "
194 self.warn("could not find msgfmt executable, no translations "
195 "will be built")
195 "will be built")
196 return
196 return
197
197
198 podir = 'i18n'
198 podir = 'i18n'
199 if not os.path.isdir(podir):
199 if not os.path.isdir(podir):
200 self.warn("could not find %s/ directory" % podir)
200 self.warn("could not find %s/ directory" % podir)
201 return
201 return
202
202
203 join = os.path.join
203 join = os.path.join
204 for po in os.listdir(podir):
204 for po in os.listdir(podir):
205 if not po.endswith('.po'):
205 if not po.endswith('.po'):
206 continue
206 continue
207 pofile = join(podir, po)
207 pofile = join(podir, po)
208 modir = join('locale', po[:-3], 'LC_MESSAGES')
208 modir = join('locale', po[:-3], 'LC_MESSAGES')
209 mofile = join(modir, 'hg.mo')
209 mofile = join(modir, 'hg.mo')
210 mobuildfile = join('mercurial', mofile)
210 mobuildfile = join('mercurial', mofile)
211 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
211 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
212 if sys.platform != 'sunos5':
212 if sys.platform != 'sunos5':
213 # msgfmt on Solaris does not know about -c
213 # msgfmt on Solaris does not know about -c
214 cmd.append('-c')
214 cmd.append('-c')
215 self.mkpath(join('mercurial', modir))
215 self.mkpath(join('mercurial', modir))
216 self.make_file([pofile], mobuildfile, spawn, (cmd,))
216 self.make_file([pofile], mobuildfile, spawn, (cmd,))
217
217
218
218
219 # Insert hgbuildmo first so that files in mercurial/locale/ are found
219 # Insert hgbuildmo first so that files in mercurial/locale/ are found
220 # when build_py is run next.
220 # when build_py is run next.
221 build.sub_commands.insert(0, ('build_mo', None))
221 build.sub_commands.insert(0, ('build_mo', None))
222
222
223 Distribution.pure = 0
223 Distribution.pure = 0
224 Distribution.global_options.append(('pure', None, "use pure (slow) Python "
224 Distribution.global_options.append(('pure', None, "use pure (slow) Python "
225 "code instead of C extensions"))
225 "code instead of C extensions"))
226
226
227 class hgbuildext(build_ext):
227 class hgbuildext(build_ext):
228
228
229 def build_extension(self, ext):
229 def build_extension(self, ext):
230 try:
230 try:
231 build_ext.build_extension(self, ext)
231 build_ext.build_extension(self, ext)
232 except CCompilerError:
232 except CCompilerError:
233 if not getattr(ext, 'optional', False):
233 if not getattr(ext, 'optional', False):
234 raise
234 raise
235 log.warn("Failed to build optional extension '%s' (skipping)",
235 log.warn("Failed to build optional extension '%s' (skipping)",
236 ext.name)
236 ext.name)
237
237
238 class hgbuildpy(build_py):
238 class hgbuildpy(build_py):
239
239
240 def finalize_options(self):
240 def finalize_options(self):
241 build_py.finalize_options(self)
241 build_py.finalize_options(self)
242
242
243 if self.distribution.pure:
243 if self.distribution.pure:
244 if self.py_modules is None:
244 if self.py_modules is None:
245 self.py_modules = []
245 self.py_modules = []
246 for ext in self.distribution.ext_modules:
246 for ext in self.distribution.ext_modules:
247 if ext.name.startswith("mercurial."):
247 if ext.name.startswith("mercurial."):
248 self.py_modules.append("mercurial.pure.%s" % ext.name[10:])
248 self.py_modules.append("mercurial.pure.%s" % ext.name[10:])
249 self.distribution.ext_modules = []
249 self.distribution.ext_modules = []
250 else:
250 else:
251 if not os.path.exists(os.path.join(get_python_inc(), 'Python.h')):
251 if not os.path.exists(os.path.join(get_python_inc(), 'Python.h')):
252 raise SystemExit("Python headers are required to build Mercurial")
252 raise SystemExit("Python headers are required to build Mercurial")
253
253
254 def find_modules(self):
254 def find_modules(self):
255 modules = build_py.find_modules(self)
255 modules = build_py.find_modules(self)
256 for module in modules:
256 for module in modules:
257 if module[0] == "mercurial.pure":
257 if module[0] == "mercurial.pure":
258 if module[1] != "__init__":
258 if module[1] != "__init__":
259 yield ("mercurial", module[1], module[2])
259 yield ("mercurial", module[1], module[2])
260 else:
260 else:
261 yield module
261 yield module
262
262
263 class buildhgextindex(Command):
263 class buildhgextindex(Command):
264 description = 'generate prebuilt index of hgext (for frozen package)'
264 description = 'generate prebuilt index of hgext (for frozen package)'
265 user_options = []
265 user_options = []
266 _indexfilename = 'hgext/__index__.py'
266 _indexfilename = 'hgext/__index__.py'
267
267
268 def initialize_options(self):
268 def initialize_options(self):
269 pass
269 pass
270
270
271 def finalize_options(self):
271 def finalize_options(self):
272 pass
272 pass
273
273
274 def run(self):
274 def run(self):
275 if os.path.exists(self._indexfilename):
275 if os.path.exists(self._indexfilename):
276 os.unlink(self._indexfilename)
276 os.unlink(self._indexfilename)
277
277
278 # here no extension enabled, disabled() lists up everything
278 # here no extension enabled, disabled() lists up everything
279 code = ('import pprint; from mercurial import extensions; '
279 code = ('import pprint; from mercurial import extensions; '
280 'pprint.pprint(extensions.disabled())')
280 'pprint.pprint(extensions.disabled())')
281 out, err = runcmd([sys.executable, '-c', code], env)
281 out, err = runcmd([sys.executable, '-c', code], env)
282 if err:
282 if err:
283 raise DistutilsExecError(err)
283 raise DistutilsExecError(err)
284
284
285 f = open(self._indexfilename, 'w')
285 f = open(self._indexfilename, 'w')
286 f.write('# this file is autogenerated by setup.py\n')
286 f.write('# this file is autogenerated by setup.py\n')
287 f.write('docs = ')
287 f.write('docs = ')
288 f.write(out)
288 f.write(out)
289 f.close()
289 f.close()
290
290
291 class hginstallscripts(install_scripts):
291 class hginstallscripts(install_scripts):
292 '''
292 '''
293 This is a specialization of install_scripts that replaces the @LIBDIR@ with
293 This is a specialization of install_scripts that replaces the @LIBDIR@ with
294 the configured directory for modules. If possible, the path is made relative
294 the configured directory for modules. If possible, the path is made relative
295 to the directory for scripts.
295 to the directory for scripts.
296 '''
296 '''
297
297
298 def initialize_options(self):
298 def initialize_options(self):
299 install_scripts.initialize_options(self)
299 install_scripts.initialize_options(self)
300
300
301 self.install_lib = None
301 self.install_lib = None
302
302
303 def finalize_options(self):
303 def finalize_options(self):
304 install_scripts.finalize_options(self)
304 install_scripts.finalize_options(self)
305 self.set_undefined_options('install',
305 self.set_undefined_options('install',
306 ('install_lib', 'install_lib'))
306 ('install_lib', 'install_lib'))
307
307
308 def run(self):
308 def run(self):
309 install_scripts.run(self)
309 install_scripts.run(self)
310
310
311 if (os.path.splitdrive(self.install_dir)[0] !=
311 if (os.path.splitdrive(self.install_dir)[0] !=
312 os.path.splitdrive(self.install_lib)[0]):
312 os.path.splitdrive(self.install_lib)[0]):
313 # can't make relative paths from one drive to another, so use an
313 # can't make relative paths from one drive to another, so use an
314 # absolute path instead
314 # absolute path instead
315 libdir = self.install_lib
315 libdir = self.install_lib
316 else:
316 else:
317 common = os.path.commonprefix((self.install_dir, self.install_lib))
317 common = os.path.commonprefix((self.install_dir, self.install_lib))
318 rest = self.install_dir[len(common):]
318 rest = self.install_dir[len(common):]
319 uplevel = len([n for n in os.path.split(rest) if n])
319 uplevel = len([n for n in os.path.split(rest) if n])
320
320
321 libdir = uplevel * ('..' + os.sep) + self.install_lib[len(common):]
321 libdir = uplevel * ('..' + os.sep) + self.install_lib[len(common):]
322
322
323 for outfile in self.outfiles:
323 for outfile in self.outfiles:
324 fp = open(outfile, 'rb')
324 fp = open(outfile, 'rb')
325 data = fp.read()
325 data = fp.read()
326 fp.close()
326 fp.close()
327
327
328 # skip binary files
328 # skip binary files
329 if '\0' in data:
329 if '\0' in data:
330 continue
330 continue
331
331
332 data = data.replace('@LIBDIR@', libdir.encode('string_escape'))
332 data = data.replace('@LIBDIR@', libdir.encode('string_escape'))
333 fp = open(outfile, 'wb')
333 fp = open(outfile, 'wb')
334 fp.write(data)
334 fp.write(data)
335 fp.close()
335 fp.close()
336
336
337 cmdclass = {'build_mo': hgbuildmo,
337 cmdclass = {'build_mo': hgbuildmo,
338 'build_ext': hgbuildext,
338 'build_ext': hgbuildext,
339 'build_py': hgbuildpy,
339 'build_py': hgbuildpy,
340 'build_hgextindex': buildhgextindex,
340 'build_hgextindex': buildhgextindex,
341 'install_scripts': hginstallscripts}
341 'install_scripts': hginstallscripts}
342
342
343 packages = ['mercurial', 'mercurial.hgweb',
343 packages = ['mercurial', 'mercurial.hgweb',
344 'mercurial.httpclient', 'mercurial.httpclient.tests',
344 'mercurial.httpclient', 'mercurial.httpclient.tests',
345 'hgext', 'hgext.convert', 'hgext.highlight', 'hgext.zeroconf']
345 'hgext', 'hgext.convert', 'hgext.highlight', 'hgext.zeroconf']
346
346
347 pymodules = []
347 pymodules = []
348
348
349 extmodules = [
349 extmodules = [
350 Extension('mercurial.base85', ['mercurial/base85.c']),
350 Extension('mercurial.base85', ['mercurial/base85.c']),
351 Extension('mercurial.bdiff', ['mercurial/bdiff.c']),
351 Extension('mercurial.bdiff', ['mercurial/bdiff.c']),
352 Extension('mercurial.diffhelpers', ['mercurial/diffhelpers.c']),
352 Extension('mercurial.diffhelpers', ['mercurial/diffhelpers.c']),
353 Extension('mercurial.mpatch', ['mercurial/mpatch.c']),
353 Extension('mercurial.mpatch', ['mercurial/mpatch.c']),
354 Extension('mercurial.parsers', ['mercurial/parsers.c']),
354 Extension('mercurial.parsers', ['mercurial/parsers.c']),
355 ]
355 ]
356
356
357 osutil_ldflags = []
357 osutil_ldflags = []
358
358
359 if sys.platform == 'darwin':
359 if sys.platform == 'darwin':
360 osutil_ldflags += ['-framework', 'ApplicationServices']
360 osutil_ldflags += ['-framework', 'ApplicationServices']
361
361
362 # disable osutil.c under windows + python 2.4 (issue1364)
362 # disable osutil.c under windows + python 2.4 (issue1364)
363 if sys.platform == 'win32' and sys.version_info < (2, 5, 0, 'final'):
363 if sys.platform == 'win32' and sys.version_info < (2, 5, 0, 'final'):
364 pymodules.append('mercurial.pure.osutil')
364 pymodules.append('mercurial.pure.osutil')
365 else:
365 else:
366 extmodules.append(Extension('mercurial.osutil', ['mercurial/osutil.c'],
366 extmodules.append(Extension('mercurial.osutil', ['mercurial/osutil.c'],
367 extra_link_args=osutil_ldflags))
367 extra_link_args=osutil_ldflags))
368
368
369 if sys.platform == 'linux2' and os.uname()[2] > '2.6':
369 if sys.platform.startswith('linux') and os.uname()[2] > '2.6':
370 # The inotify extension is only usable with Linux 2.6 kernels.
370 # The inotify extension is only usable with Linux 2.6 kernels.
371 # You also need a reasonably recent C library.
371 # You also need a reasonably recent C library.
372 # In any case, if it fails to build the error will be skipped ('optional').
372 # In any case, if it fails to build the error will be skipped ('optional').
373 cc = new_compiler()
373 cc = new_compiler()
374 if hasfunction(cc, 'inotify_add_watch'):
374 if hasfunction(cc, 'inotify_add_watch'):
375 inotify = Extension('hgext.inotify.linux._inotify',
375 inotify = Extension('hgext.inotify.linux._inotify',
376 ['hgext/inotify/linux/_inotify.c'],
376 ['hgext/inotify/linux/_inotify.c'],
377 ['mercurial'])
377 ['mercurial'])
378 inotify.optional = True
378 inotify.optional = True
379 extmodules.append(inotify)
379 extmodules.append(inotify)
380 packages.extend(['hgext.inotify', 'hgext.inotify.linux'])
380 packages.extend(['hgext.inotify', 'hgext.inotify.linux'])
381
381
382 packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo',
382 packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo',
383 'help/*.txt']}
383 'help/*.txt']}
384
384
385 def ordinarypath(p):
385 def ordinarypath(p):
386 return p and p[0] != '.' and p[-1] != '~'
386 return p and p[0] != '.' and p[-1] != '~'
387
387
388 for root in ('templates',):
388 for root in ('templates',):
389 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
389 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
390 curdir = curdir.split(os.sep, 1)[1]
390 curdir = curdir.split(os.sep, 1)[1]
391 dirs[:] = filter(ordinarypath, dirs)
391 dirs[:] = filter(ordinarypath, dirs)
392 for f in filter(ordinarypath, files):
392 for f in filter(ordinarypath, files):
393 f = os.path.join(curdir, f)
393 f = os.path.join(curdir, f)
394 packagedata['mercurial'].append(f)
394 packagedata['mercurial'].append(f)
395
395
396 datafiles = []
396 datafiles = []
397 setupversion = version
397 setupversion = version
398 extra = {}
398 extra = {}
399
399
400 if py2exeloaded:
400 if py2exeloaded:
401 extra['console'] = [
401 extra['console'] = [
402 {'script':'hg',
402 {'script':'hg',
403 'copyright':'Copyright (C) 2005-2010 Matt Mackall and others',
403 'copyright':'Copyright (C) 2005-2010 Matt Mackall and others',
404 'product_version':version}]
404 'product_version':version}]
405 # sub command of 'build' because 'py2exe' does not handle sub_commands
405 # sub command of 'build' because 'py2exe' does not handle sub_commands
406 build.sub_commands.insert(0, ('build_hgextindex', None))
406 build.sub_commands.insert(0, ('build_hgextindex', None))
407
407
408 if os.name == 'nt':
408 if os.name == 'nt':
409 # Windows binary file versions for exe/dll files must have the
409 # Windows binary file versions for exe/dll files must have the
410 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
410 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
411 setupversion = version.split('+', 1)[0]
411 setupversion = version.split('+', 1)[0]
412
412
413 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
413 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
414 # XCode 4.0 dropped support for ppc architecture, which is hardcoded in
414 # XCode 4.0 dropped support for ppc architecture, which is hardcoded in
415 # distutils.sysconfig
415 # distutils.sysconfig
416 version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[0].splitlines()[0]
416 version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[0].splitlines()[0]
417 # Also parse only first digit, because 3.2.1 can't be parsed nicely
417 # Also parse only first digit, because 3.2.1 can't be parsed nicely
418 if (version.startswith('Xcode') and
418 if (version.startswith('Xcode') and
419 StrictVersion(version.split()[1]) >= StrictVersion('4.0')):
419 StrictVersion(version.split()[1]) >= StrictVersion('4.0')):
420 os.environ['ARCHFLAGS'] = ''
420 os.environ['ARCHFLAGS'] = ''
421
421
422 setup(name='mercurial',
422 setup(name='mercurial',
423 version=setupversion,
423 version=setupversion,
424 author='Matt Mackall',
424 author='Matt Mackall',
425 author_email='mpm@selenic.com',
425 author_email='mpm@selenic.com',
426 url='http://mercurial.selenic.com/',
426 url='http://mercurial.selenic.com/',
427 description='Scalable distributed SCM',
427 description='Scalable distributed SCM',
428 license='GNU GPLv2+',
428 license='GNU GPLv2+',
429 scripts=scripts,
429 scripts=scripts,
430 packages=packages,
430 packages=packages,
431 py_modules=pymodules,
431 py_modules=pymodules,
432 ext_modules=extmodules,
432 ext_modules=extmodules,
433 data_files=datafiles,
433 data_files=datafiles,
434 package_data=packagedata,
434 package_data=packagedata,
435 cmdclass=cmdclass,
435 cmdclass=cmdclass,
436 options=dict(py2exe=dict(packages=['hgext', 'email']),
436 options=dict(py2exe=dict(packages=['hgext', 'email']),
437 bdist_mpkg=dict(zipdist=True,
437 bdist_mpkg=dict(zipdist=True,
438 license='COPYING',
438 license='COPYING',
439 readme='contrib/macosx/Readme.html',
439 readme='contrib/macosx/Readme.html',
440 welcome='contrib/macosx/Welcome.html')),
440 welcome='contrib/macosx/Welcome.html')),
441 **extra)
441 **extra)
General Comments 0
You need to be logged in to leave comments. Login now