Skip to content

Commit 29e549f

Browse files
committed
scripts: Introduce kernel_source.py
`kernel_source.py` contains functions used to check version of desired kernel, download, extract kernel source to specific directory and install the headers of specific architecture. Signed-off-by: Ruoqing He <[email protected]>
1 parent 5302337 commit 29e549f

File tree

1 file changed

+164
-0
lines changed

1 file changed

+164
-0
lines changed

scripts/lib/kernel_source.py

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
# Copyright 2025 © Institute of Software, CAS. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
import os
5+
import re
6+
import tarfile
7+
import requests
8+
import subprocess
9+
import tempfile
10+
from lib import SUPPORT_ARCHS
11+
12+
KERNEL_ORG_CDN = "https://cdn.kernel.org/pub/linux/kernel"
13+
14+
15+
def prepare_source(args):
16+
check_kernel_version(args.version)
17+
18+
# Create `temp_dir` under `/tmp`
19+
temp_dir = create_temp_dir(args.version)
20+
21+
# Download kernel tarball from https://cdn.kernel.org/
22+
tarball = download_kernel(args.version, temp_dir)
23+
24+
# Extract kernel source
25+
src_dir = extract_kernel(tarball, temp_dir)
26+
27+
# If arch is not provided, install headers for all supported archs
28+
if args.arch is None:
29+
for arch in SUPPORT_ARCHS:
30+
installed_header_path = install_headers(
31+
src_dir=src_dir,
32+
arch=arch,
33+
install_path=args.install_path,
34+
)
35+
else:
36+
installed_header_path = install_headers(
37+
src_dir=src_dir,
38+
arch=args.arch,
39+
install_path=args.install_path,
40+
)
41+
42+
print(f"\nSuccessfully installed kernel headers to {installed_header_path}")
43+
return installed_header_path
44+
45+
46+
def check_kernel_version(version):
47+
"""
48+
Validate if the input kernel version exists in remote. Supports both X.Y
49+
(namely X.Y.0 and .0 should be omitted) and X.Y.Z formats
50+
"""
51+
# Validate version format
52+
if not re.match(r"^\d+\.\d+(\.\d+)?$", version):
53+
raise ValueError("Invalid version format. Use X.Y or X.Y.Z")
54+
55+
main_ver = version.split(".")[0]
56+
base_url = f"{KERNEL_ORG_CDN}/v{main_ver}.x/"
57+
tarball = f"linux-{version}.tar.xz"
58+
59+
try:
60+
# Fetch content of `base_url`
61+
response = requests.get(base_url, timeout=15)
62+
response.raise_for_status()
63+
64+
# Check for exact filename match
65+
if tarball in response.text:
66+
print(f"Kernel version {version} found in remote")
67+
return
68+
69+
raise RuntimeError(f"Kernel version {version} not found in remote")
70+
71+
except requests.exceptions.HTTPError as e:
72+
if e.response.status_code == 404:
73+
raise RuntimeError(f"Kernel series v{main_ver}.x does not exist")
74+
75+
raise RuntimeError(f"HTTP error ({e.response.status_code}): {str(e)}")
76+
except requests.exceptions.Timeout:
77+
raise RuntimeError("Connection timeout while checking version")
78+
except requests.exceptions.RequestException as e:
79+
raise RuntimeError(f"Network error: {str(e)}")
80+
81+
82+
def create_temp_dir(version):
83+
prefix = f"linux-{version}-source-"
84+
try:
85+
temp_dir = tempfile.TemporaryDirectory(prefix=prefix, dir="/tmp", delete=False)
86+
return temp_dir.name
87+
except OSError as e:
88+
raise RuntimeError(f"Failed to create temp directory: {e}") from e
89+
90+
91+
def download_kernel(version, temp_dir):
92+
version_major = re.match(r"^(\d+)\.\d+(\.\d+)?$", version).group(1)
93+
url = f"{KERNEL_ORG_CDN}/v{version_major}.x/linux-{version}.tar.xz"
94+
tarball_path = os.path.join(temp_dir, f"linux-{version}.tar.xz")
95+
print(f"Downloading {url} to {tarball_path}")
96+
97+
try:
98+
with requests.get(url, stream=True) as response:
99+
response.raise_for_status()
100+
total_size = int(response.headers.get("content-length", 0))
101+
downloaded = 0
102+
103+
with open(tarball_path, "wb") as f:
104+
for chunk in response.iter_content(chunk_size=8192):
105+
f.write(chunk)
106+
downloaded += len(chunk)
107+
if total_size > 0:
108+
progress = downloaded / total_size * 100
109+
print(f"\rDownloading: {progress:.1f}%", end="")
110+
print()
111+
return tarball_path
112+
except Exception as e:
113+
raise RuntimeError(f"Download failed: {e}") from e
114+
115+
116+
def extract_kernel(tarball_path, temp_dir):
117+
print("Extracting...")
118+
try:
119+
with tarfile.open(tarball_path, "r:xz") as tar:
120+
tar.extractall(path=temp_dir)
121+
extract_path = os.path.join(
122+
temp_dir, f"{os.path.basename(tarball_path).split('.tar')[0]}"
123+
)
124+
print(f"Extracted to {extract_path}")
125+
return extract_path
126+
except (tarfile.TarError, IOError) as e:
127+
raise RuntimeError(f"Extraction failed: {e}") from e
128+
129+
130+
def install_headers(src_dir, arch, install_path):
131+
# If install_path is not provided, install to parent directory of src_dir to
132+
# prevent messing up with extracted kernel source code
133+
if install_path is None:
134+
install_path = os.path.dirname(src_dir)
135+
136+
try:
137+
os.makedirs(install_path, exist_ok=True)
138+
139+
abs_install_path = os.path.abspath(
140+
os.path.join(install_path, f"{arch}_headers")
141+
)
142+
print(f"Installing to {abs_install_path}")
143+
result = subprocess.run(
144+
[
145+
"make",
146+
"-C",
147+
f"{src_dir}",
148+
f"ARCH={arch}",
149+
f"INSTALL_HDR_PATH={abs_install_path}",
150+
"headers_install",
151+
],
152+
check=True,
153+
stdout=subprocess.PIPE,
154+
stderr=subprocess.PIPE,
155+
text=True,
156+
)
157+
print(result.stdout)
158+
return install_path
159+
160+
except subprocess.CalledProcessError as e:
161+
raise RuntimeError(
162+
f"Header installation failed:\n{e.output}"
163+
f"Temporary files kept at: {os.path.dirname(src_dir)}"
164+
)

0 commit comments

Comments
 (0)