Skip to content

Commit

Permalink
Restore local prebuilds feature (#137)
Browse files Browse the repository at this point in the history
Previously removed in #81 / a069253.
  • Loading branch information
jchook committed Apr 3, 2021
1 parent 43d581a commit dc4e5ea
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 40 deletions.
17 changes: 17 additions & 0 deletions README.md
Expand Up @@ -97,6 +97,23 @@ So if you are installing `leveldown@1.2.3` the resulting url will be:
http://overriden-host.com/overriden-path/v1.2.3/leveldown-v1.2.3-node-v57-win32-x64.tar.gz
```


#### Local prebuilds

If you want to use prebuilds from your local filesystem, you can use the `% your package name %_local_prebuilds` .npmrc variable to set a path to the folder containing prebuilds. For example:

```
leveldown_local_prebuilds=/path/to/prebuilds
```

This option will look directly in that folder for bundles created with `prebuild`, for example:

```
/path/to/prebuilds/leveldown-v1.2.3-node-v57-win32-x64.tar.gz
```

Non-absolute paths resolve relative to the directory of the package invoking prebuild-install, e.g. for nested dependencies.

### Cache

All prebuilt binaries are cached to minimize traffic. So first `prebuild-install` picks binaries from the cache and if no binary could be found, it will be downloaded. Depending on the environment, the cache folder is determined in the following order:
Expand Down
92 changes: 54 additions & 38 deletions download.js
Expand Up @@ -15,58 +15,74 @@ var mkdirp = require('mkdirp-classic')

function downloadPrebuild (downloadUrl, opts, cb) {
var cachedPrebuild = util.cachedPrebuild(downloadUrl)
var localPrebuild = util.localPrebuild(downloadUrl, opts)
var tempFile = util.tempFile(cachedPrebuild)
var log = opts.log || noop

ensureNpmCacheDir(function (err) {
if (err) return onerror(err)
if (opts.nolocal) return download()

log.info('looking for cached prebuild @', cachedPrebuild)
fs.access(cachedPrebuild, fs.R_OK | fs.W_OK, function (err) {
if (!(err && err.code === 'ENOENT')) {
log.info('found cached prebuild')
return unpack()
}
log.info('looking for local prebuild @', localPrebuild)
fs.access(localPrebuild, fs.R_OK | fs.W_OK, function (err) {
if (err && err.code === 'ENOENT') {
return download()
}

log.http('request', 'GET ' + downloadUrl)
var reqOpts = proxy({ url: downloadUrl }, opts)
log.info('found local prebuild')
cachedPrebuild = localPrebuild
unpack()
})

if (opts.token) {
reqOpts.url += '?access_token=' + opts.token
reqOpts.headers = {
'User-Agent': 'simple-get',
Accept: 'application/octet-stream'
function download () {
ensureNpmCacheDir(function (err) {
if (err) return onerror(err)

log.info('looking for cached prebuild @', cachedPrebuild)
fs.access(cachedPrebuild, fs.R_OK | fs.W_OK, function (err) {
if (!(err && err.code === 'ENOENT')) {
log.info('found cached prebuild')
return unpack()
}
}

var req = get(reqOpts, function (err, res) {
if (err) return onerror(err)
log.http(res.statusCode, downloadUrl)
if (res.statusCode !== 200) return onerror()
mkdirp(util.prebuildCache(), function () {
log.info('downloading to @', tempFile)
pump(res, fs.createWriteStream(tempFile), function (err) {
if (err) return onerror(err)
fs.rename(tempFile, cachedPrebuild, function (err) {
if (err) return cb(err)
log.info('renaming to @', cachedPrebuild)
unpack()
log.http('request', 'GET ' + downloadUrl)
var reqOpts = proxy({ url: downloadUrl }, opts)

if (opts.token) {
reqOpts.url += '?access_token=' + opts.token
reqOpts.headers = {
'User-Agent': 'simple-get',
Accept: 'application/octet-stream'
}
}

var req = get(reqOpts, function (err, res) {
if (err) return onerror(err)
log.http(res.statusCode, downloadUrl)
if (res.statusCode !== 200) return onerror()
mkdirp(util.prebuildCache(), function () {
log.info('downloading to @', tempFile)
pump(res, fs.createWriteStream(tempFile), function (err) {
if (err) return onerror(err)
fs.rename(tempFile, cachedPrebuild, function (err) {
if (err) return cb(err)
log.info('renaming to @', cachedPrebuild)
unpack()
})
})
})
})
})

req.setTimeout(30 * 1000, function () {
req.abort()
req.setTimeout(30 * 1000, function () {
req.abort()
})
})
})

function onerror (err) {
fs.unlink(tempFile, function () {
cb(err || error.noPrebuilts(opts))
})
}
})
function onerror (err) {
fs.unlink(tempFile, function () {
cb(err || error.noPrebuilts(opts))
})
}
})
}

function unpack () {
var binaryName
Expand Down
1 change: 1 addition & 0 deletions rc.js
Expand Up @@ -25,6 +25,7 @@ module.exports = function (pkg) {
proxy: env.npm_config_proxy || env['http_proxy'] || env['HTTP_PROXY'],
'https-proxy': env.npm_config_https_proxy || env['https_proxy'] || env['HTTPS_PROXY'],
'local-address': env.npm_config_local_address,
'local-prebuilds': 'prebuilds',
'tag-prefix': 'v',
download: env.npm_config_download
}, minimist(process.argv, {
Expand Down
40 changes: 39 additions & 1 deletion test/download-test.js
Expand Up @@ -89,6 +89,43 @@ test('cached prebuild', function (t) {
})
})

test('local prebuild', function (t) {
t.plan(6)
rm.sync(build)

var opts = getOpts()
var downloadUrl = util.getDownloadUrl(opts)
var cachedPrebuild = util.cachedPrebuild(downloadUrl)
var localPrebuild = util.localPrebuild(downloadUrl, opts)

t.ok(fs.existsSync(cachedPrebuild), 'cached prebuild exists')

// fs.copyFileSync() not available before Node 8.5
fs.writeFileSync(localPrebuild, fs.readFileSync(cachedPrebuild))

var _createWriteStream = fs.createWriteStream
fs.createWriteStream = function (path) {
t.ok(/\.node$/i.test(path), 'this is the unpacked file')
return _createWriteStream(path)
}

var _createReadStream = fs.createReadStream
fs.createReadStream = function (path) {
t.equal(path, localPrebuild, 'createReadStream called for localPrebuild')
return _createReadStream(path)
}

t.equal(fs.existsSync(build), false, 'no build folder')

download(downloadUrl, opts, function (err) {
t.error(err, 'no error')
t.equal(fs.existsSync(unpacked), true, unpacked + ' should exist')
fs.createReadStream = _createReadStream
fs.createWriteStream = _createWriteStream
rm.sync(localPrebuild)
})
})

test('non existing host should fail with no dangling temp file', function (t) {
t.plan(3)

Expand Down Expand Up @@ -225,6 +262,7 @@ function getOpts () {
platform: process.platform,
arch: process.arch,
path: __dirname,
'tag-prefix': 'v'
'tag-prefix': 'v',
'local-prebuilds': __dirname
}
}
26 changes: 26 additions & 0 deletions test/util-test.js
Expand Up @@ -190,3 +190,29 @@ test('getDownloadUrl() expands template to correct values', function (t) {
t.equal(url3, url2, 'scope does not matter for download url')
t.end()
})

test('localPrebuild', function (t) {
var envProp = 'npm_config_a_native_module_local_prebuilds'
var basename = 'a-native-module-v1.4.0-node-v14-linux-x64.tar.gz'
var url = 'https://github.com/a-native-module/a-native-module/releases/download/v1.4.0/' + basename
var o1 = {
pkg: {
name: 'a-native-module'
}
}
var path1 = util.localPrebuild(url, o1)
t.equal(path1, path.join('prebuilds', basename))
var o2 = {
pkg: {
name: 'a-native-module'
},
'local-prebuilds': path.join('', 'path', 'to', 'prebuilds')
}
var path2 = util.localPrebuild(url, o2)
t.equal(path2, path.join(o2['local-prebuilds'], basename), 'opts overrides default')
var envPrefix = path.join('', 'overriden', 'path', 'to', 'prebuilds')
process.env[envProp] = envPrefix
var path3 = util.localPrebuild(url, o2)
t.equal(path3, path.join(envPrefix, basename), 'env overrides opts')
t.end()
})
13 changes: 12 additions & 1 deletion util.js
Expand Up @@ -60,8 +60,12 @@ function urlTemplate (opts) {
return github(opts.pkg) + '/releases/download/{tag_prefix}{version}/' + packageName
}

function getEnvPrefix (pkgName) {
return 'npm_config_' + (pkgName || '').replace(/[^a-zA-Z0-9]/g, '_')
}

function getHostMirrorUrl (opts) {
var propName = 'npm_config_' + (opts.pkg.name || '').replace(/[^a-zA-Z0-9]/g, '_') + '_binary_host'
var propName = getEnvPrefix(opts.pkg.name) + '_binary_host'
return process.env[propName] || process.env[propName + '_mirror']
}

Expand Down Expand Up @@ -105,11 +109,18 @@ function packageOrigin (env, pkg) {
}
}

function localPrebuild (url, opts) {
var propName = getEnvPrefix(opts.pkg.name) + '_local_prebuilds'
var prefix = process.env[propName] || opts['local-prebuilds'] || 'prebuilds'
return path.join(prefix, path.basename(url))
}

exports.getDownloadUrl = getDownloadUrl
exports.getApiUrl = getApiUrl
exports.getAssetUrl = getAssetUrl
exports.urlTemplate = urlTemplate
exports.cachedPrebuild = cachedPrebuild
exports.localPrebuild = localPrebuild
exports.prebuildCache = prebuildCache
exports.npmCache = npmCache
exports.tempFile = tempFile
Expand Down

0 comments on commit dc4e5ea

Please sign in to comment.