From ef04351bb40dfc89b59b6808a4e9b60509fcaa1f Mon Sep 17 00:00:00 2001 From: "K.J. Valencik" Date: Wed, 16 Dec 2020 17:31:59 -0500 Subject: [PATCH] RFC: Writing create-neon in Rust as a neon module --- .gitignore | 6 +++ Cargo.toml | 25 +++++++++ index.js | 3 -- neon-cargo | 127 ++++++++++++++++++++++++++++++++++++++++++++++ package-lock.json | 16 ++++++ package.json | 5 +- src/lib.rs | 43 ++++++++++++++++ 7 files changed, 221 insertions(+), 4 deletions(-) create mode 100644 Cargo.toml delete mode 100755 index.js create mode 100755 neon-cargo create mode 100644 package-lock.json create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore index 3c3629e..11fd805 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,7 @@ node_modules +index.node + +# Added by cargo + +/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..f7df572 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "create-neon" +version = "0.1.0" +authors = ["Dave Herman "] +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"] diff --git a/index.js b/index.js deleted file mode 100755 index 4d551ef..0000000 --- a/index.js +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env node - -console.log('create-neon'); diff --git a/neon-cargo b/neon-cargo new file mode 100755 index 0000000..d7002ba --- /dev/null +++ b/neon-cargo @@ -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 }; +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..479398e --- /dev/null +++ b/package-lock.json @@ -0,0 +1,16 @@ +{ + "name": "create-neon", + "version": "0.1.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "version": "0.1.0", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "create-neon": "index.node" + } + } + } +} diff --git a/package.json b/package.json index 29f3668..c29f995 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..ad9a9bf --- /dev/null +++ b/src/lib.rs @@ -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, + #[structopt(short, long)] + submodule: Option, +} + +fn cli(argv: Vec) { + 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::(&mut cx)?; + + let argv = process.get(&mut cx, "argv")? + .downcast_or_throw::(&mut cx)? + .to_vec(&mut cx)? + .into_iter() + .skip(1) + .map(|v| { + v.downcast_or_throw::(&mut cx) + .map(|v| v.value(&mut cx)) + }) + .collect::>()?; + + cli(argv); + + Ok(()) +}