diff --git a/capsule/src/main/java/Capsule.java b/capsule/src/main/java/Capsule.java index d4e70ddf..bb56f573 100644 --- a/capsule/src/main/java/Capsule.java +++ b/capsule/src/main/java/Capsule.java @@ -123,6 +123,7 @@ public class Capsule implements Runnable { private static final String ATTR_SECURITY_POLICY_A = "Security-Policy-A"; private static final String ATTR_JAVA_AGENTS = "Java-Agents"; private static final String ATTR_REPOSITORIES = "Repositories"; + private static final String ATTR_ALLOW_SNAPSHOTS = "Allow-Snapshots"; private static final String ATTR_DEPENDENCIES = "Dependencies"; private static final String ATTR_NATIVE_DEPENDENCIES_LINUX = "Native-Dependencies-Linux"; private static final String ATTR_NATIVE_DEPENDENCIES_WIN = "Native-Dependencies-Win"; @@ -958,28 +959,25 @@ private static void addSystemProperty(String p, Map ps) { // /////////// Native Dependencies /////////////////////////////////// private List buildNativeLibraryPath() { - final List libraryPath = new ArrayList(); + final List libraryPath = new ArrayList(toPath(Arrays.asList(System.getProperty(PROP_JAVA_LIBRARY_PATH).split(PATH_SEPARATOR)))); + resolveNativeDependencies(); - if (appCache != null) - libraryPath.addAll(nullToEmpty(toAbsolutePath(appCache, getListAttribute(ATTR_LIBRARY_PATH_P)))); - libraryPath.addAll(toPath(Arrays.asList(System.getProperty(PROP_JAVA_LIBRARY_PATH).split(PATH_SEPARATOR)))); if (appCache != null) { + libraryPath.addAll(0, nullToEmpty(toAbsolutePath(appCache, getListAttribute(ATTR_LIBRARY_PATH_P)))); libraryPath.addAll(nullToEmpty(toAbsolutePath(appCache, getListAttribute(ATTR_LIBRARY_PATH_A)))); libraryPath.add(appCache); - } + } else if (hasAttribute(ATTR_LIBRARY_PATH_P) || hasAttribute(ATTR_LIBRARY_PATH_A)) + throw new IllegalStateException("Cannot use the " + ATTR_LIBRARY_PATH_P + " or the " + ATTR_LIBRARY_PATH_A + + " attributes when the " + ATTR_EXTRACT + " attribute is set to false"); return libraryPath; } private void resolveNativeDependencies() { - if (!hasAttribute(ATTR_LIBRARY_PATH_P) && !hasAttribute(ATTR_LIBRARY_PATH_A)) - return; - if (appCache == null) - throw new IllegalStateException("Cannot use the " + ATTR_LIBRARY_PATH_P + " or the " + ATTR_LIBRARY_PATH_A - + " attributes when the " + ATTR_EXTRACT + " attribute is set to false"); - final List depsAndRename = getNativeDependenciesAndRename(); if (depsAndRename == null || depsAndRename.isEmpty()) return; + if (appCache == null) + throw new IllegalStateException("Cannot have native dependencies when the " + ATTR_EXTRACT + " attribute is set to false"); final List deps = new ArrayList(depsAndRename.size()); final List renames = new ArrayList(depsAndRename.size()); for (String depAndRename : depsAndRename) { @@ -990,7 +988,7 @@ private void resolveNativeDependencies() { verbose("Resolving native libs " + deps); final List resolved = resolveDependencies(deps, getNativeLibExtension()); if (resolved.size() != deps.size()) - throw new RuntimeException("One of the native artifacts " + deps + " reolved to more than a single file"); + throw new RuntimeException("One of the native artifacts " + deps + " reolved to more than a single file or to none"); assert appCache != null; if (!cacheUpToDate) { @@ -1003,7 +1001,7 @@ private void resolveNativeDependencies() { Files.copy(lib, appCache.resolve(rename != null ? rename : lib.getFileName().toString())); } } catch (IOException e) { - throw new RuntimeException("Exception while copying native libs"); + throw new RuntimeException("Exception while copying native libs", e); } } } diff --git a/capsule/src/test/java/CapsuleTest.java b/capsule/src/test/java/CapsuleTest.java index 374c8e63..6e2aeba6 100644 --- a/capsule/src/test/java/CapsuleTest.java +++ b/capsule/src/test/java/CapsuleTest.java @@ -52,7 +52,7 @@ public class CapsuleTest { public void tearDown() throws Exception { fs.close(); } - + @Test public void testParseJavaVersion() { int[] ver; @@ -101,22 +101,22 @@ public void isJavaDir() { @Test public void testDelete() throws Exception { - Files.createDirectories(fs.getPath("a", "b", "c")); - Files.createDirectories(fs.getPath("a", "b1")); - Files.createDirectories(fs.getPath("a", "b", "c1")); - Files.createFile(fs.getPath("a", "x")); - Files.createFile(fs.getPath("a", "b", "x")); - Files.createFile(fs.getPath("a", "b1", "x")); - Files.createFile(fs.getPath("a", "b", "c", "x")); - Files.createFile(fs.getPath("a", "b", "c1", "x")); + Files.createDirectories(path("a", "b", "c")); + Files.createDirectories(path("a", "b1")); + Files.createDirectories(path("a", "b", "c1")); + Files.createFile(path("a", "x")); + Files.createFile(path("a", "b", "x")); + Files.createFile(path("a", "b1", "x")); + Files.createFile(path("a", "b", "c", "x")); + Files.createFile(path("a", "b", "c1", "x")); - assertTrue(Files.exists(fs.getPath("a"))); - assertTrue(Files.isDirectory(fs.getPath("a"))); + assertTrue(Files.exists(path("a"))); + assertTrue(Files.isDirectory(path("a"))); - //Files.delete(fs.getPath("a")); - Capsule.delete(fs.getPath("a")); + //Files.delete(path("a")); + Capsule.delete(path("a")); - assertTrue(!Files.exists(fs.getPath("a"))); + assertTrue(!Files.exists(path("a"))); } @Test @@ -140,10 +140,10 @@ public void testSimpleExtract() throws Exception { Path appCache = cache.resolve("apps").resolve("com.acme.Foo"); assertEquals("com.acme.Foo", getProperty(pb, "capsule.app")); - assertEquals(appCache.toString(), getProperty(pb, "capsule.dir")); - assertEquals(getPath("capsule.jar").toString(), getProperty(pb, "capsule.jar")); - assertEquals(appCache.toString(), getEnv(pb, "CAPSULE_DIR")); - assertEquals(getPath("capsule.jar").toString(), getEnv(pb, "CAPSULE_JAR")); + assertEquals(appCache, path(getProperty(pb, "capsule.dir"))); + assertEquals(path("capsule.jar"), path(getProperty(pb, "capsule.jar"))); + assertEquals(appCache, path(getEnv(pb, "CAPSULE_DIR"))); + assertEquals(path("capsule.jar"), path(getEnv(pb, "CAPSULE_JAR"))); assertEquals(list("com.acme.Foo", "hi", "there"), getMainAndArgs(pb)); @@ -160,7 +160,7 @@ public void testSimpleExtract() throws Exception { assertTrue(!Files.isDirectory(appCache.resolve("META-INF"))); assertTrue(!Files.isRegularFile(appCache.resolve("META-INF").resolve("x.txt"))); - ASSERT.that(getClassPath(pb)).has().item(getPath("capsule.jar")); + ASSERT.that(getClassPath(pb)).has().item(path("capsule.jar")); ASSERT.that(getClassPath(pb)).has().item(appCache); ASSERT.that(getClassPath(pb)).has().item(appCache.resolve("foo.jar")); ASSERT.that(getClassPath(pb)).has().noneOf(appCache.resolve("lib").resolve("a.jar")); @@ -228,13 +228,116 @@ public void testClassPath() throws Exception { assertTrue(Files.isDirectory(appCache.resolve("lib"))); assertTrue(Files.isRegularFile(appCache.resolve("lib").resolve("a.jar"))); - ASSERT.that(getClassPath(pb)).has().item(getPath("capsule.jar")); + ASSERT.that(getClassPath(pb)).has().item(path("capsule.jar")); ASSERT.that(getClassPath(pb)).has().item(appCache); ASSERT.that(getClassPath(pb)).has().item(appCache.resolve("foo.jar")); ASSERT.that(getClassPath(pb)).has().item(appCache.resolve("lib").resolve("a.jar")); ASSERT.that(getClassPath(pb)).has().item(appCache.resolve("lib").resolve("b.jar")); } + @Test + public void testNatives1() throws Exception { + Jar jar = newCapsuleJar() + .setAttribute("Application-Class", "com.acme.Foo") + .setListAttribute("Library-Path-A", list("lib/a.so")) + .setListAttribute("Library-Path-P", list("lib/b.so")) + .addEntry("foo.jar", Jar.toInputStream("", UTF_8)) + .addEntry("lib/a.so", Jar.toInputStream("", UTF_8)) + .addEntry("lib/b.so", Jar.toInputStream("", UTF_8)) + .addEntry("lib/c.jar", Jar.toInputStream("", UTF_8)) + .addEntry("lib/d.jar", Jar.toInputStream("", UTF_8)); + + String[] args = strings("hi", "there"); + List cmdLine = list(); + + Capsule capsule = newCapsule(jar, null); + ProcessBuilder pb = capsule.prepareForLaunch(cmdLine, args); + + Path appCache = cache.resolve("apps").resolve("com.acme.Foo"); + + int len = paths(getProperty(pb, "java.library.path")).size(); + ASSERT.that(paths(getProperty(pb, "java.library.path")).get(0)).isEqualTo(appCache.resolve("lib").resolve("b.so")); + ASSERT.that(paths(getProperty(pb, "java.library.path")).get(len - 2)).isEqualTo(appCache.resolve("lib").resolve("a.so")); + ASSERT.that(paths(getProperty(pb, "java.library.path")).get(len - 1)).isEqualTo(appCache); + } + + @Test + public void testNatives2() throws Exception { + final String orig = System.getProperty("java.library.path"); + try { + Jar jar = newCapsuleJar() + .setAttribute("Application-Class", "com.acme.Foo") + .setListAttribute("Library-Path-A", list("lib/a.so")) + .setListAttribute("Library-Path-P", list("lib/b.so")) + .addEntry("foo.jar", Jar.toInputStream("", UTF_8)) + .addEntry("lib/a.so", Jar.toInputStream("", UTF_8)) + .addEntry("lib/b.so", Jar.toInputStream("", UTF_8)) + .addEntry("lib/c.jar", Jar.toInputStream("", UTF_8)) + .addEntry("lib/d.jar", Jar.toInputStream("", UTF_8)); + + String[] args = strings("hi", "there"); + System.setProperty("java.library.path", "/foo/bar"); + List cmdLine = list(); + + Capsule capsule = newCapsule(jar, null); + ProcessBuilder pb = capsule.prepareForLaunch(cmdLine, args); + + Path appCache = cache.resolve("apps").resolve("com.acme.Foo"); + + ASSERT.that(paths(getProperty(pb, "java.library.path"))).isEqualTo(list( + appCache.resolve("lib").resolve("b.so"), + path("/foo", "bar"), + appCache.resolve("lib").resolve("a.so"), + appCache)); + } finally { + System.setProperty("java.library.path", orig); + } + } + + @Test + public void testNativesWithDeps() throws Exception { + Jar jar = newCapsuleJar() + .setAttribute("Application-Class", "com.acme.Foo") + .setListAttribute("Native-Dependencies-Linux", list("com.acme:baz-linux:3.4,libbaz.so")) + .setListAttribute("Native-Dependencies-Windows", list("com.acme:baz-win:3.4,libbaz.dll")) + .setListAttribute("Native-Dependencies-Mac", list("com.acme:baz-macos:3.4,libbaz.dylib")) + .addEntry("foo.jar", Jar.toInputStream("", UTF_8)) + .addEntry("lib/a.so", Jar.toInputStream("", UTF_8)) + .addEntry("lib/b.so", Jar.toInputStream("", UTF_8)) + .addEntry("lib/c.jar", Jar.toInputStream("", UTF_8)) + .addEntry("lib/d.jar", Jar.toInputStream("", UTF_8)); + + DependencyManager dm = mock(DependencyManager.class); + Files.createDirectories(cache.resolve("deps").resolve("com.acme").resolve("baz")); + Path bazLinuxPath = cache.resolve("deps").resolve("com.acme").resolve("baz").resolve("baz-linux-3.4.so"); + Files.createFile(bazLinuxPath); + when(dm.resolveDependencies(list("com.acme:baz-linux:3.4"), "so")).thenReturn(list(bazLinuxPath)); + Path bazWindowsPath = cache.resolve("deps").resolve("com.acme").resolve("baz").resolve("baz-win-3.4.dll"); + Files.createFile(bazWindowsPath); + when(dm.resolveDependencies(list("com.acme:baz-win:3.4"), "dll")).thenReturn(list(bazWindowsPath)); + Path bazMacPath = cache.resolve("deps").resolve("com.acme").resolve("baz").resolve("baz-macos-3.4.dylib"); + Files.createFile(bazMacPath); + when(dm.resolveDependencies(list("com.acme:baz-macos:3.4"), "dylib")).thenReturn(list(bazMacPath)); + + String[] args = strings("hi", "there"); + System.setProperty("java.library.path", "/foo/bar"); + List cmdLine = list(); + + Capsule capsule = newCapsule(jar, dm); + ProcessBuilder pb = capsule.prepareForLaunch(cmdLine, args); + + Path appCache = cache.resolve("apps").resolve("com.acme.Foo"); + + ASSERT.that(paths(getProperty(pb, "java.library.path"))).has().item(appCache); + + if (Capsule.isUnix()) + assertTrue(Files.isRegularFile(appCache.resolve("libbaz.so"))); + else if (Capsule.isWindows()) + assertTrue(Files.isRegularFile(appCache.resolve("libbaz.dll"))); + else if (Capsule.isMac()) + assertTrue(Files.isRegularFile(appCache.resolve("libbaz.dylib"))); + } + @Test public void testBootClassPath1() throws Exception { Jar jar = newCapsuleJar() @@ -305,7 +408,7 @@ public void testBootClassPathWithDeps() throws Exception { DependencyManager dm = mock(DependencyManager.class); Path barPath = cache.resolve("deps").resolve("com.acme").resolve("bar").resolve("bar-1.2.jar"); when(dm.resolveDependency("com.acme:bar:1.2", "jar")).thenReturn(list(barPath)); - Path bazPath = cache.resolve("deps").resolve("com.acme").resolve("baz").resolve("bar-3.4.jar"); + Path bazPath = cache.resolve("deps").resolve("com.acme").resolve("baz").resolve("baz-3.4.jar"); when(dm.resolveDependency("com.acme:baz:3.4", "jar")).thenReturn(list(bazPath)); String[] args = strings("hi", "there"); @@ -432,7 +535,7 @@ public void testCapsuleInClassPath() throws Exception { assertTrue(Files.isDirectory(appCache.resolve("lib"))); assertTrue(Files.isRegularFile(appCache.resolve("lib").resolve("a.jar"))); - ASSERT.that(getClassPath(pb)).has().noneOf(getPath("capsule.jar")); + ASSERT.that(getClassPath(pb)).has().noneOf(path("capsule.jar")); ASSERT.that(getClassPath(pb)).has().allOf( appCache, appCache.resolve("foo.jar"), @@ -548,7 +651,7 @@ public void testReallyExecutableCapsule() throws Exception { String[] args = strings("hi", "there"); List cmdLine = list("-Dfoo=x", "-Dzzz", "-Xms15"); - final Path capsuleJar = getPath("capsule.jar"); + final Path capsuleJar = path("capsule.jar"); jar.write(capsuleJar); Capsule capsule = Capsule.newCapsule(capsuleJar, cache); @@ -567,7 +670,7 @@ public void testCustomCapsule() throws Exception { String[] args = strings("hi", "there"); List cmdLine = list("-Dfoo=x", "-Dzzz", "-Xms15"); - final Path capsuleJar = getPath("capsule.jar"); + final Path capsuleJar = path("capsule.jar"); jar.write(capsuleJar); Capsule capsule = Capsule.newCapsule(capsuleJar, cache); @@ -649,7 +752,7 @@ public void testEmptyCapsule() throws Exception { // may be called once per test (always writes jar into /capsule.jar) private Capsule newCapsule(Jar jar, DependencyManager dependencyManager) { try { - final Path capsuleJar = getPath("capsule.jar"); + final Path capsuleJar = path("capsule.jar"); jar.write(capsuleJar); Constructor ctor = Capsule.class.getDeclaredConstructor(Path.class, Path.class, Object.class); ctor.setAccessible(true); @@ -667,7 +770,7 @@ private Jar newCapsuleJar() { .setAttribute("Main-Class", "Capsule"); } - private Path getPath(String first, String... more) { + private Path path(String first, String... more) { return fs.getPath(first, more); } @@ -690,7 +793,7 @@ private InputStream toInputStream(Model model) { private List paths(String cp) { final List res = new ArrayList<>(); for (String p : cp.split(":")) - res.add(getPath(p)); + res.add(path(p)); return res; }