1 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
2 # vim: set filetype=python:
3 # This Source Code Form is subject to the terms of the Mozilla Public
4 # License, v. 2.0. If a copy of the MPL was not distributed with this
5 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
10 when="MOZ_AUTOMATION",
11 help="Directory containing fetched artifacts",
15 @depends("MOZ_FETCHES_DIR", when="MOZ_AUTOMATION")
16 def moz_fetches_dir(value):
21 @depends(vcs_checkout_type, milestone.is_nightly, "MOZ_AUTOMATION")
22 def bootstrap_default(vcs_checkout_type, is_nightly, automation):
25 # We only enable if building off a VCS checkout of central.
26 if is_nightly and vcs_checkout_type:
33 default=bootstrap_default,
34 help="{Automatically bootstrap or update some toolchains|Disable bootstrap or update of toolchains}",
38 @depends_if("--enable-bootstrap")
39 def enable_bootstrap(bootstrap):
42 for item in bootstrap:
43 if item.startswith("-"):
44 exclude.add(item.lstrip("-"))
51 if include and name in include:
53 return not bool(include)
58 @depends(developer_options, "--enable-bootstrap", moz_fetches_dir)
59 def bootstrap_search_path_order(developer_options, bootstrap, moz_fetches_dir):
61 log.debug("Prioritizing MOZ_FETCHES_DIR in toolchain path.")
66 "Prioritizing mozbuild state dir in toolchain paths because "
67 "bootstrap mode is enabled."
69 return "maybe-prepend"
73 "Prioritizing mozbuild state dir in toolchain paths because "
74 "you are not building in release mode."
79 "Prioritizing system over mozbuild state dir in "
80 "toolchain paths because you are building in "
86 toolchains_base_dir = moz_fetches_dir | mozbuild_state_path
91 @imports(_from="os", _import="environ")
93 return environ["PATH"].split(os.pathsep)
96 @depends(host, when="--enable-bootstrap")
99 @imports(_from="mozbuild.toolchains", _import="toolchain_task_definitions")
100 @imports(_from="__builtin__", _import="Exception")
101 def bootstrap_toolchain_tasks(host):
103 ("x86_64", "GNU", "Linux"): "linux64",
104 ("x86_64", "OSX", "Darwin"): "macosx64",
105 ("aarch64", "OSX", "Darwin"): "macosx64-aarch64",
106 ("x86_64", "WINNT", "WINNT"): "win64",
107 ("aarch64", "WINNT", "WINNT"): "win64-aarch64",
108 }.get((host.cpu, host.os, host.kernel))
110 tasks = toolchain_task_definitions()
111 except Exception as e:
112 message = traceback.format_exc()
119 "index": t.optimization["index-search"],
120 "artifact": t.attributes["toolchain-artifact"],
122 command = t.attributes.get("toolchain-command")
124 result["command"] = command
127 # We only want to use toolchains annotated with "local-toolchain". We also limit the
128 # amount of data to what we use, so that trace logs can be more useful.
131 for k, t in tasks.items()
132 if t.attributes.get("local-toolchain") and "index-search" in t.optimization
135 return namespace(prefix=prefix, tasks=tasks)
139 def bootstrap_path(path, **kwargs):
140 when = kwargs.pop("when", None)
141 allow_failure = kwargs.pop("allow_failure", None)
144 "bootstrap_path only takes `when` and `allow_failure` as a keyword argument"
151 bootstrap_toolchain_tasks,
154 dependable(allow_failure),
158 @imports("subprocess")
160 @imports(_from="mozbuild.util", _import="ensureParentDir")
161 @imports(_from="importlib", _import="import_module")
162 @imports(_from="shutil", _import="rmtree")
163 @imports(_from="__builtin__", _import="open")
164 @imports(_from="__builtin__", _import="Exception")
176 path_parts = path.split("/")
178 # Small hack until clang-tidy stops being a separate toolchain in a
180 if path_parts[0] == "clang-tools":
181 path_prefix = path_parts.pop(0)
183 def try_bootstrap(exists):
188 prefixes.insert(0, "{}-".format(tasks.prefix))
189 for prefix in prefixes:
190 label = "toolchain-{}{}".format(prefix, path_parts[0])
191 task = tasks.tasks.get(label)
194 log.debug("Trying to bootstrap %s", label)
197 task_index = task["index"]
198 log.debug("Resolved %s to %s", label, task_index[0])
199 task_index = task_index[0].split(".")[-1]
200 artifact = task["artifact"]
201 # `mach artifact toolchain` doesn't support authentication for
202 # private artifacts. Some toolchains may provide a command that can be
203 # used for local production of the artifact.
205 if not artifact.startswith("public/"):
206 command = task.get("command")
208 log.debug("Cannot bootstrap %s: not a public artifact", label)
210 index_file = os.path.join(toolchains_base_dir, "indices", path_parts[0])
212 with open(index_file) as fh:
213 index = fh.read().strip()
215 # On automation, if there's an artifact in MOZ_FETCHES_DIR, we assume it's
217 index = task_index if moz_fetches_dir else None
218 if index == task_index and exists:
219 log.debug("%s is up-to-date", label)
221 # Manually import with import_module so that we can gracefully disable bootstrap
222 # when e.g. building from a js standalone tarball, that doesn't contain the
223 # taskgraph code. In those cases, `mach artifact toolchain --from-build` would
228 IndexSearch = import_module(
229 "taskgraph.optimize.strategies"
232 log.debug("Cannot bootstrap %s: missing taskgraph module", label)
234 task_id = IndexSearch().should_replace_task(
235 task, {}, None, task["index"]
238 # If we found the task in the index, use the `mach artifact toolchain`
244 f"{task_id}:{artifact}",
247 # For private local toolchains, run the associated command.
255 "taskcluster/scripts/misc",
259 + command["arguments"]
262 # Clean up anything that was bootstrapped previously before going
263 # forward. In other cases, that's taken care of by mach artifact toolchain.
265 os.path.join(toolchains_base_dir, path_prefix, path_parts[0]),
269 # Otherwise, use the slower path, which will print a better error than
270 # we would be able to.
271 command = ["artifact", "toolchain", "--from-build", label]
274 "%s bootstrapped toolchain in %s",
275 "Updating" if exists else "Installing",
276 os.path.join(toolchains_base_dir, path_prefix, path_parts[0]),
278 os.makedirs(os.path.join(toolchains_base_dir, path_prefix), exist_ok=True)
279 proc = subprocess.run(
282 os.path.join(build_env.topsrcdir, "mach"),
286 cwd=os.path.join(toolchains_base_dir, path_prefix),
287 check=not allow_failure,
289 if proc.returncode != 0 and allow_failure:
291 ensureParentDir(index_file)
292 with open(index_file, "w") as fh:
296 path = os.path.join(toolchains_base_dir, path_prefix, *path_parts)
297 if bootstrap and bootstrap(path_parts[0]):
299 if not try_bootstrap(os.path.exists(path)):
300 # If there aren't toolchain artifacts to use for this build,
301 # don't return a path.
303 except Exception as e:
305 die("If you can't fix the above, retry with --disable-bootstrap.")
306 # We re-test whether the path exists because it may have been created by
307 # try_bootstrap. Automation will not have gone through the bootstrap
308 # process, but we want to return the path if it exists.
309 if os.path.exists(path):
312 return bootstrap_path
316 def bootstrap_search_path(path, paths=original_path, **kwargs):
319 bootstrap_path(path, **kwargs),
320 bootstrap_search_path_order,
324 def bootstrap_search_path(bootstrap, path, order, paths, original_path):
326 paths = original_path
329 if order == "maybe-prepend":
330 if bootstrap(path.split("/")[0]):
334 if order == "prepend":
335 return [path] + paths
336 return paths + [path]
338 return bootstrap_search_path