From 7de0e3f0ccd4111996806d8249dd7ff0c37cc998 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Tue, 11 Apr 2017 15:25:40 +0200 Subject: [PATCH] [objc] Add support fat and static libraries for all platforms. (#97) [objc] Add support fat and static libraries for all platforms. --- objcgen/driver.cs | 250 ++++++++++++++++++++++++++++++++++---- objcgen/objcgenerator.cs | 8 +- tests/objc-cli/.gitignore | 3 + tests/objc-cli/Makefile | 12 +- 4 files changed, 246 insertions(+), 27 deletions(-) diff --git a/objcgen/driver.cs b/objcgen/driver.cs index 2b92040..dbc722b 100644 --- a/objcgen/driver.cs +++ b/objcgen/driver.cs @@ -228,59 +228,263 @@ namespace Embeddinator { return 0; } + class BuildInfo + { + public string Sdk; + public string [] Architectures; + public string SdkName; // used in -m{SdkName}-version-min + public string MinVersion; + public string XamariniOSSDK; + public string CompilerFlags; + public string LinkerFlags; + } + static int Compile () { Console.WriteLine ("Compiling binding code..."); + BuildInfo [] build_infos; + switch (Platform) { case Platform.macOS: + build_infos = new BuildInfo [] { + new BuildInfo { Sdk = "MacOSX", Architectures = new string [] { "i386", "x86_64" }, SdkName = "macosx", MinVersion = "10.7" }, + }; break; case Platform.iOS: - case Platform.watchOS: + build_infos = new BuildInfo [] { + new BuildInfo { Sdk = "iPhoneOS", Architectures = new string [] { "armv7", "armv7s", "arm64" }, SdkName = "iphoneos", MinVersion = "8.0", XamariniOSSDK = "MonoTouch.iphoneos.sdk" }, + new BuildInfo { Sdk = "iPhoneSimulator", Architectures = new string [] { "i386", "x86_64" }, SdkName = "ios-simulator", MinVersion = "8.0", XamariniOSSDK = "MonoTouch.iphonesimulator.sdk" }, + }; + break; case Platform.tvOS: - throw new NotImplementedException ($"platform={Platform}"); + build_infos = new BuildInfo [] { + new BuildInfo { Sdk = "AppleTVOS", Architectures = new string [] { "arm64" }, SdkName = "tvos", MinVersion = "9.0", XamariniOSSDK = "Xamarin.AppleTVOS.sdk", CompilerFlags = "-fembed-bitcode", LinkerFlags = "-fembed-bitcode" }, + new BuildInfo { Sdk = "AppleTVSimulator", Architectures = new string [] { "x86_64" }, SdkName = "tvos-simulator", MinVersion = "9.0", XamariniOSSDK = "Xamarin.AppleTVSimulator.sdk" }, + }; + break; + case Platform.watchOS: + build_infos = new BuildInfo [] { + new BuildInfo { Sdk = "WatchOS", Architectures = new string [] { "armv7k" }, SdkName = "watchos", MinVersion = "2.0", XamariniOSSDK = "Xamarin.WatchOS.sdk", CompilerFlags = "-fembed-bitcode", LinkerFlags = "-fembed-bitcode" }, + new BuildInfo { Sdk = "WatchSimulator", Architectures = new string [] { "i386" }, SdkName = "watchos-simulator", MinVersion = "2.0", XamariniOSSDK = "Xamarin.WatchSimulator.sdk" }, + }; + break; default: throw ErrorHelper.CreateError (99, "Internal error: invalid platform {0}. Please file a bug report with a test case (https://github.com/mono/Embeddinator-4000/issues).", Platform); } switch (CompilationTarget) { case CompilationTarget.SharedLibrary: + case CompilationTarget.StaticLibrary: break; case CompilationTarget.Framework: - case CompilationTarget.StaticLibrary: throw new NotImplementedException ($"Compilation target: {CompilationTarget}"); default: throw ErrorHelper.CreateError (99, "Internal error: invalid compilation target {0}. Please file a bug report with a test case (https://github.com/mono/Embeddinator-4000/issues).", CompilationTarget); } - StringBuilder options = new StringBuilder ("clang "); - if (Debug) - options.Append ("-g -O0 "); - options.Append ("-fobjc-arc "); - options.Append ("-DMONO_EMBEDDINATOR_DLL_EXPORT "); - options.Append ("-framework CoreFoundation "); - options.Append ("-framework Foundation "); - options.Append ("-I\"/Library/Frameworks/Mono.framework/Versions/Current/include/mono-2.0\" -L\"/Library/Frameworks/Mono.framework/Versions/Current/lib/\" -lmonosgen-2.0 "); - options.Append ("glib.c mono_embeddinator.c objc-support.m bindings.m "); - options.Append ("-ObjC -lobjc "); + var lipo_files = new List (); + var output_file = string.Empty; + + var files = new string [] { + Path.Combine (OutputDirectory, "glib.c"), + Path.Combine (OutputDirectory, "mono_embeddinator.c"), + Path.Combine (OutputDirectory, "objc-support.m"), + Path.Combine (OutputDirectory, "bindings.m"), + }; + switch (CompilationTarget) { case CompilationTarget.SharedLibrary: - options.Append ($"-dynamiclib "); - options.Append ($"-install_name @rpath/lib{LibraryName}.dylib "); - options.Append ($"-o lib{LibraryName}.dylib "); + output_file = $"lib{LibraryName}.dylib"; break; case CompilationTarget.StaticLibrary: - throw new NotImplementedException ("compile to static library"); - case CompilationTarget.Framework: - throw new NotImplementedException ("compile to framework"); + output_file = $"{LibraryName}.a"; + break; default: throw ErrorHelper.CreateError (99, "Internal error: invalid compilation target {0}. Please file a bug report with a test case (https://github.com/mono/Embeddinator-4000/issues).", CompilationTarget); } - Console.WriteLine ($"\tInvoking: xcrun {options}"); - var p = Process.Start ("xcrun", options.ToString ()); - p.WaitForExit (); - return p.ExitCode; + int exitCode; + + foreach (var build_info in build_infos) { + foreach (var arch in build_info.Architectures) { + var archOutputDirectory = Path.Combine (OutputDirectory, arch); + Directory.CreateDirectory (archOutputDirectory); + + var common_options = new StringBuilder ("clang "); + if (Debug) + common_options.Append ("-g -O0 "); + common_options.Append ("-fobjc-arc "); + common_options.Append ("-ObjC "); + common_options.Append ($"-arch {arch} "); + common_options.Append ($"-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/{build_info.Sdk}.platform/Developer/SDKs/{build_info.Sdk}.sdk "); + common_options.Append ($"-m{build_info.SdkName}-version-min={build_info.MinVersion} "); + common_options.Append ("-I/Library/Frameworks/Mono.framework/Versions/Current/include/mono-2.0 "); + + // Build each file to a .o + var object_files = new List (); + foreach (var file in files) { + var compiler_options = new StringBuilder (common_options.ToString ()); + compiler_options.Append ("-DMONO_EMBEDDINATOR_DLL_EXPORT "); + compiler_options.Append (build_info.CompilerFlags).Append (" "); + compiler_options.Append ("-c "); + compiler_options.Append (Quote (file)).Append (" "); + var objfile = Path.Combine (archOutputDirectory, Path.ChangeExtension (Path.GetFileName (file), "o")); + compiler_options.Append ($"-o {Quote (objfile)} "); + object_files.Add (objfile); + if (!Xcrun (compiler_options, out exitCode)) + return exitCode; + } + + switch (CompilationTarget) { + case CompilationTarget.SharedLibrary: + // Link all the .o files into a .dylib + var options = new StringBuilder (common_options.ToString ()); + options.Append ($"-dynamiclib "); + options.Append (build_info.LinkerFlags).Append (" "); + options.Append ("-lobjc "); + options.Append ("-framework CoreFoundation "); + options.Append ("-framework Foundation "); + options.Append ($"-install_name {Quote ("@rpath/" + output_file)} "); + + foreach (var objfile in object_files) + options.Append (Quote (objfile)).Append (" "); + + var dynamic_ofile = Path.Combine (archOutputDirectory, output_file); + options.Append ($"-o ").Append (Quote (dynamic_ofile)).Append (" "); + lipo_files.Add (dynamic_ofile); + if (!string.IsNullOrEmpty (build_info.XamariniOSSDK)) { + options.Append ($"-L/Library/Frameworks/Xamarin.iOS.framework/Versions/Current/SDKs/{build_info.XamariniOSSDK}/usr/lib "); + } else { + options.Append ("-L/Library/Frameworks/Mono.framework/Versions/Current/lib/ "); + } + options.Append ("-lmonosgen-2.0 "); + if (!Xcrun (options, out exitCode)) + return exitCode; + break; + case CompilationTarget.StaticLibrary: + // Archive all the .o files into a .a + var archive_options = new StringBuilder ("ar cru "); + var static_ofile = Path.Combine (archOutputDirectory, output_file); + archive_options.Append (static_ofile).Append (" "); + lipo_files.Add (static_ofile); + foreach (var objfile in object_files) + archive_options.Append (objfile).Append (" "); + if (!Xcrun (archive_options, out exitCode)) + return exitCode; + break; + case CompilationTarget.Framework: + throw new NotImplementedException ("compile to framework"); + default: + throw ErrorHelper.CreateError (99, "Internal error: invalid compilation target {0}. Please file a bug report with a test case (https://github.com/mono/Embeddinator-4000/issues).", CompilationTarget); + } + } + } + + var output_path = Path.Combine (OutputDirectory, output_file); + if (!Lipo (lipo_files, output_path, out exitCode)) + return exitCode; + + if (!DSymUtil (output_path, out exitCode)) + return exitCode; + + return 0; + } + + static string GetTargetFramework () + { + switch (Platform) { + case Platform.macOS: + throw new NotImplementedException ("target framework for macOS"); + case Platform.iOS: + return "Xamarin.iOS,v1.0"; + case Platform.tvOS: + return "Xamarin.TVOS,v1.0"; + case Platform.watchOS: + return "Xamarin.WatchOS,v1.0"; + default: + throw ErrorHelper.CreateError (99, "Internal error: invalid platform {0}. Please file a bug report with a test case (https://github.com/mono/Embeddinator-4000/issues).", Platform); + } + } + + static int RunProcess (string filename, string arguments) + { + Console.WriteLine ($"\t{filename} {arguments}"); + using (var p = Process.Start (filename, arguments)) { + p.WaitForExit (); + return p.ExitCode; + } + } + + static bool RunProcess (string filename, string arguments, out int exitCode) + { + exitCode = RunProcess (filename, arguments); + return exitCode == 0; + } + + static bool Xcrun (StringBuilder options, out int exitCode) + { + return RunProcess ("xcrun", options.ToString (), out exitCode); + } + + static bool DSymUtil (string input, out int exitCode) + { + exitCode = 0; + + if (!Debug) + return true; + + string output; + switch (CompilationTarget) { + case CompilationTarget.StaticLibrary: + return true; + case CompilationTarget.SharedLibrary: + output = input + ".dSYM"; + break; + default: + throw ErrorHelper.CreateError (99, "Internal error: invalid compilation target {0}. Please file a bug report with a test case (https://github.com/mono/Embeddinator-4000/issues).", CompilationTarget); + } + + var dsymutil_options = new StringBuilder ("dsymutil "); + dsymutil_options.Append (Quote (input)).Append (" "); + dsymutil_options.Append ($"-o {Quote (output)} "); + return Xcrun (dsymutil_options, out exitCode); + } + + static bool Lipo (List inputs, string output, out int exitCode) + { + Directory.CreateDirectory (Path.GetDirectoryName (output)); + if (inputs.Count == 1) { + File.Copy (inputs [0], output, true); + exitCode = 0; + return true; + } else { + var lipo_options = new StringBuilder ("lipo "); + foreach (var file in inputs) + lipo_options.Append (file).Append (" "); + lipo_options.Append ("-create -output "); + lipo_options.Append (Quote (output)); + return Xcrun (lipo_options, out exitCode); + } + } + + public static string Quote (string f) + { + if (f.IndexOf (' ') == -1 && f.IndexOf ('\'') == -1 && f.IndexOf (',') == -1 && f.IndexOf ('$') == -1) + return f; + + var s = new StringBuilder (); + + s.Append ('"'); + foreach (var c in f) { + if (c == '"' || c == '\\') + s.Append ('\\'); + + s.Append (c); + } + s.Append ('"'); + + return s.ToString (); } } } diff --git a/objcgen/objcgenerator.cs b/objcgen/objcgenerator.cs index 78f892f..89ef92e 100644 --- a/objcgen/objcgenerator.cs +++ b/objcgen/objcgenerator.cs @@ -151,15 +151,17 @@ namespace ObjC { var native_name = GetTypeName (t); headers.WriteLine (); headers.WriteLine ($"// {t.AssemblyQualifiedName}"); - headers.WriteLine ($"@interface {native_name} : {GetTypeName (t.BaseType)}"); + headers.WriteLine ($"@interface {native_name} : {GetTypeName (t.BaseType)} {{"); + if (!static_type && !has_bound_base_class) { + headers.WriteLine ("\tMonoEmbedObject* _object;"); + } + headers.WriteLine ("}"); headers.WriteLine (); implementation.WriteLine (); implementation.WriteLine ($"// {t.AssemblyQualifiedName}"); implementation.WriteLine ($"@implementation {native_name} {{"); // our internal field is only needed once in the type hierarchy - if (!static_type && !has_bound_base_class) - implementation.WriteLine ("\t@public MonoEmbedObject* _object;"); implementation.WriteLine ("}"); implementation.WriteLine (); diff --git a/tests/objc-cli/.gitignore b/tests/objc-cli/.gitignore index ed9ce8f..f86a6ad 100644 --- a/tests/objc-cli/.gitignore +++ b/tests/objc-cli/.gitignore @@ -24,3 +24,6 @@ profile *.moved-aside DerivedData .idea/ +i386 +x86_64 + diff --git a/tests/objc-cli/Makefile b/tests/objc-cli/Makefile index 0e7398f..b3ebb83 100644 --- a/tests/objc-cli/Makefile +++ b/tests/objc-cli/Makefile @@ -1,4 +1,4 @@ -all: run-test perf xctest test-leaks +all: run-test perf xctest test-leaks test-static test-dynamic OBJC_GEN_DIR=../../objcgen OBJC_GEN=$(OBJC_GEN_DIR)/bin/Debug/objcgen.exe @@ -52,3 +52,13 @@ test-xctest-leaks: ../leaktest/bin/Debug/leaktest.exe $(MANAGED_DLL) libmanaged. libLeakCheckAtExit.dylib: leak-at-exit.c clang -arch i386 -arch x86_64 -shared $< -o $@ + +test-static: test-static-macos test-static-ios test-static-tvos test-static-watchos + +test-static-%: managed.dll $(OBJC_GEN) + /Library/Frameworks/Mono.framework/Versions/Current/Commands/mono --debug $(OBJC_GEN) --debug managed.dll -c --outdir=build/$@-temp-dir --target=staticlibrary --platform=$* + +test-dynamic: test-dynamic-macos test-dynamic-ios test-dynamic-tvos test-dynamic-watchos + +test-dynamic-%: managed.dll $(OBJC_GEN) + /Library/Frameworks/Mono.framework/Versions/Current/Commands/mono --debug $(OBJC_GEN) --debug managed.dll -c --outdir=build/$@-temp-dir --target=dylib --platform=$*