blob: e15bc053e4c068cf2f056446a0208a3879d0f11b [file] [log] [blame]
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00001// Copyright 2014 The Bazel Authors. All rights reserved.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01002//
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.
14package com.google.devtools.build.lib.actions;
15
16import com.google.common.annotations.VisibleForTesting;
tomluad34b9a2018-06-08 10:45:04 -070017import com.google.devtools.build.lib.unsafe.StringUnsafe;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010018import com.google.devtools.build.lib.util.FileType;
Googler3bfba782019-05-17 07:39:06 -070019import com.google.devtools.build.lib.util.GccParamFileEscaper;
tomlu11f20372018-04-11 06:15:13 -070020import com.google.devtools.build.lib.util.ShellEscaper;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010021import com.google.devtools.build.lib.vfs.PathFragment;
fellyec4fccd2019-07-09 09:35:30 -070022import java.io.BufferedOutputStream;
tomlu11f20372018-04-11 06:15:13 -070023import java.io.IOException;
24import java.io.OutputStream;
25import java.io.OutputStreamWriter;
tomluad34b9a2018-06-08 10:45:04 -070026import java.nio.ByteBuffer;
27import java.nio.CharBuffer;
tomlu11f20372018-04-11 06:15:13 -070028import java.nio.charset.Charset;
tomluad34b9a2018-06-08 10:45:04 -070029import java.nio.charset.CharsetEncoder;
30import java.nio.charset.StandardCharsets;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010031
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010032/**
33 * Support for parameter file generation (as used by gcc and other tools, e.g.
34 * {@code gcc @param_file}. Note that the parameter file needs to be explicitly
35 * deleted after use. Different tools require different parameter file formats,
36 * which can be selected via the {@link ParameterFileType} enum.
37 *
38 * <p>The default charset is ISO-8859-1 (latin1). This also has to match the
39 * expectation of the tool.
40 *
41 * <p>Don't use this class for new code. Use the ParameterFileWriteAction
42 * instead!
43 */
44public class ParameterFile {
45
fellyec4fccd2019-07-09 09:35:30 -070046 /** Different styles of parameter files. */
47 public enum ParameterFileType {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010048 /**
49 * A parameter file with every parameter on a separate line. This format
50 * cannot handle newlines in parameters. It is currently used for most
51 * tools, but may not be interpreted correctly if parameters contain
52 * white space or other special characters. It should be avoided for new
53 * development.
54 */
55 UNQUOTED,
56
57 /**
Googler3bfba782019-05-17 07:39:06 -070058 * A parameter file where each parameter is correctly quoted for shell use, and separated by
59 * white space (space, tab, newline). This format is safe for all characters, but must be
60 * specially supported by the tool. In particular, it must not be used with gcc and related
61 * tools, which do not support this format as it is.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010062 */
Googler3bfba782019-05-17 07:39:06 -070063 SHELL_QUOTED,
64
65 /**
66 * A parameter file where each parameter is correctly quoted for gcc or clang use, and separated
67 * by white space (space, tab, newline).
68 */
69 GCC_QUOTED;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010070 }
71
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010072 @VisibleForTesting
73 public static final FileType PARAMETER_FILE = FileType.of(".params");
74
75 /**
76 * Creates a parameter file with the given parameters.
77 */
Eric Fellheimer09a900f2015-06-12 15:34:48 +000078 private ParameterFile() {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010079 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010080 /**
Han-Wen Nienhuysad358232015-06-18 13:24:49 +000081 * Derives an path from a given path by appending <code>".params"</code>.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010082 */
83 public static PathFragment derivePath(PathFragment original) {
Han-Wen Nienhuys42574a42015-08-10 12:27:56 +000084 return derivePath(original, "2");
85 }
86
87 /**
88 * Derives an path from a given path by appending <code>".params"</code>.
89 */
90 public static PathFragment derivePath(PathFragment original, String flavor) {
91 return original.replaceName(original.getBaseName() + "-" + flavor + ".params");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010092 }
93
tomlu11f20372018-04-11 06:15:13 -070094 /** Writes an argument list to a parameter file. */
95 public static void writeParameterFile(
96 OutputStream out, Iterable<String> arguments, ParameterFileType type, Charset charset)
97 throws IOException {
fellyec4fccd2019-07-09 09:35:30 -070098 OutputStream bufferedOut = new BufferedOutputStream(out);
tomlu11f20372018-04-11 06:15:13 -070099 switch (type) {
100 case SHELL_QUOTED:
fellyec4fccd2019-07-09 09:35:30 -0700101 writeContent(bufferedOut, ShellEscaper.escapeAll(arguments), charset);
Googler3bfba782019-05-17 07:39:06 -0700102 break;
103 case GCC_QUOTED:
fellyec4fccd2019-07-09 09:35:30 -0700104 writeContent(bufferedOut, GccParamFileEscaper.escapeAll(arguments), charset);
tomlu11f20372018-04-11 06:15:13 -0700105 break;
106 case UNQUOTED:
fellyec4fccd2019-07-09 09:35:30 -0700107 writeContent(bufferedOut, arguments, charset);
tomlu11f20372018-04-11 06:15:13 -0700108 break;
109 }
110 }
111
tomluad34b9a2018-06-08 10:45:04 -0700112 private static void writeContent(
tomlu11f20372018-04-11 06:15:13 -0700113 OutputStream outputStream, Iterable<String> arguments, Charset charset) throws IOException {
tomluad34b9a2018-06-08 10:45:04 -0700114 if (charset.equals(StandardCharsets.ISO_8859_1) && StringUnsafe.canUse()) {
115 writeContentLatin1Jdk9(outputStream, arguments);
116 } else if (charset.equals(StandardCharsets.UTF_8) && StringUnsafe.canUse()) {
117 writeContentUtf8Jdk9(outputStream, arguments);
118 } else {
119 // Generic charset support
120 OutputStreamWriter out = new OutputStreamWriter(outputStream, charset);
121 for (String line : arguments) {
122 out.write(line);
123 out.write('\n');
124 }
125 out.flush();
tomlu11f20372018-04-11 06:15:13 -0700126 }
tomlu11f20372018-04-11 06:15:13 -0700127 }
128
129 /**
tomluad34b9a2018-06-08 10:45:04 -0700130 * Fast LATIN-1 path that avoids GC overhead. This takes advantage of the fact that strings are
131 * encoded as either LATIN-1 or UTF-16 under JDK9. When LATIN-1 we can simply copy the byte
132 * buffer, when UTF-16 we can fail loudly.
tomlu11f20372018-04-11 06:15:13 -0700133 */
tomluad34b9a2018-06-08 10:45:04 -0700134 private static void writeContentLatin1Jdk9(OutputStream outputStream, Iterable<String> arguments)
135 throws IOException {
136 StringUnsafe stringUnsafe = StringUnsafe.getInstance();
137 for (String line : arguments) {
138 if (stringUnsafe.getCoder(line) == StringUnsafe.LATIN1) {
139 byte[] bytes = stringUnsafe.getByteArray(line);
140 outputStream.write(bytes);
141 } else {
142 // Error case, encode with '?' characters
143 ByteBuffer encodedBytes = StandardCharsets.ISO_8859_1.encode(CharBuffer.wrap(line));
144 outputStream.write(
145 encodedBytes.array(),
146 encodedBytes.arrayOffset(),
147 encodedBytes.arrayOffset() + encodedBytes.limit());
148 }
149 outputStream.write('\n');
tomlu11f20372018-04-11 06:15:13 -0700150 }
tomluad34b9a2018-06-08 10:45:04 -0700151 outputStream.flush();
152 }
153
154 /**
155 * Fast UTF-8 path that tries to coder GC overhead. This takes advantage of the fact that strings
156 * are encoded as either LATIN-1 or UTF-16 under JDK9. When LATIN-1 we can check if the buffer is
157 * ASCII and copy that directly (since this is both valid LATIN-1 and UTF-8), in all other cases
158 * we must re-encode.
159 */
160 private static void writeContentUtf8Jdk9(OutputStream outputStream, Iterable<String> arguments)
161 throws IOException {
162 CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder();
163 StringUnsafe stringUnsafe = StringUnsafe.getInstance();
164 for (String line : arguments) {
165 byte[] bytes = stringUnsafe.getByteArray(line);
166 if (stringUnsafe.getCoder(line) == StringUnsafe.LATIN1 && isAscii(bytes)) {
167 outputStream.write(bytes);
168 } else {
169 ByteBuffer encodedBytes = encoder.encode(CharBuffer.wrap(line));
170 outputStream.write(
171 encodedBytes.array(),
172 encodedBytes.arrayOffset(),
173 encodedBytes.arrayOffset() + encodedBytes.limit());
174 }
175 outputStream.write('\n');
176 }
177 outputStream.flush();
178 }
179
180 private static boolean isAscii(byte[] latin1Bytes) {
181 boolean hiBitSet = false;
182 int n = latin1Bytes.length;
183 for (int i = 0; i < n; ++i) {
184 hiBitSet |= ((latin1Bytes[i] & 0x80) != 0);
185 }
186 return !hiBitSet;
tomlu11f20372018-04-11 06:15:13 -0700187 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100188}