private static BuildArtifactsInfo CreateForStandaloneWin(string buildPath, string dataDirectoryOverride) { var dataDirectory = DropExtension(buildPath) + "_Data"; if (!string.IsNullOrEmpty(dataDirectoryOverride)) { dataDirectory = dataDirectoryOverride; } var result = CreateFromFileSystem(dataDirectory, dataDirectory, "StreamingAssets"); result.runtimeSize = result.runtimeSize.uncompressed + GetDirectorySizeNoThrow(ReliablePath.Combine(dataDirectory, "Mono")); // get the exe var additionalRuntimeSize = GetFileSizeNoThrow(buildPath); if (UnityVersionAgnostic.HasStuffInRootForStandaloneBuild) { var directory = ReliablePath.GetDirectoryName(buildPath); additionalRuntimeSize += GetFileSizeNoThrow(ReliablePath.Combine(directory, "UnityPlayer.dll"), logError: false); additionalRuntimeSize += GetFileSizeNoThrow(ReliablePath.Combine(directory, "UnityCrashHandler64.exe"), logError: false); additionalRuntimeSize += GetDirectorySizeNoThrow(ReliablePath.Combine(directory, "Mono")); } result.totalSize.uncompressed += additionalRuntimeSize; result.runtimeSize.uncompressed += additionalRuntimeSize; return(result); }
public static void RefreshModulesInfo(List<AssetInfo> infos, BuildArtifactsInfo artifactsInfo) { if (artifactsInfo.managedModules.Count <= 0) return; var modulesSuspects = infos.Where(x => x.path.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)) .ToLookup(x => ReliablePath.GetFileName(x.path)); // add modules foreach (var kv in artifactsInfo.managedModules) { var existingEntries = modulesSuspects[kv.Key].ToList(); if (existingEntries.Any()) { foreach (var entry in existingEntries) { entry.size += kv.Value.uncompressed; UpdateCompressedSize(entry, kv.Value.compressed); } } else { var entry = new AssetInfo() { path = kv.Key, size = kv.Value.uncompressed, }; UpdateCompressedSize(entry, kv.Value.compressed); infos.Add(entry); } } }
private static BuildArtifactsInfo CreateFromFileSystem(string totalSizeDir, string dataDirectory, string streamingAssetsName) { var modulesDirectory = ReliablePath.Combine(dataDirectory, "Managed"); var streamingAssetsDirectory = ReliablePath.Combine(dataDirectory, streamingAssetsName); Dictionary <string, SizePair> modules = new Dictionary <string, SizePair>(); long runtimeSize = 0; if (Directory.Exists(modulesDirectory)) { // dlls are included as assets, so don't count them as runtime size modules = Directory.GetFiles(modulesDirectory, "*.dll", SearchOption.TopDirectoryOnly) .ToDictionary(x => ReliablePath.GetFileName(x), x => (SizePair)GetFileSizeNoThrow(x)); runtimeSize = GetDirectorySizeNoThrow(modulesDirectory) - Enumerable.Sum(modules, x => x.Value.uncompressed); } var unityResources = s_unityResourcesNames .Select(x => new { Relative = x, Actual = ReliablePath.Combine(dataDirectory, x) }) .Where(x => !Directory.Exists(x.Actual)) .Select(x => new { x.Relative, File = new FileInfo(x.Actual) }) .Where(x => x.File.Exists) .ToDictionary(x => x.Relative, x => (SizePair)x.File.Length); return(new BuildArtifactsInfo() { totalSize = GetDirectorySizeNoThrow(totalSizeDir), streamingAssetsSize = GetDirectorySizeNoThrow(streamingAssetsDirectory), runtimeSize = runtimeSize, managedModules = modules, unityResources = unityResources, sceneSizes = CalculateScenesSizes(x => { var fileInfo = new FileInfo(ReliablePath.Combine(dataDirectory, x)); if (!fileInfo.Exists) { return null; } return fileInfo.Length; }) }); }
private static BuildArtifactsInfo CreateForWebGL(string buildPath) { var compressedSize = GetDirectorySizeNoThrow(buildPath); var totalSize = compressedSize; var streamingAssetsSize = GetDirectorySizeNoThrow(ReliablePath.Combine(buildPath, "StreamingAssets")); var latestReport = UnityVersionAgnostic.GetLatestBuildReport(); if (latestReport == null) { throw new System.InvalidOperationException("Unable to retreive native Unity report"); } var prop = new SerializedObject(latestReport).FindPropertyOrThrow("m_Files"); var scenes = new List <SizePair>(); var modules = new Dictionary <string, SizePair>(); for (int propIdx = 0; propIdx < prop.arraySize; ++propIdx) { var elem = prop.GetArrayElementAtIndex(propIdx); var role = elem.FindPropertyRelativeOrThrow("role").stringValue; if (role == "Scene") { var path = elem.FindPropertyRelativeOrThrow("path").stringValue; var prefix = "level"; var lastIndex = path.LastIndexOf(prefix); if (lastIndex < 0) { Log.Warning("Unexpected level path: " + path); continue; } var levelNumberStr = path.Substring(lastIndex + prefix.Length); var levelNumber = int.Parse(levelNumberStr); // pad with zeros for (int i = scenes.Count; i <= levelNumber; ++i) { scenes.Add(0); } var s = elem.FindPropertyRelative("totalSize").longValue; scenes[levelNumber] = new SizePair(s, s); } else if (role == "DependentManagedLibrary" || role == "ManagedLibrary") { var path = elem.FindPropertyRelativeOrThrow("path").stringValue; var prefix = "/Managed/"; var lastIndex = path.LastIndexOf(prefix); if (lastIndex < 0) { Log.Warning("Unexpected module path: " + path); continue; } var moduleName = path.Substring(lastIndex + prefix.Length); var s = elem.FindPropertyRelative("totalSize").longValue; modules.Add(moduleName, new SizePair(0, s)); } } // try to run 7z to get actual data size var releaseDir = ReliablePath.Combine(buildPath, "Release"); if (Directory.Exists(releaseDir)) { var buildName = buildPath.Split(new[] { "/", "\\" }, StringSplitOptions.RemoveEmptyEntries).Last(); var zipPath = ReliablePath.Combine(releaseDir, buildName + ".datagz"); var uncompressedSize = Get7ZipArchiveUncompressedSize(zipPath); if (uncompressedSize >= 0) { totalSize += uncompressedSize; totalSize -= GetFileSizeNoThrow(zipPath); } } else { var buildDir = ReliablePath.Combine(buildPath, "Build"); if (Directory.Exists(buildDir)) { foreach (var compressedFile in Directory.GetFiles(buildDir, "*.unityweb")) { var uncompressedSize = Get7ZipArchiveUncompressedSize(compressedFile); if (uncompressedSize >= 0) { totalSize += uncompressedSize; totalSize -= GetFileSizeNoThrow(compressedFile); } } } } return(new BuildArtifactsInfo() { totalSize = new SizePair(compressedSize, totalSize), streamingAssetsSize = streamingAssetsSize, sceneSizes = scenes, managedModules = modules, }); }
private static string DropExtension(string path) { return(ReliablePath.Combine(ReliablePath.GetDirectoryName(path), ReliablePath.GetFileNameWithoutExtension(path))); }
private static BuildArtifactsInfo CreateForAndroid(string buildPath, bool hasObb) { Dictionary <string, SizePair> partialResults = new Dictionary <string, SizePair>(); Dictionary <string, SizePair> managedModules = new Dictionary <string, SizePair>(); Dictionary <string, SizePair> otherAssets = new Dictionary <string, SizePair>(); const string DataDirectory = "assets/bin/Data/"; long compressedSize = 0; long uncompressedSize = 0; long streamingAssetsSize = 0; SizePair runtimeSize = new SizePair(); var unityResources = new Dictionary <string, SizePair>(); //var sources = Enumerable.Repeat(new { Source = "Apk", Files = GetFilesFromZipArchive(buildPath) }, 1); var sources = new List <KeyValuePair <string, string> >(); sources.Add(new KeyValuePair <string, string>("Apk", buildPath)); if (hasObb) { var obbPath = DropExtension(buildPath) + ".main.obb"; sources.Add(new KeyValuePair <string, string>("Obb", obbPath)); } foreach (var source in sources) { var path = source.Value; compressedSize += GetFileSizeNoThrow(path); //files = files.Concat(GetFilesFromZipArchive(obbPath)); var files = GetFilesFromZipArchive(path); foreach (var entry in files) { uncompressedSize += entry.size.uncompressed; if (entry.path.StartsWith(DataDirectory)) { var fileName = entry.path.Substring(DataDirectory.Length); if (fileName.StartsWith("level") || fileName.StartsWith("mainData")) { partialResults.Add(fileName, entry.size); } else if (fileName.StartsWith("Managed/")) { // dlls are included as assets, so don't count them as a part of runtime if (fileName.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)) { var actualFileName = ReliablePath.GetFileName(fileName); managedModules.Add(actualFileName, entry.size); } else { runtimeSize.compressed += entry.size.compressed; runtimeSize.uncompressed += entry.size.uncompressed; } } else if (s_unityResourcesNames.Contains(fileName)) { unityResources.Add(fileName, entry.size); } else { // is it a guid? var justFileName = Path.GetFileNameWithoutExtension(fileName); if (justFileName.Length == 32 && justFileName.All(x => char.IsDigit(x) || x >= 'a' && x <= 'f' || x >= 'A' && x <= 'F')) { SizePair existingEntry; if (otherAssets.TryGetValue(justFileName, out existingEntry)) { otherAssets[justFileName] = new SizePair(existingEntry.compressed + entry.size.compressed, existingEntry.uncompressed + entry.size.uncompressed); } else { otherAssets.Add(justFileName, entry.size); } } } } else if (entry.path.StartsWith("assets/")) { streamingAssetsSize += entry.size.uncompressed; } else if (entry.path.StartsWith("lib/")) { runtimeSize.compressed += entry.size.compressed; runtimeSize.uncompressed += entry.size.uncompressed; } } } var scenes = CalculateScenesSizes(x => { SizePair result; if (partialResults.TryGetValue(x, out result)) { return(result); } return(null); }); return(new BuildArtifactsInfo() { managedModules = managedModules, sceneSizes = scenes, streamingAssetsSize = streamingAssetsSize, totalSize = new SizePair(compressedSize, uncompressedSize), runtimeSize = runtimeSize, unityResources = unityResources, otherAssets = otherAssets, }); }
private static BuildArtifactsInfo CreateForIOS(string buildPath) { var dataDirectory = ReliablePath.Combine(buildPath, "Data"); return(CreateFromFileSystem(dataDirectory, dataDirectory, "Raw")); }
public void Refresh(IEnumerable <string> allAssets, IEnumerable <AssetInfo> usedAssets) { foreach (var assetPath in allAssets) { string directoryPath; bool isDirectory = true; if (Directory.Exists(assetPath)) { directoryPath = assetPath.TrimEnd('/', '\\'); } else { isDirectory = false; directoryPath = ReliablePath.GetDirectoryName(assetPath); } bool hadSpecialEditorDirectory = false; while (!string.IsNullOrEmpty(directoryPath) && IsInSpecialEditorDirectory(directoryPath)) { hadSpecialEditorDirectory = true; directoryPath = ReliablePath.GetDirectoryName(directoryPath); } // add directory entries while (!string.IsNullOrEmpty(directoryPath)) { var directoryIndex = paths.BinarySearch(directoryPath); if (directoryIndex < 0) { directoryIndex = ~directoryIndex; paths.Insert(directoryIndex, directoryPath); entries.Insert(directoryIndex, new DirectoryEntry()); } if (!isDirectory) { if (hadSpecialEditorDirectory) { ++entries[directoryIndex].editorAssetsCount; } else { ++entries[directoryIndex].assetsCount; } } entries[directoryIndex].hadSpecialEditorDirectory |= hadSpecialEditorDirectory; directoryPath = ReliablePath.GetDirectoryName(directoryPath); } } // now do the count foreach (var asset in usedAssets) { var guid = AssetDatabase.AssetPathToGUID(asset.path); if (IsInSpecialEditorDirectory(asset.path)) { continue; } if (string.IsNullOrEmpty(guid)) { continue; } var directoryPath = ReliablePath.GetDirectoryName(asset.path); while (!string.IsNullOrEmpty(directoryPath)) { var directoryIndex = paths.BinarySearch(directoryPath); if (directoryIndex >= 0) { ++entries[directoryIndex].usedAssetsCount; entries[directoryIndex].size += asset.size; directoryPath = ReliablePath.GetDirectoryName(directoryPath); } else { break; } } } }
public static Dictionary <string, long> GetLastBuildAssetsSizes(string buildLogPath, Dictionary <string, long> scenes) { Log.Debug("Using editor log at: {0}", buildLogPath); // copying log var tempFileName = ReliablePath.GetTempFileName(); Dictionary <string, long> assets = new Dictionary <string, long>(); try { File.Copy(buildLogPath, tempFileName, true); List <string> assetsLines = null; #if UNITY_2017_1_OR_NEWER var parsingLevels = false; var levelSizeRegex = new Regex(@"Level \d+ '(.*)' uses .* ([\d.]+) ([MK]B) uncompressed", RegexOptions.IgnoreCase | UnityVersionAgnostic.CompiledRegexOptions); #endif int lineCount = 0; using (ProfileSection.Create("Reading Log")) using (var reader = new StreamReader(File.Open(tempFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))) { for (string line = reader.ReadLine(); line != null; line = reader.ReadLine(), ++lineCount) { if (line.StartsWith("Used Assets") && line.EndsWith("sorted by uncompressed size:")) { Log.Debug("Found assets marker in the log at line {0}", lineCount); if (assetsLines == null) { assetsLines = new List <string>(); } else { assetsLines.Clear(); } } else if (assetsLines != null) { assetsLines.Add(line); } #if UNITY_2017_1_OR_NEWER if (line.Equals("***Player size statistics***", StringComparison.OrdinalIgnoreCase)) { Log.Debug("Found level statistics start at line {0}", lineCount); scenes.Clear(); parsingLevels = true; } else if (parsingLevels) { var match = levelSizeRegex.Match(line); if (match.Success) { Log.Debug("Found level size at line {0}: {1}", lineCount, match.Value); var path = match.Groups[1].Value; var size = SafeParseUnitySize(match.Groups[2].Value, match.Groups[3].Value, path); long existingSize; if (!scenes.TryGetValue(path, out existingSize)) { scenes.Add(path, size); } else { scenes[path] += size; } } else { Log.Debug("Level statistics ended at {0}", lineCount); parsingLevels = false; } } #endif } if (assetsLines == null) { throw new ArgumentException("No asset info found in the log"); } } // now go through all the lines until separator is found using (ProfileSection.Create("Parsing Assets")) { // line example: 8.1 mb 1.1% Assets/UI/Menu/HUB/SpriteSheets/HUBSpriteSheetMapRegions.png // unity 5.6.1 has a bug: 2.7 mb 1.$% Resources/unity_builtin_extra // mac: 2.7 mb inf% Resources/unity_builtin_extra var assetRegex = new Regex(@"^\s*(\d+\.\d+)\s*([mk]b)\s*(?:\d+\.[\d\$]+|inf)%\s*(.*)$", UnityVersionAgnostic.CompiledRegexOptions); // get assets and sizes foreach (var line in assetsLines) { var match = assetRegex.Match(line); if (!match.Success) { break; } var path = match.Groups[3].Value; if (path.StartsWith(SpriteAtlasPrefix)) { // ignore this, on Android size seems ok, but on PC & iOS it's complete bollocks // 2017_3_1: for new sprites atlases, this entry "merges" by resolution, which is utterly useless continue; } assets.Add(path, SafeParseUnitySize(match.Groups[1].Value, match.Groups[2].Value, path)); } } } finally { try { File.Delete(tempFileName); } catch (System.Exception) { } } return(assets); }