[msbuild] Make sure all the Base/Core tasks are abstract. (#8888)
Also add a cecil test ensure our leaf MSBuild tasks don't have code.
This commit is contained in:
Родитель
76a61f1290
Коммит
b39bd0e420
|
@ -2,7 +2,7 @@ using System.Text;
|
||||||
|
|
||||||
namespace Xamarin.Mac.Tasks
|
namespace Xamarin.Mac.Tasks
|
||||||
{
|
{
|
||||||
public class ALToolUploadTaskBase : MacDev.Tasks.ALToolTaskBase
|
public abstract class ALToolUploadTaskBase : MacDev.Tasks.ALToolTaskBase
|
||||||
{
|
{
|
||||||
protected override string GenerateCommandLineCommands ()
|
protected override string GenerateCommandLineCommands ()
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,7 +2,7 @@ using System.Text;
|
||||||
|
|
||||||
namespace Xamarin.Mac.Tasks
|
namespace Xamarin.Mac.Tasks
|
||||||
{
|
{
|
||||||
public class ALToolValidateTaskBase : MacDev.Tasks.ALToolTaskBase
|
public abstract class ALToolValidateTaskBase : MacDev.Tasks.ALToolTaskBase
|
||||||
{
|
{
|
||||||
protected override string GenerateCommandLineCommands ()
|
protected override string GenerateCommandLineCommands ()
|
||||||
{
|
{
|
||||||
|
|
|
@ -11,7 +11,7 @@ using Xamarin.Localization.MSBuild;
|
||||||
|
|
||||||
namespace Xamarin.Mac.Tasks
|
namespace Xamarin.Mac.Tasks
|
||||||
{
|
{
|
||||||
public class EmbedProvisionProfileTaskBase : XamarinTask
|
public abstract class EmbedProvisionProfileTaskBase : XamarinTask
|
||||||
{
|
{
|
||||||
#region Inputs
|
#region Inputs
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ using Xamarin.Localization.MSBuild;
|
||||||
|
|
||||||
namespace Xamarin.Mac.Tasks
|
namespace Xamarin.Mac.Tasks
|
||||||
{
|
{
|
||||||
public class MmpTaskBase : BundlerToolTaskBase
|
public abstract class MmpTaskBase : BundlerToolTaskBase
|
||||||
{
|
{
|
||||||
protected override string ToolName {
|
protected override string ToolName {
|
||||||
get { return "mmp"; }
|
get { return "mmp"; }
|
||||||
|
|
|
@ -9,7 +9,7 @@ using Xamarin.Localization.MSBuild;
|
||||||
|
|
||||||
namespace Xamarin.iOS.Tasks
|
namespace Xamarin.iOS.Tasks
|
||||||
{
|
{
|
||||||
public class CollectFrameworksBase : XamarinTask
|
public abstract class CollectFrameworksBase : XamarinTask
|
||||||
{
|
{
|
||||||
#region Inputs
|
#region Inputs
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ using Xamarin.Localization.MSBuild;
|
||||||
|
|
||||||
namespace Xamarin.MacDev.Tasks
|
namespace Xamarin.MacDev.Tasks
|
||||||
{
|
{
|
||||||
public class CreateInstallerPackageTaskBase : XamarinToolTask
|
public abstract class CreateInstallerPackageTaskBase : XamarinToolTask
|
||||||
{
|
{
|
||||||
#region Inputs
|
#region Inputs
|
||||||
[Required]
|
[Required]
|
||||||
|
|
|
@ -2,7 +2,7 @@ using System.Text;
|
||||||
|
|
||||||
namespace Xamarin.iOS.Tasks
|
namespace Xamarin.iOS.Tasks
|
||||||
{
|
{
|
||||||
public class ALToolUploadTaskBase : MacDev.Tasks.ALToolTaskBase
|
public abstract class ALToolUploadTaskBase : MacDev.Tasks.ALToolTaskBase
|
||||||
{
|
{
|
||||||
protected override string GenerateCommandLineCommands ()
|
protected override string GenerateCommandLineCommands ()
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,7 +2,7 @@ using System.Text;
|
||||||
|
|
||||||
namespace Xamarin.iOS.Tasks
|
namespace Xamarin.iOS.Tasks
|
||||||
{
|
{
|
||||||
public class ALToolValidateTaskBase : MacDev.Tasks.ALToolTaskBase
|
public abstract class ALToolValidateTaskBase : MacDev.Tasks.ALToolTaskBase
|
||||||
{
|
{
|
||||||
protected override string GenerateCommandLineCommands ()
|
protected override string GenerateCommandLineCommands ()
|
||||||
{
|
{
|
||||||
|
|
|
@ -10,7 +10,7 @@ using Xamarin.MacDev.Tasks;
|
||||||
|
|
||||||
namespace Xamarin.iOS.Tasks
|
namespace Xamarin.iOS.Tasks
|
||||||
{
|
{
|
||||||
public class ArchiveTaskBase : Xamarin.MacDev.Tasks.ArchiveTaskBase
|
public abstract class ArchiveTaskBase : Xamarin.MacDev.Tasks.ArchiveTaskBase
|
||||||
{
|
{
|
||||||
[Required]
|
[Required]
|
||||||
public ITaskItem[] ITunesSourceFiles { get; set; }
|
public ITaskItem[] ITunesSourceFiles { get; set; }
|
||||||
|
|
|
@ -9,7 +9,7 @@ using Xamarin.MacDev.Tasks;
|
||||||
|
|
||||||
namespace Xamarin.iOS.Tasks
|
namespace Xamarin.iOS.Tasks
|
||||||
{
|
{
|
||||||
public class CollectAssetPacksTaskBase : XamarinTask
|
public abstract class CollectAssetPacksTaskBase : XamarinTask
|
||||||
{
|
{
|
||||||
#region Inputs
|
#region Inputs
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ using Xamarin.MacDev.Tasks;
|
||||||
|
|
||||||
namespace Xamarin.iOS.Tasks
|
namespace Xamarin.iOS.Tasks
|
||||||
{
|
{
|
||||||
public class CollectITunesSourceFilesTaskBase : XamarinTask
|
public abstract class CollectITunesSourceFilesTaskBase : XamarinTask
|
||||||
{
|
{
|
||||||
static readonly string[] iTunesFileNames = { "iTunesMetadata.plist", "iTunesArtwork@2x", "iTunesArtwork" };
|
static readonly string[] iTunesFileNames = { "iTunesMetadata.plist", "iTunesArtwork@2x", "iTunesArtwork" };
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ using Xamarin.MacDev.Tasks;
|
||||||
|
|
||||||
namespace Xamarin.iOS.Tasks
|
namespace Xamarin.iOS.Tasks
|
||||||
{
|
{
|
||||||
public class CreateAssetPackTaskBase : XamarinToolTask
|
public abstract class CreateAssetPackTaskBase : XamarinToolTask
|
||||||
{
|
{
|
||||||
#region Inputs
|
#region Inputs
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ using Xamarin.MacDev.Tasks;
|
||||||
|
|
||||||
namespace Xamarin.iOS.Tasks
|
namespace Xamarin.iOS.Tasks
|
||||||
{
|
{
|
||||||
public class WriteAssetPackManifestTaskBase : XamarinTask
|
public abstract class WriteAssetPackManifestTaskBase : XamarinTask
|
||||||
{
|
{
|
||||||
#region Inputs
|
#region Inputs
|
||||||
|
|
||||||
|
|
|
@ -18,12 +18,16 @@ namespace Cecil.Tests {
|
||||||
static Dictionary<string, AssemblyDefinition> cache = new Dictionary<string, AssemblyDefinition> ();
|
static Dictionary<string, AssemblyDefinition> cache = new Dictionary<string, AssemblyDefinition> ();
|
||||||
|
|
||||||
// make sure we load assemblies only once into memory
|
// make sure we load assemblies only once into memory
|
||||||
public static AssemblyDefinition? GetAssembly (string assembly)
|
public static AssemblyDefinition? GetAssembly (string assembly, ReaderParameters? parameters = null)
|
||||||
{
|
{
|
||||||
if (!File.Exists (assembly))
|
if (!File.Exists (assembly))
|
||||||
return null;
|
return null;
|
||||||
if (!cache.TryGetValue (assembly, out var ad)) {
|
if (!cache.TryGetValue (assembly, out var ad)) {
|
||||||
|
if (parameters == null) {
|
||||||
ad = AssemblyDefinition.ReadAssembly (assembly);
|
ad = AssemblyDefinition.ReadAssembly (assembly);
|
||||||
|
} else {
|
||||||
|
ad = AssemblyDefinition.ReadAssembly (assembly, parameters);
|
||||||
|
}
|
||||||
cache.Add (assembly, ad);
|
cache.Add (assembly, ad);
|
||||||
}
|
}
|
||||||
return ad;
|
return ad;
|
||||||
|
@ -72,5 +76,19 @@ namespace Cecil.Tests {
|
||||||
yield return new TestCaseData (Configuration.XamarinMacFullDll);
|
yield return new TestCaseData (Configuration.XamarinMacFullDll);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IEnumerable TaskAssemblies {
|
||||||
|
get {
|
||||||
|
yield return CreateTestFixtureDataFromPath (Path.Combine (Configuration.SdkRootXI, "lib", "msbuild", "iOS", "Xamarin.iOS.Tasks.dll"));
|
||||||
|
yield return CreateTestFixtureDataFromPath (Path.Combine (Configuration.SdkRootXM, "lib", "msbuild", "Xamarin.Mac.Tasks.dll"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static TestFixtureData CreateTestFixtureDataFromPath (string path)
|
||||||
|
{
|
||||||
|
var rv = new TestFixtureData (path);
|
||||||
|
rv.SetArgDisplayNames (Path.GetFileName (path));
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,169 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
using Mono.Cecil;
|
||||||
|
using Mono.Cecil.Cil;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
namespace Cecil.Tests {
|
||||||
|
[TestFixture]
|
||||||
|
[TestFixtureSource (typeof (Helper), "TaskAssemblies")]
|
||||||
|
public class TaskAssemblyTests {
|
||||||
|
string assembly;
|
||||||
|
|
||||||
|
public TaskAssemblyTests (string assembly)
|
||||||
|
{
|
||||||
|
this.assembly = assembly;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsTaskType (TypeDefinition? td)
|
||||||
|
{
|
||||||
|
if (td == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (td.HasInterfaces) {
|
||||||
|
foreach (var iface in td.Interfaces) {
|
||||||
|
if (iface.InterfaceType.Namespace == "Microsoft.Build.Framework" && iface.InterfaceType.Name == "ITask")
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return IsTaskType (td.BaseType?.Resolve ());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void EnsureOnlyCodeInBaseTasks ()
|
||||||
|
{
|
||||||
|
var parameters = new ReaderParameters (ReadingMode.Deferred);
|
||||||
|
var resolver = new DefaultAssemblyResolver ();
|
||||||
|
resolver.AddSearchDirectory ("/Library/Frameworks/Mono.framework/Versions/Current/lib/mono/msbuild/Current/bin");
|
||||||
|
parameters.AssemblyResolver = resolver;
|
||||||
|
|
||||||
|
var asm = Helper.GetAssembly (assembly, parameters)!;
|
||||||
|
var checking_types = new List<TypeDefinition> ();
|
||||||
|
var should_be_abstract = new List<string> ();
|
||||||
|
foreach (var type in asm.MainModule.Types.OrderBy (v => v.FullName)) {
|
||||||
|
if (!IsTaskType (type)) {
|
||||||
|
// Console.WriteLine ($"ℹ️ {type.FullName} does not implement ITask");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type.IsAbstract)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// All the Base and Core classes should be abstract
|
||||||
|
if (type.Name.EndsWith ("Base", StringComparison.Ordinal) || type.Name.EndsWith ("Core", StringComparison.Ordinal)) {
|
||||||
|
should_be_abstract.Add (type.FullName);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
checking_types.Add (type);
|
||||||
|
}
|
||||||
|
|
||||||
|
var failures = new List<string> ();
|
||||||
|
foreach (var type in checking_types) {
|
||||||
|
// We don't care about default constructors
|
||||||
|
var methods = type.Methods.Where (v => {
|
||||||
|
if (v.IsConstructor && v.Parameters.Count == 0 && v.HasBody) {
|
||||||
|
var skipNop = new Func<Instruction, Instruction?> (v => {
|
||||||
|
if (v == null)
|
||||||
|
return null;
|
||||||
|
while (v.OpCode.Code == Code.Nop)
|
||||||
|
v = v.Next;
|
||||||
|
return v;
|
||||||
|
});
|
||||||
|
// There should be this sequence of instructions, otherwise the default constructor has user code, and shouldn't be ignored:
|
||||||
|
var ins = skipNop (v.Body.Instructions [0]);
|
||||||
|
if (ins?.OpCode.Code != Code.Ldarg_0)
|
||||||
|
return true;
|
||||||
|
ins = skipNop (ins.Next);
|
||||||
|
if (ins?.OpCode.Code != Code.Call)
|
||||||
|
return true;
|
||||||
|
ins = skipNop (ins.Next);
|
||||||
|
if (ins?.OpCode.Code != Code.Ret)
|
||||||
|
return true;
|
||||||
|
ins = skipNop (ins.Next);
|
||||||
|
if (ins != null)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
var failed = methods.Any () || type.HasProperties || type.HasFields;
|
||||||
|
var known_failure = IsKnownFailure (type.FullName);
|
||||||
|
|
||||||
|
if (!failed) {
|
||||||
|
if (known_failure) {
|
||||||
|
failures.Add ($"{type.FullName} is marked as a known failure when it didn't fail. Maybe the list of known failures need to be updated?");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
continue; // We successfully succeeded!
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failed && known_failure) {
|
||||||
|
Console.WriteLine ($"⚠️ {type.FullName} is known failure");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We failed, and this type is not a known failure
|
||||||
|
var sb = new StringBuilder ();
|
||||||
|
sb.AppendLine ($"{type.FullName} is not an empty type:");
|
||||||
|
if (methods.Any ()) {
|
||||||
|
sb.AppendLine ($" It has {methods.Count ()} methods:");
|
||||||
|
foreach (var method in methods)
|
||||||
|
sb.AppendLine ($" {method.FullName}");
|
||||||
|
}
|
||||||
|
if (type.HasProperties) {
|
||||||
|
sb.AppendLine ($" It has {type.Properties.Count} properties:");
|
||||||
|
foreach (var property in type.Properties)
|
||||||
|
sb.AppendLine ($" {property.Name}");
|
||||||
|
}
|
||||||
|
if (type.HasProperties) {
|
||||||
|
sb.AppendLine ($" It has {type.Fields.Count} fields:");
|
||||||
|
foreach (var field in type.Fields)
|
||||||
|
sb.AppendLine ($" {field.Name}");
|
||||||
|
}
|
||||||
|
failures.Add (sb.ToString ());
|
||||||
|
Console.WriteLine ($"❌ {sb}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.That (failures, Is.Empty, "Types with code");
|
||||||
|
Assert.That (checking_types.Count, Is.AtLeast (50), "Checked types"); // Make sure the initial type filtering doesn't filter away too much by mistake
|
||||||
|
Assert.That (should_be_abstract, Is.Empty, "Classes that should be abstract");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsKnownFailure (string typename)
|
||||||
|
{
|
||||||
|
switch (typename) {
|
||||||
|
case "Xamarin.MacDev.Tasks.UnpackLibraryResources":
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (Path.GetFileNameWithoutExtension (assembly)) {
|
||||||
|
case "Xamarin.iOS.Tasks":
|
||||||
|
// No other known failures
|
||||||
|
return false;
|
||||||
|
case "Xamarin.Mac.Tasks":
|
||||||
|
switch (typename) {
|
||||||
|
case "Xamarin.Mac.Tasks.CodesignVerify":
|
||||||
|
case "Xamarin.Mac.Tasks.CompileEntitlements":
|
||||||
|
case "Xamarin.Mac.Tasks.IBTool":
|
||||||
|
case "Xamarin.Mac.Tasks.Metal":
|
||||||
|
case "Xamarin.Mac.Tasks.MetalLib":
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
throw new NotImplementedException ($"Unknown assembly: {assembly}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Загрузка…
Ссылка в новой задаче