diff --git a/.github/workflows/brew.yaml b/.github/workflows/brew.yaml new file mode 100644 index 00000000..cdafe041 --- /dev/null +++ b/.github/workflows/brew.yaml @@ -0,0 +1,98 @@ +# SPDX-FileCopyrightText: 2024 Duncan Greatwood +# +# SPDX-License-Identifier: Apache-2.0 + +# See: +# https://docs.github.com/en/actions/writing-workflows/choosing-where-your-workflow-runs/choosing-the-runner-for-a-job +# https://github.com/actions/runner-images/blob/main/images/macos/macos-14-Readme.md + +name: Homebrew Formula + +on: + push: + branches: + - master + pull_request: + branches: + - master + +defaults: + run: + shell: sh + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + macos: + name: Homebrew Formula + + strategy: + fail-fast: false + matrix: + # Note: If you change the list of OS by adding a newer one, + # PLEASE UPDATE THE AUDIT PHASE to use the most recent macOS + # (See "runformula.sh --auditonly ..." later in this file) + os: [ 'macos-13', 'macos-14', 'macos-15' ] + compiler: [ 'clang' ] + sanitizer: [ 'none' ] + tls: [ 'true' ] + def_debug: [ 'false' ] + + runs-on: ${{ matrix.os }} + + steps: + # Avoid doing "brew update" - the brew formulas that are + # preinstalled on the github runner image are likely + # consistent with the pre-installed software on the image. If + # we do "brew upate", and then install something new with + # brew, and the "something new" depends on pre-installed + # software on the image, and there are new versions of the + # pre-installed software revealed by doing "brew update", then + # when we install the "something new" brew may try and also + # install a new version of the pre-installed software on which + # the "something new" depends, but that attempt to install a + # new version of the pre-installed software can fail as a + # result of being blocked by the software that is already + # installed. + + - uses: actions/checkout@v4 + with: + submodules: true + + - name: Test Homebrew + run: | + if [ ${{ matrix.compiler }} = gcc ]; then + gcc_lns=`brew list gcc | grep "bin/g++-[0-9][0-9]" | sort -r` + gcc_fstln=`echo "$gcc_lns" | head -1` + CXX=`basename $gcc_fstln` + + gcc_lns=`brew list gcc | grep "bin/gcc-[0-9][0-9]" | sort -r` + gcc_fstln=`echo "$gcc_lns" | head -1` + CC=`basename $gcc_fstln` + else + CXX=clang++ + CC=CXX + fi + export CXX + export CC + echo "CXX is $CXX; CC is $CC" + + # This is a fine-grained personal access token restricted to a + # single repository and with all permissions left on "No + # Access". We set this token to avoid being blocked by the + # GitHub access rate limit for unauthenticated access + # (i.e. the rate limit for access with no token). + export HOMEBREW_GITHUB_API_TOKEN=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 + + homebrew/runformula.sh --skipaudit --force --HEAD + # --skipaudit Say "yes" to doing the brew audit + # --force Run even if pistache brew formula unchanged + # --HEAD Test with head of pistacheio/pistache master + # branch + + # if brew list pistache &>/dev/null; then brew remove pistache; fi + ## Use release (not HEAD), do audit (-y), and force even if pistache + ## formula unchanged + # homebrew/runformula.sh -y --force diff --git a/.gitignore b/.gitignore index 5e2fc950..94968408 100644 --- a/.gitignore +++ b/.gitignore @@ -73,5 +73,7 @@ repeatx86_*.sh !/subprojects/cpp-httplib !/subprojects/hinnant-date *.*~ +*.bak +\#*\# /How To Run All Tests (README).txt diff --git a/Building on macOS.txt b/Building on macOS.txt index 7c70da81..46094433 100644 --- a/Building on macOS.txt +++ b/Building on macOS.txt @@ -2,8 +2,8 @@ # # SPDX-License-Identifier: Apache-2.0 -Making Pistache on macOS -======================== +Pistache on macOS +================= Apple's clang compiler must be installed. It comes as part of Xcode. If not already installed, at terminal command line do: @@ -13,7 +13,22 @@ Homebrew (also known as "brew") is required. If not already installed, follow the Homebrew instructions to install: In your browser: https://brew.sh/ -Then, install the necessary brew packages via terminal command line: +The simplest way to install Pistache on macOS is to use brew. For the +latest version of Pistache, do: + brew install --HEAD pistache +For the most recent officially designated release, do: + brew install pistache +We normally suggest using the most recent ("HEAD") Pistache version. +In case of difficultly, please see later Troubleshooting note. + + +Building Pistache from Source on macOS +====================================== + +If you prefer not to install with brew, Pistache can be built from +source. + +Install the necessary brew packages via terminal command line: brew install meson brew install doxygen brew install googletest (skip this if installing with gcc - see later) @@ -33,24 +48,70 @@ To test: To install: bldscripts/mesinstall.sh -The meaning of each convenience script is as follows: - mesbuild.sh - build release version using meson - mestest.sh - test release version using meson - mesinstall.sh - install release version using meson +See later section for more details on teh conveinence scripts. + + +Troubleshooting brew install on Intel Macs +------------------------------------------ + +On some Intel-based MACs, when installing Pistache with brew you may +see errors like: + ==> meson setup build ... + Traceback (most recent call last): + File "/usr/local/opt/meson/bin/meson", line 5, in + from mesonbuild.mesonmain import main + ModuleNotFoundError: No module named 'mesonbuild' + ... + Error: Testing requires the latest version of pistache + +This a problem installing Python3, a dependency of meson. We found it +could be fixed by: + sudo mkdir /usr/local/Frameworks + sudo chown /usr/local/Frameworks + (Substituting your own macOS username for ) +NB: This applies solely to Intel-based Macs. Homebrew uses a different +location for files on Apple-silicon("M")/Arm-based MACs. + + +Building with GCC +----------------- + +By default, Pistache on macOS builds with clang, which is the default +Apple compiler. If you prefer to use gcc, you can utilize the +gccmacsetup.sh script provided by Pistache before doing the build, +like this: + source bldscripts/gccmacsetup.sh + bldscripts/mesbuild.sh + bldscripts/mestest.sh + bldscripts/mesinstall.sh + + +Convenience Scripts +------------------- + +The following scripts cna be used in any environment except +Windows. The meaning of each convenience script is as follows: + mesbuild.sh - build release version + mestest.sh - test release version + mesinstall.sh - install release version - mesbuilddebug.sh - build debug version using meson + mesbuilddebug.sh - build debug version mestestdebug.sh - test debug version with meson - mesinstalldebug.sh - install debug version using meson + mesinstalldebug.sh - install debug version - mesbuildflibev.sh - build version using meson forcing libevnet use - mestestflibev.sh - test version using meson with libevnet use - mesinstallflibev.sh - install version using meson with libevnet use +Linux-only scripts. Note - all environments except Linux use libevent +by default. libevent is optional on Linux: + mesbuildflibev.sh - build version forcing libevent use + mestestflibev.sh - test version with libevent use + mesinstallflibev.sh - install version with libevent use - mesbuildflibevdebug.sh - build debug ver using meson forcing libevnet use - mestestflibevdebug.sh - test debug ver using meson with libevnet use - mesinstallflibevdebug.sh - install debug ver using meson with libevnet use + mesbuildflibevdebug.sh - build debug version forcing libevent use + mestestflibevdebug.sh - test debug version with libevent use + mesinstallflibevdebug.sh - install debug version with libevent use +Also: clean.sh - remove build directories + gccmacsetup.sh - configure for GCC build on macOS Building with GCC diff --git a/README.md b/README.md index 91f0cddf..107e1c6b 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,10 @@ If you have no need to modify the Pistache source, you are strongly recommended Pistache is available in the official repositories since Debian 12 and Ubuntu 23.10, under the package name `libpistache-dev`. +### macOS + +Pistache can be installed using the Homebrew package manager. See *Building on macOS.txt* for specifics. + #### Supported Architectures Currently Pistache is built and tested on a number of [architectures](https://wiki.debian.org/SupportedArchitectures). Some of these are suitable for desktop or server use and others for embedded environments. As of this writing we do not currently have any MIPS related packages that have been either built or tested. @@ -93,7 +97,7 @@ Currently Pistache is built and tested on a number of [architectures](https://wi - riscv64 - s390x -#### Ubuntu PPA (Unstable) +### Ubuntu PPA (Unstable) The project builds [daily unstable snapshots](https://launchpad.net/~pistache+team/+archive/ubuntu/unstable) in a separate unstable PPA. To use it, run the following: @@ -103,9 +107,9 @@ $ sudo apt update $ sudo apt install libpistache-dev ``` -#### Ubuntu PPA (Stable) +### Ubuntu PPA (Stable) -Currently there are no stable release of Pistache published into the [stable](https://launchpad.net/~pistache+team/+archive/ubuntu/stable) PPA. However, when that time comes, run the following to install a stable package: +From time to time, the project transfers release packages into the [stable](https://launchpad.net/~pistache+team/+archive/ubuntu/stable) PPA. Run the following to install a stable package: ```sh $ sudo add-apt-repository ppa:pistache+team/stable @@ -205,8 +209,7 @@ To download the latest available release, clone the repository over GitHub. $ git clone https://github.com/pistacheio/pistache.git ``` -To build for macOS, you can follow the instructions in: - *Building on macOS.txt* +To build on macOS, Windows, or BSD, see the respective files *Building on macOS.txt*, *Building on Windows.txt* or *Building on BSD.txt*. Continuing the Linux instructions: diff --git a/homebrew/pistache.rb b/homebrew/pistache.rb new file mode 100644 index 00000000..4ecbca21 --- /dev/null +++ b/homebrew/pistache.rb @@ -0,0 +1,166 @@ +# +# SPDX-FileCopyrightText: 2024 Duncan Greatwood +# +# SPDX-License-Identifier: Apache-2.0 +# + +class Pistache < Formula + desc "Modern, fast, elegant HTTP + REST C++17 framework with pleasant API" + homepage "https://github.com/pistacheio/pistache" + url "https://github.com/pistacheio/pistache/archive/refs/tags/v0.4.26.tar.gz" + sha256 "29af6562547497acf6f49170661786fe8cf1ed3712ad80e69c53da4661c59544" + license "Apache-2.0" + head "https://github.com/pistacheio/pistache.git", branch: "master" + + depends_on "cmake" => :build # for howard-hinnant-date + depends_on "cpp-httplib" => :build + depends_on "googletest" => :build + depends_on "howard-hinnant-date" => :build + depends_on "meson" => :build + depends_on "ninja" => :build + depends_on "pkgconf" => :build + depends_on "rapidjson" => :build + + depends_on "brotli" + depends_on "libevent" + depends_on "openssl@3" + depends_on "zstd" + + uses_from_macos "curl" => :build + uses_from_macos "zlib" + + def install + system "meson", "setup", "build", + "-DPISTACHE_USE_SSL=true", + "-DPISTACHE_BUILD_EXAMPLES=false", + "-DPISTACHE_BUILD_TESTS=false", + "-DPISTACHE_BUILD_DOCS=false", + "-DPISTACHE_USE_CONTENT_ENCODING_DEFLATE=true", + "-DPISTACHE_USE_CONTENT_ENCODING_BROTLI=true", + "-DPISTACHE_USE_CONTENT_ENCODING_ZSTD=true", + *std_meson_args + + system "meson", "compile", "-C", "build", "--verbose" + system "meson", "install", "-C", "build" + end + + test do + (testpath/"test.cpp").write <<~CPP + // Testing multiple clients making requests of a multithreaded server + + #include + #include + #include + #include + + #include + #include + #include + + using namespace Pistache; + using namespace std::chrono; + + struct HelloHandler : public Http::Handler + { + HTTP_PROTOTYPE(HelloHandler) + + void onRequest(const Http::Request& /*request*/, + Http::ResponseWriter writer) override + { + writer.send(Http::Code::Ok, "Hello, World!"); + } + }; + + static int clientLogicFunc(size_t response_size, + const std::string& server_page, + int wait_seconds) + { + Http::Experimental::Client client; + client.init(); + + std::vector> responses; + auto rb = client.get(server_page); + + int resolver_counter = 0; + int reject_counter = 0; + for (size_t i = 0; i < response_size; ++i) + { + auto response = rb.send(); + + response.then( + [&resolver_counter, pos = i](Http::Response resp) { + if (resp.code() == Http::Code::Ok) + { + ++resolver_counter; + } + }, + [&reject_counter, pos = i](std::exception_ptr exc) { + std::cout << "Request rejected" << std::endl; + PrintException excPrinter; + + excPrinter(exc); + ++reject_counter; + }); + responses.push_back(std::move(response)); + } + + { // encapsulate + auto sync = Async::whenAll(responses.begin(), responses.end()); + Async::Barrier> barrier(sync); + barrier.wait_for(std::chrono::seconds(wait_seconds)); + } + + client.shutdown(); + return resolver_counter; + } + + int main() + { + const Pistache::Address address("localhost", Pistache::Port(0)); + + Http::Endpoint server(address); + auto flags = Tcp::Options::ReuseAddr; + auto server_opts = Http::Endpoint::options().flags(flags).threads(3); + server.init(server_opts); + server.setHandler(Http::make_handler()); + server.serveThreaded(); + + const std::string server_address = + "localhost:" + server.getPort().toString(); + + const int SIX_SECONDS_TIMOUT = 6; + const int FIRST_CLIENT_REQUEST_SIZE = 4; + std::future result1(std::async(clientLogicFunc, + FIRST_CLIENT_REQUEST_SIZE, server_address, + SIX_SECONDS_TIMOUT)); + const int SECOND_CLIENT_REQUEST_SIZE = 5; + std::future result2( + std::async(clientLogicFunc, SECOND_CLIENT_REQUEST_SIZE, + server_address, SIX_SECONDS_TIMOUT)); + + int res1 = result1.get(); + int res2 = result2.get(); + + server.shutdown(); + + if (res1 != FIRST_CLIENT_REQUEST_SIZE) + { + std::cerr << "Response count res1 is " << res1 << ", expected " + << FIRST_CLIENT_REQUEST_SIZE << std::endl; + return 1; + } + + if (res2 != SECOND_CLIENT_REQUEST_SIZE) + { + std::cerr << "Response count res2 is " << res2 << ", expected " + << SECOND_CLIENT_REQUEST_SIZE << std::endl; + return 2; + } + + return 0; + } + CPP + system ENV.cxx, "-std=c++17", "test.cpp", "-L#{lib}", "-lpistache", "-o", "test" + system "./test" + end +end diff --git a/homebrew/runformula.sh b/homebrew/runformula.sh new file mode 100755 index 00000000..b18d8200 --- /dev/null +++ b/homebrew/runformula.sh @@ -0,0 +1,200 @@ +#!/bin/bash + +# +# SPDX-FileCopyrightText: 2024 Duncan Greatwood +# +# SPDX-License-Identifier: Apache-2.0 +# + +# This script test the homebrew formula for Pistache. It will copy the +# local brew formula, pistache.rb, to the brew repo directory and +# install it in macOS + +# Documentation: https://docs.brew.sh/Adding-Software-to-Homebrew +# https://docs.brew.sh/Formula-Cookbook +# https://rubydoc.brew.sh/Formula +# Also: +# https://docs.github.com/en/repositories/releasing-projects-on-github/ +# managing-releases-in-a-repository +# +# And to make a sha-256 from a release, Go to Pistache home page -> +# Releases and download the tarball for the release. Then: +# shasum -a 256 +# Note: brew formula audit prefers tarball to zip file. + +# For option parsing, see: +# https://stackoverflow.com/questions/402377/using-getopts-to-process-long-and-short-command-line-options +# (The answer that begins "The Bash builtin getopts function can be...") +# And: +# https://linuxsimply.com/bash-scripting-tutorial/functions/script-argument/bash-getopts/ +# MAKE SURE below that you set optspec to correctly reflect short options + +MY_SCRIPT_DIR="$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +do_error=false +do_yes=false +do_usage=false +use_head=false +skip_audit=false +audit_only=false +do_force=false +optspec=":hfy-:" +while getopts "$optspec" optchar; do + case "${optchar}" in + -) + case "${OPTARG}" in + HEAD) + use_head=true + ;; + help) + do_usage=true + ;; + skipaudit) + skip_audit=true + ;; + auditonly) + audit_only=true + ;; + force) + do_force=true + ;; + *) + echo "Error: Unknown option --${OPTARG}" >&2 + do_error=true + break + ;; + esac;; + h) + do_usage=true + ;; + y) + do_yes=true + ;; + f) + do_force=true + ;; + *) + echo "Error: Non-option argument: '-${OPTARG}'" >&2 + do_error=true + break + ;; + esac +done + +if [ "$do_error" = true ]; then do_usage=true; fi + +if [ "$do_usage" = true ]; then + echo "Usage: $(basename "$0") [-h] [--help] [-y] [--HEAD]" + echo " -h, --help Prints usage message, then exits" + echo " --HEAD Tests with head of pistache master" + echo " (otherwise, tests with pistache release)" + echo " -f, --force Test even if forumla already up-to-date" + echo " -y Answer yes to questions (i.e. do audit)" + echo " --skipaudit Skips brew audit; overrides -y for audit question" + echo " --auditonly Skips brew install, does audit" + if [ "$do_yes" = true ] || [ "$do_head" = true ]; then + echo "Error: Usage requested with other options" + do_error=true + fi + if [ "$do_error" = true ]; then + exit 1 + fi + exit 0 +fi + +if ! type "brew" > /dev/null; then + echo "brew not found; brew is required to proceed" + exit 1 +fi + +export HOMEBREW_NO_INSTALL_FROM_API=1 + +if hbcore_repo=$(brew --repository homebrew/core); then + if [ ! -d "$hbcore_repo" ]; then + echo "Cloning homebrew/core to $hbcore_repo" + brew tap --force homebrew/core + fi +else + echo "Cloning homebrew/core" + brew tap --force homebrew/core + if ! hbcore_repo=$(brew --repository homebrew/core); then + echo "Failed to find homebrew/core repo" + exit 1 + fi +fi + +pist_form_dir="$hbcore_repo/Formula/p" +if [ ! -d "$pist_form_dir" ]; then + echo "Failed to find homebrew/core formula dir $pist_form_dir" + exit 1 +fi +pist_form_file="$pist_form_dir/pistache.rb" + +if [ -f "$pist_form_file" ]; then + sed '1,6d' "$MY_SCRIPT_DIR/pistache.rb" >"$MY_SCRIPT_DIR/tmp_pistache.rb" + if cmp --silent -- "$MY_SCRIPT_DIR/tmp_pistache.rb" "$pist_form_file"; then + pistache_rb_unchanged=true + else + pistache_rb_unchanged=false + fi + rm "$MY_SCRIPT_DIR/tmp_pistache.rb" + if [ "$pistache_rb_unchanged" == true ]; then + if [ "$do_force" != true ]; then + echo "$pist_form_file is already up to date, exiting" + if [ "$use_head" != true ]; then + if brew list pistache &>/dev/null; then + echo "You can try: brew remove pistache; brew install --build-from-source pistache" + else + echo "You can try: brew install --build-from-source pistache" + fi + else + if brew list pistache &>/dev/null; then + echo "You can try: brew remove pistache; brew install --HEAD pistache" + else + echo "You can try: brew install --HEAD pistache" + fi + fi + exit 0 + fi + else + pist_form_bak="$MY_SCRIPT_DIR/pistache.rb.bak" + echo "Overwriting $pist_form_file" + echo "Saving prior to $pist_form_bak" + cp "$pist_form_file" "$pist_form_bak" + fi +else + echo "Copying $MY_SCRIPT_DIR/pistache.rb to $pist_form_file" +fi + +if [ "$audit_only" != true ]; then + # Drop copyright + license SPDX message when adding forumla to homebrew/core + sed '1,6d' "$MY_SCRIPT_DIR/pistache.rb" >"$pist_form_file" + + if brew list pistache &>/dev/null; then brew remove pistache; fi + if [ "$use_head" = true ]; then + brew install --HEAD pistache + else + brew install --build-from-source pistache + fi + brew test --verbose pistache +fi + +if [ "$skip_audit" != true ]; then + do_audit=$do_yes + if [ "$do_audit" != true ]; then + do_audit=$audit_only + fi + if [ "$do_audit" != true ]; then + read -e -p 'brew audit? [y/N]> ' + if [[ "$REPLY" == [Yy]* ]]; then + do_audit=true + fi + fi + + if [ "$do_audit" = true ]; then + echo "Auditing brew formula..." + brew audit --strict --new --online pistache + else + echo "Skipping audit" + fi +fi diff --git a/tests/meson.build b/tests/meson.build index c23df5f4..303ea7fd 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -3,6 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 curl_dep = dependency('libcurl') + gtest_main_dep = dependency('gtest', main: true, fallback: ['gtest', 'gtest_main_dep']) gmock_dep = dependency('gmock', version: '>=1.11.0', fallback: ['gtest', 'gmock_dep']) @@ -18,7 +19,7 @@ gmock_dep = dependency('gmock', version: '>=1.11.0', fallback: ['gtest', 'gmock_ # subproject without using meson dependency's "fallback:" option. # Note: Have to do the same thing on Linux too, so "linuxbrew" # (homebrew on Linux) works. -if (host_machine.system() == 'darwin' or host_machine.system() == 'linux') and get_option('wrap_mode') == 'nofallback' +if (host_machine.system() == 'darwin' or host_machine.system() == 'linux') and get_option('wrap_mode') == 'nofallback' cpp_httplib_dep = dependency('cpp-httplib', required: false) if not cpp_httplib_dep.found() cpp_httplib_dep = subproject('cpp-httplib').get_variable('cpp_httplib_dep') diff --git a/tests/rest_server_test.cc b/tests/rest_server_test.cc index 76481ae5..73308916 100644 --- a/tests/rest_server_test.cc +++ b/tests/rest_server_test.cc @@ -11,7 +11,15 @@ #include #include +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +#endif #include +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + #include #include @@ -121,7 +129,7 @@ TEST(rest_server_test, basic_test) else { const unsigned int my_max_hostname_len = 1024; - + // NetBSD showed this case, when hostname was not "localhost" char name[my_max_hostname_len + 6]; name[0] = 0; @@ -131,7 +139,7 @@ TEST(rest_server_test, basic_test) ASSERT_EQ(res->body, &(name[0])); } - + stats.shutdown(); } diff --git a/tests/rest_swagger_server_test.cc b/tests/rest_swagger_server_test.cc index 2400e4be..e73c353a 100644 --- a/tests/rest_swagger_server_test.cc +++ b/tests/rest_swagger_server_test.cc @@ -13,7 +13,14 @@ #include #include +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +#endif #include +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif #if __has_include() #include diff --git a/tests/router_test.cc b/tests/router_test.cc index 42e31740..787b4cfe 100644 --- a/tests/router_test.cc +++ b/tests/router_test.cc @@ -19,7 +19,14 @@ #include #include +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +#endif #include +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif using namespace Pistache; using namespace Pistache::Rest; diff --git a/version.txt b/version.txt index 66c6aac8..fed3af26 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.4.26.20241222 +0.4.27.20241227