Skip to content

Commit

Permalink
v0.3.0: Add cargo check and other updates (#2)
Browse files Browse the repository at this point in the history
* Add `cargo check` and other updates

* update `setupLogging` usage
* Add `cargo check` to validate Rust code
* Make console log messages a bit cleaner

* Minor code refactor

* Add a CHANGELOG file

* Update CHANGELOG

* Update docs

* Style fix

* Update docs

* Update docs

* Bump version
  • Loading branch information
cb-rnag committed Feb 23, 2022
1 parent 4fa61ac commit b964cac
Show file tree
Hide file tree
Showing 9 changed files with 235 additions and 37 deletions.
3 changes: 3 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,6 @@ test/

# exclude examples
cdk-examples

# exclude changelog (can always be found on GitHub)
CHANGELOG.md
42 changes: 42 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Changelog

This project follows semantic versioning.

Possible header types:

- `Features` for any new features added, or for backwards-compatible
changes to existing functionality.
- `Bug Fixes` for any bug fixes.
- `Breaking Changes` for any backwards-incompatible changes.

## [Unreleased]

## v0.3.0 (2022-02-23)

### Features

- Add support to run `cargo check` by default before building Rust code.
- Clean up console log messages that get printed out so they are a bit nicer.
- Change the value that is set for the `RUST_LOG` environment variable when `setupLogging` is enabled.
- The format is now `warn,module_name=debug` instead of `module_name=trace`.
- Add below global _Settings_:
- `RUN_CARGO_CHECK`
- `DEFAULT_LOG_LEVEL`
- `MODULE_LOG_LEVEL`

## v0.2.0 (2022-02-19)

### Features

- Add docs on _Rust Function Properties_ and _Settings_.
- Add new Rust Function Properties such as `setupLogging`.
- Update Readme docs.
- Add `cdk-examples/`.
- Reduce overall package size when publishing to `npm`.
- Some other stuff that I don't remember.

## v0.1.0 (2022-02-17)

- Initial Release on [npmjs.com] :tada:

[npmjs.com]: https://www.npmjs.com/package/rust.aws-cdk-lambda
60 changes: 43 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
![rust.aws-cdk-lambda: Stable](https://img.shields.io/badge/rust.aws--cdk--lambda-stable-success.svg?style=for-the-badge)
[![npm](https://img.shields.io/npm/v/rust.aws-cdk-lambda?style=for-the-badge)](https://www.npmjs.com/package/rust.aws-cdk-lambda)

> **This is unofficial CDK library based on the [Amazon Lambda Node.js] and [aws-lambda-rust] Libraries.**
> **This is an unofficial CDK library based on the [Amazon Lambda Node.js] and [aws-lambda-rust] Libraries.**
>
> _It's intended for use with the new **[AWS CDK v2]**_.
Expand All @@ -28,16 +28,10 @@ in the [official AWS documentation].
[`cross`]: https://github.com/rust-embedded/cross
[official aws documentation]: https://docs.aws.amazon.com/sdk-for-rust/latest/dg/lambda.html

## Rust Fuction
## Rust Function

The `RustFunction` construct creates a Lambda function with automatic bundling and compilation of Rust code.

## Examples

You can find sample CDK apps built using _Typescript_ or _Node.js_ in the [cdk-examples/] folder of the GitHub project repo.

[cdk-examples/]: https://github.com/rnag/rust.aws-cdk-lambda/tree/main/cdk-examples

## Getting Started

1. Install the [npm](https://nodejs.org/) package:
Expand All @@ -62,6 +56,12 @@ Finally, ensure you have [Docker] installed and running, as it will be used by `

[`cargo`]: https://www.rust-lang.org/

## Examples

You can find sample CDK apps built using _Typescript_ or _Node.js_ in the [cdk-examples/] folder of the GitHub project repo.

[cdk-examples/]: https://github.com/rnag/rust.aws-cdk-lambda/tree/main/cdk-examples

## Usage

First, import the construct:
Expand All @@ -80,6 +80,15 @@ By default, the construct will use directory where `cdk` was invoked as director

If no `bin` or `package` argument is passed in, it will default to the package name as defined in the main `Cargo.toml`.

That is, the above usage should work for a project structure that looks like this:

```plaintext
.
├── Cargo.toml
└── src
    └── main.rs
```

Alternatively, `directory` and `bin` can be specified:

```ts
Expand All @@ -92,6 +101,19 @@ new RustFunction(this, 'MyLambdaFunction', {

All other properties of `lambda.Function` are supported, see also the [AWS Lambda construct library](https://github.com/aws/aws-cdk/tree/master/packages/%40aws-cdk/aws-lambda).

## How It Works

When bundling the code, the `RustFunction` runs the following steps in order:

- First it runs `cargo check` to confirm that the Rust code can compile.
Note that this is an optional step, and [can be disabled](#settings) as mentioned below.

- Next it calls `cross build`, and passes in the `--release` and `--target` flags, so it compiles for a Lambda environment - which defaults to the **x86_64-unknown-linux-musl** target, as mentioned above.

- Finally, it copies the release app binary from the `target/` folder to a file named `bootstrap`, which the Lambda custom runtime environment looks for. It adds this new file under the _build directory_, which defaults to a `.build/` folder under the directory where `cdk` was invoked.

- The directory path to the executable is then passed in to `lambda.Code.fromAsset`, which creates a _zip file_ from the release binary asset.

## Multiple Rust Lambdas

Assuming you have a CDK project with more than one Rust
Expand All @@ -111,7 +133,7 @@ Suppose your project layout looks like this:
    └── lambda2.rs
```

Here's one way to deploy that:
Here's one way to deploy that via `cdk`:

```ts
new RustFunction(this, 'my-function-1', {
Expand Down Expand Up @@ -184,14 +206,15 @@ You can find a more complete project structure in the [rust-workspaces/] CDK sam

Below lists some commonly used properties you can pass in to the `RustFunction` construct.

| Name | Description |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `target` | Build target to cross-compile to. Defaults to the target for Linux MUSL, `x86_64-unknown-linux-musl`. |
| `directory` | Entry point where the project's main `Cargo.toml` is located. By default, the construct will use directory where `cdk` was invoked as the directory where Cargo files are located. |
| `buildDir` | Default Build directory, which defaults to a `.build` folder under the project's root directory. |
| `bin` | Executable name to pass to `--bin` |
| `package` | Workspace package name to pass to `--package` |
| `setupLogging` | Determines whether we want to set up [library logging](https://rust-lang-nursery.github.io/rust-cookbook/development_tools/debugging/config_log.html) - i.e. set the `RUST_LOG` environment variable - for the lambda function. |
| Name | Description |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `target` | Build target to cross-compile to. Defaults to the target for Linux MUSL, `x86_64-unknown-linux-musl`. |
| `directory` | Entry point where the project's main `Cargo.toml` is located. By default, the construct will use directory where `cdk` was invoked as the directory where Cargo files are located. |
| `buildDir` | Default Build directory, which defaults to a `.build` folder under the project's root directory. |
| `bin` | Executable name to pass to `--bin` |
| `package` | Workspace package name to pass to `--package` |
| `setupLogging` | Determines whether we want to set up [library logging](https://rust-lang-nursery.github.io/rust-cookbook/development_tools/debugging/config_log.html) - i.e. set the `RUST_LOG` environment variable - for the lambda function.<br><br>The format defaults to `warn,module_name=debug`, which means that the default log level is `warn`, and the executable or library's log level is `debug`. |
| |

## Settings

Expand All @@ -206,4 +229,7 @@ Below are some useful _global_ defaults which can be set for all Rust Lambda Fun
| Name | Description |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `BUILD_INDIVIDUALLY` | Whether to build each executable individually, either via `--bin` or `--package`. |
| `RUN_CARGO_CHECK` | Whether to run `cargo check` to validate Rust code before building it with `cross`. Defaults to _true_. |
| `DEFAULT_LOG_LEVEL` | Log Level for non-module libraries. Note that this value is only used when `RustFunctionProps.setupLogging` is enabled. Defaults to `warn`. |
| `MODULE_LOG_LEVEL` | Log Level for a module (i.e. the executable). Note that this value is only used when `RustFunctionProps.setupLogging` is enabled. Defaults to `debug`. |
| `workspace_dir` | Sets the root workspace directory. By default, the workspace directory is assumed to be the directory where `cdk` was invoked.<br><br>This directory should contain at the minimum a `Cargo.toml` file which defines the workspace members. |
26 changes: 26 additions & 0 deletions cdk-examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,29 @@ For example, using:
```shell
echo '\n# Rust lambda build directory\n.build' >> .gitignore
```

## Local Development and Testing

In case it's desirable to uncomment the following import in the `lib/` folder of a sample CDK app, for local testing purposes:

```ts
import { RustFunction, Settings } from '../../../lib';
```

You may then potentially run into some import errors when deploying the stack via `cdk`.

To fix that, run this command from both the project root folder `$root`, and compare the output when running it from within `$root/cdk-examples/my-app`:

```shell
npm list
```

To resolve the import issues, you'll need to ensure that certain package versions are the same between the two directories.

For example, here are the important ones you'd need to verify:

```plaintext
├── aws-cdk-lib@2.12.0
├── aws-cdk@2.12.0
├── constructs@10.0.65
```
98 changes: 87 additions & 11 deletions lib/build.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { spawnSync } from 'child_process';
import * as fs from 'fs';
import * as path from 'path';
import { performance } from 'perf_hooks';
import { Settings } from '.';
import { logTime } from './utils';

let _builtWorkspaces = false,
_builtBinaries = false;
_builtBinaries = false,
_ranCargoCheck = false;

export interface BaseBuildProps {
/**
Expand Down Expand Up @@ -87,10 +90,35 @@ export function build(options: BuildOptions): void {
}
}

let targetReleaseDir = path.join(
options.entry,
'target',
options.target,
'release'
);
const releaseDirExists = fs.existsSync(targetReleaseDir);

if (shouldCompile) {
console.log(
`🍺 Building Rust code with \`cross\`. This may take a few minutes...`
);
// Run `cargo check` on an initial time, if needed
if (Settings.RUN_CARGO_CHECK && !_ranCargoCheck) {
_ranCargoCheck = true;
checkCode(options, releaseDirExists);
}

if (releaseDirExists) {
console.log(`🍺 Building Rust code...`);
} else {
// The `release` directory doesn't exist for the specified
// target. This is most likely an initial run, so `cross` will
// take much longer than usual to cross-compile the code.
//
// Print out an informative message that the `build` step is
// expected to take longer than usual.
console.log(
`🍺 Building Rust code with \`cross\`. This may take a few minutes...`
);
}

const args: string[] = [
'build',
'--release',
Expand All @@ -112,13 +140,7 @@ export function build(options: BuildOptions): void {
}
}

let from = path.join(
options.entry,
'target',
options.target,
'release',
outputName
);
let from = path.join(targetReleaseDir, outputName);
let to = path.join(options.outDir, 'bootstrap');

fs.copyFileSync(from, to);
Expand All @@ -128,3 +150,57 @@ export function build(options: BuildOptions): void {
);
}
}

/**
* Validate code with `cargo check`
*
* Note: this step is optional, and can be disabled with
* `Settings.RUN_CARGO_CHECK` as needed.
*/
export function checkCode(
options: BuildOptions,
releaseDirExists: boolean
) {
if (!releaseDirExists) {
// The `release` directory doesn't exist for the specified
// target. This is most likely an initial run, so `cargo` will
// take much longer than usual to check the code.
//
// Print out an informative message that the `validate` step is
// expected to take longer than usual.
console.log(
`🧪 Checking code with \`cargo\`. This may take a few minutes...`
);
}

let start = performance.now();

const args: string[] = [
'check',
'--release',
'--target',
options.target,
'--color',
'always',
];

const check = spawnSync('cargo', args, {
cwd: options.entry,
});

if (check.error) {
throw check.error;
}

if (check.status !== 0) {
console.error(check.stderr.toString().trim());
console.error(`💥 Run \`cargo check\` errored.`);
process.exit(1);
// Note: I don't want to raise an error here, as that will clutter the
// output with the stack trace here. But maybe, there's a way to
// suppress that?
// throw new Error(check.stderr.toString().trim());
}

logTime(start, `✅ Run \`cargo check\``);
}
10 changes: 2 additions & 8 deletions lib/function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { performance } from 'perf_hooks';
import * as toml from 'toml';
import { Settings } from '.';
import { BaseBuildProps, build } from './build';
import { logTime } from './utils';

/**
* Properties for a RustFunction
Expand Down Expand Up @@ -114,7 +115,7 @@ export class RustFunction extends lambda.Function {
// coincidentally how Rust imports are done.
let underscoredName = executable.split('-').join('_');
// Set the `RUST_LOG` environment variable.
lambdaEnv.RUST_LOG = `${underscoredName}=trace`;
lambdaEnv.RUST_LOG = `${Settings.DEFAULT_LOG_LEVEL},${underscoredName}=${Settings.MODULE_LOG_LEVEL}`;
}

super(scope, id, {
Expand All @@ -133,13 +134,6 @@ function createDirectory(dir: string) {
}
}

function logTime(start: number, message: string) {
const elapsedSec = ((performance.now() - start) / 1000).toFixed(
2
);
console.log(`${message}: ${elapsedSec}s`);
}

function getPackageName(entry: string) {
const tomlFilePath = path.join(entry, 'Cargo.toml');
// console.trace(`Parsing TOML file at ${tomlFilePath}`);
Expand Down
23 changes: 23 additions & 0 deletions lib/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,29 @@ export const Settings = {
*/
BUILD_INDIVIDUALLY: false,

/**
* Whether to run `cargo check` to validate Rust code before building it with `cross`.
*
* Defaults to true.
*/
RUN_CARGO_CHECK: true,

/**
* Default Log Level, for non-module libraries.
*
* Note: this value is only used when `RustFunctionProps.setupLogging`
* is enabled.
*/
DEFAULT_LOG_LEVEL: 'warn',

/**
* Default Log Level for a module (i.e. the executable)
*
* Note: this value is only used when `RustFunctionProps.setupLogging`
* is enabled.
*/
MODULE_LOG_LEVEL: 'debug',

/**
* Sets the root workspace directory. By default, the workspace directory
* is assumed to be the directory where `cdk` was invoked.
Expand Down
8 changes: 8 additions & 0 deletions lib/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { performance } from 'perf_hooks';

export function logTime(start: number, message: string) {
const elapsedSec = ((performance.now() - start) / 1000).toFixed(
2
);
console.log(`${message}: ${elapsedSec}s`);
}
Loading

0 comments on commit b964cac

Please sign in to comment.