Add class to fork and exec kati, based on the commandline option given.

Test: m product-config-test && java -jar out/host/linux-x86/testcases/product-config-test/product-config-test.jar
Change-Id: I4706e32ff7ac4424b6835b94fef40a2c838f8492
This commit is contained in:
Joe Onorato
2021-01-19 22:20:09 -08:00
parent 7c01d47c9a
commit 9de9652582
6 changed files with 305 additions and 17 deletions

View File

@@ -30,7 +30,7 @@ import java.util.Map;
* <b>Naming Convention:</b> * <b>Naming Convention:</b>
* <ul> * <ul>
* <li>ERROR_ for Categories with isLevelSettable false and Level.ERROR * <li>ERROR_ for Categories with isLevelSettable false and Level.ERROR
* <li>WARNING_ for Categories with isLevelSettable false and default WARNING or HIDDEN * <li>WARNING_ for Categories with isLevelSettable true and default WARNING or HIDDEN
* <li>Don't have isLevelSettable true and not ERROR. (The constructor asserts this). * <li>Don't have isLevelSettable true and not ERROR. (The constructor asserts this).
* </ul> * </ul>
*/ */
@@ -42,4 +42,7 @@ public class Errors extends ErrorReporter {
public final Category WARNING_UNKNOWN_COMMAND_LINE_ERROR = new Category(2, true, Level.HIDDEN, public final Category WARNING_UNKNOWN_COMMAND_LINE_ERROR = new Category(2, true, Level.HIDDEN,
"Passing unknown errors on the command line. Hidden by default for\n" "Passing unknown errors on the command line. Hidden by default for\n"
+ "forward compatibility."); + "forward compatibility.");
public final Category ERROR_KATI = new Category(3, false, Level.ERROR,
"Error executing or reading from Kati.");
} }

View File

@@ -0,0 +1,43 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* 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.android.build.config;
import java.util.Arrays;
import java.util.List;
public interface KatiCommand {
public static class KatiException extends Exception {
private String mStderr;
public KatiException(List<String> cmd, String stderr) {
super("Error running kati: " + Arrays.toString(cmd.toArray()));
mStderr = stderr;
}
public String getStderr() {
return mStderr;
}
}
/**
* Run kati directly. Returns stdout data.
*
* @throws KatiException if there is an error. KatiException will contain
* the stderr from the kati invocation.
*/
public String run(String[] args) throws KatiException;
}

View File

@@ -0,0 +1,139 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* 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.android.build.config;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.util.ArrayList;
import java.nio.charset.StandardCharsets;
public class KatiCommandImpl implements KatiCommand {
final Errors mErrors;
final Options mOptions;
/**
* Runnable that consumes all of an InputStream until EOF, writes the contents
* into a StringBuilder, and then closes the stream.
*/
class OutputReader implements Runnable {
private final InputStream mStream;
private final StringBuilder mOutput;
OutputReader(InputStream stream, StringBuilder output) {
mStream = stream;
mOutput = output;
}
@Override
public void run() {
final char[] buf = new char[16*1024];
final InputStreamReader reader = new InputStreamReader(mStream, StandardCharsets.UTF_8);
try {
int amt;
while ((amt = reader.read(buf, 0, buf.length)) >= 0) {
mOutput.append(buf, 0, amt);
}
} catch (IOException ex) {
mErrors.ERROR_KATI.add("Error reading from kati: " + ex.getMessage());
} finally {
try {
reader.close();
} catch (IOException ex) {
// Close doesn't throw
}
}
}
}
public KatiCommandImpl(Errors errors, Options options) {
mErrors = errors;
mOptions = options;
}
/**
* Run kati directly. Returns stdout data.
*
* @throws KatiException if there is an error. KatiException will contain
* the stderr from the kati invocation.
*/
public String run(String[] args) throws KatiException {
final ArrayList<String> cmd = new ArrayList();
cmd.add(mOptions.getCKatiBin());
for (String arg: args) {
cmd.add(arg);
}
final ProcessBuilder builder = new ProcessBuilder(cmd);
builder.redirectOutput(ProcessBuilder.Redirect.PIPE);
builder.redirectError(ProcessBuilder.Redirect.PIPE);
Process process = null;
try {
process = builder.start();
} catch (IOException ex) {
throw new KatiException(cmd, "IOException running process: " + ex.getMessage());
}
final StringBuilder stdout = new StringBuilder();
final Thread stdoutThread = new Thread(new OutputReader(process.getInputStream(), stdout),
"kati_stdout_reader");
stdoutThread.start();
final StringBuilder stderr = new StringBuilder();
final Thread stderrThread = new Thread(new OutputReader(process.getErrorStream(), stderr),
"kati_stderr_reader");
stderrThread.start();
int returnCode = waitForProcess(process);
joinThread(stdoutThread);
joinThread(stderrThread);
if (returnCode != 0) {
throw new KatiException(cmd, stderr.toString());
}
return stdout.toString();
}
/**
* Wrap Process.waitFor() because it throws InterruptedException.
*/
private static int waitForProcess(Process proc) {
while (true) {
try {
return proc.waitFor();
} catch (InterruptedException ex) {
}
}
}
/**
* Wrap Thread.join() because it throws InterruptedException.
*/
private static void joinThread(Thread thread) {
while (true) {
try {
thread.join();
return;
} catch (InterruptedException ex) {
}
}
}
}

View File

@@ -47,7 +47,7 @@ public class Main {
int exitCode = 0; int exitCode = 0;
try { try {
Options options = Options.parse(errors, args); Options options = Options.parse(errors, args, System.getenv());
if (errors.hadError()) { if (errors.hadError()) {
Options.printHelp(System.err); Options.printHelp(System.err);
System.err.println(); System.err.println();
@@ -62,7 +62,7 @@ public class Main {
Options.printHelp(System.out); Options.printHelp(System.out);
return; return;
} }
} catch (CommandException ex) { } catch (CommandException | Errors.FatalException ex) {
// These are user errors, so don't show a stack trace // These are user errors, so don't show a stack trace
exitCode = 1; exitCode = 1;
} catch (Throwable ex) { } catch (Throwable ex) {

View File

@@ -17,6 +17,7 @@
package com.android.build.config; package com.android.build.config;
import java.io.PrintStream; import java.io.PrintStream;
import java.util.Map;
import java.util.TreeMap; import java.util.TreeMap;
public class Options { public class Options {
@@ -27,19 +28,50 @@ public class Options {
private Action mAction = Action.DEFAULT; private Action mAction = Action.DEFAULT;
private String mProduct;
private String mVariant;
private String mOutDir;
private String mCKatiBin;
public Action getAction() { public Action getAction() {
return mAction; return mAction;
} }
public String getProduct() {
return mProduct;
}
public String getVariant() {
return mVariant;
}
public String getOutDir() {
return mOutDir != null ? mOutDir : "out";
}
public String getCKatiBin() {
return mCKatiBin;
}
public static void printHelp(PrintStream out) { public static void printHelp(PrintStream out) {
out.println("usage: product_config"); out.println("usage: product_config");
out.println(); out.println();
out.println("OPTIONS"); out.println("REQUIRED FLAGS");
out.println(" --ckati_bin CKATI Kati binary to use.");
out.println();
out.println("OPTIONAL FLAGS");
out.println(" --hide ERROR_ID Suppress this error."); out.println(" --hide ERROR_ID Suppress this error.");
out.println(" --error ERROR_ID Make this ERROR_ID a fatal error."); out.println(" --error ERROR_ID Make this ERROR_ID a fatal error.");
out.println(" --help -h This message."); out.println(" --help -h This message.");
out.println(" --warning ERROR_ID Make this ERROR_ID a warning."); out.println(" --warning ERROR_ID Make this ERROR_ID a warning.");
out.println(); out.println();
out.println("REQUIRED ENVIRONMENT");
out.println(" TARGET_PRODUCT Product to build from lunch command.");
out.println(" TARGET_BUILD_VARIANT Build variant from lunch command.");
out.println();
out.println("OPTIONAL ENVIRONMENT");
out.println(" OUT_DIR Build output directory. Defaults to \"out\".");
out.println();
out.println("ERRORS"); out.println("ERRORS");
out.println(" The following are the errors that can be controlled on the"); out.println(" The following are the errors that can be controlled on the");
out.println(" commandline with the --hide --warning --error flags."); out.println(" commandline with the --hide --warning --error flags.");
@@ -63,20 +95,26 @@ public class Options {
private Errors mErrors; private Errors mErrors;
private String[] mArgs; private String[] mArgs;
private Map<String,String> mEnv;
private Options mResult = new Options(); private Options mResult = new Options();
private int mIndex; private int mIndex;
private boolean mSkipRequiredArgValidation;
public Parser(Errors errors, String[] args) { public Parser(Errors errors, String[] args, Map<String,String> env) {
mErrors = errors; mErrors = errors;
mArgs = args; mArgs = args;
mEnv = env;
} }
public Options parse() { public Options parse() {
// Args
try { try {
while (mIndex < mArgs.length) { while (mIndex < mArgs.length) {
final String arg = mArgs[mIndex]; final String arg = mArgs[mIndex];
if ("--hide".equals(arg)) { if ("--ckati_bin".equals(arg)) {
mResult.mCKatiBin = requireNextStringArg(arg);
} else if ("--hide".equals(arg)) {
handleErrorCode(arg, Errors.Level.HIDDEN); handleErrorCode(arg, Errors.Level.HIDDEN);
} else if ("--error".equals(arg)) { } else if ("--error".equals(arg)) {
handleErrorCode(arg, Errors.Level.ERROR); handleErrorCode(arg, Errors.Level.ERROR);
@@ -99,11 +137,45 @@ public class Options {
mErrors.ERROR_COMMAND_LINE.add(ex.getMessage()); mErrors.ERROR_COMMAND_LINE.add(ex.getMessage());
} }
// Environment
mResult.mProduct = mEnv.get("TARGET_PRODUCT");
mResult.mVariant = mEnv.get("TARGET_BUILD_VARIANT");
mResult.mOutDir = mEnv.get("OUT_DIR");
validateArgs();
return mResult; return mResult;
} }
private void addWarning(Errors.Category category, String message) { /**
category.add(message); * For testing; don't generate errors about missing arguments
*/
public void setSkipRequiredArgValidation() {
mSkipRequiredArgValidation = true;
}
private void validateArgs() {
if (!mSkipRequiredArgValidation) {
if (mResult.mCKatiBin == null || "".equals(mResult.mCKatiBin)) {
addMissingArgError("--ckati_bin");
}
if (mResult.mProduct == null) {
addMissingEnvError("TARGET_PRODUCT");
}
if (mResult.mVariant == null) {
addMissingEnvError("TARGET_BUILD_VARIANT");
}
}
}
private void addMissingArgError(String argName) {
mErrors.ERROR_COMMAND_LINE.add("Required command line argument missing: "
+ argName);
}
private void addMissingEnvError(String envName) {
mErrors.ERROR_COMMAND_LINE.add("Required environment variable missing: "
+ envName);
} }
private String getNextNonFlagArg() { private String getNextNonFlagArg() {
@@ -117,6 +189,14 @@ public class Options {
return mArgs[mIndex]; return mArgs[mIndex];
} }
private String requireNextStringArg(String arg) throws ParseException {
final String val = getNextNonFlagArg();
if (val == null) {
throw new ParseException(arg + " requires a string argument.");
}
return val;
}
private int requireNextNumberArg(String arg) throws ParseException { private int requireNextNumberArg(String arg) throws ParseException {
final String val = getNextNonFlagArg(); final String val = getNextNonFlagArg();
if (val == null) { if (val == null) {
@@ -151,7 +231,7 @@ public class Options {
* <p> * <p>
* Adds errors encountered to Errors object. * Adds errors encountered to Errors object.
*/ */
public static Options parse(Errors errors, String[] args) { public static Options parse(Errors errors, String[] args, Map<String, String> env) {
return (new Parser(errors, args)).parse(); return (new Parser(errors, args, env)).parse();
} }
} }

View File

@@ -19,12 +19,24 @@ package com.android.build.config;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import java.util.HashMap;
public class OptionsTest { public class OptionsTest {
private Options parse(Errors errors, String[] args) {
final HashMap<String, String> env = new HashMap();
env.put("TARGET_PRODUCT", "test_product");
env.put("TARGET_BUILD_VARIANT", "user");
final Options.Parser parser = new Options.Parser(errors, args, env);
parser.setSkipRequiredArgValidation();
return parser.parse();
}
@Test @Test
public void testErrorMissingLast() { public void testErrorMissingLast() {
final Errors errors = new Errors(); final Errors errors = new Errors();
final Options options = Options.parse(errors, new String[] { final Options options = parse(errors, new String[] {
"--error" "--error"
}); });
@@ -37,7 +49,7 @@ public class OptionsTest {
public void testErrorMissingNotLast() { public void testErrorMissingNotLast() {
final Errors errors = new Errors(); final Errors errors = new Errors();
final Options options = Options.parse(errors, new String[] { final Options options = parse(errors, new String[] {
"--error", "--warning", "2" "--error", "--warning", "2"
}); });
@@ -50,7 +62,7 @@ public class OptionsTest {
public void testErrorNotNumeric() { public void testErrorNotNumeric() {
final Errors errors = new Errors(); final Errors errors = new Errors();
final Options options = Options.parse(errors, new String[] { final Options options = parse(errors, new String[] {
"--error", "notgood" "--error", "notgood"
}); });
@@ -63,7 +75,7 @@ public class OptionsTest {
public void testErrorInvalidError() { public void testErrorInvalidError() {
final Errors errors = new Errors(); final Errors errors = new Errors();
final Options options = Options.parse(errors, new String[] { final Options options = parse(errors, new String[] {
"--error", "50000" "--error", "50000"
}); });
@@ -76,7 +88,7 @@ public class OptionsTest {
public void testErrorOne() { public void testErrorOne() {
final Errors errors = new Errors(); final Errors errors = new Errors();
final Options options = Options.parse(errors, new String[] { final Options options = parse(errors, new String[] {
"--error", "2" "--error", "2"
}); });
@@ -89,7 +101,7 @@ public class OptionsTest {
public void testWarningOne() { public void testWarningOne() {
final Errors errors = new Errors(); final Errors errors = new Errors();
final Options options = Options.parse(errors, new String[] { final Options options = parse(errors, new String[] {
"--warning", "2" "--warning", "2"
}); });
@@ -102,7 +114,7 @@ public class OptionsTest {
public void testHideOne() { public void testHideOne() {
final Errors errors = new Errors(); final Errors errors = new Errors();
final Options options = Options.parse(errors, new String[] { final Options options = parse(errors, new String[] {
"--hide", "2" "--hide", "2"
}); });
@@ -110,5 +122,16 @@ public class OptionsTest {
Assert.assertEquals(Options.Action.DEFAULT, options.getAction()); Assert.assertEquals(Options.Action.DEFAULT, options.getAction());
Assert.assertFalse(errors.hadWarningOrError()); Assert.assertFalse(errors.hadWarningOrError());
} }
@Test
public void testEnv() {
final Errors errors = new Errors();
final Options options = parse(errors, new String[0]);
Assert.assertEquals("test_product", options.getProduct());
Assert.assertEquals("user", options.getVariant());
Assert.assertFalse(errors.hadWarningOrError());
}
} }