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

jreleaser / jreleaser / #558

08 Dec 2025 02:56PM UTC coverage: 48.239% (+0.02%) from 48.215%
#558

push

github

aalmiray
feat(core): warn when a name template cannot be resolved

Closes #1960

Closes #1961

299 of 573 new or added lines in 133 files covered. (52.18%)

4 existing lines in 4 files now uncovered.

26047 of 53996 relevant lines covered (48.24%)

0.48 hits per line

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

68.4
/core/jreleaser-engine/src/main/java/org/jreleaser/assemblers/JlinkAssemblerProcessor.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.internal.JReleaserContext;
23
import org.jreleaser.model.internal.assemble.JlinkAssembler;
24
import org.jreleaser.model.internal.common.Artifact;
25
import org.jreleaser.model.internal.common.JvmOptions;
26
import org.jreleaser.model.spi.assemble.AssemblerProcessingException;
27
import org.jreleaser.mustache.TemplateContext;
28
import org.jreleaser.sdk.command.Command;
29
import org.jreleaser.util.FileUtils;
30
import org.jreleaser.util.PlatformUtils;
31
import org.jreleaser.util.StringUtils;
32
import org.jreleaser.version.SemanticVersion;
33

34
import java.io.File;
35
import java.io.IOException;
36
import java.nio.file.Files;
37
import java.nio.file.Path;
38
import java.nio.file.Paths;
39
import java.util.Arrays;
40
import java.util.Optional;
41
import java.util.Set;
42
import java.util.TreeSet;
43
import java.util.stream.Stream;
44

45
import static java.lang.String.join;
46
import static java.util.stream.Collectors.joining;
47
import static java.util.stream.Collectors.toList;
48
import static java.util.stream.Collectors.toSet;
49
import static org.jreleaser.assemblers.AssemblerUtils.copyJars;
50
import static org.jreleaser.assemblers.AssemblerUtils.readJavaVersion;
51
import static org.jreleaser.model.Constants.KEY_ARCHIVE_FORMAT;
52
import static org.jreleaser.model.Constants.KEY_DISTRIBUTION_ASSEMBLE_DIRECTORY;
53
import static org.jreleaser.model.Constants.KEY_DISTRIBUTION_EXECUTABLE;
54
import static org.jreleaser.model.Constants.KEY_DISTRIBUTION_JAVA_ENVIRONMENT_VARIABLES_LINUX;
55
import static org.jreleaser.model.Constants.KEY_DISTRIBUTION_JAVA_ENVIRONMENT_VARIABLES_OSX;
56
import static org.jreleaser.model.Constants.KEY_DISTRIBUTION_JAVA_ENVIRONMENT_VARIABLES_UNIVERSAL;
57
import static org.jreleaser.model.Constants.KEY_DISTRIBUTION_JAVA_ENVIRONMENT_VARIABLES_UNIX;
58
import static org.jreleaser.model.Constants.KEY_DISTRIBUTION_JAVA_ENVIRONMENT_VARIABLES_WINDOWS;
59
import static org.jreleaser.model.Constants.KEY_DISTRIBUTION_JAVA_JVM_OPTIONS_LINUX;
60
import static org.jreleaser.model.Constants.KEY_DISTRIBUTION_JAVA_JVM_OPTIONS_OSX;
61
import static org.jreleaser.model.Constants.KEY_DISTRIBUTION_JAVA_JVM_OPTIONS_UNIVERSAL;
62
import static org.jreleaser.model.Constants.KEY_DISTRIBUTION_JAVA_JVM_OPTIONS_UNIX;
63
import static org.jreleaser.model.Constants.KEY_DISTRIBUTION_JAVA_JVM_OPTIONS_WINDOWS;
64
import static org.jreleaser.model.Constants.KEY_DISTRIBUTION_JAVA_MAIN_CLASS;
65
import static org.jreleaser.model.Constants.KEY_DISTRIBUTION_JAVA_MAIN_JAR;
66
import static org.jreleaser.model.Constants.KEY_DISTRIBUTION_JAVA_MAIN_MODULE;
67
import static org.jreleaser.mustache.MustacheUtils.passThrough;
68
import static org.jreleaser.mustache.Templates.resolveTemplate;
69
import static org.jreleaser.util.FileType.BAT;
70
import static org.jreleaser.util.FileType.JAR;
71
import static org.jreleaser.util.FileUtils.listFilesAndConsume;
72
import static org.jreleaser.util.FileUtils.listFilesAndProcess;
73
import static org.jreleaser.util.StringUtils.isBlank;
74
import static org.jreleaser.util.StringUtils.isNotBlank;
75

76
/**
77
 * @author Andres Almiray
78
 * @since 0.2.0
79
 */
80
public class JlinkAssemblerProcessor extends AbstractAssemblerProcessor<org.jreleaser.model.api.assemble.JlinkAssembler, JlinkAssembler> {
81
    public JlinkAssemblerProcessor(JReleaserContext context) {
82
        super(context);
1✔
83
    }
1✔
84

85
    @Override
86
    protected void fillAssemblerProperties(TemplateContext props) {
87
        super.fillAssemblerProperties(props);
1✔
88
        if (isNotBlank(assembler.getMainJar().getPath())) {
1✔
89
            props.set(KEY_DISTRIBUTION_JAVA_MAIN_JAR, assembler.getMainJar().getEffectivePath(context, assembler)
1✔
90
                .getFileName());
1✔
91
        } else {
92
            props.set(KEY_DISTRIBUTION_JAVA_MAIN_JAR, "");
×
93
        }
94
        props.set(KEY_DISTRIBUTION_JAVA_MAIN_CLASS, assembler.getJava().getMainClass());
1✔
95
        props.set(KEY_DISTRIBUTION_JAVA_MAIN_MODULE, assembler.getJava().getMainModule());
1✔
96
        JvmOptions jvmOptions = assembler.getJava().getJvmOptions();
1✔
97
        props.set(KEY_DISTRIBUTION_EXECUTABLE, assembler.getExecutable());
1✔
98
        props.set(KEY_DISTRIBUTION_JAVA_JVM_OPTIONS_UNIVERSAL,
1✔
99
            !jvmOptions.getUniversal().isEmpty() ? passThrough(join(" ", jvmOptions.getResolvedUniversal(context))) : "");
1✔
100
        props.set(KEY_DISTRIBUTION_JAVA_JVM_OPTIONS_UNIX,
1✔
101
            !jvmOptions.getUnix().isEmpty() ? passThrough(join(" ", jvmOptions.getResolvedUnix(context))) : "");
1✔
102
        props.set(KEY_DISTRIBUTION_JAVA_JVM_OPTIONS_LINUX,
1✔
103
            !jvmOptions.getLinux().isEmpty() ? passThrough(join(" ", jvmOptions.getResolvedLinux(context))) : "");
1✔
104
        props.set(KEY_DISTRIBUTION_JAVA_JVM_OPTIONS_OSX,
1✔
105
            !jvmOptions.getOsx().isEmpty() ? passThrough(join(" ", jvmOptions.getResolvedOsx(context))) : "");
1✔
106
        props.set(KEY_DISTRIBUTION_JAVA_JVM_OPTIONS_WINDOWS,
1✔
107
            !jvmOptions.getWindows().isEmpty() ? passThrough(join(" ", jvmOptions.getResolvedWindows(context))) : "");
1✔
108
        props.set(KEY_DISTRIBUTION_JAVA_ENVIRONMENT_VARIABLES_UNIVERSAL,
1✔
109
            assembler.getJava().getEnvironmentVariables().getResolvedUniversal(context).entrySet());
1✔
110
        props.set(KEY_DISTRIBUTION_JAVA_ENVIRONMENT_VARIABLES_UNIX,
1✔
111
            assembler.getJava().getEnvironmentVariables().getResolvedUnix(context).entrySet());
1✔
112
        props.set(KEY_DISTRIBUTION_JAVA_ENVIRONMENT_VARIABLES_LINUX,
1✔
113
            assembler.getJava().getEnvironmentVariables().getResolvedLinux(context).entrySet());
1✔
114
        props.set(KEY_DISTRIBUTION_JAVA_ENVIRONMENT_VARIABLES_OSX,
1✔
115
            assembler.getJava().getEnvironmentVariables().getResolvedOsx(context).entrySet());
1✔
116
        props.set(KEY_DISTRIBUTION_JAVA_ENVIRONMENT_VARIABLES_WINDOWS,
1✔
117
            assembler.getJava().getEnvironmentVariables().getResolvedWindows(context).entrySet());
1✔
118
    }
1✔
119

120
    @Override
121
    protected void doAssemble(TemplateContext props) throws AssemblerProcessingException {
122
        // verify jdk
123
        Path jdkPath = assembler.getJdk().getEffectivePath(context, assembler);
1✔
124
        SemanticVersion jdkVersion = SemanticVersion.of(readJavaVersion(jdkPath));
1✔
125
        context.getLogger().debug(RB.$("assembler.jlink.jdk"), jdkVersion, jdkPath.toAbsolutePath().toString());
1✔
126

127
        boolean selectedJdks = false;
1✔
128
        // verify jdks
129
        for (Artifact targetJdk : assembler.getTargetJdks()) {
1✔
130
            if (!targetJdk.isActiveAndSelected()) continue;
1✔
131
            selectedJdks = true;
1✔
132

133
            Path targetJdkPath = targetJdk.getEffectivePath(context, assembler);
1✔
134
            SemanticVersion targetJdkVersion = SemanticVersion.of(readJavaVersion(targetJdkPath));
1✔
135
            context.getLogger().debug(RB.$("assembler.jlink.target"), jdkVersion, targetJdkPath.toAbsolutePath().toString());
1✔
136

137
            if (jdkVersion.getMajor() != targetJdkVersion.getMajor()) {
1✔
138
                throw new AssemblerProcessingException(RB.$("ERROR_jlink_target_not_compatible", targetJdkVersion, jdkVersion));
×
139
            }
140
        }
1✔
141

142
        if (!selectedJdks) return;
1✔
143

144
        Path assembleDirectory = props.get(KEY_DISTRIBUTION_ASSEMBLE_DIRECTORY);
1✔
145
        Path inputsDirectory = assembleDirectory.resolve(INPUTS_DIRECTORY);
1✔
146

147
        // copy templates
148
        copyTemplates(context, props, inputsDirectory);
1✔
149

150
        // run jlink x jdk
151
        String imageName = assembler.getResolvedImageName(context);
1✔
152
        if (isNotBlank(assembler.getImageNameTransform())) {
1✔
153
            imageName = assembler.getResolvedImageNameTransform(context);
×
154
        }
155

156
        boolean hasJavaArchive = assembler.getJavaArchive().isSet();
1✔
157

158
        if (hasJavaArchive) {
1✔
159
            String configuredPath = assembler.getJavaArchive().getPath();
×
NEW
160
            String archiveFile = resolveTemplate(context.getLogger(), configuredPath, props);
×
161
            Path archivePath = context.getBasedir().resolve(Paths.get(archiveFile));
×
162
            if (!Files.exists(archivePath)) {
×
163
                throw new AssemblerProcessingException(RB.$("ERROR_path_does_not_exist_2", configuredPath, archivePath));
×
164
            }
165

166
            Path archiveDirectory = inputsDirectory.resolve(ARCHIVE_DIRECTORY);
×
167
            try {
168
                FileUtils.unpackArchive(archivePath, archiveDirectory, true);
×
169
            } catch (IOException e) {
×
170
                throw new AssemblerProcessingException(RB.$("ERROR_unexpected_error"), e);
×
171
            }
×
172
        }
173

174
        // copy jars to assembly
175
        Path jarsDirectory = inputsDirectory.resolve(JARS_DIRECTORY);
1✔
176
        Path universalJarsDirectory = jarsDirectory.resolve(UNIVERSAL_DIRECTORY);
1✔
177

178
        if (hasJavaArchive) {
1✔
NEW
179
            String libDirectoryName = resolveTemplate(context.getLogger(), assembler.getJavaArchive().getLibDirectoryName(), props);
×
180
            Path libPath = inputsDirectory.resolve(ARCHIVE_DIRECTORY).resolve(libDirectoryName);
×
181
            try {
182
                FileUtils.copyFiles(context.getLogger(), libPath, universalJarsDirectory);
×
183
            } catch (IOException e) {
×
184
                throw new AssemblerProcessingException(RB.$("ERROR_unexpected_error"), e);
×
185
            }
×
186
        }
187
        context.getLogger().debug(RB.$("assembler.copy.jars"), context.relativizeToBasedir(universalJarsDirectory));
1✔
188
        copyJars(context, assembler, universalJarsDirectory, "");
1✔
189

190
        Optional<String> compress = assembler.getArgs().stream()
1✔
191
            .filter(arg -> arg.contains("--compress") || arg.startsWith("-c=") || arg.startsWith("-c "))
1✔
192
            .findFirst();
1✔
193
        if (!compress.isPresent()) {
1✔
194
            if (jdkVersion.getMajor() >= 21) {
1✔
195
                assembler.getArgs().add("--compress");
×
196
                assembler.getArgs().add("zip-9");
×
197
            } else {
198
                assembler.getArgs().add("--compress=2");
1✔
199
            }
200
        }
201

202
        for (Artifact targetJdk : assembler.getTargetJdks()) {
1✔
203
            if (!targetJdk.isActiveAndSelected()) continue;
1✔
204

205
            String platform = targetJdk.getPlatform();
1✔
206
            Path platformJarsDirectory = jarsDirectory.resolve(platform);
1✔
207
            context.getLogger().debug(RB.$("assembler.copy.jars"), context.relativizeToBasedir(platformJarsDirectory));
1✔
208
            copyJars(context, assembler, platformJarsDirectory, platform);
1✔
209

210
            // resolve module names
211
            Set<String> moduleNames = new TreeSet<>(resolveModuleNames(context, jdkPath, jarsDirectory, platform, props));
1✔
212
            context.getLogger().debug(RB.$("assembler.resolved.module.names"), moduleNames);
1✔
213
            if (moduleNames.isEmpty()) {
1✔
214
                throw new AssemblerProcessingException(RB.$("ERROR_assembler_no_module_names"));
×
215
            }
216
            moduleNames.addAll(assembler.getAdditionalModuleNames().stream()
1✔
217
                .map(arg -> resolveTemplate(context.getLogger(), arg, props))
1✔
218
                .collect(toList()));
1✔
219
            String moduleName = resolveTemplate(context.getLogger(), assembler.getJava().getMainModule(), props);
1✔
220
            if (isNotBlank(moduleName)) {
1✔
221
                moduleNames.add(moduleName);
×
222
            }
223
            context.getLogger().debug(RB.$("assembler.module.names"), moduleNames);
1✔
224

225
            String str = targetJdk.getExtraProperties()
1✔
226
                .getOrDefault(KEY_ARCHIVE_FORMAT, assembler.getArchiveFormat())
1✔
227
                .toString();
1✔
228
            Archive.Format archiveFormat = Archive.Format.of(str);
1✔
229

230
            jlink(props, assembleDirectory, jdkPath, targetJdk, moduleNames, imageName, archiveFormat);
1✔
231
        }
1✔
232
    }
1✔
233

234
    private void jlink(TemplateContext props, Path assembleDirectory, Path jdkPath, Artifact targetJdk, Set<String> moduleNames, String imageName, Archive.Format archiveFormat) throws AssemblerProcessingException {
235
        String platform = targetJdk.getPlatform();
1✔
236
        String platformReplaced = assembler.getPlatform().applyReplacements(platform);
1✔
237
        String finalImageName = imageName + "-" + platformReplaced;
1✔
238
        context.getLogger().info("- {}", finalImageName);
1✔
239

240
        boolean hasJavaArchive = assembler.getJavaArchive().isSet();
1✔
241
        Path inputsDirectory = assembleDirectory.resolve(INPUTS_DIRECTORY);
1✔
242
        Path archiveDirectory = inputsDirectory.resolve(ARCHIVE_DIRECTORY);
1✔
243
        Path jarsDirectory = inputsDirectory.resolve(JARS_DIRECTORY);
1✔
244
        Path workDirectory = assembleDirectory.resolve(WORK_DIRECTORY + "-" + platform);
1✔
245
        Path imageDirectory = workDirectory.resolve(finalImageName).toAbsolutePath();
1✔
246
        try {
247
            FileUtils.deleteFiles(imageDirectory);
1✔
248
        } catch (IOException e) {
×
249
            throw new AssemblerProcessingException(RB.$("ERROR_assembler_delete_image", finalImageName), e);
×
250
        }
1✔
251

252
        // jlink it
253
        String moduleName = resolveTemplate(context.getLogger(), assembler.getJava().getMainModule(), props);
1✔
254
        String mainClass = resolveTemplate(context.getLogger(), assembler.getJava().getMainClass(), props);
1✔
255
        String modulePath = maybeQuote(targetJdk.getEffectivePath(context, assembler).resolve("jmods").toAbsolutePath().toString());
1✔
256
        if (isNotBlank(moduleName) || assembler.isCopyJars()) {
1✔
257
            modulePath += File.pathSeparator + maybeQuote(jarsDirectory
1✔
258
                .resolve(UNIVERSAL_DIRECTORY)
1✔
259
                .toAbsolutePath().toString());
1✔
260

261
            try {
262
                Path platformJarsDirectory = jarsDirectory.resolve(platform).toAbsolutePath();
1✔
263
                if (listFilesAndProcess(platformJarsDirectory, Stream::count).orElse(0L) > 1) {
1✔
264
                    modulePath += File.pathSeparator + maybeQuote(platformJarsDirectory.toString());
×
265
                }
266
            } catch (IOException e) {
×
267
                throw new AssemblerProcessingException(RB.$("ERROR_unexpected_error", e));
×
268
            }
1✔
269
        }
270

271
        Path jlinkExecutable = jdkPath
1✔
272
            .resolve(BIN_DIRECTORY)
1✔
273
            .resolve(PlatformUtils.isWindows() ? "jlink.exe" : "jlink")
1✔
274
            .toAbsolutePath();
1✔
275

276
        Command cmd = new Command(jlinkExecutable.toString(), true)
1✔
277
            .args(assembler.getArgs().stream()
1✔
278
                .map(arg -> resolveTemplate(context.getLogger(), arg, props))
1✔
279
                .collect(toList()))
1✔
280
            .arg("--module-path")
1✔
281
            .arg(modulePath)
1✔
282
            .arg("--add-modules")
1✔
283
            .arg(String.join(",", moduleNames));
1✔
284
        if (isNotBlank(moduleName)) {
1✔
285
            cmd.arg("--launcher")
×
286
                .arg(assembler.getExecutable() + "=" + moduleName + "/" + mainClass);
×
287
        }
288
        cmd.arg("--output")
1✔
289
            .arg(maybeQuote(imageDirectory.toString()));
1✔
290

291
        context.getLogger().debug(String.join(" ", cmd.getArgs()));
1✔
292
        executeCommand(cmd);
1✔
293

294
        if (isBlank(moduleName)) {
1✔
295
            // non modular
296
            // copy jars & launcher
297

298
            if (assembler.isCopyJars()) {
1✔
299
                Path outputJarsDirectory = imageDirectory.resolve(JARS_DIRECTORY);
1✔
300

301
                try {
302
                    Files.createDirectory(outputJarsDirectory);
1✔
303
                    FileUtils.copyFiles(context.getLogger(),
1✔
304
                        jarsDirectory.resolve(UNIVERSAL_DIRECTORY),
1✔
305
                        outputJarsDirectory);
306
                    FileUtils.copyFiles(context.getLogger(),
1✔
307
                        jarsDirectory.resolve(platform),
1✔
308
                        outputJarsDirectory);
309
                } catch (IOException e) {
×
310
                    throw new AssemblerProcessingException(RB.$("ERROR_assembler_copy_jars",
×
311
                        context.relativizeToBasedir(outputJarsDirectory)), e);
×
312
                }
1✔
313
            }
314

315
            Path binDirectory = imageDirectory.resolve(BIN_DIRECTORY);
1✔
316
            try {
317
                Files.createDirectories(binDirectory);
1✔
318

319
                Optional<Set<Path>> launchers = listFilesAndProcess(inputsDirectory.resolve(BIN_DIRECTORY), files -> files.collect(toSet()));
1✔
320
                if (launchers.isPresent()) {
1✔
321
                    for (Path srcLauncher : launchers.get()) {
1✔
322
                        Path destLauncher = binDirectory.resolve(srcLauncher.getFileName());
1✔
323
                        Files.copy(srcLauncher, destLauncher);
1✔
324
                        FileUtils.grantExecutableAccess(destLauncher);
1✔
325
                    }
1✔
326
                }
327
            } catch (IOException e) {
×
328
                throw new AssemblerProcessingException(RB.$("ERROR_assembler_copy_launcher",
×
329
                    context.relativizeToBasedir(binDirectory)), e);
×
330
            }
1✔
331
        }
332

333
        try {
334
            Path imageArchive = assembleDirectory.resolve(finalImageName + "." + archiveFormat.extension());
1✔
335
            FileUtils.copyFiles(context.getLogger(),
1✔
336
                context.getBasedir(),
1✔
337
                imageDirectory, path -> path.getFileName().startsWith(LICENSE));
1✔
338
            // copy all templates, filter existing launchers
339
            FileUtils.copyFiles(context.getLogger(), inputsDirectory, imageDirectory, path -> {
1✔
340
                if (!BIN_DIRECTORY.equals(path.getParent().getFileName().toString())) return true;
×
341
                String fileName = path.getFileName().toString();
×
342
                // don't copy jars twice
343
                if (fileName.endsWith(JAR.extension()) && JARS_DIRECTORY.equals(path.getParent().getParent().getFileName().toString())) {
×
344
                    return false;
×
345
                }
346
                Path candidateBinary = imageDirectory.resolve(BIN_DIRECTORY).resolve(fileName);
×
347
                return !Files.exists(candidateBinary);
×
348
            });
349

350
            if (hasJavaArchive) {
1✔
NEW
351
                String libDirectory = resolveTemplate(context.getLogger(), assembler.getJavaArchive().getLibDirectoryName(), props);
×
352
                String archivePathName = archiveDirectory.toString();
×
353
                FileUtils.copyFiles(context.getLogger(),
×
354
                    archiveDirectory,
355
                    imageDirectory, path -> {
356
                        String fileName = path.getFileName().toString();
×
357
                        if (!fileName.endsWith(JAR.extension())) return true;
×
358
                        return !path.getParent().toString()
×
359
                            .substring(archivePathName.length())
×
360
                            .contains(libDirectory);
×
361
                    });
362
            }
363

364
            copyArtifacts(context, imageDirectory, platform, true);
1✔
365
            copyFiles(context, imageDirectory);
1✔
366
            copyFileSets(context, imageDirectory);
1✔
367
            generateSwidTag(context, imageDirectory);
1✔
368

369
            FileUtils.packArchive(workDirectory, imageArchive, assembler.getOptions().toOptions());
1✔
370

371
            context.getLogger().debug("- {}", imageArchive.getFileName());
1✔
372
        } catch (IOException e) {
×
373
            throw new AssemblerProcessingException(RB.$("ERROR_unexpected_error"), e);
×
374
        }
1✔
375
    }
1✔
376

377
    private Set<String> resolveModuleNames(JReleaserContext context, Path jdkPath, Path jarsDirectory, String platform, TemplateContext props) throws AssemblerProcessingException {
378
        if (!assembler.getModuleNames().isEmpty()) {
1✔
379
            return assembler.getModuleNames().stream()
×
NEW
380
                .map(arg -> resolveTemplate(context.getLogger(), arg, props))
×
381
                .collect(toSet());
×
382
        }
383

384
        Path jdepsExecutable = jdkPath
1✔
385
            .resolve(BIN_DIRECTORY)
1✔
386
            .resolve(PlatformUtils.isWindows() ? "jdeps.exe" : "jdeps")
1✔
387
            .toAbsolutePath();
1✔
388

389
        Command cmd = new Command(jdepsExecutable.toAbsolutePath().toString());
1✔
390
        String multiRelease = assembler.getJdeps().getMultiRelease();
1✔
391
        if (isNotBlank(multiRelease)) {
1✔
392
            cmd.arg("--multi-release")
1✔
393
                .arg(multiRelease);
1✔
394
        }
395
        if (assembler.getJdeps().isIgnoreMissingDeps()) {
1✔
396
            cmd.arg("--ignore-missing-deps");
1✔
397
        }
398
        cmd.arg("--print-module-deps");
1✔
399

400
        String moduleName = assembler.getJava().getMainModule();
1✔
401
        if (isNotBlank(moduleName)) {
1✔
402
            cmd.arg("--module")
×
403
                .arg(moduleName)
×
404
                .arg("--module-path");
×
405
            calculateJarPath(jarsDirectory, platform, cmd, true);
×
406
        } else if (!assembler.getJdeps().getTargets().isEmpty()) {
1✔
407
            cmd.arg("--class-path");
×
408
            if (assembler.getJdeps().isUseWildcardInPath()) {
×
409
                cmd.arg(UNIVERSAL_DIRECTORY +
×
410
                    File.separator + "*" +
411
                    File.pathSeparator +
412
                    platform +
413
                    File.separator + "*");
414
            } else {
415
                calculateJarPath(jarsDirectory, platform, cmd, true);
×
416
            }
417

418
            assembler.getJdeps().getTargets().stream()
×
NEW
419
                .map(target -> resolveTemplate(context.getLogger(), target, props))
×
420
                .filter(StringUtils::isNotBlank)
×
421
                .map(AssemblerUtils::maybeAdjust)
×
422
                .map(context::relativizeToBasedir)
×
423
                .map(p -> p.toAbsolutePath().normalize().toString())
×
424
                .forEach(cmd::arg);
×
425
        } else {
426
            calculateJarPath(jarsDirectory, platform, cmd, false);
1✔
427
        }
428

429
        context.getLogger().debug(String.join(" ", cmd.getArgs()));
1✔
430
        Command.Result result = executeCommand(jarsDirectory, cmd);
1✔
431

432
        String output = result.getOut();
1✔
433
        long lineCount = Arrays.stream(output.split(System.lineSeparator()))
1✔
434
            .map(String::trim)
1✔
435
            .count();
1✔
436

437
        if (lineCount == 1 && isNotBlank(output)) {
1✔
438
            return Arrays.stream(output.split(",")).collect(toSet());
1✔
439
        }
440

441
        throw new AssemblerProcessingException(RB.$("ERROR_assembler_jdeps_error", output));
×
442
    }
443

444
    private void calculateJarPath(Path jarsDirectory, String platform, Command cmd, boolean join) throws AssemblerProcessingException {
445
        try {
446
            if (join) {
1✔
447
                StringBuilder pathBuilder = new StringBuilder();
×
448

449
                listFilesAndProcess(jarsDirectory.resolve(UNIVERSAL_DIRECTORY), files ->
×
450
                    files.map(Path::toAbsolutePath)
×
451
                        .map(Object::toString)
×
452
                        .collect(joining(File.pathSeparator)))
×
453
                    .ifPresent(pathBuilder::append);
×
454

455
                listFilesAndProcess(jarsDirectory.resolve(platform), files ->
×
456
                    files.map(Path::toAbsolutePath)
×
457
                        .map(Object::toString)
×
458
                        .collect(joining(File.pathSeparator)))
×
459
                    .ifPresent(platformSpecific -> {
×
460
                        if (isNotBlank(platformSpecific)) {
×
461
                            pathBuilder.append(File.pathSeparator)
×
462
                                .append(platformSpecific);
×
463
                        }
464
                    });
×
465

466
                cmd.arg(pathBuilder.toString());
×
467
            } else {
×
468
                listFilesAndConsume(jarsDirectory.resolve(UNIVERSAL_DIRECTORY), files ->
1✔
469
                    files.map(Path::toAbsolutePath)
1✔
470
                        .map(Object::toString)
1✔
471
                        .forEach(cmd::arg));
1✔
472

473
                listFilesAndConsume(jarsDirectory.resolve(platform), files ->
1✔
474
                    files.map(Path::toAbsolutePath)
1✔
475
                        .map(Object::toString)
1✔
476
                        .forEach(cmd::arg));
1✔
477
            }
478
        } catch (IOException e) {
×
479
            throw new AssemblerProcessingException(RB.$("ERROR_assembler_jdeps_error", e.getMessage(), e));
×
480
        }
1✔
481
    }
1✔
482

483
    @Override
484
    protected Path resolveOutputFile(TemplateContext props, Path targetDirectory, String fileName) throws AssemblerProcessingException {
485
        String executableName = assembler.getExecutable();
1✔
486

487
        return "bin/launcher.bat".equals(fileName) ?
1✔
488
            targetDirectory.resolve(BIN_DIRECTORY).resolve(executableName.concat(BAT.extension())) :
1✔
489
            "bin/launcher".equals(fileName) ?
1✔
490
                targetDirectory.resolve(BIN_DIRECTORY).resolve(executableName) :
1✔
491
                targetDirectory.resolve(fileName);
×
492
    }
493
}
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