##// END OF EJS Templates
cleanup: fix a few recent black formatting violations...
Matt Harbison -
r46551:66f6ca2b default
parent child Browse files
Show More
@@ -1,535 +1,533 b''
1 from __future__ import absolute_import
1 from __future__ import absolute_import
2
2
3 from mercurial.i18n import _
3 from mercurial.i18n import _
4
4
5 from mercurial import (
5 from mercurial import (
6 ancestor,
6 ancestor,
7 changelog as hgchangelog,
7 changelog as hgchangelog,
8 dagop,
8 dagop,
9 encoding,
9 encoding,
10 error,
10 error,
11 manifest,
11 manifest,
12 node as nodemod,
12 node as nodemod,
13 pycompat,
13 pycompat,
14 )
14 )
15 from mercurial.interfaces import (
15 from mercurial.interfaces import (
16 repository,
16 repository,
17 util as interfaceutil,
17 util as interfaceutil,
18 )
18 )
19 from mercurial.utils import stringutil
19 from mercurial.utils import stringutil
20 from . import (
20 from . import (
21 gitutil,
21 gitutil,
22 index,
22 index,
23 manifest as gitmanifest,
23 manifest as gitmanifest,
24 )
24 )
25
25
26 pygit2 = gitutil.get_pygit2()
26 pygit2 = gitutil.get_pygit2()
27
27
28
28
29 class baselog(object): # revlog.revlog):
29 class baselog(object): # revlog.revlog):
30 """Common implementations between changelog and manifestlog."""
30 """Common implementations between changelog and manifestlog."""
31
31
32 def __init__(self, gr, db):
32 def __init__(self, gr, db):
33 self.gitrepo = gr
33 self.gitrepo = gr
34 self._db = db
34 self._db = db
35
35
36 def __len__(self):
36 def __len__(self):
37 return int(
37 return int(
38 self._db.execute('SELECT COUNT(*) FROM changelog').fetchone()[0]
38 self._db.execute('SELECT COUNT(*) FROM changelog').fetchone()[0]
39 )
39 )
40
40
41 def rev(self, n):
41 def rev(self, n):
42 if n == nodemod.nullid:
42 if n == nodemod.nullid:
43 return -1
43 return -1
44 t = self._db.execute(
44 t = self._db.execute(
45 'SELECT rev FROM changelog WHERE node = ?', (gitutil.togitnode(n),)
45 'SELECT rev FROM changelog WHERE node = ?', (gitutil.togitnode(n),)
46 ).fetchone()
46 ).fetchone()
47 if t is None:
47 if t is None:
48 raise error.LookupError(n, b'00changelog.i', _(b'no node %d'))
48 raise error.LookupError(n, b'00changelog.i', _(b'no node %d'))
49 return t[0]
49 return t[0]
50
50
51 def node(self, r):
51 def node(self, r):
52 if r == nodemod.nullrev:
52 if r == nodemod.nullrev:
53 return nodemod.nullid
53 return nodemod.nullid
54 t = self._db.execute(
54 t = self._db.execute(
55 'SELECT node FROM changelog WHERE rev = ?', (r,)
55 'SELECT node FROM changelog WHERE rev = ?', (r,)
56 ).fetchone()
56 ).fetchone()
57 if t is None:
57 if t is None:
58 raise error.LookupError(r, b'00changelog.i', _(b'no node'))
58 raise error.LookupError(r, b'00changelog.i', _(b'no node'))
59 return nodemod.bin(t[0])
59 return nodemod.bin(t[0])
60
60
61 def hasnode(self, n):
61 def hasnode(self, n):
62 t = self._db.execute(
62 t = self._db.execute(
63 'SELECT node FROM changelog WHERE node = ?', (n,)
63 'SELECT node FROM changelog WHERE node = ?', (n,)
64 ).fetchone()
64 ).fetchone()
65 return t is not None
65 return t is not None
66
66
67
67
68 class baselogindex(object):
68 class baselogindex(object):
69 def __init__(self, log):
69 def __init__(self, log):
70 self._log = log
70 self._log = log
71
71
72 def has_node(self, n):
72 def has_node(self, n):
73 return self._log.rev(n) != -1
73 return self._log.rev(n) != -1
74
74
75 def __len__(self):
75 def __len__(self):
76 return len(self._log)
76 return len(self._log)
77
77
78 def __getitem__(self, idx):
78 def __getitem__(self, idx):
79 p1rev, p2rev = self._log.parentrevs(idx)
79 p1rev, p2rev = self._log.parentrevs(idx)
80 # TODO: it's messy that the index leaks so far out of the
80 # TODO: it's messy that the index leaks so far out of the
81 # storage layer that we have to implement things like reading
81 # storage layer that we have to implement things like reading
82 # this raw tuple, which exposes revlog internals.
82 # this raw tuple, which exposes revlog internals.
83 return (
83 return (
84 # Pretend offset is just the index, since we don't really care.
84 # Pretend offset is just the index, since we don't really care.
85 idx,
85 idx,
86 # Same with lengths
86 # Same with lengths
87 idx, # length
87 idx, # length
88 idx, # rawsize
88 idx, # rawsize
89 -1, # delta base
89 -1, # delta base
90 idx, # linkrev TODO is this right?
90 idx, # linkrev TODO is this right?
91 p1rev,
91 p1rev,
92 p2rev,
92 p2rev,
93 self._log.node(idx),
93 self._log.node(idx),
94 )
94 )
95
95
96
96
97 # TODO: an interface for the changelog type?
97 # TODO: an interface for the changelog type?
98 class changelog(baselog):
98 class changelog(baselog):
99 # TODO: this appears to be an enumerated type, and should probably
99 # TODO: this appears to be an enumerated type, and should probably
100 # be part of the public changelog interface
100 # be part of the public changelog interface
101 _copiesstorage = b'extra'
101 _copiesstorage = b'extra'
102
102
103 def __contains__(self, rev):
103 def __contains__(self, rev):
104 try:
104 try:
105 self.node(rev)
105 self.node(rev)
106 return True
106 return True
107 except error.LookupError:
107 except error.LookupError:
108 return False
108 return False
109
109
110 def __iter__(self):
110 def __iter__(self):
111 return iter(pycompat.xrange(len(self)))
111 return iter(pycompat.xrange(len(self)))
112
112
113 @property
113 @property
114 def filteredrevs(self):
114 def filteredrevs(self):
115 # TODO: we should probably add a refs/hg/ namespace for hidden
115 # TODO: we should probably add a refs/hg/ namespace for hidden
116 # heads etc, but that's an idea for later.
116 # heads etc, but that's an idea for later.
117 return set()
117 return set()
118
118
119 @property
119 @property
120 def index(self):
120 def index(self):
121 return baselogindex(self)
121 return baselogindex(self)
122
122
123 @property
123 @property
124 def nodemap(self):
124 def nodemap(self):
125 r = {
125 r = {
126 nodemod.bin(v[0]): v[1]
126 nodemod.bin(v[0]): v[1]
127 for v in self._db.execute('SELECT node, rev FROM changelog')
127 for v in self._db.execute('SELECT node, rev FROM changelog')
128 }
128 }
129 r[nodemod.nullid] = nodemod.nullrev
129 r[nodemod.nullid] = nodemod.nullrev
130 return r
130 return r
131
131
132 def tip(self):
132 def tip(self):
133 t = self._db.execute(
133 t = self._db.execute(
134 'SELECT node FROM changelog ORDER BY rev DESC LIMIT 1'
134 'SELECT node FROM changelog ORDER BY rev DESC LIMIT 1'
135 ).fetchone()
135 ).fetchone()
136 if t:
136 if t:
137 return nodemod.bin(t[0])
137 return nodemod.bin(t[0])
138 return nodemod.nullid
138 return nodemod.nullid
139
139
140 def revs(self, start=0, stop=None):
140 def revs(self, start=0, stop=None):
141 if stop is None:
141 if stop is None:
142 stop = self.tip()
142 stop = self.tip()
143 t = self._db.execute(
143 t = self._db.execute(
144 'SELECT rev FROM changelog '
144 'SELECT rev FROM changelog '
145 'WHERE rev >= ? AND rev <= ? '
145 'WHERE rev >= ? AND rev <= ? '
146 'ORDER BY REV ASC',
146 'ORDER BY REV ASC',
147 (start, stop),
147 (start, stop),
148 )
148 )
149 return (int(r[0]) for r in t)
149 return (int(r[0]) for r in t)
150
150
151 def tiprev(self):
151 def tiprev(self):
152 t = self._db.execute(
152 t = self._db.execute(
153 'SELECT rev FROM changelog '
153 'SELECT rev FROM changelog ' 'ORDER BY REV DESC ' 'LIMIT 1'
154 'ORDER BY REV DESC '
155 'LIMIT 1'
156 )
154 )
157 return next(t)
155 return next(t)
158
156
159 def _partialmatch(self, id):
157 def _partialmatch(self, id):
160 if nodemod.wdirhex.startswith(id):
158 if nodemod.wdirhex.startswith(id):
161 raise error.WdirUnsupported
159 raise error.WdirUnsupported
162 candidates = [
160 candidates = [
163 nodemod.bin(x[0])
161 nodemod.bin(x[0])
164 for x in self._db.execute(
162 for x in self._db.execute(
165 'SELECT node FROM changelog WHERE node LIKE ?', (id + b'%',)
163 'SELECT node FROM changelog WHERE node LIKE ?', (id + b'%',)
166 )
164 )
167 ]
165 ]
168 if nodemod.nullhex.startswith(id):
166 if nodemod.nullhex.startswith(id):
169 candidates.append(nodemod.nullid)
167 candidates.append(nodemod.nullid)
170 if len(candidates) > 1:
168 if len(candidates) > 1:
171 raise error.AmbiguousPrefixLookupError(
169 raise error.AmbiguousPrefixLookupError(
172 id, b'00changelog.i', _(b'ambiguous identifier')
170 id, b'00changelog.i', _(b'ambiguous identifier')
173 )
171 )
174 if candidates:
172 if candidates:
175 return candidates[0]
173 return candidates[0]
176 return None
174 return None
177
175
178 def flags(self, rev):
176 def flags(self, rev):
179 return 0
177 return 0
180
178
181 def shortest(self, node, minlength=1):
179 def shortest(self, node, minlength=1):
182 nodehex = nodemod.hex(node)
180 nodehex = nodemod.hex(node)
183 for attempt in pycompat.xrange(minlength, len(nodehex) + 1):
181 for attempt in pycompat.xrange(minlength, len(nodehex) + 1):
184 candidate = nodehex[:attempt]
182 candidate = nodehex[:attempt]
185 matches = int(
183 matches = int(
186 self._db.execute(
184 self._db.execute(
187 'SELECT COUNT(*) FROM changelog WHERE node LIKE ?',
185 'SELECT COUNT(*) FROM changelog WHERE node LIKE ?',
188 (pycompat.sysstr(candidate + b'%'),),
186 (pycompat.sysstr(candidate + b'%'),),
189 ).fetchone()[0]
187 ).fetchone()[0]
190 )
188 )
191 if matches == 1:
189 if matches == 1:
192 return candidate
190 return candidate
193 return nodehex
191 return nodehex
194
192
195 def headrevs(self, revs=None):
193 def headrevs(self, revs=None):
196 realheads = [
194 realheads = [
197 int(x[0])
195 int(x[0])
198 for x in self._db.execute(
196 for x in self._db.execute(
199 'SELECT rev FROM changelog '
197 'SELECT rev FROM changelog '
200 'INNER JOIN heads ON changelog.node = heads.node'
198 'INNER JOIN heads ON changelog.node = heads.node'
201 )
199 )
202 ]
200 ]
203 if revs:
201 if revs:
204 return sorted([r for r in revs if r in realheads])
202 return sorted([r for r in revs if r in realheads])
205 return sorted(realheads)
203 return sorted(realheads)
206
204
207 def changelogrevision(self, nodeorrev):
205 def changelogrevision(self, nodeorrev):
208 # Ensure we have a node id
206 # Ensure we have a node id
209 if isinstance(nodeorrev, int):
207 if isinstance(nodeorrev, int):
210 n = self.node(nodeorrev)
208 n = self.node(nodeorrev)
211 else:
209 else:
212 n = nodeorrev
210 n = nodeorrev
213 # handle looking up nullid
211 # handle looking up nullid
214 if n == nodemod.nullid:
212 if n == nodemod.nullid:
215 return hgchangelog._changelogrevision(extra={})
213 return hgchangelog._changelogrevision(extra={})
216 hn = gitutil.togitnode(n)
214 hn = gitutil.togitnode(n)
217 # We've got a real commit!
215 # We've got a real commit!
218 files = [
216 files = [
219 r[0]
217 r[0]
220 for r in self._db.execute(
218 for r in self._db.execute(
221 'SELECT filename FROM changedfiles '
219 'SELECT filename FROM changedfiles '
222 'WHERE node = ? and filenode != ?',
220 'WHERE node = ? and filenode != ?',
223 (hn, gitutil.nullgit),
221 (hn, gitutil.nullgit),
224 )
222 )
225 ]
223 ]
226 filesremoved = [
224 filesremoved = [
227 r[0]
225 r[0]
228 for r in self._db.execute(
226 for r in self._db.execute(
229 'SELECT filename FROM changedfiles '
227 'SELECT filename FROM changedfiles '
230 'WHERE node = ? and filenode = ?',
228 'WHERE node = ? and filenode = ?',
231 (hn, nodemod.nullhex),
229 (hn, nodemod.nullhex),
232 )
230 )
233 ]
231 ]
234 c = self.gitrepo[hn]
232 c = self.gitrepo[hn]
235 return hgchangelog._changelogrevision(
233 return hgchangelog._changelogrevision(
236 manifest=n, # pretend manifest the same as the commit node
234 manifest=n, # pretend manifest the same as the commit node
237 user=b'%s <%s>'
235 user=b'%s <%s>'
238 % (c.author.name.encode('utf8'), c.author.email.encode('utf8')),
236 % (c.author.name.encode('utf8'), c.author.email.encode('utf8')),
239 date=(c.author.time, -c.author.offset * 60),
237 date=(c.author.time, -c.author.offset * 60),
240 files=files,
238 files=files,
241 # TODO filesadded in the index
239 # TODO filesadded in the index
242 filesremoved=filesremoved,
240 filesremoved=filesremoved,
243 description=c.message.encode('utf8'),
241 description=c.message.encode('utf8'),
244 # TODO do we want to handle extra? how?
242 # TODO do we want to handle extra? how?
245 extra={b'branch': b'default'},
243 extra={b'branch': b'default'},
246 )
244 )
247
245
248 def ancestors(self, revs, stoprev=0, inclusive=False):
246 def ancestors(self, revs, stoprev=0, inclusive=False):
249 revs = list(revs)
247 revs = list(revs)
250 tip = self.rev(self.tip())
248 tip = self.rev(self.tip())
251 for r in revs:
249 for r in revs:
252 if r > tip:
250 if r > tip:
253 raise IndexError(b'Invalid rev %r' % r)
251 raise IndexError(b'Invalid rev %r' % r)
254 return ancestor.lazyancestors(
252 return ancestor.lazyancestors(
255 self.parentrevs, revs, stoprev=stoprev, inclusive=inclusive
253 self.parentrevs, revs, stoprev=stoprev, inclusive=inclusive
256 )
254 )
257
255
258 # Cleanup opportunity: this is *identical* to the revlog.py version
256 # Cleanup opportunity: this is *identical* to the revlog.py version
259 def descendants(self, revs):
257 def descendants(self, revs):
260 return dagop.descendantrevs(revs, self.revs, self.parentrevs)
258 return dagop.descendantrevs(revs, self.revs, self.parentrevs)
261
259
262 def incrementalmissingrevs(self, common=None):
260 def incrementalmissingrevs(self, common=None):
263 """Return an object that can be used to incrementally compute the
261 """Return an object that can be used to incrementally compute the
264 revision numbers of the ancestors of arbitrary sets that are not
262 revision numbers of the ancestors of arbitrary sets that are not
265 ancestors of common. This is an ancestor.incrementalmissingancestors
263 ancestors of common. This is an ancestor.incrementalmissingancestors
266 object.
264 object.
267
265
268 'common' is a list of revision numbers. If common is not supplied, uses
266 'common' is a list of revision numbers. If common is not supplied, uses
269 nullrev.
267 nullrev.
270 """
268 """
271 if common is None:
269 if common is None:
272 common = [nodemod.nullrev]
270 common = [nodemod.nullrev]
273
271
274 return ancestor.incrementalmissingancestors(self.parentrevs, common)
272 return ancestor.incrementalmissingancestors(self.parentrevs, common)
275
273
276 def findmissing(self, common=None, heads=None):
274 def findmissing(self, common=None, heads=None):
277 """Return the ancestors of heads that are not ancestors of common.
275 """Return the ancestors of heads that are not ancestors of common.
278
276
279 More specifically, return a list of nodes N such that every N
277 More specifically, return a list of nodes N such that every N
280 satisfies the following constraints:
278 satisfies the following constraints:
281
279
282 1. N is an ancestor of some node in 'heads'
280 1. N is an ancestor of some node in 'heads'
283 2. N is not an ancestor of any node in 'common'
281 2. N is not an ancestor of any node in 'common'
284
282
285 The list is sorted by revision number, meaning it is
283 The list is sorted by revision number, meaning it is
286 topologically sorted.
284 topologically sorted.
287
285
288 'heads' and 'common' are both lists of node IDs. If heads is
286 'heads' and 'common' are both lists of node IDs. If heads is
289 not supplied, uses all of the revlog's heads. If common is not
287 not supplied, uses all of the revlog's heads. If common is not
290 supplied, uses nullid."""
288 supplied, uses nullid."""
291 if common is None:
289 if common is None:
292 common = [nodemod.nullid]
290 common = [nodemod.nullid]
293 if heads is None:
291 if heads is None:
294 heads = self.heads()
292 heads = self.heads()
295
293
296 common = [self.rev(n) for n in common]
294 common = [self.rev(n) for n in common]
297 heads = [self.rev(n) for n in heads]
295 heads = [self.rev(n) for n in heads]
298
296
299 inc = self.incrementalmissingrevs(common=common)
297 inc = self.incrementalmissingrevs(common=common)
300 return [self.node(r) for r in inc.missingancestors(heads)]
298 return [self.node(r) for r in inc.missingancestors(heads)]
301
299
302 def children(self, node):
300 def children(self, node):
303 """find the children of a given node"""
301 """find the children of a given node"""
304 c = []
302 c = []
305 p = self.rev(node)
303 p = self.rev(node)
306 for r in self.revs(start=p + 1):
304 for r in self.revs(start=p + 1):
307 prevs = [pr for pr in self.parentrevs(r) if pr != nodemod.nullrev]
305 prevs = [pr for pr in self.parentrevs(r) if pr != nodemod.nullrev]
308 if prevs:
306 if prevs:
309 for pr in prevs:
307 for pr in prevs:
310 if pr == p:
308 if pr == p:
311 c.append(self.node(r))
309 c.append(self.node(r))
312 elif p == nodemod.nullrev:
310 elif p == nodemod.nullrev:
313 c.append(self.node(r))
311 c.append(self.node(r))
314 return c
312 return c
315
313
316 def reachableroots(self, minroot, heads, roots, includepath=False):
314 def reachableroots(self, minroot, heads, roots, includepath=False):
317 return dagop._reachablerootspure(
315 return dagop._reachablerootspure(
318 self.parentrevs, minroot, roots, heads, includepath
316 self.parentrevs, minroot, roots, heads, includepath
319 )
317 )
320
318
321 # Cleanup opportunity: this is *identical* to the revlog.py version
319 # Cleanup opportunity: this is *identical* to the revlog.py version
322 def isancestor(self, a, b):
320 def isancestor(self, a, b):
323 a, b = self.rev(a), self.rev(b)
321 a, b = self.rev(a), self.rev(b)
324 return self.isancestorrev(a, b)
322 return self.isancestorrev(a, b)
325
323
326 # Cleanup opportunity: this is *identical* to the revlog.py version
324 # Cleanup opportunity: this is *identical* to the revlog.py version
327 def isancestorrev(self, a, b):
325 def isancestorrev(self, a, b):
328 if a == nodemod.nullrev:
326 if a == nodemod.nullrev:
329 return True
327 return True
330 elif a == b:
328 elif a == b:
331 return True
329 return True
332 elif a > b:
330 elif a > b:
333 return False
331 return False
334 return bool(self.reachableroots(a, [b], [a], includepath=False))
332 return bool(self.reachableroots(a, [b], [a], includepath=False))
335
333
336 def parentrevs(self, rev):
334 def parentrevs(self, rev):
337 n = self.node(rev)
335 n = self.node(rev)
338 hn = gitutil.togitnode(n)
336 hn = gitutil.togitnode(n)
339 if hn != gitutil.nullgit:
337 if hn != gitutil.nullgit:
340 c = self.gitrepo[hn]
338 c = self.gitrepo[hn]
341 else:
339 else:
342 return nodemod.nullrev, nodemod.nullrev
340 return nodemod.nullrev, nodemod.nullrev
343 p1 = p2 = nodemod.nullrev
341 p1 = p2 = nodemod.nullrev
344 if c.parents:
342 if c.parents:
345 p1 = self.rev(c.parents[0].id.raw)
343 p1 = self.rev(c.parents[0].id.raw)
346 if len(c.parents) > 2:
344 if len(c.parents) > 2:
347 raise error.Abort(b'TODO octopus merge handling')
345 raise error.Abort(b'TODO octopus merge handling')
348 if len(c.parents) == 2:
346 if len(c.parents) == 2:
349 p2 = self.rev(c.parents[1].id.raw)
347 p2 = self.rev(c.parents[1].id.raw)
350 return p1, p2
348 return p1, p2
351
349
352 # Private method is used at least by the tags code.
350 # Private method is used at least by the tags code.
353 _uncheckedparentrevs = parentrevs
351 _uncheckedparentrevs = parentrevs
354
352
355 def commonancestorsheads(self, a, b):
353 def commonancestorsheads(self, a, b):
356 # TODO the revlog verson of this has a C path, so we probably
354 # TODO the revlog verson of this has a C path, so we probably
357 # need to optimize this...
355 # need to optimize this...
358 a, b = self.rev(a), self.rev(b)
356 a, b = self.rev(a), self.rev(b)
359 return [
357 return [
360 self.node(n)
358 self.node(n)
361 for n in ancestor.commonancestorsheads(self.parentrevs, a, b)
359 for n in ancestor.commonancestorsheads(self.parentrevs, a, b)
362 ]
360 ]
363
361
364 def branchinfo(self, rev):
362 def branchinfo(self, rev):
365 """Git doesn't do named branches, so just put everything on default."""
363 """Git doesn't do named branches, so just put everything on default."""
366 return b'default', False
364 return b'default', False
367
365
368 def delayupdate(self, tr):
366 def delayupdate(self, tr):
369 # TODO: I think we can elide this because we're just dropping
367 # TODO: I think we can elide this because we're just dropping
370 # an object in the git repo?
368 # an object in the git repo?
371 pass
369 pass
372
370
373 def add(
371 def add(
374 self,
372 self,
375 manifest,
373 manifest,
376 files,
374 files,
377 desc,
375 desc,
378 transaction,
376 transaction,
379 p1,
377 p1,
380 p2,
378 p2,
381 user,
379 user,
382 date=None,
380 date=None,
383 extra=None,
381 extra=None,
384 p1copies=None,
382 p1copies=None,
385 p2copies=None,
383 p2copies=None,
386 filesadded=None,
384 filesadded=None,
387 filesremoved=None,
385 filesremoved=None,
388 ):
386 ):
389 parents = []
387 parents = []
390 hp1, hp2 = gitutil.togitnode(p1), gitutil.togitnode(p2)
388 hp1, hp2 = gitutil.togitnode(p1), gitutil.togitnode(p2)
391 if p1 != nodemod.nullid:
389 if p1 != nodemod.nullid:
392 parents.append(hp1)
390 parents.append(hp1)
393 if p2 and p2 != nodemod.nullid:
391 if p2 and p2 != nodemod.nullid:
394 parents.append(hp2)
392 parents.append(hp2)
395 assert date is not None
393 assert date is not None
396 timestamp, tz = date
394 timestamp, tz = date
397 sig = pygit2.Signature(
395 sig = pygit2.Signature(
398 encoding.unifromlocal(stringutil.person(user)),
396 encoding.unifromlocal(stringutil.person(user)),
399 encoding.unifromlocal(stringutil.email(user)),
397 encoding.unifromlocal(stringutil.email(user)),
400 int(timestamp),
398 int(timestamp),
401 -int(tz // 60),
399 -int(tz // 60),
402 )
400 )
403 oid = self.gitrepo.create_commit(
401 oid = self.gitrepo.create_commit(
404 None, sig, sig, desc, gitutil.togitnode(manifest), parents
402 None, sig, sig, desc, gitutil.togitnode(manifest), parents
405 )
403 )
406 # Set up an internal reference to force the commit into the
404 # Set up an internal reference to force the commit into the
407 # changelog. Hypothetically, we could even use this refs/hg/
405 # changelog. Hypothetically, we could even use this refs/hg/
408 # namespace to allow for anonymous heads on git repos, which
406 # namespace to allow for anonymous heads on git repos, which
409 # would be neat.
407 # would be neat.
410 self.gitrepo.references.create(
408 self.gitrepo.references.create(
411 'refs/hg/internal/latest-commit', oid, force=True
409 'refs/hg/internal/latest-commit', oid, force=True
412 )
410 )
413 # Reindex now to pick up changes. We omit the progress
411 # Reindex now to pick up changes. We omit the progress
414 # and log callbacks because this will be very quick.
412 # and log callbacks because this will be very quick.
415 index._index_repo(self.gitrepo, self._db)
413 index._index_repo(self.gitrepo, self._db)
416 return oid.raw
414 return oid.raw
417
415
418
416
419 class manifestlog(baselog):
417 class manifestlog(baselog):
420 def __getitem__(self, node):
418 def __getitem__(self, node):
421 return self.get(b'', node)
419 return self.get(b'', node)
422
420
423 def get(self, relpath, node):
421 def get(self, relpath, node):
424 if node == nodemod.nullid:
422 if node == nodemod.nullid:
425 # TODO: this should almost certainly be a memgittreemanifestctx
423 # TODO: this should almost certainly be a memgittreemanifestctx
426 return manifest.memtreemanifestctx(self, relpath)
424 return manifest.memtreemanifestctx(self, relpath)
427 commit = self.gitrepo[gitutil.togitnode(node)]
425 commit = self.gitrepo[gitutil.togitnode(node)]
428 t = commit.tree
426 t = commit.tree
429 if relpath:
427 if relpath:
430 parts = relpath.split(b'/')
428 parts = relpath.split(b'/')
431 for p in parts:
429 for p in parts:
432 te = t[p]
430 te = t[p]
433 t = self.gitrepo[te.id]
431 t = self.gitrepo[te.id]
434 return gitmanifest.gittreemanifestctx(self.gitrepo, t)
432 return gitmanifest.gittreemanifestctx(self.gitrepo, t)
435
433
436
434
437 @interfaceutil.implementer(repository.ifilestorage)
435 @interfaceutil.implementer(repository.ifilestorage)
438 class filelog(baselog):
436 class filelog(baselog):
439 def __init__(self, gr, db, path):
437 def __init__(self, gr, db, path):
440 super(filelog, self).__init__(gr, db)
438 super(filelog, self).__init__(gr, db)
441 assert isinstance(path, bytes)
439 assert isinstance(path, bytes)
442 self.path = path
440 self.path = path
443
441
444 def read(self, node):
442 def read(self, node):
445 if node == nodemod.nullid:
443 if node == nodemod.nullid:
446 return b''
444 return b''
447 return self.gitrepo[gitutil.togitnode(node)].data
445 return self.gitrepo[gitutil.togitnode(node)].data
448
446
449 def lookup(self, node):
447 def lookup(self, node):
450 if len(node) not in (20, 40):
448 if len(node) not in (20, 40):
451 node = int(node)
449 node = int(node)
452 if isinstance(node, int):
450 if isinstance(node, int):
453 assert False, b'todo revnums for nodes'
451 assert False, b'todo revnums for nodes'
454 if len(node) == 40:
452 if len(node) == 40:
455 node = nodemod.bin(node)
453 node = nodemod.bin(node)
456 hnode = gitutil.togitnode(node)
454 hnode = gitutil.togitnode(node)
457 if hnode in self.gitrepo:
455 if hnode in self.gitrepo:
458 return node
456 return node
459 raise error.LookupError(self.path, node, _(b'no match found'))
457 raise error.LookupError(self.path, node, _(b'no match found'))
460
458
461 def cmp(self, node, text):
459 def cmp(self, node, text):
462 """Returns True if text is different than content at `node`."""
460 """Returns True if text is different than content at `node`."""
463 return self.read(node) != text
461 return self.read(node) != text
464
462
465 def add(self, text, meta, transaction, link, p1=None, p2=None):
463 def add(self, text, meta, transaction, link, p1=None, p2=None):
466 assert not meta # Should we even try to handle this?
464 assert not meta # Should we even try to handle this?
467 return self.gitrepo.create_blob(text).raw
465 return self.gitrepo.create_blob(text).raw
468
466
469 def __iter__(self):
467 def __iter__(self):
470 for clrev in self._db.execute(
468 for clrev in self._db.execute(
471 '''
469 '''
472 SELECT rev FROM changelog
470 SELECT rev FROM changelog
473 INNER JOIN changedfiles ON changelog.node = changedfiles.node
471 INNER JOIN changedfiles ON changelog.node = changedfiles.node
474 WHERE changedfiles.filename = ? AND changedfiles.filenode != ?
472 WHERE changedfiles.filename = ? AND changedfiles.filenode != ?
475 ''',
473 ''',
476 (pycompat.fsdecode(self.path), gitutil.nullgit),
474 (pycompat.fsdecode(self.path), gitutil.nullgit),
477 ):
475 ):
478 yield clrev[0]
476 yield clrev[0]
479
477
480 def linkrev(self, fr):
478 def linkrev(self, fr):
481 return fr
479 return fr
482
480
483 def rev(self, node):
481 def rev(self, node):
484 row = self._db.execute(
482 row = self._db.execute(
485 '''
483 '''
486 SELECT rev FROM changelog
484 SELECT rev FROM changelog
487 INNER JOIN changedfiles ON changelog.node = changedfiles.node
485 INNER JOIN changedfiles ON changelog.node = changedfiles.node
488 WHERE changedfiles.filename = ? AND changedfiles.filenode = ?''',
486 WHERE changedfiles.filename = ? AND changedfiles.filenode = ?''',
489 (pycompat.fsdecode(self.path), gitutil.togitnode(node)),
487 (pycompat.fsdecode(self.path), gitutil.togitnode(node)),
490 ).fetchone()
488 ).fetchone()
491 if row is None:
489 if row is None:
492 raise error.LookupError(self.path, node, _(b'no such node'))
490 raise error.LookupError(self.path, node, _(b'no such node'))
493 return int(row[0])
491 return int(row[0])
494
492
495 def node(self, rev):
493 def node(self, rev):
496 maybe = self._db.execute(
494 maybe = self._db.execute(
497 '''SELECT filenode FROM changedfiles
495 '''SELECT filenode FROM changedfiles
498 INNER JOIN changelog ON changelog.node = changedfiles.node
496 INNER JOIN changelog ON changelog.node = changedfiles.node
499 WHERE changelog.rev = ? AND filename = ?
497 WHERE changelog.rev = ? AND filename = ?
500 ''',
498 ''',
501 (rev, pycompat.fsdecode(self.path)),
499 (rev, pycompat.fsdecode(self.path)),
502 ).fetchone()
500 ).fetchone()
503 if maybe is None:
501 if maybe is None:
504 raise IndexError('gitlog %r out of range %d' % (self.path, rev))
502 raise IndexError('gitlog %r out of range %d' % (self.path, rev))
505 return nodemod.bin(maybe[0])
503 return nodemod.bin(maybe[0])
506
504
507 def parents(self, node):
505 def parents(self, node):
508 gn = gitutil.togitnode(node)
506 gn = gitutil.togitnode(node)
509 gp = pycompat.fsdecode(self.path)
507 gp = pycompat.fsdecode(self.path)
510 ps = []
508 ps = []
511 for p in self._db.execute(
509 for p in self._db.execute(
512 '''SELECT p1filenode, p2filenode FROM changedfiles
510 '''SELECT p1filenode, p2filenode FROM changedfiles
513 WHERE filenode = ? AND filename = ?
511 WHERE filenode = ? AND filename = ?
514 ''',
512 ''',
515 (gn, gp),
513 (gn, gp),
516 ).fetchone():
514 ).fetchone():
517 if p is None:
515 if p is None:
518 commit = self._db.execute(
516 commit = self._db.execute(
519 "SELECT node FROM changedfiles "
517 "SELECT node FROM changedfiles "
520 "WHERE filenode = ? AND filename = ?",
518 "WHERE filenode = ? AND filename = ?",
521 (gn, gp),
519 (gn, gp),
522 ).fetchone()[0]
520 ).fetchone()[0]
523 # This filelog is missing some data. Build the
521 # This filelog is missing some data. Build the
524 # filelog, then recurse (which will always find data).
522 # filelog, then recurse (which will always find data).
525 if pycompat.ispy3:
523 if pycompat.ispy3:
526 commit = commit.decode('ascii')
524 commit = commit.decode('ascii')
527 index.fill_in_filelog(self.gitrepo, self._db, commit, gp, gn)
525 index.fill_in_filelog(self.gitrepo, self._db, commit, gp, gn)
528 return self.parents(node)
526 return self.parents(node)
529 else:
527 else:
530 ps.append(nodemod.bin(p))
528 ps.append(nodemod.bin(p))
531 return ps
529 return ps
532
530
533 def renamed(self, node):
531 def renamed(self, node):
534 # TODO: renames/copies
532 # TODO: renames/copies
535 return False
533 return False
@@ -1,1816 +1,1815 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 import os
6 import os
7
7
8 # Mercurial will never work on Python 3 before 3.5 due to a lack
8 # Mercurial will never work on Python 3 before 3.5 due to a lack
9 # of % formatting on bytestrings, and can't work on 3.6.0 or 3.6.1
9 # of % formatting on bytestrings, and can't work on 3.6.0 or 3.6.1
10 # due to a bug in % formatting in bytestrings.
10 # due to a bug in % formatting in bytestrings.
11 # We cannot support Python 3.5.0, 3.5.1, 3.5.2 because of bug in
11 # We cannot support Python 3.5.0, 3.5.1, 3.5.2 because of bug in
12 # codecs.escape_encode() where it raises SystemError on empty bytestring
12 # codecs.escape_encode() where it raises SystemError on empty bytestring
13 # bug link: https://bugs.python.org/issue25270
13 # bug link: https://bugs.python.org/issue25270
14 supportedpy = ','.join(
14 supportedpy = ','.join(
15 [
15 [
16 '>=2.7.4',
16 '>=2.7.4',
17 '!=3.0.*',
17 '!=3.0.*',
18 '!=3.1.*',
18 '!=3.1.*',
19 '!=3.2.*',
19 '!=3.2.*',
20 '!=3.3.*',
20 '!=3.3.*',
21 '!=3.4.*',
21 '!=3.4.*',
22 '!=3.5.0',
22 '!=3.5.0',
23 '!=3.5.1',
23 '!=3.5.1',
24 '!=3.5.2',
24 '!=3.5.2',
25 '!=3.6.0',
25 '!=3.6.0',
26 '!=3.6.1',
26 '!=3.6.1',
27 ]
27 ]
28 )
28 )
29
29
30 import sys, platform
30 import sys, platform
31 import sysconfig
31 import sysconfig
32
32
33 if sys.version_info[0] >= 3:
33 if sys.version_info[0] >= 3:
34 printf = eval('print')
34 printf = eval('print')
35 libdir_escape = 'unicode_escape'
35 libdir_escape = 'unicode_escape'
36
36
37 def sysstr(s):
37 def sysstr(s):
38 return s.decode('latin-1')
38 return s.decode('latin-1')
39
39
40
40
41 else:
41 else:
42 libdir_escape = 'string_escape'
42 libdir_escape = 'string_escape'
43
43
44 def printf(*args, **kwargs):
44 def printf(*args, **kwargs):
45 f = kwargs.get('file', sys.stdout)
45 f = kwargs.get('file', sys.stdout)
46 end = kwargs.get('end', '\n')
46 end = kwargs.get('end', '\n')
47 f.write(b' '.join(args) + end)
47 f.write(b' '.join(args) + end)
48
48
49 def sysstr(s):
49 def sysstr(s):
50 return s
50 return s
51
51
52
52
53 # Attempt to guide users to a modern pip - this means that 2.6 users
53 # Attempt to guide users to a modern pip - this means that 2.6 users
54 # should have a chance of getting a 4.2 release, and when we ratchet
54 # should have a chance of getting a 4.2 release, and when we ratchet
55 # the version requirement forward again hopefully everyone will get
55 # the version requirement forward again hopefully everyone will get
56 # something that works for them.
56 # something that works for them.
57 if sys.version_info < (2, 7, 4, 'final'):
57 if sys.version_info < (2, 7, 4, 'final'):
58 pip_message = (
58 pip_message = (
59 'This may be due to an out of date pip. '
59 'This may be due to an out of date pip. '
60 'Make sure you have pip >= 9.0.1.'
60 'Make sure you have pip >= 9.0.1.'
61 )
61 )
62 try:
62 try:
63 import pip
63 import pip
64
64
65 pip_version = tuple([int(x) for x in pip.__version__.split('.')[:3]])
65 pip_version = tuple([int(x) for x in pip.__version__.split('.')[:3]])
66 if pip_version < (9, 0, 1):
66 if pip_version < (9, 0, 1):
67 pip_message = (
67 pip_message = (
68 'Your pip version is out of date, please install '
68 'Your pip version is out of date, please install '
69 'pip >= 9.0.1. pip {} detected.'.format(pip.__version__)
69 'pip >= 9.0.1. pip {} detected.'.format(pip.__version__)
70 )
70 )
71 else:
71 else:
72 # pip is new enough - it must be something else
72 # pip is new enough - it must be something else
73 pip_message = ''
73 pip_message = ''
74 except Exception:
74 except Exception:
75 pass
75 pass
76 error = """
76 error = """
77 Mercurial does not support Python older than 2.7.4.
77 Mercurial does not support Python older than 2.7.4.
78 Python {py} detected.
78 Python {py} detected.
79 {pip}
79 {pip}
80 """.format(
80 """.format(
81 py=sys.version_info, pip=pip_message
81 py=sys.version_info, pip=pip_message
82 )
82 )
83 printf(error, file=sys.stderr)
83 printf(error, file=sys.stderr)
84 sys.exit(1)
84 sys.exit(1)
85
85
86 import ssl
86 import ssl
87
87
88 try:
88 try:
89 ssl.SSLContext
89 ssl.SSLContext
90 except AttributeError:
90 except AttributeError:
91 error = """
91 error = """
92 The `ssl` module does not have the `SSLContext` class. This indicates an old
92 The `ssl` module does not have the `SSLContext` class. This indicates an old
93 Python version which does not support modern security features (which were
93 Python version which does not support modern security features (which were
94 added to Python 2.7 as part of "PEP 466"). Please make sure you have installed
94 added to Python 2.7 as part of "PEP 466"). Please make sure you have installed
95 at least Python 2.7.9 or a Python version with backports of these security
95 at least Python 2.7.9 or a Python version with backports of these security
96 features.
96 features.
97 """
97 """
98 printf(error, file=sys.stderr)
98 printf(error, file=sys.stderr)
99 sys.exit(1)
99 sys.exit(1)
100
100
101 # ssl.HAS_TLSv1* are preferred to check support but they were added in Python
101 # ssl.HAS_TLSv1* are preferred to check support but they were added in Python
102 # 3.7. Prior to CPython commit 6e8cda91d92da72800d891b2fc2073ecbc134d98
102 # 3.7. Prior to CPython commit 6e8cda91d92da72800d891b2fc2073ecbc134d98
103 # (backported to the 3.7 branch), ssl.PROTOCOL_TLSv1_1 / ssl.PROTOCOL_TLSv1_2
103 # (backported to the 3.7 branch), ssl.PROTOCOL_TLSv1_1 / ssl.PROTOCOL_TLSv1_2
104 # were defined only if compiled against a OpenSSL version with TLS 1.1 / 1.2
104 # were defined only if compiled against a OpenSSL version with TLS 1.1 / 1.2
105 # support. At the mentioned commit, they were unconditionally defined.
105 # support. At the mentioned commit, they were unconditionally defined.
106 _notset = object()
106 _notset = object()
107 has_tlsv1_1 = getattr(ssl, 'HAS_TLSv1_1', _notset)
107 has_tlsv1_1 = getattr(ssl, 'HAS_TLSv1_1', _notset)
108 if has_tlsv1_1 is _notset:
108 if has_tlsv1_1 is _notset:
109 has_tlsv1_1 = getattr(ssl, 'PROTOCOL_TLSv1_1', _notset) is not _notset
109 has_tlsv1_1 = getattr(ssl, 'PROTOCOL_TLSv1_1', _notset) is not _notset
110 has_tlsv1_2 = getattr(ssl, 'HAS_TLSv1_2', _notset)
110 has_tlsv1_2 = getattr(ssl, 'HAS_TLSv1_2', _notset)
111 if has_tlsv1_2 is _notset:
111 if has_tlsv1_2 is _notset:
112 has_tlsv1_2 = getattr(ssl, 'PROTOCOL_TLSv1_2', _notset) is not _notset
112 has_tlsv1_2 = getattr(ssl, 'PROTOCOL_TLSv1_2', _notset) is not _notset
113 if not (has_tlsv1_1 or has_tlsv1_2):
113 if not (has_tlsv1_1 or has_tlsv1_2):
114 error = """
114 error = """
115 The `ssl` module does not advertise support for TLS 1.1 or TLS 1.2.
115 The `ssl` module does not advertise support for TLS 1.1 or TLS 1.2.
116 Please make sure that your Python installation was compiled against an OpenSSL
116 Please make sure that your Python installation was compiled against an OpenSSL
117 version enabling these features (likely this requires the OpenSSL version to
117 version enabling these features (likely this requires the OpenSSL version to
118 be at least 1.0.1).
118 be at least 1.0.1).
119 """
119 """
120 printf(error, file=sys.stderr)
120 printf(error, file=sys.stderr)
121 sys.exit(1)
121 sys.exit(1)
122
122
123 if sys.version_info[0] >= 3:
123 if sys.version_info[0] >= 3:
124 DYLIB_SUFFIX = sysconfig.get_config_vars()['EXT_SUFFIX']
124 DYLIB_SUFFIX = sysconfig.get_config_vars()['EXT_SUFFIX']
125 else:
125 else:
126 # deprecated in Python 3
126 # deprecated in Python 3
127 DYLIB_SUFFIX = sysconfig.get_config_vars()['SO']
127 DYLIB_SUFFIX = sysconfig.get_config_vars()['SO']
128
128
129 # Solaris Python packaging brain damage
129 # Solaris Python packaging brain damage
130 try:
130 try:
131 import hashlib
131 import hashlib
132
132
133 sha = hashlib.sha1()
133 sha = hashlib.sha1()
134 except ImportError:
134 except ImportError:
135 try:
135 try:
136 import sha
136 import sha
137
137
138 sha.sha # silence unused import warning
138 sha.sha # silence unused import warning
139 except ImportError:
139 except ImportError:
140 raise SystemExit(
140 raise SystemExit(
141 "Couldn't import standard hashlib (incomplete Python install)."
141 "Couldn't import standard hashlib (incomplete Python install)."
142 )
142 )
143
143
144 try:
144 try:
145 import zlib
145 import zlib
146
146
147 zlib.compressobj # silence unused import warning
147 zlib.compressobj # silence unused import warning
148 except ImportError:
148 except ImportError:
149 raise SystemExit(
149 raise SystemExit(
150 "Couldn't import standard zlib (incomplete Python install)."
150 "Couldn't import standard zlib (incomplete Python install)."
151 )
151 )
152
152
153 # The base IronPython distribution (as of 2.7.1) doesn't support bz2
153 # The base IronPython distribution (as of 2.7.1) doesn't support bz2
154 isironpython = False
154 isironpython = False
155 try:
155 try:
156 isironpython = (
156 isironpython = (
157 platform.python_implementation().lower().find("ironpython") != -1
157 platform.python_implementation().lower().find("ironpython") != -1
158 )
158 )
159 except AttributeError:
159 except AttributeError:
160 pass
160 pass
161
161
162 if isironpython:
162 if isironpython:
163 sys.stderr.write("warning: IronPython detected (no bz2 support)\n")
163 sys.stderr.write("warning: IronPython detected (no bz2 support)\n")
164 else:
164 else:
165 try:
165 try:
166 import bz2
166 import bz2
167
167
168 bz2.BZ2Compressor # silence unused import warning
168 bz2.BZ2Compressor # silence unused import warning
169 except ImportError:
169 except ImportError:
170 raise SystemExit(
170 raise SystemExit(
171 "Couldn't import standard bz2 (incomplete Python install)."
171 "Couldn't import standard bz2 (incomplete Python install)."
172 )
172 )
173
173
174 ispypy = "PyPy" in sys.version
174 ispypy = "PyPy" in sys.version
175
175
176 import ctypes
176 import ctypes
177 import errno
177 import errno
178 import stat, subprocess, time
178 import stat, subprocess, time
179 import re
179 import re
180 import shutil
180 import shutil
181 import tempfile
181 import tempfile
182 from distutils import log
182 from distutils import log
183
183
184 # We have issues with setuptools on some platforms and builders. Until
184 # We have issues with setuptools on some platforms and builders. Until
185 # those are resolved, setuptools is opt-in except for platforms where
185 # those are resolved, setuptools is opt-in except for platforms where
186 # we don't have issues.
186 # we don't have issues.
187 issetuptools = os.name == 'nt' or 'FORCE_SETUPTOOLS' in os.environ
187 issetuptools = os.name == 'nt' or 'FORCE_SETUPTOOLS' in os.environ
188 if issetuptools:
188 if issetuptools:
189 from setuptools import setup
189 from setuptools import setup
190 else:
190 else:
191 from distutils.core import setup
191 from distutils.core import setup
192 from distutils.ccompiler import new_compiler
192 from distutils.ccompiler import new_compiler
193 from distutils.core import Command, Extension
193 from distutils.core import Command, Extension
194 from distutils.dist import Distribution
194 from distutils.dist import Distribution
195 from distutils.command.build import build
195 from distutils.command.build import build
196 from distutils.command.build_ext import build_ext
196 from distutils.command.build_ext import build_ext
197 from distutils.command.build_py import build_py
197 from distutils.command.build_py import build_py
198 from distutils.command.build_scripts import build_scripts
198 from distutils.command.build_scripts import build_scripts
199 from distutils.command.install import install
199 from distutils.command.install import install
200 from distutils.command.install_lib import install_lib
200 from distutils.command.install_lib import install_lib
201 from distutils.command.install_scripts import install_scripts
201 from distutils.command.install_scripts import install_scripts
202 from distutils.spawn import spawn, find_executable
202 from distutils.spawn import spawn, find_executable
203 from distutils import file_util
203 from distutils import file_util
204 from distutils.errors import (
204 from distutils.errors import (
205 CCompilerError,
205 CCompilerError,
206 DistutilsError,
206 DistutilsError,
207 DistutilsExecError,
207 DistutilsExecError,
208 )
208 )
209 from distutils.sysconfig import get_python_inc, get_config_var
209 from distutils.sysconfig import get_python_inc, get_config_var
210 from distutils.version import StrictVersion
210 from distutils.version import StrictVersion
211
211
212 # Explain to distutils.StrictVersion how our release candidates are versionned
212 # Explain to distutils.StrictVersion how our release candidates are versionned
213 StrictVersion.version_re = re.compile(r'^(\d+)\.(\d+)(\.(\d+))?-?(rc(\d+))?$')
213 StrictVersion.version_re = re.compile(r'^(\d+)\.(\d+)(\.(\d+))?-?(rc(\d+))?$')
214
214
215
215
216 def write_if_changed(path, content):
216 def write_if_changed(path, content):
217 """Write content to a file iff the content hasn't changed."""
217 """Write content to a file iff the content hasn't changed."""
218 if os.path.exists(path):
218 if os.path.exists(path):
219 with open(path, 'rb') as fh:
219 with open(path, 'rb') as fh:
220 current = fh.read()
220 current = fh.read()
221 else:
221 else:
222 current = b''
222 current = b''
223
223
224 if current != content:
224 if current != content:
225 with open(path, 'wb') as fh:
225 with open(path, 'wb') as fh:
226 fh.write(content)
226 fh.write(content)
227
227
228
228
229 scripts = ['hg']
229 scripts = ['hg']
230 if os.name == 'nt':
230 if os.name == 'nt':
231 # We remove hg.bat if we are able to build hg.exe.
231 # We remove hg.bat if we are able to build hg.exe.
232 scripts.append('contrib/win32/hg.bat')
232 scripts.append('contrib/win32/hg.bat')
233
233
234
234
235 def cancompile(cc, code):
235 def cancompile(cc, code):
236 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
236 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
237 devnull = oldstderr = None
237 devnull = oldstderr = None
238 try:
238 try:
239 fname = os.path.join(tmpdir, 'testcomp.c')
239 fname = os.path.join(tmpdir, 'testcomp.c')
240 f = open(fname, 'w')
240 f = open(fname, 'w')
241 f.write(code)
241 f.write(code)
242 f.close()
242 f.close()
243 # Redirect stderr to /dev/null to hide any error messages
243 # Redirect stderr to /dev/null to hide any error messages
244 # from the compiler.
244 # from the compiler.
245 # This will have to be changed if we ever have to check
245 # This will have to be changed if we ever have to check
246 # for a function on Windows.
246 # for a function on Windows.
247 devnull = open('/dev/null', 'w')
247 devnull = open('/dev/null', 'w')
248 oldstderr = os.dup(sys.stderr.fileno())
248 oldstderr = os.dup(sys.stderr.fileno())
249 os.dup2(devnull.fileno(), sys.stderr.fileno())
249 os.dup2(devnull.fileno(), sys.stderr.fileno())
250 objects = cc.compile([fname], output_dir=tmpdir)
250 objects = cc.compile([fname], output_dir=tmpdir)
251 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
251 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
252 return True
252 return True
253 except Exception:
253 except Exception:
254 return False
254 return False
255 finally:
255 finally:
256 if oldstderr is not None:
256 if oldstderr is not None:
257 os.dup2(oldstderr, sys.stderr.fileno())
257 os.dup2(oldstderr, sys.stderr.fileno())
258 if devnull is not None:
258 if devnull is not None:
259 devnull.close()
259 devnull.close()
260 shutil.rmtree(tmpdir)
260 shutil.rmtree(tmpdir)
261
261
262
262
263 # simplified version of distutils.ccompiler.CCompiler.has_function
263 # simplified version of distutils.ccompiler.CCompiler.has_function
264 # that actually removes its temporary files.
264 # that actually removes its temporary files.
265 def hasfunction(cc, funcname):
265 def hasfunction(cc, funcname):
266 code = 'int main(void) { %s(); }\n' % funcname
266 code = 'int main(void) { %s(); }\n' % funcname
267 return cancompile(cc, code)
267 return cancompile(cc, code)
268
268
269
269
270 def hasheader(cc, headername):
270 def hasheader(cc, headername):
271 code = '#include <%s>\nint main(void) { return 0; }\n' % headername
271 code = '#include <%s>\nint main(void) { return 0; }\n' % headername
272 return cancompile(cc, code)
272 return cancompile(cc, code)
273
273
274
274
275 # py2exe needs to be installed to work
275 # py2exe needs to be installed to work
276 try:
276 try:
277 import py2exe
277 import py2exe
278
278
279 py2exe.Distribution # silence unused import warning
279 py2exe.Distribution # silence unused import warning
280 py2exeloaded = True
280 py2exeloaded = True
281 # import py2exe's patched Distribution class
281 # import py2exe's patched Distribution class
282 from distutils.core import Distribution
282 from distutils.core import Distribution
283 except ImportError:
283 except ImportError:
284 py2exeloaded = False
284 py2exeloaded = False
285
285
286
286
287 def runcmd(cmd, env, cwd=None):
287 def runcmd(cmd, env, cwd=None):
288 p = subprocess.Popen(
288 p = subprocess.Popen(
289 cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, cwd=cwd
289 cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, cwd=cwd
290 )
290 )
291 out, err = p.communicate()
291 out, err = p.communicate()
292 return p.returncode, out, err
292 return p.returncode, out, err
293
293
294
294
295 class hgcommand(object):
295 class hgcommand(object):
296 def __init__(self, cmd, env):
296 def __init__(self, cmd, env):
297 self.cmd = cmd
297 self.cmd = cmd
298 self.env = env
298 self.env = env
299
299
300 def run(self, args):
300 def run(self, args):
301 cmd = self.cmd + args
301 cmd = self.cmd + args
302 returncode, out, err = runcmd(cmd, self.env)
302 returncode, out, err = runcmd(cmd, self.env)
303 err = filterhgerr(err)
303 err = filterhgerr(err)
304 if err or returncode != 0:
304 if err or returncode != 0:
305 printf("stderr from '%s':" % (' '.join(cmd)), file=sys.stderr)
305 printf("stderr from '%s':" % (' '.join(cmd)), file=sys.stderr)
306 printf(err, file=sys.stderr)
306 printf(err, file=sys.stderr)
307 return b''
307 return b''
308 return out
308 return out
309
309
310
310
311 def filterhgerr(err):
311 def filterhgerr(err):
312 # If root is executing setup.py, but the repository is owned by
312 # If root is executing setup.py, but the repository is owned by
313 # another user (as in "sudo python setup.py install") we will get
313 # another user (as in "sudo python setup.py install") we will get
314 # trust warnings since the .hg/hgrc file is untrusted. That is
314 # trust warnings since the .hg/hgrc file is untrusted. That is
315 # fine, we don't want to load it anyway. Python may warn about
315 # fine, we don't want to load it anyway. Python may warn about
316 # a missing __init__.py in mercurial/locale, we also ignore that.
316 # a missing __init__.py in mercurial/locale, we also ignore that.
317 err = [
317 err = [
318 e
318 e
319 for e in err.splitlines()
319 for e in err.splitlines()
320 if (
320 if (
321 not e.startswith(b'not trusting file')
321 not e.startswith(b'not trusting file')
322 and not e.startswith(b'warning: Not importing')
322 and not e.startswith(b'warning: Not importing')
323 and not e.startswith(b'obsolete feature not enabled')
323 and not e.startswith(b'obsolete feature not enabled')
324 and not e.startswith(b'*** failed to import extension')
324 and not e.startswith(b'*** failed to import extension')
325 and not e.startswith(b'devel-warn:')
325 and not e.startswith(b'devel-warn:')
326 and not (
326 and not (
327 e.startswith(b'(third party extension')
327 e.startswith(b'(third party extension')
328 and e.endswith(b'or newer of Mercurial; disabling)')
328 and e.endswith(b'or newer of Mercurial; disabling)')
329 )
329 )
330 )
330 )
331 ]
331 ]
332 return b'\n'.join(b' ' + e for e in err)
332 return b'\n'.join(b' ' + e for e in err)
333
333
334
334
335 def findhg():
335 def findhg():
336 """Try to figure out how we should invoke hg for examining the local
336 """Try to figure out how we should invoke hg for examining the local
337 repository contents.
337 repository contents.
338
338
339 Returns an hgcommand object."""
339 Returns an hgcommand object."""
340 # By default, prefer the "hg" command in the user's path. This was
340 # By default, prefer the "hg" command in the user's path. This was
341 # presumably the hg command that the user used to create this repository.
341 # presumably the hg command that the user used to create this repository.
342 #
342 #
343 # This repository may require extensions or other settings that would not
343 # This repository may require extensions or other settings that would not
344 # be enabled by running the hg script directly from this local repository.
344 # be enabled by running the hg script directly from this local repository.
345 hgenv = os.environ.copy()
345 hgenv = os.environ.copy()
346 # Use HGPLAIN to disable hgrc settings that would change output formatting,
346 # Use HGPLAIN to disable hgrc settings that would change output formatting,
347 # and disable localization for the same reasons.
347 # and disable localization for the same reasons.
348 hgenv['HGPLAIN'] = '1'
348 hgenv['HGPLAIN'] = '1'
349 hgenv['LANGUAGE'] = 'C'
349 hgenv['LANGUAGE'] = 'C'
350 hgcmd = ['hg']
350 hgcmd = ['hg']
351 # Run a simple "hg log" command just to see if using hg from the user's
351 # Run a simple "hg log" command just to see if using hg from the user's
352 # path works and can successfully interact with this repository. Windows
352 # path works and can successfully interact with this repository. Windows
353 # gives precedence to hg.exe in the current directory, so fall back to the
353 # gives precedence to hg.exe in the current directory, so fall back to the
354 # python invocation of local hg, where pythonXY.dll can always be found.
354 # python invocation of local hg, where pythonXY.dll can always be found.
355 check_cmd = ['log', '-r.', '-Ttest']
355 check_cmd = ['log', '-r.', '-Ttest']
356 if os.name != 'nt' or not os.path.exists("hg.exe"):
356 if os.name != 'nt' or not os.path.exists("hg.exe"):
357 try:
357 try:
358 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
358 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
359 except EnvironmentError:
359 except EnvironmentError:
360 retcode = -1
360 retcode = -1
361 if retcode == 0 and not filterhgerr(err):
361 if retcode == 0 and not filterhgerr(err):
362 return hgcommand(hgcmd, hgenv)
362 return hgcommand(hgcmd, hgenv)
363
363
364 # Fall back to trying the local hg installation.
364 # Fall back to trying the local hg installation.
365 hgenv = localhgenv()
365 hgenv = localhgenv()
366 hgcmd = [sys.executable, 'hg']
366 hgcmd = [sys.executable, 'hg']
367 try:
367 try:
368 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
368 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
369 except EnvironmentError:
369 except EnvironmentError:
370 retcode = -1
370 retcode = -1
371 if retcode == 0 and not filterhgerr(err):
371 if retcode == 0 and not filterhgerr(err):
372 return hgcommand(hgcmd, hgenv)
372 return hgcommand(hgcmd, hgenv)
373
373
374 raise SystemExit(
374 raise SystemExit(
375 'Unable to find a working hg binary to extract the '
375 'Unable to find a working hg binary to extract the '
376 'version from the repository tags'
376 'version from the repository tags'
377 )
377 )
378
378
379
379
380 def localhgenv():
380 def localhgenv():
381 """Get an environment dictionary to use for invoking or importing
381 """Get an environment dictionary to use for invoking or importing
382 mercurial from the local repository."""
382 mercurial from the local repository."""
383 # Execute hg out of this directory with a custom environment which takes
383 # Execute hg out of this directory with a custom environment which takes
384 # care to not use any hgrc files and do no localization.
384 # care to not use any hgrc files and do no localization.
385 env = {
385 env = {
386 'HGMODULEPOLICY': 'py',
386 'HGMODULEPOLICY': 'py',
387 'HGRCPATH': '',
387 'HGRCPATH': '',
388 'LANGUAGE': 'C',
388 'LANGUAGE': 'C',
389 'PATH': '',
389 'PATH': '',
390 } # make pypi modules that use os.environ['PATH'] happy
390 } # make pypi modules that use os.environ['PATH'] happy
391 if 'LD_LIBRARY_PATH' in os.environ:
391 if 'LD_LIBRARY_PATH' in os.environ:
392 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
392 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
393 if 'SystemRoot' in os.environ:
393 if 'SystemRoot' in os.environ:
394 # SystemRoot is required by Windows to load various DLLs. See:
394 # SystemRoot is required by Windows to load various DLLs. See:
395 # https://bugs.python.org/issue13524#msg148850
395 # https://bugs.python.org/issue13524#msg148850
396 env['SystemRoot'] = os.environ['SystemRoot']
396 env['SystemRoot'] = os.environ['SystemRoot']
397 return env
397 return env
398
398
399
399
400 version = ''
400 version = ''
401
401
402 if os.path.isdir('.hg'):
402 if os.path.isdir('.hg'):
403 hg = findhg()
403 hg = findhg()
404 cmd = ['log', '-r', '.', '--template', '{tags}\n']
404 cmd = ['log', '-r', '.', '--template', '{tags}\n']
405 numerictags = [t for t in sysstr(hg.run(cmd)).split() if t[0:1].isdigit()]
405 numerictags = [t for t in sysstr(hg.run(cmd)).split() if t[0:1].isdigit()]
406 hgid = sysstr(hg.run(['id', '-i'])).strip()
406 hgid = sysstr(hg.run(['id', '-i'])).strip()
407 if not hgid:
407 if not hgid:
408 # Bail out if hg is having problems interacting with this repository,
408 # Bail out if hg is having problems interacting with this repository,
409 # rather than falling through and producing a bogus version number.
409 # rather than falling through and producing a bogus version number.
410 # Continuing with an invalid version number will break extensions
410 # Continuing with an invalid version number will break extensions
411 # that define minimumhgversion.
411 # that define minimumhgversion.
412 raise SystemExit('Unable to determine hg version from local repository')
412 raise SystemExit('Unable to determine hg version from local repository')
413 if numerictags: # tag(s) found
413 if numerictags: # tag(s) found
414 version = numerictags[-1]
414 version = numerictags[-1]
415 if hgid.endswith('+'): # propagate the dirty status to the tag
415 if hgid.endswith('+'): # propagate the dirty status to the tag
416 version += '+'
416 version += '+'
417 else: # no tag found
417 else: # no tag found
418 ltagcmd = ['parents', '--template', '{latesttag}']
418 ltagcmd = ['parents', '--template', '{latesttag}']
419 ltag = sysstr(hg.run(ltagcmd))
419 ltag = sysstr(hg.run(ltagcmd))
420 changessincecmd = ['log', '-T', 'x\n', '-r', "only(.,'%s')" % ltag]
420 changessincecmd = ['log', '-T', 'x\n', '-r', "only(.,'%s')" % ltag]
421 changessince = len(hg.run(changessincecmd).splitlines())
421 changessince = len(hg.run(changessincecmd).splitlines())
422 version = '%s+%s-%s' % (ltag, changessince, hgid)
422 version = '%s+%s-%s' % (ltag, changessince, hgid)
423 if version.endswith('+'):
423 if version.endswith('+'):
424 version += time.strftime('%Y%m%d')
424 version += time.strftime('%Y%m%d')
425 elif os.path.exists('.hg_archival.txt'):
425 elif os.path.exists('.hg_archival.txt'):
426 kw = dict(
426 kw = dict(
427 [[t.strip() for t in l.split(':', 1)] for l in open('.hg_archival.txt')]
427 [[t.strip() for t in l.split(':', 1)] for l in open('.hg_archival.txt')]
428 )
428 )
429 if 'tag' in kw:
429 if 'tag' in kw:
430 version = kw['tag']
430 version = kw['tag']
431 elif 'latesttag' in kw:
431 elif 'latesttag' in kw:
432 if 'changessincelatesttag' in kw:
432 if 'changessincelatesttag' in kw:
433 version = '%(latesttag)s+%(changessincelatesttag)s-%(node).12s' % kw
433 version = '%(latesttag)s+%(changessincelatesttag)s-%(node).12s' % kw
434 else:
434 else:
435 version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
435 version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
436 else:
436 else:
437 version = kw.get('node', '')[:12]
437 version = kw.get('node', '')[:12]
438
438
439 if version:
439 if version:
440 versionb = version
440 versionb = version
441 if not isinstance(versionb, bytes):
441 if not isinstance(versionb, bytes):
442 versionb = versionb.encode('ascii')
442 versionb = versionb.encode('ascii')
443
443
444 write_if_changed(
444 write_if_changed(
445 'mercurial/__version__.py',
445 'mercurial/__version__.py',
446 b''.join(
446 b''.join(
447 [
447 [
448 b'# this file is autogenerated by setup.py\n'
448 b'# this file is autogenerated by setup.py\n'
449 b'version = b"%s"\n' % versionb,
449 b'version = b"%s"\n' % versionb,
450 ]
450 ]
451 ),
451 ),
452 )
452 )
453
453
454 try:
454 try:
455 oldpolicy = os.environ.get('HGMODULEPOLICY', None)
455 oldpolicy = os.environ.get('HGMODULEPOLICY', None)
456 os.environ['HGMODULEPOLICY'] = 'py'
456 os.environ['HGMODULEPOLICY'] = 'py'
457 from mercurial import __version__
457 from mercurial import __version__
458
458
459 version = __version__.version
459 version = __version__.version
460 except ImportError:
460 except ImportError:
461 version = b'unknown'
461 version = b'unknown'
462 finally:
462 finally:
463 if oldpolicy is None:
463 if oldpolicy is None:
464 del os.environ['HGMODULEPOLICY']
464 del os.environ['HGMODULEPOLICY']
465 else:
465 else:
466 os.environ['HGMODULEPOLICY'] = oldpolicy
466 os.environ['HGMODULEPOLICY'] = oldpolicy
467
467
468
468
469 class hgbuild(build):
469 class hgbuild(build):
470 # Insert hgbuildmo first so that files in mercurial/locale/ are found
470 # Insert hgbuildmo first so that files in mercurial/locale/ are found
471 # when build_py is run next.
471 # when build_py is run next.
472 sub_commands = [('build_mo', None)] + build.sub_commands
472 sub_commands = [('build_mo', None)] + build.sub_commands
473
473
474
474
475 class hgbuildmo(build):
475 class hgbuildmo(build):
476
476
477 description = "build translations (.mo files)"
477 description = "build translations (.mo files)"
478
478
479 def run(self):
479 def run(self):
480 if not find_executable('msgfmt'):
480 if not find_executable('msgfmt'):
481 self.warn(
481 self.warn(
482 "could not find msgfmt executable, no translations "
482 "could not find msgfmt executable, no translations "
483 "will be built"
483 "will be built"
484 )
484 )
485 return
485 return
486
486
487 podir = 'i18n'
487 podir = 'i18n'
488 if not os.path.isdir(podir):
488 if not os.path.isdir(podir):
489 self.warn("could not find %s/ directory" % podir)
489 self.warn("could not find %s/ directory" % podir)
490 return
490 return
491
491
492 join = os.path.join
492 join = os.path.join
493 for po in os.listdir(podir):
493 for po in os.listdir(podir):
494 if not po.endswith('.po'):
494 if not po.endswith('.po'):
495 continue
495 continue
496 pofile = join(podir, po)
496 pofile = join(podir, po)
497 modir = join('locale', po[:-3], 'LC_MESSAGES')
497 modir = join('locale', po[:-3], 'LC_MESSAGES')
498 mofile = join(modir, 'hg.mo')
498 mofile = join(modir, 'hg.mo')
499 mobuildfile = join('mercurial', mofile)
499 mobuildfile = join('mercurial', mofile)
500 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
500 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
501 if sys.platform != 'sunos5':
501 if sys.platform != 'sunos5':
502 # msgfmt on Solaris does not know about -c
502 # msgfmt on Solaris does not know about -c
503 cmd.append('-c')
503 cmd.append('-c')
504 self.mkpath(join('mercurial', modir))
504 self.mkpath(join('mercurial', modir))
505 self.make_file([pofile], mobuildfile, spawn, (cmd,))
505 self.make_file([pofile], mobuildfile, spawn, (cmd,))
506
506
507
507
508 class hgdist(Distribution):
508 class hgdist(Distribution):
509 pure = False
509 pure = False
510 rust = False
510 rust = False
511 no_rust = False
511 no_rust = False
512 cffi = ispypy
512 cffi = ispypy
513
513
514 global_options = Distribution.global_options + [
514 global_options = Distribution.global_options + [
515 ('pure', None, "use pure (slow) Python code instead of C extensions"),
515 ('pure', None, "use pure (slow) Python code instead of C extensions"),
516 ('rust', None, "use Rust extensions additionally to C extensions"),
516 ('rust', None, "use Rust extensions additionally to C extensions"),
517 (
517 (
518 'no-rust',
518 'no-rust',
519 None,
519 None,
520 "do not use Rust extensions additionally to C extensions",
520 "do not use Rust extensions additionally to C extensions",
521 ),
521 ),
522 ]
522 ]
523
523
524 negative_opt = Distribution.negative_opt.copy()
524 negative_opt = Distribution.negative_opt.copy()
525 boolean_options = ['pure', 'rust', 'no-rust']
525 boolean_options = ['pure', 'rust', 'no-rust']
526 negative_opt['no-rust'] = 'rust'
526 negative_opt['no-rust'] = 'rust'
527
527
528 def _set_command_options(self, command_obj, option_dict=None):
528 def _set_command_options(self, command_obj, option_dict=None):
529 # Not all distutils versions in the wild have boolean_options.
529 # Not all distutils versions in the wild have boolean_options.
530 # This should be cleaned up when we're Python 3 only.
530 # This should be cleaned up when we're Python 3 only.
531 command_obj.boolean_options = (
531 command_obj.boolean_options = (
532 getattr(command_obj, 'boolean_options', []) + self.boolean_options
532 getattr(command_obj, 'boolean_options', []) + self.boolean_options
533 )
533 )
534 return Distribution._set_command_options(
534 return Distribution._set_command_options(
535 self, command_obj, option_dict=option_dict
535 self, command_obj, option_dict=option_dict
536 )
536 )
537
537
538 def parse_command_line(self):
538 def parse_command_line(self):
539 ret = Distribution.parse_command_line(self)
539 ret = Distribution.parse_command_line(self)
540 if not (self.rust or self.no_rust):
540 if not (self.rust or self.no_rust):
541 hgrustext = os.environ.get('HGWITHRUSTEXT')
541 hgrustext = os.environ.get('HGWITHRUSTEXT')
542 # TODO record it for proper rebuild upon changes
542 # TODO record it for proper rebuild upon changes
543 # (see mercurial/__modulepolicy__.py)
543 # (see mercurial/__modulepolicy__.py)
544 if hgrustext != 'cpython' and hgrustext is not None:
544 if hgrustext != 'cpython' and hgrustext is not None:
545 if hgrustext:
545 if hgrustext:
546 msg = 'unkown HGWITHRUSTEXT value: %s' % hgrustext
546 msg = 'unkown HGWITHRUSTEXT value: %s' % hgrustext
547 printf(msg, file=sys.stderr)
547 printf(msg, file=sys.stderr)
548 hgrustext = None
548 hgrustext = None
549 self.rust = hgrustext is not None
549 self.rust = hgrustext is not None
550 self.no_rust = not self.rust
550 self.no_rust = not self.rust
551 return ret
551 return ret
552
552
553 def has_ext_modules(self):
553 def has_ext_modules(self):
554 # self.ext_modules is emptied in hgbuildpy.finalize_options which is
554 # self.ext_modules is emptied in hgbuildpy.finalize_options which is
555 # too late for some cases
555 # too late for some cases
556 return not self.pure and Distribution.has_ext_modules(self)
556 return not self.pure and Distribution.has_ext_modules(self)
557
557
558
558
559 # This is ugly as a one-liner. So use a variable.
559 # This is ugly as a one-liner. So use a variable.
560 buildextnegops = dict(getattr(build_ext, 'negative_options', {}))
560 buildextnegops = dict(getattr(build_ext, 'negative_options', {}))
561 buildextnegops['no-zstd'] = 'zstd'
561 buildextnegops['no-zstd'] = 'zstd'
562 buildextnegops['no-rust'] = 'rust'
562 buildextnegops['no-rust'] = 'rust'
563
563
564
564
565 class hgbuildext(build_ext):
565 class hgbuildext(build_ext):
566 user_options = build_ext.user_options + [
566 user_options = build_ext.user_options + [
567 ('zstd', None, 'compile zstd bindings [default]'),
567 ('zstd', None, 'compile zstd bindings [default]'),
568 ('no-zstd', None, 'do not compile zstd bindings'),
568 ('no-zstd', None, 'do not compile zstd bindings'),
569 (
569 (
570 'rust',
570 'rust',
571 None,
571 None,
572 'compile Rust extensions if they are in use '
572 'compile Rust extensions if they are in use '
573 '(requires Cargo) [default]',
573 '(requires Cargo) [default]',
574 ),
574 ),
575 ('no-rust', None, 'do not compile Rust extensions'),
575 ('no-rust', None, 'do not compile Rust extensions'),
576 ]
576 ]
577
577
578 boolean_options = build_ext.boolean_options + ['zstd', 'rust']
578 boolean_options = build_ext.boolean_options + ['zstd', 'rust']
579 negative_opt = buildextnegops
579 negative_opt = buildextnegops
580
580
581 def initialize_options(self):
581 def initialize_options(self):
582 self.zstd = True
582 self.zstd = True
583 self.rust = True
583 self.rust = True
584
584
585 return build_ext.initialize_options(self)
585 return build_ext.initialize_options(self)
586
586
587 def finalize_options(self):
587 def finalize_options(self):
588 # Unless overridden by the end user, build extensions in parallel.
588 # Unless overridden by the end user, build extensions in parallel.
589 # Only influences behavior on Python 3.5+.
589 # Only influences behavior on Python 3.5+.
590 if getattr(self, 'parallel', None) is None:
590 if getattr(self, 'parallel', None) is None:
591 self.parallel = True
591 self.parallel = True
592
592
593 return build_ext.finalize_options(self)
593 return build_ext.finalize_options(self)
594
594
595 def build_extensions(self):
595 def build_extensions(self):
596 ruststandalones = [
596 ruststandalones = [
597 e for e in self.extensions if isinstance(e, RustStandaloneExtension)
597 e for e in self.extensions if isinstance(e, RustStandaloneExtension)
598 ]
598 ]
599 self.extensions = [
599 self.extensions = [
600 e for e in self.extensions if e not in ruststandalones
600 e for e in self.extensions if e not in ruststandalones
601 ]
601 ]
602 # Filter out zstd if disabled via argument.
602 # Filter out zstd if disabled via argument.
603 if not self.zstd:
603 if not self.zstd:
604 self.extensions = [
604 self.extensions = [
605 e for e in self.extensions if e.name != 'mercurial.zstd'
605 e for e in self.extensions if e.name != 'mercurial.zstd'
606 ]
606 ]
607
607
608 # Build Rust standalon extensions if it'll be used
608 # Build Rust standalon extensions if it'll be used
609 # and its build is not explictely disabled (for external build
609 # and its build is not explictely disabled (for external build
610 # as Linux distributions would do)
610 # as Linux distributions would do)
611 if self.distribution.rust and self.rust:
611 if self.distribution.rust and self.rust:
612 for rustext in ruststandalones:
612 for rustext in ruststandalones:
613 rustext.build('' if self.inplace else self.build_lib)
613 rustext.build('' if self.inplace else self.build_lib)
614
614
615 return build_ext.build_extensions(self)
615 return build_ext.build_extensions(self)
616
616
617 def build_extension(self, ext):
617 def build_extension(self, ext):
618 if (
618 if (
619 self.distribution.rust
619 self.distribution.rust
620 and self.rust
620 and self.rust
621 and isinstance(ext, RustExtension)
621 and isinstance(ext, RustExtension)
622 ):
622 ):
623 ext.rustbuild()
623 ext.rustbuild()
624 try:
624 try:
625 build_ext.build_extension(self, ext)
625 build_ext.build_extension(self, ext)
626 except CCompilerError:
626 except CCompilerError:
627 if not getattr(ext, 'optional', False):
627 if not getattr(ext, 'optional', False):
628 raise
628 raise
629 log.warn(
629 log.warn(
630 "Failed to build optional extension '%s' (skipping)", ext.name
630 "Failed to build optional extension '%s' (skipping)", ext.name
631 )
631 )
632
632
633
633
634 class hgbuildscripts(build_scripts):
634 class hgbuildscripts(build_scripts):
635 def run(self):
635 def run(self):
636 if os.name != 'nt' or self.distribution.pure:
636 if os.name != 'nt' or self.distribution.pure:
637 return build_scripts.run(self)
637 return build_scripts.run(self)
638
638
639 exebuilt = False
639 exebuilt = False
640 try:
640 try:
641 self.run_command('build_hgexe')
641 self.run_command('build_hgexe')
642 exebuilt = True
642 exebuilt = True
643 except (DistutilsError, CCompilerError):
643 except (DistutilsError, CCompilerError):
644 log.warn('failed to build optional hg.exe')
644 log.warn('failed to build optional hg.exe')
645
645
646 if exebuilt:
646 if exebuilt:
647 # Copying hg.exe to the scripts build directory ensures it is
647 # Copying hg.exe to the scripts build directory ensures it is
648 # installed by the install_scripts command.
648 # installed by the install_scripts command.
649 hgexecommand = self.get_finalized_command('build_hgexe')
649 hgexecommand = self.get_finalized_command('build_hgexe')
650 dest = os.path.join(self.build_dir, 'hg.exe')
650 dest = os.path.join(self.build_dir, 'hg.exe')
651 self.mkpath(self.build_dir)
651 self.mkpath(self.build_dir)
652 self.copy_file(hgexecommand.hgexepath, dest)
652 self.copy_file(hgexecommand.hgexepath, dest)
653
653
654 # Remove hg.bat because it is redundant with hg.exe.
654 # Remove hg.bat because it is redundant with hg.exe.
655 self.scripts.remove('contrib/win32/hg.bat')
655 self.scripts.remove('contrib/win32/hg.bat')
656
656
657 return build_scripts.run(self)
657 return build_scripts.run(self)
658
658
659
659
660 class hgbuildpy(build_py):
660 class hgbuildpy(build_py):
661 def finalize_options(self):
661 def finalize_options(self):
662 build_py.finalize_options(self)
662 build_py.finalize_options(self)
663
663
664 if self.distribution.pure:
664 if self.distribution.pure:
665 self.distribution.ext_modules = []
665 self.distribution.ext_modules = []
666 elif self.distribution.cffi:
666 elif self.distribution.cffi:
667 from mercurial.cffi import (
667 from mercurial.cffi import (
668 bdiffbuild,
668 bdiffbuild,
669 mpatchbuild,
669 mpatchbuild,
670 )
670 )
671
671
672 exts = [
672 exts = [
673 mpatchbuild.ffi.distutils_extension(),
673 mpatchbuild.ffi.distutils_extension(),
674 bdiffbuild.ffi.distutils_extension(),
674 bdiffbuild.ffi.distutils_extension(),
675 ]
675 ]
676 # cffi modules go here
676 # cffi modules go here
677 if sys.platform == 'darwin':
677 if sys.platform == 'darwin':
678 from mercurial.cffi import osutilbuild
678 from mercurial.cffi import osutilbuild
679
679
680 exts.append(osutilbuild.ffi.distutils_extension())
680 exts.append(osutilbuild.ffi.distutils_extension())
681 self.distribution.ext_modules = exts
681 self.distribution.ext_modules = exts
682 else:
682 else:
683 h = os.path.join(get_python_inc(), 'Python.h')
683 h = os.path.join(get_python_inc(), 'Python.h')
684 if not os.path.exists(h):
684 if not os.path.exists(h):
685 raise SystemExit(
685 raise SystemExit(
686 'Python headers are required to build '
686 'Python headers are required to build '
687 'Mercurial but weren\'t found in %s' % h
687 'Mercurial but weren\'t found in %s' % h
688 )
688 )
689
689
690 def run(self):
690 def run(self):
691 basepath = os.path.join(self.build_lib, 'mercurial')
691 basepath = os.path.join(self.build_lib, 'mercurial')
692 self.mkpath(basepath)
692 self.mkpath(basepath)
693
693
694 rust = self.distribution.rust
694 rust = self.distribution.rust
695 if self.distribution.pure:
695 if self.distribution.pure:
696 modulepolicy = 'py'
696 modulepolicy = 'py'
697 elif self.build_lib == '.':
697 elif self.build_lib == '.':
698 # in-place build should run without rebuilding and Rust extensions
698 # in-place build should run without rebuilding and Rust extensions
699 modulepolicy = 'rust+c-allow' if rust else 'allow'
699 modulepolicy = 'rust+c-allow' if rust else 'allow'
700 else:
700 else:
701 modulepolicy = 'rust+c' if rust else 'c'
701 modulepolicy = 'rust+c' if rust else 'c'
702
702
703 content = b''.join(
703 content = b''.join(
704 [
704 [
705 b'# this file is autogenerated by setup.py\n',
705 b'# this file is autogenerated by setup.py\n',
706 b'modulepolicy = b"%s"\n' % modulepolicy.encode('ascii'),
706 b'modulepolicy = b"%s"\n' % modulepolicy.encode('ascii'),
707 ]
707 ]
708 )
708 )
709 write_if_changed(os.path.join(basepath, '__modulepolicy__.py'), content)
709 write_if_changed(os.path.join(basepath, '__modulepolicy__.py'), content)
710
710
711 build_py.run(self)
711 build_py.run(self)
712
712
713
713
714 class buildhgextindex(Command):
714 class buildhgextindex(Command):
715 description = 'generate prebuilt index of hgext (for frozen package)'
715 description = 'generate prebuilt index of hgext (for frozen package)'
716 user_options = []
716 user_options = []
717 _indexfilename = 'hgext/__index__.py'
717 _indexfilename = 'hgext/__index__.py'
718
718
719 def initialize_options(self):
719 def initialize_options(self):
720 pass
720 pass
721
721
722 def finalize_options(self):
722 def finalize_options(self):
723 pass
723 pass
724
724
725 def run(self):
725 def run(self):
726 if os.path.exists(self._indexfilename):
726 if os.path.exists(self._indexfilename):
727 with open(self._indexfilename, 'w') as f:
727 with open(self._indexfilename, 'w') as f:
728 f.write('# empty\n')
728 f.write('# empty\n')
729
729
730 # here no extension enabled, disabled() lists up everything
730 # here no extension enabled, disabled() lists up everything
731 code = (
731 code = (
732 'import pprint; from mercurial import extensions; '
732 'import pprint; from mercurial import extensions; '
733 'ext = extensions.disabled();'
733 'ext = extensions.disabled();'
734 'ext.pop("__index__", None);'
734 'ext.pop("__index__", None);'
735 'pprint.pprint(ext)'
735 'pprint.pprint(ext)'
736 )
736 )
737 returncode, out, err = runcmd(
737 returncode, out, err = runcmd(
738 [sys.executable, '-c', code], localhgenv()
738 [sys.executable, '-c', code], localhgenv()
739 )
739 )
740 if err or returncode != 0:
740 if err or returncode != 0:
741 raise DistutilsExecError(err)
741 raise DistutilsExecError(err)
742
742
743 with open(self._indexfilename, 'wb') as f:
743 with open(self._indexfilename, 'wb') as f:
744 f.write(b'# this file is autogenerated by setup.py\n')
744 f.write(b'# this file is autogenerated by setup.py\n')
745 f.write(b'docs = ')
745 f.write(b'docs = ')
746 f.write(out)
746 f.write(out)
747
747
748
748
749 class buildhgexe(build_ext):
749 class buildhgexe(build_ext):
750 description = 'compile hg.exe from mercurial/exewrapper.c'
750 description = 'compile hg.exe from mercurial/exewrapper.c'
751 user_options = build_ext.user_options + [
751 user_options = build_ext.user_options + [
752 (
752 (
753 'long-paths-support',
753 'long-paths-support',
754 None,
754 None,
755 'enable support for long paths on '
755 'enable support for long paths on '
756 'Windows (off by default and '
756 'Windows (off by default and '
757 'experimental)',
757 'experimental)',
758 ),
758 ),
759 ]
759 ]
760
760
761 LONG_PATHS_MANIFEST = """
761 LONG_PATHS_MANIFEST = """
762 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
762 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
763 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
763 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
764 <application>
764 <application>
765 <windowsSettings
765 <windowsSettings
766 xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
766 xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
767 <ws2:longPathAware>true</ws2:longPathAware>
767 <ws2:longPathAware>true</ws2:longPathAware>
768 </windowsSettings>
768 </windowsSettings>
769 </application>
769 </application>
770 </assembly>"""
770 </assembly>"""
771
771
772 def initialize_options(self):
772 def initialize_options(self):
773 build_ext.initialize_options(self)
773 build_ext.initialize_options(self)
774 self.long_paths_support = False
774 self.long_paths_support = False
775
775
776 def build_extensions(self):
776 def build_extensions(self):
777 if os.name != 'nt':
777 if os.name != 'nt':
778 return
778 return
779 if isinstance(self.compiler, HackedMingw32CCompiler):
779 if isinstance(self.compiler, HackedMingw32CCompiler):
780 self.compiler.compiler_so = self.compiler.compiler # no -mdll
780 self.compiler.compiler_so = self.compiler.compiler # no -mdll
781 self.compiler.dll_libraries = [] # no -lmsrvc90
781 self.compiler.dll_libraries = [] # no -lmsrvc90
782
782
783 pythonlib = None
783 pythonlib = None
784
784
785 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
785 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
786 self.hgtarget = os.path.join(dir, 'hg')
786 self.hgtarget = os.path.join(dir, 'hg')
787
787
788 if getattr(sys, 'dllhandle', None):
788 if getattr(sys, 'dllhandle', None):
789 # Different Python installs can have different Python library
789 # Different Python installs can have different Python library
790 # names. e.g. the official CPython distribution uses pythonXY.dll
790 # names. e.g. the official CPython distribution uses pythonXY.dll
791 # and MinGW uses libpythonX.Y.dll.
791 # and MinGW uses libpythonX.Y.dll.
792 _kernel32 = ctypes.windll.kernel32
792 _kernel32 = ctypes.windll.kernel32
793 _kernel32.GetModuleFileNameA.argtypes = [
793 _kernel32.GetModuleFileNameA.argtypes = [
794 ctypes.c_void_p,
794 ctypes.c_void_p,
795 ctypes.c_void_p,
795 ctypes.c_void_p,
796 ctypes.c_ulong,
796 ctypes.c_ulong,
797 ]
797 ]
798 _kernel32.GetModuleFileNameA.restype = ctypes.c_ulong
798 _kernel32.GetModuleFileNameA.restype = ctypes.c_ulong
799 size = 1000
799 size = 1000
800 buf = ctypes.create_string_buffer(size + 1)
800 buf = ctypes.create_string_buffer(size + 1)
801 filelen = _kernel32.GetModuleFileNameA(
801 filelen = _kernel32.GetModuleFileNameA(
802 sys.dllhandle, ctypes.byref(buf), size
802 sys.dllhandle, ctypes.byref(buf), size
803 )
803 )
804
804
805 if filelen > 0 and filelen != size:
805 if filelen > 0 and filelen != size:
806 dllbasename = os.path.basename(buf.value)
806 dllbasename = os.path.basename(buf.value)
807 if not dllbasename.lower().endswith(b'.dll'):
807 if not dllbasename.lower().endswith(b'.dll'):
808 raise SystemExit(
808 raise SystemExit(
809 'Python DLL does not end with .dll: %s' % dllbasename
809 'Python DLL does not end with .dll: %s' % dllbasename
810 )
810 )
811 pythonlib = dllbasename[:-4]
811 pythonlib = dllbasename[:-4]
812
812
813 # Copy the pythonXY.dll next to the binary so that it runs
813 # Copy the pythonXY.dll next to the binary so that it runs
814 # without tampering with PATH.
814 # without tampering with PATH.
815 fsdecode = lambda x: x
815 fsdecode = lambda x: x
816 if sys.version_info[0] >= 3:
816 if sys.version_info[0] >= 3:
817 fsdecode = os.fsdecode
817 fsdecode = os.fsdecode
818 dest = os.path.join(
818 dest = os.path.join(
819 os.path.dirname(self.hgtarget),
819 os.path.dirname(self.hgtarget), fsdecode(dllbasename),
820 fsdecode(dllbasename),
821 )
820 )
822
821
823 if not os.path.exists(dest):
822 if not os.path.exists(dest):
824 shutil.copy(buf.value, dest)
823 shutil.copy(buf.value, dest)
825
824
826 if not pythonlib:
825 if not pythonlib:
827 log.warn(
826 log.warn(
828 'could not determine Python DLL filename; assuming pythonXY'
827 'could not determine Python DLL filename; assuming pythonXY'
829 )
828 )
830
829
831 hv = sys.hexversion
830 hv = sys.hexversion
832 pythonlib = b'python%d%d' % (hv >> 24, (hv >> 16) & 0xFF)
831 pythonlib = b'python%d%d' % (hv >> 24, (hv >> 16) & 0xFF)
833
832
834 log.info('using %s as Python library name' % pythonlib)
833 log.info('using %s as Python library name' % pythonlib)
835 with open('mercurial/hgpythonlib.h', 'wb') as f:
834 with open('mercurial/hgpythonlib.h', 'wb') as f:
836 f.write(b'/* this file is autogenerated by setup.py */\n')
835 f.write(b'/* this file is autogenerated by setup.py */\n')
837 f.write(b'#define HGPYTHONLIB "%s"\n' % pythonlib)
836 f.write(b'#define HGPYTHONLIB "%s"\n' % pythonlib)
838
837
839 macros = None
838 macros = None
840 if sys.version_info[0] >= 3:
839 if sys.version_info[0] >= 3:
841 macros = [('_UNICODE', None), ('UNICODE', None)]
840 macros = [('_UNICODE', None), ('UNICODE', None)]
842
841
843 objects = self.compiler.compile(
842 objects = self.compiler.compile(
844 ['mercurial/exewrapper.c'],
843 ['mercurial/exewrapper.c'],
845 output_dir=self.build_temp,
844 output_dir=self.build_temp,
846 macros=macros,
845 macros=macros,
847 )
846 )
848 self.compiler.link_executable(
847 self.compiler.link_executable(
849 objects, self.hgtarget, libraries=[], output_dir=self.build_temp
848 objects, self.hgtarget, libraries=[], output_dir=self.build_temp
850 )
849 )
851 if self.long_paths_support:
850 if self.long_paths_support:
852 self.addlongpathsmanifest()
851 self.addlongpathsmanifest()
853
852
854 def addlongpathsmanifest(self):
853 def addlongpathsmanifest(self):
855 r"""Add manifest pieces so that hg.exe understands long paths
854 r"""Add manifest pieces so that hg.exe understands long paths
856
855
857 This is an EXPERIMENTAL feature, use with care.
856 This is an EXPERIMENTAL feature, use with care.
858 To enable long paths support, one needs to do two things:
857 To enable long paths support, one needs to do two things:
859 - build Mercurial with --long-paths-support option
858 - build Mercurial with --long-paths-support option
860 - change HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\
859 - change HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\
861 LongPathsEnabled to have value 1.
860 LongPathsEnabled to have value 1.
862
861
863 Please ignore 'warning 81010002: Unrecognized Element "longPathAware"';
862 Please ignore 'warning 81010002: Unrecognized Element "longPathAware"';
864 it happens because Mercurial uses mt.exe circa 2008, which is not
863 it happens because Mercurial uses mt.exe circa 2008, which is not
865 yet aware of long paths support in the manifest (I think so at least).
864 yet aware of long paths support in the manifest (I think so at least).
866 This does not stop mt.exe from embedding/merging the XML properly.
865 This does not stop mt.exe from embedding/merging the XML properly.
867
866
868 Why resource #1 should be used for .exe manifests? I don't know and
867 Why resource #1 should be used for .exe manifests? I don't know and
869 wasn't able to find an explanation for mortals. But it seems to work.
868 wasn't able to find an explanation for mortals. But it seems to work.
870 """
869 """
871 exefname = self.compiler.executable_filename(self.hgtarget)
870 exefname = self.compiler.executable_filename(self.hgtarget)
872 fdauto, manfname = tempfile.mkstemp(suffix='.hg.exe.manifest')
871 fdauto, manfname = tempfile.mkstemp(suffix='.hg.exe.manifest')
873 os.close(fdauto)
872 os.close(fdauto)
874 with open(manfname, 'w') as f:
873 with open(manfname, 'w') as f:
875 f.write(self.LONG_PATHS_MANIFEST)
874 f.write(self.LONG_PATHS_MANIFEST)
876 log.info("long paths manifest is written to '%s'" % manfname)
875 log.info("long paths manifest is written to '%s'" % manfname)
877 inputresource = '-inputresource:%s;#1' % exefname
876 inputresource = '-inputresource:%s;#1' % exefname
878 outputresource = '-outputresource:%s;#1' % exefname
877 outputresource = '-outputresource:%s;#1' % exefname
879 log.info("running mt.exe to update hg.exe's manifest in-place")
878 log.info("running mt.exe to update hg.exe's manifest in-place")
880 # supplying both -manifest and -inputresource to mt.exe makes
879 # supplying both -manifest and -inputresource to mt.exe makes
881 # it merge the embedded and supplied manifests in the -outputresource
880 # it merge the embedded and supplied manifests in the -outputresource
882 self.spawn(
881 self.spawn(
883 [
882 [
884 'mt.exe',
883 'mt.exe',
885 '-nologo',
884 '-nologo',
886 '-manifest',
885 '-manifest',
887 manfname,
886 manfname,
888 inputresource,
887 inputresource,
889 outputresource,
888 outputresource,
890 ]
889 ]
891 )
890 )
892 log.info("done updating hg.exe's manifest")
891 log.info("done updating hg.exe's manifest")
893 os.remove(manfname)
892 os.remove(manfname)
894
893
895 @property
894 @property
896 def hgexepath(self):
895 def hgexepath(self):
897 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
896 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
898 return os.path.join(self.build_temp, dir, 'hg.exe')
897 return os.path.join(self.build_temp, dir, 'hg.exe')
899
898
900
899
901 class hgbuilddoc(Command):
900 class hgbuilddoc(Command):
902 description = 'build documentation'
901 description = 'build documentation'
903 user_options = [
902 user_options = [
904 ('man', None, 'generate man pages'),
903 ('man', None, 'generate man pages'),
905 ('html', None, 'generate html pages'),
904 ('html', None, 'generate html pages'),
906 ]
905 ]
907
906
908 def initialize_options(self):
907 def initialize_options(self):
909 self.man = None
908 self.man = None
910 self.html = None
909 self.html = None
911
910
912 def finalize_options(self):
911 def finalize_options(self):
913 # If --man or --html are set, only generate what we're told to.
912 # If --man or --html are set, only generate what we're told to.
914 # Otherwise generate everything.
913 # Otherwise generate everything.
915 have_subset = self.man is not None or self.html is not None
914 have_subset = self.man is not None or self.html is not None
916
915
917 if have_subset:
916 if have_subset:
918 self.man = True if self.man else False
917 self.man = True if self.man else False
919 self.html = True if self.html else False
918 self.html = True if self.html else False
920 else:
919 else:
921 self.man = True
920 self.man = True
922 self.html = True
921 self.html = True
923
922
924 def run(self):
923 def run(self):
925 def normalizecrlf(p):
924 def normalizecrlf(p):
926 with open(p, 'rb') as fh:
925 with open(p, 'rb') as fh:
927 orig = fh.read()
926 orig = fh.read()
928
927
929 if b'\r\n' not in orig:
928 if b'\r\n' not in orig:
930 return
929 return
931
930
932 log.info('normalizing %s to LF line endings' % p)
931 log.info('normalizing %s to LF line endings' % p)
933 with open(p, 'wb') as fh:
932 with open(p, 'wb') as fh:
934 fh.write(orig.replace(b'\r\n', b'\n'))
933 fh.write(orig.replace(b'\r\n', b'\n'))
935
934
936 def gentxt(root):
935 def gentxt(root):
937 txt = 'doc/%s.txt' % root
936 txt = 'doc/%s.txt' % root
938 log.info('generating %s' % txt)
937 log.info('generating %s' % txt)
939 res, out, err = runcmd(
938 res, out, err = runcmd(
940 [sys.executable, 'gendoc.py', root], os.environ, cwd='doc'
939 [sys.executable, 'gendoc.py', root], os.environ, cwd='doc'
941 )
940 )
942 if res:
941 if res:
943 raise SystemExit(
942 raise SystemExit(
944 'error running gendoc.py: %s'
943 'error running gendoc.py: %s'
945 % '\n'.join([sysstr(out), sysstr(err)])
944 % '\n'.join([sysstr(out), sysstr(err)])
946 )
945 )
947
946
948 with open(txt, 'wb') as fh:
947 with open(txt, 'wb') as fh:
949 fh.write(out)
948 fh.write(out)
950
949
951 def gengendoc(root):
950 def gengendoc(root):
952 gendoc = 'doc/%s.gendoc.txt' % root
951 gendoc = 'doc/%s.gendoc.txt' % root
953
952
954 log.info('generating %s' % gendoc)
953 log.info('generating %s' % gendoc)
955 res, out, err = runcmd(
954 res, out, err = runcmd(
956 [sys.executable, 'gendoc.py', '%s.gendoc' % root],
955 [sys.executable, 'gendoc.py', '%s.gendoc' % root],
957 os.environ,
956 os.environ,
958 cwd='doc',
957 cwd='doc',
959 )
958 )
960 if res:
959 if res:
961 raise SystemExit(
960 raise SystemExit(
962 'error running gendoc: %s'
961 'error running gendoc: %s'
963 % '\n'.join([sysstr(out), sysstr(err)])
962 % '\n'.join([sysstr(out), sysstr(err)])
964 )
963 )
965
964
966 with open(gendoc, 'wb') as fh:
965 with open(gendoc, 'wb') as fh:
967 fh.write(out)
966 fh.write(out)
968
967
969 def genman(root):
968 def genman(root):
970 log.info('generating doc/%s' % root)
969 log.info('generating doc/%s' % root)
971 res, out, err = runcmd(
970 res, out, err = runcmd(
972 [
971 [
973 sys.executable,
972 sys.executable,
974 'runrst',
973 'runrst',
975 'hgmanpage',
974 'hgmanpage',
976 '--halt',
975 '--halt',
977 'warning',
976 'warning',
978 '--strip-elements-with-class',
977 '--strip-elements-with-class',
979 'htmlonly',
978 'htmlonly',
980 '%s.txt' % root,
979 '%s.txt' % root,
981 root,
980 root,
982 ],
981 ],
983 os.environ,
982 os.environ,
984 cwd='doc',
983 cwd='doc',
985 )
984 )
986 if res:
985 if res:
987 raise SystemExit(
986 raise SystemExit(
988 'error running runrst: %s'
987 'error running runrst: %s'
989 % '\n'.join([sysstr(out), sysstr(err)])
988 % '\n'.join([sysstr(out), sysstr(err)])
990 )
989 )
991
990
992 normalizecrlf('doc/%s' % root)
991 normalizecrlf('doc/%s' % root)
993
992
994 def genhtml(root):
993 def genhtml(root):
995 log.info('generating doc/%s.html' % root)
994 log.info('generating doc/%s.html' % root)
996 res, out, err = runcmd(
995 res, out, err = runcmd(
997 [
996 [
998 sys.executable,
997 sys.executable,
999 'runrst',
998 'runrst',
1000 'html',
999 'html',
1001 '--halt',
1000 '--halt',
1002 'warning',
1001 'warning',
1003 '--link-stylesheet',
1002 '--link-stylesheet',
1004 '--stylesheet-path',
1003 '--stylesheet-path',
1005 'style.css',
1004 'style.css',
1006 '%s.txt' % root,
1005 '%s.txt' % root,
1007 '%s.html' % root,
1006 '%s.html' % root,
1008 ],
1007 ],
1009 os.environ,
1008 os.environ,
1010 cwd='doc',
1009 cwd='doc',
1011 )
1010 )
1012 if res:
1011 if res:
1013 raise SystemExit(
1012 raise SystemExit(
1014 'error running runrst: %s'
1013 'error running runrst: %s'
1015 % '\n'.join([sysstr(out), sysstr(err)])
1014 % '\n'.join([sysstr(out), sysstr(err)])
1016 )
1015 )
1017
1016
1018 normalizecrlf('doc/%s.html' % root)
1017 normalizecrlf('doc/%s.html' % root)
1019
1018
1020 # This logic is duplicated in doc/Makefile.
1019 # This logic is duplicated in doc/Makefile.
1021 sources = {
1020 sources = {
1022 f
1021 f
1023 for f in os.listdir('mercurial/helptext')
1022 for f in os.listdir('mercurial/helptext')
1024 if re.search(r'[0-9]\.txt$', f)
1023 if re.search(r'[0-9]\.txt$', f)
1025 }
1024 }
1026
1025
1027 # common.txt is a one-off.
1026 # common.txt is a one-off.
1028 gentxt('common')
1027 gentxt('common')
1029
1028
1030 for source in sorted(sources):
1029 for source in sorted(sources):
1031 assert source[-4:] == '.txt'
1030 assert source[-4:] == '.txt'
1032 root = source[:-4]
1031 root = source[:-4]
1033
1032
1034 gentxt(root)
1033 gentxt(root)
1035 gengendoc(root)
1034 gengendoc(root)
1036
1035
1037 if self.man:
1036 if self.man:
1038 genman(root)
1037 genman(root)
1039 if self.html:
1038 if self.html:
1040 genhtml(root)
1039 genhtml(root)
1041
1040
1042
1041
1043 class hginstall(install):
1042 class hginstall(install):
1044
1043
1045 user_options = install.user_options + [
1044 user_options = install.user_options + [
1046 (
1045 (
1047 'old-and-unmanageable',
1046 'old-and-unmanageable',
1048 None,
1047 None,
1049 'noop, present for eggless setuptools compat',
1048 'noop, present for eggless setuptools compat',
1050 ),
1049 ),
1051 (
1050 (
1052 'single-version-externally-managed',
1051 'single-version-externally-managed',
1053 None,
1052 None,
1054 'noop, present for eggless setuptools compat',
1053 'noop, present for eggless setuptools compat',
1055 ),
1054 ),
1056 ]
1055 ]
1057
1056
1058 # Also helps setuptools not be sad while we refuse to create eggs.
1057 # Also helps setuptools not be sad while we refuse to create eggs.
1059 single_version_externally_managed = True
1058 single_version_externally_managed = True
1060
1059
1061 def get_sub_commands(self):
1060 def get_sub_commands(self):
1062 # Screen out egg related commands to prevent egg generation. But allow
1061 # Screen out egg related commands to prevent egg generation. But allow
1063 # mercurial.egg-info generation, since that is part of modern
1062 # mercurial.egg-info generation, since that is part of modern
1064 # packaging.
1063 # packaging.
1065 excl = {'bdist_egg'}
1064 excl = {'bdist_egg'}
1066 return filter(lambda x: x not in excl, install.get_sub_commands(self))
1065 return filter(lambda x: x not in excl, install.get_sub_commands(self))
1067
1066
1068
1067
1069 class hginstalllib(install_lib):
1068 class hginstalllib(install_lib):
1070 '''
1069 '''
1071 This is a specialization of install_lib that replaces the copy_file used
1070 This is a specialization of install_lib that replaces the copy_file used
1072 there so that it supports setting the mode of files after copying them,
1071 there so that it supports setting the mode of files after copying them,
1073 instead of just preserving the mode that the files originally had. If your
1072 instead of just preserving the mode that the files originally had. If your
1074 system has a umask of something like 027, preserving the permissions when
1073 system has a umask of something like 027, preserving the permissions when
1075 copying will lead to a broken install.
1074 copying will lead to a broken install.
1076
1075
1077 Note that just passing keep_permissions=False to copy_file would be
1076 Note that just passing keep_permissions=False to copy_file would be
1078 insufficient, as it might still be applying a umask.
1077 insufficient, as it might still be applying a umask.
1079 '''
1078 '''
1080
1079
1081 def run(self):
1080 def run(self):
1082 realcopyfile = file_util.copy_file
1081 realcopyfile = file_util.copy_file
1083
1082
1084 def copyfileandsetmode(*args, **kwargs):
1083 def copyfileandsetmode(*args, **kwargs):
1085 src, dst = args[0], args[1]
1084 src, dst = args[0], args[1]
1086 dst, copied = realcopyfile(*args, **kwargs)
1085 dst, copied = realcopyfile(*args, **kwargs)
1087 if copied:
1086 if copied:
1088 st = os.stat(src)
1087 st = os.stat(src)
1089 # Persist executable bit (apply it to group and other if user
1088 # Persist executable bit (apply it to group and other if user
1090 # has it)
1089 # has it)
1091 if st[stat.ST_MODE] & stat.S_IXUSR:
1090 if st[stat.ST_MODE] & stat.S_IXUSR:
1092 setmode = int('0755', 8)
1091 setmode = int('0755', 8)
1093 else:
1092 else:
1094 setmode = int('0644', 8)
1093 setmode = int('0644', 8)
1095 m = stat.S_IMODE(st[stat.ST_MODE])
1094 m = stat.S_IMODE(st[stat.ST_MODE])
1096 m = (m & ~int('0777', 8)) | setmode
1095 m = (m & ~int('0777', 8)) | setmode
1097 os.chmod(dst, m)
1096 os.chmod(dst, m)
1098
1097
1099 file_util.copy_file = copyfileandsetmode
1098 file_util.copy_file = copyfileandsetmode
1100 try:
1099 try:
1101 install_lib.run(self)
1100 install_lib.run(self)
1102 finally:
1101 finally:
1103 file_util.copy_file = realcopyfile
1102 file_util.copy_file = realcopyfile
1104
1103
1105
1104
1106 class hginstallscripts(install_scripts):
1105 class hginstallscripts(install_scripts):
1107 '''
1106 '''
1108 This is a specialization of install_scripts that replaces the @LIBDIR@ with
1107 This is a specialization of install_scripts that replaces the @LIBDIR@ with
1109 the configured directory for modules. If possible, the path is made relative
1108 the configured directory for modules. If possible, the path is made relative
1110 to the directory for scripts.
1109 to the directory for scripts.
1111 '''
1110 '''
1112
1111
1113 def initialize_options(self):
1112 def initialize_options(self):
1114 install_scripts.initialize_options(self)
1113 install_scripts.initialize_options(self)
1115
1114
1116 self.install_lib = None
1115 self.install_lib = None
1117
1116
1118 def finalize_options(self):
1117 def finalize_options(self):
1119 install_scripts.finalize_options(self)
1118 install_scripts.finalize_options(self)
1120 self.set_undefined_options('install', ('install_lib', 'install_lib'))
1119 self.set_undefined_options('install', ('install_lib', 'install_lib'))
1121
1120
1122 def run(self):
1121 def run(self):
1123 install_scripts.run(self)
1122 install_scripts.run(self)
1124
1123
1125 # It only makes sense to replace @LIBDIR@ with the install path if
1124 # It only makes sense to replace @LIBDIR@ with the install path if
1126 # the install path is known. For wheels, the logic below calculates
1125 # the install path is known. For wheels, the logic below calculates
1127 # the libdir to be "../..". This is because the internal layout of a
1126 # the libdir to be "../..". This is because the internal layout of a
1128 # wheel archive looks like:
1127 # wheel archive looks like:
1129 #
1128 #
1130 # mercurial-3.6.1.data/scripts/hg
1129 # mercurial-3.6.1.data/scripts/hg
1131 # mercurial/__init__.py
1130 # mercurial/__init__.py
1132 #
1131 #
1133 # When installing wheels, the subdirectories of the "<pkg>.data"
1132 # When installing wheels, the subdirectories of the "<pkg>.data"
1134 # directory are translated to system local paths and files therein
1133 # directory are translated to system local paths and files therein
1135 # are copied in place. The mercurial/* files are installed into the
1134 # are copied in place. The mercurial/* files are installed into the
1136 # site-packages directory. However, the site-packages directory
1135 # site-packages directory. However, the site-packages directory
1137 # isn't known until wheel install time. This means we have no clue
1136 # isn't known until wheel install time. This means we have no clue
1138 # at wheel generation time what the installed site-packages directory
1137 # at wheel generation time what the installed site-packages directory
1139 # will be. And, wheels don't appear to provide the ability to register
1138 # will be. And, wheels don't appear to provide the ability to register
1140 # custom code to run during wheel installation. This all means that
1139 # custom code to run during wheel installation. This all means that
1141 # we can't reliably set the libdir in wheels: the default behavior
1140 # we can't reliably set the libdir in wheels: the default behavior
1142 # of looking in sys.path must do.
1141 # of looking in sys.path must do.
1143
1142
1144 if (
1143 if (
1145 os.path.splitdrive(self.install_dir)[0]
1144 os.path.splitdrive(self.install_dir)[0]
1146 != os.path.splitdrive(self.install_lib)[0]
1145 != os.path.splitdrive(self.install_lib)[0]
1147 ):
1146 ):
1148 # can't make relative paths from one drive to another, so use an
1147 # can't make relative paths from one drive to another, so use an
1149 # absolute path instead
1148 # absolute path instead
1150 libdir = self.install_lib
1149 libdir = self.install_lib
1151 else:
1150 else:
1152 libdir = os.path.relpath(self.install_lib, self.install_dir)
1151 libdir = os.path.relpath(self.install_lib, self.install_dir)
1153
1152
1154 for outfile in self.outfiles:
1153 for outfile in self.outfiles:
1155 with open(outfile, 'rb') as fp:
1154 with open(outfile, 'rb') as fp:
1156 data = fp.read()
1155 data = fp.read()
1157
1156
1158 # skip binary files
1157 # skip binary files
1159 if b'\0' in data:
1158 if b'\0' in data:
1160 continue
1159 continue
1161
1160
1162 # During local installs, the shebang will be rewritten to the final
1161 # During local installs, the shebang will be rewritten to the final
1163 # install path. During wheel packaging, the shebang has a special
1162 # install path. During wheel packaging, the shebang has a special
1164 # value.
1163 # value.
1165 if data.startswith(b'#!python'):
1164 if data.startswith(b'#!python'):
1166 log.info(
1165 log.info(
1167 'not rewriting @LIBDIR@ in %s because install path '
1166 'not rewriting @LIBDIR@ in %s because install path '
1168 'not known' % outfile
1167 'not known' % outfile
1169 )
1168 )
1170 continue
1169 continue
1171
1170
1172 data = data.replace(b'@LIBDIR@', libdir.encode(libdir_escape))
1171 data = data.replace(b'@LIBDIR@', libdir.encode(libdir_escape))
1173 with open(outfile, 'wb') as fp:
1172 with open(outfile, 'wb') as fp:
1174 fp.write(data)
1173 fp.write(data)
1175
1174
1176
1175
1177 # virtualenv installs custom distutils/__init__.py and
1176 # virtualenv installs custom distutils/__init__.py and
1178 # distutils/distutils.cfg files which essentially proxy back to the
1177 # distutils/distutils.cfg files which essentially proxy back to the
1179 # "real" distutils in the main Python install. The presence of this
1178 # "real" distutils in the main Python install. The presence of this
1180 # directory causes py2exe to pick up the "hacked" distutils package
1179 # directory causes py2exe to pick up the "hacked" distutils package
1181 # from the virtualenv and "import distutils" will fail from the py2exe
1180 # from the virtualenv and "import distutils" will fail from the py2exe
1182 # build because the "real" distutils files can't be located.
1181 # build because the "real" distutils files can't be located.
1183 #
1182 #
1184 # We work around this by monkeypatching the py2exe code finding Python
1183 # We work around this by monkeypatching the py2exe code finding Python
1185 # modules to replace the found virtualenv distutils modules with the
1184 # modules to replace the found virtualenv distutils modules with the
1186 # original versions via filesystem scanning. This is a bit hacky. But
1185 # original versions via filesystem scanning. This is a bit hacky. But
1187 # it allows us to use virtualenvs for py2exe packaging, which is more
1186 # it allows us to use virtualenvs for py2exe packaging, which is more
1188 # deterministic and reproducible.
1187 # deterministic and reproducible.
1189 #
1188 #
1190 # It's worth noting that the common StackOverflow suggestions for this
1189 # It's worth noting that the common StackOverflow suggestions for this
1191 # problem involve copying the original distutils files into the
1190 # problem involve copying the original distutils files into the
1192 # virtualenv or into the staging directory after setup() is invoked.
1191 # virtualenv or into the staging directory after setup() is invoked.
1193 # The former is very brittle and can easily break setup(). Our hacking
1192 # The former is very brittle and can easily break setup(). Our hacking
1194 # of the found modules routine has a similar result as copying the files
1193 # of the found modules routine has a similar result as copying the files
1195 # manually. But it makes fewer assumptions about how py2exe works and
1194 # manually. But it makes fewer assumptions about how py2exe works and
1196 # is less brittle.
1195 # is less brittle.
1197
1196
1198 # This only catches virtualenvs made with virtualenv (as opposed to
1197 # This only catches virtualenvs made with virtualenv (as opposed to
1199 # venv, which is likely what Python 3 uses).
1198 # venv, which is likely what Python 3 uses).
1200 py2exehacked = py2exeloaded and getattr(sys, 'real_prefix', None) is not None
1199 py2exehacked = py2exeloaded and getattr(sys, 'real_prefix', None) is not None
1201
1200
1202 if py2exehacked:
1201 if py2exehacked:
1203 from distutils.command.py2exe import py2exe as buildpy2exe
1202 from distutils.command.py2exe import py2exe as buildpy2exe
1204 from py2exe.mf import Module as py2exemodule
1203 from py2exe.mf import Module as py2exemodule
1205
1204
1206 class hgbuildpy2exe(buildpy2exe):
1205 class hgbuildpy2exe(buildpy2exe):
1207 def find_needed_modules(self, mf, files, modules):
1206 def find_needed_modules(self, mf, files, modules):
1208 res = buildpy2exe.find_needed_modules(self, mf, files, modules)
1207 res = buildpy2exe.find_needed_modules(self, mf, files, modules)
1209
1208
1210 # Replace virtualenv's distutils modules with the real ones.
1209 # Replace virtualenv's distutils modules with the real ones.
1211 modules = {}
1210 modules = {}
1212 for k, v in res.modules.items():
1211 for k, v in res.modules.items():
1213 if k != 'distutils' and not k.startswith('distutils.'):
1212 if k != 'distutils' and not k.startswith('distutils.'):
1214 modules[k] = v
1213 modules[k] = v
1215
1214
1216 res.modules = modules
1215 res.modules = modules
1217
1216
1218 import opcode
1217 import opcode
1219
1218
1220 distutilsreal = os.path.join(
1219 distutilsreal = os.path.join(
1221 os.path.dirname(opcode.__file__), 'distutils'
1220 os.path.dirname(opcode.__file__), 'distutils'
1222 )
1221 )
1223
1222
1224 for root, dirs, files in os.walk(distutilsreal):
1223 for root, dirs, files in os.walk(distutilsreal):
1225 for f in sorted(files):
1224 for f in sorted(files):
1226 if not f.endswith('.py'):
1225 if not f.endswith('.py'):
1227 continue
1226 continue
1228
1227
1229 full = os.path.join(root, f)
1228 full = os.path.join(root, f)
1230
1229
1231 parents = ['distutils']
1230 parents = ['distutils']
1232
1231
1233 if root != distutilsreal:
1232 if root != distutilsreal:
1234 rel = os.path.relpath(root, distutilsreal)
1233 rel = os.path.relpath(root, distutilsreal)
1235 parents.extend(p for p in rel.split(os.sep))
1234 parents.extend(p for p in rel.split(os.sep))
1236
1235
1237 modname = '%s.%s' % ('.'.join(parents), f[:-3])
1236 modname = '%s.%s' % ('.'.join(parents), f[:-3])
1238
1237
1239 if modname.startswith('distutils.tests.'):
1238 if modname.startswith('distutils.tests.'):
1240 continue
1239 continue
1241
1240
1242 if modname.endswith('.__init__'):
1241 if modname.endswith('.__init__'):
1243 modname = modname[: -len('.__init__')]
1242 modname = modname[: -len('.__init__')]
1244 path = os.path.dirname(full)
1243 path = os.path.dirname(full)
1245 else:
1244 else:
1246 path = None
1245 path = None
1247
1246
1248 res.modules[modname] = py2exemodule(
1247 res.modules[modname] = py2exemodule(
1249 modname, full, path=path
1248 modname, full, path=path
1250 )
1249 )
1251
1250
1252 if 'distutils' not in res.modules:
1251 if 'distutils' not in res.modules:
1253 raise SystemExit('could not find distutils modules')
1252 raise SystemExit('could not find distutils modules')
1254
1253
1255 return res
1254 return res
1256
1255
1257
1256
1258 cmdclass = {
1257 cmdclass = {
1259 'build': hgbuild,
1258 'build': hgbuild,
1260 'build_doc': hgbuilddoc,
1259 'build_doc': hgbuilddoc,
1261 'build_mo': hgbuildmo,
1260 'build_mo': hgbuildmo,
1262 'build_ext': hgbuildext,
1261 'build_ext': hgbuildext,
1263 'build_py': hgbuildpy,
1262 'build_py': hgbuildpy,
1264 'build_scripts': hgbuildscripts,
1263 'build_scripts': hgbuildscripts,
1265 'build_hgextindex': buildhgextindex,
1264 'build_hgextindex': buildhgextindex,
1266 'install': hginstall,
1265 'install': hginstall,
1267 'install_lib': hginstalllib,
1266 'install_lib': hginstalllib,
1268 'install_scripts': hginstallscripts,
1267 'install_scripts': hginstallscripts,
1269 'build_hgexe': buildhgexe,
1268 'build_hgexe': buildhgexe,
1270 }
1269 }
1271
1270
1272 if py2exehacked:
1271 if py2exehacked:
1273 cmdclass['py2exe'] = hgbuildpy2exe
1272 cmdclass['py2exe'] = hgbuildpy2exe
1274
1273
1275 packages = [
1274 packages = [
1276 'mercurial',
1275 'mercurial',
1277 'mercurial.cext',
1276 'mercurial.cext',
1278 'mercurial.cffi',
1277 'mercurial.cffi',
1279 'mercurial.defaultrc',
1278 'mercurial.defaultrc',
1280 'mercurial.helptext',
1279 'mercurial.helptext',
1281 'mercurial.helptext.internals',
1280 'mercurial.helptext.internals',
1282 'mercurial.hgweb',
1281 'mercurial.hgweb',
1283 'mercurial.interfaces',
1282 'mercurial.interfaces',
1284 'mercurial.pure',
1283 'mercurial.pure',
1285 'mercurial.templates',
1284 'mercurial.templates',
1286 'mercurial.thirdparty',
1285 'mercurial.thirdparty',
1287 'mercurial.thirdparty.attr',
1286 'mercurial.thirdparty.attr',
1288 'mercurial.thirdparty.zope',
1287 'mercurial.thirdparty.zope',
1289 'mercurial.thirdparty.zope.interface',
1288 'mercurial.thirdparty.zope.interface',
1290 'mercurial.utils',
1289 'mercurial.utils',
1291 'mercurial.revlogutils',
1290 'mercurial.revlogutils',
1292 'mercurial.testing',
1291 'mercurial.testing',
1293 'hgext',
1292 'hgext',
1294 'hgext.convert',
1293 'hgext.convert',
1295 'hgext.fsmonitor',
1294 'hgext.fsmonitor',
1296 'hgext.fastannotate',
1295 'hgext.fastannotate',
1297 'hgext.fsmonitor.pywatchman',
1296 'hgext.fsmonitor.pywatchman',
1298 'hgext.git',
1297 'hgext.git',
1299 'hgext.highlight',
1298 'hgext.highlight',
1300 'hgext.hooklib',
1299 'hgext.hooklib',
1301 'hgext.infinitepush',
1300 'hgext.infinitepush',
1302 'hgext.largefiles',
1301 'hgext.largefiles',
1303 'hgext.lfs',
1302 'hgext.lfs',
1304 'hgext.narrow',
1303 'hgext.narrow',
1305 'hgext.remotefilelog',
1304 'hgext.remotefilelog',
1306 'hgext.zeroconf',
1305 'hgext.zeroconf',
1307 'hgext3rd',
1306 'hgext3rd',
1308 'hgdemandimport',
1307 'hgdemandimport',
1309 ]
1308 ]
1310
1309
1311 for name in os.listdir(os.path.join('mercurial', 'templates')):
1310 for name in os.listdir(os.path.join('mercurial', 'templates')):
1312 if name != '__pycache__' and os.path.isdir(
1311 if name != '__pycache__' and os.path.isdir(
1313 os.path.join('mercurial', 'templates', name)
1312 os.path.join('mercurial', 'templates', name)
1314 ):
1313 ):
1315 packages.append('mercurial.templates.%s' % name)
1314 packages.append('mercurial.templates.%s' % name)
1316
1315
1317 if sys.version_info[0] == 2:
1316 if sys.version_info[0] == 2:
1318 packages.extend(
1317 packages.extend(
1319 [
1318 [
1320 'mercurial.thirdparty.concurrent',
1319 'mercurial.thirdparty.concurrent',
1321 'mercurial.thirdparty.concurrent.futures',
1320 'mercurial.thirdparty.concurrent.futures',
1322 ]
1321 ]
1323 )
1322 )
1324
1323
1325 if 'HG_PY2EXE_EXTRA_INSTALL_PACKAGES' in os.environ:
1324 if 'HG_PY2EXE_EXTRA_INSTALL_PACKAGES' in os.environ:
1326 # py2exe can't cope with namespace packages very well, so we have to
1325 # py2exe can't cope with namespace packages very well, so we have to
1327 # install any hgext3rd.* extensions that we want in the final py2exe
1326 # install any hgext3rd.* extensions that we want in the final py2exe
1328 # image here. This is gross, but you gotta do what you gotta do.
1327 # image here. This is gross, but you gotta do what you gotta do.
1329 packages.extend(os.environ['HG_PY2EXE_EXTRA_INSTALL_PACKAGES'].split(' '))
1328 packages.extend(os.environ['HG_PY2EXE_EXTRA_INSTALL_PACKAGES'].split(' '))
1330
1329
1331 common_depends = [
1330 common_depends = [
1332 'mercurial/bitmanipulation.h',
1331 'mercurial/bitmanipulation.h',
1333 'mercurial/compat.h',
1332 'mercurial/compat.h',
1334 'mercurial/cext/util.h',
1333 'mercurial/cext/util.h',
1335 ]
1334 ]
1336 common_include_dirs = ['mercurial']
1335 common_include_dirs = ['mercurial']
1337
1336
1338 common_cflags = []
1337 common_cflags = []
1339
1338
1340 # MSVC 2008 still needs declarations at the top of the scope, but Python 3.9
1339 # MSVC 2008 still needs declarations at the top of the scope, but Python 3.9
1341 # makes declarations not at the top of a scope in the headers.
1340 # makes declarations not at the top of a scope in the headers.
1342 if os.name != 'nt' and sys.version_info[1] < 9:
1341 if os.name != 'nt' and sys.version_info[1] < 9:
1343 common_cflags = ['-Werror=declaration-after-statement']
1342 common_cflags = ['-Werror=declaration-after-statement']
1344
1343
1345 osutil_cflags = []
1344 osutil_cflags = []
1346 osutil_ldflags = []
1345 osutil_ldflags = []
1347
1346
1348 # platform specific macros
1347 # platform specific macros
1349 for plat, func in [('bsd', 'setproctitle')]:
1348 for plat, func in [('bsd', 'setproctitle')]:
1350 if re.search(plat, sys.platform) and hasfunction(new_compiler(), func):
1349 if re.search(plat, sys.platform) and hasfunction(new_compiler(), func):
1351 osutil_cflags.append('-DHAVE_%s' % func.upper())
1350 osutil_cflags.append('-DHAVE_%s' % func.upper())
1352
1351
1353 for plat, macro, code in [
1352 for plat, macro, code in [
1354 (
1353 (
1355 'bsd|darwin',
1354 'bsd|darwin',
1356 'BSD_STATFS',
1355 'BSD_STATFS',
1357 '''
1356 '''
1358 #include <sys/param.h>
1357 #include <sys/param.h>
1359 #include <sys/mount.h>
1358 #include <sys/mount.h>
1360 int main() { struct statfs s; return sizeof(s.f_fstypename); }
1359 int main() { struct statfs s; return sizeof(s.f_fstypename); }
1361 ''',
1360 ''',
1362 ),
1361 ),
1363 (
1362 (
1364 'linux',
1363 'linux',
1365 'LINUX_STATFS',
1364 'LINUX_STATFS',
1366 '''
1365 '''
1367 #include <linux/magic.h>
1366 #include <linux/magic.h>
1368 #include <sys/vfs.h>
1367 #include <sys/vfs.h>
1369 int main() { struct statfs s; return sizeof(s.f_type); }
1368 int main() { struct statfs s; return sizeof(s.f_type); }
1370 ''',
1369 ''',
1371 ),
1370 ),
1372 ]:
1371 ]:
1373 if re.search(plat, sys.platform) and cancompile(new_compiler(), code):
1372 if re.search(plat, sys.platform) and cancompile(new_compiler(), code):
1374 osutil_cflags.append('-DHAVE_%s' % macro)
1373 osutil_cflags.append('-DHAVE_%s' % macro)
1375
1374
1376 if sys.platform == 'darwin':
1375 if sys.platform == 'darwin':
1377 osutil_ldflags += ['-framework', 'ApplicationServices']
1376 osutil_ldflags += ['-framework', 'ApplicationServices']
1378
1377
1379 if sys.platform == 'sunos5':
1378 if sys.platform == 'sunos5':
1380 osutil_ldflags += ['-lsocket']
1379 osutil_ldflags += ['-lsocket']
1381
1380
1382 xdiff_srcs = [
1381 xdiff_srcs = [
1383 'mercurial/thirdparty/xdiff/xdiffi.c',
1382 'mercurial/thirdparty/xdiff/xdiffi.c',
1384 'mercurial/thirdparty/xdiff/xprepare.c',
1383 'mercurial/thirdparty/xdiff/xprepare.c',
1385 'mercurial/thirdparty/xdiff/xutils.c',
1384 'mercurial/thirdparty/xdiff/xutils.c',
1386 ]
1385 ]
1387
1386
1388 xdiff_headers = [
1387 xdiff_headers = [
1389 'mercurial/thirdparty/xdiff/xdiff.h',
1388 'mercurial/thirdparty/xdiff/xdiff.h',
1390 'mercurial/thirdparty/xdiff/xdiffi.h',
1389 'mercurial/thirdparty/xdiff/xdiffi.h',
1391 'mercurial/thirdparty/xdiff/xinclude.h',
1390 'mercurial/thirdparty/xdiff/xinclude.h',
1392 'mercurial/thirdparty/xdiff/xmacros.h',
1391 'mercurial/thirdparty/xdiff/xmacros.h',
1393 'mercurial/thirdparty/xdiff/xprepare.h',
1392 'mercurial/thirdparty/xdiff/xprepare.h',
1394 'mercurial/thirdparty/xdiff/xtypes.h',
1393 'mercurial/thirdparty/xdiff/xtypes.h',
1395 'mercurial/thirdparty/xdiff/xutils.h',
1394 'mercurial/thirdparty/xdiff/xutils.h',
1396 ]
1395 ]
1397
1396
1398
1397
1399 class RustCompilationError(CCompilerError):
1398 class RustCompilationError(CCompilerError):
1400 """Exception class for Rust compilation errors."""
1399 """Exception class for Rust compilation errors."""
1401
1400
1402
1401
1403 class RustExtension(Extension):
1402 class RustExtension(Extension):
1404 """Base classes for concrete Rust Extension classes.
1403 """Base classes for concrete Rust Extension classes.
1405 """
1404 """
1406
1405
1407 rusttargetdir = os.path.join('rust', 'target', 'release')
1406 rusttargetdir = os.path.join('rust', 'target', 'release')
1408
1407
1409 def __init__(
1408 def __init__(
1410 self, mpath, sources, rustlibname, subcrate, py3_features=None, **kw
1409 self, mpath, sources, rustlibname, subcrate, py3_features=None, **kw
1411 ):
1410 ):
1412 Extension.__init__(self, mpath, sources, **kw)
1411 Extension.__init__(self, mpath, sources, **kw)
1413 srcdir = self.rustsrcdir = os.path.join('rust', subcrate)
1412 srcdir = self.rustsrcdir = os.path.join('rust', subcrate)
1414 self.py3_features = py3_features
1413 self.py3_features = py3_features
1415
1414
1416 # adding Rust source and control files to depends so that the extension
1415 # adding Rust source and control files to depends so that the extension
1417 # gets rebuilt if they've changed
1416 # gets rebuilt if they've changed
1418 self.depends.append(os.path.join(srcdir, 'Cargo.toml'))
1417 self.depends.append(os.path.join(srcdir, 'Cargo.toml'))
1419 cargo_lock = os.path.join(srcdir, 'Cargo.lock')
1418 cargo_lock = os.path.join(srcdir, 'Cargo.lock')
1420 if os.path.exists(cargo_lock):
1419 if os.path.exists(cargo_lock):
1421 self.depends.append(cargo_lock)
1420 self.depends.append(cargo_lock)
1422 for dirpath, subdir, fnames in os.walk(os.path.join(srcdir, 'src')):
1421 for dirpath, subdir, fnames in os.walk(os.path.join(srcdir, 'src')):
1423 self.depends.extend(
1422 self.depends.extend(
1424 os.path.join(dirpath, fname)
1423 os.path.join(dirpath, fname)
1425 for fname in fnames
1424 for fname in fnames
1426 if os.path.splitext(fname)[1] == '.rs'
1425 if os.path.splitext(fname)[1] == '.rs'
1427 )
1426 )
1428
1427
1429 @staticmethod
1428 @staticmethod
1430 def rustdylibsuffix():
1429 def rustdylibsuffix():
1431 """Return the suffix for shared libraries produced by rustc.
1430 """Return the suffix for shared libraries produced by rustc.
1432
1431
1433 See also: https://doc.rust-lang.org/reference/linkage.html
1432 See also: https://doc.rust-lang.org/reference/linkage.html
1434 """
1433 """
1435 if sys.platform == 'darwin':
1434 if sys.platform == 'darwin':
1436 return '.dylib'
1435 return '.dylib'
1437 elif os.name == 'nt':
1436 elif os.name == 'nt':
1438 return '.dll'
1437 return '.dll'
1439 else:
1438 else:
1440 return '.so'
1439 return '.so'
1441
1440
1442 def rustbuild(self):
1441 def rustbuild(self):
1443 env = os.environ.copy()
1442 env = os.environ.copy()
1444 if 'HGTEST_RESTOREENV' in env:
1443 if 'HGTEST_RESTOREENV' in env:
1445 # Mercurial tests change HOME to a temporary directory,
1444 # Mercurial tests change HOME to a temporary directory,
1446 # but, if installed with rustup, the Rust toolchain needs
1445 # but, if installed with rustup, the Rust toolchain needs
1447 # HOME to be correct (otherwise the 'no default toolchain'
1446 # HOME to be correct (otherwise the 'no default toolchain'
1448 # error message is issued and the build fails).
1447 # error message is issued and the build fails).
1449 # This happens currently with test-hghave.t, which does
1448 # This happens currently with test-hghave.t, which does
1450 # invoke this build.
1449 # invoke this build.
1451
1450
1452 # Unix only fix (os.path.expanduser not really reliable if
1451 # Unix only fix (os.path.expanduser not really reliable if
1453 # HOME is shadowed like this)
1452 # HOME is shadowed like this)
1454 import pwd
1453 import pwd
1455
1454
1456 env['HOME'] = pwd.getpwuid(os.getuid()).pw_dir
1455 env['HOME'] = pwd.getpwuid(os.getuid()).pw_dir
1457
1456
1458 cargocmd = ['cargo', 'rustc', '--release']
1457 cargocmd = ['cargo', 'rustc', '--release']
1459
1458
1460 feature_flags = []
1459 feature_flags = []
1461
1460
1462 if sys.version_info[0] == 3 and self.py3_features is not None:
1461 if sys.version_info[0] == 3 and self.py3_features is not None:
1463 feature_flags.append(self.py3_features)
1462 feature_flags.append(self.py3_features)
1464 cargocmd.append('--no-default-features')
1463 cargocmd.append('--no-default-features')
1465
1464
1466 rust_features = env.get("HG_RUST_FEATURES")
1465 rust_features = env.get("HG_RUST_FEATURES")
1467 if rust_features:
1466 if rust_features:
1468 feature_flags.append(rust_features)
1467 feature_flags.append(rust_features)
1469
1468
1470 cargocmd.extend(('--features', " ".join(feature_flags)))
1469 cargocmd.extend(('--features', " ".join(feature_flags)))
1471
1470
1472 cargocmd.append('--')
1471 cargocmd.append('--')
1473 if sys.platform == 'darwin':
1472 if sys.platform == 'darwin':
1474 cargocmd.extend(
1473 cargocmd.extend(
1475 ("-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup")
1474 ("-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup")
1476 )
1475 )
1477 try:
1476 try:
1478 subprocess.check_call(cargocmd, env=env, cwd=self.rustsrcdir)
1477 subprocess.check_call(cargocmd, env=env, cwd=self.rustsrcdir)
1479 except OSError as exc:
1478 except OSError as exc:
1480 if exc.errno == errno.ENOENT:
1479 if exc.errno == errno.ENOENT:
1481 raise RustCompilationError("Cargo not found")
1480 raise RustCompilationError("Cargo not found")
1482 elif exc.errno == errno.EACCES:
1481 elif exc.errno == errno.EACCES:
1483 raise RustCompilationError(
1482 raise RustCompilationError(
1484 "Cargo found, but permisssion to execute it is denied"
1483 "Cargo found, but permisssion to execute it is denied"
1485 )
1484 )
1486 else:
1485 else:
1487 raise
1486 raise
1488 except subprocess.CalledProcessError:
1487 except subprocess.CalledProcessError:
1489 raise RustCompilationError(
1488 raise RustCompilationError(
1490 "Cargo failed. Working directory: %r, "
1489 "Cargo failed. Working directory: %r, "
1491 "command: %r, environment: %r"
1490 "command: %r, environment: %r"
1492 % (self.rustsrcdir, cargocmd, env)
1491 % (self.rustsrcdir, cargocmd, env)
1493 )
1492 )
1494
1493
1495
1494
1496 class RustStandaloneExtension(RustExtension):
1495 class RustStandaloneExtension(RustExtension):
1497 def __init__(self, pydottedname, rustcrate, dylibname, **kw):
1496 def __init__(self, pydottedname, rustcrate, dylibname, **kw):
1498 RustExtension.__init__(
1497 RustExtension.__init__(
1499 self, pydottedname, [], dylibname, rustcrate, **kw
1498 self, pydottedname, [], dylibname, rustcrate, **kw
1500 )
1499 )
1501 self.dylibname = dylibname
1500 self.dylibname = dylibname
1502
1501
1503 def build(self, target_dir):
1502 def build(self, target_dir):
1504 self.rustbuild()
1503 self.rustbuild()
1505 target = [target_dir]
1504 target = [target_dir]
1506 target.extend(self.name.split('.'))
1505 target.extend(self.name.split('.'))
1507 target[-1] += DYLIB_SUFFIX
1506 target[-1] += DYLIB_SUFFIX
1508 shutil.copy2(
1507 shutil.copy2(
1509 os.path.join(
1508 os.path.join(
1510 self.rusttargetdir, self.dylibname + self.rustdylibsuffix()
1509 self.rusttargetdir, self.dylibname + self.rustdylibsuffix()
1511 ),
1510 ),
1512 os.path.join(*target),
1511 os.path.join(*target),
1513 )
1512 )
1514
1513
1515
1514
1516 extmodules = [
1515 extmodules = [
1517 Extension(
1516 Extension(
1518 'mercurial.cext.base85',
1517 'mercurial.cext.base85',
1519 ['mercurial/cext/base85.c'],
1518 ['mercurial/cext/base85.c'],
1520 include_dirs=common_include_dirs,
1519 include_dirs=common_include_dirs,
1521 extra_compile_args=common_cflags,
1520 extra_compile_args=common_cflags,
1522 depends=common_depends,
1521 depends=common_depends,
1523 ),
1522 ),
1524 Extension(
1523 Extension(
1525 'mercurial.cext.bdiff',
1524 'mercurial.cext.bdiff',
1526 ['mercurial/bdiff.c', 'mercurial/cext/bdiff.c'] + xdiff_srcs,
1525 ['mercurial/bdiff.c', 'mercurial/cext/bdiff.c'] + xdiff_srcs,
1527 include_dirs=common_include_dirs,
1526 include_dirs=common_include_dirs,
1528 extra_compile_args=common_cflags,
1527 extra_compile_args=common_cflags,
1529 depends=common_depends + ['mercurial/bdiff.h'] + xdiff_headers,
1528 depends=common_depends + ['mercurial/bdiff.h'] + xdiff_headers,
1530 ),
1529 ),
1531 Extension(
1530 Extension(
1532 'mercurial.cext.mpatch',
1531 'mercurial.cext.mpatch',
1533 ['mercurial/mpatch.c', 'mercurial/cext/mpatch.c'],
1532 ['mercurial/mpatch.c', 'mercurial/cext/mpatch.c'],
1534 include_dirs=common_include_dirs,
1533 include_dirs=common_include_dirs,
1535 extra_compile_args=common_cflags,
1534 extra_compile_args=common_cflags,
1536 depends=common_depends,
1535 depends=common_depends,
1537 ),
1536 ),
1538 Extension(
1537 Extension(
1539 'mercurial.cext.parsers',
1538 'mercurial.cext.parsers',
1540 [
1539 [
1541 'mercurial/cext/charencode.c',
1540 'mercurial/cext/charencode.c',
1542 'mercurial/cext/dirs.c',
1541 'mercurial/cext/dirs.c',
1543 'mercurial/cext/manifest.c',
1542 'mercurial/cext/manifest.c',
1544 'mercurial/cext/parsers.c',
1543 'mercurial/cext/parsers.c',
1545 'mercurial/cext/pathencode.c',
1544 'mercurial/cext/pathencode.c',
1546 'mercurial/cext/revlog.c',
1545 'mercurial/cext/revlog.c',
1547 ],
1546 ],
1548 include_dirs=common_include_dirs,
1547 include_dirs=common_include_dirs,
1549 extra_compile_args=common_cflags,
1548 extra_compile_args=common_cflags,
1550 depends=common_depends
1549 depends=common_depends
1551 + ['mercurial/cext/charencode.h', 'mercurial/cext/revlog.h',],
1550 + ['mercurial/cext/charencode.h', 'mercurial/cext/revlog.h',],
1552 ),
1551 ),
1553 Extension(
1552 Extension(
1554 'mercurial.cext.osutil',
1553 'mercurial.cext.osutil',
1555 ['mercurial/cext/osutil.c'],
1554 ['mercurial/cext/osutil.c'],
1556 include_dirs=common_include_dirs,
1555 include_dirs=common_include_dirs,
1557 extra_compile_args=common_cflags + osutil_cflags,
1556 extra_compile_args=common_cflags + osutil_cflags,
1558 extra_link_args=osutil_ldflags,
1557 extra_link_args=osutil_ldflags,
1559 depends=common_depends,
1558 depends=common_depends,
1560 ),
1559 ),
1561 Extension(
1560 Extension(
1562 'mercurial.thirdparty.zope.interface._zope_interface_coptimizations',
1561 'mercurial.thirdparty.zope.interface._zope_interface_coptimizations',
1563 [
1562 [
1564 'mercurial/thirdparty/zope/interface/_zope_interface_coptimizations.c',
1563 'mercurial/thirdparty/zope/interface/_zope_interface_coptimizations.c',
1565 ],
1564 ],
1566 extra_compile_args=common_cflags,
1565 extra_compile_args=common_cflags,
1567 ),
1566 ),
1568 Extension(
1567 Extension(
1569 'mercurial.thirdparty.sha1dc',
1568 'mercurial.thirdparty.sha1dc',
1570 [
1569 [
1571 'mercurial/thirdparty/sha1dc/cext.c',
1570 'mercurial/thirdparty/sha1dc/cext.c',
1572 'mercurial/thirdparty/sha1dc/lib/sha1.c',
1571 'mercurial/thirdparty/sha1dc/lib/sha1.c',
1573 'mercurial/thirdparty/sha1dc/lib/ubc_check.c',
1572 'mercurial/thirdparty/sha1dc/lib/ubc_check.c',
1574 ],
1573 ],
1575 extra_compile_args=common_cflags,
1574 extra_compile_args=common_cflags,
1576 ),
1575 ),
1577 Extension(
1576 Extension(
1578 'hgext.fsmonitor.pywatchman.bser',
1577 'hgext.fsmonitor.pywatchman.bser',
1579 ['hgext/fsmonitor/pywatchman/bser.c'],
1578 ['hgext/fsmonitor/pywatchman/bser.c'],
1580 extra_compile_args=common_cflags,
1579 extra_compile_args=common_cflags,
1581 ),
1580 ),
1582 RustStandaloneExtension(
1581 RustStandaloneExtension(
1583 'mercurial.rustext', 'hg-cpython', 'librusthg', py3_features='python3'
1582 'mercurial.rustext', 'hg-cpython', 'librusthg', py3_features='python3'
1584 ),
1583 ),
1585 ]
1584 ]
1586
1585
1587
1586
1588 sys.path.insert(0, 'contrib/python-zstandard')
1587 sys.path.insert(0, 'contrib/python-zstandard')
1589 import setup_zstd
1588 import setup_zstd
1590
1589
1591 zstd = setup_zstd.get_c_extension(
1590 zstd = setup_zstd.get_c_extension(
1592 name='mercurial.zstd', root=os.path.abspath(os.path.dirname(__file__))
1591 name='mercurial.zstd', root=os.path.abspath(os.path.dirname(__file__))
1593 )
1592 )
1594 zstd.extra_compile_args += common_cflags
1593 zstd.extra_compile_args += common_cflags
1595 extmodules.append(zstd)
1594 extmodules.append(zstd)
1596
1595
1597 try:
1596 try:
1598 from distutils import cygwinccompiler
1597 from distutils import cygwinccompiler
1599
1598
1600 # the -mno-cygwin option has been deprecated for years
1599 # the -mno-cygwin option has been deprecated for years
1601 mingw32compilerclass = cygwinccompiler.Mingw32CCompiler
1600 mingw32compilerclass = cygwinccompiler.Mingw32CCompiler
1602
1601
1603 class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
1602 class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
1604 def __init__(self, *args, **kwargs):
1603 def __init__(self, *args, **kwargs):
1605 mingw32compilerclass.__init__(self, *args, **kwargs)
1604 mingw32compilerclass.__init__(self, *args, **kwargs)
1606 for i in 'compiler compiler_so linker_exe linker_so'.split():
1605 for i in 'compiler compiler_so linker_exe linker_so'.split():
1607 try:
1606 try:
1608 getattr(self, i).remove('-mno-cygwin')
1607 getattr(self, i).remove('-mno-cygwin')
1609 except ValueError:
1608 except ValueError:
1610 pass
1609 pass
1611
1610
1612 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
1611 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
1613 except ImportError:
1612 except ImportError:
1614 # the cygwinccompiler package is not available on some Python
1613 # the cygwinccompiler package is not available on some Python
1615 # distributions like the ones from the optware project for Synology
1614 # distributions like the ones from the optware project for Synology
1616 # DiskStation boxes
1615 # DiskStation boxes
1617 class HackedMingw32CCompiler(object):
1616 class HackedMingw32CCompiler(object):
1618 pass
1617 pass
1619
1618
1620
1619
1621 if os.name == 'nt':
1620 if os.name == 'nt':
1622 # Allow compiler/linker flags to be added to Visual Studio builds. Passing
1621 # Allow compiler/linker flags to be added to Visual Studio builds. Passing
1623 # extra_link_args to distutils.extensions.Extension() doesn't have any
1622 # extra_link_args to distutils.extensions.Extension() doesn't have any
1624 # effect.
1623 # effect.
1625 from distutils import msvccompiler
1624 from distutils import msvccompiler
1626
1625
1627 msvccompilerclass = msvccompiler.MSVCCompiler
1626 msvccompilerclass = msvccompiler.MSVCCompiler
1628
1627
1629 class HackedMSVCCompiler(msvccompiler.MSVCCompiler):
1628 class HackedMSVCCompiler(msvccompiler.MSVCCompiler):
1630 def initialize(self):
1629 def initialize(self):
1631 msvccompilerclass.initialize(self)
1630 msvccompilerclass.initialize(self)
1632 # "warning LNK4197: export 'func' specified multiple times"
1631 # "warning LNK4197: export 'func' specified multiple times"
1633 self.ldflags_shared.append('/ignore:4197')
1632 self.ldflags_shared.append('/ignore:4197')
1634 self.ldflags_shared_debug.append('/ignore:4197')
1633 self.ldflags_shared_debug.append('/ignore:4197')
1635
1634
1636 msvccompiler.MSVCCompiler = HackedMSVCCompiler
1635 msvccompiler.MSVCCompiler = HackedMSVCCompiler
1637
1636
1638 packagedata = {
1637 packagedata = {
1639 'mercurial': ['locale/*/LC_MESSAGES/hg.mo', 'dummycert.pem',],
1638 'mercurial': ['locale/*/LC_MESSAGES/hg.mo', 'dummycert.pem',],
1640 'mercurial.defaultrc': ['*.rc',],
1639 'mercurial.defaultrc': ['*.rc',],
1641 'mercurial.helptext': ['*.txt',],
1640 'mercurial.helptext': ['*.txt',],
1642 'mercurial.helptext.internals': ['*.txt',],
1641 'mercurial.helptext.internals': ['*.txt',],
1643 }
1642 }
1644
1643
1645
1644
1646 def ordinarypath(p):
1645 def ordinarypath(p):
1647 return p and p[0] != '.' and p[-1] != '~'
1646 return p and p[0] != '.' and p[-1] != '~'
1648
1647
1649
1648
1650 for root in ('templates',):
1649 for root in ('templates',):
1651 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
1650 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
1652 packagename = curdir.replace(os.sep, '.')
1651 packagename = curdir.replace(os.sep, '.')
1653 packagedata[packagename] = list(filter(ordinarypath, files))
1652 packagedata[packagename] = list(filter(ordinarypath, files))
1654
1653
1655 datafiles = []
1654 datafiles = []
1656
1655
1657 # distutils expects version to be str/unicode. Converting it to
1656 # distutils expects version to be str/unicode. Converting it to
1658 # unicode on Python 2 still works because it won't contain any
1657 # unicode on Python 2 still works because it won't contain any
1659 # non-ascii bytes and will be implicitly converted back to bytes
1658 # non-ascii bytes and will be implicitly converted back to bytes
1660 # when operated on.
1659 # when operated on.
1661 assert isinstance(version, bytes)
1660 assert isinstance(version, bytes)
1662 setupversion = version.decode('ascii')
1661 setupversion = version.decode('ascii')
1663
1662
1664 extra = {}
1663 extra = {}
1665
1664
1666 py2exepackages = [
1665 py2exepackages = [
1667 'hgdemandimport',
1666 'hgdemandimport',
1668 'hgext3rd',
1667 'hgext3rd',
1669 'hgext',
1668 'hgext',
1670 'email',
1669 'email',
1671 # implicitly imported per module policy
1670 # implicitly imported per module policy
1672 # (cffi wouldn't be used as a frozen exe)
1671 # (cffi wouldn't be used as a frozen exe)
1673 'mercurial.cext',
1672 'mercurial.cext',
1674 #'mercurial.cffi',
1673 #'mercurial.cffi',
1675 'mercurial.pure',
1674 'mercurial.pure',
1676 ]
1675 ]
1677
1676
1678 py2exeexcludes = []
1677 py2exeexcludes = []
1679 py2exedllexcludes = ['crypt32.dll']
1678 py2exedllexcludes = ['crypt32.dll']
1680
1679
1681 if issetuptools:
1680 if issetuptools:
1682 extra['python_requires'] = supportedpy
1681 extra['python_requires'] = supportedpy
1683
1682
1684 if py2exeloaded:
1683 if py2exeloaded:
1685 extra['console'] = [
1684 extra['console'] = [
1686 {
1685 {
1687 'script': 'hg',
1686 'script': 'hg',
1688 'copyright': 'Copyright (C) 2005-2020 Matt Mackall and others',
1687 'copyright': 'Copyright (C) 2005-2020 Matt Mackall and others',
1689 'product_version': version,
1688 'product_version': version,
1690 }
1689 }
1691 ]
1690 ]
1692 # Sub command of 'build' because 'py2exe' does not handle sub_commands.
1691 # Sub command of 'build' because 'py2exe' does not handle sub_commands.
1693 # Need to override hgbuild because it has a private copy of
1692 # Need to override hgbuild because it has a private copy of
1694 # build.sub_commands.
1693 # build.sub_commands.
1695 hgbuild.sub_commands.insert(0, ('build_hgextindex', None))
1694 hgbuild.sub_commands.insert(0, ('build_hgextindex', None))
1696 # put dlls in sub directory so that they won't pollute PATH
1695 # put dlls in sub directory so that they won't pollute PATH
1697 extra['zipfile'] = 'lib/library.zip'
1696 extra['zipfile'] = 'lib/library.zip'
1698
1697
1699 # We allow some configuration to be supplemented via environment
1698 # We allow some configuration to be supplemented via environment
1700 # variables. This is better than setup.cfg files because it allows
1699 # variables. This is better than setup.cfg files because it allows
1701 # supplementing configs instead of replacing them.
1700 # supplementing configs instead of replacing them.
1702 extrapackages = os.environ.get('HG_PY2EXE_EXTRA_PACKAGES')
1701 extrapackages = os.environ.get('HG_PY2EXE_EXTRA_PACKAGES')
1703 if extrapackages:
1702 if extrapackages:
1704 py2exepackages.extend(extrapackages.split(' '))
1703 py2exepackages.extend(extrapackages.split(' '))
1705
1704
1706 excludes = os.environ.get('HG_PY2EXE_EXTRA_EXCLUDES')
1705 excludes = os.environ.get('HG_PY2EXE_EXTRA_EXCLUDES')
1707 if excludes:
1706 if excludes:
1708 py2exeexcludes.extend(excludes.split(' '))
1707 py2exeexcludes.extend(excludes.split(' '))
1709
1708
1710 dllexcludes = os.environ.get('HG_PY2EXE_EXTRA_DLL_EXCLUDES')
1709 dllexcludes = os.environ.get('HG_PY2EXE_EXTRA_DLL_EXCLUDES')
1711 if dllexcludes:
1710 if dllexcludes:
1712 py2exedllexcludes.extend(dllexcludes.split(' '))
1711 py2exedllexcludes.extend(dllexcludes.split(' '))
1713
1712
1714 if os.environ.get('PYOXIDIZER'):
1713 if os.environ.get('PYOXIDIZER'):
1715 hgbuild.sub_commands.insert(0, ('build_hgextindex', None))
1714 hgbuild.sub_commands.insert(0, ('build_hgextindex', None))
1716
1715
1717 if os.name == 'nt':
1716 if os.name == 'nt':
1718 # Windows binary file versions for exe/dll files must have the
1717 # Windows binary file versions for exe/dll files must have the
1719 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
1718 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
1720 setupversion = setupversion.split(r'+', 1)[0]
1719 setupversion = setupversion.split(r'+', 1)[0]
1721
1720
1722 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
1721 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
1723 version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[1].splitlines()
1722 version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[1].splitlines()
1724 if version:
1723 if version:
1725 version = version[0]
1724 version = version[0]
1726 if sys.version_info[0] == 3:
1725 if sys.version_info[0] == 3:
1727 version = version.decode('utf-8')
1726 version = version.decode('utf-8')
1728 xcode4 = version.startswith('Xcode') and StrictVersion(
1727 xcode4 = version.startswith('Xcode') and StrictVersion(
1729 version.split()[1]
1728 version.split()[1]
1730 ) >= StrictVersion('4.0')
1729 ) >= StrictVersion('4.0')
1731 xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None
1730 xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None
1732 else:
1731 else:
1733 # xcodebuild returns empty on OS X Lion with XCode 4.3 not
1732 # xcodebuild returns empty on OS X Lion with XCode 4.3 not
1734 # installed, but instead with only command-line tools. Assume
1733 # installed, but instead with only command-line tools. Assume
1735 # that only happens on >= Lion, thus no PPC support.
1734 # that only happens on >= Lion, thus no PPC support.
1736 xcode4 = True
1735 xcode4 = True
1737 xcode51 = False
1736 xcode51 = False
1738
1737
1739 # XCode 4.0 dropped support for ppc architecture, which is hardcoded in
1738 # XCode 4.0 dropped support for ppc architecture, which is hardcoded in
1740 # distutils.sysconfig
1739 # distutils.sysconfig
1741 if xcode4:
1740 if xcode4:
1742 os.environ['ARCHFLAGS'] = ''
1741 os.environ['ARCHFLAGS'] = ''
1743
1742
1744 # XCode 5.1 changes clang such that it now fails to compile if the
1743 # XCode 5.1 changes clang such that it now fails to compile if the
1745 # -mno-fused-madd flag is passed, but the version of Python shipped with
1744 # -mno-fused-madd flag is passed, but the version of Python shipped with
1746 # OS X 10.9 Mavericks includes this flag. This causes problems in all
1745 # OS X 10.9 Mavericks includes this flag. This causes problems in all
1747 # C extension modules, and a bug has been filed upstream at
1746 # C extension modules, and a bug has been filed upstream at
1748 # http://bugs.python.org/issue21244. We also need to patch this here
1747 # http://bugs.python.org/issue21244. We also need to patch this here
1749 # so Mercurial can continue to compile in the meantime.
1748 # so Mercurial can continue to compile in the meantime.
1750 if xcode51:
1749 if xcode51:
1751 cflags = get_config_var('CFLAGS')
1750 cflags = get_config_var('CFLAGS')
1752 if cflags and re.search(r'-mno-fused-madd\b', cflags) is not None:
1751 if cflags and re.search(r'-mno-fused-madd\b', cflags) is not None:
1753 os.environ['CFLAGS'] = (
1752 os.environ['CFLAGS'] = (
1754 os.environ.get('CFLAGS', '') + ' -Qunused-arguments'
1753 os.environ.get('CFLAGS', '') + ' -Qunused-arguments'
1755 )
1754 )
1756
1755
1757 setup(
1756 setup(
1758 name='mercurial',
1757 name='mercurial',
1759 version=setupversion,
1758 version=setupversion,
1760 author='Matt Mackall and many others',
1759 author='Matt Mackall and many others',
1761 author_email='mercurial@mercurial-scm.org',
1760 author_email='mercurial@mercurial-scm.org',
1762 url='https://mercurial-scm.org/',
1761 url='https://mercurial-scm.org/',
1763 download_url='https://mercurial-scm.org/release/',
1762 download_url='https://mercurial-scm.org/release/',
1764 description=(
1763 description=(
1765 'Fast scalable distributed SCM (revision control, version '
1764 'Fast scalable distributed SCM (revision control, version '
1766 'control) system'
1765 'control) system'
1767 ),
1766 ),
1768 long_description=(
1767 long_description=(
1769 'Mercurial is a distributed SCM tool written in Python.'
1768 'Mercurial is a distributed SCM tool written in Python.'
1770 ' It is used by a number of large projects that require'
1769 ' It is used by a number of large projects that require'
1771 ' fast, reliable distributed revision control, such as '
1770 ' fast, reliable distributed revision control, such as '
1772 'Mozilla.'
1771 'Mozilla.'
1773 ),
1772 ),
1774 license='GNU GPLv2 or any later version',
1773 license='GNU GPLv2 or any later version',
1775 classifiers=[
1774 classifiers=[
1776 'Development Status :: 6 - Mature',
1775 'Development Status :: 6 - Mature',
1777 'Environment :: Console',
1776 'Environment :: Console',
1778 'Intended Audience :: Developers',
1777 'Intended Audience :: Developers',
1779 'Intended Audience :: System Administrators',
1778 'Intended Audience :: System Administrators',
1780 'License :: OSI Approved :: GNU General Public License (GPL)',
1779 'License :: OSI Approved :: GNU General Public License (GPL)',
1781 'Natural Language :: Danish',
1780 'Natural Language :: Danish',
1782 'Natural Language :: English',
1781 'Natural Language :: English',
1783 'Natural Language :: German',
1782 'Natural Language :: German',
1784 'Natural Language :: Italian',
1783 'Natural Language :: Italian',
1785 'Natural Language :: Japanese',
1784 'Natural Language :: Japanese',
1786 'Natural Language :: Portuguese (Brazilian)',
1785 'Natural Language :: Portuguese (Brazilian)',
1787 'Operating System :: Microsoft :: Windows',
1786 'Operating System :: Microsoft :: Windows',
1788 'Operating System :: OS Independent',
1787 'Operating System :: OS Independent',
1789 'Operating System :: POSIX',
1788 'Operating System :: POSIX',
1790 'Programming Language :: C',
1789 'Programming Language :: C',
1791 'Programming Language :: Python',
1790 'Programming Language :: Python',
1792 'Topic :: Software Development :: Version Control',
1791 'Topic :: Software Development :: Version Control',
1793 ],
1792 ],
1794 scripts=scripts,
1793 scripts=scripts,
1795 packages=packages,
1794 packages=packages,
1796 ext_modules=extmodules,
1795 ext_modules=extmodules,
1797 data_files=datafiles,
1796 data_files=datafiles,
1798 package_data=packagedata,
1797 package_data=packagedata,
1799 cmdclass=cmdclass,
1798 cmdclass=cmdclass,
1800 distclass=hgdist,
1799 distclass=hgdist,
1801 options={
1800 options={
1802 'py2exe': {
1801 'py2exe': {
1803 'bundle_files': 3,
1802 'bundle_files': 3,
1804 'dll_excludes': py2exedllexcludes,
1803 'dll_excludes': py2exedllexcludes,
1805 'excludes': py2exeexcludes,
1804 'excludes': py2exeexcludes,
1806 'packages': py2exepackages,
1805 'packages': py2exepackages,
1807 },
1806 },
1808 'bdist_mpkg': {
1807 'bdist_mpkg': {
1809 'zipdist': False,
1808 'zipdist': False,
1810 'license': 'COPYING',
1809 'license': 'COPYING',
1811 'readme': 'contrib/packaging/macosx/Readme.html',
1810 'readme': 'contrib/packaging/macosx/Readme.html',
1812 'welcome': 'contrib/packaging/macosx/Welcome.html',
1811 'welcome': 'contrib/packaging/macosx/Welcome.html',
1813 },
1812 },
1814 },
1813 },
1815 **extra
1814 **extra
1816 )
1815 )
General Comments 0
You need to be logged in to leave comments. Login now