From 9bdb953f1b48c8d69d86e9e42295cd36453c1648 Mon Sep 17 00:00:00 2001 From: Naman Garg <155433377+naman-crabnebula@users.noreply.github.com> Date: Wed, 24 Jan 2024 07:26:42 +0530 Subject: [PATCH] feat(pacman): Add support for Arch Linux Packaging (#137) * Init Pacman Support * Add Arch Linux packaging * Add PKGBUILD generator * cargo fmt * Update ts config * Remove `data` from path * Add changes file * Rectify Test * Update Docs * Rectify Test * Remove redundant comment Co-authored-by: Amr Bashir * clone single field instead of whole config * Add file option to pacman config * Update README files * Improve docs * Remove -bin suffix * add back pacman --------- --- .changes/add-pacman-support.md | 6 + Cargo.lock | 62 +-------- README.md | 1 + bindings/packager/nodejs/README.md | 1 + bindings/packager/nodejs/schema.json | 87 +++++++++++- bindings/packager/nodejs/src-ts/config.d.ts | 47 ++++++- crates/packager/Cargo.toml | 2 +- crates/packager/README.md | 1 + crates/packager/schema.json | 87 +++++++++++- crates/packager/src/config/builder.rs | 8 +- crates/packager/src/config/mod.rs | 116 +++++++++++++++- crates/packager/src/lib.rs | 6 +- crates/packager/src/package/deb/mod.rs | 7 +- crates/packager/src/package/mod.rs | 16 +++ crates/packager/src/package/pacman/mod.rs | 140 ++++++++++++++++++++ crates/utils/src/lib.rs | 26 +++- 16 files changed, 536 insertions(+), 77 deletions(-) create mode 100644 .changes/add-pacman-support.md create mode 100644 crates/packager/src/package/pacman/mod.rs diff --git a/.changes/add-pacman-support.md b/.changes/add-pacman-support.md new file mode 100644 index 00000000..d7636008 --- /dev/null +++ b/.changes/add-pacman-support.md @@ -0,0 +1,6 @@ +--- +"cargo-packager": minor +"@crabnebula/packager": minor +--- + +Add Arch Linux package manager, `pacman` support for cargo packager. diff --git a/Cargo.lock b/Cargo.lock index b7fe76b6..4bc0adde 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -173,12 +173,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -[[package]] -name = "adler32" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" - [[package]] name = "ahash" version = "0.8.7" @@ -993,12 +987,12 @@ dependencies = [ "clap", "dirs", "dunce", + "flate2", "glob", "handlebars", "heck", "hex", "image", - "libflate", "md5", "minisign", "native-tls", @@ -1511,15 +1505,6 @@ dependencies = [ "libc", ] -[[package]] -name = "core2" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" -dependencies = [ - "memchr", -] - [[package]] name = "countme" version = "3.0.1" @@ -1782,12 +1767,6 @@ dependencies = [ "syn 2.0.48", ] -[[package]] -name = "dary_heap" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7762d17f1241643615821a8455a0b2c3e803784b058693d990b11f2dce25a0ca" - [[package]] name = "data-url" version = "0.3.1" @@ -3296,15 +3275,6 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -[[package]] -name = "hashbrown" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" -dependencies = [ - "ahash", -] - [[package]] name = "hashbrown" version = "0.14.3" @@ -4122,30 +4092,6 @@ version = "0.2.152" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" -[[package]] -name = "libflate" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7d5654ae1795afc7ff76f4365c2c8791b0feb18e8996a96adad8ffd7c3b2bf" -dependencies = [ - "adler32", - "core2", - "crc32fast", - "dary_heap", - "libflate_lz77", -] - -[[package]] -name = "libflate_lz77" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be5f52fb8c451576ec6b79d3f4deb327398bc05bbdbd99021a6e77a4c855d524" -dependencies = [ - "core2", - "hashbrown 0.13.2", - "rle-decode-fast", -] - [[package]] name = "libloading" version = "0.7.4" @@ -5850,12 +5796,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "rle-decode-fast" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422" - [[package]] name = "rowan" version = "0.15.15" diff --git a/README.md b/README.md index c2921814..9d2dc97c 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ It also has a compatible updater through [cargo-packager-updater](./crates/updat - Linux - Debian package (.deb) - AppImage (.AppImage) + - Pacman (.tar.gz and PKGBUILD) - Windows - NSIS (.exe) - MSI using WiX Toolset (.msi) diff --git a/bindings/packager/nodejs/README.md b/bindings/packager/nodejs/README.md index 66d8c804..bcf0fbb0 100644 --- a/bindings/packager/nodejs/README.md +++ b/bindings/packager/nodejs/README.md @@ -14,6 +14,7 @@ It also comes with useful addons: - Linux - Debian package (.deb) - AppImage (.AppImage) + - Pacman (.tar.gz and PKGBUILD) - Windows - NSIS (.exe) - MSI using WiX Toolset (.msi) diff --git a/bindings/packager/nodejs/schema.json b/bindings/packager/nodejs/schema.json index 62b8645e..9482eda3 100644 --- a/bindings/packager/nodejs/schema.json +++ b/bindings/packager/nodejs/schema.json @@ -252,6 +252,17 @@ } ] }, + "pacman": { + "description": "Pacman configuration.", + "anyOf": [ + { + "$ref": "#/definitions/PacmanConfig" + }, + { + "type": "null" + } + ] + }, "wix": { "description": "WiX configuration.", "anyOf": [ @@ -434,6 +445,13 @@ "enum": [ "appimage" ] + }, + { + "description": "The Linux Pacman package (.tar.gz and PKGBUILD)", + "type": "string", + "enum": [ + "pacman" + ] } ] }, @@ -723,7 +741,7 @@ ] }, "section": { - "description": "Define the section in Debian Control file. See : https://www.debian.org/doc/debian-policy/ch-archive.html#s-subsections", + "description": "Define the section in Debian Control file. See : ", "type": [ "string", "null" @@ -796,6 +814,73 @@ }, "additionalProperties": false }, + "PacmanConfig": { + "description": "The Linux pacman configuration.", + "type": "object", + "properties": { + "files": { + "description": "List of custom files to add to the pacman package. Maps a dir/file to a dir/file inside the pacman package.", + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "string" + } + }, + "depends": { + "description": "List of softwares that must be installed for the app to build and run.\n\nSee : ", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "provides": { + "description": "Additional packages that are provided by this app.\n\nSee : ", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "conflicts": { + "description": "Packages that conflict or cause problems with the app. All these packages and packages providing this item will need to be removed\n\nSee : ", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "replaces": { + "description": "Only use if this app replaces some obsolete packages. For example, if you rename any package.\n\nSee : ", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "source": { + "description": "Source of the package to be stored at PKGBUILD. PKGBUILD is a bash script, so version can be referred as ${pkgver}", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, "WixConfig": { "description": "The wix format configuration", "type": "object", diff --git a/bindings/packager/nodejs/src-ts/config.d.ts b/bindings/packager/nodejs/src-ts/config.d.ts index 1fe64a30..f5d27edd 100644 --- a/bindings/packager/nodejs/src-ts/config.d.ts +++ b/bindings/packager/nodejs/src-ts/config.d.ts @@ -28,7 +28,7 @@ export type LogLevel = "error" | "warn" | "info" | "debug" | "trace"; /** * Types of supported packages by [`cargo-packager`](https://docs.rs/cargo-packager). */ -export type PackageFormat = "all" | "default" | "app" | "dmg" | "wix" | "nsis" | "deb" | "appimage"; +export type PackageFormat = "all" | "default" | "app" | "dmg" | "wix" | "nsis" | "deb" | "appimage" | "pacman"; /** * The possible app categories. Corresponds to `LSApplicationCategoryType` on macOS and the GNOME desktop categories on Debian. */ @@ -264,6 +264,10 @@ export interface Config { * AppImage configuration. */ appimage?: AppImageConfig | null; + /** + * Pacman configuration. + */ + pacman?: PacmanConfig | null; /** * WiX configuration. */ @@ -408,7 +412,7 @@ export interface DebianConfig { */ desktopTemplate?: string | null; /** - * Define the section in Debian Control file. See : https://www.debian.org/doc/debian-policy/ch-archive.html#s-subsections + * Define the section in Debian Control file. See : */ section?: string | null; /** @@ -447,6 +451,45 @@ export interface AppImageConfig { [k: string]: string; } | null; } +/** + * The Linux pacman configuration. + */ +export interface PacmanConfig { + /** + * List of custom files to add to the pacman package. Maps a dir/file to a dir/file inside the pacman package. + */ + files?: { + [k: string]: string; + } | null; + /** + * List of softwares that must be installed for the app to build and run. + * + * See : + */ + depends?: string[] | null; + /** + * Additional packages that are provided by this app. + * + * See : + */ + provides?: string[] | null; + /** + * Packages that conflict or cause problems with the app. All these packages and packages providing this item will need to be removed + * + * See : + */ + conflicts?: string[] | null; + /** + * Only use if this app replaces some obsolete packages. For example, if you rename any package. + * + * See : + */ + replaces?: string[] | null; + /** + * Source of the package to be stored at PKGBUILD. PKGBUILD is a bash script, so version can be referred as ${pkgver} + */ + source?: string[] | null; +} /** * The wix format configuration */ diff --git a/crates/packager/Cargo.toml b/crates/packager/Cargo.toml index 713b3dd9..55fec3d9 100644 --- a/crates/packager/Cargo.toml +++ b/crates/packager/Cargo.toml @@ -62,7 +62,7 @@ walkdir = "2" os_pipe = "1" minisign = "0.7" tar = { workspace = true } -libflate = "2.0" +flate2 = "1.0" strsim = "0.10" schemars = { workspace = true, optional = true } native-tls = { version = "0.2", optional = true } diff --git a/crates/packager/README.md b/crates/packager/README.md index e83154a9..15769418 100644 --- a/crates/packager/README.md +++ b/crates/packager/README.md @@ -16,6 +16,7 @@ It also comes with useful addons: - Linux - Debian package (.deb) - AppImage (.AppImage) + - Pacman (.tar.gz and PKGBUILD) - Windows - NSIS (.exe) - MSI using WiX Toolset (.msi) diff --git a/crates/packager/schema.json b/crates/packager/schema.json index 62b8645e..9482eda3 100644 --- a/crates/packager/schema.json +++ b/crates/packager/schema.json @@ -252,6 +252,17 @@ } ] }, + "pacman": { + "description": "Pacman configuration.", + "anyOf": [ + { + "$ref": "#/definitions/PacmanConfig" + }, + { + "type": "null" + } + ] + }, "wix": { "description": "WiX configuration.", "anyOf": [ @@ -434,6 +445,13 @@ "enum": [ "appimage" ] + }, + { + "description": "The Linux Pacman package (.tar.gz and PKGBUILD)", + "type": "string", + "enum": [ + "pacman" + ] } ] }, @@ -723,7 +741,7 @@ ] }, "section": { - "description": "Define the section in Debian Control file. See : https://www.debian.org/doc/debian-policy/ch-archive.html#s-subsections", + "description": "Define the section in Debian Control file. See : ", "type": [ "string", "null" @@ -796,6 +814,73 @@ }, "additionalProperties": false }, + "PacmanConfig": { + "description": "The Linux pacman configuration.", + "type": "object", + "properties": { + "files": { + "description": "List of custom files to add to the pacman package. Maps a dir/file to a dir/file inside the pacman package.", + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "string" + } + }, + "depends": { + "description": "List of softwares that must be installed for the app to build and run.\n\nSee : ", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "provides": { + "description": "Additional packages that are provided by this app.\n\nSee : ", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "conflicts": { + "description": "Packages that conflict or cause problems with the app. All these packages and packages providing this item will need to be removed\n\nSee : ", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "replaces": { + "description": "Only use if this app replaces some obsolete packages. For example, if you rename any package.\n\nSee : ", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "source": { + "description": "Source of the package to be stored at PKGBUILD. PKGBUILD is a bash script, so version can be referred as ${pkgver}", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, "WixConfig": { "description": "The wix format configuration", "type": "object", diff --git a/crates/packager/src/config/builder.rs b/crates/packager/src/config/builder.rs index 50d70a9d..ea56ae6c 100644 --- a/crates/packager/src/config/builder.rs +++ b/crates/packager/src/config/builder.rs @@ -4,7 +4,7 @@ use crate::{Config, PackageFormat}; use super::{ AppImageConfig, Binary, DebianConfig, FileAssociation, HookCommand, LogLevel, MacOsConfig, - NsisConfig, Resource, WindowsConfig, WixConfig, + NsisConfig, PacmanConfig, Resource, WindowsConfig, WixConfig, }; /// A builder type for [`Config`]. @@ -206,4 +206,10 @@ impl ConfigBuilder { self.0.appimage.replace(appimage); self } + + /// Set the [Pacman](Config::pacman) specific configuration. + pub fn pacman(mut self, pacman: PacmanConfig) -> Self { + self.0.pacman.replace(pacman); + self + } } diff --git a/crates/packager/src/config/mod.rs b/crates/packager/src/config/mod.rs index 52d08e59..178b3b9c 100644 --- a/crates/packager/src/config/mod.rs +++ b/crates/packager/src/config/mod.rs @@ -161,7 +161,7 @@ pub struct DebianConfig { /// ``` #[serde(alias = "desktop-template", alias = "desktop_template")] pub desktop_template: Option, - /// Define the section in Debian Control file. See : https://www.debian.org/doc/debian-policy/ch-archive.html#s-subsections + /// Define the section in Debian Control file. See : pub section: Option, /// Change the priority of the Debian Package. By default, it is set to `optional`. /// Recognized Priorities as of now are : `required`, `important`, `standard`, `optional`, `extra` @@ -213,7 +213,7 @@ impl DebianConfig { self } - /// Define the section in Debian Control file. See : https://www.debian.org/doc/debian-policy/ch-archive.html#s-subsections + /// Define the section in Debian Control file. See : pub fn section>(mut self, section: S) -> Self { self.section.replace(section.into()); self @@ -338,6 +338,111 @@ impl AppImageConfig { } } +/// The Linux pacman 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 PacmanConfig { + /// List of custom files to add to the pacman package. + /// Maps a dir/file to a dir/file inside the pacman package. + pub files: Option>, + /// List of softwares that must be installed for the app to build and run. + /// + /// See : + pub depends: Option>, + /// Additional packages that are provided by this app. + /// + /// See : + pub provides: Option>, + /// Packages that conflict or cause problems with the app. + /// All these packages and packages providing this item will need to be removed + /// + /// See : + pub conflicts: Option>, + /// Only use if this app replaces some obsolete packages. + /// For example, if you rename any package. + /// + /// See : + pub replaces: Option>, + /// Source of the package to be stored at PKGBUILD. + /// PKGBUILD is a bash script, so version can be referred as ${pkgver} + pub source: Option>, +} + +impl PacmanConfig { + /// Creates a new [`PacmanConfig`]. + pub fn new() -> Self { + Self::default() + } + /// Set the list of custom files to add to the pacman package. + /// Maps a dir/file to a dir/file inside the pacman package. + pub fn files(mut self, files: I) -> Self + where + I: IntoIterator, + S: Into, + T: Into, + { + self.files.replace( + files + .into_iter() + .map(|(k, v)| (k.into(), v.into())) + .collect(), + ); + self + } + /// Set the list of pacman dependencies. + pub fn depends(mut self, depends: I) -> Self + where + I: IntoIterator, + S: Into, + { + self.depends + .replace(depends.into_iter().map(Into::into).collect()); + self + } + /// Set the list of additional packages that are provided by this app. + pub fn provides(mut self, provides: I) -> Self + where + I: IntoIterator, + S: Into, + { + self.provides + .replace(provides.into_iter().map(Into::into).collect()); + self + } + /// Set the list of packages that conflict with the app. + pub fn conflicts(mut self, conflicts: I) -> Self + where + I: IntoIterator, + S: Into, + { + self.conflicts + .replace(conflicts.into_iter().map(Into::into).collect()); + self + } + /// Set the list of obsolete packages that are replaced by this package. + pub fn replaces(mut self, replaces: I) -> Self + where + I: IntoIterator, + S: Into, + { + self.replaces + .replace(replaces.into_iter().map(Into::into).collect()); + self + } + /// Set the list of sources where the package will be stored. + pub fn source(mut self, source: I) -> Self + where + I: IntoIterator, + S: Into, + { + self.source + .replace(source.into_iter().map(Into::into).collect()); + self + } +} + /// Position coordinates struct. #[derive(Default, Copy, Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] @@ -1396,6 +1501,8 @@ pub struct Config { pub deb: Option, /// AppImage configuration. pub appimage: Option, + /// Pacman configuration. + pub pacman: Option, /// WiX configuration. pub wix: Option, /// Nsis configuration. @@ -1440,6 +1547,11 @@ impl Config { self.appimage.as_ref() } + /// Returns the [pacman](Config::pacman) specific configuration. + pub fn pacman(&self) -> Option<&PacmanConfig> { + self.pacman.as_ref() + } + /// Returns the [dmg](Config::dmg) specific configuration. pub fn dmg(&self) -> Option<&DmgConfig> { self.dmg.as_ref() diff --git a/crates/packager/src/lib.rs b/crates/packager/src/lib.rs index 14533413..24d613d6 100644 --- a/crates/packager/src/lib.rs +++ b/crates/packager/src/lib.rs @@ -17,6 +17,7 @@ //! - Linux //! - Debian package (.deb) //! - AppImage (.AppImage) +//! - Pacman (.tar.gz and PKGBUILD) //! - Windows //! - NSIS (.exe) //! - MSI using WiX Toolset (.msi) @@ -91,6 +92,7 @@ pub mod sign; pub use config::{Config, PackageFormat}; pub use error::{Error, Result}; +use flate2::{write::GzEncoder, Compression}; pub use sign::SigningConfig; pub use package::{package, PackageOuput}; @@ -142,9 +144,9 @@ pub fn sign_outputs( let path = if path.is_dir() { let zip = path.with_additional_extension("tar.gz"); let dest_file = util::create_file(&zip)?; - let gzip_encoder = libflate::gzip::Encoder::new(dest_file)?; + let gzip_encoder = GzEncoder::new(dest_file, Compression::default()); let writer = util::create_tar_from_dir(path, gzip_encoder)?; - let mut dest_file = writer.finish().into_result()?; + let mut dest_file = writer.finish()?; dest_file.flush()?; package.paths.push(zip); diff --git a/crates/packager/src/package/deb/mod.rs b/crates/packager/src/package/deb/mod.rs index 618253d9..371c1766 100644 --- a/crates/packager/src/package/deb/mod.rs +++ b/crates/packager/src/package/deb/mod.rs @@ -13,6 +13,7 @@ use std::{ path::{Path, PathBuf}, }; +use flate2::{write::GzEncoder, Compression}; use handlebars::Handlebars; use heck::AsKebabCase; use image::{codecs::png::PngDecoder, ImageDecoder}; @@ -329,13 +330,13 @@ fn generate_md5sums(control_dir: &Path, data_dir: &Path) -> crate::Result<()> { /// Creates a `.tar.gz` file from the given directory (placing the new file /// within the given directory's parent directory), then deletes the original /// directory and returns the path to the new file. -fn tar_and_gzip_dir>(src_dir: P) -> crate::Result { +pub fn tar_and_gzip_dir>(src_dir: P) -> crate::Result { let src_dir = src_dir.as_ref(); let dest_path = src_dir.with_additional_extension("tar.gz"); let dest_file = util::create_file(&dest_path)?; - let gzip_encoder = libflate::gzip::Encoder::new(dest_file)?; + let gzip_encoder = GzEncoder::new(dest_file, Compression::default()); let gzip_encoder = create_tar_from_dir(src_dir, gzip_encoder)?; - let mut dest_file = gzip_encoder.finish().into_result()?; + let mut dest_file = gzip_encoder.finish()?; dest_file.flush()?; Ok(dest_path) } diff --git a/crates/packager/src/package/mod.rs b/crates/packager/src/package/mod.rs index 3551e15b..9e4b7b21 100644 --- a/crates/packager/src/package/mod.rs +++ b/crates/packager/src/package/mod.rs @@ -29,6 +29,14 @@ mod deb; #[cfg(target_os = "macos")] mod dmg; mod nsis; +#[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" +))] +mod pacman; #[cfg(windows)] mod wix; @@ -121,6 +129,14 @@ pub fn package(config: &Config) -> crate::Result> { target_os = "openbsd" ))] PackageFormat::AppImage => appimage::package(&ctx), + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + PackageFormat::Pacman => pacman::package(&ctx), _ => { tracing::warn!("ignoring {}", format.short_name()); diff --git a/crates/packager/src/package/pacman/mod.rs b/crates/packager/src/package/pacman/mod.rs new file mode 100644 index 00000000..4ccfa83a --- /dev/null +++ b/crates/packager/src/package/pacman/mod.rs @@ -0,0 +1,140 @@ +// Copyright 2024-2024 CrabNebula Ltd. +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use super::{ + deb::{copy_custom_files, generate_data, tar_and_gzip_dir}, + Context, +}; +use crate::{config::Config, util}; +use heck::AsKebabCase; +use sha2::{Digest, Sha512}; +use std::{ + fs::File, + io::{self, Write}, + path::{Path, PathBuf}, +}; + +#[tracing::instrument(level = "trace")] +pub(crate) fn package(ctx: &Context) -> crate::Result> { + let Context { + config, + intermediates_path, + .. + } = ctx; + + let arch = match config.target_arch()? { + "x86" => "i386", + "arm" => "armhf", + other => other, + }; + + let intermediates_path = intermediates_path.join("pacman"); + util::create_clean_dir(&intermediates_path)?; + + let package_base_name = format!("{}_{}_{}", config.main_binary_name()?, config.version, arch); + let package_name = format!("{}.tar.gz", package_base_name); + + let pkg_dir = intermediates_path.join(&package_base_name); + let pkg_path = config.out_dir().join(&package_name); + let pkgbuild_path = pkg_path.with_file_name("PKGBUILD"); + + tracing::info!("Packaging {} ({})", package_name, pkg_path.display()); + + tracing::debug!("Generating data"); + let _ = generate_data(config, &pkg_dir)?; + + tracing::debug!("Copying files specified in `pacman.files`"); + if let Some(files) = config.pacman().and_then(|d| d.files.as_ref()) { + copy_custom_files(files, &pkg_dir)?; + } + + // Apply tar/gzip to create the final package file. + tracing::debug!("Creating package archive using tar and gzip"); + let data_tar_gz_path = tar_and_gzip_dir(pkg_dir)?; + std::fs::copy(data_tar_gz_path, &pkg_path)?; + + tracing::info!("Generating PKGBUILD: {}", pkgbuild_path.display()); + generate_pkgbuild_file(config, arch, pkgbuild_path.as_path(), pkg_path.as_path())?; + + Ok(vec![pkg_path]) +} + +/// Generates the pacman PKGBUILD file. +/// For more information about the format of this file, see +/// +fn generate_pkgbuild_file( + config: &Config, + arch: &str, + dest_dir: &Path, + package_path: &Path, +) -> crate::Result<()> { + let pkgbuild_path = dest_dir.with_file_name("PKGBUILD"); + let mut file = util::create_file(&pkgbuild_path)?; + + if let Some(authors) = &config.authors { + writeln!(file, "# Maintainer: {}", authors.join(", "))?; + } + writeln!(file, "pkgname={}", AsKebabCase(&config.product_name))?; + writeln!(file, "pkgver={}", config.version)?; + writeln!(file, "pkgrel=1")?; + writeln!(file, "epoch=")?; + writeln!( + file, + "pkgdesc=\"{}\"", + config.description.as_deref().unwrap_or("") + )?; + writeln!(file, "arch=('{}')", arch)?; + + if let Some(homepage) = &config.homepage { + writeln!(file, "url=\"{}\"", homepage)?; + } + + let dependencies = config + .pacman() + .and_then(|d| d.depends.clone()) + .unwrap_or_default(); + writeln!(file, "depends=({})", dependencies.join(" \n"))?; + + let provides = config + .pacman() + .and_then(|d| d.provides.clone()) + .unwrap_or_default(); + writeln!(file, "provides=({})", provides.join(" \n"))?; + + let conflicts = config + .pacman() + .and_then(|d| d.conflicts.clone()) + .unwrap_or_default(); + writeln!(file, "conflicts=({})", conflicts.join(" \n"))?; + + let replaces = config + .pacman() + .and_then(|d| d.replaces.clone()) + .unwrap_or_default(); + writeln!(file, "replaces=({})", replaces.join(" \n"))?; + + writeln!(file, "options=(!lto)")?; + let source = config + .pacman() + .and_then(|d| d.source.clone()) + .unwrap_or_default(); + + if source.is_empty() { + writeln!(file, "source=({:?})", package_path.file_name().unwrap())?; + } else { + writeln!(file, "source=({})", source.join(" \n"))?; + } + + // Generate SHA512 sum of the package + let mut sha_file = File::open(package_path)?; + let mut sha512 = Sha512::new(); + io::copy(&mut sha_file, &mut sha512)?; + let sha_hash = sha512.finalize(); + + writeln!(file, "sha512sums=(\"{:x}\")", sha_hash)?; + writeln!(file, "package() {{\n\tcp -r ${{srcdir}}/* ${{pkgdir}}/\n}}")?; + + file.flush()?; + Ok(()) +} diff --git a/crates/utils/src/lib.rs b/crates/utils/src/lib.rs index 2584b3be..2977f470 100644 --- a/crates/utils/src/lib.rs +++ b/crates/utils/src/lib.rs @@ -43,6 +43,8 @@ pub enum PackageFormat { Deb, /// The Linux AppImage package (.AppImage). AppImage, + /// The Linux Pacman package (.tar.gz and PKGBUILD) + Pacman, } impl Display for PackageFormat { @@ -53,7 +55,7 @@ impl Display for PackageFormat { impl PackageFormat { /// Maps a short name to a [PackageFormat]. - /// Possible values are "deb", "ios", "wix", "app", "rpm", "appimage", "dmg". + /// Possible values are "deb", "pacman", "appimage", "dmg", "app", "wix", "nsis". pub fn from_short_name(name: &str) -> Option { // Other types we may eventually want to support: apk. match name { @@ -80,6 +82,7 @@ impl PackageFormat { PackageFormat::Nsis => "nsis", PackageFormat::Deb => "deb", PackageFormat::AppImage => "appimage", + PackageFormat::Pacman => "pacman", } } @@ -87,7 +90,7 @@ impl PackageFormat { /// /// - **macOS**: App, Dmg /// - **Windows**: Nsis, Wix - /// - **Linux**: Deb, AppImage + /// - **Linux**: Deb, AppImage, Pacman pub fn platform_all() -> &'static [PackageFormat] { &[ #[cfg(target_os = "macos")] @@ -114,6 +117,14 @@ impl PackageFormat { target_os = "openbsd" ))] PackageFormat::AppImage, + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + PackageFormat::Pacman, ] } @@ -121,7 +132,7 @@ impl PackageFormat { /// /// - **macOS**: App, Dmg /// - **Windows**: Nsis - /// - **Linux**: Deb, AppImage + /// - **Linux**: Deb, AppImage, Pacman pub fn platform_default() -> &'static [PackageFormat] { &[ #[cfg(target_os = "macos")] @@ -146,6 +157,14 @@ impl PackageFormat { target_os = "openbsd" ))] PackageFormat::AppImage, + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + PackageFormat::Pacman, ] } @@ -166,6 +185,7 @@ impl PackageFormat { PackageFormat::Nsis => 0, PackageFormat::Deb => 0, PackageFormat::AppImage => 0, + PackageFormat::Pacman => 0, PackageFormat::Dmg => 1, } }