Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do dependency checking via a validation action #62

Merged
merged 5 commits into from
Nov 13, 2024
Merged
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
1 change: 1 addition & 0 deletions docs/newdocs/phases.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ def _scala_binary_implementation(ctx):
("ijinfo", _phase_ijinfo),
("binary_deployjar", _phase_binary_deployjar),
("binary_launcher", _phase_binary_launcher),
("outputgroupinfo", _phase_outputgroupinfo),
("coda", _phase_coda),
]).coda
```
Expand Down
5 changes: 1 addition & 4 deletions rules/common/private/utils.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,6 @@ def action_singlejar(
ctx,
inputs,
output,
phantom_inputs = depset(),
main_class = None,
progress_message = None,
resources = {},
Expand All @@ -174,8 +173,6 @@ def action_singlejar(

if type(inputs) == "list":
inputs = depset(inputs)
if type(phantom_inputs) == "list":
phantom_inputs = depset(phantom_inputs)

args = ctx.actions.args()
args.add("--exclude_build_data")
Expand All @@ -190,7 +187,7 @@ def action_singlejar(
args.set_param_file_format("multiline")
args.use_param_file("@%s", use_always = True)

all_inputs = depset(resources.values(), transitive = [inputs, phantom_inputs])
all_inputs = depset(resources.values(), transitive = [inputs])

ctx.actions.run(
arguments = [args],
Expand Down
3 changes: 3 additions & 0 deletions rules/private/phases.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ load(":phases/phase_ijinfo.bzl", _phase_ijinfo = "phase_ijinfo")
load(":phases/phase_javainfo.bzl", _phase_javainfo = "phase_javainfo")
load(":phases/phase_library_defaultinfo.bzl", _phase_library_defaultinfo = "phase_library_defaultinfo")
load(":phases/phase_noop.bzl", _phase_noop = "phase_noop")
load(":phases/phase_outputgroupinfo.bzl", _phase_outputgroupinfo = "phase_outputgroupinfo")
load(":phases/phase_resources.bzl", _phase_resources = "phase_resources")
load(":phases/phase_scalafmt_nondefault_outputs.bzl", _phase_scalafmt_nondefault_outputs = "phase_scalafmt_nondefault_outputs")
load(":phases/phase_semanticdb.bzl", _phase_semanticdb = "phase_semanticdb")
Expand Down Expand Up @@ -41,6 +42,8 @@ phase_library_defaultinfo = _phase_library_defaultinfo

phase_noop = _phase_noop

phase_outputgroupinfo = _phase_outputgroupinfo

phase_resources = _phase_resources

phase_scalafmt_nondefault_outputs = _phase_scalafmt_nondefault_outputs
Expand Down
2 changes: 1 addition & 1 deletion rules/private/phases/phase_binary_launcher.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def phase_binary_launcher(ctx, g):

g.out.providers.append(DefaultInfo(
executable = ctx.outputs.bin,
files = depset([ctx.outputs.bin, ctx.outputs.jar]),
files = depset([ctx.outputs.bin, ctx.outputs.jar] + g.semanticdb.outputs),
runfiles = ctx.runfiles(
files = inputs + files,
transitive_files = depset(
Expand Down
13 changes: 2 additions & 11 deletions rules/private/phases/phase_javainfo.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ load(
# PHASE: javainfo
#
# Builds up the JavaInfo provider. And the ScalaInfo, while we're at it.
# And DefaultInfo.
#

def phase_javainfo(ctx, g):
Expand Down Expand Up @@ -61,18 +60,10 @@ def phase_javainfo(ctx, g):
scala_configuration = g.init.scala_configuration,
)

output_group_info = OutputGroupInfo(
**g.out.output_groups
)

g.out.providers.extend([
output_group_info,
java_info,
scala_info,
])
g.out.providers.append(java_info)
g.out.providers.append(scala_info)

return struct(
java_info = java_info,
output_group_info = output_group_info,
scala_info = scala_info,
)
2 changes: 1 addition & 1 deletion rules/private/phases/phase_library_defaultinfo.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@

def phase_library_defaultinfo(ctx, g):
g.out.providers.append(DefaultInfo(
files = depset([ctx.outputs.jar]),
files = depset([ctx.outputs.jar] + g.semanticdb.outputs),
))
8 changes: 8 additions & 0 deletions rules/private/phases/phase_outputgroupinfo.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#
# PHASE: outputgroupinfo
#
# Generates the `OutputGroupInfo` provider.
#

def phase_outputgroupinfo(ctx, g):
g.out.providers.append(OutputGroupInfo(**g.out.output_groups))
5 changes: 1 addition & 4 deletions rules/private/phases/phase_singlejar.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,9 @@ def phase_singlejar(ctx, g):
# cause the build to fail, cleanly, if any declared outputs are
# missing from previous phases.
inputs = [f for f in ctx.files.resource_jars if f.extension.lower() in ["jar"]]
phantom_inputs = []
for v in [getattr(g, k) for k in dir(g) if k not in ["to_json", "to_proto"]]:
if hasattr(v, "jar"):
jar = getattr(v, "jar")
inputs.append(jar)
if hasattr(v, "outputs"):
phantom_inputs.extend(getattr(v, "outputs"))

_action_singlejar(ctx, inputs, ctx.outputs.jar, phantom_inputs)
_action_singlejar(ctx, inputs, ctx.outputs.jar)
7 changes: 1 addition & 6 deletions rules/private/phases/phase_test_launcher.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,7 @@ load(
"@rules_scala_annex//rules/private:coverage_replacements_provider.bzl",
_coverage_replacements_provider = "coverage_replacements_provider",
)
load(
"//rules/common:private/utils.bzl",
_action_singlejar = "action_singlejar",
_collect = "collect",
_write_launcher = "write_launcher",
)
load("//rules/common:private/utils.bzl", _collect = "collect", _write_launcher = "write_launcher")

#
# PHASE: test_launcher
Expand Down
24 changes: 9 additions & 15 deletions rules/private/phases/phase_zinc_depscheck.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,10 @@ def phase_zinc_depscheck(ctx, g):
return

deps_configuration = ctx.attr.scala[_DepsConfiguration]

deps_checks = {}
labeled_jar_groups = depset(transitive = [dep[_LabeledJars].values for dep in ctx.attr.deps])

worker_inputs, _, worker_input_manifests = ctx.resolve_command(tools = [deps_configuration.worker])
outputs = []

for name in ("direct", "used"):
deps_check = ctx.actions.declare_file("{}/depscheck_{}.success".format(ctx.label.name, name))
deps_args = ctx.actions.args()
Expand Down Expand Up @@ -61,21 +60,16 @@ def phase_zinc_depscheck(ctx, g):
),
arguments = [deps_args],
)
deps_checks[name] = deps_check

outputs = []
if deps_configuration.direct == "error":
outputs.append(deps_checks["direct"])
if deps_configuration.used == "error":
outputs.append(deps_checks["used"])
if getattr(deps_configuration, name) == "error":
outputs.append(deps_check)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you removed deps_check from line 24, then where is deps_check coming from?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be coming from line 28.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Totally missed that. My bad.


g.out.output_groups["depscheck"] = depset(outputs)
if "_validation" in g.out.output_groups:
validation_transitive = [g.out.output_groups["_validation"]]
else:
validation_transitive = None

return struct(
checks = deps_checks,
outputs = outputs,
toolchain = deps_configuration,
)
g.out.output_groups["_validation"] = depset(outputs, transitive = validation_transitive)

# If you use avoid using map_each, then labels are converted to their apparent repo name rather than
# their canonical repo name. The apparent repo name is the human readable one that we want for use
Expand Down
8 changes: 8 additions & 0 deletions rules/scala.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ load(
_phase_javainfo = "phase_javainfo",
_phase_library_defaultinfo = "phase_library_defaultinfo",
_phase_noop = "phase_noop",
_phase_outputgroupinfo = "phase_outputgroupinfo",
_phase_resources = "phase_resources",
_phase_semanticdb = "phase_semanticdb",
_phase_singlejar = "phase_singlejar",
_phase_test_launcher = "phase_test_launcher",
_run_phases = "run_phases",
Expand Down Expand Up @@ -211,11 +213,13 @@ def _scala_library_implementation(ctx):
("resources", _phase_resources),
("classpaths", _phase_classpaths),
("javainfo", _phase_javainfo),
("semanticdb", _phase_semanticdb),
("compile", _phase_noop),
("singlejar", _phase_singlejar),
("coverage", _phase_coverage_jacoco),
("ijinfo", _phase_ijinfo),
("library_defaultinfo", _phase_library_defaultinfo),
("outputgroupinfo", _phase_outputgroupinfo),
("coda", _phase_coda),
]).coda

Expand All @@ -224,12 +228,14 @@ def _scala_binary_implementation(ctx):
("resources", _phase_resources),
("classpaths", _phase_classpaths),
("javainfo", _phase_javainfo),
("semanticdb", _phase_semanticdb),
("compile", _phase_noop),
("singlejar", _phase_singlejar),
("coverage", _phase_coverage_jacoco),
("ijinfo", _phase_ijinfo),
("binary_deployjar", _phase_binary_deployjar),
("binary_launcher", _phase_binary_launcher),
("outputgroupinfo", _phase_outputgroupinfo),
("coda", _phase_coda),
]).coda

Expand All @@ -238,11 +244,13 @@ def _scala_test_implementation(ctx):
("resources", _phase_resources),
("classpaths", _phase_classpaths),
("javainfo", _phase_javainfo),
("semanticdb", _phase_semanticdb),
("compile", _phase_noop),
("singlejar", _phase_singlejar),
("coverage", _phase_coverage_jacoco),
("ijinfo", _phase_ijinfo),
("test_launcher", _phase_test_launcher),
("outputgroupinfo", _phase_outputgroupinfo),
("coda", _phase_coda),
]).coda

Expand Down
2 changes: 0 additions & 2 deletions rules/scala/private/provider.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ load(
load(
"//rules/private:phases.bzl",
_phase_bootstrap_compile = "phase_bootstrap_compile",
_phase_semanticdb = "phase_semanticdb",
_phase_zinc_compile = "phase_zinc_compile",
_phase_zinc_depscheck = "phase_zinc_depscheck",
)
Expand Down Expand Up @@ -60,7 +59,6 @@ def configure_zinc_scala_implementation(ctx):
_ScalaRulePhase(
phases = [
("=", "compile", "compile", _phase_zinc_compile),
("-", "compile", "semanticdb", _phase_semanticdb),
("+", "compile", "depscheck", _phase_zinc_depscheck),
],
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,18 @@ abstract class WorkerMain[S](stdin: InputStream = System.in, stdout: PrintStream
// close them after the async work in the Future is all done.
// If we do something synchronous with Using, then there's a race condition where the
// streams can get closed before the Future is completed.
var outStream: ByteArrayOutputStream = null
var out: PrintStream = null
var maybeOutStream: Option[ByteArrayOutputStream] = None
var maybeOut: Option[PrintStream] = None

def flushOut(): Unit = {
maybeOut.map(_.flush())
}

val workTask = CancellableTask {
outStream = new ByteArrayOutputStream
out = new PrintStream(outStream)
val outStream = new ByteArrayOutputStream()
val out = new PrintStream(outStream)
maybeOutStream = Some(outStream)
maybeOut = Some(out)
try {
work(ctx, args, out, sandboxDir, verbosity)
0
Expand All @@ -137,24 +143,25 @@ abstract class WorkerMain[S](stdin: InputStream = System.in, stdout: PrintStream
.andThen {
// Work task succeeded or failed in an expected way
case Success(code) =>
out.flush()
writeResponse(requestId, Some(outStream), Some(code))
flushOut()
writeResponse(requestId, maybeOutStream, Some(code))
logVerbose(s"WorkResponse $requestId sent with code $code")

case Failure(e: ExecutionException) =>
e.getCause() match {
// Task successfully cancelled
case cancelError: InterruptedException =>
flushOut()
writeResponse(requestId, None, None, wasCancelled = true)
logVerbose(
s"Cancellation WorkResponse sent for request id: $requestId in response to an" +
" InterruptedException",
)
// Work task threw a non-fatal error
case e =>
e.printStackTrace(out)
out.flush()
writeResponse(requestId, Some(outStream), Some(-1))
maybeOut.map(e.printStackTrace(_))
flushOut()
writeResponse(requestId, maybeOutStream, Some(-1))
logVerbose(
"Encountered an uncaught exception that was wrapped in an ExecutionException while" +
s" proccessing the Future for WorkRequest $requestId. This usually means a non-fatal" +
Expand All @@ -165,6 +172,7 @@ abstract class WorkerMain[S](stdin: InputStream = System.in, stdout: PrintStream

// Task successfully cancelled
case Failure(e: CancellationException) =>
flushOut()
writeResponse(requestId, None, None, wasCancelled = true)
logVerbose(
s"Cancellation WorkResponse sent for request id: $requestId in response to a" +
Expand All @@ -173,15 +181,15 @@ abstract class WorkerMain[S](stdin: InputStream = System.in, stdout: PrintStream

// Work task threw an uncaught exception
case Failure(e) =>
e.printStackTrace(out)
out.flush()
writeResponse(requestId, Some(outStream), Some(-1))
maybeOut.map(e.printStackTrace(_))
flushOut()
writeResponse(requestId, maybeOutStream, Some(-1))
logVerbose(s"Uncaught exception in Future while proccessing WorkRequest $requestId:")
e.printStackTrace(System.err)
}(scala.concurrent.ExecutionContext.global)
.andThen { case _ =>
out.close()
outStream.close()
maybeOut.map(_.close())
maybeOutStream.map(_.close())
}(scala.concurrent.ExecutionContext.global)

// putIfAbsent will return a non-null value if there was already a value in the map
Expand Down
7 changes: 7 additions & 0 deletions tests/dependencies/validation_action/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
load("@rules_scala_annex//rules:scala.bzl", "scala_library")

scala_library(
name = "validation_action",
scala = "//scala:2_13",
tags = ["manual"],
)
24 changes: 24 additions & 0 deletions tests/dependencies/validation_action/test
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/bash -e
. "$(dirname "$0")"/../../common.sh

scalacheckdeps_outputs="$(
bazel aquery 'mnemonic("^ScalaCheckDeps$", //dependencies/validation_action)' |
grep -oP '(?<=^ Outputs: \[).*(?=\]$)' |
sed 's/, /\n/g' |
sort -u
)"

action_inputs="$(
bazel aquery '//dependencies/validation_action' |
grep -oP '(?<=^ Inputs: \[).*(?=\]$)' |
sed 's/, /\n/g' |
sort -u
)"

common_files="$(comm -12 <(echo "$scalacheckdeps_outputs") <(echo "$action_inputs"))"

if [ -n "$common_files" ]; then
echo "Found some ScalaCheckDeps outputs that are the inputs to other actons:"
echo "$common_files"
exit 1
fi
Loading