• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

jreleaser / jreleaser / #524

01 Aug 2025 02:16PM UTC coverage: 48.454% (-0.9%) from 49.315%
#524

push

github

aalmiray
fix(hooks): Inherit matrix and environment from named groups

Relates to #1947

56 of 125 new or added lines in 5 files covered. (44.8%)

450 existing lines in 37 files now uncovered.

25680 of 52999 relevant lines covered (48.45%)

0.48 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

68.29
/core/jreleaser-engine/src/main/java/org/jreleaser/assemblers/NativeImageAssemblerProcessor.java
1
/*
2
 * SPDX-License-Identifier: Apache-2.0
3
 *
4
 * Copyright 2020-2025 The JReleaser authors.
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 *     https://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
package org.jreleaser.assemblers;
19

20
import org.jreleaser.bundle.RB;
21
import org.jreleaser.model.Archive;
22
import org.jreleaser.model.Constants;
23
import org.jreleaser.model.internal.JReleaserContext;
24
import org.jreleaser.model.internal.assemble.NativeImageAssembler;
25
import org.jreleaser.model.spi.assemble.AssemblerProcessingException;
26
import org.jreleaser.mustache.TemplateContext;
27
import org.jreleaser.sdk.command.Command;
28
import org.jreleaser.sdk.command.CommandException;
29
import org.jreleaser.sdk.tool.ToolException;
30
import org.jreleaser.sdk.tool.Upx;
31
import org.jreleaser.util.FileUtils;
32
import org.jreleaser.util.PlatformUtils;
33
import org.jreleaser.version.SemanticVersion;
34

35
import java.io.File;
36
import java.io.IOException;
37
import java.io.InputStream;
38
import java.nio.file.Files;
39
import java.nio.file.Path;
40
import java.util.ArrayList;
41
import java.util.List;
42
import java.util.Properties;
43
import java.util.Set;
44

45
import static java.util.stream.Collectors.joining;
46
import static org.jreleaser.assemblers.AssemblerUtils.copyJars;
47
import static org.jreleaser.assemblers.AssemblerUtils.readJavaVersion;
48
import static org.jreleaser.model.Constants.KEY_ARCHIVE_FORMAT;
49
import static org.jreleaser.util.FileType.EXE;
50
import static org.jreleaser.util.StringUtils.isNotBlank;
51

52
/**
53
 * @author Andres Almiray
54
 * @since 0.2.0
55
 */
56
public class NativeImageAssemblerProcessor extends AbstractAssemblerProcessor<org.jreleaser.model.api.assemble.NativeImageAssembler, NativeImageAssembler> {
57
    private static final String KEY_GRAALVM_VERSION = "GRAALVM_VERSION";
58

59
    public NativeImageAssemblerProcessor(JReleaserContext context) {
60
        super(context);
1✔
61
    }
1✔
62

63
    @Override
64
    protected void doAssemble(TemplateContext props) throws AssemblerProcessingException {
65
        if (!assembler.getGraal().isActiveAndSelected()) return;
1✔
66

67
        // verify graal
68
        Path graalPath = assembler.getGraal().getEffectivePath(context, assembler);
1✔
69
        SemanticVersion javaVersion = SemanticVersion.of(readJavaVersion(graalPath));
1✔
70
        SemanticVersion graalVersion = SemanticVersion.of(readGraalVersion(graalPath));
1✔
71
        context.getLogger().debug(RB.$("assembler.graal.java"), javaVersion, graalPath.toAbsolutePath().toString());
1✔
72
        context.getLogger().debug(RB.$("assembler.graal.graal"), graalVersion, graalPath.toAbsolutePath().toString());
1✔
73

74
        String platform = assembler.getGraal().getPlatform();
1✔
75
        // copy jars to assembly
76
        Path assembleDirectory = props.get(Constants.KEY_DISTRIBUTION_ASSEMBLE_DIRECTORY);
1✔
77
        Path jarsDirectory = assembleDirectory.resolve(JARS_DIRECTORY);
1✔
78
        Path universalJarsDirectory = jarsDirectory.resolve(UNIVERSAL_DIRECTORY);
1✔
79
        context.getLogger().debug(RB.$("assembler.copy.jars"), context.relativizeToBasedir(universalJarsDirectory));
1✔
80
        Set<Path> jars = copyJars(context, assembler, universalJarsDirectory, "");
1✔
81
        Path platformJarsDirectory = jarsDirectory.resolve(platform);
1✔
82
        context.getLogger().debug(RB.$("assembler.copy.jars"), context.relativizeToBasedir(platformJarsDirectory));
1✔
83
        jars.addAll(copyJars(context, assembler, platformJarsDirectory, platform));
1✔
84

85
        // install native-image
86
        installNativeImage(graalPath);
1✔
87
        installComponents(graalPath);
1✔
88

89
        // run native-image
90
        String imageName = assembler.getResolvedImageName(context);
1✔
91
        if (isNotBlank(assembler.getImageNameTransform())) {
1✔
92
            imageName = assembler.getResolvedImageNameTransform(context);
×
93
        }
94

95
        nativeImage(props, assembleDirectory, graalPath, jars, imageName);
1✔
96
    }
1✔
97

98
    private void installNativeImage(Path graalPath) throws AssemblerProcessingException {
99
        Path nativeImageExecutable = graalPath
1✔
100
            .resolve("bin")
1✔
101
            .resolve(PlatformUtils.isWindows() ? "native-image.cmd" : "native-image")
1✔
102
            .toAbsolutePath();
1✔
103

104
        if (!Files.exists(nativeImageExecutable)) {
1✔
105
            Path guExecutable = graalPath
×
106
                .resolve(BIN_DIRECTORY)
×
107
                .resolve(PlatformUtils.isWindows() ? "gu.cmd" : "gu")
×
108
                .toAbsolutePath();
×
109

110
            context.getLogger().debug(RB.$("assembler.graal.install.native.exec"));
×
111
            Command cmd = new Command(guExecutable.toString())
×
112
                .arg("install")
×
113
                .arg("-n")
×
114
                .arg("native-image");
×
115
            context.getLogger().debug(String.join(" ", cmd.getArgs()));
×
116
            executeCommand(cmd);
×
117
        }
118
    }
1✔
119

120
    private void installComponents(Path graalPath) throws AssemblerProcessingException {
121
        Path guExecutable = graalPath
1✔
122
            .resolve(BIN_DIRECTORY)
1✔
123
            .resolve(PlatformUtils.isWindows() ? "gu.cmd" : "gu")
1✔
124
            .toAbsolutePath();
1✔
125

126
        for (String component : assembler.getComponents()) {
1✔
127
            context.getLogger().debug(RB.$("assembler.graal.install.component", component));
×
128
            Command cmd = new Command(guExecutable.toString())
×
129
                .arg("install")
×
130
                .arg("-n")
×
131
                .arg(component);
×
132
            context.getLogger().debug(String.join(" ", cmd.getArgs()));
×
133
            executeCommand(cmd);
×
134
        }
×
135
    }
1✔
136

137
    private void nativeImage(TemplateContext props, Path assembleDirectory, Path graalPath, Set<Path> jars, String imageName) throws AssemblerProcessingException {
138
        String platform = assembler.getGraal().getPlatform();
1✔
139
        String platformReplaced = assembler.getPlatform().applyReplacements(platform);
1✔
140
        String finalImageName = imageName + "-" + platformReplaced;
1✔
141

142
        String executable = assembler.getExecutable();
1✔
143
        if (!assembler.getArchiving().isEnabled()) {
1✔
144
            executable = finalImageName;
×
145
        }
146
        String executableFileName = executable;
1✔
147
        if (PlatformUtils.isWindows()) {
1✔
148
            executableFileName += EXE.extension();
×
149
        }
150
        context.getLogger().info("- {}", finalImageName);
1✔
151

152
        Path image = assembleDirectory.resolve(executableFileName).toAbsolutePath();
1✔
153
        try {
154
            if (Files.exists(image)) {
1✔
155
                Files.deleteIfExists(image);
×
156
            }
157
        } catch (IOException e) {
×
158
            throw new AssemblerProcessingException(RB.$("ERROR_assembler_delete_image", executableFileName), e);
×
159
        }
1✔
160

161
        assembler.getArgs().stream()
1✔
162
            .filter(arg -> arg.startsWith("-H:Name"))
1✔
163
            .findFirst()
1✔
164
            .ifPresent(assembler.getArgs()::remove);
1✔
165

166
        Path nativeImageExecutable = graalPath
1✔
167
            .resolve("bin")
1✔
168
            .resolve(PlatformUtils.isWindows() ? "native-image.cmd" : "native-image")
1✔
169
            .toAbsolutePath();
1✔
170

171
        Command cmd = new Command(nativeImageExecutable.toString(), true)
1✔
172
            .args(assembler.getArgs());
1✔
173

174
        NativeImageAssembler.PlatformCustomizer customizer = assembler.getResolvedPlatformCustomizer();
1✔
175
        cmd.args(customizer.getArgs());
1✔
176

177
        if (isNotBlank(assembler.getJava().getMainModule())) {
1✔
178
            cmd.arg("--module")
×
179
                .arg(assembler.getJava().getMainModule() + "/" + assembler.getJava().getMainClass());
×
180

181
            cmd.arg("--module-path")
×
182
                .arg(jars.stream()
×
183
                    .map(Path::toAbsolutePath)
×
184
                    .map(Path::getParent)
×
185
                    .distinct()
×
186
                    .map(Path::toString)
×
187
                    .map(this::maybeQuote)
×
188
                    .collect(joining(File.pathSeparator)));
×
189

190
        } else {
191
            cmd.arg("-jar")
1✔
192
                .arg(maybeQuote(assembler.getMainJar().getEffectivePath(context, assembler).toAbsolutePath().toString()));
1✔
193

194
            if (!jars.isEmpty()) {
1✔
195
                cmd.arg("-cp")
1✔
196
                    .arg(jars.stream()
1✔
197
                        .map(Path::toAbsolutePath)
1✔
198
                        .map(image.getParent()::relativize)
1✔
199
                        .map(Path::toString)
1✔
200
                        .map(this::maybeQuote)
1✔
201
                        .collect(joining(File.pathSeparator)));
1✔
202
            }
203
        }
204

205
        cmd.arg("-H:Name=" + executable);
1✔
206
        context.getLogger().debug(String.join(" ", cmd.getArgs()));
1✔
207
        executeCommand(image.getParent(), cmd);
1✔
208

209
        if (assembler.getUpx().isEnabled()) {
1✔
210
            upx(image);
1✔
211
        }
212

213
        if (!assembler.getArchiving().isEnabled()) return;
1✔
214

215
        try {
216
            Path tempDirectory = Files.createTempDirectory("jreleaser");
1✔
217
            Path distDirectory = tempDirectory.resolve(finalImageName);
1✔
218
            Files.createDirectories(distDirectory);
1✔
219
            Path binDirectory = distDirectory.resolve(BIN_DIRECTORY);
1✔
220
            Files.createDirectories(binDirectory);
1✔
221
            Files.copy(image, binDirectory.resolve(image.getFileName()));
1✔
222
            FileUtils.copyFiles(context.getLogger(),
1✔
223
                context.getBasedir(),
1✔
224
                distDirectory, path -> path.getFileName().startsWith(LICENSE));
1✔
225
            copyTemplates(context, props, distDirectory);
1✔
226
            copyArtifacts(context, distDirectory, platform, true);
1✔
227
            copyFiles(context, distDirectory);
1✔
228
            copyFileSets(context, distDirectory);
1✔
229
            generateSwidTag(context, distDirectory);
1✔
230

231
            String str = assembler.getGraal().getExtraProperties()
1✔
232
                .getOrDefault(KEY_ARCHIVE_FORMAT, assembler.getArchiveFormat())
1✔
233
                .toString();
1✔
234
            Archive.Format archiveFormat = Archive.Format.of(str);
1✔
235

236
            Path imageArchive = assembleDirectory.resolve(finalImageName + "." + archiveFormat.extension());
1✔
237
            FileUtils.packArchive(tempDirectory, imageArchive, assembler.getOptions().toOptions());
1✔
238

239
            context.getLogger().debug("- {}", imageArchive.getFileName());
1✔
240
        } catch (IOException e) {
×
241
            throw new AssemblerProcessingException(RB.$("ERROR_unexpected_error"), e);
×
242
        }
1✔
243
    }
1✔
244

245
    private void upx(Path image) throws AssemblerProcessingException {
246
        Upx upx = new Upx(context.asImmutable(), assembler.getUpx().getVersion());
1✔
247
        try {
248
            if (!upx.setup()) {
1✔
249
                context.getLogger().warn(RB.$("tool_unavailable", "upx"));
1✔
250
                return;
1✔
251
            }
252
        } catch (ToolException e) {
×
253
            throw new AssemblerProcessingException(e.getMessage(), e);
×
UNCOV
254
        }
×
255

UNCOV
256
        List<String> args = new ArrayList<>(assembler.getUpx().getArgs());
×
UNCOV
257
        args.add(image.getFileName().toString());
×
UNCOV
258
        context.getLogger().info("  upx {}", image.getFileName().toString());
×
259

260
        try {
UNCOV
261
            upx.invoke(image.getParent(), args);
×
262
        } catch (CommandException e) {
×
263
            throw new AssemblerProcessingException(RB.$("ERROR_unexpected_error"), e);
×
UNCOV
264
        }
×
UNCOV
265
    }
×
266

267
    private String readGraalVersion(Path path) throws AssemblerProcessingException {
268
        Path release = path.resolve("release");
1✔
269
        if (!Files.exists(release)) {
1✔
270
            throw new AssemblerProcessingException(RB.$("ERROR_assembler_invalid_graal_release", path.toAbsolutePath()));
×
271
        }
272

273
        try (InputStream in = Files.newInputStream(release)) {
1✔
274
            Properties props = new Properties();
1✔
275
            props.load(in);
1✔
276
            if (props.containsKey(KEY_GRAALVM_VERSION)) {
1✔
277
                String version = props.getProperty(KEY_GRAALVM_VERSION);
1✔
278
                if (version.startsWith("\"") && version.endsWith("\"")) {
1✔
279
                    return version.substring(1, version.length() - 1);
1✔
280
                }
281
                return version;
×
282
            } else {
283
                throw new AssemblerProcessingException(RB.$("ERROR_assembler_invalid_graal_release_file", release.toAbsolutePath()));
×
284
            }
285
        } catch (IOException e) {
1✔
286
            throw new AssemblerProcessingException(RB.$("ERROR_assembler_invalid_graal_release_file", release.toAbsolutePath()), e);
×
287
        }
288
    }
289
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc