blob: d28280b9dde8dd9b5915a53f5ee28436c75369b0 [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
Serge Bazanski662b5b32020-12-21 13:49:00 +010053def _node_initramfs_impl(ctx):
Serge Bazanski140bddc2020-06-05 21:01:19 +020054 """
55 Generate an lz4-compressed initramfs based on a label/file list.
56 """
57
58 # Generate config file for gen_init_cpio that describes the initramfs to build.
59 cpio_list_name = ctx.label.name + ".cpio_list"
60 cpio_list = ctx.actions.declare_file(cpio_list_name)
61
62 # Start out with some standard initramfs device files.
63 cpio_list_content = [
64 "dir /dev 0755 0 0",
65 "nod /dev/console 0600 0 0 c 5 1",
66 "nod /dev/null 0644 0 0 c 1 3",
67 "nod /dev/kmsg 0644 0 0 c 1 11",
68 "nod /dev/ptmx 0644 0 0 c 5 2",
69 ]
70
71 # Find all directories that need to be created.
72 directories_needed = []
73 for _, p in ctx.attr.files.items():
74 if not p.startswith("/"):
75 fail("file {} invalid: must begin with /".format(p))
76
77 # Get all intermediate directories on path to file
78 parts = p.split("/")[1:-1]
79 directories_needed.append(parts)
80
Lorenz Brun5e4fc2d2020-09-22 18:35:15 +020081 for _, p in ctx.attr.files_cc.items():
82 if not p.startswith("/"):
83 fail("file {} invalid: must begin with /".format(p))
84
85 # Get all intermediate directories on path to file
86 parts = p.split("/")[1:-1]
87 directories_needed.append(parts)
88
Serge Bazanski140bddc2020-06-05 21:01:19 +020089 # Extend with extra directories defined by user.
90 for p in ctx.attr.extra_dirs:
91 if not p.startswith("/"):
92 fail("directory {} invalid: must begin with /".format(p))
93
94 parts = p.split("/")[1:]
95 directories_needed.append(parts)
96
97 directories = []
98 for parts in directories_needed:
99 # Turn directory parts [usr, local, bin] into successive subpaths [/usr, /usr/local, /usr/local/bin].
100 last = ""
101 for part in parts:
102 last += "/" + part
103
104 # TODO(q3k): this is slow - this should be a set instead, but starlark doesn't implement them.
105 # For the amount of files we're dealing with this doesn't matter, but all stars are pointing towards this
106 # becoming accidentally quadratic at some point in the future.
107 if last not in directories:
108 directories.append(last)
109
110 # Append instructions to create directories.
111 # Serendipitously, the directories should already be in the right order due to us not using a set to create the
112 # list. They might not be in an elegant order (ie, if files [/foo/one/one, /bar, /foo/two/two] are request, the
113 # order will be [/foo, /foo/one, /bar, /foo/two]), but that's fine.
114 for d in directories:
115 cpio_list_content.append("dir {} 0755 0 0".format(d))
116
117 # Append instructions to add files.
118 inputs = []
119 for label, p in ctx.attr.files.items():
120 # Figure out if this is an executable.
121 is_executable = True
122
123 di = label[DefaultInfo]
124 if di.files_to_run.executable == None:
125 # Generated non-executable files will have DefaultInfo.files_to_run.executable == None
126 is_executable = False
127 elif di.files_to_run.executable.is_source:
128 # Source files will have executable.is_source == True
129 is_executable = False
130
131 # Ensure only single output is declared.
132 # If you hit this error, figure out a better logic to find what file you need, maybe looking at providers other
133 # than DefaultInfo.
134 files = di.files.to_list()
135 if len(files) > 1:
136 fail("file {} has more than one output: {}", p, files)
137 src = files[0]
138 inputs.append(src)
139
140 mode = "0755" if is_executable else "0444"
141
142 cpio_list_content.append("file {} {} {} 0 0".format(p, src.path, mode))
143
Lorenz Brun5e4fc2d2020-09-22 18:35:15 +0200144 for label, p in ctx.attr.files_cc.items():
145 # Figure out if this is an executable.
146 is_executable = True
147
148 di = label[DefaultInfo]
149 if di.files_to_run.executable == None:
150 # Generated non-executable files will have DefaultInfo.files_to_run.executable == None
151 is_executable = False
152 elif di.files_to_run.executable.is_source:
153 # Source files will have executable.is_source == True
154 is_executable = False
155
156 # Ensure only single output is declared.
157 # If you hit this error, figure out a better logic to find what file you need, maybe looking at providers other
158 # than DefaultInfo.
159 files = di.files.to_list()
160 if len(files) > 1:
161 fail("file {} has more than one output: {}", p, files)
162 src = files[0]
163 inputs.append(src)
164
165 mode = "0755" if is_executable else "0444"
166
167 cpio_list_content.append("file {} {} {} 0 0".format(p, src.path, mode))
168
Serge Bazanski140bddc2020-06-05 21:01:19 +0200169 # Write cpio_list.
170 ctx.actions.write(cpio_list, "\n".join(cpio_list_content))
171
172 gen_init_cpio = ctx.executable._gen_init_cpio
173 savestdout = ctx.executable._savestdout
174 lz4 = ctx.executable._lz4
175
176 # Generate 'raw' (uncompressed) initramfs
177 initramfs_raw_name = ctx.label.name
178 initramfs_raw = ctx.actions.declare_file(initramfs_raw_name)
179 ctx.actions.run(
180 outputs = [initramfs_raw],
181 inputs = [cpio_list] + inputs,
182 tools = [savestdout, gen_init_cpio],
183 executable = savestdout,
184 arguments = [initramfs_raw.path, gen_init_cpio.path, cpio_list.path],
185 )
186
187 # Compress raw initramfs using lz4c.
188 initramfs_name = ctx.label.name + ".lz4"
189 initramfs = ctx.actions.declare_file(initramfs_name)
190 ctx.actions.run(
191 outputs = [initramfs],
192 inputs = [initramfs_raw],
193 tools = [savestdout, lz4],
194 executable = lz4.path,
195 arguments = ["-l", initramfs_raw.path, initramfs.path],
196 )
197
Lorenz Brunddd6caf2021-03-04 17:16:04 +0100198 # TODO(q3k): Document why this is needed
199 return [DefaultInfo(runfiles = ctx.runfiles(files = [initramfs]), files = depset([initramfs]))]
Serge Bazanski140bddc2020-06-05 21:01:19 +0200200
Serge Bazanski662b5b32020-12-21 13:49:00 +0100201node_initramfs = rule(
202 implementation = _node_initramfs_impl,
Serge Bazanski140bddc2020-06-05 21:01:19 +0200203 doc = """
Serge Bazanski662b5b32020-12-21 13:49:00 +0100204 Build a node initramfs. The initramfs will contain a basic /dev directory and all the files specified by the
Serge Bazanski140bddc2020-06-05 21:01:19 +0200205 `files` attribute. Executable files will have their permissions set to 0755, non-executable files will have
206 their permissions set to 0444. All parent directories will be created with 0755 permissions.
207 """,
208 attrs = {
209 "files": attr.label_keyed_string_dict(
210 mandatory = True,
211 allow_files = True,
212 doc = """
213 Dictionary of Labels to String, placing a given Label's output file in the initramfs at the location
214 specified by the String value. The specified labels must only have a single output.
215 """,
Serge Bazanskic3ae7582020-06-08 17:15:26 +0200216 # Attach pure transition to ensure all binaries added to the initramfs are pure/static binaries.
217 cfg = build_pure_transition,
Serge Bazanski140bddc2020-06-05 21:01:19 +0200218 ),
Lorenz Brun5e4fc2d2020-09-22 18:35:15 +0200219 "files_cc": attr.label_keyed_string_dict(
220 allow_files = True,
221 doc = """
222 Special case of 'files' for compilation targets that need to be built with the musl toolchain like
223 go_binary targets which need cgo or cc_binary targets.
224 """,
225 # Attach static transition to all files_cc inputs to ensure they are built with musl and static.
226 cfg = build_static_transition,
227 ),
Serge Bazanski140bddc2020-06-05 21:01:19 +0200228 "extra_dirs": attr.string_list(
229 default = [],
230 doc = """
231 Extra directories to create. These will be created in addition to all the directories required to
232 contain the files specified in the `files` attribute.
233 """,
234 ),
235
236 # Tools, implicit dependencies.
237 "_gen_init_cpio": attr.label(
238 default = Label("@linux//:gen_init_cpio"),
239 executable = True,
240 cfg = "host",
241 ),
242 "_lz4": attr.label(
243 default = Label("@com_github_lz4_lz4//programs:lz4"),
244 executable = True,
245 cfg = "host",
246 ),
247 "_savestdout": attr.label(
248 default = Label("//build/savestdout"),
249 executable = True,
250 cfg = "host",
251 ),
Serge Bazanskic3ae7582020-06-08 17:15:26 +0200252
253 # Allow for transitions to be attached to this rule.
254 "_whitelist_function_transition": attr.label(
255 default = "@bazel_tools//tools/whitelists/function_transition_whitelist",
256 ),
Serge Bazanski140bddc2020-06-05 21:01:19 +0200257 },
258)
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100259
260def _erofs_image_impl(ctx):
261 """
262 Generate an EROFS filesystem based on a label/file list.
263 """
264
265 # Generate config file for gen_init_cpio that describes the initramfs to build.
266 fs_spec_name = ctx.label.name + ".prototxt"
267 fs_spec = ctx.actions.declare_file(fs_spec_name)
268
269 fs_files = []
270 inputs = []
271 for label, p in ctx.attr.files.items() + ctx.attr.files_cc.items():
272 if not p.startswith("/"):
273 fail("file {} invalid: must begin with /".format(p))
274
275 # Figure out if this is an executable.
276 is_executable = True
277
278 di = label[DefaultInfo]
279 if di.files_to_run.executable == None:
280 # Generated non-executable files will have DefaultInfo.files_to_run.executable == None
281 is_executable = False
282 elif di.files_to_run.executable.is_source:
283 # Source files will have executable.is_source == True
284 is_executable = False
285
286 # Ensure only single output is declared.
287 # If you hit this error, figure out a better logic to find what file you need, maybe looking at providers other
288 # than DefaultInfo.
289 files = di.files.to_list()
290 if len(files) > 1:
291 fail("file {} has more than one output: {}", p, files)
292 src = files[0]
293 inputs.append(src)
294
295 mode = 0o555 if is_executable else 0o444
296 fs_files.append(struct(path = p, source_path = src.path, mode = mode, uid = 0, gid = 0))
297
298 fs_dirs = []
299 for p in ctx.attr.extra_dirs:
300 if not p.startswith("/"):
301 fail("directory {} invalid: must begin with /".format(p))
302
303 fs_dirs.append(struct(path = p, mode = 0o555, uid = 0, gid = 0))
304
305 fs_symlinks = []
306 for target, p in ctx.attr.symlinks.items():
307 fs_symlinks.append(struct(path = p, target_path = target))
308
309 fs_spec_content = struct(file = fs_files, directory = fs_dirs, symbolic_link = fs_symlinks)
310 ctx.actions.write(fs_spec, fs_spec_content.to_proto())
311
312 fs_name = ctx.label.name + ".img"
313 fs_out = ctx.actions.declare_file(fs_name)
314 ctx.actions.run(
315 outputs = [fs_out],
316 inputs = [fs_spec] + inputs,
317 tools = [ctx.executable._mkerofs],
318 executable = ctx.executable._mkerofs,
319 arguments = ["-out", fs_out.path, "-spec", fs_spec.path],
320 )
321
322 return [DefaultInfo(files = depset([fs_out]))]
323
324erofs_image = rule(
325 implementation = _erofs_image_impl,
326 doc = """
327 Build an EROFS. All files specified in files, files_cc and all specified symlinks will be contained.
328 Executable files will have their permissions set to 0555, non-executable files will have
329 their permissions set to 0444. All parent directories will be created with 0555 permissions.
330 """,
331 attrs = {
332 "files": attr.label_keyed_string_dict(
333 mandatory = True,
334 allow_files = True,
335 doc = """
336 Dictionary of Labels to String, placing a given Label's output file in the EROFS at the location
337 specified by the String value. The specified labels must only have a single output.
338 """,
339 # Attach pure transition to ensure all binaries added to the initramfs are pure/static binaries.
340 cfg = build_pure_transition,
341 ),
342 "files_cc": attr.label_keyed_string_dict(
343 allow_files = True,
344 doc = """
345 Special case of 'files' for compilation targets that need to be built with the musl toolchain like
346 go_binary targets which need cgo or cc_binary targets.
347 """,
348 # Attach static transition to all files_cc inputs to ensure they are built with musl and static.
349 cfg = build_static_transition,
350 ),
351 "extra_dirs": attr.string_list(
352 default = [],
353 doc = """
354 Extra directories to create. These will be created in addition to all the directories required to
355 contain the files specified in the `files` attribute.
356 """,
357 ),
358 "symlinks": attr.string_dict(
359 default = {},
360 doc = """
361 Symbolic links to create. Similar format as in files and files_cc, so the target of the symlink is the
362 key and the value of it is the location of the symlink itself. Only raw strings are allowed as targets,
363 labels are not permitted. Include the file using files or files_cc, then symlink to its location.
364 """,
365 ),
366
367 # Tools, implicit dependencies.
368 "_mkerofs": attr.label(
369 default = Label("//metropolis/node/build/mkerofs"),
370 executable = True,
371 cfg = "host",
372 ),
373
374 # Allow for transitions to be attached to this rule.
375 "_whitelist_function_transition": attr.label(
376 default = "@bazel_tools//tools/whitelists/function_transition_whitelist",
377 ),
378 },
379)