Merge pull request #8733 from rolfbjarne/dotnet-linker

[dotnet] Add dotnet-linker, a project to contain custom linker steps for our net5.0 build.
This commit is contained in:
Rolf Bjarne Kvinge 2020-06-03 09:11:15 +02:00 коммит произвёл GitHub
Родитель 246b65fa6d c06ece7276
Коммит cb637a08a0
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
13 изменённых файлов: 262 добавлений и 59 удалений

Просмотреть файл

@ -1,45 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Sdk="Microsoft.NET.Sdk" Project="Sdk.targets" />
<!-- Default item includes (globs and implicit references) -->
<Import Project="Xamarin.Shared.Sdk.DefaultItems.targets" />
<Import Project="Xamarin.Shared.Sdk.TargetFrameworkInference.targets" />
<!-- Inject our custom logic into *DependsOn variables -->
<PropertyGroup>
<BuildDependsOn>
$(BuildDependsOn);
_CreateAppBundle;
</BuildDependsOn>
<!-- We re-use ComputeFilesToPublish & CopyFilesToPublishDirectory to copy files to the .app -->
<CreateAppBundleDependsOn>
ComputeFilesToPublish;
_ComputePublishLocation;
CopyFilesToPublishDirectory;
</CreateAppBundleDependsOn>
</PropertyGroup>
<Target Name="_ComputePublishLocation" DependsOnTargets="_GenerateBundleName">
<!-- Put .dll, .pdb, .exe and .dylib in the .app -->
<PropertyGroup>
<_AssemblyPublishDir Condition="'$(_PlatformName)' != 'macOS'">$(MSBuildProjectDirectory)$(_AppBundlePath)\</_AssemblyPublishDir>
<_AssemblyPublishDir Condition="'$(_PlatformName)' == 'macOS'">$(MSBuildProjectDirectory)$(_AppBundlePath)\Contents\MonoBundle\</_AssemblyPublishDir>
<_DylibPublishDir Condition="'$(_PlatformName)' != 'macOS'">$(MSBuildProjectDirectory)$(_AppBundlePath)\</_DylibPublishDir>
<_DylibPublishDir Condition="'$(_PlatformName)' == 'macOS'">$(MSBuildProjectDirectory)$(_AppBundlePath)\Contents\MonoBundle\</_DylibPublishDir>
</PropertyGroup>
<ItemGroup>
<ResolvedFileToPublish
Update="@(ResolvedFileToPublish)"
RelativePath="$([MSBuild]::MakeRelative($(MSBuildProjectDirectory)$(PublishDir),$(_AssemblyPublishDir)))%(Filename)%(Extension)"
Condition="'%(Extension)' == '.dll' Or '%(Extension)' == '.pdb' Or '$(Extension)' == '.exe'" />
<ResolvedFileToPublish
Update="@(ResolvedFileToPublish)"
RelativePath="$([MSBuild]::MakeRelative($(MSBuildProjectDirectory)$(PublishDir),$(_DylibPublishDir)))%(Filename)%(Extension)"
Condition="'%(Extension)' == '.dylib'" />
</ItemGroup>
</Target>
<!-- Project types and how do we distinguish between them
@ -87,7 +47,77 @@
<_ProjectType Condition="'$(_ProjectType)' == '' And '$(_PlatformName)' == 'macOS' And '$(OutputType)' == 'Library' And '$(IsAppExtension)' != ''">macOSAppExtensionProject</_ProjectType>
<_ProjectType Condition="'$(_ProjectType)' == '' And '$(_PlatformName)' == 'macOS' And '$(OutputType)' == 'Library' And '$(IsBindingProject)' != ''">macOSBindingProject</_ProjectType>
<_ProjectType Condition="'$(_ProjectType)' == '' And '$(_PlatformName)' == 'macOS' And '$(OutputType)' == 'Library'">macOSClassLibrary</_ProjectType>
</PropertyGroup>
<PropertyGroup>
<!-- We must run the linker for executable projects and app extension projects. We must set PublishTrimmed before importing Microsoft.NET.Sdk, because it'll be evaluated there. -->
<PublishTrimmed Condition="'$(PublishTrimmed)' == '' And ($(_ProjectType.EndsWith ('ExecutableProject')) Or $(_ProjectType.EndsWith ('AppExtensionProject')))">true</PublishTrimmed>
<!-- App extensions are self-contained, even though their OutputType=Library -->
<SelfContained Condition="'$(SelfContained)' == '' And $(_ProjectType.EndsWith ('AppExtensionProject'))">true</SelfContained>
</PropertyGroup>
<Import Sdk="Microsoft.NET.Sdk" Project="Sdk.targets" />
<!-- Default item includes (globs and implicit references) -->
<Import Project="Xamarin.Shared.Sdk.DefaultItems.targets" />
<Import Project="Xamarin.Shared.Sdk.TargetFrameworkInference.targets" />
<!-- Inject our custom logic into *DependsOn variables -->
<PropertyGroup>
<BuildDependsOn>
$(BuildDependsOn);
_CreateAppBundle;
</BuildDependsOn>
<!-- We re-use ComputeFilesToPublish & CopyFilesToPublishDirectory to copy files to the .app -->
<!-- ComputeFilesToPublish will run ILLink -->
<CreateAppBundleDependsOn>
_ComputeLinkerArguments;
ComputeFilesToPublish;
_ComputePublishLocation;
CopyFilesToPublishDirectory;
</CreateAppBundleDependsOn>
</PropertyGroup>
<!-- Linker -->
<PropertyGroup>
<_AdditionalTaskAssemblyDirectory>$(_XamarinSdkRootDirectory)tools/dotnet-linker/</_AdditionalTaskAssemblyDirectory>
<_AdditionalTaskAssembly>$(_AdditionalTaskAssemblyDirectory)dotnet-linker.dll</_AdditionalTaskAssembly>
</PropertyGroup>
<Target Name="_ComputeLinkerArguments">
<ItemGroup>
<!-- add our custom steps -->
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)">
<BeforeStep>LoadReferencesStep</BeforeStep>
<Type>Xamarin.SetupStep</Type>
</_TrimmerCustomSteps>
</ItemGroup>
</Target>
<Target Name="_ComputePublishLocation" DependsOnTargets="_GenerateBundleName">
<!-- Put .dll, .pdb, .exe and .dylib in the .app -->
<PropertyGroup>
<_AssemblyPublishDir Condition="'$(_PlatformName)' != 'macOS'">$(MSBuildProjectDirectory)$(_AppBundlePath)\</_AssemblyPublishDir>
<_AssemblyPublishDir Condition="'$(_PlatformName)' == 'macOS'">$(MSBuildProjectDirectory)$(_AppBundlePath)\Contents\MonoBundle\</_AssemblyPublishDir>
<_DylibPublishDir Condition="'$(_PlatformName)' != 'macOS'">$(MSBuildProjectDirectory)$(_AppBundlePath)\</_DylibPublishDir>
<_DylibPublishDir Condition="'$(_PlatformName)' == 'macOS'">$(MSBuildProjectDirectory)$(_AppBundlePath)\Contents\MonoBundle\</_DylibPublishDir>
</PropertyGroup>
<ItemGroup>
<ResolvedFileToPublish
Update="@(ResolvedFileToPublish)"
RelativePath="$([MSBuild]::MakeRelative($(MSBuildProjectDirectory)$(PublishDir),$(_AssemblyPublishDir)))%(Filename)%(Extension)"
Condition="'%(Extension)' == '.dll' Or '%(Extension)' == '.pdb' Or '$(Extension)' == '.exe'" />
<ResolvedFileToPublish
Update="@(ResolvedFileToPublish)"
RelativePath="$([MSBuild]::MakeRelative($(MSBuildProjectDirectory)$(PublishDir),$(_DylibPublishDir)))%(Filename)%(Extension)"
Condition="'%(Extension)' == '.dylib'" />
</ItemGroup>
</Target>
<!-- Import existing targets -->
<PropertyGroup>
<_ProjectLanguage>$(Language)</_ProjectLanguage>
<_ProjectLanguage Condition="'$(_ProjectLanguage)' == '' Or '$(_ProjectLanguage)' == 'C#' ">CSharp</_ProjectLanguage>
<_ProjectLanguage Condition="'$(_ProjectLanguage)' == 'F#' ">FSharp</_ProjectLanguage>
@ -95,8 +125,6 @@
<_TargetsDirectory>$(_XamarinSdkRootDirectory)\tools\msbuild\$(_PlatformName)\</_TargetsDirectory>
</PropertyGroup>
<!-- Import existing targets -->
<Import Project="$(_TargetsDirectory)Xamarin.iOS.$(_ProjectLanguage).targets" Condition="'$(_ProjectType)' == 'iOSExecutableProject' Or '$(_ProjectType)' == 'iOSClassLibrary' " />
<Import Project="$(_TargetsDirectory)Xamarin.iOS.AppExtension.$(_ProjectLanguage).targets" Condition="'$(_ProjectType)' == 'iOSAppExtensionProject' " />
<Import Project="$(_TargetsDirectory)Xamarin.iOS.ObjCBinding.$(_ProjectLanguage).targets" Condition="'$(_ProjectType)' == 'iOSBindingProject' " />

Просмотреть файл

@ -24,12 +24,19 @@ namespace Xamarin.Tests {
}
}
public static void AssertBuild (string project, Dictionary<string, string> properties = null, string verbosity = "diagnostic")
public static ExecutionResult AssertBuild (string project, Dictionary<string, string> properties = null, string verbosity = "diagnostic")
{
Execute ("build", project, properties, out var _, verbosity, true);
return Execute ("build", project, properties, verbosity, true);
}
public static int Execute (string verb, string project, Dictionary<string, string> properties, out StringBuilder output, string verbosity = "diagnostic", bool assert_success = true)
public static ExecutionResult AssertBuildFailure (string project, Dictionary<string, string> properties = null, string verbosity = "diagnostic")
{
var rv = Execute ("build", project, properties, verbosity, false);
Assert.AreNotEqual (0, rv.ExitCode, "Unexpected success");
return rv;
}
public static ExecutionResult Execute (string verb, string project, Dictionary<string, string> properties, string verbosity = "diagnostic", bool assert_success = true)
{
if (!File.Exists (project))
throw new FileNotFoundException ($"The project file '{project}' does not exist.");
@ -51,7 +58,7 @@ namespace Xamarin.Tests {
var env = new Dictionary<string, string> ();
env ["MSBuildSDKsPath"] = null;
env ["MSBUILD_EXE_PATH"] = null;
output = new StringBuilder ();
var output = new StringBuilder ();
var rv = ExecutionHelper.Execute (Executable, args, env, output, output, workingDirectory: Path.GetDirectoryName (project), timeout: TimeSpan.FromMinutes (10));
if (rv != 0) {
Console.WriteLine ($"'{Executable} {StringUtils.FormatArguments (args)}' failed with exit code {rv}.");
@ -65,10 +72,21 @@ namespace Xamarin.Tests {
msg = "\n\t" + string.Join ("\n\t", errors);
Assert.AreEqual (0, rv, $"Exit code: {Executable} {StringUtils.FormatArguments (args)}{msg}");
}
return rv;
return new ExecutionResult {
StandardOutput = output,
StandardError = output,
ExitCode = rv,
};
default:
throw new NotImplementedException ($"Unknown dotnet action: '{verb}'");
}
}
}
public class ExecutionResult {
public StringBuilder StandardOutput;
public StringBuilder StandardError;
public int ExitCode;
public bool TimedOut;
}
}

Просмотреть файл

@ -5,7 +5,7 @@ using NUnit.Framework;
namespace Xamarin.Tests {
[TestFixture]
public class DotNetProjectTest {
void Build (string project, string subdir = null)
string GetProjectPath (string project, string subdir = null)
{
var project_dir = Path.Combine (Configuration.SourceRoot, "tests", "dotnet", project);
if (!string.IsNullOrEmpty (subdir))
@ -15,31 +15,57 @@ namespace Xamarin.Tests {
if (!File.Exists (project_path))
project_path = Path.ChangeExtension (project_path, "sln");
DotNet.AssertBuild (project_path);
if (!File.Exists (project_path))
throw new FileNotFoundException ($"Could not find the project or solution {project}");
return project_path;
}
void Clean (string project_path)
{
var dirs = Directory.GetDirectories (Path.GetDirectoryName (project_path), "*", SearchOption.AllDirectories);
foreach (var dir in dirs) {
var name = Path.GetFileName (dir);
if (name != "bin" && name != "obj")
continue;
Directory.Delete (dir, true);
}
}
[Test]
public void BuildMySingleView ()
{
Build ("MySingleView");
var project_path = GetProjectPath ("MySingleView");
Clean (project_path);
var result = DotNet.AssertBuild (project_path);
AssertThatLinkerExecuted (result);
}
[Test]
public void BuildMyCocoaApp ()
{
Build ("MyCocoaApp");
var project_path = GetProjectPath ("MyCocoaApp");
Clean (project_path);
var result = DotNet.AssertBuild (project_path);
AssertThatLinkerExecuted (result);
}
[Test]
public void BuildMyTVApp ()
{
Build ("MyTVApp");
var project_path = GetProjectPath ("MyTVApp");
Clean (project_path);
var result = DotNet.AssertBuild (project_path);
AssertThatLinkerExecuted (result);
}
[Test]
public void BuildMyWatchApp ()
{
Build ("MyWatchApp");
var project_path = GetProjectPath ("MyWatchApp");
Clean (project_path);
var result = DotNet.AssertBuildFailure (project_path);
Assert.That (result.StandardOutput.ToString (), Does.Contain ("The specified RuntimeIdentifier 'watchos-x86' is not recognized."), "Missing runtime pack for watchOS");
}
[TestCase ("iOS")]
@ -48,7 +74,17 @@ namespace Xamarin.Tests {
[TestCase ("macOS")]
public void BuildMyClassLibrary (string platform)
{
Build ("MyClassLibrary", platform);
var project_path = GetProjectPath ("MyClassLibrary", platform);
Clean (project_path);
var result = DotNet.AssertBuild (project_path);
Assert.That (result.StandardOutput.ToString (), Does.Not.Contain ("Task \"ILLink\""), "Linker executed unexpectedly.");
}
void AssertThatLinkerExecuted (ExecutionResult result)
{
var output = result.StandardOutput.ToString ();
Assert.That (output, Does.Contain ("Building target \"_RunILLink\" completely."), "Linker did not executed as expected.");
Assert.That (output, Does.Contain ("Hello SetupStep"), "Custom steps did not run as expected.");
}
}
}

Просмотреть файл

@ -1,3 +1,3 @@
TOP=..
SUBDIRS=mmp mtouch install-source xibuild mlaunch siminstaller
SUBDIRS=mmp mtouch install-source xibuild mlaunch siminstaller dotnet-linker
include $(TOP)/Make.config

Просмотреть файл

@ -27,17 +27,26 @@ PROJECT_FILE="$1"
PROJECT=$(basename -s .csproj "$PROJECT_FILE")
PROJECT_DIR=$(dirname "$PROJECT_FILE")
FRAGMENT_PATH="$2"
REFERENCES_PATH=$PROJECT-references.txt
REFERENCES_PATH=$(pwd)/$PROJECT-references.txt
if test -z "$FRAGMENT_PATH"; then
FRAGMENT_PATH=$PROJECT_FILE.inc
fi
if test -z "$BUILD_EXECUTABLE"; then
BUILD_EXECUTABLE=msbuild
fi
# ProjectInspector.csproj is an MSBuild file with a target
# (WriteProjectReferences) that takes another project file as input (the
# ProjectFile variable) and writes all the project references (recursively) to
# a file (the ReferenceListPath variable).
msbuild ProjectInspector.csproj "/t:WriteProjectReferences" "/p:ProjectFile=$PROJECT_FILE" "/p:ReferenceListPath=$REFERENCES_PATH" /verbosity:quiet /nologo
(
cp ProjectInspector.csproj "$PROJECT_DIR"
cd "$PROJECT_DIR"
$BUILD_EXECUTABLE ProjectInspector.csproj "/t:WriteProjectReferences" "/p:ProjectFile=$PROJECT_FILE" "/p:ReferenceListPath=$REFERENCES_PATH" /verbosity:quiet /nologo
rm -f ProjectInspector.csproj
)
# Now we have a list of all the project referenced by the input project. The
# ProjectInspector.csproj also has a target (WriteInputs) that can list all
@ -79,7 +88,11 @@ for proj in $(sort "$REFERENCES_PATH" | uniq); do
proj_dir=$(dirname "$proj")
inputs_path=$PWD/$proj_name.inputs
INPUT_PATHS+=("$inputs_path")
msbuild "$TMPPROJ" "/t:WriteInputs" "/p:ProjectFile=$proj" "/p:InputsPath=$inputs_path" /verbosity:quiet /nologo
(
cd "$(dirname "$proj")"
$BUILD_EXECUTABLE "$TMPPROJ" "/t:WriteInputs" "/p:ProjectFile=$proj" "/p:InputsPath=$inputs_path" /verbosity:quiet /nologo
)
# The output contains relative paths, relative to the csproj directory
# Change those to full paths by prepending the csproj directory.
@ -99,7 +112,7 @@ done
# First add a dependency for the csproj.inc so that it's rebuilt if the project itself changes.
echo "${PROJECT}.csproj.inc: ${PROJECT_FILE} $PWD/ProjectInspector.csproj" > "$FRAGMENT_PATH"
# Now create the variable with all the input files.
echo "${PROJECT}_dependencies = \\" >> "$FRAGMENT_PATH"
echo "${PROJECT//-/_}_dependencies = \\" >> "$FRAGMENT_PATH"
sort "${INPUT_PATHS[@]}" | uniq >> "$FRAGMENT_PATH"
# Simplify paths somewhat by removing the current directory

2
tools/dotnet-linker/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,2 @@
*.csproj.inc
.vscode/

Просмотреть файл

@ -0,0 +1,42 @@
TOP=../..
include $(TOP)/Make.config
PLATFORMS=iOS tvOS watchOS macOS
BUILD_DIR=bin/Debug/net5.0
DOTNET_TARGETS += \
$(BUILD_DIR)/dotnet-linker.dll \
$(foreach platform,$(PLATFORMS),$(DOTNET_DESTDIR)/Microsoft.$(platform).Sdk/tools/dotnet-linker/dotnet-linker.dll) \
$(foreach platform,$(PLATFORMS),$(DOTNET_DESTDIR)/Microsoft.$(platform).Sdk/tools/dotnet-linker/dotnet-linker.pdb) \
DOTNET_DIRECTORIES += \
$(foreach platform,$(PLATFORMS),$(DOTNET_DESTDIR)/Microsoft.$(platform).Sdk/tools/dotnet-linker) \
$(BUILD_DIR)/dotnet-linker%dll $(BUILD_DIR)/dotnet-linker%pdb: Makefile $(dotnet_linker_dependencies)
$(Q_DOTNET_BUILD) $(DOTNET5) build $(XBUILD_VERBOSITY)
$(DOTNET_DESTDIR)/Microsoft.iOS.Sdk/tools/dotnet-linker/%: $(BUILD_DIR)/% | $(DOTNET_DESTDIR)/Microsoft.iOS.Sdk/tools/dotnet-linker
$(Q) $(CP) $< $@
$(DOTNET_DESTDIR)/Microsoft.tvOS.Sdk/tools/dotnet-linker/%: $(BUILD_DIR)/% | $(DOTNET_DESTDIR)/Microsoft.tvOS.Sdk/tools/dotnet-linker
$(Q) $(CP) $< $@
$(DOTNET_DESTDIR)/Microsoft.watchOS.Sdk/tools/dotnet-linker/%: $(BUILD_DIR)/% | $(DOTNET_DESTDIR)/Microsoft.watchOS.Sdk/tools/dotnet-linker
$(Q) $(CP) $< $@
$(DOTNET_DESTDIR)/Microsoft.macOS.Sdk/tools/dotnet-linker/%: $(BUILD_DIR)/% | $(DOTNET_DESTDIR)/Microsoft.macOS.Sdk/tools/dotnet-linker
$(Q) $(CP) $< $@
$(DOTNET_DIRECTORIES):
$(Q) mkdir -p $@
# dotnet-linker.csproj.inc contains the dotnet_linker_dependencies variable used to determine if mtouch needs to be rebuilt or not.
dotnet-linker.csproj.inc: export BUILD_EXECUTABLE=$(DOTNET5) build
dotnet-linker.csproj.inc: dotnet-linker.csproj Makefile ../common/create-makefile-fragment.sh $(TOP)/Make.config $(TOP)/mk/mono.mk
$(Q_GEN) ../common/create-makefile-fragment.sh $(abspath $<)
-include dotnet-linker.csproj.inc
all-local:: $(DOTNET_TARGETS)
install-local:: $(DOTNET_TARGETS)

Просмотреть файл

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<config>
<add key="repositorypath" value="packages" />
<add key="globalPackagesFolder" value="packages" />
</config>
<packageSources>
<add key="Nuget Official" value="https://www.nuget.org/api/v2/" />
<add key="Dotnet arcade" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" />
<add key="dotnet5" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5/nuget/v3/index.json" />
</packageSources>
</configuration>

Просмотреть файл

@ -0,0 +1,7 @@
# Linker
This directory contains the custom linker steps for the managed linker when
building Xamarin.iOS/tvOS/watchOS/macOS apps using .NET 5.
This is work in progress, and is not in a releasable state yet.

Просмотреть файл

@ -0,0 +1,15 @@
using System;
using Mono.Linker.Steps;
namespace Xamarin {
public class SetupStep : BaseStep {
protected override void Process ()
{
// This will be replaced with something more useful later.
Console.WriteLine ("Hello SetupStep");
}
}
}

Просмотреть файл

@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<RootNamespace>dotnet_linker</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.ILLink" Version="5.0.0-preview.3.20302.1" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,17 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dotnet-linker", "dotnet-linker.csproj", "{7F7392F4-AB2D-4153-B591-46C79501B3C6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{7F7392F4-AB2D-4153-B591-46C79501B3C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7F7392F4-AB2D-4153-B591-46C79501B3C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7F7392F4-AB2D-4153-B591-46C79501B3C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7F7392F4-AB2D-4153-B591-46C79501B3C6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

Просмотреть файл

@ -0,0 +1,3 @@
{
"sdk": { "version": "5.*" }
}