diff --git a/pipelines/parts/build-packages.Jenkinsfile b/pipelines/parts/build-packages.Jenkinsfile index 48aca87..38a29c2 100644 --- a/pipelines/parts/build-packages.Jenkinsfile +++ b/pipelines/parts/build-packages.Jenkinsfile @@ -58,6 +58,18 @@ pipeline { booleanParam ( name: 'BUILD_ISO' ) + string( + name: 'STX_SHARED_SOURCE' + ) + string( + name: 'STX_SHARED_REPO' + ) + booleanParam( + name: 'PATCH_BUILD', + defaultValue: false, + description: "Identify if we are building a patch." + + "As we use the same sub-jobs than the usual build we need to know when we are creating a patch." + ) } stages { diff --git a/pipelines/parts/ostree-pull.Jenkinsfile b/pipelines/parts/ostree-pull.Jenkinsfile new file mode 100644 index 0000000..1059d56 --- /dev/null +++ b/pipelines/parts/ostree-pull.Jenkinsfile @@ -0,0 +1,59 @@ +// vim: syn=groovy + +// +// Copyright (c) 2024 Wind River Systems, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// + +library "common@${params.JENKINS_SCRIPTS_BRANCH}" + +setBuildDescr() + +pipeline { + agent any + options { + timestamps() + } + parameters { + string( + name: 'MASTER_JOB_NAME' + ) + string( + name: 'MASTER_BUILD_NUMBER' + ) + string( + name: 'JENKINS_SCRIPTS_BRANCH' + ) + string( + name: 'BUILD_HOME' + ) + string( + name: 'REMOTE_SERVER' + ) + string( + name: 'REMOTE_BUILD_HOME' + ) + string( + name: 'TIMESTAMP' + ) + booleanParam( + name: 'SHELL_XTRACE' + ) + booleanParam( + name: 'DRY_RUN' + ) + } + stages { + stage("ostree-pull") { + steps { + sh ("bash ${Constants.SCRIPTS_DIR}/ostree-pull.sh") + } + } + } + post { + cleanup { + cleanupPartJob() + } + } +} diff --git a/pipelines/parts/patch-iso.Jenkinsfile b/pipelines/parts/patch-iso.Jenkinsfile new file mode 100644 index 0000000..9c0956a --- /dev/null +++ b/pipelines/parts/patch-iso.Jenkinsfile @@ -0,0 +1,68 @@ +// vim: syn=groovy + +// +// Copyright (c) 2024 Wind River Systems, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// + +library "common@${params.JENKINS_SCRIPTS_BRANCH}" + +setBuildDescr() + +pipeline { + agent any + options { + timestamps() + } + parameters { + string( + name: 'MASTER_JOB_NAME' + ) + string( + name: 'MASTER_BUILD_NUMBER' + ) + string( + name: 'JENKINS_SCRIPTS_BRANCH' + ) + string( + name: 'BUILD_HOME' + ) + string( + name: 'SW_VERSION' + ) + string( + name: 'PATCH_NUM' + ) + string( + name: 'TIMESTAMP' + ) + booleanParam( + name: 'SHELL_XTRACE' + ) + booleanParam( + name: 'DRY_RUN' + ) + string( + name: 'REMOTE_SERVER' + ) + string( + name: 'REMOTE_BUILD_HOME' + ) + string( + name: 'BUILD_HOME' + ) + } + stages { + stage("patch-iso") { + steps { + sh("bash ${Constants.SCRIPTS_DIR}/patch-iso.sh") + } + } + } + post { + cleanup { + cleanupPartJob() + } + } +} diff --git a/pipelines/parts/patch-make.Jenkinsfile b/pipelines/parts/patch-make.Jenkinsfile new file mode 100644 index 0000000..e461263 --- /dev/null +++ b/pipelines/parts/patch-make.Jenkinsfile @@ -0,0 +1,69 @@ +// vim: syn=groovy + +// +// Copyright (c) 2024 Wind River Systems, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// + +library "common@${params.JENKINS_SCRIPTS_BRANCH}" + +setBuildDescr() + +pipeline { + agent any + options { + timestamps() + } + parameters { + string( + name: 'MASTER_JOB_NAME' + ) + string( + name: 'MASTER_BUILD_NUMBER' + ) + string( + name: 'JENKINS_SCRIPTS_BRANCH' + ) + string( + name: 'SW_VERSION' + ) + string( + name: 'PATCH_NUM' + ) + string( + name: 'TIMESTAMP' + ) + booleanParam( + name: 'SHELL_XTRACE' + ) + booleanParam( + name: 'DRY_RUN' + ) + string ( + name: 'BUILD_HOME' + ) + string( + name: 'CUSTOM_PATCH_RECIPE', + description: "Allow you to specify the path to a custom patch recipe to be used when creating the patch.", + defaultValue: "" + ) + string( + name: 'PATCH_NAME', + description: "Allow you to specify a custom patch name for the .patch file.", + defaultValue: "" + ) + } + stages { + stage("patch-make") { + steps { + sh("bash ${Constants.SCRIPTS_DIR}/patch-make.sh") + } + } + } + post { + cleanup { + cleanupPartJob() + } + } +} diff --git a/pipelines/parts/publish-patch.Jenkinsfile b/pipelines/parts/publish-patch.Jenkinsfile new file mode 100644 index 0000000..3a1919f --- /dev/null +++ b/pipelines/parts/publish-patch.Jenkinsfile @@ -0,0 +1,66 @@ +// vim: syn=groovy + +// +// Copyright (c) 2024 Wind River Systems, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// + +library "common@${params.JENKINS_SCRIPTS_BRANCH}" + +setBuildDescr() + +pipeline { + agent any + options { + timestamps() + } + parameters { + string( + name: 'MASTER_JOB_NAME' + ) + string( + name: 'MASTER_BUILD_NUMBER' + ) + string( + name: 'JENKINS_SCRIPTS_BRANCH' + ) + string( + name: 'BUILD_HOME' + ) + string( + name: 'SW_VERSION' + ) + string( + name: 'PATCH_NUM' + ) + string( + name: 'TIMESTAMP' + ) + booleanParam( + name: 'SHELL_XTRACE' + ) + booleanParam( + name: 'DRY_RUN' + ) + string( + name: 'PATCH_PREVIOUS' + ) + string( + name: 'PATCH_TAG' + ) + } + + stages { + stage ("publish-patch") { + steps { + sh("bash ${Constants.SCRIPTS_DIR}/publish-patch.sh") + } + } + } + post { + cleanup { + cleanupPartJob() + } + } +} \ No newline at end of file diff --git a/pipelines/patch-monolithic.Jenkinsfile b/pipelines/patch-monolithic.Jenkinsfile new file mode 100644 index 0000000..f299a13 --- /dev/null +++ b/pipelines/patch-monolithic.Jenkinsfile @@ -0,0 +1,334 @@ +// vim: syn=groovy + +// +// Copyright (c) 2024 Wind River Systems, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// + +library "common@${params.JENKINS_SCRIPTS_BRANCH}" + +PROPS = null +IMG_PARAMS = null +IMAGES_FAILED = false + +def parseProps(text) { + def x = {} + for (line in text.split (/\n+/)) { + if (line.matches (/\s*(?:#.*)?#/)) { + continue + } + parts = line.split ("=", 2) + key = parts[0] + value = parts[1] + x."${key}" = value + } + return x +} + +def loadEnv() { + def data = {} + data.NEED_BUILD = false + data.SUPPRESS_DOCKER_IMAGE_BUILD_ERRORS = true + ws(params.BUILD_HOME) { + if (fileExists ("NEED_BUILD")) { + data.NEED_BUILD = true + } + } + final String configText = sh (script: "${Constants.SCRIPTS_DIR}/print-config.sh", returnStdout: true) + final props = parseProps (configText) + data.BUILD_OUTPUT_HOME_URL = props.BUILD_OUTPUT_HOME_URL + data.PUBLISH_URL = props.PUBLISH_URL + PROPS = data + return data.NEED_BUILD +} + +def partJobName(name) { + final String folder = env.JOB_NAME.replaceAll (/(.*\/).+$/, '$1'); + if (folder == env.JOB_NAME) { + error "This job must be in a Jenkins folder!" + } + return "/" + folder + "parts/" + name +} + +def runPart(name, params = []) { + // Tell Jenkins to checkout the same commit of the sub-job's Jenkinsfile, + // as the current builds' Jenkinsfile's commit. + final gitRef = string (name: 'JENKINS_SCRIPTS_BRANCH', value: env.GIT_COMMIT) + build job: partJobName (name), parameters: copyCurrentParams() + [ gitRef ] + params +} + +def printBuildFooter() { + if (PROPS) { + String msg = "" + msg += "\n" + msg += "========================================\n" + msg += "\n" + if (PROPS.NEED_BUILD) { + msg += "Build output: ${PROPS.BUILD_OUTPUT_HOME_URL}\n" + if (PROPS.PUBLISH_URL) { + msg += "Publish output: ${PROPS.PUBLISH_URL}\n" + } + if (IMAGES_FAILED) { + msg += "\n" + msg += "WARNING:\n" + msg += "WARNING: docker images build attempted, but failed!\n" + msg += "WARNING: see log output above\n" + msg += "WARNING:\n" + } + } + else { + echo "*** NO CHANGES - BUILD NOT REQUIRED" + } + msg += "\n" + msg += "========================================\n" + msg += "\n" + echo (msg) + } +} + +pipeline { + agent any + options { + timestamps() + } + parameters { + string( + name: 'MASTER_JOB_NAME', + description: 'Name of the job that has trigger this pipeline.' + ) + string( + name: 'MASTER_BUILD_NUMBER', + description: 'Number of the job that has trigger this pipeline.' + ) + string( + name: 'BUILD_HOME', + description: 'Full path to where the build system is located' + ) + string( + name: 'TIMESTAMP', + description: 'Time when the build is started' + ) + string( + name: 'PUBLISH_TIMESTAMP', + description: 'Time when the build is published' + ) + booleanParam( + name: 'REBUILD_BUILDER_IMAGES' + ) + booleanParam( + name: 'BUILDER_USE_DOCKER_CACHE' + ) + booleanParam( + name: 'BUILD_PACKAGES', + description: "Enable stage 'Build packages'" + ) + booleanParam( + name: 'REFRESH_SOURCE' + ) + booleanParam( + name: 'PKG_REUSE' + ) + booleanParam( + name: 'BUILD_ISO' + ) + booleanParam( + name: 'DRY_RUN' + ) + booleanParam( + name: 'SHELL_XTRACE' + ) + booleanParam( + name: 'CLEAN_PACKAGES' + ) + booleanParam( + name: 'CLEAN_ISO' + ) + booleanParam( + name: 'CLEAN_REPOMGR' + ) + booleanParam( + name: 'CLEAN_DOWNLOADS' + ) + booleanParam( + name: 'CLEAN_DOCKER' + ) + booleanParam( + name: 'FORCE_BUILD' + ) + booleanParam( + name: 'USE_DOCKER_CACHE', + ) + string( + name: 'JENKINS_SCRIPTS_BRANCH' + ) + string( + name: 'REMOTE_SERVER', + description: 'Address of the remote server who holds the base build system that the patch will be built on top of.' + + 'Used together with REMOTE_BUILD_HOME' + + 'e.g.: http://example-build-server.com:8080' + ) + string( + name: 'REMOTE_BUILD_HOME', + description: 'Full path from the remote server who holds the base build system that the patch will be built on top of.' + + 'Used together with REMOTE_SERVER' + + 'e.g.: /localdisk/loadbuild/starlingx-master/latest_build' + ) + string( + name: 'SW_VERSION', + description: 'Version of the build being used. e.g., XX.YY' + ) + string( + name: 'PATCH_NUM', + description: 'Number of the patch, e.g., 1. To be used together with SW_VERSION like this: XX.YY.PP.' + ) + string( + name: 'CUSTOM_PATCH_RECIPE', + description: "Allow you to specify the path to a custom patch recipe to be used when creating the patch.", + defaultValue: "" + ) + string( + name: 'PATCH_NAME', + description: "Allow you to specify a custom patch name for the .patch file.", + defaultValue: "" + ) + string( + name: 'PATCH_PREVIOUS', + description: "Git ref for the previous created patch. It can be a git tag name.", + defaultValue: "" + ) + string( + name: 'PATCH_TAG', + description: "Git ref to be used as HEAD", + defaultValue: "HEAD" + ) + string( + name: 'STX_SHARED_SOURCE', + description: "Full HTTPS address of the deb-local-source repository from where we pull the packages to be re-used", + defaultValue: "" + ) + string( + name: 'STX_SHARED_REPO', + description: "Full HTTPS address of the deb-local-build repository from where we pull the packages to be re-used", + defaultValue: "" + ) + text( + name: 'PATCH_LIST', + defaultValue: '-', + description: '''\ +
List of Gerrit URLs to apply before running the build, one per line "[PATH] URL REF", eg:
+
+ https://review.opendev.org/starlingx/config refs/changes/71/859571/4
+ https://review.opendev.org/starlingx/stx-puppet refs/changes/75/859575/1
+ https://review.opendev.org/starlingx/tools refs/changes/76/859576/2
+
+or with paths relative to repo root:
+
+ cgcs-root/stx/config https://review.opendev.org/starlingx/config refs/changes/71/859571/4
+ cgcs-root/stx/stx-puppet https://review.opendev.org/starlingx/stx-puppet refs/changes/75/859575/1
+ stx-tools https://review.opendev.org/starlingx/tools refs/changes/76/859576/2
+
+ '''
+ )
+ }
+ stages {
+ stage('Start Environment') {
+ steps {
+ script {
+ // Initialize BUILD_HOME, create build.conf & stx.conf
+ runPart("init-env")
+ // Update source tree
+ runPart("clone-source")
+ // create BUILD & stx.conf
+ runPart("configure-build")
+ // Stop containers before updating source treee
+ runPart("stop-containers")
+ // Create changelog, LAST_COMMITS, NEED_BUILD etc
+ runPart("create-changelog")
+ // Is build required?
+ if (!loadEnv()) {
+ println "*** NO CHANGES, BUILD NOT REQUIRED ***"
+ }
+ }
+ }
+ }
+ stage('Build') {
+ when { expression { PROPS.NEED_BUILD } }
+ stages {
+ stage('Prepare:CLEAN') {
+ steps {
+ // Delete or keep packages, aptly state, etc depending on build params
+ runPart("clean-build")
+ // start containers
+ runPart("start-containers")
+ // login to docker early to catch login errors
+ runPart ("docker-login")
+ }
+ }
+ stage('Prepare:DOWNLOAD') {
+ steps {
+ // populate mirrors
+ runPart("download-prerequisites")
+ }
+ }
+ stage('PACKAGES') {
+ when {
+ expression {
+ params.BUILD_PACKAGES
+ }
+ }
+ steps {
+ // build and publish packages
+ runPart("build-packages",[ booleanParam (name: 'PATCH_BUILD', value: true) ])
+ runPart("publish-packages")
+ }
+ }
+ stage('PATCH:prepare') {
+ steps {
+ // pull ostree that we will use as base
+ runPart("ostree-pull")
+ }
+ }
+ stage('PATCH:make') {
+ steps {
+ // create the patch
+ runPart("patch-make")
+ // create the pre-patched iso
+ runPart("patch-iso")
+ // publish patches and pre-patched iso
+ runPart("publish-patch")
+ }
+ }
+ stage('PATCH:export') {
+ steps {
+ runPart("build-export-dir")
+ }
+ }
+ }
+ post {
+ always {
+ echo "build result: ${currentBuild.result}"
+ runPart("stop-containers")
+ // archive anything we may have missed
+ runPart("archive-misc")
+ // save this job's build number on disk (for publish-logs)
+ saveCurrentJenkinsBuildInfo()
+ }
+ success {
+ // copy LAST_COMMITS to archive root & update the "latest_build" symlink in
+ // both archive and publish roots
+ sh("BUILD_STATUS=success ${Constants.SCRIPTS_DIR}/create-latest-symlinks.sh")
+ // Print archive & publish URLs
+ printBuildFooter()
+ // publish this job's Jenkins log
+ runPart("publish-logs")
+ }
+ unsuccessful {
+ sh("BUILD_STATUS=fail ${Constants.SCRIPTS_DIR}/create-latest-symlinks.sh")
+ // publish this job's Jenkins log
+ runPart("publish-logs")
+ }
+ }
+ }
+ } // stages
+}
diff --git a/scripts/build-packages.sh b/scripts/build-packages.sh
index ba89270..a22c4d5 100755
--- a/scripts/build-packages.sh
+++ b/scripts/build-packages.sh
@@ -17,7 +17,8 @@ require_job_env BUILD_ISO
declare_job_env BUILD_PACKAGES_PARALLEL_JOBS
require_job_env PKG_REUSE
declare_job_env STX_SHARED_SOURCE
-declare_job_env STX_SHARED_SOURCE
+declare_job_env STX_SHARED_REPO
+declare_job_env PATCH_BUILD
load_build_env
@@ -53,7 +54,19 @@ while [[ $count -lt $BUILD_PACKAGES_ITERATIONS ]] ; do
if $PKG_REUSE && [[ $count -eq 0 ]] ; then
extra_args+=("--clean")
- extra_args+=("--reuse")
+
+ # When building packages with "--reuse" flag, we re-use almost all packages
+ # except the ones in debian-mirror-tools/config/debian/common/never_reuse.lst
+ # the packages listed there are built, not re used.
+ # When building packages with "--reuse_maximum" flag, we re-use every packages
+ # ignoring the debian-mirror-tools/config/debian/common/never_reuse.lst
+ # this is useful when building a patch because we don't want to built anything
+ # that we didn't update in the patch.
+ if $PATCH_BUILD; then
+ extra_args+=("--reuse_maximum")
+ else
+ extra_args+=("--reuse")
+ fi
if [[ -n $STX_SHARED_SOURCE ]]; then
environment_args+=("STX_SHARED_SOURCE=$STX_SHARED_SOURCE")
diff --git a/scripts/lib/job_utils.sh b/scripts/lib/job_utils.sh
index 56c0aef..c420daa 100644
--- a/scripts/lib/job_utils.sh
+++ b/scripts/lib/job_utils.sh
@@ -276,15 +276,24 @@ check_all_pods_running() {
fi
}
-# Usage: stx_docker_cmd [--dry-run] SHELL_SNIPPET
+# Usage: stx_docker_cmd [--dry-run] [--container=NAME] SHELL_SNIPPET
stx_docker_cmd() {
local dry_run=0
+ local container
if [[ "$1" == "--dry-run" ]] ; then
dry_run=1
shift
fi
+ if [[ "${1}" = "--container"* ]] ; then
+ container="${1#*=}"
+ shift 1
+ fi
if [[ "$QUIET" != "true" ]] ; then
- echo ">>> running builder pod command:" >&2
+ if [[ -n "$container" ]]; then
+ echo ">>> running $container pod command:" >&2
+ else
+ echo ">>> running builder pod command:" >&2
+ fi
echo "$1" | sed -r 's/^/\t/' >&2
fi
if [[ "$dry_run" -ne 1 ]] ; then
@@ -292,6 +301,9 @@ stx_docker_cmd() {
if __started_by_jenkins ; then
args+=("--no-tty")
fi
+ if [[ -n "$container" ]]; then
+ args+=("--container" $container)
+ fi
stx -d shell "${args[@]}" -c "$1"
fi
}
@@ -941,6 +953,19 @@ __print_deb_release() {
__print_deb_release_checksums "SHA512" "sha512sum" || return 1
}
+# Usage: ostree_cmd [--dry-run] OSTREE_REPO CMD
+ostree_cmd() {
+ # ostree commands will be executed in the LAT container
+ local dry_run_arg
+ if [[ "$1" == "--dry-run" ]] ; then
+ dry_run_arg=$1
+ shift
+ fi
+ local ostree_repo=$1
+ local command=$2
+ stx_docker_cmd ${dry_run_arg} "--container=lat" "ostree --repo=/localdisk/deploy/${ostree_repo} ${command}"
+}
+
if [[ "${SHELL_XTRACE,,}" == "true" || "${SHELL_XTRACE}" == "1" ]] ; then
set -x
export SHELL_XTRACE=true
diff --git a/scripts/ostree-pull.sh b/scripts/ostree-pull.sh
new file mode 100644
index 0000000..9d3230d
--- /dev/null
+++ b/scripts/ostree-pull.sh
@@ -0,0 +1,46 @@
+#!/bin/bash
+
+#
+# Copyright (c) 2024 Wind River Systems, Inc.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+set -e
+source $(dirname "$0")/lib/job_utils.sh
+
+require_job_env BUILD_HOME
+require_job_env REMOTE_BUILD_HOME
+require_job_env REMOTE_SERVER
+
+load_build_env
+
+# run the patch prepare step
+# Prepare the folder to receive the ostree repository
+DEPLOY_DIR="${BUILD_HOME}/localdisk/deploy"
+OSTREE_REPO="ostree_repo"
+REMOTE_BUILD_OSTREE=${REMOTE_BUILD_HOME}/localdisk/deploy/${OSTREE_REPO}
+
+mkdir -p "${DEPLOY_DIR}" && cd "$_"
+
+# Init ostree repo
+ostree_cmd ${DRY_RUN_ARG} ${OSTREE_REPO} "init --mode=archive-z2"
+
+notice "Pulling content from remote ostree ${REMOTE_SERVER}/${REMOTE_BUILD_HOME}"
+# Add build as remote
+ostree_cmd ${DRY_RUN_ARG} ${OSTREE_REPO} "remote add --set=gpg-verify=false deb-build \
+ ${REMOTE_SERVER}/${REMOTE_BUILD_OSTREE} starlingx"
+# Pull only the latest commit
+ostree_cmd ${DRY_RUN_ARG} ${OSTREE_REPO} "pull --depth=0 --mirror deb-build starlingx"
+# Update ostree summary
+ostree_cmd ${DRY_RUN_ARG} ${OSTREE_REPO} "summary --update"
+notice "Pull done, ostree commit log"
+ostree_cmd ${DRY_RUN_ARG} ${OSTREE_REPO} "log starlingx"
+
+if ! $DRY_RUN ; then
+ notice "Fixing ostree_repo permissions"
+ OSTREE_USER=`stat -c %u ${REMOTE_BUILD_OSTREE}`
+ OSTREE_GROUP=`stat -c %g ${REMOTE_BUILD_OSTREE}`
+ notice "Changing ostree permission to match source, user: ${OSTREE_USER}, group: ${OSTREE_GROUP}"
+ docker run --rm --volume "${BUILD_HOME}:${BUILD_HOME}" debian:bullseye chown -R "${OSTREE_USER}:${OSTREE_GROUP}" "${DEPLOY_DIR}/${OSTREE_REPO}"
+fi
diff --git a/scripts/patch-iso.sh b/scripts/patch-iso.sh
new file mode 100644
index 0000000..519a71d
--- /dev/null
+++ b/scripts/patch-iso.sh
@@ -0,0 +1,53 @@
+#!/bin/bash
+
+#
+# Copyright (c) 2024 Wind River Systems, Inc.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+set -e
+source $(dirname "$0")/lib/job_utils.sh
+
+require_job_env BUILD_HOME
+require_job_env SW_VERSION
+require_job_env PATCH_NUM
+
+load_build_env
+
+require_job_env SIGNING_SERVER
+require_job_env SIGNING_USER
+require_job_env REMOTE_BUILD_HOME
+require_job_env REMOTE_SERVER
+require_job_env BUILD_HOME
+
+declare_job_env MY_REPO "$REPO_ROOT/cgcs-root"
+declare_job_env PATCH_PREVIOUS_NAME "${PATCH_PREVIOUS:1}"
+
+PREPATCHED_NAME="prepatched-iso-${SW_VERSION}.${PATCH_NUM}.iso"
+DEFAULT_PATCH_OUTPUT="/localdisk/deploy/patch_output/"
+
+# Collect every patches from the default patch output path
+FULL_PATH_PATCH_OUTPUT="${BUILD_HOME}${DEFAULT_PATCH_OUTPUT}"
+PATCHES_LIST=$(find $FULL_PATH_PATCH_OUTPUT -type f -name "*.patch" -printf '%f\n' | sort)
+# Prepare the patches list to be used in lat container, adding -p prefix and the path
+PATCHES_FLAG=$(printf '%s\n' ${PATCHES_LIST[*]} | xargs -I {} echo "-p ${DEFAULT_PATCH_OUTPUT}{}")
+
+# We collect the iso from the remote build home to be used as base
+REMOTE_ISO="${REMOTE_SERVER}/${REMOTE_BUILD_HOME}/localdisk/deploy/starlingx-intel-x86-64-cd.iso"
+curl -L ${REMOTE_ISO} --output ${BUILD_HOME}/localdisk/starlingx-base.iso
+ISO_BASE="/localdisk/starlingx-base.iso"
+
+# Create pre-patched ISO
+stx_docker_cmd ${DRY_RUN_ARG} "--container=lat" "create-prepatched-iso -i ${ISO_BASE} \
+ ${PATCHES_FLAG[*]} -o /localdisk/deploy/${PREPATCHED_NAME}"
+
+if ! $DRY_RUN ; then
+ (
+ export MY_WORKSPACE=$WORKSPACE_ROOT
+ export PATH=$MY_REPO/build-tools:$PATH:/usr/local/bin
+ PREPATCHED_PATH="${BUILD_HOME}/localdisk/deploy/${PREPATCHED_NAME}"
+ maybe_run sign_iso_formal.sh "${PREPATCHED_PATH}" || die "failed to sign ISO"
+ )
+ notice "Formal signing done"
+fi
diff --git a/scripts/patch-make.sh b/scripts/patch-make.sh
new file mode 100644
index 0000000..aef9ef7
--- /dev/null
+++ b/scripts/patch-make.sh
@@ -0,0 +1,34 @@
+#!/bin/bash
+
+#
+# Copyright (c) 2024 Wind River Systems, Inc.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+set -e
+source $(dirname "$0")/lib/job_utils.sh
+
+require_job_env BUILD_HOME
+require_job_env SW_VERSION
+require_job_env PATCH_NUM
+
+load_build_env
+
+# RECIPE_PATH indicate a path inside the builder container
+RECIPE_PATH="\${MY_REPO_ROOT_DIR}/cgcs-root/patch-xml/${SW_VERSION}/${SW_VERSION}.${PATCH_NUM}.xml"
+
+# Check if we are using a custom patch recipe
+if [ ! -z "${CUSTOM_PATCH_RECIPE}" ]; then
+ RECIPE_PATH="${CUSTOM_PATCH_RECIPE}"
+fi
+
+# If custom name is provided add it to the parameters
+EXTRA_ARGS=()
+if [ ! -z "${PATCH_NAME}" ]; then
+ EXTRA_ARGS+=("--name ${PATCH_NAME}")
+fi
+
+# Build the patch
+stx_docker_cmd $DRY_RUN_ARG "patch-builder --recipe ${RECIPE_PATH} ${EXTRA_ARGS[*]}"
+
diff --git a/scripts/publish-patch.sh b/scripts/publish-patch.sh
new file mode 100644
index 0000000..dd33bb3
--- /dev/null
+++ b/scripts/publish-patch.sh
@@ -0,0 +1,73 @@
+#!/bin/bash
+
+#
+# Copyright (c) 2024 Wind River Systems, Inc.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+source $(dirname "$0")/lib/job_utils.sh || exit 1
+source $(dirname "$0")/lib/publish_utils.sh || exit 1
+
+require_job_env BUILD_HOME
+require_job_env SW_VERSION
+require_job_env PATCH_NUM
+require_job_env PATCH_PREVIOUS
+require_job_env PATCH_TAG
+
+load_build_env || exit 1
+
+require_job_env PUBLISH_ROOT
+
+if $DRY_RUN ; then
+ bail "DRY_RUN=false is not supported, bailing out"
+fi
+
+# Create temporary folder
+TEMP_DIR="$BUILD_OUTPUT_HOME/tmp"
+mkdir -p "$TEMP_DIR" || exit 1
+
+# Add the file checksum to the list
+checksum_files_list_file="$TEMP_DIR/published_patch_checksum_files"
+find_checksum_files "${PUBLISH_SUBDIR}/outputs/patch" \
+ >"$checksum_files_list_file" || exit 1
+
+dst_dir="${PUBLISH_DIR}/outputs/patch"
+checksum_file="$dst_dir/$CHECKSUMS_FILENAME"
+
+src_dir="$BUILD_OUTPUT_HOME/localdisk/deploy"
+abs_src_dir="$(readlink -e "$src_dir")" || continue
+
+# Clean destination folder
+rm -rf --one-file-system "$dst_dir" || exit 1
+mkdir -p "$dst_dir" || exit 1
+
+# Go to every repo and get the new commits we have added to make the patch
+# since the $PATCH_PREVIOUS
+(
+ cd "${REPO_ROOT}"
+ (
+ set -e
+ repo forall -c 'echo $REPO_PATH'
+ ) | while read gitdir ; do
+ cd ${REPO_ROOT}/$gitdir
+ commit="$(git tag -l ${PATCH_PREVIOUS} | grep -q ${PATCH_PREVIOUS} && git log --pretty=format:'%ad %an %H %s' --date=short ${PATCH_PREVIOUS}.. | grep -v '.gitreview')"
+ if [ "$commit" ]; then printf "%b" "$gitdir \n$commit \n\n"; fi
+ done
+) > ${dst_dir}/COMMITS_${PATCH_PREVIOUS}_to_${PATCH_TAG}
+
+# Setup the tag symlink
+link_target=$(basename "$BUILD_OUTPUT_HOME")
+ln -sfn "$link_target" "${PUBLISH_ROOT}/${PATCH_TAG}"
+
+# Publish patch and pre-patched ISO
+DEPLOY_DIR=$BUILD_HOME/localdisk/deploy
+patch_file=($DEPLOY_DIR/patch_output/*.patch)
+iso_file=$DEPLOY_DIR/prepatched-iso-${SW_VERSION}.${PATCH_NUM}.iso
+iso_sig=$DEPLOY_DIR/prepatched-iso-${SW_VERSION}.${PATCH_NUM}.sig
+
+declare -a file_list=( $patch_file $iso_file $iso_sig )
+for f in ${file_list[@]}; do
+ publish_file "$f" "$dst_dir" "$checksum_files_list_file" >>"$checksum_file" || exit 1
+done
+check_pipe_status || exit 1