Repository: openfisca/country-template (language: python)

Issue:
Lint YAML and Python files on CI
OpenFisca-France has tests to automatically lint [YAML](https://github.com/openfisca/openfisca-france/blob/master/.circleci/lint-changed-yaml-tests.sh) and [Python](https://github.com/openfisca/openfisca-france/blob/master/.circleci/lint-changed-python-files.sh) files.

I believe the publishing process is already part of the OpenFisca Country Template suggestions, and that maintaining consistent quality and syntax is a meaningful way to ease contribution.

I thus suggest importing these tests, alongside any other improvements made to the testing procedure, to the Country Template (probably toning down a bit the tongue-in-cheek phrasings 😉).

Opinions welcome @openfisca/international-contrib!


Discussion / hints:
@MattiSG Should we only add the linting job, or restructure the entire CircleCI configuration like we did with `OpenFisca France` [here](https://github.com/openfisca/openfisca-france/pull/1650)?

The restructuring would include amongst other things:

*  Separating the `install` from the `build`
*  Separating the `build`and the `test`
*  Using `openfisca test` instead of `make test`, which does check for syntax errors and does some linting as well
*  Adding a separate job which lints Python and YAML files
*  Changing the job dependencies in the workflow
Yes, and we should also switch to GitHub Actions as agreed on https://github.com/openfisca/openfisca-core/issues/1030 😉
This issue is only about adding linting. It can be closed alongside others if a larger PR is open on that topic 🙂 

Fix patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
deleted file mode 100644
index 4051e4dc..00000000
--- a/.circleci/config.yml
+++ /dev/null
@@ -1,117 +0,0 @@
-# CircleCI 2.0 configuration file. See <https://circleci.com/docs/2.0/language-python/>.
-version: 2
-jobs:
-  build:
-    docker:
-      - image: python:3.7
-
-    steps:
-      - checkout
-
-      - restore_cache:
-          key: v1-py3-deps-{{ checksum "setup.py" }}
-
-      - run:
-          name: Create a virtualenv
-          command: |
-            mkdir -p /tmp/venv/country_template
-            python -m venv /tmp/venv/country_template
-            echo "source /tmp/venv/country_template/bin/activate" >> $BASH_ENV
-
-      - run:
-          name: Install dependencies
-          command: |
-            make build
-            # pip install --editable git+https://github.com/openfisca/openfisca-core.git@BRANCH#egg=OpenFisca-Core[web-api]  # use a specific branch of OpenFisca-Core
-
-      - save_cache:
-          key: v1-py3-deps-{{ checksum "setup.py" }}
-          paths:
-            - /tmp/venv/country_template
-
-      - save_cache:
-          key: v1-py3-build-{{ .Revision }}
-          paths:
-            - dist
-
-      - run:
-          name: Run tests
-          command: make test
-
-  check_version:
-    docker:
-      - image: python:3.7
-
-    steps:
-      - checkout
-
-      - run:
-          name: Check version number has been properly updated
-          command: |
-            git fetch
-            .circleci/is-version-number-acceptable.sh
-
-  test_api:
-    docker:
-      - image: python:3.7
-
-    steps:
-      - checkout
-
-      - restore_cache:
-          key: v1-py3-deps-{{ checksum "setup.py" }}
-
-      - run:
-          name: Activate virtualenv
-          command: echo "source /tmp/venv/country_template/bin/activate" >> $BASH_ENV
-
-      - run:
-          name: Test the Web API
-          command: .circleci/test-api.sh
-
-  deploy:
-    docker:
-      - image: python:3.7
-    environment:
-      PYPI_USERNAME: openfisca-bot  # Edit this value to replace it by your Pypi username
-      # PYPI_PASSWORD: this value is set in CircleCI's web interface; do not set it here, it is a secret!
-
-    steps:
-      - checkout
-
-      - restore_cache:
-          key: v1-py3-deps-{{ checksum "setup.py" }}
-
-      - restore_cache:
-          key: v1-py3-build-{{ .Revision }}
-
-      - run:
-          name: Check for functional changes
-          command: if ! .circleci/has-functional-changes.sh ; then circleci step halt ; fi
-
-      - run:
-          name: Upload a Python package to Pypi
-          command: |
-            source /tmp/venv/country_template/bin/activate
-            twine upload dist/* --username $PYPI_USERNAME --password $PYPI_PASSWORD
-
-      - run:
-          name: Publish a git tag
-          command: .circleci/publish-git-tag.sh
-
-workflows:
-  version: 2
-  build_and_deploy:
-    jobs:
-      - build
-      - test_api:
-          requires:
-            - build
-      - check_version
-      - deploy:
-          requires:
-            - build
-            - check_version
-          filters:
-            branches:
-              only: master
diff --git a/.circleci/has-functional-changes.sh b/.github/has-functional-changes.sh
similarity index 57%
rename from .circleci/has-functional-changes.sh
rename to .github/has-functional-changes.sh
index b787de99..517fbd6d 100755
--- a/.circleci/has-functional-changes.sh
+++ b/.github/has-functional-changes.sh
@@ -1,8 +1,8 @@
 #! /usr/bin/env bash
 
-IGNORE_DIFF_ON="README.md CONTRIBUTING.md Makefile .gitignore .circleci/* .github/*"
+IGNORE_DIFF_ON="README.md CONTRIBUTING.md Makefile .gitignore .github/*"
 
-last_tagged_commit=`git describe --tags --abbrev=0 --first-parent`  # --first-parent ensures we don't follow tags not published in master through an unlikely intermediary merge commit
+last_tagged_commit=$(git describe --tags --abbrev=0 --first-parent)  # --first-parent ensures we don't follow tags not published in master through an unlikely intermediary merge commit
 
 if git diff-index --name-only --exit-code $last_tagged_commit -- . `echo " $IGNORE_DIFF_ON" | sed 's/ / :(exclude)/g'`  # Check if any file that has not be listed in IGNORE_DIFF_ON has changed since the last tag was published.
 then
diff --git a/.circleci/is-version-number-acceptable.sh b/.github/is-version-number-acceptable.sh
similarity index 91%
rename from .circleci/is-version-number-acceptable.sh
rename to .github/is-version-number-acceptable.sh
index ae370e2a..b22a5966 100755
--- a/.circleci/is-version-number-acceptable.sh
+++ b/.github/is-version-number-acceptable.sh
@@ -1,6 +1,6 @@
 #! /usr/bin/env bash
 
-if [[ $CIRCLE_BRANCH == master ]]
+if [[ ${GITHUB_REF#refs/heads/} == master ]]
 then
     echo "No need for a version check on master."
     exit 0
@@ -12,7 +12,7 @@ then
     exit 0
 fi
 
-current_version=`python setup.py --version`
+current_version=$(python setup.py --version)
 
 if git rev-parse --verify --quiet $current_version
 then
diff --git a/.github/lint-changed-python-files.sh b/.github/lint-changed-python-files.sh
new file mode 100755
index 00000000..72d8aad6
--- /dev/null
+++ b/.github/lint-changed-python-files.sh
@@ -0,0 +1,11 @@
+#! /usr/bin/env bash
+
+last_tagged_commit=`git describe --tags --abbrev=0 --first-parent`  # --first-parent ensures we don't follow tags not published in master through an unlikely intermediary merge commit
+
+if ! changes=$(git diff-index --name-only --diff-filter=ACMR --exit-code $last_tagged_commit -- "*.py")
+then
+  echo "Linting the following Python files:"
+  echo $changes
+  flake8 $changes
+else echo "No changed Python files to lint"
+fi
diff --git a/.circleci/publish-git-tag.sh b/.github/publish-git-tag.sh
similarity index 65%
rename from .circleci/publish-git-tag.sh
rename to .github/publish-git-tag.sh
index 4450357c..ea0eb489 100755
--- a/.circleci/publish-git-tag.sh
+++ b/.github/publish-git-tag.sh
@@ -1,4 +1,4 @@
 #! /usr/bin/env bash
 
-git tag `python setup.py --version`
+git tag $(python setup.py --version)
 git push --tags  # update the repository version
diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml
new file mode 100644
index 00000000..f32bf232
--- /dev/null
+++ b/.github/workflows/workflow.yml
@@ -0,0 +1,156 @@
+name: Country-Template
+
+on: [ push ]
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+      - name: Set up Python
+        uses: actions/setup-python@v2
+        with:
+          python-version: 3.7.12 # Patch version must be specified to avoid any cache confusion, since the cache key depends on the full Python version. Any potentiel difference in patches between jobs will lead to a cache not found error.
+      - name: Cache build
+        id: restore-build
+        uses: actions/cache@v2
+        with:
+          path: ${{ env.pythonLocation }}
+          key: build-${{ env.pythonLocation }}-${{ hashFiles('setup.py') }}-${{ github.sha }} # Cache the entire build Python environment
+          restore-keys: |
+            build-${{ env.pythonLocation }}-${{ hashFiles('setup.py') }}
+            build-${{ env.pythonLocation }}-
+      - name: Build package
+        run: make build
+      - name: Cache release
+        id: restore-release
+        uses: actions/cache@v2
+        with:
+          path: dist
+          key: release-${{ env.pythonLocation }}-${{ hashFiles('setup.py') }}-${{ github.sha }}
+
+  lint-files:
+    runs-on: ubuntu-latest
+    needs: [ build ]
+    steps:
+      - uses: actions/checkout@v2
+        with:
+          fetch-depth: 0 # Fetch all the tags
+      - name: Set up Python
+        uses: actions/setup-python@v2
+        with:
+          python-version: 3.7.12
+      - name: Cache build
+        id: restore-build
+        uses: actions/cache@v2
+        with:
+          path: ${{ env.pythonLocation }}
+          key: build-${{ env.pythonLocation }}-${{ hashFiles('setup.py') }}-${{ github.sha }}
+      - run: make check-syntax-errors
+      - run: make check-style
+      - name: Lint Python files
+        run: "${GITHUB_WORKSPACE}/.github/lint-changed-python-files.sh"
+      - name: Lint YAML tests
+        run: "${GITHUB_WORKSPACE}/.github/lint-changed-yaml-tests.sh"
+
+  test-yaml:
+    runs-on: ubuntu-latest
+    needs: [ build ]
+    steps:
+      - uses: actions/checkout@v2
+      - name: Set up Python
+        uses: actions/setup-python@v2
+        with:
+          python-version: 3.7.12
+      - name: Cache build
+        id: restore-build
+        uses: actions/cache@v2
+        with:
+          path: ${{ env.pythonLocation }}
+          key: build-${{ env.pythonLocation }}-${{ hashFiles('setup.py') }}-${{ github.sha }}
+      - run: openfisca test --country-package openfisca_country_template openfisca_country_template/tests
+
+  test-api:
+    runs-on: ubuntu-latest
+    needs: [ build ]
+    steps:
+      - uses: actions/checkout@v2
+      - name: Set up Python
+        uses: actions/setup-python@v2
+        with:
+          python-version: 3.7.12
+      - name: Cache build
+        id: restore-build
+        uses: actions/cache@v2
+        with:
+          path: ${{ env.pythonLocation }}
+          key: build-${{ env.pythonLocation }}-${{ hashFiles('setup.py') }}-${{ github.sha }}
+      - name: Test the Web API
+        run: "${GITHUB_WORKSPACE}/.github/test-api.sh"
+
+  check-version-and-changelog:
+    runs-on: ubuntu-latest
+    needs: [ lint-files, test-yaml, test-api ] # Last job to run
+    steps:
+      - uses: actions/checkout@v2
+        with:
+          fetch-depth: 0 # Fetch all the tags
+      - name: Set up Python
+        uses: actions/setup-python@v2
+        with:
+          python-version: 3.7.12
+      - name: Check version number has been properly updated
+        run: "${GITHUB_WORKSPACE}/.github/is-version-number-acceptable.sh"
+
+  # GitHub Actions does not have a halt job option, to stop from deploying if no functional changes were found.
+  # We build a separate job to substitute the halt option.
+  # The `deploy` job is dependent on the output of the `check-for-functional-changes`job.
+  check-for-functional-changes:
+    runs-on: ubuntu-latest
+    if: github.ref == 'refs/heads/master' # Only triggered for the `master` branch
+    needs: [ check-version-and-changelog ]
+    outputs:
+      status: ${{ steps.stop-early.outputs.status }}
+    steps:
+      - uses: actions/checkout@v2
+        with:
+          fetch-depth: 0 # Fetch all the tags
+      - name: Set up Python
+        uses: actions/setup-python@v2
+        with:
+          python-version: 3.7.12
+      - id: stop-early
+        run: if "${GITHUB_WORKSPACE}/.github/has-functional-changes.sh" ; then echo "::set-output name=status::success" ; fi
+
+  deploy:
+    runs-on: ubuntu-latest
+    needs: [ check-for-functional-changes ]
+    if: needs.check-for-functional-changes.outputs.status == 'success'
+    env:
+      PYPI_USERNAME: openfisca-bot
+      PYPI_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
+    steps:
+      - uses: actions/checkout@v2
+        with:
+          fetch-depth: 0 # Fetch all the tags
+      - name: Set up Python
+        uses: actions/setup-python@v2
+        with:
+          python-version: 3.7.12
+      - name: Cache build
+        id: restore-build
+        uses: actions/cache@v2
+        with:
+          path: ${{ env.pythonLocation }}
+          key: build-${{ env.pythonLocation }}-${{ hashFiles('setup.py') }}-${{ github.sha }}
+      - name: Cache release
+        id: restore-release
+        uses: actions/cache@v2
+        with:
+          path: dist
+          key: release-${{ env.pythonLocation }}-${{ hashFiles('setup.py') }}-${{ github.sha }}
+      - name: Upload a Python package to PyPi
+        run: twine upload dist/* --username $PYPI_USERNAME --password $PYPI_PASSWORD
+      - name: Publish a git tag
+        run: "${GITHUB_WORKSPACE}/.github/publish-git-tag.sh"
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1a83ea33..c2ba2bcf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,12 @@
 # Changelog
 
-### 3.12.10 - [#115](https://github.com/openfisca/country-template/pull/115)
+## 3.13.0 - [#115](https://github.com/openfisca/country-template/pull/115)
+
+* Technical improvement.
+* Details:
+  - Switch continuous integration pipeline from CircleCI to GitHub Actions 
+
+### 3.12.10 - [#119](https://github.com/openfisca/country-template/pull/119)
 
 * Technical improvement.
 * Details:
diff --git a/bootstrap.sh b/bootstrap.sh
index 5a1f459e..0bb1b983 100755
--- a/bootstrap.sh
+++ b/bootstrap.sh
@@ -36,8 +36,9 @@ all_module_files=`find openfisca_country_template -type f`
 set -x
 
 # Use intermediate backup files (`-i`) with a weird syntax due to lack of portable 'no backup' option. See https://stackoverflow.com/q/5694228/594053.
-sed -i.template "s|country_template|$lowercase_country_name|g" README.md setup.py .circleci/config.yml Makefile MANIFEST.in $all_module_files
-sed -i.template "s|Country-Template|$COUNTRY_NAME|g" README.md setup.py .github/PULL_REQUEST_TEMPLATE.md CONTRIBUTING.md $all_module_files
+# If you're using MacOS, add LC_ALL=C before sed https://stackoverflow.com/questions/19242275/re-error-illegal-byte-sequence-on-mac-os-x/19770395#19770395
+sed -i.template "s|country_template|$lowercase_country_name|g" README.md setup.py .github/workflows/workflow.yml Makefile MANIFEST.in $all_module_files
+sed -i.template "s|Country-Template|$COUNTRY_NAME|g" README.md setup.py .github/workflows/workflow.yml .github/PULL_REQUEST_TEMPLATE.md CONTRIBUTING.md $all_module_files
 sed -i.template -e "3,${last_bootstrapping_line_number}d" README.md  # remove instructions lines
 sed -i.template "s|country-template|$lowercase_country_name|g" README.md
 sed -i.template "s|https://github.com/openfisca/openfisca-country-template|$URL|g" setup.py
diff --git a/setup.py b/setup.py
index 4bbed210..b90d490e 100644
--- a/setup.py
+++ b/setup.py
@@ -4,7 +4,7 @@
 
 setup(
     name = "OpenFisca-Country-Template",
-    version = "3.12.10",
+    version = "3.13.0",
     author = "OpenFisca Team",
     author_email = "contact@openfisca.org",
     classifiers = [


Test patch:
diff --git a/.github/lint-changed-yaml-tests.sh b/.github/lint-changed-yaml-tests.sh
new file mode 100755
index 00000000..16e99432
--- /dev/null
+++ b/.github/lint-changed-yaml-tests.sh
@@ -0,0 +1,11 @@
+#! /usr/bin/env bash
+
+last_tagged_commit=`git describe --tags --abbrev=0 --first-parent`  # --first-parent ensures we don't follow tags not published in master through an unlikely intermediary merge commit
+
+if ! changes=$(git diff-index --name-only --diff-filter=ACMR --exit-code $last_tagged_commit -- "tests/*.yaml")
+then
+  echo "Linting the following changed YAML tests:"
+  echo $changes
+  yamllint $changes
+else echo "No changed YAML tests to lint"
+fi
diff --git a/.circleci/test-api.sh b/.github/test-api.sh
similarity index 100%
rename from .circleci/test-api.sh
rename to .github/test-api.sh