From b3027e50cdb3618b88c03cc5abc04bf401485cba Mon Sep 17 00:00:00 2001 From: John Warwick Date: Fri, 30 Oct 2020 18:34:05 -0400 Subject: [PATCH] Github Actions Support (#545) * Adding Github Actions support * Removing previous CI files * Add dependency caching (dialyzer) --- .github/CODEOWNERS | 2 +- .github/workflows/ci.yml | 105 ++++++++++++++++++++++++++++++ .github/workflows/configlet.yml | 18 ++++++ .github/workflows/pr.ci.yml | 111 ++++++++++++++++++++++++++++++++ .travis.yml | 38 ----------- bin/check_formatting.sh | 26 ++++++++ bin/ci-check.sh | 24 +++++++ bin/ci.sh | 20 ++++++ bin/dialyzer_check.sh | 6 +- bin/fetch-configlet | 52 --------------- bin/pr-check.sh | 24 +++++++ bin/pr.sh | 20 ++++++ bin/test_exercises.sh | 4 +- mix.exs | 1 + mix.lock | 4 +- 15 files changed, 359 insertions(+), 96 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/configlet.yml create mode 100644 .github/workflows/pr.ci.yml delete mode 100644 .travis.yml create mode 100755 bin/check_formatting.sh create mode 100755 bin/ci-check.sh create mode 100755 bin/ci.sh delete mode 100755 bin/fetch-configlet create mode 100755 bin/pr-check.sh create mode 100755 bin/pr.sh diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 6ec34c7086..a0bee154ff 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,4 +1,4 @@ -# Maintainers +# Maintainers config/maintainers.json @exercism/maintainers-admin # Code owners diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..922097ba4e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,105 @@ +# This workflow will do a clean install of the dependencies and run tests across different versions +# +# Requires scripts: +# - bin/ci.sh +# - bin/ci-check.sh + +name: elixir / main ci + +on: + push: + branches: [master, main] + workflow_dispatch: + +jobs: + precheck: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Use Elixir + uses: actions/setup-elixir@v1 + with: + otp-version: '22.2' + elixir-version: '1.10.0' + + - name: Retrieve Mix Dependencies Cache + uses: actions/cache@v1 + id: mix-cache # id to use in retrieve action + with: + path: deps + key: ${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-mix-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }} + + - name: Install Mix Dependencies + if: steps.mix-cache.outputs.cache-hit != 'true' + run: | + mix local.rebar --force + mix local.hex --force + mix deps.get + + - name: Build Project + run: mix + + - name: Retrieve PLT Cache + uses: actions/cache@v1 + id: plt-cache + with: + path: priv/plts + key: ${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-plts-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }} + + - name: Create PLTs + if: steps.plt-cache.outputs.cache-hit != 'true' + run: | + mkdir -p priv/plts + mix dialyzer --plt + + - name: Run Prechecks + run: bin/ci-check.sh + + ci: + runs-on: ubuntu-latest + + strategy: + matrix: + #Note: pick a canonical set of supported versions + elixir: ['1.6.0', '1.7.0'] + otp: ['19.0'] + include: + - elixir: '1.8.0' + otp: '20.0' + - elixir: '1.9.0' + otp: '20.0' + - elixir: '1.10.0' + otp: '21.0' + - elixir: '1.10.0' + otp: '22.2' + + steps: + - uses: actions/checkout@v2 + - name: Use Elixir ${{matrix.elixir}} / OTP ${{matrix.otp}} + uses: actions/setup-elixir@v1 + with: + otp-version: ${{matrix.otp}} + elixir-version: ${{matrix.elixir}} + + - name: Retrieve Mix Dependencies Cache + uses: actions/cache@v1 + id: mix-cache # id to use in retrieve action + with: + path: deps + key: ${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-mix-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }} + + - name: Install Mix Dependencies + if: steps.mix-cache.outputs.cache-hit != 'true' + run: | + mix local.rebar --force + mix local.hex --force + mix deps.get + + - name: Build Project + run: mix + + - name: Run Checks + run: bin/ci.sh diff --git a/.github/workflows/configlet.yml b/.github/workflows/configlet.yml new file mode 100644 index 0000000000..1b44888e66 --- /dev/null +++ b/.github/workflows/configlet.yml @@ -0,0 +1,18 @@ +# This workflow will do a fetch the latest configlet binary and lint this repository. + +name: configlet + +on: [push, pull_request, workflow_dispatch] + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Fetch configlet + uses: exercism/github-actions/configlet-ci@master + + - name: Configlet Linter + run: configlet lint . diff --git a/.github/workflows/pr.ci.yml b/.github/workflows/pr.ci.yml new file mode 100644 index 0000000000..3a25f3ca2e --- /dev/null +++ b/.github/workflows/pr.ci.yml @@ -0,0 +1,111 @@ +# This workflow will do a clean install of the dependencies and run tests across different versions +# +# Requires scripts: +# - bin/pr-check.sh +# - bin/pr.sh + +name: elixir / pr + +on: pull_request + +jobs: + precheck: + runs-on: ubuntu-latest + + steps: + - name: Checkout PR + uses: actions/checkout@v2 + + - name: Use Elixir + uses: actions/setup-elixir@v1 + with: + otp-version: '22.2' + elixir-version: '1.10.0' + + - name: Retrieve Mix Dependencies Cache + uses: actions/cache@v1 + id: mix-cache # id to use in retrieve action + with: + path: deps + key: ${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-mix-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }} + + - name: Install Mix Dependencies + if: steps.mix-cache.outputs.cache-hit != 'true' + run: | + mix local.rebar --force + mix local.hex --force + mix deps.get + + - name: Build Project + run: mix + + - name: Retrieve PLT Cache + uses: actions/cache@v1 + id: plt-cache + with: + path: priv/plts + key: ${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-plts-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }} + + - name: Create PLTs + if: steps.plt-cache.outputs.cache-hit != 'true' + run: | + mkdir -p priv/plts + mix dialyzer --plt + + # Replace with file extensions that should trigger this check. Replace with .* to allow anything. + - name: Run exercism/elixir ci pre-check (stub files, config integrity) for changed exercises + run: | + PULL_REQUEST_URL=$(jq -r ".pull_request.url" "$GITHUB_EVENT_PATH") + curl --url $"${PULL_REQUEST_URL}/files" --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' | \ + jq -c '.[] | select(.status == "added" or .status == "modified") | select(.filename | match("\\.(ex|exs|md|json)$")) | .filename' | \ + xargs -r bin/pr-check.sh + + ci: + runs-on: ubuntu-latest + + strategy: + matrix: + elixir: ['1.6.0', '1.7.0'] + otp: ['19.0'] + include: + - elixir: '1.8.0' + otp: '20.0' + - elixir: '1.9.0' + otp: '20.0' + - elixir: '1.10.0' + otp: '21.0' + - elixir: '1.10.0' + otp: '22.2' + + steps: + - uses: actions/checkout@v2 + - name: Use Elixir ${{matrix.elixir}} / OTP ${{matrix.otp}} + uses: actions/setup-elixir@v1 + with: + otp-version: ${{matrix.otp}} + elixir-version: ${{matrix.elixir}} + + - name: Retrieve Mix Dependencies Cache + uses: actions/cache@v1 + id: mix-cache # id to use in retrieve action + with: + path: deps + key: ${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-mix-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }} + + - name: Install Mix Dependencies + if: steps.mix-cache.outputs.cache-hit != 'true' + run: | + mix local.rebar --force + mix local.hex --force + mix deps.get + + - name: Build Project + run: mix + + # Replace with file extensions that should trigger running tests. Replace with .* to allow anything. + - name: Run exercism/elixir ci (runs tests) for changed/added exercises + run: | + PULL_REQUEST_URL=$(jq -r ".pull_request.url" "$GITHUB_EVENT_PATH") + curl --url $"${PULL_REQUEST_URL}/files" --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' | \ + jq -c '.[] | select(.status == "added" or .status == "modified") | select(.filename | match("\\.(ex|exs|md|json)$")) | .filename' | \ + xargs -r bin/pr.sh diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index fe459b4a01..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,38 +0,0 @@ -language: elixir - -# be explicit so travis ci doesn't autopick undesired versions -jobs: - include: - - elixir: '1.6.0' - otp_release: '19.0' - - elixir: '1.7.0' - otp_release: '19.0' - - elixir: '1.8.0' - otp_release: '20.0' - - elixir: '1.9.0' - otp_release: '20.0' - - elixir: '1.10.0' - otp_release: '21.0' - -script: - - mix format --check-formatted - - bin/fetch-configlet - - bin/configlet lint . - - mix deps.get - - # fail if there are any compiler and test problems for individual tests. - - bin/test_exercises.sh - - - travis_wait 30 bin/dialyzer_check.sh - - # check for any trailing whitespace - - "! git grep ' $' -- \\*.exs" - -sudo: false - -cache: - directories: - - _build - - deps - - ~/.mix/ - - priv/plts diff --git a/bin/check_formatting.sh b/bin/check_formatting.sh new file mode 100755 index 0000000000..3ab24923e3 --- /dev/null +++ b/bin/check_formatting.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# ### +# check_formatting.sh +# ### +# Uses `mix format` to validate formatting of all elixir code +# Looks for trailing whitespace in files +# ### +echo "Running 'mix format'" +mix format --check-formatted +FORMAT_EXIT_CODE="$?" + +echo "Checking for trailing whitespace" +# git grep returns a 0 status if there is a match +# so we negate the result for consistency +! git grep --line-number ' $' +GREP_EXIT_CODE="$?" + +if [ "$FORMAT_EXIT_CODE" -ne 0 -o "$GREP_EXIT_CODE" -ne 0 ] +then + echo "Formatting checks failed" + exit 1; +fi + +echo "Formatting checks passed" +exit 0; diff --git a/bin/ci-check.sh b/bin/ci-check.sh new file mode 100755 index 0000000000..62340b6bd4 --- /dev/null +++ b/bin/ci-check.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +# ### +# ci-check.sh +# ### +# Precheck actions to run on a commit to master +# ### + +echo "Running check_formatting.sh" +("bin/check_formatting.sh") +CHECK_FORMAT_EXIT_CODE="$?" + +echo "Running dialyzer_check.sh" +("bin/dialyzer_check.sh") +DIALYZER_EXIT_CODE="$?" + +if [ "$CHECK_FORMAT_EXIT_CODE" -ne 0 -o "$DIALYZER_EXIT_CODE" -ne 0 ] +then + echo "Precheck failed" + exit 1; +fi + +echo "Precheck passed" +exit 0; diff --git a/bin/ci.sh b/bin/ci.sh new file mode 100755 index 0000000000..c12d64a31b --- /dev/null +++ b/bin/ci.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# ### +# ci.sh +# ### +# Actions to run on a commit to master +# ### + +echo "Running test_exercises.sh" +("bin/test_exercises.sh") +TEST_EXIT_CODE="$?" + +if [ "$TEST_EXIT_CODE" -ne 0 ] +then + echo "Tests failed" + exit 1; +fi + +echo "Tests passed" +exit 0; diff --git a/bin/dialyzer_check.sh b/bin/dialyzer_check.sh index fe9ab419c2..3fe057107c 100755 --- a/bin/dialyzer_check.sh +++ b/bin/dialyzer_check.sh @@ -1,9 +1,13 @@ +#!/bin/bash + # currently dialyzer is timing out after 30 mins only on elixir 1.7 / otp 19 if elixir --version | grep -q 'Elixir 1.7'; then echo "skipping dialyzer for Elixir 1.7" exit 0 fi +mkdir -p ./priv/plts + mkdir -p ./tmp/src mkdir ./tmp/build @@ -15,7 +19,7 @@ do done elixirc -o ./_build ./tmp/src/*.exs -mix dialyzer +mix dialyzer --no-check RESULT=$? rm -rf ./tmp diff --git a/bin/fetch-configlet b/bin/fetch-configlet deleted file mode 100755 index 915ee034d4..0000000000 --- a/bin/fetch-configlet +++ /dev/null @@ -1,52 +0,0 @@ -#!/bin/bash - -set -eo pipefail - -readonly LATEST='https://api.github.com/repos/exercism/configlet/releases/latest' - -case "$(uname)" in - (Darwin*) OS='mac' ;; - (Linux*) OS='linux' ;; - (Windows*) OS='windows' ;; - (MINGW*) OS='windows' ;; - (MSYS_NT-*) OS='windows' ;; - (*) OS='linux' ;; -esac - -case "$OS" in - (windows*) EXT='zip' ;; - (*) EXT='tgz' ;; -esac - -case "$(uname -m)" in - (*64*) ARCH='64bit' ;; - (*686*) ARCH='32bit' ;; - (*386*) ARCH='32bit' ;; - (*) ARCH='64bit' ;; -esac - -if [ -z "${GITHUB_TOKEN}" ] -then - HEADER='' -else - HEADER="authorization: Bearer ${GITHUB_TOKEN}" -fi - -FILENAME="configlet-${OS}-${ARCH}.${EXT}" - -get_url () { - curl --header "$HEADER" -s "$LATEST" | - awk -v filename=$FILENAME '$1 ~ /browser_download_url/ && $2 ~ filename { print $2 }' | - tr -d '"' -} - -URL=$(get_url) - -case "$EXT" in - (*zip) - curl --header "$HEADER" -s --location "$URL" -o bin/latest-configlet.zip - unzip bin/latest-configlet.zip -d bin/ - rm bin/latest-configlet.zip - ;; - (*) curl --header "$HEADER" -s --location "$URL" | tar xz -C bin/ ;; -esac diff --git a/bin/pr-check.sh b/bin/pr-check.sh new file mode 100755 index 0000000000..6a64e28165 --- /dev/null +++ b/bin/pr-check.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +# ### +# pr-check.sh +# ### +# Precheck actions to run on a pull request +# ### + +echo "Running check_formatting.sh" +("bin/check_formatting.sh") +CHECK_FORMAT_EXIT_CODE="$?" + +echo "Running dialyzer_check.sh" +("bin/dialyzer_check.sh") +DIALYZER_EXIT_CODE="$?" + +if [ "$CHECK_FORMAT_EXIT_CODE" -ne 0 -o "$DIALYZER_EXIT_CODE" -ne 0 ] +then + echo "Precheck failed" + exit 1; +fi + +echo "Precheck passed" +exit 0; diff --git a/bin/pr.sh b/bin/pr.sh new file mode 100755 index 0000000000..372c62921e --- /dev/null +++ b/bin/pr.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# ### +# pr.sh +# ### +# Actions to run on a pull request +# ### + +echo "Running test_exercises.sh" +("bin/test_exercises.sh") +TEST_EXIT_CODE="$?" + +if [ "$TEST_EXIT_CODE" -ne 0 ] +then + echo "Tests failed" + exit 1; +fi + +echo "Tests passed" +exit 0; diff --git a/bin/test_exercises.sh b/bin/test_exercises.sh index a39f514e21..bb304b7037 100755 --- a/bin/test_exercises.sh +++ b/bin/test_exercises.sh @@ -41,7 +41,7 @@ cp -a exercises tmp-exercises # test each exercise for exercise in tmp-exercises/* -do +do if [ -d $exercise ] then cd "$exercise" @@ -53,7 +53,7 @@ do # Move the example into the lib file for file in lib/*.ex - do + do rm "$file" done diff --git a/mix.exs b/mix.exs index 64974915ee..154f096ae3 100644 --- a/mix.exs +++ b/mix.exs @@ -11,6 +11,7 @@ defmodule ExercismTestRunner.Mixfile do consolidate_protocols: false, dialyzer: [ paths: ["_build"], + plt_core_path: "priv/plts", plt_file: {:no_warn, "priv/plts/eventstore.plt"} ] ] diff --git a/mix.lock b/mix.lock index 7340c6382e..5fecee1529 100644 --- a/mix.lock +++ b/mix.lock @@ -1,4 +1,4 @@ %{ - "dialyxir": {:hex, :dialyxir, "1.0.0-rc.7", "6287f8f2cb45df8584317a4be1075b8c9b8a69de8eeb82b4d9e6c761cf2664cd", [:mix], [{:erlex, ">= 0.2.5", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm"}, - "erlex": {:hex, :erlex, "0.2.5", "e51132f2f472e13d606d808f0574508eeea2030d487fc002b46ad97e738b0510", [:mix], [], "hexpm"}, + "dialyxir": {:hex, :dialyxir, "1.0.0-rc.7", "6287f8f2cb45df8584317a4be1075b8c9b8a69de8eeb82b4d9e6c761cf2664cd", [:mix], [{:erlex, ">= 0.2.5", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "506294d6c543e4e5282d4852aead19ace8a35bedeb043f9256a06a6336827122"}, + "erlex": {:hex, :erlex, "0.2.5", "e51132f2f472e13d606d808f0574508eeea2030d487fc002b46ad97e738b0510", [:mix], [], "hexpm", "756d3e19b056339af674b715fdd752c5dac468cf9d0e2d1a03abf4574e99fbf8"}, }