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

Dotnet publish with filter profile #685

Merged
merged 2 commits into from
Jan 27, 2017
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,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="Newtonsoft.Json">
<Version>9.0.1</Version>
</PackageReference>
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ public class GenerateRuntimeConfigurationFiles : TaskBase
[Required]
public string TargetFramework { get; set; }

[Required]
public string TargetFrameworkMoniker { get; set; }
Copy link
Member

Choose a reason for hiding this comment

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

Taking both TargetFramework and TargetFrameworkMoniker seems redundant.

Copy link
Member Author

Choose a reason for hiding this comment

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

i had a chat with @nguerrera there seems to be no other clean way

Copy link
Member Author

Choose a reason for hiding this comment

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

There seems to be no good way to get reliably the same TargetFramework which was used to create the TargetFrameworkMoniker. Since we use the user supplied TargetFramework at build time, we want it to be the same here


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

Expand Down Expand Up @@ -56,7 +59,7 @@ protected override void ExecuteCore()
{
LockFile lockFile = new LockFileCache(BuildEngine4).GetLockFile(AssetsFilePath);
ProjectContext projectContext = lockFile.CreateProjectContext(
NuGetUtils.ParseFrameworkName(TargetFramework),
NuGetUtils.ParseFrameworkName(TargetFrameworkMoniker),
RuntimeIdentifier,
PlatformLibraryName);

Expand Down Expand Up @@ -99,6 +102,7 @@ private void AddFramework(RuntimeOptions runtimeOptions, ProjectContext projectC
}

runtimeOptions.Framework = framework;
runtimeOptions.tfm = TargetFramework;
}
}
}
Expand Down
12 changes: 10 additions & 2 deletions src/Tasks/Microsoft.NET.Build.Tasks/LockFileExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ public static ProjectContext CreateProjectContext(
this LockFile lockFile,
NuGetFramework framework,
string runtime,
string platformLibraryName)
string platformLibraryName,
LockFile filterlockFile = null)
{
if (lockFile == null)
{
Expand All @@ -28,6 +29,12 @@ public static ProjectContext CreateProjectContext(
}

LockFileTarget lockFileTarget = lockFile.GetTarget(framework, runtime);
LockFileTarget filterlockFileTarget = null;

if (filterlockFile != null)
{
filterlockFileTarget = filterlockFile.GetTarget(framework, runtime);
}

if (lockFileTarget == null)
{
Expand All @@ -39,7 +46,7 @@ public static ProjectContext CreateProjectContext(
throw new BuildErrorException(Strings.AssetsFileMissingTarget, lockFile.Path, targetMoniker, framework.GetShortFolderName(), runtime);
}

return new ProjectContext(lockFile, lockFileTarget, platformLibraryName);
return new ProjectContext(lockFile, lockFileTarget, platformLibraryName, filterlockFileTarget);
}

public static LockFileTargetLibrary GetLibrary(this LockFileTarget lockFileTarget, string libraryName)
Expand Down Expand Up @@ -76,6 +83,7 @@ public static Dictionary<string, string> GetProjectFileDependencies(this LockFil

return projectDeps;
}


public static HashSet<string> GetPlatformExclusionList(
this LockFileTarget lockFileTarget,
Expand Down
42 changes: 41 additions & 1 deletion src/Tasks/Microsoft.NET.Build.Tasks/ProjectContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace Microsoft.NET.Build.Tasks
public class ProjectContext
{
private readonly LockFile _lockFile;
private readonly LockFileTarget _filterlockFileTarget;
private readonly LockFileTarget _lockFileTarget;
private readonly string _platformLibraryName;

Expand All @@ -21,9 +22,10 @@ public class ProjectContext
public LockFile LockFile => _lockFile;
public LockFileTarget LockFileTarget => _lockFileTarget;

public ProjectContext(LockFile lockFile, LockFileTarget lockFileTarget, string platformLibraryName)
public ProjectContext(LockFile lockFile, LockFileTarget lockFileTarget, string platformLibraryName, LockFileTarget filterlockFileTarget = null)
{
_lockFile = lockFile;
_filterlockFileTarget = filterlockFileTarget;
_lockFileTarget = lockFileTarget;
_platformLibraryName = platformLibraryName;

Expand Down Expand Up @@ -54,6 +56,14 @@ public IEnumerable<LockFileTargetLibrary> GetRuntimeLibraries(IEnumerable<string
allExclusionList.UnionWith(privateAssetsExclusionList);
}

if(_filterlockFileTarget != null)
{
IEnumerable<LockFileTargetLibrary> filterLibraries = _filterlockFileTarget.Libraries;
Dictionary<string, LockFileTargetLibrary> filterLookup = filterLibraries.ToDictionary(e => e.Name, StringComparer.OrdinalIgnoreCase);

allExclusionList.UnionWith(GetIntersection(filterLookup, libraryLookup));
}

return runtimeLibraries.Filter(allExclusionList).ToArray();
}

Expand Down Expand Up @@ -171,5 +181,35 @@ public HashSet<string> GetPrivateAssetsExclusionList(

return privateAssetsToExclude;
}
private static HashSet<string> GetIntersection(
IDictionary<string, LockFileTargetLibrary> collection1,
IDictionary<string, LockFileTargetLibrary> collection2)
{
var exclusionList = new HashSet<string>();
var iterated = collection1;
var lookup = collection2;

if (collection1.Count > collection2.Count)
{
iterated = collection2;
lookup = collection1;
}
foreach (var entry in iterated)
{
LockFileTargetLibrary library = lookup[entry.Key];

if (library != null)
{
LockFileTargetLibrary dependency = entry.Value;

if (library.Version.Equals(dependency.Version))
{
exclusionList.Add(entry.Key);
}
}
}

return exclusionList;
}
}
}
20 changes: 16 additions & 4 deletions src/Tasks/Microsoft.NET.Build.Tasks/ResolvePublishAssemblies.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,11 @@ public class ResolvePublishAssemblies : TaskBase
public string PlatformLibraryName { get; set; }

public ITaskItem[] PrivateAssetsPackageReferences { get; set; }

public bool PreserveCacheLayout { get; set; }

public string FilterProjectAssetsFile { get; set; }

/// <summary>
/// All the assemblies to publish.
/// </summary>
Expand All @@ -42,16 +45,25 @@ public ITaskItem[] AssembliesToPublish

protected override void ExecuteCore()
{
LockFile lockFile = new LockFileCache(BuildEngine4).GetLockFile(AssetsFilePath);
var lockFileCache = new LockFileCache(BuildEngine4);
LockFile lockFile = lockFileCache.GetLockFile(AssetsFilePath);
IEnumerable<string> privateAssetsPackageIds = PackageReferenceConverter.GetPackageIds(PrivateAssetsPackageReferences);
IPackageResolver packageResolver = NuGetPackageResolver.CreateResolver(lockFile, ProjectPath);

LockFile filterLockFile = null;
if (!string.IsNullOrEmpty(FilterProjectAssetsFile))
{
filterLockFile = lockFileCache.GetLockFile(FilterProjectAssetsFile);

}
ProjectContext projectContext = lockFile.CreateProjectContext(
NuGetUtils.ParseFrameworkName(TargetFramework),
RuntimeIdentifier,
PlatformLibraryName);
PlatformLibraryName,
filterLockFile
);

IEnumerable<ResolvedFile> resolvedAssemblies =
IEnumerable<ResolvedFile> resolvedAssemblies =
new PublishAssembliesResolver(packageResolver)
.WithPrivateAssets(privateAssetsPackageIds)
.WithPreserveCacheLayout(PreserveCacheLayout)
Expand Down
2 changes: 2 additions & 0 deletions src/Tasks/Microsoft.NET.Build.Tasks/RuntimeOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ namespace Microsoft.NET.Build.Tasks
{
internal class RuntimeOptions
{
public string tfm { get; set; }
Copy link
Member

Choose a reason for hiding this comment

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

The property doesn't really align with the rest of the properties in the .json file.

  1. Its casing is different than the existing properties.
  2. It is an abbreviation, where the rest aren't.

Copy link
Member Author

Choose a reason for hiding this comment

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

Runtime options are only read by the dotnet host, so we have some leeway here

Copy link
Member

Choose a reason for hiding this comment

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

Why can't this value be inferred from the existing "Framework" property? Also, TFMs don't have patch numbers in them. So if we had a bug fix in crossgen between 1.1.0 and 1.1.1, how would the cache get updated? The assembly was crossgened using 1.1.0 and cached in "netcoreapp1.1", but when I installed 1.1.1, there is no way to get the cache updated, because the assembly was already cached for "netcoreapp1.1".

Copy link
Member Author

Choose a reason for hiding this comment

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

The caching step is typically done by admin or installer scripts. We do not expect the end user to do this themselves.

When this feature was spec'd, we decided to not do side by side install for the same TFM, to fix the crossgen bug- we would replace the assemblies already cached with the updated assembles, using the above mechanism

The readytorun images are supposed to be resilient to runtime changes within the same TFM, worst case we are to fallback to jitting

@gkhanna79 : Do correct me if i am wrong

Copy link
Member

Choose a reason for hiding this comment

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

@ramarag is correct.


public RuntimeConfigFramework Framework { get; set; }

public List<string> AdditionalProbingPaths { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,10 +227,12 @@ Copyright (c) .NET Foundation. All rights reserved.
<UsingTask TaskName="ResolvePublishAssemblies" AssemblyFile="$(MicrosoftNETBuildTasksAssembly)" />
<Target Name="RunResolvePublishAssemblies"
DependsOnTargets="ComputePrivateAssetsPackageReferences;
ComputePackagesToBeFiltered;
_DefaultMicrosoftNETPlatformLibrary">

<ResolvePublishAssemblies ProjectPath="$(MSBuildProjectFullPath)"
AssetsFilePath="$(ProjectAssetsFile)"
FilterProjectAssetsFile="$(FilterProjectAssetsFile)"
TargetFramework="$(TargetFrameworkMoniker)"
RuntimeIdentifier="$(RuntimeIdentifier)"
PlatformLibraryName="$(MicrosoftNETPlatformLibrary)"
Expand All @@ -242,6 +244,33 @@ Copyright (c) .NET Foundation. All rights reserved.
</ResolvePublishAssemblies>

</Target>

<!--
============================================================
ComputePackagesToBeFiltered

Gets the closure of all the specified packages in $(FilterProjFile)
These will be removed from the assets that are published
============================================================
-->

<Target Name="ComputePackagesToBeFiltered"
Condition="'$(FilterProjFile)'!=''" >
<PropertyGroup>
<FilterProjFileDir>$(MSBuildProjectExtensionsPath)\filterprofile</FilterProjFileDir>
<FilterProjectAssetsFile>$(FilterProjFileDir)\project.assets.json</FilterProjectAssetsFile>
</PropertyGroup>
<MSBuild Projects="$(FilterProjFile)"
Targets="Restore"
Properties="RestoreGraphProjectInput=$(FilterProjFile);
DisableImplicitFrameworkReferences=true;
RestoreOutputPath=$(FilterProjFileDir);
TargetFramework=$(TargetFramework);
RuntimeIdentifier=$(RuntimeIdentifier)"/>



</Target>

<!--
============================================================
Expand Down Expand Up @@ -498,7 +527,8 @@ Copyright (c) .NET Foundation. All rights reserved.
</PropertyGroup>

<GenerateRuntimeConfigurationFiles AssetsFilePath="$(ProjectAssetsFile)"
TargetFramework="$(TargetFrameworkMoniker)"
TargetFrameworkMoniker="$(TargetFrameworkMoniker)"
TargetFramework="$(TargetFramework)"
RuntimeConfigPath="$(PublishRuntimeConfigFilePath)"
RuntimeIdentifier="$(RuntimeIdentifier)"
PlatformLibraryName="$(MicrosoftNETPlatformLibrary)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@ Copyright (c) .NET Foundation. All rights reserved.
Outputs="$(ProjectRuntimeConfigFilePath);$(ProjectRuntimeConfigDevFilePath)">

<GenerateRuntimeConfigurationFiles AssetsFilePath="$(ProjectAssetsFile)"
TargetFramework="$(TargetFrameworkMoniker)"
TargetFrameworkMoniker="$(TargetFrameworkMoniker)"
TargetFramework="$(TargetFramework)"
RuntimeConfigPath="$(ProjectRuntimeConfigFilePath)"
RuntimeConfigDevPath="$(ProjectRuntimeConfigDevFilePath)"
RuntimeIdentifier="$(RuntimeIdentifier)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@

using System.IO;
using Microsoft.DotNet.Cli.Utils;
using FluentAssertions;
using Microsoft.NET.TestFramework;
using Microsoft.NET.TestFramework.Assertions;
using Microsoft.NET.TestFramework.Commands;
using Xunit;
using static Microsoft.NET.TestFramework.Commands.MSBuildTest;
using Microsoft.DotNet.InternalAbstractions;
using System.Runtime.InteropServices;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Microsoft.NET.Publish.Tests
{
Expand Down Expand Up @@ -109,5 +113,94 @@ public void It_publishes_projects_targeting_netcoreapp11_with_p2p_targeting_netc
.Should()
.Pass();
}

[Fact]
public void It_publishes_projects_with_simple_dependencies_with_filter_profile()
{
string project = "SimpleDependencies";
string tfm = "netcoreapp1.0";
TestAsset simpleDependenciesAsset = _testAssetsManager
.CopyTestAsset(project)
.WithSource()
.Restore("", $"/p:TargetFramework={tfm}");

string filterProjDir = _testAssetsManager.GetAndValidateTestProjectDirectory("NewtonsoftFilterProfile");
string filterProjFile = Path.Combine(filterProjDir, "NewtonsoftFilterProfile.csproj");

PublishCommand publishCommand = new PublishCommand(Stage0MSBuild, simpleDependenciesAsset.TestRoot);
publishCommand
.Execute($"/p:TargetFramework={tfm}", $"/p:FilterProjFile={filterProjFile}")
.Should()
.Pass();

DirectoryInfo publishDirectory = publishCommand.GetOutputDirectory();

publishDirectory.Should().OnlyHaveFiles(new[] {
$"{project}.dll",
$"{project}.pdb",
$"{project}.deps.json",
$"{project}.runtimeconfig.json",
"System.Collections.NonGeneric.dll"
});

var runtimeConfig = ReadJson(System.IO.Path.Combine(publishDirectory.ToString(), $"{project}.runtimeconfig.json"));

runtimeConfig["runtimeOptions"]["tfm"].ToString().Should().Be(tfm);

//TODO: Enable testing the run once dotnet host has the notion of looking up shared packages
}

[Fact]
public void It_publishes_projects_with_filter_and_rid()
{
string project = "SimpleDependencies";
var rid = RuntimeEnvironment.GetRuntimeIdentifier();
TestAsset simpleDependenciesAsset = _testAssetsManager
.CopyTestAsset(project)
.WithSource()
.Restore("", $"/p:RuntimeIdentifier={rid}");

string filterProjDir = _testAssetsManager.GetAndValidateTestProjectDirectory("NewtonsoftFilterProfile");
string filterProjFile = Path.Combine(filterProjDir, "NewtonsoftFilterProfile.csproj");


PublishCommand publishCommand = new PublishCommand(Stage0MSBuild, simpleDependenciesAsset.TestRoot);
publishCommand
.Execute($"/p:RuntimeIdentifier={rid}", $"/p:FilterProjFile={filterProjFile}")
.Should()
.Pass();

DirectoryInfo publishDirectory = publishCommand.GetOutputDirectory(runtimeIdentifier: rid);

string libPrefix = "";
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
libPrefix = "lib";
}

publishDirectory.Should().HaveFiles(new[] {
$"{project}.dll",
$"{project}.pdb",
$"{project}.deps.json",
$"{project}.runtimeconfig.json",
"System.Collections.NonGeneric.dll",
$"{libPrefix}coreclr{Constants.DynamicLibSuffix}"
});

publishDirectory.Should().NotHaveFiles(new[] {
"Newtonsoft.Json.dll",
"System.Runtime.Serialization.Primitives.dll"
});

//TODO: Enable testing the run once dotnet host has the notion of looking up shared packages
}
private static JObject ReadJson(string path)
{
using (JsonTextReader jsonReader = new JsonTextReader(File.OpenText(path)))
{
JsonSerializer serializer = new JsonSerializer();
return serializer.Deserialize<JObject>(jsonReader);
}
}
}
}
2 changes: 1 addition & 1 deletion test/Microsoft.NET.TestFramework/TestAssetsManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public TestAsset CreateTestProject(
return testAsset;
}

private string GetAndValidateTestProjectDirectory(string testProjectName)
public string GetAndValidateTestProjectDirectory(string testProjectName)
{
string testProjectDirectory = Path.Combine(ProjectsRoot, testProjectName);

Expand Down