fix: revalidate shared libs if type def not found (#2689)

This commit is contained in:
liuyi 2025-06-04 15:05:24 +08:00 committed by GitHub
parent a6f133cf7f
commit 5a17b88636
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 74 additions and 60 deletions

View File

@ -32,6 +32,7 @@ import {
writeFileAsync,
dirExistsAsync,
readdirAsync,
CargoWorkspaceMetadata,
} from '../utils/index.js'
import { createCjsBinding, createEsmBinding } from './templates/index.js'
@ -51,20 +52,23 @@ type OutputKind = 'js' | 'dts' | 'node' | 'exe' | 'wasm'
type Output = { kind: OutputKind; path: string }
type BuildOptions = RawBuildOptions & { cargoOptions?: string[] }
type ParsedBuildOptions = Omit<BuildOptions, 'cwd'> & { cwd: string }
export async function buildProject(options: BuildOptions) {
debug('napi build command receive options: %O', options)
export async function buildProject(rawOptions: BuildOptions) {
debug('napi build command receive options: %O', rawOptions)
options = { dtsCache: true, ...options }
const options: ParsedBuildOptions = {
dtsCache: true,
...rawOptions,
cwd: rawOptions.cwd ?? process.cwd(),
}
const cwd = options.cwd ?? process.cwd()
const resolvePath = (...paths: string[]) => resolve(cwd, ...paths)
const resolvePath = (...paths: string[]) => resolve(options.cwd, ...paths)
const manifestPath = resolvePath(options.manifestPath ?? 'Cargo.toml')
const metadata = await parseMetadata(manifestPath)
const pkg = metadata.packages.find((p) => {
const crate = metadata.packages.find((p) => {
// package with given name
if (options.package) {
return p.name === options.package
@ -73,36 +77,20 @@ export async function buildProject(options: BuildOptions) {
}
})
if (!pkg) {
if (!crate) {
throw new Error(
'Unable to find crate to build. It seems you are trying to build a crate in a workspace, try using `--package` option to specify the package to build.',
)
}
const crateDir = parse(pkg.manifest_path).dir
const builder = new Builder(
options,
pkg,
cwd,
options.target
? parseTriple(options.target)
: process.env.CARGO_BUILD_TARGET
? parseTriple(process.env.CARGO_BUILD_TARGET)
: getSystemDefaultTarget(),
crateDir,
resolvePath(options.outputDir ?? crateDir),
options.targetDir ??
process.env.CARGO_BUILD_TARGET_DIR ??
metadata.target_directory,
await readNapiConfig(
resolvePath(
options.configPath ?? options.packageJsonPath ?? 'package.json',
),
options.configPath ? resolvePath(options.configPath) : undefined,
const config = await readNapiConfig(
resolvePath(
options.configPath ?? options.packageJsonPath ?? 'package.json',
),
options.configPath ? resolvePath(options.configPath) : undefined,
)
const builder = new Builder(metadata, crate, config, options)
return builder.build()
}
@ -111,16 +99,32 @@ class Builder {
private readonly envs: Record<string, string> = {}
private readonly outputs: Output[] = []
private readonly target: Target
private readonly crateDir: string
private readonly outputDir: string
private readonly targetDir: string
constructor(
private readonly options: BuildOptions,
private readonly metadata: CargoWorkspaceMetadata,
private readonly crate: Crate,
private readonly cwd: string,
private readonly target: Target,
private readonly crateDir: string,
private readonly outputDir: string,
private readonly targetDir: string,
private readonly config: NapiConfig,
) {}
private readonly options: ParsedBuildOptions,
) {
this.target = options.target
? parseTriple(options.target)
: process.env.CARGO_BUILD_TARGET
? parseTriple(process.env.CARGO_BUILD_TARGET)
: getSystemDefaultTarget()
this.crateDir = parse(crate.manifest_path).dir
this.outputDir = resolve(
this.options.cwd,
options.outputDir ?? this.crateDir,
)
this.targetDir =
options.targetDir ??
process.env.CARGO_BUILD_TARGET_DIR ??
metadata.target_directory
}
get cdyLibName() {
return this.crate.targets.find((t) => t.crate_types.includes('cdylib'))
@ -303,7 +307,7 @@ class Builder {
const buildProcess = spawn(command, this.args, {
env: { ...process.env, ...this.envs },
stdio: watch ? ['inherit', 'inherit', 'pipe'] : 'inherit',
cwd: this.cwd,
cwd: this.options.cwd,
signal: controller.signal,
})
@ -449,7 +453,9 @@ class Builder {
private setEnvs() {
// folder for intermediate type definition files
this.envs.TYPE_DEF_TMP_FOLDER = this.generateIntermediateTypeDefFolder()
this.envs.NAPI_TYPE_DEF_TMP_FOLDER =
this.generateIntermediateTypeDefFolder()
this.setForceBuildEnvs(this.envs.NAPI_TYPE_DEF_TMP_FOLDER)
// RUSTFLAGS
let rustflags =
@ -577,6 +583,20 @@ class Builder {
return this
}
private setForceBuildEnvs(typeDefTmpFolder: string) {
// dynamically check all napi-rs deps and set `NAPI_FORCE_BUILD_{uppercase(snake_case(name))} = timestamp`
this.metadata.packages.forEach((crate) => {
if (
crate.dependencies.some((d) => d.name === 'napi-derive') &&
!existsSync(join(typeDefTmpFolder, crate.name))
) {
this.envs[
`NAPI_FORCE_BUILD_${crate.name.replace(/-/g, '_').toUpperCase()}`
] = Date.now().toString()
}
})
}
private setFeatures() {
const args = []
if (this.options.allFeatures && this.options.noDefaultFeatures) {
@ -793,7 +813,7 @@ class Builder {
}
private async generateTypeDef() {
const typeDefDir = this.envs.TYPE_DEF_TMP_FOLDER
const typeDefDir = this.envs.NAPI_TYPE_DEF_TMP_FOLDER
if (!(await dirExistsAsync(typeDefDir))) {
return []
}
@ -810,7 +830,7 @@ class Builder {
if (this.config.dtsHeaderFile) {
try {
header = await readFileAsync(
join(this.cwd, this.config.dtsHeaderFile),
join(this.options.cwd, this.config.dtsHeaderFile),
'utf-8',
)
} catch (e) {

View File

@ -1,7 +1,6 @@
use std::{
cell::RefCell,
collections::HashMap,
env,
fmt::{self, Display, Formatter},
sync::LazyLock,
};
@ -14,11 +13,6 @@ mod r#type;
use syn::{PathSegment, Type, TypePath, TypeSlice};
pub static NAPI_RS_CLI_VERSION: LazyLock<semver::Version> = LazyLock::new(|| {
let version = env::var("CARGO_CFG_NAPI_RS_CLI_VERSION").unwrap_or_else(|_| "0.0.0".to_string());
semver::Version::parse(&version).unwrap_or_else(|_| semver::Version::new(0, 0, 0))
});
#[derive(Default, Debug)]
pub struct TypeDef {
pub kind: String,

View File

@ -6,14 +6,14 @@ mod wasi;
mod windows;
pub fn setup() {
let package_name = env::var("CARGO_PKG_NAME").expect("CARGO_PKG_NAME is not set");
println!("cargo:rerun-if-env-changed=DEBUG_GENERATED_CODE");
println!("cargo:rerun-if-env-changed=TYPE_DEF_TMP_PATH");
println!("cargo:rerun-if-env-changed=CARGO_CFG_NAPI_RS_CLI_VERSION");
println!("cargo::rerun-if-env-changed=NAPI_DEBUG_GENERATED_CODE");
println!("cargo::rerun-if-env-changed=NAPI_TYPE_DEF_TMP_FOLDER");
println!(
"cargo:rerun-if-env-changed=NAPI_PACKAGE_{}_INVALID",
package_name.to_uppercase().replace("-", "_")
"cargo::rerun-if-env-changed=NAPI_FORCE_BUILD_{}",
env::var("CARGO_PKG_NAME")
.expect("CARGO_PKG_NAME is not set")
.to_uppercase()
.replace("-", "_")
);
match env::var("CARGO_CFG_TARGET_OS").as_deref() {

View File

@ -1,5 +1,4 @@
use std::env;
use std::env::VarError;
use std::fs;
use std::io::{BufWriter, Write};
use std::path::PathBuf;
@ -9,16 +8,17 @@ use napi_derive_backend::{Napi, ToTypeDef};
static PKG_NAME: LazyLock<String> =
LazyLock::new(|| env::var("CARGO_PKG_NAME").expect("Expected `CARGO_PKG_NAME` to be set"));
static TYPE_DEF_FOLDER: LazyLock<Result<String, VarError>> =
LazyLock::new(|| env::var("TYPE_DEF_TMP_FOLDER"));
static TYPE_DEF_FOLDER: LazyLock<Option<String>> =
LazyLock::new(|| env::var("NAPI_TYPE_DEF_TMP_FOLDER").ok());
fn get_type_def_file() -> Option<PathBuf> {
if let Ok(folder) = TYPE_DEF_FOLDER.as_ref() {
if let Some(folder) = TYPE_DEF_FOLDER.as_ref() {
let file = PathBuf::from(folder).join(&*PKG_NAME);
Some(file)
} else {
// the environment variable set by old `@napi-rs/cli`
if env::var("TYPE_DEF_TMP_PATH").is_ok() {
panic!("Expected `TYPE_DEF_TMP_FOLDER` to be set. It may caused by an older version of '@napi-rs/cli' used. Please upgrade to the latest version.");
panic!("[napi-rs] missing environment variables. please upgrade `@napi-rs/cli` to the latest version.");
}
None
}

View File

@ -27,7 +27,7 @@ use syn::{fold::Fold, parse_macro_input};
pub fn napi(attr: TokenStream, input: TokenStream) -> TokenStream {
match expand::expand(attr.into(), input.into()) {
Ok(tokens) => {
if env::var("DEBUG_GENERATED_CODE").is_ok() {
if env::var("NAPI_DEBUG_GENERATED_CODE").is_ok() {
println!("{}", tokens);
}
tokens.into()