From 696cdd2f8ff1991b1d5d3f3856db57eb11e6beba Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 1 Jul 2020 17:07:29 +0200 Subject: [PATCH] [xharness] Add support for generating a tvOS version of .NET iOS projects. --- tests/xharness/Harness.cs | 2 +- .../Utilities/ProjectFileExtensions.cs | 56 +++++++++-- tests/xharness/Targets/MacTarget.cs | 8 +- tests/xharness/Targets/TVOSTarget.cs | 5 + tests/xharness/Targets/Target.cs | 93 +++++++++++++++---- tests/xharness/Targets/UnifiedTarget.cs | 5 + tests/xharness/Targets/WatchOSTarget.cs | 5 + tests/xharness/Targets/iOSTarget.cs | 2 +- 8 files changed, 147 insertions(+), 29 deletions(-) diff --git a/tests/xharness/Harness.cs b/tests/xharness/Harness.cs index f5b7d65afe9b..3c4f798e48cc 100644 --- a/tests/xharness/Harness.cs +++ b/tests/xharness/Harness.cs @@ -389,7 +389,7 @@ void AutoConfigureIOS () IOSTestProjects.Add (new iOSTestProject (Path.GetFullPath (Path.Combine (RootDirectory, p + "/" + p + ".fsproj")), false) { Name = p }); IOSTestProjects.Add (new iOSTestProject (Path.GetFullPath (Path.Combine (RootDirectory, "introspection", "iOS", "introspection-ios.csproj"))) { Name = "introspection" }); - IOSTestProjects.Add (new iOSTestProject (Path.GetFullPath (Path.Combine (RootDirectory, "introspection", "iOS", "introspection-ios-dotnet.csproj"))) { Name = "introspection", IsDotNetProject = true, SkipiOSVariation = false, SkiptvOSVariation = true, SkipwatchOSVariation = true, SkipTodayExtensionVariation = true, SkipDeviceVariations = true, SkipiOS32Variation = true, }); + IOSTestProjects.Add (new iOSTestProject (Path.GetFullPath (Path.Combine (RootDirectory, "introspection", "iOS", "introspection-ios-dotnet.csproj"))) { Name = "introspection", IsDotNetProject = true, SkipiOSVariation = false, SkiptvOSVariation = false, SkipwatchOSVariation = true, SkipTodayExtensionVariation = true, SkipDeviceVariations = true, SkipiOS32Variation = true, }); IOSTestProjects.Add (new iOSTestProject (Path.GetFullPath (Path.Combine (RootDirectory, "linker", "ios", "dont link", "dont link.csproj"))) { Configurations = new string [] { "Debug", "Release" } }); IOSTestProjects.Add (new iOSTestProject (Path.GetFullPath (Path.Combine (RootDirectory, "linker", "ios", "link all", "link all.csproj"))) { Configurations = new string [] { "Debug", "Release" } }); IOSTestProjects.Add (new iOSTestProject (Path.GetFullPath (Path.Combine (RootDirectory, "linker", "ios", "link sdk", "link sdk.csproj"))) { Configurations = new string [] { "Debug", "Release" } }); diff --git a/tests/xharness/Microsoft.DotNet.XHarness.iOS.Shared/Utilities/ProjectFileExtensions.cs b/tests/xharness/Microsoft.DotNet.XHarness.iOS.Shared/Utilities/ProjectFileExtensions.cs index a905313340c6..65db34b3f2e7 100644 --- a/tests/xharness/Microsoft.DotNet.XHarness.iOS.Shared/Utilities/ProjectFileExtensions.cs +++ b/tests/xharness/Microsoft.DotNet.XHarness.iOS.Shared/Utilities/ProjectFileExtensions.cs @@ -151,11 +151,14 @@ public static string GetOutputPath (this XmlDocument csproj, string platform, st return GetElementValue (csproj, platform, configuration, "OutputPath"); } - static string GetElementValue (this XmlDocument csproj, string platform, string configuration, string elementName) + static string GetElementValue (this XmlDocument csproj, string platform, string configuration, string elementName, bool throwIfNotFound = true) { var nodes = csproj.SelectNodes ($"/*/*/*[local-name() = '{elementName}']"); - if (nodes.Count == 0) - throw new Exception ($"Could not find node {elementName}"); + if (nodes.Count == 0) { + if (throwIfNotFound) + throw new Exception ($"Could not find node {elementName}"); + return null; + } foreach (XmlNode n in nodes) { if (IsNodeApplicable (n, platform, configuration)) return n.InnerText.Replace ("$(Platform)", platform).Replace ("$(Configuration)", configuration); @@ -182,6 +185,11 @@ public static string GetOutputAssemblyPath (this XmlDocument csproj, string plat return outputPath + "\\" + assemblyName + "." + extension; // MSBuild-style paths. } + public static string GetIsBindingProject (this XmlDocument csproj) + { + return GetElementValue (csproj, string.Empty, string.Empty, "IsBindingProject", throwIfNotFound: false); + } + public static void SetIntermediateOutputPath (this XmlDocument csproj, string value) { // Set any existing IntermediateOutputPath @@ -433,19 +441,21 @@ public static string GetImport (this XmlDocument csproj) } public delegate bool FixReferenceDelegate (string reference, out string fixed_reference); - public static void FixProjectReferences (this XmlDocument csproj, string suffix, FixReferenceDelegate fixCallback = null) + public static void FixProjectReferences (this XmlDocument csproj, string suffix, FixReferenceDelegate fixCallback = null, FixReferenceDelegate fixIncludeCallback = null) { var nodes = csproj.SelectNodes ("/*/*/*[local-name() = 'ProjectReference']"); if (nodes.Count == 0) return; foreach (XmlNode n in nodes) { - var name = n ["Name"].InnerText; + var name = n ["Name"]?.InnerText; string fixed_name = null; - if (fixCallback != null && !fixCallback (name, out fixed_name)) + if (name != null && fixCallback != null && !fixCallback (name, out fixed_name)) continue; var include = n.Attributes ["Include"]; string fixed_include; - if (fixed_name == null) { + if (fixIncludeCallback != null && fixIncludeCallback (include.Value, out fixed_include)) { + // we're done here + } else if (fixed_name == null) { fixed_include = include.Value; fixed_include = fixed_include.Replace (".csproj", suffix + ".csproj"); fixed_include = fixed_include.Replace (".fsproj", suffix + ".fsproj"); @@ -456,9 +466,11 @@ public static void FixProjectReferences (this XmlDocument csproj, string suffix, fixed_include = fixed_include.Replace ('/', '\\'); } n.Attributes ["Include"].Value = fixed_include; - var nameElement = n ["Name"]; - name = System.IO.Path.GetFileNameWithoutExtension (fixed_include.Replace ('\\', '/')); - nameElement.InnerText = name; + if (name != null) { + var nameElement = n ["Name"]; + name = System.IO.Path.GetFileNameWithoutExtension (fixed_include.Replace ('\\', '/')); + nameElement.InnerText = name; + } } } @@ -632,12 +644,36 @@ public static IEnumerable GetNunitAndXunitTestReferences (this XmlDocume } } + public static void SetSdk (this XmlDocument csproj, string sdk) + { + var node = csproj.SelectSingleNode ("//*[local-name() = 'Project']"); + node.Attributes ["Sdk"].Value = sdk; + } + + public static void SetRuntimeIdentifier (this XmlDocument csproj, string runtimeIdentifier) + { + var node = csproj.SelectSingleNode ("//*[local-name() = 'RuntimeIdentifier']"); + node.InnerText = runtimeIdentifier; + } + public static void SetProjectReferenceValue (this XmlDocument csproj, string projectInclude, string node, string value) { var nameNode = csproj.SelectSingleNode ("//*[local-name() = 'ProjectReference' and @Include = '" + projectInclude + "']/*[local-name() = '" + node + "']"); nameNode.InnerText = value; } + public static string GetAssetTargetFallback (this XmlDocument csproj) + { + return csproj.SelectSingleNode ("//*[local-name() = 'AssetTargetFallback']")?.InnerText; + } + + public static void SetAssetTargetFallback (this XmlDocument csproj, string value) + { + var node = csproj.SelectSingleNode ("//*[local-name() = 'AssetTargetFallback']"); + if (node != null) + node.InnerText = value; + } + public static void SetProjectReferenceInclude (this XmlDocument csproj, string projectInclude, string value) { var elements = csproj.SelectElementNodes ("ProjectReference"); diff --git a/tests/xharness/Targets/MacTarget.cs b/tests/xharness/Targets/MacTarget.cs index e0cedc248ec1..46600d86d91f 100644 --- a/tests/xharness/Targets/MacTarget.cs +++ b/tests/xharness/Targets/MacTarget.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using Microsoft.DotNet.XHarness.iOS.Shared; +using Microsoft.DotNet.XHarness.iOS.Shared.Hardware; using Microsoft.DotNet.XHarness.iOS.Shared.Utilities; namespace Xharness.Targets @@ -112,7 +113,12 @@ public override string Platform { return "mac"; } } - + + public override string DotNetSdk => "Microsoft.macOS.Sdk"; + public override string RuntimeIdentifier => "osx-x64"; + public override DevicePlatform ApplePlatform => DevicePlatform.macOS; + public override string TargetFrameworkForNuGet => "xamarinmac10"; + public MonoNativeInfo MonoNativeInfo { get; set; } protected override bool FixProjectReference (string name, out string fixed_name) diff --git a/tests/xharness/Targets/TVOSTarget.cs b/tests/xharness/Targets/TVOSTarget.cs index 1c71cad8cee9..7a2c72f7cae1 100644 --- a/tests/xharness/Targets/TVOSTarget.cs +++ b/tests/xharness/Targets/TVOSTarget.cs @@ -100,6 +100,11 @@ protected override bool SupportsBitcode { } } + public override string DotNetSdk => "Microsoft.tvOS.Sdk"; + public override string RuntimeIdentifier => "tvos-x64"; + public override DevicePlatform ApplePlatform => DevicePlatform.tvOS; + public override string TargetFrameworkForNuGet => "xamarintvos10"; + static Dictionary project_guids = new Dictionary (); protected override void ProcessProject () diff --git a/tests/xharness/Targets/Target.cs b/tests/xharness/Targets/Target.cs index ad258a5a465c..278e055f7962 100644 --- a/tests/xharness/Targets/Target.cs +++ b/tests/xharness/Targets/Target.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using System.Xml; +using Microsoft.DotNet.XHarness.iOS.Shared.Hardware; using Microsoft.DotNet.XHarness.iOS.Shared.Utilities; using Microsoft.DotNet.XHarness.iOS.Shared; @@ -26,6 +27,8 @@ public abstract class Target public string TemplateProjectPath { get; set; } + bool? is_dotnet_project; + public bool IsDotNetProject { get { return is_dotnet_project ?? (is_dotnet_project = inputProject.IsDotNetProject ()).Value; } } public string OutputType { get { return outputType; } } public string TargetDirectory { get { return targetDirectory; } } public bool IsLibrary { get { return outputType == "Library"; } } @@ -67,12 +70,55 @@ public abstract class Target public string LanguageGuid { get { return IsFSharp ? FSharpGuid : CSharpGuid; } } + public abstract string DotNetSdk { get; } + public abstract string RuntimeIdentifier { get; } + public abstract DevicePlatform ApplePlatform { get; } + public abstract string TargetFrameworkForNuGet { get; } + + public string PlatformString { + get { + switch (ApplePlatform) { + case DevicePlatform.iOS: + return "iOS"; + case DevicePlatform.tvOS: + return "tvOS"; + case DevicePlatform.watchOS: + return "watchOS"; + case DevicePlatform.macOS: + return "macOS"; + default: + throw new Exception ($"Unknown platform: {ApplePlatform}"); + } + } + } + protected virtual bool FixProjectReference (string name, out string fixed_name) { fixed_name = null; return true; } + protected virtual bool FixDotNetProjectReference (string include, out string fixed_include) + { + if (include.EndsWith ("Touch.Client-iOS.dotnet.csproj", StringComparison.Ordinal)) { + fixed_include = include.Replace ("-iOS", "-" + PlatformString); + } else { + fixed_include = include; + } + + return true; + } + + protected virtual void ProcessDotNetProject () + { + inputProject.SetSdk (DotNetSdk); + inputProject.SetRuntimeIdentifier (RuntimeIdentifier); + inputProject.FixProjectReferences (Suffix, fixIncludeCallback: FixDotNetProjectReference); + var fixedAssetTargetFallback = inputProject.GetAssetTargetFallback ()?.Replace ("xamarinios10", TargetFrameworkForNuGet); + if (fixedAssetTargetFallback != null) + inputProject.SetAssetTargetFallback (fixedAssetTargetFallback); + } + protected virtual void ProcessProject () { if (SupportsBitcode) { @@ -143,8 +189,19 @@ protected void CreateLibraryProject () ProjectGuid = inputProject.GetProjectGuid (); } + protected virtual void CreateDotNetProject () + { + ProcessDotNetProject (); + inputProject.Save (ProjectPath, (l, m) => Harness.Log (l, m)); + UpdateInfoPList (); + } + protected virtual void ExecuteInternal () { + if (IsDotNetProject) { + CreateDotNetProject (); + return; + } switch (OutputType) { case "Exe": CreateExecutableProject (); @@ -185,22 +242,26 @@ public void Execute () inputProject.LoadWithoutNetworkAccess (TemplateProjectPath); outputType = inputProject.GetOutputType (); - - switch (inputProject.GetImport ()) { - case "$(MSBuildExtensionsPath)\\Xamarin\\iOS\\Xamarin.iOS.CSharp.targets": - case "$(MSBuildExtensionsPath)\\Xamarin\\iOS\\Xamarin.iOS.FSharp.targets": - case "$(MSBuildExtensionsPath)\\Xamarin\\Mac\\Xamarin.Mac.CSharp.targets": - case "$(MSBuildExtensionsPath": - case "$(MSBuildBinPath)\\Microsoft.CSharp.targets": - IsBindingProject = false; - break; - case "$(MSBuildExtensionsPath)\\Xamarin\\iOS\\Xamarin.iOS.ObjCBinding.CSharp.targets": - case "$(MSBuildExtensionsPath)\\Xamarin\\iOS\\Xamarin.iOS.ObjCBinding.FSharp.targets": - case "$(MSBuildExtensionsPath)\\Xamarin\\Mac\\Xamarin.Mac.ObjcBinding.CSharp": - IsBindingProject = true; - break; - default: - throw new Exception (string.Format ("Unknown Imports: {0} in {1}", inputProject.GetImport (), TemplateProjectPath)); + + if (inputProject.IsDotNetProject ()) { + IsBindingProject = string.Equals (inputProject.GetIsBindingProject (), "true", StringComparison.OrdinalIgnoreCase); + } else { + switch (inputProject.GetImport ()) { + case "$(MSBuildExtensionsPath)\\Xamarin\\iOS\\Xamarin.iOS.CSharp.targets": + case "$(MSBuildExtensionsPath)\\Xamarin\\iOS\\Xamarin.iOS.FSharp.targets": + case "$(MSBuildExtensionsPath)\\Xamarin\\Mac\\Xamarin.Mac.CSharp.targets": + case "$(MSBuildExtensionsPath": + case "$(MSBuildBinPath)\\Microsoft.CSharp.targets": + IsBindingProject = false; + break; + case "$(MSBuildExtensionsPath)\\Xamarin\\iOS\\Xamarin.iOS.ObjCBinding.CSharp.targets": + case "$(MSBuildExtensionsPath)\\Xamarin\\iOS\\Xamarin.iOS.ObjCBinding.FSharp.targets": + case "$(MSBuildExtensionsPath)\\Xamarin\\Mac\\Xamarin.Mac.ObjcBinding.CSharp": + IsBindingProject = true; + break; + default: + throw new Exception (string.Format ("Unknown Imports: {0} in {1}", inputProject.GetImport (), TemplateProjectPath)); + } } ExecuteInternal (); diff --git a/tests/xharness/Targets/UnifiedTarget.cs b/tests/xharness/Targets/UnifiedTarget.cs index 1a9463d70e77..950ed20c3bcd 100644 --- a/tests/xharness/Targets/UnifiedTarget.cs +++ b/tests/xharness/Targets/UnifiedTarget.cs @@ -128,6 +128,11 @@ protected override bool SupportsBitcode { } } + public override string DotNetSdk => "Microsoft.iOS.Sdk"; + public override string RuntimeIdentifier => "ios-x64"; + public override DevicePlatform ApplePlatform => DevicePlatform.iOS; + public override string TargetFrameworkForNuGet => "xamarinios10"; + protected override void ExecuteInternal () { if (MonoNativeInfo == null) diff --git a/tests/xharness/Targets/WatchOSTarget.cs b/tests/xharness/Targets/WatchOSTarget.cs index 06ad4f15a840..db9c82472069 100644 --- a/tests/xharness/Targets/WatchOSTarget.cs +++ b/tests/xharness/Targets/WatchOSTarget.cs @@ -28,6 +28,11 @@ public override string DeviceArchitectures { get { return "ARMv7k, ARM64_32"; } } + public override string DotNetSdk => "Microsoft.watchOS.Sdk"; + public override string RuntimeIdentifier => throw new NotImplementedException (); + public override DevicePlatform ApplePlatform => DevicePlatform.watchOS; + public override string TargetFrameworkForNuGet => "xamarinwatch10"; + void CreateWatchOSAppProject () { var csproj = new XmlDocument (); diff --git a/tests/xharness/Targets/iOSTarget.cs b/tests/xharness/Targets/iOSTarget.cs index d30095240361..63dbbba4e449 100644 --- a/tests/xharness/Targets/iOSTarget.cs +++ b/tests/xharness/Targets/iOSTarget.cs @@ -6,7 +6,7 @@ namespace Xharness.Targets { // iOS here means Xamarin.iOS, not iOS as opposed to tvOS/watchOS. - public class iOSTarget : Target + public abstract class iOSTarget : Target { public iOSTestProject TestProject;