version: 2.1
orbs:
  gh: circleci/github-cli@2.2.0

executors:
  golang:
    docker:
      - image: cimg/go:1.21

commands:
  make:
    parameters:
      description:
        type: string
      target:
        type: string
    steps:
      - attach_workspace:
          at: /tmp/bin
      - restore_cache:
          name: "Restore source code cache"
          keys:
            - go-src-v1-{{ .Revision }}
      - checkout
      - restore_cache:
          name: "Restore go modules cache"
          keys:
            - go-mod-v1-{{ checksum "go.sum" }}
      - run:
          name: << parameters.description >>
          command: |
            export BINDIR=/tmp/bin
            make << parameters.target >>

jobs:
  setup-dependencies:
    executor: golang
    steps:
      - checkout
      - restore_cache:
          name: "Restore go modules cache"
          keys:
            - go-mod-v1-{{ checksum "go.sum" }}
      - run:
          name: Cache go modules
          command: make go-mod-cache
      - run:
          name: Build
          command: make build
      - run:
          name: "Git garbage collection"
          command: git gc
      - save_cache:
          name: "Save go modules cache"
          key: go-mod-v1-{{ checksum "go.sum" }}
          paths:
            - "/go/pkg/mod"
      - save_cache:
          name: "Save source code cache"
          key: go-src-v1-{{ .Revision }}
          paths:
            - ".git"

  tidy-go:
    executor: golang
    steps:
      - checkout
      - run:
          name: Check go mod tidy
          # Use --diff when available (Go 1.23)
          # https://github.com/golang/go/issues/27005
          command: |
            go mod tidy
            CHANGES_IN_REPO=$(git status --porcelain)
            if [[ -n "$CHANGES_IN_REPO" ]]; then
              echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:"
              git status && git --no-pager diff
              exit 1
            fi

  lint:
    docker:
      - image: golangci/golangci-lint:v1.54.2
    steps:
      - checkout
      - run:
          name: Lint
          command: |
            golangci-lint run --version
            golangci-lint run --tests=false --timeout=5m0s

  test-cover:
    executor: golang
    parallelism: 4
    steps:
      - checkout
      - restore_cache:
          keys:
            - go-mod-v1-{{ checksum "go.sum" }}
      - run:
          name: Run tests with coverage
          command: |
            export GORACE=halt_on_error=1
            export VERSION="$(git describe --tags --long | sed 's/v\(.*\)/\1/')"
            export GO111MODULE=on
            mkdir -p /tmp/logs /tmp/workspace/profiles
            for pkg in $(go list ./... | grep -v '/simulation' | circleci tests split); do
              id=$(echo "$pkg" | sed 's|[/.]|_|g')
              go test -mod=readonly -timeout 8m -race -coverprofile=/tmp/workspace/profiles/$id.out -covermode=atomic -tags='ledger test_ledger_mock' "$pkg" | tee "/tmp/logs/$id-$RANDOM.log"
            done
      - persist_to_workspace:
          root: /tmp/workspace
          paths:
            - "profiles/*"
      - store_artifacts:
          path: /tmp/logs

  test-system:
    executor: golang
    parallelism: 1
    resource_class: xlarge
    steps:
      - attach_workspace:
          at: /tmp/workspace
      - checkout
      - restore_cache:
          keys:
            - go-mod-v1-{{ checksum "go.sum" }}
      - run:
          name: Build and run system tests
          command: make test-system
      - run:
          command: |
            mkdir -p /tmp/system-test-workspace
            mv  /home/circleci/project/tests/system/testnet /tmp/system-test-workspace
          when: on_fail
      - store_artifacts:
          path: /tmp/system-test-workspace

  benchmark:
    executor: golang
    parallelism: 1
    steps:
      - checkout
      - restore_cache:
          keys:
            - go-mod-v1-{{ checksum "go.sum" }}
      - run:
          name: Benchmarks for gas calculations
          command: |
            cd ./x/wasm/keeper
            go test -bench .
      - run:
          name: Benchmarks to compare with native modules
          command: |
            cd ./benchmarks
            go test -bench .

  simulations:
    executor: golang
    parallelism: 1
    resource_class: xlarge
    steps:
      - checkout
      - run:
          name: Run simulations
          command: |
            make test-sim-deterministic test-sim-multi-seed-short test-sim-import-export
      - store_artifacts:
          path: /tmp

  upload-coverage:
    executor: golang
    steps:
      - attach_workspace:
          at: /tmp/workspace
      - checkout
      - run:
          name: gather
          command: |
            set -ex

            echo "--> Concatenating profiles:"
            ls /tmp/workspace/profiles/
            echo "mode: atomic" > coverage.txt
            for prof in $(ls /tmp/workspace/profiles/); do
              tail -n +2 /tmp/workspace/profiles/"$prof" >> coverage.txt
            done
      - run:
          name: upload
          command: bash <(curl -s https://codecov.io/bash) -f coverage.txt

  docker-image:
    executor: golang
    steps:
      - attach_workspace:
          at: /tmp/workspace
      - checkout
      - setup_remote_docker:
          docker_layer_caching: true
      - run:
          name: Build Docker artifact
          command: docker build --pull -t "cosmwasm/wasmd:${CIRCLE_SHA1}" .
      - run:
          name: Ensure libwasmvm version is correct
          command: |
            IN_DOCKER=$(docker run --rm "cosmwasm/wasmd:${CIRCLE_SHA1}" /usr/bin/wasmd query wasm libwasmvm-version)
            echo "Runtime libwasmvm-version in docker: $IN_DOCKER"
            IN_GOMOD=$(go list -m github.com/CosmWasm/wasmvm/v2 | cut -d" " -f2 | cut -d"v" -f2)
            echo "wasmvm version in go.mod: $IN_GOMOD"
            if [[ "$IN_DOCKER" != "$IN_GOMOD" ]]; then
              echo "Mismatch of wasmvm versions detected"
              exit 1
            fi
      - when:
          condition:
            equal: [main, << pipeline.git.branch >>]
          steps:
            - run:
                name: Push application Docker image to docker hub
                command: |
                  docker tag "cosmwasm/wasmd:${CIRCLE_SHA1}" cosmwasm/wasmd:latest
                  docker login --password-stdin -u "$DOCKER_USER" \<<<"$DOCKER_PASS"
                  docker push cosmwasm/wasmd:latest
                  docker logout

  docker-tagged:
    executor: golang
    steps:
      - attach_workspace:
          at: /tmp/workspace
      - checkout
      - setup_remote_docker:
          docker_layer_caching: true
      - run:
          name: Build Docker artifact
          command: docker build --pull -t "cosmwasm/wasmd:${CIRCLE_TAG}" .
      - run:
          name: Push application Docker image to docker hub
          command: |
            docker login --password-stdin -u "$DOCKER_USER" \<<<"$DOCKER_PASS"
            docker push "cosmwasm/wasmd:${CIRCLE_TAG}"
            docker logout

  release-tagged:
    executor: golang
    steps:
      - checkout
      - gh/setup:
          token: WASMD_GITHUB_RELEASES_TOKEN
      - run:
          name: Create release
          command: |
            gh release create ${CIRCLE_TAG} \
            --title "$CIRCLE_TAG" \
            --draft \
            --notes "See the [CHANGELOG](https://github.com/CosmWasm/wasmd/blob/${CIRCLE_TAG}/CHANGELOG.md) for details on the changes in this version."

workflows:
  test-suite:
    jobs:
      - docker-image:
          requires:
            - setup-dependencies
      - docker-tagged:
          filters:
            tags:
              only:
                - /^v.*/
            branches:
              ignore:
                - /.*/
          requires:
            - setup-dependencies
      - setup-dependencies:
          # filters here are needed to enable this job also for tags
          filters:
            tags:
              only:
                - /^v.*/
      - tidy-go:
          requires:
            - setup-dependencies
      - lint:
          requires:
            - setup-dependencies
      - test-cover:
          requires:
            - setup-dependencies
      - upload-coverage:
          requires:
            - test-cover
      - test-system:
          requires:
            - test-cover
      - benchmark:
          requires:
            - test-cover
      - simulations:
          requires:
            - setup-dependencies
      - release-tagged:
          filters:
            tags:
              only:
                - /^v.*/
            branches:
              ignore:
                - /.*/
