Skip to content

Commit

Permalink
Update Mac application bundles for native libraries.
Browse files Browse the repository at this point in the history
  • Loading branch information
colorizenl committed Jan 10, 2024
1 parent 38c130c commit 9f9088c
Show file tree
Hide file tree
Showing 15 changed files with 229 additions and 75 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ apply plugin: "com.gradle.plugin-publish"
apply plugin: "jacoco"

group = "nl.colorize"
version = "2024.1"
version = "2024.2"
compileJava.options.encoding = "UTF-8"

java {
Expand Down
21 changes: 20 additions & 1 deletion example/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,30 @@ java {
sourceSets.test.java.srcDirs = ["source"]
}

repositories {
mavenCentral()
maven {
url "https://jitpack.io"
}
}

dependencies {
implementation "nl.colorize:colorize-java-commons:2024.1"
implementation "nl.colorize:multimedialib:2024.1"
}

jar {
archiveFileName = "example.jar"
duplicatesStrategy = DuplicatesStrategy.WARN
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
exclude "**/module-info.class"

manifest {
attributes "Main-Class": "com.example.ExampleApp"
}

from {
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
}
}

macApplicationBundle {
Expand All @@ -44,6 +61,8 @@ macApplicationBundle {
icon = "../resources/icon.icns"
applicationCategory = "public.app-category.developer-tools"
mainClassName = "com.example.ExampleApp"
extractNatives = true
args = ["gdx"]
}

msi {
Expand Down
89 changes: 53 additions & 36 deletions example/source/ExampleApp.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,52 +6,69 @@

package com.example;

import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import nl.colorize.multimedialib.renderer.Canvas;
import nl.colorize.multimedialib.renderer.DisplayMode;
import nl.colorize.multimedialib.renderer.ErrorHandler;
import nl.colorize.multimedialib.renderer.FilePointer;
import nl.colorize.multimedialib.renderer.GraphicsMode;
import nl.colorize.multimedialib.renderer.Renderer;
import nl.colorize.multimedialib.renderer.ScaleStrategy;
import nl.colorize.multimedialib.renderer.WindowOptions;
import nl.colorize.multimedialib.renderer.java2d.Java2DRenderer;
import nl.colorize.multimedialib.renderer.libgdx.GDXRenderer;
import nl.colorize.multimedialib.stage.ColorRGB;
import nl.colorize.multimedialib.stage.Image;
import nl.colorize.multimedialib.stage.Sprite;
import nl.colorize.multimedialib.scene.Scene;
import nl.colorize.multimedialib.scene.SceneContext;
import nl.colorize.util.swing.ApplicationMenuListener;

/**
* Example application that displays an extremely simple Swing user interface.
* This is included in the plugin code so that the plugin can be tested from
* a Gradle build.
* Example application that displays an extremely simple MultimediaLib scene.
* This acts as a "real" application that is included in the plugin code,
* both for testing purposes and as an example on how to use the plugin.
*/
public class ExampleApp extends JPanel {

private BufferedImage logo;
public class ExampleApp implements Scene, ApplicationMenuListener {

public static void main(String[] args) {
JFrame window = new JFrame();
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setResizable(true);
window.setTitle("Example");
window.setContentPane(new ExampleApp());
window.pack();
window.setLocationRelativeTo(null);
window.setVisible(true);
}
ExampleApp app = new ExampleApp();

public ExampleApp() {
super();
setLayout(null);
setPreferredSize(new Dimension(800, 600));
setBackground(new Color(235, 235, 235));
Canvas canvas = new Canvas(800, 600, ScaleStrategy.flexible());
DisplayMode displayMode = new DisplayMode(canvas, 60);

try (InputStream stream = getClass().getClassLoader().getResourceAsStream("icon.png")) {
logo = ImageIO.read(stream);
} catch (IOException e) {
throw new RuntimeException("Unable to load image", e);
WindowOptions windowOptions = new WindowOptions("Example");
windowOptions.setAppMenuListener(app);

if (args.length > 0 && args[0].contains("java2d")) {
windowOptions.setTitle(windowOptions.getTitle() + " (Java2D renderer)");
Renderer renderer = new Java2DRenderer(displayMode, windowOptions);
renderer.start(app, ErrorHandler.DEFAULT);
} else {
Renderer renderer = new GDXRenderer(GraphicsMode.MODE_2D, displayMode, windowOptions);
renderer.start(app, ErrorHandler.DEFAULT);
}
}

@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(logo, getWidth() / 2 - 100, getHeight() / 2 - 100, 200, 200, null);
public void start(SceneContext context) {
context.getStage().setBackgroundColor(new ColorRGB(235, 235, 235));

Image icon = context.getMediaLoader().loadImage(new FilePointer("icon.png"));
Sprite sprite = new Sprite(icon);
sprite.setPosition(context.getCanvas().getWidth() / 2f, context.getCanvas().getHeight() / 2f);
sprite.getTransform().setScale(25);
context.getStage().getRoot().addChild(sprite);
}

@Override
public void update(SceneContext context, float deltaTime) {
}

@Override
public void onQuit() {
}

@Override
public void onAbout() {
}
}
5 changes: 3 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ The plugin is available from the [Gradle plugin registry](https://plugins.gradle
use the plugin in your Gradle project by adding the following to `build.gradle`:

plugins {
id "nl.colorize.gradle.application" version "2024.1"
id "nl.colorize.gradle.application" version "2024.2"
}

Building native Mac application bundles
Expand Down Expand Up @@ -100,6 +100,7 @@ The following configuration options are available:
| `args` | no | List of command line arguments provided to the main class. |
| `startOnFirstThread` | no | When true, starts the application with `-XstartOnFirstThread`. |
| `icon` | yes | Location of the `.icns` file. |
| `extractNatives` | no | Extracts embedded native libraries from JAR files. |
| `outputDir` | no | Output directory path, defaults to `build/mac`. |

- Note that, in addition to the `bundleVersion` property, there is also the concept of build
Expand All @@ -116,7 +117,7 @@ The following configuration options are available:

```
jar {
duplicatesStrategy = DuplicatesStrategy.WARN
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
exclude "**/module-info.class"
exclude "**/META-INF/INDEX.LIST"
exclude "**/META-INF/*.SF"
Expand Down
12 changes: 11 additions & 1 deletion resources/entitlements-app.plist
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,15 @@
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-executable-page-protection</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
</dict>
</plist>
</plist>
2 changes: 1 addition & 1 deletion resources/entitlements-jre.plist
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@
<key>com.apple.security.inherit</key>
<true/>
</dict>
</plist>
</plist>
16 changes: 16 additions & 0 deletions source/nl/colorize/gradle/application/AppHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,20 @@ public static String loadResourceFile(String path, Map<String, String> propertie
}
return contents;
}

public static void clearOutputDir(File outputDir) {
if (!outputDir.exists()) {
return;
}

try {
Files.walk(outputDir.toPath())
.sorted(Comparator.reverseOrder())
.map(Path::toFile)
.filter(file -> !file.equals(outputDir))
.forEach(File::delete);
} catch (IOException e) {
throw new RuntimeException("Unable to clear directory: " + outputDir.getAbsolutePath());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,14 @@
import org.gradle.api.tasks.TaskAction;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class CreateApplicationBundleTask extends DefaultTask {

Expand All @@ -38,7 +44,11 @@ protected void run(MacApplicationBundleExt config) {

File jdk = new File(config.getJdkPath());
File outputDir = config.getOutputDir(getProject());
AppHelper.cleanDirectory(outputDir);
bundle(config, jdk, outputDir);
if (config.isExtractNatives()) {
extractNativeLibraries(outputDir);
}
}

private void bundle(MacApplicationBundleExt config, File jdk, File outputDir) {
Expand Down Expand Up @@ -84,7 +94,6 @@ private List<String> getCombinedOptions(MacApplicationBundleExt config) {
private FileSet createClassPath(MacApplicationBundleExt config) {
FileSet classPath = new FileSet();
classPath.setDir(getContentDir(config));
classPath.setIncludes("*.jar");
classPath.setExcludes("*-sources.jar,*-javadoc.jar");
return classPath;
}
Expand Down Expand Up @@ -162,4 +171,45 @@ private String getShortVersion(MacApplicationBundleExt config) {
}
return config.getBundleVersion();
}

/**
* Extracts all embedded native libraries from JAR files, as extracting
* at runtime is not allowed by the Mac App Store.
*/
private void extractNativeLibraries(File outputDir) {
try {
Files.walk(outputDir.toPath())
.map(Path::toFile)
.filter(file -> file.getName().endsWith(".jar"))
.filter(file -> file.getParentFile().getName().equals("Java"))
.forEach(this::extractNativeLibrariesFromJAR);
} catch (IOException e) {
throw new RuntimeException("Failed to extract native libraries", e);
}
}

private void extractNativeLibrariesFromJAR(File jarFile) {
File outputDir = jarFile.getParentFile();

try (JarFile jar = new JarFile(jarFile)) {
jar.stream()
.filter(entry -> entry.getName().endsWith(".dylib"))
.forEach(entry -> extractNativeLibrary(jar, entry, outputDir));
} catch (IOException e) {
throw new RuntimeException("Failed to extract native libraries from " + jarFile, e);
}
}

private void extractNativeLibrary(JarFile jar, JarEntry entry, File outputDir) {
File outputFile = new File(outputDir, "native-" + entry.getName().replace("/", "-"));
if (outputFile.exists()) {
throw new IllegalStateException("File already exists: " + outputFile);
}

try (InputStream stream = jar.getInputStream(entry)) {
Files.copy(stream, outputFile.toPath());
} catch (IOException e) {
throw new RuntimeException("Failed to extract " + entry.getName(), e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public class MacApplicationBundleExt implements Validatable {
private List<String> args;
private boolean startOnFirstThread;
private String jdkPath;
private boolean extractNatives;
private String outputDir;

public static final List<String> SUPPORTED_EMBEDDED_JDKS = List.of(
Expand Down Expand Up @@ -78,6 +79,7 @@ public MacApplicationBundleExt() {
startOnFirstThread = false;

jdkPath = AppHelper.getEnvironmentVariable("JAVA_HOME");
extractNatives = false;
outputDir = "mac";
}

Expand Down
Loading

0 comments on commit 9f9088c

Please sign in to comment.