blob: 9315bd7fab282d273dfee5fce9d1ca194386e627 [file] [log] [blame]
/*
* Copyright 2016 The Kythe 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.
*/
// objc_extractor_bazel is a Objective-C extractor meant to be run as a Bazel
// extra_action. It may be used with third_party/bazel/get_devdir.sh and
// third_party/bazel/get_sdkroot.sh to fix placeholders left in arguments by
// Bazel.
//
// For example:
//
// action_listener(
// name = "extract_kindex",
// extra_actions = [":extra_action"],
// mnemonics = ["ObjcCompile"],
// visibility = ["//visibility:public"],
// )
//
// extra_action(
// name = "extra_action",
// cmd = "$(location :objc_extractor_binary) \
// $(EXTRA_ACTION_FILE) \
// $(output $(ACTION_ID).objc.kindex) \
// $(location :vnames_config) \
// $(location :get_devdir) \
// $(location :get_sdkroot)",
// data = [
// ":get_devdir",
// ":get_sdkroot",
// ":vnames_config",
// ],
// out_templates = ["$(ACTION_ID).objc.kindex"],
// tools = [":objc_extractor_binary"],
// )
//
// # In this example, the extractor binary is pre-built.
// filegroup(
// name = "objc_extractor_binary",
// srcs = ["objc_extractor_bazel"],
// )
//
// filegroup(
// name = "vnames_config",
// srcs = ["vnames.json"],
// )
//
// sh_binary(
// name = "get_devdir",
// srcs = ["get_devdir.sh"],
// )
//
// sh_binary(
// name = "get_sdkroot",
// srcs = ["get_sdkroot.sh"],
// )
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include "absl/strings/str_format.h"
#include "cxx_extractor.h"
#include "gflags/gflags.h"
#include "glog/logging.h"
#include "google/protobuf/io/coded_stream.h"
#include "google/protobuf/io/zero_copy_stream.h"
#include "google/protobuf/io/zero_copy_stream_impl.h"
#include "google/protobuf/stubs/common.h"
#include "kythe/cxx/extractor/language.h"
#include "objc_bazel_support.h"
#include "third_party/bazel/src/main/protobuf/extra_actions_base.pb.h"
struct XAState {
std::string extra_action_file;
std::string output_file;
std::string vname_config;
std::string devdir_script;
std::string sdkroot_script;
};
static bool ContainsUnsupportedArg(const std::vector<std::string>& args) {
for (const auto& arg : args) {
// We do not support compilations using modules yet.
if (arg == "-fmodules") {
return true;
}
}
return false;
}
static bool LoadSpawnInfo(const XAState& xa_state,
const blaze::ExtraActionInfo& info,
kythe::ExtractorConfiguration& config) {
blaze::SpawnInfo spawn_info = info.GetExtension(blaze::SpawnInfo::spawn_info);
std::vector<std::string> args;
// If the user didn't specify a script path, don't mutate the arguments in the
// extra action.
if (xa_state.devdir_script.empty() || xa_state.sdkroot_script.empty()) {
for (const auto& i : spawn_info.argument()) {
std::string arg = i;
args.push_back(arg);
}
} else {
auto cmdPrefix = kythe::BuildEnvVarCommandPrefix(spawn_info.variable());
auto devdir = kythe::RunScript(cmdPrefix + xa_state.devdir_script);
auto sdkroot = kythe::RunScript(cmdPrefix + xa_state.sdkroot_script);
kythe::FillWithFixedArgs(args, spawn_info, devdir, sdkroot);
}
if (ContainsUnsupportedArg(args)) {
LOG(ERROR) << "Not extracting " << info.owner()
<< " because it had an unsupported argument.";
return false;
}
config.SetOutputFile(xa_state.output_file);
config.SetArgs(args);
config.SetVNameConfig(xa_state.vname_config);
config.SetTargetName(info.owner());
if (spawn_info.output_file_size() > 0) {
config.SetCompilationOutputPath(spawn_info.output_file(0));
}
return true;
}
static bool LoadCppInfo(const XAState& xa_state,
const blaze::ExtraActionInfo& info,
kythe::ExtractorConfiguration& config) {
blaze::CppCompileInfo cpp_info =
info.GetExtension(blaze::CppCompileInfo::cpp_compile_info);
std::vector<std::string> args;
// If the user didn't specify a script path, don't mutate the arguments in the
// extra action.
if (xa_state.devdir_script.empty() || xa_state.sdkroot_script.empty()) {
args.push_back(cpp_info.tool());
for (const auto& i : cpp_info.compiler_option()) {
std::string arg = i;
args.push_back(arg);
}
} else {
auto cmdPrefix = kythe::BuildEnvVarCommandPrefix(cpp_info.variable());
auto devdir = kythe::RunScript(cmdPrefix + xa_state.devdir_script);
auto sdkroot = kythe::RunScript(cmdPrefix + xa_state.sdkroot_script);
kythe::FillWithFixedArgs(args, cpp_info, devdir, sdkroot);
}
if (ContainsUnsupportedArg(args)) {
LOG(ERROR) << "Not extracting " << info.owner()
<< " because it had an unsupported argument.";
return false;
}
config.SetOutputFile(xa_state.output_file);
config.SetArgs(args);
config.SetVNameConfig(xa_state.vname_config);
config.SetTargetName(info.owner());
config.SetCompilationOutputPath(cpp_info.output_file());
return true;
}
static bool LoadExtraAction(const XAState& xa_state,
kythe::ExtractorConfiguration& config) {
using namespace google::protobuf::io;
blaze::ExtraActionInfo info;
int fd =
open(xa_state.extra_action_file.c_str(), O_RDONLY, S_IREAD | S_IWRITE);
CHECK_GE(fd, 0) << "Couldn't open input file " << xa_state.extra_action_file;
FileInputStream file_input_stream(fd);
CodedInputStream coded_input_stream(&file_input_stream);
coded_input_stream.SetTotalBytesLimit(INT_MAX);
CHECK(info.ParseFromCodedStream(&coded_input_stream));
close(fd);
if (info.HasExtension(blaze::SpawnInfo::spawn_info)) {
return LoadSpawnInfo(xa_state, info, config);
} else if (info.HasExtension(blaze::CppCompileInfo::cpp_compile_info)) {
return LoadCppInfo(xa_state, info, config);
}
LOG(ERROR)
<< "ObjcCompile Extra Action didn't have SpawnInfo or CppCompileInfo.";
return false;
}
int main(int argc, char* argv[]) {
GOOGLE_PROTOBUF_VERIFY_VERSION;
google::InitGoogleLogging(argv[0]);
gflags::SetVersionString("0.2");
if (argc != 4 && argc != 6) {
absl::FPrintF(
stderr,
"Invalid number of arguments:\n\tCall as %s extra-action-file "
"output-file vname-config [devdir-script sdkroot-script]\n",
argv[0]);
return 1;
}
XAState xa_state;
xa_state.extra_action_file = argv[1];
xa_state.output_file = argv[2];
xa_state.vname_config = argv[3];
if (argc == 6) {
xa_state.devdir_script = argv[4];
xa_state.sdkroot_script = argv[5];
} else {
xa_state.devdir_script = "";
xa_state.sdkroot_script = "";
}
kythe::ExtractorConfiguration config;
bool success = LoadExtraAction(xa_state, config);
if (success) {
config.Extract(kythe::supported_language::Language::kObjectiveC);
} else {
LOG(ERROR) << "Couldn't load extra action";
// If we couldn't extract, just write an empty output file. This way the
// extra_action will be a success from bazel's perspective, which should
// remove some log spam.
auto F = fopen(xa_state.output_file.c_str(), "w");
if (F != nullptr) {
fclose(F);
}
}
google::protobuf::ShutdownProtobufLibrary();
return 0;
}