#[cfg]
и feature
Добро пожаловать в главу нашего курса по Rust, где мы подробно разберём кроссплатформенную разработку. Rust позиционируется как язык, который из коробки поддерживает написание надёжного и переносимого кода для множества платформ. В этой лекции мы изучим инструменты и подходы для создания приложений, которые без лишних усилий работают на Windows, Linux, macOS и других системах. Мы рассмотрим условную компиляцию, настройку кросс-компиляции, особенности операционных систем, тестирование и практические примеры. Лекция завершится упражнением, которое поможет закрепить материал.
#[cfg]
и feature
Условная компиляция — это способ адаптировать код под разные платформы или условия без лишних runtime-проверок, это сердце кроссплатформенности в Rust. Она позволяет компилировать только нужный код в зависимости от платформы, архитектуры или пользовательских настроек.
Rust предоставляет мощный механизм условной компиляции через атрибут #[cfg]. Это позволяет включать или исключать код в зависимости от целевой платформы, конфигурации или пользовательских флагов.
#[cfg]
Атрибут #[cfg]
— это макрос, который Rust использует на этапе компиляции для включения или исключения кода. Если условие истинно, код компилируется; если ложно — игнорируется. Условия задаются через предопределённые переменные или пользовательские флаги.
Rust поддерживает множество предопределённых переменных, таких как:
target_os
: определяет операционную систему, возможные значения: "linux"
, "windows"
, "macos"
, "unix"
, "freebsd"
, и другие.target_arch
: определяет архитектуру, например, "x86_64"
, "aarch64"
, "arm"
.target_family
: определяет семейство ОС, например, "unix"
, "windows"
.target_pointer_width
: определяет разрядность указателей, например, "32"
, "64"
.fn main() {
#[cfg(target_os = "linux")]
println!("Linux приветствует вас!");
#[cfg(target_os = "windows")]
println!("Windows приветствует вас!");
#[cfg(not(target_os = "macos"))]
println!("Это точно не macOS!");
}
Полный список целей доступен в документации Rust: Rust targets.
В примере компилятор проверяет цель (target), заданную через --target
, и включает только соответствующий код.
target_os
— переменная, указывающая целевую ОС. Возможные значения: "linux"
, "windows", "macos"
, etc.not
— инверсия условия.Rust поддерживает логические операторы для сложных проверок:
all(...)
: все условия должны быть истинны.any(...)
: хотя бы одно условие истинно.not(...)
: инверсия условия.#[cfg(all(target_os = "linux", target_pointer_width = "64"))]
fn linux_64bit() {
println!("Linux 64-bit!");
}
#[cfg(any(target_os = "windows", target_os = "macos"))]
fn win_or_mac() {
println!("Windows или macOS!");
}
Вы можете определить свои флаги компиляции через командную строку с --cfg
или файл конфигурации.
В файле конфигурации config.toml
. Файл config.toml
находится в корне проекта (рядом с Cargo.toml
) или в домашней директории (~/.cargo/config.toml
для глобальных настроек). В разделе [build]
указываются флаги через ключ rustflags
, где можно передать --cfg
. Rust компилятор (rustc
) читает эти настройки и применяет их при сборке.
config.toml
[build]
rustflags = ["--cfg", "debug_mode"]
main.rs
#[cfg(debug_mode)]
fn debug_only() {
println!("Режим отладки!");
}
Запуск с пользовательским флагом: cargo build --cfg debug_mode
. Это полезно для экспериментальных функций или отладки.
feature
в Cargo.toml
Для библиотек и проектов Cargo использует фич-флаги, которые задаются в Cargo.toml
:
[features]
default = ["core"]
core = []
windows_gui = []
Использование в коде:
#[cfg(feature = "windows_gui")]
fn gui_mode() {
println!("GUI-режим для Windows");
}
Запуск с активацией фичи: cargo build --features windows_gui
.
Избегайте путаницы между #[cfg]
(условие на этапе компиляции) и cfg!()
(возвращает bool
во время выполнения). Чрезмерное использование #[cfg]
делает код трудно читаемым. Для больших различий в логике между платформами рекомендуется вынести платформозависимую логику в отдельные модули: linux_impl.rs
, windows_impl.rs
.
#[cfg(target_os = "linux")]
mod linux_impl;
#[cfg(target_os = "windows")]
mod windows_impl;
#[cfg(target_os = "linux")]
use linux_impl::run;
#[cfg(target_os = "windows")]
use windows_impl::run;
fn main() {
run();
}
cargo check
с разными --target
, чтобы убедиться, что код компилируется корректно.#[cfg]
с runtime-проверками вроде if cfg!(...)
. cfg!
возвращает bool
и работает во время выполнения, а не компиляции.Кросс-компиляция позволяет собирать бинарники для платформ, отличных от хост-системы, т.е. собирать бинарные файлы для другой платформы, отличной от той, на которой вы работаете. Например, собрать Windows-приложение на Linux.
Rust использует rustup
для управления целями компиляции которых поддерживает множество. Установите нужные цели с помощью команд:
rustup target add x86_64-pc-windows-gnu
rustup target add x86_64-unknown-linux-gnu
rustup target add x86_64-apple-darwin
Посмотреть список доступных целей можно командой: rustup target list
. Установленные цели будут отмечены как (installed)
.
Для кросс-компиляции нужен linker (связыватель), соответствующий целевой платформе. Каждая цель требует подходящий linker (связыватель):
sudo apt install mingw-w64
.Пример файла .cargo/config.toml
:
[target.x86_64-pc-windows-gnu]
linker = "x86_64-w64-mingw32-gcc"
Сборка проекта для нужной цели:
cargo build --target x86_64-pc-windows-gnu
cargo build --target x86_64-unknown-linux-gnu
windows_subsystem
windows_subsystem
— это внутренняя инструкция Rust (inner attribute, с #![...]
), которая указывает тип подсистемы Windows в PE-файле (Portable Executable). Она определяет, как Windows будет запускать приложение: с консольным окном или без него.
Этот атрибут унаследован от Windows API и связан с полем Subsystem
в заголовке PE-файла. Rust транслирует его в флаги компоновщика через rustc
. Без указания подсистемы используется значение по умолчанию — консольное приложение (SUBSYSTEM:CONSOLE
).
Существует несколько значений для windows_subsystem
:
console
(по умолчанию): Запускает приложение с консольным окном. Подходит для CLI-приложений. Пример:#![windows_subsystem = "console"]
fn main() {
println!("С консолью!");
}
windows
: Запускает приложение без консоли (GUI-режим). Используется для графических приложений. Пример:#![windows_subsystem = "windows"]
fn main() {
// Логика GUI
}
native
: Для драйверов или системных утилит (редко используется в Rust).posix
: Для POSIX-совместимых приложений в Windows (устарело).Rust передаёт значение в linker через флаг -Wl,--subsystem,<value>
(для MinGW) или эквивалент для MSVC. Например, #![windows_subsystem = "windows"]
эквивалентно rustc -C link-args="-mwindows"
.
Для Unix-систем нет прямого аналога windows_subsystem
, так как поведение приложения определяется:
./app & disown
)..app
-бандлы. Для GUI-приложений Rust интегрируется с cocoa
или objc
, а консоль скрывается автоматически при сборке бандла.Документация по атрибутам доступна здесь: Rustc attributes.
Соберите проект для нужной цели с помощью команд:
cargo build --target x86_64-pc-windows-gnu --release
cargo build --target x86_64-unknown-linux-gnu --release
Бинарники будут находиться в папке target/<target>/release/
.
Если вам нужен вывод в консоль в GUI-режиме на Windows, используйте функцию AllocConsole()
из библиотеки winapi
. Для Linux, чтобы уменьшить размер бинарника, используйте утилиту strip
, например: strip target/x86_64-unknown-linux-gnu/release/myapp
.
/
в путях. Что это значит?В Linux (и других POSIX-совместимых системах, таких как macOS) пути к файлам и директориям используют прямой слэш /
как разделитель.
Это отличается от Windows, где традиционно используется обратный слэш \
(например, C:\Users\file.txt
), хотя Windows также поддерживает /
в некоторых случаях.
Пример пути в Linux: /home/user/docs/file.txt
.
Rust предоставляет кроссплатформенные инструменты для работы с путями через модуль std::path
. Использование этих инструментов позволяет избежать хардкода разделителей и делает код переносимым.
Например, вместо ручного конструирования пути с /
или \
, лучше использовать Path
или PathBuf
:
use std::path::Path;
let path = Path::new("/home/user/file.txt"); // Работает на Linux
println!("{:?}", path);
На Windows этот же код автоматически адаптируется к локальным разделителям, если используется Path::new
.
Абсолютные и относительные пути:
/
(например, /usr/bin
).docs/file.txt
).Кодировка: Linux обычно использует UTF-8 для путей, но Rust обрабатывает их как OsString
, что позволяет работать с не-UTF-8 именами файлов (хотя это редкость).
Кроссплатформенность: Если код должен работать на Windows и Linux, избегайте прямого использования /
в строках. Вместо этого:
use std::path::PathBuf;
let mut path = PathBuf::new();
path.push("home");
path.push("user");
path.push("file.txt");
// На Linux: "home/user/file.txt"
// На Windows: "home\user\file.txt"
std::os::unix::fs::symlink("src", "link").unwrap();
. Что такое символические ссылки?Символическая ссылка (symlink) — это специальный файл, который указывает на другой файл или директорию в файловой системе.
В Linux это аналог "ярлыков" в Windows, но более мощный и гибкий инструмент.
Пример в терминале:
ln -s /original/file.txt link.txt
Теперь link.txt
указывает на /original/file.txt
.
Rust предоставляет функцию std::os::unix::fs::symlink
для создания символических ссылок, но она доступна только на Unix-системах (Linux, macOS, BSD и т.д.), так как это POSIX-функциональность.
Синтаксис:
std::os::unix::fs::symlink("src", "link").unwrap();
Первый аргумент ("src"
) — путь к исходному файлу или директории (цель ссылки).
Второй аргумент ("link"
) — имя создаваемой ссылки.
Метод возвращает Result<(), std::io::Error>
. unwrap()
разворачивает результат, вызывая панику при ошибке.
use std::os::unix::fs;
fn main() {
// Создаём символическую ссылку "link.txt", указывающую на "original.txt"
match fs::symlink("original.txt", "link.txt") {
Ok(()) => println!("Символическая ссылка создана!"),
Err(e) => eprintln!("Ошибка: {}", e),
}
}
Если original.txt
не существует, ссылка всё равно создаётся (это "висячая ссылка", что допустимо в Linux).
После выполнения ls -l
в терминале покажет: link.txt -> original.txt
.
Платформозависимость:
std::os::unix::fs::symlink
доступен только на Unix-системах. На Windows этот код не скомпилируется, если не использовать условную компиляцию:
#[cfg(target_family = "unix")]
fn create_symlink() {
std::os::unix::fs::symlink("src", "link").unwrap();
}
Для Windows используйте std::os::windows::fs::symlink_file
или symlink_dir
, но с оговоркой: создание ссылок требует прав администратора (до Windows 10 1703) или включённого режима разработчика.
Ошибки:
Если файл link
уже существует, symlink
вернёт ошибку io::ErrorKind::AlreadyExists
.
Используйте match
или expect
вместо unwrap()
в production-коде для обработки ошибок.
Типы ссылок:
symlink
создаёт мягкие (soft) ссылки. Жёсткие (hard) ссылки доступны через std::fs::hard_link
.
Для поддержки и Windows, и Linux можно написать обёртку:
use std::path::Path;
fn create_symlink(src: &str, link: &str) -> std::io::Result<()> {
#[cfg(target_family = "unix")]
std::os::unix::fs::symlink(src, link)?;
#[cfg(target_os = "windows")]
{
let src_path = Path::new(src);
let link_path = Path::new(link);
if src_path.is_dir() {
std::os::windows::fs::symlink_dir(src, link)?;
} else {
std::os::windows::fs::symlink_file(src, link)?;
}
}
Ok(())
}
fn main() {
if let Err(e) = create_symlink("original.txt", "link.txt") {
eprintln!("Ошибка: {}", e);
}
}
signal-hook
. Что такое сигналы в Linux?Сигналы — это механизм в Unix-системах для передачи уведомлений процессам (например, о прерывании, завершении или ошибке).
Примеры сигналов:
SIGINT
(Ctrl+C в терминале).SIGTERM
(просьба завершить процесс).SIGHUP
(перезапуск конфигурации).В отличие от Windows, где используются события и сообщения, сигналы — ключевая часть POSIX-систем.
signal-hook
?Стандартная библиотека Rust (std
) не предоставляет удобных средств для обработки сигналов, так как это платформозависимая функциональность.
Crate signal-hook
— популярное решение для безопасной и удобной работы с сигналами в Rust.
Альтернативы: nix
(низкоуровневый), tokio-signal
(асинхронный).
Добавьте в Cargo.toml
:
[dependencies]
signal-hook = "0.3"
use signal_hook::consts::SIGINT;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
fn main() -> std::io::Result<()> {
// Флаг для отслеживания сигнала
let running = Arc::new(AtomicBool::new(true));
let r = running.clone();
// Регистрация обработчика SIGINT
signal_hook::flag::register(SIGINT, running)?;
println!("Нажмите Ctrl+C для завершения...");
while r.load(Ordering::Relaxed) {
std::thread::sleep(std::time::Duration::from_secs(1));
println!("Работаю...");
}
println!("Получен SIGINT, завершаю работу.");
Ok(())
}
Что происходит?
register
связывает сигнал SIGINT
с флагом running
.
При получении SIGINT
(Ctrl+C) флаг становится false
, прерывая цикл.
Вывод:
Нажмите Ctrl+C для завершения...
Работаю...
Работаю...
^CПолучен SIGINT, завершаю работу.
Безопасность: signal-hook
минимизирует риски, связанные с сигналами (например, race conditions), но обработчик должен быть минимальным — избегайте сложной логики.
Множественные сигналы:
use signal_hook::consts::{SIGINT, SIGTERM};
use signal_hook::iterator::Signals;
fn main() -> std::io::Result<()> {
let mut signals = Signals::new(&[SIGINT, SIGTERM])?;
for sig in signals.forever() {
println!("Получен сигнал: {:?}", sig);
break;
}
Ok(())
}
Signals::new
создаёт итератор для обработки нескольких сигналов.
Кроссплатформенность: Сигналы — это Unix-функциональность. На Windows используйте ctrlc
crate:
#[cfg(target_os = "windows")]
use ctrlc;
#[cfg(target_os = "windows")]
fn handle_signals() {
ctrlc::set_handler(|| {
println!("Получен Ctrl+C на Windows!");
std::process::exit(0);
}).expect("Ошибка установки обработчика");
}
Пути: Linux использует /
, и Rust помогает абстрагироваться от платформ через std::path
.
Символические ссылки: std::os::unix::fs::symlink
— простой способ их создания в Unix, но требует условной компиляции для кроссплатформенности.
Сигналы: signal-hook
— удобный и безопасный crate для их обработки в Linux, в отличие от Windows, где нужны другие подходы.
Запускайте тесты для каждой цели с помощью команд:
cargo test --target x86_64-pc-windows-gnu
cargo test --target x86_64-unknown-linux-gnu
Давайте подробно разберём, что происходит при выполнении команд
Эти команды запускают тестирование проекта Rust с использованием кросс-компиляции для указанных целей (targets). Чтобы понять процесс, разобьём его на шаги и рассмотрим, что делает cargo test
, как работает флаг --target
, и какие нюансы возникают при выполнении тестов для разных платформ.
cargo test
?cargo test
— это команда в Cargo (системе сборки Rust), которая выполняет следующие действия:
#[cfg(test)]
или в файлах в папке tests/
.src/
(например, через use crate::...
), этот код тоже компилируется.По умолчанию cargo test
использует хост-платформу (ту, на которой вы работаете, например, x86_64-unknown-linux-gnu
на Linux или x86_64-pc-windows-msvc
на Windows).
--target
Флаг --target
указывает Cargo, для какой платформы нужно компилировать код. В данном случае:
x86_64-pc-windows-gnu
: Цель для Windows с использованием toolchain MinGW (GNU-совместимый компоновщик).x86_64-unknown-linux-gnu
: Цель для Linux с архитектурой x86_64 и стандартной GNU-библиотекой (glibc).Когда вы добавляете --target
, Cargo переключается с хост-платформы на указанную цель, что означает кросс-компиляцию. Это полезно для тестирования кода на платформах, отличных от той, на которой вы работаете.
Cargo проверяет, установлен ли нужный target через rustup
. Если нет, вы получите ошибку вроде:
error: target 'x86_64-pc-windows-gnu' is not installed
Исправление: rustup target add x86_64-pc-windows-gnu
.
Проверяется наличие подходящего linker’а (компоновщика) для цели. Например:
x86_64-pc-windows-gnu
нужен x86_64-w64-mingw32-gcc
(из MinGW).x86_64-unknown-linux-gnu
обычно достаточно стандартного gcc
на Linux.Если linker не настроен, это можно указать в .cargo/config.toml
:
[target.x86_64-pc-windows-gnu]
linker = "x86_64-w64-mingw32-gcc"
Cargo компилирует исходный код и тесты для указанной цели.
Используется rustc
с флагом --target
, например:
rustc --target x86_64-pc-windows-gnu ...
Выходные файлы (тестовые бинарники) сохраняются в:
target/x86_64-pc-windows-gnu/debug/
(или release
, если добавить --release
).target/x86_64-unknown-linux-gnu/debug/
.Если в коде есть условная компиляция (например, #[cfg(target_os = "windows")]
), то только соответствующие блоки будут включены в сборку для этой цели.
После компиляции Cargo пытается запустить скомпилированные тестовые бинарники.
Важный нюанс: Cargo ожидает, что тесты можно выполнить на текущей хост-системе. Однако:
x86_64-pc-windows-gnu
, тесты не запустятся напрямую, так как это Windows-бинарники (.exe
).x86_64-unknown-linux-gnu
, тесты тоже не запустятся, так как это Linux-бинарники (ELF-файлы).Результат:
error: cannot execute tests for target 'x86_64-pc-windows-gnu' on this host
x86_64-unknown-linux-gnu
), тесты выполнятся.Если тесты не могут быть запущены из-за кросс-платформенности, Cargo сообщит об этом.
Если тесты запускаются (на хосте или через эмуляцию), вы увидите стандартный вывод:
running 2 tests
test test_one ... ok
test test_two ... ok
test result: ok. 2 passed; 0 failed; 0 ignored
cargo test --target
только компилирует тесты для указанной платформы, но не гарантирует их запуск на хосте.winapi
) работают только на Windows. Если они используются в тестах, сборка для Linux завершится ошибкой, если не изолировать их через #[cfg]
.--target
тесты компилируются и запускаются для хост-платформы.--target
вы проверяете только компилируемость для указанной платформы, если не настроена эмуляция.Чтобы действительно протестировать код на разных платформах, нужно:
cargo build --target x86_64-pc-windows-gnu
wine target/x86_64-pc-windows-gnu/debug/my_test.exe
wsl cargo test --target x86_64-unknown-linux-gnu
Допустим, у вас есть код:
#[cfg(test)]
mod tests {
#[test]
fn test_windows() {
#[cfg(target_os = "windows")]
assert_eq!(std::env::var("OS").unwrap(), "Windows_NT");
}
#[test]
fn test_linux() {
#[cfg(target_os = "linux")]
assert!(std::env::var("HOME").is_ok());
}
}
cargo test --target x86_64-pc-windows-gnu
:
test_windows
.cargo test --target x86_64-unknown-linux-gnu
:
test_linux
.--target
для проверки компилируемости, а для полного тестирования настройте эмуляцию или CI/CD.CI/CD — это автоматизация сборки, тестирования и деплоя. GitHub Actions — популярный инструмент для этого.
Вот пример базового workflow для проверки кода на разных ОС:
name: Rust CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build-and-test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- uses: actions/checkout@v3
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose
Этот пример демонстрирует кросс-компиляцию и сохранение бинарников:
name: Cross-Platform CI
on: [push, pull_request]
jobs:
cross-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: x86_64-pc-windows-gnu, x86_64-unknown-linux-gnu, x86_64-apple-darwin
- name: Install MinGW
run: sudo apt update && sudo apt install -y mingw-w64
- name: Build for Windows
run: cargo build --target x86_64-pc-windows-gnu --release
- name: Build for Linux
run: cargo build --target x86_64-unknown-linux-gnu --release
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: binaries
path: |
target/x86_64-pc-windows-gnu/release/*.exe
target/x86_64-unknown-linux-gnu/release/*
Кэширование в GitHub Actions — это механизм, который позволяет сохранять файлы или папки между выполнениями workflow (jobs) и повторно использовать их в последующих запусках. Это особенно полезно для проектов с большими зависимостями или длительными процессами компиляции, таких как Rust-проекты, где загрузка crates и сборка могут занимать значительное время.
Кэширование ускоряет сборку. Добавьте следующий шаг в workflow:
- name: Cache cargo
uses: actions/cache@v3
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
Этот шаг добавляется в workflow GitHub Actions, чтобы сохранить и повторно использовать данные между запусками сборки, минимизируя время на повторную загрузку зависимостей и компиляцию.
- name: Cache cargo
Это просто название шага, которое отображается в логах GitHub Actions.
Оно не влияет на функциональность, но помогает понять, что делает этот шаг (кэширование для Cargo).
uses: actions/cache@v3
Указывает, что используется действие (action) actions/cache
версии 3.
actions/cache
— официальное действие от GitHub для работы с кэшированием.
@v3
— конкретная версия действия (на март 2025 года это актуальная версия; в будущем может быть @v4
или новее).
Действие отвечает за сохранение и восстановление файлов, указанных в path
, на основе ключа key
.
with:
Блок with
передаёт параметры для действия actions/cache@v3
.
path: |
Определяет, какие файлы или папки будут кэшироваться. Используется многострочный синтаксис YAML (|
) для удобства.
Указанные пути:
~/.cargo/registry
:
~/.cargo/registry/index
) и сами архивы crates (~/.cargo/registry/cache
).~/.cargo/git
:
Cargo.toml
(например, some_dep = { git = "https://github.com/user/repo" }
).target
:
target/
в корне проекта, где хранятся скомпилированные артефакты (бинарники, объектные файлы, промежуточные результаты).target/
ускоряет сборку, если зависимости или код не изменились, так как Cargo может повторно использовать уже скомпилированные файлы.key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
key
— это уникальный идентификатор кэша. Если ключ совпадает с предыдущим запуском, кэш восстанавливается; если нет — создаётся новый.
Разберём его состав:
runner.os
:
ubuntu-latest
, windows-latest
, macos-latest
).target/
платформозависимы.-cargo-
:
hashFiles('**/Cargo.lock')
:
Cargo.lock
.**/
означает поиск Cargo.lock
во всех подкаталогах (полезно для монрепозиториев).Cargo.lock
изменился (например, добавлена или обновлена зависимость), хэш изменится, и кэш будет считаться устаревшим.Пример ключа:
Cargo.lock
: ubuntu-latest-cargo-abc123...
.Cargo.lock
: ubuntu-latest-cargo-def456...
(новый кэш).target/
.Cargo.lock
не изменился:~/.cargo/registry
, ~/.cargo/git
и target/
.target/
, если код не изменился.Cargo.lock
изменился:
Экономия: До 90% времени сборки в больших проектах с неизменёнными зависимостями.
Полный пример workflow с кэшированием:
name: Rust CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Cache cargo
uses: actions/cache@v3
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Build
run: cargo build --verbose
- name: Test
run: cargo test --verbose
Порядок важен: Кэширование должно быть после checkout
(чтобы получить Cargo.lock
), но перед cargo build
.
target/
. Кэширование зависимостей всё равно экономит время.target/
может быть большим (сотни МБ). Убедитесь, что он не превышает лимит GitHub (10 ГБ на репозиторий).ubuntu-latest
несовместим с windows-latest
, так как target/
содержит платформозависимые бинарники. Поэтому runner.os
в ключе обязателен.restore-keys
: Позволяет использовать частично совпадающий кэш, если точного совпадения нет:restore-keys: |
${{ runner.os }}-cargo-
Cargo.lock
изменился, но вы хотите повторно использовать старые зависимости.Добавление шага:
- name: Cache cargo
uses: actions/cache@v3
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
ускоряет сборку Rust-проектов в GitHub Actions, сохраняя зависимости и скомпилированные артефакты между запусками. Оно особенно эффективно для проектов с большим количеством зависимостей или частыми CI/CD-запусками, где повторная компиляция с нуля была бы затратной. Настройка ключа с учётом Cargo.lock
гарантирует, что кэш обновляется только при необходимости.
qemu-aarch64 target/aarch64-unknown-linux-gnu/debug/myapp
wine target/x86_64-pc-windows-gnu/debug/myapp.exe
Создадим проект с платформозависимым кодом:
#![cfg(target_os = "windows")]
#![windows_subsystem = "windows"]
use std::fs;
fn main() {
#[cfg(target_os = "windows")]
fs::write("out.txt", "Windows!\r\n").unwrap();
#[cfg(target_os = "linux")]
fs::write("out.txt", "Linux!\n").unwrap();
}
Сборка проекта:
cargo build --target x86_64-pc-windows-gnu --release
cargo build --target x86_64-unknown-linux-gnu --release
Создайте проект с GUI-режимом на Windows и консолью на Linux.
Код для файла src/main.rs
:
#![cfg(target_os = "windows")]
#![windows_subsystem = "windows"]
fn main() {
#[cfg(target_os = "windows")]
let msg = "Windows GUI mode";
#[cfg(target_os = "linux")]
let msg = format!("Linux kernel: {}", std::fs::read_to_string("/proc/version").unwrap_or_default());
println!("{}", msg);
}
Настройка в файле .cargo/config.toml
:
[target.x86_64-pc-windows-gnu]
linker = "x86_64-w64-mingw32-gcc"
rustflags = ["-C", "link-args=-mwindows"]
[target.x86_64-unknown-linux-gnu]
linker = "gcc"
Сборка и запуск:
cargo run --target x86_64-pc-windows-gnu
cargo run --target x86_64-unknown-linux-gnu
Мы подробно разобрали кроссплатформенную разработку в Rust, включая windows_subsystem
и CI/CD с GitHub Actions. Теперь вы можете создавать и тестировать приложения для любых платформ!