UNPKG

appcenter-cli

Version:

Command line tool for Visual Studio App Center

296 lines (295 loc) 14.6 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const commandline_1 = require("../../util/commandline"); const commandline_2 = require("../../util/commandline"); const commandline_3 = require("../../util/commandline"); const util_1 = require("util"); const interaction_1 = require("../../util/interaction"); const symbols_uploading_helper_1 = require("./lib/symbols-uploading-helper"); const subfolder_symbols_helper_1 = require("./lib/subfolder-symbols-helper"); const temp_zip_file_helper_1 = require("./lib/temp-zip-file-helper"); const mdfind_1 = require("./lib/mdfind"); const Pfs = require("../../util/misc/promisfied-fs"); const Path = require("path"); const JsZip = require("jszip"); const JsZipHelper = require("../../util/misc/jszip-helper"); const _ = require("lodash"); const Os = require("os"); const ChildProcess = require("child_process"); const debug = require("debug")("appcenter-cli:commands:apps:crashes:upload-missing-symbols"); const bplist = require("bplist"); const MAX_SQL_INTEGER = 2147483647; let UploadMissingSymbols = class UploadMissingSymbols extends commandline_1.AppCommand { run(client) { return __awaiter(this, void 0, void 0, function* () { if (Os.platform() !== "darwin") { return commandline_2.failure(commandline_2.ErrorCodes.IllegalCommand, "This command must be run under macOS"); } const app = this.app; yield this.validateParameters(); const missingSymbolsIds = yield interaction_1.out.progress("Getting list of missing symbols...", this.getMissingSymbolsIds(client, app)); let output; if (missingSymbolsIds.length) { // there are missing symbols - find and upload them const uuidToPath = yield interaction_1.out.progress("Searching for missing symbols...", this.searchForMissingSymbols(missingSymbolsIds, client, app)); const found = yield interaction_1.out.progress("Uploading found symbols...", this.uploadFoundSymbols(uuidToPath, client, app)); output = { missingSymbols: missingSymbolsIds.length, found }; } else { output = { missingSymbols: 0, found: 0 }; } interaction_1.out.text((result) => { return (`${result.missingSymbols} symbols are needed to symbolicate all crashes` + Os.EOL + `${result.found} of these symbols were found and uploaded`); }, output); return commandline_2.success(); }); } validateParameters() { return __awaiter(this, void 0, void 0, function* () { if (!_.isNil(this.symbolsPath)) { if (!(yield Pfs.exists(this.symbolsPath))) { throw commandline_2.failure(commandline_2.ErrorCodes.InvalidParameter, `path ${this.symbolsPath} doesn't exist`); } } }); } getMissingSymbolsIds(client, app) { return __awaiter(this, void 0, void 0, function* () { try { const result = yield client.missingSymbolGroups.list(MAX_SQL_INTEGER, app.ownerName, app.appName); return _.flatten(result.groups.map((crashGroup) => crashGroup.missingSymbols.filter((s) => s.status === "missing").map((s) => s.symbolId))); } catch (error) { debug(`Failed to get list of missing symbols - ${util_1.inspect(error)}`); throw commandline_2.failure(commandline_2.ErrorCodes.Exception, "failed to get list of missing symbols"); } }); } searchForMissingSymbols(missingSymbolsIds, client, app) { return __awaiter(this, void 0, void 0, function* () { console.assert(missingSymbolsIds.every((id) => /^[0-9a-f]{32}$/g.test(id)), "the API has returned abnormal missing symbols IDs"); const missingSymbolsUuids = missingSymbolsIds.map((id) => id .toUpperCase() .match(/(.{8})(.{4})(.{4})(.{4})(.{12})/) .slice(1) .join("-")); let uuidToPath; if (_.isNil(this.symbolsPath)) { // symbols path is not specified, looking in default locations // searching with mdfind uuidToPath = yield this.getMdfindResultsForUuids(missingSymbolsUuids); // check if all of the missing symbols were found const notYetFoundUuids = Array.from(uuidToPath.keys()).filter((key) => _.isNull(uuidToPath.get(key))); if (notYetFoundUuids.length) { // looking for the rest of missing symbols in Xcode Archive folder const xcodeArchivesPath = yield this.getXcodeArchiveFolderLocation(); if (xcodeArchivesPath) { // xcode is installed, searching for dSYMs in Archives folder uuidToPath = new Map(Array.from(uuidToPath).concat(Array.from(yield this.searchDsyms(xcodeArchivesPath, notYetFoundUuids)))); } } } else { uuidToPath = yield this.searchDsyms(this.symbolsPath, _.clone(missingSymbolsUuids)); } return uuidToPath; }); } uploadFoundSymbols(uuidToPath, client, app) { return __awaiter(this, void 0, void 0, function* () { // packing and uploading each found dSYM package const helper = new symbols_uploading_helper_1.default(client, app, debug); const paths = Array.from(uuidToPath.values()) .filter((path) => !_.isNull(path)) .map((path) => Path.resolve(path)); const uniquePaths = _.uniq(paths); for (const path of uniquePaths) { yield this.uploadSymbolsZip(path, helper); } return paths.length; }); } getMdfindResultsForUuids(uuids) { return __awaiter(this, void 0, void 0, function* () { const uuidToPath = new Map(); for (const uuid of uuids) { uuidToPath.set(uuid, yield this.executeMdfindSearch(uuid)); } return uuidToPath; }); } executeMdfindSearch(uuid) { return new Promise((resolve, reject) => { const context = mdfind_1.mdfind({ query: `com_apple_xcode_dsym_uuids == ${uuid}` }); let result = null; context.output .on("data", (data) => { // *.xcarchive symbols have higher priority over non-archive symbols result = data.kMDItemPath; if (Path.extname(result) === ".xcarchive") { // stop search and return xcarchive context.terminate(); resolve(result); } }) .on("error", (err) => reject(err)) .on("end", () => resolve(result)); // return what was found (or null if nothing was found) }).catch((error) => { debug(`Failed to find symbols for ${uuid} using mdfind - ${util_1.inspect(error)}`); throw commandline_2.failure(commandline_2.ErrorCodes.Exception, `failed to find symbols for ${uuid} using mdfind`); }); } getXcodeArchiveFolderLocation() { return __awaiter(this, void 0, void 0, function* () { let xcodeSettingsBuffer; try { xcodeSettingsBuffer = yield Pfs.readFile(Path.join(Os.homedir(), "Library/Preferences/com.apple.dt.Xcode.plist")); } catch (error) { if (error.code === "ENOENT") { // Xcode settings file not found, most likely xcode is not installed return null; } else { debug(`Failed to read Xcode settings file - ${util_1.inspect(error)}`); throw commandline_2.failure(commandline_2.ErrorCodes.Exception, "failed to read Xcode settings file"); } } try { const xcodeSettings = yield this.parseBinaryPlist(xcodeSettingsBuffer); // return default value if custom is not specified return xcodeSettings[0].IDECustomDistributionArchivesLocation || Path.join(Os.homedir(), "Library/Developer/Xcode/Archives"); } catch (error) { debug(`Failed to process Xcode settings - ${util_1.inspect(error)}`); throw commandline_2.failure(commandline_2.ErrorCodes.Exception, "failed to process Xcode settings"); } }); } parseBinaryPlist(buffer) { return new Promise((resolve, reject) => { bplist.parseBuffer(buffer, (error, result) => { if (error) { reject(error); } else { resolve(result); } }); }); } searchDsyms(path, uuids) { return __awaiter(this, void 0, void 0, function* () { if (uuids.length) { // get list of children entities (and check the existence of path) let childrenEntities; try { childrenEntities = yield Pfs.readdir(path); } catch (error) { if (error.code === "ENOENT" || error.code === "ENOTDIR") { return new Map(); } else { throw error; } } let uuidToDsym; if (Path.extname(path) === ".dSYM") { const dSymUuids = yield this.extractUuidsFromDsym(path); uuidToDsym = new Map(); for (const dsymUuid of dSymUuids) { if (uuids.indexOf(dsymUuid) > -1) { // removing found uuid from uuids to quickly stop execution when all of the uuids are found _.pull(uuids, dsymUuid); uuidToDsym.set(dsymUuid, path); } } } else { let childrenEntitiesMaps = []; for (const childrenEntity of childrenEntities) { const pathToChildrenEntity = Path.join(path, childrenEntity); childrenEntitiesMaps = childrenEntitiesMaps.concat(Array.from(yield this.searchDsyms(pathToChildrenEntity, uuids))); } uuidToDsym = new Map(childrenEntitiesMaps); } return uuidToDsym; } else { return new Map(); } }); } extractUuidsFromDsym(path) { return __awaiter(this, void 0, void 0, function* () { try { const dwarfDumpOutput = yield this.runExternalApp(`dwarfdump --uuid "${path}"`); return dwarfDumpOutput.match(/[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}/g) || []; } catch (error) { debug(`Failed to get UUID from dSym ${path} - ${util_1.inspect(error)}`); throw commandline_2.failure(commandline_2.ErrorCodes.Exception, `failed to get UUID from dSym ${path}`); } }); } runExternalApp(command) { return new Promise((resolve, reject) => { ChildProcess.exec(command, (error, stdout) => { if (error) { reject(error); } else { resolve(stdout); } }); }); } uploadSymbolsZip(path, helper) { return __awaiter(this, void 0, void 0, function* () { let zip; if (Path.extname(path) === ".xcarchive") { // *.xcarchive has symbols inside zip = yield subfolder_symbols_helper_1.getSymbolsZipFromXcarchive(path, debug); } else { try { zip = new JsZip(); yield JsZipHelper.addFolderToZipRecursively(path, zip); } catch (error) { debug(`Unable to add ${path} to the ZIP archive - ${util_1.inspect(error)}`); throw commandline_2.failure(commandline_2.ErrorCodes.Exception, `unable to add ${path} to the ZIP archive`); } } const tempFilePath = yield temp_zip_file_helper_1.createTempFileFromZip(zip); yield helper.uploadSymbolsArtifact(tempFilePath, { symbolType: symbols_uploading_helper_1.SymbolType.Apple }); }); } }; __decorate([ commandline_3.help("Path to a dSYM package or a directory containing dSYM packages"), commandline_3.position(0), commandline_3.name("search-path") ], UploadMissingSymbols.prototype, "symbolsPath", void 0); UploadMissingSymbols = __decorate([ commandline_3.help("Upload missing crash symbols for the application (only from macOS)") ], UploadMissingSymbols); exports.default = UploadMissingSymbols;