audacity-actions/lib/appleCodeSigning.js
2022-01-26 19:42:49 +03:00

164 lines
4.6 KiB
JavaScript

const plist = require('simple-plist');
const fetch = require('node-fetch');
const helpers = require('../lib/helpers.js');
const fileUtils = require('./fileUtils.js');
const entitlements = `${workspaceDir}/mac/Audacity.entitlements`;
async function signFile(file, identity, args) {
args = args || [];
return helpers.execWithLog('xcrun', [
'codesign', '--verbose=3',
'--timestamp',
'--sign', identity,
'--options', 'runtime',
'--entitlements', entitlements,
'--force',
...args,
file
]);
}
async function fixupRPath(file) {
const output = (await helpers.getExecOutput('otool', ['-L', file])).stdout;
const libs = output.split('\n').
filter(line => line.indexOf('@rpath') != -1).
map(line => line.match(/@rpath\/.+\.dylib/)[0]);
if (libs.length == 0) {
return;
}
const loaderCommands =
(await helpers.getExecOutput('otool', ['-l', file])).stdout.
split('\n').
map(line => line.trim()).
filter(line => line.search(/path\s+@(?:executable|loader)_path/) != -1).
map(line => line.match(/path\s+(@(?:executable|loader)_path.*)\s+\(/)[1]).
map(line => ['-delete_rpath', line]).flat(Infinity);
const nameToolArgs = libs.map(lib => {
return ['-change', lib, lib.replace('@rpath', '@executable_path/../Frameworks') ]
}).flat(Infinity);
helpers.log(`Fixing rpath for ${file}`);
return helpers.execWithLog('install_name_tool', [
...nameToolArgs,
...loaderCommands,
file
])
}
async function singApp(appLocation, identity) {
if (!identity) {
helpers.log("Skipping code signings, as there is no codesign identity provided");
return ;
}
const bundleContens = await fileUtils.getAudacityMacOSBundleFiles(appLocation);
exeFiles = bundleContens.MacOS;
modules = bundleContens.modules;
frameworks = bundleContens.Frameworks.dylib;
binaries = [ ...exeFiles, ...modules, ...frameworks ];
for (const file of binaries) {
await fixupRPath(file);
}
for (const file of [/*...exeFiles,*/ ...modules]) {
await signFile(file, identity);
}
await signFile(appLocation, identity, [ '--deep' ]);
// Validate the signautre
await helpers.execWithLog('codesign', [
'--verify',
'--deep',
'--verbose=4',
'--strict',
appLocation
])
}
async function signDMG(dmgPath, appIdentifier, codesignIdentifier) {
if (!codesignIdentifier) {
helpers.log("Skipping code signings, as there is no codesign identity provided");
return ;
}
await helpers.execWithLog('xcrun', [
'codesign', '--verbose',
'--timestamp',
'--identifier', appIdentifier,
'--sign', codesignIdentifier,
dmgPath
]);
}
async function notarizeDMG(dmgPath, appIdentifier, notarizationUser, notarizationPassword) {
if (!notarizationUser || !notarizationUser) {
helpers.log("Skipping notarization, as there are np credentials provided");
return ;
}
helpers.log(`Notarizing DMG: ${dmgPath}`)
const notarizationResult = await helpers.getExecOutput('xcrun', [
'altool', '--notarize-app',
'--primary-bundle-id', appIdentifier,
'--file', dmgPath,
'--username', notarizationUser,
'--password', notarizationPassword,
'--output-format', 'xml'
]);
const requestId = plist.parse(notarizationResult.stdout)['notarization-upload']['RequestUUID'];
for(;;) {
await helpers.sleep(30000);
const notarizationInfo = await helpers.getExecOutput('xcrun', [
'altool',
'--notarization-info', requestId,
'--username', notarizationUser,
'--password', notarizationPassword,
'--output-format', 'xml'
]);
const parsedInfo = plist.parse(notarizationInfo.stdout);
const status = parsedInfo['notarization-info']['Status'];
if(status == 'success') {
return
} else if (status != 'in progress') {
const logUrl = parsedInfo['notarization-info']['LogFileURL'];
if (logUrl) {
const logResponse = await fetch(logUrl);
if (logResponse.ok) {
const log = await logResponse.text();
throw Error(`Notarization failed:\n${log}`);
}
}
throw Error(notarizationInfo.stdout);
}
}
}
module.exports = {
singApp: singApp,
signDMG: signDMG,
notarizeDMG: notarizeDMG,
}