From 0e5c9222f1ff12fc8ad48a9f846ba50c0c8937e4 Mon Sep 17 00:00:00 2001 From: TejaswiniIBM Date: Tue, 17 Mar 2026 03:27:24 -0400 Subject: [PATCH 01/31] Added rpm package as part of build Signed-off-by: TejaswiniIBM --- bin/zopen-build | 15 + bin/zopen-pax2rpm | 885 ++++++++++++++++++++++++++++++++++++++++++++ cicd/publish.groovy | 17 + zopen-build | 0 4 files changed, 917 insertions(+) create mode 100755 bin/zopen-pax2rpm create mode 100644 zopen-build diff --git a/bin/zopen-build b/bin/zopen-build index c0b44ecb9..4ad0e8699 100755 --- a/bin/zopen-build +++ b/bin/zopen-build @@ -335,6 +335,7 @@ Option: with the .rej extension. -g, --get-source get the source and apply patch without building. -gp, --generate-pax generate a pax.Z file based on the install contents. + -gr, --generate-rpm generate an RPM package from the pax archive. -h, --help, -? print this information. --no-set-active do not change the pinned version. --no-install-deps do not install project's runtime dependencies. @@ -379,6 +380,7 @@ processOptions() buildEnvFile="./buildenv" getSourceOnly=false generatePax=false + generateRPM=false setActive=true signPax=false forcePatchApply=false @@ -476,6 +478,9 @@ processOptions() "-gp" | "--generate-pax") generatePax=true ;; + "-gr" | "--generate-rpm") + generateRPM=true + ;; "-s" | "--shell") startShell=true ;; @@ -2305,6 +2310,16 @@ install() printError "Could not generate pax \"${paxFileName}\"" fi + if ${generateRPM}; then + if [ -f "${paxFileName}" ]; then + printHeader "Generating RPM from ${ZOPEN_INSTALL_DIR}" + rpm_deps=$(echo "${ZOPEN_RUNTIME_DEPS}" | xargs -n1 | sort -u | xargs) + PATH="${ZOPEN_ROOTFS}/usr/local/bin:${PATH}" "${MYDIR}/zopen-pax2rpm" "${paxFileName}" --summary "${ZOPEN_NAME} package" --build + else + printError "Pax file ${paxFileName} not found. Ensure --generate-pax is also used." + fi + fi + #TODO: Hack so that we can use coreutils md5sum without impacting builds ZOPEN_DEPS="${ZOPEN_DEPS} coreutils jq" if [ "${signPax}" = "true" ] && ( [ -z "${ZOPEN_GPG_SECRET_KEY_FILE}" ] || [ -z "${ZOPEN_GPG_SECRET_KEY_PASSPHRASE_FILE}" ] || [ -z "${ZOPEN_GPG_PUBLIC_KEY_FILE}" ] || [ ! -r "${ZOPEN_GPG_SECRET_KEY_FILE}" ] || [ ! -r "${ZOPEN_GPG_SECRET_KEY_PASSPHRASE_FILE}" ] || [ ! -r "${ZOPEN_GPG_PUBLIC_KEY_FILE}" ] ); then diff --git a/bin/zopen-pax2rpm b/bin/zopen-pax2rpm new file mode 100755 index 000000000..456db6479 --- /dev/null +++ b/bin/zopen-pax2rpm @@ -0,0 +1,885 @@ +#!/bin/bash +# +# pax2rpm.sh - Generate RPM spec file from z/OS pax archive +# +# Usage: pax2rpm.sh [options] +# +# Options: +# --name Override package name +# --version Override version +# --release Override release number (default: 1) +# --license Specify license (default: Proprietary) +# --summary Package summary +# --description Package description +# --url Project URL +# --output Output spec file (default: .spec) +# --build Build the RPM after generating spec file +# --buildroot RPM build root directory (default: ~/rpmbuild) +# --validate Validate spec file after generation +# --dry-run Show what would be done without doing it +# + +set -e + +# Default values +RELEASE="1" +LICENSE="Proprietary" +BUILD_ARCH=$(uname -m 2>/dev/null || echo "s390x") +PACKAGER_NAME="${USER}" +PACKAGER_EMAIL="${USER}@$(hostname)" + +# Build flag +BUILD_RPM=false +BUILDROOT="${HOME}/rpmbuild" +VALIDATE_SPEC=false +DRY_RUN=false +VERBOSE=false + +# Function to display usage +usage() { + cat << EOF +Usage: $0 [options] + +Generate an RPM spec file from a z/OS pax archive. + +Arguments: + pax_file Path to the pax file (e.g., /path/to/file.pax or file.pax.Z) + +Options: + --name Override package name (default: extracted from filename) + --version Override version (default: extracted from filename) + --release Override release number (default: 1) + --license Specify license (default: Proprietary) + --summary Package summary (required) + --description Package description (default: same as summary) + --url Project URL (default: none) + --requires Package dependencies (e.g., "oef >= 1.1.0") + --output Output spec file (default: .spec) + --build Build the RPM after generating spec file + --buildroot RPM build root directory (default: ~/rpmbuild) + --validate Validate spec file after generation (checks syntax and runs rpmlint) + --dry-run Show what would be done without actually doing it + --verbose Enable verbose debug output + --help Display this help message + +Example: + $0 /nfsmnts/bpidrivers/oefv1r1/os390/latest/HAMN110.runnable.pax.Z \\ + --summary "HAMN110 Runtime Package" \\ + --license "IBM" \\ + --url "https://www.ibm.com" + +EOF + exit 1 +} + +# Function to extract package name and version from filename +parse_filename() { + local filename="$1" + local basename=$(basename "$filename") + + # Remove .pax.Z or .pax extension + basename="${basename%.pax.Z}" + basename="${basename%.pax}" + + # Try to extract name and version + # Pattern 1: NAME-VERSION (e.g., package-1.0) + if [[ "$basename" =~ ^(.+)-([0-9]+\.[0-9]+.*)$ ]]; then + PKG_NAME="${BASH_REMATCH[1]}" + PKG_VERSION="${BASH_REMATCH[2]}" + # Pattern 2: NAMEVERSION.suffix (e.g., HAMN110.runnable) + elif [[ "$basename" =~ ^([A-Za-z]+)([0-9]+)\.(.+)$ ]]; then + PKG_NAME="${BASH_REMATCH[1]}" + PKG_VERSION="${BASH_REMATCH[2]}" + # Don't include suffix like "runnable" in version + # Pattern 3: NAMEVERSION (e.g., HAMN110) + elif [[ "$basename" =~ ^([A-Za-z]+)([0-9]+)$ ]]; then + PKG_NAME="${BASH_REMATCH[1]}" + PKG_VERSION="${BASH_REMATCH[2]}" + else + PKG_NAME="$basename" + PKG_VERSION="1.0" + fi + + # Convert to lowercase and replace invalid characters (keep alphanumeric and hyphens) + PKG_NAME=$(echo "$PKG_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9-]/-/g' | sed 's/^-//;s/-$//') +} + +# Function to analyze pax contents and categorize files +analyze_pax_contents() { + local pax_file="$1" + local temp_dir=$(mktemp -d) + + echo "# Analyzing pax file structure..." >&2 + + # Extract pax listing (limit to first 1000 entries for performance) + local pax_listing + if [[ "$pax_file" == *.pax.Z ]]; then + pax_listing=$(pax -vzf "$pax_file" 2>&1 | head -1000) + else + pax_listing=$(pax -vf "$pax_file" 2>&1 | head -1000) + fi + + # Check if pax listing failed or is empty + # Only treat as error if pax command actually failed (not just listing files) + if [ -z "$pax_listing" ]; then + echo "# Warning: Could not analyze pax contents, using default structure" >&2 + rm -rf "$temp_dir" + return 1 + fi + + # Initialize arrays for different file types + declare -g -A FILE_CATEGORIES + FILE_CATEGORIES[bins]="" + FILE_CATEGORIES[libs]="" + FILE_CATEGORIES[includes]="" + FILE_CATEGORIES[configs]="" + FILE_CATEGORIES[docs]="" + FILE_CATEGORIES[data]="" + FILE_CATEGORIES[scripts]="" + FILE_CATEGORIES[other]="" + + declare -g -A DIR_STRUCTURE + DIR_STRUCTURE[has_bin]=false + DIR_STRUCTURE[has_lib]=false + DIR_STRUCTURE[has_include]=false + DIR_STRUCTURE[has_etc]=false + DIR_STRUCTURE[has_share]=false + DIR_STRUCTURE[has_doc]=false + DIR_STRUCTURE[has_man]=false + + # Analyze each file + while IFS= read -r line; do + # Skip empty lines and headers + [[ -z "$line" ]] && continue + + # Extract filename (last field in pax -v output) + local filename=$(echo "$line" | awk '{print $NF}') + [[ -z "$filename" ]] && continue + + # Detect directory structure (handles top-level dir if present) + # Matches: bin/, ./bin/, package-ver/bin/, ./package-ver/bin/ + # Does NOT match: usr/lpp/IBM/bin/ (too deep) + if [[ "$filename" =~ ^(\./)?([^/]+/)?bin/ ]]; then + DIR_STRUCTURE[has_bin]=true + elif [[ "$filename" =~ ^(\./)?([^/]+/)?lib/ ]]; then + DIR_STRUCTURE[has_lib]=true + elif [[ "$filename" =~ ^(\./)?([^/]+/)?include/ ]]; then + DIR_STRUCTURE[has_include]=true + elif [[ "$filename" =~ ^(\./)?([^/]+/)?etc/ ]]; then + DIR_STRUCTURE[has_etc]=true + elif [[ "$filename" =~ ^(\./)?([^/]+/)?share/ ]]; then + DIR_STRUCTURE[has_share]=true + elif [[ "$filename" =~ ^(\./)?([^/]+/)?(doc|docs)/ ]]; then + DIR_STRUCTURE[has_doc]=true + elif [[ "$filename" =~ ^(\./)?([^/]+/)?man/ ]]; then + DIR_STRUCTURE[has_man]=true + elif [[ "$filename" =~ ^(\./)?([^/]+/)?scripts/ ]]; then + DIR_STRUCTURE[has_scripts]=true + elif [[ "$filename" =~ ^(\./)?usr/lpp/ ]]; then + DIR_STRUCTURE[is_os_layout]=true + fi + + # Categorize by file type + if [[ "$filename" =~ \.(so|so\.[0-9]+|a|dylib)$ ]]; then + FILE_CATEGORIES[libs]+="$filename"$'\n' + elif [[ "$filename" =~ \.(h|hpp|hxx)$ ]]; then + FILE_CATEGORIES[includes]+="$filename"$'\n' + elif [[ "$filename" =~ \.(conf|cfg|ini|yaml|yml|json|xml|properties)$ ]]; then + FILE_CATEGORIES[configs]+="$filename"$'\n' + elif [[ "$filename" =~ \.(md|txt|pdf|html|htm|README|LICENSE|COPYING|CHANGELOG|AUTHORS)$ ]] || [[ "$filename" =~ (README|LICENSE|COPYING|CHANGELOG|AUTHORS|INSTALL|NEWS)$ ]]; then + FILE_CATEGORIES[docs]+="$filename"$'\n' + elif [[ "$filename" =~ \.(sh|bash|ksh|pl|py|rb)$ ]] && [[ "$line" =~ ^-r.x ]]; then + FILE_CATEGORIES[scripts]+="$filename"$'\n' + elif [[ "$line" =~ ^-r.x ]] && [[ ! "$filename" =~ \. ]]; then + # Executable without extension (likely binary) + FILE_CATEGORIES[bins]+="$filename"$'\n' + elif [[ "$filename" =~ \.(dat|data|db|sqlite)$ ]]; then + FILE_CATEGORIES[data]+="$filename"$'\n' + else + FILE_CATEGORIES[other]+="$filename"$'\n' + fi + done <<< "$pax_listing" + + rm -rf "$temp_dir" +} + +# Function to list contents of pax file +list_pax_contents() { + local pax_file="$1" + + echo "# Pax file contents (first 100 entries):" >&2 + + if [[ "$pax_file" == *.pax.Z ]]; then + pax -rvzf "$pax_file" 2>&1 | head -100 + else + pax -rvf "$pax_file" 2>&1 | head -100 + fi +} + +# Function to generate install commands based on analyzed contents +# Function to generate install commands based on analyzed contents +# Function to generate install commands based on analyzed contents +generate_install_commands() { + local pax_file="$1" + + cat << 'EOF' +# Install files based on detected structure + +# Detect the source layout +if [ -d usr ]; then + echo "OS layout (usr/) detected, searching for actual content root..." + # Find the deepest directory that contains bin, lib, pyz, bash, or go + FOUND_PATH=$(find . -type d \( -name bin -o -name lib -o -name pyz -o -name bash -o -name go \) -print -quit) + + if [ -n "$FOUND_PATH" ]; then + FOUND_PATH=${FOUND_PATH#./} + if [[ "$FOUND_PATH" == */pyz ]]; then + ROOT_DIR="$FOUND_PATH" + else + ROOT_DIR=$(echo "$FOUND_PATH" | sed 's/\/\(bin\|lib\|bash\|go\)$//') + fi + else + # Fallback for OS layout: find first dir with more than one entry + ROOT_DIR="." + while [ $(ls -1 "$ROOT_DIR" 2>/dev/null | wc -l) -eq 1 ]; do + SUB_DIR=$(ls -1 "$ROOT_DIR") + if [ -d "$ROOT_DIR/$SUB_DIR" ]; then + ROOT_DIR="$ROOT_DIR/$SUB_DIR" + else + break + fi + done + fi + echo "Detected nested root: $ROOT_DIR" + mkdir -p %{buildroot}%{install_dir} + cp -RP "$ROOT_DIR"/* %{buildroot}%{install_dir}/ +elif [ $(ls -1 | grep -v "^$" | wc -l) -eq 1 ] && [ -d * ]; then + # Standard single-directory layout (e.g., go/) + SUB_DIR=$(ls -1) + echo "Standard single-directory layout detected: $SUB_DIR" + mkdir -p %{buildroot}%{install_dir} + cp -RP "$SUB_DIR"/* %{buildroot}%{install_dir}/ +else + # Flat or mixed layout + echo "Flat or mixed layout detected" + mkdir -p %{buildroot}%{install_dir} + cp -RP * %{buildroot}%{install_dir}/ +fi + +# Install root-level documentation if it hasn't been copied already +# (Matches files in the CURRENT directory, not ROOT_DIR) +for doc in README* LICENSE* COPYING* CHANGELOG* AUTHORS* INSTALL* NEWS*; do + if [ -f "$doc" ]; then + mkdir -p %{buildroot}%{install_dir}/doc + cp -RP "$doc" %{buildroot}%{install_dir}/doc/ + fi +done +EOF +} + + +generate_files_list() { + cat << 'EOF' +%defattr(-,root,root,-) +%{install_dir} +EOF +} + +# Function to generate spec file +generate_spec() { + local output_file="$1" + local source_file="$2" + local date=$(date "+%a %b %d %Y") + + # Analyze the pax contents first (populate global DIR_STRUCTURE) + analyze_pax_contents "$source_file" + + cat > "$output_file" << EOF +# RPM Spec file generated from pax archive +# Source: $source_file +# Generated: $(date) + +%define _rpmfilename %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}-ibm-zos.rpm + +Name: ${PKG_NAME} +Version: ${PKG_VERSION} +Release: ${RELEASE}%{?dist} +Summary: ${SUMMARY} + +%define install_dir /usr/lpp/pkg/IBM/%{name}/%{version} + +License: ${LICENSE} +${URL:+URL: $URL} +Source0: $(basename "$source_file") + +BuildArch: ${BUILD_ARCH} +${REQUIRES:+Requires: $REQUIRES} + +%description +${DESCRIPTION} + +%prep +# Extract pax archive +%setup -c -T +cd %{_builddir} +mkdir -p %{name}-%{version} +cd %{name}-%{version} + +EOF + + if [[ "$source_file" == *.pax.Z ]]; then + cat >> "$output_file" << 'EOF' +# Extract compressed pax archive +pax -rzf %{SOURCE0} +EOF + else + cat >> "$output_file" << 'EOF' +# Extract pax archive +pax -rf %{SOURCE0} +EOF + fi + + cat >> "$output_file" << EOF + +%build +# No build step required for pre-built pax archives + +%install +rm -rf %{buildroot} + +# Create base installation directories +mkdir -p %{buildroot}%{install_dir} + +$(generate_install_commands "$source_file") + +%files +$(generate_files_list) + +%changelog +* ${date} ${PACKAGER_NAME} <${PACKAGER_EMAIL}> - ${PKG_VERSION}-${RELEASE} +- Initial RPM package created from pax archive +- Source: $(basename "$source_file") +EOF + + echo "RPM spec file generated: $output_file" + echo "" + echo "Detected structure:" + [[ "${DIR_STRUCTURE[has_bin]}" == "true" ]] && echo " ✓ Binaries in bin/" + [[ "${DIR_STRUCTURE[has_lib]}" == "true" ]] && echo " ✓ Libraries in lib/" + [[ "${DIR_STRUCTURE[has_include]}" == "true" ]] && echo " ✓ Headers in include/" + [[ "${DIR_STRUCTURE[has_etc]}" == "true" ]] && echo " ✓ Configuration files in etc/" + [[ "${DIR_STRUCTURE[has_share]}" == "true" ]] && echo " ✓ Data files in share/" + [[ "${DIR_STRUCTURE[has_doc]}" == "true" ]] && echo " ✓ Documentation in doc/" + [[ "${DIR_STRUCTURE[has_man]}" == "true" ]] && echo " ✓ Man pages in man/" + [[ "${DIR_STRUCTURE[has_scripts]}" == "true" ]] && echo " ✓ Scripts in scripts/" + + return 0 +} + +# Function to setup rpmbuild directories +setup_rpmbuild() { + local buildroot="$1" + + echo "Setting up RPM build directories in: $buildroot" + + mkdir -p "$buildroot"/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS} + + if [ ! -f "$HOME/.rpmmacros" ]; then + cat > "$HOME/.rpmmacros" << EOF +%_topdir $buildroot +EOF + echo "Created ~/.rpmmacros with _topdir set to $buildroot" + fi +} + +# Function to check for required tools +check_required_tools() { + local missing_tools=() + local pax_file="$1" + + # Check for pax + if ! command -v pax &> /dev/null; then + missing_tools+=("pax") + fi + + # Check for uncompress if dealing with .Z files + if [[ "$pax_file" == *.pax.Z ]] && ! command -v uncompress &> /dev/null; then + missing_tools+=("uncompress") + fi + + # Check for rpmbuild if building or validating + if [ "$BUILD_RPM" = true ] || [ "$VALIDATE_SPEC" = true ]; then + echo "DEBUG: Current PATH is $PATH" >&2 + if ! command -v rpmbuild &> /dev/null; then + missing_tools+=("rpmbuild") + fi + fi + + if [ ${#missing_tools[@]} -gt 0 ]; then + echo "" >&2 + echo "=========================================" >&2 + echo "ERROR: Missing Required Tools" >&2 + echo "=========================================" >&2 + echo "" >&2 + echo "The following tools are required but not found:" >&2 + echo "" >&2 + + for tool in "${missing_tools[@]}"; do + echo " ✗ $tool" >&2 + + case "$tool" in + pax) + echo " Purpose: Extract pax archives" >&2 + echo " Install: Usually pre-installed on z/OS" >&2 + echo " On Linux: apt-get install pax (Debian/Ubuntu)" >&2 + echo " yum install pax (RHEL/CentOS)" >&2 + ;; + uncompress) + echo " Purpose: Decompress .Z files" >&2 + echo " Install: Usually part of ncompress package" >&2 + echo " On z/OS: May be in /usr/bin or /bin" >&2 + echo " On Linux: apt-get install ncompress (Debian/Ubuntu)" >&2 + echo " yum install ncompress (RHEL/CentOS)" >&2 + ;; + rpmbuild) + echo " Purpose: Build RPM packages and validate spec files" >&2 + echo " Install: Part of rpm-build package" >&2 + echo " On z/OS: zopen install rpm" >&2 + echo " On Linux: apt-get install rpm (Debian/Ubuntu)" >&2 + echo " yum install rpm-build (RHEL/CentOS)" >&2 + ;; + esac + echo "" >&2 + done + + echo "Please install the missing tools and try again." >&2 + echo "" >&2 + return 1 + fi + + # Optional tools check (informational only) + local optional_missing=() + + if [ "$VALIDATE_SPEC" = true ] && ! command -v rpmlint &> /dev/null; then + optional_missing+=("rpmlint") + fi + + if [ ${#optional_missing[@]} -gt 0 ]; then + echo "" >&2 + echo "Note: Optional tools not found (validation will be limited):" >&2 + for tool in "${optional_missing[@]}"; do + echo " - $tool (for enhanced spec file quality checks)" >&2 + case "$tool" in + rpmlint) + echo " Install: On z/OS: zopen install rpmlint" >&2 + echo " On Linux: apt-get install rpmlint (Debian/Ubuntu)" >&2 + echo " yum install rpmlint (RHEL/CentOS)" >&2 + ;; + esac + done + echo "" >&2 + fi + + return 0 +} + +# Function to validate spec file syntax +validate_spec_syntax() { + local spec_file="$1" + + echo "" + echo "==========================================" + echo "Validating spec file syntax..." + echo "==========================================" + echo "" + + # Check if rpmbuild is available + if ! command -v rpmbuild &> /dev/null; then + echo "Warning: rpmbuild not found, skipping syntax validation" >&2 + return 0 + fi + + # Try to use rpmbuild -bp (prep stage) to validate syntax + # This will check syntax without actually building + # Use --nodeps to check syntax/prep logic without requiring local RPM DB to have all deps + local temp_output=$(mktemp) + if rpmbuild -bp --nodeps "$spec_file" &> "$temp_output"; then + echo "✓ Spec file syntax is valid" + rm -f "$temp_output" + return 0 + else + # Check if it's a real syntax error or just missing source files + if grep -q "No such file or directory" "$temp_output" || grep -q "does not exist" "$temp_output"; then + echo "✓ Spec file syntax appears valid (source files not present for full validation)" + rm -f "$temp_output" + return 0 + else + echo "✗ Spec file has syntax errors:" >&2 + cat "$temp_output" | head -20 + rm -f "$temp_output" + return 1 + fi + fi +} + +# Function to run rpmlint on spec file +run_rpmlint() { + local spec_file="$1" + + echo "" + echo "==========================================" + echo "Running rpmlint checks..." + echo "==========================================" + echo "" + + # Check if rpmlint is available + if ! command -v rpmlint &> /dev/null; then + echo "Note: rpmlint not found, skipping quality checks" + echo "Install rpmlint for additional spec file validation" + return 0 + fi + + # Run rpmlint + local rpmlint_output + rpmlint_output=$(rpmlint "$spec_file" 2>&1) + local rpmlint_exit=$? + + echo "$rpmlint_output" + echo "" + + # Count errors and warnings + local errors=$(echo "$rpmlint_output" | grep -c "E:" || true) + local warnings=$(echo "$rpmlint_output" | grep -c "W:" || true) + + if [ $errors -gt 0 ]; then + echo "⚠ Found $errors error(s) and $warnings warning(s)" + echo "Please review and fix errors before building" + return 1 + elif [ $warnings -gt 0 ]; then + echo "⚠ Found $warnings warning(s)" + echo "Warnings can often be ignored, but please review them" + return 0 + else + echo "✓ No errors or warnings found" + return 0 + fi +} + +# Function to validate generated spec file +validate_spec_file() { + local spec_file="$1" + local pax_file="$2" + local buildroot="$3" + local validation_failed=false + + echo "" + echo "==========================================" + echo "Validating generated spec file..." + echo "==========================================" + + # Setup environment for validation (rpmbuild -bp needs source) + setup_rpmbuild "$buildroot" + + # Copy pax file to SOURCES if not already there + local source_name=$(basename "$pax_file") + if [ ! -f "$buildroot/SOURCES/$source_name" ] || [ "$pax_file" -nt "$buildroot/SOURCES/$source_name" ]; then + echo "Copying source file to $buildroot/SOURCES/$source_name for validation" + cp "$pax_file" "$buildroot/SOURCES/" + fi + + # Check syntax + if ! validate_spec_syntax "$spec_file"; then + validation_failed=true + fi + + # Run rpmlint + if ! run_rpmlint "$spec_file"; then + validation_failed=true + fi + + if [ "$validation_failed" = true ]; then + echo "" + echo "==========================================" + echo "✗ Validation failed" + echo "==========================================" + echo "" + echo "Please review the errors above and edit the spec file:" + echo " $spec_file" + echo "" + return 1 + else + echo "" + echo "==========================================" + echo "✓ Validation passed" + echo "==========================================" + echo "" + return 0 + fi +} + +# Function to perform dry run +perform_dry_run() { + local pax_file="$1" + + echo "" + echo "==========================================" + echo "DRY RUN MODE - No files will be created" + echo "==========================================" + echo "" + + echo "Would perform the following actions:" + echo "" + echo "1. Analyze pax file: $pax_file" + echo "2. Extract package information:" + echo " - Name: $PKG_NAME" + echo " - Version: $PKG_VERSION" + echo " - Release: $RELEASE" + echo "3. Generate spec file: $OUTPUT_FILE" + + if [ "$VALIDATE_SPEC" = true ]; then + echo "4. Validate spec file syntax and run rpmlint" + fi + + if [ "$BUILD_RPM" = true ]; then + echo "5. Build RPM package in: $BUILDROOT" + echo " - Copy $pax_file to $BUILDROOT/SOURCES/" + echo " - Copy spec file to $BUILDROOT/SPECS/" + echo " - Run: rpmbuild -ba $OUTPUT_FILE" + fi + + echo "" + echo "To execute these actions, run without --dry-run flag" + echo "" +} + +# Function to build RPM +build_rpm() { + local spec_file="$1" + local pax_file="$2" + local buildroot="$3" + + echo "" + echo "==========================================" + echo "Building RPM package..." + echo "==========================================" + echo "" + + # Setup rpmbuild directories + setup_rpmbuild "$buildroot" + + # Copy pax file to SOURCES + local source_name=$(basename "$pax_file") + echo "Copying source file to $buildroot/SOURCES/$source_name" + cp "$pax_file" "$buildroot/SOURCES/" + + # Copy spec file to SPECS + echo "Copying spec file to $buildroot/SPECS/" + cp "$spec_file" "$buildroot/SPECS/" + + # Build the RPM + echo "" + echo "Running rpmbuild -ba $spec_file" + echo "" + + if rpmbuild -ba "$spec_file"; then + echo "" + echo "==========================================" + echo "✓ RPM build completed successfully!" + echo "==========================================" + echo "" + echo "Generated packages:" + echo "" + echo "Binary RPMs:" + find "$buildroot/RPMS" -name "*.rpm" -type f 2>/dev/null | while read rpm; do + echo " - $rpm" + done + echo "" + echo "Source RPMs:" + find "$buildroot/SRPMS" -name "*.rpm" -type f 2>/dev/null | while read rpm; do + echo " - $rpm" + done + echo "" + return 0 + else + echo "" + echo "==========================================" + echo "✗ RPM build failed!" + echo "==========================================" + echo "" + echo "Please review the spec file and build output above." + echo "Common issues:" + echo " - Missing BuildRequires dependencies" + echo " - Incorrect file paths in %install or %files sections" + echo " - Pax extraction errors" + echo "" + return 1 + fi +} + +# Main script +main() { + if [ $# -eq 0 ]; then + usage + fi + + PAX_FILE="$1" + shift + + # Check if pax file exists + if [ ! -f "$PAX_FILE" ]; then + echo "Error: Pax file not found: $PAX_FILE" >&2 + exit 1 + fi + + # Parse filename to get default name and version + parse_filename "$PAX_FILE" + + # Parse command line options + while [ $# -gt 0 ]; do + case "$1" in + --name) + PKG_NAME="$2" + shift 2 + ;; + --version) + PKG_VERSION="$2" + shift 2 + ;; + --release) + RELEASE="$2" + shift 2 + ;; + --license) + LICENSE="$2" + shift 2 + ;; + --summary) + SUMMARY="$2" + shift 2 + ;; + --description) + DESCRIPTION="$2" + shift 2 + ;; + --url) + URL="$2" + shift 2 + ;; + --output) + OUTPUT_FILE="$2" + shift 2 + ;; + --requires) + REQUIRES="$2" + shift 2 + ;; + --build) + BUILD_RPM=true + shift + ;; + --buildroot) + BUILDROOT="$2" + shift 2 + ;; + --validate) + VALIDATE_SPEC=true + shift + ;; + --dry-run) + DRY_RUN=true + shift + ;; + --verbose) + VERBOSE=true + set -x + shift + ;; + --help) + usage + ;; + *) + echo "Error: Unknown option: $1" >&2 + usage + ;; + esac + done + + # Set defaults for required fields + if [ -z "$SUMMARY" ]; then + echo "Error: --summary is required" >&2 + exit 1 + fi + + if [ -z "$DESCRIPTION" ]; then + DESCRIPTION="$SUMMARY" + fi + + if [ -z "$OUTPUT_FILE" ]; then + OUTPUT_FILE="${PKG_NAME}.spec" + fi + + # Check for required tools + if ! check_required_tools "$PAX_FILE"; then + exit 1 + fi + + # Display extracted information + echo "Package Information:" + echo " Name: $PKG_NAME" + echo " Version: $PKG_VERSION" + echo " Release: $RELEASE" + echo " License: $LICENSE" + echo " Summary: $SUMMARY" + echo " Source: $(basename "$PAX_FILE")" + echo " Output: $OUTPUT_FILE" + echo "" + + # Handle dry-run mode + if [ "$DRY_RUN" = true ]; then + perform_dry_run "$PAX_FILE" + exit 0 + fi + + # List pax contents for reference + list_pax_contents "$PAX_FILE" || echo " (Could not list contents)" + echo "" + + # Generate spec file + generate_spec "$OUTPUT_FILE" "$PAX_FILE" + + # Validate spec file if requested + if [ "$VALIDATE_SPEC" = true ]; then + if ! validate_spec_file "$OUTPUT_FILE" "$PAX_FILE" "$BUILDROOT"; then + echo "Validation failed. Please fix the errors and try again." >&2 + exit 1 + fi + fi + + # Build RPM if requested + if [ "$BUILD_RPM" = true ]; then + if build_rpm "$OUTPUT_FILE" "$PAX_FILE" "$BUILDROOT"; then + echo "RPM package built successfully!" + else + echo "Warning: RPM build failed. Please review the spec file and try again." >&2 + exit 1 + fi + else + echo "" + echo "Next steps:" + echo " 1. Review and edit the generated spec file: $OUTPUT_FILE" + echo " 2. Adjust the %install and %files sections if needed" + if [ "$VALIDATE_SPEC" = false ]; then + echo " 3. Validate the spec: $0 $PAX_FILE --summary \"$SUMMARY\" --validate" + echo " 4. Copy the pax file to rpmbuild/SOURCES/" + echo " 5. Build the RPM: rpmbuild -ba $OUTPUT_FILE" + else + echo " 3. Copy the pax file to rpmbuild/SOURCES/" + echo " 4. Build the RPM: rpmbuild -ba $OUTPUT_FILE" + fi + echo "" + echo "Or run with --build flag to build automatically:" + echo " $0 $PAX_FILE --summary \"$SUMMARY\" --build" + fi +} + +main "$@" diff --git a/cicd/publish.groovy b/cicd/publish.groovy index 9336e696a..16e2bea83 100755 --- a/cicd/publish.groovy +++ b/cicd/publish.groovy @@ -22,6 +22,7 @@ GITHUB_REPO=$RELEASE_PREFIX # PAX file should be a copied artifact PAX=`find . -type f -path "*install/*zos.pax.Z"` +RPM=`find rpmbuild/RPMS -type f -name "*.rpm"` METADATA=`find . -type f -path "*install/metadata.json"` BUILD_STATUS=`find . -name "test.status" | xargs cat` DEPENDENCIES=`find . -name ".runtimedeps" | xargs cat` @@ -33,6 +34,11 @@ if [ ! -f "$PAX" ]; then exit 1; fi +if [ ! -f "$RPM" ]; then + echo "Port RPM file does not exist"; + exit 1; +fi + if [ ! -f "$METADATA" ]; then echo "Port metadata.json file does not exist"; exit 1; @@ -57,6 +63,8 @@ PAX_BASENAME=$(basename "${PAX}") DIR_NAME=${PAX_BASENAME%%.pax.Z} DIR_NAME=$(echo "$DIR_NAME" | sed -e "s/\.202[0-9]*_[0-9]*\.zos/.zos/g" -e "s/\.zos//g") BUILD_ID=${BUILD_NUMBER} +RPM_BASENAME=$(basename "${RPM}") + # Check for python dependencies if pip3 show numpy &> /dev/null; then @@ -198,6 +206,15 @@ else exit 1 fi +echo "Uploading the RPM artifacts into github" +github-release -v upload --user ${GITHUB_ORGANIZATION} --repo ${GITHUB_REPO} --tag "${TAG}" --name "${RPM_BASENAME}" --file "${RPM}" +if [ $? -eq 0 ]; then + echo "RPM Artifact uploaded successfully!" +else + echo "Failed to upload RPM artifact!" + exit 1 +fi + echo "Uploading metadata artifacts into github" github-release -v upload --user ${GITHUB_ORGANIZATION} --repo ${GITHUB_REPO} --tag "${TAG}" --name "metadata.json" --file "${METADATA}" if [ $? -eq 0 ]; then diff --git a/zopen-build b/zopen-build new file mode 100644 index 000000000..e69de29bb From 993c3ea6401442863be78e0e5487f46a9609223b Mon Sep 17 00:00:00 2001 From: TejaswiniIBM Date: Mon, 23 Mar 2026 01:14:07 -0400 Subject: [PATCH 02/31] Resolved augment comments Signed-off-by: TejaswiniIBM --- bin/zopen-build | 20 ++++++++++++-------- bin/zopen-pax2rpm | 23 +++++++++++++---------- cicd/publish.groovy | 6 +++--- 3 files changed, 28 insertions(+), 21 deletions(-) diff --git a/bin/zopen-build b/bin/zopen-build index 4ad0e8699..9b5fe3ae2 100755 --- a/bin/zopen-build +++ b/bin/zopen-build @@ -480,12 +480,14 @@ processOptions() ;; "-gr" | "--generate-rpm") generateRPM=true + generatePax=true ;; "-s" | "--shell") startShell=true ;; "-sp" | "--sign-pax") signPax=true + generatePax=true ;; *) printError "Unknown option ${1} specified" @@ -2309,17 +2311,19 @@ install() if ! runAndLog "${ZOPEN_PAX_CMD}"; then printError "Could not generate pax \"${paxFileName}\"" fi + fi - if ${generateRPM}; then - if [ -f "${paxFileName}" ]; then - printHeader "Generating RPM from ${ZOPEN_INSTALL_DIR}" - rpm_deps=$(echo "${ZOPEN_RUNTIME_DEPS}" | xargs -n1 | sort -u | xargs) - PATH="${ZOPEN_ROOTFS}/usr/local/bin:${PATH}" "${MYDIR}/zopen-pax2rpm" "${paxFileName}" --summary "${ZOPEN_NAME} package" --build - else - printError "Pax file ${paxFileName} not found. Ensure --generate-pax is also used." - fi + if ${generateRPM}; then + if [ -f "${paxFileName}" ]; then + printHeader "Generating RPM from ${ZOPEN_INSTALL_DIR}" + rpm_deps=$(echo "${ZOPEN_RUNTIME_DEPS}" | xargs -n1 | sort -u | xargs) + PATH="${ZOPEN_ROOTFS}/usr/local/bin:${PATH}" "${MYDIR}/zopen-pax2rpm" "${paxFileName}" --summary "${ZOPEN_NAME} package" --requires "${rpm_deps}" --build + else + printError "Pax file ${paxFileName} not found. Ensure --generate-pax is also used." fi + fi + if ${generatePax}; then #TODO: Hack so that we can use coreutils md5sum without impacting builds ZOPEN_DEPS="${ZOPEN_DEPS} coreutils jq" if [ "${signPax}" = "true" ] && ( [ -z "${ZOPEN_GPG_SECRET_KEY_FILE}" ] || [ -z "${ZOPEN_GPG_SECRET_KEY_PASSPHRASE_FILE}" ] || [ -z "${ZOPEN_GPG_PUBLIC_KEY_FILE}" ] || [ ! -r "${ZOPEN_GPG_SECRET_KEY_FILE}" ] || [ ! -r "${ZOPEN_GPG_SECRET_KEY_PASSPHRASE_FILE}" ] || [ ! -r "${ZOPEN_GPG_PUBLIC_KEY_FILE}" ] ); then diff --git a/bin/zopen-pax2rpm b/bin/zopen-pax2rpm index 456db6479..56a36a35c 100755 --- a/bin/zopen-pax2rpm +++ b/bin/zopen-pax2rpm @@ -233,11 +233,14 @@ if [ -d usr ]; then if [ -n "$FOUND_PATH" ]; then FOUND_PATH=${FOUND_PATH#./} - if [[ "$FOUND_PATH" == */pyz ]]; then - ROOT_DIR="$FOUND_PATH" - else - ROOT_DIR=$(echo "$FOUND_PATH" | sed 's/\/\(bin\|lib\|bash\|go\)$//') - fi + case "$FOUND_PATH" in + */pyz) + ROOT_DIR="$FOUND_PATH" + ;; + *) + ROOT_DIR=$(echo "$FOUND_PATH" | sed 's/\/\(bin\|lib\|bash\|go\)$//') + ;; + esac else # Fallback for OS layout: find first dir with more than one entry ROOT_DIR="." @@ -328,14 +331,14 @@ cd %{name}-%{version} EOF if [[ "$source_file" == *.pax.Z ]]; then - cat >> "$output_file" << 'EOF' + cat >> "$output_file" << EOF # Extract compressed pax archive -pax -rzf %{SOURCE0} +pax -rzf %{_sourcedir}/$(basename "$source_file") EOF else - cat >> "$output_file" << 'EOF' + cat >> "$output_file" << EOF # Extract pax archive -pax -rf %{SOURCE0} +pax -rf %{_sourcedir}/$(basename "$source_file") EOF fi @@ -541,7 +544,7 @@ run_rpmlint() { # Run rpmlint local rpmlint_output - rpmlint_output=$(rpmlint "$spec_file" 2>&1) + rpmlint_output=$(rpmlint "$spec_file" 2>&1 || true) local rpmlint_exit=$? echo "$rpmlint_output" diff --git a/cicd/publish.groovy b/cicd/publish.groovy index 16e2bea83..08bf34c95 100755 --- a/cicd/publish.groovy +++ b/cicd/publish.groovy @@ -21,9 +21,9 @@ PORT_NAME=${RELEASE_PREFIX%%port} GITHUB_REPO=$RELEASE_PREFIX # PAX file should be a copied artifact -PAX=`find . -type f -path "*install/*zos.pax.Z"` -RPM=`find rpmbuild/RPMS -type f -name "*.rpm"` -METADATA=`find . -type f -path "*install/metadata.json"` +PAX=`find . -type f -path "*install/*zos.pax.Z" | head -n 1` +RPM=`find rpmbuild/RPMS -type f -name "*.rpm" | head -n 1` +METADATA=`find . -type f -path "*install/metadata.json" | head -n 1` BUILD_STATUS=`find . -name "test.status" | xargs cat` DEPENDENCIES=`find . -name ".runtimedeps" | xargs cat` BUILD_DEPENDENCIES=`find . -name ".builddeps" | xargs cat` From f271bac1c2428dbc68ac5fd5a3eb47d13c925781 Mon Sep 17 00:00:00 2001 From: TejaswiniIBM Date: Mon, 23 Mar 2026 01:47:04 -0400 Subject: [PATCH 03/31] Resolved PR reviews in zopen-pax2rpm, pubilsh.groovy Signed-off-by: TejaswiniIBM --- bin/zopen-pax2rpm | 19 +++++++++++++------ cicd/publish.groovy | 25 +++++++++++++++++-------- zopen-build | 0 zopen-diagnostics | 0 4 files changed, 30 insertions(+), 14 deletions(-) delete mode 100644 zopen-build delete mode 100644 zopen-diagnostics diff --git a/bin/zopen-pax2rpm b/bin/zopen-pax2rpm index 56a36a35c..f8bf3d205 100755 --- a/bin/zopen-pax2rpm +++ b/bin/zopen-pax2rpm @@ -392,6 +392,13 @@ setup_rpmbuild() { %_topdir $buildroot EOF echo "Created ~/.rpmmacros with _topdir set to $buildroot" + else + # Check if _topdir is already defined and different + local existing_topdir=$(grep "%_topdir" "$HOME/.rpmmacros" | awk '{print $2}') + if [ "$existing_topdir" != "$buildroot" ]; then + echo "Warning: ~/.rpmmacros exists and has _topdir set to '$existing_topdir'." + echo "The script will use '$buildroot' by overriding it with --define \"_topdir $buildroot\"." + fi fi } @@ -506,7 +513,7 @@ validate_spec_syntax() { # This will check syntax without actually building # Use --nodeps to check syntax/prep logic without requiring local RPM DB to have all deps local temp_output=$(mktemp) - if rpmbuild -bp --nodeps "$spec_file" &> "$temp_output"; then + if rpmbuild --define "_topdir $buildroot" -bp --nodeps "$spec_file" &> "$temp_output"; then echo "✓ Spec file syntax is valid" rm -f "$temp_output" return 0 @@ -647,7 +654,7 @@ perform_dry_run() { echo "5. Build RPM package in: $BUILDROOT" echo " - Copy $pax_file to $BUILDROOT/SOURCES/" echo " - Copy spec file to $BUILDROOT/SPECS/" - echo " - Run: rpmbuild -ba $OUTPUT_FILE" + echo " - Run: rpmbuild --define \"_topdir $buildroot\" -ba $OUTPUT_FILE" fi echo "" @@ -681,10 +688,10 @@ build_rpm() { # Build the RPM echo "" - echo "Running rpmbuild -ba $spec_file" + echo "Running rpmbuild --define \"_topdir $buildroot\" -ba $spec_file" echo "" - if rpmbuild -ba "$spec_file"; then + if rpmbuild --define "_topdir $buildroot" -ba "$spec_file"; then echo "" echo "==========================================" echo "✓ RPM build completed successfully!" @@ -874,10 +881,10 @@ main() { if [ "$VALIDATE_SPEC" = false ]; then echo " 3. Validate the spec: $0 $PAX_FILE --summary \"$SUMMARY\" --validate" echo " 4. Copy the pax file to rpmbuild/SOURCES/" - echo " 5. Build the RPM: rpmbuild -ba $OUTPUT_FILE" + echo " 5. Build the RPM: rpmbuild --define \"_topdir $BUILDROOT\" -ba $OUTPUT_FILE" else echo " 3. Copy the pax file to rpmbuild/SOURCES/" - echo " 4. Build the RPM: rpmbuild -ba $OUTPUT_FILE" + echo " 4. Build the RPM: rpmbuild --define \"_topdir $BUILDROOT\" -ba $OUTPUT_FILE" fi echo "" echo "Or run with --build flag to build automatically:" diff --git a/cicd/publish.groovy b/cicd/publish.groovy index 08bf34c95..104343099 100755 --- a/cicd/publish.groovy +++ b/cicd/publish.groovy @@ -21,9 +21,23 @@ PORT_NAME=${RELEASE_PREFIX%%port} GITHUB_REPO=$RELEASE_PREFIX # PAX file should be a copied artifact -PAX=`find . -type f -path "*install/*zos.pax.Z" | head -n 1` -RPM=`find rpmbuild/RPMS -type f -name "*.rpm" | head -n 1` -METADATA=`find . -type f -path "*install/metadata.json" | head -n 1` +PAX=`find . -type f -path "*install/*zos.pax.Z"` +if [ $(echo "$PAX" | grep -c /) -ne 1 ]; then + echo "Error: Expected exactly 1 PAX file, found: $PAX" + exit 1 +fi + +RPM=`find rpmbuild/RPMS -type f -name "*.rpm"` +if [ $(echo "$RPM" | grep -c /) -ne 1 ]; then + echo "Error: Expected exactly 1 RPM file, found: $RPM" + exit 1 +fi + +METADATA=`find . -type f -path "*install/metadata.json"` +if [ $(echo "$METADATA" | grep -c /) -ne 1 ]; then + echo "Error: Expected exactly 1 metadata.json file, found: $METADATA" + exit 1 +fi BUILD_STATUS=`find . -name "test.status" | xargs cat` DEPENDENCIES=`find . -name ".runtimedeps" | xargs cat` BUILD_DEPENDENCIES=`find . -name ".builddeps" | xargs cat` @@ -34,11 +48,6 @@ if [ ! -f "$PAX" ]; then exit 1; fi -if [ ! -f "$RPM" ]; then - echo "Port RPM file does not exist"; - exit 1; -fi - if [ ! -f "$METADATA" ]; then echo "Port metadata.json file does not exist"; exit 1; diff --git a/zopen-build b/zopen-build deleted file mode 100644 index e69de29bb..000000000 diff --git a/zopen-diagnostics b/zopen-diagnostics deleted file mode 100644 index e69de29bb..000000000 From d9b16e592dd3edc8c447ba152459e3be39c9e71d Mon Sep 17 00:00:00 2001 From: TejaswiniIBM Date: Mon, 23 Mar 2026 05:01:21 -0400 Subject: [PATCH 04/31] Resolved PR reviews in zopen-pax2rpm Signed-off-by: TejaswiniIBM --- bin/zopen-pax2rpm | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bin/zopen-pax2rpm b/bin/zopen-pax2rpm index f8bf3d205..0be49bfea 100755 --- a/bin/zopen-pax2rpm +++ b/bin/zopen-pax2rpm @@ -124,7 +124,7 @@ analyze_pax_contents() { if [ -z "$pax_listing" ]; then echo "# Warning: Could not analyze pax contents, using default structure" >&2 rm -rf "$temp_dir" - return 1 + return 0 fi # Initialize arrays for different file types @@ -496,6 +496,7 @@ check_required_tools() { # Function to validate spec file syntax validate_spec_syntax() { local spec_file="$1" + local buildroot="$2" echo "" echo "==========================================" @@ -598,7 +599,7 @@ validate_spec_file() { fi # Check syntax - if ! validate_spec_syntax "$spec_file"; then + if ! validate_spec_syntax "$spec_file" "$buildroot"; then validation_failed=true fi @@ -654,7 +655,7 @@ perform_dry_run() { echo "5. Build RPM package in: $BUILDROOT" echo " - Copy $pax_file to $BUILDROOT/SOURCES/" echo " - Copy spec file to $BUILDROOT/SPECS/" - echo " - Run: rpmbuild --define \"_topdir $buildroot\" -ba $OUTPUT_FILE" + echo " - Run: rpmbuild --define \"_topdir $BUILDROOT\" -ba $OUTPUT_FILE" fi echo "" From 5531b9b2691febc5d099fcad0a863255d70d5cf5 Mon Sep 17 00:00:00 2001 From: TejaswiniIBM Date: Thu, 26 Mar 2026 01:23:37 -0400 Subject: [PATCH 05/31] Resolved PR comments Signed-off-by: TejaswiniIBM --- bin/zopen-build | 2 +- bin/zopen-pax2rpm | 9 +++++++-- cicd/publish.groovy | 32 +++++++++++++++++++++----------- 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/bin/zopen-build b/bin/zopen-build index 9b5fe3ae2..611f25e20 100755 --- a/bin/zopen-build +++ b/bin/zopen-build @@ -2317,7 +2317,7 @@ install() if [ -f "${paxFileName}" ]; then printHeader "Generating RPM from ${ZOPEN_INSTALL_DIR}" rpm_deps=$(echo "${ZOPEN_RUNTIME_DEPS}" | xargs -n1 | sort -u | xargs) - PATH="${ZOPEN_ROOTFS}/usr/local/bin:${PATH}" "${MYDIR}/zopen-pax2rpm" "${paxFileName}" --summary "${ZOPEN_NAME} package" --requires "${rpm_deps}" --build + PATH="${ZOPEN_ROOTFS}/usr/local/bin:${PATH}" "${MYDIR}/zopen-pax2rpm" "${paxFileName}" --summary "${ZOPEN_NAME} package" --requires "${rpm_deps}" --build --buildroot "${ZOPEN_ROOT}/rpmbuild" else printError "Pax file ${paxFileName} not found. Ensure --generate-pax is also used." fi diff --git a/bin/zopen-pax2rpm b/bin/zopen-pax2rpm index 0be49bfea..b4e3b0f01 100755 --- a/bin/zopen-pax2rpm +++ b/bin/zopen-pax2rpm @@ -388,10 +388,15 @@ setup_rpmbuild() { mkdir -p "$buildroot"/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS} if [ ! -f "$HOME/.rpmmacros" ]; then - cat > "$HOME/.rpmmacros" << EOF + if touch "$HOME/.rpmmacros" 2>/dev/null; then + cat > "$HOME/.rpmmacros" << EOF %_topdir $buildroot EOF - echo "Created ~/.rpmmacros with _topdir set to $buildroot" + echo "Created ~/.rpmmacros with _topdir set to $buildroot" + else + echo "Warning: could not create ~/.rpmmacros. This is normal in some CI environments." + echo "The script will use '$buildroot' by overriding it with --define \"_topdir $buildroot\"." + fi else # Check if _topdir is already defined and different local existing_topdir=$(grep "%_topdir" "$HOME/.rpmmacros" | awk '{print $2}') diff --git a/cicd/publish.groovy b/cicd/publish.groovy index 104343099..9343a16e6 100755 --- a/cicd/publish.groovy +++ b/cicd/publish.groovy @@ -27,10 +27,13 @@ if [ $(echo "$PAX" | grep -c /) -ne 1 ]; then exit 1 fi -RPM=`find rpmbuild/RPMS -type f -name "*.rpm"` -if [ $(echo "$RPM" | grep -c /) -ne 1 ]; then - echo "Error: Expected exactly 1 RPM file, found: $RPM" - exit 1 +RPM_FILES=($(find rpmbuild/RPMS -type f -name "*.rpm" 2>/dev/null)) +NUM_RPMS=${#RPM_FILES[@]} + +if [ $NUM_RPMS -gt 0 ]; then + echo "Found $NUM_RPMS RPM file(s): ${RPM_FILES[@]}" +else + echo "No RPM files found. Skipping RPM upload." fi METADATA=`find . -type f -path "*install/metadata.json"` @@ -72,7 +75,6 @@ PAX_BASENAME=$(basename "${PAX}") DIR_NAME=${PAX_BASENAME%%.pax.Z} DIR_NAME=$(echo "$DIR_NAME" | sed -e "s/\.202[0-9]*_[0-9]*\.zos/.zos/g" -e "s/\.zos//g") BUILD_ID=${BUILD_NUMBER} -RPM_BASENAME=$(basename "${RPM}") # Check for python dependencies @@ -215,13 +217,21 @@ else exit 1 fi -echo "Uploading the RPM artifacts into github" -github-release -v upload --user ${GITHUB_ORGANIZATION} --repo ${GITHUB_REPO} --tag "${TAG}" --name "${RPM_BASENAME}" --file "${RPM}" -if [ $? -eq 0 ]; then - echo "RPM Artifact uploaded successfully!" +if [ $NUM_RPMS -gt 0 ]; then + echo "Uploading the RPM artifacts into github" + for RPM in "${RPM_FILES[@]}"; do + RPM_BASENAME=$(basename "${RPM}") + echo "Uploading ${RPM_BASENAME}..." + github-release -v upload --user ${GITHUB_ORGANIZATION} --repo ${GITHUB_REPO} --tag "${TAG}" --name "${RPM_BASENAME}" --file "${RPM}" + if [ $? -eq 0 ]; then + echo "RPM Artifact ${RPM_BASENAME} uploaded successfully!" + else + echo "Failed to upload RPM artifact ${RPM_BASENAME}!" + exit 1 + fi + done else - echo "Failed to upload RPM artifact!" - exit 1 + echo "No RPM artifacts to upload." fi echo "Uploading metadata artifacts into github" From 9da910e9731ade2e4749e0e4adb4d3e6501d7a46 Mon Sep 17 00:00:00 2001 From: TejaswiniIBM Date: Tue, 31 Mar 2026 07:12:24 -0400 Subject: [PATCH 06/31] Fixing the review comments Signed-off-by: TejaswiniIBM --- bin/zopen-build | 4 +++- bin/zopen-pax2rpm | 17 +++++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/bin/zopen-build b/bin/zopen-build index 611f25e20..216ec2baa 100755 --- a/bin/zopen-build +++ b/bin/zopen-build @@ -2317,7 +2317,9 @@ install() if [ -f "${paxFileName}" ]; then printHeader "Generating RPM from ${ZOPEN_INSTALL_DIR}" rpm_deps=$(echo "${ZOPEN_RUNTIME_DEPS}" | xargs -n1 | sort -u | xargs) - PATH="${ZOPEN_ROOTFS}/usr/local/bin:${PATH}" "${MYDIR}/zopen-pax2rpm" "${paxFileName}" --summary "${ZOPEN_NAME} package" --requires "${rpm_deps}" --build --buildroot "${ZOPEN_ROOT}/rpmbuild" + if ! runAndLog "PATH=\"${ZOPEN_ROOTFS}/usr/local/bin:${PATH}\" \"${MYDIR}/zopen-pax2rpm\" \"${paxFileName}\" --summary \"${ZOPEN_NAME} package\" --requires \"${rpm_deps}\" --build --buildroot \"${ZOPEN_ROOT}/rpmbuild\""; then + printError "Could not generate RPM from \"${paxFileName}\"" + fi else printError "Pax file ${paxFileName} not found. Ensure --generate-pax is also used." fi diff --git a/bin/zopen-pax2rpm b/bin/zopen-pax2rpm index b4e3b0f01..e76de855e 100755 --- a/bin/zopen-pax2rpm +++ b/bin/zopen-pax2rpm @@ -229,7 +229,8 @@ generate_install_commands() { if [ -d usr ]; then echo "OS layout (usr/) detected, searching for actual content root..." # Find the deepest directory that contains bin, lib, pyz, bash, or go - FOUND_PATH=$(find . -type d \( -name bin -o -name lib -o -name pyz -o -name bash -o -name go \) -print -quit) + # We sort by the number of slashes to find the deepest one + FOUND_PATH=$(find . -type d \( -name bin -o -name lib -o -name pyz -o -name bash -o -name go \) -print | awk -F/ '{print NF, $0}' | sort -rn | head -n 1 | cut -d' ' -f2-) if [ -n "$FOUND_PATH" ]; then FOUND_PATH=${FOUND_PATH#./} @@ -424,7 +425,9 @@ check_required_tools() { # Check for rpmbuild if building or validating if [ "$BUILD_RPM" = true ] || [ "$VALIDATE_SPEC" = true ]; then - echo "DEBUG: Current PATH is $PATH" >&2 + if [ "$VERBOSE" = true ]; then + echo "DEBUG: Current PATH is $PATH" >&2 + fi if ! command -v rpmbuild &> /dev/null; then missing_tools+=("rpmbuild") fi @@ -754,38 +757,47 @@ main() { while [ $# -gt 0 ]; do case "$1" in --name) + [ -n "$2" ] || { echo "Error: --name requires a value" >&2; usage; } PKG_NAME="$2" shift 2 ;; --version) + [ -n "$2" ] || { echo "Error: --version requires a value" >&2; usage; } PKG_VERSION="$2" shift 2 ;; --release) + [ -n "$2" ] || { echo "Error: --release requires a value" >&2; usage; } RELEASE="$2" shift 2 ;; --license) + [ -n "$2" ] || { echo "Error: --license requires a value" >&2; usage; } LICENSE="$2" shift 2 ;; --summary) + [ -n "$2" ] || { echo "Error: --summary requires a value" >&2; usage; } SUMMARY="$2" shift 2 ;; --description) + [ -n "$2" ] || { echo "Error: --description requires a value" >&2; usage; } DESCRIPTION="$2" shift 2 ;; --url) + [ -n "$2" ] || { echo "Error: --url requires a value" >&2; usage; } URL="$2" shift 2 ;; --output) + [ -n "$2" ] || { echo "Error: --output requires a value" >&2; usage; } OUTPUT_FILE="$2" shift 2 ;; --requires) + [ -n "$2" ] || { echo "Error: --requires requires a value" >&2; usage; } REQUIRES="$2" shift 2 ;; @@ -794,6 +806,7 @@ main() { shift ;; --buildroot) + [ -n "$2" ] || { echo "Error: --buildroot requires a value" >&2; usage; } BUILDROOT="$2" shift 2 ;; From 962d0b3187cbd7529a39aae9e129bb3043559153 Mon Sep 17 00:00:00 2001 From: TejaswiniIBM Date: Tue, 31 Mar 2026 07:53:51 -0400 Subject: [PATCH 07/31] Resolved the pr review comments v2 Signed-off-by: TejaswiniIBM --- bin/zopen-build | 6 +++++- bin/zopen-pax2rpm | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/bin/zopen-build b/bin/zopen-build index 216ec2baa..d03dfa877 100755 --- a/bin/zopen-build +++ b/bin/zopen-build @@ -2317,7 +2317,11 @@ install() if [ -f "${paxFileName}" ]; then printHeader "Generating RPM from ${ZOPEN_INSTALL_DIR}" rpm_deps=$(echo "${ZOPEN_RUNTIME_DEPS}" | xargs -n1 | sort -u | xargs) - if ! runAndLog "PATH=\"${ZOPEN_ROOTFS}/usr/local/bin:${PATH}\" \"${MYDIR}/zopen-pax2rpm\" \"${paxFileName}\" --summary \"${ZOPEN_NAME} package\" --requires \"${rpm_deps}\" --build --buildroot \"${ZOPEN_ROOT}/rpmbuild\""; then + cmd="PATH=\"${ZOPEN_ROOTFS}/usr/local/bin:${PATH}\" \"${MYDIR}/zopen-pax2rpm\" \"${paxFileName}\" --summary \"${ZOPEN_NAME} package\" --build --buildroot \"${ZOPEN_ROOT}/rpmbuild\"" + if [ -n "${rpm_deps}" ]; then + cmd="${cmd} --requires \"${rpm_deps}\"" + fi + if ! runAndLog "${cmd}"; then printError "Could not generate RPM from \"${paxFileName}\"" fi else diff --git a/bin/zopen-pax2rpm b/bin/zopen-pax2rpm index e76de855e..4c51a8e25 100755 --- a/bin/zopen-pax2rpm +++ b/bin/zopen-pax2rpm @@ -210,9 +210,9 @@ list_pax_contents() { echo "# Pax file contents (first 100 entries):" >&2 if [[ "$pax_file" == *.pax.Z ]]; then - pax -rvzf "$pax_file" 2>&1 | head -100 + pax -vzf "$pax_file" 2>&1 | head -100 else - pax -rvf "$pax_file" 2>&1 | head -100 + pax -vf "$pax_file" 2>&1 | head -100 fi } From 541aa2e91e7bfa03e414f3aae4bb6dfb225dd4e1 Mon Sep 17 00:00:00 2001 From: TejaswiniIBM Date: Tue, 31 Mar 2026 11:07:39 -0400 Subject: [PATCH 08/31] resolved pr comments 3 Signed-off-by: TejaswiniIBM --- bin/zopen-pax2rpm | 82 +++++++++++++++++++++++++---------------------- 1 file changed, 43 insertions(+), 39 deletions(-) diff --git a/bin/zopen-pax2rpm b/bin/zopen-pax2rpm index 4c51a8e25..89b889109 100755 --- a/bin/zopen-pax2rpm +++ b/bin/zopen-pax2rpm @@ -19,14 +19,31 @@ # --dry-run Show what would be done without doing it # -set -e +# +# All zopen-* scripts MUST start with this code to maintain consistency. +# +setupMyself() +{ + ME=$(basename "$0") + MYDIR="$( cd "$(dirname "$0")" >/dev/null 2>&1 && pwd -P )" + INCDIR="${MYDIR}/../include" + if ! [ -d "${INCDIR}" ] && ! [ -f "${INCDIR}/common.sh" ]; then + echo "Internal Error. Unable to find common.sh file to source." >&2 + exit 8 + fi + . "${INCDIR}/common.sh" +} +setupMyself # Default values RELEASE="1" LICENSE="Proprietary" BUILD_ARCH=$(uname -m 2>/dev/null || echo "s390x") -PACKAGER_NAME="${USER}" -PACKAGER_EMAIL="${USER}@$(hostname)" + +# Get current user safely for packager info +CURRENT_USER="${USER:-$(whoami 2>/dev/null || echo "zopen-community")}" +PACKAGER_NAME="${CURRENT_USER}" +PACKAGER_EMAIL="${CURRENT_USER}@$(hostname 2>/dev/null || echo "localhost")" # Build flag BUILD_RPM=false @@ -37,6 +54,7 @@ VERBOSE=false # Function to display usage usage() { + local exit_code="${1:-1}" cat << EOF Usage: $0 [options] @@ -69,7 +87,7 @@ Example: --url "https://www.ibm.com" EOF - exit 1 + exit "$exit_code" } # Function to extract package name and version from filename @@ -80,17 +98,19 @@ parse_filename() { # Remove .pax.Z or .pax extension basename="${basename%.pax.Z}" basename="${basename%.pax}" + + # Handle .zos suffix commonly used in zopen packages + basename="${basename%.zos}" # Try to extract name and version - # Pattern 1: NAME-VERSION (e.g., package-1.0) - if [[ "$basename" =~ ^(.+)-([0-9]+\.[0-9]+.*)$ ]]; then + # Pattern 1: NAME-VERSION (e.g., package-1.0, curl-8.10.1.20241001_214340) + if [[ "$basename" =~ ^(.+)-([0-9].*)$ ]]; then PKG_NAME="${BASH_REMATCH[1]}" PKG_VERSION="${BASH_REMATCH[2]}" # Pattern 2: NAMEVERSION.suffix (e.g., HAMN110.runnable) elif [[ "$basename" =~ ^([A-Za-z]+)([0-9]+)\.(.+)$ ]]; then PKG_NAME="${BASH_REMATCH[1]}" PKG_VERSION="${BASH_REMATCH[2]}" - # Don't include suffix like "runnable" in version # Pattern 3: NAMEVERSION (e.g., HAMN110) elif [[ "$basename" =~ ^([A-Za-z]+)([0-9]+)$ ]]; then PKG_NAME="${BASH_REMATCH[1]}" @@ -107,23 +127,21 @@ parse_filename() { # Function to analyze pax contents and categorize files analyze_pax_contents() { local pax_file="$1" - local temp_dir=$(mktemp -d) + local temp_dir=$(mktempdir "pax2rpm") - echo "# Analyzing pax file structure..." >&2 + printInfo "# Analyzing pax file structure..." - # Extract pax listing (limit to first 1000 entries for performance) + # Extract pax listing (limit to first 5000 entries for performance/accuracy balance) local pax_listing if [[ "$pax_file" == *.pax.Z ]]; then - pax_listing=$(pax -vzf "$pax_file" 2>&1 | head -1000) + pax_listing=$(pax -vzf "$pax_file" 2>&1 | head -5000) else - pax_listing=$(pax -vf "$pax_file" 2>&1 | head -1000) + pax_listing=$(pax -vf "$pax_file" 2>&1 | head -5000) fi # Check if pax listing failed or is empty - # Only treat as error if pax command actually failed (not just listing files) if [ -z "$pax_listing" ]; then - echo "# Warning: Could not analyze pax contents, using default structure" >&2 - rm -rf "$temp_dir" + printWarning "Could not analyze pax contents, using default structure" return 0 fi @@ -207,7 +225,7 @@ analyze_pax_contents() { list_pax_contents() { local pax_file="$1" - echo "# Pax file contents (first 100 entries):" >&2 + printHeader "# Pax file contents (first 100 entries):" if [[ "$pax_file" == *.pax.Z ]]; then pax -vzf "$pax_file" 2>&1 | head -100 @@ -418,11 +436,6 @@ check_required_tools() { missing_tools+=("pax") fi - # Check for uncompress if dealing with .Z files - if [[ "$pax_file" == *.pax.Z ]] && ! command -v uncompress &> /dev/null; then - missing_tools+=("uncompress") - fi - # Check for rpmbuild if building or validating if [ "$BUILD_RPM" = true ] || [ "$VALIDATE_SPEC" = true ]; then if [ "$VERBOSE" = true ]; then @@ -435,10 +448,7 @@ check_required_tools() { if [ ${#missing_tools[@]} -gt 0 ]; then echo "" >&2 - echo "=========================================" >&2 - echo "ERROR: Missing Required Tools" >&2 - echo "=========================================" >&2 - echo "" >&2 + printError "Missing Required Tools" echo "The following tools are required but not found:" >&2 echo "" >&2 @@ -449,29 +459,19 @@ check_required_tools() { pax) echo " Purpose: Extract pax archives" >&2 echo " Install: Usually pre-installed on z/OS" >&2 - echo " On Linux: apt-get install pax (Debian/Ubuntu)" >&2 - echo " yum install pax (RHEL/CentOS)" >&2 - ;; - uncompress) - echo " Purpose: Decompress .Z files" >&2 - echo " Install: Usually part of ncompress package" >&2 - echo " On z/OS: May be in /usr/bin or /bin" >&2 - echo " On Linux: apt-get install ncompress (Debian/Ubuntu)" >&2 - echo " yum install ncompress (RHEL/CentOS)" >&2 + echo " On Linux: apt-get install pax (Debian/Ubuntu/RHEL/CentOS)" >&2 ;; rpmbuild) echo " Purpose: Build RPM packages and validate spec files" >&2 echo " Install: Part of rpm-build package" >&2 echo " On z/OS: zopen install rpm" >&2 - echo " On Linux: apt-get install rpm (Debian/Ubuntu)" >&2 - echo " yum install rpm-build (RHEL/CentOS)" >&2 + echo " On Linux: apt-get install rpm (Debian/Ubuntu/RHEL/CentOS)" >&2 ;; esac echo "" >&2 done - echo "Please install the missing tools and try again." >&2 - echo "" >&2 + printInfo "Please install the missing tools and try again." return 1 fi @@ -741,6 +741,10 @@ main() { usage fi + if [ "$1" == "--help" ]; then + usage 0 + fi + PAX_FILE="$1" shift @@ -824,7 +828,7 @@ main() { shift ;; --help) - usage + usage 0 ;; *) echo "Error: Unknown option: $1" >&2 From 057745dea4e61c988798bce8b97c1e73d5ae6f98 Mon Sep 17 00:00:00 2001 From: TejaswiniIBM Date: Thu, 2 Apr 2026 00:01:48 -0400 Subject: [PATCH 09/31] versioning added Signed-off-by: TejaswiniIBM --- bin/zopen-pax2rpm | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/bin/zopen-pax2rpm b/bin/zopen-pax2rpm index 89b889109..a97b9a3ea 100755 --- a/bin/zopen-pax2rpm +++ b/bin/zopen-pax2rpm @@ -66,6 +66,7 @@ Arguments: Options: --name Override package name (default: extracted from filename) --version Override version (default: extracted from filename) + --pkg-version Override version (alternative to --version) --release Override release number (default: 1) --license Specify license (default: Proprietary) --summary Package summary (required) @@ -79,6 +80,7 @@ Options: --dry-run Show what would be done without actually doing it --verbose Enable verbose debug output --help Display this help message + --version Display tool version Example: $0 /nfsmnts/bpidrivers/oefv1r1/os390/latest/HAMN110.runnable.pax.Z \\ @@ -745,6 +747,15 @@ main() { usage 0 fi + if [ "$1" == "--version" ]; then + if [ -x "${MYDIR}/zopen-version" ]; then + "${MYDIR}/zopen-version" "${ME}" + else + echo "${ME} version $(cat "${INCDIR}/zopen_version" 2>/dev/null || echo "unknown")" + fi + exit 0 + fi + PAX_FILE="$1" shift @@ -766,7 +777,19 @@ main() { shift 2 ;; --version) - [ -n "$2" ] || { echo "Error: --version requires a value" >&2; usage; } + if [ -z "$2" ] || [[ "$2" == --* ]]; then + if [ -x "${MYDIR}/zopen-version" ]; then + "${MYDIR}/zopen-version" "${ME}" + else + echo "${ME} version $(cat "${INCDIR}/zopen_version" 2>/dev/null || echo "unknown")" + fi + exit 0 + fi + PKG_VERSION="$2" + shift 2 + ;; + --pkg-version) + [ -n "$2" ] || { echo "Error: --pkg-version requires a value" >&2; usage; } PKG_VERSION="$2" shift 2 ;; From edb48242a2b37f28ff17aabed929d269c6c5b2e5 Mon Sep 17 00:00:00 2001 From: TejaswiniIBM Date: Mon, 6 Apr 2026 09:49:00 -0400 Subject: [PATCH 10/31] refactored the script to matchother zopen tools Signed-off-by: TejaswiniIBM --- bin/zopen-pax2rpm | 297 +++++++++++++++++++++------------------------- 1 file changed, 132 insertions(+), 165 deletions(-) diff --git a/bin/zopen-pax2rpm b/bin/zopen-pax2rpm index a97b9a3ea..c3aeb6b45 100755 --- a/bin/zopen-pax2rpm +++ b/bin/zopen-pax2rpm @@ -1,22 +1,6 @@ -#!/bin/bash +#!/bin/sh # -# pax2rpm.sh - Generate RPM spec file from z/OS pax archive -# -# Usage: pax2rpm.sh [options] -# -# Options: -# --name Override package name -# --version Override version -# --release Override release number (default: 1) -# --license Specify license (default: Proprietary) -# --summary Package summary -# --description Package description -# --url Project URL -# --output Output spec file (default: .spec) -# --build Build the RPM after generating spec file -# --buildroot RPM build root directory (default: ~/rpmbuild) -# --validate Validate spec file after generation -# --dry-run Show what would be done without doing it +# RPM generation utility for zopen community - https://github.com/zopencommunity # # @@ -24,8 +8,8 @@ # setupMyself() { - ME=$(basename "$0") - MYDIR="$( cd "$(dirname "$0")" >/dev/null 2>&1 && pwd -P )" + ME=$(basename $0) + MYDIR="$(cd "$(dirname "$0")" > /dev/null 2>&1 && pwd -P)" INCDIR="${MYDIR}/../include" if ! [ -d "${INCDIR}" ] && ! [ -f "${INCDIR}/common.sh" ]; then echo "Internal Error. Unable to find common.sh file to source." >&2 @@ -54,7 +38,7 @@ VERBOSE=false # Function to display usage usage() { - local exit_code="${1:-1}" +exit_code="${1:-1}" cat << EOF Usage: $0 [options] @@ -94,8 +78,8 @@ EOF # Function to extract package name and version from filename parse_filename() { - local filename="$1" - local basename=$(basename "$filename") + filename="$1" + basename=$(basename "$filename") # Remove .pax.Z or .pax extension basename="${basename%.pax.Z}" @@ -104,19 +88,19 @@ parse_filename() { # Handle .zos suffix commonly used in zopen packages basename="${basename%.zos}" - # Try to extract name and version + # Try to extract name and version using sed since BASH_REMATCH is a bashism # Pattern 1: NAME-VERSION (e.g., package-1.0, curl-8.10.1.20241001_214340) - if [[ "$basename" =~ ^(.+)-([0-9].*)$ ]]; then - PKG_NAME="${BASH_REMATCH[1]}" - PKG_VERSION="${BASH_REMATCH[2]}" + if echo "$basename" | grep -qE '^(.+)-([0-9].*)$'; then + PKG_NAME=$(echo "$basename" | sed -E 's/^(.+)-([0-9].*)$/\1/') + PKG_VERSION=$(echo "$basename" | sed -E 's/^(.+)-([0-9].*)$/\2/') # Pattern 2: NAMEVERSION.suffix (e.g., HAMN110.runnable) - elif [[ "$basename" =~ ^([A-Za-z]+)([0-9]+)\.(.+)$ ]]; then - PKG_NAME="${BASH_REMATCH[1]}" - PKG_VERSION="${BASH_REMATCH[2]}" + elif echo "$basename" | grep -qE '^([A-Za-z]+)([0-9]+)\.(.+)$'; then + PKG_NAME=$(echo "$basename" | sed -E 's/^([A-Za-z]+)([0-9]+)\.(.+)$/\1/') + PKG_VERSION=$(echo "$basename" | sed -E 's/^([A-Za-z]+)([0-9]+)\.(.+)$/\2/') # Pattern 3: NAMEVERSION (e.g., HAMN110) - elif [[ "$basename" =~ ^([A-Za-z]+)([0-9]+)$ ]]; then - PKG_NAME="${BASH_REMATCH[1]}" - PKG_VERSION="${BASH_REMATCH[2]}" + elif echo "$basename" | grep -qE '^([A-Za-z]+)([0-9]+)$'; then + PKG_NAME=$(echo "$basename" | sed -E 's/^([A-Za-z]+)([0-9]+)$/\1/') + PKG_VERSION=$(echo "$basename" | sed -E 's/^([A-Za-z]+)([0-9]+)$/\2/') else PKG_NAME="$basename" PKG_VERSION="1.0" @@ -128,18 +112,20 @@ parse_filename() { # Function to analyze pax contents and categorize files analyze_pax_contents() { - local pax_file="$1" - local temp_dir=$(mktempdir "pax2rpm") + pax_file="$1" + temp_dir=$(mktempdir "pax2rpm") printInfo "# Analyzing pax file structure..." # Extract pax listing (limit to first 5000 entries for performance/accuracy balance) - local pax_listing - if [[ "$pax_file" == *.pax.Z ]]; then - pax_listing=$(pax -vzf "$pax_file" 2>&1 | head -5000) - else - pax_listing=$(pax -vf "$pax_file" 2>&1 | head -5000) - fi + case "$pax_file" in + *.pax.Z) + pax_listing=$(pax -vzf "$pax_file" 2>&1 | head -5000) + ;; + *) + pax_listing=$(pax -vf "$pax_file" 2>&1 | head -5000) + ;; + esac # Check if pax listing failed or is empty if [ -z "$pax_listing" ]; then @@ -147,100 +133,74 @@ analyze_pax_contents() { return 0 fi - # Initialize arrays for different file types - declare -g -A FILE_CATEGORIES - FILE_CATEGORIES[bins]="" - FILE_CATEGORIES[libs]="" - FILE_CATEGORIES[includes]="" - FILE_CATEGORIES[configs]="" - FILE_CATEGORIES[docs]="" - FILE_CATEGORIES[data]="" - FILE_CATEGORIES[scripts]="" - FILE_CATEGORIES[other]="" - - declare -g -A DIR_STRUCTURE - DIR_STRUCTURE[has_bin]=false - DIR_STRUCTURE[has_lib]=false - DIR_STRUCTURE[has_include]=false - DIR_STRUCTURE[has_etc]=false - DIR_STRUCTURE[has_share]=false - DIR_STRUCTURE[has_doc]=false - DIR_STRUCTURE[has_man]=false + # Initialize variables for different file types (removed unused FILE_CATEGORIES) + has_bin=false + has_lib=false + has_include=false + has_etc=false + has_share=false + has_doc=false + has_man=false + has_scripts=false + is_os_layout=false # Analyze each file - while IFS= read -r line; do + echo "$pax_listing" | while IFS= read -r line; do # Skip empty lines and headers - [[ -z "$line" ]] && continue + [ -z "$line" ] && continue # Extract filename (last field in pax -v output) - local filename=$(echo "$line" | awk '{print $NF}') - [[ -z "$filename" ]] && continue + filename=$(echo "$line" | awk '{print $NF}') + [ -z "$filename" ] && continue # Detect directory structure (handles top-level dir if present) # Matches: bin/, ./bin/, package-ver/bin/, ./package-ver/bin/ # Does NOT match: usr/lpp/IBM/bin/ (too deep) - if [[ "$filename" =~ ^(\./)?([^/]+/)?bin/ ]]; then - DIR_STRUCTURE[has_bin]=true - elif [[ "$filename" =~ ^(\./)?([^/]+/)?lib/ ]]; then - DIR_STRUCTURE[has_lib]=true - elif [[ "$filename" =~ ^(\./)?([^/]+/)?include/ ]]; then - DIR_STRUCTURE[has_include]=true - elif [[ "$filename" =~ ^(\./)?([^/]+/)?etc/ ]]; then - DIR_STRUCTURE[has_etc]=true - elif [[ "$filename" =~ ^(\./)?([^/]+/)?share/ ]]; then - DIR_STRUCTURE[has_share]=true - elif [[ "$filename" =~ ^(\./)?([^/]+/)?(doc|docs)/ ]]; then - DIR_STRUCTURE[has_doc]=true - elif [[ "$filename" =~ ^(\./)?([^/]+/)?man/ ]]; then - DIR_STRUCTURE[has_man]=true - elif [[ "$filename" =~ ^(\./)?([^/]+/)?scripts/ ]]; then - DIR_STRUCTURE[has_scripts]=true - elif [[ "$filename" =~ ^(\./)?usr/lpp/ ]]; then - DIR_STRUCTURE[is_os_layout]=true - fi - - # Categorize by file type - if [[ "$filename" =~ \.(so|so\.[0-9]+|a|dylib)$ ]]; then - FILE_CATEGORIES[libs]+="$filename"$'\n' - elif [[ "$filename" =~ \.(h|hpp|hxx)$ ]]; then - FILE_CATEGORIES[includes]+="$filename"$'\n' - elif [[ "$filename" =~ \.(conf|cfg|ini|yaml|yml|json|xml|properties)$ ]]; then - FILE_CATEGORIES[configs]+="$filename"$'\n' - elif [[ "$filename" =~ \.(md|txt|pdf|html|htm|README|LICENSE|COPYING|CHANGELOG|AUTHORS)$ ]] || [[ "$filename" =~ (README|LICENSE|COPYING|CHANGELOG|AUTHORS|INSTALL|NEWS)$ ]]; then - FILE_CATEGORIES[docs]+="$filename"$'\n' - elif [[ "$filename" =~ \.(sh|bash|ksh|pl|py|rb)$ ]] && [[ "$line" =~ ^-r.x ]]; then - FILE_CATEGORIES[scripts]+="$filename"$'\n' - elif [[ "$line" =~ ^-r.x ]] && [[ ! "$filename" =~ \. ]]; then - # Executable without extension (likely binary) - FILE_CATEGORIES[bins]+="$filename"$'\n' - elif [[ "$filename" =~ \.(dat|data|db|sqlite)$ ]]; then - FILE_CATEGORIES[data]+="$filename"$'\n' - else - FILE_CATEGORIES[other]+="$filename"$'\n' - fi - done <<< "$pax_listing" + case "$filename" in + bin/* | */bin/*) + has_bin=true ;; + lib/* | */lib/*) + has_lib=true ;; + include/* | */include/*) + has_include=true ;; + etc/* | */etc/*) + has_etc=true ;; + share/* | */share/*) + has_share=true ;; + doc/* | docs/* | */doc/* | */docs/*) + has_doc=true ;; + man/* | */man/*) + has_man=true ;; + scripts/* | */scripts/*) + has_scripts=true ;; + usr/lpp/* | */usr/lpp/*) + is_os_layout=true ;; + esac + done rm -rf "$temp_dir" } # Function to list contents of pax file list_pax_contents() { - local pax_file="$1" - + pax_file="$1" + printHeader "# Pax file contents (first 100 entries):" - - if [[ "$pax_file" == *.pax.Z ]]; then - pax -vzf "$pax_file" 2>&1 | head -100 - else - pax -vf "$pax_file" 2>&1 | head -100 - fi -} + case "$pax_file" in + *.pax.Z) + pax -vzf "$pax_file" 2>&1 | head -100 + ;; + *) + pax -vf "$pax_file" 2>&1 | head -100 + ;; + esac +} # Function to generate install commands based on analyzed contents # Function to generate install commands based on analyzed contents # Function to generate install commands based on analyzed contents generate_install_commands() { - local pax_file="$1" +pax_file="$1" cat << 'EOF' # Install files based on detected structure @@ -311,9 +271,9 @@ EOF # Function to generate spec file generate_spec() { - local output_file="$1" - local source_file="$2" - local date=$(date "+%a %b %d %Y") +output_file="$1" +source_file="$2" +date=$(date "+%a %b %d %Y") # Analyze the pax contents first (populate global DIR_STRUCTURE) analyze_pax_contents "$source_file" @@ -351,17 +311,20 @@ cd %{name}-%{version} EOF - if [[ "$source_file" == *.pax.Z ]]; then - cat >> "$output_file" << EOF + case "$source_file" in + *.pax.Z) + cat >> "$output_file" << EOF # Extract compressed pax archive pax -rzf %{_sourcedir}/$(basename "$source_file") EOF - else - cat >> "$output_file" << EOF + ;; + *) + cat >> "$output_file" << EOF # Extract pax archive pax -rf %{_sourcedir}/$(basename "$source_file") EOF - fi + ;; + esac cat >> "$output_file" << EOF @@ -388,21 +351,21 @@ EOF echo "RPM spec file generated: $output_file" echo "" echo "Detected structure:" - [[ "${DIR_STRUCTURE[has_bin]}" == "true" ]] && echo " ✓ Binaries in bin/" - [[ "${DIR_STRUCTURE[has_lib]}" == "true" ]] && echo " ✓ Libraries in lib/" - [[ "${DIR_STRUCTURE[has_include]}" == "true" ]] && echo " ✓ Headers in include/" - [[ "${DIR_STRUCTURE[has_etc]}" == "true" ]] && echo " ✓ Configuration files in etc/" - [[ "${DIR_STRUCTURE[has_share]}" == "true" ]] && echo " ✓ Data files in share/" - [[ "${DIR_STRUCTURE[has_doc]}" == "true" ]] && echo " ✓ Documentation in doc/" - [[ "${DIR_STRUCTURE[has_man]}" == "true" ]] && echo " ✓ Man pages in man/" - [[ "${DIR_STRUCTURE[has_scripts]}" == "true" ]] && echo " ✓ Scripts in scripts/" + [ "$has_bin" = "true" ] && echo " ✓ Binaries in bin/" + [ "$has_lib" = "true" ] && echo " ✓ Libraries in lib/" + [ "$has_include" = "true" ] && echo " ✓ Headers in include/" + [ "$has_etc" = "true" ] && echo " ✓ Configuration files in etc/" + [ "$has_share" = "true" ] && echo " ✓ Data files in share/" + [ "$has_doc" = "true" ] && echo " ✓ Documentation in doc/" + [ "$has_man" = "true" ] && echo " ✓ Man pages in man/" + [ "$has_scripts" = "true" ] && echo " ✓ Scripts in scripts/" return 0 } # Function to setup rpmbuild directories setup_rpmbuild() { - local buildroot="$1" +buildroot="$1" echo "Setting up RPM build directories in: $buildroot" @@ -420,7 +383,7 @@ EOF fi else # Check if _topdir is already defined and different - local existing_topdir=$(grep "%_topdir" "$HOME/.rpmmacros" | awk '{print $2}') +existing_topdir=$(grep "%_topdir" "$HOME/.rpmmacros" | awk '{print $2}') if [ "$existing_topdir" != "$buildroot" ]; then echo "Warning: ~/.rpmmacros exists and has _topdir set to '$existing_topdir'." echo "The script will use '$buildroot' by overriding it with --define \"_topdir $buildroot\"." @@ -430,8 +393,8 @@ EOF # Function to check for required tools check_required_tools() { - local missing_tools=() - local pax_file="$1" +missing_tools=() +pax_file="$1" # Check for pax if ! command -v pax &> /dev/null; then @@ -478,7 +441,7 @@ check_required_tools() { fi # Optional tools check (informational only) - local optional_missing=() +optional_missing=() if [ "$VALIDATE_SPEC" = true ] && ! command -v rpmlint &> /dev/null; then optional_missing+=("rpmlint") @@ -505,8 +468,8 @@ check_required_tools() { # Function to validate spec file syntax validate_spec_syntax() { - local spec_file="$1" - local buildroot="$2" +spec_file="$1" +buildroot="$2" echo "" echo "==========================================" @@ -523,7 +486,7 @@ validate_spec_syntax() { # Try to use rpmbuild -bp (prep stage) to validate syntax # This will check syntax without actually building # Use --nodeps to check syntax/prep logic without requiring local RPM DB to have all deps - local temp_output=$(mktemp) +temp_output=$(mktemp) if rpmbuild --define "_topdir $buildroot" -bp --nodeps "$spec_file" &> "$temp_output"; then echo "✓ Spec file syntax is valid" rm -f "$temp_output" @@ -545,7 +508,7 @@ validate_spec_syntax() { # Function to run rpmlint on spec file run_rpmlint() { - local spec_file="$1" +spec_file="$1" echo "" echo "==========================================" @@ -561,16 +524,16 @@ run_rpmlint() { fi # Run rpmlint - local rpmlint_output +rpmlint_output rpmlint_output=$(rpmlint "$spec_file" 2>&1 || true) - local rpmlint_exit=$? +rpmlint_exit=$? echo "$rpmlint_output" echo "" # Count errors and warnings - local errors=$(echo "$rpmlint_output" | grep -c "E:" || true) - local warnings=$(echo "$rpmlint_output" | grep -c "W:" || true) +errors=$(echo "$rpmlint_output" | grep -c "E:" || true) +warnings=$(echo "$rpmlint_output" | grep -c "W:" || true) if [ $errors -gt 0 ]; then echo "⚠ Found $errors error(s) and $warnings warning(s)" @@ -588,10 +551,10 @@ run_rpmlint() { # Function to validate generated spec file validate_spec_file() { - local spec_file="$1" - local pax_file="$2" - local buildroot="$3" - local validation_failed=false +spec_file="$1" +pax_file="$2" +buildroot="$3" +validation_failed=false echo "" echo "==========================================" @@ -602,7 +565,7 @@ validate_spec_file() { setup_rpmbuild "$buildroot" # Copy pax file to SOURCES if not already there - local source_name=$(basename "$pax_file") +source_name=$(basename "$pax_file") if [ ! -f "$buildroot/SOURCES/$source_name" ] || [ "$pax_file" -nt "$buildroot/SOURCES/$source_name" ]; then echo "Copying source file to $buildroot/SOURCES/$source_name for validation" cp "$pax_file" "$buildroot/SOURCES/" @@ -640,7 +603,7 @@ validate_spec_file() { # Function to perform dry run perform_dry_run() { - local pax_file="$1" +pax_file="$1" echo "" echo "==========================================" @@ -675,9 +638,9 @@ perform_dry_run() { # Function to build RPM build_rpm() { - local spec_file="$1" - local pax_file="$2" - local buildroot="$3" +spec_file="$1" +pax_file="$2" +buildroot="$3" echo "" echo "==========================================" @@ -689,7 +652,7 @@ build_rpm() { setup_rpmbuild "$buildroot" # Copy pax file to SOURCES - local source_name=$(basename "$pax_file") +source_name=$(basename "$pax_file") echo "Copying source file to $buildroot/SOURCES/$source_name" cp "$pax_file" "$buildroot/SOURCES/" @@ -743,11 +706,11 @@ main() { usage fi - if [ "$1" == "--help" ]; then + if [ "$1" = "--help" ]; then usage 0 fi - if [ "$1" == "--version" ]; then + if [ "$1" = "--version" ]; then if [ -x "${MYDIR}/zopen-version" ]; then "${MYDIR}/zopen-version" "${ME}" else @@ -777,16 +740,20 @@ main() { shift 2 ;; --version) - if [ -z "$2" ] || [[ "$2" == --* ]]; then - if [ -x "${MYDIR}/zopen-version" ]; then - "${MYDIR}/zopen-version" "${ME}" - else - echo "${ME} version $(cat "${INCDIR}/zopen_version" 2>/dev/null || echo "unknown")" - fi - exit 0 - fi - PKG_VERSION="$2" - shift 2 + case "$2" in + "" | --*) + if [ -x "${MYDIR}/zopen-version" ]; then + "${MYDIR}/zopen-version" "${ME}" + else + echo "${ME} version $(cat "${INCDIR}/zopen_version" 2>/dev/null || echo "unknown")" + fi + exit 0 + ;; + *) + PKG_VERSION="$2" + shift 2 + ;; + esac ;; --pkg-version) [ -n "$2" ] || { echo "Error: --pkg-version requires a value" >&2; usage; } From 2fa9f22b14954ad021acb86bf3ef8323db4c4ec9 Mon Sep 17 00:00:00 2001 From: TejaswiniIBM Date: Mon, 6 Apr 2026 10:28:30 -0400 Subject: [PATCH 11/31] Fixed the build errors Signed-off-by: TejaswiniIBM --- bin/zopen-pax2rpm | 90 +++++++++++++++++++++++------------------------ 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/bin/zopen-pax2rpm b/bin/zopen-pax2rpm index c3aeb6b45..fad80a50b 100755 --- a/bin/zopen-pax2rpm +++ b/bin/zopen-pax2rpm @@ -369,7 +369,7 @@ buildroot="$1" echo "Setting up RPM build directories in: $buildroot" - mkdir -p "$buildroot"/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS} + mkdir -p "$buildroot/BUILD" "$buildroot/BUILDROOT" "$buildroot/RPMS" "$buildroot/SOURCES" "$buildroot/SPECS" "$buildroot/SRPMS" if [ ! -f "$HOME/.rpmmacros" ]; then if touch "$HOME/.rpmmacros" 2>/dev/null; then @@ -393,12 +393,12 @@ existing_topdir=$(grep "%_topdir" "$HOME/.rpmmacros" | awk '{print $2}') # Function to check for required tools check_required_tools() { -missing_tools=() -pax_file="$1" + missing_tools="" + pax_file="$1" # Check for pax - if ! command -v pax &> /dev/null; then - missing_tools+=("pax") + if ! command -v pax > /dev/null 2>&1; then + missing_tools="${missing_tools} pax" fi # Check for rpmbuild if building or validating @@ -406,18 +406,18 @@ pax_file="$1" if [ "$VERBOSE" = true ]; then echo "DEBUG: Current PATH is $PATH" >&2 fi - if ! command -v rpmbuild &> /dev/null; then - missing_tools+=("rpmbuild") + if ! command -v rpmbuild > /dev/null 2>&1; then + missing_tools="${missing_tools} rpmbuild" fi fi - if [ ${#missing_tools[@]} -gt 0 ]; then + if [ -n "$missing_tools" ]; then echo "" >&2 printError "Missing Required Tools" echo "The following tools are required but not found:" >&2 echo "" >&2 - for tool in "${missing_tools[@]}"; do + for tool in $missing_tools; do echo " ✗ $tool" >&2 case "$tool" in @@ -441,16 +441,16 @@ pax_file="$1" fi # Optional tools check (informational only) -optional_missing=() + optional_missing="" - if [ "$VALIDATE_SPEC" = true ] && ! command -v rpmlint &> /dev/null; then - optional_missing+=("rpmlint") + if [ "$VALIDATE_SPEC" = true ] && ! command -v rpmlint > /dev/null 2>&1; then + optional_missing="${optional_missing} rpmlint" fi - if [ ${#optional_missing[@]} -gt 0 ]; then + if [ -n "$optional_missing" ]; then echo "" >&2 echo "Note: Optional tools not found (validation will be limited):" >&2 - for tool in "${optional_missing[@]}"; do + for tool in $optional_missing; do echo " - $tool (for enhanced spec file quality checks)" >&2 case "$tool" in rpmlint) @@ -468,26 +468,26 @@ optional_missing=() # Function to validate spec file syntax validate_spec_syntax() { -spec_file="$1" -buildroot="$2" - + spec_file="$1" + buildroot="$2" + echo "" echo "==========================================" echo "Validating spec file syntax..." echo "==========================================" echo "" - + # Check if rpmbuild is available - if ! command -v rpmbuild &> /dev/null; then + if ! command -v rpmbuild > /dev/null 2>&1; then echo "Warning: rpmbuild not found, skipping syntax validation" >&2 return 0 fi - + # Try to use rpmbuild -bp (prep stage) to validate syntax # This will check syntax without actually building # Use --nodeps to check syntax/prep logic without requiring local RPM DB to have all deps -temp_output=$(mktemp) - if rpmbuild --define "_topdir $buildroot" -bp --nodeps "$spec_file" &> "$temp_output"; then + temp_output=$(mktemp) + if rpmbuild --define "_topdir $buildroot" -bp --nodeps "$spec_file" > "$temp_output" 2>&1; then echo "✓ Spec file syntax is valid" rm -f "$temp_output" return 0 @@ -508,33 +508,32 @@ temp_output=$(mktemp) # Function to run rpmlint on spec file run_rpmlint() { -spec_file="$1" - + spec_file="$1" + echo "" echo "==========================================" echo "Running rpmlint checks..." echo "==========================================" echo "" - + # Check if rpmlint is available - if ! command -v rpmlint &> /dev/null; then + if ! command -v rpmlint > /dev/null 2>&1; then echo "Note: rpmlint not found, skipping quality checks" echo "Install rpmlint for additional spec file validation" return 0 fi - + # Run rpmlint -rpmlint_output rpmlint_output=$(rpmlint "$spec_file" 2>&1 || true) -rpmlint_exit=$? + rpmlint_exit=$? echo "$rpmlint_output" echo "" - + # Count errors and warnings -errors=$(echo "$rpmlint_output" | grep -c "E:" || true) -warnings=$(echo "$rpmlint_output" | grep -c "W:" || true) - + errors=$(echo "$rpmlint_output" | grep -c "E:" || true) + warnings=$(echo "$rpmlint_output" | grep -c "W:" || true) + if [ $errors -gt 0 ]; then echo "⚠ Found $errors error(s) and $warnings warning(s)" echo "Please review and fix errors before building" @@ -706,18 +705,19 @@ main() { usage fi - if [ "$1" = "--help" ]; then - usage 0 - fi - - if [ "$1" = "--version" ]; then - if [ -x "${MYDIR}/zopen-version" ]; then - "${MYDIR}/zopen-version" "${ME}" - else - echo "${ME} version $(cat "${INCDIR}/zopen_version" 2>/dev/null || echo "unknown")" - fi - exit 0 - fi + case "$1" in + --help) + usage 0 + ;; + --version) + if [ -x "${MYDIR}/zopen-version" ]; then + "${MYDIR}/zopen-version" "${ME}" + else + echo "${ME} version $(cat "${INCDIR}/zopen_version" 2>/dev/null || echo "unknown")" + fi + exit 0 + ;; + esac PAX_FILE="$1" shift From 703956e53af66cf9a039f489ee5464fc3485158c Mon Sep 17 00:00:00 2001 From: TejaswiniIBM Date: Mon, 6 Apr 2026 10:57:03 -0400 Subject: [PATCH 12/31] Fixed the build issues Signed-off-by: TejaswiniIBM --- bin/zopen-pax2rpm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/zopen-pax2rpm b/bin/zopen-pax2rpm index fad80a50b..2668391fe 100755 --- a/bin/zopen-pax2rpm +++ b/bin/zopen-pax2rpm @@ -1,6 +1,6 @@ #!/bin/sh # -# RPM generation utility for zopen community - https://github.com/zopencommunity +# RPM generation from the PAX generated file utility for zopen # # From 5eb36a2c80cf7f8336f07116233a74ef474fccad Mon Sep 17 00:00:00 2001 From: TejaswiniIBM Date: Tue, 7 Apr 2026 00:02:14 -0400 Subject: [PATCH 13/31] Fixed the review comments Signed-off-by: TejaswiniIBM --- bin/zopen-pax2rpm | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bin/zopen-pax2rpm b/bin/zopen-pax2rpm index 2668391fe..03f35b0a8 100755 --- a/bin/zopen-pax2rpm +++ b/bin/zopen-pax2rpm @@ -145,7 +145,7 @@ analyze_pax_contents() { is_os_layout=false # Analyze each file - echo "$pax_listing" | while IFS= read -r line; do + while IFS= read -r line; do # Skip empty lines and headers [ -z "$line" ] && continue @@ -176,7 +176,9 @@ analyze_pax_contents() { usr/lpp/* | */usr/lpp/*) is_os_layout=true ;; esac - done + done << EOF +$pax_listing +EOF rm -rf "$temp_dir" } From c04a5146557d760d56ba9deb04d9abef4f87418b Mon Sep 17 00:00:00 2001 From: TejaswiniIBM Date: Tue, 7 Apr 2026 00:09:59 -0400 Subject: [PATCH 14/31] Updated the build command to test rpm Signed-off-by: TejaswiniIBM --- cicd/build.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cicd/build.groovy b/cicd/build.groovy index 75cfdcdb3..0ae38d20b 100755 --- a/cicd/build.groovy +++ b/cicd/build.groovy @@ -55,7 +55,7 @@ fi git clone -b "${PORT_BRANCH}" "${PORT_GITHUB_REPO}" ${PORT_NAME} && cd ${PORT_NAME} # Always run tests and update dependencies and generate pax file -zopen build -v -b release -u -gp -sp --no-set-active $extraOptions +zopen build -v -b release -u -gr -sp --no-set-active $extraOptions # Clean the cache after build is complete zopen clean -c -v From eacfd784372ceacd608e69f4650942fe8e920583 Mon Sep 17 00:00:00 2001 From: TejaswiniIBM Date: Tue, 7 Apr 2026 04:32:05 -0400 Subject: [PATCH 15/31] Changed the portbuild to portbuild test for testing the command Signed-off-by: TejaswiniIBM --- bin/zopen-pax2rpm | 4 ++-- cicd/pipeline.jenkins | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bin/zopen-pax2rpm b/bin/zopen-pax2rpm index 03f35b0a8..2324880f8 100755 --- a/bin/zopen-pax2rpm +++ b/bin/zopen-pax2rpm @@ -385,8 +385,8 @@ EOF fi else # Check if _topdir is already defined and different -existing_topdir=$(grep "%_topdir" "$HOME/.rpmmacros" | awk '{print $2}') - if [ "$existing_topdir" != "$buildroot" ]; then + existing_topdir=$(grep "%_topdir" "$HOME/.rpmmacros" | awk '{print $2}') + if [ -n "$existing_topdir" ] && [ "$existing_topdir" != "$buildroot" ]; then echo "Warning: ~/.rpmmacros exists and has _topdir set to '$existing_topdir'." echo "The script will use '$buildroot' by overriding it with --define \"_topdir $buildroot\"." fi diff --git a/cicd/pipeline.jenkins b/cicd/pipeline.jenkins index 9dbe318bf..7c0ec0f6c 100644 --- a/cicd/pipeline.jenkins +++ b/cicd/pipeline.jenkins @@ -27,7 +27,7 @@ node('linux') { stage('Build and Test') { // Build Job : https://cicd.zopen.community/view/Framework/job/Port-Build/ - buildResult = build job: 'Port-Build', propagate: false, parameters: [string(name: 'PORT_GITHUB_REPO', value: "${repo}"), + buildResult = build job: 'Port-Build-Test', propagate: false, parameters: [string(name: 'PORT_GITHUB_REPO', value: "${repo}"), string(name: 'PORT_BRANCH', value: "${branch}"), string(name: 'node', value: "${node_label}"), booleanParam(name: 'FORCE_CLANG', value: params.FORCE_CLANG), @@ -39,7 +39,7 @@ node('linux') } copyArtifacts filter: '**/test.status', fingerprintArtifacts: true, - projectName: 'Port-Build', + projectName: 'Port-Build-Test', selector: specific(buildResult.number.toString()), optional: true def testStatusFile = sh(script: 'find . -name test.status | head -n 1', returnStdout: true).trim() From eb996718b227ab1941e2030973a6db797545b98a Mon Sep 17 00:00:00 2001 From: TejaswiniIBM Date: Wed, 8 Apr 2026 05:35:46 -0400 Subject: [PATCH 16/31] Fixed the test build error No compatible architectures found for build Signed-off-by: TejaswiniIBM --- bin/zopen-pax2rpm | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/bin/zopen-pax2rpm b/bin/zopen-pax2rpm index 2324880f8..fc020352f 100755 --- a/bin/zopen-pax2rpm +++ b/bin/zopen-pax2rpm @@ -22,7 +22,15 @@ setupMyself # Default values RELEASE="1" LICENSE="Proprietary" -BUILD_ARCH=$(uname -m 2>/dev/null || echo "s390x") +BUILD_ARCH=$(rpm --eval "%{_target_cpu}" 2>/dev/null || uname -m 2>/dev/null || echo "s390x") + +# On z/OS, uname -m returns numeric machine types (e.g., 8561, 3931). +# RPM expects 's390x' for the build architecture. +if [ "$(uname -s)" = "OS/390" ]; then + case "${BUILD_ARCH}" in + [0-9]*) BUILD_ARCH="s390x" ;; + esac +fi # Get current user safely for packager info CURRENT_USER="${USER:-$(whoami 2>/dev/null || echo "zopen-community")}" From 36ee648b93df00f99b6afdadf83000a5561e87ff Mon Sep 17 00:00:00 2001 From: TejaswiniIBM Date: Wed, 8 Apr 2026 06:11:25 -0400 Subject: [PATCH 17/31] Fixed the test build error - RPM build errors: Signed-off-by: TejaswiniIBM --- bin/zopen-pax2rpm | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/bin/zopen-pax2rpm b/bin/zopen-pax2rpm index fc020352f..d4a016057 100755 --- a/bin/zopen-pax2rpm +++ b/bin/zopen-pax2rpm @@ -309,6 +309,17 @@ Source0: $(basename "$source_file") BuildArch: ${BUILD_ARCH} ${REQUIRES:+Requires: $REQUIRES} +EOF + + if [ "$(uname -s)" = "OS/390" ]; then + cat >> "$output_file" << EOF +# On z/OS, disable broken post-install processing (stripping, etc.) +%define __os_install_post %{nil} + +EOF + fi + + cat >> "$output_file" << EOF %description ${DESCRIPTION} From e7315bb9d6fe7c08e45cd96995eaf13d1b19fdd6 Mon Sep 17 00:00:00 2001 From: TejaswiniIBM Date: Wed, 8 Apr 2026 07:33:05 -0400 Subject: [PATCH 18/31] Reverted the Port-Build-Test to Port-Build Signed-off-by: TejaswiniIBM --- cicd/pipeline.jenkins | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cicd/pipeline.jenkins b/cicd/pipeline.jenkins index 7c0ec0f6c..9dbe318bf 100644 --- a/cicd/pipeline.jenkins +++ b/cicd/pipeline.jenkins @@ -27,7 +27,7 @@ node('linux') { stage('Build and Test') { // Build Job : https://cicd.zopen.community/view/Framework/job/Port-Build/ - buildResult = build job: 'Port-Build-Test', propagate: false, parameters: [string(name: 'PORT_GITHUB_REPO', value: "${repo}"), + buildResult = build job: 'Port-Build', propagate: false, parameters: [string(name: 'PORT_GITHUB_REPO', value: "${repo}"), string(name: 'PORT_BRANCH', value: "${branch}"), string(name: 'node', value: "${node_label}"), booleanParam(name: 'FORCE_CLANG', value: params.FORCE_CLANG), @@ -39,7 +39,7 @@ node('linux') } copyArtifacts filter: '**/test.status', fingerprintArtifacts: true, - projectName: 'Port-Build-Test', + projectName: 'Port-Build', selector: specific(buildResult.number.toString()), optional: true def testStatusFile = sh(script: 'find . -name test.status | head -n 1', returnStdout: true).trim() From a5b764f6d3af1fe62b83b2364ef363c50bfa1c2f Mon Sep 17 00:00:00 2001 From: TejaswiniIBM Date: Mon, 20 Apr 2026 10:06:45 +0530 Subject: [PATCH 19/31] Update publish.groovy Added command to push the RPM to pulp repo Signed-off-by: TejaswiniIBM --- cicd/publish.groovy | 1 + 1 file changed, 1 insertion(+) diff --git a/cicd/publish.groovy b/cicd/publish.groovy index 9343a16e6..73811baa6 100755 --- a/cicd/publish.groovy +++ b/cicd/publish.groovy @@ -223,6 +223,7 @@ if [ $NUM_RPMS -gt 0 ]; then RPM_BASENAME=$(basename "${RPM}") echo "Uploading ${RPM_BASENAME}..." github-release -v upload --user ${GITHUB_ORGANIZATION} --repo ${GITHUB_REPO} --tag "${TAG}" --name "${RPM_BASENAME}" --file "${RPM}" + pulp artifact upload --file "${RPM}" if [ $? -eq 0 ]; then echo "RPM Artifact ${RPM_BASENAME} uploaded successfully!" else From b8fe3d7118359b81af07c43c04c159b12d188f91 Mon Sep 17 00:00:00 2001 From: TejaswiniIBM Date: Tue, 28 Apr 2026 18:38:18 +0530 Subject: [PATCH 20/31] Update Jenkins pipeline to use test jobs Signed-off-by: TejaswiniIBM --- cicd/pipeline.jenkins | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cicd/pipeline.jenkins b/cicd/pipeline.jenkins index 9dbe318bf..3e6af97e0 100644 --- a/cicd/pipeline.jenkins +++ b/cicd/pipeline.jenkins @@ -27,7 +27,7 @@ node('linux') { stage('Build and Test') { // Build Job : https://cicd.zopen.community/view/Framework/job/Port-Build/ - buildResult = build job: 'Port-Build', propagate: false, parameters: [string(name: 'PORT_GITHUB_REPO', value: "${repo}"), + buildResult = build job: 'Port-Build-Test', propagate: false, parameters: [string(name: 'PORT_GITHUB_REPO', value: "${repo}"), string(name: 'PORT_BRANCH', value: "${branch}"), string(name: 'node', value: "${node_label}"), booleanParam(name: 'FORCE_CLANG', value: params.FORCE_CLANG), @@ -52,7 +52,7 @@ node('linux') stage('Promote') { if(!params.NO_PROMOTE) { // Publish Job : https://128.168.139.253:8443/view/Framework/job/Port-Publish/ - promoteResult = build job: 'Port-Publish', propagate: false, parameters: + promoteResult = build job: 'Port-Publish-Test', propagate: false, parameters: [string(name: 'BUILD_SELECTOR', value: " ${buildResult.number.toString()}"), string(name: 'PORT_GITHUB_REPO', value: "${repo}"), string(name: 'PORT_DESCRIPTION', value: "${description}"), string(name: 'BUILD_LINE', value: "${build_line}") From da8ececbdc584c0c02182dee3e039df8009fa52c Mon Sep 17 00:00:00 2001 From: TejaswiniIBM Date: Tue, 28 Apr 2026 18:49:11 +0530 Subject: [PATCH 21/31] Update project name for artifact copying in Jenkins Signed-off-by: TejaswiniIBM --- cicd/pipeline.jenkins | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cicd/pipeline.jenkins b/cicd/pipeline.jenkins index 3e6af97e0..d79dda45a 100644 --- a/cicd/pipeline.jenkins +++ b/cicd/pipeline.jenkins @@ -39,7 +39,7 @@ node('linux') } copyArtifacts filter: '**/test.status', fingerprintArtifacts: true, - projectName: 'Port-Build', + projectName: 'Port-Build-Test', selector: specific(buildResult.number.toString()), optional: true def testStatusFile = sh(script: 'find . -name test.status | head -n 1', returnStdout: true).trim() From b4f4673c34dcd58175f45377096de9230e22dd39 Mon Sep 17 00:00:00 2001 From: TejaswiniIBM Date: Mon, 4 May 2026 10:36:55 -0400 Subject: [PATCH 22/31] Refactor the code to use pulp command to upload the rpm Signed-off-by: TejaswiniIBM --- bin/zopen-publish | 73 +++++++++++++++++++++++++++++++++++++++++++-- cicd/publish.groovy | 16 +++++++--- 2 files changed, 83 insertions(+), 6 deletions(-) diff --git a/bin/zopen-publish b/bin/zopen-publish index f38bbe56e..9108b1bd3 100755 --- a/bin/zopen-publish +++ b/bin/zopen-publish @@ -39,14 +39,16 @@ printSyntax() echo " GitHub Personal Access Token (required, or set GITHUB_TOKEN env var)" echo " -o, --github-org ORG GitHub Organization (default: zopencommunity)" echo " -l, --latest Mark release as 'Latest' (not pre-release)." + echo " --pulp Push RPM artifacts to Pulp repository." + echo " --pulp-repo REPO Pulp repository name (default: zopen-stable or zopen-dev)" echo " --version print version" echo "" echo "Environment Variables:" echo " GITHUB_TOKEN GitHub Personal Access Token (alternative to --github-token)" echo "" echo "Example:" - echo " zopen-publish -f -p install/mypackage.zos.pax.Z -m metadata.json -g DEV_mypackage_12345 -t " - echo " zopen-publish -v -f -p install/mypackage.zos.pax.Z -m metadata.json -r https://github.com/zopencommunity/override-repo.git -d \"My custom release description\" -b DEV -g REL-1.0.1 -t -l" + echo " zopen-publish -f -p install/mypackage.zos.pax.Z -m metadata.json -g DEV_mypackage_12345 -t --pulp" + echo " zopen-publish -v -f -p install/mypackage.zos.pax.Z -m metadata.json -r https://github.com/zopencommunity/override-repo.git -d \"My custom release description\" -b DEV -g REL-1.0.1 -t -l --pulp" } checkDependencies() { @@ -58,6 +60,12 @@ checkDependencies() { printError "Error: jq command not found. Please install it." return 1 fi + if [ "$PUSH_TO_PULP" = true ]; then + if ! command -v pulp > /dev/null 2>&1; then + printError "Error: pulp command not found. Please install it to use --pulp option." + return 1 + fi + fi return 0 } @@ -122,6 +130,13 @@ processOptions() { RELEASE_TAG_OPT="$2" shift ;; + "--pulp") + PUSH_TO_PULP=true + ;; + "--pulp-repo") + CUSTOM_PULP_REPO="$2" + shift + ;; "--version") zopen-version "${ME}" exit 0 @@ -342,6 +357,36 @@ deleteGitHubReleaseByTag() { return 0 } +uploadToPulp() { + rpm_file="$1" + build_line="$2" + + # Default to repo.zopen.community if not specified in environment + if [ -z "$PULP_BASE_ADDR" ]; then + export PULP_BASE_ADDR="https://repo.zopen.community" + printVerbose "PULP_BASE_ADDR not set; defaulting to $PULP_BASE_ADDR" + fi + + if [ -n "$CUSTOM_PULP_REPO" ]; then + PULP_REPO="$CUSTOM_PULP_REPO" + elif [ "$build_line" = "STABLE" ]; then + PULP_REPO="zopen-stable" + else + PULP_REPO="zopen-dev" + fi + + printInfo "- Pushing $rpm_file to Pulp repository: $PULP_REPO..." + + # Perform the upload and automatic repository version update + if ! pulp rpm repository upload --name "$PULP_REPO" --file "$rpm_file"; then + printError "Failed to push RPM to Pulp repository: $PULP_REPO" + return 1 + fi + + printVerbose "Successfully pushed $rpm_file to Pulp repository: $PULP_REPO" + return 0 +} + publishRelease() { PAX_BASENAME=$(basename "${PAX_FILE}") @@ -443,6 +488,28 @@ publishRelease() { return 1 # metadata.json upload failed fi + if [ "$PUSH_TO_PULP" = true ]; then + # Look for RPMs in rpmbuild/RPMS first (typical build location) + RPM_FILES=($(find rpmbuild/RPMS -type f -name "*.rpm" 2>/dev/null)) + + # If not found there, look in the same directory as the PAX file + if [ ${#RPM_FILES[@]} -eq 0 ]; then + PAX_DIR=$(dirname "$PAX_FILE") + RPM_FILES=($(find "$PAX_DIR" -type f -name "*.rpm" 2>/dev/null)) + fi + + if [ ${#RPM_FILES[@]} -gt 0 ]; then + printInfo "- Found ${#RPM_FILES[@]} RPM(s) to push to Pulp." + for RPM in "${RPM_FILES[@]}"; do + if ! uploadToPulp "$RPM" "$BUILD_LINE"; then + return 1 + fi + done + else + printWarning "Pulp push requested, but no RPM files were found." + fi + fi + echo "Release published successfully to https://github.com/${GITHUB_ORGANIZATION}/${GITHUB_REPO}/releases/tag/${TAG}" return 0 @@ -465,6 +532,8 @@ GITHUB_TOKEN_OPT="" MAKE_LATEST_RELEASE=false RELEASE_TAG_OPT="" FORCE_OVERWRITE=false +PUSH_TO_PULP=false +CUSTOM_PULP_REPO="" verbose=false # Initialize verbose flag diff --git a/cicd/publish.groovy b/cicd/publish.groovy index 73811baa6..b25aa26f2 100755 --- a/cicd/publish.groovy +++ b/cicd/publish.groovy @@ -218,16 +218,24 @@ else fi if [ $NUM_RPMS -gt 0 ]; then - echo "Uploading the RPM artifacts into github" + if [ "$BUILD_LINE" = "STABLE" ]; then + PULP_REPO="zopen-stable" + else + PULP_REPO="zopen-dev" + fi + + echo "Uploading the RPM artifacts into github and Pulp repository: ${PULP_REPO}" for RPM in "${RPM_FILES[@]}"; do RPM_BASENAME=$(basename "${RPM}") echo "Uploading ${RPM_BASENAME}..." github-release -v upload --user ${GITHUB_ORGANIZATION} --repo ${GITHUB_REPO} --tag "${TAG}" --name "${RPM_BASENAME}" --file "${RPM}" - pulp artifact upload --file "${RPM}" + + # Push to the specific Pulp repository + pulp rpm repository upload --name "${PULP_REPO}" --file "${RPM}" if [ $? -eq 0 ]; then - echo "RPM Artifact ${RPM_BASENAME} uploaded successfully!" + echo "RPM Artifact ${RPM_BASENAME} uploaded successfully to GitHub and Pulp!" else - echo "Failed to upload RPM artifact ${RPM_BASENAME}!" + echo "Failed to upload RPM artifact ${RPM_BASENAME} to Pulp!" exit 1 fi done From c69d7d79f96021ea33cf680bb4c148b7e4ffa6f2 Mon Sep 17 00:00:00 2001 From: TejaswiniIBM Date: Mon, 4 May 2026 20:30:36 +0530 Subject: [PATCH 23/31] Update Jenkins pipeline to use Port-Publish-Test job Signed-off-by: TejaswiniIBM --- cicd/pipeline.jenkins | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cicd/pipeline.jenkins b/cicd/pipeline.jenkins index d79dda45a..a5eef907f 100644 --- a/cicd/pipeline.jenkins +++ b/cicd/pipeline.jenkins @@ -27,7 +27,7 @@ node('linux') { stage('Build and Test') { // Build Job : https://cicd.zopen.community/view/Framework/job/Port-Build/ - buildResult = build job: 'Port-Build-Test', propagate: false, parameters: [string(name: 'PORT_GITHUB_REPO', value: "${repo}"), + buildResult = build job: 'Port-Publish-Test', propagate: false, parameters: [string(name: 'PORT_GITHUB_REPO', value: "${repo}"), string(name: 'PORT_BRANCH', value: "${branch}"), string(name: 'node', value: "${node_label}"), booleanParam(name: 'FORCE_CLANG', value: params.FORCE_CLANG), @@ -39,7 +39,7 @@ node('linux') } copyArtifacts filter: '**/test.status', fingerprintArtifacts: true, - projectName: 'Port-Build-Test', + projectName: 'Port-Publish-Test', selector: specific(buildResult.number.toString()), optional: true def testStatusFile = sh(script: 'find . -name test.status | head -n 1', returnStdout: true).trim() From ce87ca7a698ce811aa942ef0e7af5ab4ac5a5176 Mon Sep 17 00:00:00 2001 From: TejaswiniIBM Date: Mon, 4 May 2026 20:58:22 +0530 Subject: [PATCH 24/31] Change job name from Port-Publish-Test to Port-Build-Test Signed-off-by: TejaswiniIBM --- cicd/pipeline.jenkins | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cicd/pipeline.jenkins b/cicd/pipeline.jenkins index a5eef907f..d79dda45a 100644 --- a/cicd/pipeline.jenkins +++ b/cicd/pipeline.jenkins @@ -27,7 +27,7 @@ node('linux') { stage('Build and Test') { // Build Job : https://cicd.zopen.community/view/Framework/job/Port-Build/ - buildResult = build job: 'Port-Publish-Test', propagate: false, parameters: [string(name: 'PORT_GITHUB_REPO', value: "${repo}"), + buildResult = build job: 'Port-Build-Test', propagate: false, parameters: [string(name: 'PORT_GITHUB_REPO', value: "${repo}"), string(name: 'PORT_BRANCH', value: "${branch}"), string(name: 'node', value: "${node_label}"), booleanParam(name: 'FORCE_CLANG', value: params.FORCE_CLANG), @@ -39,7 +39,7 @@ node('linux') } copyArtifacts filter: '**/test.status', fingerprintArtifacts: true, - projectName: 'Port-Publish-Test', + projectName: 'Port-Build-Test', selector: specific(buildResult.number.toString()), optional: true def testStatusFile = sh(script: 'find . -name test.status | head -n 1', returnStdout: true).trim() From 22636bd730fb6d79123bd7af90d80ef5ff19757d Mon Sep 17 00:00:00 2001 From: TejaswiniIBM Date: Tue, 5 May 2026 10:49:00 +0530 Subject: [PATCH 25/31] Enhance Pulp configuration and error handling Add error handling for missing PULP environment variables and configure Pulp server. Signed-off-by: TejaswiniIBM --- cicd/publish.groovy | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/cicd/publish.groovy b/cicd/publish.groovy index b25aa26f2..04d2eca5f 100755 --- a/cicd/publish.groovy +++ b/cicd/publish.groovy @@ -19,6 +19,10 @@ RELEASE_PREFIX=${RELEASE_PREFIX%%.*} PORT_NAME=${RELEASE_PREFIX%%port} # Get the REPO name GITHUB_REPO=$RELEASE_PREFIX +: "${PULP_HOST:?Missing PULP_HOST}" +: "${PULP_USERNAME:?Missing PULP_USERNAME}" +: "${PULP_PASSWORD:?Missing PULP_PASSWORD}" + # PAX file should be a copied artifact PAX=`find . -type f -path "*install/*zos.pax.Z"` @@ -223,7 +227,15 @@ if [ $NUM_RPMS -gt 0 ]; then else PULP_REPO="zopen-dev" fi - + echo "Configuring Pulp server..." + +pulp config create \ + --base-url "$PULP_HOST" \ + --username "$PULP_USERNAME" \ + --password "$PULP_PASSWORD" \ + --overwrite +pulp status || exit 1 + echo "Uploading the RPM artifacts into github and Pulp repository: ${PULP_REPO}" for RPM in "${RPM_FILES[@]}"; do RPM_BASENAME=$(basename "${RPM}") From a48a13a005f8be952bc1c9b02d96a86e3401b469 Mon Sep 17 00:00:00 2001 From: TejaswiniIBM Date: Tue, 5 May 2026 10:53:50 +0530 Subject: [PATCH 26/31] Add PULP_HOST variable for package repository Signed-off-by: TejaswiniIBM --- cicd/publish.groovy | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cicd/publish.groovy b/cicd/publish.groovy index 04d2eca5f..19711267e 100755 --- a/cicd/publish.groovy +++ b/cicd/publish.groovy @@ -19,6 +19,8 @@ RELEASE_PREFIX=${RELEASE_PREFIX%%.*} PORT_NAME=${RELEASE_PREFIX%%port} # Get the REPO name GITHUB_REPO=$RELEASE_PREFIX + +PULP_HOST="https://repo.zopen.community/pulp/content/rpm/zopen/Packages/z/" : "${PULP_HOST:?Missing PULP_HOST}" : "${PULP_USERNAME:?Missing PULP_USERNAME}" : "${PULP_PASSWORD:?Missing PULP_PASSWORD}" From b58d0e1dbd1abc524d970a83223def67877fed5d Mon Sep 17 00:00:00 2001 From: TejaswiniIBM Date: Tue, 5 May 2026 11:01:41 +0530 Subject: [PATCH 27/31] Refactor publish.groovy for improved clarity and functionality Signed-off-by: TejaswiniIBM --- cicd/publish.groovy | 376 +++++++++++++++++--------------------------- 1 file changed, 141 insertions(+), 235 deletions(-) diff --git a/cicd/publish.groovy b/cicd/publish.groovy index 19711267e..34aeee1f9 100755 --- a/cicd/publish.groovy +++ b/cicd/publish.groovy @@ -1,288 +1,194 @@ #!/bin/bash -# Publish Job : https://cicd.zopen.community/view/Framework/job/Port-Publish/ -# This publish job will a pax.Z artifact from the Port-Build (https://cicd.zopen.community/view/Framework/job/Port-Build/) -# Inputs: -# - PORT_GITHUB_REPO : Github repoistory to publish the artifact to e.g: https://github.com/zopencommunity/xzport.git -# - PORT_DESCRIPTION : Description of the tool that is presented in the Github release page -# - BUILD_LINE: dev or stable -# zos.pax.Z artifact is copied as input -# requires a GITHUB_TOKEN environment variable (already configured in Jenkins) -# Output: -# - pax.Z artifact is published as a Jenkins artifact -# - package is copied to /jenkins/build -GITHUB_ORGANIZATION="zopencommunity" -RELEASE_NOTES_SCRIPT="tools/create_release_notes.sh" # Path to your release notes script +set -euo pipefail -RELEASE_PREFIX=$(basename "${PORT_GITHUB_REPO}") -# Used for Release/Tag name -RELEASE_PREFIX=${RELEASE_PREFIX%%.*} -PORT_NAME=${RELEASE_PREFIX%%port} -# Get the REPO name -GITHUB_REPO=$RELEASE_PREFIX +echo "=== STARTING PUBLISH JOB ===" -PULP_HOST="https://repo.zopen.community/pulp/content/rpm/zopen/Packages/z/" -: "${PULP_HOST:?Missing PULP_HOST}" +# --- REQUIRED ENV --- +: "${PORT_GITHUB_REPO:?Missing PORT_GITHUB_REPO}" +: "${PORT_DESCRIPTION:?Missing PORT_DESCRIPTION}" +: "${BUILD_NUMBER:?Missing BUILD_NUMBER}" +: "${GITHUB_TOKEN:?Missing GITHUB_TOKEN}" : "${PULP_USERNAME:?Missing PULP_USERNAME}" : "${PULP_PASSWORD:?Missing PULP_PASSWORD}" - - -# PAX file should be a copied artifact -PAX=`find . -type f -path "*install/*zos.pax.Z"` -if [ $(echo "$PAX" | grep -c /) -ne 1 ]; then - echo "Error: Expected exactly 1 PAX file, found: $PAX" - exit 1 -fi - -RPM_FILES=($(find rpmbuild/RPMS -type f -name "*.rpm" 2>/dev/null)) -NUM_RPMS=${#RPM_FILES[@]} -if [ $NUM_RPMS -gt 0 ]; then - echo "Found $NUM_RPMS RPM file(s): ${RPM_FILES[@]}" -else - echo "No RPM files found. Skipping RPM upload." -fi +# --- STATIC CONFIG --- +GITHUB_ORGANIZATION="zopencommunity" +RELEASE_NOTES_SCRIPT="tools/create_release_notes.sh" +PULP_HOST="https://repo.zopen.community" # ✅ FIXED -METADATA=`find . -type f -path "*install/metadata.json"` -if [ $(echo "$METADATA" | grep -c /) -ne 1 ]; then - echo "Error: Expected exactly 1 metadata.json file, found: $METADATA" - exit 1 -fi -BUILD_STATUS=`find . -name "test.status" | xargs cat` -DEPENDENCIES=`find . -name ".runtimedeps" | xargs cat` -BUILD_DEPENDENCIES=`find . -name ".builddeps" | xargs cat` -VERSION=`find . -name "*install/.version" | xargs cat` - -if [ ! -f "$PAX" ]; then - echo "Port pax file does not exist"; - exit 1; -fi +BUILD_LINE=${BUILD_LINE:-DEV} -if [ ! -f "$METADATA" ]; then - echo "Port metadata.json file does not exist"; - exit 1; -fi +# --- DERIVE VALUES --- +RELEASE_PREFIX=$(basename "${PORT_GITHUB_REPO}") +RELEASE_PREFIX=${RELEASE_PREFIX%%.*} +PORT_NAME=$(echo "$RELEASE_PREFIX" | sed 's/port$//') +GITHUB_REPO=$RELEASE_PREFIX -echo "PAX: $PAX" -echo "METADATA: $METADATA" +# --- FIND FILES --- +PAX=$(find . -type f -name "*zos.pax.Z" | head -n 1) +METADATA=$(find . -type f -name "metadata.json" | head -n 1) -if [ -z "$DEPENDENCIES" ]; then - DEPENDENCIES="No dependencies"; -fi +[ -f "$PAX" ] || { echo "ERROR: Missing PAX file"; exit 1; } +[ -f "$METADATA" ] || { echo "ERROR: Missing metadata.json"; exit 1; } -if [ -z "$BUILD_DEPENDENCIES" ]; then - BUILD_DEPENDENCIES="No dependencies"; -fi +# --- RPM FILES --- +RPM_FILES=() +while IFS= read -r -d '' f; do + RPM_FILES+=("$f") +done < <(find rpmbuild/RPMS -type f -name "*.rpm" -print0 2>/dev/null) -if [ ! -z "$VERSION" ]; then - VERSION="$VERSION "; -fi +NUM_RPMS=${#RPM_FILES[@]} -PAX_BASENAME=$(basename "${PAX}") -DIR_NAME=${PAX_BASENAME%%.pax.Z} -DIR_NAME=$(echo "$DIR_NAME" | sed -e "s/\.202[0-9]*_[0-9]*\.zos/.zos/g" -e "s/\.zos//g") -BUILD_ID=${BUILD_NUMBER} +# --- INFO --- +BUILD_STATUS=$(find . -name "test.status" -exec cat {} + 2>/dev/null || echo "Unknown") +DEPENDENCIES=$(find . -name ".runtimedeps" -exec cat {} + 2>/dev/null || echo "None") +VERSION=$(find . -name ".version" -exec cat {} + 2>/dev/null || echo "unknown") +unset http_proxy https_proxy -# Check for python dependencies -if pip3 show numpy &> /dev/null; then - echo "NumPy is already installed." -else - echo "NumPy is not installed. Installing now..." - pip3 install numpy -fi +PAX_BASENAME=$(basename "$PAX") +TAG="${BUILD_LINE}_${RELEASE_PREFIX}_${BUILD_NUMBER}" +NAME="${PORT_NAME} ${VERSION} (Build ${BUILD_NUMBER}) - (${BUILD_LINE})" -if pip3 show PyGithub &> /dev/null; then - echo "PyGithub is already installed." +# --- RELEASE NOTES --- +if [ -f "$RELEASE_NOTES_SCRIPT" ]; then + RELEASE_NOTES=$("$RELEASE_NOTES_SCRIPT" -m release -p "$GITHUB_REPO" -t markdown || echo "No notes") else - echo "PyGithub is not installed. Installing now..." - pip3 install PyGithub + RELEASE_NOTES="No release notes" fi -if pip3 show matplotlib &> /dev/null; then - echo "matplotlib is already installed." -else - echo "matplotlib is not installed. Installing now..." - pip3 install matplotlib -fi +echo "$RELEASE_NOTES" > CHANGELOG.md -# Needed for uploading releases -unset http_proxy -unset https_proxy +DESCRIPTION="${PORT_DESCRIPTION}
Status: ${BUILD_STATUS}
" +DESCRIPTION+="Dependencies: ${DEPENDENCIES}
" +DESCRIPTION+=$'\n\n'"$RELEASE_NOTES" -DESCRIPTION="${PORT_DESCRIPTION}" -DESCRIPTION="${DESCRIPTION}
Test Status: ${BUILD_STATUS}
" -DESCRIPTION="${DESCRIPTION}Runtime Dependencies: ${DEPENDENCIES}
" -DESCRIPTION="${DESCRIPTION}Build Dependencies: ${BUILD_DEPENDENCIES}
" +# --- TOOL CHECK --- +command -v github-release >/dev/null || { echo "ERROR: github-release not installed"; exit 1; } -if [ -z "$BUILD_LINE" ]; then - BUILD_LINE="STABLE" +# --- CREATE / RECREATE RELEASE --- +if github-release info -u "$GITHUB_ORGANIZATION" -r "$GITHUB_REPO" --tag "$TAG" -j &>/dev/null; then + github-release delete --user "$GITHUB_ORGANIZATION" --repo "$GITHUB_REPO" --tag "$TAG" fi -TAG="${BUILD_LINE}_${RELEASE_PREFIX}_${BUILD_ID}" +github-release release \ + --user "$GITHUB_ORGANIZATION" \ + --repo "$GITHUB_REPO" \ + --tag "$TAG" \ + --name "$NAME" \ + --description "$DESCRIPTION" -URL_LINE="https://github.com/${GITHUB_ORGANIZATION}/${GITHUB_REPO}/releases/download/${TAG}/$PAX_BASENAME" -DESCRIPTION="${DESCRIPTION}
Command to download and install on z/OS (if you have curl)
curl -o ${PAX_BASENAME} -L ${URL_LINE} && pax -rf ${PAX_BASENAME} && cd $DIR_NAME && . ./.env
" -DESCRIPTION="${DESCRIPTION}
Or use:
zopen install ${PORT_NAME}
" +# --- VALIDATE RELEASE (FIXES YOUR ERROR) --- +github-release info \ + -u "$GITHUB_ORGANIZATION" \ + -r "$GITHUB_REPO" \ + --tag "$TAG" || { + echo "ERROR: Release creation failed" + exit 1 +} -NAME="${PORT_NAME} ${VERSION}(Build ${BUILD_ID}) - ($BUILD_LINE)" +# --- UPLOAD PAX --- +github-release upload \ + --user "$GITHUB_ORGANIZATION" \ + --repo "$GITHUB_REPO" \ + --tag "$TAG" \ + --name "$PAX_BASENAME" \ + --file "$PAX" -# --- Generate Release Notes using create_release_notes.sh in Markdown format --- -RELEASE_NOTES_MD_CONTENT=$( - "$RELEASE_NOTES_SCRIPT" -m release -p "$GITHUB_REPO" -t markdown -) +# ========================================================= +# 🔧 PULP SETUP (AUTO INSTALL + CONFIG) +# ========================================================= -# --- Generate Release Notes using create_release_notes.sh in Text format --- -RELEASE_NOTES_TXT_CONTENT=$( # New variable for text content - "$RELEASE_NOTES_SCRIPT" -m release -p "$GITHUB_REPO" -t text -) +echo "=== Checking Pulp CLI ===" +if ! command -v pulp >/dev/null 2>&1; then + echo "Pulp CLI not found. Installing..." -if [ -z "$RELEASE_NOTES_MD_CONTENT" ]; then - echo "Error: No Markdown release notes were generated by $RELEASE_NOTES_SCRIPT." - exit 1 -else - echo "Markdown release notes generated successfully by $RELEASE_NOTES_SCRIPT" -fi - -if [ -z "$RELEASE_NOTES_TXT_CONTENT" ]; then - echo "Error: No Text release notes were generated by $RELEASE_NOTES_SCRIPT." - exit 1 -else - echo "Text release notes generated successfully by $RELEASE_NOTES_SCRIPT" + if command -v pip3 >/dev/null 2>&1; then + python3 -m pip install --user pulp-cli || true + export PATH="$HOME/.local/bin:$PATH" + fi fi +if command -v pulp >/dev/null 2>&1; then + echo "Pulp CLI available" -# --- Create release notes files --- -CHANGELOG_TXT_FILE="CHANGELOG.txt" -RELEASE_NOTES_MD_FILE="CHANGELOG.md" + pulp config create \ + --base-url "$PULP_HOST" \ + --username "$PULP_USERNAME" \ + --password "$PULP_PASSWORD" \ + --overwrite -echo "$RELEASE_NOTES_TXT_CONTENT" > "$CHANGELOG_TXT_FILE" # Write TEXT release notes to .txt file # Using new content variable -echo "$RELEASE_NOTES_MD_CONTENT" > "$RELEASE_NOTES_MD_FILE" # Write Markdown notes to .md file - -echo "Release notes written to $CHANGELOG_TXT_FILE" -echo "Release notes written to $RELEASE_NOTES_MD_FILE" - -# --- Append CHANGELOG.md content to DESCRIPTION --- -DESCRIPTION_WITH_CHANGELOG="$DESCRIPTION" # Start with the basic description -DESCRIPTION_WITH_CHANGELOG+=$'\n\n' # Add a couple of newlines to separate -DESCRIPTION_WITH_CHANGELOG+="$RELEASE_NOTES_MD_CONTENT" # Append the Markdown changelog -DESCRIPTION="$DESCRIPTION_WITH_CHANGELOG" # Update DESCRIPTION to include changelog - - -exists=$(github-release info -u ${GITHUB_ORGANIZATION} -r ${GITHUB_REPO} --tag "${TAG}" -j) -if [ $? -gt 0 ]; then - echo "Creating a new tag in github" - github-release -v release --user ${GITHUB_ORGANIZATION} --repo ${GITHUB_REPO} --tag "${TAG}" --name "${NAME}" --description "${DESCRIPTION}" # Use --description - if [ $? -eq 0 ]; then - echo "Release created successfully!" - else - echo "Failed to create release." + pulp status || { + echo "ERROR: Pulp connection failed" exit 1 - fi + } + + PULP_AVAILABLE=true else - echo "Deleting and creating new tag" - github-release -v delete --user ${GITHUB_ORGANIZATION} --repo ${GITHUB_REPO} --tag "${TAG}" - github-release -v release --user ${GITHUB_ORGANIZATION} --repo ${GITHUB_REPO} --tag "${TAG}" --name "${NAME}" --description "${DESCRIPTION}" # Use --description - if [ $? -eq 0 ]; then - echo "Release recreated successfully!" - else - echo "Failed to recreate release." - exit 1 - fi + echo "WARNING: Pulp not available, skipping RPM upload" + PULP_AVAILABLE=false fi -# check up to 5 times for the release to appear on github -for i in {1..5}; do - github-release info -u "${GITHUB_ORGANIZATION}" -r "${GITHUB_REPO}" --tag "${TAG}" -j - if [ $? -eq 0 ]; then - echo "Release found!" - break - else - if [ $i -eq 5 ]; then - echo "Command failed after 5 attempts. Recreating release..." - github-release -v release --user ${GITHUB_ORGANIZATION} --repo ${GITHUB_REPO} --tag "${TAG}" --name "${NAME}" --description "${DESCRIPTION}" # Use --description - if [ $? -eq 0 ]; then - echo "Release recreated successfully!" - else - echo "Failed to recreate release." - exit 1 - fi - else - echo "Command failed. Retrying in 10 seconds..." - sleep 10 - fi - fi -done +# ========================================================= +# 📦 RPM UPLOAD +# ========================================================= -echo "Uploading the artifacts into github" -github-release -v upload --user ${GITHUB_ORGANIZATION} --repo ${GITHUB_REPO} --tag "${TAG}" --name "${PAX_BASENAME}" --file "${PAX}" -if [ $? -eq 0 ]; then - echo "PAX Artifact uploaded successfully!" -else - echo "Failed to upload PAX artifact!" - exit 1 -fi +if [ "$NUM_RPMS" -gt 0 ] && [ "$PULP_AVAILABLE" = true ]; then -if [ $NUM_RPMS -gt 0 ]; then if [ "$BUILD_LINE" = "STABLE" ]; then PULP_REPO="zopen-stable" else PULP_REPO="zopen-dev" fi - echo "Configuring Pulp server..." - -pulp config create \ - --base-url "$PULP_HOST" \ - --username "$PULP_USERNAME" \ - --password "$PULP_PASSWORD" \ - --overwrite -pulp status || exit 1 - - echo "Uploading the RPM artifacts into github and Pulp repository: ${PULP_REPO}" + + echo "Uploading RPMs to Pulp repo: $PULP_REPO" + for RPM in "${RPM_FILES[@]}"; do - RPM_BASENAME=$(basename "${RPM}") - echo "Uploading ${RPM_BASENAME}..." - github-release -v upload --user ${GITHUB_ORGANIZATION} --repo ${GITHUB_REPO} --tag "${TAG}" --name "${RPM_BASENAME}" --file "${RPM}" - - # Push to the specific Pulp repository - pulp rpm repository upload --name "${PULP_REPO}" --file "${RPM}" - if [ $? -eq 0 ]; then - echo "RPM Artifact ${RPM_BASENAME} uploaded successfully to GitHub and Pulp!" - else - echo "Failed to upload RPM artifact ${RPM_BASENAME} to Pulp!" - exit 1 - fi + RPM_NAME=$(basename "$RPM") + + # Upload to GitHub + github-release upload \ + --user "$GITHUB_ORGANIZATION" \ + --repo "$GITHUB_REPO" \ + --tag "$TAG" \ + --name "$RPM_NAME" \ + --file "$RPM" + + # Upload to Pulp (with retry) + for i in 1 2 3; do + if pulp rpm repository upload --name "$PULP_REPO" --file "$RPM"; then + echo "SUCCESS: $RPM_NAME" + break + else + echo "Retry $i for $RPM_NAME" + sleep 5 + fi + + if [ $i -eq 3 ]; then + echo "ERROR: Failed to upload $RPM_NAME" + exit 1 + fi + done done -else - echo "No RPM artifacts to upload." -fi -echo "Uploading metadata artifacts into github" -github-release -v upload --user ${GITHUB_ORGANIZATION} --repo ${GITHUB_REPO} --tag "${TAG}" --name "metadata.json" --file "${METADATA}" -if [ $? -eq 0 ]; then - echo "Metadata Artifact uploaded successfully!" else - echo "Failed to upload Metadata artifact!" - exit 1 + echo "Skipping RPM upload" fi -# --- Upload CHANGELOG.md as release asset --- -echo "Uploading CHANGELOG.md as release asset into github" -github-release -v upload --user ${GITHUB_ORGANIZATION} --repo ${GITHUB_REPO} --tag "${TAG}" --name "CHANGELOG.md" --file "$RELEASE_NOTES_MD_FILE" -if [ $? -eq 0 ]; then - echo "CHANGELOG.md uploaded successfully as release asset!" -else - echo "Failed to upload CHANGELOG.md as release asset!" - exit 1 -fi +# --- FINAL FILES --- +github-release upload \ + --user "$GITHUB_ORGANIZATION" \ + --repo "$GITHUB_REPO" \ + --tag "$TAG" \ + --name "metadata.json" \ + --file "$METADATA" -# --- Upload CHANGELOG.txt as release asset --- -echo "Uploading CHANGELOG.txt as release asset into github" -github-release -v upload --user ${GITHUB_ORGANIZATION} --repo ${GITHUB_REPO} --tag "${TAG}" --name "CHANGELOG.txt" --file "$CHANGELOG_TXT_FILE" # Use CHANGELOG_TXT_FILE -if [ $? -eq 0 ]; then - echo "CHANGELOG.txt uploaded successfully as release asset!" -else - echo "Failed to upload CHANGELOG.txt as release asset!" - exit 1 -fi +github-release upload \ + --user "$GITHUB_ORGANIZATION" \ + --repo "$GITHUB_REPO" \ + --tag "$TAG" \ + --name "CHANGELOG.md" \ + --file "CHANGELOG.md" +echo "=== SUCCESS: PUBLISH COMPLETED ===" From 91b80cc21df1e421ce1183f177f7313676564481 Mon Sep 17 00:00:00 2001 From: TejaswiniIBM Date: Thu, 7 May 2026 03:37:31 -0400 Subject: [PATCH 28/31] Adding changed upload command Signed-off-by: TejaswiniIBM --- CHANGES_MAY_2026.md | 55 +++++++++++++++++++++++++++++++++++++++++++++ bin/zopen-publish | 14 ++++++++++++ cicd/publish.groovy | 19 ++++++++++++++++ 3 files changed, 88 insertions(+) create mode 100644 CHANGES_MAY_2026.md diff --git a/CHANGES_MAY_2026.md b/CHANGES_MAY_2026.md new file mode 100644 index 000000000..f89178436 --- /dev/null +++ b/CHANGES_MAY_2026.md @@ -0,0 +1,55 @@ +# Documentation of Recent Changes (May 2026) + +This document summarizes the significant changes made to the `zopen` tools recently, focusing on RPM generation, Pulp integration, and dependency management improvements. + +## 1. `zopen-pax2rpm` Refactoring and Improvements + +The `zopen-pax2rpm` tool, used for generating RPM spec files and building RPMs from pax archives, has undergone a major overhaul: + +- **Portability:** Switched shebang from `#!/bin/bash` to `#!/bin/sh`. +- **Consistency:** Integrated `setupMyself` to align with other `zopen` scripts and source `common.sh`. +- **Platform Support:** + - Improved architecture detection for z/OS, correctly mapping numeric machine types to `s390x`. + - Added `%define __os_install_post %{nil}` to spec files on z/OS to prevent broken post-install processing (like stripping). +- **Robust Parsing:** + - Improved filename parsing to handle `.zos` suffixes and avoid bash-specific syntax. + - Enhanced pax archive analysis with higher entry limits (up to 5000) and more robust directory structure detection. +- **Improved Build Environment:** + - Better handling of `~/.rpmmacros` and `_topdir`. + - Uses `--define "_topdir ..."` in `rpmbuild` commands for better isolation. +- **User Interface:** + - Added `--pkg-version` and `--version` options. + - Improved error messages and dependency checking. + +## 2. Pulp Integration for RPM Publishing + +Support for the Pulp repository manager has been added to the publishing workflow: + +- **`zopen-publish`**: + - New options: `--pulp` (enable Pulp upload) and `--pulp-repo ` (specify target repository). + - Automatic detection of RPM files in common build locations (`rpmbuild/RPMS` or the same directory as the pax file). + - New `uploadToPulp` function to handle the interaction with the Pulp CLI. +- **`cicd/publish.groovy`**: + - Updated Jenkins pipeline to automatically push RPMs to the appropriate Pulp repository (`zopen-stable` or `zopen-dev`) during the release process. + +## 3. Binary-Only Dependency Support (`:bin`) + +A new mechanism for handling binary-only dependencies has been introduced to `zopen-build`: + +- **Syntax:** Dependencies can now be specified with a `:bin` suffix (e.g., `git:bin`). +- **Behavior:** `:bin` dependencies are intended for tools that are only needed for execution (relying on `bin/` and `share/`), rather than for linking or compiling against. +- **`zopen-build` Changes:** + - Refactored environment sourcing (`setDepsEnv`) to handle `:bin` dependencies by temporarily unsetting `ZOPEN_IN_ZOPEN_BUILD`. + - Added `normalizeDeps` logic to prefer full packages if both full and `:bin` versions of the same package are requested. +- **Environment Protection:** + - Modified `.env` generation to wrap `PKG_CONFIG_PATH` updates in a check for `ZOPEN_IN_ZOPEN_BUILD`. This prevents build-time environment variables from leaking into the runtime environment of `:bin` dependencies. + +## 4. Shared Library Updates (`include/common.sh`) + +New utility functions were added to `common.sh` to support the new dependency logic: +- `parseDeps`: Now correctly handles the `:bin` suffix and extracts it as a property. +- `normalizeDeps`: Deduplicates and prioritizes dependencies, ensuring a consistent environment. + +## 5. Automated Reports + +- **`docs/upstreamstatus.md`**: The Upstream Patch Status Report has been updated with the latest data on project patches and historical trends. diff --git a/bin/zopen-publish b/bin/zopen-publish index 9108b1bd3..836eef17f 100755 --- a/bin/zopen-publish +++ b/bin/zopen-publish @@ -384,6 +384,20 @@ uploadToPulp() { fi printVerbose "Successfully pushed $rpm_file to Pulp repository: $PULP_REPO" + + # Update the distribution to point to the latest publication + DIST_NAME="$PULP_REPO" + + printInfo "- Updating Pulp distribution $DIST_NAME..." + LATEST_PUB=$(pulp rpm publication list --repository "${PULP_REPO}" --limit 1 | jq -r '.[0].pulp_href') + if [ -n "$LATEST_PUB" ] && [ "$LATEST_PUB" != "null" ]; then + if ! pulp rpm distribution update --name "${DIST_NAME}" --publication "${LATEST_PUB}" > /dev/null; then + printWarning "Warning: Failed to update Pulp distribution ${DIST_NAME}" + fi + else + printWarning "Warning: Could not find latest publication for ${PULP_REPO}" + fi + return 0 } diff --git a/cicd/publish.groovy b/cicd/publish.groovy index b25aa26f2..b03f8b76e 100755 --- a/cicd/publish.groovy +++ b/cicd/publish.groovy @@ -239,6 +239,25 @@ if [ $NUM_RPMS -gt 0 ]; then exit 1 fi done + + # After all RPMs are uploaded, update the distribution to the latest publication + echo "Updating Pulp distribution for ${PULP_REPO}..." + + # Determine distribution name (matching the repo name in this setup) + DIST_NAME="$PULP_REPO" + + LATEST_PUB=$(pulp rpm publication list --repository "${PULP_REPO}" --limit 1 | jq -r '.[0].pulp_href') + if [ -n "$LATEST_PUB" ] && [ "$LATEST_PUB" != "null" ]; then + echo "Updating distribution ${DIST_NAME} to publication ${LATEST_PUB}" + pulp rpm distribution update --name "${DIST_NAME}" --publication "${LATEST_PUB}" + if [ $? -eq 0 ]; then + echo "Successfully updated Pulp distribution ${DIST_NAME}" + else + echo "Warning: Failed to update Pulp distribution ${DIST_NAME}" + fi + else + echo "Warning: Could not find latest publication for ${PULP_REPO}" + fi else echo "No RPM artifacts to upload." fi From 471ae279ac236310c88eca3997801a80d602662a Mon Sep 17 00:00:00 2001 From: TejaswiniIBM Date: Tue, 12 May 2026 00:51:11 -0400 Subject: [PATCH 29/31] Updated the pulp command with autopublish Signed-off-by: TejaswiniIBM --- cicd/publish.groovy | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/cicd/publish.groovy b/cicd/publish.groovy index 34aeee1f9..dafbe8bb6 100755 --- a/cicd/publish.groovy +++ b/cicd/publish.groovy @@ -134,15 +134,19 @@ fi # 📦 RPM UPLOAD # ========================================================= +PULP_REPO="zopen" + if [ "$NUM_RPMS" -gt 0 ] && [ "$PULP_AVAILABLE" = true ]; then - if [ "$BUILD_LINE" = "STABLE" ]; then - PULP_REPO="zopen-stable" - else - PULP_REPO="zopen-dev" - fi + echo "--- Configuring Pulp Pipeline ---" + + # 1. Enable autopublish so every upload triggers a new live version + pulp rpm repository update --name "$PULP_REPO" --autopublish - echo "Uploading RPMs to Pulp repo: $PULP_REPO" + # 2. Link the distribution to the repository + pulp rpm distribution update --name "$PULP_REPO" --repository "$PULP_REPO" + + echo "Uploading $NUM_RPMS RPMs to Pulp repo: $PULP_REPO" for RPM in "${RPM_FILES[@]}"; do RPM_NAME=$(basename "$RPM") @@ -172,6 +176,9 @@ if [ "$NUM_RPMS" -gt 0 ] && [ "$PULP_AVAILABLE" = true ]; then done done + echo "=== SUCCESS: Packages are being processed ===" + echo "Check the status at: ${PULP_HOST}/pulp/content/rpm/${PULP_REPO}/" + else echo "Skipping RPM upload" fi From 25749eb629e6cec6e9bc5d1bcb08d1f3931705df Mon Sep 17 00:00:00 2001 From: TejaswiniIBM Date: Tue, 12 May 2026 15:19:15 +0530 Subject: [PATCH 30/31] Fix formatting and syntax errors in zopen-publish script - Fixed logical operator on line 11: changed && to || for proper directory/file existence check - Replaced bash-specific [[ ]] syntax with POSIX-compatible [ ] on lines 234, 236, 281, 283, 321, 351, 448, 458 - Replaced bash == with POSIX = for string comparison - Fixed incomplete/truncated lines 51 and 474 - complete the example commands - Replaced bash array syntax with POSIX-compatible alternatives for lines 507-525 - Ensured all if/then/fi blocks are properly closed - Made script fully POSIX sh compatible as declared in shebang --- bin/zopen-publish | 57 ++++++++++++++++++++++++++++++----------------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/bin/zopen-publish b/bin/zopen-publish index 836eef17f..6ca6e7730 100755 --- a/bin/zopen-publish +++ b/bin/zopen-publish @@ -8,7 +8,7 @@ setupMyself() { ME=$(basename "$0") MYDIR="$(cd "$(dirname "$0")" > /dev/null 2>&1 && pwd -P)" INCDIR="${MYDIR}/../include" - if ! [ -d "${INCDIR}" ] && ! [ -f "${INCDIR}/common.sh" ]; then + if ! [ -d "${INCDIR}" ] || ! [ -f "${INCDIR}/common.sh" ]; then echo "Internal Error. Unable to find common.sh file to source." >&2 exit 8 fi @@ -48,7 +48,7 @@ printSyntax() echo "" echo "Example:" echo " zopen-publish -f -p install/mypackage.zos.pax.Z -m metadata.json -g DEV_mypackage_12345 -t --pulp" - echo " zopen-publish -v -f -p install/mypackage.zos.pax.Z -m metadata.json -r https://github.com/zopencommunity/override-repo.git -d \"My custom release description\" -b DEV -g REL-1.0.1 -t -l --pulp" + echo " zopen-publish -v -f -p install/mypackage.zos.pax.Z -m metadata.json -r https://github.com/zopencommunity/override-repo.git -d \"My custom release description\" -b DEV -g REL-1.0.1 -t " } checkDependencies() { @@ -231,9 +231,9 @@ createGitHubRelease() { fi - if [ -z "$release_id" ] || [[ "$release_id" == "null" ]]; then + if [ -z "$release_id" ] || [ "$release_id" = "null" ]; then printError "Error creating GitHub release tag: $tag.\n$error_message" - if [ -n "$api_error_message" ] && [[ "$api_error_message" != "null" ]]; then + if [ -n "$api_error_message" ] && [ "$api_error_message" != "null" ]; then printError "GitHub API Error: $api_error_message" fi printError "Full GitHub API response (for debugging):" @@ -278,9 +278,9 @@ uploadGitHubReleaseAsset() { return 1 fi - if [ -z "$asset_state" ] || [[ "$asset_state" == "null" ]] || [ "$asset_state" != "uploaded" ]; then + if [ -z "$asset_state" ] || [ "$asset_state" = "null" ] || [ "$asset_state" != "uploaded" ]; then printError "Error uploading asset: $asset_file to release ID: $release_id" - if [ -n "$api_error_message" ] && [[ "$api_error_message" != "null" ]]; then + if [ -n "$api_error_message" ] && [ "$api_error_message" != "null" ]; then printError "GitHub API Error: $api_error_message" fi printError "Full GitHub API response (for debugging):" # Print full response for debugging @@ -318,7 +318,7 @@ deleteGitHubReleaseByTag() { fi - if [ -z "$release_id" ] || [[ "$release_id" == "null" ]]; then + if [ -z "$release_id" ] || [ "$release_id" = "null" ]; then printVerbose "No existing release found for tag: $tag. Skipping deletion." return 0 # No release to delete - consider it success. fi @@ -348,7 +348,7 @@ deleteGitHubReleaseByTag() { fi - if [ -n "$delete_api_error_message" ] && [[ "$delete_api_error_message" != "null" ]]; then + if [ -n "$delete_api_error_message" ] && [ "$delete_api_error_message" != "null" ]; then printError "Error deleting GitHub release ID: $release_id for tag: $tag. GitHub API Error: $delete_api_error_message. Full GitHub API response (for debugging): $DELETE_RESPONSE" return 1 fi @@ -445,7 +445,7 @@ publishRelease() { if [ -z "$BUILD_LINE_OPT" ]; then BUILD_LINE=$(jq -r '.product.buildline' "$METADATA_FILE") BUILD_LINE_UPPER=$(echo "$BUILD_LINE" | tr '[:lower:]' '[:upper:]') - if [[ "$BUILD_LINE_UPPER" != "DEV" && "$BUILD_LINE_UPPER" != "STABLE" ]]; then + if [ "$BUILD_LINE_UPPER" != "DEV" ] && [ "$BUILD_LINE_UPPER" != "STABLE" ]; then printError "Error: Invalid build line in metadata.json: $BUILD_LINE. Must be 'DEV' or 'STABLE'." return 1 fi @@ -455,7 +455,7 @@ publishRelease() { else BUILD_LINE="$BUILD_LINE_OPT" BUILD_LINE_UPPER=$(echo "$BUILD_LINE" | tr '[:lower:]' '[:upper:]') - if [[ "$BUILD_LINE_UPPER" != "DEV" && "$BUILD_LINE_UPPER" != "STABLE" ]]; then + if [ "$BUILD_LINE_UPPER" != "DEV" ] && [ "$BUILD_LINE_UPPER" != "STABLE" ]; then printError "Invalid build line: $BUILD_LINE. Must be 'DEV' or 'STABLE'." printSyntax return 1 @@ -471,7 +471,7 @@ publishRelease() { DESCRIPTION="${PORT_DESCRIPTION}
" DESCRIPTION="${DESCRIPTION}Version: ${VERSION}
" URL_LINE="https://github.com/${GITHUB_ORGANIZATION}/${GITHUB_REPO}/releases/download/${TAG}/${PAX_BASENAME}" - DESCRIPTION="${DESCRIPTION}
Command to download and install on z/OS (if you have curl)
curl -o ${PAX_BASENAME} -L ${URL_LINE} && pax -rf ${PAX_BASENAME} && cd ${PORT_NAME} && . ./.env
" + DESCRIPTION="${DESCRIPTION}
Command to download and install on z/OS (if you have curl)
curl -o ${PAX_BASENAME} -L ${URL_LINE} && pax -rf ${PAX_BASENAME} && cd ${PORT_NAME} && . ./setup.sh
" DESCRIPTION="${DESCRIPTION}
Or use:
zopen install ${PORT_NAME}
" if ${FORCE_OVERWRITE} || ! deleteGitHubReleaseByTag "${TAG}"; then @@ -504,21 +504,36 @@ publishRelease() { if [ "$PUSH_TO_PULP" = true ]; then # Look for RPMs in rpmbuild/RPMS first (typical build location) - RPM_FILES=($(find rpmbuild/RPMS -type f -name "*.rpm" 2>/dev/null)) + RPM_COUNT=0 + RPM_DIRS="rpmbuild/RPMS" + + # Check if rpmbuild/RPMS exists + if [ -d "$RPM_DIRS" ]; then + for rpm_file in "$RPM_DIRS"/*.rpm; do + if [ -f "$rpm_file" ]; then + if ! uploadToPulp "$rpm_file" "$BUILD_LINE"; then + return 1 + fi + RPM_COUNT=$((RPM_COUNT + 1)) + fi + done + fi # If not found there, look in the same directory as the PAX file - if [ ${#RPM_FILES[@]} -eq 0 ]; then + if [ $RPM_COUNT -eq 0 ]; then PAX_DIR=$(dirname "$PAX_FILE") - RPM_FILES=($(find "$PAX_DIR" -type f -name "*.rpm" 2>/dev/null)) - fi - - if [ ${#RPM_FILES[@]} -gt 0 ]; then - printInfo "- Found ${#RPM_FILES[@]} RPM(s) to push to Pulp." - for RPM in "${RPM_FILES[@]}"; do - if ! uploadToPulp "$RPM" "$BUILD_LINE"; then - return 1 + for rpm_file in "$PAX_DIR"/*.rpm; do + if [ -f "$rpm_file" ]; then + if ! uploadToPulp "$rpm_file" "$BUILD_LINE"; then + return 1 + fi + RPM_COUNT=$((RPM_COUNT + 1)) fi done + fi + + if [ $RPM_COUNT -gt 0 ]; then + printInfo "- Found $RPM_COUNT RPM(s) to push to Pulp." else printWarning "Pulp push requested, but no RPM files were found." fi From 7f2eafe19e4df86c8d3a766cf0d7d8114fcd73f4 Mon Sep 17 00:00:00 2001 From: TejaswiniIBM Date: Tue, 12 May 2026 15:53:05 +0530 Subject: [PATCH 31/31] Fix missing newline at end of zopen-publish file Signed-off-by: TejaswiniIBM