Skip to content

Commit 2ab91ae

Browse files
authored
Add Shared Lib support (mostly for unity) (#41)
* Remove Linux settings that are no longer needed * Delete .DS_Store * Update cmake.yml * make CI generate both static and shared libs * Update setup.py * Add GA_API exports and fix parameter types in C extern functions * check for null for all optional strings that have default values * Update GameAnalyticsExtern.cpp * add shared lib sample * disable tests for shared lib builds * Update setup.py * Update setup.py * Fix freeze caused by infinite signal loop when an exception is raised * Update CMakeLists.txt * add missing apis * Update CHANGELOG.md
1 parent a4981e3 commit 2ab91ae

13 files changed

Lines changed: 541 additions & 299 deletions

File tree

.github/workflows/cmake.yml

Lines changed: 34 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,15 @@ jobs:
2424
# Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable.
2525
fail-fast: false
2626

27-
# Set up a matrix to run the following 3 configurations:
28-
# 1. <Windows, Release, latest MSVC compiler toolchain on the default runner image, default generator>
29-
# 2. <Linux, Release, latest GCC compiler toolchain on the default runner image, default generator>
30-
# 3. <Linux, Release, latest Clang compiler toolchain on the default runner image, default generator>
31-
#
32-
# To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list.
27+
# Set up a matrix to run the following configurations:
28+
# - Multiple platforms (Windows, Linux, macOS)
29+
# - Multiple compilers (cl, gcc, clang)
30+
# - Multiple library types (static, shared)
3331
matrix:
3432
os: [ubuntu-latest, windows-latest, macOS-latest]
3533
build_type: ${{ fromJSON(format('[{0}]', inputs.build_type || '"Debug","Release"')) }}
3634
c_compiler: [gcc, clang, cl]
35+
lib_type: [static, shared]
3736
include:
3837
- os: windows-latest
3938
c_compiler: cl
@@ -70,22 +69,27 @@ jobs:
7069
shell: bash
7170
run: |
7271
echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT"
72+
# Map OS to platform
73+
if [ "${{ matrix.os }}" == "ubuntu-latest" ]; then
74+
echo "platform=linux_x64" >> "$GITHUB_OUTPUT"
75+
elif [ "${{ matrix.os }}" == "windows-latest" ]; then
76+
echo "platform=win64" >> "$GITHUB_OUTPUT"
77+
elif [ "${{ matrix.os }}" == "macOS-latest" ]; then
78+
echo "platform=osx" >> "$GITHUB_OUTPUT"
79+
fi
7380
74-
- name: Install Clang and Libraries
81+
- name: Set up Python
82+
uses: actions/setup-python@v5
83+
with:
84+
python-version: '3.x'
85+
86+
- name: Install libc++ on Ubuntu
7587
if: matrix.os == 'ubuntu-latest' && matrix.c_compiler == 'clang'
7688
run: |
7789
sudo apt-get update
78-
sudo apt-get install -y clang libc++-dev libc++abi-dev
90+
sudo apt-get install -y libc++-dev libc++abi-dev
7991
80-
- name: Set Clang 16 as Default
81-
if: matrix.os == 'ubuntu-24.04' && matrix.c_compiler == 'clang'
82-
run: |
83-
sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-16 100
84-
sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-16 100
85-
sudo update-alternatives --set clang /usr/bin/clang-16
86-
sudo update-alternatives --set clang++ /usr/bin/clang++-16
87-
88-
- name: Check Clang Settings
92+
- name: Display Clang Info
8993
if: matrix.os == 'ubuntu-latest' && matrix.c_compiler == 'clang'
9094
run: |
9195
clang --version
@@ -95,39 +99,25 @@ jobs:
9599
echo "check lld..."
96100
ldd --version
97101
98-
- name: Configure CMake
99-
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
100-
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
101-
run: >
102-
cmake -B ${{ steps.strings.outputs.build-output-dir }}
103-
-DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }}
104-
-DCMAKE_C_COMPILER=${{ matrix.c_compiler }}
105-
-DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
106-
-S ${{ github.workspace }}
107-
108-
- name: Build
109-
# Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator).
110-
run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }} --verbose
111-
112-
- name: Test
113-
working-directory: ${{ steps.strings.outputs.build-output-dir }}
114-
# Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator).
115-
# See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
116-
run: ctest --build-config ${{ matrix.build_type }} --verbose --output-on-failure
117-
118-
- name: Package Build Artifacts
119-
if: ${{ inputs.upload_artifacts && success() }}
102+
- name: Build, Test, and Package with setup.py
103+
shell: bash
120104
run: |
121-
mkdir -p ${{ steps.strings.outputs.build-output-dir }}/package
122-
# Adjust the path to your built library files
123-
cp ${{ steps.strings.outputs.build-output-dir }}/${{ matrix.build_type }}/*GameAnalytics.* ${{ steps.strings.outputs.build-output-dir }}/package/
124-
cp -r ${{ github.workspace }}/include ${{ steps.strings.outputs.build-output-dir }}/package/
105+
SHARED_FLAG=""
106+
if [ "${{ matrix.lib_type }}" == "shared" ]; then
107+
SHARED_FLAG="--shared"
108+
fi
109+
110+
if [ "${{ matrix.os }}" == "ubuntu-latest" ]; then
111+
python setup.py --platform ${{ steps.strings.outputs.platform }} --compiler ${{ matrix.c_compiler }} --cfg ${{ matrix.build_type }} $SHARED_FLAG --build --test
112+
else
113+
python setup.py --platform ${{ steps.strings.outputs.platform }} --cfg ${{ matrix.build_type }} $SHARED_FLAG --build --test
114+
fi
125115
126116
- name: Upload Build Artifact
127117
if: ${{ inputs.upload_artifacts && success() }}
128118
uses: actions/upload-artifact@v4
129119
with:
130-
name: ga-cpp-sdk-${{ matrix.os }}-${{ matrix.c_compiler }}-${{ matrix.build_type }}
120+
name: ga-cpp-sdk-${{ matrix.os }}-${{ matrix.c_compiler }}-${{ matrix.build_type }}-${{ matrix.lib_type }}
131121
path: ${{ steps.strings.outputs.build-output-dir }}/package/
132122
retention-days: 3
133123

.github/workflows/create_release.yml

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -44,31 +44,65 @@ jobs:
4444

4545
- name: Organize release files
4646
run: |
47-
# Create the base include directory in the final release
48-
mkdir -p ./final-release/include
47+
# Create separate directories for static and shared libraries
48+
mkdir -p ./static-release/include
49+
mkdir -p ./shared-release/include
4950
50-
# Copy the entire include directory from one of the platform folders (assuming they are the same across platforms)
51-
cp -r ./release-artifacts/ga-cpp-sdk-macOS-latest-clang-Release/include/* ./final-release/include/
51+
# Copy include directory from any artifact (they're all identical)
52+
FIRST_ARTIFACT=$(find ./release-artifacts -mindepth 1 -maxdepth 1 -type d | head -n 1)
53+
cp -r $FIRST_ARTIFACT/include/* ./static-release/include/
54+
cp -r $FIRST_ARTIFACT/include/* ./shared-release/include/
55+
56+
# Copy GameAnalyticsExtern.h for shared library release
57+
cp ./source/gameanalytics/GameAnalyticsExtern.h ./shared-release/include/GameAnalytics/
5258

53-
# Dynamically find all platform directories
54-
platform_dirs=$(find ./release-artifacts -mindepth 1 -maxdepth 1 -type d)
55-
56-
# Iterate over each platform directory and copy the respective binaries
57-
for platform in $platform_dirs; do
58-
platform_name=$(basename $platform)
59-
mkdir -p ./final-release/$platform_name
60-
if [[ $platform_name == *"windows"* ]]; then
61-
cp $platform/*.lib ./final-release/$platform_name/
59+
# Process static library artifacts
60+
for artifact_dir in ./release-artifacts/*-static; do
61+
artifact_name=$(basename $artifact_dir)
62+
# Extract platform info (remove ga-cpp-sdk- prefix and -static suffix)
63+
platform_info=${artifact_name#ga-cpp-sdk-}
64+
platform_info=${platform_info%-static}
65+
66+
mkdir -p ./static-release/$platform_info
67+
68+
# Copy static libraries
69+
if [[ $artifact_name == *"windows"* ]]; then
70+
cp $artifact_dir/*.lib ./static-release/$platform_info/ 2>/dev/null || true
6271
else
63-
cp $platform/*.a ./final-release/$platform_name/
72+
cp $artifact_dir/*.a ./static-release/$platform_info/ 2>/dev/null || true
73+
fi
74+
done
75+
76+
# Process shared library artifacts
77+
for artifact_dir in ./release-artifacts/*-shared; do
78+
artifact_name=$(basename $artifact_dir)
79+
# Extract platform info (remove ga-cpp-sdk- prefix and -shared suffix)
80+
platform_info=${artifact_name#ga-cpp-sdk-}
81+
platform_info=${platform_info%-shared}
82+
83+
mkdir -p ./shared-release/$platform_info
84+
85+
# Copy shared libraries
86+
if [[ $artifact_name == *"windows"* ]]; then
87+
cp $artifact_dir/*.dll ./shared-release/$platform_info/ 2>/dev/null || true
88+
elif [[ $artifact_name == *"macOS"* ]]; then
89+
cp $artifact_dir/*.dylib ./shared-release/$platform_info/ 2>/dev/null || true
90+
else
91+
cp $artifact_dir/*.so ./shared-release/$platform_info/ 2>/dev/null || true
6492
fi
6593
done
6694

67-
# Create a zip archive of the final-release directory
68-
zip -r ga-sdk-release-${{ inputs.tag_name }}.zip ./final-release
95+
# Create zip archives
96+
zip -r ga-sdk-static-${{ inputs.tag_name }}.zip ./static-release
97+
zip -r ga-sdk-shared-${{ inputs.tag_name }}.zip ./shared-release
6998

7099
- name: Show organized release files
71-
run: tree ./final-release
100+
run: |
101+
echo "=== Static Libraries ==="
102+
tree ./static-release || ls -R ./static-release
103+
echo ""
104+
echo "=== Shared Libraries ==="
105+
tree ./shared-release || ls -R ./shared-release
72106
73107
- name: Create release
74108
uses: softprops/action-gh-release@v2.0.8
@@ -77,4 +111,6 @@ jobs:
77111
name: Release GA-CPP-SDK ${{ inputs.tag_name }}
78112
generate_release_notes: true
79113
make_latest: true
80-
files: ga-sdk-release-${{ inputs.tag_name }}.zip
114+
files: |
115+
ga-sdk-static-${{ inputs.tag_name }}.zip
116+
ga-sdk-shared-${{ inputs.tag_name }}.zip

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,24 @@
11
# Changelog
22

3+
## 5.2.0
4+
5+
### Added
6+
7+
- **Shared library support** — The SDK can now be built as a shared library (`.dll`/`.so`/`.dylib`) via `-DGA_SHARED_LIB=ON` (or `--shared` in `setup.py`). Primarily aimed at Unity native plugin usage.
8+
- **Fully exported C extern API** — All `GameAnalyticsExtern` functions now carry the correct `GA_API` export/import macro (`__declspec(dllexport)` on Windows, `__attribute__((visibility("default")))` on Linux/macOS).
9+
- **New exported C API functions**: `gameAnalytics_configureBuildPlatform`, `gameAnalytics_configureCustomLogHandler` (with `GALogHandler` callback and `GALoggerMessageType` enum), `gameAnalytics_disableDeviceInfo`.
10+
- **Shared library sample** — New `sample_shared/` project demonstrating shared lib integration.
11+
- **`setup.py` `--shared` and `--compiler` flags** — Linux builds can now select `gcc` or `clang`; all platforms support `--shared` to produce a shared library.
12+
- **Null-safe C string handling** — All extern functions guard against null `const char*` inputs via a `safeString()` helper, preventing crashes from managed runtimes (e.g. Unity P/Invoke).
13+
- **Cross-platform `ga_strndup`** — Replaced POSIX-only `strndup` with a portable implementation, fixing MSVC/Windows compilation.
14+
15+
### Fixed
16+
17+
- **Linux: infinite signal loop freeze** — Signal handlers now store per-signal previous handlers in a per-signal array instead of a single shared variable, preventing re-entrant signal delivery from looping infinitely when an exception is raised.
18+
- **macOS: equivalent signal handler isolation** — Same signal handler fix applied to the macOS platform layer.
19+
- **Windows MSVC runtime library** — Switched to the `CMAKE_MSVC_RUNTIME_LIBRARY` CMake abstraction (replaces manual `/MD`/`/MT` flags), correctly handling Debug/Release and static/shared runtime selection.
20+
21+
322
# 5.1.0
423

524
### Added

CMakeLists.txt

Lines changed: 42 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -169,11 +169,9 @@ endif()
169169
if(WIN32)
170170
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DGUID_WINDOWS")
171171
if(${GA_SHARED_LIB})
172-
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MDd")
173-
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MD")
172+
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")
174173
else()
175-
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd")
176-
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT")
174+
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
177175
endif()
178176

179177
if(${GA_UWP_BUILD})
@@ -207,7 +205,11 @@ elseif(LINUX)
207205
endif()
208206

209207
if(${GA_BUILD_SAMPLE})
210-
add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/sample")
208+
if(${GA_SHARED_LIB})
209+
add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/sample_shared")
210+
else()
211+
add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/sample")
212+
endif()
211213
endif()
212214

213215
add_library(GameAnalytics ${LIB_TYPE} ${CPP_SOURCES})
@@ -217,43 +219,52 @@ message(STATUS "CMAKE_EXE_LINKER_FLAGS: ${CMAKE_EXE_LINKER_FLAGS}")
217219
message(STATUS "CMAKE_SHARED_LINKER_FLAGS: ${CMAKE_SHARED_LINKER_FLAGS}")
218220
# --------------------------- Google Test Setup --------------------------- #
219221

220-
# Set Project Name
221-
set(UT_PROJECT_NAME "${PROJECT_NAME}UnitTests")
222+
# Only build tests for static library builds
223+
# Shared library builds don't export internal symbols needed by tests
224+
if(NOT GA_SHARED_LIB)
225+
message(STATUS "Building unit tests (tests are only available for static library builds)")
226+
227+
# Set Project Name
228+
set(UT_PROJECT_NAME "${PROJECT_NAME}UnitTests")
222229

223-
# Add Google Test
224-
set(GTEST_DIR "${EXTERNALS_DIR}/googletest")
225-
add_subdirectory(${GTEST_DIR} ${PROJECT_SOURCE_DIR}/gtest_build)
230+
# Add Google Test
231+
set(GTEST_DIR "${EXTERNALS_DIR}/googletest")
232+
add_subdirectory(${GTEST_DIR} ${PROJECT_SOURCE_DIR}/gtest_build)
226233

227-
# Add tests
228-
enable_testing()
234+
# Add tests
235+
enable_testing()
229236

230-
########################################
231-
# Test files
232-
########################################
237+
########################################
238+
# Test files
239+
########################################
233240

234-
file(GLOB_RECURSE TEST_SRC_FILES "${PROJECT_SOURCE_DIR}/test/*.cpp")
241+
file(GLOB_RECURSE TEST_SRC_FILES "${PROJECT_SOURCE_DIR}/test/*.cpp")
235242

236-
########################################
237-
# Unit Tests
238-
#######################################
239-
add_executable(${UT_PROJECT_NAME} ${TEST_SRC_FILES})
243+
########################################
244+
# Unit Tests
245+
#######################################
246+
add_executable(${UT_PROJECT_NAME} ${TEST_SRC_FILES})
240247

241-
########################################
242-
# Standard linking to gtest and gmock components
243-
########################################
244-
target_link_libraries(${UT_PROJECT_NAME} gtest gtest_main gmock_main)
248+
########################################
249+
# Standard linking to gtest and gmock components
250+
########################################
251+
target_link_libraries(${UT_PROJECT_NAME} gtest gtest_main gmock_main)
245252

246-
########################################
247-
# Linking to GA SDK
248-
########################################
249-
target_link_libraries(${UT_PROJECT_NAME} ${PROJECT_NAME})
253+
########################################
254+
# Linking to GA SDK
255+
########################################
256+
target_link_libraries(${UT_PROJECT_NAME} ${PROJECT_NAME})
250257

251-
########################################
252-
add_test(NAME ${UT_PROJECT_NAME} COMMAND GameAnalyticsUnitTests)
258+
########################################
259+
add_test(NAME ${UT_PROJECT_NAME} COMMAND GameAnalyticsUnitTests)
260+
else()
261+
message(STATUS "Skipping unit tests (not available for shared library builds)")
262+
endif()
253263

254264
# --------------------------- Code Coverage Setup --------------------------- #
255265

256-
if (ENABLE_COVERAGE)
266+
# Coverage requires tests, which are only available for static library builds
267+
if (ENABLE_COVERAGE AND NOT GA_SHARED_LIB)
257268
find_program(GCOV_PATH gcov)
258269
if (NOT GCOV_PATH)
259270
message(WARNING "program gcov not found")

include/.DS_Store

-6 KB
Binary file not shown.

sample_shared/CMakeLists.txt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# set directories
2+
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/Debug")
3+
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/Release")
4+
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/Debug")
5+
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/Release")
6+
7+
set(CMAKE_CXX_STANDARD 17)
8+
9+
if(LINUX)
10+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17")
11+
12+
if(CLANG)
13+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
14+
endif()
15+
endif()
16+
17+
set(SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/Main.cpp")
18+
19+
add_executable(GASampleShared ${SOURCES})
20+
target_link_libraries(GASampleShared GameAnalytics)

0 commit comments

Comments
 (0)