using System.Collections.Generic; using UnityEditor; using UnityEngine; using System.Linq; //Only avaliable in 2018 #if UNITY_2018_1_OR_NEWER using UnityEditor.Build.Reporting; #endif #if ADRESSABLES_1_2_0_OR_NEWER using UnityEditor.AddressableAssets; #endif namespace HeurekaGames.AssetHunterPRO { [System.Serializable] public class AH_SerializedBuildInfo { private const string mergeIdentifier = "MergedBuildInfo"; public string versionNumber; public string buildTargetInfo; public string dateTime; public ulong TotalSize; //Temporary dict for populating the asset usage data Dictionary> assetDict = new Dictionary>(); //The serialized information stored in the JSON public List AssetListUnSorted = new List(); //Only avaliable in 2018 #if UNITY_2018_1_OR_NEWER public List BuildReportInfoList = new List(); #endif //A sorted version of the assetList SortedList assetListSorted = new SortedList(); public SortedList AssetListSorted { get { return assetListSorted; } } void setMetaData() { setMetaData(EditorUserBuildSettings.activeBuildTarget.ToString()); } void setMetaData(string buildTargetInfoString) { dateTime = AH_SerializationHelper.GetDateString(); buildTargetInfo = buildTargetInfoString; versionNumber = AH_Window.VERSION; } private void addFolderToReport(string foldername) { var foo = System.IO.Directory.GetDirectories(Application.dataPath, foldername, System.IO.SearchOption.AllDirectories); //Add resources to build report var folders = (from subdirectory in System.IO.Directory.GetDirectories(Application.dataPath, foldername, System.IO.SearchOption.AllDirectories) where subdirectory.EndsWith(System.IO.Path.DirectorySeparatorChar + foldername) select subdirectory).ToArray(); //Change pats to project relative for (int i = 0; i < folders.Count(); i++) { var relativePath = folders[i] = FileUtil.GetProjectRelativePath(folders[i]); } //For some reason streamingassets folders are generated by unity when building, only to be deleted immediately after. Need to take that under consideration here. folders = folders.Where(x => AssetDatabase.IsValidFolder(x)).ToArray(); //Make sure folder result is valid if (folders != null && folders.Count() >= 1 && !string.IsNullOrEmpty(folders[0])) foreach (string assetguid in AssetDatabase.FindAssets("*", folders)) { AddBuildDependency(null, AssetDatabase.GUIDToAssetPath(assetguid)); } } private void SerializeAndSave() { setMetaData(); AH_SerializationHelper.SerializeAndSave(this); } internal void MergeWith(string fullName) { AH_SerializedBuildInfo newBuildInfo = AH_SerializationHelper.LoadBuildReport(fullName); if (newBuildInfo != null) { Debug.Log("AH: Merging with " + fullName); foreach (var newItem in newBuildInfo.AssetListUnSorted) { //If asset ID already exists if (AssetListUnSorted.Any(val => val.ID == newItem.ID)) { AH_SerializableAssetInfo OrigItem = AssetListUnSorted.Find(val => val.ID == newItem.ID); //Check if new scene ref list have entries that doesn't exist in orig bool newSceneRefsExist = newItem.Refs.Any(val => !OrigItem.Refs.Contains(val)); //Get the new refs if (newSceneRefsExist) { List newSceneRefs = newItem.Refs.FindAll(val => !OrigItem.Refs.Contains(val)); //Add them to list OrigItem.Refs.AddRange(newSceneRefs); //Does this serialize as expected? Since we are adding through a setter? } } else AssetListUnSorted.Add(newItem); } } else Debug.Log("AH: Merging failed: " + fullName); } internal bool IsMergedReport() { return buildTargetInfo.Equals(mergeIdentifier); } internal void SaveAfterMerge() { setMetaData(mergeIdentifier); AH_SerializationHelper.SerializeAndSave(this); } //Add the assets specific to buildtarget private void addBuildtargetAssets(BuildTarget buildTarget) { BuildTargetGroup targetGroup = BuildPipeline.GetBuildTargetGroup(buildTarget); List buildTargetAssetDependencies = AH_Utils.GetTargetGroupAssetDependencies(targetGroup); //Add all the textures to unsorted foreach (var item in buildTargetAssetDependencies) { AddBuildDependency(null, AssetDatabase.GetAssetPath(item)); } } //Only avaliable in 2018 #if UNITY_2018_1_OR_NEWER internal void ProcessBuildReport(BuildReport report) { TotalSize = report.summary.totalSize; foreach (var file in report.files) { BuildReportInfoList.Add(new AH_BuildReportFileInfo(file)); } } #endif //Stores in XML friendly format and saves internal void FinalizeReport(BuildTarget target) { addBuildtargetAssets(target); FinalizeReport(); } internal void FinalizeReport() { //TODO: "Note that if the Resources folder is an Editor subfolder, the Assets in it are loadable from Editor scripts but are stripped from builds." //https://docs.unity3d.com/Manual/SpecialFolders.html addFolderToReport("Resources"); addFolderToReport("StreamingAssets"); addAssetBundlesToReport(); addAdressablesToReport(); addRenderPipelinesToReport(); //Add all the used assets to list foreach (var item in assetDict) { var sceneIDs = item.Value.Select(x => AssetDatabase.AssetPathToGUID(x)).ToList(); //Id scene IDs from paths AH_SerializableAssetInfo newAssetInfo = new AH_SerializableAssetInfo(item.Key, sceneIDs); AssetListUnSorted.Add(newAssetInfo); } //TODO Clean AssetListUnsorted to make sure we dont have duplicates? How does this work with the scene refs SerializeAndSave(); } private void addRenderPipelinesToReport() { #if UNITY_2019_3_OR_NEWER var pipelines = UnityEngine.Rendering.GraphicsSettings.allConfiguredRenderPipelines; foreach (var pipeline in pipelines) { var path = AssetDatabase.GetAssetPath(pipeline); AddBuildDependency("", path); } #endif } private void addAdressablesToReport() { #if ADRESSABLES_1_2_0_OR_NEWER if (AddressableAssetSettingsDefaultObject.SettingsExists) { var fooSettings = AddressableAssetSettingsDefaultObject.Settings; //Get all the build relevant files for addressables string[] guids = AssetDatabase.FindAssets(string.Format("t:{0}", typeof(AddressableAssetSettingsDefaultObject))); foreach (var guid in guids) AddBuildDependency("", AssetDatabase.GUIDToAssetPath(guid)); guids = AssetDatabase.FindAssets(string.Format("addressables_content_state")); foreach (var guid in guids) AddBuildDependency("", AssetDatabase.GUIDToAssetPath(guid)); //TODO add addressable as path? (But that requires some refactor since we assume that to be a scene rather than an addressable NB: IF we do that we need to make sure dependencies are still being added in AddBuildDependency method AddBuildDependency("", AssetDatabase.GetAssetPath(AddressableAssetSettingsDefaultObject.Settings)); foreach (var item in AddressableAssetSettingsDefaultObject.Settings.groups) { foreach (var entry in item.entries) { AddBuildDependency("", entry.AssetPath); } } } #endif } private void addAssetBundlesToReport() { string[] assetBundleNames = AssetDatabase.GetAllAssetBundleNames(); foreach (var bundleName in assetBundleNames) { foreach (var bundledAssetPath in AssetDatabase.GetAssetPathsFromAssetBundle(bundleName)) { //TODO add assetbundle as path? (But that requires some refactor since we assume that to be a scene rather than a bundle NB: IF we do that we need to make sure dependencies are still being added in AddBuildDependency method AddBuildDependency("", bundledAssetPath); } } } internal void AddBuildDependency(string scenePath, string assetPath) { if (!assetDict.ContainsKey(assetPath)) assetDict.Add(assetPath, new List()); if (!string.IsNullOrEmpty(scenePath)) assetDict[assetPath].Add(scenePath); //This is not a scene asset so it must be in resources/streaming ressources so we need to manage dependencies manually else { string[] dependencies = AssetDatabase.GetDependencies(assetPath, false); dependencies.ToList().AddRange(AssetDatabase.GetDependencies(assetPath, true).ToList()); //Loop assets foreach (var aPath in dependencies) { //This asset is already referenced, so return if (assetDict.ContainsKey(aPath)) continue; //Dependencies also return the asset itself, and we dont want to keep looking at the same asset if (!assetPath.Equals(aPath)) AddBuildDependency(scenePath, aPath); } } } internal AH_SerializableAssetInfo GetItemInfo(string assetID) { AH_SerializableAssetInfo assetInfo; if (assetListSorted.TryGetValue(assetID, out assetInfo)) return assetInfo; else return null; } internal void Sort() { foreach (var item in AssetListUnSorted) { if (!assetListSorted.ContainsKey(item.ID)) assetListSorted.Add(item.ID, item); } } } }