From 92f0c4e0b1a7f52db7ce1702004794c933a406c5 Mon Sep 17 00:00:00 2001 From: Andrei Dabija Date: Thu, 19 Feb 2026 14:38:59 +0200 Subject: [PATCH 01/11] Remove Linux settings that are no longer needed --- .github/workflows/cmake.yml | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index fce32ecf..eba9eb7b 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -71,21 +71,7 @@ jobs: run: | echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" - - name: Install Clang and Libraries - if: matrix.os == 'ubuntu-latest' && matrix.c_compiler == 'clang' - run: | - sudo apt-get update - sudo apt-get install -y clang libc++-dev libc++abi-dev - - - name: Set Clang 16 as Default - if: matrix.os == 'ubuntu-24.04' && matrix.c_compiler == 'clang' - run: | - sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-16 100 - sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-16 100 - sudo update-alternatives --set clang /usr/bin/clang-16 - sudo update-alternatives --set clang++ /usr/bin/clang++-16 - - - name: Check Clang Settings + - name: Display Clang Info if: matrix.os == 'ubuntu-latest' && matrix.c_compiler == 'clang' run: | clang --version From fea73ba172579512e4987a0203a847a14412574c Mon Sep 17 00:00:00 2001 From: Andrei Dabija Date: Thu, 19 Feb 2026 14:41:45 +0200 Subject: [PATCH 02/11] Delete .DS_Store --- include/.DS_Store | Bin 6148 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 include/.DS_Store diff --git a/include/.DS_Store b/include/.DS_Store deleted file mode 100644 index 85fee15f3d4464810fa82e9200398a4d081b7eb0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK!AiqG5S?wSO({YT3Oz1(E!avG#7nFd??&{XQj?}=FlMDmEm8_O>ks)QevdP| zTT!aLDJnBC`?j-hvYWSIw*vs8GYVS(H2`qX2@4f$J~0|6pR# zQ|Z;}lS!-HYRJv4ooPc(b~c+0+1_qXrxj;yePj2icN5e0w5@C($oVsQpf*jey~ zVcL%aTrtrX(C)oq60772S+j{KgN+qn1z3SUsenDmoa&!kJujLSUi&eY;*~LSi-P2>{Bg4InttQF*k@KXu_l-np9y+3}MnSE-lZsm>V?dAZ+m=?99Se zC_>MU&zG7G!ZpY(E5HhTRDfzfv>M(22Vd9!n#2t&zzY0N1w^UqbvyWO_HLbfOS)?% t`XxFU@p6N2DcI3hF>>iDuA^(ixTFrEYcV&75j6fIpk?5O75GsFUI73#ST6to From fe03bcc8e8853e521b99e8d8d2f339e4779e3676 Mon Sep 17 00:00:00 2001 From: Andrei Dabija Date: Thu, 19 Feb 2026 16:16:47 +0200 Subject: [PATCH 03/11] Update cmake.yml --- .github/workflows/cmake.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index eba9eb7b..084100aa 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -71,6 +71,12 @@ jobs: run: | echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" + - name: Install libc++ on Ubuntu + if: matrix.os == 'ubuntu-latest' && matrix.c_compiler == 'clang' + run: | + sudo apt-get update + sudo apt-get install -y libc++-dev libc++abi-dev + - name: Display Clang Info if: matrix.os == 'ubuntu-latest' && matrix.c_compiler == 'clang' run: | From 3cdac947c3e3d84458567ef95b276e5c29f4aa27 Mon Sep 17 00:00:00 2001 From: Andrei Dabija Date: Thu, 19 Feb 2026 17:15:31 +0200 Subject: [PATCH 04/11] make CI generate both static and shared libs --- .github/workflows/cmake.yml | 64 ++++++++++++------------- .github/workflows/create_release.yml | 72 +++++++++++++++++++++------- setup.py | 54 ++++++++++++++++++++- 3 files changed, 137 insertions(+), 53 deletions(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 084100aa..197f2bb8 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -24,16 +24,15 @@ jobs: # 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. fail-fast: false - # Set up a matrix to run the following 3 configurations: - # 1. - # 2. - # 3. - # - # To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list. + # Set up a matrix to run the following configurations: + # - Multiple platforms (Windows, Linux, macOS) + # - Multiple compilers (cl, gcc, clang) + # - Multiple library types (static, shared) matrix: os: [ubuntu-latest, windows-latest, macOS-latest] build_type: ${{ fromJSON(format('[{0}]', inputs.build_type || '"Debug","Release"')) }} c_compiler: [gcc, clang, cl] + lib_type: [static, shared] include: - os: windows-latest c_compiler: cl @@ -70,6 +69,19 @@ jobs: shell: bash run: | echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" + # Map OS to platform + if [ "${{ matrix.os }}" == "ubuntu-latest" ]; then + echo "platform=linux_x64" >> "$GITHUB_OUTPUT" + elif [ "${{ matrix.os }}" == "windows-latest" ]; then + echo "platform=win64" >> "$GITHUB_OUTPUT" + elif [ "${{ matrix.os }}" == "macOS-latest" ]; then + echo "platform=osx" >> "$GITHUB_OUTPUT" + fi + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' - name: Install libc++ on Ubuntu if: matrix.os == 'ubuntu-latest' && matrix.c_compiler == 'clang' @@ -87,39 +99,25 @@ jobs: echo "check lld..." ldd --version - - name: Configure CMake - # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. - # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: > - cmake -B ${{ steps.strings.outputs.build-output-dir }} - -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} - -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} - -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} - -S ${{ github.workspace }} - - - name: Build - # 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). - run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }} --verbose - - - name: Test - working-directory: ${{ steps.strings.outputs.build-output-dir }} - # 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). - # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest --build-config ${{ matrix.build_type }} --verbose --output-on-failure - - - name: Package Build Artifacts - if: ${{ inputs.upload_artifacts && success() }} + - name: Build, Test, and Package with setup.py + shell: bash run: | - mkdir -p ${{ steps.strings.outputs.build-output-dir }}/package - # Adjust the path to your built library files - cp ${{ steps.strings.outputs.build-output-dir }}/${{ matrix.build_type }}/*GameAnalytics.* ${{ steps.strings.outputs.build-output-dir }}/package/ - cp -r ${{ github.workspace }}/include ${{ steps.strings.outputs.build-output-dir }}/package/ + SHARED_FLAG="" + if [ "${{ matrix.lib_type }}" == "shared" ]; then + SHARED_FLAG="--shared" + fi + + if [ "${{ matrix.os }}" == "ubuntu-latest" ]; then + python setup.py --platform ${{ steps.strings.outputs.platform }} --compiler ${{ matrix.c_compiler }} --cfg ${{ matrix.build_type }} $SHARED_FLAG --build --test + else + python setup.py --platform ${{ steps.strings.outputs.platform }} --cfg ${{ matrix.build_type }} $SHARED_FLAG --build --test + fi - name: Upload Build Artifact if: ${{ inputs.upload_artifacts && success() }} uses: actions/upload-artifact@v4 with: - name: ga-cpp-sdk-${{ matrix.os }}-${{ matrix.c_compiler }}-${{ matrix.build_type }} + name: ga-cpp-sdk-${{ matrix.os }}-${{ matrix.c_compiler }}-${{ matrix.build_type }}-${{ matrix.lib_type }} path: ${{ steps.strings.outputs.build-output-dir }}/package/ retention-days: 3 diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml index f8cb19e9..188f7a50 100644 --- a/.github/workflows/create_release.yml +++ b/.github/workflows/create_release.yml @@ -44,31 +44,65 @@ jobs: - name: Organize release files run: | - # Create the base include directory in the final release - mkdir -p ./final-release/include + # Create separate directories for static and shared libraries + mkdir -p ./static-release/include + mkdir -p ./shared-release/include - # Copy the entire include directory from one of the platform folders (assuming they are the same across platforms) - cp -r ./release-artifacts/ga-cpp-sdk-macOS-latest-clang-Release/include/* ./final-release/include/ + # Copy include directory from any artifact (they're all identical) + FIRST_ARTIFACT=$(find ./release-artifacts -mindepth 1 -maxdepth 1 -type d | head -n 1) + cp -r $FIRST_ARTIFACT/include/* ./static-release/include/ + cp -r $FIRST_ARTIFACT/include/* ./shared-release/include/ + + # Copy GameAnalyticsExtern.h for shared library release + cp ./source/gameanalytics/GameAnalyticsExtern.h ./shared-release/include/GameAnalytics/ - # Dynamically find all platform directories - platform_dirs=$(find ./release-artifacts -mindepth 1 -maxdepth 1 -type d) - - # Iterate over each platform directory and copy the respective binaries - for platform in $platform_dirs; do - platform_name=$(basename $platform) - mkdir -p ./final-release/$platform_name - if [[ $platform_name == *"windows"* ]]; then - cp $platform/*.lib ./final-release/$platform_name/ + # Process static library artifacts + for artifact_dir in ./release-artifacts/*-static; do + artifact_name=$(basename $artifact_dir) + # Extract platform info (remove ga-cpp-sdk- prefix and -static suffix) + platform_info=${artifact_name#ga-cpp-sdk-} + platform_info=${platform_info%-static} + + mkdir -p ./static-release/$platform_info + + # Copy static libraries + if [[ $artifact_name == *"windows"* ]]; then + cp $artifact_dir/*.lib ./static-release/$platform_info/ 2>/dev/null || true else - cp $platform/*.a ./final-release/$platform_name/ + cp $artifact_dir/*.a ./static-release/$platform_info/ 2>/dev/null || true + fi + done + + # Process shared library artifacts + for artifact_dir in ./release-artifacts/*-shared; do + artifact_name=$(basename $artifact_dir) + # Extract platform info (remove ga-cpp-sdk- prefix and -shared suffix) + platform_info=${artifact_name#ga-cpp-sdk-} + platform_info=${platform_info%-shared} + + mkdir -p ./shared-release/$platform_info + + # Copy shared libraries + if [[ $artifact_name == *"windows"* ]]; then + cp $artifact_dir/*.dll ./shared-release/$platform_info/ 2>/dev/null || true + elif [[ $artifact_name == *"macOS"* ]]; then + cp $artifact_dir/*.dylib ./shared-release/$platform_info/ 2>/dev/null || true + else + cp $artifact_dir/*.so ./shared-release/$platform_info/ 2>/dev/null || true fi done - # Create a zip archive of the final-release directory - zip -r ga-sdk-release-${{ inputs.tag_name }}.zip ./final-release + # Create zip archives + zip -r ga-sdk-static-${{ inputs.tag_name }}.zip ./static-release + zip -r ga-sdk-shared-${{ inputs.tag_name }}.zip ./shared-release - name: Show organized release files - run: tree ./final-release + run: | + echo "=== Static Libraries ===" + tree ./static-release || ls -R ./static-release + echo "" + echo "=== Shared Libraries ===" + tree ./shared-release || ls -R ./shared-release - name: Create release uses: softprops/action-gh-release@v2.0.8 @@ -77,4 +111,6 @@ jobs: name: Release GA-CPP-SDK ${{ inputs.tag_name }} generate_release_notes: true make_latest: true - files: ga-sdk-release-${{ inputs.tag_name }}.zip + files: | + ga-sdk-static-${{ inputs.tag_name }}.zip + ga-sdk-shared-${{ inputs.tag_name }}.zip diff --git a/setup.py b/setup.py index 5d4d400a..72589d04 100644 --- a/setup.py +++ b/setup.py @@ -3,6 +3,7 @@ import subprocess import shutil import glob +import platform as plat def run_command(command, shell=True, cwd=None): if os.name == 'nt': # Check if the OS is Windows @@ -11,21 +12,67 @@ def run_command(command, shell=True, cwd=None): result = subprocess.run(command, shell=shell, check=True, text=True, cwd=cwd) return result +def get_compiler_for_platform(platform, compiler=None): + """Get compiler configuration for a platform (single compiler, matching cmake.yml behavior)""" + if platform == 'osx': + return {'c': 'clang', 'cxx': 'clang++'} + elif platform.startswith('win'): + return {'c': 'cl', 'cxx': 'cl'} + elif platform.startswith('linux'): + if compiler == 'gcc': + return {'c': 'gcc', 'cxx': 'g++'} + else: + # Default to Clang for Linux + return {'c': 'clang', 'cxx': 'clang++'} + return {} + def main(): parser = argparse.ArgumentParser(description="CMake Build and Test Script") - parser.add_argument('--platform', required=True, choices=['linux_x64', 'linux_x86', 'osx', 'win32', 'win64', 'uwp'], help='Platform to build for') + parser.add_argument('--platform', choices=['linux_x64', 'linux_x86', 'osx', 'win32', 'win64', 'uwp'], help='Platform to build for', required=True) parser.add_argument('--cfg', default='Debug', choices=['Release', 'Debug'], help='Configuration Type') + parser.add_argument('--compiler', choices=['gcc', 'clang'], help='Compiler to use (Linux only: gcc or clang, default=clang)') + parser.add_argument('--shared', action='store_true', help='Build shared library instead of static') parser.add_argument('--build', action='store_true', help='Execute the build step') parser.add_argument('--test', action='store_true', help='Execute the test step') parser.add_argument('--coverage', action='store_true', help='Generate code coverage report') args = parser.parse_args() + # Validate compiler argument is only used with Linux + if args.compiler and not args.platform.startswith('linux'): + parser.error('--compiler can only be used with Linux platforms') + + # Get compiler configuration for this platform (single compiler, like cmake.yml) + compiler_config = get_compiler_for_platform(args.platform, args.compiler) + c_compiler = compiler_config.get('c', '') + cxx_compiler = compiler_config.get('cxx', '') + compiler_name = c_compiler if c_compiler else 'default' + + lib_type = 'shared' if args.shared else 'static' + print(f"\n{'='*60}") + print(f"Building {lib_type} library for {args.platform} with {compiler_name}") + print(f"{'='*60}\n") + + # Always use 'build/' directory (matching cmake.yml behavior) build_output_dir = os.path.join(os.getcwd(), 'build') os.makedirs(build_output_dir, exist_ok=True) # Configure cmake_command = f'cmake -B {build_output_dir} -S {os.getcwd()}' + + # Add compiler flags + if c_compiler: + cmake_command += f' -DCMAKE_C_COMPILER={c_compiler}' + if cxx_compiler: + cmake_command += f' -DCMAKE_CXX_COMPILER={cxx_compiler}' + + # Add build type for single-config generators (Makefile, Ninja) + cmake_command += f' -DCMAKE_BUILD_TYPE={args.cfg}' + + # Configure for shared library build + if args.shared: + cmake_command += ' -DGA_SHARED_LIB=ON -DGA_BUILD_SAMPLE=OFF' + if args.platform == 'osx': cmake_command += ' -G "Xcode"' if args.platform: @@ -37,7 +84,7 @@ def main(): # Build if args.build: - run_command(f'cmake --build {build_output_dir} --config {args.cfg}') + run_command(f'cmake --build {build_output_dir} --config {args.cfg} --verbose') else: exit(0) @@ -68,6 +115,9 @@ def main(): if args.platform == 'osx': run_command(f'lipo -info {package_dir}/*GameAnalytics.*') + + print(f"\n✓ {lib_type.capitalize()} library build completed for {args.platform} with {compiler_name}") + print(f" Package location: {package_dir}\n") if __name__ == "__main__": main() From c2bed3d16da741c96f7f8ac4a1eb9b0add1fd879 Mon Sep 17 00:00:00 2001 From: Andrei Dabija Date: Thu, 19 Feb 2026 17:19:59 +0200 Subject: [PATCH 05/11] Update setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 72589d04..8ed763b5 100644 --- a/setup.py +++ b/setup.py @@ -116,7 +116,7 @@ def main(): if args.platform == 'osx': run_command(f'lipo -info {package_dir}/*GameAnalytics.*') - print(f"\n✓ {lib_type.capitalize()} library build completed for {args.platform} with {compiler_name}") + print(f"\n[OK] {lib_type.capitalize()} library build completed for {args.platform} with {compiler_name}") print(f" Package location: {package_dir}\n") if __name__ == "__main__": From 6a781898255c49ca821597c82ccd0f4938bc4fcc Mon Sep 17 00:00:00 2001 From: Andrei Dabija Date: Thu, 19 Feb 2026 17:24:18 +0200 Subject: [PATCH 06/11] Add GA_API exports and fix parameter types in C extern functions --- source/gameanalytics/GameAnalyticsExtern.cpp | 118 +++++++++---------- 1 file changed, 59 insertions(+), 59 deletions(-) diff --git a/source/gameanalytics/GameAnalyticsExtern.cpp b/source/gameanalytics/GameAnalyticsExtern.cpp index c6694b56..bcadf788 100644 --- a/source/gameanalytics/GameAnalyticsExtern.cpp +++ b/source/gameanalytics/GameAnalyticsExtern.cpp @@ -24,7 +24,7 @@ gameanalytics::StringVector makeStringVector(const char** arr, int size) return {}; } -void gameAnalytics_freeString(const char* ptr) +GA_API void gameAnalytics_freeString(const char* ptr) { std::free((void*)ptr); } @@ -34,52 +34,52 @@ const char* gameAnalytics_allocString(std::string const& s) return strndup(s.c_str(), s.size()); } -void gameAnalytics_configureAvailableCustomDimensions01(const char **customDimensions, int size) +GA_API void gameAnalytics_configureAvailableCustomDimensions01(const char **customDimensions, int size) { gameanalytics::StringVector values = makeStringVector(customDimensions, size); gameanalytics::GameAnalytics::configureAvailableCustomDimensions01(values); } -void gameAnalytics_configureAvailableCustomDimensions02(const char **customDimensions, int size) +GA_API void gameAnalytics_configureAvailableCustomDimensions02(const char **customDimensions, int size) { gameanalytics::StringVector values = makeStringVector(customDimensions, size); gameanalytics::GameAnalytics::configureAvailableCustomDimensions02(values); } -void gameAnalytics_configureAvailableCustomDimensions03(const char **customDimensions, int size) +GA_API void gameAnalytics_configureAvailableCustomDimensions03(const char **customDimensions, int size) { gameanalytics::StringVector values = makeStringVector(customDimensions, size); gameanalytics::GameAnalytics::configureAvailableCustomDimensions03(values); } -void gameAnalytics_configureAvailableResourceCurrencies(const char** currencies, int size) +GA_API void gameAnalytics_configureAvailableResourceCurrencies(const char** currencies, int size) { gameanalytics::StringVector values = makeStringVector(currencies, size); gameanalytics::GameAnalytics::configureAvailableResourceCurrencies(values); } -void gameAnalytics_configureAvailableResourceItemTypes(const char** resources, int size) +GA_API void gameAnalytics_configureAvailableResourceItemTypes(const char** resources, int size) { gameanalytics::StringVector values = makeStringVector(resources, size); gameanalytics::GameAnalytics::configureAvailableResourceItemTypes(values); } -void gameAnalytics_configureBuild(const char *build) +GA_API void gameAnalytics_configureBuild(const char *build) { gameanalytics::GameAnalytics::configureBuild(build); } -void gameAnalytics_configureWritablePath(const char *writablePath) +GA_API void gameAnalytics_configureWritablePath(const char *writablePath) { gameanalytics::GameAnalytics::configureWritablePath(writablePath); } -void gameAnalytics_configureDeviceModel(const char *deviceModel) +GA_API void gameAnalytics_configureDeviceModel(const char *deviceModel) { gameanalytics::GameAnalytics::configureDeviceModel(deviceModel); } -void gameAnalytics_configureDeviceManufacturer(const char *deviceManufacturer) +GA_API void gameAnalytics_configureDeviceManufacturer(const char *deviceManufacturer) { gameanalytics::GameAnalytics::configureDeviceManufacturer(deviceManufacturer); } @@ -87,223 +87,223 @@ void gameAnalytics_configureDeviceManufacturer(const char *deviceManufacturer) // the version of SDK code used in an engine. Used for sdk_version field. // !! if set then it will override the SdkWrapperVersion. // example "unity 4.6.9" -void gameAnalytics_configureSdkGameEngineVersion(const char *sdkGameEngineVersion) +GA_API void gameAnalytics_configureSdkGameEngineVersion(const char *sdkGameEngineVersion) { gameanalytics::GameAnalytics::configureSdkGameEngineVersion(sdkGameEngineVersion); } // the version of the game engine (if used and version is available) -void gameAnalytics_configureGameEngineVersion(const char *engineVersion) +GA_API void gameAnalytics_configureGameEngineVersion(const char *engineVersion) { gameanalytics::GameAnalytics::configureGameEngineVersion(engineVersion); } -void gameAnalytics_configureUserId(const char *uId) +GA_API void gameAnalytics_configureUserId(const char *uId) { gameanalytics::GameAnalytics::configureUserId(uId); } -void gameAnalytics_configureExternalUserId(const char* extId) +GA_API void gameAnalytics_configureExternalUserId(const char* extId) { gameanalytics::GameAnalytics::configureExternalUserId(extId); } // initialize - starting SDK (need configuration before starting) -void gameAnalytics_initialize(const char *gameKey, const char *gameSecret) +GA_API void gameAnalytics_initialize(const char *gameKey, const char *gameSecret) { gameanalytics::GameAnalytics::initialize(gameKey, gameSecret); } // add events -void gameAnalytics_addBusinessEvent(const char *currency, double amount, const char *itemType, const char *itemId, const char *cartType, const char *fields, GAStatus mergeFields) +GA_API void gameAnalytics_addBusinessEvent(const char *currency, double amount, const char *itemType, const char *itemId, const char *cartType, const char *customFields, GAStatus mergeFields) { - gameanalytics::GameAnalytics::addBusinessEvent(currency, (int)amount, itemType, itemId, cartType, fields, mergeFields); + gameanalytics::GameAnalytics::addBusinessEvent(currency, (int)amount, itemType, itemId, cartType, customFields, mergeFields); } -void gameAnalytics_addResourceEvent(GAResourceFlowType flowType, const char *currency, double amount, const char *itemType, const char *itemId, const char *fields, GAStatus mergeFields) +GA_API void gameAnalytics_addResourceEvent(GAResourceFlowType flowType, const char *currency, double amount, const char *itemType, const char *itemId, const char *customFields, GAStatus mergeFields) { - gameanalytics::GameAnalytics::addResourceEvent((gameanalytics::EGAResourceFlowType)flowType, currency, (float)amount, itemType, itemId, fields, mergeFields); + gameanalytics::GameAnalytics::addResourceEvent((gameanalytics::EGAResourceFlowType)flowType, currency, (float)amount, itemType, itemId, customFields, mergeFields); } -void gameAnalytics_addProgressionEvent(int progressionStatus, const char *progression01, const char *progression02, const char *progression03, const char *fields, GAStatus mergeFields) +GA_API void gameAnalytics_addProgressionEvent(GAProgressionStatus progressionStatus, const char *progression01, const char *progression02, const char *progression03, const char *customFields, GAStatus mergeFields) { - gameanalytics::GameAnalytics::addProgressionEvent((gameanalytics::EGAProgressionStatus)progressionStatus, progression01, progression02, progression03, fields, mergeFields); + gameanalytics::GameAnalytics::addProgressionEvent((gameanalytics::EGAProgressionStatus)progressionStatus, progression01, progression02, progression03, customFields, mergeFields); } -void gameAnalytics_addProgressionEventWithScore(int progressionStatus, const char *progression01, const char *progression02, const char *progression03, int score, const char *fields, GAStatus mergeFields) +GA_API void gameAnalytics_addProgressionEventWithScore(GAProgressionStatus progressionStatus, const char *progression01, const char *progression02, const char *progression03, int score, const char *customFields, GAStatus mergeFields) { - gameanalytics::GameAnalytics::addProgressionEvent((gameanalytics::EGAProgressionStatus)progressionStatus, score, progression01, progression02, progression03, fields, mergeFields); + gameanalytics::GameAnalytics::addProgressionEvent((gameanalytics::EGAProgressionStatus)progressionStatus, score, progression01, progression02, progression03, customFields, mergeFields); } -void gameAnalytics_addDesignEvent(const char *eventId, const char *fields, GAStatus mergeFields) +GA_API void gameAnalytics_addDesignEvent(const char *eventId, const char *customFields, GAStatus mergeFields) { - gameanalytics::GameAnalytics::addDesignEvent(eventId, fields, (bool)mergeFields); + gameanalytics::GameAnalytics::addDesignEvent(eventId, customFields, (bool)mergeFields); } -void gameAnalytics_addDesignEventWithValue(const char *eventId, double value, const char *fields, GAStatus mergeFields) +GA_API void gameAnalytics_addDesignEventWithValue(const char *eventId, double value, const char *customFields, GAStatus mergeFields) { - gameanalytics::GameAnalytics::addDesignEvent(eventId, value, fields, (bool)mergeFields); + gameanalytics::GameAnalytics::addDesignEvent(eventId, value, customFields, (bool)mergeFields); } -void gameAnalytics_addErrorEvent(int severity, const char *message, const char *fields, GAStatus mergeFields) +GA_API void gameAnalytics_addErrorEvent(GAErrorSeverity severity, const char *message, const char *customFields, GAStatus mergeFields) { - gameanalytics::GameAnalytics::addErrorEvent((gameanalytics::EGAErrorSeverity)severity, message, fields, (bool)mergeFields); + gameanalytics::GameAnalytics::addErrorEvent((gameanalytics::EGAErrorSeverity)severity, message, customFields, (bool)mergeFields); } // set calls can be changed at any time (pre- and post-initialize) // some calls only work after a configure is called (setCustomDimension) -void gameAnalytics_setEnabledInfoLog(GAStatus flag) +GA_API void gameAnalytics_setEnabledInfoLog(GAStatus flag) { gameanalytics::GameAnalytics::setEnabledInfoLog(flag); } -void gameAnalytics_setEnabledVerboseLog(GAStatus flag) +GA_API void gameAnalytics_setEnabledVerboseLog(GAStatus flag) { gameanalytics::GameAnalytics::setEnabledVerboseLog(flag); } -void gameAnalytics_setEnabledManualSessionHandling(GAStatus flag) +GA_API void gameAnalytics_setEnabledManualSessionHandling(GAStatus flag) { gameanalytics::GameAnalytics::setEnabledManualSessionHandling(flag); } -void gameAnalytics_setEnabledErrorReporting(GAStatus flag) +GA_API void gameAnalytics_setEnabledErrorReporting(GAStatus flag) { gameanalytics::GameAnalytics::setEnabledErrorReporting(flag); } -void gameAnalytics_setEnabledEventSubmission(GAStatus flag) +GA_API void gameAnalytics_setEnabledEventSubmission(GAStatus flag) { gameanalytics::GameAnalytics::setEnabledEventSubmission(flag); } -void gameAnalytics_setCustomDimension01(const char *dimension01) +GA_API void gameAnalytics_setCustomDimension01(const char *dimension01) { gameanalytics::GameAnalytics::setCustomDimension01(dimension01); } -void gameAnalytics_setCustomDimension02(const char *dimension02) +GA_API void gameAnalytics_setCustomDimension02(const char *dimension02) { gameanalytics::GameAnalytics::setCustomDimension02(dimension02); } -void gameAnalytics_setCustomDimension03(const char *dimension03) +GA_API void gameAnalytics_setCustomDimension03(const char *dimension03) { gameanalytics::GameAnalytics::setCustomDimension03(dimension03); } -void gameAnalytics_setGlobalCustomEventFields(const char *customFields) +GA_API void gameAnalytics_setGlobalCustomEventFields(const char *customFields) { gameanalytics::GameAnalytics::setGlobalCustomEventFields(customFields); } -void gameAnalytics_startSession() +GA_API void gameAnalytics_startSession() { gameanalytics::GameAnalytics::startSession(); } -void gameAnalytics_endSession() +GA_API void gameAnalytics_endSession() { gameanalytics::GameAnalytics::endSession(); } // game state changes // will affect how session is started / ended -void gameAnalytics_onResume() +GA_API void gameAnalytics_onResume() { gameanalytics::GameAnalytics::onResume(); } -void gameAnalytics_onSuspend() +GA_API void gameAnalytics_onSuspend() { gameanalytics::GameAnalytics::onSuspend(); } -void gameAnalytics_onQuit() +GA_API void gameAnalytics_onQuit() { gameanalytics::GameAnalytics::onQuit(); } -const char* gameAnalytics_getUserId() +GA_API const char* gameAnalytics_getUserId() { std::string returnValue = gameanalytics::GameAnalytics::getUserId(); return gameAnalytics_allocString(returnValue); } -const char* gameAnalytics_getExternalUserId() +GA_API const char* gameAnalytics_getExternalUserId() { std::string returnValue = gameanalytics::GameAnalytics::getExternalUserId(); return gameAnalytics_allocString(returnValue); } -const char* gameAnalytics_getRemoteConfigsValueAsString(const char *key) +GA_API const char* gameAnalytics_getRemoteConfigsValueAsString(const char *key) { std::string returnValue = gameanalytics::GameAnalytics::getRemoteConfigsValueAsString(key); return gameAnalytics_allocString(returnValue); } -const char* gameAnalytics_getRemoteConfigsValueAsStringWithDefaultValue(const char *key, const char *defaultValue) +GA_API const char* gameAnalytics_getRemoteConfigsValueAsStringWithDefaultValue(const char *key, const char *defaultValue) { std::string returnValue = gameanalytics::GameAnalytics::getRemoteConfigsValueAsString(key, defaultValue); return gameAnalytics_allocString(returnValue); } -GAStatus gameAnalytics_isRemoteConfigsReady() +GA_API GAStatus gameAnalytics_isRemoteConfigsReady() { return gameanalytics::GameAnalytics::isRemoteConfigsReady() ? EGAEnabled : EGADisabled; } -const char* gameAnalytics_getRemoteConfigsContentAsString() +GA_API const char* gameAnalytics_getRemoteConfigsContentAsString() { std::string returnValue = gameanalytics::GameAnalytics::getRemoteConfigsContentAsString(); return gameAnalytics_allocString(returnValue); } -const char* gameAnalytics_getRemoteConfigsValueAsJson(const char* key) +GA_API const char* gameAnalytics_getRemoteConfigsValueAsJson(const char* key) { std::string returnValue = gameanalytics::GameAnalytics::getRemoteConfigsValueAsJson(key); return gameAnalytics_allocString(returnValue); } -const char* gameAnalytics_getABTestingId() +GA_API const char* gameAnalytics_getABTestingId() { std::string returnValue = gameanalytics::GameAnalytics::getABTestingId(); return gameAnalytics_allocString(returnValue); } -const char* gameAnalytics_getABTestingVariantId(char* out) +GA_API const char* gameAnalytics_getABTestingVariantId() { std::string returnValue = gameanalytics::GameAnalytics::getABTestingVariantId(); return gameAnalytics_allocString(returnValue); } -long long gameAnalytics_getElapsedSessionTime() +GA_API long long gameAnalytics_getElapsedSessionTime() { return gameanalytics::GameAnalytics::getElapsedSessionTime(); } -long long gameAnalytics_getElapsedTimeFromAllSessions() +GA_API long long gameAnalytics_getElapsedTimeFromAllSessions() { return gameanalytics::GameAnalytics::getElapsedTimeFromAllSessions(); } -long long gameAnalytics_getElapsedTimeForPreviousSession() +GA_API long long gameAnalytics_getElapsedTimeForPreviousSession() { return gameanalytics::GameAnalytics::getElapsedTimeForPreviousSession(); } -void gameAnalytics_enableSDKInitEvent(GAStatus status) +GA_API void gameAnalytics_enableSDKInitEvent(GAStatus status) { return gameanalytics::GameAnalytics::enableSDKInitEvent(status); } -void gameAnalytics_enableMemoryHistogram(GAStatus status) +GA_API void gameAnalytics_enableMemoryHistogram(GAStatus status) { return gameanalytics::GameAnalytics::enableMemoryHistogram(status); } -void gameAnalytics_enableFPSHistogram(GAFpsTracker tracker, GAStatus status) +GA_API void gameAnalytics_enableFPSHistogram(GAFpsTracker tracker, GAStatus status) { gameanalytics::FPSTracker fpsTracker = [=]() -> float @@ -314,7 +314,7 @@ void gameAnalytics_enableFPSHistogram(GAFpsTracker tracker, GAStatus status) return gameanalytics::GameAnalytics::enableFPSHistogram(fpsTracker, status); } -void gameAnalytics_enableHardwareTracking(GAStatus status) +GA_API void gameAnalytics_enableHardwareTracking(GAStatus status) { return gameanalytics::GameAnalytics::enableHardwareTracking(status); } From 0d941cf4652a214550fc394a57fc2c5e44b39a55 Mon Sep 17 00:00:00 2001 From: Andrei Dabija Date: Thu, 19 Feb 2026 17:33:50 +0200 Subject: [PATCH 07/11] check for null for all optional strings that have default values --- source/gameanalytics/GameAnalyticsExtern.cpp | 51 +++++++++++--------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/source/gameanalytics/GameAnalyticsExtern.cpp b/source/gameanalytics/GameAnalyticsExtern.cpp index bcadf788..889b2e3a 100644 --- a/source/gameanalytics/GameAnalyticsExtern.cpp +++ b/source/gameanalytics/GameAnalyticsExtern.cpp @@ -34,6 +34,11 @@ const char* gameAnalytics_allocString(std::string const& s) return strndup(s.c_str(), s.size()); } +static inline const char* safeString(const char* str) +{ + return str ? str : ""; +} + GA_API void gameAnalytics_configureAvailableCustomDimensions01(const char **customDimensions, int size) { gameanalytics::StringVector values = makeStringVector(customDimensions, size); @@ -66,22 +71,22 @@ GA_API void gameAnalytics_configureAvailableResourceItemTypes(const char** resou GA_API void gameAnalytics_configureBuild(const char *build) { - gameanalytics::GameAnalytics::configureBuild(build); + gameanalytics::GameAnalytics::configureBuild(safeString(build)); } GA_API void gameAnalytics_configureWritablePath(const char *writablePath) { - gameanalytics::GameAnalytics::configureWritablePath(writablePath); + gameanalytics::GameAnalytics::configureWritablePath(safeString(writablePath)); } GA_API void gameAnalytics_configureDeviceModel(const char *deviceModel) { - gameanalytics::GameAnalytics::configureDeviceModel(deviceModel); + gameanalytics::GameAnalytics::configureDeviceModel(safeString(deviceModel)); } GA_API void gameAnalytics_configureDeviceManufacturer(const char *deviceManufacturer) { - gameanalytics::GameAnalytics::configureDeviceManufacturer(deviceManufacturer); + gameanalytics::GameAnalytics::configureDeviceManufacturer(safeString(deviceManufacturer)); } // the version of SDK code used in an engine. Used for sdk_version field. @@ -89,65 +94,65 @@ GA_API void gameAnalytics_configureDeviceManufacturer(const char *deviceManufact // example "unity 4.6.9" GA_API void gameAnalytics_configureSdkGameEngineVersion(const char *sdkGameEngineVersion) { - gameanalytics::GameAnalytics::configureSdkGameEngineVersion(sdkGameEngineVersion); + gameanalytics::GameAnalytics::configureSdkGameEngineVersion(safeString(sdkGameEngineVersion)); } // the version of the game engine (if used and version is available) GA_API void gameAnalytics_configureGameEngineVersion(const char *engineVersion) { - gameanalytics::GameAnalytics::configureGameEngineVersion(engineVersion); + gameanalytics::GameAnalytics::configureGameEngineVersion(safeString(engineVersion)); } GA_API void gameAnalytics_configureUserId(const char *uId) { - gameanalytics::GameAnalytics::configureUserId(uId); + gameanalytics::GameAnalytics::configureUserId(safeString(uId)); } GA_API void gameAnalytics_configureExternalUserId(const char* extId) { - gameanalytics::GameAnalytics::configureExternalUserId(extId); + gameanalytics::GameAnalytics::configureExternalUserId(safeString(extId)); } // initialize - starting SDK (need configuration before starting) GA_API void gameAnalytics_initialize(const char *gameKey, const char *gameSecret) { - gameanalytics::GameAnalytics::initialize(gameKey, gameSecret); + gameanalytics::GameAnalytics::initialize(safeString(gameKey), safeString(gameSecret)); } // add events GA_API void gameAnalytics_addBusinessEvent(const char *currency, double amount, const char *itemType, const char *itemId, const char *cartType, const char *customFields, GAStatus mergeFields) { - gameanalytics::GameAnalytics::addBusinessEvent(currency, (int)amount, itemType, itemId, cartType, customFields, mergeFields); + gameanalytics::GameAnalytics::addBusinessEvent(safeString(currency), (int)amount, safeString(itemType), safeString(itemId), safeString(cartType), safeString(customFields), mergeFields); } GA_API void gameAnalytics_addResourceEvent(GAResourceFlowType flowType, const char *currency, double amount, const char *itemType, const char *itemId, const char *customFields, GAStatus mergeFields) { - gameanalytics::GameAnalytics::addResourceEvent((gameanalytics::EGAResourceFlowType)flowType, currency, (float)amount, itemType, itemId, customFields, mergeFields); + gameanalytics::GameAnalytics::addResourceEvent((gameanalytics::EGAResourceFlowType)flowType, safeString(currency), (float)amount, safeString(itemType), safeString(itemId), safeString(customFields), mergeFields); } GA_API void gameAnalytics_addProgressionEvent(GAProgressionStatus progressionStatus, const char *progression01, const char *progression02, const char *progression03, const char *customFields, GAStatus mergeFields) { - gameanalytics::GameAnalytics::addProgressionEvent((gameanalytics::EGAProgressionStatus)progressionStatus, progression01, progression02, progression03, customFields, mergeFields); + gameanalytics::GameAnalytics::addProgressionEvent((gameanalytics::EGAProgressionStatus)progressionStatus, safeString(progression01), safeString(progression02), safeString(progression03), safeString(customFields), mergeFields); } GA_API void gameAnalytics_addProgressionEventWithScore(GAProgressionStatus progressionStatus, const char *progression01, const char *progression02, const char *progression03, int score, const char *customFields, GAStatus mergeFields) { - gameanalytics::GameAnalytics::addProgressionEvent((gameanalytics::EGAProgressionStatus)progressionStatus, score, progression01, progression02, progression03, customFields, mergeFields); + gameanalytics::GameAnalytics::addProgressionEvent((gameanalytics::EGAProgressionStatus)progressionStatus, score, safeString(progression01), safeString(progression02), safeString(progression03), safeString(customFields), mergeFields); } GA_API void gameAnalytics_addDesignEvent(const char *eventId, const char *customFields, GAStatus mergeFields) { - gameanalytics::GameAnalytics::addDesignEvent(eventId, customFields, (bool)mergeFields); + gameanalytics::GameAnalytics::addDesignEvent(safeString(eventId), safeString(customFields), (bool)mergeFields); } GA_API void gameAnalytics_addDesignEventWithValue(const char *eventId, double value, const char *customFields, GAStatus mergeFields) { - gameanalytics::GameAnalytics::addDesignEvent(eventId, value, customFields, (bool)mergeFields); + gameanalytics::GameAnalytics::addDesignEvent(safeString(eventId), value, safeString(customFields), (bool)mergeFields); } GA_API void gameAnalytics_addErrorEvent(GAErrorSeverity severity, const char *message, const char *customFields, GAStatus mergeFields) { - gameanalytics::GameAnalytics::addErrorEvent((gameanalytics::EGAErrorSeverity)severity, message, customFields, (bool)mergeFields); + gameanalytics::GameAnalytics::addErrorEvent((gameanalytics::EGAErrorSeverity)severity, safeString(message), safeString(customFields), (bool)mergeFields); } // set calls can be changed at any time (pre- and post-initialize) @@ -180,22 +185,22 @@ GA_API void gameAnalytics_setEnabledEventSubmission(GAStatus flag) GA_API void gameAnalytics_setCustomDimension01(const char *dimension01) { - gameanalytics::GameAnalytics::setCustomDimension01(dimension01); + gameanalytics::GameAnalytics::setCustomDimension01(safeString(dimension01)); } GA_API void gameAnalytics_setCustomDimension02(const char *dimension02) { - gameanalytics::GameAnalytics::setCustomDimension02(dimension02); + gameanalytics::GameAnalytics::setCustomDimension02(safeString(dimension02)); } GA_API void gameAnalytics_setCustomDimension03(const char *dimension03) { - gameanalytics::GameAnalytics::setCustomDimension03(dimension03); + gameanalytics::GameAnalytics::setCustomDimension03(safeString(dimension03)); } GA_API void gameAnalytics_setGlobalCustomEventFields(const char *customFields) { - gameanalytics::GameAnalytics::setGlobalCustomEventFields(customFields); + gameanalytics::GameAnalytics::setGlobalCustomEventFields(safeString(customFields)); } GA_API void gameAnalytics_startSession() @@ -239,13 +244,13 @@ GA_API const char* gameAnalytics_getExternalUserId() GA_API const char* gameAnalytics_getRemoteConfigsValueAsString(const char *key) { - std::string returnValue = gameanalytics::GameAnalytics::getRemoteConfigsValueAsString(key); + std::string returnValue = gameanalytics::GameAnalytics::getRemoteConfigsValueAsString(safeString(key)); return gameAnalytics_allocString(returnValue); } GA_API const char* gameAnalytics_getRemoteConfigsValueAsStringWithDefaultValue(const char *key, const char *defaultValue) { - std::string returnValue = gameanalytics::GameAnalytics::getRemoteConfigsValueAsString(key, defaultValue); + std::string returnValue = gameanalytics::GameAnalytics::getRemoteConfigsValueAsString(safeString(key), safeString(defaultValue)); return gameAnalytics_allocString(returnValue); } @@ -262,7 +267,7 @@ GA_API const char* gameAnalytics_getRemoteConfigsContentAsString() GA_API const char* gameAnalytics_getRemoteConfigsValueAsJson(const char* key) { - std::string returnValue = gameanalytics::GameAnalytics::getRemoteConfigsValueAsJson(key); + std::string returnValue = gameanalytics::GameAnalytics::getRemoteConfigsValueAsJson(safeString(key)); return gameAnalytics_allocString(returnValue); } From e8db21c09a7a5db0bdde4fac1400a581b6560868 Mon Sep 17 00:00:00 2001 From: Andrei Dabija Date: Thu, 19 Feb 2026 17:41:57 +0200 Subject: [PATCH 08/11] Update GameAnalyticsExtern.cpp --- source/gameanalytics/GameAnalyticsExtern.cpp | 21 +++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/source/gameanalytics/GameAnalyticsExtern.cpp b/source/gameanalytics/GameAnalyticsExtern.cpp index 889b2e3a..fc810446 100644 --- a/source/gameanalytics/GameAnalyticsExtern.cpp +++ b/source/gameanalytics/GameAnalyticsExtern.cpp @@ -4,6 +4,8 @@ #include "GameAnalytics/GameAnalytics.h" #include "GAUtilities.h" +#include +#include gameanalytics::StringVector makeStringVector(const char** arr, int size) { @@ -29,9 +31,26 @@ GA_API void gameAnalytics_freeString(const char* ptr) std::free((void*)ptr); } +// Cross-platform string duplication function +static char* ga_strndup(const char* s, size_t n) +{ + if (!s) return nullptr; + + size_t len = std::strlen(s); + if (len > n) len = n; + + char* result = (char*)std::malloc(len + 1); + if (result) + { + std::memcpy(result, s, len); + result[len] = '\0'; + } + return result; +} + const char* gameAnalytics_allocString(std::string const& s) { - return strndup(s.c_str(), s.size()); + return ga_strndup(s.c_str(), s.size()); } static inline const char* safeString(const char* str) From a283133ebf0d7e28d7bff2eb03add596dc84ba30 Mon Sep 17 00:00:00 2001 From: Andrei Dabija Date: Thu, 19 Feb 2026 18:12:06 +0200 Subject: [PATCH 09/11] add shared lib sample --- CMakeLists.txt | 10 ++++-- sample_shared/CMakeLists.txt | 20 +++++++++++ sample_shared/Main.cpp | 68 ++++++++++++++++++++++++++++++++++++ setup.py | 2 +- 4 files changed, 96 insertions(+), 4 deletions(-) create mode 100644 sample_shared/CMakeLists.txt create mode 100644 sample_shared/Main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index c4072dc3..31dee2d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -206,9 +206,13 @@ elseif(LINUX) endif() -if(${GA_BUILD_SAMPLE}) - add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/sample") -endif() +if(${GA_BUILD_SAMPLE}) + if(${GA_SHARED_LIB}) + add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/sample_shared") + else() + add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/sample") + endif() +endif() add_library(GameAnalytics ${LIB_TYPE} ${CPP_SOURCES}) target_link_libraries(GameAnalytics PRIVATE ${LIBS} PUBLIC ${PUBLIC_LIBS}) diff --git a/sample_shared/CMakeLists.txt b/sample_shared/CMakeLists.txt new file mode 100644 index 00000000..ed1e7821 --- /dev/null +++ b/sample_shared/CMakeLists.txt @@ -0,0 +1,20 @@ +# set directories +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/Debug") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/Release") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/Debug") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/Release") + +set(CMAKE_CXX_STANDARD 17) + +if(LINUX) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17") + + if(CLANG) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") + endif() +endif() + +set(SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/Main.cpp") + +add_executable(GASampleShared ${SOURCES}) +target_link_libraries(GASampleShared GameAnalytics) diff --git a/sample_shared/Main.cpp b/sample_shared/Main.cpp new file mode 100644 index 00000000..853cac95 --- /dev/null +++ b/sample_shared/Main.cpp @@ -0,0 +1,68 @@ +#include +#include +#include + +#include "GameAnalyticsExtern.h" + +constexpr const char* GAME_KEY = "INSERT_GAME_KEY"; +constexpr const char* SECRET_KEY = "INSERT_SECRET_KEY"; + +void testCrash() +{ + int* i = nullptr; + *i = 10; +} + +int main(int argc, char** argv) +{ + std::cout << "start\n"; + + gameAnalytics_setEnabledVerboseLog(EGAEnabled); + gameAnalytics_setEnabledInfoLog(EGAEnabled); + + gameAnalytics_setGlobalCustomEventFields("{\"my_field\": 1000}"); + + gameAnalytics_configureExternalUserId("user_supernew657566453442"); + gameAnalytics_setEnabledErrorReporting(EGAEnabled); + gameAnalytics_setEnabledManualSessionHandling(EGAEnabled); + + gameAnalytics_configureBuild("1.0.0"); + + const char* resourceCurrencies[] = {"diamonds"}; + gameAnalytics_configureAvailableResourceCurrencies(resourceCurrencies, 1); + + const char* resourceItemTypes[] = {"diamonds_pack_10000"}; + gameAnalytics_configureAvailableResourceItemTypes(resourceItemTypes, 1); + + const char* customDimensions01[] = {"test"}; + gameAnalytics_configureAvailableCustomDimensions01(customDimensions01, 1); + + gameAnalytics_enableSDKInitEvent(EGAEnabled); + gameAnalytics_enableMemoryHistogram(EGAEnabled); + gameAnalytics_enableHardwareTracking(EGAEnabled); + gameAnalytics_enableFPSHistogram([]() -> float { return 60.f; }, EGAEnabled); + + gameAnalytics_setCustomDimension01("test"); + + using namespace std::chrono_literals; + + gameAnalytics_initialize(GAME_KEY, SECRET_KEY); + + gameAnalytics_startSession(); + gameAnalytics_addDesignEventWithValue("test_event", 10.0, "", EGADisabled); + gameAnalytics_addBusinessEvent("EUR", 100, "diamonds", "diamond_pack_10000", "dungeon_shop", "", EGADisabled); + gameAnalytics_addErrorEvent(EGACritical, "failed to load level", "", EGADisabled); + gameAnalytics_addResourceEvent(EGASource, "diamonds", 100, "diamonds", "big_pack_01", "", EGADisabled); + gameAnalytics_addProgressionEventWithScore(EGAComplete, "volcano", "dungeon", "dragon", 100, "", EGADisabled); + + std::this_thread::sleep_for(5000ms); + //testCrash(); + + //gameAnalytics_endSession(); + + std::cout << "test\n"; + + //std::cin.get(); + + return 0; +} diff --git a/setup.py b/setup.py index 8ed763b5..882204e4 100644 --- a/setup.py +++ b/setup.py @@ -71,7 +71,7 @@ def main(): # Configure for shared library build if args.shared: - cmake_command += ' -DGA_SHARED_LIB=ON -DGA_BUILD_SAMPLE=OFF' + cmake_command += ' -DGA_SHARED_LIB=ON' if args.platform == 'osx': cmake_command += ' -G "Xcode"' From 27693837c39f70b8f6d7fc703d9ff74bf2bc98be Mon Sep 17 00:00:00 2001 From: Andrei Dabija Date: Thu, 19 Feb 2026 18:17:38 +0200 Subject: [PATCH 10/11] disable tests for shared lib builds --- CMakeLists.txt | 75 ++++++++++++++++++++++++++++---------------------- setup.py | 4 +++ 2 files changed, 46 insertions(+), 33 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 31dee2d5..6d74ad49 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -206,13 +206,13 @@ elseif(LINUX) endif() -if(${GA_BUILD_SAMPLE}) - if(${GA_SHARED_LIB}) - add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/sample_shared") - else() - add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/sample") - endif() -endif() +if(${GA_BUILD_SAMPLE}) + if(${GA_SHARED_LIB}) + add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/sample_shared") + else() + add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/sample") + endif() +endif() add_library(GameAnalytics ${LIB_TYPE} ${CPP_SOURCES}) target_link_libraries(GameAnalytics PRIVATE ${LIBS} PUBLIC ${PUBLIC_LIBS}) @@ -221,43 +221,52 @@ message(STATUS "CMAKE_EXE_LINKER_FLAGS: ${CMAKE_EXE_LINKER_FLAGS}") message(STATUS "CMAKE_SHARED_LINKER_FLAGS: ${CMAKE_SHARED_LINKER_FLAGS}") # --------------------------- Google Test Setup --------------------------- # -# Set Project Name -set(UT_PROJECT_NAME "${PROJECT_NAME}UnitTests") +# Only build tests for static library builds +# Shared library builds don't export internal symbols needed by tests +if(NOT GA_SHARED_LIB) + message(STATUS "Building unit tests (tests are only available for static library builds)") + + # Set Project Name + set(UT_PROJECT_NAME "${PROJECT_NAME}UnitTests") -# Add Google Test -set(GTEST_DIR "${EXTERNALS_DIR}/googletest") -add_subdirectory(${GTEST_DIR} ${PROJECT_SOURCE_DIR}/gtest_build) + # Add Google Test + set(GTEST_DIR "${EXTERNALS_DIR}/googletest") + add_subdirectory(${GTEST_DIR} ${PROJECT_SOURCE_DIR}/gtest_build) -# Add tests -enable_testing() + # Add tests + enable_testing() -######################################## -# Test files -######################################## + ######################################## + # Test files + ######################################## -file(GLOB_RECURSE TEST_SRC_FILES "${PROJECT_SOURCE_DIR}/test/*.cpp") + file(GLOB_RECURSE TEST_SRC_FILES "${PROJECT_SOURCE_DIR}/test/*.cpp") -######################################## -# Unit Tests -####################################### -add_executable(${UT_PROJECT_NAME} ${TEST_SRC_FILES}) + ######################################## + # Unit Tests + ####################################### + add_executable(${UT_PROJECT_NAME} ${TEST_SRC_FILES}) -######################################## -# Standard linking to gtest and gmock components -######################################## -target_link_libraries(${UT_PROJECT_NAME} gtest gtest_main gmock_main) + ######################################## + # Standard linking to gtest and gmock components + ######################################## + target_link_libraries(${UT_PROJECT_NAME} gtest gtest_main gmock_main) -######################################## -# Linking to GA SDK -######################################## -target_link_libraries(${UT_PROJECT_NAME} ${PROJECT_NAME}) + ######################################## + # Linking to GA SDK + ######################################## + target_link_libraries(${UT_PROJECT_NAME} ${PROJECT_NAME}) -######################################## -add_test(NAME ${UT_PROJECT_NAME} COMMAND GameAnalyticsUnitTests) + ######################################## + add_test(NAME ${UT_PROJECT_NAME} COMMAND GameAnalyticsUnitTests) +else() + message(STATUS "Skipping unit tests (not available for shared library builds)") +endif() # --------------------------- Code Coverage Setup --------------------------- # -if (ENABLE_COVERAGE) +# Coverage requires tests, which are only available for static library builds +if (ENABLE_COVERAGE AND NOT GA_SHARED_LIB) find_program(GCOV_PATH gcov) if (NOT GCOV_PATH) message(WARNING "program gcov not found") diff --git a/setup.py b/setup.py index 882204e4..ab8e6616 100644 --- a/setup.py +++ b/setup.py @@ -42,6 +42,10 @@ def main(): if args.compiler and not args.platform.startswith('linux'): parser.error('--compiler can only be used with Linux platforms') + # Validate coverage is not used with shared library + if args.coverage and args.shared: + parser.error('--coverage cannot be used with --shared (coverage requires tests which need static library)') + # Get compiler configuration for this platform (single compiler, like cmake.yml) compiler_config = get_compiler_for_platform(args.platform, args.compiler) c_compiler = compiler_config.get('c', '') From 803333d34836f9a6b325ccc7ebd43672ec4631f5 Mon Sep 17 00:00:00 2001 From: Andrei Dabija Date: Thu, 19 Feb 2026 18:33:21 +0200 Subject: [PATCH 11/11] Update setup.py --- setup.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index ab8e6616..90f71f23 100644 --- a/setup.py +++ b/setup.py @@ -70,15 +70,17 @@ def main(): if cxx_compiler: cmake_command += f' -DCMAKE_CXX_COMPILER={cxx_compiler}' - # Add build type for single-config generators (Makefile, Ninja) - cmake_command += f' -DCMAKE_BUILD_TYPE={args.cfg}' - # Configure for shared library build if args.shared: cmake_command += ' -DGA_SHARED_LIB=ON' if args.platform == 'osx': cmake_command += ' -G "Xcode"' + + # Add build type for single-config generators (Linux uses Makefile/Ninja) + # Multi-config generators (Xcode, Visual Studio) use --config at build time instead + if args.platform.startswith('linux'): + cmake_command += f' -DCMAKE_BUILD_TYPE={args.cfg}' if args.platform: cmake_command += f' -DPLATFORM:STRING={args.platform}' if args.coverage: