diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 101bcb4..0000000 --- a/.flake8 +++ /dev/null @@ -1,59 +0,0 @@ -[flake8] -max-line-length = 120 - -extend-ignore = - # black - E203, - # too many leading '#' for block comment - E266, - # expected 2 blank lines, found 1 - E302, - # do not use mutable data structures for argument defaults (too many false positives) - B006, - # ===== TODO: to be fixed: - # invalid escape sequence, necessary for sphinx directives in docstrings but should switch to raw string - W605, - # line length, exceeded by some docstrings - E501, - # Function definition does not bind loop variable, happens everywhere in our code - B023, - # pydocstyle - D - -# Only add patterns here that are not included by the defaults of flake8 or other plugins -# extend-select = - -# flake8-docstrings -docstring-convention = numpy - -# flake8-rst-docstrings -rst-roles = - class, - func, - ref, - meth, - -rst-directives = - # Custom directives defined in the sphinx_mdolab_theme - embed-compare, - embed-bibtex, - embed-code, - embed-shell-cmd, - embed-n2, - -# mccabe complexity -# max-complexity = 10 - -# ignored files/directories -# we use exclude here and extend-exclude in repo-specific config files -# so that we can pass both to flake8 directly without needing to merge them first -exclude = - # No need to traverse the git directory - .git, - # There's no value in checking cache directories - __pycache__, - # The conf file is mostly autogenerated, ignore it - doc/conf.py, - # No need for init and setup files - __init__.py, - setup.py, diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index d7caabd..5a506c4 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -32,7 +32,7 @@ Select the appropriate type(s) that describe this PR ## Checklist -- [ ] I have run `flake8` and `black` to make sure the Python code adheres to PEP-8 and is consistently formatted +- [ ] I have run `ruff check` and `ruff format` to make sure the Python code adheres to PEP-8 and is consistently formatted - [ ] I have formatted the Fortran code with `fprettify` or C/C++ code with `clang-format` as applicable - [ ] I have run unit and regression tests which pass locally with my changes - [ ] I have added new tests that prove my fix is effective or that my feature works diff --git a/.github/workflows/black.yaml b/.github/workflows/black.yaml deleted file mode 100644 index b87347b..0000000 --- a/.github/workflows/black.yaml +++ /dev/null @@ -1,17 +0,0 @@ -on: - workflow_call: - inputs: - black_version: - required: false - type: string - default: "23.1.0" - -jobs: - black: - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v4 - - uses: psf/black@stable - with: - options: "--check --diff -l 120 --target-version py39 --target-version py311" - version: ${{ inputs.black_version }} diff --git a/.github/workflows/flake8.yaml b/.github/workflows/flake8.yaml deleted file mode 100644 index e03de52..0000000 --- a/.github/workflows/flake8.yaml +++ /dev/null @@ -1,34 +0,0 @@ -on: - workflow_call: - -jobs: - flake8: - runs-on: ubuntu-24.04 - strategy: - matrix: - python-version: [3.9, 3.11] - steps: - - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - pip install wheel - wget https://raw.githubusercontent.com/mdolab/.github/main/flake8-requirements.txt - pip install -r flake8-requirements.txt - - name: Lint with flake8 - run: | - if [[ -f ".flake8" ]]; then - export FL8=.flake8-project - mv .flake8 $FL8; # rename the file from code repo; should have higher precedence in merge - fi - - wget https://raw.githubusercontent.com/mdolab/.github/main/.flake8; - - if [[ -f "$FL8" ]]; then - flake8 . --append-config $FL8 --count --show-source --statistics; - else - flake8 . --count --show-source --statistics; - fi diff --git a/.github/workflows/format-and-lint.yaml b/.github/workflows/format-and-lint.yaml new file mode 100644 index 0000000..cfd9a4c --- /dev/null +++ b/.github/workflows/format-and-lint.yaml @@ -0,0 +1,32 @@ +on: + workflow_call: + +jobs: + format-and-lint: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v5 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.12 + - name: Install dependencies + run: | + wget https://raw.githubusercontent.com/mdolab/.github/main/.pre-commit-config.yaml + pip install pre-commit + # If there's already a ruff.toml file in the repo, we should rename the file we're downloading to ruff.toml-global, then put the line `extend = "ruff.toml-global"` at the top of the local ruff.toml so that ruff will use both configurations. + url=https://raw.githubusercontent.com/mdolab/.github/main/ruff.toml + if [[ -f "ruff.toml" ]]; then + wget $url -O ruff.toml-global; + sed -i '1s/^/extend = "ruff.toml-global" \n/' ruff.toml + else + wget $url + fi + echo "Ruff config:" + if [[ -f "ruff.toml-global" ]]; then + cat ruff.toml-global + fi + cat ruff.toml + - name: Run pre-commit checks + run: | + pre-commit run --all-files --show-diff-on-failure diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..36a9353 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,20 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-json + - id: mixed-line-ending + - id: check-merge-conflict + - id: debug-statements +- repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.12.10 + hooks: + # Run the linter. + - id: ruff-check + args: [ --fix, --exit-non-zero-on-fix ] + # Run the formatter. + - id: ruff-format diff --git a/README.md b/README.md index 55ff7e4..9c726c0 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,8 @@ This repo stores the following shared repository settings/configurations/templates: - Issue and pull request templates on GitHub - Shared configurations for - - `flake8` + - `pre-commit` + - `ruff` - `isort` - `pylint` - `codecov` diff --git a/azure/README.md b/azure/README.md index b2b8a57..3b6737e 100644 --- a/azure/README.md +++ b/azure/README.md @@ -10,29 +10,30 @@ The templates are organized into the following files: - `azure_template.yaml` which handles the job itself, calling the necessary sub-templates. ## Template Options -| Name | Type | Default | Description. | -| :------------------ | :------ | :--------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------ | -| `REPO_NAME` | string | | Name of repository | -| `COMPLEX` | boolean | `false` | Flag for triggering complex build and tests | -| `TIMEOUT_BUILD` | number | `120` | Runtime allowed for each build and test job, in minutes | -| `GCC_CONFIG` | string | `None` | Path to GCC configuration file (from repository root) | -| `INTEL_CONFIG` | string | `None` | Path to Intel configuration file (from repository root) | -| `BUILD_REAL` | string | `.github/build_real.sh` | Path to Bash script with commands to build real code. Using `None` will skip this step. | -| `TEST_REAL` | string | `.github/text_real.sh` | Path to Bash script to run real tests. Using `None` will skip this step. | -| `BUILD_COMPLEX` | string | `.github/build_complex.sh` | Path to Bash script with commands to build complex code. Using `None` will skip this step. | -| `TEST_COMPLEX` | string | `.github/text_complex.sh` | Path to Bash script with commands to run complex tests. Using `None` will skip this step. | -| `IMAGE` | string | `public` | Select Docker image. Can be `public`, `private`, or `auto`. `auto` uses the private image on trusted builds and the public image otherwise. | -| `COVERAGE` | boolean | `false` | Flag to report test coverage to `codecov` | -| `TIMEOUT_STYLE` | number | `10` | Runtime allowed for each style check, in minutes | -| `IGNORE_STYLE` | boolean | `false` | Flag to allow `black` and `flake8` checks to fail without failing the pipeline | -| `ISORT` | boolean | `false` | Flag to trigger the `isort` check | -| `PYLINT` | boolean | `false` | Flag to trigger the `pylint` check | -| `CLANG_FORMAT` | boolean | `false` | Flag to trigger the `clang-format` check | -| `FPRETTIFY` | boolean | `false` | Flag to trigger the `fprettify` check | -| `TAPENADE` | boolean | `false` | Flag to trigger the Tapenade check | -| `TIMEOUT_TAPENADE` | number | `10` | Runtime allowed for the Tapenade check, in minutes | -| `TAPENADE_SCRIPT` | string | `.github/build_tapenade.sh` | Path to Bash script with commands to run Tapenade | -| `TAPENADE_VERSION` | string | `3.10` | Version of Tapenade to use | +| Name | Type | Default | Description. | +| :----------------- | :------ | :-------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------ | +| `REPO_NAME` | string | | Name of repository | +| `COMPLEX` | boolean | `false` | Flag for triggering complex build and tests | +| `TIMEOUT_BUILD` | number | `120` | Runtime allowed for each build and test job, in minutes | +| `GCC_CONFIG` | string | `None` | Path to GCC configuration file (from repository root) | +| `INTEL_CONFIG` | string | `None` | Path to Intel configuration file (from repository root) | +| `BUILD_REAL` | string | `.github/build_real.sh` | Path to Bash script with commands to build real code. Using `None` will skip this step. | +| `TEST_REAL` | string | `.github/text_real.sh` | Path to Bash script to run real tests. Using `None` will skip this step. | +| `BUILD_COMPLEX` | string | `.github/build_complex.sh` | Path to Bash script with commands to build complex code. Using `None` will skip this step. | +| `TEST_COMPLEX` | string | `.github/text_complex.sh` | Path to Bash script with commands to run complex tests. Using `None` will skip this step. | +| `IMAGE` | string | `public` | Select Docker image. Can be `public`, `private`, or `auto`. `auto` uses the private image on trusted builds and the public image otherwise. | +| `COVERAGE` | boolean | `false` | Flag to report test coverage to `codecov` | +| `TIMEOUT_STYLE` | number | `10` | Runtime allowed for each style check, in minutes | +| `IGNORE_STYLE` | boolean | `false` | Flag to allow `formatting and linting` checks to fail without failing the pipeline | +| `RUFF` | boolean | `false` | Flag to trigger the `ruff` check | +| `ISORT` | boolean | `false` | Flag to trigger the `isort` check | +| `PYLINT` | boolean | `false` | Flag to trigger the `pylint` check | +| `CLANG_FORMAT` | boolean | `false` | Flag to trigger the `clang-format` check | +| `FPRETTIFY` | boolean | `false` | Flag to trigger the `fprettify` check | +| `TAPENADE` | boolean | `false` | Flag to trigger the Tapenade check | +| `TIMEOUT_TAPENADE` | number | `10` | Runtime allowed for the Tapenade check, in minutes | +| `TAPENADE_SCRIPT` | string | `.github/build_tapenade.sh` | Path to Bash script with commands to run Tapenade | +| `TAPENADE_VERSION` | string | `3.10` | Version of Tapenade to use | ## Setting up a pipeline ### Step 1: Setup Azure Pipelines YAML File: diff --git a/azure/azure_style.yaml b/azure/azure_style.yaml index 5ccf121..bb0e8f4 100644 --- a/azure/azure_style.yaml +++ b/azure/azure_style.yaml @@ -8,6 +8,9 @@ parameters: - name: IGNORE_STYLE type: boolean default: false + - name: RUFF + type: boolean + default: true - name: ISORT type: boolean default: false @@ -20,61 +23,48 @@ parameters: - name: FPRETTIFY type: boolean default: false - jobs: - - job: flake8 + - job: ruff pool: vmImage: "ubuntu-22.04" timeoutInMinutes: ${{ parameters.TIMEOUT }} continueOnError: ${{ parameters.IGNORE_STYLE }} - strategy: - matrix: - "py39": - PYTHON_VERSION: "3.9" - "py311": - PYTHON_VERSION: "3.11" + condition: ${{ parameters.RUFF }} steps: - checkout: self - checkout: azure_template - task: UsePythonVersion@0 inputs: - versionSpec: $(PYTHON_VERSION) + versionSpec: "3.9" - script: | cd ${{ parameters.REPO_NAME }} - pip install wheel - pip install -r ../.github/flake8-requirements.txt - - if [[ -f ".flake8" ]]; then - export FL8=.flake8-project - mv .flake8 $FL8; # rename the file from code repo; should have higher precedence in merge - fi - - cp ../.github/.flake8 . + pip install ruff==0.12.10 - if [[ -f "$FL8" ]]; then - flake8 . --append-config $FL8 --count --show-source --statistics; + # If there's already a ruff.toml file in the repo, we should rename the global config to ruff.toml-global, then put the line `extend = "ruff.toml-global"` at the top of the local ruff.toml so that ruff will use both configurations. + globalConfigFile="../.github/ruff.toml" + if [[ -f "ruff.toml" ]]; then + cp $globalConfigFile ruff.toml-global + sed -i '1s/^/extend = "ruff.toml-global" \n/' ruff.toml else - flake8 . --count --show-source --statistics; + cp $globalConfigFile . fi + echo "Ruff config:" + if [[ -f "ruff.toml-global" ]]; then + cat ruff.toml-global + fi + cat ruff.toml - - job: black - pool: - vmImage: "ubuntu-22.04" - timeoutInMinutes: ${{ parameters.TIMEOUT }} - continueOnError: ${{ parameters.IGNORE_STYLE }} - steps: - - task: UsePythonVersion@0 - inputs: - versionSpec: "3.9" - - script: | - pip install wheel - pip install black==23.1.0 - black . --check --diff -l 120 --target-version py39 --target-version py311 + # Run the linting + ruff check . + + # Run the formatting + ruff format --check . - job: isort pool: vmImage: "ubuntu-22.04" timeoutInMinutes: ${{ parameters.TIMEOUT }} + continueOnError: ${{ parameters.IGNORE_STYLE }} condition: ${{ parameters.ISORT }} steps: - checkout: self @@ -98,6 +88,7 @@ jobs: pool: vmImage: "ubuntu-22.04" timeoutInMinutes: ${{ parameters.TIMEOUT }} + continueOnError: ${{ parameters.IGNORE_STYLE }} condition: ${{ parameters.PYLINT }} steps: - checkout: self @@ -118,6 +109,7 @@ jobs: pool: vmImage: "ubuntu-22.04" timeoutInMinutes: ${{ parameters.TIMEOUT }} + continueOnError: ${{ parameters.IGNORE_STYLE }} condition: ${{ parameters.CLANG_FORMAT }} steps: - checkout: self @@ -141,6 +133,7 @@ jobs: pool: vmImage: "ubuntu-22.04" timeoutInMinutes: ${{ parameters.TIMEOUT }} + continueOnError: ${{ parameters.IGNORE_STYLE }} condition: ${{ parameters.FPRETTIFY }} steps: - checkout: self @@ -165,3 +158,5 @@ jobs: # Exit with an error if any of the tracked files changed git diff --summary --exit-code + + diff --git a/azure/azure_template.yaml b/azure/azure_template.yaml index 20cc52f..d435042 100644 --- a/azure/azure_template.yaml +++ b/azure/azure_template.yaml @@ -54,6 +54,9 @@ parameters: - name: FPRETTIFY type: boolean default: false + - name: RUFF + type: boolean + default: false # Tapenade - name: TAPENADE type: boolean diff --git a/flake8-requirements.txt b/flake8-requirements.txt deleted file mode 100644 index 6be24e5..0000000 --- a/flake8-requirements.txt +++ /dev/null @@ -1,7 +0,0 @@ -flake8==5.0.4 -flake8-bugbear -flake8-builtins -flake8-docstrings -flake8-rst-docstrings -# flake8-comprehensions -# flake8-broken-line diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 0000000..ac85386 --- /dev/null +++ b/ruff.toml @@ -0,0 +1,47 @@ +# Minimum Python syntax support +target-version = "py39" + +line-length = 120 + +exclude = [ + ".git", + "**/__pycache__", + "doc/conf.py", + "**/__init__.py", + "setup.py", +] + +[lint] +ignore = [ + "E266", # Too many leading '#' for block comment + "B006", # do not use mutable data structures for argument defaults (too many false positives) + "E501", # line length, exceeded by some docstrings + "W605", # TODO (to be fixed), invalid escape sequence, necessary for sphinx directives in docstrings but should switch to raw string + "D301", # Same reason as W605 + "D10", # D100-107, missing docstring in public function, class etc (yes we should have docstrings for all public methods, but that is a pipe dream) + "D400", # First line of docstring should end with a period (assumes the summary fits on one line, which is not always the case) + "D205", # 1 blank line required between summary line and description (Makes same assumption as D400) + "D200", # One-line docstring should fit on one line, don't like this because it enforces inconsistent formatting between one-line and multi-line docstrings + "D202", # No blank lines allowed after function docstring (silly rule, if formatter doesn't enforce it then checks shouldn't either) + "D401", # First line of docstring should be in imperative mood (silly rule) + "D404", # First word of the docstring should not be "This" (Probably a good rule to follow going forward but it occurs in so many places in our code and it's not a big enough issue to warrant fixing) +] + +select = [ + "B", + "D", + "E", + "F", + "W", +] + +[lint.pydocstyle] +convention = "numpy" + +[format] +quote-style = "double" +indent-style = "space" +docstring-code-format = true + +[lint.per-file-ignores] +"tests/*" = ["D"] # Ignore docstring checks in tests