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:
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()
|
||||
Reference in new issue
Block a user