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; }