Yun Peng | 09dd8c0 | 2017-07-21 15:57:05 +0200 | [diff] [blame] | 1 | // Copyright 2017 The Bazel Authors. All rights reserved. |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
Yun Peng | 54c5c5c | 2017-08-24 17:40:22 +0200 | [diff] [blame] | 15 | #include <sstream> |
| 16 | #include <string> |
| 17 | #include <vector> |
| 18 | |
Yun Peng | 09dd8c0 | 2017-07-21 15:57:05 +0200 | [diff] [blame] | 19 | #include "src/tools/launcher/java_launcher.h" |
Yun Peng | 54c5c5c | 2017-08-24 17:40:22 +0200 | [diff] [blame] | 20 | #include "src/tools/launcher/util/launcher_util.h" |
Yun Peng | 09dd8c0 | 2017-07-21 15:57:05 +0200 | [diff] [blame] | 21 | |
| 22 | namespace bazel { |
| 23 | namespace launcher { |
| 24 | |
Yun Peng | 54c5c5c | 2017-08-24 17:40:22 +0200 | [diff] [blame] | 25 | using std::getline; |
| 26 | using std::ofstream; |
| 27 | using std::ostringstream; |
| 28 | using std::string; |
| 29 | using std::stringstream; |
| 30 | using std::vector; |
| 31 | |
| 32 | // The runfile path of java binary, eg. local_jdk/bin/java.exe |
| 33 | static constexpr const char* JAVA_BIN_PATH = "java_bin_path"; |
| 34 | static constexpr const char* JAR_BIN_PATH = "jar_bin_path"; |
| 35 | static constexpr const char* CLASSPATH = "classpath"; |
| 36 | static constexpr const char* JAVA_START_CLASS = "java_start_class"; |
| 37 | static constexpr const char* JVM_FLAGS = "jvm_flags"; |
| 38 | |
| 39 | // Check if a string start with a certain prefix. |
| 40 | // If it's true, store the substring without the prefix in value. |
| 41 | // If value is quoted, then remove the quotes. |
| 42 | static bool GetFlagValue(const string& str, const string& prefix, |
| 43 | string* value_ptr) { |
| 44 | if (str.compare(0, prefix.length(), prefix)) { |
| 45 | return false; |
| 46 | } |
| 47 | string& value = *value_ptr; |
| 48 | value = str.substr(prefix.length()); |
| 49 | int len = value.length(); |
| 50 | if (len >= 2 && value[0] == '"' && value[len - 1] == '"') { |
| 51 | value = value.substr(1, len - 2); |
| 52 | } |
| 53 | return true; |
| 54 | } |
| 55 | |
| 56 | // Parses one launcher flag and updates this object's state accordingly. |
| 57 | // |
| 58 | // Returns true if the flag is a valid launcher flag; false otherwise. |
| 59 | bool JavaBinaryLauncher::ProcessWrapperArgument(const string& argument) { |
| 60 | string flag_value; |
| 61 | if (argument.compare("--debug") == 0) { |
| 62 | string default_jvm_debug_port; |
| 63 | if (GetEnv("DEFAULT_JVM_DEBUG_PORT", &default_jvm_debug_port)) { |
| 64 | this->jvm_debug_port = default_jvm_debug_port; |
| 65 | } else { |
| 66 | this->jvm_debug_port = "5005"; |
| 67 | } |
| 68 | } else if (GetFlagValue(argument, "--debug=", &flag_value)) { |
| 69 | this->jvm_debug_port = flag_value; |
| 70 | } else if (GetFlagValue(argument, "--main_advice=", &flag_value)) { |
| 71 | this->main_advice = flag_value; |
| 72 | } else if (GetFlagValue(argument, "--main_advice_classpath=", &flag_value)) { |
| 73 | this->main_advice_classpath = flag_value; |
| 74 | } else if (GetFlagValue(argument, "--jvm_flag=", &flag_value)) { |
| 75 | this->jvm_flags_cmdline.push_back(flag_value); |
| 76 | } else if (GetFlagValue(argument, "--jvm_flags=", &flag_value)) { |
| 77 | stringstream flag_value_ss(flag_value); |
| 78 | string item; |
| 79 | while (getline(flag_value_ss, item, ' ')) { |
| 80 | this->jvm_flags_cmdline.push_back(item); |
| 81 | } |
| 82 | } else if (argument.compare("--singlejar") == 0) { |
| 83 | this->singlejar = true; |
| 84 | } else if (argument.compare("--print_javabin") == 0) { |
| 85 | this->print_javabin = true; |
| 86 | } else if (GetFlagValue(argument, "--classpath_limit=", &flag_value)) { |
| 87 | this->classpath_limit = std::stoi(flag_value); |
| 88 | } else { |
| 89 | return false; |
| 90 | } |
| 91 | return true; |
| 92 | } |
| 93 | |
| 94 | vector<string> JavaBinaryLauncher::ProcessesCommandLine() { |
| 95 | vector<string> args; |
| 96 | bool first = 1; |
| 97 | for (const auto& arg : this->GetCommandlineArguments()) { |
| 98 | // Skip the first arugment. |
| 99 | if (first) { |
| 100 | first = 0; |
| 101 | continue; |
| 102 | } |
| 103 | string flag_value; |
| 104 | // TODO(pcloudy): Should rename this flag to --native_launcher_flag. |
| 105 | // But keep it as it is for now to be consistent with the shell script |
| 106 | // launcher. |
| 107 | if (GetFlagValue(arg, "--wrapper_script_flag=", &flag_value)) { |
| 108 | if (!ProcessWrapperArgument(flag_value)) { |
| 109 | die("invalid wrapper argument '%s'", arg); |
| 110 | } |
| 111 | } else if (!args.empty() || !ProcessWrapperArgument(arg)) { |
| 112 | args.push_back(arg); |
| 113 | } |
| 114 | } |
| 115 | return args; |
| 116 | } |
| 117 | |
| 118 | string JavaBinaryLauncher::CreateClasspathJar(const string& classpath) { |
| 119 | static constexpr const char* URI_PREFIX = "file:/"; |
| 120 | ostringstream manifest_classpath; |
| 121 | manifest_classpath << "Class-Path:"; |
| 122 | stringstream classpath_ss(classpath); |
| 123 | string path; |
| 124 | while (getline(classpath_ss, path, ';')) { |
| 125 | manifest_classpath << ' '; |
| 126 | manifest_classpath << URI_PREFIX; |
| 127 | for (const auto& x : path) { |
| 128 | if (x == ' ') { |
| 129 | manifest_classpath << "%20"; |
| 130 | } else { |
| 131 | manifest_classpath << x; |
| 132 | } |
| 133 | } |
| 134 | } |
| 135 | |
| 136 | string binary_base_path = |
| 137 | GetBinaryPathWithoutExtension(this->GetCommandlineArguments()[0]); |
Yun Peng | 5f09033 | 2017-09-18 16:34:19 +0200 | [diff] [blame] | 138 | string rand_id = "-" + GetRandomStr(10); |
| 139 | string jar_manifest_file_path = binary_base_path + rand_id + ".jar_manifest"; |
Yun Peng | 54c5c5c | 2017-08-24 17:40:22 +0200 | [diff] [blame] | 140 | ofstream jar_manifest_file(jar_manifest_file_path); |
| 141 | jar_manifest_file << "Manifest-Version: 1.0\n"; |
| 142 | // No line in the MANIFEST.MF file may be longer than 72 bytes. |
| 143 | // A space prefix indicates the line is still the content of the last |
| 144 | // attribute. |
| 145 | string manifest_classpath_str = manifest_classpath.str(); |
| 146 | for (size_t i = 0; i < manifest_classpath_str.length(); i += 71) { |
| 147 | if (i > 0) { |
| 148 | jar_manifest_file << " "; |
| 149 | } |
| 150 | jar_manifest_file << manifest_classpath_str.substr(i, 71) << "\n"; |
| 151 | } |
| 152 | jar_manifest_file.close(); |
| 153 | |
| 154 | // Create the command for generating classpath jar. |
| 155 | // We pass the command to cmd.exe to use redirection for suppressing output. |
Yun Peng | 5f09033 | 2017-09-18 16:34:19 +0200 | [diff] [blame] | 156 | string manifest_jar_path = binary_base_path + rand_id + "-classpath.jar"; |
pcloudy | 9e3a3e2 | 2017-08-25 16:00:47 +0200 | [diff] [blame] | 157 | string jar_bin = this->Rlocation(this->GetLaunchInfoByKey(JAR_BIN_PATH)); |
Yun Peng | 54c5c5c | 2017-08-24 17:40:22 +0200 | [diff] [blame] | 158 | vector<string> arguments; |
| 159 | arguments.push_back("/c"); |
| 160 | |
| 161 | ostringstream jar_command; |
| 162 | jar_command << jar_bin << " cvfm "; |
| 163 | jar_command << manifest_jar_path << " "; |
| 164 | jar_command << jar_manifest_file_path << " "; |
| 165 | jar_command << "> nul"; |
| 166 | arguments.push_back(jar_command.str()); |
| 167 | |
| 168 | if (this->LaunchProcess("cmd.exe", arguments) != 0) { |
| 169 | die("Couldn't create classpath jar: %s", manifest_jar_path.c_str()); |
| 170 | } |
| 171 | |
Yun Peng | 5f09033 | 2017-09-18 16:34:19 +0200 | [diff] [blame] | 172 | // Delete jar_manifest_file after classpath jar is created. |
| 173 | DeleteFileByPath(jar_manifest_file_path.c_str()); |
| 174 | |
Yun Peng | 54c5c5c | 2017-08-24 17:40:22 +0200 | [diff] [blame] | 175 | return manifest_jar_path; |
| 176 | } |
| 177 | |
Yun Peng | 09dd8c0 | 2017-07-21 15:57:05 +0200 | [diff] [blame] | 178 | ExitCode JavaBinaryLauncher::Launch() { |
Yun Peng | 54c5c5c | 2017-08-24 17:40:22 +0200 | [diff] [blame] | 179 | // Parse the original command line. |
| 180 | vector<string> remaining_args = this->ProcessesCommandLine(); |
| 181 | |
| 182 | // Set JAVA_RUNFILES |
| 183 | string java_runfiles; |
| 184 | if (!GetEnv("JAVA_RUNFILES", &java_runfiles)) { |
| 185 | java_runfiles = this->GetRunfilesPath(); |
| 186 | } |
| 187 | SetEnv("JAVA_RUNFILES", java_runfiles); |
| 188 | |
| 189 | // Print Java binary path if needed |
| 190 | string java_bin = this->Rlocation(this->GetLaunchInfoByKey(JAVA_BIN_PATH), |
| 191 | /*need_workspace_name =*/false); |
| 192 | if (this->print_javabin || |
| 193 | this->GetLaunchInfoByKey(JAVA_START_CLASS) == "--print_javabin") { |
| 194 | printf("%s\n", java_bin.c_str()); |
| 195 | return 0; |
| 196 | } |
| 197 | |
| 198 | ostringstream classpath; |
| 199 | |
| 200 | // Run deploy jar if needed, otherwise generate the CLASSPATH by rlocation. |
| 201 | if (this->singlejar) { |
| 202 | string deploy_jar = |
| 203 | GetBinaryPathWithoutExtension(this->GetCommandlineArguments()[0]) + |
| 204 | "_deploy.jar"; |
| 205 | if (!DoesFilePathExist(deploy_jar.c_str())) { |
| 206 | die("Option --singlejar was passed, but %s does not exist.\n (You may " |
| 207 | "need to build it explicitly.)", |
| 208 | deploy_jar.c_str()); |
| 209 | } |
| 210 | classpath << deploy_jar << ';'; |
| 211 | } else { |
| 212 | // Add main advice classpath if exists |
| 213 | if (!this->main_advice_classpath.empty()) { |
| 214 | classpath << this->main_advice_classpath << ';'; |
| 215 | } |
| 216 | string path; |
| 217 | stringstream classpath_ss(this->GetLaunchInfoByKey(CLASSPATH)); |
| 218 | while (getline(classpath_ss, path, ';')) { |
| 219 | classpath << this->Rlocation(path) << ';'; |
| 220 | } |
| 221 | } |
| 222 | |
| 223 | // Set jvm debug options |
| 224 | ostringstream jvm_debug_flags; |
| 225 | if (!this->jvm_debug_port.empty()) { |
| 226 | string jvm_debug_suspend; |
| 227 | if (!GetEnv("DEFAULT_JVM_DEBUG_SUSPEND", &jvm_debug_suspend)) { |
| 228 | jvm_debug_suspend = "y"; |
| 229 | } |
| 230 | jvm_debug_flags << "-agentlib:jdwp=transport=dt_socket,server=y"; |
| 231 | jvm_debug_flags << ",suspend=" << jvm_debug_suspend; |
| 232 | jvm_debug_flags << ",address=" << jvm_debug_port; |
| 233 | |
| 234 | string value; |
| 235 | if (GetEnv("PERSISTENT_TEST_RUNNER", &value) && value == "true") { |
| 236 | jvm_debug_flags << ",quiet=y"; |
| 237 | } |
| 238 | } |
| 239 | |
| 240 | // Get jvm flags from JVM_FLAGS environment variable and JVM_FLAGS launch info |
| 241 | vector<string> jvm_flags; |
| 242 | string jvm_flags_env; |
| 243 | GetEnv("JVM_FLAGS", &jvm_flags_env); |
| 244 | string flag; |
| 245 | stringstream jvm_flags_env_ss(jvm_flags_env); |
| 246 | while (getline(jvm_flags_env_ss, flag, ' ')) { |
| 247 | jvm_flags.push_back(flag); |
| 248 | } |
| 249 | stringstream jvm_flags_launch_info_ss(this->GetLaunchInfoByKey(JVM_FLAGS)); |
| 250 | while (getline(jvm_flags_launch_info_ss, flag, ' ')) { |
| 251 | jvm_flags.push_back(flag); |
| 252 | } |
| 253 | |
| 254 | // Check if TEST_TMPDIR is available to use for scratch. |
| 255 | string test_tmpdir; |
| 256 | if (GetEnv("TEST_TMPDIR", &test_tmpdir) && |
| 257 | DoesDirectoryPathExist(test_tmpdir.c_str())) { |
| 258 | jvm_flags.push_back("-Djava.io.tmpdir=" + test_tmpdir); |
| 259 | } |
| 260 | |
| 261 | // Construct the final command line arguments |
| 262 | vector<string> arguments; |
| 263 | // Add classpath flags |
| 264 | arguments.push_back("-classpath"); |
| 265 | // Check if CLASSPATH is over classpath length limit. |
| 266 | // If it does, then we create a classpath jar to pass CLASSPATH value. |
| 267 | string classpath_str = classpath.str(); |
Yun Peng | 5f09033 | 2017-09-18 16:34:19 +0200 | [diff] [blame] | 268 | string classpath_jar = ""; |
Yun Peng | 54c5c5c | 2017-08-24 17:40:22 +0200 | [diff] [blame] | 269 | if (classpath_str.length() > this->classpath_limit) { |
Yun Peng | 5f09033 | 2017-09-18 16:34:19 +0200 | [diff] [blame] | 270 | classpath_jar = CreateClasspathJar(classpath_str); |
| 271 | arguments.push_back(classpath_jar); |
Yun Peng | 54c5c5c | 2017-08-24 17:40:22 +0200 | [diff] [blame] | 272 | } else { |
| 273 | arguments.push_back(classpath_str); |
| 274 | } |
| 275 | // Add JVM debug flags |
| 276 | string jvm_debug_flags_str = jvm_debug_flags.str(); |
| 277 | if (!jvm_debug_flags_str.empty()) { |
| 278 | arguments.push_back(jvm_debug_flags_str); |
| 279 | } |
| 280 | // Add JVM flags parsed from env and launch info. |
| 281 | for (const auto& arg : jvm_flags) { |
| 282 | arguments.push_back(arg); |
| 283 | } |
| 284 | // Add JVM flags parsed from command line. |
| 285 | for (const auto& arg : this->jvm_flags_cmdline) { |
| 286 | arguments.push_back(arg); |
| 287 | } |
| 288 | // Add main advice class |
| 289 | if (!this->main_advice.empty()) { |
| 290 | arguments.push_back(this->main_advice); |
| 291 | } |
| 292 | // Add java start class |
| 293 | arguments.push_back(this->GetLaunchInfoByKey(JAVA_START_CLASS)); |
| 294 | // Add the remaininng arguements, they will be passed to the program. |
| 295 | for (const auto& arg : remaining_args) { |
| 296 | arguments.push_back(arg); |
| 297 | } |
| 298 | |
Yun Peng | 5f09033 | 2017-09-18 16:34:19 +0200 | [diff] [blame] | 299 | ExitCode exit_code = this->LaunchProcess(java_bin, arguments); |
| 300 | |
| 301 | // Delete classpath jar file after execution. |
| 302 | if (!classpath_jar.empty()) { |
| 303 | DeleteFileByPath(classpath_jar.c_str()); |
| 304 | } |
| 305 | |
| 306 | return exit_code; |
Yun Peng | 09dd8c0 | 2017-07-21 15:57:05 +0200 | [diff] [blame] | 307 | } |
| 308 | |
| 309 | } // namespace launcher |
| 310 | } // namespace bazel |