appcenter-cli
Version:
Command line tool for Visual Studio App Center
296 lines (295 loc) • 14.6 kB
JavaScript
;
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;