# The main hledger continuous integration test workflow.
# Passing this is required for merging/pushing to master, per
# https://github.com/simonmichael/hledger/settings/branch_protection_rules/17386787

name: ci

# When and where does this workflow run ?
on:
  # When manually triggered in github ui, it runs in master.
  workflow_dispatch:

  # On this schedule, it runs in master.
  # schedule:
  # - cron: "0 07 * * 0"  # sunday midnight pacific
  # Avoid, because these run in all forks also.
  # Possible workaround; does it prevent wasteful startups ?
  # https://github.com/orgs/community/discussions/26409#discussioncomment-3251818
  # jobs:
  #   job_id:
  #     if: github.event.pull_request.head.repo.full_name == github.repository

  # When there's a push to the ci branch, it runs there.
  # After passing there it can be merged/pushed to master.
  # (Don't use these branches for pull requests, or it will run twice,
  # https://github.community/t/how-to-trigger-an-action-on-push-or-pull-request-but-not-both/16662/2)
  push:
    branches: [ ci ]

  # When there's a pull request against master, it runs in the PR branch.
  # After passing there it can be merged/pushed to master.
  pull_request:
    branches: [ master ]
    # Uncomment to run it only for changes to these paths: (but that could prevent merging)
    # paths:
    #   - '.github/workflows/pushpull.yml'
    #   - 'stack*.yaml'
    #   - 'hledger-lib/**'
    #   - 'hledger/**'
    #   - 'hledger-ui/**'
    #   - 'hledger-web/**'
    #   - 'bin/*.hs'
    #   - 'examples/**'
    # Or to ignore certain paths:
    #   # examples
    #   - '!**.journal'
    #   - '!**.j'
    #   - '!**.ledger'
    #   - '!**.csv'
    #   # docs
    #   - '!**.m4'
    #   - '!**.md'
    #   - '!**.1'
    #   - '!**.5'
    #   - '!**.info'
    #   - '!**.txt'

# What does it do ?
jobs:

  # Build all expecting no warnings and run unit/doc/functional/haddock/bench tests,
  # with the platform(s) and GHC version(s) enabled below.
  # On success, upload the binaries as a downloadable artifact.
  ci:

    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        # keep synced with branch protection rules, see link above
        plan:  
        # the oldest supported ghc, to check backward compatibility
        # - { ghc: "810", stack: "stack --stack-yaml=stack8.10.yaml" }
        - { ghc: "944", stack: "stack --stack-yaml=stack9.4.yaml" }
    steps:

    - name: Check out
      uses: actions/checkout@v3
      # have to fetch everything for git describe for hledger's --version
      with: 
        fetch-depth: 0

    - name: Print debug output
      env:
        GITHUB_CONTEXT: ${{ toJson(github) }}
      run: |
        echo $GITHUB_CONTEXT
        # echo "$GITHUB_SHA"
        # echo "$GITHUB_REF"
        # echo "$GITHUB_HEAD_REF"
        # echo "$GITHUB_BASE_REF"
        # git log "$GITHUB_BASE_REF"..
        # tools/commitlint "$GITHUB_BASE_REF"..

    - name: Check commit messages
      # keep this step synced in all workflows which do it
      # For a PR, the range will be: master..origin/$GITHUB_HEAD_REF
      # For a push it will be: $BEFORE..
      # For a force push, BEFORE is the previous HEAD, and on github (though not locally) this is an "invalid revision range".
      # 202310: we skip this check when we can't detect the commits, which happens in certain cases
      # related: https://stackoverflow.com/questions/64708371/how-to-run-github-workflow-on-every-commit-of-a-push
      # 202312: ignore this if it fails, it may be not worth the hassle
      env:
        BEFORE: ${{ github.event.before }}
        # NUM:    5
      shell: bash
      run: |
        RANGE=${BEFORE:-origin/master}..${GITHUB_HEAD_REF:-}

        echo "debug: last 10 commits:"
        echo "$(git log --format='%h -%d %s (%an, %ci)' -10)"
        echo "debug: origin/master:"
        echo "$(git log --format='%h -%d %s (%an, %ci)' -1 origin/master)"
        echo "debug: BEFORE=$BEFORE"
        echo "$(git log --format='%h -%d %s (%an, %ci)' -1 $BEFORE)"
        echo "debug: GITHUB_HEAD_REF=$GITHUB_HEAD_REF"
        echo "$(git log --format='%h -%d %s (%an, %ci)' -1 $GITHUB_HEAD_REF)"
        echo "debug: RANGE=$RANGE"
        echo "debug: commits to check:"
        echo "$(git log --format='%h -%d %s (%an, %ci)' --abbrev-commit --date=relative --date-order $RANGE)"

        if git rev-list --quiet $RANGE
        then
          tools/commitlint $RANGE || echo "commit lint failed, ignoring"
        else
          # echo "could not identify commits, checking last $NUM instead:"; tools/commitlint -$NUM
          echo "could not identify commits, not checking them"  # XXX
        fi

    - name: Skip remaining steps if the last commit message begins with ;
      shell: bash
      run: |
        echo "git log -1 --pretty='%s' ${GITHUB_HEAD_REF:+origin/$GITHUB_HEAD_REF} >> $$.gitlog"
        (git log -1 --pretty='%s' ${GITHUB_HEAD_REF:+origin/$GITHUB_HEAD_REF} >> $$.gitlog \
          && (grep -qE '^ *;' $$.gitlog || echo "CONTINUE=true" >> $GITHUB_ENV)) \
          || ( echo "could not identify commit range, continuing CI steps"; echo "CONTINUE=true" >> $GITHUB_ENV )

    - name: Check embedded files
      run: |
        sudo apt install -y ripgrep
        tools/checkembeddedfiles
      if: env.CONTINUE

    # things to be cached/restored:

    - name: Cache stack global package db
      id:   stack-global
      uses: actions/cache@v3
      with:
        path: ~/.stack
        # XXX if stack.yaml is a symlink, this fails with
        #  Error: The template is not valid. .github/workflows/push.yml (Line: 103, Col: 14): hashFiles('**.yaml') failed. 
        #  Fail to hash files under directory '/home/runner/work/hledger/hledger'
        key: ${{ runner.os }}-stack-global-${{ matrix.plan.ghc }}-${{ hashFiles('**.yaml') }}
        restore-keys: |
             ${{ runner.os }}-stack-global-${{ matrix.plan.ghc }}
      if: env.CONTINUE

    - name: Cache stack-installed programs in ~/.local/bin
      id:   stack-programs
      uses: actions/cache@v3
      with:
        path: ~/.local/bin
        key: ${{ runner.os }}-stack-programs-${{ matrix.plan.ghc }}-${{ hashFiles('**.yaml') }}
        restore-keys: |
             ${{ runner.os }}-stack-programs-${{ matrix.plan.ghc }}
      if: env.CONTINUE

    - name: Cache .stack-work
      uses: actions/cache@v3
      with:
        path: .stack-work
        key: ${{ runner.os }}-stack-work-${{ matrix.plan.ghc }}-${{ hashFiles('**.yaml') }}
        restore-keys: |
             ${{ runner.os }}-stack-work-${{ matrix.plan.ghc }}
      if: env.CONTINUE

    - name: Cache hledger-lib/.stack-work
      uses: actions/cache@v3
      with:
        path: hledger-lib/.stack-work
        key: ${{ runner.os }}-hledger-lib-stack-work-${{ matrix.plan.ghc }}-${{ hashFiles('hledger-lib/package.yaml') }}
        restore-keys: |
             ${{ runner.os }}-hledger-lib-stack-work-${{ matrix.plan.ghc }}
      if: env.CONTINUE

    - name: Cache hledger/.stack-work
      uses: actions/cache@v3
      with:
        path: hledger/.stack-work
        key: ${{ runner.os }}-hledger-stack-work-${{ matrix.plan.ghc }}-${{ hashFiles('hledger/package.yaml') }}
        restore-keys: |
             ${{ runner.os }}-hledger-stack-work-${{ matrix.plan.ghc }}
      if: env.CONTINUE

    - name: Cache hledger-ui/.stack-work
      uses: actions/cache@v3
      with:
        path: hledger-ui/.stack-work
        key: ${{ runner.os }}-hledger-ui-stack-work-${{ matrix.plan.ghc }}-${{ hashFiles('hledger-ui/package.yaml') }}
        restore-keys: |
             ${{ runner.os }}-hledger-ui-stack-work-${{ matrix.plan.ghc }}
      if: env.CONTINUE

    - name: Cache hledger-web/.stack-work
      uses: actions/cache@v3
      with:
        path: hledger-web/.stack-work
        key: ${{ runner.os }}-hledger-web-stack-work-${{ matrix.plan.ghc }}-${{ hashFiles('hledger-web/package.yaml') }}
        restore-keys: |
             ${{ runner.os }}-hledger-web-stack-work-${{ matrix.plan.ghc }}
      if: env.CONTINUE

    # actions:

    - name: Install stack
      run: |
        mkdir -p ~/.local/bin
        export PATH=~/.local/bin:$PATH
        # curl -sL https://get.haskellstack.org/stable/linux-x86_64.tar.gz | tar xz --wildcards --strip-components=1 -C ~/.local/bin '*/stack'; chmod a+x ~/.local/bin/stack
        if [[ ! -x ~/.local/bin/stack ]]; then curl -sL https://get.haskellstack.org/stable/linux-x86_64.tar.gz | tar xz --wildcards --strip-components=1 -C ~/.local/bin '*/stack'; chmod a+x ~/.local/bin/stack; fi
        stack --version
      if: env.CONTINUE

    - name: Install GHC
      env:
        stack: ${{ matrix.plan.stack }}
      run: |
        $stack setup --install-ghc
      if: env.CONTINUE

    - name: Install haskell deps
      env:
        stack: ${{ matrix.plan.stack }}
      run: |
        $stack build --test --bench --only-dependencies --dry-run
        $stack build --test --bench --only-dependencies
      if: env.CONTINUE

    # - name: Build all hledger modules warning free, optimised and minimised, run unit/doc/bench tests
    #   env:
    #     stack: ${{ matrix.plan.stack }}
    #   run: |
    #     $stack install --test --bench --force-dirty --ghc-options=-fforce-recomp --ghc-options=-Werror --ghc-options=-split-sections --no-terminal
    #     # build quicker when tweaking ci: $stack install --ghc-options=-Werror --ghc-options=-split-sections --no-terminal
    #     # -split-sections shrinks binaries by 30% on average here
    #     # --pedantic --no-run-benchmarks
    #   if: env.CONTINUE

    # Packages are built one at a time to fail faster on error.
    # Note: doctests won't run if using GHC 9.0, see hledger-lib/package.yaml
    # Takes ~2m on a 2023 github worker.
    - name: Build all hledger modules fast, warning free, run unit/doc/bench tests
      env:
        stack: ${{ matrix.plan.stack }}
      run: |
        # These previously used --force-dirty --ghc-options--fforce-recomp for some reason ?
        # But that built hledger-lib four times and hledger three times. Try without.
        $stack install --fast --ghc-options=-Werror  --test --bench hledger-lib
        $stack install --fast --ghc-options=-Werror  --test --bench hledger
        $stack install --fast --ghc-options=-Werror  --test --bench hledger-ui
        $stack install --fast --ghc-options=-Werror  --test --bench hledger-web
        #  --ghc-options=-split-sections --no-terminal
      if: env.CONTINUE

    - name: Install shelltestrunner
      env:
        stack: ${{ matrix.plan.stack }}
      run: |
        export PATH=~/.local/bin:$PATH
        if [[ ! -x ~/.local/bin/shelltest ]]; then $stack install shelltestrunner-1.10; fi
        shelltest --version
      if: env.CONTINUE

    # Takes ~30s on a 2023 github worker.
    - name: Test functional tests (excluding addons)
      env:
        stack: ${{ matrix.plan.stack }}
      run: |
        export PATH=~/.local/bin:$PATH
        COLUMNS=80 $stack exec -- shelltest --execdir -j16 hledger/test -x /_ -x /addons -x ledger-compat/ledger-baseline -x ledger-compat/ledger-regress -x ledger-compat/ledger-collected
        # XXX run the bin/ func tests corresponding to the GHC version enabled above, only
      if: env.CONTINUE

    # Takes 1m+ on a 2023 github worker.
    # Moved to (one of) the binaries-* workflows instead;
    # haddock breakage might not be found until release time but it's easy to fix.
    # - name: Test haddock generation
    #   env:
    #     stack: ${{ matrix.plan.stack }}
    #   run: |
    #     printf "haddock version: "; haddock --version
    #     time $stack build --fast --haddock --no-haddock-deps --no-haddock-hyperlink-source --haddock-arguments="--no-print-missing-docs" || echo "HADDOCK FAILED, IGNORING"
    #     # --no-haddock-hyperlink-source is 25% faster
    #     # --no-print-missing-docs is 600% quieter
    #   if: env.CONTINUE

    - name: Gather binaries
      id: exes
      run: |
        mkdir tmp
        cd tmp
        cp -P ~/.local/bin/hledger .
        cp -P ~/.local/bin/hledger-ui .
        cp -P ~/.local/bin/hledger-web .
        strip hledger
        strip hledger-ui
        strip hledger-web
        tar cvf hledger-linux-x64.tar hledger hledger-ui hledger-web
      if: env.CONTINUE

      # how to set a context variable, and an attempt to make a nice artifact version suffix:
      # echo "::set-output name=version::$(git branch --show-current | sed 's/-.*//')-$(git rev-parse --short HEAD)"

    # upload-artifact loses execute permissions, so we tar the binaries to preserve them.
    # github UI always zips artifacts when they are downloaded, so we don't bother compressing the tar.
    # Unfortunately it means users must both unzip and untar.
    - name: Upload binaries artifact
      uses: actions/upload-artifact@v3
      with:
        name: hledger-linux-x64
        path: tmp/hledger-linux-x64.tar
      if: env.CONTINUE




    # - name: show stuff
    #   run: |
    #     if [[ -e ~/.local/bin ]]; then ls -lFRa ~/.local/bin; fi

    # inspect available context info, per
    # https://docs.github.com/en/free-pro-team@latest/actions/reference/context-and-expression-syntax-for-github-actions.
    # sample output: https://github.com/simonmichael/hledger/runs/1619227104
    # - name: Dump GitHub context
    #   env:
    #     GITHUB_CONTEXT: ${{ toJson(github) }}
    #   run: echo "$GITHUB_CONTEXT"
    # - name: Dump job context
    #   env:
    #     JOB_CONTEXT: ${{ toJson(job) }}
    #   run: echo "$JOB_CONTEXT"
    # - name: Dump steps context
    #   env:
    #     STEPS_CONTEXT: ${{ toJson(steps) }}
    #   run: echo "$STEPS_CONTEXT"
    # - name: Dump runner context
    #   env:
    #     RUNNER_CONTEXT: ${{ toJson(runner) }}
    #   run: echo "$RUNNER_CONTEXT"
    # - name: Dump strategy context
    #   env:
    #     STRATEGY_CONTEXT: ${{ toJson(strategy) }}
    #   run: echo "$STRATEGY_CONTEXT"
    # - name: Dump matrix context
    #   env:
    #     MATRIX_CONTEXT: ${{ toJson(matrix) }}
    #   run: echo "$MATRIX_CONTEXT"
