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 code.

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. For more information, see Managing GitHub Actions permissions for your repository in the GitHub documentation.

Use GitHub Actions#

You must declare the GitHub Actions to be executed in the CI process in a common ci.yml file 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:
  <Triggering 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. If you are using public GitHub hardware for running your workflows, disabling concurrent CI workflows is a way to show that you care about the environment and sustainability.

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 or a 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 PR. To do so, prior to the jobs section in the ci.yml file, add the following lines to your workflow:

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

Required workflows#

PyAnsys projects require workflows for performing these types of checks:

You should collect all workflows in 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/UNIX. For supported Python versions, see 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 in the GitHub documentation

Consider this example of a parametrized workflow:

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

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

Workflow examples#

Workflow examples are provided for various checks, such as code style, tests, documentation style, documentation building, and releasing.

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

doc-style:
  name: Doc style
  runs-on: ubuntu-latest
  steps:
    - name: "Run PyAnsys documentation style checks"
      uses: ansys/actions/doc-style@v4
      with:
        token: ${{ secrets.GITHUB_TOKEN }}
tests:
  name: "Test library"
  runs-on: ${{ matrix.os }}
  strategy:
     matrix:
         os: [ubuntu-latest, windows-latest]
         python-version: ['3.9', '3.10', '3.11', '3.12']
  steps:
    - name: "Run pytest"
      uses: ansys/actions/tests-pytest@v4
      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@v4
      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: ansys/actions/doc-build@v4
      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: ansys/actions/doc-deploy-dev@v4
      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: ansys/actions/doc-deploy-stable@v4
      with:
        cname: ${{ env.CNAME }}
        token: ${{ secrets.GITHUB_TOKEN }}

  doc-deploy-index:
    name: "Index the documentation and scrap using PyMeilisearch"
    runs-on: ubuntu-latest
    needs: doc-deploy-dev
    if: github.event_name == 'push'
    steps:
      - name: Scrape the stable documentation to PyMeilisearch
        run: |
          VERSION=$(python -c "from <your-package> import __version__; print('.'.join(__version__.split('.')[:2]))")
          VERSION_MEILI=$(python -c "from <your-package> import __version__; print('-'.join(__version__.split('.')[:2]))")
          echo "Calculated VERSION: $VERSION"
          echo "Calculated VERSION_MEILI: $VERSION_MEILI"
      
      - name: "Deploy the latest documentation index"
        uses: ansys/actions/doc-deploy-index@v4.1
        with:
          cname: "<library>.docs.pyansys.com/version/$VERSION"
          index-name: "<index-name>v$VERSION_MEILI"
          host-url: "<meilisearch-host-url>"
          api-key: ${{ secrets.MEILISEARCH_API_KEY }}
build-wheelhouse:
  name: Build the wheelhouse of the Python library
  runs-on: ${{ matrix.os }}
  strategy:
     matrix:
         os: [ubuntu-latest, windows-latest]
         python-version: ['3.9', '3.10', '3.11', '3.12']
  steps:
    - name: "Build a wheelhouse of the Python library"
      uses: ansys/actions/build-wheelhouse@v4
      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: ansys/actions/build-library@v4
      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: ansys/actions/release-pypi-private@v4
      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: ansys/actions/release-pypi-public@v4
      with:
        library-name: ${{ env.LIBRARY_NAME }}
        twine-username: "__token__"
        twine-token: ${{ secrets.PYPI_TOKEN }}

release-github:
  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: ansys/actions/release-github@v4
      with:
        library-name: ${{ env.LIBRARY_NAME }}