Example #1
0
        // 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();
        }
Example #3
0
        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"));
            }
        }
Example #4
0
        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();
            }
        }