在bazel java测试中将自定义文件夹添加到类路径

在bazel java测试中将自定义文件夹添加到类路径,java,maven,bazel,Java,Maven,Bazel,我正在尝试将一个大型代码库从maven迁移到bazel,我发现一些测试写入target/classes和target/testclasses,而生产代码将其作为类路径上的资源读取。这是因为maven surefire/failsafe默认从模块目录运行,并将target/classes和target/test classes添加到类路径中。 对于我来说,要迁移这个庞大的代码库,唯一合理的解决方案是创建target、target/classes和target/testclasses文件夹,并将最后

我正在尝试将一个大型代码库从maven迁移到bazel,我发现一些测试写入
target/classes
target/testclasses
,而生产代码将其作为类路径上的资源读取。这是因为maven surefire/failsafe默认从模块目录运行,并将
target/classes
target/test classes
添加到类路径中。 对于我来说,要迁移这个庞大的代码库,唯一合理的解决方案是创建target、target/classes和target/testclasses文件夹,并将最后两个添加到测试的类路径中。
关于如何实现这一点有什么想法吗


如果您能告诉测试在哪里写入这些文件(如果
目标/类
目标/测试类
是硬编码的),然后将测试运行转换为
genrule
,那就谢谢了,然后,您可以将genrule的输出指定为生产二进制文件的
*\u binary
规则的
data

我解决了第一部分,创建了目录。我仍然不知道如何将后两个添加到类路径中

从开始,我将其修改为

_OUTPUT = """import org.junit.runners.Suite;
import org.junit.runner.RunWith;
import org.junit.BeforeClass;
import java.io.File;
@RunWith(Suite.class)
@Suite.SuiteClasses({%s})
public class %s {
    @BeforeClass
    public static void setUp() throws Exception {
      new File("./target").mkdir();
    }
}
"""
_PREFIXES = ("org", "com", "edu")
# ...
我添加了
@BeforeClass设置方法

我将其作为junit.bzl存储到项目中的第三方目录中

然后在
生成文件中

load("//third_party:junit.bzl", "junit_tests")

junit_tests(
    name = "my_bundled_test",
    srcs = glob(["src/test/java/**/*.java"]),
    data = glob(["src/test/resources/**"]),
resources = glob(["src/test/resources/**"]),
tags = [
    # ...
],
    runtime_deps = [
        # ...
    ],
],
    deps = [
        # ...
    ],
)
现在,测试本身用一个setUp方法包装,它将为我创建一个目录。之后我不会删除它们,这可能是一个好主意


我需要目录中的测试资源(与bazel默认提供的jar文件相反)的原因是,我的测试将URI传递给
新文件输入流(新文件(URI))
。如果文件位于JAR中,URI将是
file:/path/to/my.JAR/my.file
和测试的其余部分无法使用此类URI。

另一种方法。不要生成测试套件,而是创建一个自定义javaagent和一个自定义类加载器。使用
jvm\u标志来设置和配置它

javaagent有一个premain方法。这听起来像是一个自然的地方,可以在常规的main方法之前完成一些事情,即使它们与类插装、调试、覆盖率收集或javaagent的任何其他常见用途无关

自定义javaagent读取系统属性
extra.dirs
,并创建其中指定的目录。然后,它读取属性
extra.link.path
,并按照其中的指定创建符号链接,这样我就可以将资源放置在测试期望的位置,而不必复制它们

类加载器是必需的,这样我们就可以在运行时修改类路径而无需黑客攻击。最大的优点是该解决方案可以在Java10上运行

自定义类加载器读取系统属性
extra.class.path
,并(实际上)在
java.class.path
中的内容之前加上它

这样做意味着可以使用标准的bazel规则

建造 工具/构建 工具/ResourceJavaAgent.java
导入java.io.File;
导入java.io.IOException;
导入java.lang.instrument.Instrumentation;
导入java.net.MalformedURLException;
导入java.net.URL;
导入java.net.URLClassLoader;
导入java.nio.file.Files;
导入java.nio.file.Path;
导入java.nio.file.path;
导入java.util.array;
导入java.util.LinkedList;
导入java.util.List;
// https://stackoverflow.com/questions/60764/how-should-i-load-jars-dynamically-at-runtime
公共类ResourceJavaAgent扩展URLClassLoader{
私有最终类加载器父级;
public ResourceJavaAgent(类装入器父级)引发错误的FormedUrlexception{
super(buildClassPath(),null);
this.parent=parent;//我需要父对象作为SecretKeyFactory.getInstance(“PBKDF2WithHmacSHA1”)的备份;
System.out.println(“初始化url类加载器”);
}
私有静态URL[]buildClassPath()引发畸形的异常{
最后一个字符串JAVA\u CLASS\u PATH=“JAVA.CLASS.PATH”;
最后一个字符串EXTRA\u CLASS\u PATH=“EXTRA.CLASS.PATH”;
列表路径=新建LinkedList();
paths.addAll(Arrays.asList(System.getProperty(EXTRA_CLASS_PATH,”).split(File.pathSeparator));
paths.addAll(Arrays.asList(System.getProperty(JAVA_CLASS_PATH,”).split(File.pathSeparator));
URL[]URL=新URL[path.size()];
对于(int i=0;iruntime_classgen_dirs = ":".join([
            "target/classes",
            "target/test-classes",
])
java_test(
    ...,
    jvm_flags = [
        # agent
        "-javaagent:$(location //tools:test-agent_deploy.jar)",
        "-Dextra.dirs=" + runtime_classgen_dirs,
        # classloader
        "-Djava.system.class.loader=ResourceJavaAgent",
        "-Dextra.class.path=" + runtime_classgen_dirs,
    ],
    ,,,,
    deps = [
        # not runtime_deps, cause https://github.com/bazelbuild/bazel/issues/1566
        "//tools:test-agent_deploy.jartest-agent_deploy.jar"
    ],
    ...,
)
java_binary(
    name = "test-agent",
    testonly = True,
    srcs = ["ResourceJavaAgent.java"],
    deploy_manifest_lines = ["Premain-Class: ResourceJavaAgent"],
    main_class = "ResourceJavaAgent",
    visibility = ["//visibility:public"],
)
import java.io.File;
import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

// https://stackoverflow.com/questions/60764/how-should-i-load-jars-dynamically-at-runtime
public class ResourceJavaAgent extends URLClassLoader {
    private final ClassLoader parent;

    public ResourceJavaAgent(ClassLoader parent) throws MalformedURLException {
        super(buildClassPath(), null);
        this.parent = parent; // I need the parent as backup for SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
        System.out.println("initializing url classloader");
    }

    private static URL[] buildClassPath() throws MalformedURLException {
        final String JAVA_CLASS_PATH = "java.class.path";
        final String EXTRA_CLASS_PATH = "extra.class.path";
        List<String> paths = new LinkedList<>();
        paths.addAll(Arrays.asList(System.getProperty(EXTRA_CLASS_PATH, "").split(File.pathSeparator)));
        paths.addAll(Arrays.asList(System.getProperty(JAVA_CLASS_PATH, "").split(File.pathSeparator)));
        URL[] urls = new URL[paths.size()];
        for (int i = 0; i < paths.size(); i++) {
            urls[i] = Paths.get(paths.get(i)).toUri().toURL(); // important only for resource url, really: this url must be absolute, to pass getClass().getResource("/users.properties").toURI()) with uri that isOpaque == false.
//            System.out.println(urls[i]);
        }
        // this is for spawnVM functionality in tests
        System.setProperty(JAVA_CLASS_PATH, System.getProperty(EXTRA_CLASS_PATH, "") + File.pathSeparator + System.getProperty(JAVA_CLASS_PATH));
        return urls;
    }

    @Override
    public Class<?> loadClass(String s) throws ClassNotFoundException {
        try {
            return super.loadClass(s);
        } catch (ClassNotFoundException e) {
            return parent.loadClass(s);  // we search parent second, not first, as the default URLClassLoader would
        }
    }

    private static void createRequestedDirs() {
        for (String path : System.getProperty("extra.dirs", "").split(File.pathSeparator)) {
            new File(path).mkdirs();
        }
    }

    private static void createRequestedLinks() {
        String linkPaths = System.getProperty("extra.link.path", null);
        if (linkPaths == null) {
            return;
        }
        for (String linkPath : linkPaths.split(",")) {
            String[] fromTo = linkPath.split(":");
            Path from = Paths.get(fromTo[0]);
            Path to = Paths.get(fromTo[1]);
            try {
                Files.createSymbolicLink(from.toAbsolutePath(), to.toAbsolutePath());
            } catch (IOException e) {
                throw new IllegalArgumentException("Unable to create link " + linkPath, e);
            }
        }
    }

    public static void premain(String args, Instrumentation instrumentation) throws Exception {
        createRequestedDirs();
        createRequestedLinks();
    }
}