Skip to content

Commit 6d8f4e0

Browse files
Add CI workflow to check for problems with shell scripts
On every push or pull request that modifies one of the shell scripts in the repository, and periodically, the workflow: - Runs ShellCheck to detect common problems. - Runs shfmt to check formatting. - Checks for forgotten executable script file permissions.
1 parent 60a5883 commit 6d8f4e0

File tree

4 files changed

+313
-0
lines changed

4 files changed

+313
-0
lines changed

.editorconfig

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/general/.editorconfig
2+
# See: https://editorconfig.org/
3+
# The formatting style defined in this file is the official standardized style to be used in all Arduino Tooling
4+
# projects and should not be modified.
5+
# Note: indent style for each file type is defined even when it matches the universal config in order to make it clear
6+
# that this type has an official style.
7+
8+
[*]
9+
charset = utf-8
10+
end_of_line = lf
11+
indent_size = 2
12+
indent_style = space
13+
insert_final_newline = true
14+
trim_trailing_whitespace = true
15+
16+
[*.{adoc,asc,asciidoc}]
17+
indent_size = 2
18+
indent_style = space
19+
20+
[*.{bash,sh}]
21+
indent_size = 2
22+
indent_style = space
23+
24+
[*.{c,cc,cp,cpp,cxx,h,hh,hpp,hxx,ii,inl,ino,ixx,pde,tpl,tpp,txx}]
25+
indent_size = 2
26+
indent_style = space
27+
28+
[*.{go,mod}]
29+
indent_style = tab
30+
31+
[*.java]
32+
indent_size = 2
33+
indent_style = space
34+
35+
[*.{js,jsx,json,jsonc,json5,ts,tsx}]
36+
indent_size = 2
37+
indent_style = space
38+
39+
[*.{md,mdx,mkdn,mdown,markdown}]
40+
indent_size = unset
41+
indent_style = space
42+
43+
[*.proto]
44+
indent_size = 2
45+
indent_style = space
46+
47+
[*.py]
48+
indent_size = 4
49+
indent_style = space
50+
51+
[*.svg]
52+
indent_size = 2
53+
indent_style = space
54+
55+
[*.{yaml,yml}]
56+
indent_size = 2
57+
indent_style = space
58+
59+
[{.gitconfig,.gitmodules}]
60+
indent_style = tab
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/check-shell-task.md
2+
name: Check Shell Scripts
3+
4+
# See: https://docs.github.com/actions/using-workflows/events-that-trigger-workflows
5+
on:
6+
create:
7+
push:
8+
paths:
9+
- ".github/workflows/check-shell-task.ya?ml"
10+
- "Taskfile.ya?ml"
11+
- "**/.editorconfig"
12+
- "**.bash"
13+
- "**.sh"
14+
pull_request:
15+
paths:
16+
- ".github/workflows/check-shell-task.ya?ml"
17+
- "Taskfile.ya?ml"
18+
- "**/.editorconfig"
19+
- "**.bash"
20+
- "**.sh"
21+
schedule:
22+
# Run every Tuesday at 8 AM UTC to catch breakage caused by tool changes.
23+
- cron: "0 8 * * TUE"
24+
workflow_dispatch:
25+
repository_dispatch:
26+
27+
jobs:
28+
run-determination:
29+
runs-on: ubuntu-latest
30+
outputs:
31+
result: ${{ steps.determination.outputs.result }}
32+
steps:
33+
- name: Determine if the rest of the workflow should run
34+
id: determination
35+
run: |
36+
RELEASE_BRANCH_REGEX="refs/heads/[0-9]+.[0-9]+.x"
37+
# The `create` event trigger doesn't support `branches` filters, so it's necessary to use Bash instead.
38+
if [[
39+
"${{ github.event_name }}" != "create" ||
40+
"${{ github.ref }}" =~ $RELEASE_BRANCH_REGEX
41+
]]; then
42+
# Run the other jobs.
43+
RESULT="true"
44+
else
45+
# There is no need to run the other jobs.
46+
RESULT="false"
47+
fi
48+
49+
echo "result=$RESULT" >> $GITHUB_OUTPUT
50+
51+
lint:
52+
name: ${{ matrix.configuration.name }}
53+
needs: run-determination
54+
if: needs.run-determination.outputs.result == 'true'
55+
runs-on: ubuntu-latest
56+
57+
env:
58+
# See: https://github.com/koalaman/shellcheck/releases/latest
59+
SHELLCHECK_RELEASE_ASSET_SUFFIX: .linux.x86_64.tar.xz
60+
61+
strategy:
62+
fail-fast: false
63+
64+
matrix:
65+
configuration:
66+
- name: Generate problem matcher output
67+
# ShellCheck's "gcc" output format is required for annotated diffs, but inferior for humans reading the log.
68+
format: gcc
69+
# The other matrix job is used to set the result, so this job is configured to always pass.
70+
continue-on-error: true
71+
- name: ShellCheck
72+
# ShellCheck's "tty" output format is most suitable for humans reading the log.
73+
format: tty
74+
continue-on-error: false
75+
76+
steps:
77+
- name: Set environment variables
78+
run: |
79+
# See: https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#setting-an-environment-variable
80+
echo "INSTALL_PATH=${{ runner.temp }}/shellcheck" >> "$GITHUB_ENV"
81+
82+
- name: Checkout repository
83+
uses: actions/checkout@v3
84+
85+
- name: Install Task
86+
uses: arduino/setup-task@v1
87+
with:
88+
repo-token: ${{ secrets.GITHUB_TOKEN }}
89+
version: 3.x
90+
91+
- name: Download latest ShellCheck release binary package
92+
id: download
93+
uses: MrOctopus/[email protected]
94+
with:
95+
repository: koalaman/shellcheck
96+
excludes: prerelease, draft
97+
asset: ${{ env.SHELLCHECK_RELEASE_ASSET_SUFFIX }}
98+
target: ${{ env.INSTALL_PATH }}
99+
100+
- name: Install ShellCheck
101+
run: |
102+
cd "${{ env.INSTALL_PATH }}"
103+
tar --extract --file="${{ steps.download.outputs.name }}"
104+
EXTRACTION_FOLDER="$(basename "${{ steps.download.outputs.name }}" "${{ env.SHELLCHECK_RELEASE_ASSET_SUFFIX }}")"
105+
# Add installation to PATH:
106+
# See: https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-system-path
107+
echo "${{ env.INSTALL_PATH }}/$EXTRACTION_FOLDER" >> "$GITHUB_PATH"
108+
109+
- name: Run ShellCheck
110+
uses: liskin/gh-problem-matcher-wrap@v2
111+
continue-on-error: ${{ matrix.configuration.continue-on-error }}
112+
with:
113+
linters: gcc
114+
run: task --silent shell:check SHELLCHECK_FORMAT=${{ matrix.configuration.format }}
115+
116+
formatting:
117+
needs: run-determination
118+
if: needs.run-determination.outputs.result == 'true'
119+
runs-on: ubuntu-latest
120+
121+
steps:
122+
- name: Set environment variables
123+
run: |
124+
# See: https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#setting-an-environment-variable
125+
echo "SHFMT_INSTALL_PATH=${{ runner.temp }}/shfmt" >> "$GITHUB_ENV"
126+
127+
- name: Checkout repository
128+
uses: actions/checkout@v3
129+
130+
- name: Install Task
131+
uses: arduino/setup-task@v1
132+
with:
133+
repo-token: ${{ secrets.GITHUB_TOKEN }}
134+
version: 3.x
135+
136+
- name: Download shfmt
137+
id: download
138+
uses: MrOctopus/[email protected]
139+
with:
140+
repository: mvdan/sh
141+
excludes: prerelease, draft
142+
asset: _linux_amd64
143+
target: ${{ env.SHFMT_INSTALL_PATH }}
144+
145+
- name: Install shfmt
146+
run: |
147+
# Executable permissions of release assets are lost
148+
chmod +x "${{ env.SHFMT_INSTALL_PATH }}/${{ steps.download.outputs.name }}"
149+
# Standardize binary name
150+
mv "${{ env.SHFMT_INSTALL_PATH }}/${{ steps.download.outputs.name }}" "${{ env.SHFMT_INSTALL_PATH }}/shfmt"
151+
# Add installation to PATH:
152+
# See: https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-system-path
153+
echo "${{ env.SHFMT_INSTALL_PATH }}" >> "$GITHUB_PATH"
154+
155+
- name: Format shell scripts
156+
run: task --silent shell:format
157+
158+
- name: Check formatting
159+
run: git diff --color --exit-code
160+
161+
executable:
162+
needs: run-determination
163+
if: needs.run-determination.outputs.result == 'true'
164+
runs-on: ubuntu-latest
165+
166+
steps:
167+
- name: Checkout repository
168+
uses: actions/checkout@v3
169+
170+
- name: Install Task
171+
uses: arduino/setup-task@v1
172+
with:
173+
repo-token: ${{ secrets.GITHUB_TOKEN }}
174+
version: 3.x
175+
176+
- name: Check for non-executable scripts
177+
run: task --silent shell:check-mode

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
[![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)
55
[![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)
66
[![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)
7+
[![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)
78

89
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.
910

Taskfile.yml

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,78 @@ tasks:
8484
desc: Install dependencies managed by npm
8585
cmds:
8686
- npm install
87+
88+
# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/check-shell-task/Taskfile.yml
89+
shell:check:
90+
desc: Check for problems with shell scripts
91+
cmds:
92+
- |
93+
if ! which shellcheck &>/dev/null; then
94+
echo "shellcheck not installed or not in PATH. Please install: https://github.com/koalaman/shellcheck#installing"
95+
exit 1
96+
fi
97+
- |
98+
# There is something odd about shellcheck that causes the task to always exit on the first fail, despite any
99+
# measures that would prevent this with any other command. So it's necessary to call shellcheck only once with
100+
# the list of script paths as an argument. This could lead to exceeding the maximum command length on Windows if
101+
# the repository contained a large number of scripts, but it's unlikely to happen in reality.
102+
shellcheck \
103+
--format={{default "tty" .SHELLCHECK_FORMAT}} \
104+
$(
105+
# The odd method for escaping . in the regex is required for windows compatibility because mvdan.cc/sh gives
106+
# \ characters special treatment on Windows in an attempt to support them as path separators.
107+
find . \
108+
-type d -name '.git' -prune -or \
109+
-type d -name '.licenses' -prune -or \
110+
-type d -name '__pycache__' -prune -or \
111+
-type d -name 'node_modules' -prune -or \
112+
\( \
113+
-regextype posix-extended \
114+
-regex '.*[.](bash|sh)' -and \
115+
-type f \
116+
\) \
117+
-print
118+
)
119+
120+
# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/check-shell-task/Taskfile.yml
121+
shell:check-mode:
122+
desc: Check for non-executable shell scripts
123+
cmds:
124+
- |
125+
EXIT_STATUS=0
126+
while read -r nonExecutableScriptPath; do
127+
# The while loop always runs once, even if no file was found
128+
if [[ "$nonExecutableScriptPath" == "" ]]; then
129+
continue
130+
fi
131+
132+
echo "::error file=${nonExecutableScriptPath}::non-executable script file: $nonExecutableScriptPath";
133+
EXIT_STATUS=1
134+
done <<<"$(
135+
# The odd approach to escaping `.` in the regex is required for windows compatibility because mvdan.cc/sh
136+
# gives `\` characters special treatment on Windows in an attempt to support them as path separators.
137+
find . \
138+
-type d -name '.git' -prune -or \
139+
-type d -name '.licenses' -prune -or \
140+
-type d -name '__pycache__' -prune -or \
141+
-type d -name 'node_modules' -prune -or \
142+
\( \
143+
-regextype posix-extended \
144+
-regex '.*[.](bash|sh)' -and \
145+
-type f -and \
146+
-not -executable \
147+
-print \
148+
\)
149+
)"
150+
exit $EXIT_STATUS
151+
152+
# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/check-shell-task/Taskfile.yml
153+
shell:format:
154+
desc: Format shell script files
155+
cmds:
156+
- |
157+
if ! which shfmt &>/dev/null; then
158+
echo "shfmt not installed or not in PATH. Please install: https://github.com/mvdan/sh#shfmt"
159+
exit 1
160+
fi
161+
- shfmt -w .

0 commit comments

Comments
 (0)