Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[xharness] Add support for generating a tvOS version of .NET iOS projects. #9032

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion tests/xharness/Harness.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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" } });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,24 @@ public enum DevicePlatform {
macOS,
}

public static class DevicePlatform_Extensions {
public static string AsString (this DevicePlatform value)
{
switch (value) {
case DevicePlatform.iOS:
return "iOS";
case DevicePlatform.tvOS:
return "tvOS";
case DevicePlatform.watchOS:
return "watchOS";
case DevicePlatform.macOS:
return "macOS";
default:
throw new System.Exception ($"Unknown platform: {value}");
}
}
}

public interface IDevice {
string Name { get; }
string UDID { get; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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
Expand Down Expand Up @@ -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");
Expand All @@ -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;
}
}
}

Expand Down Expand Up @@ -632,12 +644,43 @@ public static IEnumerable<string> GetNunitAndXunitTestReferences (this XmlDocume
}
}

public static void SetSdk (this XmlDocument csproj, string sdk)
{
var node = csproj.SelectSingleNode ("//*[local-name() = 'Project']");
if (node == null)
throw new Exception ($"Could not find a 'Project' node");
var attrib = node.Attributes ["Sdk"];
if (attrib == null)
throw new Exception ($"The 'Project' node doesn't have an 'Sdk' attribute");
attrib.Value = sdk;
}

public static void SetRuntimeIdentifier (this XmlDocument csproj, string runtimeIdentifier)
{
var node = csproj.SelectSingleNode ("//*[local-name() = 'RuntimeIdentifier']");
if (node == null)
throw new Exception ($"Could not find a 'RuntimeIdentifier' node");
node.InnerText = runtimeIdentifier;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

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");
Expand Down
8 changes: 7 additions & 1 deletion tests/xharness/Targets/MacTarget.cs
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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)
Expand Down
5 changes: 5 additions & 0 deletions tests/xharness/Targets/TVOSTarget.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string> project_guids = new Dictionary<string, string> ();

protected override void ProcessProject ()
Expand Down
82 changes: 66 additions & 16 deletions tests/xharness/Targets/Target.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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"; } }
Expand Down Expand Up @@ -67,12 +70,44 @@ 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 {
return ApplePlatform.AsString ();
}
}

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) {
Expand Down Expand Up @@ -143,8 +178,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 ();
Expand Down Expand Up @@ -185,22 +231,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 ();
Expand Down
5 changes: 5 additions & 0 deletions tests/xharness/Targets/UnifiedTarget.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
5 changes: 5 additions & 0 deletions tests/xharness/Targets/WatchOSTarget.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 ();
Expand Down
2 changes: 1 addition & 1 deletion tests/xharness/Targets/iOSTarget.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down