← Оглавление

5.3 Что несёт воздух

Хорошая инструкция описывает общее; детали - дело мастерской. Где хранить готовые изделия - не вопрос инструмента, а вопрос места. На одном станке это один угол, на другом - другой, на временном - нигде и ненадолго. Файл с настройкой не умеет говорить сам за себя. Его приходится менять перед переездом. Переезд случается чаще, чем планировали.

Что нужно

tq читает config.toml - берёт data_dir оттуда, или использует текущий каталог по умолчанию. Это работает для одного места. Разработчик с данными в ~/tq-data и CI с данными в /tmp/ci-tq должны либо менять файл перед каждым запуском, либо держать два файла и подкладывать нужный. Оба варианта предполагают ручную работу.

Переменная среды решает вопрос иначе: не меняет файл, не требует версионирования, живёт ровно столько, сколько нужно - один запуск, один сеанс, один job в CI.

Сборка

std::env::var("TQ_DATA_DIR") возвращает Ok(String) если переменная установлена, и Err если нет. if let Ok(dir) берёт значение только в успешном случае; если переменная не задана, блок пропускается.

В src/config/mod.rs два изменения - config становится mut, и после загрузки файла добавляется проверка переменной среды:

// src/config/mod.rs - CHANGED
pub fn load(path: &Path) -> TqResult<Config> {
    let mut config = if !path.exists() {           // CHANGED: let -> let mut
        Config::default()
    } else {
        let content = fs::read_to_string(path).map_err(|e| TqError::Parse(e.to_string()))?;
        toml::from_str(&content).map_err(|e| TqError::Parse(e.to_string()))?
    };

    if let Ok(dir) = std::env::var("TQ_DATA_DIR") {  // NEW
        config.data_dir = PathBuf::from(dir);          // NEW
    }                                                   // NEW

    if !config.data_dir.exists() {
        return Err(TqError::Parse(format!(
            "data_dir '{}' does not exist; create it with: mkdir -p {}",
            config.data_dir.display(),
            config.data_dir.display()
        )));
    }
    Ok(config)
}

Сначала загружаем файл (или берём default), потом - если переменная задана - перезаписываем data_dir. Проверка существования директории стоит в конце, после всех источников. Приоритет: TQ_DATA_DIR > config.toml > default.

Заглавные буквы и префикс TQ_ - соглашение Unix: переменные среды читает любой инструмент, и имя должно быть достаточно уникальным, чтобы не пересечься с чужими.

Тест для нового пути - в src/config/tests.rs. В Rust 2024 set_var требует unsafe (детали - в M6.x), и переменную убираем до unwrap() - если тест упадёт на проверке, remove_var уже выполнен:

#[test]
fn env_var_overrides_data_dir() {
    let tmp = std::env::temp_dir().join("tq-m53-env");
    std::fs::create_dir_all(&tmp).unwrap();
    unsafe { std::env::set_var("TQ_DATA_DIR", &tmp) };
    let result = Config::load(Path::new("nonexistent.toml"));
    unsafe { std::env::remove_var("TQ_DATA_DIR") };
    assert_eq!(result.unwrap().data_dir, tmp);
}

Примечание. env_var_overrides_data_dir меняет TQ_DATA_DIR - переменную окружения процесса, общую для всех потоков. При параллельном запуске тестов два теста могут одновременно устанавливать разные значения и мешать друг другу. В этой книге мы выбираем простой путь: make ci запускает тесты с флагом --test-threads=1, что устраняет возможную проблему.

# Makefile - CHANGED
ci:
	@cargo fmt --check && cargo clippy -- -D warnings && cargo test -- --test-threads=1

Результат

$ mkdir -p /tmp/tq-test
$ TQ_DATA_DIR=/tmp/tq-test cargo run -- list

Список пустой: /tmp/tq-test не содержит tasks.json. Это ожидаемо - переменная меняет то, куда смотрит tq, а не то, что там лежит.

Неверный путь:

$ TQ_DATA_DIR=/nonexistent cargo run -- list
tq: parse error: data_dir '/nonexistent' does not exist; create it with: mkdir -p /nonexistent

make ci проходит.

Полный код tq для этой главы - 5-interface/03-what-the-air-carries/.