##// END OF EJS Templates
help: create packages for the help text...
Matt Harbison -
r44031:2e017696 default
parent child Browse files
Show More
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
@@ -1,1119 +1,1119 b''
1 # help.py - help data for mercurial
1 # help.py - help data for mercurial
2 #
2 #
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006 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 itertools
10 import itertools
11 import os
11 import os
12 import re
12 import re
13 import textwrap
13 import textwrap
14
14
15 from .i18n import (
15 from .i18n import (
16 _,
16 _,
17 gettext,
17 gettext,
18 )
18 )
19 from .pycompat import getattr
19 from .pycompat import getattr
20 from . import (
20 from . import (
21 cmdutil,
21 cmdutil,
22 encoding,
22 encoding,
23 error,
23 error,
24 extensions,
24 extensions,
25 fancyopts,
25 fancyopts,
26 filemerge,
26 filemerge,
27 fileset,
27 fileset,
28 minirst,
28 minirst,
29 pycompat,
29 pycompat,
30 registrar,
30 registrar,
31 revset,
31 revset,
32 templatefilters,
32 templatefilters,
33 templatefuncs,
33 templatefuncs,
34 templatekw,
34 templatekw,
35 ui as uimod,
35 ui as uimod,
36 util,
36 util,
37 )
37 )
38 from .hgweb import webcommands
38 from .hgweb import webcommands
39 from .utils import compression
39 from .utils import compression
40
40
41 _exclkeywords = {
41 _exclkeywords = {
42 b"(ADVANCED)",
42 b"(ADVANCED)",
43 b"(DEPRECATED)",
43 b"(DEPRECATED)",
44 b"(EXPERIMENTAL)",
44 b"(EXPERIMENTAL)",
45 # i18n: "(ADVANCED)" is a keyword, must be translated consistently
45 # i18n: "(ADVANCED)" is a keyword, must be translated consistently
46 _(b"(ADVANCED)"),
46 _(b"(ADVANCED)"),
47 # i18n: "(DEPRECATED)" is a keyword, must be translated consistently
47 # i18n: "(DEPRECATED)" is a keyword, must be translated consistently
48 _(b"(DEPRECATED)"),
48 _(b"(DEPRECATED)"),
49 # i18n: "(EXPERIMENTAL)" is a keyword, must be translated consistently
49 # i18n: "(EXPERIMENTAL)" is a keyword, must be translated consistently
50 _(b"(EXPERIMENTAL)"),
50 _(b"(EXPERIMENTAL)"),
51 }
51 }
52
52
53 # The order in which command categories will be displayed.
53 # The order in which command categories will be displayed.
54 # Extensions with custom categories should insert them into this list
54 # Extensions with custom categories should insert them into this list
55 # after/before the appropriate item, rather than replacing the list or
55 # after/before the appropriate item, rather than replacing the list or
56 # assuming absolute positions.
56 # assuming absolute positions.
57 CATEGORY_ORDER = [
57 CATEGORY_ORDER = [
58 registrar.command.CATEGORY_REPO_CREATION,
58 registrar.command.CATEGORY_REPO_CREATION,
59 registrar.command.CATEGORY_REMOTE_REPO_MANAGEMENT,
59 registrar.command.CATEGORY_REMOTE_REPO_MANAGEMENT,
60 registrar.command.CATEGORY_COMMITTING,
60 registrar.command.CATEGORY_COMMITTING,
61 registrar.command.CATEGORY_CHANGE_MANAGEMENT,
61 registrar.command.CATEGORY_CHANGE_MANAGEMENT,
62 registrar.command.CATEGORY_CHANGE_ORGANIZATION,
62 registrar.command.CATEGORY_CHANGE_ORGANIZATION,
63 registrar.command.CATEGORY_FILE_CONTENTS,
63 registrar.command.CATEGORY_FILE_CONTENTS,
64 registrar.command.CATEGORY_CHANGE_NAVIGATION,
64 registrar.command.CATEGORY_CHANGE_NAVIGATION,
65 registrar.command.CATEGORY_WORKING_DIRECTORY,
65 registrar.command.CATEGORY_WORKING_DIRECTORY,
66 registrar.command.CATEGORY_IMPORT_EXPORT,
66 registrar.command.CATEGORY_IMPORT_EXPORT,
67 registrar.command.CATEGORY_MAINTENANCE,
67 registrar.command.CATEGORY_MAINTENANCE,
68 registrar.command.CATEGORY_HELP,
68 registrar.command.CATEGORY_HELP,
69 registrar.command.CATEGORY_MISC,
69 registrar.command.CATEGORY_MISC,
70 registrar.command.CATEGORY_NONE,
70 registrar.command.CATEGORY_NONE,
71 ]
71 ]
72
72
73 # Human-readable category names. These are translated.
73 # Human-readable category names. These are translated.
74 # Extensions with custom categories should add their names here.
74 # Extensions with custom categories should add their names here.
75 CATEGORY_NAMES = {
75 CATEGORY_NAMES = {
76 registrar.command.CATEGORY_REPO_CREATION: b'Repository creation',
76 registrar.command.CATEGORY_REPO_CREATION: b'Repository creation',
77 registrar.command.CATEGORY_REMOTE_REPO_MANAGEMENT: b'Remote repository management',
77 registrar.command.CATEGORY_REMOTE_REPO_MANAGEMENT: b'Remote repository management',
78 registrar.command.CATEGORY_COMMITTING: b'Change creation',
78 registrar.command.CATEGORY_COMMITTING: b'Change creation',
79 registrar.command.CATEGORY_CHANGE_NAVIGATION: b'Change navigation',
79 registrar.command.CATEGORY_CHANGE_NAVIGATION: b'Change navigation',
80 registrar.command.CATEGORY_CHANGE_MANAGEMENT: b'Change manipulation',
80 registrar.command.CATEGORY_CHANGE_MANAGEMENT: b'Change manipulation',
81 registrar.command.CATEGORY_CHANGE_ORGANIZATION: b'Change organization',
81 registrar.command.CATEGORY_CHANGE_ORGANIZATION: b'Change organization',
82 registrar.command.CATEGORY_WORKING_DIRECTORY: b'Working directory management',
82 registrar.command.CATEGORY_WORKING_DIRECTORY: b'Working directory management',
83 registrar.command.CATEGORY_FILE_CONTENTS: b'File content management',
83 registrar.command.CATEGORY_FILE_CONTENTS: b'File content management',
84 registrar.command.CATEGORY_IMPORT_EXPORT: b'Change import/export',
84 registrar.command.CATEGORY_IMPORT_EXPORT: b'Change import/export',
85 registrar.command.CATEGORY_MAINTENANCE: b'Repository maintenance',
85 registrar.command.CATEGORY_MAINTENANCE: b'Repository maintenance',
86 registrar.command.CATEGORY_HELP: b'Help',
86 registrar.command.CATEGORY_HELP: b'Help',
87 registrar.command.CATEGORY_MISC: b'Miscellaneous commands',
87 registrar.command.CATEGORY_MISC: b'Miscellaneous commands',
88 registrar.command.CATEGORY_NONE: b'Uncategorized commands',
88 registrar.command.CATEGORY_NONE: b'Uncategorized commands',
89 }
89 }
90
90
91 # Topic categories.
91 # Topic categories.
92 TOPIC_CATEGORY_IDS = b'ids'
92 TOPIC_CATEGORY_IDS = b'ids'
93 TOPIC_CATEGORY_OUTPUT = b'output'
93 TOPIC_CATEGORY_OUTPUT = b'output'
94 TOPIC_CATEGORY_CONFIG = b'config'
94 TOPIC_CATEGORY_CONFIG = b'config'
95 TOPIC_CATEGORY_CONCEPTS = b'concepts'
95 TOPIC_CATEGORY_CONCEPTS = b'concepts'
96 TOPIC_CATEGORY_MISC = b'misc'
96 TOPIC_CATEGORY_MISC = b'misc'
97 TOPIC_CATEGORY_NONE = b'none'
97 TOPIC_CATEGORY_NONE = b'none'
98
98
99 # The order in which topic categories will be displayed.
99 # The order in which topic categories will be displayed.
100 # Extensions with custom categories should insert them into this list
100 # Extensions with custom categories should insert them into this list
101 # after/before the appropriate item, rather than replacing the list or
101 # after/before the appropriate item, rather than replacing the list or
102 # assuming absolute positions.
102 # assuming absolute positions.
103 TOPIC_CATEGORY_ORDER = [
103 TOPIC_CATEGORY_ORDER = [
104 TOPIC_CATEGORY_IDS,
104 TOPIC_CATEGORY_IDS,
105 TOPIC_CATEGORY_OUTPUT,
105 TOPIC_CATEGORY_OUTPUT,
106 TOPIC_CATEGORY_CONFIG,
106 TOPIC_CATEGORY_CONFIG,
107 TOPIC_CATEGORY_CONCEPTS,
107 TOPIC_CATEGORY_CONCEPTS,
108 TOPIC_CATEGORY_MISC,
108 TOPIC_CATEGORY_MISC,
109 TOPIC_CATEGORY_NONE,
109 TOPIC_CATEGORY_NONE,
110 ]
110 ]
111
111
112 # Human-readable topic category names. These are translated.
112 # Human-readable topic category names. These are translated.
113 TOPIC_CATEGORY_NAMES = {
113 TOPIC_CATEGORY_NAMES = {
114 TOPIC_CATEGORY_IDS: b'Mercurial identifiers',
114 TOPIC_CATEGORY_IDS: b'Mercurial identifiers',
115 TOPIC_CATEGORY_OUTPUT: b'Mercurial output',
115 TOPIC_CATEGORY_OUTPUT: b'Mercurial output',
116 TOPIC_CATEGORY_CONFIG: b'Mercurial configuration',
116 TOPIC_CATEGORY_CONFIG: b'Mercurial configuration',
117 TOPIC_CATEGORY_CONCEPTS: b'Concepts',
117 TOPIC_CATEGORY_CONCEPTS: b'Concepts',
118 TOPIC_CATEGORY_MISC: b'Miscellaneous',
118 TOPIC_CATEGORY_MISC: b'Miscellaneous',
119 TOPIC_CATEGORY_NONE: b'Uncategorized topics',
119 TOPIC_CATEGORY_NONE: b'Uncategorized topics',
120 }
120 }
121
121
122
122
123 def listexts(header, exts, indent=1, showdeprecated=False):
123 def listexts(header, exts, indent=1, showdeprecated=False):
124 '''return a text listing of the given extensions'''
124 '''return a text listing of the given extensions'''
125 rst = []
125 rst = []
126 if exts:
126 if exts:
127 for name, desc in sorted(pycompat.iteritems(exts)):
127 for name, desc in sorted(pycompat.iteritems(exts)):
128 if not showdeprecated and any(w in desc for w in _exclkeywords):
128 if not showdeprecated and any(w in desc for w in _exclkeywords):
129 continue
129 continue
130 rst.append(b'%s:%s: %s\n' % (b' ' * indent, name, desc))
130 rst.append(b'%s:%s: %s\n' % (b' ' * indent, name, desc))
131 if rst:
131 if rst:
132 rst.insert(0, b'\n%s\n\n' % header)
132 rst.insert(0, b'\n%s\n\n' % header)
133 return rst
133 return rst
134
134
135
135
136 def extshelp(ui):
136 def extshelp(ui):
137 rst = loaddoc(b'extensions')(ui).splitlines(True)
137 rst = loaddoc(b'extensions')(ui).splitlines(True)
138 rst.extend(
138 rst.extend(
139 listexts(
139 listexts(
140 _(b'enabled extensions:'), extensions.enabled(), showdeprecated=True
140 _(b'enabled extensions:'), extensions.enabled(), showdeprecated=True
141 )
141 )
142 )
142 )
143 rst.extend(
143 rst.extend(
144 listexts(
144 listexts(
145 _(b'disabled extensions:'),
145 _(b'disabled extensions:'),
146 extensions.disabled(),
146 extensions.disabled(),
147 showdeprecated=ui.verbose,
147 showdeprecated=ui.verbose,
148 )
148 )
149 )
149 )
150 doc = b''.join(rst)
150 doc = b''.join(rst)
151 return doc
151 return doc
152
152
153
153
154 def optrst(header, options, verbose):
154 def optrst(header, options, verbose):
155 data = []
155 data = []
156 multioccur = False
156 multioccur = False
157 for option in options:
157 for option in options:
158 if len(option) == 5:
158 if len(option) == 5:
159 shortopt, longopt, default, desc, optlabel = option
159 shortopt, longopt, default, desc, optlabel = option
160 else:
160 else:
161 shortopt, longopt, default, desc = option
161 shortopt, longopt, default, desc = option
162 optlabel = _(b"VALUE") # default label
162 optlabel = _(b"VALUE") # default label
163
163
164 if not verbose and any(w in desc for w in _exclkeywords):
164 if not verbose and any(w in desc for w in _exclkeywords):
165 continue
165 continue
166
166
167 so = b''
167 so = b''
168 if shortopt:
168 if shortopt:
169 so = b'-' + shortopt
169 so = b'-' + shortopt
170 lo = b'--' + longopt
170 lo = b'--' + longopt
171 if default is True:
171 if default is True:
172 lo = b'--[no-]' + longopt
172 lo = b'--[no-]' + longopt
173
173
174 if isinstance(default, fancyopts.customopt):
174 if isinstance(default, fancyopts.customopt):
175 default = default.getdefaultvalue()
175 default = default.getdefaultvalue()
176 if default and not callable(default):
176 if default and not callable(default):
177 # default is of unknown type, and in Python 2 we abused
177 # default is of unknown type, and in Python 2 we abused
178 # the %s-shows-repr property to handle integers etc. To
178 # the %s-shows-repr property to handle integers etc. To
179 # match that behavior on Python 3, we do str(default) and
179 # match that behavior on Python 3, we do str(default) and
180 # then convert it to bytes.
180 # then convert it to bytes.
181 defaultstr = pycompat.bytestr(default)
181 defaultstr = pycompat.bytestr(default)
182 if default is True:
182 if default is True:
183 defaultstr = _(b"on")
183 defaultstr = _(b"on")
184 desc += _(b" (default: %s)") % defaultstr
184 desc += _(b" (default: %s)") % defaultstr
185
185
186 if isinstance(default, list):
186 if isinstance(default, list):
187 lo += b" %s [+]" % optlabel
187 lo += b" %s [+]" % optlabel
188 multioccur = True
188 multioccur = True
189 elif (default is not None) and not isinstance(default, bool):
189 elif (default is not None) and not isinstance(default, bool):
190 lo += b" %s" % optlabel
190 lo += b" %s" % optlabel
191
191
192 data.append((so, lo, desc))
192 data.append((so, lo, desc))
193
193
194 if multioccur:
194 if multioccur:
195 header += _(b" ([+] can be repeated)")
195 header += _(b" ([+] can be repeated)")
196
196
197 rst = [b'\n%s:\n\n' % header]
197 rst = [b'\n%s:\n\n' % header]
198 rst.extend(minirst.maketable(data, 1))
198 rst.extend(minirst.maketable(data, 1))
199
199
200 return b''.join(rst)
200 return b''.join(rst)
201
201
202
202
203 def indicateomitted(rst, omitted, notomitted=None):
203 def indicateomitted(rst, omitted, notomitted=None):
204 rst.append(b'\n\n.. container:: omitted\n\n %s\n\n' % omitted)
204 rst.append(b'\n\n.. container:: omitted\n\n %s\n\n' % omitted)
205 if notomitted:
205 if notomitted:
206 rst.append(b'\n\n.. container:: notomitted\n\n %s\n\n' % notomitted)
206 rst.append(b'\n\n.. container:: notomitted\n\n %s\n\n' % notomitted)
207
207
208
208
209 def filtercmd(ui, cmd, func, kw, doc):
209 def filtercmd(ui, cmd, func, kw, doc):
210 if not ui.debugflag and cmd.startswith(b"debug") and kw != b"debug":
210 if not ui.debugflag and cmd.startswith(b"debug") and kw != b"debug":
211 # Debug command, and user is not looking for those.
211 # Debug command, and user is not looking for those.
212 return True
212 return True
213 if not ui.verbose:
213 if not ui.verbose:
214 if not kw and not doc:
214 if not kw and not doc:
215 # Command had no documentation, no point in showing it by default.
215 # Command had no documentation, no point in showing it by default.
216 return True
216 return True
217 if getattr(func, 'alias', False) and not getattr(func, 'owndoc', False):
217 if getattr(func, 'alias', False) and not getattr(func, 'owndoc', False):
218 # Alias didn't have its own documentation.
218 # Alias didn't have its own documentation.
219 return True
219 return True
220 if doc and any(w in doc for w in _exclkeywords):
220 if doc and any(w in doc for w in _exclkeywords):
221 # Documentation has excluded keywords.
221 # Documentation has excluded keywords.
222 return True
222 return True
223 if kw == b"shortlist" and not getattr(func, 'helpbasic', False):
223 if kw == b"shortlist" and not getattr(func, 'helpbasic', False):
224 # We're presenting the short list but the command is not basic.
224 # We're presenting the short list but the command is not basic.
225 return True
225 return True
226 if ui.configbool(b'help', b'hidden-command.%s' % cmd):
226 if ui.configbool(b'help', b'hidden-command.%s' % cmd):
227 # Configuration explicitly hides the command.
227 # Configuration explicitly hides the command.
228 return True
228 return True
229 return False
229 return False
230
230
231
231
232 def filtertopic(ui, topic):
232 def filtertopic(ui, topic):
233 return ui.configbool(b'help', b'hidden-topic.%s' % topic, False)
233 return ui.configbool(b'help', b'hidden-topic.%s' % topic, False)
234
234
235
235
236 def topicmatch(ui, commands, kw):
236 def topicmatch(ui, commands, kw):
237 """Return help topics matching kw.
237 """Return help topics matching kw.
238
238
239 Returns {'section': [(name, summary), ...], ...} where section is
239 Returns {'section': [(name, summary), ...], ...} where section is
240 one of topics, commands, extensions, or extensioncommands.
240 one of topics, commands, extensions, or extensioncommands.
241 """
241 """
242 kw = encoding.lower(kw)
242 kw = encoding.lower(kw)
243
243
244 def lowercontains(container):
244 def lowercontains(container):
245 return kw in encoding.lower(container) # translated in helptable
245 return kw in encoding.lower(container) # translated in helptable
246
246
247 results = {
247 results = {
248 b'topics': [],
248 b'topics': [],
249 b'commands': [],
249 b'commands': [],
250 b'extensions': [],
250 b'extensions': [],
251 b'extensioncommands': [],
251 b'extensioncommands': [],
252 }
252 }
253 for topic in helptable:
253 for topic in helptable:
254 names, header, doc = topic[0:3]
254 names, header, doc = topic[0:3]
255 # Old extensions may use a str as doc.
255 # Old extensions may use a str as doc.
256 if (
256 if (
257 sum(map(lowercontains, names))
257 sum(map(lowercontains, names))
258 or lowercontains(header)
258 or lowercontains(header)
259 or (callable(doc) and lowercontains(doc(ui)))
259 or (callable(doc) and lowercontains(doc(ui)))
260 ):
260 ):
261 name = names[0]
261 name = names[0]
262 if not filtertopic(ui, name):
262 if not filtertopic(ui, name):
263 results[b'topics'].append((names[0], header))
263 results[b'topics'].append((names[0], header))
264 for cmd, entry in pycompat.iteritems(commands.table):
264 for cmd, entry in pycompat.iteritems(commands.table):
265 if len(entry) == 3:
265 if len(entry) == 3:
266 summary = entry[2]
266 summary = entry[2]
267 else:
267 else:
268 summary = b''
268 summary = b''
269 # translate docs *before* searching there
269 # translate docs *before* searching there
270 func = entry[0]
270 func = entry[0]
271 docs = _(pycompat.getdoc(func)) or b''
271 docs = _(pycompat.getdoc(func)) or b''
272 if kw in cmd or lowercontains(summary) or lowercontains(docs):
272 if kw in cmd or lowercontains(summary) or lowercontains(docs):
273 doclines = docs.splitlines()
273 doclines = docs.splitlines()
274 if doclines:
274 if doclines:
275 summary = doclines[0]
275 summary = doclines[0]
276 cmdname = cmdutil.parsealiases(cmd)[0]
276 cmdname = cmdutil.parsealiases(cmd)[0]
277 if filtercmd(ui, cmdname, func, kw, docs):
277 if filtercmd(ui, cmdname, func, kw, docs):
278 continue
278 continue
279 results[b'commands'].append((cmdname, summary))
279 results[b'commands'].append((cmdname, summary))
280 for name, docs in itertools.chain(
280 for name, docs in itertools.chain(
281 pycompat.iteritems(extensions.enabled(False)),
281 pycompat.iteritems(extensions.enabled(False)),
282 pycompat.iteritems(extensions.disabled()),
282 pycompat.iteritems(extensions.disabled()),
283 ):
283 ):
284 if not docs:
284 if not docs:
285 continue
285 continue
286 name = name.rpartition(b'.')[-1]
286 name = name.rpartition(b'.')[-1]
287 if lowercontains(name) or lowercontains(docs):
287 if lowercontains(name) or lowercontains(docs):
288 # extension docs are already translated
288 # extension docs are already translated
289 results[b'extensions'].append((name, docs.splitlines()[0]))
289 results[b'extensions'].append((name, docs.splitlines()[0]))
290 try:
290 try:
291 mod = extensions.load(ui, name, b'')
291 mod = extensions.load(ui, name, b'')
292 except ImportError:
292 except ImportError:
293 # debug message would be printed in extensions.load()
293 # debug message would be printed in extensions.load()
294 continue
294 continue
295 for cmd, entry in pycompat.iteritems(getattr(mod, 'cmdtable', {})):
295 for cmd, entry in pycompat.iteritems(getattr(mod, 'cmdtable', {})):
296 if kw in cmd or (len(entry) > 2 and lowercontains(entry[2])):
296 if kw in cmd or (len(entry) > 2 and lowercontains(entry[2])):
297 cmdname = cmdutil.parsealiases(cmd)[0]
297 cmdname = cmdutil.parsealiases(cmd)[0]
298 func = entry[0]
298 func = entry[0]
299 cmddoc = pycompat.getdoc(func)
299 cmddoc = pycompat.getdoc(func)
300 if cmddoc:
300 if cmddoc:
301 cmddoc = gettext(cmddoc).splitlines()[0]
301 cmddoc = gettext(cmddoc).splitlines()[0]
302 else:
302 else:
303 cmddoc = _(b'(no help text available)')
303 cmddoc = _(b'(no help text available)')
304 if filtercmd(ui, cmdname, func, kw, cmddoc):
304 if filtercmd(ui, cmdname, func, kw, cmddoc):
305 continue
305 continue
306 results[b'extensioncommands'].append((cmdname, cmddoc))
306 results[b'extensioncommands'].append((cmdname, cmddoc))
307 return results
307 return results
308
308
309
309
310 def loaddoc(topic, subdir=None):
310 def loaddoc(topic, subdir=None):
311 """Return a delayed loader for help/topic.txt."""
311 """Return a delayed loader for help/topic.txt."""
312
312
313 def loader(ui):
313 def loader(ui):
314 docdir = os.path.join(util.datapath, b'help')
314 docdir = os.path.join(util.datapath, b'helptext')
315 if subdir:
315 if subdir:
316 docdir = os.path.join(docdir, subdir)
316 docdir = os.path.join(docdir, subdir)
317 path = os.path.join(docdir, topic + b".txt")
317 path = os.path.join(docdir, topic + b".txt")
318 doc = gettext(util.readfile(path))
318 doc = gettext(util.readfile(path))
319 for rewriter in helphooks.get(topic, []):
319 for rewriter in helphooks.get(topic, []):
320 doc = rewriter(ui, topic, doc)
320 doc = rewriter(ui, topic, doc)
321 return doc
321 return doc
322
322
323 return loader
323 return loader
324
324
325
325
326 internalstable = sorted(
326 internalstable = sorted(
327 [
327 [
328 ([b'bundle2'], _(b'Bundle2'), loaddoc(b'bundle2', subdir=b'internals')),
328 ([b'bundle2'], _(b'Bundle2'), loaddoc(b'bundle2', subdir=b'internals')),
329 ([b'bundles'], _(b'Bundles'), loaddoc(b'bundles', subdir=b'internals')),
329 ([b'bundles'], _(b'Bundles'), loaddoc(b'bundles', subdir=b'internals')),
330 ([b'cbor'], _(b'CBOR'), loaddoc(b'cbor', subdir=b'internals')),
330 ([b'cbor'], _(b'CBOR'), loaddoc(b'cbor', subdir=b'internals')),
331 ([b'censor'], _(b'Censor'), loaddoc(b'censor', subdir=b'internals')),
331 ([b'censor'], _(b'Censor'), loaddoc(b'censor', subdir=b'internals')),
332 (
332 (
333 [b'changegroups'],
333 [b'changegroups'],
334 _(b'Changegroups'),
334 _(b'Changegroups'),
335 loaddoc(b'changegroups', subdir=b'internals'),
335 loaddoc(b'changegroups', subdir=b'internals'),
336 ),
336 ),
337 (
337 (
338 [b'config'],
338 [b'config'],
339 _(b'Config Registrar'),
339 _(b'Config Registrar'),
340 loaddoc(b'config', subdir=b'internals'),
340 loaddoc(b'config', subdir=b'internals'),
341 ),
341 ),
342 (
342 (
343 [b'extensions', b'extension'],
343 [b'extensions', b'extension'],
344 _(b'Extension API'),
344 _(b'Extension API'),
345 loaddoc(b'extensions', subdir=b'internals'),
345 loaddoc(b'extensions', subdir=b'internals'),
346 ),
346 ),
347 (
347 (
348 [b'mergestate'],
348 [b'mergestate'],
349 _(b'Mergestate'),
349 _(b'Mergestate'),
350 loaddoc(b'mergestate', subdir=b'internals'),
350 loaddoc(b'mergestate', subdir=b'internals'),
351 ),
351 ),
352 (
352 (
353 [b'requirements'],
353 [b'requirements'],
354 _(b'Repository Requirements'),
354 _(b'Repository Requirements'),
355 loaddoc(b'requirements', subdir=b'internals'),
355 loaddoc(b'requirements', subdir=b'internals'),
356 ),
356 ),
357 (
357 (
358 [b'revlogs'],
358 [b'revlogs'],
359 _(b'Revision Logs'),
359 _(b'Revision Logs'),
360 loaddoc(b'revlogs', subdir=b'internals'),
360 loaddoc(b'revlogs', subdir=b'internals'),
361 ),
361 ),
362 (
362 (
363 [b'wireprotocol'],
363 [b'wireprotocol'],
364 _(b'Wire Protocol'),
364 _(b'Wire Protocol'),
365 loaddoc(b'wireprotocol', subdir=b'internals'),
365 loaddoc(b'wireprotocol', subdir=b'internals'),
366 ),
366 ),
367 (
367 (
368 [b'wireprotocolrpc'],
368 [b'wireprotocolrpc'],
369 _(b'Wire Protocol RPC'),
369 _(b'Wire Protocol RPC'),
370 loaddoc(b'wireprotocolrpc', subdir=b'internals'),
370 loaddoc(b'wireprotocolrpc', subdir=b'internals'),
371 ),
371 ),
372 (
372 (
373 [b'wireprotocolv2'],
373 [b'wireprotocolv2'],
374 _(b'Wire Protocol Version 2'),
374 _(b'Wire Protocol Version 2'),
375 loaddoc(b'wireprotocolv2', subdir=b'internals'),
375 loaddoc(b'wireprotocolv2', subdir=b'internals'),
376 ),
376 ),
377 ]
377 ]
378 )
378 )
379
379
380
380
381 def internalshelp(ui):
381 def internalshelp(ui):
382 """Generate the index for the "internals" topic."""
382 """Generate the index for the "internals" topic."""
383 lines = [
383 lines = [
384 b'To access a subtopic, use "hg help internals.{subtopic-name}"\n',
384 b'To access a subtopic, use "hg help internals.{subtopic-name}"\n',
385 b'\n',
385 b'\n',
386 ]
386 ]
387 for names, header, doc in internalstable:
387 for names, header, doc in internalstable:
388 lines.append(b' :%s: %s\n' % (names[0], header))
388 lines.append(b' :%s: %s\n' % (names[0], header))
389
389
390 return b''.join(lines)
390 return b''.join(lines)
391
391
392
392
393 helptable = sorted(
393 helptable = sorted(
394 [
394 [
395 (
395 (
396 [b'bundlespec'],
396 [b'bundlespec'],
397 _(b"Bundle File Formats"),
397 _(b"Bundle File Formats"),
398 loaddoc(b'bundlespec'),
398 loaddoc(b'bundlespec'),
399 TOPIC_CATEGORY_CONCEPTS,
399 TOPIC_CATEGORY_CONCEPTS,
400 ),
400 ),
401 (
401 (
402 [b'color'],
402 [b'color'],
403 _(b"Colorizing Outputs"),
403 _(b"Colorizing Outputs"),
404 loaddoc(b'color'),
404 loaddoc(b'color'),
405 TOPIC_CATEGORY_OUTPUT,
405 TOPIC_CATEGORY_OUTPUT,
406 ),
406 ),
407 (
407 (
408 [b"config", b"hgrc"],
408 [b"config", b"hgrc"],
409 _(b"Configuration Files"),
409 _(b"Configuration Files"),
410 loaddoc(b'config'),
410 loaddoc(b'config'),
411 TOPIC_CATEGORY_CONFIG,
411 TOPIC_CATEGORY_CONFIG,
412 ),
412 ),
413 (
413 (
414 [b'deprecated'],
414 [b'deprecated'],
415 _(b"Deprecated Features"),
415 _(b"Deprecated Features"),
416 loaddoc(b'deprecated'),
416 loaddoc(b'deprecated'),
417 TOPIC_CATEGORY_MISC,
417 TOPIC_CATEGORY_MISC,
418 ),
418 ),
419 (
419 (
420 [b"dates"],
420 [b"dates"],
421 _(b"Date Formats"),
421 _(b"Date Formats"),
422 loaddoc(b'dates'),
422 loaddoc(b'dates'),
423 TOPIC_CATEGORY_OUTPUT,
423 TOPIC_CATEGORY_OUTPUT,
424 ),
424 ),
425 (
425 (
426 [b"flags"],
426 [b"flags"],
427 _(b"Command-line flags"),
427 _(b"Command-line flags"),
428 loaddoc(b'flags'),
428 loaddoc(b'flags'),
429 TOPIC_CATEGORY_CONFIG,
429 TOPIC_CATEGORY_CONFIG,
430 ),
430 ),
431 (
431 (
432 [b"patterns"],
432 [b"patterns"],
433 _(b"File Name Patterns"),
433 _(b"File Name Patterns"),
434 loaddoc(b'patterns'),
434 loaddoc(b'patterns'),
435 TOPIC_CATEGORY_IDS,
435 TOPIC_CATEGORY_IDS,
436 ),
436 ),
437 (
437 (
438 [b'environment', b'env'],
438 [b'environment', b'env'],
439 _(b'Environment Variables'),
439 _(b'Environment Variables'),
440 loaddoc(b'environment'),
440 loaddoc(b'environment'),
441 TOPIC_CATEGORY_CONFIG,
441 TOPIC_CATEGORY_CONFIG,
442 ),
442 ),
443 (
443 (
444 [
444 [
445 b'revisions',
445 b'revisions',
446 b'revs',
446 b'revs',
447 b'revsets',
447 b'revsets',
448 b'revset',
448 b'revset',
449 b'multirevs',
449 b'multirevs',
450 b'mrevs',
450 b'mrevs',
451 ],
451 ],
452 _(b'Specifying Revisions'),
452 _(b'Specifying Revisions'),
453 loaddoc(b'revisions'),
453 loaddoc(b'revisions'),
454 TOPIC_CATEGORY_IDS,
454 TOPIC_CATEGORY_IDS,
455 ),
455 ),
456 (
456 (
457 [b'filesets', b'fileset'],
457 [b'filesets', b'fileset'],
458 _(b"Specifying File Sets"),
458 _(b"Specifying File Sets"),
459 loaddoc(b'filesets'),
459 loaddoc(b'filesets'),
460 TOPIC_CATEGORY_IDS,
460 TOPIC_CATEGORY_IDS,
461 ),
461 ),
462 (
462 (
463 [b'diffs'],
463 [b'diffs'],
464 _(b'Diff Formats'),
464 _(b'Diff Formats'),
465 loaddoc(b'diffs'),
465 loaddoc(b'diffs'),
466 TOPIC_CATEGORY_OUTPUT,
466 TOPIC_CATEGORY_OUTPUT,
467 ),
467 ),
468 (
468 (
469 [b'merge-tools', b'mergetools', b'mergetool'],
469 [b'merge-tools', b'mergetools', b'mergetool'],
470 _(b'Merge Tools'),
470 _(b'Merge Tools'),
471 loaddoc(b'merge-tools'),
471 loaddoc(b'merge-tools'),
472 TOPIC_CATEGORY_CONFIG,
472 TOPIC_CATEGORY_CONFIG,
473 ),
473 ),
474 (
474 (
475 [b'templating', b'templates', b'template', b'style'],
475 [b'templating', b'templates', b'template', b'style'],
476 _(b'Template Usage'),
476 _(b'Template Usage'),
477 loaddoc(b'templates'),
477 loaddoc(b'templates'),
478 TOPIC_CATEGORY_OUTPUT,
478 TOPIC_CATEGORY_OUTPUT,
479 ),
479 ),
480 ([b'urls'], _(b'URL Paths'), loaddoc(b'urls'), TOPIC_CATEGORY_IDS),
480 ([b'urls'], _(b'URL Paths'), loaddoc(b'urls'), TOPIC_CATEGORY_IDS),
481 (
481 (
482 [b"extensions"],
482 [b"extensions"],
483 _(b"Using Additional Features"),
483 _(b"Using Additional Features"),
484 extshelp,
484 extshelp,
485 TOPIC_CATEGORY_CONFIG,
485 TOPIC_CATEGORY_CONFIG,
486 ),
486 ),
487 (
487 (
488 [b"subrepos", b"subrepo"],
488 [b"subrepos", b"subrepo"],
489 _(b"Subrepositories"),
489 _(b"Subrepositories"),
490 loaddoc(b'subrepos'),
490 loaddoc(b'subrepos'),
491 TOPIC_CATEGORY_CONCEPTS,
491 TOPIC_CATEGORY_CONCEPTS,
492 ),
492 ),
493 (
493 (
494 [b"hgweb"],
494 [b"hgweb"],
495 _(b"Configuring hgweb"),
495 _(b"Configuring hgweb"),
496 loaddoc(b'hgweb'),
496 loaddoc(b'hgweb'),
497 TOPIC_CATEGORY_CONFIG,
497 TOPIC_CATEGORY_CONFIG,
498 ),
498 ),
499 (
499 (
500 [b"glossary"],
500 [b"glossary"],
501 _(b"Glossary"),
501 _(b"Glossary"),
502 loaddoc(b'glossary'),
502 loaddoc(b'glossary'),
503 TOPIC_CATEGORY_CONCEPTS,
503 TOPIC_CATEGORY_CONCEPTS,
504 ),
504 ),
505 (
505 (
506 [b"hgignore", b"ignore"],
506 [b"hgignore", b"ignore"],
507 _(b"Syntax for Mercurial Ignore Files"),
507 _(b"Syntax for Mercurial Ignore Files"),
508 loaddoc(b'hgignore'),
508 loaddoc(b'hgignore'),
509 TOPIC_CATEGORY_IDS,
509 TOPIC_CATEGORY_IDS,
510 ),
510 ),
511 (
511 (
512 [b"phases"],
512 [b"phases"],
513 _(b"Working with Phases"),
513 _(b"Working with Phases"),
514 loaddoc(b'phases'),
514 loaddoc(b'phases'),
515 TOPIC_CATEGORY_CONCEPTS,
515 TOPIC_CATEGORY_CONCEPTS,
516 ),
516 ),
517 (
517 (
518 [b'scripting'],
518 [b'scripting'],
519 _(b'Using Mercurial from scripts and automation'),
519 _(b'Using Mercurial from scripts and automation'),
520 loaddoc(b'scripting'),
520 loaddoc(b'scripting'),
521 TOPIC_CATEGORY_MISC,
521 TOPIC_CATEGORY_MISC,
522 ),
522 ),
523 (
523 (
524 [b'internals'],
524 [b'internals'],
525 _(b"Technical implementation topics"),
525 _(b"Technical implementation topics"),
526 internalshelp,
526 internalshelp,
527 TOPIC_CATEGORY_MISC,
527 TOPIC_CATEGORY_MISC,
528 ),
528 ),
529 (
529 (
530 [b'pager'],
530 [b'pager'],
531 _(b"Pager Support"),
531 _(b"Pager Support"),
532 loaddoc(b'pager'),
532 loaddoc(b'pager'),
533 TOPIC_CATEGORY_CONFIG,
533 TOPIC_CATEGORY_CONFIG,
534 ),
534 ),
535 ]
535 ]
536 )
536 )
537
537
538 # Maps topics with sub-topics to a list of their sub-topics.
538 # Maps topics with sub-topics to a list of their sub-topics.
539 subtopics = {
539 subtopics = {
540 b'internals': internalstable,
540 b'internals': internalstable,
541 }
541 }
542
542
543 # Map topics to lists of callable taking the current topic help and
543 # Map topics to lists of callable taking the current topic help and
544 # returning the updated version
544 # returning the updated version
545 helphooks = {}
545 helphooks = {}
546
546
547
547
548 def addtopichook(topic, rewriter):
548 def addtopichook(topic, rewriter):
549 helphooks.setdefault(topic, []).append(rewriter)
549 helphooks.setdefault(topic, []).append(rewriter)
550
550
551
551
552 def makeitemsdoc(ui, topic, doc, marker, items, dedent=False):
552 def makeitemsdoc(ui, topic, doc, marker, items, dedent=False):
553 """Extract docstring from the items key to function mapping, build a
553 """Extract docstring from the items key to function mapping, build a
554 single documentation block and use it to overwrite the marker in doc.
554 single documentation block and use it to overwrite the marker in doc.
555 """
555 """
556 entries = []
556 entries = []
557 for name in sorted(items):
557 for name in sorted(items):
558 text = (pycompat.getdoc(items[name]) or b'').rstrip()
558 text = (pycompat.getdoc(items[name]) or b'').rstrip()
559 if not text or not ui.verbose and any(w in text for w in _exclkeywords):
559 if not text or not ui.verbose and any(w in text for w in _exclkeywords):
560 continue
560 continue
561 text = gettext(text)
561 text = gettext(text)
562 if dedent:
562 if dedent:
563 # Abuse latin1 to use textwrap.dedent() on bytes.
563 # Abuse latin1 to use textwrap.dedent() on bytes.
564 text = textwrap.dedent(text.decode('latin1')).encode('latin1')
564 text = textwrap.dedent(text.decode('latin1')).encode('latin1')
565 lines = text.splitlines()
565 lines = text.splitlines()
566 doclines = [(lines[0])]
566 doclines = [(lines[0])]
567 for l in lines[1:]:
567 for l in lines[1:]:
568 # Stop once we find some Python doctest
568 # Stop once we find some Python doctest
569 if l.strip().startswith(b'>>>'):
569 if l.strip().startswith(b'>>>'):
570 break
570 break
571 if dedent:
571 if dedent:
572 doclines.append(l.rstrip())
572 doclines.append(l.rstrip())
573 else:
573 else:
574 doclines.append(b' ' + l.strip())
574 doclines.append(b' ' + l.strip())
575 entries.append(b'\n'.join(doclines))
575 entries.append(b'\n'.join(doclines))
576 entries = b'\n\n'.join(entries)
576 entries = b'\n\n'.join(entries)
577 return doc.replace(marker, entries)
577 return doc.replace(marker, entries)
578
578
579
579
580 def addtopicsymbols(topic, marker, symbols, dedent=False):
580 def addtopicsymbols(topic, marker, symbols, dedent=False):
581 def add(ui, topic, doc):
581 def add(ui, topic, doc):
582 return makeitemsdoc(ui, topic, doc, marker, symbols, dedent=dedent)
582 return makeitemsdoc(ui, topic, doc, marker, symbols, dedent=dedent)
583
583
584 addtopichook(topic, add)
584 addtopichook(topic, add)
585
585
586
586
587 addtopicsymbols(
587 addtopicsymbols(
588 b'bundlespec',
588 b'bundlespec',
589 b'.. bundlecompressionmarker',
589 b'.. bundlecompressionmarker',
590 compression.bundlecompressiontopics(),
590 compression.bundlecompressiontopics(),
591 )
591 )
592 addtopicsymbols(b'filesets', b'.. predicatesmarker', fileset.symbols)
592 addtopicsymbols(b'filesets', b'.. predicatesmarker', fileset.symbols)
593 addtopicsymbols(
593 addtopicsymbols(
594 b'merge-tools', b'.. internaltoolsmarker', filemerge.internalsdoc
594 b'merge-tools', b'.. internaltoolsmarker', filemerge.internalsdoc
595 )
595 )
596 addtopicsymbols(b'revisions', b'.. predicatesmarker', revset.symbols)
596 addtopicsymbols(b'revisions', b'.. predicatesmarker', revset.symbols)
597 addtopicsymbols(b'templates', b'.. keywordsmarker', templatekw.keywords)
597 addtopicsymbols(b'templates', b'.. keywordsmarker', templatekw.keywords)
598 addtopicsymbols(b'templates', b'.. filtersmarker', templatefilters.filters)
598 addtopicsymbols(b'templates', b'.. filtersmarker', templatefilters.filters)
599 addtopicsymbols(b'templates', b'.. functionsmarker', templatefuncs.funcs)
599 addtopicsymbols(b'templates', b'.. functionsmarker', templatefuncs.funcs)
600 addtopicsymbols(
600 addtopicsymbols(
601 b'hgweb', b'.. webcommandsmarker', webcommands.commands, dedent=True
601 b'hgweb', b'.. webcommandsmarker', webcommands.commands, dedent=True
602 )
602 )
603
603
604
604
605 def inserttweakrc(ui, topic, doc):
605 def inserttweakrc(ui, topic, doc):
606 marker = b'.. tweakdefaultsmarker'
606 marker = b'.. tweakdefaultsmarker'
607 repl = uimod.tweakrc
607 repl = uimod.tweakrc
608
608
609 def sub(m):
609 def sub(m):
610 lines = [m.group(1) + s for s in repl.splitlines()]
610 lines = [m.group(1) + s for s in repl.splitlines()]
611 return b'\n'.join(lines)
611 return b'\n'.join(lines)
612
612
613 return re.sub(br'( *)%s' % re.escape(marker), sub, doc)
613 return re.sub(br'( *)%s' % re.escape(marker), sub, doc)
614
614
615
615
616 addtopichook(b'config', inserttweakrc)
616 addtopichook(b'config', inserttweakrc)
617
617
618
618
619 def help_(
619 def help_(
620 ui,
620 ui,
621 commands,
621 commands,
622 name,
622 name,
623 unknowncmd=False,
623 unknowncmd=False,
624 full=True,
624 full=True,
625 subtopic=None,
625 subtopic=None,
626 fullname=None,
626 fullname=None,
627 **opts
627 **opts
628 ):
628 ):
629 '''
629 '''
630 Generate the help for 'name' as unformatted restructured text. If
630 Generate the help for 'name' as unformatted restructured text. If
631 'name' is None, describe the commands available.
631 'name' is None, describe the commands available.
632 '''
632 '''
633
633
634 opts = pycompat.byteskwargs(opts)
634 opts = pycompat.byteskwargs(opts)
635
635
636 def helpcmd(name, subtopic=None):
636 def helpcmd(name, subtopic=None):
637 try:
637 try:
638 aliases, entry = cmdutil.findcmd(
638 aliases, entry = cmdutil.findcmd(
639 name, commands.table, strict=unknowncmd
639 name, commands.table, strict=unknowncmd
640 )
640 )
641 except error.AmbiguousCommand as inst:
641 except error.AmbiguousCommand as inst:
642 # py3 fix: except vars can't be used outside the scope of the
642 # py3 fix: except vars can't be used outside the scope of the
643 # except block, nor can be used inside a lambda. python issue4617
643 # except block, nor can be used inside a lambda. python issue4617
644 prefix = inst.args[0]
644 prefix = inst.args[0]
645 select = lambda c: cmdutil.parsealiases(c)[0].startswith(prefix)
645 select = lambda c: cmdutil.parsealiases(c)[0].startswith(prefix)
646 rst = helplist(select)
646 rst = helplist(select)
647 return rst
647 return rst
648
648
649 rst = []
649 rst = []
650
650
651 # check if it's an invalid alias and display its error if it is
651 # check if it's an invalid alias and display its error if it is
652 if getattr(entry[0], 'badalias', None):
652 if getattr(entry[0], 'badalias', None):
653 rst.append(entry[0].badalias + b'\n')
653 rst.append(entry[0].badalias + b'\n')
654 if entry[0].unknowncmd:
654 if entry[0].unknowncmd:
655 try:
655 try:
656 rst.extend(helpextcmd(entry[0].cmdname))
656 rst.extend(helpextcmd(entry[0].cmdname))
657 except error.UnknownCommand:
657 except error.UnknownCommand:
658 pass
658 pass
659 return rst
659 return rst
660
660
661 # synopsis
661 # synopsis
662 if len(entry) > 2:
662 if len(entry) > 2:
663 if entry[2].startswith(b'hg'):
663 if entry[2].startswith(b'hg'):
664 rst.append(b"%s\n" % entry[2])
664 rst.append(b"%s\n" % entry[2])
665 else:
665 else:
666 rst.append(b'hg %s %s\n' % (aliases[0], entry[2]))
666 rst.append(b'hg %s %s\n' % (aliases[0], entry[2]))
667 else:
667 else:
668 rst.append(b'hg %s\n' % aliases[0])
668 rst.append(b'hg %s\n' % aliases[0])
669 # aliases
669 # aliases
670 if full and not ui.quiet and len(aliases) > 1:
670 if full and not ui.quiet and len(aliases) > 1:
671 rst.append(_(b"\naliases: %s\n") % b', '.join(aliases[1:]))
671 rst.append(_(b"\naliases: %s\n") % b', '.join(aliases[1:]))
672 rst.append(b'\n')
672 rst.append(b'\n')
673
673
674 # description
674 # description
675 doc = gettext(pycompat.getdoc(entry[0]))
675 doc = gettext(pycompat.getdoc(entry[0]))
676 if not doc:
676 if not doc:
677 doc = _(b"(no help text available)")
677 doc = _(b"(no help text available)")
678 if util.safehasattr(entry[0], b'definition'): # aliased command
678 if util.safehasattr(entry[0], b'definition'): # aliased command
679 source = entry[0].source
679 source = entry[0].source
680 if entry[0].definition.startswith(b'!'): # shell alias
680 if entry[0].definition.startswith(b'!'): # shell alias
681 doc = _(b'shell alias for: %s\n\n%s\n\ndefined by: %s\n') % (
681 doc = _(b'shell alias for: %s\n\n%s\n\ndefined by: %s\n') % (
682 entry[0].definition[1:],
682 entry[0].definition[1:],
683 doc,
683 doc,
684 source,
684 source,
685 )
685 )
686 else:
686 else:
687 doc = _(b'alias for: hg %s\n\n%s\n\ndefined by: %s\n') % (
687 doc = _(b'alias for: hg %s\n\n%s\n\ndefined by: %s\n') % (
688 entry[0].definition,
688 entry[0].definition,
689 doc,
689 doc,
690 source,
690 source,
691 )
691 )
692 doc = doc.splitlines(True)
692 doc = doc.splitlines(True)
693 if ui.quiet or not full:
693 if ui.quiet or not full:
694 rst.append(doc[0])
694 rst.append(doc[0])
695 else:
695 else:
696 rst.extend(doc)
696 rst.extend(doc)
697 rst.append(b'\n')
697 rst.append(b'\n')
698
698
699 # check if this command shadows a non-trivial (multi-line)
699 # check if this command shadows a non-trivial (multi-line)
700 # extension help text
700 # extension help text
701 try:
701 try:
702 mod = extensions.find(name)
702 mod = extensions.find(name)
703 doc = gettext(pycompat.getdoc(mod)) or b''
703 doc = gettext(pycompat.getdoc(mod)) or b''
704 if b'\n' in doc.strip():
704 if b'\n' in doc.strip():
705 msg = _(
705 msg = _(
706 b"(use 'hg help -e %s' to show help for "
706 b"(use 'hg help -e %s' to show help for "
707 b"the %s extension)"
707 b"the %s extension)"
708 ) % (name, name)
708 ) % (name, name)
709 rst.append(b'\n%s\n' % msg)
709 rst.append(b'\n%s\n' % msg)
710 except KeyError:
710 except KeyError:
711 pass
711 pass
712
712
713 # options
713 # options
714 if not ui.quiet and entry[1]:
714 if not ui.quiet and entry[1]:
715 rst.append(optrst(_(b"options"), entry[1], ui.verbose))
715 rst.append(optrst(_(b"options"), entry[1], ui.verbose))
716
716
717 if ui.verbose:
717 if ui.verbose:
718 rst.append(
718 rst.append(
719 optrst(_(b"global options"), commands.globalopts, ui.verbose)
719 optrst(_(b"global options"), commands.globalopts, ui.verbose)
720 )
720 )
721
721
722 if not ui.verbose:
722 if not ui.verbose:
723 if not full:
723 if not full:
724 rst.append(_(b"\n(use 'hg %s -h' to show more help)\n") % name)
724 rst.append(_(b"\n(use 'hg %s -h' to show more help)\n") % name)
725 elif not ui.quiet:
725 elif not ui.quiet:
726 rst.append(
726 rst.append(
727 _(
727 _(
728 b'\n(some details hidden, use --verbose '
728 b'\n(some details hidden, use --verbose '
729 b'to show complete help)'
729 b'to show complete help)'
730 )
730 )
731 )
731 )
732
732
733 return rst
733 return rst
734
734
735 def helplist(select=None, **opts):
735 def helplist(select=None, **opts):
736 # Category -> list of commands
736 # Category -> list of commands
737 cats = {}
737 cats = {}
738 # Command -> short description
738 # Command -> short description
739 h = {}
739 h = {}
740 # Command -> string showing synonyms
740 # Command -> string showing synonyms
741 syns = {}
741 syns = {}
742 for c, e in pycompat.iteritems(commands.table):
742 for c, e in pycompat.iteritems(commands.table):
743 fs = cmdutil.parsealiases(c)
743 fs = cmdutil.parsealiases(c)
744 f = fs[0]
744 f = fs[0]
745 syns[f] = b', '.join(fs)
745 syns[f] = b', '.join(fs)
746 func = e[0]
746 func = e[0]
747 if select and not select(f):
747 if select and not select(f):
748 continue
748 continue
749 doc = pycompat.getdoc(func)
749 doc = pycompat.getdoc(func)
750 if filtercmd(ui, f, func, name, doc):
750 if filtercmd(ui, f, func, name, doc):
751 continue
751 continue
752 doc = gettext(doc)
752 doc = gettext(doc)
753 if not doc:
753 if not doc:
754 doc = _(b"(no help text available)")
754 doc = _(b"(no help text available)")
755 h[f] = doc.splitlines()[0].rstrip()
755 h[f] = doc.splitlines()[0].rstrip()
756
756
757 cat = getattr(func, 'helpcategory', None) or (
757 cat = getattr(func, 'helpcategory', None) or (
758 registrar.command.CATEGORY_NONE
758 registrar.command.CATEGORY_NONE
759 )
759 )
760 cats.setdefault(cat, []).append(f)
760 cats.setdefault(cat, []).append(f)
761
761
762 rst = []
762 rst = []
763 if not h:
763 if not h:
764 if not ui.quiet:
764 if not ui.quiet:
765 rst.append(_(b'no commands defined\n'))
765 rst.append(_(b'no commands defined\n'))
766 return rst
766 return rst
767
767
768 # Output top header.
768 # Output top header.
769 if not ui.quiet:
769 if not ui.quiet:
770 if name == b"shortlist":
770 if name == b"shortlist":
771 rst.append(_(b'basic commands:\n\n'))
771 rst.append(_(b'basic commands:\n\n'))
772 elif name == b"debug":
772 elif name == b"debug":
773 rst.append(_(b'debug commands (internal and unsupported):\n\n'))
773 rst.append(_(b'debug commands (internal and unsupported):\n\n'))
774 else:
774 else:
775 rst.append(_(b'list of commands:\n'))
775 rst.append(_(b'list of commands:\n'))
776
776
777 def appendcmds(cmds):
777 def appendcmds(cmds):
778 cmds = sorted(cmds)
778 cmds = sorted(cmds)
779 for c in cmds:
779 for c in cmds:
780 if ui.verbose:
780 if ui.verbose:
781 rst.append(b" :%s: %s\n" % (syns[c], h[c]))
781 rst.append(b" :%s: %s\n" % (syns[c], h[c]))
782 else:
782 else:
783 rst.append(b' :%s: %s\n' % (c, h[c]))
783 rst.append(b' :%s: %s\n' % (c, h[c]))
784
784
785 if name in (b'shortlist', b'debug'):
785 if name in (b'shortlist', b'debug'):
786 # List without categories.
786 # List without categories.
787 appendcmds(h)
787 appendcmds(h)
788 else:
788 else:
789 # Check that all categories have an order.
789 # Check that all categories have an order.
790 missing_order = set(cats.keys()) - set(CATEGORY_ORDER)
790 missing_order = set(cats.keys()) - set(CATEGORY_ORDER)
791 if missing_order:
791 if missing_order:
792 ui.develwarn(
792 ui.develwarn(
793 b'help categories missing from CATEGORY_ORDER: %s'
793 b'help categories missing from CATEGORY_ORDER: %s'
794 % missing_order
794 % missing_order
795 )
795 )
796
796
797 # List per category.
797 # List per category.
798 for cat in CATEGORY_ORDER:
798 for cat in CATEGORY_ORDER:
799 catfns = cats.get(cat, [])
799 catfns = cats.get(cat, [])
800 if catfns:
800 if catfns:
801 if len(cats) > 1:
801 if len(cats) > 1:
802 catname = gettext(CATEGORY_NAMES[cat])
802 catname = gettext(CATEGORY_NAMES[cat])
803 rst.append(b"\n%s:\n" % catname)
803 rst.append(b"\n%s:\n" % catname)
804 rst.append(b"\n")
804 rst.append(b"\n")
805 appendcmds(catfns)
805 appendcmds(catfns)
806
806
807 ex = opts.get
807 ex = opts.get
808 anyopts = ex('keyword') or not (ex('command') or ex('extension'))
808 anyopts = ex('keyword') or not (ex('command') or ex('extension'))
809 if not name and anyopts:
809 if not name and anyopts:
810 exts = listexts(
810 exts = listexts(
811 _(b'enabled extensions:'),
811 _(b'enabled extensions:'),
812 extensions.enabled(),
812 extensions.enabled(),
813 showdeprecated=ui.verbose,
813 showdeprecated=ui.verbose,
814 )
814 )
815 if exts:
815 if exts:
816 rst.append(b'\n')
816 rst.append(b'\n')
817 rst.extend(exts)
817 rst.extend(exts)
818
818
819 rst.append(_(b"\nadditional help topics:\n"))
819 rst.append(_(b"\nadditional help topics:\n"))
820 # Group commands by category.
820 # Group commands by category.
821 topiccats = {}
821 topiccats = {}
822 for topic in helptable:
822 for topic in helptable:
823 names, header, doc = topic[0:3]
823 names, header, doc = topic[0:3]
824 if len(topic) > 3 and topic[3]:
824 if len(topic) > 3 and topic[3]:
825 category = topic[3]
825 category = topic[3]
826 else:
826 else:
827 category = TOPIC_CATEGORY_NONE
827 category = TOPIC_CATEGORY_NONE
828
828
829 topicname = names[0]
829 topicname = names[0]
830 if not filtertopic(ui, topicname):
830 if not filtertopic(ui, topicname):
831 topiccats.setdefault(category, []).append(
831 topiccats.setdefault(category, []).append(
832 (topicname, header)
832 (topicname, header)
833 )
833 )
834
834
835 # Check that all categories have an order.
835 # Check that all categories have an order.
836 missing_order = set(topiccats.keys()) - set(TOPIC_CATEGORY_ORDER)
836 missing_order = set(topiccats.keys()) - set(TOPIC_CATEGORY_ORDER)
837 if missing_order:
837 if missing_order:
838 ui.develwarn(
838 ui.develwarn(
839 b'help categories missing from TOPIC_CATEGORY_ORDER: %s'
839 b'help categories missing from TOPIC_CATEGORY_ORDER: %s'
840 % missing_order
840 % missing_order
841 )
841 )
842
842
843 # Output topics per category.
843 # Output topics per category.
844 for cat in TOPIC_CATEGORY_ORDER:
844 for cat in TOPIC_CATEGORY_ORDER:
845 topics = topiccats.get(cat, [])
845 topics = topiccats.get(cat, [])
846 if topics:
846 if topics:
847 if len(topiccats) > 1:
847 if len(topiccats) > 1:
848 catname = gettext(TOPIC_CATEGORY_NAMES[cat])
848 catname = gettext(TOPIC_CATEGORY_NAMES[cat])
849 rst.append(b"\n%s:\n" % catname)
849 rst.append(b"\n%s:\n" % catname)
850 rst.append(b"\n")
850 rst.append(b"\n")
851 for t, desc in topics:
851 for t, desc in topics:
852 rst.append(b" :%s: %s\n" % (t, desc))
852 rst.append(b" :%s: %s\n" % (t, desc))
853
853
854 if ui.quiet:
854 if ui.quiet:
855 pass
855 pass
856 elif ui.verbose:
856 elif ui.verbose:
857 rst.append(
857 rst.append(
858 b'\n%s\n'
858 b'\n%s\n'
859 % optrst(_(b"global options"), commands.globalopts, ui.verbose)
859 % optrst(_(b"global options"), commands.globalopts, ui.verbose)
860 )
860 )
861 if name == b'shortlist':
861 if name == b'shortlist':
862 rst.append(
862 rst.append(
863 _(b"\n(use 'hg help' for the full list of commands)\n")
863 _(b"\n(use 'hg help' for the full list of commands)\n")
864 )
864 )
865 else:
865 else:
866 if name == b'shortlist':
866 if name == b'shortlist':
867 rst.append(
867 rst.append(
868 _(
868 _(
869 b"\n(use 'hg help' for the full list of commands "
869 b"\n(use 'hg help' for the full list of commands "
870 b"or 'hg -v' for details)\n"
870 b"or 'hg -v' for details)\n"
871 )
871 )
872 )
872 )
873 elif name and not full:
873 elif name and not full:
874 rst.append(
874 rst.append(
875 _(b"\n(use 'hg help %s' to show the full help text)\n")
875 _(b"\n(use 'hg help %s' to show the full help text)\n")
876 % name
876 % name
877 )
877 )
878 elif name and syns and name in syns.keys():
878 elif name and syns and name in syns.keys():
879 rst.append(
879 rst.append(
880 _(
880 _(
881 b"\n(use 'hg help -v -e %s' to show built-in "
881 b"\n(use 'hg help -v -e %s' to show built-in "
882 b"aliases and global options)\n"
882 b"aliases and global options)\n"
883 )
883 )
884 % name
884 % name
885 )
885 )
886 else:
886 else:
887 rst.append(
887 rst.append(
888 _(
888 _(
889 b"\n(use 'hg help -v%s' to show built-in aliases "
889 b"\n(use 'hg help -v%s' to show built-in aliases "
890 b"and global options)\n"
890 b"and global options)\n"
891 )
891 )
892 % (name and b" " + name or b"")
892 % (name and b" " + name or b"")
893 )
893 )
894 return rst
894 return rst
895
895
896 def helptopic(name, subtopic=None):
896 def helptopic(name, subtopic=None):
897 # Look for sub-topic entry first.
897 # Look for sub-topic entry first.
898 header, doc = None, None
898 header, doc = None, None
899 if subtopic and name in subtopics:
899 if subtopic and name in subtopics:
900 for names, header, doc in subtopics[name]:
900 for names, header, doc in subtopics[name]:
901 if subtopic in names:
901 if subtopic in names:
902 break
902 break
903 if not any(subtopic in s[0] for s in subtopics[name]):
903 if not any(subtopic in s[0] for s in subtopics[name]):
904 raise error.UnknownCommand(name)
904 raise error.UnknownCommand(name)
905
905
906 if not header:
906 if not header:
907 for topic in helptable:
907 for topic in helptable:
908 names, header, doc = topic[0:3]
908 names, header, doc = topic[0:3]
909 if name in names:
909 if name in names:
910 break
910 break
911 else:
911 else:
912 raise error.UnknownCommand(name)
912 raise error.UnknownCommand(name)
913
913
914 rst = [minirst.section(header)]
914 rst = [minirst.section(header)]
915
915
916 # description
916 # description
917 if not doc:
917 if not doc:
918 rst.append(b" %s\n" % _(b"(no help text available)"))
918 rst.append(b" %s\n" % _(b"(no help text available)"))
919 if callable(doc):
919 if callable(doc):
920 rst += [b" %s\n" % l for l in doc(ui).splitlines()]
920 rst += [b" %s\n" % l for l in doc(ui).splitlines()]
921
921
922 if not ui.verbose:
922 if not ui.verbose:
923 omitted = _(
923 omitted = _(
924 b'(some details hidden, use --verbose'
924 b'(some details hidden, use --verbose'
925 b' to show complete help)'
925 b' to show complete help)'
926 )
926 )
927 indicateomitted(rst, omitted)
927 indicateomitted(rst, omitted)
928
928
929 try:
929 try:
930 cmdutil.findcmd(name, commands.table)
930 cmdutil.findcmd(name, commands.table)
931 rst.append(
931 rst.append(
932 _(b"\nuse 'hg help -c %s' to see help for the %s command\n")
932 _(b"\nuse 'hg help -c %s' to see help for the %s command\n")
933 % (name, name)
933 % (name, name)
934 )
934 )
935 except error.UnknownCommand:
935 except error.UnknownCommand:
936 pass
936 pass
937 return rst
937 return rst
938
938
939 def helpext(name, subtopic=None):
939 def helpext(name, subtopic=None):
940 try:
940 try:
941 mod = extensions.find(name)
941 mod = extensions.find(name)
942 doc = gettext(pycompat.getdoc(mod)) or _(b'no help text available')
942 doc = gettext(pycompat.getdoc(mod)) or _(b'no help text available')
943 except KeyError:
943 except KeyError:
944 mod = None
944 mod = None
945 doc = extensions.disabledext(name)
945 doc = extensions.disabledext(name)
946 if not doc:
946 if not doc:
947 raise error.UnknownCommand(name)
947 raise error.UnknownCommand(name)
948
948
949 if b'\n' not in doc:
949 if b'\n' not in doc:
950 head, tail = doc, b""
950 head, tail = doc, b""
951 else:
951 else:
952 head, tail = doc.split(b'\n', 1)
952 head, tail = doc.split(b'\n', 1)
953 rst = [_(b'%s extension - %s\n\n') % (name.rpartition(b'.')[-1], head)]
953 rst = [_(b'%s extension - %s\n\n') % (name.rpartition(b'.')[-1], head)]
954 if tail:
954 if tail:
955 rst.extend(tail.splitlines(True))
955 rst.extend(tail.splitlines(True))
956 rst.append(b'\n')
956 rst.append(b'\n')
957
957
958 if not ui.verbose:
958 if not ui.verbose:
959 omitted = _(
959 omitted = _(
960 b'(some details hidden, use --verbose'
960 b'(some details hidden, use --verbose'
961 b' to show complete help)'
961 b' to show complete help)'
962 )
962 )
963 indicateomitted(rst, omitted)
963 indicateomitted(rst, omitted)
964
964
965 if mod:
965 if mod:
966 try:
966 try:
967 ct = mod.cmdtable
967 ct = mod.cmdtable
968 except AttributeError:
968 except AttributeError:
969 ct = {}
969 ct = {}
970 modcmds = {c.partition(b'|')[0] for c in ct}
970 modcmds = {c.partition(b'|')[0] for c in ct}
971 rst.extend(helplist(modcmds.__contains__))
971 rst.extend(helplist(modcmds.__contains__))
972 else:
972 else:
973 rst.append(
973 rst.append(
974 _(
974 _(
975 b"(use 'hg help extensions' for information on enabling"
975 b"(use 'hg help extensions' for information on enabling"
976 b" extensions)\n"
976 b" extensions)\n"
977 )
977 )
978 )
978 )
979 return rst
979 return rst
980
980
981 def helpextcmd(name, subtopic=None):
981 def helpextcmd(name, subtopic=None):
982 cmd, ext, doc = extensions.disabledcmd(
982 cmd, ext, doc = extensions.disabledcmd(
983 ui, name, ui.configbool(b'ui', b'strict')
983 ui, name, ui.configbool(b'ui', b'strict')
984 )
984 )
985 doc = doc.splitlines()[0]
985 doc = doc.splitlines()[0]
986
986
987 rst = listexts(
987 rst = listexts(
988 _(b"'%s' is provided by the following extension:") % cmd,
988 _(b"'%s' is provided by the following extension:") % cmd,
989 {ext: doc},
989 {ext: doc},
990 indent=4,
990 indent=4,
991 showdeprecated=True,
991 showdeprecated=True,
992 )
992 )
993 rst.append(b'\n')
993 rst.append(b'\n')
994 rst.append(
994 rst.append(
995 _(
995 _(
996 b"(use 'hg help extensions' for information on enabling "
996 b"(use 'hg help extensions' for information on enabling "
997 b"extensions)\n"
997 b"extensions)\n"
998 )
998 )
999 )
999 )
1000 return rst
1000 return rst
1001
1001
1002 rst = []
1002 rst = []
1003 kw = opts.get(b'keyword')
1003 kw = opts.get(b'keyword')
1004 if kw or name is None and any(opts[o] for o in opts):
1004 if kw or name is None and any(opts[o] for o in opts):
1005 matches = topicmatch(ui, commands, name or b'')
1005 matches = topicmatch(ui, commands, name or b'')
1006 helpareas = []
1006 helpareas = []
1007 if opts.get(b'extension'):
1007 if opts.get(b'extension'):
1008 helpareas += [(b'extensions', _(b'Extensions'))]
1008 helpareas += [(b'extensions', _(b'Extensions'))]
1009 if opts.get(b'command'):
1009 if opts.get(b'command'):
1010 helpareas += [(b'commands', _(b'Commands'))]
1010 helpareas += [(b'commands', _(b'Commands'))]
1011 if not helpareas:
1011 if not helpareas:
1012 helpareas = [
1012 helpareas = [
1013 (b'topics', _(b'Topics')),
1013 (b'topics', _(b'Topics')),
1014 (b'commands', _(b'Commands')),
1014 (b'commands', _(b'Commands')),
1015 (b'extensions', _(b'Extensions')),
1015 (b'extensions', _(b'Extensions')),
1016 (b'extensioncommands', _(b'Extension Commands')),
1016 (b'extensioncommands', _(b'Extension Commands')),
1017 ]
1017 ]
1018 for t, title in helpareas:
1018 for t, title in helpareas:
1019 if matches[t]:
1019 if matches[t]:
1020 rst.append(b'%s:\n\n' % title)
1020 rst.append(b'%s:\n\n' % title)
1021 rst.extend(minirst.maketable(sorted(matches[t]), 1))
1021 rst.extend(minirst.maketable(sorted(matches[t]), 1))
1022 rst.append(b'\n')
1022 rst.append(b'\n')
1023 if not rst:
1023 if not rst:
1024 msg = _(b'no matches')
1024 msg = _(b'no matches')
1025 hint = _(b"try 'hg help' for a list of topics")
1025 hint = _(b"try 'hg help' for a list of topics")
1026 raise error.Abort(msg, hint=hint)
1026 raise error.Abort(msg, hint=hint)
1027 elif name and name != b'shortlist':
1027 elif name and name != b'shortlist':
1028 queries = []
1028 queries = []
1029 if unknowncmd:
1029 if unknowncmd:
1030 queries += [helpextcmd]
1030 queries += [helpextcmd]
1031 if opts.get(b'extension'):
1031 if opts.get(b'extension'):
1032 queries += [helpext]
1032 queries += [helpext]
1033 if opts.get(b'command'):
1033 if opts.get(b'command'):
1034 queries += [helpcmd]
1034 queries += [helpcmd]
1035 if not queries:
1035 if not queries:
1036 queries = (helptopic, helpcmd, helpext, helpextcmd)
1036 queries = (helptopic, helpcmd, helpext, helpextcmd)
1037 for f in queries:
1037 for f in queries:
1038 try:
1038 try:
1039 rst = f(name, subtopic)
1039 rst = f(name, subtopic)
1040 break
1040 break
1041 except error.UnknownCommand:
1041 except error.UnknownCommand:
1042 pass
1042 pass
1043 else:
1043 else:
1044 if unknowncmd:
1044 if unknowncmd:
1045 raise error.UnknownCommand(name)
1045 raise error.UnknownCommand(name)
1046 else:
1046 else:
1047 if fullname:
1047 if fullname:
1048 formatname = fullname
1048 formatname = fullname
1049 else:
1049 else:
1050 formatname = name
1050 formatname = name
1051 if subtopic:
1051 if subtopic:
1052 hintname = subtopic
1052 hintname = subtopic
1053 else:
1053 else:
1054 hintname = name
1054 hintname = name
1055 msg = _(b'no such help topic: %s') % formatname
1055 msg = _(b'no such help topic: %s') % formatname
1056 hint = _(b"try 'hg help --keyword %s'") % hintname
1056 hint = _(b"try 'hg help --keyword %s'") % hintname
1057 raise error.Abort(msg, hint=hint)
1057 raise error.Abort(msg, hint=hint)
1058 else:
1058 else:
1059 # program name
1059 # program name
1060 if not ui.quiet:
1060 if not ui.quiet:
1061 rst = [_(b"Mercurial Distributed SCM\n"), b'\n']
1061 rst = [_(b"Mercurial Distributed SCM\n"), b'\n']
1062 rst.extend(helplist(None, **pycompat.strkwargs(opts)))
1062 rst.extend(helplist(None, **pycompat.strkwargs(opts)))
1063
1063
1064 return b''.join(rst)
1064 return b''.join(rst)
1065
1065
1066
1066
1067 def formattedhelp(
1067 def formattedhelp(
1068 ui, commands, fullname, keep=None, unknowncmd=False, full=True, **opts
1068 ui, commands, fullname, keep=None, unknowncmd=False, full=True, **opts
1069 ):
1069 ):
1070 """get help for a given topic (as a dotted name) as rendered rst
1070 """get help for a given topic (as a dotted name) as rendered rst
1071
1071
1072 Either returns the rendered help text or raises an exception.
1072 Either returns the rendered help text or raises an exception.
1073 """
1073 """
1074 if keep is None:
1074 if keep is None:
1075 keep = []
1075 keep = []
1076 else:
1076 else:
1077 keep = list(keep) # make a copy so we can mutate this later
1077 keep = list(keep) # make a copy so we can mutate this later
1078
1078
1079 # <fullname> := <name>[.<subtopic][.<section>]
1079 # <fullname> := <name>[.<subtopic][.<section>]
1080 name = subtopic = section = None
1080 name = subtopic = section = None
1081 if fullname is not None:
1081 if fullname is not None:
1082 nameparts = fullname.split(b'.')
1082 nameparts = fullname.split(b'.')
1083 name = nameparts.pop(0)
1083 name = nameparts.pop(0)
1084 if nameparts and name in subtopics:
1084 if nameparts and name in subtopics:
1085 subtopic = nameparts.pop(0)
1085 subtopic = nameparts.pop(0)
1086 if nameparts:
1086 if nameparts:
1087 section = encoding.lower(b'.'.join(nameparts))
1087 section = encoding.lower(b'.'.join(nameparts))
1088
1088
1089 textwidth = ui.configint(b'ui', b'textwidth')
1089 textwidth = ui.configint(b'ui', b'textwidth')
1090 termwidth = ui.termwidth() - 2
1090 termwidth = ui.termwidth() - 2
1091 if textwidth <= 0 or termwidth < textwidth:
1091 if textwidth <= 0 or termwidth < textwidth:
1092 textwidth = termwidth
1092 textwidth = termwidth
1093 text = help_(
1093 text = help_(
1094 ui,
1094 ui,
1095 commands,
1095 commands,
1096 name,
1096 name,
1097 fullname=fullname,
1097 fullname=fullname,
1098 subtopic=subtopic,
1098 subtopic=subtopic,
1099 unknowncmd=unknowncmd,
1099 unknowncmd=unknowncmd,
1100 full=full,
1100 full=full,
1101 **opts
1101 **opts
1102 )
1102 )
1103
1103
1104 blocks, pruned = minirst.parse(text, keep=keep)
1104 blocks, pruned = minirst.parse(text, keep=keep)
1105 if b'verbose' in pruned:
1105 if b'verbose' in pruned:
1106 keep.append(b'omitted')
1106 keep.append(b'omitted')
1107 else:
1107 else:
1108 keep.append(b'notomitted')
1108 keep.append(b'notomitted')
1109 blocks, pruned = minirst.parse(text, keep=keep)
1109 blocks, pruned = minirst.parse(text, keep=keep)
1110 if section:
1110 if section:
1111 blocks = minirst.filtersections(blocks, section)
1111 blocks = minirst.filtersections(blocks, section)
1112
1112
1113 # We could have been given a weird ".foo" section without a name
1113 # We could have been given a weird ".foo" section without a name
1114 # to look for, or we could have simply failed to found "foo.bar"
1114 # to look for, or we could have simply failed to found "foo.bar"
1115 # because bar isn't a section of foo
1115 # because bar isn't a section of foo
1116 if section and not (blocks and name):
1116 if section and not (blocks and name):
1117 raise error.Abort(_(b"help section not found: %s") % fullname)
1117 raise error.Abort(_(b"help section not found: %s") % fullname)
1118
1118
1119 return minirst.formatplain(blocks, textwidth)
1119 return minirst.formatplain(blocks, textwidth)
1 NO CONTENT: file renamed from mercurial/help/bundlespec.txt to mercurial/helptext/bundlespec.txt
NO CONTENT: file renamed from mercurial/help/bundlespec.txt to mercurial/helptext/bundlespec.txt
1 NO CONTENT: file renamed from mercurial/help/color.txt to mercurial/helptext/color.txt
NO CONTENT: file renamed from mercurial/help/color.txt to mercurial/helptext/color.txt
1 NO CONTENT: file renamed from mercurial/help/common.txt to mercurial/helptext/common.txt
NO CONTENT: file renamed from mercurial/help/common.txt to mercurial/helptext/common.txt
1 NO CONTENT: file renamed from mercurial/help/config.txt to mercurial/helptext/config.txt
NO CONTENT: file renamed from mercurial/help/config.txt to mercurial/helptext/config.txt
1 NO CONTENT: file renamed from mercurial/help/dates.txt to mercurial/helptext/dates.txt
NO CONTENT: file renamed from mercurial/help/dates.txt to mercurial/helptext/dates.txt
1 NO CONTENT: file renamed from mercurial/help/deprecated.txt to mercurial/helptext/deprecated.txt
NO CONTENT: file renamed from mercurial/help/deprecated.txt to mercurial/helptext/deprecated.txt
1 NO CONTENT: file renamed from mercurial/help/diffs.txt to mercurial/helptext/diffs.txt
NO CONTENT: file renamed from mercurial/help/diffs.txt to mercurial/helptext/diffs.txt
1 NO CONTENT: file renamed from mercurial/help/environment.txt to mercurial/helptext/environment.txt
NO CONTENT: file renamed from mercurial/help/environment.txt to mercurial/helptext/environment.txt
1 NO CONTENT: file renamed from mercurial/help/extensions.txt to mercurial/helptext/extensions.txt
NO CONTENT: file renamed from mercurial/help/extensions.txt to mercurial/helptext/extensions.txt
1 NO CONTENT: file renamed from mercurial/help/filesets.txt to mercurial/helptext/filesets.txt
NO CONTENT: file renamed from mercurial/help/filesets.txt to mercurial/helptext/filesets.txt
1 NO CONTENT: file renamed from mercurial/help/flags.txt to mercurial/helptext/flags.txt
NO CONTENT: file renamed from mercurial/help/flags.txt to mercurial/helptext/flags.txt
1 NO CONTENT: file renamed from mercurial/help/glossary.txt to mercurial/helptext/glossary.txt
NO CONTENT: file renamed from mercurial/help/glossary.txt to mercurial/helptext/glossary.txt
1 NO CONTENT: file renamed from mercurial/help/hg-ssh.8.txt to mercurial/helptext/hg-ssh.8.txt
NO CONTENT: file renamed from mercurial/help/hg-ssh.8.txt to mercurial/helptext/hg-ssh.8.txt
1 NO CONTENT: file renamed from mercurial/help/hg.1.txt to mercurial/helptext/hg.1.txt
NO CONTENT: file renamed from mercurial/help/hg.1.txt to mercurial/helptext/hg.1.txt
1 NO CONTENT: file renamed from mercurial/help/hgignore.5.txt to mercurial/helptext/hgignore.5.txt
NO CONTENT: file renamed from mercurial/help/hgignore.5.txt to mercurial/helptext/hgignore.5.txt
1 NO CONTENT: file renamed from mercurial/help/hgignore.txt to mercurial/helptext/hgignore.txt
NO CONTENT: file renamed from mercurial/help/hgignore.txt to mercurial/helptext/hgignore.txt
1 NO CONTENT: file renamed from mercurial/help/hgrc.5.txt to mercurial/helptext/hgrc.5.txt
NO CONTENT: file renamed from mercurial/help/hgrc.5.txt to mercurial/helptext/hgrc.5.txt
1 NO CONTENT: file renamed from mercurial/help/hgweb.txt to mercurial/helptext/hgweb.txt
NO CONTENT: file renamed from mercurial/help/hgweb.txt to mercurial/helptext/hgweb.txt
1 NO CONTENT: file renamed from mercurial/help/internals/bundle2.txt to mercurial/helptext/internals/bundle2.txt
NO CONTENT: file renamed from mercurial/help/internals/bundle2.txt to mercurial/helptext/internals/bundle2.txt
1 NO CONTENT: file renamed from mercurial/help/internals/bundles.txt to mercurial/helptext/internals/bundles.txt
NO CONTENT: file renamed from mercurial/help/internals/bundles.txt to mercurial/helptext/internals/bundles.txt
1 NO CONTENT: file renamed from mercurial/help/internals/cbor.txt to mercurial/helptext/internals/cbor.txt
NO CONTENT: file renamed from mercurial/help/internals/cbor.txt to mercurial/helptext/internals/cbor.txt
1 NO CONTENT: file renamed from mercurial/help/internals/censor.txt to mercurial/helptext/internals/censor.txt
NO CONTENT: file renamed from mercurial/help/internals/censor.txt to mercurial/helptext/internals/censor.txt
1 NO CONTENT: file renamed from mercurial/help/internals/changegroups.txt to mercurial/helptext/internals/changegroups.txt
NO CONTENT: file renamed from mercurial/help/internals/changegroups.txt to mercurial/helptext/internals/changegroups.txt
1 NO CONTENT: file renamed from mercurial/help/internals/config.txt to mercurial/helptext/internals/config.txt
NO CONTENT: file renamed from mercurial/help/internals/config.txt to mercurial/helptext/internals/config.txt
1 NO CONTENT: file renamed from mercurial/help/internals/extensions.txt to mercurial/helptext/internals/extensions.txt
NO CONTENT: file renamed from mercurial/help/internals/extensions.txt to mercurial/helptext/internals/extensions.txt
1 NO CONTENT: file renamed from mercurial/help/internals/linelog.txt to mercurial/helptext/internals/linelog.txt
NO CONTENT: file renamed from mercurial/help/internals/linelog.txt to mercurial/helptext/internals/linelog.txt
1 NO CONTENT: file renamed from mercurial/help/internals/mergestate.txt to mercurial/helptext/internals/mergestate.txt
NO CONTENT: file renamed from mercurial/help/internals/mergestate.txt to mercurial/helptext/internals/mergestate.txt
1 NO CONTENT: file renamed from mercurial/help/internals/requirements.txt to mercurial/helptext/internals/requirements.txt
NO CONTENT: file renamed from mercurial/help/internals/requirements.txt to mercurial/helptext/internals/requirements.txt
1 NO CONTENT: file renamed from mercurial/help/internals/revlogs.txt to mercurial/helptext/internals/revlogs.txt
NO CONTENT: file renamed from mercurial/help/internals/revlogs.txt to mercurial/helptext/internals/revlogs.txt
1 NO CONTENT: file renamed from mercurial/help/internals/wireprotocol.txt to mercurial/helptext/internals/wireprotocol.txt
NO CONTENT: file renamed from mercurial/help/internals/wireprotocol.txt to mercurial/helptext/internals/wireprotocol.txt
1 NO CONTENT: file renamed from mercurial/help/internals/wireprotocolrpc.txt to mercurial/helptext/internals/wireprotocolrpc.txt
NO CONTENT: file renamed from mercurial/help/internals/wireprotocolrpc.txt to mercurial/helptext/internals/wireprotocolrpc.txt
1 NO CONTENT: file renamed from mercurial/help/internals/wireprotocolv2.txt to mercurial/helptext/internals/wireprotocolv2.txt
NO CONTENT: file renamed from mercurial/help/internals/wireprotocolv2.txt to mercurial/helptext/internals/wireprotocolv2.txt
1 NO CONTENT: file renamed from mercurial/help/merge-tools.txt to mercurial/helptext/merge-tools.txt
NO CONTENT: file renamed from mercurial/help/merge-tools.txt to mercurial/helptext/merge-tools.txt
1 NO CONTENT: file renamed from mercurial/help/pager.txt to mercurial/helptext/pager.txt
NO CONTENT: file renamed from mercurial/help/pager.txt to mercurial/helptext/pager.txt
1 NO CONTENT: file renamed from mercurial/help/patterns.txt to mercurial/helptext/patterns.txt
NO CONTENT: file renamed from mercurial/help/patterns.txt to mercurial/helptext/patterns.txt
1 NO CONTENT: file renamed from mercurial/help/phases.txt to mercurial/helptext/phases.txt
NO CONTENT: file renamed from mercurial/help/phases.txt to mercurial/helptext/phases.txt
1 NO CONTENT: file renamed from mercurial/help/revisions.txt to mercurial/helptext/revisions.txt
NO CONTENT: file renamed from mercurial/help/revisions.txt to mercurial/helptext/revisions.txt
1 NO CONTENT: file renamed from mercurial/help/scripting.txt to mercurial/helptext/scripting.txt
NO CONTENT: file renamed from mercurial/help/scripting.txt to mercurial/helptext/scripting.txt
1 NO CONTENT: file renamed from mercurial/help/subrepos.txt to mercurial/helptext/subrepos.txt
NO CONTENT: file renamed from mercurial/help/subrepos.txt to mercurial/helptext/subrepos.txt
1 NO CONTENT: file renamed from mercurial/help/templates.txt to mercurial/helptext/templates.txt
NO CONTENT: file renamed from mercurial/help/templates.txt to mercurial/helptext/templates.txt
1 NO CONTENT: file renamed from mercurial/help/urls.txt to mercurial/helptext/urls.txt
NO CONTENT: file renamed from mercurial/help/urls.txt to mercurial/helptext/urls.txt
@@ -1,1716 +1,1718 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 os
7 import os
8
8
9 # Mercurial will never work on Python 3 before 3.5 due to a lack
9 # Mercurial will never work on Python 3 before 3.5 due to a lack
10 # of % formatting on bytestrings, and can't work on 3.6.0 or 3.6.1
10 # of % formatting on bytestrings, and can't work on 3.6.0 or 3.6.1
11 # due to a bug in % formatting in bytestrings.
11 # due to a bug in % formatting in bytestrings.
12 # We cannot support Python 3.5.0, 3.5.1, 3.5.2 because of bug in
12 # We cannot support Python 3.5.0, 3.5.1, 3.5.2 because of bug in
13 # codecs.escape_encode() where it raises SystemError on empty bytestring
13 # codecs.escape_encode() where it raises SystemError on empty bytestring
14 # bug link: https://bugs.python.org/issue25270
14 # bug link: https://bugs.python.org/issue25270
15 supportedpy = ','.join(
15 supportedpy = ','.join(
16 [
16 [
17 '>=2.7',
17 '>=2.7',
18 '!=3.0.*',
18 '!=3.0.*',
19 '!=3.1.*',
19 '!=3.1.*',
20 '!=3.2.*',
20 '!=3.2.*',
21 '!=3.3.*',
21 '!=3.3.*',
22 '!=3.4.*',
22 '!=3.4.*',
23 '!=3.5.0',
23 '!=3.5.0',
24 '!=3.5.1',
24 '!=3.5.1',
25 '!=3.5.2',
25 '!=3.5.2',
26 '!=3.6.0',
26 '!=3.6.0',
27 '!=3.6.1',
27 '!=3.6.1',
28 ]
28 ]
29 )
29 )
30
30
31 import sys, platform
31 import sys, platform
32 import sysconfig
32 import sysconfig
33
33
34 if sys.version_info[0] >= 3:
34 if sys.version_info[0] >= 3:
35 printf = eval('print')
35 printf = eval('print')
36 libdir_escape = 'unicode_escape'
36 libdir_escape = 'unicode_escape'
37
37
38 def sysstr(s):
38 def sysstr(s):
39 return s.decode('latin-1')
39 return s.decode('latin-1')
40
40
41
41
42 else:
42 else:
43 libdir_escape = 'string_escape'
43 libdir_escape = 'string_escape'
44
44
45 def printf(*args, **kwargs):
45 def printf(*args, **kwargs):
46 f = kwargs.get('file', sys.stdout)
46 f = kwargs.get('file', sys.stdout)
47 end = kwargs.get('end', '\n')
47 end = kwargs.get('end', '\n')
48 f.write(b' '.join(args) + end)
48 f.write(b' '.join(args) + end)
49
49
50 def sysstr(s):
50 def sysstr(s):
51 return s
51 return s
52
52
53
53
54 # Attempt to guide users to a modern pip - this means that 2.6 users
54 # Attempt to guide users to a modern pip - this means that 2.6 users
55 # should have a chance of getting a 4.2 release, and when we ratchet
55 # should have a chance of getting a 4.2 release, and when we ratchet
56 # the version requirement forward again hopefully everyone will get
56 # the version requirement forward again hopefully everyone will get
57 # something that works for them.
57 # something that works for them.
58 if sys.version_info < (2, 7, 0, 'final'):
58 if sys.version_info < (2, 7, 0, 'final'):
59 pip_message = (
59 pip_message = (
60 'This may be due to an out of date pip. '
60 'This may be due to an out of date pip. '
61 'Make sure you have pip >= 9.0.1.'
61 'Make sure you have pip >= 9.0.1.'
62 )
62 )
63 try:
63 try:
64 import pip
64 import pip
65
65
66 pip_version = tuple([int(x) for x in pip.__version__.split('.')[:3]])
66 pip_version = tuple([int(x) for x in pip.__version__.split('.')[:3]])
67 if pip_version < (9, 0, 1):
67 if pip_version < (9, 0, 1):
68 pip_message = (
68 pip_message = (
69 'Your pip version is out of date, please install '
69 'Your pip version is out of date, please install '
70 'pip >= 9.0.1. pip {} detected.'.format(pip.__version__)
70 'pip >= 9.0.1. pip {} detected.'.format(pip.__version__)
71 )
71 )
72 else:
72 else:
73 # pip is new enough - it must be something else
73 # pip is new enough - it must be something else
74 pip_message = ''
74 pip_message = ''
75 except Exception:
75 except Exception:
76 pass
76 pass
77 error = """
77 error = """
78 Mercurial does not support Python older than 2.7.
78 Mercurial does not support Python older than 2.7.
79 Python {py} detected.
79 Python {py} detected.
80 {pip}
80 {pip}
81 """.format(
81 """.format(
82 py=sys.version_info, pip=pip_message
82 py=sys.version_info, pip=pip_message
83 )
83 )
84 printf(error, file=sys.stderr)
84 printf(error, file=sys.stderr)
85 sys.exit(1)
85 sys.exit(1)
86
86
87 if sys.version_info[0] >= 3:
87 if sys.version_info[0] >= 3:
88 DYLIB_SUFFIX = sysconfig.get_config_vars()['EXT_SUFFIX']
88 DYLIB_SUFFIX = sysconfig.get_config_vars()['EXT_SUFFIX']
89 else:
89 else:
90 # deprecated in Python 3
90 # deprecated in Python 3
91 DYLIB_SUFFIX = sysconfig.get_config_vars()['SO']
91 DYLIB_SUFFIX = sysconfig.get_config_vars()['SO']
92
92
93 # Solaris Python packaging brain damage
93 # Solaris Python packaging brain damage
94 try:
94 try:
95 import hashlib
95 import hashlib
96
96
97 sha = hashlib.sha1()
97 sha = hashlib.sha1()
98 except ImportError:
98 except ImportError:
99 try:
99 try:
100 import sha
100 import sha
101
101
102 sha.sha # silence unused import warning
102 sha.sha # silence unused import warning
103 except ImportError:
103 except ImportError:
104 raise SystemExit(
104 raise SystemExit(
105 "Couldn't import standard hashlib (incomplete Python install)."
105 "Couldn't import standard hashlib (incomplete Python install)."
106 )
106 )
107
107
108 try:
108 try:
109 import zlib
109 import zlib
110
110
111 zlib.compressobj # silence unused import warning
111 zlib.compressobj # silence unused import warning
112 except ImportError:
112 except ImportError:
113 raise SystemExit(
113 raise SystemExit(
114 "Couldn't import standard zlib (incomplete Python install)."
114 "Couldn't import standard zlib (incomplete Python install)."
115 )
115 )
116
116
117 # The base IronPython distribution (as of 2.7.1) doesn't support bz2
117 # The base IronPython distribution (as of 2.7.1) doesn't support bz2
118 isironpython = False
118 isironpython = False
119 try:
119 try:
120 isironpython = (
120 isironpython = (
121 platform.python_implementation().lower().find("ironpython") != -1
121 platform.python_implementation().lower().find("ironpython") != -1
122 )
122 )
123 except AttributeError:
123 except AttributeError:
124 pass
124 pass
125
125
126 if isironpython:
126 if isironpython:
127 sys.stderr.write("warning: IronPython detected (no bz2 support)\n")
127 sys.stderr.write("warning: IronPython detected (no bz2 support)\n")
128 else:
128 else:
129 try:
129 try:
130 import bz2
130 import bz2
131
131
132 bz2.BZ2Compressor # silence unused import warning
132 bz2.BZ2Compressor # silence unused import warning
133 except ImportError:
133 except ImportError:
134 raise SystemExit(
134 raise SystemExit(
135 "Couldn't import standard bz2 (incomplete Python install)."
135 "Couldn't import standard bz2 (incomplete Python install)."
136 )
136 )
137
137
138 ispypy = "PyPy" in sys.version
138 ispypy = "PyPy" in sys.version
139
139
140 hgrustext = os.environ.get('HGWITHRUSTEXT')
140 hgrustext = os.environ.get('HGWITHRUSTEXT')
141 # TODO record it for proper rebuild upon changes
141 # TODO record it for proper rebuild upon changes
142 # (see mercurial/__modulepolicy__.py)
142 # (see mercurial/__modulepolicy__.py)
143 if hgrustext != 'cpython' and hgrustext is not None:
143 if hgrustext != 'cpython' and hgrustext is not None:
144 hgrustext = 'direct-ffi'
144 hgrustext = 'direct-ffi'
145
145
146 import ctypes
146 import ctypes
147 import errno
147 import errno
148 import stat, subprocess, time
148 import stat, subprocess, time
149 import re
149 import re
150 import shutil
150 import shutil
151 import tempfile
151 import tempfile
152 from distutils import log
152 from distutils import log
153
153
154 # We have issues with setuptools on some platforms and builders. Until
154 # We have issues with setuptools on some platforms and builders. Until
155 # those are resolved, setuptools is opt-in except for platforms where
155 # those are resolved, setuptools is opt-in except for platforms where
156 # we don't have issues.
156 # we don't have issues.
157 issetuptools = os.name == 'nt' or 'FORCE_SETUPTOOLS' in os.environ
157 issetuptools = os.name == 'nt' or 'FORCE_SETUPTOOLS' in os.environ
158 if issetuptools:
158 if issetuptools:
159 from setuptools import setup
159 from setuptools import setup
160 else:
160 else:
161 from distutils.core import setup
161 from distutils.core import setup
162 from distutils.ccompiler import new_compiler
162 from distutils.ccompiler import new_compiler
163 from distutils.core import Command, Extension
163 from distutils.core import Command, Extension
164 from distutils.dist import Distribution
164 from distutils.dist import Distribution
165 from distutils.command.build import build
165 from distutils.command.build import build
166 from distutils.command.build_ext import build_ext
166 from distutils.command.build_ext import build_ext
167 from distutils.command.build_py import build_py
167 from distutils.command.build_py import build_py
168 from distutils.command.build_scripts import build_scripts
168 from distutils.command.build_scripts import build_scripts
169 from distutils.command.install import install
169 from distutils.command.install import install
170 from distutils.command.install_lib import install_lib
170 from distutils.command.install_lib import install_lib
171 from distutils.command.install_scripts import install_scripts
171 from distutils.command.install_scripts import install_scripts
172 from distutils.spawn import spawn, find_executable
172 from distutils.spawn import spawn, find_executable
173 from distutils import file_util
173 from distutils import file_util
174 from distutils.errors import (
174 from distutils.errors import (
175 CCompilerError,
175 CCompilerError,
176 DistutilsError,
176 DistutilsError,
177 DistutilsExecError,
177 DistutilsExecError,
178 )
178 )
179 from distutils.sysconfig import get_python_inc, get_config_var
179 from distutils.sysconfig import get_python_inc, get_config_var
180 from distutils.version import StrictVersion
180 from distutils.version import StrictVersion
181
181
182 # Explain to distutils.StrictVersion how our release candidates are versionned
182 # Explain to distutils.StrictVersion how our release candidates are versionned
183 StrictVersion.version_re = re.compile(r'^(\d+)\.(\d+)(\.(\d+))?-?(rc(\d+))?$')
183 StrictVersion.version_re = re.compile(r'^(\d+)\.(\d+)(\.(\d+))?-?(rc(\d+))?$')
184
184
185
185
186 def write_if_changed(path, content):
186 def write_if_changed(path, content):
187 """Write content to a file iff the content hasn't changed."""
187 """Write content to a file iff the content hasn't changed."""
188 if os.path.exists(path):
188 if os.path.exists(path):
189 with open(path, 'rb') as fh:
189 with open(path, 'rb') as fh:
190 current = fh.read()
190 current = fh.read()
191 else:
191 else:
192 current = b''
192 current = b''
193
193
194 if current != content:
194 if current != content:
195 with open(path, 'wb') as fh:
195 with open(path, 'wb') as fh:
196 fh.write(content)
196 fh.write(content)
197
197
198
198
199 scripts = ['hg']
199 scripts = ['hg']
200 if os.name == 'nt':
200 if os.name == 'nt':
201 # We remove hg.bat if we are able to build hg.exe.
201 # We remove hg.bat if we are able to build hg.exe.
202 scripts.append('contrib/win32/hg.bat')
202 scripts.append('contrib/win32/hg.bat')
203
203
204
204
205 def cancompile(cc, code):
205 def cancompile(cc, code):
206 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
206 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
207 devnull = oldstderr = None
207 devnull = oldstderr = None
208 try:
208 try:
209 fname = os.path.join(tmpdir, 'testcomp.c')
209 fname = os.path.join(tmpdir, 'testcomp.c')
210 f = open(fname, 'w')
210 f = open(fname, 'w')
211 f.write(code)
211 f.write(code)
212 f.close()
212 f.close()
213 # Redirect stderr to /dev/null to hide any error messages
213 # Redirect stderr to /dev/null to hide any error messages
214 # from the compiler.
214 # from the compiler.
215 # This will have to be changed if we ever have to check
215 # This will have to be changed if we ever have to check
216 # for a function on Windows.
216 # for a function on Windows.
217 devnull = open('/dev/null', 'w')
217 devnull = open('/dev/null', 'w')
218 oldstderr = os.dup(sys.stderr.fileno())
218 oldstderr = os.dup(sys.stderr.fileno())
219 os.dup2(devnull.fileno(), sys.stderr.fileno())
219 os.dup2(devnull.fileno(), sys.stderr.fileno())
220 objects = cc.compile([fname], output_dir=tmpdir)
220 objects = cc.compile([fname], output_dir=tmpdir)
221 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
221 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
222 return True
222 return True
223 except Exception:
223 except Exception:
224 return False
224 return False
225 finally:
225 finally:
226 if oldstderr is not None:
226 if oldstderr is not None:
227 os.dup2(oldstderr, sys.stderr.fileno())
227 os.dup2(oldstderr, sys.stderr.fileno())
228 if devnull is not None:
228 if devnull is not None:
229 devnull.close()
229 devnull.close()
230 shutil.rmtree(tmpdir)
230 shutil.rmtree(tmpdir)
231
231
232
232
233 # simplified version of distutils.ccompiler.CCompiler.has_function
233 # simplified version of distutils.ccompiler.CCompiler.has_function
234 # that actually removes its temporary files.
234 # that actually removes its temporary files.
235 def hasfunction(cc, funcname):
235 def hasfunction(cc, funcname):
236 code = 'int main(void) { %s(); }\n' % funcname
236 code = 'int main(void) { %s(); }\n' % funcname
237 return cancompile(cc, code)
237 return cancompile(cc, code)
238
238
239
239
240 def hasheader(cc, headername):
240 def hasheader(cc, headername):
241 code = '#include <%s>\nint main(void) { return 0; }\n' % headername
241 code = '#include <%s>\nint main(void) { return 0; }\n' % headername
242 return cancompile(cc, code)
242 return cancompile(cc, code)
243
243
244
244
245 # py2exe needs to be installed to work
245 # py2exe needs to be installed to work
246 try:
246 try:
247 import py2exe
247 import py2exe
248
248
249 py2exe.Distribution # silence unused import warning
249 py2exe.Distribution # silence unused import warning
250 py2exeloaded = True
250 py2exeloaded = True
251 # import py2exe's patched Distribution class
251 # import py2exe's patched Distribution class
252 from distutils.core import Distribution
252 from distutils.core import Distribution
253 except ImportError:
253 except ImportError:
254 py2exeloaded = False
254 py2exeloaded = False
255
255
256
256
257 def runcmd(cmd, env, cwd=None):
257 def runcmd(cmd, env, cwd=None):
258 p = subprocess.Popen(
258 p = subprocess.Popen(
259 cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, cwd=cwd
259 cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, cwd=cwd
260 )
260 )
261 out, err = p.communicate()
261 out, err = p.communicate()
262 return p.returncode, out, err
262 return p.returncode, out, err
263
263
264
264
265 class hgcommand(object):
265 class hgcommand(object):
266 def __init__(self, cmd, env):
266 def __init__(self, cmd, env):
267 self.cmd = cmd
267 self.cmd = cmd
268 self.env = env
268 self.env = env
269
269
270 def run(self, args):
270 def run(self, args):
271 cmd = self.cmd + args
271 cmd = self.cmd + args
272 returncode, out, err = runcmd(cmd, self.env)
272 returncode, out, err = runcmd(cmd, self.env)
273 err = filterhgerr(err)
273 err = filterhgerr(err)
274 if err or returncode != 0:
274 if err or returncode != 0:
275 printf("stderr from '%s':" % (' '.join(cmd)), file=sys.stderr)
275 printf("stderr from '%s':" % (' '.join(cmd)), file=sys.stderr)
276 printf(err, file=sys.stderr)
276 printf(err, file=sys.stderr)
277 return ''
277 return ''
278 return out
278 return out
279
279
280
280
281 def filterhgerr(err):
281 def filterhgerr(err):
282 # If root is executing setup.py, but the repository is owned by
282 # If root is executing setup.py, but the repository is owned by
283 # another user (as in "sudo python setup.py install") we will get
283 # another user (as in "sudo python setup.py install") we will get
284 # trust warnings since the .hg/hgrc file is untrusted. That is
284 # trust warnings since the .hg/hgrc file is untrusted. That is
285 # fine, we don't want to load it anyway. Python may warn about
285 # fine, we don't want to load it anyway. Python may warn about
286 # a missing __init__.py in mercurial/locale, we also ignore that.
286 # a missing __init__.py in mercurial/locale, we also ignore that.
287 err = [
287 err = [
288 e
288 e
289 for e in err.splitlines()
289 for e in err.splitlines()
290 if (
290 if (
291 not e.startswith(b'not trusting file')
291 not e.startswith(b'not trusting file')
292 and not e.startswith(b'warning: Not importing')
292 and not e.startswith(b'warning: Not importing')
293 and not e.startswith(b'obsolete feature not enabled')
293 and not e.startswith(b'obsolete feature not enabled')
294 and not e.startswith(b'*** failed to import extension')
294 and not e.startswith(b'*** failed to import extension')
295 and not e.startswith(b'devel-warn:')
295 and not e.startswith(b'devel-warn:')
296 and not (
296 and not (
297 e.startswith(b'(third party extension')
297 e.startswith(b'(third party extension')
298 and e.endswith(b'or newer of Mercurial; disabling)')
298 and e.endswith(b'or newer of Mercurial; disabling)')
299 )
299 )
300 )
300 )
301 ]
301 ]
302 return b'\n'.join(b' ' + e for e in err)
302 return b'\n'.join(b' ' + e for e in err)
303
303
304
304
305 def findhg():
305 def findhg():
306 """Try to figure out how we should invoke hg for examining the local
306 """Try to figure out how we should invoke hg for examining the local
307 repository contents.
307 repository contents.
308
308
309 Returns an hgcommand object."""
309 Returns an hgcommand object."""
310 # By default, prefer the "hg" command in the user's path. This was
310 # By default, prefer the "hg" command in the user's path. This was
311 # presumably the hg command that the user used to create this repository.
311 # presumably the hg command that the user used to create this repository.
312 #
312 #
313 # This repository may require extensions or other settings that would not
313 # This repository may require extensions or other settings that would not
314 # be enabled by running the hg script directly from this local repository.
314 # be enabled by running the hg script directly from this local repository.
315 hgenv = os.environ.copy()
315 hgenv = os.environ.copy()
316 # Use HGPLAIN to disable hgrc settings that would change output formatting,
316 # Use HGPLAIN to disable hgrc settings that would change output formatting,
317 # and disable localization for the same reasons.
317 # and disable localization for the same reasons.
318 hgenv['HGPLAIN'] = '1'
318 hgenv['HGPLAIN'] = '1'
319 hgenv['LANGUAGE'] = 'C'
319 hgenv['LANGUAGE'] = 'C'
320 hgcmd = ['hg']
320 hgcmd = ['hg']
321 # Run a simple "hg log" command just to see if using hg from the user's
321 # Run a simple "hg log" command just to see if using hg from the user's
322 # path works and can successfully interact with this repository. Windows
322 # path works and can successfully interact with this repository. Windows
323 # gives precedence to hg.exe in the current directory, so fall back to the
323 # gives precedence to hg.exe in the current directory, so fall back to the
324 # python invocation of local hg, where pythonXY.dll can always be found.
324 # python invocation of local hg, where pythonXY.dll can always be found.
325 check_cmd = ['log', '-r.', '-Ttest']
325 check_cmd = ['log', '-r.', '-Ttest']
326 if os.name != 'nt':
326 if os.name != 'nt':
327 try:
327 try:
328 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
328 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
329 except EnvironmentError:
329 except EnvironmentError:
330 retcode = -1
330 retcode = -1
331 if retcode == 0 and not filterhgerr(err):
331 if retcode == 0 and not filterhgerr(err):
332 return hgcommand(hgcmd, hgenv)
332 return hgcommand(hgcmd, hgenv)
333
333
334 # Fall back to trying the local hg installation.
334 # Fall back to trying the local hg installation.
335 hgenv = localhgenv()
335 hgenv = localhgenv()
336 hgcmd = [sys.executable, 'hg']
336 hgcmd = [sys.executable, 'hg']
337 try:
337 try:
338 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
338 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
339 except EnvironmentError:
339 except EnvironmentError:
340 retcode = -1
340 retcode = -1
341 if retcode == 0 and not filterhgerr(err):
341 if retcode == 0 and not filterhgerr(err):
342 return hgcommand(hgcmd, hgenv)
342 return hgcommand(hgcmd, hgenv)
343
343
344 raise SystemExit(
344 raise SystemExit(
345 'Unable to find a working hg binary to extract the '
345 'Unable to find a working hg binary to extract the '
346 'version from the repository tags'
346 'version from the repository tags'
347 )
347 )
348
348
349
349
350 def localhgenv():
350 def localhgenv():
351 """Get an environment dictionary to use for invoking or importing
351 """Get an environment dictionary to use for invoking or importing
352 mercurial from the local repository."""
352 mercurial from the local repository."""
353 # Execute hg out of this directory with a custom environment which takes
353 # Execute hg out of this directory with a custom environment which takes
354 # care to not use any hgrc files and do no localization.
354 # care to not use any hgrc files and do no localization.
355 env = {
355 env = {
356 'HGMODULEPOLICY': 'py',
356 'HGMODULEPOLICY': 'py',
357 'HGRCPATH': '',
357 'HGRCPATH': '',
358 'LANGUAGE': 'C',
358 'LANGUAGE': 'C',
359 'PATH': '',
359 'PATH': '',
360 } # make pypi modules that use os.environ['PATH'] happy
360 } # make pypi modules that use os.environ['PATH'] happy
361 if 'LD_LIBRARY_PATH' in os.environ:
361 if 'LD_LIBRARY_PATH' in os.environ:
362 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
362 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
363 if 'SystemRoot' in os.environ:
363 if 'SystemRoot' in os.environ:
364 # SystemRoot is required by Windows to load various DLLs. See:
364 # SystemRoot is required by Windows to load various DLLs. See:
365 # https://bugs.python.org/issue13524#msg148850
365 # https://bugs.python.org/issue13524#msg148850
366 env['SystemRoot'] = os.environ['SystemRoot']
366 env['SystemRoot'] = os.environ['SystemRoot']
367 return env
367 return env
368
368
369
369
370 version = ''
370 version = ''
371
371
372 if os.path.isdir('.hg'):
372 if os.path.isdir('.hg'):
373 hg = findhg()
373 hg = findhg()
374 cmd = ['log', '-r', '.', '--template', '{tags}\n']
374 cmd = ['log', '-r', '.', '--template', '{tags}\n']
375 numerictags = [t for t in sysstr(hg.run(cmd)).split() if t[0:1].isdigit()]
375 numerictags = [t for t in sysstr(hg.run(cmd)).split() if t[0:1].isdigit()]
376 hgid = sysstr(hg.run(['id', '-i'])).strip()
376 hgid = sysstr(hg.run(['id', '-i'])).strip()
377 if not hgid:
377 if not hgid:
378 # Bail out if hg is having problems interacting with this repository,
378 # Bail out if hg is having problems interacting with this repository,
379 # rather than falling through and producing a bogus version number.
379 # rather than falling through and producing a bogus version number.
380 # Continuing with an invalid version number will break extensions
380 # Continuing with an invalid version number will break extensions
381 # that define minimumhgversion.
381 # that define minimumhgversion.
382 raise SystemExit('Unable to determine hg version from local repository')
382 raise SystemExit('Unable to determine hg version from local repository')
383 if numerictags: # tag(s) found
383 if numerictags: # tag(s) found
384 version = numerictags[-1]
384 version = numerictags[-1]
385 if hgid.endswith('+'): # propagate the dirty status to the tag
385 if hgid.endswith('+'): # propagate the dirty status to the tag
386 version += '+'
386 version += '+'
387 else: # no tag found
387 else: # no tag found
388 ltagcmd = ['parents', '--template', '{latesttag}']
388 ltagcmd = ['parents', '--template', '{latesttag}']
389 ltag = sysstr(hg.run(ltagcmd))
389 ltag = sysstr(hg.run(ltagcmd))
390 changessincecmd = ['log', '-T', 'x\n', '-r', "only(.,'%s')" % ltag]
390 changessincecmd = ['log', '-T', 'x\n', '-r', "only(.,'%s')" % ltag]
391 changessince = len(hg.run(changessincecmd).splitlines())
391 changessince = len(hg.run(changessincecmd).splitlines())
392 version = '%s+%s-%s' % (ltag, changessince, hgid)
392 version = '%s+%s-%s' % (ltag, changessince, hgid)
393 if version.endswith('+'):
393 if version.endswith('+'):
394 version += time.strftime('%Y%m%d')
394 version += time.strftime('%Y%m%d')
395 elif os.path.exists('.hg_archival.txt'):
395 elif os.path.exists('.hg_archival.txt'):
396 kw = dict(
396 kw = dict(
397 [[t.strip() for t in l.split(':', 1)] for l in open('.hg_archival.txt')]
397 [[t.strip() for t in l.split(':', 1)] for l in open('.hg_archival.txt')]
398 )
398 )
399 if 'tag' in kw:
399 if 'tag' in kw:
400 version = kw['tag']
400 version = kw['tag']
401 elif 'latesttag' in kw:
401 elif 'latesttag' in kw:
402 if 'changessincelatesttag' in kw:
402 if 'changessincelatesttag' in kw:
403 version = '%(latesttag)s+%(changessincelatesttag)s-%(node).12s' % kw
403 version = '%(latesttag)s+%(changessincelatesttag)s-%(node).12s' % kw
404 else:
404 else:
405 version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
405 version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
406 else:
406 else:
407 version = kw.get('node', '')[:12]
407 version = kw.get('node', '')[:12]
408
408
409 if version:
409 if version:
410 versionb = version
410 versionb = version
411 if not isinstance(versionb, bytes):
411 if not isinstance(versionb, bytes):
412 versionb = versionb.encode('ascii')
412 versionb = versionb.encode('ascii')
413
413
414 write_if_changed(
414 write_if_changed(
415 'mercurial/__version__.py',
415 'mercurial/__version__.py',
416 b''.join(
416 b''.join(
417 [
417 [
418 b'# this file is autogenerated by setup.py\n'
418 b'# this file is autogenerated by setup.py\n'
419 b'version = b"%s"\n' % versionb,
419 b'version = b"%s"\n' % versionb,
420 ]
420 ]
421 ),
421 ),
422 )
422 )
423
423
424 try:
424 try:
425 oldpolicy = os.environ.get('HGMODULEPOLICY', None)
425 oldpolicy = os.environ.get('HGMODULEPOLICY', None)
426 os.environ['HGMODULEPOLICY'] = 'py'
426 os.environ['HGMODULEPOLICY'] = 'py'
427 from mercurial import __version__
427 from mercurial import __version__
428
428
429 version = __version__.version
429 version = __version__.version
430 except ImportError:
430 except ImportError:
431 version = b'unknown'
431 version = b'unknown'
432 finally:
432 finally:
433 if oldpolicy is None:
433 if oldpolicy is None:
434 del os.environ['HGMODULEPOLICY']
434 del os.environ['HGMODULEPOLICY']
435 else:
435 else:
436 os.environ['HGMODULEPOLICY'] = oldpolicy
436 os.environ['HGMODULEPOLICY'] = oldpolicy
437
437
438
438
439 class hgbuild(build):
439 class hgbuild(build):
440 # Insert hgbuildmo first so that files in mercurial/locale/ are found
440 # Insert hgbuildmo first so that files in mercurial/locale/ are found
441 # when build_py is run next.
441 # when build_py is run next.
442 sub_commands = [('build_mo', None)] + build.sub_commands
442 sub_commands = [('build_mo', None)] + build.sub_commands
443
443
444
444
445 class hgbuildmo(build):
445 class hgbuildmo(build):
446
446
447 description = "build translations (.mo files)"
447 description = "build translations (.mo files)"
448
448
449 def run(self):
449 def run(self):
450 if not find_executable('msgfmt'):
450 if not find_executable('msgfmt'):
451 self.warn(
451 self.warn(
452 "could not find msgfmt executable, no translations "
452 "could not find msgfmt executable, no translations "
453 "will be built"
453 "will be built"
454 )
454 )
455 return
455 return
456
456
457 podir = 'i18n'
457 podir = 'i18n'
458 if not os.path.isdir(podir):
458 if not os.path.isdir(podir):
459 self.warn("could not find %s/ directory" % podir)
459 self.warn("could not find %s/ directory" % podir)
460 return
460 return
461
461
462 join = os.path.join
462 join = os.path.join
463 for po in os.listdir(podir):
463 for po in os.listdir(podir):
464 if not po.endswith('.po'):
464 if not po.endswith('.po'):
465 continue
465 continue
466 pofile = join(podir, po)
466 pofile = join(podir, po)
467 modir = join('locale', po[:-3], 'LC_MESSAGES')
467 modir = join('locale', po[:-3], 'LC_MESSAGES')
468 mofile = join(modir, 'hg.mo')
468 mofile = join(modir, 'hg.mo')
469 mobuildfile = join('mercurial', mofile)
469 mobuildfile = join('mercurial', mofile)
470 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
470 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
471 if sys.platform != 'sunos5':
471 if sys.platform != 'sunos5':
472 # msgfmt on Solaris does not know about -c
472 # msgfmt on Solaris does not know about -c
473 cmd.append('-c')
473 cmd.append('-c')
474 self.mkpath(join('mercurial', modir))
474 self.mkpath(join('mercurial', modir))
475 self.make_file([pofile], mobuildfile, spawn, (cmd,))
475 self.make_file([pofile], mobuildfile, spawn, (cmd,))
476
476
477
477
478 class hgdist(Distribution):
478 class hgdist(Distribution):
479 pure = False
479 pure = False
480 rust = hgrustext is not None
480 rust = hgrustext is not None
481 cffi = ispypy
481 cffi = ispypy
482
482
483 global_options = Distribution.global_options + [
483 global_options = Distribution.global_options + [
484 ('pure', None, "use pure (slow) Python code instead of C extensions"),
484 ('pure', None, "use pure (slow) Python code instead of C extensions"),
485 ('rust', None, "use Rust extensions additionally to C extensions"),
485 ('rust', None, "use Rust extensions additionally to C extensions"),
486 ]
486 ]
487
487
488 def has_ext_modules(self):
488 def has_ext_modules(self):
489 # self.ext_modules is emptied in hgbuildpy.finalize_options which is
489 # self.ext_modules is emptied in hgbuildpy.finalize_options which is
490 # too late for some cases
490 # too late for some cases
491 return not self.pure and Distribution.has_ext_modules(self)
491 return not self.pure and Distribution.has_ext_modules(self)
492
492
493
493
494 # This is ugly as a one-liner. So use a variable.
494 # This is ugly as a one-liner. So use a variable.
495 buildextnegops = dict(getattr(build_ext, 'negative_options', {}))
495 buildextnegops = dict(getattr(build_ext, 'negative_options', {}))
496 buildextnegops['no-zstd'] = 'zstd'
496 buildextnegops['no-zstd'] = 'zstd'
497 buildextnegops['no-rust'] = 'rust'
497 buildextnegops['no-rust'] = 'rust'
498
498
499
499
500 class hgbuildext(build_ext):
500 class hgbuildext(build_ext):
501 user_options = build_ext.user_options + [
501 user_options = build_ext.user_options + [
502 ('zstd', None, 'compile zstd bindings [default]'),
502 ('zstd', None, 'compile zstd bindings [default]'),
503 ('no-zstd', None, 'do not compile zstd bindings'),
503 ('no-zstd', None, 'do not compile zstd bindings'),
504 (
504 (
505 'rust',
505 'rust',
506 None,
506 None,
507 'compile Rust extensions if they are in use '
507 'compile Rust extensions if they are in use '
508 '(requires Cargo) [default]',
508 '(requires Cargo) [default]',
509 ),
509 ),
510 ('no-rust', None, 'do not compile Rust extensions'),
510 ('no-rust', None, 'do not compile Rust extensions'),
511 ]
511 ]
512
512
513 boolean_options = build_ext.boolean_options + ['zstd', 'rust']
513 boolean_options = build_ext.boolean_options + ['zstd', 'rust']
514 negative_opt = buildextnegops
514 negative_opt = buildextnegops
515
515
516 def initialize_options(self):
516 def initialize_options(self):
517 self.zstd = True
517 self.zstd = True
518 self.rust = True
518 self.rust = True
519
519
520 return build_ext.initialize_options(self)
520 return build_ext.initialize_options(self)
521
521
522 def finalize_options(self):
522 def finalize_options(self):
523 # Unless overridden by the end user, build extensions in parallel.
523 # Unless overridden by the end user, build extensions in parallel.
524 # Only influences behavior on Python 3.5+.
524 # Only influences behavior on Python 3.5+.
525 if getattr(self, 'parallel', None) is None:
525 if getattr(self, 'parallel', None) is None:
526 self.parallel = True
526 self.parallel = True
527
527
528 return build_ext.finalize_options(self)
528 return build_ext.finalize_options(self)
529
529
530 def build_extensions(self):
530 def build_extensions(self):
531 ruststandalones = [
531 ruststandalones = [
532 e for e in self.extensions if isinstance(e, RustStandaloneExtension)
532 e for e in self.extensions if isinstance(e, RustStandaloneExtension)
533 ]
533 ]
534 self.extensions = [
534 self.extensions = [
535 e for e in self.extensions if e not in ruststandalones
535 e for e in self.extensions if e not in ruststandalones
536 ]
536 ]
537 # Filter out zstd if disabled via argument.
537 # Filter out zstd if disabled via argument.
538 if not self.zstd:
538 if not self.zstd:
539 self.extensions = [
539 self.extensions = [
540 e for e in self.extensions if e.name != 'mercurial.zstd'
540 e for e in self.extensions if e.name != 'mercurial.zstd'
541 ]
541 ]
542
542
543 # Build Rust standalon extensions if it'll be used
543 # Build Rust standalon extensions if it'll be used
544 # and its build is not explictely disabled (for external build
544 # and its build is not explictely disabled (for external build
545 # as Linux distributions would do)
545 # as Linux distributions would do)
546 if self.distribution.rust and self.rust and hgrustext != 'direct-ffi':
546 if self.distribution.rust and self.rust and hgrustext != 'direct-ffi':
547 for rustext in ruststandalones:
547 for rustext in ruststandalones:
548 rustext.build('' if self.inplace else self.build_lib)
548 rustext.build('' if self.inplace else self.build_lib)
549
549
550 return build_ext.build_extensions(self)
550 return build_ext.build_extensions(self)
551
551
552 def build_extension(self, ext):
552 def build_extension(self, ext):
553 if (
553 if (
554 self.distribution.rust
554 self.distribution.rust
555 and self.rust
555 and self.rust
556 and isinstance(ext, RustExtension)
556 and isinstance(ext, RustExtension)
557 ):
557 ):
558 ext.rustbuild()
558 ext.rustbuild()
559 try:
559 try:
560 build_ext.build_extension(self, ext)
560 build_ext.build_extension(self, ext)
561 except CCompilerError:
561 except CCompilerError:
562 if not getattr(ext, 'optional', False):
562 if not getattr(ext, 'optional', False):
563 raise
563 raise
564 log.warn(
564 log.warn(
565 "Failed to build optional extension '%s' (skipping)", ext.name
565 "Failed to build optional extension '%s' (skipping)", ext.name
566 )
566 )
567
567
568
568
569 class hgbuildscripts(build_scripts):
569 class hgbuildscripts(build_scripts):
570 def run(self):
570 def run(self):
571 if os.name != 'nt' or self.distribution.pure:
571 if os.name != 'nt' or self.distribution.pure:
572 return build_scripts.run(self)
572 return build_scripts.run(self)
573
573
574 exebuilt = False
574 exebuilt = False
575 try:
575 try:
576 self.run_command('build_hgexe')
576 self.run_command('build_hgexe')
577 exebuilt = True
577 exebuilt = True
578 except (DistutilsError, CCompilerError):
578 except (DistutilsError, CCompilerError):
579 log.warn('failed to build optional hg.exe')
579 log.warn('failed to build optional hg.exe')
580
580
581 if exebuilt:
581 if exebuilt:
582 # Copying hg.exe to the scripts build directory ensures it is
582 # Copying hg.exe to the scripts build directory ensures it is
583 # installed by the install_scripts command.
583 # installed by the install_scripts command.
584 hgexecommand = self.get_finalized_command('build_hgexe')
584 hgexecommand = self.get_finalized_command('build_hgexe')
585 dest = os.path.join(self.build_dir, 'hg.exe')
585 dest = os.path.join(self.build_dir, 'hg.exe')
586 self.mkpath(self.build_dir)
586 self.mkpath(self.build_dir)
587 self.copy_file(hgexecommand.hgexepath, dest)
587 self.copy_file(hgexecommand.hgexepath, dest)
588
588
589 # Remove hg.bat because it is redundant with hg.exe.
589 # Remove hg.bat because it is redundant with hg.exe.
590 self.scripts.remove('contrib/win32/hg.bat')
590 self.scripts.remove('contrib/win32/hg.bat')
591
591
592 return build_scripts.run(self)
592 return build_scripts.run(self)
593
593
594
594
595 class hgbuildpy(build_py):
595 class hgbuildpy(build_py):
596 def finalize_options(self):
596 def finalize_options(self):
597 build_py.finalize_options(self)
597 build_py.finalize_options(self)
598
598
599 if self.distribution.pure:
599 if self.distribution.pure:
600 self.distribution.ext_modules = []
600 self.distribution.ext_modules = []
601 elif self.distribution.cffi:
601 elif self.distribution.cffi:
602 from mercurial.cffi import (
602 from mercurial.cffi import (
603 bdiffbuild,
603 bdiffbuild,
604 mpatchbuild,
604 mpatchbuild,
605 )
605 )
606
606
607 exts = [
607 exts = [
608 mpatchbuild.ffi.distutils_extension(),
608 mpatchbuild.ffi.distutils_extension(),
609 bdiffbuild.ffi.distutils_extension(),
609 bdiffbuild.ffi.distutils_extension(),
610 ]
610 ]
611 # cffi modules go here
611 # cffi modules go here
612 if sys.platform == 'darwin':
612 if sys.platform == 'darwin':
613 from mercurial.cffi import osutilbuild
613 from mercurial.cffi import osutilbuild
614
614
615 exts.append(osutilbuild.ffi.distutils_extension())
615 exts.append(osutilbuild.ffi.distutils_extension())
616 self.distribution.ext_modules = exts
616 self.distribution.ext_modules = exts
617 else:
617 else:
618 h = os.path.join(get_python_inc(), 'Python.h')
618 h = os.path.join(get_python_inc(), 'Python.h')
619 if not os.path.exists(h):
619 if not os.path.exists(h):
620 raise SystemExit(
620 raise SystemExit(
621 'Python headers are required to build '
621 'Python headers are required to build '
622 'Mercurial but weren\'t found in %s' % h
622 'Mercurial but weren\'t found in %s' % h
623 )
623 )
624
624
625 def run(self):
625 def run(self):
626 basepath = os.path.join(self.build_lib, 'mercurial')
626 basepath = os.path.join(self.build_lib, 'mercurial')
627 self.mkpath(basepath)
627 self.mkpath(basepath)
628
628
629 rust = self.distribution.rust
629 rust = self.distribution.rust
630 if self.distribution.pure:
630 if self.distribution.pure:
631 modulepolicy = 'py'
631 modulepolicy = 'py'
632 elif self.build_lib == '.':
632 elif self.build_lib == '.':
633 # in-place build should run without rebuilding and Rust extensions
633 # in-place build should run without rebuilding and Rust extensions
634 modulepolicy = 'rust+c-allow' if rust else 'allow'
634 modulepolicy = 'rust+c-allow' if rust else 'allow'
635 else:
635 else:
636 modulepolicy = 'rust+c' if rust else 'c'
636 modulepolicy = 'rust+c' if rust else 'c'
637
637
638 content = b''.join(
638 content = b''.join(
639 [
639 [
640 b'# this file is autogenerated by setup.py\n',
640 b'# this file is autogenerated by setup.py\n',
641 b'modulepolicy = b"%s"\n' % modulepolicy.encode('ascii'),
641 b'modulepolicy = b"%s"\n' % modulepolicy.encode('ascii'),
642 ]
642 ]
643 )
643 )
644 write_if_changed(os.path.join(basepath, '__modulepolicy__.py'), content)
644 write_if_changed(os.path.join(basepath, '__modulepolicy__.py'), content)
645
645
646 build_py.run(self)
646 build_py.run(self)
647
647
648
648
649 class buildhgextindex(Command):
649 class buildhgextindex(Command):
650 description = 'generate prebuilt index of hgext (for frozen package)'
650 description = 'generate prebuilt index of hgext (for frozen package)'
651 user_options = []
651 user_options = []
652 _indexfilename = 'hgext/__index__.py'
652 _indexfilename = 'hgext/__index__.py'
653
653
654 def initialize_options(self):
654 def initialize_options(self):
655 pass
655 pass
656
656
657 def finalize_options(self):
657 def finalize_options(self):
658 pass
658 pass
659
659
660 def run(self):
660 def run(self):
661 if os.path.exists(self._indexfilename):
661 if os.path.exists(self._indexfilename):
662 with open(self._indexfilename, 'w') as f:
662 with open(self._indexfilename, 'w') as f:
663 f.write('# empty\n')
663 f.write('# empty\n')
664
664
665 # here no extension enabled, disabled() lists up everything
665 # here no extension enabled, disabled() lists up everything
666 code = (
666 code = (
667 'import pprint; from mercurial import extensions; '
667 'import pprint; from mercurial import extensions; '
668 'pprint.pprint(extensions.disabled())'
668 'pprint.pprint(extensions.disabled())'
669 )
669 )
670 returncode, out, err = runcmd(
670 returncode, out, err = runcmd(
671 [sys.executable, '-c', code], localhgenv()
671 [sys.executable, '-c', code], localhgenv()
672 )
672 )
673 if err or returncode != 0:
673 if err or returncode != 0:
674 raise DistutilsExecError(err)
674 raise DistutilsExecError(err)
675
675
676 with open(self._indexfilename, 'wb') as f:
676 with open(self._indexfilename, 'wb') as f:
677 f.write(b'# this file is autogenerated by setup.py\n')
677 f.write(b'# this file is autogenerated by setup.py\n')
678 f.write(b'docs = ')
678 f.write(b'docs = ')
679 f.write(out)
679 f.write(out)
680
680
681
681
682 class buildhgexe(build_ext):
682 class buildhgexe(build_ext):
683 description = 'compile hg.exe from mercurial/exewrapper.c'
683 description = 'compile hg.exe from mercurial/exewrapper.c'
684 user_options = build_ext.user_options + [
684 user_options = build_ext.user_options + [
685 (
685 (
686 'long-paths-support',
686 'long-paths-support',
687 None,
687 None,
688 'enable support for long paths on '
688 'enable support for long paths on '
689 'Windows (off by default and '
689 'Windows (off by default and '
690 'experimental)',
690 'experimental)',
691 ),
691 ),
692 ]
692 ]
693
693
694 LONG_PATHS_MANIFEST = """
694 LONG_PATHS_MANIFEST = """
695 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
695 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
696 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
696 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
697 <application>
697 <application>
698 <windowsSettings
698 <windowsSettings
699 xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
699 xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
700 <ws2:longPathAware>true</ws2:longPathAware>
700 <ws2:longPathAware>true</ws2:longPathAware>
701 </windowsSettings>
701 </windowsSettings>
702 </application>
702 </application>
703 </assembly>"""
703 </assembly>"""
704
704
705 def initialize_options(self):
705 def initialize_options(self):
706 build_ext.initialize_options(self)
706 build_ext.initialize_options(self)
707 self.long_paths_support = False
707 self.long_paths_support = False
708
708
709 def build_extensions(self):
709 def build_extensions(self):
710 if os.name != 'nt':
710 if os.name != 'nt':
711 return
711 return
712 if isinstance(self.compiler, HackedMingw32CCompiler):
712 if isinstance(self.compiler, HackedMingw32CCompiler):
713 self.compiler.compiler_so = self.compiler.compiler # no -mdll
713 self.compiler.compiler_so = self.compiler.compiler # no -mdll
714 self.compiler.dll_libraries = [] # no -lmsrvc90
714 self.compiler.dll_libraries = [] # no -lmsrvc90
715
715
716 # Different Python installs can have different Python library
716 # Different Python installs can have different Python library
717 # names. e.g. the official CPython distribution uses pythonXY.dll
717 # names. e.g. the official CPython distribution uses pythonXY.dll
718 # and MinGW uses libpythonX.Y.dll.
718 # and MinGW uses libpythonX.Y.dll.
719 _kernel32 = ctypes.windll.kernel32
719 _kernel32 = ctypes.windll.kernel32
720 _kernel32.GetModuleFileNameA.argtypes = [
720 _kernel32.GetModuleFileNameA.argtypes = [
721 ctypes.c_void_p,
721 ctypes.c_void_p,
722 ctypes.c_void_p,
722 ctypes.c_void_p,
723 ctypes.c_ulong,
723 ctypes.c_ulong,
724 ]
724 ]
725 _kernel32.GetModuleFileNameA.restype = ctypes.c_ulong
725 _kernel32.GetModuleFileNameA.restype = ctypes.c_ulong
726 size = 1000
726 size = 1000
727 buf = ctypes.create_string_buffer(size + 1)
727 buf = ctypes.create_string_buffer(size + 1)
728 filelen = _kernel32.GetModuleFileNameA(
728 filelen = _kernel32.GetModuleFileNameA(
729 sys.dllhandle, ctypes.byref(buf), size
729 sys.dllhandle, ctypes.byref(buf), size
730 )
730 )
731
731
732 if filelen > 0 and filelen != size:
732 if filelen > 0 and filelen != size:
733 dllbasename = os.path.basename(buf.value)
733 dllbasename = os.path.basename(buf.value)
734 if not dllbasename.lower().endswith(b'.dll'):
734 if not dllbasename.lower().endswith(b'.dll'):
735 raise SystemExit(
735 raise SystemExit(
736 'Python DLL does not end with .dll: %s' % dllbasename
736 'Python DLL does not end with .dll: %s' % dllbasename
737 )
737 )
738 pythonlib = dllbasename[:-4]
738 pythonlib = dllbasename[:-4]
739 else:
739 else:
740 log.warn(
740 log.warn(
741 'could not determine Python DLL filename; ' 'assuming pythonXY'
741 'could not determine Python DLL filename; ' 'assuming pythonXY'
742 )
742 )
743
743
744 hv = sys.hexversion
744 hv = sys.hexversion
745 pythonlib = 'python%d%d' % (hv >> 24, (hv >> 16) & 0xFF)
745 pythonlib = 'python%d%d' % (hv >> 24, (hv >> 16) & 0xFF)
746
746
747 log.info('using %s as Python library name' % pythonlib)
747 log.info('using %s as Python library name' % pythonlib)
748 with open('mercurial/hgpythonlib.h', 'wb') as f:
748 with open('mercurial/hgpythonlib.h', 'wb') as f:
749 f.write(b'/* this file is autogenerated by setup.py */\n')
749 f.write(b'/* this file is autogenerated by setup.py */\n')
750 f.write(b'#define HGPYTHONLIB "%s"\n' % pythonlib)
750 f.write(b'#define HGPYTHONLIB "%s"\n' % pythonlib)
751
751
752 macros = None
752 macros = None
753 if sys.version_info[0] >= 3:
753 if sys.version_info[0] >= 3:
754 macros = [('_UNICODE', None), ('UNICODE', None)]
754 macros = [('_UNICODE', None), ('UNICODE', None)]
755
755
756 objects = self.compiler.compile(
756 objects = self.compiler.compile(
757 ['mercurial/exewrapper.c'],
757 ['mercurial/exewrapper.c'],
758 output_dir=self.build_temp,
758 output_dir=self.build_temp,
759 macros=macros,
759 macros=macros,
760 )
760 )
761 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
761 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
762 self.hgtarget = os.path.join(dir, 'hg')
762 self.hgtarget = os.path.join(dir, 'hg')
763 self.compiler.link_executable(
763 self.compiler.link_executable(
764 objects, self.hgtarget, libraries=[], output_dir=self.build_temp
764 objects, self.hgtarget, libraries=[], output_dir=self.build_temp
765 )
765 )
766 if self.long_paths_support:
766 if self.long_paths_support:
767 self.addlongpathsmanifest()
767 self.addlongpathsmanifest()
768
768
769 def addlongpathsmanifest(self):
769 def addlongpathsmanifest(self):
770 r"""Add manifest pieces so that hg.exe understands long paths
770 r"""Add manifest pieces so that hg.exe understands long paths
771
771
772 This is an EXPERIMENTAL feature, use with care.
772 This is an EXPERIMENTAL feature, use with care.
773 To enable long paths support, one needs to do two things:
773 To enable long paths support, one needs to do two things:
774 - build Mercurial with --long-paths-support option
774 - build Mercurial with --long-paths-support option
775 - change HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\
775 - change HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\
776 LongPathsEnabled to have value 1.
776 LongPathsEnabled to have value 1.
777
777
778 Please ignore 'warning 81010002: Unrecognized Element "longPathAware"';
778 Please ignore 'warning 81010002: Unrecognized Element "longPathAware"';
779 it happens because Mercurial uses mt.exe circa 2008, which is not
779 it happens because Mercurial uses mt.exe circa 2008, which is not
780 yet aware of long paths support in the manifest (I think so at least).
780 yet aware of long paths support in the manifest (I think so at least).
781 This does not stop mt.exe from embedding/merging the XML properly.
781 This does not stop mt.exe from embedding/merging the XML properly.
782
782
783 Why resource #1 should be used for .exe manifests? I don't know and
783 Why resource #1 should be used for .exe manifests? I don't know and
784 wasn't able to find an explanation for mortals. But it seems to work.
784 wasn't able to find an explanation for mortals. But it seems to work.
785 """
785 """
786 exefname = self.compiler.executable_filename(self.hgtarget)
786 exefname = self.compiler.executable_filename(self.hgtarget)
787 fdauto, manfname = tempfile.mkstemp(suffix='.hg.exe.manifest')
787 fdauto, manfname = tempfile.mkstemp(suffix='.hg.exe.manifest')
788 os.close(fdauto)
788 os.close(fdauto)
789 with open(manfname, 'w') as f:
789 with open(manfname, 'w') as f:
790 f.write(self.LONG_PATHS_MANIFEST)
790 f.write(self.LONG_PATHS_MANIFEST)
791 log.info("long paths manifest is written to '%s'" % manfname)
791 log.info("long paths manifest is written to '%s'" % manfname)
792 inputresource = '-inputresource:%s;#1' % exefname
792 inputresource = '-inputresource:%s;#1' % exefname
793 outputresource = '-outputresource:%s;#1' % exefname
793 outputresource = '-outputresource:%s;#1' % exefname
794 log.info("running mt.exe to update hg.exe's manifest in-place")
794 log.info("running mt.exe to update hg.exe's manifest in-place")
795 # supplying both -manifest and -inputresource to mt.exe makes
795 # supplying both -manifest and -inputresource to mt.exe makes
796 # it merge the embedded and supplied manifests in the -outputresource
796 # it merge the embedded and supplied manifests in the -outputresource
797 self.spawn(
797 self.spawn(
798 [
798 [
799 'mt.exe',
799 'mt.exe',
800 '-nologo',
800 '-nologo',
801 '-manifest',
801 '-manifest',
802 manfname,
802 manfname,
803 inputresource,
803 inputresource,
804 outputresource,
804 outputresource,
805 ]
805 ]
806 )
806 )
807 log.info("done updating hg.exe's manifest")
807 log.info("done updating hg.exe's manifest")
808 os.remove(manfname)
808 os.remove(manfname)
809
809
810 @property
810 @property
811 def hgexepath(self):
811 def hgexepath(self):
812 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
812 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
813 return os.path.join(self.build_temp, dir, 'hg.exe')
813 return os.path.join(self.build_temp, dir, 'hg.exe')
814
814
815
815
816 class hgbuilddoc(Command):
816 class hgbuilddoc(Command):
817 description = 'build documentation'
817 description = 'build documentation'
818 user_options = [
818 user_options = [
819 ('man', None, 'generate man pages'),
819 ('man', None, 'generate man pages'),
820 ('html', None, 'generate html pages'),
820 ('html', None, 'generate html pages'),
821 ]
821 ]
822
822
823 def initialize_options(self):
823 def initialize_options(self):
824 self.man = None
824 self.man = None
825 self.html = None
825 self.html = None
826
826
827 def finalize_options(self):
827 def finalize_options(self):
828 # If --man or --html are set, only generate what we're told to.
828 # If --man or --html are set, only generate what we're told to.
829 # Otherwise generate everything.
829 # Otherwise generate everything.
830 have_subset = self.man is not None or self.html is not None
830 have_subset = self.man is not None or self.html is not None
831
831
832 if have_subset:
832 if have_subset:
833 self.man = True if self.man else False
833 self.man = True if self.man else False
834 self.html = True if self.html else False
834 self.html = True if self.html else False
835 else:
835 else:
836 self.man = True
836 self.man = True
837 self.html = True
837 self.html = True
838
838
839 def run(self):
839 def run(self):
840 def normalizecrlf(p):
840 def normalizecrlf(p):
841 with open(p, 'rb') as fh:
841 with open(p, 'rb') as fh:
842 orig = fh.read()
842 orig = fh.read()
843
843
844 if b'\r\n' not in orig:
844 if b'\r\n' not in orig:
845 return
845 return
846
846
847 log.info('normalizing %s to LF line endings' % p)
847 log.info('normalizing %s to LF line endings' % p)
848 with open(p, 'wb') as fh:
848 with open(p, 'wb') as fh:
849 fh.write(orig.replace(b'\r\n', b'\n'))
849 fh.write(orig.replace(b'\r\n', b'\n'))
850
850
851 def gentxt(root):
851 def gentxt(root):
852 txt = 'doc/%s.txt' % root
852 txt = 'doc/%s.txt' % root
853 log.info('generating %s' % txt)
853 log.info('generating %s' % txt)
854 res, out, err = runcmd(
854 res, out, err = runcmd(
855 [sys.executable, 'gendoc.py', root], os.environ, cwd='doc'
855 [sys.executable, 'gendoc.py', root], os.environ, cwd='doc'
856 )
856 )
857 if res:
857 if res:
858 raise SystemExit(
858 raise SystemExit(
859 'error running gendoc.py: %s' % '\n'.join([out, err])
859 'error running gendoc.py: %s' % '\n'.join([out, err])
860 )
860 )
861
861
862 with open(txt, 'wb') as fh:
862 with open(txt, 'wb') as fh:
863 fh.write(out)
863 fh.write(out)
864
864
865 def gengendoc(root):
865 def gengendoc(root):
866 gendoc = 'doc/%s.gendoc.txt' % root
866 gendoc = 'doc/%s.gendoc.txt' % root
867
867
868 log.info('generating %s' % gendoc)
868 log.info('generating %s' % gendoc)
869 res, out, err = runcmd(
869 res, out, err = runcmd(
870 [sys.executable, 'gendoc.py', '%s.gendoc' % root],
870 [sys.executable, 'gendoc.py', '%s.gendoc' % root],
871 os.environ,
871 os.environ,
872 cwd='doc',
872 cwd='doc',
873 )
873 )
874 if res:
874 if res:
875 raise SystemExit(
875 raise SystemExit(
876 'error running gendoc: %s' % '\n'.join([out, err])
876 'error running gendoc: %s' % '\n'.join([out, err])
877 )
877 )
878
878
879 with open(gendoc, 'wb') as fh:
879 with open(gendoc, 'wb') as fh:
880 fh.write(out)
880 fh.write(out)
881
881
882 def genman(root):
882 def genman(root):
883 log.info('generating doc/%s' % root)
883 log.info('generating doc/%s' % root)
884 res, out, err = runcmd(
884 res, out, err = runcmd(
885 [
885 [
886 sys.executable,
886 sys.executable,
887 'runrst',
887 'runrst',
888 'hgmanpage',
888 'hgmanpage',
889 '--halt',
889 '--halt',
890 'warning',
890 'warning',
891 '--strip-elements-with-class',
891 '--strip-elements-with-class',
892 'htmlonly',
892 'htmlonly',
893 '%s.txt' % root,
893 '%s.txt' % root,
894 root,
894 root,
895 ],
895 ],
896 os.environ,
896 os.environ,
897 cwd='doc',
897 cwd='doc',
898 )
898 )
899 if res:
899 if res:
900 raise SystemExit(
900 raise SystemExit(
901 'error running runrst: %s' % '\n'.join([out, err])
901 'error running runrst: %s' % '\n'.join([out, err])
902 )
902 )
903
903
904 normalizecrlf('doc/%s' % root)
904 normalizecrlf('doc/%s' % root)
905
905
906 def genhtml(root):
906 def genhtml(root):
907 log.info('generating doc/%s.html' % root)
907 log.info('generating doc/%s.html' % root)
908 res, out, err = runcmd(
908 res, out, err = runcmd(
909 [
909 [
910 sys.executable,
910 sys.executable,
911 'runrst',
911 'runrst',
912 'html',
912 'html',
913 '--halt',
913 '--halt',
914 'warning',
914 'warning',
915 '--link-stylesheet',
915 '--link-stylesheet',
916 '--stylesheet-path',
916 '--stylesheet-path',
917 'style.css',
917 'style.css',
918 '%s.txt' % root,
918 '%s.txt' % root,
919 '%s.html' % root,
919 '%s.html' % root,
920 ],
920 ],
921 os.environ,
921 os.environ,
922 cwd='doc',
922 cwd='doc',
923 )
923 )
924 if res:
924 if res:
925 raise SystemExit(
925 raise SystemExit(
926 'error running runrst: %s' % '\n'.join([out, err])
926 'error running runrst: %s' % '\n'.join([out, err])
927 )
927 )
928
928
929 normalizecrlf('doc/%s.html' % root)
929 normalizecrlf('doc/%s.html' % root)
930
930
931 # This logic is duplicated in doc/Makefile.
931 # This logic is duplicated in doc/Makefile.
932 sources = set(
932 sources = set(
933 f
933 f
934 for f in os.listdir('mercurial/help')
934 for f in os.listdir('mercurial/help')
935 if re.search(r'[0-9]\.txt$', f)
935 if re.search(r'[0-9]\.txt$', f)
936 )
936 )
937
937
938 # common.txt is a one-off.
938 # common.txt is a one-off.
939 gentxt('common')
939 gentxt('common')
940
940
941 for source in sorted(sources):
941 for source in sorted(sources):
942 assert source[-4:] == '.txt'
942 assert source[-4:] == '.txt'
943 root = source[:-4]
943 root = source[:-4]
944
944
945 gentxt(root)
945 gentxt(root)
946 gengendoc(root)
946 gengendoc(root)
947
947
948 if self.man:
948 if self.man:
949 genman(root)
949 genman(root)
950 if self.html:
950 if self.html:
951 genhtml(root)
951 genhtml(root)
952
952
953
953
954 class hginstall(install):
954 class hginstall(install):
955
955
956 user_options = install.user_options + [
956 user_options = install.user_options + [
957 (
957 (
958 'old-and-unmanageable',
958 'old-and-unmanageable',
959 None,
959 None,
960 'noop, present for eggless setuptools compat',
960 'noop, present for eggless setuptools compat',
961 ),
961 ),
962 (
962 (
963 'single-version-externally-managed',
963 'single-version-externally-managed',
964 None,
964 None,
965 'noop, present for eggless setuptools compat',
965 'noop, present for eggless setuptools compat',
966 ),
966 ),
967 ]
967 ]
968
968
969 # Also helps setuptools not be sad while we refuse to create eggs.
969 # Also helps setuptools not be sad while we refuse to create eggs.
970 single_version_externally_managed = True
970 single_version_externally_managed = True
971
971
972 def get_sub_commands(self):
972 def get_sub_commands(self):
973 # Screen out egg related commands to prevent egg generation. But allow
973 # Screen out egg related commands to prevent egg generation. But allow
974 # mercurial.egg-info generation, since that is part of modern
974 # mercurial.egg-info generation, since that is part of modern
975 # packaging.
975 # packaging.
976 excl = set(['bdist_egg'])
976 excl = set(['bdist_egg'])
977 return filter(lambda x: x not in excl, install.get_sub_commands(self))
977 return filter(lambda x: x not in excl, install.get_sub_commands(self))
978
978
979
979
980 class hginstalllib(install_lib):
980 class hginstalllib(install_lib):
981 '''
981 '''
982 This is a specialization of install_lib that replaces the copy_file used
982 This is a specialization of install_lib that replaces the copy_file used
983 there so that it supports setting the mode of files after copying them,
983 there so that it supports setting the mode of files after copying them,
984 instead of just preserving the mode that the files originally had. If your
984 instead of just preserving the mode that the files originally had. If your
985 system has a umask of something like 027, preserving the permissions when
985 system has a umask of something like 027, preserving the permissions when
986 copying will lead to a broken install.
986 copying will lead to a broken install.
987
987
988 Note that just passing keep_permissions=False to copy_file would be
988 Note that just passing keep_permissions=False to copy_file would be
989 insufficient, as it might still be applying a umask.
989 insufficient, as it might still be applying a umask.
990 '''
990 '''
991
991
992 def run(self):
992 def run(self):
993 realcopyfile = file_util.copy_file
993 realcopyfile = file_util.copy_file
994
994
995 def copyfileandsetmode(*args, **kwargs):
995 def copyfileandsetmode(*args, **kwargs):
996 src, dst = args[0], args[1]
996 src, dst = args[0], args[1]
997 dst, copied = realcopyfile(*args, **kwargs)
997 dst, copied = realcopyfile(*args, **kwargs)
998 if copied:
998 if copied:
999 st = os.stat(src)
999 st = os.stat(src)
1000 # Persist executable bit (apply it to group and other if user
1000 # Persist executable bit (apply it to group and other if user
1001 # has it)
1001 # has it)
1002 if st[stat.ST_MODE] & stat.S_IXUSR:
1002 if st[stat.ST_MODE] & stat.S_IXUSR:
1003 setmode = int('0755', 8)
1003 setmode = int('0755', 8)
1004 else:
1004 else:
1005 setmode = int('0644', 8)
1005 setmode = int('0644', 8)
1006 m = stat.S_IMODE(st[stat.ST_MODE])
1006 m = stat.S_IMODE(st[stat.ST_MODE])
1007 m = (m & ~int('0777', 8)) | setmode
1007 m = (m & ~int('0777', 8)) | setmode
1008 os.chmod(dst, m)
1008 os.chmod(dst, m)
1009
1009
1010 file_util.copy_file = copyfileandsetmode
1010 file_util.copy_file = copyfileandsetmode
1011 try:
1011 try:
1012 install_lib.run(self)
1012 install_lib.run(self)
1013 finally:
1013 finally:
1014 file_util.copy_file = realcopyfile
1014 file_util.copy_file = realcopyfile
1015
1015
1016
1016
1017 class hginstallscripts(install_scripts):
1017 class hginstallscripts(install_scripts):
1018 '''
1018 '''
1019 This is a specialization of install_scripts that replaces the @LIBDIR@ with
1019 This is a specialization of install_scripts that replaces the @LIBDIR@ with
1020 the configured directory for modules. If possible, the path is made relative
1020 the configured directory for modules. If possible, the path is made relative
1021 to the directory for scripts.
1021 to the directory for scripts.
1022 '''
1022 '''
1023
1023
1024 def initialize_options(self):
1024 def initialize_options(self):
1025 install_scripts.initialize_options(self)
1025 install_scripts.initialize_options(self)
1026
1026
1027 self.install_lib = None
1027 self.install_lib = None
1028
1028
1029 def finalize_options(self):
1029 def finalize_options(self):
1030 install_scripts.finalize_options(self)
1030 install_scripts.finalize_options(self)
1031 self.set_undefined_options('install', ('install_lib', 'install_lib'))
1031 self.set_undefined_options('install', ('install_lib', 'install_lib'))
1032
1032
1033 def run(self):
1033 def run(self):
1034 install_scripts.run(self)
1034 install_scripts.run(self)
1035
1035
1036 # It only makes sense to replace @LIBDIR@ with the install path if
1036 # It only makes sense to replace @LIBDIR@ with the install path if
1037 # the install path is known. For wheels, the logic below calculates
1037 # the install path is known. For wheels, the logic below calculates
1038 # the libdir to be "../..". This is because the internal layout of a
1038 # the libdir to be "../..". This is because the internal layout of a
1039 # wheel archive looks like:
1039 # wheel archive looks like:
1040 #
1040 #
1041 # mercurial-3.6.1.data/scripts/hg
1041 # mercurial-3.6.1.data/scripts/hg
1042 # mercurial/__init__.py
1042 # mercurial/__init__.py
1043 #
1043 #
1044 # When installing wheels, the subdirectories of the "<pkg>.data"
1044 # When installing wheels, the subdirectories of the "<pkg>.data"
1045 # directory are translated to system local paths and files therein
1045 # directory are translated to system local paths and files therein
1046 # are copied in place. The mercurial/* files are installed into the
1046 # are copied in place. The mercurial/* files are installed into the
1047 # site-packages directory. However, the site-packages directory
1047 # site-packages directory. However, the site-packages directory
1048 # isn't known until wheel install time. This means we have no clue
1048 # isn't known until wheel install time. This means we have no clue
1049 # at wheel generation time what the installed site-packages directory
1049 # at wheel generation time what the installed site-packages directory
1050 # will be. And, wheels don't appear to provide the ability to register
1050 # will be. And, wheels don't appear to provide the ability to register
1051 # custom code to run during wheel installation. This all means that
1051 # custom code to run during wheel installation. This all means that
1052 # we can't reliably set the libdir in wheels: the default behavior
1052 # we can't reliably set the libdir in wheels: the default behavior
1053 # of looking in sys.path must do.
1053 # of looking in sys.path must do.
1054
1054
1055 if (
1055 if (
1056 os.path.splitdrive(self.install_dir)[0]
1056 os.path.splitdrive(self.install_dir)[0]
1057 != os.path.splitdrive(self.install_lib)[0]
1057 != os.path.splitdrive(self.install_lib)[0]
1058 ):
1058 ):
1059 # can't make relative paths from one drive to another, so use an
1059 # can't make relative paths from one drive to another, so use an
1060 # absolute path instead
1060 # absolute path instead
1061 libdir = self.install_lib
1061 libdir = self.install_lib
1062 else:
1062 else:
1063 common = os.path.commonprefix((self.install_dir, self.install_lib))
1063 common = os.path.commonprefix((self.install_dir, self.install_lib))
1064 rest = self.install_dir[len(common) :]
1064 rest = self.install_dir[len(common) :]
1065 uplevel = len([n for n in os.path.split(rest) if n])
1065 uplevel = len([n for n in os.path.split(rest) if n])
1066
1066
1067 libdir = uplevel * ('..' + os.sep) + self.install_lib[len(common) :]
1067 libdir = uplevel * ('..' + os.sep) + self.install_lib[len(common) :]
1068
1068
1069 for outfile in self.outfiles:
1069 for outfile in self.outfiles:
1070 with open(outfile, 'rb') as fp:
1070 with open(outfile, 'rb') as fp:
1071 data = fp.read()
1071 data = fp.read()
1072
1072
1073 # skip binary files
1073 # skip binary files
1074 if b'\0' in data:
1074 if b'\0' in data:
1075 continue
1075 continue
1076
1076
1077 # During local installs, the shebang will be rewritten to the final
1077 # During local installs, the shebang will be rewritten to the final
1078 # install path. During wheel packaging, the shebang has a special
1078 # install path. During wheel packaging, the shebang has a special
1079 # value.
1079 # value.
1080 if data.startswith(b'#!python'):
1080 if data.startswith(b'#!python'):
1081 log.info(
1081 log.info(
1082 'not rewriting @LIBDIR@ in %s because install path '
1082 'not rewriting @LIBDIR@ in %s because install path '
1083 'not known' % outfile
1083 'not known' % outfile
1084 )
1084 )
1085 continue
1085 continue
1086
1086
1087 data = data.replace(b'@LIBDIR@', libdir.encode(libdir_escape))
1087 data = data.replace(b'@LIBDIR@', libdir.encode(libdir_escape))
1088 with open(outfile, 'wb') as fp:
1088 with open(outfile, 'wb') as fp:
1089 fp.write(data)
1089 fp.write(data)
1090
1090
1091
1091
1092 # virtualenv installs custom distutils/__init__.py and
1092 # virtualenv installs custom distutils/__init__.py and
1093 # distutils/distutils.cfg files which essentially proxy back to the
1093 # distutils/distutils.cfg files which essentially proxy back to the
1094 # "real" distutils in the main Python install. The presence of this
1094 # "real" distutils in the main Python install. The presence of this
1095 # directory causes py2exe to pick up the "hacked" distutils package
1095 # directory causes py2exe to pick up the "hacked" distutils package
1096 # from the virtualenv and "import distutils" will fail from the py2exe
1096 # from the virtualenv and "import distutils" will fail from the py2exe
1097 # build because the "real" distutils files can't be located.
1097 # build because the "real" distutils files can't be located.
1098 #
1098 #
1099 # We work around this by monkeypatching the py2exe code finding Python
1099 # We work around this by monkeypatching the py2exe code finding Python
1100 # modules to replace the found virtualenv distutils modules with the
1100 # modules to replace the found virtualenv distutils modules with the
1101 # original versions via filesystem scanning. This is a bit hacky. But
1101 # original versions via filesystem scanning. This is a bit hacky. But
1102 # it allows us to use virtualenvs for py2exe packaging, which is more
1102 # it allows us to use virtualenvs for py2exe packaging, which is more
1103 # deterministic and reproducible.
1103 # deterministic and reproducible.
1104 #
1104 #
1105 # It's worth noting that the common StackOverflow suggestions for this
1105 # It's worth noting that the common StackOverflow suggestions for this
1106 # problem involve copying the original distutils files into the
1106 # problem involve copying the original distutils files into the
1107 # virtualenv or into the staging directory after setup() is invoked.
1107 # virtualenv or into the staging directory after setup() is invoked.
1108 # The former is very brittle and can easily break setup(). Our hacking
1108 # The former is very brittle and can easily break setup(). Our hacking
1109 # of the found modules routine has a similar result as copying the files
1109 # of the found modules routine has a similar result as copying the files
1110 # manually. But it makes fewer assumptions about how py2exe works and
1110 # manually. But it makes fewer assumptions about how py2exe works and
1111 # is less brittle.
1111 # is less brittle.
1112
1112
1113 # This only catches virtualenvs made with virtualenv (as opposed to
1113 # This only catches virtualenvs made with virtualenv (as opposed to
1114 # venv, which is likely what Python 3 uses).
1114 # venv, which is likely what Python 3 uses).
1115 py2exehacked = py2exeloaded and getattr(sys, 'real_prefix', None) is not None
1115 py2exehacked = py2exeloaded and getattr(sys, 'real_prefix', None) is not None
1116
1116
1117 if py2exehacked:
1117 if py2exehacked:
1118 from distutils.command.py2exe import py2exe as buildpy2exe
1118 from distutils.command.py2exe import py2exe as buildpy2exe
1119 from py2exe.mf import Module as py2exemodule
1119 from py2exe.mf import Module as py2exemodule
1120
1120
1121 class hgbuildpy2exe(buildpy2exe):
1121 class hgbuildpy2exe(buildpy2exe):
1122 def find_needed_modules(self, mf, files, modules):
1122 def find_needed_modules(self, mf, files, modules):
1123 res = buildpy2exe.find_needed_modules(self, mf, files, modules)
1123 res = buildpy2exe.find_needed_modules(self, mf, files, modules)
1124
1124
1125 # Replace virtualenv's distutils modules with the real ones.
1125 # Replace virtualenv's distutils modules with the real ones.
1126 modules = {}
1126 modules = {}
1127 for k, v in res.modules.items():
1127 for k, v in res.modules.items():
1128 if k != 'distutils' and not k.startswith('distutils.'):
1128 if k != 'distutils' and not k.startswith('distutils.'):
1129 modules[k] = v
1129 modules[k] = v
1130
1130
1131 res.modules = modules
1131 res.modules = modules
1132
1132
1133 import opcode
1133 import opcode
1134
1134
1135 distutilsreal = os.path.join(
1135 distutilsreal = os.path.join(
1136 os.path.dirname(opcode.__file__), 'distutils'
1136 os.path.dirname(opcode.__file__), 'distutils'
1137 )
1137 )
1138
1138
1139 for root, dirs, files in os.walk(distutilsreal):
1139 for root, dirs, files in os.walk(distutilsreal):
1140 for f in sorted(files):
1140 for f in sorted(files):
1141 if not f.endswith('.py'):
1141 if not f.endswith('.py'):
1142 continue
1142 continue
1143
1143
1144 full = os.path.join(root, f)
1144 full = os.path.join(root, f)
1145
1145
1146 parents = ['distutils']
1146 parents = ['distutils']
1147
1147
1148 if root != distutilsreal:
1148 if root != distutilsreal:
1149 rel = os.path.relpath(root, distutilsreal)
1149 rel = os.path.relpath(root, distutilsreal)
1150 parents.extend(p for p in rel.split(os.sep))
1150 parents.extend(p for p in rel.split(os.sep))
1151
1151
1152 modname = '%s.%s' % ('.'.join(parents), f[:-3])
1152 modname = '%s.%s' % ('.'.join(parents), f[:-3])
1153
1153
1154 if modname.startswith('distutils.tests.'):
1154 if modname.startswith('distutils.tests.'):
1155 continue
1155 continue
1156
1156
1157 if modname.endswith('.__init__'):
1157 if modname.endswith('.__init__'):
1158 modname = modname[: -len('.__init__')]
1158 modname = modname[: -len('.__init__')]
1159 path = os.path.dirname(full)
1159 path = os.path.dirname(full)
1160 else:
1160 else:
1161 path = None
1161 path = None
1162
1162
1163 res.modules[modname] = py2exemodule(
1163 res.modules[modname] = py2exemodule(
1164 modname, full, path=path
1164 modname, full, path=path
1165 )
1165 )
1166
1166
1167 if 'distutils' not in res.modules:
1167 if 'distutils' not in res.modules:
1168 raise SystemExit('could not find distutils modules')
1168 raise SystemExit('could not find distutils modules')
1169
1169
1170 return res
1170 return res
1171
1171
1172
1172
1173 cmdclass = {
1173 cmdclass = {
1174 'build': hgbuild,
1174 'build': hgbuild,
1175 'build_doc': hgbuilddoc,
1175 'build_doc': hgbuilddoc,
1176 'build_mo': hgbuildmo,
1176 'build_mo': hgbuildmo,
1177 'build_ext': hgbuildext,
1177 'build_ext': hgbuildext,
1178 'build_py': hgbuildpy,
1178 'build_py': hgbuildpy,
1179 'build_scripts': hgbuildscripts,
1179 'build_scripts': hgbuildscripts,
1180 'build_hgextindex': buildhgextindex,
1180 'build_hgextindex': buildhgextindex,
1181 'install': hginstall,
1181 'install': hginstall,
1182 'install_lib': hginstalllib,
1182 'install_lib': hginstalllib,
1183 'install_scripts': hginstallscripts,
1183 'install_scripts': hginstallscripts,
1184 'build_hgexe': buildhgexe,
1184 'build_hgexe': buildhgexe,
1185 }
1185 }
1186
1186
1187 if py2exehacked:
1187 if py2exehacked:
1188 cmdclass['py2exe'] = hgbuildpy2exe
1188 cmdclass['py2exe'] = hgbuildpy2exe
1189
1189
1190 packages = [
1190 packages = [
1191 'mercurial',
1191 'mercurial',
1192 'mercurial.cext',
1192 'mercurial.cext',
1193 'mercurial.cffi',
1193 'mercurial.cffi',
1194 'mercurial.helptext',
1195 'mercurial.helptext.internals',
1194 'mercurial.hgweb',
1196 'mercurial.hgweb',
1195 'mercurial.interfaces',
1197 'mercurial.interfaces',
1196 'mercurial.pure',
1198 'mercurial.pure',
1197 'mercurial.thirdparty',
1199 'mercurial.thirdparty',
1198 'mercurial.thirdparty.attr',
1200 'mercurial.thirdparty.attr',
1199 'mercurial.thirdparty.zope',
1201 'mercurial.thirdparty.zope',
1200 'mercurial.thirdparty.zope.interface',
1202 'mercurial.thirdparty.zope.interface',
1201 'mercurial.utils',
1203 'mercurial.utils',
1202 'mercurial.revlogutils',
1204 'mercurial.revlogutils',
1203 'mercurial.testing',
1205 'mercurial.testing',
1204 'hgext',
1206 'hgext',
1205 'hgext.convert',
1207 'hgext.convert',
1206 'hgext.fsmonitor',
1208 'hgext.fsmonitor',
1207 'hgext.fastannotate',
1209 'hgext.fastannotate',
1208 'hgext.fsmonitor.pywatchman',
1210 'hgext.fsmonitor.pywatchman',
1209 'hgext.highlight',
1211 'hgext.highlight',
1210 'hgext.infinitepush',
1212 'hgext.infinitepush',
1211 'hgext.largefiles',
1213 'hgext.largefiles',
1212 'hgext.lfs',
1214 'hgext.lfs',
1213 'hgext.narrow',
1215 'hgext.narrow',
1214 'hgext.remotefilelog',
1216 'hgext.remotefilelog',
1215 'hgext.zeroconf',
1217 'hgext.zeroconf',
1216 'hgext3rd',
1218 'hgext3rd',
1217 'hgdemandimport',
1219 'hgdemandimport',
1218 ]
1220 ]
1219 if sys.version_info[0] == 2:
1221 if sys.version_info[0] == 2:
1220 packages.extend(
1222 packages.extend(
1221 [
1223 [
1222 'mercurial.thirdparty.concurrent',
1224 'mercurial.thirdparty.concurrent',
1223 'mercurial.thirdparty.concurrent.futures',
1225 'mercurial.thirdparty.concurrent.futures',
1224 ]
1226 ]
1225 )
1227 )
1226
1228
1227 if 'HG_PY2EXE_EXTRA_INSTALL_PACKAGES' in os.environ:
1229 if 'HG_PY2EXE_EXTRA_INSTALL_PACKAGES' in os.environ:
1228 # py2exe can't cope with namespace packages very well, so we have to
1230 # py2exe can't cope with namespace packages very well, so we have to
1229 # install any hgext3rd.* extensions that we want in the final py2exe
1231 # install any hgext3rd.* extensions that we want in the final py2exe
1230 # image here. This is gross, but you gotta do what you gotta do.
1232 # image here. This is gross, but you gotta do what you gotta do.
1231 packages.extend(os.environ['HG_PY2EXE_EXTRA_INSTALL_PACKAGES'].split(' '))
1233 packages.extend(os.environ['HG_PY2EXE_EXTRA_INSTALL_PACKAGES'].split(' '))
1232
1234
1233 common_depends = [
1235 common_depends = [
1234 'mercurial/bitmanipulation.h',
1236 'mercurial/bitmanipulation.h',
1235 'mercurial/compat.h',
1237 'mercurial/compat.h',
1236 'mercurial/cext/util.h',
1238 'mercurial/cext/util.h',
1237 ]
1239 ]
1238 common_include_dirs = ['mercurial']
1240 common_include_dirs = ['mercurial']
1239
1241
1240 osutil_cflags = []
1242 osutil_cflags = []
1241 osutil_ldflags = []
1243 osutil_ldflags = []
1242
1244
1243 # platform specific macros
1245 # platform specific macros
1244 for plat, func in [('bsd', 'setproctitle')]:
1246 for plat, func in [('bsd', 'setproctitle')]:
1245 if re.search(plat, sys.platform) and hasfunction(new_compiler(), func):
1247 if re.search(plat, sys.platform) and hasfunction(new_compiler(), func):
1246 osutil_cflags.append('-DHAVE_%s' % func.upper())
1248 osutil_cflags.append('-DHAVE_%s' % func.upper())
1247
1249
1248 for plat, macro, code in [
1250 for plat, macro, code in [
1249 (
1251 (
1250 'bsd|darwin',
1252 'bsd|darwin',
1251 'BSD_STATFS',
1253 'BSD_STATFS',
1252 '''
1254 '''
1253 #include <sys/param.h>
1255 #include <sys/param.h>
1254 #include <sys/mount.h>
1256 #include <sys/mount.h>
1255 int main() { struct statfs s; return sizeof(s.f_fstypename); }
1257 int main() { struct statfs s; return sizeof(s.f_fstypename); }
1256 ''',
1258 ''',
1257 ),
1259 ),
1258 (
1260 (
1259 'linux',
1261 'linux',
1260 'LINUX_STATFS',
1262 'LINUX_STATFS',
1261 '''
1263 '''
1262 #include <linux/magic.h>
1264 #include <linux/magic.h>
1263 #include <sys/vfs.h>
1265 #include <sys/vfs.h>
1264 int main() { struct statfs s; return sizeof(s.f_type); }
1266 int main() { struct statfs s; return sizeof(s.f_type); }
1265 ''',
1267 ''',
1266 ),
1268 ),
1267 ]:
1269 ]:
1268 if re.search(plat, sys.platform) and cancompile(new_compiler(), code):
1270 if re.search(plat, sys.platform) and cancompile(new_compiler(), code):
1269 osutil_cflags.append('-DHAVE_%s' % macro)
1271 osutil_cflags.append('-DHAVE_%s' % macro)
1270
1272
1271 if sys.platform == 'darwin':
1273 if sys.platform == 'darwin':
1272 osutil_ldflags += ['-framework', 'ApplicationServices']
1274 osutil_ldflags += ['-framework', 'ApplicationServices']
1273
1275
1274 xdiff_srcs = [
1276 xdiff_srcs = [
1275 'mercurial/thirdparty/xdiff/xdiffi.c',
1277 'mercurial/thirdparty/xdiff/xdiffi.c',
1276 'mercurial/thirdparty/xdiff/xprepare.c',
1278 'mercurial/thirdparty/xdiff/xprepare.c',
1277 'mercurial/thirdparty/xdiff/xutils.c',
1279 'mercurial/thirdparty/xdiff/xutils.c',
1278 ]
1280 ]
1279
1281
1280 xdiff_headers = [
1282 xdiff_headers = [
1281 'mercurial/thirdparty/xdiff/xdiff.h',
1283 'mercurial/thirdparty/xdiff/xdiff.h',
1282 'mercurial/thirdparty/xdiff/xdiffi.h',
1284 'mercurial/thirdparty/xdiff/xdiffi.h',
1283 'mercurial/thirdparty/xdiff/xinclude.h',
1285 'mercurial/thirdparty/xdiff/xinclude.h',
1284 'mercurial/thirdparty/xdiff/xmacros.h',
1286 'mercurial/thirdparty/xdiff/xmacros.h',
1285 'mercurial/thirdparty/xdiff/xprepare.h',
1287 'mercurial/thirdparty/xdiff/xprepare.h',
1286 'mercurial/thirdparty/xdiff/xtypes.h',
1288 'mercurial/thirdparty/xdiff/xtypes.h',
1287 'mercurial/thirdparty/xdiff/xutils.h',
1289 'mercurial/thirdparty/xdiff/xutils.h',
1288 ]
1290 ]
1289
1291
1290
1292
1291 class RustCompilationError(CCompilerError):
1293 class RustCompilationError(CCompilerError):
1292 """Exception class for Rust compilation errors."""
1294 """Exception class for Rust compilation errors."""
1293
1295
1294
1296
1295 class RustExtension(Extension):
1297 class RustExtension(Extension):
1296 """Base classes for concrete Rust Extension classes.
1298 """Base classes for concrete Rust Extension classes.
1297 """
1299 """
1298
1300
1299 rusttargetdir = os.path.join('rust', 'target', 'release')
1301 rusttargetdir = os.path.join('rust', 'target', 'release')
1300
1302
1301 def __init__(
1303 def __init__(
1302 self, mpath, sources, rustlibname, subcrate, py3_features=None, **kw
1304 self, mpath, sources, rustlibname, subcrate, py3_features=None, **kw
1303 ):
1305 ):
1304 Extension.__init__(self, mpath, sources, **kw)
1306 Extension.__init__(self, mpath, sources, **kw)
1305 srcdir = self.rustsrcdir = os.path.join('rust', subcrate)
1307 srcdir = self.rustsrcdir = os.path.join('rust', subcrate)
1306 self.py3_features = py3_features
1308 self.py3_features = py3_features
1307
1309
1308 # adding Rust source and control files to depends so that the extension
1310 # adding Rust source and control files to depends so that the extension
1309 # gets rebuilt if they've changed
1311 # gets rebuilt if they've changed
1310 self.depends.append(os.path.join(srcdir, 'Cargo.toml'))
1312 self.depends.append(os.path.join(srcdir, 'Cargo.toml'))
1311 cargo_lock = os.path.join(srcdir, 'Cargo.lock')
1313 cargo_lock = os.path.join(srcdir, 'Cargo.lock')
1312 if os.path.exists(cargo_lock):
1314 if os.path.exists(cargo_lock):
1313 self.depends.append(cargo_lock)
1315 self.depends.append(cargo_lock)
1314 for dirpath, subdir, fnames in os.walk(os.path.join(srcdir, 'src')):
1316 for dirpath, subdir, fnames in os.walk(os.path.join(srcdir, 'src')):
1315 self.depends.extend(
1317 self.depends.extend(
1316 os.path.join(dirpath, fname)
1318 os.path.join(dirpath, fname)
1317 for fname in fnames
1319 for fname in fnames
1318 if os.path.splitext(fname)[1] == '.rs'
1320 if os.path.splitext(fname)[1] == '.rs'
1319 )
1321 )
1320
1322
1321 @staticmethod
1323 @staticmethod
1322 def rustdylibsuffix():
1324 def rustdylibsuffix():
1323 """Return the suffix for shared libraries produced by rustc.
1325 """Return the suffix for shared libraries produced by rustc.
1324
1326
1325 See also: https://doc.rust-lang.org/reference/linkage.html
1327 See also: https://doc.rust-lang.org/reference/linkage.html
1326 """
1328 """
1327 if sys.platform == 'darwin':
1329 if sys.platform == 'darwin':
1328 return '.dylib'
1330 return '.dylib'
1329 elif os.name == 'nt':
1331 elif os.name == 'nt':
1330 return '.dll'
1332 return '.dll'
1331 else:
1333 else:
1332 return '.so'
1334 return '.so'
1333
1335
1334 def rustbuild(self):
1336 def rustbuild(self):
1335 env = os.environ.copy()
1337 env = os.environ.copy()
1336 if 'HGTEST_RESTOREENV' in env:
1338 if 'HGTEST_RESTOREENV' in env:
1337 # Mercurial tests change HOME to a temporary directory,
1339 # Mercurial tests change HOME to a temporary directory,
1338 # but, if installed with rustup, the Rust toolchain needs
1340 # but, if installed with rustup, the Rust toolchain needs
1339 # HOME to be correct (otherwise the 'no default toolchain'
1341 # HOME to be correct (otherwise the 'no default toolchain'
1340 # error message is issued and the build fails).
1342 # error message is issued and the build fails).
1341 # This happens currently with test-hghave.t, which does
1343 # This happens currently with test-hghave.t, which does
1342 # invoke this build.
1344 # invoke this build.
1343
1345
1344 # Unix only fix (os.path.expanduser not really reliable if
1346 # Unix only fix (os.path.expanduser not really reliable if
1345 # HOME is shadowed like this)
1347 # HOME is shadowed like this)
1346 import pwd
1348 import pwd
1347
1349
1348 env['HOME'] = pwd.getpwuid(os.getuid()).pw_dir
1350 env['HOME'] = pwd.getpwuid(os.getuid()).pw_dir
1349
1351
1350 cargocmd = ['cargo', 'rustc', '-vv', '--release']
1352 cargocmd = ['cargo', 'rustc', '-vv', '--release']
1351 if sys.version_info[0] == 3 and self.py3_features is not None:
1353 if sys.version_info[0] == 3 and self.py3_features is not None:
1352 cargocmd.extend(
1354 cargocmd.extend(
1353 ('--features', self.py3_features, '--no-default-features')
1355 ('--features', self.py3_features, '--no-default-features')
1354 )
1356 )
1355 cargocmd.append('--')
1357 cargocmd.append('--')
1356 if sys.platform == 'darwin':
1358 if sys.platform == 'darwin':
1357 cargocmd.extend(
1359 cargocmd.extend(
1358 ("-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup")
1360 ("-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup")
1359 )
1361 )
1360 try:
1362 try:
1361 subprocess.check_call(cargocmd, env=env, cwd=self.rustsrcdir)
1363 subprocess.check_call(cargocmd, env=env, cwd=self.rustsrcdir)
1362 except OSError as exc:
1364 except OSError as exc:
1363 if exc.errno == errno.ENOENT:
1365 if exc.errno == errno.ENOENT:
1364 raise RustCompilationError("Cargo not found")
1366 raise RustCompilationError("Cargo not found")
1365 elif exc.errno == errno.EACCES:
1367 elif exc.errno == errno.EACCES:
1366 raise RustCompilationError(
1368 raise RustCompilationError(
1367 "Cargo found, but permisssion to execute it is denied"
1369 "Cargo found, but permisssion to execute it is denied"
1368 )
1370 )
1369 else:
1371 else:
1370 raise
1372 raise
1371 except subprocess.CalledProcessError:
1373 except subprocess.CalledProcessError:
1372 raise RustCompilationError(
1374 raise RustCompilationError(
1373 "Cargo failed. Working directory: %r, "
1375 "Cargo failed. Working directory: %r, "
1374 "command: %r, environment: %r"
1376 "command: %r, environment: %r"
1375 % (self.rustsrcdir, cargocmd, env)
1377 % (self.rustsrcdir, cargocmd, env)
1376 )
1378 )
1377
1379
1378
1380
1379 class RustEnhancedExtension(RustExtension):
1381 class RustEnhancedExtension(RustExtension):
1380 """A C Extension, conditionally enhanced with Rust code.
1382 """A C Extension, conditionally enhanced with Rust code.
1381
1383
1382 If the HGRUSTEXT environment variable is set to something else
1384 If the HGRUSTEXT environment variable is set to something else
1383 than 'cpython', the Rust sources get compiled and linked within the
1385 than 'cpython', the Rust sources get compiled and linked within the
1384 C target shared library object.
1386 C target shared library object.
1385 """
1387 """
1386
1388
1387 def __init__(self, mpath, sources, rustlibname, subcrate, **kw):
1389 def __init__(self, mpath, sources, rustlibname, subcrate, **kw):
1388 RustExtension.__init__(
1390 RustExtension.__init__(
1389 self, mpath, sources, rustlibname, subcrate, **kw
1391 self, mpath, sources, rustlibname, subcrate, **kw
1390 )
1392 )
1391 if hgrustext != 'direct-ffi':
1393 if hgrustext != 'direct-ffi':
1392 return
1394 return
1393 self.extra_compile_args.append('-DWITH_RUST')
1395 self.extra_compile_args.append('-DWITH_RUST')
1394 self.libraries.append(rustlibname)
1396 self.libraries.append(rustlibname)
1395 self.library_dirs.append(self.rusttargetdir)
1397 self.library_dirs.append(self.rusttargetdir)
1396
1398
1397 def rustbuild(self):
1399 def rustbuild(self):
1398 if hgrustext == 'direct-ffi':
1400 if hgrustext == 'direct-ffi':
1399 RustExtension.rustbuild(self)
1401 RustExtension.rustbuild(self)
1400
1402
1401
1403
1402 class RustStandaloneExtension(RustExtension):
1404 class RustStandaloneExtension(RustExtension):
1403 def __init__(self, pydottedname, rustcrate, dylibname, **kw):
1405 def __init__(self, pydottedname, rustcrate, dylibname, **kw):
1404 RustExtension.__init__(
1406 RustExtension.__init__(
1405 self, pydottedname, [], dylibname, rustcrate, **kw
1407 self, pydottedname, [], dylibname, rustcrate, **kw
1406 )
1408 )
1407 self.dylibname = dylibname
1409 self.dylibname = dylibname
1408
1410
1409 def build(self, target_dir):
1411 def build(self, target_dir):
1410 self.rustbuild()
1412 self.rustbuild()
1411 target = [target_dir]
1413 target = [target_dir]
1412 target.extend(self.name.split('.'))
1414 target.extend(self.name.split('.'))
1413 target[-1] += DYLIB_SUFFIX
1415 target[-1] += DYLIB_SUFFIX
1414 shutil.copy2(
1416 shutil.copy2(
1415 os.path.join(
1417 os.path.join(
1416 self.rusttargetdir, self.dylibname + self.rustdylibsuffix()
1418 self.rusttargetdir, self.dylibname + self.rustdylibsuffix()
1417 ),
1419 ),
1418 os.path.join(*target),
1420 os.path.join(*target),
1419 )
1421 )
1420
1422
1421
1423
1422 extmodules = [
1424 extmodules = [
1423 Extension(
1425 Extension(
1424 'mercurial.cext.base85',
1426 'mercurial.cext.base85',
1425 ['mercurial/cext/base85.c'],
1427 ['mercurial/cext/base85.c'],
1426 include_dirs=common_include_dirs,
1428 include_dirs=common_include_dirs,
1427 depends=common_depends,
1429 depends=common_depends,
1428 ),
1430 ),
1429 Extension(
1431 Extension(
1430 'mercurial.cext.bdiff',
1432 'mercurial.cext.bdiff',
1431 ['mercurial/bdiff.c', 'mercurial/cext/bdiff.c'] + xdiff_srcs,
1433 ['mercurial/bdiff.c', 'mercurial/cext/bdiff.c'] + xdiff_srcs,
1432 include_dirs=common_include_dirs,
1434 include_dirs=common_include_dirs,
1433 depends=common_depends + ['mercurial/bdiff.h'] + xdiff_headers,
1435 depends=common_depends + ['mercurial/bdiff.h'] + xdiff_headers,
1434 ),
1436 ),
1435 Extension(
1437 Extension(
1436 'mercurial.cext.mpatch',
1438 'mercurial.cext.mpatch',
1437 ['mercurial/mpatch.c', 'mercurial/cext/mpatch.c'],
1439 ['mercurial/mpatch.c', 'mercurial/cext/mpatch.c'],
1438 include_dirs=common_include_dirs,
1440 include_dirs=common_include_dirs,
1439 depends=common_depends,
1441 depends=common_depends,
1440 ),
1442 ),
1441 RustEnhancedExtension(
1443 RustEnhancedExtension(
1442 'mercurial.cext.parsers',
1444 'mercurial.cext.parsers',
1443 [
1445 [
1444 'mercurial/cext/charencode.c',
1446 'mercurial/cext/charencode.c',
1445 'mercurial/cext/dirs.c',
1447 'mercurial/cext/dirs.c',
1446 'mercurial/cext/manifest.c',
1448 'mercurial/cext/manifest.c',
1447 'mercurial/cext/parsers.c',
1449 'mercurial/cext/parsers.c',
1448 'mercurial/cext/pathencode.c',
1450 'mercurial/cext/pathencode.c',
1449 'mercurial/cext/revlog.c',
1451 'mercurial/cext/revlog.c',
1450 ],
1452 ],
1451 'hgdirectffi',
1453 'hgdirectffi',
1452 'hg-direct-ffi',
1454 'hg-direct-ffi',
1453 include_dirs=common_include_dirs,
1455 include_dirs=common_include_dirs,
1454 depends=common_depends
1456 depends=common_depends
1455 + [
1457 + [
1456 'mercurial/cext/charencode.h',
1458 'mercurial/cext/charencode.h',
1457 'mercurial/cext/revlog.h',
1459 'mercurial/cext/revlog.h',
1458 'rust/hg-core/src/ancestors.rs',
1460 'rust/hg-core/src/ancestors.rs',
1459 'rust/hg-core/src/lib.rs',
1461 'rust/hg-core/src/lib.rs',
1460 ],
1462 ],
1461 ),
1463 ),
1462 Extension(
1464 Extension(
1463 'mercurial.cext.osutil',
1465 'mercurial.cext.osutil',
1464 ['mercurial/cext/osutil.c'],
1466 ['mercurial/cext/osutil.c'],
1465 include_dirs=common_include_dirs,
1467 include_dirs=common_include_dirs,
1466 extra_compile_args=osutil_cflags,
1468 extra_compile_args=osutil_cflags,
1467 extra_link_args=osutil_ldflags,
1469 extra_link_args=osutil_ldflags,
1468 depends=common_depends,
1470 depends=common_depends,
1469 ),
1471 ),
1470 Extension(
1472 Extension(
1471 'mercurial.thirdparty.zope.interface._zope_interface_coptimizations',
1473 'mercurial.thirdparty.zope.interface._zope_interface_coptimizations',
1472 [
1474 [
1473 'mercurial/thirdparty/zope/interface/_zope_interface_coptimizations.c',
1475 'mercurial/thirdparty/zope/interface/_zope_interface_coptimizations.c',
1474 ],
1476 ],
1475 ),
1477 ),
1476 Extension(
1478 Extension(
1477 'hgext.fsmonitor.pywatchman.bser', ['hgext/fsmonitor/pywatchman/bser.c']
1479 'hgext.fsmonitor.pywatchman.bser', ['hgext/fsmonitor/pywatchman/bser.c']
1478 ),
1480 ),
1479 RustStandaloneExtension(
1481 RustStandaloneExtension(
1480 'mercurial.rustext', 'hg-cpython', 'librusthg', py3_features='python3'
1482 'mercurial.rustext', 'hg-cpython', 'librusthg', py3_features='python3'
1481 ),
1483 ),
1482 ]
1484 ]
1483
1485
1484
1486
1485 sys.path.insert(0, 'contrib/python-zstandard')
1487 sys.path.insert(0, 'contrib/python-zstandard')
1486 import setup_zstd
1488 import setup_zstd
1487
1489
1488 extmodules.append(
1490 extmodules.append(
1489 setup_zstd.get_c_extension(
1491 setup_zstd.get_c_extension(
1490 name='mercurial.zstd', root=os.path.abspath(os.path.dirname(__file__))
1492 name='mercurial.zstd', root=os.path.abspath(os.path.dirname(__file__))
1491 )
1493 )
1492 )
1494 )
1493
1495
1494 try:
1496 try:
1495 from distutils import cygwinccompiler
1497 from distutils import cygwinccompiler
1496
1498
1497 # the -mno-cygwin option has been deprecated for years
1499 # the -mno-cygwin option has been deprecated for years
1498 mingw32compilerclass = cygwinccompiler.Mingw32CCompiler
1500 mingw32compilerclass = cygwinccompiler.Mingw32CCompiler
1499
1501
1500 class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
1502 class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
1501 def __init__(self, *args, **kwargs):
1503 def __init__(self, *args, **kwargs):
1502 mingw32compilerclass.__init__(self, *args, **kwargs)
1504 mingw32compilerclass.__init__(self, *args, **kwargs)
1503 for i in 'compiler compiler_so linker_exe linker_so'.split():
1505 for i in 'compiler compiler_so linker_exe linker_so'.split():
1504 try:
1506 try:
1505 getattr(self, i).remove('-mno-cygwin')
1507 getattr(self, i).remove('-mno-cygwin')
1506 except ValueError:
1508 except ValueError:
1507 pass
1509 pass
1508
1510
1509 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
1511 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
1510 except ImportError:
1512 except ImportError:
1511 # the cygwinccompiler package is not available on some Python
1513 # the cygwinccompiler package is not available on some Python
1512 # distributions like the ones from the optware project for Synology
1514 # distributions like the ones from the optware project for Synology
1513 # DiskStation boxes
1515 # DiskStation boxes
1514 class HackedMingw32CCompiler(object):
1516 class HackedMingw32CCompiler(object):
1515 pass
1517 pass
1516
1518
1517
1519
1518 if os.name == 'nt':
1520 if os.name == 'nt':
1519 # Allow compiler/linker flags to be added to Visual Studio builds. Passing
1521 # Allow compiler/linker flags to be added to Visual Studio builds. Passing
1520 # extra_link_args to distutils.extensions.Extension() doesn't have any
1522 # extra_link_args to distutils.extensions.Extension() doesn't have any
1521 # effect.
1523 # effect.
1522 from distutils import msvccompiler
1524 from distutils import msvccompiler
1523
1525
1524 msvccompilerclass = msvccompiler.MSVCCompiler
1526 msvccompilerclass = msvccompiler.MSVCCompiler
1525
1527
1526 class HackedMSVCCompiler(msvccompiler.MSVCCompiler):
1528 class HackedMSVCCompiler(msvccompiler.MSVCCompiler):
1527 def initialize(self):
1529 def initialize(self):
1528 msvccompilerclass.initialize(self)
1530 msvccompilerclass.initialize(self)
1529 # "warning LNK4197: export 'func' specified multiple times"
1531 # "warning LNK4197: export 'func' specified multiple times"
1530 self.ldflags_shared.append('/ignore:4197')
1532 self.ldflags_shared.append('/ignore:4197')
1531 self.ldflags_shared_debug.append('/ignore:4197')
1533 self.ldflags_shared_debug.append('/ignore:4197')
1532
1534
1533 msvccompiler.MSVCCompiler = HackedMSVCCompiler
1535 msvccompiler.MSVCCompiler = HackedMSVCCompiler
1534
1536
1535 packagedata = {
1537 packagedata = {
1536 'mercurial': [
1538 'mercurial': [
1537 'locale/*/LC_MESSAGES/hg.mo',
1539 'locale/*/LC_MESSAGES/hg.mo',
1538 'help/*.txt',
1539 'help/internals/*.txt',
1540 'default.d/*.rc',
1540 'default.d/*.rc',
1541 'dummycert.pem',
1541 'dummycert.pem',
1542 ]
1542 ],
1543 'mercurial.helptext': ['*.txt',],
1544 'mercurial.helptext.internals': ['*.txt',],
1543 }
1545 }
1544
1546
1545
1547
1546 def ordinarypath(p):
1548 def ordinarypath(p):
1547 return p and p[0] != '.' and p[-1] != '~'
1549 return p and p[0] != '.' and p[-1] != '~'
1548
1550
1549
1551
1550 for root in ('templates',):
1552 for root in ('templates',):
1551 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
1553 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
1552 curdir = curdir.split(os.sep, 1)[1]
1554 curdir = curdir.split(os.sep, 1)[1]
1553 dirs[:] = filter(ordinarypath, dirs)
1555 dirs[:] = filter(ordinarypath, dirs)
1554 for f in filter(ordinarypath, files):
1556 for f in filter(ordinarypath, files):
1555 f = os.path.join(curdir, f)
1557 f = os.path.join(curdir, f)
1556 packagedata['mercurial'].append(f)
1558 packagedata['mercurial'].append(f)
1557
1559
1558 datafiles = []
1560 datafiles = []
1559
1561
1560 # distutils expects version to be str/unicode. Converting it to
1562 # distutils expects version to be str/unicode. Converting it to
1561 # unicode on Python 2 still works because it won't contain any
1563 # unicode on Python 2 still works because it won't contain any
1562 # non-ascii bytes and will be implicitly converted back to bytes
1564 # non-ascii bytes and will be implicitly converted back to bytes
1563 # when operated on.
1565 # when operated on.
1564 assert isinstance(version, bytes)
1566 assert isinstance(version, bytes)
1565 setupversion = version.decode('ascii')
1567 setupversion = version.decode('ascii')
1566
1568
1567 extra = {}
1569 extra = {}
1568
1570
1569 py2exepackages = [
1571 py2exepackages = [
1570 'hgdemandimport',
1572 'hgdemandimport',
1571 'hgext3rd',
1573 'hgext3rd',
1572 'hgext',
1574 'hgext',
1573 'email',
1575 'email',
1574 # implicitly imported per module policy
1576 # implicitly imported per module policy
1575 # (cffi wouldn't be used as a frozen exe)
1577 # (cffi wouldn't be used as a frozen exe)
1576 'mercurial.cext',
1578 'mercurial.cext',
1577 #'mercurial.cffi',
1579 #'mercurial.cffi',
1578 'mercurial.pure',
1580 'mercurial.pure',
1579 ]
1581 ]
1580
1582
1581 py2exeexcludes = []
1583 py2exeexcludes = []
1582 py2exedllexcludes = ['crypt32.dll']
1584 py2exedllexcludes = ['crypt32.dll']
1583
1585
1584 if issetuptools:
1586 if issetuptools:
1585 extra['python_requires'] = supportedpy
1587 extra['python_requires'] = supportedpy
1586
1588
1587 if py2exeloaded:
1589 if py2exeloaded:
1588 extra['console'] = [
1590 extra['console'] = [
1589 {
1591 {
1590 'script': 'hg',
1592 'script': 'hg',
1591 'copyright': 'Copyright (C) 2005-2019 Matt Mackall and others',
1593 'copyright': 'Copyright (C) 2005-2019 Matt Mackall and others',
1592 'product_version': version,
1594 'product_version': version,
1593 }
1595 }
1594 ]
1596 ]
1595 # Sub command of 'build' because 'py2exe' does not handle sub_commands.
1597 # Sub command of 'build' because 'py2exe' does not handle sub_commands.
1596 # Need to override hgbuild because it has a private copy of
1598 # Need to override hgbuild because it has a private copy of
1597 # build.sub_commands.
1599 # build.sub_commands.
1598 hgbuild.sub_commands.insert(0, ('build_hgextindex', None))
1600 hgbuild.sub_commands.insert(0, ('build_hgextindex', None))
1599 # put dlls in sub directory so that they won't pollute PATH
1601 # put dlls in sub directory so that they won't pollute PATH
1600 extra['zipfile'] = 'lib/library.zip'
1602 extra['zipfile'] = 'lib/library.zip'
1601
1603
1602 # We allow some configuration to be supplemented via environment
1604 # We allow some configuration to be supplemented via environment
1603 # variables. This is better than setup.cfg files because it allows
1605 # variables. This is better than setup.cfg files because it allows
1604 # supplementing configs instead of replacing them.
1606 # supplementing configs instead of replacing them.
1605 extrapackages = os.environ.get('HG_PY2EXE_EXTRA_PACKAGES')
1607 extrapackages = os.environ.get('HG_PY2EXE_EXTRA_PACKAGES')
1606 if extrapackages:
1608 if extrapackages:
1607 py2exepackages.extend(extrapackages.split(' '))
1609 py2exepackages.extend(extrapackages.split(' '))
1608
1610
1609 excludes = os.environ.get('HG_PY2EXE_EXTRA_EXCLUDES')
1611 excludes = os.environ.get('HG_PY2EXE_EXTRA_EXCLUDES')
1610 if excludes:
1612 if excludes:
1611 py2exeexcludes.extend(excludes.split(' '))
1613 py2exeexcludes.extend(excludes.split(' '))
1612
1614
1613 dllexcludes = os.environ.get('HG_PY2EXE_EXTRA_DLL_EXCLUDES')
1615 dllexcludes = os.environ.get('HG_PY2EXE_EXTRA_DLL_EXCLUDES')
1614 if dllexcludes:
1616 if dllexcludes:
1615 py2exedllexcludes.extend(dllexcludes.split(' '))
1617 py2exedllexcludes.extend(dllexcludes.split(' '))
1616
1618
1617 if os.name == 'nt':
1619 if os.name == 'nt':
1618 # Windows binary file versions for exe/dll files must have the
1620 # Windows binary file versions for exe/dll files must have the
1619 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
1621 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
1620 setupversion = setupversion.split(r'+', 1)[0]
1622 setupversion = setupversion.split(r'+', 1)[0]
1621
1623
1622 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
1624 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
1623 version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[1].splitlines()
1625 version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[1].splitlines()
1624 if version:
1626 if version:
1625 version = version[0]
1627 version = version[0]
1626 if sys.version_info[0] == 3:
1628 if sys.version_info[0] == 3:
1627 version = version.decode('utf-8')
1629 version = version.decode('utf-8')
1628 xcode4 = version.startswith('Xcode') and StrictVersion(
1630 xcode4 = version.startswith('Xcode') and StrictVersion(
1629 version.split()[1]
1631 version.split()[1]
1630 ) >= StrictVersion('4.0')
1632 ) >= StrictVersion('4.0')
1631 xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None
1633 xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None
1632 else:
1634 else:
1633 # xcodebuild returns empty on OS X Lion with XCode 4.3 not
1635 # xcodebuild returns empty on OS X Lion with XCode 4.3 not
1634 # installed, but instead with only command-line tools. Assume
1636 # installed, but instead with only command-line tools. Assume
1635 # that only happens on >= Lion, thus no PPC support.
1637 # that only happens on >= Lion, thus no PPC support.
1636 xcode4 = True
1638 xcode4 = True
1637 xcode51 = False
1639 xcode51 = False
1638
1640
1639 # XCode 4.0 dropped support for ppc architecture, which is hardcoded in
1641 # XCode 4.0 dropped support for ppc architecture, which is hardcoded in
1640 # distutils.sysconfig
1642 # distutils.sysconfig
1641 if xcode4:
1643 if xcode4:
1642 os.environ['ARCHFLAGS'] = ''
1644 os.environ['ARCHFLAGS'] = ''
1643
1645
1644 # XCode 5.1 changes clang such that it now fails to compile if the
1646 # XCode 5.1 changes clang such that it now fails to compile if the
1645 # -mno-fused-madd flag is passed, but the version of Python shipped with
1647 # -mno-fused-madd flag is passed, but the version of Python shipped with
1646 # OS X 10.9 Mavericks includes this flag. This causes problems in all
1648 # OS X 10.9 Mavericks includes this flag. This causes problems in all
1647 # C extension modules, and a bug has been filed upstream at
1649 # C extension modules, and a bug has been filed upstream at
1648 # http://bugs.python.org/issue21244. We also need to patch this here
1650 # http://bugs.python.org/issue21244. We also need to patch this here
1649 # so Mercurial can continue to compile in the meantime.
1651 # so Mercurial can continue to compile in the meantime.
1650 if xcode51:
1652 if xcode51:
1651 cflags = get_config_var('CFLAGS')
1653 cflags = get_config_var('CFLAGS')
1652 if cflags and re.search(r'-mno-fused-madd\b', cflags) is not None:
1654 if cflags and re.search(r'-mno-fused-madd\b', cflags) is not None:
1653 os.environ['CFLAGS'] = (
1655 os.environ['CFLAGS'] = (
1654 os.environ.get('CFLAGS', '') + ' -Qunused-arguments'
1656 os.environ.get('CFLAGS', '') + ' -Qunused-arguments'
1655 )
1657 )
1656
1658
1657 setup(
1659 setup(
1658 name='mercurial',
1660 name='mercurial',
1659 version=setupversion,
1661 version=setupversion,
1660 author='Matt Mackall and many others',
1662 author='Matt Mackall and many others',
1661 author_email='mercurial@mercurial-scm.org',
1663 author_email='mercurial@mercurial-scm.org',
1662 url='https://mercurial-scm.org/',
1664 url='https://mercurial-scm.org/',
1663 download_url='https://mercurial-scm.org/release/',
1665 download_url='https://mercurial-scm.org/release/',
1664 description=(
1666 description=(
1665 'Fast scalable distributed SCM (revision control, version '
1667 'Fast scalable distributed SCM (revision control, version '
1666 'control) system'
1668 'control) system'
1667 ),
1669 ),
1668 long_description=(
1670 long_description=(
1669 'Mercurial is a distributed SCM tool written in Python.'
1671 'Mercurial is a distributed SCM tool written in Python.'
1670 ' It is used by a number of large projects that require'
1672 ' It is used by a number of large projects that require'
1671 ' fast, reliable distributed revision control, such as '
1673 ' fast, reliable distributed revision control, such as '
1672 'Mozilla.'
1674 'Mozilla.'
1673 ),
1675 ),
1674 license='GNU GPLv2 or any later version',
1676 license='GNU GPLv2 or any later version',
1675 classifiers=[
1677 classifiers=[
1676 'Development Status :: 6 - Mature',
1678 'Development Status :: 6 - Mature',
1677 'Environment :: Console',
1679 'Environment :: Console',
1678 'Intended Audience :: Developers',
1680 'Intended Audience :: Developers',
1679 'Intended Audience :: System Administrators',
1681 'Intended Audience :: System Administrators',
1680 'License :: OSI Approved :: GNU General Public License (GPL)',
1682 'License :: OSI Approved :: GNU General Public License (GPL)',
1681 'Natural Language :: Danish',
1683 'Natural Language :: Danish',
1682 'Natural Language :: English',
1684 'Natural Language :: English',
1683 'Natural Language :: German',
1685 'Natural Language :: German',
1684 'Natural Language :: Italian',
1686 'Natural Language :: Italian',
1685 'Natural Language :: Japanese',
1687 'Natural Language :: Japanese',
1686 'Natural Language :: Portuguese (Brazilian)',
1688 'Natural Language :: Portuguese (Brazilian)',
1687 'Operating System :: Microsoft :: Windows',
1689 'Operating System :: Microsoft :: Windows',
1688 'Operating System :: OS Independent',
1690 'Operating System :: OS Independent',
1689 'Operating System :: POSIX',
1691 'Operating System :: POSIX',
1690 'Programming Language :: C',
1692 'Programming Language :: C',
1691 'Programming Language :: Python',
1693 'Programming Language :: Python',
1692 'Topic :: Software Development :: Version Control',
1694 'Topic :: Software Development :: Version Control',
1693 ],
1695 ],
1694 scripts=scripts,
1696 scripts=scripts,
1695 packages=packages,
1697 packages=packages,
1696 ext_modules=extmodules,
1698 ext_modules=extmodules,
1697 data_files=datafiles,
1699 data_files=datafiles,
1698 package_data=packagedata,
1700 package_data=packagedata,
1699 cmdclass=cmdclass,
1701 cmdclass=cmdclass,
1700 distclass=hgdist,
1702 distclass=hgdist,
1701 options={
1703 options={
1702 'py2exe': {
1704 'py2exe': {
1703 'bundle_files': 3,
1705 'bundle_files': 3,
1704 'dll_excludes': py2exedllexcludes,
1706 'dll_excludes': py2exedllexcludes,
1705 'excludes': py2exeexcludes,
1707 'excludes': py2exeexcludes,
1706 'packages': py2exepackages,
1708 'packages': py2exepackages,
1707 },
1709 },
1708 'bdist_mpkg': {
1710 'bdist_mpkg': {
1709 'zipdist': False,
1711 'zipdist': False,
1710 'license': 'COPYING',
1712 'license': 'COPYING',
1711 'readme': 'contrib/packaging/macosx/Readme.html',
1713 'readme': 'contrib/packaging/macosx/Readme.html',
1712 'welcome': 'contrib/packaging/macosx/Welcome.html',
1714 'welcome': 'contrib/packaging/macosx/Welcome.html',
1713 },
1715 },
1714 },
1716 },
1715 **extra
1717 **extra
1716 )
1718 )
@@ -1,47 +1,47 b''
1 #require test-repo
1 #require test-repo
2
2
3 $ . "$TESTDIR/helpers-testrepo.sh"
3 $ . "$TESTDIR/helpers-testrepo.sh"
4
4
5 Sanity check check-config.py
5 Sanity check check-config.py
6
6
7 $ cat > testfile.py << EOF
7 $ cat > testfile.py << EOF
8 > # Good
8 > # Good
9 > foo = ui.config('ui', 'username')
9 > foo = ui.config('ui', 'username')
10 > # Missing
10 > # Missing
11 > foo = ui.config('ui', 'doesnotexist')
11 > foo = ui.config('ui', 'doesnotexist')
12 > # Missing different type
12 > # Missing different type
13 > foo = ui.configint('ui', 'missingint')
13 > foo = ui.configint('ui', 'missingint')
14 > # Missing with default value
14 > # Missing with default value
15 > foo = ui.configbool('ui', 'missingbool1', default=True)
15 > foo = ui.configbool('ui', 'missingbool1', default=True)
16 > foo = ui.configbool('ui', 'missingbool2', False)
16 > foo = ui.configbool('ui', 'missingbool2', False)
17 > # Inconsistent values for defaults.
17 > # Inconsistent values for defaults.
18 > foo = ui.configint('ui', 'intdefault', default=1)
18 > foo = ui.configint('ui', 'intdefault', default=1)
19 > foo = ui.configint('ui', 'intdefault', default=42)
19 > foo = ui.configint('ui', 'intdefault', default=42)
20 > # Can suppress inconsistent value error
20 > # Can suppress inconsistent value error
21 > foo = ui.configint('ui', 'intdefault2', default=1)
21 > foo = ui.configint('ui', 'intdefault2', default=1)
22 > # inconsistent config: ui.intdefault2
22 > # inconsistent config: ui.intdefault2
23 > foo = ui.configint('ui', 'intdefault2', default=42)
23 > foo = ui.configint('ui', 'intdefault2', default=42)
24 > EOF
24 > EOF
25
25
26 $ cat > files << EOF
26 $ cat > files << EOF
27 > mercurial/help/config.txt
27 > mercurial/helptext/config.txt
28 > $TESTTMP/testfile.py
28 > $TESTTMP/testfile.py
29 > EOF
29 > EOF
30
30
31 $ cd "$TESTDIR"/..
31 $ cd "$TESTDIR"/..
32
32
33 $ "$PYTHON" contrib/check-config.py < $TESTTMP/files
33 $ "$PYTHON" contrib/check-config.py < $TESTTMP/files
34 foo = ui.configint('ui', 'intdefault', default=42)
34 foo = ui.configint('ui', 'intdefault', default=42)
35 conflict on ui.intdefault: ('int', '42') != ('int', '1')
35 conflict on ui.intdefault: ('int', '42') != ('int', '1')
36 at $TESTTMP/testfile.py:12:
36 at $TESTTMP/testfile.py:12:
37 undocumented: ui.doesnotexist (str)
37 undocumented: ui.doesnotexist (str)
38 undocumented: ui.intdefault (int) [42]
38 undocumented: ui.intdefault (int) [42]
39 undocumented: ui.intdefault2 (int) [42]
39 undocumented: ui.intdefault2 (int) [42]
40 undocumented: ui.missingbool1 (bool) [True]
40 undocumented: ui.missingbool1 (bool) [True]
41 undocumented: ui.missingbool2 (bool)
41 undocumented: ui.missingbool2 (bool)
42 undocumented: ui.missingint (int)
42 undocumented: ui.missingint (int)
43
43
44 New errors are not allowed. Warnings are strongly discouraged.
44 New errors are not allowed. Warnings are strongly discouraged.
45
45
46 $ testrepohg files "set:(**.py or **.txt) - tests/**" | sed 's|\\|/|g' |
46 $ testrepohg files "set:(**.py or **.txt) - tests/**" | sed 's|\\|/|g' |
47 > "$PYTHON" contrib/check-config.py
47 > "$PYTHON" contrib/check-config.py
General Comments 0
You need to be logged in to leave comments. Login now