use {
crate::{
config::{Config, Manifest, PackageManager, WithPath},
VERSION,
},
anyhow::{anyhow, Result},
semver::{Version, VersionReq},
std::{fs, path::Path},
};
pub fn check_overflow(cargo_toml_path: impl AsRef<Path>) -> Result<bool> {
Manifest::from_path(cargo_toml_path)?
.profile
.release
.as_ref()
.and_then(|profile| profile.overflow_checks)
.ok_or(anyhow!(
"`overflow-checks` is not enabled. To enable, \
add:\n\n[profile.release]\noverflow-checks = true\n\nin workspace root Cargo.toml",
))
}
fn anchor_crate_names(manifest: &cargo_toml::Manifest) -> Option<(&'static str, &'static str)> {
if manifest.dependencies.contains_key("anchor-lang-v2") {
Some(("anchor-lang-v2", "anchor-spl-v2"))
} else if manifest.dependencies.contains_key("anchor-lang") {
Some(("anchor-lang", "anchor-spl"))
} else {
None
}
}
pub fn check_anchor_version(cfg: &WithPath<Config>) -> Result<()> {
let cli_version = Version::parse(VERSION)?;
let manifests: Vec<cargo_toml::Manifest> = cfg
.get_program_list()?
.into_iter()
.map(|path| path.join("Cargo.toml"))
.filter_map(|path| cargo_toml::Manifest::from_path(path).ok())
.collect();
for dep_name in ["anchor-lang", "anchor-lang-v2"] {
let mismatched = manifests
.iter()
.filter_map(|m| m.dependencies.get(dep_name))
.filter_map(|dep| Version::parse(dep.req()).ok())
.find(|ver| ver != &cli_version);
if let Some(ver) = mismatched {
eprintln!(
"WARNING: `{dep_name}` version({ver}) and the current CLI version({cli_version}) \
don't match.\n\n\tThis can lead to unwanted behavior. To use the same CLI \
version, add:\n\n\t[toolchain]\n\tanchor_version = \"{ver}\"\n\n\tto \
Anchor.toml\n"
);
}
}
let package_json = {
let package_json_path = cfg.path().parent().unwrap().join("package.json");
let package_json_content = fs::read_to_string(package_json_path)?;
serde_json::from_str::<serde_json::Value>(&package_json_content)?
};
let mismatched_ts_version = package_json
.get("dependencies")
.and_then(|deps| deps.get("@anchor-lang/core"))
.and_then(|ver| ver.as_str())
.and_then(|ver| VersionReq::parse(ver).ok())
.filter(|ver| !ver.matches(&cli_version));
if let Some(ver) = mismatched_ts_version {
let update_cmd = match cfg
.toolchain
.package_manager
.clone()
.unwrap_or(PackageManager::NPM)
{
PackageManager::NPM => "npm update",
PackageManager::Yarn => "yarn upgrade",
PackageManager::PNPM => "pnpm update",
PackageManager::Bun => "bun update",
};
eprintln!(
"WARNING: `@anchor-lang/core` version({ver}) and the current CLI \
version({cli_version}) don't match.\n\n\tThis can lead to unwanted behavior. To fix, \
upgrade the package by running:\n\n\t{update_cmd} @anchor-lang/core@{cli_version}\n"
);
}
Ok(())
}
pub fn check_deps(cfg: &WithPath<Config>) -> Result<()> {
fn compatible_solana_program(version_req: &str) -> bool {
let Ok(req) = VersionReq::parse(version_req) else {
return false;
};
let version = include_str!("../solana-program-version").trim();
let workspace_solana_prog_version = semver::Version::parse(version).unwrap();
req.matches(&workspace_solana_prog_version)
}
cfg.get_program_list()?
.into_iter()
.map(|path| path.join("Cargo.toml"))
.map(cargo_toml::Manifest::from_path)
.map(|man| man.map_err(|e| anyhow!("Failed to read manifest: {e}")))
.collect::<Result<Vec<_>>>()?
.into_iter()
.filter(|man| {
man.dependencies
.get("solana-program")
.is_some_and(|dep| match dep {
cargo_toml::Dependency::Simple(version) => !compatible_solana_program(version),
cargo_toml::Dependency::Detailed(detail) => {
if let Some(version) = &detail.version {
!compatible_solana_program(version)
} else {
true
}
}
_ => true,
})
})
.for_each(|man| {
eprintln!(
"WARNING: Adding `solana-program` as a separate dependency might cause \
conflicts.\nTo solve, remove the `solana-program` dependency and use the \
exported crate from `anchor-lang`.\n`use solana_program` becomes `use \
anchor_lang::solana_program`.\nProgram name: `{}`\n",
man.package().name()
)
});
Ok(())
}
pub fn check_idl_build_feature() -> Result<()> {
let manifest_path = Path::new("Cargo.toml").canonicalize()?;
let manifest = Manifest::from_path(&manifest_path)?;
let (lang_crate, spl_crate) =
anchor_crate_names(&manifest).unwrap_or(("anchor-lang", "anchor-spl"));
let has_idl_build_feature = manifest
.features
.iter()
.any(|(feature, _)| feature == "idl-build");
if !has_idl_build_feature {
let anchor_spl_idl_build = if manifest.dependencies.contains_key(spl_crate) {
format!(r#", "{spl_crate}/idl-build""#)
} else {
String::new()
};
return Err(anyhow!(
r#"`idl-build` feature is missing. To solve, add
[features]
idl-build = ["{lang_crate}/idl-build"{anchor_spl_idl_build}]
in `{manifest_path:?}`."#
));
}
manifest
.dependencies
.iter()
.filter(|(_, dep)| dep.req_features().contains(&"idl-build".into()))
.for_each(|(name, _)| {
eprintln!(
"WARNING: `idl-build` feature of crate `{name}` is enabled by default. This is \
not the intended usage.\n\n\tTo solve, do not enable the `idl-build` feature and \
include crates that have `idl-build` feature in the `idl-build` feature \
list:\n\n\t[features]\n\tidl-build = [\"{name}/idl-build\", ...]\n"
)
});
let spl_feature = format!("{spl_crate}/idl-build");
manifest
.dependencies
.get(spl_crate)
.and_then(|_| manifest.features.get("idl-build"))
.map(|feature_list| !feature_list.contains(&spl_feature))
.unwrap_or_default()
.then(|| {
eprintln!(
"WARNING: `idl-build` feature of `{spl_crate}` is not enabled. This is likely to \
result in cryptic compile errors.\n\n\tTo solve, add `{spl_feature}` to the \
`idl-build` feature list:\n\n\t[features]\n\tidl-build = [\"{spl_feature}\", \
...]\n"
)
});
Ok(())
}