cairo_lang_compiler/
project.rs1use 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
27pub 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 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
73pub 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
81pub 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
98pub 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
115pub 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 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}