diff options
author | Yun Peng <pcloudy@google.com> | 2017-07-21 15:57:05 +0200 |
---|---|---|
committer | Jakob Buchgraber <buchgr@google.com> | 2017-07-24 09:51:07 +0200 |
commit | 09dd8c0330a3e18951d9481751ef99d3668115c0 (patch) | |
tree | 2308b2881af63db841ee66ae9e9800b262df170d /src/tools/launcher | |
parent | 0736aead33ba3d144f6cb2498ec175c3bfce9556 (diff) |
Windows: Implement native binary launcher
The native launcher is implemented based on this design doc:
https://docs.google.com/document/d/1z6Xv95CJYNYNYylcRklA6xBeesNLc54dqXfri0z0e14/edit?usp=sharing
Change-Id: I83bae844f792a587ce0e342a3c0f238b760afeaa
PiperOrigin-RevId: 162736269
Diffstat (limited to 'src/tools/launcher')
-rw-r--r-- | src/tools/launcher/BUILD | 53 | ||||
-rw-r--r-- | src/tools/launcher/bash_launcher.cc | 54 | ||||
-rw-r--r-- | src/tools/launcher/bash_launcher.h | 37 | ||||
-rw-r--r-- | src/tools/launcher/java_launcher.cc | 26 | ||||
-rw-r--r-- | src/tools/launcher/java_launcher.h | 34 | ||||
-rw-r--r-- | src/tools/launcher/launcher.cc | 158 | ||||
-rw-r--r-- | src/tools/launcher/launcher.h | 102 | ||||
-rw-r--r-- | src/tools/launcher/launcher_main.cc | 63 | ||||
-rw-r--r-- | src/tools/launcher/python_launcher.cc | 26 | ||||
-rw-r--r-- | src/tools/launcher/python_launcher.h | 34 | ||||
-rw-r--r-- | src/tools/launcher/util/BUILD | 38 | ||||
-rw-r--r-- | src/tools/launcher/util/data_parser.cc | 105 | ||||
-rw-r--r-- | src/tools/launcher/util/data_parser.h | 50 | ||||
-rw-r--r-- | src/tools/launcher/util/data_parser_test.cc | 189 | ||||
-rw-r--r-- | src/tools/launcher/util/launcher_util.cc | 122 | ||||
-rw-r--r-- | src/tools/launcher/util/launcher_util.h | 56 | ||||
-rw-r--r-- | src/tools/launcher/util/launcher_util_test.cc | 94 |
17 files changed, 1241 insertions, 0 deletions
diff --git a/src/tools/launcher/BUILD b/src/tools/launcher/BUILD new file mode 100644 index 0000000000..d6617ce172 --- /dev/null +++ b/src/tools/launcher/BUILD @@ -0,0 +1,53 @@ +filegroup( + name = "srcs", + srcs = glob(["**"]) + ["//src/tools/launcher/util:srcs"], + visibility = ["//src:__pkg__"], +) + +cc_binary( + name = "launcher", + srcs = ["launcher_main.cc"], + visibility = [ + "//src:__pkg__", + "//tools/launcher:__pkg__", + ], + deps = [ + ":bash_launcher", + ":java_launcher", + ":launcher_base", + ":python_launcher", + "//src/tools/launcher/util", + "//src/tools/launcher/util:data_parser", + ], +) + +cc_library( + name = "launcher_base", + srcs = ["launcher.cc"], + hdrs = ["launcher.h"], + deps = [ + "//src/tools/launcher/util", + "//src/tools/launcher/util:data_parser", + ], +) + +cc_library( + name = "java_launcher", + srcs = ["java_launcher.cc"], + hdrs = ["java_launcher.h"], + deps = [":launcher_base"], +) + +cc_library( + name = "python_launcher", + srcs = ["python_launcher.cc"], + hdrs = ["python_launcher.h"], + deps = [":launcher_base"], +) + +cc_library( + name = "bash_launcher", + srcs = ["bash_launcher.cc"], + hdrs = ["bash_launcher.h"], + deps = [":launcher_base"], +) diff --git a/src/tools/launcher/bash_launcher.cc b/src/tools/launcher/bash_launcher.cc new file mode 100644 index 0000000000..e1203a3f58 --- /dev/null +++ b/src/tools/launcher/bash_launcher.cc @@ -0,0 +1,54 @@ +// Copyright 2017 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <sstream> +#include <string> +#include <vector> + +#include "src/tools/launcher/bash_launcher.h" +#include "src/tools/launcher/util/launcher_util.h" + +namespace bazel { +namespace launcher { + +using std::ostringstream; +using std::string; +using std::vector; + +ExitCode BashBinaryLauncher::Launch() { + string bash_binary = this->GetLaunchInfoByKey(BASH_BIN_PATH); + // If specified bash binary path doesn't exist, then fall back to + // bash.exe and hope it's in PATH. + if (!DoesFilePathExist(bash_binary)) { + bash_binary = "bash.exe"; + } + + vector<string> origin_args = this->GetCommandlineArguments(); + ostringstream bash_command; + string bash_main_file = + this->Rlocation(this->GetLaunchInfoByKey(BASH_MAIN_FILE)); + bash_command << GetEscapedArgument(bash_main_file); + for (int i = 1; i < origin_args.size(); i++) { + bash_command << ' '; + bash_command << GetEscapedArgument(origin_args[i]); + } + + vector<string> args; + args.push_back("-c"); + args.push_back(bash_command.str()); + return this->LaunchProcess(bash_binary, args); +} + +} // namespace launcher +} // namespace bazel diff --git a/src/tools/launcher/bash_launcher.h b/src/tools/launcher/bash_launcher.h new file mode 100644 index 0000000000..8070016800 --- /dev/null +++ b/src/tools/launcher/bash_launcher.h @@ -0,0 +1,37 @@ +// Copyright 2017 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef BAZEL_SRC_TOOLS_LAUNCHER_BASH_LAUNCHER_H_ +#define BAZEL_SRC_TOOLS_LAUNCHER_BASH_LAUNCHER_H_ + +#include "src/tools/launcher/launcher.h" + +namespace bazel { +namespace launcher { + +static constexpr const char* BASH_BIN_PATH = "bash_bin_path"; +static constexpr const char* BASH_MAIN_FILE = "bash_main_file"; + +class BashBinaryLauncher : public BinaryLauncherBase { + public: + BashBinaryLauncher(const LaunchDataParser::LaunchInfo& launch_info, int argc, + char* argv[]) + : BinaryLauncherBase(launch_info, argc, argv){} + ExitCode Launch(); +}; + +} // namespace launcher +} // namespace bazel + +#endif // BAZEL_SRC_TOOLS_LAUNCHER_BASH_LAUNCHER_H_ diff --git a/src/tools/launcher/java_launcher.cc b/src/tools/launcher/java_launcher.cc new file mode 100644 index 0000000000..ce626d347e --- /dev/null +++ b/src/tools/launcher/java_launcher.cc @@ -0,0 +1,26 @@ +// Copyright 2017 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "src/tools/launcher/java_launcher.h" + +namespace bazel { +namespace launcher { + +ExitCode JavaBinaryLauncher::Launch() { + // TODO(pcloudy): Implement Java launcher + return 0; +} + +} // namespace launcher +} // namespace bazel diff --git a/src/tools/launcher/java_launcher.h b/src/tools/launcher/java_launcher.h new file mode 100644 index 0000000000..5ffe5881ab --- /dev/null +++ b/src/tools/launcher/java_launcher.h @@ -0,0 +1,34 @@ +// Copyright 2017 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef BAZEL_SRC_TOOLS_LAUNCHER_JAVA_LAUNCHER_H_ +#define BAZEL_SRC_TOOLS_LAUNCHER_JAVA_LAUNCHER_H_ + +#include "src/tools/launcher/launcher.h" + +namespace bazel { +namespace launcher { + +class JavaBinaryLauncher : public BinaryLauncherBase { + public: + JavaBinaryLauncher(const LaunchDataParser::LaunchInfo& launch_info, int argc, + char* argv[]) + : BinaryLauncherBase(launch_info, argc, argv){} + ExitCode Launch(); +}; + +} // namespace launcher +} // namespace bazel + +#endif // BAZEL_SRC_TOOLS_LAUNCHER_JAVA_LAUNCHER_H_ diff --git a/src/tools/launcher/launcher.cc b/src/tools/launcher/launcher.cc new file mode 100644 index 0000000000..86e9d8d473 --- /dev/null +++ b/src/tools/launcher/launcher.cc @@ -0,0 +1,158 @@ +// Copyright 2017 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <windows.h> +#include <fstream> +#include <iostream> +#include <sstream> +#include <string> +#include <vector> + +#include "src/tools/launcher/launcher.h" +#include "src/tools/launcher/util/data_parser.h" +#include "src/tools/launcher/util/launcher_util.h" + +namespace bazel { +namespace launcher { + +using std::ifstream; +using std::ostringstream; +using std::string; +using std::unordered_map; +using std::vector; + +BinaryLauncherBase::BinaryLauncherBase( + const LaunchDataParser::LaunchInfo& _launch_info, int argc, char* argv[]) + : launch_info(_launch_info) { + this->workspace_name = GetLaunchInfoByKey(WORKSPACE_NAME); + for (int i = 0; i < argc; i++) { + this->commandline_arguments.push_back(argv[i]); + } + ParseManifestFile(&this->manifest_file_map, FindManifestFile()); +} + +string BinaryLauncherBase::FindManifestFile() const { + // Get the name of the binary + string binary = GetBinaryPathWithoutExtension(this->commandline_arguments[0]); + + // Try to find <path to binary>.runfiles/MANIFEST + string manifest_file = binary + ".runfiles\\MANIFEST"; + if (DoesFilePathExist(manifest_file)) { + return manifest_file; + } + + // Also try to check if <path to binary>.runfiles_manifest exists + manifest_file = binary + ".runfiles_manifest"; + if (DoesFilePathExist(manifest_file)) { + return manifest_file; + } + + die("Couldn't find MANIFEST file %s.runfiles\\", binary.c_str()); +} + +void BinaryLauncherBase::ParseManifestFile(ManifestFileMap* manifest_file_map, + const string& manifest_path) { + ifstream manifest_file(manifest_path.c_str()); + + if (!manifest_file) { + die("Couldn't open MANIFEST file: %s", manifest_path.c_str()); + } + + string line; + while (getline(manifest_file, line)) { + size_t space_pos = line.find_first_of(' '); + if (space_pos == string::npos) { + die("Wrong MANIFEST format at line: %s", line.c_str()); + } + string key = line.substr(0, space_pos); + string value = line.substr(space_pos + 1); + manifest_file_map->insert(make_pair(key, value)); + } +} + +string BinaryLauncherBase::Rlocation(const string& path) const { + auto entry = manifest_file_map.find(this->workspace_name + "/" + path); + if (entry == manifest_file_map.end()) { + die("Rlocation failed on %s, path doesn't exist in MANIFEST file", + path.c_str()); + } + return entry->second; +} + +string BinaryLauncherBase::GetLaunchInfoByKey(const string& key) { + auto item = launch_info.find(key); + if (item == launch_info.end()) { + die("Cannot find key \"%s\" from launch data.\n", key.c_str()); + } + return item->second; +} + +const vector<string>& BinaryLauncherBase::GetCommandlineArguments() const { + return this->commandline_arguments; +} + +void BinaryLauncherBase::CreateCommandLine( + CmdLine* result, const string& executable, + const vector<string>& arguments) const { + ostringstream cmdline; + cmdline << '\"' << executable << '\"'; + bool first = true; + for (const auto& s : arguments) { + cmdline << ' ' << GetEscapedArgument(s); + } + + string cmdline_str = cmdline.str(); + if (cmdline_str.size() >= MAX_CMDLINE_LENGTH) { + die("Command line too long: %s", cmdline_str.c_str()); + } + + // Copy command line into a mutable buffer. + // CreateProcess is allowed to mutate its command line argument. + strncpy(result->cmdline, cmdline_str.c_str(), MAX_CMDLINE_LENGTH - 1); + result->cmdline[MAX_CMDLINE_LENGTH - 1] = 0; +} + +ExitCode BinaryLauncherBase::LaunchProcess( + const string& executable, const vector<string>& arguments) const { + CmdLine cmdline; + CreateCommandLine(&cmdline, executable, arguments); + PROCESS_INFORMATION processInfo = {0}; + STARTUPINFOA startupInfo = {0}; + startupInfo.cb = sizeof(startupInfo); + BOOL ok = CreateProcessA( + /* lpApplicationName */ NULL, + /* lpCommandLine */ cmdline.cmdline, + /* lpProcessAttributes */ NULL, + /* lpThreadAttributes */ NULL, + /* bInheritHandles */ FALSE, + /* dwCreationFlags */ 0, + /* lpEnvironment */ NULL, + /* lpCurrentDirectory */ NULL, + /* lpStartupInfo */ &startupInfo, + /* lpProcessInformation */ &processInfo); + if (!ok) { + PrintError("Cannot launch process:\n%s", GetLastErrorString().c_str()); + return GetLastError(); + } + WaitForSingleObject(processInfo.hProcess, INFINITE); + ExitCode exit_code; + GetExitCodeProcess(processInfo.hProcess, + reinterpret_cast<LPDWORD>(&exit_code)); + CloseHandle(processInfo.hProcess); + CloseHandle(processInfo.hThread); + return exit_code; +} + +} // namespace launcher +} // namespace bazel diff --git a/src/tools/launcher/launcher.h b/src/tools/launcher/launcher.h new file mode 100644 index 0000000000..e9f461b324 --- /dev/null +++ b/src/tools/launcher/launcher.h @@ -0,0 +1,102 @@ +// Copyright 2017 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef BAZEL_SRC_TOOLS_LAUNCHER_LAUNCHER_H_ +#define BAZEL_SRC_TOOLS_LAUNCHER_LAUNCHER_H_ + +#include <string> +#include <unordered_map> +#include <vector> + +#include "src/tools/launcher/util/data_parser.h" + +namespace bazel { +namespace launcher { + +typedef int32_t ExitCode; +static constexpr const char* WORKSPACE_NAME = "workspace_name"; + +// The maximum length of lpCommandLine is 32768 characters. +// https://msdn.microsoft.com/en-us/library/windows/desktop/ms682425(v=vs.85).aspx +static const int MAX_CMDLINE_LENGTH = 32768; + +struct CmdLine { + char cmdline[MAX_CMDLINE_LENGTH]; +}; + +class BinaryLauncherBase { + typedef std::unordered_map<std::string, std::string> ManifestFileMap; + + public: + BinaryLauncherBase(const LaunchDataParser::LaunchInfo& launch_info, int argc, + char* argv[]); + + // Get launch information based on a launch info key. + std::string GetLaunchInfoByKey(const std::string& key); + + // Get the original command line arguments passed to this binary. + const std::vector<std::string>& GetCommandlineArguments() const; + + // Map a runfile path to its absolute path. + std::string Rlocation(const std::string& path) const; + + // Lauch a process with given executable and command line arguments. + // + // exectuable: the binary to be executed. + // arguments: the command line arguments to be passed to the exectuable, + // it doesn't include the exectuable itself. + ExitCode LaunchProcess(const std::string& executable, + const std::vector<std::string>& arguments) const; + + // A launch function to be implemented for a specific language. + virtual ExitCode Launch() = 0; + + private: + // A map to store all the launch information. + const LaunchDataParser::LaunchInfo& launch_info; + + // The commandline arguments recieved. + // The first argument is the path of this launcher itself. + std::vector<std::string> commandline_arguments; + + // The workspace name of the repository this target belongs to. + std::string workspace_name; + + // A map to store all entries of the manifest file. + std::unordered_map<std::string, std::string> manifest_file_map; + + // Create a command line to be passed to Windows CreateProcessA API. + // + // exectuable: the binary to be executed. + // arguments: the command line arguments to be passed to the exectuable, + // it doesn't include the exectuable itself. + void CreateCommandLine(CmdLine* result, const std::string& executable, + const std::vector<std::string>& arguments) const; + + // Find manifest file of the binary + // + // Expect the manifest file to be at + // 1. <path>/<to>/<binary>/<target_name>.runfiles/MANIFEST + // or 2. <path>/<to>/<binary>/<target_name>.runfiles_manifest + std::string FindManifestFile() const; + + // Parse manifest file into a map + static void ParseManifestFile(ManifestFileMap* manifest_file_map, + const std::string& manifest_path); +}; + +} // namespace launcher +} // namespace bazel + +#endif // BAZEL_SRC_TOOLS_LAUNCHER_LAUNCHER_H_ diff --git a/src/tools/launcher/launcher_main.cc b/src/tools/launcher/launcher_main.cc new file mode 100644 index 0000000000..6f9133b6f2 --- /dev/null +++ b/src/tools/launcher/launcher_main.cc @@ -0,0 +1,63 @@ +// Copyright 2017 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <memory> + +#include "src/tools/launcher/bash_launcher.h" +#include "src/tools/launcher/java_launcher.h" +#include "src/tools/launcher/launcher.h" +#include "src/tools/launcher/python_launcher.h" +#include "src/tools/launcher/util/data_parser.h" +#include "src/tools/launcher/util/launcher_util.h" + +static constexpr const char* BINARY_TYPE = "binary_type"; + +using bazel::launcher::BashBinaryLauncher; +using bazel::launcher::BinaryLauncherBase; +using bazel::launcher::GetBinaryPathWithExtension; +using bazel::launcher::JavaBinaryLauncher; +using bazel::launcher::LaunchDataParser; +using bazel::launcher::PythonBinaryLauncher; +using bazel::launcher::die; +using std::make_unique; +using std::unique_ptr; + +int main(int argc, char* argv[]) { + LaunchDataParser::LaunchInfo launch_info; + + if (!LaunchDataParser::GetLaunchInfo(GetBinaryPathWithExtension(argv[0]), + &launch_info)) { + die("Failed to parse launch info."); + } + + auto result = launch_info.find(BINARY_TYPE); + if (result == launch_info.end()) { + die("Cannot find key \"%s\" from launch data.", BINARY_TYPE); + } + + unique_ptr<BinaryLauncherBase> binary_launcher; + + if (result->second == "Python") { + binary_launcher = + make_unique<PythonBinaryLauncher>(launch_info, argc, argv); + } else if (result->second == "Bash") { + binary_launcher = make_unique<BashBinaryLauncher>(launch_info, argc, argv); + } else if (result->second == "Java") { + binary_launcher = make_unique<JavaBinaryLauncher>(launch_info, argc, argv); + } else { + die("Unknown binary type, cannot launch anything."); + } + + return binary_launcher->Launch(); +} diff --git a/src/tools/launcher/python_launcher.cc b/src/tools/launcher/python_launcher.cc new file mode 100644 index 0000000000..b5dded9d23 --- /dev/null +++ b/src/tools/launcher/python_launcher.cc @@ -0,0 +1,26 @@ +// Copyright 2017 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "src/tools/launcher/python_launcher.h" + +namespace bazel { +namespace launcher { + +ExitCode PythonBinaryLauncher::Launch() { + // TODO(pcloudy): Implement Python launcher + return 0; +} + +} // namespace launcher +} // namespace bazel diff --git a/src/tools/launcher/python_launcher.h b/src/tools/launcher/python_launcher.h new file mode 100644 index 0000000000..1a21ca308e --- /dev/null +++ b/src/tools/launcher/python_launcher.h @@ -0,0 +1,34 @@ +// Copyright 2017 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef BAZEL_SRC_TOOLS_LAUNCHER_PYTHON_LAUNCHER_H_ +#define BAZEL_SRC_TOOLS_LAUNCHER_PYTHON_LAUNCHER_H_ + +#include "src/tools/launcher/launcher.h" + +namespace bazel { +namespace launcher { + +class PythonBinaryLauncher : public BinaryLauncherBase { + public: + PythonBinaryLauncher(const LaunchDataParser::LaunchInfo& launch_info, + int argc, char* argv[]) + : BinaryLauncherBase(launch_info, argc, argv){} + ExitCode Launch(); +}; + +} // namespace launcher +} // namespace bazel + +#endif // BAZEL_SRC_TOOLS_LAUNCHER_PYTHON_LAUNCHER_H_ diff --git a/src/tools/launcher/util/BUILD b/src/tools/launcher/util/BUILD new file mode 100644 index 0000000000..7ecee600b6 --- /dev/null +++ b/src/tools/launcher/util/BUILD @@ -0,0 +1,38 @@ +package(default_visibility = ["//src/tools/launcher:__subpackages__"]) + +filegroup( + name = "srcs", + srcs = glob(["**"]), + visibility = ["//visibility:public"], +) + +cc_library( + name = "data_parser", + srcs = ["data_parser.cc"], + hdrs = ["data_parser.h"], + deps = [":util"], +) + +cc_library( + name = "util", + srcs = ["launcher_util.cc"], + hdrs = ["launcher_util.h"], +) + +cc_test( + name = "util_test", + srcs = ["launcher_util_test.cc"], + deps = [ + ":util", + "//third_party:gtest", + ], +) + +cc_test( + name = "data_parser_test", + srcs = ["data_parser_test.cc"], + deps = [ + ":data_parser", + "//third_party:gtest", + ], +) diff --git a/src/tools/launcher/util/data_parser.cc b/src/tools/launcher/util/data_parser.cc new file mode 100644 index 0000000000..98c8b1daf6 --- /dev/null +++ b/src/tools/launcher/util/data_parser.cc @@ -0,0 +1,105 @@ +// Copyright 2017 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <fstream> +#include <memory> +#include <string> +#include <unordered_map> + +#include "src/tools/launcher/util/data_parser.h" +#include "src/tools/launcher/util/launcher_util.h" + +namespace bazel { +namespace launcher { + +using std::ifstream; +using std::ios; +using std::make_unique; +using std::string; +using std::unique_ptr; + +int64_t LaunchDataParser::ReadDataSize(ifstream* binary) { + int64_t data_size; + binary->seekg(0 - sizeof(data_size), ios::end); + binary->read(reinterpret_cast<char*>(&data_size), sizeof(data_size)); + return data_size; +} + +void LaunchDataParser::ReadLaunchData(ifstream* binary, char* launch_data, + int64_t data_size) { + binary->seekg(0 - data_size - sizeof(data_size), ios::end); + binary->read(launch_data, data_size); +} + +bool LaunchDataParser::ParseLaunchData(LaunchInfo* launch_info, + const char* launch_data, + int64_t data_size) { + int64_t start, end, equal; + start = 0; + while (start < data_size) { + // Move start to point to the next non-null character. + while (launch_data[start] == '\0' && start < data_size) { + start++; + } + // Move end to the next null character or end of the string, + // also find the first equal symbol appears. + end = start; + equal = -1; + while (launch_data[end] != '\0' && end < data_size) { + if (equal == -1 && launch_data[end] == '=') { + equal = end; + } + end++; + } + if (equal == -1) { + PrintError("Cannot find equal symbol in line: %s", + string(launch_data + start, end - start).c_str()); + return false; + } else if (start == equal) { + PrintError("Key is empty string in line: %s", + string(launch_data + start, end - start).c_str()); + return false; + } else { + string key(launch_data + start, equal - start); + string value(launch_data + equal + 1, end - equal - 1); + if (launch_info->find(key) != launch_info->end()) { + PrintError("Duplicated launch info key: %s", key.c_str()); + return false; + } + launch_info->insert(make_pair(key, value)); + } + start = end + 1; + } + return true; +} + +bool LaunchDataParser::GetLaunchInfo(const string& binary_path, + LaunchInfo* launch_info) { + unique_ptr<ifstream> binary = + make_unique<ifstream>(binary_path, ios::binary | ios::in); + int64_t data_size = ReadDataSize(binary.get()); + if (data_size == 0) { + PrintError("No data appended, cannot launch anything!"); + return false; + } + unique_ptr<char[]> launch_data(new char[data_size]); + ReadLaunchData(binary.get(), launch_data.get(), data_size); + if (!ParseLaunchData(launch_info, launch_data.get(), data_size)) { + return false; + } + return true; +} + +} // namespace launcher +} // namespace bazel diff --git a/src/tools/launcher/util/data_parser.h b/src/tools/launcher/util/data_parser.h new file mode 100644 index 0000000000..ecaf12a9b9 --- /dev/null +++ b/src/tools/launcher/util/data_parser.h @@ -0,0 +1,50 @@ +// Copyright 2017 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef BAZEL_SRC_TOOLS_LAUNCHER_UTIL_DATA_PARSER_H_ +#define BAZEL_SRC_TOOLS_LAUNCHER_UTIL_DATA_PARSER_H_ + +#include <fstream> +#include <memory> +#include <string> +#include <unordered_map> + +namespace bazel { +namespace launcher { + +class LaunchDataParser { + public: + typedef std::unordered_map<std::string, std::string> LaunchInfo; + LaunchDataParser() = delete; + ~LaunchDataParser() = delete; + static bool GetLaunchInfo(const std::string& binary_path, + LaunchInfo* launch_info); + + private: + // Read the last 64 bit from the given binary to get the data size + static int64_t ReadDataSize(std::ifstream* binary); + + // Read launch data at the end of the given binary into a buffer + static void ReadLaunchData(std::ifstream* binary, char* launch_data, + int64_t data_size); + + // Parse the launch data into a map + static bool ParseLaunchData(LaunchInfo* launch_info, const char* launch_data, + int64_t data_size); +}; + +} // namespace launcher +} // namespace bazel + +#endif // BAZEL_SRC_TOOLS_LAUNCHER_UTIL_DATA_PARSER_H_ diff --git a/src/tools/launcher/util/data_parser_test.cc b/src/tools/launcher/util/data_parser_test.cc new file mode 100644 index 0000000000..1adaf3aebd --- /dev/null +++ b/src/tools/launcher/util/data_parser_test.cc @@ -0,0 +1,189 @@ +// Copyright 2017 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <cstdlib> +#include <fstream> +#include <iostream> +#include <memory> +#include <vector> + +#include "src/tools/launcher/util/data_parser.h" +#include "gtest/gtest.h" +#include "src/tools/launcher/util/launcher_util.h" + +namespace bazel { +namespace launcher { + +using std::getenv; +using std::ios; +using std::make_unique; +using std::ofstream; +using std::pair; +using std::string; +using std::unique_ptr; +using std::vector; + +class LaunchDataParserTest : public ::testing::Test { + protected: + LaunchDataParserTest() {} + + virtual ~LaunchDataParserTest() {} + + void SetUp() override { + char* tmpdir = getenv("TEST_TMPDIR"); + if (tmpdir != NULL) { + test_tmpdir = string(tmpdir); + } else { + tmpdir = getenv("TEMP"); + ASSERT_FALSE(tmpdir == NULL); + test_tmpdir = string(tmpdir); + } + } + + void TearDown() override {} + + static void WriteBinaryFileWithList(const string& binary_file, + const vector<string>& launch_info) { + ofstream binary_file_stream(binary_file, ios::out | ios::binary); + + int64_t data_size = 0; + for (auto const& entry : launch_info) { + binary_file_stream << entry; + binary_file_stream << '\0'; + data_size += entry.length() + 1; + } + + binary_file_stream.write(reinterpret_cast<char*>(&data_size), + sizeof(data_size)); + } + + static void WriteBinaryFileWithMap( + const string& binary_file, + const vector<pair<string, string>>& launch_info) { + ofstream binary_file_stream(binary_file, ios::out | ios::binary); + + int64_t data_size = 0; + for (auto const& entry : launch_info) { + binary_file_stream << entry.first; + binary_file_stream.put('='); + binary_file_stream << entry.second; + binary_file_stream.put('\0'); + data_size += entry.first.length() + entry.second.length() + 2; + } + + binary_file_stream.write(reinterpret_cast<char*>(&data_size), + sizeof(data_size)); + } + + static bool ParseBinaryFile( + const string& binary_file, + LaunchDataParser::LaunchInfo* parsed_launch_info) { + if (LaunchDataParser::GetLaunchInfo(binary_file, parsed_launch_info)) { + return true; + } + exit(-1); + } + + string GetLaunchInfo(const string& key) const { + auto item = parsed_launch_info->find(key); + if (item == parsed_launch_info->end()) { + return "Cannot find key: " + key; + } + return item->second; + } + + string test_tmpdir; + unique_ptr<LaunchDataParser::LaunchInfo> parsed_launch_info; +}; + +TEST_F(LaunchDataParserTest, GetLaunchInfoTest) { + vector<pair<string, string>> launch_info = { + {"binary_type", "Bash"}, + {"workspace_name", "__main__"}, + {"bash_bin_path", "C:\\foo\\bar\\bash.exe"}, + {"bash_main_file", "./bazel-bin/foo/bar/bin.sh"}, + {"empty_value_key", ""}, + }; + + string binary_file = test_tmpdir + "/binary_file"; + WriteBinaryFileWithMap(binary_file, launch_info); + + parsed_launch_info = make_unique<LaunchDataParser::LaunchInfo>(); + ASSERT_TRUE(ParseBinaryFile(binary_file, parsed_launch_info.get())); + + for (auto const& entry : launch_info) { + ASSERT_EQ(entry.second, GetLaunchInfo(entry.first)); + } + ASSERT_EQ(GetLaunchInfo("no_such_key"), "Cannot find key: no_such_key"); +} + +TEST_F(LaunchDataParserTest, EmptyLaunchInfoTest) { + string binary_file = test_tmpdir + "/empty_binary_file"; + WriteBinaryFileWithMap(binary_file, {}); + + parsed_launch_info = make_unique<LaunchDataParser::LaunchInfo>(); + // ASSERT_DEATH requires TEMP environment variable to be set. + // Otherwise, it will try to write to C:/Windows, then fails. + // A workaround in Bazel is to use --action_env to set TEMP. + ASSERT_DEATH(ParseBinaryFile(binary_file, parsed_launch_info.get()), + "LAUNCHER ERROR: No data appended, cannot launch anything!"); +} + +TEST_F(LaunchDataParserTest, DuplicatedLaunchInfoTest) { + string binary_file = test_tmpdir + "/duplicated_binary_file"; + WriteBinaryFileWithMap(binary_file, { + {"foo", "bar1"}, + {"foo", "bar2"}, + }); + + parsed_launch_info = make_unique<LaunchDataParser::LaunchInfo>(); + // ASSERT_DEATH requires TEMP environment variable to be set. + // Otherwise, it will try to write to C:/Windows, then fails. + // A workaround in Bazel is to use --action_env to set TEMP. + ASSERT_DEATH(ParseBinaryFile(binary_file, parsed_launch_info.get()), + "LAUNCHER ERROR: Duplicated launch info key: foo"); +} + +TEST_F(LaunchDataParserTest, EmptyKeyLaunchInfoTest) { + string binary_file = test_tmpdir + "/empty_key_binary_file"; + WriteBinaryFileWithMap(binary_file, { + {"foo", "bar"}, + {"", "bar2"}, + }); + + parsed_launch_info = make_unique<LaunchDataParser::LaunchInfo>(); + // ASSERT_DEATH requires TEMP environment variable to be set. + // Otherwise, it will try to write to C:/Windows, then fails. + // A workaround in Bazel is to use --action_env to set TEMP. + ASSERT_DEATH(ParseBinaryFile(binary_file, parsed_launch_info.get()), + "LAUNCHER ERROR: Key is empty string in line: =bar2"); +} + +TEST_F(LaunchDataParserTest, NoEqualSignLaunchInfoTest) { + string binary_file = test_tmpdir + "/no_equal_binary_file"; + WriteBinaryFileWithList(binary_file, { + "foo1=bar1", + "foo2bar2", + }); + + parsed_launch_info = make_unique<LaunchDataParser::LaunchInfo>(); + // ASSERT_DEATH requires TEMP environment variable to be set. + // Otherwise, it will try to write to C:/Windows, then fails. + // A workaround in Bazel is to use --action_env to set TEMP. + ASSERT_DEATH(ParseBinaryFile(binary_file, parsed_launch_info.get()), + "LAUNCHER ERROR: Cannot find equal symbol in line: foo2bar2"); +} + +} // namespace launcher +} // namespace bazel diff --git a/src/tools/launcher/util/launcher_util.cc b/src/tools/launcher/util/launcher_util.cc new file mode 100644 index 0000000000..4ef64eaa70 --- /dev/null +++ b/src/tools/launcher/util/launcher_util.cc @@ -0,0 +1,122 @@ +// Copyright 2017 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <windows.h> +#include <sstream> +#include <string> + +#include "src/tools/launcher/util/launcher_util.h" + +namespace bazel { +namespace launcher { + +using std::ostringstream; +using std::string; +using std::stringstream; + +string GetLastErrorString() { + DWORD last_error = GetLastError(); + if (last_error == 0) { + return string(); + } + + char* message_buffer; + size_t size = FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, last_error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&message_buffer, 0, NULL); + + stringstream result; + result << "(error: " << last_error << "): " << message_buffer; + LocalFree(message_buffer); + return result.str(); +} + +void die(const char* format, ...) { + va_list ap; + va_start(ap, format); + fputs("LAUNCHER ERROR: ", stderr); + vfprintf(stderr, format, ap); + va_end(ap); + fputc('\n', stderr); + exit(1); +} + +void PrintError(const char* format, ...) { + va_list ap; + va_start(ap, format); + fputs("LAUNCHER ERROR: ", stderr); + vfprintf(stderr, format, ap); + va_end(ap); + fputc('\n', stderr); +} + +bool DoesFilePathExist(const string& path) { + DWORD dwAttrib = GetFileAttributes(path.c_str()); + + return (dwAttrib != INVALID_FILE_ATTRIBUTES && + !(dwAttrib & FILE_ATTRIBUTE_DIRECTORY)); +} + +string GetBinaryPathWithoutExtension(const string& binary) { + if (binary.find(".exe", binary.size() - 4) != string::npos) { + return binary.substr(0, binary.length() - 4); + } + return binary; +} + +string GetBinaryPathWithExtension(const string& binary) { + return GetBinaryPathWithoutExtension(binary) + ".exe"; +} + +string GetEscapedArgument(const string& argument) { + ostringstream escaped_arg; + bool has_space = argument.find_first_of(' ') != string::npos; + + if (has_space) { + escaped_arg << '\"'; + } + + string::const_iterator it = argument.begin(); + while (it != argument.end()) { + char ch = *it++; + switch (ch) { + case '"': + // Escape double quotes + escaped_arg << "\\\""; + break; + + case '\\': + // Escape back slashes + escaped_arg << "\\\\"; + break; + + default: + escaped_arg << ch; + } + } + + if (has_space) { + escaped_arg << '\"'; + } + return escaped_arg.str(); +} + +} // namespace launcher +} // namespace bazel diff --git a/src/tools/launcher/util/launcher_util.h b/src/tools/launcher/util/launcher_util.h new file mode 100644 index 0000000000..10f5f53000 --- /dev/null +++ b/src/tools/launcher/util/launcher_util.h @@ -0,0 +1,56 @@ +// Copyright 2017 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef BAZEL_SRC_TOOLS_LAUNCHER_UTIL_LAUNCHER_UTIL_H_ +#define BAZEL_SRC_TOOLS_LAUNCHER_UTIL_LAUNCHER_UTIL_H_ + +#define PRINTF_ATTRIBUTE(string_index, first_to_check) + +#include <string> + +namespace bazel { +namespace launcher { + +std::string GetLastErrorString(); + +// Prints the specified error message and exits nonzero. +__declspec(noreturn) void die(const char* format, ...) PRINTF_ATTRIBUTE(1, 2); + +// Prints the specified error message. +void PrintError(const char* format, ...) PRINTF_ATTRIBUTE(1, 2); + +// Strip the .exe extension from binary path. +// +// On Windows, if the binary path is foo/bar/bin.exe then return foo/bar/bin +std::string GetBinaryPathWithoutExtension(const std::string& binary); + +// Add exectuable extension to binary path +// +// On Windows, if the binary path is foo/bar/bin then return foo/bar/bin.exe +std::string GetBinaryPathWithExtension(const std::string& binary); + +// Escape a command line argument. +// +// If the argument has space, then we quote it. +// Escape \ to \\ +// Escape " to \" +std::string GetEscapedArgument(const std::string& argument); + +// Check if a file exists at a given path. +bool DoesFilePathExist(const std::string& path); + +} // namespace launcher +} // namespace bazel + +#endif // BAZEL_SRC_TOOLS_LAUNCHER_UTIL_LAUNCHER_UTIL_H_ diff --git a/src/tools/launcher/util/launcher_util_test.cc b/src/tools/launcher/util/launcher_util_test.cc new file mode 100644 index 0000000000..2e528a88a3 --- /dev/null +++ b/src/tools/launcher/util/launcher_util_test.cc @@ -0,0 +1,94 @@ +// Copyright 2017 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <cstdlib> +#include <fstream> +#include <iostream> +#include <string> + +#include "src/tools/launcher/util/launcher_util.h" +#include "gtest/gtest.h" + +namespace bazel { +namespace launcher { + +using std::getenv; +using std::ios; +using std::ofstream; +using std::string; + +class LaunchUtilTest : public ::testing::Test { + protected: + LaunchUtilTest() {} + + virtual ~LaunchUtilTest() {} + + void SetUp() override { + char* tmpdir = getenv("TEST_TMPDIR"); + if (tmpdir != NULL) { + test_tmpdir = string(tmpdir); + } else { + tmpdir = getenv("TEMP"); + ASSERT_FALSE(tmpdir == NULL); + test_tmpdir = string(tmpdir); + } + } + + void TearDown() override {} + + string GetTmpDir() { return this->test_tmpdir; } + + // Create an empty file at path + static void CreateEmptyFile(const string& path) { + ofstream file_stream(path.c_str(), ios::out | ios::binary); + file_stream.put('\0'); + } + + private: + string test_tmpdir; +}; + +TEST_F(LaunchUtilTest, GetBinaryPathWithoutExtensionTest) { + ASSERT_EQ("foo", GetBinaryPathWithoutExtension("foo.exe")); + ASSERT_EQ("foo.sh", GetBinaryPathWithoutExtension("foo.sh.exe")); + ASSERT_EQ("foo.sh", GetBinaryPathWithoutExtension("foo.sh")); +} + +TEST_F(LaunchUtilTest, GetBinaryPathWithExtensionTest) { + ASSERT_EQ("foo.exe", GetBinaryPathWithExtension("foo")); + ASSERT_EQ("foo.sh.exe", GetBinaryPathWithExtension("foo.sh.exe")); + ASSERT_EQ("foo.sh.exe", GetBinaryPathWithExtension("foo.sh")); +} + +TEST_F(LaunchUtilTest, GetEscapedArgumentTest) { + ASSERT_EQ("foo", GetEscapedArgument("foo")); + ASSERT_EQ("\"foo bar\"", GetEscapedArgument("foo bar")); + ASSERT_EQ("\"\\\"foo bar\\\"\"", GetEscapedArgument("\"foo bar\"")); + ASSERT_EQ("foo\\\\bar", GetEscapedArgument("foo\\bar")); + ASSERT_EQ("foo\\\"bar", GetEscapedArgument("foo\"bar")); + ASSERT_EQ("C:\\\\foo\\\\bar\\\\", GetEscapedArgument("C:\\foo\\bar\\")); + ASSERT_EQ("\"C:\\\\foo foo\\\\bar\\\\\"", + GetEscapedArgument("C:\\foo foo\\bar\\")); +} + +TEST_F(LaunchUtilTest, DoesFilePathExistTest) { + string file1 = GetTmpDir() + "/foo"; + string file2 = GetTmpDir() + "/bar"; + CreateEmptyFile(file1); + ASSERT_TRUE(DoesFilePathExist(file1)); + ASSERT_FALSE(DoesFilePathExist(file2)); +} + +} // namespace launcher +} // namespace bazel |