From 8a79de5255fb6756f3a1d26579b1e9bc36e0c811 Mon Sep 17 00:00:00 2001 From: Tobias Date: Sat, 5 Sep 2026 15:30:49 +0200 Subject: [PATCH] Improve build process on macOS --- gulp/gulpfile.js | 5 ++ gulp/package.json | 1 + gulp/standalone.js | 45 ++++++++++++++- gulp/steampipe/templates/app-all.vdf | 16 ++++++ .../steampipe/templates/standalone-darwin.vdf | 2 +- gulp/steampipe/templates/standalone-linux.vdf | 2 +- .../templates/standalone-windows.vdf | 2 +- gulp/steampipe/upload-all.sh | 6 ++ gulp/win32-resources.js | 55 +++++++++++++++++++ gulp/yarn.lock | 12 ++++ 10 files changed, 141 insertions(+), 5 deletions(-) create mode 100644 gulp/steampipe/templates/app-all.vdf create mode 100755 gulp/steampipe/upload-all.sh create mode 100644 gulp/win32-resources.js diff --git a/gulp/gulpfile.js b/gulp/gulpfile.js index 04d3c9c9..ca1323ed 100644 --- a/gulp/gulpfile.js +++ b/gulp/gulpfile.js @@ -268,6 +268,10 @@ for (const variant in BUILD_VARIANTS) { "bundle." + variant + ".from-darwin", gulp.series(buildName, "standalone." + variant + ".build-from-darwin") ); + gulp.task( + "bundle." + variant + ".all", + gulp.series(buildName, "standalone." + variant + ".build-all") + ); } // serve @@ -300,6 +304,7 @@ gulp.task( "bundle.standalone-steam-china.from-windows" ) ); +gulp.task("bundle.steam.all", gulp.series("utils.cleanBuildOutputFolder", "bundle.standalone-steam.all")); gulp.task( "bundle.steam-demo.from-darwin", gulp.series("utils.cleanBuildOutputFolder", "bundle.standalone-steam-demo.from-darwin") diff --git a/gulp/package.json b/gulp/package.json index 61dfcaf9..efcbda01 100644 --- a/gulp/package.json +++ b/gulp/package.json @@ -112,6 +112,7 @@ "postcss-preset-env": "^6.5.0", "postcss-round-subpixels": "^1.2.0", "postcss-unprefix": "^2.1.3", + "resedit": "1.7.2", "sass-unused": "^0.3.0", "strip-json-comments": "^3.0.1", "trim": "^0.0.1", diff --git a/gulp/standalone.js b/gulp/standalone.js index b70ef04c..8aa369c5 100644 --- a/gulp/standalone.js +++ b/gulp/standalone.js @@ -9,6 +9,17 @@ const buildutils = require("./buildutils"); const execSync = require("child_process").execSync; const electronNotarize = require("electron-notarize"); const { BUILD_VARIANTS } = require("./build_variants"); +const { applyWin32Resources } = require("./win32-resources"); + +// electron-packager edits win32 exe resources through rcedit, requiring wine on non-windows hosts otherwise. +const useRcedit = process.platform === "win32"; +if (!useRcedit) { + const rceditPath = require.resolve("rcedit", { + paths: [path.dirname(require.resolve("electron-packager"))], + }); + require(rceditPath); + require.cache[rceditPath].exports = async () => {}; +} let signAsync; try { @@ -120,16 +131,19 @@ function gulptasksStandalone($, gulp) { asar = { unpackDir: privateArtifactsPath }; } + const icon = path.join(electronBaseDir, "favicon"); + packager({ dir: tempDestBuildDir, + electronVersion: pj.dependencies.electron, appCopyright: "tobspr Games", appVersion: getVersion(), buildVersion: "1.0.0", + icon, arch, platform, asar: asar, executableName: "shapezio", - icon: path.join(electronBaseDir, "favicon"), name: "shapez", out: tempDestDir, overwrite: true, @@ -160,6 +174,16 @@ function gulptasksStandalone($, gulp) { return; } + if (platform === "win32" && !useRcedit) { + console.log("Applying win32 icon and version info via resedit"); + applyWin32Resources(path.join(appPath, "shapezio.exe"), { + icon: icon + ".ico", + version: getVersion(), + productName: "shapez", + company: "tobspr Games", + }); + } + if (variantData.steamAppId) { fs.writeFileSync( path.join(appPath, "LICENSE"), @@ -215,6 +239,13 @@ function gulptasksStandalone($, gulp) { () => { const appFile = path.join(tempDestDir, "shapez-darwin-x64"); const appFileInner = path.join(appFile, "shapez.app"); + + if (!process.env.SHAPEZ_CLI_APPLE_CERT_NAME) { + console.warn("SHAPEZ_CLI_APPLE_CERT_NAME not set, skipping signing and notarization"); + cb(); + return; + } + console.warn("++ Signing ++"); if (variantData.steamAppId) { @@ -263,7 +294,7 @@ function gulptasksStandalone($, gulp) { electronNotarize .notarize({ appPath: path.join(appFile, "shapez.app"), - tool: "legacy", + tool: "notarytool", appBundleId: "tobspr.shapezio.standalone", appleId: process.env.SHAPEZ_CLI_APPLE_ID, @@ -293,6 +324,16 @@ function gulptasksStandalone($, gulp) { taskPrefix + ".build-from-darwin", gulp.series(taskPrefix + ".prepare", gulp.parallel(taskPrefix + ".package.darwin64")) ); + // All three platforms on one machine (win32 needs wine for rcedit on non-windows hosts) + gulp.task( + taskPrefix + ".build-all", + gulp.series( + taskPrefix + ".prepare", + taskPrefix + ".package.darwin64", + taskPrefix + ".package.win64", + taskPrefix + ".package.linux64" + ) + ); } // Steam helpers diff --git a/gulp/steampipe/templates/app-all.vdf b/gulp/steampipe/templates/app-all.vdf new file mode 100644 index 00000000..0a45481c --- /dev/null +++ b/gulp/steampipe/templates/app-all.vdf @@ -0,0 +1,16 @@ +"appbuild" +{ + "appid" "1318690" + "desc" "$BUILD_DESC$" + "buildoutput" "$TMP_DIR$" + "contentroot" "" + "setlive" "" + "preview" "0" + "local" "" + "depots" + { + "1318691" "$VDF_DIR$/standalone-windows.vdf" + "1318692" "$VDF_DIR$/standalone-linux.vdf" + "1318693" "$VDF_DIR$/standalone-darwin.vdf" + } +} diff --git a/gulp/steampipe/templates/standalone-darwin.vdf b/gulp/steampipe/templates/standalone-darwin.vdf index 026ab768..99d2a20d 100644 --- a/gulp/steampipe/templates/standalone-darwin.vdf +++ b/gulp/steampipe/templates/standalone-darwin.vdf @@ -1,7 +1,7 @@ "DepotBuildConfig" { "DepotID" "1318693" - "contentroot" "$BUNDLE_DIR$\standalone-steam\shapez-darwin-x64" + "contentroot" "$BUNDLE_DIR$/standalone-steam/shapez-darwin-x64" "FileMapping" { "LocalPath" "*" diff --git a/gulp/steampipe/templates/standalone-linux.vdf b/gulp/steampipe/templates/standalone-linux.vdf index 9edb1963..d7d3fe35 100644 --- a/gulp/steampipe/templates/standalone-linux.vdf +++ b/gulp/steampipe/templates/standalone-linux.vdf @@ -1,7 +1,7 @@ "DepotBuildConfig" { "DepotID" "1318692" - "contentroot" "$BUNDLE_DIR$\standalone-steam\shapez-linux-x64" + "contentroot" "$BUNDLE_DIR$/standalone-steam/shapez-linux-x64" "FileMapping" { "LocalPath" "*" diff --git a/gulp/steampipe/templates/standalone-windows.vdf b/gulp/steampipe/templates/standalone-windows.vdf index 6f7cb408..1680e8e9 100644 --- a/gulp/steampipe/templates/standalone-windows.vdf +++ b/gulp/steampipe/templates/standalone-windows.vdf @@ -1,7 +1,7 @@ "DepotBuildConfig" { "DepotID" "1318691" - "contentroot" "$BUNDLE_DIR$\standalone-steam\shapez-win32-x64" + "contentroot" "$BUNDLE_DIR$/standalone-steam/shapez-win32-x64" "FileMapping" { "LocalPath" "*" diff --git a/gulp/steampipe/upload-all.sh b/gulp/steampipe/upload-all.sh new file mode 100755 index 00000000..af30c3c0 --- /dev/null +++ b/gulp/steampipe/upload-all.sh @@ -0,0 +1,6 @@ +#!/bin/sh +set -e +cd "$(dirname "$0")/.." +mkdir -p steampipe/built_vdfs steampipe/tmp +yarn gulp standalone.prepareVDF +steamcmd +login "$STEAM_UPLOAD_SHAPEZ_ID" $STEAM_UPLOAD_SHAPEZ_USER +run_app_build "$PWD/steampipe/built_vdfs/app-all.vdf" +quit diff --git a/gulp/win32-resources.js b/gulp/win32-resources.js new file mode 100644 index 00000000..9e9e09dc --- /dev/null +++ b/gulp/win32-resources.js @@ -0,0 +1,55 @@ +// Sets icon and version info on a packaged Windows executable without rcedit/wine, +// so Windows builds can be produced from macOS or Linux hosts. +const fs = require("fs"); +const ResEdit = require("resedit"); + +const LANG_EN_US = 1033; +const CODEPAGE_UNICODE = 1200; +const ICON_GROUP_ID = 1; + +/** + * @param {string} exePath + * @param {{ icon: string, version: string, productName: string, company: string }} opts + */ +function applyWin32Resources(exePath, opts) { + const exe = ResEdit.NtExecutable.from(fs.readFileSync(exePath)); + const res = ResEdit.NtExecutableResource.from(exe); + + const iconFile = ResEdit.Data.IconFile.from(fs.readFileSync(opts.icon)); + ResEdit.Resource.IconGroupEntry.replaceIconsForResource( + res.entries, + ICON_GROUP_ID, + LANG_EN_US, + iconFile.icons.map(icon => icon.data) + ); + + const parts = opts.version.split(".").map(part => parseInt(part, 10) || 0); + while (parts.length < 4) { + parts.push(0); + } + const [major, minor, patch, build] = parts; + + const existing = ResEdit.Resource.VersionInfo.fromEntries(res.entries); + const versionInfo = existing.length > 0 ? existing[0] : ResEdit.Resource.VersionInfo.createEmpty(); + versionInfo.setFileVersion(major, minor, patch, build, LANG_EN_US); + versionInfo.setProductVersion(major, minor, patch, build, LANG_EN_US); + versionInfo.setStringValues( + { lang: LANG_EN_US, codepage: CODEPAGE_UNICODE }, + { + FileDescription: opts.productName, + ProductName: opts.productName, + CompanyName: opts.company, + LegalCopyright: opts.company, + OriginalFilename: "shapezio.exe", + InternalName: "shapezio", + FileVersion: opts.version, + ProductVersion: opts.version, + } + ); + versionInfo.outputToResourceEntries(res.entries); + + res.outputResource(exe); + fs.writeFileSync(exePath, Buffer.from(exe.generate())); +} + +module.exports = { applyWin32Resources }; diff --git a/gulp/yarn.lock b/gulp/yarn.lock index 04f974f3..5a49d6d2 100644 --- a/gulp/yarn.lock +++ b/gulp/yarn.lock @@ -9383,6 +9383,11 @@ pbkdf2@^3.0.3: safe-buffer "^5.0.1" sha.js "^2.4.8" +pe-library@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/pe-library/-/pe-library-0.4.1.tgz#e269be0340dcb13aa6949d743da7d658c3e2fbea" + integrity sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw== + pend@~1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz" @@ -10850,6 +10855,13 @@ requires-port@^1.0.0: resolved "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz" integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= +resedit@1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/resedit/-/resedit-1.7.2.tgz#b1041170b99811710c13f949c7d225871de4cc78" + integrity sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA== + dependencies: + pe-library "^0.4.1" + resolve-cwd@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz"