@datadog/datadog-ci 1.1.2 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE-3rdparty.csv +2 -0
- package/dist/commands/dsyms/__tests__/upload.test.js +219 -16
- package/dist/commands/dsyms/__tests__/utils.test.js +86 -46
- package/dist/commands/dsyms/interfaces.d.ts +14 -5
- package/dist/commands/dsyms/interfaces.js +15 -28
- package/dist/commands/dsyms/renderer.d.ts +7 -5
- package/dist/commands/dsyms/renderer.js +18 -6
- package/dist/commands/dsyms/upload.d.ts +31 -0
- package/dist/commands/dsyms/upload.js +127 -13
- package/dist/commands/dsyms/utils.d.ts +12 -6
- package/dist/commands/dsyms/utils.js +23 -51
- package/package.json +4 -2
package/LICENSE-3rdparty.csv
CHANGED
|
@@ -10,6 +10,7 @@ Component,Origin,Licence,Copyright
|
|
|
10
10
|
@types/inquirer,dev,MIT,Copyright Microsoft Corporation
|
|
11
11
|
@types/jest,dev,MIT,Copyright Microsoft Corporation
|
|
12
12
|
@types/node,dev,MIT,Copyright Microsoft Corporation
|
|
13
|
+
@types/rimraf,dev,MIT,Copyright Carlos Ballesteros Velasco <https://github.com/soywiz> and contributors
|
|
13
14
|
@types/ssh2,dev,MIT,Copyright Microsoft Corporation
|
|
14
15
|
@types/ssh2-streams,dev,MIT,Copyright Microsoft Corporation
|
|
15
16
|
@types/sshpk,dev,MIT,Copyright Microsoft Corporation
|
|
@@ -34,6 +35,7 @@ pkg,dev,MIT,Copyright (c) 2021 Vercel, Inc.
|
|
|
34
35
|
prettier,dev,MIT,Copyright © James Long and contributors
|
|
35
36
|
proxy,dev,MIT,Copyright (c) 2013 Nathan Rajlich <nathan@tootallnate.net>
|
|
36
37
|
proxy-agent,import,MIT,Copyright (c) 2013 Nathan Rajlich <nathan@tootallnate.net>
|
|
38
|
+
rimraf,import,ISC,Copyright (c) 2011-2022 Isaac Z. Schlueter and Contributors
|
|
37
39
|
simple-git,import,MIT,Copyright (c) 2015 Steve King
|
|
38
40
|
ssh2,import,MIT,Copyright (c) Brian White
|
|
39
41
|
ssh2-streams,import,MIT,Copyright (c) 2014 Brian White
|
|
@@ -8,15 +8,207 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
11
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
15
|
// tslint:disable: no-string-literal
|
|
13
|
-
// tslint:disable: no-var-requires
|
|
14
16
|
const advanced_1 = require("clipanion/lib/advanced");
|
|
17
|
+
const fs_1 = require("fs");
|
|
18
|
+
const glob_1 = __importDefault(require("glob"));
|
|
15
19
|
const os_1 = require("os");
|
|
20
|
+
const path_1 = __importDefault(require("path"));
|
|
21
|
+
const utils_1 = require("../../../helpers/utils");
|
|
16
22
|
const upload_1 = require("../upload");
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
23
|
+
const utils_2 = require("../utils");
|
|
24
|
+
/**
|
|
25
|
+
* `dwarfdump` and `lipo` are only available in macOS, so we mock their behaviour when running tests on other platforms.
|
|
26
|
+
*/
|
|
27
|
+
const mockDwarfdumpAndLipoIfNotMacOS = () => {
|
|
28
|
+
if (os_1.platform() !== 'darwin') {
|
|
29
|
+
// For `dwarfdump --uuid` mock, return the same output as the command would give on macOS:
|
|
30
|
+
require('../utils').executeDwarfdump = jest.fn().mockImplementation((dsymPath) => {
|
|
31
|
+
let fixture = dsymPath.includes('multiple-archs') ? fatDSYMFixture : undefined;
|
|
32
|
+
fixture = fixture !== null && fixture !== void 0 ? fixture : (dsymPath.includes('single-arch') ? slimDSYMFixture : undefined);
|
|
33
|
+
if (fixture !== undefined) {
|
|
34
|
+
const outputLines = fixture.slices.map((slice) => {
|
|
35
|
+
const objectPathInDsym = path_1.default.relative(fixture.bundlePath, slice.objectPath);
|
|
36
|
+
const objectPathInMockedDSYM = utils_1.buildPath(dsymPath, objectPathInDsym);
|
|
37
|
+
return `UUID: ${slice.uuid} (${slice.arch}) ${objectPathInMockedDSYM}`;
|
|
38
|
+
});
|
|
39
|
+
return { stderr: '', stdout: outputLines.join('\n') };
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
throw new Error(`Cannot find mock dSYM fixture for dsymPath: ${dsymPath}`);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
// For `lipo -thin` mock, just copy the object to new location (without extracting the slice as macOS would do):
|
|
46
|
+
require('../utils').executeLipo = jest
|
|
47
|
+
.fn()
|
|
48
|
+
.mockImplementation((objectPath, arch, newObjectPath) => __awaiter(void 0, void 0, void 0, function* () {
|
|
49
|
+
yield fs_1.promises.copyFile(objectPath, newObjectPath);
|
|
50
|
+
return { stderr: '', stdout: '' };
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Fixture for dSYM containing two arch slices. This is the same dSYM information as can be
|
|
56
|
+
* read with `dwarfdump --uuid ./src/commands/dsyms/__tests__/fixtures/multiple-archs/DDTest.framework.dSYM` on macOS.
|
|
57
|
+
*/
|
|
58
|
+
const fatDSYMFixture = {
|
|
59
|
+
bundlePath: 'src/commands/dsyms/__tests__/fixtures/multiple-archs/DDTest.framework.dSYM',
|
|
60
|
+
slices: [
|
|
61
|
+
{
|
|
62
|
+
arch: 'armv7',
|
|
63
|
+
objectPath: 'src/commands/dsyms/__tests__/fixtures/multiple-archs/DDTest.framework.dSYM/Contents/Resources/DWARF/DDTest',
|
|
64
|
+
uuid: 'C8469F85-B060-3085-B69D-E46C645560EA',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
arch: 'arm64',
|
|
68
|
+
objectPath: 'src/commands/dsyms/__tests__/fixtures/multiple-archs/DDTest.framework.dSYM/Contents/Resources/DWARF/DDTest',
|
|
69
|
+
uuid: '06EE3D68-D605-3E92-B92D-2F48C02A505E',
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Fixture for dSYM containing single arch slice. This is the same dSYM information as can be
|
|
75
|
+
* read with `dwarfdump --uuid ./src/commands/dsyms/__tests__/fixtures/single-archs/DDTest.framework.dSYM` on macOS.
|
|
76
|
+
*/
|
|
77
|
+
const slimDSYMFixture = {
|
|
78
|
+
bundlePath: 'src/commands/dsyms/__tests__/fixtures/single-arch/DDTest.framework.dSYM',
|
|
79
|
+
slices: [
|
|
80
|
+
{
|
|
81
|
+
arch: 'arm64',
|
|
82
|
+
objectPath: 'src/commands/dsyms/__tests__/fixtures/single-arch/DDTest.framework.dSYM/Contents/Resources/DWARF/DDTest',
|
|
83
|
+
uuid: '3BC12422-63CC-30E8-B916-E5006CE3286C',
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
};
|
|
87
|
+
describe('upload', () => {
|
|
88
|
+
beforeAll(() => {
|
|
89
|
+
mockDwarfdumpAndLipoIfNotMacOS();
|
|
90
|
+
});
|
|
91
|
+
describe('findDSYMsInDirectory', () => {
|
|
92
|
+
const command = new upload_1.UploadCommand();
|
|
93
|
+
test('Should find dSYMs recursively', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
94
|
+
const expectedDSYMs = [fatDSYMFixture, slimDSYMFixture];
|
|
95
|
+
const actualDSYMs = yield command['findDSYMsInDirectory']('src/commands/dsyms/__tests__/fixtures');
|
|
96
|
+
expect(actualDSYMs.length).toEqual(2);
|
|
97
|
+
expect(actualDSYMs).toContainEqual(expectedDSYMs[0]);
|
|
98
|
+
expect(actualDSYMs).toContainEqual(expectedDSYMs[1]);
|
|
99
|
+
}));
|
|
100
|
+
});
|
|
101
|
+
describe('parseDwarfdumpOutput', () => {
|
|
102
|
+
const command = new upload_1.UploadCommand();
|
|
103
|
+
test('Should read arch slice from single-line output', () => {
|
|
104
|
+
const output = 'UUID: 00000000-1111-2222-3333-444444444444 (arm64) /folder/Foo.dSYM/Contents/Resources/DWARF/Foo';
|
|
105
|
+
const slices = command['parseDwarfdumpOutput'](output);
|
|
106
|
+
expect(slices).toEqual([
|
|
107
|
+
{
|
|
108
|
+
arch: 'arm64',
|
|
109
|
+
objectPath: '/folder/Foo.dSYM/Contents/Resources/DWARF/Foo',
|
|
110
|
+
uuid: '00000000-1111-2222-3333-444444444444',
|
|
111
|
+
},
|
|
112
|
+
]);
|
|
113
|
+
});
|
|
114
|
+
test('Should read arch slices from multi-line output', () => {
|
|
115
|
+
const output = 'UUID: 00000000-1111-2222-3333-444444444444 (arm64) /folder/Foo.dSYM/Contents/Resources/DWARF/Foo\n' +
|
|
116
|
+
'UUID: AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE (x86_64) /folder/Foo.dSYM/Contents/Resources/DWARF/Foo\n' +
|
|
117
|
+
'UUID: FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF (armv7) /folder/Foo.dSYM/Contents/Resources/DWARF/Foo\n';
|
|
118
|
+
const slices = command['parseDwarfdumpOutput'](output);
|
|
119
|
+
expect(slices).toEqual([
|
|
120
|
+
{
|
|
121
|
+
arch: 'arm64',
|
|
122
|
+
objectPath: '/folder/Foo.dSYM/Contents/Resources/DWARF/Foo',
|
|
123
|
+
uuid: '00000000-1111-2222-3333-444444444444',
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
arch: 'x86_64',
|
|
127
|
+
objectPath: '/folder/Foo.dSYM/Contents/Resources/DWARF/Foo',
|
|
128
|
+
uuid: 'AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE',
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
arch: 'armv7',
|
|
132
|
+
objectPath: '/folder/Foo.dSYM/Contents/Resources/DWARF/Foo',
|
|
133
|
+
uuid: 'FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF',
|
|
134
|
+
},
|
|
135
|
+
]);
|
|
136
|
+
});
|
|
137
|
+
test('Should read arch slice if object path contains whitespaces', () => {
|
|
138
|
+
const output = 'UUID: 00000000-1111-2222-3333-444444444444 (arm64) /folder with whitespaces/Foo Bar.dSYM/Contents/Resources/DWARF/Foo Bar';
|
|
139
|
+
const slices = command['parseDwarfdumpOutput'](output);
|
|
140
|
+
expect(slices).toEqual([
|
|
141
|
+
{
|
|
142
|
+
arch: 'arm64',
|
|
143
|
+
objectPath: '/folder with whitespaces/Foo Bar.dSYM/Contents/Resources/DWARF/Foo Bar',
|
|
144
|
+
uuid: '00000000-1111-2222-3333-444444444444',
|
|
145
|
+
},
|
|
146
|
+
]);
|
|
147
|
+
});
|
|
148
|
+
test('Should read no arch slices from invalid output', () => {
|
|
149
|
+
const slices = command['parseDwarfdumpOutput']('');
|
|
150
|
+
expect(slices).toEqual([]);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
describe('thinDSYMs', () => {
|
|
154
|
+
const command = new upload_1.UploadCommand();
|
|
155
|
+
test('Given fat dSYM, it should extract each arch slice to separate dSYM in target folder', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
156
|
+
const tmpDirectory = yield utils_2.createUniqueTmpDirectory();
|
|
157
|
+
// Given
|
|
158
|
+
const inputDSYM = fatDSYMFixture;
|
|
159
|
+
expect(inputDSYM.slices.length).toBeGreaterThan(1);
|
|
160
|
+
// When
|
|
161
|
+
const extractedDSYMs = yield command['thinDSYMs']([inputDSYM], tmpDirectory);
|
|
162
|
+
// Then
|
|
163
|
+
expect(extractedDSYMs.length).toEqual(inputDSYM.slices.length);
|
|
164
|
+
inputDSYM.slices.forEach((slice) => {
|
|
165
|
+
expect(extractedDSYMs).toContainEqual({
|
|
166
|
+
bundlePath: `${utils_1.buildPath(tmpDirectory, slice.uuid)}.dSYM`,
|
|
167
|
+
slices: [
|
|
168
|
+
{
|
|
169
|
+
arch: slice.arch,
|
|
170
|
+
objectPath: `${utils_1.buildPath(tmpDirectory, slice.uuid)}.dSYM/Contents/Resources/DWARF/DDTest`,
|
|
171
|
+
uuid: slice.uuid,
|
|
172
|
+
},
|
|
173
|
+
],
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
const objectFilesInTargetFolder = glob_1.default.sync(utils_1.buildPath(tmpDirectory, '**/Contents/Resources/DWARF/DDTest'));
|
|
177
|
+
expect(objectFilesInTargetFolder.length).toEqual(inputDSYM.slices.length);
|
|
178
|
+
yield utils_2.deleteDirectory(tmpDirectory);
|
|
179
|
+
}));
|
|
180
|
+
test('Given slim dSYM, it should leave it untouched and not extract anything into target folder', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
181
|
+
const tmpDirectory = yield utils_2.createUniqueTmpDirectory();
|
|
182
|
+
// Given
|
|
183
|
+
const inputDSYM = slimDSYMFixture;
|
|
184
|
+
expect(inputDSYM.slices.length).toEqual(1);
|
|
185
|
+
// When
|
|
186
|
+
const extractedDSYMs = yield command['thinDSYMs']([inputDSYM], tmpDirectory);
|
|
187
|
+
// Then
|
|
188
|
+
expect(extractedDSYMs).toEqual([inputDSYM]);
|
|
189
|
+
const filesInTargetFolder = glob_1.default.sync(utils_1.buildPath(tmpDirectory, '*'));
|
|
190
|
+
expect(filesInTargetFolder.length).toEqual(0);
|
|
191
|
+
yield utils_2.deleteDirectory(tmpDirectory);
|
|
192
|
+
}));
|
|
193
|
+
});
|
|
194
|
+
describe('compressDSYMsToDirectory', () => {
|
|
195
|
+
const command = new upload_1.UploadCommand();
|
|
196
|
+
test('Should archive dSYMs to target directory and name archives by their UUIDs', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
197
|
+
const tmpDirectory = yield utils_2.createUniqueTmpDirectory();
|
|
198
|
+
const dsymFixtures = [fatDSYMFixture, slimDSYMFixture];
|
|
199
|
+
// When
|
|
200
|
+
const compressedDSYMs = yield command['compressDSYMsToDirectory'](dsymFixtures, tmpDirectory);
|
|
201
|
+
// Then
|
|
202
|
+
expect(compressedDSYMs[0].dsym).toEqual(dsymFixtures[0]);
|
|
203
|
+
expect(compressedDSYMs[0].archivePath).toEqual(utils_1.buildPath(tmpDirectory, `${dsymFixtures[0].slices[0].uuid}.zip`));
|
|
204
|
+
expect(fs_1.existsSync(compressedDSYMs[0].archivePath)).toBeTruthy();
|
|
205
|
+
expect(compressedDSYMs[1].dsym).toEqual(dsymFixtures[1]);
|
|
206
|
+
expect(compressedDSYMs[1].archivePath).toEqual(utils_1.buildPath(tmpDirectory, `${dsymFixtures[1].slices[0].uuid}.zip`));
|
|
207
|
+
expect(fs_1.existsSync(compressedDSYMs[1].archivePath)).toBeTruthy();
|
|
208
|
+
yield utils_2.deleteDirectory(tmpDirectory);
|
|
209
|
+
}));
|
|
210
|
+
});
|
|
211
|
+
});
|
|
20
212
|
describe('execute', () => {
|
|
21
213
|
const makeCli = () => {
|
|
22
214
|
const cli = new advanced_1.Cli();
|
|
@@ -34,33 +226,44 @@ describe('execute', () => {
|
|
|
34
226
|
},
|
|
35
227
|
};
|
|
36
228
|
};
|
|
37
|
-
const runCLI = (
|
|
229
|
+
const runCLI = (dsymPath) => __awaiter(void 0, void 0, void 0, function* () {
|
|
38
230
|
const cli = makeCli();
|
|
39
231
|
const context = createMockContext();
|
|
40
232
|
process.env = { DATADOG_API_KEY: 'PLACEHOLDER' };
|
|
41
|
-
const code = yield cli.run(['dsyms', 'upload',
|
|
233
|
+
const code = yield cli.run(['dsyms', 'upload', dsymPath, '--dry-run'], context);
|
|
42
234
|
return { context, code };
|
|
43
235
|
});
|
|
44
|
-
|
|
45
|
-
|
|
236
|
+
beforeAll(() => {
|
|
237
|
+
mockDwarfdumpAndLipoIfNotMacOS();
|
|
238
|
+
});
|
|
239
|
+
test('Should succeed with folder input', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
240
|
+
const { context, code } = yield runCLI('src/commands/dsyms/__tests__/fixtures/');
|
|
46
241
|
const outputString = context.stdout.toString();
|
|
47
242
|
const output = outputString.split(os_1.EOL);
|
|
48
243
|
expect(outputString).not.toContain('Error');
|
|
49
244
|
expect(code).toBe(0);
|
|
50
245
|
expect(output[1]).toContain('Starting upload with concurrency 20. ');
|
|
51
|
-
expect(output[2]).toContain('Will look for dSYMs in src/commands/dsyms/__tests__/
|
|
52
|
-
expect(output[3]).toContain('
|
|
53
|
-
expect(output[
|
|
246
|
+
expect(output[2]).toContain('Will look for dSYMs in src/commands/dsyms/__tests__/fixtures/');
|
|
247
|
+
expect(output[3]).toContain('Will use temporary intermediate directory: ');
|
|
248
|
+
expect(output[4]).toContain('Will use temporary upload directory: ');
|
|
249
|
+
expect(output[5]).toContain('Uploading C8469F85-B060-3085-B69D-E46C645560EA.zip (DDTest, arch: armv7, UUID: C8469F85-B060-3085-B69D-E46C645560EA)');
|
|
250
|
+
expect(output[6]).toContain('Uploading 06EE3D68-D605-3E92-B92D-2F48C02A505E.zip (DDTest, arch: arm64, UUID: 06EE3D68-D605-3E92-B92D-2F48C02A505E)');
|
|
251
|
+
expect(output[7]).toContain('Uploading 3BC12422-63CC-30E8-B916-E5006CE3286C.zip (DDTest, arch: arm64, UUID: 3BC12422-63CC-30E8-B916-E5006CE3286C)');
|
|
252
|
+
expect(output[10]).toContain('Handled 3 dSYMs with success');
|
|
54
253
|
}));
|
|
55
|
-
test('
|
|
56
|
-
const { context, code } = yield runCLI('
|
|
254
|
+
test('Should succeed with zip file input', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
255
|
+
const { context, code } = yield runCLI('src/commands/dsyms/__tests__/fixtures/all.zip');
|
|
57
256
|
const outputString = context.stdout.toString();
|
|
58
257
|
const output = outputString.split(os_1.EOL);
|
|
59
258
|
expect(outputString).not.toContain('Error');
|
|
60
259
|
expect(code).toBe(0);
|
|
61
260
|
expect(output[1]).toContain('Starting upload with concurrency 20. ');
|
|
62
|
-
expect(output[2]).toContain('Will look for dSYMs in src/commands/dsyms/__tests__/
|
|
63
|
-
expect(output[3]).toContain('
|
|
64
|
-
expect(output[
|
|
261
|
+
expect(output[2]).toContain('Will look for dSYMs in src/commands/dsyms/__tests__/fixtures/all.zip');
|
|
262
|
+
expect(output[3]).toContain('Will use temporary intermediate directory: ');
|
|
263
|
+
expect(output[4]).toContain('Will use temporary upload directory: ');
|
|
264
|
+
expect(output[5]).toContain('Uploading C8469F85-B060-3085-B69D-E46C645560EA.zip (DDTest, arch: armv7, UUID: C8469F85-B060-3085-B69D-E46C645560EA)');
|
|
265
|
+
expect(output[6]).toContain('Uploading 06EE3D68-D605-3E92-B92D-2F48C02A505E.zip (DDTest, arch: arm64, UUID: 06EE3D68-D605-3E92-B92D-2F48C02A505E)');
|
|
266
|
+
expect(output[7]).toContain('Uploading 3BC12422-63CC-30E8-B916-E5006CE3286C.zip (DDTest, arch: arm64, UUID: 3BC12422-63CC-30E8-B916-E5006CE3286C)');
|
|
267
|
+
expect(output[10]).toContain('Handled 3 dSYMs with success');
|
|
65
268
|
}));
|
|
66
269
|
});
|
|
@@ -1,4 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
|
5
|
+
}) : (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
o[k2] = m[k];
|
|
8
|
+
}));
|
|
9
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
10
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
11
|
+
}) : function(o, v) {
|
|
12
|
+
o["default"] = v;
|
|
13
|
+
});
|
|
14
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
15
|
+
if (mod && mod.__esModule) return mod;
|
|
16
|
+
var result = {};
|
|
17
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
18
|
+
__setModuleDefault(result, mod);
|
|
19
|
+
return result;
|
|
20
|
+
};
|
|
2
21
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
22
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
23
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -8,51 +27,72 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
27
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
28
|
});
|
|
10
29
|
};
|
|
30
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
31
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
32
|
+
};
|
|
11
33
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
describe('
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
});
|
|
52
|
-
describe('
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
34
|
+
const fs_1 = __importStar(require("fs"));
|
|
35
|
+
const glob_1 = __importDefault(require("glob"));
|
|
36
|
+
const utils_1 = require("../../../helpers/utils");
|
|
37
|
+
const utils_2 = require("../utils");
|
|
38
|
+
describe('utils', () => {
|
|
39
|
+
describe('createTmpDirectory', () => {
|
|
40
|
+
test('Create unique directory', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
41
|
+
const tmpDirectory1 = yield utils_2.createUniqueTmpDirectory();
|
|
42
|
+
const tmpDirectory2 = yield utils_2.createUniqueTmpDirectory();
|
|
43
|
+
expect(fs_1.default.existsSync(tmpDirectory1)).toBeTruthy();
|
|
44
|
+
expect(fs_1.default.existsSync(tmpDirectory2)).toBeTruthy();
|
|
45
|
+
expect(tmpDirectory1).not.toEqual(tmpDirectory2);
|
|
46
|
+
yield utils_2.deleteDirectory(tmpDirectory1);
|
|
47
|
+
yield utils_2.deleteDirectory(tmpDirectory2);
|
|
48
|
+
}));
|
|
49
|
+
});
|
|
50
|
+
describe('deleteDirectory', () => {
|
|
51
|
+
test('Delete empty directory', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
52
|
+
const tmpDirectory = yield utils_2.createUniqueTmpDirectory();
|
|
53
|
+
yield utils_2.deleteDirectory(tmpDirectory);
|
|
54
|
+
expect(fs_1.default.existsSync(tmpDirectory)).toBeFalsy();
|
|
55
|
+
}));
|
|
56
|
+
test('Delete non-empty directory', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
57
|
+
const tmpDirectory = yield utils_2.createUniqueTmpDirectory();
|
|
58
|
+
yield fs_1.promises.mkdir(utils_1.buildPath(tmpDirectory, 'foo'));
|
|
59
|
+
yield fs_1.promises.writeFile(utils_1.buildPath(tmpDirectory, 'foo', 'bar1'), 'mock1');
|
|
60
|
+
yield fs_1.promises.writeFile(utils_1.buildPath(tmpDirectory, 'foo', 'bar2'), 'mock2');
|
|
61
|
+
yield utils_2.deleteDirectory(tmpDirectory);
|
|
62
|
+
expect(fs_1.default.existsSync(tmpDirectory)).toBeFalsy();
|
|
63
|
+
}));
|
|
64
|
+
});
|
|
65
|
+
describe('zipDirectoryToArchive', () => {
|
|
66
|
+
test('Compress folder to archive at given path', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
67
|
+
const archiveDirectory = yield utils_2.createUniqueTmpDirectory();
|
|
68
|
+
const archivePath = utils_1.buildPath(archiveDirectory, 'archive.zip');
|
|
69
|
+
yield utils_2.zipDirectoryToArchive('./src/commands/dsyms/__tests__/fixtures', archivePath);
|
|
70
|
+
expect(fs_1.default.existsSync(archivePath)).toBeTruthy();
|
|
71
|
+
yield utils_2.deleteDirectory(archiveDirectory);
|
|
72
|
+
}));
|
|
73
|
+
});
|
|
74
|
+
describe('unzipArchiveToDirectory', () => {
|
|
75
|
+
test('Uncompress archive to given destination', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
76
|
+
const archiveDirectory = yield utils_2.createUniqueTmpDirectory();
|
|
77
|
+
const destinationDirectory = yield utils_2.createUniqueTmpDirectory();
|
|
78
|
+
const archivePath = utils_1.buildPath(archiveDirectory, 'archive.zip');
|
|
79
|
+
yield utils_2.zipDirectoryToArchive('./src/commands/dsyms/__tests__/fixtures', archivePath);
|
|
80
|
+
yield utils_2.unzipArchiveToDirectory(archivePath, destinationDirectory);
|
|
81
|
+
const originalContentList = glob_1.default.sync(utils_1.buildPath('./src/commands/dsyms/__tests__/', 'fixtures/**/*'));
|
|
82
|
+
const unzippedContentList = glob_1.default.sync(utils_1.buildPath(destinationDirectory, 'fixtures/**/*'));
|
|
83
|
+
expect(originalContentList.length).toEqual(unzippedContentList.length);
|
|
84
|
+
yield utils_2.deleteDirectory(archiveDirectory);
|
|
85
|
+
yield utils_2.deleteDirectory(destinationDirectory);
|
|
86
|
+
}));
|
|
87
|
+
});
|
|
88
|
+
describe('isZipFile', () => {
|
|
89
|
+
test('Zip file should return true', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
90
|
+
const file = './src/commands/dsyms/__tests__/fixtures/all.zip';
|
|
91
|
+
expect(yield utils_2.isZipFile(file)).toBeTruthy();
|
|
92
|
+
}));
|
|
93
|
+
test('Arbitrary file should return false', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
94
|
+
const file = './src/commands/dsyms/__tests__/fixtures/multiple-archs/DDTest.framework.dSYM';
|
|
95
|
+
expect(yield utils_2.isZipFile(file)).toBeFalsy();
|
|
96
|
+
}));
|
|
97
|
+
});
|
|
58
98
|
});
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
import { MultipartPayload } from '../../helpers/upload';
|
|
2
|
-
export
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
export interface Dsym {
|
|
3
|
+
bundlePath: string;
|
|
4
|
+
slices: ArchSlice[];
|
|
5
|
+
}
|
|
6
|
+
export interface ArchSlice {
|
|
7
|
+
arch: string;
|
|
8
|
+
objectPath: string;
|
|
9
|
+
uuid: string;
|
|
10
|
+
}
|
|
11
|
+
export declare class CompressedDsym {
|
|
12
|
+
archivePath: string;
|
|
13
|
+
dsym: Dsym;
|
|
14
|
+
constructor(archivePath: string, dsym: Dsym);
|
|
15
|
+
asMultipartPayload(): MultipartPayload;
|
|
7
16
|
}
|
|
@@ -1,38 +1,25 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
4
|
};
|
|
14
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.
|
|
6
|
+
exports.CompressedDsym = void 0;
|
|
16
7
|
const fs_1 = __importDefault(require("fs"));
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
this.
|
|
21
|
-
this.uuids = uuids;
|
|
8
|
+
class CompressedDsym {
|
|
9
|
+
constructor(archivePath, dsym) {
|
|
10
|
+
this.archivePath = archivePath;
|
|
11
|
+
this.dsym = dsym;
|
|
22
12
|
}
|
|
23
13
|
asMultipartPayload() {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
content,
|
|
34
|
-
};
|
|
35
|
-
});
|
|
14
|
+
const concatUUIDs = this.dsym.slices.map((slice) => slice.uuid).join();
|
|
15
|
+
const content = new Map([
|
|
16
|
+
['symbols_archive', { value: fs_1.default.createReadStream(this.archivePath) }],
|
|
17
|
+
['type', { value: 'ios_symbols' }],
|
|
18
|
+
['uuids', { value: concatUUIDs }],
|
|
19
|
+
]);
|
|
20
|
+
return {
|
|
21
|
+
content,
|
|
22
|
+
};
|
|
36
23
|
}
|
|
37
24
|
}
|
|
38
|
-
exports.
|
|
25
|
+
exports.CompressedDsym = CompressedDsym;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { Dsym } from './interfaces';
|
|
2
1
|
import { UploadStatus } from '../../helpers/upload';
|
|
2
|
+
import { ArchSlice, CompressedDsym, Dsym } from './interfaces';
|
|
3
3
|
export declare const renderConfigurationError: (error: Error) => string;
|
|
4
|
-
export declare const renderInvalidDsymWarning: (
|
|
5
|
-
export declare const
|
|
6
|
-
export declare const
|
|
4
|
+
export declare const renderInvalidDsymWarning: (dSYMPath: string) => string;
|
|
5
|
+
export declare const renderDSYMSlimmingFailure: (dSYM: Dsym, slice: ArchSlice) => string;
|
|
6
|
+
export declare const renderFailedUpload: (dSYM: CompressedDsym, errorMessage: string) => string;
|
|
7
|
+
export declare const renderRetriedUpload: (dSYM: CompressedDsym, errorMessage: string, attempt: number) => string;
|
|
7
8
|
export declare const renderSuccessfulCommand: (statuses: UploadStatus[], duration: number, dryRun: boolean) => string;
|
|
8
9
|
export declare const renderCommandInfo: (basePath: string, poolLimit: number, dryRun: boolean) => string;
|
|
9
|
-
export declare const
|
|
10
|
+
export declare const renderCommandDetail: (intermediateDirectory: string, uploadDirectory: string) => string;
|
|
11
|
+
export declare const renderUpload: (dSYM: CompressedDsym) => string;
|
|
@@ -3,22 +3,25 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.renderUpload = exports.renderCommandInfo = exports.renderSuccessfulCommand = exports.renderRetriedUpload = exports.renderFailedUpload = exports.renderInvalidDsymWarning = exports.renderConfigurationError = void 0;
|
|
6
|
+
exports.renderUpload = exports.renderCommandDetail = exports.renderCommandInfo = exports.renderSuccessfulCommand = exports.renderRetriedUpload = exports.renderFailedUpload = exports.renderDSYMSlimmingFailure = exports.renderInvalidDsymWarning = exports.renderConfigurationError = void 0;
|
|
7
7
|
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
-
const
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const formatting_1 = require("../../helpers/formatting");
|
|
10
10
|
const upload_1 = require("../../helpers/upload");
|
|
11
|
+
const utils_1 = require("./utils");
|
|
11
12
|
const renderConfigurationError = (error) => chalk_1.default.red(`${formatting_1.ICONS.FAILED} Configuration error: ${error}.\n`);
|
|
12
13
|
exports.renderConfigurationError = renderConfigurationError;
|
|
13
|
-
const renderInvalidDsymWarning = (
|
|
14
|
+
const renderInvalidDsymWarning = (dSYMPath) => chalk_1.default.yellow(`${formatting_1.ICONS.WARNING} Invalid dSYM file, will be skipped: ${dSYMPath}\n`);
|
|
14
15
|
exports.renderInvalidDsymWarning = renderInvalidDsymWarning;
|
|
16
|
+
const renderDSYMSlimmingFailure = (dSYM, slice) => chalk_1.default.yellow(`${formatting_1.ICONS.WARNING} Failed to export '${slice.arch}' arch (${slice.uuid}) from ${dSYM.bundlePath}\n`);
|
|
17
|
+
exports.renderDSYMSlimmingFailure = renderDSYMSlimmingFailure;
|
|
15
18
|
const renderFailedUpload = (dSYM, errorMessage) => {
|
|
16
|
-
const dSYMPathBold = `[${chalk_1.default.bold.dim(dSYM.
|
|
19
|
+
const dSYMPathBold = `[${chalk_1.default.bold.dim(dSYM.dsym.bundlePath)}]`;
|
|
17
20
|
return chalk_1.default.red(`${formatting_1.ICONS.FAILED} Failed upload dSYM for ${dSYMPathBold}: ${errorMessage}\n`);
|
|
18
21
|
};
|
|
19
22
|
exports.renderFailedUpload = renderFailedUpload;
|
|
20
23
|
const renderRetriedUpload = (dSYM, errorMessage, attempt) => {
|
|
21
|
-
const dSYMPathBold = `[${chalk_1.default.bold.dim(dSYM.
|
|
24
|
+
const dSYMPathBold = `[${chalk_1.default.bold.dim(dSYM.dsym.bundlePath)}]`;
|
|
22
25
|
return chalk_1.default.yellow(`[attempt ${attempt}] Retrying dSYM upload ${dSYMPathBold}: ${errorMessage}\n`);
|
|
23
26
|
};
|
|
24
27
|
exports.renderRetriedUpload = renderRetriedUpload;
|
|
@@ -75,5 +78,14 @@ const renderCommandInfo = (basePath, poolLimit, dryRun) => {
|
|
|
75
78
|
return fullStr;
|
|
76
79
|
};
|
|
77
80
|
exports.renderCommandInfo = renderCommandInfo;
|
|
78
|
-
const
|
|
81
|
+
const renderCommandDetail = (intermediateDirectory, uploadDirectory) => `Will use temporary intermediate directory: ${intermediateDirectory}\n` +
|
|
82
|
+
`Will use temporary upload directory: ${uploadDirectory}\n`;
|
|
83
|
+
exports.renderCommandDetail = renderCommandDetail;
|
|
84
|
+
const renderUpload = (dSYM) => {
|
|
85
|
+
const archiveName = path_1.default.basename(dSYM.archivePath);
|
|
86
|
+
const objectName = dSYM.dsym.slices.map((slice) => path_1.default.basename(slice.objectPath))[0];
|
|
87
|
+
const archs = dSYM.dsym.slices.map((slice) => slice.arch).join();
|
|
88
|
+
const uuids = dSYM.dsym.slices.map((slice) => slice.uuid).join();
|
|
89
|
+
return `Uploading ${archiveName} (${objectName}, arch: ${archs}, UUID: ${uuids})\n`;
|
|
90
|
+
};
|
|
79
91
|
exports.renderUpload = renderUpload;
|
|
@@ -2,10 +2,41 @@ import { Command } from 'clipanion';
|
|
|
2
2
|
export declare class UploadCommand extends Command {
|
|
3
3
|
static usage: import("clipanion").Usage;
|
|
4
4
|
private basePath;
|
|
5
|
+
private cliVersion;
|
|
5
6
|
private config;
|
|
6
7
|
private dryRun;
|
|
7
8
|
private maxConcurrency;
|
|
9
|
+
constructor();
|
|
8
10
|
execute(): Promise<1 | 0>;
|
|
11
|
+
private compressDSYMsToDirectory;
|
|
12
|
+
private findDSYMsInDirectory;
|
|
9
13
|
private getRequestBuilder;
|
|
14
|
+
/**
|
|
15
|
+
* Parses the output of `dwarfdump --uuid` command (ref.: https://www.unix.com/man-page/osx/1/dwarfdump/).
|
|
16
|
+
* It returns one or many arch slices read from the output.
|
|
17
|
+
*
|
|
18
|
+
* Example `dwarfdump --uuid` output:
|
|
19
|
+
* ```
|
|
20
|
+
* $ dwarfdump --uuid DDTest.framework.dSYM
|
|
21
|
+
* UUID: C8469F85-B060-3085-B69D-E46C645560EA (armv7) DDTest.framework.dSYM/Contents/Resources/DWARF/DDTest
|
|
22
|
+
* UUID: 06EE3D68-D605-3E92-B92D-2F48C02A505E (arm64) DDTest.framework.dSYM/Contents/Resources/DWARF/DDTest
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
private parseDwarfdumpOutput;
|
|
26
|
+
/**
|
|
27
|
+
* It takes fat dSYM as input and returns multiple dSYMs by extracting **each arch**
|
|
28
|
+
* to separate dSYM file. New files are saved to `intermediatePath` and named by their object uuid (`<uuid>.dSYM`).
|
|
29
|
+
*
|
|
30
|
+
* For example, given `<source path>/Foo.dSYM/Contents/Resources/DWARF/Foo` dSYM with two arch slices: `arm64` (uuid1)
|
|
31
|
+
* and `x86_64` (uuid2), it will:
|
|
32
|
+
* - create `<intermediate path>/<uuid1>.dSYM/Contents/Resources/DWARF/Foo` for `arm64`,
|
|
33
|
+
* - create `<intermediate path>/<uuid2>.dSYM/Contents/Resources/DWARF/Foo` for `x86_64`.
|
|
34
|
+
*/
|
|
35
|
+
private thinDSYM;
|
|
36
|
+
/**
|
|
37
|
+
* It takes `N` dSYMs and returns `N` or more dSYMs. If a dSYM includes more than one arch slice,
|
|
38
|
+
* it will be thinned by extracting each arch to a new dSYM in `intermediatePath`.
|
|
39
|
+
*/
|
|
40
|
+
private thinDSYMs;
|
|
10
41
|
private uploadDSYM;
|
|
11
42
|
}
|
|
@@ -15,6 +15,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
15
15
|
exports.UploadCommand = void 0;
|
|
16
16
|
const chalk_1 = __importDefault(require("chalk"));
|
|
17
17
|
const clipanion_1 = require("clipanion");
|
|
18
|
+
const fs_1 = require("fs");
|
|
19
|
+
const glob_1 = __importDefault(require("glob"));
|
|
18
20
|
const path_1 = __importDefault(require("path"));
|
|
19
21
|
const tiny_async_pool_1 = __importDefault(require("tiny-async-pool"));
|
|
20
22
|
const apikey_1 = require("../../helpers/apikey");
|
|
@@ -22,44 +24,155 @@ const errors_1 = require("../../helpers/errors");
|
|
|
22
24
|
const metrics_1 = require("../../helpers/metrics");
|
|
23
25
|
const upload_1 = require("../../helpers/upload");
|
|
24
26
|
const utils_1 = require("../../helpers/utils");
|
|
27
|
+
const interfaces_1 = require("./interfaces");
|
|
25
28
|
const renderer_1 = require("./renderer");
|
|
26
29
|
const utils_2 = require("./utils");
|
|
27
30
|
class UploadCommand extends clipanion_1.Command {
|
|
28
31
|
constructor() {
|
|
29
|
-
super(
|
|
32
|
+
super();
|
|
30
33
|
this.config = {
|
|
31
34
|
apiKey: process.env.DATADOG_API_KEY,
|
|
32
35
|
datadogSite: process.env.DATADOG_SITE || 'datadoghq.com',
|
|
33
36
|
};
|
|
34
37
|
this.dryRun = false;
|
|
35
38
|
this.maxConcurrency = 20;
|
|
39
|
+
this.compressDSYMsToDirectory = (dsyms, directoryPath) => __awaiter(this, void 0, void 0, function* () {
|
|
40
|
+
yield fs_1.promises.mkdir(directoryPath, { recursive: true });
|
|
41
|
+
return Promise.all(dsyms.map((dsym) => __awaiter(this, void 0, void 0, function* () {
|
|
42
|
+
const archivePath = utils_1.buildPath(directoryPath, `${dsym.slices[0].uuid}.zip`);
|
|
43
|
+
yield utils_2.zipDirectoryToArchive(dsym.bundlePath, archivePath);
|
|
44
|
+
return new interfaces_1.CompressedDsym(archivePath, dsym);
|
|
45
|
+
})));
|
|
46
|
+
});
|
|
47
|
+
this.findDSYMsInDirectory = (directoryPath) => __awaiter(this, void 0, void 0, function* () {
|
|
48
|
+
const dsyms = [];
|
|
49
|
+
for (const dSYMPath of glob_1.default.sync(utils_1.buildPath(directoryPath, '**/*.dSYM'))) {
|
|
50
|
+
try {
|
|
51
|
+
const stdout = (yield utils_2.executeDwarfdump(dSYMPath)).stdout;
|
|
52
|
+
const archSlices = this.parseDwarfdumpOutput(stdout);
|
|
53
|
+
dsyms.push({ bundlePath: dSYMPath, slices: archSlices });
|
|
54
|
+
}
|
|
55
|
+
catch (_a) {
|
|
56
|
+
this.context.stdout.write(renderer_1.renderInvalidDsymWarning(dSYMPath));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return Promise.all(dsyms);
|
|
60
|
+
});
|
|
61
|
+
/**
|
|
62
|
+
* Parses the output of `dwarfdump --uuid` command (ref.: https://www.unix.com/man-page/osx/1/dwarfdump/).
|
|
63
|
+
* It returns one or many arch slices read from the output.
|
|
64
|
+
*
|
|
65
|
+
* Example `dwarfdump --uuid` output:
|
|
66
|
+
* ```
|
|
67
|
+
* $ dwarfdump --uuid DDTest.framework.dSYM
|
|
68
|
+
* UUID: C8469F85-B060-3085-B69D-E46C645560EA (armv7) DDTest.framework.dSYM/Contents/Resources/DWARF/DDTest
|
|
69
|
+
* UUID: 06EE3D68-D605-3E92-B92D-2F48C02A505E (arm64) DDTest.framework.dSYM/Contents/Resources/DWARF/DDTest
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
this.parseDwarfdumpOutput = (output) => {
|
|
73
|
+
const lineRegexp = /UUID: ([0-9A-F]{8}-(?:[0-9A-F]{4}-){3}[0-9A-F]{12}) \(([a-z0-9_]+)\) (.+)/;
|
|
74
|
+
return output
|
|
75
|
+
.split('\n')
|
|
76
|
+
.map((line) => {
|
|
77
|
+
const match = line.match(lineRegexp);
|
|
78
|
+
return match ? [{ arch: match[2], objectPath: match[3], uuid: match[1] }] : [];
|
|
79
|
+
})
|
|
80
|
+
.reduce((acc, nextSlice) => acc.concat(nextSlice), []);
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* It takes fat dSYM as input and returns multiple dSYMs by extracting **each arch**
|
|
84
|
+
* to separate dSYM file. New files are saved to `intermediatePath` and named by their object uuid (`<uuid>.dSYM`).
|
|
85
|
+
*
|
|
86
|
+
* For example, given `<source path>/Foo.dSYM/Contents/Resources/DWARF/Foo` dSYM with two arch slices: `arm64` (uuid1)
|
|
87
|
+
* and `x86_64` (uuid2), it will:
|
|
88
|
+
* - create `<intermediate path>/<uuid1>.dSYM/Contents/Resources/DWARF/Foo` for `arm64`,
|
|
89
|
+
* - create `<intermediate path>/<uuid2>.dSYM/Contents/Resources/DWARF/Foo` for `x86_64`.
|
|
90
|
+
*/
|
|
91
|
+
this.thinDSYM = (dsym, intermediatePath) => __awaiter(this, void 0, void 0, function* () {
|
|
92
|
+
const slimmedDSYMs = [];
|
|
93
|
+
for (const slice of dsym.slices) {
|
|
94
|
+
try {
|
|
95
|
+
const newDSYMBundleName = `${slice.uuid}.dSYM`;
|
|
96
|
+
const newDSYMBundlePath = utils_1.buildPath(intermediatePath, newDSYMBundleName);
|
|
97
|
+
const newObjectPath = utils_1.buildPath(newDSYMBundlePath, path_1.default.relative(dsym.bundlePath, slice.objectPath));
|
|
98
|
+
// Extract arch slice:
|
|
99
|
+
yield fs_1.promises.mkdir(path_1.default.dirname(newObjectPath), { recursive: true });
|
|
100
|
+
yield utils_2.executeLipo(slice.objectPath, slice.arch, newObjectPath);
|
|
101
|
+
// The original dSYM bundle can also include `Info.plist` file, so copy it to the `<uuid>.dSYM` as well.
|
|
102
|
+
// Ref.: https://opensource.apple.com/source/lldb/lldb-179.1/www/symbols.html
|
|
103
|
+
const infoPlistPath = glob_1.default.sync(utils_1.buildPath(dsym.bundlePath, '**/Info.plist'))[0];
|
|
104
|
+
if (infoPlistPath) {
|
|
105
|
+
const newInfoPlistPath = utils_1.buildPath(newDSYMBundlePath, path_1.default.relative(dsym.bundlePath, infoPlistPath));
|
|
106
|
+
yield fs_1.promises.mkdir(path_1.default.dirname(newInfoPlistPath), { recursive: true });
|
|
107
|
+
yield fs_1.promises.copyFile(infoPlistPath, newInfoPlistPath);
|
|
108
|
+
}
|
|
109
|
+
slimmedDSYMs.push({
|
|
110
|
+
bundlePath: newDSYMBundlePath,
|
|
111
|
+
slices: [{ arch: slice.arch, uuid: slice.uuid, objectPath: newObjectPath }],
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
catch (_b) {
|
|
115
|
+
this.context.stdout.write(renderer_1.renderDSYMSlimmingFailure(dsym, slice));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return Promise.all(slimmedDSYMs);
|
|
119
|
+
});
|
|
120
|
+
/**
|
|
121
|
+
* It takes `N` dSYMs and returns `N` or more dSYMs. If a dSYM includes more than one arch slice,
|
|
122
|
+
* it will be thinned by extracting each arch to a new dSYM in `intermediatePath`.
|
|
123
|
+
*/
|
|
124
|
+
this.thinDSYMs = (dsyms, intermediatePath) => __awaiter(this, void 0, void 0, function* () {
|
|
125
|
+
yield fs_1.promises.mkdir(intermediatePath, { recursive: true });
|
|
126
|
+
let slimDSYMs = [];
|
|
127
|
+
for (const dsym of dsyms) {
|
|
128
|
+
if (dsym.slices.length > 1) {
|
|
129
|
+
slimDSYMs = slimDSYMs.concat(yield this.thinDSYM(dsym, intermediatePath));
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
slimDSYMs.push(dsym);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return Promise.all(slimDSYMs);
|
|
136
|
+
});
|
|
137
|
+
this.cliVersion = require('../../../package.json').version;
|
|
36
138
|
}
|
|
37
139
|
execute() {
|
|
38
140
|
return __awaiter(this, void 0, void 0, function* () {
|
|
141
|
+
// Normalizing the basePath to resolve .. and .
|
|
39
142
|
this.basePath = path_1.default.posix.normalize(this.basePath);
|
|
40
|
-
|
|
143
|
+
this.context.stdout.write(renderer_1.renderCommandInfo(this.basePath, this.maxConcurrency, this.dryRun));
|
|
41
144
|
const metricsLogger = metrics_1.getMetricsLogger({
|
|
42
145
|
datadogSite: process.env.DATADOG_SITE,
|
|
43
|
-
defaultTags: [`cli_version:${cliVersion}`],
|
|
146
|
+
defaultTags: [`cli_version:${this.cliVersion}`],
|
|
44
147
|
prefix: 'datadog.ci.dsyms.',
|
|
45
148
|
});
|
|
46
|
-
this.context.stdout.write(renderer_1.renderCommandInfo(this.basePath, this.maxConcurrency, this.dryRun));
|
|
47
|
-
const initialTime = Date.now();
|
|
48
|
-
let searchPath = this.basePath;
|
|
49
|
-
if (yield utils_2.isZipFile(this.basePath)) {
|
|
50
|
-
searchPath = yield utils_2.unzipToTmpDir(this.basePath);
|
|
51
|
-
}
|
|
52
149
|
const apiKeyValidator = apikey_1.newApiKeyValidator({
|
|
53
150
|
apiKey: this.config.apiKey,
|
|
54
151
|
datadogSite: this.config.datadogSite,
|
|
55
152
|
metricsLogger: metricsLogger.logger,
|
|
56
153
|
});
|
|
57
|
-
const
|
|
58
|
-
const
|
|
154
|
+
const initialTime = Date.now();
|
|
155
|
+
const tmpDirectory = yield utils_2.createUniqueTmpDirectory();
|
|
156
|
+
const intermediateDirectory = utils_1.buildPath(tmpDirectory, 'datadog-ci', 'dsyms', 'intermediate');
|
|
157
|
+
const uploadDirectory = utils_1.buildPath(tmpDirectory, 'datadog-ci', 'dsyms', 'upload');
|
|
158
|
+
this.context.stdout.write(renderer_1.renderCommandDetail(intermediateDirectory, uploadDirectory));
|
|
159
|
+
// The CLI input path can be a folder or `.zip` archive with `*.dSYM` files.
|
|
160
|
+
// In case of `.zip`, extract it to temporary location, so it can be handled the same way as folder.
|
|
161
|
+
let dSYMsSearchDirectory = this.basePath;
|
|
162
|
+
if (yield utils_2.isZipFile(this.basePath)) {
|
|
163
|
+
yield utils_2.unzipArchiveToDirectory(this.basePath, tmpDirectory);
|
|
164
|
+
dSYMsSearchDirectory = tmpDirectory;
|
|
165
|
+
}
|
|
166
|
+
const dsyms = yield this.findDSYMsInDirectory(dSYMsSearchDirectory);
|
|
167
|
+
// Reduce dSYMs size by extracting arch slices from fat dSYMs to separate single-arch dSYMs in intermediate location.
|
|
168
|
+
// This is to avoid exceeding intake limit whenever possible.
|
|
169
|
+
const slimDSYMs = yield this.thinDSYMs(dsyms, intermediateDirectory);
|
|
170
|
+
// Compress each dSYM into single `.zip` archive.
|
|
171
|
+
const compressedDSYMs = yield this.compressDSYMsToDirectory(slimDSYMs, uploadDirectory);
|
|
59
172
|
const requestBuilder = this.getRequestBuilder();
|
|
60
173
|
const uploadDSYM = this.uploadDSYM(requestBuilder, metricsLogger, apiKeyValidator);
|
|
61
174
|
try {
|
|
62
|
-
const results = yield tiny_async_pool_1.default(this.maxConcurrency,
|
|
175
|
+
const results = yield tiny_async_pool_1.default(this.maxConcurrency, compressedDSYMs, uploadDSYM);
|
|
63
176
|
const totalTime = (Date.now() - initialTime) / 1000;
|
|
64
177
|
this.context.stdout.write(renderer_1.renderSuccessfulCommand(results, totalTime, this.dryRun));
|
|
65
178
|
metricsLogger.logger.gauge('duration', totalTime);
|
|
@@ -74,6 +187,7 @@ class UploadCommand extends clipanion_1.Command {
|
|
|
74
187
|
throw error;
|
|
75
188
|
}
|
|
76
189
|
finally {
|
|
190
|
+
yield utils_2.deleteDirectory(tmpDirectory);
|
|
77
191
|
try {
|
|
78
192
|
yield metricsLogger.flush();
|
|
79
193
|
}
|
|
@@ -94,7 +208,7 @@ class UploadCommand extends clipanion_1.Command {
|
|
|
94
208
|
}
|
|
95
209
|
uploadDSYM(requestBuilder, metricsLogger, apiKeyValidator) {
|
|
96
210
|
return (dSYM) => __awaiter(this, void 0, void 0, function* () {
|
|
97
|
-
const payload =
|
|
211
|
+
const payload = dSYM.asMultipartPayload();
|
|
98
212
|
if (this.dryRun) {
|
|
99
213
|
this.context.stdout.write(`[DRYRUN] ${renderer_1.renderUpload(dSYM)}`);
|
|
100
214
|
return upload_1.UploadStatus.Success;
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
import { BaseContext } from 'clipanion';
|
|
2
|
-
import { Dsym } from './interfaces';
|
|
3
1
|
export declare const isZipFile: (filepath: string) => Promise<boolean>;
|
|
4
|
-
export declare const
|
|
5
|
-
export declare const
|
|
6
|
-
export declare const
|
|
7
|
-
export declare const
|
|
2
|
+
export declare const createUniqueTmpDirectory: () => Promise<string>;
|
|
3
|
+
export declare const deleteDirectory: (directoryPath: string) => Promise<void>;
|
|
4
|
+
export declare const zipDirectoryToArchive: (directoryPath: string, archivePath: string) => Promise<void>;
|
|
5
|
+
export declare const unzipArchiveToDirectory: (archivePath: string, directoryPath: string) => Promise<void>;
|
|
6
|
+
export declare const executeDwarfdump: (dSYMPath: string) => Promise<{
|
|
7
|
+
stderr: string;
|
|
8
|
+
stdout: string;
|
|
9
|
+
}>;
|
|
10
|
+
export declare const executeLipo: (objectPath: string, arch: string, newObjectPath: string) => Promise<{
|
|
11
|
+
stderr: string;
|
|
12
|
+
stdout: string;
|
|
13
|
+
}>;
|
|
8
14
|
export declare const getBaseIntakeUrl: () => string;
|
|
9
15
|
export declare const pluralize: (nb: number, singular: string, plural: string) => string;
|
|
@@ -12,18 +12,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
12
12
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
13
|
};
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.pluralize = exports.getBaseIntakeUrl = exports.
|
|
15
|
+
exports.pluralize = exports.getBaseIntakeUrl = exports.executeLipo = exports.executeDwarfdump = exports.unzipArchiveToDirectory = exports.zipDirectoryToArchive = exports.deleteDirectory = exports.createUniqueTmpDirectory = exports.isZipFile = void 0;
|
|
16
16
|
const child_process_1 = require("child_process");
|
|
17
17
|
const fs_1 = require("fs");
|
|
18
|
-
const glob_1 = __importDefault(require("glob"));
|
|
19
18
|
const os_1 = require("os");
|
|
20
19
|
const path_1 = __importDefault(require("path"));
|
|
20
|
+
const rimraf_1 = __importDefault(require("rimraf"));
|
|
21
21
|
const util_1 = require("util");
|
|
22
|
-
const interfaces_1 = require("./interfaces");
|
|
23
22
|
const utils_1 = require("../../helpers/utils");
|
|
24
|
-
const renderer_1 = require("./renderer");
|
|
25
|
-
const UUID_REGEX = '[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}';
|
|
26
|
-
const globAsync = util_1.promisify(glob_1.default);
|
|
27
23
|
const isZipFile = (filepath) => __awaiter(void 0, void 0, void 0, function* () {
|
|
28
24
|
try {
|
|
29
25
|
const stats = yield fs_1.promises.stat(filepath);
|
|
@@ -35,54 +31,30 @@ const isZipFile = (filepath) => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
35
31
|
}
|
|
36
32
|
});
|
|
37
33
|
exports.isZipFile = isZipFile;
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
return new interfaces_1.Dsym(dSYMPath, uuids);
|
|
44
|
-
}
|
|
45
|
-
catch (_a) {
|
|
46
|
-
context.stdout.write(renderer_1.renderInvalidDsymWarning(dSYMPath));
|
|
47
|
-
return undefined;
|
|
48
|
-
}
|
|
49
|
-
}));
|
|
50
|
-
return Promise.all(allDsyms);
|
|
34
|
+
const createUniqueTmpDirectory = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
35
|
+
const uniqueValue = Math.random() * Number.MAX_SAFE_INTEGER;
|
|
36
|
+
const directoryPath = utils_1.buildPath(os_1.tmpdir(), uniqueValue.toString());
|
|
37
|
+
yield fs_1.promises.mkdir(directoryPath, { recursive: true });
|
|
38
|
+
return directoryPath;
|
|
51
39
|
});
|
|
52
|
-
exports.
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
uuids.push(regexMatches[0]);
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
return uuids;
|
|
63
|
-
});
|
|
64
|
-
exports.dwarfdumpUUID = dwarfdumpUUID;
|
|
65
|
-
const tmpFolder = utils_1.buildPath(os_1.tmpdir(), 'datadog-ci', 'dsyms');
|
|
66
|
-
const zipToTmpDir = (sourcePath, targetFilename) => __awaiter(void 0, void 0, void 0, function* () {
|
|
67
|
-
yield fs_1.promises.mkdir(tmpFolder, { recursive: true });
|
|
68
|
-
const targetPath = utils_1.buildPath(tmpFolder, targetFilename);
|
|
69
|
-
const sourceDir = path_1.default.dirname(sourcePath);
|
|
70
|
-
const sourceFile = path_1.default.basename(sourcePath);
|
|
71
|
-
// `zip -r foo.zip f1/f2/f3/foo.dSYM`
|
|
72
|
-
// this keeps f1/f2/f3 folders in foo.zip, we don't want this
|
|
73
|
-
// `cwd: sourceDir` is passed to avoid that
|
|
74
|
-
yield execute(`zip -r '${targetPath}' '${sourceFile}'`, sourceDir);
|
|
75
|
-
return targetPath;
|
|
40
|
+
exports.createUniqueTmpDirectory = createUniqueTmpDirectory;
|
|
41
|
+
const deleteDirectory = (directoryPath) => __awaiter(void 0, void 0, void 0, function* () { return new Promise((resolve, reject) => rimraf_1.default(directoryPath, () => resolve())); });
|
|
42
|
+
exports.deleteDirectory = deleteDirectory;
|
|
43
|
+
const zipDirectoryToArchive = (directoryPath, archivePath) => __awaiter(void 0, void 0, void 0, function* () {
|
|
44
|
+
const cwd = path_1.default.dirname(directoryPath);
|
|
45
|
+
const directoryName = path_1.default.basename(directoryPath);
|
|
46
|
+
yield execute(`zip -r '${archivePath}' '${directoryName}'`, cwd);
|
|
76
47
|
});
|
|
77
|
-
exports.
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
yield fs_1.promises.mkdir(dirPath, { recursive: true });
|
|
82
|
-
yield execute(`unzip -o '${sourcePath}' -d '${targetPath}'`);
|
|
83
|
-
return targetPath;
|
|
48
|
+
exports.zipDirectoryToArchive = zipDirectoryToArchive;
|
|
49
|
+
const unzipArchiveToDirectory = (archivePath, directoryPath) => __awaiter(void 0, void 0, void 0, function* () {
|
|
50
|
+
yield fs_1.promises.mkdir(directoryPath, { recursive: true });
|
|
51
|
+
yield execute(`unzip -o '${archivePath}' -d '${directoryPath}'`);
|
|
84
52
|
});
|
|
85
|
-
exports.
|
|
53
|
+
exports.unzipArchiveToDirectory = unzipArchiveToDirectory;
|
|
54
|
+
const executeDwarfdump = (dSYMPath) => __awaiter(void 0, void 0, void 0, function* () { return execute(`dwarfdump --uuid '${dSYMPath}'`); });
|
|
55
|
+
exports.executeDwarfdump = executeDwarfdump;
|
|
56
|
+
const executeLipo = (objectPath, arch, newObjectPath) => __awaiter(void 0, void 0, void 0, function* () { return execute(`lipo '${objectPath}' -thin ${arch} -output '${newObjectPath}'`); });
|
|
57
|
+
exports.executeLipo = executeLipo;
|
|
86
58
|
const execProc = util_1.promisify(child_process_1.exec);
|
|
87
59
|
const execute = (cmd, cwd) => execProc(cmd, {
|
|
88
60
|
cwd,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datadog/datadog-ci",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Run datadog actions from the CI.",
|
|
5
5
|
"repository": "https://github.com/DataDog/datadog-ci",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -64,8 +64,9 @@
|
|
|
64
64
|
"glob": "7.1.4",
|
|
65
65
|
"inquirer": "8.2.0",
|
|
66
66
|
"proxy-agent": "5.0.0",
|
|
67
|
+
"rimraf": "^3.0.2",
|
|
67
68
|
"simple-git": "3.3.0",
|
|
68
|
-
"ssh2": "1.
|
|
69
|
+
"ssh2": "1.8.0",
|
|
69
70
|
"ssh2-streams": "0.4.10",
|
|
70
71
|
"sshpk": "1.16.1",
|
|
71
72
|
"tiny-async-pool": "1.2.0",
|
|
@@ -83,6 +84,7 @@
|
|
|
83
84
|
"@types/inquirer": "8.1.3",
|
|
84
85
|
"@types/jest": "24.0.15",
|
|
85
86
|
"@types/node": "10.17.57",
|
|
87
|
+
"@types/rimraf": "^3.0.2",
|
|
86
88
|
"@types/ssh2": "0.5.46",
|
|
87
89
|
"@types/ssh2-streams": "0.1.8",
|
|
88
90
|
"@types/sshpk": "1.10.5",
|