2022-12-08 12:32:45 +03:00

399 lines
12 KiB
JavaScript

const fs = require('fs');
const path = require('path');
const fetch = require('node-fetch');
const glob = require('@actions/glob');
const toolCache = require('@actions/tool-cache')
const helpers = require("../lib/helpers.js");
const fileUtils = require("../lib/fileUtils.js");
const artifactorySymbolsURL = process.env['ARTIFACTORY_SYMBOLS_URL'];
const artifactorySymbolsKey = process.env['ARTIFACTORY_SYMBOLS_KEY'];
const symstore = 'C:\\Program Files (x86)\\Windows Kits\\10\\Debuggers\\x64\\symstore.exe';
const symstoreFound = process.platform === 'win32' && fs.existsSync(symstore);
const debugDir = path.join(workspaceDir, '.debug')
const symstoreDir = path.join(debugDir, 'SymStore')
async function getSentryCli() {
let sentryCliPath = ''
try {
if (process.platform === 'win32') {
sentryCliPath = await toolCache.downloadTool('https://downloads.sentry-cdn.com/sentry-cli/1.71.0/sentry-cli-Windows-x86_64.exe');
}
else if (process.platform === 'darwin') {
sentryCliPath = await toolCache.downloadTool('https://downloads.sentry-cdn.com/sentry-cli/1.71.0/sentry-cli-Darwin-universal');
}
else {
sentryCliPath = await toolCache.downloadTool('https://downloads.sentry-cdn.com/sentry-cli/1.71.0/sentry-cli-Linux-x86_64');
}
const name = process.platform === 'win32' ? 'sentry-cli.exe' : 'sentry-cli';
const renamedPath = path.join(
path.dirname(sentryCliPath),
name
);
await fs.promises.rename(sentryCliPath, renamedPath)
if (process.platform !== 'win32') {
await fs.promises.chmod(renamedPath, '0766');
}
return renamedPath;
} catch (err) {
helpers.error(err);
return '';
}
}
const sentryConfig = {
authToken: process.env['SENTRY_AUTH_TOKEN'] || '',
url: 'https://' + process.env['SENTRY_HOST'],
org: process.env['SENTRY_ORG_SLUG'] || '',
project: process.env['SENTRY_PROJECT_SLUG'] || '',
cliInitialized: false,
cliFound: false,
}
const sentryConfigMayBeValid =
sentryConfig.authToken.length > 0 &&
sentryConfig.url.length > 0 &&
sentryConfig.org.length > 0 &&
sentryConfig.project.length > 0;
async function download(artifactoryUrl, file, symstoreDir) {
const response = await fetch(artifactoryUrl + file);
if (!response.ok) {
return false;
}
try {
await new Promise((resolve, reject) => {
let targetFilePath = path.join(symstoreDir, file);
let basePath = path.dirname(targetFilePath);
if (!fs.existsSync(basePath)) {
fs.mkdirSync(basePath, { recursive: true })
}
const fileStream = fs.createWriteStream(targetFilePath);
response.body.pipe(fileStream);
response.body.on("error", (err) => {
reject(err);
});
fileStream.on("finish", function() {
resolve();
});
});
return true;
} catch (err) {
return false;
}
}
async function download000Admin(targetPath) {
if(await download(artifactorySymbolsURL, '000Admin/lastid.txt', targetPath)) {
await download(artifactorySymbolsURL, '000Admin/history.txt', targetPath);
await download(artifactorySymbolsURL, '000Admin/server.txt', targetPath);
}
}
async function uploadSymStore() {
await download000Admin();
const files = await fileUtils.listDirectory(symstoreDir);
for(const file of files) {
let url = artifactorySymbolsURL + path.relative(symstoreDir, file).replaceAll(path.sep, '/');
helpers.log(`Uploading ${url}`);
let fileData = fs.createReadStream(file);
try {
let response = await fetch(url, {
method: 'PUT',
headers: {
'Authorization': 'Bearer ' + artifactorySymbolsKey,
},
body: fileData
})
if (!response.ok) {
helpers.error("Failed to upload " + response.status);
}
} catch (err) {
helpers.error(err);
}
}
}
async function uploadAllToSentry(files) {
if (!sentryConfig.cliInitialized) {
sentryConfig.cliInitialized = true;
sentryConfig.cli = await getSentryCli();
sentryConfig.cliFound = sentryConfig.cli.length > 0;
if (!sentryConfig.cliFound) {
helpers.error("sentry-cli is not available");
return;
}
}
if (sentryConfig.cliFound && sentryConfigMayBeValid) {
for (const filePath of files) {
await helpers.execWithLog(sentryConfig.cli, [
'--auth-token', sentryConfig.authToken,
'--url', sentryConfig.url,
'upload-dif', '--include-sources',
'--org', sentryConfig.org,
'--project', sentryConfig.project,
filePath
]);
}
}
}
async function addToSymStore(fileName) {
if (symstoreFound) {
await helpers.execWithLog('"' + symstore + '"', [
'add',
'/s', symstoreDir,
'/compress', '/r', '/f',
fileName,
'/t', path.basename(fileName, '.pdb')
])
}
}
async function splitDsymFile(file) {
if (!fs.existsSync(debugDir)) {
fs.mkdirSync(debugDir, { recursive: true });
}
helpers.log(`Calling dsymutil on ${file}`)
let dsymPath = path.join(debugDir, path.basename(file) + '.dSYM')
await helpers.execWithLog('dsymutil', [file, '-o', dsymPath]);
return dsymPath;
}
const doNotStrip = [
/libicu.+/
]
function skipSplit(file) {
for (const pattern of doNotStrip) {
if (file.match(pattern)) {
return true;
}
}
return false;
}
async function splitDebugFile(file) {
if (skipSplit(file)) {
return '';
}
if (!fs.existsSync(debugDir)) {
fs.mkdirSync(debugDir, { recursive: true });
}
let debugPath = path.join(debugDir, path.basename(file) + '.debug')
try {
await helpers.execWithLog('objcopy', ['--only-keep-debug', '--compress-debug-section=zlib', file, debugPath]);
if (!fs.existsSync(debugPath)) {
return '';
}
await helpers.execWithLog('objcopy', ['--strip-debug', '--strip-unneeded', file]);
await helpers.execWithLog('objcopy', ['--add-gnu-debuglink=' + debugPath, file]);
return debugPath;
} catch (err) {
helpers.error(err);
return '';
}
}
async function getMatchingFiles(mainPatterns, controlPatterns, skipExt) {
const mainFiles = await fileUtils.globFiles(mainPatterns);
const controlFiles = await fileUtils.globFiles(controlPatterns);
return mainFiles.filter(file => {
const buildFile = !skipExt ?
path.basename(file) :
path.basename(file, path.extname(file));
return controlFiles.findIndex(value => value.indexOf(buildFile) != -1) != -1;
});
}
async function splitDepsDebugSymbols(extension, splitter, performUpload) {
const files = await getMatchingFiles(
path.join(conanCachePath, 'data/**/package/**/*' + extension),
path.join(conanCachePath, 'data/**/build/**/*' + extension)
);
let debugFiles = []
for(const file of files) {
const debugPath = await splitter(file);
if (debugPath.length > 0) {
debugFiles.push(debugPath);
}
}
if (performUpload && debugFiles.length > 0) {
await uploadAllToSentry([...debugFiles, ...files])
}
}
async function splitAudacityDebugSymbols(buildDir, buildType, extension, splitter, performUpload) {
const binDir = path.join(buildDir, 'bin', buildType);
// Find all executable files, that
// do not have an extesion
const executableFiles = (await fileUtils.globFiles(`${binDir}/**/*`)).filter(file => {
const stat = fs.lstatSync(file);
return stat.isFile() && (stat.mode & fs.constants.S_IXUSR) && path.extname(file).length == 0;
})
const systemLibraries = await getMatchingFiles(
`${binDir}/**/*${extension}`,
`${conanCachePath}/**/package/**/*${extension}`);
const libraries = (await fileUtils.globFiles(`${binDir}/**/*${extension}`)).filter(file => {
const stat = fs.lstatSync(file);
if (!stat.isFile())
return false;
return systemLibraries.indexOf(file) == -1;
});
const binaries = [...executableFiles, ...libraries]
let debugFiles = []
for(const file of binaries) {
const debugPath = await splitter(file);
if (debugPath.length > 0) {
debugFiles.push(debugPath);
}
}
if (performUpload && debugFiles.length > 0) {
await uploadAllToSentry([...debugFiles, ...binaries])
}
}
async function processDependenciesDebugInformation(buildDir, buildType, performUpload) {
if (process.platform == 'win32') {
// On Windows, nothing needs to be done
// if we are processeing dependencies and
// no upload is needed
if (!performUpload || !artifactorySymbolsURL) {
return;
}
if(!symstoreFound) {
helpers.error("symstore.exe is not available");
return;
}
// Collect PDBs
const pdbs = await fileUtils.globFiles([
path.join(conanCachePath, 'data/**/build/**/*.pdb'),
'C:/.conan/**/*.pdb'
]);
for(const pdb of pdbs) {
await addToSymStore(pdb);
}
if(pdbs.length > 0) {
await uploadSymStore();
const dlls = await fileUtils.globFiles([
path.join(conanCachePath, 'data/**/build/**/*.dll'),
'C:/.conan/**/*.dll'
]);
await uploadAllToSentry([...pdbs, ...dlls])
}
} else if (process.platform == 'darwin') {
await splitDepsDebugSymbols('.dylib', splitDsymFile, performUpload);
} else {
await splitDepsDebugSymbols('.so*', splitDebugFile, performUpload);
}
}
async function processDebugInformation(buildDir, buildType, performUpload) {
if (process.platform == 'win32') {
const exePdbs = await fileUtils.globFiles(path.join(buildDir, `bin/${buildType}/**/*.pdb`));
if (performUpload) {
const sharedPdbs = await fileUtils.globFiles(path.join(buildDir, `shared/${buildType}/**/*.pdb`));
const dlls = await getMatchingFiles(
path.join(buildDir, `shared/${buildType}/**/*.dll`),
path.join(buildDir, `shared/${buildType}/**/*.pdb`),
true
)
const exes = await getMatchingFiles(
[ path.join(buildDir, `bin/${buildType}/**/*.exe`), path.join(buildDir, `bin/${buildType}/**/*.dll`) ],
path.join(buildDir, `bin/${buildType}/**/*.pdb`),
true
)
const pdbs = [...exePdbs, ...sharedPdbs];
for(const pdb of pdbs) {
await addToSymStore(pdb);
}
await uploadAllToSentry(pdbs);
await uploadAllToSentry([...dlls, ...pdbs, ...exes]);
}
// Remove PDBs from the distribution directory.
const miscFiles = await fileUtils.globFiles([
path.join(buildDir, `bin/${buildType}/**/*.ipdb`),
path.join(buildDir, `bin/${buildType}/**/*.iobj`),
path.join(buildDir, `bin/${buildType}/**/*.ilk`),
]);
for (const pdb of [ ...exePdbs, ...miscFiles ]) {
await fs.promises.rm(pdb);
}
} else if (process.platform == 'darwin') {
await splitAudacityDebugSymbols(buildDir, buildType, '.dylib', splitDsymFile, performUpload);
} else {
await splitAudacityDebugSymbols(buildDir, buildType, '.so*', splitDebugFile, performUpload);
}
}
module.exports = {
processDependenciesDebugInformation: processDependenciesDebugInformation,
processDebugInformation: processDebugInformation,
}