From c8ef225f1f1aa1f904285394c5dce6b372135267 Mon Sep 17 00:00:00 2001 From: Kevin Boos Date: Fri, 28 Jun 2024 12:13:06 -0700 Subject: [PATCH 1/3] feat: allow Linux dependencies to be specified via a file path This makes it possible to dynamically generate the list of dependencies for Linux Debian and pacman package formats. Fixed a small documentation bug in `PacmanConfig`. --- bindings/packager/nodejs/schema.json | 52 +++++++++----- crates/packager/schema.json | 52 +++++++++----- crates/packager/src/config/mod.rs | 85 ++++++++++++++++++++--- crates/packager/src/package/deb/mod.rs | 15 ++-- crates/packager/src/package/pacman/mod.rs | 4 +- 5 files changed, 156 insertions(+), 52 deletions(-) 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..003fb906 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()); + .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,49 @@ 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 +451,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 +493,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()); + .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..641b9a08 100644 --- a/crates/packager/src/package/deb/mod.rs +++ b/crates/packager/src/package/deb/mod.rs @@ -280,14 +280,15 @@ fn generate_control_file( if let Some(homepage) = &config.homepage { writeln!(file, "Homepage: {}", homepage)?; } - let dependencies = config + if let Some(depends) = config .deb() - .cloned() - .and_then(|d| d.depends) - .unwrap_or_default(); - if !dependencies.is_empty() { - writeln!(file, "Depends: {}", dependencies.join(", "))?; - } + .and_then(|d| d.depends.as_ref()) + { + let dependencies = depends.to_list()?; + if !dependencies.is_empty() { + writeln!(file, "Depends: {}", dependencies.join(", "))?; + } + } writeln!( file, 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 From 596c5a418f26ab511447a23ba9687a056598227f Mon Sep 17 00:00:00 2001 From: Kevin Boos Date: Fri, 28 Jun 2024 12:24:35 -0700 Subject: [PATCH 2/3] Add .changes file --- .changes/pr254.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .changes/pr254.md 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. + From 76f7e715658d9aecb467f88c98f60ab52953d919 Mon Sep 17 00:00:00 2001 From: Kevin Boos Date: Mon, 1 Jul 2024 18:11:05 -0700 Subject: [PATCH 3/3] rustfmt fix --- crates/packager/src/config/mod.rs | 21 ++++++++++----------- crates/packager/src/package/deb/mod.rs | 7 ++----- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/crates/packager/src/config/mod.rs b/crates/packager/src/config/mod.rs index 003fb906..48451ebb 100644 --- a/crates/packager/src/config/mod.rs +++ b/crates/packager/src/config/mod.rs @@ -227,8 +227,9 @@ impl DebianConfig { I: IntoIterator, S: Into, { - self.depends - .replace(Dependencies::List(depends.into_iter().map(Into::into).collect())); + self.depends.replace(Dependencies::List( + depends.into_iter().map(Into::into).collect(), + )); self } @@ -236,10 +237,9 @@ impl DebianConfig { /// which must contain one dependency (a package name) per line. pub fn depends_path

(mut self, path: P) -> Self where - P: Into + P: Into, { - self.depends - .replace(Dependencies::Path(path.into())); + self.depends.replace(Dependencies::Path(path.into())); self } @@ -299,7 +299,6 @@ 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)] @@ -500,8 +499,9 @@ impl PacmanConfig { I: IntoIterator, S: Into, { - self.depends - .replace(Dependencies::List(depends.into_iter().map(Into::into).collect())); + self.depends.replace(Dependencies::List( + depends.into_iter().map(Into::into).collect(), + )); self } @@ -509,10 +509,9 @@ impl PacmanConfig { /// which must contain one dependency (a package name) per line. pub fn depends_path

(mut self, path: P) -> Self where - P: Into + P: Into, { - self.depends - .replace(Dependencies::Path(path.into())); + self.depends.replace(Dependencies::Path(path.into())); self } diff --git a/crates/packager/src/package/deb/mod.rs b/crates/packager/src/package/deb/mod.rs index 641b9a08..3e12e7c1 100644 --- a/crates/packager/src/package/deb/mod.rs +++ b/crates/packager/src/package/deb/mod.rs @@ -280,15 +280,12 @@ fn generate_control_file( if let Some(homepage) = &config.homepage { writeln!(file, "Homepage: {}", homepage)?; } - if let Some(depends) = config - .deb() - .and_then(|d| d.depends.as_ref()) - { + 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!( file,