text: /*
   * 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;
  }
  