1 # This Source Code Form is subject to the terms of the Mozilla Public
2 # License, v. 2.0. If a copy of the MPL was not distributed with this
3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
10 from collections
import defaultdict
12 import mozpack
.path
as mozpath
14 from mach
.util
import get_state_dir
15 from mozbuild
.base
import MozbuildObject
16 from mozpack
.files
import FileFinder
17 from moztest
.resolve
import TestManifestLoader
, TestResolver
, get_suite_definition
18 from taskgraph
.generator
import TaskGraphGenerator
19 from taskgraph
.parameters
import ParameterMismatch
, parameters_loader
20 from taskgraph
.taskgraph
import TaskGraph
22 here
= os
.path
.abspath(os
.path
.dirname(__file__
))
23 build
= MozbuildObject
.from_environment(cwd
=here
)
25 PARAMETER_MISMATCH
= """
26 ERROR - The parameters being used to generate tasks differ from those expected
31 To fix this, either rebase onto the latest mozilla-central or pass in
32 -p/--parameters. For more information on how to define parameters, see:
33 https://firefox-source-docs.mozilla.org/taskcluster/taskcluster/mach.html#parameters
37 def invalidate(cache
):
39 cmod
= os
.path
.getmtime(cache
)
41 # File does not exist. We catch OSError rather than use `isfile`
42 # because the recommended watchman hook could possibly invalidate the
43 # cache in-between the check to `isfile` and the call to `getmtime`
49 tc_dir
= os
.path
.join(build
.topsrcdir
, "taskcluster")
50 tmod
= max(os
.path
.getmtime(os
.path
.join(tc_dir
, p
)) for p
, _
in FileFinder(tc_dir
))
56 def cache_key(attr
, params
, disable_target_task_filter
):
58 if params
and params
["project"] not in ("autoland", "mozilla-central"):
59 key
+= f
"-{params['project']}"
61 if disable_target_task_filter
and "full" not in attr
:
66 def generate_tasks(params
=None, full
=False, disable_target_task_filter
=False):
67 attr
= "full_task_set" if full
else "target_task_set"
68 target_tasks_method
= (
70 if not disable_target_task_filter
71 else "try_select_tasks_uncommon"
73 params
= parameters_loader(
77 "try_mode": "try_select",
78 "target_tasks_method": target_tasks_method
,
81 root
= os
.path
.join(build
.topsrcdir
, "taskcluster", "ci")
83 generator
= TaskGraphGenerator(root_dir
=root
, parameters
=params
)
85 cache_dir
= os
.path
.join(
86 get_state_dir(specific_to_topsrcdir
=True), "cache", "taskgraph"
88 key
= cache_key(attr
, generator
.parameters
, disable_target_task_filter
)
89 cache
= os
.path
.join(cache_dir
, key
)
92 if os
.path
.isfile(cache
):
93 with
open(cache
) as fh
:
94 return TaskGraph
.from_json(json
.load(fh
))[1]
96 if not os
.path
.isdir(cache_dir
):
97 os
.makedirs(cache_dir
)
99 print("Task configuration changed, generating {}".format(attr
.replace("_", " ")))
102 os
.chdir(build
.topsrcdir
)
106 tg
= getattr(generator
, attr
)
107 except ParameterMismatch
as e
:
108 print(PARAMETER_MISMATCH
.format(e
.args
[0]))
112 key
= cache_key(attr
, generator
.parameters
, disable_target_task_filter
)
113 with
open(os
.path
.join(cache_dir
, key
), "w") as fh
:
114 json
.dump(tg
.to_json(), fh
)
117 # Cache both full_task_set and target_task_set regardless of whether or not
118 # --full was requested. Caching is cheap and can potentially save a lot of
120 tg_full
= generate("full_task_set")
121 tg_target
= generate("target_task_set")
123 # discard results from these, we only need cache.
125 generate("full_task_graph")
126 generate("target_task_graph")
134 def filter_tasks_by_paths(tasks
, paths
):
135 resolver
= TestResolver
.from_environment(cwd
=here
, loader_cls
=TestManifestLoader
)
136 run_suites
, run_tests
= resolver
.resolve_metadata(paths
)
137 flavors
= {(t
["flavor"], t
.get("subsuite")) for t
in run_tests
}
140 for flavor
, subsuite
in flavors
:
141 _
, suite
= get_suite_definition(flavor
, subsuite
, strict
=True)
142 if "task_regex" not in suite
:
144 "warning: no tasks could be resolved from flavor '{}'{}".format(
145 flavor
, " and subsuite '{}'".format(subsuite
) if subsuite
else ""
150 task_regexes
.update(suite
["task_regex"])
152 def match_task(task
):
153 return any(re
.search(pattern
, task
) for pattern
in task_regexes
)
155 return filter(match_task
, tasks
)
158 def resolve_tests_by_suite(paths
):
159 resolver
= TestResolver
.from_environment(cwd
=here
, loader_cls
=TestManifestLoader
)
160 _
, run_tests
= resolver
.resolve_metadata(paths
)
162 suite_to_tests
= defaultdict(list)
164 # A dictionary containing all the input paths that we haven't yet
165 # assigned to a specific test flavor.
166 remaining_paths_by_suite
= defaultdict(lambda: set(paths
))
168 for test
in run_tests
:
169 key
, _
= get_suite_definition(test
["flavor"], test
.get("subsuite"), strict
=True)
171 test_path
= test
.get("srcdir_relpath")
172 if test_path
is None:
175 manifest_relpath
= None
176 if "manifest_relpath" in test
:
177 manifest_relpath
= mozpath
.normpath(test
["manifest_relpath"])
178 for path
in remaining_paths_by_suite
[key
]:
179 if test_path
.startswith(path
) or manifest_relpath
== path
:
183 suite_to_tests
[key
].append(found_path
)
184 remaining_paths_by_suite
[key
].remove(found_path
)
186 return suite_to_tests