diff --git a/.github/actions/prepare-testing/action.yml b/.github/actions/prepare-testing/action.yml index 96ae0b9fb..6ec1b6969 100644 --- a/.github/actions/prepare-testing/action.yml +++ b/.github/actions/prepare-testing/action.yml @@ -21,7 +21,7 @@ runs: git submodule update --init --depth 1 project/test/util/json_assert - name: Download artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: name: sentry-godot-gdextension path: project/ diff --git a/.github/workflows/build_gdextension.yml b/.github/workflows/build_gdextension.yml index 080667a91..b16775f8b 100644 --- a/.github/workflows/build_gdextension.yml +++ b/.github/workflows/build_gdextension.yml @@ -292,7 +292,7 @@ jobs: scons platform=${{matrix.platform}} target=${{matrix.target}} arch=${{matrix.arch}} ${{matrix.scons-flags}} - name: Upload artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: sentry.${{matrix.platform}}.${{matrix.target}}.${{matrix.arch}}${{ contains(matrix.scons-flags, 'threads=no') && '.nothreads' || '' }} path: | @@ -319,7 +319,7 @@ jobs: run: ./gradlew assemble - name: Upload artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: sentry.android.godot_plugin # NOTE: Include-exclude pattern forces archive to start with project/ as root directory. @@ -341,7 +341,7 @@ jobs: submodules: recursive - name: Download iOS artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: pattern: sentry.ios.* path: project/ @@ -366,7 +366,7 @@ jobs: rm -rf project/addons/sentry/bin/ios/temp/ - name: Upload artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: sentry.ios-framework # NOTE: Include-exclude pattern forces archive to start with project/ as root directory. @@ -386,7 +386,7 @@ jobs: needs: [build, android-plugin, ios-framework] steps: - name: Merge artifacts - uses: actions/upload-artifact/merge@v4 + uses: actions/upload-artifact/merge@v6 with: name: sentry-godot-gdextension pattern: sentry.* diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index e65f1a82b..3345e23bb 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -27,7 +27,7 @@ jobs: uses: actions/checkout@v4 - name: Download artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: name: sentry-godot-gdextension path: artifact/ @@ -108,7 +108,7 @@ jobs: zip -r ${GITHUB_WORKSPACE}/out/${archive_file} ./* - name: Upload artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: ${{github.sha}} path: out/* diff --git a/SConstruct b/SConstruct index 2a74004b1..0194bb6e9 100644 --- a/SConstruct +++ b/SConstruct @@ -81,6 +81,7 @@ arch = env["arch"] env.Tool("copy") env.Tool("separate_debug_symbols") env.Tool("plist") +env.Tool("link_bundle") # Restore original ARGUMENTS and add custom options to environment ARGUMENTS.clear() @@ -225,13 +226,14 @@ elif platform == "macos": # *** Build macOS shared library. lib_name = f"libsentry.{platform}.{build_type}{extra}" - lib_path = f"{out_dir}/{lib_name}.framework/{lib_name}" + lib_path = f"{out_dir}/{lib_name}.framework/Versions/A/{lib_name}" library = env.SharedLibrary(lib_path, source=sources) Default(library) # Create Info.plist - plist_path = f"{out_dir}/{lib_name}.framework/Resources/Info.plist" + res_path = f"{out_dir}/{lib_name}.framework/Versions/A/Resources" + plist_path = f"{res_path}/Info.plist" plist = env.FrameworkPlist(File(plist_path), File("SConstruct"), bundle_executable=lib_name, bundle_identifier=f"io.sentry.SentryForGodot.{build_type}", @@ -241,6 +243,11 @@ elif platform == "macos": Depends(plist, library) Default(plist) + # Framework + framework_done = env.Alias('framework_done', [plist, library]) + Default(framework_done) + env.LinkBundle(f"{out_dir}/{lib_name}.framework/", framework_done) + else: # *** Build shared library on other platforms. diff --git a/modules/sentry-cocoa.SConscript b/modules/sentry-cocoa.SConscript index 0d5195718..3526fcb6d 100644 --- a/modules/sentry-cocoa.SConscript +++ b/modules/sentry-cocoa.SConscript @@ -73,70 +73,6 @@ def detect_xcode(): Exit(1) -def flatten_framework(framework_path): - """Flatten macOS framework by removing symlinks and versioned structure""" - framework_path = Path(framework_path) - if not framework_path.exists(): - print(f"ERROR: Framework not found at {framework_path}") - Exit(1) - - versions_dir = framework_path / "Versions" - if not versions_dir.exists(): - # Framework doesn't need flattening. - return - - print(f"Flattening framework structure: {framework_path}") - - # 1. Remove all symlinks from the root of the framework - for item_path in framework_path.iterdir(): - if item_path.is_symlink(): - print(f" Removing symlink: {item_path.name}") - item_path.unlink() - - # 2. Move Versions/A/* to root of the framework - version_a_dir = versions_dir / "A" - if version_a_dir.exists(): - print(" Moving contents from Versions/A/ to root") - for item_path in version_a_dir.iterdir(): - dest_path = framework_path / item_path.name - - # Handle potential conflicts - if dest_path.exists(): - print(f" WARNING: {item_path.name} already exists at root, removing old version") - remove_if_exists(dest_path) - - shutil.move(str(item_path), str(dest_path)) - print(f" Moved: {item_path.name}") - else: - print(f" WARNING: Versions/A directory not found in {framework_path}") - - # 3. Remove Versions/ directory - print(" Removing Versions directory") - shutil.rmtree(versions_dir) - - # 4. Patch the binary's install name for flattened structure - binary_name = framework_path.name.replace('.framework', '') - binary_path = framework_path / binary_name - - if not binary_path.exists(): - print(f"ERROR: Framework binary not found at {binary_path}") - Exit(1) - - print(f" Patching install name for binary: {binary_name}") - install_name = f"@rpath/{binary_name}.framework/{binary_name}" - cmd = ["install_name_tool", "-id", install_name, str(binary_path)] - - try: - subprocess.run(cmd, capture_output=True, text=True, check=True) - print(f" Successfully updated install name to: {install_name}") - except subprocess.CalledProcessError as e: - print(f" WARNING: Failed to update install name: {e}") - print(f" stdout: {e.stdout}") - print(f" stderr: {e.stderr}") - - print(f"Framework flattening completed: {framework_path}") - - def update_cocoa_framework(): """Updates Sentry Cocoa to the latest version.""" project_root = Path(env.Dir("#").abspath) @@ -184,9 +120,6 @@ def update_cocoa_framework(): print(f"Extracting {zip_path}") extract_zip_with_symlinks(zip_path, cocoa_dir) - # NOTE: We need to flatten macOS slice due to issues with symlinks on Windows. - flatten_framework(cocoa_dir / "Sentry-Dynamic.xcframework" / "macos-arm64_x86_64" / "Sentry.framework") - zip_path.unlink() # delete file version_file.write_text(cocoa_version) @@ -242,7 +175,7 @@ if platform in ["macos", "ios"]: "-framework", "Sentry", "-F" + str(framework_container_dir), # Allow extension to find framework in addons/sentry/ directory. - "-Wl,-rpath,@loader_path/..", + "-Wl,-rpath,@loader_path/../../..", ] ) @@ -405,11 +338,14 @@ def DeploySentryCocoa(self, target_dir): target_framework = target_dir_path / "Sentry.framework" # Copy only the binary and "Resources" dir -- we don't need to export headers or modules. + # commands.append( + # env.Copy(File(target_framework / "Sentry"), File(source_framework / "Sentry")) + # ) + # commands.append( + # env.Copy(Dir(target_framework / "Resources"), Dir(source_framework / "Resources")) + # ) commands.append( - env.Copy(File(target_framework / "Sentry"), File(source_framework / "Sentry")) - ) - commands.append( - env.Copy(Dir(target_framework / "Resources"), Dir(source_framework / "Resources")) + env.Copy(Dir(target_framework), Dir(source_framework)) ) # Debug symbols diff --git a/site_scons/site_tools/link_bundle.py b/site_scons/site_tools/link_bundle.py new file mode 100644 index 000000000..a5e05c8ca --- /dev/null +++ b/site_scons/site_tools/link_bundle.py @@ -0,0 +1,74 @@ +""" +Tool to create framework symlinks as post-process action. + +Usage: env.LinkBundle(FRAMEWORK_PATH, FRAMEWORK_TARGET) + +This tool expects the framework path to contain a Versions/A subdirectory +with complete bundle resources after the specified targets are built. + +Example: + env.LinkBundle("mylib.framework/", [framework_resources]) +""" + +import os +from SCons.Script import Action + + +def link_bundle_action(target, source, env, framework_path): + """Post-process action to create framework symlinks""" + framework_dir = os.path.abspath(framework_path) + version_dir = f"{framework_dir}/Versions/A" + + if not os.path.exists(version_dir): + print(f"Versions/A directory does not exist: {version_dir}") + return False + + original_cwd = os.getcwd() + os.chdir(framework_dir) + + try: + # Create Current -> A symlink in Versions directory + versions_dir = os.path.dirname(version_dir) + current_link = os.path.join(versions_dir, "Current") + version_name = os.path.basename(version_dir) + + if os.path.islink(current_link): + os.unlink(current_link) + + os.chdir(versions_dir) + os.symlink(version_name, "Current") + print(f"Created symlink: Current -> {version_name}") + + os.chdir(framework_dir) + + # Create symlinks in framework root that point to Versions/Current/* + for item in os.listdir(version_dir): + link_name = item + relative_target = os.path.join("Versions", "Current", item) + + if os.path.islink(link_name): + os.unlink(link_name) + + os.symlink(relative_target, link_name) + print(f"Created symlink: {link_name} -> {relative_target}") + finally: + os.chdir(original_cwd) + + return True + + +def command(env, framework_path, target): + # Closure captures framework_path + def action(target, source, env): + link_bundle_action(target, source, env, framework_path) + + env.AddPostAction(target, Action(action, cmdstr=f"Creating framework symlinks in {framework_path}")) + return target + + +def generate(env): + env.AddMethod(command, "LinkBundle") + + +def exists(env): + return True