Skip to content

Dipping toes into linker friendliness #24458

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

Merged
merged 8 commits into from
Aug 3, 2020
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
8 changes: 5 additions & 3 deletions src/Hosting/Hosting/src/GenericHost/GenericWebHostBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Runtime.ExceptionServices;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting.Builder;
using Microsoft.AspNetCore.Hosting.Internal;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
Expand Down Expand Up @@ -196,7 +198,7 @@ public IWebHostBuilder UseDefaultServiceProvider(Action<WebHostBuilderContext, S
return this;
}

public IWebHostBuilder UseStartup(Type startupType)
public IWebHostBuilder UseStartup([DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType)
{
// UseStartup can be called multiple times. Only run the last one.
_startupObject = startupType;
Expand All @@ -213,7 +215,7 @@ public IWebHostBuilder UseStartup(Type startupType)
return this;
}

public IWebHostBuilder UseStartup(Func<WebHostBuilderContext, object> startupFactory)
public IWebHostBuilder UseStartup<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]TStartup>(Func<WebHostBuilderContext, TStartup> startupFactory)
{
// Clear the startup type
_startupObject = startupFactory;
Expand All @@ -232,7 +234,7 @@ public IWebHostBuilder UseStartup(Func<WebHostBuilderContext, object> startupFac
return this;
}

private void UseStartup(Type startupType, HostBuilderContext context, IServiceCollection services, object instance = null)
private void UseStartup([DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType, HostBuilderContext context, IServiceCollection services, object instance = null)
{
var webHostBuilderContext = GetWebHostBuilderContext(context);
var webHostOptions = (WebHostOptions)context.Properties[typeof(WebHostOptions)];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting.Internal;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

Expand Down Expand Up @@ -71,12 +73,12 @@ public IWebHostBuilder Configure(Action<WebHostBuilderContext, IApplicationBuild
return _builder.Configure(configure);
}

public IWebHostBuilder UseStartup(Type startupType)
public IWebHostBuilder UseStartup([DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType)
{
return _builder.UseStartup(startupType);
}

public IWebHostBuilder UseStartup(Func<WebHostBuilderContext, object> startupFactory)
public IWebHostBuilder UseStartup<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]TStartup>(Func<WebHostBuilderContext, TStartup> startupFactory)
{
return _builder.UseStartup(startupFactory);
}
Expand Down
6 changes: 4 additions & 2 deletions src/Hosting/Hosting/src/GenericHost/ISupportsStartup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting.Internal;

namespace Microsoft.AspNetCore.Hosting
{
internal interface ISupportsStartup
{
IWebHostBuilder Configure(Action<WebHostBuilderContext, IApplicationBuilder> configure);
IWebHostBuilder UseStartup(Type startupType);
IWebHostBuilder UseStartup(Func<WebHostBuilderContext, object> startupFactory);
IWebHostBuilder UseStartup([DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType);
IWebHostBuilder UseStartup<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]TStartup>(Func<WebHostBuilderContext, TStartup> startupFactory);
}
}
13 changes: 13 additions & 0 deletions src/Hosting/Hosting/src/Internal/StartupLinkerOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Diagnostics.CodeAnalysis;

namespace Microsoft.AspNetCore.Hosting.Internal
{
internal static class StartupLinkerOptions
{
// We're going to keep all public constructors and public methods on Startup classes
public const DynamicallyAccessedMemberTypes Accessibility = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods;
}
}
14 changes: 8 additions & 6 deletions src/Hosting/Hosting/src/Internal/StartupLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Hosting.Internal;
using Microsoft.Extensions.DependencyInjection;

namespace Microsoft.AspNetCore.Hosting
Expand Down Expand Up @@ -37,7 +39,7 @@ internal class StartupLoader
//
// If the Startup class ConfigureServices returns an <see cref="IServiceProvider"/> and there is at least an <see cref="IStartupConfigureServicesFilter"/> registered we
// throw as the filters can't be applied.
public static StartupMethods LoadMethods(IServiceProvider hostingServiceProvider, Type startupType, string environmentName, object instance = null)
public static StartupMethods LoadMethods(IServiceProvider hostingServiceProvider, [DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType, string environmentName, object instance = null)
{
var configureMethod = FindConfigureDelegate(startupType, environmentName);

Expand Down Expand Up @@ -272,31 +274,31 @@ public static Type FindStartupType(string startupAssemblyName, string environmen
return type;
}

internal static ConfigureBuilder FindConfigureDelegate(Type startupType, string environmentName)
internal static ConfigureBuilder FindConfigureDelegate([DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType, string environmentName)
{
var configureMethod = FindMethod(startupType, "Configure{0}", environmentName, typeof(void), required: true);
return new ConfigureBuilder(configureMethod);
}

internal static ConfigureContainerBuilder FindConfigureContainerDelegate(Type startupType, string environmentName)
internal static ConfigureContainerBuilder FindConfigureContainerDelegate([DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType, string environmentName)
{
var configureMethod = FindMethod(startupType, "Configure{0}Container", environmentName, typeof(void), required: false);
return new ConfigureContainerBuilder(configureMethod);
}

internal static bool HasConfigureServicesIServiceProviderDelegate(Type startupType, string environmentName)
internal static bool HasConfigureServicesIServiceProviderDelegate([DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType, string environmentName)
{
return null != FindMethod(startupType, "Configure{0}Services", environmentName, typeof(IServiceProvider), required: false);
}

internal static ConfigureServicesBuilder FindConfigureServicesDelegate(Type startupType, string environmentName)
internal static ConfigureServicesBuilder FindConfigureServicesDelegate([DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType, string environmentName)
{
var servicesMethod = FindMethod(startupType, "Configure{0}Services", environmentName, typeof(IServiceProvider), required: false)
?? FindMethod(startupType, "Configure{0}Services", environmentName, typeof(void), required: false);
return new ConfigureServicesBuilder(servicesMethod);
}

private static MethodInfo FindMethod(Type startupType, string methodName, string environmentName, Type returnType = null, bool required = true)
private static MethodInfo FindMethod([DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType, string methodName, string environmentName, Type returnType = null, bool required = true)
{
var methodNameWithEnv = string.Format(CultureInfo.InvariantCulture, methodName, environmentName);
var methodNameWithNoEnv = string.Format(CultureInfo.InvariantCulture, methodName, "");
Expand Down
9 changes: 6 additions & 3 deletions src/Hosting/Hosting/src/WebHostBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting.Internal;
using Microsoft.AspNetCore.Hosting.StaticWebAssets;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
Expand Down Expand Up @@ -67,7 +69,8 @@ private static IWebHostBuilder Configure(this IWebHostBuilder hostBuilder, Actio
/// <param name="hostBuilder">The <see cref="IWebHostBuilder"/> to configure.</param>
/// <param name="startupFactory">A delegate that specifies a factory for the startup class.</param>
/// <returns>The <see cref="IWebHostBuilder"/>.</returns>
public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Func<WebHostBuilderContext, object> startupFactory)
/// <remarks>When using the il linker, all public methods of <typeparamref name="TStartup"/> are preserved. This should match the Startup type directly (and not a base type).</remarks>
public static IWebHostBuilder UseStartup<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]TStartup>(this IWebHostBuilder hostBuilder, Func<WebHostBuilderContext, TStartup> startupFactory) where TStartup : class
{
if (startupFactory == null)
{
Expand Down Expand Up @@ -110,7 +113,7 @@ public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Func<
/// <param name="hostBuilder">The <see cref="IWebHostBuilder"/> to configure.</param>
/// <param name="startupType">The <see cref="Type"/> to be used.</param>
/// <returns>The <see cref="IWebHostBuilder"/>.</returns>
public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Type startupType)
public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, [DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType)
{
if (startupType == null)
{
Expand Down Expand Up @@ -151,7 +154,7 @@ public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Type
/// <param name="hostBuilder">The <see cref="IWebHostBuilder"/> to configure.</param>
/// <typeparam name ="TStartup">The type containing the startup methods for the application.</typeparam>
/// <returns>The <see cref="IWebHostBuilder"/>.</returns>
public static IWebHostBuilder UseStartup<TStartup>(this IWebHostBuilder hostBuilder) where TStartup : class
public static IWebHostBuilder UseStartup<[DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)]TStartup>(this IWebHostBuilder hostBuilder) where TStartup : class
{
return hostBuilder.UseStartup(typeof(TStartup));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public IWebHostBuilder UseStartup(Type startupType)
return this;
}

public IWebHostBuilder UseStartup(Func<WebHostBuilderContext, object> startupFactory)
public IWebHostBuilder UseStartup<TStartup>(Func<WebHostBuilderContext, TStartup> startupFactory)
{
_builder.UseStartup(startupFactory);
return this;
Expand Down
4 changes: 2 additions & 2 deletions src/Hosting/Hosting/test/WebHostBuilderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ public void UseStartupThrowsWhenFactoryIsNull(IWebHostBuilder builder)
public void UseStartupThrowsWhenFactoryReturnsNull(IWebHostBuilder builder)
{
var server = new TestServer();
var ex = Assert.Throws<InvalidOperationException>(() => builder.UseServer(server).UseStartup(context => null).Build());
var ex = Assert.Throws<InvalidOperationException>(() => builder.UseServer(server).UseStartup<object>(context => null).Build());
Assert.Equal("The specified factory returned null startup instance.", ex.Message);
}

Expand All @@ -96,7 +96,7 @@ public async Task MultipleUseStartupCallsLastWins(IWebHostBuilder builder)
var server = new TestServer();
var host = builder.UseServer(server)
.UseStartup<StartupCtorThrows>()
.UseStartup(context => throw new InvalidOperationException("This doesn't run"))
.UseStartup<object>(context => throw new InvalidOperationException("This doesn't run"))
.Configure(app =>
{
throw new InvalidOperationException("This doesn't run");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public virtual Task<PublishedApplication> Publish(DeploymentParameters deploymen
// avoids triggering builds of dependencies of the test app which could cause issues like https://github.com/dotnet/arcade/issues/2941
+ $" --no-dependencies"
+ $" /p:TargetArchitecture={deploymentParameters.RuntimeArchitecture}"
+ " --no-restore";
+ (deploymentParameters.RestoreDependencies ? "" : " --no-restore");
Copy link
Member

Choose a reason for hiding this comment

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

I recall no-restore is required for all tests on Helix because the test assets need to be pre-built before publishing to helix.

Copy link
Member Author

Choose a reason for hiding this comment

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

Works fine on helix as far as I could tell.


if (deploymentParameters.ApplicationType == ApplicationType.Standalone)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ public DeploymentParameters(DeploymentParameters parameters)
/// </summary>
public string ApplicationBaseUriHint { get; set; }

public bool RestoreDependencies { get; set; }

/// <summary>
/// Scheme used by the deployed application if <see cref="ApplicationBaseUriHint"/> is empty.
/// </summary>
Expand Down
57 changes: 57 additions & 0 deletions src/Hosting/test/FunctionalTests/LinkedApplicationTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.IntegrationTesting;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Logging;
using Xunit;

namespace Microsoft.AspNetCore.Hosting.FunctionalTests
{
public class LinkedApplicationTests : LoggedTest
{
[Fact]
public async Task LinkedApplicationWorks()
{
using (StartLog(out var loggerFactory))
{
var logger = loggerFactory.CreateLogger("LinkedApplicationWorks");

// https://github.com/dotnet/aspnetcore/issues/8247
#pragma warning disable 0618
var applicationPath = Path.Combine(TestPathUtilities.GetSolutionRootDirectory("Hosting"), "test", "testassets",
"BasicLinkedApp");
#pragma warning restore 0618

var deploymentParameters = new DeploymentParameters(
applicationPath,
ServerType.Kestrel,
RuntimeFlavor.CoreClr,
RuntimeArchitecture.x64)
{
TargetFramework = Tfm.Net50,
RuntimeArchitecture = RuntimeArchitecture.x64,
ApplicationType = ApplicationType.Standalone,
PublishApplicationBeforeDeployment = true,
RestoreDependencies = true,
StatusMessagesEnabled = false
};

using var deployer = new SelfHostDeployer(deploymentParameters, loggerFactory);

var result = await deployer.DeployAsync();

// The app should have started up
Assert.False(deployer.HostProcess.HasExited);

var response = await RetryHelper.RetryRequest(() => result.HttpClient.GetAsync("/"), logger, retryCount: 10);
var body = await response.Content.ReadAsStringAsync();

Assert.Equal("Hello World", body);
}
}
}
}
31 changes: 31 additions & 0 deletions src/Hosting/test/testassets/BasicLinkedApp/BasicLinkedApp.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
<OutputType>Exe</OutputType>
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>link</TrimMode>
</PropertyGroup>

<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Hosting" />
<Reference Include="Microsoft.Extensions.Hosting" />
<Reference Include="Microsoft.AspNetCore.Server.Kestrel" />
</ItemGroup>

<ItemGroup>
<TrimmerRootDescriptor Include="Linker.xml" />
</ItemGroup>

<!-- Link all assemblies -->
<Target Name="EnsureAllAssembliesAreLinked" BeforeTargets="PrepareForILLink">
<ItemGroup>
<ManagedAssemblyToLink>
<TrimMode>link</TrimMode>
</ManagedAssemblyToLink>
<!-- Root this project assembly -->
<TrimmerRootAssembly Include="$(TargetName)" />
</ItemGroup>
</Target>

</Project>
45 changes: 45 additions & 0 deletions src/Hosting/test/testassets/BasicLinkedApp/Linker.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- See: https://github.com/mono/linker/blob/master/src/linker/README.md#syntax-of-xml-descriptor -->
<linker>
<assembly fullname="Microsoft.Extensions.Hosting">
<type fullname="Microsoft.Extensions.Hosting.Internal.ApplicationLifetime" />
<type fullname="Microsoft.Extensions.Hosting.Internal.ConsoleLifetime" />
<type fullname="Microsoft.Extensions.Hosting.ConsoleLifetimeOptions" />
<type fullname="Microsoft.Extensions.Hosting.Internal.Host" />
<type fullname="Microsoft.Extensions.Hosting.HostOptions" />
</assembly>
<assembly fullname="Microsoft.Extensions.Logging">
<type fullname="Microsoft.Extensions.Logging.LoggerFactory" />
</assembly>
<assembly fullname="Microsoft.Extensions.Logging.Configuration">
<type fullname="Microsoft.Extensions.Logging.Configuration.LoggerProviderConfigurationFactory" />
<type fullname="Microsoft.Extensions.Logging.Configuration.LoggerProviderConfigureOptions`2" />
<type fullname="Microsoft.Extensions.Logging.Configuration.LoggerProviderOptionsChangeTokenSource`2" />
<type fullname="Microsoft.Extensions.Logging.Configuration.LoggerProviderConfiguration`1" />
</assembly>
<assembly fullname="Microsoft.Extensions.Logging.Console" preserve="all" />
<assembly fullname="Microsoft.AspNetCore.Hosting">
<type fullname="Microsoft.AspNetCore.Hosting.GenericWebHostApplicationLifetime" />
<type fullname="Microsoft.AspNetCore.Http.DefaultHttpContextFactory" />
<type fullname="Microsoft.AspNetCore.Hosting.Builder.ApplicationBuilderFactory" />
<type fullname="Microsoft.AspNetCore.Hosting.GenericWebHostService" />
<type fullname="Microsoft.AspNetCore.Hosting.GenericWebHostServiceOptions" />
</assembly>
<assembly fullname="Microsoft.AspNetCore.Http">
<type fullname="Microsoft.AspNetCore.Http.MiddlewareFactory" />
</assembly>
<assembly fullname="Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets">
<type fullname="Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportFactory" />
<type fullname="Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportOptions" />
</assembly>
<assembly fullname="Microsoft.AspNetCore.Server.Kestrel.Core">
<type fullname="Microsoft.AspNetCore.Server.Kestrel.Core.Internal.KestrelServerOptionsSetup" />
<type fullname="Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServer" />
</assembly>
<assembly fullname="Microsoft.Extensions.Options">
<type fullname="Microsoft.Extensions.Options.OptionsCache`1" />
<type fullname="Microsoft.Extensions.Options.OptionsFactory`1" />
<type fullname="Microsoft.Extensions.Options.OptionsMonitor`1" />
<type fullname="Microsoft.Extensions.Options.OptionsManager`1" />
</assembly>
</linker>
Loading