Esempio n. 1
0
        public async Task <UpdateResult> Recompile(Project gameProject, LoggerResult logger)
        {
            var result = new UpdateResult(logger);

            if (solution == null)
            {
                solution = gameProject.Solution;
            }

            // Detect new groups
            var gameProjectCompilation = await gameProject.GetCompilationAsync();

            // Generate source dependency graph
            var sourceDependencyGraph = new SourceGroup();

            // Make sure all vertices are added
            foreach (var syntaxTree in gameProjectCompilation.SyntaxTrees)
            {
                sourceDependencyGraph.AddVertex(syntaxTree);
            }

            foreach (var syntaxTree in gameProjectCompilation.SyntaxTrees)
            {
                var syntaxRoot    = syntaxTree.GetRoot();
                var semanticModel = gameProjectCompilation.GetSemanticModel(syntaxTree);

                var dependencies = new SourceDependencySyntaxVisitor(new HashSet <SyntaxTree>(gameProjectCompilation.SyntaxTrees), semanticModel).DefaultVisit(syntaxRoot);

                foreach (var dependency in dependencies)
                {
                    sourceDependencyGraph.AddEdge(new SEdge <SyntaxTree>(syntaxTree, dependency));
                }
            }

            // Generate strongly connected components for sources (group of sources that needs to be compiled together, and their dependencies)
            var stronglyConnected     = sourceDependencyGraph.CondensateStronglyConnected <SyntaxTree, SEdge <SyntaxTree>, SourceGroup>();
            var sortedConnectedGroups = stronglyConnected.TopologicalSort().ToArray();
            var connectedGroups       = ImmutableHashSet.Create(SourceGroupComparer.Default, sortedConnectedGroups);

            // Merge changes since previous time
            // 1. Tag obsolete groups (everything that don't match, and their dependencies)
            var groupsToUnload = new HashSet <SourceGroup>(SourceGroupComparer.Default);

            foreach (var sourceGroup in previousSortedConnectedGroups.Reverse())
            {
                // Does this group needs reload?
                SourceGroup newSourceGroup;
                if (connectedGroups.TryGetValue(sourceGroup, out newSourceGroup))
                {
                    // Transfer project, as it can be reused
                    newSourceGroup.Project  = sourceGroup.Project;
                    newSourceGroup.PE       = sourceGroup.PE;
                    newSourceGroup.PDB      = sourceGroup.PDB;
                    newSourceGroup.Assembly = sourceGroup.Assembly;
                }
                else
                {
                    groupsToUnload.Add(sourceGroup);
                }

                // Mark dependencies
                if (groupsToUnload.Contains(sourceGroup))
                {
                    foreach (var test in previousStronglyConnected.InEdges(sourceGroup))
                    {
                        groupsToUnload.Add(test.Source);
                    }
                }
            }

            // Generate common InternalsVisibleTo attributes
            // TODO: Find more graceful solution
            var internalsVisibleToBuilder = new StringBuilder();

            internalsVisibleToBuilder.Append("using System.Runtime.CompilerServices;");

            for (int i = 0; i < 1000; i++)
            {
                internalsVisibleToBuilder.AppendFormat(@"[assembly: InternalsVisibleTo(""{0}.Part{1}"")]", gameProject.AssemblyName, i);
            }

            var internalsVisibleToSource = CSharpSyntaxTree.ParseText(internalsVisibleToBuilder.ToString(), null, "", Encoding.UTF8).GetText();

            // 2. Compile assemblies
            foreach (var sourceGroup in sortedConnectedGroups.Reverse())
            {
                // Check if it's either a new group, or one that has been unloaded
                if (!previousConnectedGroups.Contains(sourceGroup) || groupsToUnload.Contains(sourceGroup))
                {
                    var assemblyName = gameProject.AssemblyName + ".Part" + assemblyCounter++;

                    // Create a project out of the source group
                    var project = solution.AddProject(assemblyName, assemblyName, LanguageNames.CSharp)
                                  .WithMetadataReferences(gameProject.MetadataReferences)
                                  .WithProjectReferences(gameProject.AllProjectReferences)
                                  .WithCompilationOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));

                    // Add sources
                    foreach (var syntaxTree in sourceGroup.Vertices)
                    {
                        project = project.AddDocument(syntaxTree.FilePath, syntaxTree.GetText()).Project;
                    }

                    // Add references to other sources
                    foreach (var dependencySourceGroup in stronglyConnected.OutEdges(sourceGroup))
                    {
                        project = project.AddProjectReference(new ProjectReference(dependencySourceGroup.Target.Project.Id));
                    }

                    // Make internals visible to other assembly parts
                    project = project.AddDocument("GeneratedInternalsVisibleTo", internalsVisibleToSource).Project;

                    sourceGroup.Project = project;
                    solution            = project.Solution;

                    var compilation = await project.GetCompilationAsync();

                    using (var peStream = new MemoryStream())
                        using (var pdbStream = new MemoryStream())
                        {
                            var emitResult = compilation.Emit(peStream, pdbStream);
                            result.Info($"Compiling assembly containing {sourceGroup}");

                            foreach (var diagnostic in emitResult.Diagnostics)
                            {
                                switch (diagnostic.Severity)
                                {
                                case DiagnosticSeverity.Error:
                                    result.Error(diagnostic.GetMessage());
                                    break;

                                case DiagnosticSeverity.Warning:
                                    result.Warning(diagnostic.GetMessage());
                                    break;

                                case DiagnosticSeverity.Info:
                                    result.Info(diagnostic.GetMessage());
                                    break;
                                }
                            }

                            if (!emitResult.Success)
                            {
                                result.Error($"Error compiling assembly containing {sourceGroup}");
                                break;
                            }

                            // Load csproj to evaluate assembly processor parameters
                            var msbuildProject = await Task.Run(() => VSProjectHelper.LoadProject(gameProject.FilePath));

                            if (msbuildProject.GetPropertyValue("StrideAssemblyProcessor") == "true")
                            {
                                var referenceBuild = await Task.Run(() => VSProjectHelper.CompileProjectAssemblyAsync(null, gameProject.FilePath, result, "ResolveReferences", flags: Microsoft.Build.Execution.BuildRequestDataFlags.ProvideProjectStateAfterBuild));

                                if (referenceBuild == null)
                                {
                                    result.Error("Could not properly run ResolveAssemblyReferences");
                                    break;
                                }
                                var referenceBuildResult = await referenceBuild.BuildTask;
                                if (referenceBuild.IsCanceled || result.HasErrors)
                                {
                                    break;
                                }

                                var assemblyProcessorParameters = "--parameter-key --auto-module-initializer --serialization";
                                var assemblyProcessorApp        = AssemblyProcessorProgram.CreateAssemblyProcessorApp(SplitCommandLine(assemblyProcessorParameters).ToArray(), new LoggerAssemblyProcessorWrapper(result));

                                foreach (var referencePath in referenceBuildResult.ProjectStateAfterBuild.Items.Where(x => x.ItemType == "ReferencePath"))
                                {
                                    assemblyProcessorApp.References.Add(referencePath.EvaluatedInclude);
                                    if (referencePath.EvaluatedInclude.EndsWith("Stride.SpriteStudio.Runtime.dll")) //todo hard-coded! needs to go when plug in system is in
                                    {
                                        assemblyProcessorApp.ReferencesToAdd.Add(referencePath.EvaluatedInclude);
                                    }
                                    else if (referencePath.EvaluatedInclude.EndsWith("Stride.Physics.dll")) //todo hard-coded! needs to go when plug in system is in
                                    {
                                        assemblyProcessorApp.ReferencesToAdd.Add(referencePath.EvaluatedInclude);
                                    }
                                    else if (referencePath.EvaluatedInclude.EndsWith("Stride.Particles.dll")) //todo hard-coded! needs to go when plug in system is in
                                    {
                                        assemblyProcessorApp.ReferencesToAdd.Add(referencePath.EvaluatedInclude);
                                    }
                                    else if (referencePath.EvaluatedInclude.EndsWith("Stride.Native.dll")) //todo hard-coded! needs to go when plug in system is in
                                    {
                                        assemblyProcessorApp.ReferencesToAdd.Add(referencePath.EvaluatedInclude);
                                    }
                                    else if (referencePath.EvaluatedInclude.EndsWith("Stride.UI.dll")) //todo hard-coded! needs to go when plug in system is in
                                    {
                                        assemblyProcessorApp.ReferencesToAdd.Add(referencePath.EvaluatedInclude);
                                    }
                                    else if (referencePath.EvaluatedInclude.EndsWith("Stride.Video.dll")) //todo hard-coded! needs to go when plug in system is in
                                    {
                                        assemblyProcessorApp.ReferencesToAdd.Add(referencePath.EvaluatedInclude);
                                    }
                                }

                                var assemblyResolver = assemblyProcessorApp.CreateAssemblyResolver();

                                // Add dependencies to assembly resolver
                                var recursiveDependencies = stronglyConnected.OutEdges(sourceGroup).SelectDeep(edge => stronglyConnected.OutEdges(edge.Target));
                                foreach (var dependencySourceGroup in recursiveDependencies)
                                {
                                    assemblyResolver.Register(dependencySourceGroup.Target.Assembly, dependencySourceGroup.Target.PE);
                                    assemblyProcessorApp.MemoryReferences.Add(dependencySourceGroup.Target.Assembly);
                                }

                                // Rewind streams
                                peStream.Position  = 0;
                                pdbStream.Position = 0;

                                var assemblyDefinition = AssemblyDefinition.ReadAssembly(peStream,
                                                                                         new ReaderParameters {
                                    AssemblyResolver = assemblyResolver, ReadSymbols = true, SymbolStream = pdbStream
                                });

                                // Run assembly processor
                                bool readWriteSymbols = true;
                                bool modified;
                                assemblyProcessorApp.SerializationAssembly = true;
                                if (!assemblyProcessorApp.Run(ref assemblyDefinition, ref readWriteSymbols, out modified, out var _))
                                {
                                    result.Error("Error running assembly processor");
                                    break;
                                }

                                sourceGroup.Assembly = assemblyDefinition;

                                // Write to file for now, since Cecil does not use the SymbolStream
                                var peFileName  = Path.ChangeExtension(Path.GetTempFileName(), ".dll");
                                var pdbFileName = Path.ChangeExtension(peFileName, ".pdb");
                                assemblyDefinition.Write(peFileName, new WriterParameters {
                                    WriteSymbols = true
                                });

                                sourceGroup.PE  = File.ReadAllBytes(peFileName);
                                sourceGroup.PDB = File.ReadAllBytes(pdbFileName);

                                File.Delete(peFileName);
                                File.Delete(pdbFileName);
                            }
                            else
                            {
                                sourceGroup.PE  = peStream.ToArray();
                                sourceGroup.PDB = pdbStream.ToArray();
                            }
                        }
                }
            }

            // We register unloading/loading only if everything succeeded
            if (!result.HasErrors)
            {
                // 3. Register old assemblies to unload
                foreach (var sourceGroup in previousSortedConnectedGroups)
                {
                    if (groupsToUnload.Contains(sourceGroup))
                    {
                        sourceGroup.Project  = null;
                        sourceGroup.Assembly = null;
                        result.UnloadedProjects.Add(sourceGroup);
                    }
                }

                // 4. Register new assemblies to load
                foreach (var sourceGroup in sortedConnectedGroups.Reverse())
                {
                    // Check if it's either a new group, or one that has been unloaded
                    if (!previousConnectedGroups.Contains(sourceGroup) || groupsToUnload.Contains(sourceGroup))
                    {
                        result.LoadedProjects.Add(sourceGroup);
                    }
                }

                // Set as new state
                previousSortedConnectedGroups = sortedConnectedGroups;
                previousStronglyConnected     = stronglyConnected;
                previousConnectedGroups       = connectedGroups;
            }

            return(result);
        }