taskcluster-taskgraph 24.2.2__py3-none-any.whl → 24.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {taskcluster_taskgraph-24.2.2.dist-info → taskcluster_taskgraph-24.3.0.dist-info}/METADATA +2 -2
- {taskcluster_taskgraph-24.2.2.dist-info → taskcluster_taskgraph-24.3.0.dist-info}/RECORD +10 -10
- {taskcluster_taskgraph-24.2.2.dist-info → taskcluster_taskgraph-24.3.0.dist-info}/WHEEL +1 -1
- taskgraph/graph.py +18 -14
- taskgraph/run-task/fetch-content +66 -1
- taskgraph/run-task/robustcheckout.py +78 -6
- taskgraph/run-task/run-task +301 -24
- taskgraph/transforms/task.py +2 -1
- {taskcluster_taskgraph-24.2.2.dist-info → taskcluster_taskgraph-24.3.0.dist-info}/entry_points.txt +0 -0
- {taskcluster_taskgraph-24.2.2.dist-info → taskcluster_taskgraph-24.3.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: taskcluster-taskgraph
|
|
3
|
-
Version: 24.
|
|
3
|
+
Version: 24.3.0
|
|
4
4
|
Summary: Build taskcluster taskgraphs
|
|
5
5
|
Project-URL: Repository, https://github.com/taskcluster/taskgraph
|
|
6
6
|
Project-URL: Issues, https://github.com/taskcluster/taskgraph/issues
|
|
@@ -5,7 +5,7 @@ taskgraph/decision.py,sha256=V5TUWSeIlUbKcRFMBGhYd3t7wLmd2rwAgdx6jN4Yt1Q,11769
|
|
|
5
5
|
taskgraph/docker.py,sha256=5k9ZjooqvtBDtb3AAZijUdUNzgRcm6CXM5CrmLO0KFA,24429
|
|
6
6
|
taskgraph/filter_tasks.py,sha256=R7tYXiaVPGIkQ6O1c9-QJrKZ59m9pFXCloUlPraVnZU,866
|
|
7
7
|
taskgraph/generator.py,sha256=zanO2GOb0HWEGOQkEZGmXP0JySql71xoeENjk-cKOc8,23170
|
|
8
|
-
taskgraph/graph.py,sha256=
|
|
8
|
+
taskgraph/graph.py,sha256=Gnv4pC2sRn4rVPNegnXD1ni3eUb1ULju04zq-6bmH0A,6304
|
|
9
9
|
taskgraph/main.py,sha256=miuQkXuxD9eAUa0MqORARdyCRVtsqobLA-vW6n1sf_Y,39424
|
|
10
10
|
taskgraph/morph.py,sha256=pF9_bqM2aNpNfSH7xEFWQz83ohxxVdUUDqdv2yq7waA,9232
|
|
11
11
|
taskgraph/parameters.py,sha256=dlEC048z06WKdIL8QhH1nu2hy9jF4AQ4KcVfJauIXyk,13867
|
|
@@ -26,10 +26,10 @@ taskgraph/loader/transform.py,sha256=_PPHSLdnqa8MUlXqIZSkRsGAndQBVTZjNLAaIUVU5ps
|
|
|
26
26
|
taskgraph/optimize/__init__.py,sha256=Oqpq1RW8QzOcu7zaMlNQ3BHT9ws9e_93FWfCqzNcQps,123
|
|
27
27
|
taskgraph/optimize/base.py,sha256=c2ETEJChLCFm6yoA-IcWepu7V0eOIA_e_tQfFwld-EA,20449
|
|
28
28
|
taskgraph/optimize/strategies.py,sha256=SAzMXbp_oQ3EJ2pDLOvShh2RosJExCRTZR4PFsD-3l0,3776
|
|
29
|
-
taskgraph/run-task/fetch-content,sha256=
|
|
29
|
+
taskgraph/run-task/fetch-content,sha256=0QsAbgf8O65N8kvfFc2d0YjOKl-oqATXhRLBeXwRwTE,39216
|
|
30
30
|
taskgraph/run-task/hgrc,sha256=BybWLDR89bWi3pE5T05UqmDHs02CbLypE-omLZWU6Uk,896
|
|
31
|
-
taskgraph/run-task/robustcheckout.py,sha256=
|
|
32
|
-
taskgraph/run-task/run-task,sha256=
|
|
31
|
+
taskgraph/run-task/robustcheckout.py,sha256=85tcHqlAlWre1RYJ5qu3CJtqAAsMnB9AlDeN4zfpOrQ,33021
|
|
32
|
+
taskgraph/run-task/run-task,sha256=DRpXBcHXLmfLPwsz7S0IJwcWC0XFIDiE99Pu2yZCZrA,58070
|
|
33
33
|
taskgraph/transforms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
34
34
|
taskgraph/transforms/base.py,sha256=NULQNfwJfBTo0Bem1hp_6aDztUABwbnJrQ0MBpOgGDE,5125
|
|
35
35
|
taskgraph/transforms/cached_tasks.py,sha256=Izxfl4_UG56Hk0W2vIj5OK8hji7adG2zfvtELJNaE6Q,2592
|
|
@@ -40,7 +40,7 @@ taskgraph/transforms/fetch.py,sha256=Rf8FGMDY84sLNKp1mw7MVQspiJvses5-njWmIBIi0AQ
|
|
|
40
40
|
taskgraph/transforms/from_deps.py,sha256=hifdCsKY_kiBzUCxsOAfCR0VR525x-H0kWNutygTDCQ,8085
|
|
41
41
|
taskgraph/transforms/matrix.py,sha256=anoUjnPnpvguQgHkj2HiVdHs3hDCROxQ6Wb0dLJ6l_8,3069
|
|
42
42
|
taskgraph/transforms/notify.py,sha256=KTku8694xEicls5vhUC4Uvpg-aaSwDUsVizVlR5RObw,6360
|
|
43
|
-
taskgraph/transforms/task.py,sha256=
|
|
43
|
+
taskgraph/transforms/task.py,sha256=6OP9b-EWc4AZjfBF-Dh4h7nIyB5DLcwUaAMNAFt8h-g,43148
|
|
44
44
|
taskgraph/transforms/task_context.py,sha256=diijTlofODLCkgc0ZDR02_VojGXDW8m54lAvEkUIpBc,5144
|
|
45
45
|
taskgraph/transforms/run/__init__.py,sha256=s9GkP3rYtLUa8IbpfZQC-x5YiiQSQHjHVfgf8jaEOWg,18195
|
|
46
46
|
taskgraph/transforms/run/common.py,sha256=ANiOZJ0sSl1lL09Qo3aTESFd1gK6SvwLX8cTj_J2SmU,6861
|
|
@@ -75,8 +75,8 @@ taskgraph/util/vcs.py,sha256=Z2sCJT0-kwP_AbSUMIzya9lk8SyjWPzMYtQPYJ2WtnA,22496
|
|
|
75
75
|
taskgraph/util/verify.py,sha256=hY6xl4rEgXeVuPswG8HCGBHJNKHF2QXx2cTxrrRtDeE,14829
|
|
76
76
|
taskgraph/util/workertypes.py,sha256=SpqQmf99kB9QxtgOdXeMUvzuVv1o0ZVB4lYahZH3jDY,2534
|
|
77
77
|
taskgraph/util/yaml.py,sha256=29h6RE7JA4z0U2V3WCu-S39lPMBS9CEZEglv1delDvw,1075
|
|
78
|
-
taskcluster_taskgraph-24.
|
|
79
|
-
taskcluster_taskgraph-24.
|
|
80
|
-
taskcluster_taskgraph-24.
|
|
81
|
-
taskcluster_taskgraph-24.
|
|
82
|
-
taskcluster_taskgraph-24.
|
|
78
|
+
taskcluster_taskgraph-24.3.0.dist-info/METADATA,sha256=CNocuQbuMOJ1k24k2MTavl1rSVvRmBes4ny6OJyKYek,5065
|
|
79
|
+
taskcluster_taskgraph-24.3.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
80
|
+
taskcluster_taskgraph-24.3.0.dist-info/entry_points.txt,sha256=2hxDzE3qq_sHh-J3ROqwpxgQgxO-196phWAQREl2-XA,50
|
|
81
|
+
taskcluster_taskgraph-24.3.0.dist-info/licenses/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
|
|
82
|
+
taskcluster_taskgraph-24.3.0.dist-info/RECORD,,
|
taskgraph/graph.py
CHANGED
|
@@ -61,20 +61,24 @@ class Graph(_Graph):
|
|
|
61
61
|
f"Unknown nodes in transitive closure: {nodes - self.nodes}"
|
|
62
62
|
)
|
|
63
63
|
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
64
|
+
# Build an adjacency map keyed by the node to expand from. This reduces
|
|
65
|
+
# traversal below from O(V·E) -> O(V+E).
|
|
66
|
+
adjacency = collections.defaultdict(list)
|
|
67
|
+
for edge in self.edges:
|
|
68
|
+
left, right, _ = edge
|
|
69
|
+
adjacency[right if reverse else left].append(edge)
|
|
70
|
+
|
|
71
|
+
new_nodes = set(nodes)
|
|
72
|
+
new_edges = set()
|
|
73
|
+
queue = collections.deque(nodes)
|
|
74
|
+
while queue:
|
|
75
|
+
node = queue.popleft()
|
|
76
|
+
for edge in adjacency.get(node, ()):
|
|
77
|
+
new_edges.add(edge)
|
|
78
|
+
neighbor = edge[0] if reverse else edge[1]
|
|
79
|
+
if neighbor not in new_nodes:
|
|
80
|
+
new_nodes.add(neighbor)
|
|
81
|
+
queue.append(neighbor)
|
|
78
82
|
return Graph(new_nodes, new_edges)
|
|
79
83
|
|
|
80
84
|
def _visit(self, reverse):
|
taskgraph/run-task/fetch-content
CHANGED
|
@@ -18,6 +18,7 @@ import os
|
|
|
18
18
|
import pathlib
|
|
19
19
|
import random
|
|
20
20
|
import re
|
|
21
|
+
import shutil
|
|
21
22
|
import ssl
|
|
22
23
|
import stat
|
|
23
24
|
import subprocess
|
|
@@ -377,6 +378,70 @@ def archive_type(path: pathlib.Path):
|
|
|
377
378
|
return None
|
|
378
379
|
|
|
379
380
|
|
|
381
|
+
class TarFile(tarfile.TarFile):
|
|
382
|
+
# When a tarball contains symbolic links, the python tarfile module ends up
|
|
383
|
+
# trying to seek in the tar, which doesn't quite work when the tar is a
|
|
384
|
+
# stream (which it is when decompressing at the same time). We wrap TarFile
|
|
385
|
+
# so as to handle the symbolic link situation ourselves, and when we can't
|
|
386
|
+
# create symbolic links (e.g. on Windows), we instead create a copy of the
|
|
387
|
+
# file the link points to.
|
|
388
|
+
def _do_extract(
|
|
389
|
+
self, extract, member, path="", set_attrs=True, numeric_owner=False, **kwargs
|
|
390
|
+
):
|
|
391
|
+
deferred_links = getattr(self, "_deferred_links", None)
|
|
392
|
+
if not isinstance(member, tarfile.TarInfo):
|
|
393
|
+
member = self.getmember(member)
|
|
394
|
+
targetpath = os.path.normcase(os.path.join(path, member.name))
|
|
395
|
+
|
|
396
|
+
if deferred_links is not None and member.issym():
|
|
397
|
+
if os.path.lexists(targetpath):
|
|
398
|
+
# Avoid FileExistsError on following os.symlink.
|
|
399
|
+
os.unlink(targetpath)
|
|
400
|
+
try:
|
|
401
|
+
os.symlink(member.linkname, targetpath)
|
|
402
|
+
except (NotImplementedError, OSError):
|
|
403
|
+
# On Windows, os.symlink can fail, in this case fallback to
|
|
404
|
+
# creating a copy. If the destination was not already created,
|
|
405
|
+
# defer the link creation.
|
|
406
|
+
source = os.path.normcase(
|
|
407
|
+
os.path.join(os.path.dirname(targetpath), member.linkname)
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
if source in self._extracted_members:
|
|
411
|
+
shutil.copy(source, targetpath)
|
|
412
|
+
self.chown(member, targetpath, numeric_owner)
|
|
413
|
+
else:
|
|
414
|
+
deferred_links.setdefault(source, []).append(
|
|
415
|
+
(member, targetpath, numeric_owner)
|
|
416
|
+
)
|
|
417
|
+
return
|
|
418
|
+
|
|
419
|
+
extract(member, path, set_attrs, numeric_owner=numeric_owner, **kwargs)
|
|
420
|
+
if deferred_links is not None:
|
|
421
|
+
for tarinfo, linkpath, numeric_owner in deferred_links.pop(targetpath, []):
|
|
422
|
+
shutil.copy(targetpath, linkpath)
|
|
423
|
+
self.chown(tarinfo, linkpath, numeric_owner)
|
|
424
|
+
self._extracted_members.add(targetpath)
|
|
425
|
+
|
|
426
|
+
def extract(self, *args, **kwargs):
|
|
427
|
+
self._do_extract(super(TarFile, self).extract, *args, **kwargs)
|
|
428
|
+
|
|
429
|
+
# extractall in versions for cpython that implement PEP 706 call _extract_one
|
|
430
|
+
# instead of extract.
|
|
431
|
+
def _extract_one(self, *args, **kwargs):
|
|
432
|
+
self._do_extract(super(TarFile, self)._extract_one, *args, **kwargs)
|
|
433
|
+
|
|
434
|
+
def extractall(self, *args, **kwargs):
|
|
435
|
+
self._deferred_links = {}
|
|
436
|
+
self._extracted_members = set()
|
|
437
|
+
super(TarFile, self).extractall(*args, **kwargs)
|
|
438
|
+
for links in self._deferred_links.values():
|
|
439
|
+
for tarinfo, linkpath, numeric_owner in links:
|
|
440
|
+
log(f"Cannot create dangling symbolic link: {linkpath}")
|
|
441
|
+
delattr(self, "_deferred_links")
|
|
442
|
+
delattr(self, "_extracted_members")
|
|
443
|
+
|
|
444
|
+
|
|
380
445
|
def extract_archive(path, dest_dir):
|
|
381
446
|
"""Extract an archive to a destination directory."""
|
|
382
447
|
|
|
@@ -398,7 +463,7 @@ def extract_archive(path, dest_dir):
|
|
|
398
463
|
# also not much slower on Windows, presumably because of the
|
|
399
464
|
# notoriously bad I/O).
|
|
400
465
|
if sys.platform == "win32":
|
|
401
|
-
tar =
|
|
466
|
+
tar = TarFile.open(fileobj=ifh, mode="r|")
|
|
402
467
|
tar.extractall(str(dest_dir))
|
|
403
468
|
args = []
|
|
404
469
|
else:
|
|
@@ -18,6 +18,7 @@ import random
|
|
|
18
18
|
import re
|
|
19
19
|
import socket
|
|
20
20
|
import ssl
|
|
21
|
+
import stat
|
|
21
22
|
import time
|
|
22
23
|
|
|
23
24
|
from mercurial.i18n import _
|
|
@@ -38,6 +39,22 @@ from mercurial import (
|
|
|
38
39
|
vfs,
|
|
39
40
|
)
|
|
40
41
|
|
|
42
|
+
# TRACKING hg72 - `hg.repository`/`hg.peer` moved to `mercurial.repo.factory`.
|
|
43
|
+
try:
|
|
44
|
+
from mercurial.repo import factory as _repofactory
|
|
45
|
+
|
|
46
|
+
_repository = _repofactory.repository
|
|
47
|
+
_peer = _repofactory.peer
|
|
48
|
+
except ImportError:
|
|
49
|
+
_repository = hg.repository
|
|
50
|
+
_peer = hg.peer
|
|
51
|
+
|
|
52
|
+
# TRACKING hg72 - `hg.clone` moved to `mercurial.cmd_impls.clone.clone`.
|
|
53
|
+
try:
|
|
54
|
+
from mercurial.cmd_impls.clone import clone as _clone
|
|
55
|
+
except ImportError:
|
|
56
|
+
_clone = hg.clone
|
|
57
|
+
|
|
41
58
|
# Causes worker to purge caches on process exit and for task to retry.
|
|
42
59
|
EXIT_PURGE_CACHE = 72
|
|
43
60
|
|
|
@@ -67,6 +84,57 @@ def peerlookup(remote, v):
|
|
|
67
84
|
return e.callcommand(b"lookup", {b"key": v}).result()
|
|
68
85
|
|
|
69
86
|
|
|
87
|
+
def remove_dangling_links(ui, path):
|
|
88
|
+
"""On Windows, remove dangling symlinks and junctions under ``path``.
|
|
89
|
+
|
|
90
|
+
npm's ``file:`` protocol dependencies create directory junctions in
|
|
91
|
+
``node_modules/`` with absolute targets. When a Taskcluster cache
|
|
92
|
+
is restored to a different task directory those targets no longer
|
|
93
|
+
exist, and Mercurial's purge crashes in ``vfs.listdir()`` with
|
|
94
|
+
``FileNotFoundError``. Removing them first lets purge proceed.
|
|
95
|
+
"""
|
|
96
|
+
if os.name != "nt":
|
|
97
|
+
return
|
|
98
|
+
|
|
99
|
+
ui.write(b"windows detected, removing dangling links\n")
|
|
100
|
+
|
|
101
|
+
stack = [path]
|
|
102
|
+
while stack:
|
|
103
|
+
dirpath = stack.pop()
|
|
104
|
+
try:
|
|
105
|
+
entries = os.scandir(dirpath)
|
|
106
|
+
except OSError:
|
|
107
|
+
continue
|
|
108
|
+
|
|
109
|
+
with entries:
|
|
110
|
+
for entry in entries:
|
|
111
|
+
p = entry.path
|
|
112
|
+
try:
|
|
113
|
+
attrs = entry.stat(follow_symlinks=False).st_file_attributes
|
|
114
|
+
except OSError:
|
|
115
|
+
continue
|
|
116
|
+
|
|
117
|
+
if attrs & stat.FILE_ATTRIBUTE_REPARSE_POINT:
|
|
118
|
+
if not os.path.exists(p):
|
|
119
|
+
ui.write(
|
|
120
|
+
b"(removing dangling link %s)\n"
|
|
121
|
+
% os.fsencode(os.path.relpath(p, path))
|
|
122
|
+
)
|
|
123
|
+
try:
|
|
124
|
+
os.rmdir(p)
|
|
125
|
+
except OSError:
|
|
126
|
+
os.remove(p)
|
|
127
|
+
continue
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
is_dir = entry.is_dir(follow_symlinks=False)
|
|
131
|
+
except OSError:
|
|
132
|
+
continue
|
|
133
|
+
|
|
134
|
+
if is_dir:
|
|
135
|
+
stack.append(p)
|
|
136
|
+
|
|
137
|
+
|
|
70
138
|
@command(
|
|
71
139
|
b"robustcheckout",
|
|
72
140
|
[
|
|
@@ -427,7 +495,7 @@ def _docheckout(
|
|
|
427
495
|
def handlerepoerror(e):
|
|
428
496
|
if pycompat.bytestr(e) == _(b"abandoned transaction found"):
|
|
429
497
|
ui.warn(b"(abandoned transaction found; trying to recover)\n")
|
|
430
|
-
repo =
|
|
498
|
+
repo = _repository(ui, dest)
|
|
431
499
|
if not repo.recover():
|
|
432
500
|
ui.warn(b"(could not recover repo state; deleting shared store)\n")
|
|
433
501
|
with timeit("remove_unrecovered_shared_store", "remove-store"):
|
|
@@ -535,7 +603,7 @@ def _docheckout(
|
|
|
535
603
|
cloneurl = upstream or url
|
|
536
604
|
|
|
537
605
|
try:
|
|
538
|
-
clonepeer =
|
|
606
|
+
clonepeer = _peer(ui, {}, cloneurl)
|
|
539
607
|
rootnode = peerlookup(clonepeer, b"0")
|
|
540
608
|
except error.RepoLookupError:
|
|
541
609
|
raise error.Abort(b"unable to resolve root revision from clone source")
|
|
@@ -623,12 +691,12 @@ def _docheckout(
|
|
|
623
691
|
ui.write(b"(cloning from upstream repo %s)\n" % upstream)
|
|
624
692
|
|
|
625
693
|
if not storevfs.exists():
|
|
626
|
-
behaviors.add(
|
|
694
|
+
behaviors.add("create-store")
|
|
627
695
|
|
|
628
696
|
try:
|
|
629
697
|
with timeit("clone", "clone"):
|
|
630
698
|
shareopts = {b"pool": sharebase, b"mode": b"identity"}
|
|
631
|
-
res =
|
|
699
|
+
res = _clone(
|
|
632
700
|
ui,
|
|
633
701
|
{},
|
|
634
702
|
clonepeer,
|
|
@@ -667,7 +735,7 @@ def _docheckout(
|
|
|
667
735
|
# The destination .hg directory should exist. Now make sure we have the
|
|
668
736
|
# wanted revision.
|
|
669
737
|
|
|
670
|
-
repo =
|
|
738
|
+
repo = _repository(ui, dest)
|
|
671
739
|
|
|
672
740
|
# We only pull if we are using symbolic names or the requested revision
|
|
673
741
|
# doesn't exist.
|
|
@@ -694,7 +762,7 @@ def _docheckout(
|
|
|
694
762
|
|
|
695
763
|
remote = None
|
|
696
764
|
try:
|
|
697
|
-
remote =
|
|
765
|
+
remote = _peer(repo, {}, url)
|
|
698
766
|
pullrevs = [peerlookup(remote, revision or branch)]
|
|
699
767
|
checkoutrevision = hex(pullrevs[0])
|
|
700
768
|
if branch:
|
|
@@ -741,6 +809,10 @@ def _docheckout(
|
|
|
741
809
|
# guaranteed to not have conflicts on `hg update`.
|
|
742
810
|
if purge and not created:
|
|
743
811
|
ui.write(b"(purging working directory)\n")
|
|
812
|
+
|
|
813
|
+
with timeit("purge", "dangling_link_remove"):
|
|
814
|
+
remove_dangling_links(ui, dest)
|
|
815
|
+
|
|
744
816
|
purge = getattr(commands, "purge", None)
|
|
745
817
|
if not purge:
|
|
746
818
|
purge = extensions.find(b"purge").purge
|
taskgraph/run-task/run-task
CHANGED
|
@@ -27,12 +27,13 @@ import socket
|
|
|
27
27
|
import stat
|
|
28
28
|
import subprocess
|
|
29
29
|
import sys
|
|
30
|
+
import tempfile
|
|
30
31
|
import threading
|
|
31
32
|
import time
|
|
32
33
|
import urllib.error
|
|
33
34
|
import urllib.request
|
|
34
35
|
from pathlib import Path
|
|
35
|
-
from typing import Dict, Optional
|
|
36
|
+
from typing import Dict, List, Literal, NamedTuple, Optional
|
|
36
37
|
|
|
37
38
|
SECRET_BASEURL_TPL = "{}/secrets/v1/secret/{{}}".format(
|
|
38
39
|
os.environ.get("TASKCLUSTER_PROXY_URL", "http://taskcluster").rstrip("/")
|
|
@@ -234,8 +235,8 @@ def remove(path):
|
|
|
234
235
|
_call_windows_retry(shutil.rmtree, (path,))
|
|
235
236
|
|
|
236
237
|
|
|
237
|
-
def run_required_command(prefix, args, *, extra_env=None, cwd=None):
|
|
238
|
-
res = run_command(prefix, args, extra_env=extra_env, cwd=cwd)
|
|
238
|
+
def run_required_command(prefix, args, *, extra_env=None, cwd=None, stdin_data=None):
|
|
239
|
+
res = run_command(prefix, args, extra_env=extra_env, cwd=cwd, stdin_data=stdin_data)
|
|
239
240
|
if res:
|
|
240
241
|
sys.exit(res)
|
|
241
242
|
|
|
@@ -253,7 +254,7 @@ def retry_required_command(prefix, args, *, extra_env=None, cwd=None, retries=2)
|
|
|
253
254
|
time.sleep(backoff)
|
|
254
255
|
|
|
255
256
|
|
|
256
|
-
def run_command(prefix, args, *, extra_env=None, cwd=None):
|
|
257
|
+
def run_command(prefix, args, *, extra_env=None, cwd=None, stdin_data=None):
|
|
257
258
|
"""Runs a process and prefixes its output with the time.
|
|
258
259
|
|
|
259
260
|
Returns the process exit code.
|
|
@@ -274,6 +275,12 @@ def run_command(prefix, args, *, extra_env=None, cwd=None):
|
|
|
274
275
|
# we manually install a latin1 stream wrapper. This allows us to readline()
|
|
275
276
|
# and preserves bytes, without losing any data.
|
|
276
277
|
|
|
278
|
+
stdin_file = None
|
|
279
|
+
if stdin_data is not None:
|
|
280
|
+
stdin_file = tempfile.TemporaryFile()
|
|
281
|
+
stdin_file.write(stdin_data)
|
|
282
|
+
stdin_file.seek(0)
|
|
283
|
+
|
|
277
284
|
p = subprocess.Popen(
|
|
278
285
|
args,
|
|
279
286
|
# Disable buffering because we want to receive output
|
|
@@ -282,11 +289,14 @@ def run_command(prefix, args, *, extra_env=None, cwd=None):
|
|
|
282
289
|
bufsize=0,
|
|
283
290
|
stdout=subprocess.PIPE,
|
|
284
291
|
stderr=subprocess.STDOUT,
|
|
285
|
-
stdin=sys.stdin.fileno(),
|
|
292
|
+
stdin=stdin_file if stdin_file is not None else sys.stdin.fileno(),
|
|
286
293
|
cwd=cwd,
|
|
287
294
|
env=env,
|
|
288
295
|
)
|
|
289
296
|
|
|
297
|
+
if stdin_file is not None:
|
|
298
|
+
stdin_file.close()
|
|
299
|
+
|
|
290
300
|
stdout = io.TextIOWrapper(p.stdout, encoding="latin1")
|
|
291
301
|
|
|
292
302
|
if os.getpid() == 1:
|
|
@@ -571,8 +581,9 @@ def configure_volume_posix(volume, user, group, running_as_root):
|
|
|
571
581
|
class PerfRecorder:
|
|
572
582
|
"""Collects operation timings and emits them as PERFHERDER_DATA."""
|
|
573
583
|
|
|
574
|
-
def __init__(self, framework: str):
|
|
584
|
+
def __init__(self, framework: str, extra_options: Optional[List[str]] = None):
|
|
575
585
|
self.framework = framework
|
|
586
|
+
self.extra_options = list(extra_options or [])
|
|
576
587
|
self.optimes = []
|
|
577
588
|
self._stack = []
|
|
578
589
|
|
|
@@ -610,7 +621,7 @@ class PerfRecorder:
|
|
|
610
621
|
"value": duration,
|
|
611
622
|
"lowerIsBetter": True,
|
|
612
623
|
"shouldAlert": False,
|
|
613
|
-
"extraOptions": [instance_type],
|
|
624
|
+
"extraOptions": [instance_type, *self.extra_options],
|
|
614
625
|
"subtests": [],
|
|
615
626
|
}
|
|
616
627
|
)
|
|
@@ -673,6 +684,176 @@ def _clean_git_checkout(destination_path):
|
|
|
673
684
|
print_line(b"vcs", b"successfully cleaned git checkout!\n")
|
|
674
685
|
|
|
675
686
|
|
|
687
|
+
def _git_config(repo_path: str, key: str) -> Optional[str]:
|
|
688
|
+
p = subprocess.run(
|
|
689
|
+
["git", "config", "--get", key],
|
|
690
|
+
stdout=subprocess.PIPE,
|
|
691
|
+
stderr=subprocess.PIPE,
|
|
692
|
+
cwd=repo_path,
|
|
693
|
+
env=os.environ,
|
|
694
|
+
encoding="utf-8",
|
|
695
|
+
)
|
|
696
|
+
if p.returncode == 1:
|
|
697
|
+
return None
|
|
698
|
+
if p.returncode != 0:
|
|
699
|
+
raise RuntimeError(f"git config --get {key} failed: {p.stderr.strip()}")
|
|
700
|
+
return p.stdout.strip()
|
|
701
|
+
|
|
702
|
+
|
|
703
|
+
def _git_lines(repo_path: str, *args: str) -> List[str]:
|
|
704
|
+
return subprocess.check_output(
|
|
705
|
+
["git", *args], cwd=repo_path, env=os.environ, encoding="utf-8"
|
|
706
|
+
).splitlines()
|
|
707
|
+
|
|
708
|
+
|
|
709
|
+
_PATTERN_METACHARS_RE = re.compile(r"([*?\[\\])")
|
|
710
|
+
|
|
711
|
+
|
|
712
|
+
def _escape_pattern(path: str) -> str:
|
|
713
|
+
escaped = _PATTERN_METACHARS_RE.sub(r"\\\1", path)
|
|
714
|
+
if escaped.endswith(" "):
|
|
715
|
+
escaped = escaped[:-1] + "\\ "
|
|
716
|
+
return escaped
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
def _cone_ancestor_files(
|
|
720
|
+
repo_path: str, rev: str, directories: List[str]
|
|
721
|
+
) -> List[str]:
|
|
722
|
+
patterns = []
|
|
723
|
+
seen = set()
|
|
724
|
+
for directory in directories:
|
|
725
|
+
parts = directory.split("/")
|
|
726
|
+
for depth in range(len(parts)):
|
|
727
|
+
ancestor = "/".join(parts[:depth])
|
|
728
|
+
if ancestor in seen:
|
|
729
|
+
continue
|
|
730
|
+
seen.add(ancestor)
|
|
731
|
+
args = ["ls-tree", "-z", rev]
|
|
732
|
+
if ancestor:
|
|
733
|
+
args.append(f"{ancestor}/")
|
|
734
|
+
out = subprocess.check_output(
|
|
735
|
+
["git", *args], cwd=repo_path, env=os.environ, encoding="utf-8"
|
|
736
|
+
)
|
|
737
|
+
for entry in out.split("\0"):
|
|
738
|
+
if not entry:
|
|
739
|
+
continue
|
|
740
|
+
meta, path = entry.split("\t", 1)
|
|
741
|
+
if meta.split(" ")[1] == "blob" and "\n" not in path and "\r" not in path:
|
|
742
|
+
patterns.append(f"/{_escape_pattern(path)}")
|
|
743
|
+
return patterns
|
|
744
|
+
|
|
745
|
+
|
|
746
|
+
def _cone_directories(patterns: List[str]) -> Optional[List[str]]:
|
|
747
|
+
directories = []
|
|
748
|
+
for pattern in patterns:
|
|
749
|
+
body = pattern[1:].rstrip("/")
|
|
750
|
+
if not pattern.startswith("/") or not body or _PATTERN_METACHARS_RE.search(body):
|
|
751
|
+
return None
|
|
752
|
+
directories.append(body)
|
|
753
|
+
return directories
|
|
754
|
+
|
|
755
|
+
|
|
756
|
+
class SparsePlan(NamedTuple):
|
|
757
|
+
"""The one `git sparse-checkout` command a checkout needs, if any."""
|
|
758
|
+
|
|
759
|
+
cone: bool
|
|
760
|
+
op: Optional[Literal["sparse_set", "sparse_add", "sparse_convert", "sparse_disable"]]
|
|
761
|
+
args: List[str]
|
|
762
|
+
entries: List[str]
|
|
763
|
+
message: str
|
|
764
|
+
|
|
765
|
+
|
|
766
|
+
def _plan_sparse_checkout(
|
|
767
|
+
repo_path: str,
|
|
768
|
+
has_repo: bool,
|
|
769
|
+
is_sparse: bool,
|
|
770
|
+
sparse_profile: Optional[str],
|
|
771
|
+
sparse_patterns: Optional[List[str]],
|
|
772
|
+
rev: str,
|
|
773
|
+
) -> SparsePlan:
|
|
774
|
+
profile = sparse_profile or ""
|
|
775
|
+
if not sparse_patterns:
|
|
776
|
+
if is_sparse:
|
|
777
|
+
return SparsePlan(
|
|
778
|
+
False,
|
|
779
|
+
"sparse_disable",
|
|
780
|
+
["git", "sparse-checkout", "disable"],
|
|
781
|
+
[],
|
|
782
|
+
"widening the sparse checkout to the full tree",
|
|
783
|
+
)
|
|
784
|
+
return SparsePlan(False, None, [], [], "")
|
|
785
|
+
|
|
786
|
+
if has_repo and not is_sparse:
|
|
787
|
+
message = (
|
|
788
|
+
f"the cached checkout is a full checkout, sparse profile "
|
|
789
|
+
f"{profile} is not applied"
|
|
790
|
+
)
|
|
791
|
+
return SparsePlan(False, None, [], [], message)
|
|
792
|
+
|
|
793
|
+
cone_directories = _cone_directories(sparse_patterns)
|
|
794
|
+
if not has_repo:
|
|
795
|
+
if cone_directories is not None:
|
|
796
|
+
args = [
|
|
797
|
+
"git",
|
|
798
|
+
"sparse-checkout",
|
|
799
|
+
"set",
|
|
800
|
+
"--cone",
|
|
801
|
+
"--sparse-index",
|
|
802
|
+
"--stdin",
|
|
803
|
+
]
|
|
804
|
+
cone, entries = True, cone_directories
|
|
805
|
+
else:
|
|
806
|
+
args = ["git", "sparse-checkout", "set", "--no-cone", "--stdin"]
|
|
807
|
+
cone, entries = False, sparse_patterns
|
|
808
|
+
op = "sparse_set"
|
|
809
|
+
else:
|
|
810
|
+
is_cone = _git_config(repo_path, "core.sparseCheckoutCone") == "true"
|
|
811
|
+
current = _git_lines(repo_path, "sparse-checkout", "list")
|
|
812
|
+
# Newly checked out patterns are compatible with a `cone` sparse
|
|
813
|
+
# checkout and the existing sparse checkout is `cone`: simply add the
|
|
814
|
+
# new directories to it.
|
|
815
|
+
if cone_directories is not None and is_cone:
|
|
816
|
+
args = ["git", "sparse-checkout", "add", "--stdin"]
|
|
817
|
+
cone, op = True, "sparse_add"
|
|
818
|
+
entries = [d for d in cone_directories if d not in set(current)]
|
|
819
|
+
# Newly checked out patterns are not compatible with a `cone` sparse
|
|
820
|
+
# checkout and the existing sparse checkout is `cone`: perform a new
|
|
821
|
+
# sparse checkout with `cone` disabled, including the existing
|
|
822
|
+
# directories, the parent directory files that `cone` mode included
|
|
823
|
+
# implicitly, and the new patterns.
|
|
824
|
+
elif is_cone:
|
|
825
|
+
args = [
|
|
826
|
+
"git",
|
|
827
|
+
"sparse-checkout",
|
|
828
|
+
"set",
|
|
829
|
+
"--no-cone",
|
|
830
|
+
"--no-sparse-index",
|
|
831
|
+
"--stdin",
|
|
832
|
+
]
|
|
833
|
+
cone, op = False, "sparse_convert"
|
|
834
|
+
entries = list(
|
|
835
|
+
dict.fromkeys(
|
|
836
|
+
[f"/{_escape_pattern(d)}" for d in current]
|
|
837
|
+
+ _cone_ancestor_files(repo_path, rev, current)
|
|
838
|
+
+ sparse_patterns
|
|
839
|
+
)
|
|
840
|
+
)
|
|
841
|
+
# Newly checked out patterns are not compatible with a `cone` sparse
|
|
842
|
+
# checkout and the existing sparse checkout is not `cone`: simply add the
|
|
843
|
+
# new patterns to it.
|
|
844
|
+
else:
|
|
845
|
+
args = ["git", "sparse-checkout", "add", "--stdin"]
|
|
846
|
+
cone, op = False, "sparse_add"
|
|
847
|
+
entries = [p for p in sparse_patterns if p not in set(current)]
|
|
848
|
+
if not entries:
|
|
849
|
+
return SparsePlan(
|
|
850
|
+
cone, None, [], [], f"sparse profile {profile} already applied"
|
|
851
|
+
)
|
|
852
|
+
|
|
853
|
+
message = f"{op}: sparse profile {profile}, {len(entries)} entries"
|
|
854
|
+
return SparsePlan(cone, op, args, entries, message)
|
|
855
|
+
|
|
856
|
+
|
|
676
857
|
def shortref(ref: str) -> str:
|
|
677
858
|
"""Normalize a git ref to its short form.
|
|
678
859
|
|
|
@@ -696,6 +877,8 @@ def git_checkout(
|
|
|
696
877
|
ssh_key_file: Optional[Path],
|
|
697
878
|
ssh_known_hosts_file: Optional[Path],
|
|
698
879
|
shallow: bool = False,
|
|
880
|
+
sparse_profile: Optional[str] = None,
|
|
881
|
+
sparse_patterns: Optional[List[str]] = None,
|
|
699
882
|
):
|
|
700
883
|
assert head_ref or head_rev
|
|
701
884
|
|
|
@@ -725,20 +908,32 @@ def git_checkout(
|
|
|
725
908
|
)
|
|
726
909
|
|
|
727
910
|
has_repo = os.path.exists(destination_path)
|
|
728
|
-
perf = PerfRecorder("vcs")
|
|
729
|
-
with perf.record("overall", "overall_pull" if has_repo else "overall_clone"):
|
|
730
|
-
# Bypass Git's "safe directory" feature as the destination could be
|
|
731
|
-
# coming from a cache and therefore cloned by a different user.
|
|
732
|
-
args = [
|
|
733
|
-
"git",
|
|
734
|
-
"config",
|
|
735
|
-
"--global",
|
|
736
|
-
"--add",
|
|
737
|
-
"safe.directory",
|
|
738
|
-
Path(destination_path).as_posix(),
|
|
739
|
-
]
|
|
740
|
-
retry_required_command(b"vcs", args, extra_env=env)
|
|
741
911
|
|
|
912
|
+
# Bypass Git's "safe directory" feature as the destination could be
|
|
913
|
+
# coming from a cache and therefore cloned by a different user.
|
|
914
|
+
args = [
|
|
915
|
+
"git",
|
|
916
|
+
"config",
|
|
917
|
+
"--global",
|
|
918
|
+
"--add",
|
|
919
|
+
"safe.directory",
|
|
920
|
+
Path(destination_path).as_posix(),
|
|
921
|
+
]
|
|
922
|
+
retry_required_command(b"vcs", args, extra_env=env)
|
|
923
|
+
|
|
924
|
+
workers = os.environ.get("RUN_TASK_GIT_CHECKOUT_WORKERS", "0")
|
|
925
|
+
args = ["git", "config", "--global", "checkout.workers", workers]
|
|
926
|
+
retry_required_command(b"vcs", args, extra_env=env)
|
|
927
|
+
|
|
928
|
+
is_sparse = (
|
|
929
|
+
has_repo and _git_config(destination_path, "core.sparseCheckout") == "true"
|
|
930
|
+
)
|
|
931
|
+
sparse = bool(sparse_patterns) and (not has_repo or is_sparse)
|
|
932
|
+
|
|
933
|
+
action = "pull" if has_repo else "clone"
|
|
934
|
+
mode = "sparsecheckout" if sparse else "fullcheckout"
|
|
935
|
+
perf = PerfRecorder("vcs")
|
|
936
|
+
with perf.record("overall", f"overall_{action}", f"overall_{action}_{mode}"):
|
|
742
937
|
if not has_repo:
|
|
743
938
|
# Repository doesn't already exist, needs to be cloned
|
|
744
939
|
args = [
|
|
@@ -747,7 +942,13 @@ def git_checkout(
|
|
|
747
942
|
]
|
|
748
943
|
|
|
749
944
|
if shallow:
|
|
750
|
-
args.
|
|
945
|
+
args.append("--depth=1")
|
|
946
|
+
|
|
947
|
+
if sparse:
|
|
948
|
+
args.append("--filter=blob:none")
|
|
949
|
+
|
|
950
|
+
if shallow or sparse:
|
|
951
|
+
args.append("--no-checkout")
|
|
751
952
|
|
|
752
953
|
args.extend(
|
|
753
954
|
[
|
|
@@ -758,6 +959,11 @@ def git_checkout(
|
|
|
758
959
|
|
|
759
960
|
with perf.record("clone"):
|
|
760
961
|
retry_required_command(b"vcs", args, extra_env=env)
|
|
962
|
+
else:
|
|
963
|
+
# Fetches go through origin by name, so a cached clone has to
|
|
964
|
+
# point origin at the repository this task wants.
|
|
965
|
+
args = ["git", "remote", "set-url", "origin", base_repo or head_repo]
|
|
966
|
+
run_required_command(b"vcs", args, cwd=destination_path)
|
|
761
967
|
|
|
762
968
|
# For Github based repos, base_rev often doesn't refer to an ancestor of
|
|
763
969
|
# head_rev simply due to Github not providing that information in their
|
|
@@ -786,16 +992,42 @@ def git_checkout(
|
|
|
786
992
|
# shallow clone, head_rev needs to be fetched independently regardless.
|
|
787
993
|
targets.append(head_rev)
|
|
788
994
|
|
|
995
|
+
# A partial clone keeps its blob filter on the origin remote, and a
|
|
996
|
+
# fetch only inherits the filter when it names that remote. Fetching
|
|
997
|
+
# the same repository by URL would download every blob of the commit.
|
|
998
|
+
remote = "origin" if head_repo == (base_repo or head_repo) else head_repo
|
|
999
|
+
|
|
789
1000
|
with perf.record("pull"):
|
|
790
1001
|
git_fetch(
|
|
791
1002
|
destination_path,
|
|
792
1003
|
*targets,
|
|
793
|
-
remote=
|
|
1004
|
+
remote=remote,
|
|
794
1005
|
tags=tags,
|
|
795
1006
|
shallow=shallow,
|
|
796
1007
|
env=env,
|
|
797
1008
|
)
|
|
798
1009
|
|
|
1010
|
+
# `git fetch` set `FETCH_HEAD` reference to the last commit of the desired branch
|
|
1011
|
+
rev = head_rev if head_rev else "FETCH_HEAD"
|
|
1012
|
+
|
|
1013
|
+
plan = _plan_sparse_checkout(
|
|
1014
|
+
destination_path, has_repo, is_sparse, sparse_profile, sparse_patterns, rev
|
|
1015
|
+
)
|
|
1016
|
+
if sparse:
|
|
1017
|
+
perf.extra_options.append("cone" if plan.cone else "no-cone")
|
|
1018
|
+
if sparse_profile:
|
|
1019
|
+
perf.extra_options.append(os.path.basename(sparse_profile))
|
|
1020
|
+
if plan.message:
|
|
1021
|
+
print_line(b"vcs", f"{plan.message}\n".encode("utf-8"))
|
|
1022
|
+
if plan.op:
|
|
1023
|
+
stdin_data = None
|
|
1024
|
+
if plan.entries:
|
|
1025
|
+
stdin_data = ("\n".join(plan.entries) + "\n").encode("utf-8")
|
|
1026
|
+
with perf.record(plan.op):
|
|
1027
|
+
run_required_command(
|
|
1028
|
+
b"vcs", plan.args, cwd=destination_path, stdin_data=stdin_data
|
|
1029
|
+
)
|
|
1030
|
+
|
|
799
1031
|
args = [
|
|
800
1032
|
"git",
|
|
801
1033
|
"checkout",
|
|
@@ -805,8 +1037,7 @@ def git_checkout(
|
|
|
805
1037
|
if head_ref:
|
|
806
1038
|
args.extend(["-B", shortref(head_ref)])
|
|
807
1039
|
|
|
808
|
-
|
|
809
|
-
args.append(head_rev if head_rev else "FETCH_HEAD")
|
|
1040
|
+
args.append(rev)
|
|
810
1041
|
|
|
811
1042
|
with perf.record("update"):
|
|
812
1043
|
run_required_command(b"vcs", args, cwd=destination_path)
|
|
@@ -893,6 +1124,7 @@ def hg_checkout(
|
|
|
893
1124
|
store_path: str,
|
|
894
1125
|
branch: Optional[str],
|
|
895
1126
|
revision: Optional[str],
|
|
1127
|
+
sparse_profile: Optional[str] = None,
|
|
896
1128
|
):
|
|
897
1129
|
if IS_MACOSX or IS_POSIX:
|
|
898
1130
|
hg_bin = "hg"
|
|
@@ -916,6 +1148,9 @@ def hg_checkout(
|
|
|
916
1148
|
if base_repo:
|
|
917
1149
|
args.extend(["--upstream", base_repo])
|
|
918
1150
|
|
|
1151
|
+
if sparse_profile:
|
|
1152
|
+
args.extend(["--sparseprofile", sparse_profile])
|
|
1153
|
+
|
|
919
1154
|
# Specify method to checkout a revision. This defaults to revisions as
|
|
920
1155
|
# SHA-1 strings, but also supports symbolic revisions like `tip` via the
|
|
921
1156
|
# branch flag.
|
|
@@ -983,13 +1218,35 @@ def add_vcs_arguments(parser, project, name):
|
|
|
983
1218
|
action="store_true",
|
|
984
1219
|
help=f"Use shallow clone for {name}",
|
|
985
1220
|
)
|
|
1221
|
+
parser.add_argument(
|
|
1222
|
+
f"--{project}-sparse-profile",
|
|
1223
|
+
help=f"Path to sparse profile for {name} checkout. For git checkouts "
|
|
1224
|
+
f"the patterns come from the {project.upper()}_SPARSE_PATTERNS "
|
|
1225
|
+
"environment variable, a JSON list of git sparse-checkout patterns.",
|
|
1226
|
+
)
|
|
986
1227
|
|
|
987
1228
|
|
|
988
1229
|
def collect_vcs_options(args, project, name):
|
|
989
1230
|
checkout = getattr(args, f"{project}_checkout")
|
|
990
1231
|
shallow_clone = getattr(args, f"{project}_shallow_clone")
|
|
1232
|
+
sparse_profile = getattr(args, f"{project}_sparse_profile", None)
|
|
991
1233
|
|
|
992
1234
|
env_prefix = project.upper()
|
|
1235
|
+
sparse_patterns = None
|
|
1236
|
+
if f"{env_prefix}_SPARSE_PATTERNS" in os.environ:
|
|
1237
|
+
try:
|
|
1238
|
+
sparse_patterns = json.loads(os.environ[f"{env_prefix}_SPARSE_PATTERNS"])
|
|
1239
|
+
except ValueError as e:
|
|
1240
|
+
raise RuntimeError(
|
|
1241
|
+
f"{env_prefix}_SPARSE_PATTERNS is not a JSON list of patterns: {e}"
|
|
1242
|
+
) from None
|
|
1243
|
+
if not isinstance(sparse_patterns, list) or not all(
|
|
1244
|
+
isinstance(p, str) and p.strip() for p in sparse_patterns
|
|
1245
|
+
):
|
|
1246
|
+
raise RuntimeError(
|
|
1247
|
+
f"{env_prefix}_SPARSE_PATTERNS must be a JSON list of non-empty "
|
|
1248
|
+
"pattern strings"
|
|
1249
|
+
)
|
|
993
1250
|
|
|
994
1251
|
repo_type = os.environ.get(f"{env_prefix}_REPOSITORY_TYPE")
|
|
995
1252
|
base_repo = os.environ.get(f"{env_prefix}_BASE_REPOSITORY")
|
|
@@ -1032,6 +1289,8 @@ def collect_vcs_options(args, project, name):
|
|
|
1032
1289
|
"ssh-secret-name": private_key_secret,
|
|
1033
1290
|
"pip-requirements": pip_requirements,
|
|
1034
1291
|
"shallow-clone": shallow_clone,
|
|
1292
|
+
"sparse-profile": sparse_profile,
|
|
1293
|
+
"sparse-patterns": sparse_patterns,
|
|
1035
1294
|
}
|
|
1036
1295
|
|
|
1037
1296
|
|
|
@@ -1040,6 +1299,10 @@ def vcs_checkout_from_args(options):
|
|
|
1040
1299
|
if options["head-ref"] and not options["head-rev"]:
|
|
1041
1300
|
print("task should be defined in terms of non-symbolic revision")
|
|
1042
1301
|
sys.exit(1)
|
|
1302
|
+
if options["sparse-profile"] or options["sparse-patterns"] is not None:
|
|
1303
|
+
raise RuntimeError(
|
|
1304
|
+
f"--{options['project']}-sparse-profile needs --{options['project']}-checkout"
|
|
1305
|
+
)
|
|
1043
1306
|
return
|
|
1044
1307
|
|
|
1045
1308
|
head_ref = options["head-ref"]
|
|
@@ -1071,6 +1334,17 @@ def vcs_checkout_from_args(options):
|
|
|
1071
1334
|
if not head_ref:
|
|
1072
1335
|
print("Providing a ref will improve the performance of this checkout")
|
|
1073
1336
|
|
|
1337
|
+
if bool(options["sparse-profile"]) != (options["sparse-patterns"] is not None):
|
|
1338
|
+
raise RuntimeError(
|
|
1339
|
+
f"--{options['project']}-sparse-profile and the "
|
|
1340
|
+
f"{options['env-prefix']}_SPARSE_PATTERNS environment variable "
|
|
1341
|
+
"must be given together for a git checkout"
|
|
1342
|
+
)
|
|
1343
|
+
if options["sparse-patterns"] is not None and not options["sparse-patterns"]:
|
|
1344
|
+
raise RuntimeError(
|
|
1345
|
+
f"{options['env-prefix']}_SPARSE_PATTERNS holds no patterns"
|
|
1346
|
+
)
|
|
1347
|
+
|
|
1074
1348
|
revision = git_checkout(
|
|
1075
1349
|
options["checkout"],
|
|
1076
1350
|
options["head-repo"],
|
|
@@ -1081,6 +1355,8 @@ def vcs_checkout_from_args(options):
|
|
|
1081
1355
|
ssh_key_file,
|
|
1082
1356
|
ssh_known_hosts_file,
|
|
1083
1357
|
shallow=options.get("shallow-clone", False),
|
|
1358
|
+
sparse_profile=options["sparse-profile"],
|
|
1359
|
+
sparse_patterns=options["sparse-patterns"],
|
|
1084
1360
|
)
|
|
1085
1361
|
elif options["repo-type"] == "hg":
|
|
1086
1362
|
revision = hg_checkout(
|
|
@@ -1090,6 +1366,7 @@ def vcs_checkout_from_args(options):
|
|
|
1090
1366
|
options["store-path"],
|
|
1091
1367
|
head_ref,
|
|
1092
1368
|
head_rev,
|
|
1369
|
+
sparse_profile=options["sparse-profile"],
|
|
1093
1370
|
)
|
|
1094
1371
|
else:
|
|
1095
1372
|
raise RuntimeError('Type of VCS must be either "git" or "hg"')
|
taskgraph/transforms/task.py
CHANGED
|
@@ -870,6 +870,8 @@ def add_generic_index_routes(config, task):
|
|
|
870
870
|
|
|
871
871
|
@transforms.add
|
|
872
872
|
def process_treeherder_metadata(config, tasks):
|
|
873
|
+
group_names = config.graph_config.get("treeherder", {}).get("group-names", {})
|
|
874
|
+
|
|
873
875
|
for task in tasks:
|
|
874
876
|
routes = task.get("routes", [])
|
|
875
877
|
extra = task.get("extra", {})
|
|
@@ -891,7 +893,6 @@ def process_treeherder_metadata(config, tasks):
|
|
|
891
893
|
treeherder["machine"] = {"platform": machine_platform}
|
|
892
894
|
treeherder["collection"] = {collection: True}
|
|
893
895
|
|
|
894
|
-
group_names = config.graph_config["treeherder"]["group-names"]
|
|
895
896
|
groupSymbol, symbol = split_symbol(merged_th["symbol"])
|
|
896
897
|
if groupSymbol != "?":
|
|
897
898
|
treeherder["groupSymbol"] = groupSymbol
|
{taskcluster_taskgraph-24.2.2.dist-info → taskcluster_taskgraph-24.3.0.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{taskcluster_taskgraph-24.2.2.dist-info → taskcluster_taskgraph-24.3.0.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|