Skip to content
Tauri

Embedding External Binaries

You may need to embed external binaries to add additional functionality to your application or prevent users from installing additional dependencies (e.g., Node.js or Python). We call this binary a sidecar.

Binaries are executables written in any programming language. Common use cases are Python CLI applications or API servers bundled using pyinstaller.

To bundle the binaries of your choice, you can add the externalBin property to the tauri > bundle object in your tauri.conf.json. externalBin expects a list of strings targeting binaries either with absolute or relative paths.

Here is a sample to illustrate the configuration. This is not a complete tauri.conf.json file:

src-tauri/tauri.conf.json
{
"bundle": {
"externalBin": [
"/absolute/path/to/sidecar",
"relative/path/to/binary",
"binaries/my-sidecar"
]
}
}

To make the external binary work on each supported architecture, a binary with the same name and a -$TARGET_TRIPLE suffix must exist on the specified path. For instance, "externalBin": ["binaries/my-sidecar"] requires a src-tauri/binaries/my-sidecar-x86_64-unknown-linux-gnu executable on Linux or src-tauri/binaries/my-sidecar-aarch64-apple-darwin on Mac OS with Apple Silicon.

You can find your current platform’s -$TARGET_TRIPLE suffix by looking at the host: property reported by the rustc -Vv command.

If the grep and cut commands are available, as they should on most Unix systems, you can extract the target triple directly with the following command:

Terminal window
rustc -Vv | grep host | cut -f2 -d' '

On Windows you can use PowerShell instead:

Terminal window
rustc -Vv | Select-String "host:" | ForEach-Object {$_.Line.split(" ")[1]}

Here’s a Node.js script to append the target triple to a binary:

const execa = require('execa');
const fs = require('fs');
let extension = '';
if (process.platform === 'win32') {
extension = '.exe';
}
async function main() {
const rustInfo = (await execa('rustc', ['-vV'])).stdout;
const targetTriple = /host: (\S+)/g.exec(rustInfo)[1];
if (!targetTriple) {
console.error('Failed to determine platform target triple');
}
fs.renameSync(
`src-tauri/binaries/sidecar${extension}`,
`src-tauri/binaries/sidecar-${targetTriple}${extension}`
);
}
main().catch((e) => {
throw e;
});

Note that this script will not work if you compile for a different architecture than the one its running on.

Running it from Rust

On the Rust side, import the tauri_plugin_shell::ShellExt trait and call the shell().sidecar() function on the AppHandle:

use tauri_plugin_shell::ShellExt;
use tauri_plugin_shell::process::CommandEvent;
// `sidecar()` expects just the filename, NOT the whole path like in JavaScript
let sidecar_command = app.shell().sidecar("my-sidecar").unwrap();
let (mut rx, mut _child) = sidecar_command
.spawn()
.expect("Failed to spawn sidecar");
tauri::async_runtime::spawn(async move {
// read events such as stdout
while let Some(event) = rx.recv().await {
if let CommandEvent::Stdout(line) = event {
window
.emit("message", Some(format!("'{}'", line)))
.expect("failed to emit event");
// write to stdin
child.write("message from Rust\n".as_bytes()).unwrap();
}
}
});

You can place this code inside a Tauri command to easily pass the AppHandle or you can store a reference to the AppHandle in the builder script to access it elsewhere in your application.

Running it from JavaScript

In the JavaScript code, import the Command class from the @tauri-apps/plugin-shell module and use the sidecar static method.

import { Command } from '@tauri-apps/plugin-shell';
// `binaries/my-sidecar` is the EXACT value specified on `tauri.conf.json > tauri > bundle > externalBin`
const sidecar_command = Command.sidecar('binaries/my-sidecar');
const output = await sidecar_command.execute();

Passing arguments

You can pass arguments to Sidecar commands just like you would for running normal Commands.

Arguments can be either static (e.g. -o or serve) or dynamic (e.g. <file_path> or localhost:<PORT>). You define the arguments in the exact order in which you’d call them. Static arguments are defined as-is, while dynamic arguments can be defined using a regular expression.

First, define the arguments that need to be passed to the sidecar command in src-tauri/capabilities/main.json:

src-tauri/capabilities/main.json
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Capability for the main window",
"windows": ["main"],
"permissions": [
"path:default",
"event:default",
"window:default",
"app:default",
"resources:default",
"menu:default",
"tray:default",
{
"identifier": "shell:allow-execute",
"allow": [
{
"args": [
"arg1",
"-a",
"--arg2",
{
"validator": "\\S+"
}
],
"cmd": "",
"name": "binaries/my-sidecar",
"sidecar": true
}
]
},
"shell:allow-open"
]
}

Then, to call the sidecar command, simply pass in all the arguments as an array.

In Rust:

use tauri_plugin_shell::ShellExt;
#[tauri::command]
async fn call_my_sidecar(app: tauri::AppHandle) {
let sidecar_command = app.shell()
.sidecar("my-sidecar")
.unwrap()
.args(["arg1", "-a", "--arg2", "any-string-that-matches-the-validator"]);
let (mut _rx, mut _child) = sidecar_command.spawn().unwrap();
}

In JavaScript:

import { Command } from '@tauri-apps/plugin-shell';
// `binaries/my-sidecar` is the EXACT value specified on `tauri.conf.json > tauri > bundle > externalBin`
// notice that the args array matches EXACTLY what is specified on `tauri.conf.json`.
const command = Command.sidecar('binaries/my-sidecar', [
'arg1',
'-a',
'--arg2',
'any-string-that-matches-the-validator',
]);
const output = await command.execute();

© 2024 Tauri Contributors. CC-BY / MIT