commit e7270dd695e9d0f9e47c57c00af94872ec31582b
parent abc5fdd01dababbdaedebf4d2fcbcc04817f98c5
Author: triesap <tyson@radroots.org>
Date: Fri, 17 Apr 2026 20:19:36 +0000
build: restore standalone buildability
Diffstat:
6 files changed, 318 insertions(+), 46 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -3523,6 +3523,7 @@ dependencies = [
[[package]]
name = "mf2-i18n-build"
version = "0.1.0"
+source = "git+https://github.com/triesap/mf2-i18n.git?rev=0c3ba2729b309f27aed3e27ae4e753b0147a75ec#0c3ba2729b309f27aed3e27ae4e753b0147a75ec"
dependencies = [
"blake3",
"hex",
@@ -3537,10 +3538,12 @@ dependencies = [
[[package]]
name = "mf2-i18n-core"
version = "0.1.0"
+source = "git+https://github.com/triesap/mf2-i18n.git?rev=0c3ba2729b309f27aed3e27ae4e753b0147a75ec#0c3ba2729b309f27aed3e27ae4e753b0147a75ec"
[[package]]
name = "mf2-i18n-embedded"
version = "0.1.0"
+source = "git+https://github.com/triesap/mf2-i18n.git?rev=0c3ba2729b309f27aed3e27ae4e753b0147a75ec#0c3ba2729b309f27aed3e27ae4e753b0147a75ec"
dependencies = [
"mf2-i18n-core",
]
@@ -3548,6 +3551,7 @@ dependencies = [
[[package]]
name = "mf2-i18n-native"
version = "0.1.0"
+source = "git+https://github.com/triesap/mf2-i18n.git?rev=0c3ba2729b309f27aed3e27ae4e753b0147a75ec#0c3ba2729b309f27aed3e27ae4e753b0147a75ec"
dependencies = [
"mf2-i18n-core",
"mf2-i18n-embedded",
@@ -3559,6 +3563,7 @@ dependencies = [
[[package]]
name = "mf2-i18n-runtime"
version = "0.1.0"
+source = "git+https://github.com/triesap/mf2-i18n.git?rev=0c3ba2729b309f27aed3e27ae4e753b0147a75ec#0c3ba2729b309f27aed3e27ae4e753b0147a75ec"
dependencies = [
"ed25519-dalek",
"hex",
@@ -3573,6 +3578,7 @@ dependencies = [
[[package]]
name = "mf2-i18n-std"
version = "0.1.0"
+source = "git+https://github.com/triesap/mf2-i18n.git?rev=0c3ba2729b309f27aed3e27ae4e753b0147a75ec#0c3ba2729b309f27aed3e27ae4e753b0147a75ec"
dependencies = [
"chrono",
"intl_pluralrules",
@@ -4611,7 +4617,6 @@ dependencies = [
"radroots_app_state",
"radroots_app_sync",
"radroots_app_ui",
- "radroots_runtime_paths",
"thiserror 2.0.18",
]
@@ -4676,14 +4681,6 @@ dependencies = [
]
[[package]]
-name = "radroots_runtime_paths"
-version = "0.1.0-alpha.2"
-dependencies = [
- "serde",
- "thiserror 1.0.69",
-]
-
-[[package]]
name = "rand"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
@@ -26,9 +26,9 @@ gpui = "0.2.2"
gpui-component = "0.5.1"
gpui-component-assets = "0.5.1"
hex = "0.4"
-mf2-i18n-build = { path = "../../../../vendor/triesap/mf2-i18n/crates/mf2-i18n-build", version = "0.1.0" }
-mf2-i18n-core = { path = "../../../../vendor/triesap/mf2-i18n/crates/mf2-i18n-core", version = "0.1.0" }
-mf2-i18n-native = { path = "../../../../vendor/triesap/mf2-i18n/crates/mf2-i18n-native", version = "0.1.0" }
+mf2-i18n-build = { git = "https://github.com/triesap/mf2-i18n.git", rev = "0c3ba2729b309f27aed3e27ae4e753b0147a75ec" }
+mf2-i18n-core = { git = "https://github.com/triesap/mf2-i18n.git", rev = "0c3ba2729b309f27aed3e27ae4e753b0147a75ec" }
+mf2-i18n-native = { git = "https://github.com/triesap/mf2-i18n.git", rev = "0c3ba2729b309f27aed3e27ae4e753b0147a75ec" }
radroots_app_core = { path = "crates/shared/core", version = "0.1.0" }
radroots_app_i18n = { path = "crates/shared/i18n", version = "0.1.0" }
radroots_app_models = { path = "crates/shared/models", version = "0.1.0" }
@@ -36,7 +36,6 @@ radroots_app_sqlite = { path = "crates/shared/sqlite", version = "0.1.0" }
radroots_app_state = { path = "crates/shared/state", version = "0.1.0" }
radroots_app_sync = { path = "crates/shared/sync", version = "0.1.0" }
radroots_app_ui = { path = "crates/shared/ui", version = "0.1.0" }
-radroots_runtime_paths = { path = "../lib/crates/runtime_paths", version = "0.1.0-alpha.2" }
rusqlite = { version = "0.32", features = ["bundled"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
diff --git a/crates/launchers/desktop/Cargo.toml b/crates/launchers/desktop/Cargo.toml
@@ -18,7 +18,6 @@ radroots_app_sqlite.workspace = true
radroots_app_state.workspace = true
radroots_app_sync.workspace = true
radroots_app_ui.workspace = true
-radroots_runtime_paths.workspace = true
thiserror.workspace = true
[lints]
diff --git a/crates/launchers/desktop/src/substrate.rs b/crates/launchers/desktop/src/substrate.rs
@@ -1,15 +1,12 @@
use std::path::PathBuf;
+use radroots_app_core::{AppRuntimePathsError, AppRuntimeRoots};
use radroots_app_models::AppMode;
use radroots_app_sqlite::{AppSqliteError, AppSqliteStore, DatabaseTarget};
use radroots_app_state::{
AppShellProjection, AppStateStore, AppStateStoreError, InMemoryAppStateRepository,
};
use radroots_app_sync::{AppSyncProjection, SyncCheckpointStatus, SyncConflictStatus};
-use radroots_runtime_paths::{
- RadrootsPathOverrides, RadrootsPathProfile, RadrootsPathResolver, RadrootsRuntimeNamespace,
- RadrootsRuntimePathsError,
-};
use thiserror::Error;
const APP_DATABASE_FILE_NAME: &str = "app.sqlite3";
@@ -34,13 +31,7 @@ impl DesktopAppSubstrateSummary {
}
fn try_bootstrap() -> Result<Self, DesktopAppSubstrateError> {
- let namespace = RadrootsRuntimeNamespace::app("app")?;
- let roots = RadrootsPathResolver::current()
- .resolve(
- RadrootsPathProfile::InteractiveUser,
- &RadrootsPathOverrides::default(),
- )?
- .namespaced(&namespace);
+ let roots = AppRuntimeRoots::current_desktop()?;
let database_path = roots.data.join(APP_DATABASE_FILE_NAME);
let sqlite_store = AppSqliteStore::open(DatabaseTarget::Path(database_path.clone()))?;
let shell_store = AppStateStore::load(InMemoryAppStateRepository::default())?;
@@ -80,7 +71,7 @@ impl DesktopAppSubstrateSummary {
#[derive(Debug, Error)]
enum DesktopAppSubstrateError {
#[error(transparent)]
- RuntimePaths(#[from] RadrootsRuntimePathsError),
+ RuntimePaths(#[from] AppRuntimePathsError),
#[error(transparent)]
Sqlite(#[from] AppSqliteError),
#[error(transparent)]
@@ -91,41 +82,31 @@ enum DesktopAppSubstrateError {
mod tests {
use std::path::PathBuf;
- use radroots_runtime_paths::{
- RadrootsHostEnvironment, RadrootsPathOverrides, RadrootsPathProfile, RadrootsPathResolver,
- RadrootsPlatform, RadrootsRuntimeNamespace,
- };
+ use radroots_app_core::{AppRuntimeHostEnvironment, AppRuntimePlatform, AppRuntimeRoots};
use super::APP_DATABASE_FILE_NAME;
#[test]
fn desktop_namespace_uses_canonical_app_data_root() {
- let namespace = RadrootsRuntimeNamespace::app("app").expect("app namespace should parse");
- let resolver = RadrootsPathResolver::new(
- RadrootsPlatform::Macos,
- RadrootsHostEnvironment {
+ let roots = AppRuntimeRoots::for_desktop(
+ AppRuntimePlatform::Macos,
+ AppRuntimeHostEnvironment {
home_dir: Some(PathBuf::from("/Users/treesap")),
- ..RadrootsHostEnvironment::default()
+ ..AppRuntimeHostEnvironment::default()
},
- );
- let namespaced = resolver
- .resolve(
- RadrootsPathProfile::InteractiveUser,
- &RadrootsPathOverrides::default(),
- )
- .expect("interactive user roots should resolve")
- .namespaced(&namespace);
+ )
+ .expect("interactive user roots should resolve");
assert_eq!(
- namespaced.data,
+ roots.data,
PathBuf::from("/Users/treesap/.radroots/data/apps/app")
);
assert_eq!(
- namespaced.logs,
+ roots.logs,
PathBuf::from("/Users/treesap/.radroots/logs/apps/app")
);
assert_eq!(
- namespaced.data.join(APP_DATABASE_FILE_NAME),
+ roots.data.join(APP_DATABASE_FILE_NAME),
PathBuf::from("/Users/treesap/.radroots/data/apps/app/app.sqlite3")
);
}
diff --git a/crates/shared/core/src/lib.rs b/crates/shared/core/src/lib.rs
@@ -1,8 +1,13 @@
#![forbid(unsafe_code)]
+mod paths;
mod runtime;
mod startup;
+pub use paths::{
+ APP_RUNTIME_NAMESPACE, APP_RUNTIME_NAMESPACE_KIND, APP_RUNTIME_NAMESPACE_VALUE,
+ AppRuntimeHostEnvironment, AppRuntimePathsError, AppRuntimePlatform, AppRuntimeRoots,
+};
pub use runtime::{
APP_ID, APP_NAME, APP_PLATFORM_RUNTIME, APP_PROJECTION_SOURCE, APP_RUNTIME_ORIGIN,
AppBuildIdentity, AppCoreRuntimeMetadata, AppHostRuntimeMetadata, AppRuntimeCapture,
diff --git a/crates/shared/core/src/paths.rs b/crates/shared/core/src/paths.rs
@@ -0,0 +1,291 @@
+use std::{
+ env,
+ error::Error,
+ fmt,
+ path::{Path, PathBuf},
+};
+
+pub const APP_RUNTIME_NAMESPACE_KIND: &str = "apps";
+pub const APP_RUNTIME_NAMESPACE_VALUE: &str = "app";
+pub const APP_RUNTIME_NAMESPACE: &str = "apps/app";
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum AppRuntimePlatform {
+ Linux,
+ Macos,
+ Windows,
+ Other(&'static str),
+}
+
+impl AppRuntimePlatform {
+ pub fn current() -> Self {
+ match env::consts::OS {
+ "linux" => Self::Linux,
+ "macos" => Self::Macos,
+ "windows" => Self::Windows,
+ other => Self::Other(other),
+ }
+ }
+
+ pub const fn label(self) -> &'static str {
+ match self {
+ Self::Linux => "linux",
+ Self::Macos => "macos",
+ Self::Windows => "windows",
+ Self::Other(other) => other,
+ }
+ }
+}
+
+#[derive(Clone, Debug, Default, Eq, PartialEq)]
+pub struct AppRuntimeHostEnvironment {
+ pub home_dir: Option<PathBuf>,
+ pub appdata_dir: Option<PathBuf>,
+ pub localappdata_dir: Option<PathBuf>,
+}
+
+impl AppRuntimeHostEnvironment {
+ pub fn from_current_process() -> Self {
+ Self {
+ home_dir: env::var_os("HOME").map(PathBuf::from),
+ appdata_dir: env::var_os("APPDATA").map(PathBuf::from),
+ localappdata_dir: env::var_os("LOCALAPPDATA").map(PathBuf::from),
+ }
+ }
+}
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct AppRuntimeRoots {
+ pub config: PathBuf,
+ pub data: PathBuf,
+ pub cache: PathBuf,
+ pub logs: PathBuf,
+ pub run: PathBuf,
+ pub secrets: PathBuf,
+}
+
+impl AppRuntimeRoots {
+ pub fn current_desktop() -> Result<Self, AppRuntimePathsError> {
+ Self::for_desktop(
+ AppRuntimePlatform::current(),
+ AppRuntimeHostEnvironment::from_current_process(),
+ )
+ }
+
+ pub fn for_desktop(
+ platform: AppRuntimePlatform,
+ host_environment: AppRuntimeHostEnvironment,
+ ) -> Result<Self, AppRuntimePathsError> {
+ let roots = match platform {
+ AppRuntimePlatform::Linux | AppRuntimePlatform::Macos => {
+ let home_dir = host_environment
+ .home_dir
+ .ok_or(AppRuntimePathsError::MissingHomeDir { platform })?;
+ Self::from_base_root(home_dir.join(".radroots"))
+ }
+ AppRuntimePlatform::Windows => {
+ let appdata_dir = host_environment
+ .appdata_dir
+ .ok_or(AppRuntimePathsError::MissingWindowsUserDirs)?;
+ let localappdata_dir = host_environment
+ .localappdata_dir
+ .ok_or(AppRuntimePathsError::MissingWindowsUserDirs)?;
+ let config_root = appdata_dir.join("Radroots");
+ let local_root = localappdata_dir.join("Radroots");
+ Self {
+ config: config_root.join("config"),
+ data: local_root.join("data"),
+ cache: local_root.join("cache"),
+ logs: local_root.join("logs"),
+ run: local_root.join("run"),
+ secrets: config_root.join("secrets"),
+ }
+ }
+ AppRuntimePlatform::Other(_) => {
+ return Err(AppRuntimePathsError::UnsupportedPlatform { platform });
+ }
+ };
+
+ Ok(roots.namespaced_app())
+ }
+
+ pub fn from_base_root(base_root: impl AsRef<Path>) -> Self {
+ let base_root = base_root.as_ref();
+ Self {
+ config: base_root.join("config"),
+ data: base_root.join("data"),
+ cache: base_root.join("cache"),
+ logs: base_root.join("logs"),
+ run: base_root.join("run"),
+ secrets: base_root.join("secrets"),
+ }
+ }
+
+ pub fn namespaced_app(&self) -> Self {
+ let namespace = PathBuf::from(APP_RUNTIME_NAMESPACE_KIND).join(APP_RUNTIME_NAMESPACE_VALUE);
+ Self {
+ config: self.config.join(&namespace),
+ data: self.data.join(&namespace),
+ cache: self.cache.join(&namespace),
+ logs: self.logs.join(&namespace),
+ run: self.run.join(&namespace),
+ secrets: self.secrets.join(namespace),
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum AppRuntimePathsError {
+ MissingHomeDir { platform: AppRuntimePlatform },
+ MissingWindowsUserDirs,
+ UnsupportedPlatform { platform: AppRuntimePlatform },
+}
+
+impl fmt::Display for AppRuntimePathsError {
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::MissingHomeDir { platform } => {
+ write!(
+ formatter,
+ "desktop runtime roots require HOME for {}",
+ platform.label()
+ )
+ }
+ Self::MissingWindowsUserDirs => formatter
+ .write_str("desktop runtime roots require APPDATA and LOCALAPPDATA on windows"),
+ Self::UnsupportedPlatform { platform } => write!(
+ formatter,
+ "desktop runtime roots are unsupported on {}",
+ platform.label()
+ ),
+ }
+ }
+}
+
+impl Error for AppRuntimePathsError {}
+
+#[cfg(test)]
+mod tests {
+ use std::path::PathBuf;
+
+ use super::{
+ APP_RUNTIME_NAMESPACE, AppRuntimeHostEnvironment, AppRuntimePathsError, AppRuntimePlatform,
+ AppRuntimeRoots,
+ };
+
+ #[test]
+ fn desktop_runtime_roots_use_canonical_macos_namespace() {
+ let roots = AppRuntimeRoots::for_desktop(
+ AppRuntimePlatform::Macos,
+ AppRuntimeHostEnvironment {
+ home_dir: Some(PathBuf::from("/Users/treesap")),
+ ..AppRuntimeHostEnvironment::default()
+ },
+ )
+ .expect("macos roots should resolve");
+
+ assert_eq!(
+ roots.data,
+ PathBuf::from("/Users/treesap/.radroots/data").join(APP_RUNTIME_NAMESPACE)
+ );
+ assert_eq!(
+ roots.logs,
+ PathBuf::from("/Users/treesap/.radroots/logs").join(APP_RUNTIME_NAMESPACE)
+ );
+ }
+
+ #[test]
+ fn desktop_runtime_roots_use_canonical_linux_namespace() {
+ let roots = AppRuntimeRoots::for_desktop(
+ AppRuntimePlatform::Linux,
+ AppRuntimeHostEnvironment {
+ home_dir: Some(PathBuf::from("/home/treesap")),
+ ..AppRuntimeHostEnvironment::default()
+ },
+ )
+ .expect("linux roots should resolve");
+
+ assert_eq!(
+ roots.data,
+ PathBuf::from("/home/treesap/.radroots/data").join(APP_RUNTIME_NAMESPACE)
+ );
+ assert_eq!(
+ roots.logs,
+ PathBuf::from("/home/treesap/.radroots/logs").join(APP_RUNTIME_NAMESPACE)
+ );
+ }
+
+ #[test]
+ fn desktop_runtime_roots_use_native_windows_roots() {
+ let roots = AppRuntimeRoots::for_desktop(
+ AppRuntimePlatform::Windows,
+ AppRuntimeHostEnvironment {
+ appdata_dir: Some(PathBuf::from(r"C:\Users\treesap\AppData\Roaming")),
+ localappdata_dir: Some(PathBuf::from(r"C:\Users\treesap\AppData\Local")),
+ ..AppRuntimeHostEnvironment::default()
+ },
+ )
+ .expect("windows roots should resolve");
+
+ assert_eq!(
+ roots.config,
+ PathBuf::from(r"C:\Users\treesap\AppData\Roaming")
+ .join("Radroots")
+ .join("config")
+ .join(APP_RUNTIME_NAMESPACE)
+ );
+ assert_eq!(
+ roots.data,
+ PathBuf::from(r"C:\Users\treesap\AppData\Local")
+ .join("Radroots")
+ .join("data")
+ .join(APP_RUNTIME_NAMESPACE)
+ );
+ }
+
+ #[test]
+ fn desktop_runtime_roots_require_home_dir_on_unix() {
+ let err = AppRuntimeRoots::for_desktop(
+ AppRuntimePlatform::Macos,
+ AppRuntimeHostEnvironment::default(),
+ )
+ .expect_err("missing home dir should fail");
+
+ assert_eq!(
+ err,
+ AppRuntimePathsError::MissingHomeDir {
+ platform: AppRuntimePlatform::Macos,
+ }
+ );
+ }
+
+ #[test]
+ fn desktop_runtime_roots_require_windows_user_dirs() {
+ let err = AppRuntimeRoots::for_desktop(
+ AppRuntimePlatform::Windows,
+ AppRuntimeHostEnvironment {
+ appdata_dir: Some(PathBuf::from(r"C:\Users\treesap\AppData\Roaming")),
+ ..AppRuntimeHostEnvironment::default()
+ },
+ )
+ .expect_err("missing local appdata should fail");
+
+ assert_eq!(err, AppRuntimePathsError::MissingWindowsUserDirs);
+ }
+
+ #[test]
+ fn desktop_runtime_roots_reject_unsupported_platforms() {
+ let err = AppRuntimeRoots::for_desktop(
+ AppRuntimePlatform::Other("freebsd"),
+ AppRuntimeHostEnvironment::default(),
+ )
+ .expect_err("unsupported platform should fail");
+
+ assert_eq!(
+ err,
+ AppRuntimePathsError::UnsupportedPlatform {
+ platform: AppRuntimePlatform::Other("freebsd"),
+ }
+ );
+ }
+}