1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/mirrors-rust-language-server

Клонировать/Скачать
architecture.md 20 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 08.06.2025 20:50 545024d

Языковый сервер Rust (RLS) - Архитектура

Предисловие

Кроме данного документа, общую архитектуру можно найти в блоге поста @nrc Как работает RLS (2017). Хотя некоторые части изменились, основная идея осталась той же.

Здесь мы стремимся подробно объяснить, как RLS получает подлежащие данные для драйвера своих индексных функций в качестве контекста для предстоящего планирования и обсуждения IDE на Rust All-Hands 2019 года.

Также руководство rust-analyzer является отличным ресурсом, так как оно охватывает много общего.

Высокоуровневый обзор

На момент написания, на самом высоком уровне RLS компилирует ваш пакет/рабочее пространство (аналогично cargo check) и использует внутренние структуры данных компилятора Rust для обеспечения своих индексных функций.

При инициализации (если не переопределено пользовательской командой сборки) RLS выполняет cargo check текущего проекта и собирает граф зависимостей между crates [1] вместе с точными вызовами компиляции crates, которые используются позже для повторного запуска компилятора (но в процессе).Запуски компиляции в процессе возвращают заполненные внутренние структуры данных (rls_data::Analysis), которые затем упрощаются и ссылаются друг на друга для предоставления низкоуровневого API индексации (rls_analysis::Analysis), который в конечном итоге используется языковым сервером Rust для ответа на соответствующие запросы LSP [2].Основная причина, по которой мы выполняем компиляцию в процессе, заключается в оптимизации задержки — мы передаем полученные структуры данных в памяти. Для зависимостей, которые не меняются часто (неосновные/зависимости по пути), мы выполняем компиляцию вне процесса один раз, где мы сохраняем и кэшируем полученные данные в JSON-файл, который требуется прочитать только один раз при начале индексации.

[1] crate — это единица компиляции, компилируемая rustc. Например, пакет Cargo с bin+lib имеет два crates (иногда называемых целями Cargo).

[2] Языковой серверный протокол — это языково-независимый протокол JSON-RPC, который служит для предоставления общих языковых "умных" операций через стандартизированное интерфейсное устройство, независимо от используемого IDE/редактора.

Поток информации (детально)

Текущий поток информации следующий:

rustc -> rustc_save_analysis -> rls_data -> rls_analysis -> rls

rustc_save_analysisКомпилятор Rust включает в себя крейт rustc_save_analysis, который позволяет сохранять информацию о текущем компилируемом крейте. Основная точка входа — это функция process_crate, которая обходит AST после расширения макросов и сохраняет собранные данные либо записывает их в JSON-файл, либо возвращает данные структуры.### rls_data

Как упоминалось ранее, возвращаемая структура данных — это rls_data::Analysis внутри крейта rls_data:```rust /// В основном это типовой псевдоним, мы ссылаемся на узлы с HIR id. /// Все узлы ниже либо идентифицируются, либо ссылаются на эти id. type Id = rustc::hir::def_id::DefId;

pub struct Анализ {
    . . .
    /// Содержит вызов rustc, который создал эти данные сохраненного анализа. В настоящее время используется для поддержки RLS в контексте пользовательской системы сборки (например, Buck).
    pub компиляция: Option<CompilationOptions>,
    /// Путь к основному файлу текущего крейта и `GlobalCrateId` текущего крейта. Также включает `GlobalCrateId` внешних крейтов и их локальные индексы `CrateNum`, сохраненные rustc с точки зрения этого крейта.
    pub прелюд: Option<CratePreludeData>,
    /// Узлы леса деревьев использования, включая отношение, диапазон, опциональное значение псевдонима и тип (внешний крейт, простое использование или glob).
    pub импорты: Vec<Import>,
    /// Основные узлы данных. Приблизительно соответствуют узлам расширенного AST, включая диапазон, квалифицированное имя, отношения, опциональную сигнатуру (если это функция), документацию и атрибуты.
    pub определения: Vec<Def>,
    /// Узлы для элементов `impl`, включая тип (внутренний, реализация трейта, . . . ), диапазон, идентификаторы дочерних элементов, документацию, атрибуты и сигнатуру.
    pub импли: Vec<Impl>,
    /// Диапазон, который ссылается на `Id` типа функция/модуль/тип/переменная.
    pub ссылки: Vec<Ref>,
    /// Содержит вызовы и вызываемые диапазоны вместе с квалифицированным именем макроса.
}
``````rust
   pub макрос_ссылки: Vec<MacroRef>,
   /// Отношение Impl/SuperTrait между `id` с ассоциированным диапазоном.
   pub отношения: Vec<Relation>,
}
```

### [rls_analysis](https://github.com/rust-lang/rls/tree/master/rls-analysis)Этот [crate](https://github.com/rust-lang/rls/tree/master/rls-analysis) отвечает за загрузку и объединение нескольких структур данных `rls_data::Analysis` в единый, согласованный интерфейс.

В то время как формат `rls_data` можно рассматривать как деталь реализации, которая может изменяться, этот crate стремится предоставить 'стабильный' API.

Еще одной причиной является то, что каждая из этих структур содержит данные, центрированные вокруг компилируемого crate. Этот [понижение](https://github.com/rust-lang/rls/blob/3df74381f37617ec800537c11fb0c3130f5f3616/rls-analysis/src/lowering.rs#L161) кросс-ссылается на данные и индексирует их, что приводит к созданию базы данных, охватывающей несколько crate, которую можно запросить, например, "по всем известным crate, найти все ссылки на определение в данном диапазоне" или подобное.

Мы можем обновлять индекс с новыми данными crate. Каждый раз, когда мы встречаем новый crate, мы [записываем и переводим](https://github.com/rust-lang/rls/blob/3df74381f37617ec800537c11fb0c3130f5f3616/rls-analysis/src/lowering.rs#L122-L144) идентификатор crate в нашу карту идентификаторов crate на уровне базы данных.

Однако, если данные для уже пониженного crate загружаются снова, мы просто заменяем определения для данного crate и переиндексируем.Одним интересным граничным случаем является ситуация, когда мы понижаем данные для crate с одинаковым именем, например, для бинарного и `#[cfg(test)]`-скомпилированной версии. Нам нужно убедиться, что мы понижаем данное определение [только один раз](https://github.com/rust-lang/rls/blob/3df74381f37617ec800537c11fb0c3130f5f3616/rls-analysis/src/lowering.rs#L271-L276), даже если оно технически повторяется в нескольких crate.### rls

С учетом всех логик понижения данных, все, что нам нужно сделать, это фактически получить данные  это происходит внутри RLS.

В общем, помимо функции LSP сервера, RLS также занимается оркестровкой сборки и координацией других компонентов, таких как

* Racer для автозаполнения
* Cargo для обнаружения структуры проекта и координации начальной сборки
* внутренний виртуальный файловый систем (VFS) для обработки текстовых буферов в памяти,
* rls-analysis, выступающий в роли нашей базы знаний
* Clippy для выполнения дополнительных проверок
* Rustfmt для управления нашими возможностями форматирования

После выполнения начальной компиляции с помощью Cargo мы кэшируем подграф графа взаимной зависимости crate-ов, а также вызовы компиляции и входные файлы для тех crate-ов, которые нас интересуют (внутри [основных или путь-ориентированных пакетов](https://github.com/rust-lang/rls/blob/3df74381f37617ec800537c11fb0c3130f5f3616/rls/src/build/cargo.rs#L358-L363)),
которые мы позже перезапускаем вручную.

В нашем случае Cargo настроен на использование отдельной целевой директории
(`$target-dir/rls`), чтобы анализ не мешал обычным сборкам.Мы перехватываем процесс сборки Cargo не только для записи данных о сборке, упомянутых выше, но также для внедрения дополнительных флагов компилятора, заставляющих компилятор выгружать JSON-файлы сохраненного анализа для каждого зависимости. Это служит оптимизацией, чтобы нам не приходилось заново проверять зависимости в свежем запуске.

Исправленный текст:
Мы перехватываем процесс сборки Cargo не только для записи данных о сборке, упомянутых выше, но также для внедрения дополнительных флагов компилятора, заставляющих компилятор выгружать JSON-файлы сохраненного анализа для каждой зависимости. Это служит оптимизацией, чтобы нам не приходилось заново проверять зависимости в свежем запуске.Поскольку RLS стремится предоставлять правдивое представление о компиляции и поддерживать паритет с обычным потоком `cargo check`, наши запуски Cargo также выполняют `build.rs` и макросы процесса вначале и при необходимости (например, при изменении файла, вызывающем перезапуск `build.rs`).

## Расписание сборки

При каждом значимом изменении файла мы [отмечаем файлы как испорченные](https://github.com/rust-lang/rls/blob/3df74381f37617ec800537c11fb0c3130f5f3616/rls/src/actions/notifications.rs#L113) и планируем обычную сборку.

В настоящее время мы различаем два [приоритета сборки](https://github.com/rust-lang/rls/blob/3df74381f37617ec800537c11fb0c3130f5f3616/rls/src/build/mod.rs#L114-L124):

* Обычный
* Cargo

Последний планируется всякий раз, когда происходит изменение, которое может повлиять на весь проект. Это включает:* [Первоначальная сборка](https://github.com/rust-lang/rls/blob/3df74381f37617ec800537c11fb0c3130f5f3616/rls/src/actions/mod.rs#L328)
* [Изменение конфигурации](https://github.com/rust-lang/rls/blob/3df74381f37617ec800537c11fb0c3130f5f3616/rls/src/actions/notifications.rs#L116)
(может потенциально собрать другой набор пакетов)
* [Изменение Cargo.toml](https://github.com/rust-lang/rls/blob/3df74381f37617ec800537c11fb0c3130f5f3616/rls/src/actions/notifications.rs#L264)
(тоже самое)
* [Изменение директории сборки](https://github.com/rust-lang/rls/blob/3df74381f37617ec800537c11fb0c3130f5f3616/rls/src/build/mod.rs#L468-L472)
* [Изменение файла](https://github.com/rust-lang/rls/blob/3df74381f37617ec800537c11fb0c3130f5f3616/rls/src/build/cargo_plan.rs#L350-L354) в пакете, который мы не собирали
* [Изменение build.rs](https://github.com/rust-lang/rls/blob/3df74381f37617ec800537c11fb0c3130f5f3616/rls/src/build/cargo_plan.rs#L359-L360)

В обычном сборке мы отображаем загрязненные файлы в загрязненные crate, сортируем их топологически и запускаем rustc для каждого crate отдельно. С каждым компиляционным процессом мы напрямую [получаем `rls_data::Analysis`](https://github.com/rust-lang/rls/blob/3df74381f37617ec800537c11fb0c3130f5f3616/rls/src/build/rustc.rs#L293-L305) в обратном вызове, [отмечаем соответствующие файлы как собранные](https://github.com/rust-lang/rls/blob/3df74381f37617ec800537c11fb0c3130f5f3616/rls/src/build/mod.rs#L478-L490) и, наконец, [обновляем нашу базу данных анализа](https://github.com/rust-lang/rls/blob/3df74381f37617ec800537c11fb0c3130f5f3616/rls/src/actions/post_build.rs#L180-L184) текущими данными сборки для каждого пересобранного crate.Если после того, как мы запланировали сборку, файлы все еще были изменены (пользователь продолжал вводить данные), мы не считаем сборку завершенной и запускаем обычную сборку снова. Стоит отметить, что мы объединяем сборки, когда пользователь продолжал вводить данные до запуска сборки (мы буферизируем запросы на сборку, чтобы не тратить время на что-то, что мы можем потенциально аннулировать).## Ввод/вывод

Как упоминалось ранее, мы запускаем Cargo с помощью отдельной целевой директории, поэтому мы выполняем ту же работу, что и Cargo, а также сохраняем JSON-файлы анализа для наших зависимостей, не имеющих пути.

### VFS

Чтобы позволить выполнение анализа на незаписанных в память текстовых буферах, мы используем крейт [`rls-vfs`](https://github.com/rust-lang/rls/tree/master/rls-vfs) в качестве нашего виртуального файловой системы.

Компилятор Rust поддерживает использование пользовательских поставщиков файлов через трейт [`FileLoader`](https://github.com/rust-lang/rust/blame/f19851069efd6ee1fe899a469f08ad2d66e76050/compiler/rustc_span/src/source_map.rs#L98-L105), который [мы используем](https://github.com/rust-lang/rls/blob/3df74381f37617ec800537c11fb0c3130f5f3616/rls/src/build/rustc.rs#L355-L367).

Он делегирует реальной файловой системе, когда нет буферизированных изменений в файле, но подаёт незаписанные буферы [из VFS](https://github.com/rust-lang/rls/blob/3df74381f37617ec800537c11fb0c3130f5f3616/rls/src/build/rustc.rs#L54) в противном случае.

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/mirrors-rust-language-server.git
git@api.gitlife.ru:oschina-mirror/mirrors-rust-language-server.git
oschina-mirror
mirrors-rust-language-server
mirrors-rust-language-server
master