Skip to content

Add CI workflow to check for problems with shell scripts #11

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 5 commits into from
Dec 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
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
63 changes: 63 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/general/.editorconfig
# See: https://editorconfig.org/
# The formatting style defined in this file is the official standardized style to be used in all Arduino Tooling
# projects and should not be modified.
# Note: indent style for each file type is defined even when it matches the universal config in order to make it clear
# that this type has an official style.

[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true

[*.{adoc,asc,asciidoc}]
indent_size = 2
indent_style = space

[*.{bash,sh}]
indent_size = 2
indent_style = space

[*.{c,cc,cp,cpp,cxx,h,hh,hpp,hxx,ii,inl,ino,ixx,pde,tpl,tpp,txx}]
indent_size = 2
indent_style = space

[*.{go,mod}]
indent_style = tab

[*.java]
indent_size = 2
indent_style = space

[*.{js,jsx,json,jsonc,json5,ts,tsx}]
indent_size = 2
indent_style = space

[*.{md,mdx,mkdn,mdown,markdown}]
indent_size = unset
indent_style = space

[*.proto]
indent_size = 2
indent_style = space

[*.py]
indent_size = 4
indent_style = space

[*.svg]
indent_size = 2
indent_style = space

[*.{yaml,yml}]
indent_size = 2
indent_style = space

[{.gitconfig,.gitmodules}]
indent_style = tab

[deps/*/*]
ignore = true
177 changes: 177 additions & 0 deletions .github/workflows/check-shell-task.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/check-shell-task.md
name: Check Shell Scripts

# See: https://docs.github.com/actions/using-workflows/events-that-trigger-workflows
on:
create:
push:
paths:
- ".github/workflows/check-shell-task.ya?ml"
- "Taskfile.ya?ml"
- "**/.editorconfig"
- "**.bash"
- "**.sh"
pull_request:
paths:
- ".github/workflows/check-shell-task.ya?ml"
- "Taskfile.ya?ml"
- "**/.editorconfig"
- "**.bash"
- "**.sh"
schedule:
# Run every Tuesday at 8 AM UTC to catch breakage caused by tool changes.
- cron: "0 8 * * TUE"
workflow_dispatch:
repository_dispatch:

jobs:
run-determination:
runs-on: ubuntu-latest
outputs:
result: ${{ steps.determination.outputs.result }}
steps:
- name: Determine if the rest of the workflow should run
id: determination
run: |
RELEASE_BRANCH_REGEX="refs/heads/[0-9]+.[0-9]+.x"
# The `create` event trigger doesn't support `branches` filters, so it's necessary to use Bash instead.
if [[
"${{ github.event_name }}" != "create" ||
"${{ github.ref }}" =~ $RELEASE_BRANCH_REGEX
]]; then
# Run the other jobs.
RESULT="true"
else
# There is no need to run the other jobs.
RESULT="false"
fi

echo "result=$RESULT" >> $GITHUB_OUTPUT

lint:
name: ${{ matrix.configuration.name }}
needs: run-determination
if: needs.run-determination.outputs.result == 'true'
runs-on: ubuntu-latest

env:
# See: https://github.com/koalaman/shellcheck/releases/latest
SHELLCHECK_RELEASE_ASSET_SUFFIX: .linux.x86_64.tar.xz

strategy:
fail-fast: false

matrix:
configuration:
- name: Generate problem matcher output
# ShellCheck's "gcc" output format is required for annotated diffs, but inferior for humans reading the log.
format: gcc
# The other matrix job is used to set the result, so this job is configured to always pass.
continue-on-error: true
- name: ShellCheck
# ShellCheck's "tty" output format is most suitable for humans reading the log.
format: tty
continue-on-error: false

steps:
- name: Set environment variables
run: |
# See: https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#setting-an-environment-variable
echo "INSTALL_PATH=${{ runner.temp }}/shellcheck" >> "$GITHUB_ENV"

- 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: Download latest ShellCheck release binary package
id: download
uses: MrOctopus/[email protected]
with:
repository: koalaman/shellcheck
excludes: prerelease, draft
asset: ${{ env.SHELLCHECK_RELEASE_ASSET_SUFFIX }}
target: ${{ env.INSTALL_PATH }}

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

- name: Run ShellCheck
uses: liskin/gh-problem-matcher-wrap@v2
continue-on-error: ${{ matrix.configuration.continue-on-error }}
with:
linters: gcc
run: task --silent shell:check SHELLCHECK_FORMAT=${{ matrix.configuration.format }}

formatting:
needs: run-determination
if: needs.run-determination.outputs.result == 'true'
runs-on: ubuntu-latest

steps:
- name: Set environment variables
run: |
# See: https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#setting-an-environment-variable
echo "SHFMT_INSTALL_PATH=${{ runner.temp }}/shfmt" >> "$GITHUB_ENV"

- 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: Download shfmt
id: download
uses: MrOctopus/[email protected]
with:
repository: mvdan/sh
excludes: prerelease, draft
asset: _linux_amd64
target: ${{ env.SHFMT_INSTALL_PATH }}

- name: Install shfmt
run: |
# Executable permissions of release assets are lost
chmod +x "${{ env.SHFMT_INSTALL_PATH }}/${{ steps.download.outputs.name }}"
# Standardize binary name
mv "${{ env.SHFMT_INSTALL_PATH }}/${{ steps.download.outputs.name }}" "${{ env.SHFMT_INSTALL_PATH }}/shfmt"
# Add installation to PATH:
# See: https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-system-path
echo "${{ env.SHFMT_INSTALL_PATH }}" >> "$GITHUB_PATH"

- name: Format shell scripts
run: task --silent shell:format

- name: Check formatting
run: git diff --color --exit-code

executable:
needs: run-determination
if: needs.run-determination.outputs.result == 'true'
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 for non-executable scripts
run: task --silent shell:check-mode
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
[![Check Markdown status](https://github.com/arduino/crossbuild/actions/workflows/check-markdown-task.yml/badge.svg)](https://github.com/arduino/crossbuild/actions/workflows/check-markdown-task.yml)
[![Check License status](https://github.com/arduino/crossbuild/actions/workflows/check-license.yml/badge.svg)](https://github.com/arduino/crossbuild/actions/workflows/check-license.yml)
[![Check Taskfiles status](https://github.com/arduino/crossbuild/actions/workflows/check-taskfiles.yml/badge.svg)](https://github.com/arduino/crossbuild/actions/workflows/check-taskfiles.yml)
[![Check Shell Scripts status](https://github.com/arduino/crossbuild/actions/workflows/check-shell-task.yml/badge.svg)](https://github.com/arduino/crossbuild/actions/workflows/check-shell-task.yml)

This docker container has been created to allow us to easily crosscompile our c++ tools. The idea comes from [multiarch/crossbuild](https://github.com/multiarch/crossbuild), but that container unfortunately is outdated and the apt sources are no longer available.

Expand Down
77 changes: 77 additions & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,80 @@ tasks:
desc: Install dependencies managed by npm
cmds:
- npm install

# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/check-shell-task/Taskfile.yml
shell:check:
desc: Check for problems with shell scripts
cmds:
- |
if ! which shellcheck &>/dev/null; then
echo "shellcheck not installed or not in PATH. Please install: https://github.com/koalaman/shellcheck#installing"
exit 1
fi
- |
# There is something odd about shellcheck that causes the task to always exit on the first fail, despite any
# measures that would prevent this with any other command. So it's necessary to call shellcheck only once with
# the list of script paths as an argument. This could lead to exceeding the maximum command length on Windows if
# the repository contained a large number of scripts, but it's unlikely to happen in reality.
shellcheck \
--format={{default "tty" .SHELLCHECK_FORMAT}} \
$(
# The odd method for escaping . in the regex is required for windows compatibility because mvdan.cc/sh gives
# \ characters special treatment on Windows in an attempt to support them as path separators.
find . \
-type d -name '.git' -prune -or \
-type d -name '.licenses' -prune -or \
-type d -name '__pycache__' -prune -or \
-type d -name 'node_modules' -prune -or \
-type d -path './deps/*' -prune -or \
\( \
-regextype posix-extended \
-regex '.*[.](bash|sh)' -and \
-type f \
\) \
-print
)

# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/check-shell-task/Taskfile.yml
shell:check-mode:
desc: Check for non-executable shell scripts
cmds:
- |
EXIT_STATUS=0
while read -r nonExecutableScriptPath; do
# The while loop always runs once, even if no file was found
if [[ "$nonExecutableScriptPath" == "" ]]; then
continue
fi

echo "::error file=${nonExecutableScriptPath}::non-executable script file: $nonExecutableScriptPath";
EXIT_STATUS=1
done <<<"$(
# The odd approach to escaping `.` in the regex is required for windows compatibility because mvdan.cc/sh
# gives `\` characters special treatment on Windows in an attempt to support them as path separators.
find . \
-type d -name '.git' -prune -or \
-type d -name '.licenses' -prune -or \
-type d -name '__pycache__' -prune -or \
-type d -name 'node_modules' -prune -or \
-type d -path './deps/*' -prune -or \
\( \
-regextype posix-extended \
-regex '.*[.](bash|sh)' -and \
-type f -and \
-not -executable \
-print \
\)
)"
exit $EXIT_STATUS

# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/check-shell-task/Taskfile.yml
shell:format:
desc: Format shell script files
cmds:
- |
if ! which shfmt &>/dev/null; then
echo "shfmt not installed or not in PATH. Please install: https://github.com/mvdan/sh#shfmt"
exit 1
fi
- shfmt -w .
Loading