| // 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/main/cpp/util/file_platform.h" | 
 | #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; | 
 |  | 
 | static std::string GetRunfilesDir(const char* argv0) { | 
 |   string runfiles_dir; | 
 |   // If RUNFILES_DIR is already set (probably we are either in a test or in a | 
 |   // data dependency) then use it. | 
 |   if (GetEnv("RUNFILES_DIR", &runfiles_dir)) { | 
 |     return runfiles_dir; | 
 |   } | 
 |   // Otherwise this is probably a top-level non-test binary (e.g. a genrule | 
 |   // tool) and should look for its runfiles beside the executable. | 
 |   return GetBinaryPathWithExtension(argv0) + ".runfiles"; | 
 | } | 
 |  | 
 | BinaryLauncherBase::BinaryLauncherBase( | 
 |     const LaunchDataParser::LaunchInfo& _launch_info, int argc, char* argv[]) | 
 |     : launch_info(_launch_info), | 
 |       manifest_file(FindManifestFile(argv[0])), | 
 |       runfiles_dir(GetRunfilesDir(argv[0])), | 
 |       workspace_name(GetLaunchInfoByKey(WORKSPACE_NAME)) { | 
 |   for (int i = 0; i < argc; i++) { | 
 |     commandline_arguments.push_back(argv[i]); | 
 |   } | 
 |   // Prefer to use the runfiles manifest, if it exists, but otherwise the | 
 |   // runfiles directory will be used by default. On Windows, the manifest is | 
 |   // used locally, and the runfiles directory is used remotely. | 
 |   if (manifest_file != "") { | 
 |     ParseManifestFile(&manifest_file_map, 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)) { | 
 |     return ""; | 
 |   } | 
 |   // 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 { | 
 |   // If the manifest file map is empty, then we're using the runfiles directory | 
 |   // instead. | 
 |   if (manifest_file_map.empty()) { | 
 |     if (blaze_util::IsAbsolute(path)) { | 
 |       return path; | 
 |     } | 
 |  | 
 |     string query_path = runfiles_dir; | 
 |     if (need_workspace_name) { | 
 |       query_path += "/" + this->workspace_name; | 
 |     } | 
 |     query_path += "/" + path; | 
 |     return query_path; | 
 |   } | 
 |  | 
 |   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; | 
 |   } | 
 |   if (manifest_file != "") { | 
 |     SetEnv("RUNFILES_MANIFEST_ONLY", "1"); | 
 |     SetEnv("RUNFILES_MANIFEST_FILE", manifest_file); | 
 |   } else { | 
 |     SetEnv("RUNFILES_DIR", runfiles_dir); | 
 |   } | 
 |   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 |