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