// Automatically generates the link.xml file to prevent stripping. // Currently only used for plugin assemblies, because blanket preserving // all setting assemblies sometimes causes the IL2CPP process to fail. // For settings assemblies, the AOT stubs are good enough to fool // the static code analysis without needing this full coverage. // https://docs.unity3d.com/Manual/iphone-playerSizeOptimization.html // However, for FullSerializer, we need to preserve our custom assemblies. // This is mostly because IL2CPP will attempt to transform non-public // property setters used in deserialization into read-only accessors // that return false on PropertyInfo.CanWrite, but only in stripped builds. // Therefore, in stripped builds, FS will skip properties that should be // deserialized without any error (and that took hours of debugging to figure out). public static void GenerateLinker(string path) { var linker = new XDocument(); var linkerNode = new XElement("linker"); foreach (var pluginAssembly in PluginContainer.plugins .SelectMany(plugin => plugin.GetType() .GetAttributes <PluginRuntimeAssemblyAttribute>() .Select(a => a.assemblyName)) .Distinct()) { var assemblyNode = new XElement("assembly"); var fullnameAttribute = new XAttribute("fullname", pluginAssembly); var preserveAttribute = new XAttribute("preserve", "all"); assemblyNode.Add(fullnameAttribute); assemblyNode.Add(preserveAttribute); linkerNode.Add(assemblyNode); } linker.Add(linkerNode); PathUtility.CreateDirectoryIfNeeded(LudiqCore.Paths.transientGenerated); VersionControlUtility.Unlock(path); if (File.Exists(path)) { File.Delete(path); } // Using ToString instead of Save to omit the <?xml> declaration, // which doesn't appear in the Unity documentation page for the linker. File.WriteAllText(path, linker.ToString()); }
public void GenerateProviderScripts() { if (Directory.Exists(LudiqCore.Paths.propertyProviders)) { foreach (var file in Directory.GetFiles(LudiqCore.Paths.propertyProviders)) { File.Delete(file); } } if (Directory.Exists(LudiqCore.Paths.propertyProvidersEditor)) { foreach (var file in Directory.GetFiles(LudiqCore.Paths.propertyProvidersEditor)) { File.Delete(file); } } PathUtility.CreateDirectoryIfNeeded(LudiqCore.Paths.propertyProviders); PathUtility.CreateDirectoryIfNeeded(LudiqCore.Paths.propertyProvidersEditor); foreach (var type in Codebase.ludiqTypes.Where(SerializedPropertyUtility.HasCustomDrawer)) { var directory = Codebase.IsEditorType(type) ? LudiqCore.Paths.propertyProvidersEditor : LudiqCore.Paths.propertyProviders; var path = Path.Combine(directory, GetProviderScriptName(type) + ".cs"); VersionControlUtility.Unlock(path); File.WriteAllText(path, GenerateProviderSource(type)); } AssetDatabase.Refresh(); }
public static void GenerateStubScript(string scriptPath, IEnumerable <object> stubs) { Ensure.That(nameof(stubs)).IsNotNull(stubs); var stubWriters = stubs.Select(s => AotStubWriterProvider.instance.GetDecorator(s)).ToHashSet(); var unit = new CodeCompileUnit(); unit.StartDirectives.Add(new CodePragmaWarningDirective(CodePragmaWarningSetting.Disable, new[] { 219 })); // Disable unused variable warning var @namespace = new CodeNamespace("Ludiq.Generated.Aot"); unit.Namespaces.Add(@namespace); var @class = new CodeClassTypeDeclaration(CodeMemberModifiers.Public, "AotStubs"); @class.CustomAttributes.Add(new CodeAttributeDeclaration(Code.TypeRef(typeof(PreserveAttribute)))); @namespace.Types.Add(@class); var usedMethodNames = new HashSet <string>(); foreach (var stubWriter in stubWriters.OrderBy(sw => sw.stubMethodComment)) { if (stubWriter.skip) { continue; } var methodName = stubWriter.stubMethodName; var i = 0; while (usedMethodNames.Contains(methodName)) { methodName = stubWriter.stubMethodName + "_" + i++; } usedMethodNames.Add(methodName); var method = new CodeMethodMember(CodeMemberModifiers.Public | CodeMemberModifiers.Static, Code.TypeRef(typeof(void)), methodName, Enumerable.Empty <CodeParameterDeclaration>(), stubWriter.GetStubStatements().ToArray()); method.CustomAttributes.Add(new CodeAttributeDeclaration(Code.TypeRef(typeof(PreserveAttribute), true))); method.Comments.Add(new CodeComment(stubWriter.stubMethodComment)); @class.Members.Add(method); } PathUtility.CreateDirectoryIfNeeded(LudiqCore.Paths.transientGenerated); VersionControlUtility.Unlock(scriptPath); if (File.Exists(scriptPath)) { File.Delete(scriptPath); } using (var scriptWriter = new StreamWriter(scriptPath)) { CodeGenerator.GenerateCodeFromCompileUnit(unit, new TextCodeWriter(scriptWriter), new CodeGeneratorOptions(indentString: "\t")); } }
public static void Run(IEnumerable <string> paths, IEnumerable <ScriptReferenceReplacement> replacements, Mode mode) { if (!canRun) { var message = "Cannot run missing script resolver with the current serialization mode.\nSet the project serialization mode to 'Force Text' and try again."; if (mode == Mode.Dialog) { EditorUtility.DisplayDialog("Script Reference Resolver", message, "OK"); } else if (mode == Mode.Console) { Debug.LogWarning(message); } return; } // Doing a naive approach here: replacing the exact string by regex instead of parsing the YAML, // since Unity sometimes breaks YAML specifications. This is whitespace dependant, but it should work. var newContents = new Dictionary <string, string[]>(); var _paths = paths.ToArray(); var pathIndex = 0; var regexes = new Dictionary <ScriptReferenceReplacement, Regex>(); foreach (var replacement in replacements) { var regex = new Regex($@"\{{fileID: {replacement.previousReference.fileID}, guid: {replacement.previousReference.guid}, type: 3\}}", RegexOptions.Compiled); regexes.Add(replacement, regex); } foreach (var path in _paths) { if (newContents.ContainsKey(path)) { // Duplicate path continue; } var replaced = false; var fileContents = new List <string>(); if (mode == Mode.Dialog) { ProgressUtility.DisplayProgressBar("Script Reference Resolver", $"Analyzing '{path}'...", pathIndex++ / (float)_paths.Length); } foreach (var line in File.ReadAllLines(path)) { var newLine = line; foreach (var replacement in replacements) { newLine = regexes[replacement].Replace(newLine, (match) => { replaced = true; return($@"{{fileID: {replacement.newReference.fileID}, guid: {replacement.newReference.guid}, type: 3}}"); }); } fileContents.Add(newLine); } if (replaced) { newContents.Add(path, fileContents.ToArray()); } } pathIndex = 0; if (newContents.Count > 0) { var pathMaxLength = 40; var fileLimit = 15; var fileList = newContents.Keys.Select(p => StringUtility.PathEllipsis(PathUtility.FromProject(p), pathMaxLength)).Take(fileLimit).ToLineSeparatedString(); if (newContents.Count > fileLimit) { fileList += "\n..."; } var replace = true; if (mode == Mode.Dialog) { var message = $"Missing script references have been found in {newContents.Count} file{(newContents.Count > 1 ? "s" : "")}: \n\n{fileList}\n\nProceed with replacement?"; replace = EditorUtility.DisplayDialog("Script Reference Resolver", message, "Replace References", "Cancel"); } if (replace) { foreach (var newContent in newContents) { if (mode == Mode.Dialog) { ProgressUtility.DisplayProgressBar("Script Reference Resolver", $"Fixing '{newContent.Key}'...", pathIndex++ / (float)_paths.Length); } VersionControlUtility.Unlock(newContent.Key); File.WriteAllLines(newContent.Key, newContent.Value); } if (mode == Mode.Dialog) { EditorUtility.DisplayDialog("Script Reference Resolver", "Script references have been successfully replaced.\nRestarting Unity is recommended.", "OK"); } else if (mode == Mode.Console) { Debug.Log($"Missing script references have been replaced in {newContents.Count} file{(newContents.Count > 1 ? "s" : "")}.\nRestarting Unity is recommended.\n{fileList}\n"); } AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport); } } else { var message = "No missing script reference was found."; if (mode == Mode.Dialog) { EditorUtility.DisplayDialog("Script Reference Resolver", message, "OK"); } else if (mode == Mode.Console) { // Debug.Log(message); } } if (mode == Mode.Dialog) { ProgressUtility.ClearProgressBar(); } }