[macos] Rework framework/weak_framework handling in mmp (#1953)

- Before this mmp was not adding -framework, -weak_framework consistently on non-static registrar use cases
- GatherFrameworks was previously not ported from mtouch, and did not work as DeploymentTarget was unset in mmp
- Added verbose prints so users can determine why various framework linkages are added
- Fixed an issue where duplicate were being added due to HandleFramework shoving args by hand
- Tested with auto test and https://github.com/chamons/xm-version-regression-test manual test
This commit is contained in:
Chris Hamons 2017-04-05 14:38:40 -05:00 коммит произвёл GitHub
Родитель 35b71c096f
Коммит d20ccf5bc6
12 изменённых файлов: 267 добавлений и 94 удалений

Просмотреть файл

@ -96,6 +96,7 @@ namespace XamCore.Foundation {
static IntPtr nc = Dlfcn.dlopen (Constants.NotificationCenterLibrary, 1);
static IntPtr pl = Dlfcn.dlopen (Constants.PhotosLibrary, 1);
static IntPtr mp = Dlfcn.dlopen (Constants.MediaPlayerLibrary, 1);
static IntPtr pc = Dlfcn.dlopen (Constants.PrintCoreLibrary, 1);
#endif
// ** IF YOU ADD ITEMS HERE PLEASE UPDATE linker/ObjCExtensions.cs and mmp/linker/MonoMac.Tuner/MonoMacNamespaces.cs

Просмотреть файл

@ -95,6 +95,7 @@
<Compile Include="..\..\tools\mmp\tests\aot.cs">
<Link>unit\aot.cs</Link>
</Compile>
<Compile Include="src\FrameworkLinksTests.cs" />
</ItemGroup>
<ItemGroup>
<None Include="Info.plist" />

Просмотреть файл

@ -0,0 +1,118 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using NUnit.Framework;
using System.Reflection;
namespace Xamarin.MMP.Tests
{
public partial class MMPTests
{
const string LinkerEnabledConfig = "<LinkMode>Full</LinkMode>";
const string StaticRegistrarConfig = "<MonoBundlingExtraArgs>--registrar=static</MonoBundlingExtraArgs>";
enum LinkStatus { Strong, Weak }
Dictionary <string, LinkStatus> CalculateFrameworkLinkStatus (string[] clangParts)
{
var status = new Dictionary<string, LinkStatus> ();
for (int i = 0; i < clangParts.Length; ++i) {
string currentPart = clangParts[i];
if (currentPart == "-weak_framework" || currentPart == "-framework") {
string name = clangParts[i + 1];
status[name] = currentPart == "-framework" ? LinkStatus.Strong : LinkStatus.Weak;
}
}
return status;
}
void AssertAppKitLinkage (Dictionary<string, LinkStatus> status)
{
Assert.IsTrue (status.ContainsKey ("AppKit"), "AppKit must have framework reference in clang invocation");
Assert.AreEqual (LinkStatus.Strong, status["AppKit"], "AppKit must be strong linked");
}
void AssertFrameworkMinOSRespected (Dictionary <string, LinkStatus> status)
{
// Walk rest of frameworks and verify they are weak_framework if newer than 10.7, which is defined in tests/common/mac/Info-Unified.plist
foreach (var entry in status) {
LinkStatus linkStatus;
Framework currentFramework = Frameworks.MacFrameworks.Find (entry.Key);
if (currentFramework == null) {
// There are a few entries not in Framesworks.cs that we know about
switch (entry.Key) {
case "Carbon":
case "CoreGraphics":
case "CoreFoundation":
case "ApplicationServices":
linkStatus = LinkStatus.Strong;
break;
default:
Assert.Fail ("Unknown entry in AssertFrameworkMinOSRespected - " + entry.Key);
return;
}
}
else {
linkStatus = currentFramework.Version > new Version (10, 7) ? LinkStatus.Weak : LinkStatus.Strong;
}
Assert.AreEqual (linkStatus, entry.Value, $"Framework link status of {entry.Key} was {entry.Value} but expected to be {linkStatus}");
}
}
[Test]
public void UnifiedWithoutLinking_ShouldHaveManyFrameworkClangLines ()
{
RunMMPTest (tmpDir => {
// By default we -framework in all frameworks we bind.
string [] clangParts = GetUnifiedProjectClangInvocation (tmpDir);
AssertUnlinkedFrameworkStatus (clangParts);
// Even with static registrar
clangParts = GetUnifiedProjectClangInvocation (tmpDir, StaticRegistrarConfig);
AssertUnlinkedFrameworkStatus (clangParts);
});
}
void AssertUnlinkedFrameworkStatus (string[] clangParts)
{
Dictionary<string, LinkStatus> status = CalculateFrameworkLinkStatus (clangParts);
AssertAppKitLinkage (status);
// We expect a large number of entires, which will grow as we add more bindings
Assert.Greater (status.Count, 20, "Did not found as many framework entries in clang invocation as expected - {0}\n{1}", status.Count, string.Join (" ", clangParts));
AssertFrameworkMinOSRespected (status);
}
[Test]
public void UnifiedWithLinking_ShouldHaveFewFrameworkClangLines ()
{
RunMMPTest (tmpDir => {
// When we link, we should throw away pretty much everything that isn't AppKit.
string[] clangParts = GetUnifiedProjectClangInvocation (tmpDir, LinkerEnabledConfig);
AssertLinkedFrameworkStatus (clangParts);
// Even with static registrar
clangParts = GetUnifiedProjectClangInvocation (tmpDir, LinkerEnabledConfig + StaticRegistrarConfig);
AssertLinkedFrameworkStatus (clangParts);
});
}
void AssertLinkedFrameworkStatus (string[] clangParts)
{
Dictionary<string, LinkStatus> status = CalculateFrameworkLinkStatus (clangParts);
AssertAppKitLinkage (status);
// We expect a few number of entires, which should not grow much over time
// Today - Foundation, AppKit, Security, QuartzCore, CoreFoundation, CFNetwork, Carbon, CoreServices
Assert.Less (status.Count, 10, "Found more framework entries in clang invocation then expected - {0}\n{1}", status.Count, string.Join (" ", clangParts));
AssertFrameworkMinOSRespected (status);
}
}
}

Просмотреть файл

@ -28,6 +28,16 @@ namespace Xamarin.MMP.Tests
}
}
// TODO - We have multiple tests using this. It doesn't take that long, but is it worth caching?
string [] GetUnifiedProjectClangInvocation (string tmpDir, string projectConfig = "")
{
TI.UnifiedTestConfig test = new TI.UnifiedTestConfig (tmpDir) { CSProjConfig = projectConfig };
string buildOutput = TI.TestUnifiedExecutable (test).BuildOutput;
string [] splitBuildOutput = TI.TestUnifiedExecutable (test).BuildOutput.Split (new string[] { Environment.NewLine }, StringSplitOptions.None);
string clangInvocation = splitBuildOutput.Single (x => x.Contains ("clang"));
return clangInvocation.Split (new string[] { " " }, StringSplitOptions.None);
}
[Test]
public void CollisionsBetweenLibraryNameAndEXE_ShouldFailBuild ()
{
@ -630,10 +640,8 @@ namespace Xamarin.MMP.Tests
public void UnifiedDebug_ShouldOnlyHaveOne_ObjCArgument ()
{
RunMMPTest (tmpDir => {
TI.UnifiedTestConfig test = new TI.UnifiedTestConfig (tmpDir);
var buildOutput = TI.TestUnifiedExecutable (test).BuildOutput.Split (new string[] { Environment.NewLine }, StringSplitOptions.None);
string clangInvocation = buildOutput.Single (x => x.Contains ("clang"));
int objcCount = clangInvocation.Split (new string[] { " " }, StringSplitOptions.None).Count (x => x.Contains ("-ObjC"));
string [] clangParts = GetUnifiedProjectClangInvocation (tmpDir);
int objcCount = clangParts.Count (x => x.Contains ("-ObjC"));
Assert.AreEqual (1, objcCount, "Found more than one -OjbC");
});
}

Просмотреть файл

@ -384,6 +384,20 @@ namespace Xamarin.Bundler {
if (Frameworks.Add ("OpenAL"))
Driver.Log (3, "Linking with the framework OpenAL because {0} is referenced by a module reference in {1}", file, FileName);
break;
#if MONOMAC
case "PrintCore":
if (Frameworks.Add ("ApplicationServices"))
Driver.Log (3, "Linking with the framework ApplicationServices because {0} is referenced by a module reference in {1}", file, FileName);
break;
case "SearchKit":
if (Frameworks.Add ("CoreServices"))
Driver.Log (3, "Linking with the framework CoreServices because {0} is referenced by a module reference in {1}", file, FileName);
break;
case "CFNetwork":
if (Frameworks.Add ("CoreServices"))
Driver.Log (3, "Linking with the framework CoreServices because {0} is referenced by a module reference in {1}", file, FileName);
break;
#endif
default:
// detect frameworks
int f = name.IndexOf (".framework/", StringComparison.Ordinal);

Просмотреть файл

@ -101,5 +101,85 @@ namespace Xamarin.Bundler {
ErrorHelper.Warning (54, "Unable to canonicalize the path '{0}': {1} ({2}).", path, strerror (errno), errno);
return path;
}
public void ComputeLinkerFlags ()
{
foreach (var a in Assemblies)
a.ComputeLinkerFlags ();
#if MTOUCH
if (App.Platform != ApplePlatform.WatchOS && App.Platform != ApplePlatform.TVOS)
Frameworks.Add ("CFNetwork"); // required by xamarin_start_wwan
#endif
}
public void GatherFrameworks ()
{
AssemblyDefinition productAssembly = null;
foreach (var assembly in Assemblies) {
if (assembly.AssemblyDefinition.Name.Name == Driver.GetProductAssembly (App)) {
productAssembly = assembly.AssemblyDefinition;
break;
}
}
// *** make sure any change in the above lists (or new list) are also reflected in
// *** Makefile so simlauncher-sgen does not miss any framework
HashSet<string> processed = new HashSet<string> ();
#if !MONOMAC
Version v80 = new Version (8, 0);
#endif
foreach (ModuleDefinition md in productAssembly.Modules) {
foreach (TypeDefinition td in md.Types) {
// process only once each namespace (as we keep adding logic below)
string nspace = td.Namespace;
if (processed.Contains (nspace))
continue;
processed.Add (nspace);
Framework framework;
if (Driver.GetFrameworks (App).TryGetValue (nspace, out framework)) {
// framework specific processing
switch (framework.Name) {
#if !MONOMAC
case "CoreAudioKit":
// CoreAudioKit seems to be functional in the iOS 9 simulator.
if (App.IsSimulatorBuild && App.SdkVersion.Major < 9)
continue;
break;
case "Metal":
case "MetalKit":
case "MetalPerformanceShaders":
// some frameworks do not exists on simulators and will result in linker errors if we include them
if (App.IsSimulatorBuild)
continue;
break;
case "PushKit":
// in Xcode 6 beta 7 this became an (ld) error - it was a warning earlier :(
// ld: embedded dylibs/frameworks are only supported on iOS 8.0 and later (@rpath/PushKit.framework/PushKit) for architecture armv7
// this was fixed in Xcode 6.2 (6.1 was still buggy) see #29786
if ((App.DeploymentTarget < v80) && (Driver.XcodeVersion < new Version (6, 2))) {
ErrorHelper.Warning (49, "{0}.framework is supported only if deployment target is 8.0 or later. {0} features might not work correctly.", framework.Name);
continue;
}
break;
#endif
}
if (App.SdkVersion >= framework.Version) {
var add_to = App.DeploymentTarget >= framework.Version ? Frameworks : WeakFrameworks;
add_to.Add (framework.Name);
continue;
}
}
}
}
// Make sure there are no duplicates between frameworks and weak frameworks.
// Keep the weak ones.
Frameworks.ExceptWith (WeakFrameworks);
}
}
}

Просмотреть файл

@ -70,6 +70,7 @@ namespace Xamarin.Linker {
ScriptingBridge = profile.GetNamespace ("ScriptingBridge");
WebKit = profile.GetNamespace ("WebKit");
MediaPlayer = profile.GetNamespace ("MediaPlayer");
PrintCore = profile.GetNamespace ("PrintCore");
#else
Registrar = profile.GetNamespace ("Registrar");
UIKit = profile.GetNamespace ("UIKit");
@ -151,6 +152,7 @@ namespace Xamarin.Linker {
public static string WebKit { get; private set; }
public static string MediaPlayer { get; private set; }
public static string PrintCore { get; private set; }
#else
public static string Registrar { get; private set; }

Просмотреть файл

@ -1,7 +1,15 @@
using System;
namespace Xamarin.Bundler {
public partial class Application
{
public bool Is32Build => !Driver.Is64Bit;
public bool Is64Build => Driver.Is64Bit;
public bool Is64Build => Driver.Is64Bit;
internal void Initialize ()
{
if (DeploymentTarget == null)
DeploymentTarget = new Version (10, 7);
}
}
}

Просмотреть файл

@ -93,7 +93,6 @@ namespace Xamarin.Bundler {
static bool arch_set = false;
static string arch = "i386";
static Version minos = new Version (10, 7);
static string contents_dir;
static string frameworks_dir;
static string macos_dir;
@ -153,7 +152,7 @@ namespace Xamarin.Bundler {
public static string GetProductAssembly (Application app)
{
return "Xamarin.Mac";
return IsUnified ? "Xamarin.Mac" : "XamMac";
}
public static string GetPlatformFrameworkDirectory (Application app)
@ -269,7 +268,7 @@ namespace Xamarin.Bundler {
{ "minos=", "Minimum supported version of Mac OS X",
v => {
try {
minos = Version.Parse (v);
App.DeploymentTarget = Version.Parse (v);
} catch (Exception ex) {
ErrorHelper.Error (26, ex, "Could not parse the command line argument '{0}': {1}", "-minos", ex.Message);
}
@ -480,6 +479,7 @@ namespace Xamarin.Bundler {
ValidateXcode ();
App.Initialize ();
App.InitializeCommon ();
Log ("Xamarin.Mac {0}{1}", Constants.Version, verbose > 0 ? "." + Constants.Revision : string.Empty);
@ -778,6 +778,10 @@ namespace Xamarin.Bundler {
internalSymbols.UnionWith (BuildTarget.LinkContext.RequiredSymbols.Keys);
Watch (string.Format ("Linking (mode: '{0}')", App.LinkMode), 1);
}
// These must occur _after_ Linking
BuildTarget.ComputeLinkerFlags ();
BuildTarget.GatherFrameworks ();
if (App.MarshalObjectiveCExceptions != MarshalObjectiveCExceptionMode.Disable && !App.RequiresPInvokeWrappers && BuildTarget.Is64Build) {
internalSymbols.Add ("xamarin_dyn_objc_msgSend");
@ -1108,12 +1112,16 @@ namespace Xamarin.Bundler {
string path = Path.GetDirectoryName (framework);
if (!string.IsNullOrEmpty (path))
args.Append ("-F ").Append (Quote (path)).Append (' ');
args.Append (weak ? "-weak_framework " : "-framework ").Append (Quote (name)).Append (' ');
if (weak)
BuildTarget.WeakFrameworks.Add (name);
else
BuildTarget.Frameworks.Add (name);
if (!framework.EndsWith (".framework", StringComparison.Ordinal))
return;
// TODO - There is a chunk of code in mtouch that calls Xamarin.MachO.IsDynamicFramework and doesn't cpoy if framework of static libs
// TODO - There is a chunk of code in mtouch that calls Xamarin.MachO.IsDynamicFramework and doesn't copy if framework of static libs
// But IsDynamicFramework is not on XM yet
CreateDirectoryIfNeeded (frameworks_dir);
@ -1188,7 +1196,7 @@ namespace Xamarin.Bundler {
var args = new StringBuilder ();
if (App.EnableDebug)
args.Append ("-g ");
args.Append ("-mmacosx-version-min=").Append (minos.ToString ()).Append (' ');
args.Append ("-mmacosx-version-min=").Append (App.DeploymentTarget.ToString ()).Append (' ');
args.Append ("-arch ").Append (arch).Append (' ');
if (arch == "x86_64")
args.Append ("-fobjc-runtime=macosx ");
@ -1216,12 +1224,20 @@ namespace Xamarin.Bundler {
if (assembly.LinkerFlags != null)
foreach (var linkFlag in assembly.LinkerFlags)
args.Append (linkFlag).Append (' ');
if (assembly.Frameworks != null)
foreach (var f in assembly.Frameworks)
if (assembly.Frameworks != null) {
foreach (var f in assembly.Frameworks) {
if (verbose > 1)
Console.WriteLine ($"Adding Framework {f} for {assembly.FileName}");
HandleFramework (args, f, false);
if (assembly.WeakFrameworks != null)
foreach (var f in assembly.WeakFrameworks)
}
}
if (assembly.WeakFrameworks != null) {
foreach (var f in assembly.WeakFrameworks) {
if (verbose > 1)
Console.WriteLine ($"Adding Weak Framework {f} for {assembly.FileName}");
HandleFramework (args, f, true);
}
}
}
foreach (var framework in App.Frameworks)
@ -1272,7 +1288,7 @@ namespace Xamarin.Bundler {
args.Append ("-framework Quartz ");
}
args.Append ("-framework AppKit -liconv -x objective-c++ ");
args.Append ("-liconv -x objective-c++ ");
args.Append ("-I").Append (Quote (Path.Combine (GetXamMacPrefix (), "include"))).Append (' ');
if (registrarPath != null)
args.Append (registrarPath).Append (' ');

Просмотреть файл

@ -65,7 +65,8 @@ namespace MonoMac.Tuner {
{ Constants.NotificationCenterLibrary, Namespaces.NotificationCenter },
{ Constants.SceneKitLibrary, Namespaces.SceneKit },
{ Constants.StoreKitLibrary, Namespaces.StoreKit },
{ Constants.MediaPlayerLibrary, Namespaces.MediaPlayer } };
{ Constants.MediaPlayerLibrary, Namespaces.MediaPlayer },
{ Constants.PrintCoreLibrary, Namespaces.PrintCore} };
public void Process (LinkContext context)
{

Просмотреть файл

@ -284,15 +284,6 @@ namespace Xamarin.Bundler
return assemblies;
}
public void ComputeLinkerFlags ()
{
foreach (var a in Assemblies)
a.ComputeLinkerFlags ();
if (App.Platform != ApplePlatform.WatchOS && App.Platform != ApplePlatform.TVOS)
Frameworks.Add ("CFNetwork"); // required by xamarin_start_wwan
}
Dictionary<string, List<MemberReference>> entry_points;
public IDictionary<string, List<MemberReference>> GetEntryPoints ()
{
@ -853,11 +844,7 @@ namespace Xamarin.Bundler
ManagedLink ();
Driver.GatherFrameworks (this, Frameworks, WeakFrameworks);
// Make sure there are no duplicates between frameworks and weak frameworks.
// Keep the weak ones.
Frameworks.ExceptWith (WeakFrameworks);
GatherFrameworks ();
}
public void CompilePInvokeWrappers ()

Просмотреть файл

@ -837,69 +837,6 @@ namespace Xamarin.Bundler
return s.ToString ();
}
public static void GatherFrameworks (Target target, HashSet<string> frameworks, HashSet<string> weak_frameworks)
{
var app = target.App;
AssemblyDefinition monotouch = null;
foreach (var assembly in target.Assemblies) {
if (assembly.AssemblyDefinition.FullName == target.ProductAssembly.FullName) {
monotouch = assembly.AssemblyDefinition;
break;
}
}
// *** make sure any change in the above lists (or new list) are also reflected in
// *** Makefile so simlauncher-sgen does not miss any framework
HashSet<string> processed = new HashSet<string> ();
Version v80 = new Version (8, 0);
foreach (ModuleDefinition md in monotouch.Modules) {
foreach (TypeDefinition td in md.Types) {
// process only once each namespace (as we keep adding logic below)
string nspace = td.Namespace;
if (processed.Contains (nspace))
continue;
processed.Add (nspace);
Framework framework;
if (Driver.GetFrameworks (app).TryGetValue (nspace, out framework)) {
// framework specific processing
switch (framework.Name) {
case "CoreAudioKit":
// CoreAudioKit seems to be functional in the iOS 9 simulator.
if (app.IsSimulatorBuild && app.SdkVersion.Major < 9)
continue;
break;
case "Metal":
case "MetalKit":
case "MetalPerformanceShaders":
// some frameworks do not exists on simulators and will result in linker errors if we include them
if (app.IsSimulatorBuild)
continue;
break;
case "PushKit":
// in Xcode 6 beta 7 this became an (ld) error - it was a warning earlier :(
// ld: embedded dylibs/frameworks are only supported on iOS 8.0 and later (@rpath/PushKit.framework/PushKit) for architecture armv7
// this was fixed in Xcode 6.2 (6.1 was still buggy) see #29786
if ((app.DeploymentTarget < v80) && (XcodeVersion < new Version (6, 2))) {
ErrorHelper.Warning (49, "{0}.framework is supported only if deployment target is 8.0 or later. {0} features might not work correctly.", framework.Name);
continue;
}
break;
}
if (app.SdkVersion >= framework.Version) {
var add_to = app.DeploymentTarget >= framework.Version ? frameworks : weak_frameworks;
add_to.Add (framework.Name);
continue;
}
}
}
}
}
public static bool CanWeSymlinkTheApplication (Application app)
{
if (app.Platform != ApplePlatform.iOS)