Skip to content

Commit 4f24f19

Browse files
authored
Merge pull request #115 from salesforce/feature/canteen
WIP: Canteen - cross-platform self-executing jars
2 parents dbde80a + 656bba4 commit 4f24f19

File tree

15 files changed

+1126
-0
lines changed

15 files changed

+1126
-0
lines changed

.travis.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,18 @@
1+
dist: xenial
2+
13
language: java
24
jdk:
35
- openjdk8
46
- openjdk11
57

8+
env:
9+
- GO111MODULE=on
10+
11+
before_install:
12+
- eval "$(gimme 1.12)"
13+
14+
script:
15+
- mvn install
16+
617
after_success:
718
- bash <(curl -s https://codecov.io/bash)

canteen/README.md

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# Canteen
2+
3+
The `canteen-maven-plugin` is used to make executable jar files self-executing. That is, executable from the command
4+
line as if they were native programs instead of requiring a `java -jar` command.
5+
6+
```bash
7+
# For example
8+
java -jar copyjar-1.0.0.jar fromFile toFile
9+
10+
# Becomes
11+
./copyjar-1.0.0-linux-x86_64.exe fromFile toFile
12+
```
13+
14+
Canteen creates platform native self-executing jars for 64-bit Linux, MacOS, and Windows.
15+
16+
## Usage
17+
18+
Bundle your jar as an executable jar, and add the `canteen-maven-plugin` to your pom.xml.
19+
20+
```xml
21+
<build>
22+
<plugins>
23+
<!-- Shade dependencies into an uber jar -->
24+
<plugin>
25+
<groupId>org.apache.maven.plugins</groupId>
26+
<artifactId>maven-shade-plugin</artifactId>
27+
<version>3.2.1</version>
28+
<executions>
29+
<execution>
30+
<phase>package</phase>
31+
<goals>
32+
<goal>shade</goal>
33+
</goals>
34+
</execution>
35+
</executions>
36+
</plugin>
37+
38+
<!-- Populate the jar's manifest main class to make it executable -->
39+
<plugin>
40+
<groupId>org.apache.maven.plugins</groupId>
41+
<artifactId>maven-jar-plugin</artifactId>
42+
<version>2.4</version>
43+
<configuration>
44+
<archive>
45+
<manifest>
46+
<addClasspath>true</addClasspath>
47+
<mainClass>mypackage.Main</mainClass>
48+
</manifest>
49+
</archive>
50+
</configuration>
51+
</plugin>
52+
53+
<!-- Make the jar self-executing with Canteen -->
54+
<plugin>
55+
<groupId>com.salesforce.servicelibs</groupId>
56+
<artifactId>canteen-maven-plugin</artifactId>
57+
<version>${canteen.version}</version>
58+
<executions>
59+
<execution>
60+
<goals>
61+
<goal>bootstrap</goal>
62+
</goals>
63+
</execution>
64+
</executions>
65+
</plugin>
66+
</plugins>
67+
</build>
68+
```
69+
70+
Canteen will add three additional artifacts to your Maven module with types and classifiers compatible with the
71+
[os-maven-plugin](https://github.com/trustin/os-maven-plugin).
72+
73+
* `artifactId:groupId:version:exe:linux-x86_64`
74+
* `artifactId:groupId:version:exe:osx-x86_64`
75+
* `artifactId:groupId:version:exe:windows-x86_64`
76+
77+
## How does Canteen work?
78+
79+
Canteen leverages a quirk of Java's jar format to make jars behave like native executables.
80+
81+
Under the hood, jar files are just zip archives. A zip archive is made up of one or more independently compressed files,
82+
followed by an index table. The zip index table is rooted at the end of the archive with offset pointers relative to the
83+
final byte address of the file. The backwards-looking nature of the zip archive index means you can prepend a zip
84+
archive with arbitrary data or executable code without violating the integrity of the archive.
85+
86+
Unlike zip archives which are read backwards, executable programs are loaded and executed from byte zero. A common
87+
technique for adding "data" to executables is to append bytes to the end of the executable, where it won't affect
88+
execution.
89+
90+
Concatenating an executable and a zip archive results in a file that is both a valid executable (read from the front),
91+
and a valid zip archive (read from the back). This technique was historically used to create self-extracting zip files.
92+
The `canteen-maven-plugin` uses the same technique to prepend a simple platform specific bootstrap program
93+
cross-compiled with Go to the front of a jar.
94+
95+
When you execute a Canteen packaged jar, the bootstrap program captures all the command line arguments and the name
96+
of the current file. It then spawns a child process as `java -jar $args`, proxying `stdin`, `stdout`, and `stderr`
97+
between the shell and the child process.

canteen/canteen-bootstrap/README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Canteen Bootstrap
2+
3+
This module contains the golang bootstrap shim used to invoke `java -jar`. The shim tries to be transparent. It passes
4+
all command line arguments to the child process, so you can use it as if it were the Java process itself.
5+
6+
Once the bootstrap is running, stdin, stdout, and stderr are proxied to the child process. When the child process
7+
exits, the bootstrap assumes the child's exit code. At the moment, the bootstrap does not proxy signals between the
8+
shell and the child process.
9+
10+
The bootstrap is cross-compiled for 64-bit Linux, MacOS, and Windows as part of the Maven build and attached as
11+
additional artifacts with classifiers compatible with the [os-maven-plugin](https://github.com/trustin/os-maven-plugin).
12+
13+
## Improvements
14+
15+
* Propagate signals to and from child process
16+
* More platforms
17+
* More architectures

canteen/canteen-bootstrap/pom.xml

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
~ Copyright (c) 2019, Salesforce.com, Inc.
4+
~ All rights reserved.
5+
~ Licensed under the BSD 3-Clause license.
6+
~ For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
7+
-->
8+
9+
<project xmlns="http://maven.apache.org/POM/4.0.0"
10+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
11+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
12+
<parent>
13+
<artifactId>canteen-parent</artifactId>
14+
<groupId>com.salesforce.servicelibs</groupId>
15+
<version>0.1.0-SNAPSHOT</version>
16+
</parent>
17+
<modelVersion>4.0.0</modelVersion>
18+
19+
<artifactId>canteen-bootstrap</artifactId>
20+
21+
<properties>
22+
<binary.name>canteen</binary.name>
23+
</properties>
24+
25+
<build>
26+
<plugins>
27+
<plugin>
28+
<groupId>org.codehaus.mojo</groupId>
29+
<artifactId>exec-maven-plugin</artifactId>
30+
<version>1.6.0</version>
31+
<executions>
32+
<execution>
33+
<id>osx-x86_64</id>
34+
<goals>
35+
<goal>exec</goal>
36+
</goals>
37+
<phase>compile</phase>
38+
<configuration>
39+
<workingDirectory>${project.basedir}/src/main/go</workingDirectory>
40+
<executable>go</executable>
41+
<arguments>
42+
<argument>build</argument>
43+
<argument>-o</argument>
44+
<argument>${project.build.directory}/${binary.name}-${project.version}-osx-x86_64.exe</argument>
45+
<argument>.</argument>
46+
</arguments>
47+
<environmentVariables>
48+
<GOOS>darwin</GOOS>
49+
<GOARCH>amd64</GOARCH>
50+
</environmentVariables>
51+
</configuration>
52+
</execution>
53+
<execution>
54+
<id>linux_x86_64</id>
55+
<goals>
56+
<goal>exec</goal>
57+
</goals>
58+
<phase>compile</phase>
59+
<configuration>
60+
<workingDirectory>${project.basedir}/src/main/go</workingDirectory>
61+
<executable>go</executable>
62+
<arguments>
63+
<argument>build</argument>
64+
<argument>-o</argument>
65+
<argument>${project.build.directory}/${binary.name}-${project.version}-linux-x86_64.exe</argument>
66+
<argument>.</argument>
67+
</arguments>
68+
<environmentVariables>
69+
<GOOS>linux</GOOS>
70+
<GOARCH>amd64</GOARCH>
71+
</environmentVariables>
72+
</configuration>
73+
</execution>
74+
<execution>
75+
<id>windows_x86_64</id>
76+
<goals>
77+
<goal>exec</goal>
78+
</goals>
79+
<phase>compile</phase>
80+
<configuration>
81+
<workingDirectory>${project.basedir}/src/main/go</workingDirectory>
82+
<executable>go</executable>
83+
<arguments>
84+
<argument>build</argument>
85+
<argument>-o</argument>
86+
<argument>${project.build.directory}/${binary.name}-${project.version}-windows-x86_64.exe</argument>
87+
<argument>.</argument>
88+
</arguments>
89+
<environmentVariables>
90+
<GOOS>windows</GOOS>
91+
<GOARCH>amd64</GOARCH>
92+
</environmentVariables>
93+
</configuration>
94+
</execution>
95+
</executions>
96+
</plugin>
97+
98+
<plugin>
99+
<groupId>org.codehaus.mojo</groupId>
100+
<artifactId>build-helper-maven-plugin</artifactId>
101+
<version>3.0.0</version>
102+
<executions>
103+
<execution>
104+
<id>osx-x86_64</id>
105+
<goals>
106+
<goal>attach-artifact</goal>
107+
</goals>
108+
<configuration>
109+
<artifacts>
110+
<artifact>
111+
<file>${project.build.directory}/${binary.name}-${project.version}-osx-x86_64.exe</file>
112+
<type>exe</type>
113+
<classifier>osx-x86_64</classifier>
114+
</artifact>
115+
</artifacts>
116+
</configuration>
117+
</execution>
118+
<execution>
119+
<id>linux_x86_64</id>
120+
<goals>
121+
<goal>attach-artifact</goal>
122+
</goals>
123+
<configuration>
124+
<artifacts>
125+
<artifact>
126+
<file>${project.build.directory}/${binary.name}-${project.version}-linux-x86_64.exe</file>
127+
<type>exe</type>
128+
<classifier>linux-x86_64</classifier>
129+
</artifact>
130+
</artifacts>
131+
</configuration>
132+
</execution>
133+
<execution>
134+
<id>windows-x86_64</id>
135+
<goals>
136+
<goal>attach-artifact</goal>
137+
</goals>
138+
<configuration>
139+
<artifacts>
140+
<artifact>
141+
<file>${project.build.directory}/${binary.name}-${project.version}-windows-x86_64.exe</file>
142+
<type>exe</type>
143+
<classifier>windows-x86_64</classifier>
144+
</artifact>
145+
</artifacts>
146+
</configuration>
147+
</execution>
148+
</executions>
149+
</plugin>
150+
</plugins>
151+
</build>
152+
</project>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module github.com/salesforce/canteen
2+
3+
go 1.12
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright (c) 2019, Salesforce.com, Inc.
3+
* All rights reserved.
4+
* Licensed under the BSD 3-Clause license.
5+
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6+
*/
7+
8+
package main
9+
10+
import (
11+
"log"
12+
"os"
13+
"os/exec"
14+
"strings"
15+
"syscall"
16+
)
17+
18+
func main() {
19+
jarName := os.Args[0]
20+
jarArgs := os.Args[1:]
21+
22+
binary, err := exec.LookPath("java")
23+
if err != nil {
24+
log.Fatalf("Java execution error: %v", err)
25+
}
26+
27+
args := []string{"-jar", strings.TrimPrefix(jarName, "./")}
28+
args = append(args, jarArgs...)
29+
30+
cmd := exec.Command(binary, args...)
31+
cmd.Stdout = os.Stdout
32+
cmd.Stderr = os.Stderr
33+
cmd.Stdin = os.Stdin
34+
35+
if err := cmd.Run(); err != nil {
36+
if err, ok := err.(*exec.ExitError); ok {
37+
if status, ok := err.Sys().(syscall.WaitStatus); ok {
38+
// Exit with the same code as the Java program
39+
os.Exit(status.ExitStatus())
40+
}
41+
} else {
42+
log.Fatalf("Bootstrap execution error: %v", err)
43+
}
44+
}
45+
}

0 commit comments

Comments
 (0)