Ejemplo n.º 1
0
        private static bool CompileOnce(List <string> sourceFiles, HPluginCompilationConfiguration configuration, CompilerParameters compilerParams, CSharpCodeProvider csProvider, HPluginCompilationResult result)
        {
            CompilerResults compileResult  = csProvider.CompileAssemblyFromFile(compilerParams, sourceFiles.ToArray());
            bool            compileSuccess = true;

            if (compileResult.Errors.HasErrors)
            {
                foreach (CompilerError error in compileResult.Errors)
                {
                    result.CompileErrors.Add(error);
                }
                compileSuccess = false;
            }
            else
            {
                // Get the compiled assembly
                Assembly asm = compileResult.CompiledAssembly;
                result.CompiledAssemblies.Add(asm);

                // Then go through all compiled HPlugin types and deduce the relative path of each one so that it can be associated with its savedata
                // Because of some less-than-great techniques required to do this, it may fail. If that happens, we can at least let the plugin run anyways, just without access to persistent savedata.
                try
                {
                    // We need Cecil to do this. Possible solutions with Reflection and CompilerServices get close (i.e. using StackTrace or CallerFilePath), but don't work on unknown subclassed code.
                    byte[] asmBytes = asm.ToByteArray(); // We could load the on-disk assembly, but this should be much faster, especially for large assemblies with embedded resources.
                    // Turn the compiled assembly into a stream (so we can load it with cecil)
                    using (MemoryStream memStream = new MemoryStream(asmBytes))
                    {
                        // Also load the generated pdb file (which is always placed on the disk and is not held in memory). The pdb should be in the working directory.
                        string pdbFilePath = null;
                        int    spot        = compilerParams.OutputAssembly.LastIndexOf(".dll");
                        if (spot > -1)
                        {
                            pdbFilePath = compilerParams.OutputAssembly.Substring(0, spot) + ".pdb";
                        }
                        using (FileStream pdbStream = new FileStream(pdbFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete))
                        {
                            // Load the assembly with its symbols from the pdb
                            ReaderParameters readerParameters = new ReaderParameters();
                            readerParameters.ReadSymbols  = true;
                            readerParameters.SymbolStream = pdbStream;
                            AssemblyDefinition cecilAsmDef = AssemblyDefinition.ReadAssembly(memStream, readerParameters);

                            // Find the source file path of each type, using SequencePoints
                            foreach (Type type in asm.GetTypes().Where(t => t.IsClass && t.IsSubclassOf(typeof(HPlugin))).ToList())
                            {
                                string relPath = "";

                                try
                                {
                                    string foundSourcePath = null;

                                    // Use any method defined in the user's plugin file to get a SequencePoint and thus the source file path
                                    TypeDefinition typeDef = cecilAsmDef.MainModule.GetTypes().Where(x => x.FullName == type.FullName).FirstOrDefault();
                                    foreach (MethodDefinition methodDef in typeDef.Methods)
                                    {
                                        if (foundSourcePath != null)
                                        {
                                            break;
                                        }

                                        foreach (Instruction ins in methodDef.Body.Instructions)
                                        {
                                            if (foundSourcePath != null)
                                            {
                                                break;
                                            }

                                            SequencePoint seqPoint = ins.SequencePoint;
                                            if (seqPoint != null)
                                            {
                                                if (seqPoint.Document != null)
                                                {
                                                    if (!String.IsNullOrEmpty(seqPoint.Document.Url))
                                                    {
                                                        foundSourcePath = seqPoint.Document.Url;
                                                    }
                                                }
                                            }
                                        }
                                    }

                                    string standardizedSourcePath = Path.GetFullPath(foundSourcePath).ToLowerInvariant();
                                    string standardizedRootDir    = Path.GetFullPath(configuration.UserFilesRootDirectory).ToLowerInvariant();
                                    int    spot2 = standardizedSourcePath.IndexOf(standardizedRootDir);
                                    if (spot2 >= 0)
                                    {
                                        relPath = (standardizedSourcePath.Substring(0, spot2) + standardizedSourcePath.Substring(spot2 + standardizedRootDir.Length)).TrimStart('\\', '/');
                                    }
                                }
                                catch (Exception e2) { } // Just swallow it. The plugin probably broke some protocol and thus will not have persistent savedata.

                                result.CompiledTypesSourceFileRelativePaths[type.FullName] = relPath;
                            }
                        }
                    }
                }
                catch (Exception e) { } // Swallow. Persistent savedata will not work, but the plugin(s) themself will be fine.
            }

            return(compileSuccess);
        }
Ejemplo n.º 2
0
        /// <summary>
        /// Compiles and returns a list of assemblies using the provided configuration.
        /// </summary>
        /// <param name="configuration">The configuration to use when compiling.</param>
        /// <returns>The compiled assemblies.</returns>
        public static HPluginCompilationResult Compile(HPluginCompilationConfiguration configuration)
        {
            HPluginCompilationResult result = new HPluginCompilationResult();

            // Output directory
            string outputDir = configuration.DiskOutputDirectory ?? DefaultOutputFilesDirectory;

            result.OutputDirectory = outputDir;

            try
            {
                // Load all potentially required assemblies into our appdomain
                TryLoadDotNetTerrariaReferences();

                // Compiler configuration
                CompilerParameters compilerParams = new CompilerParameters();
                compilerParams.GenerateInMemory        = true; // This just affects the compilation process (should be faster than using the disk). The output assembly and its pdb are always written to a file.
                compilerParams.GenerateExecutable      = false;
                compilerParams.CompilerOptions         = configuration.CompilerArguments;
                compilerParams.IncludeDebugInformation = true;
                compilerParams.TreatWarningsAsErrors   = false;
                // References on disk
                foreach (string filePath in configuration.ReferencesOnDisk)
                {
                    compilerParams.ReferencedAssemblies.Add(filePath);
                }
                // References in memory
                if (configuration.ReuseTemporaryFiles)
                {
                    if (Directory.Exists(TemporaryFilesDirectory))
                    {
                        foreach (string refAsmPath in Directory.GetFiles(TemporaryFilesDirectory))
                        {
                            compilerParams.ReferencedAssemblies.Add(refAsmPath);
                        }
                    }
                }
                else
                {
                    int refAsmNum = 0;
                    foreach (byte[] asmBytes in configuration.ReferencesInMemory)
                    {
                        Directory.CreateDirectory(TemporaryFilesDirectory);
                        string asmFullPath = Path.Combine(Directory.GetCurrentDirectory(), TemporaryFilesDirectory, "RefAsm" + refAsmNum + ".dll");
                        File.WriteAllBytes(asmFullPath, asmBytes);
                        compilerParams.ReferencedAssemblies.Add(asmFullPath);
                        refAsmNum++;
                    }
                }
                // Reference self (TTPlugins)
                compilerParams.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().Location);
                // Reference whatever is loaded in our appdomain, which will likely contain most of the common types and namespaces from System.
                foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
                {
                    if (!asm.IsDynamic && !asm.ReflectionOnly && !String.IsNullOrEmpty(asm.Location))
                    {
                        compilerParams.ReferencedAssemblies.Add(asm.Location);
                    }
                }

                CSharpCodeProvider csProvider = new CSharpCodeProvider();

                // If output directory already exists, clear it
                if (Directory.Exists(outputDir))
                {
                    DirectoryInfo outputDirInfo = new DirectoryInfo(outputDir);
                    try { outputDirInfo.Delete(true); }
                    catch (Exception e) { } // Swallow (for now)
                }
                Directory.CreateDirectory(outputDir);

                // Setup output and compile
                if (configuration.SingleAssemblyOutput)
                {
                    string dllName   = "TTPlugins_CompiledConglomerate.dll";
                    string pdbName   = Path.GetFileNameWithoutExtension(dllName) + ".pdb";
                    string dllOutput = Path.Combine(outputDir, dllName);
                    string pdbOutput = Path.Combine(outputDir, pdbName);
                    compilerParams.OutputAssembly = dllOutput;
                    if (CompileOnce(configuration.SourceFiles, configuration, compilerParams, csProvider, result))
                    {
                        result.OutputFilesOnDisk.Add(dllOutput);
                        result.OutputFilesOnDisk.Add(pdbOutput);
                    }
                }
                else
                {
                    foreach (string sourceFile in configuration.SourceFiles)
                    {
                        int    numShift       = 0;
                        string originDllName  = "TTPlugins_CompiledAsm_" + Path.GetFileNameWithoutExtension(sourceFile).Replace(' ', '_');
                        string dllName        = originDllName;
                        string checkDllOutput = Path.Combine(outputDir, dllName + ".dll");
                        while (File.Exists(checkDllOutput)) // Ensure no file conflicts
                        {
                            numShift++;
                            dllName        = originDllName + numShift;
                            checkDllOutput = Path.Combine(outputDir, dllName + ".dll");
                        }
                        dllName += ".dll";
                        string pdbName   = Path.GetFileNameWithoutExtension(dllName) + ".pdb";
                        string dllOutput = Path.Combine(outputDir, dllName);
                        string pdbOutput = Path.Combine(outputDir, pdbName);
                        compilerParams.OutputAssembly = dllOutput;
                        if (CompileOnce(new List <string>()
                        {
                            sourceFile
                        }, configuration, compilerParams, csProvider, result))
                        {
                            result.OutputFilesOnDisk.Add(dllOutput);
                            result.OutputFilesOnDisk.Add(pdbOutput);
                        }
                    }
                }
            }
            catch (Exception e)
            {
                result.GenericCompilationFailure = true;
            }

            // Clear temporary reference assembly files if config says to or if there was a compile failure
            if (configuration.ClearTemporaryFilesWhenDone || result.GenericCompilationFailure)
            {
                ClearTemporaryCompileFiles();
            }

            // Clear output files if config says to or if there was a compile failure
            if (configuration.DeleteOutputFilesFromDiskWhenDone || result.GenericCompilationFailure)
            {
                TryRemoveDirectory(outputDir);
                result.OutputFilesOnDisk.Clear();
            }

            return(result);
        }