2016-11-29 10:23:04 -08:00
|
|
|
|
// 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.Build.Evaluation;
|
2016-12-08 12:59:14 -08:00
|
|
|
|
using Microsoft.Build.Exceptions;
|
2016-11-29 10:23:04 -08:00
|
|
|
|
using Microsoft.DotNet.Cli.Utils;
|
|
|
|
|
using Microsoft.DotNet.Tools.Common;
|
2016-12-08 12:59:14 -08:00
|
|
|
|
using NuGet.Frameworks;
|
2016-11-29 10:23:04 -08:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
|
|
|
|
namespace Microsoft.DotNet.Tools
|
|
|
|
|
{
|
|
|
|
|
internal class MsbuildProject
|
|
|
|
|
{
|
2016-11-30 13:43:43 -08:00
|
|
|
|
const string ProjectItemElementType = "ProjectReference";
|
|
|
|
|
|
2016-12-08 12:59:14 -08:00
|
|
|
|
public ProjectRootElement ProjectRoot { get; private set; }
|
2016-11-29 10:23:04 -08:00
|
|
|
|
public string ProjectDirectory { get; private set; }
|
2016-12-08 12:59:14 -08:00
|
|
|
|
|
|
|
|
|
private List<NuGetFramework> _cachedTfms = null;
|
|
|
|
|
private Project _cachedEvaluatedProject = null;
|
2016-11-29 10:23:04 -08:00
|
|
|
|
|
2016-11-30 13:43:43 -08:00
|
|
|
|
private MsbuildProject(ProjectRootElement project)
|
2016-11-29 10:23:04 -08:00
|
|
|
|
{
|
2016-12-08 12:59:14 -08:00
|
|
|
|
ProjectRoot = project;
|
|
|
|
|
ProjectDirectory = PathUtility.EnsureTrailingSlash(ProjectRoot.DirectoryPath);
|
2016-11-29 10:23:04 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static MsbuildProject FromFileOrDirectory(string fileOrDirectory)
|
|
|
|
|
{
|
|
|
|
|
if (File.Exists(fileOrDirectory))
|
|
|
|
|
{
|
|
|
|
|
return FromFile(fileOrDirectory);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return FromDirectory(fileOrDirectory);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static MsbuildProject FromFile(string projectPath)
|
|
|
|
|
{
|
|
|
|
|
if (!File.Exists(projectPath))
|
|
|
|
|
{
|
2016-12-04 21:33:43 -08:00
|
|
|
|
throw new GracefulException(CommonLocalizableStrings.ProjectDoesNotExist, projectPath);
|
2016-11-29 10:23:04 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var project = TryOpenProject(projectPath);
|
|
|
|
|
if (project == null)
|
|
|
|
|
{
|
2016-12-04 21:33:43 -08:00
|
|
|
|
throw new GracefulException(CommonLocalizableStrings.ProjectIsInvalid, projectPath);
|
2016-11-29 10:23:04 -08:00
|
|
|
|
}
|
|
|
|
|
|
2016-11-30 13:43:43 -08:00
|
|
|
|
return new MsbuildProject(project);
|
2016-11-29 10:23:04 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static MsbuildProject FromDirectory(string projectDirectory)
|
|
|
|
|
{
|
|
|
|
|
DirectoryInfo dir;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
dir = new DirectoryInfo(projectDirectory);
|
|
|
|
|
}
|
|
|
|
|
catch (ArgumentException)
|
|
|
|
|
{
|
2016-12-04 21:33:43 -08:00
|
|
|
|
throw new GracefulException(CommonLocalizableStrings.CouldNotFindProjectOrDirectory, projectDirectory);
|
2016-11-29 10:23:04 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!dir.Exists)
|
|
|
|
|
{
|
2016-12-04 21:33:43 -08:00
|
|
|
|
throw new GracefulException(CommonLocalizableStrings.CouldNotFindProjectOrDirectory, projectDirectory);
|
2016-11-29 10:23:04 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FileInfo[] files = dir.GetFiles("*proj");
|
|
|
|
|
if (files.Length == 0)
|
|
|
|
|
{
|
2016-12-04 21:33:43 -08:00
|
|
|
|
throw new GracefulException(CommonLocalizableStrings.CouldNotFindAnyProjectInDirectory, projectDirectory);
|
2016-11-29 10:23:04 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (files.Length > 1)
|
|
|
|
|
{
|
2016-12-04 21:33:43 -08:00
|
|
|
|
throw new GracefulException(CommonLocalizableStrings.MoreThanOneProjectInDirectory, projectDirectory);
|
2016-11-29 10:23:04 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FileInfo projectFile = files.First();
|
|
|
|
|
|
|
|
|
|
if (!projectFile.Exists)
|
|
|
|
|
{
|
2016-12-04 21:33:43 -08:00
|
|
|
|
throw new GracefulException(CommonLocalizableStrings.CouldNotFindAnyProjectInDirectory, projectDirectory);
|
2016-11-29 10:23:04 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var project = TryOpenProject(projectFile.FullName);
|
|
|
|
|
if (project == null)
|
|
|
|
|
{
|
2016-12-04 21:33:43 -08:00
|
|
|
|
throw new GracefulException(CommonLocalizableStrings.FoundInvalidProject, projectFile.FullName);
|
2016-11-29 10:23:04 -08:00
|
|
|
|
}
|
|
|
|
|
|
2016-11-30 13:43:43 -08:00
|
|
|
|
return new MsbuildProject(project);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int AddProjectToProjectReferences(string framework, IEnumerable<string> refs)
|
|
|
|
|
{
|
|
|
|
|
int numberOfAddedReferences = 0;
|
|
|
|
|
|
2016-12-08 12:59:14 -08:00
|
|
|
|
ProjectItemGroupElement itemGroup = ProjectRoot.FindUniformOrCreateItemGroupWithCondition(ProjectItemElementType, framework);
|
2016-11-30 13:43:43 -08:00
|
|
|
|
foreach (var @ref in refs.Select((r) => NormalizeSlashes(r)))
|
|
|
|
|
{
|
2016-12-08 12:59:14 -08:00
|
|
|
|
if (ProjectRoot.HasExistingItemWithCondition(framework, @ref))
|
2016-11-30 13:43:43 -08:00
|
|
|
|
{
|
2016-12-04 21:33:43 -08:00
|
|
|
|
Reporter.Output.WriteLine(string.Format(CommonLocalizableStrings.ProjectAlreadyHasAreference, @ref));
|
2016-11-30 13:43:43 -08:00
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
numberOfAddedReferences++;
|
2016-12-08 12:59:14 -08:00
|
|
|
|
itemGroup.AppendChild(ProjectRoot.CreateItemElement(ProjectItemElementType, @ref));
|
2016-11-30 13:43:43 -08:00
|
|
|
|
|
2016-12-04 21:33:43 -08:00
|
|
|
|
Reporter.Output.WriteLine(string.Format(CommonLocalizableStrings.ReferenceAddedToTheProject, @ref));
|
2016-11-30 13:43:43 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return numberOfAddedReferences;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int RemoveProjectToProjectReferences(string framework, IEnumerable<string> refs)
|
|
|
|
|
{
|
|
|
|
|
int totalNumberOfRemovedReferences = 0;
|
|
|
|
|
|
|
|
|
|
foreach (var @ref in refs)
|
|
|
|
|
{
|
|
|
|
|
totalNumberOfRemovedReferences += RemoveProjectToProjectReferenceAlternatives(framework, @ref);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return totalNumberOfRemovedReferences;
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-07 12:56:27 -08:00
|
|
|
|
public IEnumerable<ProjectItemElement> GetProjectToProjectReferences()
|
|
|
|
|
{
|
2016-12-08 12:59:14 -08:00
|
|
|
|
return ProjectRoot.GetAllItemsWithElementType(ProjectItemElementType);
|
2016-12-07 12:56:27 -08:00
|
|
|
|
}
|
|
|
|
|
|
2016-11-30 13:43:43 -08:00
|
|
|
|
public void ConvertPathsToRelative(ref List<string> references)
|
|
|
|
|
{
|
|
|
|
|
references = references.Select((r) => PathUtility.GetRelativePath(ProjectDirectory, Path.GetFullPath(r))).ToList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static string NormalizeSlashes(string path)
|
|
|
|
|
{
|
|
|
|
|
return path.Replace('/', '\\');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static void EnsureAllReferencesExist(List<string> references)
|
|
|
|
|
{
|
|
|
|
|
var notExisting = new List<string>();
|
|
|
|
|
foreach (var r in references)
|
|
|
|
|
{
|
|
|
|
|
if (!File.Exists(r))
|
|
|
|
|
{
|
|
|
|
|
notExisting.Add(r);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (notExisting.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
throw new GracefulException(
|
|
|
|
|
string.Join(
|
|
|
|
|
Environment.NewLine,
|
2016-12-04 21:33:43 -08:00
|
|
|
|
notExisting.Select((r) => string.Format(CommonLocalizableStrings.ReferenceDoesNotExist, r))));
|
2016-11-30 13:43:43 -08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-08 12:59:14 -08:00
|
|
|
|
public IEnumerable<NuGetFramework> GetTargetFrameworks()
|
|
|
|
|
{
|
|
|
|
|
if (_cachedTfms != null)
|
|
|
|
|
{
|
|
|
|
|
return _cachedTfms;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var project = GetEvaluatedProject();
|
|
|
|
|
|
|
|
|
|
var properties = project.AllEvaluatedProperties
|
|
|
|
|
.Where(p => p.Name.Equals("TargetFrameworks", StringComparison.OrdinalIgnoreCase) ||
|
|
|
|
|
p.Name.Equals("TargetFramework", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
.Select(p => p.EvaluatedValue.ToLower()).ToList();
|
|
|
|
|
|
|
|
|
|
var uniqueTfms = new HashSet<string>();
|
|
|
|
|
|
|
|
|
|
foreach (var property in properties)
|
|
|
|
|
{
|
|
|
|
|
var tfms = property
|
|
|
|
|
.Split(';')
|
|
|
|
|
.Select((tfm) => tfm.Trim())
|
|
|
|
|
.Where((tfm) => !string.IsNullOrEmpty(tfm));
|
|
|
|
|
|
|
|
|
|
foreach (var tfm in tfms)
|
|
|
|
|
{
|
|
|
|
|
uniqueTfms.Add(tfm);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_cachedTfms = uniqueTfms.Select((frameworkString) => NuGetFramework.Parse(frameworkString)).ToList();
|
|
|
|
|
return _cachedTfms;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool CanWorkOnFramework(NuGetFramework framework)
|
|
|
|
|
{
|
|
|
|
|
foreach (var tfm in GetTargetFrameworks())
|
|
|
|
|
{
|
|
|
|
|
if (DefaultCompatibilityProvider.Instance.IsCompatible(framework, tfm))
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool TargetsFramework(NuGetFramework framework)
|
|
|
|
|
{
|
|
|
|
|
foreach (var tfm in GetTargetFrameworks())
|
|
|
|
|
{
|
|
|
|
|
if (framework.Equals(tfm))
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Project GetEvaluatedProject()
|
|
|
|
|
{
|
|
|
|
|
if (_cachedEvaluatedProject != null)
|
|
|
|
|
{
|
|
|
|
|
return _cachedEvaluatedProject;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
_cachedEvaluatedProject = new Project(ProjectRoot);
|
|
|
|
|
}
|
|
|
|
|
catch (InvalidProjectFileException e)
|
|
|
|
|
{
|
|
|
|
|
throw new GracefulException(string.Format(CommonLocalizableStrings.ProjectCouldNotBeEvaluated, ProjectRoot.FullPath, e.Message));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return _cachedEvaluatedProject;
|
|
|
|
|
}
|
|
|
|
|
|
2016-11-30 13:43:43 -08:00
|
|
|
|
private int RemoveProjectToProjectReferenceAlternatives(string framework, string reference)
|
|
|
|
|
{
|
|
|
|
|
int numberOfRemovedRefs = 0;
|
|
|
|
|
foreach (var r in GetIncludeAlternativesForRemoval(reference))
|
|
|
|
|
{
|
2016-12-08 12:59:14 -08:00
|
|
|
|
foreach (var existingItem in ProjectRoot.FindExistingItemsWithCondition(framework, r))
|
2016-11-30 13:43:43 -08:00
|
|
|
|
{
|
|
|
|
|
ProjectElementContainer itemGroup = existingItem.Parent;
|
|
|
|
|
itemGroup.RemoveChild(existingItem);
|
|
|
|
|
if (itemGroup.Children.Count == 0)
|
|
|
|
|
{
|
|
|
|
|
itemGroup.Parent.RemoveChild(itemGroup);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
numberOfRemovedRefs++;
|
2016-12-04 21:33:43 -08:00
|
|
|
|
Reporter.Output.WriteLine(string.Format(CommonLocalizableStrings.ProjectReferenceRemoved, r));
|
2016-11-30 13:43:43 -08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (numberOfRemovedRefs == 0)
|
|
|
|
|
{
|
2016-12-04 21:33:43 -08:00
|
|
|
|
Reporter.Output.WriteLine(string.Format(CommonLocalizableStrings.ProjectReferenceCouldNotBeFound, reference));
|
2016-11-30 13:43:43 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return numberOfRemovedRefs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Easiest way to explain rationale for this function is on the example. Let's consider following directory structure:
|
|
|
|
|
// .../a/b/p.proj <project>
|
|
|
|
|
// .../a/d/ref.proj <reference>
|
|
|
|
|
// .../a/e/f/ <current working directory>
|
|
|
|
|
// Project = /some/path/a/b/p.proj
|
|
|
|
|
//
|
|
|
|
|
// We do not know the format of passed reference so
|
|
|
|
|
// path references to consider for removal are following:
|
|
|
|
|
// - full path to ref.proj [/some/path/a/d/ref.proj]
|
|
|
|
|
// - string which is passed as reference is relative to project [../d/ref.proj]
|
|
|
|
|
// - string which is passed as reference is relative to current dir [../../d/ref.proj]
|
|
|
|
|
private IEnumerable<string> GetIncludeAlternativesForRemoval(string reference)
|
|
|
|
|
{
|
|
|
|
|
// We do not care about duplicates in case when i.e. reference is already full path
|
|
|
|
|
var ret = new List<string>();
|
|
|
|
|
ret.Add(reference);
|
|
|
|
|
|
|
|
|
|
string fullPath = Path.GetFullPath(reference);
|
|
|
|
|
ret.Add(fullPath);
|
|
|
|
|
ret.Add(PathUtility.GetRelativePath(ProjectDirectory, fullPath));
|
|
|
|
|
|
|
|
|
|
return ret;
|
2016-11-29 10:23:04 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// There is ProjectRootElement.TryOpen but it does not work as expected
|
|
|
|
|
// I.e. it returns null for some valid projects
|
|
|
|
|
private static ProjectRootElement TryOpenProject(string filename)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
return ProjectRootElement.Open(filename, new ProjectCollection(), preserveFormatting: true);
|
|
|
|
|
}
|
|
|
|
|
catch (Microsoft.Build.Exceptions.InvalidProjectFileException)
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|