This repository has been archived by the owner on May 22, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 35
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: build Rust functions from source (#587)
* feat: build Go functions from source * feat: build Rust functions * feat: add support for Rust functions * chore: add tests * chore: remove unused test fixture * chore: remove outdated comment * chore: update comments * feat: check for Rust toolchain * chore: add test
- Loading branch information
1 parent
bd7206e
commit 5d48d64
Showing
13 changed files
with
1,017 additions
and
48 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
const { readFile } = require('fs') | ||
const { basename, join } = require('path') | ||
const { promisify } = require('util') | ||
|
||
const pReadFile = promisify(readFile) | ||
|
||
const tmp = require('tmp-promise') | ||
const toml = require('toml') | ||
|
||
const { lstat } = require('../../utils/fs') | ||
const { runCommand } = require('../../utils/shell') | ||
|
||
const { BUILD_TARGET, MANIFEST_NAME } = require('./constants') | ||
|
||
const build = async ({ srcDir }) => { | ||
// We compile the binary to a temporary directory so that we don't pollute | ||
// the user's functions directory. | ||
const { path: targetDirectory } = await tmp.dir() | ||
const functionName = basename(srcDir) | ||
|
||
try { | ||
await runCommand('cargo', ['build', '--target', BUILD_TARGET, '--release'], { | ||
cwd: srcDir, | ||
env: { | ||
CARGO_TARGET_DIR: targetDirectory, | ||
}, | ||
}) | ||
} catch (error) { | ||
const hasToolchain = await checkRustToolchain() | ||
|
||
if (!hasToolchain) { | ||
throw new Error( | ||
'There is no Rust toolchain installed. Visit https://ntl.fyi/missing-rust-toolchain for more information.', | ||
) | ||
} | ||
|
||
console.error(`Could not compile Rust function ${functionName}:\n`) | ||
|
||
throw error | ||
} | ||
|
||
// By default, the binary will have the same name as the crate and there's no | ||
// way to override it (https://github.com/rust-lang/cargo/issues/1706). We | ||
// must extract the crate name from the manifest and use it to form the path | ||
// to the binary. | ||
const manifest = await pReadFile(join(srcDir, MANIFEST_NAME)) | ||
const { package } = toml.parse(manifest) | ||
const binaryPath = join(targetDirectory, BUILD_TARGET, 'release', package.name) | ||
const stat = await lstat(binaryPath) | ||
|
||
return { | ||
path: binaryPath, | ||
stat, | ||
} | ||
} | ||
|
||
const checkRustToolchain = async () => { | ||
try { | ||
await runCommand('cargo', ['-V']) | ||
|
||
return true | ||
} catch (_) { | ||
return false | ||
} | ||
} | ||
|
||
module.exports = { build } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
const BUILD_TARGET = 'x86_64-unknown-linux-musl' | ||
const MANIFEST_NAME = 'Cargo.toml' | ||
|
||
module.exports = { BUILD_TARGET, MANIFEST_NAME } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
const { join, extname, dirname, basename } = require('path') | ||
|
||
const { RUNTIME_RUST } = require('../../utils/consts') | ||
const { cachedLstat, cachedReaddir } = require('../../utils/fs') | ||
const { zipBinary } = require('../../zip_binary') | ||
const { detectBinaryRuntime } = require('../detect_runtime') | ||
|
||
const { build } = require('./builder') | ||
const { MANIFEST_NAME } = require('./constants') | ||
|
||
const detectRustFunction = async ({ fsCache, path }) => { | ||
const stat = await cachedLstat(fsCache, path) | ||
|
||
if (!stat.isDirectory()) { | ||
return | ||
} | ||
|
||
const files = await cachedReaddir(fsCache, path) | ||
const hasCargoManifest = files.includes(MANIFEST_NAME) | ||
|
||
if (!hasCargoManifest) { | ||
return | ||
} | ||
|
||
const mainFilePath = join(path, 'src', 'main.rs') | ||
|
||
try { | ||
const mainFile = await cachedLstat(fsCache, mainFilePath) | ||
|
||
if (mainFile.isFile()) { | ||
return mainFilePath | ||
} | ||
} catch (_) { | ||
// no-op | ||
} | ||
} | ||
|
||
const findFunctionsInPaths = async function ({ featureFlags, fsCache, paths }) { | ||
const functions = await Promise.all( | ||
paths.map(async (path) => { | ||
const runtime = await detectBinaryRuntime({ fsCache, path }) | ||
|
||
if (runtime === RUNTIME_RUST) { | ||
return processBinary({ fsCache, path }) | ||
} | ||
|
||
if (featureFlags.buildRustSource !== true) { | ||
return | ||
} | ||
|
||
const rustSourceFile = await detectRustFunction({ fsCache, path }) | ||
|
||
if (rustSourceFile) { | ||
return processSource({ fsCache, mainFile: rustSourceFile, path }) | ||
} | ||
}), | ||
) | ||
|
||
return functions.filter(Boolean) | ||
} | ||
|
||
const processBinary = async ({ fsCache, path }) => { | ||
const stat = await cachedLstat(fsCache, path) | ||
const name = basename(path, extname(path)) | ||
|
||
return { | ||
mainFile: path, | ||
name, | ||
srcDir: dirname(path), | ||
srcPath: path, | ||
stat, | ||
} | ||
} | ||
|
||
const processSource = ({ mainFile, path }) => { | ||
const functionName = basename(path) | ||
|
||
return { | ||
mainFile, | ||
name: functionName, | ||
srcDir: path, | ||
srcPath: path, | ||
} | ||
} | ||
|
||
// The name of the binary inside the zip file must always be `bootstrap` | ||
// because they include the Lambda runtime, and that's the name that AWS | ||
// expects for those kind of functions. | ||
const zipFunction = async function ({ config, destFolder, filename, mainFile, runtime, srcDir, srcPath, stat }) { | ||
const destPath = join(destFolder, `${filename}.zip`) | ||
const isSource = extname(mainFile) === '.rs' | ||
const zipOptions = { | ||
destPath, | ||
filename: 'bootstrap', | ||
runtime, | ||
} | ||
|
||
// If we're building from source, we first need to build the source and zip | ||
// the resulting binary. Otherwise, we're dealing with a binary so we zip it | ||
// directly. | ||
if (isSource) { | ||
const { path: binaryPath, stat: binaryStat } = await build({ srcDir }) | ||
|
||
await zipBinary({ ...zipOptions, srcPath: binaryPath, stat: binaryStat }) | ||
} else { | ||
await zipBinary({ ...zipOptions, srcPath, stat }) | ||
} | ||
|
||
return { config, path: destPath } | ||
} | ||
|
||
module.exports = { findFunctionsInPaths, name: RUNTIME_RUST, zipFunction } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/target |
Oops, something went wrong.
5d48d64
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
⏱ Benchmark results
largeDepsEsbuild: 12.4s
largeDepsZisi: 1m 2.1s