blob: 722a8280d5672ccf0f31ae26f9112db0cb4c2a9a [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:
Serge Bazanskia3938142022-04-04 17:04:47 +0200110 # Skip files-as-fsspecs.
111 if FSSpecInfo not in fsspec:
112 continue
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100113 fsspecInfo = fsspec[FSSpecInfo]
114 extra_specs.append(fsspecInfo.spec)
115 for f in fsspecInfo.referenced:
116 inputs.append(f)
117
Serge Bazanskia3938142022-04-04 17:04:47 +0200118 for file in ctx.files.fsspecs:
119 # Raw .fsspec prototext. No referenced data allowed.
120 extra_specs.append(file)
121
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100122 ctx.actions.run(
123 outputs = [output_file],
124 inputs = [fs_spec] + inputs + extra_specs,
125 tools = [tool],
126 executable = tool,
127 arguments = ["-out", output_file.path, fs_spec.path] + [s.path for s in extra_specs],
128 )
129 return
130
131def _node_initramfs_impl(ctx):
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100132 initramfs_name = ctx.label.name + ".cpio.lz4"
133 initramfs = ctx.actions.declare_file(initramfs_name)
134
Serge Bazanskia3938142022-04-04 17:04:47 +0200135 _fsspec_core_impl(ctx, ctx.executable._mkcpio, initramfs)
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100136
137 # TODO(q3k): Document why this is needed
138 return [DefaultInfo(runfiles = ctx.runfiles(files = [initramfs]), files = depset([initramfs]))]
139
140node_initramfs = rule(
141 implementation = _node_initramfs_impl,
142 doc = """
143 Build a node initramfs. The initramfs will contain a basic /dev directory and all the files specified by the
144 `files` attribute. Executable files will have their permissions set to 0755, non-executable files will have
145 their permissions set to 0444. All parent directories will be created with 0755 permissions.
146 """,
147 attrs = {
148 "files": attr.label_keyed_string_dict(
149 mandatory = True,
150 allow_files = True,
151 doc = """
152 Dictionary of Labels to String, placing a given Label's output file in the initramfs at the location
153 specified by the String value. The specified labels must only have a single output.
154 """,
155 # Attach pure transition to ensure all binaries added to the initramfs are pure/static binaries.
156 cfg = build_pure_transition,
157 ),
158 "files_cc": attr.label_keyed_string_dict(
159 allow_files = True,
160 doc = """
161 Special case of 'files' for compilation targets that need to be built with the musl toolchain like
162 go_binary targets which need cgo or cc_binary targets.
163 """,
164 # Attach static transition to all files_cc inputs to ensure they are built with musl and static.
165 cfg = build_static_transition,
166 ),
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100167 "symlinks": attr.string_dict(
168 default = {},
169 doc = """
170 Symbolic links to create. Similar format as in files and files_cc, so the target of the symlink is the
171 key and the value of it is the location of the symlink itself. Only raw strings are allowed as targets,
172 labels are not permitted. Include the file using files or files_cc, then symlink to its location.
173 """,
174 ),
175 "fsspecs": attr.label_list(
176 default = [],
177 doc = """
178 List of file system specs (metropolis.node.build.fsspec.FSSpec) to also include in the resulting image.
179 These will be merged with all other given attributes.
180 """,
181 providers = [FSSpecInfo],
Serge Bazanskia3938142022-04-04 17:04:47 +0200182 allow_files = True,
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100183 ),
184
185 # Tool
186 "_mkcpio": attr.label(
187 default = Label("//metropolis/node/build/mkcpio"),
188 executable = True,
189 cfg = "exec",
190 ),
191
192 # Allow for transitions to be attached to this rule.
193 "_whitelist_function_transition": attr.label(
194 default = "@bazel_tools//tools/whitelists/function_transition_whitelist",
195 ),
196 },
197)
198
199def _erofs_image_impl(ctx):
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100200 fs_name = ctx.label.name + ".img"
201 fs_out = ctx.actions.declare_file(fs_name)
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100202
Serge Bazanskia3938142022-04-04 17:04:47 +0200203 _fsspec_core_impl(ctx, ctx.executable._mkerofs, fs_out)
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100204
205 return [DefaultInfo(files = depset([fs_out]))]
206
207erofs_image = rule(
208 implementation = _erofs_image_impl,
209 doc = """
210 Build an EROFS. All files specified in files, files_cc and all specified symlinks will be contained.
211 Executable files will have their permissions set to 0555, non-executable files will have
212 their permissions set to 0444. All parent directories will be created with 0555 permissions.
213 """,
214 attrs = {
215 "files": attr.label_keyed_string_dict(
216 mandatory = True,
217 allow_files = True,
218 doc = """
219 Dictionary of Labels to String, placing a given Label's output file in the EROFS at the location
220 specified by the String value. The specified labels must only have a single output.
221 """,
222 # Attach pure transition to ensure all binaries added to the initramfs are pure/static binaries.
223 cfg = build_pure_transition,
224 ),
225 "files_cc": attr.label_keyed_string_dict(
226 allow_files = True,
227 doc = """
228 Special case of 'files' for compilation targets that need to be built with the musl toolchain like
229 go_binary targets which need cgo or cc_binary targets.
230 """,
231 # Attach static transition to all files_cc inputs to ensure they are built with musl and static.
232 cfg = build_static_transition,
233 ),
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100234 "symlinks": attr.string_dict(
235 default = {},
236 doc = """
237 Symbolic links to create. Similar format as in files and files_cc, so the target of the symlink is the
238 key and the value of it is the location of the symlink itself. Only raw strings are allowed as targets,
239 labels are not permitted. Include the file using files or files_cc, then symlink to its location.
240 """,
241 ),
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100242 "fsspecs": attr.label_list(
243 default = [],
244 doc = """
245 List of file system specs (metropolis.node.build.fsspec.FSSpec) to also include in the resulting image.
246 These will be merged with all other given attributes.
247 """,
248 providers = [FSSpecInfo],
Serge Bazanskia3938142022-04-04 17:04:47 +0200249 allow_files = True,
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100250 ),
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100251
252 # Tools, implicit dependencies.
253 "_mkerofs": attr.label(
254 default = Label("//metropolis/node/build/mkerofs"),
255 executable = True,
256 cfg = "host",
257 ),
258
259 # Allow for transitions to be attached to this rule.
260 "_whitelist_function_transition": attr.label(
261 default = "@bazel_tools//tools/whitelists/function_transition_whitelist",
262 ),
263 },
264)
Mateusz Zalegae2bf5742022-01-25 19:36:08 +0100265
266# VerityConfig is emitted by verity_image, and contains a file enclosing a
267# singular dm-verity target table.
268VerityConfig = provider(
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100269 "Configuration necessary to mount a single dm-verity target.",
270 fields = {
271 "table": "A file containing the dm-verity target table. See: https://www.kernel.org/doc/html/latest/admin-guide/device-mapper/verity.html",
272 },
Mateusz Zalegae2bf5742022-01-25 19:36:08 +0100273)
274
275def _verity_image_impl(ctx):
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100276 """
277 Create a new file containing the source image data together with the Verity
278 metadata appended to it, and provide an associated DeviceMapper Verity target
279 table in a separate file, through VerityConfig provider.
280 """
Mateusz Zalegae2bf5742022-01-25 19:36:08 +0100281
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100282 # Run mkverity.
283 image = ctx.actions.declare_file(ctx.attr.name + ".img")
284 table = ctx.actions.declare_file(ctx.attr.name + ".dmt")
285 ctx.actions.run(
286 mnemonic = "GenVerityImage",
287 progress_message = "Generating a dm-verity image",
288 inputs = [ctx.file.source],
289 outputs = [
290 image,
291 table,
292 ],
293 executable = ctx.file._mkverity,
294 arguments = [
295 "-input=" + ctx.file.source.path,
296 "-output=" + image.path,
297 "-table=" + table.path,
298 "-data_alias=" + ctx.attr.rootfs_partlabel,
299 "-hash_alias=" + ctx.attr.rootfs_partlabel,
300 ],
Mateusz Zalegae2bf5742022-01-25 19:36:08 +0100301 )
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100302
303 return [
304 DefaultInfo(
305 files = depset([image]),
306 runfiles = ctx.runfiles(files = [image]),
307 ),
308 VerityConfig(
309 table = table,
310 ),
311 ]
Mateusz Zalegae2bf5742022-01-25 19:36:08 +0100312
313verity_image = rule(
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100314 implementation = _verity_image_impl,
315 doc = """
Mateusz Zalegae2bf5742022-01-25 19:36:08 +0100316 Build a dm-verity target image by appending Verity metadata to the source
317 image. A corresponding dm-verity target table will be made available
318 through VerityConfig provider.
319 """,
Lorenz Brunb6a9d3c2022-01-27 18:56:20 +0100320 attrs = {
321 "source": attr.label(
322 doc = "A source image.",
323 allow_single_file = True,
324 ),
325 "rootfs_partlabel": attr.string(
326 doc = "GPT partition label of the rootfs to be used with dm-mod.create.",
327 default = "PARTLABEL=METROPOLIS-SYSTEM",
328 ),
329 "_mkverity": attr.label(
330 doc = "The mkverity executable needed to generate the image.",
331 default = "//metropolis/node/build/mkverity",
332 allow_single_file = True,
333 executable = True,
334 cfg = "host",
335 ),
336 },
Mateusz Zalegae2bf5742022-01-25 19:36:08 +0100337)