##// END OF EJS Templates
extensions: also search for extension in the 'hgext3rd' package...
Pierre-Yves David -
r28541:4b81487a default
parent child Browse files
Show More
@@ -1,3 +1,4 b''
1 # name space package to host third party extensions
1 from __future__ import absolute_import
2 from __future__ import absolute_import
2 import pkgutil
3 import pkgutil
3 __path__ = pkgutil.extend_path(__path__, __name__)
4 __path__ = pkgutil.extend_path(__path__, __name__)
@@ -1,487 +1,491 b''
1 # extensions.py - extension handling for mercurial
1 # extensions.py - extension handling for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.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 __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import imp
10 import imp
11 import os
11 import os
12
12
13 from .i18n import (
13 from .i18n import (
14 _,
14 _,
15 gettext,
15 gettext,
16 )
16 )
17
17
18 from . import (
18 from . import (
19 cmdutil,
19 cmdutil,
20 error,
20 error,
21 util,
21 util,
22 )
22 )
23
23
24 _extensions = {}
24 _extensions = {}
25 _aftercallbacks = {}
25 _aftercallbacks = {}
26 _order = []
26 _order = []
27 _builtin = set(['hbisect', 'bookmarks', 'parentrevspec', 'progress', 'interhg',
27 _builtin = set(['hbisect', 'bookmarks', 'parentrevspec', 'progress', 'interhg',
28 'inotify'])
28 'inotify'])
29
29
30 def extensions(ui=None):
30 def extensions(ui=None):
31 if ui:
31 if ui:
32 def enabled(name):
32 def enabled(name):
33 for format in ['%s', 'hgext.%s']:
33 for format in ['%s', 'hgext.%s']:
34 conf = ui.config('extensions', format % name)
34 conf = ui.config('extensions', format % name)
35 if conf is not None and not conf.startswith('!'):
35 if conf is not None and not conf.startswith('!'):
36 return True
36 return True
37 else:
37 else:
38 enabled = lambda name: True
38 enabled = lambda name: True
39 for name in _order:
39 for name in _order:
40 module = _extensions[name]
40 module = _extensions[name]
41 if module and enabled(name):
41 if module and enabled(name):
42 yield name, module
42 yield name, module
43
43
44 def find(name):
44 def find(name):
45 '''return module with given extension name'''
45 '''return module with given extension name'''
46 mod = None
46 mod = None
47 try:
47 try:
48 mod = _extensions[name]
48 mod = _extensions[name]
49 except KeyError:
49 except KeyError:
50 for k, v in _extensions.iteritems():
50 for k, v in _extensions.iteritems():
51 if k.endswith('.' + name) or k.endswith('/' + name):
51 if k.endswith('.' + name) or k.endswith('/' + name):
52 mod = v
52 mod = v
53 break
53 break
54 if not mod:
54 if not mod:
55 raise KeyError(name)
55 raise KeyError(name)
56 return mod
56 return mod
57
57
58 def loadpath(path, module_name):
58 def loadpath(path, module_name):
59 module_name = module_name.replace('.', '_')
59 module_name = module_name.replace('.', '_')
60 path = util.normpath(util.expandpath(path))
60 path = util.normpath(util.expandpath(path))
61 if os.path.isdir(path):
61 if os.path.isdir(path):
62 # module/__init__.py style
62 # module/__init__.py style
63 d, f = os.path.split(path)
63 d, f = os.path.split(path)
64 fd, fpath, desc = imp.find_module(f, [d])
64 fd, fpath, desc = imp.find_module(f, [d])
65 return imp.load_module(module_name, fd, fpath, desc)
65 return imp.load_module(module_name, fd, fpath, desc)
66 else:
66 else:
67 try:
67 try:
68 return imp.load_source(module_name, path)
68 return imp.load_source(module_name, path)
69 except IOError as exc:
69 except IOError as exc:
70 if not exc.filename:
70 if not exc.filename:
71 exc.filename = path # python does not fill this
71 exc.filename = path # python does not fill this
72 raise
72 raise
73
73
74 def _importh(name):
74 def _importh(name):
75 """import and return the <name> module"""
75 """import and return the <name> module"""
76 mod = __import__(name)
76 mod = __import__(name)
77 components = name.split('.')
77 components = name.split('.')
78 for comp in components[1:]:
78 for comp in components[1:]:
79 mod = getattr(mod, comp)
79 mod = getattr(mod, comp)
80 return mod
80 return mod
81
81
82 def _reportimporterror(ui, err, failed, next):
82 def _reportimporterror(ui, err, failed, next):
83 ui.debug('could not import %s (%s): trying %s\n'
83 ui.debug('could not import %s (%s): trying %s\n'
84 % (failed, err, next))
84 % (failed, err, next))
85 if ui.debugflag:
85 if ui.debugflag:
86 ui.traceback()
86 ui.traceback()
87
87
88 def load(ui, name, path):
88 def load(ui, name, path):
89 if name.startswith('hgext.') or name.startswith('hgext/'):
89 if name.startswith('hgext.') or name.startswith('hgext/'):
90 shortname = name[6:]
90 shortname = name[6:]
91 else:
91 else:
92 shortname = name
92 shortname = name
93 if shortname in _builtin:
93 if shortname in _builtin:
94 return None
94 return None
95 if shortname in _extensions:
95 if shortname in _extensions:
96 return _extensions[shortname]
96 return _extensions[shortname]
97 _extensions[shortname] = None
97 _extensions[shortname] = None
98 if path:
98 if path:
99 # the module will be loaded in sys.modules
99 # the module will be loaded in sys.modules
100 # choose an unique name so that it doesn't
100 # choose an unique name so that it doesn't
101 # conflicts with other modules
101 # conflicts with other modules
102 mod = loadpath(path, 'hgext.%s' % name)
102 mod = loadpath(path, 'hgext.%s' % name)
103 else:
103 else:
104 try:
104 try:
105 mod = _importh("hgext.%s" % name)
105 mod = _importh("hgext.%s" % name)
106 except ImportError as err:
106 except ImportError as err:
107 _reportimporterror(ui, err, "hgext.%s" % name, name)
107 _reportimporterror(ui, err, "hgext.%s" % name, name)
108 mod = _importh(name)
108 try:
109 mod = _importh("hgext3rd.%s" % name)
110 except ImportError as err:
111 _reportimporterror(ui, err, "hgext3rd.%s" % name, name)
112 mod = _importh(name)
109
113
110 # Before we do anything with the extension, check against minimum stated
114 # Before we do anything with the extension, check against minimum stated
111 # compatibility. This gives extension authors a mechanism to have their
115 # compatibility. This gives extension authors a mechanism to have their
112 # extensions short circuit when loaded with a known incompatible version
116 # extensions short circuit when loaded with a known incompatible version
113 # of Mercurial.
117 # of Mercurial.
114 minver = getattr(mod, 'minimumhgversion', None)
118 minver = getattr(mod, 'minimumhgversion', None)
115 if minver and util.versiontuple(minver, 2) > util.versiontuple(n=2):
119 if minver and util.versiontuple(minver, 2) > util.versiontuple(n=2):
116 ui.warn(_('(third party extension %s requires version %s or newer '
120 ui.warn(_('(third party extension %s requires version %s or newer '
117 'of Mercurial; disabling)\n') % (shortname, minver))
121 'of Mercurial; disabling)\n') % (shortname, minver))
118 return
122 return
119
123
120 _extensions[shortname] = mod
124 _extensions[shortname] = mod
121 _order.append(shortname)
125 _order.append(shortname)
122 for fn in _aftercallbacks.get(shortname, []):
126 for fn in _aftercallbacks.get(shortname, []):
123 fn(loaded=True)
127 fn(loaded=True)
124 return mod
128 return mod
125
129
126 def loadall(ui):
130 def loadall(ui):
127 result = ui.configitems("extensions")
131 result = ui.configitems("extensions")
128 newindex = len(_order)
132 newindex = len(_order)
129 for (name, path) in result:
133 for (name, path) in result:
130 if path:
134 if path:
131 if path[0] == '!':
135 if path[0] == '!':
132 continue
136 continue
133 try:
137 try:
134 load(ui, name, path)
138 load(ui, name, path)
135 except KeyboardInterrupt:
139 except KeyboardInterrupt:
136 raise
140 raise
137 except Exception as inst:
141 except Exception as inst:
138 if path:
142 if path:
139 ui.warn(_("*** failed to import extension %s from %s: %s\n")
143 ui.warn(_("*** failed to import extension %s from %s: %s\n")
140 % (name, path, inst))
144 % (name, path, inst))
141 else:
145 else:
142 ui.warn(_("*** failed to import extension %s: %s\n")
146 ui.warn(_("*** failed to import extension %s: %s\n")
143 % (name, inst))
147 % (name, inst))
144 ui.traceback()
148 ui.traceback()
145
149
146 for name in _order[newindex:]:
150 for name in _order[newindex:]:
147 uisetup = getattr(_extensions[name], 'uisetup', None)
151 uisetup = getattr(_extensions[name], 'uisetup', None)
148 if uisetup:
152 if uisetup:
149 uisetup(ui)
153 uisetup(ui)
150
154
151 for name in _order[newindex:]:
155 for name in _order[newindex:]:
152 extsetup = getattr(_extensions[name], 'extsetup', None)
156 extsetup = getattr(_extensions[name], 'extsetup', None)
153 if extsetup:
157 if extsetup:
154 try:
158 try:
155 extsetup(ui)
159 extsetup(ui)
156 except TypeError:
160 except TypeError:
157 if extsetup.func_code.co_argcount != 0:
161 if extsetup.func_code.co_argcount != 0:
158 raise
162 raise
159 extsetup() # old extsetup with no ui argument
163 extsetup() # old extsetup with no ui argument
160
164
161 # Call aftercallbacks that were never met.
165 # Call aftercallbacks that were never met.
162 for shortname in _aftercallbacks:
166 for shortname in _aftercallbacks:
163 if shortname in _extensions:
167 if shortname in _extensions:
164 continue
168 continue
165
169
166 for fn in _aftercallbacks[shortname]:
170 for fn in _aftercallbacks[shortname]:
167 fn(loaded=False)
171 fn(loaded=False)
168
172
169 # loadall() is called multiple times and lingering _aftercallbacks
173 # loadall() is called multiple times and lingering _aftercallbacks
170 # entries could result in double execution. See issue4646.
174 # entries could result in double execution. See issue4646.
171 _aftercallbacks.clear()
175 _aftercallbacks.clear()
172
176
173 def afterloaded(extension, callback):
177 def afterloaded(extension, callback):
174 '''Run the specified function after a named extension is loaded.
178 '''Run the specified function after a named extension is loaded.
175
179
176 If the named extension is already loaded, the callback will be called
180 If the named extension is already loaded, the callback will be called
177 immediately.
181 immediately.
178
182
179 If the named extension never loads, the callback will be called after
183 If the named extension never loads, the callback will be called after
180 all extensions have been loaded.
184 all extensions have been loaded.
181
185
182 The callback receives the named argument ``loaded``, which is a boolean
186 The callback receives the named argument ``loaded``, which is a boolean
183 indicating whether the dependent extension actually loaded.
187 indicating whether the dependent extension actually loaded.
184 '''
188 '''
185
189
186 if extension in _extensions:
190 if extension in _extensions:
187 callback(loaded=True)
191 callback(loaded=True)
188 else:
192 else:
189 _aftercallbacks.setdefault(extension, []).append(callback)
193 _aftercallbacks.setdefault(extension, []).append(callback)
190
194
191 def bind(func, *args):
195 def bind(func, *args):
192 '''Partial function application
196 '''Partial function application
193
197
194 Returns a new function that is the partial application of args and kwargs
198 Returns a new function that is the partial application of args and kwargs
195 to func. For example,
199 to func. For example,
196
200
197 f(1, 2, bar=3) === bind(f, 1)(2, bar=3)'''
201 f(1, 2, bar=3) === bind(f, 1)(2, bar=3)'''
198 assert callable(func)
202 assert callable(func)
199 def closure(*a, **kw):
203 def closure(*a, **kw):
200 return func(*(args + a), **kw)
204 return func(*(args + a), **kw)
201 return closure
205 return closure
202
206
203 def _updatewrapper(wrap, origfn):
207 def _updatewrapper(wrap, origfn):
204 '''Copy attributes to wrapper function'''
208 '''Copy attributes to wrapper function'''
205 wrap.__module__ = getattr(origfn, '__module__')
209 wrap.__module__ = getattr(origfn, '__module__')
206 wrap.__doc__ = getattr(origfn, '__doc__')
210 wrap.__doc__ = getattr(origfn, '__doc__')
207 wrap.__dict__.update(getattr(origfn, '__dict__', {}))
211 wrap.__dict__.update(getattr(origfn, '__dict__', {}))
208
212
209 def wrapcommand(table, command, wrapper, synopsis=None, docstring=None):
213 def wrapcommand(table, command, wrapper, synopsis=None, docstring=None):
210 '''Wrap the command named `command' in table
214 '''Wrap the command named `command' in table
211
215
212 Replace command in the command table with wrapper. The wrapped command will
216 Replace command in the command table with wrapper. The wrapped command will
213 be inserted into the command table specified by the table argument.
217 be inserted into the command table specified by the table argument.
214
218
215 The wrapper will be called like
219 The wrapper will be called like
216
220
217 wrapper(orig, *args, **kwargs)
221 wrapper(orig, *args, **kwargs)
218
222
219 where orig is the original (wrapped) function, and *args, **kwargs
223 where orig is the original (wrapped) function, and *args, **kwargs
220 are the arguments passed to it.
224 are the arguments passed to it.
221
225
222 Optionally append to the command synopsis and docstring, used for help.
226 Optionally append to the command synopsis and docstring, used for help.
223 For example, if your extension wraps the ``bookmarks`` command to add the
227 For example, if your extension wraps the ``bookmarks`` command to add the
224 flags ``--remote`` and ``--all`` you might call this function like so:
228 flags ``--remote`` and ``--all`` you might call this function like so:
225
229
226 synopsis = ' [-a] [--remote]'
230 synopsis = ' [-a] [--remote]'
227 docstring = """
231 docstring = """
228
232
229 The ``remotenames`` extension adds the ``--remote`` and ``--all`` (``-a``)
233 The ``remotenames`` extension adds the ``--remote`` and ``--all`` (``-a``)
230 flags to the bookmarks command. Either flag will show the remote bookmarks
234 flags to the bookmarks command. Either flag will show the remote bookmarks
231 known to the repository; ``--remote`` will also suppress the output of the
235 known to the repository; ``--remote`` will also suppress the output of the
232 local bookmarks.
236 local bookmarks.
233 """
237 """
234
238
235 extensions.wrapcommand(commands.table, 'bookmarks', exbookmarks,
239 extensions.wrapcommand(commands.table, 'bookmarks', exbookmarks,
236 synopsis, docstring)
240 synopsis, docstring)
237 '''
241 '''
238 assert callable(wrapper)
242 assert callable(wrapper)
239 aliases, entry = cmdutil.findcmd(command, table)
243 aliases, entry = cmdutil.findcmd(command, table)
240 for alias, e in table.iteritems():
244 for alias, e in table.iteritems():
241 if e is entry:
245 if e is entry:
242 key = alias
246 key = alias
243 break
247 break
244
248
245 origfn = entry[0]
249 origfn = entry[0]
246 wrap = bind(util.checksignature(wrapper), util.checksignature(origfn))
250 wrap = bind(util.checksignature(wrapper), util.checksignature(origfn))
247 _updatewrapper(wrap, origfn)
251 _updatewrapper(wrap, origfn)
248 if docstring is not None:
252 if docstring is not None:
249 wrap.__doc__ += docstring
253 wrap.__doc__ += docstring
250
254
251 newentry = list(entry)
255 newentry = list(entry)
252 newentry[0] = wrap
256 newentry[0] = wrap
253 if synopsis is not None:
257 if synopsis is not None:
254 newentry[2] += synopsis
258 newentry[2] += synopsis
255 table[key] = tuple(newentry)
259 table[key] = tuple(newentry)
256 return entry
260 return entry
257
261
258 def wrapfunction(container, funcname, wrapper):
262 def wrapfunction(container, funcname, wrapper):
259 '''Wrap the function named funcname in container
263 '''Wrap the function named funcname in container
260
264
261 Replace the funcname member in the given container with the specified
265 Replace the funcname member in the given container with the specified
262 wrapper. The container is typically a module, class, or instance.
266 wrapper. The container is typically a module, class, or instance.
263
267
264 The wrapper will be called like
268 The wrapper will be called like
265
269
266 wrapper(orig, *args, **kwargs)
270 wrapper(orig, *args, **kwargs)
267
271
268 where orig is the original (wrapped) function, and *args, **kwargs
272 where orig is the original (wrapped) function, and *args, **kwargs
269 are the arguments passed to it.
273 are the arguments passed to it.
270
274
271 Wrapping methods of the repository object is not recommended since
275 Wrapping methods of the repository object is not recommended since
272 it conflicts with extensions that extend the repository by
276 it conflicts with extensions that extend the repository by
273 subclassing. All extensions that need to extend methods of
277 subclassing. All extensions that need to extend methods of
274 localrepository should use this subclassing trick: namely,
278 localrepository should use this subclassing trick: namely,
275 reposetup() should look like
279 reposetup() should look like
276
280
277 def reposetup(ui, repo):
281 def reposetup(ui, repo):
278 class myrepo(repo.__class__):
282 class myrepo(repo.__class__):
279 def whatever(self, *args, **kwargs):
283 def whatever(self, *args, **kwargs):
280 [...extension stuff...]
284 [...extension stuff...]
281 super(myrepo, self).whatever(*args, **kwargs)
285 super(myrepo, self).whatever(*args, **kwargs)
282 [...extension stuff...]
286 [...extension stuff...]
283
287
284 repo.__class__ = myrepo
288 repo.__class__ = myrepo
285
289
286 In general, combining wrapfunction() with subclassing does not
290 In general, combining wrapfunction() with subclassing does not
287 work. Since you cannot control what other extensions are loaded by
291 work. Since you cannot control what other extensions are loaded by
288 your end users, you should play nicely with others by using the
292 your end users, you should play nicely with others by using the
289 subclass trick.
293 subclass trick.
290 '''
294 '''
291 assert callable(wrapper)
295 assert callable(wrapper)
292
296
293 origfn = getattr(container, funcname)
297 origfn = getattr(container, funcname)
294 assert callable(origfn)
298 assert callable(origfn)
295 wrap = bind(wrapper, origfn)
299 wrap = bind(wrapper, origfn)
296 _updatewrapper(wrap, origfn)
300 _updatewrapper(wrap, origfn)
297 setattr(container, funcname, wrap)
301 setattr(container, funcname, wrap)
298 return origfn
302 return origfn
299
303
300 def _disabledpaths(strip_init=False):
304 def _disabledpaths(strip_init=False):
301 '''find paths of disabled extensions. returns a dict of {name: path}
305 '''find paths of disabled extensions. returns a dict of {name: path}
302 removes /__init__.py from packages if strip_init is True'''
306 removes /__init__.py from packages if strip_init is True'''
303 import hgext
307 import hgext
304 extpath = os.path.dirname(os.path.abspath(hgext.__file__))
308 extpath = os.path.dirname(os.path.abspath(hgext.__file__))
305 try: # might not be a filesystem path
309 try: # might not be a filesystem path
306 files = os.listdir(extpath)
310 files = os.listdir(extpath)
307 except OSError:
311 except OSError:
308 return {}
312 return {}
309
313
310 exts = {}
314 exts = {}
311 for e in files:
315 for e in files:
312 if e.endswith('.py'):
316 if e.endswith('.py'):
313 name = e.rsplit('.', 1)[0]
317 name = e.rsplit('.', 1)[0]
314 path = os.path.join(extpath, e)
318 path = os.path.join(extpath, e)
315 else:
319 else:
316 name = e
320 name = e
317 path = os.path.join(extpath, e, '__init__.py')
321 path = os.path.join(extpath, e, '__init__.py')
318 if not os.path.exists(path):
322 if not os.path.exists(path):
319 continue
323 continue
320 if strip_init:
324 if strip_init:
321 path = os.path.dirname(path)
325 path = os.path.dirname(path)
322 if name in exts or name in _order or name == '__init__':
326 if name in exts or name in _order or name == '__init__':
323 continue
327 continue
324 exts[name] = path
328 exts[name] = path
325 return exts
329 return exts
326
330
327 def _moduledoc(file):
331 def _moduledoc(file):
328 '''return the top-level python documentation for the given file
332 '''return the top-level python documentation for the given file
329
333
330 Loosely inspired by pydoc.source_synopsis(), but rewritten to
334 Loosely inspired by pydoc.source_synopsis(), but rewritten to
331 handle triple quotes and to return the whole text instead of just
335 handle triple quotes and to return the whole text instead of just
332 the synopsis'''
336 the synopsis'''
333 result = []
337 result = []
334
338
335 line = file.readline()
339 line = file.readline()
336 while line[:1] == '#' or not line.strip():
340 while line[:1] == '#' or not line.strip():
337 line = file.readline()
341 line = file.readline()
338 if not line:
342 if not line:
339 break
343 break
340
344
341 start = line[:3]
345 start = line[:3]
342 if start == '"""' or start == "'''":
346 if start == '"""' or start == "'''":
343 line = line[3:]
347 line = line[3:]
344 while line:
348 while line:
345 if line.rstrip().endswith(start):
349 if line.rstrip().endswith(start):
346 line = line.split(start)[0]
350 line = line.split(start)[0]
347 if line:
351 if line:
348 result.append(line)
352 result.append(line)
349 break
353 break
350 elif not line:
354 elif not line:
351 return None # unmatched delimiter
355 return None # unmatched delimiter
352 result.append(line)
356 result.append(line)
353 line = file.readline()
357 line = file.readline()
354 else:
358 else:
355 return None
359 return None
356
360
357 return ''.join(result)
361 return ''.join(result)
358
362
359 def _disabledhelp(path):
363 def _disabledhelp(path):
360 '''retrieve help synopsis of a disabled extension (without importing)'''
364 '''retrieve help synopsis of a disabled extension (without importing)'''
361 try:
365 try:
362 file = open(path)
366 file = open(path)
363 except IOError:
367 except IOError:
364 return
368 return
365 else:
369 else:
366 doc = _moduledoc(file)
370 doc = _moduledoc(file)
367 file.close()
371 file.close()
368
372
369 if doc: # extracting localized synopsis
373 if doc: # extracting localized synopsis
370 return gettext(doc).splitlines()[0]
374 return gettext(doc).splitlines()[0]
371 else:
375 else:
372 return _('(no help text available)')
376 return _('(no help text available)')
373
377
374 def disabled():
378 def disabled():
375 '''find disabled extensions from hgext. returns a dict of {name: desc}'''
379 '''find disabled extensions from hgext. returns a dict of {name: desc}'''
376 try:
380 try:
377 from hgext import __index__
381 from hgext import __index__
378 return dict((name, gettext(desc))
382 return dict((name, gettext(desc))
379 for name, desc in __index__.docs.iteritems()
383 for name, desc in __index__.docs.iteritems()
380 if name not in _order)
384 if name not in _order)
381 except (ImportError, AttributeError):
385 except (ImportError, AttributeError):
382 pass
386 pass
383
387
384 paths = _disabledpaths()
388 paths = _disabledpaths()
385 if not paths:
389 if not paths:
386 return {}
390 return {}
387
391
388 exts = {}
392 exts = {}
389 for name, path in paths.iteritems():
393 for name, path in paths.iteritems():
390 doc = _disabledhelp(path)
394 doc = _disabledhelp(path)
391 if doc:
395 if doc:
392 exts[name] = doc
396 exts[name] = doc
393
397
394 return exts
398 return exts
395
399
396 def disabledext(name):
400 def disabledext(name):
397 '''find a specific disabled extension from hgext. returns desc'''
401 '''find a specific disabled extension from hgext. returns desc'''
398 try:
402 try:
399 from hgext import __index__
403 from hgext import __index__
400 if name in _order: # enabled
404 if name in _order: # enabled
401 return
405 return
402 else:
406 else:
403 return gettext(__index__.docs.get(name))
407 return gettext(__index__.docs.get(name))
404 except (ImportError, AttributeError):
408 except (ImportError, AttributeError):
405 pass
409 pass
406
410
407 paths = _disabledpaths()
411 paths = _disabledpaths()
408 if name in paths:
412 if name in paths:
409 return _disabledhelp(paths[name])
413 return _disabledhelp(paths[name])
410
414
411 def disabledcmd(ui, cmd, strict=False):
415 def disabledcmd(ui, cmd, strict=False):
412 '''import disabled extensions until cmd is found.
416 '''import disabled extensions until cmd is found.
413 returns (cmdname, extname, module)'''
417 returns (cmdname, extname, module)'''
414
418
415 paths = _disabledpaths(strip_init=True)
419 paths = _disabledpaths(strip_init=True)
416 if not paths:
420 if not paths:
417 raise error.UnknownCommand(cmd)
421 raise error.UnknownCommand(cmd)
418
422
419 def findcmd(cmd, name, path):
423 def findcmd(cmd, name, path):
420 try:
424 try:
421 mod = loadpath(path, 'hgext.%s' % name)
425 mod = loadpath(path, 'hgext.%s' % name)
422 except Exception:
426 except Exception:
423 return
427 return
424 try:
428 try:
425 aliases, entry = cmdutil.findcmd(cmd,
429 aliases, entry = cmdutil.findcmd(cmd,
426 getattr(mod, 'cmdtable', {}), strict)
430 getattr(mod, 'cmdtable', {}), strict)
427 except (error.AmbiguousCommand, error.UnknownCommand):
431 except (error.AmbiguousCommand, error.UnknownCommand):
428 return
432 return
429 except Exception:
433 except Exception:
430 ui.warn(_('warning: error finding commands in %s\n') % path)
434 ui.warn(_('warning: error finding commands in %s\n') % path)
431 ui.traceback()
435 ui.traceback()
432 return
436 return
433 for c in aliases:
437 for c in aliases:
434 if c.startswith(cmd):
438 if c.startswith(cmd):
435 cmd = c
439 cmd = c
436 break
440 break
437 else:
441 else:
438 cmd = aliases[0]
442 cmd = aliases[0]
439 return (cmd, name, mod)
443 return (cmd, name, mod)
440
444
441 ext = None
445 ext = None
442 # first, search for an extension with the same name as the command
446 # first, search for an extension with the same name as the command
443 path = paths.pop(cmd, None)
447 path = paths.pop(cmd, None)
444 if path:
448 if path:
445 ext = findcmd(cmd, cmd, path)
449 ext = findcmd(cmd, cmd, path)
446 if not ext:
450 if not ext:
447 # otherwise, interrogate each extension until there's a match
451 # otherwise, interrogate each extension until there's a match
448 for name, path in paths.iteritems():
452 for name, path in paths.iteritems():
449 ext = findcmd(cmd, name, path)
453 ext = findcmd(cmd, name, path)
450 if ext:
454 if ext:
451 break
455 break
452 if ext and 'DEPRECATED' not in ext.__doc__:
456 if ext and 'DEPRECATED' not in ext.__doc__:
453 return ext
457 return ext
454
458
455 raise error.UnknownCommand(cmd)
459 raise error.UnknownCommand(cmd)
456
460
457 def enabled(shortname=True):
461 def enabled(shortname=True):
458 '''return a dict of {name: desc} of extensions'''
462 '''return a dict of {name: desc} of extensions'''
459 exts = {}
463 exts = {}
460 for ename, ext in extensions():
464 for ename, ext in extensions():
461 doc = (gettext(ext.__doc__) or _('(no help text available)'))
465 doc = (gettext(ext.__doc__) or _('(no help text available)'))
462 if shortname:
466 if shortname:
463 ename = ename.split('.')[-1]
467 ename = ename.split('.')[-1]
464 exts[ename] = doc.splitlines()[0].strip()
468 exts[ename] = doc.splitlines()[0].strip()
465
469
466 return exts
470 return exts
467
471
468 def notloaded():
472 def notloaded():
469 '''return short names of extensions that failed to load'''
473 '''return short names of extensions that failed to load'''
470 return [name for name, mod in _extensions.iteritems() if mod is None]
474 return [name for name, mod in _extensions.iteritems() if mod is None]
471
475
472 def moduleversion(module):
476 def moduleversion(module):
473 '''return version information from given module as a string'''
477 '''return version information from given module as a string'''
474 if (util.safehasattr(module, 'getversion')
478 if (util.safehasattr(module, 'getversion')
475 and callable(module.getversion)):
479 and callable(module.getversion)):
476 version = module.getversion()
480 version = module.getversion()
477 elif util.safehasattr(module, '__version__'):
481 elif util.safehasattr(module, '__version__'):
478 version = module.__version__
482 version = module.__version__
479 else:
483 else:
480 version = ''
484 version = ''
481 if isinstance(version, (list, tuple)):
485 if isinstance(version, (list, tuple)):
482 version = '.'.join(str(o) for o in version)
486 version = '.'.join(str(o) for o in version)
483 return version
487 return version
484
488
485 def ismoduleinternal(module):
489 def ismoduleinternal(module):
486 exttestedwith = getattr(module, 'testedwith', None)
490 exttestedwith = getattr(module, 'testedwith', None)
487 return exttestedwith == "internal"
491 return exttestedwith == "internal"
@@ -1,677 +1,677 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 getattr(sys, 'version_info', (0, 0, 0)) < (2, 6, 0, 'final'):
8 if getattr(sys, 'version_info', (0, 0, 0)) < (2, 6, 0, 'final'):
9 raise SystemExit("Mercurial requires Python 2.6 or later.")
9 raise SystemExit("Mercurial requires Python 2.6 or later.")
10
10
11 if sys.version_info[0] >= 3:
11 if sys.version_info[0] >= 3:
12 printf = eval('print')
12 printf = eval('print')
13 libdir_escape = 'unicode_escape'
13 libdir_escape = 'unicode_escape'
14 else:
14 else:
15 libdir_escape = 'string_escape'
15 libdir_escape = 'string_escape'
16 def printf(*args, **kwargs):
16 def printf(*args, **kwargs):
17 f = kwargs.get('file', sys.stdout)
17 f = kwargs.get('file', sys.stdout)
18 end = kwargs.get('end', '\n')
18 end = kwargs.get('end', '\n')
19 f.write(b' '.join(args) + end)
19 f.write(b' '.join(args) + end)
20
20
21 # Solaris Python packaging brain damage
21 # Solaris Python packaging brain damage
22 try:
22 try:
23 import hashlib
23 import hashlib
24 sha = hashlib.sha1()
24 sha = hashlib.sha1()
25 except ImportError:
25 except ImportError:
26 try:
26 try:
27 import sha
27 import sha
28 sha.sha # silence unused import warning
28 sha.sha # silence unused import warning
29 except ImportError:
29 except ImportError:
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 zlib.compressobj # silence unused import warning
35 zlib.compressobj # silence unused import warning
36 except ImportError:
36 except ImportError:
37 raise SystemExit(
37 raise SystemExit(
38 "Couldn't import standard zlib (incomplete Python install).")
38 "Couldn't import standard zlib (incomplete Python install).")
39
39
40 # The base IronPython distribution (as of 2.7.1) doesn't support bz2
40 # The base IronPython distribution (as of 2.7.1) doesn't support bz2
41 isironpython = False
41 isironpython = False
42 try:
42 try:
43 isironpython = (platform.python_implementation()
43 isironpython = (platform.python_implementation()
44 .lower().find("ironpython") != -1)
44 .lower().find("ironpython") != -1)
45 except AttributeError:
45 except AttributeError:
46 pass
46 pass
47
47
48 if isironpython:
48 if isironpython:
49 sys.stderr.write("warning: IronPython detected (no bz2 support)\n")
49 sys.stderr.write("warning: IronPython detected (no bz2 support)\n")
50 else:
50 else:
51 try:
51 try:
52 import bz2
52 import bz2
53 bz2.BZ2Compressor # silence unused import warning
53 bz2.BZ2Compressor # silence unused import warning
54 except ImportError:
54 except ImportError:
55 raise SystemExit(
55 raise SystemExit(
56 "Couldn't import standard bz2 (incomplete Python install).")
56 "Couldn't import standard bz2 (incomplete Python install).")
57
57
58 ispypy = "PyPy" in sys.version
58 ispypy = "PyPy" in sys.version
59
59
60 import os, stat, subprocess, time
60 import os, stat, subprocess, time
61 import re
61 import re
62 import shutil
62 import shutil
63 import tempfile
63 import tempfile
64 from distutils import log
64 from distutils import log
65 if 'FORCE_SETUPTOOLS' in os.environ:
65 if 'FORCE_SETUPTOOLS' in os.environ:
66 from setuptools import setup
66 from setuptools import setup
67 else:
67 else:
68 from distutils.core import setup
68 from distutils.core import setup
69 from distutils.core import Command, Extension
69 from distutils.core import Command, Extension
70 from distutils.dist import Distribution
70 from distutils.dist import Distribution
71 from distutils.command.build import build
71 from distutils.command.build import build
72 from distutils.command.build_ext import build_ext
72 from distutils.command.build_ext import build_ext
73 from distutils.command.build_py import build_py
73 from distutils.command.build_py import build_py
74 from distutils.command.build_scripts import build_scripts
74 from distutils.command.build_scripts import build_scripts
75 from distutils.command.install_lib import install_lib
75 from distutils.command.install_lib import install_lib
76 from distutils.command.install_scripts import install_scripts
76 from distutils.command.install_scripts import install_scripts
77 from distutils.spawn import spawn, find_executable
77 from distutils.spawn import spawn, find_executable
78 from distutils import file_util
78 from distutils import file_util
79 from distutils.errors import (
79 from distutils.errors import (
80 CCompilerError,
80 CCompilerError,
81 DistutilsError,
81 DistutilsError,
82 DistutilsExecError,
82 DistutilsExecError,
83 )
83 )
84 from distutils.sysconfig import get_python_inc, get_config_var
84 from distutils.sysconfig import get_python_inc, get_config_var
85 from distutils.version import StrictVersion
85 from distutils.version import StrictVersion
86
86
87 scripts = ['hg']
87 scripts = ['hg']
88 if os.name == 'nt':
88 if os.name == 'nt':
89 # We remove hg.bat if we are able to build hg.exe.
89 # We remove hg.bat if we are able to build hg.exe.
90 scripts.append('contrib/win32/hg.bat')
90 scripts.append('contrib/win32/hg.bat')
91
91
92 # simplified version of distutils.ccompiler.CCompiler.has_function
92 # simplified version of distutils.ccompiler.CCompiler.has_function
93 # that actually removes its temporary files.
93 # that actually removes its temporary files.
94 def hasfunction(cc, funcname):
94 def hasfunction(cc, funcname):
95 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
95 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
96 devnull = oldstderr = None
96 devnull = oldstderr = None
97 try:
97 try:
98 fname = os.path.join(tmpdir, 'funcname.c')
98 fname = os.path.join(tmpdir, 'funcname.c')
99 f = open(fname, 'w')
99 f = open(fname, 'w')
100 f.write('int main(void) {\n')
100 f.write('int main(void) {\n')
101 f.write(' %s();\n' % funcname)
101 f.write(' %s();\n' % funcname)
102 f.write('}\n')
102 f.write('}\n')
103 f.close()
103 f.close()
104 # Redirect stderr to /dev/null to hide any error messages
104 # Redirect stderr to /dev/null to hide any error messages
105 # from the compiler.
105 # from the compiler.
106 # This will have to be changed if we ever have to check
106 # This will have to be changed if we ever have to check
107 # for a function on Windows.
107 # for a function on Windows.
108 devnull = open('/dev/null', 'w')
108 devnull = open('/dev/null', 'w')
109 oldstderr = os.dup(sys.stderr.fileno())
109 oldstderr = os.dup(sys.stderr.fileno())
110 os.dup2(devnull.fileno(), sys.stderr.fileno())
110 os.dup2(devnull.fileno(), sys.stderr.fileno())
111 objects = cc.compile([fname], output_dir=tmpdir)
111 objects = cc.compile([fname], output_dir=tmpdir)
112 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
112 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
113 return True
113 return True
114 except Exception:
114 except Exception:
115 return False
115 return False
116 finally:
116 finally:
117 if oldstderr is not None:
117 if oldstderr is not None:
118 os.dup2(oldstderr, sys.stderr.fileno())
118 os.dup2(oldstderr, sys.stderr.fileno())
119 if devnull is not None:
119 if devnull is not None:
120 devnull.close()
120 devnull.close()
121 shutil.rmtree(tmpdir)
121 shutil.rmtree(tmpdir)
122
122
123 # py2exe needs to be installed to work
123 # py2exe needs to be installed to work
124 try:
124 try:
125 import py2exe
125 import py2exe
126 py2exe.Distribution # silence unused import warning
126 py2exe.Distribution # silence unused import warning
127 py2exeloaded = True
127 py2exeloaded = True
128 # import py2exe's patched Distribution class
128 # import py2exe's patched Distribution class
129 from distutils.core import Distribution
129 from distutils.core import Distribution
130 except ImportError:
130 except ImportError:
131 py2exeloaded = False
131 py2exeloaded = False
132
132
133 def runcmd(cmd, env):
133 def runcmd(cmd, env):
134 if (sys.platform == 'plan9'
134 if (sys.platform == 'plan9'
135 and (sys.version_info[0] == 2 and sys.version_info[1] < 7)):
135 and (sys.version_info[0] == 2 and sys.version_info[1] < 7)):
136 # subprocess kludge to work around issues in half-baked Python
136 # subprocess kludge to work around issues in half-baked Python
137 # ports, notably bichued/python:
137 # ports, notably bichued/python:
138 _, out, err = os.popen3(cmd)
138 _, out, err = os.popen3(cmd)
139 return str(out), str(err)
139 return str(out), str(err)
140 else:
140 else:
141 p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
141 p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
142 stderr=subprocess.PIPE, env=env)
142 stderr=subprocess.PIPE, env=env)
143 out, err = p.communicate()
143 out, err = p.communicate()
144 return out, err
144 return out, err
145
145
146 def runhg(cmd, env):
146 def runhg(cmd, env):
147 out, err = runcmd(cmd, env)
147 out, err = runcmd(cmd, env)
148 # If root is executing setup.py, but the repository is owned by
148 # If root is executing setup.py, but the repository is owned by
149 # another user (as in "sudo python setup.py install") we will get
149 # another user (as in "sudo python setup.py install") we will get
150 # trust warnings since the .hg/hgrc file is untrusted. That is
150 # trust warnings since the .hg/hgrc file is untrusted. That is
151 # fine, we don't want to load it anyway. Python may warn about
151 # fine, we don't want to load it anyway. Python may warn about
152 # a missing __init__.py in mercurial/locale, we also ignore that.
152 # a missing __init__.py in mercurial/locale, we also ignore that.
153 err = [e for e in err.splitlines()
153 err = [e for e in err.splitlines()
154 if not e.startswith(b'not trusting file') \
154 if not e.startswith(b'not trusting file') \
155 and not e.startswith(b'warning: Not importing') \
155 and not e.startswith(b'warning: Not importing') \
156 and not e.startswith(b'obsolete feature not enabled')]
156 and not e.startswith(b'obsolete feature not enabled')]
157 if err:
157 if err:
158 printf("stderr from '%s':" % (' '.join(cmd)), file=sys.stderr)
158 printf("stderr from '%s':" % (' '.join(cmd)), file=sys.stderr)
159 printf(b'\n'.join([b' ' + e for e in err]), file=sys.stderr)
159 printf(b'\n'.join([b' ' + e for e in err]), file=sys.stderr)
160 return ''
160 return ''
161 return out
161 return out
162
162
163 version = ''
163 version = ''
164
164
165 # Execute hg out of this directory with a custom environment which takes care
165 # Execute hg out of this directory with a custom environment which takes care
166 # to not use any hgrc files and do no localization.
166 # to not use any hgrc files and do no localization.
167 env = {'HGMODULEPOLICY': 'py',
167 env = {'HGMODULEPOLICY': 'py',
168 'HGRCPATH': '',
168 'HGRCPATH': '',
169 'LANGUAGE': 'C'}
169 'LANGUAGE': 'C'}
170 if 'LD_LIBRARY_PATH' in os.environ:
170 if 'LD_LIBRARY_PATH' in os.environ:
171 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
171 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
172 if 'SystemRoot' in os.environ:
172 if 'SystemRoot' in os.environ:
173 # Copy SystemRoot into the custom environment for Python 2.6
173 # Copy SystemRoot into the custom environment for Python 2.6
174 # under Windows. Otherwise, the subprocess will fail with
174 # under Windows. Otherwise, the subprocess will fail with
175 # error 0xc0150004. See: http://bugs.python.org/issue3440
175 # error 0xc0150004. See: http://bugs.python.org/issue3440
176 env['SystemRoot'] = os.environ['SystemRoot']
176 env['SystemRoot'] = os.environ['SystemRoot']
177
177
178 if os.path.isdir('.hg'):
178 if os.path.isdir('.hg'):
179 cmd = [sys.executable, 'hg', 'log', '-r', '.', '--template', '{tags}\n']
179 cmd = [sys.executable, 'hg', 'log', '-r', '.', '--template', '{tags}\n']
180 numerictags = [t for t in runhg(cmd, env).split() if t[0].isdigit()]
180 numerictags = [t for t in runhg(cmd, env).split() if t[0].isdigit()]
181 hgid = runhg([sys.executable, 'hg', 'id', '-i'], env).strip()
181 hgid = runhg([sys.executable, 'hg', 'id', '-i'], env).strip()
182 if numerictags: # tag(s) found
182 if numerictags: # tag(s) found
183 version = numerictags[-1]
183 version = numerictags[-1]
184 if hgid.endswith('+'): # propagate the dirty status to the tag
184 if hgid.endswith('+'): # propagate the dirty status to the tag
185 version += '+'
185 version += '+'
186 else: # no tag found
186 else: # no tag found
187 ltagcmd = [sys.executable, 'hg', 'parents', '--template',
187 ltagcmd = [sys.executable, 'hg', 'parents', '--template',
188 '{latesttag}']
188 '{latesttag}']
189 ltag = runhg(ltagcmd, env)
189 ltag = runhg(ltagcmd, env)
190 changessincecmd = [sys.executable, 'hg', 'log', '-T', 'x\n', '-r',
190 changessincecmd = [sys.executable, 'hg', 'log', '-T', 'x\n', '-r',
191 "only(.,'%s')" % ltag]
191 "only(.,'%s')" % ltag]
192 changessince = len(runhg(changessincecmd, env).splitlines())
192 changessince = len(runhg(changessincecmd, env).splitlines())
193 version = '%s+%s-%s' % (ltag, changessince, hgid)
193 version = '%s+%s-%s' % (ltag, changessince, hgid)
194 if version.endswith('+'):
194 if version.endswith('+'):
195 version += time.strftime('%Y%m%d')
195 version += time.strftime('%Y%m%d')
196 elif os.path.exists('.hg_archival.txt'):
196 elif os.path.exists('.hg_archival.txt'):
197 kw = dict([[t.strip() for t in l.split(':', 1)]
197 kw = dict([[t.strip() for t in l.split(':', 1)]
198 for l in open('.hg_archival.txt')])
198 for l in open('.hg_archival.txt')])
199 if 'tag' in kw:
199 if 'tag' in kw:
200 version = kw['tag']
200 version = kw['tag']
201 elif 'latesttag' in kw:
201 elif 'latesttag' in kw:
202 if 'changessincelatesttag' in kw:
202 if 'changessincelatesttag' in kw:
203 version = '%(latesttag)s+%(changessincelatesttag)s-%(node).12s' % kw
203 version = '%(latesttag)s+%(changessincelatesttag)s-%(node).12s' % kw
204 else:
204 else:
205 version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
205 version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
206 else:
206 else:
207 version = kw.get('node', '')[:12]
207 version = kw.get('node', '')[:12]
208
208
209 if version:
209 if version:
210 with open("mercurial/__version__.py", "w") as f:
210 with open("mercurial/__version__.py", "w") as f:
211 f.write('# this file is autogenerated by setup.py\n')
211 f.write('# this file is autogenerated by setup.py\n')
212 f.write('version = "%s"\n' % version)
212 f.write('version = "%s"\n' % version)
213
213
214 try:
214 try:
215 oldpolicy = os.environ.get('HGMODULEPOLICY', None)
215 oldpolicy = os.environ.get('HGMODULEPOLICY', None)
216 os.environ['HGMODULEPOLICY'] = 'py'
216 os.environ['HGMODULEPOLICY'] = 'py'
217 from mercurial import __version__
217 from mercurial import __version__
218 version = __version__.version
218 version = __version__.version
219 except ImportError:
219 except ImportError:
220 version = 'unknown'
220 version = 'unknown'
221 finally:
221 finally:
222 if oldpolicy is None:
222 if oldpolicy is None:
223 del os.environ['HGMODULEPOLICY']
223 del os.environ['HGMODULEPOLICY']
224 else:
224 else:
225 os.environ['HGMODULEPOLICY'] = oldpolicy
225 os.environ['HGMODULEPOLICY'] = oldpolicy
226
226
227 class hgbuild(build):
227 class hgbuild(build):
228 # Insert hgbuildmo first so that files in mercurial/locale/ are found
228 # Insert hgbuildmo first so that files in mercurial/locale/ are found
229 # when build_py is run next.
229 # when build_py is run next.
230 sub_commands = [('build_mo', None)] + build.sub_commands
230 sub_commands = [('build_mo', None)] + build.sub_commands
231
231
232 class hgbuildmo(build):
232 class hgbuildmo(build):
233
233
234 description = "build translations (.mo files)"
234 description = "build translations (.mo files)"
235
235
236 def run(self):
236 def run(self):
237 if not find_executable('msgfmt'):
237 if not find_executable('msgfmt'):
238 self.warn("could not find msgfmt executable, no translations "
238 self.warn("could not find msgfmt executable, no translations "
239 "will be built")
239 "will be built")
240 return
240 return
241
241
242 podir = 'i18n'
242 podir = 'i18n'
243 if not os.path.isdir(podir):
243 if not os.path.isdir(podir):
244 self.warn("could not find %s/ directory" % podir)
244 self.warn("could not find %s/ directory" % podir)
245 return
245 return
246
246
247 join = os.path.join
247 join = os.path.join
248 for po in os.listdir(podir):
248 for po in os.listdir(podir):
249 if not po.endswith('.po'):
249 if not po.endswith('.po'):
250 continue
250 continue
251 pofile = join(podir, po)
251 pofile = join(podir, po)
252 modir = join('locale', po[:-3], 'LC_MESSAGES')
252 modir = join('locale', po[:-3], 'LC_MESSAGES')
253 mofile = join(modir, 'hg.mo')
253 mofile = join(modir, 'hg.mo')
254 mobuildfile = join('mercurial', mofile)
254 mobuildfile = join('mercurial', mofile)
255 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
255 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
256 if sys.platform != 'sunos5':
256 if sys.platform != 'sunos5':
257 # msgfmt on Solaris does not know about -c
257 # msgfmt on Solaris does not know about -c
258 cmd.append('-c')
258 cmd.append('-c')
259 self.mkpath(join('mercurial', modir))
259 self.mkpath(join('mercurial', modir))
260 self.make_file([pofile], mobuildfile, spawn, (cmd,))
260 self.make_file([pofile], mobuildfile, spawn, (cmd,))
261
261
262
262
263 class hgdist(Distribution):
263 class hgdist(Distribution):
264 pure = ispypy
264 pure = ispypy
265
265
266 global_options = Distribution.global_options + \
266 global_options = Distribution.global_options + \
267 [('pure', None, "use pure (slow) Python "
267 [('pure', None, "use pure (slow) Python "
268 "code instead of C extensions"),
268 "code instead of C extensions"),
269 ]
269 ]
270
270
271 def has_ext_modules(self):
271 def has_ext_modules(self):
272 # self.ext_modules is emptied in hgbuildpy.finalize_options which is
272 # self.ext_modules is emptied in hgbuildpy.finalize_options which is
273 # too late for some cases
273 # too late for some cases
274 return not self.pure and Distribution.has_ext_modules(self)
274 return not self.pure and Distribution.has_ext_modules(self)
275
275
276 class hgbuildext(build_ext):
276 class hgbuildext(build_ext):
277
277
278 def build_extension(self, ext):
278 def build_extension(self, ext):
279 try:
279 try:
280 build_ext.build_extension(self, ext)
280 build_ext.build_extension(self, ext)
281 except CCompilerError:
281 except CCompilerError:
282 if not getattr(ext, 'optional', False):
282 if not getattr(ext, 'optional', False):
283 raise
283 raise
284 log.warn("Failed to build optional extension '%s' (skipping)",
284 log.warn("Failed to build optional extension '%s' (skipping)",
285 ext.name)
285 ext.name)
286
286
287 class hgbuildscripts(build_scripts):
287 class hgbuildscripts(build_scripts):
288 def run(self):
288 def run(self):
289 if os.name != 'nt' or self.distribution.pure:
289 if os.name != 'nt' or self.distribution.pure:
290 return build_scripts.run(self)
290 return build_scripts.run(self)
291
291
292 exebuilt = False
292 exebuilt = False
293 try:
293 try:
294 self.run_command('build_hgexe')
294 self.run_command('build_hgexe')
295 exebuilt = True
295 exebuilt = True
296 except (DistutilsError, CCompilerError):
296 except (DistutilsError, CCompilerError):
297 log.warn('failed to build optional hg.exe')
297 log.warn('failed to build optional hg.exe')
298
298
299 if exebuilt:
299 if exebuilt:
300 # Copying hg.exe to the scripts build directory ensures it is
300 # Copying hg.exe to the scripts build directory ensures it is
301 # installed by the install_scripts command.
301 # installed by the install_scripts command.
302 hgexecommand = self.get_finalized_command('build_hgexe')
302 hgexecommand = self.get_finalized_command('build_hgexe')
303 dest = os.path.join(self.build_dir, 'hg.exe')
303 dest = os.path.join(self.build_dir, 'hg.exe')
304 self.mkpath(self.build_dir)
304 self.mkpath(self.build_dir)
305 self.copy_file(hgexecommand.hgexepath, dest)
305 self.copy_file(hgexecommand.hgexepath, dest)
306
306
307 # Remove hg.bat because it is redundant with hg.exe.
307 # Remove hg.bat because it is redundant with hg.exe.
308 self.scripts.remove('contrib/win32/hg.bat')
308 self.scripts.remove('contrib/win32/hg.bat')
309
309
310 return build_scripts.run(self)
310 return build_scripts.run(self)
311
311
312 class hgbuildpy(build_py):
312 class hgbuildpy(build_py):
313 def finalize_options(self):
313 def finalize_options(self):
314 build_py.finalize_options(self)
314 build_py.finalize_options(self)
315
315
316 if self.distribution.pure:
316 if self.distribution.pure:
317 self.distribution.ext_modules = []
317 self.distribution.ext_modules = []
318 else:
318 else:
319 h = os.path.join(get_python_inc(), 'Python.h')
319 h = os.path.join(get_python_inc(), 'Python.h')
320 if not os.path.exists(h):
320 if not os.path.exists(h):
321 raise SystemExit('Python headers are required to build '
321 raise SystemExit('Python headers are required to build '
322 'Mercurial but weren\'t found in %s' % h)
322 'Mercurial but weren\'t found in %s' % h)
323
323
324 def run(self):
324 def run(self):
325 if self.distribution.pure:
325 if self.distribution.pure:
326 modulepolicy = 'py'
326 modulepolicy = 'py'
327 else:
327 else:
328 modulepolicy = 'c'
328 modulepolicy = 'c'
329 with open("mercurial/__modulepolicy__.py", "w") as f:
329 with open("mercurial/__modulepolicy__.py", "w") as f:
330 f.write('# this file is autogenerated by setup.py\n')
330 f.write('# this file is autogenerated by setup.py\n')
331 f.write('modulepolicy = "%s"\n' % modulepolicy)
331 f.write('modulepolicy = "%s"\n' % modulepolicy)
332
332
333 build_py.run(self)
333 build_py.run(self)
334
334
335 class buildhgextindex(Command):
335 class buildhgextindex(Command):
336 description = 'generate prebuilt index of hgext (for frozen package)'
336 description = 'generate prebuilt index of hgext (for frozen package)'
337 user_options = []
337 user_options = []
338 _indexfilename = 'hgext/__index__.py'
338 _indexfilename = 'hgext/__index__.py'
339
339
340 def initialize_options(self):
340 def initialize_options(self):
341 pass
341 pass
342
342
343 def finalize_options(self):
343 def finalize_options(self):
344 pass
344 pass
345
345
346 def run(self):
346 def run(self):
347 if os.path.exists(self._indexfilename):
347 if os.path.exists(self._indexfilename):
348 with open(self._indexfilename, 'w') as f:
348 with open(self._indexfilename, 'w') as f:
349 f.write('# empty\n')
349 f.write('# empty\n')
350
350
351 # here no extension enabled, disabled() lists up everything
351 # here no extension enabled, disabled() lists up everything
352 code = ('import pprint; from mercurial import extensions; '
352 code = ('import pprint; from mercurial import extensions; '
353 'pprint.pprint(extensions.disabled())')
353 'pprint.pprint(extensions.disabled())')
354 out, err = runcmd([sys.executable, '-c', code], env)
354 out, err = runcmd([sys.executable, '-c', code], env)
355 if err:
355 if err:
356 raise DistutilsExecError(err)
356 raise DistutilsExecError(err)
357
357
358 with open(self._indexfilename, 'w') as f:
358 with open(self._indexfilename, 'w') as f:
359 f.write('# this file is autogenerated by setup.py\n')
359 f.write('# this file is autogenerated by setup.py\n')
360 f.write('docs = ')
360 f.write('docs = ')
361 f.write(out)
361 f.write(out)
362
362
363 class buildhgexe(build_ext):
363 class buildhgexe(build_ext):
364 description = 'compile hg.exe from mercurial/exewrapper.c'
364 description = 'compile hg.exe from mercurial/exewrapper.c'
365
365
366 def build_extensions(self):
366 def build_extensions(self):
367 if os.name != 'nt':
367 if os.name != 'nt':
368 return
368 return
369 if isinstance(self.compiler, HackedMingw32CCompiler):
369 if isinstance(self.compiler, HackedMingw32CCompiler):
370 self.compiler.compiler_so = self.compiler.compiler # no -mdll
370 self.compiler.compiler_so = self.compiler.compiler # no -mdll
371 self.compiler.dll_libraries = [] # no -lmsrvc90
371 self.compiler.dll_libraries = [] # no -lmsrvc90
372 hv = sys.hexversion
372 hv = sys.hexversion
373 pythonlib = 'python%d%d' % (hv >> 24, (hv >> 16) & 0xff)
373 pythonlib = 'python%d%d' % (hv >> 24, (hv >> 16) & 0xff)
374 with open('mercurial/hgpythonlib.h', 'wb') as f:
374 with open('mercurial/hgpythonlib.h', 'wb') as f:
375 f.write('/* this file is autogenerated by setup.py */\n')
375 f.write('/* this file is autogenerated by setup.py */\n')
376 f.write('#define HGPYTHONLIB "%s"\n' % pythonlib)
376 f.write('#define HGPYTHONLIB "%s"\n' % pythonlib)
377 objects = self.compiler.compile(['mercurial/exewrapper.c'],
377 objects = self.compiler.compile(['mercurial/exewrapper.c'],
378 output_dir=self.build_temp)
378 output_dir=self.build_temp)
379 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
379 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
380 target = os.path.join(dir, 'hg')
380 target = os.path.join(dir, 'hg')
381 self.compiler.link_executable(objects, target,
381 self.compiler.link_executable(objects, target,
382 libraries=[],
382 libraries=[],
383 output_dir=self.build_temp)
383 output_dir=self.build_temp)
384
384
385 @property
385 @property
386 def hgexepath(self):
386 def hgexepath(self):
387 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
387 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
388 return os.path.join(self.build_temp, dir, 'hg.exe')
388 return os.path.join(self.build_temp, dir, 'hg.exe')
389
389
390 class hginstalllib(install_lib):
390 class hginstalllib(install_lib):
391 '''
391 '''
392 This is a specialization of install_lib that replaces the copy_file used
392 This is a specialization of install_lib that replaces the copy_file used
393 there so that it supports setting the mode of files after copying them,
393 there so that it supports setting the mode of files after copying them,
394 instead of just preserving the mode that the files originally had. If your
394 instead of just preserving the mode that the files originally had. If your
395 system has a umask of something like 027, preserving the permissions when
395 system has a umask of something like 027, preserving the permissions when
396 copying will lead to a broken install.
396 copying will lead to a broken install.
397
397
398 Note that just passing keep_permissions=False to copy_file would be
398 Note that just passing keep_permissions=False to copy_file would be
399 insufficient, as it might still be applying a umask.
399 insufficient, as it might still be applying a umask.
400 '''
400 '''
401
401
402 def run(self):
402 def run(self):
403 realcopyfile = file_util.copy_file
403 realcopyfile = file_util.copy_file
404 def copyfileandsetmode(*args, **kwargs):
404 def copyfileandsetmode(*args, **kwargs):
405 src, dst = args[0], args[1]
405 src, dst = args[0], args[1]
406 dst, copied = realcopyfile(*args, **kwargs)
406 dst, copied = realcopyfile(*args, **kwargs)
407 if copied:
407 if copied:
408 st = os.stat(src)
408 st = os.stat(src)
409 # Persist executable bit (apply it to group and other if user
409 # Persist executable bit (apply it to group and other if user
410 # has it)
410 # has it)
411 if st[stat.ST_MODE] & stat.S_IXUSR:
411 if st[stat.ST_MODE] & stat.S_IXUSR:
412 setmode = int('0755', 8)
412 setmode = int('0755', 8)
413 else:
413 else:
414 setmode = int('0644', 8)
414 setmode = int('0644', 8)
415 m = stat.S_IMODE(st[stat.ST_MODE])
415 m = stat.S_IMODE(st[stat.ST_MODE])
416 m = (m & ~int('0777', 8)) | setmode
416 m = (m & ~int('0777', 8)) | setmode
417 os.chmod(dst, m)
417 os.chmod(dst, m)
418 file_util.copy_file = copyfileandsetmode
418 file_util.copy_file = copyfileandsetmode
419 try:
419 try:
420 install_lib.run(self)
420 install_lib.run(self)
421 finally:
421 finally:
422 file_util.copy_file = realcopyfile
422 file_util.copy_file = realcopyfile
423
423
424 class hginstallscripts(install_scripts):
424 class hginstallscripts(install_scripts):
425 '''
425 '''
426 This is a specialization of install_scripts that replaces the @LIBDIR@ with
426 This is a specialization of install_scripts that replaces the @LIBDIR@ with
427 the configured directory for modules. If possible, the path is made relative
427 the configured directory for modules. If possible, the path is made relative
428 to the directory for scripts.
428 to the directory for scripts.
429 '''
429 '''
430
430
431 def initialize_options(self):
431 def initialize_options(self):
432 install_scripts.initialize_options(self)
432 install_scripts.initialize_options(self)
433
433
434 self.install_lib = None
434 self.install_lib = None
435
435
436 def finalize_options(self):
436 def finalize_options(self):
437 install_scripts.finalize_options(self)
437 install_scripts.finalize_options(self)
438 self.set_undefined_options('install',
438 self.set_undefined_options('install',
439 ('install_lib', 'install_lib'))
439 ('install_lib', 'install_lib'))
440
440
441 def run(self):
441 def run(self):
442 install_scripts.run(self)
442 install_scripts.run(self)
443
443
444 # It only makes sense to replace @LIBDIR@ with the install path if
444 # It only makes sense to replace @LIBDIR@ with the install path if
445 # the install path is known. For wheels, the logic below calculates
445 # the install path is known. For wheels, the logic below calculates
446 # the libdir to be "../..". This is because the internal layout of a
446 # the libdir to be "../..". This is because the internal layout of a
447 # wheel archive looks like:
447 # wheel archive looks like:
448 #
448 #
449 # mercurial-3.6.1.data/scripts/hg
449 # mercurial-3.6.1.data/scripts/hg
450 # mercurial/__init__.py
450 # mercurial/__init__.py
451 #
451 #
452 # When installing wheels, the subdirectories of the "<pkg>.data"
452 # When installing wheels, the subdirectories of the "<pkg>.data"
453 # directory are translated to system local paths and files therein
453 # directory are translated to system local paths and files therein
454 # are copied in place. The mercurial/* files are installed into the
454 # are copied in place. The mercurial/* files are installed into the
455 # site-packages directory. However, the site-packages directory
455 # site-packages directory. However, the site-packages directory
456 # isn't known until wheel install time. This means we have no clue
456 # isn't known until wheel install time. This means we have no clue
457 # at wheel generation time what the installed site-packages directory
457 # at wheel generation time what the installed site-packages directory
458 # will be. And, wheels don't appear to provide the ability to register
458 # will be. And, wheels don't appear to provide the ability to register
459 # custom code to run during wheel installation. This all means that
459 # custom code to run during wheel installation. This all means that
460 # we can't reliably set the libdir in wheels: the default behavior
460 # we can't reliably set the libdir in wheels: the default behavior
461 # of looking in sys.path must do.
461 # of looking in sys.path must do.
462
462
463 if (os.path.splitdrive(self.install_dir)[0] !=
463 if (os.path.splitdrive(self.install_dir)[0] !=
464 os.path.splitdrive(self.install_lib)[0]):
464 os.path.splitdrive(self.install_lib)[0]):
465 # can't make relative paths from one drive to another, so use an
465 # can't make relative paths from one drive to another, so use an
466 # absolute path instead
466 # absolute path instead
467 libdir = self.install_lib
467 libdir = self.install_lib
468 else:
468 else:
469 common = os.path.commonprefix((self.install_dir, self.install_lib))
469 common = os.path.commonprefix((self.install_dir, self.install_lib))
470 rest = self.install_dir[len(common):]
470 rest = self.install_dir[len(common):]
471 uplevel = len([n for n in os.path.split(rest) if n])
471 uplevel = len([n for n in os.path.split(rest) if n])
472
472
473 libdir = uplevel * ('..' + os.sep) + self.install_lib[len(common):]
473 libdir = uplevel * ('..' + os.sep) + self.install_lib[len(common):]
474
474
475 for outfile in self.outfiles:
475 for outfile in self.outfiles:
476 with open(outfile, 'rb') as fp:
476 with open(outfile, 'rb') as fp:
477 data = fp.read()
477 data = fp.read()
478
478
479 # skip binary files
479 # skip binary files
480 if b'\0' in data:
480 if b'\0' in data:
481 continue
481 continue
482
482
483 # During local installs, the shebang will be rewritten to the final
483 # During local installs, the shebang will be rewritten to the final
484 # install path. During wheel packaging, the shebang has a special
484 # install path. During wheel packaging, the shebang has a special
485 # value.
485 # value.
486 if data.startswith(b'#!python'):
486 if data.startswith(b'#!python'):
487 log.info('not rewriting @LIBDIR@ in %s because install path '
487 log.info('not rewriting @LIBDIR@ in %s because install path '
488 'not known' % outfile)
488 'not known' % outfile)
489 continue
489 continue
490
490
491 data = data.replace(b'@LIBDIR@', libdir.encode(libdir_escape))
491 data = data.replace(b'@LIBDIR@', libdir.encode(libdir_escape))
492 with open(outfile, 'wb') as fp:
492 with open(outfile, 'wb') as fp:
493 fp.write(data)
493 fp.write(data)
494
494
495 cmdclass = {'build': hgbuild,
495 cmdclass = {'build': hgbuild,
496 'build_mo': hgbuildmo,
496 'build_mo': hgbuildmo,
497 'build_ext': hgbuildext,
497 'build_ext': hgbuildext,
498 'build_py': hgbuildpy,
498 'build_py': hgbuildpy,
499 'build_scripts': hgbuildscripts,
499 'build_scripts': hgbuildscripts,
500 'build_hgextindex': buildhgextindex,
500 'build_hgextindex': buildhgextindex,
501 'install_lib': hginstalllib,
501 'install_lib': hginstalllib,
502 'install_scripts': hginstallscripts,
502 'install_scripts': hginstallscripts,
503 'build_hgexe': buildhgexe,
503 'build_hgexe': buildhgexe,
504 }
504 }
505
505
506 packages = ['mercurial', 'mercurial.hgweb', 'mercurial.httpclient',
506 packages = ['mercurial', 'mercurial.hgweb', 'mercurial.httpclient',
507 'mercurial.pure',
507 'mercurial.pure',
508 'hgext', 'hgext.convert', 'hgext.highlight', 'hgext.zeroconf',
508 'hgext', 'hgext.convert', 'hgext.highlight', 'hgext.zeroconf',
509 'hgext.largefiles']
509 'hgext.largefiles', 'hgext3rd']
510
510
511 common_depends = ['mercurial/util.h']
511 common_depends = ['mercurial/util.h']
512
512
513 osutil_ldflags = []
513 osutil_ldflags = []
514
514
515 if sys.platform == 'darwin':
515 if sys.platform == 'darwin':
516 osutil_ldflags += ['-framework', 'ApplicationServices']
516 osutil_ldflags += ['-framework', 'ApplicationServices']
517
517
518 extmodules = [
518 extmodules = [
519 Extension('mercurial.base85', ['mercurial/base85.c'],
519 Extension('mercurial.base85', ['mercurial/base85.c'],
520 depends=common_depends),
520 depends=common_depends),
521 Extension('mercurial.bdiff', ['mercurial/bdiff.c'],
521 Extension('mercurial.bdiff', ['mercurial/bdiff.c'],
522 depends=common_depends),
522 depends=common_depends),
523 Extension('mercurial.diffhelpers', ['mercurial/diffhelpers.c'],
523 Extension('mercurial.diffhelpers', ['mercurial/diffhelpers.c'],
524 depends=common_depends),
524 depends=common_depends),
525 Extension('mercurial.mpatch', ['mercurial/mpatch.c'],
525 Extension('mercurial.mpatch', ['mercurial/mpatch.c'],
526 depends=common_depends),
526 depends=common_depends),
527 Extension('mercurial.parsers', ['mercurial/dirs.c',
527 Extension('mercurial.parsers', ['mercurial/dirs.c',
528 'mercurial/manifest.c',
528 'mercurial/manifest.c',
529 'mercurial/parsers.c',
529 'mercurial/parsers.c',
530 'mercurial/pathencode.c'],
530 'mercurial/pathencode.c'],
531 depends=common_depends),
531 depends=common_depends),
532 Extension('mercurial.osutil', ['mercurial/osutil.c'],
532 Extension('mercurial.osutil', ['mercurial/osutil.c'],
533 extra_link_args=osutil_ldflags,
533 extra_link_args=osutil_ldflags,
534 depends=common_depends),
534 depends=common_depends),
535 Extension('hgext.fsmonitor.pywatchman.bser',
535 Extension('hgext.fsmonitor.pywatchman.bser',
536 ['hgext/fsmonitor/pywatchman/bser.c']),
536 ['hgext/fsmonitor/pywatchman/bser.c']),
537 ]
537 ]
538
538
539 try:
539 try:
540 from distutils import cygwinccompiler
540 from distutils import cygwinccompiler
541
541
542 # the -mno-cygwin option has been deprecated for years
542 # the -mno-cygwin option has been deprecated for years
543 compiler = cygwinccompiler.Mingw32CCompiler
543 compiler = cygwinccompiler.Mingw32CCompiler
544
544
545 class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
545 class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
546 def __init__(self, *args, **kwargs):
546 def __init__(self, *args, **kwargs):
547 compiler.__init__(self, *args, **kwargs)
547 compiler.__init__(self, *args, **kwargs)
548 for i in 'compiler compiler_so linker_exe linker_so'.split():
548 for i in 'compiler compiler_so linker_exe linker_so'.split():
549 try:
549 try:
550 getattr(self, i).remove('-mno-cygwin')
550 getattr(self, i).remove('-mno-cygwin')
551 except ValueError:
551 except ValueError:
552 pass
552 pass
553
553
554 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
554 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
555 except ImportError:
555 except ImportError:
556 # the cygwinccompiler package is not available on some Python
556 # the cygwinccompiler package is not available on some Python
557 # distributions like the ones from the optware project for Synology
557 # distributions like the ones from the optware project for Synology
558 # DiskStation boxes
558 # DiskStation boxes
559 class HackedMingw32CCompiler(object):
559 class HackedMingw32CCompiler(object):
560 pass
560 pass
561
561
562 packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo',
562 packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo',
563 'help/*.txt',
563 'help/*.txt',
564 'help/internals/*.txt',
564 'help/internals/*.txt',
565 'default.d/*.rc',
565 'default.d/*.rc',
566 'dummycert.pem']}
566 'dummycert.pem']}
567
567
568 def ordinarypath(p):
568 def ordinarypath(p):
569 return p and p[0] != '.' and p[-1] != '~'
569 return p and p[0] != '.' and p[-1] != '~'
570
570
571 for root in ('templates',):
571 for root in ('templates',):
572 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
572 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
573 curdir = curdir.split(os.sep, 1)[1]
573 curdir = curdir.split(os.sep, 1)[1]
574 dirs[:] = filter(ordinarypath, dirs)
574 dirs[:] = filter(ordinarypath, dirs)
575 for f in filter(ordinarypath, files):
575 for f in filter(ordinarypath, files):
576 f = os.path.join(curdir, f)
576 f = os.path.join(curdir, f)
577 packagedata['mercurial'].append(f)
577 packagedata['mercurial'].append(f)
578
578
579 datafiles = []
579 datafiles = []
580 setupversion = version
580 setupversion = version
581 extra = {}
581 extra = {}
582
582
583 if py2exeloaded:
583 if py2exeloaded:
584 extra['console'] = [
584 extra['console'] = [
585 {'script':'hg',
585 {'script':'hg',
586 'copyright':'Copyright (C) 2005-2016 Matt Mackall and others',
586 'copyright':'Copyright (C) 2005-2016 Matt Mackall and others',
587 'product_version':version}]
587 'product_version':version}]
588 # sub command of 'build' because 'py2exe' does not handle sub_commands
588 # sub command of 'build' because 'py2exe' does not handle sub_commands
589 build.sub_commands.insert(0, ('build_hgextindex', None))
589 build.sub_commands.insert(0, ('build_hgextindex', None))
590 # put dlls in sub directory so that they won't pollute PATH
590 # put dlls in sub directory so that they won't pollute PATH
591 extra['zipfile'] = 'lib/library.zip'
591 extra['zipfile'] = 'lib/library.zip'
592
592
593 if os.name == 'nt':
593 if os.name == 'nt':
594 # Windows binary file versions for exe/dll files must have the
594 # Windows binary file versions for exe/dll files must have the
595 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
595 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
596 setupversion = version.split('+', 1)[0]
596 setupversion = version.split('+', 1)[0]
597
597
598 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
598 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
599 version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[0].splitlines()
599 version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[0].splitlines()
600 if version:
600 if version:
601 version = version[0]
601 version = version[0]
602 if sys.version_info[0] == 3:
602 if sys.version_info[0] == 3:
603 version = version.decode('utf-8')
603 version = version.decode('utf-8')
604 xcode4 = (version.startswith('Xcode') and
604 xcode4 = (version.startswith('Xcode') and
605 StrictVersion(version.split()[1]) >= StrictVersion('4.0'))
605 StrictVersion(version.split()[1]) >= StrictVersion('4.0'))
606 xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None
606 xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None
607 else:
607 else:
608 # xcodebuild returns empty on OS X Lion with XCode 4.3 not
608 # xcodebuild returns empty on OS X Lion with XCode 4.3 not
609 # installed, but instead with only command-line tools. Assume
609 # installed, but instead with only command-line tools. Assume
610 # that only happens on >= Lion, thus no PPC support.
610 # that only happens on >= Lion, thus no PPC support.
611 xcode4 = True
611 xcode4 = True
612 xcode51 = False
612 xcode51 = False
613
613
614 # XCode 4.0 dropped support for ppc architecture, which is hardcoded in
614 # XCode 4.0 dropped support for ppc architecture, which is hardcoded in
615 # distutils.sysconfig
615 # distutils.sysconfig
616 if xcode4:
616 if xcode4:
617 os.environ['ARCHFLAGS'] = ''
617 os.environ['ARCHFLAGS'] = ''
618
618
619 # XCode 5.1 changes clang such that it now fails to compile if the
619 # XCode 5.1 changes clang such that it now fails to compile if the
620 # -mno-fused-madd flag is passed, but the version of Python shipped with
620 # -mno-fused-madd flag is passed, but the version of Python shipped with
621 # OS X 10.9 Mavericks includes this flag. This causes problems in all
621 # OS X 10.9 Mavericks includes this flag. This causes problems in all
622 # C extension modules, and a bug has been filed upstream at
622 # C extension modules, and a bug has been filed upstream at
623 # http://bugs.python.org/issue21244. We also need to patch this here
623 # http://bugs.python.org/issue21244. We also need to patch this here
624 # so Mercurial can continue to compile in the meantime.
624 # so Mercurial can continue to compile in the meantime.
625 if xcode51:
625 if xcode51:
626 cflags = get_config_var('CFLAGS')
626 cflags = get_config_var('CFLAGS')
627 if cflags and re.search(r'-mno-fused-madd\b', cflags) is not None:
627 if cflags and re.search(r'-mno-fused-madd\b', cflags) is not None:
628 os.environ['CFLAGS'] = (
628 os.environ['CFLAGS'] = (
629 os.environ.get('CFLAGS', '') + ' -Qunused-arguments')
629 os.environ.get('CFLAGS', '') + ' -Qunused-arguments')
630
630
631 setup(name='mercurial',
631 setup(name='mercurial',
632 version=setupversion,
632 version=setupversion,
633 author='Matt Mackall and many others',
633 author='Matt Mackall and many others',
634 author_email='mercurial@selenic.com',
634 author_email='mercurial@selenic.com',
635 url='https://mercurial-scm.org/',
635 url='https://mercurial-scm.org/',
636 download_url='https://mercurial-scm.org/release/',
636 download_url='https://mercurial-scm.org/release/',
637 description=('Fast scalable distributed SCM (revision control, version '
637 description=('Fast scalable distributed SCM (revision control, version '
638 'control) system'),
638 'control) system'),
639 long_description=('Mercurial is a distributed SCM tool written in Python.'
639 long_description=('Mercurial is a distributed SCM tool written in Python.'
640 ' It is used by a number of large projects that require'
640 ' It is used by a number of large projects that require'
641 ' fast, reliable distributed revision control, such as '
641 ' fast, reliable distributed revision control, such as '
642 'Mozilla.'),
642 'Mozilla.'),
643 license='GNU GPLv2 or any later version',
643 license='GNU GPLv2 or any later version',
644 classifiers=[
644 classifiers=[
645 'Development Status :: 6 - Mature',
645 'Development Status :: 6 - Mature',
646 'Environment :: Console',
646 'Environment :: Console',
647 'Intended Audience :: Developers',
647 'Intended Audience :: Developers',
648 'Intended Audience :: System Administrators',
648 'Intended Audience :: System Administrators',
649 'License :: OSI Approved :: GNU General Public License (GPL)',
649 'License :: OSI Approved :: GNU General Public License (GPL)',
650 'Natural Language :: Danish',
650 'Natural Language :: Danish',
651 'Natural Language :: English',
651 'Natural Language :: English',
652 'Natural Language :: German',
652 'Natural Language :: German',
653 'Natural Language :: Italian',
653 'Natural Language :: Italian',
654 'Natural Language :: Japanese',
654 'Natural Language :: Japanese',
655 'Natural Language :: Portuguese (Brazilian)',
655 'Natural Language :: Portuguese (Brazilian)',
656 'Operating System :: Microsoft :: Windows',
656 'Operating System :: Microsoft :: Windows',
657 'Operating System :: OS Independent',
657 'Operating System :: OS Independent',
658 'Operating System :: POSIX',
658 'Operating System :: POSIX',
659 'Programming Language :: C',
659 'Programming Language :: C',
660 'Programming Language :: Python',
660 'Programming Language :: Python',
661 'Topic :: Software Development :: Version Control',
661 'Topic :: Software Development :: Version Control',
662 ],
662 ],
663 scripts=scripts,
663 scripts=scripts,
664 packages=packages,
664 packages=packages,
665 ext_modules=extmodules,
665 ext_modules=extmodules,
666 data_files=datafiles,
666 data_files=datafiles,
667 package_data=packagedata,
667 package_data=packagedata,
668 cmdclass=cmdclass,
668 cmdclass=cmdclass,
669 distclass=hgdist,
669 distclass=hgdist,
670 options={'py2exe': {'packages': ['hgext', 'email']},
670 options={'py2exe': {'packages': ['hgext', 'email']},
671 'bdist_mpkg': {'zipdist': False,
671 'bdist_mpkg': {'zipdist': False,
672 'license': 'COPYING',
672 'license': 'COPYING',
673 'readme': 'contrib/macosx/Readme.html',
673 'readme': 'contrib/macosx/Readme.html',
674 'welcome': 'contrib/macosx/Welcome.html',
674 'welcome': 'contrib/macosx/Welcome.html',
675 },
675 },
676 },
676 },
677 **extra)
677 **extra)
@@ -1,70 +1,73 b''
1 $ echo 'raise Exception("bit bucket overflow")' > badext.py
1 $ echo 'raise Exception("bit bucket overflow")' > badext.py
2 $ abspathexc=`pwd`/badext.py
2 $ abspathexc=`pwd`/badext.py
3
3
4 $ cat >baddocext.py <<EOF
4 $ cat >baddocext.py <<EOF
5 > """
5 > """
6 > baddocext is bad
6 > baddocext is bad
7 > """
7 > """
8 > EOF
8 > EOF
9 $ abspathdoc=`pwd`/baddocext.py
9 $ abspathdoc=`pwd`/baddocext.py
10
10
11 $ cat <<EOF >> $HGRCPATH
11 $ cat <<EOF >> $HGRCPATH
12 > [extensions]
12 > [extensions]
13 > gpg =
13 > gpg =
14 > hgext.gpg =
14 > hgext.gpg =
15 > badext = $abspathexc
15 > badext = $abspathexc
16 > baddocext = $abspathdoc
16 > baddocext = $abspathdoc
17 > badext2 =
17 > badext2 =
18 > EOF
18 > EOF
19
19
20 $ hg -q help help 2>&1 |grep extension
20 $ hg -q help help 2>&1 |grep extension
21 *** failed to import extension badext from $TESTTMP/badext.py: bit bucket overflow
21 *** failed to import extension badext from $TESTTMP/badext.py: bit bucket overflow
22 *** failed to import extension badext2: No module named badext2
22 *** failed to import extension badext2: No module named badext2
23
23
24 show traceback
24 show traceback
25
25
26 $ hg -q help help --traceback 2>&1 | egrep ' extension|^Exception|Traceback|ImportError'
26 $ hg -q help help --traceback 2>&1 | egrep ' extension|^Exception|Traceback|ImportError'
27 *** failed to import extension badext from $TESTTMP/badext.py: bit bucket overflow
27 *** failed to import extension badext from $TESTTMP/badext.py: bit bucket overflow
28 Traceback (most recent call last):
28 Traceback (most recent call last):
29 Exception: bit bucket overflow
29 Exception: bit bucket overflow
30 *** failed to import extension badext2: No module named badext2
30 *** failed to import extension badext2: No module named badext2
31 Traceback (most recent call last):
31 Traceback (most recent call last):
32 ImportError: No module named badext2
32 ImportError: No module named badext2
33
33
34 names of extensions failed to load can be accessed via extensions.notloaded()
34 names of extensions failed to load can be accessed via extensions.notloaded()
35
35
36 $ cat <<EOF > showbadexts.py
36 $ cat <<EOF > showbadexts.py
37 > from mercurial import cmdutil, commands, extensions
37 > from mercurial import cmdutil, commands, extensions
38 > cmdtable = {}
38 > cmdtable = {}
39 > command = cmdutil.command(cmdtable)
39 > command = cmdutil.command(cmdtable)
40 > @command('showbadexts', norepo=True)
40 > @command('showbadexts', norepo=True)
41 > def showbadexts(ui, *pats, **opts):
41 > def showbadexts(ui, *pats, **opts):
42 > ui.write('BADEXTS: %s\n' % ' '.join(sorted(extensions.notloaded())))
42 > ui.write('BADEXTS: %s\n' % ' '.join(sorted(extensions.notloaded())))
43 > EOF
43 > EOF
44 $ hg --config extensions.badexts=showbadexts.py showbadexts 2>&1 | grep '^BADEXTS'
44 $ hg --config extensions.badexts=showbadexts.py showbadexts 2>&1 | grep '^BADEXTS'
45 BADEXTS: badext badext2
45 BADEXTS: badext badext2
46
46
47 show traceback for ImportError of hgext.name if debug is set
47 show traceback for ImportError of hgext.name if debug is set
48 (note that --debug option isn't applied yet when loading extensions)
48 (note that --debug option isn't applied yet when loading extensions)
49
49
50 $ (hg -q help help --traceback --config ui.debug=True 2>&1) \
50 $ (hg -q help help --traceback --config ui.debug=True 2>&1) \
51 > | grep -v '^ ' \
51 > | grep -v '^ ' \
52 > | egrep 'extension..[^p]|^Exception|Traceback|ImportError|not import'
52 > | egrep 'extension..[^p]|^Exception|Traceback|ImportError|not import'
53 *** failed to import extension badext from $TESTTMP/badext.py: bit bucket overflow
53 *** failed to import extension badext from $TESTTMP/badext.py: bit bucket overflow
54 Traceback (most recent call last):
54 Traceback (most recent call last):
55 Exception: bit bucket overflow
55 Exception: bit bucket overflow
56 could not import hgext.badext2 (No module named *badext2): trying badext2 (glob)
56 could not import hgext.badext2 (No module named *badext2): trying badext2 (glob)
57 Traceback (most recent call last):
57 Traceback (most recent call last):
58 ImportError: No module named *badext2 (glob)
58 ImportError: No module named *badext2 (glob)
59 could not import hgext3rd.badext2 (No module named badext2): trying badext2
60 Traceback (most recent call last):
61 ImportError: No module named badext2
59 *** failed to import extension badext2: No module named badext2
62 *** failed to import extension badext2: No module named badext2
60 Traceback (most recent call last):
63 Traceback (most recent call last):
61 ImportError: No module named badext2
64 ImportError: No module named badext2
62
65
63 confirm that there's no crash when an extension's documentation is bad
66 confirm that there's no crash when an extension's documentation is bad
64
67
65 $ hg help --keyword baddocext
68 $ hg help --keyword baddocext
66 *** failed to import extension badext from $TESTTMP/badext.py: bit bucket overflow
69 *** failed to import extension badext from $TESTTMP/badext.py: bit bucket overflow
67 *** failed to import extension badext2: No module named badext2
70 *** failed to import extension badext2: No module named badext2
68 Topics:
71 Topics:
69
72
70 extensions Using Additional Features
73 extensions Using Additional Features
General Comments 0
You need to be logged in to leave comments. Login now