static string Evaluate(this IMSBuildEvaluationContext context, ExpressionNode expression, int depth) { if (depth == maxEvaluationDepth) { throw new Exception("Property evaluation exceeded maximum depth"); } switch (expression) { case ExpressionText text: return(text.Value); case ExpressionProperty prop: { if (!prop.IsSimpleProperty) { LoggingService.LogWarning("Only simple properties are supported in imports"); return(null); } if (context.TryGetProperty(prop.Name, out var value)) { return(Evaluate(context, value.Value, depth + 1)); } return(null); } case ConcatExpression expr: { var sb = new StringBuilder(); foreach (var n in expr.Nodes) { switch (n) { case ExpressionText t: sb.Append(t.Value); continue; case ExpressionProperty p: if (!p.IsSimpleProperty) { LoggingService.LogWarning("Only simple properties are supported in imports"); return(null); } if (context.TryGetProperty(p.Name, out var value)) { sb.Append(Evaluate(context, value.Value, depth + 1)); } continue; default: LoggingService.LogWarning("Only simple properties are supported in imports"); return(null); } } return(sb.ToString()); } default: LoggingService.LogWarning("Only simple properties and expressions are supported in imports"); return(null); } }
public IEnumerable <Import> Resolve(string importExpr, string sdk) { fileEvalContext = fileEvalContext ?? new MSBuildFileEvaluationContext( parseContext.RuntimeEvaluationContext, parseContext.ProjectPath, parentFilePath); return(parseContext.ResolveImport( fileEvalContext, parentFilePath, importExpr, sdk)); }
// FIXME: need to make this more efficient. // can we ignore results where a property was simply not found? // can we tokenize it and check each level of the path exists before drilling down? // can we cache the filesystem lookups? public static IEnumerable <string> EvaluatePathWithPermutation( this IMSBuildEvaluationContext context, ExpressionNode pathExpression, string baseDirectory) { foreach (var p in EvaluateWithPermutation(context, null, pathExpression, 0)) { if (p == null) { continue; } yield return(MSBuildEscaping.FromMSBuildPath(p, baseDirectory)); } }
internal MSBuildPropertyValue Collapse(IMSBuildEvaluationContext context) { if (multiple != null) { var oldMultiple = multiple; multiple = new ExpressionNode[oldMultiple.Count]; for (int i = 0; i < multiple.Count; i++) { multiple[i] = new ExpressionText(0, context.Evaluate(oldMultiple[i]), true); } Value = multiple[0]; } else { Value = new ExpressionText(0, context.Evaluate(Value), true); } isCollapsed = true; return(this); }
internal MSBuildPropertyValue Collapse(IMSBuildEvaluationContext context) { if (multiple != null) { var oldMultiple = multiple; multiple = new string[oldMultiple.Count]; for (int i = 0; i < multiple.Count; i++) { multiple[i] = Collapse(context, oldMultiple[i]); } Value = multiple[0]; } else { Value = Collapse(context, Value); } isCollapsed = true; return(this); }
public MSBuildFileEvaluationContext( IMSBuildEvaluationContext runtimeContext, string projectPath, string thisFilePath) { this.runtimeContext = runtimeContext ?? throw new ArgumentNullException(nameof(runtimeContext)); // this file path properties if (thisFilePath != null) { values["MSBuildThisFile"] = MSBuildEscaping.EscapeString(Path.GetFileName(thisFilePath)); values["MSBuildThisFileDirectory"] = MSBuildEscaping.ToMSBuildPath(Path.GetDirectoryName(thisFilePath)) + "\\"; //"MSBuildThisFileDirectoryNoRoot" is this actually used for anything? values["MSBuildThisFileExtension"] = MSBuildEscaping.EscapeString(Path.GetExtension(thisFilePath)); values["MSBuildThisFileFullPath"] = MSBuildEscaping.ToMSBuildPath(Path.GetFullPath(thisFilePath)); values["MSBuildThisFileName"] = MSBuildEscaping.EscapeString(Path.GetFileNameWithoutExtension(thisFilePath)); } if (projectPath == null) { return; } // project path properties string escapedProjectDir = MSBuildEscaping.ToMSBuildPath(Path.GetDirectoryName(projectPath)); values["MSBuildProjectDirectory"] = escapedProjectDir; // "MSBuildProjectDirectoryNoRoot" is this actually used for anything? values["MSBuildProjectExtension"] = MSBuildEscaping.EscapeString(Path.GetExtension(projectPath)); values["MSBuildProjectFile"] = MSBuildEscaping.EscapeString(Path.GetFileName(projectPath)); values["MSBuildProjectFullPath"] = MSBuildEscaping.ToMSBuildPath(Path.GetFullPath(projectPath)); values["MSBuildProjectName"] = MSBuildEscaping.EscapeString(Path.GetFileNameWithoutExtension(projectPath)); //don't have a better value, this is as good as anything values["MSBuildStartupDirectory"] = escapedProjectDir; //HACK: we don't get a usable value for this without real evaluation so hardcode 'obj' var projectExtensionsPath = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(projectPath), "obj")); values["MSBuildProjectExtensionsPath"] = MSBuildEscaping.ToMSBuildPath(projectExtensionsPath) + "\\"; }
internal IEnumerable <Import> ResolveImport( IMSBuildEvaluationContext fileContext, string thisFilePath, ExpressionNode importExpr, string importExprString, string sdk) { //FIXME: add support for MSBuildUserExtensionsPath, the context does not currently support it if (importExprString.IndexOf("$(MSBuildUserExtensionsPath)", StringComparison.OrdinalIgnoreCase) > -1) { yield break; } //TODO: can we we re-use this context? the propvals may change between evaluations var context = new MSBuildCollectedValuesEvaluationContext(fileContext, PropertyCollector); bool foundAny = false; bool isWildcard = false; foreach (var filename in context.EvaluatePathWithPermutation(importExpr, Path.GetDirectoryName(thisFilePath))) { 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) when(IsNotCancellation(ex)) { LoggingService.LogError($"Error evaluating wildcard in import candidate '{filename}'", ex); continue; } foreach (var f in files) { Import wildImport; try { wildImport = GetCachedOrParse(importExprString, f, sdk, File.GetLastWriteTimeUtc(f)); } catch (Exception ex) when(IsNotCancellation(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(importExprString, filename, sdk, fi.LastWriteTimeUtc); } catch (Exception ex) when(IsNotCancellation(ex)) { LoggingService.LogError($"Error reading import candidate '{filename}'", ex); continue; } foundAny = true; yield return(import); continue; } //yield a placeholder for tooltips, imports pad etc to query if (!foundAny) { yield return(new Import(importExprString, sdk, null, DateTime.MinValue)); } // 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 (!foundAny && !isWildcard) { if (PreviousRootDocument == null && failedImports.Add(importExprString)) { LoggingService.LogDebug($"Could not resolve MSBuild import '{importExprString}'"); } } }
public TaskInfo CreateTaskInfo( string typeName, string assemblyName, ExpressionNode assemblyFile, string assemblyFileStr, string declaredInFile, int declaredAtOffset, IMSBuildEvaluationContext evaluationContext) { return(null); }
public TaskInfo CreateTaskInfo( string typeName, string assemblyName, string assemblyFile, string declaredInFile, int declaredAtOffset, IMSBuildEvaluationContext evaluationContext) { //ignore this, it's redundant if (assemblyName != null && assemblyName.StartsWith("Microsoft.Build.Tasks.v", StringComparison.Ordinal)) { return(null); } var tasks = GetTaskAssembly(assemblyName, assemblyFile, declaredInFile, evaluationContext); IAssemblySymbol assembly = tasks?.assembly; if (assembly == null) { //TODO log this? return(null); } string asmShortName; if (string.IsNullOrEmpty(assemblyName)) { asmShortName = Path.GetFileNameWithoutExtension(tasks.Value.path); } else { asmShortName = new AssemblyName(assemblyName).Name; } INamedTypeSymbol FindType(INamespaceSymbol ns, string name) { foreach (var m in ns.GetMembers()) { switch (m) { case INamedTypeSymbol ts: if (ts.Name == name) { return(ts); } continue; case INamespaceSymbol childNs: var found = FindType(childNs, name); if (found != null) { return(found); } continue; } } return(null); } var type = assembly.GetTypeByMetadataName(typeName) ?? FindType(assembly.GlobalNamespace, typeName); if (type == null) { switch (typeName) { case "Microsoft.Build.Tasks.RequiresFramework35SP1Assembly": case "Microsoft.Build.Tasks.ResolveNativeReference": //we don't care about these, they're not present on Mac and they're just noise return(null); } LoggingService.LogWarning($"Did not resolve {typeName}"); return(null); } var ti = new TaskInfo(type.Name, RoslynHelpers.GetDescription(type), type.GetFullName(), assemblyName, assemblyFile, declaredInFile, declaredAtOffset); PopulateTaskInfoFromType(ti, type); return(ti); }
public static IEnumerable <string> EvaluateWithPermutation(this IMSBuildEvaluationContext context, string expression) => EvaluateWithPermutation(context, null, ExpressionParser.Parse(expression), 0);
public MSBuildImportResolver(MSBuildParserContext parseContext, string parentFilePath, IMSBuildEvaluationContext fileEvalContext) { this.parseContext = parseContext; this.parentFilePath = parentFilePath; this.fileEvalContext = fileEvalContext; }
public MSBuildCollectedValuesEvaluationContext(IMSBuildEvaluationContext fileContext, PropertyValueCollector collector) { this.collector = collector; this.fileContext = fileContext; }
public static string EvaluatePath( this IMSBuildEvaluationContext context, ExpressionNode expression, string baseDirectory) => MSBuildEscaping.FromMSBuildPath(context.Evaluate(expression), baseDirectory);
public static string EvaluatePath( this IMSBuildEvaluationContext context, string expression, string baseDirectory) => context.EvaluatePath(ExpressionParser.Parse(expression), baseDirectory);
//FIXME: recursive string Collapse(IMSBuildEvaluationContext context, string expression) { var expr = ExpressionParser.Parse(expression); return(context.Evaluate(expr)); }
public static string Evaluate(this IMSBuildEvaluationContext context, ExpressionNode expression) => Evaluate(context, expression, 0);
public static string Evaluate(this IMSBuildEvaluationContext context, string expression) => Evaluate(context, ExpressionParser.Parse(expression));
static IEnumerable <string> EvaluateWithPermutation(this IMSBuildEvaluationContext context, string prefix, ExpressionNode expression, int depth) { switch (expression) { // yield plain text case ExpressionText text: yield return(prefix + text.Value); yield break; // recursively yield evaluated property case ExpressionProperty prop: { if (!prop.IsSimpleProperty) { LoggingService.LogWarning("Only simple properties are supported in imports"); break; } if (context.TryGetProperty(prop.Name, out var value)) { if (value.HasMultipleValues) { if (value.IsCollapsed) { foreach (var v in value.GetValues()) { yield return(prefix + ((ExpressionText)v).Value); } } else { foreach (var v in value.GetValues()) { foreach (var evaluated in EvaluateWithPermutation(context, prefix, v, depth + 1)) { yield return(evaluated); } } } } else { if (value.IsCollapsed) { yield return(prefix + ((ExpressionText)value.Value).Value); } else { foreach (var evaluated in EvaluateWithPermutation(context, prefix, value.Value, depth + 1)) { yield return(evaluated); } } } break; } else { yield return(prefix); } yield break; } case ConcatExpression expr: { var nodes = expr.Nodes; if (nodes.Count == 0) { yield break; } if (nodes.Count == 1) { foreach (var evaluated in EvaluateWithPermutation(context, prefix, nodes[0], depth + 1)) { yield return(evaluated); } yield break; } var zero = nodes[0]; var skip = new ExpressionNode[nodes.Count - 1]; for (int i = 1; i < nodes.Count; i++) { skip[i - 1] = nodes[i]; } foreach (var zeroVal in EvaluateWithPermutation(context, prefix, zero, depth + 1)) { ExpressionNode inner = skip.Length == 1 ? skip[0] : new ConcatExpression(0, 0, skip); foreach (var v in EvaluateWithPermutation(context, zeroVal, inner, depth + 1)) { yield return(v); } } yield break; } default: LoggingService.LogWarning("Only simple properties and expressions are supported in imports"); yield break; } }
public static IEnumerable <string> EvaluateWithPermutation(this IMSBuildEvaluationContext context, ExpressionNode expression) => EvaluateWithPermutation(context, null, expression, 0);