Skip to content

Commit 2710592

Browse files
fwalthboop
andauthored
tool-cache: Support for extracting xar compatible archives (actions#207)
* Test xar extraction * Support for extracting xar compatible archives * Only allow extractXar on mac * Create xar during test instead of using prebuilt * Update lockfiles * Add verbose flag if debug * Add extractXar example to readme * Revert "Update lockfiles" This reverts commit a6cbddc. * Use node pkg in example * Remove and ignore prebuilt xar * Tests for non-existing dir and without flags * Better arguments handling * Make sure that target directory exists Co-authored-by: Thomas Boop <[email protected]>
1 parent 7e1c59c commit 2710592

File tree

7 files changed

+159
-0
lines changed

7 files changed

+159
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ packages/*/node_modules/
33
packages/*/lib/
44
packages/*/__tests__/_temp/
55
.DS_Store
6+
*.xar
67
packages/*/audit.json

packages/tool-cache/README.md

+4
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ if (process.platform === 'win32') {
2929
const node12Path = await tc.downloadTool('https://nodejs.org/dist/v12.7.0/node-v12.7.0-win-x64.7z');
3030
const node12ExtractedFolder = await tc.extract7z(node12Path, 'path/to/extract/to');
3131
}
32+
else if (process.platform === 'darwin') {
33+
const node12Path = await tc.downloadTool('https://nodejs.org/dist/v12.7.0/node-v12.7.0.pkg');
34+
const node12ExtractedFolder = await tc.extractXar(node12Path, 'path/to/extract/to');
35+
}
3236
else {
3337
const node12Path = await tc.downloadTool('https://nodejs.org/dist/v12.7.0/node-v12.7.0-linux-x64.tar.gz');
3438
const node12ExtractedFolder = await tc.extractTar(node12Path, 'path/to/extract/to');
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
file-with-�-character.txt
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
file.txt contents
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
folder/nested-file.txt contents

packages/tool-cache/__tests__/tool-cache.test.ts

+105
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ process.env['RUNNER_TOOL_CACHE'] = cachePath
1515
import * as tc from '../src/tool-cache'
1616

1717
const IS_WINDOWS = process.platform === 'win32'
18+
const IS_MAC = process.platform === 'darwin'
1819

1920
describe('@actions/tool-cache', function() {
2021
beforeAll(function() {
@@ -346,6 +347,110 @@ describe('@actions/tool-cache', function() {
346347
await io.rmRF(tempDir)
347348
}
348349
})
350+
} else if (IS_MAC) {
351+
it('extract .xar', async () => {
352+
const tempDir = path.join(tempPath, 'test-install.xar')
353+
const sourcePath = path.join(__dirname, 'data', 'archive-content')
354+
const targetPath = path.join(tempDir, 'test.xar')
355+
await io.mkdirP(tempDir)
356+
357+
// Create test archive
358+
const xarPath = await io.which('xar', true)
359+
await exec.exec(`${xarPath}`, ['-cf', targetPath, '.'], {
360+
cwd: sourcePath
361+
})
362+
363+
// extract/cache
364+
const extPath: string = await tc.extractXar(targetPath, undefined, '-x')
365+
await tc.cacheDir(extPath, 'my-xar-contents', '1.1.0')
366+
const toolPath: string = tc.find('my-xar-contents', '1.1.0')
367+
368+
expect(fs.existsSync(toolPath)).toBeTruthy()
369+
expect(fs.existsSync(`${toolPath}.complete`)).toBeTruthy()
370+
expect(fs.existsSync(path.join(toolPath, 'file.txt'))).toBeTruthy()
371+
expect(
372+
fs.existsSync(path.join(toolPath, 'file-with-ç-character.txt'))
373+
).toBeTruthy()
374+
expect(
375+
fs.existsSync(path.join(toolPath, 'folder', 'nested-file.txt'))
376+
).toBeTruthy()
377+
expect(
378+
fs.readFileSync(
379+
path.join(toolPath, 'folder', 'nested-file.txt'),
380+
'utf8'
381+
)
382+
).toBe('folder/nested-file.txt contents')
383+
})
384+
385+
it('extract .xar to a directory that does not exist', async () => {
386+
const tempDir = path.join(tempPath, 'test-install.xar')
387+
const sourcePath = path.join(__dirname, 'data', 'archive-content')
388+
const targetPath = path.join(tempDir, 'test.xar')
389+
await io.mkdirP(tempDir)
390+
391+
const destDir = path.join(tempDir, 'not-exist')
392+
393+
// Create test archive
394+
const xarPath = await io.which('xar', true)
395+
await exec.exec(`${xarPath}`, ['-cf', targetPath, '.'], {
396+
cwd: sourcePath
397+
})
398+
399+
// extract/cache
400+
const extPath: string = await tc.extractXar(targetPath, destDir, '-x')
401+
await tc.cacheDir(extPath, 'my-xar-contents', '1.1.0')
402+
const toolPath: string = tc.find('my-xar-contents', '1.1.0')
403+
404+
expect(fs.existsSync(toolPath)).toBeTruthy()
405+
expect(fs.existsSync(`${toolPath}.complete`)).toBeTruthy()
406+
expect(fs.existsSync(path.join(toolPath, 'file.txt'))).toBeTruthy()
407+
expect(
408+
fs.existsSync(path.join(toolPath, 'file-with-ç-character.txt'))
409+
).toBeTruthy()
410+
expect(
411+
fs.existsSync(path.join(toolPath, 'folder', 'nested-file.txt'))
412+
).toBeTruthy()
413+
expect(
414+
fs.readFileSync(
415+
path.join(toolPath, 'folder', 'nested-file.txt'),
416+
'utf8'
417+
)
418+
).toBe('folder/nested-file.txt contents')
419+
})
420+
421+
it('extract .xar without flags', async () => {
422+
const tempDir = path.join(tempPath, 'test-install.xar')
423+
const sourcePath = path.join(__dirname, 'data', 'archive-content')
424+
const targetPath = path.join(tempDir, 'test.xar')
425+
await io.mkdirP(tempDir)
426+
427+
// Create test archive
428+
const xarPath = await io.which('xar', true)
429+
await exec.exec(`${xarPath}`, ['-cf', targetPath, '.'], {
430+
cwd: sourcePath
431+
})
432+
433+
// extract/cache
434+
const extPath: string = await tc.extractXar(targetPath, undefined)
435+
await tc.cacheDir(extPath, 'my-xar-contents', '1.1.0')
436+
const toolPath: string = tc.find('my-xar-contents', '1.1.0')
437+
438+
expect(fs.existsSync(toolPath)).toBeTruthy()
439+
expect(fs.existsSync(`${toolPath}.complete`)).toBeTruthy()
440+
expect(fs.existsSync(path.join(toolPath, 'file.txt'))).toBeTruthy()
441+
expect(
442+
fs.existsSync(path.join(toolPath, 'file-with-ç-character.txt'))
443+
).toBeTruthy()
444+
expect(
445+
fs.existsSync(path.join(toolPath, 'folder', 'nested-file.txt'))
446+
).toBeTruthy()
447+
expect(
448+
fs.readFileSync(
449+
path.join(toolPath, 'folder', 'nested-file.txt'),
450+
'utf8'
451+
)
452+
).toBe('folder/nested-file.txt contents')
453+
})
349454
}
350455

351456
it('extract .tar.gz', async () => {

packages/tool-cache/src/tool-cache.ts

+46
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export class HTTPError extends Error {
2323
}
2424

2525
const IS_WINDOWS = process.platform === 'win32'
26+
const IS_MAC = process.platform === 'darwin'
2627
const userAgent = 'actions/tool-cache'
2728

2829
/**
@@ -276,6 +277,43 @@ export async function extractTar(
276277
return dest
277278
}
278279

280+
/**
281+
* Extract a xar compatible archive
282+
*
283+
* @param file path to the archive
284+
* @param dest destination directory. Optional.
285+
* @param flags flags for the xar. Optional.
286+
* @returns path to the destination directory
287+
*/
288+
export async function extractXar(
289+
file: string,
290+
dest?: string,
291+
flags: string | string[] = []
292+
): Promise<string> {
293+
ok(IS_MAC, 'extractXar() not supported on current OS')
294+
ok(file, 'parameter "file" is required')
295+
296+
dest = await _createExtractFolder(dest)
297+
298+
let args: string[]
299+
if (flags instanceof Array) {
300+
args = flags
301+
} else {
302+
args = [flags]
303+
}
304+
305+
args.push('-x', '-C', dest, '-f', file)
306+
307+
if (core.isDebug()) {
308+
args.push('-v')
309+
}
310+
311+
const xarPath: string = await io.which('xar', true)
312+
await exec(`"${xarPath}"`, _unique(args))
313+
314+
return dest
315+
}
316+
279317
/**
280318
* Extract a zip
281319
*
@@ -675,3 +713,11 @@ function _getGlobal<T>(key: string, defaultValue: T): T {
675713
/* eslint-enable @typescript-eslint/no-explicit-any */
676714
return value !== undefined ? value : defaultValue
677715
}
716+
717+
/**
718+
* Returns an array of unique values.
719+
* @param values Values to make unique.
720+
*/
721+
function _unique<T>(values: T[]): T[] {
722+
return Array.from(new Set(values))
723+
}

0 commit comments

Comments
 (0)