|  | // Copyright (c) 2013 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "tools/gn/input_conversion.h" | 
|  |  | 
|  | #include <memory> | 
|  | #include <utility> | 
|  |  | 
|  | #include "base/json/json_reader.h" | 
|  | #include "base/macros.h" | 
|  | #include "base/strings/string_split.h" | 
|  | #include "base/strings/string_util.h" | 
|  | #include "base/values.h" | 
|  | #include "tools/gn/build_settings.h" | 
|  | #include "tools/gn/err.h" | 
|  | #include "tools/gn/input_file.h" | 
|  | #include "tools/gn/label.h" | 
|  | #include "tools/gn/parse_tree.h" | 
|  | #include "tools/gn/parser.h" | 
|  | #include "tools/gn/scheduler.h" | 
|  | #include "tools/gn/scope.h" | 
|  | #include "tools/gn/settings.h" | 
|  | #include "tools/gn/tokenizer.h" | 
|  | #include "tools/gn/value.h" | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | enum ValueOrScope { | 
|  | PARSE_VALUE,  // Treat the input as an expression. | 
|  | PARSE_SCOPE,  // Treat the input as code and return the resulting scope. | 
|  | }; | 
|  |  | 
|  | // Sets the origin of the value and any nested values with the given node. | 
|  | Value ParseValueOrScope(const Settings* settings, | 
|  | const std::string& input, | 
|  | ValueOrScope what, | 
|  | const ParseNode* origin, | 
|  | Err* err) { | 
|  | // The memory for these will be kept around by the input file manager | 
|  | // so the origin parse nodes for the values will be preserved. | 
|  | InputFile* input_file; | 
|  | std::vector<Token>* tokens; | 
|  | std::unique_ptr<ParseNode>* parse_root_ptr; | 
|  | g_scheduler->input_file_manager()->AddDynamicInput(SourceFile(), &input_file, | 
|  | &tokens, &parse_root_ptr); | 
|  |  | 
|  | input_file->SetContents(input); | 
|  | if (origin) { | 
|  | // This description will be the blame for any error messages caused by | 
|  | // script parsing or if a value is blamed. It will say | 
|  | // "Error at <...>:line:char" so here we try to make a string for <...> | 
|  | // that reads well in this context. | 
|  | input_file->set_friendly_name("dynamically parsed input that " + | 
|  | origin->GetRange().begin().Describe(true) + | 
|  | " loaded "); | 
|  | } else { | 
|  | input_file->set_friendly_name("dynamic input"); | 
|  | } | 
|  |  | 
|  | *tokens = Tokenizer::Tokenize(input_file, err); | 
|  | if (err->has_error()) | 
|  | return Value(); | 
|  |  | 
|  | // Parse the file according to what we're looking for. | 
|  | if (what == PARSE_VALUE) | 
|  | *parse_root_ptr = Parser::ParseValue(*tokens, err); | 
|  | else | 
|  | *parse_root_ptr = Parser::Parse(*tokens, err);  // Will return a Block. | 
|  | if (err->has_error()) | 
|  | return Value(); | 
|  | ParseNode* parse_root = parse_root_ptr->get();  // For nicer syntax below. | 
|  |  | 
|  | // It's valid for the result to be a null pointer, this just means that the | 
|  | // script returned nothing. | 
|  | if (!parse_root) | 
|  | return Value(); | 
|  |  | 
|  | std::unique_ptr<Scope> scope = std::make_unique<Scope>(settings); | 
|  | Value result = parse_root->Execute(scope.get(), err); | 
|  | if (err->has_error()) | 
|  | return Value(); | 
|  |  | 
|  | // When we want the result as a scope, the result is actually the scope | 
|  | // we made, rather than the result of running the block (which will be empty). | 
|  | if (what == PARSE_SCOPE) { | 
|  | DCHECK(result.type() == Value::NONE); | 
|  | result = Value(origin, std::move(scope)); | 
|  | } | 
|  | return result; | 
|  | } | 
|  |  | 
|  | Value ParseList(const std::string& input, const ParseNode* origin, Err* err) { | 
|  | Value ret(origin, Value::LIST); | 
|  | std::vector<std::string> as_lines = base::SplitString( | 
|  | input, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); | 
|  |  | 
|  | // Trim one empty line from the end since the last line might end in a | 
|  | // newline. If the user wants more trimming, they'll specify "trim" in the | 
|  | // input conversion options. | 
|  | if (!as_lines.empty() && as_lines[as_lines.size() - 1].empty()) | 
|  | as_lines.resize(as_lines.size() - 1); | 
|  |  | 
|  | ret.list_value().reserve(as_lines.size()); | 
|  | for (const auto& line : as_lines) | 
|  | ret.list_value().push_back(Value(origin, line)); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | bool IsIdentifier(const base::StringPiece& buffer) { | 
|  | DCHECK(buffer.size() > 0); | 
|  | if (!Tokenizer::IsIdentifierFirstChar(buffer[0])) | 
|  | return false; | 
|  | for (size_t i = 1; i < buffer.size(); i++) | 
|  | if (!Tokenizer::IsIdentifierContinuingChar(buffer[i])) | 
|  | return false; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | Value ParseJSONValue(const Settings* settings, | 
|  | const base::Value& value, | 
|  | const ParseNode* origin, | 
|  | InputFile* input_file, | 
|  | Err* err) { | 
|  | switch (value.type()) { | 
|  | case base::Value::Type::NONE: | 
|  | *err = Err(origin, "Null values are not supported."); | 
|  | return Value(); | 
|  | case base::Value::Type::BOOLEAN: | 
|  | return Value(origin, value.GetBool()); | 
|  | case base::Value::Type::INTEGER: | 
|  | return Value(origin, static_cast<int64_t>(value.GetInt())); | 
|  | case base::Value::Type::STRING: | 
|  | return Value(origin, value.GetString()); | 
|  | case base::Value::Type::BINARY: | 
|  | *err = Err(origin, "Binary values are not supported."); | 
|  | return Value(); | 
|  | case base::Value::Type::DICTIONARY: { | 
|  | std::unique_ptr<Scope> scope = std::make_unique<Scope>(settings); | 
|  | for (const auto& it : value.DictItems()) { | 
|  | Value parsed_value = | 
|  | ParseJSONValue(settings, it.second, origin, input_file, err); | 
|  | if (!IsIdentifier(it.first)) { | 
|  | *err = Err(origin, "Invalid identifier \"" + it.first + "\"."); | 
|  | return Value(); | 
|  | } | 
|  | // Search for the key in the input file. We know it's present because | 
|  | // it was parsed by the JSON reader, but we need its location to | 
|  | // construct a StringPiece that can be used as key in the Scope. | 
|  | size_t off = input_file->contents().find("\"" + it.first + "\""); | 
|  | if (off == std::string::npos) { | 
|  | *err = Err(origin, "Invalid encoding \"" + it.first + "\"."); | 
|  | return Value(); | 
|  | } | 
|  | base::StringPiece key(&input_file->contents()[off + 1], | 
|  | it.first.size()); | 
|  | scope->SetValue(key, std::move(parsed_value), origin); | 
|  | } | 
|  | return Value(origin, std::move(scope)); | 
|  | } | 
|  | case base::Value::Type::LIST: { | 
|  | Value result(origin, Value::LIST); | 
|  | result.list_value().reserve(value.GetList().size()); | 
|  | for (const auto& val : value.GetList()) { | 
|  | Value parsed_value = | 
|  | ParseJSONValue(settings, val, origin, input_file, err); | 
|  | result.list_value().push_back(parsed_value); | 
|  | } | 
|  | return result; | 
|  | } | 
|  | } | 
|  | return Value(); | 
|  | } | 
|  |  | 
|  | // Parses the JSON string and converts it to GN value. | 
|  | Value ParseJSON(const Settings* settings, | 
|  | const std::string& input, | 
|  | const ParseNode* origin, | 
|  | Err* err) { | 
|  | InputFile* input_file; | 
|  | std::vector<Token>* tokens; | 
|  | std::unique_ptr<ParseNode>* parse_root_ptr; | 
|  | g_scheduler->input_file_manager()->AddDynamicInput(SourceFile(), &input_file, | 
|  | &tokens, &parse_root_ptr); | 
|  | input_file->SetContents(input); | 
|  |  | 
|  | int error_code_out; | 
|  | std::string error_msg_out; | 
|  | std::unique_ptr<base::Value> value = base::JSONReader::ReadAndReturnError( | 
|  | input, base::JSONParserOptions::JSON_PARSE_RFC, &error_code_out, | 
|  | &error_msg_out); | 
|  | if (!value) { | 
|  | *err = Err(origin, "Input is not a valid JSON: " + error_msg_out); | 
|  | return Value(); | 
|  | } | 
|  |  | 
|  | return ParseJSONValue(settings, *value, origin, input_file, err); | 
|  | } | 
|  |  | 
|  | // Backend for ConvertInputToValue, this takes the extracted string for the | 
|  | // input conversion so we can recursively call ourselves to handle the optional | 
|  | // "trim" prefix. This original value is also kept for the purposes of throwing | 
|  | // errors. | 
|  | Value DoConvertInputToValue(const Settings* settings, | 
|  | const std::string& input, | 
|  | const ParseNode* origin, | 
|  | const Value& original_input_conversion, | 
|  | const std::string& input_conversion, | 
|  | Err* err) { | 
|  | if (input_conversion.empty()) | 
|  | return Value();  // Empty string means discard the result. | 
|  |  | 
|  | const char kTrimPrefix[] = "trim "; | 
|  | if (base::StartsWith(input_conversion, kTrimPrefix, | 
|  | base::CompareCase::SENSITIVE)) { | 
|  | std::string trimmed; | 
|  | base::TrimWhitespaceASCII(input, base::TRIM_ALL, &trimmed); | 
|  |  | 
|  | // Remove "trim" prefix from the input conversion and re-run. | 
|  | return DoConvertInputToValue( | 
|  | settings, trimmed, origin, original_input_conversion, | 
|  | input_conversion.substr(arraysize(kTrimPrefix) - 1), err); | 
|  | } | 
|  |  | 
|  | if (input_conversion == "value") | 
|  | return ParseValueOrScope(settings, input, PARSE_VALUE, origin, err); | 
|  | if (input_conversion == "string") | 
|  | return Value(origin, input); | 
|  | if (input_conversion == "list lines") | 
|  | return ParseList(input, origin, err); | 
|  | if (input_conversion == "scope") | 
|  | return ParseValueOrScope(settings, input, PARSE_SCOPE, origin, err); | 
|  | if (input_conversion == "json") | 
|  | return ParseJSON(settings, input, origin, err); | 
|  |  | 
|  | *err = Err(original_input_conversion, "Not a valid input_conversion.", | 
|  | "Run gn help input_conversion to see your options."); | 
|  | return Value(); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | const char kInputConversion_Help[] = | 
|  | R"(input_conversion: Specifies how to transform input to a variable. | 
|  |  | 
|  | input_conversion is an argument to read_file and exec_script that specifies | 
|  | how the result of the read operation should be converted into a variable. | 
|  |  | 
|  | "" (the default) | 
|  | Discard the result and return None. | 
|  |  | 
|  | "list lines" | 
|  | Return the file contents as a list, with a string for each line. The | 
|  | newlines will not be present in the result. The last line may or may not | 
|  | end in a newline. | 
|  |  | 
|  | After splitting, each individual line will be trimmed of whitespace on | 
|  | both ends. | 
|  |  | 
|  | "scope" | 
|  | Execute the block as GN code and return a scope with the resulting values | 
|  | in it. If the input was: | 
|  | a = [ "hello.cc", "world.cc" ] | 
|  | b = 26 | 
|  | and you read the result into a variable named "val", then you could | 
|  | access contents the "." operator on "val": | 
|  | sources = val.a | 
|  | some_count = val.b | 
|  |  | 
|  | "string" | 
|  | Return the file contents into a single string. | 
|  |  | 
|  | "value" | 
|  | Parse the input as if it was a literal rvalue in a buildfile. Examples of | 
|  | typical program output using this mode: | 
|  | [ "foo", "bar" ]     (result will be a list) | 
|  | or | 
|  | "foo bar"            (result will be a string) | 
|  | or | 
|  | 5                    (result will be an integer) | 
|  |  | 
|  | Note that if the input is empty, the result will be a null value which | 
|  | will produce an error if assigned to a variable. | 
|  |  | 
|  | "json" | 
|  | Parse the input as a JSON and convert it to equivalent GN rvalue. The data | 
|  | type mapping is: | 
|  | a string in JSON maps to string in GN | 
|  | an integer in JSON maps to integer in GN | 
|  | a float in JSON is unsupported and will result in an error | 
|  | an object in JSON maps to scope in GN | 
|  | an array in JSON maps to list in GN | 
|  | a boolean in JSON maps to boolean in GN | 
|  | a null in JSON is unsupported and will result in an error | 
|  |  | 
|  | Nota that the dictionary keys have to be valid GN identifiers otherwise | 
|  | they will produce an error. | 
|  |  | 
|  | "trim ..." | 
|  | Prefixing any of the other transformations with the word "trim" will | 
|  | result in whitespace being trimmed from the beginning and end of the | 
|  | result before processing. | 
|  |  | 
|  | Examples: "trim string" or "trim list lines" | 
|  |  | 
|  | Note that "trim value" is useless because the value parser skips | 
|  | whitespace anyway. | 
|  | )"; | 
|  |  | 
|  | Value ConvertInputToValue(const Settings* settings, | 
|  | const std::string& input, | 
|  | const ParseNode* origin, | 
|  | const Value& input_conversion_value, | 
|  | Err* err) { | 
|  | if (input_conversion_value.type() == Value::NONE) | 
|  | return Value();  // Allow null inputs to mean discard the result. | 
|  | if (!input_conversion_value.VerifyTypeIs(Value::STRING, err)) | 
|  | return Value(); | 
|  | return DoConvertInputToValue(settings, input, origin, input_conversion_value, | 
|  | input_conversion_value.string_value(), err); | 
|  | } |