commit
7615f83e42
|
@ -8,11 +8,13 @@ on:
|
|||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
- 'dev'
|
||||
paths-ignore:
|
||||
- "**/*.md"
|
||||
pull_request:
|
||||
branches:
|
||||
- 'main'
|
||||
- 'dev'
|
||||
paths-ignore:
|
||||
- "**/*.md"
|
||||
|
||||
|
@ -23,7 +25,7 @@ jobs:
|
|||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-22.04, ubuntu-20.04]
|
||||
rust: [1.79, 1.78, 1.77]
|
||||
rust: [1.79]
|
||||
env:
|
||||
LD_LIBRARY_PATH: ~\.wasmedge\lib
|
||||
|
||||
|
@ -45,9 +47,13 @@ jobs:
|
|||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
|
||||
- name: Test Build
|
||||
- name: Build
|
||||
run: |
|
||||
cargo build --release
|
||||
cargo build
|
||||
|
||||
- name: Build before-packaging-command
|
||||
run: |
|
||||
cargo build --manifest-path packaging/before-packaging-command/Cargo.toml
|
||||
|
||||
build_macos:
|
||||
name: MacOS
|
||||
|
@ -55,7 +61,7 @@ jobs:
|
|||
strategy:
|
||||
matrix:
|
||||
os: [macos-14, macos-13]
|
||||
rust: [1.79, 1.78, 1.77]
|
||||
rust: [1.79]
|
||||
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
|
@ -71,16 +77,20 @@ jobs:
|
|||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
|
||||
- name: Test Build
|
||||
- name: Build
|
||||
run: |
|
||||
cargo build --release
|
||||
cargo build
|
||||
|
||||
- name: Build before-packaging-command
|
||||
run: |
|
||||
cargo build --manifest-path packaging/before-packaging-command/Cargo.toml
|
||||
|
||||
build_windows:
|
||||
name: Windows
|
||||
runs-on: windows-2022
|
||||
strategy:
|
||||
matrix:
|
||||
rust: [1.79, 1.78, 1.77]
|
||||
rust: [1.79]
|
||||
env:
|
||||
WASMEDGE_DIR: ${{ github.workspace }}\WasmEdge-0.14.0-Windows
|
||||
steps:
|
||||
|
@ -108,6 +118,10 @@ jobs:
|
|||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
|
||||
- name: Test Build
|
||||
- name: Build
|
||||
run: |
|
||||
cargo build
|
||||
cargo build
|
||||
|
||||
- name: Build before-packaging-command
|
||||
run: |
|
||||
cargo build --manifest-path packaging/before-packaging-command/Cargo.toml
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/target
|
||||
target/
|
||||
preferences.json
|
||||
data.*
|
||||
.vscode
|
||||
|
|
File diff suppressed because it is too large
Load Diff
56
Cargo.toml
56
Cargo.toml
|
@ -7,11 +7,13 @@ members = [
|
|||
"moxin-backend",
|
||||
"moxin-fake-backend",
|
||||
]
|
||||
exclude = ["packaging/before-packaging-command"]
|
||||
|
||||
[package]
|
||||
name = "moxin"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.79" ## required by cargo-packager
|
||||
description = "Desktop app for downloading and chatting with AI LLMs"
|
||||
|
||||
## Rename the binary to `_moxin_app` to avoid naming conflicts
|
||||
|
@ -88,19 +90,7 @@ binaries = [
|
|||
## In addition, on macOS only, we must download the WasmEdge plugins in order to include them
|
||||
## in the macOS app bundle. This is because macOS apps must include all dependencies in order to pass notarization.
|
||||
before-packaging-command = """
|
||||
mkdir -p ./dist/resources/makepad_widgets/ \
|
||||
&& cp -r `cargo metadata --format-version 1 | sed -e 's/,\"/,\\n\"/g' | grep 'widgets/Cargo.toml\"' | sed -e 's/\"manifest_path\":\"//g' -e 's/Cargo.toml\",/resources/g'` ./dist/resources/makepad_widgets/ \
|
||||
&& mkdir -p ./dist/resources/moxin/ \
|
||||
&& cp -r ./resources ./dist/resources/moxin/; \
|
||||
\
|
||||
export CARGO_PACKAGER_HOST_OS=`rustc --print cfg | grep target_os= | sed -e 's/target_os=\"//g' -e 's/\"//g'`; \
|
||||
echo "\n\nPackaging on host platform '$CARGO_PACKAGER_HOST_OS'\n\n"; \
|
||||
if [ "$CARGO_PACKAGER_HOST_OS" = macos ]; then \
|
||||
mkdir -p ./wasmedge \
|
||||
&& curl -sfL --show-error https://github.com/WasmEdge/WasmEdge/releases/download/0.13.5/WasmEdge-0.13.5-darwin_arm64.tar.gz | bsdtar -xf- -C ./wasmedge \
|
||||
&& mkdir -p ./wasmedge/WasmEdge-0.13.5-Darwin/plugin \
|
||||
&& curl -sf --location --progress-bar --show-error https://github.com/WasmEdge/WasmEdge/releases/download/0.13.5/WasmEdge-plugin-wasi_nn-ggml-0.13.5-darwin_arm64.tar.gz | bsdtar -xf- -C ./wasmedge/WasmEdge-0.13.5-Darwin/plugin; \
|
||||
fi; \
|
||||
cargo run --manifest-path packaging/before-packaging-command/Cargo.toml before-packaging
|
||||
"""
|
||||
|
||||
## See the above paragraph comments for more info on how we create/populate the below `src` directories.
|
||||
|
@ -121,31 +111,11 @@ resources = [
|
|||
## which is currently `/usr/lib/moxin-runner`.
|
||||
## This is the directory in which `dpkg` copies app resource files to when installing the `.deb` package.
|
||||
## * On Linux, we also strip the binaries of unneeded content, as required for Debian packages.
|
||||
## * For Debian and Pacman packages, we also auto-generate the list of dependencies required by moxin,
|
||||
## * For Debian and Pacman (still a to-do!) packages, we also auto-generate the list of dependencies required by Moxin,
|
||||
## making sure to add `curl` since it is used by an invocation in `moxin-runner`.
|
||||
##
|
||||
before-each-package-command = """
|
||||
export CARGO_PACKAGER_HOST_OS=`rustc --print cfg | grep target_os= | sed -e 's/target_os=\"//g' -e 's/\"//g'`; \
|
||||
echo -e "\n\n================================================\n Packaging '$CARGO_PACKAGER_FORMAT' on host platform '$CARGO_PACKAGER_HOST_OS'\n================================================\n\n"; \
|
||||
if [ "$CARGO_PACKAGER_HOST_OS" = macos ]; then \
|
||||
MAKEPAD_PACKAGE_DIR=../Resources cargo build --workspace --release --features macos_bundle \
|
||||
&& install_name_tool -add_rpath "@executable_path/../Frameworks" ./target/release/_moxin_app; \
|
||||
elif [ "$CARGO_PACKAGER_HOST_OS" = linux ]; then \
|
||||
if [ "$CARGO_PACKAGER_FORMAT" = appimage ]; then \
|
||||
MAKEPAD_PACKAGE_DIR=../../usr/lib/moxin cargo build --workspace --release; \
|
||||
else \
|
||||
MAKEPAD_PACKAGE_DIR=/usr/lib/moxin cargo build --workspace --release --features "reqwest/native-tls-vendored"; \
|
||||
if [ "$CARGO_PACKAGER_FORMAT" = deb ]; then \
|
||||
for path in $(ldd target/release/_moxin_app | awk '{print $3}'); do \
|
||||
basename "$path" ; \
|
||||
done \
|
||||
| xargs dpkg -S 2> /dev/null | awk '{print $1}' | awk -F ':' '{print $1}' | sort | uniq > ./dist/depends_deb.txt; \
|
||||
echo "curl" >> ./dist/depends_deb.txt; \
|
||||
fi; \
|
||||
fi \
|
||||
&& strip --strip-unneeded --remove-section=.comment --remove-section=.note target/release/_moxin_app target/release/moxin; \
|
||||
else exit 2; \
|
||||
fi; \
|
||||
cargo run --manifest-path packaging/before-packaging-command/Cargo.toml before-each-package
|
||||
"""
|
||||
|
||||
deep_link_protocols = [
|
||||
|
@ -159,18 +129,28 @@ section = "utils"
|
|||
|
||||
[package.metadata.packager.appimage]
|
||||
## `curl` is needed for `moxin-runner` to auto-install wasmedge.
|
||||
bins = [ "/usr/bin/curl" ]
|
||||
bins = ["/usr/bin/curl"]
|
||||
|
||||
[package.metadata.packager.macos]
|
||||
minimum_system_version = "11.0"
|
||||
frameworks = [
|
||||
"./wasmedge/WasmEdge-0.13.5-Darwin/lib/libwasmedge.0.dylib",
|
||||
"./wasmedge/WasmEdge-0.13.5-Darwin/plugin/libwasmedgePluginWasiNN.dylib",
|
||||
"./wasmedge/WasmEdge-0.14.0-Darwin/lib/libwasmedge.0.dylib",
|
||||
"./wasmedge/WasmEdge-0.14.0-Darwin/plugin/libwasmedgePluginWasiNN.dylib",
|
||||
]
|
||||
|
||||
|
||||
## Configuration for `cargo packager`'s generation of a macOS `.dmg`.
|
||||
[package.metadata.packager.dmg]
|
||||
background = "./packaging/Moxin macOS dmg background.png"
|
||||
window_size = { width = 960, height = 540 }
|
||||
app_position = { x = 200, y = 250 }
|
||||
application_folder_position = { x = 760, y = 250 }
|
||||
|
||||
|
||||
## Configuration for `cargo packager`'s generation of a Windows `.exe` setup installer.
|
||||
[package.metadata.packager.nsis]
|
||||
## See this: <https://nsis.sourceforge.io/Docs/Chapter4.html#varconstant>
|
||||
appdata_paths = [
|
||||
"$APPDATA/$PUBLISHER/$PRODUCTNAME",
|
||||
"$LOCALAPPDATA/$PRODUCTNAME",
|
||||
]
|
||||
|
|
81
README.md
81
README.md
|
@ -14,29 +14,32 @@ The following table shows which host systems can currently be used to build Moxi
|
|||
|
||||
First, [install Rust](https://www.rust-lang.org/tools/install).
|
||||
|
||||
Then, install the required WasmEdge WASM runtime:
|
||||
|
||||
```sh
|
||||
curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install_v2.sh | bash -s -- --version=0.14.0
|
||||
|
||||
source $HOME/.wasmedge/env
|
||||
```
|
||||
|
||||
Obtain the source code from this repository:
|
||||
```sh
|
||||
git clone https://github.com/moxin-org/moxin.git
|
||||
```
|
||||
|
||||
### macOS
|
||||
Install the required WasmEdge WASM runtime:
|
||||
```sh
|
||||
curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install_v2.sh | bash -s -- --version=0.14.0
|
||||
|
||||
Then, on a standard desktop platform (macOS), simply run:
|
||||
source $HOME/.wasmedge/env
|
||||
```
|
||||
|
||||
Then use `cargo` to build and run Moxin:
|
||||
```sh
|
||||
cd moxin
|
||||
cargo run
|
||||
cargo run --release
|
||||
```
|
||||
|
||||
### Linux
|
||||
Install the required WasmEdge WASM runtime:
|
||||
```sh
|
||||
curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install_v2.sh | bash -s -- --version=0.14.0
|
||||
|
||||
source $HOME/.wasmedge/env
|
||||
```
|
||||
|
||||
To build Moxin on Linux, you must install the following dependencies:
|
||||
`openssl`, `clang`/`libclang`, `binfmt`, `Xcursor`/`X11`, `asound`/`pulse`.
|
||||
|
@ -47,17 +50,18 @@ sudo apt-get update
|
|||
sudo apt-get install libssl-dev pkg-config llvm clang libclang-dev binfmt-support libxcursor-dev libx11-dev libasound2-dev libpulse-dev
|
||||
```
|
||||
|
||||
Then, run:
|
||||
|
||||
Then use `cargo` to build and run Moxin:
|
||||
```sh
|
||||
cd moxin
|
||||
cargo run
|
||||
cargo run --release
|
||||
```
|
||||
|
||||
## Windows (Windows 10 or higher)
|
||||
1. Download and install the LLVM v17.0.6 release for Windows: [Here is a direct link to LLVM-17.0.6-win64.exe](https://github.com/llvm/llvm-project/releases/download/llvmorg-17.0.6/LLVM-17.0.6-win64.exe), 333MB in size.
|
||||
> [!IMPORTANT]
|
||||
> During the setup procedure, make sure to select `Add LLVM to the system PATH for all users` or `for the current user`.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> During the setup procedure, make sure to select `Add LLVM to the system PATH for all users` or `for the current user`.
|
||||
|
||||
2. Restart your PC, or log out and log back in, which allows the LLVM path to be properly
|
||||
* Alternatively you can add the LLVM path `C:\Program Files\LLVM\bin` to your system PATH.
|
||||
3. Download the [WasmEdge-0.14.0-windows.zip](https://github.com/WasmEdge/WasmEdge/releases/download/0.14.0/WasmEdge-0.14.0-windows.zip) file from [the WasmEdge v0.14.0 release page](https://github.com/WasmEdge/WasmEdge/releases/tag/0.14.0),
|
||||
|
@ -75,10 +79,9 @@ cargo run
|
|||
```
|
||||
|
||||
4. Download the WasmEdge WASI-NN plugin here: [WasmEdge-plugin-wasi_nn-ggml-0.14.0-windows_x86_64.zip](https://github.com/WasmEdge/WasmEdge/releases/download/0.14.0/WasmEdge-plugin-wasi_nn-ggml-0.14.0-windows_x86_64.zip) (15.5MB) and extract it to the same directory as above, e.g., `C:\Users\<USERNAME>\WasmEdge-0.14.0-Windows`.
|
||||
> [!IMPORTANT]
|
||||
> You will be asked whether you want to replace the files that already exist; select `Replace the files in the destination` when doing so.
|
||||
|
||||
To do this quickly in powershell:
|
||||
> [!IMPORTANT]
|
||||
> You will be asked whether you want to replace the files that already exist; select `Replace the files in the destination` when doing so.
|
||||
* To do this quickly in powershell:
|
||||
```powershell
|
||||
$ProgressPreference = 'SilentlyContinue' ## makes downloads much faster
|
||||
Invoke-WebRequest -Uri "https://github.com/WasmEdge/WasmEdge/releases/download/0.14.0/WasmEdge-plugin-wasi_nn-ggml-0.14.0-windows_x86_64.zip" -OutFile "WasmEdge-plugin-wasi_nn-ggml-0.14.0-windows_x86_64.zip"
|
||||
|
@ -91,30 +94,49 @@ cargo run
|
|||
```powershell
|
||||
$env:WASMEDGE_DIR="$home\WasmEdge-0.14.0-Windows\"
|
||||
$env:WASMEDGE_PLUGIN_PATH="$home\WasmEdge-0.14.0-Windows\"
|
||||
cargo run
|
||||
cargo run --release
|
||||
```
|
||||
|
||||
In Windows `cmd`, you can do this like so:
|
||||
```batch
|
||||
set WASMEDGE_DIR=%homedrive%%homepath%\WasmEdge-0.14.0-Windows
|
||||
set WASMEDGE_PLUGIN_PATH=%homedrive%%homepath%\WasmEdge-0.14.0-Windows
|
||||
cargo run
|
||||
cargo run --release
|
||||
```
|
||||
|
||||
In a Unix-like shell on Windows (e.g., GitBash, cygwin, msys2, WSL/WSL2):
|
||||
```sh
|
||||
WASMEDGE_DIR=$HOME/WasmEdge-0.14.0-Windows \
|
||||
WASMEDGE_PLUGIN_PATH=$HOME/WasmEdge-0.14.0-Windows \
|
||||
cargo run --release
|
||||
```
|
||||
|
||||
|
||||
## Packaging Moxin for Distribution
|
||||
|
||||
Install the version of `cargo-packager` maintained by Project Robius:
|
||||
Install `cargo-packager`:
|
||||
```sh
|
||||
cargo install --force --locked --git https://github.com/project-robius/cargo-packager cargo-packager --branch robius
|
||||
rustup update stable ## Rust version 1.79 or higher is required
|
||||
cargo +stable install --force --locked cargo-packager
|
||||
```
|
||||
For posterity, these instructions have been tested on `cargo-packager` version 0.10.1, which requires Rust v1.79.
|
||||
|
||||
|
||||
### Packaging for Linux
|
||||
On a Debian-based Linux distribution (e.g., Ubuntu), you can generate a `.deb` Debian package, an AppImage, and a pacman installation package.
|
||||
|
||||
|
||||
> [!IMPORTANT]
|
||||
> You can only generate a `.deb` Debian package on a Debian-based Linux distribution, as `dpkg` is needed.
|
||||
|
||||
> [!NOTE]
|
||||
> The `pacman` package has not yet been tested.
|
||||
|
||||
Ensure you are in the root `moxin` directory, and then you can use `cargo packager` to generate all three package types at once:
|
||||
```sh
|
||||
cargo packager --release --verbose ## --verbose is optional
|
||||
```
|
||||
|
||||
To install the Moxin app from the `.deb`package on a Debian-based Linux distribution (e.g., Ubuntu), run:
|
||||
```sh
|
||||
cd dist/
|
||||
|
@ -130,6 +152,19 @@ chmod +x moxin_0.1.0_x86_64.AppImage
|
|||
./moxin_0.1.0_x86_64.AppImage
|
||||
```
|
||||
|
||||
### Packaging for Windows
|
||||
This can only be run on an actual Windows machine, due to platform restrictions.
|
||||
|
||||
First, [follow the above instructions for building on Windows](#windows-windows-10-or-higher).
|
||||
|
||||
Ensure you are in the root `moxin` directory, and then you can use `cargo packager` to generate a `setup.exe` file using NSIS:
|
||||
```sh
|
||||
WASMEDGE_DIR=path/to/WasmEdge-0.14.0-Windows cargo packager --release --formats nsis --verbose ## --verbose is optional
|
||||
```
|
||||
|
||||
After the command completes, you should see a Windows installer called `moxin_0.1.0_x64-setup` in the `dist/` directory.
|
||||
Double-click that file to install Moxin on your machine, and then run it as you would a regular application.
|
||||
|
||||
|
||||
### Packaging for macOS
|
||||
This can only be run on an actual macOS machine, due to platform restrictions.
|
||||
|
|
|
@ -1,140 +0,0 @@
|
|||
use rusqlite::Row;
|
||||
use std::{sync::Arc, vec};
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Clone)]
|
||||
pub enum PendingDownloadsStatus {
|
||||
#[default]
|
||||
Downloading,
|
||||
Paused,
|
||||
Error,
|
||||
}
|
||||
|
||||
impl PendingDownloadsStatus {
|
||||
pub fn to_string(&self) -> &str {
|
||||
match self {
|
||||
Self::Downloading => "downloading",
|
||||
Self::Paused => "paused",
|
||||
Self::Error => "error",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_string(s: &str) -> Self {
|
||||
match s {
|
||||
"downloading" => Self::Downloading,
|
||||
"paused" => Self::Paused,
|
||||
"error" => Self::Error,
|
||||
_ => Self::Downloading,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Clone)]
|
||||
pub struct PendingDownloads {
|
||||
pub file_id: Arc<String>,
|
||||
pub progress: f64,
|
||||
pub status: PendingDownloadsStatus,
|
||||
}
|
||||
|
||||
// TODO I'm not 100% convinced that this is the best way to handle this
|
||||
// I will attempt to merge PendingDownloads and DownloadedFile into a single table, or
|
||||
// at least a single struct, to see if that makes more sense
|
||||
|
||||
impl PendingDownloads {
|
||||
pub fn insert_into_db(&self, conn: &rusqlite::Connection) -> rusqlite::Result<()> {
|
||||
conn.execute(
|
||||
"INSERT INTO pending_downloads (file_id) VALUES (?1)",
|
||||
rusqlite::params![self.file_id],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn save_to_db(&self, conn: &rusqlite::Connection) -> rusqlite::Result<()> {
|
||||
conn.execute(
|
||||
"UPDATE pending_downloads
|
||||
SET progress = ?2,
|
||||
status = ?3
|
||||
WHERE file_id = ?1",
|
||||
rusqlite::params![self.file_id, self.progress, self.status.to_string()],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn exists_by_id(conn: &rusqlite::Connection, id: String) -> rusqlite::Result<bool> {
|
||||
conn.query_row(
|
||||
"SELECT EXISTS (SELECT file_id FROM pending_downloads WHERE file_id = ?1)",
|
||||
[id],
|
||||
|row| row.get::<_, bool>(0),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn insert_if_not_exists(
|
||||
file_id: Arc<String>,
|
||||
conn: &rusqlite::Connection,
|
||||
) -> rusqlite::Result<()> {
|
||||
if !Self::exists_by_id(conn, file_id.to_string())? {
|
||||
let pending_download = PendingDownloads {
|
||||
file_id: file_id.into(),
|
||||
..Default::default()
|
||||
};
|
||||
pending_download.insert_into_db(conn)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove(file_id: Arc<String>, conn: &rusqlite::Connection) -> rusqlite::Result<()> {
|
||||
conn.execute(
|
||||
"DELETE FROM pending_downloads WHERE file_id = ?1",
|
||||
rusqlite::params![file_id],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn from_row(row: &Row<'_>) -> rusqlite::Result<Self> {
|
||||
let status = PendingDownloadsStatus::from_string(row.get::<_, String>(2)?.as_str());
|
||||
|
||||
Ok(PendingDownloads {
|
||||
file_id: Arc::new(row.get(0)?),
|
||||
progress: row.get(1)?,
|
||||
status: status,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_all(conn: &rusqlite::Connection) -> rusqlite::Result<Vec<Self>> {
|
||||
let mut stmt = conn.prepare("SELECT * FROM pending_downloads")?;
|
||||
let mut rows = stmt.query([])?;
|
||||
let mut downloads = vec![];
|
||||
|
||||
while let Some(row) = rows.next()? {
|
||||
let item = Self::from_row(row)?;
|
||||
downloads.push(item);
|
||||
}
|
||||
|
||||
Ok(downloads)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_table_pending_downloads(conn: &rusqlite::Connection) -> rusqlite::Result<()> {
|
||||
conn.execute_batch(
|
||||
"BEGIN;
|
||||
CREATE TABLE IF NOT EXISTS pending_downloads (
|
||||
file_id TEXT PRIMARY KEY,
|
||||
progress REAL DEFAULT 0,
|
||||
status TEXT DEFAULT 'downloading'
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS index_pending_downloads_file_id ON pending_downloads (file_id);
|
||||
COMMIT;",
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn mark_pending_downloads_as_paused(conn: &rusqlite::Connection) -> rusqlite::Result<()> {
|
||||
conn.execute(
|
||||
"UPDATE pending_downloads
|
||||
SET status = 'paused'
|
||||
WHERE status = 'downloading'",
|
||||
[],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
|
@ -56,6 +56,7 @@ pub struct DownloadedFile {
|
|||
#[derive(Clone, Debug, Default)]
|
||||
pub enum PendingDownloadsStatus {
|
||||
#[default]
|
||||
Initializing,
|
||||
Downloading,
|
||||
Paused,
|
||||
Error,
|
||||
|
|
|
@ -7,6 +7,12 @@ description = "A companion app that runs before Moxin to handle wasmedge configu
|
|||
[dependencies]
|
||||
directories = "5.0.1"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
## For running a powershell script, which downloads and extracts/installs WasmEdge
|
||||
powershell_script = "1.1.0"
|
||||
## For showing a dialog box modal when the CPU is unsupported.
|
||||
windows-sys = { version = "0.52", features = ["Win32_UI_WindowsAndMessaging", "Win32_Foundation"] }
|
||||
|
||||
[[bin]]
|
||||
name = "moxin"
|
||||
path = "src/main.rs"
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
//! such that the main `moxin` app can locate the wasmedge dylibs and plugin dylibs.
|
||||
//!
|
||||
//! First, we discover the wasmedge installation.
|
||||
//! The standard installation directory is `$HOME/.wasmedge`.
|
||||
//! The default layout of the wasmedge installation directory is as follows:
|
||||
//! * The standard installation directory on macOS and Linux is `$HOME/.wasmedge`.
|
||||
//! * On macOS, the default layout of the wasmedge installation directory is as follows:
|
||||
//! ----------------------------------------------------
|
||||
//! $HOME/.wasmedge
|
||||
//! ├── bin
|
||||
|
@ -36,11 +36,10 @@
|
|||
//! ----------------------------------------------------
|
||||
//!
|
||||
//! The key environment variables of interest are those that get set by the wasmedge installer.
|
||||
//! 1. LIBRARY_PATH=$HOME/.wasmedge/lib
|
||||
//! 2. C_INCLUDE_PATH=$HOME/.wasmedge/include
|
||||
//! 3. CPLUS_INCLUDE_PATH=$HOME/.wasmedge/include
|
||||
//!
|
||||
//! Of these 3, we only care about the `LIBRARY_PATH`, where the `libwasmedge.0.dylib` is located.
|
||||
//! 1. WASMEDGE_DIR=$HOME/.wasmedge
|
||||
//! 2. LIBRARY_PATH=$HOME/.wasmedge/lib
|
||||
//! 3. C_INCLUDE_PATH=$HOME/.wasmedge/include
|
||||
//! 4. CPLUS_INCLUDE_PATH=$HOME/.wasmedge/include
|
||||
//!
|
||||
//! For loading plugins, we need to discover the plugin path. The plugin path can be set in the following ways:
|
||||
//!/ * The environment variable "WASMEDGE_PLUGIN_PATH".
|
||||
|
@ -54,21 +53,51 @@
|
|||
//! 2. the wasi-nn plugin `libwasmedgePluginWasiNN.dylib`,
|
||||
//! which is located in `$HOME/.wasmedge/plugin/libwasmedgePluginWasiNN.dylib`.
|
||||
//!
|
||||
//! On Windows and Linux, the concepts are the same, but the file names and
|
||||
//! directory layout of WasmEdge differ from macOS.
|
||||
//!
|
||||
|
||||
#![cfg_attr(feature = "macos_bundle", allow(unused))]
|
||||
|
||||
use std::{
|
||||
ffi::OsStr,
|
||||
path::{Path, PathBuf},
|
||||
process::{Command, Stdio},
|
||||
process::Command,
|
||||
};
|
||||
|
||||
pub const MOXIN_APP_BINARY: &str = "_moxin_app";
|
||||
|
||||
const WASMEDGE_DIR_NAME: &str = ".wasmedge";
|
||||
const LIB_DIR_NAME: &str = "lib";
|
||||
const PLUGIN_DIR_NAME: &str = "plugin";
|
||||
/// The name of the wasmedge installation directory.
|
||||
const WASMEDGE_ROOT_DIR_NAME: &str = {
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))] {
|
||||
".wasmedge"
|
||||
}
|
||||
#[cfg(windows)] {
|
||||
"WasmEdge-0.14.0-Windows"
|
||||
}
|
||||
};
|
||||
|
||||
/// The subdirectory within the WasmEdge root directory where the main dylib is located.
|
||||
const DYLIB_DIR_NAME: &str = {
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))] {
|
||||
"lib"
|
||||
}
|
||||
#[cfg(windows)] {
|
||||
"bin"
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/// The subdirectory within the WasmEdge root directory where the plugin dylibs are located.
|
||||
fn plugin_dir_path_from_root() -> PathBuf {
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))] {
|
||||
PathBuf::from("plugin")
|
||||
}
|
||||
#[cfg(windows)] {
|
||||
Path::new("lib").join("wasmedge")
|
||||
}
|
||||
}
|
||||
|
||||
/// The file name of the main WasmEdge dylib.
|
||||
const WASMEDGE_MAIN_DYLIB: &str = {
|
||||
#[cfg(target_os = "macos")] {
|
||||
"libwasmedge.0.dylib"
|
||||
|
@ -76,7 +105,11 @@ const WASMEDGE_MAIN_DYLIB: &str = {
|
|||
#[cfg(target_os = "linux")] {
|
||||
"libwasmedge.so.0"
|
||||
}
|
||||
#[cfg(windows)] {
|
||||
"wasmedge.dll"
|
||||
}
|
||||
};
|
||||
/// The file name of the Wasi-NN plugin dylib.
|
||||
const WASMEDGE_WASI_NN_PLUGIN_DYLIB: &str = {
|
||||
#[cfg(target_os = "macos")] {
|
||||
"libwasmedgePluginWasiNN.dylib"
|
||||
|
@ -84,20 +117,22 @@ const WASMEDGE_WASI_NN_PLUGIN_DYLIB: &str = {
|
|||
#[cfg(target_os = "linux")] {
|
||||
"libwasmedgePluginWasiNN.so"
|
||||
}
|
||||
#[cfg(windows)] {
|
||||
"wasmedgePluginWasiNN.dll"
|
||||
}
|
||||
};
|
||||
|
||||
const ENV_WASMEDGE_DIR: &str = "WASMEDGE_DIR";
|
||||
#[allow(unused)]
|
||||
const ENV_WASMEDGE_PLUGIN_PATH: &str = "WASMEDGE_PLUGIN_PATH";
|
||||
const ENV_PATH: &str = "PATH";
|
||||
const ENV_C_INCLUDE_PATH: &str = "C_INCLUDE_PATH";
|
||||
const ENV_CPLUS_INCLUDE_PATH: &str = "CPLUS_INCLUDE_PATH";
|
||||
const ENV_LIBRARY_PATH: &str = "LIBRARY_PATH";
|
||||
const ENV_LD_LIBRARY_PATH: &str = {
|
||||
#[cfg(target_os = "macos")] {
|
||||
"DYLD_LIBRARY_PATH"
|
||||
}
|
||||
#[cfg(target_os = "linux")] {
|
||||
"LD_LIBRARY_PATH"
|
||||
}
|
||||
};
|
||||
#[cfg(target_os = "macos")]
|
||||
const ENV_LD_LIBRARY_PATH: &str = "DYLD_LIBRARY_PATH";
|
||||
#[cfg(target_os = "linux")]
|
||||
const ENV_LD_LIBRARY_PATH: &str = "LD_LIBRARY_PATH";
|
||||
#[cfg(target_os = "macos")]
|
||||
const ENV_DYLD_FALLBACK_LIBRARY_PATH: &str = "DYLD_FALLBACK_LIBRARY_PATH";
|
||||
|
||||
|
@ -132,33 +167,35 @@ fn main() -> std::io::Result<()> {
|
|||
// Thus, we set the `WASMEDGE_PLUGIN_PATH` environment variable to `../Frameworks`,
|
||||
// because the run_moxin() function will set the current working directory to `Contents/MacOS/`
|
||||
// within the app bundle, which is the subdirectory that contains the actual moxin executables.
|
||||
std::env::set_var("WASMEDGE_PLUGIN_PATH", "../Frameworks");
|
||||
std::env::set_var(ENV_WASMEDGE_PLUGIN_PATH, "../Frameworks");
|
||||
|
||||
println!("Running within a macOS app bundle.
|
||||
WASMEDGE_PLUGIN_PATH: {:?}",
|
||||
std::env::var("WASMEDGE_PLUGIN_PATH").ok()
|
||||
{ENV_WASMEDGE_PLUGIN_PATH}: {:?}",
|
||||
std::env::var(ENV_WASMEDGE_PLUGIN_PATH).ok()
|
||||
);
|
||||
|
||||
run_moxin().unwrap();
|
||||
run_moxin(None).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
#[cfg(not(feature = "macos_bundle"))]
|
||||
fn main() -> std::io::Result<()> {
|
||||
let (wasmedge_dir_in_use, main_dylib_path, wasi_nn_plugin_path) =
|
||||
// First, check if the wasmedge installation directory exists in the default location.
|
||||
existing_wasmedge_default_dir()
|
||||
// If not, try to find the wasmedge installation directory using environment vars.
|
||||
.or_else(wasmedge_dir_from_env_vars)
|
||||
check_cpu_features();
|
||||
|
||||
let (wasmedge_root_dir_in_use, main_dylib_path, wasi_nn_plugin_path) =
|
||||
// First, try to find the wasmedge installation directory using environment vars.
|
||||
wasmedge_root_dir_from_env_vars()
|
||||
// If not, check if the wasmedge installation directory exists in the default location.
|
||||
.or_else(existing_wasmedge_default_dir)
|
||||
// If we have a wasmedge installation directory, try to find the dylibs within it.
|
||||
.and_then(|wasmedge_dir| find_wasmedge_dylibs(&wasmedge_dir))
|
||||
.and_then(|wasmedge_root_dir| find_wasmedge_dylibs_in_dir(&wasmedge_root_dir))
|
||||
// If we couldn't find the wasmedge directory or the dylibs within an existing directory,
|
||||
// then we must install wasmedge.
|
||||
.or_else(|| wasmedge_default_dir_path()
|
||||
.and_then(|default_path| install_wasmedge(default_path).ok())
|
||||
// If we successfully installed wasmedge, try to find the dylibs again.
|
||||
.and_then(find_wasmedge_dylibs)
|
||||
.and_then(find_wasmedge_dylibs_in_dir)
|
||||
)
|
||||
.expect("failed to find or install wasmedge dylibs");
|
||||
|
||||
|
@ -166,52 +203,52 @@ fn main() -> std::io::Result<()> {
|
|||
wasmedge root dir: {}
|
||||
wasmedge dylib: {}
|
||||
wasi_nn plugin: {}",
|
||||
wasmedge_dir_in_use.display(),
|
||||
wasmedge_root_dir_in_use.display(),
|
||||
main_dylib_path.display(),
|
||||
wasi_nn_plugin_path.display(),
|
||||
);
|
||||
|
||||
apply_env_vars(&wasmedge_dir_in_use);
|
||||
run_moxin().unwrap();
|
||||
Ok(())
|
||||
apply_env_vars(&wasmedge_root_dir_in_use);
|
||||
|
||||
run_moxin(main_dylib_path.parent())
|
||||
}
|
||||
|
||||
|
||||
/// Returns an existing path to the default wasmedge installation directory, i.e., `$HOME/.wasmedge`,
|
||||
/// but only if it exists.
|
||||
/// Returns the path to the default wasmedge installation directory, if it exists.
|
||||
fn existing_wasmedge_default_dir() -> Option<PathBuf> {
|
||||
wasmedge_default_dir_path().and_then(PathExt::path_if_exists)
|
||||
wasmedge_default_dir_path()?.path_if_exists()
|
||||
}
|
||||
|
||||
|
||||
/// Returns the path to where wasmedge is installed by default, i.e., `$HOME/.wasmedge`.
|
||||
/// Returns the path to where wasmedge is installed by default.
|
||||
///
|
||||
/// This does not check if the directory actually exists.
|
||||
fn wasmedge_default_dir_path() -> Option<PathBuf> {
|
||||
directories::UserDirs::new()
|
||||
.map(|user_dirs| user_dirs.home_dir().join(WASMEDGE_DIR_NAME))
|
||||
.map(|dirs| dirs.home_dir().join(WASMEDGE_ROOT_DIR_NAME))
|
||||
}
|
||||
|
||||
|
||||
/// Looks for the wasmedge dylib and wasi_nn plugin dylib in the given `wasmedge_dir`.
|
||||
/// Looks for the wasmedge dylib and wasi_nn plugin dylib in the given `wasmedge_root_dir`.
|
||||
///
|
||||
/// The `wasmedge_dir` should be the root directory of the wasmedge installation;
|
||||
/// The `wasmedge_root_dir` should be the root directory of the wasmedge installation;
|
||||
/// see the crate-level documentation for more information about the expected layout.
|
||||
///
|
||||
/// Returns a tuple of:
|
||||
/// 1. the wasmedge root directory path
|
||||
/// 2. the main wasmedge dylib path
|
||||
/// 3. the wasi_nn plugin dylib path
|
||||
/// if found in `wasmedge_dir/lib/` and `wasmedge_dir/plugin/`.
|
||||
fn find_wasmedge_dylibs<P: AsRef<Path>>(wasmedge_dir: P) -> Option<(PathBuf, PathBuf, PathBuf)> {
|
||||
let main_dylib_path = wasmedge_dir.as_ref()
|
||||
.join(LIB_DIR_NAME)
|
||||
/// If all items were found in their expected locations, this returns a tuple of:
|
||||
/// 1. the wasmedge root directory path,
|
||||
/// 2. the main wasmedge dylib path,
|
||||
/// 3. the wasi_nn plugin dylib path.
|
||||
fn find_wasmedge_dylibs_in_dir<P: AsRef<Path>>(wasmedge_root_dir: P) -> Option<(PathBuf, PathBuf, PathBuf)> {
|
||||
let main_dylib_path = wasmedge_root_dir.as_ref()
|
||||
.join(DYLIB_DIR_NAME)
|
||||
.join(WASMEDGE_MAIN_DYLIB)
|
||||
.path_if_exists()?;
|
||||
let wasi_nn_plugin_path = wasmedge_dir.as_ref()
|
||||
.join(PLUGIN_DIR_NAME)
|
||||
let wasi_nn_plugin_path = wasmedge_root_dir.as_ref()
|
||||
.join(plugin_dir_path_from_root())
|
||||
.join(WASMEDGE_WASI_NN_PLUGIN_DYLIB)
|
||||
.path_if_exists()?;
|
||||
|
||||
Some((wasmedge_dir.as_ref().into(), main_dylib_path, wasi_nn_plugin_path))
|
||||
Some((wasmedge_root_dir.as_ref().into(), main_dylib_path, wasi_nn_plugin_path))
|
||||
}
|
||||
|
||||
|
||||
|
@ -223,8 +260,10 @@ fn find_wasmedge_dylibs<P: AsRef<Path>>(wasmedge_dir: P) -> Option<(PathBuf, Pat
|
|||
///
|
||||
/// source $HOME/.wasmedge/env
|
||||
/// ```
|
||||
fn install_wasmedge<P: AsRef<Path>>(install_path: P) -> Result<P, std::io::Error> {
|
||||
println!("Attempting to install wasmedge to: {}", install_path.as_ref().display());
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
fn install_wasmedge<P: AsRef<Path>>(install_path: P) -> Result<PathBuf, std::io::Error> {
|
||||
use std::process::Stdio;
|
||||
println!("Downloading WasmEdge 0.14.0 from GitHub and installing it to {}", install_path.as_ref().display());
|
||||
let temp_dir = std::env::temp_dir();
|
||||
let curl_script_cmd = Command::new("curl")
|
||||
.arg("-s")
|
||||
|
@ -259,24 +298,77 @@ fn install_wasmedge<P: AsRef<Path>>(install_path: P) -> Result<P, std::io::Error
|
|||
|
||||
apply_env_vars(&install_path);
|
||||
|
||||
Ok(install_path)
|
||||
Ok(install_path.as_ref().to_path_buf())
|
||||
}
|
||||
|
||||
|
||||
/// Applies the environment variable changes defined by `wasmedge_dir/env`.
|
||||
/// Installs WasmEdge by calling out to PowerShell to run the Windows installation steps
|
||||
/// provided in the main Moxin README.
|
||||
///
|
||||
/// The `wasmedge_dir` should be the root directory of the wasmedge installation,
|
||||
/// The given `install_path` is currently ignored, using the [wasmedge_default_dir_path()] instead.
|
||||
///
|
||||
/// The PowerShell script we run simply downloads and extracts the main WasmEdge files and the Wasi-NN plugin.
|
||||
/// ```powershell
|
||||
/// Invoke-WebRequest -Uri "https://github.com/WasmEdge/WasmEdge/releases/download/0.14.0/WasmEdge-0.14.0-windows.zip" -OutFile "$env:TEMP\WasmEdge-0.14.0-windows.zip"
|
||||
/// Expand-Archive -Force -Path "$env:TEMP\WasmEdge-0.14.0-windows.zip" -DestinationPath $home
|
||||
/// Invoke-WebRequest -Uri "https://github.com/WasmEdge/WasmEdge/releases/download/0.14.0/WasmEdge-plugin-wasi_nn-ggml-0.14.0-windows_x86_64.zip" -OutFile "$env:TEMP\WasmEdge-plugin-wasi_nn-ggml-0.14.0-windows_x86_64.zip"
|
||||
/// Expand-Archive -Force -Path "$env:TEMP\WasmEdge-plugin-wasi_nn-ggml-0.14.0-windows_x86_64.zip" -DestinationPath "$home\WasmEdge-0.14.0-Windows"
|
||||
/// ```
|
||||
#[cfg(windows)]
|
||||
fn install_wasmedge<P: AsRef<Path>>(_install_path: P) -> Result<PathBuf, std::io::Error> {
|
||||
println!("Downloading and installing WasmEdge 0.14.0 from GitHub.");
|
||||
let install_wasmedge_ps1 = include_str!("powershell_install_wasmedge.ps1");
|
||||
match powershell_script::PsScriptBuilder::new()
|
||||
.non_interactive(true)
|
||||
.hidden(true) // Don't display a PowerShell window
|
||||
.print_commands(false) // enable this for debugging
|
||||
.build()
|
||||
.run(&install_wasmedge_ps1)
|
||||
{
|
||||
Ok(output) => {
|
||||
if output.success() {
|
||||
// The wasmedge installation directory is currently forced to the default dir path.
|
||||
wasmedge_default_dir_path().ok_or_else(
|
||||
|| std::io::Error::new(std::io::ErrorKind::Other, "BUG: couldn't get WasmEdge default directory path.")
|
||||
)
|
||||
} else {
|
||||
eprintln!("------------- Powershell stdout --------------\n{}", output.stdout().unwrap_or_default());
|
||||
eprintln!("----------------------------------------------\n");
|
||||
eprintln!("------------- Powershell stderr --------------\n{}", output.stderr().unwrap_or_default());
|
||||
eprintln!("----------------------------------------------\n");
|
||||
Err(std::io::Error::new(std::io::ErrorKind::Other, "The WasmEdge install PowerShell script failed."))
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("Failed to install wasmedge: {:?}", err);
|
||||
if let powershell_script::PsError::Powershell(output) = err {
|
||||
eprintln!("------------- Powershell stdout --------------\n{}", output.stdout().unwrap_or_default());
|
||||
eprintln!("----------------------------------------------\n");
|
||||
eprintln!("------------- Powershell stderr --------------\n{}", output.stderr().unwrap_or_default());
|
||||
eprintln!("----------------------------------------------\n");
|
||||
}
|
||||
Err(std::io::Error::new(std::io::ErrorKind::Other, "The WasmEdge install PowerShell script failed."))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Applies the environment variable changes defined by `wasmedge_root_dir/env`.
|
||||
///
|
||||
/// The `wasmedge_root_dir` should be the root directory of the wasmedge installation,
|
||||
/// which is typically `$HOME/.wasmedge`.
|
||||
///
|
||||
/// This does the following:
|
||||
/// * Prepends `wasmedge_dir/bin` to `PATH`.
|
||||
/// * Prepends `wasmedge_dir/lib` to `DYLD_LIBRARY_PATH`, `DYLD_FALLBACK_LIBRARY_PATH`, and `LIBRARY_PATH`.
|
||||
/// * Prepends `wasmedge_dir/include` to `C_INCLUDE_PATH` and `CPLUS_INCLUDE_PATH`.
|
||||
/// * Prepends `wasmedge_root_dir/bin` to `PATH`.
|
||||
/// * Prepends `wasmedge_root_dir/lib` to `DYLD_LIBRARY_PATH`, `DYLD_FALLBACK_LIBRARY_PATH`, and `LIBRARY_PATH`.
|
||||
/// * Prepends `wasmedge_root_dir/include` to `C_INCLUDE_PATH` and `CPLUS_INCLUDE_PATH`.
|
||||
///
|
||||
/// Note that we cannot simply run something like `Command::new("source")...`,
|
||||
/// because `source` is a shell builtin, and the environment changes would only be visible
|
||||
/// within that new process's shell instance -- not to this program.
|
||||
fn apply_env_vars<P: AsRef<Path>>(wasmedge_dir_path: &P) {
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
fn apply_env_vars<P: AsRef<Path>>(wasmedge_root_dir_path: &P) {
|
||||
use std::ffi::OsStr;
|
||||
/// Prepends the given `prefix` to the environment variable with the given `key`.
|
||||
///
|
||||
/// If the environment variable `key` is not set, it is set to the `prefix` value alone.
|
||||
|
@ -291,36 +383,63 @@ fn apply_env_vars<P: AsRef<Path>>(wasmedge_dir_path: &P) {
|
|||
}
|
||||
}
|
||||
|
||||
let wasmedge_dir = wasmedge_dir_path.as_ref();
|
||||
prepend_env_var(ENV_PATH, wasmedge_dir.join("bin"));
|
||||
prepend_env_var(ENV_C_INCLUDE_PATH, wasmedge_dir.join("include"));
|
||||
prepend_env_var(ENV_CPLUS_INCLUDE_PATH, wasmedge_dir.join("include"));
|
||||
prepend_env_var(ENV_LIBRARY_PATH, wasmedge_dir.join("lib"));
|
||||
prepend_env_var(ENV_LD_LIBRARY_PATH, wasmedge_dir.join("lib"));
|
||||
let wasmedge_root_dir = wasmedge_root_dir_path.as_ref();
|
||||
prepend_env_var(ENV_PATH, wasmedge_root_dir.join("bin"));
|
||||
prepend_env_var(ENV_C_INCLUDE_PATH, wasmedge_root_dir.join("include"));
|
||||
prepend_env_var(ENV_CPLUS_INCLUDE_PATH, wasmedge_root_dir.join("include"));
|
||||
prepend_env_var(ENV_LIBRARY_PATH, wasmedge_root_dir.join("lib"));
|
||||
prepend_env_var(ENV_LD_LIBRARY_PATH, wasmedge_root_dir.join("lib"));
|
||||
|
||||
// The DYLD_FALLBACK_LIBRARY_PATH is only used on macOS.
|
||||
#[cfg(target_os = "macos")]
|
||||
prepend_env_var(ENV_DYLD_FALLBACK_LIBRARY_PATH, wasmedge_dir.join("lib"));
|
||||
prepend_env_var(ENV_DYLD_FALLBACK_LIBRARY_PATH, wasmedge_root_dir.join("lib"));
|
||||
}
|
||||
|
||||
|
||||
/// Applies the environment variables needed for Moxin to find WasmEdge on Windows.
|
||||
///
|
||||
/// Currently, this only does the following:
|
||||
/// * Sets [ENV_WASMEDGE_DIR] and [ENV_WASMEDGE_PLUGIN_PATH] to the given `wasmedge_root_dir_path`.
|
||||
#[cfg(windows)]
|
||||
fn apply_env_vars<P: AsRef<Path>>(wasmedge_root_dir_path: &P) {
|
||||
std::env::set_var(ENV_WASMEDGE_DIR, wasmedge_root_dir_path.as_ref());
|
||||
std::env::set_var(ENV_WASMEDGE_PLUGIN_PATH, wasmedge_root_dir_path.as_ref());
|
||||
}
|
||||
|
||||
/// Attempts to discover the wasmedge installation directory using environment variables.
|
||||
fn wasmedge_dir_from_env_vars() -> Option<PathBuf> {
|
||||
std::env::var_os(ENV_LD_LIBRARY_PATH)
|
||||
.or_else(|| std::env::var_os(ENV_LIBRARY_PATH))
|
||||
.or_else(|| std::env::var_os(ENV_C_INCLUDE_PATH))
|
||||
.or_else(|| std::env::var_os(ENV_CPLUS_INCLUDE_PATH))
|
||||
.and_then(|lib_path| PathBuf::from(lib_path)
|
||||
// All four of the above environment variables should point to a child directory
|
||||
// (either `lib/` or `include/`) within the wasmedge root directory.
|
||||
.parent()
|
||||
.and_then(PathExt::path_if_exists)
|
||||
.map(ToOwned::to_owned)
|
||||
)
|
||||
///
|
||||
/// * On Windows, only the [ENV_WASMEDGE_DIR] environment variable can be used.
|
||||
/// * On Linux and macOS, all other environment variables are checked.
|
||||
fn wasmedge_root_dir_from_env_vars() -> Option<PathBuf> {
|
||||
if let Some(dir) = std::env::var_os(ENV_WASMEDGE_DIR).and_then(PathExt::path_if_exists) {
|
||||
return Some(dir.into());
|
||||
}
|
||||
// Note: we cannot use ENV_WASMEDGE_PLUGIN_PATH here, because it can point to multiple directories,
|
||||
// e.g., the wasmedge root dir, or one of the subdirectories within it.
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))] {
|
||||
std::env::var_os(ENV_LD_LIBRARY_PATH)
|
||||
.or_else(|| std::env::var_os(ENV_LIBRARY_PATH))
|
||||
.or_else(|| std::env::var_os(ENV_C_INCLUDE_PATH))
|
||||
.or_else(|| std::env::var_os(ENV_CPLUS_INCLUDE_PATH))
|
||||
.and_then(|lib_path| PathBuf::from(lib_path)
|
||||
// All four of the above environment variables should point to a child directory
|
||||
// (either `lib/` or `include/`) within the wasmedge root directory.
|
||||
.parent()
|
||||
.and_then(PathExt::path_if_exists)
|
||||
.map(ToOwned::to_owned)
|
||||
)
|
||||
}
|
||||
#[cfg(windows)] {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs the `_moxin_app` binary, which must be located in the same directory as this moxin-runner binary.
|
||||
fn run_moxin() -> std::io::Result<()> {
|
||||
///
|
||||
/// An optional path to the directory containing the main WasmEdge dylib can be provided,
|
||||
/// which is currently only used to set the path on Windows.
|
||||
fn run_moxin(_main_wasmedge_dylib_dir: Option<&Path>) -> std::io::Result<()> {
|
||||
let current_exe = std::env::current_exe()?;
|
||||
let current_exe_dir = current_exe.parent().unwrap();
|
||||
let args = std::env::args().collect::<Vec<_>>();
|
||||
|
@ -332,6 +451,25 @@ fn run_moxin() -> std::io::Result<()> {
|
|||
args,
|
||||
);
|
||||
|
||||
// On Windows, the MOXIN_APP_BINARY needs to be able to find the WASMEDGE_MAIN_DYLIB (wasmedge.dll),
|
||||
// so we prepend it to the PATH.
|
||||
#[cfg(windows)] {
|
||||
match (std::env::var_os(ENV_PATH), _main_wasmedge_dylib_dir) {
|
||||
(Some(path), Some(dylib_parent)) => {
|
||||
println!("Prepending \"{}\" to Windows PATH", dylib_parent.display());
|
||||
let new_path = std::env::join_paths(
|
||||
Some(dylib_parent.to_path_buf())
|
||||
.into_iter()
|
||||
.chain(std::env::split_paths(&path))
|
||||
)
|
||||
.expect("BUG: failed to join paths for the main Moxin binary.");
|
||||
std::env::set_var(ENV_PATH, &new_path);
|
||||
|
||||
}
|
||||
_ => eprintln!("BUG: failed to set PATH for the main Moxin binary."),
|
||||
}
|
||||
}
|
||||
|
||||
let _output = Command::new(current_exe_dir.join(MOXIN_APP_BINARY))
|
||||
.current_dir(current_exe_dir)
|
||||
.args(args.into_iter().skip(1)) // skip the first arg (the binary name)
|
||||
|
@ -340,3 +478,85 @@ fn run_moxin() -> std::io::Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
/// Checks that the current CPU supports AVX512, which is required by the current
|
||||
/// builds of WasmEdge 0.14.0 on Windows.
|
||||
///
|
||||
/// Does nothing for other platforms.
|
||||
fn check_cpu_features() {
|
||||
#[cfg(windows)] {
|
||||
if !is_x86_feature_detected!("avx512f") {
|
||||
eprintln!("Feature aes: {}", is_x86_feature_detected!("aes"));
|
||||
eprintln!("Feature pclmulqdq: {}", is_x86_feature_detected!("pclmulqdq"));
|
||||
eprintln!("Feature rdrand: {}", is_x86_feature_detected!("rdrand"));
|
||||
eprintln!("Feature rdseed: {}", is_x86_feature_detected!("rdseed"));
|
||||
eprintln!("Feature tsc: {}", is_x86_feature_detected!("tsc"));
|
||||
eprintln!("Feature mmx: {}", is_x86_feature_detected!("mmx"));
|
||||
eprintln!("Feature sse: {}", is_x86_feature_detected!("sse"));
|
||||
eprintln!("Feature sse2: {}", is_x86_feature_detected!("sse2"));
|
||||
eprintln!("Feature sse3: {}", is_x86_feature_detected!("sse3"));
|
||||
eprintln!("Feature ssse3: {}", is_x86_feature_detected!("ssse3"));
|
||||
eprintln!("Feature sse4.1: {}", is_x86_feature_detected!("sse4.1"));
|
||||
eprintln!("Feature sse4.2: {}", is_x86_feature_detected!("sse4.2"));
|
||||
eprintln!("Feature sse4a: {}", is_x86_feature_detected!("sse4a"));
|
||||
eprintln!("Feature sha: {}", is_x86_feature_detected!("sha"));
|
||||
eprintln!("Feature avx: {}", is_x86_feature_detected!("avx"));
|
||||
eprintln!("Feature avx2: {}", is_x86_feature_detected!("avx2"));
|
||||
eprintln!("Feature avx512f: {}", is_x86_feature_detected!("avx512f"));
|
||||
eprintln!("Feature avx512cd: {}", is_x86_feature_detected!("avx512cd"));
|
||||
eprintln!("Feature avx512er: {}", is_x86_feature_detected!("avx512er"));
|
||||
eprintln!("Feature avx512pf: {}", is_x86_feature_detected!("avx512pf"));
|
||||
eprintln!("Feature avx512bw: {}", is_x86_feature_detected!("avx512bw"));
|
||||
eprintln!("Feature avx512dq: {}", is_x86_feature_detected!("avx512dq"));
|
||||
eprintln!("Feature avx512vl: {}", is_x86_feature_detected!("avx512vl"));
|
||||
eprintln!("Feature avx512ifma: {}", is_x86_feature_detected!("avx512ifma"));
|
||||
eprintln!("Feature avx512vbmi: {}", is_x86_feature_detected!("avx512vbmi"));
|
||||
eprintln!("Feature avx512vpopcntdq: {}", is_x86_feature_detected!("avx512vpopcntdq"));
|
||||
eprintln!("Feature avx512vbmi2: {}", is_x86_feature_detected!("avx512vbmi2"));
|
||||
eprintln!("Feature gfni: {}", is_x86_feature_detected!("gfni"));
|
||||
eprintln!("Feature vaes: {}", is_x86_feature_detected!("vaes"));
|
||||
eprintln!("Feature vpclmulqdq: {}", is_x86_feature_detected!("vpclmulqdq"));
|
||||
eprintln!("Feature avx512vnni: {}", is_x86_feature_detected!("avx512vnni"));
|
||||
eprintln!("Feature avx512bitalg: {}", is_x86_feature_detected!("avx512bitalg"));
|
||||
eprintln!("Feature avx512bf16: {}", is_x86_feature_detected!("avx512bf16"));
|
||||
eprintln!("Feature avx512vp2intersect: {}", is_x86_feature_detected!("avx512vp2intersect"));
|
||||
// eprintln!("Feature avx512fp16: {}", is_x86_feature_detected!("avx512fp16"));
|
||||
eprintln!("Feature f16c: {}", is_x86_feature_detected!("f16c"));
|
||||
eprintln!("Feature fma: {}", is_x86_feature_detected!("fma"));
|
||||
eprintln!("Feature bmi1: {}", is_x86_feature_detected!("bmi1"));
|
||||
eprintln!("Feature bmi2: {}", is_x86_feature_detected!("bmi2"));
|
||||
eprintln!("Feature abm: {}", is_x86_feature_detected!("abm"));
|
||||
eprintln!("Feature lzcnt: {}", is_x86_feature_detected!("lzcnt"));
|
||||
eprintln!("Feature tbm: {}", is_x86_feature_detected!("tbm"));
|
||||
eprintln!("Feature popcnt: {}", is_x86_feature_detected!("popcnt"));
|
||||
eprintln!("Feature fxsr: {}", is_x86_feature_detected!("fxsr"));
|
||||
eprintln!("Feature xsave: {}", is_x86_feature_detected!("xsave"));
|
||||
eprintln!("Feature xsaveopt: {}", is_x86_feature_detected!("xsaveopt"));
|
||||
eprintln!("Feature xsaves: {}", is_x86_feature_detected!("xsaves"));
|
||||
eprintln!("Feature xsavec: {}", is_x86_feature_detected!("xsavec"));
|
||||
eprintln!("Feature cmpxchg16b: {}", is_x86_feature_detected!("cmpxchg16b"));
|
||||
eprintln!("Feature adx: {}", is_x86_feature_detected!("adx"));
|
||||
eprintln!("Feature rtm: {}", is_x86_feature_detected!("rtm"));
|
||||
eprintln!("Feature movbe: {}", is_x86_feature_detected!("movbe"));
|
||||
eprintln!("Feature ermsb: {}", is_x86_feature_detected!("ermsb"));
|
||||
|
||||
use windows_sys::Win32::UI::WindowsAndMessaging::{
|
||||
MessageBoxW, MB_ICONERROR, MB_SETFOREGROUND, MB_TOPMOST,
|
||||
};
|
||||
// SAFE: just displaying an Error dialog box; the program will be terminated regardless.
|
||||
unsafe {
|
||||
MessageBoxW(
|
||||
0,
|
||||
windows_sys::w!(
|
||||
"This CPU does not support AVX512, which is required by Moxin.\n\n\
|
||||
The list of supported CPU features has been logged to the console.\
|
||||
"),
|
||||
windows_sys::w!("Error: Unsupported CPU!"),
|
||||
MB_SETFOREGROUND | MB_TOPMOST | MB_ICONERROR,
|
||||
);
|
||||
}
|
||||
panic!("\nError: this CPU does not support AVX512, which is required by Moxin.\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
$ProgressPreference = 'SilentlyContinue' ## makes downloads much faster
|
||||
Invoke-WebRequest -Uri "https://github.com/WasmEdge/WasmEdge/releases/download/0.14.0/WasmEdge-0.14.0-windows.zip" -OutFile "$env:TEMP\WasmEdge-0.14.0-windows.zip"
|
||||
Expand-Archive -Force -Path "$env:TEMP\WasmEdge-0.14.0-windows.zip" -DestinationPath $home
|
||||
|
||||
Invoke-WebRequest -Uri "https://github.com/WasmEdge/WasmEdge/releases/download/0.14.0/WasmEdge-plugin-wasi_nn-ggml-0.14.0-windows_x86_64.zip" -OutFile "$env:TEMP\WasmEdge-plugin-wasi_nn-ggml-0.14.0-windows_x86_64.zip"
|
||||
Expand-Archive -Force -Path "$env:TEMP\WasmEdge-plugin-wasi_nn-ggml-0.14.0-windows_x86_64.zip" -DestinationPath "$home\WasmEdge-0.14.0-Windows"
|
||||
$ProgressPreference = 'Continue' ## restore default progress bars
|
|
@ -0,0 +1,299 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "before-packaging-command"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cargo_metadata",
|
||||
"directories",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
|
||||
|
||||
[[package]]
|
||||
name = "camino"
|
||||
version = "1.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cargo-platform"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cargo_metadata"
|
||||
version = "0.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037"
|
||||
dependencies = [
|
||||
"camino",
|
||||
"cargo-platform",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "directories"
|
||||
version = "5.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35"
|
||||
dependencies = [
|
||||
"dirs-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.155"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "option-ext"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"libredox",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.204"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.204"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.120"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.72"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
|
@ -0,0 +1,10 @@
|
|||
[package]
|
||||
name = "before-packaging-command"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.79" ## required by cargo-packager
|
||||
description = "A small Rust program that is run by cargo-packager's 'before packaging' commands."
|
||||
|
||||
[dependencies]
|
||||
cargo_metadata = "0.18"
|
||||
directories = "5.0.1"
|
|
@ -0,0 +1,479 @@
|
|||
//! This small program is invoked by cargo-packager during its before-packaging steps.
|
||||
//!
|
||||
//! This program must be run from the root of the project directory,
|
||||
//! which is also where the cargo-packager command must be invoked from.
|
||||
//!
|
||||
//! This program runs in two modes, one for each kind of before-packaging step in cargo-packager.
|
||||
//! It accepts arguments to determine which mode:
|
||||
//! * Argument `before-packaging`: specifies that the `before-packaging-command` is being run
|
||||
//! by cargo-packager, which gets executed only *once* before cargo-packager generates any package bundles.
|
||||
//! * Argument `before-each-package`: specifies that the `before-each-package-command` is being run
|
||||
//! by cargo-packager, which gets executed multiple times: once for *each* package
|
||||
//! that cargo-packager is going to generate.
|
||||
//! * The environment variable `CARGO_PACKAGER_FORMAT` is set by cargo-packager to
|
||||
//! the declare which package format is about to be generated, which include the values
|
||||
//! given here: <https://docs.rs/cargo-packager/latest/cargo_packager/enum.PackageFormat.html>.
|
||||
//! * `app`, `dmg`: for macOS.
|
||||
//! * `deb`, `appimage`, `pacman`: for Linux.
|
||||
//! * `nsis`: for Windows; `nsis` generates an installer `setup.exe`.
|
||||
//! * `wix`: (UNSUPPORTED) for Windows; generates an `.msi` installer package.
|
||||
//!
|
||||
//! This program uses the `CARGO_PACKAGER_FORMAT` environment variable to determine
|
||||
//! which specific build commands and configuration options should be used.
|
||||
//!
|
||||
|
||||
use core::panic;
|
||||
use std::{ffi::OsStr, fs, path::Path, process::{Command, Stdio}};
|
||||
use cargo_metadata::MetadataCommand;
|
||||
|
||||
|
||||
/// Returns the value of the `MAKEPAD_PACKAGE_DIR` environment variable
|
||||
/// that must be set for the given package format.
|
||||
///
|
||||
/// * For macOS app bundles, this should be set to `../Resources`.
|
||||
/// * This only works because the `moxin-runner` binary sets the current working directory
|
||||
/// to the directory where the binary is located, which is `Moxin.app/Contents/MacOS/`.
|
||||
/// (See the `run_moxin` function in `moxin-runner/src/main.rs` for more details.)
|
||||
/// * In a macOS app bundle, the resources directory is in `Moxin.app/Context/Resources/`,
|
||||
/// so that's why we set `MAKEPAD_PACKAGE_DIR` to `../Resources`.
|
||||
/// This must be relative to the binary's location, i.e. up one parent directory.
|
||||
/// * For AppImage packages, this should be set to a relative path that goes up two parent directories
|
||||
/// to account for the fact that AppImage binaries run in a simulated `usr/bin/` directory.
|
||||
/// * Thus, we need to get to the simulated `usr/lib/` directory for Moxin's resources,
|
||||
/// which currently works out to `../../usr/lib/moxin`.
|
||||
/// * Note that this must be a relative path, not an absolute path.
|
||||
/// * For Debian `.deb` packages, this should be set to `/usr/lib/<main-binary-name>`,
|
||||
/// which is currently `/usr/lib/moxin-runner`.
|
||||
/// * This is the directory in which `dpkg` copies app resource files to
|
||||
/// when a user installs the `.deb` package.
|
||||
/// * For Windows NSIS packages, this should be set to `.` (the current dir).
|
||||
/// * This is because the NSIS installer script copies the resources to the same directory
|
||||
/// as the installed binaries, and our `moxin-runner` app changes the current working directory
|
||||
/// to that same directory before running the main Moxin binary.
|
||||
fn makepad_package_dir_value(package_format: &str) -> &'static str {
|
||||
match package_format {
|
||||
"app" | "dmg" => "../Resources",
|
||||
"appimage" => "../../usr/lib/moxin",
|
||||
"deb" | "pacman" => "/usr/lib/moxin",
|
||||
"nsis" => ".",
|
||||
_other => panic!("Unsupported package format: {}", _other),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
let mut is_before_packaging = false;
|
||||
let mut is_before_each_package = false;
|
||||
let mut host_os_opt: Option<String> = None;
|
||||
|
||||
let mut args = std::env::args().peekable();
|
||||
while let Some(arg) = args.next() {
|
||||
if arg.ends_with("before-packaging") || arg.ends_with("before_packaging") {
|
||||
is_before_packaging = true;
|
||||
}
|
||||
if arg.contains("before-each") || arg.contains("before_each") {
|
||||
is_before_each_package = true;
|
||||
}
|
||||
if host_os_opt.is_none() && (arg.contains("host_os") || arg.contains("host-os"))
|
||||
{
|
||||
host_os_opt = arg
|
||||
.split("=")
|
||||
.last()
|
||||
.map(|s| s.to_string())
|
||||
.or_else(|| args.peek().map(|s| s.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
let host_os = host_os_opt.as_deref().unwrap_or(std::env::consts::OS);
|
||||
|
||||
match (is_before_packaging, is_before_each_package) {
|
||||
(true, false) => before_packaging(host_os),
|
||||
(false, true) => before_each_package(host_os),
|
||||
(true, true) => panic!("Cannot run both 'before-packaging' and 'before-each-package' commands at the same time."),
|
||||
(false, false) => panic!("Please specify either the 'before-packaging' or 'before-each-package' command."),
|
||||
}
|
||||
}
|
||||
|
||||
/// This function is run only *once* before cargo-packager generates any package bundles.
|
||||
///
|
||||
/// ## Functionality
|
||||
/// 1. Creates a directory for the resources to be packaged, which is currently `./dist/resources/`.
|
||||
/// 2. Recursively copies the resources from the `makepad-widgets` crate to a subdirectory
|
||||
/// of the directory created in step 1, which is currently `./dist/resources/makepad_widgets/`.
|
||||
/// The location of the `makepad-widgets` crate is determined using `cargo-metadata`.
|
||||
/// 3. Recursively copies the Moxin-specific `./resources` directory to `./dist/resources/moxin/`.
|
||||
/// 4. (If macOS) Downloads WasmEdge and sets up the plugins into the `./wasmedge/` directory.
|
||||
fn before_packaging(host_os: &str) -> std::io::Result<()> {
|
||||
let cwd = std::env::current_dir()?;
|
||||
let dist_resources_dir = cwd.join("dist").join("resources");
|
||||
fs::create_dir_all(&dist_resources_dir)?;
|
||||
|
||||
let moxin_resources_dest = dist_resources_dir.join("moxin").join("resources");
|
||||
let moxin_resources_src = cwd.join("resources");
|
||||
let makepad_widgets_resources_dest = dist_resources_dir.join("makepad_widgets").join("resources");
|
||||
let makepad_widgets_resources_src = {
|
||||
let cargo_metadata = MetadataCommand::new()
|
||||
.exec()
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
|
||||
|
||||
let makepad_widgets_package = cargo_metadata
|
||||
.packages
|
||||
.iter()
|
||||
.find(|package| package.name == "makepad-widgets")
|
||||
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::NotFound, "makepad-widgets package not found"))?;
|
||||
|
||||
makepad_widgets_package.manifest_path
|
||||
.parent()
|
||||
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::NotFound, "makepad-widgets package manifest path not found"))?
|
||||
.join("resources")
|
||||
};
|
||||
|
||||
/// Copy files from source to destination recursively.
|
||||
fn copy_recursively(source: impl AsRef<Path>, destination: impl AsRef<Path>) -> std::io::Result<()> {
|
||||
fs::create_dir_all(&destination)?;
|
||||
for entry in fs::read_dir(source)? {
|
||||
let entry = entry?;
|
||||
let filetype = entry.file_type()?;
|
||||
if filetype.is_dir() {
|
||||
copy_recursively(entry.path(), destination.as_ref().join(entry.file_name()))?;
|
||||
} else {
|
||||
fs::copy(entry.path(), destination.as_ref().join(entry.file_name()))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
println!("Copying makepad-widgets resources...\n --> From: {}\n to: {}", makepad_widgets_resources_src.as_std_path().display(), makepad_widgets_resources_dest.display());
|
||||
copy_recursively(&makepad_widgets_resources_src, &makepad_widgets_resources_dest)?;
|
||||
println!(" --> Done!");
|
||||
println!("Copying moxin resources...\n --> From {}\n to: {}", moxin_resources_src.display(), moxin_resources_dest.display());
|
||||
copy_recursively(&moxin_resources_src, &moxin_resources_dest)?;
|
||||
println!(" --> Done!");
|
||||
|
||||
if host_os == "macos" {
|
||||
download_wasmedge_macos("0.14.0")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
/// Downloads WasmEdge and extracts it to the `./wasmedge/` directory.
|
||||
///
|
||||
/// This function effectively runs the following shell commands:
|
||||
/// ```sh
|
||||
/// mkdir -p ./wasmedge \
|
||||
/// && curl -sfL --show-error https://github.com/WasmEdge/WasmEdge/releases/download/0.14.0/WasmEdge-0.14.0-darwin_arm64.tar.gz | bsdtar -xf- -C ./wasmedge \
|
||||
/// && mkdir -p ./wasmedge/WasmEdge-0.14.0-Darwin/plugin \
|
||||
/// && curl -sf --location --progress-bar --show-error https://github.com/WasmEdge/WasmEdge/releases/download/0.14.0/WasmEdge-plugin-wasi_nn-ggml-0.14.0-darwin_arm64.tar.gz | bsdtar -xf- -C ./wasmedge/WasmEdge-0.14.0-Darwin/plugin; \
|
||||
/// ```
|
||||
fn download_wasmedge_macos(version: &str) -> std::io::Result<()> {
|
||||
// Command 1: Create the destination directory.
|
||||
let dest_dir = std::env::current_dir()?.join("wasmedge");
|
||||
fs::create_dir_all(&dest_dir)?;
|
||||
|
||||
// Command 2: Download and extract WasmEdge.
|
||||
{
|
||||
println!("Downloading wasmedge v{version} to: {}", dest_dir.display());
|
||||
let curl_script_cmd = Command::new("curl")
|
||||
.arg("-sSfL")
|
||||
.arg("https://github.com/WasmEdge/WasmEdge/releases/download/0.14.0/WasmEdge-0.14.0-darwin_arm64.tar.gz")
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()?;
|
||||
|
||||
let bsdtar_cmd = Command::new("bsdtar")
|
||||
.arg("-xf-")
|
||||
.arg("-C")
|
||||
.arg(&dest_dir)
|
||||
.stdin(Stdio::from(curl_script_cmd.stdout.expect("failed to pipe curl stdout into bsdtar stdin")))
|
||||
.spawn()?;
|
||||
|
||||
let output = bsdtar_cmd.wait_with_output()?;
|
||||
if !output.status.success() {
|
||||
eprintln!("Failed to install WasmEdge: {}
|
||||
------------------------- stderr: -------------------------
|
||||
{:?}",
|
||||
output.status,
|
||||
String::from_utf8_lossy(&output.stderr),
|
||||
);
|
||||
return Err(std::io::Error::new(std::io::ErrorKind::Other, "The wasmedge install_v2.sh script failed."));
|
||||
}
|
||||
}
|
||||
|
||||
// Command 3: Create the plugin destination directory.
|
||||
let plugin_dest_dir = dest_dir.join("WasmEdge-0.14.0-Darwin").join("plugin");
|
||||
fs::create_dir_all(&plugin_dest_dir)?;
|
||||
|
||||
// Command 4: Download and extract the Wasi-NN plugin.
|
||||
{
|
||||
println!("Downloading wasmedge v{version} WASI-NN plugin to: {}", plugin_dest_dir.display());
|
||||
let curl_script_cmd = Command::new("curl")
|
||||
.arg("-sSfL")
|
||||
.arg("https://github.com/WasmEdge/WasmEdge/releases/download/0.14.0/WasmEdge-plugin-wasi_nn-ggml-0.14.0-darwin_arm64.tar.gz")
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()?;
|
||||
|
||||
let bsdtar_cmd = Command::new("bsdtar")
|
||||
.arg("-xf-")
|
||||
.arg("-C")
|
||||
.arg(&plugin_dest_dir)
|
||||
.stdin(Stdio::from(curl_script_cmd.stdout.expect("failed to pipe curl stdout into bsdtar stdin")))
|
||||
.spawn()?;
|
||||
|
||||
let output = bsdtar_cmd.wait_with_output()?;
|
||||
if !output.status.success() {
|
||||
eprintln!("Failed to install WasmEdge Wasi-NN plugin: {}
|
||||
------------------------- stderr: -------------------------
|
||||
{:?}",
|
||||
output.status,
|
||||
String::from_utf8_lossy(&output.stderr),
|
||||
);
|
||||
return Err(std::io::Error::new(std::io::ErrorKind::Other, "The wasmedge install_v2.sh script failed."));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
/// The function that is run by cargo-packager's `before-each-package-command`.
|
||||
///
|
||||
/// It's just a simple wrapper that invokes the function for each specific package format.
|
||||
fn before_each_package(host_os: &str) -> std::io::Result<()> {
|
||||
// The `CARGO_PACKAGER_FORMAT` environment variable is required.
|
||||
let format = std::env::var("CARGO_PACKAGER_FORMAT")
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
|
||||
|
||||
let package_format = format.as_str();
|
||||
println!("Running before-each-package-command for {package_format:?}");
|
||||
match package_format {
|
||||
"app" | "dmg" => before_each_package_macos(package_format, host_os),
|
||||
"deb" => before_each_package_deb(package_format, host_os),
|
||||
"appimage" => before_each_package_appimage(package_format, host_os),
|
||||
"pacman" => before_each_package_pacman(package_format, host_os),
|
||||
"nsis" => before_each_package_windows(package_format, host_os),
|
||||
_other => return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
format!("Unknown/unsupported package format {_other:?}"),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Runs the macOS-specific build commands for "app" and "dmg" package formats.
|
||||
///
|
||||
/// This function effectively runs the following shell commands:
|
||||
/// ```sh
|
||||
/// MAKEPAD_PACKAGE_DIR=../Resources cargo build --workspace --release --features macos_bundle \
|
||||
/// && install_name_tool -add_rpath "@executable_path/../Frameworks" ./target/release/_moxin_app;
|
||||
/// ```
|
||||
fn before_each_package_macos(package_format: &str, host_os: &str) -> std::io::Result<()> {
|
||||
assert!(host_os == "macos", "'app' and 'dmg' packages can only be created on macOS.");
|
||||
|
||||
cargo_build(
|
||||
package_format,
|
||||
host_os,
|
||||
&["--features", "macos_bundle"],
|
||||
)?;
|
||||
|
||||
// Use `install_name_tool` to add the `@executable_path` rpath to the binary.
|
||||
let install_name_tool_cmd = Command::new("install_name_tool")
|
||||
.arg("-add_rpath")
|
||||
.arg("@executable_path/../Frameworks")
|
||||
.arg("./target/release/_moxin_app")
|
||||
.spawn()?;
|
||||
|
||||
let output = install_name_tool_cmd.wait_with_output()?;
|
||||
if !output.status.success() {
|
||||
eprintln!("Failed to run install_name_tool command: {}
|
||||
------------------------- stderr: -------------------------
|
||||
{:?}",
|
||||
output.status,
|
||||
String::from_utf8_lossy(&output.stderr),
|
||||
);
|
||||
return Err(std::io::Error::new(std::io::ErrorKind::Other, "Failed to run install_name_tool command for macOS"));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
}
|
||||
|
||||
/// Runs the Linux-specific build commands for AppImage packages.
|
||||
fn before_each_package_appimage(package_format: &str, host_os: &str) -> std::io::Result<()> {
|
||||
assert!(host_os == "linux", "AppImage packages can only be created on Linux.");
|
||||
|
||||
cargo_build(
|
||||
package_format,
|
||||
host_os,
|
||||
std::iter::empty::<&str>(),
|
||||
)?;
|
||||
|
||||
strip_unneeded_linux_binaries(host_os)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
/// Runs the Linux-specific build commands for Debian `.deb` packages.
|
||||
///
|
||||
/// This function effectively runs the following shell commands:
|
||||
/// ```sh
|
||||
/// for path in $(ldd target/release/_moxin_app | awk '{print $3}'); do \
|
||||
/// basename "$/path" ; \
|
||||
/// done \
|
||||
/// | xargs dpkg -S 2> /dev/null | awk '{print $1}' | awk -F ':' '{print $1}' | sort | uniq > ./dist/depends_deb.txt; \
|
||||
/// echo "curl" >> ./dist/depends_deb.txt; \
|
||||
///
|
||||
fn before_each_package_deb(package_format: &str, host_os: &str) -> std::io::Result<()> {
|
||||
assert!(host_os == "linux", "'deb' packages can only be created on Linux.");
|
||||
|
||||
cargo_build(
|
||||
package_format,
|
||||
host_os,
|
||||
&["--features", "reqwest/native-tls-vendored"],
|
||||
)?;
|
||||
|
||||
|
||||
// Create Debian dependencies file by running `ldd` on the binary
|
||||
// and then running `dpkg -S` on each unique shared libraries outputted by `ldd`.
|
||||
let ldd_output = Command::new("ldd")
|
||||
.arg("target/release/_moxin_app")
|
||||
.output()?;
|
||||
|
||||
let ldd_output = if ldd_output.status.success() {
|
||||
String::from_utf8_lossy(&ldd_output.stdout)
|
||||
} else {
|
||||
eprintln!("Failed to run ldd command: {}
|
||||
------------------------- stderr: -------------------------
|
||||
{:?}",
|
||||
ldd_output.status,
|
||||
String::from_utf8_lossy(&ldd_output.stderr),
|
||||
);
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
format!("Failed to run ldd command on {host_os} for package format {package_format:?}")
|
||||
));
|
||||
};
|
||||
|
||||
let mut dpkgs = Vec::new();
|
||||
for line_raw in ldd_output.lines() {
|
||||
let line = line_raw.trim();
|
||||
let lib_name_opt = line.split_whitespace()
|
||||
.next()
|
||||
.and_then(|path| Path::new(path)
|
||||
.file_name()
|
||||
.and_then(|f| f.to_str().to_owned())
|
||||
);
|
||||
let Some(lib_name) = lib_name_opt else { continue };
|
||||
|
||||
let dpkg_output = Command::new("dpkg")
|
||||
.arg("-S")
|
||||
.arg(lib_name)
|
||||
.stderr(Stdio::null())
|
||||
.output()?;
|
||||
let dpkg_output = if dpkg_output.status.success() {
|
||||
String::from_utf8_lossy(&dpkg_output.stdout)
|
||||
} else {
|
||||
// Skip shared libraries that dpkg doesn't know about, e.g., `linux-vdso.so*`
|
||||
continue;
|
||||
};
|
||||
|
||||
let Some(package_name) = dpkg_output.split(':').next() else { continue };
|
||||
println!("Got dpkg dependency {package_name:?} from ldd output: {line:?}");
|
||||
dpkgs.push(package_name.to_string());
|
||||
}
|
||||
dpkgs.sort();
|
||||
dpkgs.dedup();
|
||||
println!("Sorted and de-duplicated dependencies: {:#?}", dpkgs);
|
||||
// `curl` is a fixed dependency for the moxin-runner binary.
|
||||
dpkgs.push("curl".to_string());
|
||||
std::fs::write("./dist/depends_deb.txt", dpkgs.join("\n"))?;
|
||||
|
||||
strip_unneeded_linux_binaries(host_os)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
/// Runs the Linux-specific build commands for PacMan packages.
|
||||
///
|
||||
/// This is untested and may be incomplete, e.g., dependencies are not determined.
|
||||
fn before_each_package_pacman(package_format: &str, host_os: &str) -> std::io::Result<()> {
|
||||
assert!(host_os == "linux", "Pacman packages can only be created on Linux.");
|
||||
|
||||
cargo_build(
|
||||
package_format,
|
||||
host_os,
|
||||
&["--features", "reqwest/native-tls-vendored"],
|
||||
)?;
|
||||
|
||||
strip_unneeded_linux_binaries(host_os)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Runs the Windows-specific build commands for WiX (`.msi`) and NSIS (`.exe`) packages.
|
||||
fn before_each_package_windows(package_format: &str, host_os: &str) -> std::io::Result<()> {
|
||||
assert!(host_os == "windows", "'.exe' and '.msi' packages can only be created on Windows.");
|
||||
|
||||
cargo_build(
|
||||
package_format,
|
||||
host_os,
|
||||
std::iter::empty::<&str>(),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cargo_build<I, S>(package_format: &str, _host_os: &str, extra_args: I) -> std::io::Result<()>
|
||||
where
|
||||
I: IntoIterator<Item = S>,
|
||||
S: AsRef<OsStr>,
|
||||
{
|
||||
let cargo_build_cmd = Command::new("cargo")
|
||||
.arg("build")
|
||||
.arg("--workspace")
|
||||
.arg("--release")
|
||||
.args(extra_args)
|
||||
.env("MAKEPAD_PACKAGE_DIR", makepad_package_dir_value(package_format))
|
||||
.spawn()?;
|
||||
|
||||
let output = cargo_build_cmd.wait_with_output()?;
|
||||
if !output.status.success() {
|
||||
eprintln!("Failed to run cargo build command: {}
|
||||
------------------------- stderr: -------------------------
|
||||
{:?}",
|
||||
output.status,
|
||||
String::from_utf8_lossy(&output.stderr),
|
||||
);
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
format!("Failed to run cargo build command on {_host_os} for package format {package_format:?}")
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
/// Strips unneeded symbols from the Linux binary, which is required for Debian `.deb` packages
|
||||
/// and recommended for all other Linux package formats.
|
||||
fn strip_unneeded_linux_binaries(host_os: &str) -> std::io::Result<()> {
|
||||
assert!(host_os == "linux", "'strip --strip-unneeded' can only be run on Linux.");
|
||||
let strip_cmd = Command::new("strip")
|
||||
.arg("--strip-unneeded")
|
||||
.arg("--remove-section=.comment")
|
||||
.arg("--remove-section=.note")
|
||||
.arg("target/release/_moxin_app")
|
||||
.arg("target/release/moxin")
|
||||
.spawn()?;
|
||||
|
||||
let output = strip_cmd.wait_with_output()?;
|
||||
if !output.status.success() {
|
||||
eprintln!("Failed to run strip command: {}
|
||||
------------------------- stderr: -------------------------
|
||||
{:?}",
|
||||
output.status,
|
||||
String::from_utf8_lossy(&output.stderr),
|
||||
);
|
||||
return Err(std::io::Error::new(std::io::ErrorKind::Other, "Failed to run strip command for Linux"));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -8,7 +8,7 @@ Icon={{icon}}
|
|||
Name={{name}}
|
||||
Terminal=false
|
||||
Type=Application
|
||||
StartupWMClass={{name}}
|
||||
StartupWMClass=_moxin_app
|
||||
{{#if mime_type}}
|
||||
MimeType={{mime_type}}
|
||||
{{/if}}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3.654 1.059 C 3.292 1.125,3.120 1.181,2.820 1.331 C 2.137 1.672,1.637 2.181,1.304 2.878 C 0.998 3.516,1.020 2.804,1.020 12.000 C 1.020 19.750,1.024 20.237,1.091 20.524 C 1.361 21.672,2.328 22.639,3.476 22.909 C 3.763 22.976,4.250 22.980,12.000 22.980 C 19.750 22.980,20.237 22.976,20.524 22.909 C 21.672 22.639,22.639 21.672,22.909 20.524 C 22.976 20.237,22.980 19.750,22.980 12.000 C 22.980 4.250,22.976 3.763,22.909 3.476 C 22.646 2.357,21.747 1.433,20.606 1.107 L 20.300 1.020 12.120 1.013 C 5.545 1.008,3.884 1.017,3.654 1.059 M7.680 12.000 L 7.680 21.000 5.842 21.000 C 4.083 21.000,3.993 20.996,3.783 20.918 C 3.528 20.822,3.251 20.572,3.113 20.315 L 3.020 20.140 3.010 12.086 C 3.003 6.680,3.013 3.981,3.040 3.879 C 3.131 3.543,3.524 3.139,3.854 3.044 C 3.923 3.024,4.813 3.006,5.830 3.004 L 7.680 3.000 7.680 12.000 M20.220 3.083 C 20.518 3.195,20.805 3.482,20.917 3.780 L 21.000 4.003 21.000 12.000 L 21.000 19.997 20.917 20.220 C 20.805 20.518,20.518 20.805,20.220 20.917 C 19.998 21.000,19.970 21.000,14.838 21.000 L 9.680 21.000 9.680 12.000 L 9.680 3.000 14.838 3.000 C 19.970 3.000,19.998 3.000,20.220 3.083 M16.115 7.735 C 16.036 7.764,15.919 7.825,15.855 7.871 C 15.631 8.034,12.287 11.422,12.215 11.560 C 12.163 11.659,12.141 11.788,12.141 12.000 C 12.140 12.255,12.156 12.327,12.247 12.480 C 12.383 12.707,15.812 16.127,15.998 16.221 C 16.074 16.259,16.249 16.300,16.387 16.311 C 16.602 16.328,16.668 16.316,16.858 16.223 C 17.358 15.978,17.575 15.387,17.338 14.912 C 17.302 14.839,16.649 14.155,15.886 13.390 L 14.500 12.000 15.886 10.610 C 16.649 9.845,17.302 9.161,17.338 9.088 C 17.573 8.619,17.349 8.002,16.863 7.775 C 16.634 7.669,16.345 7.653,16.115 7.735 " stroke="none" fill-rule="evenodd" fill="black"></path></svg>
|
Before Width: | Height: | Size: 1.8 KiB |
|
@ -1 +0,0 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M4.654 1.059 C 4.292 1.125,4.120 1.181,3.820 1.331 C 3.137 1.672,2.637 2.181,2.304 2.878 C 1.998 3.516,2.020 2.804,2.020 12.000 C 2.020 19.750,2.024 20.237,2.091 20.524 C 2.361 21.672,3.328 22.639,4.476 22.909 C 4.763 22.976,5.250 22.980,13.000 22.980 C 20.750 22.980,21.237 22.976,21.524 22.909 C 22.672 22.639,23.639 21.672,23.909 20.524 C 23.976 20.237,23.980 19.750,23.980 12.000 C 23.980 4.250,23.976 3.763,23.909 3.476 C 23.646 2.357,22.747 1.433,21.606 1.107 L 21.300 1.020 13.120 1.013 C 6.545 1.008,4.884 1.017,4.654 1.059 M7.560 12.000 L 7.560 21.000 6.282 21.000 C 5.088 21.000,4.988 20.994,4.783 20.918 C 4.528 20.822,4.251 20.572,4.113 20.315 L 4.020 20.140 4.010 12.086 C 4.003 6.680,4.013 3.981,4.040 3.879 C 4.131 3.543,4.524 3.139,4.854 3.044 C 4.923 3.024,5.560 3.006,6.270 3.004 L 7.560 3.000 7.560 12.000 M21.220 3.083 C 21.518 3.195,21.805 3.482,21.917 3.780 L 22.000 4.003 22.000 12.000 L 22.000 19.997 21.917 20.220 C 21.805 20.518,21.518 20.805,21.220 20.917 C 20.998 21.000,20.978 21.000,15.278 21.000 L 9.560 21.000 9.560 12.000 L 9.560 3.000 15.278 3.000 C 20.978 3.000,20.998 3.000,21.220 3.083 M12.670 7.735 C 12.325 7.863,12.109 8.120,12.031 8.495 C 11.991 8.684,11.995 8.750,12.056 8.948 C 12.125 9.171,12.179 9.232,13.533 10.590 L 14.938 12.000 13.533 13.410 C 12.179 14.768,12.125 14.829,12.056 15.052 C 11.994 15.252,11.991 15.315,12.032 15.512 C 12.092 15.801,12.249 16.025,12.498 16.179 C 12.671 16.286,12.729 16.300,12.997 16.299 C 13.212 16.299,13.341 16.277,13.440 16.225 C 13.632 16.124,17.059 12.704,17.193 12.480 C 17.284 12.327,17.300 12.255,17.299 12.000 C 17.299 11.788,17.277 11.659,17.225 11.560 C 17.127 11.372,13.706 7.942,13.488 7.813 C 13.277 7.687,12.895 7.651,12.670 7.735 " stroke="none" fill-rule="evenodd" fill="black"></path></svg>
|
Before Width: | Height: | Size: 1.8 KiB |
|
@ -257,11 +257,11 @@ impl MatchEvent for App {
|
|||
self.ui.redraw(cx);
|
||||
}
|
||||
DownloadAction::Pause(file_id) => {
|
||||
self.store.downloads.pause_download_file(file_id);
|
||||
self.store.downloads.pause_download_file(&file_id);
|
||||
self.ui.redraw(cx);
|
||||
}
|
||||
DownloadAction::Cancel(file_id) => {
|
||||
self.store.downloads.cancel_download_file(file_id);
|
||||
self.store.downloads.cancel_download_file(&file_id);
|
||||
self.ui.redraw(cx);
|
||||
}
|
||||
_ => {}
|
||||
|
|
|
@ -1,73 +1,22 @@
|
|||
use makepad_widgets::*;
|
||||
use crate::data::store::Store;
|
||||
use super::chat_history_card::ChatHistoryCardWidgetRefExt;
|
||||
use crate::data::store::Store;
|
||||
use makepad_widgets::*;
|
||||
|
||||
live_design! {
|
||||
import makepad_widgets::base::*;
|
||||
import makepad_widgets::theme_desktop_dark::*;
|
||||
|
||||
import crate::shared::styles::*;
|
||||
import crate::shared::widgets::FadeView;
|
||||
import crate::shared::widgets::MoxinButton;
|
||||
import crate::shared::widgets::*;
|
||||
import makepad_draw::shader::std::*;
|
||||
|
||||
import crate::chat::shared::ChatAgentAvatar;
|
||||
import crate::chat::chat_history_card::ChatHistoryCard;
|
||||
|
||||
ICON_NEW_CHAT = dep("crate://self/resources/icons/new_chat.svg")
|
||||
ICON_CLOSE_PANEL = dep("crate://self/resources/icons/close_left_panel.svg")
|
||||
ICON_OPEN_PANEL = dep("crate://self/resources/icons/open_left_panel.svg")
|
||||
|
||||
ChatHistoryActions = <View> {
|
||||
spacing: 10,
|
||||
height: Fit
|
||||
|
||||
close_panel_button = <MoxinButton> {
|
||||
width: Fit,
|
||||
height: Fit,
|
||||
icon_walk: {width: 20, height: 20},
|
||||
draw_icon: {
|
||||
svg_file: (ICON_CLOSE_PANEL),
|
||||
fn get_color(self) -> vec4 {
|
||||
return #475467;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open_panel_button = <MoxinButton> {
|
||||
width: Fit,
|
||||
height: Fit,
|
||||
visible: false,
|
||||
icon_walk: {width: 20, height: 20},
|
||||
draw_icon: {
|
||||
svg_file: (ICON_OPEN_PANEL),
|
||||
fn get_color(self) -> vec4 {
|
||||
return #475467;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new_chat_button = <MoxinButton> {
|
||||
width: Fit,
|
||||
height: Fit,
|
||||
icon_walk: {margin: { top: -1 }, width: 21, height: 21},
|
||||
draw_icon: {
|
||||
svg_file: (ICON_NEW_CHAT),
|
||||
fn get_color(self) -> vec4 {
|
||||
return #475467;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ChatHistory = {{ChatHistory}} {
|
||||
flow: Overlay
|
||||
width: Fit
|
||||
height: Fill
|
||||
|
||||
main_content = <FadeView> {
|
||||
width: 300
|
||||
height: Fill,
|
||||
ChatHistory = {{ChatHistory}} <MoxinTogglePanel> {
|
||||
open_content = {
|
||||
<View> {
|
||||
width: Fill,
|
||||
height: Fill,
|
||||
|
@ -90,42 +39,36 @@ live_design! {
|
|||
}
|
||||
}
|
||||
|
||||
<ChatHistoryActions> {
|
||||
padding: {top: 58, left: 25, right: 25}
|
||||
}
|
||||
|
||||
animator: {
|
||||
panel = {
|
||||
default: show,
|
||||
show = {
|
||||
redraw: true,
|
||||
from: {all: Forward {duration: 0.3}}
|
||||
ease: ExpDecay {d1: 0.80, d2: 0.97}
|
||||
apply: {main_content = { width: 300, draw_bg: {opacity: 1.0} }}
|
||||
}
|
||||
hide = {
|
||||
redraw: true,
|
||||
from: {all: Forward {duration: 0.3}}
|
||||
ease: ExpDecay {d1: 0.80, d2: 0.97}
|
||||
apply: {main_content = { width: 110, draw_bg: {opacity: 0.0} }}
|
||||
persistent_content = {
|
||||
default = {
|
||||
after = {
|
||||
new_chat_button = <MoxinButton> {
|
||||
width: Fit,
|
||||
height: Fit,
|
||||
icon_walk: {margin: { top: -1 }, width: 21, height: 21},
|
||||
draw_icon: {
|
||||
svg_file: (ICON_NEW_CHAT),
|
||||
fn get_color(self) -> vec4 {
|
||||
return #475467;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Live, LiveHook, Widget)]
|
||||
pub struct ChatHistory {
|
||||
#[deref]
|
||||
view: View,
|
||||
|
||||
#[animator]
|
||||
animator: Animator,
|
||||
deref: TogglePanel,
|
||||
}
|
||||
|
||||
impl Widget for ChatHistory {
|
||||
fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
|
||||
self.view.handle_event(cx, event, scope);
|
||||
self.deref.handle_event(cx, event, scope);
|
||||
self.widget_match_event(cx, event, scope);
|
||||
|
||||
// TODO This is a hack to redraw the chat history and reflect the
|
||||
|
@ -134,10 +77,6 @@ impl Widget for ChatHistory {
|
|||
if let Event::Signal = event {
|
||||
self.redraw(cx);
|
||||
}
|
||||
|
||||
if self.animator_handle_event(cx, event).must_redraw() {
|
||||
self.redraw(cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep {
|
||||
|
@ -154,7 +93,7 @@ impl Widget for ChatHistory {
|
|||
|
||||
let chats_count = chats.saved_chats.len();
|
||||
|
||||
while let Some(view_item) = self.view.draw_walk(cx, scope, walk).step() {
|
||||
while let Some(view_item) = self.deref.draw_walk(cx, scope, walk).step() {
|
||||
if let Some(mut list) = view_item.as_portal_list().borrow_mut() {
|
||||
list.set_item_range(cx, 0, chats_count);
|
||||
while let Some(item_id) = list.next_visible_item(cx) {
|
||||
|
@ -186,13 +125,13 @@ impl WidgetMatchEvent for ChatHistory {
|
|||
if self.button(id!(close_panel_button)).clicked(&actions) {
|
||||
self.button(id!(close_panel_button)).set_visible(false);
|
||||
self.button(id!(open_panel_button)).set_visible(true);
|
||||
self.animator_play(cx, id!(panel.hide));
|
||||
self.deref.set_open(cx, false);
|
||||
}
|
||||
|
||||
if self.button(id!(open_panel_button)).clicked(&actions) {
|
||||
self.button(id!(open_panel_button)).set_visible(false);
|
||||
self.button(id!(close_panel_button)).set_visible(true);
|
||||
self.animator_play(cx, id!(panel.show));
|
||||
self.deref.set_open(cx, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,11 +10,7 @@ live_design! {
|
|||
import makepad_widgets::theme_desktop_dark::*;
|
||||
|
||||
import crate::shared::styles::*;
|
||||
import crate::shared::widgets::FadeView;
|
||||
import crate::shared::widgets::MoxinButton;
|
||||
import crate::shared::widgets::MoxinSlider;
|
||||
import crate::shared::widgets::MoxinSwitch;
|
||||
import crate::shared::widgets::MoxinTextInput;
|
||||
import crate::shared::widgets::*;
|
||||
import makepad_draw::shader::std::*;
|
||||
|
||||
ICON_CLOSE_PANEL = dep("crate://self/resources/icons/close_right_panel.svg")
|
||||
|
@ -36,50 +32,8 @@ live_design! {
|
|||
}
|
||||
}
|
||||
|
||||
ChatParamsActions = <View> {
|
||||
height: Fit
|
||||
flow: Right
|
||||
|
||||
<View> {
|
||||
width: Fill
|
||||
height: Fit
|
||||
}
|
||||
|
||||
|
||||
close_panel_button = <MoxinButton> {
|
||||
width: Fit,
|
||||
height: Fit,
|
||||
icon_walk: {width: 20, height: 20},
|
||||
draw_icon: {
|
||||
svg_file: (ICON_CLOSE_PANEL),
|
||||
fn get_color(self) -> vec4 {
|
||||
return #475467;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open_panel_button = <MoxinButton> {
|
||||
width: Fit,
|
||||
height: Fit,
|
||||
visible: false,
|
||||
icon_walk: {width: 20, height: 20},
|
||||
draw_icon: {
|
||||
svg_file: (ICON_OPEN_PANEL),
|
||||
fn get_color(self) -> vec4 {
|
||||
return #475467;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ChatParams = {{ChatParams}} {
|
||||
flow: Overlay,
|
||||
width: Fit,
|
||||
height: Fill,
|
||||
|
||||
main_content = <FadeView> {
|
||||
width: 300
|
||||
height: Fill
|
||||
ChatParams = {{ChatParams}} <MoxinTogglePanel> {
|
||||
open_content = {
|
||||
<View> {
|
||||
width: Fill
|
||||
height: Fill
|
||||
|
@ -239,40 +193,39 @@ live_design! {
|
|||
}
|
||||
}
|
||||
|
||||
<ChatParamsActions> {
|
||||
padding: {top: 58, left: 25, right: 25}
|
||||
}
|
||||
|
||||
animator: {
|
||||
panel = {
|
||||
default: show,
|
||||
show = {
|
||||
redraw: true,
|
||||
from: {all: Forward {duration: 0.3}}
|
||||
ease: ExpDecay {d1: 0.80, d2: 0.97}
|
||||
apply: {main_content = { width: 300, draw_bg: {opacity: 1.0} }}
|
||||
persistent_content = {
|
||||
default = {
|
||||
before = {
|
||||
width: Fill
|
||||
}
|
||||
hide = {
|
||||
redraw: true,
|
||||
from: {all: Forward {duration: 0.3}}
|
||||
ease: ExpDecay {d1: 0.80, d2: 0.97}
|
||||
apply: {main_content = { width: 110, draw_bg: {opacity: 0.0} }}
|
||||
open = {
|
||||
draw_icon: {
|
||||
svg_file: (ICON_OPEN_PANEL),
|
||||
}
|
||||
}
|
||||
close = {
|
||||
draw_icon: {
|
||||
svg_file: (ICON_CLOSE_PANEL),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const TOOLTIP_OFFSET: DVec2 = DVec2 { x: -320.0, y: -30.0 };
|
||||
const TOOLTIP_OFFSET_BOTTOM: DVec2 = DVec2 { x: -320.0, y: -100.0 };
|
||||
const TOOLTIP_OFFSET: DVec2 = DVec2 {
|
||||
x: -320.0,
|
||||
y: -30.0,
|
||||
};
|
||||
const TOOLTIP_OFFSET_BOTTOM: DVec2 = DVec2 {
|
||||
x: -320.0,
|
||||
y: -100.0,
|
||||
};
|
||||
|
||||
#[derive(Live, LiveHook, Widget)]
|
||||
pub struct ChatParams {
|
||||
#[deref]
|
||||
view: View,
|
||||
|
||||
#[animator]
|
||||
animator: Animator,
|
||||
deref: TogglePanel,
|
||||
|
||||
#[rust]
|
||||
current_chat_id: Option<ChatID>,
|
||||
|
@ -280,12 +233,8 @@ pub struct ChatParams {
|
|||
|
||||
impl Widget for ChatParams {
|
||||
fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
|
||||
self.view.handle_event(cx, event, scope);
|
||||
self.deref.handle_event(cx, event, scope);
|
||||
self.widget_match_event(cx, event, scope);
|
||||
|
||||
if self.animator_handle_event(cx, event).must_redraw() {
|
||||
self.redraw(cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep {
|
||||
|
@ -327,7 +276,7 @@ impl Widget for ChatParams {
|
|||
self.visible = false;
|
||||
}
|
||||
|
||||
self.view.draw_walk(cx, scope, walk)
|
||||
self.deref.draw_walk(cx, scope, walk)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -342,13 +291,13 @@ impl WidgetMatchEvent for ChatParams {
|
|||
if close.clicked(&actions) {
|
||||
close.set_visible(false);
|
||||
open.set_visible(true);
|
||||
self.animator_play(cx, id!(panel.hide));
|
||||
self.set_open(cx, false);
|
||||
}
|
||||
|
||||
if open.clicked(&actions) {
|
||||
open.set_visible(false);
|
||||
close.set_visible(true);
|
||||
self.animator_play(cx, id!(panel.show));
|
||||
self.set_open(cx, true);
|
||||
}
|
||||
|
||||
if let Some(chat) = store.chats.get_current_chat() {
|
||||
|
@ -402,7 +351,7 @@ impl WidgetMatchEvent for ChatParams {
|
|||
|
||||
impl ChatParams {
|
||||
fn handle_tooltip_actions(&mut self, cx: &mut Cx, actions: &Actions, scope: &mut Scope) {
|
||||
if self.animator.animator_in_state(cx, id!(panel.hide)) {
|
||||
if !self.is_open(cx) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ pub enum DownloadFileAction {
|
|||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum DownloadState {
|
||||
Initializing(f64),
|
||||
Downloading(f64),
|
||||
Errored(f64),
|
||||
Completed,
|
||||
|
@ -36,7 +37,7 @@ impl Download {
|
|||
model: model,
|
||||
sender: tx,
|
||||
receiver: rx,
|
||||
state: DownloadState::Downloading(progress),
|
||||
state: DownloadState::Initializing(progress),
|
||||
notification_pending: false,
|
||||
};
|
||||
|
||||
|
@ -104,12 +105,17 @@ impl Download {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn is_initializing(&self) -> bool {
|
||||
matches!(self.state, DownloadState::Initializing(..))
|
||||
}
|
||||
|
||||
pub fn is_complete(&self) -> bool {
|
||||
matches!(self.state, DownloadState::Completed)
|
||||
}
|
||||
|
||||
pub fn get_progress(&self) -> f64 {
|
||||
match self.state {
|
||||
DownloadState::Initializing(progress) => progress,
|
||||
DownloadState::Downloading(progress) => progress,
|
||||
DownloadState::Errored(progress) => progress,
|
||||
DownloadState::Completed => 1.0,
|
||||
|
|
|
@ -64,13 +64,18 @@ impl Downloads {
|
|||
Ok(files) => {
|
||||
self.pending_downloads = files;
|
||||
|
||||
self.pending_downloads.sort_by(|a, b| b.file.id.cmp(&a.file.id));
|
||||
self.pending_downloads
|
||||
.sort_by(|a, b| b.file.id.cmp(&a.file.id));
|
||||
|
||||
// There is a issue with the backend response where all pending
|
||||
// downloads come with status `Paused` even if they are downloading.
|
||||
self.pending_downloads.iter_mut().for_each(|d| {
|
||||
if self.current_downloads.contains_key(&d.file.id) {
|
||||
d.status = PendingDownloadsStatus::Downloading;
|
||||
if let Some(current) = self.current_downloads.get(&d.file.id) {
|
||||
if current.is_initializing() {
|
||||
d.status = PendingDownloadsStatus::Initializing;
|
||||
} else {
|
||||
d.status = PendingDownloadsStatus::Downloading;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -88,13 +93,13 @@ impl Downloads {
|
|||
.find(|d| d.file.id == file.id)
|
||||
{
|
||||
current_progress = pending.progress;
|
||||
pending.status = PendingDownloadsStatus::Downloading;
|
||||
pending.status = PendingDownloadsStatus::Initializing;
|
||||
} else {
|
||||
let pending_download = PendingDownload {
|
||||
file: file.clone(),
|
||||
model: model.clone(),
|
||||
progress: 0.0,
|
||||
status: PendingDownloadsStatus::Downloading,
|
||||
status: PendingDownloadsStatus::Initializing,
|
||||
};
|
||||
self.pending_downloads.push(pending_download);
|
||||
}
|
||||
|
@ -105,7 +110,14 @@ impl Downloads {
|
|||
);
|
||||
}
|
||||
|
||||
pub fn pause_download_file(&mut self, file_id: FileID) {
|
||||
pub fn pause_download_file(&mut self, file_id: &FileID) {
|
||||
let Some(current_download) = self.current_downloads.get(file_id) else {
|
||||
return;
|
||||
};
|
||||
if current_download.is_initializing() {
|
||||
return;
|
||||
}
|
||||
|
||||
let (tx, rx) = channel();
|
||||
self.backend
|
||||
.as_ref()
|
||||
|
@ -116,15 +128,25 @@ impl Downloads {
|
|||
if let Ok(response) = rx.recv() {
|
||||
match response {
|
||||
Ok(()) => {
|
||||
self.current_downloads.remove(&file_id);
|
||||
self.load_pending_downloads();
|
||||
self.current_downloads.remove(file_id);
|
||||
self.pending_downloads.iter_mut().for_each(|d| {
|
||||
if d.file.id == *file_id {
|
||||
d.status = PendingDownloadsStatus::Paused;
|
||||
}
|
||||
});
|
||||
}
|
||||
Err(err) => eprintln!("Error pausing download: {:?}", err),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn cancel_download_file(&mut self, file_id: FileID) {
|
||||
pub fn cancel_download_file(&mut self, file_id: &FileID) {
|
||||
if let Some(current_download) = self.current_downloads.get(file_id) {
|
||||
if current_download.is_initializing() {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let (tx, rx) = channel();
|
||||
self.backend
|
||||
.as_ref()
|
||||
|
@ -135,8 +157,8 @@ impl Downloads {
|
|||
if let Ok(response) = rx.recv() {
|
||||
match response {
|
||||
Ok(()) => {
|
||||
self.current_downloads.remove(&file_id);
|
||||
self.load_pending_downloads();
|
||||
self.current_downloads.remove(file_id);
|
||||
self.pending_downloads.retain(|d| d.file.id != *file_id);
|
||||
}
|
||||
Err(err) => eprintln!("Error cancelling download: {:?}", err),
|
||||
}
|
||||
|
@ -155,7 +177,6 @@ impl Downloads {
|
|||
.context("Failed to receive delete file response")?
|
||||
.context("Delete file operation failed")?;
|
||||
|
||||
|
||||
self.load_downloaded_files();
|
||||
self.load_pending_downloads();
|
||||
Ok(())
|
||||
|
@ -190,22 +211,25 @@ impl Downloads {
|
|||
.find(|d| d.file.id == id.to_string())
|
||||
{
|
||||
match download.state {
|
||||
DownloadState::Initializing(_) => {
|
||||
pending.status = PendingDownloadsStatus::Initializing;
|
||||
}
|
||||
DownloadState::Downloading(_) => {
|
||||
pending.status = PendingDownloadsStatus::Downloading;
|
||||
}
|
||||
DownloadState::Errored(_) => {
|
||||
pending.status = PendingDownloadsStatus::Error;
|
||||
if download.must_show_notification() {
|
||||
self.pending_notifications.push(DownloadPendingNotification::DownloadErrored(
|
||||
download.file.clone()
|
||||
));
|
||||
self.pending_notifications.push(
|
||||
DownloadPendingNotification::DownloadErrored(download.file.clone()),
|
||||
);
|
||||
}
|
||||
}
|
||||
DownloadState::Completed => {
|
||||
if download.must_show_notification() {
|
||||
self.pending_notifications.push(DownloadPendingNotification::DownloadedFile(
|
||||
download.file.clone()
|
||||
));
|
||||
self.pending_notifications.push(
|
||||
DownloadPendingNotification::DownloadedFile(download.file.clone()),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -146,7 +146,7 @@ live_design! {
|
|||
|
||||
Actions = <View> {
|
||||
width: Fill,
|
||||
height: Fit,
|
||||
height: 40,
|
||||
flow: Right,
|
||||
spacing: 12,
|
||||
|
||||
|
@ -232,6 +232,29 @@ impl Widget for DownloadItem {
|
|||
let progress_bar_width = download.progress * 6.0; // 6.0 = 600px / 100%
|
||||
let label = self.label(id!(progress));
|
||||
match download.status {
|
||||
PendingDownloadsStatus::Initializing => {
|
||||
let downloading_color = vec3(0.035, 0.572, 0.314); //#099250
|
||||
|
||||
label.set_text(&format!("Downloading {:.1}%", download.progress));
|
||||
label.apply_over(
|
||||
cx,
|
||||
live! { draw_text: { color: (downloading_color) }
|
||||
},
|
||||
);
|
||||
|
||||
self.view(id!(progress_bar)).apply_over(
|
||||
cx,
|
||||
live! {
|
||||
width: (progress_bar_width)
|
||||
draw_bg: { color: (downloading_color) }
|
||||
},
|
||||
);
|
||||
|
||||
self.button(id!(pause_button)).set_visible(false);
|
||||
self.button(id!(play_button)).set_visible(false);
|
||||
self.button(id!(retry_button)).set_visible(false);
|
||||
self.button(id!(cancel_button)).set_visible(false);
|
||||
}
|
||||
PendingDownloadsStatus::Downloading => {
|
||||
let downloading_color = vec3(0.035, 0.572, 0.314); //#099250
|
||||
|
||||
|
@ -253,6 +276,7 @@ impl Widget for DownloadItem {
|
|||
self.button(id!(pause_button)).set_visible(true);
|
||||
self.button(id!(play_button)).set_visible(false);
|
||||
self.button(id!(retry_button)).set_visible(false);
|
||||
self.button(id!(cancel_button)).set_visible(true);
|
||||
}
|
||||
PendingDownloadsStatus::Paused => {
|
||||
let paused_color = vec3(0.4, 0.44, 0.52); //#667085
|
||||
|
@ -275,6 +299,7 @@ impl Widget for DownloadItem {
|
|||
self.button(id!(pause_button)).set_visible(false);
|
||||
self.button(id!(play_button)).set_visible(true);
|
||||
self.button(id!(retry_button)).set_visible(false);
|
||||
self.button(id!(cancel_button)).set_visible(true);
|
||||
}
|
||||
PendingDownloadsStatus::Error => {
|
||||
let failed_color = vec3(0.7, 0.11, 0.09); // #B42318
|
||||
|
@ -297,6 +322,7 @@ impl Widget for DownloadItem {
|
|||
self.button(id!(pause_button)).set_visible(false);
|
||||
self.button(id!(play_button)).set_visible(false);
|
||||
self.button(id!(retry_button)).set_visible(true);
|
||||
self.button(id!(cancel_button)).set_visible(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -158,7 +158,7 @@ impl Widget for Downloads {
|
|||
|
||||
let download_count = pending_downloads
|
||||
.iter()
|
||||
.filter(|d| matches!(d.status, PendingDownloadsStatus::Downloading))
|
||||
.filter(|d| matches!(d.status, PendingDownloadsStatus::Downloading | PendingDownloadsStatus::Initializing))
|
||||
.count();
|
||||
self.label(id!(downloading_count))
|
||||
.set_text(&format!("{} downloading", download_count));
|
||||
|
|
|
@ -258,11 +258,14 @@ impl Widget for ModelFilesItem {
|
|||
matches!(download.status, PendingDownloadsStatus::Downloading);
|
||||
let is_retry_download_visible =
|
||||
matches!(download.status, PendingDownloadsStatus::Error);
|
||||
let is_cancel_download_visible = !matches!(download.status, PendingDownloadsStatus::Initializing);
|
||||
|
||||
let status_color = match download.status {
|
||||
PendingDownloadsStatus::Downloading => vec3(0.035, 0.572, 0.314), // #099250
|
||||
PendingDownloadsStatus::Paused => vec3(0.4, 0.44, 0.52), // #667085
|
||||
PendingDownloadsStatus::Error => vec3(0.7, 0.11, 0.09), // #B42318
|
||||
PendingDownloadsStatus::Downloading | PendingDownloadsStatus::Initializing => {
|
||||
vec3(0.035, 0.572, 0.314)
|
||||
} // #099250
|
||||
PendingDownloadsStatus::Paused => vec3(0.4, 0.44, 0.52), // #667085
|
||||
PendingDownloadsStatus::Error => vec3(0.7, 0.11, 0.09), // #B42318
|
||||
};
|
||||
|
||||
self.apply_over(
|
||||
|
@ -295,6 +298,9 @@ impl Widget for ModelFilesItem {
|
|||
pause_download_button = {
|
||||
visible: (is_pause_download_visible)
|
||||
}
|
||||
cancel_download_button = {
|
||||
visible: (is_cancel_download_visible)
|
||||
}
|
||||
}
|
||||
start_chat_button = { visible: false }
|
||||
resume_chat_button = { visible: false }
|
||||
|
|
|
@ -466,4 +466,33 @@ live_design! {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
TogglePanelButton = <MoxinButton> {
|
||||
width: Fit,
|
||||
height: Fit,
|
||||
icon_walk: {width: 20, height: 20},
|
||||
draw_icon: {
|
||||
fn get_color(self) -> vec4 {
|
||||
return #475467;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MoxinTogglePanel = <TogglePanel> {
|
||||
persistent_content = {
|
||||
default = {
|
||||
open = <TogglePanelButton> {
|
||||
visible: false,
|
||||
draw_icon: {
|
||||
svg_file: (TOGGLE_PANEL_OPEN_ICON)
|
||||
}
|
||||
}
|
||||
close = <TogglePanelButton> {
|
||||
draw_icon: {
|
||||
svg_file: (TOGGLE_PANEL_CLOSE_ICON)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue