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