diff --git a/scala/private/rule_impls.bzl b/scala/private/rule_impls.bzl index 9436e2d0a..bc11e2e7d 100644 --- a/scala/private/rule_impls.bzl +++ b/scala/private/rule_impls.bzl @@ -590,74 +590,97 @@ 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): - template = ctx.attr._java_stub_template.files.to_list()[0] - - jvm_flags = " ".join( - [ctx.expand_location(f, ctx.attr.data) for f in jvm_flags], - ) - - javabin = "export REAL_EXTERNAL_JAVA_BIN=${JAVABIN};JAVABIN=%s/%s" % ( - _runfiles_root(ctx), - wrapper.short_path, - ) - - if use_jacoco and _coverage_replacements_provider.is_enabled(ctx): - classpath = ":".join( - ["${RUNPATH}%s" % (j.short_path) for j in rjars.to_list() + ctx.files._jacocorunner + ctx.files._lcov_merger], - ) - jacoco_metadata_file = ctx.actions.declare_file( - "%s.jacoco_metadata.txt" % ctx.attr.name, - sibling = ctx.outputs.executable, +def _write_executable(ctx, executable, rjars, main_class, jvm_flags, wrapper, use_jacoco): + if (_is_windows(ctx)): + classpath = ";".join( + [("external/%s" % (j.short_path[3:]) if j.short_path.startswith("../") else j.short_path) for j in rjars.to_list()], ) - ctx.actions.write(jacoco_metadata_file, "\n".join([ - jar.short_path.replace("../", "external/") - for jar in rjars - ])) - ctx.actions.expand_template( - template = template, - output = ctx.outputs.executable, - substitutions = { - "%classpath%": classpath, - "%javabin%": javabin, - "%jarbin%": _jar_path_based_on_java_bin(ctx), - "%jvm_flags%": jvm_flags, - "%needs_runfiles%": "", - "%runfiles_manifest_only%": "", - "%workspace_prefix%": ctx.workspace_name + "/", - "%java_start_class%": "com.google.testing.coverage.JacocoCoverageRunner", - "%set_jacoco_metadata%": "export JACOCO_METADATA_JAR=\"$JAVA_RUNFILES/{}/{}\"".format(ctx.workspace_name, jacoco_metadata_file.short_path), - "%set_jacoco_main_class%": """export JACOCO_MAIN_CLASS={}""".format(main_class), - "%set_jacoco_java_runfiles_root%": """export JACOCO_JAVA_RUNFILES_ROOT=$JAVA_RUNFILES/{}/""".format(ctx.workspace_name), - }, - is_executable = True, + jvm_flags_str = ";".join(jvm_flags) + java_for_exe = "%s/%s" % (ctx.workspace_name, str(ctx.attr._java_runtime[java_common.JavaRuntimeInfo].java_executable_exec_path)) + + ctx.actions.run( + outputs = [executable], + inputs = [], + executable = ctx.attr._exe.files_to_run.executable, + arguments = [executable.path, ctx.workspace_name, java_for_exe, main_class, classpath, jvm_flags_str], + mnemonic = "ExeLauncher", + progress_message = "Creating exe launcher", ) - return [jacoco_metadata_file] + return [] else: - # RUNPATH is defined here: - # https://github.com/bazelbuild/bazel/blob/0.4.5/src/main/java/com/google/devtools/build/lib/bazel/rules/java/java_stub_template.txt#L227 - classpath = ":".join( - ["${RUNPATH}%s" % (j.short_path) for j in rjars.to_list()], + template = ctx.attr._java_stub_template.files.to_list()[0] + + jvm_flags = " ".join( + [ctx.expand_location(f, ctx.attr.data) for f in jvm_flags], ) - ctx.actions.expand_template( - template = template, - output = ctx.outputs.executable, - substitutions = { - "%classpath%": classpath, - "%java_start_class%": main_class, - "%javabin%": javabin, - "%jarbin%": _jar_path_based_on_java_bin(ctx), - "%jvm_flags%": jvm_flags, - "%needs_runfiles%": "", - "%runfiles_manifest_only%": "", - "%set_jacoco_metadata%": "", - "%set_jacoco_main_class%": "", - "%set_jacoco_java_runfiles_root%": "", - "%workspace_prefix%": ctx.workspace_name + "/", - }, - is_executable = True, + + javabin = "export REAL_EXTERNAL_JAVA_BIN=${JAVABIN};JAVABIN=%s/%s" % ( + _runfiles_root(ctx), + wrapper.short_path, ) - return [] + + if use_jacoco and _coverage_replacements_provider.is_enabled(ctx): + classpath = ":".join( + ["${RUNPATH}%s" % (j.short_path) for j in rjars.to_list() + ctx.files._jacocorunner + ctx.files._lcov_merger], + ) + jacoco_metadata_file = ctx.actions.declare_file( + "%s.jacoco_metadata.txt" % ctx.attr.name, + sibling = executable, + ) + ctx.actions.write(jacoco_metadata_file, "\n".join([ + jar.short_path.replace("../", "external/") + for jar in rjars + ])) + ctx.actions.expand_template( + template = template, + output = executable, + substitutions = { + "%classpath%": classpath, + "%javabin%": javabin, + "%jarbin%": _jar_path_based_on_java_bin(ctx), + "%jvm_flags%": jvm_flags, + "%needs_runfiles%": "", + "%runfiles_manifest_only%": "", + "%workspace_prefix%": ctx.workspace_name + "/", + "%java_start_class%": "com.google.testing.coverage.JacocoCoverageRunner", + "%set_jacoco_metadata%": "export JACOCO_METADATA_JAR=\"$JAVA_RUNFILES/{}/{}\"".format(ctx.workspace_name, jacoco_metadata_file.short_path), + "%set_jacoco_main_class%": """export JACOCO_MAIN_CLASS={}""".format(main_class), + "%set_jacoco_java_runfiles_root%": """export JACOCO_JAVA_RUNFILES_ROOT=$JAVA_RUNFILES/{}/""".format(ctx.workspace_name), + }, + is_executable = True, + ) + return [jacoco_metadata_file] + else: + # RUNPATH is defined here: + # https://github.com/bazelbuild/bazel/blob/0.4.5/src/main/java/com/google/devtools/build/lib/bazel/rules/java/java_stub_template.txt#L227 + classpath = ":".join( + ["${RUNPATH}%s" % (j.short_path) for j in rjars.to_list()], + ) + ctx.actions.expand_template( + template = template, + output = executable, + substitutions = { + "%classpath%": classpath, + "%java_start_class%": main_class, + "%javabin%": javabin, + "%jarbin%": _jar_path_based_on_java_bin(ctx), + "%jvm_flags%": jvm_flags, + "%needs_runfiles%": "", + "%runfiles_manifest_only%": "", + "%set_jacoco_metadata%": "", + "%set_jacoco_main_class%": "", + "%set_jacoco_java_runfiles_root%": "", + "%workspace_prefix%": ctx.workspace_name + "/", + }, + is_executable = True, + ) + 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 = [] @@ -848,6 +871,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, @@ -876,7 +900,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, transitive = [rjars], ), collect_data = True, @@ -898,8 +922,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, @@ -950,8 +975,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, @@ -966,6 +995,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, @@ -989,6 +1019,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, @@ -1010,6 +1043,7 @@ trap finish EXIT out = _scala_binary_common( ctx, + executable, cjars, transitive_rjars, jars.transitive_compile_jars, @@ -1024,6 +1058,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, @@ -1086,10 +1121,13 @@ def scala_test_impl(ctx): "-C io.bazel.rules.scala.JUnitXmlReporter ", ]) + executable = _declare_executable(ctx) + # main_class almost has to be "org.scalatest.tools.Runner" due to args.... wrapper = _write_java_wrapper(ctx, args, "") out = _scala_binary_common( ctx, + executable, cjars, transitive_rjars, transitive_compile_jars, @@ -1117,6 +1155,7 @@ def scala_test_impl(ctx): coverage_runfiles.extend(_write_executable( ctx = ctx, + executable = executable, jvm_flags = ctx.attr.jvm_flags, main_class = ctx.attr.main_class, rjars = rjars, @@ -1125,6 +1164,7 @@ def scala_test_impl(ctx): )) return struct( + executable = executable, files = out.files, instrumented_files = out.instrumented_files, providers = out.providers, @@ -1202,9 +1242,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, @@ -1237,6 +1280,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, @@ -1293,3 +1337,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..98761c594 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)): diff --git a/src/java/io/bazel/rulesscala/exe/BUILD b/src/java/io/bazel/rulesscala/exe/BUILD new file mode 100644 index 000000000..3cea7c9a9 --- /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:runfiles", + "//external:io_bazel_rules_scala/dependency/scala/guava", + ], + visibility = ["//visibility:public"], +) + +java_binary( + name = "exe", + main_class = "io.bazel.rulesscala.exe.LauncherFileWriter", + visibility = ["//visibility:public"], + runtime_deps = [ + ":exe-lib", + ], + data = [ + "@bazel_tools//tools/launcher:launcher", + ], +) 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..8931a3a41 --- /dev/null +++ b/src/java/io/bazel/rulesscala/exe/LaunchInfo.java @@ -0,0 +1,170 @@ +// 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.annotations.VisibleForTesting; +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. */ + @VisibleForTesting + 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: + * + *

+ */ + 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: + * + *

+ */ + 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..1e7c09b69 --- /dev/null +++ b/src/java/io/bazel/rulesscala/exe/LauncherFileWriter.java @@ -0,0 +1,56 @@ +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 classpath = args[4]; + final List jvmFlags = Arrays.asList(args[5].split(";")); + + LaunchInfo launchInfo = + LaunchInfo.builder() + .addKeyValuePair("binary_type", "Java") + .addKeyValuePair("workspace_name", workspaceName) + .addKeyValuePair("symlink_runfiles_enabled", "0") + .addKeyValuePair("java_bin_path", 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/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/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)