2021-05-19 13:53:01 +02:00
|
|
|
import {babelDesktopPlugins, resolveLibs} from "./RollupConfig.js"
|
2021-05-07 10:28:22 +02:00
|
|
|
import {nativeDepWorkaroundPlugin, pluginNativeLoader} from "./RollupPlugins.js"
|
2019-09-13 13:49:11 +02:00
|
|
|
import nodeResolve from "@rollup/plugin-node-resolve"
|
|
|
|
|
import fs from "fs"
|
|
|
|
|
import path from "path"
|
|
|
|
|
import {rollup} from "rollup"
|
|
|
|
|
import {terser} from "rollup-plugin-terser"
|
|
|
|
|
import pluginBabel from "@rollup/plugin-babel"
|
|
|
|
|
import commonjs from "@rollup/plugin-commonjs"
|
|
|
|
|
import electronBuilder from "electron-builder"
|
|
|
|
|
import generatePackgeJson from "./electron-package-json-template.js"
|
|
|
|
|
import {create as createEnv, preludeEnvPlugin} from "./env.js"
|
2021-06-17 14:33:18 +02:00
|
|
|
import cp from 'child_process'
|
|
|
|
|
import util from 'util'
|
2018-09-24 11:56:41 +02:00
|
|
|
|
2019-09-13 13:49:11 +02:00
|
|
|
const {babel} = pluginBabel
|
2021-08-11 17:53:48 +02:00
|
|
|
const exec = util.promisify(cp.exec)
|
2019-09-13 13:49:11 +02:00
|
|
|
|
|
|
|
|
export async function buildDesktop({
|
|
|
|
|
dirname, // directory this was called from
|
|
|
|
|
version, // application version that gets built
|
|
|
|
|
targets, // which desktop targets to build and how to package them
|
|
|
|
|
updateUrl, // where the client should pull its updates from, if any
|
|
|
|
|
nameSuffix, // suffix used to distinguish test-, prod- or snapshot builds on the same machine
|
|
|
|
|
notarize, // for the MacOs notarization feature
|
|
|
|
|
outDir, // where copy the finished artifacts
|
2021-05-03 14:03:52 +02:00
|
|
|
unpacked, // output desktop client without packing it into an installer
|
2021-01-29 13:56:41 +01:00
|
|
|
}) {
|
2019-09-13 13:49:11 +02:00
|
|
|
// The idea is that we
|
|
|
|
|
// - build desktop code into build/dist/desktop
|
|
|
|
|
// - package the whole dist directory into the app
|
|
|
|
|
// - move installers out of the dist into build/desktop-whatever
|
|
|
|
|
// - cleanup dist directory
|
|
|
|
|
// It's messy
|
2018-10-02 16:19:03 +02:00
|
|
|
const targetString = Object.keys(targets)
|
|
|
|
|
.filter(k => typeof targets[k] !== "undefined")
|
|
|
|
|
.join(" ")
|
|
|
|
|
console.log("Building desktop client for v" + version + " (" + targetString + ")...")
|
2019-09-13 13:49:11 +02:00
|
|
|
const updateSubDir = "desktop" + nameSuffix
|
|
|
|
|
const distDir = path.join(dirname, "build", "dist")
|
|
|
|
|
outDir = path.join(outDir || path.join(distDir, ".."), updateSubDir)
|
|
|
|
|
await fs.promises.mkdir(outDir, {recursive: true})
|
|
|
|
|
|
2019-11-07 16:08:49 +01:00
|
|
|
|
2019-09-13 13:49:11 +02:00
|
|
|
// We need to get the right build of native dependencies. There's a tool called node-gyp which can build for different architectures
|
|
|
|
|
// and downloads everything it needs. Usually dependencies build themselves in post-install script.
|
|
|
|
|
// Currently we have keytar which avoids building itself if possible and only build
|
2018-12-13 17:59:47 +01:00
|
|
|
console.log("Updating electron-builder config...")
|
2019-09-13 13:49:11 +02:00
|
|
|
const content = generatePackgeJson({
|
|
|
|
|
nameSuffix,
|
|
|
|
|
version,
|
|
|
|
|
updateUrl,
|
2020-04-07 10:32:03 +02:00
|
|
|
iconPath: path.join(dirname, "/resources/desktop-icons/logo-solo-red.png"),
|
2019-09-13 13:49:11 +02:00
|
|
|
notarize,
|
|
|
|
|
unpacked,
|
2021-03-16 11:16:48 +01:00
|
|
|
sign: (process.env.DEBUG_SIGN && updateUrl !== "") || !!process.env.JENKINS,
|
2020-04-07 10:32:03 +02:00
|
|
|
})
|
2019-09-13 13:49:11 +02:00
|
|
|
console.log("updateUrl is", updateUrl)
|
|
|
|
|
await fs.promises.writeFile("./build/dist/package.json", JSON.stringify(content), 'utf-8')
|
2021-09-09 15:50:46 +02:00
|
|
|
if (targets["win32"] != null) await getMapirs(distDir)
|
2021-06-17 14:33:18 +02:00
|
|
|
|
2021-09-07 13:50:51 +02:00
|
|
|
await maybeGetKeytar(targets)
|
2021-06-17 14:33:18 +02:00
|
|
|
|
2019-09-13 13:49:11 +02:00
|
|
|
// prepare files
|
|
|
|
|
try {
|
2021-06-17 14:33:18 +02:00
|
|
|
await fs.promises.rm(path.join(distDir, "..", updateSubDir), {recursive: true})
|
2019-09-13 13:49:11 +02:00
|
|
|
} catch (e) {
|
|
|
|
|
if (e.code !== 'ENOENT') {
|
|
|
|
|
throw e
|
2018-12-13 17:59:47 +01:00
|
|
|
}
|
|
|
|
|
}
|
2019-09-13 13:49:11 +02:00
|
|
|
console.log("Bundling desktop client")
|
2021-01-29 13:56:41 +01:00
|
|
|
await rollupDesktop(dirname, path.join(distDir, "desktop"), version)
|
2018-12-13 17:59:47 +01:00
|
|
|
|
2019-09-13 13:49:11 +02:00
|
|
|
console.log("Starting installer build...")
|
2021-08-11 17:53:48 +02:00
|
|
|
if (process.platform.startsWith("darwin")) {
|
|
|
|
|
// dmg-license is required by electron to build the mac installer
|
|
|
|
|
// We can't put dmg-license as a dependency in package.json because
|
|
|
|
|
// it will cause npm install to fail if you do it in linux or windows
|
|
|
|
|
// We could install it in mac and then it will be in package-lock.json
|
|
|
|
|
// but then we will have to be vigilant that it doesn't get removed ever
|
|
|
|
|
await exec("npm install dmg-license")
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-13 13:49:11 +02:00
|
|
|
// package for linux, win, mac
|
|
|
|
|
await electronBuilder.build({
|
|
|
|
|
_: ['build'],
|
2021-06-17 14:33:18 +02:00
|
|
|
win: targets.win32,
|
2019-09-13 13:49:11 +02:00
|
|
|
mac: targets.mac,
|
|
|
|
|
linux: targets.linux,
|
2021-02-23 08:36:38 +01:00
|
|
|
publish: 'always',
|
2019-09-13 13:49:11 +02:00
|
|
|
project: distDir
|
|
|
|
|
})
|
|
|
|
|
console.log("Move output to ", outDir)
|
|
|
|
|
await fs.promises.mkdir(outDir, {recursive: true})
|
|
|
|
|
await Promise.all(
|
|
|
|
|
fs.readdirSync(path.join(distDir, '/installers'))
|
2021-04-06 14:43:41 +02:00
|
|
|
.filter((file => file.startsWith(content.name) || file.endsWith('.yml') || file.endsWith("-unpacked")))
|
|
|
|
|
.map(file => fs.promises.rename(
|
2021-05-19 13:53:01 +02:00
|
|
|
path.join(distDir, '/installers/', file),
|
|
|
|
|
path.join(outDir, file)
|
|
|
|
|
)
|
|
|
|
|
)
|
2019-09-13 13:49:11 +02:00
|
|
|
)
|
|
|
|
|
await Promise.all([
|
2021-06-17 14:33:18 +02:00
|
|
|
fs.promises.rm(path.join(distDir, '/installers/'), {recursive: true}),
|
|
|
|
|
fs.promises.rm(path.join(distDir, '/node_modules/'), {recursive: true}),
|
2019-09-13 13:49:11 +02:00
|
|
|
fs.promises.unlink(path.join(distDir, '/package.json')),
|
|
|
|
|
fs.promises.unlink(path.join(distDir, '/package-lock.json'),),
|
|
|
|
|
])
|
|
|
|
|
}
|
2018-12-13 17:59:47 +01:00
|
|
|
|
2021-01-29 13:56:41 +01:00
|
|
|
async function rollupDesktop(dirname, outDir, version) {
|
2019-09-13 13:49:11 +02:00
|
|
|
function babelPreset() {
|
|
|
|
|
return babel({
|
2021-05-19 13:53:01 +02:00
|
|
|
plugins: babelDesktopPlugins,
|
2019-09-13 13:49:11 +02:00
|
|
|
babelHelpers: "bundled",
|
|
|
|
|
})
|
2018-12-13 17:59:47 +01:00
|
|
|
}
|
|
|
|
|
|
2019-09-13 13:49:11 +02:00
|
|
|
const mainBundle = await rollup({
|
|
|
|
|
input: path.join(dirname, "src/desktop/DesktopMain.js"),
|
2021-02-23 08:36:38 +01:00
|
|
|
preserveEntrySignatures: false,
|
2019-09-13 13:49:11 +02:00
|
|
|
plugins: [
|
|
|
|
|
babelPreset(),
|
|
|
|
|
resolveLibs(),
|
|
|
|
|
nativeDepWorkaroundPlugin(),
|
2021-05-03 14:03:52 +02:00
|
|
|
pluginNativeLoader(),
|
2019-09-13 13:49:11 +02:00
|
|
|
nodeResolve({preferBuiltins: true}),
|
2021-05-07 10:28:22 +02:00
|
|
|
// requireReturnsDefault: "preferred" is needed in order to correclty generate a wrapper for the native keytar module
|
2021-05-03 14:03:52 +02:00
|
|
|
commonjs({
|
|
|
|
|
exclude: "src/**",
|
|
|
|
|
requireReturnsDefault: "preferred",
|
|
|
|
|
}),
|
2021-01-29 13:56:41 +01:00
|
|
|
terser(),
|
2019-09-13 13:49:11 +02:00
|
|
|
preludeEnvPlugin(createEnv(null, version, "Desktop", true))
|
|
|
|
|
]
|
|
|
|
|
})
|
|
|
|
|
await mainBundle.write({sourcemap: true, format: "commonjs", dir: outDir})
|
2021-02-23 08:36:38 +01:00
|
|
|
await fs.promises.copyFile(path.join(dirname, "src/desktop/preload.js"), path.join(outDir, "preload.js"))
|
2021-06-17 14:33:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* we can't cross-compile keytar, so we need to have the prebuilt version
|
|
|
|
|
* when building a desktop client for windows on linux
|
2021-09-07 13:50:51 +02:00
|
|
|
*
|
|
|
|
|
* napiVersion is the N-API version that's used by keytar.
|
|
|
|
|
* the current release artifacts on github are namend accordingly,
|
|
|
|
|
* e.g. keytar-v7.7.0-napi-v3-linux-x64.tar.gz for N-API v3
|
2021-06-17 14:33:18 +02:00
|
|
|
*/
|
2021-09-07 13:50:51 +02:00
|
|
|
async function maybeGetKeytar(targets, napiVersion = 3) {
|
2021-06-17 14:33:18 +02:00
|
|
|
const trg = Object.keys(targets)
|
|
|
|
|
.filter(t => targets[t] != null)
|
|
|
|
|
.filter(t => t !== process.platform)
|
|
|
|
|
if (trg.length === 0 || process.env.JENKINS) return
|
2021-09-07 13:50:51 +02:00
|
|
|
console.log("fetching prebuilt keytar for", trg, "N-API", napiVersion)
|
2021-08-11 17:53:48 +02:00
|
|
|
return Promise.all(trg.map(t => exec(
|
2021-09-07 13:50:51 +02:00
|
|
|
`prebuild-install --platform ${t} --target ${napiVersion} --tag-prefix v --runtime napi --verbose`,
|
|
|
|
|
{
|
|
|
|
|
cwd: './node_modules/keytar/',
|
|
|
|
|
stdout: 'inherit'
|
|
|
|
|
}
|
2021-06-17 14:33:18 +02:00
|
|
|
)))
|
2021-09-09 15:50:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function getMapirs(distDir) {
|
|
|
|
|
const dllName = "mapirs.dll"
|
|
|
|
|
const dllSrc = process.platform === "win32"
|
2021-09-28 16:10:39 +02:00
|
|
|
? path.join('../mapirs/target/x86_64-pc-windows-msvc/release', dllName)
|
2021-09-09 15:50:46 +02:00
|
|
|
: path.join('../mapirs/target/x86_64-pc-windows-gnu/release', dllName)
|
|
|
|
|
const dllTrg = path.join(distDir, dllName)
|
|
|
|
|
console.log("trying to copy", dllName, "from", dllSrc, "to", dllTrg)
|
|
|
|
|
try {
|
|
|
|
|
await fs.promises.copyFile(dllSrc, dllTrg)
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.log("no local", dllName, "found, using release from github")
|
|
|
|
|
await downloadLatestMapirs(dllName, dllTrg)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function downloadLatestMapirs(dllName, dllTrg) {
|
|
|
|
|
const {Octokit} = await import("@octokit/rest")
|
|
|
|
|
const octokit = new Octokit();
|
|
|
|
|
const opts = {
|
|
|
|
|
owner: "tutao",
|
|
|
|
|
repo: "mapirs"
|
|
|
|
|
}
|
|
|
|
|
const res = await octokit.request('GET /repos/{owner}/{repo}/releases/latest', opts)
|
|
|
|
|
const asset_id = res.data.assets.find(a => a.name.startsWith(dllName)).id
|
|
|
|
|
const asset = await octokit.repos.getReleaseAsset(Object.assign(opts, {
|
|
|
|
|
asset_id,
|
|
|
|
|
headers: {
|
|
|
|
|
"Accept": "application/octet-stream"
|
|
|
|
|
}
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
await fs.promises.writeFile(dllTrg, Buffer.from(asset.data))
|
2019-09-13 13:49:11 +02:00
|
|
|
}
|