Skip to content

Commit f96f037

Browse files
meteorcloudyCopybara-Service
authored and
Copybara-Service
committed
Windows, Java launcher: Support jar files under different drives
Create junctions to jar's directory when java launcher and its jar are under different drives Fixed bazelbuild#5135 Change-Id: I21c5b28f5f36c1fe234f8b781fe40d526db846cc PiperOrigin-RevId: 196477704
1 parent 43181c9 commit f96f037

File tree

7 files changed

+167
-18
lines changed

7 files changed

+167
-18
lines changed

src/main/cpp/util/file.cc

+2-2
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,8 @@ class DirectoryTreeWalker : public DirectoryEntryConsumer {
124124
_ForEachDirectoryEntry walk_entries)
125125
: _files(files), _walk_entries(walk_entries) {}
126126

127-
void Consume(const string &path, bool is_directory) override {
128-
if (is_directory) {
127+
void Consume(const string &path, bool follow_directory) override {
128+
if (follow_directory) {
129129
Walk(path);
130130
} else {
131131
_files->push_back(path);

src/main/cpp/util/file_windows.cc

+3-1
Original file line numberDiff line numberDiff line change
@@ -1219,7 +1219,9 @@ void ForEachDirectoryEntry(const string &path,
12191219
wstring wname = wpath + metadata.cFileName;
12201220
string name(WstringToCstring(/* omit prefix */ 4 + wname.c_str()).get());
12211221
bool is_dir = (metadata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
1222-
consume->Consume(name, is_dir);
1222+
bool is_junc =
1223+
(metadata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
1224+
consume->Consume(name, is_dir && !is_junc);
12231225
}
12241226
} while (::FindNextFileW(handle, &metadata));
12251227
::FindClose(handle);

src/test/shell/bazel/bazel_windows_example_test.sh

+37-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ function set_up() {
3939
startup --host_jvm_args=-Dbazel.windows_unix_root=C:/fake/msys
4040
4141
startup --batch
42-
build --cpu=x64_windows_msvc
4342
EOF
4443
export MSYS_NO_PATHCONV=1
4544
export MSYS2_ARG_CONV_EXCL="*"
@@ -129,6 +128,43 @@ function test_java() {
129128
assert_binary_run_from_subdir "bazel-bin/${java_pkg}/hello-world foo" "Hello foo"
130129
}
131130

131+
function create_tmp_drive() {
132+
mkdir "$TEST_TMPDIR/tmp_drive"
133+
134+
TMP_DRIVE_PATH=$(cygpath -w "$TEST_TMPDIR\\tmp_drive")
135+
for X in {A..Z}
136+
do
137+
TMP_DRIVE=${X}
138+
subst ${TMP_DRIVE}: ${TMP_DRIVE_PATH} >NUL || TMP_DRIVE=""
139+
if [ -n "${TMP_DRIVE}" ]; then
140+
break
141+
fi
142+
done
143+
144+
if [ -z "${TMP_DRIVE}" ]; then
145+
fail "Cannot create temporary drive."
146+
fi
147+
148+
export TMP_DRIVE
149+
}
150+
151+
function delete_tmp_drive() {
152+
if [ -n "${TMP_DRIVE}" ]; then
153+
subst ${TMP_DRIVE}: /D
154+
fi
155+
}
156+
157+
function test_java_with_jar_under_different_drive() {
158+
create_tmp_drive
159+
160+
trap delete_tmp_drive EXIT
161+
162+
local java_pkg=examples/java-native/src/main/java/com/example/myproject
163+
bazel --output_user_root=${TMP_DRIVE}:/tmp build ${java_pkg}:hello-world
164+
165+
assert_binary_run_from_subdir "bazel-bin/${java_pkg}/hello-world --classpath_limit=0" "Hello world"
166+
}
167+
132168
function test_java_test() {
133169
setup_javatest_support
134170
local java_native_tests=//examples/java-native/src/test/java/com/example/myproject

src/tools/launcher/java_launcher.cc

+94-14
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,16 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
#include <memory>
1516
#include <sstream>
1617
#include <string>
18+
#include <unordered_map>
1719
#include <vector>
1820

21+
#include "src/main/cpp/util/file.h"
1922
#include "src/main/cpp/util/file_platform.h"
23+
#include "src/main/cpp/util/strings.h"
24+
#include "src/main/native/windows/file.h"
2025
#include "src/tools/launcher/java_launcher.h"
2126
#include "src/tools/launcher/util/launcher_util.h"
2227

@@ -29,6 +34,7 @@ using std::ostringstream;
2934
using std::string;
3035
using std::stringstream;
3136
using std::vector;
37+
using std::wstring;
3238

3339
// The runfile path of java binary, eg. local_jdk/bin/java.exe
3440
static constexpr const char* JAVA_BIN_PATH = "java_bin_path";
@@ -135,6 +141,52 @@ static string GetManifestJarDir(const string& binary_base_path) {
135141
return result;
136142
}
137143

144+
static void WriteJarClasspath(const string& jar_path,
145+
ostringstream* manifest_classpath) {
146+
*manifest_classpath << ' ';
147+
if (jar_path.find_first_of(" \\") != string::npos) {
148+
for (const auto& x : jar_path) {
149+
if (x == ' ') {
150+
*manifest_classpath << "%20";
151+
}
152+
if (x == '\\') {
153+
*manifest_classpath << "/";
154+
} else {
155+
*manifest_classpath << x;
156+
}
157+
}
158+
} else {
159+
*manifest_classpath << jar_path;
160+
}
161+
}
162+
163+
string JavaBinaryLauncher::GetJunctionBaseDir() {
164+
string binary_base_path =
165+
GetBinaryPathWithExtension(this->GetCommandlineArguments()[0]);
166+
string result;
167+
if (!NormalizePath(binary_base_path + ".j", &result)) {
168+
die("Failed to get normalized junction base directory.");
169+
}
170+
return result;
171+
}
172+
173+
void JavaBinaryLauncher::DeleteJunctionBaseDir() {
174+
string junction_base_dir_norm = GetJunctionBaseDir();
175+
if (!DoesDirectoryPathExist(junction_base_dir_norm.c_str())) {
176+
return;
177+
}
178+
vector<string> junctions;
179+
blaze_util::GetAllFilesUnder(junction_base_dir_norm, &junctions);
180+
for (const auto& junction : junctions) {
181+
if (!DeleteDirectoryByPath(junction.c_str())) {
182+
PrintError(GetLastErrorString().c_str());
183+
}
184+
}
185+
if (!DeleteDirectoryByPath(junction_base_dir_norm.c_str())) {
186+
PrintError(GetLastErrorString().c_str());
187+
}
188+
}
189+
138190
string JavaBinaryLauncher::CreateClasspathJar(const string& classpath) {
139191
string binary_base_path =
140192
GetBinaryPathWithoutExtension(this->GetCommandlineArguments()[0]);
@@ -144,28 +196,55 @@ string JavaBinaryLauncher::CreateClasspathJar(const string& classpath) {
144196
manifest_classpath << "Class-Path:";
145197
stringstream classpath_ss(classpath);
146198
string path, path_norm;
199+
200+
// A set to store all junctions created.
201+
// The key is the target path, the value is the junction path.
202+
std::unordered_map<string, string> jar_dirs;
203+
string junction_base_dir_norm = GetJunctionBaseDir();
204+
int junction_count = 0;
205+
// Make sure the junction base directory doesn't exist already.
206+
DeleteJunctionBaseDir();
207+
blaze_util::MakeDirectories(junction_base_dir_norm, 0755);
208+
147209
while (getline(classpath_ss, path, ';')) {
148-
manifest_classpath << ' ';
149210
if (blaze_util::IsAbsolute(path)) {
150-
if (!NormalizePath(path, &path_norm) ||
151-
!RelativeTo(path_norm, abs_manifest_jar_dir_norm, &path)) {
211+
if (!NormalizePath(path, &path_norm)) {
152212
die("CreateClasspathJar failed");
153213
}
154-
}
155-
if (path.find_first_of(" \\") != string::npos) {
156-
for (const auto& x : path) {
157-
if (x == ' ') {
158-
manifest_classpath << "%20";
159-
}
160-
if (x == '\\') {
161-
manifest_classpath << "/";
214+
215+
// If two paths are under different drives, we should create a junction to
216+
// the jar's directory
217+
if (path_norm[0] != abs_manifest_jar_dir_norm[0]) {
218+
string jar_dir = GetParentDirFromPath(path_norm);
219+
string jar_base_name = GetBaseNameFromPath(path_norm);
220+
string junction;
221+
auto search = jar_dirs.find(jar_dir);
222+
if (search == jar_dirs.end()) {
223+
junction =
224+
junction_base_dir_norm + "\\" + std::to_string(junction_count++);
225+
226+
wstring wjar_dir(
227+
blaze_util::CstringToWstring(junction.c_str()).get());
228+
wstring wjunction(
229+
blaze_util::CstringToWstring(jar_dir.c_str()).get());
230+
wstring werror(bazel::windows::CreateJunction(wjar_dir, wjunction));
231+
if (!werror.empty()) {
232+
string error(werror.begin(), werror.end());
233+
die("CreateClasspathJar failed: %s", error.c_str());
234+
}
235+
236+
jar_dirs.insert(std::make_pair(jar_dir, junction));
162237
} else {
163-
manifest_classpath << x;
238+
junction = search->second;
164239
}
240+
path_norm = junction + "\\" + jar_base_name;
241+
}
242+
243+
if (!RelativeTo(path_norm, abs_manifest_jar_dir_norm, &path)) {
244+
die("CreateClasspathJar failed");
165245
}
166-
} else {
167-
manifest_classpath << path;
168246
}
247+
WriteJarClasspath(path, &manifest_classpath);
169248
}
170249

171250
string rand_id = "-" + GetRandomStr(10);
@@ -335,6 +414,7 @@ ExitCode JavaBinaryLauncher::Launch() {
335414
// Delete classpath jar file after execution.
336415
if (!classpath_jar.empty()) {
337416
DeleteFileByPath(classpath_jar.c_str());
417+
DeleteJunctionBaseDir();
338418
}
339419

340420
return exit_code;

src/tools/launcher/java_launcher.h

+7
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,13 @@ class JavaBinaryLauncher : public BinaryLauncherBase {
9090
//
9191
// Return the path of the classpath jar created.
9292
std::string CreateClasspathJar(const std::string& classpath);
93+
94+
// Creat a directory based on the binary path, all the junctions will be
95+
// generated under this directory.
96+
std::string GetJunctionBaseDir();
97+
98+
// Delete all the junction directory and all the junctions under it.
99+
void DeleteJunctionBaseDir();
93100
};
94101

95102
} // namespace launcher

src/tools/launcher/util/launcher_util.cc

+12
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@ bool DeleteFileByPath(const char* path) {
100100
return DeleteFileW(AsAbsoluteWindowsPath(path).c_str());
101101
}
102102

103+
bool DeleteDirectoryByPath(const char* path) {
104+
return RemoveDirectoryW(AsAbsoluteWindowsPath(path).c_str());
105+
}
106+
103107
string GetBinaryPathWithoutExtension(const string& binary) {
104108
if (binary.find(".exe", binary.size() - 4) != string::npos) {
105109
return binary.substr(0, binary.length() - 4);
@@ -184,6 +188,14 @@ bool NormalizePath(const string& path, string* result) {
184188
return true;
185189
}
186190

191+
string GetBaseNameFromPath(const string& path) {
192+
return path.substr(path.find_last_of("\\/") + 1);
193+
}
194+
195+
string GetParentDirFromPath(const string& path) {
196+
return path.substr(0, path.find_last_of("\\/"));
197+
}
198+
187199
bool RelativeTo(const string& path, const string& base, string* result) {
188200
if (blaze_util::IsAbsolute(path) != blaze_util::IsAbsolute(base)) {
189201
PrintError(

src/tools/launcher/util/launcher_util.h

+12
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ bool DoesDirectoryPathExist(const char* path);
6161
// Delete a file at a given path.
6262
bool DeleteFileByPath(const char* path);
6363

64+
// Delete a directory at a given path,.
65+
// If it's a real directory, it must be empty
66+
// If it's a junction, the target directory it points to doesn't have to be
67+
// empty, the junction will be deleted regardless of the state of the target.
68+
bool DeleteDirectoryByPath(const char* path);
69+
6470
// Get the value of a specific environment variable
6571
//
6672
// Return true if succeeded and the result is stored in buffer.
@@ -79,6 +85,12 @@ std::string GetRandomStr(size_t len);
7985
// Normalize a path to a Windows path in lower case
8086
bool NormalizePath(const std::string& path, std::string* result);
8187

88+
// Get the base name from a normalized absoulute path
89+
std::string GetBaseNameFromPath(const std::string& path);
90+
91+
// Get parent directory from a normalized absoulute path
92+
std::string GetParentDirFromPath(const std::string& path);
93+
8294
// Calculate a relative path from `path` to `base`.
8395
// This function expects normalized Windows path in lower case.
8496
// `path` and `base` should be both absolute or both relative.

0 commit comments

Comments
 (0)