use heck::AsShoutySnakeCase;
use tauri_utils::write_if_changed;
use std::{
env, fs,
path::{Path, PathBuf},
sync::{Mutex, OnceLock},
};
static CHECKED_FEATURES: OnceLock<Mutex<Vec<String>>> = OnceLock::new();
const PLUGINS: &[(&str, &[(&str, bool)])] = &[
(
"core:path",
&[
("resolve_directory", true),
("resolve", true),
("normalize", true),
("join", true),
("dirname", true),
("extname", true),
("basename", true),
("is_absolute", true),
],
),
(
"core:event",
&[
("listen", true),
("unlisten", true),
("emit", true),
("emit_to", true),
],
),
(
"core:window",
&[
("create", false),
("get_all_windows", true),
("scale_factor", true),
("inner_position", true),
("outer_position", true),
("inner_size", true),
("outer_size", true),
("is_fullscreen", true),
("is_minimized", true),
("is_maximized", true),
("is_focused", true),
("is_decorated", true),
("is_resizable", true),
("is_maximizable", true),
("is_minimizable", true),
("is_closable", true),
("is_visible", true),
("is_enabled", true),
("title", true),
("current_monitor", true),
("primary_monitor", true),
("monitor_from_point", true),
("available_monitors", true),
("cursor_position", true),
("theme", true),
("center", false),
("request_user_attention", false),
("set_enabled", false),
("set_resizable", false),
("set_maximizable", false),
("set_minimizable", false),
("set_closable", false),
("set_title", false),
("maximize", false),
("unmaximize", false),
("minimize", false),
("unminimize", false),
("show", false),
("hide", false),
("close", false),
("destroy", false),
("set_decorations", false),
("set_shadow", false),
("set_effects", false),
("set_always_on_top", false),
("set_always_on_bottom", false),
("set_visible_on_all_workspaces", false),
("set_content_protected", false),
("set_size", false),
("set_min_size", false),
("set_size_constraints", false),
("set_max_size", false),
("set_position", false),
("set_fullscreen", false),
("set_focus", false),
("set_skip_taskbar", false),
("set_cursor_grab", false),
("set_cursor_visible", false),
("set_cursor_icon", false),
("set_cursor_position", false),
("set_ignore_cursor_events", false),
("start_dragging", false),
("start_resize_dragging", false),
("set_progress_bar", false),
("set_icon", false),
("set_title_bar_style", false),
("set_theme", false),
("toggle_maximize", false),
("internal_toggle_maximize", true),
],
),
(
"core:webview",
&[
("create_webview", false),
("create_webview_window", false),
("get_all_webviews", true),
("webview_position", true),
("webview_size", true),
("webview_close", false),
("set_webview_size", false),
("set_webview_position", false),
("set_webview_focus", false),
("set_webview_zoom", false),
("webview_hide", false),
("webview_show", false),
("print", false),
("reparent", false),
("clear_all_browsing_data", false),
("internal_toggle_devtools", true),
],
),
(
"core:app",
&[
("version", true),
("name", true),
("tauri_version", true),
("app_show", false),
("app_hide", false),
("default_window_icon", false),
("set_app_theme", false),
],
),
(
"core:image",
&[
("new", true),
("from_bytes", true),
("from_path", true),
("rgba", true),
("size", true),
],
),
("core:resources", &[("close", true)]),
(
"core:menu",
&[
("new", true),
("append", true),
("prepend", true),
("insert", true),
("remove", true),
("remove_at", true),
("items", true),
("get", true),
("popup", true),
("create_default", true),
("set_as_app_menu", true),
("set_as_window_menu", true),
("text", true),
("set_text", true),
("is_enabled", true),
("set_enabled", true),
("set_accelerator", true),
("set_as_windows_menu_for_nsapp", true),
("set_as_help_menu_for_nsapp", true),
("is_checked", true),
("set_checked", true),
("set_icon", true),
],
),
(
"core:tray",
&[
("new", true),
("get_by_id", true),
("remove_by_id", true),
("set_icon", true),
("set_menu", true),
("set_tooltip", true),
("set_title", true),
("set_visible", true),
("set_temp_dir_path", true),
("set_icon_as_template", true),
("set_show_menu_on_left_click", true),
],
),
];
fn has_feature(feature: &str) -> bool {
CHECKED_FEATURES
.get_or_init(Default::default)
.lock()
.unwrap()
.push(feature.to_string());
std::env::var(format!("CARGO_FEATURE_{}", AsShoutySnakeCase(feature)))
.map(|x| x == "1")
.unwrap_or(false)
}
fn alias(alias: &str, has_feature: bool) {
println!("cargo:rustc-check-cfg=cfg({alias})");
if has_feature {
println!("cargo:rustc-cfg={alias}");
}
}
fn main() {
let custom_protocol = has_feature("custom-protocol");
let dev = !custom_protocol;
alias("custom_protocol", custom_protocol);
alias("dev", dev);
println!("cargo:dev={dev}");
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
let mobile = target_os == "ios" || target_os == "android";
alias("desktop", !mobile);
alias("mobile", mobile);
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let checked_features_out_path = out_dir.join("checked_features");
std::fs::write(
checked_features_out_path,
CHECKED_FEATURES.get().unwrap().lock().unwrap().join(","),
)
.expect("failed to write checked_features file");
let target_env = std::env::var("CARGO_CFG_TARGET_ENV");
let is_tauri_workspace = std::env::var("__TAURI_WORKSPACE__").map_or(false, |v| v == "true");
if is_tauri_workspace && target_os == "windows" && Ok("msvc") == target_env.as_deref() {
embed_manifest_for_tests();
}
if target_os == "android" {
fn env_var(var: &str) -> String {
std::env::var(var).unwrap_or_else(|_| {
panic!("`{var}` is not set, which is needed to generate the kotlin files for android.")
})
}
if let Ok(kotlin_out_dir) = std::env::var("WRY_ANDROID_KOTLIN_FILES_OUT_DIR") {
let package = env_var("WRY_ANDROID_PACKAGE");
let library = env_var("WRY_ANDROID_LIBRARY");
let kotlin_out_dir = PathBuf::from(&kotlin_out_dir)
.canonicalize()
.unwrap_or_else(move |_| {
panic!("Failed to canonicalize `WRY_ANDROID_KOTLIN_FILES_OUT_DIR` path {kotlin_out_dir}")
});
let kotlin_files_path =
PathBuf::from(env_var("CARGO_MANIFEST_DIR")).join("mobile/android-codegen");
println!("cargo:rerun-if-changed={}", kotlin_files_path.display());
let kotlin_files =
fs::read_dir(kotlin_files_path).expect("failed to read Android codegen directory");
for file in kotlin_files {
let file = file.unwrap();
let content = fs::read_to_string(file.path())
.expect("failed to read kotlin file as string")
.replace("{{package}}", &package)
.replace("{{library}}", &library);
let out_path = kotlin_out_dir.join(file.file_name());
write_if_changed(&out_path, &content).expect("Failed to write kotlin file");
println!("cargo:rerun-if-changed={}", out_path.display());
}
}
if let Some(project_dir) = env::var_os("TAURI_ANDROID_PROJECT_PATH").map(PathBuf::from) {
let package_unescaped = env::var("TAURI_ANDROID_PACKAGE_UNESCAPED")
.unwrap_or_else(|_| env_var("WRY_ANDROID_PACKAGE").replace('`', ""));
let tauri_proguard =
include_str!("./mobile/proguard-tauri.pro").replace("$PACKAGE", &package_unescaped);
std::fs::write(
project_dir.join("app").join("proguard-tauri.pro"),
tauri_proguard,
)
.expect("failed to write proguard-tauri.pro");
}
let lib_path =
PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR").unwrap()).join("mobile/android");
println!("cargo:android_library_path={}", lib_path.display());
}
#[cfg(target_os = "macos")]
{
if target_os == "ios" {
let lib_path =
PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR").unwrap()).join("mobile/ios-api");
tauri_utils::build::link_apple_library("Tauri", &lib_path);
println!("cargo:ios_library_path={}", lib_path.display());
}
}
define_permissions(&out_dir);
}
const LICENSE_HEADER: &str = r"# Copyright 2019-2024 Tauri Programme within The Commons Conservancy
# SPDX-License-Identifier: Apache-2.0
# SPDX-License-Identifier: MIT
";
fn define_permissions(out_dir: &Path) {
for (plugin, commands) in PLUGINS {
let plugin_directory_name = plugin.strip_prefix("core:").unwrap_or(plugin);
let permissions_out_dir = out_dir.join("permissions").join(plugin_directory_name);
let autogenerated =
permissions_out_dir.join(tauri_utils::acl::build::AUTOGENERATED_FOLDER_NAME);
let commands_dir = autogenerated.join("commands");
tauri_utils::acl::build::autogenerate_command_permissions(
&commands_dir,
&commands.iter().map(|(cmd, _)| *cmd).collect::<Vec<_>>(),
LICENSE_HEADER,
false,
);
let default_permissions = commands
.iter()
.filter(|(_cmd, default)| *default)
.map(|(cmd, _)| {
let slugified_command = cmd.replace('_', "-");
format!("\"allow-{slugified_command}\"")
})
.collect::<Vec<_>>()
.join(", ");
let default_toml = format!(
r###"{LICENSE_HEADER}# Automatically generated - DO NOT EDIT!
[default]
description = "Default permissions for the plugin."
permissions = [{default_permissions}]
"###,
);
let out_path = autogenerated.join("default.toml");
write_if_changed(out_path, default_toml)
.unwrap_or_else(|_| panic!("unable to autogenerate default permissions"));
let permissions = tauri_utils::acl::build::define_permissions(
&permissions_out_dir
.join("**")
.join("*.toml")
.to_string_lossy(),
&format!("tauri:{plugin}"),
out_dir,
|_| true,
)
.unwrap_or_else(|e| panic!("failed to define permissions for {plugin}: {e}"));
let docs_out_dir = Path::new("permissions")
.join(plugin_directory_name)
.join("autogenerated");
fs::create_dir_all(&docs_out_dir).expect("failed to create plugin documentation directory");
tauri_utils::acl::build::generate_docs(
&permissions,
&docs_out_dir,
plugin.strip_prefix("tauri-plugin-").unwrap_or(plugin),
)
.expect("failed to generate plugin documentation page");
}
define_default_permission_set(out_dir);
}
fn define_default_permission_set(out_dir: &Path) {
let permissions_out_dir = out_dir.join("permissions");
fs::create_dir_all(&permissions_out_dir)
.expect("failed to create core:default permissions directory");
let default_toml = permissions_out_dir.join("default.toml");
let toml_content = format!(
r#"# {LICENSE_HEADER}
[default]
description = """Default core plugins set which includes:
{}
"""
permissions = [{}]
"#,
PLUGINS
.iter()
.map(|(k, _)| format!("- '{k}:default'"))
.collect::<Vec<_>>()
.join("\n"),
PLUGINS
.iter()
.map(|(k, _)| format!("'{k}:default'"))
.collect::<Vec<_>>()
.join(",")
);
write_if_changed(&default_toml, toml_content)
.unwrap_or_else(|_| panic!("unable to autogenerate core:default set"));
let _ = tauri_utils::acl::build::define_permissions(
&permissions_out_dir.join("*.toml").to_string_lossy(),
"tauri:core",
out_dir,
|_| true,
)
.unwrap_or_else(|e| panic!("failed to define permissions for `core:default` : {e}"));
}
fn embed_manifest_for_tests() {
static WINDOWS_MANIFEST_FILE: &str = "windows-app-manifest.xml";
let manifest = std::env::current_dir()
.unwrap()
.join("../tauri-build/src")
.join(WINDOWS_MANIFEST_FILE);
println!("cargo:rerun-if-changed={}", manifest.display());
println!("cargo:rustc-link-arg=/MANIFEST:EMBED");
println!(
"cargo:rustc-link-arg=/MANIFESTINPUT:{}",
manifest.to_str().unwrap()
);
println!("cargo:rustc-link-arg=/WX");
}