Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion rules/attrs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ load(
":collect_aar_outputs_aspect.bzl",
_collect_aar_outputs_aspect = "collect_aar_outputs_aspect",
)
load(
":lint_analysis_aspect.bzl",
_lint_analysis_aspect = "lint_analysis_aspect",
)

ATTRS = dict(
_lint_wrapper = attr.label(
Expand Down Expand Up @@ -38,7 +42,7 @@ ATTRS = dict(
mandatory = False,
allow_empty = True,
default = [],
aspects = [_collect_aar_outputs_aspect],
aspects = [_collect_aar_outputs_aspect, _lint_analysis_aspect],
doc = "Dependencies that should be on the classpath during execution.",
),
android_lint_config = attr.label(
Expand Down
1 change: 1 addition & 0 deletions rules/collect_aar_outputs_aspect.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,5 @@ def _collect_aar_outputs_aspect(tgt, ctx):
collect_aar_outputs_aspect = aspect(
implementation = _collect_aar_outputs_aspect,
attr_aspects = ["aar", "deps", "exports", "associates"],
provides = [AndroidLintAARInfo],
)
158 changes: 118 additions & 40 deletions rules/impl.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ load(
)
load(
":providers.bzl",
_AndroidLintPartialResultsInfo = "AndroidLintPartialResultsInfo",
_AndroidLintResultsInfo = "AndroidLintResultsInfo",
)
load(
Expand All @@ -22,9 +23,12 @@ load(

def _run_android_lint(
ctx,
mode,
android_lint,
module_name,
output,
partial_results,
dependency_modules,
srcs,
deps,
aars,
Expand All @@ -41,17 +45,20 @@ def _run_android_lint(
enable_checks,
autofix,
regenerate,
android_lint_enable_check_dependencies,
android_lint_skip_bytecode_verifier,
android_lint_toolchain,
java_runtime_info):
"""Constructs the Android Lint actions
"""Constructs an Android Lint action for the given phase.

Args:
ctx: The target context
mode: One of "analyze" (write partial results) or "report" (consume them, emit XML)
android_lint: The Android Lint binary to use
module_name: The name of the module
output: The output file
output: The XML output file (report mode only; None in analyze mode)
partial_results: The partial-results directory (output in analyze, input in report)
dependency_modules: List of structs(module_name, partial_results) for first-party deps
whose partial results should be merged (report mode only)
srcs: The source files
deps: Depset of aars and jars to include on the classpath
aars: Depset of the aar nodes
Expand All @@ -68,21 +75,30 @@ def _run_android_lint(
enable_checks: List of additional checks to enable
autofix: Whether to autofix (This is a no-op feature right now)
regenerate: Whether to regenerate the baseline files
android_lint_enable_check_dependencies: Enables dependency checking during analysis
android_lint_skip_bytecode_verifier: Disables bytecode verification
android_lint_toolchain: The android lint toolchain
java_runtime_info: The java runtime toolchain info
"""
is_report = mode == "report"
inputs = []
outputs = [output]
outputs = []

args = ctx.actions.args()
args.set_param_file_format("multiline")
args.use_param_file("@%s", use_always = True)

args.add("--android-lint-cli-tool", android_lint)
inputs.append(android_lint)
args.add("--label", "{}".format(module_name))
args.add("--label", module_name)
args.add("--mode", mode)

# The partial-results directory is an output of analyze and an input of report.
args.add("--partial-results", partial_results.path)
if is_report:
inputs.append(partial_results)
else:
outputs.append(partial_results)

if compile_sdk_version:
args.add("--compile-sdk-version", compile_sdk_version)
if java_language_level:
Expand All @@ -98,21 +114,12 @@ def _run_android_lint(
if manifest:
args.add("--android-manifest", manifest)
inputs.append(manifest)
if not regenerate and baseline:
args.add("--baseline-file", baseline)
inputs.append(baseline)
if regenerate:
args.add("--regenerate-baseline-files")
if config:
args.add("--config-file", config)
inputs.append(config)
if warnings_as_errors:
args.add("--warnings-as-errors")
for custom_rule in _utils.list_or_depset_to_list(custom_rules):
args.add("--custom-rule", custom_rule)
inputs.append(custom_rule)
if autofix == True:
args.add("--autofix")
for check in disable_checks:
args.add("--disable-check", check)
for check in enable_checks:
Expand All @@ -129,12 +136,24 @@ def _run_android_lint(
args.add("--classpath-aar", "%s:%s" % (aar.path, aar_dir.path))
inputs.append(aar)
inputs.append(aar_dir)
if android_lint_enable_check_dependencies:
args.add("--enable-check-dependencies")

# Declare the output file
args.add("--output", output)
outputs.append(output)
# Report-phase-only arguments: baseline, reporting filters, output, and the dependency
# partial results merged into the final verdict.
if is_report:
if not regenerate and baseline:
args.add("--baseline-file", baseline)
inputs.append(baseline)
if regenerate:
args.add("--regenerate-baseline-files")
if warnings_as_errors:
args.add("--warnings-as-errors")
if autofix == True:
args.add("--autofix")
for dependency in dependency_modules:
args.add("--dependency-partial-results", "%s=%s" % (dependency.module_name, dependency.partial_results.path))
inputs.append(dependency.partial_results)
args.add("--output", output)
outputs.append(output)

if android_lint_toolchain.android_home != None:
args.add("--android-home", android_lint_toolchain.android_home.label.workspace_root)
Expand All @@ -145,11 +164,14 @@ def _run_android_lint(
inputs.extend(java_runtime_info.files.to_list())

ctx.actions.run(
mnemonic = "AndroidLint",
mnemonic = "AndroidLintAnalyze" if mode == "analyze" else "AndroidLint",
inputs = inputs,
outputs = outputs,
executable = ctx.executable._lint_wrapper,
progress_message = "Running Android Lint {}".format(str(ctx.label)),
progress_message = "{} Android Lint {}".format(
"Analyzing" if mode == "analyze" else "Reporting",
str(ctx.label),
),
arguments = [args],
tools = [ctx.executable._lint_wrapper],
toolchain = _ANDROID_LINT_TOOLCHAIN_TYPE,
Expand Down Expand Up @@ -180,9 +202,32 @@ def _get_module_name(ctx):
return "%s_%s" % (path.replace("/", "_").replace("-", "_"), ctx.attr.name)
return name

def _collect_dependency_modules(ctx):
"""Collects the transitive partial-results modules from the rule's dependencies.

Returns:
A deduplicated list of structs(module_name, partial_results) for every analyzed
transitive dependency.
"""
transitive = []
for dep in ctx.attr.deps:
if _AndroidLintPartialResultsInfo in dep:
transitive.append(dep[_AndroidLintPartialResultsInfo].transitive_results)
seen = {}
modules = []
for node in depset(transitive = transitive).to_list():
if node.module_name and node.module_name not in seen:
seen[node.module_name] = True
modules.append(node)
return modules

def process_android_lint_issues(ctx, regenerate):
"""Runs Android Lint for the given target

Runs the analysis phase on the target's own sources to produce partial results, then the
report phase to merge those (and, when check-dependencies is enabled, the dependencies'
partial results produced by lint_analysis_aspect) into the final XML report.

Args:
ctx: The target context
regenerate: Whether to regenerate the baseline files
Expand All @@ -195,7 +240,7 @@ def process_android_lint_issues(ctx, regenerate):
# exactly `AndroidManifest.xml`.
manifest = ctx.file.manifest
if manifest and manifest.basename != "AndroidManifest.xml":
manifest = ctx.actions.declare_file("AndroidManifest.xml")
manifest = ctx.actions.declare_file("{}/AndroidManifest.xml".format(ctx.label.name))
ctx.actions.symlink(output = manifest, target_file = ctx.file.manifest)

# Collect the transitive classpath jars to run lint against.
Expand Down Expand Up @@ -229,32 +274,65 @@ def process_android_lint_issues(ctx, regenerate):
_utils.list_or_depset_to_list(_utils.get_android_lint_toolchain(ctx).android_lint_config.files),
)

output = ctx.actions.declare_file("{}.xml".format(ctx.label.name))
_run_android_lint(
ctx,
android_lint = _utils.only(_utils.list_or_depset_to_list(_utils.get_android_lint_toolchain(ctx).android_lint.files)),
module_name = _get_module_name(ctx),
output = output,
toolchain = _utils.get_android_lint_toolchain(ctx)
android_lint = _utils.only(_utils.list_or_depset_to_list(toolchain.android_lint.files))
module_name = _get_module_name(ctx)
deps_depset = depset(transitive = deps)
aars_depset = depset(transitive = aars)
java_runtime_info = ctx.attr._javabase[java_common.JavaRuntimeInfo]

common = dict(
android_lint = android_lint,
module_name = module_name,
srcs = ctx.files.srcs,
deps = depset(transitive = deps),
aars = depset(transitive = aars),
deps = deps_depset,
aars = aars_depset,
resource_files = ctx.files.resource_files,
manifest = manifest,
compile_sdk_version = _utils.get_android_lint_toolchain(ctx).compile_sdk_version,
java_language_level = _utils.get_android_lint_toolchain(ctx).java_language_level,
kotlin_language_level = _utils.get_android_lint_toolchain(ctx).kotlin_language_level,
baseline = getattr(ctx.file, "baseline", None),
compile_sdk_version = toolchain.compile_sdk_version,
java_language_level = toolchain.java_language_level,
kotlin_language_level = toolchain.kotlin_language_level,
config = config,
warnings_as_errors = ctx.attr.warnings_as_errors,
custom_rules = ctx.files.custom_rules,
disable_checks = ctx.attr.disable_checks,
enable_checks = ctx.attr.enable_checks,
android_lint_skip_bytecode_verifier = toolchain.android_lint_skip_bytecode_verifier,
android_lint_toolchain = toolchain,
java_runtime_info = java_runtime_info,
)

# Analysis phase: analyze this target's own sources, producing partial results.
own_partial_results = ctx.actions.declare_directory("{}_lint_partial_results".format(ctx.label.name))
_run_android_lint(
ctx,
mode = "analyze",
output = None,
partial_results = own_partial_results,
dependency_modules = [],
baseline = None,
warnings_as_errors = False,
autofix = False,
regenerate = False,
**common
)

# Report phase: merge this target's own partial results and, when enabled, the dependencies'.
dependency_modules = []
if toolchain.android_lint_enable_check_dependencies:
dependency_modules = _collect_dependency_modules(ctx)

output = ctx.actions.declare_file("{}.xml".format(ctx.label.name))
_run_android_lint(
ctx,
mode = "report",
output = output,
partial_results = own_partial_results,
dependency_modules = dependency_modules,
baseline = getattr(ctx.file, "baseline", None),
warnings_as_errors = ctx.attr.warnings_as_errors,
autofix = ctx.attr.autofix,
regenerate = regenerate,
android_lint_enable_check_dependencies = _utils.get_android_lint_toolchain(ctx).android_lint_enable_check_dependencies,
android_lint_skip_bytecode_verifier = _utils.get_android_lint_toolchain(ctx).android_lint_skip_bytecode_verifier,
android_lint_toolchain = _utils.get_android_lint_toolchain(ctx),
java_runtime_info = ctx.attr._javabase[java_common.JavaRuntimeInfo],
**common
)

return struct(
Expand Down
Loading