diff --git a/.changes/dmg-config.md b/.changes/dmg-config.md new file mode 100644 index 00000000..90ca4e1f --- /dev/null +++ b/.changes/dmg-config.md @@ -0,0 +1,5 @@ +--- +"cargo-packager": "patch" +--- + +Add `config.dmg` to configure the DMG on macOS. diff --git a/Cargo.toml b/Cargo.toml index be804786..4a046afe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ base64 = "0.21" tracing = "0.1" time = "0.3" tar = "0.4" -napi = { version = "2.14.1", default-features = false } +napi = { version = "2.14", default-features = false } napi-derive = "2.14" napi-build = "2.1.0" diff --git a/crates/packager/schema.json b/crates/packager/schema.json index aa83e3ca..39d4c0ce 100644 --- a/crates/packager/schema.json +++ b/crates/packager/schema.json @@ -273,6 +273,17 @@ "type": "null" } ] + }, + "dmg": { + "description": "Dmg configuration.", + "anyOf": [ + { + "$ref": "#/definitions/DmgConfig" + }, + { + "type": "null" + } + ] } }, "additionalProperties": false, @@ -1082,6 +1093,110 @@ ] } ] + }, + "DmgConfig": { + "description": "The Apple Disk Image (.dmg) configuration.", + "type": "object", + "properties": { + "background": { + "description": "Image to use as the background in dmg file. Accepted formats: `png`/`jpg`/`gif`.", + "type": [ + "string", + "null" + ] + }, + "windowPosition": { + "description": "Position of volume window on screen.", + "anyOf": [ + { + "$ref": "#/definitions/Position" + }, + { + "type": "null" + } + ] + }, + "windowSize": { + "description": "Size of volume window.", + "anyOf": [ + { + "$ref": "#/definitions/Size" + }, + { + "type": "null" + } + ] + }, + "appPosition": { + "description": "Position of application file on window.", + "anyOf": [ + { + "$ref": "#/definitions/Position" + }, + { + "type": "null" + } + ] + }, + "appFolderPosition": { + "description": "Position of application folder on window.", + "anyOf": [ + { + "$ref": "#/definitions/Position" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "Position": { + "description": "Position coordinates struct.", + "type": "object", + "required": [ + "x", + "y" + ], + "properties": { + "x": { + "description": "X coordinate.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "y": { + "description": "Y coordinate.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "Size": { + "description": "Size struct.", + "type": "object", + "required": [ + "height", + "width" + ], + "properties": { + "width": { + "description": "Width.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "height": { + "description": "Height.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false } } } \ No newline at end of file diff --git a/crates/packager/src/config/mod.rs b/crates/packager/src/config/mod.rs index 343231fd..29b7e0b8 100644 --- a/crates/packager/src/config/mod.rs +++ b/crates/packager/src/config/mod.rs @@ -470,10 +470,94 @@ impl AppImageConfig { } } +/// Position coordinates struct. +#[derive(Default, Copy, Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct Position { + /// X coordinate. + pub x: u32, + /// Y coordinate. + pub y: u32, +} + +/// Size struct. +#[derive(Default, Copy, Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct Size { + /// Width. + pub width: u32, + /// Height. + pub height: u32, +} + +/// The Apple Disk Image (.dmg) 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 DmgConfig { + /// Image to use as the background in dmg file. Accepted formats: `png`/`jpg`/`gif`. + pub background: Option, + /// Position of volume window on screen. + pub window_position: Option, + /// Size of volume window. + #[serde(alias = "window-size", alias = "window_size")] + pub window_size: Option, + /// Position of application file on window. + #[serde(alias = "app-position", alias = "app_position")] + pub app_position: Option, + /// Position of application folder on window. + #[serde( + alias = "application-folder-position", + alias = "application_folder_position" + )] + pub app_folder_position: Option, +} + +impl DmgConfig { + /// Creates a new [`DmgConfig`]. + pub fn new() -> Self { + Self::default() + } + + /// Set an image to use as the background in dmg file. Accepted formats: `png`/`jpg`/`gif`. + pub fn background>(mut self, path: P) -> Self { + self.background.replace(path.into()); + self + } + + /// Set the poosition of volume window on screen. + pub fn window_position(mut self, position: Position) -> Self { + self.window_position.replace(position); + self + } + + /// Set the size of volume window. + pub fn window_size(mut self, size: Size) -> Self { + self.window_size.replace(size); + self + } + + /// Set the poosition of app file on window. + pub fn app_position(mut self, position: Position) -> Self { + self.app_position.replace(position); + self + } + + /// Set the position of application folder on window. + pub fn app_folder_position(mut self, position: Position) -> Self { + self.app_folder_position.replace(position); + self + } +} + /// The macOS 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 MacOsConfig { /// MacOS frameworks that need to be packaged with the app. /// @@ -1437,6 +1521,8 @@ pub struct Config { pub wix: Option, /// Nsis configuration. pub nsis: Option, + /// Dmg configuration. + pub dmg: Option, } impl Config { @@ -1475,6 +1561,11 @@ impl Config { self.appimage.as_ref() } + /// Returns the [dmg](Config::dmg) specific configuration. + pub fn dmg(&self) -> Option<&DmgConfig> { + self.dmg.as_ref() + } + /// Returns the target triple of this config, if not set, fallsback to the current OS target triple. pub fn target_triple(&self) -> String { self.target_triple.clone().unwrap_or_else(|| { diff --git a/crates/packager/src/package/dmg/mod.rs b/crates/packager/src/package/dmg/mod.rs index e98b4008..4e56734d 100644 --- a/crates/packager/src/package/dmg/mod.rs +++ b/crates/packager/src/package/dmg/mod.rs @@ -86,57 +86,106 @@ pub(crate) fn package(ctx: &Context) -> crate::Result> { include_str!("eula-resources-template.xml"), )?; - let mut args = vec![ + let dmg = config.dmg(); + + let mut bundle_dmg_cmd = Command::new(&create_dmg_script_path); + + let app_x = dmg + .and_then(|d| d.app_position) + .map(|p| p.x) + .unwrap_or(180) + .to_string(); + let app_y = dmg + .and_then(|d| d.app_position) + .map(|p| p.y) + .unwrap_or(170) + .to_string(); + let app_folder_x = dmg + .and_then(|d| d.app_folder_position) + .map(|p| p.x) + .unwrap_or(480) + .to_string(); + let app_folder_y = dmg + .and_then(|d| d.app_folder_position) + .map(|p| p.y) + .unwrap_or(170) + .to_string(); + let window_width = dmg + .and_then(|d| d.window_size) + .map(|s| s.width) + .unwrap_or(600) + .to_string(); + let window_height = dmg + .and_then(|d| d.window_size) + .map(|s| s.height) + .unwrap_or(400) + .to_string(); + + bundle_dmg_cmd.args([ "--volname", &config.product_name, "--icon", &app_bundle_file_name, - "180", - "170", + &app_x, + &app_y, "--app-drop-link", - "480", - "170", + &app_folder_x, + &app_folder_y, "--window-size", - "660", - "400", + &window_width, + &window_height, "--hide-extension", &app_bundle_file_name, - ]; + ]); + + let window_position = dmg + .and_then(|d| d.window_position) + .map(|p| (p.x.to_string(), p.y.to_string())); + if let Some((x, y)) = window_position { + bundle_dmg_cmd.arg("--window-pos"); + bundle_dmg_cmd.arg(&x); + bundle_dmg_cmd.arg(&y); + } + + let background_path = match &dmg.and_then(|d| d.background.as_ref()) { + Some(p) => Some(std::env::current_dir()?.join(p)), + None => None, + }; + + if let Some(background_path) = &background_path { + bundle_dmg_cmd.arg("--background"); + bundle_dmg_cmd.arg(background_path); + } tracing::debug!("Creating icns file"); - let icns_icon_path = util::create_icns_file(&intermediates_path, config)? - .map(|path| path.to_string_lossy().to_string()); + let icns_icon_path = util::create_icns_file(&intermediates_path, config)?; if let Some(icon) = &icns_icon_path { - args.push("--volicon"); - args.push(icon); + bundle_dmg_cmd.arg("--volicon"); + bundle_dmg_cmd.arg(icon); } - let license_file = config.license_file.as_ref().map(|l| { - std::env::current_dir() - .unwrap() - .join(l) - .to_string_lossy() - .to_string() - }); + let license_file = match config.license_file.as_ref() { + Some(l) => Some(std::env::current_dir()?.join(l)), + None => None, + }; if let Some(license_path) = &license_file { - args.push("--eula"); - args.push(license_path.as_str()); + bundle_dmg_cmd.arg("--eula"); + bundle_dmg_cmd.arg(license_path); } // Issue #592 - Building MacOS dmg files on CI // https://github.com/tauri-apps/tauri/issues/592 if let Some(value) = std::env::var_os("CI") { if value == "true" { - args.push("--skip-jenkins"); + bundle_dmg_cmd.arg("--skip-jenkins"); } } tracing::info!("Running create-dmg"); // execute the bundle script - Command::new(&create_dmg_script_path) + bundle_dmg_cmd .current_dir(&out_dir) - .args(args) .args(vec![dmg_name.as_str(), app_bundle_file_name.as_str()]) .output_ok() .map_err(crate::Error::CreateDmgFailed)?; diff --git a/crates/packager/src/package/wix/mod.rs b/crates/packager/src/package/wix/mod.rs index 583b2da2..c331d057 100644 --- a/crates/packager/src/package/wix/mod.rs +++ b/crates/packager/src/package/wix/mod.rs @@ -476,10 +476,8 @@ fn build_wix_app_installer(ctx: &Context, wix_path: &Path) -> crate::Result