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

jreleaser / jreleaser / #510

27 Jul 2025 12:31PM UTC coverage: 45.783% (-3.6%) from 49.39%
#510

push

github

aalmiray
feat(packagers): Stage distribution publication in a fixed directory

Closes #1943

12 of 25 new or added lines in 4 files covered. (48.0%)

2208 existing lines in 190 files now uncovered.

23924 of 52255 relevant lines covered (45.78%)

0.46 hits per line

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

71.26
/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✔
161
            value = resolveTemplate(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) {
UNCOV
305
        return getResolvedPath(context, null, basedir, checkIfExists);
×
306
    }
307

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

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

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

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

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

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

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

357
    public boolean resolvedPathExists() {
358
        return null != resolvedPath && exists(resolvedPath);
1✔
359
    }
360

361
    public Path getResolvedTransform(JReleaserContext context, Path basedir) {
UNCOV
362
        return getResolvedTransform(context, null, basedir);
×
363
    }
364

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

373
    public Path getResolvedTransform(JReleaserContext context) {
374
        return getResolvedTransform(context, (TemplateContext) null);
×
375
    }
376

377
    public Path getResolvedTransform(JReleaserContext context, TemplateContext additionalContext) {
378
        return getResolvedTransform(context, additionalContext, context.getArtifactsDirectory());
1✔
379
    }
380

381
    public Path getResolvedTransform(JReleaserContext context, Distribution distribution) {
382
        return getResolvedTransform(context, null, distribution);
×
383
    }
384

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

393
    public Path getResolvedTransform(JReleaserContext context, Assembler<?> assembler) {
394
        return getResolvedTransform(context, null, assembler);
1✔
395
    }
396

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

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

412
    public Artifact copy() {
413
        Artifact copy = new Artifact();
×
414
        copy.mergeWith(this);
×
415
        return copy;
×
416
    }
417

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

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

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

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

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

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