cargo_up/runner/
mod.rs

1use crate::{
2    ra_ap_syntax::AstNode,
3    semver::{Error as SemVerError, Version as SemverVersion},
4    utils::{normalize, Error, INTERNAL_ERR},
5    Semantics, Version,
6};
7
8use log::{debug, info, trace};
9use oclif::term::{OUT_YELLOW, TERM_OUT};
10use ra_ap_base_db::{FileId, SourceDatabase, SourceDatabaseExt};
11use ra_ap_hir::Crate;
12use ra_ap_ide_db::symbol_index::SymbolsDatabase;
13use ra_ap_paths::AbsPathBuf;
14use ra_ap_project_model::{CargoConfig, ProjectManifest, ProjectWorkspace};
15use ra_ap_rust_analyzer::cli::load_cargo::{load_workspace, LoadCargoConfig};
16use ra_ap_text_edit::TextEdit;
17use rust_visitor::Visitor;
18
19use std::{
20    collections::HashMap as Map,
21    fs::{read_to_string, write},
22    path::Path,
23};
24
25mod context;
26mod helpers;
27mod visitor_impl;
28
29pub(crate) use context::Context;
30
31pub struct Runner {
32    pub(crate) minimum: Option<SemverVersion>,
33    pub(crate) versions: Vec<Version>,
34    version: SemverVersion,
35}
36
37impl Runner {
38    pub fn new() -> Self {
39        Self {
40            minimum: None,
41            versions: vec![],
42            version: SemverVersion::parse("0.0.0").expect(INTERNAL_ERR),
43        }
44    }
45
46    pub fn minimum(mut self, version: &str) -> Result<Self, SemVerError> {
47        self.minimum = Some(SemverVersion::parse(version)?);
48        Ok(self)
49    }
50
51    pub fn version(mut self, version: Version) -> Self {
52        self.versions.push(version);
53        self
54    }
55}
56
57impl Runner {
58    fn get_version(&self) -> Option<&Version> {
59        self.versions.iter().find(|x| x.version == self.version)
60    }
61}
62
63#[doc(hidden)]
64pub fn run(
65    root: &Path,
66    dep: &str,
67    mut runner: Runner,
68    from: SemverVersion,
69    to: SemverVersion,
70) -> Result<(), Error> {
71    info!("Workspace root: {}", root.display());
72
73    if let Some(min) = &runner.minimum {
74        if from < *min {
75            return Err(Error::NotMinimum(dep.into(), min.to_string()));
76        }
77    }
78
79    runner.version = to;
80    let version = runner.get_version();
81
82    let peers = if let Some(version) = version {
83        let mut peers = vec![dep.to_string()];
84        peers.extend(version.peers.clone());
85        peers
86    } else {
87        return Ok(TERM_OUT.write_line(&format!(
88            "Upgrader for crate {} has not described any changes for {} version",
89            OUT_YELLOW.apply_to(dep),
90            OUT_YELLOW.apply_to(runner.version),
91        ))?);
92    };
93
94    // Loading project
95    let manifest = ProjectManifest::discover_single(&AbsPathBuf::assert(root.into())).unwrap();
96
97    let no_progress = &|_| {};
98
99    let mut cargo_config = CargoConfig::default();
100    cargo_config.no_sysroot = true;
101
102    let mut workspace = ProjectWorkspace::load(manifest, &cargo_config, no_progress)?;
103    let bs = workspace.run_build_scripts(&cargo_config, no_progress)?;
104    workspace.set_build_scripts(bs);
105
106    let load_cargo_config = LoadCargoConfig {
107        load_out_dirs_from_check: true,
108        with_proc_macro: true,
109        prefill_caches: false,
110    };
111    let (host, vfs, _) = load_workspace(workspace, &load_cargo_config).unwrap();
112
113    // Preparing running wrapper
114    let db = host.raw_database();
115    let semantics = Semantics::new(db);
116
117    let mut changes = Map::<FileId, TextEdit>::new();
118    let mut context = Context::new(runner, semantics);
119
120    // Run init hook
121    context.init(&from)?;
122
123    trace!("Crate graph: {:#?}", db.crate_graph());
124
125    // Loop to find and eager load the dep we are upgrading
126    for krate in Crate::all(db) {
127        if let Some(name) = krate.display_name(db) {
128            debug!("Checking if we need to preload: {}", name);
129
130            if let Some(peer) = peers
131                .iter()
132                .find(|x| **x == normalize(&format!("{}", name)))
133            {
134                context.preloader.load(peer, db, &krate);
135            }
136        }
137    }
138
139    // Actual loop to walk through the source code
140    for source_root_id in db.local_roots().iter() {
141        let source_root = db.source_root(*source_root_id);
142        let krates = db.source_root_crates(*source_root_id);
143
144        // Get all crates for this source root and skip if no root files of those crates
145        // are in the root path we are upgrading.
146        if !krates
147            .iter()
148            .filter_map(|crate_id| {
149                let krate: Crate = (*crate_id).into();
150                source_root.path_for_file(&krate.root_file(db))
151            })
152            .filter_map(|path| path.as_path())
153            .any(|path| {
154                debug!("Checking if path in workspace: {}", path.display());
155                path.as_ref().starts_with(root)
156            })
157        {
158            continue;
159        }
160
161        for file_id in source_root.iter() {
162            let file = vfs.file_path(file_id);
163            info!("Walking: {}", file.as_path().expect(INTERNAL_ERR).display());
164
165            let source_file = context.semantics.parse(file_id);
166            trace!("Syntax: {:#?}", source_file.syntax());
167
168            context.walk(source_file.syntax());
169
170            let edit = context.upgrader.finish();
171            debug!("Changes to be made: {:#?}", edit);
172
173            changes.insert(file_id, edit);
174        }
175    }
176
177    // Apply changes
178    for (file_id, edit) in changes {
179        let full_path = vfs.file_path(file_id);
180        let full_path = full_path.as_path().expect(INTERNAL_ERR);
181
182        let mut file_text = read_to_string(&full_path)?;
183
184        edit.apply(&mut file_text);
185        write(&full_path, file_text)?;
186    }
187
188    // TODO: Modify Cargo.toml
189
190    Ok(())
191}