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
16 changes: 10 additions & 6 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,16 @@ else()
message(STATUS "ZeroMQ director-client/zmqml surrogate disabled")
endif()


# Ctest creates BUILD_TESTING option and sets true by default
# We disable it by default
option(BUILD_TESTING "Build the testing tree (unit + model tests)" OFF)
include(CTest)
mark_as_advanced(CLEAR BUILD_TESTING)

# Vendored third-party dependencies (built before src, which links them).
add_subdirectory(thirdparty)

add_subdirectory(src)


Expand All @@ -285,13 +295,7 @@ if(CODES_BUILD_DOXYGEN)
add_subdirectory(doc)
endif()

# Tests are gated solely on BUILD_TESTING (the canonical CTest knob). A
# $<CONFIG:...> genex can't gate this since add_subdirectory() runs at
# configure time, and the old `STREQUAL "RELEASE"` compare never matched the
# conventional `Release` spelling anyway, so tests already built in every
# standard config.
if(BUILD_TESTING)
include(CTest)
set(CODES_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
set(CODES_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}")
add_subdirectory(tests)
Expand Down
12 changes: 12 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@ enable_testing()

configure_file(run-test.sh.in run-test.sh)

# ---------------------------------------------------------------------------
# Unit-test smoke test. The GoogleTest framework (GTest::gtest / gtest_main) is
# built by the thirdparty dispatcher when BUILD_TESTING is on — see
# thirdparty/googletest/. This smoke test proves the vendored framework builds,
# links, runs, and reports through CTest. Real unit-test consumers land in
# a future PR. Unit tests run serially (no MPI), distinct from the mpiexec-launched
# model tests below.
add_executable(codes-unit-smoke codes-unit-smoke.cxx)
target_link_libraries(codes-unit-smoke PRIVATE GTest::gtest_main)
add_test(NAME codes-unit-smoke COMMAND codes-unit-smoke)
# ---------------------------------------------------------------------------

# codes_add_equivalence_test — register an equivalence/determinism test.
#
# Runs a model binary two or more times and asserts a marker line (default
Expand Down
9 changes: 9 additions & 0 deletions tests/codes-unit-smoke.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Smoke test for the vendored GoogleTest wiring: proves the
// framework builds, links, runs, and reports a result through CTest. It asserts
// nothing about CODES itself — real unit tests (starting with the YAML config
// compiler) arrive soon. Runs serially; no MPI.
#include <gtest/gtest.h>

TEST(Smoke, ArithmeticSanity) {
EXPECT_EQ(1 + 1, 2);
}
29 changes: 29 additions & 0 deletions thirdparty/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Vendored third-party dependencies.
#
# Each subdirectory is an upstream drop managed with the Kitware third-party
# update convention (the shared update-common.sh here, driven by a per-package
# update.sh) plus a thin CMake wrapper that exposes a codes::thirdparty::<name>
# target. See each package's README.md for its upstream origin, pinned
# version, and how to re-import it.

# Disable warnings in thirdparty code
if(CMAKE_C_COMPILER_ID MATCHES
"^(GNU|Clang|AppleClang|XLClang|XL|VisualAge|SunPro|HP|Intel|IntelLLVM|PGI|NVIDIA|NVHPC)$")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -w")
endif()
if(CMAKE_CXX_COMPILER_ID MATCHES
"^(GNU|Clang|AppleClang|XLClang|XL|VisualAge|SunPro|HP|Intel|IntelLLVM|PGI|NVIDIA|NVHPC)$")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -w")
endif()

function(message_start_thirdparty)
get_filename_component(tp_base "${CMAKE_CURRENT_LIST_DIR}" NAME_WE)
message("")
message(STATUS "CODES ThirdParty: Configuring ${tp_base}")
endfunction()

add_subdirectory(rapidyaml)

if(BUILD_TESTING)
add_subdirectory(googletest)
endif()
172 changes: 172 additions & 0 deletions thirdparty/UPDATING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
# Updating Third Party Projects

CODES vendors a small number of third-party libraries under `thirdparty/`. Each
one is a reduced copy of an upstream project, imported with the Kitware
third-party update convention so that every change we carry is tracked as an
explicit, diffable commit rather than an ad-hoc edit.

This document explains how that machinery works: the
directory layout, how to re-import a project at a new version, and how to add a
new one.

## The two scripts

The update machinery is split in two:

- **`update-common.sh`** — the shared engine. It does all the real work: clone
upstream, check out the requested version, run the package's extraction, and
merge the result into our tree as a single import commit. This file is
**Kitware's, vendored verbatim** (see the license header). Do **not** fork or
hand-edit it — keeping it pristine is what lets us pull in upstream fixes to
the convention itself with a clean diff.
- **`<pkg>/update.sh`** — a thin per-package driver. It sets a handful of
metadata variables, defines an `extract_source` function, and then sources
`update-common.sh`. This is the only script you normally edit, and usually the
only line you touch is `tag`.

## Anatomy of a vendored package

Each package directory is **nested**, and the distinction is important:

```
thirdparty/
googletest/ <- CODES-maintained "wrapper" directory
CMakeLists.txt <- thin wrapper exposing a codes::thirdparty::<name> target
README.md <- package-specific notes
update.sh <- the per-package driver
googletest/ <- the imported subtree (pristine upstream, do not edit)
CMakeLists.txt
include/ src/ ...
```

The **outer** directory (`thirdparty/<pkg>/`) holds files CODES maintains: the
wrapper `CMakeLists.txt`, the `README.md`, the `update.sh` driver, and any glue
code (for example rapidyaml's `codes_ryml.hpp` shim and `ryml.cpp` translation
unit). These are **preserved across re-imports**.

The **inner** directory (`thirdparty/<pkg>/<pkg>/`) is the imported subtree —
this is the `subtree` variable in `update.sh`. It is a pristine drop of upstream
and is **replaced wholesale** on every import.

**Never edit files in the inner subtree directly.** They will be overwritten on
the next re-import, and the change will be lost. Changes belong upstream (see
below); local glue belongs in the outer wrapper directory.

## Prerequisites

- **Git 2.5 or newer** — the engine uses `git worktree` to make the imported
commits available to the main checkout.
- **`python3`** — for projects whose `extract_source` generates code. RapidYAML,
for example, regenerates its single-header amalgamation with upstream's
`tools/amalgamate.py` (standard library only; no extra packages).
- **`git-archive-all`** — only for projects that vendor a subset of an upstream
tree that *contains submodules*, via the engine's `git_archive_all` helper.
No current packages need it. Install with `pip install git-archive-all`
and make sure the installed script (often `$HOME/.local/bin`) is on your
`PATH`.

## Updating an existing project

### 1. Get the change upstream first

Any *code* change to a vendored library should first go to the upstream project,
using whatever workflow they require. Only once it is accepted do we bring it
into CODES. If an important fix stalls upstream, we can temporarily point `tag`
at a fork/branch carrying the patch, but the goal is always to track upstream.

### 2. Bump the version and re-import

The pinned version lives in exactly one place: the `tag` variable in the
package's `update.sh`. It is usually a release tag but may be `master`, a branch,
or any commit SHA.

```sh
# from the repo root
$ git checkout -b update_ryml_YYYY_MM_DD
# edit `tag` in thirdparty/rapidyaml/update.sh, then commit that edit
$ git commit -am 'thirdparty: bump rapidyaml to <version>'
# now run the driver
$ ./thirdparty/rapidyaml/update.sh
```

`update.sh` creates the import commit(s) for you. Dating the branch name is not
required; it just avoids collisions if you have an old update branch sitting around.

### 3. Validate, then open a PR

Re-configure and build to confirm the new drop compiles, and run the relevant
tests (for example, a `BUILD_TESTING` build exercises GoogleTest). Then review
the import commit and open a pull request from the branch as normal.

## Adding a new project

Create the package's outer directory and write its `update.sh`, filling in the
metadata the engine expects (all documented at the top of `update-common.sh`):

- `name` — the project name.
- `ownership` — a git author `Name <email>` for the import commits. Use the
`"<Name> Upstream <robot@codes>"` convention. **Keep this stable across
imports** (see "How re-import finds the previous drop" below).
- `subtree` — where the import lands, i.e. the nested
`thirdparty/<name>/<name>` path.
- `repo` — the upstream git URL.
- `tag` — the version to import.
- `shortlog` — optional; `true` to include an upstream shortlog in the commit
message.
- `exact_tree_match` — see below; reduced or generated imports must set this to
`false`.

The key piece is the **`extract_source`** function, run inside a checkout of
upstream at `tag`. It must place the desired tree at `$extractdir/$name-reduced`.
If you only need to keep a subset of upstream's files verbatim, set the `paths`
variable and call the provided `git_archive` helper (or `git_archive_all` for
submoduled upstreams). If you need to generate or transform files, do that
instead (see rapidyaml's `update.sh` for a generated example).

Make `update.sh` executable and stage it before committing:

```sh
$ chmod u+x thirdparty/<name>/update.sh && git add thirdparty/<name>/update.sh
```

Finally, add the package to `thirdparty/CMakeLists.txt` and give it a wrapper
`CMakeLists.txt` and `README.md`.

## How it works under the hood

When you run a package's `update.sh`, the engine:

1. Clones upstream (recursively, for submodules) and checks out `tag`.
2. Runs your `extract_source`, producing `$name-reduced`.
3. Commits that tree as a **single squashed import commit** on a temporary
`upstream-<name>` branch, authored by `ownership` and dated to match
upstream.
4. Merges that branch onto the real tree under `subtree/` (a subtree merge for
an existing package, or an initial subtree read for a brand-new one) and
deletes the temporary branch.

### The import-commit contract

Import commits have a summary of the form `name YYYY-MM-DD (shorthash)`. This
is not cosmetic — re-import uses it to locate the previous drop (see below), so
don't reword these commits.

### How re-import finds the previous drop

To merge a new version onto the old one, the engine has to find the previous
import commit. It does this by searching history for commits authored by
`ownership` whose message matches the `name YYYY-MM-DD (hash)` pattern. Two
consequences:

- **`ownership` must stay identical between imports.** Change it and the engine
can't find the prior drop; it will treat the re-import as a fresh initial
import (or fail outright).
- **`exact_tree_match`** controls how strictly the match is made. The default
(`true`) additionally requires the committed tree object to match what's
currently in the checkout — correct only when you vendor upstream's tree
*verbatim*. If `extract_source` vendors a **reduced subset** or **generates**
files (as both current packages do), the trees won't match, so you must set
`exact_tree_match=false` to fall back to log-based matching. The trap is
delayed: the initial import succeeds regardless, and only the *first
re-import* fails with "No previous import commit found" if this is left at the
default.
46 changes: 46 additions & 0 deletions thirdparty/googletest/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# GoogleTest — the unit-test framework.
#
# Test-only: built only when BUILD_TESTING is on. Wired from the thirdparty
# dispatcher (thirdparty/CMakeLists.txt), which gates its add_subdirectory of this
# wrapper on BUILD_TESTING — an option declared in the top-level CMakeLists before
# the dispatcher runs. It is never installed or exported.
#
# Vendored as a reduced copy of the upstream repo under googletest/ (see
# README.kitware.md), integrated the standard way — add_subdirectory of upstream's
# own CMake, the same approach ADIOS2 uses — rather than hand-compiling gtest-all.cc.
message_start_thirdparty()

# gtest only: gmock isn't needed (death tests live in gtest core). With BUILD_GMOCK
# OFF, upstream's root CMake recurses into googletest/ alone.
set(BUILD_GMOCK OFF CACHE BOOL "" FORCE)
# Keep the framework out of the CODES install/export.
set(INSTALL_GTEST OFF CACHE BOOL "" FORCE)
# Windows-only knob (link the CRT dynamically); harmless elsewhere. Matches
# upstream's recommendation for projects embedding googletest.
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)

# The project builds its own sources with -Wundef (see the top-level CMakeLists), a
# directory property inherited here. Upstream googletest is not -Wundef-clean, so
# disable it for gtest's own compiles — appended after the inherited -Wundef, so it
# wins — keeping third-party warnings out of the build log.
add_compile_options(-Wno-undef)

# EXCLUDE_FROM_ALL: gtest compiles only when a test target actually links it, never
# as part of `all`. Upstream exposes the gtest / gtest_main targets and the
# GTest::gtest / GTest::gtest_main aliases that unit-test targets link.
add_subdirectory(googletest EXCLUDE_FROM_ALL)

# Upstream googletest doesn't mark its headers SYSTEM, so a unit-test TU that
# #includes <gtest/gtest.h> would inherit the project's own warning flags (the
# -Wundef above, say) through the framework's headers. Re-expose the include dirs
# as SYSTEM on the gtest targets so every consumer gets them via -isystem and stays
# warning-clean.
foreach(_gtest_tgt gtest gtest_main)
if(TARGET ${_gtest_tgt})
get_target_property(_gtest_inc ${_gtest_tgt} INTERFACE_INCLUDE_DIRECTORIES)
if(_gtest_inc)
set_target_properties(${_gtest_tgt} PROPERTIES
INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${_gtest_inc}")
endif()
endif()
endforeach()
11 changes: 11 additions & 0 deletions thirdparty/googletest/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# GoogleTest

A reduced copy of [GoogleTest][gtest] vendored into CODES as the unit-test
framework.

See [../UPDATING.md](../UPDATING.md) for the vendoring layout and how to
re-import. In short: do not edit the imported `googletest/` subtree directly;
the pinned version is the `tag` in `update.sh`, and re-importing is `./update.sh`
after bumping it.

[gtest]: https://github.com/google/googletest
36 changes: 36 additions & 0 deletions thirdparty/googletest/googletest/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Note: CMake support is community-based. The maintainers do not use CMake
# internally.

cmake_minimum_required(VERSION 3.16)

project(googletest-distribution)
set(GOOGLETEST_VERSION 1.17.0)

if(NOT CYGWIN AND NOT MSYS AND NOT ${CMAKE_SYSTEM_NAME} STREQUAL QNX)
set(CMAKE_CXX_EXTENSIONS OFF)
endif()

enable_testing()

include(CMakeDependentOption)
include(GNUInstallDirs)

# Note that googlemock target already builds googletest.
option(BUILD_GMOCK "Builds the googlemock subproject" ON)
option(INSTALL_GTEST "Enable installation of googletest. (Projects embedding googletest may want to turn this OFF.)" ON)
option(GTEST_HAS_ABSL "Use Abseil and RE2. Requires Abseil and RE2 to be separately added to the build." OFF)

if(GTEST_HAS_ABSL)
if(NOT TARGET absl::base)
find_package(absl REQUIRED)
endif()
if(NOT TARGET re2::re2)
find_package(re2 REQUIRED)
endif()
endif()

if(BUILD_GMOCK)
add_subdirectory( googlemock )
else()
add_subdirectory( googletest )
endif()
Loading
Loading