Skip to content

Commit

Permalink
added preliminary support for bundling jre in mac app
Browse files Browse the repository at this point in the history
bundleJVM: true is the property in the jdeploy object of package.json to make this happen.
Addd integration test for this.
  • Loading branch information
Steve Hannah committed Jul 21, 2024
1 parent 4641545 commit 710e2f0
Show file tree
Hide file tree
Showing 26 changed files with 1,050 additions and 5 deletions.
6 changes: 6 additions & 0 deletions .github/workflows/integration_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ jobs:
distribution: 'adopt'
cache: maven
- name: Test Linux
env:
JDEPLOY_TEST_JVM_DOWNLOADS: true
run: bash build_and_test.sh


Expand All @@ -35,6 +37,8 @@ jobs:
cache: maven

- name: Test Mac
env:
JDEPLOY_TEST_JVM_DOWNLOADS: true
run: bash build_and_test.sh


Expand All @@ -51,6 +55,8 @@ jobs:
cache: maven

- name: Build Windows
env:
JDEPLOY_TEST_JVM_DOWNLOADS: true
run: bash build_and_test.sh
shell: bash

Expand Down
47 changes: 46 additions & 1 deletion cli/src/main/java/ca/weblite/jdeploy/JDeploy.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package ca.weblite.jdeploy;

import ca.weblite.jdeploy.app.AppInfo;
import ca.weblite.jdeploy.app.JVMSpecification;
import ca.weblite.jdeploy.appbundler.Bundler;
import ca.weblite.jdeploy.appbundler.BundlerSettings;
import ca.weblite.jdeploy.cli.controllers.CheerpjController;
Expand All @@ -32,6 +33,8 @@
import java.nio.charset.StandardCharsets;
import java.nio.file.*;

import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.PosixFilePermission;
import java.util.*;
import java.util.List;
import java.util.jar.Attributes;
Expand Down Expand Up @@ -78,6 +81,8 @@ public class JDeploy {
System.setProperty("apple.eawt.quitStrategy", "CLOSE_ALL_WINDOWS");
}

private static final int DEFAULT_JAVA_VERSION = 11;

private static final String BUNDLE_MAC_X64 = "mac-x64";
private static final String BUNDLE_MAC_ARM64 = "mac-arm64";
private static final String BUNDLE_WIN = "win";
Expand Down Expand Up @@ -1153,7 +1158,7 @@ public void bundleSplash() throws IOException {
}

public String processJdeployTemplate(String jdeployContents) {
jdeployContents = jdeployContents.replace("{{JAVA_VERSION}}", String.valueOf(getJavaVersion(11)));
jdeployContents = jdeployContents.replace("{{JAVA_VERSION}}", String.valueOf(getJavaVersion(DEFAULT_JAVA_VERSION)));
jdeployContents = jdeployContents.replace("{{PORT}}", String.valueOf(getPort(0)));
if (getWar(null) != null) {
jdeployContents = jdeployContents.replace("{{WAR_PATH}}", new File(getWar(null)).getName());
Expand Down Expand Up @@ -1495,6 +1500,15 @@ private void loadAppInfo(AppInfo appInfo) throws IOException {
appInfo.setCodeSignSettings(AppInfo.CodeSignSettings.CodeSign);
}

if (rj().getAsBoolean("bundleJVM")) {
appInfo.setUseBundledJVM(true);
JVMSpecification jvmSpec = new JVMSpecification();
jvmSpec.javaVersion = getJavaVersion(DEFAULT_JAVA_VERSION);
jvmSpec.javafx = "true".equals(getString("javafx", "false"));
jvmSpec.jdk = "true".equals(getString("jdk", "false"));
appInfo.setJVMSpecification(jvmSpec);
}

String jarPath = getString("jar", null);
if (jarPath != null) {
JarFile jarFile = new JarFile(new File(directory, toNativePath(jarPath)));
Expand Down Expand Up @@ -2434,11 +2448,23 @@ public static void main(String[] args) {
File jDeployDir = new File("jdeploy");
if (jDeployDir.exists()) {
System.out.println("Deleting "+jDeployDir);
try {
makeWritable(jDeployDir.toPath());
} catch (IOException ex) {
System.err.println("Failed to make "+jDeployDir+" writable. "+ex.getMessage());
ex.printStackTrace();
}
FileUtils.deleteDirectory(jDeployDir);
}
jDeployDir = new File("jdeploy-bundle");
if (jDeployDir.exists()) {
System.out.println("Deleting "+jDeployDir);
try {
makeWritable(jDeployDir.toPath());
} catch (IOException ex) {
System.err.println("Failed to make "+jDeployDir+" writable. "+ex.getMessage());
ex.printStackTrace();
}
FileUtils.deleteDirectory(jDeployDir);
}
if (args.length > 1) {
Expand Down Expand Up @@ -2498,6 +2524,25 @@ public static void main(String[] args) {
}
}

private static void makeWritable(Path path) throws IOException {
Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Set<PosixFilePermission> perms = EnumSet.allOf(PosixFilePermission.class);
Files.setPosixFilePermissions(file, perms);
return FileVisitResult.CONTINUE;
}

@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
Set<PosixFilePermission> perms = EnumSet.allOf(PosixFilePermission.class);
Files.setPosixFilePermissions(dir, perms);
dir.toFile().setWritable(true, false);
return FileVisitResult.CONTINUE;
}
});
}

private void githubInit(String[] githubInitArgs) {
GitHubRepositoryInitializerCLIController controller = new GitHubRepositoryInitializerCLIController(getPackageJsonFile(), githubInitArgs);
controller.run();
Expand Down
6 changes: 6 additions & 0 deletions shared/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,12 @@
<artifactId>feather</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.7.0</version>
<scope>test</scope>
</dependency>

</dependencies>
<repositories>
Expand Down
29 changes: 29 additions & 0 deletions shared/src/main/java/ca/weblite/jdeploy/app/AppInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,20 @@ public class AppInfo {

private Set<String> urlSchemes;

/**
* Indicates that the app should use a dedicated JVM rather than the default shared JVM.
* This is required for Java 8 on Windows (at least) since Java 8 didn't provide a way to
* use a JRE at an arbitrary location. This is no longer necessary in Java 9+, but we may
* want to employ its use for other reasons.
*/
private boolean usePrivateJVM = false;

/**
* Indicates that the build should include an embedded JRE as part of the app bundle.
*/
private boolean useBundledJVM = false;

private JVMSpecification jvmSpecification;

public void addUrlScheme(String scheme) {
if (urlSchemes == null) urlSchemes = new HashSet<>();
Expand Down Expand Up @@ -320,6 +332,21 @@ public void setUsePrivateJVM(boolean usePrivateJVM) {
this.usePrivateJVM = usePrivateJVM;
}

public boolean isUseBundledJVM() {
return useBundledJVM;
}

public void setUseBundledJVM(boolean useBundledJVM) {
this.useBundledJVM = useBundledJVM;
}

public void setJVMSpecification(JVMSpecification spec) {
jvmSpecification = spec;
}

public JVMSpecification getJVMSpecification() {
return jvmSpecification;
}

public static enum Updates {
Auto,
Expand Down Expand Up @@ -1192,6 +1219,8 @@ public AppInfo copy() {
out.setWindowsInstallerUrl(getWindowsInstallerUrl());
out.setLinuxAppUrl(getLinuxAppUrl());
out.setLinuxInstallerUrl(getLinuxInstallerUrl());
out.setUsePrivateJVM(isUsePrivateJVM());
out.setUseBundledJVM(isUseBundledJVM());
out.codeSignSettings = codeSignSettings;
out.macAppBundleId = macAppBundleId;
if (permissions != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package ca.weblite.jdeploy.app;

public class JVMSpecification {
public int javaVersion;
public boolean jdk;
public boolean javafx;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

package ca.weblite.jdeploy.appbundler;

import java.io.File;
import java.util.*;

/**
Expand Down Expand Up @@ -38,6 +39,7 @@ public class AppDescription {

private String macDeveloperTeamID;

private File bundleJre;

public AppDescription() {

Expand Down Expand Up @@ -295,4 +297,12 @@ public boolean isFork() {
public void setFork(boolean fork) {
this.fork = fork;
}

public void setBundleJre(File bundleJre) {
this.bundleJre = bundleJre;
}

public File getBundleJre() {
return bundleJre;
}
}
60 changes: 60 additions & 0 deletions shared/src/main/java/ca/weblite/jdeploy/appbundler/Bundler.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
package ca.weblite.jdeploy.appbundler;

import ca.weblite.jdeploy.app.AppInfo;
import ca.weblite.jdeploy.app.JVMSpecification;
import ca.weblite.jdeploy.jvmdownloader.JVMKit;
import ca.weblite.tools.io.URLUtil;
import com.joshondesign.appbundler.linux.LinuxBundler;
import com.joshondesign.appbundler.mac.MacBundler;
Expand All @@ -19,6 +21,7 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.List;

Expand Down Expand Up @@ -185,6 +188,53 @@ private static void setupUrlSchemes(AppInfo appInfo, AppDescription app) {
}
}

private static String getBundlerJVMsPath() {
return Paths.get(System.getProperty("user.home"), ".jdeploy", "bundler", "JavaVirtualMachines").toString();
}

private static void setupBundledJVM(AppInfo appInfo, AppDescription app, Target target) throws IOException {
if (target != Target.MacX64 && target != Target.MacArm) {
// Currently we only support bundling JVM on Mac.
return;
}
if (appInfo.isUseBundledJVM()) {
JVMSpecification jvmSpecification = appInfo.getJVMSpecification();
if (jvmSpecification == null) {
throw new RuntimeException("jvmSpecification is required when useBundledJVM is true");
}
JVMKit jvmKit = new JVMKit();
String arch;
String platform = "macos";
String bitness = "64";
switch (target) {
case MacX64:
arch = "x86";
break;
case MacArm:
arch = "arm";
break;
default:
throw new IllegalArgumentException("Unsupported target: " + target);
}
app.setBundleJre(
jvmKit.createFinder(true).findJVM(
getBundlerJVMsPath(),
String.valueOf(jvmSpecification.javaVersion),
jvmSpecification.jdk ? "jdk" : "jre",
jvmSpecification.javafx,
platform,
arch,
bitness
)
);

}
}

private static void cleanupBundledJVM(AppDescription app) {
app.setBundleJre(null);
}

public static BundlerResult runit(
BundlerSettings bundlerSettings,
AppInfo appInfo,
Expand All @@ -196,6 +246,7 @@ public static BundlerResult runit(
AppDescription app = createAppDescription(appInfo, url);
verifyNativeLibs(app);
Target target = Target.fromString(targetStr);
setupBundledJVM(appInfo, app, target);
if(target == Target.MacX64) {
BundlerResult bundlerResult = MacBundler.start(
bundlerSettings,
Expand Down Expand Up @@ -234,6 +285,7 @@ public static BundlerResult runit(

if(target == Target.All) {
BundlerResult out = new BundlerResult("all");
setupBundledJVM(appInfo, app, Target.MacX64);
out.setResultForType(
"mac",
MacBundler.start(bundlerSettings, MacBundler.TargetArchitecture.X64, app,DEST_DIR, RELEASE_DIR)
Expand All @@ -242,16 +294,24 @@ public static BundlerResult runit(
"mac-x64",
out.getResultForType("mac", false)
);
cleanupBundledJVM(app);
setupBundledJVM(appInfo, app, Target.MacArm);
out.setResultForType(
"mac-arm64",
MacBundler.start(bundlerSettings, MacBundler.TargetArchitecture.ARM64, app,DEST_DIR, RELEASE_DIR)
);
cleanupBundledJVM(app);
setupBundledJVM(appInfo, app, Target.WinX64);
out.setResultForType("win", WindowsBundler2.start(bundlerSettings, app,DEST_DIR, RELEASE_DIR));
cleanupBundledJVM(app);
out.setResultForType(
"win-installer",
WindowsBundler2.start(bundlerSettings, app, DEST_DIR, RELEASE_DIR, true)
);
cleanupBundledJVM(app);
setupBundledJVM(appInfo, app, Target.LinuxX64);
out.setResultForType("linux", LinuxBundler.start(bundlerSettings, app, DEST_DIR, RELEASE_DIR));
cleanupBundledJVM(app);
out.setResultForType(
"linux-installer",
LinuxBundler.start(bundlerSettings, app, DEST_DIR, RELEASE_DIR, true)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package ca.weblite.jdeploy.jvmdownloader;

import java.io.IOException;

public interface JVMDownloader {
/**
* Download a JVM
* @param basePath The base path to download the JVM to. It will be downloaded into a subdirectory of this path
* based on the platform, architecture, and bundle type.
* @param version The Java version
* @param bundleType The bundle type. Either "jdk" or "jre"
* @param javafx Whether to include JavaFX
* @param overridePlatform The platform to download for. If null, the current platform will be used.
* @param overrideArch The architecture to download for. If null, the current architecture will be used.
* @param overrideBitness The bitness to download for. If null, the current bitness will be used.
* @throws IOException
*/
void downloadJVM(
String basePath,
String version,
String bundleType,
boolean javafx,
String overridePlatform,
String overrideArch,
String overrideBitness
) throws IOException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package ca.weblite.jdeploy.jvmdownloader;

import java.io.IOException;

public class JVMEmbedder {

private JVMFinder finder;

/**
* Embed a JVM into a directory
* @param sourcePath The source path of the JVM to install. May be either a .zip or a .tar.gz file.
* @param targetPath The target path where the JVM will be installed. E.g. path/to/jre. The JVM should be extracted to this directory.
* Not as a subdirectory, but as the targetPath itself.
* @throws IOException
*/
public void embedJVM(
String sourcePath,
String targetPath
) throws IOException {

}
}
Loading

0 comments on commit 710e2f0

Please sign in to comment.