blob: e4714037902f0093a12ad8922d0dd450b90de828 [file] [log] [blame]
# Copyright 2017 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 QEMU."""
import shlex
from PB.go.chromium.org.luci.common.proto.srcman.manifest import Manifest
from PB.recipes.fuchsia.qemu import InputProperties
DEPS = [
"fuchsia/cas_util",
"fuchsia/cipd_util",
"fuchsia/cmake",
"fuchsia/git_checkout",
"fuchsia/goma",
"fuchsia/linux_sdk",
"fuchsia/macos_sdk",
"fuchsia/ninja",
"fuchsia/platform_util",
"fuchsia/toolchain",
"fuchsia/zlib",
"recipe_engine/cipd",
"recipe_engine/context",
"recipe_engine/file",
"recipe_engine/path",
"recipe_engine/platform",
"recipe_engine/properties",
"recipe_engine/step",
]
PROPERTIES = InputProperties
def RunSteps(api, props):
manifest = Manifest()
checkout_dir, revision = api.git_checkout(
props.remote,
fallback_ref=props.ref or "refs/heads/master",
submodules=True,
config={
"url.https://qemu.googlesource.com/.insteadOf": "https://gitlab.com/qemu-project/"
},
)
qemu_version = api.file.read_text(
"read qemu version", checkout_dir / "VERSION"
).strip()
git_checkout = manifest.directories[str(checkout_dir)].git_checkout
git_checkout.repo_url = props.remote
git_checkout.revision = revision
host_platform = api.platform_util.host
target_platform = api.platform_util.platform(
props.platform or host_platform.platform
)
with api.step.nest("ensure_packages"), api.context(infra_steps=True):
cipd_dir = api.path.start_dir / "cipd"
pkgs = api.cipd.EnsureFile()
pkgs.add_package("fuchsia/third_party/clang/${platform}", "integration")
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/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")
pkgs.add_package(
"fuchsia/third_party/source/gettext",
"version:0.19.8.1",
"source/gettext",
)
api.cipd.ensure(cipd_dir, pkgs)
pkg_dir = api.path.start_dir / "pkgconfig"
api.file.ensure_directory("create pkg dir", pkg_dir)
variables = {
"PKG_CONFIG_PATH": "",
# TODO: We should be using the following but that breaks the SDL dependency.
# "PKG_CONFIG_SYSROOT_DIR": platform_sysroot(api, target_platform),
"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")),
]
),
}
with api.linux_sdk(), api.macos_sdk(), api.context(
env=variables,
env_prefixes={"PATH": [cipd_dir.joinpath("bin"), pkg_dir.joinpath("bin")]},
):
with api.step.nest("meson"):
build_meson(api, pkg_dir)
if target_platform.is_linux:
with api.step.nest("sdl"):
build_sdl(
api,
cipd_dir,
pkg_dir,
target_platform,
host_platform,
props.prod,
manifest,
)
with api.step.nest("zlib"):
api.zlib.build(
cmake_extra_args=cmake_args(api, cipd_dir, target_platform)
+ [
"-DCMAKE_INSTALL_PREFIX=%s" % pkg_dir,
],
platform=target_platform,
)
with api.step.nest("bzip2"):
build_bzip2(
api,
cipd_dir,
pkg_dir,
target_platform,
host_platform,
props.prod,
manifest,
)
with api.step.nest("pixman"):
build_pixman(
api,
cipd_dir,
pkg_dir,
target_platform,
host_platform,
props.prod,
manifest,
)
with api.step.nest("libffi"):
build_libffi(
api,
cipd_dir,
pkg_dir,
target_platform,
host_platform,
props.prod,
manifest,
)
if target_platform.is_mac:
with api.step.nest("gettext"):
build_gettext(
api,
cipd_dir,
pkg_dir,
target_platform,
host_platform,
props.prod,
manifest,
)
with api.step.nest("pcre2"):
build_pcre2(
api,
cipd_dir,
pkg_dir,
target_platform,
host_platform,
props.prod,
manifest,
)
with api.step.nest("glib"):
build_glib(
api,
cipd_dir,
pkg_dir,
target_platform,
host_platform,
props.prod,
manifest,
)
with api.step.nest("libslirp"):
build_libslirp(
api,
cipd_dir,
pkg_dir,
target_platform,
host_platform,
props.prod,
manifest,
)
with api.step.nest("qemu"):
install_dir = build_qemu(
api,
checkout_dir,
pkg_dir,
cipd_dir,
target_platform,
host_platform,
props.prod,
)
api.file.write_proto(
"source manifest", install_dir / "source_manifest.json", manifest, "JSONPB"
)
cas_digest = api.cas_util.upload(install_dir, output_property="isolated")
if props.prod:
api.cipd_util.upload_package(
f"fuchsia/third_party/qemu/{target_platform.platform}",
install_dir,
search_tag={"git_revision": revision},
repository=props.remote,
metadata=[("version", qemu_version)],
)
if props.builders and target_platform.platform in props.builders:
# Do a full integration build. This will use the just-built QEMU to
# test against a representative set of Fuchsia builders to smoke test
# for any regressions.
api.toolchain.trigger_build(
"qemu",
props.remote,
revision,
cas_digest,
builders=props.builders[target_platform.platform],
)
def cmake_args(api, cipd_dir, platform):
# TODO(b/270963251): Enable optimizations for cmake.
args = [
f"-DCMAKE_MAKE_PROGRAM={api.ninja.path}",
f"-DCMAKE_C_COMPILER={cipd_dir.joinpath('bin', 'clang')}",
f"-DCMAKE_C_COMPILER_TARGET={platform.triple}",
f"-DCMAKE_CXX_COMPILER={cipd_dir.joinpath('bin', 'clang++')}",
f"-DCMAKE_CXX_COMPILER_TARGET={platform.triple}",
f"-DCMAKE_SYSROOT={platform_sysroot(api, platform)}",
"-DCMAKE_INSTALL_PREFIX=",
f"-DCMAKE_LINKER={'/usr/bin/ld' if platform.is_mac else cipd_dir.joinpath('bin', 'ld.lld')}",
f"-DCMAKE_NM={cipd_dir.joinpath('bin', 'llvm-nm')}",
f"-DCMAKE_OBJCOPY={cipd_dir.joinpath('bin', 'llvm-objcopy')}",
f"-DCMAKE_OBJDUMP={cipd_dir.joinpath('bin', 'llvm-objdump')}",
f"-DCMAKE_RANLIB={cipd_dir.joinpath('bin', 'llvm-ranlib')}",
f"-DCMAKE_STRIP={cipd_dir.joinpath('bin', 'llvm-strip')}",
]
return args
def platform_sysroot(api, platform):
if platform.is_linux:
return api.linux_sdk.sysroot
if platform.is_mac:
return api.macos_sdk.sysroot
raise api.step.StepFailure("supported platform") # pragma: no cover
def environment(
api, cipd_dir, platform, host, optimize, cflags=(), cxxflags=(), ldflags=()
):
sysroot = [f"--sysroot={str(platform_sysroot(api, platform)).replace('%', '%%')}"]
target = (
[f"--target={platform.triple}"] if platform.platform != host.platform else []
)
opt = []
if optimize:
opt = ["-O3"]
# TODO: Enable LTO for Mac targets.
#
# On Mac this currently fails basic Make compile checks
# configure: error: in `/opt/s/w/ir/x/w/make':
# configure: error: C compiler cannot create executables
if platform.is_linux:
opt.extend(["-flto", "-fwhole-program-vtables"])
variables = {
"CC": cipd_dir.joinpath("bin", "clang"),
"CXX": cipd_dir.joinpath("bin", "clang++"),
"LD": cipd_dir.joinpath("bin", "ld.lld"),
"CFLAGS": " ".join(sysroot + target + opt + list(cflags)),
"CPPFLAGS": " ".join(sysroot + target + opt + list(cflags)),
"CXXFLAGS": " ".join(sysroot + target + opt + list(cxxflags)),
"LDFLAGS": " ".join(sysroot + target + opt + list(ldflags)),
"AR": cipd_dir.joinpath("bin", "llvm-ar"),
"RANLIB": cipd_dir.joinpath("bin", "llvm-ranlib"),
"NM": cipd_dir.joinpath("bin", "llvm-nm"),
"STRIP": cipd_dir.joinpath("bin", "llvm-strip"),
"OBJCOPY": cipd_dir.joinpath("bin", "llvm-objcopy"),
# The context API interprets bare percent signs as formatting
# directives so they must be escaped.
"NINJA": str(api.ninja.path).replace("%", "%%"),
}
return variables
def configure(
api,
cipd_dir,
src_dir,
platform,
host,
optimize,
flags=(),
cflags=(),
cxxflags=(),
ldflags=(),
step_name="configure",
):
variables = environment(
api, cipd_dir, platform, host, optimize, cflags, cxxflags, ldflags
)
if platform.platform != host.platform:
flags.extend(
[
# config.sub used by QEMU doesn't yet recognize arm64-apple-darwin which
# is used by Clang, so replace it with aarch64-apple-darwin to make the
# script not fail.
f"--build={host.triple.replace('arm64-apple-darwin', 'aarch64-apple-darwin')}",
f"--host={platform.triple.replace('arm64-apple-darwin', 'aarch64-apple-darwin')}",
]
)
return api.step(
step_name,
[src_dir / "configure"]
+ flags
+ sorted(f"{k}={v}" for k, v in variables.items()),
)
def meson(
api, cipd_dir, platform, _host, _optimize, cflags=(), cxxflags=(), ldflags=()
):
cross_file = api.path.start_dir.joinpath("%s.txt" % platform.triple)
api.file.write_text(
"write cross-file",
cross_file,
f"""
[constants]
sysroot = '--sysroot=' + '{str(platform_sysroot(api, platform)).replace("%", "%%")}'
target = '--target={platform.triple}'
[built-in options]
buildtype = 'release'
c_args = [sysroot, target] + [{", ".join("'%s'" % f for f in cflags)}]
cpp_args = [sysroot, target] + [{", ".join("'%s'" % f for f in cxxflags)}]
c_link_args = c_args + [{", ".join("'%s'" % f for f in ldflags)}]
cpp_link_args = cpp_args + [{", ".join("'%s'" % f for f in ldflags)}]
default_library = 'static'
includedir = 'include'
libdir = 'lib'
# sys_root = /some/path
# pkg_config_libdir = /some/path
[binaries]
c = '{str(cipd_dir.joinpath("bin", "clang"))}'
c_ld = 'lld'
objc = '{str(cipd_dir.joinpath("bin", "clang"))}'
objc_ld = 'lld'
cpp = '{str(cipd_dir.joinpath("bin", "clang++"))}'
cpp_ld = 'lld'
ar = '{str(cipd_dir.joinpath("bin", "llvm-ar"))}'
strip = '{str(cipd_dir.joinpath("bin", "llvm-strip"))}'
pkgconfig = '{str(cipd_dir.joinpath("bin", "pkg-config"))}'
# exe_wrapper = 'qemu-aarch64'
[host_machine]
system = '{platform.os.replace('mac', 'darwin')}'
cpu_family = '{platform.arch.replace('amd64', 'x86_64').replace('arm64', 'aarch64')}'
cpu = cpu_family
endian = 'little'
""",
)
# Pass as `--native-file` when not cross-compiling
return cross_file
def build_meson(api, pkg_dir):
MESON_GIT = "https://qemu.googlesource.com/meson"
src_dir, _ = api.git_checkout(MESON_GIT, revision="refs/tags/1.2.1")
bin_dir = pkg_dir / "bin"
api.file.ensure_directory("bin", bin_dir)
outfile = bin_dir / "meson.pyz"
api.step(
"create zipapp",
[
"vpython3",
src_dir.joinpath("packaging", "create_zipapp.py"),
"--outfile",
outfile,
"--interpreter",
"/usr/bin/env vpython3",
src_dir,
],
)
return outfile
def build_sdl(api, cipd_dir, pkg_dir, platform, _host, _optimize, manifest):
SDL_GIT = "https://fuchsia.googlesource.com/third_party/sdl"
src_dir, revision = api.git_checkout(SDL_GIT, revision="refs/tags/release-2.26.5")
git_checkout = manifest.directories[str(src_dir)].git_checkout
git_checkout.repo_url = SDL_GIT
git_checkout.revision = revision
api.cmake.build_with_ninja(
src_dir=src_dir,
extra_args=cmake_args(api, cipd_dir, platform)
+ [
"-DBUILD_SHARED_LIBS=OFF",
"-DCMAKE_INSTALL_PREFIX=%s" % pkg_dir,
"-DSDL_GCC_ATOMICS=ON",
"-DSDL_STATIC_PIC=ON",
"-DSDL_TEST=OFF",
"-DSDL_WAYLAND=OFF",
],
)
def build_bzip2(api, cipd_dir, pkg_dir, platform, host, optimize, manifest):
BZIP2_GIT = "https://fuchsia.googlesource.com/third_party/bzip2"
src_dir, revision = api.git_checkout(BZIP2_GIT, revision="refs/heads/main")
git_checkout = manifest.directories[str(src_dir)].git_checkout
git_checkout.repo_url = BZIP2_GIT
git_checkout.revision = revision
build_dir = api.path.start_dir / "bzip2"
api.file.ensure_directory("bzip2", build_dir)
with api.context(cwd=src_dir):
# First build for host.
env = environment(api, cipd_dir, host, host, optimize)
var = ["%s=%s" % (k, v) for k, v in env.items()]
api.step(
"build (host)",
["make", f"-j{int(api.goma.jobs)}", "install", f"PREFIX={pkg_dir}", *var],
)
api.step("clean", ["make", "clean"])
# Second build for target.
env = environment(api, cipd_dir, platform, host, optimize)
var = ["%s=%s" % (k, v) for k, v in env.items()]
api.step(
"build (target)",
["make", f"-j{int(api.goma.jobs)}", "install", f"PREFIX={build_dir}", *var],
)
api.file.copy(
"copy libbz2.a",
build_dir.joinpath("lib", "libbz2.a"),
pkg_dir.joinpath("lib"),
)
def build_pixman(api, cipd_dir, pkg_dir, platform, host, optimize, manifest):
PIXMAN_GIT = "https://fuchsia.googlesource.com/third_party/pixman"
src_dir, revision = api.git_checkout(
PIXMAN_GIT, revision="593a970266042d702c1577fdec8c33449f60b628"
)
git_checkout = manifest.directories[str(src_dir)].git_checkout
git_checkout.repo_url = PIXMAN_GIT
git_checkout.revision = revision
build_dir = api.path.start_dir / "pixman"
api.file.ensure_directory("pixman", build_dir)
with api.context(
cwd=src_dir, env=environment(api, cipd_dir, platform, host, False)
):
try:
cross_file = meson(api, cipd_dir, platform, host, optimize)
api.step(
"setup",
[
pkg_dir.joinpath("bin", "meson.pyz"),
"setup",
"--default-library=static",
"--prefix=%s" % pkg_dir,
"--includedir=include",
"--libdir=lib",
"--buildtype=release",
"--wrap-mode=nofallback",
"--cross-file=%s" % cross_file,
]
+ (["-Db_lto=true"] if optimize else [])
+ [
build_dir,
],
)
finally:
try:
api.file.read_text(
"meson-log.txt",
build_dir.joinpath("meson-logs", "meson-log.txt"),
test_data="The Meson build system\nVersion: 1.0.0",
)
except api.step.StepFailure: # pragma: no cover
pass
api.ninja("install", ["install", "-v"], build_dir=build_dir)
def build_libffi(api, cipd_dir, pkg_dir, platform, host, optimize, manifest):
# TODO(phosek): Newer versions require autoconf >=2.71.
LIBFFI_GIT = "https://fuchsia.googlesource.com/third_party/libffi"
src_dir, revision = api.git_checkout(LIBFFI_GIT, revision="refs/tags/v3.4.2")
git_checkout = manifest.directories[str(src_dir)].git_checkout
git_checkout.repo_url = LIBFFI_GIT
git_checkout.revision = revision
build_dir = api.path.start_dir / "libffi"
api.file.ensure_directory("libffi", build_dir)
with api.context(cwd=src_dir, env={"M4": cipd_dir.joinpath("bin", "m4")}):
api.step("autogen", [src_dir / "autogen.sh"])
with api.context(cwd=build_dir):
try:
configure(
api,
cipd_dir,
src_dir,
platform,
host,
optimize,
[
"--disable-debug",
"--disable-dependency-tracking",
"--disable-docs",
"--disable-shared",
"--enable-static",
"--prefix=%s" % pkg_dir,
"--target=%s"
# config.sub used by libffi doesn't yet recognize arm64-apple-darwin which
# is used by Clang, so replace it with aarch64-apple-darwin to make the
# script not fail.
% platform.triple.replace(
"arm64-apple-darwin", "aarch64-apple-darwin"
),
"--with-pic",
],
)
finally:
try:
api.file.read_text(
"config.log",
build_dir / "config.log",
test_data="# libffi configure log",
)
except api.step.StepFailure: # pragma: no cover
pass
api.step("build", ["make", f"-j{int(api.goma.jobs)}"])
api.step("install", ["make", "install"]) # , f"DESTDIR={pkg_dir}"])
def build_gettext(api, cipd_dir, pkg_dir, platform, host, optimize, _manifest):
# Build gettext-{runtime,tools} separately, when cross-compiling we need to
# always build `tools` for the host and the `runtime` for the target. This
# is how the PACKAGING file recommends we split them.
#
# The order of tools, runtime is important here; the tools build will
# overwrite the runtimes installation.
for package in ["gettext-tools", "gettext-runtime"]:
with api.step.nest(package):
src_dir = cipd_dir.joinpath("source", "gettext", package)
build_dir = api.path.start_dir.joinpath("gettext", package)
api.file.ensure_directory("gettext", build_dir)
with api.context(cwd=build_dir):
configure(
api,
cipd_dir,
src_dir,
platform if package == "gettext-runtime" else host,
host,
optimize,
[
"--disable-curses",
"--disable-dependency-tracking",
"--disable-silent-rules",
"--disable-debug",
"--disable-shared",
"--disable-java",
"--disable-csharp",
"--disable-c++",
"--disable-openmp",
"--enable-static",
"--prefix=%s" % pkg_dir,
"--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"],
)
api.step("build", ["make", f"-j{int(api.goma.jobs)}"])
api.step("install", ["make", "install"])
def build_pcre2(api, cipd_dir, pkg_dir, platform, _host, _optimize, manifest):
# TODO(phosek): We should be using our mirror.
PCRE2_GIT = "https://github.com/PCRE2Project/pcre2"
src_dir, revision = api.git_checkout(PCRE2_GIT, revision="refs/tags/pcre2-10.42")
git_checkout = manifest.directories[str(src_dir)].git_checkout
git_checkout.repo_url = PCRE2_GIT
git_checkout.revision = revision
api.cmake.build_with_ninja(
src_dir=src_dir,
extra_args=cmake_args(api, cipd_dir, platform)
+ [
"-DBUILD_SHARED_LIBS=OFF",
"-DCMAKE_INSTALL_PREFIX=%s" % pkg_dir,
],
)
def build_glib(api, cipd_dir, pkg_dir, platform, host, optimize, manifest):
GLIB_GIT = "https://fuchsia.googlesource.com/third_party/glib"
src_dir, revision = api.git_checkout(
GLIB_GIT,
revision="refs/tags/2.77.0",
submodules=True,
config={
# TODO(phosek): We should be using our mirror.
"url.https://gitlab.gnome.org/GNOME/gvdb.insteadOf": "https://fuchsia.googlesource.com/GNOME/gvdb"
},
)
git_checkout = manifest.directories[str(src_dir)].git_checkout
git_checkout.repo_url = GLIB_GIT
git_checkout.revision = revision
build_dir = api.path.start_dir / "glib"
api.file.ensure_directory("glib", build_dir)
with api.context(
cwd=src_dir,
env=environment(
api,
cipd_dir,
platform,
host,
optimize,
cflags=["-I%s" % pkg_dir.joinpath("include")],
ldflags=["-L%s" % pkg_dir.joinpath("lib")],
),
):
try:
cross_file = meson(api, cipd_dir, platform, host, optimize)
api.step(
"setup",
[
pkg_dir.joinpath("bin", "meson.pyz"),
"setup",
"--default-library=static",
"--prefix=%s" % pkg_dir,
"--includedir=include",
"--libdir=lib",
"--buildtype=release",
# "--wrap-mode=nofallback",
"--cross-file=%s" % cross_file,
build_dir,
],
)
finally:
try:
api.file.read_text(
"meson-log.txt",
build_dir.joinpath("meson-logs", "meson-log.txt"),
test_data="The Meson build system\nVersion: 1.0.0",
)
except api.step.StepFailure: # pragma: no cover
pass
api.ninja("install", ["install"], build_dir=build_dir)
def build_libslirp(api, cipd_dir, pkg_dir, platform, host, optimize, manifest):
LIBSLIRP_GIT = "https://qemu.googlesource.com/libslirp"
src_dir, revision = api.git_checkout(LIBSLIRP_GIT, revision="refs/tags/v4.7.0")
git_checkout = manifest.directories[str(src_dir)].git_checkout
git_checkout.repo_url = LIBSLIRP_GIT
git_checkout.revision = revision
build_dir = api.path.start_dir / "libslirp"
api.file.ensure_directory("libslirp", build_dir)
with api.context(
cwd=src_dir, env=environment(api, cipd_dir, platform, host, optimize)
):
try:
cross_file = meson(api, cipd_dir, platform, host, optimize)
api.step(
"setup",
[
pkg_dir.joinpath("bin", "meson.pyz"),
"setup",
"--default-library=static",
"--prefix=%s" % pkg_dir,
"--includedir=include",
"--libdir=lib",
"--buildtype=release",
"--wrap-mode=nofallback",
"--cross-file=%s" % cross_file,
build_dir,
],
)
finally:
try:
api.file.read_text(
"meson-log.txt",
build_dir.joinpath("meson-logs", "meson-log.txt"),
test_data="The Meson build system\nVersion: 1.0.0",
)
except api.step.StepFailure: # pragma: no cover
pass
api.ninja("install", ["install"], build_dir=build_dir)
def build_qemu(
api,
checkout_dir,
pkg_dir,
cipd_dir,
target_platform,
host_platform,
optimize,
):
"""
Build QEMU.
Args:
checkout_dir (Path): Path to QEMU Git repository.
cipd_dir (Path): Path to prepopulated CIPD package dependencies.
target_platform (Platform): Target platform.
host_platform (Platform): Host platform.
Returns:
Path: Path to install directory.
"""
build_dir = api.path.start_dir.joinpath("qemu", "build")
api.file.ensure_directory("qemu/build", build_dir)
install_dir = api.path.start_dir.joinpath("qemu", "install")
api.file.ensure_directory("qemu/install", install_dir)
sysroot = [
f"--sysroot={str(platform_sysroot(api, target_platform)).replace('%', '%%')}"
]
target = (
[f"--target={target_platform.triple}"]
if target_platform.platform != host_platform.platform
else []
)
# TODO: QEMU builds fails to find zlib headers without an explicit -I which
# is a bug.
includes = ["-I%s" % pkg_dir.joinpath("include")]
opt = [
# TODO: Remove these once we upgrade the host Linux sysroot.
"-DHWCAP_ATOMICS=256",
"-DHWCAP_USCAT=33554432",
]
if target_platform.arch == "amd64":
opt.append("-mcx16")
variables = {
"AR": cipd_dir.joinpath("bin", "llvm-ar"),
"RANLIB": cipd_dir.joinpath("bin", "llvm-ranlib"),
"NM": cipd_dir.joinpath("bin", "llvm-nm"),
"STRIP": cipd_dir.joinpath("bin", "llvm-strip"),
"OBJCOPY": cipd_dir.joinpath("bin", "llvm-objcopy"),
"QEMU_PKG_CONFIG_FLAGS": "--static",
"CFLAGS": " ".join(sysroot + opt + target + includes),
"CXXFLAGS": " ".join(sysroot + opt + target + includes),
"OBJCFLAGS": " ".join(sysroot + opt + target + includes),
"NINJA": str(api.ninja.path).replace("%", "%%"),
}
if target_platform.is_linux:
variables["LDFLAGS"] = " ".join(
sysroot + target + opt + ["-ldl -static-libstdc++ -Qunused-arguments"]
)
if target_platform.is_mac:
variables["LDFLAGS"] = " ".join(
sysroot + target + ["-nostdlib++ %s" % cipd_dir.joinpath("lib", "libc++.a")]
)
# NOTE: We don't want to pass --static to configure since we still link libc
# as a shared library, but we want everything else to use static linking.
configure_options = []
meson_options = []
if target_platform.is_linux:
configure_options.extend(
[
f"--build={host_platform.triple}",
f"--host={target_platform.triple}",
f"--extra-cflags={' '.join(sysroot + opt + target + includes)}",
f"--extra-cxxflags={' '.join(sysroot + opt + target + includes)}",
# Suppress warning about the unused arguments because QEMU ignores
# --disable-werror at configure time which triggers an error because
# -static-libstdc++ is unused when linking C code.
f"--extra-ldflags={' '.join(sysroot + target + opt + ['-ldl -static-libstdc++ -Qunused-arguments'])}",
]
)
meson_options.extend(
[
"-Dkvm=enabled",
"-Dgtk=disabled",
"-Dsdl=enabled",
]
)
if target_platform.is_mac:
configure_options.extend(
[
f"--objcc={cipd_dir.joinpath('bin', 'clang')}",
"--enable-cocoa",
f"--extra-cflags={' '.join(sysroot + opt + target + includes)}",
f"--extra-cxxflags={' '.join(sysroot + opt + target + includes)}",
f"--extra-objcflags={' '.join(sysroot + opt + target + includes)}",
"--extra-ldflags=%s"
% " ".join(
sysroot
+ target
+ ["-nostdlib++ %s" % cipd_dir.joinpath("lib", "libc++.a")]
),
]
)
meson_options.extend(
[
"-Dcocoa=enabled",
"-Dhvf=enabled",
]
)
# TODO: Use `meson subprojects` to download all subprojects and override their URL
# to point to https://qemu.googlesource.com/.
with api.context(cwd=build_dir, env=variables):
try:
api.step(
"configure qemu",
[
checkout_dir / "configure",
f"--cc={cipd_dir.joinpath('bin', 'clang')}",
# See function docstring for justification and associated bug.
f"--cxx={wrap_clang(api, cipd_dir.joinpath('bin', 'clang++'))}",
f"--ninja={api.ninja.path}",
f"--prefix={install_dir}",
"--target-list=aarch64-softmmu,arm-softmmu,riscv64-softmmu,x86_64-softmmu",
"--skip-meson",
]
+ configure_options,
)
finally:
try:
api.file.read_text(
"config.log",
build_dir / "config.log",
test_data="# QEMU configure log",
)
except api.step.StepFailure: # pragma: no cover
pass
with api.context(cwd=checkout_dir, env=variables):
try:
cross_file = meson(
api,
cipd_dir,
target_platform,
host_platform,
optimize,
cflags=opt,
cxxflags=opt,
)
api.step(
"setup",
[
pkg_dir.joinpath("bin", "meson.pyz"),
"setup",
"--default-library=static",
"--prefix=%s" % install_dir,
"--includedir=include",
"--libdir=lib",
"--buildtype=release",
"--wrap-mode=nofallback",
"--cross-file=%s" % cross_file,
"-Dattr=disabled",
"-Dauth_pam=disabled",
"-Dbochs=disabled",
"-Dbrlapi=disabled",
"-Dcap_ng=disabled",
"-Dcapstone=disabled",
"-Dcloop=disabled",
"-Dcrypto_afalg=disabled",
"-Dcurl=disabled",
"-Dcurses=disabled",
"-Ddbus_display=disabled",
"-Ddebug_graph_lock=false",
"-Ddebug_mutex=false",
"-Ddebug_stack_usage=false",
"-Ddmg=disabled",
"-Ddocs=disabled",
"-Dfdt=internal",
"-Dgcrypt=disabled",
"-Dglusterfs=disabled",
"-Dgnutls=disabled",
"-Dguest_agent=disabled",
"-Diconv=disabled",
"-Dlibdaxctl=disabled",
"-Dlibiscsi=disabled",
"-Dlibnfs=disabled",
"-Dlibpmem=disabled",
"-Dlibssh=disabled",
"-Dlibudev=disabled",
"-Dlibusb=disabled",
"-Dlinux_aio=disabled",
"-Dlinux_io_uring=disabled",
"-Dlzfse=disabled",
"-Dlzo=disabled",
"-Dmpath=disabled",
"-Dmultiprocess=disabled",
"-Dnetmap=disabled",
"-Dnettle=disabled",
"-Dnuma=disabled",
"-Dnvmm=disabled",
"-Dopengl=disabled",
"-Dparallels=disabled",
"-Dqcow1=disabled",
"-Dqed=disabled",
"-Dqga_vss=disabled",
"-Dqom_cast_debug=false",
"-Drbd=disabled",
"-Drdma=disabled",
"-Dreplication=disabled",
"-Dsdl_image=disabled",
"-Dseccomp=disabled",
"-Dsmartcard=disabled",
"-Dsnappy=disabled",
"-Dsparse=disabled",
"-Dspice=disabled",
"-Dtcg=enabled",
"-Dtcg_interpreter=false",
"-Dtpm=disabled",
"-Dusb_redir=disabled",
"-Dvde=disabled",
"-Dvdi=disabled",
"-Dvirglrenderer=disabled",
"-Dvirtfs=disabled",
"-Dvnc=disabled",
"-Dvte=disabled",
"-Dvvfat=enabled",
"-Dwhpx=disabled",
"-Dxen=disabled",
"-Dxkbcommon=disabled",
]
+ meson_options
+ (["-Db_lto=true"] if optimize else [])
+ [
build_dir,
],
)
finally:
try:
api.file.read_text(
"meson-log.txt",
build_dir.joinpath("meson-logs", "meson-log.txt"),
test_data="The Meson build system\nVersion: 1.0.0",
)
except api.step.StepFailure: # pragma: no cover
pass
# config-host.h is a generated header consisting of #-defines detailing
# configuration settings, including whether certain features are
# enabled. It serves as an important diagnostic.
config_host = build_dir / "config-host.h"
config = api.file.read_text(
"config-host.h",
config_host,
test_data="#pragma once\n#define CONFIG_FOO\n#undef CONFIG_BAR",
)
if target_platform.is_linux:
# <sys/auxv.h> in glibc 2.27 doesn't define HWCAP_USCAT which breaks the
# build, disable getauxval to force the use of <asm/hwcap.h>.
api.file.write_text(
"update config-host.h",
config_host,
config.replace("#define CONFIG_GETAUXVAL", "#undef CONFIG_GETAUXVAL"),
)
api.step(
"compile",
[
pkg_dir.joinpath("bin", "meson.pyz"),
"compile",
"-C",
build_dir,
],
)
api.step(
"install",
[
pkg_dir.joinpath("bin", "meson.pyz"),
"install",
"-C",
build_dir,
"--skip-subprojects",
],
)
# TODO(fxbug.dev/106763): Re-enable tests when passing again.
return install_dir
# TODO(https://github.com/mesonbuild/meson/issues/8592): Meson links against
# libstdc++ by default when using clang. We use libc++ and so need to filter
# that out.
def wrap_clang(api, clang):
contents = (
"""#!/bin/sh
args=("$@")
args=("${args[@]/-lstdc++}")
exec %s "${args[@]}"
"""
% clang
)
wrapper = api.path.tmp_base_dir / "clang-wrapper.sh"
api.file.write_text(f"write {api.path.basename(wrapper)}", wrapper, contents)
api.step(
f"make {api.path.basename(wrapper)} executable",
["chmod", "+x", wrapper],
)
return wrapper
def GenTests(api):
version = "8.0.50"
def properties(platform, prod=True, builders=None):
return api.properties(
remote="https://fuchsia.googlesource.com/third_party/qemu",
platform=platform,
prod=prod,
builders=builders,
)
yield (
api.test("linux-amd64")
+ api.platform.name("linux")
+ properties(
"linux-amd64",
builders={
"linux-amd64": ["fuchsia/linux-x64-builder"],
"mac-amd64": ["fuchsia/mac-x64-builder"],
},
)
+ api.step_data("read qemu version", api.file.read_text(version))
)
yield (
api.test("mac-amd64")
+ api.platform.name("mac")
+ properties("mac-amd64")
+ api.step_data("read qemu version", api.file.read_text(version))
)
yield (
api.test("linux-arm64")
+ api.platform.name("linux")
+ properties("linux-arm64", prod=False)
+ api.step_data("read qemu version", api.file.read_text(version))
)