This is an automated email from the ASF dual-hosted git repository.
tuhaihe pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudberry.git
The following commit(s) were added to refs/heads/main by this push:
new 1daae25e0e9 Packaging: Enable RPM package relocation via --prefix
1daae25e0e9 is described below
commit 1daae25e0e95fe17d7e96cb7995ebabc8b95cd60
Author: Dianjin Wang <[email protected]>
AuthorDate: Mon Jul 27 13:54:41 2026 +0800
Packaging: Enable RPM package relocation via --prefix
Rework the RPM packaging so Cloudberry can be installed to a custom
location and so multiple major versions can coexist, aligning the
behavior with Greenplum's packaging model.
Relocation (rpm --prefix):
- Split the hardcoded install path into base_dir + name components and
set Prefix to the base directory (/usr/local) so RPM's relocate
engine can substitute it correctly.
- Move the convenience symlink out of %files and create it in %post
using RPM_INSTALL_PREFIX, so it follows the actual --prefix (with a
fallback to the default base_dir). %postun removes the symlink only
when it still points to the version being erased.
- Disable build-id links so they are not emitted outside the
relocatable prefix.
Major-version coexistence:
- Embed the major version in the package Name
(apache-cloudberry-db-incubating-<major>), derived from %{version},
so different major versions register as distinct packages and can be
installed side by side (dnf) or under separate prefixes (rpm -i).
- Obsolete the previous unversioned package name on upgrade; this does
not match the versioned names, so majors still coexist.
- In %post, move the generic symlink only when the existing target is
the same major version, leaving other majors untouched. The version
is parsed from the target directory name rather than by sourcing
environment files or executing installed binaries.
- Keep the published file name in the historical format (without the
"-<major>" segment) by renaming artifacts in build-rpm.sh; the file
name does not affect the Name/Version stored in the RPM header.
Packaging correctness and hardening:
- Stop changing installed files to gpadmin ownership in %post; the
package tree stays root-owned.
- Do not expose bundled private shared libraries (libpq.so.5, etc.) as
Provides, and do not require them from the system; they ship in the
package and are resolved via RPATH.
- Validate that version/release macros are supplied, mark
cloudberry-env.sh as %config(noreplace), copy the tree with cp -a to
preserve hidden files and attributes, add coreutils scriptlet
dependencies, add python3 to the el9 runtime requires, and update the
license tag to the SPDX identifier Apache-2.0.
CI workflows:
- Query the installed package by glob (rpm -qa 'apache-...*') instead of
the fixed name, since the registered Name now carries the major.
- Keep locating the published artifact by its historical file name,
which build-rpm.sh restores after the build.
Assisted-by: Claude Code
Assisted-by: DeepSeek
---
.github/workflows/build-cloudberry-rocky10.yml | 15 ++-
.github/workflows/build-cloudberry-rocky8.yml | 15 ++-
.github/workflows/build-cloudberry.yml | 15 ++-
.github/workflows/build-dbg-cloudberry.yml | 11 +-
.../rpm/apache-cloudberry-db-incubating.spec | 121 +++++++++++++++++----
devops/build/packaging/rpm/build-rpm.sh | 29 +++++
6 files changed, 169 insertions(+), 37 deletions(-)
diff --git a/.github/workflows/build-cloudberry-rocky10.yml
b/.github/workflows/build-cloudberry-rocky10.yml
index 15a9b6da2aa..ac5a1d6def0 100644
--- a/.github/workflows/build-cloudberry-rocky10.yml
+++ b/.github/workflows/build-cloudberry-rocky10.yml
@@ -661,6 +661,9 @@ jobs:
"${SRC_DIR}"/devops/build/packaging/rpm/build-rpm.sh --version
"${CBDB_VERSION}" --release "${BUILD_NUMBER}" "${DEBUG_RPMBUILD_OPT}"
# Get OS version and move RPM
+ # build-rpm.sh normalizes the published file name to the historical
+ # format (without the "-<major>" segment); the package Name
metadata
+ # still embeds the major version.
os_version=$(grep -oP '(?<=^VERSION_ID=")[0-9]+' /etc/os-release)
RPM_FILE="${HOME}"/rpmbuild/RPMS/x86_64/apache-cloudberry-db-incubating-"${CBDB_VERSION}"-"${BUILD_NUMBER}""${DEBUG_IDENTIFIER}".el"${os_version}".x86_64.rpm
cp "${RPM_FILE}" "${SRC_DIR}"
@@ -920,9 +923,9 @@ jobs:
fi
echo "Installation completed successfully"
- rpm -qi apache-cloudberry-db-incubating
+ rpm -qa 'apache-cloudberry-db-incubating*' | xargs -r rpm -qi
echo "Installed files:"
- rpm -ql apache-cloudberry-db-incubating
+ rpm -qa 'apache-cloudberry-db-incubating*' | xargs -r rpm -ql
} 2>&1 | tee -a install-logs/details/rpm-installation.log
- name: Upload install logs
@@ -942,7 +945,7 @@ jobs:
echo "# Installed Package Summary"
echo "\`\`\`"
- rpm -qi apache-cloudberry-db-incubating
+ rpm -qa 'apache-cloudberry-db-incubating*' | xargs -r rpm -qi
echo "\`\`\`"
} >> "$GITHUB_STEP_SUMMARY" || true
@@ -1330,7 +1333,7 @@ jobs:
fi
echo "Installation completed successfully"
- rpm -qi apache-cloudberry-db-incubating
+ rpm -qa 'apache-cloudberry-db-incubating*' | xargs -r rpm -qi
} 2>&1 | tee -a build-logs/details/rpm-installation.log
# Clean up downloaded RPM artifacts to free disk space
@@ -1423,6 +1426,10 @@ jobs:
run: |
set -o pipefail
+ # Grant gpadmin write access to the install directory
+ # -H follows the command-line symlink to the real directory.
+ chown -RH gpadmin:gpadmin "${BUILD_DESTINATION}/"
+
# Initialize test status
overall_status=0
diff --git a/.github/workflows/build-cloudberry-rocky8.yml
b/.github/workflows/build-cloudberry-rocky8.yml
index d48e436265b..59238524fa0 100644
--- a/.github/workflows/build-cloudberry-rocky8.yml
+++ b/.github/workflows/build-cloudberry-rocky8.yml
@@ -660,6 +660,9 @@ jobs:
"${SRC_DIR}"/devops/build/packaging/rpm/build-rpm.sh --version
"${CBDB_VERSION}" --release "${BUILD_NUMBER}" "${DEBUG_RPMBUILD_OPT}"
# Get OS version and move RPM
+ # build-rpm.sh normalizes the published file name to the historical
+ # format (without the "-<major>" segment); the package Name
metadata
+ # still embeds the major version.
os_version=$(grep -oP '(?<=^VERSION_ID=")[0-9]+' /etc/os-release)
RPM_FILE="${HOME}"/rpmbuild/RPMS/x86_64/apache-cloudberry-db-incubating-"${CBDB_VERSION}"-"${BUILD_NUMBER}""${DEBUG_IDENTIFIER}".el"${os_version}".x86_64.rpm
cp "${RPM_FILE}" "${SRC_DIR}"
@@ -919,9 +922,9 @@ jobs:
fi
echo "Installation completed successfully"
- rpm -qi apache-cloudberry-db-incubating
+ rpm -qa 'apache-cloudberry-db-incubating*' | xargs -r rpm -qi
echo "Installed files:"
- rpm -ql apache-cloudberry-db-incubating
+ rpm -qa 'apache-cloudberry-db-incubating*' | xargs -r rpm -ql
} 2>&1 | tee -a install-logs/details/rpm-installation.log
- name: Upload install logs
@@ -941,7 +944,7 @@ jobs:
echo "# Installed Package Summary"
echo "\`\`\`"
- rpm -qi apache-cloudberry-db-incubating
+ rpm -qa 'apache-cloudberry-db-incubating*' | xargs -r rpm -qi
echo "\`\`\`"
} >> "$GITHUB_STEP_SUMMARY" || true
@@ -1329,7 +1332,7 @@ jobs:
fi
echo "Installation completed successfully"
- rpm -qi apache-cloudberry-db-incubating
+ rpm -qa 'apache-cloudberry-db-incubating*' | xargs -r rpm -qi
} 2>&1 | tee -a build-logs/details/rpm-installation.log
# Clean up downloaded RPM artifacts to free disk space
@@ -1414,6 +1417,10 @@ jobs:
run: |
set -o pipefail
+ # Grant gpadmin write access to the install directory
+ # -H follows the command-line symlink to the real directory.
+ chown -RH gpadmin:gpadmin "${BUILD_DESTINATION}/"
+
# Initialize test status
overall_status=0
diff --git a/.github/workflows/build-cloudberry.yml
b/.github/workflows/build-cloudberry.yml
index 6289785bb14..7839fe0a5d9 100644
--- a/.github/workflows/build-cloudberry.yml
+++ b/.github/workflows/build-cloudberry.yml
@@ -659,6 +659,9 @@ jobs:
"${SRC_DIR}"/devops/build/packaging/rpm/build-rpm.sh --version
"${CBDB_VERSION}" --release "${BUILD_NUMBER}" "${DEBUG_RPMBUILD_OPT}"
# Get OS version and move RPM
+ # build-rpm.sh normalizes the published file name to the historical
+ # format (without the "-<major>" segment); the package Name
metadata
+ # still embeds the major version.
os_version=$(grep -oP '(?<=^VERSION_ID=")[0-9]+' /etc/os-release)
RPM_FILE="${HOME}"/rpmbuild/RPMS/x86_64/apache-cloudberry-db-incubating-"${CBDB_VERSION}"-"${BUILD_NUMBER}""${DEBUG_IDENTIFIER}".el"${os_version}".x86_64.rpm
cp "${RPM_FILE}" "${SRC_DIR}"
@@ -918,9 +921,9 @@ jobs:
fi
echo "Installation completed successfully"
- rpm -qi apache-cloudberry-db-incubating
+ rpm -qa 'apache-cloudberry-db-incubating*' | xargs -r rpm -qi
echo "Installed files:"
- rpm -ql apache-cloudberry-db-incubating
+ rpm -qa 'apache-cloudberry-db-incubating*' | xargs -r rpm -ql
} 2>&1 | tee -a install-logs/details/rpm-installation.log
- name: Upload install logs
@@ -940,7 +943,7 @@ jobs:
echo "# Installed Package Summary"
echo "\`\`\`"
- rpm -qi apache-cloudberry-db-incubating
+ rpm -qa 'apache-cloudberry-db-incubating*' | xargs -r rpm -qi
echo "\`\`\`"
} >> "$GITHUB_STEP_SUMMARY" || true
@@ -1328,7 +1331,7 @@ jobs:
fi
echo "Installation completed successfully"
- rpm -qi apache-cloudberry-db-incubating
+ rpm -qa 'apache-cloudberry-db-incubating*' | xargs -r rpm -qi
} 2>&1 | tee -a build-logs/details/rpm-installation.log
# Clean up downloaded RPM artifacts to free disk space
@@ -1421,6 +1424,10 @@ jobs:
run: |
set -o pipefail
+ # Grant gpadmin write access to the install directory
+ # -H follows the command-line symlink to the real directory.
+ chown -RH gpadmin:gpadmin "${BUILD_DESTINATION}/"
+
# Initialize test status
overall_status=0
diff --git a/.github/workflows/build-dbg-cloudberry.yml
b/.github/workflows/build-dbg-cloudberry.yml
index d79bd78c77c..660447348e9 100644
--- a/.github/workflows/build-dbg-cloudberry.yml
+++ b/.github/workflows/build-dbg-cloudberry.yml
@@ -503,6 +503,9 @@ jobs:
"${SRC_DIR}"/devops/build/packaging/rpm/build-rpm.sh --version
"${CBDB_VERSION}" --release "${BUILD_NUMBER}" "${DEBUG_RPMBUILD_OPT}"
# Get OS version and move RPM
+ # build-rpm.sh normalizes the published file name to the historical
+ # format (without the "-<major>" segment); the package Name
metadata
+ # still embeds the major version.
os_version=$(grep -oP '(?<=^VERSION_ID=")[0-9]+' /etc/os-release)
RPM_FILE="${HOME}"/rpmbuild/RPMS/x86_64/apache-cloudberry-db-incubating-"${CBDB_VERSION}"-"${BUILD_NUMBER}""${DEBUG_IDENTIFIER}".el"${os_version}".x86_64.rpm
cp "${RPM_FILE}" "${SRC_DIR}"
@@ -723,9 +726,9 @@ jobs:
fi
echo "Installation completed successfully"
- rpm -qi apache-cloudberry-db-incubating
+ rpm -qa 'apache-cloudberry-db-incubating*' | xargs -r rpm -qi
echo "Installed files:"
- rpm -ql apache-cloudberry-db-incubating
+ rpm -qa 'apache-cloudberry-db-incubating*' | xargs -r rpm -ql
} 2>&1 | tee -a install-logs/details/rpm-installation.log
- name: Upload install logs
@@ -745,7 +748,7 @@ jobs:
echo "# Installed Package Summary"
echo "\`\`\`"
- rpm -qi apache-cloudberry-db-incubating
+ rpm -qa 'apache-cloudberry-db-incubating*' | xargs -r rpm -qi
echo "\`\`\`"
} >> "$GITHUB_STEP_SUMMARY" || true
@@ -1095,7 +1098,7 @@ jobs:
fi
echo "Installation completed successfully"
- rpm -qi apache-cloudberry-db-incubating
+ rpm -qa 'apache-cloudberry-db-incubating*' | xargs -r rpm -qi
} 2>&1 | tee -a build-logs/details/rpm-installation.log
- name: Extract source tarball
diff --git a/devops/build/packaging/rpm/apache-cloudberry-db-incubating.spec
b/devops/build/packaging/rpm/apache-cloudberry-db-incubating.spec
index e228f8fe76a..707af2352eb 100644
--- a/devops/build/packaging/rpm/apache-cloudberry-db-incubating.spec
+++ b/devops/build/packaging/rpm/apache-cloudberry-db-incubating.spec
@@ -15,9 +15,32 @@
# specific language governing permissions and limitations
# under the License.
-%define cloudberry_install_dir /usr/local/cloudberry-db
+# Validate required macros early so rpmbuild fails with a clear message
+# before reaching the header.
+%{!?version:%{error:The macro 'version' must be supplied as --define 'version
...'}}
+%{!?release:%{error:The macro 'release' must be supplied as --define 'release
...'}}
+
+%define cloudberry_base_dir /usr/local
+%define cloudberry_name cloudberry-db
+%define cloudberry_install_dir %{cloudberry_base_dir}/%{cloudberry_name}
+
+# Major version, used to build a versioned package Name so that different
+# major versions can be installed side by side (like greenplum-db-6 vs
+# greenplum-db-7). Derived from the version macro by default (single source
+# of truth), but can be overridden via --define 'cloudberry_major_version N'.
+%{!?cloudberry_major_version:%define cloudberry_major_version %(echo
%{version} | cut -d. -f1)}
+
+# Suppress build-id links so they are not created outside the relocatable
prefix.
+%define _build_id_links none
+
+# Do not expose bundled/private shared libraries as RPM Provides.
+# (e.g., libpq.so.5 would conflict with system postgresql-libs.)
+%global __provides_exclude_from ^%{cloudberry_install_dir}-%{version}/.*\.so
+
+# Do not require these bundled libraries from the system;
+# they are shipped inside the package and located via RPATH.
+%global __requires_exclude
^(libpax\.so|libpaxformat\.so|libpostgres\.so|libpq\.so\.5|libxerces-c-3\.3\.so)
-# Add at the top of the spec file
# Default to non-debug build
%bcond_with debug
@@ -27,7 +50,11 @@
%define __strip /bin/true
%endif
-Name: apache-cloudberry-db-incubating
+Name: apache-cloudberry-db-incubating-%{cloudberry_major_version}
+# Replace the previous unversioned package on upgrade. This targets only the
+# old fixed name; it does NOT match apache-cloudberry-db-incubating-<major>,
+# so different major versions still coexist.
+Obsoletes: apache-cloudberry-db-incubating < %{version}-%{release}
Version: %{version}
# In the release definition section
%if %{with debug}
@@ -37,11 +64,11 @@ Release: %{release}%{?dist}
%endif
Summary: High-performance, open-source data warehouse based on
PostgreSQL/Greenplum
-License: ASL 2.0
+License: Apache-2.0
URL: https://cloudberry.apache.org
Vendor: Apache Cloudberry (Incubating)
Group: Applications/Databases
-Prefix: %{cloudberry_install_dir}
+Prefix: %{cloudberry_base_dir}
# Disabled as we are shipping GO programs (e.g. gpbackup)
%define _missing_build_ids_terminate_build 0
@@ -62,6 +89,10 @@ Requires: openssh-server
Requires: rsync
Requires: which
+# Scriptlet dependencies (ln, readlink, rm are from coreutils).
+Requires(post): coreutils
+Requires(postun): coreutils
+
%if 0%{?rhel} == 8
Requires: apr
Requires: audit
@@ -105,6 +136,7 @@ Requires: pam
Requires: pcre2
Requires: perl
Requires: readline
+Requires: python3
Requires: xz
%endif
@@ -145,7 +177,7 @@ project has yet to be fully endorsed by the ASF.
# No prep needed for binary RPM
%build
-# No prep needed for binary RPM
+# No build needed for binary RPM
%install
rm -rf %{buildroot}
@@ -153,33 +185,80 @@ rm -rf %{buildroot}
# Create the versioned directory
mkdir -p %{buildroot}%{cloudberry_install_dir}-%{version}
-cp -R %{cloudberry_install_dir}/*
%{buildroot}%{cloudberry_install_dir}-%{version}
+# Use cp -a with /. to include all the files
+cp -a %{cloudberry_install_dir}/.
%{buildroot}%{cloudberry_install_dir}-%{version}/
# Copy Apache mandatory compliance files from the SOURCES directory into the
installation directory
cp %{_sourcedir}/LICENSE %{buildroot}%{cloudberry_install_dir}-%{version}/
cp %{_sourcedir}/NOTICE %{buildroot}%{cloudberry_install_dir}-%{version}/
cp %{_sourcedir}/DISCLAIMER %{buildroot}%{cloudberry_install_dir}-%{version}/
-cp -R %{_sourcedir}/licenses %{buildroot}%{cloudberry_install_dir}-%{version}/
-
-# Create the symbolic link
-ln -sfn %{cloudberry_install_dir}-%{version}
%{buildroot}%{cloudberry_install_dir}
+cp -a %{_sourcedir}/licenses %{buildroot}%{cloudberry_install_dir}-%{version}/
%files
-%{prefix}-%{version}
-%{prefix}
+%{cloudberry_install_dir}-%{version}
+%config(noreplace) %{cloudberry_install_dir}-%{version}/cloudberry-env.sh
%debug_package
%post
-# Change ownership to gpadmin.gpadmin if the gpadmin user exists
-if id "gpadmin" &>/dev/null; then
- chown -R gpadmin:gpadmin %{cloudberry_install_dir}-%{version}
- chown gpadmin:gpadmin %{cloudberry_install_dir}
+# RPM_INSTALL_PREFIX is set dynamically by RPM to the actual --prefix value.
+# Fall back to cloudberry_base_dir when --prefix was not used.
+INSTALL_PREFIX="${RPM_INSTALL_PREFIX:-%{cloudberry_base_dir}}"
+INSTALL_BASE="${INSTALL_PREFIX%/}"
+
+LINK_PATH="${INSTALL_BASE}/%{cloudberry_name}"
+VERSIONED_DIR="${INSTALL_BASE}/%{cloudberry_name}-%{version}"
+LINK_TARGET_REL="%{cloudberry_name}-%{version}"
+
+if [ ! -e "${LINK_PATH}" ] && [ ! -L "${LINK_PATH}" ]; then
+ # Nothing at the symlink location yet — create it.
+ ln -s "${LINK_TARGET_REL}" "${LINK_PATH}" || :
+elif [ -L "${LINK_PATH}" ]; then
+ # A symlink already exists. Update it when it points to a
+ # recognized Cloudberry versioned directory.
+ EXISTING_TARGET=$(readlink -f -- "${LINK_PATH}" 2>/dev/null || :)
+ EXISTING_NAME=${EXISTING_TARGET##*/}
+
+ case "${EXISTING_NAME}" in
+ %{cloudberry_name}-*)
+ EXISTING_VERSION=${EXISTING_NAME#%{cloudberry_name}-}
+ EXISTING_MAJOR=${EXISTING_VERSION%%.*}
+ if [ "${EXISTING_MAJOR}" = "%{cloudberry_major_version}" ]; then
+ # Same major version: move the generic symlink to this build.
+ ln -sfnT "${LINK_TARGET_REL}" "${LINK_PATH}" || :
+ else
+ # Different major version: leave the existing symlink untouched
+ # so that multiple major versions can coexist under one prefix.
+ echo "Warning: ${LINK_PATH} points to Cloudberry major version
${EXISTING_MAJOR}; leaving it unchanged so major versions can coexist" >&2
+ fi
+ ;;
+ *)
+ echo "Warning: ${LINK_PATH} does not point to a recognized
Cloudberry installation; leaving it unchanged" >&2
+ ;;
+ esac
+else
+ echo "Warning: ${LINK_PATH} exists and is not a symbolic link; leaving it
unchanged" >&2
fi
+exit 0
+
%postun
-if [ $1 -eq 0 ] ; then
- if [ "$(readlink -f "%{cloudberry_install_dir}")" ==
"%{cloudberry_install_dir}-%{version}" ]; then
- unlink "%{cloudberry_install_dir}" || true
- fi
+INSTALL_PREFIX="${RPM_INSTALL_PREFIX:-%{cloudberry_base_dir}}"
+INSTALL_BASE="${INSTALL_PREFIX%/}"
+
+LINK_PATH="${INSTALL_BASE}/%{cloudberry_name}"
+VERSIONED_DIR="${INSTALL_BASE}/%{cloudberry_name}-%{version}"
+
+if [ -L "${LINK_PATH}" ]; then
+ LINK_TARGET=$(readlink "${LINK_PATH}" 2>/dev/null || :)
+
+ # Remove the symlink only when it still points to the version
+ # being removed (handles both absolute and relative targets).
+ case "${LINK_TARGET}" in
+ "${VERSIONED_DIR}"|"%{cloudberry_name}-%{version}")
+ rm -f -- "${LINK_PATH}" || :
+ ;;
+ esac
fi
+
+exit 0
diff --git a/devops/build/packaging/rpm/build-rpm.sh
b/devops/build/packaging/rpm/build-rpm.sh
index ef73a14d4bb..bc284c73cd0 100755
--- a/devops/build/packaging/rpm/build-rpm.sh
+++ b/devops/build/packaging/rpm/build-rpm.sh
@@ -195,5 +195,34 @@ if ! eval "$RPMBUILD_CMD"; then
exit 1
fi
+# Normalize published artifact file names.
+#
+# The RPM's Name metadata embeds the major version
+# (apache-cloudberry-db-incubating-<major>) so that different major versions
+# can be installed side by side. rpmbuild therefore emits files named
+# apache-cloudberry-db-incubating-<major>-<version>-<release>....rpm.
+#
+# For published artifacts we keep the historical file name that omits the
+# "-<major>" segment (e.g.
apache-cloudberry-db-incubating-2.1.0-1.el8.x86_64.rpm),
+# matching the distribution naming used by Greenplum. Renaming the file does
+# NOT change the package identity: RPM reads Name/Version/Release from the
+# package header, not from the file name, so install/upgrade/coexistence and
+# `dnf`/`rpm` queries are unaffected.
+MAJOR_VERSION="${VERSION%%.*}"
+RPMS_DIR="$(rpm --eval '%{_rpmdir}')"
+
+shopt -s nullglob
+for rpm_path in
"${RPMS_DIR}"/*/apache-cloudberry-db-incubating-"${MAJOR_VERSION}"-*"${VERSION}"-*.rpm;
do
+ rpm_dir="$(dirname "$rpm_path")"
+ rpm_base="$(basename "$rpm_path")"
+ # Drop the "-<major>" that immediately follows the fixed name prefix.
+
new_base="${rpm_base/apache-cloudberry-db-incubating-${MAJOR_VERSION}-/apache-cloudberry-db-incubating-}"
+ if [ "$new_base" != "$rpm_base" ]; then
+ mv -f "$rpm_path" "${rpm_dir}/${new_base}"
+ echo "Renamed published artifact: ${rpm_base} -> ${new_base}"
+ fi
+done
+shopt -u nullglob
+
# Print completion message
echo "RPM build completed successfully with Version: $VERSION, Release:
$RELEASE"
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]