uhd/tools/debs/uhd_ubuntu_deb.py
Virendra Kakade eb785ff23b ci: Build UHD DEBs with B310 support
Add nib310rio-dev to the Debian build dependencies so the UHD package
build fails if the B310 userspace header is unavailable instead of
silently disabling B300 support.

The Ubuntu installer pipeline downloads the nib310rio Debian package
artifact and exposes it to pbuilder as a local apt repository. This lets
pbuilder satisfy the new dependency inside the clean build root where
CMake checks for nib310rio/nib310rio.h.
2026-09-15 23:44:29 +01:00

208 lines
8.0 KiB
Python

#!/usr/bin/env python3
# uhd_ubuntu_deb.py
# This script is used to generate UHD dsc, changes, and source archives for upload to Launchpad.
# After dsc generation, pbuilder is called to build the debs. This sets up an envionment similar to
# the Launchpad build process. To build the dsc you must have pbuilder, debootstrap, and devscripts
# installed and have run:
#
# pbuilder create --debootstrapopts --variant=buildd --distribution=<target distro>
#
# See here for more on pbuilder: https://wiki.ubuntu.com/PbuilderHowto
import argparse
import os
import pathlib
import re
import shlex
import shutil
import stat
import subprocess
import sys
import tarfile
import tempfile
supported_ubuntu_releases = ["jammy", "noble", "plucky", "resolute"]
tar_command = "tar --exclude='.git*' --exclude='./debian' --exclude='*.swp' --exclude='./fpga/usrp1' --exclude='./fpga/usrp2' --exclude='./fpga/usrp3/top/b200' --exclude='./fpga/usrp3/top/b2xxmini' --exclude='./fpga/usrp3/lib/*_200' --exclude='build' --exclude='./images/*.pyc' --exclude='./images/uhd-*' --exclude='tags' --exclude='.ci' --exclude='.clang*' -cJf {}/uhd_{}.orig.tar.xz ."
debuild_command = "debuild -S -i -sa"
debuild_nosign = " -uc -us"
def main(args):
if not pathlib.Path("host").exists():
print("Check path. This script must be run on uhd base path")
sys.exit(1)
if not args.release in supported_ubuntu_releases:
print(
"Unsupported release selected. Supported releases are {}".format(
supported_ubuntu_releases
)
)
sys.exit(1)
# Determine UHD version number
uhd_version = ""
orig_release = ""
with open("host/cmake/debian/changelog") as cl:
first_line = cl.readline()
uhd_version = re.findall(r"[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+", first_line)
if len(uhd_version) != 1:
print("uhd_version in changelog malformed. Check host/cmake/debian/changelog")
sys.exit(1)
uhd_version = uhd_version[0]
orig_release = re.findall("[A-Za-z_]*;", first_line)
if len(orig_release) != 1:
print("orig_release in changelog malformed. Check host/cmake/debian/changelog")
sys.exit(1)
orig_release = orig_release[0].replace(";", "")
# Compress UHD source
if pathlib.Path(args.buildpath).exists():
shutil.rmtree(args.buildpath)
os.mkdir(args.buildpath)
print("Compressing UHD Source...")
result = subprocess.run(shlex.split(tar_command.format(args.buildpath, uhd_version)))
if result.returncode:
print("Compressing source failed")
sys.exit(result.returncode)
# Extract UHD source to build folder
uhd_deb_build_path = pathlib.Path(args.buildpath, "uhd-{}".format(uhd_version))
if uhd_deb_build_path.exists():
shutil.rmtree(uhd_deb_build_path)
with tarfile.open(
args.buildpath + "/uhd_{}.orig.tar.xz".format(uhd_version), "r:xz"
) as uhd_archive:
uhd_archive.extractall(path=uhd_deb_build_path)
# Copy debian build files to build folder
shutil.copytree("host/cmake/debian", uhd_deb_build_path / "debian")
# Copy version specific files (e.g. debian/control.noble to debian/control)
for version_specific_file in pathlib.Path(uhd_deb_build_path / "debian").glob(
f"*.{args.release}"
):
shutil.copy2(version_specific_file, version_specific_file.with_suffix(""))
shutil.copy2("host/utils/uhd-usrp.rules", uhd_deb_build_path / "debian/uhd-host.udev")
with open(uhd_deb_build_path / "debian/uhd-host.manpages", "w") as man_file:
for file in uhd_deb_build_path.glob("host/docs/*.1"):
man_file.write(os.path.relpath(file, uhd_deb_build_path) + "\n")
man_file.write("\n")
for file in uhd_deb_build_path.glob("debian/*.in"):
os.remove(file)
# Modify changelog for selected release
with open(uhd_deb_build_path / "debian/changelog", "r+") as cl:
cl_text = cl.read()
cl_text = re.sub(orig_release, args.release, cl_text)
cl_text = re.sub("0ubuntu1", "0ubuntu1~{}1".format(args.release), cl_text)
cl.seek(0)
cl.write(cl_text)
cl.truncate()
# Generate dsc file
result = ""
print("Running debuild / dsc generation")
if args.sign:
result = subprocess.run(shlex.split(debuild_command), cwd=uhd_deb_build_path)
else:
result = subprocess.run(
shlex.split(debuild_command + debuild_nosign), cwd=uhd_deb_build_path
)
if result.returncode:
print("debuild / dsc generation failed")
sys.exit(result.returncode)
# Build debs using dsc
if not args.nobuild:
print("Building deb with dsc using pbuilder for {}".format(args.release))
os.mkdir(args.buildpath + "/result")
pbuilder_command = [
"sudo",
"pbuilder",
"build",
"--buildresult",
"./result",
]
# --othermirror is silently ignored by "pbuilder build" (it only applies
# to "pbuilder create"/"update --override-config"), so a local apt repo
# for extra build-deps has to be added via a hook script instead. The D
# hook runs after bind mounts are active but before build-deps are
# resolved, which is the documented place to call apt-get update.
if args.pbuilder_extra_apt_repo:
repo_dir = os.path.abspath(args.pbuilder_extra_apt_repo)
hook_dir = tempfile.mkdtemp(prefix="uhd-pbuilder-hooks-")
hook_path = os.path.join(hook_dir, "D50uhd_local_apt_repo")
with open(hook_path, "w") as hook_file:
hook_file.write(
"#!/bin/bash\n"
"set -euo pipefail\n"
"echo 'deb [trusted=yes] file:{repo_dir} ./' "
"> /etc/apt/sources.list.d/uhd-local-extra.list\n"
"apt-get update\n".format(repo_dir=repo_dir)
)
os.chmod(
hook_path, os.stat(hook_path).st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
)
pbuilder_command.extend(
[
"--bindmounts",
repo_dir,
"--hookdir",
hook_dir,
]
)
pbuilder_command.append(
"uhd_{}-0ubuntu1~{}1.dsc".format(uhd_version, args.release)
)
result = subprocess.run(pbuilder_command, cwd=args.buildpath)
if result.returncode:
print("pbuilder failed")
sys.exit(result.returncode)
# Upload dsc to Launchpad
if args.upload:
if not args.sign:
print("Uploading requires signing. Add --sign.")
sys.exit(1)
result = subprocess.run(
shlex.split(
"dput ppa:ettusresearch/uhd uhd_${}-0ubuntu1~${}1_source.changes".format(
uhd_version, args.release
)
),
cwd=args.buildpath,
)
if result.returncode:
print("PPA upload failed")
sys.exit(result.returncode)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(
"--sign", action="store_true", help="Signs files with GPG key. Not required for test builds"
)
parser.add_argument(
"--upload", action="store_true", help="Uploads to launchpad. Requires--sign"
)
parser.add_argument("--nobuild", action="store_true", help="Disables building using pbuilder")
parser.add_argument(
"--buildpath",
type=str,
required=True,
help="Output path for build files. " "Will get nuked before creating packages.",
)
parser.add_argument(
"--pbuilder-extra-apt-repo",
type=str,
default="",
help="Extra apt repository path to bind mount into pbuilder and use for build dependencies.",
)
parser.add_argument(
"release",
type=str,
help="Ubuntu release version. This must match pbuilder create --distribution if building.",
)
args = parser.parse_args()
main(args)