From ffec528b9defe5fc16f0ca2421840837925585f2 Mon Sep 17 00:00:00 2001 From: vapormusic Date: Wed, 9 Feb 2022 11:40:45 +0700 Subject: [PATCH] universal attempt --- resources/afterPack.js | 9 +- resources/macPackager.js | 399 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 406 insertions(+), 2 deletions(-) create mode 100644 resources/macPackager.js diff --git a/resources/afterPack.js b/resources/afterPack.js index f37d1c48..55dcbc91 100644 --- a/resources/afterPack.js +++ b/resources/afterPack.js @@ -5,12 +5,17 @@ exports.default = function(context) { if (process.platform !== 'darwin') return - fs.unlinkSync(context.appOutDir + '/Cider.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/Electron Framework.sig') + if (fs.existsSync('dist/mac-universal--x64/Cider.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/Electron Framework.sig')) + fs.unlinkSync('dist/mac-universal--x64/Cider.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/Electron Framework.sig') + if (fs.existsSync('dist/mac-universal--arm64/Cider.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/Electron Framework.sig')) + fs.unlinkSync('dist/mac-universal--arm64/Cider.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/Electron Framework.sig') console.log('Castlabs-evs update start') execSync('python3 -m pip install --upgrade castlabs-evs') console.log('Castlabs-evs update complete') - + // xcode 13 + if (fs.existsSync('dist/mac-universal--x64') && fs.existsSync('dist/mac-universal--arm64')) + execSync("cp 'dist/mac-universal--x64/Cider.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/MainMenu.nib/keyedobjects-101300.nib' 'dist/mac-universal--arm64/Cider.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/MainMenu.nib/keyedobjects-101300.nib'",{stdio: 'inherit'}) console.log('VMP signing start') if (fs.existsSync('dist/mac-universal')) diff --git a/resources/macPackager.js b/resources/macPackager.js new file mode 100644 index 00000000..24d56d81 --- /dev/null +++ b/resources/macPackager.js @@ -0,0 +1,399 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const bluebird_lst_1 = require("bluebird-lst"); +const builder_util_1 = require("builder-util"); +const electron_osx_sign_1 = require("electron-osx-sign"); +const promises_1 = require("fs/promises"); +const lazy_val_1 = require("lazy-val"); +const path = require("path"); +const fs_1 = require("builder-util/out/fs"); +const promise_1 = require("builder-util/out/promise"); +const appInfo_1 = require("./appInfo"); +const macCodeSign_1 = require("./codeSign/macCodeSign"); +const core_1 = require("./core"); +const platformPackager_1 = require("./platformPackager"); +const ArchiveTarget_1 = require("./targets/ArchiveTarget"); +const pkg_1 = require("./targets/pkg"); +const targetFactory_1 = require("./targets/targetFactory"); +const macosVersion_1 = require("./util/macosVersion"); +const pathManager_1 = require("./util/pathManager"); +const fs = require("fs/promises"); +class MacPackager extends platformPackager_1.PlatformPackager { + constructor(info) { + super(info, core_1.Platform.MAC); + this.codeSigningInfo = new lazy_val_1.Lazy(() => { + const cscLink = this.getCscLink(); + if (cscLink == null || process.platform !== "darwin") { + return Promise.resolve({ keychainFile: process.env.CSC_KEYCHAIN || null }); + } + return macCodeSign_1.createKeychain({ + tmpDir: this.info.tempDirManager, + cscLink, + cscKeyPassword: this.getCscPassword(), + cscILink: platformPackager_1.chooseNotNull(this.platformSpecificBuildOptions.cscInstallerLink, process.env.CSC_INSTALLER_LINK), + cscIKeyPassword: platformPackager_1.chooseNotNull(this.platformSpecificBuildOptions.cscInstallerKeyPassword, process.env.CSC_INSTALLER_KEY_PASSWORD), + currentDir: this.projectDir, + }).then(result => { + const keychainFile = result.keychainFile; + if (keychainFile != null) { + this.info.disposeOnBuildFinish(() => macCodeSign_1.removeKeychain(keychainFile)); + } + return result; + }); + }); + this._iconPath = new lazy_val_1.Lazy(() => this.getOrConvertIcon("icns")); + } + get defaultTarget() { + return this.info.framework.macOsDefaultTargets; + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + prepareAppInfo(appInfo) { + return new appInfo_1.AppInfo(this.info, this.platformSpecificBuildOptions.bundleVersion, this.platformSpecificBuildOptions); + } + async getIconPath() { + return this._iconPath.value; + } + createTargets(targets, mapper) { + for (const name of targets) { + switch (name) { + case core_1.DIR_TARGET: + break; + case "dmg": { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { DmgTarget } = require("dmg-builder"); + mapper(name, outDir => new DmgTarget(this, outDir)); + break; + } + case "zip": + // https://github.com/electron-userland/electron-builder/issues/2313 + mapper(name, outDir => new ArchiveTarget_1.ArchiveTarget(name, outDir, this, true)); + break; + case "pkg": + mapper(name, outDir => new pkg_1.PkgTarget(this, outDir)); + break; + default: + mapper(name, outDir => (name === "mas" || name === "mas-dev" ? new targetFactory_1.NoOpTarget(name) : targetFactory_1.createCommonTarget(name, outDir, this))); + break; + } + } + } + async doPack(outDir, appOutDir, platformName, arch, platformSpecificBuildOptions, targets) { + switch (arch) { + default: { + return super.doPack(outDir, appOutDir, platformName, arch, platformSpecificBuildOptions, targets); + } + case builder_util_1.Arch.universal: { + const x64Arch = builder_util_1.Arch.x64; + const x64AppOutDir = appOutDir + "--" + builder_util_1.Arch[x64Arch]; + await super.doPack(outDir, x64AppOutDir, platformName, x64Arch, platformSpecificBuildOptions, targets, false, true); + const arm64Arch = builder_util_1.Arch.arm64; + const arm64AppOutPath = appOutDir + "--" + builder_util_1.Arch[arm64Arch]; + await super.doPack(outDir, arm64AppOutPath, platformName, arm64Arch, platformSpecificBuildOptions, targets, false, true); + const framework = this.info.framework; + builder_util_1.log.info({ + platform: platformName, + arch: builder_util_1.Arch[arch], + [`${framework.name}`]: framework.version, + appOutDir: builder_util_1.log.filePath(appOutDir), + }, `packaging`); + const appFile = `${this.appInfo.productFilename}.app`; + const { makeUniversalApp } = require("@electron/universal"); + await makeUniversalApp({ + x64AppPath: path.join(x64AppOutDir, appFile), + arm64AppPath: path.join(arm64AppOutPath, appFile), + outAppPath: path.join(appOutDir, appFile), + force: true, + }); + await fs.rm(x64AppOutDir, { recursive: true, force: true }); + await fs.rm(arm64AppOutPath, { recursive: true, force: true }); + const packContext = { + appOutDir, + outDir, + arch, + targets, + packager: this, + electronPlatformName: platformName, + } + await this.info.afterPack(packContext) + if (framework.afterPack != null) { + await framework.afterPack(packContext) + } + await this.doSignAfterPack(outDir, appOutDir, platformName, arch, platformSpecificBuildOptions, targets); + break; + } + } + } + async pack(outDir, arch, targets, taskManager) { + let nonMasPromise = null; + const hasMas = targets.length !== 0 && targets.some(it => it.name === "mas" || it.name === "mas-dev"); + const prepackaged = this.packagerOptions.prepackaged; + if (!hasMas || targets.length > 1) { + const appPath = prepackaged == null ? path.join(this.computeAppOutDir(outDir, arch), `${this.appInfo.productFilename}.app`) : prepackaged; + nonMasPromise = (prepackaged + ? Promise.resolve() + : this.doPack(outDir, path.dirname(appPath), this.platform.nodeName, arch, this.platformSpecificBuildOptions, targets)).then(() => this.packageInDistributableFormat(appPath, arch, targets, taskManager)); + } + for (const target of targets) { + const targetName = target.name; + if (!(targetName === "mas" || targetName === "mas-dev")) { + continue; + } + const masBuildOptions = builder_util_1.deepAssign({}, this.platformSpecificBuildOptions, this.config.mas); + if (targetName === "mas-dev") { + builder_util_1.deepAssign(masBuildOptions, this.config.masDev, { + type: "development", + }); + } + const targetOutDir = path.join(outDir, `${targetName}${builder_util_1.getArchSuffix(arch)}`); + if (prepackaged == null) { + await this.doPack(outDir, targetOutDir, "mas", arch, masBuildOptions, [target]); + await this.sign(path.join(targetOutDir, `${this.appInfo.productFilename}.app`), targetOutDir, masBuildOptions, arch); + } + else { + await this.sign(prepackaged, targetOutDir, masBuildOptions, arch); + } + } + if (nonMasPromise != null) { + await nonMasPromise; + } + } + async sign(appPath, outDir, masOptions, arch) { + if (!macCodeSign_1.isSignAllowed()) { + return; + } + const isMas = masOptions != null; + const options = masOptions == null ? this.platformSpecificBuildOptions : masOptions; + const qualifier = options.identity; + if (!isMas && qualifier === null) { + if (this.forceCodeSigning) { + throw new builder_util_1.InvalidConfigurationError("identity explicitly is set to null, but forceCodeSigning is set to true"); + } + builder_util_1.log.info({ reason: "identity explicitly is set to null" }, "skipped macOS code signing"); + return; + } + const keychainFile = (await this.codeSigningInfo.value).keychainFile; + const explicitType = options.type; + const type = explicitType || "distribution"; + const isDevelopment = type === "development"; + const certificateTypes = getCertificateTypes(isMas, isDevelopment); + let identity = null; + for (const certificateType of certificateTypes) { + identity = await macCodeSign_1.findIdentity(certificateType, qualifier, keychainFile); + if (identity != null) { + break; + } + } + if (identity == null) { + if (!isMas && !isDevelopment && explicitType !== "distribution") { + identity = await macCodeSign_1.findIdentity("Mac Developer", qualifier, keychainFile); + if (identity != null) { + builder_util_1.log.warn("Mac Developer is used to sign app — it is only for development and testing, not for production"); + } + } + if (identity == null) { + await macCodeSign_1.reportError(isMas, certificateTypes, qualifier, keychainFile, this.forceCodeSigning); + return; + } + } + if (!macosVersion_1.isMacOsHighSierra()) { + throw new builder_util_1.InvalidConfigurationError("macOS High Sierra 10.13.6 is required to sign"); + } + let filter = options.signIgnore; + if (Array.isArray(filter)) { + if (filter.length == 0) { + filter = null; + } + } + else if (filter != null) { + filter = filter.length === 0 ? null : [filter]; + } + const filterRe = filter == null ? null : filter.map(it => new RegExp(it)); + let binaries = options.binaries || undefined; + if (binaries) { + // Accept absolute paths for external binaries, else resolve relative paths from the artifact's app Contents path. + const userDefinedBinaries = await Promise.all(binaries.map(async (destination) => { + if (await fs_1.statOrNull(destination)) { + return destination; + } + return path.resolve(appPath, destination); + })); + // Insert at front to prioritize signing. We still sort by depth next + binaries = userDefinedBinaries.concat(binaries); + builder_util_1.log.info("Signing addtional user-defined binaries: " + JSON.stringify(userDefinedBinaries, null, 1)); + } + const signOptions = { + "identity-validation": false, + // https://github.com/electron-userland/electron-builder/issues/1699 + // kext are signed by the chipset manufacturers. You need a special certificate (only available on request) from Apple to be able to sign kext. + ignore: (file) => { + if (filterRe != null) { + for (const regExp of filterRe) { + if (regExp.test(file)) { + return true; + } + } + } + return (file.endsWith(".kext") || + file.startsWith("/Contents/PlugIns", appPath.length) || + file.includes("/node_modules/puppeteer/.local-chromium") || + file.includes("/node_modules/playwright-firefox/.local-browsers") || + file.includes("/node_modules/playwright/.local-browsers")); + /* Those are browser automating modules, browser (chromium, nightly) cannot be signed + https://github.com/electron-userland/electron-builder/issues/2010 + https://github.com/electron-userland/electron-builder/issues/5383 + */ + }, + identity: identity, + type, + platform: isMas ? "mas" : "darwin", + version: this.config.electronVersion, + app: appPath, + keychain: keychainFile || undefined, + binaries, + timestamp: isMas ? masOptions === null || masOptions === void 0 ? void 0 : masOptions.timestamp : options.timestamp, + requirements: isMas || this.platformSpecificBuildOptions.requirements == null ? undefined : await this.getResource(this.platformSpecificBuildOptions.requirements), + // https://github.com/electron-userland/electron-osx-sign/issues/196 + // will fail on 10.14.5+ because a signed but unnotarized app is also rejected. + "gatekeeper-assess": options.gatekeeperAssess === true, + // https://github.com/electron-userland/electron-builder/issues/1480 + "strict-verify": options.strictVerify, + hardenedRuntime: isMas ? masOptions && masOptions.hardenedRuntime === true : options.hardenedRuntime !== false, + }; + await this.adjustSignOptions(signOptions, masOptions); + builder_util_1.log.info({ + file: builder_util_1.log.filePath(appPath), + identityName: identity.name, + identityHash: identity.hash, + provisioningProfile: signOptions["provisioning-profile"] || "none", + }, "signing"); + await this.doSign(signOptions); + // https://github.com/electron-userland/electron-builder/issues/1196#issuecomment-312310209 + if (masOptions != null && !isDevelopment) { + const certType = isDevelopment ? "Mac Developer" : "3rd Party Mac Developer Installer"; + const masInstallerIdentity = await macCodeSign_1.findIdentity(certType, masOptions.identity, keychainFile); + if (masInstallerIdentity == null) { + throw new builder_util_1.InvalidConfigurationError(`Cannot find valid "${certType}" identity to sign MAS installer, please see https://electron.build/code-signing`); + } + // mas uploaded to AppStore, so, use "-" instead of space for name + const artifactName = this.expandArtifactNamePattern(masOptions, "pkg", arch); + const artifactPath = path.join(outDir, artifactName); + await this.doFlat(appPath, artifactPath, masInstallerIdentity, keychainFile); + await this.dispatchArtifactCreated(artifactPath, null, builder_util_1.Arch.x64, this.computeSafeArtifactName(artifactName, "pkg", arch, true, this.platformSpecificBuildOptions.defaultArch)); + } + } + async adjustSignOptions(signOptions, masOptions) { + const resourceList = await this.resourceList; + const customSignOptions = masOptions || this.platformSpecificBuildOptions; + const entitlementsSuffix = masOptions == null ? "mac" : "mas"; + let entitlements = customSignOptions.entitlements; + if (entitlements == null) { + const p = `entitlements.${entitlementsSuffix}.plist`; + if (resourceList.includes(p)) { + entitlements = path.join(this.info.buildResourcesDir, p); + } + else { + entitlements = pathManager_1.getTemplatePath("entitlements.mac.plist"); + } + } + signOptions.entitlements = entitlements; + let entitlementsInherit = customSignOptions.entitlementsInherit; + if (entitlementsInherit == null) { + const p = `entitlements.${entitlementsSuffix}.inherit.plist`; + if (resourceList.includes(p)) { + entitlementsInherit = path.join(this.info.buildResourcesDir, p); + } + else { + entitlementsInherit = pathManager_1.getTemplatePath("entitlements.mac.plist"); + } + } + signOptions["entitlements-inherit"] = entitlementsInherit; + if (customSignOptions.provisioningProfile != null) { + signOptions["provisioning-profile"] = customSignOptions.provisioningProfile; + } + signOptions["entitlements-loginhelper"] = customSignOptions.entitlementsLoginHelper; + } + //noinspection JSMethodCanBeStatic + async doSign(opts) { + return electron_osx_sign_1.signAsync(opts); + } + //noinspection JSMethodCanBeStatic + async doFlat(appPath, outFile, identity, keychain) { + // productbuild doesn't created directory for out file + await promises_1.mkdir(path.dirname(outFile), { recursive: true }); + const args = pkg_1.prepareProductBuildArgs(identity, keychain); + args.push("--component", appPath, "/Applications"); + args.push(outFile); + return await builder_util_1.exec("productbuild", args); + } + getElectronSrcDir(dist) { + return path.resolve(this.projectDir, dist, this.info.framework.distMacOsAppName); + } + getElectronDestinationDir(appOutDir) { + return path.join(appOutDir, this.info.framework.distMacOsAppName); + } + // todo fileAssociations + async applyCommonInfo(appPlist, contentsPath) { + const appInfo = this.appInfo; + const appFilename = appInfo.productFilename; + // https://github.com/electron-userland/electron-builder/issues/1278 + appPlist.CFBundleExecutable = appFilename.endsWith(" Helper") ? appFilename.substring(0, appFilename.length - " Helper".length) : appFilename; + const icon = await this.getIconPath(); + if (icon != null) { + const oldIcon = appPlist.CFBundleIconFile; + const resourcesPath = path.join(contentsPath, "Resources"); + if (oldIcon != null) { + await fs_1.unlinkIfExists(path.join(resourcesPath, oldIcon)); + } + const iconFileName = "icon.icns"; + appPlist.CFBundleIconFile = iconFileName; + await fs_1.copyFile(icon, path.join(resourcesPath, iconFileName)); + } + appPlist.CFBundleName = appInfo.productName; + appPlist.CFBundleDisplayName = appInfo.productName; + const minimumSystemVersion = this.platformSpecificBuildOptions.minimumSystemVersion; + if (minimumSystemVersion != null) { + appPlist.LSMinimumSystemVersion = minimumSystemVersion; + } + appPlist.CFBundleIdentifier = appInfo.macBundleIdentifier; + appPlist.CFBundleShortVersionString = this.platformSpecificBuildOptions.bundleShortVersion || appInfo.version; + appPlist.CFBundleVersion = appInfo.buildVersion; + builder_util_1.use(this.platformSpecificBuildOptions.category || this.config.category, it => (appPlist.LSApplicationCategoryType = it)); + appPlist.NSHumanReadableCopyright = appInfo.copyright; + if (this.platformSpecificBuildOptions.darkModeSupport) { + appPlist.NSRequiresAquaSystemAppearance = false; + } + const extendInfo = this.platformSpecificBuildOptions.extendInfo; + if (extendInfo != null) { + Object.assign(appPlist, extendInfo); + } + } + async signApp(packContext, isAsar) { + const appFileName = `${this.appInfo.productFilename}.app`; + await bluebird_lst_1.default.map(promises_1.readdir(packContext.appOutDir), (file) => { + if (file === appFileName) { + return this.sign(path.join(packContext.appOutDir, file), null, null, null); + } + return null; + }); + if (!isAsar) { + return; + } + const outResourcesDir = path.join(packContext.appOutDir, "resources", "app.asar.unpacked"); + await bluebird_lst_1.default.map(promise_1.orIfFileNotExist(promises_1.readdir(outResourcesDir), []), (file) => { + if (file.endsWith(".app")) { + return this.sign(path.join(outResourcesDir, file), null, null, null); + } + else { + return null; + } + }); + } +} +exports.default = MacPackager; +function getCertificateTypes(isMas, isDevelopment) { + if (isDevelopment) { + return isMas ? ["Mac Developer", "Apple Development"] : ["Developer ID Application"]; + } + return isMas ? ["Apple Distribution"] : ["Developer ID Application"]; +} +//# sourceMappingURL=macPackager.js.map \ No newline at end of file