diff --git a/README.md b/README.md index 746c4b95..4f72dd1a 100644 --- a/README.md +++ b/README.md @@ -226,6 +226,61 @@ filegroup( +### nixpkgs_cc_configure + +Tells Bazel to use compilers and linkers from Nixpkgs for the CC +toolchain. By default, Bazel autodetects a toolchain on the current +`PATH`. Overriding this autodetection makes builds more hermetic and +is considered a best practice. + +Example: + +```bzl +nixpkgs_cc_configure(repository = "@nixpkgs//:default.nix") +``` + + + + + + + + + + + + + + + + + + + + + + + + + +
Attributes
nix_file_content +

String; optional

+

An expression for a Nix environment derivation.

+
repository +

Label; optional

+

A repository label identifying which Nixpkgs to use.

+
repositories +

String-keyed label dict; optional

+

A dictionary mapping `NIX_PATH` entries to repository labels.

+

Setting it to +

repositories = { "myrepo" : "//:myrepo" }
+ for example would replace all instances + of <myrepo> in the called nix code by the + path to the target "//:myrepo". See the + relevant + section in the nix manual for more information.

+

Specify one of `path` or `repositories`.

+
## Migration diff --git a/WORKSPACE b/WORKSPACE index 4a19e8f2..00373245 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -1,6 +1,11 @@ workspace(name = "io_tweag_rules_nixpkgs") -load("//nixpkgs:nixpkgs.bzl", "nixpkgs_git_repository", "nixpkgs_package") +load( + "//nixpkgs:nixpkgs.bzl", + "nixpkgs_cc_configure", + "nixpkgs_git_repository", + "nixpkgs_package" +) # For tests @@ -74,3 +79,5 @@ filegroup( ) """, ) + +nixpkgs_cc_configure(repository = "@remote_nixpkgs//:default.nix") diff --git a/nixpkgs/BUILD.pkg b/nixpkgs/BUILD.pkg index 288cf998..badfdcc2 100644 --- a/nixpkgs/BUILD.pkg +++ b/nixpkgs/BUILD.pkg @@ -1,4 +1,4 @@ -package(default_visibility = [ "//visibility:public" ]) +package(default_visibility = ["//visibility:public"]) filegroup( name = "bin", diff --git a/nixpkgs/nixpkgs.bzl b/nixpkgs/nixpkgs.bzl index 413cce85..1991b4b7 100644 --- a/nixpkgs/nixpkgs.bzl +++ b/nixpkgs/nixpkgs.bzl @@ -1,3 +1,5 @@ +load("@bazel_tools//tools/cpp:cc_configure.bzl", "cc_autoconf_impl") + """Rules for importing Nixpkgs packages.""" def _nixpkgs_git_repository_impl(repository_ctx): @@ -97,17 +99,22 @@ def _nixpkgs_package_impl(repository_ctx): # as we can do. timeout = 1073741824 - res = repository_ctx.execute(nix_build, quiet = False, timeout = timeout, - environment=dict(NIX_PATH=nix_path)) - if res.return_code == 0: - output_path = res.stdout.splitlines()[-1] - else: - _execute_error(res, "Cannot build Nix attribute `{}`" - .format(repository_ctx.attr.attribute_path)) - + exec_result = _execute_or_fail( + repository_ctx, + nix_build, + failure_message = "Cannot build Nix attribute '{}'.".format( + repository_ctx.attr.attribute_path + ), + quiet = False, + timeout = timeout, + environment=dict(NIX_PATH=nix_path), + ) + output_path = exec_result.stdout.splitlines()[-1] # Build a forest of symlinks (like new_local_package() does) to the # Nix store. - _symlink_children(repository_ctx, output_path) + for target in _find_children(repository_ctx, output_path): + basename = target.rpartition("/")[-1] + repository_ctx.symlink(target, basename) _nixpkgs_package = repository_rule( @@ -125,6 +132,7 @@ _nixpkgs_package = repository_rule( local = True, ) + def nixpkgs_package(*args, **kwargs): # Because of https://github.com/bazelbuild/bazel/issues/5356 we can't # directly pass a dict from strings to labels to the rule (which we'd like @@ -142,11 +150,94 @@ def nixpkgs_package(*args, **kwargs): else: _nixpkgs_package(*args, **kwargs) -def _symlink_children(repository_ctx, target_dir): - """Create a symlink to all children of `target_dir` in the current - build directory.""" + +def _nixpkgs_cc_autoconf_impl(repository_ctx): + # Calling repository_ctx.path() on anything but a regular file + # fails. So the roundabout way to do the same thing is to find + # a regular file we know is in the workspace (i.e. the WORKSPACE + # file itself) and then use dirname to get the path of the workspace + # root. + workspace_file_path = repository_ctx.path( + Label("@nixpkgs_cc_toolchain//:WORKSPACE") + ) + workspace_root = _execute_or_fail( + repository_ctx, + ["dirname", workspace_file_path], + ).stdout.rstrip() + + # Make a list of all available tools in the Nix derivation. Override + # the Bazel autoconfiguration with the tools we found. + bin_contents = _find_children(repository_ctx, workspace_root + "/bin") + overriden_tools = { + tool: repository_ctx.path(Label("@nixpkgs_cc_toolchain//:bin/" + tool)) + for entry in bin_contents + for tool in [entry.rpartition("/")[-1]] # Compute basename + } + cc_autoconf_impl(repository_ctx, overriden_tools = overriden_tools) + +_nixpkgs_cc_autoconf = repository_rule( + implementation = _nixpkgs_cc_autoconf_impl +) + + +def nixpkgs_cc_configure( + repository = None, + repositories = None, + nix_file_content = """ + with import {}; buildEnv { + name = "bazel-cc-toolchain"; + paths = [ gcc binutils ]; + } + """): + """Use a CC toolchain from Nixpkgs. + + By default, Bazel auto-configures a CC toolchain from commands (e.g. + `gcc`) available in the environment. To make builds more hermetic, use + this rule to specific explicitly which commands the toolchain should + use. + """ + if repository and repositories or not repository and not repositories: + fail("Specify one of repository or repositories (but not both).") + elif repository: + repositories = {"nixpkgs": repository} + + nixpkgs_package( + name = "nixpkgs_cc_toolchain", + repositories = repositories, + nix_file_content = nix_file_content, + build_file_content = """exports_files(glob(["bin/*"]))""", + ) + # Following lines should match + # https://github.com/bazelbuild/bazel/blob/master/tools/cpp/cc_configure.bzl#L93. + _nixpkgs_cc_autoconf(name = "local_config_cc") + native.bind(name = "cc_toolchain", actual = "@local_config_cc//:toolchain") + native.register_toolchains("@local_config_cc//:all") + + +def _execute_or_fail(repository_ctx, arguments, failure_message = "", *args, **kwargs): + """Call repository_ctx.execute() and fail if non-zero return code.""" + result = repository_ctx.execute(arguments, *args, **kwargs) + if result.return_code: + outputs = dict( + failure_message = failure_message, + arguments = arguments, + return_code = result.return_code, + stderr = result.stderr, + ) + fail(""" +{failure_message} +Command: {arguments} +Return code: {return_code} +Error output: +{stderr} +""").format(**outputs) + return result + + +def _find_children(repository_ctx, target_dir): find_args = [ _executable_path(repository_ctx, "find"), + "-L", target_dir, "-maxdepth", "1", # otherwise the directory is printed as well @@ -154,14 +245,9 @@ def _symlink_children(repository_ctx, target_dir): # filenames can contain \n "-print0", ] - find_res = repository_ctx.execute(find_args) - if find_res.return_code == 0: - for target in find_res.stdout.rstrip("\0").split("\0"): - basename = target.rpartition("/")[-1] - repository_ctx.symlink(target, basename) - else: - _execute_error(find_res) - + exec_result = _execute_or_fail(repository_ctx, find_args) + return exec_result.stdout.rstrip("\0").split("\0") + def _executable_path(repository_ctx, exe_name, extra_msg=""): """Try to find the executable, fail with an error.""" @@ -170,19 +256,3 @@ def _executable_path(repository_ctx, exe_name, extra_msg=""): fail("Could not find the `{}` executable in PATH.{}\n" .format(exe_name, " " + extra_msg if extra_msg else "")) return path - - -def _execute_error(exec_result, msg): - """Print a nice error message for a failed `execute`.""" - fail(""" -execute() error: {msg} -status code: {code} -stdout: -{stdout} -stderr: -{stderr} -""".format( - msg=msg, - code=exec_result.return_code, - stdout=exec_result.stdout, - stderr=exec_result.stderr)) diff --git a/tests/BUILD b/tests/BUILD index ba338fb8..866f8fbc 100644 --- a/tests/BUILD +++ b/tests/BUILD @@ -47,3 +47,9 @@ package(default_testonly = 1) timeout = "short", ) ] + +# Test nixpkgs_cc_configure() by building some CC code. +cc_binary( + name = "cc-test", + srcs = ["cc-test.cc"], +) diff --git a/tests/cc-test.cc b/tests/cc-test.cc new file mode 100644 index 00000000..76e81970 --- /dev/null +++ b/tests/cc-test.cc @@ -0,0 +1 @@ +int main() { return 0; }