cairo_lang_compiler/
project.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
use std::ffi::OsStr;
use std::path::Path;

use cairo_lang_defs::ids::ModuleId;
use cairo_lang_filesystem::db::{CrateConfiguration, FilesGroupEx};
use cairo_lang_filesystem::ids::{CrateId, CrateLongId, Directory};
pub use cairo_lang_project::*;
use cairo_lang_semantic::db::SemanticGroup;
use cairo_lang_utils::Intern;
use smol_str::SmolStr;

#[derive(thiserror::Error, Debug)]
pub enum ProjectError {
    #[error("Only files with .cairo extension can be compiled.")]
    BadFileExtension,
    #[error("Couldn't read {path}: No such file.")]
    NoSuchFile { path: String },
    #[error("Couldn't handle {path}: Not a legal path.")]
    BadPath { path: String },
    #[error("Failed to load project config.")]
    LoadProjectError,
}

/// Setup to 'db' to compile the file at the given path.
/// Returns the id of the generated crate.
pub fn setup_single_file_project(
    db: &mut dyn SemanticGroup,
    path: &Path,
) -> Result<CrateId, ProjectError> {
    match path.extension().and_then(OsStr::to_str) {
        Some("cairo") => (),
        _ => {
            return Err(ProjectError::BadFileExtension);
        }
    }
    if !path.exists() {
        return Err(ProjectError::NoSuchFile { path: path.to_string_lossy().to_string() });
    }
    let bad_path_err = || ProjectError::BadPath { path: path.to_string_lossy().to_string() };
    let canonical = path.canonicalize().map_err(|_| bad_path_err())?;
    let file_dir = canonical.parent().ok_or_else(bad_path_err)?;
    let file_stem = path.file_stem().and_then(OsStr::to_str).ok_or_else(bad_path_err)?;
    if file_stem == "lib" {
        let crate_name = file_dir.to_str().ok_or_else(bad_path_err)?;
        let crate_id = CrateLongId::Real(crate_name.into()).intern(db);
        db.set_crate_config(
            crate_id,
            Some(CrateConfiguration::default_for_root(Directory::Real(file_dir.to_path_buf()))),
        );
        Ok(crate_id)
    } else {
        // If file_stem is not lib, create a fake lib file.
        let crate_id = CrateLongId::Real(file_stem.into()).intern(db);
        db.set_crate_config(
            crate_id,
            Some(CrateConfiguration::default_for_root(Directory::Real(file_dir.to_path_buf()))),
        );

        let module_id = ModuleId::CrateRoot(crate_id);
        let file_id = db.module_main_file(module_id).unwrap();
        db.as_files_group_mut()
            .override_file_content(file_id, Some(format!("mod {file_stem};").into()));
        Ok(crate_id)
    }
}

/// Updates the crate roots from a ProjectConfig object.
pub fn update_crate_roots_from_project_config(db: &mut dyn SemanticGroup, config: &ProjectConfig) {
    for (crate_name, directory_path) in config.content.crate_roots.iter() {
        let root = Directory::Real(config.absolute_crate_root(directory_path));
        update_crate_root(db, config, crate_name.clone(), root);
    }
}

/// Updates a single crate root from a ProjectConfig.
/// If the crate defines settings in the config, it will be used.
/// Crate is identified by name and the root directory.
pub fn update_crate_root(
    db: &mut dyn SemanticGroup,
    config: &ProjectConfig,
    crate_name: SmolStr,
    root: Directory,
) {
    let crate_settings = config.content.crates_config.get(&crate_name);
    let crate_id = CrateLongId::Real(crate_name).intern(db);
    db.set_crate_config(
        crate_id,
        Some(CrateConfiguration { root, settings: crate_settings.clone() }),
    );
}

/// Setup the 'db' to compile the project in the given path.
/// The path can be either a directory with cairo project file or a .cairo file.
/// Returns the ids of the project crates.
pub fn setup_project(
    db: &mut dyn SemanticGroup,
    path: &Path,
) -> Result<Vec<CrateId>, ProjectError> {
    if path.is_dir() {
        match ProjectConfig::from_directory(path) {
            Ok(config) => {
                let main_crate_ids = get_main_crate_ids_from_project(db, &config);
                update_crate_roots_from_project_config(db, &config);
                Ok(main_crate_ids)
            }
            _ => Err(ProjectError::LoadProjectError),
        }
    } else {
        Ok(vec![setup_single_file_project(db, path)?])
    }
}

/// Checks that the given path is a valid compiler path.
pub fn check_compiler_path(single_file: bool, path: &Path) -> anyhow::Result<()> {
    if path.is_file() {
        if !single_file {
            anyhow::bail!("The given path is a file, but --single-file was not supplied.");
        }
    } else if path.is_dir() {
        if single_file {
            anyhow::bail!("The given path is a directory, but --single-file was supplied.");
        }
    } else {
        anyhow::bail!("The given path does not exist.");
    }
    Ok(())
}

pub fn get_main_crate_ids_from_project(
    db: &mut dyn SemanticGroup,
    config: &ProjectConfig,
) -> Vec<CrateId> {
    config
        .content
        .crate_roots
        .keys()
        .map(|crate_id| CrateLongId::Real(crate_id.clone()).intern(db))
        .collect()
}