Skip to content

Commit 65d5d3d

Browse files
committed
Add a step to check if patches apply before invoking TuxSuite
There is no point in spinning up TuxSuite for a build if any patches that we need to apply are going to fail to do so. At the beginning of a build, check if the patches apply to the current repository and reference. This is done by downloading a tarball of the source, running 'git init' and committing the files, then finally running 'git quiltimport' to make sure that that patches still apply. It would be simpler to use 'git clone --depth=1' but cloning can be expensive on the server side and these jobs may run at the same time. Closes: #796 Signed-off-by: Nathan Chancellor <[email protected]>
1 parent 0a7c934 commit 65d5d3d

File tree

2 files changed

+131
-2
lines changed

2 files changed

+131
-2
lines changed

generator/generate_workflow.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,24 @@ def sanitize_job_name(name):
8585
return "_" + hashlib.new("md5", name.encode("utf-8")).hexdigest()
8686

8787

88+
def check_patches_job_setup(repo, ref, tree_name):
89+
return {
90+
'check_patches': {
91+
'name': 'Check that patches are applicable',
92+
'runs-on': 'ubuntu-latest',
93+
'steps': [
94+
{
95+
'uses': 'actions/checkout@v4',
96+
},
97+
{
98+
'name': 'check-patches-apply.py',
99+
'run': f"python3 scripts/check-patches-apply.py --patches-dir patches/{tree_name} --repo {repo} --ref {ref}",
100+
},
101+
],
102+
}
103+
} # yapf: disable
104+
105+
88106
def check_cache_job_setup(repo, ref, toolchain):
89107
with LLVM_TOT_VERSION.open(encoding='utf-8') as fd:
90108
llvm_tot_version = fd.read().strip()
@@ -102,6 +120,7 @@ def check_cache_job_setup(repo, ref, toolchain):
102120
"name": "Check Cache",
103121
"runs-on": "ubuntu-latest",
104122
"container": f"tuxmake/x86_64_{toolchain}",
123+
"needs": "check_patches",
105124
"env": {
106125
"GIT_REPO": repo,
107126
"GIT_REF": ref
@@ -147,7 +166,7 @@ def tuxsuite_setups(job_name, tuxsuite_yml, repo, ref):
147166
# https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on
148167
"runs-on": "ubuntu-latest",
149168
"container": "tuxsuite/tuxsuite",
150-
"needs": "check_cache",
169+
"needs": ["check_cache", "check_patches"],
151170
"env": {
152171
"TUXSUITE_TOKEN": "${{ secrets.TUXSUITE_TOKEN }}",
153172
"REPO_SCOPED_PAT": "${{ secrets.REPO_SCOPED_PAT }}"
@@ -213,7 +232,7 @@ def get_steps(build, build_set):
213232
return {
214233
sanitize_job_name(name): {
215234
"runs-on": "ubuntu-latest",
216-
"needs": [f"kick_tuxsuite_{build_set}", "check_cache"],
235+
"needs": [f"kick_tuxsuite_{build_set}", "check_cache", "check_patches"],
217236
"name": name,
218237
"if": "${{ needs.check_cache.outputs.status != 'pass' }}",
219238
"env": {
@@ -293,6 +312,7 @@ def print_builds(config, tree_name, llvm_version):
293312
workflow = initial_workflow(workflow_name, cron_schedule, tuxsuite_yml,
294313
github_yml)
295314

315+
workflow['jobs'].update(check_patches_job_setup(repo, ref, tree_name))
296316
workflow['jobs'].update(check_cache_job_setup(repo, ref, toolchain))
297317
workflow["jobs"].update(
298318
tuxsuite_setups("defconfigs", tuxsuite_yml, repo, ref))

scripts/check-patch-apply.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
#!/usr/bin/env python3
2+
# pylint: disable=invalid-name
3+
4+
from argparse import ArgumentParser
5+
from pathlib import Path
6+
import os
7+
import subprocess
8+
import sys
9+
from tempfile import TemporaryDirectory
10+
11+
parser = ArgumentParser(
12+
description='Check that patches apply to their tree before running CI')
13+
parser.add_argument(
14+
'-p',
15+
'--patches-dir',
16+
help='Path to patches directory (can be relative or absolute)',
17+
required=True,
18+
type=Path)
19+
parser.add_argument('-r',
20+
'--repo',
21+
help='URL to git repository',
22+
required=True)
23+
parser.add_argument('-R',
24+
'--ref',
25+
help='Git reference to apply patches upon',
26+
required=True)
27+
args = parser.parse_args()
28+
29+
# If patches directory does not exist, there is nothing to check
30+
if not (patches_dir := args.patches_dir.resolve()).exists():
31+
print(f"{patches_dir} does not exist, exiting 0...")
32+
sys.exit(0)
33+
34+
# There should be patches in there due to check-patches.py but we should double
35+
# check and fail if not
36+
if not list(patches_dir.glob('*.patch')):
37+
print(f"{patches_dir} does not contain any patches?")
38+
sys.exit(1)
39+
40+
# Rather that invoke 'git clone', which can be expensive for servers depending
41+
# on the frequency and duration of requests, we fetch a tarball and 'git init'
42+
# that.
43+
with TemporaryDirectory() as workdir:
44+
# Fetch the tarball from the repository. This is different for each type of
45+
# tree that we support.
46+
if args.repo.startswith(
47+
'https://git.kernel.org/pub/scm/linux/kernel/git/'):
48+
# https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git -> 'linux'
49+
if (base_repo := args.repo.rsplit('/', 1)[1]).endswith('.git'):
50+
base_repo = base_repo[:-len('.git')]
51+
52+
# https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git ->
53+
# https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/snapshot/linux-master.tar.gz
54+
tarball_url = f"{args.repo}/snapshot/{base_repo}-{args.ref}.tar.gz"
55+
strip = 1
56+
elif 'googlesource.com' in args.repo:
57+
tarball_url = f"{args.repo}/+archive/refs/heads/{args.ref}.tar.gz"
58+
strip = 0
59+
else:
60+
raise RuntimeError(f"Do not know how to download {args.repo}?")
61+
62+
try:
63+
print(f"Downloading {tarball_url}...")
64+
tarball = subprocess.run(['curl', '-LSs', tarball_url],
65+
capture_output=True,
66+
check=True).stdout
67+
68+
print(f"Extracting {tarball_url}...")
69+
subprocess.run(
70+
['tar', '-C', workdir, f"--strip-components={strip}", '-xzf-'],
71+
check=True,
72+
input=tarball)
73+
except subprocess.CalledProcessError:
74+
print(
75+
'Downloading or extracting tarball failed! As this may be flakiness on the server end, exiting 0 to have TuxSuite fail later...'
76+
)
77+
sys.exit(0)
78+
79+
# Ensure that we can always commit regardless of whether user.name or
80+
# user.email are set in whatever environment we are running in, as this is
81+
# a temporary tree.
82+
git_name = 'check-patch-apply.py'
83+
git_email = f"{git_name}@{os.uname().nodename}.local"
84+
git_commit_env_vars = {
85+
**os.environ, # clone the environment, as subprocess may need it
86+
'GIT_AUTHOR_NAME': git_name,
87+
'GIT_AUTHOR_EMAIL': git_email,
88+
'GIT_COMMITTER_NAME': git_name,
89+
'GIT_COMMITTER_EMAIL': git_email,
90+
} # yapf: disable
91+
92+
print(f"Creating initial git repository in {workdir}...")
93+
subprocess.run(['git', 'init', '-q'], check=True, cwd=workdir)
94+
95+
print(f"Adding files in {workdir}...")
96+
subprocess.run(['git', 'add', '.'], check=True, cwd=workdir)
97+
98+
print(
99+
f"Creating initial commit '{args.repo} @ {args.ref}' in {workdir}...")
100+
subprocess.run(['git', 'commit', '-m', f"{args.repo} @ {args.ref}", '-q'],
101+
check=True,
102+
cwd=workdir,
103+
env=git_commit_env_vars)
104+
105+
print(f"Applying patches in {workdir}...")
106+
subprocess.run(['git', 'quiltimport', '--patches', patches_dir],
107+
check=True,
108+
cwd=workdir,
109+
env=git_commit_env_vars)

0 commit comments

Comments
 (0)