firecracker
186 строк · 5.1 Кб
1# Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2# SPDX-License-Identifier: Apache-2.0
3
4"""
5Common helpers to create Buildkite pipelines
6"""
7
8import argparse
9import json
10import subprocess
11from pathlib import Path
12
13DEFAULT_INSTANCES = [
14"c5n.metal", # Intel Skylake
15"m5n.metal", # Intel Cascade Lake
16"m6i.metal", # Intel Icelake
17"m6a.metal", # AMD Milan
18"m6g.metal", # Graviton2
19"m7g.metal", # Graviton3
20]
21
22DEFAULT_PLATFORMS = [
23("al2", "linux_4.14"),
24("al2", "linux_5.10"),
25("al2023", "linux_6.1"),
26]
27
28
29def overlay_dict(base: dict, update: dict):
30"""Overlay a dict over a base one"""
31base = base.copy()
32for key, val in update.items():
33if key in base and isinstance(val, dict):
34base[key] = overlay_dict(base.get(key, {}), val)
35else:
36base[key] = val
37return base
38
39
40def field_fmt(field, args):
41"""If `field` is a string, interpolate variables in `args`"""
42if not isinstance(field, str):
43return field
44return field.format(**args)
45
46
47def dict_fmt(dict_tmpl, args):
48"""Apply field_fmt over a hole dict"""
49res = {}
50for key, val in dict_tmpl.items():
51if isinstance(val, dict):
52res[key] = dict_fmt(val, args)
53else:
54res[key] = field_fmt(val, args)
55return res
56
57
58def group(label, command, instances, platforms, **kwargs):
59"""
60Generate a group step with specified parameters, for each instance+kernel
61combination
62
63https://buildkite.com/docs/pipelines/group-step
64"""
65# Use the 1st character of the group name (should be an emoji)
66label1 = label[0]
67steps = []
68commands = command
69if isinstance(command, str):
70commands = [command]
71for instance in instances:
72for os, kv in platforms:
73# fill any templated variables
74args = {"instance": instance, "os": os, "kv": kv}
75step = {
76"command": [cmd.format(**args) for cmd in commands],
77"label": f"{label1} {instance} {os} {kv}",
78"agents": args,
79}
80step_kwargs = dict_fmt(kwargs, args)
81step = overlay_dict(step_kwargs, step)
82steps.append(step)
83
84return {"group": label, "steps": steps}
85
86
87def pipeline_to_json(pipeline):
88"""Serialize a pipeline dictionary to JSON"""
89return json.dumps(pipeline, indent=4, sort_keys=True, ensure_ascii=False)
90
91
92def get_changed_files(branch):
93"""
94Get all files changed since `branch`
95"""
96stdout = subprocess.check_output(["git", "diff", "--name-only", branch])
97return [Path(line) for line in stdout.decode().splitlines()]
98
99
100def run_all_tests(changed_files):
101"""
102Check if we should run all tests, based on the files that have been changed
103"""
104
105# run the whole test suite if either of:
106# - any file changed that is not documentation nor GitHub action config file
107# - no files changed
108return not changed_files or any(
109x.suffix != ".md" and not (x.parts[0] == ".github" and x.suffix == ".yml")
110for x in changed_files
111)
112
113
114def devtool_test(devtool_opts=None, pytest_opts=None, binary_dir=None):
115"""Generate a `devtool test` command"""
116cmds = []
117parts = ["./tools/devtool -y test"]
118if devtool_opts:
119parts.append(devtool_opts)
120parts.append("--")
121if binary_dir is not None:
122cmds.append(f'buildkite-agent artifact download "{binary_dir}/$(uname -m)/*" .')
123cmds.append(f"chmod -v a+x {binary_dir}/**/*")
124parts.append(f"--binary-dir=../{binary_dir}/$(uname -m)")
125if pytest_opts:
126parts.append(pytest_opts)
127cmds.append(" ".join(parts))
128return cmds
129
130
131class DictAction(argparse.Action):
132"""An argparse action that can receive a nested dictionary
133
134Examples:
135
136--step-param a/b/c=3
137{"a": {"b": {"c": 3}}}
138"""
139
140def __init__(self, option_strings, dest, nargs=None, **kwargs):
141if nargs is not None:
142raise ValueError("nargs not allowed")
143super().__init__(option_strings, dest, **kwargs)
144
145def __call__(self, parser, namespace, value, option_string=None):
146res = getattr(namespace, self.dest, {})
147key_str, val = value.split("=", maxsplit=1)
148keys = key_str.split("/")
149update = {keys[-1]: val}
150for key in list(reversed(keys))[1:]:
151update = {key: update}
152res = overlay_dict(res, update)
153setattr(namespace, self.dest, res)
154
155
156COMMON_PARSER = argparse.ArgumentParser()
157COMMON_PARSER.add_argument(
158"--instances",
159required=False,
160nargs="+",
161default=DEFAULT_INSTANCES,
162)
163COMMON_PARSER.add_argument(
164"--platforms",
165metavar="OS-KV",
166required=False,
167nargs="+",
168default=DEFAULT_PLATFORMS,
169type=lambda arg: tuple(arg.split("-", maxsplit=1)),
170)
171COMMON_PARSER.add_argument(
172"--step-param",
173metavar="PARAM=VALUE",
174help="parameters to add to each step",
175required=False,
176action=DictAction,
177default={},
178type=str,
179)
180COMMON_PARSER.add_argument(
181"--binary-dir",
182help="Use the Firecracker binaries from this path",
183required=False,
184default=None,
185type=str,
186)
187