xref: /aosp_15_r20/external/executorch/build/resolve_buck.py (revision 523fa7a60841cd1ecfb9cc4201f1ca8b03ed023a)
1*523fa7a6SAndroid Build Coastguard Worker#!/usr/bin/env python3
2*523fa7a6SAndroid Build Coastguard Worker# Copyright (c) Meta Platforms, Inc. and affiliates.
3*523fa7a6SAndroid Build Coastguard Worker# Copyright 2024 Arm Limited and/or its affiliates.
4*523fa7a6SAndroid Build Coastguard Worker# All rights reserved.
5*523fa7a6SAndroid Build Coastguard Worker#
6*523fa7a6SAndroid Build Coastguard Worker# This source code is licensed under the BSD-style license found in the
7*523fa7a6SAndroid Build Coastguard Worker# LICENSE file in the root directory of this source tree.
8*523fa7a6SAndroid Build Coastguard Worker
9*523fa7a6SAndroid Build Coastguard Workerimport argparse
10*523fa7a6SAndroid Build Coastguard Workerimport os
11*523fa7a6SAndroid Build Coastguard Workerimport platform
12*523fa7a6SAndroid Build Coastguard Workerimport stat
13*523fa7a6SAndroid Build Coastguard Workerimport sys
14*523fa7a6SAndroid Build Coastguard Workerimport tempfile
15*523fa7a6SAndroid Build Coastguard Workerimport urllib.request
16*523fa7a6SAndroid Build Coastguard Worker
17*523fa7a6SAndroid Build Coastguard Workerfrom dataclasses import dataclass
18*523fa7a6SAndroid Build Coastguard Workerfrom pathlib import Path
19*523fa7a6SAndroid Build Coastguard Workerfrom typing import Sequence, Union
20*523fa7a6SAndroid Build Coastguard Worker
21*523fa7a6SAndroid Build Coastguard Workerimport buck_util
22*523fa7a6SAndroid Build Coastguard Workerimport zstd
23*523fa7a6SAndroid Build Coastguard Worker
24*523fa7a6SAndroid Build Coastguard Worker"""
25*523fa7a6SAndroid Build Coastguard WorkerLocate or download the version of buck2 needed to build ExecuTorch.
26*523fa7a6SAndroid Build Coastguard WorkerIt is intended to be invoked from the CMake build logic, and it returns
27*523fa7a6SAndroid Build Coastguard Workerthe path to 'buck2' via stdout. Log messages are written to stderr.
28*523fa7a6SAndroid Build Coastguard Worker
29*523fa7a6SAndroid Build Coastguard WorkerIt uses the following logic, in order of precedence, to locate or download
30*523fa7a6SAndroid Build Coastguard Workerbuck2:
31*523fa7a6SAndroid Build Coastguard Worker
32*523fa7a6SAndroid Build Coastguard Worker 1) If BUCK2 argument is set explicitly, use it. Warn if the version is
33*523fa7a6SAndroid Build Coastguard Worker    incorrect.
34*523fa7a6SAndroid Build Coastguard Worker 2) Look for a binary named buck2 on the system path. Take it if it is
35*523fa7a6SAndroid Build Coastguard Worker    the correct version.
36*523fa7a6SAndroid Build Coastguard Worker 3) Check for a previously downloaded buck2 binary (from step 4).
37*523fa7a6SAndroid Build Coastguard Worker 4) Download and cache correct version of buck2.
38*523fa7a6SAndroid Build Coastguard Worker
39*523fa7a6SAndroid Build Coastguard Worker"""
40*523fa7a6SAndroid Build Coastguard Worker
41*523fa7a6SAndroid Build Coastguard Worker# Path to the file containing BUCK2 version (build date) for ExecuTorch.
42*523fa7a6SAndroid Build Coastguard Worker# Note that this path is relative to this script file, not the working
43*523fa7a6SAndroid Build Coastguard Worker# directory.
44*523fa7a6SAndroid Build Coastguard WorkerBUCK_VERSION_FILE = "../.ci/docker/ci_commit_pins/buck2.txt"
45*523fa7a6SAndroid Build Coastguard Worker
46*523fa7a6SAndroid Build Coastguard Worker
47*523fa7a6SAndroid Build Coastguard Worker@dataclass
48*523fa7a6SAndroid Build Coastguard Workerclass BuckInfo:
49*523fa7a6SAndroid Build Coastguard Worker    archive_name: str
50*523fa7a6SAndroid Build Coastguard Worker    target_versions: Sequence[str]
51*523fa7a6SAndroid Build Coastguard Worker
52*523fa7a6SAndroid Build Coastguard Worker
53*523fa7a6SAndroid Build Coastguard Worker# Mapping of os family and architecture to buck2 binary versions. The
54*523fa7a6SAndroid Build Coastguard Worker# target version is the hash given by running 'buck2 --version'. The
55*523fa7a6SAndroid Build Coastguard Worker# archive name is the archive file name to download, as seen under
56*523fa7a6SAndroid Build Coastguard Worker# https://github.com/facebook/buck2/releases/.
57*523fa7a6SAndroid Build Coastguard Worker#
58*523fa7a6SAndroid Build Coastguard Worker# To add or update versions, download the appropriate version of buck2
59*523fa7a6SAndroid Build Coastguard Worker# and run 'buck2 --version'. Add the corresponding entry to the platform
60*523fa7a6SAndroid Build Coastguard Worker# map below, and if adding new os families or architectures, update the
61*523fa7a6SAndroid Build Coastguard Worker# platform detection logic in resolve_buck2().
62*523fa7a6SAndroid Build Coastguard Worker#
63*523fa7a6SAndroid Build Coastguard Worker# Some platforms (linux) provide multiple binaries (GNU and MUSL). All
64*523fa7a6SAndroid Build Coastguard Worker# versions in the list are accepted when validating a user-provided or
65*523fa7a6SAndroid Build Coastguard Worker# system buck2.
66*523fa7a6SAndroid Build Coastguard WorkerBUCK_PLATFORM_MAP = {
67*523fa7a6SAndroid Build Coastguard Worker    ("linux", "x86_64"): BuckInfo(
68*523fa7a6SAndroid Build Coastguard Worker        archive_name="buck2-x86_64-unknown-linux-musl.zst",
69*523fa7a6SAndroid Build Coastguard Worker        target_versions=[
70*523fa7a6SAndroid Build Coastguard Worker            # MUSL
71*523fa7a6SAndroid Build Coastguard Worker            "3bbde7daa94987db468d021ad625bc93dc62ba7fcb16945cb09b64aab077f284",
72*523fa7a6SAndroid Build Coastguard Worker            # GNU
73*523fa7a6SAndroid Build Coastguard Worker            "029b0bcc6f8e399185c1d0f574eba204934722b5",
74*523fa7a6SAndroid Build Coastguard Worker        ],
75*523fa7a6SAndroid Build Coastguard Worker    ),
76*523fa7a6SAndroid Build Coastguard Worker    ("linux", "aarch64"): BuckInfo(
77*523fa7a6SAndroid Build Coastguard Worker        archive_name="buck2-aarch64-unknown-linux-gnu.zst",
78*523fa7a6SAndroid Build Coastguard Worker        target_versions=["49670bee56a7d8a7696409ca6fbf7551d2469787"],
79*523fa7a6SAndroid Build Coastguard Worker    ),
80*523fa7a6SAndroid Build Coastguard Worker    ("darwin", "aarch64"): BuckInfo(
81*523fa7a6SAndroid Build Coastguard Worker        archive_name="buck2-aarch64-apple-darwin.zst",
82*523fa7a6SAndroid Build Coastguard Worker        target_versions=["99773fe6f7963a72ae5f7b737c02836e"],
83*523fa7a6SAndroid Build Coastguard Worker    ),
84*523fa7a6SAndroid Build Coastguard Worker    ("darwin", "x86_64"): BuckInfo(
85*523fa7a6SAndroid Build Coastguard Worker        archive_name="buck2-x86_64-apple-darwin.zst",
86*523fa7a6SAndroid Build Coastguard Worker        target_versions=["3eb1ae97ea963086866b4d2d9ffa966d"],
87*523fa7a6SAndroid Build Coastguard Worker    ),
88*523fa7a6SAndroid Build Coastguard Worker}
89*523fa7a6SAndroid Build Coastguard Worker
90*523fa7a6SAndroid Build Coastguard Worker
91*523fa7a6SAndroid Build Coastguard Workerdef parse_args() -> argparse.Namespace:
92*523fa7a6SAndroid Build Coastguard Worker    parser = argparse.ArgumentParser(
93*523fa7a6SAndroid Build Coastguard Worker        description="Locates or downloads the appropriate version of buck2.",
94*523fa7a6SAndroid Build Coastguard Worker    )
95*523fa7a6SAndroid Build Coastguard Worker    parser.add_argument(
96*523fa7a6SAndroid Build Coastguard Worker        "--buck2",
97*523fa7a6SAndroid Build Coastguard Worker        default="",
98*523fa7a6SAndroid Build Coastguard Worker        help="Optional user-provided 'buck2' path. If provided, it will be "
99*523fa7a6SAndroid Build Coastguard Worker        "used. If the version is incorrect, a warning will be logged.",
100*523fa7a6SAndroid Build Coastguard Worker    )
101*523fa7a6SAndroid Build Coastguard Worker    parser.add_argument(
102*523fa7a6SAndroid Build Coastguard Worker        "--cache_dir",
103*523fa7a6SAndroid Build Coastguard Worker        help="Directory to cache downloaded versions of buck2.",
104*523fa7a6SAndroid Build Coastguard Worker    )
105*523fa7a6SAndroid Build Coastguard Worker    return parser.parse_args()
106*523fa7a6SAndroid Build Coastguard Worker
107*523fa7a6SAndroid Build Coastguard Worker
108*523fa7a6SAndroid Build Coastguard Worker# Returns the path to buck2 on success or a return code on failure.
109*523fa7a6SAndroid Build Coastguard Workerdef resolve_buck2(args: argparse.Namespace) -> Union[str, int]:
110*523fa7a6SAndroid Build Coastguard Worker    # Find buck2, in order of priority:
111*523fa7a6SAndroid Build Coastguard Worker    #  1) Explicit buck2 argument.
112*523fa7a6SAndroid Build Coastguard Worker    #  2) System buck2 (if correct version).
113*523fa7a6SAndroid Build Coastguard Worker    #  3) Cached buck2 (previously downloaded).
114*523fa7a6SAndroid Build Coastguard Worker    #  3) Download buck2.
115*523fa7a6SAndroid Build Coastguard Worker
116*523fa7a6SAndroid Build Coastguard Worker    # Read the target version (build date) from the CI pin file. Note that
117*523fa7a6SAndroid Build Coastguard Worker    # this path is resolved relative to the directory containing this script.
118*523fa7a6SAndroid Build Coastguard Worker    script_dir = os.path.dirname(__file__)
119*523fa7a6SAndroid Build Coastguard Worker    version_file_path = Path(script_dir) / BUCK_VERSION_FILE
120*523fa7a6SAndroid Build Coastguard Worker    with open(version_file_path.absolute().as_posix()) as f:
121*523fa7a6SAndroid Build Coastguard Worker        target_buck_version = f.read().strip()
122*523fa7a6SAndroid Build Coastguard Worker
123*523fa7a6SAndroid Build Coastguard Worker    # Determine the target buck2 version string according to the current
124*523fa7a6SAndroid Build Coastguard Worker    # platform. If the platform isn't linux or darwin, we won't perform
125*523fa7a6SAndroid Build Coastguard Worker    # any version validation.
126*523fa7a6SAndroid Build Coastguard Worker    machine = platform.machine().lower()
127*523fa7a6SAndroid Build Coastguard Worker    arch = "unknown"
128*523fa7a6SAndroid Build Coastguard Worker    if machine == "x86" or machine == "x86_64" or machine == "amd64":
129*523fa7a6SAndroid Build Coastguard Worker        arch = "x86_64"
130*523fa7a6SAndroid Build Coastguard Worker    elif machine == "arm64" or machine == "aarch64":
131*523fa7a6SAndroid Build Coastguard Worker        arch = "aarch64"
132*523fa7a6SAndroid Build Coastguard Worker
133*523fa7a6SAndroid Build Coastguard Worker    os_family = "unknown"
134*523fa7a6SAndroid Build Coastguard Worker    if sys.platform.startswith("linux"):
135*523fa7a6SAndroid Build Coastguard Worker        os_family = "linux"
136*523fa7a6SAndroid Build Coastguard Worker    elif sys.platform.startswith("darwin"):
137*523fa7a6SAndroid Build Coastguard Worker        os_family = "darwin"
138*523fa7a6SAndroid Build Coastguard Worker
139*523fa7a6SAndroid Build Coastguard Worker    platform_key = (os_family, arch)
140*523fa7a6SAndroid Build Coastguard Worker    if platform_key not in BUCK_PLATFORM_MAP:
141*523fa7a6SAndroid Build Coastguard Worker        print(
142*523fa7a6SAndroid Build Coastguard Worker            f"Unknown platform {platform_key}. Buck2 binary must be downloaded manually.",
143*523fa7a6SAndroid Build Coastguard Worker            file=sys.stderr,
144*523fa7a6SAndroid Build Coastguard Worker        )
145*523fa7a6SAndroid Build Coastguard Worker        return args.buck2 or "buck2"
146*523fa7a6SAndroid Build Coastguard Worker
147*523fa7a6SAndroid Build Coastguard Worker    buck_info = BUCK_PLATFORM_MAP[platform_key]
148*523fa7a6SAndroid Build Coastguard Worker
149*523fa7a6SAndroid Build Coastguard Worker    if args.buck2:
150*523fa7a6SAndroid Build Coastguard Worker        # If we have an explicit buck2 arg, check the version and fail if
151*523fa7a6SAndroid Build Coastguard Worker        # there is a mismatch.
152*523fa7a6SAndroid Build Coastguard Worker        ver = buck_util.get_buck2_version(args.buck2)
153*523fa7a6SAndroid Build Coastguard Worker        if ver in buck_info.target_versions:
154*523fa7a6SAndroid Build Coastguard Worker            return args.buck2
155*523fa7a6SAndroid Build Coastguard Worker        else:
156*523fa7a6SAndroid Build Coastguard Worker            print(
157*523fa7a6SAndroid Build Coastguard Worker                f'The provided buck2 binary "{args.buck2}" reports version '
158*523fa7a6SAndroid Build Coastguard Worker                f'"{ver}", but ExecuTorch needs version '
159*523fa7a6SAndroid Build Coastguard Worker                f'"{buck_info.target_versions[0]}". Ensure that the correct buck2'
160*523fa7a6SAndroid Build Coastguard Worker                " version is installed or avoid explicitly passing the BUCK2 "
161*523fa7a6SAndroid Build Coastguard Worker                "version to automatically download the correct version.",
162*523fa7a6SAndroid Build Coastguard Worker                file=sys.stderr,
163*523fa7a6SAndroid Build Coastguard Worker            )
164*523fa7a6SAndroid Build Coastguard Worker
165*523fa7a6SAndroid Build Coastguard Worker            # Return an error, since the build will fail later. This lets us
166*523fa7a6SAndroid Build Coastguard Worker            # give the user a more useful error message. Note that an exit
167*523fa7a6SAndroid Build Coastguard Worker            # code of 2 allows us to distinguish from an unexpected error,
168*523fa7a6SAndroid Build Coastguard Worker            # such as a failed import, which exits with 1.
169*523fa7a6SAndroid Build Coastguard Worker            return 2
170*523fa7a6SAndroid Build Coastguard Worker    else:
171*523fa7a6SAndroid Build Coastguard Worker        # Look for system buck2 and check version. Note that this can return
172*523fa7a6SAndroid Build Coastguard Worker        # None.
173*523fa7a6SAndroid Build Coastguard Worker        ver = buck_util.get_buck2_version("buck2")
174*523fa7a6SAndroid Build Coastguard Worker        if ver in buck_info.target_versions:
175*523fa7a6SAndroid Build Coastguard Worker            # Use system buck2.
176*523fa7a6SAndroid Build Coastguard Worker            return "buck2"
177*523fa7a6SAndroid Build Coastguard Worker        else:
178*523fa7a6SAndroid Build Coastguard Worker            # Download buck2 or used previously cached download.
179*523fa7a6SAndroid Build Coastguard Worker            cache_dir = Path(args.cache_dir)
180*523fa7a6SAndroid Build Coastguard Worker            os.makedirs(cache_dir, exist_ok=True)
181*523fa7a6SAndroid Build Coastguard Worker
182*523fa7a6SAndroid Build Coastguard Worker            buck2_local_path = (
183*523fa7a6SAndroid Build Coastguard Worker                (cache_dir / f"buck2-{buck_info.target_versions[0]}")
184*523fa7a6SAndroid Build Coastguard Worker                .absolute()
185*523fa7a6SAndroid Build Coastguard Worker                .as_posix()
186*523fa7a6SAndroid Build Coastguard Worker            )
187*523fa7a6SAndroid Build Coastguard Worker
188*523fa7a6SAndroid Build Coastguard Worker            # Check for a previously cached buck2 binary. The filename includes
189*523fa7a6SAndroid Build Coastguard Worker            # the version hash, so we don't have to worry about using an
190*523fa7a6SAndroid Build Coastguard Worker            # outdated binary, in the event that the target version is updated.
191*523fa7a6SAndroid Build Coastguard Worker            if os.path.isfile(buck2_local_path):
192*523fa7a6SAndroid Build Coastguard Worker                return buck2_local_path
193*523fa7a6SAndroid Build Coastguard Worker
194*523fa7a6SAndroid Build Coastguard Worker            buck2_archive_url = f"https://github.com/facebook/buck2/releases/download/{target_buck_version}/{buck_info.archive_name}"
195*523fa7a6SAndroid Build Coastguard Worker
196*523fa7a6SAndroid Build Coastguard Worker            with tempfile.NamedTemporaryFile() as archive_file:
197*523fa7a6SAndroid Build Coastguard Worker                print(f"Downloading buck2 from {buck2_archive_url}...", file=sys.stderr)
198*523fa7a6SAndroid Build Coastguard Worker                urllib.request.urlretrieve(buck2_archive_url, archive_file.name)
199*523fa7a6SAndroid Build Coastguard Worker
200*523fa7a6SAndroid Build Coastguard Worker                # Extract and chmod.
201*523fa7a6SAndroid Build Coastguard Worker                with open(archive_file.name, "rb") as f:
202*523fa7a6SAndroid Build Coastguard Worker                    data = f.read()
203*523fa7a6SAndroid Build Coastguard Worker                    decompressed_bytes = zstd.decompress(data)
204*523fa7a6SAndroid Build Coastguard Worker
205*523fa7a6SAndroid Build Coastguard Worker                with open(buck2_local_path, "wb") as f:
206*523fa7a6SAndroid Build Coastguard Worker                    f.write(decompressed_bytes)
207*523fa7a6SAndroid Build Coastguard Worker
208*523fa7a6SAndroid Build Coastguard Worker                file_stat = os.stat(buck2_local_path)
209*523fa7a6SAndroid Build Coastguard Worker                os.chmod(buck2_local_path, file_stat.st_mode | stat.S_IEXEC)
210*523fa7a6SAndroid Build Coastguard Worker
211*523fa7a6SAndroid Build Coastguard Worker            return buck2_local_path
212*523fa7a6SAndroid Build Coastguard Worker
213*523fa7a6SAndroid Build Coastguard Worker
214*523fa7a6SAndroid Build Coastguard Workerdef main():
215*523fa7a6SAndroid Build Coastguard Worker    args = parse_args()
216*523fa7a6SAndroid Build Coastguard Worker    resolved_path_or_error = resolve_buck2(args)
217*523fa7a6SAndroid Build Coastguard Worker    if isinstance(resolved_path_or_error, str):
218*523fa7a6SAndroid Build Coastguard Worker        print(resolved_path_or_error)
219*523fa7a6SAndroid Build Coastguard Worker    else:
220*523fa7a6SAndroid Build Coastguard Worker        sys.exit(resolved_path_or_error)
221*523fa7a6SAndroid Build Coastguard Worker
222*523fa7a6SAndroid Build Coastguard Worker
223*523fa7a6SAndroid Build Coastguard Workerif __name__ == "__main__":
224*523fa7a6SAndroid Build Coastguard Worker    main()
225