1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.build.config; 18 19 import java.io.InputStream; 20 import java.io.InputStreamReader; 21 import java.io.IOException; 22 import java.util.ArrayList; 23 import java.nio.charset.StandardCharsets; 24 25 public class KatiCommandImpl implements KatiCommand { 26 final Errors mErrors; 27 final Options mOptions; 28 29 /** 30 * Runnable that consumes all of an InputStream until EOF, writes the contents 31 * into a StringBuilder, and then closes the stream. 32 */ 33 class OutputReader implements Runnable { 34 private final InputStream mStream; 35 private final StringBuilder mOutput; 36 OutputReader(InputStream stream, StringBuilder output)37 OutputReader(InputStream stream, StringBuilder output) { 38 mStream = stream; 39 mOutput = output; 40 } 41 42 @Override run()43 public void run() { 44 final char[] buf = new char[16*1024]; 45 final InputStreamReader reader = new InputStreamReader(mStream, StandardCharsets.UTF_8); 46 try { 47 int amt; 48 while ((amt = reader.read(buf, 0, buf.length)) >= 0) { 49 mOutput.append(buf, 0, amt); 50 } 51 } catch (IOException ex) { 52 mErrors.ERROR_KATI.add("Error reading from kati: " + ex.getMessage()); 53 } finally { 54 try { 55 reader.close(); 56 } catch (IOException ex) { 57 // Close doesn't throw 58 } 59 } 60 } 61 } 62 KatiCommandImpl(Errors errors, Options options)63 public KatiCommandImpl(Errors errors, Options options) { 64 mErrors = errors; 65 mOptions = options; 66 } 67 68 /** 69 * Run kati directly. Returns stdout data. 70 * 71 * @throws KatiException if there is an error. KatiException will contain 72 * the stderr from the kati invocation. 73 */ run(String[] args)74 public String run(String[] args) throws KatiException { 75 final ArrayList<String> cmd = new ArrayList(); 76 cmd.add(mOptions.getCKatiBin()); 77 for (String arg: args) { 78 cmd.add(arg); 79 } 80 81 final ProcessBuilder builder = new ProcessBuilder(cmd); 82 builder.redirectOutput(ProcessBuilder.Redirect.PIPE); 83 builder.redirectError(ProcessBuilder.Redirect.PIPE); 84 85 Process process = null; 86 87 try { 88 process = builder.start(); 89 } catch (IOException ex) { 90 throw new KatiException(cmd, "IOException running process: " + ex.getMessage()); 91 } 92 93 final StringBuilder stdout = new StringBuilder(); 94 final Thread stdoutThread = new Thread(new OutputReader(process.getInputStream(), stdout), 95 "kati_stdout_reader"); 96 stdoutThread.start(); 97 98 final StringBuilder stderr = new StringBuilder(); 99 final Thread stderrThread = new Thread(new OutputReader(process.getErrorStream(), stderr), 100 "kati_stderr_reader"); 101 stderrThread.start(); 102 103 int returnCode = waitForProcess(process); 104 joinThread(stdoutThread); 105 joinThread(stderrThread); 106 107 if (returnCode != 0) { 108 throw new KatiException(cmd, stderr.toString()); 109 } 110 111 return stdout.toString(); 112 } 113 114 /** 115 * Wrap Process.waitFor() because it throws InterruptedException. 116 */ waitForProcess(Process proc)117 private static int waitForProcess(Process proc) { 118 while (true) { 119 try { 120 return proc.waitFor(); 121 } catch (InterruptedException ex) { 122 } 123 } 124 } 125 126 /** 127 * Wrap Thread.join() because it throws InterruptedException. 128 */ joinThread(Thread thread)129 private static void joinThread(Thread thread) { 130 while (true) { 131 try { 132 thread.join(); 133 return; 134 } catch (InterruptedException ex) { 135 } 136 } 137 } 138 } 139 140