blob: 5e2b53cc35051a3cf9dc909d0228dee69b754aab [file] [log] [blame]
# 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)