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 ;
using Microsoft.DotNet.Cli.Utils ;
using Microsoft.DotNet.Tools.Common ;
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-11-29 10:23:04 -08:00
public ProjectRootElement Project { get ; private set ; }
public string ProjectDirectory { get ; private set ; }
2016-11-30 13:43:43 -08:00
private MsbuildProject ( ProjectRootElement project )
2016-11-29 10:23:04 -08:00
{
Project = project ;
2016-11-30 13:43:43 -08:00
ProjectDirectory = PathUtility . EnsureTrailingSlash ( Project . 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 ;
ProjectItemGroupElement itemGroup = Project . FindUniformOrCreateItemGroupWithCondition ( ProjectItemElementType , framework ) ;
foreach ( var @ref in refs . Select ( ( r ) = > NormalizeSlashes ( r ) ) )
{
if ( Project . HasExistingItemWithCondition ( framework , @ref ) )
{
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 + + ;
itemGroup . AppendChild ( Project . CreateItemElement ( ProjectItemElementType , @ref ) ) ;
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 ;
}
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
}
}
private int RemoveProjectToProjectReferenceAlternatives ( string framework , string reference )
{
int numberOfRemovedRefs = 0 ;
foreach ( var r in GetIncludeAlternativesForRemoval ( reference ) )
{
foreach ( var existingItem in Project . FindExistingItemsWithCondition ( framework , r ) )
{
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 ;
}
}
}
}