From 072865e45071aa648ca3b0bd1346f70baea629ca Mon Sep 17 00:00:00 2001 From: ObserverOfTime Date: Sat, 9 Mar 2024 19:25:14 +0200 Subject: [PATCH] feat(bindings): add prebuildify to node Co-Authored-By: Amaan Qureshi --- cli/src/generate/grammar_files.rs | 168 ++++++++++++++---------- cli/src/generate/templates/gitignore | 1 + cli/src/generate/templates/index.js | 18 +-- cli/src/generate/templates/package.json | 7 +- 4 files changed, 106 insertions(+), 88 deletions(-) diff --git a/cli/src/generate/grammar_files.rs b/cli/src/generate/grammar_files.rs index a3db4224..4cf03c35 100644 --- a/cli/src/generate/grammar_files.rs +++ b/cli/src/generate/grammar_files.rs @@ -3,7 +3,7 @@ use anyhow::{anyhow, Context, Result}; use filetime::FileTime; use heck::{ToKebabCase, ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase}; use serde::Deserialize; -use serde_json::{Map, Value}; +use serde_json::{json, Map, Value}; use std::fs::File; use std::io::BufReader; use std::path::{Path, PathBuf}; @@ -83,8 +83,8 @@ pub fn path_in_ignore(repo_path: &Path) -> bool { fn insert_after( map: Map, - key: &str, after: &str, + key: &str, value: Value, ) -> Map { let mut entries = map.into_iter().collect::>(); @@ -104,6 +104,8 @@ pub fn generate_grammar_files( ) -> Result<()> { let dashed_language_name = language_name.to_kebab_case(); + // TODO: remove legacy code updates in v0.24.0 + // Create or update package.json let package_json_path_state = missing_path_else( repo_path.join("package.json"), @@ -113,82 +115,115 @@ pub fn generate_grammar_files( fs::read_to_string(path).with_context(|| "Failed to read package.json")?; let mut package_json = serde_json::from_str::>(&package_json_str) .with_context(|| "Failed to parse package.json")?; - let package_json_needs_update = generate_bindings - && (package_json - .get("dependencies") - .map_or(false, |d| d.get("nan").is_some()) - || !package_json.contains_key("peerDependencies") - || !package_json.contains_key("types") - || !package_json.contains_key("files")); - if package_json_needs_update { + if generate_bindings { + let mut updated = false; + let dependencies = package_json .entry("dependencies".to_string()) - .or_insert_with(|| Value::Object(Map::new())); - let dependencies = dependencies.as_object_mut().unwrap(); + .or_insert_with(|| Value::Object(Map::new())) + .as_object_mut() + .unwrap(); if dependencies.remove("nan").is_some() { - eprintln!("Replacing package.json's nan dependency with node-addon-api"); - dependencies.insert( - "node-addon-api".to_string(), - Value::String("^7.1.0".to_string()), + eprintln!("Replacing nan dependency with node-addon-api in package.json"); + dependencies.insert("node-addon-api".to_string(), "^7.1.0".into()); + updated = true; + } + if !dependencies.contains_key("node-gyp-build") { + eprintln!("Adding node-gyp-build dependency to package.json"); + dependencies.insert("node-gyp-build".to_string(), "^4.8.0".into()); + updated = true; + } + + let dev_dependencies = package_json + .entry("devDependencies".to_string()) + .or_insert_with(|| Value::Object(Map::new())) + .as_object_mut() + .unwrap(); + if !dev_dependencies.contains_key("prebuildify") { + eprintln!("Adding prebuildify devDependency to package.json"); + dev_dependencies.insert("prebuildify".to_string(), "^6.0.0".into()); + updated = true; + } + + let scripts = package_json + .entry("scripts".to_string()) + .or_insert_with(|| Value::Object(Map::new())) + .as_object_mut() + .unwrap(); + match scripts.get("install") { + None => { + eprintln!("Adding an install script to package.json"); + scripts.insert("install".to_string(), "node-gyp-build".into()); + updated = true; + } + Some(Value::String(v)) if v != "node-gyp-build" => { + eprintln!("Updating the install script in package.json"); + scripts.insert("install".to_string(), "node-gyp-build".into()); + updated = true; + } + Some(_) => {} + } + if !scripts.contains_key("prebuildify") { + eprintln!("Adding a prebuildify script to package.json"); + scripts.insert( + "prebuildify".to_string(), + "prebuildify --napi --strip".into(), ); + updated = true; } // insert `peerDependencies` after `dependencies` if !package_json.contains_key("peerDependencies") { eprintln!("Adding peerDependencies to package.json"); - let mut peer_dependencies = Map::new(); - peer_dependencies.insert( - "tree-sitter".to_string(), - Value::String("^0.21.0".to_string()), - ); package_json = insert_after( package_json, - "peerDependencies", "dependencies", - Value::Object(peer_dependencies), + "peerDependencies", + json!({"tree-sitter": "^0.21.0"}), ); - let mut tree_sitter_meta = Map::new(); - tree_sitter_meta.insert("optional".to_string(), Value::Bool(true)); - let mut peer_dependencies_meta = Map::new(); - peer_dependencies_meta - .insert("tree_sitter".to_string(), Value::Object(tree_sitter_meta)); package_json = insert_after( package_json, "peerDependencies", - "dependencies", - Value::Object(peer_dependencies_meta), + "peerDependenciesMeta", + json!({"tree_sitter": {"optional": true}}), ); + updated = true; } // insert `types` right after `main` if !package_json.contains_key("types") { eprintln!("Adding types to package.json"); - package_json = insert_after( - package_json, - "types", - "main", - Value::String("bindings/node".to_string()), - ); + package_json = + insert_after(package_json, "main", "types", "bindings/node".into()); + updated = true; } // insert `files` right after `keywords` if !package_json.contains_key("files") { eprintln!("Adding files to package.json"); - let files = Value::Array(vec![ - Value::String("grammar.js".to_string()), - Value::String("binding.gyp".to_string()), - Value::String("types/dsl.d.ts".to_string()), - Value::String("bindings/node/*".to_string()), - Value::String("queries/*".to_string()), - Value::String("src/**".to_string()), - ]); - package_json = insert_after(package_json, "files", "keywords", files); + package_json = insert_after( + package_json, + "keywords", + "files", + json!([ + "grammar.js", + "binding.gyp", + "types/dsl.d.ts", + "prebuilds/**", + "bindings/node/*", + "queries/*", + "src/**", + ]), + ); + updated = true; } - let mut package_json_str = serde_json::to_string_pretty(&package_json)?; - package_json_str.push('\n'); - write_file(path, package_json_str)?; + if updated { + let mut package_json_str = serde_json::to_string_pretty(&package_json)?; + package_json_str.push('\n'); + write_file(path, package_json_str)?; + } } Ok(()) @@ -272,9 +307,19 @@ pub fn generate_grammar_files( // Generate Node bindings missing_path(bindings_dir.join("node"), create_dir)?.apply(|path| { - missing_path(path.join("index.js"), |path| { - generate_file(path, INDEX_JS_TEMPLATE, language_name) - })?; + missing_path_else( + path.join("index.js"), + |path| generate_file(path, INDEX_JS_TEMPLATE, language_name), + |path| { + let index_js = + fs::read_to_string(path).with_context(|| "Failed to read index.js")?; + if index_js.contains("../../build/Release") { + eprintln!("Replacing index.js with new binding API"); + generate_file(path, INDEX_JS_TEMPLATE, language_name)?; + } + Ok(()) + }, + )?; missing_path(path.join("index.d.ts"), |path| { generate_file(path, INDEX_D_TS_TEMPLATE, language_name) @@ -309,10 +354,6 @@ pub fn generate_grammar_files( }, )?; - // Remove files from old node binding paths. - existing_path(repo_path.join("index.js"), remove_file)?; - existing_path(repo_path.join("src").join("binding.cc"), remove_file)?; - Ok(()) })?; @@ -464,11 +505,6 @@ fn create_dir(path: &Path) -> Result<()> { .with_context(|| format!("Failed to create {:?}", path.to_string_lossy())) } -fn remove_file(path: &Path) -> Result<()> { - fs::remove_file(path).ok(); - Ok(()) -} - #[derive(PartialEq, Eq, Debug)] enum PathState { Exists(PathBuf), @@ -508,18 +544,6 @@ impl PathState { } } -fn existing_path(path: PathBuf, mut action: F) -> Result -where - F: FnMut(&Path) -> Result<()>, -{ - if path.exists() { - action(path.as_path())?; - Ok(PathState::Exists(path)) - } else { - Ok(PathState::Missing(path)) - } -} - fn missing_path(path: PathBuf, mut action: F) -> Result where F: FnMut(&Path) -> Result<()>, diff --git a/cli/src/generate/templates/gitignore b/cli/src/generate/templates/gitignore index 70bef74b..1f2e23ae 100644 --- a/cli/src/generate/templates/gitignore +++ b/cli/src/generate/templates/gitignore @@ -4,6 +4,7 @@ target/ # Node artifacts build/ +prebuilds/ node_modules/ *.tgz diff --git a/cli/src/generate/templates/index.js b/cli/src/generate/templates/index.js index ea822bfc..6657bcf4 100644 --- a/cli/src/generate/templates/index.js +++ b/cli/src/generate/templates/index.js @@ -1,18 +1,6 @@ -try { - module.exports = require("../../build/Release/tree_sitter_PARSER_NAME_binding"); -} catch (error1) { - if (error1.code !== "MODULE_NOT_FOUND") { - throw error1; - } - try { - module.exports = require("../../build/Debug/tree_sitter_PARSER_NAME_binding"); - } catch (error2) { - if (error2.code !== "MODULE_NOT_FOUND") { - throw error2; - } - throw error1 - } -} +const root = require("path").join(__dirname, "..", ".."); + +module.exports = require("node-gyp-build")(root); try { module.exports.nodeTypeInfo = require("../../src/node-types.json"); diff --git a/cli/src/generate/templates/package.json b/cli/src/generate/templates/package.json index 234b519c..a73d296e 100644 --- a/cli/src/generate/templates/package.json +++ b/cli/src/generate/templates/package.json @@ -16,14 +16,17 @@ "grammar.js", "binding.gyp", "types/dsl.d.ts", + "prebuilds/**", "bindings/node/*", "queries/*", "src/**" ], "dependencies": { - "node-addon-api": "^7.1.0" + "node-addon-api": "^7.1.0", + "node-gyp-build": "^4.8.0" }, "devDependencies": { + "prebuildify": "^6.0.0", "tree-sitter-cli": "^CLI_VERSION" }, "peerDependencies": { @@ -35,6 +38,8 @@ } }, "scripts": { + "install": "node-gyp-build", + "prebuildify": "prebuildify --napi --strip", "build": "tree-sitter generate --no-bindings", "build-wasm": "tree-sitter build-wasm", "test": "tree-sitter test",