Replace glob with custom fs-utils

- On windows the glob library wouldn't work, let's
   create our own file discovery mechanism using readdir
- This fixes #395
This commit is contained in:
Jimver
2025-11-02 15:48:58 +01:00
parent 1a3c14e268
commit 59ba6fa3e9
8 changed files with 485 additions and 1573 deletions
+56
View File
@@ -0,0 +1,56 @@
import fs from 'fs'
import os from 'os'
import path from 'path'
import { getFilesRecursive, filterReadable } from '../src/fs-utils.js'
describe('fs-utils', () => {
const tmpRoot = path.join(os.tmpdir(), `cuda-toolkit-test-${Date.now()}`)
beforeAll(async () => {
await fs.promises.mkdir(tmpRoot, { recursive: true })
// create files and nested directories
await fs.promises.mkdir(path.join(tmpRoot, 'subdir'))
await fs.promises.writeFile(path.join(tmpRoot, 'a.txt'), 'a')
await fs.promises.writeFile(path.join(tmpRoot, 'subdir', 'b.txt'), 'b')
// create a non-readable file (if platform supports chmod)
try {
const p = path.join(tmpRoot, 'noaccess.txt')
await fs.promises.writeFile(p, 'x')
await fs.promises.chmod(p, 0o000)
} catch {
// ignore chmod failures on platforms that don't support it
}
})
afterAll(async () => {
try {
const p = path.join(tmpRoot, 'noaccess.txt')
await fs.promises.chmod(p, 0o644)
} catch {
// ignore
}
await fs.promises.rm(tmpRoot, { recursive: true, force: true })
})
test('getFilesRecursive returns all files under directory', async () => {
const files = await getFilesRecursive(tmpRoot)
const normalizedNames = files.map((f) => path.relative(tmpRoot, f)).sort()
expect(normalizedNames).toEqual(
['a.txt', 'noaccess.txt', path.join('subdir', 'b.txt')].sort()
)
})
test('filterReadable filters out non-readable or missing files', async () => {
const a = path.join(tmpRoot, 'a.txt')
const nosuch = path.join(tmpRoot, 'does-not-exist.txt')
const noaccess = path.join(tmpRoot, 'noaccess.txt')
const result = await filterReadable([a, nosuch, noaccess])
expect(result).toContain(a)
expect(result).not.toContain(nosuch)
})
})
+1 -1
View File
@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="116" height="20" role="img" aria-label="Coverage: 18.44%"><title>Coverage: 18.44%</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="116" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="63" height="20" fill="#555"/><rect x="63" width="53" height="20" fill="#e05d44"/><rect width="116" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="325" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="530">Coverage</text><text x="325" y="140" transform="scale(.1)" fill="#fff" textLength="530">Coverage</text><text aria-hidden="true" x="885" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="430">18.44%</text><text x="885" y="140" transform="scale(.1)" fill="#fff" textLength="430">18.44%</text></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="116" height="20" role="img" aria-label="Coverage: 22.86%"><title>Coverage: 22.86%</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="116" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="63" height="20" fill="#555"/><rect x="63" width="53" height="20" fill="#e05d44"/><rect width="116" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="325" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="530">Coverage</text><text x="325" y="140" transform="scale(.1)" fill="#fff" textLength="530">Coverage</text><text aria-hidden="true" x="885" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="430">22.86%</text><text x="885" y="140" transform="scale(.1)" fill="#fff" textLength="430">22.86%</text></g></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Generated Vendored
+354 -1562
View File
File diff suppressed because it is too large Load Diff
Generated Vendored
+1 -1
View File
File diff suppressed because one or more lines are too long
+20
View File
@@ -483,6 +483,7 @@
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz",
"integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==",
"dev": true,
"peer": true,
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.27.1",
@@ -1613,6 +1614,7 @@
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.1.tgz",
"integrity": "sha512-dKYCMuPO1bmrpuogcjQ8z7ICCH3FP6WmxpwC03yjzGfZhj9fTJg6+bS1+UAplekbN2C+M61UNllGOOoAfGCrdQ==",
"dev": true,
"peer": true,
"dependencies": {
"@octokit/auth-token": "^4.0.0",
"@octokit/graphql": "^7.1.0",
@@ -1741,6 +1743,7 @@
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.5.tgz",
"integrity": "sha512-vvmsN0r7rguA+FySiCsbaTTobSftpIDIpPW81trAmsv9TGxg3YCujAxRYp/Uy8xmDgYCzzgulG62H7KYUFmeIg==",
"dev": true,
"peer": true,
"dependencies": {
"@octokit/auth-token": "^5.0.0",
"@octokit/graphql": "^8.2.2",
@@ -2711,6 +2714,7 @@
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz",
"integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==",
"peer": true,
"dependencies": {
"@octokit/auth-token": "^2.4.4",
"@octokit/graphql": "^4.5.8",
@@ -2874,6 +2878,7 @@
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.5.tgz",
"integrity": "sha512-vvmsN0r7rguA+FySiCsbaTTobSftpIDIpPW81trAmsv9TGxg3YCujAxRYp/Uy8xmDgYCzzgulG62H7KYUFmeIg==",
"dev": true,
"peer": true,
"dependencies": {
"@octokit/auth-token": "^5.0.0",
"@octokit/graphql": "^8.2.2",
@@ -3165,6 +3170,7 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"dev": true,
"peer": true,
"engines": {
"node": ">=12"
},
@@ -3723,6 +3729,7 @@
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.33.0.tgz",
"integrity": "sha512-CACyQuqSHt7ma3Ns601xykeBK/rDeZa3w6IS6UtMQbixO5DWy+8TilKkviGDH6jtWCo8FGRKEK5cLLkPvEammQ==",
"dev": true,
"peer": true,
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.33.0",
@@ -3752,6 +3759,7 @@
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.33.0.tgz",
"integrity": "sha512-JaehZvf6m0yqYp34+RVnihBAChkqeH+tqqhS0GuX1qgPpwLvmTPheKEs6OeCK6hVJgXZHJ2vbjnC9j119auStQ==",
"dev": true,
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.33.0",
"@typescript-eslint/types": "8.33.0",
@@ -4232,6 +4240,7 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
"dev": true,
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -4744,6 +4753,7 @@
"url": "https://github.com/sponsors/ai"
}
],
"peer": true,
"dependencies": {
"caniuse-lite": "^1.0.30001718",
"electron-to-chromium": "^1.5.160",
@@ -5697,6 +5707,7 @@
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.28.0.tgz",
"integrity": "sha512-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ==",
"dev": true,
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -5757,6 +5768,7 @@
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.5.tgz",
"integrity": "sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==",
"dev": true,
"peer": true,
"bin": {
"eslint-config-prettier": "bin/cli.js"
},
@@ -5876,6 +5888,7 @@
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz",
"integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==",
"dev": true,
"peer": true,
"dependencies": {
"@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.8",
@@ -7551,6 +7564,7 @@
"resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz",
"integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==",
"dev": true,
"peer": true,
"dependencies": {
"@jest/core": "^29.7.0",
"@jest/types": "^29.6.3",
@@ -9071,6 +9085,7 @@
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
"dev": true,
"peer": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
@@ -9305,6 +9320,7 @@
"integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==",
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
"dev": true,
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
@@ -9830,6 +9846,7 @@
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.1.tgz",
"integrity": "sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw==",
"dev": true,
"peer": true,
"dependencies": {
"@types/estree": "1.0.7"
},
@@ -10574,6 +10591,7 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"dev": true,
"peer": true,
"engines": {
"node": ">=12"
},
@@ -10870,6 +10888,7 @@
"version": "5.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -10924,6 +10943,7 @@
"integrity": "sha512-2zsXwyOXmCX9nGz4vhtZRYhe30V78heAv+KDc21A/KMdovGHbZcixeD5JHEF0DrFXzdytwuzYclcPbvp8A3Jlw==",
"dev": true,
"hasInstallScript": true,
"peer": true,
"dependencies": {
"napi-postinstall": "^0.2.2"
},
+12 -5
View File
@@ -1,6 +1,5 @@
import * as cache from '@actions/cache'
import * as core from '@actions/core'
import * as glob from '@actions/glob'
import * as tc from '@actions/tool-cache'
import * as io from '@actions/io'
import { OSType, getOs, getRelease } from './platform.js'
@@ -11,6 +10,7 @@ import { WindowsLinks } from './links/windows-links.js'
import fs from 'fs'
import { getLinks } from './links/get-links.js'
import { getArch } from './arch.js'
import { getFilesRecursive } from './fs-utils.js'
// Download helper which returns the installer executable and caches it for next runs
export async function download(
@@ -88,6 +88,15 @@ export async function download(
core.debug(`Copying ${destFilePath} to ${cacheDirectory}`)
await io.mkdirP(cacheDirectory)
await io.mv(destFilePath, cacheDirectory)
// Log full path and files in cache directory
const filesInCacheDir = await getFilesRecursive(cacheDirectory)
core.debug(`Files in GitHub cache directory ${cacheDirectory}:`)
for (const f of filesInCacheDir) {
core.debug(f)
}
// Log absolute path
const absoluteCacheDir = await fs.promises.realpath(cacheDirectory)
core.debug(`Absolute path of cache directory: ${absoluteCacheDir}`)
// Save cache directory to GitHub cache
const cacheId = await cache.saveCache([cacheDirectory], cacheKey)
if (cacheId !== -1) {
@@ -105,10 +114,8 @@ export async function download(
core.debug(`Executable path ${executableDirectory}`)
// String with full executable path
let fullExecutablePath: string
// Get list of files in tool cache
const filesInCache = await (
await glob.create(`${executableDirectory}/**.*`)
).glob()
// Get list of files in tool cache using readdir recursive helper
const filesInCache = await getFilesRecursive(executableDirectory)
core.debug(`Files in tool cache:`)
for (const f of filesInCache) {
core.debug(f)
+38
View File
@@ -0,0 +1,38 @@
import fs from 'fs'
import path from 'path'
import * as core from '@actions/core'
export async function getFilesRecursive(dir: string): Promise<string[]> {
const results: string[] = []
async function walk(current: string) {
const entries = await fs.promises.readdir(current, { withFileTypes: true })
for (const entry of entries) {
const fullPath = path.join(current, entry.name)
if (entry.isDirectory()) {
await walk(fullPath)
} else if (entry.isFile()) {
results.push(fullPath)
}
}
}
try {
await walk(dir)
} catch (e) {
core.debug(`Error reading files from ${dir}: ${e}`)
return []
}
return results
}
export async function filterReadable(paths: string[]): Promise<string[]> {
const readable: string[] = []
for (const path of paths) {
try {
await fs.promises.access(path, fs.constants.R_OK)
readable.push(path)
} catch (e) {
core.debug(`Path not readable: ${path} - ${e}`)
}
}
return readable
}
+3 -4
View File
@@ -1,6 +1,6 @@
import { DefaultArtifactClient } from '@actions/artifact'
import * as core from '@actions/core'
import * as glob from '@actions/glob'
import { filterReadable } from './fs-utils.js'
import { OSType, getOs, getRelease } from './platform.js'
import { SemVer } from 'semver'
import { exec } from '@actions/exec'
@@ -76,9 +76,8 @@ export async function install(
const osRelease = await getRelease()
if (osType === OSType.linux) {
const artifactName = `cuda-install-${osType}-${osRelease}-${method}-${logFileSuffix}`
const patterns = ['/var/log/cuda-installer.log']
const globber = await glob.create(patterns.join('\n'))
const files = await globber.glob()
const candidates = ['/var/log/cuda-installer.log']
const files = await filterReadable(candidates)
const username = os.userInfo().username
if (files.length > 0) {
// If any of the files is not readable without root permissions, the upload will fail, so we need to