├── src ├── rootshell.sh ├── backtrace.hpp ├── backtrace.cpp ├── sysctl_option.hpp ├── terminal-helper ├── utils.hpp ├── utils.cpp ├── sm-window.hpp ├── sm-window.ui ├── sysctl_option.cpp ├── main.cpp └── sm-window.cpp ├── cachyos-sysctl-manager.png ├── subprojects ├── fmt.wrap └── packagefiles │ └── fmt │ └── meson.build ├── scripts ├── format_file.sh └── auto-changelog.sh ├── .github └── workflows │ ├── labeler.yml │ ├── build.yml │ └── checks.yml ├── compile_flags.txt ├── cachyos-sysctl-manager.desktop ├── cmake ├── EnableCcache.cmake ├── Linker.cmake ├── StaticAnalyzers.cmake ├── StandardProjectSettings.cmake ├── Sanitizers.cmake ├── CompilerWarnings.cmake └── CPM.cmake ├── .clang-format ├── .gitignore ├── org.cachyos.cachyos-sysctl-manager.pkexec.policy ├── README.md ├── .clang-tidy ├── CMakeLists.txt ├── meson.build ├── configure.sh └── LICENSE /src/rootshell.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec bash $@ 4 | -------------------------------------------------------------------------------- /cachyos-sysctl-manager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CachyOS/sysctl-manager/HEAD/cachyos-sysctl-manager.png -------------------------------------------------------------------------------- /subprojects/fmt.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | url = https://github.com/fmtlib/fmt.git 3 | revision = 11.1.1 4 | 5 | patch_directory = fmt 6 | 7 | [provide] 8 | fmt = fmt_dep 9 | -------------------------------------------------------------------------------- /scripts/format_file.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | 3 | set -eu 4 | 5 | # go to the git root dir 6 | cd "$(git rev-parse --show-toplevel)" 7 | 8 | clang-format -i src/*.{cpp,hpp} 9 | -------------------------------------------------------------------------------- /src/backtrace.hpp: -------------------------------------------------------------------------------- 1 | #ifndef BACKTRACE_HPP 2 | #define BACKTRACE_HPP 3 | 4 | namespace manager::backtrace { 5 | 6 | void print_trace() noexcept; 7 | 8 | } // namespace manager::backtrace 9 | 10 | #endif // BACKTRACE_HPP 11 | -------------------------------------------------------------------------------- /.github/workflows/labeler.yml: -------------------------------------------------------------------------------- 1 | name: "PR Labeler" 2 | 3 | on: 4 | - pull_request_target 5 | 6 | jobs: 7 | triage: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/labeler@v4 11 | with: 12 | repo-token: "${{ secrets.GITHUB_TOKEN }}" 13 | -------------------------------------------------------------------------------- /compile_flags.txt: -------------------------------------------------------------------------------- 1 | -std=gnu++2a 2 | -DQT_CORE_LIB 3 | -DQT_GUI_LIB 4 | -DQT_NETWORK_LIB 5 | -DQT_WIDGETS_LIB 6 | -I/usr/include/qt 7 | -I/usr/include/qt/QtWidgets 8 | -I/usr/include/qt/QtGui 9 | -I/usr/include/qt/QtCore 10 | -I/usr/include/qt/QtNetwork 11 | -Iinclude 12 | -xc++ 13 | -------------------------------------------------------------------------------- /cachyos-sysctl-manager.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Type=Application 3 | Version=1.0 4 | Name=CachyOS Sysctl Manager 5 | GenericName=Sysctl Manager 6 | Keywords=cachyos;system;kernel;sysctl; 7 | Exec=cachyos-sysctl-manager 8 | Comment=Manage sysctl options 9 | Icon=cachyos-sysctl-manager 10 | Terminal=false 11 | StartupNotify=false 12 | Categories=Qt;System; 13 | X-AppStream-Ignore=true 14 | -------------------------------------------------------------------------------- /cmake/EnableCcache.cmake: -------------------------------------------------------------------------------- 1 | # Setup ccache. 2 | # 3 | # The ccache is auto-enabled if the tool is found. 4 | # To disable set -DCCACHE=OFF option. 5 | if(NOT DEFINED CMAKE_CXX_COMPILER_LAUNCHER) 6 | find_program(CCACHE ccache DOC "ccache tool path; set to OFF to disable") 7 | if(CCACHE) 8 | set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE}) 9 | message(STATUS "[ccache] Enabled: ${CCACHE}") 10 | else() 11 | message(STATUS "[ccache] Disabled.") 12 | endif() 13 | endif() 14 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: WebKit 2 | AccessModifierOffset: -3 3 | AllowShortLoopsOnASingleLine: true 4 | AlignConsecutiveAssignments: true 5 | AlignConsecutiveMacros: true 6 | AlignTrailingComments: true 7 | BreakBeforeBraces: Custom 8 | BreakConstructorInitializers: BeforeColon 9 | BreakStringLiterals: false 10 | ConstructorInitializerIndentWidth: 2 11 | Cpp11BracedListStyle: true 12 | PointerAlignment: Left 13 | FixNamespaceComments: true 14 | SpaceBeforeCpp11BracedList: false 15 | SpacesBeforeTrailingComments: 2 16 | Standard: c++17 17 | TabWidth: 4 18 | UseTab: Never 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | *.dump 3 | *.ctu-info 4 | .cache 5 | .idea 6 | build 7 | build.sh 8 | cmake-build-release 9 | cmake-build-debug 10 | CMakeLists.txt.user 11 | subprojects/fmt 12 | subprojects/packagecache 13 | 14 | # Prerequisites 15 | *.d 16 | 17 | # Compiled Object files 18 | *.slo 19 | *.lo 20 | *.o 21 | *.obj 22 | 23 | # Precompiled Headers 24 | *.gch 25 | *.pch 26 | 27 | # Compiled Dynamic libraries 28 | *.so 29 | *.dylib 30 | 31 | # Fortran module files 32 | *.mod 33 | *.smod 34 | 35 | # Compiled Static libraries 36 | *.lai 37 | *.la 38 | *.a 39 | 40 | # Executables 41 | *.exe 42 | *.out 43 | *.app 44 | -------------------------------------------------------------------------------- /src/backtrace.cpp: -------------------------------------------------------------------------------- 1 | #include "backtrace.hpp" 2 | 3 | #if defined(__clang__) 4 | #pragma clang diagnostic push 5 | #pragma clang diagnostic ignored "-Wunqualified-std-cast-call" 6 | #endif 7 | 8 | #define BACKWARD_HAS_BFD 1 9 | #include "backward.hpp" 10 | 11 | #if defined(__clang__) 12 | #pragma clang diagnostic pop 13 | #endif 14 | 15 | #include 16 | 17 | namespace manager::backtrace { 18 | 19 | void print_trace() noexcept { 20 | // Load trace 21 | backward::StackTrace st; 22 | st.load_here(100); 23 | 24 | // Print trace 25 | backward::Printer p; 26 | p.print(st); 27 | 28 | std::abort(); 29 | } 30 | 31 | } // namespace manager::backtrace 32 | -------------------------------------------------------------------------------- /org.cachyos.cachyos-sysctl-manager.pkexec.policy: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | cachyos-sysctl-manager 7 | https://github.com/CachyOS/sysctl-manager 8 | 9 | 10 | Apply sysctl options 11 | Authentication is required to apply options 12 | cachyos-sysctl-manager 13 | 14 | no 15 | no 16 | auth_admin 17 | 18 | /usr/lib/cachyos-sysctl-manager/rootshell.sh 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /subprojects/packagefiles/fmt/meson.build: -------------------------------------------------------------------------------- 1 | project('fmt', 'cpp', 2 | version : '10.0.0', 3 | license : 'BSD', 4 | default_options : [ 5 | 'cpp_std=c++20', 6 | 'default_library=static', 7 | ] 8 | ) 9 | 10 | fmt_private_cpp_args = [ ] 11 | fmt_interface_cpp_args = [ ] 12 | libtype = get_option('default_library') 13 | if libtype == 'shared' 14 | fmt_private_cpp_args += [ '-DFMT_EXPORT' ] 15 | fmt_interface_cpp_args += [ '-DFMT_SHARED' ] 16 | endif 17 | 18 | fmt_inc = include_directories('include') 19 | fmt_lib = library('fmt', 20 | sources : [ 21 | 'src/format.cc', 22 | 'src/os.cc' 23 | ], 24 | cpp_args : fmt_private_cpp_args, 25 | include_directories : fmt_inc 26 | ) 27 | 28 | fmt_dep = declare_dependency( 29 | include_directories : fmt_inc, 30 | compile_args : fmt_interface_cpp_args, 31 | link_with : fmt_lib 32 | ) 33 | 34 | fmt_header_only_dep = declare_dependency( 35 | include_directories : fmt_inc, 36 | compile_args : '-DFMT_HEADER_ONLY' 37 | ) 38 | -------------------------------------------------------------------------------- /cmake/Linker.cmake: -------------------------------------------------------------------------------- 1 | option(ENABLE_USER_LINKER "Enable a specific linker if available" OFF) 2 | 3 | include(CheckCXXCompilerFlag) 4 | 5 | set(USER_LINKER_OPTION 6 | "lld" 7 | CACHE STRING "Linker to be used") 8 | set(USER_LINKER_OPTION_VALUES "lld" "gold" "bfd" "mold") 9 | set_property(CACHE USER_LINKER_OPTION PROPERTY STRINGS ${USER_LINKER_OPTION_VALUES}) 10 | list( 11 | FIND 12 | USER_LINKER_OPTION_VALUES 13 | ${USER_LINKER_OPTION} 14 | USER_LINKER_OPTION_INDEX) 15 | 16 | if(${USER_LINKER_OPTION_INDEX} EQUAL -1) 17 | message( 18 | STATUS 19 | "Using custom linker: '${USER_LINKER_OPTION}', explicitly supported entries are ${USER_LINKER_OPTION_VALUES}") 20 | endif() 21 | 22 | function(configure_linker project_name) 23 | if(NOT ENABLE_USER_LINKER) 24 | return() 25 | endif() 26 | 27 | set(LINKER_FLAG "-fuse-ld=${USER_LINKER_OPTION}") 28 | 29 | check_cxx_compiler_flag(${LINKER_FLAG} CXX_SUPPORTS_USER_LINKER) 30 | if(CXX_SUPPORTS_USER_LINKER) 31 | target_compile_options(${project_name} INTERFACE ${LINKER_FLAG}) 32 | endif() 33 | endfunction() 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sysctl-manager 2 | Manage linux kernel options via sysctl with simple GUI. 3 | 4 | Requirements 5 | ------------ 6 | * C++23 feature required (tested with GCC 14.1.1 and Clang 18) 7 | Any compiler which support C++23 standard should work. 8 | 9 | ###### 10 | ## Installing from source 11 | 12 | This is tested on Arch Linux, but *any* recent Arch Linux based system with latest C++23 compiler should do: 13 | 14 | ```sh 15 | sudo pacman -Sy \ 16 | base-devel cmake pkg-config make qt6-base procps-ng 17 | ``` 18 | 19 | ### Cloning the source code 20 | ```sh 21 | git clone https://github.com/cachyos/sysctl-manager.git 22 | cd sysctl-manager 23 | ``` 24 | 25 | ### Building and Configuring 26 | To build, first, configure it(if you intend to install it globally, you 27 | might also want `--prefix=/usr`): 28 | ```sh 29 | ./configure.sh --prefix=/usr/local 30 | ``` 31 | Second, build it: 32 | ```sh 33 | ./build.sh 34 | ``` 35 | 36 | 37 | ### Libraries used in this project 38 | 39 | * [Qt](https://www.qt.io) used for GUI. 40 | * [A modern formatting library](https://github.com/fmtlib/fmt) used for formatting strings, output and logging. 41 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | branches: 5 | - develop 6 | pull_request: 7 | types: [opened, synchronize, reopened] 8 | 9 | concurrency: 10 | group: ${{ github.ref }} 11 | cancel-in-progress: true 12 | 13 | env: 14 | BUILD_TYPE: Debug 15 | CTEST_OUTPUT_ON_FAILURE: 1 16 | 17 | jobs: 18 | build: 19 | name: Build 20 | runs-on: ubuntu-latest 21 | container: archlinux:base-devel 22 | steps: 23 | - uses: actions/checkout@v4 24 | 25 | - name: install deps 26 | run: | 27 | pacman -Syu --noconfirm cmake pkg-config ninja clang mold llvm qt5-base qt5-tools polkit-qt5 python git 28 | shell: bash 29 | 30 | - name: Configure CMake 31 | # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. 32 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} 33 | 34 | - name: Build & Test 35 | # Build your program with the given configuration 36 | run: | 37 | cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} 38 | shell: bash 39 | -------------------------------------------------------------------------------- /.github/workflows/checks.yml: -------------------------------------------------------------------------------- 1 | name: Checks 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - '.github/ISSUE_TEMPLATE/**' 7 | - 'LICENSE' 8 | branches: 9 | - develop 10 | pull_request: 11 | branches: 12 | - develop 13 | 14 | concurrency: 15 | group: checks-${{ github.ref }} 16 | cancel-in-progress: true 17 | 18 | jobs: 19 | cpp-linter: 20 | runs-on: ubuntu-latest 21 | container: archlinux:base-devel 22 | steps: 23 | - uses: actions/checkout@v4 24 | 25 | - name: install deps 26 | run: | 27 | pacman -Syu --noconfirm cmake pkg-config ninja clang mold llvm libc++ qt5-base qt5-tools polkit-qt5 python git 28 | shell: bash 29 | 30 | - name: Configure CMake 31 | run: | 32 | export AR=llvm-ar 33 | export CC=clang 34 | export CXX=clang++ 35 | export NM=llvm-nm 36 | export RANLIB=llvm-ranlib 37 | 38 | ./configure.sh -t=Debug -p=${{github.workspace}}/build --use_clang 39 | ./build.sh 40 | shell: bash 41 | 42 | - name: Run clang-tidy 43 | run: | 44 | clang-tidy -p ${{github.workspace}}/build/Debug src/*.cpp 45 | shell: bash 46 | 47 | check_clang_format: 48 | name: "Check C++ style" 49 | runs-on: ubuntu-latest 50 | steps: 51 | - uses: actions/checkout@v4 52 | - name: Run clang-format style check for C/C++/Protobuf programs. 53 | uses: jidicula/clang-format-action@v4.11.0 54 | with: 55 | clang-format-version: '16' 56 | check-path: 'src' 57 | exclude-regex: 'src/ini.hpp' 58 | 59 | -------------------------------------------------------------------------------- /cmake/StaticAnalyzers.cmake: -------------------------------------------------------------------------------- 1 | option(ENABLE_CPPCHECK "Enable cppcheck [default: OFF]" OFF) 2 | option(ENABLE_CLANG_TIDY "Enable clang-tidy [default: OFF]" OFF) 3 | option(ENABLE_INCLUDE_WHAT_YOU_USE "Enable include-what-you-use [default: OFF]" OFF) 4 | 5 | if(ENABLE_CPPCHECK) 6 | find_program(CPPCHECK_EXE 7 | NAMES cppcheck 8 | DOC "Path to cppcheck executable") 9 | if(NOT CPPCHECK_EXE) 10 | message(STATUS "[cppcheck] Not found.") 11 | else() 12 | message(STATUS "[cppcheck] found: ${CPPCHECK_EXE}") 13 | set(CMAKE_CXX_CPPCHECK 14 | "${CPPCHECK_EXE}" 15 | --suppress=missingInclude 16 | --enable=all 17 | --inline-suppr 18 | --inconclusive) 19 | if(WARNINGS_AS_ERRORS) 20 | list(APPEND CMAKE_CXX_CPPCHECK --error-exitcode=2) 21 | endif() 22 | endif() 23 | else() 24 | message(STATUS "[cppcheck] Disabled.") 25 | endif() 26 | 27 | if(ENABLE_CLANG_TIDY) 28 | find_program(CLANG_TIDY_EXE 29 | NAMES clang-tidy-9 clang-tidy-8 clang-tidy-7 clang-tidy 30 | DOC "Path to clang-tidy executable") 31 | if(NOT CLANG_TIDY_EXE) 32 | message(STATUS "[clang-tidy] Not found.") 33 | else() 34 | message(STATUS "[clang-tidy] found: ${CLANG_TIDY_EXE}") 35 | set(CMAKE_CXX_CLANG_TIDY "${CLANG_TIDY_EXE}") 36 | endif() 37 | else() 38 | message(STATUS "[clang-tidy] Disabled.") 39 | endif() 40 | 41 | if(ENABLE_INCLUDE_WHAT_YOU_USE) 42 | find_program(IWYU_EXE 43 | NAMES include-what-you-use 44 | DOC "Path to include-what-you-use executable") 45 | if(NOT IWYU_EXE) 46 | message(STATUS "[iwyu] Not found.") 47 | else() 48 | message(STATUS "[iwyu] found: ${IWYU_EXE}") 49 | set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE "${IWYU_EXE}") 50 | endif() 51 | else() 52 | message(STATUS "[iwyu] Disabled.") 53 | endif() 54 | -------------------------------------------------------------------------------- /scripts/auto-changelog.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | set -u 5 | set -o pipefail 6 | 7 | readonly URL='https://github.com/cachyos/sysctl-manager' 8 | 9 | git fetch --tags 10 | 11 | most_recent_tag="$(git describe --tags "$(git rev-list --tags --max-count=1)")" 12 | 13 | git rev-list "${most_recent_tag}"..HEAD | while read -r commit 14 | do 15 | subject="$(git show -s --format="%s" "$commit")" 16 | 17 | # Squash & Merge: the commit subject is parsed as ` (#)` 18 | if grep -E -q '\(#[[:digit:]]+\)' <<< "$subject" 19 | then 20 | pr="$(awk '{print $NF}' <<< "$subject" | tr -d '()')" 21 | prefix="[$pr]($URL/pull/${pr###}): " 22 | description="$(awk '{NF--; print $0}' <<< "$subject")" 23 | 24 | # Merge: the PR ID is parsed from the git subject (which is of the form `Merge pull request 25 | # # from `, and the description is assumed to be the first line of the body. 26 | # If no body is found, the description is set to the commit subject 27 | elif grep -E -q '#[[:digit:]]+\sfrom' <<< "$subject" 28 | then 29 | pr="$(awk '{print $4}' <<< "$subject")" 30 | prefix="[$pr]($URL/pull/${pr###}): " 31 | 32 | first_line_of_body="$(git show -s --format="%b" "$commit" | head -n 1 | tr -d '\r')" 33 | if [[ -z "$first_line_of_body" ]] 34 | then 35 | description="$subject" 36 | else 37 | description="$first_line_of_body" 38 | fi 39 | 40 | # Normal commits: The commit subject is the description, and the PR ID is omitted. 41 | else 42 | pr='' 43 | prefix='' 44 | description="$subject" 45 | fi 46 | 47 | # add entry to CHANGELOG 48 | if [[ "$OSTYPE" == "linux-gnu" ]] 49 | then 50 | # shellcheck disable=SC1004 51 | sed -i'' '/## Current Develop Branch/a\ 52 | - '"$prefix$description"''$'\n' CHANGELOG.md 53 | else 54 | # shellcheck disable=SC1004 55 | sed -i '' '/## Current Develop Branch/a\ 56 | - '"$prefix$description"''$'\n' CHANGELOG.md 57 | fi 58 | done 59 | 60 | echo 'CHANGELOG updated' 61 | -------------------------------------------------------------------------------- /src/sysctl_option.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022-2025 Vladislav Nepogodin 2 | // 3 | // This file is part of CachyOS sysctl manager. 4 | // 5 | // This program is free software; you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation; either version 2 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License along 16 | // with this program; if not, write to the Free Software Foundation, Inc., 17 | // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | 19 | #ifndef SYSCTL_OPTION_HPP 20 | #define SYSCTL_OPTION_HPP 21 | 22 | #include // for string 23 | #include // for string_view 24 | #include // for vector 25 | 26 | class SysctlOption { 27 | public: 28 | consteval SysctlOption() = default; 29 | explicit SysctlOption(std::string&& raw, std::string&& name, std::string&& value, std::string&& doc_link) 30 | : m_raw(std::move(raw)), m_name(std::move(name)), m_value(std::move(value)), m_doc(std::move(doc_link)) { } 31 | 32 | static constexpr std::string_view PROC_PATH = "/proc/sys/"; 33 | 34 | /* clang-format off */ 35 | inline std::string_view get_raw() const noexcept 36 | { return m_raw.c_str(); } 37 | 38 | inline std::string_view get_name() const noexcept 39 | { return m_name.c_str(); } 40 | 41 | inline std::string_view get_value() const noexcept 42 | { return m_value.c_str(); } 43 | 44 | inline std::string_view get_doc() const noexcept 45 | { return m_doc.c_str(); } 46 | /* clang-format on */ 47 | 48 | static std::vector get_options() noexcept; 49 | 50 | private: 51 | std::string m_raw{}; 52 | std::string m_name{}; 53 | std::string m_value{}; 54 | std::string m_doc{}; 55 | }; 56 | 57 | #endif // SYSCTL_OPTION_HPP 58 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | Checks: '-*, 2 | bugprone-*, 3 | cert-*, 4 | clang-analyzer-*, 5 | cppcoreguidelines-*, 6 | fuchsia-multiple-inheritance, 7 | google-*, 8 | hicpp-*, 9 | misc-*, 10 | modernize-*, 11 | performance-*, 12 | readability-*, 13 | -bugprone-easily-swappable-parameters, 14 | -bugprone-exception-escape, 15 | -bugprone-unchecked-optional-access, 16 | -cert-dcl21-cpp, 17 | -cert-dcl58-cpp, 18 | -cert-err58-cpp, 19 | -cert-env33-c, 20 | -cert-err33-c, 21 | -clang-analyzer-optin.cplusplus.VirtualCall, 22 | -cppcoreguidelines-avoid-c-arrays, 23 | -cppcoreguidelines-avoid-const-or-ref-data-members, 24 | -cppcoreguidelines-owning-memory, 25 | -cppcoreguidelines-avoid-magic-numbers, 26 | -cppcoreguidelines-avoid-non-const-global-variables, 27 | -cppcoreguidelines-pro-bounds-array-to-pointer-decay, 28 | -cppcoreguidelines-pro-bounds-constant-array-index, 29 | -cppcoreguidelines-pro-bounds-pointer-arithmetic, 30 | -cppcoreguidelines-pro-type-reinterpret-cast, 31 | -cppcoreguidelines-pro-type-union-access, 32 | -hicpp-avoid-c-arrays, 33 | -hicpp-member-init, 34 | -hicpp-uppercase-literal-suffix, 35 | -hicpp-move-const-arg, 36 | -hicpp-named-parameter, 37 | -hicpp-no-array-decay, 38 | -misc-include-cleaner, 39 | -misc-no-recursion, 40 | -modernize-avoid-c-arrays, 41 | -modernize-use-trailing-return-type, 42 | -performance-move-const-arg, 43 | -performance-unnecessary-value-param, 44 | -readability-else-after-return, 45 | -readability-function-cognitive-complexity, 46 | -readability-identifier-length, 47 | -readability-implicit-bool-conversion, 48 | -readability-magic-numbers, 49 | -readability-named-parameter, 50 | -readability-uppercase-literal-suffix, 51 | -readability-use-anyofallof' 52 | FormatStyle: 'file' 53 | 54 | WarningsAsErrors: '*' 55 | 56 | CheckOptions: 57 | - key: misc-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic 58 | value: '1' 59 | - key: readability-redundant-access-specifiers.CheckFirstDeclaration 60 | value: '1' 61 | -------------------------------------------------------------------------------- /src/terminal-helper: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | # 3 | ### 4 | # This code has been taken from Garuda 5 | # Its is only temporal implementation 6 | ### 7 | # 8 | # This script tries to exec a terminal emulator by trying some known terminal 9 | # emulators. 10 | # 11 | # Invariants: 12 | # 1. $TERMINAL must come first 13 | # 2. Distribution-specific mechanisms come next, e.g. x-terminal-emulator 14 | # 3. The terminal emulator with best accessibility comes first. 15 | # 4. No order is guaranteed/desired for the remaining terminal emulators. 16 | 17 | set -e 18 | LAUNCHER_CMD=bash 19 | 20 | usage() { 21 | echo "Usage: ${0##*/} [cmd]" 22 | echo ' -s [shell] Change shell to [shell]' 23 | echo ' -h This help' 24 | exit 1 25 | } 26 | 27 | opts='s:h' 28 | 29 | while getopts "${opts}" arg; do 30 | case "${arg}" in 31 | s) LAUNCHER_CMD="$OPTARG" ;; 32 | h|?) usage 0 ;; 33 | *) echo "invalid argument '${arg}'"; usage 1 ;; 34 | esac 35 | done 36 | 37 | shift $(($OPTIND - 1)) 38 | 39 | file="$(mktemp)" 40 | echo "$1" > "$file" 41 | cmd="${LAUNCHER_CMD} \"$file\"" 42 | echo $cmd 43 | 44 | #declare -a terminals=(x-terminal-emulator mate-terminal gnome-terminal terminator xfce4-terminal urxvt rxvt termit Eterm aterm uxterm xterm roxterm termite lxterminal terminology st qterminal lilyterm tilix terminix konsole kitty guake tilda alacritty) 45 | terminal="" 46 | declare -A terminals=( ["alacritty"]="alacritty -e $cmd || LIBGL_ALWAYS_SOFTWARE=1 alacritty -e $cmd" ["kitty"]="kitty $cmd" ["konsole"]="konsole -e $cmd" ["gnome-terminal"]="gnome-terminal --wait -- $cmd" ["xfce4-terminal"]="xfce4-terminal --disable-server --command '$cmd'" ["lxterminal"]="lxterminal -e $cmd" ["xterm"]="xterm -e $cmd" ["st"]="st $cmd" ["foot"]="foot $cmd") 47 | declare -a term_order=( "alacritty" "kitty" "konsole" "gnome-terminal" "xfce4-terminal" "lxterminal" "xterm" "st" "foot") 48 | 49 | if [ -z "$terminal" ] || ! command -v "$terminal" &> /dev/null; then 50 | for entry in ${term_order[@]}; do 51 | if command -v "$entry" > /dev/null 2>&1; then 52 | terminal="$entry" 53 | break; 54 | fi 55 | done 56 | fi 57 | 58 | if [ -z "$terminal" ]; then 59 | notify-send -t 1500 --app-name=CachyOS "No terminal installed" "Could not find a terminal emulator. Please install one." 60 | exit 1 61 | fi 62 | 63 | eval "${terminals[${terminal}]}" || { rm "$file"; exit 2; } 64 | rm "$file" 65 | -------------------------------------------------------------------------------- /cmake/StandardProjectSettings.cmake: -------------------------------------------------------------------------------- 1 | # Set a default build type if none was specified 2 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 3 | message(STATUS "Setting build type to 'RelWithDebInfo' as none was specified.") 4 | set(CMAKE_BUILD_TYPE 5 | RelWithDebInfo 6 | CACHE STRING "Choose the type of build." FORCE) 7 | # Set the possible values of build type for cmake-gui, ccmake 8 | set_property( 9 | CACHE CMAKE_BUILD_TYPE 10 | PROPERTY STRINGS 11 | "Debug" 12 | "Release" 13 | "MinSizeRel" 14 | "RelWithDebInfo") 15 | endif() 16 | 17 | set(CMAKE_CXX_STANDARD 23) 18 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 19 | set(CMAKE_CXX_EXTENSIONS OFF) 20 | 21 | set(BUILD_SHARED_LIBS OFF CACHE INTERNAL "" FORCE) 22 | add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x050F00) 23 | 24 | # Generate compile_commands.json to make it easier to work with clang based tools 25 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 26 | 27 | if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") 28 | #add_compile_options(-nostdlib++ -stdlib=libc++ -nodefaultlibs -fexperimental-library) 29 | #add_link_options(-stdlib=libc++) 30 | 31 | add_compile_options(-fstrict-vtable-pointers) 32 | 33 | if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 16) 34 | # Set new experimental pass manager, it's a performance, build time and binary size win. 35 | # Can be removed after https://reviews.llvm.org/D66490 merged and released to at least two versions of clang. 36 | add_compile_options(-fexperimental-new-pass-manager) 37 | endif() 38 | endif() 39 | 40 | option(ENABLE_IPO "Enable Interprocedural Optimization, aka Link Time Optimization (LTO)" OFF) 41 | 42 | if(ENABLE_IPO) 43 | include(CheckIPOSupported) 44 | check_ipo_supported( 45 | RESULT 46 | result 47 | OUTPUT 48 | output) 49 | if(result) 50 | set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON) 51 | else() 52 | message(SEND_ERROR "IPO is not supported: ${output}") 53 | endif() 54 | endif() 55 | if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") 56 | add_compile_options(-fcolor-diagnostics) 57 | elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 58 | add_compile_options(-fdiagnostics-color=always) 59 | else() 60 | message(STATUS "No colored compiler diagnostic set for '${CMAKE_CXX_COMPILER_ID}' compiler.") 61 | endif() 62 | 63 | # Enables STL container checker if not building a release. 64 | if(CMAKE_BUILD_TYPE STREQUAL "Debug") 65 | add_definitions(-D_GLIBCXX_ASSERTIONS) 66 | add_definitions(-D_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS=1) 67 | add_definitions(-D_LIBCPP_ENABLE_ASSERTIONS=1) 68 | endif() 69 | -------------------------------------------------------------------------------- /src/utils.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022-2025 Vladislav Nepogodin 2 | // 3 | // This file is part of CachyOS sysctl manager. 4 | // 5 | // This program is free software; you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation; either version 2 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License along 16 | // with this program; if not, write to the Free Software Foundation, Inc., 17 | // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | 19 | #ifndef UTILS_HPP 20 | #define UTILS_HPP 21 | 22 | #include // for span 23 | #include // for string 24 | #include // for string_view 25 | 26 | #if defined(__clang__) 27 | #pragma clang diagnostic push 28 | #pragma clang diagnostic ignored "-Wsign-conversion" 29 | #elif defined(__GNUC__) 30 | #pragma GCC diagnostic push 31 | #pragma GCC diagnostic ignored "-Wuseless-cast" 32 | #pragma GCC diagnostic ignored "-Wsign-conversion" 33 | #endif 34 | 35 | #include 36 | 37 | #if defined(__clang__) 38 | #pragma clang diagnostic pop 39 | #elif defined(__GNUC__) 40 | #pragma GCC diagnostic pop 41 | #endif 42 | 43 | namespace utils { 44 | 45 | [[nodiscard]] auto join_vec(std::span lines, std::string_view delim) noexcept -> std::string; 46 | [[nodiscard]] auto read_whole_file(std::string_view filepath) noexcept -> std::string; 47 | [[nodiscard]] auto write_to_file(std::string_view filepath, std::string_view data) noexcept -> bool; 48 | 49 | // Runs a command in a terminal, escalates using pkexec if escalate is true 50 | int runCmdTerminal(QString cmd, bool escalate) noexcept; 51 | 52 | inline std::size_t replace_all(std::string& inout, std::string_view what, std::string_view with) noexcept { 53 | std::size_t count{}; 54 | std::size_t pos{}; 55 | while (std::string::npos != (pos = inout.find(what.data(), pos, what.length()))) { 56 | inout.replace(pos, what.length(), with.data(), with.length()); 57 | pos += with.length(), ++count; 58 | } 59 | return count; 60 | } 61 | 62 | inline std::size_t remove_all(std::string& inout, std::string_view what) noexcept { 63 | return replace_all(inout, what, ""); 64 | } 65 | 66 | } // namespace utils 67 | 68 | #endif // UTILS_HPP 69 | -------------------------------------------------------------------------------- /cmake/Sanitizers.cmake: -------------------------------------------------------------------------------- 1 | function(enable_sanitizers project_name) 2 | if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") 3 | option(ENABLE_COVERAGE "Enable coverage reporting for gcc/clang" OFF) 4 | 5 | if(ENABLE_COVERAGE) 6 | target_compile_options(${project_name} INTERFACE --coverage -O0 -g) 7 | target_link_libraries(${project_name} INTERFACE --coverage) 8 | endif() 9 | 10 | set(SANITIZERS "") 11 | 12 | option(ENABLE_SANITIZER_ADDRESS "Enable address sanitizer" OFF) 13 | if(ENABLE_SANITIZER_ADDRESS) 14 | list(APPEND SANITIZERS "address") 15 | endif() 16 | 17 | option(ENABLE_SANITIZER_LEAK "Enable leak sanitizer" OFF) 18 | if(ENABLE_SANITIZER_LEAK) 19 | list(APPEND SANITIZERS "leak") 20 | endif() 21 | 22 | option(ENABLE_SANITIZER_UNDEFINED_BEHAVIOR "Enable undefined behavior sanitizer" OFF) 23 | if(ENABLE_SANITIZER_UNDEFINED_BEHAVIOR) 24 | list(APPEND SANITIZERS "undefined") 25 | endif() 26 | 27 | option(ENABLE_SANITIZER_THREAD "Enable thread sanitizer" OFF) 28 | if(ENABLE_SANITIZER_THREAD) 29 | if("address" IN_LIST SANITIZERS OR "leak" IN_LIST SANITIZERS) 30 | message(WARNING "Thread sanitizer does not work with Address and Leak sanitizer enabled") 31 | else() 32 | list(APPEND SANITIZERS "thread") 33 | endif() 34 | endif() 35 | 36 | option(ENABLE_SANITIZER_MEMORY "Enable memory sanitizer" OFF) 37 | if(ENABLE_SANITIZER_MEMORY AND CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") 38 | message(WARNING "Memory sanitizer requires all the code (including libc++) to be MSan-instrumented otherwise it reports false positives") 39 | if("address" IN_LIST SANITIZERS 40 | OR "thread" IN_LIST SANITIZERS 41 | OR "leak" IN_LIST SANITIZERS) 42 | message(WARNING "Memory sanitizer does not work with Address, Thread and Leak sanitizer enabled") 43 | else() 44 | list(APPEND SANITIZERS "memory") 45 | endif() 46 | endif() 47 | 48 | list( 49 | JOIN 50 | SANITIZERS 51 | "," 52 | LIST_OF_SANITIZERS) 53 | 54 | endif() 55 | 56 | if(LIST_OF_SANITIZERS) 57 | if(NOT "${LIST_OF_SANITIZERS}" STREQUAL "") 58 | target_compile_options(${project_name} INTERFACE -fsanitize=${LIST_OF_SANITIZERS}) 59 | target_link_options(${project_name} INTERFACE -fsanitize=${LIST_OF_SANITIZERS}) 60 | endif() 61 | endif() 62 | endfunction() 63 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") 4 | 5 | ## 6 | ## PROJECT 7 | ## name and version 8 | ## 9 | project(cachyos-sysctl-manager 10 | VERSION 1.2.0 11 | LANGUAGES CXX) 12 | 13 | 14 | ## 15 | ## INCLUDE 16 | ## 17 | include(GNUInstallDirs) 18 | include(StandardProjectSettings) 19 | include(CompilerWarnings) 20 | include(EnableCcache) 21 | include(Linker) 22 | include(StaticAnalyzers) 23 | include(Sanitizers) 24 | include(CPM) 25 | 26 | find_package(Threads REQUIRED) 27 | find_package(PkgConfig REQUIRED) 28 | find_package(Qt6 COMPONENTS Widgets REQUIRED) 29 | 30 | CPMAddPackage( 31 | NAME fmt 32 | GITHUB_REPOSITORY fmtlib/fmt 33 | GIT_TAG 11.2.0 34 | EXCLUDE_FROM_ALL YES 35 | ) 36 | 37 | find_package(PolkitQt6-1 REQUIRED) 38 | 39 | ## 40 | ## CONFIGURATION 41 | ## 42 | set(CMAKE_AUTOUIC ON) 43 | set(CMAKE_AUTOMOC ON) 44 | set(CMAKE_AUTORCC ON) 45 | set(CMAKE_AUTOUIC_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/src) 46 | 47 | if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") 48 | set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -flto=thin -fwhole-program-vtables") 49 | set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} -flto=thin -fwhole-program-vtables") 50 | elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 51 | set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -flto -fwhole-program -fuse-linker-plugin") 52 | endif() 53 | 54 | 55 | # Link this 'library' to set the c++ standard / compile-time options requested 56 | add_library(project_options INTERFACE) 57 | target_compile_features(project_options INTERFACE cxx_std_23) 58 | 59 | ## 60 | ## Target 61 | ## 62 | qt_add_executable(${PROJECT_NAME} 63 | src/backtrace.hpp src/backtrace.cpp 64 | src/utils.hpp src/utils.cpp 65 | src/sysctl_option.hpp src/sysctl_option.cpp 66 | src/sm-window.hpp src/sm-window.cpp 67 | src/sm-window.ui 68 | src/main.cpp 69 | ) 70 | 71 | # Link this 'library' to use the warnings specified in CompilerWarnings.cmake 72 | add_library(project_warnings INTERFACE) 73 | set_project_warnings(project_warnings) 74 | 75 | # Add linker configuration 76 | configure_linker(project_options) 77 | 78 | # sanitizer options if supported by compiler 79 | enable_sanitizers(project_options) 80 | 81 | include_directories(${CMAKE_SOURCE_DIR}/src ${CMAKE_BINARY_DIR}) 82 | 83 | target_link_libraries(${PROJECT_NAME} PRIVATE project_warnings project_options Qt6::Widgets Threads::Threads fmt::fmt bfd) 84 | 85 | option(ENABLE_UNITY "Enable Unity builds of projects" OFF) 86 | if(ENABLE_UNITY) 87 | # Add for any project you want to apply unity builds for 88 | set_target_properties(${PROJECT_NAME} PROPERTIES UNITY_BUILD ON) 89 | endif() 90 | 91 | install( 92 | TARGETS ${PROJECT_NAME} 93 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 94 | ) 95 | 96 | install( 97 | PROGRAMS ${CMAKE_SOURCE_DIR}/src/terminal-helper 98 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/cachyos-sysctl-manager 99 | ) 100 | 101 | install( 102 | PROGRAMS ${CMAKE_SOURCE_DIR}/src/rootshell.sh 103 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/cachyos-sysctl-manager 104 | ) 105 | 106 | install( 107 | FILES org.cachyos.cachyos-sysctl-manager.pkexec.policy 108 | DESTINATION "${POLKITQT-1_POLICY_FILES_INSTALL_DIR}" 109 | ) 110 | 111 | install( 112 | FILES cachyos-sysctl-manager.desktop 113 | DESTINATION ${CMAKE_INSTALL_DATADIR}/applications 114 | ) 115 | install( 116 | FILES cachyos-sysctl-manager.png 117 | DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps 118 | ) 119 | -------------------------------------------------------------------------------- /src/utils.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022-2025 Vladislav Nepogodin 2 | // 3 | // This file is part of CachyOS sysctl manager. 4 | // 5 | // This program is free software; you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation; either version 2 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License along 16 | // with this program; if not, write to the Free Software Foundation, Inc., 17 | // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | 19 | #include "utils.hpp" 20 | 21 | #include // for int32_t 22 | #include // for FILE, fclose, fopen, fseek 23 | 24 | #include // for ofstream 25 | #include // for ranges::* 26 | #include // for string 27 | 28 | #if defined(__clang__) 29 | #pragma clang diagnostic push 30 | #pragma clang diagnostic ignored "-Wsign-conversion" 31 | #elif defined(__GNUC__) 32 | #pragma GCC diagnostic push 33 | #pragma GCC diagnostic ignored "-Wuseless-cast" 34 | #pragma GCC diagnostic ignored "-Wsign-conversion" 35 | #pragma GCC diagnostic ignored "-Wsuggest-attribute=pure" 36 | #endif 37 | 38 | #include 39 | 40 | #if defined(__clang__) 41 | #pragma clang diagnostic pop 42 | #elif defined(__GNUC__) 43 | #pragma GCC diagnostic pop 44 | #endif 45 | 46 | namespace utils { 47 | 48 | auto join_vec(std::span lines, std::string_view delim) noexcept -> std::string { 49 | return lines | std::ranges::views::join_with(delim) | std::ranges::to(); 50 | } 51 | 52 | auto runCmdTerminal(QString cmd, bool escalate) noexcept -> std::int32_t { 53 | QProcess proc; 54 | cmd += "; read -p 'Press enter to exit'"; 55 | auto paramlist = QStringList(); 56 | if (escalate) { 57 | paramlist << "-s" 58 | << "pkexec /usr/lib/cachyos-sysctl-manager/rootshell.sh"; 59 | } 60 | paramlist << cmd; 61 | 62 | proc.start("/usr/lib/cachyos-sysctl-manager/terminal-helper", paramlist); 63 | proc.waitForFinished(-1); 64 | return proc.exitCode(); 65 | } 66 | 67 | auto read_whole_file(std::string_view filepath) noexcept -> std::string { 68 | // Use std::fopen because it's faster than std::ifstream 69 | auto* file = std::fopen(filepath.data(), "rb"); 70 | if (file == nullptr) { 71 | std::perror("read_whole_file"); 72 | return {}; 73 | } 74 | 75 | std::fseek(file, 0u, SEEK_END); 76 | const auto size = static_cast(std::ftell(file)); 77 | std::fseek(file, 0u, SEEK_SET); 78 | 79 | std::string buf; 80 | buf.resize(size); 81 | 82 | const std::size_t read = std::fread(buf.data(), sizeof(char), size, file); 83 | if (read != size) { 84 | std::perror("read_whole_file"); 85 | std::fclose(file); 86 | return {}; 87 | } 88 | std::fclose(file); 89 | 90 | return buf; 91 | } 92 | 93 | auto write_to_file(std::string_view filepath, std::string_view data) noexcept -> bool { 94 | std::ofstream file{std::string{filepath}}; 95 | if (!file.is_open()) { 96 | std::perror("write_to_file"); 97 | return false; 98 | } 99 | file << data; 100 | return true; 101 | } 102 | 103 | } // namespace utils 104 | -------------------------------------------------------------------------------- /src/sm-window.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022-2025 Vladislav Nepogodin 2 | // 3 | // This file is part of CachyOS sysctl manager. 4 | // 5 | // This program is free software; you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation; either version 2 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License along 16 | // with this program; if not, write to the Free Software Foundation, Inc., 17 | // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | 19 | #ifndef MAINWINDOW_HPP_ 20 | #define MAINWINDOW_HPP_ 21 | 22 | #if defined(__clang__) 23 | #pragma clang diagnostic push 24 | #pragma clang diagnostic ignored "-Wsign-conversion" 25 | #pragma clang diagnostic ignored "-Wfloat-conversion" 26 | #pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" 27 | #pragma clang diagnostic ignored "-Wdeprecated-enum-enum-conversion" 28 | #elif defined(__GNUC__) 29 | #pragma GCC diagnostic push 30 | #pragma GCC diagnostic ignored "-Wuseless-cast" 31 | #pragma GCC diagnostic ignored "-Wsign-conversion" 32 | #pragma GCC diagnostic ignored "-Wconversion" 33 | #pragma GCC diagnostic ignored "-Wdeprecated-enum-enum-conversion" 34 | #pragma GCC diagnostic ignored "-Wsuggest-attribute=pure" 35 | #endif 36 | 37 | #include 38 | 39 | #include "sysctl_option.hpp" 40 | #include "utils.hpp" 41 | 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | 48 | #include 49 | #include 50 | 51 | #if defined(__clang__) 52 | #pragma clang diagnostic pop 53 | #elif defined(__GNUC__) 54 | #pragma GCC diagnostic pop 55 | #endif 56 | 57 | class Work final : public QObject { 58 | Q_OBJECT 59 | 60 | public: 61 | using function_t = std::function; 62 | explicit Work(function_t&& func) 63 | : m_func(std::move(func)) { } 64 | ~Work() = default; 65 | 66 | public: 67 | void doHeavyCalculations(); 68 | 69 | private: 70 | function_t m_func; 71 | }; 72 | 73 | namespace TreeCol { 74 | enum { Name, 75 | Value, 76 | Displayed, 77 | Immutable }; 78 | } 79 | 80 | class MainWindow final : public QMainWindow { 81 | Q_OBJECT 82 | Q_DISABLE_COPY_MOVE(MainWindow) 83 | public: 84 | explicit MainWindow(QWidget* parent = nullptr); 85 | virtual ~MainWindow(); 86 | 87 | protected: 88 | void closeEvent(QCloseEvent* event) override; 89 | 90 | private: 91 | void on_cancel() noexcept; 92 | void on_execute() noexcept; 93 | 94 | void build_change_list(QTreeWidgetItem* item) noexcept; 95 | 96 | void find_options() noexcept; 97 | 98 | void on_item_double_clicked(QTreeWidgetItem* item, int column) noexcept; 99 | void item_changed(QTreeWidgetItem* item, int column) noexcept; 100 | 101 | std::atomic_bool m_running{}; 102 | std::atomic_bool m_thread_running{true}; 103 | std::mutex m_mutex{}; 104 | std::condition_variable m_cv{}; 105 | 106 | QStringList m_change_list{}; 107 | 108 | QThread* m_worker_th = new QThread(this); 109 | Work* m_worker{nullptr}; 110 | 111 | std::vector m_options = SysctlOption::get_options(); 112 | std::unique_ptr m_ui = std::make_unique(); 113 | }; 114 | 115 | #endif // MAINWINDOW_HPP_ 116 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('cachyos-sysctl-manager', 'cpp', 2 | version: '1.2.0', 3 | license: 'GPLv3', 4 | meson_version: '>=0.59.0', 5 | default_options: ['cpp_std=c++23', 6 | 'buildtype=debugoptimized', 7 | 'warning_level=3', 8 | 'werror=false', 9 | 'b_ndebug=if-release']) 10 | 11 | is_debug_build = get_option('buildtype').startswith('debug') 12 | cc = meson.get_compiler('cpp') 13 | if cc.get_id() == 'clang' 14 | specific_cc_flags = [ 15 | '-nostdlib++', 16 | '-stdlib=libc++', 17 | '-nodefaultlibs', 18 | '-fexperimental-library', 19 | '-fstrict-vtable-pointers', 20 | '-fexperimental-new-pass-manager', 21 | ] 22 | specific_link_flags = [ 23 | '-stdlib=libc++', 24 | ] 25 | add_global_arguments(cc.get_supported_arguments(specific_cc_flags), language : 'cpp') 26 | add_global_link_arguments(cc.get_supported_link_arguments(specific_link_flags), language : 'cpp') 27 | endif 28 | 29 | if is_debug_build 30 | add_global_arguments('-D_GLIBCXX_ASSERTIONS', language : 'cpp') 31 | add_global_arguments('-D_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS=1', language : 'cpp') 32 | add_global_arguments('-D_LIBCPP_ENABLE_ASSERTIONS=1', language : 'cpp') 33 | endif 34 | add_global_arguments('-DQT_DISABLE_DEPRECATED_BEFORE=0x050F00', language : 'cpp') 35 | 36 | qt6 = import('qt6') 37 | qt6_dep = dependency('qt6', modules: ['Widgets']) 38 | 39 | # Common dependencies 40 | fmt = dependency('fmt', version : ['>=10.0.0'], fallback : ['fmt', 'fmt_dep']) 41 | 42 | src_files = files( 43 | 'src/utils.hpp', 'src/utils.cpp', 44 | 'src/sysctl_option.hpp', 'src/sysctl_option.cpp', 45 | 'src/sm-window.hpp', 'src/sm-window.cpp', 46 | 'src/main.cpp', 47 | ) 48 | 49 | possible_cc_flags = [ 50 | '-Wshadow', 51 | 52 | '-Wnon-virtual-dtor', 53 | 54 | '-Wold-style-cast', 55 | '-Wcast-align', 56 | '-Wunused', 57 | '-Woverloaded-virtual', 58 | 59 | '-Wpedantic', # non-standard C++ 60 | '-Wconversion', # type conversion that may lose data 61 | '-Wsign-conversion', 62 | '-Wnull-dereference', 63 | '-Wdouble-promotion', # float to double 64 | 65 | '-Wformat=2', 66 | '-Wimplicit-fallthrough', # fallthrough without an explicit annotation 67 | ] 68 | 69 | if cc.get_id() == 'gcc' 70 | possible_cc_flags += [ 71 | '-Wmisleading-indentation', 72 | 73 | '-Wduplicated-cond', 74 | '-Wduplicated-branches', 75 | '-Wlogical-op', 76 | '-Wuseless-cast', 77 | 78 | '-Wsuggest-attribute=cold', 79 | '-Wsuggest-attribute=format', 80 | '-Wsuggest-attribute=malloc', 81 | '-Wsuggest-attribute=noreturn', 82 | '-Wsuggest-attribute=pure', 83 | '-Wsuggest-final-methods', 84 | '-Wsuggest-final-types', 85 | '-Wdiv-by-zero', 86 | '-Wanalyzer-double-fclose', 87 | '-Wanalyzer-double-free', 88 | '-Wanalyzer-malloc-leak', 89 | '-Wanalyzer-use-after-free', 90 | ] 91 | endif 92 | 93 | if not is_debug_build 94 | if cc.get_id() == 'gcc' 95 | possible_cc_flags += [ 96 | '-flto', 97 | '-fwhole-program', 98 | '-fuse-linker-plugin', 99 | ] 100 | else 101 | possible_cc_flags += [ 102 | '-flto=thin', 103 | '-fwhole-program-vtables', 104 | ] 105 | endif 106 | 107 | possible_cc_flags += ['-fdata-sections', '-ffunction-sections'] 108 | possible_link_flags = ['-Wl,--gc-sections'] 109 | add_project_link_arguments(cc.get_supported_link_arguments(possible_link_flags), language : 'cpp') 110 | endif 111 | 112 | add_project_arguments(cc.get_supported_arguments(possible_cc_flags), language : 'cpp') 113 | 114 | deps = [qt6_dep, fmt] 115 | 116 | prep = qt6.compile_moc( 117 | headers : ['src/sm-window.hpp'] # These need to be fed through the moc tool before use. 118 | ) 119 | # XML files that need to be compiled with the uic tol. 120 | prep += qt6.compile_ui(sources : ['src/sm-window.ui']) 121 | 122 | src_files += [prep] 123 | executable( 124 | 'cachyos-sysctl-manager', 125 | src_files, 126 | dependencies: deps, 127 | include_directories: [include_directories('src')], 128 | install: true) 129 | 130 | summary( 131 | { 132 | 'Build type': get_option('buildtype'), 133 | }, 134 | bool_yn: true 135 | ) 136 | -------------------------------------------------------------------------------- /configure.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright (C) 2022-2025 Vladislav Nepogodin 3 | # 4 | # This file is part of CachyOS sysctl manager. 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License along 17 | # with this program; if not, write to the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 19 | 20 | set -e 21 | 22 | cd "`dirname "$0"`" 23 | 24 | if [[ $1 == "--help" ]]; then 25 | cat< /dev/null; then 99 | _configure_flags+=('-GNinja') 100 | fi 101 | 102 | if command -v mold &> /dev/null; then 103 | _configure_flags+=('-DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=mold"') 104 | fi 105 | 106 | cmake -S . -B ${_buildpath}/${_buildtype} \ 107 | -DENABLE_SANITIZER_ADDRESS=${_sanitizer_address} \ 108 | -DENABLE_SANITIZER_UNDEFINED_BEHAVIOR=${_sanitizer_UB} \ 109 | -DENABLE_SANITIZER_LEAK=${_sanitizer_leak} \ 110 | -DCMAKE_BUILD_TYPE=${_buildtype} \ 111 | -DCMAKE_INSTALL_PREFIX=${_prefix} \ 112 | -DCMAKE_INSTALL_LIBDIR=${_libdir} \ 113 | "${_configure_flags[@]}" 114 | 115 | 116 | cat > build.sh < 2 | 3 | MainWindow 4 | 5 | 6 | Qt::NonModal 7 | 8 | 9 | 10 | 0 11 | 0 12 | 887 13 | 544 14 | 15 | 16 | 17 | CachyOS Sysctl Manager 18 | 19 | 20 | 21 | .. 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | Qt::Horizontal 32 | 33 | 34 | 35 | 40 36 | 20 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | Qt::Horizontal 45 | 46 | 47 | 48 | 40 49 | 20 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | search 58 | 59 | 60 | true 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | QFrame::Raised 71 | 72 | 73 | QAbstractItemView::EditKeyPressed 74 | 75 | 76 | true 77 | 78 | 79 | QAbstractItemView::SingleSelection 80 | 81 | 82 | 83 | Choose 84 | 85 | 86 | 87 | 88 | PkgName 89 | 90 | 91 | 92 | 93 | Version 94 | 95 | 96 | 97 | 98 | Category 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | Qt::Horizontal 110 | 111 | 112 | 113 | 40 114 | 20 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | Qt::Horizontal 123 | 124 | 125 | 126 | 40 127 | 20 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | Cancel 136 | 137 | 138 | 139 | 140 | 141 | 142 | Execute 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /cmake/CompilerWarnings.cmake: -------------------------------------------------------------------------------- 1 | function(set_project_warnings project_name) 2 | option(WARNINGS_AS_ERRORS "Treat compiler warnings as errors" OFF) 3 | 4 | set(MSVC_WARNINGS 5 | /W4 # Baseline reasonable warnings 6 | /w14242 # 'identifier': conversion from 'type1' to 'type1', possible loss of data 7 | /w14254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data 8 | /w14263 # 'function': member function does not override any base class virtual member function 9 | /w14265 # 'classname': class has virtual functions, but destructor is not virtual instances of this class may not 10 | # be destructed correctly 11 | /w14287 # 'operator': unsigned/negative constant mismatch 12 | /we4289 # nonstandard extension used: 'variable': loop control variable declared in the for-loop is used outside 13 | # the for-loop scope 14 | /w14296 # 'operator': expression is always 'boolean_value' 15 | /w14311 # 'variable': pointer truncation from 'type1' to 'type2' 16 | /w14545 # expression before comma evaluates to a function which is missing an argument list 17 | /w14546 # function call before comma missing argument list 18 | /w14547 # 'operator': operator before comma has no effect; expected operator with side-effect 19 | /w14549 # 'operator': operator before comma has no effect; did you intend 'operator'? 20 | /w14555 # expression has no effect; expected expression with side- effect 21 | /w14619 # pragma warning: there is no warning number 'number' 22 | /w14640 # Enable warning on thread un-safe static member initialization 23 | /w14826 # Conversion from 'type1' to 'type_2' is sign-extended. This may cause unexpected runtime behavior. 24 | /w14905 # wide string literal cast to 'LPSTR' 25 | /w14906 # string literal cast to 'LPWSTR' 26 | /w14928 # illegal copy-initialization; more than one user-defined conversion has been implicitly applied 27 | /permissive- # standards conformance mode for MSVC compiler. 28 | ) 29 | 30 | set(CLANG_WARNINGS 31 | -Wall 32 | -Wextra # reasonable and standard 33 | -Wshadow # warn the user if a variable declaration shadows one from a parent context 34 | -Wnon-virtual-dtor # warn the user if a class with virtual functions has a non-virtual destructor. This helps 35 | # catch hard to track down memory errors 36 | -Wold-style-cast # warn for c-style casts 37 | -Wcast-align # warn for potential performance problem casts 38 | -Wunused # warn on anything being unused 39 | -Woverloaded-virtual # warn if you overload (not override) a virtual function 40 | -Wpedantic # warn if non-standard C++ is used 41 | -Wconversion # warn on type conversions that may lose data 42 | -Wsign-conversion # warn on sign conversions 43 | -Wnull-dereference # warn if a null dereference is detected 44 | -Wdouble-promotion # warn if float is implicit promoted to double 45 | -Wformat=2 # warn on security issues around functions that format output (ie printf) 46 | -Wimplicit-fallthrough # warn on statements that fallthrough without an explicit annotation 47 | ) 48 | 49 | if(WARNINGS_AS_ERRORS) 50 | set(CLANG_WARNINGS ${CLANG_WARNINGS} -Werror) 51 | set(MSVC_WARNINGS ${MSVC_WARNINGS} /WX) 52 | endif() 53 | 54 | set(GCC_WARNINGS 55 | ${CLANG_WARNINGS} 56 | -Wmisleading-indentation # warn if indentation implies blocks where blocks do not exist 57 | -Wduplicated-cond # warn if if / else chain has duplicated conditions 58 | -Wduplicated-branches # warn if if / else branches have duplicated code 59 | -Wlogical-op # warn about logical operations being used where bitwise were probably wanted 60 | -Wuseless-cast # warn if you perform a cast to the same type 61 | 62 | -Wsuggest-attribute=cold 63 | -Wsuggest-attribute=format 64 | -Wsuggest-attribute=malloc 65 | -Wsuggest-attribute=noreturn 66 | -Wsuggest-attribute=pure 67 | -Wsuggest-final-methods 68 | -Wsuggest-final-types 69 | -Wdiv-by-zero 70 | -Wanalyzer-double-fclose 71 | -Wanalyzer-double-free 72 | -Wanalyzer-malloc-leak 73 | -Wanalyzer-use-after-free 74 | ) 75 | 76 | if(MSVC) 77 | set(PROJECT_WARNINGS ${MSVC_WARNINGS}) 78 | elseif(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") 79 | set(PROJECT_WARNINGS ${CLANG_WARNINGS}) 80 | elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 81 | set(PROJECT_WARNINGS ${GCC_WARNINGS}) 82 | else() 83 | message(AUTHOR_WARNING "No compiler warnings set for '${CMAKE_CXX_COMPILER_ID}' compiler.") 84 | endif() 85 | 86 | target_compile_options(${project_name} INTERFACE ${PROJECT_WARNINGS}) 87 | endfunction() 88 | -------------------------------------------------------------------------------- /src/sysctl_option.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022-2025 Vladislav Nepogodin 2 | // 3 | // This file is part of CachyOS sysctl manager. 4 | // 5 | // This program is free software; you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation; either version 2 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License along 16 | // with this program; if not, write to the Free Software Foundation, Inc., 17 | // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | 19 | #include "sysctl_option.hpp" 20 | #include "utils.hpp" 21 | 22 | #include // for contains 23 | #include // for array 24 | #include // for recursive_directory_iterator, is_directory 25 | #include // for ifstream 26 | #include // for ranges::* 27 | #include // for string 28 | 29 | #include 30 | 31 | namespace fs = std::filesystem; 32 | 33 | namespace { 34 | 35 | // NOLINTNEXTLINE 36 | static constexpr std::string_view DOC_ENDPOINT = "https://www.kernel.org/doc/html/latest/admin-guide/sysctl"; 37 | // NOLINTNEXTLINE 38 | static constexpr std::array DEPRECATED{ 39 | "base_reachable_time", 40 | "retrans_time", 41 | ""}; 42 | 43 | constexpr std::string_view get_appendix_if_available(std::string_view entry) noexcept { 44 | if (entry.starts_with("net/ipv4")) { 45 | return "#proc-sys-net-ipv4-ipv4-settings"; 46 | } 47 | if (entry.starts_with("net/core")) { 48 | return "#proc-sys-net-core-network-core-options"; 49 | } 50 | if (entry.starts_with("fs/binfmt_misc")) { 51 | return "#proc-sys-fs-binfmt-misc"; 52 | } 53 | if (entry.starts_with("fs/mqueue")) { 54 | return "#proc-sys-fs-mqueue-posix-message-queues-filesystem"; 55 | } 56 | if (entry.starts_with("fs/epoll")) { 57 | return "#proc-sys-fs-epoll-configuration-options-for-the-epoll-interface"; 58 | } 59 | return ""; 60 | } 61 | 62 | constexpr std::string_view get_category(std::string_view entry) noexcept { 63 | return entry.substr(0, entry.find_first_of('/')); 64 | } 65 | 66 | } // namespace 67 | 68 | std::vector SysctlOption::get_options() noexcept { 69 | std::vector options{}; 70 | 71 | for (const auto& dir_entry : fs::recursive_directory_iterator{PROC_PATH}) { 72 | if (fs::is_directory(dir_entry)) { 73 | // Skip directories 74 | continue; 75 | } 76 | 77 | // Remove proc path, to leave just the option path, 78 | // within the proc directory. 79 | std::string_view file_path = dir_entry.path().c_str(); 80 | if (file_path.starts_with(PROC_PATH)) { 81 | file_path.remove_prefix(PROC_PATH.size()); 82 | } 83 | 84 | // Skip `debug` and `dev`. 85 | if (file_path.starts_with("debug") || file_path.starts_with("dev")) { 86 | continue; 87 | } 88 | 89 | // Skip deprecated. 90 | if (std::ranges::contains(DEPRECATED, dir_entry.path().filename())) { 91 | continue; 92 | } 93 | 94 | // Generate doc link. 95 | auto&& doc_link = fmt::format("{}/{}.html{}", DOC_ENDPOINT, get_category(file_path), get_appendix_if_available(file_path)); 96 | 97 | // Parse option value. 98 | std::string&& option_value{"nil"}; 99 | std::string file_content{}; 100 | 101 | // Skip if failed to open file descriptor. 102 | std::ifstream file_stream{dir_entry.path().c_str()}; 103 | if (!file_stream.is_open()) { 104 | continue; 105 | } 106 | // auto&& file_content = utils::read_whole_file(dir_entry.path().c_str()); 107 | // if (file_content.empty()) { 108 | if (!std::getline(file_stream, file_content)) { 109 | fmt::print(stderr, "Failed to read := '{}'\n", dir_entry.path().c_str()); 110 | continue; 111 | } 112 | option_value = std::move(file_content); 113 | utils::replace_all(option_value, "\t", " "); 114 | 115 | // Option name is path, with path delimeters('/') replaced with '.'. 116 | std::string option_name{file_path}; 117 | utils::replace_all(option_name, "/", "."); 118 | 119 | auto option_obj = SysctlOption{std::string{file_path}, std::move(option_name), std::move(option_value), std::move(doc_link)}; 120 | options.emplace_back(std::move(option_obj)); 121 | } 122 | 123 | return options; 124 | } 125 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022-2025 Vladislav Nepogodin 2 | // 3 | // This file is part of CachyOS sysctl manager. 4 | // 5 | // This program is free software; you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation; either version 2 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License along 16 | // with this program; if not, write to the Free Software Foundation, Inc., 17 | // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | 19 | #include "backtrace.hpp" 20 | #include "sm-window.hpp" 21 | 22 | #include 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | #if defined(__clang__) 29 | #pragma clang diagnostic push 30 | #pragma clang diagnostic ignored "-Wsign-conversion" 31 | #pragma clang diagnostic ignored "-Wimplicit-int-conversion" 32 | #elif defined(__GNUC__) 33 | #pragma GCC diagnostic push 34 | #pragma GCC diagnostic ignored "-Wsign-conversion" 35 | #pragma GCC diagnostic ignored "-Wconversion" 36 | #endif 37 | 38 | #include 39 | 40 | #if defined(__clang__) 41 | #pragma clang diagnostic pop 42 | #elif defined(__GNUC__) 43 | #pragma GCC diagnostic pop 44 | #endif 45 | 46 | namespace { 47 | 48 | bool IsInstanceAlreadyRunning(QSharedMemory& memoryLock) noexcept { 49 | if (!memoryLock.create(1)) { 50 | memoryLock.attach(); 51 | memoryLock.detach(); 52 | 53 | if (!memoryLock.create(1)) { 54 | return true; 55 | } 56 | } 57 | 58 | return false; 59 | } 60 | 61 | /* Adopted from bitcoin-qt source code. 62 | * Licensed under MIT 63 | */ 64 | /** Set up translations */ 65 | void initTranslations(QTranslator& qtTranslatorBase, QTranslator& qtTranslator, QTranslator& translatorBase, QTranslator& translator) noexcept { 66 | // Remove old translators 67 | QApplication::removeTranslator(&qtTranslatorBase); 68 | QApplication::removeTranslator(&qtTranslator); 69 | QApplication::removeTranslator(&translatorBase); 70 | QApplication::removeTranslator(&translator); 71 | 72 | // Get desired locale (e.g. "de_DE") 73 | // 1) System default language 74 | const auto lang_territory = QLocale::system().name(); 75 | 76 | // Convert to "de" only by truncating "_DE" 77 | QString lang = lang_territory; 78 | lang.truncate(lang_territory.lastIndexOf('_')); 79 | 80 | // Load language files for configured locale: 81 | // - First load the translator for the base language, without territory 82 | // - Then load the more specific locale translator 83 | 84 | #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) 85 | const auto translation_path{QLibraryInfo::location(QLibraryInfo::TranslationsPath)}; 86 | #else 87 | const auto translation_path{QLibraryInfo::path(QLibraryInfo::TranslationsPath)}; 88 | #endif 89 | 90 | // Load e.g. qt_de.qm 91 | if (qtTranslatorBase.load("qt_" + lang, translation_path)) { 92 | QApplication::installTranslator(&qtTranslatorBase); 93 | } 94 | 95 | // Load e.g. qt_de_DE.qm 96 | if (qtTranslator.load("qt_" + lang_territory, translation_path)) { 97 | QApplication::installTranslator(&qtTranslator); 98 | } 99 | 100 | // Load e.g. cachyos-kernel-manager_de.qm (shortcut "de" needs to be defined in bitcoin.qrc) 101 | if (translatorBase.load(lang, ":/translations/")) { 102 | QApplication::installTranslator(&translatorBase); 103 | } 104 | 105 | // Load e.g. cachyos-kernel-manager_de_DE.qm (shortcut "de_DE" needs to be defined in bitcoin.qrc) 106 | if (translator.load(lang_territory, ":/translations/")) { 107 | QApplication::installTranslator(&translator); 108 | } 109 | } 110 | 111 | } // namespace 112 | 113 | auto main(int argc, char** argv) -> std::int32_t { 114 | std::signal(SIGSEGV, [](auto) { manager::backtrace::print_trace(); }); 115 | 116 | QSharedMemory sharedMemoryLock("CachyOS-SM-lock"); 117 | if (IsInstanceAlreadyRunning(sharedMemoryLock)) { 118 | return -1; 119 | } 120 | 121 | /// 1. Basic Qt initialization (not dependent on parameters or configuration) 122 | #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) 123 | // Generate high-dpi pixmaps 124 | QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); 125 | QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); 126 | #endif 127 | 128 | /// 2. Application identification 129 | QApplication::setOrganizationName("CachyOS"); 130 | QApplication::setOrganizationDomain("cachyos.org"); 131 | QApplication::setApplicationName("CachyOS-SM"); 132 | 133 | // Set application attributes 134 | const QApplication app(argc, argv); 135 | 136 | /// 3. Initialization of translations 137 | QTranslator qtTranslatorBase; 138 | QTranslator qtTranslator; 139 | QTranslator translatorBase; 140 | QTranslator translator; 141 | initTranslations(qtTranslatorBase, qtTranslator, translatorBase, translator); 142 | 143 | MainWindow w; 144 | w.show(); 145 | return app.exec(); // NOLINT 146 | } 147 | -------------------------------------------------------------------------------- /src/sm-window.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2022-2025 Vladislav Nepogodin 2 | // 3 | // This file is part of CachyOS sysctl manager. 4 | // 5 | // This program is free software; you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation; either version 2 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License along 16 | // with this program; if not, write to the Free Software Foundation, Inc., 17 | // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | 19 | #include "sm-window.hpp" 20 | #include "sysctl_option.hpp" 21 | #include "utils.hpp" 22 | 23 | #include // for find_if 24 | #include // for permissions 25 | #include // for optional 26 | #include // for ranges::* 27 | #include // for string_view 28 | #include // for this_thread 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | #include 39 | 40 | namespace fs = std::filesystem; 41 | using namespace std::string_literals; 42 | using namespace std::string_view_literals; 43 | 44 | namespace { 45 | 46 | auto generate_script_from_options(QTreeWidget* tree_options, std::span change_list) noexcept -> std::optional { 47 | std::string sysctl_script{}; 48 | 49 | // Iterate through changed options, 50 | // and generate cmd to apply changes. 51 | for (auto&& option_name : change_list) { 52 | const auto& items = tree_options->findItems(option_name, Qt::MatchExactly, TreeCol::Name); 53 | /* clang-format off */ 54 | if (items.isEmpty()) { continue; } 55 | /* clang-format on */ 56 | 57 | const auto& found_item = items.at(0); 58 | 59 | auto item_value = found_item->text(TreeCol::Value); 60 | auto fileline = fmt::format("{} = {}\n", option_name.toStdString(), item_value.toStdString()); 61 | sysctl_script += fileline; 62 | } 63 | 64 | // FIXME(vnepogodin): should be own executable which accepts options as input(e.g. CLI tool as backend for the GUI) 65 | const auto sysctl_filepath = "/tmp/.sysctl-manager.bashscript"s; 66 | if (!utils::write_to_file(sysctl_filepath, sysctl_script)) { 67 | return std::nullopt; 68 | } 69 | 70 | auto sysctl_cmd = fmt::format("sysctl -f '{}'", sysctl_filepath); 71 | return QString::fromStdString(sysctl_cmd); 72 | } 73 | 74 | void init_options_tree_widget(QTreeWidget* tree_options, std::span options) noexcept { 75 | for (auto&& sysctl_option : options) { 76 | auto&& option_name = std::string{sysctl_option.get_name()}; 77 | auto&& option_value = std::string{sysctl_option.get_value()}; 78 | 79 | auto* widget_item = new QTreeWidgetItem(tree_options); // NOLINT 80 | widget_item->setText(TreeCol::Name, QString::fromStdString(option_name)); 81 | widget_item->setText(TreeCol::Value, QString::fromStdString(option_value)); 82 | widget_item->setText(TreeCol::Displayed, QStringLiteral("true")); 83 | widget_item->setFlags(widget_item->flags() | Qt::ItemIsEditable); 84 | } 85 | } 86 | 87 | } // namespace 88 | 89 | MainWindow::MainWindow(QWidget* parent) 90 | : QMainWindow(parent) { 91 | m_ui->setupUi(this); 92 | 93 | setAttribute(Qt::WA_NativeWindow); 94 | setWindowFlags(Qt::Window); // for the close, min and max buttons 95 | 96 | m_ui->ok->setEnabled(false); 97 | 98 | // Create worker thread 99 | m_worker = new Work([&]() { 100 | while (m_thread_running.load(std::memory_order_consume)) { 101 | std::unique_lock lock(m_mutex); 102 | fmt::print(stderr, "Waiting... \n"); 103 | 104 | m_cv.wait(lock, [&] { return m_running.load(std::memory_order_consume); }); 105 | 106 | if (m_running.load(std::memory_order_consume) && m_thread_running.load(std::memory_order_consume)) { 107 | m_ui->ok->setEnabled(false); 108 | 109 | // Generate script for sysctl 110 | auto bash_script = generate_script_from_options(m_ui->treeOptions, std::span{m_change_list}); 111 | if (!bash_script) { 112 | QMessageBox::critical(this, "CachyOS Sysctl Manager", tr("Wasn't able to write sysctl options to file!")); 113 | // Reset state 114 | m_running.store(false, std::memory_order_relaxed); 115 | m_ui->ok->setEnabled(!m_change_list.isEmpty()); 116 | continue; 117 | } 118 | utils::runCmdTerminal(std::move(*bash_script), true); 119 | 120 | // Fetch new changes 121 | m_options.clear(); 122 | m_options = SysctlOption::get_options(); 123 | 124 | // Go through change_list and remove changed ones 125 | auto change_list = m_change_list; 126 | auto* tree_options = m_ui->treeOptions; 127 | for (auto&& option_name : change_list) { 128 | auto functor = [&option_name](auto&& option) { return option_name == option.get_name(); }; 129 | auto result = std::ranges::find_if(m_options, functor); 130 | /* clang-format off */ 131 | if (result == std::ranges::end(m_options)) { continue; } 132 | /* clang-format on */ 133 | 134 | const auto& items = tree_options->findItems(option_name, Qt::MatchExactly, TreeCol::Name); 135 | /* clang-format off */ 136 | if (items.isEmpty()) { continue; } 137 | /* clang-format on */ 138 | 139 | const auto& item_value = items.at(0)->text(TreeCol::Value); 140 | if (item_value == result->get_value()) { 141 | m_change_list.removeOne(option_name); 142 | } 143 | } 144 | 145 | // Reset state 146 | m_running.store(false, std::memory_order_relaxed); 147 | m_ui->ok->setEnabled(!m_change_list.isEmpty()); 148 | } 149 | } 150 | }); 151 | 152 | m_worker->moveToThread(m_worker_th); 153 | // name to appear in ps, task manager, etc. 154 | m_worker_th->setObjectName("WorkerThread"); 155 | 156 | m_ui->ok->setEnabled(false); 157 | 158 | // Setup tree widget 159 | auto* tree_options = m_ui->treeOptions; 160 | QStringList column_names; 161 | column_names << "Name" 162 | << "Value"; 163 | tree_options->setHeaderLabels(column_names); 164 | tree_options->hideColumn(TreeCol::Displayed); // Displayed status true/false 165 | tree_options->hideColumn(TreeCol::Immutable); // Immutable status true/false 166 | tree_options->header()->setSectionResizeMode(QHeaderView::ResizeToContents); 167 | 168 | // Set context menu policy 169 | tree_options->setContextMenuPolicy(Qt::CustomContextMenu); 170 | 171 | tree_options->setEditTriggers(QTreeWidget::NoEditTriggers); 172 | tree_options->blockSignals(true); 173 | 174 | // TODO(vnepogodin): parallelize it 175 | auto a2 = std::async(std::launch::deferred, [&] { 176 | const std::lock_guard guard(m_mutex); 177 | init_options_tree_widget(tree_options, std::span{m_options}); 178 | }); 179 | 180 | if (m_options.empty()) { 181 | QMessageBox::critical(this, "CachyOS Sysctl Manager", tr("Wasn't able to parse kernel sysfs!")); 182 | } 183 | 184 | // Connect buttons signal 185 | connect(m_ui->cancel, &QPushButton::clicked, this, &MainWindow::on_cancel); 186 | connect(m_ui->ok, &QPushButton::clicked, this, &MainWindow::on_execute); 187 | 188 | // Connect worker thread signals 189 | connect(m_worker_th, &QThread::finished, m_worker, &QObject::deleteLater); 190 | connect(m_worker_th, &QThread::started, m_worker, &Work::doHeavyCalculations, Qt::QueuedConnection); 191 | 192 | // connect search box 193 | connect(m_ui->search_option, &QLineEdit::textChanged, this, &MainWindow::find_options); 194 | 195 | // Connect tree widget 196 | connect(tree_options, &QTreeWidget::itemChanged, this, &MainWindow::item_changed); 197 | connect(tree_options, &QTreeWidget::itemDoubleClicked, this, &MainWindow::on_item_double_clicked); 198 | 199 | // Wait for async function to finish 200 | a2.wait(); 201 | tree_options->blockSignals(false); 202 | } 203 | 204 | MainWindow::~MainWindow() { 205 | std::this_thread::sleep_for(std::chrono::milliseconds(1)); 206 | if (m_worker_th != nullptr) { 207 | m_worker_th->exit(); 208 | } 209 | } 210 | 211 | // Find package in view 212 | void MainWindow::find_options() noexcept { 213 | const auto& word = m_ui->search_option->text(); 214 | /* clang-format off */ 215 | if (word.length() == 1) { return; } 216 | /* clang-format on */ 217 | 218 | auto* tree_options = m_ui->treeOptions; 219 | auto found_items = tree_options->findItems(word, Qt::MatchContains, TreeCol::Name); 220 | 221 | for (QTreeWidgetItemIterator it(tree_options); *it; ++it) { 222 | auto current_item = *it; 223 | current_item->setHidden(current_item->text(TreeCol::Displayed) != QLatin1String("true") || !found_items.contains(current_item)); 224 | } 225 | for (int i = 0; i < tree_options->columnCount(); ++i) { 226 | tree_options->resizeColumnToContents(i); 227 | } 228 | } 229 | 230 | // When double-clicking on value column 231 | void MainWindow::on_item_double_clicked(QTreeWidgetItem* item, int column) noexcept { 232 | switch (column) { 233 | case TreeCol::Value: 234 | m_ui->treeOptions->editItem(item, column); 235 | break; 236 | case TreeCol::Name: { 237 | auto item_name = item->text(TreeCol::Name); 238 | auto functor = [&item_name](auto&& option) { return item_name == option.get_name(); }; 239 | if (auto result = std::ranges::find_if(m_options, functor); result != std::ranges::end(m_options)) { 240 | auto doc_url = QString::fromStdString(std::string{result->get_doc()}); 241 | QDesktopServices::openUrl(QUrl(doc_url)); 242 | } 243 | break; 244 | } 245 | default: 246 | break; 247 | } 248 | } 249 | 250 | // When selecting on item in the list 251 | void MainWindow::item_changed(QTreeWidgetItem* item, int /*unused*/) noexcept { 252 | build_change_list(item); 253 | } 254 | 255 | // Build the change_list when selecting on item in the tree 256 | void MainWindow::build_change_list(QTreeWidgetItem* item) noexcept { 257 | auto item_value = item->text(TreeCol::Value); 258 | auto item_name = item->text(TreeCol::Name); 259 | 260 | for (auto&& sysctl_option : m_options) { 261 | /* clang-format off */ 262 | if (item_name != sysctl_option.get_name()) { continue; } 263 | /* clang-format on */ 264 | 265 | if (item_value != sysctl_option.get_value()) { 266 | m_ui->ok->setEnabled(true); 267 | m_change_list.append(item_name); 268 | return; 269 | } 270 | if (item_value == sysctl_option.get_value()) { 271 | m_change_list.removeOne(item_name); 272 | return; 273 | } 274 | } 275 | 276 | if (m_change_list.isEmpty()) { 277 | m_ui->ok->setEnabled(false); 278 | } 279 | } 280 | 281 | void MainWindow::closeEvent(QCloseEvent* event) { 282 | // Exit worker thread 283 | m_running.store(true, std::memory_order_relaxed); 284 | m_thread_running.store(false, std::memory_order_relaxed); 285 | m_cv.notify_all(); 286 | 287 | // Execute parent function 288 | QWidget::closeEvent(event); 289 | } 290 | 291 | void MainWindow::on_cancel() noexcept { 292 | close(); 293 | } 294 | 295 | void Work::doHeavyCalculations() { 296 | m_func(); 297 | } 298 | 299 | void MainWindow::on_execute() noexcept { 300 | if (m_running.load(std::memory_order_consume)) { 301 | return; 302 | } 303 | m_running.store(true, std::memory_order_relaxed); 304 | m_thread_running.store(true, std::memory_order_relaxed); 305 | m_cv.notify_all(); 306 | m_worker_th->start(); 307 | } 308 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | {one line to give the program's name and a brief idea of what it does.} 635 | Copyright (C) {year} {name of author} 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | {project} Copyright (C) {year} {fullname} 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /cmake/CPM.cmake: -------------------------------------------------------------------------------- 1 | # CPM.cmake - CMake's missing package manager 2 | # =========================================== 3 | # See https://github.com/cpm-cmake/CPM.cmake for usage and update instructions. 4 | # 5 | # MIT License 6 | # ----------- 7 | #[[ 8 | Copyright (c) 2021 Lars Melchior and additional contributors 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | ]] 28 | 29 | cmake_minimum_required(VERSION 3.14 FATAL_ERROR) 30 | 31 | set(CURRENT_CPM_VERSION 1.0.0-development-version) 32 | 33 | if(CPM_DIRECTORY) 34 | if(NOT CPM_DIRECTORY STREQUAL CMAKE_CURRENT_LIST_DIR) 35 | if(CPM_VERSION VERSION_LESS CURRENT_CPM_VERSION) 36 | message( 37 | AUTHOR_WARNING 38 | "${CPM_INDENT} \ 39 | A dependency is using a more recent CPM version (${CURRENT_CPM_VERSION}) than the current project (${CPM_VERSION}). \ 40 | It is recommended to upgrade CPM to the most recent version. \ 41 | See https://github.com/cpm-cmake/CPM.cmake for more information." 42 | ) 43 | endif() 44 | if(${CMAKE_VERSION} VERSION_LESS "3.17.0") 45 | include(FetchContent) 46 | endif() 47 | return() 48 | endif() 49 | 50 | get_property( 51 | CPM_INITIALIZED GLOBAL "" 52 | PROPERTY CPM_INITIALIZED 53 | SET 54 | ) 55 | if(CPM_INITIALIZED) 56 | return() 57 | endif() 58 | endif() 59 | 60 | if(CURRENT_CPM_VERSION MATCHES "development-version") 61 | message(WARNING "Your project is using an unstable development version of CPM.cmake. \ 62 | Please update to a recent release if possible. \ 63 | See https://github.com/cpm-cmake/CPM.cmake for details." 64 | ) 65 | endif() 66 | 67 | set_property(GLOBAL PROPERTY CPM_INITIALIZED true) 68 | 69 | option(CPM_USE_LOCAL_PACKAGES "Always try to use `find_package` to get dependencies" 70 | $ENV{CPM_USE_LOCAL_PACKAGES} 71 | ) 72 | option(CPM_LOCAL_PACKAGES_ONLY "Only use `find_package` to get dependencies" 73 | $ENV{CPM_LOCAL_PACKAGES_ONLY} 74 | ) 75 | option(CPM_DOWNLOAD_ALL "Always download dependencies from source" $ENV{CPM_DOWNLOAD_ALL}) 76 | option(CPM_DONT_UPDATE_MODULE_PATH "Don't update the module path to allow using find_package" 77 | $ENV{CPM_DONT_UPDATE_MODULE_PATH} 78 | ) 79 | option(CPM_DONT_CREATE_PACKAGE_LOCK "Don't create a package lock file in the binary path" 80 | $ENV{CPM_DONT_CREATE_PACKAGE_LOCK} 81 | ) 82 | option(CPM_INCLUDE_ALL_IN_PACKAGE_LOCK 83 | "Add all packages added through CPM.cmake to the package lock" 84 | $ENV{CPM_INCLUDE_ALL_IN_PACKAGE_LOCK} 85 | ) 86 | option(CPM_USE_NAMED_CACHE_DIRECTORIES 87 | "Use additional directory of package name in cache on the most nested level." 88 | $ENV{CPM_USE_NAMED_CACHE_DIRECTORIES} 89 | ) 90 | 91 | set(CPM_VERSION 92 | ${CURRENT_CPM_VERSION} 93 | CACHE INTERNAL "" 94 | ) 95 | set(CPM_DIRECTORY 96 | ${CMAKE_CURRENT_LIST_DIR} 97 | CACHE INTERNAL "" 98 | ) 99 | set(CPM_FILE 100 | ${CMAKE_CURRENT_LIST_FILE} 101 | CACHE INTERNAL "" 102 | ) 103 | set(CPM_PACKAGES 104 | "" 105 | CACHE INTERNAL "" 106 | ) 107 | set(CPM_DRY_RUN 108 | OFF 109 | CACHE INTERNAL "Don't download or configure dependencies (for testing)" 110 | ) 111 | 112 | if(DEFINED ENV{CPM_SOURCE_CACHE}) 113 | set(CPM_SOURCE_CACHE_DEFAULT $ENV{CPM_SOURCE_CACHE}) 114 | else() 115 | set(CPM_SOURCE_CACHE_DEFAULT OFF) 116 | endif() 117 | 118 | set(CPM_SOURCE_CACHE 119 | ${CPM_SOURCE_CACHE_DEFAULT} 120 | CACHE PATH "Directory to download CPM dependencies" 121 | ) 122 | 123 | if(NOT CPM_DONT_UPDATE_MODULE_PATH) 124 | set(CPM_MODULE_PATH 125 | "${CMAKE_BINARY_DIR}/CPM_modules" 126 | CACHE INTERNAL "" 127 | ) 128 | # remove old modules 129 | file(REMOVE_RECURSE ${CPM_MODULE_PATH}) 130 | file(MAKE_DIRECTORY ${CPM_MODULE_PATH}) 131 | # locally added CPM modules should override global packages 132 | set(CMAKE_MODULE_PATH "${CPM_MODULE_PATH};${CMAKE_MODULE_PATH}") 133 | endif() 134 | 135 | if(NOT CPM_DONT_CREATE_PACKAGE_LOCK) 136 | set(CPM_PACKAGE_LOCK_FILE 137 | "${CMAKE_BINARY_DIR}/cpm-package-lock.cmake" 138 | CACHE INTERNAL "" 139 | ) 140 | file(WRITE ${CPM_PACKAGE_LOCK_FILE} 141 | "# CPM Package Lock\n# This file should be committed to version control\n\n" 142 | ) 143 | endif() 144 | 145 | include(FetchContent) 146 | 147 | # Try to infer package name from git repository uri (path or url) 148 | function(cpm_package_name_from_git_uri URI RESULT) 149 | if("${URI}" MATCHES "([^/:]+)/?.git/?$") 150 | set(${RESULT} 151 | ${CMAKE_MATCH_1} 152 | PARENT_SCOPE 153 | ) 154 | else() 155 | unset(${RESULT} PARENT_SCOPE) 156 | endif() 157 | endfunction() 158 | 159 | # Try to infer package name and version from a url 160 | function(cpm_package_name_and_ver_from_url url outName outVer) 161 | if(url MATCHES "[/\\?]([a-zA-Z0-9_\\.-]+)\\.(tar|tar\\.gz|tar\\.bz2|zip|ZIP)(\\?|/|$)") 162 | # We matched an archive 163 | set(filename "${CMAKE_MATCH_1}") 164 | 165 | if(filename MATCHES "([a-zA-Z0-9_\\.-]+)[_-]v?(([0-9]+\\.)*[0-9]+[a-zA-Z0-9]*)") 166 | # We matched - (ie foo-1.2.3) 167 | set(${outName} 168 | "${CMAKE_MATCH_1}" 169 | PARENT_SCOPE 170 | ) 171 | set(${outVer} 172 | "${CMAKE_MATCH_2}" 173 | PARENT_SCOPE 174 | ) 175 | elseif(filename MATCHES "(([0-9]+\\.)+[0-9]+[a-zA-Z0-9]*)") 176 | # We couldn't find a name, but we found a version 177 | # 178 | # In many cases (which we don't handle here) the url would look something like 179 | # `irrelevant/ACTUAL_PACKAGE_NAME/irrelevant/1.2.3.zip`. In such a case we can't possibly 180 | # distinguish the package name from the irrelevant bits. Moreover if we try to match the 181 | # package name from the filename, we'd get bogus at best. 182 | unset(${outName} PARENT_SCOPE) 183 | set(${outVer} 184 | "${CMAKE_MATCH_1}" 185 | PARENT_SCOPE 186 | ) 187 | else() 188 | # Boldly assume that the file name is the package name. 189 | # 190 | # Yes, something like `irrelevant/ACTUAL_NAME/irrelevant/download.zip` will ruin our day, but 191 | # such cases should be quite rare. No popular service does this... we think. 192 | set(${outName} 193 | "${filename}" 194 | PARENT_SCOPE 195 | ) 196 | unset(${outVer} PARENT_SCOPE) 197 | endif() 198 | else() 199 | # No ideas yet what to do with non-archives 200 | unset(${outName} PARENT_SCOPE) 201 | unset(${outVer} PARENT_SCOPE) 202 | endif() 203 | endfunction() 204 | 205 | # Initialize logging prefix 206 | if(NOT CPM_INDENT) 207 | set(CPM_INDENT 208 | "CPM:" 209 | CACHE INTERNAL "" 210 | ) 211 | endif() 212 | 213 | function(cpm_find_package NAME VERSION) 214 | string(REPLACE " " ";" EXTRA_ARGS "${ARGN}") 215 | find_package(${NAME} ${VERSION} ${EXTRA_ARGS} QUIET) 216 | if(${CPM_ARGS_NAME}_FOUND) 217 | message(STATUS "${CPM_INDENT} using local package ${CPM_ARGS_NAME}@${VERSION}") 218 | CPMRegisterPackage(${CPM_ARGS_NAME} "${VERSION}") 219 | set(CPM_PACKAGE_FOUND 220 | YES 221 | PARENT_SCOPE 222 | ) 223 | else() 224 | set(CPM_PACKAGE_FOUND 225 | NO 226 | PARENT_SCOPE 227 | ) 228 | endif() 229 | endfunction() 230 | 231 | # Create a custom FindXXX.cmake module for a CPM package This prevents `find_package(NAME)` from 232 | # finding the system library 233 | function(cpm_create_module_file Name) 234 | if(NOT CPM_DONT_UPDATE_MODULE_PATH) 235 | # erase any previous modules 236 | file(WRITE ${CPM_MODULE_PATH}/Find${Name}.cmake 237 | "include(\"${CPM_FILE}\")\n${ARGN}\nset(${Name}_FOUND TRUE)" 238 | ) 239 | endif() 240 | endfunction() 241 | 242 | # Find a package locally or fallback to CPMAddPackage 243 | function(CPMFindPackage) 244 | set(oneValueArgs NAME VERSION GIT_TAG FIND_PACKAGE_ARGUMENTS) 245 | 246 | cmake_parse_arguments(CPM_ARGS "" "${oneValueArgs}" "" ${ARGN}) 247 | 248 | if(NOT DEFINED CPM_ARGS_VERSION) 249 | if(DEFINED CPM_ARGS_GIT_TAG) 250 | cpm_get_version_from_git_tag("${CPM_ARGS_GIT_TAG}" CPM_ARGS_VERSION) 251 | endif() 252 | endif() 253 | 254 | if(CPM_DOWNLOAD_ALL) 255 | CPMAddPackage(${ARGN}) 256 | cpm_export_variables(${CPM_ARGS_NAME}) 257 | return() 258 | endif() 259 | 260 | cpm_check_if_package_already_added(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}") 261 | if(CPM_PACKAGE_ALREADY_ADDED) 262 | cpm_export_variables(${CPM_ARGS_NAME}) 263 | return() 264 | endif() 265 | 266 | cpm_find_package(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}" ${CPM_ARGS_FIND_PACKAGE_ARGUMENTS}) 267 | 268 | if(NOT CPM_PACKAGE_FOUND) 269 | CPMAddPackage(${ARGN}) 270 | cpm_export_variables(${CPM_ARGS_NAME}) 271 | endif() 272 | 273 | endfunction() 274 | 275 | # checks if a package has been added before 276 | function(cpm_check_if_package_already_added CPM_ARGS_NAME CPM_ARGS_VERSION) 277 | if("${CPM_ARGS_NAME}" IN_LIST CPM_PACKAGES) 278 | CPMGetPackageVersion(${CPM_ARGS_NAME} CPM_PACKAGE_VERSION) 279 | if("${CPM_PACKAGE_VERSION}" VERSION_LESS "${CPM_ARGS_VERSION}") 280 | message( 281 | WARNING 282 | "${CPM_INDENT} requires a newer version of ${CPM_ARGS_NAME} (${CPM_ARGS_VERSION}) than currently included (${CPM_PACKAGE_VERSION})." 283 | ) 284 | endif() 285 | cpm_get_fetch_properties(${CPM_ARGS_NAME}) 286 | set(${CPM_ARGS_NAME}_ADDED NO) 287 | set(CPM_PACKAGE_ALREADY_ADDED 288 | YES 289 | PARENT_SCOPE 290 | ) 291 | cpm_export_variables(${CPM_ARGS_NAME}) 292 | else() 293 | set(CPM_PACKAGE_ALREADY_ADDED 294 | NO 295 | PARENT_SCOPE 296 | ) 297 | endif() 298 | endfunction() 299 | 300 | # Parse the argument of CPMAddPackage in case a single one was provided and convert it to a list of 301 | # arguments which can then be parsed idiomatically. For example gh:foo/bar@1.2.3 will be converted 302 | # to: GITHUB_REPOSITORY;foo/bar;VERSION;1.2.3 303 | function(cpm_parse_add_package_single_arg arg outArgs) 304 | # Look for a scheme 305 | if("${arg}" MATCHES "^([a-zA-Z]+):(.+)$") 306 | string(TOLOWER "${CMAKE_MATCH_1}" scheme) 307 | set(uri "${CMAKE_MATCH_2}") 308 | 309 | # Check for CPM-specific schemes 310 | if(scheme STREQUAL "gh") 311 | set(out "GITHUB_REPOSITORY;${uri}") 312 | set(packageType "git") 313 | elseif(scheme STREQUAL "gl") 314 | set(out "GITLAB_REPOSITORY;${uri}") 315 | set(packageType "git") 316 | elseif(scheme STREQUAL "bb") 317 | set(out "BITBUCKET_REPOSITORY;${uri}") 318 | set(packageType "git") 319 | # A CPM-specific scheme was not found. Looks like this is a generic URL so try to determine 320 | # type 321 | elseif(arg MATCHES ".git/?(@|#|$)") 322 | set(out "GIT_REPOSITORY;${arg}") 323 | set(packageType "git") 324 | else() 325 | # Fall back to a URL 326 | set(out "URL;${arg}") 327 | set(packageType "archive") 328 | 329 | # We could also check for SVN since FetchContent supports it, but SVN is so rare these days. 330 | # We just won't bother with the additional complexity it will induce in this function. SVN is 331 | # done by multi-arg 332 | endif() 333 | else() 334 | if(arg MATCHES ".git/?(@|#|$)") 335 | set(out "GIT_REPOSITORY;${arg}") 336 | set(packageType "git") 337 | else() 338 | # Give up 339 | message(FATAL_ERROR "CPM: Can't determine package type of '${arg}'") 340 | endif() 341 | endif() 342 | 343 | # For all packages we interpret @... as version. Only replace the last occurence. Thus URIs 344 | # containing '@' can be used 345 | string(REGEX REPLACE "@([^@]+)$" ";VERSION;\\1" out "${out}") 346 | 347 | # Parse the rest according to package type 348 | if(packageType STREQUAL "git") 349 | # For git repos we interpret #... as a tag or branch or commit hash 350 | string(REGEX REPLACE "#([^#]+)$" ";GIT_TAG;\\1" out "${out}") 351 | elseif(packageType STREQUAL "archive") 352 | # For archives we interpret #... as a URL hash. 353 | string(REGEX REPLACE "#([^#]+)$" ";URL_HASH;\\1" out "${out}") 354 | # We don't try to parse the version if it's not provided explicitly. cpm_get_version_from_url 355 | # should do this at a later point 356 | else() 357 | # We should never get here. This is an assertion and hitting it means there's a bug in the code 358 | # above. A packageType was set, but not handled by this if-else. 359 | message(FATAL_ERROR "CPM: Unsupported package type '${packageType}' of '${arg}'") 360 | endif() 361 | 362 | set(${outArgs} 363 | ${out} 364 | PARENT_SCOPE 365 | ) 366 | endfunction() 367 | 368 | # Check that the working directory for a git repo is clean 369 | function(cpm_check_git_working_dir_is_clean repoPath gitTag isClean) 370 | 371 | find_package(Git REQUIRED) 372 | 373 | if(NOT GIT_EXECUTABLE) 374 | # No git executable, assume directory is clean 375 | set(${isClean} 376 | TRUE 377 | PARENT_SCOPE 378 | ) 379 | return() 380 | endif() 381 | 382 | # check for uncommited changes 383 | execute_process( 384 | COMMAND ${GIT_EXECUTABLE} status --porcelain 385 | RESULT_VARIABLE resultGitStatus 386 | OUTPUT_VARIABLE repoStatus 387 | OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET 388 | WORKING_DIRECTORY ${repoPath} 389 | ) 390 | if(resultGitStatus) 391 | # not supposed to happen, assume clean anyway 392 | message(WARNING "Calling git status on folder ${repoPath} failed") 393 | set(${isClean} 394 | TRUE 395 | PARENT_SCOPE 396 | ) 397 | return() 398 | endif() 399 | 400 | if(NOT "${repoStatus}" STREQUAL "") 401 | set(${isClean} 402 | FALSE 403 | PARENT_SCOPE 404 | ) 405 | return() 406 | endif() 407 | 408 | # check for commited changes 409 | execute_process( 410 | COMMAND ${GIT_EXECUTABLE} diff -s --exit-code ${gitTag} 411 | RESULT_VARIABLE resultGitDiff 412 | OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_QUIET 413 | WORKING_DIRECTORY ${repoPath} 414 | ) 415 | 416 | if(${resultGitDiff} EQUAL 0) 417 | set(${isClean} 418 | TRUE 419 | PARENT_SCOPE 420 | ) 421 | else() 422 | set(${isClean} 423 | FALSE 424 | PARENT_SCOPE 425 | ) 426 | endif() 427 | 428 | endfunction() 429 | 430 | # Download and add a package from source 431 | function(CPMAddPackage) 432 | list(LENGTH ARGN argnLength) 433 | if(argnLength EQUAL 1) 434 | cpm_parse_add_package_single_arg("${ARGN}" ARGN) 435 | 436 | # The shorthand syntax implies EXCLUDE_FROM_ALL 437 | set(ARGN "${ARGN};EXCLUDE_FROM_ALL;YES") 438 | endif() 439 | 440 | set(oneValueArgs 441 | NAME 442 | FORCE 443 | VERSION 444 | GIT_TAG 445 | DOWNLOAD_ONLY 446 | GITHUB_REPOSITORY 447 | GITLAB_REPOSITORY 448 | BITBUCKET_REPOSITORY 449 | GIT_REPOSITORY 450 | SOURCE_DIR 451 | DOWNLOAD_COMMAND 452 | FIND_PACKAGE_ARGUMENTS 453 | NO_CACHE 454 | GIT_SHALLOW 455 | EXCLUDE_FROM_ALL 456 | SOURCE_SUBDIR 457 | ) 458 | 459 | set(multiValueArgs URL OPTIONS) 460 | 461 | cmake_parse_arguments(CPM_ARGS "" "${oneValueArgs}" "${multiValueArgs}" "${ARGN}") 462 | 463 | # Set default values for arguments 464 | 465 | if(NOT DEFINED CPM_ARGS_VERSION) 466 | if(DEFINED CPM_ARGS_GIT_TAG) 467 | cpm_get_version_from_git_tag("${CPM_ARGS_GIT_TAG}" CPM_ARGS_VERSION) 468 | endif() 469 | endif() 470 | 471 | if(CPM_ARGS_DOWNLOAD_ONLY) 472 | set(DOWNLOAD_ONLY ${CPM_ARGS_DOWNLOAD_ONLY}) 473 | else() 474 | set(DOWNLOAD_ONLY NO) 475 | endif() 476 | 477 | if(DEFINED CPM_ARGS_GITHUB_REPOSITORY) 478 | set(CPM_ARGS_GIT_REPOSITORY "https://github.com/${CPM_ARGS_GITHUB_REPOSITORY}.git") 479 | elseif(DEFINED CPM_ARGS_GITLAB_REPOSITORY) 480 | set(CPM_ARGS_GIT_REPOSITORY "https://gitlab.com/${CPM_ARGS_GITLAB_REPOSITORY}.git") 481 | elseif(DEFINED CPM_ARGS_BITBUCKET_REPOSITORY) 482 | set(CPM_ARGS_GIT_REPOSITORY "https://bitbucket.org/${CPM_ARGS_BITBUCKET_REPOSITORY}.git") 483 | endif() 484 | 485 | if(DEFINED CPM_ARGS_GIT_REPOSITORY) 486 | list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_REPOSITORY ${CPM_ARGS_GIT_REPOSITORY}) 487 | if(NOT DEFINED CPM_ARGS_GIT_TAG) 488 | set(CPM_ARGS_GIT_TAG v${CPM_ARGS_VERSION}) 489 | endif() 490 | 491 | # If a name wasn't provided, try to infer it from the git repo 492 | if(NOT DEFINED CPM_ARGS_NAME) 493 | cpm_package_name_from_git_uri(${CPM_ARGS_GIT_REPOSITORY} CPM_ARGS_NAME) 494 | endif() 495 | endif() 496 | 497 | set(CPM_SKIP_FETCH FALSE) 498 | 499 | if(DEFINED CPM_ARGS_GIT_TAG) 500 | list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_TAG ${CPM_ARGS_GIT_TAG}) 501 | # If GIT_SHALLOW is explicitly specified, honor the value. 502 | if(DEFINED CPM_ARGS_GIT_SHALLOW) 503 | list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_SHALLOW ${CPM_ARGS_GIT_SHALLOW}) 504 | endif() 505 | endif() 506 | 507 | if(DEFINED CPM_ARGS_URL) 508 | # If a name or version aren't provided, try to infer them from the URL 509 | list(GET CPM_ARGS_URL 0 firstUrl) 510 | cpm_package_name_and_ver_from_url(${firstUrl} nameFromUrl verFromUrl) 511 | # If we fail to obtain name and version from the first URL, we could try other URLs if any. 512 | # However multiple URLs are expected to be quite rare, so for now we won't bother. 513 | 514 | # If the caller provided their own name and version, they trump the inferred ones. 515 | if(NOT DEFINED CPM_ARGS_NAME) 516 | set(CPM_ARGS_NAME ${nameFromUrl}) 517 | endif() 518 | if(NOT DEFINED CPM_ARGS_VERSION) 519 | set(CPM_ARGS_VERSION ${verFromUrl}) 520 | endif() 521 | 522 | list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS URL "${CPM_ARGS_URL}") 523 | endif() 524 | 525 | # Check for required arguments 526 | 527 | if(NOT DEFINED CPM_ARGS_NAME) 528 | message( 529 | FATAL_ERROR 530 | "CPM: 'NAME' was not provided and couldn't be automatically inferred for package added with arguments: '${ARGN}'" 531 | ) 532 | endif() 533 | 534 | # Check if package has been added before 535 | cpm_check_if_package_already_added(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}") 536 | if(CPM_PACKAGE_ALREADY_ADDED) 537 | cpm_export_variables(${CPM_ARGS_NAME}) 538 | return() 539 | endif() 540 | 541 | # Check for manual overrides 542 | if(NOT CPM_ARGS_FORCE AND NOT "${CPM_${CPM_ARGS_NAME}_SOURCE}" STREQUAL "") 543 | set(PACKAGE_SOURCE ${CPM_${CPM_ARGS_NAME}_SOURCE}) 544 | set(CPM_${CPM_ARGS_NAME}_SOURCE "") 545 | CPMAddPackage( 546 | NAME "${CPM_ARGS_NAME}" 547 | SOURCE_DIR "${PACKAGE_SOURCE}" 548 | EXCLUDE_FROM_ALL "${CPM_ARGS_EXCLUDE_FROM_ALL}" 549 | OPTIONS "${CPM_ARGS_OPTIONS}" 550 | SOURCE_SUBDIR "${CPM_ARGS_SOURCE_SUBDIR}" 551 | DOWNLOAD_ONLY "${DOWNLOAD_ONLY}" 552 | FORCE True 553 | ) 554 | cpm_export_variables(${CPM_ARGS_NAME}) 555 | return() 556 | endif() 557 | 558 | # Check for available declaration 559 | if(NOT CPM_ARGS_FORCE AND NOT "${CPM_DECLARATION_${CPM_ARGS_NAME}}" STREQUAL "") 560 | set(declaration ${CPM_DECLARATION_${CPM_ARGS_NAME}}) 561 | set(CPM_DECLARATION_${CPM_ARGS_NAME} "") 562 | CPMAddPackage(${declaration}) 563 | cpm_export_variables(${CPM_ARGS_NAME}) 564 | # checking again to ensure version and option compatibility 565 | cpm_check_if_package_already_added(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}") 566 | return() 567 | endif() 568 | 569 | if(CPM_USE_LOCAL_PACKAGES OR CPM_LOCAL_PACKAGES_ONLY) 570 | cpm_find_package(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}" ${CPM_ARGS_FIND_PACKAGE_ARGUMENTS}) 571 | 572 | if(CPM_PACKAGE_FOUND) 573 | cpm_export_variables(${CPM_ARGS_NAME}) 574 | return() 575 | endif() 576 | 577 | if(CPM_LOCAL_PACKAGES_ONLY) 578 | message( 579 | SEND_ERROR 580 | "CPM: ${CPM_ARGS_NAME} not found via find_package(${CPM_ARGS_NAME} ${CPM_ARGS_VERSION})" 581 | ) 582 | endif() 583 | endif() 584 | 585 | CPMRegisterPackage("${CPM_ARGS_NAME}" "${CPM_ARGS_VERSION}") 586 | 587 | if(DEFINED CPM_ARGS_GIT_TAG) 588 | set(PACKAGE_INFO "${CPM_ARGS_GIT_TAG}") 589 | elseif(DEFINED CPM_ARGS_SOURCE_DIR) 590 | set(PACKAGE_INFO "${CPM_ARGS_SOURCE_DIR}") 591 | else() 592 | set(PACKAGE_INFO "${CPM_ARGS_VERSION}") 593 | endif() 594 | 595 | if(DEFINED FETCHCONTENT_BASE_DIR) 596 | # respect user's FETCHCONTENT_BASE_DIR if set 597 | set(CPM_FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR}) 598 | else() 599 | set(CPM_FETCHCONTENT_BASE_DIR ${CMAKE_BINARY_DIR}/_deps) 600 | endif() 601 | 602 | if(DEFINED CPM_ARGS_DOWNLOAD_COMMAND) 603 | list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS DOWNLOAD_COMMAND ${CPM_ARGS_DOWNLOAD_COMMAND}) 604 | elseif(DEFINED CPM_ARGS_SOURCE_DIR) 605 | list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS SOURCE_DIR ${CPM_ARGS_SOURCE_DIR}) 606 | elseif(CPM_SOURCE_CACHE AND NOT CPM_ARGS_NO_CACHE) 607 | string(TOLOWER ${CPM_ARGS_NAME} lower_case_name) 608 | set(origin_parameters ${CPM_ARGS_UNPARSED_ARGUMENTS}) 609 | list(SORT origin_parameters) 610 | if(CPM_USE_NAMED_CACHE_DIRECTORIES) 611 | string(SHA1 origin_hash "${origin_parameters};NEW_CACHE_STRUCTURE_TAG") 612 | set(download_directory ${CPM_SOURCE_CACHE}/${lower_case_name}/${origin_hash}/${CPM_ARGS_NAME}) 613 | else() 614 | string(SHA1 origin_hash "${origin_parameters}") 615 | set(download_directory ${CPM_SOURCE_CACHE}/${lower_case_name}/${origin_hash}) 616 | endif() 617 | # Expand `download_directory` relative path. This is important because EXISTS doesn't work for 618 | # relative paths. 619 | get_filename_component(download_directory ${download_directory} ABSOLUTE) 620 | list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS SOURCE_DIR ${download_directory}) 621 | if(EXISTS ${download_directory}) 622 | cpm_store_fetch_properties( 623 | ${CPM_ARGS_NAME} "${download_directory}" 624 | "${CPM_FETCHCONTENT_BASE_DIR}/${lower_case_name}-build" 625 | ) 626 | cpm_get_fetch_properties("${CPM_ARGS_NAME}") 627 | 628 | if(DEFINED CPM_ARGS_GIT_TAG) 629 | # warn if cache has been changed since checkout 630 | cpm_check_git_working_dir_is_clean(${download_directory} ${CPM_ARGS_GIT_TAG} IS_CLEAN) 631 | if(NOT ${IS_CLEAN}) 632 | message(WARNING "Cache for ${CPM_ARGS_NAME} (${download_directory}) is dirty") 633 | endif() 634 | endif() 635 | 636 | cpm_add_subdirectory( 637 | "${CPM_ARGS_NAME}" "${DOWNLOAD_ONLY}" 638 | "${${CPM_ARGS_NAME}_SOURCE_DIR}/${CPM_ARGS_SOURCE_SUBDIR}" "${${CPM_ARGS_NAME}_BINARY_DIR}" 639 | "${CPM_ARGS_EXCLUDE_FROM_ALL}" "${CPM_ARGS_OPTIONS}" 640 | ) 641 | set(CPM_SKIP_FETCH TRUE) 642 | set(PACKAGE_INFO "${PACKAGE_INFO} at ${download_directory}") 643 | else() 644 | # Enable shallow clone when GIT_TAG is not a commit hash. Our guess may not be accurate, but 645 | # it should guarantee no commit hash get mis-detected. 646 | if(NOT DEFINED CPM_ARGS_GIT_SHALLOW) 647 | cpm_is_git_tag_commit_hash("${CPM_ARGS_GIT_TAG}" IS_HASH) 648 | if(NOT ${IS_HASH}) 649 | list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_SHALLOW TRUE) 650 | endif() 651 | endif() 652 | 653 | # remove timestamps so CMake will re-download the dependency 654 | file(REMOVE_RECURSE ${CPM_FETCHCONTENT_BASE_DIR}/${lower_case_name}-subbuild) 655 | set(PACKAGE_INFO "${PACKAGE_INFO} to ${download_directory}") 656 | endif() 657 | endif() 658 | 659 | cpm_create_module_file(${CPM_ARGS_NAME} "CPMAddPackage(\"${ARGN}\")") 660 | 661 | if(CPM_PACKAGE_LOCK_ENABLED) 662 | if((CPM_ARGS_VERSION AND NOT CPM_ARGS_SOURCE_DIR) OR CPM_INCLUDE_ALL_IN_PACKAGE_LOCK) 663 | cpm_add_to_package_lock(${CPM_ARGS_NAME} "${ARGN}") 664 | elseif(CPM_ARGS_SOURCE_DIR) 665 | cpm_add_comment_to_package_lock(${CPM_ARGS_NAME} "local directory") 666 | else() 667 | cpm_add_comment_to_package_lock(${CPM_ARGS_NAME} "${ARGN}") 668 | endif() 669 | endif() 670 | 671 | message( 672 | STATUS "${CPM_INDENT} adding package ${CPM_ARGS_NAME}@${CPM_ARGS_VERSION} (${PACKAGE_INFO})" 673 | ) 674 | 675 | if(NOT CPM_SKIP_FETCH) 676 | cpm_declare_fetch( 677 | "${CPM_ARGS_NAME}" "${CPM_ARGS_VERSION}" "${PACKAGE_INFO}" "${CPM_ARGS_UNPARSED_ARGUMENTS}" 678 | ) 679 | cpm_fetch_package("${CPM_ARGS_NAME}" populated) 680 | if(${populated}) 681 | cpm_add_subdirectory( 682 | "${CPM_ARGS_NAME}" "${DOWNLOAD_ONLY}" 683 | "${${CPM_ARGS_NAME}_SOURCE_DIR}/${CPM_ARGS_SOURCE_SUBDIR}" "${${CPM_ARGS_NAME}_BINARY_DIR}" 684 | "${CPM_ARGS_EXCLUDE_FROM_ALL}" "${CPM_ARGS_OPTIONS}" 685 | ) 686 | endif() 687 | cpm_get_fetch_properties("${CPM_ARGS_NAME}") 688 | endif() 689 | 690 | set(${CPM_ARGS_NAME}_ADDED YES) 691 | cpm_export_variables("${CPM_ARGS_NAME}") 692 | endfunction() 693 | 694 | # Fetch a previously declared package 695 | macro(CPMGetPackage Name) 696 | if(DEFINED "CPM_DECLARATION_${Name}") 697 | CPMAddPackage(NAME ${Name}) 698 | else() 699 | message(SEND_ERROR "Cannot retrieve package ${Name}: no declaration available") 700 | endif() 701 | endmacro() 702 | 703 | # export variables available to the caller to the parent scope expects ${CPM_ARGS_NAME} to be set 704 | macro(cpm_export_variables name) 705 | set(${name}_SOURCE_DIR 706 | "${${name}_SOURCE_DIR}" 707 | PARENT_SCOPE 708 | ) 709 | set(${name}_BINARY_DIR 710 | "${${name}_BINARY_DIR}" 711 | PARENT_SCOPE 712 | ) 713 | set(${name}_ADDED 714 | "${${name}_ADDED}" 715 | PARENT_SCOPE 716 | ) 717 | endmacro() 718 | 719 | # declares a package, so that any call to CPMAddPackage for the package name will use these 720 | # arguments instead. Previous declarations will not be overriden. 721 | macro(CPMDeclarePackage Name) 722 | if(NOT DEFINED "CPM_DECLARATION_${Name}") 723 | set("CPM_DECLARATION_${Name}" "${ARGN}") 724 | endif() 725 | endmacro() 726 | 727 | function(cpm_add_to_package_lock Name) 728 | if(NOT CPM_DONT_CREATE_PACKAGE_LOCK) 729 | cpm_prettify_package_arguments(PRETTY_ARGN false ${ARGN}) 730 | file(APPEND ${CPM_PACKAGE_LOCK_FILE} "# ${Name}\nCPMDeclarePackage(${Name}\n${PRETTY_ARGN})\n") 731 | endif() 732 | endfunction() 733 | 734 | function(cpm_add_comment_to_package_lock Name) 735 | if(NOT CPM_DONT_CREATE_PACKAGE_LOCK) 736 | cpm_prettify_package_arguments(PRETTY_ARGN true ${ARGN}) 737 | file(APPEND ${CPM_PACKAGE_LOCK_FILE} 738 | "# ${Name} (unversioned)\n# CPMDeclarePackage(${Name}\n${PRETTY_ARGN}#)\n" 739 | ) 740 | endif() 741 | endfunction() 742 | 743 | # includes the package lock file if it exists and creates a target `cpm-update-package-lock` to 744 | # update it 745 | macro(CPMUsePackageLock file) 746 | if(NOT CPM_DONT_CREATE_PACKAGE_LOCK) 747 | get_filename_component(CPM_ABSOLUTE_PACKAGE_LOCK_PATH ${file} ABSOLUTE) 748 | if(EXISTS ${CPM_ABSOLUTE_PACKAGE_LOCK_PATH}) 749 | include(${CPM_ABSOLUTE_PACKAGE_LOCK_PATH}) 750 | endif() 751 | if(NOT TARGET cpm-update-package-lock) 752 | add_custom_target( 753 | cpm-update-package-lock COMMAND ${CMAKE_COMMAND} -E copy ${CPM_PACKAGE_LOCK_FILE} 754 | ${CPM_ABSOLUTE_PACKAGE_LOCK_PATH} 755 | ) 756 | endif() 757 | set(CPM_PACKAGE_LOCK_ENABLED true) 758 | endif() 759 | endmacro() 760 | 761 | # registers a package that has been added to CPM 762 | function(CPMRegisterPackage PACKAGE VERSION) 763 | list(APPEND CPM_PACKAGES ${PACKAGE}) 764 | set(CPM_PACKAGES 765 | ${CPM_PACKAGES} 766 | CACHE INTERNAL "" 767 | ) 768 | set("CPM_PACKAGE_${PACKAGE}_VERSION" 769 | ${VERSION} 770 | CACHE INTERNAL "" 771 | ) 772 | endfunction() 773 | 774 | # retrieve the current version of the package to ${OUTPUT} 775 | function(CPMGetPackageVersion PACKAGE OUTPUT) 776 | set(${OUTPUT} 777 | "${CPM_PACKAGE_${PACKAGE}_VERSION}" 778 | PARENT_SCOPE 779 | ) 780 | endfunction() 781 | 782 | # declares a package in FetchContent_Declare 783 | function(cpm_declare_fetch PACKAGE VERSION INFO) 784 | if(${CPM_DRY_RUN}) 785 | message(STATUS "${CPM_INDENT} package not declared (dry run)") 786 | return() 787 | endif() 788 | 789 | FetchContent_Declare(${PACKAGE} ${ARGN}) 790 | endfunction() 791 | 792 | # returns properties for a package previously defined by cpm_declare_fetch 793 | function(cpm_get_fetch_properties PACKAGE) 794 | if(${CPM_DRY_RUN}) 795 | return() 796 | endif() 797 | 798 | set(${PACKAGE}_SOURCE_DIR 799 | "${CPM_PACKAGE_${PACKAGE}_SOURCE_DIR}" 800 | PARENT_SCOPE 801 | ) 802 | set(${PACKAGE}_BINARY_DIR 803 | "${CPM_PACKAGE_${PACKAGE}_BINARY_DIR}" 804 | PARENT_SCOPE 805 | ) 806 | endfunction() 807 | 808 | function(cpm_store_fetch_properties PACKAGE source_dir binary_dir) 809 | if(${CPM_DRY_RUN}) 810 | return() 811 | endif() 812 | 813 | set(CPM_PACKAGE_${PACKAGE}_SOURCE_DIR 814 | "${source_dir}" 815 | CACHE INTERNAL "" 816 | ) 817 | set(CPM_PACKAGE_${PACKAGE}_BINARY_DIR 818 | "${binary_dir}" 819 | CACHE INTERNAL "" 820 | ) 821 | endfunction() 822 | 823 | # adds a package as a subdirectory if viable, according to provided options 824 | function( 825 | cpm_add_subdirectory 826 | PACKAGE 827 | DOWNLOAD_ONLY 828 | SOURCE_DIR 829 | BINARY_DIR 830 | EXCLUDE 831 | OPTIONS 832 | ) 833 | if(NOT DOWNLOAD_ONLY AND EXISTS ${SOURCE_DIR}/CMakeLists.txt) 834 | if(EXCLUDE) 835 | set(addSubdirectoryExtraArgs EXCLUDE_FROM_ALL) 836 | else() 837 | set(addSubdirectoryExtraArgs "") 838 | endif() 839 | if(OPTIONS) 840 | # the policy allows us to change options without caching 841 | cmake_policy(SET CMP0077 NEW) 842 | set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) 843 | 844 | # the policy allows us to change set(CACHE) without caching 845 | if(POLICY CMP0126) 846 | cmake_policy(SET CMP0126 NEW) 847 | set(CMAKE_POLICY_DEFAULT_CMP0126 NEW) 848 | endif() 849 | 850 | foreach(OPTION ${OPTIONS}) 851 | cpm_parse_option("${OPTION}") 852 | set(${OPTION_KEY} "${OPTION_VALUE}") 853 | endforeach() 854 | endif() 855 | set(CPM_OLD_INDENT "${CPM_INDENT}") 856 | set(CPM_INDENT "${CPM_INDENT} ${PACKAGE}:") 857 | add_subdirectory(${SOURCE_DIR} ${BINARY_DIR} ${addSubdirectoryExtraArgs}) 858 | set(CPM_INDENT "${CPM_OLD_INDENT}") 859 | endif() 860 | endfunction() 861 | 862 | # downloads a previously declared package via FetchContent and exports the variables 863 | # `${PACKAGE}_SOURCE_DIR` and `${PACKAGE}_BINARY_DIR` to the parent scope 864 | function(cpm_fetch_package PACKAGE populated) 865 | set(${populated} 866 | FALSE 867 | PARENT_SCOPE 868 | ) 869 | if(${CPM_DRY_RUN}) 870 | message(STATUS "${CPM_INDENT} package ${PACKAGE} not fetched (dry run)") 871 | return() 872 | endif() 873 | 874 | FetchContent_GetProperties(${PACKAGE}) 875 | 876 | string(TOLOWER "${PACKAGE}" lower_case_name) 877 | 878 | if(NOT ${lower_case_name}_POPULATED) 879 | FetchContent_Populate(${PACKAGE}) 880 | set(${populated} 881 | TRUE 882 | PARENT_SCOPE 883 | ) 884 | endif() 885 | 886 | cpm_store_fetch_properties( 887 | ${CPM_ARGS_NAME} ${${lower_case_name}_SOURCE_DIR} ${${lower_case_name}_BINARY_DIR} 888 | ) 889 | 890 | set(${PACKAGE}_SOURCE_DIR 891 | ${${lower_case_name}_SOURCE_DIR} 892 | PARENT_SCOPE 893 | ) 894 | set(${PACKAGE}_BINARY_DIR 895 | ${${lower_case_name}_BINARY_DIR} 896 | PARENT_SCOPE 897 | ) 898 | endfunction() 899 | 900 | # splits a package option 901 | function(cpm_parse_option OPTION) 902 | string(REGEX MATCH "^[^ ]+" OPTION_KEY "${OPTION}") 903 | string(LENGTH "${OPTION}" OPTION_LENGTH) 904 | string(LENGTH "${OPTION_KEY}" OPTION_KEY_LENGTH) 905 | if(OPTION_KEY_LENGTH STREQUAL OPTION_LENGTH) 906 | # no value for key provided, assume user wants to set option to "ON" 907 | set(OPTION_VALUE "ON") 908 | else() 909 | math(EXPR OPTION_KEY_LENGTH "${OPTION_KEY_LENGTH}+1") 910 | string(SUBSTRING "${OPTION}" "${OPTION_KEY_LENGTH}" "-1" OPTION_VALUE) 911 | endif() 912 | set(OPTION_KEY 913 | "${OPTION_KEY}" 914 | PARENT_SCOPE 915 | ) 916 | set(OPTION_VALUE 917 | "${OPTION_VALUE}" 918 | PARENT_SCOPE 919 | ) 920 | endfunction() 921 | 922 | # guesses the package version from a git tag 923 | function(cpm_get_version_from_git_tag GIT_TAG RESULT) 924 | string(LENGTH ${GIT_TAG} length) 925 | if(length EQUAL 40) 926 | # GIT_TAG is probably a git hash 927 | set(${RESULT} 928 | 0 929 | PARENT_SCOPE 930 | ) 931 | else() 932 | string(REGEX MATCH "v?([0123456789.]*).*" _ ${GIT_TAG}) 933 | set(${RESULT} 934 | ${CMAKE_MATCH_1} 935 | PARENT_SCOPE 936 | ) 937 | endif() 938 | endfunction() 939 | 940 | # guesses if the git tag is a commit hash or an actual tag or a branch nane. 941 | function(cpm_is_git_tag_commit_hash GIT_TAG RESULT) 942 | string(LENGTH "${GIT_TAG}" length) 943 | # full hash has 40 characters, and short hash has at least 7 characters. 944 | if(length LESS 7 OR length GREATER 40) 945 | set(${RESULT} 946 | 0 947 | PARENT_SCOPE 948 | ) 949 | else() 950 | if(${GIT_TAG} MATCHES "^[a-fA-F0-9]+$") 951 | set(${RESULT} 952 | 1 953 | PARENT_SCOPE 954 | ) 955 | else() 956 | set(${RESULT} 957 | 0 958 | PARENT_SCOPE 959 | ) 960 | endif() 961 | endif() 962 | endfunction() 963 | 964 | function(cpm_prettify_package_arguments OUT_VAR IS_IN_COMMENT) 965 | set(oneValueArgs 966 | NAME 967 | FORCE 968 | VERSION 969 | GIT_TAG 970 | DOWNLOAD_ONLY 971 | GITHUB_REPOSITORY 972 | GITLAB_REPOSITORY 973 | GIT_REPOSITORY 974 | SOURCE_DIR 975 | DOWNLOAD_COMMAND 976 | FIND_PACKAGE_ARGUMENTS 977 | NO_CACHE 978 | GIT_SHALLOW 979 | ) 980 | set(multiValueArgs OPTIONS) 981 | cmake_parse_arguments(CPM_ARGS "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) 982 | 983 | foreach(oneArgName ${oneValueArgs}) 984 | if(DEFINED CPM_ARGS_${oneArgName}) 985 | if(${IS_IN_COMMENT}) 986 | string(APPEND PRETTY_OUT_VAR "#") 987 | endif() 988 | if(${oneArgName} STREQUAL "SOURCE_DIR") 989 | string(REPLACE ${CMAKE_SOURCE_DIR} "\${CMAKE_SOURCE_DIR}" CPM_ARGS_${oneArgName} 990 | ${CPM_ARGS_${oneArgName}} 991 | ) 992 | endif() 993 | string(APPEND PRETTY_OUT_VAR " ${oneArgName} ${CPM_ARGS_${oneArgName}}\n") 994 | endif() 995 | endforeach() 996 | foreach(multiArgName ${multiValueArgs}) 997 | if(DEFINED CPM_ARGS_${multiArgName}) 998 | if(${IS_IN_COMMENT}) 999 | string(APPEND PRETTY_OUT_VAR "#") 1000 | endif() 1001 | string(APPEND PRETTY_OUT_VAR " ${multiArgName}\n") 1002 | foreach(singleOption ${CPM_ARGS_${multiArgName}}) 1003 | if(${IS_IN_COMMENT}) 1004 | string(APPEND PRETTY_OUT_VAR "#") 1005 | endif() 1006 | string(APPEND PRETTY_OUT_VAR " \"${singleOption}\"\n") 1007 | endforeach() 1008 | endif() 1009 | endforeach() 1010 | 1011 | if(NOT "${CPM_ARGS_UNPARSED_ARGUMENTS}" STREQUAL "") 1012 | if(${IS_IN_COMMENT}) 1013 | string(APPEND PRETTY_OUT_VAR "#") 1014 | endif() 1015 | string(APPEND PRETTY_OUT_VAR " ") 1016 | foreach(CPM_ARGS_UNPARSED_ARGUMENT ${CPM_ARGS_UNPARSED_ARGUMENTS}) 1017 | string(APPEND PRETTY_OUT_VAR " ${CPM_ARGS_UNPARSED_ARGUMENT}") 1018 | endforeach() 1019 | string(APPEND PRETTY_OUT_VAR "\n") 1020 | endif() 1021 | 1022 | set(${OUT_VAR} 1023 | ${PRETTY_OUT_VAR} 1024 | PARENT_SCOPE 1025 | ) 1026 | 1027 | endfunction() 1028 | --------------------------------------------------------------------------------