PackageReference

This commit is contained in:
Bryan Thornbury 2016-09-26 21:40:11 -07:00
parent 19f295113a
commit bf45ab19ca
12 changed files with 434 additions and 89 deletions

View file

@ -16,6 +16,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration
new MigrateRuntimeOptionsRule(),
new MigratePublishOptionsRule(),
new MigrateProjectDependenciesRule(),
new MigratePackageDependenciesRule(),
new MigrateConfigurationsRule(),
new MigrateScriptsRule(),
new TemporaryMutateProjectJsonRule(),

View file

@ -0,0 +1,15 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using NuGet.Frameworks;
namespace Microsoft.DotNet.ProjectJsonMigration
{
public static class MigrationNuGetFrameworkExtensions
{
public static string GetMSBuildCondition(this NuGetFramework framework)
{
return $" '$(TargetFramework)' == '{framework.GetTwoDigitShortFolderName()}' ";
}
}
}

View file

@ -18,7 +18,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration
public ProjectPropertyGroupElement CommonPropertyGroup { get; }
public IEnumerable<ProjectContext> ProjectContexts { get; }
public List<ProjectContext> ProjectContexts { get; }
public ProjectContext DefaultProjectContext
{
@ -36,7 +36,7 @@ namespace Microsoft.DotNet.ProjectJsonMigration
ProjectRootElement projectXproj=null)
{
ProjectXproj = projectXproj;
ProjectContexts = projectContexts;
ProjectContexts = projectContexts.ToList();
OutputMSBuildProject = outputMSBuildProject;
CommonItemGroup = commonItemGroup;
CommonPropertyGroup = commonPropertyGroup;

View file

@ -5,15 +5,17 @@ using System;
using System.Collections.Generic;
using Microsoft.Build.Construction;
using Microsoft.DotNet.ProjectModel;
using Microsoft.DotNet.ProjectModel.Graph;
using System.Linq;
using System.IO;
using Newtonsoft.Json.Linq;
using Microsoft.DotNet.Tools.Common;
using NuGet.Frameworks;
using NuGet.LibraryModel;
namespace Microsoft.DotNet.ProjectJsonMigration
{
public class ProjectDependencyFinder
internal class ProjectDependencyFinder
{
public IEnumerable<ProjectDependency> ResolveProjectDependencies(string projectDir, string xprojFile = null)
{
@ -41,13 +43,57 @@ namespace Microsoft.DotNet.ProjectJsonMigration
}
}
}
public IEnumerable<ProjectDependency> ResolveProjectDependencies(
ProjectContext projectContext,
public IEnumerable<ProjectDependency> ResolveProjectDependenciesForFramework(
Project project,
NuGetFramework framework,
IEnumerable<string> preResolvedProjects=null)
{
preResolvedProjects = preResolvedProjects ?? new HashSet<string>();
var possibleProjectDependencies =
FindPossibleProjectDependencies(project.ProjectFilePath);
var projectDependencies = new List<ProjectDependency>();
IEnumerable<ProjectLibraryDependency> projectFileDependenciesForFramework;
if (framework == null)
{
projectFileDependenciesForFramework = project.Dependencies;
}
else
{
projectFileDependenciesForFramework = project.GetTargetFramework(framework).Dependencies;
}
foreach (var projectFileDependency in projectFileDependenciesForFramework)
{
var dependencyName = projectFileDependency.Name;
ProjectDependency projectDependency;
if (!possibleProjectDependencies.TryGetValue(dependencyName, out projectDependency))
{
if (projectFileDependency.LibraryRange.TypeConstraint == LibraryDependencyTarget.Project
&& !preResolvedProjects.Contains(dependencyName))
{
MigrationErrorCodes
.MIGRATE1014($"Unresolved project dependency ({dependencyName})").Throw();
}
else
{
continue;
}
}
projectDependencies.Add(projectDependency);
}
return projectDependencies;
}
public IEnumerable<ProjectDependency> ResolveProjectDependencies(ProjectContext projectContext, IEnumerable<string> preResolvedProjects=null)
{
preResolvedProjects = preResolvedProjects ?? new HashSet<string>();
var projectExports = projectContext.CreateExporter("_").GetDependencies();
var possibleProjectDependencies =
FindPossibleProjectDependencies(projectContext.ProjectFile.ProjectFilePath);

View file

@ -40,15 +40,15 @@ namespace Microsoft.DotNet.ProjectJsonMigration
throw new ArgumentNullException();
}
MigrateProject(rootSettings);
var projectDependencies = ResolveTransitiveClosureProjectDependencies(rootSettings.ProjectDirectory, rootSettings.ProjectXProjFilePath);
MigrateProject(rootSettings);
if (skipProjectReferences)
{
return;
}
var projectDependencies = ResolveTransitiveClosureProjectDependencies(rootSettings.ProjectDirectory, rootSettings.ProjectXProjFilePath);
foreach(var project in projectDependencies)
{
var projectDir = Path.GetDirectoryName(project.ProjectFilePath);

View file

@ -0,0 +1,221 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Build.Construction;
using Microsoft.DotNet.ProjectJsonMigration.Transforms;
using Microsoft.DotNet.ProjectModel;
using Microsoft.DotNet.ProjectModel.Graph;
using Microsoft.DotNet.Tools.Common;
using NuGet.Frameworks;
using NuGet.LibraryModel;
namespace Microsoft.DotNet.ProjectJsonMigration.Rules
{
public class MigratePackageDependenciesRule : IMigrationRule
{
private readonly ITransformApplicator _transformApplicator;
private readonly ProjectDependencyFinder _projectDependencyFinder;
private string _projectDirectory;
public MigratePackageDependenciesRule(ITransformApplicator transformApplicator = null)
{
_transformApplicator = transformApplicator ?? new TransformApplicator();
_projectDependencyFinder = new ProjectDependencyFinder();
}
public void Apply(MigrationSettings migrationSettings, MigrationRuleInputs migrationRuleInputs)
{
CleanExistingPackageReferences(migrationRuleInputs.OutputMSBuildProject);
_projectDirectory = migrationSettings.ProjectDirectory;
var project = migrationRuleInputs.DefaultProjectContext.ProjectFile;
var tfmDependencyMap = new Dictionary<string, IEnumerable<ProjectLibraryDependency>>();
var targetFrameworks = project.GetTargetFrameworks();
// Inject Sdk dependency
PackageDependencyInfoTransform.Transform(new PackageDependencyInfo
{
Name = ConstantPackageNames.CSdkPackageName,
Version = migrationSettings.SdkPackageVersion
});
// Migrate Direct Deps first
MigrateDependencies(
project,
migrationRuleInputs.OutputMSBuildProject,
null,
project.Dependencies,
migrationRuleInputs.ProjectXproj);
foreach (var targetFramework in targetFrameworks)
{
MigrateImports(migrationRuleInputs.CommonItemGroup, targetFramework);
MigrateDependencies(
project,
migrationRuleInputs.OutputMSBuildProject,
targetFramework.FrameworkName,
project.Dependencies,
migrationRuleInputs.ProjectXproj);
}
}
private void MigrateImports(ProjectItemGroupElement commonItemGroup, TargetFrameworkInformation targetFramework)
{
var transform = ImportsTransformation.Transform(targetFramework);
if (transform != null)
{
transform.Condition = targetFramework.FrameworkName.GetMSBuildCondition();
_transformApplicator.Execute(transform, commonItemGroup);
}
}
private void CleanExistingPackageReferences(ProjectRootElement outputMSBuildProject)
{
var packageRefs = outputMSBuildProject.Items.Where(i => i.ItemType == "PackageReference").ToList();
foreach (var packageRef in packageRefs)
{
var parent = packageRef.Parent;
packageRef.Parent.RemoveChild(packageRef);
parent.RemoveIfEmpty();
}
}
private void MigrateDependencies(
Project project,
ProjectRootElement output,
NuGetFramework framework,
IEnumerable<ProjectLibraryDependency> dependencies,
ProjectRootElement xproj)
{
var projectDependencies = new HashSet<string>(GetAllProjectReferenceNames(project, framework, xproj));
var packageDependencies = dependencies.Where(d => !projectDependencies.Contains(d.Name));
string condition = framework?.GetMSBuildCondition() ?? "";
var itemGroup = output.ItemGroups.FirstOrDefault(i => i.Condition == condition)
?? output.AddItemGroup();
itemGroup.Condition = condition;
foreach (var packageDependency in packageDependencies)
{
AddItemTransform<ProjectLibraryDependency> transform;
if (packageDependency.LibraryRange.TypeConstraint == LibraryDependencyTarget.Reference)
{
transform = FrameworkDependencyTransform;
}
else
{
transform = PackageDependencyTransform();
if (packageDependency.Type == LibraryDependencyType.Build)
{
Console.WriteLine("Build type!!!");
transform = transform.WithMetadata("PrivateAssets", "all");
}
else if (packageDependency.SuppressParent != LibraryIncludeFlagUtils.DefaultSuppressParent)
{
var metadataValue = ReadLibraryIncludeFlags(packageDependency.SuppressParent);
transform = transform.WithMetadata("PrivateAssets", metadataValue);
}
// TODO: include/exclude
if (packageDependency.IncludeType != LibraryIncludeFlags.All)
{
var metadataValue = ReadLibraryIncludeFlags(packageDependency.IncludeType);
transform = transform.WithMetadata("IncludeAssets", metadataValue);
}
}
_transformApplicator.Execute(transform.Transform(packageDependency), itemGroup);
}
}
private string ReadLibraryIncludeFlags(LibraryIncludeFlags includeFlags)
{
if ((includeFlags & LibraryIncludeFlags.All) == LibraryIncludeFlags.All)
{
return "All";
}
var flagString = "";
var allFlagsAndNames = new List<Tuple<string, LibraryIncludeFlags>>
{
Tuple.Create("Analyzers", LibraryIncludeFlags.Analyzers),
Tuple.Create("Build", LibraryIncludeFlags.Build),
Tuple.Create("Compile", LibraryIncludeFlags.Compile),
Tuple.Create("ContentFiles", LibraryIncludeFlags.ContentFiles),
Tuple.Create("Native", LibraryIncludeFlags.Native),
Tuple.Create("Runtime", LibraryIncludeFlags.Runtime)
};
foreach (var flagAndName in allFlagsAndNames)
{
var name = flagAndName.Item1;
var flag = flagAndName.Item2;
if ((includeFlags & flag) == flag)
{
if (!string.IsNullOrEmpty(flagString))
{
flagString += ";";
}
flagString += name;
}
}
return flagString;
}
private IEnumerable<string> GetAllProjectReferenceNames(Project project, NuGetFramework framework, ProjectRootElement xproj)
{
var csprojReferenceItems = _projectDependencyFinder.ResolveXProjProjectDependencies(xproj);
var migratedXProjDependencyPaths = csprojReferenceItems.SelectMany(p => p.Includes());
var migratedXProjDependencyNames = new HashSet<string>(migratedXProjDependencyPaths.Select(p => Path.GetFileNameWithoutExtension(
PathUtility.GetPathWithDirectorySeparator(p))));
var projectDependencies = _projectDependencyFinder.ResolveProjectDependenciesForFramework(
project,
framework,
preResolvedProjects: migratedXProjDependencyNames);
return projectDependencies.Select(p => p.Name).Concat(migratedXProjDependencyNames);
}
private AddItemTransform<ProjectLibraryDependency> FrameworkDependencyTransform => new AddItemTransform<ProjectLibraryDependency>(
"Reference",
dep => dep.Name,
dep => "",
dep => true);
private Func<AddItemTransform<ProjectLibraryDependency>> PackageDependencyTransform => () => new AddItemTransform<ProjectLibraryDependency>(
"PackageReference",
dep => dep.Name,
dep => "",
dep => true)
.WithMetadata("Version", r => r.LibraryRange.VersionRange.OriginalString);
private AddItemTransform<PackageDependencyInfo> PackageDependencyInfoTransform => new AddItemTransform<PackageDependencyInfo>(
"PackageReference",
dep => dep.Name,
dep => "",
dep => true)
.WithMetadata("Version", r => r.Version);
private AddItemTransform<TargetFrameworkInformation> ImportsTransformation => new AddItemTransform<TargetFrameworkInformation>(
"PackageTargetFallback",
t => $"$(PackageTargetFallback);{string.Join(";", t.Imports)}",
t => "",
t => t.Imports.Any());
private class PackageDependencyInfo
{
public string Name {get; set;}
public string Version {get; set;}
}
}
}

View file

@ -5,6 +5,7 @@ using System;
using System.IO;
using System.Linq;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
namespace Microsoft.DotNet.ProjectJsonMigration.Rules
{
@ -21,73 +22,11 @@ namespace Microsoft.DotNet.ProjectJsonMigration.Rules
{
public void Apply(MigrationSettings migrationSettings, MigrationRuleInputs migrationRuleInputs)
{
bool shouldRenameOldProject = PathsAreEqual(migrationSettings.OutputDirectory, migrationSettings.ProjectDirectory);
if (!shouldRenameOldProject && File.Exists(Path.Combine(migrationSettings.OutputDirectory, "project.json")))
{
// TODO: should there be a setting to overwrite anything in output directory?
throw new Exception("Existing project.json found in output directory.");
}
var sourceProjectFile = Path.Combine(migrationSettings.ProjectDirectory, "project.json");
var destinationProjectFile = Path.Combine(migrationSettings.OutputDirectory, "project.json");
if (shouldRenameOldProject)
{
var renamedProjectFile = Path.Combine(migrationSettings.ProjectDirectory, "project.migrated.json");
File.Move(sourceProjectFile, renamedProjectFile);
sourceProjectFile = renamedProjectFile;
}
var json = CreateDestinationProjectFile(sourceProjectFile, destinationProjectFile);
InjectSdkReference(json, ConstantPackageNames.CSdkPackageName, migrationSettings.SdkPackageVersion);
RemoveRuntimesNode(json);
File.WriteAllText(destinationProjectFile, json.ToString());
}
private JObject CreateDestinationProjectFile(string sourceProjectFile, string destinationProjectFile)
{
File.Copy(sourceProjectFile, destinationProjectFile);
return JObject.Parse(File.ReadAllText(destinationProjectFile));
}
private void InjectSdkReference(JObject json, string sdkPackageName, string sdkPackageVersion)
{
JToken dependenciesNode;
if (json.TryGetValue("dependencies", out dependenciesNode))
{
var dependenciesNodeObject = dependenciesNode.Value<JObject>();
dependenciesNodeObject.Add(sdkPackageName, sdkPackageVersion);
}
else
{
var dependenciesNodeObject = new JObject();
dependenciesNodeObject.Add(sdkPackageName, sdkPackageVersion);
json.Add("dependencies", dependenciesNodeObject);
}
}
private void RemoveRuntimesNode(JObject json)
{
json.Remove("runtimes");
}
private bool PathsAreEqual(params string[] paths)
{
var normalizedPaths = paths.Select(path => Path.GetFullPath(path).TrimEnd(Path.DirectorySeparatorChar)).ToList();
for (int i=1; i<normalizedPaths.Count(); ++i)
{
var path1 = normalizedPaths[i - 1];
var path2 = normalizedPaths[i];
if (!string.Equals(path1, path2, StringComparison.Ordinal))
{
return false;
}
}
return true;
var renamedProjectFile = Path.Combine(migrationSettings.ProjectDirectory, "project.migrated.json");
File.Move(sourceProjectFile, renamedProjectFile);
sourceProjectFile = renamedProjectFile;
}
}
}

View file

@ -278,6 +278,11 @@ namespace Microsoft.DotNet.ProjectModel
var dependencyValue = dependency.Value;
var dependencyTypeValue = LibraryDependencyType.Default;
var dependencyIncludeFlagsValue = LibraryIncludeFlags.All;
var dependencyExcludeFlagsValue = LibraryIncludeFlags.None;
var suppressParentFlagsValue = LibraryIncludeFlagUtils.DefaultSuppressParent;
var target = isGacOrFrameworkReference ? LibraryDependencyTarget.Reference : LibraryDependencyTarget.All;
string dependencyVersionAsString = null;
@ -298,6 +303,23 @@ namespace Microsoft.DotNet.ProjectModel
var targetStr = dependencyValue.Value<string>("target");
target = LibraryDependencyTargetUtils.Parse(targetStr);
}
IEnumerable<string> strings;
if (TryGetStringEnumerable(dependencyValue["include"], out strings))
{
dependencyIncludeFlagsValue = LibraryIncludeFlagUtils.GetFlags(strings);
}
if (TryGetStringEnumerable(dependencyValue["exclude"], out strings))
{
dependencyExcludeFlagsValue = LibraryIncludeFlagUtils.GetFlags(strings);
}
if (TryGetStringEnumerable(dependencyValue["suppressParent"], out strings))
{
// This overrides any settings that came from the type property.
suppressParentFlagsValue = LibraryIncludeFlagUtils.GetFlags(strings);
}
}
else if (dependencyValue.Type == JTokenType.String)
{
@ -327,6 +349,9 @@ namespace Microsoft.DotNet.ProjectModel
}
}
// the dependency flags are: Include flags - Exclude flags
var includeFlags = dependencyIncludeFlagsValue & ~dependencyExcludeFlagsValue;
var lineInfo = (IJsonLineInfo)dependencyValue;
results.Add(new ProjectLibraryDependency
{
@ -335,6 +360,8 @@ namespace Microsoft.DotNet.ProjectModel
dependencyVersionRange,
target),
Type = dependencyTypeValue,
IncludeType = includeFlags,
SuppressParent = suppressParentFlagsValue,
SourceFilePath = projectPath,
SourceLine = lineInfo.LineNumber,
SourceColumn = lineInfo.LinePosition
@ -472,7 +499,8 @@ namespace Microsoft.DotNet.ProjectModel
Dependencies = new List<ProjectLibraryDependency>(),
CompilerOptions = compilerOptions,
Line = lineInfo.LineNumber,
Column = lineInfo.LinePosition
Column = lineInfo.LinePosition,
Imports = GetImports(frameworkValue)
};
var frameworkDependencies = new List<ProjectLibraryDependency>();
@ -508,6 +536,26 @@ namespace Microsoft.DotNet.ProjectModel
return true;
}
private IEnumerable<string> GetImports(JObject frameworkValue)
{
var prop = frameworkValue.Property("imports");
if (prop == null)
{
return Enumerable.Empty<string>();
}
if (prop.Type == JTokenType.Array)
{
return prop.Value<IEnumerable<string>>();
}
else if (prop.Type == JTokenType.String)
{
return new [] { prop.Value<string>() };
}
return null;
}
private static CommonCompilerOptions GetCompilationOptions(JObject rawObject, Project project)
{
var compilerName = rawObject.Value<string>("compilerName");
@ -819,5 +867,29 @@ namespace Microsoft.DotNet.ProjectModel
lineInfo.LineNumber,
lineInfo.LinePosition));
}
private static bool TryGetStringEnumerable(JToken token, out IEnumerable<string> result)
{
IEnumerable<string> values;
if (token == null)
{
result = null;
return false;
}
else if (token.Type == JTokenType.String)
{
values = new[]
{
token.Value<string>()
};
}
else
{
values = token.Value<string[]>();
}
result = values
.SelectMany(value => value.Split(new[] { ' ', ',' }, StringSplitOptions.RemoveEmptyEntries));
return true;
}
}
}

View file

@ -22,5 +22,7 @@ namespace Microsoft.DotNet.ProjectModel
public string WrappedProject { get; set; }
public string AssemblyPath { get; set; }
public IEnumerable<string> Imports { get; set; }
}
}

View file

@ -0,0 +1,59 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using Microsoft.Build.Construction;
using Microsoft.DotNet.Tools.Test.Utilities;
using System.Linq;
using Xunit;
using FluentAssertions;
using Microsoft.DotNet.ProjectJsonMigration.Rules;
using System;
namespace Microsoft.DotNet.ProjectJsonMigration.Tests
{
public class GivenThatIWantToMigratePackageDependencies : TestBase
{
[Fact]
public void It_migrates_basic_PackageReference()
{
var mockProj = RunPackageDependenciesRuleOnPj(@"
{
""dependencies"": {
""APackage"" : ""1.0.0-preview"",
""BPackage"" : ""1.0.0""
}
}");
Console.WriteLine(mockProj.RawXml);
EmitsPackageReferences(mockProj, Tuple.Create("APackage", "1.0.0-preview", ""), Tuple.Create("BPackage", "1.0.0", ""));
}
private void EmitsPackageReferences(ProjectRootElement mockProj, params Tuple<string, string, string>[] packageSpecs)
{
foreach (var packageSpec in packageSpecs)
{
var packageName = packageSpec.Item1;
var packageVersion = packageSpec.Item2;
var packageTFM = packageSpec.Item3;
var items = mockProj.Items
.Where(i => i.ItemType == "PackageReference")
.Where(i => string.IsNullOrEmpty(packageTFM) || i.ConditionChain().Any(c => c.Contains(packageTFM)))
.Where(i => i.Include == packageName)
.Where(i => i.GetMetadataWithName("Version").Value == packageVersion);
items.Should().HaveCount(1);
}
}
private ProjectRootElement RunPackageDependenciesRuleOnPj(string s, string testDirectory = null)
{
testDirectory = testDirectory ?? Temp.CreateDirectory().Path;
return TemporaryProjectFileRuleRunner.RunRules(new IMigrationRule[]
{
new MigratePackageDependenciesRule()
}, s, testDirectory);
}
}
}

View file

@ -1,12 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.DotNet.ProjectJsonMigration.Transforms;
namespace Microsoft.DotNet.ProjectJsonMigration.Tests
{
public class GivenAnIncludeContextTransformation
{
}
}

View file

@ -241,8 +241,10 @@ namespace Microsoft.DotNet.Migration.Tests
File.Delete(Path.Combine(projectDirectory, "project.lock.json"));
MigrateProject(projectDirectory);
Restore(projectDirectory);
BuildMSBuild(projectDirectory, projectName);
DeleteXproj(projectDirectory);
Restore3(projectDirectory);
BuildMSBuild(projectDirectory);
var msbuildBuildOutputs = new HashSet<string>(CollectBuildOutputs(projectDirectory));