| # 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 = "" |
| |
| EDK2_REPO = "https://fuchsia.googlesource.com/third_party/edk2" |
| EDK2_REF = "refs/tags/edk2-stable202308" |
| |
| LIBUUID_REPO = "https://kernel.googlesource.com/pub/scm/utils/util-linux/util-linux" |
| LIBUUID_REF = "refs/tags/v2.39.2" |
| |
| ACPICA_REPO = "https://fuchsia.googlesource.com/third_party/acpica" |
| ACPICA_REF = "refs/heads/upstream/master" |
| |
| # |
| # Build flags, as they are spelled by the project. |
| # |
| |
| # 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 = "GCC" |
| |
| 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 RunSteps(api): |
| checkout_dir, revision = api.git_checkout( |
| EDK2_REPO, fallback_ref=EDK2_REF, submodules=True, recursive=True |
| ) |
| |
| # TODO(joshuaseaton): Report or fix bugs upstream where possible. |
| with api.context(cwd=checkout_dir): |
| api.git.apply_patchfile(api.resource("ld-notext.patch")) |
| api.git.apply_patchfile(api.resource("no-maybe-uninitialized.patch")) |
| |
| # NOTE: Ideally we would use the GCCNOLTO toolchain, but there is |
| # no RISCV64 version of that yet. |
| api.git.apply_patchfile(api.resource("no-lto.patch")) |
| |
| cipd_dir = api.path.start_dir / "cipd" |
| with api.step.nest("ensure packages"): |
| with api.context(infra_steps=True): |
| pkgs = api.cipd.EnsureFile() |
| |
| pkgs.add_package( |
| "fuchsia/third_party/sysroot/linux", "integration", "sysroot" |
| ) |
| pkgs.add_package("fuchsia/third_party/clang/${platform}", "integration") |
| pkgs.add_package("fuchsia/third_party/gcc/${platform}", "integration") |
| |
| # autopoint is needed to build libuuid. |
| pkgs.add_package( |
| "fuchsia/third_party/source/gettext", |
| "version:0.19.8.1", |
| "source/gettext", |
| ) |
| |
| # |
| # The usual GNU tools for building gettext and libuuid. |
| # |
| pkgs.add_package("fuchsia/third_party/autoconf/${platform}", "version:2.69") |
| pkgs.add_package( |
| "fuchsia/third_party/automake/${platform}", "version:1.16.2" |
| ) |
| pkgs.add_package("fuchsia/third_party/bison/${platform}", "version:3.7") |
| pkgs.add_package("fuchsia/third_party/libtool/${platform}", "version:2.4.6") |
| pkgs.add_package("fuchsia/third_party/m4/${platform}", "version:1.4.18") |
| pkgs.add_package("fuchsia/third_party/make/${platform}", "version:4.3") |
| pkgs.add_package( |
| "fuchsia/third_party/pkg-config/${platform}", "version:0.29.2" |
| ) |
| |
| # Used in the firmware builds. |
| pkgs.add_package( |
| "fuchsia/third_party/iasl/${platform}", "version:2@2020.09.25", "bin" |
| ) |
| pkgs.add_package( |
| "fuchsia/third_party/nasm/${platform}", "version:2@2.15.05.1" |
| ) |
| |
| api.cipd.ensure(cipd_dir, pkgs) |
| |
| bin_dir = cipd_dir / "bin" |
| sysroot = cipd_dir / "sysroot" |
| |
| # |
| # We need to build BaseTools before building the firmware. |
| # |
| |
| pkg_dir = api.path.start_dir / "pkgconfig" |
| pkg_bin_dir = pkg_dir / "bin" |
| api.file.ensure_directory("create pkg directory", pkg_dir) |
| |
| with api.context( |
| env={ |
| "PKG_CONFIG_PATH": "", |
| "PKG_CONFIG_SYSROOT_DIR": sysroot, |
| "PKG_CONFIG_ALLOW_SYSTEM_CFLAGS": 0, |
| "PKG_CONFIG_ALLOW_SYSTEM_LIBS": 0, |
| "PKG_CONFIG_LIBDIR": ":".join( |
| [ |
| str(pkg_dir.joinpath("share", "pkgconfig")), |
| str(pkg_dir.joinpath("lib", "pkgconfig")), |
| str(pkg_dir.joinpath("lib64", "pkgconfig")), |
| ] |
| ), |
| # TODO(fxbug.dev/58251): remove environment variable when possible. |
| "M4": bin_dir / "m4", |
| }, |
| env_prefixes={"PATH": [bin_dir, pkg_bin_dir]}, |
| ): |
| # The BaseTools build requires libuuid, and the uuid build requires |
| # autopoint. |
| BuildGettext(api, cipd_dir / "source/gettext", pkg_dir, sysroot) |
| BuildUuid(api, pkg_dir, sysroot) |
| |
| base_tools_dir = checkout_dir / "BaseTools" |
| optflags = [ |
| f"--sysroot={sysroot}", |
| f"-I{pkg_dir / 'include'}", |
| "-Wno-deprecated-non-prototype", |
| ] |
| ldflags = [ |
| f"--sysroot={sysroot}", |
| f"-L{pkg_dir / 'lib'}", |
| ] |
| env = Environment(sysroot) |
| env.update( |
| { |
| "EXTRA_OPTFLAGS": " ".join(optflags), |
| "EXTRA_LDFLAGS": " ".join(ldflags), |
| "PYTHON_COMMAND": "vpython3", |
| } |
| ) |
| with api.context(cwd=base_tools_dir, env=env): |
| 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.joinpath("Conf", basename + ".template") |
| dest = checkout_dir.joinpath("Conf", basename + ".txt") |
| api.file.copy(f"install {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, GCC_AARCH64_PREFIX is an environment variable that |
| # can be set by the user, but GCC_X64_PREFIX is not and is defined |
| # in terms of a GCC_BIN one. |
| "GCC_AARCH64_PREFIX": "aarch64-elf-", |
| "GCC_BIN": "x86_64-elf-", |
| }, |
| env_prefixes={ |
| "PATH": [ |
| base_tools_dir.joinpath("BinWrappers", "PosixLike"), |
| bin_dir, |
| pkg_bin_dir, |
| ], |
| }, |
| ): |
| staging_dir = api.path.mkdtemp("staging") |
| build_dir = checkout_dir / "Build" |
| for pkg in Packages(checkout_dir): |
| with api.step.nest(f"build {pkg.name}"): |
| api.step( |
| "build", |
| [ |
| "build", |
| "--verbose", |
| f"--debug={int(DEBUG_LEVEL)}", |
| f"--tagname={TOOLCHAIN_TAGNAME}", |
| f"--buildtarget={BUILDTARGET}", |
| f"--arch={pkg.arch}", |
| f"--platform={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.joinpath( |
| pkg.build_subdir, |
| f"{BUILDTARGET}_{TOOLCHAIN_TAGNAME}", |
| "FV", |
| ) |
| dest_dir = staging_dir / 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( |
| f"resize {pkg.staging_subdir}/{constraint.volume}", |
| [ |
| api.resource("resize.py"), |
| "--file", |
| dest_dir / constraint.volume, |
| "--size-mb", |
| constraint.size_mb, |
| ], |
| ) |
| |
| api.file.copy( |
| "stage License.txt", |
| checkout_dir / "License.txt", |
| staging_dir / "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_dev_or_try: |
| api.cipd_util.upload_package( |
| "fuchsia/third_party/edk2", |
| staging_dir, |
| search_tag={ |
| "git_revision": revision + (f",{RECIPE_SALT}" if RECIPE_SALT else "") |
| }, |
| repository=EDK2_REPO, |
| ) |
| |
| |
| def Packages(checkout_dir): |
| return [ |
| Package( |
| name="OvmfPkg", |
| arch="X64", |
| platform=checkout_dir.joinpath("OvmfPkg", "OvmfPkgX64.dsc"), |
| build_subdir="OvmfX64", |
| staging_subdir="qemu-x64", |
| size_constraints=[], |
| ), |
| Package( |
| name="ArmVirtPkg", |
| arch="AARCH64", |
| platform=checkout_dir.joinpath("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 BuildGettext(api, src_dir, pkg_dir, sysroot): |
| with api.step.nest("build gettext"): |
| ConfigureAndMake( |
| api, |
| src_dir / "gettext-tools", |
| pkg_dir, |
| sysroot, |
| configure_flags=[ |
| "--disable-curses", |
| "--disable-dependency-tracking", |
| "--disable-silent-rules", |
| "--disable-debug", |
| "--disable-shared", |
| "--disable-java", |
| "--disable-csharp", |
| "--disable-c++", |
| "--disable-openmp", |
| "--enable-static", |
| "--with-pic", |
| "--with-included-gettext", |
| "--with-included-glib", |
| "--with-included-libcroco", |
| "--with-included-libunistring", |
| "--with-included-libxml", |
| "--without-git", |
| "--without-cvs", |
| "--without-xz", |
| ], |
| ldflags=["-lm"], |
| ) |
| |
| |
| def BuildUuid(api, pkg_dir, sysroot): |
| with api.step.nest("build uuid"): |
| src_dir, _ = api.git_checkout( |
| LIBUUID_REPO, |
| fallback_ref=LIBUUID_REF, |
| submodules=True, |
| recursive=True, |
| ) |
| |
| with api.context(cwd=src_dir): |
| api.step("autogen", ["./autogen.sh"]) |
| |
| ConfigureAndMake( |
| api, |
| src_dir, |
| pkg_dir, |
| sysroot, |
| configure_flags=[ |
| "--disable-shared", |
| "--enable-static", |
| "--disable-all-programs", |
| "--enable-libuuid", |
| "--enable-libuuid-force-uuidd", |
| ], |
| ) |
| |
| |
| # Invokes the configure script for the given source and installs it in the |
| # given package directory. |
| # |
| # Assumes that any needed tool is already available in $PATH. |
| # |
| # The configure flag `--prefix` is set by this function (to the package |
| # directory) and should not be supplied. |
| def ConfigureAndMake( |
| api, |
| src_dir, |
| pkg_dir, |
| sysroot, |
| configure_flags, |
| ldflags=[], |
| ): |
| with api.context(cwd=pkg_dir): |
| try: |
| api.step( |
| "configure", |
| [ |
| src_dir / "configure", |
| f"--prefix={pkg_dir}", |
| ] |
| + configure_flags |
| + sorted( |
| [f"{k}={v}" for k, v in Environment(sysroot, ldflags).items()] |
| ), |
| ) |
| finally: |
| api.file.read_text( |
| "config.log", |
| pkg_dir / "config.log", |
| test_data="error", |
| ) |
| api.step("make", ["make", f"-j{int(api.platform.cpu_count)}"]) |
| api.step("make install", ["make", "install"]) |
| |
| |
| def Environment(sysroot, cflags=[], ldflags=[]): |
| extra_flags = [f"--sysroot={sysroot}"] |
| return { |
| "CC": "clang", |
| "CXX": "clang++", |
| "CFLAGS": " ".join(cflags + extra_flags), |
| "CPPFLAGS": " ".join(cflags + extra_flags), |
| "CXXFLAGS": " ".join(extra_flags), |
| "LD": "clang", |
| "LDFLAGS": " ".join(extra_flags + ldflags), |
| "AR": "llvm-ar", |
| "RANLIB": "llvm-ranlib", |
| "NM": "llvm-nm", |
| "STRIP": "llvm-strip", |
| "OBJCOPY": "llvm-objcopy", |
| } |
| |
| |
| def GenTests(api): |
| yield api.buildbucket_util.test("ci") |
| yield api.buildbucket_util.test("cq", tryjob=True) |