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/.
12 from mach
.util
import get_state_dir
13 from mozbuild
.base
import MozbuildObject
14 from mozversioncontrol
import MissingVCSExtension
, get_repository_object
16 from .util
.estimates
import duration_summary
17 from .util
.manage_estimates
import (
18 download_task_history_data
,
19 make_trimmed_taskgraph_cache
,
22 GIT_CINNABAR_NOT_FOUND
= """
23 Could not detect `git-cinnabar`.
25 The `mach try` command requires git-cinnabar to be installed when
26 pushing from git. Please install it by running:
31 HG_PUSH_TO_TRY_NOT_FOUND
= """
32 Could not detect `push-to-try`.
34 The `mach try` command requires the push-to-try extension enabled
35 when pushing from hg. Please install it by running:
41 Could not detect version control. Only `hg` or `git` are supported.
44 UNCOMMITTED_CHANGES
= """
45 ERROR please commit changes before continuing
50 here
= os
.path
.abspath(os
.path
.dirname(__file__
))
51 build
= MozbuildObject
.from_environment(cwd
=here
)
52 vcs
= get_repository_object(build
.topsrcdir
)
54 history_path
= os
.path
.join(
55 get_state_dir(specific_to_topsrcdir
=True), "history", "try_task_configs.json"
59 def write_task_config(try_task_config
):
60 config_path
= os
.path
.join(vcs
.path
, "try_task_config.json")
61 with
open(config_path
, "w") as fh
:
62 json
.dump(try_task_config
, fh
, indent
=4, separators
=(",", ": "), sort_keys
=True)
67 def write_task_config_history(msg
, try_task_config
):
68 if not os
.path
.isfile(history_path
):
69 if not os
.path
.isdir(os
.path
.dirname(history_path
)):
70 os
.makedirs(os
.path
.dirname(history_path
))
73 with
open(history_path
) as fh
:
74 history
= fh
.read().strip().splitlines()
76 history
.insert(0, json
.dumps([msg
, try_task_config
]))
77 history
= history
[:MAX_HISTORY
]
78 with
open(history_path
, "w") as fh
:
79 fh
.write("\n".join(history
))
82 def check_working_directory(push
=True):
86 if not vcs
.working_directory_clean():
87 print(UNCOMMITTED_CHANGES
)
91 def generate_try_task_config(method
, labels
, try_config
=None, routes
=None):
92 try_task_config
= try_config
or {}
93 try_task_config
.setdefault("env", {})["TRY_SELECTOR"] = method
94 try_task_config
.update(
97 "tasks": sorted(labels
),
101 try_task_config
["routes"] = routes
103 return try_task_config
106 def task_labels_from_try_config(try_task_config
):
107 if try_task_config
["version"] == 2:
108 parameters
= try_task_config
.get("parameters", {})
109 if parameters
.get("try_mode") == "try_task_config":
110 return parameters
["try_task_config"]["tasks"]
113 elif try_task_config
["version"] == 1:
114 return try_task_config
.get("tasks", list())
119 def display_push_estimates(try_task_config
):
120 task_labels
= task_labels_from_try_config(try_task_config
)
121 if task_labels
is None:
124 cache_dir
= os
.path
.join(
125 get_state_dir(specific_to_topsrcdir
=True), "cache", "taskgraph"
131 for graph_cache_file
in ["target_task_graph", "full_task_graph"]:
132 graph_cache
= os
.path
.join(cache_dir
, graph_cache_file
)
133 if os
.path
.isfile(graph_cache
):
134 dep_cache
= graph_cache
.replace("task_graph", "task_dependencies")
135 target_file
= graph_cache
.replace("task_graph", "task_set")
141 download_task_history_data(cache_dir
=cache_dir
)
142 make_trimmed_taskgraph_cache(graph_cache
, dep_cache
, target_file
=target_file
)
144 durations
= duration_summary(dep_cache
, task_labels
, cache_dir
)
147 "estimates: Runs {} tasks ({} selected, {} dependencies)".format(
148 durations
["dependency_count"] + durations
["selected_count"],
149 durations
["selected_count"],
150 durations
["dependency_count"],
154 "estimates: Total task duration {}".format(
155 durations
["dependency_duration"] + durations
["selected_duration"]
158 if "percentile" in durations
:
160 "estimates: In the top {}% of durations".format(
161 100 - durations
["percentile"]
165 "estimates: Should take about {} (Finished around {})".format(
166 durations
["wall_duration_seconds"],
167 durations
["eta_datetime"].strftime("%Y-%m-%d %H:%M"),
175 try_task_config
=None,
179 files_to_change
=None,
180 allow_log_capture
=False,
182 push
= not stage_changes
and not dry_run
183 check_working_directory(push
)
185 if try_task_config
and method
not in ("auto", "empty"):
187 display_push_estimates(try_task_config
)
189 traceback
.print_exc()
190 print("warning: unable to display push estimates")
192 # Format the commit message
193 closed_tree_string
= " ON A CLOSED TREE" if closed_tree
else ""
194 commit_message
= "{}{}\n\nPushed via `mach try {}`".format(
203 if push
and method
not in ("again", "auto", "empty"):
204 write_task_config_history(msg
, try_task_config
)
205 config_path
= write_task_config(try_task_config
)
206 changed_files
.append(config_path
)
208 if (push
or stage_changes
) and files_to_change
:
209 for path
, content
in files_to_change
.items():
210 path
= os
.path
.join(vcs
.path
, path
)
211 with
open(path
, "wb") as fh
:
212 fh
.write(six
.ensure_binary(content
))
213 changed_files
.append(path
)
217 print("Commit message:")
218 print(commit_message
)
220 print("Calculated try_task_config.json:")
221 with
open(config_path
) as fh
:
225 vcs
.add_remove_files(*changed_files
)
228 vcs
.push_to_try(commit_message
, allow_log_capture
=allow_log_capture
)
229 except MissingVCSExtension
as e
:
230 if e
.ext
== "push-to-try":
231 print(HG_PUSH_TO_TRY_NOT_FOUND
)
232 elif e
.ext
== "cinnabar":
233 print(GIT_CINNABAR_NOT_FOUND
)
238 if config_path
and os
.path
.isfile(config_path
):
239 os
.remove(config_path
)