From a1ef7ddcf79ab70d2b15187e6e95d8f024749172 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 28 Dec 2021 17:07:18 +0100 Subject: [PATCH] Mac support progress --- Source/Engine/Platform/Mac/MacPlatform.cpp | 55 ++++ Source/Engine/Platform/Mac/MacPlatform.h | 7 +- Source/ThirdParty/fmt/format.h | 6 +- .../Flax.Build/Build/DotNet/Builder.DotNet.cs | 6 + Source/Tools/Flax.Build/Build/Platform.cs | 30 +- .../Flax.Build/Platforms/Mac/MacPlatform.cs | 26 +- .../Flax.Build/Platforms/Mac/MacToolchain.cs | 290 +++++++++++++++++- .../Platforms/Unix/UnixToolchain.cs | 42 +-- .../Tools/Flax.Build/Utilities/Utilities.cs | 23 ++ 9 files changed, 428 insertions(+), 57 deletions(-) diff --git a/Source/Engine/Platform/Mac/MacPlatform.cpp b/Source/Engine/Platform/Mac/MacPlatform.cpp index f900b6cb3..c2dc09307 100644 --- a/Source/Engine/Platform/Mac/MacPlatform.cpp +++ b/Source/Engine/Platform/Mac/MacPlatform.cpp @@ -29,6 +29,9 @@ #include "Engine/Input/Input.h" #include "Engine/Input/Mouse.h" #include "Engine/Input/Keyboard.h" +#include +#include +#include CPUInfo MacCpu; Guid DeviceId; @@ -70,6 +73,53 @@ public: } }; +typedef uint16_t offset_t; +#define align_mem_up(num, align) (((num) + ((align) - 1)) & ~((align) - 1)) + +void* MacPlatform::Allocate(uint64 size, uint64 alignment) +{ + void* ptr = nullptr; + + // Alignment always has to be power of two + ASSERT_LOW_LAYER((alignment & (alignment - 1)) == 0); + + if (alignment && size) + { + uint32_t pad = sizeof(offset_t) + (alignment - 1); + void* p = malloc(size + pad); + if (p) + { + // Add the offset size to malloc's pointer + ptr = (void*)align_mem_up(((uintptr_t)p + sizeof(offset_t)), alignment); + + // Calculate the offset and store it behind aligned pointer + *((offset_t*)ptr - 1) = (offset_t)((uintptr_t)ptr - (uintptr_t)p); + } +#if COMPILE_WITH_PROFILER + OnMemoryAlloc(ptr, size); +#endif + } + return ptr; +} + +void MacPlatform::Free(void* ptr) +{ + if (ptr) + { +#if COMPILE_WITH_PROFILER + OnMemoryFree(ptr); +#endif + // Walk backwards from the passed-in pointer to get the pointer offset + offset_t offset = *((offset_t*)ptr - 1); + + // Get original pointer + void* p = (void*)((uint8_t*)ptr - offset); + + // Free memory + free(p); + } +} + bool MacPlatform::Is64BitPlatform() { return PLATFORM_64BITS; @@ -97,6 +147,11 @@ ProcessMemoryStats MacPlatform::GetProcessMemoryStats() return ProcessMemoryStats(); // TODO: platform stats on Mac } +uint64 UnixPlatform::GetCurrentProcessId() +{ + return getpid(); +} + uint64 MacPlatform::GetCurrentThreadID() { MISSING_CODE("MacPlatform::GetCurrentThreadID"); diff --git a/Source/Engine/Platform/Mac/MacPlatform.h b/Source/Engine/Platform/Mac/MacPlatform.h index 86bb07e63..8edccb832 100644 --- a/Source/Engine/Platform/Mac/MacPlatform.h +++ b/Source/Engine/Platform/Mac/MacPlatform.h @@ -9,11 +9,11 @@ /// /// The Mac platform implementation and application management utilities. /// -class FLAXENGINE_API MacPlatform : public UnixPlatform +class FLAXENGINE_API MacPlatform : public PlatformBase { public: - // [UnixPlatform] + // [PlatformBase] FORCE_INLINE static void MemoryBarrier() { __sync_synchronize(); @@ -66,11 +66,14 @@ public: { __builtin_prefetch(static_cast(ptr)); } + static void* Allocate(uint64 size, uint64 alignment); + static void Free(void* ptr); static bool Is64BitPlatform(); static CPUInfo GetCPUInfo(); static int32 GetCacheLineSize(); static MemoryStats GetMemoryStats(); static ProcessMemoryStats GetProcessMemoryStats(); + static uint64 GetCurrentProcessId(); static uint64 GetCurrentThreadID(); static void SetThreadPriority(ThreadPriority priority); static void SetThreadAffinityMask(uint64 affinityMask); diff --git a/Source/ThirdParty/fmt/format.h b/Source/ThirdParty/fmt/format.h index f8f54fde3..7d43f9ee4 100644 --- a/Source/ThirdParty/fmt/format.h +++ b/Source/ThirdParty/fmt/format.h @@ -1543,15 +1543,15 @@ template class basic_writer { } float_specs fspecs = parse_float_type_spec(specs); fspecs.sign = specs.sign; - if (::signbit(value)) { // value < 0 is false for NaN so use signbit. + if (signbit(value)) { // value < 0 is false for NaN so use signbit. fspecs.sign = sign::minus; value = -value; } else if (fspecs.sign == sign::minus) { fspecs.sign = sign::none; } - if (!::isfinite(value)) { - auto str = ::isinf(value) ? (fspecs.upper ? "INF" : "inf") + if (!isfinite(value)) { + auto str = isinf(value) ? (fspecs.upper ? "INF" : "inf") : (fspecs.upper ? "NAN" : "nan"); return write_padded(specs, nonfinite_writer{fspecs.sign, str}); } diff --git a/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs b/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs index 35e923645..589ab72bb 100644 --- a/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs +++ b/Source/Tools/Flax.Build/Build/DotNet/Builder.DotNet.cs @@ -70,6 +70,12 @@ namespace Flax.Build monoPath = Path.Combine(monoRoot, "bin", "mono"); cscPath = Path.Combine(monoRoot, "lib", "mono", "4.5", "csc.exe"); break; + case TargetPlatform.Mac: + // TODO: use bundled mono for Mac with csc + monoRoot = Path.Combine(Globals.EngineRoot, "Source", "Platforms", "Editor", "Linux", "Mono"); + monoPath = null; + cscPath = "csc"; + break; default: throw new InvalidPlatformException(buildPlatform); } var referenceAssemblies = Path.Combine(monoRoot, "lib", "mono", "4.5-api"); diff --git a/Source/Tools/Flax.Build/Build/Platform.cs b/Source/Tools/Flax.Build/Build/Platform.cs index 2d051216b..4a0cc9a5f 100644 --- a/Source/Tools/Flax.Build/Build/Platform.cs +++ b/Source/Tools/Flax.Build/Build/Platform.cs @@ -36,20 +36,26 @@ namespace Flax.Build case PlatformID.WinCE: return TargetPlatform.Windows; case PlatformID.Unix: { - Process p = new Process + try { - StartInfo = + Process p = new Process { - UseShellExecute = false, - RedirectStandardOutput = true, - FileName = "uname", - Arguments = "-s", - } - }; - p.Start(); - string uname = p.StandardOutput.ReadToEnd().Trim(); - if (uname == "Darwin") - return TargetPlatform.Mac; + StartInfo = + { + UseShellExecute = false, + RedirectStandardOutput = true, + FileName = "uname", + Arguments = "-s", + } + }; + p.Start(); + string uname = p.StandardOutput.ReadToEnd().Trim(); + if (uname == "Darwin") + return TargetPlatform.Mac; + } + catch (Exception) + { + } return TargetPlatform.Linux; } case PlatformID.MacOSX: return TargetPlatform.Mac; diff --git a/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs b/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs index 4ef2e8bc2..7a1051ae8 100644 --- a/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs +++ b/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs @@ -43,6 +43,11 @@ namespace Flax.Build.Platforms /// public override ProjectFormat DefaultProjectFormat => ProjectFormat.XCode; + /// + /// XCode Devloper path returned by xcode-select. + /// + public string XCodePath; + /// /// Initializes a new instance of the class. /// @@ -50,26 +55,13 @@ namespace Flax.Build.Platforms { if (Platform.BuildTargetPlatform != TargetPlatform.Mac) return; - try { // Check if XCode is installed - Process p = new Process - { - StartInfo = - { - FileName = "xcode-select", - Arguments = "--print-path", - UseShellExecute = false, - CreateNoWindow = true, - RedirectStandardOutput = true, - } - }; - p.Start(); - string xcodePath = p.StandardOutput.ReadToEnd().Trim(); - if (string.IsNullOrEmpty(xcodePath) || !Directory.Exists(xcodePath)) - throw new Exception(xcodePath); - Log.Verbose(string.Format("Found XCode at {0}", xcodePath)); + XCodePath = Utilities.ReadProcessOutput("xcode-select", "--print-path"); + if (string.IsNullOrEmpty(XCodePath) || !Directory.Exists(XCodePath)) + throw new Exception(XCodePath); + Log.Verbose(string.Format("Found XCode at {0}", XCodePath)); HasRequiredSDKsInstalled = true; } catch diff --git a/Source/Tools/Flax.Build/Platforms/Mac/MacToolchain.cs b/Source/Tools/Flax.Build/Platforms/Mac/MacToolchain.cs index 1d1e88d3e..714551952 100644 --- a/Source/Tools/Flax.Build/Platforms/Mac/MacToolchain.cs +++ b/Source/Tools/Flax.Build/Platforms/Mac/MacToolchain.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; +using System.IO; using System.Collections.Generic; using Flax.Build.Graph; using Flax.Build.NativeCpp; @@ -13,6 +14,12 @@ namespace Flax.Build.Platforms /// public sealed class MacToolchain : Toolchain { + public string ToolchainPath; + public string SdkPath; + public string ClangPath; + public string LinkerPath; + public string ArchiverPath; + /// /// Initializes a new instance of the class. /// @@ -21,6 +28,56 @@ namespace Flax.Build.Platforms public MacToolchain(MacPlatform platform, TargetArchitecture architecture) : base(platform, architecture) { + // Setup tools paths + if (platform.XCodePath.Contains("/Xcode.app")) + { + // XCode App + ToolchainPath = Path.Combine(platform.XCodePath, "Toolchains/XcodeDefault.xctoolchain"); + SdkPath = Path.Combine(platform.XCodePath, "Platforms/MacOSX.platform/Developer"); + } + else + { + // XCode Command Line Tools + ToolchainPath = SdkPath = platform.XCodePath; + if (!Directory.Exists(Path.Combine(ToolchainPath, "usr/bin"))) + throw new Exception("Missing XCode Command Line Tools. Run 'xcode-select --install'."); + } + ClangPath = Path.Combine(ToolchainPath, "usr/bin/clang++"); + LinkerPath = Path.Combine(ToolchainPath, "usr/bin/clang++"); + ArchiverPath = Path.Combine(ToolchainPath, "usr/bin/libtool"); + var clangVersion = UnixToolchain.GetClangVersion(ClangPath); + SdkPath = Path.Combine(SdkPath, "SDKs"); + var sdks = Directory.GetDirectories(SdkPath); + var sdkPrefix = "MacOSX"; + var bestSdk = string.Empty; + var bestSdkVer = new Version(0, 0, 0, 1); + foreach (var sdk in sdks) + { + var name = Path.GetFileName(sdk); + if (!name.StartsWith(sdkPrefix)) + continue; + var versionName = name.Replace(sdkPrefix, "").Replace(".sdk", ""); + if (string.IsNullOrEmpty(versionName)) + continue; + if (!versionName.Contains(".")) + versionName += ".0"; + var version = new Version(versionName); + if (version > bestSdkVer) + { + bestSdkVer = version; + bestSdk = sdk; + } + } + if (bestSdk.Length == 0) + throw new Exception("Failed to find any valid SDK for " + sdkPrefix); + SdkPath = bestSdk; + + // Setup system paths + SystemIncludePaths.Add(Path.Combine(ToolchainPath, "usr/include")); + SystemIncludePaths.Add(Path.Combine(ToolchainPath, "usr/include/c++/v1")); + SystemIncludePaths.Add(Path.Combine(ToolchainPath, "usr/lib/clang", clangVersion.ToString(3), "include")); + SystemIncludePaths.Add(Path.Combine(SdkPath, "usr/include")); + SystemLibraryPaths.Add(Path.Combine(SdkPath, "usr/lib")); } /// @@ -32,7 +89,9 @@ namespace Flax.Build.Platforms /// public override void LogInfo() { - throw new NotImplementedException("TODO: MacToolchain.LogInfo"); + Log.Info("Toolchain: " + ToolchainPath); + Log.Info("SDK: " + SdkPath); + Log.Info("Clang version: " + Utilities.ReadProcessOutput(ClangPath, "--version")); } /// @@ -46,13 +105,238 @@ namespace Flax.Build.Platforms /// public override CompileOutput CompileCppFiles(TaskGraph graph, BuildOptions options, List sourceFiles, string outputPath) { - throw new NotImplementedException("TODO: MacToolchain.CompileCppFiles"); + var compileEnvironment = options.CompileEnv; + var output = new CompileOutput(); + + // Setup arguments shared by all source files + var commonArgs = new List(); + { + commonArgs.Add("-c"); + commonArgs.Add("-fmessage-length=0"); + commonArgs.Add("-pipe"); + commonArgs.Add("-x"); + commonArgs.Add("c++"); + commonArgs.Add("-std=c++14"); + commonArgs.Add("-stdlib=libc++"); + + commonArgs.Add("-Wdelete-non-virtual-dtor"); + commonArgs.Add("-fno-math-errno"); + commonArgs.Add("-fasm-blocks"); + commonArgs.Add("-fdiagnostics-format=msvc"); + + // Hide all symbols by default + commonArgs.Add("-fvisibility-inlines-hidden"); + commonArgs.Add("-fvisibility-ms-compat"); + + if (compileEnvironment.RuntimeTypeInfo) + commonArgs.Add("-frtti"); + else + commonArgs.Add("-fno-rtti"); + + if (compileEnvironment.TreatWarningsAsErrors) + commonArgs.Add("-Wall -Werror"); + + // TODO: compileEnvironment.IntrinsicFunctions + // TODO: compileEnvironment.FunctionLevelLinking + // TODO: compileEnvironment.FavorSizeOrSpeed + // TODO: compileEnvironment.RuntimeChecks + // TODO: compileEnvironment.StringPooling + // TODO: compileEnvironment.BufferSecurityCheck + + if (compileEnvironment.DebugInformation) + commonArgs.Add("-gdwarf-2"); + + commonArgs.Add("-pthread"); + + if (compileEnvironment.Optimization) + commonArgs.Add("-O3"); + else + commonArgs.Add("-O0"); + + if (!compileEnvironment.Inlining) + { + commonArgs.Add("-fno-inline-functions"); + commonArgs.Add("-fno-inline"); + } + + if (compileEnvironment.EnableExceptions) + commonArgs.Add("-fexceptions"); + else + commonArgs.Add("-fno-exceptions"); + } + + // Add preprocessor definitions + foreach (var definition in compileEnvironment.PreprocessorDefinitions) + { + commonArgs.Add(string.Format("-D \"{0}\"", definition)); + } + + // Add include paths + foreach (var includePath in compileEnvironment.IncludePaths) + { + commonArgs.Add(string.Format("-I\"{0}\"", includePath.Replace('\\', '/'))); + } + + // Compile all C++ files + var args = new List(); + foreach (var sourceFile in sourceFiles) + { + var sourceFilename = Path.GetFileNameWithoutExtension(sourceFile); + var task = graph.Add(); + + // Use shared arguments + args.Clear(); + args.AddRange(commonArgs); + + // Object File Name + var objFile = Path.Combine(outputPath, sourceFilename + ".o"); + args.Add(string.Format("-o \"{0}\"", objFile.Replace('\\', '/'))); + output.ObjectFiles.Add(objFile); + task.ProducedFiles.Add(objFile); + + // Source File Name + args.Add("\"" + sourceFile.Replace('\\', '/') + "\""); + + // Request included files to exist + var includes = IncludesCache.FindAllIncludedFiles(sourceFile); + task.PrerequisiteFiles.AddRange(includes); + + // Compile + task.WorkingDirectory = options.WorkingDirectory; + task.CommandPath = ClangPath; + task.CommandArguments = string.Join(" ", args); + task.PrerequisiteFiles.Add(sourceFile); + task.InfoMessage = Path.GetFileName(sourceFile); + task.Cost = task.PrerequisiteFiles.Count; // TODO: include source file size estimation to improve tasks sorting + } + + return output; } /// public override void LinkFiles(TaskGraph graph, BuildOptions options, string outputFilePath) { - throw new NotImplementedException("TODO: MacToolchain.LinkFiles"); + var linkEnvironment = options.LinkEnv; + var task = graph.Add(); + Console.WriteLine("Linking " + outputFilePath + " as " + linkEnvironment.Output); + foreach (var f in linkEnvironment.InputFiles) + Console.WriteLine(f); + + // Setup arguments + var args = new List(); + { + args.Add(string.Format("-o \"{0}\"", outputFilePath)); + + if (!options.LinkEnv.DebugInformation) + args.Add("-Wl,--strip-debug"); + + switch (linkEnvironment.Output) + { + case LinkerOutput.Executable: + case LinkerOutput.SharedLibrary: + break; + case LinkerOutput.StaticLibrary: + case LinkerOutput.ImportLibrary: + break; + default: throw new ArgumentOutOfRangeException(); + } + } + + // Input libraries + var libraryPaths = new HashSet(); + foreach (var library in linkEnvironment.InputLibraries) + { + var dir = Path.GetDirectoryName(library); + var ext = Path.GetExtension(library); + if (string.IsNullOrEmpty(dir)) + { + args.Add(string.Format("\"-l{0}\"", library)); + } + else if (string.IsNullOrEmpty(ext)) + { + // Skip executable + } + else if (ext == ".dylib") + { + // Link against dynamic library + task.PrerequisiteFiles.Add(library); + libraryPaths.Add(dir); + args.Add(string.Format("\"-l{0}\"", UnixToolchain.GetLibName(library))); + } + else + { + task.PrerequisiteFiles.Add(library); + args.Add(string.Format("\"{0}\"", UnixToolchain.GetLibName(library))); + } + } + foreach (var library in options.Libraries) + { + var dir = Path.GetDirectoryName(library); + var ext = Path.GetExtension(library); + if (string.IsNullOrEmpty(dir)) + { + args.Add(string.Format("\"-l{0}\"", library)); + } + else if (string.IsNullOrEmpty(ext)) + { + // Skip executable + } + else if (ext == ".dylib") + { + // Link against dynamic library + task.PrerequisiteFiles.Add(library); + libraryPaths.Add(dir); + args.Add(string.Format("\"-l{0}\"", UnixToolchain.GetLibName(library))); + } + else + { + task.PrerequisiteFiles.Add(library); + args.Add(string.Format("\"{0}\"", UnixToolchain.GetLibName(library))); + } + } + + // Input files + task.PrerequisiteFiles.AddRange(linkEnvironment.InputFiles); + foreach (var file in linkEnvironment.InputFiles) + { + args.Add(string.Format("\"{0}\"", file.Replace('\\', '/'))); + } + + // Additional lib paths + libraryPaths.AddRange(linkEnvironment.LibraryPaths); + foreach (var path in libraryPaths) + { + args.Add(string.Format("-L\"{0}\"", path.Replace('\\', '/'))); + } + + // Use a response file (it can contain any commands that you would specify on the command line) + bool useResponseFile = true; + string responseFile = null; + if (useResponseFile) + { + responseFile = Path.Combine(options.IntermediateFolder, Path.GetFileName(outputFilePath) + ".response"); + task.PrerequisiteFiles.Add(responseFile); + Utilities.WriteFileIfChanged(responseFile, string.Join(Environment.NewLine, args)); + } + + // Link + task.WorkingDirectory = options.WorkingDirectory; + switch (linkEnvironment.Output) + { + case LinkerOutput.Executable: + case LinkerOutput.SharedLibrary: + task.CommandPath = LinkerPath; + break; + case LinkerOutput.StaticLibrary: + case LinkerOutput.ImportLibrary: + task.CommandPath = ArchiverPath; + break; + default: throw new ArgumentOutOfRangeException(); + } + task.CommandArguments = useResponseFile ? string.Format("@\"{0}\"", responseFile) : string.Join(" ", args); + task.InfoMessage = "Linking " + outputFilePath; + task.Cost = task.PrerequisiteFiles.Count; + task.ProducedFiles.Add(outputFilePath); } } } diff --git a/Source/Tools/Flax.Build/Platforms/Unix/UnixToolchain.cs b/Source/Tools/Flax.Build/Platforms/Unix/UnixToolchain.cs index 35299b3b1..f4f4a25f2 100644 --- a/Source/Tools/Flax.Build/Platforms/Unix/UnixToolchain.cs +++ b/Source/Tools/Flax.Build/Platforms/Unix/UnixToolchain.cs @@ -111,15 +111,32 @@ namespace Flax.Build.Platforms } // Determinate compiler version - if (!File.Exists(ClangPath)) - throw new Exception(string.Format("Missing Clang ({0})", ClangPath)); + ClangVersion = GetClangVersion(ClangPath); + + // Check version + if (ClangVersion.Major < 6) + throw new Exception(string.Format("Unsupported Clang version {0}. Minimum supported is 6.", ClangVersion)); + } + + public static string GetLibName(string path) + { + var libName = Path.GetFileNameWithoutExtension(path); + if (libName.StartsWith("lib")) + libName = libName.Substring(3); + return libName; + } + + public static Version GetClangVersion(string path) + { + if (!File.Exists(path)) + throw new Exception(string.Format("Missing Clang ({0})", path)); using (var process = new Process()) { process.StartInfo.UseShellExecute = false; process.StartInfo.CreateNoWindow = true; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.RedirectStandardError = true; - process.StartInfo.FileName = ClangPath; + process.StartInfo.FileName = path; process.StartInfo.Arguments = "--version"; process.Start(); @@ -142,26 +159,11 @@ namespace Flax.Build.Platforms minor = Convert.ToInt32(parts[1]); if (parts.Length >= 3) patch = Convert.ToInt32(parts[2]); - ClangVersion = new Version(major, minor, patch); + return new Version(major, minor, patch); } } - else - { - throw new Exception(string.Format("Failed to get Clang version ({0})", ClangPath)); - } + throw new Exception(string.Format("Failed to get Clang version ({0})", path)); } - - // Check version - if (ClangVersion.Major < 6) - throw new Exception(string.Format("Unsupported Clang version {0}. Minimum supported is 6.", ClangVersion)); - } - - private static string GetLibName(string path) - { - var libName = Path.GetFileNameWithoutExtension(path); - if (libName.StartsWith("lib")) - libName = libName.Substring(3); - return libName; } /// diff --git a/Source/Tools/Flax.Build/Utilities/Utilities.cs b/Source/Tools/Flax.Build/Utilities/Utilities.cs index a59feb025..faf22221b 100644 --- a/Source/Tools/Flax.Build/Utilities/Utilities.cs +++ b/Source/Tools/Flax.Build/Utilities/Utilities.cs @@ -426,6 +426,29 @@ namespace Flax.Build return result; } + /// + /// Runs the process and reds its standard output as a string. + /// + /// The executable file path. + /// The custom arguments. + /// Returned process output. + public static string ReadProcessOutput(string filename, string args = null) + { + Process p = new Process + { + StartInfo = + { + FileName = filename, + Arguments = args, + UseShellExecute = false, + CreateNoWindow = true, + RedirectStandardOutput = true, + } + }; + p.Start(); + return p.StandardOutput.ReadToEnd().Trim(); + } + /// /// Constructs a relative path from the given base directory. ///