Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion .clang-tidy
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,16 @@ Checks: >
-cppcoreguidelines-avoid-magic-numbers,
-cppcoreguidelines-owning-memory,
-cppcoreguidelines-non-private-member-variables-in-classes,
-cppcoreguidelines-pro-type-member-init,
-cppcoreguidelines-rvalue-reference-param-not-moved,
-cppcoreguidelines-missing-std-forward,
-cppcoreguidelines-use-enum-class,
bugprone-assert-side-effect,
performance-*,
-performance-enum-size,
performance-move-const-arg,
modernize-use-auto,
misc-const-correctness,
-misc-const-correctness,
misc-non-copyable-objects,
misc-redundant-expression,
misc-static-assert,
Expand All @@ -32,6 +37,13 @@ Checks: >
-readability-magic-numbers,
-readability-identifier-length,
-readability-avoid-nested-conditional-operator,
-readability-identifier-naming,
-readability-redundant-typename,
-readability-redundant-parentheses,
-readability-avoid-const-params-in-decls,
-cppcoreguidelines-init-variables,
-readability-use-concise-preprocessor-directives,
-readability-use-std-min-max,
WarningsAsErrors: ""
HeaderFilterRegex: ".*"
FormatStyle: none
Expand Down
17 changes: 17 additions & 0 deletions .env.local.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Local build overrides (optional).
# Copy to .env.local and edit; .env.local is not committed (see .gitignore).
# The Makefile includes .env.local only when the file exists.
#
# Uncomment and set to override the default compiler used by the build.

# Example: use Homebrew GCC on macOS (e.g. when LLVM build fails with fmt/consteval)
# CC := $(shell brew --prefix gcc)/bin/gcc-15
# CXX := $(shell brew --prefix gcc)/bin/g++-15

# Example: explicit paths (adjust version or prefix as needed)
# CC := /opt/homebrew/bin/gcc-15
# CXX := /opt/homebrew/bin/g++-15

# Example: use Homebrew LLVM on macOS
# CC := $(shell brew --prefix llvm)/bin/clang
# CXX := $(shell brew --prefix llvm)/bin/clang++
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# cmake folders
# local env overrides (see .env.local.example)
.env.local

# cmake folders / build trees
build/
build-lint/
debug/
qsyn*
!src/qsyn*
Expand All @@ -18,6 +22,9 @@ wip/
.cache/
compile_commands.json

# log files
*.log

# doxygen, sphinx, and other documentation files and folders
docs/_build/
docs/_doxygen/
Expand Down
44 changes: 38 additions & 6 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ set(CMAKE_CXX_FLAGS "")
set(CMAKE_CXX_FLAGS_DEBUG "-g")
set(CMAKE_CXX_FLAGS_RELEASE "-O3")

# On Apple Silicon with Homebrew GCC, the default CMake OSX architectures
# may include x86, which GCC does not support and will emit a warning for
# every compilation. Clear architectures in that case to keep the build
# output clean while still producing a correct arm64 binary.
if(APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
set(CMAKE_OSX_ARCHITECTURES "" CACHE STRING "Architectures for OS X (cleared for GCC)" FORCE)
endif()

include(FetchContent)
include(CheckCXXCompilerFlag)
set(FETCHCONTENT_UPDATES_DISCONNECTED TRUE)
Expand Down Expand Up @@ -280,6 +288,12 @@ target_link_libraries(
${BLAS_LIBRARIES}
${LAPACK_LIBRARIES})

# GCC 15+ cast-user-defined: same as qsyn executable
if(COMPILER_SUPPORTS_WNOERROR_CAST_USER_DEFINED)
target_compile_options(${QSYN_LIB_NAME}
PRIVATE -Wno-error=cast-user-defined)
endif()

# ----------------------------------------------------------------------------
# config for qsyn target
# builds qsyn executable
Expand All @@ -302,12 +316,21 @@ target_compile_options(
target_compile_options(
${CMAKE_PROJECT_NAME} PRIVATE -Wno-missing-field-initializers)

# GCC 12+ is too strict about restrict warnings in std::string operations
# Only apply to GCC, as Clang doesn't support this flag
# GCC 12+ is too strict about restrict warnings in std::string operations.
# Only apply when the compiler actually supports the flag.
check_cxx_compiler_flag(-Wno-restrict COMPILER_SUPPORTS_WNO_RESTRICT)
if(COMPILER_SUPPORTS_WNO_RESTRICT)
target_compile_options(
${CMAKE_PROJECT_NAME} PRIVATE -Wno-restrict)
target_compile_options(${CMAKE_PROJECT_NAME} PRIVATE -Wno-restrict)
endif()

# GCC 15+ (and some toolchains) treat C-style casts in ordered_hashtable as
# -Werror=cast-user-defined. Relax this to a warning, but only if the flag
# is recognized by the current compiler (older GCC/Clang may not know it).
check_cxx_compiler_flag(
-Wno-error=cast-user-defined COMPILER_SUPPORTS_WNOERROR_CAST_USER_DEFINED)
if(COMPILER_SUPPORTS_WNOERROR_CAST_USER_DEFINED)
target_compile_options(${CMAKE_PROJECT_NAME}
PRIVATE -Wno-error=cast-user-defined)
endif()

target_include_directories(
Expand Down Expand Up @@ -381,11 +404,20 @@ target_compile_options(
PRIVATE
-Wno-missing-field-initializers)

# GCC 12+ is too strict about restrict warnings in std::string operations
# Only apply to GCC, as Clang doesn't support this flag
# GCC 12+ is too strict about restrict warnings in std::string operations.
# Only apply to GCC, as Clang doesn't support this flag.
if(COMPILER_SUPPORTS_WNO_RESTRICT)
target_compile_options(
${UNIT_TEST_NAME}
PRIVATE
-Wno-restrict)
endif()

# GCC 15+ treats the C-style cast in ordered_hashtable iterator dereference
# as -Werror=cast-user-defined when building unit tests. Relax this to a
# warning so the test target can still be built while keeping the warning
# visible in the output.
if(COMPILER_SUPPORTS_WNOERROR_CAST_USER_DEFINED)
target_compile_options(${UNIT_TEST_NAME}
PRIVATE -Wno-error=cast-user-defined)
endif()
8 changes: 6 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
UNAME_S := $(shell uname -s)

# On MacOS, the default compiler is clang
# Platform defaults for compiler (used when CC/CXX not set by environment)
ifeq ($(UNAME_S), Darwin)
# Override CC if it is set to the default 'cc'
ifeq ($(origin CC), default)
CC := $(shell which clang)
endif
Expand All @@ -18,6 +17,11 @@ else
endif
endif

# Optional local overrides (CC, CXX, etc.): included only if .env.local exists
ifneq (,$(wildcard .env.local))
include .env.local
endif

ECHO := $(shell which echo) -e

RELEASE_DIR := build
Expand Down
67 changes: 53 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ Some of the future work for Qsyn includes:

### System Requirements

`qsyn` requires `c++-20` to build. We support compilation with (1) `g++-11` or above or (2) `clang++-16` or above. We regularly perform build tests for the two compilers.
- **CMake**: 3.29 or higher. Check with `cmake --version`; upgrade via your package manager (e.g. `brew install cmake` on macOS, or install from [cmake.org](https://cmake.org/download/)).
- **C++**: C++20. We support compilation with (1) **g++-11** or above or (2) **clang++-16** or above. We regularly perform build tests for the two compilers.
- **BLAS/LAPACK**: OpenBLAS and LAPACK are required (see platform-specific steps below).

### Installation

Expand Down Expand Up @@ -89,52 +91,89 @@ Then, run the following commands to build `qsyn`:
make -j8
```

which triggers our suggested CMake build process. Alternatively, use `make build-g++` or `make build-clang++` to manually specify the compiler.
This uses the default compiler (gcc/g++) and triggers the CMake build. To override the compiler, set `CC` and `CXX` in the environment or in a local override file (see **Local overrides** below).

</details>

<details>
<summary>For MacOS Users</summary>

Since Qsyn uses some C++20 features that are not yet supported by Apple Clang, you'll need to install another compiler yourself. We recommend installing the `llvm` toolchain with the `brew` package manager.
```sh
brew install llvm
```
Since Qsyn uses C++20 features not fully supported by Apple Clang, install a C++20-capable compiler. Options:

1. **LLVM (clang++)** via Homebrew:
```sh
brew install llvm
```
After installation, follow `brew`’s instructions to use LLVM’s `clang++` (e.g. add its `bin` to `PATH`).

After installation, `brew` will guide you to configure your environment to use LLVM `clang++` and its associated libraries.
> **Note:** Use `llvm@18` on some device may work. The unversioned `llvm` formula installs the latest LLVM (currently 20+), which has a known `consteval`/`FMT_STRING` incompatibility that causes build failures with the `fmt` version used by qsyn.

You would probably want to install `OpenBLAS` by running
2. **GCC** via Homebrew (recommended if you see fmt/consteval or similar build errors with LLVM):
```sh
brew install gcc
```
Then use GCC for the build by creating a `.env.local` (see **Local overrides** below) with:
```make
CC := $(shell brew --prefix gcc)/bin/gcc-15
CXX := $(shell brew --prefix gcc)/bin/g++-15
```
(Replace `15` with your installed GCC version if different.)

Install **OpenBLAS**:

```sh
brew install openblas
```

Finally, run the following commands to build `qsyn`:
Then build:

```sh
make -j8
```

which triggers our suggested CMake build process. Alternatively, use `make build-g++` or `make build-clang++` to manually specify the compiler.
</details>

<details>
<summary>Local overrides (.env.local)</summary>

To keep machine-specific settings (e.g. compiler path) out of the repo, you can use an optional **`.env.local`** file at the project root. The Makefile will include it only if the file exists; it is ignored by git (see `.gitignore`).

Use it to override `CC`, `CXX`, or other variables used by the build. Copy the example and edit as needed:

```sh
cp .env.local.example .env.local
# edit .env.local (e.g. set CC and CXX for your compiler)
```

Example **`.env.local`** (Makefile syntax; use `:=` for your paths):

```make
# Optional: override compilers (e.g. for macOS with Homebrew GCC)
CC := /opt/homebrew/bin/gcc-15
CXX := /opt/homebrew/bin/g++-15
```

Then run `make -j8` as usual; the build will use these values.

</details>

<details>
<summary>Docker Builds</summary>

You can also build `qsyn` in a containerized environment by running
You can run `qsyn` without building (Docker image has a pre-built binary):

```sh
make build-docker
docker run -it --rm dvlab/qsyn:latest
```

And run that executable by running
To build `qsyn` inside a container on your machine:

```sh
make build-docker
make run-docker
```

Of course, this requires you to have Docker installed on your machine.
This requires Docker and uses the image defined in `docker/dev.Dockerfile`. Note: the image must provide **CMake ≥ 3.29**; if `make build-docker` fails with a CMake version error, the base image may need to be updated.

</details>

Expand Down
19 changes: 14 additions & 5 deletions scripts/LINT
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,27 @@ format_one() {
export -f format_one

lint_one() {
clang-tidy -p build "$1" --quiet 2>&1 | grep -v -E "warnings? generated"
clang-tidy -p build-lint "$1" --quiet 2>&1 \
| grep -v -E "warnings? generated" \
| grep -v -E "file not found \[clang-diagnostic-error\]" \
| grep -v -E "^Error while processing .*\.$" \
| grep -v -E "^[[:space:]]*[0-9]+ \\|[[:space:]]*#include <" \
| grep -v -E "^[[:space:]]*\\|[[:space:]]*\\^~+"
}
export -f lint_one

FILES=$(find ./src -regex '.*\.[ch]pp' -type f)
CPPS=$(find ./src -regex '.*\.cpp' -type f)

# Format all files first to avoid linting errors
echo "Formatting files..."
parallel -j"$(num_procs)" format_one ::: "$FILES"
# NOTE:
# We intentionally do NOT auto-run clang-format here.
# GitHub CI runs clang-format (v16) separately for style checks.
# Keeping LINT to clang-tidy only avoids local version drift reformatting code.
echo "Generating compile commands..."
cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=1 > /dev/null
# Use Apple clang for the lint build to avoid toolchain/header
# incompatibilities when analyzing with clang-tidy.
cmake -S . -B build-lint -DCMAKE_EXPORT_COMPILE_COMMANDS=1 \
-DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++ > /dev/null
echo "Linting files..."
parallel -j"$(num_procs)" lint_one ::: "$CPPS"
echo "Done"
40 changes: 35 additions & 5 deletions scripts/RUN_TESTS
Original file line number Diff line number Diff line change
Expand Up @@ -151,12 +151,42 @@ done

ref-path() {
local TEST=$1
# use grealpath on macOS because realpath does not have a --relative-to option
if [ "$(uname)" == "Darwin" ]; then
grealpath --relative-to "$(pwd)" "$(dirname "${TEST}")/../ref/$(basename "${TEST%.*}").log"
else
realpath --relative-to "$(pwd)" "$(dirname "${TEST}")/../ref/$(basename "${TEST%.*}").log"
local TARGET
TARGET="$(dirname "${TEST}")/../ref/$(basename "${TEST%.*}").log"

# Prefer GNU realpath/grealpath if available
if command -v grealpath >/dev/null 2>&1; then
grealpath "$TARGET"
return
fi

if command -v realpath >/dev/null 2>&1; then
realpath "$TARGET"
return
fi

# Fallback to (g)readlink -f if available
if command -v greadlink >/dev/null 2>&1; then
greadlink -f "$TARGET"
return
fi

if command -v readlink >/dev/null 2>&1; then
readlink -f "$TARGET" 2>/dev/null && return
fi

# Last resort: use Python to emulate realpath
if command -v python3 >/dev/null 2>&1; then
python3 - "$TARGET" << 'PY'
import os, sys
print(os.path.realpath(sys.argv[1]))
PY
return
fi

# If everything else fails, just echo the target; downstream code will
# still behave reasonably (it will see a non-empty path).
echo "$TARGET"
}
export -f ref-path

Expand Down
2 changes: 1 addition & 1 deletion src/argparse/arg_parser_print.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ void ArgumentParser::print_help() const {

constexpr auto left_margin = 2;
constexpr auto cell_padding = 2;
constexpr auto total_padding = left_margin + 3 * cell_padding;
constexpr auto total_padding = left_margin + (3 * cell_padding);
auto const max_help_string_width =
dvlab::utils::get_terminal_size().width -
get_max_length([](Argument const& arg) -> size_t { return unicode::display_width(arg.get_type_string()); }) -
Expand Down
2 changes: 1 addition & 1 deletion src/cli/cli.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ bool dvlab::CommandLineInterface::add_alias(std::string_view alias, std::string_

bool dvlab::CommandLineInterface::remove_alias(std::string_view alias) {
if (!_identifiers.erase(alias)) {
return false;
return false; // NOLINT(readability-simplify-boolean-expr)
}
_aliases.erase(std::string{alias});

Expand Down
2 changes: 1 addition & 1 deletion src/cli/cli.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ class CommandLineInterface {
void _reprint_command();
void _retrieve_history(size_t index);
size_t _prev_matching_history(size_t count = 1);
size_t _next_matching_history(size_t count = 1);
size_t _next_matching_history(size_t count = 1) const;
void _add_to_history(HistoryEntry const& entry);
void _replace_read_buffer_with_history();

Expand Down
Loading
Loading