Esempio n. 1
0
 private void CreateOrLoadGeneratedFileCache()
 {
     _generatedFileCache = GhostCompilerServiceUtils.Load <GeneratedFileGuidCache>(_generateCacheFilename);
     if (_generatedFileCache == null)
     {
         _generatedFileCache = CreateInstance <GeneratedFileGuidCache>();
     }
 }
Esempio n. 2
0
 private void FirstTimeInitialization()
 {
     //If autocompilation is set to true, it is necessary to force the project to regenerate any changes and/or generated files the first
     //time we open the project, in order to keep up-to-date the temp folder with the current project files
     if (_settings.autoRecompile)
     {
         GhostCompilerServiceUtils.DebugLog($"Initial load, force recompilation of everything");
         _regenerateAll = true;
     }
 }
        private void CopyChangedGeneratedFiles(DirectoryInfo sourceFolderInfo, string destFolder)
        {
            GhostCompilerServiceUtils.DebugLog($"-- Sync {destFolder}");

            bool ShouldCopyAssemblyDefinition(string originalAssemblyName)
            {
                return(!originalAssemblyName.StartsWith("Assembly-CSharp"));
            }

            var assemblyName   = sourceFolderInfo.Name;
            var guidDictionary = compilerCache.assemblies[sourceFolderInfo.Name].files;

            //TODO: make again an async version of this to make copy operation a little faster
            int filesCopied = 0;

            foreach (var sourceInfo in sourceFolderInfo.EnumerateFiles())
            {
                var destInfo = new FileInfo(Path.Combine(destFolder, sourceInfo.Name));
                if (sourceInfo.Name.EndsWith(".asmdef") && !ShouldCopyAssemblyDefinition(assemblyName))
                {
                    continue;
                }

                if (!destInfo.Exists)
                {
                    GhostCompilerServiceUtils.DebugLog($"Copy {sourceInfo.Name}");
                    sourceInfo.CopyTo(destInfo.FullName);
                    ++filesCopied;
                    continue;
                }
                if (sourceInfo.Length != destInfo.Length)
                {
                    GhostCompilerServiceUtils.DebugLog($"Replacing {destInfo.Name}");
                    GhostCompilerServiceUtils.CheckoutFile(destInfo);
                    sourceInfo.CopyTo(destInfo.FullName, true);
                    ++filesCopied;
                    continue;
                }
                var guid = GhostCompilerServiceUtils.ComputeFileGuid(destInfo.FullName);

                if (!guidDictionary.ContainsKey(sourceInfo.Name))
                {
                    UnityEngine.Debug.LogError($"Could not find {sourceInfo.Name}");
                    continue;
                }
                if (guid != guidDictionary[sourceInfo.Name])
                {
                    GhostCompilerServiceUtils.DebugLog($"Replacing (guid changed) {destInfo.Name}");
                    GhostCompilerServiceUtils.CheckoutFile(destInfo);
                    sourceInfo.CopyTo(destInfo.FullName, true);
                    ++filesCopied;
                }
            }
            _numChanges += filesCopied;
        }
        //Folders to update contains a relative path to the temporary output directory
        public bool SyncFolders()
        {
            GhostCompilerServiceUtils.DebugLog($"---- Syncing folder {options.destFolder} ----");
            var rootOutputFolderInfo = new DirectoryInfo(options.destFolder);

            if (!rootOutputFolderInfo.Exists)
            {
                rootOutputFolderInfo.Create();
            }

            _numChanges = 0;
            //If we force to replace everything just remove everything
            if (options.forceReplaceFolders)
            {
                FileUtil.DeleteFileOrDirectory(options.destFolder);
                ++_numChanges;
            }

            //Remove everything from the temp and dest folder that is not present in the generated file cache
            foreach (var sourceSubFolder in new DirectoryInfo(options.sourceFolder).GetDirectories())
            {
                if (!compilerCache.assemblies.ContainsKey(sourceSubFolder.Name))
                {
                    FileUtil.DeleteFileOrDirectory(sourceSubFolder.FullName);
                    FileUtil.DeleteFileOrDirectory(MapGeneratedAssemblyToOutputFolder(options.destFolder, sourceSubFolder.Name));
                    ++_numChanges;
                }
            }

            if (!options.forceReplaceFolders && !options.keepOrphans)
            {
                GhostCompilerServiceUtils.DebugLog($"-- Check for orphans in {options.destFolder}");
                RemoveOrphanInFolder(options.destFolder, options.sourceFolder);
            }

            ////Copy to the destination folder any missing and successfully built assembly
            foreach (var sourceFolderInfo in new DirectoryInfo(options.sourceFolder).EnumerateDirectories())
            {
                if (compilerCache.assemblies[sourceFolderInfo.Name].compilationErrors != 0)
                {
                    GhostCompilerServiceUtils.DebugLog($"Skipped {sourceFolderInfo.Name} because build failed");
                    continue;
                }
                var destFolder = MapGeneratedAssemblyToOutputFolder(options.destFolder, sourceFolderInfo.Name);
                if (!Directory.Exists(destFolder))
                {
                    Directory.CreateDirectory(destFolder);
                }

                CopyChangedGeneratedFiles(sourceFolderInfo, destFolder);
            }
            return(_numChanges > 0);
        }
Esempio n. 5
0
 internal void DomainReload()
 {
     GhostCompilerServiceUtils.DebugLog("All assemblies has been loaded. Collecting components...");
     if (_regenerateAll)
     {
         _regenerateAll = false;
         RegenerateAll();
     }
     else if (_settings.autoRecompile)
     {
         System.Threading.Interlocked.Increment(ref _regenerateChangeCount);
     }
 }
Esempio n. 6
0
        private void LoadFileCache()
        {
            if (_fileCache != null)
            {
                return;
            }

            _fileCache = GhostCompilerServiceUtils.Load <GeneratedFileGuidCache>(_cachePath);
            if (_fileCache == null)
            {
                _fileCache = ScriptableObject.CreateInstance <GeneratedFileGuidCache>();
            }
        }
Esempio n. 7
0
        internal void DomainReload()
        {
            GhostCompilerServiceUtils.DebugLog("All assemblies has been loaded. Collecting components...");

            if (_regenerateAll)
            {
                _regenerateAll = false;
                RegenerateAll();
            }
            else if (_settings.autoRecompile)
            {
                RegenerateAllChanges();
            }
        }
Esempio n. 8
0
 private void CreateOrLoadSettings()
 {
     _settings = GhostCompilerServiceUtils.Load <GhostCompilerSettings>(_settingFilename);
     if (_settings == null)
     {
         _settings = CreateInstance <GhostCompilerSettings>();
         _settings.tempOutputFolderPath = "Temp/NetCodeGenerated";
         _settings.outputFolder         = "Assets/NetCodeGenerated";
         _settings.autoRecompile        = true;
         _settings.alwaysGenerateFiles  = false;
         _settings.keepOrphans          = false;
         _settings.excludeFlags         = AssemblyFilterExcludeFlag.None;
         _settings.hideFlags            = HideFlags.HideAndDontSave;
         SaveSettings(_settings);
     }
 }
Esempio n. 9
0
        internal bool RegenerateAllChanges()
        {
            if (EditorApplication.isPlayingOrWillChangePlaymode)
            {
                //Debug.LogWarning("Cannot run code-generation while entering playmode");
                return(false);
            }
            // I need to trigger regeneration now, in presence of both errors or not.
            // In general:
            // - added assemblies are not loaded into the domain yet -> but I can detect the change
            // - remove assemblies are not remove yet -> but I can detect the change so I can remove the folders
            // - If I removed a type -> is not detected -> but cecil will so that work
            // - If I added a type -> is not detected -> but cecil will so I need to parse it to know
            //
            // All those consideration leads to the following truth table for asssemblies:
            //
            //                     Added  Removed  Changed
            //   Shoul I Gen         Y      Y        Y
            //
            // In practice I should always process all assemblies in order to remove folders
            // and update any changed script (if the user mess up the generated folder)
            // Can be optimized a little later

            // Do not generate the assembly list if we are not going to use it for anything
            if (changesFlags == ChangesFlags.None)
            {
                return(false);
            }

            var ghostProvider = GetAssemblyProvider();
            var assemblies    = ghostProvider.GetAssemblies().ToArray();

            //If any template has changed, I need a full rebuild
            if (changesFlags.HasFlag(ChangesFlags.TemplatesChanged))
            {
                GhostCompilerServiceUtils.DebugLog("Some templates changed. Force recompilation");
                return(CompileAssemblies(assemblies));
            }
            //otherwise just update the changed relevant assemblies
            if (changesFlags.HasFlag(ChangesFlags.AssembliesChanged))
            {
                GhostCompilerServiceUtils.DebugLog("Some assemblies has changed. Force recompilation");
                return(CompileAssemblies(assemblies));
            }
            return(false);
        }
        //Recursively cleanup the folders from any orphans files and directories
        private void RemoveOrphanInFolder(string destFolder, string sourceFolder)
        {
            //First validate what I'm expecting
            Debug.Assert(!Path.IsPathRooted(destFolder));

            //GhostCompilerServiceUtils.DebugLog($"orphan: check directory {destFolder}");

            //Remove any files added to the generated output folders that aren't present in the generated files set
            foreach (var fileInfo in new DirectoryInfo(destFolder).GetFiles())
            {
                //GhostCompilerServiceUtils.DebugLog($"orphan: check file {fileInfo.Name}");
                string sourceFile = fileInfo.Extension == ".meta"
                    ? Path.Combine(sourceFolder, Path.GetFileNameWithoutExtension(fileInfo.Name))
                    :Path.Combine(sourceFolder, fileInfo.Name);

                if (!File.Exists(sourceFile) && !Directory.Exists(sourceFile))
                {
                    if (!sourceFile.EndsWith("/Assembly-CSharp.Generated/Editor") || !compilerCache.assemblies.ContainsKey("Assembly-CSharp-Editor.Generated"))
                    {
                        GhostCompilerServiceUtils.DebugLog($"Deleting orphan file {fileInfo.FullName}");
                        fileInfo.Delete();
                        ++_numChanges;
                    }
                }
            }

            // Delete all the generated directories in the output root folder that aren't present in the generated assemblies
            foreach (var subFolder in Directory.GetDirectories(destFolder))
            {
                //need to remap to handle the special "Assembly-CSharp/Editor case"
                var mappedSubFolder = InverseMapOutputFolderToGeneratedAssembly(subFolder,
                                                                                options.destFolder, options.sourceFolder);

                if (!Directory.Exists(mappedSubFolder))
                {
                    GhostCompilerServiceUtils.DebugLog($"Deleting orphan folder {subFolder}");

                    GhostCompilerServiceUtils.DeleteFileOrDirectory(subFolder);
                    ++_numChanges;
                    continue;
                }
                RemoveOrphanInFolder(subFolder, mappedSubFolder);
            }
        }
Esempio n. 11
0
        private void OnUpdate()
        {
            if (_codegenTemplatesAssemblies.Length == 0)
            {
                RetrieveTemplatesFoldersAndRegisterWatcher();
            }

            if (_loadCustomOverrides)
            {
                LoadCustomGhostSnapshotValueTypes();
            }

            if (foldersToDelete.Count != 0)
            {
                foreach (var f in foldersToDelete)
                {
                    GhostCompilerServiceUtils.DebugLog(
                        $"Deleting generated folder {f}. Error in code generated files");
                    if (!AssetDatabase.DeleteAsset(f))
                    {
                        GhostCompilerServiceUtils.DeleteFileOrDirectory(f);
                    }
                }
                foldersToDelete.Clear();
                AssetDatabase.Refresh();
            }
            if (System.Threading.Interlocked.Exchange(ref _templateChangeCount, 0) != 0)
            {
                //If either a recompilation is in progress or if we already scheduled a delay regeneration don't do it again
                //Also avoid scheduling something while transitioning in between modes
                if (_settings.autoRecompile && changesFlags == ChangesFlags.None && !EditorApplication.isCompiling)
                {
                    System.Threading.Interlocked.Increment(ref _regenerateChangeCount);
                }
                changesFlags |= ChangesFlags.TemplatesChanged;
            }
            if (System.Threading.Interlocked.Exchange(ref _regenerateChangeCount, 0) != 0)
            {
                RegenerateAllChanges();
            }
        }
Esempio n. 12
0
        private void OnAssemblyCompilationFinished(string assemblyName, CompilerMessage[] messages)
        {
            //Don't track those. They are going to be regenerated because their "parent" did
            if (assemblyName.Contains(".Generated"))
            {
                return;
            }

            if (messages != null && messages.Any(msg => msg.type == CompilerMessageType.Error))
            {
                //Don't mark the assembly has changed yet but Assembly-CSharp need special treatment
                if (Path.GetFileName(assemblyName).StartsWith("Assembly-CSharp"))
                {
                    var generatedAssemblyCSharpFiles = Path.Combine(_settings.outputFolder, "Assembly-CSharp.Generated");
                    if (!Directory.Exists(generatedAssemblyCSharpFiles))
                    {
                        return;
                    }

                    //Special case for Assembly-CSharp and Assembly-CSharp-Editor
                    //If there are errors in some code-generated files, delete the generated folder
                    if (messages.Any(msg => msg.type == CompilerMessageType.Error &&
                                     msg.file.Contains(generatedAssemblyCSharpFiles)))
                    {
                        foldersToDelete.Add(generatedAssemblyCSharpFiles);
                    }
                }

                return;
            }
            if (ignoreAssemblyCSharpNextCompilation && Path.GetFileName(assemblyName).StartsWith("Assembly-CSharp"))
            {
                GhostCompilerServiceUtils.DebugLog($"Assembly {assemblyName} changes ignored");
                return;
            }

            //Mark the assembly as changed if it is not a ."Generated" one
            GhostCompilerServiceUtils.DebugLog($"Assembly {assemblyName} added to the changelist");
            changesFlags |= ChangesFlags.AssembliesChanged;
            changedAssemblies.Add(Path.GetFileNameWithoutExtension(assemblyName));
        }
Esempio n. 13
0
        public static GhostCompilerService LoadOrCreateService(string assetPath = DefaultCachePath, string tempPath = "Temp")
        {
            if (string.IsNullOrEmpty(assetPath))
            {
                assetPath = DefaultCachePath;
            }

            var service = GhostCompilerServiceUtils.Load <GhostCompilerService>(Path.Combine(tempPath, "GhostCompilerServiceTemp.asset"));

            if (service == null)
            {
                service = CreateInstance <GhostCompilerService>();
            }
            service._cachePath             = assetPath;
            service._tempPath              = tempPath;
            service._settingFilename       = Path.Combine(assetPath, "settings.asset");
            service._generateCacheFilename = Path.Combine(assetPath, "generatedfiles.asset");
            service.CreateOrLoadSettings();
            service.CreateOrLoadGeneratedFileCache();
            service.hideFlags = HideFlags.HideAndDontSave;
            return(service);
        }
Esempio n. 14
0
 private void SaveSettings(GhostCompilerSettings settings)
 {
     GhostCompilerServiceUtils.Save(settings, _settingFilename);
 }
Esempio n. 15
0
 public void Save()
 {
     Debug.Assert(!string.IsNullOrEmpty(_cachePath));
     GhostCompilerServiceUtils.Save(this, Path.Combine(_tempPath, "GhostCompilerServiceTemp.asset"));
     SaveSettings(_settings);
 }
        //Return true if the assembly folder is changed
        public bool FlushBatch(CompilationContext context)
        {
            var assemblyFolder = Path.Combine(kOutputFolderPath, context.assemblyNameGenerated);
            var assemblyEntry  = new GeneratedFileGuidCache.AssemblyEntry {
                files = new Dictionary <string, Guid>()
            };

            //Generate all the assembly guids and file entries
            foreach (var op in context.generatedBatch.m_PendingOperations)
            {
                var newGuid = GhostCompilerServiceUtils.ComputeGuidHashFor(op.Item2);
                assemblyEntry.files.Add(Path.GetFileName(op.Item1), newGuid);
            }
            context.generatedAssembly = assemblyEntry;

            if (_alwaysRegenerateAllFiles)
            {
                FileUtil.DeleteFileOrDirectory(assemblyFolder);
            }

            if (!Directory.Exists(assemblyFolder))
            {
                Directory.CreateDirectory(assemblyFolder);
                foreach (var op in context.generatedBatch.m_PendingOperations)
                {
                    var path = op.Item1;
                    File.WriteAllText(path, op.Item2);
                }
                return(true);
            }

            //Check for any changes (files added or content changed) in library/temp directory first
            //and compute the new contents guid
            if (!context.compilerCache.assemblies.TryGetValue(context.assemblyNameGenerated, out var cachedAssemblyInfo))
            {
                cachedAssemblyInfo       = new GeneratedFileGuidCache.AssemblyEntry();
                cachedAssemblyInfo.files = new Dictionary <string, Guid>();
                context.compilerCache.assemblies.Add(context.assemblyNameGenerated, cachedAssemblyInfo);
            }

            bool FileGuidChanged(string filename)
            {
                return(!cachedAssemblyInfo.files.TryGetValue(filename, out var guid) || guid != assemblyEntry.files[filename]);
            }

            bool ShouldFlushOpPredicate(Tuple <string, string> op)
            {
                return(!File.Exists(op.Item1) || FileGuidChanged(Path.GetFileName(op.Item1)));
            }

            //check for any files that need to be removed
            var toRemove = cachedAssemblyInfo.files.Select(pair => pair.Key)
                           .Where(f => !assemblyEntry.files.ContainsKey(f)).ToArray();

            var anyRemoved = toRemove.Length > 0;

            foreach (var f in toRemove)
            {
                var filePath = Path.Combine(context.outputFolder, f);
                FileUtil.DeleteFileOrDirectory(filePath);
                cachedAssemblyInfo.files.Remove(f);
            }

            bool anyWritten = false;

            foreach (var op in context.generatedBatch.m_PendingOperations.Where(ShouldFlushOpPredicate))
            {
                File.WriteAllText(op.Item1, op.Item2);
                anyWritten = true;
            }
            ;

            return(anyWritten || anyRemoved);
        }
Esempio n. 17
0
        public void Generate(IEnumerable <Assembly> assemblyEnumerable, GhostCompilerOptions options)
        {
            Directory.CreateDirectory(options.tempOutputFolderPath);
            LoadFileCache();

            var assemblies    = assemblyEnumerable.ToArray();
            var postProcessor =
                new GhostCodeGeneratorPostProcessor(options.tempOutputFolderPath, options.alwaysGenerateFiles);

            buildErrors = 0;
            var progress = 0.0f;

            void UpdateProgressBar(string step)
            {
                progress += 1.0f / assemblies.Length;
                EditorUtility.DisplayProgressBar("Generating Ghosts", step, progress);
            }

            //Removing any orphan assembly
            var toRemove = fileCache.assemblies.Keys.Where(key =>
                                                           !assemblies.Any(a => CompilationContext.AssemblyNameGenerated(a.name) == key));

            foreach (var key in toRemove.ToArray())
            {
                GhostCompilerServiceUtils.DebugLog($"Assembly {key} not present. Removing from cache");
                fileCache.assemblies.Remove(key);
                FileUtil.DeleteFileOrDirectory(Path.Combine(options.tempOutputFolderPath, key));
            }

            GhostCompilerServiceUtils.DebugLog($"--> Generating netcode files");
            var codeGenCache = new Dictionary <string, GhostCodeGen>();

            foreach (var assembly in assemblies)
            {
                var context = new CompilationContext(assembly, _fileCache, codeGenCache);
                context.excludeTypeFilter = options.excludeTypes;
                context.outputFolder      = Path.Combine(options.tempOutputFolderPath, context.assemblyNameGenerated);
                if (_fileCache.assemblies.TryGetValue(context.assemblyNameGenerated, out var assemblyEntry))
                {
                    context.generatedAssembly = assemblyEntry;
                }

                UpdateProgressBar($"Generating {context.assemblyNameGenerated}");
                try
                {
                    postProcessor.Generate(context);
                    //if there is nothing to generate for this assembly, remove it from the file cache if it is present
                    if (context.generatedBatch == null || context.generatedBatch.m_PendingOperations.Count == 0)
                    {
                        _fileCache.assemblies.Remove(context.assemblyNameGenerated);
                        FileUtil.DeleteFileOrDirectory(Path.Combine(options.tempOutputFolderPath,
                                                                    context.assemblyNameGenerated));
                        continue;
                    }

                    postProcessor.FlushBatch(context);
                    _fileCache.assemblies[context.assemblyNameGenerated] = context.generatedAssembly;
                }
                catch (Exception e)
                {
                    ++buildErrors;
                    Debug.LogException(e);
                }
            }

            GhostCompilerServiceUtils.Save(_fileCache, _cachePath);
            EditorUtility.ClearProgressBar();
        }
Esempio n. 18
0
 //Track CodeGenTemplates folders changes. In general, a template change will require a full regeneration.
 private void OnNetCodeTemplateChanged(string templateFolder)
 {
     GhostCompilerServiceUtils.DebugLog($"template {templateFolder} changed");
     System.Threading.Interlocked.Increment(ref _templateChangeCount);
 }