Skip to content

Commit

Permalink
Windows, Java launcher: Support jar files under different drives
Browse files Browse the repository at this point in the history
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
  • Loading branch information
meteorcloudy authored and Copybara-Service committed May 14, 2018
1 parent 43181c9 commit f96f037
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 18 deletions.
4 changes: 2 additions & 2 deletions src/main/cpp/util/file.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 3 additions & 1 deletion src/main/cpp/util/file_windows.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
38 changes: 37 additions & 1 deletion src/test/shell/bazel/bazel_windows_example_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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="*"
Expand Down Expand Up @@ -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
Expand Down
108 changes: 94 additions & 14 deletions src/tools/launcher/java_launcher.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.

#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/strings.h"
#include "src/main/native/windows/file.h"
#include "src/tools/launcher/java_launcher.h"
#include "src/tools/launcher/util/launcher_util.h"

Expand All @@ -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";
Expand Down Expand Up @@ -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<string> 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]);
Expand All @@ -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<string, string> 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);
Expand Down Expand Up @@ -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;
Expand Down
7 changes: 7 additions & 0 deletions src/tools/launcher/java_launcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions src/tools/launcher/util/launcher_util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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(
Expand Down
12 changes: 12 additions & 0 deletions src/tools/launcher/util/launcher_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand Down

0 comments on commit f96f037

Please sign in to comment.