Skip to content

Commit

Permalink
[infra] Add hermetic toolchain for C/C++ using Clang+Musl
Browse files Browse the repository at this point in the history
This can successfully build a C library:
   bazel build --config=clang //third_party:libpng

This can build and run a statically-linked executable:
   bazel test --config=clang //:bazel_test

For more verbose compile and linking output, add the
`--features diagnostic`
flag to a Bazel command (see _make_diagnostic_flags() in
toolchain/clang_toolchain_config.bzl. Similarly, a
`--features print_search_dirs` can be used to show where
clang is looking for libraries etc to link against.
These features are made available for easier debugging.

Suggested review order:
 - Read https://docs.bazel.build/versions/4.2.1/tutorial/cc-toolchain-config.html
   if unfamiliar with setting up C++ toolchains in Bazel
 - .bazelrc and WORKSPACE.bazel that configure use and download
   of the toolchain (Clang 13, musl 1.2.2)
 - toolchain/build_toolchain.bzl which downloads and assembles
   the toolchain (w/o installing anything on the host machine)
 - toolchain/BUILD.bazel and toolchain/*trampoline.sh to see
   the setup of the toolchain rules.
 - toolchain/clang_toolchain_config.bzl to see the configuration
   of the toolchain. Pay special attention to the various
   command line flags that are set.
 - See that tools/bazel_test.cc has made a new home in
   experimental/bazel_test/bazel_test.cpp, with a companion
   BUILD.bazel. Note the addition of some function calls
   that test use of the C++ standard library.
   The number being used to test the PNG library is the latest
   and greatest that verifies we are compiling the one brought
   in via DEPS (and not a local one).
 - third_party/* to see how png (and its dependent zlib) have
   been built. Pay special attention to the musl_compat hack
   to fix static linking (any idea what the real cause is?)
- //BUILD.bazel to see definition of the bazel_test executable.

Change-Id: I7b0922d0d45cb9be8df2fd5fa5a1f48492654d5f
Bug: skia:12541
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/461178
Reviewed-by: Ben Wagner <[email protected]>
Reviewed-by: Leandro Lovisolo <[email protected]>
  • Loading branch information
kjlubick committed Oct 21, 2021
1 parent 9cb2040 commit 4d41304
Show file tree
Hide file tree
Showing 15 changed files with 640 additions and 20 deletions.
1 change: 0 additions & 1 deletion .bazelignore

This file was deleted.

6 changes: 6 additions & 0 deletions .bazelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
build:clang --crosstool_top=//toolchain:clang_suite
build:clang --compiler=clang

# Use the default Bazel C++ toolchain to build the tools used during the
# build.
build:clang --host_crosstool_top=@bazel_tools//tools/cpp:toolchain
9 changes: 6 additions & 3 deletions BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
cc_test(
name = "bazel_test",
size = "small",
srcs = [
"tools/bazel_test.cc",
"//experimental/bazel_test:srcs",
],
deps = [
"//third_party:libpng",
"//third_party:musl_compat",
],
deps = ["@libpng//:libpng"],
size = "small",
)
21 changes: 13 additions & 8 deletions WORKSPACE.bazel
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
load("@bazel_tools//tools/build_defs/repo:git.bzl", "new_git_repository")
workspace(name = "skia")

new_git_repository(
name = "libpng",
remote = "https://skia.googlesource.com/third_party/libpng",
#tag = "v1.6.37",
commit = "a40189cf881e9f0db80511c382292a5604c3c3d1",
shallow_since = "1555265432 -0400",
build_file = "//bazel:libpng.bazel",
load("//toolchain:build_toolchain.bzl", "build_cpp_toolchain")

build_cpp_toolchain(
# Meant to run on amd64 linux and compile for amd64 linux using musl as the c library.
name = "clang_linux_amd64_musl",
# From https://github.com/llvm/llvm-project/releases/download/llvmorg-13.0.0/clang+llvm-13.0.0-x86_64-linux-gnu-ubuntu-20.04.tar.xz.sha256
clang_prefix = "clang+llvm-13.0.0-x86_64-linux-gnu-ubuntu-20.04/",
clang_sha256 = "2c2fb857af97f41a5032e9ecadf7f78d3eff389a5cd3c9ec620d24f134ceb3c8",
clang_url = "https://github.com/llvm/llvm-project/releases/download/llvmorg-13.0.0/clang+llvm-13.0.0-x86_64-linux-gnu-ubuntu-20.04.tar.xz",
# From https://packages.debian.org/bullseye/amd64/musl-dev/download
musl_dev_sha256 = "b017792ad6ba3650b4889238c73cd19c1d6b0e39ca8319cdd3ad9e16374e614e",
musl_dev_url = "http://ftp.debian.org/debian/pool/main/m/musl/musl-dev_1.2.2-1_amd64.deb",
)
8 changes: 8 additions & 0 deletions experimental/bazel_test/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package(default_visibility = ["//:__subpackages__"])

filegroup(
name = "srcs",
srcs = [
"bazel_test.cpp",
],
)
22 changes: 22 additions & 0 deletions experimental/bazel_test/bazel_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright 2020 Google LLC.
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
#include <png.h>
#include <ctime>
#include <iostream>

void print_localtime() {
std::time_t result = std::time(nullptr);
std::cout << std::asctime(std::localtime(&result));
}

int main(int argc, char** argv) {
printf("Hello world\n");
print_localtime();
// https://docs.bazel.build/versions/main/test-encyclopedia.html#role-of-the-test-runner
if (png_access_version_number() == 10638) {
printf("PASS\n"); // This tells the human the test passed.
return 0; // This tells Bazel the test passed.
}
printf("FAIL\n"); // This tells the human the test failed.
return 1; // This tells Bazel the test failed.
}
87 changes: 87 additions & 0 deletions third_party/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
load("@rules_cc//cc:defs.bzl", "cc_library")

package(default_visibility = ["//:__subpackages__"])

cc_library(
name = "libpng",
srcs = [
"externals/libpng/png.c",
"externals/libpng/pngerror.c",
"externals/libpng/pngget.c",
"externals/libpng/pngmem.c",
"externals/libpng/pngpread.c",
"externals/libpng/pngread.c",
"externals/libpng/pngrio.c",
"externals/libpng/pngrtran.c",
"externals/libpng/pngrutil.c",
"externals/libpng/pngset.c",
"externals/libpng/pngtrans.c",
"externals/libpng/pngwio.c",
"externals/libpng/pngwrite.c",
"externals/libpng/pngwtran.c",
"externals/libpng/pngwutil.c",
# TODO(kjlubick) arm/x86 support
],
hdrs = ["libpng/pnglibconf.h"] + glob([
"externals/libpng/*.h",
]),
copts = [
"-Ithird_party/libpng/",
"-Wno-unused-but-set-variable",
],
includes = [
# This adds -isystem "third_party/externals/libpng" to any dependent
# compilation steps. This allows #include <png.h> to work
"externals/libpng",
# png.h attempts to #include "pnglibconf.h" , which we store in //third_party/libpng/
# This rule adds -isystem "third_party/externals/libpng" to any dependent
# rule on this, which avoids having to add "-Ithird_party/libpng/" to copts for
# those dependent rules.
"libpng",
],
textual_hdrs = ["externals/libpng/scripts/pnglibconf.h.prebuilt"],
deps = [":zlib"],
)

cc_library(
name = "zlib",
srcs = [
"externals/zlib/adler32.c",
"externals/zlib/compress.c",
"externals/zlib/cpu_features.c",
"externals/zlib/crc32.c",
"externals/zlib/deflate.c",
"externals/zlib/gzclose.c",
"externals/zlib/gzlib.c",
"externals/zlib/gzread.c",
"externals/zlib/gzwrite.c",
"externals/zlib/infback.c",
"externals/zlib/inffast.c",
"externals/zlib/inflate.c",
"externals/zlib/inftrees.c",
"externals/zlib/trees.c",
"externals/zlib/uncompr.c",
"externals/zlib/zutil.c",
] + glob([
"externals/zlib/**/*.h",
]),
hdrs = glob([
"externals/zlib/*.h",
]),
copts = [
"-Wno-unused-function",
],
strip_include_prefix = "externals/zlib/",
)

# This library is used to fix linking errors when trying to statically link in some symbols
# The symbols defined here:
# https://github.com/llvm/llvm-project/blob/main/libcxx/include/__support/musl/xlocale.h
# are defined to be inlined, however they are missing during the final linking of a static
# executable. By re-defining them in our own .a file, this makes the linker happy.
cc_library(
name = "musl_compat",
srcs = [
"musl_compat/locale.c",
],
)
20 changes: 20 additions & 0 deletions third_party/musl_compat/locale.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright 2021 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*
* Fixes linking issues with musl due to missing locale-specific string functions.
*/

#include <stdlib.h>

long long strtoll_l(const char *nptr, char **endptr, int base,
int locale_t) {
return strtoll(nptr, endptr, base);
}

long long strtoull_l(const char *nptr, char **endptr, int base,
int locale_t) {
return strtoull(nptr, endptr, base);
}
41 changes: 41 additions & 0 deletions toolchain/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
load("@rules_cc//cc:defs.bzl", "cc_toolchain", "cc_toolchain_suite")
load(":clang_toolchain_config.bzl", "provide_clang_toolchain_config")

package(default_visibility = ["//visibility:public"])

cc_toolchain_suite(
name = "clang_suite",
toolchains = {
"k8|clang": ":clang_toolchain",
},
)

filegroup(name = "not_implemented")

filegroup(
name = "all-toolchain-files",
srcs = [
"ar_trampoline.sh",
"clang_trampoline.sh",
"lld_trampoline.sh",
"@clang_linux_amd64_musl//:all_files",
],
)

provide_clang_toolchain_config(
name = "clang_toolchain_config",
)

cc_toolchain(
name = "clang_toolchain",
all_files = ":all-toolchain-files",
ar_files = ":all-toolchain-files",
compiler_files = ":all-toolchain-files",
dwp_files = ":not_implemented",
linker_files = ":all-toolchain-files",
objcopy_files = ":not_implemented",
strip_files = ":not_implemented",
supports_param_files = 0,
toolchain_config = ":clang_toolchain_config",
toolchain_identifier = "clang-toolchain",
)
7 changes: 7 additions & 0 deletions toolchain/ar_trampoline.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/bash
# Copyright 2021 Google LLC
#
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

external/clang_linux_amd64_musl/bin/llvm-ar $@
90 changes: 90 additions & 0 deletions toolchain/build_toolchain.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
"""
This file assembles a toolchain for Linux using the Clang Compiler and musl.
It currently makes use of musl and not glibc because the pre-compiled libraries from the latter
have absolute paths baked in and this makes linking difficult. The pre-compiled musl library
does not have this restriction and is much easier to statically link in as a result.
As inputs, it takes external URLs from which to download the clang binaries/libraries/includes
as well as the musl headers and pre-compiled binaries. Those files are downloaded and put
into one folder, with a little bit of re-arrangement so clang can find files (like the C runtime).
"""

def _download_and_extract_deb(ctx, deb, sha256, prefix, output = ""):
"""Downloads a debian file and extracts the data into the provided output directory"""

# https://docs.bazel.build/versions/main/skylark/lib/repository_ctx.html#download
download_info = ctx.download(
url = deb,
output = "deb.ar",
sha256 = sha256,
)

# https://docs.bazel.build/versions/main/skylark/lib/repository_ctx.html#execute
# This uses the extracted llvm-ar that comes with clang.
ctx.execute(["bin/llvm-ar", "x", "deb.ar"])

# https://docs.bazel.build/versions/main/skylark/lib/repository_ctx.html#extract
extract_info = ctx.extract(
archive = "data.tar.xz",
output = output,
stripPrefix = prefix,
)

# Clean up
ctx.delete("deb.ar")
ctx.delete("data.tar.xz")
ctx.delete("control.tar.xz")

def _build_cpp_toolchain_impl(ctx):
# Download the clang toolchain (the extraction can take a while)
# https://docs.bazel.build/versions/main/skylark/lib/repository_ctx.html#download
download_info = ctx.download_and_extract(
url = ctx.attr.clang_url,
output = "",
stripPrefix = ctx.attr.clang_prefix,
sha256 = ctx.attr.clang_sha256,
)

# This puts the musl include files in ${PWD}/usr/include/x86_64-linux-musl
# and the runtime files and lib.a files in ${PWD}/usr/lib/x86_64-linux-musl
_download_and_extract_deb(
ctx,
ctx.attr.musl_dev_url,
ctx.attr.musl_dev_sha256,
".",
)

# kjlubick@ cannot figure out how to get clang to look in ${PWD}/usr/lib/x86_64-linux-musl
# for the crt1.o files, so we'll move them to ${PWD}/usr/lib/ where clang *is* looking.
for file in ["crt1.o", "crtn.o", "Scrt1.o", "crti.o", "rcrt1.o"]:
ctx.execute(["cp", "usr/lib/x86_64-linux-musl/" + file, "usr/lib/"])

# Create a BUILD.bazel file that makes all the files in this subfolder
# available for use in rules, i.e. in the toolchain declaration.
# https://docs.bazel.build/versions/main/skylark/lib/repository_ctx.html#file
ctx.file(
"BUILD.bazel",
content = """
filegroup(
name = "all_files",
srcs = glob([
"**",
]),
visibility = ["//visibility:public"]
)
""",
executable = False,
)

build_cpp_toolchain = repository_rule(
implementation = _build_cpp_toolchain_impl,
attrs = {
"clang_url": attr.string(mandatory = True),
"clang_sha256": attr.string(mandatory = True),
"clang_prefix": attr.string(mandatory = True),
"musl_dev_url": attr.string(mandatory = True),
"musl_dev_sha256": attr.string(mandatory = True),
},
doc = "",
)
Loading

0 comments on commit 4d41304

Please sign in to comment.