public static IEnumerable <string> EvaluateExpressionAsPaths(ExpressionNode expression, MSBuildRootDocument doc, int skipEndChars = 0) { if (expression == null) { yield return(Path.GetDirectoryName(doc.Filename)); yield break; } if (expression is ExpressionText lit) { var path = TrimEndChars(lit.GetUnescapedValue()); //FIXME handle encoding yield return(Projects.MSBuild.MSBuildProjectService.FromMSBuildPath(Path.GetDirectoryName(doc.Filename), path)); yield break; } if (!(expression is Expression expr)) { yield break; } //FIXME evaluate directly without the MSBuildEvaluationContext var sb = new StringBuilder(); for (int i = 0; i < expr.Nodes.Count; i++) { var node = expr.Nodes [i]; if (node is ExpressionText l) { var val = l.GetUnescapedValue(); if (i == expr.Nodes.Count - 1) { val = TrimEndChars(val); } sb.Append(val); } else if (node is ExpressionProperty p) { sb.Append($"$({p.Name})"); } else { yield break; } } var evalCtx = MSBuildEvaluationContext.Create(doc.RuntimeInformation, doc.Filename, doc.Filename); foreach (var variant in evalCtx.EvaluatePathWithPermutation(sb.ToString(), Path.GetDirectoryName(doc.Filename), null)) { yield return(variant); } string TrimEndChars(string s) => s.Substring(0, Math.Min(s.Length, s.Length - skipEndChars)); }
IEnumerable <Import> ResolveImport(HashSet <string> importedFiles, MSBuildRootDocument oldDoc, string projectPath, string thisFilePath, string importExpr, string sdk, PropertyValueCollector propVals, TaskMetadataBuilder taskBuilder, MSBuildSchemaProvider schemaProvider, CancellationToken token) { //FIXME: add support for MSBuildUserExtensionsPath, the context does not currently support it if (importExpr.IndexOf("$(MSBuildUserExtensionsPath)", StringComparison.OrdinalIgnoreCase) > -1) { yield break; } //TODO: re-use these contexts instead of recreating them var importEvalCtx = MSBuildEvaluationContext.Create(RuntimeInformation, projectPath, thisFilePath); bool foundAny = false; bool isWildcard = false; //the ToList is necessary because nested parses can alter the list between this yielding values foreach (var filename in importEvalCtx.EvaluatePathWithPermutation(importExpr, Path.GetDirectoryName(thisFilePath), propVals).ToList()) { if (string.IsNullOrEmpty(filename)) { continue; } //dedup if (!importedFiles.Add(filename)) { foundAny = true; continue; } //wildcards var wildcardIdx = filename.IndexOf('*'); //arbitrary limit to skip improbably short values from bad evaluation const int MIN_WILDCARD_STAR_IDX = 15; const int MIN_WILDCARD_PATTERN_IDX = 10; if (wildcardIdx > MIN_WILDCARD_STAR_IDX) { isWildcard |= true; var lastSlash = filename.LastIndexOf(Path.DirectorySeparatorChar); if (lastSlash < MIN_WILDCARD_PATTERN_IDX) { continue; } if (lastSlash > wildcardIdx) { continue; } string [] files; try { var dir = filename.Substring(0, lastSlash); if (!Directory.Exists(dir)) { continue; } //finding the folder's enough for this to "count" as resolved even if there aren't any files in it foundAny = true; var pattern = filename.Substring(lastSlash + 1); files = Directory.GetFiles(dir, pattern); } catch (Exception ex) { LoggingService.LogError($"Error evaluating wildcard in import candidate '{filename}'", ex); continue; } foreach (var f in files) { Import wildImport; try { wildImport = GetCachedOrParse(importedFiles, oldDoc, f, sdk, File.GetLastWriteTimeUtc(f), projectPath, propVals, taskBuilder, schemaProvider, token); } catch (Exception ex) { LoggingService.LogError($"Error reading wildcard import candidate '{files}'", ex); continue; } yield return(wildImport); } continue; } Import import; try { var fi = new FileInfo(filename); if (!fi.Exists) { continue; } import = GetCachedOrParse(importedFiles, oldDoc, filename, sdk, fi.LastWriteTimeUtc, projectPath, propVals, taskBuilder, schemaProvider, token); } catch (Exception ex) { LoggingService.LogError($"Error reading import candidate '{filename}'", ex); continue; } foundAny = true; yield return(import); continue; } // we skip logging for wildcards as these are generally extensibility points that are often unused // this is here (rather than being folded into the next condition) for ease of breakpointing if (isWildcard) { foundAny = true; } if (!foundAny) { if (oldDoc == null && failedImports.Add(importExpr)) { LoggingService.LogDebug($"Could not resolve MSBuild import '{importExpr}'"); } yield return(new Import(importExpr, sdk, DateTime.MinValue)); } }