Skip to content
Permalink
Browse files

Implement bootstrap as MSBuild task

Motivation
----------
Improve performance by running bootstrap in-process and will make
implementing .NET Core builds (#18) easier.

Modifications
-------------
Add Aspekt.Bootstrap.Tasks assembly, and change Aspekt NuGet package
to use this task instead of Aspekt.Bootstrap.Host.

Add build property to allow consumers to disable automatic bootstrap,
as well as some points for build customization.

Update Bootstrap to accept a list of referenced assemblies, which are
resolved using PreregisteredAssemblyResolver. This supports bootstrap
in the obj folder instead of the bin folder, accepting the list of
assemblies located in other folders from MSBuild.

Results
--------
Consumers have the bootstrap performing in-process. Still only
works on MSBuild Full flavor, not the MSBuild Core flavor.

*Note:* Test assemblies are still using Aspekt.Bootstrap.Host due to
problems with file locks being held by MSBuild in Visual Studio.
  • Loading branch information...
brantburnett committed Mar 3, 2019
1 parent a8a421d commit f8a4cc1c293401cb410200b0fe41c42a01c06940
@@ -1,10 +1,12 @@
using System;

namespace Aspekt.Bootstrap.Host
{
public class Program
{
public static void Main(string[] args)
{
Bootstrap.Apply(args[0]);
Bootstrap.Apply(args[0], new ReferencedAssembly[] {});
}
}
}
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net46</TargetFrameworks>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Build.Framework" Version="15.1.1012" />
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="15.1.1012" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Aspekt.Bootstrap\Aspekt.Bootstrap.csproj" />
<ProjectReference Include="..\Aspekt\Aspekt.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,54 @@
using System;
using System.Linq;
using Microsoft.Build.Framework;

namespace Aspekt.Bootstrap.Tasks
{
public class AspektBootstrap : Microsoft.Build.Utilities.Task
{
[Required]
public ITaskItem[] Assemblies { get; set; }

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

public override bool Execute()
{
var success = true;

foreach (var item in Assemblies)
{
var assemblyPath = item.GetMetadata("FullPath");

try
{
Log.LogMessage(MessageImportance.Low, "Aspekt bootstrapping \"{0}\"", assemblyPath);

Bootstrap.Apply(assemblyPath,
References
.Select(p => new ReferencedAssembly
{
FilePath = p.ItemSpec,
FullName = p.GetMetadata("FusionName")
}));

Log.LogMessage(MessageImportance.High, "Aspekt bootstrapped \"{0}\"", assemblyPath);
}
catch (Exception ex)
{
#if DEBUG
var showStackTrace = true;
#else
var showStackTrace = false;
#endif

// ReSharper disable once ConditionIsAlwaysTrueOrFalse
Log.LogErrorFromException(ex, showStackTrace, true, assemblyPath);
success = false;
}
}

return success;
}
}
}
@@ -2,6 +2,7 @@
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

@@ -13,13 +14,23 @@ public static class Bootstrap
// Generating IL


public static void Apply(string targetFileName)
public static void Apply(string targetFileName, IEnumerable<ReferencedAssembly> referencedAssemblies)
{
var targetOutputName = Path.ChangeExtension(targetFileName, "tmp");
var rp = new ReaderParameters { ReadSymbols = true, ReadWrite = true };
using (var assembly = AssemblyDefinition.ReadAssembly(targetFileName, rp))
var assemblyResolver = new PreregisteredAssemblyResolver();
foreach (var referencedAssembly in referencedAssemblies)
{
assemblyResolver.PreregisterAssembly(referencedAssembly);
}

var rp = new ReaderParameters
{
ReadSymbols = true,
ReadWrite = true,
AssemblyResolver = assemblyResolver
};

using (var assembly = AssemblyDefinition.ReadAssembly(targetFileName, rp))
{
// I know that right now, if we have multiple attributes, we're going to generate multiple method arguments.
// I know how to deal with this.
AttributeEnumerator.EnumerateMethodAttributes(assembly, (classType, target, attr) =>
@@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using Mono.Cecil;

namespace Aspekt.Bootstrap
{
public class PreregisteredAssemblyResolver : DefaultAssemblyResolver
{
private readonly Dictionary<string, string> preregistered_ = new Dictionary<string, string>();

protected override AssemblyDefinition SearchDirectory(AssemblyNameReference name, IEnumerable<string> directories, ReaderParameters parameters)
{
if (preregistered_.TryGetValue(name.Name, out var filePath))
{
if (parameters.AssemblyResolver == null)
{
parameters.AssemblyResolver = this;
}

var module = ModuleDefinition.ReadModule(filePath, parameters);

return module.Assembly;
}

return base.SearchDirectory(name, directories, parameters);
}

public void PreregisterAssembly(ReferencedAssembly referencedAssembly)
{
if (string.IsNullOrEmpty(referencedAssembly.FullName))
{
return;
}

var simpleName = GetSimpleName(referencedAssembly.FullName);
if (!string.IsNullOrEmpty(simpleName) &&
!preregistered_.ContainsKey(simpleName))
{
preregistered_.Add(simpleName, referencedAssembly.FilePath);
}
}

private static string GetSimpleName(string fullName)
{
return fullName.Split(',')[0].Trim();
}
}
}
@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Aspekt.Bootstrap
{
public class ReferencedAssembly
{
public string FullName { get; set; }
public string FilePath { get; set; }
}
}
@@ -25,10 +25,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspekt.Contracts", "Aspekt.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspekt.Logging.Test", "Aspekt.Logging.Test\Aspekt.Logging.Test.csproj", "{C4AACDE6-B8BE-4BC5-BB67-7917F02F0B6F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspekt.Foundation.Test", "Aspekt.Foundation.Test\Aspekt.Foundation.Test.csproj", "{FD868722-056B-42BA-A7AB-7F3E2B61F827}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspekt.Foundation.Test", "Aspekt.Foundation.Test\Aspekt.Foundation.Test.csproj", "{FD868722-056B-42BA-A7AB-7F3E2B61F827}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspekt.Bootstrap.Test", "Aspekt.Bootstrap.Test\Aspekt.Bootstrap.Test.csproj", "{C42ADB9D-3748-43FA-911B-9207521ADD50}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspekt.Bootstrap.Tasks", "Aspekt.Bootstrap.Tasks\Aspekt.Bootstrap.Tasks.csproj", "{E2A9BDBF-3C5F-48FE-B23E-4A99D3463240}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -75,6 +77,10 @@ Global
{C42ADB9D-3748-43FA-911B-9207521ADD50}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C42ADB9D-3748-43FA-911B-9207521ADD50}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C42ADB9D-3748-43FA-911B-9207521ADD50}.Release|Any CPU.Build.0 = Release|Any CPU
{E2A9BDBF-3C5F-48FE-B23E-4A99D3463240}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E2A9BDBF-3C5F-48FE-B23E-4A99D3463240}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E2A9BDBF-3C5F-48FE-B23E-4A99D3463240}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E2A9BDBF-3C5F-48FE-B23E-4A99D3463240}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net452;net461;netstandard2.0</TargetFrameworks>
@@ -12,17 +12,21 @@
<PackageTags>AOP;Aspect Oriented Programming</PackageTags>
<RepositoryUrl>http://github.com/mvpete/aspekt</RepositoryUrl>

<BootstrapHostFolder>..\Aspekt.Bootstrap.Host\bin\$(Configuration)\net452\</BootstrapHostFolder>
<BootstrapTaskOutputFolder>..\Aspekt.Bootstrap.Tasks\bin\$(Configuration)\</BootstrapTaskOutputFolder>
<NoWarn>$(NoWarn);NU5100</NoWarn>
</PropertyGroup>

<ItemGroup>
<None Remove="*.targets" />
<Content Include="*.targets">
<None Remove="**\*.targets;**\*.props" />
<Content Include="build\*.targets;build\*.props">
<PackagePath>build</PackagePath>
</Content>
<Content Include="$(BootstrapHostFolder)*.exe;$(BootstrapHostFolder)*.dll">
<Link>tools\%(Filename)%(Extension)</Link>
<PackagePath>tools</PackagePath>
<Content Include="buildMultiTargeting\*.targets;buildMultiTargeting\*.props">
<PackagePath>buildMultiTargeting</PackagePath>
</Content>
<Content Include="$(BootstrapTaskOutputFolder)net46\*.dll" Visible="false">
<PackagePath>tasks\net46</PackagePath>
</Content>
</ItemGroup>

</Project>

This file was deleted.

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ExecuteAspektBootstrap>true</ExecuteAspektBootstrap>

<AspektGatherAssembliesDependsOn>Compile</AspektGatherAssembliesDependsOn>
<AspektBootstrapDependsOn>AspektGatherAssemblies</AspektBootstrapDependsOn>
</PropertyGroup>
</Project>
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<_AspektTaskAssembly Condition="'$(_AspektTaskAssembly)' == ''">$(MSBuildThisFileDirectory)..\tasks\net46\Aspekt.Bootstrap.Tasks.dll</_AspektTaskAssembly>
</PropertyGroup>

<UsingTask TaskName="Aspekt.Bootstrap.Tasks.AspektBootstrap"
AssemblyFile="$(_AspektTaskAssembly)" />

<Target Name="AspektGatherAssemblies"
DependsOnTargets="$(AspektGatherAssembliesDependsOn)"
Condition=" '$(ExecuteAspektBootstrap)' == 'true' And '$(IsCrossTargetingBuild)' != 'true' ">
<ItemGroup>
<AspektAssemblies Include="@(IntermediateAssembly)" />
</ItemGroup>
</Target>

<Target Name="AspektBootstrap"
AfterTargets="Compile"
DependsOnTargets="$(AspektBootstrapDependsOn)"
Condition=" '$(ExecuteAspektBootstrap)' == 'true' And '$(IsCrossTargetingBuild)' != 'true' ">
<AspektBootstrap Assemblies="@(AspektAssemblies)"
References="@(ReferencePathWithRefAssemblies)"/>
</Target>
</Project>
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildThisFileDirectory)..\build\Aspekt.props" />
</Project>
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildThisFileDirectory)..\build\Aspekt.targets" />
</Project>

0 comments on commit f8a4cc1

Please sign in to comment.
You can’t perform that action at this time.