-
Notifications
You must be signed in to change notification settings - Fork 84
/
Copy pathnixpkgs.bzl
258 lines (226 loc) · 9.19 KB
/
nixpkgs.bzl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
load("@bazel_tools//tools/cpp:cc_configure.bzl", "cc_autoconf_impl")
"""Rules for importing Nixpkgs packages."""
def _nixpkgs_git_repository_impl(repository_ctx):
repository_ctx.file('BUILD')
# XXX Hack because repository_ctx.path below bails out if resolved path not a regular file.
repository_ctx.file(repository_ctx.name)
repository_ctx.download_and_extract(
url = "%s/archive/%s.tar.gz" % (repository_ctx.attr.remote, repository_ctx.attr.revision),
stripPrefix = "nixpkgs-" + repository_ctx.attr.revision,
sha256 = repository_ctx.attr.sha256,
)
nixpkgs_git_repository = repository_rule(
implementation = _nixpkgs_git_repository_impl,
attrs = {
"revision": attr.string(mandatory = True),
"remote": attr.string(default = "https://github.com/NixOS/nixpkgs"),
"sha256": attr.string(),
},
local = False,
)
def _nixpkgs_package_impl(repository_ctx):
repositories = None
if repository_ctx.attr.repositories:
repositories = repository_ctx.attr.repositories
if repository_ctx.attr.repository:
print("The 'repository' attribute is deprecated, use 'repositories' instead")
repositories = { repository_ctx.attr.repository: "nixpkgs" } + \
(repositories if repositories else {})
if repository_ctx.attr.build_file and repository_ctx.attr.build_file_content:
fail("Specify one of 'build_file' or 'build_file_content', but not both.")
elif repository_ctx.attr.build_file:
repository_ctx.symlink(repository_ctx.attr.build_file, "BUILD")
elif repository_ctx.attr.build_file_content:
repository_ctx.file("BUILD", content = repository_ctx.attr.build_file_content)
else:
repository_ctx.template("BUILD", Label("@io_tweag_rules_nixpkgs//nixpkgs:BUILD.pkg"))
strFailureImplicitNixpkgs = (
"One of 'repositories', 'nix_file' or 'nix_file_content' must be provided. "
+ "The NIX_PATH environment variable is not inherited.")
expr_args = []
if repository_ctx.attr.nix_file and repository_ctx.attr.nix_file_content:
fail("Specify one of 'nix_file' or 'nix_file_content', but not both.")
elif repository_ctx.attr.nix_file:
repository_ctx.symlink(repository_ctx.attr.nix_file, "default.nix")
elif repository_ctx.attr.nix_file_content:
expr_args = ["-E", repository_ctx.attr.nix_file_content]
elif not repositories:
fail(strFailureImplicitNixpkgs)
else:
expr_args = ["-E", "import <nixpkgs> {}"]
# Introduce an artificial dependency with a bogus name on each of
# the nix_file_deps.
for dep in repository_ctx.attr.nix_file_deps:
components = [c for c in [dep.workspace_root, dep.package, dep.name] if c]
link = '/'.join(components).replace('_', '_U').replace('/', '_S')
repository_ctx.symlink(dep, link)
expr_args.extend([
"-A", repository_ctx.attr.attribute_path
if repository_ctx.attr.nix_file or repository_ctx.attr.nix_file_content
else repository_ctx.attr.attribute_path or repository_ctx.attr.name,
# Creating an out link prevents nix from garbage collecting the store path.
# nixpkgs uses `nix-support/` for such house-keeping files, so we mirror them
# and use `bazel-support/`, under the assumption that no nix package has
# a file named `bazel-support` in its root.
# A `bazel clean` deletes the symlink and thus nix is free to garbage collect
# the store path.
"--out-link", "bazel-support/nix-out-link"
])
# If repositories is not set, leave empty so nix will fail
# unless a pinned nixpkgs is set in the `nix_file` attribute.
nix_path = ""
if repositories:
nix_path = ":".join(
[(path_name + "=" + str(repository_ctx.path(target)))
for (target, path_name) in repositories.items()])
elif not (repository_ctx.attr.nix_file or repository_ctx.attr.nix_file_content):
fail(strFailureImplicitNixpkgs)
nix_build_path = _executable_path(
repository_ctx,
"nix-build",
extra_msg = "See: https://nixos.org/nix/"
)
nix_build = [nix_build_path] + expr_args
# Large enough integer that Bazel can still parse. We don't have
# access to MAX_INT and 0 is not a valid timeout so this is as good
# as we can do.
timeout = 1073741824
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.
for target in _find_children(repository_ctx, output_path):
basename = target.rpartition("/")[-1]
repository_ctx.symlink(target, basename)
_nixpkgs_package = repository_rule(
implementation = _nixpkgs_package_impl,
attrs = {
"attribute_path": attr.string(),
"nix_file": attr.label(allow_single_file = [".nix"]),
"nix_file_deps": attr.label_list(),
"nix_file_content": attr.string(),
"repositories": attr.label_keyed_string_dict(),
"repository": attr.label(),
"build_file": attr.label(),
"build_file_content": attr.string(),
},
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
# for the `repositories` arguments), but we can pass a dict from labels to
# strings. So we swap the keys and the values (assuming they all are
# distinct).
if "repositories" in kwargs:
inversed_repositories = { value: key for (key, value) in kwargs["repositories"].items() }
kwargs.pop("repositories")
_nixpkgs_package(
repositories = inversed_repositories,
*args,
**kwargs
)
else:
_nixpkgs_package(*args, **kwargs)
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 <nixpkgs> {}; 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
"-mindepth", "1",
# filenames can contain \n
"-print0",
]
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."""
path = repository_ctx.which(exe_name)
if path == None:
fail("Could not find the `{}` executable in PATH.{}\n"
.format(exe_name, " " + extra_msg if extra_msg else ""))
return path