Skip to content

Host standardized ClangFormat configuration file #250

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Jul 26, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions .github/workflows/check-clang-format.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ on:
paths:
- ".github/workflows/check-clang-format.yml"
- "other/clang-format-configuration/scripts/convert-clang-format-configuration.js"
- "other/clang-format-configuration/testdata/**"
- "other/clang-format-configuration/.clang-format"
- "package.json"
- "package-lock.json"
Expand All @@ -18,6 +19,7 @@ on:
paths:
- ".github/workflows/check-clang-format.yml"
- "other/clang-format-configuration/scripts/convert-clang-format-configuration.js"
- "other/clang-format-configuration/testdata/**"
- "other/clang-format-configuration/.clang-format"
- "package.json"
- "package-lock.json"
Expand All @@ -26,6 +28,12 @@ on:
# Run periodically to catch breakage caused by external changes.
- cron: "0 17 * * WED"
workflow_dispatch:
inputs:
clang-format-version:
description: ClangFormat version (leave empty for standard version)
type: string
default: ""
required: false
repository_dispatch:

jobs:
Expand All @@ -50,6 +58,80 @@ jobs:
- name: Validate ClangFormat configuration files
run: task --silent clang-format:validate

check-output:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v3

- name: Install Task
uses: arduino/setup-task@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
version: 3.x

- name: Set environment variables
run: |
# See: https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#setting-an-environment-variable
if [[ "${{ github.event.inputs.clang-format-version }}" == "" ]]; then
echo "CLANG_FORMAT_VERSION=$(task clang-format:get-version)" >> "$GITHUB_ENV"
else
echo "CLANG_FORMAT_VERSION=${{ github.event.inputs.clang-format-version }}" >> "$GITHUB_ENV"
fi
echo "CLANG_FORMAT_INSTALL_PATH=${{ runner.temp }}/clang-format" >> "$GITHUB_ENV"
echo "WORKING_FOLDER=${{ runner.temp }}" >> "$GITHUB_ENV"

- name: Download ClangFormat
id: download
uses: MrOctopus/[email protected]
with:
repository: arduino/clang-static-binaries
tag: ${{ env.CLANG_FORMAT_VERSION }}
asset: clang-format_${{ env.CLANG_FORMAT_VERSION }}_Linux_64bit.tar.bz2
target: ${{ env.CLANG_FORMAT_INSTALL_PATH }}

- name: Install ClangFormat
run: |
cd "${{ env.CLANG_FORMAT_INSTALL_PATH }}"
tar --extract --file="${{ steps.download.outputs.name }}"
# Add installation to PATH:
# See: https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-system-path
echo "${{ env.CLANG_FORMAT_INSTALL_PATH }}/clang_Linux_64bit" >> "$GITHUB_PATH"

- name: Check ClangFormat output
id: check
run: |
task \
clang-format:check-output \
CLANG_FORMAT_VERSION="${{ env.CLANG_FORMAT_VERSION }}" \
WORKING_FOLDER="${{ env.WORKING_FOLDER }}"

- name: Save formatted test data to a workflow artifact
if: >
always() &&
steps.check.outcome == 'failure'
uses: actions/upload-artifact@v3
with:
path: ${{ env.WORKING_FOLDER }}/output
if-no-files-found: error
name: testdata-output

check-testdata:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v3

- name: Install Task
uses: arduino/setup-task@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
version: 3.x

- name: Check ClangFormat test data
run: task --silent clang-format:check-testdata

convert:
runs-on: ubuntu-latest
Expand Down
111 changes: 111 additions & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ vars:
CLANG_FORMAT_INPUT_TEST_DATA_FOLDER: "{{.CLANG_FORMAT_TEST_DATA_FOLDER}}/input"
CLANG_FORMAT_TARGETED_INPUT_TEST_DATA_FOLDER: "{{.CLANG_FORMAT_INPUT_TEST_DATA_FOLDER}}/targeted"
CLANG_FORMAT_GOLDEN_TEST_DATA_FOLDER: "{{.CLANG_FORMAT_TEST_DATA_FOLDER}}/golden"
# See: https://github.com/arduino/arduino-ide/blob/main/arduino-ide-extension/package.json
DEFAULT_CLANG_FORMAT_VERSION: 14.0.0
# Last version of ajv-cli with support for the JSON schema "Draft 4" specification
SCHEMA_DRAFT_4_AJV_CLI_VERSION: 3.3.0

Expand All @@ -15,6 +17,8 @@ tasks:
desc: Check for problems with the project
deps:
- task: ci:validate
- task: clang-format:check-output
- task: clang-format:check-testdata
- task: clang-format:validate
- task: dependabot:validate
- task: eslint:validate
Expand Down Expand Up @@ -107,6 +111,74 @@ tasks:
-s "{{.WORKFLOW_SCHEMA_PATH}}" \
-d "{{.TEMPLATE_WORKFLOWS_DATA_PATH}}"

# Check if ClangFormat is installed and the expected version
clang-format:check-installed:
vars:
EXPECTED_CLANG_FORMAT_VERSION: "{{default .DEFAULT_CLANG_FORMAT_VERSION .CLANG_FORMAT_VERSION}}"
cmds:
- |
if ! which clang-format &>/dev/null; then
echo "clang-format not found or not in PATH. Please install: https://github.com/arduino/clang-static-binaries/releases"
exit 1
fi
- |
INSTALLED_CLANG_FORMAT_VERSION_ARRAY=($(clang-format --version))
INSTALLED_CLANG_FORMAT_VERSION="${INSTALLED_CLANG_FORMAT_VERSION_ARRAY[${#INSTALLED_CLANG_FORMAT_VERSION_ARRAY[@]}-1]}"
if [[ "$INSTALLED_CLANG_FORMAT_VERSION" != "{{.EXPECTED_CLANG_FORMAT_VERSION}}" ]]; then
echo "Installed version of clang-format $INSTALLED_CLANG_FORMAT_VERSION (at $(which clang-format)) does not match expected {{.EXPECTED_CLANG_FORMAT_VERSION}}"
exit 1
fi

clang-format:check-output:
desc: Compare actual vs expected output of ClangFormat
vars:
WORKING_FOLDER:
sh: |
if [[ "{{.WORKING_FOLDER}}" == "" ]]; then
# Generate a path
task utility:mktemp-folder TEMPLATE="clang-format-check-XXXXXXXXXX"
else
# A path was specified via the command line
echo "{{.WORKING_FOLDER}}"
fi
deps:
- task: clang-format:check-installed
cmds:
- |
cp \
--recursive \
--target-directory="{{.WORKING_FOLDER}}" \
"{{.CLANG_FORMAT_GOLDEN_TEST_DATA_FOLDER}}" \
"{{.CLANG_FORMAT_INPUT_TEST_DATA_FOLDER}}"
- task: clang-format:format
vars:
TARGET_FOLDER: "{{.WORKING_FOLDER}}/input/"
- |
# Give the folder an appropriate name to make the diff easier to understand
mv "{{.WORKING_FOLDER}}/input/" "{{.WORKING_FOLDER}}/output/"
- |
cd "{{.WORKING_FOLDER}}"
diff --color=always --recursive --unified "golden/" "output/"

clang-format:check-testdata:
desc: Check whether the targeted test data C++ code for the ClangFormat configuration is valid and correct
cmds:
- |
if ! which g++ &>/dev/null; then
echo "GCC not found or not in PATH."
exit 1
fi
- |
# Only the "targeted" test data is checked because the "samples" test data is not maintained in this repo.
g++ \
-fconcepts \
-fsyntax-only \
-I "{{.CLANG_FORMAT_TARGETED_INPUT_TEST_DATA_FOLDER}}/include" \
-Wall \
-Werror \
-Wextra \
"{{.CLANG_FORMAT_TARGETED_INPUT_TEST_DATA_FOLDER}}"/*.cpp

clang-format:convert:
desc: Convert the ClangFormat configuration file into the JavaScript object used by Arduino IDE 2.x
vars:
Expand All @@ -126,6 +198,45 @@ tasks:
"{{.CLANG_FORMAT_CONFIGURATION_PATH}}" \
"{{.OUTPUT_PATH}}"

# Use ClangFormat to format the files under the path specified by TARGET_FOLDER recursively
clang-format:format:
cmds:
- |
find \
"{{.TARGET_FOLDER}}" \
\( \
-name '*.c' -or \
-name '*.cpp' -or \
-name '*.h' -or \
-name '*.ino' -or \
-name '*.inot' -or \
-name '*.ipp' -or \
-name '*.tpp' -and \
-type f \
\) \
-exec \
clang-format \
--assume-filename=foo.cpp \
-i \
--style=file:"{{.CLANG_FORMAT_CONFIGURATION_PATH}}" \
{} \;

# Print the standard version of ClangFormat for current use
clang-format:get-version:
cmds:
- echo "{{.DEFAULT_CLANG_FORMAT_VERSION}}"

clang-format:update-golden:
desc: Update golden master test data for current configuration
deps:
- task: clang-format:check-installed
cmds:
- rm --recursive "{{.CLANG_FORMAT_GOLDEN_TEST_DATA_FOLDER}}/"
- cp --recursive "{{.CLANG_FORMAT_INPUT_TEST_DATA_FOLDER}}/" "{{.CLANG_FORMAT_GOLDEN_TEST_DATA_FOLDER}}/"
- task: clang-format:format
vars:
TARGET_FOLDER: "{{.CLANG_FORMAT_GOLDEN_TEST_DATA_FOLDER}}/"

clang-format:validate:
desc: Validate ClangFormat configuration file against its JSON schema
vars:
Expand Down
58 changes: 58 additions & 0 deletions other/clang-format-configuration/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,61 @@ For this reason, it is necessary to carefully validate the configuration in prep

- Changes to the configuration file
- Updating the version of **ClangFormat** used in Arduino's projects

Validation is done by formatting a collection of test data files and then comparing the result against "golden masters" which have the intended formatting. The repository's continuous integration system does this check after every change to relevant files. It can also be run locally using [the **Task** task runner tool](https://taskfile.dev/):

```text
task clang-format:check-configuration
```

### ClangFormat version updates

The validation is done using Arduino's current standard version of **ClangFormat** by default. When an update to a different version of **ClangFormat** is planned, configuration validation must be done against the candidate version.

#### Running the check locally

1. Install the candidate **ClangFormat** version on your machine.
**ⓘ** In addition to the standard sources, Arduino provides builds [here](https://github.com/arduino/clang-static-binaries/releases)
1. Add the location of the **ClangFormat** installation to your `PATH`.
1. Run the Task command, specifying the version via an argument:
```text
task clang-format:check-configuration CLANG_FORMAT_VERSION=<version>
```
**ⓘ**Replace `<version>` in the command with the version of the **ClangFormat** candidate.

#### Running the check via GitHub Actions

The GitHub Actions workflow can be triggered manually, with the option to specify an arbitrary version of **ClangFormat** to run the validation against.

1. Check whether a build of the candidate version is available [here](https://github.com/arduino/clang-static-binaries/releases).
If not, submit an issue in that repository requesting a build.
1. Open https://github.com/arduino/tooling-project-assets/actions/workflows/check-clang-format.yml
1. Click the <kbd>**Run Workflow**</kbd> button.
1. Enter the **ClangFormat** candidate version in the "**ClangFormat version**" input field.
1. Click the <kbd>**Run Workflow**</kbd> button.
1. Wait for the workflow run to finish.
1. Check the results.

#### Updating the default ClangFormat version

Once a new version of **ClangFormat** has been adopted as the standard for use in Arduino projects, it must be set as the default for use in the validations. This default version is defined by the `vars.DEFAULT_CLANG_FORMAT_VERSION` field of [`Taskfile.yml`](../../Taskfile.yml).

### Updating the "golden masters"

The goal is for the configuration to produce output compliant with the established official Arduino code style. For this reason, differences in output resulting from a configuration change or **ClangFormat** update should not be taken lightly.

When changes are unavoidable, the test data "golden masters" must be updated accordingly:

#### Via local operation

Run the following command

```text
task clang-format:update-golden
```

#### Via GitHub Actions

As an alternative to running the update command locally, you can download the files from a workflow artifact named "**testdata-output**" which is available in the "**Summary**" page of each workflow run where the `check-output` job of the "**Check ClangFormat Configuration**" workflow failed due to the current golden masters not matching the output.

Save the contents of the downloaded ZIP file to [the `testdata/golden/` folder](testdata/golden/).