Skip to content

Commit eabdf11

Browse files
sdtwiggjohnynek
authored andcommitted
Cleanup rules, esp. executables, to be more consistent (#171)
* Cleanup rules, esp. executables, to be proper In trying to get these rules to work in other build environments (with different locations and ways for building scalac, javac, etc.) found that various constructs were not being applied properly. Also, in general, the launchers for scala_test, scala_binary, and scala_repl were not resilient to the various permutations of places that Bazel will place your runfiles. In particular, a scala_binary could not reliably be used in a genrule or shell test. Thus... The biggest change is unifying the launcher for all three of the executable rules (see _write_launcher). This borrows from the form in the java_skylark rules to inspect itself and more reliably find its runfiles. As a bonus, now all rules set CLASSPATH now. Also, importantly, the binary rules (whose launcher is actually a shell script) were not setting up their runfiles correctly (this is kind of partly evidenced by them directly depending on _java and _jdk since _java runfiles should be including a _jdk). So, the rules now define two helper functions for getting runfiles and then use them. At the moment, was only necessary to grab the runfiles of _java, since rjars already was a transitive list. Added tests that the scala_binary outputs could be run in a genrule and test. I am still slightly concerned of edge cases (as the nesting gets even deeper, like if a genrule has an executable that itself has a scala_binary as a runfile). Other changes are: * For the implicit deps, many of them were marked as single_file when they did not need to be (in particular, any executable). That marking has been removed and the various associated uses of file were replaced by executable. * I am fairly certain that ctx.action already pulls in runfiles of all inputs, so for the scalac action, amended its input list to pull in just what is relevant (and prefer pulling in executable over files when it can). * scala_repl no longer relies on bin/scala but instead just invokes it directly from scalac. To do this, also had yo add _scalacompiler to repl rjars * Add jvm_flags to all binaries, scala_repl stty All binaries (repl, test, and binary) will now pass the JVM flags along. Also, no longer unnecessarily prepending -J to those flags. The scala_repl launcher will now save and restore stty like is done in the bin/scala script. In implementation, this was done by adding two new parameters to _write_launcher. Restore deprecation warning for suites argument in scala_test
1 parent 690cf39 commit eabdf11

File tree

3 files changed

+163
-75
lines changed

3 files changed

+163
-75
lines changed

scala/scala.bzl

Lines changed: 128 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,17 @@ _srcjar_filetype = FileType([".srcjar"])
2121
# TODO is there a way to derive this from the above?
2222
_scala_srcjar_filetype = FileType([".scala", ".srcjar", ".java"])
2323

24+
def _get_runfiles(target):
25+
runfiles = depset()
26+
runfiles += target.data_runfiles.files
27+
runfiles += target.default_runfiles.files
28+
return runfiles
29+
30+
def _get_all_runfiles(targets):
31+
runfiles = depset()
32+
for target in targets:
33+
runfiles += _get_runfiles(target)
34+
return runfiles
2435

2536
def _adjust_resources_path(path):
2637
# Here we are looking to find out the offset of this resource inside
@@ -84,14 +95,16 @@ def _build_nosrc_jar(ctx, buildijar):
8495
cp_resources=cp_resources,
8596
out=ctx.outputs.jar.path,
8697
manifest=ctx.outputs.manifest.path,
87-
java=ctx.file._java.path,
98+
java=ctx.executable._java.path,
8899
jar=_get_jar_path(ctx.files._jar))
89100
outs = [ctx.outputs.jar]
90101
if buildijar:
91102
outs.extend([ctx.outputs.ijar])
92103

93-
inputs = ctx.files.resources + ctx.files._jdk + ctx.files._jar + [
94-
ctx.outputs.manifest, ctx.file._java
104+
inputs = ctx.files.resources + [
105+
ctx.outputs.manifest,
106+
ctx.executable._jar,
107+
ctx.executable._java,
95108
]
96109

97110
ctx.action(
@@ -124,7 +137,7 @@ def _compile(ctx, _jars, dep_srcjars, buildijar):
124137
ijar_cmd_path = ""
125138
if buildijar:
126139
ijar_output_path = ctx.outputs.ijar.path
127-
ijar_cmd_path = ctx.file._ijar.path
140+
ijar_cmd_path = ctx.executable._ijar.path
128141

129142
java_srcs = _java_filetype.filter(ctx.files.srcs)
130143
sources = _scala_filetype.filter(ctx.files.srcs) + java_srcs
@@ -174,7 +187,7 @@ SourceJars: {srcjars}
174187
ijar_cmd_path=ijar_cmd_path,
175188
srcjars=",".join([f.path for f in all_srcjars]),
176189
javac_opts=" ".join(ctx.attr.javacopts),
177-
javac_path=ctx.file._javac.path,
190+
javac_path=ctx.executable._javac.path,
178191
java_files=",".join([f.path for f in java_srcs]),
179192
# these are the flags passed to javac, which needs them prefixed by -J
180193
jvm_flags=",".join(["-J" + flag for flag in ctx.attr.jvm_flags]),
@@ -202,10 +215,10 @@ SourceJars: {srcjars}
202215
ctx.files.plugins +
203216
ctx.files.resources +
204217
ctx.files.resource_jars +
205-
ctx.files._jdk +
206218
[ctx.outputs.manifest,
207-
ctx.file._ijar,
208-
ctx.file._java,
219+
ctx.executable._ijar,
220+
ctx.executable._java,
221+
ctx.executable._javac,
209222
ctx.file._scalacompiler,
210223
ctx.file._scalareflect,
211224
ctx.file._scalalib,
@@ -280,45 +293,49 @@ def write_manifest(ctx):
280293
output=ctx.outputs.manifest,
281294
content=manifest)
282295

283-
284-
def _write_launcher(ctx, jars):
296+
def _write_launcher(ctx, rjars, main_class, jvm_flags, args, run_before_binary, run_after_binary):
285297
classpath = ':'.join(
286-
["$0.runfiles/%s/%s" % (ctx.workspace_name, f.short_path) for f in jars]
287-
)
298+
["$JAVA_RUNFILES/{repo}/{spath}".format(
299+
repo = ctx.workspace_name, spath = f.short_path
300+
) for f in rjars])
288301

289302
content = """#!/bin/bash
290-
export CLASSPATH={classpath}
291-
$0.runfiles/{repo}/{java} {name} "$@"
292-
""".format(
293-
repo=ctx.workspace_name,
294-
java=ctx.file._java.short_path,
295-
name=ctx.attr.main_class,
296-
deploy_jar=ctx.outputs.jar.path,
297-
classpath=classpath,
298-
)
299-
ctx.file_action(
300-
output=ctx.outputs.executable,
301-
content=content)
302-
303303
304-
def _write_test_launcher(ctx, jars):
305-
if len(ctx.attr.suites) != 0:
306-
print(
307-
"suites attribute is deprecated. All scalatest test suites are run"
308-
)
304+
case "$0" in
305+
/*) self="$0" ;;
306+
*) self="$PWD/$0" ;;
307+
esac
308+
309+
if [[ -z "$JAVA_RUNFILES" ]]; then
310+
if [[ -e "${{self}}.runfiles" ]]; then
311+
export JAVA_RUNFILES="${{self}}.runfiles"
312+
fi
313+
if [[ -n "$JAVA_RUNFILES" ]]; then
314+
export TEST_SRCDIR=${{TEST_SRCDIR:-$JAVA_RUNFILES}}
315+
fi
316+
fi
317+
318+
export CLASSPATH={classpath}
319+
{run_before_binary}
320+
$JAVA_RUNFILES/{repo}/{java} {jvm_flags} {main_class} {args} "$@"
321+
BINARY_EXIT_CODE=$?
322+
{run_after_binary}
323+
exit $BINARY_EXIT_CODE
324+
""".format(
325+
classpath = classpath,
326+
repo = ctx.workspace_name,
327+
java = ctx.executable._java.short_path,
328+
jvm_flags = " ".join(jvm_flags),
329+
main_class = main_class,
330+
args = args,
331+
run_before_binary = run_before_binary,
332+
run_after_binary = run_after_binary,
333+
)
309334

310-
content = """#!/bin/bash
311-
{java} -cp {cp} {name} {args} -C io.bazel.rules.scala.JUnitXmlReporter "$@"
312-
"""
313-
content = content.format(
314-
java=ctx.file._java.short_path,
315-
cp=":".join([j.short_path for j in jars]),
316-
name=ctx.attr.main_class,
317-
args="-R \"{path}\" -oWDS".format(path=ctx.outputs.jar.short_path))
318335
ctx.file_action(
319-
output=ctx.outputs.executable,
320-
content=content)
321-
336+
output = ctx.outputs.executable,
337+
content = content,
338+
)
322339

323340
def collect_srcjars(targets):
324341
srcjars = set()
@@ -384,6 +401,9 @@ def _lib(ctx, non_macro_lib):
384401
transitive_compile_exports=texp.compiletime,
385402
transitive_runtime_exports=texp.runtime
386403
)
404+
405+
# Note that rjars already transitive so don't really
406+
# need to use transitive_files with _get_all_runfiles
387407
runfiles = ctx.runfiles(
388408
files=list(rjars),
389409
collect_data=True)
@@ -425,11 +445,15 @@ def _scala_binary_common(ctx, cjars, rjars):
425445
_build_deployable(ctx, list(rjars))
426446

427447
runfiles = ctx.runfiles(
428-
files = list(rjars) + [ctx.outputs.executable] + [ctx.file._java] + ctx.files._jdk,
448+
files = list(rjars) + [ctx.outputs.executable],
449+
transitive_files = _get_runfiles(ctx.attr._java),
429450
collect_data = True)
430451

431-
jars = _collect_jars(ctx.attr.deps)
432-
rule_outputs = struct(ijar=outputs.class_jar, class_jar=outputs.class_jar, deploy_jar=ctx.outputs.deploy_jar)
452+
rule_outputs = struct(
453+
ijar=outputs.class_jar,
454+
class_jar=outputs.class_jar,
455+
deploy_jar=ctx.outputs.deploy_jar,
456+
)
433457
scalaattr = struct(outputs = rule_outputs,
434458
transitive_runtime_deps = rjars,
435459
transitive_compile_exports = set(),
@@ -446,41 +470,59 @@ def _scala_binary_impl(ctx):
446470
cjars += [ctx.file._scalareflect]
447471
rjars += [ctx.outputs.jar, ctx.file._scalalib, ctx.file._scalareflect]
448472
rjars += _collect_jars(ctx.attr.runtime_deps).runtime
449-
_write_launcher(ctx, rjars)
473+
_write_launcher(
474+
ctx = ctx,
475+
rjars = rjars,
476+
main_class = ctx.attr.main_class,
477+
jvm_flags = ctx.attr.jvm_flags,
478+
args = "",
479+
run_before_binary = "",
480+
run_after_binary = "",
481+
)
450482
return _scala_binary_common(ctx, cjars, rjars)
451483

452484
def _scala_repl_impl(ctx):
453485
jars = _collect_jars(ctx.attr.deps)
454486
rjars = jars.runtime
455-
rjars += [ctx.file._scalalib, ctx.file._scalareflect]
487+
rjars += [ctx.file._scalalib, ctx.file._scalareflect, ctx.file._scalacompiler]
456488
rjars += _collect_jars(ctx.attr.runtime_deps).runtime
457-
classpath = ':'.join(["$0.runfiles/%s/%s" % (ctx.workspace_name, f.short_path) for f in rjars])
458-
content = """#!/bin/bash
459-
env JAVACMD=$0.runfiles/{repo}/{java} $0.runfiles/{repo}/{scala} {jvm_flags} -classpath {classpath} {scala_opts} "$@"
460-
""".format(
461-
java=ctx.file._java.short_path,
462-
repo=ctx.workspace_name,
463-
jvm_flags=" ".join(["-J" + flag for flag in ctx.attr.jvm_flags]),
464-
scala=ctx.file._scala.short_path,
465-
classpath=classpath,
466-
scala_opts=" ".join(ctx.attr.scalacopts),
489+
490+
args = " ".join(ctx.attr.scalacopts)
491+
_write_launcher(
492+
ctx = ctx,
493+
rjars = rjars,
494+
main_class = "scala.tools.nsc.MainGenericRunner",
495+
jvm_flags = ["-Dscala.usejavacp=true"] + ctx.attr.jvm_flags,
496+
args = args,
497+
run_before_binary = """
498+
# save stty like in bin/scala
499+
saved_stty=$(stty -g 2>/dev/null)
500+
if [[ ! $? ]]; then
501+
saved_stty=""
502+
fi
503+
""",
504+
run_after_binary = """
505+
if [[ "$saved_stty" != "" ]]; then
506+
stty $saved_stty
507+
saved_stty=""
508+
fi
509+
""",
467510
)
468-
ctx.file_action(
469-
output=ctx.outputs.executable,
470-
content=content)
471511

472512
runfiles = ctx.runfiles(
473-
files = list(rjars) +
474-
[ctx.outputs.executable] +
475-
[ctx.file._java] +
476-
ctx.files._jdk +
477-
[ctx.file._scala],
513+
files = list(rjars) + [ctx.outputs.executable],
514+
transitive_files = _get_runfiles(ctx.attr._java),
478515
collect_data = True)
516+
479517
return struct(
480-
files=set([ctx.outputs.executable]),
481-
runfiles=runfiles)
518+
files = set([ctx.outputs.executable]),
519+
runfiles = runfiles)
482520

483521
def _scala_test_impl(ctx):
522+
if len(ctx.attr.suites) != 0:
523+
print(
524+
"suites attribute is deprecated. All scalatest test suites are run"
525+
)
484526
deps = ctx.attr.deps
485527
deps += [ctx.attr._scalatest_reporter]
486528
jars = _collect_jars(deps)
@@ -494,17 +536,32 @@ def _scala_test_impl(ctx):
494536
ctx.file._scalaxml
495537
]
496538
rjars += _collect_jars(ctx.attr.runtime_deps).runtime
497-
_write_test_launcher(ctx, rjars)
539+
540+
args = " ".join([
541+
"-R \"{path}\"".format(path=ctx.outputs.jar.short_path),
542+
"-oWDS",
543+
"-C io.bazel.rules.scala.JUnitXmlReporter ",
544+
])
545+
# main_class almost has to be "org.scalatest.tools.Runner" due to args....
546+
_write_launcher(
547+
ctx = ctx,
548+
rjars = rjars,
549+
main_class = ctx.attr.main_class,
550+
jvm_flags = ctx.attr.jvm_flags,
551+
args = args,
552+
run_before_binary = "",
553+
run_after_binary = "",
554+
)
498555
return _scala_binary_common(ctx, cjars, rjars)
499556

500557
_implicit_deps = {
501-
"_ijar": attr.label(executable=True, cfg="host", default=Label("@bazel_tools//tools/jdk:ijar"), single_file=True, allow_files=True),
558+
"_ijar": attr.label(executable=True, cfg="host", default=Label("@bazel_tools//tools/jdk:ijar"), allow_files=True),
502559
"_scalac": attr.label(executable=True, cfg="host", default=Label("//src/java/io/bazel/rulesscala/scalac"), allow_files=True),
503560
"_scalalib": attr.label(default=Label("@scala//:lib/scala-library.jar"), single_file=True, allow_files=True),
504561
"_scalacompiler": attr.label(default=Label("@scala//:lib/scala-compiler.jar"), single_file=True, allow_files=True),
505562
"_scalareflect": attr.label(default=Label("@scala//:lib/scala-reflect.jar"), single_file=True, allow_files=True),
506-
"_java": attr.label(executable=True, cfg="host", default=Label("@bazel_tools//tools/jdk:java"), single_file=True, allow_files=True),
507-
"_javac": attr.label(executable=True, cfg="host", default=Label("@bazel_tools//tools/jdk:javac"), single_file=True, allow_files=True),
563+
"_java": attr.label(executable=True, cfg="host", default=Label("@bazel_tools//tools/jdk:java"), allow_files=True),
564+
"_javac": attr.label(executable=True, cfg="host", default=Label("@bazel_tools//tools/jdk:javac"), allow_files=True),
508565
"_jar": attr.label(executable=True, cfg="host", default=Label("//src/java/io/bazel/rulesscala/jar:binary_deploy.jar"), allow_files=True),
509566
"_jar_bin": attr.label(executable=True, cfg="host", default=Label("//src/java/io/bazel/rulesscala/jar:binary")),
510567
"_jdk": attr.label(default=Label("//tools/defaults:jdk"), allow_files=True),
@@ -587,11 +644,7 @@ scala_test = rule(
587644

588645
scala_repl = rule(
589646
implementation=_scala_repl_impl,
590-
attrs= _implicit_deps +
591-
_common_attrs +
592-
{
593-
"_scala": attr.label(executable=True, cfg="data", default=Label("@scala//:bin/scala"), single_file=True, allow_files=True)
594-
},
647+
attrs= _implicit_deps + _common_attrs,
595648
outputs={},
596649
executable=True,
597650
)

test/BUILD

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,3 +216,34 @@ scala_binary(
216216
srcs = ["src/main/scala/scala/test/only_java/Alpha.java"],
217217
main_class = "scala.test.Alpha",
218218
)
219+
220+
# Make sure scala_binary works in test environment
221+
[sh_test(
222+
name = "Run" + binary,
223+
srcs = ["test_binary.sh"],
224+
args = ["$(location %s)" % binary],
225+
data = [":%s" % binary],
226+
) for binary in [
227+
"JavaBinary",
228+
"JavaBinary2",
229+
"ScalaBinary",
230+
"ScalaLibBinary",
231+
"MoreScalaLibBinary",
232+
"MixJavaScalaLibBinary",
233+
"JavaOnlySources",
234+
]]
235+
236+
# Make sure scala_binary works in genrule environment
237+
genrule(
238+
name = "ScalaBinaryInGenrule",
239+
tools = [":ScalaBinary"],
240+
outs = ["scala_binary_out.txt"],
241+
cmd = "$(location :ScalaBinary) > $@",
242+
)
243+
244+
sh_test(
245+
name = "TestScalaBinaryInGenrule",
246+
srcs = ["test_binary.sh"],
247+
args = ["cat $(location :ScalaBinaryInGenrule)"],
248+
data = [":ScalaBinaryInGenrule"],
249+
)

test/test_binary.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/bin/bash
2+
3+
echo "Executing: " $@
4+
$@

0 commit comments

Comments
 (0)