Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions scripts/vcpkg-fetch-asset.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// this should not use any third party dependencies! Only native Node.js modules!
//
// vcpkg x-script asset fetcher.
//
// Why this exists: the Windows source build pulls some dependencies (e.g.
// vcpkg's `gsasl` port -> gsasl-<ver>.tar.gz) exclusively from the GNU FTP
// network (ftpmirror.gnu.org / ftp.gnu.org). That network is intermittently
// unreachable and, when it times out, the whole native build fails. The same
// files are mirrored verbatim across the GNU mirror network, so we redirect
// GNU-FTP asset URLs to a reachable mirror and fall back to the original URL
// for every other asset. vcpkg verifies the SHA512 of whatever is produced,
// so swapping to a mirror cannot substitute a different file.
//
// Wired in from vcpkg-setup.js via:
// X_VCPKG_ASSET_SOURCES="x-script,node <this> {url} {sha512} {dst}"
// vcpkg substitutes {url}/{sha512}/{dst} when it invokes the command.

const fs = require('fs')
const path = require('path')
const https = require('https')

const [, , url, , dst] = process.argv

if (!url || !dst) {
console.error('vcpkg-fetch-asset: usage: <url> <sha512> <dst>')
process.exit(2)
}

// A reachable GNU mirror that serves the identical files under the same
// /gnu/ layout. Overridable so consumers behind a proxy can point elsewhere.
const GNU_MIRROR =
process.env.NODE_LIBCURL_GNU_MIRROR || 'https://mirrors.kernel.org'
const DEAD_GNU_HOSTS = ['ftpmirror.gnu.org', 'ftp.gnu.org']

function mirrorFor(originalUrl) {
try {
const u = new URL(originalUrl)
if (DEAD_GNU_HOSTS.includes(u.hostname) && u.pathname.startsWith('/gnu/')) {
return `${GNU_MIRROR}${u.pathname}`
}
} catch {
// fall through to the original URL
}

return null
}

function download(fromUrl, toPath, redirects = 0) {
return new Promise((resolve, reject) => {
if (redirects > 5) {
return reject(new Error('too many redirects'))
}

fs.mkdirSync(path.dirname(toPath), { recursive: true })

https
.get(fromUrl, (res) => {
if (
res.statusCode >= 300 &&
res.statusCode < 400 &&
res.headers.location
) {
res.resume()
const next = new URL(res.headers.location, fromUrl).toString()

return resolve(download(next, toPath, redirects + 1))
}

if (res.statusCode !== 200) {
res.resume()

return reject(new Error(`HTTP ${res.statusCode} for ${fromUrl}`))
}

const file = fs.createWriteStream(toPath)
res.pipe(file)
file.on('finish', () => file.close(() => resolve()))
file.on('error', reject)
})
.on('error', reject)
})
}

async function fetchAsset() {
const candidates = []
const mirror = mirrorFor(url)
if (mirror) {
candidates.push(mirror)
}
candidates.push(url)

for (const candidate of candidates) {
try {
console.log(`vcpkg-fetch-asset: downloading ${candidate}`)
await download(candidate, dst)

// success; vcpkg verifies the SHA512 of the result afterwards
return
} catch (error) {
console.error(`vcpkg-fetch-asset: ${candidate} failed: ${error.message}`)
}
}

console.error(`vcpkg-fetch-asset: all sources failed for ${url}`)
process.exit(1)
}

fetchAsset()
18 changes: 18 additions & 0 deletions scripts/vcpkg-setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,27 @@ const {

const modulePackageJson = require('../package.json')

// Route vcpkg's asset downloads through our fetcher so that dependencies only
// published on the (intermittently unreachable) GNU FTP network — e.g. gsasl —
// fall back to a reachable GNU mirror. Appended to any asset source the
// consumer already configured, so their cache still takes precedence. vcpkg
// still verifies each asset's SHA512, so this cannot swap in a different file.
//
// vcpkg launches the x-script command with CreateProcessW (no shell) and runs
// it inside its own build environment where PATH is reset, so a bare `node`
// isn't found (CreateProcessW fails with 2). Use the absolute path to the
// current Node executable instead. Both paths are quoted for spaces.
const assetScript = path.join(__dirname, 'vcpkg-fetch-asset.js')
const assetSource = `x-script,"${process.execPath}" "${assetScript}" {url} {sha512} {dst}`
const existingAssetSources = process.env.X_VCPKG_ASSET_SOURCES
const X_VCPKG_ASSET_SOURCES = existingAssetSources
? `${existingAssetSources};${assetSource}`
: assetSource

const commonEnv = {
...process.env,
VCPKG_DISABLE_METRICS: '1',
X_VCPKG_ASSET_SOURCES,
}

async function setupVcpkg() {
Expand Down
Loading