bootstrap.configure (16266B)
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/. 6 7 option( 8 "--with-tor-browser-build-out", 9 env="TOR_BROWSER_BUILD_OUT", 10 nargs=1, 11 default="https://tb-build-06.torproject.org/~tb-builder/tor-browser-build/out", 12 help="URL pointing to a Tor Browser Build out folder, served over HTTP[S].", 13 ) 14 15 16 @depends("--with-tor-browser-build-out") 17 def tor_browser_build_out(value): 18 if value: 19 return value[0] 20 21 22 option( 23 "--enable-tor-browser-build-only-bootstrap", 24 env="TBB_ONLY_BOOTSTRAP", 25 default=False, 26 help="Flag that disables bootstrapping any artifact from Mozilla's Taskcluster. Will only bootstrap artifacts from tor-browser-build.", 27 ) 28 29 30 option( 31 env="MOZ_FETCHES_DIR", 32 nargs=1, 33 when=moz_automation, 34 help="Directory containing fetched artifacts", 35 ) 36 37 38 @depends("MOZ_FETCHES_DIR", when=moz_automation) 39 def moz_fetches_dir(value): 40 if value: 41 return value[0] 42 43 44 @depends(vcs_checkout_type, milestone.is_nightly, moz_automation) 45 def bootstrap_default(vcs_checkout_type, is_nightly, automation): 46 if automation: 47 return False 48 # We only enable if building off a VCS checkout of central. 49 if is_nightly and vcs_checkout_type: 50 return True 51 52 53 option( 54 "--enable-bootstrap", 55 nargs="*", 56 default=bootstrap_default, 57 help="{Automatically bootstrap or update some toolchains|Disable bootstrap or update of toolchains}", 58 ) 59 60 61 @depends("--enable-bootstrap") 62 def want_bootstrap(bootstrap): 63 include = set() 64 exclude = set() 65 for item in bootstrap: 66 if item == "no-update": 67 continue 68 if item.startswith("-"): 69 exclude.add(item.lstrip("-")) 70 else: 71 include.add(item) 72 73 def match(name): 74 if not bootstrap: 75 return False 76 77 if name in exclude: 78 return False 79 if include and name in include: 80 return True 81 return not bool(include) 82 83 return match 84 85 86 toolchains_base_dir = moz_fetches_dir | mozbuild_state_path 87 88 89 @dependable 90 @imports("os") 91 @imports(_from="os", _import="environ") 92 def original_path(): 93 return environ["PATH"].split(os.pathsep) 94 95 96 @depends(host, when="--enable-bootstrap") 97 @imports("os") 98 @imports("traceback") 99 @imports(_from="mozbuild.toolchains", _import="toolchain_task_definitions") 100 @imports(_from="__builtin__", _import="Exception") 101 def bootstrap_toolchain_tasks(host): 102 prefix = { 103 ("x86_64", "GNU", "Linux"): "linux64", 104 ("aarch64", "GNU", "Linux"): "linux64-aarch64", 105 ("x86_64", "OSX", "Darwin"): "macosx64", 106 ("aarch64", "OSX", "Darwin"): "macosx64-aarch64", 107 ("x86_64", "WINNT", "WINNT"): "win64", 108 ("aarch64", "WINNT", "WINNT"): "win64-aarch64", 109 }.get((host.cpu, host.os, host.kernel)) 110 try: 111 tasks = toolchain_task_definitions() 112 except Exception as e: 113 message = traceback.format_exc() 114 log.warning(str(e)) 115 log.debug(message) 116 return None 117 118 def task_data(t): 119 result = { 120 "index": t.optimization["index-search"], 121 "artifact": t.attributes[f"{t.kind}-artifact"], 122 "extract": t.attributes.get(f"{t.kind}-extract", True), 123 } 124 command = t.attributes.get("toolchain-command") 125 if command: 126 result["command"] = command 127 return result 128 129 # We only want to use toolchains annotated with "local-toolchain". We also limit the 130 # amount of data to what we use, so that trace logs can be more useful. 131 tasks = { 132 k: task_data(t) 133 for k, t in tasks.items() 134 if t.attributes.get(f"local-{t.kind}") 135 and t.optimization 136 and "index-search" in t.optimization 137 } 138 139 return namespace(prefix=prefix, tasks=tasks) 140 141 142 @template 143 def bootstrap_path(path, **kwargs): 144 when = kwargs.pop("when", None) 145 allow_failure = kwargs.pop("allow_failure", None) 146 no_unpack = kwargs.pop("no_unpack", False) 147 if kwargs: 148 configure_error( 149 "bootstrap_path only takes `when`, `allow_failure` and `no_unpack` as keyword arguments" 150 ) 151 152 @depends( 153 "--enable-bootstrap", 154 want_bootstrap, 155 toolchains_base_dir, 156 moz_fetches_dir, 157 bootstrap_toolchain_tasks, 158 build_environment, 159 dependable(path), 160 dependable(allow_failure), 161 dependable(no_unpack), 162 tor_browser_build_out, 163 "--enable-tor-browser-build-only-bootstrap", 164 target, 165 when=when, 166 ) 167 @imports("os") 168 @imports("re") 169 @imports("subprocess") 170 @imports("sys") 171 @imports("mozbuild.tbbutils") 172 @imports(_from="mozbuild.dirutils", _import="ensureParentDir") 173 @imports(_from="importlib", _import="import_module") 174 @imports(_from="shutil", _import="rmtree") 175 @imports(_from="__builtin__", _import="open") 176 @imports(_from="__builtin__", _import="Exception") 177 def bootstrap_path( 178 enable_bootstrap, 179 want_bootstrap, 180 toolchains_base_dir, 181 moz_fetches_dir, 182 tasks, 183 build_env, 184 path, 185 allow_failure, 186 no_unpack, 187 tor_browser_build_out, 188 tbb_only_bootstrap, 189 target, 190 ): 191 if not path: 192 return 193 path_parts = path.split("/") 194 path_prefix = "" 195 # Small hack until clang-tidy stops being a separate toolchain in a 196 # weird location. 197 if path_parts[0] == "clang-tools": 198 path_prefix = path_parts.pop(0) 199 200 # Small hack because extensions are inside the browser folder. 201 if path_parts[0] in ("noscript",): 202 path_prefix = "browser" 203 204 def try_tbb_bootstrap(exists): 205 if not tor_browser_build_out: 206 return False 207 208 # Tor browser build doesn't have artifacts for all targets supported 209 # by the Firefox build system. When this is empty it means we are 210 # building for a platform which tbb doesn't support. 211 if not target.tor_browser_build_alias: 212 return False 213 214 artifact = mozbuild.tbbutils.get_artifact_name(path_parts[0], tasks.prefix) 215 if not artifact: 216 log.info("%s is not mapped to a tbb artifact", path_parts[0]) 217 return False 218 219 artifact_path = mozbuild.tbbutils.get_artifact_path( 220 tor_browser_build_out, 221 artifact, 222 target, 223 prefix=path_prefix, 224 log=log.warning, 225 ) 226 if not artifact_path: 227 log.info("no path found in tbb/out for %s", artifact) 228 return False 229 230 artifact_index = mozbuild.tbbutils.get_artifact_index( 231 artifact_path, artifact 232 ) 233 index_file = os.path.join(toolchains_base_dir, "indices", artifact) 234 try: 235 with open(index_file) as fh: 236 index = fh.read().strip() 237 except Exception: 238 index = None 239 if index == artifact_index and exists: 240 log.debug("%s is up-to-date", artifact) 241 return True 242 243 command = ["artifact", "toolchain", "--from-url", artifact_path] 244 245 if no_unpack: 246 command.append("--no-unpack") 247 248 # Note to rebasers: 249 # From here on, it's a slightly modified copy/paste 250 # from the end of the try_bootstrap function 251 log.info( 252 "%s bootstrapped toolchain from TBB in %s", 253 "Updating" if exists else "Installing", 254 os.path.join(toolchains_base_dir, path_prefix, artifact), 255 ) 256 os.makedirs(os.path.join(toolchains_base_dir, path_prefix), exist_ok=True) 257 proc = subprocess.run( 258 [ 259 sys.executable, 260 os.path.join(build_env.topsrcdir, "mach"), 261 "--log-no-times", 262 ] 263 + command, 264 cwd=os.path.join(toolchains_base_dir, path_prefix), 265 check=not allow_failure, 266 ) 267 if proc.returncode != 0 and allow_failure: 268 return False 269 ensureParentDir(index_file) 270 with open(index_file, "w") as fh: 271 fh.write(artifact_index) 272 273 return True 274 275 def try_bootstrap(exists): 276 if not tasks: 277 return False 278 prefixes = [""] 279 if tasks.prefix: 280 prefixes.insert(0, "{}-".format(tasks.prefix)) 281 for prefix in prefixes: 282 for kind in ("toolchain", "fetch"): 283 label = f"{kind}-{prefix}{path_parts[0]}" 284 task = tasks.tasks.get(label) 285 if task: 286 break 287 if task: 288 break 289 log.debug("Trying to bootstrap %s", label) 290 if not task: 291 return False 292 task_index = task["index"] 293 log.debug("Resolved %s to %s", label, task_index[0]) 294 task_index = task_index[0].split(".")[-1] 295 artifact = task["artifact"] 296 # `mach artifact toolchain` doesn't support authentication for 297 # private artifacts. Some toolchains may provide a command that can be 298 # used for local production of the artifact. 299 command = None 300 if not artifact.startswith("public/"): 301 command = task.get("command") 302 if not command: 303 log.debug("Cannot bootstrap %s: not a public artifact", label) 304 return False 305 index_file = os.path.join(toolchains_base_dir, "indices", path_parts[0]) 306 try: 307 with open(index_file) as fh: 308 index = fh.read().strip() 309 except Exception: 310 # On automation, if there's an artifact in MOZ_FETCHES_DIR, we assume it's 311 # up-to-date. 312 index = task_index if moz_fetches_dir else None 313 if index == task_index and exists: 314 log.debug("%s is up-to-date", label) 315 return True 316 # Manually import with import_module so that we can gracefully disable bootstrap 317 # when e.g. building from a js standalone tarball, that doesn't contain the 318 # taskgraph code. In those cases, `mach artifact toolchain --from-build` would 319 # also fail. 320 task_id = None 321 if not command: 322 try: 323 IndexSearch = import_module( 324 "taskgraph.optimize.strategies" 325 ).IndexSearch 326 except Exception: 327 log.debug("Cannot bootstrap %s: missing taskgraph module", label) 328 return False 329 task_id = IndexSearch().should_replace_task( 330 task, {}, None, task["index"] 331 ) 332 if task_id: 333 # If we found the task in the index, use the `mach artifact toolchain` 334 # fast path. 335 command = [ 336 "artifact", 337 "toolchain", 338 "--from-task", 339 f"{task_id}:{artifact}", 340 ] 341 if not task["extract"]: 342 command.append("--no-unpack") 343 344 elif command: 345 # For private local toolchains, run the associated command. 346 command = ( 347 [ 348 "python", 349 "--virtualenv", 350 "build", 351 os.path.join( 352 build_env.topsrcdir, 353 "taskcluster/scripts/misc", 354 command["script"], 355 ), 356 ] 357 + command["arguments"] 358 + [path_parts[0]] 359 ) 360 361 # BIG HACK: Replace the Apple CDN link with our mirror, 362 # otherwise bootstrapping will fail whenever a new MacOS SDK 363 # is released, since Apple seems to retire the previous link everytime. 364 # Our mirror serves an _unmodified_ version of the file. 365 macosx_sdk_match = re.match(r"^MacOSX(.*)\.sdk$", path_parts[0]) 366 if macosx_sdk_match: 367 version = macosx_sdk_match.group(1) 368 command = [ 369 re.sub( 370 r"https://swcdn\.apple\.com/.*/CLTools_macOSNMOS_SDK\.pkg", 371 f"https://build-sources.tbb.torproject.org/CLTools_macOSNMOS_SDK-{version}.pkg", 372 c, 373 ) 374 for c in command 375 ] 376 377 # Clean up anything that was bootstrapped previously before going 378 # forward. In other cases, that's taken care of by mach artifact toolchain. 379 rmtree( 380 os.path.join(toolchains_base_dir, path_prefix, path_parts[0]), 381 ignore_errors=True, 382 ) 383 else: 384 # Otherwise, use the slower path, which will print a better error than 385 # we would be able to. 386 command = ["artifact", "toolchain", "--from-build", label] 387 388 log.info( 389 "%s bootstrapped toolchain in %s", 390 "Updating" if exists else "Installing", 391 os.path.join(toolchains_base_dir, path_prefix, path_parts[0]), 392 ) 393 os.makedirs(os.path.join(toolchains_base_dir, path_prefix), exist_ok=True) 394 proc = subprocess.run( 395 [ 396 sys.executable, 397 os.path.join(build_env.topsrcdir, "mach"), 398 "--log-no-times", 399 ] 400 + command, 401 cwd=os.path.join(toolchains_base_dir, path_prefix), 402 check=not allow_failure, 403 ) 404 if proc.returncode != 0 and allow_failure: 405 return False 406 ensureParentDir(index_file) 407 with open(index_file, "w") as fh: 408 fh.write(task_index) 409 return True 410 411 path = os.path.join(toolchains_base_dir, path_prefix, *path_parts) 412 if want_bootstrap(path_parts[0]): 413 exists = os.path.exists(path) 414 try: 415 # With --enable-bootstrap=no-update, we don't `try_bootstrap`, except 416 # when the toolchain can't be found. 417 if ("no-update" not in enable_bootstrap or not exists) and not ( 418 try_tbb_bootstrap(exists) 419 or (not tbb_only_bootstrap and try_bootstrap(exists)) 420 ): 421 # If there aren't toolchain artifacts to use for this build, 422 # don't return a path. 423 return None 424 except Exception as e: 425 log.error("%s", e) 426 die( 427 "If you can't fix the above, retry with --enable-bootstrap=no-update." 428 ) 429 if enable_bootstrap or enable_bootstrap.origin == "default": 430 # We re-test whether the path exists because it may have been created by 431 # try_bootstrap. Automation will not have gone through the bootstrap 432 # process, but we want to return the path if it exists. 433 if os.path.exists(path): 434 return path 435 436 return bootstrap_path 437 438 439 @template 440 def bootstrap_search_path(path, paths=original_path, **kwargs): 441 @depends( 442 bootstrap_path(path, **kwargs), 443 paths, 444 original_path, 445 ) 446 def bootstrap_search_path(path, paths, original_path): 447 if paths is None: 448 paths = original_path 449 if not path: 450 return paths 451 return [path] + paths 452 453 return bootstrap_search_path