text: // Copyright 2017 The Bazel 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.
  
  #include <windows.h>
  #include <algorithm>
  #include <fstream>
  #include <iostream>
  #include <sstream>
  #include <string>
  #include <vector>
  
  #include "src/tools/launcher/launcher.h"
  #include "src/tools/launcher/util/data_parser.h"
  #include "src/tools/launcher/util/launcher_util.h"
  
  namespace bazel {
  namespace launcher {
  
  using std::ifstream;
  using std::ostringstream;
  using std::string;
  using std::unordered_map;
  using std::vector;
  
  BinaryLauncherBase::BinaryLauncherBase(
      const LaunchDataParser::LaunchInfo& _launch_info, int argc, char* argv[])
      : launch_info(_launch_info),
        manifest_file(FindManifestFile(argv[0])),
        workspace_name(GetLaunchInfoByKey(WORKSPACE_NAME)) {
    for (int i = 0; i < argc; i++) {
      this->commandline_arguments.push_back(argv[i]);
    }
    ParseManifestFile(&this->manifest_file_map, this->manifest_file);
  }
  
  static bool FindManifestFileImpl(const char* argv0, string* result) {
    // If this binary X runs as the data-dependency of some other binary Y, then
    // X has no runfiles manifest/directory and should use Y's.
    if (GetEnv("RUNFILES_MANIFEST_FILE", result) &&
        DoesFilePathExist(result->c_str())) {
      return true;
    }
  
    string directory;
    if (GetEnv("RUNFILES_DIR", &directory)) {
      *result = directory + "/MANIFEST";
      if (DoesFilePathExist(result->c_str())) {
        return true;
      }
    }
  
    // If this binary X runs by itself (not as a data-dependency of another
    // binary), then look for the manifest in a runfiles directory next to the
    // main binary, then look for it (the manifest) next to the main binary.
    directory = GetBinaryPathWithExtension(argv0) + ".runfiles";
    *result = directory + "/MANIFEST";
    if (DoesFilePathExist(result->c_str())) {
      return true;
    }
  
    *result = directory + "_manifest";
    if (DoesFilePathExist(result->c_str())) {
      return true;
    }
  
    return false;
  }
  
  string BinaryLauncherBase::FindManifestFile(const char* argv0) {
    string manifest_file;
    if (!FindManifestFileImpl(argv0, &manifest_file)) {
      die("Couldn't find runfiles manifest file.");
    }
    // The path will be set as the RUNFILES_MANIFEST_FILE envvar and used by the
    // shell script, so let's convert backslashes to forward slashes.
    std::replace(manifest_file.begin(), manifest_file.end(), '\\', '/');
    return manifest_file;
  }
  
  string BinaryLauncherBase::GetRunfilesPath() const {
    string runfiles_path =
        GetBinaryPathWithExtension(this->commandline_arguments[0]) + ".runfiles";
    std::replace(runfiles_path.begin(), runfiles_path.end(), '/', '\\');
    return runfiles_path;
  }
  
  void BinaryLauncherBase::ParseManifestFile(ManifestFileMap* manifest_file_map,
                                             const string& manifest_path) {
    ifstream manifest_file(AsAbsoluteWindowsPath(manifest_path.c_str()).c_str());
  
    if (!manifest_file) {
      die("Couldn't open MANIFEST file: %s", manifest_path.c_str());
    }
  
    string line;
    while (getline(manifest_file, line)) {
      size_t space_pos = line.find_first_of(' ');
      if (space_pos == string::npos) {
        die("Wrong MANIFEST format at line: %s", line.c_str());
      }
      string key = line.substr(0, space_pos);
      string value = line.substr(space_pos + 1);
      manifest_file_map->insert(make_pair(key, value));
    }
  }
  
  string BinaryLauncherBase::Rlocation(const string& path,
                                       bool need_workspace_name) const {
    string query_path = path;
    if (need_workspace_name) {
      query_path = this->workspace_name + "/" + path;
    }
    auto entry = manifest_file_map.find(query_path);
    if (entry == manifest_file_map.end()) {
      die("Rlocation failed on %s, path doesn't exist in MANIFEST file",
          query_path.c_str());
    }
    return entry->second;
  }
  
  string BinaryLauncherBase::GetLaunchInfoByKey(const string& key) {
    auto item = launch_info.find(key);
    if (item == launch_info.end()) {
      die("Cannot find key \"%s\" from launch data.\n", key.c_str());
    }
    return item->second;
  }
  
  const vector<string>& BinaryLauncherBase::GetCommandlineArguments() const {
    return this->commandline_arguments;
  }
  
  void BinaryLauncherBase::CreateCommandLine(
      CmdLine* result, const string& executable,
      const vector<string>& arguments) const {
    ostringstream cmdline;
    cmdline << '\"' << executable << '\"';
    for (const auto& s : arguments) {
      cmdline << ' ' << s;
    }
  
    string cmdline_str = cmdline.str();
    if (cmdline_str.size() >= MAX_CMDLINE_LENGTH) {
      die("Command line too long: %s", cmdline_str.c_str());
    }
  
    // Copy command line into a mutable buffer.
    // CreateProcess is allowed to mutate its command line argument.
    strncpy(result->cmdline, cmdline_str.c_str(), MAX_CMDLINE_LENGTH - 1);
    result->cmdline[MAX_CMDLINE_LENGTH - 1] = 0;
  }
  
  bool BinaryLauncherBase::PrintLauncherCommandLine(
      const string& executable, const vector<string>& arguments) const {
    bool has_print_cmd_flag = false;
    for (const auto& arg : arguments) {
      has_print_cmd_flag |= (arg == "--print_launcher_command");
    }
    if (has_print_cmd_flag) {
      printf("%s\n", executable.c_str());
      for (const auto& arg : arguments) {
        printf("%s\n", arg.c_str());
      }
    }
    return has_print_cmd_flag;
  }
  
  ExitCode BinaryLauncherBase::LaunchProcess(const string& executable,
                                             const vector<string>& arguments,
                                             bool suppressOutput) const {
    if (PrintLauncherCommandLine(executable, arguments)) {
      return 0;
    }
    SetEnv("RUNFILES_MANIFEST_ONLY", "1");
    SetEnv("RUNFILES_MANIFEST_FILE", manifest_file);
    CmdLine cmdline;
    CreateCommandLine(&cmdline, executable, arguments);
    PROCESS_INFORMATION processInfo = {0};
    STARTUPINFOA startupInfo = {0};
    startupInfo.cb = sizeof(startupInfo);
    BOOL ok = CreateProcessA(
        /* lpApplicationName */ NULL,
        /* lpCommandLine */ cmdline.cmdline,
        /* lpProcessAttributes */ NULL,
        /* lpThreadAttributes */ NULL,
        /* bInheritHandles */ FALSE,
        /* dwCreationFlags */
            suppressOutput ? CREATE_NO_WINDOW  // no console window => no output
                           : 0,
        /* lpEnvironment */ NULL,
        /* lpCurrentDirectory */ NULL,
        /* lpStartupInfo */ &startupInfo,
        /* lpProcessInformation */ &processInfo);
    if (!ok) {
      PrintError("Cannot launch process: %s\nReason: %s",
                 cmdline.cmdline,
                 GetLastErrorString().c_str());
      return GetLastError();
    }
    WaitForSingleObject(processInfo.hProcess, INFINITE);
    ExitCode exit_code;
    GetExitCodeProcess(processInfo.hProcess,
                       reinterpret_cast<LPDWORD>(&exit_code));
    CloseHandle(processInfo.hProcess);
    CloseHandle(processInfo.hThread);
    return exit_code;
  }
  
  }  // namespace launcher
  }  // namespace bazel
  