tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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