Add C# iOS support
This support is experimental and requires .NET 8 Known issues: - Requires macOS due to use of lipo and xcodebuild - arm64 simulator templates are not currently included in the official packaging
This commit is contained in:
parent
9215b03429
commit
ee9a735c26
16 changed files with 471 additions and 298 deletions
|
@ -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):
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -59,6 +59,18 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<!-- 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>
|
||||
|
@ -97,4 +109,6 @@
|
|||
|
||||
<DefineConstants>$(GodotDefineConstants);$(DefineConstants)</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="$(MSBuildThisFileDirectory)\iOSNativeAOT.props" Condition=" '$(GodotTargetPlatform)' == 'ios' " />
|
||||
</Project>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
<Project>
|
||||
<PropertyGroup>
|
||||
<PublishAot>true</PublishAot>
|
||||
<PublishAotUsingRuntimePack>true</PublishAotUsingRuntimePack>
|
||||
<UseNativeAOTRuntime>true</UseNativeAOTRuntime>
|
||||
<TrimmerSingleWarn>false</TrimmerSingleWarn>
|
||||
</PropertyGroup>
|
||||
</Project>
|
|
@ -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>
|
|
@ -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.
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<string>(),
|
||||
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<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");
|
||||
}
|
||||
}
|
||||
|
||||
var targets = new List<PublishConfig> { publishConfig };
|
||||
|
||||
if (platform == OS.Platforms.iOS)
|
||||
{
|
||||
targets.Add(new PublishConfig
|
||||
{
|
||||
BuildConfig = publishConfig.BuildConfig,
|
||||
Archs = new List<string> { "arm64", "x86_64" },
|
||||
BundleOutputs = false,
|
||||
IncludeDebugSymbols = publishConfig.IncludeDebugSymbols,
|
||||
RidOS = OS.DotNetOS.iOSSimulator,
|
||||
UseTempDir = true,
|
||||
});
|
||||
}
|
||||
|
||||
List<string> 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<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);
|
||||
}
|
||||
|
||||
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<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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}");
|
||||
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue