cairo_lang_compiler/
project.rs

1use std::ffi::OsStr;
2use std::path::Path;
3
4use cairo_lang_defs::db::DefsGroup;
5use cairo_lang_defs::ids::ModuleId;
6use cairo_lang_filesystem::db::{
7    CORELIB_CRATE_NAME, CrateConfiguration, CrateIdentifier, CrateSettings, FilesGroup,
8};
9use cairo_lang_filesystem::ids::{CrateId, CrateInput, CrateLongId, Directory, SmolStrId};
10use cairo_lang_filesystem::{override_file_content, set_crate_config};
11pub use cairo_lang_project::*;
12use cairo_lang_utils::Intern;
13use salsa::Database;
14
15#[derive(thiserror::Error, Debug)]
16pub enum ProjectError {
17    #[error("Only files with .cairo extension can be compiled.")]
18    BadFileExtension,
19    #[error("Couldn't read {path}: No such file.")]
20    NoSuchFile { path: String },
21    #[error("Couldn't handle {path}: Not a legal path.")]
22    BadPath { path: String },
23    #[error("Failed to load project config: {0}")]
24    LoadProjectError(DeserializationError),
25}
26
27/// Set up the 'db' to compile the file at the given path.
28/// Returns the id of the generated crate.
29pub fn setup_single_file_project(
30    db: &mut dyn Database,
31    path: &Path,
32) -> Result<CrateInput, ProjectError> {
33    match path.extension().and_then(OsStr::to_str) {
34        Some("cairo") => (),
35        _ => {
36            return Err(ProjectError::BadFileExtension);
37        }
38    }
39    if !path.exists() {
40        return Err(ProjectError::NoSuchFile { path: path.to_string_lossy().to_string() });
41    }
42    let bad_path_err = || ProjectError::BadPath { path: path.to_string_lossy().to_string() };
43    let canonical = path.canonicalize().map_err(|_| bad_path_err())?;
44    let file_dir = canonical.parent().ok_or_else(bad_path_err)?;
45    let file_stem = path.file_stem().and_then(OsStr::to_str).ok_or_else(bad_path_err)?;
46    if file_stem == "lib" {
47        let crate_name = file_dir.to_str().ok_or_else(bad_path_err)?;
48        let crate_id = CrateId::plain(db, SmolStrId::from(db, crate_name));
49        set_crate_config!(
50            db,
51            crate_id,
52            Some(CrateConfiguration::default_for_root(Directory::Real(file_dir.to_path_buf())))
53        );
54        let crate_id = CrateId::plain(db, SmolStrId::from(db, crate_name));
55        Ok(crate_id.long(db).clone().into_crate_input(db))
56    } else {
57        // If file_stem is not lib, create a fake lib file.
58        let crate_id = CrateId::plain(db, SmolStrId::from(db, file_stem));
59        set_crate_config!(
60            db,
61            crate_id,
62            Some(CrateConfiguration::default_for_root(Directory::Real(file_dir.to_path_buf())))
63        );
64        let crate_id = CrateId::plain(db, SmolStrId::from(db, file_stem));
65        let module_id = ModuleId::CrateRoot(crate_id);
66        let file_id = db.module_main_file(module_id).unwrap();
67        override_file_content!(db, file_id, Some(format!("mod {file_stem};").into()));
68        let crate_id = CrateId::plain(db, SmolStrId::from(db, file_stem));
69        Ok(crate_id.long(db).clone().into_crate_input(db))
70    }
71}
72
73/// Updates the crate roots from a ProjectConfig object.
74pub fn update_crate_roots_from_project_config(db: &mut dyn Database, config: &ProjectConfig) {
75    for (crate_identifier, directory_path) in config.content.crate_roots.iter() {
76        let root = Directory::Real(config.absolute_crate_root(directory_path));
77        update_crate_root(db, config, crate_identifier, root);
78    }
79}
80
81/// Updates a single crate root from a ProjectConfig.
82/// If the crate defines settings in the config, it will be used.
83/// Crate is identified by name and the root directory.
84pub fn update_crate_root(
85    db: &mut dyn Database,
86    config: &ProjectConfig,
87    crate_identifier: &CrateIdentifier,
88    root: Directory<'_>,
89) {
90    let (crate_id, crate_settings) = get_crate_id_and_settings(db, crate_identifier, config);
91    set_crate_config!(
92        db,
93        crate_id,
94        Some(CrateConfiguration { root, settings: crate_settings.clone(), cache_file: None })
95    );
96}
97
98/// Setup the 'db' to compile the project in the given path.
99/// The path can be either a directory with cairo project file or a .cairo file.
100/// Returns the ids of the project crates.
101pub fn setup_project(db: &mut dyn Database, path: &Path) -> Result<Vec<CrateInput>, ProjectError> {
102    if path.is_dir() {
103        let config = ProjectConfig::from_directory(path).map_err(ProjectError::LoadProjectError)?;
104        let main_crate_ids: Vec<_> = get_main_crate_ids_from_project(db, &config)
105            .into_iter()
106            .map(|id| id.long(db).clone().into_crate_input(db))
107            .collect();
108        update_crate_roots_from_project_config(db, &config);
109        Ok(main_crate_ids)
110    } else {
111        Ok(vec![setup_single_file_project(db, path)?])
112    }
113}
114
115/// Checks that the given path is a valid compiler path.
116pub fn check_compiler_path(single_file: bool, path: &Path) -> anyhow::Result<()> {
117    if path.is_file() {
118        if !single_file {
119            anyhow::bail!("The given path is a file, but --single-file was not supplied.");
120        }
121    } else if path.is_dir() {
122        if single_file {
123            anyhow::bail!("The given path is a directory, but --single-file was supplied.");
124        }
125    } else {
126        anyhow::bail!("The given path does not exist.");
127    }
128    Ok(())
129}
130
131pub fn get_main_crate_ids_from_project<'db>(
132    db: &'db dyn Database,
133    config: &ProjectConfig,
134) -> Vec<CrateId<'db>> {
135    config
136        .content
137        .crate_roots
138        .keys()
139        .map(|crate_identifier| get_crate_id_and_settings(db, crate_identifier, config).0)
140        .collect()
141}
142
143fn get_crate_id_and_settings<'db, 'a>(
144    db: &'db dyn Database,
145    crate_identifier: &CrateIdentifier,
146    config: &'a ProjectConfig,
147) -> (CrateId<'db>, &'a CrateSettings) {
148    let crate_settings = config.content.crates_config.get(crate_identifier);
149    let name = crate_settings.name.clone().unwrap_or_else(|| crate_identifier.clone().into());
150    // It has to be done due to how `CrateId::core` works.
151    let discriminator =
152        if name == CORELIB_CRATE_NAME { None } else { Some(crate_identifier.clone().into()) };
153
154    let crate_id = CrateLongId::Real { name: SmolStrId::from(db, name), discriminator }.intern(db);
155
156    (crate_id, crate_settings)
157}