From f96f037f8f77335dc444844abcc31a372a3e1849 Mon Sep 17 00:00:00 2001 From: Yun Peng Date: Mon, 14 May 2018 02:53:01 -0700 Subject: [PATCH] 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 https://github.com/bazelbuild/bazel/issues/5135 Change-Id: I21c5b28f5f36c1fe234f8b781fe40d526db846cc PiperOrigin-RevId: 196477704 --- src/main/cpp/util/file.cc | 4 +- src/main/cpp/util/file_windows.cc | 4 +- .../shell/bazel/bazel_windows_example_test.sh | 38 +++++- src/tools/launcher/java_launcher.cc | 108 +++++++++++++++--- src/tools/launcher/java_launcher.h | 7 ++ src/tools/launcher/util/launcher_util.cc | 12 ++ src/tools/launcher/util/launcher_util.h | 12 ++ 7 files changed, 167 insertions(+), 18 deletions(-) diff --git a/src/main/cpp/util/file.cc b/src/main/cpp/util/file.cc index 16e8a08a52f673..3eb614c0dcf7f1 100644 --- a/src/main/cpp/util/file.cc +++ b/src/main/cpp/util/file.cc @@ -124,8 +124,8 @@ class DirectoryTreeWalker : public DirectoryEntryConsumer { _ForEachDirectoryEntry walk_entries) : _files(files), _walk_entries(walk_entries) {} - void Consume(const string &path, bool is_directory) override { - if (is_directory) { + void Consume(const string &path, bool follow_directory) override { + if (follow_directory) { Walk(path); } else { _files->push_back(path); diff --git a/src/main/cpp/util/file_windows.cc b/src/main/cpp/util/file_windows.cc index 124ae7ed172368..39160c9c225266 100644 --- a/src/main/cpp/util/file_windows.cc +++ b/src/main/cpp/util/file_windows.cc @@ -1219,7 +1219,9 @@ void ForEachDirectoryEntry(const string &path, wstring wname = wpath + metadata.cFileName; string name(WstringToCstring(/* omit prefix */ 4 + wname.c_str()).get()); bool is_dir = (metadata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; - consume->Consume(name, is_dir); + bool is_junc = + (metadata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0; + consume->Consume(name, is_dir && !is_junc); } } while (::FindNextFileW(handle, &metadata)); ::FindClose(handle); diff --git a/src/test/shell/bazel/bazel_windows_example_test.sh b/src/test/shell/bazel/bazel_windows_example_test.sh index 97de66108dcec1..b36d30148eb614 100755 --- a/src/test/shell/bazel/bazel_windows_example_test.sh +++ b/src/test/shell/bazel/bazel_windows_example_test.sh @@ -39,7 +39,6 @@ function set_up() { startup --host_jvm_args=-Dbazel.windows_unix_root=C:/fake/msys startup --batch -build --cpu=x64_windows_msvc EOF export MSYS_NO_PATHCONV=1 export MSYS2_ARG_CONV_EXCL="*" @@ -129,6 +128,43 @@ function test_java() { assert_binary_run_from_subdir "bazel-bin/${java_pkg}/hello-world foo" "Hello foo" } +function create_tmp_drive() { + mkdir "$TEST_TMPDIR/tmp_drive" + + TMP_DRIVE_PATH=$(cygpath -w "$TEST_TMPDIR\\tmp_drive") + for X in {A..Z} + do + TMP_DRIVE=${X} + subst ${TMP_DRIVE}: ${TMP_DRIVE_PATH} >NUL || TMP_DRIVE="" + if [ -n "${TMP_DRIVE}" ]; then + break + fi + done + + if [ -z "${TMP_DRIVE}" ]; then + fail "Cannot create temporary drive." + fi + + export TMP_DRIVE +} + +function delete_tmp_drive() { + if [ -n "${TMP_DRIVE}" ]; then + subst ${TMP_DRIVE}: /D + fi +} + +function test_java_with_jar_under_different_drive() { + create_tmp_drive + + trap delete_tmp_drive EXIT + + local java_pkg=examples/java-native/src/main/java/com/example/myproject + bazel --output_user_root=${TMP_DRIVE}:/tmp build ${java_pkg}:hello-world + + assert_binary_run_from_subdir "bazel-bin/${java_pkg}/hello-world --classpath_limit=0" "Hello world" +} + function test_java_test() { setup_javatest_support local java_native_tests=//examples/java-native/src/test/java/com/example/myproject diff --git a/src/tools/launcher/java_launcher.cc b/src/tools/launcher/java_launcher.cc index af60de2fed26fe..5b5a498b7133cd 100644 --- a/src/tools/launcher/java_launcher.cc +++ b/src/tools/launcher/java_launcher.cc @@ -12,11 +12,16 @@ // 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/strings.h" +#include "src/main/native/windows/file.h" #include "src/tools/launcher/java_launcher.h" #include "src/tools/launcher/util/launcher_util.h" @@ -29,6 +34,7 @@ using std::ostringstream; using std::string; using std::stringstream; using std::vector; +using std::wstring; // The runfile path of java binary, eg. local_jdk/bin/java.exe static constexpr const char* JAVA_BIN_PATH = "java_bin_path"; @@ -135,6 +141,52 @@ static string GetManifestJarDir(const string& binary_base_path) { return result; } +static void WriteJarClasspath(const string& jar_path, + ostringstream* manifest_classpath) { + *manifest_classpath << ' '; + if (jar_path.find_first_of(" \\") != string::npos) { + for (const auto& x : jar_path) { + if (x == ' ') { + *manifest_classpath << "%20"; + } + if (x == '\\') { + *manifest_classpath << "/"; + } else { + *manifest_classpath << x; + } + } + } else { + *manifest_classpath << jar_path; + } +} + +string JavaBinaryLauncher::GetJunctionBaseDir() { + string binary_base_path = + GetBinaryPathWithExtension(this->GetCommandlineArguments()[0]); + string result; + if (!NormalizePath(binary_base_path + ".j", &result)) { + die("Failed to get normalized junction base directory."); + } + return result; +} + +void JavaBinaryLauncher::DeleteJunctionBaseDir() { + string junction_base_dir_norm = GetJunctionBaseDir(); + if (!DoesDirectoryPathExist(junction_base_dir_norm.c_str())) { + return; + } + vector junctions; + blaze_util::GetAllFilesUnder(junction_base_dir_norm, &junctions); + for (const auto& junction : junctions) { + if (!DeleteDirectoryByPath(junction.c_str())) { + PrintError(GetLastErrorString().c_str()); + } + } + if (!DeleteDirectoryByPath(junction_base_dir_norm.c_str())) { + PrintError(GetLastErrorString().c_str()); + } +} + string JavaBinaryLauncher::CreateClasspathJar(const string& classpath) { string binary_base_path = GetBinaryPathWithoutExtension(this->GetCommandlineArguments()[0]); @@ -144,28 +196,55 @@ string JavaBinaryLauncher::CreateClasspathJar(const string& classpath) { manifest_classpath << "Class-Path:"; stringstream classpath_ss(classpath); string 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; + string junction_base_dir_norm = GetJunctionBaseDir(); + int junction_count = 0; + // Make sure the junction base directory doesn't exist already. + DeleteJunctionBaseDir(); + blaze_util::MakeDirectories(junction_base_dir_norm, 0755); + while (getline(classpath_ss, path, ';')) { - manifest_classpath << ' '; if (blaze_util::IsAbsolute(path)) { - if (!NormalizePath(path, &path_norm) || - !RelativeTo(path_norm, abs_manifest_jar_dir_norm, &path)) { + if (!NormalizePath(path, &path_norm)) { die("CreateClasspathJar failed"); } - } - if (path.find_first_of(" \\") != string::npos) { - for (const auto& x : path) { - if (x == ' ') { - manifest_classpath << "%20"; - } - if (x == '\\') { - manifest_classpath << "/"; + + // 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]) { + string jar_dir = GetParentDirFromPath(path_norm); + string jar_base_name = GetBaseNameFromPath(path_norm); + string junction; + auto search = jar_dirs.find(jar_dir); + if (search == jar_dirs.end()) { + junction = + junction_base_dir_norm + "\\" + std::to_string(junction_count++); + + wstring wjar_dir( + blaze_util::CstringToWstring(junction.c_str()).get()); + wstring wjunction( + blaze_util::CstringToWstring(jar_dir.c_str()).get()); + wstring werror(bazel::windows::CreateJunction(wjar_dir, wjunction)); + if (!werror.empty()) { + string error(werror.begin(), werror.end()); + die("CreateClasspathJar failed: %s", error.c_str()); + } + + jar_dirs.insert(std::make_pair(jar_dir, junction)); } else { - manifest_classpath << x; + junction = search->second; } + path_norm = junction + "\\" + jar_base_name; + } + + if (!RelativeTo(path_norm, abs_manifest_jar_dir_norm, &path)) { + die("CreateClasspathJar failed"); } - } else { - manifest_classpath << path; } + WriteJarClasspath(path, &manifest_classpath); } string rand_id = "-" + GetRandomStr(10); @@ -335,6 +414,7 @@ ExitCode JavaBinaryLauncher::Launch() { // Delete classpath jar file after execution. if (!classpath_jar.empty()) { DeleteFileByPath(classpath_jar.c_str()); + DeleteJunctionBaseDir(); } return exit_code; diff --git a/src/tools/launcher/java_launcher.h b/src/tools/launcher/java_launcher.h index 896aacd74a2f52..93366b3a8aadc2 100644 --- a/src/tools/launcher/java_launcher.h +++ b/src/tools/launcher/java_launcher.h @@ -90,6 +90,13 @@ class JavaBinaryLauncher : public BinaryLauncherBase { // // Return the path of the classpath jar created. std::string CreateClasspathJar(const std::string& classpath); + + // Creat a directory based on the binary path, all the junctions will be + // generated under this directory. + std::string GetJunctionBaseDir(); + + // Delete all the junction directory and all the junctions under it. + void DeleteJunctionBaseDir(); }; } // namespace launcher diff --git a/src/tools/launcher/util/launcher_util.cc b/src/tools/launcher/util/launcher_util.cc index 573a99669a5a29..2ef25a352f21d2 100644 --- a/src/tools/launcher/util/launcher_util.cc +++ b/src/tools/launcher/util/launcher_util.cc @@ -100,6 +100,10 @@ bool DeleteFileByPath(const char* path) { return DeleteFileW(AsAbsoluteWindowsPath(path).c_str()); } +bool DeleteDirectoryByPath(const char* path) { + return RemoveDirectoryW(AsAbsoluteWindowsPath(path).c_str()); +} + string GetBinaryPathWithoutExtension(const string& binary) { if (binary.find(".exe", binary.size() - 4) != string::npos) { return binary.substr(0, binary.length() - 4); @@ -184,6 +188,14 @@ bool NormalizePath(const string& path, string* result) { return true; } +string GetBaseNameFromPath(const string& path) { + return path.substr(path.find_last_of("\\/") + 1); +} + +string GetParentDirFromPath(const string& path) { + return path.substr(0, path.find_last_of("\\/")); +} + bool RelativeTo(const string& path, const string& base, string* result) { if (blaze_util::IsAbsolute(path) != blaze_util::IsAbsolute(base)) { PrintError( diff --git a/src/tools/launcher/util/launcher_util.h b/src/tools/launcher/util/launcher_util.h index ae254bd09a044b..760eb7266e2d5b 100644 --- a/src/tools/launcher/util/launcher_util.h +++ b/src/tools/launcher/util/launcher_util.h @@ -61,6 +61,12 @@ bool DoesDirectoryPathExist(const char* path); // Delete a file at a given path. bool DeleteFileByPath(const char* path); +// Delete a directory at a given path,. +// If it's a real directory, it must be empty +// If it's a junction, the target directory it points to doesn't have to be +// empty, the junction will be deleted regardless of the state of the target. +bool DeleteDirectoryByPath(const char* path); + // Get the value of a specific environment variable // // Return true if succeeded and the result is stored in buffer. @@ -79,6 +85,12 @@ std::string GetRandomStr(size_t len); // Normalize a path to a Windows path in lower case bool NormalizePath(const std::string& path, std::string* result); +// Get the base name from a normalized absoulute path +std::string GetBaseNameFromPath(const std::string& path); + +// Get parent directory from a normalized absoulute path +std::string GetParentDirFromPath(const std::string& path); + // Calculate a relative path from `path` to `base`. // This function expects normalized Windows path in lower case. // `path` and `base` should be both absolute or both relative.