Skip to content

Commit e8c240b

Browse files
committed
Initial version. Converts a jar or class into a native image
1 parent 48200ed commit e8c240b

8 files changed

Lines changed: 414 additions & 0 deletions

File tree

.circleci/config.yml

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
docker_image: &docker_image
2+
docker:
3+
- image: docker:19.03.6
4+
5+
slack_image: &slack_image
6+
docker:
7+
- image: devatherock/simple-slack:0.4.0
8+
9+
work_directory: &work_directory
10+
working_directory: ~/java-to-native
11+
12+
resource_class: &resource_class
13+
resource_class: small
14+
15+
version: 2.1
16+
jobs:
17+
groovy_script_to_jar:
18+
docker:
19+
- image: devatherock/vela-groovy-script-to-jar:0.6.0
20+
<<: *resource_class
21+
<<: *work_directory
22+
environment:
23+
PARAMETER_SCRIPT_PATH: CreateNativeImage.groovy
24+
steps:
25+
- checkout
26+
- run: sh /scripts/entry-point.sh
27+
- persist_to_workspace:
28+
root: ~/java-to-native
29+
paths:
30+
- CreateNativeImage.jar
31+
publish:
32+
<<: *docker_image
33+
<<: *resource_class
34+
<<: *work_directory
35+
steps:
36+
- checkout
37+
- setup_remote_docker
38+
- attach_workspace:
39+
at: ~/java-to-native
40+
- run: |
41+
TAG=0.1.0
42+
docker build -t devatherock/java-to-native:$TAG .
43+
docker tag devatherock/java-to-native:$TAG devatherock/java-to-native:latest
44+
docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
45+
docker push devatherock/java-to-native:$TAG
46+
docker push devatherock/java-to-native:latest
47+
48+
notify_success:
49+
<<: *slack_image
50+
<<: *resource_class
51+
environment:
52+
TEXT: "Success: {{.CircleBuildUrl}} by {{.CircleUsername}}"
53+
COLOR: "#33ad7f"
54+
CHANNEL: general
55+
TITLE: "Build completed"
56+
steps:
57+
- run: /bin/simpleslack
58+
notify_failure:
59+
<<: *slack_image
60+
<<: *resource_class
61+
environment:
62+
TEXT: "Failure: {{.CircleBuildUrl}} by {{.CircleUsername}}"
63+
COLOR: "#a1040c"
64+
CHANNEL: general
65+
TITLE: "Build completed"
66+
steps:
67+
- run:
68+
name: Failure notification
69+
command: /bin/simpleslack
70+
when: on_fail
71+
72+
workflows:
73+
version: 2.1
74+
build_publish:
75+
jobs:
76+
- groovy_script_to_jar:
77+
filters:
78+
branches:
79+
only: master
80+
- publish:
81+
requires:
82+
- groovy_script_to_jar
83+
- notify_success:
84+
requires:
85+
- publish
86+
- notify_failure:
87+
requires:
88+
- publish
89+
pr_check:
90+
jobs:
91+
- groovy_script_to_jar:
92+
filters:
93+
branches:
94+
ignore: master

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.project
2+
*.jar
3+
*.iml

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Changelog
2+
3+
## [0.1.0] - 2020-06-20
4+
### Added
5+
- Initial version. Converts a jar or class into a native image, generating reflection configuration if required

CreateNativeImage.groovy

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
@Grab(group = 'org.yaml', module = 'snakeyaml', version = '1.25')
2+
@Grab(group = 'org.codehaus.groovy', module = 'groovy-cli-commons', version = '2.5.7')
3+
4+
import groovy.cli.commons.CliBuilder
5+
import groovy.transform.Field
6+
import org.yaml.snakeyaml.Yaml
7+
8+
import java.nio.file.Files
9+
import java.nio.file.Paths
10+
import java.util.logging.Level
11+
import java.util.logging.Logger
12+
13+
@Field static final String CONFIG_AGENT_ARGS = 'native-image.agent.args'
14+
@Field static final String CONFIG_BUILD_ADDITIONAL_ARGS = 'native-image.build.additional-args'
15+
@Field static final String CONFIG_BUILD_OVERRIDE_ARGS = 'native-image.build.override-args'
16+
@Field static final String ARG_GRAPE_DISABLE = '-Dgroovy.grape.enable=false'
17+
@Field static final String REGEX_ENV_VARIABLE = '\\$\\{([A-Z_]+)\\}'
18+
19+
System.setProperty('java.util.logging.SimpleFormatter.format',
20+
'%1$tY-%1$tm-%1$tdT%1$tH:%1$tM:%1$tS.%1$tL%1$tz %4$s %5$s%6$s%n')
21+
@Field Logger logger = Logger.getLogger('CreateNativeImage.log')
22+
23+
def cli = new CliBuilder(usage: 'groovy CreateNativeImage.groovy [options]')
24+
cli.m(longOpt: 'main-class-name', args: 1, argName: 'main-class-name', 'The name of the main class')
25+
cli.j(longOpt: 'jar-name', args: 1, argName: 'jar-name', 'The jar containing the main class')
26+
cli.c(longOpt: 'config', args: 1, argName: 'config', 'File containing configuration for the native image build')
27+
cli.cp(longOpt: 'classpath', args: 1, argName: 'classpath', 'Classpath for the native image build')
28+
cli.rp(longOpt: 'reflection-config-path', args: 1, argName: 'reflection-config-path', 'Path in which to generate the reflection config')
29+
cli.d(longOpt: 'debug', args: 0, argName: 'debug', 'Enable debug logs')
30+
31+
def options = cli.parse(args)
32+
if (!(options.m || options.j)) {
33+
cli.usage()
34+
System.exit(1)
35+
}
36+
37+
// Enable debug logs
38+
if (options.d) {
39+
Logger root = Logger.getLogger('')
40+
root.setLevel(Level.FINE)
41+
root.getHandlers().each { it.setLevel(Level.FINE) }
42+
}
43+
44+
// Read config file
45+
String configFile = options.c
46+
def config = [:]
47+
if (configFile) {
48+
Yaml yaml = new Yaml()
49+
config.putAll(getFlattenedMap('', yaml.load(new File(configFile).text)))
50+
}
51+
logger.fine({ String.valueOf(config) })
52+
53+
// Generate reflection config
54+
String classPath = options.cp ?: 'build/native/libs/*:build/native/classes'
55+
String reflectConfigPath = options.rp ?: 'build/native/graal-config/'
56+
Files.createDirectories(Paths.get(reflectConfigPath))
57+
58+
def firstCommand = ['java', "-agentlib:native-image-agent=config-output-dir=${reflectConfigPath}",
59+
'-cp', classPath, ARG_GRAPE_DISABLE]
60+
def baseCommand = ['java', "-agentlib:native-image-agent=config-merge-dir=${reflectConfigPath}",
61+
'-cp', classPath, ARG_GRAPE_DISABLE]
62+
def executable = []
63+
if (options.m) {
64+
executable.add(options.m)
65+
} else {
66+
executable.add('-jar')
67+
executable.add(options.j)
68+
}
69+
firstCommand.addAll(executable)
70+
baseCommand.addAll(executable)
71+
72+
if (config[CONFIG_AGENT_ARGS]) {
73+
boolean first = true
74+
def commandWithArgs = []
75+
76+
config[CONFIG_AGENT_ARGS].each { arguments ->
77+
commandWithArgs.clear()
78+
79+
if (first) {
80+
commandWithArgs.addAll(firstCommand)
81+
first = false
82+
} else {
83+
commandWithArgs.addAll(baseCommand)
84+
}
85+
86+
arguments.each { argument ->
87+
// If argument is an environment variable, replace it with its value if present
88+
if (argument =~ REGEX_ENV_VARIABLE) {
89+
String envVariableName = argument.find(REGEX_ENV_VARIABLE) { matchedText, group ->
90+
return group
91+
}
92+
String envVariableValue = System.getenv(envVariableName)
93+
commandWithArgs.add(envVariableValue ?: argument)
94+
} else {
95+
commandWithArgs.add(argument)
96+
}
97+
}
98+
executeCommand(commandWithArgs)
99+
}
100+
} else {
101+
executeCommand(firstCommand)
102+
}
103+
104+
/** Build native image **/
105+
def nativeImageCommand = ['native-image', '-cp', classPath]
106+
107+
// If override arguments are specified, add only those
108+
if (config[CONFIG_BUILD_OVERRIDE_ARGS]) {
109+
nativeImageCommand.addAll(config[CONFIG_BUILD_OVERRIDE_ARGS])
110+
} else {
111+
nativeImageCommand.addAll([ARG_GRAPE_DISABLE, '--no-server', '--allow-incomplete-classpath',
112+
'--report-unsupported-elements-at-runtime', '--initialize-at-build-time',
113+
'--enable-url-protocols=http,https', "-H:ConfigurationFileDirectories=${reflectConfigPath}"])
114+
115+
if (config[CONFIG_BUILD_ADDITIONAL_ARGS]) {
116+
nativeImageCommand.addAll(config[CONFIG_BUILD_ADDITIONAL_ARGS])
117+
}
118+
}
119+
nativeImageCommand.addAll(executable)
120+
executeCommand(nativeImageCommand)
121+
122+
123+
/**
124+
* Executes a command
125+
*
126+
* @param command
127+
*/
128+
void executeCommand(def command) {
129+
logger.fine({ String.valueOf(command) })
130+
Process process = command.execute()
131+
process.consumeProcessOutput(System.out, System.err)
132+
process.waitFor()
133+
}
134+
135+
/**
136+
* Converts the YAML configuration into a flat map
137+
*
138+
* @param prefix
139+
* @param map
140+
* @return
141+
*/
142+
Map getFlattenedMap(String prefix, Map map) {
143+
Map flatMap = [:]
144+
145+
map.each { key, value ->
146+
if (value instanceof Map) {
147+
flatMap.putAll(getFlattenedMap(prefix + key + '.', value))
148+
} else {
149+
flatMap[(prefix + key)] = value
150+
}
151+
}
152+
153+
return flatMap
154+
}

DOCS.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
## Config
2+
3+
The following parameters can be set to configure the plugin.
4+
5+
* **debug** - Flag to enable debug logs. Optional, by default, debug logs are disabled
6+
* **jar_name** - Executable jar file to convert into native binary
7+
* **config_file** - Path to a yaml configuration file
8+
* **classpath** - Path containing class files and jars to use as class path. Defaults to `build/libs/*:build/classes`
9+
* **reflection_path** - Path to the reflection configuration if it already exists. If not specified, it will be generated at `build/native/graal-config/`
10+
11+
## Examples
12+
### Circle CI
13+
```yaml
14+
java_to_native:
15+
docker:
16+
- image: devatherock/java-to-native:0.1.0
17+
working_directory: ~/drone-yaml-validator
18+
environment:
19+
PLUGIN_JAR_NAME: YamlValidator.jar
20+
PLUGIN_CONFIG_FILE: config/graal.yml
21+
steps:
22+
- checkout
23+
- run: sh /scripts/entry-point.sh
24+
- persist_to_workspace:
25+
root: ~/drone-yaml-validator
26+
paths:
27+
- YamlValidator
28+
```
29+
30+
### vela
31+
32+
```yaml
33+
steps:
34+
- name: java_to_native
35+
ruleset:
36+
branch: master
37+
event: push
38+
image: devatherock/java-to-native:0.1.0
39+
parameters:
40+
jar_name: YamlValidator.jar
41+
config_file: config/graal.yml
42+
```

Dockerfile

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
FROM oracle/graalvm-ce:20.1.0-java11
2+
3+
LABEL maintainer="devatherock@gmail.com"
4+
LABEL io.github.devatherock.version="0.1.0"
5+
6+
RUN gu install native-image
7+
8+
COPY entry-point.sh /scripts/entry-point.sh
9+
COPY CreateNativeImage.jar /scripts/CreateNativeImage.jar
10+
11+
ENTRYPOINT ["/bin/sh", "/scripts/entry-point.sh"]

README.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
[![CircleCI](https://circleci.com/gh/devatherock/java-to-native.svg?style=svg)](https://circleci.com/gh/devatherock/java-to-native)
2+
[![Docker Pulls](https://img.shields.io/docker/pulls/devatherock/java-to-native.svg)](https://hub.docker.com/r/devatherock/java-to-native/)
3+
[![Docker Image Size](https://img.shields.io/docker/image-size/devatherock/java-to-native.svg?sort=date)](https://hub.docker.com/r/devatherock/java-to-native/)
4+
[![Docker Image Layers](https://img.shields.io/microbadger/layers/devatherock/java-to-native.svg)](https://microbadger.com/images/devatherock/java-to-native)
5+
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6+
# java-to-native
7+
CI plugin to convert a java program into a [graalvm native image](https://www.graalvm.org/docs/reference-manual/native-image/).
8+
For a listing of available options and usage samples, please take a look at the [docs](DOCS.md).
9+
10+
## Usage
11+
12+
Execute from the working directory:
13+
14+
```
15+
docker run --rm \
16+
-e PLUGIN_JAR_NAME=YamlValidator.jar \
17+
-e PLUGIN_CONFIG_FILE=config/graal.yml \
18+
devatherock/java-to-native:0.1.0
19+
```
20+
21+
## Configuration
22+
A YAML configuration file can be provided to supply additional arguments to the `native-image` command and to specify
23+
a list of arguments with which to run the main class or the jar to generate reflection config
24+
25+
### Sample
26+
```yaml
27+
native-image:
28+
build:
29+
# Additional arguments to pass to the native image build
30+
additional-args:
31+
- "--static"
32+
- "--no-fallback"
33+
- "-H:IncludeResourceBundles=net.sourceforge.argparse4j.internal.ArgumentParserImpl"
34+
# If specified, only these arguments will be passed to the image build
35+
#override-args:
36+
# - "--no-server"
37+
agent:
38+
args:
39+
- ['--debug', 'false', '--path', 'src/test/resources/data/valid']
40+
- ['--debug', 'false', '--path', 'src/test/resources/data/invalid']
41+
- ['--debug', 'false', '--path', 'src/test/resources/data/invalid2']
42+
- ['--debug', 'true', '--path', 'src/test/resources/data/valid']
43+
- ['--debug', 'true', '--path', 'src/test/resources/data/invalid']
44+
- ['--debug', 'true', '--path', 'src/test/resources/data/invalid2']
45+
- []
46+
- ['--help']
47+
```

0 commit comments

Comments
 (0)