// 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 #include #include #include #include #include "src/main/cpp/util/file.h" #include "src/main/cpp/util/file_platform.h" #include "src/main/cpp/util/path_platform.h" #include "src/main/cpp/util/strings.h" #include "src/main/native/windows/file.h" #include "src/tools/launcher/java_launcher.h" #include "src/tools/launcher/util/launcher_util.h" namespace bazel { namespace launcher { using std::getline; using std::string; using std::vector; using std::wofstream; using std::wostringstream; using std::wstring; using std::wstringstream; // The runfile path of java binary, eg. local_jdk/bin/java.exe static constexpr const char* JAVA_BIN_PATH = "java_bin_path"; static constexpr const char* JAR_BIN_PATH = "jar_bin_path"; static constexpr const char* CLASSPATH = "classpath"; static constexpr const char* JAVA_START_CLASS = "java_start_class"; static constexpr const char* JVM_FLAGS = "jvm_flags"; // Check if a string start with a certain prefix. // If it's true, store the substring without the prefix in value. // If value is quoted, then remove the quotes. static bool GetFlagValue(const wstring& str, const wstring& prefix, wstring* value_ptr) { if (str.compare(0, prefix.length(), prefix)) { return false; } wstring& value = *value_ptr; value = str.substr(prefix.length()); int len = value.length(); if (len >= 2 && value[0] == L'"' && value[len - 1] == L'"') { value = value.substr(1, len - 2); } return true; } // Parses one launcher flag and updates this object's state accordingly. // // Returns true if the flag is a valid launcher flag; false otherwise. bool JavaBinaryLauncher::ProcessWrapperArgument(const wstring& argument) { wstring flag_value; if (argument.compare(L"--debug") == 0) { wstring default_jvm_debug_port; if (GetEnv(L"DEFAULT_JVM_DEBUG_PORT", &default_jvm_debug_port)) { this->jvm_debug_port = default_jvm_debug_port; } else { this->jvm_debug_port = L"5005"; } } else if (GetFlagValue(argument, L"--debug=", &flag_value)) { this->jvm_debug_port = flag_value; } else if (GetFlagValue(argument, L"--main_advice=", &flag_value)) { this->main_advice = flag_value; } else if (GetFlagValue(argument, L"--main_advice_classpath=", &flag_value)) { this->main_advice_classpath = flag_value; } else if (GetFlagValue(argument, L"--jvm_flag=", &flag_value)) { this->jvm_flags_cmdline.push_back(flag_value); } else if (GetFlagValue(argument, L"--jvm_flags=", &flag_value)) { wstringstream flag_value_ss(flag_value); wstring item; while (getline(flag_value_ss, item, L' ')) { this->jvm_flags_cmdline.push_back(item); } } else if (argument.compare(L"--singlejar") == 0) { this->singlejar = true; } else if (argument.compare(L"--print_javabin") == 0) { this->print_javabin = true; } else if (GetFlagValue(argument, L"--classpath_limit=", &flag_value)) { this->classpath_limit = std::stoi(flag_value); } else { return false; } return true; } vector JavaBinaryLauncher::ProcessesCommandLine() { vector args; bool first = 1; for (const auto& arg : this->GetCommandlineArguments()) { // Skip the first arugment. if (first) { first = 0; continue; } wstring flag_value; // TODO(pcloudy): Should rename this flag to --native_launcher_flag. // But keep it as it is for now to be consistent with the shell script // launcher. if (GetFlagValue(arg, L"--wrapper_script_flag=", &flag_value)) { if (!ProcessWrapperArgument(flag_value)) { die(L"invalid wrapper argument '%s'", arg.c_str()); } } else if (!args.empty() || !ProcessWrapperArgument(arg)) { args.push_back(arg); } } return args; } // Return an absolute normalized path for the directory of manifest jar static wstring GetManifestJarDir(const wstring& binary_base_path) { wstring abs_manifest_jar_dir; std::size_t slash = binary_base_path.find_last_of(L"/\\"); if (slash == wstring::npos) { abs_manifest_jar_dir = L""; } else { abs_manifest_jar_dir = binary_base_path.substr(0, slash); } if (!blaze_util::IsAbsolute(binary_base_path)) { abs_manifest_jar_dir = blaze_util::GetCwdW() + L"\\" + abs_manifest_jar_dir; } wstring result; if (!NormalizePath(abs_manifest_jar_dir, &result)) { die(L"GetManifestJarDir Failed"); } return result; } static void WriteJarClasspath(const wstring& jar_path, wostringstream* manifest_classpath) { *manifest_classpath << L' '; if (jar_path.find_first_of(L" \\") != wstring::npos) { for (const auto& x : jar_path) { if (x == L' ') { *manifest_classpath << L"%20"; } if (x == L'\\') { *manifest_classpath << L"/"; } else { *manifest_classpath << x; } } } else { *manifest_classpath << jar_path; } } wstring JavaBinaryLauncher::GetJunctionBaseDir() { wstring binary_base_path = GetBinaryPathWithExtension(this->GetCommandlineArguments()[0]); wstring result; if (!NormalizePath(binary_base_path + L".j", &result)) { die(L"Failed to get normalized junction base directory."); } return result; } void JavaBinaryLauncher::DeleteJunctionBaseDir() { wstring junction_base_dir_norm = GetJunctionBaseDir(); if (!DoesDirectoryPathExist(junction_base_dir_norm.c_str())) { return; } vector junctions; blaze_util::GetAllFilesUnderW(junction_base_dir_norm, &junctions); for (const auto& junction : junctions) { if (!DeleteDirectoryByPath(junction.c_str())) { PrintError(L"Failed to delete junction directory: %hs", GetLastErrorString().c_str()); } } if (!DeleteDirectoryByPath(junction_base_dir_norm.c_str())) { PrintError(L"Failed to delete junction directory: %hs", GetLastErrorString().c_str()); } } wstring JavaBinaryLauncher::CreateClasspathJar(const wstring& classpath) { wstring binary_base_path = GetBinaryPathWithoutExtension(this->GetCommandlineArguments()[0]); wstring abs_manifest_jar_dir_norm = GetManifestJarDir(binary_base_path); wostringstream manifest_classpath; manifest_classpath << L"Class-Path:"; wstringstream classpath_ss(classpath); wstring path, path_norm; // A set to store all junctions created. // The key is the target path, the value is the junction path. std::unordered_map jar_dirs; wstring junction_base_dir_norm = GetJunctionBaseDir(); int junction_count = 0; // Make sure the junction base directory doesn't exist already. DeleteJunctionBaseDir(); blaze_util::MakeDirectoriesW(junction_base_dir_norm, 0755); while (getline(classpath_ss, path, L';')) { if (blaze_util::IsAbsolute(path)) { if (!NormalizePath(path, &path_norm)) { die(L"CreateClasspathJar failed"); } // If two paths are under different drives, we should create a junction to // the jar's directory if (path_norm[0] != abs_manifest_jar_dir_norm[0]) { wstring jar_dir = GetParentDirFromPath(path_norm); wstring jar_base_name = GetBaseNameFromPath(path_norm); wstring junction; auto search = jar_dirs.find(jar_dir); if (search == jar_dirs.end()) { junction = junction_base_dir_norm + L"\\" + std::to_wstring(junction_count++); wstring error; if (bazel::windows::CreateJunction(junction, jar_dir, &error) != bazel::windows::CreateJunctionResult::kSuccess) { die(L"CreateClasspathJar failed: %s", error.c_str()); } jar_dirs.insert(std::make_pair(jar_dir, junction)); } else { junction = search->second; } path_norm = junction + L"\\" + jar_base_name; } if (!RelativeTo(path_norm, abs_manifest_jar_dir_norm, &path)) { die(L"CreateClasspathJar failed"); } } WriteJarClasspath(path, &manifest_classpath); } wstring rand_id = L"-" + GetRandomStr(10); wstring jar_manifest_file_path = binary_base_path + rand_id + L".jar_manifest"; wofstream jar_manifest_file(jar_manifest_file_path); jar_manifest_file << L"Manifest-Version: 1.0\n"; // No line in the MANIFEST.MF file may be longer than 72 bytes. // A space prefix indicates the line is still the content of the last // attribute. wstring manifest_classpath_str = manifest_classpath.str(); for (size_t i = 0; i < manifest_classpath_str.length(); i += 71) { if (i > 0) { jar_manifest_file << L" "; } jar_manifest_file << manifest_classpath_str.substr(i, 71) << "\n"; } jar_manifest_file.close(); // Create the command for generating classpath jar. wstring manifest_jar_path = binary_base_path + rand_id + L"-classpath.jar"; wstring jar_bin = this->Rlocation(this->GetLaunchInfoByKey(JAR_BIN_PATH)); vector arguments; arguments.push_back(L"cvfm"); arguments.push_back(manifest_jar_path); arguments.push_back(jar_manifest_file_path); if (this->LaunchProcess(jar_bin, arguments, /* suppressOutput */ true) != 0) { die(L"Couldn't create classpath jar: %s", manifest_jar_path.c_str()); } // Delete jar_manifest_file after classpath jar is created. DeleteFileByPath(jar_manifest_file_path.c_str()); return manifest_jar_path; } ExitCode JavaBinaryLauncher::Launch() { // Parse the original command line. vector remaining_args = this->ProcessesCommandLine(); // Set JAVA_RUNFILES wstring java_runfiles; if (!GetEnv(L"JAVA_RUNFILES", &java_runfiles)) { java_runfiles = this->GetRunfilesPath(); } SetEnv(L"JAVA_RUNFILES", java_runfiles); // Print Java binary path if needed wstring java_bin = this->Rlocation(this->GetLaunchInfoByKey(JAVA_BIN_PATH), /*need_workspace_name =*/false); if (this->print_javabin || this->GetLaunchInfoByKey(JAVA_START_CLASS) == L"--print_javabin") { wprintf(L"%s\n", java_bin.c_str()); return 0; } wostringstream classpath; // Run deploy jar if needed, otherwise generate the CLASSPATH by rlocation. if (this->singlejar) { wstring deploy_jar = GetBinaryPathWithoutExtension(this->GetCommandlineArguments()[0]) + L"_deploy.jar"; if (!DoesFilePathExist(deploy_jar.c_str())) { die(L"Option --singlejar was passed, but %s does not exist.\n (You may " "need to build it explicitly.)", deploy_jar.c_str()); } classpath << deploy_jar << L';'; } else { // Add main advice classpath if exists if (!this->main_advice_classpath.empty()) { classpath << this->main_advice_classpath << L';'; } wstring path; wstringstream classpath_ss(this->GetLaunchInfoByKey(CLASSPATH)); while (getline(classpath_ss, path, L';')) { classpath << this->Rlocation(path) << L';'; } } // Set jvm debug options wostringstream jvm_debug_flags; if (!this->jvm_debug_port.empty()) { wstring jvm_debug_suspend; if (!GetEnv(L"DEFAULT_JVM_DEBUG_SUSPEND", &jvm_debug_suspend)) { jvm_debug_suspend = L"y"; } jvm_debug_flags << L"-agentlib:jdwp=transport=dt_socket,server=y"; jvm_debug_flags << L",suspend=" << jvm_debug_suspend; jvm_debug_flags << L",address=" << jvm_debug_port; wstring value; if (GetEnv(L"PERSISTENT_TEST_RUNNER", &value) && value == L"true") { jvm_debug_flags << L",quiet=y"; } } // Get jvm flags from JVM_FLAGS environment variable and JVM_FLAGS launch info vector jvm_flags; wstring jvm_flags_env; GetEnv(L"JVM_FLAGS", &jvm_flags_env); wstring flag; wstringstream jvm_flags_env_ss(jvm_flags_env); while (getline(jvm_flags_env_ss, flag, L' ')) { jvm_flags.push_back(flag); } wstringstream jvm_flags_launch_info_ss(this->GetLaunchInfoByKey(JVM_FLAGS)); while (getline(jvm_flags_launch_info_ss, flag, L' ')) { jvm_flags.push_back(flag); } // Check if TEST_TMPDIR is available to use for scratch. wstring test_tmpdir; if (GetEnv(L"TEST_TMPDIR", &test_tmpdir) && DoesDirectoryPathExist(test_tmpdir.c_str())) { jvm_flags.push_back(L"-Djava.io.tmpdir=" + test_tmpdir); } // Construct the final command line arguments vector arguments; // Add classpath flags arguments.push_back(L"-classpath"); // Check if CLASSPATH is over classpath length limit. // If it does, then we create a classpath jar to pass CLASSPATH value. wstring classpath_str = classpath.str(); wstring classpath_jar = L""; if (classpath_str.length() > this->classpath_limit) { classpath_jar = CreateClasspathJar(classpath_str); arguments.push_back(classpath_jar); } else { arguments.push_back(classpath_str); } // Add JVM debug flags wstring jvm_debug_flags_str = jvm_debug_flags.str(); if (!jvm_debug_flags_str.empty()) { arguments.push_back(jvm_debug_flags_str); } // Add JVM flags parsed from env and launch info. for (const auto& arg : jvm_flags) { arguments.push_back(arg); } // Add JVM flags parsed from command line. for (const auto& arg : this->jvm_flags_cmdline) { arguments.push_back(arg); } // Add main advice class if (!this->main_advice.empty()) { arguments.push_back(this->main_advice); } // Add java start class arguments.push_back(this->GetLaunchInfoByKey(JAVA_START_CLASS)); // Add the remaininng arguements, they will be passed to the program. for (const auto& arg : remaining_args) { arguments.push_back(arg); } vector escaped_arguments; // Quote the arguments if having spaces for (const auto& arg : arguments) { escaped_arguments.push_back( GetEscapedArgument(arg, /*escape_backslash = */ false)); } ExitCode exit_code = this->LaunchProcess(java_bin, escaped_arguments); // Delete classpath jar file after execution. if (!classpath_jar.empty()) { DeleteFileByPath(classpath_jar.c_str()); DeleteJunctionBaseDir(); } return exit_code; } } // namespace launcher } // namespace bazel