github.com/openshift/source-to-image@v1.4.1-0.20240516041539-bf52fc02204e/hack/common.sh (about)

     1  #!/bin/bash
     2  
     3  # This script provides common script functions for the hacks
     4  # Requires S2I_ROOT to be set
     5  
     6  set -o errexit
     7  set -o nounset
     8  set -o pipefail
     9  
    10  # The root of the build/dist directory
    11  S2I_ROOT=$(
    12    unset CDPATH
    13    sti_root=$(dirname "${BASH_SOURCE}")/..
    14    cd "${sti_root}"
    15    pwd
    16  )
    17  
    18  S2I_OUTPUT_SUBPATH="${S2I_OUTPUT_SUBPATH:-_output/local}"
    19  S2I_OUTPUT="${S2I_ROOT}/${S2I_OUTPUT_SUBPATH}"
    20  S2I_OUTPUT_BINPATH="${S2I_OUTPUT}/bin"
    21  S2I_OUTPUT_PKGDIR="${S2I_OUTPUT}/pkgdir"
    22  S2I_LOCAL_BINPATH="${S2I_OUTPUT}/go/bin"
    23  S2I_LOCAL_RELEASEPATH="${S2I_OUTPUT}/releases"
    24  RELEASE_LDFLAGS=${RELEASE_LDFLAGS:-""}
    25  
    26  
    27  readonly S2I_GO_PACKAGE=github.com/openshift/source-to-image
    28  readonly S2I_GOPATH="${S2I_OUTPUT}/go"
    29  
    30  readonly S2I_CROSS_COMPILE_PLATFORMS=(
    31    darwin/amd64
    32    darwin/arm64
    33    linux/386
    34    linux/amd64
    35    linux/arm64
    36    linux/ppc64le
    37    linux/s390x
    38    windows/amd64
    39    windows/arm64
    40  )
    41  readonly S2I_CROSS_COMPILE_TARGETS=(
    42    cmd/s2i
    43  )
    44  readonly S2I_CROSS_COMPILE_BINARIES=("${S2I_CROSS_COMPILE_TARGETS[@]##*/}")
    45  
    46  readonly S2I_ALL_TARGETS=(
    47    "${S2I_CROSS_COMPILE_TARGETS[@]}"
    48  )
    49  
    50  readonly S2I_BINARY_SYMLINKS=(
    51    sti
    52  )
    53  readonly S2I_BINARY_RELEASE_WINDOWS=(
    54    sti.exe
    55    s2i.exe
    56  )
    57  
    58  # s2i::build::binaries_from_targets take a list of build targets and return the
    59  # full go package to be built
    60  s2i::build::binaries_from_targets() {
    61    local target
    62    for target; do
    63      echo "${S2I_GO_PACKAGE}/${target}"
    64    done
    65  }
    66  
    67  # Asks golang what it thinks the host platform is.  The go tool chain does some
    68  # slightly different things when the target platform matches the host platform.
    69  s2i::build::host_platform() {
    70    echo "$(go env GOHOSTOS)/$(go env GOHOSTARCH)"
    71  }
    72  
    73  
    74  # Build binaries targets specified
    75  #
    76  # Input:
    77  #   $@ - targets and go flags.  If no targets are set then all binaries targets
    78  #     are built.
    79  #   S2I_BUILD_PLATFORMS - Incoming variable of targets to build for.  If unset
    80  #     then just the host architecture is built.
    81  s2i::build::build_binaries() {
    82    # Create a sub-shell so that we don't pollute the outer environment
    83    (
    84      # Check for `go` binary and set ${GOPATH}.
    85      s2i::build::setup_env
    86  
    87      # Fetch the version.
    88      local version_ldflags
    89      version_ldflags=$(s2i::build::ldflags)
    90  
    91      s2i::build::export_targets "$@"
    92  
    93      local platform
    94      for platform in "${platforms[@]}"; do
    95        s2i::build::set_platform_envs "${platform}"
    96        echo "++ Building go targets for ${platform}:" "${targets[@]}"
    97        CGO_ENABLED=0 go install "${goflags[@]:+${goflags[@]}}" \
    98            -pkgdir "${S2I_OUTPUT_PKGDIR}" \
    99            -ldflags "${version_ldflags} ${RELEASE_LDFLAGS}" \
   100            -mod vendor \
   101            "${binaries[@]}"
   102        s2i::build::unset_platform_envs "${platform}"
   103      done
   104    )
   105  }
   106  
   107  # Generates the set of target packages, binaries, and platforms to build for.
   108  # Accepts binaries via $@, and platforms via S2I_BUILD_PLATFORMS, or defaults to
   109  # the current platform.
   110  s2i::build::export_targets() {
   111    # Use eval to preserve embedded quoted strings.
   112    local goflags
   113    eval "goflags=(${S2I_GOFLAGS:-})"
   114  
   115    targets=()
   116    local arg
   117    for arg; do
   118      if [[ "${arg}" == -* ]]; then
   119        # Assume arguments starting with a dash are flags to pass to go.
   120        goflags+=("${arg}")
   121      else
   122        targets+=("${arg}")
   123      fi
   124    done
   125  
   126    if [[ ${#targets[@]} -eq 0 ]]; then
   127      targets=("${S2I_ALL_TARGETS[@]}")
   128    fi
   129  
   130    binaries=($(s2i::build::binaries_from_targets "${targets[@]}"))
   131  
   132    platforms=("${S2I_BUILD_PLATFORMS[@]:+${S2I_BUILD_PLATFORMS[@]}}")
   133    if [[ ${#platforms[@]} -eq 0 ]]; then
   134      platforms=("$(s2i::build::host_platform)")
   135    fi
   136  }
   137  
   138  
   139  # Takes the platform name ($1) and sets the appropriate golang env variables
   140  # for that platform.
   141  s2i::build::set_platform_envs() {
   142    [[ -n ${1-} ]] || {
   143      echo "!!! Internal error.  No platform set in s2i::build::set_platform_envs"
   144      exit 1
   145    }
   146  
   147    export GOOS=${platform%/*}
   148    export GOARCH=${platform##*/}
   149  }
   150  
   151  # Takes the platform name ($1) and resets the appropriate golang env variables
   152  # for that platform.
   153  s2i::build::unset_platform_envs() {
   154    unset GOOS
   155    unset GOARCH
   156  }
   157  
   158  
   159  # Create the GOPATH tree under $S2I_ROOT
   160  s2i::build::create_gopath_tree() {
   161    local go_pkg_dir="${S2I_GOPATH}/src/${S2I_GO_PACKAGE}"
   162    local go_pkg_basedir=$(dirname "${go_pkg_dir}")
   163  
   164    mkdir -p "${go_pkg_basedir}"
   165    rm -f "${go_pkg_dir}"
   166  
   167    # TODO: This symlink should be relative.
   168    if [[ "$OSTYPE" == "cygwin" ]]; then
   169      S2I_ROOT_cyg=$(cygpath -w ${S2I_ROOT})
   170      go_pkg_dir_cyg=$(cygpath -w ${go_pkg_dir})
   171      cmd /c "mklink ${go_pkg_dir_cyg} ${S2I_ROOT_cyg}" &>/dev/null
   172    else
   173      ln -s "${S2I_ROOT}" "${go_pkg_dir}"
   174    fi
   175  }
   176  
   177  
   178  # s2i::build::setup_env will check that the `go` commands is available in
   179  # ${PATH}. If not running on Travis, it will also check that the Go version is
   180  # good enough for the Kubernetes build.
   181  #
   182  # Input Vars:
   183  #   S2I_EXTRA_GOPATH - If set, this is included in created GOPATH
   184  #
   185  # Output Vars:
   186  #   export GOPATH - A modified GOPATH to our created tree along with extra
   187  #     stuff.
   188  #   export GOBIN - This is actively unset if already set as we want binaries
   189  #     placed in a predictable place.
   190  s2i::build::setup_env() {
   191    s2i::build::create_gopath_tree
   192  
   193    if [[ -z "$(which go)" ]]; then
   194      cat <<EOF
   195  
   196  Can't find 'go' in PATH, please fix and retry.
   197  See http://golang.org/doc/install for installation instructions.
   198  
   199  EOF
   200      exit 2
   201    fi
   202  
   203    # Enabling support for Go Modules.
   204    export GO111MODULE=on
   205  
   206    # For any tools that expect this to be set (it is default in golang 1.6),
   207    # force vendor experiment.
   208    export GO15VENDOREXPERIMENT=1
   209  
   210    GOPATH=${S2I_GOPATH}
   211  
   212    # Append S2I_EXTRA_GOPATH to the GOPATH if it is defined.
   213    if [[ -n ${S2I_EXTRA_GOPATH:-} ]]; then
   214      GOPATH="${GOPATH}:${S2I_EXTRA_GOPATH}"
   215    fi
   216  
   217    if [[ "$OSTYPE" == "cygwin" ]]; then
   218      GOPATH=$(cygpath -w -p $GOPATH)
   219    fi
   220  
   221    export GOPATH
   222  
   223    # Unset GOBIN in case it already exists in the current session.
   224    unset GOBIN
   225  }
   226  
   227  # This will take binaries from $GOPATH/bin and copy them to the appropriate
   228  # place in ${S2I_OUTPUT_BINDIR}
   229  #
   230  # If S2I_RELEASE_ARCHIVE is set to a directory, it will have tar archives of
   231  # each S2I_RELEASE_PLATFORMS created
   232  #
   233  # Ideally this wouldn't be necessary and we could just set GOBIN to
   234  # S2I_OUTPUT_BINDIR but that won't work in the face of cross compilation.  'go
   235  # install' will place binaries that match the host platform directly in $GOBIN
   236  # while placing cross compiled binaries into `platform_arch` subdirs.  This
   237  # complicates pretty much everything else we do around packaging and such.
   238  s2i::build::place_bins() {
   239    (
   240      local host_platform
   241      host_platform=$(s2i::build::host_platform)
   242  
   243      echo "++ Placing binaries"
   244  
   245      if [[ "${S2I_RELEASE_ARCHIVE-}" != "" ]]; then
   246        s2i::build::get_version_vars
   247        mkdir -p "${S2I_LOCAL_RELEASEPATH}"
   248      fi
   249  
   250      s2i::build::export_targets "$@"
   251  
   252      for platform in "${platforms[@]}"; do
   253        # The substitution on platform_src below will replace all slashes with
   254        # underscores.  It'll transform darwin/amd64 -> darwin_amd64.
   255        local platform_src="/${platform//\//_}"
   256        if [[ $platform == $host_platform ]]; then
   257          platform_src=""
   258        fi
   259  
   260        # Skip this directory if the platform has no binaries.
   261        local full_binpath_src="${S2I_GOPATH}/bin${platform_src}"
   262        if [[ ! -d "${full_binpath_src}" ]]; then
   263          continue
   264        fi
   265  
   266        mkdir -p "${S2I_OUTPUT_BINPATH}/${platform}"
   267  
   268        # Create an array of binaries to release. Append .exe variants if the platform is windows.
   269        local -a binaries=()
   270        for binary in "${targets[@]}"; do
   271          binary=$(basename $binary)
   272          if [[ $platform =~ ^windows ]]; then
   273            binaries+=("${binary}.exe")
   274          else
   275            binaries+=("${binary}")
   276          fi
   277        done
   278  
   279        # Move the specified release binaries to the shared S2I_OUTPUT_BINPATH.
   280        for binary in "${binaries[@]}"; do
   281          mv "${full_binpath_src}/${binary}" "${S2I_OUTPUT_BINPATH}/${platform}/"
   282        done
   283  
   284        # If no release archive was requested, we're done.
   285        if [[ "${S2I_RELEASE_ARCHIVE-}" == "" ]]; then
   286          continue
   287        fi
   288  
   289        # Create a temporary bin directory containing only the binaries marked for release.
   290        local release_binpath=$(mktemp -d sti.release.${S2I_RELEASE_ARCHIVE}.XXX)
   291        for binary in "${binaries[@]}"; do
   292          cp "${S2I_OUTPUT_BINPATH}/${platform}/${binary}" "${release_binpath}/"
   293        done
   294  
   295        # Create binary copies where specified.
   296        local suffix=""
   297        if [[ $platform =~ ^windows ]]; then
   298          suffix=".exe"
   299        fi
   300        for linkname in "${S2I_BINARY_SYMLINKS[@]}"; do
   301          local src="${release_binpath}/s2i${suffix}"
   302          if [[ -f "${src}" ]]; then
   303            ln -s "s2i${suffix}" "${release_binpath}/${linkname}${suffix}"
   304          fi
   305        done
   306  
   307        # Create the release archive.
   308        local platform_segment="${platform//\//-}"
   309        if [[ $platform =~ ^windows ]]; then
   310          local archive_name="${S2I_RELEASE_ARCHIVE}-${S2I_GIT_VERSION}-${S2I_GIT_COMMIT}-${platform_segment}.zip"
   311          echo "++ Creating archive ${archive_name}"
   312          for file in "${S2I_BINARY_RELEASE_WINDOWS[@]}"; do
   313            zip "${S2I_LOCAL_RELEASEPATH}/${archive_name}" -qj "${release_binpath}/${file}"
   314          done
   315          pushd "${S2I_LOCAL_RELEASEPATH}" > /dev/null
   316          echo "++ Generating sha512sum for ${archive_name}"
   317          sha512sum "${archive_name}" >> "SHA512-SUMS.txt"
   318          popd  > /dev/null
   319        else
   320          local archive_name="${S2I_RELEASE_ARCHIVE}-${S2I_GIT_VERSION}-${S2I_GIT_COMMIT}-${platform_segment}.tar.gz"
   321          echo "++ Creating archive ${archive_name}"
   322          tar -czf "${S2I_LOCAL_RELEASEPATH}/${archive_name}" -C "${release_binpath}" .
   323          pushd "${S2I_LOCAL_RELEASEPATH}"  > /dev/null
   324          echo "++ Generating sha512sum for ${archive_name}"
   325          sha512sum "${archive_name}" >> "SHA512-SUMS.txt"
   326          popd  > /dev/null
   327        fi
   328        rm -rf "${release_binpath}"
   329      done
   330      if [[ -d "${S2I_LOCAL_RELEASEPATH}" ]]; then
   331        pushd "${S2I_LOCAL_RELEASEPATH}"  > /dev/null
   332        echo "++ Verifying sha512sums for archives"
   333        if ! sha512sum -c "SHA512-SUMS.txt"; then
   334          echo "!! Unable to verify sha512sum for archives"
   335          exit 1
   336        else
   337          echo "++ Verified sha512sums for archives"
   338        fi
   339        popd  > /dev/null
   340      fi
   341    )
   342  
   343  }
   344  
   345  # s2i::build::make_binary_symlinks makes symlinks for the sti
   346  # binary in _output/local/go/bin
   347  s2i::build::make_binary_symlinks() {
   348    platform=$(s2i::build::host_platform)
   349    if [[ -f "${S2I_OUTPUT_BINPATH}/${platform}/s2i" ]]; then
   350      for linkname in "${S2I_BINARY_SYMLINKS[@]}"; do
   351        if [[ $platform =~ ^windows ]]; then
   352          cp "${S2I_OUTPUT_BINPATH}/${platform}/s2i.exe" "${S2I_OUTPUT_BINPATH}/${platform}/${linkname}.exe"
   353        else
   354          ln -sf s2i "${S2I_OUTPUT_BINPATH}/${platform}/${linkname}"
   355        fi
   356      done
   357    fi
   358  }
   359  
   360  # s2i::build::detect_local_release_tars verifies there is only one primary and one
   361  # image binaries release tar in S2I_LOCAL_RELEASEPATH for the given platform specified by
   362  # argument 1, exiting if more than one of either is found.
   363  #
   364  # If the tars are discovered, their full paths are exported to the following env vars:
   365  #
   366  #   S2I_PRIMARY_RELEASE_TAR
   367  s2i::build::detect_local_release_tars() {
   368    local platform="$1"
   369  
   370    if [[ ! -d "${S2I_LOCAL_RELEASEPATH}" ]]; then
   371      echo "There are no release artifacts in ${S2I_LOCAL_RELEASEPATH}"
   372      exit 2
   373    fi
   374    if [[ ! -f "${S2I_LOCAL_RELEASEPATH}/.commit" ]]; then
   375      echo "There is no release .commit identifier ${S2I_LOCAL_RELEASEPATH}"
   376      exit 2
   377    fi
   378    local primary=$(find ${S2I_LOCAL_RELEASEPATH} -maxdepth 1 -type f -name source-to-image-*-${platform}*)
   379    if [[ $(echo "${primary}" | wc -l) -ne 1 ]]; then
   380      echo "There should be exactly one ${platform} primary tar in $S2I_LOCAL_RELEASEPATH"
   381      exit 2
   382    fi
   383  
   384    export S2I_PRIMARY_RELEASE_TAR="${primary}"
   385    export S2I_RELEASE_COMMIT="$(cat ${S2I_LOCAL_RELEASEPATH}/.commit)"
   386  }
   387  
   388  
   389  # s2i::build::get_version_vars loads the standard version variables as
   390  # ENV vars
   391  s2i::build::get_version_vars() {
   392    if [[ -n ${S2I_VERSION_FILE-} ]]; then
   393      source "${S2I_VERSION_FILE}"
   394      return
   395    fi
   396    s2i::build::sti_version_vars
   397  }
   398  
   399  # s2i::build::sti_version_vars looks up the current Git vars
   400  s2i::build::sti_version_vars() {
   401    local git=(git --work-tree "${S2I_ROOT}")
   402  
   403    if [[ -n ${S2I_GIT_COMMIT-} ]] || S2I_GIT_COMMIT=$("${git[@]}" rev-parse --short "HEAD^{commit}" 2>/dev/null); then
   404      if [[ -z ${S2I_GIT_TREE_STATE-} ]]; then
   405        # Check if the tree is dirty.  default to dirty
   406        if git_status=$("${git[@]}" status --porcelain 2>/dev/null) && [[ -z ${git_status} ]]; then
   407          S2I_GIT_TREE_STATE="clean"
   408        else
   409          S2I_GIT_TREE_STATE="dirty"
   410        fi
   411      fi
   412  
   413      # Use git describe to find the version based on annotated tags.
   414      if [[ -n ${S2I_GIT_VERSION-} ]] || S2I_GIT_VERSION=$("${git[@]}" describe --tags "${S2I_GIT_COMMIT}^{commit}" 2>/dev/null); then
   415        if [[ "${S2I_GIT_TREE_STATE}" == "dirty" ]]; then
   416          # git describe --dirty only considers changes to existing files, but
   417          # that is problematic since new untracked .go files affect the build,
   418          # so use our idea of "dirty" from git status instead.
   419          S2I_GIT_VERSION+="-dirty"
   420        fi
   421  
   422        # Try to match the "git describe" output to a regex to try to extract
   423        # the "major" and "minor" versions and whether this is the exact tagged
   424        # version or whether the tree is between two tagged versions.
   425        if [[ "${S2I_GIT_VERSION}" =~ ^v([0-9]+)\.([0-9]+)([.-].*)?$ ]]; then
   426          S2I_GIT_MAJOR=${BASH_REMATCH[1]}
   427          S2I_GIT_MINOR=${BASH_REMATCH[2]}
   428          if [[ -n "${BASH_REMATCH[3]}" ]]; then
   429            S2I_GIT_MINOR+="+"
   430          fi
   431        fi
   432      fi
   433    fi
   434  }
   435  
   436  # Saves the environment flags to $1
   437  s2i::build::save_version_vars() {
   438    local version_file=${1-}
   439    [[ -n ${version_file} ]] || {
   440      echo "!!! Internal error.  No file specified in s2i::build::save_version_vars"
   441      return 1
   442    }
   443  
   444    cat <<EOF >"${version_file}"
   445  S2I_GIT_COMMIT='${S2I_GIT_COMMIT-}'
   446  S2I_GIT_TREE_STATE='${S2I_GIT_TREE_STATE-}'
   447  S2I_GIT_VERSION='${S2I_GIT_VERSION-}'
   448  S2I_GIT_MAJOR='${S2I_GIT_MAJOR-}'
   449  S2I_GIT_MINOR='${S2I_GIT_MINOR-}'
   450  EOF
   451  }
   452  
   453  # golang 1.5 wants `-X key=val`, but golang 1.4- REQUIRES `-X key val`
   454  s2i::build::ldflag() {
   455    local key=${1}
   456    local val=${2}
   457  
   458    GO_VERSION=($(go version))
   459    if [[ -n $(echo "${GO_VERSION[2]}" | grep -E 'go1.4') ]]; then
   460      echo "-X ${S2I_GO_PACKAGE}/pkg/version.${key} ${val}"
   461    else
   462      echo "-X ${S2I_GO_PACKAGE}/pkg/version.${key}=${val}"
   463    fi
   464  }
   465  
   466  # s2i::build::ldflags calculates the -ldflags argument for building STI
   467  s2i::build::ldflags() {
   468    (
   469      # Run this in a subshell to prevent settings/variables from leaking.
   470      set -o errexit
   471      set -o nounset
   472      set -o pipefail
   473  
   474      cd "${S2I_ROOT}"
   475  
   476      s2i::build::get_version_vars
   477  
   478      declare -a ldflags=()
   479      ldflags+=($(s2i::build::ldflag "majorFromGit" "${S2I_GIT_MAJOR}"))
   480      ldflags+=($(s2i::build::ldflag "minorFromGit" "${S2I_GIT_MINOR}"))
   481      ldflags+=($(s2i::build::ldflag "versionFromGit" "${S2I_GIT_VERSION}"))
   482      ldflags+=($(s2i::build::ldflag "commitFromGit" "${S2I_GIT_COMMIT}"))
   483      # The -ldflags parameter takes a single string, so join the output.
   484      echo "${ldflags[*]-}"
   485    )
   486  }