diff --git a/modules/mono/config.py b/modules/mono/config.py
index 9846d60c335c..859d77b262c9 100644
--- a/modules/mono/config.py
+++ b/modules/mono/config.py
@@ -1,6 +1,6 @@
# Prior to .NET Core, we supported these: ["windows", "macos", "linuxbsd", "android", "web", "ios"]
# Eventually support for each them should be added back.
-supported_platforms = ["windows", "macos", "linuxbsd", "android"]
+supported_platforms = ["windows", "macos", "linuxbsd", "android", "ios"]
def can_build(env, platform):
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj
index 663eb14f075a..ad3a10ba4937 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj
@@ -29,5 +29,7 @@
Sdk\SdkPackageVersions.props
+
+
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props
index b35cec64f3db..b6c72bce9dfb 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props
@@ -59,6 +59,18 @@
+
+ ios
+ android
+ web
+
+ linuxbsd
+ linuxbsd
+ macos
+ windows
+
+
+
linuxbsd
linuxbsd
@@ -97,4 +109,6 @@
$(GodotDefineConstants);$(DefineConstants)
+
+
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets
index 4dcc96a1f67a..29ef76a5e82f 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets
@@ -20,4 +20,8 @@
+
+
+
+
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/iOSNativeAOT.props b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/iOSNativeAOT.props
new file mode 100644
index 000000000000..e3c953ccaca4
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/iOSNativeAOT.props
@@ -0,0 +1,8 @@
+
+
+ true
+ true
+ true
+ false
+
+
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/iOSNativeAOT.targets b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/iOSNativeAOT.targets
new file mode 100644
index 000000000000..d8129a66527e
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/iOSNativeAOT.targets
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+ true
+ true
+ /Applications/Xcode.app/Contents/Developer
+ $([MSBuild]::EnsureTrailingSlash('$(XCodePath)'))
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(XcodeSelect)
+ $([MSBuild]::EnsureTrailingSlash('$(XCodePath)'))
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs
index f3c8e89dfff9..1e5d7c901ee5 100644
--- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs
@@ -25,6 +25,9 @@ namespace GodotTools.ProjectEditor
mainGroup.AddProperty("TargetFramework", "net6.0");
mainGroup.AddProperty("EnableDynamicLoading", "true");
+ var net8 = mainGroup.AddProperty("TargetFramework", "net8.0");
+ net8.Condition = " '$(GodotTargetPlatform)' == 'ios' ";
+
string sanitizedName = IdentifierUtils.SanitizeQualifiedIdentifier(name, allowEmptyIdentifiers: true);
// If the name is not a valid namespace, manually set RootNamespace to a sanitized one.
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
index 9bb4fd153b28..2a6090eb6d60 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading.Tasks;
@@ -67,7 +68,7 @@ namespace GodotTools.Build
{
BuildStarted?.Invoke(buildInfo);
- // Required in order to update the build tasks list
+ // Required in order to update the build tasks list.
Internal.GodotMainIteration();
try
@@ -162,7 +163,7 @@ namespace GodotTools.Build
{
BuildStarted?.Invoke(buildInfo);
- // Required in order to update the build tasks list
+ // Required in order to update the build tasks list.
Internal.GodotMainIteration();
try
@@ -317,6 +318,45 @@ namespace GodotTools.Build
) => PublishProjectBlocking(CreatePublishBuildInfo(configuration,
platform, runtimeIdentifier, publishOutputDir, includeDebugSymbols));
+ public static bool GenerateXCFrameworkBlocking(
+ List outputPaths,
+ string xcFrameworkPath)
+ {
+ using var pr = new EditorProgress("generate_xcframework", "Generating XCFramework...", 1);
+
+ pr.Step("Running xcodebuild -create-xcframework", 0);
+
+ if (!GenerateXCFramework(outputPaths, xcFrameworkPath))
+ {
+ ShowBuildErrorDialog("Failed to generate XCFramework");
+ return false;
+ }
+
+ return true;
+ }
+
+ private static bool GenerateXCFramework(List outputPaths, string xcFrameworkPath)
+ {
+ // Required in order to update the build tasks list.
+ Internal.GodotMainIteration();
+
+ try
+ {
+ int exitCode = BuildSystem.GenerateXCFramework(outputPaths, xcFrameworkPath, StdOutputReceived, StdErrorReceived);
+
+ if (exitCode != 0)
+ PrintVerbose(
+ $"xcodebuild create-xcframework exited with code: {exitCode}.");
+
+ return exitCode == 0;
+ }
+ catch (Exception e)
+ {
+ Console.Error.WriteLine(e);
+ return false;
+ }
+ }
+
public static bool EditorBuildCallback()
{
if (!File.Exists(GodotSharpDirs.ProjectCsProjPath))
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs
index 8a292fd73a0d..57b5598a787d 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs
@@ -9,7 +9,9 @@ using System.Text;
using System.Threading.Tasks;
using Godot;
using GodotTools.BuildLogger;
+using GodotTools.Internals;
using GodotTools.Utils;
+using Directory = GodotTools.Utils.Directory;
namespace GodotTools.Build
{
@@ -293,5 +295,81 @@ namespace GodotTools.Build
foreach (string env in platformEnvironmentVariables)
environmentVariables.Remove(env);
}
+
+ private static Process DoGenerateXCFramework(List outputPaths, string xcFrameworkPath,
+ Action stdOutHandler, Action stdErrHandler)
+ {
+ if (Directory.Exists(xcFrameworkPath))
+ {
+ Directory.Delete(xcFrameworkPath, true);
+ }
+
+ var startInfo = new ProcessStartInfo("xcrun");
+
+ BuildXCFrameworkArguments(outputPaths, xcFrameworkPath, startInfo.ArgumentList);
+
+ string launchMessage = startInfo.GetCommandLineDisplay(new StringBuilder("Packaging: ")).ToString();
+ stdOutHandler?.Invoke(launchMessage);
+ if (Godot.OS.IsStdOutVerbose())
+ Console.WriteLine(launchMessage);
+
+ startInfo.RedirectStandardOutput = true;
+ startInfo.RedirectStandardError = true;
+ startInfo.UseShellExecute = false;
+
+ if (OperatingSystem.IsWindows())
+ {
+ startInfo.StandardOutputEncoding = Encoding.UTF8;
+ startInfo.StandardErrorEncoding = Encoding.UTF8;
+ }
+
+ // Needed when running from Developer Command Prompt for VS.
+ RemovePlatformVariable(startInfo.EnvironmentVariables);
+
+ var process = new Process { StartInfo = startInfo };
+
+ if (stdOutHandler != null)
+ process.OutputDataReceived += (_, e) => stdOutHandler.Invoke(e.Data);
+ if (stdErrHandler != null)
+ process.ErrorDataReceived += (_, e) => stdErrHandler.Invoke(e.Data);
+
+ process.Start();
+
+ process.BeginOutputReadLine();
+ process.BeginErrorReadLine();
+
+ return process;
+ }
+
+ public static int GenerateXCFramework(List outputPaths, string xcFrameworkPath, Action stdOutHandler, Action stdErrHandler)
+ {
+ using (var process = DoGenerateXCFramework(outputPaths, xcFrameworkPath, stdOutHandler, stdErrHandler))
+ {
+ process.WaitForExit();
+
+ return process.ExitCode;
+ }
+ }
+
+ private static void BuildXCFrameworkArguments(List outputPaths,
+ string xcFrameworkPath, Collection arguments)
+ {
+ var baseDylib = $"{GodotSharpDirs.ProjectAssemblyName}.dylib";
+ var baseSym = $"{GodotSharpDirs.ProjectAssemblyName}.framework.dSYM";
+
+ arguments.Add("xcodebuild");
+ arguments.Add("-create-xcframework");
+
+ foreach (var outputPath in outputPaths)
+ {
+ arguments.Add("-library");
+ arguments.Add(Path.Combine(outputPath, baseDylib));
+ arguments.Add("-debug-symbols");
+ arguments.Add(Path.Combine(outputPath, baseSym));
+ }
+
+ arguments.Add("-output");
+ arguments.Add(xcFrameworkPath);
+ }
}
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
index b98df190ca69..595c9a126841 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
@@ -6,9 +6,7 @@ using System.Linq;
using System.Security.Cryptography;
using System.Text;
using GodotTools.Build;
-using GodotTools.Core;
using GodotTools.Internals;
-using static GodotTools.Internals.Globals;
using Directory = GodotTools.Utils.Directory;
using File = GodotTools.Utils.File;
using OS = GodotTools.Utils.OS;
@@ -77,7 +75,7 @@ namespace GodotTools.Export
$"Resource of type {Internal.CSharpLanguageType} has an invalid file extension: {path}",
nameof(path));
- // TODO What if the source file is not part of the game's C# project
+ // TODO: What if the source file is not part of the game's C# project?
bool includeScriptsContent = (bool)GetOption("dotnet/include_scripts_content");
@@ -89,7 +87,7 @@ namespace GodotTools.Export
// Because of this, we add a file which contains a line break.
AddFile(path, System.Text.Encoding.UTF8.GetBytes("\n"), remap: false);
- // Tell the Godot exporter that we already took care of the file
+ // Tell the Godot exporter that we already took care of the file.
Skip();
}
}
@@ -119,7 +117,7 @@ namespace GodotTools.Export
private void _ExportBeginImpl(string[] features, bool isDebug, string path, long flags)
{
- _ = flags; // Unused
+ _ = flags; // Unused.
if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
return;
@@ -127,115 +125,261 @@ namespace GodotTools.Export
if (!DeterminePlatformFromFeatures(features, out string platform))
throw new NotSupportedException("Target platform not supported.");
- if (!new[] { OS.Platforms.Windows, OS.Platforms.LinuxBSD, OS.Platforms.MacOS, OS.Platforms.Android }
+ if (!new[] { OS.Platforms.Windows, OS.Platforms.LinuxBSD, OS.Platforms.MacOS, OS.Platforms.Android, OS.Platforms.iOS }
.Contains(platform))
{
throw new NotImplementedException("Target platform not yet implemented.");
}
- string buildConfig = isDebug ? "ExportDebug" : "ExportRelease";
+ PublishConfig publishConfig = new()
+ {
+ BuildConfig = isDebug ? "ExportDebug" : "ExportRelease",
+ IncludeDebugSymbols = (bool)GetOption("dotnet/include_debug_symbols"),
+ RidOS = DetermineRuntimeIdentifierOS(platform),
+ Archs = new List(),
+ UseTempDir = platform != OS.Platforms.iOS, // xcode project links directly to files in the publish dir, so use one that sticks around.
+ BundleOutputs = true,
+ };
- bool includeDebugSymbols = (bool)GetOption("dotnet/include_debug_symbols");
-
- var archs = new List();
if (features.Contains("x86_64"))
{
- archs.Add("x86_64");
+ publishConfig.Archs.Add("x86_64");
}
+
if (features.Contains("x86_32"))
{
- archs.Add("x86_32");
+ publishConfig.Archs.Add("x86_32");
}
+
if (features.Contains("arm64"))
{
- archs.Add("arm64");
+ publishConfig.Archs.Add("arm64");
}
+
if (features.Contains("arm32"))
{
- archs.Add("arm32");
+ publishConfig.Archs.Add("arm32");
}
+
if (features.Contains("universal"))
{
if (platform == OS.Platforms.MacOS)
{
- archs.Add("x86_64");
- archs.Add("arm64");
+ publishConfig.Archs.Add("x86_64");
+ publishConfig.Archs.Add("arm64");
}
}
+ var targets = new List { publishConfig };
+
+ if (platform == OS.Platforms.iOS)
+ {
+ targets.Add(new PublishConfig
+ {
+ BuildConfig = publishConfig.BuildConfig,
+ Archs = new List { "arm64", "x86_64" },
+ BundleOutputs = false,
+ IncludeDebugSymbols = publishConfig.IncludeDebugSymbols,
+ RidOS = OS.DotNetOS.iOSSimulator,
+ UseTempDir = true,
+ });
+ }
+
+ List outputPaths = new();
+
bool embedBuildResults = (bool)GetOption("dotnet/embed_build_outputs") || features.Contains("android");
- foreach (var arch in archs)
+ foreach (PublishConfig config in targets)
{
- string ridOS = DetermineRuntimeIdentifierOS(platform);
- string ridArch = DetermineRuntimeIdentifierArch(arch);
- string runtimeIdentifier = $"{ridOS}-{ridArch}";
- string projectDataDirName = $"data_{GodotSharpDirs.CSharpProjectName}_{platform}_{arch}";
- if (platform == OS.Platforms.MacOS)
+ string ridOS = config.RidOS;
+ string buildConfig = config.BuildConfig;
+ bool includeDebugSymbols = config.IncludeDebugSymbols;
+
+ foreach (string arch in config.Archs)
{
- projectDataDirName = Path.Combine("Contents", "Resources", projectDataDirName);
- }
-
- // Create temporary publish output directory
-
- string publishOutputTempDir = Path.Combine(Path.GetTempPath(), "godot-publish-dotnet",
- $"{System.Environment.ProcessId}-{buildConfig}-{runtimeIdentifier}");
-
- _tempFolders.Add(publishOutputTempDir);
-
- if (!Directory.Exists(publishOutputTempDir))
- Directory.CreateDirectory(publishOutputTempDir);
-
- // Execute dotnet publish
-
- if (!BuildManager.PublishProjectBlocking(buildConfig, platform,
- runtimeIdentifier, publishOutputTempDir, includeDebugSymbols))
- {
- throw new InvalidOperationException("Failed to build project.");
- }
-
- string soExt = ridOS switch
- {
- OS.DotNetOS.Win or OS.DotNetOS.Win10 => "dll",
- OS.DotNetOS.OSX or OS.DotNetOS.iOS => "dylib",
- _ => "so"
- };
-
- if (!File.Exists(Path.Combine(publishOutputTempDir, $"{GodotSharpDirs.ProjectAssemblyName}.dll"))
- // NativeAOT shared library output
- && !File.Exists(Path.Combine(publishOutputTempDir, $"{GodotSharpDirs.ProjectAssemblyName}.{soExt}")))
- {
- throw new NotSupportedException(
- "Publish succeeded but project assembly not found in the output directory");
- }
-
- var manifest = new StringBuilder();
-
- // Add to the exported project shared object list or packed resources.
- foreach (string file in Directory.GetFiles(publishOutputTempDir, "*", SearchOption.AllDirectories))
- {
- if (embedBuildResults)
+ string ridArch = DetermineRuntimeIdentifierArch(arch);
+ string runtimeIdentifier = $"{ridOS}-{ridArch}";
+ string projectDataDirName = $"data_{GodotSharpDirs.CSharpProjectName}_{platform}_{arch}";
+ if (platform == OS.Platforms.MacOS)
{
- var filePath = SanitizeSlashes(Path.GetRelativePath(publishOutputTempDir, file));
- var fileData = File.ReadAllBytes(file);
- var hash = Convert.ToBase64String(SHA512.HashData(fileData));
+ projectDataDirName = Path.Combine("Contents", "Resources", projectDataDirName);
+ }
- manifest.Append($"{filePath}\t{hash}\n");
+ // Create temporary publish output directory.
+ string publishOutputDir;
- AddFile($"res://.godot/mono/publish/{arch}/{filePath}", fileData, false);
+ if (config.UseTempDir)
+ {
+ publishOutputDir = Path.Combine(Path.GetTempPath(), "godot-publish-dotnet",
+ $"{System.Environment.ProcessId}-{buildConfig}-{runtimeIdentifier}");
+ _tempFolders.Add(publishOutputDir);
}
else
{
- AddSharedObject(file, tags: null,
- Path.Join(projectDataDirName,
- Path.GetRelativePath(publishOutputTempDir, Path.GetDirectoryName(file))));
+ publishOutputDir = Path.Combine(GodotSharpDirs.ProjectBaseOutputPath, "godot-publish-dotnet",
+ $"{buildConfig}-{runtimeIdentifier}");
+
+ }
+
+ outputPaths.Add(publishOutputDir);
+
+ if (!Directory.Exists(publishOutputDir))
+ Directory.CreateDirectory(publishOutputDir);
+
+ // Execute dotnet publish.
+ if (!BuildManager.PublishProjectBlocking(buildConfig, platform,
+ runtimeIdentifier, publishOutputDir, includeDebugSymbols))
+ {
+ throw new InvalidOperationException("Failed to build project.");
+ }
+
+ string soExt = ridOS switch
+ {
+ OS.DotNetOS.Win or OS.DotNetOS.Win10 => "dll",
+ OS.DotNetOS.OSX or OS.DotNetOS.iOS or OS.DotNetOS.iOSSimulator => "dylib",
+ _ => "so"
+ };
+
+ string assemblyPath = Path.Combine(publishOutputDir, $"{GodotSharpDirs.ProjectAssemblyName}.dll");
+ string nativeAotPath = Path.Combine(publishOutputDir,
+ $"{GodotSharpDirs.ProjectAssemblyName}.{soExt}");
+
+ if (!File.Exists(assemblyPath) && !File.Exists(nativeAotPath))
+ {
+ throw new NotSupportedException(
+ $"Publish succeeded but project assembly not found at '{assemblyPath}' or '{nativeAotPath}'.");
+ }
+
+ // For ios simulator builds, skip packaging the build outputs.
+ if (!config.BundleOutputs)
+ continue;
+
+ var manifest = new StringBuilder();
+
+ // Add to the exported project shared object list or packed resources.
+ RecursePublishContents(publishOutputDir,
+ filterDir: dir =>
+ {
+ if (platform == OS.Platforms.iOS)
+ {
+ // Exclude dsym folders.
+ return !dir.EndsWith(".dsym", StringComparison.InvariantCultureIgnoreCase);
+ }
+
+ return true;
+ },
+ filterFile: file =>
+ {
+ if (platform == OS.Platforms.iOS)
+ {
+ // Exclude the dylib artifact, since it's included separately as an xcframework.
+ return Path.GetFileName(file) != $"{GodotSharpDirs.ProjectAssemblyName}.dylib";
+ }
+
+ return true;
+ },
+ recurseDir: dir =>
+ {
+ if (platform == OS.Platforms.iOS)
+ {
+ // Don't recurse into dsym folders.
+ return !dir.EndsWith(".dsym", StringComparison.InvariantCultureIgnoreCase);
+ }
+
+ return true;
+ },
+ addEntry: (path, isFile) =>
+ {
+ // We get called back for both directories and files, but we only package files for now.
+ if (isFile)
+ {
+ if (embedBuildResults)
+ {
+ string filePath = SanitizeSlashes(Path.GetRelativePath(publishOutputDir, path));
+ byte[] fileData = File.ReadAllBytes(path);
+ string hash = Convert.ToBase64String(SHA512.HashData(fileData));
+
+ manifest.Append($"{filePath}\t{hash}\n");
+
+ AddFile($"res://.godot/mono/publish/{arch}/{filePath}", fileData, false);
+ }
+ else
+ {
+ if (platform == OS.Platforms.iOS && path.EndsWith(".dat"))
+ {
+ AddIosBundleFile(path);
+ }
+ else
+ {
+ AddSharedObject(path, tags: null,
+ Path.Join(projectDataDirName,
+ Path.GetRelativePath(publishOutputDir,
+ Path.GetDirectoryName(path))));
+ }
+ }
+ }
+ });
+
+ if (embedBuildResults)
+ {
+ byte[] fileData = Encoding.Default.GetBytes(manifest.ToString());
+ AddFile($"res://.godot/mono/publish/{arch}/.dotnet-publish-manifest", fileData, false);
}
}
+ }
- if (embedBuildResults)
+ if (platform == OS.Platforms.iOS)
+ {
+ if (outputPaths.Count > 2)
{
- var fileData = Encoding.Default.GetBytes(manifest.ToString());
- AddFile($"res://.godot/mono/publish/{arch}/.dotnet-publish-manifest", fileData, false);
+ // lipo the simulator binaries together
+ // TODO: Move this to the native lipo implementation we have in the macos export plugin.
+ var lipoArgs = new List();
+ lipoArgs.Add("-create");
+ lipoArgs.AddRange(outputPaths.Skip(1).Select(x => Path.Combine(x, $"{GodotSharpDirs.ProjectAssemblyName}.dylib")));
+ lipoArgs.Add("-output");
+ lipoArgs.Add(Path.Combine(outputPaths[1], $"{GodotSharpDirs.ProjectAssemblyName}.dylib"));
+
+ int lipoExitCode = OS.ExecuteCommand(XcodeHelper.FindXcodeTool("lipo"), lipoArgs);
+ if (lipoExitCode != 0)
+ throw new InvalidOperationException($"Command 'lipo' exited with code: {lipoExitCode}.");
+
+ outputPaths.RemoveRange(2, outputPaths.Count - 2);
+ }
+
+ var xcFrameworkPath = Path.Combine(GodotSharpDirs.ProjectBaseOutputPath, publishConfig.BuildConfig,
+ $"{GodotSharpDirs.ProjectAssemblyName}.xcframework");
+ if (!BuildManager.GenerateXCFrameworkBlocking(outputPaths,
+ Path.Combine(GodotSharpDirs.ProjectBaseOutputPath, publishConfig.BuildConfig, xcFrameworkPath)))
+ {
+ throw new InvalidOperationException("Failed to generate xcframework.");
+ }
+
+ AddIosEmbeddedFramework(xcFrameworkPath);
+ }
+ }
+
+ private static void RecursePublishContents(string path, Func filterDir,
+ Func filterFile, Func recurseDir,
+ Action addEntry)
+ {
+ foreach (string file in Directory.GetFiles(path, "*", SearchOption.TopDirectoryOnly))
+ {
+ if (filterFile(file))
+ {
+ addEntry(file, true);
+ }
+ }
+
+ foreach (string dir in Directory.GetDirectories(path, "*", SearchOption.TopDirectoryOnly))
+ {
+ if (filterDir(dir))
+ {
+ addEntry(dir, false);
+ }
+ else if (recurseDir(dir))
+ {
+ RecursePublishContents(dir, filterDir, filterFile, recurseDir, addEntry);
}
}
}
@@ -304,5 +448,15 @@ namespace GodotTools.Export
platform = null;
return false;
}
+
+ private struct PublishConfig
+ {
+ public bool UseTempDir;
+ public bool BundleOutputs;
+ public string RidOS;
+ public List Archs;
+ public string BuildConfig;
+ public bool IncludeDebugSymbols;
+ }
}
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs
index 55b413453d52..67891a0594bf 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs
@@ -118,6 +118,16 @@ namespace GodotTools.Internals
}
}
+ public static string ProjectBaseOutputPath
+ {
+ get
+ {
+ if (_projectCsProjPath == null)
+ DetermineProjectLocation();
+ return Path.Combine(Path.GetDirectoryName(_projectCsProjPath)!, ".godot", "mono", "temp", "bin");
+ }
+ }
+
public static string LogsDirPathFor(string solution, string configuration)
=> Path.Combine(BuildLogsDirs, $"{solution.Md5Text()}_{configuration}");
diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs
index bff0c0df7c9b..c24b730c89a8 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs
@@ -56,6 +56,7 @@ namespace GodotTools.Utils
public const string Win10 = "win10";
public const string Android = "android";
public const string iOS = "ios";
+ public const string iOSSimulator = "iossimulator";
public const string Browser = "browser";
}
diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp
index 247968e25194..23f2f2ff13e9 100644
--- a/modules/mono/mono_gd/gd_mono.cpp
+++ b/modules/mono/mono_gd/gd_mono.cpp
@@ -322,7 +322,7 @@ godot_plugins_initialize_fn try_load_native_aot_library(void *&r_aot_dll_handle)
#if defined(WINDOWS_ENABLED)
String native_aot_so_path = GodotSharpDirs::get_api_assemblies_dir().path_join(assembly_name + ".dll");
-#elif defined(MACOS_ENABLED)
+#elif defined(MACOS_ENABLED) || defined(IOS_ENABLED)
String native_aot_so_path = GodotSharpDirs::get_api_assemblies_dir().path_join(assembly_name + ".dylib");
#elif defined(UNIX_ENABLED)
String native_aot_so_path = GodotSharpDirs::get_api_assemblies_dir().path_join(assembly_name + ".so");
@@ -330,23 +330,19 @@ godot_plugins_initialize_fn try_load_native_aot_library(void *&r_aot_dll_handle)
#error "Platform not supported (yet?)"
#endif
- if (FileAccess::exists(native_aot_so_path)) {
- Error err = OS::get_singleton()->open_dynamic_library(native_aot_so_path, r_aot_dll_handle);
+ Error err = OS::get_singleton()->open_dynamic_library(native_aot_so_path, r_aot_dll_handle);
- if (err != OK) {
- return nullptr;
- }
-
- void *lib = r_aot_dll_handle;
-
- void *symbol = nullptr;
-
- err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "godotsharp_game_main_init", symbol);
- ERR_FAIL_COND_V(err != OK, nullptr);
- return (godot_plugins_initialize_fn)symbol;
+ if (err != OK) {
+ return nullptr;
}
- return nullptr;
+ void *lib = r_aot_dll_handle;
+
+ void *symbol = nullptr;
+
+ err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "godotsharp_game_main_init", symbol);
+ ERR_FAIL_COND_V(err != OK, nullptr);
+ return (godot_plugins_initialize_fn)symbol;
}
#endif
@@ -376,11 +372,13 @@ void GDMono::initialize() {
godot_plugins_initialize_fn godot_plugins_initialize = nullptr;
+#if !defined(IOS_ENABLED)
// Check that the .NET assemblies directory exists before trying to use it.
if (!DirAccess::exists(GodotSharpDirs::get_api_assemblies_dir())) {
OS::get_singleton()->alert(vformat(RTR("Unable to find the .NET assemblies directory.\nMake sure the '%s' directory exists and contains the .NET assemblies."), GodotSharpDirs::get_api_assemblies_dir()), RTR(".NET assemblies not found"));
ERR_FAIL_MSG(".NET: Assemblies not found");
}
+#endif
if (!load_hostfxr(hostfxr_dll_handle)) {
#if !defined(TOOLS_ENABLED)
diff --git a/modules/mono/mono_gd/support/ios_support.h b/modules/mono/mono_gd/support/ios_support.h
deleted file mode 100644
index cb397c8b46b4..000000000000
--- a/modules/mono/mono_gd/support/ios_support.h
+++ /dev/null
@@ -1,50 +0,0 @@
-/**************************************************************************/
-/* ios_support.h */
-/**************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/**************************************************************************/
-/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
-/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/**************************************************************************/
-
-#ifndef IOS_SUPPORT_H
-#define IOS_SUPPORT_H
-
-#if defined(IOS_ENABLED)
-
-#include "core/string/ustring.h"
-
-namespace gdmono {
-namespace ios {
-namespace support {
-
-void initialize();
-void cleanup();
-} // namespace support
-} // namespace ios
-} // namespace gdmono
-
-#endif // IOS_ENABLED
-
-#endif // IOS_SUPPORT_H
diff --git a/modules/mono/mono_gd/support/ios_support.mm b/modules/mono/mono_gd/support/ios_support.mm
deleted file mode 100644
index df8b3e2626d4..000000000000
--- a/modules/mono/mono_gd/support/ios_support.mm
+++ /dev/null
@@ -1,150 +0,0 @@
-/**************************************************************************/
-/* ios_support.mm */
-/**************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/**************************************************************************/
-/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
-/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/**************************************************************************/
-
-#include "ios_support.h"
-
-#if defined(IOS_ENABLED)
-
-#include "../gd_mono_marshal.h"
-
-#include "core/ustring.h"
-
-#import
-#include
-
-// Implemented mostly following: https://github.com/mono/mono/blob/master/sdks/ios/app/runtime.m
-
-// Definition generated by the Godot exporter
-extern "C" void gd_mono_setup_aot();
-
-namespace gdmono {
-namespace ios {
-namespace support {
-
-void ios_mono_log_callback(const char *log_domain, const char *log_level, const char *message, mono_bool fatal, void *user_data) {
- os_log_info(OS_LOG_DEFAULT, "(%s %s) %s", log_domain, log_level, message);
- if (fatal) {
- os_log_info(OS_LOG_DEFAULT, "Exit code: %d.", 1);
- exit(1);
- }
-}
-
-void initialize() {
- mono_dllmap_insert(nullptr, "System.Native", nullptr, "__Internal", nullptr);
- mono_dllmap_insert(nullptr, "System.IO.Compression.Native", nullptr, "__Internal", nullptr);
- mono_dllmap_insert(nullptr, "System.Security.Cryptography.Native.Apple", nullptr, "__Internal", nullptr);
-
-#ifdef IOS_DEVICE
- // This function is defined in an auto-generated source file
- gd_mono_setup_aot();
-#endif
-
- mono_set_signal_chaining(true);
- mono_set_crash_chaining(true);
-}
-
-void cleanup() {
-}
-} // namespace support
-} // namespace ios
-} // namespace gdmono
-
-// The following are P/Invoke functions required by the monotouch profile of the BCL.
-// These are P/Invoke functions and not internal calls, hence why they use
-// 'mono_bool' and 'const char*' instead of 'MonoBoolean' and 'MonoString*'.
-
-#define GD_PINVOKE_EXPORT extern "C" __attribute__((visibility("default")))
-
-GD_PINVOKE_EXPORT const char *xamarin_get_locale_country_code() {
- NSLocale *locale = [NSLocale currentLocale];
- NSString *countryCode = [locale objectForKey:NSLocaleCountryCode];
- if (countryCode == nullptr) {
- return strdup("US");
- }
- return strdup([countryCode UTF8String]);
-}
-
-GD_PINVOKE_EXPORT void xamarin_log(const uint16_t *p_unicode_message) {
- int length = 0;
- const uint16_t *ptr = p_unicode_message;
- while (*ptr++) {
- length += sizeof(uint16_t);
- }
- NSString *msg = [[NSString alloc] initWithBytes:p_unicode_message length:length encoding:NSUTF16LittleEndianStringEncoding];
-
- os_log_info(OS_LOG_DEFAULT, "%{public}@", msg);
-}
-
-GD_PINVOKE_EXPORT const char *xamarin_GetFolderPath(int p_folder) {
- NSSearchPathDirectory dd = (NSSearchPathDirectory)p_folder;
- NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:dd inDomains:NSUserDomainMask] lastObject];
- NSString *path = [url path];
- return strdup([path UTF8String]);
-}
-
-GD_PINVOKE_EXPORT char *xamarin_timezone_get_local_name() {
- NSTimeZone *tz = nil;
- tz = [NSTimeZone localTimeZone];
- NSString *name = [tz name];
- return (name != nil) ? strdup([name UTF8String]) : strdup("Local");
-}
-
-GD_PINVOKE_EXPORT char **xamarin_timezone_get_names(uint32_t *p_count) {
- NSArray *array = [NSTimeZone knownTimeZoneNames];
- *p_count = array.count;
- char **result = (char **)malloc(sizeof(char *) * (*p_count));
- for (uint32_t i = 0; i < *p_count; i++) {
- NSString *s = [array objectAtIndex:i];
- result[i] = strdup(s.UTF8String);
- }
- return result;
-}
-
-GD_PINVOKE_EXPORT void *xamarin_timezone_get_data(const char *p_name, uint32_t *p_size) { // FIXME: uint32_t since Dec 2019, unsigned long before
- NSTimeZone *tz = nil;
- if (p_name) {
- NSString *n = [[NSString alloc] initWithUTF8String:p_name];
- tz = [[NSTimeZone alloc] initWithName:n];
- } else {
- tz = [NSTimeZone localTimeZone];
- }
- NSData *data = [tz data];
- *p_size = [data length];
- void *result = malloc(*p_size);
- memcpy(result, data.bytes, *p_size);
- return result;
-}
-
-GD_PINVOKE_EXPORT void xamarin_start_wwan(const char *p_uri) {
- // FIXME: What's this for? No idea how to implement.
- os_log_error(OS_LOG_DEFAULT, "Not implemented: 'xamarin_start_wwan'");
-}
-
-#endif // IOS_ENABLED
diff --git a/platform/ios/export/export_plugin.cpp b/platform/ios/export/export_plugin.cpp
index a8596c30a65b..ed92cac59334 100644
--- a/platform/ios/export/export_plugin.cpp
+++ b/platform/ios/export/export_plugin.cpp
@@ -1928,11 +1928,15 @@ Error EditorExportPlatformIOS::_export_project_helper(const Ref &p_preset, String &r_error, bool &r_missing_templates, bool p_debug) const {
#ifdef MODULE_MONO_ENABLED
- // Don't check for additional errors, as this particular error cannot be resolved.
- r_error += TTR("Exporting to iOS is currently not supported in Godot 4 when using C#/.NET. Use Godot 3 to target iOS with C#/Mono instead.") + "\n";
- r_error += TTR("If this project does not use C#, use a non-C# editor build to export the project.") + "\n";
- return false;
+#ifdef MACOS_ENABLED
+ // iOS export is still a work in progress, keep a message as a warning.
+ r_error += TTR("Exporting to iOS when using C#/.NET is experimental.") + "\n";
#else
+ // TODO: Remove this restriction when we don't rely on macOS tools to package up the native libraries anymore.
+ r_error += TTR("Exporting to iOS when using C#/.NET is experimental and requires macOS.") + "\n";
+ return false;
+#endif
+#endif
String err;
bool valid = false;
@@ -1963,7 +1967,6 @@ bool EditorExportPlatformIOS::has_valid_export_configuration(const Ref &p_preset, String &r_error) const {