From 019bb370b2e763b7243b6f4eef3e98aae999a3c2 Mon Sep 17 00:00:00 2001 From: anirudhwarrier <12178754+anirudhwarrier@users.noreply.github.com> Date: Fri, 10 Jul 2026 12:06:19 +0400 Subject: [PATCH 1/2] Add Linux asset suffix handling and tests - Introduced functions to determine the appropriate asset suffix based on glibc version for Linux. - Added tests for parsing glibc versions and verifying the correct asset suffix. - Updated installation script to utilize the new suffix logic. - Enhanced the getAssetName function to include the Linux suffix in asset naming. This change improves compatibility for older Linux distributions. --- .github/workflows/pull-request-main.yml | 3 + cmd/update/linux_asset_suffix_linux.go | 61 ++++++++++++++ cmd/update/linux_asset_suffix_linux_test.go | 87 ++++++++++++++++++++ cmd/update/linux_asset_suffix_stub.go | 7 ++ cmd/update/update.go | 15 ++-- cmd/update/update_test.go | 4 +- cmd/update/verify.go | 4 +- cmd/update/verify_test.go | 7 +- install/install.sh | 58 +++++++++++++- install/linux_asset_suffix_test.sh | 89 +++++++++++++++++++++ 10 files changed, 319 insertions(+), 16 deletions(-) create mode 100644 cmd/update/linux_asset_suffix_linux.go create mode 100644 cmd/update/linux_asset_suffix_linux_test.go create mode 100644 cmd/update/linux_asset_suffix_stub.go create mode 100755 install/linux_asset_suffix_test.sh diff --git a/.github/workflows/pull-request-main.yml b/.github/workflows/pull-request-main.yml index 173cd97a..deb5e154 100644 --- a/.github/workflows/pull-request-main.yml +++ b/.github/workflows/pull-request-main.yml @@ -48,6 +48,9 @@ jobs: - name: Verify install.sh embedded release public key run: bash install/check_embedded_key.sh + - name: Verify install.sh linux asset suffix helpers + run: bash install/linux_asset_suffix_test.sh + ci-test-unit: runs-on: ubuntu-latest permissions: diff --git a/cmd/update/linux_asset_suffix_linux.go b/cmd/update/linux_asset_suffix_linux.go new file mode 100644 index 00000000..9da6c62f --- /dev/null +++ b/cmd/update/linux_asset_suffix_linux.go @@ -0,0 +1,61 @@ +//go:build linux + +package update + +import ( + "fmt" + "os/exec" + "regexp" + "strings" + + "github.com/Masterminds/semver/v3" +) + +const ( + linuxLdd235Suffix = "_ldd2-35" + linuxGlibcThreshold = "2.36" +) + +var glibcVersionPattern = regexp.MustCompile(`(\d+\.\d+)(?:\.\d+)?`) + +func linuxAssetSuffix() string { + out, err := exec.Command("ldd", "--version").Output() // #nosec G204 -- fixed args, standard glibc probe + if err != nil { + return "" + } + + version, err := parseGlibcVersionFromLddOutput(string(out)) + if err != nil { + return "" + } + + threshold, err := semver.NewVersion(linuxGlibcThreshold) + if err != nil { + return "" + } + if version.LessThan(threshold) { + return linuxLdd235Suffix + } + return "" +} + +func parseGlibcVersionFromLddOutput(output string) (*semver.Version, error) { + firstLine := strings.TrimSpace(output) + if idx := strings.IndexByte(firstLine, '\n'); idx >= 0 { + firstLine = firstLine[:idx] + } + if firstLine == "" { + return nil, fmt.Errorf("empty ldd --version output") + } + + matches := glibcVersionPattern.FindAllString(firstLine, -1) + if len(matches) == 0 { + return nil, fmt.Errorf("could not parse glibc version from %q", firstLine) + } + + version, err := semver.NewVersion(matches[len(matches)-1]) + if err != nil { + return nil, fmt.Errorf("parse glibc version %q: %w", matches[len(matches)-1], err) + } + return version, nil +} diff --git a/cmd/update/linux_asset_suffix_linux_test.go b/cmd/update/linux_asset_suffix_linux_test.go new file mode 100644 index 00000000..873831e8 --- /dev/null +++ b/cmd/update/linux_asset_suffix_linux_test.go @@ -0,0 +1,87 @@ +//go:build linux + +package update + +import ( + "testing" + + "github.com/Masterminds/semver/v3" + "github.com/stretchr/testify/require" +) + +func TestParseGlibcVersionFromLddOutput(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + output string + want string + wantErr bool + }{ + { + name: "ubuntu 22.04", + output: "ldd (Ubuntu GLIBC 2.35-0ubuntu3.8) 2.35\nCopyright (C) 2022 Free Software Foundation, Inc.\n", + want: "2.35", + }, + { + name: "ubuntu 24.04", + output: "ldd (Ubuntu GLIBC 2.39-0ubuntu8.4) 2.39\n", + want: "2.39", + }, + { + name: "rhel style", + output: "ldd (GNU libc) 2.34\n", + want: "2.34", + }, + { + name: "empty", + output: "", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got, err := parseGlibcVersionFromLddOutput(tt.output) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + require.Equal(t, tt.want, got.String()) + }) + } +} + +func TestLinuxAssetSuffixFromGlibcVersion(t *testing.T) { + t.Parallel() + + tests := []struct { + output string + want string + }{ + { + output: "ldd (Ubuntu GLIBC 2.35-0ubuntu3.8) 2.35\n", + want: linuxLdd235Suffix, + }, + { + output: "ldd (Ubuntu GLIBC 2.39-0ubuntu8.4) 2.39\n", + want: "", + }, + } + + threshold, err := semver.NewVersion(linuxGlibcThreshold) + require.NoError(t, err) + + for _, tt := range tests { + version, err := parseGlibcVersionFromLddOutput(tt.output) + require.NoError(t, err) + + suffix := "" + if version.LessThan(threshold) { + suffix = linuxLdd235Suffix + } + require.Equal(t, tt.want, suffix) + } +} diff --git a/cmd/update/linux_asset_suffix_stub.go b/cmd/update/linux_asset_suffix_stub.go new file mode 100644 index 00000000..71682f6f --- /dev/null +++ b/cmd/update/linux_asset_suffix_stub.go @@ -0,0 +1,7 @@ +//go:build !linux + +package update + +func linuxAssetSuffix() string { + return "" +} diff --git a/cmd/update/update.go b/cmd/update/update.go index 76b6de5f..5d3e5a41 100644 --- a/cmd/update/update.go +++ b/cmd/update/update.go @@ -57,7 +57,7 @@ func getLatestTag() (string, error) { return info.TagName, nil } -func getAssetName() (asset string, platform string, archName string, err error) { +func getAssetName() (asset string, platform string, archName string, linuxSuffix string, err error) { osName := osruntime.GOOS arch := osruntime.GOARCH var ext string @@ -68,11 +68,12 @@ func getAssetName() (asset string, platform string, archName string, err error) case "linux": platform = "linux" ext = ".tar.gz" + linuxSuffix = linuxAssetSuffix() case "windows": platform = "windows" ext = ".zip" default: - return "", "", "", fmt.Errorf("unsupported OS: %s", osName) + return "", "", "", "", fmt.Errorf("unsupported OS: %s", osName) } switch arch { case "amd64", "x86_64": @@ -84,10 +85,10 @@ func getAssetName() (asset string, platform string, archName string, err error) archName = "arm64" } default: - return "", "", "", fmt.Errorf("unsupported architecture: %s", arch) + return "", "", "", "", fmt.Errorf("unsupported architecture: %s", arch) } - asset = fmt.Sprintf("%s_%s_%s%s", cliName, platform, archName, ext) - return asset, platform, archName, nil + asset = fmt.Sprintf("%s_%s_%s%s%s", cliName, platform, archName, linuxSuffix, ext) + return asset, platform, archName, linuxSuffix, nil } func downloadFile(url, dest, message string) error { @@ -335,7 +336,7 @@ func Run(currentVersion string) error { } // If we're here, an update is needed. - asset, platform, archName, err := getAssetName() + asset, platform, archName, linuxSuffix, err := getAssetName() if err != nil { spinner.Stop() return fmt.Errorf("error determining asset name: %w", err) @@ -369,7 +370,7 @@ func Run(currentVersion string) error { var sigPath string if platform == "linux" { - sigAsset := getSigAssetName(platform, archName) + sigAsset := getSigAssetName(platform, archName, linuxSuffix) sigPath = filepath.Join(tmpDir, sigAsset) sigURL := fmt.Sprintf("https://github.com/%s/releases/download/%s/%s", repo, tag, sigAsset) sigDownloadMsg := fmt.Sprintf("Downloading signature for %s...", tag) diff --git a/cmd/update/update_test.go b/cmd/update/update_test.go index bc5df1aa..04b0bc30 100644 --- a/cmd/update/update_test.go +++ b/cmd/update/update_test.go @@ -18,7 +18,7 @@ func TestRun_abortsWhenSignatureVerificationFails(t *testing.T) { httpmock.ActivateNonDefault(httpClient) t.Cleanup(httpmock.DeactivateAndReset) - asset, platform, archName, err := getAssetName() + asset, platform, archName, linuxSuffix, err := getAssetName() require.NoError(t, err) tag := "v99.0.0-test" @@ -39,7 +39,7 @@ func TestRun_abortsWhenSignatureVerificationFails(t *testing.T) { ) if platform == "linux" { - sigAsset := getSigAssetName(platform, archName) + sigAsset := getSigAssetName(platform, archName, linuxSuffix) sigURL := "https://github.com/smartcontractkit/cre-cli/releases/download/" + tag + "/" + sigAsset httpmock.RegisterResponder("GET", sigURL, func(_ *http.Request) (*http.Response, error) { diff --git a/cmd/update/verify.go b/cmd/update/verify.go index 83633032..124aec59 100644 --- a/cmd/update/verify.go +++ b/cmd/update/verify.go @@ -6,6 +6,6 @@ const ( codesignIdentifier = "com.smartcontract.cre.cli" ) -func getSigAssetName(platform, archName string) string { - return "cre_" + platform + "_" + archName + ".sig" +func getSigAssetName(platform, archName, linuxSuffix string) string { + return "cre_" + platform + "_" + archName + linuxSuffix + ".sig" } diff --git a/cmd/update/verify_test.go b/cmd/update/verify_test.go index 4452022e..5977ac57 100644 --- a/cmd/update/verify_test.go +++ b/cmd/update/verify_test.go @@ -7,14 +7,15 @@ import ( ) func TestGetSigAssetName(t *testing.T) { - require.Equal(t, "cre_linux_amd64.sig", getSigAssetName("linux", "amd64")) + require.Equal(t, "cre_linux_amd64.sig", getSigAssetName("linux", "amd64", "")) + require.Equal(t, "cre_linux_amd64_ldd2-35.sig", getSigAssetName("linux", "amd64", "_ldd2-35")) } func TestGetAssetName(t *testing.T) { - asset, platform, archName, err := getAssetName() + asset, platform, archName, linuxSuffix, err := getAssetName() require.NoError(t, err) require.NotEmpty(t, asset) require.NotEmpty(t, platform) require.NotEmpty(t, archName) - require.Contains(t, asset, "cre_"+platform+"_"+archName) + require.Contains(t, asset, "cre_"+platform+"_"+archName+linuxSuffix) } diff --git a/install/install.sh b/install/install.sh index 8e0b3675..ed601a89 100755 --- a/install/install.sh +++ b/install/install.sh @@ -195,6 +195,52 @@ verify_release_binary() { esac } +LINUX_LDD235_SUFFIX="_ldd2-35" +LINUX_GLIBC_THRESHOLD="2.36" + +parse_glibc_version_from_ldd_output() { + local output=$1 + local first_line version + + first_line=$(printf '%s' "$output" | head -n1) + version=$(printf '%s' "$first_line" | grep -oE '[0-9]+\.[0-9]+' | tail -n1) + if [ -z "$version" ]; then + return 1 + fi + printf '%s' "$version" +} + +version_lt() { + local left=$1 + local right=$2 + [ "$(printf '%s\n' "$left" "$right" | sort -V | head -n1)" = "$left" ] && [ "$left" != "$right" ] +} + +linux_asset_suffix() { + local ldd_output version + + if [ "$PLATFORM" != "linux" ]; then + printf '' + return + fi + + ldd_output=$(ldd --version 2>/dev/null | head -n1) || { + printf '' + return + } + + version=$(parse_glibc_version_from_ldd_output "$ldd_output") || { + printf '' + return + } + + if version_lt "$version" "$LINUX_GLIBC_THRESHOLD"; then + printf '%s' "$LINUX_LDD235_SUFFIX" + else + printf '' + fi +} + tildify() { if [[ $1 = $HOME/* ]]; then local replacement=\~/ @@ -321,7 +367,15 @@ else fi # 4. Construct Download URL and Download asset -ASSET="${cli_name}_${PLATFORM}_${ARCH_NAME}" +LINUX_SUFFIX="" +if [ "$PLATFORM" = "linux" ]; then + LINUX_SUFFIX=$(linux_asset_suffix) + if [ -n "$LINUX_SUFFIX" ]; then + echo "Using glibc-compatible build for older Linux (ldd2-35)..." + fi +fi + +ASSET="${cli_name}_${PLATFORM}_${ARCH_NAME}${LINUX_SUFFIX}" # Determine the file extension based on OS if [ "$PLATFORM" = "linux" ]; then ASSET="${ASSET}.tar.gz" @@ -343,7 +397,7 @@ SIG_PATH="" if [ "$PLATFORM" = "linux" ]; then check_command "gpg" - SIG_ASSET="${cli_name}_${PLATFORM}_${ARCH_NAME}.sig" + SIG_ASSET="${cli_name}_${PLATFORM}_${ARCH_NAME}${LINUX_SUFFIX}.sig" SIG_URL="https://github.com/$github_repo/releases/download/$LATEST_TAG/$SIG_ASSET" SIG_PATH="$TMP_DIR/$SIG_ASSET" curl --fail --location --progress-bar "$SIG_URL" --output "$SIG_PATH" || diff --git a/install/linux_asset_suffix_test.sh b/install/linux_asset_suffix_test.sh new file mode 100755 index 00000000..071c5862 --- /dev/null +++ b/install/linux_asset_suffix_test.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +install_sh="$repo_root/install/install.sh" +failures=0 + +pass() { echo "PASS: $1"; } +fail_test() { echo "FAIL: $1"; failures=$((failures + 1)); } + +# shellcheck disable=SC1090 +source <(sed -n '198,242p' "$install_sh") + +assert_parse_version() { + local name=$1 + local output=$2 + local want=$3 + + if got=$(parse_glibc_version_from_ldd_output "$output"); then + if [ "$got" = "$want" ]; then + pass "$name" + else + fail_test "$name (got $got, want $want)" + fi + else + fail_test "$name (parse failed)" + fi +} + +assert_suffix() { + local name=$1 + local output=$2 + local want=$3 + local version suffix + + PLATFORM=linux + if ! version=$(parse_glibc_version_from_ldd_output "$output"); then + suffix="" + elif version_lt "$version" "$LINUX_GLIBC_THRESHOLD"; then + suffix="$LINUX_LDD235_SUFFIX" + else + suffix="" + fi + + if [ "$suffix" = "$want" ]; then + pass "$name" + else + fail_test "$name (got '$suffix', want '$want')" + fi +} + +assert_parse_version "ubuntu 22.04" \ + $'ldd (Ubuntu GLIBC 2.35-0ubuntu3.8) 2.35\nCopyright (C) 2022 Free Software Foundation, Inc.\n' \ + "2.35" + +assert_parse_version "ubuntu 24.04" \ + $'ldd (Ubuntu GLIBC 2.39-0ubuntu8.4) 2.39\n' \ + "2.39" + +assert_parse_version "rhel style" \ + $'ldd (GNU libc) 2.34\n' \ + "2.34" + +if parse_glibc_version_from_ldd_output "" >/dev/null 2>&1; then + fail_test "empty output rejects parse" +else + pass "empty output rejects parse" +fi + +assert_suffix "ubuntu 22.04 uses ldd2-35" \ + $'ldd (Ubuntu GLIBC 2.35-0ubuntu3.8) 2.35\n' \ + "_ldd2-35" + +assert_suffix "ubuntu 24.04 uses default asset" \ + $'ldd (Ubuntu GLIBC 2.39-0ubuntu8.4) 2.39\n' \ + "" + +assert_suffix "rhel 2.34 uses ldd2-35" \ + $'ldd (GNU libc) 2.34\n' \ + "_ldd2-35" + +assert_suffix "empty output fails open" "" "" + +if [ "$failures" -ne 0 ]; then + echo "$failures linux asset suffix test(s) failed" >&2 + exit 1 +fi + +echo "All linux asset suffix tests passed." From 0d6158d93d459b4fb477bc62bebcd72787f1665c Mon Sep 17 00:00:00 2001 From: anirudhwarrier <12178754+anirudhwarrier@users.noreply.github.com> Date: Fri, 10 Jul 2026 12:25:35 +0400 Subject: [PATCH 2/2] fix test --- cmd/update/linux_asset_suffix_linux_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmd/update/linux_asset_suffix_linux_test.go b/cmd/update/linux_asset_suffix_linux_test.go index 873831e8..e1c9edcd 100644 --- a/cmd/update/linux_asset_suffix_linux_test.go +++ b/cmd/update/linux_asset_suffix_linux_test.go @@ -49,7 +49,9 @@ func TestParseGlibcVersionFromLddOutput(t *testing.T) { return } require.NoError(t, err) - require.Equal(t, tt.want, got.String()) + want, err := semver.NewVersion(tt.want) + require.NoError(t, err) + require.True(t, got.Equal(want)) }) } }