diff --git a/.github/actions/setup-go-paths/action.yml b/.github/actions/setup-go-paths/action.yml new file mode 100644 index 0000000000000..8423ddb4c5dab --- /dev/null +++ b/.github/actions/setup-go-paths/action.yml @@ -0,0 +1,57 @@ +name: "Setup Go Paths" +description: Overrides Go paths like GOCACHE and GOMODCACHE to use temporary directories. +outputs: + gocache: + description: "Value of GOCACHE" + value: ${{ steps.paths.outputs.gocache }} + gomodcache: + description: "Value of GOMODCACHE" + value: ${{ steps.paths.outputs.gomodcache }} + gopath: + description: "Value of GOPATH" + value: ${{ steps.paths.outputs.gopath }} + gotmp: + description: "Value of GOTMPDIR" + value: ${{ steps.paths.outputs.gotmp }} + cached-dirs: + description: "Go directories that should be cached between CI runs" + value: ${{ steps.paths.outputs.cached-dirs }} +runs: + using: "composite" + steps: + - name: Override Go paths + id: paths + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 + with: + script: | + const path = require('path'); + + // RUNNER_TEMP should be backed by a RAM disk on Windows if + // coder/setup-ramdisk-action was used + const runnerTemp = process.env.RUNNER_TEMP; + const gocacheDir = path.join(runnerTemp, 'go-cache'); + const gomodcacheDir = path.join(runnerTemp, 'go-mod-cache'); + const gopathDir = path.join(runnerTemp, 'go-path'); + const gotmpDir = path.join(runnerTemp, 'go-tmp'); + + core.exportVariable('GOCACHE', gocacheDir); + core.exportVariable('GOMODCACHE', gomodcacheDir); + core.exportVariable('GOPATH', gopathDir); + core.exportVariable('GOTMPDIR', gotmpDir); + + core.setOutput('gocache', gocacheDir); + core.setOutput('gomodcache', gomodcacheDir); + core.setOutput('gopath', gopathDir); + core.setOutput('gotmp', gotmpDir); + + const cachedDirs = `${gocacheDir}\n${gomodcacheDir}`; + core.setOutput('cached-dirs', cachedDirs); + + - name: Create directories + shell: bash + run: | + set -e + mkdir -p "$GOCACHE" + mkdir -p "$GOMODCACHE" + mkdir -p "$GOPATH" + mkdir -p "$GOTMPDIR" diff --git a/.github/actions/setup-go/action.yaml b/.github/actions/setup-go/action.yaml index 6ee57ff57db6b..6656ba5d06490 100644 --- a/.github/actions/setup-go/action.yaml +++ b/.github/actions/setup-go/action.yaml @@ -8,42 +8,26 @@ inputs: use-preinstalled-go: description: "Whether to use preinstalled Go." default: "false" - use-temp-cache-dirs: - description: "Whether to use temporary GOCACHE and GOMODCACHE directories." - default: "false" + use-cache: + description: "Whether to use the cache." + default: "true" runs: using: "composite" steps: - - name: Override GOCACHE and GOMODCACHE - shell: bash - if: inputs.use-temp-cache-dirs == 'true' - run: | - # cd to another directory to ensure we're not inside a Go project. - # That'd trigger Go to download the toolchain for that project. - cd "$RUNNER_TEMP" - # RUNNER_TEMP should be backed by a RAM disk on Windows if - # coder/setup-ramdisk-action was used - export GOCACHE_DIR="$RUNNER_TEMP""\go-cache" - export GOMODCACHE_DIR="$RUNNER_TEMP""\go-mod-cache" - export GOPATH_DIR="$RUNNER_TEMP""\go-path" - export GOTMP_DIR="$RUNNER_TEMP""\go-tmp" - mkdir -p "$GOCACHE_DIR" - mkdir -p "$GOMODCACHE_DIR" - mkdir -p "$GOPATH_DIR" - mkdir -p "$GOTMP_DIR" - go env -w GOCACHE="$GOCACHE_DIR" - go env -w GOMODCACHE="$GOMODCACHE_DIR" - go env -w GOPATH="$GOPATH_DIR" - go env -w GOTMPDIR="$GOTMP_DIR" - name: Setup Go uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: go-version: ${{ inputs.use-preinstalled-go == 'false' && inputs.version || '' }} + cache: ${{ inputs.use-cache }} - name: Install gotestsum shell: bash run: go install gotest.tools/gotestsum@0d9599e513d70e5792bb9334869f82f6e8b53d4d # main as of 2025-05-15 + - name: Install mtimehash + shell: bash + run: go install github.com/slsyy/mtimehash/cmd/mtimehash@a6b5da4ed2c4a40e7b805534b004e9fde7b53ce0 # v1.0.0 + # It isn't necessary that we ever do this, but it helps # separate the "setup" from the "run" times. - name: go mod download diff --git a/.github/actions/setup-imdisk/action.yaml b/.github/actions/setup-imdisk/action.yaml deleted file mode 100644 index 52ef7eb08fd81..0000000000000 --- a/.github/actions/setup-imdisk/action.yaml +++ /dev/null @@ -1,27 +0,0 @@ -name: "Setup ImDisk" -if: runner.os == 'Windows' -description: | - Sets up the ImDisk toolkit for Windows and creates a RAM disk on drive R:. -runs: - using: "composite" - steps: - - name: Download ImDisk - if: runner.os == 'Windows' - shell: bash - run: | - mkdir imdisk - cd imdisk - curl -L -o files.cab https://github.com/coder/imdisk-artifacts/raw/92a17839ebc0ee3e69be019f66b3e9b5d2de4482/files.cab - curl -L -o install.bat https://github.com/coder/imdisk-artifacts/raw/92a17839ebc0ee3e69be019f66b3e9b5d2de4482/install.bat - cd .. - - - name: Install ImDisk - shell: cmd - run: | - cd imdisk - install.bat /silent - - - name: Create RAM Disk - shell: cmd - run: | - imdisk -a -s 4096M -m R: -p "/fs:ntfs /q /y" diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ad8f5d1289715..bc7747f430528 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -224,7 +224,7 @@ jobs: gen: timeout-minutes: 8 runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || 'ubuntu-latest' }} - if: always() + if: ${{ !cancelled() }} steps: - name: Harden Runner uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 @@ -336,13 +336,16 @@ jobs: # a separate repository to allow its use before actions/checkout. - name: Setup RAM Disks if: runner.os == 'Windows' - uses: coder/setup-ramdisk-action@81c5c441bda00c6c3d6bcee2e5a33ed4aadbbcc1 + uses: coder/setup-ramdisk-action@a4b59caa8be2e88c348abeef042d7c1a33d8743e - name: Checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 1 + - name: Setup Go Paths + uses: ./.github/actions/setup-go-paths + - name: Setup Go uses: ./.github/actions/setup-go with: @@ -350,7 +353,6 @@ jobs: # download the toolchain configured in go.mod, so we don't # need to reinstall it. It's faster on Windows runners. use-preinstalled-go: ${{ runner.os == 'Windows' }} - use-temp-cache-dirs: ${{ runner.os == 'Windows' }} - name: Setup Terraform uses: ./.github/actions/setup-tf @@ -398,63 +400,10 @@ jobs: with: api-key: ${{ secrets.DATADOG_API_KEY }} - # We don't run the full test-suite for Windows & MacOS, so we just run the CLI tests on every PR. - # We run the test suite in test-go-pg, including CLI. - test-cli: - runs-on: ${{ matrix.os == 'macos-latest' && github.repository_owner == 'coder' && 'depot-macos-latest' || matrix.os == 'windows-2022' && github.repository_owner == 'coder' && 'windows-latest-16-cores' || matrix.os }} - needs: changes - if: needs.changes.outputs.go == 'true' || needs.changes.outputs.ci == 'true' || github.ref == 'refs/heads/main' - strategy: - matrix: - os: - - macos-latest - - windows-2022 - steps: - - name: Harden Runner - uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 - with: - egress-policy: audit - - - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - fetch-depth: 1 - - - name: Setup Go - uses: ./.github/actions/setup-go - - - name: Setup Terraform - uses: ./.github/actions/setup-tf - - # Sets up the ImDisk toolkit for Windows and creates a RAM disk on drive R:. - - name: Setup ImDisk - if: runner.os == 'Windows' - uses: ./.github/actions/setup-imdisk - - - name: Test CLI - env: - TS_DEBUG_DISCO: "true" - LC_CTYPE: "en_US.UTF-8" - LC_ALL: "en_US.UTF-8" - TEST_RETRIES: 2 - shell: bash - run: | - # By default Go will use the number of logical CPUs, which - # is a fine default. - PARALLEL_FLAG="" - - make test-cli - - - name: Upload test stats to Datadog - timeout-minutes: 1 - continue-on-error: true - uses: ./.github/actions/upload-datadog - if: success() || failure() - with: - api-key: ${{ secrets.DATADOG_API_KEY }} - test-go-pg: - runs-on: ${{ matrix.os == 'ubuntu-latest' && github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || matrix.os }} + # make sure to adjust NUM_PARALLEL_PACKAGES and NUM_PARALLEL_TESTS below + # when changing runner sizes + runs-on: ${{ matrix.os == 'ubuntu-latest' && github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || matrix.os && matrix.os == 'macos-latest' && github.repository_owner == 'coder' && 'depot-macos-latest' || matrix.os == 'windows-2022' && github.repository_owner == 'coder' && 'depot-windows-2022-16' || matrix.os }} needs: changes if: needs.changes.outputs.go == 'true' || needs.changes.outputs.ci == 'true' || github.ref == 'refs/heads/main' # This timeout must be greater than the timeout set by `go test` in @@ -466,48 +415,157 @@ jobs: matrix: os: - ubuntu-latest + - macos-latest + - windows-2022 steps: - name: Harden Runner uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 with: egress-policy: audit + # macOS indexes all new files in the background. Our Postgres tests + # create and destroy thousands of databases on disk, and Spotlight + # tries to index all of them, seriously slowing down the tests. + - name: Disable Spotlight Indexing + if: runner.os == 'macOS' + run: | + sudo mdutil -a -i off + sudo mdutil -X / + sudo launchctl bootout system /System/Library/LaunchDaemons/com.apple.metadata.mds.plist + + # Set up RAM disks to speed up the rest of the job. This action is in + # a separate repository to allow its use before actions/checkout. + - name: Setup RAM Disks + if: runner.os == 'Windows' + uses: coder/setup-ramdisk-action@a4b59caa8be2e88c348abeef042d7c1a33d8743e + - name: Checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 1 + - name: Setup Go Paths + id: go-paths + uses: ./.github/actions/setup-go-paths + + - name: Download Go Build Cache + id: download-go-build-cache + uses: ./.github/actions/test-cache/download + with: + key-prefix: test-go-build-${{ runner.os }}-${{ runner.arch }} + cache-path: ${{ steps.go-paths.outputs.cached-dirs }} + - name: Setup Go uses: ./.github/actions/setup-go + with: + # Runners have Go baked-in and Go will automatically + # download the toolchain configured in go.mod, so we don't + # need to reinstall it. It's faster on Windows runners. + use-preinstalled-go: ${{ runner.os == 'Windows' }} + # Cache is already downloaded above + use-cache: false - name: Setup Terraform uses: ./.github/actions/setup-tf - # Sets up the ImDisk toolkit for Windows and creates a RAM disk on drive R:. - - name: Setup ImDisk - if: runner.os == 'Windows' - uses: ./.github/actions/setup-imdisk - - name: Download Test Cache id: download-cache uses: ./.github/actions/test-cache/download with: key-prefix: test-go-pg-${{ runner.os }}-${{ runner.arch }} + - name: Normalize File and Directory Timestamps + shell: bash + # Normalize file modification timestamps so that go test can use the + # cache from the previous CI run. See https://github.com/golang/go/issues/58571 + # for more details. + run: | + find . -type f ! -path ./.git/\*\* | mtimehash + find . -type d ! -path ./.git/\*\* -exec touch -t 200601010000 {} + + - name: Test with PostgreSQL Database env: POSTGRES_VERSION: "13" TS_DEBUG_DISCO: "true" LC_CTYPE: "en_US.UTF-8" LC_ALL: "en_US.UTF-8" - TEST_RETRIES: 2 shell: bash run: | - # By default Go will use the number of logical CPUs, which - # is a fine default. - PARALLEL_FLAG="" + set -o errexit + set -o pipefail + + if [ "${{ runner.os }}" == "Windows" ]; then + # Create a temp dir on the R: ramdisk drive for Windows. The default + # C: drive is extremely slow: https://github.com/actions/runner-images/issues/8755 + mkdir -p "R:/temp/embedded-pg" + go run scripts/embedded-pg/main.go -path "R:/temp/embedded-pg" + elif [ "${{ runner.os }}" == "macOS" ]; then + # Postgres runs faster on a ramdisk on macOS too + mkdir -p /tmp/tmpfs + sudo mount_tmpfs -o noowners -s 8g /tmp/tmpfs + go run scripts/embedded-pg/main.go -path /tmp/tmpfs/embedded-pg + elif [ "${{ runner.os }}" == "Linux" ]; then + make test-postgres-docker + fi - make test-postgres + # if macOS, install google-chrome for scaletests + # As another concern, should we really have this kind of external dependency + # requirement on standard CI? + if [ "${{ matrix.os }}" == "macos-latest" ]; then + brew install google-chrome + fi + + # macOS will output "The default interactive shell is now zsh" + # intermittently in CI... + if [ "${{ matrix.os }}" == "macos-latest" ]; then + touch ~/.bash_profile && echo "export BASH_SILENCE_DEPRECATION_WARNING=1" >> ~/.bash_profile + fi + + if [ "${{ runner.os }}" == "Windows" ]; then + # Our Windows runners have 16 cores. + # On Windows Postgres chokes up when we have 16x16=256 tests + # running in parallel, and dbtestutil.NewDB starts to take more than + # 10s to complete sometimes causing test timeouts. With 16x8=128 tests + # Postgres tends not to choke. + NUM_PARALLEL_PACKAGES=8 + NUM_PARALLEL_TESTS=16 + elif [ "${{ runner.os }}" == "macOS" ]; then + # Our macOS runners have 8 cores. We set NUM_PARALLEL_TESTS to 16 + # because the tests complete faster and Postgres doesn't choke. It seems + # that macOS's tmpfs is faster than the one on Windows. + NUM_PARALLEL_PACKAGES=8 + NUM_PARALLEL_TESTS=16 + elif [ "${{ runner.os }}" == "Linux" ]; then + # Our Linux runners have 8 cores. + NUM_PARALLEL_PACKAGES=8 + NUM_PARALLEL_TESTS=8 + fi + + # by default, run tests with cache + TESTCOUNT="" + if [ "${{ github.ref }}" == "refs/heads/main" ]; then + # on main, run tests without cache + TESTCOUNT="-count=1" + fi + + mkdir -p "$RUNNER_TEMP/sym" + source scripts/normalize_path.sh + # terraform gets installed in a random directory, so we need to normalize + # the path to the terraform binary or a bunch of cached tests will be + # invalidated. See scripts/normalize_path.sh for more details. + normalize_path_with_symlinks "$RUNNER_TEMP/sym" "$(dirname $(which terraform))" + + # We rerun failing tests to counteract flakiness coming from Postgres + # choking on macOS and Windows sometimes. + DB=ci gotestsum --rerun-fails=2 --rerun-fails-max-failures=50 \ + --format standard-quiet --packages "./..." \ + -- -timeout=20m -v -p $NUM_PARALLEL_PACKAGES -parallel=$NUM_PARALLEL_TESTS $TESTCOUNT + + - name: Upload Go Build Cache + uses: ./.github/actions/test-cache/upload + with: + cache-key: ${{ steps.download-go-build-cache.outputs.cache-key }} + cache-path: ${{ steps.go-paths.outputs.cached-dirs }} - name: Upload Test Cache uses: ./.github/actions/test-cache/upload diff --git a/.github/workflows/nightly-gauntlet.yaml b/.github/workflows/nightly-gauntlet.yaml deleted file mode 100644 index 64b520d07ba6e..0000000000000 --- a/.github/workflows/nightly-gauntlet.yaml +++ /dev/null @@ -1,183 +0,0 @@ -# The nightly-gauntlet runs tests that are either too flaky or too slow to block -# every PR. -name: nightly-gauntlet -on: - schedule: - # Every day at 4AM - - cron: "0 4 * * 1-5" - workflow_dispatch: - -permissions: - contents: read - -jobs: - test-go-pg: - # make sure to adjust NUM_PARALLEL_PACKAGES and NUM_PARALLEL_TESTS below - # when changing runner sizes - runs-on: ${{ matrix.os == 'macos-latest' && github.repository_owner == 'coder' && 'depot-macos-latest' || matrix.os == 'windows-2022' && github.repository_owner == 'coder' && 'depot-windows-2022-16' || matrix.os }} - # This timeout must be greater than the timeout set by `go test` in - # `make test-postgres` to ensure we receive a trace of running - # goroutines. Setting this to the timeout +5m should work quite well - # even if some of the preceding steps are slow. - timeout-minutes: 25 - strategy: - fail-fast: false - matrix: - os: - - macos-latest - - windows-2022 - steps: - - name: Harden Runner - uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 - with: - egress-policy: audit - - # macOS indexes all new files in the background. Our Postgres tests - # create and destroy thousands of databases on disk, and Spotlight - # tries to index all of them, seriously slowing down the tests. - - name: Disable Spotlight Indexing - if: runner.os == 'macOS' - run: | - sudo mdutil -a -i off - sudo mdutil -X / - sudo launchctl bootout system /System/Library/LaunchDaemons/com.apple.metadata.mds.plist - - # Set up RAM disks to speed up the rest of the job. This action is in - # a separate repository to allow its use before actions/checkout. - - name: Setup RAM Disks - if: runner.os == 'Windows' - uses: coder/setup-ramdisk-action@79dacfe70c47ad6d6c0dd7f45412368802641439 - - - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - fetch-depth: 1 - - - name: Setup Go - uses: ./.github/actions/setup-go - with: - # Runners have Go baked-in and Go will automatically - # download the toolchain configured in go.mod, so we don't - # need to reinstall it. It's faster on Windows runners. - use-preinstalled-go: ${{ runner.os == 'Windows' }} - use-temp-cache-dirs: ${{ runner.os == 'Windows' }} - - - name: Setup Terraform - uses: ./.github/actions/setup-tf - - - name: Test with PostgreSQL Database - env: - POSTGRES_VERSION: "13" - TS_DEBUG_DISCO: "true" - LC_CTYPE: "en_US.UTF-8" - LC_ALL: "en_US.UTF-8" - shell: bash - run: | - if [ "${{ runner.os }}" == "Windows" ]; then - # Create a temp dir on the R: ramdisk drive for Windows. The default - # C: drive is extremely slow: https://github.com/actions/runner-images/issues/8755 - mkdir -p "R:/temp/embedded-pg" - go run scripts/embedded-pg/main.go -path "R:/temp/embedded-pg" - fi - if [ "${{ runner.os }}" == "macOS" ]; then - # Postgres runs faster on a ramdisk on macOS too - mkdir -p /tmp/tmpfs - sudo mount_tmpfs -o noowners -s 8g /tmp/tmpfs - go run scripts/embedded-pg/main.go -path /tmp/tmpfs/embedded-pg - fi - - # if macOS, install google-chrome for scaletests - # As another concern, should we really have this kind of external dependency - # requirement on standard CI? - if [ "${{ matrix.os }}" == "macos-latest" ]; then - brew install google-chrome - fi - - # By default Go will use the number of logical CPUs, which - # is a fine default. - PARALLEL_FLAG="" - - # macOS will output "The default interactive shell is now zsh" - # intermittently in CI... - if [ "${{ matrix.os }}" == "macos-latest" ]; then - touch ~/.bash_profile && echo "export BASH_SILENCE_DEPRECATION_WARNING=1" >> ~/.bash_profile - fi - - # Golang's default for these 2 variables is the number of logical CPUs. - # Our Windows and Linux runners have 16 cores, so they match up there. - NUM_PARALLEL_PACKAGES=16 - NUM_PARALLEL_TESTS=16 - if [ "${{ runner.os }}" == "Windows" ]; then - # On Windows Postgres chokes up when we have 16x16=256 tests - # running in parallel, and dbtestutil.NewDB starts to take more than - # 10s to complete sometimes causing test timeouts. With 16x8=128 tests - # Postgres tends not to choke. - NUM_PARALLEL_PACKAGES=8 - fi - if [ "${{ runner.os }}" == "macOS" ]; then - # Our macOS runners have 8 cores. We leave NUM_PARALLEL_TESTS at 16 - # because the tests complete faster and Postgres doesn't choke. It seems - # that macOS's tmpfs is faster than the one on Windows. - NUM_PARALLEL_PACKAGES=8 - fi - - # We rerun failing tests to counteract flakiness coming from Postgres - # choking on macOS and Windows sometimes. - DB=ci gotestsum --rerun-fails=2 --rerun-fails-max-failures=1000 \ - --format standard-quiet --packages "./..." \ - -- -v -p $NUM_PARALLEL_PACKAGES -parallel=$NUM_PARALLEL_TESTS -count=1 - - - name: Upload test stats to Datadog - timeout-minutes: 1 - continue-on-error: true - uses: ./.github/actions/upload-datadog - if: success() || failure() - with: - api-key: ${{ secrets.DATADOG_API_KEY }} - - notify-slack-on-failure: - needs: - - test-go-pg - runs-on: ubuntu-latest - if: failure() && github.ref == 'refs/heads/main' - - steps: - - name: Send Slack notification - run: | - curl -X POST -H 'Content-type: application/json' \ - --data '{ - "blocks": [ - { - "type": "header", - "text": { - "type": "plain_text", - "text": "❌ Nightly gauntlet failed", - "emoji": true - } - }, - { - "type": "section", - "fields": [ - { - "type": "mrkdwn", - "text": "*Workflow:*\n${{ github.workflow }}" - }, - { - "type": "mrkdwn", - "text": "*Committer:*\n${{ github.actor }}" - }, - { - "type": "mrkdwn", - "text": "*Commit:*\n${{ github.sha }}" - } - ] - }, - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "*View failure:* <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Click here>" - } - } - ] - }' ${{ secrets.CI_FAILURE_SLACK_WEBHOOK }} diff --git a/scripts/normalize_path.sh b/scripts/normalize_path.sh new file mode 100644 index 0000000000000..07427aa2bae77 --- /dev/null +++ b/scripts/normalize_path.sh @@ -0,0 +1,55 @@ +#!/bin/bash + +# Call: normalize_path_with_symlinks [target_dir] [dir_prefix] +# +# Normalizes the PATH environment variable by replacing each directory that +# begins with dir_prefix with a symbolic link in target_dir. For example, if +# PATH is "/usr/bin:/bin", target_dir is /tmp, and dir_prefix is /usr, then +# PATH will become "/tmp/0:/bin", where /tmp/0 links to /usr/bin. +# +# This is useful for ensuring that PATH is consistent across CI runs and helps +# with reusing the same cache across them. Many of our go tests read the PATH +# variable, and if it changes between runs, the cache gets invalidated. +normalize_path_with_symlinks() { + local target_dir="${1:-}" + local dir_prefix="${2:-}" + + if [[ -z "$target_dir" || -z "$dir_prefix" ]]; then + echo "Usage: normalize_path_with_symlinks " + return 1 + fi + + local old_path="$PATH" + local -a new_parts=() + local i=0 + + IFS=':' read -ra _parts <<<"$old_path" + for dir in "${_parts[@]}"; do + # Skip empty components that can arise from "::" + [[ -z $dir ]] && continue + + # Skip directories that don't start with $dir_prefix + if [[ "$dir" != "$dir_prefix"* ]]; then + new_parts+=("$dir") + continue + fi + + local link="$target_dir/$i" + + # Replace any pre-existing file or link at $target_dir/$i + if [[ -e $link || -L $link ]]; then + rm -rf -- "$link" + fi + + # without MSYS ln will deepcopy the directory on Windows + MSYS=winsymlinks:nativestrict ln -s -- "$dir" "$link" + new_parts+=("$link") + i=$((i + 1)) + done + + export PATH + PATH="$( + IFS=':' + echo "${new_parts[*]}" + )" +} pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy