// Copyright 2014 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.singlejar; import static java.nio.charset.StandardCharsets.ISO_8859_1; import com.google.devtools.build.lib.shell.ShellUtils; import com.google.devtools.build.lib.shell.ShellUtils.TokenizationException; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.util.ArrayList; import java.util.List; import javax.annotation.concurrent.Immutable; /** * A utility class to parse option files and expand them. */ @Immutable final class OptionFileExpander { /** * An interface that allows injecting different implementations for reading * files. This is mostly used for testing. */ interface OptionFileProvider { /** * Opens a file for reading and returns an input stream. */ InputStream getInputStream(String filename) throws IOException; } private final OptionFileProvider fileSystem; /** * Creates an instance with the given option file provider. */ public OptionFileExpander(OptionFileProvider fileSystem) { this.fileSystem = fileSystem; } /** * Pre-processes an argument list, expanding options of the form &at;filename * to read in the content of the file and add it to the list of arguments. * * @param args the List of arguments to pre-process. * @return the List of pre-processed arguments. * @throws IOException if one of the files containing options cannot be read. */ public List<String> expandArguments(List<String> args) throws IOException { List<String> expanded = new ArrayList<>(args.size()); for (String arg : args) { expandArgument(arg, expanded); } return expanded; } /** * Expands a single argument, expanding options &at;filename to read in * the content of the file and add it to the list of processed arguments. * * @param arg the argument to pre-process. * @param expanded the List of pre-processed arguments. * @throws IOException if one of the files containing options cannot be read. */ private void expandArgument(String arg, List<String> expanded) throws IOException { if (arg.startsWith("@")) { InputStream in = fileSystem.getInputStream(arg.substring(1)); try { // TODO(bazel-team): This code doesn't handle escaped newlines correctly. // ShellUtils doesn't support them either. for (String line : readAllLines(new InputStreamReader(in, ISO_8859_1))) { List<String> parsedTokens = new ArrayList<>(); try { ShellUtils.tokenize(parsedTokens, line); } catch (TokenizationException e) { throw new IOException("Could not tokenize parameter file!", e); } for (String token : parsedTokens) { expandArgument(token, expanded); } } InputStream inToClose = in; in = null; inToClose.close(); } finally { if (in != null) { try { in.close(); } catch (IOException e) { // Ignore the exception. It can only occur if an exception already // happened and in that case, we want to preserve the original one. } } } } else { expanded.add(arg); } } private List<String> readAllLines(Reader in) throws IOException { List<String> result = new ArrayList<>(); BufferedReader reader = new BufferedReader(in); String line; while ((line = reader.readLine()) != null) { result.add(line); } return result; } }