blob: d98fca089441652fa07702699a6cec4339afb19e [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.
16
Serge Bazanskic3ae7582020-06-08 17:15:26 +020017def _build_pure_transition_impl(settings, attr):
18 """
19 Transition that enables pure, static build of Go binaries.
20 """
21 return {
22 "@io_bazel_rules_go//go/config:pure": True,
23 "@io_bazel_rules_go//go/config:static": True,
24 }
25
26build_pure_transition = transition(
27 implementation = _build_pure_transition_impl,
28 inputs = [],
29 outputs = [
30 "@io_bazel_rules_go//go/config:pure",
31 "@io_bazel_rules_go//go/config:static",
32 ],
33)
34
Lorenz Brun5e4fc2d2020-09-22 18:35:15 +020035def _build_static_transition_impl(settings, attr):
36 """
37 Transition that enables static builds with CGo and musl for Go binaries.
38 """
39 return {
40 "@io_bazel_rules_go//go/config:static": True,
41 "//command_line_option:crosstool_top": "//build/toolchain/musl-host-gcc:musl_host_cc_suite",
42 }
43
44build_static_transition = transition(
45 implementation = _build_static_transition_impl,
46 inputs = [],
47 outputs = [
48 "@io_bazel_rules_go//go/config:static",
49 "//command_line_option:crosstool_top",
50 ],
51)
52
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +010053FSSpecInfo = provider(
54 "Provides parts of an FSSpec used to assemble filesystem images",
55 fields = {
56 "spec": "File containing the partial FSSpec as prototext",
57 "referenced": "Files (potentially) referenced by the spec",
Serge Bazanski140bddc2020-06-05 21:01:19 +020058 },
59)
Lorenz Brun6b13bf12021-01-26 19:54:24 +010060
Serge Bazanskia3938142022-04-04 17:04:47 +020061def _fsspec_core_impl(ctx, tool, output_file):
Lorenz Brun6b13bf12021-01-26 19:54:24 +010062 """
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +010063 _fsspec_core_impl implements the core of an fsspec-based rule. It takes
Serge Bazanskia3938142022-04-04 17:04:47 +020064 input from the `files`,`files_cc`, `symlinks` and `fsspecs` attributes
65 and calls `tool` with the `-out` parameter pointing to `output_file`
66 and paths to all fsspecs as positional arguments.
Lorenz Brun6b13bf12021-01-26 19:54:24 +010067 """
Lorenz Brun6b13bf12021-01-26 19:54:24 +010068 fs_spec_name = ctx.label.name + ".prototxt"
69 fs_spec = ctx.actions.declare_file(fs_spec_name)
70
71 fs_files = []
72 inputs = []
73 for label, p in ctx.attr.files.items() + ctx.attr.files_cc.items():
74 if not p.startswith("/"):
75 fail("file {} invalid: must begin with /".format(p))
76
77 # Figure out if this is an executable.
78 is_executable = True
79
80 di = label[DefaultInfo]
81 if di.files_to_run.executable == None:
82 # Generated non-executable files will have DefaultInfo.files_to_run.executable == None
83 is_executable = False
84 elif di.files_to_run.executable.is_source:
85 # Source files will have executable.is_source == True
86 is_executable = False
87
88 # Ensure only single output is declared.
89 # If you hit this error, figure out a better logic to find what file you need, maybe looking at providers other
90 # than DefaultInfo.
91 files = di.files.to_list()
92 if len(files) > 1:
93 fail("file {} has more than one output: {}", p, files)
94 src = files[0]
95 inputs.append(src)
96
97 mode = 0o555 if is_executable else 0o444
98 fs_files.append(struct(path = p, source_path = src.path, mode = mode, uid = 0, gid = 0))
99
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100100 fs_symlinks = []
101 for target, p in ctx.attr.symlinks.items():
102 fs_symlinks.append(struct(path = p, target_path = target))
103
Serge Bazanskia3938142022-04-04 17:04:47 +0200104 fs_spec_content = struct(file = fs_files, directory = [], symbolic_link = fs_symlinks)
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100105 ctx.actions.write(fs_spec, proto.encode_text(fs_spec_content))
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100106
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100107 extra_specs = []
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100108
109 for fsspec in ctx.attr.fsspecs:
Lorenz Brund1bc4a62022-09-12 16:45:18 +0000110 if FSSpecInfo in fsspec:
111 fsspecInfo = fsspec[FSSpecInfo]
112 extra_specs.append(fsspecInfo.spec)
113 for f in fsspecInfo.referenced:
114 inputs.append(f)
115 else:
116 # Raw .fsspec prototext. No referenced data allowed.
117 di = fsspec[DefaultInfo]
118 extra_specs += di.files.to_list()
Serge Bazanskia3938142022-04-04 17:04:47 +0200119
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100120 ctx.actions.run(
121 outputs = [output_file],
122 inputs = [fs_spec] + inputs + extra_specs,
123 tools = [tool],
124 executable = tool,
125 arguments = ["-out", output_file.path, fs_spec.path] + [s.path for s in extra_specs],
126 )
127 return
128
129def _node_initramfs_impl(ctx):
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100130 initramfs_name = ctx.label.name + ".cpio.lz4"
131 initramfs = ctx.actions.declare_file(initramfs_name)
132
Serge Bazanskia3938142022-04-04 17:04:47 +0200133 _fsspec_core_impl(ctx, ctx.executable._mkcpio, initramfs)
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100134
135 # TODO(q3k): Document why this is needed
136 return [DefaultInfo(runfiles = ctx.runfiles(files = [initramfs]), files = depset([initramfs]))]
137
138node_initramfs = rule(
139 implementation = _node_initramfs_impl,
140 doc = """
141 Build a node initramfs. The initramfs will contain a basic /dev directory and all the files specified by the
142 `files` attribute. Executable files will have their permissions set to 0755, non-executable files will have
143 their permissions set to 0444. All parent directories will be created with 0755 permissions.
144 """,
145 attrs = {
146 "files": attr.label_keyed_string_dict(
147 mandatory = True,
148 allow_files = True,
149 doc = """
150 Dictionary of Labels to String, placing a given Label's output file in the initramfs at the location
151 specified by the String value. The specified labels must only have a single output.
152 """,
153 # Attach pure transition to ensure all binaries added to the initramfs are pure/static binaries.
154 cfg = build_pure_transition,
155 ),
156 "files_cc": attr.label_keyed_string_dict(
157 allow_files = True,
158 doc = """
159 Special case of 'files' for compilation targets that need to be built with the musl toolchain like
160 go_binary targets which need cgo or cc_binary targets.
161 """,
162 # Attach static transition to all files_cc inputs to ensure they are built with musl and static.
163 cfg = build_static_transition,
164 ),
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100165 "symlinks": attr.string_dict(
166 default = {},
167 doc = """
168 Symbolic links to create. Similar format as in files and files_cc, so the target of the symlink is the
169 key and the value of it is the location of the symlink itself. Only raw strings are allowed as targets,
170 labels are not permitted. Include the file using files or files_cc, then symlink to its location.
171 """,
172 ),
173 "fsspecs": attr.label_list(
174 default = [],
175 doc = """
176 List of file system specs (metropolis.node.build.fsspec.FSSpec) to also include in the resulting image.
177 These will be merged with all other given attributes.
178 """,
179 providers = [FSSpecInfo],
Serge Bazanskia3938142022-04-04 17:04:47 +0200180 allow_files = True,
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100181 ),
182
183 # Tool
184 "_mkcpio": attr.label(
185 default = Label("//metropolis/node/build/mkcpio"),
186 executable = True,
187 cfg = "exec",
188 ),
189
190 # Allow for transitions to be attached to this rule.
191 "_whitelist_function_transition": attr.label(
192 default = "@bazel_tools//tools/whitelists/function_transition_whitelist",
193 ),
194 },
195)
196
197def _erofs_image_impl(ctx):
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100198 fs_name = ctx.label.name + ".img"
199 fs_out = ctx.actions.declare_file(fs_name)
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100200
Serge Bazanskia3938142022-04-04 17:04:47 +0200201 _fsspec_core_impl(ctx, ctx.executable._mkerofs, fs_out)
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100202
203 return [DefaultInfo(files = depset([fs_out]))]
204
205erofs_image = rule(
206 implementation = _erofs_image_impl,
207 doc = """
208 Build an EROFS. All files specified in files, files_cc and all specified symlinks will be contained.
209 Executable files will have their permissions set to 0555, non-executable files will have
210 their permissions set to 0444. All parent directories will be created with 0555 permissions.
211 """,
212 attrs = {
213 "files": attr.label_keyed_string_dict(
214 mandatory = True,
215 allow_files = True,
216 doc = """
217 Dictionary of Labels to String, placing a given Label's output file in the EROFS at the location
218 specified by the String value. The specified labels must only have a single output.
219 """,
220 # Attach pure transition to ensure all binaries added to the initramfs are pure/static binaries.
221 cfg = build_pure_transition,
222 ),
223 "files_cc": attr.label_keyed_string_dict(
224 allow_files = True,
225 doc = """
226 Special case of 'files' for compilation targets that need to be built with the musl toolchain like
227 go_binary targets which need cgo or cc_binary targets.
228 """,
229 # Attach static transition to all files_cc inputs to ensure they are built with musl and static.
230 cfg = build_static_transition,
231 ),
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100232 "symlinks": attr.string_dict(
233 default = {},
234 doc = """
235 Symbolic links to create. Similar format as in files and files_cc, so the target of the symlink is the
236 key and the value of it is the location of the symlink itself. Only raw strings are allowed as targets,
237 labels are not permitted. Include the file using files or files_cc, then symlink to its location.
238 """,
239 ),
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100240 "fsspecs": attr.label_list(
241 default = [],
242 doc = """
243 List of file system specs (metropolis.node.build.fsspec.FSSpec) to also include in the resulting image.
244 These will be merged with all other given attributes.
245 """,
246 providers = [FSSpecInfo],
Serge Bazanskia3938142022-04-04 17:04:47 +0200247 allow_files = True,
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100248 ),
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100249
250 # Tools, implicit dependencies.
251 "_mkerofs": attr.label(
252 default = Label("//metropolis/node/build/mkerofs"),
253 executable = True,
254 cfg = "host",
255 ),
256
257 # Allow for transitions to be attached to this rule.
258 "_whitelist_function_transition": attr.label(
259 default = "@bazel_tools//tools/whitelists/function_transition_whitelist",
260 ),
261 },
262)
Mateusz Zalegae2bf5742022-01-25 19:36:08 +0100263
264# VerityConfig is emitted by verity_image, and contains a file enclosing a
265# singular dm-verity target table.
266VerityConfig = provider(
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100267 "Configuration necessary to mount a single dm-verity target.",
268 fields = {
269 "table": "A file containing the dm-verity target table. See: https://www.kernel.org/doc/html/latest/admin-guide/device-mapper/verity.html",
270 },
Mateusz Zalegae2bf5742022-01-25 19:36:08 +0100271)
272
273def _verity_image_impl(ctx):
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100274 """
275 Create a new file containing the source image data together with the Verity
276 metadata appended to it, and provide an associated DeviceMapper Verity target
277 table in a separate file, through VerityConfig provider.
278 """
Mateusz Zalegae2bf5742022-01-25 19:36:08 +0100279
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100280 # Run mkverity.
281 image = ctx.actions.declare_file(ctx.attr.name + ".img")
282 table = ctx.actions.declare_file(ctx.attr.name + ".dmt")
283 ctx.actions.run(
284 mnemonic = "GenVerityImage",
285 progress_message = "Generating a dm-verity image",
286 inputs = [ctx.file.source],
287 outputs = [
288 image,
289 table,
290 ],
291 executable = ctx.file._mkverity,
292 arguments = [
293 "-input=" + ctx.file.source.path,
294 "-output=" + image.path,
295 "-table=" + table.path,
296 "-data_alias=" + ctx.attr.rootfs_partlabel,
297 "-hash_alias=" + ctx.attr.rootfs_partlabel,
298 ],
Mateusz Zalegae2bf5742022-01-25 19:36:08 +0100299 )
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100300
301 return [
302 DefaultInfo(
303 files = depset([image]),
304 runfiles = ctx.runfiles(files = [image]),
305 ),
306 VerityConfig(
307 table = table,
308 ),
309 ]
Mateusz Zalegae2bf5742022-01-25 19:36:08 +0100310
311verity_image = rule(
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100312 implementation = _verity_image_impl,
313 doc = """
Mateusz Zalegae2bf5742022-01-25 19:36:08 +0100314 Build a dm-verity target image by appending Verity metadata to the source
315 image. A corresponding dm-verity target table will be made available
316 through VerityConfig provider.
317 """,
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100318 attrs = {
319 "source": attr.label(
320 doc = "A source image.",
321 allow_single_file = True,
322 ),
323 "rootfs_partlabel": attr.string(
324 doc = "GPT partition label of the rootfs to be used with dm-mod.create.",
325 default = "PARTLABEL=METROPOLIS-SYSTEM",
326 ),
327 "_mkverity": attr.label(
328 doc = "The mkverity executable needed to generate the image.",
329 default = "//metropolis/node/build/mkverity",
330 allow_single_file = True,
331 executable = True,
332 cfg = "host",
333 ),
334 },
Mateusz Zalegae2bf5742022-01-25 19:36:08 +0100335)