Skip to content
This repository has been archived by the owner on Jun 9, 2022. It is now read-only.

Commit

Permalink
Merge pull request #1 from neon-bindings/kv/initial
Browse files Browse the repository at this point in the history
Initial implementation
  • Loading branch information
kjvalencik authored Feb 16, 2021
2 parents 66a4f25 + cb1ced3 commit c3b0d94
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 104 deletions.
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,35 @@ npm install -g cargo-cp-artifact
## Usage

```
cargo-cp-artifact [crate-name=output-file] -- [wrapped-command]
cargo-cp-artifact [crate-name=artifact-kind=output-file] -- [wrapped-command]
```

`cargo-cp-artifact` accepts a list of crate name to output file mappings and a command to wrap.`cargo-cp-artifact` will read `stdout` of the wrapped command and parse it as [cargo metadata](https://doc.rust-lang.org/cargo/reference/external-tools.html#json-messages). Compiler artifacts that match arguments provided will be copied to the target destination.
`cargo-cp-artifact` accepts a list of crate name and artifact kind to output file mappings and a command to wrap.`cargo-cp-artifact` will read `stdout` of the wrapped command and parse it as [cargo metadata](https://doc.rust-lang.org/cargo/reference/external-tools.html#json-messages). Compiler artifacts that match arguments provided will be copied to the target destination.

When wrapping a `cargo` command, it is necessary to include a `json` `--message-format`.
When wrapping a `cargo` command, it is necessary to include a `json` format to `--message-format`.

## Examples

### Wrapping cargo

```sh
cargo-cp-artifact my-crate=lib/index.node -- cargo build --message-format=json-render-diagnostics
cargo-cp-artifact my-crate=cdylib=lib/index.node -- cargo build --message-format=json-render-diagnostics
```

### Parsing a file

```sh
cargo-cp-artifact my-crate=lib/index.node -- cat build-output.txt
cargo-cp-artifact my-crate=cdylib=lib/index.node -- cat build-output.txt
```

### `npm` script

`package.json`
```json
{
"name": "my-crate",
"scripts": {
"build": "cargo-cp-artifact my-crate=lib/index.node -- cargo build --message-format=json-render-diagnostics"
"build": "cargo-cp-artifact $npm_package_name=cdylib=lib/index.node -- cargo build --message-format=json-render-diagnostics"
}
}
```
Expand Down
217 changes: 119 additions & 98 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,143 +10,164 @@ const options = parseArgs(process.argv.slice(2));
const copied = {};

const cp = spawn(options.command, options.arguments, {
stdio: ["inherit", "pipe", "inherit"]
stdio: ["inherit", "pipe", "inherit"]
});

const rl = readline.createInterface({ input: cp.stdout });

cp.on("error", (err) => {
console.error(err);
process.exitCode = 1;
console.error(err);
process.exitCode = 1;
});

cp.on("exit", (code) => {
if (!process.exitCode) {
process.exitCode = code;
}
if (!process.exitCode) {
process.exitCode = code;
}
});

rl.on("line", (line) => {
try {
processCargoBuildLine(line);
} catch (err) {
console.error(err);
process.exitCode = 1;
}
try {
processCargoBuildLine(line);
} catch (err) {
console.error(err);
process.exitCode = 1;
}
});

process.on("exit", () => {
Object.keys(options.outputFiles).forEach((name) => {
if (!copied[name]) {
console.error(`Did not copy "${name}"`);

if (!process.exitCode) {
process.exitCode = 1;
}
}
});
Object.keys(options.outputFiles).forEach((name) => {
if (!copied[name]) {
console.error(`Did not copy "${name}"`);

if (!process.exitCode) {
process.exitCode = 1;
}
}
});
});

function processCargoBuildLine(line) {
const data = JSON.parse(line);

// TODO: Check if all this validation is necessary
// https://crates.io/crates/cargo_metadata
if (!data || data.reason !== "compiler-artifact" || !data.target) {
return;
}

const { name } = data.target;
const outputFile = options.outputFiles[name];

if (!outputFile || !Array.isArray(data.filenames)) {
return;
}

const [filename] = data.filenames;

if (!filename) {
return;
}

copyArtifact(filename, outputFile)
.then(() => {
copied[name] = true;
})
.catch((err) => {
process.exitCode = 1;
console.error(err);
});
const data = JSON.parse(line);

if (!data || data.reason !== "compiler-artifact" || !data.target) {
return;
}

const { kind: kinds, name } = data.target;

if (!Array.isArray(kinds) || !kinds.length) {
return;
}

const [kind] = kinds;
const key = makeKey(name, kind);
const artifactConfig = options.outputFiles[key];

if (!artifactConfig || !Array.isArray(data.filenames)) {
return;
}

const [filename] = data.filenames;
const { outputFile } = artifactConfig;

if (!filename) {
return;
}

copyArtifact(filename, outputFile)
.then(() => {
copied[key] = true;
})
.catch((err) => {
process.exitCode = 1;
console.error(err);
});
}

async function isNewer(filename, outputFile) {
try {
const prevStats = await stat(outputFile);
const nextStats = await stat(filename);
try {
const prevStats = await stat(outputFile);
const nextStats = await stat(filename);

return nextStats.mtime > prevStats.mtime;
} catch (_err) {}
return nextStats.mtime > prevStats.mtime;
} catch (_err) {}

return true;
return true;
}

async function copyArtifact(filename, outputFile) {
if (!(await isNewer(filename, outputFile))) {
return;
}
if (!(await isNewer(filename, outputFile))) {
return;
}

await mkdir(dirname(outputFile), { recursive: true });
await copyFile(filename, outputFile);
await mkdir(dirname(outputFile), { recursive: true });
await copyFile(filename, outputFile);
}

// Expects: Options and command separated by "--"
// Example: "arguments to CLI -- command to execute"
function parseArgs(args) {
const splitAt = args.indexOf("--");
const options = splitAt >= 0 ? args.slice(0, splitAt) : args;
const command = splitAt >= 0 ? args.slice(splitAt + 1) : [];
const outputFiles = parseOutputFiles(options);

if (!command.length) {
quitError([
"Missing command to execute.",
[
"cargo-cp-artifct my-crate=index.node",
"--",
"cargo build --message-format=json-render-diagnostics"
].join(" ")
].join("\n"));
}

return {
command: command[0],
arguments: command.slice(1),
outputFiles
};
const splitAt = args.indexOf("--");
const options = splitAt >= 0 ? args.slice(0, splitAt) : args;
const command = splitAt >= 0 ? args.slice(splitAt + 1) : [];
const outputFiles = parseOutputFiles(options);

if (!command.length) {
quitError([
"Missing command to execute.",
[
"cargo-cp-artifct my-crate=cdylib=index.node",
"--",
"cargo build --message-format=json-render-diagnostics"
].join(" ")
].join("\n"));
}

return {
command: command[0],
arguments: command.slice(1),
outputFiles
};
}

// Expects: List of "crate-name=output/file" pairs
// Expects: List of "crate-name=kind=output_file_path" sets
function parseOutputFiles(args) {
return args
.map(parseOutputFile)
.reduce((acc, [crate, file]) => ({
...acc,
[crate]: file
}), {});
return args
.map(parseOutputFile)
.reduce((acc, opts) => ({
...acc,
[makeKey(opts.crateName, opts.kind)]: opts
}), {});
}

// Expects: "crate-name=output/file" pair
function parseOutputFile(pair) {
const splitAt = pair.indexOf("=");
// Expects: "crate-name=kind=output_file_path" set
function parseOutputFile(opts) {
const nameSplitAt = opts.indexOf("=");

if (nameSplitAt < 0) {
quitError(`Missing artifact kind: ${opts}`);
}

const crateName = opts.slice(0, nameSplitAt);
const remainder = opts.slice(nameSplitAt + 1);
const kindSplitAt = remainder.indexOf("=");

if (splitAt < 0) {
quitError(`Missing output file name: ${pair}`);
}
if (kindSplitAt < 0) {
quitError(`Missing output file name: ${opts}`);
}

const kind = remainder.slice(0, kindSplitAt);
const outputFile = remainder.slice(kindSplitAt + 1);

return { crateName, kind, outputFile };
}

return [pair.slice(0, splitAt), pair.slice(splitAt + 1)];
function makeKey(crateName, kind) {
return `${crateName}=${kind}`;
}

function quitError(msg) {
console.error(msg);
process.exit(1);
console.error(msg);
process.exit(1);
}

0 comments on commit c3b0d94

Please sign in to comment.