Copy rpmbuild into the source tree with only what the release flow needs

Keep make_rpm_ubuntu26.py (byte-identical to the validated working tree,
including the package-content validator fixes). It is self-contained
(standard library only, no helpers.py import) and its staging artifacts
land in the workspace root, so nothing else from the outer rpmbuild/
directory is needed:

- helpers.py: zero references from the Ubuntu 26 script (belongs to the
  other platform variants)
- make_rpm.py / make_rpm7.py / make_rpm15.1.py / make_rpm15.2.py:
  other platform packaging (base, RHEL7, CentOS 15.x)
- make_rpmModule.py / make_rpmModule7.py: module packaging
- make_rpmSVOnly.py: SV-only packaging
- make_rpm_extfw.py: external framework packaging
- make_tag.py: release tagging tool
- make_license_rpm.sh + ctc_TDS_license.spec.in + ctc_WMS_license.spec.in:
  CTC license RPM flow
- .git (submodule pointer) and .gitignore (no artifacts are created in
  this directory)
This commit is contained in:
berrux-dev committed 2026-10-13 22:33:37 +02:00
1 parent 862d0dbe2b
commit ba99b74608
1 file changed
+921
@@ -0,0 +1,921 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
DEB package builder for Ubuntu 26.04 - Qt6 Release
Creates 5 separate DEB packages as per customer requirements:
1. qt6-release-runtime - binaries and libraries from S
2. qt6-release-sources - Qt6 C++ sources from S
3. qt6-release-ctest-runtime - CTest executables
4. qt6-release-ctest-sources - CTest source files
5. qt6-release-build-pipeline - Dockerfiles, build scripts, CI/CD files
"""
import argparse
import configparser
import getpass
import os
import re
import shutil
import stat
import subprocess
import sys
from datetime import datetime
from pathlib import Path
def verbose():
verb = os.getenv("EMERGE_VERBOSE")
if verb is not None and verb.isdigit() and int(verb) > 0:
return int(verb)
return 0
def debug(message):
if verbose() >= 1:
print(f"debug: {message}")
return True
def info(message):
if verbose() >= 0:
print(f"info: {message}")
return True
def warning(message):
print(f"warning: {message}")
return True
def error(message):
die(message)
def die(message):
print(f"emerge fatal error: {message}", file=sys.stderr)
sys.exit(1)
def remove_directory(path):
"""Remove directory tree"""
if os.path.exists(path):
shutil.rmtree(path)
def copy_file_to_dest_dir(src_file, dest_dir):
"""Copy a single file to destination directory"""
os.makedirs(dest_dir, exist_ok=True)
shutil.copy2(src_file, dest_dir)
def is_dds_file(filepath):
"""Check if a file or directory path contains DDS-related content that should be excluded
Args:
filepath: Path to check
Returns:
True if file should be excluded (is DDS-related), False otherwise
"""
# Convert to lowercase for case-insensitive matching
path_lower = filepath.lower()
# Exclude files/directories with 'dds' in the name
if 'dds' in path_lower:
return True
return False
def copy_src_dir_to_dest_dir(src_dir, dest_dir, release_scope=False, file_filter=None):
"""Copy source directory to destination with optional filtering
Args:
src_dir: Source directory
dest_dir: Destination directory
release_scope: If True, only copy release scope directories
file_filter: Optional function to filter files (filepath) -> bool
"""
os.makedirs(dest_dir, exist_ok=True)
# Release scope directories
release_dirs = [
"TetraIF",
"Z/src",
"Lts/Common"
]
if release_scope:
info(f"Copying only release scope directories: {release_dirs}")
# Also copy root-level build files needed for CMake
root_build_files = ['CMakeLists.txt', 'CTestCustom.ctest']
root_build_dirs = ['cmake']
for bf in root_build_files:
src_file = os.path.join(src_dir, bf)
if os.path.exists(src_file):
debug(f" Copying root file {bf}...")
shutil.copy2(src_file, os.path.join(dest_dir, bf))
for bd in root_build_dirs:
src_bdir = os.path.join(src_dir, bd)
dest_bdir = os.path.join(dest_dir, bd)
if os.path.exists(src_bdir):
debug(f" Copying root directory {bd}...")
shutil.copytree(src_bdir, dest_bdir, dirs_exist_ok=True)
for rel_dir in release_dirs:
src_subdir = os.path.join(src_dir, rel_dir)
dest_subdir = os.path.join(dest_dir, rel_dir)
if os.path.exists(src_subdir):
debug(f" Copying {rel_dir}...")
for root, dirs, files in os.walk(src_subdir):
# Skip build directories, hidden directories, deb_build_tmp (prevent recursion), and DDS-related directories
dirs[:] = [d for d in dirs if not d.startswith('.')
and d not in ['build', 'cmake-build-debug', 'cmake-build-release', 'deb_build_tmp']
and (file_filter is None or not file_filter(os.path.join(root, d)))]
for file in files:
src_file = os.path.join(root, file)
rel_path = os.path.relpath(src_file, src_subdir)
dest_file = os.path.join(dest_subdir, rel_path)
# Apply file filter if provided (skip file if filter returns True)
if file_filter and file_filter(src_file):
continue
os.makedirs(os.path.dirname(dest_file), exist_ok=True)
shutil.copy2(src_file, dest_file)
else:
# Copy entire source tree
for root, dirs, files in os.walk(src_dir):
# Skip build directories, hidden directories, deb_build_tmp (prevent recursion), and DDS-related directories
dirs[:] = [d for d in dirs if not d.startswith('.')
and d not in ['build', 'cmake-build-debug', 'cmake-build-release', 'deb_build_tmp']
and (file_filter is None or not file_filter(os.path.join(root, d)))]
for file in files:
src_file = os.path.join(root, file)
rel_path = os.path.relpath(src_file, src_dir)
dest_file = os.path.join(dest_dir, rel_path)
# Apply file filter if provided (skip file if filter returns True)
if file_filter and file_filter(src_file):
continue
os.makedirs(os.path.dirname(dest_file), exist_ok=True)
shutil.copy2(src_file, dest_file)
def find_test_executables(pkg_dir, release_dirs):
"""Find test executables from release scope modules in build4/bin/
Args:
pkg_dir: Package staging directory
release_dirs: List of release scope directory paths
Returns:
List of (executable_path, module_path) tuples for tests in release scope
"""
test_executables = []
build_bin_dir = os.path.join(pkg_dir, "build4", "bin")
if not os.path.exists(build_bin_dir):
warning(f"Build bin directory not found: {build_bin_dir}")
return test_executables
# Get all executable files in build4/bin/
for filename in os.listdir(build_bin_dir):
file_path = os.path.join(build_bin_dir, filename)
if not os.path.isfile(file_path) or not os.access(file_path, os.X_OK):
continue
# Skip non-test executables (common patterns)
if not ('test' in filename.lower() or 'Test' in filename):
continue
# Find which module this test belongs to by searching CMakeLists.txt
module_path = find_test_module_source(pkg_dir, filename, release_dirs)
if module_path:
test_executables.append((file_path, module_path))
debug(f" Found test executable: {filename} from {module_path}")
else:
debug(f" Skipping test executable (no module found): {filename}")
return test_executables
def find_test_module_source(pkg_dir, test_name, release_dirs):
"""Find the source module directory for a test executable
Args:
pkg_dir: Package staging directory
test_name: Name of the test executable
release_dirs: List of release scope directory paths
Returns:
Relative path to module directory, or None if not found
"""
# Search for CMakeLists.txt files that define this test
for rel_dir in release_dirs:
module_dir = os.path.join(pkg_dir, rel_dir)
if not os.path.exists(module_dir):
continue
for root, dirs, files in os.walk(module_dir):
# Skip build directories
dirs[:] = [d for d in dirs if not d.startswith('.') and d not in ['build', 'cmake-build-debug', 'cmake-build-release']]
if 'CMakeLists.txt' in files:
cmake_file = os.path.join(root, 'CMakeLists.txt')
try:
with open(cmake_file, 'r') as f:
content = f.read()
# Look for ADD_EXECUTABLE or ADD_TEST with this test name
if f'ADD_EXECUTABLE({test_name}' in content or f'ADD_TEST(NAME {test_name}' in content:
# Return the module directory relative to pkg_dir
return os.path.relpath(root, pkg_dir)
except Exception:
pass
return None
def find_test_sources(pkg_dir, release_dirs):
"""Find test source files in release scope modules
Args:
pkg_dir: Package staging directory
release_dirs: List of release scope directory paths
Returns:
List of relative paths to test source files
"""
test_sources = []
test_patterns = ['*test*.cpp', '*test*.h', '*Test*.cpp', '*Test*.h', 'test_*.cpp', 'test_*.h']
for rel_dir in release_dirs:
module_dir = os.path.join(pkg_dir, rel_dir)
if not os.path.exists(module_dir):
continue
for root, dirs, files in os.walk(module_dir):
# Skip build directories and hidden directories
dirs[:] = [d for d in dirs if not d.startswith('.') and d not in ['build', 'cmake-build-debug', 'cmake-build-release']]
for filename in files:
# Check if this is a test file
if 'test' in filename.lower() and (filename.endswith('.cpp') or filename.endswith('.h')):
file_path = os.path.join(root, filename)
rel_path = os.path.relpath(file_path, pkg_dir)
test_sources.append(rel_path)
debug(f" Found test source: {rel_path}")
return test_sources
def create_debian_control(deb_dir, package_name, version, build_number, description, depends="", section="devel"):
"""Create debian/control file for a package"""
control_content = f"""Source: {package_name}
Section: {section}
Priority: optional
Maintainer: Legatus Team <team@legatus.example.com>
Build-Depends: debhelper (>= 13), cmake, qt6-base-dev, qt6-tools-dev
Standards-Version: 4.6.0
Package: {package_name}
Architecture: any
Depends: ${{shlibs:Depends}}, ${{misc:Depends}}{", " + depends if depends else ""}
Description: {description}
Legatus Qt6 Release Package
This package is part of the Legatus Qt6 migration release.
"""
control_file = os.path.join(deb_dir, "control")
with open(control_file, "w") as f:
f.write(control_content)
info(f"Created {control_file}")
def create_debian_changelog(deb_dir, package_name, version, build_number):
"""Create debian/changelog file"""
# Use +0000 as timezone offset (UTC) for reproducibility
from email.utils import formatdate
timestamp = formatdate(localtime=True)
changelog_content = f"""{package_name} ({version}-{build_number}) unstable; urgency=medium
* Qt6 Release Build
-- Legatus Team <team@legatus.example.com> {timestamp}
"""
changelog_file = os.path.join(deb_dir, "changelog")
with open(changelog_file, "w") as f:
f.write(changelog_content)
info(f"Created {changelog_file}")
def create_debian_rules(deb_dir, cmake_flags="", skip_tests=False):
"""Create debian/rules file"""
test_override = ""
if skip_tests:
test_override = """
override_dh_auto_test:
# Tests skipped for this package type
"""
rules_content = f"""#!/usr/bin/make -f
%:
dh $@ --buildsystem=cmake
override_dh_auto_configure:
dh_auto_configure -- -DCMAKE_BUILD_TYPE=Release {cmake_flags}
override_dh_auto_build:
dh_auto_build --parallel
override_dh_auto_install:
dh_auto_install --destdir=debian/tmp
{test_override}"""
rules_file = os.path.join(deb_dir, "rules")
with open(rules_file, "w") as f:
f.write(rules_content)
os.chmod(rules_file, stat.S_IREAD | stat.S_IWRITE | stat.S_IXUSR | stat.S_IRGRP | stat.S_IROTH | stat.S_IXGRP | stat.S_IXOTH)
info(f"Created {rules_file}")
def create_debian_rules_nobuild(deb_dir, skip_shlibdeps=False):
"""Create debian/rules file for packages that don't need building (pre-built files only)
Args:
deb_dir: Path to debian directory
skip_shlibdeps: If True, skip dh_shlibdeps (useful for test executables with external deps)
"""
rules_content = """#!/usr/bin/make -f
%:
dh $@
override_dh_auto_build:
# No build step needed - pre-built files only
override_dh_auto_configure:
# No configure step needed - pre-built files only
override_dh_auto_install:
dh_auto_install
"""
if skip_shlibdeps:
rules_content += """
override_dh_shlibdeps:
# Skip shlibdeps for test executables - Qt6 deps are external system packages
"""
rules_file = os.path.join(deb_dir, "rules")
with open(rules_file, "w") as f:
f.write(rules_content)
os.chmod(rules_file, stat.S_IREAD | stat.S_IWRITE | stat.S_IXUSR | stat.S_IRGRP | stat.S_IROTH | stat.S_IXGRP | stat.S_IXOTH)
info(f"Created {rules_file}")
rules_file = os.path.join(deb_dir, "rules")
with open(rules_file, "w") as f:
f.write(rules_content)
os.chmod(rules_file, stat.S_IREAD | stat.S_IWRITE | stat.S_IXUSR | stat.S_IRGRP | stat.S_IROTH | stat.S_IXGRP | stat.S_IXOTH)
info(f"Created {rules_file}")
def create_debian_install(deb_dir, install_lines):
"""Create debian/install file from list of file entries"""
install_file = os.path.join(deb_dir, "install")
with open(install_file, "w") as f:
for line in install_lines:
f.write(line + "\n")
info(f"Created {install_file} with {len(install_lines)} entries")
def create_manifest(output_dir, package_name, pkg_dir):
"""Create manifest file listing all package contents from actual DEB file"""
manifest_file = os.path.join(output_dir, "manifests", f"{package_name}.manifest")
os.makedirs(os.path.dirname(manifest_file), exist_ok=True)
# Find the DEB file for this package
deb_file = None
for f in os.listdir(output_dir):
if f.startswith(package_name) and f.endswith(".deb"):
deb_file = os.path.join(output_dir, f)
break
manifest_lines = []
if deb_file and os.path.exists(deb_file):
# Extract DEB contents using dpkg-deb
result = subprocess.run(
["dpkg-deb", "--contents", deb_file],
capture_output=True, text=True
)
if result.returncode == 0:
for line in result.stdout.strip().split("\n"):
if line.strip():
# dpkg-deb --contents output format: -rw-r--r-- root/root 1234 2024-01-01 00:00 ./path/to/file
# Extract the path (last field)
parts = line.split()
if len(parts) >= 6:
file_path = parts[-1]
# Remove leading ./ or /
if file_path.startswith("./"):
file_path = file_path[2:]
elif file_path.startswith("/"):
file_path = file_path[1:]
if file_path:
manifest_lines.append(file_path)
elif os.path.exists(pkg_dir):
# Fallback: scan directory if DEB not found
info(f"Warning: DEB file not found for {package_name}, scanning directory instead")
for root, dirs, files in os.walk(pkg_dir):
for file in files:
file_path = os.path.join(root, file)
rel_path = os.path.relpath(file_path, pkg_dir)
manifest_lines.append(rel_path)
manifest_lines.sort()
with open(manifest_file, "w") as f:
for line in manifest_lines:
f.write(line + "\n")
info(f"Created manifest: {manifest_file} ({len(manifest_lines)} entries)")
return manifest_lines
def validate_package_contents(package_name, manifest_lines, allowed_patterns, forbidden_patterns=None):
"""Validate package contents against allowlist/forbidden list"""
import fnmatch
errors = []
warnings = []
for file_path in manifest_lines:
# Skip directory entries (trailing '/') - only files are allowlisted
if file_path.endswith('/'):
continue
# Check forbidden patterns
if forbidden_patterns:
for pattern in forbidden_patterns:
if fnmatch.fnmatch(file_path, pattern):
errors.append(f"FORBIDDEN file in {package_name}: {file_path} (matches {pattern})")
# Check if file matches any allowed pattern
allowed = False
if allowed_patterns:
for pattern in allowed_patterns:
if fnmatch.fnmatch(file_path, pattern):
allowed = True
break
if not allowed and allowed_patterns:
warnings.append(f"Unexpected file in {package_name}: {file_path}")
if errors:
for err in errors:
error(f"Validation error: {err}")
return False
if warnings:
for warn in warnings:
warning(f"Validation warning: {warn}")
return True
def build_single_deb(pkg_name, pkg_dir, output_dir, version, build_number, cmake_flags, skip_tests, install_lines, description, section="devel", depends=""):
"""Build a single DEB package"""
info(f"Building DEB package: {pkg_name}")
deb_dir = os.path.join(pkg_dir, "debian")
os.makedirs(deb_dir)
create_debian_control(deb_dir, pkg_name, version, build_number, description, depends, section)
create_debian_changelog(deb_dir, pkg_name, version, build_number)
create_debian_rules(deb_dir, cmake_flags, skip_tests)
# Create debian/source/format
source_dir_path = os.path.join(deb_dir, "source")
os.makedirs(source_dir_path)
with open(os.path.join(source_dir_path, "format"), "w") as f:
f.write("3.0 (quilt)\n")
# Create debian/compat
with open(os.path.join(deb_dir, "compat"), "w") as f:
f.write("13\n")
# Create debian/install
if install_lines:
create_debian_install(deb_dir, install_lines)
# Build the package
info(f"Running debuild for {pkg_name}...")
os.chdir(pkg_dir)
env = os.environ.copy()
env["DEB_BUILD_OPTIONS"] = "parallel=$(nproc)"
cmd = f"debuild -us -uc -b"
debug(f"Executing: {cmd}")
ret = subprocess.run(cmd, shell=True, env=env, capture_output=True, text=True)
if ret.returncode != 0:
print(ret.stdout)
print(ret.stderr)
die(f"debuild failed for {pkg_name} with code {ret.returncode}")
# Move generated packages to output directory
parent_dir = os.path.dirname(pkg_dir)
for f in os.listdir(parent_dir):
if f.endswith(".deb") or f.endswith(".dsc") or f.endswith(".changes") or f.endswith(".buildinfo"):
src = os.path.join(parent_dir, f)
dst = os.path.join(output_dir, f)
if os.path.exists(dst):
os.remove(dst)
shutil.move(src, output_dir)
info(f"Moved {f} to {output_dir}")
return True
def build_single_deb_nobuild(pkg_name, pkg_dir, output_dir, version, build_number, install_lines, description, section="devel", depends="", skip_shlibdeps=False):
"""Build a single DEB package without running cmake/make (pre-built files only)
Args:
skip_shlibdeps: If True, skip dh_shlibdeps (useful for test executables with external deps)
"""
info(f"Building DEB package (no-build): {pkg_name}")
deb_dir = os.path.join(pkg_dir, "debian")
os.makedirs(deb_dir)
create_debian_control(deb_dir, pkg_name, version, build_number, description, depends, section)
create_debian_changelog(deb_dir, pkg_name, version, build_number)
create_debian_rules_nobuild(deb_dir, skip_shlibdeps=skip_shlibdeps)
# Create debian/source/format
source_dir_path = os.path.join(deb_dir, "source")
os.makedirs(source_dir_path)
with open(os.path.join(source_dir_path, "format"), "w") as f:
f.write("3.0 (quilt)\n")
# Create debian/compat
with open(os.path.join(deb_dir, "compat"), "w") as f:
f.write("13\n")
# Create debian/install
if install_lines:
create_debian_install(deb_dir, install_lines)
# Build the package
info(f"Running debuild for {pkg_name}...")
os.chdir(pkg_dir)
env = os.environ.copy()
env["DEB_BUILD_OPTIONS"] = "parallel=$(nproc)"
cmd = f"debuild -us -uc -b"
debug(f"Executing: {cmd}")
ret = subprocess.run(cmd, shell=True, env=env, capture_output=True, text=True)
if ret.returncode != 0:
print(ret.stdout)
print(ret.stderr)
die(f"debuild failed for {pkg_name} with code {ret.returncode}")
# Move generated packages to output directory
parent_dir = os.path.dirname(pkg_dir)
for f in os.listdir(parent_dir):
if f.endswith(".deb") or f.endswith(".dsc") or f.endswith(".changes") or f.endswith(".buildinfo"):
src = os.path.join(parent_dir, f)
dst = os.path.join(output_dir, f)
if os.path.exists(dst):
os.remove(dst)
shutil.move(src, output_dir)
info(f"Moved {f} to {output_dir}")
return True
def build_all_debs(source_dir, output_dir, project_name, version, build_number, cmake_flags, release_scope=True):
"""Build all 5 required DEB packages - build once, then package"""
info("========================================")
info("Building 5 DEB packages for Qt6 Release")
info("========================================")
os.makedirs(output_dir, exist_ok=True)
os.makedirs(os.path.join(output_dir, "manifests"), exist_ok=True)
build_base = os.path.join(source_dir, "deb_build_tmp")
remove_directory(build_base)
os.makedirs(build_base)
# Release scope directories
release_dirs = ["TetraIF", "Z/src", "Lts/Common"]
# Source file extensions
source_extensions = ['.cpp', '.h', '.hpp', '.cc', '.cxx', '.hxx']
script_extensions = ['.py', '.sh', '.cmake', '.txt']
# ============================================
# STEP 1: Build once from full source tree
# ============================================
info("\n=== Step 1: Building from full source tree ===")
build_dir = os.path.join(source_dir, "build")
os.makedirs(build_dir, exist_ok=True)
os.chdir(build_dir)
# Configure
info("Running CMake configuration...")
cmake_cmd = f"cmake -DCMAKE_BUILD_TYPE=Release {cmake_flags} .."
ret = subprocess.run(cmake_cmd, shell=True, capture_output=True, text=True)
if ret.returncode != 0:
print(ret.stdout)
print(ret.stderr)
die("CMake configuration failed")
# Build
info("Running build...")
build_cmd = f"make -j$(nproc)"
ret = subprocess.run(build_cmd, shell=True, capture_output=True, text=True)
if ret.returncode != 0:
print(ret.stdout)
print(ret.stderr)
die("Build failed")
info("Build completed successfully")
# ============================================
# Package 1: qt6-release-runtime
# ============================================
info("\n=== Building qt6-release-runtime ===")
pkg1_name = f"qt6-release-runtime"
pkg1_dir = os.path.join(build_base, f"{pkg1_name}-{version}")
os.makedirs(pkg1_dir)
# Copy FULL source tree (needed for debian/rules cmake reference)
copy_src_dir_to_dest_dir(source_dir, pkg1_dir, release_scope=False)
# Install lines for runtime: binaries and libraries from release scope only
# CMake install uses component prefixes: ALL->(none), AAR/SV/HLA->SV/, WS->WS/
install_lines_1 = []
# Install from all possible component install paths
# ALL component: bin/, lib/
# AAR/SV/HLA components: SV/bin/, SV/lib/
# WS component: WS/bin/, WS/lib/
install_lines_1.append("usr/bin/* usr/bin/")
install_lines_1.append("usr/lib/*.so* usr/lib/")
install_lines_1.append("usr/SV/bin/* usr/bin/")
install_lines_1.append("usr/SV/lib/*.so* usr/lib/")
install_lines_1.append("usr/WS/bin/* usr/bin/")
install_lines_1.append("usr/WS/lib/*.so* usr/lib/")
build_single_deb(
pkg_name=pkg1_name,
pkg_dir=pkg1_dir,
output_dir=output_dir,
version=version,
build_number=build_number,
cmake_flags=cmake_flags,
skip_tests=True,
install_lines=install_lines_1,
description="Qt6 Release Runtime - binaries and libraries from release scope S",
section="libs"
)
# Create manifest
manifest_1 = create_manifest(output_dir, pkg1_name, pkg1_dir)
validate_package_contents(pkg1_name, manifest_1,
allowed_patterns=["usr/bin/*", "usr/lib/*", "usr/SV/*", "usr/WS/*", "usr/share/doc/*", "debian/*"],
forbidden_patterns=["*.cpp", "*.h", "*.hpp", "tests/*", "jenkins/*", "Dockerfile*"])
# ============================================
# Package 2: qt6-release-sources
# ============================================
info("\n=== Building qt6-release-sources ===")
pkg2_name = f"qt6-release-sources"
pkg2_dir = os.path.join(build_base, f"{pkg2_name}-{version}")
os.makedirs(pkg2_dir)
# Copy FULL source tree (excluding DDS-related files)
copy_src_dir_to_dest_dir(source_dir, pkg2_dir, release_scope=False, file_filter=is_dds_file)
# Install lines for sources: only from release scope directories
install_lines_2 = []
for rel_dir in release_dirs:
src_dir = os.path.join(pkg2_dir, rel_dir)
if os.path.exists(src_dir):
for root, dirs, files in os.walk(src_dir):
dirs[:] = [d for d in dirs if not d.startswith('.')]
for file in files:
ext = os.path.splitext(file)[1].lower()
if ext in source_extensions or file == "CMakeLists.txt":
rel_path = os.path.relpath(os.path.join(root, file), pkg2_dir)
dest_dir = os.path.join("usr/src", os.path.dirname(rel_path))
install_lines_2.append(f"{rel_path} {dest_dir}/")
# Use no-build rules for source package
build_single_deb_nobuild(
pkg_name=pkg2_name,
pkg_dir=pkg2_dir,
output_dir=output_dir,
version=version,
build_number=build_number,
install_lines=install_lines_2,
description="Qt6 Release Sources - C++ source files from release scope S",
section="devel"
)
manifest_2 = create_manifest(output_dir, pkg2_name, pkg2_dir)
validate_package_contents(pkg2_name, manifest_2,
# scope filtering happens at staging time (release_dirs); the
# validator only guards against out-of-scope contamination
allowed_patterns=["usr/src/*", "usr/share/doc/*", "debian/*"],
forbidden_patterns=["build/*", "tests/*", "jenkins/*"])
# ============================================
# Package 3: qt6-release-ctest-runtime
# ============================================
info("\n=== Building qt6-release-ctest-runtime ===")
pkg3_name = f"qt6-release-ctest-runtime"
pkg3_dir = os.path.join(build_base, f"{pkg3_name}-{version}")
os.makedirs(pkg3_dir)
# Copy FULL source tree
copy_src_dir_to_dest_dir(source_dir, pkg3_dir, release_scope=False)
# Install lines for CTest runtime: test executables from release scope only
install_lines_3 = []
# Find test executables in build4/bin/ and map to release scope modules
test_executables = find_test_executables(pkg3_dir, release_dirs)
for exe_path, module_path in test_executables:
rel_path = os.path.relpath(exe_path, pkg3_dir)
dest_dir = "usr/lib/ctest"
install_lines_3.append(f"{rel_path} {dest_dir}/")
info(f" Including test executable: {os.path.basename(exe_path)} from {module_path}")
# Also include CTest configuration files
ctest_files = [
"build4/CTestTestfile.cmake",
"build4/DartConfiguration.tcl",
"build4/Testing/TAG",
]
for ctest_file in ctest_files:
if os.path.exists(os.path.join(pkg3_dir, ctest_file)):
install_lines_3.append(f"{ctest_file} usr/lib/ctest/")
build_single_deb_nobuild(
pkg_name=pkg3_name,
pkg_dir=pkg3_dir,
output_dir=output_dir,
version=version,
build_number=build_number,
install_lines=install_lines_3,
description="Qt6 Release CTest Runtime - executable test programs from release scope",
section="devel",
skip_shlibdeps=True
)
manifest_3 = create_manifest(output_dir, pkg3_name, pkg3_dir)
validate_package_contents(pkg3_name, manifest_3,
allowed_patterns=["usr/lib/ctest/*", "usr/share/doc/*", "debian/*"],
forbidden_patterns=["*.cpp", "*.h", "jenkins/*"])
# ============================================
# Package 4: qt6-release-ctest-sources
# ============================================
info("\n=== Building qt6-release-ctest-sources ===")
pkg4_name = f"qt6-release-ctest-sources"
pkg4_dir = os.path.join(build_base, f"{pkg4_name}-{version}")
os.makedirs(pkg4_dir)
# Copy FULL source tree
copy_src_dir_to_dest_dir(source_dir, pkg4_dir, release_scope=False)
# Install lines for test sources from release scope only
install_lines_4 = []
# Find test source files in release scope modules
test_sources = find_test_sources(pkg4_dir, release_dirs)
for rel_path in test_sources:
dest_dir = os.path.dirname(rel_path)
install_lines_4.append(f"{rel_path} {dest_dir}/")
build_single_deb_nobuild(
pkg_name=pkg4_name,
pkg_dir=pkg4_dir,
output_dir=output_dir,
version=version,
build_number=build_number,
install_lines=install_lines_4,
description="Qt6 Release CTest Sources - test source files from release scope",
section="devel"
)
manifest_4 = create_manifest(output_dir, pkg4_name, pkg4_dir)
validate_package_contents(pkg4_name, manifest_4,
allowed_patterns=["Lts/*", "TetraIF/*", "Z/*", "usr/share/doc/*", "debian/*"],
forbidden_patterns=["build/*", "jenkins/*"])
# ============================================
# Package 5: qt6-release-build-pipeline
# ============================================
info("\n=== Building qt6-release-build-pipeline ===")
pkg5_name = f"qt6-release-build-pipeline"
pkg5_dir = os.path.join(build_base, f"{pkg5_name}-{version}")
os.makedirs(pkg5_dir)
# Copy FULL source tree
copy_src_dir_to_dest_dir(source_dir, pkg5_dir, release_scope=False)
# Install lines for pipeline files only
install_lines_5 = []
pipeline_files = [
"new_docker_build.sh",
"Dockerfile",
"Dockerfile.ubuntu26",
"jenkins-ci-rme/release-16-10-2026.sh",
"rpmbuild/make_rpm_ubuntu26.py",
"CMakeLists.txt",
"cmake/FindGeographicLib.cmake",
"cmake/FindRTI.cmake",
"cmake/FindZLIB.cmake",
]
for pipeline_file in pipeline_files:
src_file = os.path.join(pkg5_dir, pipeline_file)
if os.path.exists(src_file):
dest_dir = os.path.join("usr/src/pipeline", os.path.dirname(pipeline_file))
install_lines_5.append(f"{pipeline_file} {dest_dir}/")
info(f" Added pipeline file: {pipeline_file}")
build_single_deb_nobuild(
pkg_name=pkg5_name,
pkg_dir=pkg5_dir,
output_dir=output_dir,
version=version,
build_number=build_number,
install_lines=install_lines_5,
description="Qt6 Release Build Pipeline - Docker, Jenkins, CMake, and packaging scripts",
section="devel"
)
manifest_5 = create_manifest(output_dir, pkg5_name, pkg5_dir)
validate_package_contents(pkg5_name, manifest_5,
allowed_patterns=["usr/src/pipeline/*", "usr/share/doc/*", "debian/*"],
forbidden_patterns=["*.cpp", "*.h", "tests/*"])
# Cleanup
os.chdir(source_dir)
remove_directory(build_base)
info("\n========================================")
info(f"DEB package build completed: {output_dir}")
info("Generated packages:")
for f in os.listdir(output_dir):
if f.endswith(".deb"):
info(f" - {f}")
info("========================================")
def main():
parser = argparse.ArgumentParser(description="Build DEB packages for Legatus Qt6 Release")
parser.add_argument("-c", "--config", required=True, help="Config type (LTS)")
parser.add_argument("--project_name", required=True, help="Project name (qatar|ahtc|guez)")
parser.add_argument("--version", required=True, help="Version (Major.minor)")
parser.add_argument("--output_dir", required=True, help="Output directory for packages")
parser.add_argument("--source_dir", required=True, help="Source directory path")
parser.add_argument("--cmake_build_flags", default="", help="CMake build flags")
parser.add_argument("--build_number", default="1", help="Build number")
parser.add_argument("--release_scope", action="store_true", help="Only include release scope files")
args = parser.parse_args()
info(f"Building {args.config} package for {args.project_name} v{args.version}")
info(f"Output directory: {args.output_dir}")
info(f"Source directory: {args.source_dir}")
info(f"CMake flags: {args.cmake_build_flags}")
info(f"Release scope: {args.release_scope}")
source_dir = args.source_dir
info(f"Source directory: {source_dir}")
build_all_debs(
source_dir=source_dir,
output_dir=args.output_dir,
project_name=args.project_name,
version=args.version,
build_number=args.build_number,
cmake_flags=args.cmake_build_flags,
release_scope=args.release_scope
)
if __name__ == "__main__":
main()