diff --git a/inc/filetools.ps1 b/inc/filetools.ps1 index 7bafc13..ae21ec0 100644 --- a/inc/filetools.ps1 +++ b/inc/filetools.ps1 @@ -28,4 +28,42 @@ function Find-Files { FullNames = $fullpaths } -} \ No newline at end of file +} + +# Get the root package output dir for a version / variant +function Get-Package-Dir { + param ( + [PackageConfig]$config, + [string]$versionNumber, + [string]$variantName + ) + + return Join-Path $config.OutputDir "$versionNumber/$variantName" +} + +# Get the dir where the client build is for a packaged version / variant +# This is as Get-Package-Dir except with one extra level e.g. WindowsNoEditor +function Get-Package-Client-Dir { + param ( + [PackageConfig]$config, + [string]$versionNumber, + [string]$variantName + ) + + $root = Get-Package-Dir -config:$config -versionNumber:$versionNumber -variantName:$variantName + $variant = $config.Variants | Where-Object { $_.Name -eq $variantName } | Select-Object -First 1 + + if (-not $variant) { + throw "Unknown variant $variantName" + } + # Note, currently only supporting "Game" platform type, not separate client / server + $subfolder = switch ($variant.Platform) { + "Win32" { "WindowsNoEditor" } + "Win64" { "WindowsNoEditor" } + "Linux" { "LinuxNoEditor" } + "Mac" { "MacNoEditor" } + Default { throw "Unsupported platform $($variant.Platform)" } + } + + return Join-Path $root $subfolder +} diff --git a/inc/itch.ps1 b/inc/itch.ps1 new file mode 100644 index 0000000..82c79a3 --- /dev/null +++ b/inc/itch.ps1 @@ -0,0 +1,40 @@ +function Release-Itch { + param ( + [PackageConfig]$config, + [PackageVariant]$variant, + [string]$sourcefolder, + [string]$version, + [switch]$dryrun = $false + ) + + Write-Output ">>>--- Itch Upload Start ---<<<" + + $appid = $variant.ItchAppId + $channel = $variant.ItchChannel + + if (-not $appid) { + throw "Missing property ItchAppId in $($variant.Name)" + } + if (-not $channel) { + throw "Missing property ItchChannel in $($variant.Name)" + } + + $target = "$($appid):$channel" + + if ($dryrun) { + Write-Output "Would have run butler command:" + Write-Output " > butler push --userversion=$version '$sourcefolder' $target" + } else { + Write-Output "Releasing version $version to Itch.io at $target" + Write-Output " Source: $sourcefolder" + + butler push --userversion=$version "$sourcefolder" $target + if (!$?) { + throw "Itch butler tool failed!" + } + } + + Write-Output ">>>--- Itch Upload Done! ---<<<" + Write-Output "" + +} \ No newline at end of file diff --git a/inc/packageconfig.ps1 b/inc/packageconfig.ps1 index 24cd5f9..9ea8eb1 100644 --- a/inc/packageconfig.ps1 +++ b/inc/packageconfig.ps1 @@ -12,6 +12,8 @@ class PackageVariant { [string]$ExtraBuildArguments # Whether to create a zip of this package (default false) [bool]$Zip + # List of services this variant should be released to ("steam", "itch" currently supported) + [array]$ReleaseTo # The Steam application ID, if you intend to send this variant to Steam [string]$SteamAppId # The Steam depot ID, if you intend to send this variant to Steam diff --git a/inc/steam.ps1 b/inc/steam.ps1 new file mode 100644 index 0000000..f6b494f --- /dev/null +++ b/inc/steam.ps1 @@ -0,0 +1,110 @@ +function Release-Steam { + param ( + [PackageConfig]$config, + [PackageVariant]$variant, + [string]$sourcefolder, + [string]$version, + [switch]$dryrun = $false + ) + + Write-Output ">>>--- Steam Upload Start ---<<<" + + $appid = $variant.SteamAppId + $depotid = $variant.SteamDepotId + $login = $variant.SteamLogin + + if (-not $appid) { + throw "Missing property SteamAppId in $($variant.Name)" + } + if (-not $depotid) { + throw "Missing property SteamDepotId in $($variant.Name)" + } + if (-not $login) { + throw "Missing property SteamLogin in $($variant.Name)" + } + + + $steamconfigdir = Join-Path (Get-Item $sourcefolder).Parent "SteamConfig" + New-Item -ItemType Directory $steamconfigdir -Force > $null + + # Preview mode in Steam build just outputs logs so it's dryrun + $preview = if($dryrun) { "1" } else { "0"} + + # Use the UE4 platform as Steam target + $target = $variant.Platform + + # write app file up to depot section then fill that in as we do depots + $appfile = "$steamconfigdir\app_build_$($appid).vdf" + Write-Output "Creating app build config $appfile" + Remove-Item $appfile -Force -ErrorAction SilentlyContinue + $appfp = New-Object -TypeName System.IO.FileStream( + $appfile, + [System.IO.FileMode]::Create, + [System.IO.FileAccess]::Write) + $appstream = New-Object System.IO.StreamWriter ($appfp, [System.Text.Encoding]::UTF8) + + $appstream.WriteLine("`"appbuild`"") + $appstream.WriteLine("{") + $appstream.WriteLine(" `"appid`" `"$appid`"") + $appstream.WriteLine(" `"desc`" `"$version`"") + $appstream.WriteLine(" `"buildoutput`" `".\steamcmdbuild`"") + # we don't set contentroot in app file, we specify in depot files + $appstream.WriteLine(" `"setlive`" `"`"") # never try to set live + $appstream.WriteLine(" `"preview`" `"$preview`"") + $appstream.WriteLine(" `"local`" `"`"") + $appstream.WriteLine(" `"depots`"") + $appstream.WriteLine(" {") + + # Depots inline + # Just one in this case + $depotfilerel = "depot_${target}_${depotid}.vdf" + $depotfile = "$steamconfigdir\$depotfilerel" + Write-Output "Creating depot build config $depotfile" + Remove-Item $depotfile -Force -ErrorAction SilentlyContinue + $depotfp = New-Object -TypeName System.IO.FileStream( + $depotfile, + [System.IO.FileMode]::Create, + [System.IO.FileAccess]::Write) + $depotstream = New-Object System.IO.StreamWriter($depotfp, [System.Text.Encoding]::UTF8) + $depotstream.WriteLine("`"DepotBuildConfig`"") + $depotstream.WriteLine("{") + $depotstream.WriteLine(" `"DepotID`" `"$depotid`"") + # We'll set ContentRoot specifically for + $depotstream.WriteLine(" `"ContentRoot`" `"$sourcefolder`"") + $depotstream.WriteLine(" `"FileMapping`"") + $depotstream.WriteLine(" {") + $depotstream.WriteLine(" `"LocalPath`" `"*`"") + $depotstream.WriteLine(" `"DepotPath`" `".`"") + $depotstream.WriteLine(" `"recursive`" `"1`"") + $depotstream.WriteLine(" }") + $depotstream.WriteLine(" `"FileExclusion`" `"*.pdb`"") + $depotstream.WriteLine("}") + $depotstream.Close() + $depotfp.Close() + + # Now write depot entry to in-progress app file, relative file (same folder) + $appstream.WriteLine(" `"$depotid`" `"$depotfilerel`"") + + # Finish the app file + $appstream.WriteLine(" }") + $appstream.WriteLine("}") + $appstream.Close() + + if ($dryrun) { + Write-Output "Would have run Steam command:" + Write-Output " > steamcmd +login $($config.SteamLogin) +run_app_build_http $appfile +quit" + } else { + Write-Output "Releasing version $version to Steam ($appid)" + steamcmd +login $($config.SteamLogin) +run_app_build_http $appfile +quit + if (!$?) { + throw "Steam upload tool failed!" + } + } + + Write-Output ">>>--- Steam Upload Done ---<<<" + Write-Output "" + if (-not $dryrun) { + Write-Output "-- Remember to release in Steamworks Admin --" + } + +} \ No newline at end of file diff --git a/packageconfig_template.json b/packageconfig_template.json index 6612b57..c15c674 100644 --- a/packageconfig_template.json +++ b/packageconfig_template.json @@ -32,8 +32,12 @@ "Name": "PublicWin64SteamBuild", "Platform": "Win64", "Configuration": "Shipping", + "ReleaseTo": [ + "Steam" + ], "SteamAppId": "YourSteamAppId", "SteamDepotId": "YourWindowsDepotId", + "SteamLogin": "YourSteamReleaseUser", "Zip": false, "ExtraBuildArguments": "-EnableSteamworks", "Cultures": [ @@ -46,13 +50,16 @@ "Name": "PublicWin64Build", "Platform": "Win64", "Configuration": "Shipping", + "ReleaseTo": [ + "Itch", + "SomeOtherService" + ], "ItchAppId": "itch-user/app-name", "ItchChannel": "win64", "Zip": false } ], - "SteamLogin": "YourSteamReleaseUser" } diff --git a/ue4-package.ps1 b/ue4-package.ps1 index 0e8b63c..f08c117 100644 --- a/ue4-package.ps1 +++ b/ue4-package.ps1 @@ -204,7 +204,7 @@ try { foreach ($var in $chosenVariants) { - $outDir = Join-Path $config.OutputDir "$versionNumber/$($var.Name)" + $outDir = Get-Package-Dir -config:$config -versionNumber:$versionNumber -variantName:$var.Name $argList = [System.Collections.ArrayList]@() $argList.Add("-ScriptsForProject=`"$projfile`"") > $null diff --git a/ue4-release.ps1 b/ue4-release.ps1 new file mode 100644 index 0000000..80b267f --- /dev/null +++ b/ue4-release.ps1 @@ -0,0 +1,117 @@ +[CmdletBinding()] # Fail on unknown args +param ( + # Version to release + [Parameter(Mandatory=$true)] + [string]$version, + # Variant name to release + [Parameter(Mandatory=$true)] + # Project folder (assumes current dir if not specified) + [string]$variant, + # Source folder, current dir if omitted + [string]$src, + # Which service(s) to release on e.g. "steam": defaults to "ReleaseTo" services in packageconfig.json for variant + [array]$services, + # Dry-run; does nothing but report what *would* have happened + [switch]$dryrun = $false, + [switch]$help = $false +) + +. $PSScriptRoot\inc\platform.ps1 +. $PSScriptRoot\inc\packageconfig.ps1 +. $PSScriptRoot\inc\filetools.ps1 +. $PSScriptRoot\inc\steam.ps1 +. $PSScriptRoot\inc\itch.ps1 + + +function Write-Usage { + Write-Output "Steve's UE4 release tool" + Write-Output "Usage:" + Write-Output " ue4-release.ps1 -version:ver -variant:var -services:steam,itch [-src:sourcefolder] [-dryrun]" + Write-Output " " + Write-Output " -version:ver : Version to release; must have been packaged already" + Write-Output " -variant:var : Name of package variant to release" + Write-Output " -services:s1,s2 : Name of services to release to. Can omit and rely on ReleaseTo" + Write-Output " setting of variant in packageconfig.json " + Write-Output " -src : Source folder (current folder if omitted), must contain packageconfig.json" + Write-Output " -dryrun : Don't perform any actual actions, just report what would happen" + Write-Output " -help : Print this help" +} + +$ErrorActionPreference = "Stop" + +if ($help) { + Write-Usage + Exit 0 +} + +if ($src.Length -eq 0) { + $src = "." + Write-Verbose "-src not specified, assuming current directory" +} + +Write-Output "~-~-~ UE4 Release Helper Start ~-~-~" + +try { + + # Import config + $config = Read-Package-Config -srcfolder:$src + + # Find variant (first match in case config has many) + $variantConfig = $config.Variants | Where-Object { $_.Name -eq $variant } + if ($variantConfig -is [array]) { + if ($variantConfig.Count > 1) { + throw "More than one package variant called $variant in packageconfig.json, ambiguous!" + } else { + # Don't think this will happen but still + $variantConfig = $variantConfig[0] + } + } + + # Get source dir + $sourcedir = Get-Package-Client-Dir -config:$config -versionNumber:$version -variantName:$variant + + if (-not (Test-Path $sourcedir -PathType Container)) { + throw "Release folder $sourcedir does not exist" + } + + # Find service(s) + if ($services) { + # Release to a subset of allowed services + $servicesFound = $services | Where-Object {$variantConfig.ReleaseTo -contains $_ } + if ($servicesFound.Count -ne $services.Count) { + $unmatchedServices = $services | Where-Object { $servicesFound -notcontains $_ } + Write-Warning "Services(s) not supported by $($variantConfig.Name): $($unmatchedServices -join ", ")" + } + } else { + $servicesFound = $variantConfig.ReleaseTo + } + + if (-not $servicesFound) { + throw "No matching services to release $variant to" + } + + Write-Output "" + Write-Output "Variant : $variant" + Write-Output "Source Folder : $sourcedir" + Write-Output "Service(s) : $($servicesFound -join ", ")" + Write-Output "" + + + foreach ($service in $servicesFound) { + if ($service -eq "steam") { + Release-Steam -config:$config -variant:$variantConfig -sourcefolder:$sourcedir -version:$version -dryrun:$dryrun + } elseif ($service -eq "itch") { + Release-Itch -config:$config -variant:$variantConfig -sourcefolder:$sourcedir -version:$version -dryrun:$dryrun + } else { + throw "Unknown release service: $service" + } + } + +} catch { + Write-Output $_.Exception.Message + Write-Output "~-~-~ UE4 Release Helper FAILED ~-~-~" + Exit 9 +} + + +Write-Output "~-~-~ UE4 Release Helper Completed OK ~-~-~"