private void CreateOrLoadGeneratedFileCache() { _generatedFileCache = GhostCompilerServiceUtils.Load <GeneratedFileGuidCache>(_generateCacheFilename); if (_generatedFileCache == null) { _generatedFileCache = CreateInstance <GeneratedFileGuidCache>(); } }
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); }
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); } }
private void LoadFileCache() { if (_fileCache != null) { return; } _fileCache = GhostCompilerServiceUtils.Load <GeneratedFileGuidCache>(_cachePath); if (_fileCache == null) { _fileCache = ScriptableObject.CreateInstance <GeneratedFileGuidCache>(); } }
internal void DomainReload() { GhostCompilerServiceUtils.DebugLog("All assemblies has been loaded. Collecting components..."); if (_regenerateAll) { _regenerateAll = false; RegenerateAll(); } else if (_settings.autoRecompile) { RegenerateAllChanges(); } }
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); } }
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); } }
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(); } }
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)); }
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); }
private void SaveSettings(GhostCompilerSettings settings) { GhostCompilerServiceUtils.Save(settings, _settingFilename); }
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); }
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(); }
//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); }