summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--modules/mono/config.py2
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj2
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props14
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets4
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/iOSNativeAOT.props8
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/iOSNativeAOT.targets58
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs3
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs44
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs78
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs290
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs10
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs1
-rw-r--r--modules/mono/mono_gd/gd_mono.cpp26
-rw-r--r--modules/mono/mono_gd/support/ios_support.h50
-rw-r--r--modules/mono/mono_gd/support/ios_support.mm150
-rw-r--r--platform/ios/export/export_plugin.cpp13
16 files changed, 463 insertions, 290 deletions
diff --git a/modules/mono/config.py b/modules/mono/config.py
index 9846d60c33..859d77b262 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 663eb14f07..ad3a10ba49 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 @@
<None Include="$(GodotSdkPackageVersionsFilePath)" Pack="true" PackagePath="Sdk">
<Link>Sdk\SdkPackageVersions.props</Link>
</None>
+ <None Include="Sdk\iOSNativeAOT.props" Pack="true" PackagePath="Sdk" />
+ <None Include="Sdk\iOSNativeAOT.targets" Pack="true" PackagePath="Sdk" />
</ItemGroup>
</Project>
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 b35cec64f3..b6c72bce9d 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
@@ -60,6 +60,18 @@
<!-- Auto-detect the target Godot platform if it was not specified. -->
<PropertyGroup Condition=" '$(GodotTargetPlatform)' == '' ">
+ <GodotTargetPlatform Condition=" $(RuntimeIdentifier.StartsWith('ios')) ">ios</GodotTargetPlatform>
+ <GodotTargetPlatform Condition=" '$(GodotTargetPlatform)' == '' and $(RuntimeIdentifier.StartsWith('android')) ">android</GodotTargetPlatform>
+ <GodotTargetPlatform Condition=" '$(GodotTargetPlatform)' == '' and $(RuntimeIdentifier.StartsWith('browser')) ">web</GodotTargetPlatform>
+
+ <GodotTargetPlatform Condition=" '$(GodotTargetPlatform)' == '' and $(RuntimeIdentifier.StartsWith('linux')) ">linuxbsd</GodotTargetPlatform>
+ <GodotTargetPlatform Condition=" '$(GodotTargetPlatform)' == '' and $(RuntimeIdentifier.StartsWith('freebsd')) ">linuxbsd</GodotTargetPlatform>
+ <GodotTargetPlatform Condition=" '$(GodotTargetPlatform)' == '' and $(RuntimeIdentifier.StartsWith('osx')) ">macos</GodotTargetPlatform>
+ <GodotTargetPlatform Condition=" '$(GodotTargetPlatform)' == '' and $(RuntimeIdentifier.StartsWith('win')) ">windows</GodotTargetPlatform>
+ </PropertyGroup>
+
+ <!-- Auto-detect the target Godot platform if it was not specified and there's no runtime identifier information. -->
+ <PropertyGroup Condition=" '$(GodotTargetPlatform)' == '' ">
<GodotTargetPlatform Condition=" '$([MSBuild]::IsOsPlatform(Linux))' ">linuxbsd</GodotTargetPlatform>
<GodotTargetPlatform Condition=" '$([MSBuild]::IsOsPlatform(FreeBSD))' ">linuxbsd</GodotTargetPlatform>
<GodotTargetPlatform Condition=" '$([MSBuild]::IsOsPlatform(OSX))' ">macos</GodotTargetPlatform>
@@ -97,4 +109,6 @@
<DefineConstants>$(GodotDefineConstants);$(DefineConstants)</DefineConstants>
</PropertyGroup>
+
+ <Import Project="$(MSBuildThisFileDirectory)\iOSNativeAOT.props" Condition=" '$(GodotTargetPlatform)' == 'ios' " />
</Project>
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 4dcc96a1f6..29ef76a5e8 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 @@
<PackageReference Include="GodotSharp" Version="$(PackageVersion_GodotSharp)" />
<PackageReference Include="GodotSharpEditor" Version="$(PackageVersion_GodotSharp)" Condition=" '$(Configuration)' == 'Debug' " />
</ItemGroup>
+
+ <!-- iOS-specific build targets -->
+ <Import Project="$(MSBuildThisFileDirectory)\iOSNativeAOT.targets" Condition=" '$(GodotTargetPlatform)' == 'ios' " />
+
</Project>
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 0000000000..e3c953ccac
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/iOSNativeAOT.props
@@ -0,0 +1,8 @@
+<Project>
+ <PropertyGroup>
+ <PublishAot>true</PublishAot>
+ <PublishAotUsingRuntimePack>true</PublishAotUsingRuntimePack>
+ <UseNativeAOTRuntime>true</UseNativeAOTRuntime>
+ <TrimmerSingleWarn>false</TrimmerSingleWarn>
+ </PropertyGroup>
+</Project>
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 0000000000..d8129a6652
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/iOSNativeAOT.targets
@@ -0,0 +1,58 @@
+<Project>
+ <ItemGroup>
+ <TrimmerRootAssembly Include="GodotSharp" />
+ <TrimmerRootAssembly Include="$(TargetName)" />
+ <LinkerArg Include="-install_name '@rpath/$(TargetName)$(NativeBinaryExt)'" />
+ </ItemGroup>
+
+ <PropertyGroup>
+ <LinkStandardCPlusPlusLibrary>true</LinkStandardCPlusPlusLibrary>
+ <FindXCode Condition=" '$(XCodePath)' == '' and '$([MSBuild]::IsOsPlatform(OSX))' ">true</FindXCode>
+ <XCodePath Condition=" '$(XCodePath)' == '' ">/Applications/Xcode.app/Contents/Developer</XCodePath>
+ <XCodePath>$([MSBuild]::EnsureTrailingSlash('$(XCodePath)'))</XCodePath>
+ </PropertyGroup>
+
+ <Target Name="PrepareBeforeIlcCompile"
+ BeforeTargets="IlcCompile">
+
+ <Copy SourceFiles="%(ResolvedRuntimePack.PackageDirectory)/runtimes/$(RuntimeIdentifier)/native/icudt.dat" DestinationFolder="$(PublishDir)"/>
+
+ <!-- We need to find the path to Xcode so we can set manual linker args to the correct SDKs
+ Once https://github.com/dotnet/runtime/issues/88737 is released, we can take this out
+ -->
+
+ <Exec Command="xcrun xcode-select -p" ConsoleToMSBuild="true" Condition=" '$(FindXCode)' == 'true' ">
+ <Output TaskParameter="ConsoleOutput" PropertyName="XcodeSelect" />
+ </Exec>
+
+ <PropertyGroup Condition=" '$(FindXCode)' == 'true' ">
+ <XCodePath>$(XcodeSelect)</XCodePath>
+ <XCodePath>$([MSBuild]::EnsureTrailingSlash('$(XCodePath)'))</XCodePath>
+ </PropertyGroup>
+
+ <Message Importance="normal" Text="Found XCode at $(XcodeSelect)" Condition=" '$(FindXCode)' == 'true' "/>
+
+ <ItemGroup>
+ <LinkerArg Include="-isysroot %22$(XCodePath)Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk%22"
+ Condition=" $(RuntimeIdentifier.Contains('simulator')) "/>
+ <LinkerArg Include="-isysroot %22$(XCodePath)Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk%22"
+ Condition=" !$(RuntimeIdentifier.Contains('simulator')) "/>
+ </ItemGroup>
+
+ </Target>
+
+ <Target Name="FixSymbols"
+ AfterTargets="Publish">
+
+ <RemoveDir Directories="$(PublishDir)$(TargetName).framework.dSYM"/>
+
+ <!-- create-xcframework (called from the export plugin wants the symbol files in a directory
+ with a slightly different name from the one created by dotnet publish, so we copy them over
+ to the correctly-named directory -->
+ <ItemGroup>
+ <SymbolFiles Include="$(NativeBinary).dsym\**\*.*"/>
+ </ItemGroup>
+ <Copy SourceFiles="@(SymbolFiles)" DestinationFolder="$(PublishDir)$(TargetName).framework.dSYM"/>
+ </Target>
+
+</Project>
diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs
index f3c8e89dff..1e5d7c901e 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 9bb4fd153b..2a6090eb6d 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<string> 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<string> 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 8a292fd73a..57b5598a78 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<string> outputPaths, string xcFrameworkPath,
+ Action<string> stdOutHandler, Action<string> 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<string> outputPaths, string xcFrameworkPath, Action<string> stdOutHandler, Action<string> stdErrHandler)
+ {
+ using (var process = DoGenerateXCFramework(outputPaths, xcFrameworkPath, stdOutHandler, stdErrHandler))
+ {
+ process.WaitForExit();
+
+ return process.ExitCode;
+ }
+ }
+
+ private static void BuildXCFrameworkArguments(List<string> outputPaths,
+ string xcFrameworkPath, Collection<string> 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 b98df190ca..595c9a1268 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";
-
- bool includeDebugSymbols = (bool)GetOption("dotnet/include_debug_symbols");
+ PublishConfig publishConfig = new()
+ {
+ BuildConfig = isDebug ? "ExportDebug" : "ExportRelease",
+ IncludeDebugSymbols = (bool)GetOption("dotnet/include_debug_symbols"),
+ RidOS = DetermineRuntimeIdentifierOS(platform),
+ Archs = new List<string>(),
+ UseTempDir = platform != OS.Platforms.iOS, // xcode project links directly to files in the publish dir, so use one that sticks around.
+ BundleOutputs = true,
+ };
- var archs = new List<string>();
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");
}
}
- bool embedBuildResults = (bool)GetOption("dotnet/embed_build_outputs") || features.Contains("android");
+ var targets = new List<PublishConfig> { publishConfig };
- foreach (var arch in archs)
+ if (platform == OS.Platforms.iOS)
{
- string ridOS = DetermineRuntimeIdentifierOS(platform);
- string ridArch = DetermineRuntimeIdentifierArch(arch);
- string runtimeIdentifier = $"{ridOS}-{ridArch}";
- string projectDataDirName = $"data_{GodotSharpDirs.CSharpProjectName}_{platform}_{arch}";
- if (platform == OS.Platforms.MacOS)
+ targets.Add(new PublishConfig
{
- projectDataDirName = Path.Combine("Contents", "Resources", projectDataDirName);
- }
+ BuildConfig = publishConfig.BuildConfig,
+ Archs = new List<string> { "arm64", "x86_64" },
+ BundleOutputs = false,
+ IncludeDebugSymbols = publishConfig.IncludeDebugSymbols,
+ RidOS = OS.DotNetOS.iOSSimulator,
+ UseTempDir = true,
+ });
+ }
- // Create temporary publish output directory
+ List<string> outputPaths = new();
- string publishOutputTempDir = Path.Combine(Path.GetTempPath(), "godot-publish-dotnet",
- $"{System.Environment.ProcessId}-{buildConfig}-{runtimeIdentifier}");
+ bool embedBuildResults = (bool)GetOption("dotnet/embed_build_outputs") || features.Contains("android");
- _tempFolders.Add(publishOutputTempDir);
+ foreach (PublishConfig config in targets)
+ {
+ string ridOS = config.RidOS;
+ string buildConfig = config.BuildConfig;
+ bool includeDebugSymbols = config.IncludeDebugSymbols;
- if (!Directory.Exists(publishOutputTempDir))
- Directory.CreateDirectory(publishOutputTempDir);
+ foreach (string arch in config.Archs)
+ {
+ string ridArch = DetermineRuntimeIdentifierArch(arch);
+ string runtimeIdentifier = $"{ridOS}-{ridArch}";
+ string projectDataDirName = $"data_{GodotSharpDirs.CSharpProjectName}_{platform}_{arch}";
+ if (platform == OS.Platforms.MacOS)
+ {
+ projectDataDirName = Path.Combine("Contents", "Resources", projectDataDirName);
+ }
- // Execute dotnet publish
+ // Create temporary publish output directory.
+ string publishOutputDir;
- if (!BuildManager.PublishProjectBlocking(buildConfig, platform,
- runtimeIdentifier, publishOutputTempDir, includeDebugSymbols))
- {
- throw new InvalidOperationException("Failed to build project.");
- }
+ if (config.UseTempDir)
+ {
+ publishOutputDir = Path.Combine(Path.GetTempPath(), "godot-publish-dotnet",
+ $"{System.Environment.ProcessId}-{buildConfig}-{runtimeIdentifier}");
+ _tempFolders.Add(publishOutputDir);
+ }
+ else
+ {
+ publishOutputDir = Path.Combine(GodotSharpDirs.ProjectBaseOutputPath, "godot-publish-dotnet",
+ $"{buildConfig}-{runtimeIdentifier}");
- 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();
+ outputPaths.Add(publishOutputDir);
- // Add to the exported project shared object list or packed resources.
- foreach (string file in Directory.GetFiles(publishOutputTempDir, "*", SearchOption.AllDirectories))
- {
- if (embedBuildResults)
+ if (!Directory.Exists(publishOutputDir))
+ Directory.CreateDirectory(publishOutputDir);
+
+ // Execute dotnet publish.
+ if (!BuildManager.PublishProjectBlocking(buildConfig, platform,
+ runtimeIdentifier, publishOutputDir, includeDebugSymbols))
{
- var filePath = SanitizeSlashes(Path.GetRelativePath(publishOutputTempDir, file));
- var fileData = File.ReadAllBytes(file);
- var hash = Convert.ToBase64String(SHA512.HashData(fileData));
+ 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"
+ };
- manifest.Append($"{filePath}\t{hash}\n");
+ string assemblyPath = Path.Combine(publishOutputDir, $"{GodotSharpDirs.ProjectAssemblyName}.dll");
+ string nativeAotPath = Path.Combine(publishOutputDir,
+ $"{GodotSharpDirs.ProjectAssemblyName}.{soExt}");
- AddFile($"res://.godot/mono/publish/{arch}/{filePath}", fileData, false);
+ if (!File.Exists(assemblyPath) && !File.Exists(nativeAotPath))
+ {
+ throw new NotSupportedException(
+ $"Publish succeeded but project assembly not found at '{assemblyPath}' or '{nativeAotPath}'.");
}
- else
+
+ // 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)
{
- AddSharedObject(file, tags: null,
- Path.Join(projectDataDirName,
- Path.GetRelativePath(publishOutputTempDir, Path.GetDirectoryName(file))));
+ byte[] fileData = Encoding.Default.GetBytes(manifest.ToString());
+ AddFile($"res://.godot/mono/publish/{arch}/.dotnet-publish-manifest", fileData, false);
}
}
+ }
+
+ if (platform == OS.Platforms.iOS)
+ {
+ if (outputPaths.Count > 2)
+ {
+ // lipo the simulator binaries together
+ // TODO: Move this to the native lipo implementation we have in the macos export plugin.
+ var lipoArgs = new List<string>();
+ 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);
+ }
- if (embedBuildResults)
+ var xcFrameworkPath = Path.Combine(GodotSharpDirs.ProjectBaseOutputPath, publishConfig.BuildConfig,
+ $"{GodotSharpDirs.ProjectAssemblyName}.xcframework");
+ if (!BuildManager.GenerateXCFrameworkBlocking(outputPaths,
+ Path.Combine(GodotSharpDirs.ProjectBaseOutputPath, publishConfig.BuildConfig, xcFrameworkPath)))
{
- var fileData = Encoding.Default.GetBytes(manifest.ToString());
- AddFile($"res://.godot/mono/publish/{arch}/.dotnet-publish-manifest", fileData, false);
+ throw new InvalidOperationException("Failed to generate xcframework.");
+ }
+
+ AddIosEmbeddedFramework(xcFrameworkPath);
+ }
+ }
+
+ private static void RecursePublishContents(string path, Func<string, bool> filterDir,
+ Func<string, bool> filterFile, Func<string, bool> recurseDir,
+ Action<string, bool> 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<string> 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 55b413453d..67891a0594 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 bff0c0df7c..c24b730c89 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 247968e251..23f2f2ff13 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);
-
- if (err != OK) {
- return nullptr;
- }
+ Error err = OS::get_singleton()->open_dynamic_library(native_aot_so_path, r_aot_dll_handle);
- void *lib = r_aot_dll_handle;
+ if (err != OK) {
+ return nullptr;
+ }
- void *symbol = nullptr;
+ void *lib = r_aot_dll_handle;
- 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;
- }
+ void *symbol = nullptr;
- return 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 cb397c8b46..0000000000
--- 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 df8b3e2626..0000000000
--- 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 <Foundation/Foundation.h>
-#include <os/log.h>
-
-// 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 a8596c30a6..ed92cac593 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<EditorExportPres
bool EditorExportPlatformIOS::has_valid_export_configuration(const Ref<EditorExportPreset> &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<EditorExp
}
return valid;
-#endif // !MODULE_MONO_ENABLED
}
bool EditorExportPlatformIOS::has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const {