Skip to content

Commit 55cf6fd

Browse files
committed
python: Add a simple TuxMake wrapper for kernel.org toolchains
Signed-off-by: Nathan Chancellor <nathan@kernel.org>
1 parent 22a5fd8 commit 55cf6fd

6 files changed

Lines changed: 249 additions & 3 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/usr/bin/env fish
2+
# SPDX-License-Identifier: MIT
3+
# Copyright (C) 2023 Nathan Chancellor
4+
5+
function tuxmake_bld_all -d "Wrapper for tuxmake_bld_all.py"
6+
$PYTHON_SCRIPTS_FOLDER/tuxmake_bld_all.py $argv
7+
end

python/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
__pycache__/
2+
tuxmake/

python/lib/utils.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,36 @@ def print_cmd(command):
1616
print(f"$ {' '.join([shlex.quote(str(elem)) for elem in command])}")
1717

1818

19+
def print_duration(seconds):
20+
days, seconds = divmod(int(seconds), 60 * 60 * 24)
21+
hours, seconds = divmod(seconds, 60 * 60)
22+
minutes, seconds = divmod(seconds, 60)
23+
24+
parts = []
25+
if days:
26+
parts += [f"{days}d"]
27+
if hours:
28+
parts += [f"{hours}h"]
29+
if minutes:
30+
parts += [f"{minutes}m"]
31+
parts += [f"{seconds}s"]
32+
33+
return f"{' '.join(parts)}"
34+
35+
36+
def print_header(string):
37+
border = ''.join(["=" for _ in range(0, len(string) + 6)])
38+
print_cyan(f"\n{border}\n== {string} ==\n{border}\n")
39+
40+
1941
def print_color(color, string):
2042
print(f"{color}{string}\033[0m", flush=True)
2143

2244

45+
def print_cyan(msg):
46+
print_color('\033[01;36m', msg)
47+
48+
2349
def print_green(msg):
2450
print_color('\033[01;32m', msg)
2551

python/scripts/korg_gcc.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,14 @@ def korg_gcc_canonicalize_target(value):
2626
suffix = ''
2727
if value == 'arm':
2828
suffix = '-gnueabi'
29-
elif value == 'arm64': # in case the kernel ARCH value is passed in
30-
value = 'aarch64'
31-
return f"{value}-linux{suffix}"
29+
30+
# in case the kernel ARCH value is passed in, we make an educated guess as
31+
# to what the user intended
32+
kernel_to_gcc = {
33+
'arm64': 'aarch64',
34+
'riscv': 'riscv64',
35+
}
36+
return f"{kernel_to_gcc.get(value, value)}-linux{suffix}"
3237

3338

3439
def supported_korg_gcc_arches():

python/scripts/tuxmake_bld_all.py

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
#!/usr/bin/env python3
2+
# SPDX-License-Identifier: MIT
3+
# Copyright (C) 2023 Nathan Chancellor
4+
5+
from argparse import ArgumentParser
6+
from pathlib import Path
7+
import shutil
8+
import signal
9+
import subprocess
10+
import sys
11+
import time
12+
13+
import korg_gcc
14+
15+
# Until https://gitlab.com/Linaro/tuxmake/-/merge_requests/283 is merged and released
16+
if not (tuxmake_src := Path(__file__).resolve().parents[1].joinpath('tuxmake')).exists():
17+
git_clone_cmd = [
18+
'git', 'clone', '-b', 'allow-override-make-vars', 'https://gitlab.com/Linaro/tuxmake.git',
19+
tuxmake_src
20+
]
21+
subprocess.run(git_clone_cmd, capture_output=True, check=True)
22+
23+
sys.path.insert(0, str(tuxmake_src.parent))
24+
# pylint: disable-next=wrong-import-position
25+
import lib.utils # noqa: E402
26+
27+
sys.path.insert(0, str(tuxmake_src))
28+
# pylint: disable-next=import-error,no-name-in-module,wrong-import-position
29+
import tuxmake.build # noqa: E402
30+
31+
32+
def interrupt_handler(_signum, _frame):
33+
sys.exit(130)
34+
35+
36+
def parse_arguments():
37+
parser = ArgumentParser(description='Do a series of builds with GCC from kernel.org')
38+
39+
supported_architectures = [
40+
'arm',
41+
'arm64',
42+
'i386',
43+
'mips',
44+
'powerpc',
45+
's390',
46+
'riscv',
47+
'x86_64',
48+
]
49+
parser.add_argument('-a',
50+
'--architectures',
51+
choices=supported_architectures,
52+
default=supported_architectures,
53+
help='Architectures to build (default: all supported architectures)',
54+
metavar='TARGETS',
55+
nargs='+')
56+
57+
suppported_toolchains = [f"gcc-{ver}" for ver in korg_gcc.supported_korg_gcc_versions()]
58+
parser.add_argument('-t',
59+
'--toolchains',
60+
choices=suppported_toolchains,
61+
default=[suppported_toolchains[-1]],
62+
help='Toolchains to build kernels with (default: latest GCC release)',
63+
metavar='TOOLCHAINS',
64+
nargs='+')
65+
66+
supported_targets = ['def', 'all']
67+
parser.add_argument('-T',
68+
'--targets',
69+
choices=supported_targets,
70+
default=supported_targets,
71+
help='Targets to build (default: all supported targets)',
72+
metavar='TARGETS',
73+
nargs='+')
74+
75+
default_kernel_source = Path('.').resolve()
76+
parser.add_argument('-C',
77+
'--directory',
78+
default=default_kernel_source,
79+
help='Kernel source to build (default: current working directory)',
80+
metavar='SOURCE')
81+
82+
parser.add_argument(
83+
'-o',
84+
'--output-dir',
85+
help='Output folder for build artifacts (default: build folder in kernel source)')
86+
87+
parser.add_argument('--use-ccache',
88+
action='store_true',
89+
help='Use ccache for builds (default: no caching)')
90+
91+
return parser.parse_args()
92+
93+
94+
def get_kconfigs_for_target(targets):
95+
kconfigs = {
96+
'all': ['allmodconfig', 'allnoconfig'], # allyesconfig is not super useful and slow
97+
'def': ['defconfig'],
98+
}
99+
100+
return [kconfig for target in targets for kconfig in kconfigs[target]]
101+
102+
103+
def get_make_variables(target_arch, toolchain):
104+
make_variables = {}
105+
106+
if 'gcc' in toolchain:
107+
version = int(toolchain.split('-')[1])
108+
make_variables['CROSS_COMPILE'] = korg_gcc.get_gcc_cross_compile(version, target_arch)
109+
if target_arch == 'arm64':
110+
make_variables['CROSS_COMPILE_COMPAT'] = korg_gcc.get_gcc_cross_compile(version, 'arm')
111+
112+
return make_variables
113+
114+
115+
def get_targets(kconfig):
116+
targets = ['default']
117+
if kconfig == 'defconfig':
118+
targets += ['kernel']
119+
return targets
120+
121+
122+
def build_one(tree, output_dir, target_arch, toolchain, wrapper, kconfig, results_file):
123+
bld_str = f"ARCH={target_arch} {kconfig} {toolchain}"
124+
125+
lib.utils.print_header(bld_str)
126+
127+
config_output_dir = f"{output_dir}/{target_arch}/{kconfig}"
128+
Path(config_output_dir).mkdir(exist_ok=True, parents=True)
129+
130+
# pylint: disable-next=c-extension-no-member
131+
result = tuxmake.build.build(tree=tree,
132+
output_dir=config_output_dir,
133+
target_arch=target_arch,
134+
wrapper=wrapper,
135+
kconfig=kconfig,
136+
make_variables=get_make_variables(target_arch, toolchain),
137+
targets=get_targets(kconfig))
138+
139+
duration = 0
140+
passed = True
141+
for info in result.status.values():
142+
duration += info.duration
143+
passed &= info.passed
144+
145+
res_str = 'PASS' if passed else 'FAIL'
146+
duration_str = lib.utils.print_duration(duration)
147+
with results_file.open(encoding='utf-8', mode='a') as file:
148+
file.write(f"{bld_str}: {res_str} in {duration_str}\n")
149+
150+
151+
def process_results(results_file, start_time):
152+
print()
153+
154+
failed = []
155+
passed = []
156+
for line in results_file.read_text(encoding='utf-8').splitlines(keepends=True):
157+
(passed if 'PASS' in line else failed).append(line)
158+
159+
if passed:
160+
print('Successful builds:\n')
161+
print(''.join(passed))
162+
163+
if failed:
164+
print('Failed builds:\n')
165+
print(''.join(failed))
166+
167+
print(f"Total build time: {lib.utils.print_duration(time.time() - start_time)}")
168+
169+
170+
def build_all(linux_folder, out_folder, architectures, targets, toolchains, use_ccache,
171+
results_file):
172+
for toolchain in toolchains:
173+
for target_arch in architectures:
174+
for kconfig in get_kconfigs_for_target(targets):
175+
build_one(tree=linux_folder,
176+
output_dir=out_folder,
177+
target_arch=target_arch,
178+
toolchain=toolchain,
179+
wrapper='ccache' if use_ccache and shutil.which('ccache') else None,
180+
kconfig=kconfig,
181+
results_file=results_file)
182+
183+
184+
if __name__ == '__main__':
185+
signal.signal(signal.SIGINT, interrupt_handler)
186+
187+
args = parse_arguments()
188+
189+
if not (output := args.output_dir):
190+
output = Path(args.directory, 'build')
191+
192+
if (output := Path(output).resolve()).exists():
193+
shutil.rmtree(output)
194+
195+
results = Path(output, 'results.log')
196+
start = time.time()
197+
198+
build_all(linux_folder=Path(args.directory).resolve(),
199+
out_folder=output,
200+
architectures=args.architectures,
201+
targets=args.targets,
202+
toolchains=args.toolchains,
203+
use_ccache=args.use_ccache,
204+
results_file=results)
205+
206+
process_results(results, start)

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
requests
2+
tuxmake

0 commit comments

Comments
 (0)