diff --git a/.changes/pr254.md b/.changes/pr254.md new file mode 100644 index 00000000..f146995a --- /dev/null +++ b/.changes/pr254.md @@ -0,0 +1,9 @@ +--- +"cargo-packager": "minor" +"@crabnebula/packager": "minor" +--- + +Allow Linux dependencies to be specified via a file path instead of just a direct String. +This enables the list of dependencies to by dynamically generated for both Debian `.deb` packages and pacman packages, +which can relieve the app developer from the burden of manually maintaining a fixed list of dependencies. + diff --git a/bindings/packager/nodejs/schema.json b/bindings/packager/nodejs/schema.json index a7692a50..d2ef6477 100644 --- a/bindings/packager/nodejs/schema.json +++ b/bindings/packager/nodejs/schema.json @@ -771,18 +771,19 @@ "additionalProperties": false }, "DebianConfig": { - "description": "The Linux debian configuration.", + "description": "The Linux Debian configuration.", "type": "object", "properties": { "depends": { - "description": "The list of debian dependencies.", - "type": [ - "array", - "null" - ], - "items": { - "type": "string" - } + "description": "The list of Debian dependencies.", + "anyOf": [ + { + "$ref": "#/definitions/Dependencies" + }, + { + "type": "null" + } + ] }, "desktopTemplate": { "description": "Path to a custom desktop file Handlebars template.\n\nAvailable variables: `categories`, `comment` (optional), `exec`, `icon` and `name`.\n\nDefault file contents: ```text [Desktop Entry] Categories={{categories}} {{#if comment}} Comment={{comment}} {{/if}} Exec={{exec}} Icon={{icon}} Name={{name}} Terminal=false Type=Application {{#if mime_type}} MimeType={{mime_type}} {{/if}} ```", @@ -818,6 +819,22 @@ }, "additionalProperties": false }, + "Dependencies": { + "description": "A list of dependencies specified as either a list of Strings or as a path to a file that lists the dependencies, one per line.", + "anyOf": [ + { + "description": "The list of dependencies provided directly as a vector of Strings.", + "type": "array", + "items": { + "type": "string" + } + }, + { + "description": "A path to the file containing the list of dependences, formatted as one per line: ```text libc6 libxcursor1 libdbus-1-3 libasyncns0 ... ```", + "type": "string" + } + ] + }, "AppImageConfig": { "description": "The Linux AppImage configuration.", "type": "object", @@ -890,14 +907,15 @@ } }, "depends": { - "description": "List of softwares that must be installed for the app to build and run.\n\nSee : ", - "type": [ - "array", - "null" - ], - "items": { - "type": "string" - } + "description": "List of softwares that must be installed for the app to build and run.\n\nSee : ", + "anyOf": [ + { + "$ref": "#/definitions/Dependencies" + }, + { + "type": "null" + } + ] }, "provides": { "description": "Additional packages that are provided by this app.\n\nSee : ", diff --git a/crates/packager/schema.json b/crates/packager/schema.json index a7692a50..d2ef6477 100644 --- a/crates/packager/schema.json +++ b/crates/packager/schema.json @@ -771,18 +771,19 @@ "additionalProperties": false }, "DebianConfig": { - "description": "The Linux debian configuration.", + "description": "The Linux Debian configuration.", "type": "object", "properties": { "depends": { - "description": "The list of debian dependencies.", - "type": [ - "array", - "null" - ], - "items": { - "type": "string" - } + "description": "The list of Debian dependencies.", + "anyOf": [ + { + "$ref": "#/definitions/Dependencies" + }, + { + "type": "null" + } + ] }, "desktopTemplate": { "description": "Path to a custom desktop file Handlebars template.\n\nAvailable variables: `categories`, `comment` (optional), `exec`, `icon` and `name`.\n\nDefault file contents: ```text [Desktop Entry] Categories={{categories}} {{#if comment}} Comment={{comment}} {{/if}} Exec={{exec}} Icon={{icon}} Name={{name}} Terminal=false Type=Application {{#if mime_type}} MimeType={{mime_type}} {{/if}} ```", @@ -818,6 +819,22 @@ }, "additionalProperties": false }, + "Dependencies": { + "description": "A list of dependencies specified as either a list of Strings or as a path to a file that lists the dependencies, one per line.", + "anyOf": [ + { + "description": "The list of dependencies provided directly as a vector of Strings.", + "type": "array", + "items": { + "type": "string" + } + }, + { + "description": "A path to the file containing the list of dependences, formatted as one per line: ```text libc6 libxcursor1 libdbus-1-3 libasyncns0 ... ```", + "type": "string" + } + ] + }, "AppImageConfig": { "description": "The Linux AppImage configuration.", "type": "object", @@ -890,14 +907,15 @@ } }, "depends": { - "description": "List of softwares that must be installed for the app to build and run.\n\nSee : ", - "type": [ - "array", - "null" - ], - "items": { - "type": "string" - } + "description": "List of softwares that must be installed for the app to build and run.\n\nSee : ", + "anyOf": [ + { + "$ref": "#/definitions/Dependencies" + }, + { + "type": "null" + } + ] }, "provides": { "description": "Additional packages that are provided by this app.\n\nSee : ", diff --git a/crates/packager/src/config/mod.rs b/crates/packager/src/config/mod.rs index 1a022b2c..48451ebb 100644 --- a/crates/packager/src/config/mod.rs +++ b/crates/packager/src/config/mod.rs @@ -175,14 +175,14 @@ impl DeepLinkProtocol { } } -/// The Linux debian configuration. +/// The Linux Debian configuration. #[derive(Clone, Debug, Default, Deserialize, Serialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[non_exhaustive] pub struct DebianConfig { - /// The list of debian dependencies. - pub depends: Option>, + /// The list of Debian dependencies. + pub depends: Option, /// Path to a custom desktop file Handlebars template. /// /// Available variables: `categories`, `comment` (optional), `exec`, `icon` and `name`. @@ -221,14 +221,25 @@ impl DebianConfig { Self::default() } - /// Set the list of debian dependencies. + /// Set the list of Debian dependencies directly using an iterator of strings. pub fn depends(mut self, depends: I) -> Self where I: IntoIterator, S: Into, { - self.depends - .replace(depends.into_iter().map(Into::into).collect()); + self.depends.replace(Dependencies::List( + depends.into_iter().map(Into::into).collect(), + )); + self + } + + /// Set the list of Debian dependencies indirectly via a path to a file, + /// which must contain one dependency (a package name) per line. + pub fn depends_path

(mut self, path: P) -> Self + where + P: Into, + { + self.depends.replace(Dependencies::Path(path.into())); self } @@ -288,6 +299,48 @@ impl DebianConfig { } } +/// A list of dependencies specified as either a list of Strings +/// or as a path to a file that lists the dependencies, one per line. +#[derive(Debug, Clone, Deserialize, Serialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(untagged)] +#[non_exhaustive] +pub enum Dependencies { + /// The list of dependencies provided directly as a vector of Strings. + List(Vec), + /// A path to the file containing the list of dependences, formatted as one per line: + /// ```text + /// libc6 + /// libxcursor1 + /// libdbus-1-3 + /// libasyncns0 + /// ... + /// ``` + Path(PathBuf), +} +impl Dependencies { + /// Returns the dependencies as a list of Strings. + pub fn to_list(&self) -> crate::Result> { + match self { + Self::List(v) => Ok(v.clone()), + Self::Path(path) => { + let trimmed_lines = std::fs::read_to_string(path)? + .lines() + .filter_map(|line| { + let trimmed = line.trim(); + if !trimmed.is_empty() { + Some(trimmed.to_owned()) + } else { + None + } + }) + .collect(); + Ok(trimmed_lines) + } + } + } +} + /// The Linux AppImage configuration. #[derive(Clone, Debug, Default, Deserialize, Serialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] @@ -397,8 +450,8 @@ pub struct PacmanConfig { pub files: Option>, /// List of softwares that must be installed for the app to build and run. /// - /// See : - pub depends: Option>, + /// See : + pub depends: Option, /// Additional packages that are provided by this app. /// /// See : @@ -439,16 +492,29 @@ impl PacmanConfig { ); self } - /// Set the list of pacman dependencies. + + /// Set the list of pacman dependencies directly using an iterator of strings. pub fn depends(mut self, depends: I) -> Self where I: IntoIterator, S: Into, { - self.depends - .replace(depends.into_iter().map(Into::into).collect()); + self.depends.replace(Dependencies::List( + depends.into_iter().map(Into::into).collect(), + )); self } + + /// Set the list of pacman dependencies indirectly via a path to a file, + /// which must contain one dependency (a package name) per line. + pub fn depends_path

(mut self, path: P) -> Self + where + P: Into, + { + self.depends.replace(Dependencies::Path(path.into())); + self + } + /// Set the list of additional packages that are provided by this app. pub fn provides(mut self, provides: I) -> Self where diff --git a/crates/packager/src/package/deb/mod.rs b/crates/packager/src/package/deb/mod.rs index 8390cf43..3e12e7c1 100644 --- a/crates/packager/src/package/deb/mod.rs +++ b/crates/packager/src/package/deb/mod.rs @@ -280,13 +280,11 @@ fn generate_control_file( if let Some(homepage) = &config.homepage { writeln!(file, "Homepage: {}", homepage)?; } - let dependencies = config - .deb() - .cloned() - .and_then(|d| d.depends) - .unwrap_or_default(); - if !dependencies.is_empty() { - writeln!(file, "Depends: {}", dependencies.join(", "))?; + if let Some(depends) = config.deb().and_then(|d| d.depends.as_ref()) { + let dependencies = depends.to_list()?; + if !dependencies.is_empty() { + writeln!(file, "Depends: {}", dependencies.join(", "))?; + } } writeln!( diff --git a/crates/packager/src/package/pacman/mod.rs b/crates/packager/src/package/pacman/mod.rs index 4ccfa83a..52117e58 100644 --- a/crates/packager/src/package/pacman/mod.rs +++ b/crates/packager/src/package/pacman/mod.rs @@ -92,8 +92,8 @@ fn generate_pkgbuild_file( let dependencies = config .pacman() - .and_then(|d| d.depends.clone()) - .unwrap_or_default(); + .and_then(|d| d.depends.as_ref()) + .map_or_else(|| Ok(Vec::new()), |d| d.to_list())?; writeln!(file, "depends=({})", dependencies.join(" \n"))?; let provides = config