Skip to content
Open
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
98 changes: 98 additions & 0 deletions .github/workflows/juce.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
name: JUCE module

# Builds the moonbase_licensing JUCE module and runs its behavioral test suite
# (tests/juce) on every OS. This is the only CI that exercises the native-crypto
# backends (Security.framework / CNG / libcrypto) and the ActivationController
# state machine; the default ci.yml only covers the header-only SDK.
#
# The suite signs tokens with OpenSSL then verifies them with the platform's
# native backend, i.e. a real cross-backend RS256 round-trip on each OS. The
# module needs no curl, so the build turns it off (-DMOONBASE_USE_CURL=OFF).

on:
push:
branches: [main]
pull_request:

concurrency:
group: juce-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
controller-tests:
name: ${{ matrix.os }}
runs-on: ${{ matrix.os }}
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-14, windows-latest]

steps:
- uses: actions/checkout@v4

- name: Install dependencies (Ubuntu)
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y \
libssl-dev \
libx11-dev libxext-dev libxinerama-dev libxrandr-dev libxcursor-dev \
libxcomposite-dev libxrender-dev libfreetype6-dev libfontconfig1-dev \
libasound2-dev libglu1-mesa-dev mesa-common-dev xvfb

- name: Install dependencies (macOS)
if: runner.os == 'macOS'
run: brew install openssl@3 nlohmann-json

- name: Install OpenSSL via vcpkg (Windows)
if: runner.os == 'Windows'
shell: bash
run: |
"$VCPKG_INSTALLATION_ROOT/vcpkg" install --triplet x64-windows openssl

- name: Configure (Ubuntu)
if: runner.os == 'Linux'
run: |
cmake -S . -B build \
-DCMAKE_BUILD_TYPE=Release \
-DMOONBASE_USE_CURL=OFF \
-DMOONBASE_BUILD_JUCE_TESTS=ON

- name: Configure (macOS)
if: runner.os == 'macOS'
run: |
cmake -S . -B build \
-DCMAKE_BUILD_TYPE=Release \
-DMOONBASE_USE_CURL=OFF \
-DMOONBASE_BUILD_JUCE_TESTS=ON \
-DOPENSSL_ROOT_DIR="$(brew --prefix openssl@3)"

- name: Configure (Windows)
if: runner.os == 'Windows'
shell: bash
run: |
cmake -S . -B build \
-DMOONBASE_USE_CURL=OFF \
-DMOONBASE_BUILD_JUCE_TESTS=ON \
-DCMAKE_TOOLCHAIN_FILE="$VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" \
-DVCPKG_TARGET_TRIPLET=x64-windows

# On Linux the doctest discovery step runs the binary at build time, and the
# suite initialises the JUCE GUI subsystem, so both the build and the run
# need a virtual display.
- name: Build (Ubuntu)
if: runner.os == 'Linux'
run: xvfb-run -a cmake --build build --target MoonbaseJuceTests -j

- name: Build
if: runner.os != 'Linux'
run: cmake --build build --target MoonbaseJuceTests --config Release -j

- name: Test (Ubuntu)
if: runner.os == 'Linux'
run: xvfb-run -a ctest --test-dir build -R "Juce\." --output-on-failure

- name: Test
if: runner.os != 'Linux'
run: ctest --test-dir build -R "Juce\." -C Release --output-on-failure
53 changes: 53 additions & 0 deletions .github/workflows/sanitizers.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: Sanitizers

# Runs the test suite under AddressSanitizer + UndefinedBehaviorSanitizer and
# under ThreadSanitizer. The controller and the license store both spawn
# threads, so the thread run guards against data races / use-after-free that
# unit assertions can't see. Linux only: the sanitizer runtimes are reliable
# there (the repo's macOS toolchain trips ASan's process-startup checks).

on:
push:
branches: [main]
pull_request:

concurrency:
group: sanitizers-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
sdk:
name: SDK (${{ matrix.sanitizer }})
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
sanitizer: ["address,undefined", "thread"]

steps:
- uses: actions/checkout@v4

- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y libcurl4-openssl-dev libssl-dev nlohmann-json3-dev

- name: Configure
run: |
cmake -S . -B build \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DMOONBASE_BUILD_TESTS=ON \
-DMOONBASE_BUILD_EXAMPLES=OFF \
-DMOONBASE_SANITIZER=${{ matrix.sanitizer }}

- name: Build
run: cmake --build build --target moonbase_tests -j

- name: Test
env:
# Surface the first finding with a full report and a non-zero exit.
ASAN_OPTIONS: halt_on_error=1:abort_on_error=1:detect_leaks=1
UBSAN_OPTIONS: halt_on_error=1:print_stacktrace=1
TSAN_OPTIONS: halt_on_error=1:second_deadlock_stack=1
run: ctest --test-dir build --output-on-failure
44 changes: 44 additions & 0 deletions .github/workflows/visual.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Visual tests

# Renders the activation UI in every state with the offscreen snapshot harness
# (tests/visual) and uploads the PNGs to Argos for visual diffing + baselines.
#
# Runs on macOS so the snapshots also exercise the Apple (Security.framework)
# crypto backend. Set the ARGOS_TOKEN repository secret to enable the upload;
# without it the job still builds, renders, and stores the PNGs as an artifact.

on:
push:
branches: [main]
pull_request:

concurrency:
group: visual-${{ github.ref }}
cancel-in-progress: true

jobs:
ui-snapshots:
runs-on: macos-14
env:
ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Argos uses git history to find the baseline build

- uses: actions/setup-node@v4
with:
node-version: 20

- name: Build harness + render snapshots
run: ./scripts/visual-snapshots.sh

- name: Upload snapshots artifact
uses: actions/upload-artifact@v4
with:
name: ui-snapshots
path: ui-snapshots/*.png

- name: Upload to Argos
if: ${{ env.ARGOS_TOKEN != '' }}
run: npx --yes @argos-ci/cli upload ui-snapshots
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ _deps/
license.mb
node_modules/
CMakeLists.txt.bak
.DS_Store
ui-snapshots/
node_modules
.argos
96 changes: 84 additions & 12 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,36 @@ option(MOONBASE_BUILD_TESTS "Build Moonbase C++ SDK tests" ${MOONBASE_IS_TOP_LEV
option(MOONBASE_BUILD_EXAMPLES "Build Moonbase C++ SDK examples" ${MOONBASE_IS_TOP_LEVEL})
option(MOONBASE_BUILD_JUCE_EXAMPLE
"Build the JUCE bridge example (fetches JUCE; off by default)" OFF)
option(MOONBASE_BUILD_JUCE_NATIVE_EXAMPLE
"Build the native JUCE module sample app (fetches JUCE; off by default)" OFF)
option(MOONBASE_BUILD_UI_SNAPSHOTS
"Build the UI snapshot harness for visual tests (fetches JUCE; off by default)" OFF)
option(MOONBASE_BUILD_JUCE_TESTS
"Build the JUCE module unit tests (fetches JUCE + doctest; off by default)" OFF)

# Optional sanitizer for the test targets. One of: address | thread | undefined
# | address,undefined (ASan and TSan can't be combined). Empty = off, so normal
# builds are unaffected. Example:
# cmake -B build-tsan -DMOONBASE_BUILD_JUCE_TESTS=ON -DMOONBASE_SANITIZER=thread
set(MOONBASE_SANITIZER "" CACHE STRING
"Sanitizer for test targets: address | thread | undefined | address,undefined")

function(moonbase_apply_sanitizer target)
if(MOONBASE_SANITIZER AND NOT MSVC)
target_compile_options(${target} PRIVATE
-fsanitize=${MOONBASE_SANITIZER} -fno-omit-frame-pointer -g)
target_link_options(${target} PRIVATE -fsanitize=${MOONBASE_SANITIZER})
endif()
endfunction()

# The libcurl HTTP transport is the SDK's default. The JUCE module ships its own
# JUCE-based transport and needs no curl, so JUCE-only builds (the module + its
# tests) can turn this off and avoid the dependency entirely.
option(MOONBASE_USE_CURL "Build the SDK target with the libcurl HTTP transport" ON)

find_package(CURL REQUIRED)
if(MOONBASE_USE_CURL)
find_package(CURL REQUIRED)
endif()
find_package(OpenSSL REQUIRED)

find_package(nlohmann_json QUIET)
Expand All @@ -37,30 +65,34 @@ if(NOT nlohmann_json_FOUND)
set(MOONBASE_NLOHMANN_FETCHED ON)
endif()

add_library(moonbase_licensing INTERFACE)
add_library(moonbase::licensing ALIAS moonbase_licensing)
set_target_properties(moonbase_licensing PROPERTIES EXPORT_NAME licensing)
add_library(moonbase_cpp INTERFACE)
add_library(moonbase::licensing ALIAS moonbase_cpp)
set_target_properties(moonbase_cpp PROPERTIES EXPORT_NAME licensing)

target_compile_features(moonbase_licensing INTERFACE cxx_std_17)
target_include_directories(moonbase_licensing
target_compile_features(moonbase_cpp INTERFACE cxx_std_17)
target_include_directories(moonbase_cpp
INTERFACE
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
target_link_libraries(moonbase_licensing
target_link_libraries(moonbase_cpp
INTERFACE
CURL::libcurl
OpenSSL::SSL
OpenSSL::Crypto)
if(MOONBASE_USE_CURL)
target_link_libraries(moonbase_cpp INTERFACE CURL::libcurl)
else()
target_compile_definitions(moonbase_cpp INTERFACE MOONBASE_DISABLE_CURL_TRANSPORT=1)
endif()
if(MOONBASE_NLOHMANN_FETCHED)
target_include_directories(moonbase_licensing
target_include_directories(moonbase_cpp
INTERFACE
$<BUILD_INTERFACE:${nlohmann_json_SOURCE_DIR}/include>)
else()
target_link_libraries(moonbase_licensing
target_link_libraries(moonbase_cpp
INTERFACE
nlohmann_json::nlohmann_json)
endif()
target_compile_definitions(moonbase_licensing
target_compile_definitions(moonbase_cpp
INTERFACE
MOONBASE_CPP_VERSION="${PROJECT_VERSION}")

Expand All @@ -77,6 +109,37 @@ if(MOONBASE_BUILD_JUCE_EXAMPLE)
add_subdirectory(examples/juce)
endif()

# The native sample app and the UI snapshot harness both consume the JUCE module.
# Fetch JUCE and add the module once here so the two targets can share them
# (juce_add_module on the same folder twice would collide).
if(MOONBASE_BUILD_JUCE_NATIVE_EXAMPLE OR MOONBASE_BUILD_UI_SNAPSHOTS OR MOONBASE_BUILD_JUCE_TESTS)
enable_language(C)
if(APPLE)
enable_language(OBJC OBJCXX)
endif()

include(FetchContent)
set(MOONBASE_JUCE_VERSION "8.0.4" CACHE STRING "JUCE tag to fetch for the JUCE module targets")
FetchContent_Declare(
JUCE
GIT_REPOSITORY https://github.com/juce-framework/JUCE.git
GIT_TAG ${MOONBASE_JUCE_VERSION}
GIT_SHALLOW TRUE)
FetchContent_MakeAvailable(JUCE)

if(NOT TARGET moonbase_licensing)
juce_add_module(${CMAKE_CURRENT_SOURCE_DIR}/modules/moonbase_licensing)
endif()
endif()

if(MOONBASE_BUILD_JUCE_NATIVE_EXAMPLE)
add_subdirectory(examples/juce-native)
endif()

if(MOONBASE_BUILD_UI_SNAPSHOTS)
add_subdirectory(tests/visual)
endif()

if(MOONBASE_BUILD_TESTS)
enable_testing()

Expand All @@ -103,11 +166,20 @@ if(MOONBASE_BUILD_TESTS)
tests/validator_tests.cpp
tests/live_tests.cpp)
target_link_libraries(moonbase_tests PRIVATE moonbase::licensing doctest::doctest)
moonbase_apply_sanitizer(moonbase_tests)
include(doctest)
doctest_discover_tests(moonbase_tests)
endif()

install(TARGETS moonbase_licensing
# JUCE module unit tests (doctest, links the moonbase_licensing JUCE module).
# Placed after the SDK-test block so doctest is already available when both
# MOONBASE_BUILD_TESTS and MOONBASE_BUILD_JUCE_TESTS are enabled together.
if(MOONBASE_BUILD_JUCE_TESTS)
enable_testing()
add_subdirectory(tests/juce)
endif()

install(TARGETS moonbase_cpp
EXPORT moonbase_cppTargets)
install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
install(FILES "${PROJECT_SOURCE_DIR}/LICENSE"
Expand Down
Loading
Loading