Skip to content

Commit 55940be

Browse files
authored
Add support for --external-source to shellcheck_aspect (#54)
This relates to #53 but only implements this support for `shellcheck_aspect`. The reason for comes from `shellcheck_aspect` having baked in assumptions around operating exclusively on `sh_*` rules (related to bazel-contrib/rules_shell#16) which enabled the rules to further align with adding this support for `rules_shell` targets with the aspect. This change also eliminates the unnecessary generation of files for each time the aspect runs.
1 parent ae4fc0f commit 55940be

12 files changed

Lines changed: 154 additions & 34 deletions

File tree

shellcheck/.shellcheckrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
# https://github.com/koalaman/shellcheck/blob/v0.11.0/shellcheck.1.md#rc-files
2+
external-sources=true

shellcheck/internal/BUILD.bazel

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,12 @@ filegroup(
33
srcs = glob(["*"]),
44
visibility = ["//shellcheck:__pkg__"],
55
)
6+
7+
filegroup(
8+
name = "aspect_runner",
9+
srcs = select({
10+
"@platforms//os:windows": ["aspect_runner.bat"],
11+
"//conditions:default": ["aspect_runner.sh"],
12+
}),
13+
visibility = ["//visibility:public"],
14+
)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/bin/sh
2+
3+
set -eu
4+
5+
echo "" > "${SHELLCHECK_ASPECT_OUTPUT}"
6+
exec $@
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/bin/sh
2+
3+
set -eu
4+
5+
echo "" > "${SHELLCHECK_ASPECT_OUTPUT}"
6+
exec $@

shellcheck/internal/rules.bzl

Lines changed: 63 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ def shellcheck_test_impl(ctx, expect_fail = False):
7171
DefaultInfo(
7272
executable = executable,
7373
runfiles = ctx.runfiles(
74-
files = [toolchain.shellcheck] + ctx.files.data,
74+
files = [toolchain.shellcheck, toolchain.shellcheckrc] + ctx.files.data,
7575
transitive_files = toolchain.all_files,
7676
),
7777
),
@@ -101,19 +101,52 @@ shellcheck_test = rule(
101101
toolchains = [TOOLCHAIN_TYPE],
102102
)
103103

104-
_ASPECT_SHELL_CONTENT = """\
105-
#!/bin/sh
106-
107-
echo '' > '{output}'
108-
exec '{shellcheck}' $@
109-
"""
104+
ShellcheckSrcsInfo = provider(
105+
doc = "A provider containing relevant data for linting.",
106+
fields = {
107+
"source_paths": "depset[str]: `--source-path` target paths.",
108+
"srcs": "depset[File]: Sources collected from the target.",
109+
"transitive_source_paths": "depset[str]: Transitive source paths collected from dependencies.",
110+
"transitive_srcs": "depset[File]: Transitive sources collected from dependencies.",
111+
},
112+
)
110113

111-
_ASPECT_BATCH_CONTENT = """\
112-
@ECHO OFF
114+
def _shellcheck_srcs_aspect_impl(_target, ctx):
115+
# TODO: Replace when a `rules_shell` provider is available
116+
# https://github.com/bazelbuild/rules_shell/issues/16
117+
rule_name = ctx.rule.kind
118+
if rule_name not in ["sh_binary", "sh_test", "sh_library"]:
119+
return []
113120

114-
echo "" > {output}
115-
{shellcheck} %*
116-
"""
121+
srcs = getattr(ctx.rule.files, "srcs", [])
122+
source_paths = [src.dirname for src in srcs]
123+
124+
transitive_srcs = []
125+
transitive_source_paths = []
126+
127+
for dep in getattr(ctx.rule.attr, "deps", []):
128+
if ShellcheckSrcsInfo in dep:
129+
transitive_srcs.extend([
130+
dep[ShellcheckSrcsInfo].srcs,
131+
dep[ShellcheckSrcsInfo].transitive_srcs,
132+
])
133+
transitive_source_paths.extend([
134+
dep[ShellcheckSrcsInfo].source_paths,
135+
dep[ShellcheckSrcsInfo].transitive_source_paths,
136+
])
137+
138+
return [ShellcheckSrcsInfo(
139+
srcs = depset(srcs),
140+
source_paths = depset(source_paths),
141+
transitive_srcs = depset(transitive = transitive_srcs),
142+
transitive_source_paths = depset(transitive = transitive_source_paths),
143+
)]
144+
145+
_shellcheck_srcs_aspect = aspect(
146+
doc = "An aspect for collecting data about how to lint the target.",
147+
attr_aspects = ["deps"],
148+
implementation = _shellcheck_srcs_aspect_impl,
149+
)
117150

118151
def _shellcheck_aspect_impl(target, ctx):
119152
if target.label.workspace_root.startswith("external"):
@@ -129,14 +162,14 @@ def _shellcheck_aspect_impl(target, ctx):
129162
if tag.replace("-", "_").lower() in ignore_tags:
130163
return []
131164

132-
# TODO: https://github.com/aignas/rules_shellcheck/issues/23
133-
rule_name = ctx.rule.kind
134-
if rule_name not in ["sh_binary", "sh_test", "sh_library"]:
165+
if ShellcheckSrcsInfo not in target:
135166
return []
136167

168+
src_info = target[ShellcheckSrcsInfo]
169+
137170
srcs = [
138171
src
139-
for src in getattr(ctx.rule.files, "srcs", [])
172+
for src in src_info.srcs.to_list()
140173
if src.is_source
141174
]
142175

@@ -145,8 +178,8 @@ def _shellcheck_aspect_impl(target, ctx):
145178

146179
toolchain = ctx.toolchains[TOOLCHAIN_TYPE]
147180

148-
inputs_direct = getattr(ctx.rule.files, "srcs", []) + getattr(ctx.rule.files, "data", [])
149-
inputs_transitive = []
181+
inputs_direct = [toolchain.shellcheckrc] + getattr(ctx.rule.files, "data", [])
182+
inputs_transitive = [src_info.srcs, src_info.transitive_srcs]
150183

151184
if DefaultInfo in target:
152185
inputs_transitive.extend([
@@ -157,25 +190,14 @@ def _shellcheck_aspect_impl(target, ctx):
157190
format = ctx.attr._format[BuildSettingInfo].value
158191
severity = ctx.attr._format[BuildSettingInfo].value
159192

160-
shellcheck = toolchain.shellcheck
161-
is_windows = True if shellcheck.basename.endswith(".exe") else False
162-
163-
executable = ctx.actions.declare_file("{}.shellcheck.{}".format(target.label.name, "bat" if is_windows else "sh"))
164193
output = ctx.actions.declare_file("{}.shellcheck.ok".format(target.label.name))
165194

166-
ctx.actions.write(
167-
output = executable,
168-
content = (_ASPECT_BATCH_CONTENT if is_windows else _ASPECT_SHELL_CONTENT).format(
169-
output = output.path,
170-
shellcheck = shellcheck.path,
171-
),
172-
is_executable = True,
173-
)
174-
175-
tools = depset([shellcheck], transitive = [toolchain.all_files])
195+
tools = depset([toolchain.shellcheck], transitive = [toolchain.all_files])
176196

177197
args = ctx.actions.args()
198+
args.add(toolchain.shellcheck)
178199
args.add(toolchain.shellcheckrc, format = "--rcfile=%s")
200+
args.add_all(src_info.source_paths, format_each = "--source-path=%s")
179201

180202
if format:
181203
args.add(format, format = "--format=%s")
@@ -188,10 +210,12 @@ def _shellcheck_aspect_impl(target, ctx):
188210
ctx.actions.run(
189211
mnemonic = "Shellcheck",
190212
progress_message = "Shellcheck {}".format(target.label),
191-
executable = executable,
213+
executable = ctx.file._runner,
192214
inputs = depset(inputs_direct, transitive = inputs_transitive),
193215
arguments = [args],
194-
env = ctx.configuration.default_shell_env,
216+
env = ctx.configuration.default_shell_env | {
217+
"SHELLCHECK_ASPECT_OUTPUT": output.path,
218+
},
195219
tools = tools,
196220
outputs = [output],
197221
)
@@ -209,9 +233,14 @@ shellcheck_aspect = aspect(
209233
"_format": attr.label(
210234
default = Label("//shellcheck/settings:format"),
211235
),
236+
"_runner": attr.label(
237+
allow_single_file = True,
238+
default = Label("//shellcheck/internal:aspect_runner"),
239+
),
212240
"_severity": attr.label(
213241
default = Label("//shellcheck/settings:severity"),
214242
),
215243
},
216244
toolchains = [TOOLCHAIN_TYPE],
245+
requires = [_shellcheck_srcs_aspect],
217246
)

tests/follow_source/BUILD.bazel

Whitespace-only changes.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
load("@rules_shell//shell:sh_binary.bzl", "sh_binary")
2+
3+
sh_binary(
4+
name = "bin",
5+
srcs = ["bin.sh"],
6+
visibility = ["//tests/follow_source:__subpackages__"],
7+
deps = ["//tests/follow_source/lib1"],
8+
)
9+
10+
# TODO: https://github.com/aignas/rules_shellcheck/issues/53
11+
# shellcheck_test(
12+
# name = "bin_test",
13+
# data = [
14+
# ":bin",
15+
# ],
16+
# )

tests/follow_source/bin/bin.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/bin/bash
2+
set -euo pipefail
3+
4+
# Source the external library (same directory in runfiles).
5+
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/../lib1/lib1.sh"
6+
7+
hello_from_lib1
8+
echo "Done"
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
load("@rules_shell//shell:sh_library.bzl", "sh_library")
2+
3+
sh_library(
4+
name = "lib1",
5+
srcs = ["lib1.sh"],
6+
visibility = ["//tests/follow_source:__subpackages__"],
7+
deps = ["//tests/follow_source/lib2"],
8+
)
9+
10+
# TODO: https://github.com/aignas/rules_shellcheck/issues/53
11+
# shellcheck_test(
12+
# name = "lib1_test",
13+
# data = [
14+
# ":lib1",
15+
# ],
16+
# )

tests/follow_source/lib1/lib1.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/bin/bash
2+
3+
# Source the external library (same directory in runfiles).
4+
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/../lib2/lib2.sh"
5+
6+
hello_from_lib() {
7+
echo "Hello from library 1"
8+
hello_from_lib2
9+
}

0 commit comments

Comments
 (0)