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
29 changes: 29 additions & 0 deletions .changeset/polite-breads-behave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
'@codama/renderers-rust': major
---

Refactor `renderVisitor` to use `crateFolder` as its primary argument instead of the generated output path. The generated folder is now derived internally using a new `generatedFolder` option (defaults to `'src/generated'`). Remove the `crateFolder` option as it is no longer needed.

**BREAKING CHANGES**

**First argument of `renderVisitor` changed from output path to crate folder.** The function now takes the crate folder (where `Cargo.toml` lives) and derives the generated output path internally.

```diff
- const visitor = renderVisitor('clients/rust/src/generated', {
- crateFolder: 'clients/rust',
- });
+ const visitor = renderVisitor('clients/rust');
```

**`crateFolder` option removed.** It is replaced by the first argument of `renderVisitor`.

**New `generatedFolder` option.** Defaults to `'src/generated'` and can be customized to change the output path relative to the crate folder.

```diff
- const visitor = renderVisitor('clients/rust/my/custom/path', {
- crateFolder: 'clients/rust',
- });
+ const visitor = renderVisitor('clients/rust', {
+ generatedFolder: 'my/custom/path',
+ });
```
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ pnpm install @codama/renderers-rust

## Usage

Add the following script to your Codama configuration file.
Add the following script to your Codama configuration file. The first argument is the crate folder (where `Cargo.toml` lives) and the generated code will be output to `src/generated` within that folder by default.

```json
{
"scripts": {
"rust": {
"from": "@codama/renderers-rust",
"args": ["clients/rust/src/generated"]
"args": ["clients/rust"]
}
}
}
Expand All @@ -39,16 +39,16 @@ The `renderVisitor` accepts the following options.
| Name | Type | Default | Description |
| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `deleteFolderBeforeRendering` | `boolean` | `true` | Whether the base directory should be cleaned before generating new files. |
| `formatCode` | `boolean` | `false` | Whether we should use `cargo fmt` to format the generated code. When set to `true`, the `crateFolder` option must be provided. |
| `formatCode` | `boolean` | `false` | Whether we should use `cargo fmt` to format the generated code. |
| `generatedFolder` | `string` | `'src/generated'` | The path to the generated folder, relative to the crate folder provided as the first argument. |
| `toolchain` | `string` | `"+stable"` | The toolchain to use when formatting the generated code. |
| `crateFolder` | `string` | none | The path to the root folder of the Rust crate. This option is required when `formatCode` is set to `true`. |
| `linkOverrides` | `Record<'accounts' \| 'definedTypes' \| 'instructions' \| 'pdas' \| 'programs' \| 'resolvers', Record<string, string>>` | `{}` | A object that overrides the import path of link nodes. For instance, `{ definedTypes: { counter: 'hooked' } }` uses the `hooked` folder to import any link node referring to the `counter` type. |
| `dependencyMap` | `Record<string, string>` | `{}` | A mapping between import aliases and their actual crate name or path in Rust. |
| `dependencyVersions` | `Record<string, CargoDependency>` | `{}` | A mapping between external crates — e.g. `solana-pubkey` — and the version range we should use for them — e.g. `^3.0` or the equivalent dependency object — e.g. `{ version: "^3.0" }`. The renderer offers default values for all external dependencies it relies on but this option may be used to override some of these values or add new ones. |
| `renderParentInstructions` | `boolean` | `false` | When using nested instructions, whether the parent instructions should also be rendered. When set to `false` (default), only the instruction leaves are being rendered. |
| `traitOptions` | [`TraitOptions`](#trait-options) | `DEFAULT_TRAIT_OPTIONS` | A set of options that can be used to configure how traits are rendered for every Rust types. See [documentation below](#trait-options) for more information. |
| `anchorTraits` | `boolean` | `true` | Whether to generate Anchor traits `impl` for account types. |
| `syncCargoToml` | `boolean` | `false` | Whether to update the dependencies of the existing `Cargo.toml` — or create a new `Cargo.toml` when missing — at the provided `crateFolder` option if provided. |
| `syncCargoToml` | `boolean` | `false` | Whether to update the dependencies of the existing `Cargo.toml` — or create a new `Cargo.toml` when missing — at the crate folder provided as the first argument. |

## Trait Options

Expand Down
3 changes: 1 addition & 2 deletions e2e/generate-anchor.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ async function generateProject(project) {

visit(
node,
renderVisitor(path.join(__dirname, project, 'src', 'generated'), {
crateFolder: path.join(__dirname, project),
renderVisitor(path.join(__dirname, project), {
formatCode: true,
}),
);
Expand Down
3 changes: 1 addition & 2 deletions e2e/generate.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ async function generateProject(project) {
const node = rootNode(idl.program);
visit(
node,
renderVisitor(path.join(__dirname, project, 'src', 'generated'), {
crateFolder: path.join(__dirname, project),
renderVisitor(path.join(__dirname, project), {
formatCode: true,
}),
);
Expand Down
30 changes: 13 additions & 17 deletions src/renderVisitor.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,42 @@
import { logError, logWarn } from '@codama/errors';
import { deleteDirectory, writeRenderMap } from '@codama/renderers-core';
import { deleteDirectory, joinPath, writeRenderMap } from '@codama/renderers-core';
import { rootNodeVisitor, visit } from '@codama/visitors-core';
import { spawnSync } from 'child_process';

import { GetRenderMapOptions, getRenderMapVisitor } from './getRenderMapVisitor';
import { syncCargoToml } from './utils';

export type RenderOptions = GetRenderMapOptions & {
crateFolder?: string;
deleteFolderBeforeRendering?: boolean;
formatCode?: boolean;
generatedFolder?: string;
syncCargoToml?: boolean;
toolchain?: string;
};

export function renderVisitor(path: string, options: RenderOptions = {}) {
export function renderVisitor(crateFolder: string, options: RenderOptions = {}) {
return rootNodeVisitor(root => {
const generatedFolder = joinPath(crateFolder, options.generatedFolder ?? 'src/generated');

// Delete existing generated folder.
if (options.deleteFolderBeforeRendering ?? true) {
deleteDirectory(path);
deleteDirectory(generatedFolder);
}

// Render the new files.
const renderMap = visit(root, getRenderMapVisitor(options));
writeRenderMap(renderMap, path);
writeRenderMap(renderMap, generatedFolder);

// Sync Cargo.toml dependencies and versions, if requested.
syncCargoToml(renderMap, options);
syncCargoToml(renderMap, crateFolder, options);

// format the code
if (options.formatCode) {
if (options.crateFolder) {
const removeFalsy = <T>(arg: T | false | null | undefined): arg is T => Boolean(arg);
runFormatter(
'cargo',
[options.toolchain, 'fmt', '--manifest-path', `${options.crateFolder}/Cargo.toml`].filter(
removeFalsy,
),
);
} else {
logWarn('No crate folder specified, skipping formatting.');
}
const removeFalsy = <T>(arg: T | false | null | undefined): arg is T => Boolean(arg);
runFormatter(
'cargo',
[options.toolchain, 'fmt', '--manifest-path', `${crateFolder}/Cargo.toml`].filter(removeFalsy),
);
}
});
}
Expand Down
14 changes: 2 additions & 12 deletions src/utils/cargoToml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,20 +62,10 @@ export const DEFAULT_DEPENDENCY_VERSIONS: CargoDependencies = {

export function syncCargoToml(
renderMap: RenderMap<Fragment>,
options: Pick<RenderOptions, 'crateFolder' | 'dependencyMap' | 'dependencyVersions' | 'syncCargoToml'>,
crateFolder: string,
options: Pick<RenderOptions, 'dependencyMap' | 'dependencyVersions' | 'syncCargoToml'>,
): void {
const shouldSyncCargoToml = options.syncCargoToml ?? false;
const crateFolder = options.crateFolder;

// Without a `crateFolder`, we cannot sync the Cargo.toml.
if (!crateFolder) {
// If we should sync but have no folder, warn the user.
if (shouldSyncCargoToml) {
logWarn("Cannot sync Cargo.toml. Please provide the 'crateFolder' option.");
}
return;
}

const cargoTomlPath = joinPath(crateFolder, 'Cargo.toml');
const usedDependencies = getUsedDependencyVersions(
renderMap,
Expand Down