blob: 39af1923249611259968c1ecff02a263fc1bf39f [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
198 return [DefaultInfo(files = depset([initramfs]))]
199
Serge Bazanski662b5b32020-12-21 13:49:00 +0100200node_initramfs = rule(
201 implementation = _node_initramfs_impl,
Serge Bazanski140bddc2020-06-05 21:01:19 +0200202 doc = """
Serge Bazanski662b5b32020-12-21 13:49:00 +0100203 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 +0200204 `files` attribute. Executable files will have their permissions set to 0755, non-executable files will have
205 their permissions set to 0444. All parent directories will be created with 0755 permissions.
206 """,
207 attrs = {
208 "files": attr.label_keyed_string_dict(
209 mandatory = True,
210 allow_files = True,
211 doc = """
212 Dictionary of Labels to String, placing a given Label's output file in the initramfs at the location
213 specified by the String value. The specified labels must only have a single output.
214 """,
Serge Bazanskic3ae7582020-06-08 17:15:26 +0200215 # Attach pure transition to ensure all binaries added to the initramfs are pure/static binaries.
216 cfg = build_pure_transition,
Serge Bazanski140bddc2020-06-05 21:01:19 +0200217 ),
Lorenz Brun5e4fc2d2020-09-22 18:35:15 +0200218 "files_cc": attr.label_keyed_string_dict(
219 allow_files = True,
220 doc = """
221 Special case of 'files' for compilation targets that need to be built with the musl toolchain like
222 go_binary targets which need cgo or cc_binary targets.
223 """,
224 # Attach static transition to all files_cc inputs to ensure they are built with musl and static.
225 cfg = build_static_transition,
226 ),
Serge Bazanski140bddc2020-06-05 21:01:19 +0200227 "extra_dirs": attr.string_list(
228 default = [],
229 doc = """
230 Extra directories to create. These will be created in addition to all the directories required to
231 contain the files specified in the `files` attribute.
232 """,
233 ),
234
235 # Tools, implicit dependencies.
236 "_gen_init_cpio": attr.label(
237 default = Label("@linux//:gen_init_cpio"),
238 executable = True,
239 cfg = "host",
240 ),
241 "_lz4": attr.label(
242 default = Label("@com_github_lz4_lz4//programs:lz4"),
243 executable = True,
244 cfg = "host",
245 ),
246 "_savestdout": attr.label(
247 default = Label("//build/savestdout"),
248 executable = True,
249 cfg = "host",
250 ),
Serge Bazanskic3ae7582020-06-08 17:15:26 +0200251
252 # Allow for transitions to be attached to this rule.
253 "_whitelist_function_transition": attr.label(
254 default = "@bazel_tools//tools/whitelists/function_transition_whitelist",
255 ),
Serge Bazanski140bddc2020-06-05 21:01:19 +0200256 },
257)
Lorenz Brun6b13bf12021-01-26 19:54:24 +0100258
259def _erofs_image_impl(ctx):
260 """
261 Generate an EROFS filesystem based on a label/file list.
262 """
263
264 # Generate config file for gen_init_cpio that describes the initramfs to build.
265 fs_spec_name = ctx.label.name + ".prototxt"
266 fs_spec = ctx.actions.declare_file(fs_spec_name)
267
268 fs_files = []
269 inputs = []
270 for label, p in ctx.attr.files.items() + ctx.attr.files_cc.items():
271 if not p.startswith("/"):
272 fail("file {} invalid: must begin with /".format(p))
273
274 # Figure out if this is an executable.
275 is_executable = True
276
277 di = label[DefaultInfo]
278 if di.files_to_run.executable == None:
279 # Generated non-executable files will have DefaultInfo.files_to_run.executable == None
280 is_executable = False
281 elif di.files_to_run.executable.is_source:
282 # Source files will have executable.is_source == True
283 is_executable = False
284
285 # Ensure only single output is declared.
286 # If you hit this error, figure out a better logic to find what file you need, maybe looking at providers other
287 # than DefaultInfo.
288 files = di.files.to_list()
289 if len(files) > 1:
290 fail("file {} has more than one output: {}", p, files)
291 src = files[0]
292 inputs.append(src)
293
294 mode = 0o555 if is_executable else 0o444
295 fs_files.append(struct(path = p, source_path = src.path, mode = mode, uid = 0, gid = 0))
296
297 fs_dirs = []
298 for p in ctx.attr.extra_dirs:
299 if not p.startswith("/"):
300 fail("directory {} invalid: must begin with /".format(p))
301
302 fs_dirs.append(struct(path = p, mode = 0o555, uid = 0, gid = 0))
303
304 fs_symlinks = []
305 for target, p in ctx.attr.symlinks.items():
306 fs_symlinks.append(struct(path = p, target_path = target))
307
308 fs_spec_content = struct(file = fs_files, directory = fs_dirs, symbolic_link = fs_symlinks)
309 ctx.actions.write(fs_spec, fs_spec_content.to_proto())
310
311 fs_name = ctx.label.name + ".img"
312 fs_out = ctx.actions.declare_file(fs_name)
313 ctx.actions.run(
314 outputs = [fs_out],
315 inputs = [fs_spec] + inputs,
316 tools = [ctx.executable._mkerofs],
317 executable = ctx.executable._mkerofs,
318 arguments = ["-out", fs_out.path, "-spec", fs_spec.path],
319 )
320
321 return [DefaultInfo(files = depset([fs_out]))]
322
323erofs_image = rule(
324 implementation = _erofs_image_impl,
325 doc = """
326 Build an EROFS. All files specified in files, files_cc and all specified symlinks will be contained.
327 Executable files will have their permissions set to 0555, non-executable files will have
328 their permissions set to 0444. All parent directories will be created with 0555 permissions.
329 """,
330 attrs = {
331 "files": attr.label_keyed_string_dict(
332 mandatory = True,
333 allow_files = True,
334 doc = """
335 Dictionary of Labels to String, placing a given Label's output file in the EROFS at the location
336 specified by the String value. The specified labels must only have a single output.
337 """,
338 # Attach pure transition to ensure all binaries added to the initramfs are pure/static binaries.
339 cfg = build_pure_transition,
340 ),
341 "files_cc": attr.label_keyed_string_dict(
342 allow_files = True,
343 doc = """
344 Special case of 'files' for compilation targets that need to be built with the musl toolchain like
345 go_binary targets which need cgo or cc_binary targets.
346 """,
347 # Attach static transition to all files_cc inputs to ensure they are built with musl and static.
348 cfg = build_static_transition,
349 ),
350 "extra_dirs": attr.string_list(
351 default = [],
352 doc = """
353 Extra directories to create. These will be created in addition to all the directories required to
354 contain the files specified in the `files` attribute.
355 """,
356 ),
357 "symlinks": attr.string_dict(
358 default = {},
359 doc = """
360 Symbolic links to create. Similar format as in files and files_cc, so the target of the symlink is the
361 key and the value of it is the location of the symlink itself. Only raw strings are allowed as targets,
362 labels are not permitted. Include the file using files or files_cc, then symlink to its location.
363 """,
364 ),
365
366 # Tools, implicit dependencies.
367 "_mkerofs": attr.label(
368 default = Label("//metropolis/node/build/mkerofs"),
369 executable = True,
370 cfg = "host",
371 ),
372
373 # Allow for transitions to be attached to this rule.
374 "_whitelist_function_transition": attr.label(
375 default = "@bazel_tools//tools/whitelists/function_transition_whitelist",
376 ),
377 },
378)