blob: 36c9cfa69c3ca1ed9cccab9cdebe90a30a73cb5d [file] [log] [blame]
Lukacs Berki8b074c02016-07-01 13:36:38 +00001// Copyright 2016 The Bazel Authors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package com.google.devtools.build.lib.shell;
16
Laszlo Csomorae0ad662017-01-13 16:51:56 +000017import com.google.common.base.Preconditions;
Lukacs Berki8b074c02016-07-01 13:36:38 +000018import com.google.common.collect.ImmutableList;
19import com.google.common.collect.ImmutableMap;
Lukacs Berki8b074c02016-07-01 13:36:38 +000020import java.io.File;
21import java.io.IOException;
22import java.util.Map;
ulfjackf2d45952017-08-09 15:27:49 +020023import javax.annotation.Nullable;
Lukacs Berki8b074c02016-07-01 13:36:38 +000024
25/**
26 * A builder class that starts a subprocess.
27 */
28public class SubprocessBuilder {
29 /**
30 * What to do with an output stream of the process.
31 */
32 public enum StreamAction {
33 /** Redirect to a file */
34 REDIRECT,
35
36 /** Discard. */
37 DISCARD,
38
39 /** Stream back to the parent process using an output stream. */
ulfjackf2d45952017-08-09 15:27:49 +020040 STREAM
41 }
Lukacs Berki8b074c02016-07-01 13:36:38 +000042
laszlocsomor1da48612019-02-15 01:41:57 -080043 private final SubprocessFactory factory;
Lukacs Berki8b074c02016-07-01 13:36:38 +000044 private ImmutableList<String> argv;
45 private ImmutableMap<String, String> env;
46 private StreamAction stdoutAction;
47 private File stdoutFile;
48 private StreamAction stderrAction;
49 private File stderrFile;
50 private File workingDirectory;
ulfjackf2d45952017-08-09 15:27:49 +020051 private long timeoutMillis;
Yun Peng6a4247b2017-10-24 14:36:10 +020052 private boolean redirectErrorStream;
Lukacs Berki8b074c02016-07-01 13:36:38 +000053
laszlocsomor1da48612019-02-15 01:41:57 -080054 static SubprocessFactory defaultFactory = JavaSubprocessFactory.INSTANCE;
Lukacs Berki74dcfee2016-07-04 12:57:25 +000055
laszlocsomor1da48612019-02-15 01:41:57 -080056 public static void setDefaultSubprocessFactory(SubprocessFactory factory) {
57 SubprocessBuilder.defaultFactory = factory;
Lukacs Berki74dcfee2016-07-04 12:57:25 +000058 }
59
Lukacs Berki8b074c02016-07-01 13:36:38 +000060 public SubprocessBuilder() {
laszlocsomor1da48612019-02-15 01:41:57 -080061 this(defaultFactory);
62 }
63
64 public SubprocessBuilder(SubprocessFactory factory) {
Lukacs Berki8b074c02016-07-01 13:36:38 +000065 stdoutAction = StreamAction.STREAM;
66 stderrAction = StreamAction.STREAM;
laszlocsomor1da48612019-02-15 01:41:57 -080067 this.factory = factory;
Lukacs Berki8b074c02016-07-01 13:36:38 +000068 }
69
Laszlo Csomorae0ad662017-01-13 16:51:56 +000070 /**
71 * Returns the complete argv, including argv0.
72 *
73 * <p>argv[0] is either absolute (e.g. "/foo/bar" or "c:/foo/bar.exe"), or is a single file name
74 * (no directory component, e.g. "true" or "cmd.exe"). It might be non-normalized though (e.g.
75 * "/foo/../bar/./baz").
76 */
Lukacs Berki8b074c02016-07-01 13:36:38 +000077 public ImmutableList<String> getArgv() {
78 return argv;
79 }
80
81 /**
82 * Sets the argv, including argv[0], that is, the binary to execute.
Laszlo Csomorae0ad662017-01-13 16:51:56 +000083 *
84 * <p>argv[0] must be either absolute (e.g. "/foo/bar" or "c:/foo/bar.exe"), or a single file name
85 * (no directory component, e.g. "true" or "cmd.exe") which should be on the OS-specific search
86 * path (PATH on Unixes, Windows-specific lookup paths on Windows).
87 *
88 * @throws IllegalArgumentException if argv is empty, or its first element (which becomes
89 * this.argv[0]) is neither an absolute path nor just a single file name
Lukacs Berki8b074c02016-07-01 13:36:38 +000090 */
laszlocsomord2cb96a2020-01-14 02:35:04 -080091 public SubprocessBuilder setArgv(ImmutableList<String> argv) {
92 this.argv = Preconditions.checkNotNull(argv);
Laszlo Csomorae0ad662017-01-13 16:51:56 +000093 Preconditions.checkArgument(!this.argv.isEmpty());
94 File argv0 = new File(this.argv.get(0));
95 Preconditions.checkArgument(
96 argv0.isAbsolute() || argv0.getParent() == null,
97 "argv[0] = '%s'; it should be either absolute or just a single file name"
98 + " (no directory component)",
99 this.argv.get(0));
Lukacs Berki8b074c02016-07-01 13:36:38 +0000100 return this;
101 }
102
103 public ImmutableMap<String, String> getEnv() {
104 return env;
105 }
106
107 /**
108 * Sets the environment passed to the child process. If null, inherit the environment of the
109 * server.
110 */
ulfjackf2d45952017-08-09 15:27:49 +0200111 public SubprocessBuilder setEnv(@Nullable Map<String, String> env) {
112 this.env = env == null ? null : ImmutableMap.copyOf(env);
Lukacs Berki8b074c02016-07-01 13:36:38 +0000113 return this;
114 }
115
116 public StreamAction getStdout() {
117 return stdoutAction;
118 }
119
120 public File getStdoutFile() {
121 return stdoutFile;
122 }
123
124 /**
125 * Tells the object what to do with stdout: either stream as a {@code InputStream} or discard.
Laszlo Csomorae0ad662017-01-13 16:51:56 +0000126 *
Lukacs Berki8b074c02016-07-01 13:36:38 +0000127 * <p>It can also be redirected to a file using {@link #setStdout(File)}.
128 */
129 public SubprocessBuilder setStdout(StreamAction action) {
130 if (action == StreamAction.REDIRECT) {
131 throw new IllegalStateException();
132 }
133 this.stdoutAction = action;
134 this.stdoutFile = null;
135 return this;
136 }
Laszlo Csomorae0ad662017-01-13 16:51:56 +0000137
Lukacs Berki8b074c02016-07-01 13:36:38 +0000138 /**
139 * Sets the file stdout is appended to. If null, the stdout will be available as an input stream
140 * on the resulting object representing the process.
141 */
142 public SubprocessBuilder setStdout(File file) {
143 this.stdoutAction = StreamAction.REDIRECT;
144 this.stdoutFile = file;
145 return this;
146 }
147
Lukacs Berki3d97e222016-08-19 14:40:20 +0000148 public SubprocessBuilder setTimeoutMillis(long timeoutMillis) {
149 this.timeoutMillis = timeoutMillis;
150 return this;
151 }
152
153 public long getTimeoutMillis() {
154 return timeoutMillis;
155 }
156
Lukacs Berki8b074c02016-07-01 13:36:38 +0000157 public StreamAction getStderr() {
158 return stderrAction;
159 }
160
161 public File getStderrFile() {
162 return stderrFile;
163 }
164
165 /**
166 * Tells the object what to do with stderr: either stream as a {@code InputStream} or discard.
Laszlo Csomorae0ad662017-01-13 16:51:56 +0000167 *
Lukacs Berki8b074c02016-07-01 13:36:38 +0000168 * <p>It can also be redirected to a file using {@link #setStderr(File)}.
169 */
170 public SubprocessBuilder setStderr(StreamAction action) {
171 if (action == StreamAction.REDIRECT) {
172 throw new IllegalStateException();
173 }
174 this.stderrAction = action;
175 this.stderrFile = null;
176 return this;
177 }
Laszlo Csomorae0ad662017-01-13 16:51:56 +0000178
Lukacs Berki8b074c02016-07-01 13:36:38 +0000179 /**
180 * Sets the file stderr is appended to. If null, the stderr will be available as an input stream
Yun Peng6a4247b2017-10-24 14:36:10 +0200181 * on the resulting object representing the process. When {@code redirectErrorStream} is set to
182 * True, this method has no effect.
Lukacs Berki8b074c02016-07-01 13:36:38 +0000183 */
184 public SubprocessBuilder setStderr(File file) {
185 this.stderrAction = StreamAction.REDIRECT;
186 this.stderrFile = file;
187 return this;
188 }
189
Yun Peng6a4247b2017-10-24 14:36:10 +0200190 /**
191 * Tells whether this process builder merges standard error and standard output.
192 *
193 * @return this process builder's {@code redirectErrorStream} property
194 */
195 public boolean redirectErrorStream() {
196 return redirectErrorStream;
197 }
198
199 /**
200 * Sets whether this process builder merges standard error and standard output.
201 *
202 * <p>If this property is {@code true}, then any error output generated by subprocesses
203 * subsequently started by this object's {@link #start()} method will be merged with the standard
204 * output, so that both can be read using the {@link Subprocess#getInputStream()} method. This
205 * makes it easier to correlate error messages with the corresponding output. The initial value is
206 * {@code false}.
207 */
208 public SubprocessBuilder redirectErrorStream(boolean redirectErrorStream) {
209 this.redirectErrorStream = redirectErrorStream;
210 return this;
211 }
212
Lukacs Berki8b074c02016-07-01 13:36:38 +0000213 public File getWorkingDirectory() {
214 return workingDirectory;
215 }
216
217 /**
218 * Sets the current working directory. If null, it will be that of this process.
219 */
220 public SubprocessBuilder setWorkingDirectory(File workingDirectory) {
221 this.workingDirectory = workingDirectory;
222 return this;
223 }
224
225 public Subprocess start() throws IOException {
Lukacs Berki74dcfee2016-07-04 12:57:25 +0000226 return factory.create(this);
Lukacs Berki8b074c02016-07-01 13:36:38 +0000227 }
228}