diff --git a/cmake/ecbuild_bundle.cmake b/cmake/ecbuild_bundle.cmake index c68e5213..a6d0535d 100644 --- a/cmake/ecbuild_bundle.cmake +++ b/cmake/ecbuild_bundle.cmake @@ -66,7 +66,8 @@ endmacro() # [ BRANCH | TAG ] # [ UPDATE | NOREMOTE ] # [ MANUAL ] -# [ RECURSIVE ] ) +# [ RECURSIVE ] +# [ SHALLOW ] ) # # Options # ------- @@ -101,6 +102,9 @@ endmacro() # RECURSIVE : optional # Do a recursive fetch or update # +# SHALLOW : optional +# Do a shallow clone (depth=1). See ``ecbuild_git`` for details. +# # Usage # ----- # diff --git a/cmake/ecbuild_git.cmake b/cmake/ecbuild_git.cmake index 13763c17..6a35113d 100644 --- a/cmake/ecbuild_git.cmake +++ b/cmake/ecbuild_git.cmake @@ -28,7 +28,8 @@ endif() # [ BRANCH | TAG ] # [ UPDATE | NOREMOTE ] # [ MANUAL ] -# [ RECURSIVE ] ) +# [ RECURSIVE ] +# [ SHALLOW ] ) # # Options # ------- @@ -60,11 +61,17 @@ endif() # RECURSIVE : optional # Do a recursive fetch or update # +# SHALLOW : optional +# Do a shallow clone (``--depth 1``) on initial checkout. +# When combined with RECURSIVE, submodules are also fetched at depth 1. +# Cannot be combined with TAG when it's a commit ID (SHA). +# SHALLOW is not switchable and will fail if UPDATE is requested on an existing shallow clone. +# ############################################################################## function( ecbuild_git ) - set( options UPDATE NOREMOTE MANUAL RECURSIVE ) + set( options UPDATE NOREMOTE MANUAL RECURSIVE SHALLOW ) set( single_value_args PROJECT DIR URL TAG BRANCH ) set( multi_value_args ) cmake_parse_arguments( _PAR "${options}" "${single_value_args}" "${multi_value_args}" ${_FIRST_ARG} ${ARGN} ) @@ -77,13 +84,25 @@ function( ecbuild_git ) ecbuild_critical( "Cannot pass both NOREMOTE and UPDATE in macro ecbuild_git" ) endif() + if( _PAR_UPDATE AND _PAR_SHALLOW ) + ecbuild_warn("UPDATE and SHALLOW conflict — shallow clones aren't switchable; UPDATE may be ignored or fail.") + endif() + if(_PAR_UNPARSED_ARGUMENTS) ecbuild_critical("Unknown keywords given to ecbuild_git(): \"${_PAR_UNPARSED_ARGUMENTS}\"") endif() + if( _PAR_SHALLOW AND DEFINED _PAR_TAG ) + string(LENGTH "${_PAR_TAG}" _tag_len) + if( _tag_len GREATER_EQUAL 7 AND _tag_len LESS_EQUAL 40 AND _PAR_TAG MATCHES "^[0-9a-fA-F]+$" ) + ecbuild_critical("SHALLOW cloning cannot be used: TAG (${_PAR_TAG}) looks like a commit ID (SHA)!") + endif() + endif() + if( ECBUILD_GIT ) set( _needs_switch 0 ) + set( _created_repo 0 ) get_filename_component( ABS_PAR_DIR "${_PAR_DIR}" ABSOLUTE ) get_filename_component( PARENT_DIR "${_PAR_DIR}/.." ABSOLUTE ) @@ -98,15 +117,26 @@ function( ecbuild_git ) if( NOT EXISTS "${_PAR_DIR}" ) + set( _clone_args ) + if( _PAR_SHALLOW ) + list( APPEND _clone_args "--depth" "1" ) + if( DEFINED _PAR_BRANCH ) + list( APPEND _clone_args "--branch" "${_PAR_BRANCH}" ) + elseif( DEFINED _PAR_TAG ) + list( APPEND _clone_args "--branch" "${_PAR_TAG}" ) + endif() + endif() + ecbuild_info( "Cloning ${_PAR_PROJECT} from ${_PAR_URL} into ${_PAR_DIR}...") execute_process( - COMMAND ${GIT_EXECUTABLE} "clone" ${_PAR_URL} ${clone_args} ${_PAR_DIR} "-q" + COMMAND ${GIT_EXECUTABLE} "clone" ${_PAR_URL} ${_clone_args} ${_PAR_DIR} "-q" RESULT_VARIABLE nok ERROR_VARIABLE error WORKING_DIRECTORY "${PARENT_DIR}") if(nok) - ecbuild_critical("${_PAR_DIR} git clone failed:\n ${GIT_EXECUTABLE} clone ${_PAR_URL} ${clone_args} ${_PAR_DIR} -q\n ${error}\n") + ecbuild_critical("${_PAR_DIR} git clone failed:\n ${GIT_EXECUTABLE} clone ${_PAR_URL} ${_clone_args} ${_PAR_DIR} -q\n ${error}\n") endif() ecbuild_info( "${_PAR_DIR} retrieved.") + set( _created_repo 1 ) set( _needs_switch 1 ) endif() @@ -164,7 +194,11 @@ function( ecbuild_git ) set( _needs_switch 1 ) endif() - if( DEFINED _PAR_BRANCH AND _PAR_UPDATE AND NOT _PAR_NOREMOTE ) + if( _PAR_SHALLOW AND _needs_switch AND NOT _created_repo ) + ecbuild_critical("SHALLOW repository ${_PAR_DIR} is not switchable.") + endif() + + if( DEFINED _PAR_BRANCH AND _PAR_UPDATE AND NOT _PAR_NOREMOTE AND NOT _PAR_SHALLOW ) add_custom_target( git_update_${_PAR_PROJECT} COMMAND "${GIT_EXECUTABLE}" pull -q @@ -189,7 +223,9 @@ function( ecbuild_git ) # fetching latest tags and branches - if( NOT _PAR_NOREMOTE ) + if( _PAR_SHALLOW ) + ecbuild_info("${_PAR_DIR} is SHALLOW : Skipping fetch") + elseif( NOT _PAR_NOREMOTE ) ecbuild_info("git fetch --all @ ${ABS_PAR_DIR}") execute_process( COMMAND "${GIT_EXECUTABLE}" fetch --all -q @@ -221,10 +257,11 @@ function( ecbuild_git ) ecbuild_critical("git checkout ${_gitref} on ${_PAR_DIR} failed:\n ${GIT_EXECUTABLE} checkout -q ${_gitref}\n ${error}") endif() - if( DEFINED _PAR_BRANCH AND _PAR_UPDATE ) ############################################################################# + if( DEFINED _PAR_BRANCH AND _PAR_UPDATE AND NOT _PAR_SHALLOW ) ############################# # Use git pull --ff-only, we WANT this to fail on upstream rebase and # we DON'T want merge commits here! + # Skipped for SHALLOW clones to avoid deepening history. execute_process( COMMAND "${GIT_EXECUTABLE}" pull -q --ff-only RESULT_VARIABLE nok ERROR_VARIABLE error WORKING_DIRECTORY "${ABS_PAR_DIR}") @@ -235,8 +272,12 @@ function( ecbuild_git ) endif() #################################################################################### if( _PAR_RECURSIVE ) - ecbuild_info("git submodule --quiet update --init --recursive @ ${ABS_PAR_DIR}") - execute_process( COMMAND "${GIT_EXECUTABLE}" submodule --quiet update --init --recursive + set( _submodule_args submodule --quiet update --init --recursive ) + if( _PAR_SHALLOW ) + list( APPEND _submodule_args "--depth" "1" ) + endif() + ecbuild_info("git ${_submodule_args} @ ${ABS_PAR_DIR}") + execute_process( COMMAND "${GIT_EXECUTABLE}" ${_submodule_args} RESULT_VARIABLE nok ERROR_VARIABLE error WORKING_DIRECTORY "${ABS_PAR_DIR}") if(nok) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d6b82683..9cecbc06 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -7,6 +7,7 @@ add_subdirectory( ecbuild_remove_fortran_flags ) # ECBUILD-484 add_subdirectory( ecbuild_add_option ) add_subdirectory( ecbuild_add_option_multiproject_defaults ) add_subdirectory( ecbuild_add_flags ) +add_subdirectory( ecbuild_bundle_shallow ) add_subdirectory( ecbuild_find_package ) add_subdirectory( ecbuild_find_python ) add_subdirectory( find_ecbuild ) diff --git a/tests/ecbuild_bundle_shallow/CMakeLists.txt b/tests/ecbuild_bundle_shallow/CMakeLists.txt new file mode 100644 index 00000000..74a82e34 --- /dev/null +++ b/tests/ecbuild_bundle_shallow/CMakeLists.txt @@ -0,0 +1,10 @@ + +ecbuild_add_test( + TARGET test_ecbuild_bundle_shallow + TYPE SCRIPT + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/run-test.sh + ENVIRONMENT + CMAKE_SOURCE_DIR=${CMAKE_SOURCE_DIR} + CMAKE_CURRENT_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR} + CMAKE_CURRENT_BINARY_DIR=${CMAKE_CURRENT_BINARY_DIR} +) diff --git a/tests/ecbuild_bundle_shallow/run-test.sh b/tests/ecbuild_bundle_shallow/run-test.sh new file mode 100755 index 00000000..12d415e1 --- /dev/null +++ b/tests/ecbuild_bundle_shallow/run-test.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash + +set -e +set -x + +HERE="$( cd $( dirname "${BASH_SOURCE[0]}" ) && pwd -P )" + +ECBUILD_PATH=${CMAKE_SOURCE_DIR}/bin +SOURCE_TEST_DIR=${CMAKE_CURRENT_SOURCE_DIR} +BINARY_TEST_DIR=${CMAKE_CURRENT_BINARY_DIR} + +# Git 2.38.1+ blocks local file:// submodule clones by default. +# Allow them for the duration of this script only. +export GIT_CONFIG_COUNT=1 +export GIT_CONFIG_KEY_0=protocol.file.allow +export GIT_CONFIG_VALUE_0=always + +# Add ecbuild to path +export PATH=$ECBUILD_PATH:$PATH + +# ---- cleanup ----------------------------------------- +rm -rf "${BINARY_TEST_DIR}/workspace" + +# ---- setup umbrella project (with submodules) -------- +cd "${BINARY_TEST_DIR}" +bash "${SOURCE_TEST_DIR}/setup-umbrella-project.sh" \ + "${BINARY_TEST_DIR}/workspace/projects" + +# ---- setup umbrella bundle --------------------------- +cd "${BINARY_TEST_DIR}" +bash "${SOURCE_TEST_DIR}/setup-umbrella-bundle.sh" \ + "${BINARY_TEST_DIR}/workspace/bundle" \ + "${BINARY_TEST_DIR}/workspace/projects/umbrella" + +# ---- configure umbrella bundle ----------------------- +cd ${BINARY_TEST_DIR}/workspace +mkdir build +cd build +ecbuild --prefix=$(pwd)/install -- ../bundle + +# ---- check shallowness (umbrella) -------------------- +cd ${BINARY_TEST_DIR}/workspace/bundle/umbrella + +if [[ "$(git rev-parse --is-shallow-repository)" == "true" ]]; then + echo "Submodule is shallow: PASS" + exit 0 +else + echo "Submodule is not shallow: FAIL" + exit 1 +fi + +# ---- check shallowness (umbrella submodules) --------- +for sub in alpha beta gamma; do + cd "${BINARY_TEST_DIR}/workspace/bundle/umbrella/${sub}" + + if [[ "$(git rev-parse --is-shallow-repository)" == "true" ]]; then + echo "Submodule $sub is shallow: PASS" + else + echo "Submodule $sub is not shallow: FAIL" + exit 1 + fi +done diff --git a/tests/ecbuild_bundle_shallow/setup-umbrella-bundle.sh b/tests/ecbuild_bundle_shallow/setup-umbrella-bundle.sh new file mode 100644 index 00000000..3902ab82 --- /dev/null +++ b/tests/ecbuild_bundle_shallow/setup-umbrella-bundle.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash + +set -e +set -u +set -o pipefail + +# ───────────────────────────────────────────── +# Configuration +# ───────────────────────────────────────────── +BASE_DIR="${1:-$(pwd)/bundle}" +UMBRELLA_DIR="${2:-$(pwd)/projects/umbrella}" + +echo "==> Creating bundle workspace at: $BASE_DIR" +mkdir -p "$BASE_DIR" +cd "$BASE_DIR" + +# ───────────────────────────────────────────── +# Create the umbrella bundle +# ───────────────────────────────────────────── + +UMBRELLA_GIT="file://$(dirname "${UMBRELLA_DIR}")/../bare-repos/$(basename "${UMBRELLA_DIR}").git" + +cat > CMakeLists.txt < VERSION < Creating workspace at: $BASE_DIR" +mkdir -p "$BASE_DIR" +cd "$BASE_DIR" + +# ───────────────────────────────────────────── +# Helper: make a commit +# ───────────────────────────────────────────── +commit() { + git add -A + git commit -m "$1" +} + +# ───────────────────────────────────────────── +# Create the 3 dummy projects +# ───────────────────────────────────────────── +for proj in "${PROJECTS[@]}"; do + echo "" + echo "──> Setting up project: $proj" + mkdir -p "$proj" + cd "$proj" + git init -b main + git config user.email "demo@example.com" + git config user.name "Demo User" + + # Initial commit with README + cat > README.md < "src/$proj.sh" <> README.md < config.env < Setting up umbrella project: $UMBRELLA" +mkdir -p "$UMBRELLA" +cd "$UMBRELLA" +git init -b main +git config user.email "demo@example.com" +git config user.name "Demo User" + +# Initial README +cat > README.md < CMakeLists.txt </dev/null 2>&1 + git submodule add "file://$BARE_DIR/$proj.git" "$proj" +done +commit "Add submodules: alpha, beta, gamma" + +# A follow-up commit that records a pinned state +cat >> README.md </dev/null 2>&1