Lukacs Berki | 8b074c0 | 2016-07-01 13:36:38 +0000 | [diff] [blame] | 1 | // 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 | |
| 15 | package com.google.devtools.build.lib.shell; |
| 16 | |
Laszlo Csomor | ae0ad66 | 2017-01-13 16:51:56 +0000 | [diff] [blame] | 17 | import com.google.common.base.Preconditions; |
Lukacs Berki | 8b074c0 | 2016-07-01 13:36:38 +0000 | [diff] [blame] | 18 | import com.google.common.collect.ImmutableList; |
| 19 | import com.google.common.collect.ImmutableMap; |
Lukacs Berki | 8b074c0 | 2016-07-01 13:36:38 +0000 | [diff] [blame] | 20 | import java.io.File; |
| 21 | import java.io.IOException; |
| 22 | import java.util.Map; |
ulfjack | f2d4595 | 2017-08-09 15:27:49 +0200 | [diff] [blame] | 23 | import javax.annotation.Nullable; |
Lukacs Berki | 8b074c0 | 2016-07-01 13:36:38 +0000 | [diff] [blame] | 24 | |
| 25 | /** |
| 26 | * A builder class that starts a subprocess. |
| 27 | */ |
| 28 | public 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. */ |
ulfjack | f2d4595 | 2017-08-09 15:27:49 +0200 | [diff] [blame] | 40 | STREAM |
| 41 | } |
Lukacs Berki | 8b074c0 | 2016-07-01 13:36:38 +0000 | [diff] [blame] | 42 | |
laszlocsomor | 1da4861 | 2019-02-15 01:41:57 -0800 | [diff] [blame] | 43 | private final SubprocessFactory factory; |
Lukacs Berki | 8b074c0 | 2016-07-01 13:36:38 +0000 | [diff] [blame] | 44 | 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; |
ulfjack | f2d4595 | 2017-08-09 15:27:49 +0200 | [diff] [blame] | 51 | private long timeoutMillis; |
Yun Peng | 6a4247b | 2017-10-24 14:36:10 +0200 | [diff] [blame] | 52 | private boolean redirectErrorStream; |
Lukacs Berki | 8b074c0 | 2016-07-01 13:36:38 +0000 | [diff] [blame] | 53 | |
laszlocsomor | 1da4861 | 2019-02-15 01:41:57 -0800 | [diff] [blame] | 54 | static SubprocessFactory defaultFactory = JavaSubprocessFactory.INSTANCE; |
Lukacs Berki | 74dcfee | 2016-07-04 12:57:25 +0000 | [diff] [blame] | 55 | |
laszlocsomor | 1da4861 | 2019-02-15 01:41:57 -0800 | [diff] [blame] | 56 | public static void setDefaultSubprocessFactory(SubprocessFactory factory) { |
| 57 | SubprocessBuilder.defaultFactory = factory; |
Lukacs Berki | 74dcfee | 2016-07-04 12:57:25 +0000 | [diff] [blame] | 58 | } |
| 59 | |
Lukacs Berki | 8b074c0 | 2016-07-01 13:36:38 +0000 | [diff] [blame] | 60 | public SubprocessBuilder() { |
laszlocsomor | 1da4861 | 2019-02-15 01:41:57 -0800 | [diff] [blame] | 61 | this(defaultFactory); |
| 62 | } |
| 63 | |
| 64 | public SubprocessBuilder(SubprocessFactory factory) { |
Lukacs Berki | 8b074c0 | 2016-07-01 13:36:38 +0000 | [diff] [blame] | 65 | stdoutAction = StreamAction.STREAM; |
| 66 | stderrAction = StreamAction.STREAM; |
laszlocsomor | 1da4861 | 2019-02-15 01:41:57 -0800 | [diff] [blame] | 67 | this.factory = factory; |
Lukacs Berki | 8b074c0 | 2016-07-01 13:36:38 +0000 | [diff] [blame] | 68 | } |
| 69 | |
Laszlo Csomor | ae0ad66 | 2017-01-13 16:51:56 +0000 | [diff] [blame] | 70 | /** |
| 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 Berki | 8b074c0 | 2016-07-01 13:36:38 +0000 | [diff] [blame] | 77 | public ImmutableList<String> getArgv() { |
| 78 | return argv; |
| 79 | } |
| 80 | |
| 81 | /** |
| 82 | * Sets the argv, including argv[0], that is, the binary to execute. |
Laszlo Csomor | ae0ad66 | 2017-01-13 16:51:56 +0000 | [diff] [blame] | 83 | * |
| 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 Berki | 8b074c0 | 2016-07-01 13:36:38 +0000 | [diff] [blame] | 90 | */ |
laszlocsomor | d2cb96a | 2020-01-14 02:35:04 -0800 | [diff] [blame] | 91 | public SubprocessBuilder setArgv(ImmutableList<String> argv) { |
| 92 | this.argv = Preconditions.checkNotNull(argv); |
Laszlo Csomor | ae0ad66 | 2017-01-13 16:51:56 +0000 | [diff] [blame] | 93 | 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 Berki | 8b074c0 | 2016-07-01 13:36:38 +0000 | [diff] [blame] | 100 | 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 | */ |
ulfjack | f2d4595 | 2017-08-09 15:27:49 +0200 | [diff] [blame] | 111 | public SubprocessBuilder setEnv(@Nullable Map<String, String> env) { |
| 112 | this.env = env == null ? null : ImmutableMap.copyOf(env); |
Lukacs Berki | 8b074c0 | 2016-07-01 13:36:38 +0000 | [diff] [blame] | 113 | 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 Csomor | ae0ad66 | 2017-01-13 16:51:56 +0000 | [diff] [blame] | 126 | * |
Lukacs Berki | 8b074c0 | 2016-07-01 13:36:38 +0000 | [diff] [blame] | 127 | * <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 Csomor | ae0ad66 | 2017-01-13 16:51:56 +0000 | [diff] [blame] | 137 | |
Lukacs Berki | 8b074c0 | 2016-07-01 13:36:38 +0000 | [diff] [blame] | 138 | /** |
| 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 Berki | 3d97e22 | 2016-08-19 14:40:20 +0000 | [diff] [blame] | 148 | public SubprocessBuilder setTimeoutMillis(long timeoutMillis) { |
| 149 | this.timeoutMillis = timeoutMillis; |
| 150 | return this; |
| 151 | } |
| 152 | |
| 153 | public long getTimeoutMillis() { |
| 154 | return timeoutMillis; |
| 155 | } |
| 156 | |
Lukacs Berki | 8b074c0 | 2016-07-01 13:36:38 +0000 | [diff] [blame] | 157 | 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 Csomor | ae0ad66 | 2017-01-13 16:51:56 +0000 | [diff] [blame] | 167 | * |
Lukacs Berki | 8b074c0 | 2016-07-01 13:36:38 +0000 | [diff] [blame] | 168 | * <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 Csomor | ae0ad66 | 2017-01-13 16:51:56 +0000 | [diff] [blame] | 178 | |
Lukacs Berki | 8b074c0 | 2016-07-01 13:36:38 +0000 | [diff] [blame] | 179 | /** |
| 180 | * Sets the file stderr is appended to. If null, the stderr will be available as an input stream |
Yun Peng | 6a4247b | 2017-10-24 14:36:10 +0200 | [diff] [blame] | 181 | * on the resulting object representing the process. When {@code redirectErrorStream} is set to |
| 182 | * True, this method has no effect. |
Lukacs Berki | 8b074c0 | 2016-07-01 13:36:38 +0000 | [diff] [blame] | 183 | */ |
| 184 | public SubprocessBuilder setStderr(File file) { |
| 185 | this.stderrAction = StreamAction.REDIRECT; |
| 186 | this.stderrFile = file; |
| 187 | return this; |
| 188 | } |
| 189 | |
Yun Peng | 6a4247b | 2017-10-24 14:36:10 +0200 | [diff] [blame] | 190 | /** |
| 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 Berki | 8b074c0 | 2016-07-01 13:36:38 +0000 | [diff] [blame] | 213 | 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 Berki | 74dcfee | 2016-07-04 12:57:25 +0000 | [diff] [blame] | 226 | return factory.create(this); |
Lukacs Berki | 8b074c0 | 2016-07-01 13:36:38 +0000 | [diff] [blame] | 227 | } |
| 228 | } |