in src/Build/Evaluation/Evaluator.cs [2404:2603]
private LoadImportsResult ExpandAndLoadImportsFromUnescapedImportExpression(string directoryOfImportingFile, ProjectImportElement importElement, string unescapedExpression,
bool throwOnFileNotExistsError, out List<ProjectRootElement> imports)
{
string importExpressionEscaped = _expander.ExpandIntoStringLeaveEscaped(unescapedExpression, ExpanderOptions.ExpandProperties, importElement.ProjectLocation);
ElementLocation importLocationInProject = importElement.Location;
bool atleastOneImportIgnored = false;
imports = new List<ProjectRootElement>();
string[] importFilesEscaped = null;
try
{
// Handle the case of an expression expanding to nothing specially;
// force an exception here to give a nicer message, that doesn't show the project directory in it.
if (importExpressionEscaped.Length == 0 || importExpressionEscaped.Trim().Length == 0)
{
FileUtilities.NormalizePath(EscapingUtilities.UnescapeAll(importExpressionEscaped));
}
// Expand the wildcards and provide an alphabetical order list of import statements.
importFilesEscaped = EngineFileUtilities.GetFileListEscaped(directoryOfImportingFile, importExpressionEscaped);
}
catch (Exception ex) when (ExceptionHandling.IsIoRelatedException(ex))
{
ProjectErrorUtilities.ThrowInvalidProject(importLocationInProject, "InvalidAttributeValueWithException", EscapingUtilities.UnescapeAll(importExpressionEscaped), XMakeAttributes.project, XMakeElements.import, ex.Message);
}
foreach (string importFileEscaped in importFilesEscaped)
{
string importFileUnescaped = EscapingUtilities.UnescapeAll(importFileEscaped);
// GetFileListEscaped may not return a rooted path, we need to root it. Also if there are no wild cards we still need to get the full path on the filespec.
try
{
if (directoryOfImportingFile != null && !Path.IsPathRooted(importFileUnescaped))
{
importFileUnescaped = Path.Combine(directoryOfImportingFile, importFileUnescaped);
}
// Canonicalize to eg., eliminate "\..\"
importFileUnescaped = FileUtilities.NormalizePath(importFileUnescaped);
}
catch (Exception ex) when (ExceptionHandling.IsIoRelatedException(ex))
{
ProjectErrorUtilities.ThrowInvalidProject(importLocationInProject, "InvalidAttributeValueWithException", importFileUnescaped, XMakeAttributes.project, XMakeElements.import, ex.Message);
}
// If a file is included twice, or there is a cycle of imports, we ignore all but the first import
// and issue a warning to that effect.
if (String.Equals(_projectRootElement.FullPath, importFileUnescaped, StringComparison.OrdinalIgnoreCase) /* We are trying to import ourselves */)
{
_loggingService.LogWarning(_buildEventContext, null, new BuildEventFileInfo(importLocationInProject), "SelfImport", importFileUnescaped);
atleastOneImportIgnored = true;
continue;
}
// Circular dependencies (e.g. t0.targets imports t1.targets, t1.targets imports t2.targets and t2.targets imports t0.targets) will be
// caught by the check for duplicate imports which is done later in the method. However, if the project load setting requires throwing
// on circular imports or recording duplicate-but-not-circular imports, then we need to do exclusive check for circular imports here.
if ((_loadSettings & ProjectLoadSettings.RejectCircularImports) != 0 || (_loadSettings & ProjectLoadSettings.RecordDuplicateButNotCircularImports) != 0)
{
// Check if this import introduces circularity.
if (IntroducesCircularity(importFileUnescaped, importElement))
{
// Get the full path of the MSBuild file that has this import.
string importedBy = importElement.ContainingProject.FullPath ?? String.Empty;
_loggingService.LogWarning(_buildEventContext, null, new BuildEventFileInfo(importLocationInProject), "ImportIntroducesCircularity", importFileUnescaped, importedBy);
// Throw exception if the project load settings requires us to stop the evaluation of a project when circular imports are detected.
if ((_loadSettings & ProjectLoadSettings.RejectCircularImports) != 0)
{
ProjectErrorUtilities.ThrowInvalidProject(importLocationInProject, "ImportIntroducesCircularity", importFileUnescaped, importedBy);
}
// Ignore this import and no more further processing on it.
atleastOneImportIgnored = true;
continue;
}
}
ProjectImportElement previouslyImportedAt;
bool duplicateImport = false;
if (_importsSeen.TryGetValue(importFileUnescaped, out previouslyImportedAt))
{
string parenthesizedProjectLocation = String.Empty;
// If neither file involved is the project itself, append its path in square brackets
if (previouslyImportedAt.ContainingProject != _projectRootElement && importElement.ContainingProject != _projectRootElement)
{
parenthesizedProjectLocation = "[" + _projectRootElement.FullPath + "]";
}
// TODO: Detect if the duplicate import came from an SDK attribute
_loggingService.LogWarning(_buildEventContext, null, new BuildEventFileInfo(importLocationInProject), "DuplicateImport", importFileUnescaped, previouslyImportedAt.Location.LocationString, parenthesizedProjectLocation);
duplicateImport = true;
}
ProjectRootElement importedProjectElement;
try
{
// We take the explicit loaded flag from the project ultimately being evaluated. The goal being that
// if a project system loaded a user's project, all imports (which would include property sheets and .user file)
// may impact evaluation and should be included in the weak cache without ever being cleared out to avoid
// the project system being exposed to multiple PRE instances for the same file. We only want to consider
// clearing the weak cache (and therefore setting explicitload=false) for projects the project system never
// was directly interested in (i.e. the ones that were reached for purposes of building a P2P.)
bool explicitlyLoaded = importElement.ContainingProject.IsExplicitlyLoaded;
importedProjectElement = _projectRootElementCache.Get(
importFileUnescaped,
(p, c) => ProjectRootElement.OpenProjectOrSolution(
importFileUnescaped,
new ReadOnlyConvertingDictionary<string, ProjectPropertyInstance, string>(
_data.GlobalPropertiesDictionary,
instance => ((IProperty)instance).EvaluatedValueEscaped),
_data.ExplicitToolsVersion,
_loggingService,
_projectRootElementCache,
_buildEventContext,
explicitlyLoaded),
explicitlyLoaded,
// don't care about formatting, reuse whatever is there
preserveFormatting: null);
if (duplicateImport)
{
// Only record the data if we want to record duplicate imports
if ((_loadSettings & ProjectLoadSettings.RecordDuplicateButNotCircularImports) != 0)
{
_data.RecordImportWithDuplicates(importElement, importedProjectElement,
importedProjectElement.Version);
}
// Since we have already seen this we need to not continue on in the processing.
atleastOneImportIgnored = true;
continue;
}
else
{
imports.Add(importedProjectElement);
}
}
catch (InvalidProjectFileException ex) when (ExceptionHandling.IsIoRelatedException(ex.InnerException))
{
// The import couldn't be read from disk, or something similar. In that case,
// the error message would be more useful if it pointed to the location in the importing project file instead.
// Perhaps the import tag has a typo in, for example.
// There's a specific message for file not existing
if (!File.Exists(importFileUnescaped))
{
if (!throwOnFileNotExistsError ||
(_loadSettings & ProjectLoadSettings.IgnoreMissingImports) != 0)
{
continue;
}
ProjectErrorUtilities.ThrowInvalidProject(importLocationInProject, "ImportedProjectNotFound",
importFileUnescaped);
}
else
{
// Otherwise a more generic message, still pointing to the location of the import tag
ProjectErrorUtilities.ThrowInvalidProject(importLocationInProject, "InvalidImportedProjectFile",
importFileUnescaped, ex.InnerException.Message);
}
}
// Because these expressions will never be expanded again, we
// can store the unescaped value. The only purpose of escaping is to
// avoid undesired splitting or expansion.
_importsSeen.Add(importFileUnescaped, importElement);
}
if (imports.Count > 0)
{
return LoadImportsResult.ProjectsImported;
}
if (atleastOneImportIgnored)
{
return LoadImportsResult.FoundFilesToImportButIgnored;
}
if (importFilesEscaped.Length == 0)
{
// Expression resolved to "", eg. a wildcard
return LoadImportsResult.ImportExpressionResolvedToNothing;
}
// No projects were imported, none were ignored but we did have atleast
// one file to process, which means that we did try to load a file but
// failed w/o an exception escaping from here.
// We ignore only the file not existing error, so, that is the case here
// (if @throwOnFileNotExistsError==true, then it would have thrown
// and we wouldn't be here)
return LoadImportsResult.TriedToImportButFileNotFound;
}