/// <summary> /// Performs the first compilation pass, which the semantic model which will be used for fixup. /// </summary> private static Compilation PerformInitialCompilationPass(RoslynExpressionCompilerState state, IEnumerable <DataSourceWrapperInfo> models, ConcurrentBag <String> referencedAssemblies, Boolean debug) { Parallel.ForEach(models, model => { var lastSeenDataSourceAssembly = default(Assembly); for (var dataSourceType = model.DataSourceType; dataSourceType != null; dataSourceType = dataSourceType.BaseType) { var dataSourceAssembly = dataSourceType.Assembly; if (dataSourceAssembly != lastSeenDataSourceAssembly) { lastSeenDataSourceAssembly = dataSourceAssembly; referencedAssemblies.Add(dataSourceAssembly.Location); } } foreach (var reference in model.References) { referencedAssemblies.Add(reference); } foreach (var expression in model.Expressions) { expression.GenerateGetter = true; expression.GenerateSetter = true; expression.NullableFixup = false; } WriteSourceCodeForDataSourceWrapper(state, model); }); return(CompileDataSourceWrapperSources(state, null, models, referencedAssemblies, debug)); }
/// <summary> /// Emits the specified compilation to either an in-memory stream or a file. /// </summary> private static BindingExpressionCompilationResult EmitCompilation(RoslynExpressionCompilerState state, IEnumerable <DataSourceWrapperInfo> models, String output, Compilation compilation) { var outputStream = default(Stream); try { outputStream = state.GenerateInMemory ? new MemoryStream() : (Stream)File.OpenWrite(output); var options = new EmitOptions(outputNameOverride: "Ultraviolet.Presentation.CompiledExpressions.dll", debugInformationFormat: DebugInformationFormat.PortablePdb, fileAlignment: 512, baseAddress: 0x11000000); var emitResult = compilation.Emit(outputStream, options: options); if (emitResult.Success) { var assembly = state.GenerateInMemory ? Assembly.Load(((MemoryStream)outputStream).ToArray()) : null; return(BindingExpressionCompilationResult.CreateSucceeded(assembly)); } else { return(BindingExpressionCompilationResult.CreateFailed(CompilerStrings.FailedEmit, CreateBindingExpressionCompilationErrors(state, models, emitResult.Diagnostics))); } } finally { if (outputStream != null) { outputStream.Dispose(); } } }
/// <summary> /// Compiles the specified collection of view models. /// </summary> private static BindingExpressionCompilationResult CompileViewModels(RoslynExpressionCompilerState state, IEnumerable <DataSourceWrapperInfo> models, String output, Boolean debug) { state.DeleteWorkingDirectory(); var referencedAssemblies = GetDefaultReferencedAssemblies(state); var initialPassResult = PerformInitialCompilationPass(state, models, referencedAssemblies, debug); var fixupPassResult = PerformSyntaxTreeFixup(initialPassResult); if (fixupPassResult.GetDiagnostics().Where(x => x.Severity == DiagnosticSeverity.Error).Any()) { if (state.WriteErrorsToFile) { WriteErrorsToWorkingDirectory(state, models, fixupPassResult); } return(BindingExpressionCompilationResult.CreateFailed(CompilerStrings.FailedFinalPass, CreateBindingExpressionCompilationErrors(state, models, fixupPassResult.GetDiagnostics()))); } return(EmitCompilation(state, models, output, fixupPassResult)); }
/// <summary> /// Writes the source code of the specified collection of wrappers to the working directory. /// </summary> private static void WriteCompiledFilesToWorkingDirectory(RoslynExpressionCompilerState state, IEnumerable <DataSourceWrapperInfo> models) { var workingDirectory = state.GetWorkingDirectory(); Directory.CreateDirectory(workingDirectory); foreach (var model in models) { var path = Path.ChangeExtension(Path.Combine(workingDirectory, model.DataSourceWrapperName), "cs"); File.WriteAllText(path, model.DataSourceWrapperSourceCode); } }
/// <inheritdoc/> public BindingExpressionCompilationResult Compile(UltravioletContext uv, BindingExpressionCompilerOptions options) { Contract.Require(uv, nameof(uv)); Contract.Require(options, nameof(options)); if (String.IsNullOrEmpty(options.Input) || String.IsNullOrEmpty(options.Output)) { throw new ArgumentException(PresentationStrings.InvalidCompilerOptions); } var state = new RoslynExpressionCompilerState(uv) { GenerateInMemory = options.GenerateInMemory, WorkInTemporaryDirectory = options.WorkInTemporaryDirectory, WriteErrorsToFile = options.WriteErrorsToFile }; var dataSourceWrapperInfos = DataSourceLoader.GetDataSourceWrapperInfos(state, options.Input); var cacheFile = Path.ChangeExtension(options.Output, "cache"); var cacheNew = CompilerCache.FromDataSourceWrappers(this, dataSourceWrapperInfos); if (File.Exists(options.Output)) { var cacheOld = CompilerCache.TryFromFile(cacheFile); if (cacheOld != null && !options.IgnoreCache && !cacheOld.IsDifferentFrom(cacheNew)) { return(BindingExpressionCompilationResult.CreateSucceeded()); } } var result = CompileViewModels(state, dataSourceWrapperInfos, options.Output, options.GenerateDebugAssembly); if (result.Succeeded) { if (!options.GenerateInMemory) { cacheNew.Save(cacheFile); } if (!options.WriteCompiledFilesToWorkingDirectory && !options.WorkInTemporaryDirectory) { state.DeleteWorkingDirectory(); } } if (options.WriteCompiledFilesToWorkingDirectory && !options.WorkInTemporaryDirectory) { WriteCompiledFilesToWorkingDirectory(state, dataSourceWrapperInfos); } return(result); }
/// <summary> /// Creates a collection containing the assemblies which are referenced by default during compilation. /// </summary> private static ConcurrentBag <String> GetDefaultReferencedAssemblies(RoslynExpressionCompilerState state) { var referencedAssemblies = new ConcurrentBag <String>(); var netStandardRefAdditionalPaths = new List <String>(); var netStandardRefAsmDir = DependencyFinder.GetNetStandardLibraryDirFromNuGetCache(netStandardRefAdditionalPaths) ?? DependencyFinder.GetNetStandardLibraryDirFromFallback(netStandardRefAdditionalPaths); if (netStandardRefAsmDir == null && DependencyFinder.IsNuGetAvailable()) { Directory.CreateDirectory(state.GetWorkingDirectory()); DependencyFinder.DownloadNuGetExecutable(); DependencyFinder.InstallNuGetPackage(state, "NETStandard.Library", "2.0.3"); netStandardRefAsmDir = DependencyFinder.GetNetStandardLibraryDirFromWorkingDir(state.GetWorkingDirectory()); } if (netStandardRefAsmDir == null) { if (UltravioletPlatformInfo.CurrentRuntime == UltravioletRuntime.CoreCLR && UltravioletPlatformInfo.CurrentRuntimeVersion.Major > 2) { throw new InvalidOperationException(CompilerStrings.CouldNotLocateReferenceAssembliesCore3); } else { throw new InvalidOperationException(CompilerStrings.CouldNotLocateReferenceAssemblies); } } var refDllFiles = Directory.GetFiles(netStandardRefAsmDir, "*.dll"); foreach (var refDllFile in refDllFiles) { referencedAssemblies.Add(refDllFile); } foreach (var refDllFile in netStandardRefAdditionalPaths) { referencedAssemblies.Add(refDllFile); } referencedAssemblies.Add(typeof(Contract).Assembly.Location); referencedAssemblies.Add(typeof(UltravioletContext).Assembly.Location); referencedAssemblies.Add(typeof(PresentationFoundation).Assembly.Location); return(referencedAssemblies); }
/// <summary> /// Writes any compiler errors to the working directory. /// </summary> private static void WriteErrorsToWorkingDirectory(RoslynExpressionCompilerState state, IEnumerable <DataSourceWrapperInfo> models, Compilation results) { var logpath = Path.Combine(state.GetWorkingDirectory(), "Compilation Errors.txt"); try { File.Delete(logpath); } catch (DirectoryNotFoundException) { } var logdir = Path.GetDirectoryName(logpath); Directory.CreateDirectory(logdir); // NOTE: Under Mono we seem to get warnings even when "Treat Warnings as Errors" is turned off. var trueErrors = results.GetDiagnostics().Where(x => x.Location.IsInSource && x.Location.SourceTree.FilePath != "CompilerMetadata.cs" && x.Severity == DiagnosticSeverity.Error).ToList(); if (trueErrors.Count > 0) { var filesWithErrors = trueErrors.Select(x => x.Location.SourceTree.FilePath).Where(x => !String.IsNullOrEmpty(x)).Distinct(); var filesWithErrorsPretty = new Dictionary <String, String> { { String.Empty, String.Empty } }; foreach (var fileWithErrors in filesWithErrors) { var modelNameForFile = models.Where(x => x.UniqueID.ToString() == Path.GetFileNameWithoutExtension(fileWithErrors)) .Select(x => x.DataSourceWrapperName).SingleOrDefault(); var prettyFileName = Path.ChangeExtension(modelNameForFile, "cs"); filesWithErrorsPretty[fileWithErrors] = prettyFileName; var fileWithErrorsSrc = Path.GetFullPath(fileWithErrors); var fileWithErrorsDst = Path.Combine(state.GetWorkingDirectory(), prettyFileName); File.Copy(fileWithErrorsSrc, fileWithErrorsDst, true); } var errorStrings = trueErrors.Select(x => String.Format("{0}\t{1}\t{2}\t{3}", x.Id, x.GetMessage(), filesWithErrorsPretty[x.Location.SourceTree.FilePath ?? String.Empty], GetDiagnosticLine(x))); File.WriteAllLines(logpath, Enumerable.Union(new[] { "Code\tDescription\tFile\tLine" }, errorStrings)); } }
/// <summary> /// Compiles the specified data source wrapper sources into a Roslyn compilation object. /// </summary> private static Compilation CompileDataSourceWrapperSources(RoslynExpressionCompilerState state, String output, IEnumerable <DataSourceWrapperInfo> infos, IEnumerable <String> references, Boolean debug) { var trees = new ConcurrentBag <SyntaxTree>() { CSharpSyntaxTree.ParseText(WriteCompilerMetadataFile(debug), CSharpParseOptions.Default, "CompilerMetadata.cs") }; var mrefs = references.Distinct().Select(x => MetadataReference.CreateFromFile(Path.IsPathRooted(x) ? x : Assembly.Load(x).Location)); Parallel.ForEach(infos, info => { var path = state.GetWorkingFileForDataSourceWrapper(info); File.WriteAllText(path, info.DataSourceWrapperSourceCode); trees.Add(CSharpSyntaxTree.ParseText(info.DataSourceWrapperSourceCode, path: path)); }); var optimization = debug ? OptimizationLevel.Debug : OptimizationLevel.Release; var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, optimizationLevel: optimization, deterministic: true); var compilation = CSharpCompilation.Create("Ultraviolet.Presentation.CompiledExpressions.dll", trees, mrefs, options); return(compilation); }
/// <inheritdoc/> public BindingExpressionCompilationResult CompileSingleView(UltravioletContext uv, BindingExpressionCompilerOptions options) { Contract.Require(options, nameof(options)); if (String.IsNullOrEmpty(options.Input)) { throw new ArgumentException(PresentationStrings.InvalidCompilerOptions); } var definition = DataSourceLoader.CreateDataSourceDefinitionFromXml(options.RequestedViewModelNamespace, options.RequestedViewModelName, options.Input); if (definition == null) { return(BindingExpressionCompilationResult.CreateSucceeded()); } var state = new RoslynExpressionCompilerState(uv) { GenerateInMemory = options.GenerateInMemory, WorkInTemporaryDirectory = options.WorkInTemporaryDirectory, WriteErrorsToFile = options.WriteErrorsToFile }; var dataSourceWrapperInfo = DataSourceLoader.GetDataSourceWrapperInfo(state, definition.Value); var dataSourceWrapperInfos = new[] { dataSourceWrapperInfo }; var result = CompileViewModels(state, dataSourceWrapperInfos, null, options.GenerateDebugAssembly); if (result.Succeeded) { options.Output = dataSourceWrapperInfos[0].DataSourceWrapperSourceCode; } else { state.DeleteWorkingDirectory(); } return(result); }
/// <summary> /// Writes the source code for the specified data source wrapper. /// </summary> private static void WriteSourceCodeForDataSourceWrapper(RoslynExpressionCompilerState state, DataSourceWrapperInfo dataSourceWrapperInfo) { using (var writer = new DataSourceWrapperWriter()) { // Using statements var imports = Enumerable.Union(new[] { "System" }, dataSourceWrapperInfo.Imports).Distinct().OrderBy(x => x); foreach (var import in imports) { writer.WriteLine("using {0};", import); } writer.WriteLine(); // Namespace declaration var @namespace = dataSourceWrapperInfo.DataSourceDefinition.DataSourceWrapperNamespace; writer.WriteLine("namespace " + @namespace); writer.WriteLine("{"); writer.WriteLine("#pragma warning disable 1591"); writer.WriteLine("#pragma warning disable 0184"); // Data source wrapper class - main WriteSourceCodeForDataSourceWrapperClass(state, dataSourceWrapperInfo, writer); // Data source wrapper class - dependent foreach (var dependentWrapperInfo in dataSourceWrapperInfo.DependentWrapperInfos) { WriteSourceCodeForDataSourceWrapperClass(state, dependentWrapperInfo, writer); } // Namespace complete writer.WriteLine("#pragma warning restore 0184"); writer.WriteLine("#pragma warning restore 1591"); writer.WriteLine("}"); dataSourceWrapperInfo.DataSourceWrapperSourceCode = writer.ToString(); } }
/// <summary> /// Converts an array of <see cref="Diagnostic"/> instances to a collection of <see cref="BindingExpressionCompilationError"/> objects. /// </summary> private static List <BindingExpressionCompilationError> CreateBindingExpressionCompilationErrors(RoslynExpressionCompilerState state, IEnumerable <DataSourceWrapperInfo> models, ImmutableArray <Diagnostic> diagnostics) { var result = new List <BindingExpressionCompilationError>(); var workingDirectory = state.GetWorkingDirectory(); var errorsByFile = diagnostics.Where(x => x.Severity == DiagnosticSeverity.Error && x.Location.IsInSource) .GroupBy(x => Path.GetFileName(x.Location.SourceTree.FilePath)).ToDictionary(x => x.Key, x => x.ToList()); foreach (var model in models) { var dataSourceWrapperFilename = Path.GetFileName(state.GetWorkingFileForDataSourceWrapper(model)); var dataSourceErrors = default(List <Diagnostic>); if (errorsByFile.TryGetValue(dataSourceWrapperFilename, out dataSourceErrors)) { foreach (var dataSourceError in dataSourceErrors) { var fullPathToFile = model.DataSourceWrapperName; if (state.WriteErrorsToFile) { fullPathToFile = Path.GetFullPath(Path.Combine(workingDirectory, Path.ChangeExtension(model.DataSourceWrapperName, "cs"))); } var line = GetDiagnosticLine(dataSourceError); var column = GetDiagnosticColumn(dataSourceError); var errno = dataSourceError.Id; var message = dataSourceError.GetMessage(CultureInfo.InvariantCulture); result.Add(new BindingExpressionCompilationError(fullPathToFile, line, column, errno, message)); } } } return(result); }
/// <summary> /// Writes the source code for an individual data source wrapper class. /// </summary> private static void WriteSourceCodeForDataSourceWrapperClass(RoslynExpressionCompilerState state, DataSourceWrapperInfo dataSourceWrapperInfo, DataSourceWrapperWriter writer) { // Class declaration writer.WriteLine("// Generated by the UPF Binding Expression Compiler, version {0}", FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion); writer.WriteLine("[System.CLSCompliant(false)]"); writer.WriteLine("[Ultraviolet.Presentation.WrappedDataSource(typeof({0}))]", CSharpLanguage.GetCSharpTypeName(dataSourceWrapperInfo.DataSourceType)); writer.WriteLine("public sealed partial class {0} : {1}", dataSourceWrapperInfo.DataSourceWrapperName, CSharpLanguage.GetCSharpTypeName(typeof(CompiledDataSourceWrapper))); writer.WriteLine("{"); // Constructors writer.WriteLine("#region Constructors"); writer.WriteConstructor(dataSourceWrapperInfo); writer.WriteLine("#endregion"); writer.WriteLine(); // IDataSourceWrapper writer.WriteLine("#region IDataSourceWrapper"); writer.WriteIDataSourceWrapperImplementation(dataSourceWrapperInfo); writer.WriteLine("#endregion"); writer.WriteLine(); // Methods writer.WriteLine("#region Methods"); var methods = dataSourceWrapperInfo.DataSourceType.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static); foreach (var method in methods) { if (!ExpressionUtil.NeedsWrapper(method)) { continue; } writer.WriteWrapperMethod(method); } writer.WriteLine("#endregion"); writer.WriteLine(); // Properties writer.WriteLine("#region Properties"); var properties = dataSourceWrapperInfo.DataSourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static); foreach (var property in properties) { if (!ExpressionUtil.NeedsWrapper(property)) { continue; } writer.WriteWrapperProperty(property); } writer.WriteLine("#endregion"); writer.WriteLine(); // Fields writer.WriteLine("#region Fields"); var fields = dataSourceWrapperInfo.DataSourceType.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static); foreach (var field in fields) { if (!ExpressionUtil.NeedsWrapper(field)) { continue; } writer.WriteWrapperProperty(field); } writer.WriteLine("#endregion"); writer.WriteLine(); // Expressions writer.WriteLine("#region Expressions"); for (int i = 0; i < dataSourceWrapperInfo.Expressions.Count; i++) { var expressionInfo = dataSourceWrapperInfo.Expressions[i]; writer.WriteExpressionProperty(state, dataSourceWrapperInfo, expressionInfo, i); } writer.WriteLine("#endregion"); writer.WriteLine(); // Special-case binding delegates writer.WriteLine("#region Binding Delegates"); if (typeof(Controls.ContentControl).IsAssignableFrom(dataSourceWrapperInfo.DataSourceType)) { // ContentControl writer.WriteLine("public static readonly DataBindingGetter<System.Object> __GetContent = " + "new DataBindingGetter<System.Object>(vm => (({0})vm).Content);", dataSourceWrapperInfo.DataSourceWrapperName); writer.WriteLine("public static readonly DataBindingGetter<System.String> __GetContentStringFormat = " + "new DataBindingGetter<System.String>(vm => (({0})vm).ContentStringFormat);", dataSourceWrapperInfo.DataSourceWrapperName); writer.WriteLine("public static readonly DataBindingSetter<System.Object> __SetContent = " + "new DataBindingSetter<System.Object>((vm, value) => (({0})vm).Content = value);", dataSourceWrapperInfo.DataSourceWrapperName); writer.WriteLine("public static readonly DataBindingSetter<System.String> __SetContentStringFormat = " + "new DataBindingSetter<System.String>((vm, value) => (({0})vm).ContentStringFormat = value);", dataSourceWrapperInfo.DataSourceWrapperName); } writer.WriteLine("#endregion"); // Class complete writer.WriteLine("}"); }