| # Copyright 2022 The Fuchsia Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| """Recipe for building EDK II UEFI firmware for QEMU""" |
| |
| from collections import namedtuple |
| |
| DEPS = [ |
| "fuchsia/buildbucket_util", |
| "fuchsia/cas_util", |
| "fuchsia/cipd_util", |
| "fuchsia/git", |
| "fuchsia/git_checkout", |
| "fuchsia/python3", |
| "recipe_engine/cipd", |
| "recipe_engine/context", |
| "recipe_engine/file", |
| "recipe_engine/path", |
| "recipe_engine/platform", |
| "recipe_engine/step", |
| ] |
| |
| # Bump this number whenever changing this recipe in ways that affect the |
| # package built without also changing any upstream revision pin. |
| RECIPE_SALT = "5" |
| |
| EDK2_REPO = "https://fuchsia.googlesource.com/third_party/edk2" |
| EDK2_REF = "refs/tags/edk2-stable202205" |
| |
| # |
| # Build flags, as they are spelled by the project. |
| # |
| |
| # Reads as "GCC version >= 5". |
| # |
| # TODO(fxbug.dev/99325): Unfortunately, there are too many bugs and obstacles |
| # to getting the firmware built with clang at the moment; try again down the |
| # road. |
| TOOLCHAIN_TAGNAME = "GCC5" |
| |
| BUILDTARGET = "RELEASE" |
| |
| # Ranges from 0 (lowest) to 9 (highest). In the event of failures, it is |
| # recommended to debug with a value of 9; this ensures failure messages that |
| # are actually useful (e.g., python backtraces). |
| DEBUG_LEVEL = 0 |
| |
| |
| # Represents an EDK II "package", the basic development unit. More or less |
| # corresponds to a top-level directory in edk2.git. |
| Package = namedtuple( |
| "Package", |
| [ |
| # (str): The name of the package. Just used for display purposes. |
| "name", |
| # (str): The target architecture for the package, as spelled by the |
| # project (e.g., "X64" or "AARCH64"). |
| "arch", |
| # (Path): Points to the "platform" file (*.dsc), detailing the |
| # firmware to build. |
| "platform", |
| # (str): The top-level subdirectory of the build directory under which |
| # the corresponding firmware images will be found. |
| "build_subdir", |
| # (str): The top-level directory of the final uploaded directory under |
| # which the corresponding firmware images will be organized. |
| "staging_subdir", |
| # (list(SizeConstraint)): List of size constraints on the associated |
| # firmware volumes. |
| "size_constraints", |
| ], |
| ) |
| |
| SizeConstraint = namedtuple( |
| "SizeConstraint", |
| [ |
| # (str): The path to a firmware volume within a package artifact |
| # directory. |
| "volume", |
| # (int): The size in mebibytes that the firmware volume should be extended |
| # until. |
| "size_mb", |
| ], |
| ) |
| |
| |
| def Packages(checkout_dir): |
| return [ |
| Package( |
| name="OvmfPkg", |
| arch="X64", |
| platform=checkout_dir.join("OvmfPkg", "OvmfPkgX64.dsc"), |
| build_subdir="OvmfX64", |
| staging_subdir="qemu-x64", |
| size_constraints=[], |
| ), |
| Package( |
| name="ArmVirtPkg", |
| arch="AARCH64", |
| platform=checkout_dir.join("ArmVirtPkg", "ArmVirtQemu.dsc"), |
| build_subdir="ArmVirtQemu-AARCH64", |
| staging_subdir="qemu-arm64", |
| # Undocumented "virt" machine restriction that these volumes should |
| # be precisely 64MiB to be stored in emulated flash. |
| size_constraints=[ |
| SizeConstraint( |
| volume="QEMU_EFI.fd", |
| size_mb=64, |
| ), |
| SizeConstraint( |
| volume="QEMU_VARS.fd", |
| size_mb=64, |
| ), |
| ], |
| ), |
| ] |
| |
| |
| def RunSteps(api): |
| checkout_dir, revision = api.git_checkout( |
| EDK2_REPO, fallback_ref=EDK2_REF, submodules=True, recursive=True |
| ) |
| |
| # TODO(joshuaseaton): Report or fix this bug upstream. |
| with api.context(cwd=checkout_dir): |
| api.git.apply(api.resource("ld-notext.patch")) |
| |
| cipd_dir = api.path["start_dir"].join("cipd") |
| with api.step.nest("ensure packages"): |
| with api.context(infra_steps=True): |
| pkgs = api.cipd.EnsureFile() |
| pkgs.add_package("fuchsia/sysroot/${platform}", "latest", "sysroot") |
| pkgs.add_package("fuchsia/third_party/clang/${platform}", "integration") |
| pkgs.add_package("fuchsia/third_party/gcc/${platform}", "integration") |
| pkgs.add_package("fuchsia/third_party/make/${platform}", "version:4.3") |
| api.cipd.ensure(cipd_dir, pkgs) |
| |
| bin_dir = cipd_dir.join("bin") |
| sysroot = cipd_dir.join("sysroot") |
| |
| # BaseTools is needed to build anything else. |
| base_tools_dir = checkout_dir.join("BaseTools") |
| with api.context( |
| cwd=base_tools_dir, |
| env={ |
| "CXX": "llvm", |
| "CLANG_BIN": "%s/" % bin_dir, |
| "EXTRA_OPTFLAGS": "-Wno-register -Wno-deprecated-non-prototype", |
| "BUILD_OPTFLAGS": f"--sysroot={sysroot}", |
| }, |
| env_prefixes={"PATH": [bin_dir]}, |
| ): |
| api.step("build BaseTools", ["make"]) |
| |
| # https://edk2-docs.gitbook.io/edk-ii-build-specification/4_edk_ii_build_process_overview/43_pre-build_stage_overview |
| # |
| # This copy happens as part of a `source edksetup.sh`, which is not |
| # recipe-kosher - so we copy them manually. |
| for basename in ["build_rule", "tools_def", "target"]: |
| src = base_tools_dir.join("Conf", basename + ".template") |
| dest = checkout_dir.join("Conf", basename + ".txt") |
| api.file.copy("install %s" % api.path.basename(dest), src, dest) |
| |
| with api.context( |
| cwd=checkout_dir, |
| env={ |
| "WORKSPACE": checkout_dir, |
| "EDK_TOOLS_PATH": base_tools_dir, |
| "PYTHON_COMMAND": "vpython3", |
| # Annoyingly, GCC5_AARCH64_PREFIX is an environment variable that |
| # can be set by the user, but GCC5_X64_PREFIX is not and is defined |
| # in terms of a GCC5_BIN one. |
| "GCC5_AARCH64_PREFIX": "aarch64-elf-", |
| "GCC5_BIN": "x86_64-elf-", |
| }, |
| env_prefixes={ |
| "PATH": [ |
| base_tools_dir.join("BinWrappers", "PosixLike"), |
| bin_dir, |
| ], |
| }, |
| ): |
| staging_dir = api.path.mkdtemp("staging") |
| build_dir = checkout_dir.join("Build") |
| for pkg in Packages(checkout_dir): |
| with api.step.nest("build %s" % pkg.name): |
| api.step( |
| "build", |
| [ |
| "build", |
| "--verbose", |
| "--debug=%d" % DEBUG_LEVEL, |
| "--tagname=%s" % TOOLCHAIN_TAGNAME, |
| "--buildtarget=%s" % BUILDTARGET, |
| "--arch=%s" % pkg.arch, |
| "--platform=%s" % pkg.platform, |
| "-n", |
| api.platform.cpu_count, |
| "--define=NETWORK_IP6_ENABLE", |
| "--define=NETWORK_HTTP_BOOT_ENABLE", |
| ], |
| ) |
| |
| # Where the corresponding firmware volume images are found. |
| fv_dir = build_dir.join( |
| pkg.build_subdir, |
| "%s_%s" % (BUILDTARGET, TOOLCHAIN_TAGNAME), |
| "FV", |
| ) |
| dest_dir = staging_dir.join(pkg.staging_subdir) |
| api.file.copytree( |
| "stage", |
| fv_dir, |
| dest_dir, |
| ) |
| for constraint in pkg.size_constraints: |
| # Sadly, we cannot just use api.file.truncate() directly, |
| # as that overwrites the provided file with a new one. |
| api.python3( |
| "resize %s/%s" % (pkg.staging_subdir, constraint.volume), |
| [ |
| api.resource("resize.py"), |
| "--file", |
| dest_dir.join(constraint.volume), |
| "--size-mb", |
| constraint.size_mb, |
| ], |
| ) |
| |
| api.file.copy( |
| "stage License.txt", |
| checkout_dir.join("License.txt"), |
| staging_dir.join("License.txt"), |
| ) |
| |
| # Unconditionally upload to CAS; this gives a simple view into the layout |
| # of what might eventually be uploaded to CIPD. |
| api.cas_util.upload(staging_dir, output_property="isolated") |
| |
| if not api.buildbucket_util.is_tryjob: |
| api.cipd_util.upload_package( |
| "fuchsia/third_party/edk2", |
| staging_dir, |
| search_tag={ |
| "git_revision": revision + (",%s" % RECIPE_SALT if RECIPE_SALT else "") |
| }, |
| repository=EDK2_REPO, |
| ) |
| |
| |
| def GenTests(api): |
| yield api.buildbucket_util.test("ci") |
| yield api.buildbucket_util.test("cq", tryjob=True) |