private bool GetIfMustRecompile(CompilerBuildCache assemblyCache, string outputFile)
        {
            string cacheFile = GetBuildCacheFileName(outputFile);

            if (!_fileSystem.File.Exists(cacheFile))
            {
                return(true);
            }

            CompilerBuildCache oldCache;

            try
            {
                // Load the cache file.
                using (Stream fs = _fileSystem.File.OpenRead(cacheFile))
                {
                    oldCache = _cacheSerializer.Deserialize(fs);
                }
            }
            catch
            {
                return(true);
            }

            // Check if cache is not out of sync and that all files exist.
            return(!_fileSystem.File.Exists(outputFile) ||
                   !oldCache.Equals(assemblyCache) ||
                   oldCache.SourceFiles.Any(src => !_fileSystem.File.Exists(_fileSystem.Path.Combine(ControllerDir, src.Name))));
        }
        private CompilerBuildCache PrepareBuildCache(IEnumerable <CacheFileReference> sourceFiles, string outputDir)
        {
            // Generate cache file for the specified controller dir, which is used to determine if
            // we have to rebuild the controller assembly. If one of the files has changed since last build
            // or a file is missing/added, the assembly will be rebuilt.
            var newCache = new CompilerBuildCache
            {
#if DEBUG
                BuildConfiguration = "debug",
#else
                BuildConfiguration = "release",
#endif
                Version      = FileVersionInfo.GetVersionInfo(typeof(ControllerAssemblyCompiler).Assembly.Location).FileVersion,
                Dependencies = new HashSet <CacheFileReference>(
                    RequiredDependencies
                    .Select(rd =>
                {
                    string path         = rd.IsLocal ? _fileSystem.Path.Combine(outputDir, rd.Location) : rd.Location;
                    bool includeDetails = rd.IsLocal && _fileSystem.File.Exists(path);
                    return(new CacheFileReference
                    {
                        Name = rd.Location,
                        LastModified = includeDetails ? (DateTime?)_fileSystem.File.GetLastWriteTimeUtc(path) : null,
                        Length = includeDetails ? (long?)_fileSystem.FileInfo.FromFileName(path).Length : null,
                    });
                })
                    ),
                SourceFiles = new HashSet <CacheFileReference>(sourceFiles)
            };

            return(newCache);
        }
        private void Compile(CompilerBuildCache assemblyCache, string outputFile, string docFile)
        {
            // If no changes in source files, return.
            if (!GetIfMustRecompile(assemblyCache, outputFile))
            {
                return;
            }

            // NOTE: Ensure LoaderLock Managed Debugging Assistant in Exception settings is disabled, to allow VS to run dynamic compilation within IDE.

            // Compile the source files, etc.
            string outputDir       = _fileSystem.Path.GetDirectoryName(outputFile);
            var    compilerOptions = new CompilerOptions
            {
                OutputFile           = outputFile,
                DocFile              = docFile,
                ReferencedAssemblies = assemblyCache.Dependencies.Select(d =>
                {
                    string localFilePath = _fileSystem.Path.Combine(outputDir, d.Name);
                    return(_fileSystem.File.Exists(localFilePath) ? localFilePath : d.Name);
                })
                                       .ToArray()
            };

            _compiler.CompileCode(assemblyCache.SourceFiles.Select(cs => _fileSystem.Path.Combine(ControllerDir, cs.Name)).ToArray(), compilerOptions);

            SaveBuildCache(assemblyCache, outputFile);
        }
        private void SaveBuildCache(CompilerBuildCache assemblyCache, string outputFile)
        {
            // Save the cache file.
            string cacheFile = GetBuildCacheFileName(outputFile);

            using (Stream fs = _fileSystem.File.Open(cacheFile, FileMode.Create, FileAccess.Write, FileShare.Read))
            {
                _cacheSerializer.Serialize(fs, assemblyCache);
            }
        }
        /// <inheritdoc />
        public Assembly Compile(bool force = false)
        {
            string outputDir = GetTargetDir();

            if (!_fileSystem.Directory.Exists(outputDir))
            {
                _fileSystem.Directory.CreateDirectory(outputDir);
            }
            else if (force)
            {
                // Clean out existing artifacts, which forces a new build.
                CleanArtifacts();
                _fileSystem.Directory.CreateDirectory(outputDir);
            }

            // Copy local dependencies.
            CopyLocalDependencies(AppDomain.CurrentDomain.BaseDirectory, outputDir);

            string asmShortName  = AssemblyName ?? _fileSystem.Path.GetFileNameWithoutExtension(ControllerDir);
            string asmOutputFile = _fileSystem.Path.Combine(outputDir, asmShortName + ".dll");
            string docFile       = _fileSystem.Path.Combine(outputDir, asmShortName + ".xml");

            ICollection <CacheFileReference> sourceFiles = GetCSharpFiles(ControllerDir);

            if (!sourceFiles.Any())
            {
                throw new InvalidOperationException("No controller source files found.");
            }

            CompilerBuildCache newCache = PrepareBuildCache(sourceFiles, outputDir);

            Compile(newCache, asmOutputFile, docFile);

            // Load controller assembly also into local domain. You normally wouldn't want this, because you A) couldn't unload it and B) would create security issues. May have to look at using isolated domain.
            return(Assembly.LoadFile(asmOutputFile));
        }