Skip to content

Commit 3620f12

Browse files
committed
scripts: Add build-deb to build a Debian package
Expects a yaml file such as: dsc_url: "https://snapshot.debian.org/archive/debian/20250416T143525Z/pool/main/h/hello/hello_2.10-5.dsc" dsc_sha256sum: "319f8c377eb48597798b699f72ab853f3439a991583ddd51bf94b415373ffdfe" debdiff_file: "hello_2.10-5qcom1.debdiff" suite: "trixie" Signed-off-by: Loïc Minier <[email protected]>
1 parent 97040b1 commit 3620f12

File tree

1 file changed

+136
-0
lines changed

1 file changed

+136
-0
lines changed

scripts/build-deb.py

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
3+
# SPDX-License-Identifier: BSD-3-Clause
4+
5+
# Build a Debian package using sbuild; input is a yaml configuration
6+
# with base dsc URL, debdiff file to apply and target series
7+
8+
import argparse
9+
import glob
10+
import hashlib
11+
import os
12+
import subprocess
13+
import tempfile
14+
import shutil
15+
import yaml
16+
17+
# Parse command-line arguments
18+
parser = argparse.ArgumentParser(
19+
description="Build a Debian source package with a debdiff."
20+
)
21+
parser.add_argument(
22+
'--config',
23+
type=str,
24+
required=True,
25+
help='Path to the YAML configuration file'
26+
)
27+
parser.add_argument(
28+
'--output-dir',
29+
type=str,
30+
help='Optional directory to preserve resulting files'
31+
)
32+
args = parser.parse_args()
33+
34+
# Load configuration from YAML file
35+
with open(args.config, 'r') as f:
36+
config = yaml.safe_load(f)
37+
38+
# Create a temporary directory
39+
with tempfile.TemporaryDirectory() as temp_dir:
40+
# Download the original Debian source package using dget
41+
dsc_url = config['dsc_url']
42+
subprocess.run(['dget', '-d', dsc_url], cwd=temp_dir, check=True)
43+
print("✅ Original source package downloaded successfully.")
44+
45+
# Determine the .dsc file name
46+
dsc_file = os.path.join(temp_dir, os.path.basename(dsc_url))
47+
48+
# Verify the SHA256 checksum of the .dsc file
49+
with open(dsc_file, 'rb') as f:
50+
file_data = f.read()
51+
sha256sum = hashlib.sha256(file_data).hexdigest()
52+
53+
expected_sha256 = config['dsc_sha256sum']
54+
if sha256sum != expected_sha256:
55+
raise ValueError(
56+
f"SHA256 checksum does not match!\n"
57+
f"Expected: {expected_sha256}\n"
58+
f"Actual: {sha256sum}"
59+
)
60+
print("✅ Checksum of original source package matched.")
61+
62+
# Unpack the source package
63+
subprocess.run(['dpkg-source', '-x', dsc_file], cwd=temp_dir, check=True)
64+
65+
# Find the unpacked directory
66+
unpacked_dirs = [
67+
d for d in os.listdir(temp_dir)
68+
if os.path.isdir(os.path.join(temp_dir, d))
69+
]
70+
if not unpacked_dirs:
71+
raise RuntimeError("No unpacked source directory found.")
72+
unpacked_dir = os.path.join(temp_dir, unpacked_dirs[0])
73+
74+
# Apply the debdiff
75+
debdiff_path = config.get('debdiff_file')
76+
if debdiff_path:
77+
if not os.path.isabs(debdiff_path):
78+
config_dir = os.path.dirname(args.config)
79+
debdiff_path = os.path.abspath(os.path.join(config_dir,
80+
debdiff_path))
81+
subprocess.run(
82+
['patch', '-p1', '-i', debdiff_path], cwd=unpacked_dir, check=True)
83+
print("✅ Debdiff applied successfully.")
84+
else:
85+
print("⚠️ No debdiff provided.")
86+
87+
# Determine number of CPUs for parallel build
88+
nproc_result = subprocess.run(
89+
['nproc'], stdout=subprocess.PIPE, text=True,
90+
check=True)
91+
num_cpus = nproc_result.stdout.strip()
92+
93+
# Build the resulting source package using sbuild
94+
suite = config['suite']
95+
subprocess.run(
96+
['sbuild', '--verbose', '-d', suite, '--no-clean-source',
97+
'--dpkg-source-opt=--no-check', f'-j{num_cpus}'],
98+
cwd=unpacked_dir,
99+
check=True
100+
)
101+
print("✅ Source package built successfully.")
102+
103+
# Copy results if output-dir is specified
104+
if args.output_dir:
105+
os.makedirs(args.output_dir, exist_ok=True)
106+
107+
# Find the .changes file
108+
changes_files = glob.glob(os.path.join(temp_dir, '*.changes'))
109+
if not changes_files:
110+
raise RuntimeError("No .changes file found to extract artifacts.")
111+
changes_file = changes_files[0]
112+
113+
# Run dcmd to get the list of files
114+
result = subprocess.run(
115+
['dcmd', changes_file],
116+
cwd=temp_dir,
117+
check=True,
118+
stdout=subprocess.PIPE,
119+
text=True
120+
)
121+
122+
# Parse the output to get file paths
123+
files_to_copy = [
124+
line.split()[-1] for line in result.stdout.strip().splitlines()]
125+
126+
# Add any *.build files
127+
build_files = glob.glob(os.path.join(temp_dir, '*.build'))
128+
files_to_copy.extend(build_files)
129+
130+
# Copy files
131+
for file_path in files_to_copy:
132+
full_path = os.path.join(temp_dir, file_path)
133+
if os.path.exists(full_path):
134+
shutil.copy(full_path, args.output_dir)
135+
136+
print(f"📦 Results copied to: {args.output_dir}")

0 commit comments

Comments
 (0)