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

jreleaser / jreleaser / #537

25 Sep 2025 02:50PM UTC coverage: 48.299% (-0.7%) from 48.959%
#537

push

github

web-flow
fix: check snapshot version for '-SNAPSHOT' tag

Fixe #1971

2 of 2 new or added lines in 1 file covered. (100.0%)

362 existing lines in 31 files now uncovered.

25821 of 53461 relevant lines covered (48.3%)

0.48 hits per line

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

72.29
/sdks/jreleaser-command-java-sdk/src/main/java/org/jreleaser/sdk/command/CommandExecutor.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.sdk.command;
19

20
import org.jreleaser.bundle.RB;
21
import org.jreleaser.logging.JReleaserLogger;
22
import org.jreleaser.util.IoUtils;
23

24
import java.io.ByteArrayOutputStream;
25
import java.io.File;
26
import java.io.IOException;
27
import java.io.InputStream;
28
import java.io.OutputStream;
29
import java.io.PrintWriter;
30
import java.nio.file.Path;
31
import java.util.LinkedHashMap;
32
import java.util.Map;
33
import java.util.concurrent.ExecutorService;
34
import java.util.concurrent.Executors;
35
import java.util.concurrent.ThreadFactory;
36
import java.util.concurrent.atomic.AtomicInteger;
37
import java.util.function.Consumer;
38

39
/**
40
 * @author Andres Almiray
41
 * @since 0.8.0
42
 */
43
public class CommandExecutor {
44
    private final JReleaserLogger logger;
45
    private final Output output;
46
    private final Map<String, String> environment = new LinkedHashMap<>();
1✔
47

48
    public enum Output {
1✔
49
        QUIET,
1✔
50
        DEBUG,
1✔
51
        VERBOSE
1✔
52
    }
53

54
    public CommandExecutor(JReleaserLogger logger) {
55
        this(logger, Output.DEBUG);
1✔
56
    }
1✔
57

58
    public CommandExecutor(JReleaserLogger logger, Output output) {
1✔
59
        this.logger = logger;
1✔
60
        this.output = output;
1✔
61
    }
1✔
62

63
    public CommandExecutor environment(Map<String, String> env) {
64
        environment.putAll(env);
1✔
65
        return this;
1✔
66
    }
67

68
    public CommandExecutor environment(String name, String value) {
69
        environment.put(name, value);
×
70
        return this;
×
71
    }
72

73
    private Command.Result executeCommand(ProcessExecutor processExecutor) throws CommandException {
74
        try {
75
            ByteArrayOutputStream out = new ByteArrayOutputStream();
1✔
76
            ByteArrayOutputStream err = new ByteArrayOutputStream();
1✔
77

78
            int exitValue = processExecutor
1✔
79
                .execute(logger, output, out, err);
1✔
80

81
            return Command.Result.of(IoUtils.toString(out), IoUtils.toString(err), exitValue);
1✔
82
        } catch (InterruptedException e) {
×
83
            Thread.currentThread().interrupt();
×
84
            throw new CommandException(RB.$("ERROR_unexpected_error"), e);
×
85
        } catch (Exception e) {
1✔
86
            throw new CommandException(RB.$("ERROR_unexpected_error"), e);
1✔
87
        }
88
    }
89

90
    public Command.Result executeCommand(Command command) throws CommandException {
91
        return executeCommand(createProcessExecutor(command));
1✔
92
    }
93

94
    public Command.Result executeCommand(Path directory, Command command) throws CommandException {
95
        return executeCommand(createProcessExecutor(command)
1✔
96
            .directory(directory.toFile()));
1✔
97
    }
98

99
    public Command.Result executeCommand(Command command, InputStream in) throws CommandException {
100
        return executeCommand(createProcessExecutor(command)
×
101
            .redirectInput(in));
×
102
    }
103

104
    public Command.Result executeCommand(Path directory, Command command, InputStream in) throws CommandException {
105
        return executeCommand(createProcessExecutor(command)
×
106
            .redirectInput(in)
×
107
            .directory(directory.toFile()));
×
108
    }
109

110
    private ProcessExecutor createProcessExecutor(Command command) throws CommandException {
111
        try {
112
            return new ProcessExecutor(command, environment);
1✔
113
        } catch (IOException e) {
×
114
            throw new CommandException(RB.$("ERROR_unexpected_error"), e);
×
115
        }
116
    }
117

118
    private static class ProcessExecutor {
119
        private final ProcessBuilder builder;
120
        private InputStream input;
121

122
        private static final ExecutorService EXECUTOR_SERVICE = Executors.newFixedThreadPool(2, new ThreadFactory() {
1✔
123
            private final AtomicInteger counter = new AtomicInteger(1);
1✔
124

125
            public Thread newThread(Runnable r) {
126
                Thread t = Executors.defaultThreadFactory().newThread(r);
1✔
127
                t.setDaemon(true);
1✔
128
                t.setName("jreleaser-command-executor-" + counter.getAndIncrement());
1✔
129
                return t;
1✔
130
            }
131
        });
132

133
        private ProcessExecutor(Command command, Map<String, String> environment) throws IOException {
1✔
134
            this.builder = new ProcessBuilder(command.asCommandLine());
1✔
135
            this.builder.environment().putAll(environment);
1✔
136
        }
1✔
137

138
        private ProcessExecutor directory(File directory) {
139
            builder.directory(directory);
1✔
140
            return this;
1✔
141
        }
142

143
        private ProcessExecutor redirectInput(InputStream input) {
144
            this.input = input;
×
145
            return this;
×
146
        }
147

148
        private int execute(JReleaserLogger logger, Output output, OutputStream out, OutputStream err) throws IOException, InterruptedException {
149
            Process process = builder.start();
1✔
150

151
            if (null != input) {
1✔
152
                PrintWriter writer = IoUtils.newPrintWriter(process.getOutputStream(), true);
×
153
                IoUtils.withInputStream(input, writer::write);
×
154
                writer.println();
×
155
            }
156

157
            IOException[] outException = handleStream(process.getInputStream(), out, s -> {
1✔
158
                switch (output) {
1✔
159
                    case DEBUG:
160
                        logger.debug(s);
1✔
161
                        break;
1✔
162
                    case VERBOSE:
163
                        logger.plain(s);
1✔
164
                        break;
1✔
165
                    default:
166
                        // noop
167
                }
168
            });
1✔
169
            IOException[] errException = handleStream(process.getErrorStream(), err, s -> {
1✔
UNCOV
170
                switch (output) {
×
171
                    case DEBUG:
172
                        // fall-through
173
                    case VERBOSE:
UNCOV
174
                        logger.error(s);
×
UNCOV
175
                        break;
×
176
                    default:
177
                        // noop
178
                }
UNCOV
179
            });
×
180

181
            int exitValue = process.waitFor();
1✔
182

183
            if (null != outException[0]) throw outException[0];
1✔
184
            if (null != errException[0]) throw errException[0];
1✔
185

186
            return exitValue;
1✔
187
        }
188

189
        private IOException[] handleStream(InputStream input, OutputStream target, Consumer<? super String> log) {
190
            IOException[] capture = new IOException[1];
1✔
191

192
            EXECUTOR_SERVICE.submit(() -> {
1✔
193
                try {
194
                    PrintWriter writer = IoUtils.newPrintWriter(target, true);
1✔
195
                    IoUtils.withLines(input, s -> {
1✔
196
                        log.accept(s);
1✔
197
                        writer.println(s);
1✔
198
                    });
1✔
199
                } catch (IOException e) {
×
200
                    capture[0] = e;
×
201
                }
1✔
202
            });
1✔
203

204
            return capture;
1✔
205
        }
206
    }
207
}
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