diff --git a/CMakeLists.txt b/CMakeLists.txt index e69a90f..7e67488 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,6 @@ # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -cmake_minimum_required(VERSION 3.25) - -set(CMAKE_CXX_STANDARD 23) +cmake_minimum_required(VERSION 3.30...4.3) project( beman.cstring_view # CMake Project Name, which is also the name of the top-level @@ -26,20 +24,31 @@ option( ${PROJECT_IS_TOP_LEVEL} ) -option( - BEMAN_CSTRING_VIEW_INSTALL_CONFIG_FILE_PACKAGE - "Enable creating and installing a CMake config-file package. Default: ${PROJECT_IS_TOP_LEVEL}. Values: { ON, OFF }." - ${PROJECT_IS_TOP_LEVEL} +# for find of beman-install-library +include(infra/cmake/beman-install-library.cmake) + +add_library(beman.cstring_view INTERFACE) +add_library(beman::cstring_view ALIAS beman.cstring_view) + +target_sources( + beman.cstring_view + PUBLIC FILE_SET HEADERS BASE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/include" +) + +set_target_properties( + beman.cstring_view + PROPERTIES VERIFY_INTERFACE_HEADER_SETS ${PROJECT_IS_TOP_LEVEL} ) -include(CTest) +add_subdirectory(include/beman/cstring_view) -add_subdirectory(src/beman/cstring_view) +beman_install_library(beman.cstring_view TARGETS beman.cstring_view) if(BEMAN_CSTRING_VIEW_BUILD_TESTS) - add_subdirectory(tests/beman/cstring_view) + enable_testing() + add_subdirectory(tests/beman/cstring_view) endif() if(BEMAN_CSTRING_VIEW_BUILD_EXAMPLES) - add_subdirectory(examples) + add_subdirectory(examples) endif() diff --git a/CMakePresets.json b/CMakePresets.json index 483e1a3..bd35911 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -7,7 +7,7 @@ "generator": "Ninja", "binaryDir": "${sourceDir}/build/${presetName}", "cacheVariables": { - "CMAKE_CXX_STANDARD": "20", + "CMAKE_CXX_STANDARD": "23", "CMAKE_EXPORT_COMPILE_COMMANDS": "ON", "CMAKE_PROJECT_TOP_LEVEL_INCLUDES": "./infra/cmake/use-fetch-content.cmake" } diff --git a/README.md b/README.md index 319728b..dbe462d 100644 --- a/README.md +++ b/README.md @@ -1,331 +1,660 @@ -# beman.cstring_view: cstring_view, a null-terminated string view +# beman.main: TODO Short Description + + + + ![Library Status](https://raw.githubusercontent.com/bemanproject/beman/refs/heads/main/images/badges/beman_badge-beman_library_under_development.svg) ![Continuous Integration Tests](https://github.com/dascandy/cstring_view/actions/workflows/ci_tests.yml/badge.svg) ![Lint Check (pre-commit)](https://github.com/dascandy/cstring_view/actions/workflows/pre-commit.yml/badge.svg) + + `beman.cstring_view` is a header-only `cstring_view` library. + + **Implements**: `std::cstring_view` proposed in [cstring\_view (P3655R2)](https://wg21.link/P3655R2). + + **Status**: [Under development and not yet ready for production use.](https://github.com/bemanproject/beman/blob/main/docs/beman_library_maturity_model.md#under-development-and-not-yet-ready-for-production-use) + + ## Usage + + `std::cstring_view` exposes a string\_view like type that is intended for being able to propagate prior knowledge that + a string is null-terminated throughout the type system, while fulfilling the same role as string\_view. + + ### Usage: default projection in constrained algorithms + + The following code snippet illustrates how we can use `cstring_view` to make a beginner-friendly `main`: + + ```cpp + #include + #include + + int main(int argc, const char** argv) { + std::vector args(argv, argv+argc); + } + ``` + + Full runnable examples can be found in [`examples/`](examples/). + + ## Dependencies + + ### Build Environment + + This project requires at least the following to build: + + * C++17 + * CMake 3.25 + * (Test Only) GoogleTest + + You can disable building tests by setting cmake option + [`BEMAN_CSTRING_VIEW_BUILD_TESTS`](#beman_cstring_view_build_tests) to `OFF` + when configuring the project. + + ### Supported Platforms + + This project officially supports: + + * GNU GCC Compiler \[version 12-14\] + * LLVM Clang++ Compiler \[version 17-20\] + * AppleClang compiler on Mac OS + * MSVC compiler on Windows + + > [!NOTE] + > + > Versions outside of this range would likely work as well, + > especially if you're using a version above the given range + > (e.g. HEAD/ nightly). + > These development environments are verified using our CI configuration. + + ## Development + + ### Develop using GitHub Codespace + + This project supports [GitHub Codespace](https://github.com/features/codespaces) + via [Development Containers](https://containers.dev/), + which allows rapid development and instant hacking in your browser. + We recommend you using GitHub codespace to explore this project as this + requires minimal setup. + + You can create a codespace for this project by clicking this badge: + + [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/bemanproject/cstring_view) + + For more detailed documentation regarding creating and developing inside of + GitHub codespaces, please reference [this doc](https://docs.github.com/en/codespaces/). + + > [!NOTE] + > + > The codespace container may take up to 5 minutes to build and spin-up, + > this is normal as we need to build a custom docker container to setup + > an environment appropriate for beman projects. + + ### Develop locally on your machines + +
+ For Linux based systems + + Beman libraries require [recent versions of CMake](#build-environment), + we advise you to download CMake directly from [CMake's website](https://cmake.org/download/) + or install it via the [Kitware apt library](https://apt.kitware.com/). + + A [supported compiler](#supported-platforms) should be available from your package manager. + Alternatively you could use an install script from official compiler vendors. + + Here is an example of how to install the latest stable version of clang + as per [the official LLVM install guide](https://apt.llvm.org/). + + ```bash + bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" + ``` + + If the included test suite is being built and run, a GoogleTest library will be + required. Here is an example of installing GoogleTest on a Debian-based Linux + environment: + + ```bash + apt install libgtest-dev + ``` + + The precise command and package name will vary depending on the Linux OS you are + using. Be sure to consult documentation and the package repository for the system + you are using. + +
+ +
+ For MacOS based systems + + Beman libraries require [recent versions of CMake](#build-environment). + You can use [`Homebrew`](https://brew.sh/) to install the latest major version of CMake. + + ```bash + brew install cmake + ``` + + A [supported compiler](#supported-platforms) is also available from brew. + + For example, you can install the latest major release of Clang as: + + ```bash + brew install llvm + ``` + +
+ +
+ For Windows + + To build Beman libraries, you will need the MSVC compiler. MSVC can be obtained + by installing Visual Studio; the free Visual Studio 2022 Community Edition can + be downloaded from + [Microsoft](https://visualstudio.microsoft.com/vs/community/). + + After Visual Studio has been installed, you can launch "Developer PowerShell for + VS 2022" by typing it into Windows search bar. This shell environment will + provide CMake, Ninja, and MSVC, allowing you to build the library and run the + tests. + + Note that you will need to use FetchContent to build GoogleTest. To do so, + please see the instructions in the "Build GoogleTest dependency from github.com" + dropdown in the [Project specific configure + arguments](#project-specific-configure-arguments) section. + +
+ + ### Configure and Build the Project Using CMake Presets + + This project recommends using [CMake Presets](https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html) + to configure, build and test the project. + Appropriate presets for major compilers have been included by default. + You can use `cmake --list-presets` to see all available presets. + + Here is an example to invoke the `gcc-debug` preset. + + ```shell + cmake --workflow --preset gcc-debug + ``` + + Generally, there are two kinds of presets, `debug` and `release`. + + The `debug` presets are designed to aid development, so it has debugging + instrumentation enabled and as many sanitizers turned on as possible. + + > [!NOTE] + > + > The set of sanitizer supports are different across compilers. + > You can checkout the exact set of compiler arguments by looking at the toolchain + > files under the [`cmake`](cmake/) directory. + + The `release` presets are designed for use in production environments, + thus they have the highest optimization turned on (e.g. `O3`). + + ### Configure and Build Manually + + While [CMake Presets](#configure-and-build-the-project-using-cmake-presets) are + convenient, you might want to set different configuration or compiler arguments + than any provided preset supports. + + To configure, build and test the project with extra arguments, + you can run this set of commands. + + ```bash + cmake -B build -S . -DCMAKE_CXX_STANDARD=20 # Your extra arguments here. + cmake --build build + ctest --test-dir build + ``` + + > [!IMPORTANT] + > + > Beman projects are + > [passive projects](https://github.com/bemanproject/beman/blob/main/docs/beman_standard.md#cmake), + > therefore, + > you will need to specify the C++ version via `CMAKE_CXX_STANDARD` + > when manually configuring the project. + + ### Finding and Fetching GTest from GitHub + + If you do not have GoogleTest installed on your development system, you may + optionally configure this project to download a known-compatible release of + GoogleTest from source and build it as well. + + Example commands: + + ```shell + cmake -B build -S . \ + -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=./infra/cmake/use-fetch-content.cmake \ + -DCMAKE_CXX_STANDARD=20 + cmake --build build --target all + cmake --build build --target test + ``` + + The precise version of GoogleTest that will be used is maintained in + `./lockfile.json`. + + ### Project specific configure arguments + + When configuring the project manually, + you can pass an array of project specific CMake configs to customize your build. + + Project specific options are prefixed with `BEMAN_CSTRING_VIEW`. + You can see the list of available options with: + + ```bash + cmake -LH | grep "BEMAN_CSTRING_VIEW" -C 2 + ``` + +
+ + Details of CMake arguments. + + #### `BEMAN_CSTRING_VIEW_BUILD_TESTS` + + Enable building tests and test infrastructure. Default: ON. + Values: { ON, OFF }. + + You can configure the project to have this option turned off via: + + ```bash + cmake -B build -S . -DCMAKE_CXX_STANDARD=20 -DBEMAN_CSTRING_VIEW_BUILD_TESTS=OFF + ``` + + > [!TIP] + > Because this project requires Google Tests as part of its development + > dependency, + > disable building tests avoids the project from pulling Google Tests from + > GitHub. + + #### `BEMAN_CSTRING_VIEW_BUILD_EXAMPLES` + + Enable building examples. Default: ON. Values: { ON, OFF }. + +
+ + ## Integrate beman.cstring_view into your project + + To use `beman.cstring_view` in your C++ project, + include an appropriate `beman.cstring_view` header from your source code. + + ```c++ + #include + ``` + + > [!NOTE] + > + > `beman.cstring_view` headers are to be included with the `beman/cstring_view/` directories prefixed. + > It is not supported to alter include search paths to spell the include target another way. For instance, + > `#include ` is not a supported interface. + + How you will link your project against `beman.cstring_view` will depend on your build system. + CMake instructions are provided in following sections. + + ### Linking your project to beman.cstring_view with CMake + + For CMake based projects, + you will need to use the `beman.cstring_view` CMake module + to define the `beman::cstring_view` CMake target: + + ```cmake + find_package(beman.cstring_view REQUIRED) + ``` + + You will also need to add `beman::cstring_view` to the link libraries of + any libraries or executables that include beman.cstring_view's header file. + + ```cmake + target_link_libraries(yourlib PUBLIC beman::cstring_view) + ``` + + ### Produce beman.cstring_view static library locally + + You can include cstring_view's headers locally + by producing a static `libbeman.cstring_view.a` library. + + ```bash + cmake --workflow --preset gcc-release + cmake --install build/gcc-release --prefix /opt/beman.cstring_view + ``` + + This will generate such directory structure at `/opt/beman.cstring_view`. + + ```txt + /opt/beman.cstring_view + ├── include + │ └── beman + │ └── cstring_view + │ └── cstring_view.hpp + └── lib + └── libbeman.cstring_view.a + ``` + + ## Contributing + + Please do! + You encourage you to checkout our [contributor's guide](docs/README.md). + Issues and pull requests are appreciated. diff --git a/include/beman/cstring_view/CMakeLists.txt b/include/beman/cstring_view/CMakeLists.txt new file mode 100644 index 0000000..9fb32d1 --- /dev/null +++ b/include/beman/cstring_view/CMakeLists.txt @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +target_sources( + beman.cstring_view + PUBLIC FILE_SET HEADERS FILES cstring_view.hpp +) diff --git a/include/beman/cstring_view/cstring_view.hpp b/include/beman/cstring_view/cstring_view.hpp index e71da41..c781a51 100644 --- a/include/beman/cstring_view/cstring_view.hpp +++ b/include/beman/cstring_view/cstring_view.hpp @@ -74,11 +74,11 @@ inline namespace cstring_view_literals { #pragma warning(disable : 4455) #endif // [cstring.view.literals], suffix for basic_cstring_view literals -constexpr cstring_view operator"" csv(const char* str, size_t len) noexcept; -constexpr u8cstring_view operator"" csv(const char8_t* str, size_t len) noexcept; -constexpr u16cstring_view operator"" csv(const char16_t* str, size_t len) noexcept; -constexpr u32cstring_view operator"" csv(const char32_t* str, size_t len) noexcept; -constexpr wcstring_view operator"" csv(const wchar_t* str, size_t len) noexcept; +constexpr cstring_view operator""csv(const char* str, size_t len) noexcept; +constexpr u8cstring_view operator""csv(const char8_t* str, size_t len) noexcept; +constexpr u16cstring_view operator""csv(const char16_t* str, size_t len) noexcept; +constexpr u32cstring_view operator""csv(const char32_t* str, size_t len) noexcept; +constexpr wcstring_view operator""csv(const wchar_t* str, size_t len) noexcept; #ifndef _MSC_VER #pragma GCC diagnostic pop #else diff --git a/infra/.beman_submodule b/infra/.beman_submodule index bfed167..28a0600 100644 --- a/infra/.beman_submodule +++ b/infra/.beman_submodule @@ -1,3 +1,3 @@ [beman_submodule] remote=https://github.com/bemanproject/infra.git -commit_hash=bb58b2a1cc894d58a55bf745be78f5d27029e245 +commit_hash=63cb577f6484f13ce3349de49ad5ce27e20bf1da diff --git a/infra/.github/workflows/beman-submodule.yml b/infra/.github/workflows/beman-submodule.yml deleted file mode 100644 index 8435086..0000000 --- a/infra/.github/workflows/beman-submodule.yml +++ /dev/null @@ -1,32 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -name: beman-submodule tests - -on: - push: - branches: - - main - pull_request: - workflow_dispatch: - -jobs: - beman-submodule-script-ci: - name: beman_module.py ci - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: 3.13 - - - name: Install pytest - run: | - python3 -m pip install pytest - - - name: Run pytest - run: | - cd tools/beman-submodule/ - pytest diff --git a/infra/.pre-commit-config.yaml b/infra/.pre-commit-config.yaml index e806e59..bc4dd84 100644 --- a/infra/.pre-commit-config.yaml +++ b/infra/.pre-commit-config.yaml @@ -19,14 +19,3 @@ repos: - id: gersemi name: CMake linting exclude: ^.*/tests/.*/data/ # Exclude test data directories - - # Python linting and formatting - # config file: ruff.toml (not currently present but add if needed) - # https://docs.astral.sh/ruff/configuration/ - - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.13.2 - hooks: - - id: ruff-check - files: ^tools/beman-tidy/ - - id: ruff-format - files: ^tools/beman-tidy/ diff --git a/infra/.pre-commit-hooks.yaml b/infra/.pre-commit-hooks.yaml deleted file mode 100644 index d327587..0000000 --- a/infra/.pre-commit-hooks.yaml +++ /dev/null @@ -1,7 +0,0 @@ -- id: beman-tidy - name: "beman-tidy: bemanification your repo" - entry: ./tools/beman-tidy/beman-tidy - language: script - pass_filenames: false - always_run: true - args: [".", "--verbose"] diff --git a/infra/README.md b/infra/README.md index 16b2672..a869e46 100644 --- a/infra/README.md +++ b/infra/README.md @@ -9,12 +9,11 @@ so it does not respect the usual structure of a Beman library repository nor The * `cmake/`: CMake modules and toolchain files used by Beman libraries. * `containers/`: Containers used for CI builds and tests in the Beman org. -* `tools/`: Tools used to manage the infrastructure and the codebase (e.g., linting, formatting, etc.). ## Usage This repository is intended to be used as a beman-submodule in other Beman repositories. See -[the Beman Submodule documentation](./tools/beman-submodule/README.md) for details. +[the beman-submodule documentation](https://github.com/bemanproject/beman-submodule) for details. ### CMake Modules diff --git a/infra/cmake/Config.cmake.in b/infra/cmake/Config.cmake.in new file mode 100644 index 0000000..81adf80 --- /dev/null +++ b/infra/cmake/Config.cmake.in @@ -0,0 +1,12 @@ +# cmake/Config.cmake.in -*-makefile-*- +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +include(CMakeFindDependencyMacro) + +@BEMAN_FIND_DEPENDENCIES@ + +@PACKAGE_INIT@ + +include(${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@-targets.cmake) + +check_required_components(@PROJECT_NAME@) diff --git a/infra/cmake/beman-install-library-config.cmake b/infra/cmake/beman-install-library-config.cmake deleted file mode 100644 index e7fd0ad..0000000 --- a/infra/cmake/beman-install-library-config.cmake +++ /dev/null @@ -1,169 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -include_guard(GLOBAL) - -# This file defines the function `beman_install_library` which is used to -# install a library target and its headers, along with optional CMake -# configuration files. -# -# The function is designed to be reusable across different Beman libraries. - -function(beman_install_library name) - # Usage - # ----- - # - # beman_install_library(NAME) - # - # Brief - # ----- - # - # This function installs the specified library target and its headers. - # It also handles the installation of the CMake configuration files if needed. - # - # CMake variables - # --------------- - # - # Note that configuration of the installation is generally controlled by CMake - # cache variables so that they can be controlled by the user or tool running the - # `cmake` command. Neither `CMakeLists.txt` nor `*.cmake` files should set these - # variables directly. - # - # - BEMAN_INSTALL_CONFIG_FILE_PACKAGES: - # List of packages that require config file installation. - # If the package name is in this list, it will install the config file. - # - # - _INSTALL_CONFIG_FILE_PACKAGE: - # Boolean to control config file installation for the specific library. - # The prefix `` is the uppercased name of the library with dots - # replaced by underscores. - # - if(NOT TARGET "${name}") - message(FATAL_ERROR "Target '${name}' does not exist.") - endif() - - if(NOT ARGN STREQUAL "") - message( - FATAL_ERROR - "beman_install_library does not accept extra arguments: ${ARGN}" - ) - endif() - - # Given foo.bar, the component name is bar - string(REPLACE "." ";" name_parts "${name}") - # fail if the name doesn't look like foo.bar - list(LENGTH name_parts name_parts_length) - if(NOT name_parts_length EQUAL 2) - message( - FATAL_ERROR - "beman_install_library expects a name of the form 'beman.', got '${name}'" - ) - endif() - - set(target_name "${name}") - set(install_component_name "${name}") - set(export_name "${name}") - set(package_name "${name}") - list(GET name_parts -1 component_name) - - install( - TARGETS "${target_name}" - COMPONENT "${install_component_name}" - EXPORT "${export_name}" - FILE_SET HEADERS - ) - - set_target_properties( - "${target_name}" - PROPERTIES EXPORT_NAME "${component_name}" - ) - - include(GNUInstallDirs) - - # Determine the prefix for project-specific variables - string(TOUPPER "${name}" project_prefix) - string(REPLACE "." "_" project_prefix "${project_prefix}") - - option( - ${project_prefix}_INSTALL_CONFIG_FILE_PACKAGE - "Enable building examples. Default: ${PROJECT_IS_TOP_LEVEL}. Values: { ON, OFF }." - ${PROJECT_IS_TOP_LEVEL} - ) - - # By default, install the config package - set(install_config_package ON) - - # Turn OFF installation of config package by default if, - # in order of precedence: - # 1. The specific package variable is set to OFF - # 2. The package name is not in the list of packages to install config files - if(DEFINED BEMAN_INSTALL_CONFIG_FILE_PACKAGES) - if( - NOT "${install_component_name}" - IN_LIST - BEMAN_INSTALL_CONFIG_FILE_PACKAGES - ) - set(install_config_package OFF) - endif() - endif() - if(DEFINED ${project_prefix}_INSTALL_CONFIG_FILE_PACKAGE) - set(install_config_package - ${${project_prefix}_INSTALL_CONFIG_FILE_PACKAGE} - ) - endif() - - if(install_config_package) - message( - DEBUG - "beman-install-library: Installing a config package for '${name}'" - ) - - include(CMakePackageConfigHelpers) - - find_file( - config_file_template - NAMES "${package_name}-config.cmake.in" - PATHS "${CMAKE_CURRENT_SOURCE_DIR}" - NO_DEFAULT_PATH - NO_CACHE - REQUIRED - ) - set(config_package_file - "${CMAKE_CURRENT_BINARY_DIR}/${package_name}-config.cmake" - ) - set(package_install_dir "${CMAKE_INSTALL_LIBDIR}/cmake/${package_name}") - configure_package_config_file( - "${config_file_template}" - "${config_package_file}" - INSTALL_DESTINATION "${package_install_dir}" - PATH_VARS PROJECT_NAME PROJECT_VERSION - ) - - set(config_version_file - "${CMAKE_CURRENT_BINARY_DIR}/${package_name}-config-version.cmake" - ) - write_basic_package_version_file( - "${config_version_file}" - VERSION "${PROJECT_VERSION}" - COMPATIBILITY ExactVersion - ) - - install( - FILES "${config_package_file}" "${config_version_file}" - DESTINATION "${package_install_dir}" - COMPONENT "${install_component_name}" - ) - - set(config_targets_file "${package_name}-targets.cmake") - install( - EXPORT "${export_name}" - DESTINATION "${package_install_dir}" - NAMESPACE beman:: - FILE "${config_targets_file}" - COMPONENT "${install_component_name}" - ) - else() - message( - DEBUG - "beman-install-library: Not installing a config package for '${name}'" - ) - endif() -endfunction() diff --git a/infra/cmake/beman-install-library.cmake b/infra/cmake/beman-install-library.cmake new file mode 100644 index 0000000..8a6c5a1 --- /dev/null +++ b/infra/cmake/beman-install-library.cmake @@ -0,0 +1,323 @@ +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +include_guard(GLOBAL) + +include(CMakePackageConfigHelpers) +include(GNUInstallDirs) + +# beman_install_library +# ===================== +# +# Installs a library (or set of targets) along with headers, C++ modules, +# and optional CMake package configuration files. +# +# Usage: +# ------ +# beman_install_library( +# TARGETS [ ...] +# [DEPENDENCIES [ ...]] +# [NAMESPACE ] +# [EXPORT_NAME ] +# [DESTINATION ] +# ) +# +# Arguments: +# ---------- +# +# name +# Logical package name (e.g. "beman.utility"). +# Used to derive config file names and cache variable prefixes. +# +# TARGETS (required) +# List of CMake targets to install. +# +# DEPENDENCIES (optional) +# Semicolon-separated list, one dependency per entry. +# Each entry is a valid find_dependency() argument list. +# Note: you must use the bracket form for quoting if not only a package name is used! +# "[===[beman.inplace_vector 1.0.0]===] [===[beman.scope 0.0.1 EXACT]===] fmt" +# +# NAMESPACE (optional) +# Namespace for exported targets. +# Defaults to "beman::". +# +# EXPORT_NAME (optional) +# Name of the CMake export set. +# Defaults to "-targets". +# +# DESTINATION (optional) +# The install destination for CXX_MODULES. +# Defaults to ${CMAKE_INSTALL_LIBDIR}/cmake/${name}/modules. +# +# Brief +# ----- +# +# This function installs the specified project TARGETS and its FILE_SET +# HEADERS to the default CMAKE install destination. +# +# It also handles the installation of the CMake config package files if +# needed. If the given targets has FILE_SET CXX_MODULE, it will also +# installed to the given DESTINATION +# +# Cache variables: +# ---------------- +# +# BEMAN_INSTALL_CONFIG_FILE_PACKAGES +# List of package names for which config files should be installed. +# +# _INSTALL_CONFIG_FILE_PACKAGE +# Per-package override to enable/disable config file installation. +# is the uppercased package name with dots replaced by underscores. +# +# Caveats +# ------- +# +# **Only one `FILE_SET CXX_MODULES` is yet supported to install with this +# function!** +# +# **Only header files contained in a `PUBLIC FILE_SET TYPE HEADERS` will be +# install with this function!** + +function(beman_install_library name) + # ---------------------------- + # Argument parsing + # ---------------------------- + set(oneValueArgs NAMESPACE EXPORT_NAME DESTINATION) + set(multiValueArgs TARGETS DEPENDENCIES) + + cmake_parse_arguments( + BEMAN + "${options}" + "${oneValueArgs}" + "${multiValueArgs}" + ${ARGN} + ) + + if(NOT BEMAN_TARGETS) + message( + FATAL_ERROR + "beman_install_library(${name}): TARGETS must be specified" + ) + endif() + + if(CMAKE_SKIP_INSTALL_RULES) + message( + WARNING + "beman_install_library(${name}): not installing targets '${BEMAN_TARGETS}' due to CMAKE_SKIP_INSTALL_RULES" + ) + return() + endif() + + set(_config_install_dir "${CMAKE_INSTALL_LIBDIR}/cmake/${name}") + + # ---------------------------- + # Defaults + # ---------------------------- + if(NOT BEMAN_NAMESPACE) + set(BEMAN_NAMESPACE "beman::") + endif() + + if(NOT BEMAN_EXPORT_NAME) + set(BEMAN_EXPORT_NAME "${name}-targets") + endif() + + if(NOT BEMAN_DESTINATION) + set(BEMAN_DESTINATION "${_config_install_dir}/modules") + endif() + + string(REPLACE "beman." "" install_component_name "${name}") + message( + VERBOSE + "beman-install-library(${name}): COMPONENT '${install_component_name}'" + ) + + # -------------------------------------------------- + # Install each target with all of its file sets + # -------------------------------------------------- + foreach(_tgt IN LISTS BEMAN_TARGETS) + if(NOT TARGET "${_tgt}") + message( + WARNING + "beman_install_library(${name}): '${_tgt}' is not a target" + ) + continue() + endif() + + # Given foo.bar, the component name is bar + string(REPLACE "." ";" name_parts "${_tgt}") + # fail if the name doesn't look like foo.bar + list(LENGTH name_parts name_parts_length) + if(NOT name_parts_length EQUAL 2) + message( + FATAL_ERROR + "beman_install_library(${name}): expects a name of the form 'beman.', got '${_tgt}'" + ) + endif() + list(GET name_parts -1 component_name) + set_target_properties( + "${_tgt}" + PROPERTIES EXPORT_NAME "${component_name}" + ) + message( + VERBOSE + "beman_install_library(${name}): EXPORT_NAME ${component_name} for TARGET '${_tgt}'" + ) + + # Get the list of interface header sets, exact one expected! + set(_install_header_set_args) + get_target_property( + _available_header_sets + ${_tgt} + INTERFACE_HEADER_SETS + ) + if(_available_header_sets) + message( + VERBOSE + "beman-install-library(${name}): '${_tgt}' has INTERFACE_HEADER_SETS=${_available_header_sets}" + ) + foreach(_install_header_set IN LISTS _available_header_sets) + list( + APPEND + _install_header_set_args + FILE_SET + "${_install_header_set}" + COMPONENT + "${install_component_name}_Development" + ) + endforeach() + else() + set(_install_header_set_args FILE_SET HEADERS) # Note: empty FILE_SET in this case! CK + endif() + + # Detect presence of C++ module file sets, exact one expected! + get_target_property(_module_sets "${_tgt}" CXX_MODULE_SETS) + if(_module_sets) + message( + VERBOSE + "beman-install-library(${name}): '${_tgt}' has CXX_MODULE_SETS=${_module_sets}" + ) + install( + TARGETS "${_tgt}" + EXPORT ${BEMAN_EXPORT_NAME} + ARCHIVE COMPONENT "${install_component_name}_Development" + LIBRARY + COMPONENT "${install_component_name}_Runtime" + NAMELINK_COMPONENT "${install_component_name}_Development" + RUNTIME COMPONENT "${install_component_name}_Runtime" + ${_install_header_set_args} + FILE_SET ${_module_sets} + DESTINATION "${BEMAN_DESTINATION}" + COMPONENT "${install_component_name}_Development" + # NOTE: There's currently no convention for this location! CK + CXX_MODULES_BMI + DESTINATION + ${_config_install_dir}/bmi-${CMAKE_CXX_COMPILER_ID}_$ + COMPONENT "${install_component_name}_Development" + ) + else() + install( + TARGETS "${_tgt}" + EXPORT ${BEMAN_EXPORT_NAME} + ARCHIVE COMPONENT "${install_component_name}_Development" + LIBRARY + COMPONENT "${install_component_name}_Runtime" + NAMELINK_COMPONENT "${install_component_name}_Development" + RUNTIME COMPONENT "${install_component_name}_Runtime" + ${_install_header_set_args} + ) + endif() + endforeach() + + # -------------------------------------------------- + # Export targets + # -------------------------------------------------- + # gersemi: off + install( + EXPORT ${BEMAN_EXPORT_NAME} + NAMESPACE ${BEMAN_NAMESPACE} + CXX_MODULES_DIRECTORY cxx-modules + DESTINATION ${_config_install_dir} + COMPONENT "${install_component_name}_Development" + ) + # gersemi: on + + # ---------------------------------------- + # Config file installation logic + # + # Precedence (highest to lowest): + # 1. Per-package variable _INSTALL_CONFIG_FILE_PACKAGE + # 2. Allow-list BEMAN_INSTALL_CONFIG_FILE_PACKAGES (if defined) + # 3. Default: ON + # ---------------------------------------- + string(TOUPPER "${name}" _pkg_upper) + string(REPLACE "." "_" _pkg_prefix "${_pkg_upper}") + + option( + ${_pkg_prefix}_INSTALL_CONFIG_FILE_PACKAGE + "Enable creating and installing a CMake config-file package. Default: ON. Values: { ON, OFF }." + ON + ) + + set(_pkg_var "${_pkg_prefix}_INSTALL_CONFIG_FILE_PACKAGE") + + # Default: install config files + set(_install_config ON) + + # If the allow-list is defined, only install for packages in the list + if(DEFINED BEMAN_INSTALL_CONFIG_FILE_PACKAGES) + if(NOT "${name}" IN_LIST BEMAN_INSTALL_CONFIG_FILE_PACKAGES) + set(_install_config OFF) + endif() + endif() + + # Per-package override takes highest precedence + if(DEFINED ${_pkg_var}) + set(_install_config ${${_pkg_var}}) + endif() + + # ---------------------------------------- + # expand dependencies + # ---------------------------------------- + set(_beman_find_deps "") + foreach(dep IN LISTS BEMAN_DEPENDENCIES) + message( + VERBOSE + "beman-install-library(${name}): Add find_dependency(${dep})" + ) + string(APPEND _beman_find_deps "find_dependency(${dep})\n") + endforeach() + set(BEMAN_FIND_DEPENDENCIES "${_beman_find_deps}") + + # ---------------------------------------- + # Generate + install config files + # ---------------------------------------- + if(_install_config) + configure_package_config_file( + "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Config.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/${name}-config.cmake" + INSTALL_DESTINATION ${_config_install_dir} + ) + + write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/${name}-config-version.cmake" + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMajorVersion + ) + + install( + FILES + "${CMAKE_CURRENT_BINARY_DIR}/${name}-config.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/${name}-config-version.cmake" + DESTINATION ${_config_install_dir} + COMPONENT "${install_component_name}_Development" + ) + else() + message( + WARNING + "beman-install-library(${name}): Not installing a config package for '${name}'" + ) + endif() +endfunction() + +set(CPACK_GENERATOR TGZ) +include(CPack) diff --git a/infra/tools/beman-submodule/README.md b/infra/tools/beman-submodule/README.md deleted file mode 100644 index 36883ad..0000000 --- a/infra/tools/beman-submodule/README.md +++ /dev/null @@ -1,63 +0,0 @@ -# beman-submodule - - - -## What is this script? - -`beman-submodule` provides some of the features of `git submodule`, adding child git -repositories to a parent git repository, but unlike with `git submodule`, the entire child -repo is directly checked in, so only maintainers, not users, need to run this script. The -command line interface mimics `git submodule`'s. - -## How do I add a beman submodule to my repository? - -The first beman submodule you should add is this repository, `infra/`, which you can -bootstrap by running: - - -```sh -curl -s https://raw.githubusercontent.com/bemanproject/infra/refs/heads/main/tools/beman-submodule/beman-submodule | python3 - add https://github.com/bemanproject/infra.git -``` - -Once that's added, you can run the script from `infra/tools/beman-submodule/beman-submodule`. - -## How do I update a beman submodule to the latest trunk? - -You can run `beman-submodule update --remote` to update all beman submodule to latest -trunk, or e.g. `beman-submodule update --remote infra` to update only a specific one. - -## How does it work under the hood? - -Along with the files from the child repository, it creates a dotfile called -`.beman_submodule`, which looks like this: - -```ini -[beman_submodule] -remote=https://github.com/bemanproject/infra.git -commit_hash=9b88395a86c4290794e503e94d8213b6c442ae77 -``` - -## How do I update a beman submodule to a specific commit or change the remote URL? - -You can edit the corresponding lines in the `.beman_submodule` file and run -`beman-submodule update` to update the state of the beman submodule to the new -`.beman_submodule` settings. - -## How can I make CI ensure that my beman submodules are in a valid state? - -Add this job to your CI workflow: - -```yaml - beman-submodule-test: - runs-on: ubuntu-latest - name: "Check beman submodules for consistency" - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: beman submodule consistency check - run: | - (set -o pipefail; ./infra/tools/beman-submodule/beman-submodule status | grep -qvF '+') -``` - -This will fail if the contents of any beman submodule don't match what's specified in the -`.beman_submodule` file. diff --git a/infra/tools/beman-submodule/beman-submodule b/infra/tools/beman-submodule/beman-submodule deleted file mode 100755 index 66cb96e..0000000 --- a/infra/tools/beman-submodule/beman-submodule +++ /dev/null @@ -1,260 +0,0 @@ -#!/usr/bin/env python3 - -# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -import argparse -import configparser -import filecmp -import glob -import os -import shutil -import subprocess -import sys -import tempfile -from pathlib import Path - - -def directory_compare( - reference: str | Path, actual: str | Path, ignore, allow_untracked_files: bool): - reference, actual = Path(reference), Path(actual) - - compared = filecmp.dircmp(reference, actual, ignore=ignore) - if (compared.left_only - or (compared.right_only and not allow_untracked_files) - or compared.diff_files): - return False - for common_dir in compared.common_dirs: - path1 = reference / common_dir - path2 = actual / common_dir - if not directory_compare(path1, path2, ignore, allow_untracked_files): - return False - return True - -class BemanSubmodule: - def __init__( - self, dirpath: str | Path, remote: str, commit_hash: str, - allow_untracked_files: bool): - self.dirpath = Path(dirpath) - self.remote = remote - self.commit_hash = commit_hash - self.allow_untracked_files = allow_untracked_files - -def parse_beman_submodule_file(path): - config = configparser.ConfigParser() - read_result = config.read(path) - def fail(): - raise Exception(f'Failed to parse {path} as a .beman_submodule file') - if not read_result: - fail() - if not 'beman_submodule' in config: - fail() - if not 'remote' in config['beman_submodule']: - fail() - if not 'commit_hash' in config['beman_submodule']: - fail() - allow_untracked_files = config.getboolean( - 'beman_submodule', 'allow_untracked_files', fallback=False) - return BemanSubmodule( - Path(path).resolve().parent, - config['beman_submodule']['remote'], - config['beman_submodule']['commit_hash'], - allow_untracked_files) - -def get_beman_submodule(path: str | Path): - beman_submodule_filepath = Path(path) / '.beman_submodule' - - if beman_submodule_filepath.is_file(): - return parse_beman_submodule_file(beman_submodule_filepath) - else: - return None - -def find_beman_submodules_in(path): - path = Path(path) - assert path.is_dir() - - result = [] - for dirpath, _, filenames in path.walk(): - if '.beman_submodule' in filenames: - result.append(parse_beman_submodule_file(dirpath / '.beman_submodule')) - return sorted(result, key=lambda module: module.dirpath) - -def cwd_git_repository_path(): - process = subprocess.run( - ['git', 'rev-parse', '--show-toplevel'], capture_output=True, text=True, - check=False) - if process.returncode == 0: - return process.stdout.strip() - elif "fatal: not a git repository" in process.stderr: - return None - else: - raise Exception("git rev-parse --show-toplevel failed") - -def clone_beman_submodule_into_tmpdir(beman_submodule, remote): - tmpdir = tempfile.TemporaryDirectory() - subprocess.run( - ['git', 'clone', beman_submodule.remote, tmpdir.name], capture_output=True, - check=True) - if not remote: - subprocess.run( - ['git', '-C', tmpdir.name, 'reset', '--hard', beman_submodule.commit_hash], - capture_output=True, check=True) - return tmpdir - -def get_paths(beman_submodule): - tmpdir = clone_beman_submodule_into_tmpdir(beman_submodule, False) - paths = set(glob.glob('*', root_dir=Path(tmpdir.name), include_hidden=True)) - paths.remove('.git') - return paths - -def beman_submodule_status(beman_submodule): - tmpdir = clone_beman_submodule_into_tmpdir(beman_submodule, False) - if directory_compare( - tmpdir.name, beman_submodule.dirpath, ['.beman_submodule', '.git'], - beman_submodule.allow_untracked_files): - status_character=' ' - else: - status_character='+' - parent_repo_path = cwd_git_repository_path() - if not parent_repo_path: - raise Exception('this is not a git repository') - relpath = Path(beman_submodule.dirpath).relative_to(Path(parent_repo_path)) - return status_character + ' ' + beman_submodule.commit_hash + ' ' + str(relpath) - -def beman_submodule_update(beman_submodule, remote): - tmpdir = clone_beman_submodule_into_tmpdir(beman_submodule, remote) - tmp_path = Path(tmpdir.name) - sha_process = subprocess.run( - ['git', 'rev-parse', 'HEAD'], capture_output=True, check=True, text=True, - cwd=tmp_path) - - if beman_submodule.allow_untracked_files: - for path in get_paths(beman_submodule): - path2 = Path(beman_submodule.dirpath) / path - if Path(path2).is_dir(): - shutil.rmtree(path2) - elif Path(path2).is_file(): - os.remove(path2) - else: - shutil.rmtree(beman_submodule.dirpath) - - submodule_path = tmp_path / '.beman_submodule' - with open(submodule_path, 'w') as f: - f.write('[beman_submodule]\n') - f.write(f'remote={beman_submodule.remote}\n') - f.write(f'commit_hash={sha_process.stdout.strip()}\n') - if beman_submodule.allow_untracked_files: - f.write(f'allow_untracked_files=True\n') - shutil.rmtree(tmp_path / '.git') - shutil.copytree(tmp_path, beman_submodule.dirpath, dirs_exist_ok=True) - -def update_command(remote, path): - if not path: - parent_repo_path = cwd_git_repository_path() - if not parent_repo_path: - raise Exception('this is not a git repository') - beman_submodules = find_beman_submodules_in(parent_repo_path) - else: - beman_submodule = get_beman_submodule(path) - if not beman_submodule: - raise Exception(f'{path} is not a beman_submodule') - beman_submodules = [beman_submodule] - for beman_submodule in beman_submodules: - beman_submodule_update(beman_submodule, remote) - -def add_command(repository, path, allow_untracked_files): - tmpdir = tempfile.TemporaryDirectory() - subprocess.run( - ['git', 'clone', repository], capture_output=True, check=True, cwd=tmpdir.name) - repository_name = os.listdir(tmpdir.name)[0] - if not path: - path = Path(repository_name) - else: - path = Path(path) - if not allow_untracked_files and path.exists(): - raise Exception(f'{path} exists') - path.mkdir(exist_ok=allow_untracked_files) - tmpdir_repo = Path(tmpdir.name) / repository_name - sha_process = subprocess.run( - ['git', 'rev-parse', 'HEAD'], capture_output=True, check=True, text=True, - cwd=tmpdir_repo) - with open(tmpdir_repo / '.beman_submodule', 'w') as f: - f.write('[beman_submodule]\n') - f.write(f'remote={repository}\n') - f.write(f'commit_hash={sha_process.stdout.strip()}\n') - if allow_untracked_files: - f.write(f'allow_untracked_files=True\n') - shutil.rmtree(tmpdir_repo /'.git') - shutil.copytree(tmpdir_repo, path, dirs_exist_ok=True) - -def status_command(paths): - if not paths: - parent_repo_path = cwd_git_repository_path() - if not parent_repo_path: - raise Exception('this is not a git repository') - beman_submodules = find_beman_submodules_in(parent_repo_path) - else: - beman_submodules = [] - for path in paths: - beman_submodule = get_beman_submodule(path) - if not beman_submodule: - raise Exception(f'{path} is not a beman_submodule') - beman_submodules.append(beman_submodule) - for beman_submodule in beman_submodules: - print(beman_submodule_status(beman_submodule)) - -def get_parser(): - parser = argparse.ArgumentParser(description='Beman pseudo-submodule tool') - subparsers = parser.add_subparsers(dest='command', help='available commands') - parser_update = subparsers.add_parser('update', help='update beman_submodules') - parser_update.add_argument( - '--remote', action='store_true', - help='update a beman_submodule to its latest from upstream') - parser_update.add_argument( - 'beman_submodule_path', nargs='?', - help='relative path to the beman_submodule to update') - parser_add = subparsers.add_parser('add', help='add a new beman_submodule') - parser_add.add_argument('repository', help='git repository to add') - parser_add.add_argument( - 'path', nargs='?', help='path where the repository will be added') - parser_add.add_argument( - '--allow-untracked-files', action='store_true', - help='the beman_submodule will not occupy the subdirectory exclusively') - parser_status = subparsers.add_parser( - 'status', help='show the status of beman_submodules') - parser_status.add_argument('paths', nargs='*') - return parser - -def parse_args(args): - return get_parser().parse_args(args); - -def usage(): - return get_parser().format_help() - -def run_command(args): - if args.command == 'update': - update_command(args.remote, args.beman_submodule_path) - elif args.command == 'add': - add_command(args.repository, args.path, args.allow_untracked_files) - elif args.command == 'status': - status_command(args.paths) - else: - raise Exception(usage()) - -def check_for_git(path): - env = os.environ.copy() - if path is not None: - env["PATH"] = path - return shutil.which("git", path=env.get("PATH")) is not None - -def main(): - try: - if not check_for_git(None): - raise Exception('git not found in PATH') - args = parse_args(sys.argv[1:]) - run_command(args) - except Exception as e: - print("Error:", e, file=sys.stderr) - sys.exit(1) - -if __name__ == '__main__': - main() diff --git a/infra/tools/beman-submodule/test/test_beman_submodule.py b/infra/tools/beman-submodule/test/test_beman_submodule.py deleted file mode 100644 index 600fc07..0000000 --- a/infra/tools/beman-submodule/test/test_beman_submodule.py +++ /dev/null @@ -1,539 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -import glob -import os -import pytest -import shutil -import stat -import subprocess -import tempfile -from pathlib import Path - -# https://stackoverflow.com/a/19011259 -import types -import importlib.machinery -loader = importlib.machinery.SourceFileLoader( - 'beman_submodule', - str(Path(__file__).parent.resolve().parent / 'beman-submodule')) -beman_submodule = types.ModuleType(loader.name) -loader.exec_module(beman_submodule) - -def create_test_git_repository(): - tmpdir = tempfile.TemporaryDirectory() - tmp_path = Path(tmpdir.name) - - subprocess.run(['git', 'init'], check=True, cwd=tmpdir.name, capture_output=True) - def make_commit(a_txt_contents): - with open(tmp_path / 'a.txt', 'w') as f: - f.write(a_txt_contents) - subprocess.run( - ['git', 'add', 'a.txt'], check=True, cwd=tmpdir.name, capture_output=True) - subprocess.run( - ['git', '-c', 'user.name=test', '-c', 'user.email=test@example.com', 'commit', - '--author="test "', '-m', 'test'], - check=True, cwd=tmpdir.name, capture_output=True) - make_commit('A') - make_commit('a') - return tmpdir - -def create_test_git_repository2(): - tmpdir = tempfile.TemporaryDirectory() - tmp_path = Path(tmpdir.name) - - subprocess.run(['git', 'init'], check=True, cwd=tmpdir.name, capture_output=True) - with open(tmp_path / 'a.txt', 'w') as f: - f.write('a') - subprocess.run( - ['git', 'add', 'a.txt'], check=True, cwd=tmpdir.name, capture_output=True) - subprocess.run( - ['git', '-c', 'user.name=test', '-c', 'user.email=test@example.com', 'commit', - '--author="test "', '-m', 'test'], - check=True, cwd=tmpdir.name, capture_output=True) - os.remove(tmp_path / 'a.txt') - subprocess.run( - ['git', 'rm', 'a.txt'], check=True, cwd=tmpdir.name, capture_output=True) - with open(tmp_path / 'b.txt', 'w') as f: - f.write('b') - subprocess.run( - ['git', 'add', 'b.txt'], check=True, cwd=tmpdir.name, capture_output=True) - subprocess.run( - ['git', '-c', 'user.name=test', '-c', 'user.email=test@example.com', 'commit', - '--author="test "', '-m', 'test'], - check=True, cwd=tmpdir.name, capture_output=True) - return tmpdir - -def test_directory_compare(): - def create_dir_structure(dir_path: Path): - bar_path = dir_path / 'bar' - os.makedirs(bar_path) - - with open(dir_path / 'foo.txt', 'w') as f: - f.write('foo') - with open(bar_path / 'baz.txt', 'w') as f: - f.write('baz') - - with tempfile.TemporaryDirectory() as dir_a, \ - tempfile.TemporaryDirectory() as dir_b: - path_a = Path(dir_a) - path_b = Path(dir_b) - - create_dir_structure(path_a) - create_dir_structure(path_b) - - assert beman_submodule.directory_compare(dir_a, dir_b, [], False) - - with open(path_a / 'bar' / 'quux.txt', 'w') as f: - f.write('quux') - - assert not beman_submodule.directory_compare(path_a, path_b, [], False) - assert beman_submodule.directory_compare(path_a, path_b, ['quux.txt'], False) - -def test_directory_compare_untracked_files(): - def create_dir_structure(dir_path: Path): - bar_path = dir_path / 'bar' - os.makedirs(bar_path) - - with open(dir_path / 'foo.txt', 'w') as f: - f.write('foo') - with open(bar_path / 'baz.txt', 'w') as f: - f.write('baz') - - with tempfile.TemporaryDirectory() as reference, \ - tempfile.TemporaryDirectory() as actual: - path_a = Path(reference) - path_b = Path(actual) - - create_dir_structure(path_a) - create_dir_structure(path_b) - (path_b / 'c.txt').touch() - - assert beman_submodule.directory_compare(reference, actual, [], True) - - with open(path_a / 'bar' / 'quux.txt', 'w') as f: - f.write('quux') - - assert not beman_submodule.directory_compare(path_a, path_b, [], True) - assert beman_submodule.directory_compare(path_a, path_b, ['quux.txt'], True) - -def test_parse_beman_submodule_file(): - def valid_file(): - tmpfile = tempfile.NamedTemporaryFile() - tmpfile.write('[beman_submodule]\n'.encode('utf-8')) - tmpfile.write( - 'remote=git@github.com:bemanproject/infra.git\n'.encode('utf-8')) - tmpfile.write( - 'commit_hash=9b88395a86c4290794e503e94d8213b6c442ae77\n'.encode('utf-8')) - tmpfile.flush() - module = beman_submodule.parse_beman_submodule_file(tmpfile.name) - assert module.dirpath == Path(tmpfile.name).resolve().parent - assert module.remote == 'git@github.com:bemanproject/infra.git' - assert module.commit_hash == '9b88395a86c4290794e503e94d8213b6c442ae77' - valid_file() - def invalid_file_missing_remote(): - threw = False - try: - tmpfile = tempfile.NamedTemporaryFile() - tmpfile.write('[beman_submodule]\n'.encode('utf-8')) - tmpfile.write( - 'commit_hash=9b88395a86c4290794e503e94d8213b6c442ae77\n'.encode('utf-8')) - tmpfile.flush() - beman_submodule.parse_beman_submodule_file(tmpfile.name) - except: - threw = True - assert threw - invalid_file_missing_remote() - def invalid_file_missing_commit_hash(): - threw = False - try: - tmpfile = tempfile.NamedTemporaryFile() - tmpfile.write('[beman_submodule]\n'.encode('utf-8')) - tmpfile.write( - 'remote=git@github.com:bemanproject/infra.git\n'.encode('utf-8')) - tmpfile.flush() - beman_submodule.parse_beman_submodule_file(tmpfile.name) - except: - threw = True - assert threw - invalid_file_missing_commit_hash() - def invalid_file_wrong_section(): - threw = False - try: - tmpfile = tempfile.NamedTemporaryFile() - tmpfile.write('[invalid]\n'.encode('utf-8')) - tmpfile.write( - 'remote=git@github.com:bemanproject/infra.git\n'.encode('utf-8')) - tmpfile.write( - 'commit_hash=9b88395a86c4290794e503e94d8213b6c442ae77\n'.encode('utf-8')) - tmpfile.flush() - beman_submodule.parse_beman_submodule_file(tmpfile.name) - except: - threw = True - assert threw - invalid_file_wrong_section() - -def test_get_beman_submodule(): - tmpdir = create_test_git_repository() - tmpdir2 = create_test_git_repository() - original_cwd = Path.cwd() - os.chdir(tmpdir2.name) - beman_submodule.add_command(tmpdir.name, 'foo', False) - assert beman_submodule.get_beman_submodule('foo') - os.remove('foo/.beman_submodule') - assert not beman_submodule.get_beman_submodule('foo') - os.chdir(original_cwd) - -def test_find_beman_submodules_in(): - tmpdir = create_test_git_repository() - tmpdir2 = create_test_git_repository() - original_cwd = Path.cwd() - os.chdir(tmpdir2.name) - beman_submodule.add_command(tmpdir.name, 'foo', False) - beman_submodule.add_command(tmpdir.name, 'bar', False) - beman_submodules = beman_submodule.find_beman_submodules_in(tmpdir2.name) - sha_process = subprocess.run( - ['git', 'rev-parse', 'HEAD'], capture_output=True, check=True, text=True, - cwd=tmpdir.name) - sha = sha_process.stdout.strip() - assert beman_submodules[0].dirpath == Path(tmpdir2.name) / 'bar' - assert beman_submodules[0].remote == tmpdir.name - assert beman_submodules[0].commit_hash == sha - assert beman_submodules[1].dirpath == Path(tmpdir2.name) / 'foo' - assert beman_submodules[1].remote == tmpdir.name - assert beman_submodules[1].commit_hash == sha - os.chdir(original_cwd) - -def test_cwd_git_repository_path(): - original_cwd = Path.cwd() - tmpdir = tempfile.TemporaryDirectory() - os.chdir(tmpdir.name) - assert not beman_submodule.cwd_git_repository_path() - subprocess.run(['git', 'init']) - assert beman_submodule.cwd_git_repository_path() == tmpdir.name - os.chdir(original_cwd) - -def test_clone_beman_submodule_into_tmpdir(): - tmpdir = create_test_git_repository() - tmpdir2 = create_test_git_repository() - original_cwd = Path.cwd() - os.chdir(tmpdir2.name) - sha_process = subprocess.run( - ['git', 'rev-parse', 'HEAD^'], capture_output=True, check=True, text=True, - cwd=tmpdir.name) - sha = sha_process.stdout.strip() - beman_submodule.add_command(tmpdir.name, 'foo', False) - module = beman_submodule.get_beman_submodule(Path(tmpdir2.name) / 'foo') - module.commit_hash = sha - tmpdir3 = beman_submodule.clone_beman_submodule_into_tmpdir(module, False) - assert not beman_submodule.directory_compare( - tmpdir.name, tmpdir3.name, ['.git'], False) - tmpdir4 = beman_submodule.clone_beman_submodule_into_tmpdir(module, True) - assert beman_submodule.directory_compare(tmpdir.name, tmpdir4.name, ['.git'], False) - subprocess.run( - ['git', 'reset', '--hard', sha], capture_output=True, check=True, - cwd=tmpdir.name) - assert beman_submodule.directory_compare(tmpdir.name, tmpdir3.name, ['.git'], False) - os.chdir(original_cwd) - -def test_get_paths(): - tmpdir = create_test_git_repository() - tmpdir2 = create_test_git_repository() - original_cwd = Path.cwd() - os.chdir(tmpdir2.name) - beman_submodule.add_command(tmpdir.name, 'foo', False) - module = beman_submodule.get_beman_submodule(Path(tmpdir2.name) / 'foo') - assert beman_submodule.get_paths(module) == set(['a.txt']) - os.chdir(original_cwd) - -def test_beman_submodule_status(): - tmpdir = create_test_git_repository() - tmpdir2 = create_test_git_repository() - original_cwd = Path.cwd() - os.chdir(tmpdir2.name) - beman_submodule.add_command(tmpdir.name, 'foo', False) - sha_process = subprocess.run( - ['git', 'rev-parse', 'HEAD'], capture_output=True, check=True, text=True, - cwd=tmpdir.name) - sha = sha_process.stdout.strip() - assert ' ' + sha + ' foo' == beman_submodule.beman_submodule_status( - beman_submodule.get_beman_submodule(Path(tmpdir2.name) / 'foo')) - with open(Path(tmpdir2.name) / 'foo' / 'a.txt', 'w') as f: - f.write('b') - assert '+ ' + sha + ' foo' == beman_submodule.beman_submodule_status( - beman_submodule.get_beman_submodule(Path(tmpdir2.name) / 'foo')) - os.chdir(original_cwd) - -def test_update_command_no_paths(): - tmpdir = create_test_git_repository() - tmpdir2 = create_test_git_repository() - original_cwd = Path.cwd() - os.chdir(tmpdir2.name) - orig_sha_process = subprocess.run( - ['git', 'rev-parse', 'HEAD'], capture_output=True, check=True, text=True, - cwd=tmpdir.name) - orig_sha = orig_sha_process.stdout.strip() - parent_sha_process = subprocess.run( - ['git', 'rev-parse', 'HEAD^'], capture_output=True, check=True, text=True, - cwd=tmpdir.name) - parent_sha = parent_sha_process.stdout.strip() - parent_parent_sha_process = subprocess.run( - ['git', 'rev-parse', 'HEAD^'], capture_output=True, check=True, text=True, - cwd=tmpdir.name) - parent_parent_sha = parent_parent_sha_process.stdout.strip() - subprocess.run( - ['git', 'reset', '--hard', parent_parent_sha], capture_output=True, check=True, - cwd=tmpdir.name) - beman_submodule.add_command(tmpdir.name, 'foo', False) - beman_submodule.add_command(tmpdir.name, 'bar', False) - subprocess.run( - ['git', 'reset', '--hard', orig_sha], capture_output=True, check=True, - cwd=tmpdir.name) - with open(Path(tmpdir2.name) / 'foo' / '.beman_submodule', 'w') as f: - f.write(f'[beman_submodule]\nremote={tmpdir.name}\ncommit_hash={parent_sha}\n') - with open(Path(tmpdir2.name) / 'bar' / '.beman_submodule', 'w') as f: - f.write(f'[beman_submodule]\nremote={tmpdir.name}\ncommit_hash={parent_sha}\n') - beman_submodule.update_command(False, None) - with open(Path(tmpdir2.name) / 'foo' / '.beman_submodule', 'r') as f: - assert f.read() == f'[beman_submodule]\nremote={tmpdir.name}\ncommit_hash={parent_sha}\n' - with open(Path(tmpdir2.name) / 'bar' / '.beman_submodule', 'r') as f: - assert f.read() == f'[beman_submodule]\nremote={tmpdir.name}\ncommit_hash={parent_sha}\n' - subprocess.run( - ['git', 'reset', '--hard', parent_sha], capture_output=True, check=True, - cwd=tmpdir.name) - assert beman_submodule.directory_compare( - tmpdir.name, Path(tmpdir2.name) / 'foo', ['.git', '.beman_submodule'], False) - assert beman_submodule.directory_compare( - tmpdir.name, Path(tmpdir2.name) / 'bar', ['.git', '.beman_submodule'], False) - subprocess.run( - ['git', 'reset', '--hard', orig_sha], capture_output=True, check=True, - cwd=tmpdir.name) - beman_submodule.update_command(True, None) - with open(Path(tmpdir2.name) / 'foo' / '.beman_submodule', 'r') as f: - assert f.read() == f'[beman_submodule]\nremote={tmpdir.name}\ncommit_hash={orig_sha}\n' - with open(Path(tmpdir2.name) / 'bar' / '.beman_submodule', 'r') as f: - assert f.read() == f'[beman_submodule]\nremote={tmpdir.name}\ncommit_hash={orig_sha}\n' - assert beman_submodule.directory_compare( - tmpdir.name, Path(tmpdir2.name) / 'foo', ['.git', '.beman_submodule'], False) - assert beman_submodule.directory_compare( - tmpdir.name, Path(tmpdir2.name) / 'bar', ['.git', '.beman_submodule'], False) - os.chdir(original_cwd) - -def test_update_command_with_path(): - tmpdir = create_test_git_repository() - tmpdir2 = create_test_git_repository() - original_cwd = Path.cwd() - os.chdir(tmpdir2.name) - orig_sha_process = subprocess.run( - ['git', 'rev-parse', 'HEAD'], capture_output=True, check=True, text=True, - cwd=tmpdir.name) - orig_sha = orig_sha_process.stdout.strip() - parent_sha_process = subprocess.run( - ['git', 'rev-parse', 'HEAD^'], capture_output=True, check=True, text=True, - cwd=tmpdir.name) - parent_sha = parent_sha_process.stdout.strip() - parent_parent_sha_process = subprocess.run( - ['git', 'rev-parse', 'HEAD^'], capture_output=True, check=True, text=True, - cwd=tmpdir.name) - parent_parent_sha = parent_parent_sha_process.stdout.strip() - subprocess.run( - ['git', 'reset', '--hard', parent_parent_sha], capture_output=True, check=True, - cwd=tmpdir.name) - tmpdir_parent_parent_copy = tempfile.TemporaryDirectory() - shutil.copytree(tmpdir.name, tmpdir_parent_parent_copy.name, dirs_exist_ok=True) - beman_submodule.add_command(tmpdir.name, 'foo', False) - beman_submodule.add_command(tmpdir.name, 'bar', False) - subprocess.run( - ['git', 'reset', '--hard', orig_sha], capture_output=True, check=True, - cwd=tmpdir.name) - with open(Path(tmpdir2.name) / 'foo' / '.beman_submodule', 'w') as f: - f.write(f'[beman_submodule]\nremote={tmpdir.name}\ncommit_hash={parent_sha}\n') - with open(Path(tmpdir2.name) / 'bar' / '.beman_submodule', 'w') as f: - f.write(f'[beman_submodule]\nremote={tmpdir.name}\ncommit_hash={parent_sha}\n') - beman_submodule.update_command(False, 'foo') - with open(Path(tmpdir2.name) / 'foo' / '.beman_submodule', 'r') as f: - assert f.read() == f'[beman_submodule]\nremote={tmpdir.name}\ncommit_hash={parent_sha}\n' - with open(Path(tmpdir2.name) / 'bar' / '.beman_submodule', 'r') as f: - assert f.read() == f'[beman_submodule]\nremote={tmpdir.name}\ncommit_hash={parent_sha}\n' - subprocess.run( - ['git', 'reset', '--hard', parent_sha], capture_output=True, check=True, - cwd=tmpdir.name) - assert beman_submodule.directory_compare( - tmpdir.name, Path(tmpdir2.name) / 'foo', ['.git', '.beman_submodule'], False) - assert beman_submodule.directory_compare( - tmpdir_parent_parent_copy.name, - Path(tmpdir2.name) / 'bar', ['.git', '.beman_submodule'], False) - subprocess.run( - ['git', 'reset', '--hard', orig_sha], capture_output=True, check=True, - cwd=tmpdir.name) - beman_submodule.update_command(True, 'foo') - with open(Path(tmpdir2.name) / 'foo' / '.beman_submodule', 'r') as f: - assert f.read() == f'[beman_submodule]\nremote={tmpdir.name}\ncommit_hash={orig_sha}\n' - with open(Path(tmpdir2.name) / 'bar' / '.beman_submodule', 'r') as f: - assert f.read() == f'[beman_submodule]\nremote={tmpdir.name}\ncommit_hash={parent_sha}\n' - assert beman_submodule.directory_compare( - tmpdir.name, Path(tmpdir2.name) / 'foo', ['.git', '.beman_submodule'], False) - assert beman_submodule.directory_compare( - tmpdir_parent_parent_copy.name, - Path(tmpdir2.name) / 'bar', ['.git', '.beman_submodule'], False) - os.chdir(original_cwd) - -def test_update_command_untracked_files(): - tmpdir = create_test_git_repository2() - tmpdir2 = create_test_git_repository() - original_cwd = Path.cwd(); - os.chdir(tmpdir2.name) - orig_sha_process = subprocess.run( - ['git', 'rev-parse', 'HEAD'], capture_output=True, check=True, text=True, - cwd=tmpdir.name) - orig_sha = orig_sha_process.stdout.strip() - parent_sha_process = subprocess.run( - ['git', 'rev-parse', 'HEAD^'], capture_output=True, check=True, text=True, - cwd=tmpdir.name) - parent_sha = parent_sha_process.stdout.strip() - os.makedirs(Path(tmpdir2.name) / 'foo') - (Path(tmpdir2.name) / 'foo' / 'c.txt').touch() - with open(Path(tmpdir2.name) / 'foo' / '.beman_submodule', 'w') as f: - f.write(f'[beman_submodule]\nremote={tmpdir.name}\ncommit_hash={parent_sha}\nallow_untracked_files=True') - beman_submodule.update_command(False, 'foo') - assert set(['./foo/a.txt', './foo/c.txt']) == set(glob.glob('./foo/*.txt')) - beman_submodule.update_command(True, 'foo') - assert set(['./foo/b.txt', './foo/c.txt']) == set(glob.glob('./foo/*.txt')) - os.chdir(original_cwd) - -def test_add_command(): - tmpdir = create_test_git_repository() - tmpdir2 = create_test_git_repository() - original_cwd = Path.cwd() - os.chdir(tmpdir2.name) - beman_submodule.add_command(tmpdir.name, 'foo', False) - sha_process = subprocess.run( - ['git', 'rev-parse', 'HEAD'], capture_output=True, check=True, text=True, - cwd=tmpdir.name) - sha = sha_process.stdout.strip() - assert beman_submodule.directory_compare( - tmpdir.name, Path(tmpdir2.name) / 'foo', ['.git', '.beman_submodule'], False) - with open(Path(tmpdir2.name) / 'foo' / '.beman_submodule', 'r') as f: - assert f.read() == f'[beman_submodule]\nremote={tmpdir.name}\ncommit_hash={sha}\n' - os.chdir(original_cwd) - -def test_add_command_untracked_files(): - tmpdir = create_test_git_repository() - tmpdir2 = create_test_git_repository() - original_cwd = Path.cwd() - os.chdir(tmpdir2.name) - os.makedirs(Path(tmpdir2.name) / 'foo') - (Path(tmpdir2.name) / 'foo' / 'c.txt').touch() - beman_submodule.add_command(tmpdir.name, 'foo', True) - assert set(['./foo/a.txt', './foo/c.txt']) == set(glob.glob('./foo/*.txt')) - os.chdir(original_cwd) - -def test_status_command_no_paths(capsys): - tmpdir = create_test_git_repository() - tmpdir2 = create_test_git_repository() - original_cwd = Path.cwd() - os.chdir(tmpdir2.name) - beman_submodule.add_command(tmpdir.name, 'foo', False) - beman_submodule.add_command(tmpdir.name, 'bar', False) - sha_process = subprocess.run( - ['git', 'rev-parse', 'HEAD'], capture_output=True, check=True, text=True, - cwd=tmpdir.name) - with open(Path(tmpdir2.name) / 'bar' / 'a.txt', 'w') as f: - f.write('b') - beman_submodule.status_command([]) - sha = sha_process.stdout.strip() - assert capsys.readouterr().out == '+ ' + sha + ' bar\n' + ' ' + sha + ' foo\n' - os.chdir(original_cwd) - -def test_status_command_with_path(capsys): - tmpdir = create_test_git_repository() - tmpdir2 = create_test_git_repository() - original_cwd = Path.cwd() - os.chdir(tmpdir2.name) - beman_submodule.add_command(tmpdir.name, 'foo', False) - beman_submodule.add_command(tmpdir.name, 'bar', False) - sha_process = subprocess.run( - ['git', 'rev-parse', 'HEAD'], capture_output=True, check=True, text=True, - cwd=tmpdir.name) - with open(Path(tmpdir2.name) / 'bar' / 'a.txt', 'w') as f: - f.write('b') - beman_submodule.status_command(['bar']) - sha = sha_process.stdout.strip() - assert capsys.readouterr().out == '+ ' + sha + ' bar\n' - os.chdir(original_cwd) - -def test_status_command_untracked_files(capsys): - tmpdir = create_test_git_repository() - tmpdir2 = create_test_git_repository() - original_cwd = Path.cwd() - os.chdir(tmpdir2.name) - beman_submodule.add_command(tmpdir.name, 'foo', True) - sha_process = subprocess.run( - ['git', 'rev-parse', 'HEAD'], capture_output=True, check=True, text=True, - cwd=tmpdir.name) - (Path(tmpdir2.name) / 'foo' / 'c.txt').touch() - beman_submodule.status_command(['foo']) - sha = sha_process.stdout.strip() - assert capsys.readouterr().out == ' ' + sha + ' foo\n' - os.chdir(original_cwd) - -def test_check_for_git(): - tmpdir = tempfile.TemporaryDirectory() - assert not beman_submodule.check_for_git(tmpdir.name) - fake_git_path = Path(tmpdir.name) / 'git' - with open(fake_git_path, 'w'): - pass - os.chmod(fake_git_path, stat.S_IRWXU) - assert beman_submodule.check_for_git(tmpdir.name) - -def test_parse_args(): - def plain_update(): - args = beman_submodule.parse_args(['update']) - assert args.command == 'update' - assert not args.remote - assert not args.beman_submodule_path - plain_update() - def update_remote(): - args = beman_submodule.parse_args(['update', '--remote']) - assert args.command == 'update' - assert args.remote - assert not args.beman_submodule_path - update_remote() - def update_path(): - args = beman_submodule.parse_args(['update', 'infra/']) - assert args.command == 'update' - assert not args.remote - assert args.beman_submodule_path == 'infra/' - update_path() - def update_path_remote(): - args = beman_submodule.parse_args(['update', '--remote', 'infra/']) - assert args.command == 'update' - assert args.remote - assert args.beman_submodule_path == 'infra/' - update_path_remote() - def plain_add(): - args = beman_submodule.parse_args(['add', 'git@github.com:bemanproject/infra.git']) - assert args.command == 'add' - assert args.repository == 'git@github.com:bemanproject/infra.git' - assert not args.path - plain_add() - def add_path(): - args = beman_submodule.parse_args( - ['add', 'git@github.com:bemanproject/infra.git', 'infra/']) - assert args.command == 'add' - assert args.repository == 'git@github.com:bemanproject/infra.git' - assert args.path == 'infra/' - add_path() - def plain_status(): - args = beman_submodule.parse_args(['status']) - assert args.command == 'status' - assert args.paths == [] - plain_status() - def status_one_module(): - args = beman_submodule.parse_args(['status', 'infra/']) - assert args.command == 'status' - assert args.paths == ['infra/'] - status_one_module() - def status_multiple_modules(): - args = beman_submodule.parse_args(['status', 'infra/', 'foobar/']) - assert args.command == 'status' - assert args.paths == ['infra/', 'foobar/'] - status_multiple_modules() diff --git a/src/beman/cstring_view/CMakeLists.txt b/src/beman/cstring_view/CMakeLists.txt deleted file mode 100644 index 350759f..0000000 --- a/src/beman/cstring_view/CMakeLists.txt +++ /dev/null @@ -1,64 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -include(GNUInstallDirs) - -add_library(beman.cstring_view) -add_library(beman::cstring_view ALIAS beman.cstring_view) - -target_sources(beman.cstring_view PRIVATE cstring_view.cpp) - -target_sources( - beman.cstring_view - PUBLIC - FILE_SET HEADERS - BASE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/../../../include - FILES - ${CMAKE_CURRENT_SOURCE_DIR}/../../../include/beman/cstring_view/cstring_view.hpp -) - -set_target_properties( - beman.cstring_view - PROPERTIES VERIFY_INTERFACE_HEADER_SETS ON EXPORT_NAME cstring_view -) - -install( - TARGETS beman.cstring_view - COMPONENT beman.cstring_view - EXPORT beman.cstring_view - DESTINATION ${CMAKE_INSTALL_LIBDIR}$<$:/debug> - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}$<$:/debug> - FILE_SET HEADERS DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} -) - -if(BEMAN_CSTRING_VIEW_INSTALL_CONFIG_FILE_PACKAGE) - include(CMakePackageConfigHelpers) - - configure_package_config_file( - "${CMAKE_CURRENT_SOURCE_DIR}/beman.cstring_view-config.cmake.in" - "${CMAKE_CURRENT_BINARY_DIR}/beman.cstring_view-config.cmake" - INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/beman.cstring_view" - PATH_VARS PROJECT_NAME PROJECT_VERSION - ) - - write_basic_package_version_file( - "${CMAKE_CURRENT_BINARY_DIR}/beman.cstring_view-config-version.cmake" - VERSION ${PROJECT_VERSION} - COMPATIBILITY ExactVersion - ) - - install( - FILES - "${CMAKE_CURRENT_BINARY_DIR}/beman.cstring_view-config.cmake" - "${CMAKE_CURRENT_BINARY_DIR}/beman.cstring_view-config-version.cmake" - DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/beman.cstring_view" - COMPONENT beman.cstring_view - ) - - install( - EXPORT beman.cstring_view - DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/beman.cstring_view" - NAMESPACE beman:: - FILE beman.cstring_view-targets.cmake - COMPONENT beman.cstring_view - ) -endif() diff --git a/src/beman/cstring_view/beman.cstring_view-config.cmake.in b/src/beman/cstring_view/beman.cstring_view-config.cmake.in deleted file mode 100644 index 709a6f8..0000000 --- a/src/beman/cstring_view/beman.cstring_view-config.cmake.in +++ /dev/null @@ -1,7 +0,0 @@ -set(BEMAN_CSTRING_VIEW_VERSION @PROJECT_VERSION@) - -@PACKAGE_INIT@ - -include(${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@-targets.cmake) - -check_required_components(@PROJECT_NAME@) diff --git a/src/beman/cstring_view/cstring_view.cpp b/src/beman/cstring_view/cstring_view.cpp deleted file mode 100644 index 0d608d5..0000000 --- a/src/beman/cstring_view/cstring_view.cpp +++ /dev/null @@ -1 +0,0 @@ -#include