Last active
January 11, 2022 20:23
-
-
Save ErnSur/4ac5f6cb7e3fb78d986253d111cf0ca7 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using UnityEditor; | |
using UnityEngine; | |
using System.IO; | |
using System.Linq; | |
using System.Diagnostics; | |
using Debug = UnityEngine.Debug; | |
// Modified version of https://github.com/karl-/unity-symlink-utility | |
// I added option to create symlinks in Pakcages folder | |
namespace QuickEye.Symlink | |
{ | |
[InitializeOnLoad] | |
internal static class SymlinkUtility | |
{ | |
private enum SymlinkType | |
{ | |
Junction, | |
Absolute, | |
Relative | |
} | |
// FileAttributes that match a junction folder. | |
const FileAttributes FOLDER_SYMLINK_ATTRIBUTES = FileAttributes.Directory | FileAttributes.ReparsePoint; | |
private static GUIStyle SymlinkMarkerStyle => | |
new GUIStyle(EditorStyles.label) | |
{ | |
normal = | |
{ | |
textColor = new Color(.2f, .8f, .2f, .8f) | |
}, | |
alignment = TextAnchor.MiddleRight, | |
fontStyle = FontStyle.Bold | |
}; | |
static SymlinkUtility() | |
{ | |
EditorApplication.projectWindowItemOnGUI += OnProjectWindowItemGUI; | |
} | |
private static void OnProjectWindowItemGUI(string guid, Rect r) | |
{ | |
try | |
{ | |
var path = AssetDatabase.GUIDToAssetPath(guid); | |
if (string.IsNullOrEmpty(path)) | |
return; | |
var attributes = File.GetAttributes(path); | |
if ((attributes & FOLDER_SYMLINK_ATTRIBUTES) == FOLDER_SYMLINK_ATTRIBUTES) | |
GUI.Label(r, "⇔", SymlinkMarkerStyle); | |
} | |
catch(Exception e) | |
{ | |
Debug.LogException(e); | |
} | |
} | |
private const string JunctionMenuName = "Assets/Symlink/Junction"; | |
private const string AbsSymlinkMenuName = "Assets/Symlink/Absolute Symlink"; | |
private const string RelSymlinkMenuName = "Assets/Symlink/Relative Symlink"; | |
#if UNITY_EDITOR_WIN | |
[MenuItem(JunctionMenuName, false, 20)] | |
static void Junction() | |
{ | |
Symlink(SymlinkType.Junction); | |
} | |
#endif | |
[MenuItem(AbsSymlinkMenuName, false, 21)] | |
static void SymlinkAbsolute() | |
{ | |
Symlink(SymlinkType.Absolute); | |
} | |
[MenuItem(RelSymlinkMenuName, false, 22)] | |
static void SymlinkRelative() | |
{ | |
Symlink(SymlinkType.Relative); | |
} | |
static void Symlink(SymlinkType linkType) | |
{ | |
var sourceFolderPath = EditorUtility.OpenFolderPanel("Select Folder Source", "", ""); | |
// Cancelled dialog | |
if (string.IsNullOrEmpty(sourceFolderPath)) | |
return; | |
if (sourceFolderPath.Contains(Application.dataPath)) | |
{ | |
Debug.LogWarning("Cannot create a symlink to folder in your project!"); | |
return; | |
} | |
var sourceFolderName = sourceFolderPath.Split(new char[] { '/', '\\' }).LastOrDefault(); | |
if (string.IsNullOrEmpty(sourceFolderName)) | |
{ | |
Debug.LogWarning("Couldn't deduce the folder name?"); | |
return; | |
} | |
var uobject = Selection.activeObject; | |
var targetPath = uobject != null ? AssetDatabase.GetAssetPath(uobject) : null; | |
if (string.IsNullOrEmpty(targetPath)) | |
targetPath = "Assets"; | |
if (targetPath.StartsWith("Packages/")) | |
targetPath = "Packages"; | |
var attributes = File.GetAttributes(targetPath); | |
if ((attributes & FileAttributes.Directory) != FileAttributes.Directory) | |
targetPath = Path.GetDirectoryName(targetPath); | |
// Get path to project. | |
var pathToProject = Application.dataPath.Split(new[] { "/Assets" }, StringSplitOptions.None)[0]; | |
targetPath = $"{pathToProject}/{targetPath}/{sourceFolderName}"; | |
if (Directory.Exists(targetPath)) | |
{ | |
Debug.LogWarning( | |
$"A folder already exists at this location, aborting link.\n{sourceFolderPath} -> {targetPath}"); | |
return; | |
} | |
// Use absolute path or relative path? | |
var sourcePath = linkType == SymlinkType.Relative | |
? GetRelativePath(sourceFolderPath, targetPath) | |
: sourceFolderPath; | |
#if UNITY_EDITOR_WIN | |
var linkOption = linkType == SymlinkType.Junction ? "/J" : "/D"; | |
var command = $"mklink {linkOption} \"{targetPath}\" \"{sourcePath}\""; | |
ExecuteCmdCommand(command, | |
linkType != SymlinkType.Junction); // Symlinks require admin privilege on windows, junctions do not. | |
#elif UNITY_EDITOR_OSX | |
// For some reason, OSX doesn't want to create a symlink with quotes around the paths, so escape the spaces instead. | |
sourcePath = sourcePath.Replace(" ", "\\ "); | |
targetPath = targetPath.Replace(" ", "\\ "); | |
string command = string.Format("ln -s {0} {1}", sourcePath, targetPath); | |
ExecuteBashCommand(command); | |
#elif UNITY_EDITOR_LINUX | |
// Is Linux the same as OSX? | |
#endif | |
//UnityEngine.Debug.Log(string.Format("Created symlink: {0} <=> {1}", targetPath, sourceFolderPath)); | |
AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate); | |
} | |
static string GetRelativePath(string sourcePath, string outputPath) | |
{ | |
if (string.IsNullOrEmpty(outputPath)) | |
{ | |
return sourcePath; | |
} | |
if (sourcePath == null) | |
{ | |
sourcePath = string.Empty; | |
} | |
var splitOutput = outputPath.Split(new char[1] { '/' }, StringSplitOptions.RemoveEmptyEntries); | |
var splitSource = sourcePath.Split(new char[1] { '/' }, StringSplitOptions.RemoveEmptyEntries); | |
var max = Mathf.Min(splitOutput.Length, splitSource.Length); | |
var i = 0; | |
while (i < max) | |
{ | |
if (splitOutput[i] != splitSource[i]) | |
{ | |
break; | |
} | |
++i; | |
} | |
var hopUpCount = splitOutput.Length - i - 1; | |
var newSplitCount = hopUpCount + splitSource.Length - i; | |
var newSplitTarget = new string[newSplitCount]; | |
var j = 0; | |
for (; j < hopUpCount; ++j) | |
{ | |
newSplitTarget[j] = ".."; | |
} | |
for (max = newSplitTarget.Length; j < max; ++j, ++i) | |
{ | |
newSplitTarget[j] = splitSource[i]; | |
} | |
return string.Join(Path.DirectorySeparatorChar.ToString(), newSplitTarget); | |
} | |
static void ExecuteCmdCommand(string command, bool asAdmin) | |
{ | |
var startInfo = new ProcessStartInfo | |
{ | |
FileName = "CMD.exe", | |
Arguments = "/C " + command, | |
UseShellExecute = asAdmin, | |
RedirectStandardError = !asAdmin, | |
CreateNoWindow = true, | |
}; | |
if (asAdmin) | |
{ | |
startInfo.Verb = | |
"runas"; // Runs process in admin mode. See https://stackoverflow.com/questions/2532769/how-to-start-a-process-as-administrator-mode-in-c-sharp | |
} | |
var proc = new Process() | |
{ | |
StartInfo = startInfo | |
}; | |
using (proc) | |
{ | |
proc.Start(); | |
proc.WaitForExit(); | |
if (!asAdmin && !proc.StandardError.EndOfStream) | |
{ | |
Debug.LogError(proc.StandardError.ReadToEnd()); | |
} | |
} | |
} | |
static void ExecuteBashCommand(string command) | |
{ | |
command = command.Replace("\"", "\"\""); | |
var proc = new Process() | |
{ | |
StartInfo = new ProcessStartInfo | |
{ | |
FileName = "/bin/bash", | |
Arguments = "-c \"" + command + "\"", | |
UseShellExecute = false, | |
RedirectStandardOutput = true, | |
RedirectStandardError = true, | |
CreateNoWindow = true | |
} | |
}; | |
using (proc) | |
{ | |
proc.Start(); | |
proc.WaitForExit(); | |
if (!proc.StandardError.EndOfStream) | |
{ | |
Debug.LogError(proc.StandardError.ReadToEnd()); | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment