| // 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 "src/tools/launcher/java_launcher.h" |
| |
| #include <memory> |
| #include <sstream> |
| #include <string> |
| #include <unordered_map> |
| #include <vector> |
| |
| #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/main/native/windows/process.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<wstring> JavaBinaryLauncher::ProcessesCommandLine() { |
| vector<wstring> args; |
| bool first = 1; |
| for (const auto& arg : this->GetCommandlineArguments()) { |
| // Skip the first argument. |
| 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' '; |
| for (const auto& x : jar_path) { |
| if (isalnum(x) || x == L'.' || x == L'-' || x == L'_' || x == L'~') { |
| *manifest_classpath << x; |
| } else if (x == L'\\') { |
| *manifest_classpath << L"/"; |
| } else { |
| // Replace the character with its 2-digit hexadecimal representation. |
| char buffer[4]; |
| snprintf(buffer, sizeof(buffer), "%02X", x); |
| *manifest_classpath << L"%" << buffer; |
| } |
| } |
| } |
| |
| 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<wstring> 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<wstring, wstring> 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); |
| // Enable long path support for jar_manifest_file_path. |
| wstring jar_manifest_file_path = |
| binary_base_path + rand_id + L".jar_manifest"; |
| blaze_util::AddUncPrefixMaybe(&jar_manifest_file_path); |
| 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(); |
| if (jar_manifest_file.fail()) { |
| die(L"Couldn't write jar manifest file: %s", |
| jar_manifest_file_path.c_str()); |
| } |
| |
| // 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<wstring> 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<wstring> 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), |
| /*has_workspace_name =*/true); |
| 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<wstring> 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'\t')) { |
| 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<wstring> 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 arguments, they will be passed to the program. |
| for (const auto& arg : remaining_args) { |
| arguments.push_back(arg); |
| } |
| |
| vector<wstring> escaped_arguments; |
| // Quote the arguments if having spaces |
| for (const auto& arg : arguments) { |
| escaped_arguments.push_back(bazel::windows::WindowsEscapeArg(arg)); |
| } |
| |
| 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 |