Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ ctor = "0.4.0"
derive_more = "0.99.17"
dirs = "6"
env_logger = "0.11"
# Login-shell PATH repair for GUI-launched apps (EnvironmentPolicy::LoginShell).
# Git-only crate (no crates.io release); pinned to the same rev agent-term uses.
fix-path-env = { git = "https://github.com/tauri-apps/fix-path-env-rs", rev = "c4c45d503ea115a839aae718d02f79e7c7f0f673" }
futures = "0.3"
image = "0.25.1"
inventory = "0.3.19"
Expand Down
45 changes: 40 additions & 5 deletions crates/app-manifest/src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,16 @@ fn generate_source(
cfbundle_short_version: &str,
msix_version: &str,
) -> String {
// Types are named UNQUALIFIED here. `include_identity!` includes this file
// inside a module that brings the schema types into scope via `$crate`, so
// the identifiers resolve to `gpui-component-manifest` even when the macro is
// reached through a re-export — letting apps depend on it only as a
// build-dependency. Do not reintroduce `::gpui_component_manifest::` paths:
// an absolute path would force every consumer back onto a direct runtime dep.
let mut source = String::new();
writeln!(
source,
"pub static APP_IDENTITY: ::gpui_component_manifest::schema::IdentityRef = ::gpui_component_manifest::schema::IdentityRef {{"
"pub static APP_IDENTITY: IdentityRef = IdentityRef {{"
)
.expect("writing generated source to a String cannot fail");
write_field(&mut source, "app_id", &format!("{:?}", identity.app_id));
Expand Down Expand Up @@ -140,7 +146,7 @@ fn macos_source(value: Option<&MacosIdentity>) -> String {
|value| {
let usage_strings: Vec<_> = value.usage_strings.iter().collect();
format!(
"Some(::gpui_component_manifest::schema::MacosIdentityRef {{ category: {:?}, entitlements: &{:?}, usage_strings: &{:?} }})",
"Some(MacosIdentityRef {{ category: {:?}, entitlements: &{:?}, usage_strings: &{:?} }})",
value.category, value.entitlements, usage_strings
)
},
Expand All @@ -152,7 +158,7 @@ fn linux_source(value: Option<&LinuxIdentity>) -> String {
|| "None".to_owned(),
|value| {
format!(
"Some(::gpui_component_manifest::schema::LinuxIdentityRef {{ categories: &{:?}, desktop_keywords: &{:?} }})",
"Some(LinuxIdentityRef {{ categories: &{:?}, desktop_keywords: &{:?} }})",
value.categories, value.desktop_keywords
)
},
Expand All @@ -164,7 +170,7 @@ fn windows_source(value: Option<&WindowsIdentity>) -> String {
|| "None".to_owned(),
|value| {
format!(
"Some(::gpui_component_manifest::schema::WindowsIdentityRef {{ publisher: {:?} }})",
"Some(WindowsIdentityRef {{ publisher: {:?} }})",
value.publisher
)
},
Expand All @@ -176,7 +182,7 @@ fn min_os_source(value: Option<&MinOsVersions>) -> String {
|| "None".to_owned(),
|value| {
format!(
"Some(::gpui_component_manifest::schema::MinOsVersionsRef {{ macos: {:?}, windows: {:?}, linux: {:?} }})",
"Some(MinOsVersionsRef {{ macos: {:?}, windows: {:?}, linux: {:?} }})",
value.macos, value.windows, value.linux
)
},
Expand Down Expand Up @@ -208,4 +214,33 @@ mod tests {
assert!(source.contains(r#"display_name: "Quoted \"Name\"\n""#));
assert!(source.contains(r#"binary_name: Some("bin\\name")"#));
}

#[test]
fn generated_source_names_schema_types_unqualified() {
// `include_identity!` resolves these via `$crate`, so the generated file
// must NOT hardcode `::gpui_component_manifest::` — that would force a
// direct runtime dependency on every consumer (see the macro's rustdoc).
let identity = AppIdentity {
app_id: "com.example.macro".to_owned(),
display_name: "Macro".to_owned(),
data_namespace: "macro".to_owned(),
binary_name: None,
org: None,
publisher: None,
url_schemes: Vec::new(),
categories: Vec::new(),
macos: Some(MacosIdentity::default()),
linux: None,
windows: None,
legacy_ids: Vec::new(),
min_os: None,
};
let source = generate_source(&identity, "1.0.0", "1.0.0", "1.0.0.0");
assert!(source.contains("APP_IDENTITY: IdentityRef = IdentityRef {"));
assert!(source.contains("Some(MacosIdentityRef {"));
assert!(
!source.contains("::gpui_component_manifest"),
"generated source must not hardcode an absolute manifest path:\n{source}"
);
}
}
34 changes: 30 additions & 4 deletions crates/app-manifest/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,41 @@ pub mod versions;

pub use error::ManifestError;

/// Includes identity source generated for the consuming application.
/// Includes identity source generated for the consuming application, defining a
/// `pub static APP_IDENTITY` at the call site.
///
/// Call this from the consuming application's own crate after its `build.rs` has
/// called [`build::emit_identity`]. Do not call it from a library crate: `OUT_DIR`
/// must belong to the application. The planned `gpui-component-app` crate will
/// re-export this macro for ergonomic access.
/// must belong to the application. `gpui-component-app` re-exports this macro for
/// ergonomic access.
///
/// # No direct manifest dependency required
///
/// The generated source names its schema types unqualified (`IdentityRef`, …).
/// This macro includes that source inside a private module that imports those
/// types with `use $crate::schema::*`. `$crate` resolves to the crate that
/// *defined* the macro — `gpui-component-manifest` — by def-id rather than the
/// caller's extern prelude, so it works even when the macro is reached through a
/// re-export (e.g. `gpui_component_app::include_identity!`). Consequently an app
/// that calls this via `gpui-component-app` needs `gpui-component-manifest` only
/// as a `[build-dependencies]` entry (for [`build::emit_identity`]) — never as a
/// runtime dependency.
#[macro_export]
macro_rules! include_identity {
() => {
include!(concat!(env!("OUT_DIR"), "/gpui_app_identity.rs"));
// The private module scopes the `use` so the generated file's unqualified
// type names resolve via `$crate`; `pub use` then lifts `APP_IDENTITY`
// back to the call site. `unused_imports` is allowed because an identity
// without a given platform section never references that section's type.
#[doc(hidden)]
mod __gpui_app_identity {
#[allow(unused_imports)]
use $crate::schema::{
IdentityRef, LinuxIdentityRef, MacosIdentityRef, MinOsVersionsRef,
WindowsIdentityRef,
};
include!(concat!(env!("OUT_DIR"), "/gpui_app_identity.rs"));
}
pub use __gpui_app_identity::APP_IDENTITY;
};
}
8 changes: 7 additions & 1 deletion crates/app-manifest/tests/fixtures/downstream-app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@ version = "0.4.2"
edition = "2024"
publish = false

# No runtime dependency on gpui-component-manifest. This fixture proves that an
# app reaching `include_identity!` through a re-export (here the local
# `manifest-reexport` shim, mirroring gpui-component-app) needs the manifest
# crate only as a BUILD dependency: macro `$crate` resolves the schema types to
# gpui-component-manifest transitively via the shim.
[dependencies]
gpui-component-manifest = { path = "../../.." }
manifest-reexport = { path = "manifest-reexport" }

[build-dependencies]
gpui-component-manifest = { path = "../../.." }
Expand All @@ -17,3 +22,4 @@ url_schemes = ["fixtureapp"]
categories = ["Development"]

[workspace]
members = ["manifest-reexport"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "manifest-reexport"
version = "0.0.0"
edition = "2024"
publish = false

# Mirrors how gpui-component-app re-exports the identity macro. The downstream
# fixture binary reaches `include_identity!` through this shim, proving that a
# consumer needs the manifest crate only as a build dependency.
[dependencies]
gpui-component-manifest = { path = "../../../.." }
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
//! Minimal re-export shim mirroring `gpui-component-app`'s re-export of the
//! identity macro. Its only job is to prove that reaching `include_identity!`
//! through a re-export needs no direct `gpui-component-manifest` dependency in
//! the calling crate: macro `$crate` still resolves to the defining manifest
//! crate, transitively available via this shim.
pub use gpui_component_manifest::include_identity;
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
gpui_component_manifest::include_identity!();
// Reaches `include_identity!` through the `manifest-reexport` shim rather than
// naming `gpui_component_manifest` directly — proving the re-export path compiles
// with the manifest crate present only as a build dependency (see Cargo.toml).
manifest_reexport::include_identity!();

fn main() {
assert_eq!(APP_IDENTITY.app_id, "com.example.downstreamfixture");
Expand Down
6 changes: 6 additions & 0 deletions crates/app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ serde.workspace = true
thiserror.workspace = true
toml.workspace = true

# EnvironmentPolicy::LoginShell's PATH repair. macOS/Linux only: Windows GUI
# processes inherit PATH from the launching context, so the policy is a
# documented no-op there and the crate is not pulled in.
[target.'cfg(any(target_os = "macos", target_os = "linux"))'.dependencies]
fix-path-env.workspace = true

[dev-dependencies]
serde_json.workspace = true
tempfile.workspace = true
Expand Down
Loading
Loading