Ржавчина и Wasm бок о бок |
На моей работе мы рассматриваем возможность использования Веб-сборка (далее сокращенно WASM), потому что он позволяет нам кросс-компилировать практически любой язык для использования в Интернете. WebAssembly — это «двоичный формат инструкций для виртуальной машины на основе стека». По сути, это означает, что это двоичный язык, предназначенный для запуска в любом месте, но, вообще говоря, прямо сейчас он используется в веб-браузерах в качестве замены модулей Javascript. Один из языков, которые мы рассматриваем в качестве исходного языка, это Ржавчинановый язык, разработанный для безопасности типов, производительности и параллелизма.
Таким образом, я читал язык программирования Rust и, что более важно, как его скомпилировать Rust в WebAssembly. Изучив пример модуля, приведенный в книге RustWasm, я решил конвертировать одну из моих личных библиотек npm из JS -> Rust. Первым шагом в этом начинании было преобразование существующих тестов, которые у меня были в mocha/chai, в собственный формат тестов Rust, чтобы, добавляя код, я мог убедиться, что он работает так, как я ожидал. Однако возможность запуска этих тестов стала проблемой, когда я перешел на цель WASM.
Проблема
Одним из больших недостатков компиляции Rust в WASM является то, что нет хорошего способа отлаживать код в браузере и связывать его с исходным кодом Rust. В книге Rust-WASM это отмечено в раздел по отладке:
К сожалению, история отладки для WebAssembly все еще незрелая. В большинстве систем Unix DWARF используется для кодирования информации, необходимой отладчику для обеспечения проверки работающей программы на уровне исходного кода. Существует альтернативный формат, который кодирует аналогичную информацию в Windows. В настоящее время для WebAssembly нет эквивалента.
Вместо этого они рекомендуют использовать тестирование (в частности, автоматизированное тестирование) для выявления регрессий. до они делают это в сборке. Это отличная идея, и я, безусловно, поддерживаю ее, учитывая, что я изначально писал тесты до реализации моей библиотеки. Однако есть один большой недостаток в написании тестов на Rust при компиляции в WASM, как подробно описано в следующий подраздел книги Rust-WASM:
Обратите внимание, что для запуска #[test]s без ошибок компилятора и компоновщика, вам нужно будет закомментировать биты crate-type = «cdylib» в wasm-game-of-life/Cargo.toml.
Чего ждать? Чтобы запустить мои тесты Rust, мне нужно внести изменения в мой исходный репозиторий только для того, чтобы заставить его построить? Это немного не для меня, потому что я хочу работать в системе непрерывной интеграции, где я могу собрать пакет Rust, запустить тесты, собрать двоичный файл WASM, а затем выполнить развертывание в NPM, все автоматически.
Другими словами, то, что я хочу сделать, выглядит примерно так:
# These commands compile (and subsequently test) the native Rust code
$ cargo build
$ cargo test
# This command builds the wasm module
$ cargo build --target=wasm32-unknown-unknown
Ключевым здесь, однако, является то, что я хочу быть в состоянии сделать это без внесение любых изменений в исходный файл или файл сборки.
Возможные решения
Я потратил довольно много времени, пытаясь определить, что я мог делай здесь. Одна мысль, которая пришла на ум, заключалась в том, что у меня мог бы быть сценарий предварительной сборки, который выполняет это комментирование из #[wasm_bindgen]
атрибуты вручную. Как это будет работать, так это то, что перед сборкой скрипт будет копировать все в локальном каталоге в подкаталог. Затем он переключил бы crate-type
в subdirectory/Cargo.toml
к lib
вместо cdylib
и закомментируйте любые экземпляры #[wasm_bindgen]
. Излишне говорить, что я не хотел идти по этому пути — он казался невероятно хрупким и подверженным ошибкам.
Затем мой коллега предложил мне отделить нативный код Rust от аспекта кода WASM и заставить библиотеку WASM использовать код из нативной библиотеки, подобно тому, как геоигрушка справляется с этим. Это отличное решение, но когда я устанавливал свою библиотеку, я всю жизнь не мог понять, как импортировать перечисление или структуру в часть библиотеки WASM.
По сути, у geotoy есть настройка, в которой она использует библиотеку WASM для предоставления набора функции которые служат API для библиотеки. Затем эти функции используют структуры данных в src/lib.rs
. Однако структуры данных сами себя не выставляются. С cratchit
то, что я хочу разоблачить, это Account
и AccountsChart
структуры данных, а также Currency
и AccountType
перечисления напрямуювместо набора функций шлюза, использующих эти структуры данных. я подозреваю, что там, вероятно, является способ заставить его выставлять структуры, но я не смог понять это.
Окончательное решение
Решение, к которому я в конце концов пришел, не разделяет библиотеки как таковые. Вместо этого он делает #[wasm_bindgen]
атрибуты зависят от того, строите ли вы для цели WASM.
Первое, что вам нужно сделать, это убедиться, что вы используете оба cdylib
и rlib
(или lib
) в вашей Cargo.toml
:
[lib]
crate-type = ["cdylib", "rlib"]
Далее (это может быть конкретно для меня) мне пришлось добавить #![feature(custom_attribute)]
вверху атрибутов ящика (т.е. вверху src/lib.rs
:
#![feature(custom_attribute)]
extern crate cfg_if;
extern crate json;
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
use cfg_if::cfg_if;
use std::collections::HashMap;
...
Далее, везде, где вы ранее использовали #[wasm_bindgen]
сделайте его условным для целевой архитектуры:
/// Use this instead of #[wasm_bindgen]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
Наконец, и это опять же может быть чем-то специфичным для моего варианта использования, вам нужно вытащить типы из подмодулей в корневой крейт. src/lib.rs
. Другими словами, в моем src/lib.rs
у меня было следующее:
pub mod accounts;
pub mod currency;
И у меня было два файла, src/currency.rs
и src/accounts.rs
которые определяют типы, связанные со счетами и валютами соответственно. wasm_bindgen
мне это не понравилось, поэтому я переместил этот код в src/lib.rs
и убраны ссылки на модули в тестах.
То, что я сделал для проверки, не использовалось wasm-pack
изначально. Вместо этого я побежал cargo +nightly build --target=wasm32-unknown-unknown
чтобы убедиться, что ошибок нет и что он создал файл .wasm в соответствующем target
каталог:
$ cargo +nightly build --target=wasm32-unknown-unknown
...
$ ls -al target/wasm32-unknown-unknown/debug
total 9520
drwxr-xr-x@ 13 scottj staff 416 Oct 26 10:15 .
drwxr-xr-x 3 scottj staff 96 Oct 26 10:14 ..
-rw-r--r-- 1 scottj staff 0 Oct 26 10:14 .cargo-lock
drwxr-xr-x 8 scottj staff 256 Oct 26 10:14 .fingerprint
drwxr-xr-x 3 scottj staff 96 Oct 26 10:14 build
-rw-r--r-- 1 scottj staff 122 Oct 26 10:15 cratchit.d
-rwxr-xr-x 2 scottj staff 3549051 Oct 26 10:15 cratchit.wasm
drwxr-xr-x 13 scottj staff 416 Oct 26 10:15 deps
drwxr-xr-x 2 scottj staff 64 Oct 26 10:14 examples
drwxr-xr-x 3 scottj staff 96 Oct 26 10:15 incremental
-rw-r--r-- 1 scottj staff 125 Oct 26 10:15 libcratchit.d
-rw-r--r-- 2 scottj staff 1311774 Oct 26 10:15 libcratchit.rlib
drwxr-xr-x 2 scottj staff 64 Oct 26 10:14 native
И, конечно же, я убедился, что тесты прошли без каких-либо изменений:
$ cargo test
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Running target/debug/deps/test_accounts-54475044840749bd
running 6 tests
test account_type_from_integer ... ok
test account_creation ... ok
test account_type_from_string ... ok
test adding_top_level_accounts_to_accounts_chart ... ok
test getting_all_account_ids_in_a_chart ... ok
test creating_accounts_chart_from_json ... ok
test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Running target/debug/deps/test_currency-d2d59a6d3436c103
running 1 test
test currency_translation_from_string ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Doc-tests cratchit
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Теперь, когда вы хотите создать свой модуль WASM, вы можете запустить:
wasm-pack build
И он создаст для вас упакованную библиотеку WASM в pkg
.
Бонус: запустите тесты на Travis и разверните WASM в NPM.
Это была еще одна область, в которой мне пришлось немного разобраться. Проблема, с которой я столкнулся при использовании Travis-CI, заключалась в том, что он не устанавливает wasm-pack
по умолчанию. Таким образом, вам необходимо установить его вручную в сценарии. Однако, если у вас есть cache: cargo
включен, он будет зависать, если wasm-pack
ранее был установлен. Таким образом, я добавил следующее в свой .travis.yml
файл для условной установки wasm-pack
:
before_script: |
if hash wasm-pack 2>/dev/null; then
echo "Wasm-pack already is installed"
else
curl -sSf | sh
fi
Теперь вы можете добавить следующее в свой script
раздел, посвященный сборке как модуля WASM, так и собственного модуля Rust:
script:
- cargo clean
- cargo build
- cargo test
- wasm-pack build --target=nodejs
Обратите внимание на раздел, который вызывает wasm-pack build
имеет дополнительный аргумент: --target=nodejs
. Если вы хотите протестировать локально, используя npm link
вам понадобится этот аргумент, так как он упаковывает модуль WASM с main
параметр в package.json
.
И, наконец, добавим логику развертывания:
before_deploy:
- cd pkg
deploy:
provider: npm
email: <your email address>
on:
tags: true
skip_cleanup: true
api_key:
secure: <YOUR_API_KEY>
Обратите внимание, что эта логика развертывания будет развертываться только в помеченных выпусках, поэтому вы можете изменить ее, если хотите изменить поведение.
Последнее, что нужно понять: в настоящее время для этого требуется ночной ржавчина. Итак, вам нужно убедиться, что вы используете nightly Rust локально, и вам нужно убедиться, что, если вы строите на travis, вы разрешаете stable
и beta
Ржавчина до отказа:
rust:
- stable
- beta
- nightly
matrix:
allow_failures:
- rust: stable
- rust: beta