Skip to content

Commit

Permalink
Packaging keytar prebuilts
Browse files Browse the repository at this point in the history
  • Loading branch information
thsmi committed Apr 24, 2020
1 parent 2557590 commit 6da9997
Show file tree
Hide file tree
Showing 4 changed files with 4,418 additions and 3,748 deletions.
204 changes: 186 additions & 18 deletions gulp/gulpfile.app.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,87 @@
* Thomas Schmid <schmid-thomas@gmx.net>
*/

const { src, dest, watch, parallel } = require('gulp');
const { src, dest, watch, parallel, series } = require('gulp');
const { existsSync } = require('fs');
const { readFile } = require('fs').promises;

const logger = require('gulplog');

const common = require("./gulpfile.common.js");

const path = require('path');
const tar = require('tar');
const { getAbi } = require('node-abi');


const CACHE_DIR_APP = path.join(common.BASE_DIR_BUILD, "electron/cache");
const BUILD_DIR_APP = path.join(common.BASE_DIR_BUILD, "electron/resources");
const OUTPUT_DIR_APP = path.join(common.BASE_DIR_BUILD, "electron/out");
const BASE_DIR_APP = "./src/app/";


const KEYTAR_NAME = "keytar";
const BASE_DIR_KEYTAR = `./node_modules/${KEYTAR_NAME}/`;
const BUILD_DIR_KEYTAR = path.join(BUILD_DIR_APP, `/libs/${KEYTAR_NAME}`);
const PREBUILT_URL_KEYTAR = `https://github.com/atom/node-keytar/releases/download`;

const WIN_ARCH = "x64";
const WIN_PLATFORM = "win32";
const LINUX_ARCH = "x64";
const LINUX_PLATFORM = "linux";
const MAC_ARCH = "x64";
const MAC_PLATFORM = "mas";

const RUNTIME_ELECTRON = "electron";

/**
* Extracts a tar or tar.gz file to the given destination.
*
* @param {string} filename
* the path to the tar file
* @param {string} destination
* the destination folder into which the tar should be extracted.
*/
async function untar(filename, destination) {
"use strict";

logger.debug(`Extracting ${filename} to ${destination}`);

return await tar.x({
file: filename,
cwd: destination,
strict: true
});
}

/**
* Gets electron's release as well as the abi version.
* Throws in case the electron runtime could not be found.
*
* @param {string} dir
* the path to the electron runtime.
* @returns {{ version : string, abi : string}}
* electrons release as well as the abi version.
*/
async function getElectronVersion(dir) {

"use strict";

const versionFile = path.join(dir + '/version');

if (!existsSync(versionFile))
throw new Error(`Failed to detect version, no electron runtime in ${dir}`);

const version = (await readFile(versionFile)).toString();
const abi = await getAbi(version, RUNTIME_ELECTRON);

return {
"version": version,
"abi": abi
};
}


/**
* Copies and updates the package.json inside the build directory.
* It is typically used by other tools like the electron-packager.
Expand Down Expand Up @@ -141,6 +207,100 @@ function packageCommon() {
]).pipe(dest(BUILD_DIR_APP + '/libs'));
}

/**
* The keytar files need to go into the app/lib directory.
* After packaging electron the you need to add the native
* prebuilt libraries.
*
* @returns {Stream}
* a stream to be consumed by gulp
*/
function packageKeytar() {
"use strict";

return src([
BASE_DIR_KEYTAR + "/**",
// Filter out the rfc documents
"!" + BASE_DIR_KEYTAR + "/*.gyp",
"!" + BASE_DIR_KEYTAR + "/*.ts",
"!" + BASE_DIR_KEYTAR + "/src/**",
"!" + BASE_DIR_KEYTAR + "/build/**",
"!" + BASE_DIR_KEYTAR + "/node_modules/**"
]).pipe(dest(BUILD_DIR_KEYTAR));
}

/**
* Deploys the native prebuilt node modules into an electron application.
* Typically you first package the module without any prebuilt files.
* Then invoke electron packager with the target operating system and
* architecture. Then call this method to deploy the matching prebuilt modules
* to the electron packager output.
*
* Keep in mind a prebuilt is a binary. It needs to be binary
* compatible and the abi has to match. An Windows Electron requires
* a windows prebuilt, a Linux electron a linux prebuilt, etc
*
* @param {string} destination
* the location to the electron framework
* @param {string} pkgDir
* the directory to the package
* @param {string} pkgName
* the the package name
* @param {string} platform
* the platform for which the prebuilt packages
* @param {string} arch
* the architecture for the prebuilt packages
*/
async function deployPrebuilt(destination, pkgDir, pkgName, platform, arch) {
"use strict";

logger.debug(`Packaging Prebuilt ${pkgName} for ${platform}-${arch}`);

destination = path.resolve(path.join(destination, `/sieve-${platform}-${arch}`));

const pkg = JSON.parse(
await readFile(path.join(pkgDir, '/package.json')));

const abi = (await getElectronVersion(destination)).abi;

const filename = `${pkgName}-v${pkg.version}-${RUNTIME_ELECTRON}-v${abi}-${platform}-${arch}.tar.gz`;
const prebuilt = path.join(CACHE_DIR_APP, filename);

if (!existsSync(prebuilt)) {
const url = `${PREBUILT_URL_KEYTAR}/v${pkg.version}/${filename}`;
await common.download(url, prebuilt);
}

await untar(prebuilt,
path.join(destination, "/resources/app/libs/keytar/"));

return;
}

/**
* Packages the Keytar prebuilt modules into the win32 build output
*/
async function packageKeytarWin32() {
"use strict";
return await deployPrebuilt(OUTPUT_DIR_APP, BUILD_DIR_KEYTAR, KEYTAR_NAME, WIN_PLATFORM, WIN_ARCH);
}

/**
* Packages the Keytar prebuilt modules into the linux build output
*/
async function packageKeytarLinux() {
"use strict";
return await deployPrebuilt(OUTPUT_DIR_APP, BUILD_DIR_KEYTAR, KEYTAR_NAME, LINUX_PLATFORM, LINUX_ARCH);
}

/**
* Packages the Keytar prebuilt modules into the macOS build output
*/
async function packageKeytarMacOS() {
"use strict";
return await deployPrebuilt(OUTPUT_DIR_APP, BUILD_DIR_KEYTAR, KEYTAR_NAME, MAC_PLATFORM, MAC_ARCH);
}

/**
* Packages the build directory and electron for windows.
*/
Expand All @@ -152,12 +312,11 @@ async function packageWin32() {
arch: WIN_ARCH,
platform: WIN_PLATFORM,
download: {
cacheRoot: path.join(common.BASE_DIR_BUILD, "/electron/cache")
cacheRoot: CACHE_DIR_APP
},
out: path.join(common.BASE_DIR_BUILD, "/electron/out"),
out: OUTPUT_DIR_APP,
overwrite: true,
icon: path.join(common.BASE_DIR_COMMON, "icons/win.ico"),
prune: true
icon: path.join(common.BASE_DIR_COMMON, "icons/win.ico")
};

const packager = require('electron-packager');
Expand All @@ -175,12 +334,10 @@ async function packageLinux() {
arch: LINUX_ARCH,
platform: LINUX_PLATFORM,
download: {
cache: path.join(common.BASE_DIR_BUILD, "/electron/cache")
cache: CACHE_DIR_APP
},
out: path.join(common.BASE_DIR_BUILD, "/electron/out"),
out: OUTPUT_DIR_APP,
overwrite: true,
// packageManager : "yarn"
// packageManager : false,
prune: true
};

Expand All @@ -199,9 +356,9 @@ async function packageMacOS() {
arch: MAC_ARCH,
platform: MAC_PLATFORM,
download: {
cache: path.join(common.BASE_DIR_BUILD, "/electron/cache")
cache: CACHE_DIR_APP
},
out: path.join(common.BASE_DIR_BUILD, "/electron/out"),
out: OUTPUT_DIR_APP,
overwrite: true,
icon: path.join(common.BASE_DIR_COMMON, "icons/mac.icns"),
prune: true
Expand Down Expand Up @@ -253,7 +410,7 @@ async function zipWin32() {

const version = (await common.getPackageVersion()).join(".");

const source = path.resolve(path.join(common.BASE_DIR_BUILD, `/electron/out/sieve-${WIN_PLATFORM}-${WIN_ARCH}`));
const source = path.resolve(path.join(OUTPUT_DIR_APP, `sieve-${WIN_PLATFORM}-${WIN_ARCH}`));
const destination = path.join(common.BASE_DIR_BUILD, `sieve-${version}-${WIN_PLATFORM}-${WIN_ARCH}.zip`);

await common.compress(source, destination);
Expand All @@ -267,7 +424,7 @@ async function zipLinux() {

const version = (await common.getPackageVersion()).join(".");

const source = path.resolve(path.join(common.BASE_DIR_BUILD, `/electron/out/sieve-${LINUX_PLATFORM}-${LINUX_ARCH}`));
const source = path.resolve(path.join(OUTPUT_DIR_APP, `sieve-${LINUX_PLATFORM}-${LINUX_ARCH}`));
const destination = path.join(common.BASE_DIR_BUILD, `sieve-${version}-${LINUX_PLATFORM}-${LINUX_ARCH}.zip`);

const options = {
Expand All @@ -293,9 +450,20 @@ exports["packageLicense"] = packageLicense;
exports["packageSrc"] = packageSrc;
exports["packageCommon"] = packageCommon;

exports["packageWin32"] = packageWin32;
exports["packageLinux"] = packageLinux;
exports["packageMacOS"] = packageMacOS;
exports["packageWin32"] = series(
packageWin32,
packageKeytarWin32
);

exports["packageLinux"] = series(
packageLinux,
packageKeytarLinux
);

exports["packageMacOS"] = series(
packageMacOS,
packageKeytarMacOS
);

exports["zipWin32"] = zipWin32;
exports["zipLinux"] = zipLinux;
Expand All @@ -308,6 +476,6 @@ exports['package'] = parallel(
packageMaterialIcons,
packageLicense,
packageSrc,
packageCommon
packageCommon,
packageKeytar
);

54 changes: 52 additions & 2 deletions gulp/gulpfile.common.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ const { createWriteStream, existsSync } = require('fs');
const path = require('path');
const yazl = require('yazl');



const BASE_DIR_BOOTSTRAP = "./node_modules/bootstrap/dist";
const BASE_DIR_MATERIALICONS = "./node_modules/material-design-icons-iconfont/dist";
const BASE_DIR_JQUERY = "./node_modules/jquery/dist";
Expand Down Expand Up @@ -352,8 +350,60 @@ async function compress(source, destination, options) {
});
}

/**
* Downloads a file from an https url and stores the data into the given file.
* It follows redirects. Upon a non 200 status code an error is thrown.
*
* @param {string} url
* the download url.
* @param {string} destination
* the file into which the downloaded data should be stored.
* In case the file exists it will be silently overwritten.
*/
async function download(url, destination) {

"use strict";

const https = require('https');
const fs = require('fs');

logger.debug(`Downloading ${url} to ${destination}`);

return await new Promise((resolve, reject) => {
https.get(url, async function (response) {

try {
if (response.statusCode >= 300 && response.statusCode <= 308) {
logger.debug(`Following redirect to ${response.headers.location}`);
await download(response.headers.location, destination);

resolve();
return;
}

if (response.statusCode !== 200)
throw Error(`Response failed with status code ${response.statusCode}.`);

const file = fs.createWriteStream(destination);

response.pipe(file);

file.on('finish', function () {
file.close(function () {
resolve();
});
});
} catch (ex) {
reject(new Error(ex));
}
});
});
}


exports["clean"] = clean;
exports["compress"] = compress;
exports["download"] = download;

exports["packageJQuery"] = packageJQuery;
exports["packageCodeMirror"] = packageCodeMirror;
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
"eslint-plugin-jsdoc": "^24.0.0",
"gulp": "^4.0.0",
"jquery": "~3.4.1",
"keytar": "^5.4.0",
"material-design-icons-iconfont": "^5.0.1",
"tar": "^6.0.1",
"yazl": "^2.5.1"
},
"scripts": {
Expand All @@ -35,4 +37,4 @@
"url": "https://github.com/thsmi/sieve/issues"
},
"homepage": "https://github.com/thsmi/sieve#readme"
}
}
Loading

0 comments on commit 6da9997

Please sign in to comment.