Skip to content

Commit

Permalink
RFC: Writing create-neon in Rust as a neon module
Browse files Browse the repository at this point in the history
  • Loading branch information
kjvalencik committed Dec 29, 2020
1 parent 32532f9 commit ef04351
Show file tree
Hide file tree
Showing 7 changed files with 221 additions and 4 deletions.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
node_modules
index.node

# Added by cargo

/target
Cargo.lock
25 changes: 25 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[package]
name = "create-neon"
version = "0.1.0"
authors = ["Dave Herman <[email protected]>"]
description = "Script to create Neon projects"
readme = "README.md"
homepage = "https://www.neon-bindings.com"
repository = "https://github.com/neon-bindings/neon"
license = "MIT/Apache-2.0"
publish = false
edition = "2018"

[lib]
crate-type = ["cdylib"]

[build-dependencies]
neon-build = "0.6"

[dependencies]
structopt = "0.3"

[dependencies.neon]
version = "0.6"
default-features = false
features = ["napi-runtime"]
3 changes: 0 additions & 3 deletions index.js

This file was deleted.

127 changes: 127 additions & 0 deletions neon-cargo
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#!/usr/bin/env node

"use strict";

const { spawn } = require("child_process");
const { dirname } = require("path");
const { copyFile, mkdir } = require("fs");
const readline = require("readline");

const CARGO = process.env.CARGO || "cargo";

// Split arguments between ones targeting neon-cargo and cargo
const [neonCargoArgs, cargoArgs] = splitArgs(process.argv.slice(2));
const config = parseNeonCargoArgs(neonCargoArgs);

// Determine if the user is executing a cargo build
const cargoSubcommand = cargoArgs.find(arg => !arg.startsWith("-"));
const isCargoBuild = ["build", "b"].includes(cargoSubcommand);

if (isCargoBuild) {
cargoBuild();
} else {
cargoPassthrough();
}

// Not a cargo build, pass through the command
function cargoPassthrough() {
spawn(CARGO, cargoArgs, {
stdio: "inherit",
});
}

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

if (data.reason !== 'compiler-artifact') {
return;
}

const outputFile = config.outputFiles[data.target.name];
const filename = data.filenames[0];

if (!outputFile || !filename) {
return;
}

mkdir(dirname(outputFile), { recursive: true }, (err) => {
if (err) {
return console.error(err);
}

copyFile(filename, outputFile, (err) => {
if (err) {
console.error(err);
}
});
});
}

function cargoBuild() {
const hasCustomMessageFormat = doArgsInclude(cargoArgs, "--message-format");
const [first, second] = splitArgs(cargoArgs);
const args = hasCustomMessageFormat
? cargoArgs
: [...first, "--message-format=json-render-diagnostics", ...second];

const cargo = spawn(CARGO, args, {
stdio: ["inherit", "pipe", "inherit"]
});

if (hasCustomMessageFormat) {
cargo.stdout.pipe(process.stdout);
}

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

rl.on("line", processCargoBuildLine);
}

function splitArgs(args) {
const splitAt = args.indexOf("--");
const first = splitAt < 0 ? args : args.slice(0, splitAt);
const second = splitAt < 0 ? [] : args.slice(splitAt + 1);

return [first, second];
}

function doArgsInclude(args, param) {
if (args.includes(param)) {
return true;
}

return args.some(arg => arg.startsWith(`${param}=`));
}

function printUsage() {
console.error("Expected arguments: -o [target]=[output]");
process.exit(-1);
}

function parseNeonOutputArg(arg) {
if (!arg) {
return printUsage();
}

const splitAt = arg.indexOf("=");
const target = arg.slice(0, splitAt);
const output = arg.slice(splitAt + 1);

if (splitAt < 0 || !target || !output) {
return printUsage();
}

return { target, output };
}

function parseNeonCargoArgs(args) {
const outputFiles = args
.map((arg, i, arr) => [arg, arr[i + 1]])
.filter(([arg]) => arg === "-o")
.map(([, arg]) => parseNeonOutputArg(arg))
.reduce((acc, { target, output }) => Object.assign(acc, {
[target]: output
}), {});

return { outputFiles };
}
16 changes: 16 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
"description": "Create Neon projects with zero configuration.",
"main": "index.js",
"scripts": {
"build": "npm run cargo -- build --release",
"cargo": "node neon-cargo -o $npm_package_name=index.node --",
"postinstall": "npm run build",
"test": "echo \"Error: no test specified\" && exit 1"
},
"bin": {
"create-neon": "./index.js"
"create-neon": "index.node"
},
"repository": {
"type": "git",
Expand Down
43 changes: 43 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use std::path::PathBuf;

use neon::context::{Context, ModuleContext};
use neon::object::Object;
use neon::result::NeonResult;
use neon::types::{JsArray, JsObject, JsString};
use structopt::StructOpt;

#[derive(Debug, StructOpt)]
#[structopt(bin_name = "npm init neon")]
struct Opt {
name: Option<String>,
#[structopt(short, long)]
submodule: Option<PathBuf>,
}

fn cli(argv: Vec<String>) {
let opts = Opt::from_iter(argv);
println!("{:?}", opts);
}

#[neon::main]
fn main(mut cx: ModuleContext) -> NeonResult<()> {
let global = cx.global();

let process = global.get(&mut cx, "process")?
.downcast_or_throw::<JsObject, _>(&mut cx)?;

let argv = process.get(&mut cx, "argv")?
.downcast_or_throw::<JsArray, _>(&mut cx)?
.to_vec(&mut cx)?
.into_iter()
.skip(1)
.map(|v| {
v.downcast_or_throw::<JsString, _>(&mut cx)
.map(|v| v.value(&mut cx))
})
.collect::<Result<_, _>>()?;

cli(argv);

Ok(())
}

0 comments on commit ef04351

Please sign in to comment.