diff --git a/inc/pluginconfig.ps1 b/inc/pluginconfig.ps1 new file mode 100644 index 0000000..ee8e290 --- /dev/null +++ b/inc/pluginconfig.ps1 @@ -0,0 +1,39 @@ + + +class PluginConfig { + [string]$OutputDir + + PluginConfig([PSCustomObject]$obj) { + # Construct from JSON object + + # Override just properties that are set + $obj.PSObject.Properties | ForEach-Object { + try { + # Nested array dealt with below + $this.$($_.Name) = $_.Value + } catch { + Write-Host "Invalid property in plugin config: $($_.Name) = $($_.Value)" + } + } + + } + + +} + +# Read pluginconfig.json file from a source location and return PluginConfig instance +function Read-Plugin-Config { + param ( + [string]$srcfolder + ) + + $configfile = Resolve-Path "$srcfolder\pluginconfig.json" + if (-not (Test-Path $configfile -PathType Leaf)) { + throw "$srcfolder\pluginconfig.json does not exist!" + } + + $obj = (Get-Content $configfile) | ConvertFrom-Json + + return [PluginConfig]::New($obj) + +} diff --git a/inc/pluginversion.ps1 b/inc/pluginversion.ps1 new file mode 100644 index 0000000..7085fbd --- /dev/null +++ b/inc/pluginversion.ps1 @@ -0,0 +1,64 @@ + +function Get-NextPluginVersion { + + param ( + [string]$currentVersion, + [bool]$major, + [bool]$minor, + [bool]$patch, + [bool]$hotfix + ) + + if (($major + $minor + $patch + $hotfix) -gt 1) { + throw "Can't set more than one of major/minor/patch/hotfix at the same time!" + } + + + # Regex features: + # - Can read 2-4 version components but will pad with 0s up to 4 when writing + # - captures pre- and post-fix text and retains + $regex = "([^\d]*)(\d+)\.(\d+)(?:\.(\d+))?(?:\.(\d+))?(.*)" + $matches = $currentVersion | Select-String -Pattern $regex + # 1 = prefix + # 2-5 = version number components + # 6 = postfix + if (($matches.Matches.Count -gt 0) -and ($matches.Matches[0].Groups.Count -eq 7)) { + $prefix = $matches.Matches[0].Groups[1].Value + $postfix = $matches.Matches[0].Groups[6].Value + + $intversions = $matches.Matches[0].Groups[2..5] | ForEach-Object { + if ($_.Value -ne "") { + [int]$_.Value + } else { + # We fill in the version numbers to 4 digits always + 0 + } + + } + + $versionDigit = 2; + if ($major) { + $versionDigit = 0 + } elseif ($minor) { + $versionDigit = 1 + } elseif ($patch) { + $versionDigit = 2 + } elseif ($hotfix) { + $versionDigit = 3 + } + # increment then zero anything after + $intversions[$versionDigit]++ + for ($d = $versionDigit + 1; $d -lt $intversions.Length; $d++) { + $intversions[$d] = 0 + } + + $newver = "$prefix$($intversions[0]).$($intversions[1]).$($intversions[2]).$($intversions[3])$postfix" + Write-Verbose "[version++] Bumping version to $newver" + + return "$newver" + + } else { + throw "[version++] Error: unable to read current version" + } +} + diff --git a/inc/uplugin.ps1 b/inc/uplugin.ps1 new file mode 100644 index 0000000..6774228 --- /dev/null +++ b/inc/uplugin.ps1 @@ -0,0 +1,34 @@ +. $PSScriptRoot\packageconfig.ps1 + + +function Get-Uplugin-Filename { + param ( + [string]$srcfolder, + [PluginConfig]$config + ) + + $projfile = "" + if ($config -and $config.ProjectFile) { + if (-not [System.IO.Path]::IsPathRooted($config.ProjectFile)) { + $projfile = Join-Path $srcfolder $config.ProjectFile + } else { + $projfile = Resolve-Path $config.ProjectFile + } + + if (-not (Test-Path $projfile)) { + throw "Invalid ProfileFile setting, $($config.ProjectFile) does not exist." + } + + } else { + # can return multiple results, pick the first one + $matchedfile = @(Get-ChildItem -Path $srcfolder -Filter *.uplugin)[0] + $projfile = $matchedfile.FullName + } + + # Resolve to absolute (do it here and not in join so missing file is friendlier error) + if ($projfile) { + return Resolve-Path $projfile + } else { + return $projfile + } +} diff --git a/inc/uproject.ps1 b/inc/uproject.ps1 index 9e5608a..97de23f 100644 --- a/inc/uproject.ps1 +++ b/inc/uproject.ps1 @@ -26,7 +26,11 @@ function Get-Uproject-Filename { } # Resolve to absolute (do it here and not in join so missing file is friendlier error) - return Resolve-Path $projfile + if ($projfile) { + return Resolve-Path $projfile + } else { + return $projfile + } } # Read the uproject file and return as a PSCustomObject diff --git a/pluginconfig_template.json b/pluginconfig_template.json new file mode 100644 index 0000000..9da2a50 --- /dev/null +++ b/pluginconfig_template.json @@ -0,0 +1,8 @@ +{ + "OutputDir": "/Path/To/Output/Parent/Dir", + + "PluginFile": "OptionalPluginFilenameWillDetectInDirOtherwise.uplugin" + +} + + diff --git a/ue-plugin-package.ps1 b/ue-plugin-package.ps1 new file mode 100644 index 0000000..322503c --- /dev/null +++ b/ue-plugin-package.ps1 @@ -0,0 +1,203 @@ +# Plugin packaging helper +# Bumps versions, zips for marketplace +# Put pluginconfig.json in your project folder to configure +# See pluginconfig_template.json +[CmdletBinding()] # Fail on unknown args +param ( + [string]$src, + [switch]$major = $false, + [switch]$minor = $false, + [switch]$patch = $false, + [switch]$hotfix = $false, + # Don't incrememnt version + [switch]$keepversion = $false, + # Force move tag + [switch]$forcetag = $false, + # Testing mode; skips clean checks, tags + [switch]$test = $false, + # Browse the output directory in file explorer after packaging + [switch]$browse = $false, + # Dry-run; does nothing but report what *would* have happened + [switch]$dryrun = $false, + [switch]$help = $false +) + +. $PSScriptRoot\inc\platform.ps1 +. $PSScriptRoot\inc\pluginconfig.ps1 +. $PSScriptRoot\inc\pluginversion.ps1 +. $PSScriptRoot\inc\uproject.ps1 +. $PSScriptRoot\inc\uplugin.ps1 +. $PSScriptRoot\inc\filetools.ps1 + + +function Write-Usage { + Write-Output "Steve's Unreal Plugin packaging tool" + Write-Output "Usage:" + Write-Output " ue-plugin-package.ps1 [-src:sourcefolder] [-major|-minor|-patch|-hotfix] [-keepversion] [-force] [-test] [-dryrun]" + Write-Output " " + Write-Output " -src : Source folder (current folder if omitted), must contain pluginconfig.json" + Write-Output " -major : Increment major version i.e. [x++].0.0.0" + Write-Output " -minor : Increment minor version i.e. x.[x++].0.0" + Write-Output " -patch : Increment patch version i.e. x.x.[x++].0 (default)" + Write-Output " -hotfix : Increment hotfix version i.e. x.x.x.[x++]" + Write-Output " -keepversion : Keep current version number, doesn't tag unless -forcetag" + Write-Output " -forcetag : Move any existing version tag" + Write-Output " -test : Testing mode, separate builds, allow dirty working copy" + Write-Output " -browse : After packaging, browse the output folder" + Write-Output " -dryrun : Don't perform any actual actions, just report on what you would do" + Write-Output " -help : Print this help" + Write-Output " " +} + +if ($src.Length -eq 0) { + $src = "." + Write-Verbose "-src not specified, assuming current directory" +} + +$ErrorActionPreference = "Stop" + +if ($help) { + Write-Usage + Exit 0 +} + +Write-Output "~-~-~ Unreal Plugin Package Start ~-~-~" + +if ($test) { + Write-Output "TEST MODE: No tagging, version bumping" +} + +if (([bool]$major + [bool]$minor + [bool]$patch + [bool]$hotfix) -gt 1) { + Write-Output "ERROR: Can't set more than one of major/minor/patch/hotfix at the same time!" + Print-Usage + Exit 5 +} +if (($major -or $minor -or $patch -or $hotfix) -and $keepversion) { + Write-Output "ERROR: Can't set keepversion at the same time as major/minor/patch/hotfix!" + Print-Usage + Exit 5 +} + +# Detect Git +if ($src -ne ".") { Push-Location $src } +$isGit = Test-Path ".git" +if ($src -ne ".") { Pop-Location } + +# Check working copy is clean (Git only) +if (-not $test -and $isGit) { + if ($src -ne ".") { Push-Location $src } + + if (Test-Path ".git") { + git diff --no-patch --exit-code + if ($LASTEXITCODE -ne 0) { + Write-Output "Working copy is not clean (unstaged changes)" + if ($dryrun) { + Write-Output "dryrun: Continuing but this will fail without -dryrun" + } else { + Exit $LASTEXITCODE + } + } + git diff --no-patch --cached --exit-code + if ($LASTEXITCODE -ne 0) { + Write-Output "Working copy is not clean (staged changes)" + if ($dryrun) { + Write-Output "dryrun: Continuing but this will fail without -dryrun" + } else { + Exit $LASTEXITCODE + } + } + } + if ($src -ne ".") { Pop-Location } +} + + +try { + # Import config & project settings + $config = Read-Plugin-Config -srcfolder:$src + + $pluginfile = Get-Uplugin-Filename -srcfolder:$src -config:$config + if (-not $pluginfile) { + throw "Not in a uplugin dir!" + } + $proj = Read-Uproject $pluginfile + $pluginName = (Get-Item $pluginfile).Basename + + Write-Output "" + Write-Output "Plugin File : $pluginfile" + Write-Output "Output Folder : $($config.OutputDir)" + Write-Output "" + + if (([bool]$major + [bool]$minor + [bool]$patch + [bool]$hotfix) -eq 0) { + $patch = $true + } + $versionNumber = $proj.VersionName + if (-not $keepversion) { + # Bump up version, passthrough options + try { + $versionNumber = Get-NextPluginVersion -current:$versionNumber -major:$major -minor:$minor -patch:$patch -hotfix:$hotfix + # Save incremented version back to uplugin object (will be saved later) + $proj.VersionName = $versionNumber + } + catch { + Write-Output $_.Exception.Message + Exit 6 + } + } + Write-Output "Next version will be: $versionNumber" + + # For tagging release + # We only need to grab the main version once + if ((-not $keepversion) -or $forcetag) { + $forcearg = "" + if ($forcetag) { + $forcearg = "-f" + } + if (-not $test -and -not $dryrun -and $isGit) { + if ($src -ne ".") { Push-Location $src } + git tag $forcearg -a $versionNumber -m "Automated release tag" + if ($LASTEXITCODE -ne 0) { Exit $LASTEXITCODE } + if ($src -ne ".") { Pop-Location } + } + } + + # TODO: Save .uplugin changes! + + # Zip parent of the uplugin folder + $zipsrc = (Get-Item $pluginfile).Directory.FullName + $zipdst = Join-Path $config.OutputDir "$($pluginName)_$($versionNumber).zip" + + New-Item -ItemType Directory -Path $config.OutputDir -Force > $null + Write-Output "Compressing to $zipdst" + + $argList = [System.Collections.ArrayList]@() + $argList.Add("a") > $null + $argList.Add($zipdst) > $null + $argList.Add($zipsrc) > $null + + if ($dryrun) { + Write-Output "" + Write-Output "Would have run:" + Write-Output "> 7z.exe $($argList -join " ")" + Write-Output "" + + } else { + $proc = Start-Process "7z.exe" $argList -Wait -PassThru -NoNewWindow + if ($proc.ExitCode -ne 0) { + throw "7-Zip failed!" + } + + } + + + if ($browse -and -not $dryrun) { + Invoke-Item $config.OutputDir + } + +} +catch { + Write-Output $_.Exception.Message + Write-Output "~-~-~ Unreal Plugin Package FAILED ~-~-~" + Exit 9 +} + +Write-Output "~-~-~ Unreal Plugin Package Completed OK ~-~-~"