2025-01-11 17:58:31 +03:00
using System ;
using System.Collections.Generic ;
using System.Diagnostics ;
using UnityEditor ;
using UnityEngine ;
public class BuildGeneratorEditorWindow : EditorWindow
{
private VersionDefinition editorVersion = default ;
private VersionDefinition gitVersion = default ;
[MenuItem("Window/Build Generator")]
private static void ShowWindow ( )
{
BuildGeneratorEditorWindow window = GetWindow < BuildGeneratorEditorWindow > ( ) ;
window . titleContent = new GUIContent ( "Build Generator" ) ;
window . editorVersion = new ( PlayerSettings . bundleVersion ) ;
try { window . gitVersion = GitProcess . GetLatestBuildVersion ( ) ; } catch { }
window . Show ( ) ;
}
private void OnGUI ( )
{
if ( GUILayout . Button ( $"Re-evaluate versions" ) )
{
editorVersion = new ( PlayerSettings . bundleVersion ) ;
gitVersion = GitProcess . GetLatestBuildVersion ( ) ;
}
EditorGUILayout . Space ( ) ;
EditorGUILayout . BeginHorizontal ( ) ;
EditorGUILayout . BeginVertical ( ) ;
if ( GUILayout . Button ( $"Create Release" ) ) CommitVersion ( GitProcess . GetUpcomingReleaseVersion ( ) ) ;
if ( GUILayout . Button ( $"Create Release Candidate" ) ) CommitVersion ( GitProcess . GetUpcomingReleaseCandidateVersion ( ) ) ;
EditorGUILayout . EndVertical ( ) ;
EditorGUILayout . BeginVertical ( ) ;
EditorGUILayout . LabelField ( $"Editor Version: {editorVersion} ({editorVersion.BuildNumber})" ) ;
EditorGUILayout . LabelField ( $"Git Version: {editorVersion} ({editorVersion.BuildNumber})" ) ;
EditorGUILayout . EndVertical ( ) ;
EditorGUILayout . EndHorizontal ( ) ;
EditorGUILayout . Space ( ) ;
if ( GUILayout . Button ( $"Push All" ) ) { GitProcess . Push ( ) ; GitProcess . PushTags ( ) ; }
// EditorGUILayout.BeginHorizontal();
// if (IncrementButton("Major", versionDefinition.IncreaseMajor()))
// CommitVersion(versionDefinition.IncreaseMajor());
// if (IncrementButton("Minor", versionDefinition.IncreaseMinor()))
// CommitVersion(versionDefinition.IncreaseMinor());
// if (IncrementButton("Patch", versionDefinition.IncreasePatch()))
// CommitVersion(versionDefinition.IncreasePatch());
// if (IncrementButton("Release Candidate", versionDefinition.IncreaseReleaseCandidate()))
// CommitVersion(versionDefinition.IncreaseReleaseCandidate());
// EditorGUILayout.EndHorizontal();
}
private void CommitVersion ( VersionDefinition versionDefinition )
{
if ( this . editorVersion > = versionDefinition )
{
EditorUtility . DisplayDialog (
"Release Conflict" ,
$"You can't create a new version because the old version({this.editorVersion}) is either higher or the same as the new version({versionDefinition}). Please make new commits to create new releases." ,
"Return" ) ;
return ;
}
if ( EditorUtility . DisplayDialog (
"Release Confirmation" ,
$"Are you sure to commit a new incremental version of {versionDefinition}?" ,
"Yes" ,
"Cancel" )
)
return ;
ApplyVersion ( versionDefinition ) ;
GitProcess . Add ( "ProjectSettings\\ProjectSettings.asset" ) ;
GitProcess . Add ( "Assets\\Settings\\Build Profiles\\**" ) ;
GitProcess . Commit ( $"chore: Bump Version to {versionDefinition}" ) ;
GitProcess . CreateTag ( versionDefinition ) ;
GitProcess . PushTag ( versionDefinition ) ;
}
private bool IncrementButton ( string fieldName , VersionDefinition resultDefinition )
{
EditorGUILayout . BeginVertical ( ) ;
bool isButtonPressed = GUILayout . Button ( $"Add Incremental {fieldName}" ) ;
EditorGUILayout . LabelField ( $"{editorVersion} -> {resultDefinition}" , new GUIStyle ( GUI . skin . label ) { alignment = TextAnchor . MiddleCenter } ) ;
EditorGUILayout . EndVertical ( ) ;
return isButtonPressed & & EditorUtility . DisplayDialog ( "Increment Confirmation" , $"Are you sure to add an incremental {fieldName} version?" , "Yes" , "Cancel" ) ;
}
private void ApplyVersion ( VersionDefinition versionDefinition )
{
this . editorVersion = versionDefinition ;
PlayerSettings . Android . bundleVersionCode = versionDefinition . BuildNumber ;
PlayerSettings . iOS . buildNumber = versionDefinition . BuildNumber . ToString ( ) ;
PlayerSettings . bundleVersion = versionDefinition . ToString ( ) ;
AssetDatabase . SaveAssets ( ) ;
}
private struct VersionDefinition
{
public const int MAX_VALUE = 99 ;
public const string BUILD_NUMBER_FORMAT = "00" ;
public const int DEFAULT_MAJOR = 0 ;
public const int DEFAULT_MINOR = 0 ;
public const int DEFAULT_PATCH = 0 ;
public const int DEFAULT_RELEASE_CANDIDATE = 1 ;
public const int RELEASE_RC_VALUE = 99 ;
private readonly int _major ;
private readonly int _minor ;
private readonly int _patch ;
private readonly int _releaseCandidate ;
public readonly int Major = > _major > MAX_VALUE ? MAX_VALUE : _major ;
public readonly int Minor = > _minor > MAX_VALUE ? MAX_VALUE : _minor ;
public readonly int Patch = > _patch > MAX_VALUE ? MAX_VALUE : _patch ;
public readonly int ReleaseCandidate = > _releaseCandidate > MAX_VALUE ? MAX_VALUE : _releaseCandidate ;
public readonly bool IsRelease = > ReleaseCandidate = = RELEASE_RC_VALUE ;
public VersionDefinition ( int? major = null , int? minor = null , int? patch = null , int? releaseCandidate = null )
{
_major = major ? ? DEFAULT_MAJOR ;
_minor = minor ? ? DEFAULT_MINOR ;
_patch = patch ? ? DEFAULT_PATCH ;
_releaseCandidate = releaseCandidate ? ? DEFAULT_RELEASE_CANDIDATE ;
if ( _major = = 0 & & _minor = = 0 )
_minor = _releaseCandidate = 1 ;
}
public VersionDefinition ( string versionString )
{
if ( versionString . StartsWith ( 'v' ) )
versionString = versionString [ 1. . ] ;
_major = DEFAULT_MAJOR ;
_minor = DEFAULT_MINOR ;
_patch = DEFAULT_PATCH ;
_releaseCandidate = RELEASE_RC_VALUE ;
string [ ] releaseCandidateVersionStrings = versionString . Split ( "-rc" ) ;
string [ ] versionNumbers = releaseCandidateVersionStrings [ 0 ] . Split ( '.' ) ;
if ( versionNumbers . Length > 0 & & int . TryParse ( versionNumbers [ 0 ] , out int major ) ) _major = major ;
if ( versionNumbers . Length > 1 & & int . TryParse ( versionNumbers [ 1 ] , out int minor ) ) _minor = minor ;
if ( versionNumbers . Length > 2 & & int . TryParse ( versionNumbers [ 2 ] , out int patch ) ) _patch = patch ;
if ( releaseCandidateVersionStrings . Length > 1 & & int . TryParse ( releaseCandidateVersionStrings [ 1 ] , out int releaseCandidate ) ) _releaseCandidate = releaseCandidate ;
if ( _major = = 0 & & _minor = = 0 )
_minor = _releaseCandidate = 1 ;
}
public readonly VersionDefinition IncreaseMajor ( ) = > new ( Major + 1 , DEFAULT_MINOR , DEFAULT_PATCH , DEFAULT_RELEASE_CANDIDATE ) ;
public readonly VersionDefinition IncreaseMinor ( ) = > new ( Major , Minor + 1 , DEFAULT_PATCH , DEFAULT_RELEASE_CANDIDATE ) ;
public readonly VersionDefinition IncreasePatch ( ) = > new ( Major , Minor , Patch + 1 , DEFAULT_RELEASE_CANDIDATE ) ;
public readonly VersionDefinition IncreaseReleaseCandidate ( ) = > new ( Major , Minor , Patch , ReleaseCandidate + 1 ) ;
public readonly VersionDefinition ToReleaseVersion ( ) = > new ( Major , Minor , Patch , RELEASE_RC_VALUE ) ;
public readonly int BuildNumber = > int . Parse ( $"{Major.ToString(BUILD_NUMBER_FORMAT)}{Minor.ToString(BUILD_NUMBER_FORMAT)}{Patch.ToString(BUILD_NUMBER_FORMAT)}{ReleaseCandidate.ToString(BUILD_NUMBER_FORMAT)}" ) ;
public override readonly string ToString ( )
{
if ( IsRelease )
return $"{Major}.{Minor}.{Patch}" ;
return $"{Major}.{Minor}.{Patch}-rc{ReleaseCandidate}" ;
}
public static bool operator > ( VersionDefinition left , VersionDefinition right ) = > left . BuildNumber > right . BuildNumber ;
public static bool operator < ( VersionDefinition left , VersionDefinition right ) = > left . BuildNumber < right . BuildNumber ;
public static bool operator > = ( VersionDefinition left , VersionDefinition right ) = > left . BuildNumber > = right . BuildNumber ;
public static bool operator < = ( VersionDefinition left , VersionDefinition right ) = > left . BuildNumber < = right . BuildNumber ;
public static bool operator = = ( VersionDefinition left , VersionDefinition right ) = > left . BuildNumber = = right . BuildNumber ;
public static bool operator ! = ( VersionDefinition left , VersionDefinition right ) = > left . BuildNumber ! = right . BuildNumber ;
public override readonly bool Equals ( object obj )
= > obj is VersionDefinition versionDefinition & & BuildNumber = = versionDefinition . BuildNumber ;
public override readonly int GetHashCode ( )
= > HashCode . Combine ( Major , Minor , Patch , ReleaseCandidate ) ;
}
private static class GitProcess
{
public static VersionDefinition GetLatestBuildVersion ( ) = > new ( GetLatestBuildVersionString ( ) ) ;
public static VersionDefinition GetUpcomingReleaseVersion ( ) = > GetUpcomingReleaseCandidateVersion ( ) . ToReleaseVersion ( ) ;
public static VersionDefinition GetUpcomingReleaseCandidateVersion ( )
{
VersionDefinition latestVersionDefinition = GetLatestBuildVersion ( ) ;
string [ ] commits = GetCommitsSinceLastTag ( ) ;
return ApplySemVer ( latestVersionDefinition , commits ) ;
}
public static VersionDefinition GetVersion ( string version , IList < string > gitLogs )
= > ApplySemVer ( new ( version ) , gitLogs ) ;
public static VersionDefinition ApplySemVer ( VersionDefinition latestVersionDefinition , IList < string > commits )
{
VersionDefinition result = latestVersionDefinition . IsRelease ? latestVersionDefinition . IncreasePatch ( ) : latestVersionDefinition ;
for ( int i = commits . Count - 1 ; i > = 0 ; i - - )
{
string line = commits [ i ] ;
if ( string . IsNullOrWhiteSpace ( line ) )
continue ;
string commitTitle = line . Length > 9 ? line [ 9. . ] : line ;
if ( commitTitle . StartsWith ( "breaking change" , StringComparison . InvariantCultureIgnoreCase ) )
{
result = result . IncreaseMajor ( ) ;
break ;
}
if ( latestVersionDefinition . Minor ! = result . Minor )
continue ;
if ( commitTitle . StartsWith ( "feat" , StringComparison . OrdinalIgnoreCase ) )
{
result = result . IncreaseMinor ( ) ;
continue ;
}
if ( latestVersionDefinition . Patch ! = result . Patch )
continue ;
if ( commitTitle . StartsWith ( "fix" , StringComparison . OrdinalIgnoreCase ) )
{
result = result . IncreasePatch ( ) ;
continue ;
}
if ( latestVersionDefinition . ReleaseCandidate = = result . ReleaseCandidate )
result = result . IncreaseReleaseCandidate ( ) ;
}
return result ;
}
public static string GetLatestBuildVersionString ( )
= > RunGitCommand ( $"describe --tags --abbrev=0" ) ;
public static string [ ] GetCommits ( )
= > RunGitCommand ( "log --oneline" ) . Replace ( "\r" , "" ) . Split ( '\n' ) ;
public static string [ ] GetCommitsSinceLastTag ( )
= > RunGitCommand ( $"log {GetLatestBuildVersionString()}..HEAD --oneline" ) . Replace ( "\r" , "" ) . Split ( '\n' ) ;
public static string CreateTag ( VersionDefinition versionDefinition )
= > CreateTag ( $"v{versionDefinition}" , $"{(versionDefinition.IsRelease ? " Release " : " Build ")} {versionDefinition} with build number: {versionDefinition.BuildNumber}\"" ) ;
public static string Push ( )
= > RunGitCommand ( $"push" ) ;
public static string Add ( IList < string > files )
= > RunGitCommand ( $"add \" { string . Join ( "\" \"" , files ) } \ "" ) ;
public static string Add ( string file )
= > RunGitCommand ( $"add \" { file } \ "" ) ;
public static string Commit ( string message )
= > RunGitCommand ( $"commit -m \" { message } \ "" ) ;
public static string PushTags ( )
= > RunGitCommand ( $"push --tags" ) ;
public static string PushTag ( VersionDefinition versionDefinition , string remote = "origin" )
= > RunGitCommand ( $"push {remote} tag v{versionDefinition}" ) ;
public static string CreateTag ( string title , string message )
= > RunGitCommand ( $"tag -a {title} -m \" { message } ");
public static string Unstage ( IList < string > files )
= > RunGitCommand ( $"reset \" { string . Join ( "\" \"" , files ) } \ "" ) ;
public static string Unstage ( string file )
= > RunGitCommand ( $"reset \" { file } \ "" ) ;
public static string Unstage ( )
= > RunGitCommand ( $"reset" ) ;
private static string RunGitCommand ( string gitArgs )
{
ProcessStartInfo processStartInfo = new ( )
{
FileName = "git" ,
Arguments = gitArgs ,
RedirectStandardOutput = true ,
UseShellExecute = false ,
CreateNoWindow = true
} ;
try
{
Process process = Process . Start ( processStartInfo ) ;
string output = process . StandardOutput . ReadToEnd ( ) ;
if ( output . EndsWith ( '\n' ) ) output = output [ 0. . ^ 1 ] ;
if ( output . EndsWith ( '\r' ) ) output = output [ 0. . ^ 1 ] ;
process . WaitForExit ( ) ;
return output ;
}
catch ( Exception ex )
{
Console . WriteLine ( "An error occurred: " + ex . Message ) ;
throw ;
}
}
}
}