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

jreleaser / jreleaser / #500

30 Jun 2025 04:44PM UTC coverage: 49.36% (+0.004%) from 49.356%
#500

push

github

aalmiray
fix(core): Do not display empty or blank environment vars

Fixes #1913

4 of 4 new or added lines in 2 files covered. (100.0%)

11 existing lines in 1 file now uncovered.

25731 of 52129 relevant lines covered (49.36%)

0.49 hits per line

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

82.39
/core/jreleaser-model-impl/src/main/java/org/jreleaser/model/internal/environment/Environment.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.environment;
19

20
import com.fasterxml.jackson.annotation.JsonIgnore;
21
import org.jreleaser.bundle.RB;
22
import org.jreleaser.config.JReleaserConfigLoader;
23
import org.jreleaser.config.JReleaserConfigParser;
24
import org.jreleaser.model.internal.JReleaserContext;
25
import org.jreleaser.model.internal.common.AbstractModelObject;
26
import org.jreleaser.model.internal.common.Domain;
27
import org.jreleaser.util.Env;
28

29
import java.io.File;
30
import java.io.IOException;
31
import java.io.InputStream;
32
import java.io.Serializable;
33
import java.nio.file.Files;
34
import java.nio.file.Path;
35
import java.nio.file.Paths;
36
import java.util.LinkedHashMap;
37
import java.util.Map;
38
import java.util.Optional;
39
import java.util.Properties;
40
import java.util.ServiceLoader;
41
import java.util.Set;
42
import java.util.TreeSet;
43

44
import static java.nio.file.Files.newInputStream;
45
import static java.util.Collections.unmodifiableMap;
46
import static org.jreleaser.model.Constants.DEFAULT_GIT_REMOTE;
47
import static org.jreleaser.model.Constants.JRELEASER_USER_HOME;
48
import static org.jreleaser.model.Constants.XDG_CONFIG_HOME;
49
import static org.jreleaser.util.Env.JRELEASER_ENV_PREFIX;
50
import static org.jreleaser.util.Env.JRELEASER_SYS_PREFIX;
51
import static org.jreleaser.util.Env.envKey;
52
import static org.jreleaser.util.StringUtils.getPropertyNameForLowerCaseHyphenSeparatedName;
53
import static org.jreleaser.util.StringUtils.isBlank;
54
import static org.jreleaser.util.StringUtils.isNotBlank;
55

56
/**
57
 * @author Andres Almiray
58
 * @since 0.1.0
59
 */
60
public final class Environment extends AbstractModelObject<Environment> implements Domain {
1✔
61
    private static final long serialVersionUID = 4554098923129885325L;
62

63
    private final Map<String, Object> properties = new LinkedHashMap<>();
1✔
64
    @JsonIgnore
1✔
65
    private final Map<String, Object> sourcedProperties = new LinkedHashMap<>();
66
    @JsonIgnore
67
    private PropertiesSource propertiesSource;
68
    private String variables;
69
    @JsonIgnore
70
    private Properties vars;
71
    @JsonIgnore
72
    private Path propertiesFile;
73

74
    @JsonIgnore
1✔
75
    private final org.jreleaser.model.api.environment.Environment immutable = new org.jreleaser.model.api.environment.Environment() {
1✔
76
        private static final long serialVersionUID = -7287090119869371299L;
77

78
        @Override
79
        public Properties getVars() {
80
            return vars;
1✔
81
        }
82

83
        @Override
84
        public String getVariables() {
85
            return variables;
×
86
        }
87

88
        @Override
89
        public Map<String, Object> getProperties() {
90
            return unmodifiableMap(properties);
×
91
        }
92

93
        @Override
94
        public Map<String, Object> asMap(boolean full) {
95
            return unmodifiableMap(Environment.this.asMap(full));
×
96
        }
97
    };
98

99
    public org.jreleaser.model.api.environment.Environment asImmutable() {
100
        return immutable;
1✔
101
    }
102

103
    @Override
104
    public void merge(Environment source) {
105
        this.variables = merge(this.variables, source.variables);
1✔
106
        setProperties(merge(this.properties, source.properties));
1✔
107
        setPropertiesSource(merge(this.propertiesSource, source.propertiesSource));
1✔
108
    }
1✔
109

110
    public String resolve(String key) {
111
        return env(key, Env.sys(key, ""));
1✔
112
    }
113

114
    public String resolve(String key, String value) {
115
        return env(key, Env.sys(key, value));
1✔
116
    }
117

118
    public String resolveOrDefault(String key, String value, String defaultValue) {
119
        String result = env(key, Env.sys(key, value));
1✔
120
        return isNotBlank(result) ? result : defaultValue;
1✔
121
    }
122

123
    private String env(String key, String value) {
124
        if (isNotBlank(value)) {
1✔
125
            return value;
×
126
        }
127
        return getVariable(envKey(key));
1✔
128
    }
129

130
    public Properties getVars() {
131
        return vars;
1✔
132
    }
133

134
    public String getVariable(String key) {
135
        return vars.getProperty(Env.envKey(key));
1✔
136
    }
137

138
    public boolean isSet() {
139
        return isNotBlank(variables) ||
1✔
140
            !properties.isEmpty();
1✔
141
    }
142

143
    public PropertiesSource getPropertiesSource() {
144
        return propertiesSource;
×
145
    }
146

147
    public void setPropertiesSource(PropertiesSource propertiesSource) {
148
        this.propertiesSource = propertiesSource;
1✔
149
        if (null != this.propertiesSource) {
1✔
150
            sourcedProperties.putAll(propertiesSource.getProperties());
1✔
151
        }
152
    }
1✔
153

154
    public String getVariables() {
155
        return variables;
×
156
    }
157

158
    public void setVariables(String variables) {
159
        this.variables = variables;
×
160
    }
×
161

162
    public Map<String, Object> getProperties() {
163
        return properties;
1✔
164
    }
165

166
    public void setProperties(Map<String, Object> properties) {
167
        this.properties.putAll(properties);
1✔
168
    }
1✔
169

170
    public Map<String, Object> getSourcedProperties() {
171
        return sourcedProperties;
1✔
172
    }
173

174
    public Path getPropertiesFile() {
175
        return propertiesFile;
1✔
176
    }
177

178
    @Override
179
    public Map<String, Object> asMap(boolean full) {
180
        Map<String, Object> map = new LinkedHashMap<>();
1✔
181
        map.put("variables", variables);
1✔
182
        map.put("properties", properties);
1✔
183

184
        return map;
1✔
185
    }
186

187
    public void initProps(JReleaserContext context) {
188
        if (null == vars) {
1✔
189
            vars = new Properties();
1✔
190

191
            Path configDirectory = null;
1✔
192

193
            String home = System.getenv(XDG_CONFIG_HOME);
1✔
194
            if (isNotBlank(home) && Files.exists(Paths.get(home).resolve("jreleaser"))) {
1✔
195
                configDirectory = Paths.get(home).resolve("jreleaser");
×
196
            }
197

198
            if (null == configDirectory) {
1✔
199
                home = System.getenv(JRELEASER_USER_HOME);
1✔
200
                if (isBlank(home)) {
1✔
201
                    home = System.getProperty("user.home") + File.separator + ".jreleaser";
×
202
                }
203
                configDirectory = Paths.get(home);
1✔
204
            }
205

206
            loadVariables(context, resolveConfigFileAt(configDirectory)
1✔
207
                .orElse(configDirectory.resolve("config.properties")));
1✔
208

209
            if (isNotBlank(variables)) {
1✔
210
                loadVariables(context, context.getBasedir().resolve(variables.trim()));
×
211
            }
212

213
            Path envFilePath = context.getBasedir().resolve(".env");
1✔
214
            if (Files.exists(envFilePath)) {
1✔
215
                loadVariables(context, envFilePath);
×
216
            }
217

218
            // env vars
219
            Set<String> keyNames = new TreeSet<>();
1✔
220
            Properties envVars = new Properties();
1✔
221
            System.getenv().forEach((k, v) -> {
1✔
222
                if (k.startsWith(JRELEASER_ENV_PREFIX) && isNotBlank(v)) {
1✔
223
                    keyNames.add(k);
1✔
224
                    envVars.put(k, v);
1✔
225
                }
226
            });
1✔
227
            if (System.getenv().containsKey(envKey(DEFAULT_GIT_REMOTE))) {
1✔
UNCOV
228
                keyNames.add(envKey(DEFAULT_GIT_REMOTE));
×
229
            }
230
            if (!keyNames.isEmpty()) {
1✔
231
                context.getLogger().debug(RB.$("environment.variables.env"));
1✔
232
                keyNames.forEach(message -> context.getLogger().debug("  " + message));
1✔
233
            }
234

235
            // system props
236
            keyNames.clear();
1✔
237
            System.getProperties().stringPropertyNames().forEach(k -> {
1✔
238
                if (k.startsWith(JRELEASER_SYS_PREFIX)) keyNames.add(k);
1✔
239
            });
1✔
240
            if (!keyNames.isEmpty()) {
1✔
241
                context.getLogger().debug(RB.$("environment.system.properties"));
1✔
242
                keyNames.forEach(message -> context.getLogger().debug("  " + message));
1✔
243
            }
244

245
            // merge keyNames
246
            Properties merged = new Properties();
1✔
247
            merged.putAll(envVars);
1✔
248
            merged.putAll(this.vars);
1✔
249
            this.vars.clear();
1✔
250
            this.vars.putAll(merged);
1✔
251

252
            if (null != propertiesSource) {
1✔
253
                sourcedProperties.putAll(propertiesSource.getProperties());
1✔
254
            }
255
        }
256
    }
1✔
257

258
    private void loadVariables(JReleaserContext context, Path file) {
259
        propertiesFile = file;
1✔
260
        context.getLogger().info(RB.$("environment.load.variables"), file.toAbsolutePath());
1✔
261
        if (Files.exists(file)) {
1✔
262
            try {
263
                Properties p = new Properties();
1✔
264
                if (file.getFileName().toString().endsWith(".properties") ||
1✔
265
                    file.getFileName().toString().equals(".env")) {
1✔
UNCOV
266
                    try (InputStream in = newInputStream(file)) {
×
UNCOV
267
                        p.load(in);
×
268
                    }
269
                } else {
270
                    p.putAll(JReleaserConfigLoader.loadProperties(file));
1✔
271
                }
272
                vars.putAll(p);
1✔
273

274
                Set<String> keyNames = new TreeSet<>();
1✔
275
                p.stringPropertyNames().stream()
1✔
276
                    .filter(k -> k.startsWith(JRELEASER_ENV_PREFIX)).
1✔
277
                    forEach(keyNames::add);
1✔
278

279
                if (!keyNames.isEmpty()) {
1✔
280
                    context.getLogger().debug(RB.$("environment.variables.file", file.getFileName().toString()));
1✔
281
                    keyNames.forEach(message -> context.getLogger().debug("  " + message));
1✔
282
                }
UNCOV
283
            } catch (IOException e) {
×
UNCOV
284
                context.getLogger().debug(RB.$("environment.variables.load.error"), file.toAbsolutePath(), e);
×
285
            }
1✔
286
        } else {
UNCOV
287
            context.getLogger().warn(RB.$("environment.variables.source.missing"), file.toAbsolutePath());
×
288
        }
289
    }
1✔
290

291
    private Optional<Path> resolveConfigFileAt(Path directory) {
292
        ServiceLoader<JReleaserConfigParser> parsers = ServiceLoader.load(JReleaserConfigParser.class,
1✔
293
            JReleaserConfigParser.class.getClassLoader());
1✔
294

295
        for (JReleaserConfigParser parser : parsers) {
1✔
296
            Path file = directory.resolve("config." + parser.getPreferredFileExtension());
1✔
297
            if (Files.exists(file)) {
1✔
298
                return Optional.of(file);
1✔
299
            }
300
        }
1✔
301

UNCOV
302
        return Optional.empty();
×
303
    }
304

305
    public boolean getBooleanProperty(String key) {
306
        boolean keyInProperties = properties.containsKey(key) && Boolean.parseBoolean(String.valueOf(properties.get(key)));
1✔
307
        boolean keyInSourcedProperties = sourcedProperties.containsKey(key) && Boolean.parseBoolean(String.valueOf(sourcedProperties.get(key)));
1✔
308
        return keyInProperties || keyInSourcedProperties;
1✔
309
    }
310

311
    public interface PropertiesSource extends Serializable {
312
        Map<String, String> getProperties();
313
    }
314

315
    public abstract static class AbstractPropertiesSource implements PropertiesSource {
1✔
316
        private static final long serialVersionUID = 9102569253517657171L;
317

318
        @Override
319
        public Map<String, String> getProperties() {
320
            Map<String, String> props = doGetProperties();
1✔
321
            Map<String, String> map = new LinkedHashMap<>();
1✔
322

323
            props.forEach((key, value) -> {
1✔
324
                if (key.startsWith("JRELEASER_")) return;
1✔
325
                String k = key.replace(".", "-");
1✔
326
                k = getPropertyNameForLowerCaseHyphenSeparatedName(k);
1✔
327
                map.put(k, value);
1✔
328
            });
1✔
329

330
            return map;
1✔
331
        }
332

333
        protected abstract Map<String, String> doGetProperties();
334
    }
335

336
    public static class PropertiesPropertiesSource extends AbstractPropertiesSource {
337
        private static final long serialVersionUID = 7747477120107034027L;
338

339
        private final Properties properties;
340

341
        public PropertiesPropertiesSource(Properties properties) {
1✔
342
            this.properties = properties;
1✔
343
        }
1✔
344

345
        @Override
346
        protected Map<String, String> doGetProperties() {
347
            Map<String, String> map = new LinkedHashMap<>();
1✔
348
            properties.forEach((k, v) -> map.put(String.valueOf(k), String.valueOf(v)));
1✔
349
            return map;
1✔
350
        }
351
    }
352

353
    public static class MapPropertiesSource extends AbstractPropertiesSource {
354
        private static final long serialVersionUID = 6643212572356054605L;
355

356
        private final Map<String, ?> properties;
357

UNCOV
358
        public MapPropertiesSource(Map<String, ?> properties) {
×
UNCOV
359
            this.properties = properties;
×
360
        }
×
361

362
        @Override
363
        protected Map<String, String> doGetProperties() {
UNCOV
364
            Map<String, String> map = new LinkedHashMap<>();
×
UNCOV
365
            properties.forEach((k, v) -> map.put(k, String.valueOf(v)));
×
366
            return map;
×
367
        }
368
    }
369
}
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