Using continuous integration#

Continuous Integration (CI) is the process of merging new changes into the main code base while ensuring that these changes are functional and do not break the existing logic.

This process is automated as much as possible to alleviate the developer’s workload and ensure a quick development workflow.

Because PyAnsys projects are hosted in GitHub, the GitHub Actions framework is used.

Enable GitHub actions#

By default, Actions are enabled in new repositories and can be accessed using the associated GitHub repository sections.

If Actions are not enabled, you can enable them by changing Actions Permissions in Settings -> Actions -> General.

Use GitHub actions#

Actions to be executed in the CI process must be declared in a YML and stored in the .github/workflows/ directory. Although each action is different, they all have a common structure:

  • A name identifying the action.

  • A collection of triggering events that run the action when required.

  • A collection of concurrent workflows conditions to, for example, avoid running several workflows for the same branch. (Multiple consecutive pushes could lead to multiple ongoing workflows when you want only the last push to run).

  • A collection of jobs with different steps to follow during the CI process.

name: <Name of the action>

on:
  <Trigering events and conditions>

concurrency:
  <Avoid concurrent workflows to be run>

jobs:
  <All jobs must be defined below this line>

Disable concurrent workflows#

Handling hardware resources is a big deal, especially when running with self-hosted agents. Also, if you are using public GitHub hardware for running your workflows, you should try to care about the environment and sustainability.

Disabling concurrent CI workflows is a good way to do so. For example, imagine the following situation:

  • You push some changes to your branch.

  • The CI workflow kicks in and starts executing the different stages.

  • You suddenly realize that there is a typo/file missing.

  • You push the new commit to your PR.

  • A new CI workflow kicks in and starts running.

At this moment, you probably have two parallel workflows running at the same time, though you are only interested in the results from the last one.

One way to solve this is manually cancelling the oldest workflow. However, it is also possible to automatically cancel pre-existing workflows for a certain branch/PR. To do so, prior to the jobs section, you should add the following lines to your workflow:

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

Required workflows#

These workflows are required for any PyAnsys project:

You should collect all workflows under a common ci.yml file. For more information, see Workflow examples.

Parametrize workflows#

It is important to test a PyAnsys library on different operating systems using different Python versions:

\[\text{Num. Workflows} = \text{Num. Operating Systems} \times \text{Num. Python Versions}\]

The most common operating systems are Windows, macOS, and Linux. For Python versions, see Supporting Python versions.

Because having a YML file for each workflow would be tedious, GitHub Actions provides the matrix parameter inside the strategy. For more information, see Using a Matrix for your Jobs.

Consider this example of a parametrized workflow example:

jobs:
  example_matrix:
    strategy:
      matrix:
        python: ['3.7', '3.8', '3.9', '3.10']
        os: [windows-latest, macos-latest, ubuntu-latest]

    steps:
      - echo "Running Python ${{ matrix.python }} in ${{ matrix.os }}"
Running Python 3.7 in windows-latest
Running Python 3.8 in windows-latest
Running Python 3.9 in windows-latest
Running Python 3.10 in windows-latest
Running Python 3.7 in macos-latest
Running Python 3.8 in macos-latest
Running Python 3.9 in macos-latest
Running Python 3.10 in macos-latest
Running Python 3.7 in ubuntu-latest
Running Python 3.8 in ubuntu-latest
Running Python 3.9 in ubuntu-latest
Running Python 3.10 in ubuntu-latest

Workflow examples#

Workflow examples are provided for checking Coding style, Documenting, Testing, and Releasing and publishing.

code-style:
  name: Code style
  runs-on: ubuntu-latest
  steps:
    - name: "Run PyAnsys code style checks"
      uses: pyansys/actions/code-style@v3

doc-style:
  name: Doc style
  runs-on: ubuntu-latest
  steps:
    - name: "Run PyAnsys documentation style checks"
      uses: pyansys/actions/doc-style@v3
      with:
        token: ${{ secrets.GITHUB_TOKEN }}
tests:
  name: "Test library"
  runs-on: ${{ matrix.os }}
  strategy:
     matrix:
         os: [ubuntu-latest, windows-latest]
         python-version: ['3.7', '3.8', '3.9', '3.10']
  steps:
    - name: "Run pytest"
      uses: pyansys/actions/tests-pytest@v3
      with:
        python-version: ${{ matrix.python-version }}
        pytest-markers: "-k 'mocked'"
        pytest-extra-args: "--cov=ansys.<library> --cov-report=term --cov-report=xml:.cov/coverage.xml --cov-report=html:.cov/html"

    - name: "Upload coverage results"
      uses: actions/upload-artifact@v3
      if: matrix.python-version == ${{ env.MAIN_PYTHON_VERSION }}
      with:
        name: coverage-html
        path: .cov/html
        retention-days: 7
doc-build:
  name: "Build project documentation"
  runs-on: ubuntu-latest
  steps:
    - name: "Build project documentation"
      uses: pyansys/actions/doc-build@v3
      with:
        python-version: ${{ env.MAIN_PYTHON_VERSION }}

doc-deploy-dev:
  name: "Deploy development documentation"
  if: github.event_name == 'push'
  runs-on: ubuntu-latest
  needs: doc-build
  steps:
    - name: "Deploy development documentation"
      uses: pyansys/actions/doc-deploy-dev@v3
      with:
        cname: ${{ env.CNAME }}
        token: ${{ secrets.GITHUB_TOKEN }}

doc-deploy-stable:
  name: "Deploy stable documentation"
  if: github.event_name == 'push' && contains(github.ref, 'refs/tags')
  runs-on: ubuntu-latest
  needs: doc-deploy-dev
  steps:
    - name: "Deploy stable documentation"
      uses: pyansys/actions/doc-deploy-stable@v3
      with:
        cname: ${{ env.CNAME }}
        token: ${{ secrets.GITHUB_TOKEN }}
build-wheelhouse:
  name: Build the wheelhouse of the Python library
  runs-on: ${{ matrix.os }}
  strategy:
     matrix:
         os: [ubuntu-latest, windows-latest]
         python-version: ['3.7', '3.8', '3.9', '3.10']
  steps:
    - name: "Build a wheelhouse of the Python library"
      uses: pyansys/actions/build-wheelhouse@v3
      with:
        library-name: ${{ env.LIBRARY_NAME }}
        library-namespace: ${{ env.LIBRARY_NAMESPACE }}
        operating-system: ${{ matrix.os }}
        python-version: ${{ matrix.python-version }}

build-library:
  name: Build library
  runs-on: ubuntu-latest
  steps:
    - name: "Build library source and wheel artifacts"
      uses: pyansys/actions/build-library@v3
      with:
        library-name: ${{ env.LIBRARY_NAME }}
release-pypi-private:
  name: "Release to the private PyPI repository"
  runs-on: ubuntu-latest
  needs: [build-library]
  steps:
    - name: "Release to the private PyPI repository"
      if: github.event_name == 'push' && contains(github.ref, 'refs/tags')
      uses: pyansys/actions/release-pypi-private@v3
      with:
        library-name: ${{ env.LIBRARY_NAME }}
        twine-username: "__token__"
        twine-token: ${{ secrets.PYANSYS_PYPI_PRIVATE_PAT }}

release-pypi-public:
  name: "Release to the public PyPI repository"
  runs-on: ubuntu-latest
  needs: [release-pypi-private]
  steps:
    - name: "Release to the public PyPI repository"
      if: github.event_name == 'push' && contains(github.ref, 'refs/tags')
      uses: pyansys/actions/release-pypi-public@v3
      with:
        library-name: ${{ env.LIBRARY_NAME }}
        twine-username: "__token__"
        twine-token: ${{ secrets.PYPI_TOKEN }}

release-gitub:
  name: "Release to GitHub"
  runs-on: ubuntu-latest
  needs: [release-pypi-public]
  steps:
    - name: "Release to GitHub"
      if: github.event_name == 'push' && contains(github.ref, 'refs/tags')
      uses: pyansys/actions/release-github@v3
      with:
        library-name: ${{ env.LIBRARY_NAME }}