• 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

72.88
/core/jreleaser-model-impl/src/main/java/org/jreleaser/model/internal/common/Artifact.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.model.internal.common;
19

20
import com.fasterxml.jackson.annotation.JsonIgnore;
21
import org.jreleaser.bundle.RB;
22
import org.jreleaser.model.Active;
23
import org.jreleaser.model.JReleaserException;
24
import org.jreleaser.model.internal.JReleaserContext;
25
import org.jreleaser.model.internal.assemble.Assembler;
26
import org.jreleaser.model.internal.distributions.Distribution;
27
import org.jreleaser.model.internal.util.Artifacts;
28
import org.jreleaser.mustache.TemplateContext;
29
import org.jreleaser.util.Algorithm;
30

31
import java.nio.file.Path;
32
import java.nio.file.Paths;
33
import java.util.Comparator;
34
import java.util.LinkedHashMap;
35
import java.util.LinkedHashSet;
36
import java.util.Map;
37
import java.util.Objects;
38
import java.util.Set;
39
import java.util.stream.Collectors;
40

41
import static java.nio.file.Files.exists;
42
import static java.util.Collections.unmodifiableMap;
43
import static org.jreleaser.model.Constants.OPTIONAL;
44
import static org.jreleaser.model.internal.util.Artifacts.checkAndCopyFile;
45
import static org.jreleaser.model.internal.util.Artifacts.resolveForArtifact;
46
import static org.jreleaser.mustache.Templates.resolveTemplate;
47
import static org.jreleaser.util.StringUtils.isBlank;
48
import static org.jreleaser.util.StringUtils.isNotBlank;
49
import static org.jreleaser.util.StringUtils.isTrue;
50

51
/**
52
 * @author Andres Almiray
53
 * @since 0.1.0
54
 */
55
public final class Artifact extends AbstractArtifact<Artifact> implements Domain, ExtraProperties, Comparable<Artifact> {
1✔
56
    private static final long serialVersionUID = 4590895350785446198L;
57

58
    @JsonIgnore
1✔
59
    private final Map<Algorithm, String> hashes = new LinkedHashMap<>();
60

61
    private String path;
62
    private String transform;
63
    @JsonIgnore
64
    private Path effectivePath;
65
    @JsonIgnore
66
    private Path resolvedPath;
67
    @JsonIgnore
68
    private Path resolvedTransform;
69

70
    @JsonIgnore
1✔
71
    private final org.jreleaser.model.api.common.Artifact immutable = new org.jreleaser.model.api.common.Artifact() {
1✔
72
        private static final long serialVersionUID = -5286060454190216979L;
73

74
        @Override
75
        public Active getActive() {
76
            return Artifact.this.getActive();
×
77
        }
78

79
        @Override
80
        public boolean isEnabled() {
81
            return Artifact.this.isEnabled();
×
82
        }
83

84
        @Override
85
        public boolean isSelected() {
86
            return Artifact.this.isSelected();
×
87
        }
88

89
        @Override
90
        public Path getEffectivePath() {
91
            return effectivePath;
×
92
        }
93

94
        @Override
95
        public Path getResolvedPath() {
96
            return resolvedPath;
×
97
        }
98

99
        @Override
100
        public String getPath() {
101
            return path;
×
102
        }
103

104
        @Override
105
        public String getHash() {
106
            return Artifact.this.getHash();
×
107
        }
108

109
        @Override
110
        public String getHash(Algorithm algorithm) {
111
            return Artifact.this.getHash(algorithm);
×
112
        }
113

114
        @Override
115
        public String getPlatform() {
116
            return Artifact.this.getPlatform();
×
117
        }
118

119
        @Override
120
        public String getTransform() {
121
            return transform;
×
122
        }
123

124
        @Override
125
        public Map<String, Object> asMap(boolean full) {
126
            return unmodifiableMap(Artifact.this.asMap(full));
×
127
        }
128

129
        @Override
130
        public String getPrefix() {
131
            return Artifact.this.prefix();
×
132
        }
133

134
        @Override
135
        public Map<String, Object> getExtraProperties() {
136
            return unmodifiableMap(Artifact.this.getExtraProperties());
×
137
        }
138
    };
139

140
    public org.jreleaser.model.api.common.Artifact asImmutable() {
141
        return immutable;
×
142
    }
143

144
    @Override
145
    public void merge(Artifact source) {
146
        super.merge(source);
1✔
147
        this.effectivePath = merge(this.effectivePath, source.effectivePath);
1✔
148
        this.path = merge(this.path, source.path);
1✔
149
        this.transform = merge(this.transform, source.transform);
1✔
150
        this.resolvedPath = merge(this.resolvedPath, source.resolvedPath);
1✔
151
        this.resolvedTransform = merge(this.resolvedTransform, source.resolvedTransform);
1✔
152

153
        // do not merge
154
        setHashes(source.hashes);
1✔
155
    }
1✔
156

157
    public boolean isOptional(JReleaserContext context) {
158
        Object value = getExtraProperties().get(OPTIONAL);
1✔
159

160
        if (value instanceof CharSequence && value.toString().contains("{{")) {
1✔
NEW
161
            value = resolveTemplate(context.getLogger(), value.toString(), context.fullProps());
×
162
            getExtraProperties().put(OPTIONAL, value);
×
163
        }
164

165
        return isTrue(value);
1✔
166
    }
167

168
    public Path getEffectivePath() {
169
        return effectivePath;
1✔
170
    }
171

172
    public Path getResolvedPath() {
173
        return resolvedPath;
1✔
174
    }
175

176
    public Path getResolvedTransform() {
177
        return resolvedTransform;
×
178
    }
179

180
    public String getPath() {
181
        return path;
1✔
182
    }
183

184
    public void setPath(String path) {
185
        this.path = path;
1✔
186
        this.resolvedPath = null;
1✔
187
        this.effectivePath = null;
1✔
188
        this.resolvedTransform = null;
1✔
189
    }
1✔
190

191
    public String getHash() {
192
        return getHash(Algorithm.SHA_256);
×
193
    }
194

195
    public void setHash(String hash) {
196
        setHash(Algorithm.SHA_256, hash);
×
197
    }
×
198

199
    public String getHash(Algorithm algorithm) {
200
        return hashes.get(algorithm);
1✔
201
    }
202

203
    public void setHash(Algorithm algorithm, String hash) {
204
        if (isNotBlank(hash)) {
1✔
205
            this.hashes.put(algorithm, hash.trim());
1✔
206
        }
207
    }
1✔
208

209
    public Map<Algorithm, String> getHashes() {
210
        return unmodifiableMap(hashes);
×
211
    }
212

213
    void setHashes(Map<Algorithm, String> hashes) {
214
        this.hashes.clear();
1✔
215
        this.hashes.putAll(hashes);
1✔
216
    }
1✔
217

218
    public String getTransform() {
219
        return transform;
1✔
220
    }
221

222
    public void setTransform(String transform) {
223
        this.transform = transform;
1✔
224
    }
1✔
225

226
    @Override
227
    public Map<String, Object> asMap(boolean full) {
228
        Map<String, Object> map = new LinkedHashMap<>();
1✔
229
        map.put("enabled", isEnabled());
1✔
230
        map.put("active", getActive());
1✔
231
        map.put("path", path);
1✔
232
        map.put("transform", transform);
1✔
233
        map.put("platform", getPlatform());
1✔
234
        map.put("extraProperties", getExtraProperties());
1✔
235
        return map;
1✔
236
    }
237

238
    @Override
239
    public boolean equals(Object o) {
240
        if (this == o) return true;
1✔
241
        if (null == o || getClass() != o.getClass()) return false;
1✔
242
        Artifact that = (Artifact) o;
1✔
243
        return path.equals(that.path);
1✔
244
    }
245

246
    @Override
247
    public int hashCode() {
248
        return Objects.hash(path);
1✔
249
    }
250

251
    @Override
252
    public int compareTo(Artifact o) {
253
        if (null == o) return -1;
×
254
        return path.compareTo(o.path);
×
255
    }
256

257
    public void mergeExtraProperties(Map<String, Object> extraProperties) {
258
        extraProperties.forEach((k, v) -> {
1✔
259
            if (!getExtraProperties().containsKey(k)) {
×
260
                getExtraProperties().put(k, v);
×
261
            }
262
        });
×
263
    }
1✔
264

265
    public Path getEffectivePath(JReleaserContext context) {
266
        return getEffectivePath(context, (TemplateContext) null);
1✔
267
    }
268

269
    public Path getEffectivePath(JReleaserContext context, TemplateContext additionalContext) {
270
        if (null == effectivePath) {
1✔
271
            Path rp = getResolvedPath(context, additionalContext);
1✔
272
            Path tp = getResolvedTransform(context, additionalContext);
1✔
273
            effectivePath = checkAndCopyFile(context, rp, tp, isOptional(context));
1✔
274
        }
275
        return effectivePath;
1✔
276
    }
277

278
    public Path getEffectivePath(JReleaserContext context, Distribution distribution) {
279
        return getEffectivePath(context, null, distribution);
1✔
280
    }
281

282
    public Path getEffectivePath(JReleaserContext context, TemplateContext additionalContext, Distribution distribution) {
283
        if (null == effectivePath) {
1✔
284
            Path rp = getResolvedPath(context, additionalContext, distribution);
1✔
285
            Path tp = getResolvedTransform(context, additionalContext, distribution);
1✔
286
            effectivePath = checkAndCopyFile(context, rp, tp, isOptional(context));
1✔
287
        }
288
        return effectivePath;
1✔
289
    }
290

291
    public Path getEffectivePath(JReleaserContext context, Assembler<?> assembler) {
292
        return getEffectivePath(context, null, assembler);
1✔
293
    }
294

295
    public Path getEffectivePath(JReleaserContext context, TemplateContext additionalContext, Assembler<?> assembler) {
296
        if (null == effectivePath) {
1✔
297
            Path rp = getResolvedPath(context, additionalContext, assembler);
1✔
298
            Path tp = getResolvedTransform(context, additionalContext, assembler);
1✔
299
            effectivePath = checkAndCopyFile(context, rp, tp, isOptional(context));
1✔
300
        }
301
        return effectivePath;
1✔
302
    }
303

304
    public Path getResolvedPath(JReleaserContext context, Path basedir, boolean checkIfExists) {
305
        return getResolvedPath(context, null, basedir, checkIfExists);
1✔
306
    }
307

308
    public Path getResolvedPath(JReleaserContext context, TemplateContext additionalContext, Path basedir, boolean checkIfExists) {
309
        if (null == resolvedPath) {
1✔
310
            String configuredPath = path;
1✔
311
            path = resolveForArtifact(path, context, additionalContext, this);
1✔
312
            resolvedPath = basedir.resolve(Paths.get(path)).normalize();
1✔
313
            if (checkIfExists && !isOptional(context) && !exists(resolvedPath)) {
1✔
314
                throw new JReleaserException(RB.$("ERROR_path_does_not_exist_2", configuredPath, context.relativizeToBasedir(resolvedPath)));
×
315
            }
316
        }
317
        return resolvedPath;
1✔
318
    }
319

320
    public Path getResolvedPath(JReleaserContext context) {
321
        return getResolvedPath(context, (TemplateContext) null);
×
322
    }
323

324
    public Path getResolvedPath(JReleaserContext context, TemplateContext additionalContext) {
325
        return getResolvedPath(context, additionalContext, context.getBasedir(), context.getMode().validatePaths());
1✔
326
    }
327

328
    public Path getResolvedPath(JReleaserContext context, Distribution distribution) {
329
        return getResolvedPath(context, null, distribution);
1✔
330
    }
331

332
    public Path getResolvedPath(JReleaserContext context, TemplateContext additionalContext, Distribution distribution) {
333
        if (null == resolvedPath) {
1✔
334
            String configuredPath = path;
1✔
335
            path = Artifacts.resolveForArtifact(path, context, additionalContext, this, distribution);
1✔
336
            resolvedPath = context.getBasedir().resolve(Paths.get(path)).normalize();
1✔
337
            if (context.getMode().validatePaths() && !isOptional(context) && !exists(resolvedPath)) {
1✔
338
                throw new JReleaserException(RB.$("ERROR_path_does_not_exist_2", configuredPath, context.relativizeToBasedir(resolvedPath)));
×
339
            }
340
        }
341
        return resolvedPath;
1✔
342
    }
343

344
    public Path getResolvedPath(JReleaserContext context, Assembler<?> assembler) {
345
        return getResolvedPath(context, null, assembler);
1✔
346
    }
347

348
    public Path getResolvedPath(JReleaserContext context, TemplateContext additionalContext, Assembler<?> assembler) {
349
        if (null == resolvedPath) {
1✔
350
            String configuredPath = path;
1✔
351
            path = Artifacts.resolveForArtifact(path, context, additionalContext, this, assembler);
1✔
352
            resolvedPath = context.getBasedir().resolve(Paths.get(path)).normalize();
1✔
353
            if (context.getMode().validatePaths() && !isOptional(context) && !exists(resolvedPath)) {
1✔
354
                throw new JReleaserException(RB.$("ERROR_path_does_not_exist_2", configuredPath, context.relativizeToBasedir(resolvedPath)));
×
355
            }
356
        }
357
        return resolvedPath;
1✔
358
    }
359

360
    public boolean resolvedPathExists() {
361
        return null != resolvedPath && exists(resolvedPath);
1✔
362
    }
363

364
    public Path getResolvedTransform(JReleaserContext context, Path basedir) {
365
        return getResolvedTransform(context, null, basedir);
1✔
366
    }
367

368
    public Path getResolvedTransform(JReleaserContext context, TemplateContext additionalContext, Path basedir) {
369
        if (null == resolvedTransform && isNotBlank(transform)) {
1✔
370
            transform = resolveForArtifact(transform, context, additionalContext, this);
×
371
            resolvedTransform = basedir.resolve(Paths.get(transform)).normalize();
×
372
        }
373
        return resolvedTransform;
1✔
374
    }
375

376
    public Path getResolvedTransform(JReleaserContext context) {
377
        return getResolvedTransform(context, (TemplateContext) null);
×
378
    }
379

380
    public Path getResolvedTransform(JReleaserContext context, TemplateContext additionalContext) {
381
        return getResolvedTransform(context, additionalContext, context.getArtifactsDirectory());
1✔
382
    }
383

384
    public Path getResolvedTransform(JReleaserContext context, Distribution distribution) {
385
        return getResolvedTransform(context, null, distribution);
×
386
    }
387

388
    public Path getResolvedTransform(JReleaserContext context, TemplateContext additionalContext, Distribution distribution) {
389
        if (null == resolvedTransform && isNotBlank(transform)) {
1✔
390
            transform = Artifacts.resolveForArtifact(transform, context, additionalContext, this, distribution);
×
391
            resolvedTransform = context.getArtifactsDirectory().resolve(Paths.get(transform)).normalize();
×
392
        }
393
        return resolvedTransform;
1✔
394
    }
395

396
    public Path getResolvedTransform(JReleaserContext context, Assembler<?> assembler) {
397
        return getResolvedTransform(context, null, assembler);
1✔
398
    }
399

400
    public Path getResolvedTransform(JReleaserContext context, TemplateContext additionalContext, Assembler<?> assembler) {
401
        if (null == resolvedTransform && isNotBlank(transform)) {
1✔
402
            transform = Artifacts.resolveForArtifact(transform, context, additionalContext, this, assembler);
×
403
            resolvedTransform = context.getArtifactsDirectory().resolve(Paths.get(transform)).normalize();
×
404
        }
405
        return resolvedTransform;
1✔
406
    }
407

408
    public void mergeWith(Artifact other) {
409
        if (this == other) return;
1✔
410
        if (isBlank(this.getPlatform())) this.setPlatform(other.getPlatform());
1✔
411
        if (isBlank(this.transform)) this.transform = other.transform;
1✔
412
        mergeExtraProperties(other.getExtraProperties());
1✔
413
    }
1✔
414

415
    public Artifact copy() {
416
        Artifact copy = new Artifact();
×
417
        copy.mergeWith(this);
×
418
        return copy;
×
419
    }
420

421
    public static Set<Artifact> sortArtifacts(Set<Artifact> artifacts) {
422
        return artifacts.stream()
1✔
423
            .sorted(Artifact.comparatorByPlatform())
1✔
424
            .collect(Collectors.toCollection(LinkedHashSet::new));
1✔
425
    }
426

427
    public static Comparator<Artifact> comparatorByPlatform() {
428
        return (a1, a2) -> {
1✔
429
            String p1 = a1.getPlatform();
1✔
430
            String p2 = a2.getPlatform();
1✔
431
            if (isBlank(p1)) p1 = "";
1✔
432
            if (isBlank(p2)) p2 = "";
1✔
433
            return p1.compareTo(p2);
1✔
434
        };
435
    }
436

437
    public static Artifact of(Path resolvedPath, String platform, Map<String, Object> props) {
438
        Artifact artifact = new Artifact();
×
439
        artifact.path = resolvedPath.toAbsolutePath().toString();
×
440
        artifact.setPlatform(platform);
×
441
        artifact.resolvedPath = resolvedPath;
×
442
        artifact.effectivePath = resolvedPath;
×
443
        artifact.setExtraProperties(props);
×
444
        return artifact;
×
445
    }
446

447
    public static Artifact of(Path resolvedPath, Map<String, Object> props) {
448
        Artifact artifact = new Artifact();
1✔
449
        artifact.path = resolvedPath.toAbsolutePath().toString();
1✔
450
        artifact.resolvedPath = resolvedPath;
1✔
451
        artifact.effectivePath = resolvedPath;
1✔
452
        artifact.setExtraProperties(props);
1✔
453
        return artifact;
1✔
454
    }
455

456
    public static Artifact of(Path resolvedPath) {
457
        Artifact artifact = new Artifact();
1✔
458
        artifact.path = resolvedPath.toAbsolutePath().toString();
1✔
459
        artifact.resolvedPath = resolvedPath;
1✔
460
        artifact.effectivePath = resolvedPath;
1✔
461
        return artifact;
1✔
462
    }
463

464
    public static Artifact of(Path resolvedPath, String platform) {
465
        Artifact artifact = new Artifact();
1✔
466
        artifact.path = resolvedPath.toAbsolutePath().toString();
1✔
467
        artifact.setPlatform(platform);
1✔
468
        artifact.resolvedPath = resolvedPath;
1✔
469
        artifact.effectivePath = resolvedPath;
1✔
470
        return artifact;
1✔
471
    }
472
}
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