blob: 4f0e30d8ccd28365573cb03c5f33338cdfbfa4b8 [file] [log] [blame]
Serge Bazanski140bddc2020-06-05 21:01:19 +02001# Copyright 2020 The Monogon Project Authors.
2#
3# SPDX-License-Identifier: Apache-2.0
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
Lorenz Brun54a5a052023-10-02 16:40:11 +020016load("@bazel_skylib//lib:paths.bzl", "paths")
Serge Bazanski140bddc2020-06-05 21:01:19 +020017
Serge Bazanskic3ae7582020-06-08 17:15:26 +020018def _build_pure_transition_impl(settings, attr):
19 """
20 Transition that enables pure, static build of Go binaries.
21 """
Serge Bazanski30021af2023-06-20 13:30:11 +020022 race = settings['@io_bazel_rules_go//go/config:race']
23 pure = not race
24
Serge Bazanskic3ae7582020-06-08 17:15:26 +020025 return {
Serge Bazanski30021af2023-06-20 13:30:11 +020026 "@io_bazel_rules_go//go/config:pure": pure,
Serge Bazanskic3ae7582020-06-08 17:15:26 +020027 "@io_bazel_rules_go//go/config:static": True,
28 }
29
30build_pure_transition = transition(
31 implementation = _build_pure_transition_impl,
Serge Bazanski30021af2023-06-20 13:30:11 +020032 inputs = [
33 "@io_bazel_rules_go//go/config:race",
34 ],
Serge Bazanskic3ae7582020-06-08 17:15:26 +020035 outputs = [
36 "@io_bazel_rules_go//go/config:pure",
37 "@io_bazel_rules_go//go/config:static",
38 ],
39)
40
Lorenz Brun5e4fc2d2020-09-22 18:35:15 +020041def _build_static_transition_impl(settings, attr):
42 """
43 Transition that enables static builds with CGo and musl for Go binaries.
44 """
45 return {
46 "@io_bazel_rules_go//go/config:static": True,
Leopoldbc93c2b2023-01-14 13:12:23 +010047 "//command_line_option:platforms": "//build/platforms:linux_amd64_static",
Lorenz Brun5e4fc2d2020-09-22 18:35:15 +020048 }
49
50build_static_transition = transition(
51 implementation = _build_static_transition_impl,
52 inputs = [],
53 outputs = [
54 "@io_bazel_rules_go//go/config:static",
Leopoldbc93c2b2023-01-14 13:12:23 +010055 "//command_line_option:platforms",
Lorenz Brun5e4fc2d2020-09-22 18:35:15 +020056 ],
57)
58
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +010059FSSpecInfo = provider(
60 "Provides parts of an FSSpec used to assemble filesystem images",
61 fields = {
62 "spec": "File containing the partial FSSpec as prototext",
63 "referenced": "Files (potentially) referenced by the spec",
Serge Bazanski140bddc2020-06-05 21:01:19 +020064 },
65)
Lorenz Brun6b13bf12021-01-26 19:54:24 +010066
Serge Bazanskia3938142022-04-04 17:04:47 +020067def _fsspec_core_impl(ctx, tool, output_file):
Lorenz Brun6b13bf12021-01-26 19:54:24 +010068 """
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +010069 _fsspec_core_impl implements the core of an fsspec-based rule. It takes
Serge Bazanskia3938142022-04-04 17:04:47 +020070 input from the `files`,`files_cc`, `symlinks` and `fsspecs` attributes
71 and calls `tool` with the `-out` parameter pointing to `output_file`
72 and paths to all fsspecs as positional arguments.
Lorenz Brun6b13bf12021-01-26 19:54:24 +010073 """
Lorenz Brun6b13bf12021-01-26 19:54:24 +010074 fs_spec_name = ctx.label.name + ".prototxt"
75 fs_spec = ctx.actions.declare_file(fs_spec_name)
76
77 fs_files = []
78 inputs = []
79 for label, p in ctx.attr.files.items() + ctx.attr.files_cc.items():
80 if not p.startswith("/"):
81 fail("file {} invalid: must begin with /".format(p))
82
83 # Figure out if this is an executable.
84 is_executable = True
85
86 di = label[DefaultInfo]
87 if di.files_to_run.executable == None:
88 # Generated non-executable files will have DefaultInfo.files_to_run.executable == None
89 is_executable = False
90 elif di.files_to_run.executable.is_source:
91 # Source files will have executable.is_source == True
92 is_executable = False
93
94 # Ensure only single output is declared.
95 # If you hit this error, figure out a better logic to find what file you need, maybe looking at providers other
96 # than DefaultInfo.
97 files = di.files.to_list()
98 if len(files) > 1:
99 fail("file {} has more than one output: {}", p, files)
100 src = files[0]
101 inputs.append(src)
102
103 mode = 0o555 if is_executable else 0o444
104 fs_files.append(struct(path = p, source_path = src.path, mode = mode, uid = 0, gid = 0))
105
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100106 fs_symlinks = []
107 for target, p in ctx.attr.symlinks.items():
108 fs_symlinks.append(struct(path = p, target_path = target))
109
Serge Bazanskia3938142022-04-04 17:04:47 +0200110 fs_spec_content = struct(file = fs_files, directory = [], symbolic_link = fs_symlinks)
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100111 ctx.actions.write(fs_spec, proto.encode_text(fs_spec_content))
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100112
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100113 extra_specs = []
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100114
115 for fsspec in ctx.attr.fsspecs:
Lorenz Brund1bc4a62022-09-12 16:45:18 +0000116 if FSSpecInfo in fsspec:
117 fsspecInfo = fsspec[FSSpecInfo]
118 extra_specs.append(fsspecInfo.spec)
119 for f in fsspecInfo.referenced:
120 inputs.append(f)
121 else:
122 # Raw .fsspec prototext. No referenced data allowed.
123 di = fsspec[DefaultInfo]
124 extra_specs += di.files.to_list()
Serge Bazanskia3938142022-04-04 17:04:47 +0200125
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100126 ctx.actions.run(
127 outputs = [output_file],
128 inputs = [fs_spec] + inputs + extra_specs,
129 tools = [tool],
130 executable = tool,
131 arguments = ["-out", output_file.path, fs_spec.path] + [s.path for s in extra_specs],
132 )
133 return
134
135def _node_initramfs_impl(ctx):
Lorenz Brun62f1d362023-11-14 16:18:24 +0100136 initramfs_name = ctx.label.name + ".cpio.zst"
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100137 initramfs = ctx.actions.declare_file(initramfs_name)
138
Serge Bazanskia3938142022-04-04 17:04:47 +0200139 _fsspec_core_impl(ctx, ctx.executable._mkcpio, initramfs)
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100140
141 # TODO(q3k): Document why this is needed
142 return [DefaultInfo(runfiles = ctx.runfiles(files = [initramfs]), files = depset([initramfs]))]
143
144node_initramfs = rule(
145 implementation = _node_initramfs_impl,
146 doc = """
147 Build a node initramfs. The initramfs will contain a basic /dev directory and all the files specified by the
148 `files` attribute. Executable files will have their permissions set to 0755, non-executable files will have
149 their permissions set to 0444. All parent directories will be created with 0755 permissions.
150 """,
151 attrs = {
152 "files": attr.label_keyed_string_dict(
153 mandatory = True,
154 allow_files = True,
155 doc = """
156 Dictionary of Labels to String, placing a given Label's output file in the initramfs at the location
157 specified by the String value. The specified labels must only have a single output.
158 """,
159 # Attach pure transition to ensure all binaries added to the initramfs are pure/static binaries.
160 cfg = build_pure_transition,
161 ),
162 "files_cc": attr.label_keyed_string_dict(
163 allow_files = True,
164 doc = """
165 Special case of 'files' for compilation targets that need to be built with the musl toolchain like
166 go_binary targets which need cgo or cc_binary targets.
167 """,
168 # Attach static transition to all files_cc inputs to ensure they are built with musl and static.
169 cfg = build_static_transition,
170 ),
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100171 "symlinks": attr.string_dict(
172 default = {},
173 doc = """
174 Symbolic links to create. Similar format as in files and files_cc, so the target of the symlink is the
175 key and the value of it is the location of the symlink itself. Only raw strings are allowed as targets,
176 labels are not permitted. Include the file using files or files_cc, then symlink to its location.
177 """,
178 ),
179 "fsspecs": attr.label_list(
180 default = [],
181 doc = """
182 List of file system specs (metropolis.node.build.fsspec.FSSpec) to also include in the resulting image.
183 These will be merged with all other given attributes.
184 """,
185 providers = [FSSpecInfo],
Serge Bazanskia3938142022-04-04 17:04:47 +0200186 allow_files = True,
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100187 ),
188
189 # Tool
190 "_mkcpio": attr.label(
191 default = Label("//metropolis/node/build/mkcpio"),
192 executable = True,
193 cfg = "exec",
194 ),
195
196 # Allow for transitions to be attached to this rule.
197 "_whitelist_function_transition": attr.label(
198 default = "@bazel_tools//tools/whitelists/function_transition_whitelist",
199 ),
200 },
201)
202
203def _erofs_image_impl(ctx):
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100204 fs_name = ctx.label.name + ".img"
205 fs_out = ctx.actions.declare_file(fs_name)
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100206
Serge Bazanskia3938142022-04-04 17:04:47 +0200207 _fsspec_core_impl(ctx, ctx.executable._mkerofs, fs_out)
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100208
209 return [DefaultInfo(files = depset([fs_out]))]
210
211erofs_image = rule(
212 implementation = _erofs_image_impl,
213 doc = """
214 Build an EROFS. All files specified in files, files_cc and all specified symlinks will be contained.
215 Executable files will have their permissions set to 0555, non-executable files will have
216 their permissions set to 0444. All parent directories will be created with 0555 permissions.
217 """,
218 attrs = {
219 "files": attr.label_keyed_string_dict(
220 mandatory = True,
221 allow_files = True,
222 doc = """
223 Dictionary of Labels to String, placing a given Label's output file in the EROFS at the location
224 specified by the String value. The specified labels must only have a single output.
225 """,
226 # Attach pure transition to ensure all binaries added to the initramfs are pure/static binaries.
227 cfg = build_pure_transition,
228 ),
229 "files_cc": attr.label_keyed_string_dict(
230 allow_files = True,
231 doc = """
232 Special case of 'files' for compilation targets that need to be built with the musl toolchain like
233 go_binary targets which need cgo or cc_binary targets.
234 """,
235 # Attach static transition to all files_cc inputs to ensure they are built with musl and static.
236 cfg = build_static_transition,
237 ),
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100238 "symlinks": attr.string_dict(
239 default = {},
240 doc = """
241 Symbolic links to create. Similar format as in files and files_cc, so the target of the symlink is the
242 key and the value of it is the location of the symlink itself. Only raw strings are allowed as targets,
243 labels are not permitted. Include the file using files or files_cc, then symlink to its location.
244 """,
245 ),
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100246 "fsspecs": attr.label_list(
247 default = [],
248 doc = """
249 List of file system specs (metropolis.node.build.fsspec.FSSpec) to also include in the resulting image.
250 These will be merged with all other given attributes.
251 """,
252 providers = [FSSpecInfo],
Serge Bazanskia3938142022-04-04 17:04:47 +0200253 allow_files = True,
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100254 ),
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100255
256 # Tools, implicit dependencies.
257 "_mkerofs": attr.label(
258 default = Label("//metropolis/node/build/mkerofs"),
259 executable = True,
260 cfg = "host",
261 ),
262
263 # Allow for transitions to be attached to this rule.
264 "_whitelist_function_transition": attr.label(
265 default = "@bazel_tools//tools/whitelists/function_transition_whitelist",
266 ),
267 },
268)
Mateusz Zalegae2bf5742022-01-25 19:36:08 +0100269
270# VerityConfig is emitted by verity_image, and contains a file enclosing a
271# singular dm-verity target table.
272VerityConfig = provider(
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100273 "Configuration necessary to mount a single dm-verity target.",
274 fields = {
275 "table": "A file containing the dm-verity target table. See: https://www.kernel.org/doc/html/latest/admin-guide/device-mapper/verity.html",
276 },
Mateusz Zalegae2bf5742022-01-25 19:36:08 +0100277)
278
279def _verity_image_impl(ctx):
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100280 """
281 Create a new file containing the source image data together with the Verity
282 metadata appended to it, and provide an associated DeviceMapper Verity target
283 table in a separate file, through VerityConfig provider.
284 """
Mateusz Zalegae2bf5742022-01-25 19:36:08 +0100285
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100286 # Run mkverity.
287 image = ctx.actions.declare_file(ctx.attr.name + ".img")
288 table = ctx.actions.declare_file(ctx.attr.name + ".dmt")
289 ctx.actions.run(
290 mnemonic = "GenVerityImage",
291 progress_message = "Generating a dm-verity image",
292 inputs = [ctx.file.source],
293 outputs = [
294 image,
295 table,
296 ],
297 executable = ctx.file._mkverity,
298 arguments = [
299 "-input=" + ctx.file.source.path,
300 "-output=" + image.path,
301 "-table=" + table.path,
302 "-data_alias=" + ctx.attr.rootfs_partlabel,
303 "-hash_alias=" + ctx.attr.rootfs_partlabel,
304 ],
Mateusz Zalegae2bf5742022-01-25 19:36:08 +0100305 )
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100306
307 return [
308 DefaultInfo(
309 files = depset([image]),
310 runfiles = ctx.runfiles(files = [image]),
311 ),
312 VerityConfig(
313 table = table,
314 ),
315 ]
Mateusz Zalegae2bf5742022-01-25 19:36:08 +0100316
317verity_image = rule(
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100318 implementation = _verity_image_impl,
319 doc = """
Mateusz Zalegae2bf5742022-01-25 19:36:08 +0100320 Build a dm-verity target image by appending Verity metadata to the source
321 image. A corresponding dm-verity target table will be made available
322 through VerityConfig provider.
323 """,
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100324 attrs = {
325 "source": attr.label(
326 doc = "A source image.",
327 allow_single_file = True,
328 ),
329 "rootfs_partlabel": attr.string(
330 doc = "GPT partition label of the rootfs to be used with dm-mod.create.",
Lorenz Brun35fcf032023-06-29 04:15:58 +0200331 default = "PARTLABEL=METROPOLIS-SYSTEM-X",
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100332 ),
333 "_mkverity": attr.label(
334 doc = "The mkverity executable needed to generate the image.",
335 default = "//metropolis/node/build/mkverity",
336 allow_single_file = True,
337 executable = True,
338 cfg = "host",
339 ),
340 },
Mateusz Zalegae2bf5742022-01-25 19:36:08 +0100341)
Lorenz Brun54a5a052023-10-02 16:40:11 +0200342
343# From Aspect's bazel-lib under Apache 2.0
344def _transition_platform_impl(_, attr):
345 return {"//command_line_option:platforms": str(attr.target_platform)}
346
347# Transition from any input configuration to one that includes the
348# --platforms command-line flag.
349_transition_platform = transition(
350 implementation = _transition_platform_impl,
351 inputs = [],
352 outputs = ["//command_line_option:platforms"],
353)
354
355
356def _platform_transition_binary_impl(ctx):
357 # We need to forward the DefaultInfo provider from the underlying rule.
358 # Unfortunately, we can't do this directly, because Bazel requires that the executable to run
359 # is actually generated by this rule, so we need to symlink to it, and generate a synthetic
360 # forwarding DefaultInfo.
361
362 result = []
363 binary = ctx.attr.binary[0]
364
365 default_info = binary[DefaultInfo]
366 files = default_info.files
367 new_executable = None
368 original_executable = default_info.files_to_run.executable
369 runfiles = default_info.default_runfiles
370
371 if not original_executable:
372 fail("Cannot transition a 'binary' that is not executable")
373
374 new_executable_name = ctx.attr.basename if ctx.attr.basename else original_executable.basename
375
376 # In order for the symlink to have the same basename as the original
377 # executable (important in the case of proto plugins), put it in a
378 # subdirectory named after the label to prevent collisions.
379 new_executable = ctx.actions.declare_file(paths.join(ctx.label.name, new_executable_name))
380 ctx.actions.symlink(
381 output = new_executable,
382 target_file = original_executable,
383 is_executable = True,
384 )
385 files = depset(direct = [new_executable], transitive = [files])
386 runfiles = runfiles.merge(ctx.runfiles([new_executable]))
387
388 result.append(
389 DefaultInfo(
390 files = files,
391 runfiles = runfiles,
392 executable = new_executable,
393 ),
394 )
395
396 return result
397
398platform_transition_binary = rule(
399 implementation = _platform_transition_binary_impl,
400 attrs = {
401 "basename": attr.string(),
402 "binary": attr.label(allow_files = True, cfg = _transition_platform),
403 "target_platform": attr.label(
404 doc = "The target platform to transition the binary.",
405 mandatory = True,
406 ),
407 "_allowlist_function_transition": attr.label(
408 default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
409 ),
410 },
411 executable = True,
412 doc = "Transitions the binary to use the provided platform.",
413)