blob: e13d26833d707f24e8792a58e5775d1a564a906b [file] [log] [blame]
Yun Peng09dd8c02017-07-21 15:57:05 +02001// 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 Peng54c5c5c2017-08-24 17:40:22 +020015#include <sstream>
16#include <string>
17#include <vector>
18
Yun Peng09dd8c02017-07-21 15:57:05 +020019#include "src/tools/launcher/java_launcher.h"
Yun Peng54c5c5c2017-08-24 17:40:22 +020020#include "src/tools/launcher/util/launcher_util.h"
Yun Peng09dd8c02017-07-21 15:57:05 +020021
22namespace bazel {
23namespace launcher {
24
Yun Peng54c5c5c2017-08-24 17:40:22 +020025using std::getline;
26using std::ofstream;
27using std::ostringstream;
28using std::string;
29using std::stringstream;
30using std::vector;
31
32// The runfile path of java binary, eg. local_jdk/bin/java.exe
33static constexpr const char* JAVA_BIN_PATH = "java_bin_path";
34static constexpr const char* JAR_BIN_PATH = "jar_bin_path";
35static constexpr const char* CLASSPATH = "classpath";
36static constexpr const char* JAVA_START_CLASS = "java_start_class";
37static 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.
42static 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.
59bool 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
94vector<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
118string 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 Peng5f090332017-09-18 16:34:19 +0200138 string rand_id = "-" + GetRandomStr(10);
139 string jar_manifest_file_path = binary_base_path + rand_id + ".jar_manifest";
Yun Peng54c5c5c2017-08-24 17:40:22 +0200140 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 Peng5f090332017-09-18 16:34:19 +0200156 string manifest_jar_path = binary_base_path + rand_id + "-classpath.jar";
pcloudy9e3a3e22017-08-25 16:00:47 +0200157 string jar_bin = this->Rlocation(this->GetLaunchInfoByKey(JAR_BIN_PATH));
Yun Peng54c5c5c2017-08-24 17:40:22 +0200158 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 Peng5f090332017-09-18 16:34:19 +0200172 // Delete jar_manifest_file after classpath jar is created.
173 DeleteFileByPath(jar_manifest_file_path.c_str());
174
Yun Peng54c5c5c2017-08-24 17:40:22 +0200175 return manifest_jar_path;
176}
177
Yun Peng09dd8c02017-07-21 15:57:05 +0200178ExitCode JavaBinaryLauncher::Launch() {
Yun Peng54c5c5c2017-08-24 17:40:22 +0200179 // 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 Peng5f090332017-09-18 16:34:19 +0200268 string classpath_jar = "";
Yun Peng54c5c5c2017-08-24 17:40:22 +0200269 if (classpath_str.length() > this->classpath_limit) {
Yun Peng5f090332017-09-18 16:34:19 +0200270 classpath_jar = CreateClasspathJar(classpath_str);
271 arguments.push_back(classpath_jar);
Yun Peng54c5c5c2017-08-24 17:40:22 +0200272 } 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 Peng5f090332017-09-18 16:34:19 +0200299 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 Peng09dd8c02017-07-21 15:57:05 +0200307}
308
309} // namespace launcher
310} // namespace bazel