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
24 changes: 24 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: C++ CI

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
build-and-test:
name: C++ - ${{ github.event_name }}
runs-on: ubuntu-latest
permissions:
actions: write
contents: read
steps:
# 1. Check out your code
- uses: actions/checkout@v4
# # 2. Install dependencies (if using vcpkg, Conan, etc.)
# - name: Install dependencies
# run: sudo apt update && sudo apt install -y cmake g++ make
- run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
- run: cmake --build build --parallel
- run: ctest --test-dir build --output-on-failure --verbose --no-compress-output
135 changes: 77 additions & 58 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,72 +1,91 @@
cmake_minimum_required(VERSION 3.10)
cmake_minimum_required(VERSION 3.16)
project(YAMLWrapper)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

if(APPLE)
# Check for package managers
execute_process(COMMAND which brew OUTPUT_VARIABLE HOMEBREW_EXISTS OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(COMMAND which port OUTPUT_VARIABLE MACPORTS_EXISTS OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(COMMAND which conda OUTPUT_VARIABLE CONDA_EXISTS OUTPUT_STRIP_TRAILING_WHITESPACE)
enable_testing()
include(FetchContent)

FetchContent_Declare(
yaml-cpp
GIT_REPOSITORY https://github.com/jbeder/yaml-cpp.git
GIT_TAG 0.8.0
)
FetchContent_Declare(
Catch2
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
GIT_TAG v3.5.2
)

FetchContent_MakeAvailable(yaml-cpp)
FetchContent_MakeAvailable(Catch2)
add_subdirectory(tests)

file(COPY ${CMAKE_SOURCE_DIR}/lattice_files/
DESTINATION ${CMAKE_BINARY_DIR}/lattice_files/)

# if(APPLE)
# # Check for package managers
# execute_process(COMMAND which brew OUTPUT_VARIABLE HOMEBREW_EXISTS OUTPUT_STRIP_TRAILING_WHITESPACE)
# execute_process(COMMAND which port OUTPUT_VARIABLE MACPORTS_EXISTS OUTPUT_STRIP_TRAILING_WHITESPACE)
# execute_process(COMMAND which conda OUTPUT_VARIABLE CONDA_EXISTS OUTPUT_STRIP_TRAILING_WHITESPACE)

# Error if both Homebrew and MacPorts exist
if(HOMEBREW_EXISTS AND MACPORTS_EXISTS)
message(FATAL_ERROR
"Both Homebrew and MacPorts detected. This can cause conflicts.\n"
"Please use only one package manager:\n"
" - Homebrew found at: ${HOMEBREW_EXISTS}\n"
" - MacPorts found at: ${MACPORTS_EXISTS}\n"
"Consider uninstalling one to avoid library conflicts."
)
endif()
# # Error if both Homebrew and MacPorts exist
# if(HOMEBREW_EXISTS AND MACPORTS_EXISTS)
# message(FATAL_ERROR
# "Both Homebrew and MacPorts detected. This can cause conflicts.\n"
# "Please use only one package manager:\n"
# " - Homebrew found at: ${HOMEBREW_EXISTS}\n"
# " - MacPorts found at: ${MACPORTS_EXISTS}\n"
# "Consider uninstalling one to avoid library conflicts."
# )
# endif()

# Set RPATH based on which package manager is found
set(BASE_RPATH "@executable_path/../lib;@loader_path")
# # Set RPATH based on which package manager is found
# set(BASE_RPATH "@executable_path/../lib;@loader_path")

if(HOMEBREW_EXISTS)
message(STATUS "Using Homebrew package manager")
# Check if Apple Silicon or Intel
execute_process(
COMMAND uname -m
OUTPUT_VARIABLE ARCH
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if(ARCH STREQUAL "arm64")
set(CMAKE_INSTALL_RPATH "${BASE_RPATH};/opt/homebrew/lib")
list(APPEND CMAKE_PREFIX_PATH "/opt/homebrew")
else()
set(CMAKE_INSTALL_RPATH "${BASE_RPATH};/usr/local/lib")
list(APPEND CMAKE_PREFIX_PATH "/usr/local")
endif()
elseif(MACPORTS_EXISTS)
message(STATUS "Using MacPorts package manager")
set(CMAKE_INSTALL_RPATH "${BASE_RPATH};/opt/local/lib")
list(APPEND CMAKE_PREFIX_PATH "/opt/local")
elseif(CONDA_EXISTS)
message(STATUS "Using Conda package manager")
# Get conda prefix
execute_process(
COMMAND conda info --base
OUTPUT_VARIABLE CONDA_PREFIX
OUTPUT_STRIP_TRAILING_WHITESPACE
)
set(CMAKE_INSTALL_RPATH "${BASE_RPATH};${CONDA_PREFIX}/lib")
list(APPEND CMAKE_PREFIX_PATH "${CONDA_PREFIX}")
else()
message(STATUS "No package manager detected, using default paths")
set(CMAKE_INSTALL_RPATH "${BASE_RPATH};/usr/local/lib")
endif()
# if(HOMEBREW_EXISTS)
# message(STATUS "Using Homebrew package manager")
# # Check if Apple Silicon or Intel
# execute_process(
# COMMAND uname -m
# OUTPUT_VARIABLE ARCH
# OUTPUT_STRIP_TRAILING_WHITESPACE
# )
# if(ARCH STREQUAL "arm64")
# set(CMAKE_INSTALL_RPATH "${BASE_RPATH};/opt/homebrew/lib")
# list(APPEND CMAKE_PREFIX_PATH "/opt/homebrew")
# else()
# set(CMAKE_INSTALL_RPATH "${BASE_RPATH};/usr/local/lib")
# list(APPEND CMAKE_PREFIX_PATH "/usr/local")
# endif()
# elseif(MACPORTS_EXISTS)
# message(STATUS "Using MacPorts package manager")
# set(CMAKE_INSTALL_RPATH "${BASE_RPATH};/opt/local/lib")
# list(APPEND CMAKE_PREFIX_PATH "/opt/local")
# elseif(CONDA_EXISTS)
# message(STATUS "Using Conda package manager")
# # Get conda prefix
# execute_process(
# COMMAND conda info --base
# OUTPUT_VARIABLE CONDA_PREFIX
# OUTPUT_STRIP_TRAILING_WHITESPACE
# )
# set(CMAKE_INSTALL_RPATH "${BASE_RPATH};${CONDA_PREFIX}/lib")
# list(APPEND CMAKE_PREFIX_PATH "${CONDA_PREFIX}")
# else()
# message(STATUS "No package manager detected, using default paths")
# set(CMAKE_INSTALL_RPATH "${BASE_RPATH};/usr/local/lib")
# endif()

set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
message(STATUS "RPATH set to: ${CMAKE_INSTALL_RPATH}")
endif()

find_package(yaml-cpp REQUIRED)
# set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
# message(STATUS "RPATH set to: ${CMAKE_INSTALL_RPATH}")
# endif()

add_library(yaml_c_wrapper SHARED src/yaml_c_wrapper.cpp)
target_link_libraries(yaml_c_wrapper yaml-cpp)

add_executable(yaml_reader src/yaml_reader.cpp)
target_link_libraries(yaml_reader yaml_c_wrapper)
add_executable(yaml_reader examples/yaml_reader.cpp)
target_link_libraries(yaml_reader yaml_c_wrapper)
39 changes: 27 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
## Introduction
yaml_c_wrapper.cpp wraps YAML::Node into C objects so they can be part of a shared object library to interface with other languages

<!--
First install yaml-cpp by running

macOS:
Expand All @@ -20,16 +20,31 @@ cd yaml-cpp/src
mkdir build && cd build
cmake ..
cmake --build .
cmake --install .
cmake --install . -->

Next, in pals-cpp, run
## Usage
In pals-cpp, run

mkdir build && cd build
cmake .. -DYAML_BUILD_SHARED_LIBS=ON
make

## Example
See yaml_reader.cpp for an example of how to use the library to read a lattice file,
perform a basic manipulation, and write to another lattice file.
Navigate to the build directly and run
./yaml_reader
cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -S . -B build
cmake --build build

This builds libyaml_c_wrapper.dylib, a shared object library that can be used
by other languages.

It also builds an executable using yaml_reader.cpp containing examples for how
to use the library to read lattice files, perform basic manipulations, and write
to other lattice files. To see the output, navigate to the build directory and run
./yaml_reader

It will also build the tests.

## Testing
In the root pals-cpp directory, run
ctest --test-dir build --output-on-failure

To run a specific test, run
ctest --test-dir build -R "Test Name"

## Issue
yaml-cpp's cmake only requires cmake version 3.4, which is deprecated. Warnings must
be suppressed to run properly
38 changes: 38 additions & 0 deletions examples/yaml_reader.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#include "../src/yaml_c_wrapper.h"
#include <iostream>

int main() {
// reading a lattice from a yaml file
YAMLNodeHandle handle = yaml_parse_file("../lattice_files/ex.yaml");
// printing to terminal
std::cout << yaml_to_string(handle) << std::endl << std::endl;

// type checking
std::cout << (yaml_is_sequence(handle)) << "\n";

// accessing sequence
YAMLNodeHandle node = yaml_get_index(handle, 0);
std::cout << "the first element is: \n" << yaml_to_string(node) << "\n";

// accessing map
std::cout << "\nthe value at key 'thingB' is: " << yaml_to_string(yaml_get_key(node, "thingB")) << "\n";

// creating a new node that's a map
YAMLNodeHandle map = yaml_create_map();
yaml_set_int(map, "first", 1);

// creating a new node that's a sequence
YAMLNodeHandle sequence = yaml_create_sequence();
yaml_push_string(sequence, "magnet1");
yaml_push_string(sequence, "");
YAMLNodeHandle scalar = yaml_create_scalar();
yaml_set_scalar_string(scalar, "magnet2");
yaml_set_at_index(sequence, 1, scalar);

// adding new nodes to lattice
yaml_push_node(handle, map);
yaml_push_node(handle, sequence);

// writing modified lattice file to expand.yaml
yaml_write_file(handle, "../lattice_files/expand.yaml");
}
3 changes: 1 addition & 2 deletions lattice_files/ex.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,4 @@
length: 1.03
direction: -1
- a_subline: # Item a_subline is repeated three times
repeat: 3
hi: 2
repeat: 3
56 changes: 39 additions & 17 deletions src/yaml_c_wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,6 @@ extern "C" {
return static_cast<YAML::Node*>(handle)->size();
}

// === ITERATION HELPERS ===

// For iterating over maps - get all keys
char** yaml_get_keys(YAMLNodeHandle handle, int* out_count) {
auto node = static_cast<YAML::Node*>(handle);
if (!node->IsMap()) {
Expand All @@ -149,20 +146,6 @@ extern "C" {
return result;
}

void yaml_free_keys(char** keys, int count) {
for (int i = 0; i < count; i++) {
delete[] keys[i];
}
delete[] keys;
}

// Set node at index for sequences
void yaml_set_at_index(YAMLNodeHandle handle, int index, YAMLNodeHandle value) {
auto node = static_cast<YAML::Node*>(handle);
auto val_node = static_cast<YAML::Node*>(value);
(*node)[index] = *val_node;
}

// === CONVERT TO C TYPES (caller must free returned strings) ===
char* yaml_as_string(YAMLNodeHandle handle) {
try {
Expand Down Expand Up @@ -225,6 +208,37 @@ extern "C" {
auto val_node = static_cast<YAML::Node*>(value);
(*node)[key] = *val_node;
}

void yaml_set_scalar_string(YAMLNodeHandle handle, const char* value) {
if (handle == nullptr || value == nullptr) return;
auto node = static_cast<YAML::Node*>(handle);
*node = value;
}

void yaml_set_scalar_int(YAMLNodeHandle handle, int value) {
if (handle == nullptr) return;
auto node = static_cast<YAML::Node*>(handle);
*node = value;
}

void yaml_set_scalar_float(YAMLNodeHandle handle, double value) {
if (handle == nullptr) return;
auto node = static_cast<YAML::Node*>(handle);
*node = value;
}

void yaml_set_scalar_bool(YAMLNodeHandle handle, bool value) {
if (handle == nullptr) return;
auto node = static_cast<YAML::Node*>(handle);
*node = value;
}

// Set node at index for sequences
void yaml_set_at_index(YAMLNodeHandle handle, int index, YAMLNodeHandle value) {
auto node = static_cast<YAML::Node*>(handle);
auto val_node = static_cast<YAML::Node*>(value);
(*node)[index] = *val_node;
}

void yaml_push_string(YAMLNodeHandle handle, const char* value) {
auto node = static_cast<YAML::Node*>(handle);
Expand Down Expand Up @@ -395,6 +409,14 @@ extern "C" {
void yaml_free_string(char* str) {
delete[] str;
}

void yaml_free_keys(char** keys, int count) {
for (int i = 0; i < count; i++) {
delete[] keys[i];
}
delete[] keys;
}


YAMLNodeHandle yaml_clone(YAMLNodeHandle handle) {
return new YAML::Node(YAML::Clone(*static_cast<YAML::Node*>(handle)));
Expand Down
8 changes: 7 additions & 1 deletion src/yaml_c_wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ typedef void* YAMLNodeHandle;
YAMLNodeHandle yaml_create_node(void);
YAMLNodeHandle yaml_create_map(void);
YAMLNodeHandle yaml_create_sequence(void);
YAMLNodeHandle yaml_create_scalar();
void yaml_delete_node(YAMLNodeHandle handle);

// === PARSING ===
Expand All @@ -24,7 +25,6 @@ bool yaml_is_scalar(YAMLNodeHandle handle);
bool yaml_is_sequence(YAMLNodeHandle handle);
bool yaml_is_map(YAMLNodeHandle handle);
bool yaml_is_null(YAMLNodeHandle handle);
bool yaml_is_defined(YAMLNodeHandle handle);

// === ACCESS ===
YAMLNodeHandle yaml_get_key(YAMLNodeHandle handle, const char* key);
Expand All @@ -46,6 +46,12 @@ void yaml_set_float(YAMLNodeHandle handle, const char* key, double value);
void yaml_set_bool(YAMLNodeHandle handle, const char* key, bool value);
void yaml_set_node(YAMLNodeHandle handle, const char* key, YAMLNodeHandle value);

void yaml_set_scalar_string(YAMLNodeHandle handle, const char* value);
void yaml_set_scalar_int(YAMLNodeHandle handle, int value);
void yaml_set_scalar_float(YAMLNodeHandle handle, double value);
void yaml_set_scalar_bool(YAMLNodeHandle handle, bool value);

void yaml_set_at_index(YAMLNodeHandle handle, int index, YAMLNodeHandle value) ;
void yaml_push_string(YAMLNodeHandle handle, const char* value);
void yaml_push_int(YAMLNodeHandle handle, int value);
void yaml_push_float(YAMLNodeHandle handle, double value);
Expand Down
Loading