using System.Collections.Generic; using System.IO; using System.Linq; using UnityEditor.Build.Content; using UnityEditor.Build.Pipeline.Injector; using UnityEditor.Build.Pipeline.Interfaces; using UnityEditor.Build.Pipeline.Utilities; using UnityEngine; using UnityEngine.Build.Pipeline; namespace UnityEditor.Build.Pipeline.Tasks { #if UNITY_2018_3_OR_NEWER using BuildCompression = UnityEngine.BuildCompression; #else using BuildCompression = UnityEditor.Build.Content.BuildCompression; #endif public class ArchiveAndCompressBundles : IBuildTask { public int Version { get { return 1; } } #pragma warning disable 649 [InjectContext(ContextUsage.In)] IBuildParameters m_Parameters; [InjectContext(ContextUsage.In)] IBundleWriteData m_WriteData; [InjectContext] IBundleBuildResults m_Results; [InjectContext(ContextUsage.In, true)] IProgressTracker m_Tracker; [InjectContext(ContextUsage.In, true)] IBuildCache m_Cache; #pragma warning restore 649 CacheEntry GetCacheEntry(string bundleName, IEnumerable resources, BuildCompression compression) { var entry = new CacheEntry(); entry.Type = CacheEntry.EntryType.Data; entry.Guid = HashingMethods.Calculate("ArchiveAndCompressBundles", bundleName).ToGUID(); entry.Hash = HashingMethods.Calculate(Version, resources, compression).ToHash128(); entry.Version = Version; return entry; } static CachedInfo GetCachedInfo(IBuildCache cache, CacheEntry entry, IEnumerable resources, BundleDetails details) { var info = new CachedInfo(); info.Asset = entry; var dependencies = new HashSet(); foreach (var resource in resources) dependencies.Add(cache.GetCacheEntry(resource.fileName)); info.Dependencies = dependencies.ToArray(); info.Data = new object[] { details }; return info; } static Hash128 CalculateHashVersion(Dictionary fileOffsets, ResourceFile[] resourceFiles) { List hashes = new List(); foreach (ResourceFile file in resourceFiles) { if (file.serializedFile) { // For serialized files, we ignore the header for the hash value. // This leaves us with a hash value of just the written object data. using (var stream = new FileStream(file.fileName, FileMode.Open, FileAccess.Read)) { stream.Position = (long)fileOffsets[file.fileName]; hashes.Add(HashingMethods.CalculateStream(stream)); } } else hashes.Add(HashingMethods.CalculateFile(file.fileName)); } return HashingMethods.Calculate(hashes).ToHash128(); } public ReturnCode Run() { Dictionary fileOffsets = new Dictionary(); List>> bundleResources; { Dictionary> bundleToResources = new Dictionary>(); foreach (var pair in m_Results.WriteResults) { string bundle = m_WriteData.FileToBundle[pair.Key]; List resourceFiles; bundleToResources.GetOrAdd(bundle, out resourceFiles); resourceFiles.AddRange(pair.Value.resourceFiles); foreach (ResourceFile serializedFile in pair.Value.resourceFiles) { if (!serializedFile.serializedFile) continue; ObjectSerializedInfo firstObject = pair.Value.serializedObjects.First(x => x.header.fileName == serializedFile.fileAlias); fileOffsets[serializedFile.fileName] = firstObject.header.offset; } } bundleResources = bundleToResources.ToList(); } Dictionary> bundleDependencies = new Dictionary>(); foreach (var files in m_WriteData.AssetToFiles.Values) { if (files.IsNullOrEmpty()) continue; string bundle = m_WriteData.FileToBundle[files.First()]; HashSet dependencies; bundleDependencies.GetOrAdd(bundle, out dependencies); dependencies.UnionWith(files.Select(x => m_WriteData.FileToBundle[x])); dependencies.Remove(bundle); } IList entries = bundleResources.Select(x => GetCacheEntry(x.Key, x.Value, m_Parameters.GetCompressionForIdentifier(x.Key))).ToList(); IList cachedInfo = null; IList uncachedInfo = null; if (m_Parameters.UseCache && m_Cache != null) { m_Cache.LoadCachedData(entries, out cachedInfo); uncachedInfo = new List(); } for (int i = 0; i < bundleResources.Count; i++) { string bundleName = bundleResources[i].Key; ResourceFile[] resourceFiles = bundleResources[i].Value.ToArray(); BuildCompression compression = m_Parameters.GetCompressionForIdentifier(bundleName); string writePath; BundleDetails details; if (cachedInfo != null && cachedInfo[i] != null) { if (!m_Tracker.UpdateInfoUnchecked(string.Format("{0} (Cached)", bundleName))) return ReturnCode.Canceled; details = (BundleDetails)cachedInfo[i].Data[0]; details.FileName = m_Parameters.GetOutputFilePathForIdentifier(bundleName); HashSet dependencies; if (bundleDependencies.TryGetValue(bundleName, out dependencies)) details.Dependencies = dependencies.ToArray(); else details.Dependencies = new string[0]; writePath = string.Format("{0}/{1}", m_Cache.GetCachedArtifactsDirectory(entries[i]), bundleName); } else { if (!m_Tracker.UpdateInfoUnchecked(bundleName)) return ReturnCode.Canceled; details = new BundleDetails(); writePath = string.Format("{0}/{1}", m_Parameters.TempOutputFolder, bundleName); if (m_Parameters.UseCache && m_Cache != null) writePath = string.Format("{0}/{1}", m_Cache.GetCachedArtifactsDirectory(entries[i]), bundleName); Directory.CreateDirectory(Path.GetDirectoryName(writePath)); details.FileName = m_Parameters.GetOutputFilePathForIdentifier(bundleName); details.Crc = ContentBuildInterface.ArchiveAndCompress(resourceFiles, writePath, compression); details.Hash = CalculateHashVersion(fileOffsets, resourceFiles); HashSet dependencies; if (bundleDependencies.TryGetValue(bundleName, out dependencies)) details.Dependencies = dependencies.ToArray(); else details.Dependencies = new string[0]; if (uncachedInfo != null) uncachedInfo.Add(GetCachedInfo(m_Cache, entries[i], resourceFiles, details)); } SetOutputInformation(writePath, details.FileName, bundleName, details); } if (m_Parameters.UseCache && m_Cache != null) m_Cache.SaveCachedData(uncachedInfo); return ReturnCode.Success; } void SetOutputInformation(string writePath, string finalPath, string bundleName, BundleDetails details) { if (finalPath != writePath) { var directory = Path.GetDirectoryName(finalPath); Directory.CreateDirectory(directory); File.Copy(writePath, finalPath, true); } m_Results.BundleInfos.Add(bundleName, details); } } }