@@ -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
118151def _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)
0 commit comments