diff --git a/src/tools/remote/src/main/cpp/testonly_output_service/BUILD b/src/tools/remote/src/main/cpp/testonly_output_service/BUILD index ceeed0fdc306e9..bb90b85e4d549b 100644 --- a/src/tools/remote/src/main/cpp/testonly_output_service/BUILD +++ b/src/tools/remote/src/main/cpp/testonly_output_service/BUILD @@ -18,6 +18,8 @@ cc_binary( "memory.cc", "memory.h", "memory_unix.cc", + "string.cc", + "string.h", ], deps = [ "//src/main/protobuf:bazel_output_service_cc_grpc", diff --git a/src/tools/remote/src/main/cpp/testonly_output_service/bazel_output_service_impl.cc b/src/tools/remote/src/main/cpp/testonly_output_service/bazel_output_service_impl.cc index 6da715b2026fce..152580f8a090d1 100644 --- a/src/tools/remote/src/main/cpp/testonly_output_service/bazel_output_service_impl.cc +++ b/src/tools/remote/src/main/cpp/testonly_output_service/bazel_output_service_impl.cc @@ -14,10 +14,13 @@ #include "src/tools/remote/src/main/cpp/testonly_output_service/bazel_output_service_impl.h" -#include +#include +#include + #include -#include +#include "src/tools/remote/src/main/cpp/testonly_output_service/memory.h" +#include "src/tools/remote/src/main/cpp/testonly_output_service/string.h" #include "grpcpp/security/server_credentials.h" #include "grpcpp/server_builder.h" #include "grpcpp/server_context.h" @@ -65,18 +68,59 @@ grpc::Status BazelOutputServiceImpl::BatchStat( return grpc::Status(grpc::StatusCode::UNIMPLEMENTED, ""); } -int RunServer(int argc, char** argv) { - BazelOutputServiceImpl service; +constexpr uint16_t kDefaultPort = 8080; - std::string server_address = "0.0.0.0:8080"; +struct ParsedCommandLine { + Str8 error; + uint16_t port; +}; - grpc::ServerBuilder builder; - builder.AddListeningPort(server_address, grpc::InsecureServerCredentials()); - builder.RegisterService(&service); - std::unique_ptr server(builder.BuildAndStart()); - std::cerr << "Server listening on " << server_address << std::endl; +static ParsedCommandLine* ParseCommandLine(Arena* arena, int argc, + char** argv) { + TemporaryMemory scratch = BeginScratch(arena); + ParsedCommandLine* result = PushArray(arena, ParsedCommandLine, 1); + result->port = kDefaultPort; + Str8 port_prefix = Str8FromCStr("--port="); + for (int i = 1; i < argc; ++i) { + Str8 arg = Str8FromCStr(argv[i]); + if (StartsWithStr8(arg, port_prefix)) { + Str8 port_str = PushSubStr8(scratch.arena, arg, port_prefix.len); + ParsedUInt32 port = ParseUInt32(port_str); + if (port.value) { + result->port = port.value; + } else { + result->error = PushStr8F(arena, "Not a valid port: %s", port_str.ptr); + break; + } + } else { + result->error = PushStr8F(arena, "Unknown command line: %s", arg.ptr); + break; + } + } + EndScratch(scratch); + return result; +} + +int RunServer(int argc, char** argv) { + int exit_code = 0; + TemporaryMemory scratch = BeginScratch(0); + ParsedCommandLine* command_line = ParseCommandLine(scratch.arena, argc, argv); + if (IsEmptyStr8(command_line->error)) { + BazelOutputServiceImpl service; - server->Wait(); + Str8 address = PushStr8F(scratch.arena, "0.0.0.0:%d", command_line->port); + grpc::ServerBuilder builder; + builder.AddListeningPort((char*)address.ptr, + grpc::InsecureServerCredentials()); + builder.RegisterService(&service); + std::unique_ptr server = builder.BuildAndStart(); + fprintf(stderr, "Server listening on port %d...\n", command_line->port); - return 0; + server->Wait(); + } else { + fprintf(stderr, "%s\n", command_line->error.ptr); + exit_code = 1; + } + EndScratch(scratch); + return exit_code; } diff --git a/src/tools/remote/src/main/cpp/testonly_output_service/string.cc b/src/tools/remote/src/main/cpp/testonly_output_service/string.cc new file mode 100644 index 00000000000000..e30d74a20fa574 --- /dev/null +++ b/src/tools/remote/src/main/cpp/testonly_output_service/string.cc @@ -0,0 +1,88 @@ +// Copyright 2024 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/remote/src/main/cpp/testonly_output_service/string.h" + +#include +#include +#include +#include +#include +#include + +#include "src/tools/remote/src/main/cpp/testonly_output_service/memory.h" + +ParsedUInt32 ParseUInt32(Str8 str) { + ParsedUInt32 result = {}; + + result.valid = true; + for (size_t i = 0; i < str.len; ++i) { + uint8_t ch = str.ptr[i]; + if (ch < '0' || ch > '9') { + result.valid = false; + break; + } + result.value = result.value * 10 + ch - '0'; + } + + return result; +} + +Str8 PushStr8(Arena *arena, Str8 str) { + uint8_t *ptr = PushArray(arena, uint8_t, str.len + 1); + memcpy(ptr, str.ptr, str.len + 1); + Str8 result = {ptr, str.len}; + return result; +} + +Str8 PushStr8F(Arena *arena, const char *format, ...) { + constexpr size_t kInitBufferSize = 256; + size_t buf_len = kInitBufferSize; + char *buf_ptr = PushArray(arena, char, buf_len); + + va_list args; + va_start(args, format); + size_t str_len = vsnprintf(buf_ptr, buf_len, format, args); + va_end(args); + + if (str_len + 1 <= buf_len) { + // Free the unused part of the buffer. + PopArena(arena, buf_len - str_len - 1); + } else { + // The buffer was too small. We need to resize it and try again. + PopArena(arena, buf_len); + buf_len = str_len + 1; + buf_ptr = PushArray(arena, char, buf_len); + va_start(args, format); + vsnprintf(buf_ptr, buf_len, format, args); + va_end(args); + } + + Str8 result = {(uint8_t *)buf_ptr, str_len}; + return result; +} + +Str8 PushSubStr8(Arena *arena, Str8 str, size_t begin) { + Str8 result = PushSubStr8(arena, str, begin, str.len); + return result; +} + +Str8 PushSubStr8(Arena *arena, Str8 str, size_t begin, size_t end) { + assert(begin <= end && end <= str.len); + size_t len = end - begin; + uint8_t *ptr = PushArray(arena, uint8_t, len + 1); + memcpy(ptr, str.ptr + begin, len); + Str8 result = {ptr, len}; + return result; +} diff --git a/src/tools/remote/src/main/cpp/testonly_output_service/string.h b/src/tools/remote/src/main/cpp/testonly_output_service/string.h new file mode 100644 index 00000000000000..19a2006f2d707d --- /dev/null +++ b/src/tools/remote/src/main/cpp/testonly_output_service/string.h @@ -0,0 +1,72 @@ +// Copyright 2024 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_REMOTE_SRC_MAIN_CPP_TESTONLY_OUTPUT_SERVICE_STRING_H_ +#define BAZEL_SRC_TOOLS_REMOTE_SRC_MAIN_CPP_TESTONLY_OUTPUT_SERVICE_STRING_H_ + +#include +#include + +#include "src/tools/remote/src/main/cpp/testonly_output_service/memory.h" + +// Null terminated utf-8 string. +struct Str8 { + uint8_t *ptr; + // Length in bytes, excluding the null terminator. However, the string is + // always null terminated which means the memory pointed to by ptr must be + // at least len + 1 bytes long. + size_t len; +}; + +// Returns true if the string is empty. +static inline bool IsEmptyStr8(Str8 str) { + bool result = str.len == 0; + return result; +} + +// Constructs a Str8 from a C string. +static inline Str8 Str8FromCStr(const char *str) { + Str8 result = {(uint8_t *)str, strlen(str)}; + return result; +} + +// Returns true if the string starts with the given prefix. +static inline bool StartsWithStr8(Str8 str, Str8 prefix) { + bool result = + str.len >= prefix.len && memcmp(str.ptr, prefix.ptr, prefix.len) == 0; + return result; +} + +// The result of parsing an unsigned 32-bit integer from a string. The `value` +// is only valid if `valid` is true. +struct ParsedUInt32 { + bool valid; + uint32_t value; +}; + +// Parses an unsigned 32-bit integer from the given string. +ParsedUInt32 ParseUInt32(Str8 str); + +// Pushes a copy of the given string to the arena +Str8 PushStr8(Arena *arena, Str8 str); +// Pushes a formatted string to the arena. +Str8 PushStr8F(Arena *arena, const char *format, ...); +// Pushes a substring of the given string to the arena, from the index `begin` +// to the end of the string. +Str8 PushSubStr8(Arena *arena, Str8 str, size_t begin); +// Pushes a substring of the given string to the arena, from the index `begin` +// to the index `end` (exclusive). +Str8 PushSubStr8(Arena *arena, Str8 str, size_t begin, size_t end); + +#endif // BAZEL_SRC_TOOLS_REMOTE_SRC_MAIN_CPP_TESTONLY_OUTPUT_SERVICE_STRING_H_