blob: c456a94dea85fd3a44b905852ce907f87d2db915 [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
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +010061def _fsspec_core_impl(ctx, tool, output_file, builtin_fsspec):
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
64 input from the `files`,`files_cc`, `extra_dirs`, `symlinks` and `fsspecs`
65 attributes and calls `tool` with the `-out` parameter pointing to
66 `output_file` 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
100 fs_dirs = []
101 for p in ctx.attr.extra_dirs:
102 if not p.startswith("/"):
103 fail("directory {} invalid: must begin with /".format(p))
104
105 fs_dirs.append(struct(path = p, mode = 0o555, uid = 0, gid = 0))
106
107 fs_symlinks = []
108 for target, p in ctx.attr.symlinks.items():
109 fs_symlinks.append(struct(path = p, target_path = target))
110
111 fs_spec_content = struct(file = fs_files, directory = fs_dirs, symbolic_link = fs_symlinks)
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100112 ctx.actions.write(fs_spec, proto.encode_text(fs_spec_content))
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100113
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100114 extra_specs = []
115 if builtin_fsspec != None:
116 builtin_fsspec_file = ctx.actions.declare_file(ctx.label.name + "-builtin.prototxt")
117 ctx.actions.write(builtin_fsspec_file, proto.encode_text(builtin_fsspec))
118 extra_specs.append(builtin_fsspec_file)
119
120 for fsspec in ctx.attr.fsspecs:
121 fsspecInfo = fsspec[FSSpecInfo]
122 extra_specs.append(fsspecInfo.spec)
123 for f in fsspecInfo.referenced:
124 inputs.append(f)
125
126 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):
136 # At least /dev/console and /dev/null are required to exist for Linux
137 # to properly boot an init inside the initramfs. Here we additionally
138 # include important device nodes like /dev/kmsg and /dev/ptmx which
139 # might need to be available before a proper device manager is launched.
140 builtin_fsspec = struct(special_file = [
141 struct(path = "/dev/console", mode = 0o600, major = 5, minor = 1),
142 struct(path = "/dev/ptmx", mode = 0o644, major = 5, minor = 2),
143 struct(path = "/dev/null", mode = 0o644, major = 1, minor = 3),
144 struct(path = "/dev/kmsg", mode = 0o644, major = 1, minor = 11),
145 ])
146
147 initramfs_name = ctx.label.name + ".cpio.lz4"
148 initramfs = ctx.actions.declare_file(initramfs_name)
149
150 _fsspec_core_impl(ctx, ctx.executable._mkcpio, initramfs, builtin_fsspec)
151
152 # TODO(q3k): Document why this is needed
153 return [DefaultInfo(runfiles = ctx.runfiles(files = [initramfs]), files = depset([initramfs]))]
154
155node_initramfs = rule(
156 implementation = _node_initramfs_impl,
157 doc = """
158 Build a node initramfs. The initramfs will contain a basic /dev directory and all the files specified by the
159 `files` attribute. Executable files will have their permissions set to 0755, non-executable files will have
160 their permissions set to 0444. All parent directories will be created with 0755 permissions.
161 """,
162 attrs = {
163 "files": attr.label_keyed_string_dict(
164 mandatory = True,
165 allow_files = True,
166 doc = """
167 Dictionary of Labels to String, placing a given Label's output file in the initramfs at the location
168 specified by the String value. The specified labels must only have a single output.
169 """,
170 # Attach pure transition to ensure all binaries added to the initramfs are pure/static binaries.
171 cfg = build_pure_transition,
172 ),
173 "files_cc": attr.label_keyed_string_dict(
174 allow_files = True,
175 doc = """
176 Special case of 'files' for compilation targets that need to be built with the musl toolchain like
177 go_binary targets which need cgo or cc_binary targets.
178 """,
179 # Attach static transition to all files_cc inputs to ensure they are built with musl and static.
180 cfg = build_static_transition,
181 ),
182 "extra_dirs": attr.string_list(
183 default = [],
184 doc = """
185 Extra directories to create. These will be created in addition to all the directories required to
186 contain the files specified in the `files` attribute.
187 """,
188 ),
189 "symlinks": attr.string_dict(
190 default = {},
191 doc = """
192 Symbolic links to create. Similar format as in files and files_cc, so the target of the symlink is the
193 key and the value of it is the location of the symlink itself. Only raw strings are allowed as targets,
194 labels are not permitted. Include the file using files or files_cc, then symlink to its location.
195 """,
196 ),
197 "fsspecs": attr.label_list(
198 default = [],
199 doc = """
200 List of file system specs (metropolis.node.build.fsspec.FSSpec) to also include in the resulting image.
201 These will be merged with all other given attributes.
202 """,
203 providers = [FSSpecInfo],
204 ),
205
206 # Tool
207 "_mkcpio": attr.label(
208 default = Label("//metropolis/node/build/mkcpio"),
209 executable = True,
210 cfg = "exec",
211 ),
212
213 # Allow for transitions to be attached to this rule.
214 "_whitelist_function_transition": attr.label(
215 default = "@bazel_tools//tools/whitelists/function_transition_whitelist",
216 ),
217 },
218)
219
220def _erofs_image_impl(ctx):
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100221 fs_name = ctx.label.name + ".img"
222 fs_out = ctx.actions.declare_file(fs_name)
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100223
224 _fsspec_core_impl(ctx, ctx.executable._mkerofs, fs_out, None)
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100225
226 return [DefaultInfo(files = depset([fs_out]))]
227
228erofs_image = rule(
229 implementation = _erofs_image_impl,
230 doc = """
231 Build an EROFS. All files specified in files, files_cc and all specified symlinks will be contained.
232 Executable files will have their permissions set to 0555, non-executable files will have
233 their permissions set to 0444. All parent directories will be created with 0555 permissions.
234 """,
235 attrs = {
236 "files": attr.label_keyed_string_dict(
237 mandatory = True,
238 allow_files = True,
239 doc = """
240 Dictionary of Labels to String, placing a given Label's output file in the EROFS at the location
241 specified by the String value. The specified labels must only have a single output.
242 """,
243 # Attach pure transition to ensure all binaries added to the initramfs are pure/static binaries.
244 cfg = build_pure_transition,
245 ),
246 "files_cc": attr.label_keyed_string_dict(
247 allow_files = True,
248 doc = """
249 Special case of 'files' for compilation targets that need to be built with the musl toolchain like
250 go_binary targets which need cgo or cc_binary targets.
251 """,
252 # Attach static transition to all files_cc inputs to ensure they are built with musl and static.
253 cfg = build_static_transition,
254 ),
255 "extra_dirs": attr.string_list(
256 default = [],
257 doc = """
258 Extra directories to create. These will be created in addition to all the directories required to
259 contain the files specified in the `files` attribute.
260 """,
261 ),
262 "symlinks": attr.string_dict(
263 default = {},
264 doc = """
265 Symbolic links to create. Similar format as in files and files_cc, so the target of the symlink is the
266 key and the value of it is the location of the symlink itself. Only raw strings are allowed as targets,
267 labels are not permitted. Include the file using files or files_cc, then symlink to its location.
268 """,
269 ),
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100270 "fsspecs": attr.label_list(
271 default = [],
272 doc = """
273 List of file system specs (metropolis.node.build.fsspec.FSSpec) to also include in the resulting image.
274 These will be merged with all other given attributes.
275 """,
276 providers = [FSSpecInfo],
277 ),
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100278
279 # Tools, implicit dependencies.
280 "_mkerofs": attr.label(
281 default = Label("//metropolis/node/build/mkerofs"),
282 executable = True,
283 cfg = "host",
284 ),
285
286 # Allow for transitions to be attached to this rule.
287 "_whitelist_function_transition": attr.label(
288 default = "@bazel_tools//tools/whitelists/function_transition_whitelist",
289 ),
290 },
291)
Mateusz Zalegae2bf5742022-01-25 19:36:08 +0100292
293# VerityConfig is emitted by verity_image, and contains a file enclosing a
294# singular dm-verity target table.
295VerityConfig = provider(
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100296 "Configuration necessary to mount a single dm-verity target.",
297 fields = {
298 "table": "A file containing the dm-verity target table. See: https://www.kernel.org/doc/html/latest/admin-guide/device-mapper/verity.html",
299 },
Mateusz Zalegae2bf5742022-01-25 19:36:08 +0100300)
301
302def _verity_image_impl(ctx):
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100303 """
304 Create a new file containing the source image data together with the Verity
305 metadata appended to it, and provide an associated DeviceMapper Verity target
306 table in a separate file, through VerityConfig provider.
307 """
Mateusz Zalegae2bf5742022-01-25 19:36:08 +0100308
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100309 # Run mkverity.
310 image = ctx.actions.declare_file(ctx.attr.name + ".img")
311 table = ctx.actions.declare_file(ctx.attr.name + ".dmt")
312 ctx.actions.run(
313 mnemonic = "GenVerityImage",
314 progress_message = "Generating a dm-verity image",
315 inputs = [ctx.file.source],
316 outputs = [
317 image,
318 table,
319 ],
320 executable = ctx.file._mkverity,
321 arguments = [
322 "-input=" + ctx.file.source.path,
323 "-output=" + image.path,
324 "-table=" + table.path,
325 "-data_alias=" + ctx.attr.rootfs_partlabel,
326 "-hash_alias=" + ctx.attr.rootfs_partlabel,
327 ],
Mateusz Zalegae2bf5742022-01-25 19:36:08 +0100328 )
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100329
330 return [
331 DefaultInfo(
332 files = depset([image]),
333 runfiles = ctx.runfiles(files = [image]),
334 ),
335 VerityConfig(
336 table = table,
337 ),
338 ]
Mateusz Zalegae2bf5742022-01-25 19:36:08 +0100339
340verity_image = rule(
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100341 implementation = _verity_image_impl,
342 doc = """
Mateusz Zalegae2bf5742022-01-25 19:36:08 +0100343 Build a dm-verity target image by appending Verity metadata to the source
344 image. A corresponding dm-verity target table will be made available
345 through VerityConfig provider.
346 """,
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100347 attrs = {
348 "source": attr.label(
349 doc = "A source image.",
350 allow_single_file = True,
351 ),
352 "rootfs_partlabel": attr.string(
353 doc = "GPT partition label of the rootfs to be used with dm-mod.create.",
354 default = "PARTLABEL=METROPOLIS-SYSTEM",
355 ),
356 "_mkverity": attr.label(
357 doc = "The mkverity executable needed to generate the image.",
358 default = "//metropolis/node/build/mkverity",
359 allow_single_file = True,
360 executable = True,
361 cfg = "host",
362 ),
363 },
Mateusz Zalegae2bf5742022-01-25 19:36:08 +0100364)