use std::path::{Path, PathBuf};
use std::sync::OnceLock;
use std::{fs, path};
use anyhow::Context;
use cairo_lang_filesystem::db::{
CORELIB_CRATE_NAME, CORELIB_VERSION, FilesGroup, init_dev_corelib,
};
use cairo_lang_filesystem::ids::{CrateId, CrateLongId};
use cairo_lang_filesystem::set_crate_config;
use cairo_lang_utils::Intern;
use indoc::indoc;
use semver::Version;
use tempfile::tempdir;
use tracing::{error, warn};
use crate::config::Config;
use crate::lang::db::AnalysisDatabase;
use crate::toolchain::scarb::{SCARB_TOML, ScarbToolchain};
pub fn try_to_init_unmanaged_core_if_not_present(
db: &mut AnalysisDatabase,
config: &Config,
scarb: &ScarbToolchain,
) {
if db.crates().contains(&CrateId::core(db)) {
return;
}
if let Some(UnmanagedCore { path, version }) = find_unmanaged_core(config, scarb) {
init_dev_corelib(db, path);
let core_id = CrateLongId::core(db).intern(db);
let mut crate_configs = db.crate_config(core_id).unwrap().clone();
crate_configs.settings.version = Some(version);
set_crate_config!(db, core_id, Some(crate_configs));
} else {
warn!("failed to find unmanaged core crate")
}
}
#[derive(Clone)]
struct UnmanagedCore {
path: PathBuf,
version: Version,
}
fn find_unmanaged_core(config: &Config, scarb: &ScarbToolchain) -> Option<UnmanagedCore> {
find_core_at_config_path(config).or_else(|| find_scarb_managed_core(scarb)).and_then(
|UnmanagedCore { path, version }| {
Some(UnmanagedCore { path: ensure_absolute(path)?, version })
},
)
}
fn find_core_at_config_path(config: &Config) -> Option<UnmanagedCore> {
find_core_at_path(config.unmanaged_core_path.as_ref()?.as_path())
.map(|path| UnmanagedCore { path, version: Version::parse(CORELIB_VERSION).ok().unwrap() })
}
fn find_core_at_path(root_path: &Path) -> Option<PathBuf> {
let mut path = root_path.to_owned();
path.push("corelib");
path.push("src");
if path.exists() {
return Some(path);
}
let mut path = root_path.to_owned();
path.push("src");
if path.exists() {
return Some(path);
}
if root_path.exists() {
return Some(root_path.to_owned());
}
None
}
#[tracing::instrument(skip_all)]
fn find_scarb_managed_core(scarb: &ScarbToolchain) -> Option<UnmanagedCore> {
let lookup = || {
let workspace = tempdir()
.context("failed to create temporary directory")
.inspect_err(|e| warn!("{e:?}"))
.ok()?;
let scarb_toml = workspace.path().join(SCARB_TOML);
fs::write(
&scarb_toml,
indoc! {r#"
[package]
name = "cairols_unmanaged_core_lookup"
version = "1.0.0"
"#},
)
.context("failed to write Scarb.toml")
.inspect_err(|e| warn!("{e:?}"))
.ok()?;
let metadata = scarb.silent().metadata(&scarb_toml).inspect_err(|e| warn!("{e:?}")).ok()?;
let _ = workspace
.close()
.context("failed to wipe temporary directory")
.inspect_err(|e| warn!("{e:?}"));
let path = metadata.compilation_units.into_iter().find_map(|compilation_unit| {
compilation_unit
.components
.iter()
.find(|component| component.name == CORELIB_CRATE_NAME)
.map(|component| component.source_root().to_path_buf().into_std_path_buf())
})?;
let version = metadata
.packages
.into_iter()
.find(|package| package.name == CORELIB_CRATE_NAME)
.map(|package| package.version)?;
Some(UnmanagedCore { path, version })
};
static CACHE: OnceLock<Option<UnmanagedCore>> = OnceLock::new();
CACHE.get_or_init(lookup).clone()
}
fn ensure_absolute(path: PathBuf) -> Option<PathBuf> {
path::absolute(&path)
.with_context(|| {
format!("failed to make `core` crate path absolute: {path}", path = path.display())
})
.inspect_err(|e| error!("{e:?}"))
.ok()
}