first commit

This commit is contained in:
Kirill Chikalin
2024-11-16 13:20:07 +03:00
commit a3072a3693
538 changed files with 108153 additions and 0 deletions

View File

@@ -0,0 +1,16 @@
using AssetStoreTools.Utility;
namespace AssetStoreTools.Exporter
{
internal class ExportResult
{
public bool Success;
public string ExportedPath;
public ASError Error;
public static implicit operator bool(ExportResult value)
{
return value != null && value.Success;
}
}
}

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: ce99a618d1e211444b53f18bb3444f75
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 115
packageName: Asset Store Publishing Tools
packageVersion: 11.4.3
assetPath: Packages/com.unity.asset-store-tools/Editor/Exporter/ExportResult.cs
uploadId: 681981

View File

@@ -0,0 +1,18 @@
namespace AssetStoreTools.Exporter
{
public abstract class ExporterSettings
{
public string[] ExportPaths;
public string OutputFilename;
}
public class DefaultExporterSettings : ExporterSettings
{
public string[] Dependencies;
}
public class LegacyExporterSettings : ExporterSettings
{
public bool IncludeDependencies;
}
}

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 399b115514c617d47a00b8c0a5e430fd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 115
packageName: Asset Store Publishing Tools
packageVersion: 11.4.3
assetPath: Packages/com.unity.asset-store-tools/Editor/Exporter/ExporterSettings.cs
uploadId: 681981

View File

@@ -0,0 +1,137 @@
using AssetStoreTools.Utility;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using UnityEditor;
namespace AssetStoreTools.Exporter
{
internal abstract class PackageExporter
{
protected const string ProgressBarTitle = "Exporting Package";
protected const string ProgressBarStepSavingAssets = "Saving Assets...";
protected const string ProgressBarStepGatheringFiles = "Gathering files...";
protected const string ProgressBarStepCompressingPackage = "Compressing package...";
private static readonly string[] PluginFolderExtensions = { "androidlib", "bundle", "plugin", "framework", "xcframework" };
public static async Task<ExportResult> ExportPackage(ExporterSettings exportSettings)
{
if (!IsSettingsValid(exportSettings, out Exception e))
return new ExportResult() { Success = false, Error = ASError.GetGenericError(e) };
switch (exportSettings)
{
case LegacyExporterSettings legacySettings:
return await PackageExporterLegacy.ExportPackage(legacySettings);
case DefaultExporterSettings defaultSettings:
return PackageExporterDefault.ExportPackage(defaultSettings);
default:
return new ExportResult() { Success = false, Error = ASError.GetGenericError(new ArgumentException("Unrecognized ExportSettings type was provided")) };
}
}
private static bool IsSettingsValid(ExporterSettings settings, out Exception e)
{
e = null;
if (settings == null)
e = new ArgumentException("Package Exporting failed: ExporterSettings cannot be null");
else if (settings.ExportPaths == null || settings.ExportPaths.Length == 0)
e = new ArgumentException("Package Exporting failed: received an invalid export paths array");
else if (string.IsNullOrEmpty(settings.OutputFilename))
e = new ArgumentException("Package Exporting failed: received an invalid output path");
else if (settings.OutputFilename.EndsWith("/") || settings.OutputFilename.EndsWith("\\"))
e = new ArgumentException("Package Exporting failed: output path must be a valid filename and not end with a directory separator character");
return e == null;
}
protected string[] GetAssetPaths(string rootPath)
{
// To-do: slight optimization is possible in the future by having a list of excluded folders/file extensions
List<string> paths = new List<string>();
// Add files within given directory
var filePaths = Directory.GetFiles(rootPath).Select(p => p.Replace('\\', '/')).ToArray();
paths.AddRange(filePaths);
// Add directories within given directory
var directoryPaths = Directory.GetDirectories(rootPath).Select(p => p.Replace('\\', '/')).ToArray();
foreach (var nestedDirectory in directoryPaths)
paths.AddRange(GetAssetPaths(nestedDirectory));
// Add the given directory itself if it is not empty
if (filePaths.Length > 0 || directoryPaths.Length > 0)
paths.Add(rootPath);
return paths.ToArray();
}
protected string GetAssetGuid(string assetPath, bool generateForPlugin, bool hiddenSearch)
{
// Skip meta files as they do not have guids
if (assetPath.EndsWith(".meta"))
return string.Empty;
// Skip hidden assets. They normally do not have meta files, but
// have been observed to retain them in the past due to a Unity bug
if (assetPath.EndsWith("~"))
return string.Empty;
// Skip ProjectVersion.txt file specifically as it may introduce
// project compatibility issues when imported
if (string.Compare(assetPath, "ProjectSettings/ProjectVersion.txt", StringComparison.OrdinalIgnoreCase) == 0)
return string.Empty;
// Attempt retrieving guid from the Asset Database first
var guid = AssetDatabase.AssetPathToGUID(assetPath);
if (guid != string.Empty)
return guid;
// Some special folders (e.g. SomeName.framework) do not have meta files inside them.
// Their contents should be exported with any arbitrary GUID so that Unity Importer could pick them up
if (generateForPlugin && PathBelongsToPlugin(assetPath))
return GUID.Generate().ToString();
// Files in hidden folders (e.g. Samples~) are not part of the Asset Database,
// therefore GUIDs need to be scraped from the .meta file.
// Note: only do this for custom exporter since the native exporter
// will not be able to retrieve the asset path from a hidden folder
if (hiddenSearch)
{
// To-do: handle hidden folders without meta files
var metaPath = $"{assetPath}.meta";
if (!File.Exists(metaPath))
return string.Empty;
using (StreamReader reader = new StreamReader(metaPath))
{
string line;
while ((line = reader.ReadLine()) != string.Empty)
{
if (!line.StartsWith("guid:"))
continue;
var metaGuid = line.Substring("guid:".Length).Trim();
return metaGuid;
}
}
}
return string.Empty;
}
private bool PathBelongsToPlugin(string assetPath)
{
return PluginFolderExtensions.Any(extension => assetPath.ToLower().Contains($".{extension}/"));
}
protected virtual void PostExportCleanup()
{
EditorUtility.ClearProgressBar();
}
}
}

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 52ef11a59e545544fafaa99a5fa6cce9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 115
packageName: Asset Store Publishing Tools
packageVersion: 11.4.3
assetPath: Packages/com.unity.asset-store-tools/Editor/Exporter/PackageExporter.cs
uploadId: 681981

View File

@@ -0,0 +1,321 @@
using AssetStoreTools.Uploader.Utility;
using AssetStoreTools.Utility;
using AssetStoreTools.Utility.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
namespace AssetStoreTools.Exporter
{
internal class PackageExporterDefault : PackageExporter
{
private const string TemporaryExportPathName = "CustomExport";
private DefaultExporterSettings _exportSettings;
private PackageExporterDefault(DefaultExporterSettings exportSettings)
{
_exportSettings = exportSettings;
}
public static ExportResult ExportPackage(DefaultExporterSettings exportSettings)
{
var exporter = new PackageExporterDefault(exportSettings);
return exporter.ExportPackage();
}
private ExportResult ExportPackage()
{
ASDebug.Log("Using custom package exporter");
// Save assets before exporting
EditorUtility.DisplayProgressBar(ProgressBarTitle, ProgressBarStepSavingAssets, 0.1f);
AssetDatabase.SaveAssets();
try
{
// Create a temporary export path
var temporaryExportPath = GetTemporaryExportPath();
if (!Directory.Exists(temporaryExportPath))
Directory.CreateDirectory(temporaryExportPath);
// Construct an unzipped package structure
CreateTempPackageStructure(temporaryExportPath);
// Build a .unitypackage file from the temporary folder
CreateUnityPackage(temporaryExportPath, _exportSettings.OutputFilename);
EditorUtility.RevealInFinder(_exportSettings.OutputFilename);
ASDebug.Log($"Package file has been created at {_exportSettings.OutputFilename}");
return new ExportResult() { Success = true, ExportedPath = _exportSettings.OutputFilename };
}
catch (Exception e)
{
return new ExportResult() { Success = false, Error = ASError.GetGenericError(e) };
}
finally
{
PostExportCleanup();
}
}
private string GetTemporaryExportPath()
{
return $"{AssetStoreCache.TempCachePath}/{TemporaryExportPathName}";
}
private void CreateTempPackageStructure(string tempOutputPath)
{
EditorUtility.DisplayProgressBar(ProgressBarTitle, ProgressBarStepGatheringFiles, 0.4f);
var pathGuidPairs = GetPathGuidPairs(_exportSettings.ExportPaths);
// Caching asset previews takes time, so we'll start doing it as we
// iterate through assets and only retrieve them after generating the rest
// of the package structure
AssetPreview.SetPreviewTextureCacheSize(pathGuidPairs.Count + 100);
var pathObjectPairs = new Dictionary<string, UnityEngine.Object>();
foreach (var pair in pathGuidPairs)
{
var originalAssetPath = pair.Key;
var outputAssetPath = $"{tempOutputPath}/{pair.Value}";
if (Directory.Exists(outputAssetPath))
{
var path1 = File.ReadAllText($"{outputAssetPath}/pathname");
var path2 = originalAssetPath;
throw new InvalidOperationException($"Multiple assets with guid {pair.Value} have been detected " +
$"when exporting the package. Please resolve the guid conflicts and try again:\n{path1}\n{path2}");
}
Directory.CreateDirectory(outputAssetPath);
// Every exported asset has a pathname file
using (StreamWriter writer = new StreamWriter($"{outputAssetPath}/pathname"))
writer.Write(originalAssetPath);
// Only files (not folders) have an asset file
if (File.Exists(originalAssetPath))
File.Copy(originalAssetPath, $"{outputAssetPath}/asset");
// Most files and folders have an asset.meta file (but ProjectSettings folder assets do not)
if (File.Exists($"{originalAssetPath}.meta"))
File.Copy($"{originalAssetPath}.meta", $"{outputAssetPath}/asset.meta");
// To-do: handle previews in hidden folders as they are not part of the AssetDatabase
var previewObject = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(originalAssetPath);
if (previewObject == null)
continue;
// Start caching the asset preview
AssetPreview.GetAssetPreview(previewObject);
pathObjectPairs.Add(outputAssetPath, previewObject);
}
WritePreviewTextures(pathObjectPairs);
if (_exportSettings.Dependencies == null || _exportSettings.Dependencies.Length == 0)
return;
var exportDependenciesDict = JsonValue.NewDict();
var allRegistryPackages = PackageUtility.GetAllRegistryPackages();
foreach(var exportDependency in _exportSettings.Dependencies)
{
var registryPackage = allRegistryPackages.FirstOrDefault(x => x.name == exportDependency);
if (registryPackage == null)
{
// Package is either not from a registry source or does not exist in the project
UnityEngine.Debug.LogWarning($"Found an unsupported Package Manager dependency: {exportDependency}.\n" +
"This dependency is not supported in the project's manifest.json and will be skipped.");
continue;
}
exportDependenciesDict[registryPackage.name] = registryPackage.version;
}
var exportManifestJson = JsonValue.NewDict();
exportManifestJson["dependencies"] = exportDependenciesDict;
var tempManifestDirectoryPath = $"{tempOutputPath}/packagemanagermanifest";
Directory.CreateDirectory(tempManifestDirectoryPath);
var tempManifestFilePath = $"{tempManifestDirectoryPath}/asset";
File.WriteAllText(tempManifestFilePath, exportManifestJson.ToString());
}
private Dictionary<string, string> GetPathGuidPairs(string[] exportPaths)
{
var pathGuidPairs = new Dictionary<string, string>();
foreach (var exportPath in exportPaths)
{
var assetPaths = GetAssetPaths(exportPath);
foreach (var assetPath in assetPaths)
{
var guid = GetAssetGuid(assetPath, true, true);
if (string.IsNullOrEmpty(guid))
continue;
pathGuidPairs.Add(assetPath, guid);
}
}
return pathGuidPairs;
}
private void WritePreviewTextures(Dictionary<string, UnityEngine.Object> pathObjectPairs)
{
foreach (var kvp in pathObjectPairs)
{
var obj = kvp.Value;
var queuePreview = false;
switch (obj)
{
case Material _:
case TerrainLayer _:
case AudioClip _:
case Mesh _:
case Texture _:
case UnityEngine.Tilemaps.Tile _:
case GameObject _:
queuePreview = true;
break;
}
if (!queuePreview)
continue;
AssetDatabase.TryGetGUIDAndLocalFileIdentifier(obj, out var guid, out long _);
var preview = GetAssetPreviewFromGuid(guid);
if (!preview)
continue;
var thumbnailWidth = Mathf.Min(preview.width, 128);
var thumbnailHeight = Mathf.Min(preview.height, 128);
var rt = RenderTexture.GetTemporary(thumbnailWidth, thumbnailHeight, 0, RenderTextureFormat.Default, RenderTextureReadWrite.sRGB);
var copy = new Texture2D(rt.width, rt.height, TextureFormat.ARGB32, false);
RenderTexture.active = rt;
GL.Clear(true, true, new Color(0, 0, 0, 0));
Graphics.Blit(preview, rt);
copy.ReadPixels(new Rect(0, 0, copy.width, copy.height), 0, 0, false);
copy.Apply();
RenderTexture.active = null;
var bytes = copy.EncodeToPNG();
if (bytes != null && bytes.Length > 0)
{
File.WriteAllBytes(kvp.Key + "/preview.png", bytes);
}
RenderTexture.ReleaseTemporary(rt);
}
}
private Texture2D GetAssetPreviewFromGuid(string guid)
{
var method = typeof(AssetPreview).GetMethod("GetAssetPreviewFromGUID", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof(string) }, null);
var args = new object[] { guid };
return method?.Invoke(null, args) as Texture2D;
}
private void CreateUnityPackage(string pathToArchive, string outputPath)
{
if (Directory.GetDirectories(pathToArchive).Length == 0)
throw new InvalidOperationException("Unable to export package. The specified path is empty");
EditorUtility.DisplayProgressBar(ProgressBarTitle, ProgressBarStepCompressingPackage, 0.5f);
// Archiving process working path will be set to the
// temporary package path so adjust the output path accordingly
if (!Path.IsPathRooted(outputPath))
outputPath = $"{Application.dataPath.Substring(0, Application.dataPath.Length - "/Assets".Length)}/{outputPath}";
#if UNITY_EDITOR_WIN
CreateUnityPackageUniversal(pathToArchive, outputPath);
#elif UNITY_EDITOR_OSX || UNITY_EDITOR_LINUX
CreateUnityPackageOsxLinux(pathToArchive, outputPath);
#endif
}
private void CreateUnityPackageUniversal(string pathToArchive, string outputPath)
{
var _7zPath = EditorApplication.applicationContentsPath;
#if UNITY_EDITOR_WIN
_7zPath = Path.Combine(_7zPath, "Tools", "7z.exe");
#elif UNITY_EDITOR_OSX || UNITY_EDITOR_LINUX
_7zPath = Path.Combine(_7zPath, "Tools", "7za");
#endif
if (!File.Exists(_7zPath))
throw new FileNotFoundException("Archiving utility was not found in your Unity installation directory");
var argumentsTar = $"a -r -ttar -y -bd archtemp.tar .";
var result = StartProcess(_7zPath, argumentsTar, pathToArchive);
if (result != 0)
throw new Exception("Failed to compress the package");
// Create a GZIP archive
var argumentsGzip = $"a -tgzip -bd -y \"{outputPath}\" archtemp.tar";
result = StartProcess(_7zPath, argumentsGzip, pathToArchive);
if (result != 0)
throw new Exception("Failed to compress the package");
}
private void CreateUnityPackageOsxLinux(string pathToArchive, string outputPath)
{
var tarPath = "/usr/bin/tar";
if (!File.Exists(tarPath))
{
// Fallback to the universal export method
ASDebug.LogWarning("'/usr/bin/tar' executable not found. Falling back to 7za");
CreateUnityPackageUniversal(pathToArchive, outputPath);
return;
}
// Create a TAR archive
var arguments = $"-czpf \"{outputPath}\" .";
var result = StartProcess(tarPath, arguments, pathToArchive);
if (result != 0)
throw new Exception("Failed to compress the package");
}
private int StartProcess(string processPath, string arguments, string workingDirectory)
{
var info = new ProcessStartInfo()
{
FileName = processPath,
Arguments = arguments,
WorkingDirectory = workingDirectory,
CreateNoWindow = true,
UseShellExecute = false
};
using (Process process = Process.Start(info))
{
process.WaitForExit();
return process.ExitCode;
}
}
protected override void PostExportCleanup()
{
base.PostExportCleanup();
var tempExportPath = GetTemporaryExportPath();
if (Directory.Exists(tempExportPath))
Directory.Delete(tempExportPath, true);
}
}
}

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 32f50122a1b2bc2428cf8fba321e2414
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 115
packageName: Asset Store Publishing Tools
packageVersion: 11.4.3
assetPath: Packages/com.unity.asset-store-tools/Editor/Exporter/PackageExporterDefault.cs
uploadId: 681981

View File

@@ -0,0 +1,102 @@
using AssetStoreTools.Utility;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
namespace AssetStoreTools.Exporter
{
internal class PackageExporterLegacy : PackageExporter
{
private const string ExportMethodWithoutDependencies = "UnityEditor.PackageUtility.ExportPackage";
private const string ExportMethodWithDependencies = "UnityEditor.PackageUtility.ExportPackageAndPackageManagerManifest";
private LegacyExporterSettings _exportSettings;
private PackageExporterLegacy(LegacyExporterSettings exportSettings)
{
_exportSettings = exportSettings;
}
public static async Task<ExportResult> ExportPackage(LegacyExporterSettings exportSettings)
{
var exporter = new PackageExporterLegacy(exportSettings);
return await exporter.ExportPackage();
}
private async Task<ExportResult> ExportPackage()
{
ASDebug.Log("Using native package exporter");
try
{
var guids = GetGuids(_exportSettings.ExportPaths, out bool onlyFolders);
if (guids.Length == 0 || onlyFolders)
throw new ArgumentException("Package Exporting failed: provided export paths are empty or only contain empty folders");
string exportMethod = ExportMethodWithoutDependencies;
if (_exportSettings.IncludeDependencies)
exportMethod = ExportMethodWithDependencies;
var split = exportMethod.Split('.');
var assembly = Assembly.Load(split[0]); // UnityEditor
var typeName = $"{split[0]}.{split[1]}"; // UnityEditor.PackageUtility
var methodName = split[2]; // ExportPackage or ExportPackageAndPackageManagerManifest
var type = assembly.GetType(typeName);
var method = type.GetMethod(methodName, BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public,
null, new Type[] { typeof(string[]), typeof(string) }, null);
ASDebug.Log("Invoking native export method");
method?.Invoke(null, new object[] { guids, _exportSettings.OutputFilename });
// The internal exporter methods are asynchronous, therefore
// we need to wait for exporting to finish before returning
await Task.Run(() =>
{
while (!File.Exists(_exportSettings.OutputFilename))
Thread.Sleep(100);
});
ASDebug.Log($"Package file has been created at {_exportSettings.OutputFilename}");
return new ExportResult() { Success = true, ExportedPath = _exportSettings.OutputFilename };
}
catch (Exception e)
{
return new ExportResult() { Success = false, Error = ASError.GetGenericError(e) };
}
finally
{
PostExportCleanup();
}
}
private string[] GetGuids(string[] exportPaths, out bool onlyFolders)
{
var guids = new List<string>();
onlyFolders = true;
foreach (var exportPath in exportPaths)
{
var assetPaths = GetAssetPaths(exportPath);
foreach (var assetPath in assetPaths)
{
var guid = GetAssetGuid(assetPath, false, false);
if (string.IsNullOrEmpty(guid))
continue;
guids.Add(guid);
if (onlyFolders == true && (File.Exists(assetPath)))
onlyFolders = false;
}
}
return guids.ToArray();
}
}
}

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 3200380dff2de104aa79620e4b41dc70
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 115
packageName: Asset Store Publishing Tools
packageVersion: 11.4.3
assetPath: Packages/com.unity.asset-store-tools/Editor/Exporter/PackageExporterLegacy.cs
uploadId: 681981