diff --git a/.github/workflows/.yamllint b/.github/workflows/.yamllint new file mode 100644 index 0000000..ed47667 --- /dev/null +++ b/.github/workflows/.yamllint @@ -0,0 +1,30 @@ +--- + +extends: default + +rules: + braces: + level: warning + max-spaces-inside: 1 + brackets: + level: warning + max-spaces-inside: 1 + colons: + level: warning + commas: + level: warning + comments: disable + comments-indentation: disable + document-start: disable + empty-lines: + level: warning + hyphens: + level: warning + indentation: + level: warning + indent-sequences: consistent + line-length: + max: 160 + level: warning + allow-non-breakable-inline-mappings: true + truthy: disable diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..2d78174 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,110 @@ +--- +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL Advanced" + +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + schedule: + - cron: "33 19 * * 4" + +# Declare default permissions as read-only +permissions: read-all + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners (GitHub.com only) + # Consider using larger runners or machines with greater resources for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + permissions: + # required for all workflows + security-events: write + + # required to fetch internal or private CodeQL packs + packages: read + + # only required for workflows in private repositories + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + include: + - language: actions + build-mode: none + # CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' + # Use `c-cpp` to analyze code written in C, C++ or both + # Use 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, + # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. + # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how + # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0 + with: + egress-policy: audit + + - name: Checkout repository + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + persist-credentials: false + + # Add any setup steps before running the `github/codeql-action/init` action. + # This includes steps like installing compilers or runtimes (`actions/setup-node` + # or others). This is typically only required for manual builds. + # Ensure the GitHub Actions hash is pinned if this setup step is uncommented. + # - name: Setup runtime (example) + # uses: actions/setup-example@v1 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + # If the analyze step fails for one of the languages you are analyzing with + # "We were unable to automatically build your code", modify the matrix above + # to set the build mode to "manual" for that language. Then modify this step + # to build your code. + # â„šī¸ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + - if: matrix.build-mode == 'manual' + shell: bash + run: | + echo 'If you are using a "manual" build mode for one or more of the' \ + 'languages you are analyzing, replace this with the commands to build' \ + 'your code, for example:' + echo ' make bootstrap' + echo ' make release' + exit 1 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/doxygen-gh-pages.yml b/.github/workflows/doxygen-gh-pages.yml new file mode 100644 index 0000000..d8cb6fe --- /dev/null +++ b/.github/workflows/doxygen-gh-pages.yml @@ -0,0 +1,28 @@ +name: Doxygen GitHub Pages Deploy Action + +on: + push: + branches: + - main + - doxify + +permissions: + contents: read + +jobs: + deploy: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0 + with: + egress-policy: audit + + - uses: DenverCoder1/doxygen-github-pages-action@a30f9538f8ef1305aeceb563018f452c7a62d200 # v2.0.0 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + branch: gh-pages + folder: docs/html + config_file: docs/Doxyfile diff --git a/.github/workflows/ossf-scorecard-analysis.yml b/.github/workflows/ossf-scorecard-analysis.yml new file mode 100644 index 0000000..41c9b64 --- /dev/null +++ b/.github/workflows/ossf-scorecard-analysis.yml @@ -0,0 +1,61 @@ +name: Scorecard analysis workflow +on: + push: + # Only the default branch is supported. + branches: + - main + schedule: + # Weekly on Saturdays. + - cron: '30 1 * * 6' + +permissions: read-all + +jobs: + analysis: + name: Scorecard analysis + runs-on: ubuntu-latest + permissions: + # Needed for Code scanning upload + security-events: write + # Needed for GitHub OIDC token if publish_results is true + id-token: write + + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0 + with: + egress-policy: audit + + - name: "Checkout code" + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + persist-credentials: false + + - name: "Run analysis" + uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 + with: + results_file: results.sarif + results_format: sarif + # Scorecard team runs a weekly scan of public GitHub repos, + # see https://github.com/ossf/scorecard#public-data. + # Setting `publish_results: true` helps us scale by leveraging your workflow to + # extract the results instead of relying on our own infrastructure to run scans. + # And it's free for you! + publish_results: true + + # Upload the results as artifacts (optional). Commenting out will disable + # uploads of run results in SARIF format to the repository Actions tab. + # https://docs.github.com/en/actions/advanced-guides/storing-workflow-data-as-artifacts + - name: "Upload artifact" + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + # Upload the results to GitHub's code scanning dashboard (optional). + # Commenting out will disable upload of results to your repo's Code Scanning dashboard + - name: "Upload to code-scanning" + uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9 + with: + sarif_file: results.sarif diff --git a/.gitignore b/.gitignore index d293e3b..eea32dc 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,8 @@ # ignore vscode settings .vscode +/.build/ +/.install/ +/.update-submodules +/uv.lock +.build diff --git a/CMakeLists.txt b/CMakeLists.txt index 4b414a3..19df0d4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,3 +1,4 @@ +# CMakeLists.txt -*-cmake-*- # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception cmake_minimum_required(VERSION 3.28...4.2) @@ -41,6 +42,10 @@ add_subdirectory(include/beman/expected) beman_install_library(beman.expected) +if(BEMAN_EXPECTED_BUILD_TESTS) + find_package(GTest CONFIG REQUIRED) +endif() + if(BEMAN_EXPECTED_BUILD_TESTS) enable_testing() add_subdirectory(tests/beman/expected) diff --git a/Makefile b/Makefile new file mode 100755 index 0000000..90a682b --- /dev/null +++ b/Makefile @@ -0,0 +1,270 @@ +#! /usr/bin/make -f +# Makefile -*-makefile-*- +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +NO_COLOR=1 + +INSTALL_PREFIX?=.install/ +BUILD_DIR?=.build +DEST?=$(INSTALL_PREFIX) +CMAKE_FLAGS?= + + +PYEXECPATH ?= $(shell which python3.13 || which python3.12 || which python3.11 || which python3.10 || which python3.9 || which python3.8 || which python3) +PYTHON ?= $(notdir $(PYEXECPATH)) +VENV := .venv +UV := $(shell command -v uv 2> /dev/null) +ACTIVATE := $(UV) run +PYEXEC := $(UV) run python +MARKER=.initialized.venv.stamp + +PRE_COMMIT := $(UV) run pre-commit + +TARGETS := test clean all ctest + +export + +.update-submodules: + git submodule update --init --recursive + touch .update-submodules + +.gitmodules: .update-submodules + +CONFIG?=Asan + +export + +ifeq ($(strip $(TOOLCHAIN)),) + _build_name?=build-system/ + _build_dir?=.build/ + _local_toolchain?=$(CURDIR)/cmake/toolchain.cmake +else + _build_name?=build-$(TOOLCHAIN) + _build_dir?=.build/ + _local_toolchain?=$(CURDIR)/cmake/$(TOOLCHAIN)-toolchain.cmake +endif + +_configuration_types?="RelWithDebInfo;Debug;Tsan;Asan;Gcov" + +_build_path?=$(_build_dir)/$(_build_name) +_build_path:=$(subst //,/,$(_build_path)) +_build_path:=$(patsubst %/,%,$(_build_path)) + +VCPKG ?= $(shell command -v vcpkg 2> /dev/null) + +ifeq ($(VCPKG),) + _cmake_top_level?="./cmake/use-fetch-content.cmake" + _toolchain:=$(_local_toolchain) + _args=-DBEMANINFRA_googletest_REPO=file:///home/sdowney/bld/googletest/googletest.git +else + _vcpkg_toolchain:=$(VCPKG_ROOT)/scripts/buildsystems/vcpkg.cmake + _cmake_top_level?=$(_vcpkg_toolchain) + export PROJECT_VCPKG_TOOLCHAIN=$(_local_toolchain) + _toolchain:=$(_local_toolchain) + _args=-DVCPKG_OVERLAY_TRIPLETS=$(CURDIR)/cmake -DVCPKG_TARGET_TRIPLET=x64-linux-custom + # for debugging add -DVCPKG_INSTALL_OPTIONS="--debug" +endif + +CMAKE ?= $(UV) run cmake +CTEST ?= $(UV) run ctest + +define run_cmake = + $(CMAKE) \ + -G "Ninja Multi-Config" \ + -DCMAKE_CONFIGURATION_TYPES=$(_configuration_types) \ + -DCMAKE_INSTALL_PREFIX=$(abspath $(INSTALL_PREFIX)) \ + -DCMAKE_EXPORT_COMPILE_COMMANDS=1 \ + -DCMAKE_PREFIX_PATH=$(CURDIR)/infra/cmake \ + -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=$(_cmake_top_level) \ + -DCMAKE_C_COMPILER_LAUNCHER=ccache \ + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ + -DCMAKE_TOOLCHAIN_FILE=$(_toolchain) \ + $(_args) \ + $(_cmake_args) \ + $(CURDIR) +endef + +default: test +.PHONY: default + +$(_build_path): + mkdir -p $(_build_path) + +$(_build_path)/CMakeCache.txt: | $(_build_path) .gitmodules $(VENV) + cd $(_build_path) && $(run_cmake) + +$(_build_path)/compile_commands.json : $(_build_path)/CMakeCache.txt + +.PHONY: compile_commands.json +compile_commands.json: $(_build_path)/compile_commands.json +compile_commands.json: ## symlink the current compile commands db + if [ "$(shell readlink compile_commands.json)" != "$(_build_path)/compile_commands.json" ] ; then \ + ln -sf $(_build_path)/compile_commands.json ; \ + fi + +TARGET:=all +.PHONY: TARGET + +.PHONY: compile +compile: $(_build_path)/CMakeCache.txt +compile: compile_commands.json +compile: ## Compile the project + $(CMAKE) --build $(_build_path) --config $(CONFIG) --target all -- -k 0 + +.PHONY: compile-headers +compile-headers: $(_build_path)/CMakeCache.txt ## Compile the headers + $(CMAKE) --build $(_build_path) --config $(CONFIG) --target all_verify_interface_header_sets -- -k 0 + +.PHONY: install +install: $(_build_path)/CMakeCache.txt compile ## Install the project + $(CMAKE) --install $(_build_path) --config $(CONFIG) --component beman.expected --verbose + +.PHONY: clean-install +clean-install: + -rm -rf .install + +.PHONY: realclean +realclean: clean-install + +.PHONY: ctest +ctest: $(_build_path)/CMakeCache.txt ## Run CTest on current build + $(CTEST) --test-dir $(_build_path) --output-on-failure -C $(CONFIG) + +.PHONY: ctest_ +ctest_ : compile + $(CTEST) --test-dir $(_build_path) --output-on-failure -C $(CONFIG) + +.PHONY: test +test: ctest_ ## Rebuild and run tests + +.PHONY: cmake +cmake: | $(_build_path) + cd $(_build_path) && ${run_cmake} + +.PHONY: clean +clean: $(_build_path)/CMakeCache.txt ## Clean the build artifacts + $(CMAKE) --build $(_build_path) --config $(CONFIG) --target clean + +.PHONY: realclean +realclean: ## Delete the build directory + rm -rf $(_build_path) + +.PHONY: env +env: + $(foreach v, $(.VARIABLES), $(info $(v) = $($(v)))) + +.PHONY: papers +papers: + $(MAKE) -C papers/P2988 papers + +.DEFAULT: $(_build_path)/CMakeCache.txt ## Other targets passed through to cmake + $(CMAKE) --build $(_build_path) --config $(CONFIG) --target $@ -- -k 0 + +.PHONY: all +all: compile + + +.PHONY: venv +venv: ## Create python virtual env +venv: $(VENV)/$(MARKER) + +.PHONY: clean-venv +clean-venv: +clean-venv: ## Delete python virtual env + -rm -rf $(VENV) + +realclean: clean-venv + +.PHONY: show-venv +show-venv: venv +show-venv: ## Debugging target - show venv details + $(PYEXEC) -c "import sys; print('Python ' + sys.version.replace('\n',''))" + @echo venv: $(VENV) + +uv.lock: pyproject.toml + $(UV) lock + +$(VENV): + $(UV) venv --python $(PYTHON) + +$(VENV)/$(MARKER): uv.lock | $(VENV) + $(UV) sync + touch $(VENV)/$(MARKER) + +.PHONY: dev-shell +dev-shell: venv +dev-shell: ## Shell with the venv activated + $(ACTIVATE) $(notdir $(SHELL)) + +.PHONY: bash zsh +bash zsh: venv +bash zsh: ## Run bash or zsh with the venv activated + $(ACTIVATE) $@ + +.PHONY: lint +lint: venv +lint: ## Run all configured tools in pre-commit + $(PRE_COMMIT) run -a + +.PHONY: lint-manual +lint-manual: venv +lint-manual: ## Run all manual tools in pre-commit + $(PRE_COMMIT) run --hook-stage manual -a + +.PHONY: coverage +coverage: ## Build and run the tests with the GCOV profile and process the results +coverage: venv $(_build_path)/CMakeCache.txt + $(CMAKE) --build $(_build_path) --config Gcov + $(ACTIVATE) ctest --build-config Gcov --output-on-failure --test-dir $(_build_path) + $(CMAKE) --build $(_build_path) --config Gcov --target process_coverage + +.PHONY: view-coverage +view-coverage: ## View the coverage report + sensible-browser $(_build_path)/coverage/coverage.html + +.PHONY: docs +docs: ## Build the docs with Doxygen + doxygen docs/Doxyfile + +.PHONY: mrdocs +mrdocs: ## Build the docs with Doxygen + -rm -rf docs/adoc + cd docs && NO_COLOR=1 mrdocs mrdocs.yml 2>&1 | sed 's/\x1b\[[0-9;]*m//g' + find docs/adoc -name '*.adoc' | xargs asciidoctor + +.PHONY: testinstall +testinstall: install +testinstall: ## Test the installed package + mkdir -p installtest + $(CMAKE) -S installtest -B installtest/.build + $(CMAKE) --build installtest/.build --target test + +.PHONY: clean-testinstall +clean-testinstall: + -rm -rf installtest/.build + +realclean: clean-testinstall + +ifeq ($(UV),) +define install_uv_cmd +pipx install uv +endef + +define uv_error_message + +'uv' command not found. +Please install uv or set the UV variable to the path of the uv binary. +The makefile target "install-uv" will run ``$(install_uv_cmd)'' +endef + +$(error "$(uv_error_message)") +endif + +.PHONY: install-uv +install-uv: ## install uv via `pipx install uv` + $(install_uv_cmd) + +# Help target +.PHONY: help +help: ## Show this help. + @awk 'BEGIN {FS = ":.*?## "} /^[.a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) | sort diff --git a/cmake/Config.cmake.in b/cmake/Config.cmake.in new file mode 100644 index 0000000..4c786be --- /dev/null +++ b/cmake/Config.cmake.in @@ -0,0 +1,7 @@ +# cmake/Config.cmake.in -*-cmake-*- +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@TARGETS_EXPORT_NAME@.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/cmake/ci-clang-toolchain.cmake b/cmake/ci-clang-toolchain.cmake new file mode 100644 index 0000000..8158594 --- /dev/null +++ b/cmake/ci-clang-toolchain.cmake @@ -0,0 +1,44 @@ +include_guard(GLOBAL) + +set(CMAKE_C_COMPILER clang) +set(CMAKE_CXX_COMPILER clang++) + +set(CMAKE_CXX_FLAGS + "-std=c++20 \ + -Wall -Wextra \ + -stdlib=libc++ -fexperimental-library" + CACHE STRING + "CXX_FLAGS" + FORCE +) + +set(CMAKE_CXX_FLAGS_DEBUG + "-O0 -fno-inline -g3" + CACHE STRING + "C++ DEBUG Flags" + FORCE +) +set(CMAKE_CXX_FLAGS_RELEASE + "-Ofast -g0 -DNDEBUG" + CACHE STRING + "C++ Release Flags" + FORCE +) +set(CMAKE_CXX_FLAGS_RELWITHDEBINFO + "-O3 -g -DNDEBUG" + CACHE STRING + "C++ RelWithDebInfo Flags" + FORCE +) +set(CMAKE_CXX_FLAGS_TSAN + "-O3 -g -DNDEBUG -fsanitize=thread" + CACHE STRING + "C++ TSAN Flags" + FORCE +) +set(CMAKE_CXX_FLAGS_ASAN + "-O3 -g -DNDEBUG -fsanitize=address -fsanitize=undefined -fsanitize=leak" + CACHE STRING + "C++ ASAN Flags" + FORCE +) diff --git a/cmake/clang-16-toolchain.cmake b/cmake/clang-16-toolchain.cmake new file mode 100644 index 0000000..4de4ca0 --- /dev/null +++ b/cmake/clang-16-toolchain.cmake @@ -0,0 +1,7 @@ +include_guard(GLOBAL) + +set(CMAKE_C_COMPILER clang-16) +set(CMAKE_CXX_COMPILER clang++-16) +set(GCOV_EXECUTABLE "llvm-cov-16 gcov" CACHE STRING "GCOV executable" FORCE) + +include("${CMAKE_CURRENT_LIST_DIR}/clang-flags.cmake") diff --git a/cmake/clang-17-toolchain.cmake b/cmake/clang-17-toolchain.cmake new file mode 100644 index 0000000..549eb4b --- /dev/null +++ b/cmake/clang-17-toolchain.cmake @@ -0,0 +1,7 @@ +include_guard(GLOBAL) + +set(CMAKE_C_COMPILER clang-17) +set(CMAKE_CXX_COMPILER clang++-17) +set(GCOV_EXECUTABLE "llvm-cov-17 gcov" CACHE STRING "GCOV executable" FORCE) + +include("${CMAKE_CURRENT_LIST_DIR}/clang-flags.cmake") diff --git a/cmake/clang-18-toolchain.cmake b/cmake/clang-18-toolchain.cmake new file mode 100644 index 0000000..ef6be51 --- /dev/null +++ b/cmake/clang-18-toolchain.cmake @@ -0,0 +1,7 @@ +include_guard(GLOBAL) + +set(CMAKE_C_COMPILER clang-18) +set(CMAKE_CXX_COMPILER clang++-18) +set(GCOV_EXECUTABLE "llvm-cov-18 gcov" CACHE STRING "GCOV executable" FORCE) + +include("${CMAKE_CURRENT_LIST_DIR}/clang-flags.cmake") diff --git a/cmake/clang-19-toolchain.cmake b/cmake/clang-19-toolchain.cmake new file mode 100644 index 0000000..25e6fc1 --- /dev/null +++ b/cmake/clang-19-toolchain.cmake @@ -0,0 +1,7 @@ +include_guard(GLOBAL) + +set(CMAKE_C_COMPILER clang-19) +set(CMAKE_CXX_COMPILER clang++-19) +set(GCOV_EXECUTABLE "llvm-cov-19 gcov" CACHE STRING "GCOV executable" FORCE) + +include("${CMAKE_CURRENT_LIST_DIR}/clang-flags.cmake") diff --git a/cmake/clang-20-toolchain.cmake b/cmake/clang-20-toolchain.cmake new file mode 100644 index 0000000..d2be17f --- /dev/null +++ b/cmake/clang-20-toolchain.cmake @@ -0,0 +1,12 @@ +# cmake-format: off +# etc/clang-20-toolchain.cmake -*-cmake-*- +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# cmake-format: on + +include_guard(GLOBAL) + +set(CMAKE_C_COMPILER clang-20) +set(CMAKE_CXX_COMPILER clang++-20) +set(GCOV_EXECUTABLE "llvm-cov-20 gcov" CACHE STRING "GCOV executable" FORCE) + +include("${CMAKE_CURRENT_LIST_DIR}/clang-flags.cmake") diff --git a/cmake/clang-21-toolchain.cmake b/cmake/clang-21-toolchain.cmake new file mode 100644 index 0000000..1f533a4 --- /dev/null +++ b/cmake/clang-21-toolchain.cmake @@ -0,0 +1,12 @@ +# cmake-format: off +# etc/clang-21-toolchain.cmake -*-cmake-*- +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# cmake-format: on + +include_guard(GLOBAL) + +set(CMAKE_C_COMPILER clang-21) +set(CMAKE_CXX_COMPILER clang++-21) +set(GCOV_EXECUTABLE "llvm-cov-21 gcov" CACHE STRING "GCOV executable" FORCE) + +include("${CMAKE_CURRENT_LIST_DIR}/clang-flags.cmake") diff --git a/cmake/clang-22-toolchain.cmake b/cmake/clang-22-toolchain.cmake new file mode 100644 index 0000000..a0614e1 --- /dev/null +++ b/cmake/clang-22-toolchain.cmake @@ -0,0 +1,10 @@ +# etc/clang-22-toolchain.cmake -*-cmake-*- +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +include_guard(GLOBAL) + +set(CMAKE_C_COMPILER clang-22) +set(CMAKE_CXX_COMPILER clang++-22) +set(GCOV_EXECUTABLE "llvm-cov-22 gcov" CACHE STRING "GCOV executable" FORCE) + +include("${CMAKE_CURRENT_LIST_DIR}/clang-flags.cmake") diff --git a/cmake/clang-flags.cmake b/cmake/clang-flags.cmake new file mode 100644 index 0000000..36ec05a --- /dev/null +++ b/cmake/clang-flags.cmake @@ -0,0 +1,48 @@ +include_guard(GLOBAL) + +set(CMAKE_CXX_STANDARD 20) + +set(CMAKE_CXX_FLAGS + "-stdlib=libc++ -Wall -Wextra -std=gnu++20" + CACHE STRING + "CXX_FLAGS" + FORCE +) + +set(CMAKE_CXX_FLAGS_DEBUG + "-O0 -fno-inline -g3" + CACHE STRING + "C++ DEBUG Flags" + FORCE +) +set(CMAKE_CXX_FLAGS_RELEASE + "-Ofast -g0 -DNDEBUG" + CACHE STRING + "C++ Release Flags" + FORCE +) +set(CMAKE_CXX_FLAGS_RELWITHDEBINFO + "-O3 -g -DNDEBUG" + CACHE STRING + "C++ RelWithDebInfo Flags" + FORCE +) +set(CMAKE_CXX_FLAGS_TSAN + "-O3 -g -fsanitize=thread" + CACHE STRING + "C++ TSAN Flags" + FORCE +) +set(CMAKE_CXX_FLAGS_ASAN + "-O3 -g -fsanitize=address,undefined,leak" + CACHE STRING + "C++ ASAN Flags" + FORCE +) +set(CMAKE_CXX_FLAGS_GCOV + "-O0 -fno-inline -g --coverage" + CACHE STRING + "C++ GCOV Flags" + FORCE +) +set(CMAKE_LINKER_FLAGS_GCOV "--coverage" CACHE STRING "Linker GCOV Flags" FORCE) diff --git a/cmake/gcc-12-toolchain.cmake b/cmake/gcc-12-toolchain.cmake new file mode 100644 index 0000000..3c1bd8d --- /dev/null +++ b/cmake/gcc-12-toolchain.cmake @@ -0,0 +1,7 @@ +include_guard(GLOBAL) + +include("${CMAKE_CURRENT_LIST_DIR}/gcc-flags.cmake") + +set(CMAKE_C_COMPILER gcc-12) +set(CMAKE_CXX_COMPILER g++-12) +set(GCOV_EXECUTABLE "gcov-12" CACHE STRING "GCOV executable" FORCE) diff --git a/cmake/gcc-13-toolchain.cmake b/cmake/gcc-13-toolchain.cmake new file mode 100644 index 0000000..4dc22ee --- /dev/null +++ b/cmake/gcc-13-toolchain.cmake @@ -0,0 +1,7 @@ +include_guard(GLOBAL) + +include("${CMAKE_CURRENT_LIST_DIR}/gcc-flags.cmake") + +set(CMAKE_C_COMPILER gcc-13) +set(CMAKE_CXX_COMPILER g++-13) +set(GCOV_EXECUTABLE "gcov-13" CACHE STRING "GCOV executable" FORCE) diff --git a/cmake/gcc-14-toolchain.cmake b/cmake/gcc-14-toolchain.cmake new file mode 100644 index 0000000..8089027 --- /dev/null +++ b/cmake/gcc-14-toolchain.cmake @@ -0,0 +1,14 @@ +include_guard(GLOBAL) + +include("${CMAKE_CURRENT_LIST_DIR}/gcc-flags.cmake") + +set(CMAKE_C_COMPILER gcc-14) +set(CMAKE_CXX_COMPILER g++-14) +set(GCOV_EXECUTABLE "gcov-14" CACHE STRING "GCOV executable" FORCE) + +set(CMAKE_CXX_FLAGS_ASAN + "${CMAKE_CXX_FLAGS_ASAN} -Wno-maybe-uninitialized" + CACHE STRING + "C++ ASAN Flags" + FORCE +) diff --git a/cmake/gcc-15-toolchain.cmake b/cmake/gcc-15-toolchain.cmake new file mode 100644 index 0000000..63874b3 --- /dev/null +++ b/cmake/gcc-15-toolchain.cmake @@ -0,0 +1,14 @@ +include_guard(GLOBAL) + +include("${CMAKE_CURRENT_LIST_DIR}/gcc-flags.cmake") + +set(CMAKE_C_COMPILER gcc-15) +set(CMAKE_CXX_COMPILER g++-15) +set(GCOV_EXECUTABLE "gcov-15" CACHE STRING "GCOV executable" FORCE) + +set(CMAKE_CXX_FLAGS_ASAN + "${CMAKE_CXX_FLAGS_ASAN} -Wno-maybe-uninitialized" + CACHE STRING + "C++ ASAN Flags" + FORCE +) diff --git a/cmake/gcc-16-toolchain.cmake b/cmake/gcc-16-toolchain.cmake new file mode 100644 index 0000000..8dea3ff --- /dev/null +++ b/cmake/gcc-16-toolchain.cmake @@ -0,0 +1,14 @@ +include_guard(GLOBAL) + +include("${CMAKE_CURRENT_LIST_DIR}/gcc-flags.cmake") + +set(CMAKE_C_COMPILER gcc-16) +set(CMAKE_CXX_COMPILER g++-16) +set(GCOV_EXECUTABLE "gcov-16" CACHE STRING "GCOV executable" FORCE) + +set(CMAKE_CXX_FLAGS_ASAN + "${CMAKE_CXX_FLAGS_ASAN} -Wno-maybe-uninitialized" + CACHE STRING + "C++ ASAN Flags" + FORCE +) diff --git a/cmake/gcc-flags.cmake b/cmake/gcc-flags.cmake new file mode 100644 index 0000000..ebb52b6 --- /dev/null +++ b/cmake/gcc-flags.cmake @@ -0,0 +1,45 @@ +include_guard(GLOBAL) + +set(CMAKE_CXX_STANDARD 20) + +set(CMAKE_CXX_FLAGS "-Wall -Wextra -std=gnu++20" CACHE STRING "CXX_FLAGS" FORCE) + +set(CMAKE_CXX_FLAGS_DEBUG + "-O0 -fno-inline -g3" + CACHE STRING + "C++ DEBUG Flags" + FORCE +) +set(CMAKE_CXX_FLAGS_RELEASE + "-Ofast -g0 -DNDEBUG" + CACHE STRING + "C++ Release Flags" + FORCE +) +set(CMAKE_CXX_FLAGS_RELWITHDEBINFO + "-O3 -g -DNDEBUG" + CACHE STRING + "C++ RelWithDebInfo Flags" + FORCE +) +set(CMAKE_CXX_FLAGS_TSAN + "-O3 -g -fsanitize=thread" + CACHE STRING + "C++ TSAN Flags" + FORCE +) +set(CMAKE_CXX_FLAGS_ASAN + "-O3 -g -fsanitize=address,undefined,leak" + CACHE STRING + "C++ ASAN Flags" + FORCE +) + +set(CMAKE_CXX_FLAGS_GCOV + "-O0 -fno-default-inline -fno-inline -g --coverage -fprofile-abs-path" + CACHE STRING + "C++ GCOV Flags" + FORCE +) + +set(CMAKE_LINKER_FLAGS_GCOV "--coverage" CACHE STRING "Linker GCOV Flags" FORCE) diff --git a/cmake/gcc-toolchain.cmake b/cmake/gcc-toolchain.cmake new file mode 100644 index 0000000..635c9d5 --- /dev/null +++ b/cmake/gcc-toolchain.cmake @@ -0,0 +1,37 @@ +include_guard(GLOBAL) + +set(CMAKE_C_COMPILER gcc) +set(CMAKE_CXX_COMPILER g++) + +set(CMAKE_CXX_FLAGS "-Wall -Wextra " CACHE STRING "CXX_FLAGS" FORCE) + +set(CMAKE_CXX_FLAGS_DEBUG + "-O0 -fno-inline -g3" + CACHE STRING + "C++ DEBUG Flags" + FORCE +) +set(CMAKE_CXX_FLAGS_RELEASE + "-Ofast -g0 -DNDEBUG" + CACHE STRING + "C++ Release Flags" + FORCE +) +set(CMAKE_CXX_FLAGS_RELWITHDEBINFO + "-O3 -g -DNDEBUG" + CACHE STRING + "C++ RelWithDebInfo Flags" + FORCE +) +set(CMAKE_CXX_FLAGS_TSAN + "-O3 -g -DNDEBUG -fsanitize=thread" + CACHE STRING + "C++ TSAN Flags" + FORCE +) +set(CMAKE_CXX_FLAGS_ASAN + "-O3 -g -DNDEBUG -fsanitize=address,undefined,leak" + CACHE STRING + "C++ ASAN Flags" + FORCE +) diff --git a/cmake/gcovr.cfg.in b/cmake/gcovr.cfg.in new file mode 100644 index 0000000..b4e248b --- /dev/null +++ b/cmake/gcovr.cfg.in @@ -0,0 +1,13 @@ +root = @CMAKE_SOURCE_DIR@ +cobertura = @CMAKE_BINARY_DIR@/coverage/cobertura.xml +sonarqube = @CMAKE_BINARY_DIR@/coverage/sonarqube.xml +html-details = @CMAKE_BINARY_DIR@/coverage/coverage.html +gcov-executable = @GCOV_EXECUTABLE@ +gcov-parallel = yes +html-theme = github.dark-blue +html-self-contained = yes +print-summary = yes +filter = .*/smd/conceptmap/.* +exclude = .*\.t\.cpp +coveralls = coverage.json +coveralls-pretty = yes diff --git a/cmake/llvm-16-toolchain.cmake b/cmake/llvm-16-toolchain.cmake new file mode 100644 index 0000000..317eb15 --- /dev/null +++ b/cmake/llvm-16-toolchain.cmake @@ -0,0 +1,44 @@ +include_guard(GLOBAL) + +set(CMAKE_C_COMPILER clang-16) +set(CMAKE_CXX_COMPILER clang++-16) + +set(CMAKE_CXX_FLAGS + "-std=c++20 \ + -Wall -Wextra \ + -stdlib=libc++ -fexperimental-library" + CACHE STRING + "CXX_FLAGS" + FORCE +) + +set(CMAKE_CXX_FLAGS_DEBUG + "-O0 -fno-inline -g3" + CACHE STRING + "C++ DEBUG Flags" + FORCE +) +set(CMAKE_CXX_FLAGS_RELEASE + "-Ofast -g0 -DNDEBUG" + CACHE STRING + "C++ Release Flags" + FORCE +) +set(CMAKE_CXX_FLAGS_RELWITHDEBINFO + "-O3 -g -DNDEBUG" + CACHE STRING + "C++ RelWithDebInfo Flags" + FORCE +) +set(CMAKE_CXX_FLAGS_TSAN + "-O3 -g -DNDEBUG -fsanitize=thread" + CACHE STRING + "C++ TSAN Flags" + FORCE +) +set(CMAKE_CXX_FLAGS_ASAN + "-O3 -g -DNDEBUG -fsanitize=address -fsanitize=undefined -fsanitize=leak" + CACHE STRING + "C++ ASAN Flags" + FORCE +) diff --git a/cmake/llvm-master-toolchain.cmake b/cmake/llvm-master-toolchain.cmake new file mode 100644 index 0000000..9f1ff60 --- /dev/null +++ b/cmake/llvm-master-toolchain.cmake @@ -0,0 +1,51 @@ +set(LLVM_ROOT "$ENV{LLVM_ROOT}" CACHE PATH "Path to LLVM installation") + +set(CMAKE_C_COMPILER ${LLVM_ROOT}/bin/clang) +set(CMAKE_CXX_COMPILER ${LLVM_ROOT}/bin/clang++) + +set(CMAKE_CXX_FLAGS + "-std=c++2a \ + -Wall -Wextra \ + -stdlib=libc++ " + CACHE STRING + "CXX_FLAGS" + FORCE +) + +set(CMAKE_EXE_LINKER_FLAGS + "-Wl,-rpath,${LLVM_ROOT}/lib" + CACHE STRING + "CMAKE_EXE_LINKER_FLAGS" + FORCE +) + +set(CMAKE_CXX_FLAGS_DEBUG + "-O0 -fno-inline -g3" + CACHE STRING + "C++ DEBUG Flags" + FORCE +) +set(CMAKE_CXX_FLAGS_RELEASE + "-Ofast -g0 -DNDEBUG" + CACHE STRING + "C++ Release Flags" + FORCE +) +set(CMAKE_CXX_FLAGS_RELWITHDEBINFO + "-O3 -g -DNDEBUG" + CACHE STRING + "C++ Release Flags" + FORCE +) +set(CMAKE_CXX_FLAGS_TSAN + "-O3 -g -DNDEBUG -fsanitize=thread" + CACHE STRING + "C++ TSAN Flags" + FORCE +) +set(CMAKE_CXX_FLAGS_ASAN + "-O3 -g -DNDEBUG -fsanitize=address -fsanitize=undefined -fsanitize=leak" + CACHE STRING + "C++ ASAN Flags" + FORCE +) diff --git a/cmake/llvm-toolchain.cmake b/cmake/llvm-toolchain.cmake new file mode 100644 index 0000000..b0cbfbd --- /dev/null +++ b/cmake/llvm-toolchain.cmake @@ -0,0 +1,44 @@ +include_guard(GLOBAL) + +set(CMAKE_C_COMPILER clang-14) +set(CMAKE_CXX_COMPILER clang++-14) + +set(CMAKE_CXX_FLAGS + "-std=c++20 \ + -Wall -Wextra \ + -stdlib=libstdc++ " + CACHE STRING + "CXX_FLAGS" + FORCE +) + +set(CMAKE_CXX_FLAGS_DEBUG + "-O0 -fno-inline -g3" + CACHE STRING + "C++ DEBUG Flags" + FORCE +) +set(CMAKE_CXX_FLAGS_RELEASE + "-Ofast -g0 -DNDEBUG" + CACHE STRING + "C++ Release Flags" + FORCE +) +set(CMAKE_CXX_FLAGS_RELWITHDEBINFO + "-O3 -g -DNDEBUG" + CACHE STRING + "C++ RelWithDebInfo Flags" + FORCE +) +set(CMAKE_CXX_FLAGS_TSAN + "-O3 -g -DNDEBUG -fsanitize=thread" + CACHE STRING + "C++ TSAN Flags" + FORCE +) +set(CMAKE_CXX_FLAGS_ASAN + "-O3 -g -DNDEBUG -fsanitize=address -fsanitize=undefined -fsanitize=leak" + CACHE STRING + "C++ ASAN Flags" + FORCE +) diff --git a/cmake/toolchain.cmake b/cmake/toolchain.cmake new file mode 100644 index 0000000..046d332 --- /dev/null +++ b/cmake/toolchain.cmake @@ -0,0 +1,6 @@ +include_guard(GLOBAL) + +include("${CMAKE_CURRENT_LIST_DIR}/gcc-flags.cmake") + +set(CMAKE_C_COMPILER cc) +set(CMAKE_CXX_COMPILER c++) diff --git a/cmake/use-fetch-content.cmake b/cmake/use-fetch-content.cmake new file mode 100644 index 0000000..407b660 --- /dev/null +++ b/cmake/use-fetch-content.cmake @@ -0,0 +1,179 @@ +cmake_minimum_required(VERSION 3.24) + +include(FetchContent) + +if(NOT BEMAN_INFRA_LOCKFILE) + set(BEMAN_INFRA_LOCKFILE + "lockfile.json" + CACHE FILEPATH + "Path to the dependency lockfile for the Beman Infra provider." + ) +endif() + +set(BemanInfra_projectDir "${CMAKE_CURRENT_LIST_DIR}/../") +message(TRACE "BemanInfra_projectDir=\"${BemanInfra_projectDir}\"") + +message(TRACE "BEMAN_INFRA_LOCKFILE=\"${BEMAN_INFRA_LOCKFILE}\"") +file( + REAL_PATH "${BEMAN_INFRA_LOCKFILE}" + BemanInfra_lockfile + BASE_DIRECTORY "${BemanInfra_projectDir}" + EXPAND_TILDE +) +message(DEBUG "Using lockfile: \"${BemanInfra_lockfile}\"") + +# Force CMake to reconfigure the project if the lockfile changes +set_property( + DIRECTORY "${BemanInfra_projectDir}" + APPEND + PROPERTY CMAKE_CONFIGURE_DEPENDS "${BemanInfra_lockfile}" +) + +# For more on the protocol for this function, see: +# https://cmake.org/cmake/help/latest/command/cmake_language.html#provider-commands +function(BemanInfra_provideDependency method package_name) + # Read the lockfile + file(READ "${BemanInfra_lockfile}" BemanInfra_rootObj) + + # Get the "dependencies" field and store it in BemanInfra_dependenciesObj + string( + JSON BemanInfra_dependenciesObj + ERROR_VARIABLE BemanInfra_error + GET "${BemanInfra_rootObj}" + "dependencies" + ) + if(BemanInfra_error) + message(FATAL_ERROR "${BemanInfra_lockfile}: ${BemanInfra_error}") + endif() + + # Get the length of the libraries array and store it in BemanInfra_dependenciesObj + string( + JSON BemanInfra_numDependencies + ERROR_VARIABLE BemanInfra_error + LENGTH "${BemanInfra_dependenciesObj}" + ) + if(BemanInfra_error) + message(FATAL_ERROR "${BemanInfra_lockfile}: ${BemanInfra_error}") + endif() + + if(BemanInfra_numDependencies EQUAL 0) + return() + endif() + + # Loop over each dependency object + math(EXPR BemanInfra_maxIndex "${BemanInfra_numDependencies} - 1") + foreach(BemanInfra_index RANGE "${BemanInfra_maxIndex}") + set(BemanInfra_errorPrefix + "${BemanInfra_lockfile}, dependency ${BemanInfra_index}" + ) + + # Get the dependency object at BemanInfra_index + # and store it in BemanInfra_depObj + string( + JSON BemanInfra_depObj + ERROR_VARIABLE BemanInfra_error + GET "${BemanInfra_dependenciesObj}" + "${BemanInfra_index}" + ) + if(BemanInfra_error) + message( + FATAL_ERROR + "${BemanInfra_errorPrefix}: ${BemanInfra_error}" + ) + endif() + + # Get the "name" field and store it in BemanInfra_name + string( + JSON BemanInfra_name + ERROR_VARIABLE BemanInfra_error + GET "${BemanInfra_depObj}" + "name" + ) + if(BemanInfra_error) + message( + FATAL_ERROR + "${BemanInfra_errorPrefix}: ${BemanInfra_error}" + ) + endif() + + # Get the "package_name" field and store it in BemanInfra_pkgName + string( + JSON BemanInfra_pkgName + ERROR_VARIABLE BemanInfra_error + GET "${BemanInfra_depObj}" + "package_name" + ) + if(BemanInfra_error) + message( + FATAL_ERROR + "${BemanInfra_errorPrefix}: ${BemanInfra_error}" + ) + endif() + + # Get the "git_repository" field and store it in BemanInfra_repo + if(DEFINED "BEMANINFRA_${BemanInfra_name}_REPO") + set(BemanInfra_repo ${BEMANINFRA_${BemanInfra_name}_REPO}) + else() + string( + JSON BemanInfra_repo + ERROR_VARIABLE BemanInfra_error + GET "${BemanInfra_depObj}" + "git_repository" + ) + if(BemanInfra_error) + message( + FATAL_ERROR + "${BemanInfra_errorPrefix}: ${BemanInfra_error}" + ) + endif() + endif() + + # Get the "git_tag" field and store it in BemanInfra_tag + string( + JSON BemanInfra_tag + ERROR_VARIABLE BemanInfra_error + GET "${BemanInfra_depObj}" + "git_tag" + ) + if(BemanInfra_error) + message( + FATAL_ERROR + "${BemanInfra_errorPrefix}: ${BemanInfra_error}" + ) + endif() + + if(method STREQUAL "FIND_PACKAGE") + if(package_name STREQUAL BemanInfra_pkgName) + string( + APPEND BemanInfra_debug + "Redirecting find_package calls for ${BemanInfra_pkgName} " + "to FetchContent logic.\n" + string + APPEND BemanInfra_debug + "Fetching ${BemanInfra_repo} at " + "${BemanInfra_tag} according to ${BemanInfra_lockfile}." + ) + message(DEBUG "${BemanInfra_debug}") + FetchContent_Declare( + "${BemanInfra_name}" + GIT_REPOSITORY "${BemanInfra_repo}" + GIT_TAG "${BemanInfra_tag}" + EXCLUDE_FROM_ALL + ) + FetchContent_MakeAvailable("${BemanInfra_name}") + + # Important! _FOUND tells CMake that `find_package` is + # not needed for this package anymore + set("${BemanInfra_pkgName}_FOUND" TRUE PARENT_SCOPE) + endif() + endif() + endforeach() +endfunction() + +cmake_language( + SET_DEPENDENCY_PROVIDER BemanInfra_provideDependency + SUPPORTED_METHODS FIND_PACKAGE +) + +# Add this dir to the module path so that `find_package(beman-install-library)` works +list(APPEND CMAKE_PREFIX_PATH "${CMAKE_CURRENT_LIST_DIR}") diff --git a/cmake/x64-linux-custom.cmake b/cmake/x64-linux-custom.cmake new file mode 100644 index 0000000..1d8ba8d --- /dev/null +++ b/cmake/x64-linux-custom.cmake @@ -0,0 +1,9 @@ +set(VCPKG_TARGET_ARCHITECTURE x64) +set(VCPKG_CRT_LINKAGE dynamic) +set(VCPKG_LIBRARY_LINKAGE static) + +set(VCPKG_CMAKE_SYSTEM_NAME Linux) + +message(NOTICE "USE_VCPKG_TOOLCHAIN: $ENV{PROJECT_VCPKG_TOOLCHAIN}") + +set(VCPKG_CHAINLOAD_TOOLCHAIN_FILE "$ENV{PROJECT_VCPKG_TOOLCHAIN}") diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 177b579..26c08cc 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,19 +1,7 @@ +# examples/CMakeLists.txt -*-cmake-*- # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -set(ALL_EXAMPLES identity_direct_usage) - -# Example `identity_as_default_projection` need ranges support: -include(CheckCXXSymbolExists) -check_cxx_symbol_exists(__cpp_lib_ranges "ranges" HAS_RANGES) - -if(HAS_RANGES) - list(APPEND ALL_EXAMPLES identity_as_default_projection) -else() - message( - WARNING - "Missing range support! Skip: identity_as_default_projection" - ) -endif() +set(ALL_EXAMPLES expected) message("Examples to be built: ${ALL_EXAMPLES}") diff --git a/examples/expected.cpp b/examples/expected.cpp new file mode 100644 index 0000000..538e906 --- /dev/null +++ b/examples/expected.cpp @@ -0,0 +1,4 @@ +// examples/expected.cpp -*-C++-*- +#include + +int main(int /*argc*/, char** /*argv*/) { return 0; } diff --git a/examples/identity_as_default_projection.cpp b/examples/identity_as_default_projection.cpp deleted file mode 100644 index dae4c09..0000000 --- a/examples/identity_as_default_projection.cpp +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -// This example demonstrates the usage of beman::expected::identity as a default projection in a range-printer. -// Requires: range support (C++20) and std::identity support (C++20). -// TODO Darius: Do we need to selectively compile this example? -// Or should we assume that this project is compiled with C++20 support only? - -#include - -#include -#include // std::identity -#include -#include -#include -#include - -namespace exe = beman::expected; - -// Class with a pair of values. -struct Pair { - int n; - std::string s; - - // Output the pair in the form {n, s}. - // Used by the range-printer if no custom projection is provided (default: identity projection). - friend std::ostream& operator<<(std::ostream& os, const Pair& p) { - return os << "Pair" << '{' << p.n << ", " << p.s << '}'; - } -}; - -// A range-printer that can print projected (modified) elements of a range. -// All the elements of the range are printed in the form {element1, element2, ...}. -// e.g., pairs with identity: Pair{1, one}, Pair{2, two}, Pair{3, three} -// e.g., pairs with custom projection: {1:one, 2:two, 3:three} -template -void print_helper(const std::string_view rem, R&& range, Projection projection) { - std::cout << rem << '{'; - std::ranges::for_each(range, [O = 0](const auto& o) mutable { std::cout << (O++ ? ", " : "") << o; }, projection); - std::cout << "}\n"; -}; - -// Print wrapper with exe::identity. -template // <- Notice the default projection. -void print_beman(const std::string_view rem, R&& range, Projection projection = {}) { - print_helper(rem, range, projection); -} - -// Print wrapper with std::identity. -template // <- Notice the default projection. -void print_std(const std::string_view rem, R&& range, Projection projection = {}) { - print_helper(rem, range, projection); -} - -int main() { - // A vector of pairs to print. - const std::vector pairs = { - {1, "one"}, - {2, "two"}, - {3, "three"}, - }; - - // Print the pairs using the default projection. - std::cout << "Default projection:\n"; - print_beman("\tpairs with beman: ", pairs); - print_std("\tpairs with std: ", pairs); - - // Print the pairs using a custom projection. - std::cout << "Custom projection:\n"; - print_beman("\tpairs with beman: ", pairs, [](const auto& p) { return std::to_string(p.n) + ':' + p.s; }); - print_std("\tpairs with std: ", pairs, [](const auto& p) { return std::to_string(p.n) + ':' + p.s; }); - - return 0; -} diff --git a/examples/identity_direct_usage.cpp b/examples/identity_direct_usage.cpp deleted file mode 100644 index c07574d..0000000 --- a/examples/identity_direct_usage.cpp +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -#include - -#include - -namespace exe = beman::expected; - -int main() { - std::cout << exe::identity()(2024) << '\n'; - return 0; -} diff --git a/include/beman/expected/CMakeLists.txt b/include/beman/expected/CMakeLists.txt index cb5f6df..fee3999 100644 --- a/include/beman/expected/CMakeLists.txt +++ b/include/beman/expected/CMakeLists.txt @@ -1,3 +1,7 @@ +# include/beman/expected/CMakeLists.txt -*-cmake-*- # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -target_sources(beman.expected PUBLIC FILE_SET HEADERS FILES identity.hpp) +target_sources( + beman.expected + PUBLIC FILE_SET HEADERS FILES expected.hpp unexpected.hpp +) diff --git a/include/beman/expected/expected.hpp b/include/beman/expected/expected.hpp new file mode 100644 index 0000000..373f7b6 --- /dev/null +++ b/include/beman/expected/expected.hpp @@ -0,0 +1,10 @@ +// beman/expected/expected.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +#ifndef BEMAN_EXPECTED_EXPECTED_HPP +#define BEMAN_EXPECTED_EXPECTED_HPP + +namespace beman { +namespace expected {} +} // namespace beman + +#endif diff --git a/include/beman/expected/identity.hpp b/include/beman/expected/identity.hpp deleted file mode 100644 index 6276877..0000000 --- a/include/beman/expected/identity.hpp +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -#ifndef BEMAN_EXPECTED_IDENTITY_HPP -#define BEMAN_EXPECTED_IDENTITY_HPP - -// C++ Standard Library: std::identity equivalent. -// See https://eel.is/c++draft/func.identity: -// -// 22.10.12 Class identity [func.identity] -// -// struct identity { -// template -// constexpr T&& operator()(T&& t) const noexcept; -// -// using is_transparent = unspecified; -// }; -// -// template -// constexpr T&& operator()(T&& t) const noexcept; -// -// Effects: Equivalent to: return std::forward(t); - -#include // std::forward - -namespace beman::expected { - -struct __is_transparent; // not defined - -// A function object that returns its argument unchanged. -struct identity { - // Returns `t`. - template - constexpr T&& operator()(T&& t) const noexcept { - return std::forward(t); - } - - using is_transparent = __is_transparent; -}; - -} // namespace beman::expected - -#endif // BEMAN_EXPECTED_IDENTITY_HPP diff --git a/include/beman/expected/unexpected.hpp b/include/beman/expected/unexpected.hpp new file mode 100644 index 0000000..ddbaa8f --- /dev/null +++ b/include/beman/expected/unexpected.hpp @@ -0,0 +1,10 @@ +// beman/expected/unexpected.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +#ifndef BEMAN_EXPECTED_UNEXPECTED_HPP +#define BEMAN_EXPECTED_UNEXPECTED_HPP + +namespace beman { +namespace expected {} +} // namespace beman + +#endif diff --git a/installtest/CMakeLists.txt b/installtest/CMakeLists.txt new file mode 100644 index 0000000..d7738b4 --- /dev/null +++ b/installtest/CMakeLists.txt @@ -0,0 +1,31 @@ +# installtest/CMakeLists.txt -*-CMake-*- +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +cmake_minimum_required(VERSION 3.27) +project(TestInstalledExpected) + +set(CMAKE_CXX_STANDARD 26) #current minimum C++ version + +# Enable testing in this separate project +enable_testing() + +# Find the installed package +set(EXPECTED_INSTALL_DIR "../.install/lib/cmake/beman.expected") +find_package( + beman.expected + REQUIRED + PATHS ${EXPECTED_INSTALL_DIR} + NO_DEFAULT_PATH +) + +# Add the test executable +add_executable(TestInstalledExpected test.cpp) + +# Link against the imported target +target_link_libraries(TestInstalledExpected beman::expected) + +# Register the test with CTest +add_test(NAME RunInstalledTest COMMAND TestInstalledExpected) + +# Ensure 'make test' first builds the 'all' target +set(CMAKE_SKIP_TEST_ALL_DEPENDENCY FALSE) diff --git a/installtest/README.md b/installtest/README.md new file mode 100644 index 0000000..72ffa94 --- /dev/null +++ b/installtest/README.md @@ -0,0 +1,9 @@ +# Test Project against installed `beman.optional` + +To test from the root of the source tree +```sh +cmake --workflow --preset gcc-release +cmake --install build/gcc-release --prefix .install --component beman.optional +cmake -S installtest -B installtest/build +cmake --build installtest/build --target test +``` diff --git a/installtest/test.cpp b/installtest/test.cpp new file mode 100644 index 0000000..b886fe5 --- /dev/null +++ b/installtest/test.cpp @@ -0,0 +1,6 @@ +// testinstall/test.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include + +int main() { return 0; } diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..e83d376 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,15 @@ +[project] +name = "beman-expected" +version = "0.1.0" +description = "An implementation of std::expected" +readme = "README.md" +requires-python = ">=3.13" +dependencies = [] + +[dependency-groups] +dev = [ + "cmake==4.2.1", + "clang-format==18.1.8", + "gcovr>=7.2", + "pre-commit>=3.7.1", +] diff --git a/tests/beman/expected/CMakeLists.txt b/tests/beman/expected/CMakeLists.txt index 880d550..8573982 100644 --- a/tests/beman/expected/CMakeLists.txt +++ b/tests/beman/expected/CMakeLists.txt @@ -1,13 +1,15 @@ +# tests/beman/expected/CMakeLists.txt -*-cmake-*- # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -find_package(GTest REQUIRED) - -add_executable(beman.expected.tests.identity) -target_sources(beman.expected.tests.identity PRIVATE identity.test.cpp) +add_executable(beman.expected.tests.expected) +target_sources( + beman.expected.tests.expected + PRIVATE unexpected.test.cpp expected.test.cpp +) target_link_libraries( - beman.expected.tests.identity + beman.expected.tests.expected PRIVATE beman::expected GTest::gtest GTest::gtest_main ) include(GoogleTest) -gtest_discover_tests(beman.expected.tests.identity) +gtest_discover_tests(beman.expected.tests.expected) diff --git a/tests/beman/expected/expected.test.cpp b/tests/beman/expected/expected.test.cpp new file mode 100644 index 0000000..bd3cd81 --- /dev/null +++ b/tests/beman/expected/expected.test.cpp @@ -0,0 +1,14 @@ +// beman/expected/expected.test.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include // ensure idempotent header + +#include + +#include +#include + +namespace expt = beman::expected; + +TEST(ExpectedTest, breathing) { EXPECT_EQ(true, true); } diff --git a/tests/beman/expected/identity.test.cpp b/tests/beman/expected/identity.test.cpp deleted file mode 100644 index 14d665e..0000000 --- a/tests/beman/expected/identity.test.cpp +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -#include - -#include - -#include -#include - -namespace exe = beman::expected; - -TEST(IdentityTest, call_identity_with_int) { - for (int i = -100; i < 100; ++i) { - EXPECT_EQ(i, exe::identity()(i)); - } -} - -TEST(IdentityTest, call_identity_with_custom_type) { - struct S { - int i; - }; - - for (int i = -100; i < 100; ++i) { - const S s{i}; - const S s_id = exe::identity()(s); - EXPECT_EQ(s.i, s_id.i); - } -} - -TEST(IdentityTest, compare_std_vs_beman) { -// Requires: std::identity support. -#if defined(__cpp_lib_type_identity) - std::identity std_id; - exe::identity beman_id; - for (int i = -100; i < 100; ++i) { - EXPECT_EQ(std_id(i), beman_id(i)); - } -#endif -} - -TEST(IdentityTest, check_is_transparent) { -// Requires: transparent operators support. -#if defined(__cpp_lib_transparent_operators) - - exe::identity id; - - const auto container = {1, 2, 3, 4, 5}; - auto it = std::find(std::begin(container), std::end(container), 3); - EXPECT_EQ(3, *it); - auto it_with_id = std::find(std::begin(container), std::end(container), id(3)); - EXPECT_EQ(3, *it_with_id); - - EXPECT_EQ(it, it_with_id); -#endif -} diff --git a/tests/beman/expected/unexpected.test.cpp b/tests/beman/expected/unexpected.test.cpp new file mode 100644 index 0000000..01a0844 --- /dev/null +++ b/tests/beman/expected/unexpected.test.cpp @@ -0,0 +1,14 @@ +// beman/expected/unexpected.test.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include // ensure idempotent header + +#include + +#include +#include + +namespace expt = beman::expected; + +TEST(UnexpectedTest, breathing) { EXPECT_EQ(true, true); } diff --git a/vcpkg.json b/vcpkg.json new file mode 100644 index 0000000..f79be62 --- /dev/null +++ b/vcpkg.json @@ -0,0 +1,5 @@ +{ + "dependencies": [ + "gtest" + ] +}