diff --git a/.editorconfig b/.editorconfig index 111e989c..99945e92 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,16 +1,60 @@ -# http://editorconfig.org - -root = true +# 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 -[*.go] +[*.{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 -[*.{yml,yaml}] +[*.svg] +indent_size = 2 indent_style = space + +[*.{yaml,yml}] indent_size = 2 +indent_style = space + +[.gitmodules] +indent_style = tab diff --git a/.github/workflows/check-formatting.yml b/.github/workflows/check-formatting.yml index 35c1b092..05008504 100644 --- a/.github/workflows/check-formatting.yml +++ b/.github/workflows/check-formatting.yml @@ -34,12 +34,6 @@ jobs: repo-token: ${{ secrets.GITHUB_TOKEN }} version: 3.x - - name: Check shell script formatting - # https://github.com/mvdan/sh - run: | - docker run --volume "$GITHUB_WORKSPACE/libraries/spell-check":/mnt --workdir /mnt mvdan/shfmt:latest -w . - git diff --color --exit-code - - name: Check documentation formatting run: task docs:check-formatting diff --git a/.github/workflows/check-shell-task.yml b/.github/workflows/check-shell-task.yml new file mode 100644 index 00000000..8b8bb0c5 --- /dev/null +++ b/.github/workflows/check-shell-task.yml @@ -0,0 +1,147 @@ +# 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/en/actions/reference/events-that-trigger-workflows +on: + 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: + lint: + name: ${{ matrix.configuration.name }} + 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/en/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable + echo "INSTALL_PATH=${{ runner.temp }}/shellcheck" >> "$GITHUB_ENV" + + - name: Checkout repository + uses: actions/checkout@v2 + + - 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/download-asset-action@1.0 + 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/en/actions/reference/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@v1 + continue-on-error: ${{ matrix.configuration.continue-on-error }} + with: + linters: gcc + run: task --silent shell:check SHELLCHECK_FORMAT=${{ matrix.configuration.format }} + + formatting: + runs-on: ubuntu-latest + + steps: + - name: Set environment variables + run: | + # See: https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable + echo "SHFMT_INSTALL_PATH=${{ runner.temp }}/shfmt" >> "$GITHUB_ENV" + + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Install Task + uses: arduino/setup-task@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + version: 3.x + + - name: Download shfmt + id: download + uses: MrOctopus/download-asset-action@1.0 + 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/en/actions/reference/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: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - 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 diff --git a/.github/workflows/lint-shell.yml b/.github/workflows/lint-shell.yml deleted file mode 100644 index 9b21c8d0..00000000 --- a/.github/workflows/lint-shell.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Lint shell scripts - -on: - push: - paths: - - ".github/workflows/lint-shell.yml" - - "**.sh" - pull_request: - paths: - - ".github/workflows/lint-shell.yml" - - "**.sh" - -jobs: - lint: - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Install Taskfile - uses: arduino/setup-task@v1 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - version: 3.x - - - name: Lint shell scripts - run: task shell:lint diff --git a/README.md b/README.md index df222601..b2767463 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ [](https://github.com/arduino/arduino-lint/actions?workflow=Publish+documentation) [](https://codecov.io/gh/arduino/arduino-lint) [](https://github.com/arduino/arduino-lint/actions/workflows/check-general-formatting-task.yml) +[](https://github.com/arduino/arduino-lint/actions/workflows/check-shell-task.yml) [](https://github.com/arduino/arduino-lint/actions/workflows/check-certificates.yml) **Arduino Lint** is a command line tool that checks for common problems in [Arduino](https://www.arduino.cc/) projects: diff --git a/Taskfile.yml b/Taskfile.yml index a7da6851..61abb84a 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -46,6 +46,7 @@ tasks: - task: config:check - task: general:check-formatting - task: check-spelling + - task: shell:check-mode lint: desc: Lint all files @@ -266,24 +267,73 @@ tasks: cmds: - npx {{ .PRETTIER }} --write "**/*.md" - shell:lint: - desc: Lint shell scripts + # 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: - # https://github.com/koalaman/shellcheck - | - shopt -s globstar # Needed to check all scripts recursively. - shellcheck ./**/*.sh - - shell:check-formatting: - desc: Format shell scripts + 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 . \ + -path ".git" -prune -or \ + \( \ + -regextype posix-extended \ + -regex '.*[.](bash|sh)' -and \ + -type f \ + \) + ) + + # 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: - # https://github.com/mvdan/sh#shfmt - - shfmt -d . + - | + 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 . - shell:format: - desc: Format shell scripts + # 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: - - shfmt -l -w . + - | + 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 . \ + -path ".git" -prune -or \ + \( \ + -regextype posix-extended \ + -regex '.*[.](bash|sh)' -and \ + -type f -and \ + -not -executable \ + -print \ + \) + )" + exit $EXIT_STATUS config:check: desc: Lint and check formatting of configuration files diff --git a/etc/install.sh b/etc/install.sh index 3debdc3b..920ab003 100755 --- a/etc/install.sh +++ b/etc/install.sh @@ -21,198 +21,198 @@ EFFECTIVE_BINDIR="" DEFAULT_BINDIR="$PWD/bin" fail() { - echo "$1" - exit 1 + echo "$1" + exit 1 } initDestination() { - if [ -n "$BINDIR" ]; then - if [ ! -d "$BINDIR" ]; then - # The second instance of $BINDIR is intentionally a literal in this message. - # shellcheck disable=SC2016 - fail "$BINDIR "'($BINDIR)'" folder not found. Please create it before continuing." - fi - EFFECTIVE_BINDIR="$BINDIR" - else - if [ ! -d "$DEFAULT_BINDIR" ]; then - mkdir "$DEFAULT_BINDIR" - fi - EFFECTIVE_BINDIR="$DEFAULT_BINDIR" - fi - echo "Installing in $EFFECTIVE_BINDIR" + if [ -n "$BINDIR" ]; then + if [ ! -d "$BINDIR" ]; then + # The second instance of $BINDIR is intentionally a literal in this message. + # shellcheck disable=SC2016 + fail "$BINDIR "'($BINDIR)'" folder not found. Please create it before continuing." + fi + EFFECTIVE_BINDIR="$BINDIR" + else + if [ ! -d "$DEFAULT_BINDIR" ]; then + mkdir "$DEFAULT_BINDIR" + fi + EFFECTIVE_BINDIR="$DEFAULT_BINDIR" + fi + echo "Installing in $EFFECTIVE_BINDIR" } initArch() { - ARCH=$(uname -m) - case $ARCH in - armv5*) ARCH="armv5" ;; - armv6*) ARCH="ARMv6" ;; - armv7*) ARCH="ARMv7" ;; - aarch64) ARCH="ARM64" ;; - x86) ARCH="32bit" ;; - x86_64) ARCH="64bit" ;; - i686) ARCH="32bit" ;; - i386) ARCH="32bit" ;; - esac - echo "ARCH=$ARCH" + ARCH=$(uname -m) + case $ARCH in + armv5*) ARCH="armv5" ;; + armv6*) ARCH="ARMv6" ;; + armv7*) ARCH="ARMv7" ;; + aarch64) ARCH="ARM64" ;; + x86) ARCH="32bit" ;; + x86_64) ARCH="64bit" ;; + i686) ARCH="32bit" ;; + i386) ARCH="32bit" ;; + esac + echo "ARCH=$ARCH" } initOS() { - OS=$(uname -s) - case "$OS" in - Linux*) OS='Linux' ;; - Darwin*) OS='macOS' ;; - MINGW*) OS='Windows' ;; - MSYS*) OS='Windows' ;; - esac - echo "OS=$OS" + OS=$(uname -s) + case "$OS" in + Linux*) OS='Linux' ;; + Darwin*) OS='macOS' ;; + MINGW*) OS='Windows' ;; + MSYS*) OS='Windows' ;; + esac + echo "OS=$OS" } initDownloadTool() { - if command -v "curl" >/dev/null 2>&1; then - DOWNLOAD_TOOL="curl" - elif command -v "wget" >/dev/null 2>&1; then - DOWNLOAD_TOOL="wget" - else - fail "You need curl or wget as download tool. Please install it first before continuing" - fi - echo "Using $DOWNLOAD_TOOL as download tool" + if command -v "curl" >/dev/null 2>&1; then + DOWNLOAD_TOOL="curl" + elif command -v "wget" >/dev/null 2>&1; then + DOWNLOAD_TOOL="wget" + else + fail "You need curl or wget as download tool. Please install it first before continuing" + fi + echo "Using $DOWNLOAD_TOOL as download tool" } checkLatestVersion() { - # Use the GitHub releases webpage to find the latest version for this project - # so we don't get rate-limited. - CHECKLATESTVERSION_REGEX="[0-9][A-Za-z0-9\.-]*" - CHECKLATESTVERSION_LATEST_URL="https://github.com/${PROJECT_OWNER}/${PROJECT_NAME}/releases/latest" - if [ "$DOWNLOAD_TOOL" = "curl" ]; then - CHECKLATESTVERSION_TAG=$(curl -SsL $CHECKLATESTVERSION_LATEST_URL | grep -o "