diff --git a/.bazelrc.travis b/.bazelrc.travis
index b1d24691c..6ba330e33 100644
--- a/.bazelrc.travis
+++ b/.bazelrc.travis
@@ -1,11 +1,10 @@
# This is from Bazel's former travis setup, to avoid blowing up the RAM usage.
-startup --host_jvm_args=-Xmx2500m
-startup --host_jvm_args=-Xms2500m
+startup --host_jvm_args=-Xmx3072m
+startup --host_jvm_args=-Xms1024m
# startup --batch # we actually start many bazels in the test script, we don't want batch
-test --ram_utilization_factor=10
# Just making sure that we don't OOM with parallel builds
-build --local_resources 2048,.5,1.0
+build --local_resources 3072,.5,1.0
# This is so we understand failures better
build --verbose_failures
@@ -17,7 +16,7 @@ build --verbose_failures
# runs stuff in a container, and since Travis already runs its script
# in a container (unless you require sudo in your .travis.yml) this
# fails to run tests.
-build --strategy=Scalac=worker --strategy=ScroogeRule=worker --worker_max_instances=3
+build --strategy=Scalac=worker --strategy=ScroogeRule=worker --worker_max_instances=2
#test --test_strategy=standalone
# Below this line, .travis.yml will cat the default bazelrc.
diff --git a/.travis.yml b/.travis.yml
index 9e19f23ac..d23b486b8 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,7 +4,7 @@ sudo: required
# xcode8 has jdk8
osx_image: xcode8
# Not technically required but suppresses 'Ruby' in Job status message.
-language: java
+language: sh
cache:
directories:
@@ -13,38 +13,54 @@ cache:
os:
- linux
- osx
+ - windows
env:
# Linting is broken. Disable until fixed.
# See https://github.com/bazelbuild/rules_scala/pull/622
# we want to test the last release
#- V=0.16.1 TEST_SCRIPT=test_lint.sh
- - V=0.23.1 TEST_SCRIPT=test_rules_scala.sh
+ - V=0.23.1 TEST_SCRIPT=test_rules_scala
#- V=0.14.1 TEST_SCRIPT=test_intellij_aspect.sh
- - V=0.23.1 TEST_SCRIPT=test_reproducibility.sh
+ - V=0.23.1 TEST_SCRIPT=test_reproducibility
+
before_install:
- |
- if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then
- OS=darwin
- else
- sysctl kernel.unprivileged_userns_clone=1
- sudo apt-get update -q
- sudo apt-get install libxml2-utils -y
- OS=linux
- fi
- if [[ $V =~ .*rc[0-9]+.* ]]; then
- PRE_RC=$(expr "$V" : '\([0-9.]*\)rc.*')
- RC_PRC=$(expr "$V" : '[0-9.]*\(rc.*\)')
- URL="https://storage.googleapis.com/bazel/${PRE_RC}/${RC_PRC}/bazel-${V}-installer-${OS}-x86_64.sh"
+ if [[ "${TRAVIS_OS_NAME}" == "windows" ]]; then
+ choco install jdk8 -params 'installdir=c:\\java8'
+ choco install bazel --version ${V}
else
- URL="https://github.com/bazelbuild/bazel/releases/download/${V}/bazel-${V}-installer-${OS}-x86_64.sh"
+ if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then
+ OS=darwin
+ else
+ sudo sysctl kernel.unprivileged_userns_clone=1
+ sudo add-apt-repository -y ppa:openjdk-r/ppa
+ sudo apt-get update -q
+ sudo apt-get install openjdk-8-jdk -y
+ sudo apt-get install libxml2-utils -y
+ OS=linux
+ fi
+
+ if [[ $V =~ .*rc[0-9]+.* ]]; then
+ PRE_RC=$(expr "$V" : '\([0-9.]*\)rc.*')
+ RC_PRC=$(expr "$V" : '[0-9.]*\(rc.*\)')
+ URL="https://storage.googleapis.com/bazel/${PRE_RC}/${RC_PRC}/bazel-${V}-installer-${OS}-x86_64.sh"
+ else
+ URL="https://github.com/bazelbuild/bazel/releases/download/${V}/bazel-${V}-installer-${OS}-x86_64.sh"
+ fi
+ wget -O install.sh "${URL}"
+ chmod +x install.sh
+ ./install.sh --user
+ rm -f install.sh
fi
- wget -O install.sh "${URL}"
- chmod +x install.sh
- ./install.sh --user
- rm -f install.sh
- cat .bazelrc.travis >> .bazelrc
script:
- - bash $TEST_SCRIPT ci
+ - |
+ if [[ "${TRAVIS_OS_NAME}" == "windows" ]]; then
+ powershell -Command 'Set-ExecutionPolicy RemoteSigned -scope CurrentUser'
+ powershell -File ./${TEST_SCRIPT}.ps1
+ else
+ bash ./${TEST_SCRIPT}.sh ci
+ fi
diff --git a/scala/private/rule_impls.bzl b/scala/private/rule_impls.bzl
index 863f6da8d..6ae6c4084 100644
--- a/scala/private/rule_impls.bzl
+++ b/scala/private/rule_impls.bzl
@@ -590,7 +590,35 @@ def _jar_path_based_on_java_bin(ctx):
jar_path = java_bin.rpartition("/")[0] + "/jar"
return jar_path
-def _write_executable(ctx, rjars, main_class, jvm_flags, wrapper, use_jacoco):
+def _write_executable(ctx, executable, rjars, main_class, jvm_flags, wrapper, use_jacoco):
+ if (_is_windows(ctx)):
+ return _write_executable_windows(ctx, executable, rjars, main_class, jvm_flags, wrapper, use_jacoco)
+ else:
+ return _write_executable_non_windows(ctx, executable, rjars, main_class, jvm_flags, wrapper, use_jacoco)
+
+def _write_executable_windows(ctx, executable, rjars, main_class, jvm_flags, wrapper, use_jacoco):
+ # NOTE: `use_jacoco` is currently ignored on Windows.
+ # TODO: tests coverage support for Windows
+ classpath = ";".join(
+ [("external/%s" % (j.short_path[3:]) if j.short_path.startswith("../") else j.short_path) for j in rjars.to_list()],
+ )
+ jvm_flags_str = ";".join(jvm_flags)
+ java_for_exe = str(ctx.attr._java_runtime[java_common.JavaRuntimeInfo].java_executable_exec_path)
+
+ cpfile = ctx.actions.declare_file("%s.classpath" % ctx.label.name)
+ ctx.actions.write(cpfile, classpath)
+
+ ctx.actions.run(
+ outputs = [executable],
+ inputs = [cpfile],
+ executable = ctx.attr._exe.files_to_run.executable,
+ arguments = [executable.path, ctx.workspace_name, java_for_exe, main_class, cpfile.path, jvm_flags_str],
+ mnemonic = "ExeLauncher",
+ progress_message = "Creating exe launcher",
+ )
+ return []
+
+def _write_executable_non_windows(ctx, executable, rjars, main_class, jvm_flags, wrapper, use_jacoco):
template = ctx.attr._java_stub_template.files.to_list()[0]
jvm_flags = " ".join(
@@ -608,7 +636,7 @@ def _write_executable(ctx, rjars, main_class, jvm_flags, wrapper, use_jacoco):
)
jacoco_metadata_file = ctx.actions.declare_file(
"%s.jacoco_metadata.txt" % ctx.attr.name,
- sibling = ctx.outputs.executable,
+ sibling = executable,
)
ctx.actions.write(jacoco_metadata_file, "\n".join([
jar.short_path.replace("../", "external/")
@@ -616,7 +644,7 @@ def _write_executable(ctx, rjars, main_class, jvm_flags, wrapper, use_jacoco):
]))
ctx.actions.expand_template(
template = template,
- output = ctx.outputs.executable,
+ output = executable,
substitutions = {
"%classpath%": classpath,
"%javabin%": javabin,
@@ -642,7 +670,7 @@ def _write_executable(ctx, rjars, main_class, jvm_flags, wrapper, use_jacoco):
)
ctx.actions.expand_template(
template = template,
- output = ctx.outputs.executable,
+ output = executable,
substitutions = {
"%classpath%": classpath,
"%java_start_class%": main_class,
@@ -661,6 +689,12 @@ def _write_executable(ctx, rjars, main_class, jvm_flags, wrapper, use_jacoco):
)
return []
+def _declare_executable(ctx):
+ if (_is_windows(ctx)):
+ return ctx.actions.declare_file("%s.exe" % ctx.label.name)
+ else:
+ return ctx.actions.declare_file(ctx.label.name)
+
def _collect_runtime_jars(dep_targets):
runtime_jars = []
@@ -850,6 +884,7 @@ def scala_macro_library_impl(ctx):
# Common code shared by all scala binary implementations.
def _scala_binary_common(
ctx,
+ executable,
cjars,
rjars,
transitive_compile_time_jars,
@@ -857,7 +892,8 @@ def _scala_binary_common(
java_wrapper,
unused_dependency_checker_mode,
unused_dependency_checker_ignored_targets,
- implicit_junit_deps_needed_for_java_compilation = []):
+ implicit_junit_deps_needed_for_java_compilation = [],
+ runfiles_ext = []):
write_manifest(ctx)
outputs = _compile_or_empty(
ctx,
@@ -878,7 +914,7 @@ def _scala_binary_common(
runfiles = ctx.runfiles(
transitive_files = depset(
- [ctx.outputs.executable, java_wrapper] + ctx.files._java_runtime,
+ [executable, java_wrapper] + ctx.files._java_runtime + runfiles_ext,
transitive = [rjars],
),
collect_data = True,
@@ -900,8 +936,9 @@ def _scala_binary_common(
java_provider = create_java_provider(scalaattr, transitive_compile_time_jars)
return struct(
+ executable = executable,
coverage = outputs.coverage,
- files = depset([ctx.outputs.executable, ctx.outputs.jar]),
+ files = depset([executable, ctx.outputs.jar]),
instrumented_files = outputs.coverage.instrumented_files,
providers = [java_provider, jars2labels] + outputs.coverage.providers,
runfiles = runfiles,
@@ -952,8 +989,12 @@ def scala_binary_impl(ctx):
(cjars, transitive_rjars) = (jars.compile_jars, jars.transitive_runtime_jars)
wrapper = _write_java_wrapper(ctx, "", "")
+
+ executable = _declare_executable(ctx)
+
out = _scala_binary_common(
ctx,
+ executable,
cjars,
transitive_rjars,
jars.transitive_compile_jars,
@@ -968,6 +1009,7 @@ def scala_binary_impl(ctx):
)
_write_executable(
ctx = ctx,
+ executable = executable,
jvm_flags = ctx.attr.jvm_flags,
main_class = ctx.attr.main_class,
rjars = out.transitive_rjars,
@@ -991,6 +1033,9 @@ def scala_repl_impl(ctx):
(cjars, transitive_rjars) = (jars.compile_jars, jars.transitive_runtime_jars)
args = " ".join(ctx.attr.scalacopts)
+
+ executable = _declare_executable(ctx)
+
wrapper = _write_java_wrapper(
ctx,
args,
@@ -1012,6 +1057,7 @@ trap finish EXIT
out = _scala_binary_common(
ctx,
+ executable,
cjars,
transitive_rjars,
jars.transitive_compile_jars,
@@ -1026,6 +1072,7 @@ trap finish EXIT
)
_write_executable(
ctx = ctx,
+ executable = executable,
jvm_flags = ["-Dscala.usejavacp=true"] + ctx.attr.jvm_flags,
main_class = "scala.tools.nsc.MainGenericRunner",
rjars = out.transitive_rjars,
@@ -1082,16 +1129,21 @@ def scala_test_impl(ctx):
jars.jars2labels,
)
- args = " ".join([
- "-R \"{path}\"".format(path = ctx.outputs.jar.short_path),
+ args = "\n".join([
+ "-R", ctx.outputs.jar.short_path,
_scala_test_flags(ctx),
- "-C io.bazel.rules.scala.JUnitXmlReporter ",
+ "-C", "io.bazel.rules.scala.JUnitXmlReporter",
])
- # main_class almost has to be "org.scalatest.tools.Runner" due to args....
- wrapper = _write_java_wrapper(ctx, args, "")
+ argsFile = ctx.actions.declare_file("%s.args" % ctx.label.name)
+ ctx.actions.write(argsFile, args)
+
+ executable = _declare_executable(ctx)
+
+ wrapper = _write_java_wrapper(ctx, "", "")
out = _scala_binary_common(
ctx,
+ executable,
cjars,
transitive_rjars,
transitive_compile_jars,
@@ -1100,6 +1152,7 @@ def scala_test_impl(ctx):
unused_dependency_checker_ignored_targets =
unused_dependency_checker_ignored_targets,
unused_dependency_checker_mode = unused_dependency_checker_mode,
+ runfiles_ext = [argsFile]
)
rjars = out.transitive_rjars
@@ -1119,7 +1172,11 @@ def scala_test_impl(ctx):
coverage_runfiles.extend(_write_executable(
ctx = ctx,
- jvm_flags = ctx.attr.jvm_flags,
+ executable = executable,
+ jvm_flags = [
+ "-DRULES_SCALA_MAIN_WS_NAME=%s" % ctx.workspace_name,
+ "-DRULES_SCALA_ARGS_FILE=%s" % argsFile.short_path
+ ] + ctx.attr.jvm_flags,
main_class = ctx.attr.main_class,
rjars = rjars,
use_jacoco = ctx.configuration.coverage_enabled,
@@ -1127,6 +1184,7 @@ def scala_test_impl(ctx):
))
return struct(
+ executable = executable,
files = out.files,
instrumented_files = out.instrumented_files,
providers = out.providers,
@@ -1204,9 +1262,12 @@ def scala_junit_test_impl(ctx):
ctx.attr._hamcrest,
]
+ executable = _declare_executable(ctx)
+
wrapper = _write_java_wrapper(ctx, "", "")
out = _scala_binary_common(
ctx,
+ executable,
cjars,
transitive_rjars,
jars.transitive_compile_jars,
@@ -1239,6 +1300,7 @@ def scala_junit_test_impl(ctx):
]
_write_executable(
ctx = ctx,
+ executable = executable,
jvm_flags = launcherJvmFlags + ctx.attr.jvm_flags,
main_class = "com.google.testing.junit.runner.BazelTestRunner",
rjars = out.transitive_rjars,
@@ -1295,3 +1357,6 @@ def _jacoco_offline_instrument(ctx, input_jar):
def _jacoco_offline_instrument_format_each(in_out_pair):
return (["%s=%s" % (in_out_pair[0].path, in_out_pair[1].path)])
+
+def _is_windows(ctx):
+ return ctx.configuration.host_path_separator == ";"
diff --git a/scala/scala.bzl b/scala/scala.bzl
index d201f3015..310e886bc 100644
--- a/scala/scala.bzl
+++ b/scala/scala.bzl
@@ -67,6 +67,11 @@ _implicit_deps = {
"@io_bazel_rules_scala//src/java/io/bazel/rulesscala/scalac",
),
),
+ "_exe": attr.label(
+ executable = True,
+ cfg = "host",
+ default = Label("@io_bazel_rules_scala//src/java/io/bazel/rulesscala/exe:exe"),
+ ),
}
# Single dep to allow IDEs to pickup all the implicit dependencies.
@@ -472,6 +477,14 @@ def scala_repositories(
server_urls = maven_servers,
)
+ _scala_maven_import_external(
+ name = "io_bazel_rules_scala_guava",
+ artifact = "com.google.guava:guava:21.0",
+ jar_sha256 = "972139718abc8a4893fa78cba8cf7b2c903f35c97aaf44fa3031b0669948b480",
+ licenses = ["notice"],
+ server_urls = maven_servers,
+ )
+
_scala_maven_import_external(
name = "io_bazel_rules_scala_org_jacoco_org_jacoco_core",
artifact = "org.jacoco:org.jacoco.core:0.7.5.201505241946",
@@ -487,7 +500,7 @@ def scala_repositories(
licenses = ["notice"],
server_urls = maven_servers,
)
-
+
# Using this and not the bazel regular one due to issue when classpath is too long
# until https://github.com/bazelbuild/bazel/issues/6955 is resolved
if native.existing_rule("java_stub_template") == None:
@@ -538,6 +551,11 @@ def scala_repositories(
actual = "@io_bazel_rules_scala_scala_parser_combinators",
)
+ native.bind(
+ name = "io_bazel_rules_scala/dependency/scala/guava",
+ actual = "@io_bazel_rules_scala_guava",
+ )
+
def _sanitize_string_for_usage(s):
res_array = []
for idx in range(len(s)):
@@ -554,10 +572,13 @@ def scala_test_suite(
name,
srcs = [],
visibility = None,
+ use_short_names = False,
**kwargs):
ts = []
+ i = 0
for test_file in srcs:
- n = "%s_test_suite_%s" % (name, _sanitize_string_for_usage(test_file))
+ i = i+1
+ n = ("%s_%s" % (name, i)) if use_short_names else ("%s_test_suite_%s" % (name, _sanitize_string_for_usage(test_file)))
scala_test(
name = n,
srcs = [test_file],
diff --git a/src/java/io/bazel/rulesscala/exe/BUILD b/src/java/io/bazel/rulesscala/exe/BUILD
new file mode 100644
index 000000000..da9e98fe7
--- /dev/null
+++ b/src/java/io/bazel/rulesscala/exe/BUILD
@@ -0,0 +1,24 @@
+java_library(
+ name = "exe-lib",
+ srcs = [
+ "LauncherFileWriter.java",
+ "LaunchInfo.java",
+ ],
+ deps = [
+ "@bazel_tools//tools/java/runfiles",
+ "//external:io_bazel_rules_scala/dependency/scala/guava",
+ ],
+ visibility = ["//visibility:private"],
+)
+
+java_binary(
+ name = "exe",
+ main_class = "io.bazel.rulesscala.exe.LauncherFileWriter",
+ runtime_deps = [
+ ":exe-lib",
+ ],
+ data = [
+ "@bazel_tools//tools/launcher",
+ ],
+ visibility = ["//visibility:public"],
+)
diff --git a/src/java/io/bazel/rulesscala/exe/LaunchInfo.java b/src/java/io/bazel/rulesscala/exe/LaunchInfo.java
new file mode 100644
index 000000000..1c4f76d40
--- /dev/null
+++ b/src/java/io/bazel/rulesscala/exe/LaunchInfo.java
@@ -0,0 +1,168 @@
+// Copyright 2017 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package io.bazel.rulesscala.exe;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+
+
+/**
+ * Metadata that describes the payload of the native launcher binary.
+ *
+ *
This object constructs the binary metadata lazily, to save memory.
+ */
+public final class LaunchInfo {
+
+ private final ImmutableList entries;
+
+ private LaunchInfo(ImmutableList entries) {
+ this.entries = entries;
+ }
+
+ /** Creates a new {@link Builder}. */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /** Writes this object's entries to {@code out}, returns the total written amount in bytes. */
+ long write(OutputStream out) throws IOException {
+ long len = 0;
+ for (Entry e : entries) {
+ len += e.write(out);
+ out.write('\0');
+ ++len;
+ }
+ return len;
+ }
+
+ /** Writes {@code s} to {@code out} encoded as UTF-8, returns the written length in bytes. */
+ private static long writeString(String s, OutputStream out) throws IOException {
+ byte[] b = s.getBytes(StandardCharsets.UTF_8);
+ out.write(b);
+ return b.length;
+ }
+
+ /** Represents one entry in {@link LaunchInfo.entries}. */
+ private static interface Entry {
+ /** Writes this entry to {@code out}, returns the written length in bytes. */
+ long write(OutputStream out) throws IOException;
+ }
+
+ /** A key-value pair entry. */
+ private static final class KeyValuePair implements Entry {
+ private final String key;
+ private final String value;
+
+ public KeyValuePair(String key, String value) {
+ this.key = Preconditions.checkNotNull(key);
+ this.value = value;
+ }
+
+ @Override
+ public long write(OutputStream out) throws IOException {
+ long len = writeString(key, out);
+ len += writeString("=", out);
+ if (value != null && !value.isEmpty()) {
+ len += writeString(value, out);
+ }
+ return len;
+ }
+ }
+
+ /** A pair of a key and a delimiter-joined list of values. */
+ private static final class JoinedValues implements Entry {
+ private final String key;
+ private final String delimiter;
+ private final Iterable values;
+
+ public JoinedValues(String key, String delimiter, Iterable values) {
+ this.key = Preconditions.checkNotNull(key);
+ this.delimiter = Preconditions.checkNotNull(delimiter);
+ this.values = values;
+ }
+
+ @Override
+ public long write(OutputStream out) throws IOException {
+ long len = writeString(key, out);
+ len += writeString("=", out);
+ if (values != null) {
+ boolean first = true;
+ for (String v : values) {
+ if (first) {
+ first = false;
+ } else {
+ len += writeString(delimiter, out);
+ }
+ len += writeString(v, out);
+ }
+ }
+ return len;
+ }
+ }
+
+ /** Builder for {@link LaunchInfo} instances. */
+ public static final class Builder {
+ private ImmutableList.Builder entries = ImmutableList.builder();
+
+ /** Builds a {@link LaunchInfo} from this builder. This builder may be reused. */
+ public LaunchInfo build() {
+ return new LaunchInfo(entries.build());
+ }
+
+ /**
+ * Adds a key-value pair entry.
+ *
+ * Examples:
+ *
+ *
+ * - {@code key} is "foo" and {@code value} is "bar", the written value is "foo=bar\0"
+ *
- {@code key} is "foo" and {@code value} is null or empty, the written value is
+ * "foo=\0"
+ *
+ */
+ public Builder addKeyValuePair(String key, String value) {
+ Preconditions.checkNotNull(key);
+ if (!key.isEmpty()) {
+ entries.add(new KeyValuePair(key, value));
+ }
+ return this;
+ }
+
+ /**
+ * Adds a key and list of lazily-joined values.
+ *
+ * Examples:
+ *
+ *
+ * - {@code key} is "foo", {@code delimiter} is ";", {@code values} is ["bar", "baz",
+ * "qux"], the written value is "foo=bar;baz;qux\0"
+ *
- {@code key} is "foo", {@code delimiter} is irrelevant, {@code value} is null or
+ * empty, the written value is "foo=\0"
+ *
+ */
+ public Builder addJoinedValues(
+ String key, String delimiter, Iterable values) {
+ Preconditions.checkNotNull(key);
+ Preconditions.checkNotNull(delimiter);
+ if (!key.isEmpty()) {
+ entries.add(new JoinedValues(key, delimiter, values));
+ }
+ return this;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/java/io/bazel/rulesscala/exe/LauncherFileWriter.java b/src/java/io/bazel/rulesscala/exe/LauncherFileWriter.java
new file mode 100644
index 000000000..f54a57819
--- /dev/null
+++ b/src/java/io/bazel/rulesscala/exe/LauncherFileWriter.java
@@ -0,0 +1,57 @@
+package io.bazel.rulesscala.exe;
+
+import com.google.common.base.Preconditions;
+import com.google.common.io.ByteStreams;
+import com.google.devtools.build.runfiles.Runfiles;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.file.*;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.Arrays;
+import java.util.List;
+
+
+public class LauncherFileWriter {
+ public static void main(String[] args) throws IOException {
+ Preconditions.checkArgument(args.length == 6);
+
+ final String location = args[0];
+ final String workspaceName = args[1];
+ final String javaBinPath = args[2];
+ final String jarBinPath = javaBinPath.substring(0, javaBinPath.lastIndexOf('/')) + "/jar.exe";
+ final String javaStartClass = args[3];
+ final String cpFile = args[4];
+ final List jvmFlags = Arrays.asList(args[5].split(";"));
+ final String classpath = Files.readAllLines(Paths.get(cpFile)).get(0);
+
+ LaunchInfo launchInfo =
+ LaunchInfo.builder()
+ .addKeyValuePair("binary_type", "Java")
+ .addKeyValuePair("workspace_name", workspaceName)
+ .addKeyValuePair("symlink_runfiles_enabled", "0")
+ .addKeyValuePair("java_bin_path", workspaceName + "/" + javaBinPath)
+ .addKeyValuePair("jar_bin_path", jarBinPath)
+ .addKeyValuePair("java_start_class", javaStartClass)
+ .addKeyValuePair("classpath", classpath)
+ .addJoinedValues("jvm_flags", " ", jvmFlags)
+ .build();
+
+ Path launcher = Paths.get(Runfiles.create().rlocation("bazel_tools/tools/launcher/launcher.exe"));
+ Path outPath = Paths.get(location);
+
+ try (InputStream in = Files.newInputStream(launcher); OutputStream out = Files.newOutputStream(outPath)) {
+ ByteStreams.copy(in, out);
+
+ long dataLength = launchInfo.write(out);
+ ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
+ buffer.order(ByteOrder.LITTLE_ENDIAN);
+ buffer.putLong(dataLength);
+ out.write(buffer.array());
+
+ out.flush();
+ }
+ }
+}
diff --git a/src/java/io/bazel/rulesscala/scala_test/BUILD b/src/java/io/bazel/rulesscala/scala_test/BUILD
index 34c233909..2b4f1d851 100644
--- a/src/java/io/bazel/rulesscala/scala_test/BUILD
+++ b/src/java/io/bazel/rulesscala/scala_test/BUILD
@@ -4,5 +4,6 @@ java_library(
visibility = ["//visibility:public"],
deps = [
"//external:io_bazel_rules_scala/dependency/scalatest/scalatest",
+ "@bazel_tools//tools/java/runfiles",
],
)
diff --git a/src/java/io/bazel/rulesscala/scala_test/Runner.java b/src/java/io/bazel/rulesscala/scala_test/Runner.java
index 14cfe549f..d786acd60 100644
--- a/src/java/io/bazel/rulesscala/scala_test/Runner.java
+++ b/src/java/io/bazel/rulesscala/scala_test/Runner.java
@@ -1,20 +1,68 @@
package io.bazel.rulesscala.scala_test;
+import com.google.devtools.build.runfiles.Runfiles;
+import java.io.IOException;
+import java.io.File;
+import java.util.List;
import java.util.Map;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Paths;
-/** This exists only as a proxy for scala tests's runner to provide access to env variables */
+/** This exists only as a proxy for scala tests's runner to:
+ * - provide access to env variables
+ * - unwrap runner's arguments from a file (passed via file to overcome command-line string limitation on Windows)
+ **/
public class Runner {
/**
* This is the name of the env var set by bazel when a user provides a `--test_filter` test option
*/
private static final String TESTBRIDGE_TEST_ONLY = "TESTBRIDGE_TEST_ONLY";
- public static void main(String[] args) {
+ /**
+ * This is the name of the system property used to pass the main workspace name
+ */
+ private static final String RULES_SCALA_MAIN_WS_NAME = "RULES_SCALA_MAIN_WS_NAME";
+
+ /**
+ * This is the name of the system property used to pass a short path of the file, which includes
+ * org.scalatest.tools.Runner arguments
+ */
+ private static final String RULES_SCALA_ARGS_FILE = "RULES_SCALA_ARGS_FILE";
+
+ public static void main(String[] args) throws IOException {
org.scalatest.tools.Runner.main(extendArgs(args, System.getenv()));
}
- private static String[] extendArgs(String[] args, Map env) {
- return extendFromEnvVar(args, env, TESTBRIDGE_TEST_ONLY, "-s");
+ private static String[] extendArgs(String[] args, Map env) throws IOException {
+ args = extendFromFileArgs(args);
+ args = extendFromEnvVar(args, env, TESTBRIDGE_TEST_ONLY, "-s");
+ return args;
+ }
+
+ private static String[] extendFromFileArgs(String[] args) throws IOException {
+ String runnerArgsFileKey = System.getProperty(RULES_SCALA_ARGS_FILE);
+ if (runnerArgsFileKey == null || runnerArgsFileKey.trim().isEmpty())
+ throw new IllegalArgumentException(RULES_SCALA_ARGS_FILE + " is null or empty.");
+
+ String workspace = System.getProperty(RULES_SCALA_MAIN_WS_NAME);
+ if (workspace == null || workspace.trim().isEmpty())
+ throw new IllegalArgumentException(RULES_SCALA_MAIN_WS_NAME + " is null or empty.");
+
+ String runnerArgsFilePath = Runfiles.create().rlocation(workspace + "/" + runnerArgsFileKey);
+ if (runnerArgsFilePath == null)
+ throw new IllegalArgumentException("rlocation value is null for key: " + runnerArgsFileKey);
+
+ List runnerArgs = Files.readAllLines(Paths.get(runnerArgsFilePath), Charset.forName("UTF-8"));
+ rlocateRunpathValue(workspace, runnerArgs);
+
+ String[] runnerArgsArray = runnerArgs.toArray(new String[runnerArgs.size()]);
+
+ String[] result = new String[args.length + runnerArgsArray.length];
+ System.arraycopy(args, 0, result, 0, args.length);
+ System.arraycopy(runnerArgsArray, 0, result, args.length, runnerArgsArray.length);
+
+ return result;
}
private static String[] extendFromEnvVar(
@@ -23,7 +71,6 @@ private static String[] extendFromEnvVar(
if (value == null) {
return args;
}
- ;
String[] flag = new String[] {flagName, value};
String[] result = new String[args.length + flag.length];
System.arraycopy(args, 0, result, 0, args.length);
@@ -31,4 +78,21 @@ private static String[] extendFromEnvVar(
return result;
}
+
+ /**
+ * Replaces ScalaTest Runner's runpath elements paths (see http://www.scalatest.org/user_guide/using_the_runner)
+ * with values from Bazel's runfiles
+ */
+ private static void rlocateRunpathValue(String rulesWorkspace, List runnerArgs) throws IOException {
+ int runpathFlag = runnerArgs.indexOf("-R");
+ if (runpathFlag >= 0) {
+ String[] runpathElements = runnerArgs.get(runpathFlag + 1).split(File.pathSeparator);
+ Runfiles runfiles = Runfiles.create();
+ for (int i = 0; i < runpathElements.length; i++) {
+ runpathElements[i] = runfiles.rlocation(rulesWorkspace + "/" + runpathElements[i]);
+ }
+ String runpath = String.join(File.separator, runpathElements);
+ runnerArgs.set(runpathFlag + 1, runpath);
+ }
+ }
}
diff --git a/src/java/io/bazel/rulesscala/scalac/ScalacProcessor.java b/src/java/io/bazel/rulesscala/scalac/ScalacProcessor.java
index fb09ccc75..9e43ce639 100644
--- a/src/java/io/bazel/rulesscala/scalac/ScalacProcessor.java
+++ b/src/java/io/bazel/rulesscala/scalac/ScalacProcessor.java
@@ -22,6 +22,8 @@
import scala.tools.nsc.reporters.ConsoleReporter;
class ScalacProcessor implements Processor {
+ private static boolean isWindows = System.getProperty("os.name").toLowerCase().contains("windows");
+
/** This is the reporter field for scalac, which we want to access */
private static Field reporterField;
@@ -251,6 +253,7 @@ private static void removeTmp(Path tmp) throws IOException {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
+ if (isWindows) file.toFile().setWritable(true);
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@@ -287,6 +290,7 @@ private static void copyResources(
} else {
dstr = resource.destination;
}
+
if (dstr.charAt(0) == '/') {
// we don't want to copy to an absolute destination
dstr = dstr.substring(1);
diff --git a/src/scala/io/bazel/rules_scala/jmh_support/BenchmarkGenerator.scala b/src/scala/io/bazel/rules_scala/jmh_support/BenchmarkGenerator.scala
index 579e8ddab..d367ff424 100644
--- a/src/scala/io/bazel/rules_scala/jmh_support/BenchmarkGenerator.scala
+++ b/src/scala/io/bazel/rules_scala/jmh_support/BenchmarkGenerator.scala
@@ -87,7 +87,11 @@ object BenchmarkGenerator {
}
private def collectClassesFromJar(root: Path): List[Path] = {
- val uri = new URI("jar:file", null, root.toFile.getAbsolutePath, null)
+ val path = if (System.getProperty("os.name").toLowerCase().contains("windows"))
+ "/" + root.toFile.getAbsolutePath.replaceAll("\\\\", "/")
+ else
+ root.toFile.getAbsolutePath
+ val uri = new URI("jar:file", null, path, null)
val fs = FileSystems.newFileSystem(uri, Map.empty[String, String].asJava)
fs.getRootDirectories.asScala.toList.flatMap { rootDir =>
listFilesRecursively(rootDir) { (path: Path) =>
diff --git a/src/scala/io/bazel/rules_scala/scrooge_support/FocusedZipImporter.scala b/src/scala/io/bazel/rules_scala/scrooge_support/FocusedZipImporter.scala
index f049bb8b0..e3c6ceb12 100644
--- a/src/scala/io/bazel/rules_scala/scrooge_support/FocusedZipImporter.scala
+++ b/src/scala/io/bazel/rules_scala/scrooge_support/FocusedZipImporter.scala
@@ -32,7 +32,7 @@ case class FocusedZipImporter(focus: Option[File], zips: List[File], zipFiles: L
case child :: tail => loop(new File(leftPart, child), tail)
}
val parts = n.split("/", -1).toList
- val newPath = loop(f, parts).toString
+ val newPath = loop(f, parts).getPath.replaceAllLiterally(File.separator, "/")
if (parts(0) == File.pathSeparatorChar) newPath.substring(1)
else newPath
}
diff --git a/test/BUILD b/test/BUILD
index fe788dbff..319d20edc 100644
--- a/test/BUILD
+++ b/test/BUILD
@@ -101,6 +101,13 @@ scala_test_suite(
],
)
+scala_test_suite(
+ name = "LongTestSuiteNamesSuite",
+ size = "small",
+ srcs = glob(["longnames/**/*.scala"]),
+ use_short_names = True,
+)
+
scala_test_suite(
name = "TestSuitePassesKwArgs",
size = "small", # Not a macro, can pass test-specific attributes.
diff --git a/test/longnames/looooooongnaaaaaaame/anooooootherlooooooooongname/anooooootherlooooooooongname2/anooooootherlooooooooongname3/LongNamesTest.scala b/test/longnames/looooooongnaaaaaaame/anooooootherlooooooooongname/anooooootherlooooooooongname2/anooooootherlooooooooongname3/LongNamesTest.scala
new file mode 100644
index 000000000..caac1c037
--- /dev/null
+++ b/test/longnames/looooooongnaaaaaaame/anooooootherlooooooooongname/anooooootherlooooooooongname2/anooooootherlooooooooongname3/LongNamesTest.scala
@@ -0,0 +1,9 @@
+package test.longnames.looooooongnaaaaaaame.anooooootherlooooooooongname.anooooootherlooooooooongname2.anooooootherlooooooooongname3
+
+import org.scalatest._
+
+class SomeLoooooooongTestNaaaaaaaaaaaaaaaaaaaaaaaame extends FlatSpec with Matchers {
+ "This test" should "pass" in {
+ assert(true)
+ }
+}
diff --git a/test_reproducibility.ps1 b/test_reproducibility.ps1
new file mode 100644
index 000000000..e70786fc9
--- /dev/null
+++ b/test_reproducibility.ps1
@@ -0,0 +1,3 @@
+Set-StrictMode -Version latest
+$ErrorActionPreference = 'Stop'
+
diff --git a/test_rules_scala.ps1 b/test_rules_scala.ps1
new file mode 100644
index 000000000..29bfc43bc
--- /dev/null
+++ b/test_rules_scala.ps1
@@ -0,0 +1,36 @@
+#!/usr/bin/env pwsh
+
+Set-StrictMode -Version latest
+$ErrorActionPreference = 'Stop'
+
+$env:JAVA_HOME='c:\\java8'
+
+function bazel() {
+ Write-Output ">> bazel $args"
+ $global:lastexitcode = 0
+ $backupErrorActionPreference = $script:ErrorActionPreference
+ $script:ErrorActionPreference = "Continue"
+ & bazel.exe @args 2>&1 | %{ "$_" }
+ $script:ErrorActionPreference = $backupErrorActionPreference
+ if ($global:lastexitcode -ne 0 -And $args[0] -ne "shutdown") {
+ Write-Output "<< bazel $args (failed, exit code: $global:lastexitcode)"
+ throw ("Bazel returned non-zero exit code: $global:lastexitcode")
+ }
+ Write-Output "<< bazel $args (ok)"
+}
+
+bazel build //test/...
+bazel shutdown
+
+bazel test `
+ //test:HelloLibTest `
+ //test:HelloLibTestSuite_test_suite_HelloLibTest.scala `
+ //test:HelloLibTestSuite_test_suite_HelloLibTest2.scala `
+ //test:LongTestSuiteNamesSuite `
+ //test:TestFilterTests `
+ //test:no_sig `
+ //test/aspect:aspect_test `
+ //test/aspect:scala_test `
+ //test/proto:test_blacklisted_proto `
+ //test/src/main/scala/scalarules/test/resource_jars:resource_jars `
+ //test/src/main/scala/scalarules/test/twitter_scrooge/...
diff --git a/third_party/unused_dependency_checker/src/main/io/bazel/rulesscala/unuseddependencychecker/UnusedDependencyChecker.scala b/third_party/unused_dependency_checker/src/main/io/bazel/rulesscala/unuseddependencychecker/UnusedDependencyChecker.scala
index 0334ec0b6..469db9093 100644
--- a/third_party/unused_dependency_checker/src/main/io/bazel/rulesscala/unuseddependencychecker/UnusedDependencyChecker.scala
+++ b/third_party/unused_dependency_checker/src/main/io/bazel/rulesscala/unuseddependencychecker/UnusedDependencyChecker.scala
@@ -16,6 +16,8 @@ class UnusedDependencyChecker(val global: Global) extends Plugin { self =>
var analyzerMode: AnalyzerMode = Error
var currentTarget: String = "NA"
+ val isWindows: Boolean = System.getProperty("os.name").toLowerCase.contains("windows")
+
override def init(options: List[String], error: (String) => Unit): Boolean = {
var directJars: Seq[String] = Seq.empty
var directTargets: Seq[String] = Seq.empty
@@ -66,7 +68,7 @@ class UnusedDependencyChecker(val global: Global) extends Plugin { self =>
private def unusedDependenciesFound: Set[String] = {
val usedJars: Set[AbstractFile] = findUsedJars
val directJarPaths = direct.keys.toSet
- val usedJarPaths = usedJars.map(_.path)
+ val usedJarPaths = if (!isWindows) usedJars.map(_.path) else usedJars.map(_.path.replaceAll("\\\\", "/"))
val usedTargets = usedJarPaths
.map(direct.get)