├── .bazelignore ├── .bazelrc ├── .bazelversion ├── .bcr ├── config.yml ├── metadata.template.json ├── presubmit.yml └── source.template.json ├── .envrc ├── .github ├── dependabot.yml └── workflows │ └── workflow.yml ├── .gitignore ├── .pre-commit-config.yaml ├── BUILD.bazel ├── CHANGELOG.md ├── LICENSE ├── MODULE.bazel ├── README.md ├── WORKSPACE ├── ci └── package.sh ├── def.bzl ├── deps.bzl ├── examples ├── BUILD.bazel ├── check_glob │ ├── .gitignore │ ├── BUILD.bazel │ ├── MODULE.bazel │ ├── WORKSPACE │ └── src │ │ └── script.sh └── optional_attributes │ ├── .gitignore │ ├── BUILD.bazel │ ├── MODULE.bazel │ ├── WORKSPACE │ └── script.sh ├── internal ├── BUILD.bazel ├── extensions.bzl ├── pkg │ ├── BUILD.bazel │ ├── release.sh │ └── release_notes.tmpl.md └── rules.bzl └── tests ├── BUILD.bazel └── testdata ├── bad.sh └── good.sh /.bazelignore: -------------------------------------------------------------------------------- 1 | examples/check_glob/bazel-bin 2 | examples/check_glob/bazel-out 3 | examples/check_glob/bazel-testlogs 4 | examples/check_glob/bazel-optional_attributes 5 | 6 | examples/optional_attributes/bazel-bin 7 | examples/optional_attributes/bazel-out 8 | examples/optional_attributes/bazel-testlogs 9 | examples/optional_attributes/bazel-optional_attributes 10 | -------------------------------------------------------------------------------- /.bazelrc: -------------------------------------------------------------------------------- 1 | test --test_output=errors 2 | 3 | # Fix the excessive rebuilding when using anything that depends on protobuf rules 4 | # See https://github.com/bazelbuild/buildtools/issues/744 5 | common --incompatible_strict_action_env 6 | common --enable_bzlmod 7 | 8 | try-import user.bazelrc 9 | 10 | # To update these lines, execute 11 | # `bazel run @rules_bazel_integration_test//tools:update_deleted_packages` 12 | build --deleted_packages=examples/check_glob,examples/optional_attributes 13 | query --deleted_packages=examples/check_glob,examples/optional_attributes 14 | -------------------------------------------------------------------------------- /.bazelversion: -------------------------------------------------------------------------------- 1 | 7.0.0 2 | -------------------------------------------------------------------------------- /.bcr/config.yml: -------------------------------------------------------------------------------- 1 | # See https://github.com/bazel-contrib/publish-to-bcr#a-note-on-release-automation 2 | # for guidance about whether to uncomment this section: 3 | --- 4 | fixedReleaser: 5 | login: aignas 6 | email: 240938+aignas@users.noreply.github.com 7 | -------------------------------------------------------------------------------- /.bcr/metadata.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "homepage": "https://github.com/aignas/rules_shellcheck", 3 | "maintainers": [ 4 | { 5 | "email": "240938+aignas@users.noreply.github.com", 6 | "github": "aignas", 7 | "name": "Ignas Anikevicius" 8 | } 9 | ], 10 | "repository": ["github:aignas/rules_shellcheck"], 11 | "versions": [], 12 | "yanked_versions": {} 13 | } 14 | -------------------------------------------------------------------------------- /.bcr/presubmit.yml: -------------------------------------------------------------------------------- 1 | --- 2 | bcr_test_module: 3 | module_path: "examples/check_glob" 4 | matrix: 5 | platform: ["debian10", "macos", "ubuntu2004"] 6 | bazel: [6.x, 7.x] 7 | tasks: 8 | run_tests: 9 | name: "Run test module" 10 | platform: ${{ platform }} 11 | bazel: ${{ bazel }} 12 | test_targets: 13 | - "//..." 14 | -------------------------------------------------------------------------------- /.bcr/source.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "integrity": "", 3 | "strip_prefix": "", 4 | "url": "https://github.com/{OWNER}/{REPO}/releases/download/{TAG}/{REPO}-{TAG}.tar.gz" 5 | } 6 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | WORKSPACE_ROOT=$(git rev-parse --show-toplevel) 2 | 3 | export PATH="${WORKSPACE_ROOT}/tools/scripts:${WORKSPACE_ROOT}/tools/bin:$PATH" 4 | export GO111MODULE=on 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2 3 | updates: 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "weekly" 8 | groups: 9 | github-actions: 10 | patterns: 11 | - "*" 12 | -------------------------------------------------------------------------------- /.github/workflows/workflow.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CI 3 | 4 | on: # yamllint disable rule:truthy 5 | push: 6 | branches: 7 | - main 8 | tags: 9 | - "*.*.*" 10 | pull_request: 11 | branches: 12 | - main 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - name: Test fetch 22 | run: bazel fetch //... 23 | 24 | - name: Test 25 | run: bazel test //... 26 | 27 | test-mac: 28 | runs-on: macos-latest 29 | 30 | steps: 31 | - uses: actions/checkout@v4 32 | 33 | - name: Test 34 | run: bazel test //... 35 | 36 | pkg: 37 | runs-on: ubuntu-latest 38 | needs: 39 | - build 40 | - test-mac 41 | 42 | steps: 43 | - name: Checkout 44 | uses: actions/checkout@v4 45 | 46 | - name: Create release archive and notes 47 | # Set by GH actions, see 48 | # https://docs.github.com/en/actions/learn-github-actions/environment-variables#default-environment-variables 49 | run: ./ci/package.sh "${{ github.ref_name }}" 50 | 51 | - name: Archive release artifacts 52 | uses: actions/upload-artifact@v4 53 | with: 54 | name: release 55 | path: release/* 56 | retention-days: 5 57 | 58 | release: 59 | if: startsWith(github.ref, 'refs/tags/') 60 | runs-on: ubuntu-latest 61 | needs: pkg 62 | steps: 63 | - name: Download release artifacts 64 | uses: actions/download-artifact@v4 65 | with: 66 | name: release 67 | 68 | - name: Release 69 | uses: softprops/action-gh-release@v1 70 | with: 71 | # Use GH feature to populate the changelog automatically 72 | generate_release_notes: true 73 | body_path: release_notes.md 74 | fail_on_unmatched_files: true 75 | files: rules_shellcheck-*.tar.gz 76 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bazel-* 2 | user.bazelrc 3 | /release/ 4 | MODULE.bazel.lock 5 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | repos: 3 | - repo: https://github.com/keith/pre-commit-buildifier 4 | rev: 6.3.3.1 5 | hooks: 6 | - id: buildifier 7 | args: &args 8 | - --warnings=all 9 | - id: buildifier-lint 10 | args: *args 11 | -------------------------------------------------------------------------------- /BUILD.bazel: -------------------------------------------------------------------------------- 1 | filegroup( 2 | name = "distribution", 3 | srcs = [ 4 | "BUILD.bazel", 5 | "CHANGELOG.md", 6 | "LICENSE", 7 | "MODULE.bazel", 8 | "README.md", 9 | "WORKSPACE", 10 | ":def.bzl", 11 | ":deps.bzl", 12 | "//internal:distribution", 13 | # Needed for BCR registry to run the pre-submit tests 14 | "//examples:distribution", 15 | ], 16 | visibility = ["//internal/pkg:__pkg__"], 17 | ) 18 | 19 | alias( 20 | name = "release", 21 | actual = "//internal/pkg:release", 22 | visibility = ["//:__subpackages__"], 23 | ) 24 | 25 | constraint_value( 26 | name = "armv6hf", 27 | constraint_setting = "@platforms//cpu", 28 | ) 29 | 30 | # Raspberry Pi 31 | config_setting( 32 | name = "linux_armv6hf", 33 | constraint_values = [ 34 | "@platforms//os:linux", 35 | "//:armv6hf", 36 | ], 37 | ) 38 | 39 | config_setting( 40 | name = "darwin_x86_64", 41 | constraint_values = [ 42 | "@platforms//os:macos", 43 | "@platforms//cpu:x86_64", 44 | ], 45 | ) 46 | 47 | config_setting( 48 | name = "darwin_aarch64", 49 | constraint_values = [ 50 | "@platforms//os:macos", 51 | "@platforms//cpu:aarch64", 52 | ], 53 | ) 54 | 55 | config_setting( 56 | name = "linux_x86_64", 57 | constraint_values = [ 58 | "@platforms//os:linux", 59 | "@platforms//cpu:x86_64", 60 | ], 61 | ) 62 | 63 | config_setting( 64 | name = "linux_aarch64", 65 | constraint_values = [ 66 | "@platforms//os:linux", 67 | "@platforms//cpu:aarch64", 68 | ], 69 | ) 70 | 71 | config_setting( 72 | name = "windows_x86_64", 73 | constraint_values = [ 74 | "@platforms//os:windows", 75 | "@platforms//cpu:x86_64", 76 | ], 77 | ) 78 | 79 | alias( 80 | name = "shellcheck", 81 | actual = select( 82 | { 83 | ":darwin_aarch64": "@shellcheck_darwin_aarch64//:shellcheck", 84 | ":darwin_x86_64": "@shellcheck_darwin_x86_64//:shellcheck", 85 | ":linux_aarch64": "@shellcheck_linux_aarch64//:shellcheck", 86 | ":linux_armv6hf": "@shellcheck_linux_armv6hf//:shellcheck", 87 | ":linux_x86_64": "@shellcheck_linux_x86_64//:shellcheck", 88 | ":windows_x86_64": "@shellcheck_windows_x86_64//:shellcheck", 89 | }, 90 | no_match_error = "binaries for your platform could not be found", 91 | ), 92 | visibility = ["//visibility:public"], 93 | ) 94 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | This document is maintaining changes since the last released version (0.1.1) 9 | 10 | ## Unreleased 11 | 12 | ### Changed 13 | 14 | * Packaging is now partially done with `bazel`. 15 | 16 | ## v0.2.4 17 | 18 | ### Changed 19 | 20 | * `extensions.bzl` file is now internal. 21 | * `@rules_shellcheck//:shellcheck` target now gives a more helpful error message. 22 | 23 | ## v0.2.3 24 | 25 | Attempting to publish to BCR. 26 | 27 | ## v0.2.2 28 | 29 | Attempting to publish to BCR. 30 | 31 | ## v0.2.1 32 | 33 | Attempting to publish to BCR. 34 | 35 | ## v0.2.0 36 | 37 | ### Changed 38 | 39 | Breaking changes: 40 | 41 | * Rename the workspace and module from `com_aignas_com_rules_shellcheck` to 42 | `rules_shellcheck` so that a migration to `bazel-contrib` or somewhere else 43 | could be possible at some point. 44 | 45 | ### Fixed 46 | 47 | * Add a missing `sha256` to the `README`. 48 | 49 | ### Added 50 | 51 | * Bump shellcheck to `0.9.0`. 52 | * Add `severity` and `format` attributes. 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Ignas Anikevicius 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MODULE.bazel: -------------------------------------------------------------------------------- 1 | module( 2 | name = "rules_shellcheck", 3 | version = "0.0.0", 4 | compatibility_level = 1, 5 | ) 6 | 7 | bazel_dep(name = "platforms", version = "0.0.8") 8 | 9 | deps = use_extension("//internal:extensions.bzl", "shellcheck_dependencies") 10 | use_repo( 11 | deps, 12 | "shellcheck_darwin_aarch64", 13 | "shellcheck_darwin_x86_64", 14 | "shellcheck_linux_aarch64", 15 | "shellcheck_linux_armv6hf", 16 | "shellcheck_linux_x86_64", 17 | "shellcheck_windows_x86_64", 18 | ) 19 | 20 | # Dev dependencies 21 | 22 | bazel_dep(name = "rules_pkg", version = "0.9.1", dev_dependency = True) 23 | bazel_dep( 24 | name = "rules_bazel_integration_test", 25 | version = "0.21.0", 26 | dev_dependency = True, 27 | ) 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Shellcheck rules for bazel 2 | 3 | Now you do not need to depend on the system `shellcheck` version in your bazel-managed (mono)repos. 4 | 5 | [![Build Status](https://github.com/aignas/rules_shellcheck/workflows/CI/badge.svg)](https://github.com/aignas/rules_shellcheck/actions) 6 | 7 | Choose your release from the [GH Releases](https://github.com/aignas/rules_shellcheck/releases) and follow setup instructions there. 8 | 9 | Then `shellcheck` can be accessed by running: 10 | 11 | ```shell 12 | bazel run @rules_shellcheck//:shellcheck -- 13 | ``` 14 | 15 | And you can define a lint target: 16 | 17 | ```starlark 18 | load("@rules_shellcheck//:def.bzl", "shellcheck", "shellcheck_test") 19 | 20 | shellcheck_test( 21 | name = "shellcheck_test", 22 | data = glob(["*.sh"]), 23 | tags = ["lint"], 24 | format = "gcc", 25 | severity = "warning", 26 | ) 27 | ``` 28 | 29 | Note: this is a simple project that allows me to learn about various bazel concepts. Feel free to create PRs contributing to the project or consider using [rules_lint]. 30 | 31 | [rules_lint]: https://github.com/aspect-build/rules_lint 32 | -------------------------------------------------------------------------------- /WORKSPACE: -------------------------------------------------------------------------------- 1 | workspace(name = "rules_shellcheck") 2 | 3 | load("//:deps.bzl", "shellcheck_dependencies") 4 | 5 | shellcheck_dependencies() 6 | -------------------------------------------------------------------------------- /ci/package.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # TODO: use bazel integration test framework and rewrite this to be run by `bazel test` 4 | set -euo pipefail 5 | 6 | TMPDIR="$(mktemp -d)" 7 | trap 'rm -rf -- "$TMPDIR"' EXIT 8 | 9 | _log() { 10 | echo "INFO: $*" 11 | } 12 | 13 | main() { 14 | # First change the directory to workspace root 15 | cd "$(dirname "$0")"/.. 16 | 17 | local -r version="${1:-0.0.0}" 18 | 19 | # Then build the release artifact 20 | local -r tarball=$( 21 | bazel run \ 22 | --stamp --embed_label "$version" \ 23 | //:release -- release 24 | ) 25 | 26 | _log "Extracting the tarball into a temporary directory to run examples" 27 | tar -xvf "$tarball" -C "$TMPDIR" 28 | 29 | pushd $TMPDIR 30 | 31 | # Then run examples with the packaged artifacts 32 | examples=( 33 | check_glob 34 | optional_attributes 35 | ) 36 | 37 | for example in "${examples[@]}"; do 38 | _log "Running an example with the generated archive" 39 | pushd "examples/$example" 40 | bazel \ 41 | test \ 42 | --override_module rules_shellcheck="$TMPDIR" \ 43 | ... 44 | popd 45 | done 46 | 47 | popd 48 | _log "Success" 49 | } 50 | 51 | main "$@" 52 | -------------------------------------------------------------------------------- /def.bzl: -------------------------------------------------------------------------------- 1 | """This file provides all user facing functions. 2 | """ 3 | 4 | load("//internal:rules.bzl", _shellcheck_test = "shellcheck_test") 5 | 6 | def shellcheck_test(name, data, **kwargs): 7 | """shellcheck_test takes the files to be checked as 'data' 8 | 9 | Args: 10 | name: The name of the rule. 11 | data: The list of files to be checked using shellcheck. 12 | **kwargs: Forwarded kwargs to the underlying rule. 13 | """ 14 | kwargs.pop("expect_fail", True) 15 | return _shellcheck_test( 16 | name = name, 17 | data = data, 18 | **kwargs 19 | ) 20 | -------------------------------------------------------------------------------- /deps.bzl: -------------------------------------------------------------------------------- 1 | """Provides shellcheck dependencies on all supported platforms: 2 | - Linux 64-bit and ARM64 3 | - OSX 64-bit 4 | """ 5 | 6 | load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") 7 | load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe") 8 | 9 | def _urls(arch, version): 10 | url = "https://github.com/vscode-shellcheck/shellcheck-binaries/releases/download/{version}/shellcheck-{version}.{arch}.tar.gz".format( 11 | version = version, 12 | arch = arch.replace("_", ".", 1), 13 | ) 14 | 15 | return [ 16 | url.replace("https://", "https://mirror.bazel.build/"), 17 | url, 18 | ] 19 | 20 | def shellcheck_dependencies(): 21 | version = "v0.9.0" 22 | sha256 = { 23 | "darwin_aarch64": "a75b912015aaa5b2a48698b63f3619783d90abda4d32a31362209315e6c1cdf6", 24 | "darwin_x86_64": "d1244da2aa5d0c2874f3a4a680c6ac79a488ff6dbf9928e12dc80ff3fdc294db", 25 | "linux_aarch64": "b5633bd195cfe61a310bd8dcff2514855afefea908942a0fd4d01fa6451cb4e6", 26 | "linux_armv6hf": "4791d36d84a626c4366746d14ad68daf2c07f502da09319c45fa6c5c0a847aa9", 27 | "linux_x86_64": "0ab5711861e6fcafad5aa21587ee75bbd2b16505d56f41c9ba1191a83d314074", 28 | "windows_x86_64": "a0f021057b6d6a69a22f6b0db0187bcaca3f5195385e92a7555ad63a6e39ee15", 29 | } 30 | 31 | for arch, sha256 in sha256.items(): 32 | maybe( 33 | http_archive, 34 | name = "shellcheck_{arch}".format(arch = arch), 35 | build_file_content = """exports_files(["shellcheck"]) 36 | """, 37 | sha256 = sha256, 38 | urls = _urls(arch = arch, version = version), 39 | ) 40 | -------------------------------------------------------------------------------- /examples/BUILD.bazel: -------------------------------------------------------------------------------- 1 | filegroup( 2 | name = "distribution", 3 | srcs = glob( 4 | [ 5 | "check_glob/**", 6 | "optional_attributes/**", 7 | ], 8 | exclude = [ 9 | "**/.gitignore", 10 | "**/MODULE.bazel.lock", 11 | ], 12 | ), 13 | visibility = ["//:__pkg__"], 14 | ) 15 | -------------------------------------------------------------------------------- /examples/check_glob/.gitignore: -------------------------------------------------------------------------------- 1 | /bazel-* 2 | -------------------------------------------------------------------------------- /examples/check_glob/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_shellcheck//:def.bzl", "shellcheck_test") 2 | 3 | shellcheck_test( 4 | name = "shellcheck_all", 5 | data = glob(["src/**/*.sh"]), 6 | tags = ["lint"], 7 | ) 8 | 9 | test_suite( 10 | name = "verify-all", 11 | tags = ["lint"], 12 | tests = [ 13 | ":shellcheck_all", 14 | ], 15 | ) 16 | -------------------------------------------------------------------------------- /examples/check_glob/MODULE.bazel: -------------------------------------------------------------------------------- 1 | module( 2 | name = "check_glob", 3 | version = "0.0.0", 4 | compatibility_level = 1, 5 | ) 6 | 7 | bazel_dep(name = "rules_shellcheck", version = "0.0.0") 8 | local_path_override( 9 | module_name = "rules_shellcheck", 10 | path = "../..", 11 | ) 12 | -------------------------------------------------------------------------------- /examples/check_glob/WORKSPACE: -------------------------------------------------------------------------------- 1 | local_repository( 2 | name = "rules_shellcheck", 3 | path = "../..", 4 | ) 5 | 6 | load("@rules_shellcheck//:deps.bzl", "shellcheck_dependencies") 7 | 8 | shellcheck_dependencies() 9 | -------------------------------------------------------------------------------- /examples/check_glob/src/script.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set eux 3 | 4 | echo "Hello, World!" 5 | -------------------------------------------------------------------------------- /examples/optional_attributes/.gitignore: -------------------------------------------------------------------------------- 1 | /bazel-* 2 | -------------------------------------------------------------------------------- /examples/optional_attributes/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_shellcheck//:def.bzl", "shellcheck_test") 2 | 3 | shellcheck_test( 4 | name = "shellcheck_all", 5 | data = glob(["*.sh"]), 6 | format = "gcc", 7 | severity = "warning", 8 | tags = ["lint"], 9 | ) 10 | -------------------------------------------------------------------------------- /examples/optional_attributes/MODULE.bazel: -------------------------------------------------------------------------------- 1 | module( 2 | name = "optional_attributes", 3 | version = "0.0.0", 4 | compatibility_level = 1, 5 | ) 6 | 7 | bazel_dep(name = "rules_shellcheck", version = "0.0.0") 8 | local_path_override( 9 | module_name = "rules_shellcheck", 10 | path = "../..", 11 | ) 12 | -------------------------------------------------------------------------------- /examples/optional_attributes/WORKSPACE: -------------------------------------------------------------------------------- 1 | local_repository( 2 | name = "rules_shellcheck", 3 | path = "../..", 4 | ) 5 | 6 | load("@rules_shellcheck//:deps.bzl", "shellcheck_dependencies") 7 | 8 | shellcheck_dependencies() 9 | -------------------------------------------------------------------------------- /examples/optional_attributes/script.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set eux 3 | 4 | FOO="foo " 5 | 6 | # Example (warning): SC2086: Double quote to prevent globbing and word splitting. 7 | if [ $FOO = "foo" ]; then 8 | echo "FOO is foo" 9 | fi 10 | -------------------------------------------------------------------------------- /internal/BUILD.bazel: -------------------------------------------------------------------------------- 1 | filegroup( 2 | name = "distribution", 3 | srcs = glob(["*"]), 4 | visibility = ["//:__pkg__"], 5 | ) 6 | -------------------------------------------------------------------------------- /internal/extensions.bzl: -------------------------------------------------------------------------------- 1 | """Provides shellcheck dependencies on all supported platforms: 2 | - Linux 64-bit and ARM64 3 | - OSX 64-bit 4 | """ 5 | 6 | load("@rules_shellcheck//:deps.bzl", _deps = "shellcheck_dependencies") 7 | 8 | def _impl(_): 9 | _deps() 10 | 11 | shellcheck_dependencies = module_extension( 12 | implementation = _impl, 13 | ) 14 | -------------------------------------------------------------------------------- /internal/pkg/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_pkg//pkg:mappings.bzl", "pkg_files", "strip_prefix") 2 | load("@rules_pkg//pkg:tar.bzl", "pkg_tar") 3 | load("//:def.bzl", "shellcheck_test") 4 | 5 | pkg_files( 6 | name = "files", 7 | srcs = [ 8 | "//:distribution", 9 | ], 10 | strip_prefix = strip_prefix.from_root(), 11 | ) 12 | 13 | pkg_tar( 14 | name = "tar", 15 | srcs = [ 16 | ":files", 17 | ], 18 | out = "rules_shellcheck.tar", 19 | ) 20 | 21 | genrule( 22 | name = "archive", 23 | srcs = [":tar"], 24 | outs = ["rules_shellcheck.tar.gz"], 25 | cmd = "gzip -c $< > $@", 26 | ) 27 | 28 | genrule( 29 | name = "release_stamped", 30 | srcs = ["release.sh"], 31 | outs = ["release_stamped.sh"], 32 | cmd = "; ".join([ 33 | "BUILD_EMBED_LABEL=$$(grep ^BUILD_EMBED_LABEL bazel-out/stable-status.txt | cut -d' ' -f2)", 34 | "sed \"s|{BUILD_EMBED_LABEL}|$$BUILD_EMBED_LABEL|g\" $< >$@", 35 | ]), 36 | stamp = 1, 37 | ) 38 | 39 | sh_binary( 40 | name = "release", 41 | srcs = ["release_stamped"], 42 | data = [ 43 | ":archive", 44 | ":release_notes.tmpl.md", 45 | ], 46 | env = { 47 | "ARCHIVE": "$(location :archive)", 48 | "RELEASE_NOTES_TEMPLATE": "$(location :release_notes.tmpl.md)", 49 | }, 50 | visibility = ["//:__pkg__"], 51 | ) 52 | 53 | shellcheck_test( 54 | name = "release.shellcheck", 55 | data = [":release"], 56 | ) 57 | -------------------------------------------------------------------------------- /internal/pkg/release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | DST="${BUILD_WORKSPACE_DIRECTORY}/$1" 5 | GITHUB_REF_NAME="${2:-{BUILD_EMBED_LABEL}}" 6 | 7 | # GH PRs will have the `BUILD_EMBED_LABEL` as `/merge` and 8 | # in order to keep the logic of manipulating the GITHUB_REF_NAME 9 | # we have it here. This makes it easier to test. 10 | # 11 | # https://docs.github.com/en/actions/learn-github-actions/contexts#github-context 12 | if [[ "$GITHUB_REF_NAME" == *"/merge" ]]; then 13 | GITHUB_REF_NAME="PR${GITHUB_REF_NAME%%\/*}" 14 | fi 15 | 16 | mkdir -p "$DST" 17 | 18 | RELEASE_ARCHIVE="$DST/rules_shellcheck-$GITHUB_REF_NAME.tar.gz" 19 | RELEASE_NOTES="$DST/release_notes.md" 20 | 21 | cp -v "$ARCHIVE" "$RELEASE_ARCHIVE" >&2 22 | chmod 644 "$RELEASE_ARCHIVE" 23 | SHA=$(sha256sum "$RELEASE_ARCHIVE" | awk '{print $1}') 24 | 25 | sed \ 26 | -e "s/%%TAG%%/$GITHUB_REF_NAME/g" \ 27 | -e "s/%%SHA256%%/$SHA/g" \ 28 | "${RELEASE_NOTES_TEMPLATE}" \ 29 | > "$RELEASE_NOTES" 30 | 31 | # Output the release artifact path 32 | echo "$RELEASE_ARCHIVE" 33 | -------------------------------------------------------------------------------- /internal/pkg/release_notes.tmpl.md: -------------------------------------------------------------------------------- 1 | ## Using Bzlmod with Bazel 6 2 | 3 | **NOTE: bzlmod support is still beta. APIs subject to change.** 4 | 5 | Add to your `MODULE.bazel` file: 6 | 7 | ```starlark 8 | bazel_dep(name = "rules_shellcheck", version = "%%TAG%%") 9 | ``` 10 | 11 | ## Legacy: using WORKSPACE 12 | 13 | Paste this snippet into your `WORKSPACE` file: 14 | 15 | ```starlark 16 | load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") 17 | 18 | http_archive( 19 | name = "rules_shellcheck", 20 | sha256 = "%%SHA256%%", 21 | url = "https://github.com/aignas/rules_shellcheck/releases/download/%%TAG%%/rules_shellcheck-%%TAG%%.tar.gz", 22 | ) 23 | 24 | load("@rules_shellcheck//:deps.bzl", "shellcheck_dependencies") 25 | 26 | shellcheck_dependencies() 27 | ``` 28 | -------------------------------------------------------------------------------- /internal/rules.bzl: -------------------------------------------------------------------------------- 1 | """This file provides all user facing functions. 2 | """ 3 | 4 | def _impl_test(ctx): 5 | cmd = [ctx.file._shellcheck.short_path] 6 | if ctx.attr.format: 7 | cmd.append("--format={}".format(ctx.attr.format)) 8 | if ctx.attr.severity: 9 | cmd.append("--severity={}".format(ctx.attr.severity)) 10 | cmd += [f.short_path for f in ctx.files.data] 11 | cmd = " ".join(cmd) 12 | 13 | if ctx.attr.expect_fail: 14 | script = "{cmd} || exit 0\nexit1".format(cmd = cmd) 15 | else: 16 | script = "exec {cmd}".format(cmd = cmd) 17 | 18 | ctx.actions.write( 19 | output = ctx.outputs.executable, 20 | content = script, 21 | ) 22 | 23 | return [ 24 | DefaultInfo( 25 | executable = ctx.outputs.executable, 26 | runfiles = ctx.runfiles(files = [ctx.file._shellcheck] + ctx.files.data), 27 | ), 28 | ] 29 | 30 | shellcheck_test = rule( 31 | implementation = _impl_test, 32 | attrs = { 33 | "data": attr.label_list( 34 | allow_files = True, 35 | ), 36 | "expect_fail": attr.bool( 37 | default = False, 38 | ), 39 | "format": attr.string( 40 | values = ["checkstyle", "diff", "gcc", "json", "json1", "quiet", "tty"], 41 | doc = "The format of the outputted lint results.", 42 | ), 43 | "severity": attr.string( 44 | values = ["error", "info", "style", "warning"], 45 | doc = "The severity of the lint results.", 46 | ), 47 | "_shellcheck": attr.label( 48 | default = Label("//:shellcheck"), 49 | allow_single_file = True, 50 | cfg = "exec", 51 | executable = True, 52 | doc = "The shellcheck executable to use.", 53 | ), 54 | }, 55 | test = True, 56 | ) 57 | -------------------------------------------------------------------------------- /tests/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("//internal:rules.bzl", "shellcheck_test") 2 | 3 | shellcheck_test( 4 | name = "fail_test", 5 | data = [ 6 | "testdata/bad.sh", 7 | ], 8 | expect_fail = True, 9 | ) 10 | 11 | shellcheck_test( 12 | name = "success_test", 13 | data = [ 14 | "testdata/good.sh", 15 | ], 16 | ) 17 | -------------------------------------------------------------------------------- /tests/testdata/bad.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euxo pipefail 3 | 4 | echo $UNDEFINED_VAR 5 | -------------------------------------------------------------------------------- /tests/testdata/good.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euxo pipefail 3 | 4 | echo "123" 5 | exit 1 6 | --------------------------------------------------------------------------------