use std::path::Path;
pub fn assert_plugin_version() {
let manifest_dir = env_or_panic("CARGO_MANIFEST_DIR");
assert_plugin_version_at(Path::new(&manifest_dir).join("plugin"));
}
pub fn assert_plugin_version_at(tree_dir: impl AsRef<Path>) {
let tree_dir = tree_dir.as_ref();
let plugin_json = tree_dir.join(".claude-plugin").join("plugin.json");
let pkg_version = env_or_panic("CARGO_PKG_VERSION");
let bytes = std::fs::read(&plugin_json).unwrap_or_else(|e| {
panic!(
"agentgear: cannot read {} ({e}); ship the plugin tree there or call assert_plugin_version_at with the right dir",
plugin_json.display()
)
});
let manifest: serde_json::Value =
serde_json::from_slice(&bytes).unwrap_or_else(|e| panic!("agentgear: {} is not valid JSON: {e}", plugin_json.display()));
let plugin_version = manifest
.get("version")
.and_then(|v| v.as_str())
.unwrap_or_else(|| panic!("agentgear: {} has no string `version`", plugin_json.display()));
assert!(
plugin_version == pkg_version,
"agentgear: plugin.json version `{plugin_version}` != CARGO_PKG_VERSION `{pkg_version}`; \
bump Cargo.toml and plugin.json together (CC caches on version, so a mismatch ships a silent no-op)"
);
println!("cargo:rerun-if-changed={}", tree_dir.display());
println!("cargo:rustc-env=AGENTGEAR_GUARD=1");
warn_non_portable(tree_dir);
#[cfg(feature = "embed")]
embed_blob(tree_dir);
}
fn warn_non_portable(tree_dir: &Path) {
let Ok(entries) = crate::materialize::dir_entries(tree_dir) else {
return;
};
let Ok(components) = crate::components::PluginComponents::parse(&entries) else {
return;
};
if let Some(warning) = non_portable_warning(&components) {
println!("cargo:warning={warning}");
}
}
fn non_portable_warning(components: &crate::components::PluginComponents) -> Option<String> {
let mut entries = Vec::new();
for server in &components.mcp_servers {
if !server.is_portable() {
entries.push(format!("mcp server `{}`", server.name));
}
}
for hook in &components.hooks {
if !hook.is_portable() {
entries.push(format!("`{}` hook", hook.event));
}
}
if entries.is_empty() {
return None;
}
Some(format!(
"agentgear: {} use ${{CLAUDE_PLUGIN_ROOT}}, which only expands inside Claude Code, so every non-Claude backend skips them. \
This is the canonical shape for a Claude-Code-only plugin; it only matters if you also target another agent.",
entries.join(", ")
))
}
#[cfg(feature = "embed")]
fn embed_blob(tree_dir: &Path) {
let out_dir = env_or_panic("OUT_DIR");
let blob = crate::materialize::compress_dir(tree_dir)
.unwrap_or_else(|e| panic!("agentgear: failed to compress the plugin tree at {} ({e})", tree_dir.display()));
let blob_path = Path::new(&out_dir).join("agentgear.tar.br");
std::fs::write(&blob_path, &blob).unwrap_or_else(|e| panic!("agentgear: cannot write {} ({e})", blob_path.display()));
println!("cargo:rustc-env=AGENTGEAR_BLOB={}", blob_path.display());
}
fn env_or_panic(key: &str) -> String {
std::env::var(key).unwrap_or_else(|_| panic!("agentgear: {key} is not set (call this from a build.rs)"))
}
#[cfg(test)]
#[path = "../tests/unit/build.rs"]
mod build_tests;