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

[Xamarin.Android.Build.Tasks] Add GetAndroidDependencies Target #1290

Merged
merged 11 commits into from
Feb 12, 2018
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using System;
using System.IO;
using System.Collections.Generic;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Xamarin.Android.Tools;

namespace Xamarin.Android.Tasks
{
public class CalculateProjectDependencies : Task
{
const int DefaultMinSDKVersion = 11;

[Required]
public string TargetFrameworkVersion { get; set; }

[Required]
public ITaskItem ManifestFile { get; set; }

[Required]
public string BuildToolsVersion { get; set; }

public string PlatformToolsVersion { get; set; }

public string ToolsVersion { get; set; }

public string NdkVersion { get; set; }

[Output]
public ITaskItem [] Dependencies { get; set; }

ITaskItem CreateAndroidDependency (string include, string version)
{
if (string.IsNullOrEmpty (version))
return new TaskItem (include);

return new TaskItem (include, new Dictionary<string, string> {
{ "Version", version }
});
}

public override bool Execute ()
{
var dependencies = new List<ITaskItem> ();
var targetApiLevel = MonoAndroidHelper.SupportedVersions.GetApiLevelFromFrameworkVersion (TargetFrameworkVersion);
var manifestApiLevel = DefaultMinSDKVersion;
if (File.Exists (ManifestFile.ItemSpec)) {
var manifest = AndroidAppManifest.Load (ManifestFile.ItemSpec, MonoAndroidHelper.SupportedVersions);
manifestApiLevel = manifest.TargetSdkVersion ?? manifest.MinSdkVersion ?? DefaultMinSDKVersion;
}
var sdkVersion = Math.Max (targetApiLevel.Value, manifestApiLevel);
dependencies.Add (CreateAndroidDependency ($"platforms;android-{sdkVersion}", $""));
Copy link
Member

Choose a reason for hiding this comment

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

Semicolon is a really bad choice for a separator... it has meaning in MSBuild. How about platform-android-{sdkVersion}?

dependencies.Add (CreateAndroidDependency ($"build-tools;{BuildToolsVersion}", BuildToolsVersion));
Copy link
Member

Choose a reason for hiding this comment

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

Why is the version duplicated here? Why not ItemSpec=build-tools,Version={BuildToolsVersion}

if (!string.IsNullOrEmpty (PlatformToolsVersion)) {
dependencies.Add (CreateAndroidDependency ("platform-tools", PlatformToolsVersion));
}
if (!string.IsNullOrEmpty (ToolsVersion)) {
dependencies.Add (CreateAndroidDependency ("tools", ToolsVersion));
}
if (!string.IsNullOrEmpty (NdkVersion)) {
Copy link
Member

Choose a reason for hiding this comment

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

This should only be returned if the project actually depends on the NDK.

Also, why not just use an ItemGroup in the target for this one?

Copy link
Member

Choose a reason for hiding this comment

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

In this case, using an <ItemGroup/> would complicate unit testing. @dellis1972 has discovered a wonderful way to unit test Task subclasses without using MSBuild: set the Task.BuildEngine property:

var task = new CalculateProjectDependencies {
	BuildEngine = engine,
};

By constructing it that way, you can invoke all the properties and methods "normally," without the overhead of an entire MSBuild process+context:

task.PlatformToolsVersion = "26.0.3";
task.ToolsVersion = "26.0.1";
task.NdkVersion = "12.1";
task.BuildToolsVersion = "26.0.1";
task.TargetFrameworkVersion = "v8.0";
task.ManifestFile = new TaskItem (Path.Combine (path, "AndroidManifest.xml"));
Assert.IsTrue (task.Execute ());
...

I find this hugely easier to interpret (what are the output items? What value do the metadata properties contain?) and is much faster to execute.

dependencies.Add (CreateAndroidDependency ("ndk-bundle", NdkVersion));
}
Dependencies = dependencies.ToArray ();
return !Log.HasLoggedErrors;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
using System;
using NUnit.Framework;
using Xamarin.ProjectTools;
using System.IO;
using System.Linq;
using Microsoft.Build.Framework;
using System.Text;
using Xamarin.Android.Tasks;
using Microsoft.Build.Utilities;

namespace Xamarin.Android.Build.Tests {

[TestFixture]
[Parallelizable (ParallelScope.Children)]
public class GetDependenciesTest : BaseTest {

[Test]
public void ManifestFileDoesNotExist ()
{
var path = Path.Combine ("temp", TestName);
var referencePath = CreateFauxReferencesDirectory (Path.Combine (path, "references"), new ApiInfo[] {
new ApiInfo () { Id = 26, Level = 26, Name = "Oreo", FrameworkVersion = "v8.0", Stable = true },
} );
MonoAndroidHelper.RefreshSupportedVersions (new string [] { referencePath });
IBuildEngine engine = new MockBuildEngine (TestContext.Out);
var task = new CalculateProjectDependencies {
BuildEngine = engine
};

task.PlatformToolsVersion = "26.0.3";
task.ToolsVersion = "26.0.1";
task.NdkVersion = "12.1";
task.BuildToolsVersion = "26.0.1";
task.TargetFrameworkVersion = "v8.0";
task.ManifestFile = new TaskItem (Path.Combine (path, "AndroidManifest.xml"));
Assert.IsTrue (task.Execute ());
Assert.IsNotNull (task.Dependencies);
Assert.AreEqual (5, task.Dependencies.Length);
Assert.IsNotNull (task.Dependencies.FirstOrDefault (x => x.ItemSpec == "build-tools;26.0.1" && x.GetMetadata ("Version") == "26.0.1"),
"Dependencies should contains a build-tools version 26.0.1");
Assert.IsNotNull (task.Dependencies.FirstOrDefault (x => x.ItemSpec == "tools" && x.GetMetadata ("Version") == "26.0.1"),
"Dependencies should contains a tools version 26.0.1");
Assert.IsNotNull (task.Dependencies.FirstOrDefault (x => x.ItemSpec == "platforms;android-26" && x.GetMetadata ("Version") == ""),
"Dependencies should contains a platform version android-26");
Assert.IsNotNull (task.Dependencies.FirstOrDefault (x => x.ItemSpec == "platform-tools" && x.GetMetadata ("Version") == "26.0.3"),
"Dependencies should contains a platform-tools version 26.0.3");
Assert.IsNotNull (task.Dependencies.FirstOrDefault (x => x.ItemSpec == "ndk-bundle" && x.GetMetadata ("Version") == "12.1"),
"Dependencies should contains a ndk-bundle version 12.1");
}

[Test]
public void ManifestFileExists ()
{
var path = Path.Combine (Root, "temp", TestName);
var referencePath = CreateFauxReferencesDirectory (Path.Combine (path, "references"), new ApiInfo[] {
new ApiInfo () { Id = 26, Level = 26, Name = "Oreo", FrameworkVersion = "v8.0", Stable = true },
} );
MonoAndroidHelper.RefreshSupportedVersions (new string [] { referencePath });
IBuildEngine engine = new MockBuildEngine (TestContext.Out);
var task = new CalculateProjectDependencies {
BuildEngine = engine
};


Directory.CreateDirectory (path);
var manifestFile = Path.Combine (path, "AndroidManifest.xml");
File.WriteAllText (manifestFile, @"<?xml version='1.0' ?>
<manifest xmlns:android='http://schemas.android.com/apk/res/android' android:versionCode='1' android:versionName='1.0' package='Mono.Android_Tests'>
<uses-sdk android:minSdkVersion='10' />
</manifest>");

task.PlatformToolsVersion = "26.0.3";
task.ToolsVersion = "26.0.1";
task.NdkVersion = "12.1";
task.BuildToolsVersion = "26.0.1";
task.TargetFrameworkVersion = "v8.0";
task.ManifestFile = new TaskItem (manifestFile);
Assert.IsTrue(task.Execute ());
Assert.IsNotNull (task.Dependencies);
Assert.AreEqual (5, task.Dependencies.Length);
Assert.IsNotNull (task.Dependencies.FirstOrDefault (x => x.ItemSpec == "build-tools;26.0.1" && x.GetMetadata ("Version") == "26.0.1"),
"Dependencies should contains a build-tools version 26.0.1");
Assert.IsNotNull (task.Dependencies.FirstOrDefault (x => x.ItemSpec == "tools" && x.GetMetadata ("Version") == "26.0.1"),
"Dependencies should contains a tools version 26.0.1");
Assert.IsNotNull (task.Dependencies.FirstOrDefault (x => x.ItemSpec == "platforms;android-26" && x.GetMetadata ("Version") == ""),
"Dependencies should contains a platform version android-26");
Assert.IsNotNull (task.Dependencies.FirstOrDefault (x => x.ItemSpec == "platform-tools" && x.GetMetadata ("Version") == "26.0.3"),
"Dependencies should contains a platform-tools version 26.0.3");
Assert.IsNotNull (task.Dependencies.FirstOrDefault (x => x.ItemSpec == "ndk-bundle" && x.GetMetadata ("Version") == "12.1"),
"Dependencies should contains a ndk-bundle version 12.1");

Directory.Delete (path, recursive: true);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -131,14 +131,29 @@ protected string CreateFauxAndroidSdkDirectory (string path, string buildToolsVe
return androidSdkDirectory;
}

protected string CreateFauxReferencesDirectory (string path, string[] versions)
public struct ApiInfo {
public int Id;
public int Level;
public string Name;
public string FrameworkVersion;
public bool Stable;
}

protected string CreateFauxReferencesDirectory (string path, ApiInfo [] versions)
{

string referencesDirectory = Path.Combine (Root, path);
Directory.CreateDirectory (referencesDirectory);
Directory.CreateDirectory (Path.Combine (referencesDirectory, "v1.0"));
File.WriteAllText (Path.Combine (referencesDirectory, "v1.0", "mscorlib.dll"), "");
foreach (var v in versions){
Directory.CreateDirectory (Path.Combine (referencesDirectory, v));
Directory.CreateDirectory (Path.Combine (referencesDirectory, "MonoAndroid", "v1.0"));
File.WriteAllText (Path.Combine (referencesDirectory, "MonoAndroid", "v1.0", "mscorlib.dll"), "");
foreach (var v in versions) {
Directory.CreateDirectory (Path.Combine (referencesDirectory, "MonoAndroid", v.FrameworkVersion));
Directory.CreateDirectory (Path.Combine (referencesDirectory, "MonoAndroid", v.FrameworkVersion, "RedistList"));
File.WriteAllText (Path.Combine (referencesDirectory, "MonoAndroid", v.FrameworkVersion, "MonoAndroid.dll"), "");
File.WriteAllText (Path.Combine (referencesDirectory, "MonoAndroid", v.FrameworkVersion, "AndroidApiInfo.xml"),
$"<AndroidApiInfo>\n<Id>{v.Id}</Id>\n<Level>{v.Level}</Level>\n<Name>{v.Name}</Name>\n<Version>{v.FrameworkVersion}</Version>\n<Stable>{v.Stable}</Stable>\n</AndroidApiInfo>");
File.WriteAllText (Path.Combine (referencesDirectory, "MonoAndroid", v.FrameworkVersion, "RedistList", "FrameworkList.xml"),
$"<FileList Redist=\"MonoAndroid\" Name=\"Xamarin.Android {v.FrameworkVersion} Support\" IncludeFramework=\"v1.0\"></FileList>");
}
return referencesDirectory;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Build.Framework;

namespace Xamarin.Android.Build.Tests {
public class MockBuildEngine : IBuildEngine, IBuildEngine2, IBuildEngine3, IBuildEngine4 {
public MockBuildEngine (TextWriter output)
{
this.Output = output;
}

private TextWriter Output { get; }

int IBuildEngine.ColumnNumberOfTaskNode => -1;

bool IBuildEngine.ContinueOnError => false;

int IBuildEngine.LineNumberOfTaskNode => -1;

string IBuildEngine.ProjectFileOfTaskNode => "this.xml";

bool IBuildEngine2.IsRunningMultipleNodes => false;

bool IBuildEngine.BuildProjectFile (string projectFileName, string [] targetNames, IDictionary globalProperties, IDictionary targetOutputs) => true;

void IBuildEngine.LogCustomEvent (CustomBuildEventArgs e)
{
this.Output.WriteLine ($"Custom: {e.Message}");
}

void IBuildEngine.LogErrorEvent (BuildErrorEventArgs e)
{
this.Output.WriteLine ($"Error: {e.Message}");
}

void IBuildEngine.LogMessageEvent (BuildMessageEventArgs e)
{
this.Output.WriteLine ($"Message: {e.Message}");
}

void IBuildEngine.LogWarningEvent (BuildWarningEventArgs e)
{
this.Output.WriteLine ($"Warning: {e.Message}");
}

private Dictionary<object, object> Tasks = new Dictionary<object, object> ();

void IBuildEngine4.RegisterTaskObject (object key, object obj, RegisteredTaskObjectLifetime lifetime, bool allowEarlyCollection)
{
Tasks.Add (key, obj);
}

object IBuildEngine4.GetRegisteredTaskObject (object key, RegisteredTaskObjectLifetime lifetime)
{
return null;
}

object IBuildEngine4.UnregisterTaskObject (object key, RegisteredTaskObjectLifetime lifetime)
{
var obj = Tasks [key];
Tasks.Remove (key);
return obj;
}

BuildEngineResult IBuildEngine3.BuildProjectFilesInParallel (string [] projectFileNames, string [] targetNames, IDictionary [] globalProperties, IList<string> [] removeGlobalProperties, string [] toolsVersion, bool returnTargetOutputs)
{
throw new NotImplementedException ();
}

void IBuildEngine3.Yield () { }

void IBuildEngine3.Reacquire () { }

bool IBuildEngine2.BuildProjectFile (string projectFileName, string [] targetNames, IDictionary globalProperties, IDictionary targetOutputs, string toolsVersion) => true;

bool IBuildEngine2.BuildProjectFilesInParallel (string [] projectFileNames, string [] targetNames, IDictionary [] globalProperties, IDictionary [] targetOutputsPerProject, string [] toolsVersion, bool useResultsCache, bool unloadProjectsOnCompletion) => true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@
<Compile Include="$(MSBuildThisFileDirectory)PackagingTest.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Utilities\BaseTest.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Utilities\BuildHelper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Utilities\MockBuildEngine.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="$(MSBuildThisFileDirectory)Utilities\" />
</ItemGroup>
</Project>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -72,5 +72,6 @@
<Compile Include="BuildTest.OSS.cs" />
<Compile Include="ManifestTest.OSS.cs" />
<Compile Include="AndroidRegExTests.cs" />
<Compile Include="GetDependenciesTests.cs" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@
<Compile Include="Tasks\JavaToolTask.cs" />
<Compile Include="Tasks\GenerateLayoutCodeBehind.cs" />
<Compile Include="Tasks\CalculateLayoutCodeBehind.cs" />
<Compile Include="Tasks\CalculateProjectDependencies.cs" />
<Compile Include="$(MonoSourceFullPath)\mcs\tools\pdb2mdb\BitAccess.cs">
<Link>pdb2mdb\BitAccess.cs</Link>
</Compile>
Expand Down
22 changes: 21 additions & 1 deletion src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved.
<UsingTask TaskName="Xamarin.Android.Tasks.BuildApk" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
<UsingTask TaskName="Xamarin.Android.Tasks.CalculateAdditionalResourceCacheDirectories" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
<UsingTask TaskName="Xamarin.Android.Tasks.CalculateLayoutCodeBehind" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
<UsingTask TaskName="Xamarin.Android.Tasks.CalculateProjectDependencies" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
<UsingTask TaskName="Xamarin.Android.Tasks.CheckForRemovedItems" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
<UsingTask TaskName="Xamarin.Android.Tasks.CheckTargetFrameworks" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
<UsingTask TaskName="Xamarin.Android.Tasks.CompileToDalvik" AssemblyFile="Xamarin.Android.Build.Tasks.dll" />
Expand Down Expand Up @@ -237,7 +238,7 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved.
<AndroidSdkBuildToolsVersion Condition="'$(AndroidSdkBuildToolsVersion)' == ''">27.0.3</AndroidSdkBuildToolsVersion>
<AndroidSdkPlatformToolsVersion Condition="'$(AndroidSdkPlatformToolsVersion)' == ''">27.0.1</AndroidSdkPlatformToolsVersion>
<AndroidSdkToolsVersion Condition="'$(AndroidSdkToolsVersion)' == ''">26.1.1</AndroidSdkToolsVersion>
<AndroidNdkVersion Condition="'$(AndroidNdkVersion)' == ''">r14b</AndroidNdkVersion>
<AndroidNdkVersion Condition="'$(AndroidNdkVersion)' == ''">16.1</AndroidNdkVersion>

<!-- Obsolete -->
<AndroidGdbDebugServer>None</AndroidGdbDebugServer>
Expand Down Expand Up @@ -2767,6 +2768,25 @@ because xbuild doesn't support framework reference assemblies.
DependsOnTargets="$(InstallDependsOnTargets)">
</Target>


<!-- SDK Management Targets -->
<Target Name="GetAndroidDependencies" DependsOnTargets="$(GetAndroidDependenciesDependsOn)" Returns="@(AndroidDependency)">
<PropertyGroup>
<_ProjectAndroidManifest>$(ProjectDir)$(AndroidManifest)</_ProjectAndroidManifest>
</PropertyGroup>
<Error Text="AndroidManifest file does not exist" Condition="'$(_ProjectAndroidManifest)'!='' And !Exists ('$(_ProjectAndroidManifest)')"/>
<CalculateProjectDependencies
TargetFrameworkVersion="$(TargetFrameworkVersion)"
ManifestFile="$(_ProjectAndroidManifest)"
BuildToolsVersion="$(AndroidSdkBuildToolsVersion)"
PlatformToolsVersion="$(AndroidSdkPlatformToolsVersion)"
ToolsVersion="$(AndroidSdkToolsVersion)"
NdkVersion="$(AndroidNdkVersion)"
>
<Output TaskParameter="Dependencies" ItemName="AndroidDependency" />
</CalculateProjectDependencies>
</Target>

<Import Project="$(MSBuildThisFileDirectory)Xamarin.Android.Common.Debugging.targets"
Condition="Exists('$(MSBuildThisFileDirectory)Xamarin.Android.Common.Debugging.targets')"/>

Expand Down