From d2d7a6add1ca4c682c9c88a65f3780283175b4ac Mon Sep 17 00:00:00 2001 From: Zakhar Date: Tue, 4 Jul 2023 15:23:01 +0200 Subject: [PATCH] Update to the latest dev-tools and make code build with bazel 6.x (#68) * Make code work with the latest bazel (6.2.1) * Update dev-tools submodule * Format apply and fix windows build --- .github/workflows/build_and_test.yml | 8 +- .github/workflows/check_code_style.yml | 2 +- .mergequeue/config.yml | 16 +- README.md | 216 +++++++++++-------------- bazel/repos.bzl | 6 +- dev-tools | 2 +- include/stout/cache.h | 2 +- include/stout/flags/flags.h | 5 + 8 files changed, 116 insertions(+), 141 deletions(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 551b25e..d23f62f 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -21,7 +21,7 @@ on: # When manually running this workflow: # https://docs.github.com/en/actions/managing-workflow-runs/manually-running-a-workflow debug_enabled: - description: 'Enable tmate debugging' + description: "Enable tmate debugging" type: boolean default: false @@ -31,7 +31,9 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: ["macos-latest", "ubuntu-latest", "windows-latest"] + # 'windows-latest' (windows-2022) is broken due to a github runner update. + # See https://github.com/actions/runner-images/issues/7662 for more details. + os: ["macos-latest", "ubuntu-latest", "windows-2019"] defaults: run: shell: bash @@ -40,7 +42,7 @@ jobs: # Checkout the repository under $GITHUB_WORKSPACE. - uses: actions/checkout@v2 with: - submodules: 'recursive' + submodules: "recursive" - name: Build run: | diff --git a/.github/workflows/check_code_style.yml b/.github/workflows/check_code_style.yml index a2ae536..5dc3be1 100644 --- a/.github/workflows/check_code_style.yml +++ b/.github/workflows/check_code_style.yml @@ -27,7 +27,7 @@ jobs: # style checks. - uses: actions/checkout@v2 with: - submodules: 'recursive' + submodules: "recursive" # Call the composite action to check files # for correct code style. This action (action.yml) diff --git a/.mergequeue/config.yml b/.mergequeue/config.yml index 9e4cb62..d4d94e5 100644 --- a/.mergequeue/config.yml +++ b/.mergequeue/config.yml @@ -2,9 +2,9 @@ version: 1.0.0 merge_rules: labels: trigger: mergequeue-ready - skip_line: '' + skip_line: "" merge_failed: mergequeue-failed - skip_delete_branch: '' + skip_delete_branch: "" update_latest: true delete_branch: true use_rebase: true @@ -13,10 +13,10 @@ merge_rules: preconditions: number_of_approvals: 1 required_checks: - - check_code_style (ubuntu-latest) - - Build and Test (windows-latest) - - Build and Test (ubuntu-latest) - - Build and Test (macos-latest) + - check_code_style (ubuntu-latest) + - Build and Test (windows-latest) + - Build and Test (ubuntu-latest) + - Build and Test (macos-latest) use_github_mergeability: false conversation_resolution_required: true merge_mode: @@ -24,7 +24,7 @@ merge_rules: parallel_mode: null auto_update: enabled: false - label: '' + label: "" max_runs_for_update: 0 merge_commit: use_title_and_body: false @@ -32,5 +32,5 @@ merge_rules: name: squash override_labels: squash: mergequeue-squash - merge: '' + merge: "" rebase: mergequeue-rebase diff --git a/README.md b/README.md index ed2bb17..4b938a1 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Follows a "repos/deps" pattern (in order to help with recursive dependencies). T 1. Copy `bazel/repos.bzl` into your repository at `3rdparty/stout/repos.bzl` and add an empty `BUILD` (or `BUILD.bazel`) to `3rdparty/stout` as well. -2. Copy all of the directories from `3rdparty` that you ***don't*** already have in ***your*** repository's `3rdparty` directory. +2. Copy all of the directories from `3rdparty` that you **_don't_** already have in **_your_** repository's `3rdparty` directory. 3. Either ... add the following to your `WORKSPACE` (or `WORKSPACE.bazel`): @@ -18,7 +18,7 @@ load("@com_github_3rdparty_stout//bazel:deps.bzl", stout_deps="deps") stout_deps() ``` -Or ... to simplify others depending on ***your*** repository, add the following to your `repos.bzl`: +Or ... to simplify others depending on **_your_** repository, add the following to your `repos.bzl`: ```bazel load("//3rdparty/stout:repos.bzl", stout="repos") @@ -40,7 +40,7 @@ def deps(): 5. Repeat the steps starting at (1) at the desired version of this repository that you want to use. ------------------------- +--- Stout is a header-only C++ library. Simply add the `include` folder to your include path (i.e., `-I/path/to/stout/include`) during compilation (eventually we plan to support installation). @@ -50,26 +50,25 @@ Stout provides and heavily leverages some monadic structures including [Option]( Note that the library is designed to completely avoid exceptions. See [exceptions](#exceptions) for further discussion. -* Option, Some, and None -* Try, Result, and Error -* Nothing -* fs:: -* gzip:: -* JSON:: -* `jsonify` -* lambda:: -* net:: -* os:: -* path:: -* proc:: -* protobuf:: -* strings:: -* Command Line Flags -* Collections -* Miscellaneous -* Testing -* Philosophy - +- Option, Some, and None +- Try, Result, and Error +- Nothing +- fs:: +- gzip:: +- JSON:: +- `jsonify` +- lambda:: +- net:: +- os:: +- path:: +- proc:: +- protobuf:: +- strings:: +- Command Line Flags +- Collections +- Miscellaneous +- Testing +- Philosophy @@ -77,28 +76,28 @@ Note that the library is designed to completely avoid exceptions. See [exception The `Option` type provides a safe alternative to using `nullptr`. An `Option` can be constructed explicitly or implicitly: -~~~{.cpp} +```{.cpp} Option o(true); Option o = true; -~~~ +``` You can check if a value is present using `Option::isSome()` and `Option::isNone()` and retrieve the value using `Option::get()`: -~~~{.cpp} +```{.cpp} if (!o.isNone()) { ... o.get() ... } -~~~ +``` -Note that the current implementation *copies* the underlying values (see [Philosophy](#philosophy) for more discussion). Nothing prevents you from using pointers, however, *the pointer will not be deleted when the Option is destructed*: +Note that the current implementation _copies_ the underlying values (see [Philosophy](#philosophy) for more discussion). Nothing prevents you from using pointers, however, _the pointer will not be deleted when the Option is destructed_: -~~~{.cpp} +```{.cpp} Option o = new std::string("hello world"); -~~~ +``` The `None` type acts as "syntactic sugar" to make using [Option](#option) less verbose. For example: -~~~{.cpp} +```{.cpp} Option foo(const Option& o) { return None(); // Can use 'None' here. @@ -111,18 +110,17 @@ The `None` type acts as "syntactic sugar" to make using [Option](#option) less v ... Option o = None(); // Or here. -~~~ +``` Similar to `None`, the `Some` type can be used to construct an `Option` as well. In most circumstances `Some` is unnecessary due to the implicit `Option` constructor, however, it can still be useful to remove any ambiguities as well as when embedded within collections: -~~~{.cpp} +```{.cpp} Option> o = Some("42"); std::map> values; values["value1"] = None(); values["value2"] = Some("42"); -~~~ - +``` @@ -130,26 +128,26 @@ Similar to `None`, the `Some` type can be used to construct an `Option` as well. A `Try` provides a mechanism to return a value or an error without throwing exceptions. Like `Option`, you can explicitly or implicitly construct a `Try`: -~~~{.cpp} +```{.cpp} Try t(true); Try t = true; -~~~ +``` You can check if a value is present using `Try::isSome()` and `Try::isError()` and retrieve the value using `Try::get()` or the error via `Try::error`: -~~~{.cpp} +```{.cpp} if (!t.isError()) { ... t.get() ... } else { ... t.error() ... } -~~~ +``` A `Result` is a combination of a `Try` and an `Option`; you can think of a `Result` as semantically being equivalent to `Try>`. In addition to `isSome()` and `isError()` a `Result` includes `isNone()`. The `Error` type acts as "syntactic sugar" for implicitly constructing a `Try` or `Result`. For example: -~~~{.cpp} +```{.cpp} Try parse(const std::string& s) { if (s == "true") { @@ -162,17 +160,14 @@ The `Error` type acts as "syntactic sugar" for implicitly constructing a `Try` o } Try t = parse("false"); -~~~ +``` You can also use `None` and `Some` with `Result` just like with `Option`: -~~~{.cpp} +```{.cpp} Result r = None(); Result r = Some(true); -~~~ - - - +``` @@ -182,31 +177,27 @@ A lot of functions that return `void` can also "return" an error. Since we don't use exceptions (see [Exceptions](#exceptions)) we capture this pattern using `Try` (see [Try](#try)). - - ## `fs::` A collection of utilities for working with a filesystem. Currently we provide `fs::size`, `fs::usage`, and `fs::symlink`. - ## `gzip::` A collection of utilities for doing gzip compression and decompression using `gzip::compress` and `gzip::decompress`. -~~~{.cpp} +```{.cpp} gzip::decompress(gzip::compress("hello world")); -~~~ - +``` ## `JSON::` -*Requires Boost.* +_Requires Boost._ Provides structures and rendering of the JavaScript Object Notation (JSON) grammar using `boost::variant`. A JSON "value" (`JSON::Value`) is one of (i.e., a variant of) `JSON::String`, `JSON::Number`, `JSON::Object`, `JSON::Array`, `JSON::True`, `JSON::False`, and `JSON::Null`. @@ -214,7 +205,7 @@ We explicitly define 'true' (`JSON::True`), 'false' (`JSON::False`), and 'null' We could have avoided using `JSON::String` or `JSON::Number` and just used `std::string` and `double` respectively, but we choose to include them for completeness (although, this does slow compilation due to the extra template instantiations). That being said, you can use the native types in place of constructing the `JSON` wrapper. For example: -~~~{.cpp} +```{.cpp} // { // "foo": "value1", // "bar": "value2", @@ -226,11 +217,11 @@ We could have avoided using `JSON::String` or `JSON::Number` and just used `std: object.values["bar"] = "value2"; object.values["baz"] = JSON::Number(42.0); object.values["bam"] = 0.42; -~~~ +``` Of course, nesting of a `JSON::Value` is also permitted as per the JSON specification: -~~~{.cpp} +```{.cpp} // An array of objects: // [ // { "first": "Benjamin", "last": "Hindman" }, @@ -249,11 +240,10 @@ Of course, nesting of a `JSON::Value` is also permitted as per the JSON specific object2.values["last"] = "Hindman"; array.values.push_back(object2); -~~~ +``` You can "render" a JSON value using `std::ostream operator<<` (or by using `stringify` (see [here](#stringify)). - ## `jsonify` @@ -266,13 +256,13 @@ You can "render" a JSON value using `std::ostream operator<<` (or by using `stri `json` takes two parameters: a pointer to a writer type, and the type of the object. The following are the available writers and their member functions: -* `BooleanWriter` -- `set(value)` -* `NumberWriter` -- `set(value)` -* `StringWriter` -- `append(value)` -* `ArrayWriter` -- `element(value)` -* `ObjectWriter` -- `field(key, value)` +- `BooleanWriter` -- `set(value)` +- `NumberWriter` -- `set(value)` +- `StringWriter` -- `append(value)` +- `ArrayWriter` -- `element(value)` +- `ObjectWriter` -- `field(key, value)` -~~~{.cpp} +```{.cpp} namespace store { struct Customer @@ -294,11 +284,11 @@ void json(JSON::ObjectWriter* writer, const Customer& customer) store::Customer customer{"michael", "park", 25}; std::cout << jsonify(customer); // prints: {"first name":"michael","last name":"park","age":25} -~~~ +``` `jsonify(const F&)` overload takes a function object `F` that takes a pointer to writer. This is useful in cases where we don't want to define a public `json` function out-of-line. -~~~{.cpp} +```{.cpp} namespace store { struct Customer @@ -317,7 +307,7 @@ std::cout << jsonify([&customer](JSON::ObjectWriter* writer) { writer->field("age", customer.age); }); // prints: {"first name":"michael","last name":"park","age":25} -~~~ +``` @@ -325,14 +315,12 @@ std::cout << jsonify([&customer](JSON::ObjectWriter* writer) { To help deal with compatibility issues between C++98/03 and C++11 we wrap some of the TR1 types from `functional` in `lambda`. That way, when using `lambda::bind` you'll get `std::tr1::bind` where TR1 is available and `std::bind` when C++11 is available. - ## `net::` A collection of utilities for working with the networking subsystem. Currently we provide `net::download` and `net::getHostname`. - ## `os::` @@ -359,15 +347,14 @@ There is an `os::sysctl` abstraction for getting and setting kernel state on OS There are a handful of wrappers that make working with signals easier, including `os::signals::pending`, `os::signals::block`, and `os::signals::unblock`. In addition, there is a suppression abstraction that enables executing a block of code while blocking a signal. Consider writing to a pipe which may raise a SIGPIPE if the pipe has been closed. Using `suppress` you can do: -~~~{.cpp} +```{.cpp} suppress (SIGPIPE) { write(fd, data, size); if (errno == EPIPE) { // The pipe has been closed! } } -~~~ - +``` @@ -375,32 +362,29 @@ There are a handful of wrappers that make working with signals easier, including The `path` namespace provides the `path::join` function for joining together filesystem paths. Additionally to the `path` namespace there also exists the class `Path`. `Path` provides `basename()` and `dirname()` as thread safe replacements for standard `::basename()` and `::dirname()`. - ## `proc::` -*Requires Linux.* +_Requires Linux._ The `proc` namespace provides some abstractions for working with the Linux `proc` filesystem. The key abstractions are `proc::ProcessStatus` which models the data provided in `/proc/[pid]/stat` and `proc::SystemStatus` which models the data provided in `/proc/stat`. - ## `protobuf::` -*Requires protobuf.* +_Requires protobuf._ Helpers for reading and writing protobufs from files and file descriptors are provided via `protobuf::read` and `protobuf::write`. These assume a "recordio" format where the length of the serialized data is read/written first followed by the actual data. There is also a protobuf to JSON converter via `JSON::protobuf` that enables serializing a protobuf into JSON: -~~~{.cpp} +```{.cpp} google::protobuf::Message message; ... fill in message ... JSON::Object o = JSON::protobuf(message); -~~~ - +``` @@ -410,15 +394,13 @@ Utilities for inspecting and manipulating strings are available in the `strings` String formatting is provided via `strings::format`. The `strings::format` functions produces strings based on the `printf` family of functions. Except, unlike the `printf` family of functions, the `strings::format` routines attempt to "stringify" (see [here](#stringify)) any arguments that are not POD types (i.e., "plain old data": primitives, pointers, certain structs/classes and unions, etc). This enables passing structs/classes to `strings::format` provided there is a definition/specialization of `std::ostream operator<<` available for that type. Note that the `%s` format specifier is expected for each argument that gets passed. A specialization for `std::string` is also provided so that `std::string::c_str` is not necessary (but again, `%s` is expected as the format specifier). - - ## Command Line Flags One frustration with existing command line flags libraries was the burden they put on writing tests that attempted to have many different instantiations of the flags. For example, running two instances of the same component within a test where each instance was started with different command line flags. To solve this, we provide a command line flags abstraction called `Flags` (in the `flags` namespace) that you can extend to define your own flags: -~~~{.cpp} +```{.cpp} struct MyFlags : virtual flags::Flags // Use `virtual` for composition! { MyFlags() @@ -439,11 +421,11 @@ One frustration with existing command line flags libraries was the burden they p int foo; Option bar; }; -~~~ +``` You can then load the flags via `argc` and `argv` via: -~~~{.cpp} +```{.cpp} MyFlags flags; Try load = flags.load(None(), argc, argv); @@ -451,21 +433,19 @@ You can then load the flags via `argc` and `argv` via: ... flags.foo ... ... flags.bar.isSome() ... flags.bar.get() ... -~~~ +``` You can load flags from the environment in addition to `argc` and `argv` by specifying a prefix to use when looking for flags: -~~~{.cpp} +```{.cpp} MyFlags flags; Try load = flags.load("PREFIX_", argc, argv); -~~~ +``` Then both PREFIX_foo and PREFIX_bar will be loaded from the environment as well as possibly from on the command line. There are various ways to deal with unknown flags (i.e., `--baz` in our example above) and duplicates (i.e., `--foo` on the command line twice or once in the environment and once on the command line). See the header files for the various `load` overloads. - - ## Collections @@ -486,49 +466,47 @@ Finally, we provide some overloaded operators for doing set union (`|`), set int There are a handful of types and utilities that fall into the miscellaneous category. Note that like the collections _these are not namespaced_. - #### `Bytes` Used to represent some magnitude of bytes, i.e., kilobytes, megabytes, gigabytes, etc. The main way to construct a `Bytes` is to invoke `Bytes::parse` which expects a string made up of a number and a unit, i.e., `42B`, `42MB`, `42GB`, `42TB`. For each of the supported units there are associated types: `Megabytes`, `Gigabytes`, `Terabytes`. Each of these types inherit from `Bytes` and can be used anywhere a `Bytes` is expected, for example: -~~~{.cpp} +```{.cpp} Try bytes = Bytes::parse("32MB"); Bytes bytes = Megabytes(10); -~~~ +``` There are operators for comparing (equal to, greater than or less than, etc) and manipulating (addition, subtraction, etc) `Bytes` objects, as well as a `std::ostream operator<<` overload (thus making them stringifiable, see [here](#stringify)). For example: -~~~{.cpp} +```{.cpp} Try bytes = Bytes::parse("32MB"); Bytes tengb = Gigabytes(10); Bytes onegb = Megabytes(1024); stringify(tengb + onegb); // Yields "11GB". -~~~ +``` #### `Duration` Used to represent some duration of time. The main way to construct a `Duration` is to invoke `Duration::parse` which expects a string made up of a number and a unit, i.e., `42ns`, `42us`, `42ms`, `42secs`, `42mins`, `42hrs`, `42days`, `42weeks`. For each of the supported units there are associated types: `Nanoseconds`, `Microseconds`, `Milliseconds`, `Seconds`, `Minutes`, `Hours`, `Days`, `Weeks`. Each of these types inherit from `Duration` and can be used anywhere a `Duration` is expected, for example: -~~~{.cpp} +```{.cpp} Duration d = Seconds(5); -~~~ +``` There are operators for comparing (equal to, greater than or less than, etc) and manipulating (addition, subtraction, etc) `Duration` objects, as well as a `std::ostream operator<<` overload (thus making them stringifiable, see [here](#stringify)). Note that the `std::ostream operator<<` overload formats the output (including the unit) based on the magnitude, for example: -~~~{.cpp} +```{.cpp} stringify(Seconds(42)); // Yields "42secs". stringify(Seconds(120)); // Yields "2mins". -~~~ - +``` #### `Stopwatch` A data structure for recording elapsed time (according to the underlying operating system clock): -~~~{.cpp} +```{.cpp} Stopwatch stopwatch; stopwatch.start(); @@ -537,34 +515,31 @@ A data structure for recording elapsed time (according to the underlying operati stopwatch.stop(); assert(elapsed <= stopwatch.elapsed()); -~~~ - +``` #### `UUID` -*Requires Boost.* +_Requires Boost._ A wrapper around `boost::uuid` with a simpler interface. - #### `EXIT` A macro for exiting an application without generating a signal (such as from `assert`) or a stack trace (such as from Google logging's `CHECK` family of macros). This is useful if you want to exit the program with an error message: -~~~{.cpp} +```{.cpp} EXIT(42) << "You've provided us bad input ..."; -~~~ +``` Note that a newline is automatically appended and the processes exit status is set to 42. - #### `foreach` -*Requires Boost.* +_Requires Boost._ Macros for looping over collections: -~~~{.cpp} +```{.cpp} std::list l; foreach (std::string s, l) {} @@ -577,46 +552,41 @@ Macros for looping over collections: foreachkey (const std::string& s, m) {} foreachvalue (int i, m) {} -~~~ - +``` #### `numify` -*Requires Boost.* +_Requires Boost._ Wraps `boost::lexical_cast` for converting strings to numbers but returns a `Try` rather than throwing exceptions. - #### `stringify` Converts arbitrary types into strings by attempting to use an overloaded `std::ostream operator<<` (otherwise compilation fails). Note that `stringify` aborts the program if the stringification process fails. - #### `ThreadLocal` -*Requires pthreads.* +_Requires pthreads._ You can give every thread its own copy of some data using the `ThreadLocal` abstraction: -~~~{.cpp} +```{.cpp} ThreadLocal local; local = new std::string("hello"); local->append(" world"); std::string* s = local; assert(*s == "hello world"); -~~~ - +``` ## Testing - There are some macros provided for integration with gtest that make the tests less verbose while providing better messages when tests do fail: -~~~{.cpp} +```{.cpp} Try t = foo(); // Rather than: @@ -624,16 +594,15 @@ There are some macros provided for integration with gtest that make the tests le // Just do: ASSERT_SOME(t); -~~~ +``` There available macros include `ASSERT_SOME`, `EXPECT_SOME`, `ASSERT_NONE`, `EXPECT_NONE`, `ASSERT_ERROR`, `EXPECT_ERROR`, `ASSERT_SOME_EQ`, `EXPECT_SOME_EQ`, `ASSERT_SOME_TRUE`, `EXPECT_SOME_TRUE`, `ASSERT_SOME_FALSE`, `EXPECT_SOME_FALSE`. - ## Philosophy -*"Premature optimization is the root of all evil."* +_"Premature optimization is the root of all evil."_ You'll notice that the library is designed in a way that can lead to a lot of copying. This decision was deliberate. Capturing the semantics @@ -649,7 +618,6 @@ explicitly express ownership semantics. Until then, it's unlikely that the performance overhead incurred via any extra copying is your bottleneck, and if it is we'd love to hear from you! - ### Exceptions diff --git a/bazel/repos.bzl b/bazel/repos.bzl index 1ec3ddd..833fc49 100644 --- a/bazel/repos.bzl +++ b/bazel/repos.bzl @@ -60,9 +60,9 @@ def repos(external = True, repo_mapping = {}): maybe( http_archive, name = "gtest", - sha256 = "9dc9157a9a1551ec7a7e43daea9a694a0bb5fb8bec81235d8a1e6ef64c716dcb", - strip_prefix = "googletest-release-1.10.0", - url = "https://github.com/google/googletest/archive/release-1.10.0.tar.gz", + sha256 = "b4870bf121ff7795ba20d20bcdd8627b8e088f2d1dab299a031c1034eddc93d5", + strip_prefix = "googletest-release-1.11.0", + url = "https://github.com/google/googletest/archive/release-1.11.0.tar.gz", ) maybe( diff --git a/dev-tools b/dev-tools index 338fe3f..e8b27c4 160000 --- a/dev-tools +++ b/dev-tools @@ -1 +1 @@ -Subproject commit 338fe3f4d2b36789dac74f4c66e8b9f08c621217 +Subproject commit e8b27c4c0fbe881d305c33864fff51b8b5ec7bf0 diff --git a/include/stout/cache.h b/include/stout/cache.h index 1a6521b..24d39ef 100644 --- a/include/stout/cache.h +++ b/include/stout/cache.h @@ -96,7 +96,7 @@ class Cache { Cache& operator=(const Cache&); // Give the operator access to our internals. - friend std::ostream& operator<<<>( + friend std::ostream& operator<< <>( std::ostream& stream, const Cache& c); diff --git a/include/stout/flags/flags.h b/include/stout/flags/flags.h index 64f203d..6ebab99 100644 --- a/include/stout/flags/flags.h +++ b/include/stout/flags/flags.h @@ -1,5 +1,10 @@ #pragma once +// To avoid errors related to defined severity level 'Error' from 'windows.h'. +#ifdef _WIN32 +#define GLOG_NO_ABBREVIATED_SEVERITIES +#endif + #include #include #include