From 5c0c8044ebb658d740f98626ff9c758379f545c0 Mon Sep 17 00:00:00 2001 From: CoderGamester Date: Sun, 27 Aug 2023 21:17:52 +0100 Subject: [PATCH] Add the possibility to bind multiple interfaces in one call Add the auto execute process to fill up the version service data based on the git information --- Editor.meta | 8 ++ Editor/GitEditorProcess.cs | 88 +++++++++++++++++++ Editor/GitEditorProcess.cs.meta | 11 +++ Editor/VersionEditorUtils.cs | 141 ++++++++++++++++++++++++++++++ Editor/VersionEditorUtils.cs.meta | 11 +++ Runtime/Installer.cs | 131 ++++++++++++++++++++++++++- package.json | 2 +- 7 files changed, 388 insertions(+), 4 deletions(-) create mode 100644 Editor.meta create mode 100644 Editor/GitEditorProcess.cs create mode 100644 Editor/GitEditorProcess.cs.meta create mode 100644 Editor/VersionEditorUtils.cs create mode 100644 Editor/VersionEditorUtils.cs.meta diff --git a/Editor.meta b/Editor.meta new file mode 100644 index 0000000..c1f6aa8 --- /dev/null +++ b/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 81aeaf8d2b465be4fb33785ca5e74bf0 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/GitEditorProcess.cs b/Editor/GitEditorProcess.cs new file mode 100644 index 0000000..7e5a8e7 --- /dev/null +++ b/Editor/GitEditorProcess.cs @@ -0,0 +1,88 @@ +using System; +using System.Diagnostics; + +namespace GameLovers.Services.Editor +{ + /// + /// Run git commands processes that would otherwise be used in the terminal. + /// + /// + /// https://blog.somewhatabstract.com/2015/06/22/getting-information-about-your-git-repository-with-c/ + /// + public class GitEditorProcess : IDisposable + { + private const string DefaultPathToGitBinary = "git"; + + private readonly Process Process; + + /// + /// + /// + public int ExitCode => Process.ExitCode; + + public GitEditorProcess(string workingDir, string pathToGitBinary = DefaultPathToGitBinary) + { + var startInfo = new ProcessStartInfo + { + UseShellExecute = false, + RedirectStandardOutput = true, + FileName = pathToGitBinary, + CreateNoWindow = true, + WorkingDirectory = workingDir + }; + + Process = new Process { StartInfo = startInfo }; + } + + /// + /// Is this unity project a git repo? + /// + public bool IsValidRepo() + { + return ExecuteCommand("rev-parse --is-inside-work-tree") == "true"; + } + + /// + /// Get the git branch name of the unity project. + /// + public string GetBranch() + { + return ExecuteCommand("rev-parse --abbrev-ref HEAD"); + } + + /// + /// Get the git commit hash of the unity project. + /// + public string GetCommitHash() + { + return ExecuteCommand($"rev-parse HEAD"); + } + + /// + /// Get the diff of the working directory in its current state from the state it was at at + /// a given commit. + /// + public string GetDiffFromCommit(string commitHash) + { + return ExecuteCommand($"diff --word-diff=porcelain {commitHash} -- {Process.StartInfo.WorkingDirectory}"); + } + + /// + public void Dispose() + { + Process?.Dispose(); + } + + /// + /// Execute a command eg. "status --verbose" + /// + private string ExecuteCommand(string args) + { + Process.StartInfo.Arguments = args; + Process.Start(); + var output = Process.StandardOutput.ReadToEnd().Trim(); + Process.WaitForExit(); + return output; + } + } +} diff --git a/Editor/GitEditorProcess.cs.meta b/Editor/GitEditorProcess.cs.meta new file mode 100644 index 0000000..c42436e --- /dev/null +++ b/Editor/GitEditorProcess.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1a90f1c0b9bc22e428b6325a25a87f40 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/VersionEditorUtils.cs b/Editor/VersionEditorUtils.cs new file mode 100644 index 0000000..2af2afe --- /dev/null +++ b/Editor/VersionEditorUtils.cs @@ -0,0 +1,141 @@ +using System; +using System.IO; +using UnityEditor; +using UnityEngine; + +namespace GameLovers.Services.Editor +{ + /// + /// Set the internal version in any VersionService instances in the project before building + /// and whenever the project loads. + /// + public static class VersionEditorUtils + { + private const int ShortenedCommitLength = 8; + + /// + /// Set the internal version before building the app. + /// + public static void SetAndSaveInternalVersion(bool isStoreBuild) + { + var newVersionData = GenerateInternalVersionSuffix(isStoreBuild); + var newVersionDataSerialized = JsonUtility.ToJson(newVersionData); + var oldVersionDataSerialized = LoadVersionDataSerializedSync(); + if (newVersionDataSerialized.Equals(oldVersionDataSerialized, StringComparison.Ordinal)) + { + return; + } + + Debug.Log($"Saving new version data: {newVersionDataSerialized}"); + SaveVersionData(newVersionDataSerialized); + } + + /// + /// Loads the game version saved in disk into string format + /// + public static string LoadVersionDataSerializedSync() + { + var textAsset = Resources.Load(VersionServices.VersionDataFilename); + if (!textAsset) + { + Debug.LogError("Could not load internal version from Resources."); + return string.Empty; + } + + var serialized = textAsset.text; + Resources.UnloadAsset(textAsset); + return serialized; + } + + /// + /// Set the internal version for when the app plays in editor. + /// + [InitializeOnLoadMethod] + private static void OnEditorLoad() + { + SetAndSaveInternalVersion(false); + } + + private static VersionServices.VersionData GenerateInternalVersionSuffix(bool isStoreBuild) + { + var data = new VersionServices.VersionData(); + + using (var repo = new GitEditorProcess(Application.dataPath)) + { + try + { + if (!repo.IsValidRepo()) + { + Debug.LogWarning("Project is not a git repo. Internal version not set."); + } + else + { + var branch = repo.GetBranch(); + if (string.IsNullOrEmpty(branch)) + { + Debug.LogWarning("Could not get git branch for internal version"); + } + else + { + data.BranchName = branch; + } + + var commitHash = repo.GetCommitHash(); + if (string.IsNullOrEmpty(commitHash)) + { + Debug.LogWarning("Could not get git commit for internal version"); + } + else + { + data.Commit = commitHash.Substring(0, ShortenedCommitLength); + } + } + } + catch (Exception e) + { + Debug.LogException(e); + Debug.LogWarning("Could not execute git commands. Internal version not set."); + } + } + + data.BuildNumber = PlayerSettings.iOS.buildNumber; + data.BuildType = isStoreBuild ? "prod" : "dev"; + + return data; + } + + /// + /// Set the internal version of this application and save it in resources. This should be + /// called at edit/build time. + /// + private static void SaveVersionData(string serializedData) + { + const string assets = "Assets"; + const string resources = "Build/Resources"; + + var absDirPath = Path.Combine(Application.dataPath, resources); + if (!Directory.Exists(absDirPath)) + { + Directory.CreateDirectory(absDirPath); + } + + // delete old file with incorrect extension + const string assetExtension = ".asset"; + var absFilePath = Path.Combine(absDirPath, VersionServices.VersionDataFilename); + if (File.Exists(Path.ChangeExtension(absFilePath, assetExtension))) + { + AssetDatabase.DeleteAsset( + Path.Combine(assets, resources, + Path.ChangeExtension(VersionServices.VersionDataFilename, assetExtension))); + } + + // create new text file + const string textExtension = ".txt"; + File.WriteAllText(Path.ChangeExtension(absFilePath, textExtension), serializedData); + + AssetDatabase.ImportAsset( + Path.Combine(assets, resources, + Path.ChangeExtension(VersionServices.VersionDataFilename, textExtension))); + } + } +} diff --git a/Editor/VersionEditorUtils.cs.meta b/Editor/VersionEditorUtils.cs.meta new file mode 100644 index 0000000..c244fad --- /dev/null +++ b/Editor/VersionEditorUtils.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 34cc2f450515a0f4a8aad7814fbfef39 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Installer.cs b/Runtime/Installer.cs index 4f9a4cb..be207a4 100644 --- a/Runtime/Installer.cs +++ b/Runtime/Installer.cs @@ -1,3 +1,4 @@ +using GameLovers.Services; using System; using System.Collections.Generic; @@ -21,7 +22,43 @@ public interface IInstaller /// /// Thrown if the given doesn't implement interface /// - void Bind(T instance) where T : class; + /// + /// This installer reference to allow chain calls if necessayr + /// + IInstaller Bind(T instance) where T : class; + + /// + /// Binds the interface multiple type interefaces to the given + /// + /// + /// Thrown if the given doesn't implement all type interface + /// + /// + /// This installer reference to allow chain calls if necessayr + /// + IInstaller Bind(T1 instance) where T1 : class, T2; + + /// + /// Binds the interface multiple type interefaces to the given + /// + /// + /// Thrown if the given doesn't implement all type interface + /// + /// + /// This installer reference to allow chain calls if necessayr + /// + IInstaller Bind(T1 instance) where T1 : class, T2, T3; + + /// + /// Binds the interface multiple type interefaces to the given + /// + /// + /// Thrown if the given doesn't implement all type interface + /// + /// + /// This installer reference to allow chain calls if necessayr + /// + IInstaller Bind(T1 instance) where T1 : class, T2, T3, T4; /// /// Tries requesting the instance binded to the type @@ -51,14 +88,13 @@ public interface IInstaller void Clean(); } - /// public class Installer : IInstaller { private readonly Dictionary _bindings = new Dictionary(); /// - public void Bind(T instance) where T : class + public IInstaller Bind(T instance) where T : class { var type = typeof(T); @@ -68,6 +104,95 @@ public void Bind(T instance) where T : class } _bindings.Add(type, instance); + + return this; + } + + /// + public IInstaller Bind(T1 instance) where T1 : class, T2 + { + var type1 = typeof(T1); + var type2 = typeof(T2); + + if (!type1.IsInterface) + { + throw new ArgumentException($"Cannot bind {instance} because {type1} is not an interface"); + } + + if (!type2.IsInterface) + { + throw new ArgumentException($"Cannot bind {instance} because {type1} is not an interface"); + } + + _bindings.Add(type1, instance); + _bindings.Add(type2, instance); + + return this; + } + + /// + public IInstaller Bind(T1 instance) where T1 : class, T2, T3 + { + var type1 = typeof(T1); + var type2 = typeof(T2); + var type3 = typeof(T3); + + if (!type1.IsInterface) + { + throw new ArgumentException($"Cannot bind {instance} because {type1} is not an interface"); + } + + if (!type2.IsInterface) + { + throw new ArgumentException($"Cannot bind {instance} because {type2} is not an interface"); + } + + if (!type3.IsInterface) + { + throw new ArgumentException($"Cannot bind {instance} because {type3} is not an interface"); + } + + _bindings.Add(type1, instance); + _bindings.Add(type2, instance); + _bindings.Add(type3, instance); + + return this; + } + + /// + public IInstaller Bind(T1 instance) where T1 : class, T2, T3, T4 + { + var type1 = typeof(T1); + var type2 = typeof(T2); + var type3 = typeof(T3); + var type4 = typeof(T4); + + if (!type1.IsInterface) + { + throw new ArgumentException($"Cannot bind {instance} because {type1} is not an interface"); + } + + if (!type2.IsInterface) + { + throw new ArgumentException($"Cannot bind {instance} because {type2} is not an interface"); + } + + if (!type3.IsInterface) + { + throw new ArgumentException($"Cannot bind {instance} because {type3} is not an interface"); + } + + if (!type4.IsInterface) + { + throw new ArgumentException($"Cannot bind {instance} because {type4} is not an interface"); + } + + _bindings.Add(type1, instance); + _bindings.Add(type2, instance); + _bindings.Add(type3, instance); + _bindings.Add(type4, instance); + + return this; } /// diff --git a/package.json b/package.json index ef79fe5..b50007c 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "com.gamelovers.services", "displayName": "Services", "author": "Miguel Tomas", - "version": "0.8.0", + "version": "0.8.1", "unity": "2021.4", "license": "MIT", "description": "The purpose of this package is to provide a set of services to ease the development of a basic game architecture",