Skip to content

user_impl_workflow

Francisco Dias edited this page Mar 31, 2026 · 4 revisions

Implementation Workflow 🛠️

This page explains where you implement your extension, how the generated CMake project is intended to be used, and how the workflow differs by target/platform.

extgen generates a multi-platform CMake project, but the extension logic itself is always your responsibility and lives inside the src/ folder (plus target-specific glue for some platforms).

Key idea: extgen generates the bridge + build system, you implement the feature code.


📁 Project Layout (What You Own vs What extgen Owns)

After running extgen, your folder usually looks like:

  • code_gen/ → generated bridge code (do not hand-edit)

  • cmake/ → helper scripts (packaging, xcframework, etc.)

  • src/ ✅ → your implementation lives here

    • src/CMakeLists.txt ✅ → you control which files build per platform
    • src/native/, src/ios/, src/tvos/, etc. ✅ → your platform implementations
  • third_party/ → optional integration point for SDKs / libraries

The workflow is:

  1. Edit GMIDL → defines the public API
  2. Run extgen → regenerates bindings + project
  3. Implement platform behavior in src/
  4. Build via CMake presets (or via IDE toolchains)

🧠 Platform Detection Helpers (CMake)

The root CMakeLists.txt provides platform classification booleans like:

set(EXTGEN_IS_IOS FALSE)
set(EXTGEN_IS_TVOS FALSE)
set(EXTGEN_IS_APPLE_MOBILE FALSE)
set(EXTGEN_IS_PS4 FALSE)
set(EXTGEN_IS_PS5 FALSE)
set(EXTGEN_IS_SWITCH FALSE)

if(EXTGEN_PLATFORM_SWITCH)
  set(EXTGEN_IS_SWITCH TRUE)
endif()

if(APPLE)
  if(CMAKE_OSX_SYSROOT MATCHES "iphone")
    set(EXTGEN_IS_IOS TRUE)
    set(EXTGEN_IS_APPLE_MOBILE TRUE)
  elseif(CMAKE_OSX_SYSROOT MATCHES "appletv")
    set(EXTGEN_IS_TVOS TRUE)
    set(EXTGEN_IS_APPLE_MOBILE TRUE)
  endif()
endif()

if(CMAKE_SYSTEM_NAME STREQUAL "ORBIS")
  set(EXTGEN_IS_PS4 TRUE)
elseif(CMAKE_SYSTEM_NAME STREQUAL "Prospero")
  set(EXTGEN_IS_PS5 TRUE)
endif()

You can use these in src/CMakeLists.txt to decide what to compile, which defines to add, and which files belong to each platform.

Additionally, the following preprocessor defines are automatically set:

  • OS_SWITCH - Nintendo Switch builds
  • OS_WINDOWS - Windows builds
  • OS_ANDROID - Android builds
  • OS_IOS / OS_TVOS - Apple mobile platforms

These defines control platform-specific behavior in generated code (export macros, platform APIs, etc.).


🍏 iOS / tvOS XCFramework Headers (Required)

When building an XCFramework, we must tell the packaging step which public headers to include.

In your src/CMakeLists.txt, extgen places something like:

set(EXT_PUBLIC_HEADERS_IOS
  "${CMAKE_SOURCE_DIR}/src/ios/${PROJECT_NAME}_ios.h;${CMAKE_SOURCE_DIR}/code_gen/ios/${PROJECT_NAME}Internal_ios.h"
  CACHE STRING "Semicolon-separated list of public headers to embed in the iOS xcframework")

set(EXT_PUBLIC_HEADERS_TVOS
  "${CMAKE_SOURCE_DIR}/src/tvos/${PROJECT_NAME}_tvos.h;${CMAKE_SOURCE_DIR}/code_gen/tvos/${PROJECT_NAME}Internal_tvos.h"
  CACHE STRING "Semicolon-separated list of public headers to embed in the tvOS xcframework")

You must keep this list correct if you change your src/ios or src/tvos layout.

Why this matters: xcodebuild -create-xcframework requires a headers folder. extgen collects these files into that folder during packaging.


✅ Workflow by Target


🤖 Android (Java & Kotlin modes)

What gets generated

  • No C/C++ library build
  • Pure Java/Kotlin glue code emitted into the target output folder (default: ../AndroidSource/...)

What you implement

You implement the platform behavior in the generated Java/Kotlin layer.

A module-named file is generated, for example:

  • ../AndroidSource/Java/<ModuleName>.java

And an internal interface is generated, for example:

  • ../AndroidSource/Java/code_gen/<ModuleName>Interface.java

✅ Your implementation should implement that interface.

Why this is good

  • The interface forces your Java/Kotlin implementation to stay in sync with GMIDL
  • When you regenerate, compile errors immediately point to missing functions

📝 Tip: Treat the interface as the “contract.” If GMIDL changes, you update your implementation accordingly.


🖥️ Native Targets (Desktop + Consoles + JNI + Apple Native)

This applies to:

  • ✅ Windows
  • ✅ macOS
  • ✅ Linux
  • ✅ Xbox
  • ✅ PS4 / PS5
  • ✅ Nintendo Switch (see dedicated section below for setup requirements)
  • ✅ Android (JNI mode)
  • ✅ iOS/tvOS (Native mode)

Where you implement

In src/, typically organized like:

  • src/native/ (shared native implementation)
  • src/windows/, src/macos/, etc. (optional splits)
  • src/ios/, src/tvos/ (platform wrappers if needed)

What you change

In src/CMakeLists.txt, you choose which sources compile per platform.

Example pattern:

if(WIN32)
  file(GLOB TARGET_SRCS CONFIGURE_DEPENDS "${SRC_DIR}/native/*.cpp" "${SRC_DIR}/windows/*.cpp")
elseif(APPLE)
  if(EXTGEN_IS_IOS)
    file(GLOB TARGET_SRCS CONFIGURE_DEPENDS "${SRC_DIR}/native/*.cpp" "${SRC_DIR}/ios/*.mm")
  elseif(EXTGEN_IS_TVOS)
    file(GLOB TARGET_SRCS CONFIGURE_DEPENDS "${SRC_DIR}/native/*.cpp" "${SRC_DIR}/tvos/*.mm")
  else()
    file(GLOB TARGET_SRCS CONFIGURE_DEPENDS "${SRC_DIR}/native/*.cpp" "${SRC_DIR}/macos/*.mm")
  endif()
elseif(ANDROID)
  file(GLOB TARGET_SRCS CONFIGURE_DEPENDS "${SRC_DIR}/native/*.cpp")
else()
  file(GLOB TARGET_SRCS CONFIGURE_DEPENDS "${SRC_DIR}/native/*.cpp")
endif()

target_sources(${PROJECT_NAME} PRIVATE ${TARGET_SRCS})

Build output behavior

  • Most targets build a shared library
  • iOS/tvOS build a static slice that is packaged into an XCFramework

🧩 Tip: If you support many platforms, use one “core” folder (like src/native/) and keep platform folders thin.


🍎 iOS / tvOS (ObjC, Swift, Native Modes)

iOS and tvOS development with extgen is designed to:

  • ✅ Keep all implementation inside your repository (src/)
  • ✅ Allow full Xcode debugging & real-device testing
  • ✅ Avoid manual project injection
  • ✅ Be repeatable and automation-friendly

The integration into the GameMaker-generated Xcode project is handled automatically via a dedicated CMake build target.


✅ Recommended Workflow (macOS)

1) Run extgen

Generate bindings and project files from GMIDL:

extgen --config config.json

2) Configure CMake

Run configure (via preset or manually). This prepares the extension project and build targets.

You do not need to manually open or inject anything into Xcode.


3) Export the GameMaker iOS/tvOS Project (Suppress Run)

From GameMaker:

  • Build for iOS/tvOS
  • Enable Suppress Run

This generates the Xcode project for the game.


4) Configure the GameMaker Xcode Project Path

In your CMake configuration (or preset), set:

# =============================================================================
# GameMaker iOS integration (optional)
# =============================================================================
set(EXT_GM_XCODEPROJ_OVERRIDE
    "~/GameMakerStudio2/GM_IOS/YourGame/YourGame/YourGame.xcodeproj"
    CACHE FILEPATH "" FORCE)

set(EXT_GM_APP_TARGET_OVERRIDE "" CACHE STRING "" FORCE)

What These Variables Do

Variable Purpose
EXT_GM_XCODEPROJ_OVERRIDE Path to the GameMaker-generated .xcodeproj
EXT_GM_APP_TARGET_OVERRIDE Optional override for the app target name

Because EXT_GM_XCODEPROJ_OVERRIDE is a FILEPATH, CMake automatically:

  • Expands ~
  • Normalizes the path
  • Validates it properly in GUIs and presets

5) Run the Integration Target

Instead of manually injecting the extension project into Xcode, run:

cmake --build <build-folder> --target integrate_gamemaker

This target automatically:

  • Adds the extension project to the GameMaker Xcode project
  • Creates required project references
  • Adds the static library dependency
  • Links it into the correct GameMaker target
  • Safely handles repeated runs (idempotent)

No Xcode UI steps required.


6) Open the GameMaker Xcode Project

Now open the GameMaker-generated .xcodeproj or workspace.

Your extension is already integrated.

You can:

  • Build for device or simulator
  • Debug on real hardware
  • Use full Xcode tooling
  • Edit extension source directly from the injected reference

🧠 How Source Editing Works

The integration adds a project reference, not copied files.

That means:

  • Editing code inside Xcode edits your real extension source
  • No duplicated files
  • No manual syncing required
  • Clean Git history

Switching between device/simulator is simply a configuration change.


🔁 Typical Iteration Cycle

  1. Edit GMIDL

  2. Run extgen

  3. Implement logic in src/

  4. Export from GameMaker (Suppress Run)

  5. Run:

    cmake --build <build-folder> --target integrate_gamemaker
  6. Open Xcode and build/debug normally

If you re-export from GameMaker, simply re-run the integration target.


📦 Final Packaging (XCFramework)

When ready for distribution:

cmake --build <build-folder> --target package_ios_xcframework
cmake --build <build-folder> --target package_tvos_xcframework

This produces:

  • <ExtensionName>.xcframework
  • A zipped distribution artifact (if configured)

🔥 Why This Workflow Is Preferred

  • No manual Xcode project editing
  • Deterministic and repeatable
  • CI-friendly
  • Safe to re-run after every GameMaker export
  • Single source of truth for extension code
  • Works cleanly for teams

🎮 Nintendo Switch

Nintendo Switch development with extgen is designed around NDA compliance and user-specific SDK configuration.

The extension project is structured to:

  • Keep all SDK paths out of version control
  • Use MSBuild integration with Nintendo's official props files
  • Support out-of-the-box builds once configured
  • Generate all required Switch output files (.nro, .nrr, .nrs)

✅ Setup Workflow

1) Set Environment Variable

Set the NINTENDO_SDK_ROOT environment variable to your Nintendo SDK installation:

# Windows (PowerShell)
$env:NINTENDO_SDK_ROOT = "D:\Nintendo\NativeSDK20.5.17\NintendoSDK"

# Windows (System Environment Variables - recommended)
# Control Panel → System → Advanced → Environment Variables

2) Create CMakeUserPresets.json

After running extgen, create CMakeUserPresets.json from the template:

cp templates/CMakeUserPresets.json.template CMakeUserPresets.json

Edit the file to configure your SDK paths:

{
  "version": 6,
  "configurePresets": [
    {
      "name": "switch-paths",
      "hidden": true,
      "cacheVariables": {
        "CMAKE_C_COMPILER": {
          "type": "FILEPATH",
          "value": "$env{NINTENDO_SDK_ROOT}/Compilers/NintendoClang/bin/clang-cl.exe"
        },
        "CMAKE_CXX_COMPILER": {
          "type": "FILEPATH",
          "value": "$env{NINTENDO_SDK_ROOT}/Compilers/NintendoClang/bin/clang-cl.exe"
        },
        "NINTENDO_SDK_PROPS_PATH": "Build/VcProjectUtility/ImportNintendoSdk.props"
      }
    }
  ]
}

Note: This file is automatically gitignored and contains user-specific paths only.

3) Configure and Build

# Configure (Debug or Release)
cmake --preset switch-debug

# Build
cmake --build --preset switch-debug

Output files are generated in the build directory and automatically copied to the extension output folder:

  • ExtensionName.nro - Nintendo Relocatable Object (main executable)
  • ExtensionName.nrr - Nintendo Relocatable Resource
  • ExtensionName.nrs - Debug symbols

🧠 How It Works

MSBuild Integration

The CMake project uses Visual Studio's MSBuild system with Nintendo SDK integration:

  1. Directory.Build.props is generated from a template during CMake configure
  2. This props file imports Nintendo SDK configuration from your SDK installation
  3. MSBuild automatically discovers and applies Nintendo's toolchain settings
  4. The compiler (clang-cl) and all build tools are provided by the Nintendo SDK

NDA Compliance

The committed project contains:

  • Zero hardcoded Nintendo SDK paths
  • Zero Nintendo SDK file structure references
  • Only the requirement that users configure their own SDK

The user provides via CMakeUserPresets.json:

  • Compiler paths (clang-cl.exe location)
  • Props file path (relative to NINTENDO_SDK_ROOT)

Platform-Specific Code

Use the OS_SWITCH define for platform-specific behavior:

#ifdef OS_SWITCH
  // Nintendo Switch implementation
#elif defined(OS_WINDOWS)
  // Windows implementation
#endif

The generated export macros automatically use the correct syntax:

// In generated code - you don't write this
#ifdef OS_SWITCH
#define GMEXPORT extern "C" __attribute__((visibility("default")))
#elif defined(OS_WINDOWS)
#define GMEXPORT extern "C" __declspec(dllexport)
#endif

🔄 Typical Iteration Cycle

  1. Edit GMIDL
  2. Run extgen
  3. Implement platform behavior in src/
  4. Build: cmake --build --preset switch-debug
  5. Test the generated .nro file on devkit

The three output files (.nro, .nrr, .nrs) are automatically copied to your extension folder for packaging.


📋 Requirements Summary

Requirement Description
Nintendo SDK Official Nintendo SDK installed
NINTENDO_SDK_ROOT Environment variable pointing to SDK root
CMakeUserPresets.json User-specific configuration (gitignored)
Visual Studio Generator CMake must use Visual Studio generator (automatic)
Windows Nintendo Switch builds require Windows + VS tooling

✅ Summary Table

Target Type Where You Implement What You Build
Android (Java/Kotlin) Android output folder (../AndroidSource/...) No native lib
Desktop + Consoles + JNI src/ via CMakeLists Shared library
Nintendo Switch src/ + user-configured SDK paths .nro + .nrr + .nrs
iOS/tvOS Native mode src/ + XCFramework packaging XCFramework
iOS/tvOS ObjC/Swift modes src/ + Xcode dev workflow XCFramework (final)

🧠 Best Practices

  • ✅ Keep core logic platform-agnostic (src/native/)
  • ✅ Use platform folders only for wrappers/entry points
  • ✅ Keep src/CMakeLists.txt clean and explicit
  • ✅ Always maintain the XCFramework public header list for Apple targets
  • ✅ For Nintendo Switch: never commit SDK paths - always use CMakeUserPresets.json (gitignored)
  • ✅ Use preprocessor defines (OS_SWITCH, OS_WINDOWS, etc.) for platform-specific code
  • ✅ Regenerate often — treat “compile errors after regeneration” as your update checklist

Clone this wiki locally