diff --git a/Cargo.lock b/Cargo.lock index 018585a0..9bba00b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -413,6 +413,7 @@ dependencies = [ "sha2", "tar", "thiserror", + "toml", "ureq", "uuid", "walkdir", diff --git a/Cargo.toml b/Cargo.toml index f9dc1409..81b48ea3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["crates/*", "examples/*"] +members = ["crates/*", "examples/tauri", "examples/dioxus"] resolver = "2" [workspace.dependencies] @@ -10,3 +10,6 @@ dunce = "1" schemars = { version = "0.8", features = ["url", "preserve_order", "derive"] } cargo-packager-config = { path = "crates/config", version = "0.0.0" } clap = { version = "4.4", features = ["derive"] } +log = "0.4" +dirs = "5.0" +semver = "1" diff --git a/crates/config/schema.json b/crates/config/schema.json new file mode 100644 index 00000000..e06ec112 --- /dev/null +++ b/crates/config/schema.json @@ -0,0 +1,941 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Config", + "description": "The packaging config.", + "type": "object", + "properties": { + "$schema": { + "description": "The JSON schema for the config.", + "type": [ + "string", + "null" + ] + }, + "beforePackagingCommand": { + "description": "Specify a command to run before starting to package an application.\n\nThis runs only once.", + "default": null, + "anyOf": [ + { + "$ref": "#/definitions/HookCommand" + }, + { + "type": "null" + } + ] + }, + "beforeEachPackageCommand": { + "description": "Specify a command to run before packaging each format for an application.\n\nThis will run multiple times depending on the formats specifed.", + "default": null, + "anyOf": [ + { + "$ref": "#/definitions/HookCommand" + }, + { + "type": "null" + } + ] + }, + "logLevel": { + "description": "The log level.", + "anyOf": [ + { + "$ref": "#/definitions/LogLevel" + }, + { + "type": "null" + } + ] + }, + "formats": { + "description": "The package types we're creating.\n\nif not present, we'll use the PackageType list for the target OS.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/PackageFormat" + } + }, + "outDir": { + "description": "the directory where the `binaries` exist and where the packages will be placed.", + "default": "", + "type": "string" + }, + "targetTriple": { + "description": "The target triple. Defaults to the current OS target triple.", + "type": [ + "string", + "null" + ] + }, + "productName": { + "description": "the package's product name, for example \"My Awesome App\".", + "default": "", + "type": "string" + }, + "version": { + "description": "the package's version.", + "default": "", + "type": "string" + }, + "description": { + "description": "the package's description.", + "type": [ + "string", + "null" + ] + }, + "longDescription": { + "description": "the app's long description.", + "type": [ + "string", + "null" + ] + }, + "homepage": { + "description": "the package's homepage.", + "type": [ + "string", + "null" + ] + }, + "authors": { + "description": "the package's authors.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "identifier": { + "description": "the app's identifier.", + "type": [ + "string", + "null" + ] + }, + "publisher": { + "description": "The app's publisher. Defaults to the second element in the identifier string. Currently maps to the Manufacturer property of the Windows Installer.", + "type": [ + "string", + "null" + ] + }, + "licenseFile": { + "description": "A path to the license file.", + "type": [ + "string", + "null" + ] + }, + "copyright": { + "description": "the app's copyright.", + "type": [ + "string", + "null" + ] + }, + "category": { + "description": "the app's category.", + "anyOf": [ + { + "$ref": "#/definitions/AppCategory" + }, + { + "type": "null" + } + ] + }, + "icons": { + "description": "the app's icon list.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "binaries": { + "description": "the binaries to package.", + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/Binary" + } + }, + "fileAssociations": { + "description": "the file associations", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/FileAssociation" + } + }, + "resources": { + "description": "The app's resources to package.\n\nCan be either be a list of files/folder or a map of src file/folder and target file/folder.\n\nsupports glob patterns.", + "anyOf": [ + { + "$ref": "#/definitions/Resources" + }, + { + "type": "null" + } + ] + }, + "externalBinaries": { + "description": "External binaries to add to the package.\n\nNote that each binary name should have the target platform's target triple appended, as well as `.exe` for Windows. For example, if you're packaging a sidecar called `sqlite3`, the packager expects a binary named `sqlite3-x86_64-unknown-linux-gnu` on linux, and `sqlite3-x86_64-pc-windows-gnu.exe` on windows.\n\nRun `tauri build --help` for more info on targets.\n\nIf you are building a universal binary for MacOS, the packager expects your external binary to also be universal, and named after the target triple, e.g. `sqlite3-universal-apple-darwin`. See ", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "signing": { + "description": "Signing configuration.", + "anyOf": [ + { + "$ref": "#/definitions/SigningConfig" + }, + { + "type": "null" + } + ] + }, + "deb": { + "description": "Platform-specific configurations. Debian-specific settings.", + "anyOf": [ + { + "$ref": "#/definitions/DebianConfig" + }, + { + "type": "null" + } + ] + }, + "wix": { + "description": "WiX configuration.", + "anyOf": [ + { + "$ref": "#/definitions/WixConfig" + }, + { + "type": "null" + } + ] + }, + "nsis": { + "description": "Nsis configuration.", + "anyOf": [ + { + "$ref": "#/definitions/NsisConfig" + }, + { + "type": "null" + } + ] + }, + "macos": { + "description": "MacOS-specific settings.", + "anyOf": [ + { + "$ref": "#/definitions/MacOsConfig" + }, + { + "type": "null" + } + ] + }, + "windows": { + "description": "Windows-specific settings.", + "anyOf": [ + { + "$ref": "#/definitions/WindowsConfig" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "HookCommand": { + "description": "Describes a shell command to be executed when a CLI hook is triggered.", + "anyOf": [ + { + "description": "Run the given script with the default options.", + "type": "string" + }, + { + "description": "Run the given script with custom options.", + "type": "object", + "required": [ + "script" + ], + "properties": { + "script": { + "description": "The script to execute.", + "type": "string" + }, + "dir": { + "description": "The working directory.", + "type": [ + "string", + "null" + ] + } + } + } + ] + }, + "LogLevel": { + "description": "An enum representing the available verbosity levels of the logger.", + "oneOf": [ + { + "description": "The \"error\" level.\n\nDesignates very serious errors.", + "type": "string", + "enum": [ + "error" + ] + }, + { + "description": "The \"warn\" level.\n\nDesignates hazardous situations.", + "type": "string", + "enum": [ + "warn" + ] + }, + { + "description": "The \"info\" level.\n\nDesignates useful information.", + "type": "string", + "enum": [ + "info" + ] + }, + { + "description": "The \"debug\" level.\n\nDesignates lower priority information.", + "type": "string", + "enum": [ + "debug" + ] + }, + { + "description": "The \"trace\" level.\n\nDesignates very low priority, often extremely verbose, information.", + "type": "string", + "enum": [ + "trace" + ] + } + ] + }, + "PackageFormat": { + "description": "The type of the package we're packaging.", + "oneOf": [ + { + "description": "The macOS application bundle (.app).", + "type": "string", + "enum": [ + "app" + ] + }, + { + "description": "The macOS DMG package (.dmg).", + "type": "string", + "enum": [ + "dmg" + ] + }, + { + "description": "The iOS app bundle.", + "type": "string", + "enum": [ + "ios" + ] + }, + { + "description": "The Microsoft Software Installer (.msi) through WiX Toolset.", + "type": "string", + "enum": [ + "wix" + ] + }, + { + "description": "The NSIS installer (.exe).", + "type": "string", + "enum": [ + "nsis" + ] + }, + { + "description": "The Linux Debian package (.deb).", + "type": "string", + "enum": [ + "deb" + ] + }, + { + "description": "The Linux RPM package (.rpm).", + "type": "string", + "enum": [ + "rpm" + ] + }, + { + "description": "The Linux AppImage package (.AppImage).", + "type": "string", + "enum": [ + "appimage" + ] + } + ] + }, + "AppCategory": { + "description": "The possible app categories. Corresponds to `LSApplicationCategoryType` on macOS and the GNOME desktop categories on Debian.", + "type": "string", + "enum": [ + "Business", + "DeveloperTool", + "Education", + "Entertainment", + "Finance", + "Game", + "ActionGame", + "AdventureGame", + "ArcadeGame", + "BoardGame", + "CardGame", + "CasinoGame", + "DiceGame", + "EducationalGame", + "FamilyGame", + "KidsGame", + "MusicGame", + "PuzzleGame", + "RacingGame", + "RolePlayingGame", + "SimulationGame", + "SportsGame", + "StrategyGame", + "TriviaGame", + "WordGame", + "GraphicsAndDesign", + "HealthcareAndFitness", + "Lifestyle", + "Medical", + "Music", + "News", + "Photography", + "Productivity", + "Reference", + "SocialNetworking", + "Sports", + "Travel", + "Utility", + "Video", + "Weather" + ] + }, + "Binary": { + "description": "A binary to package within the final package.", + "type": "object", + "required": [ + "filename" + ], + "properties": { + "filename": { + "description": "File name and without `.exe` on Windows", + "type": "string" + }, + "main": { + "description": "Whether this is the main binary or not", + "default": false, + "type": "boolean" + } + }, + "additionalProperties": false + }, + "FileAssociation": { + "description": "A file association configuration.", + "type": "object", + "required": [ + "ext" + ], + "properties": { + "ext": { + "description": "File extensions to associate with this app. e.g. 'png'", + "type": "array", + "items": { + "type": "string" + } + }, + "name": { + "description": "The name. Maps to `CFBundleTypeName` on macOS. Default to ext[0]", + "type": [ + "string", + "null" + ] + }, + "description": { + "description": "The association description. **Windows-only**. It is displayed on the `Type` column on Windows Explorer.", + "type": [ + "string", + "null" + ] + }, + "role": { + "description": "The app’s role with respect to the type. Maps to `CFBundleTypeRole` on macOS.", + "default": "editor", + "allOf": [ + { + "$ref": "#/definitions/BundleTypeRole" + } + ] + }, + "mimeType": { + "description": "The mime-type e.g. 'image/png' or 'text/plain'. Linux-only.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, + "BundleTypeRole": { + "description": "*macOS-only**. Corresponds to CFBundleTypeRole", + "oneOf": [ + { + "description": "CFBundleTypeRole.Editor. Files can be read and edited.", + "type": "string", + "enum": [ + "editor" + ] + }, + { + "description": "CFBundleTypeRole.Viewer. Files can be read.", + "type": "string", + "enum": [ + "viewer" + ] + }, + { + "description": "CFBundleTypeRole.Shell", + "type": "string", + "enum": [ + "shell" + ] + }, + { + "description": "CFBundleTypeRole.QLGenerator", + "type": "string", + "enum": [ + "qLGenerator" + ] + }, + { + "description": "CFBundleTypeRole.None", + "type": "string", + "enum": [ + "none" + ] + } + ] + }, + "Resources": { + "description": "A list or a map of resources.", + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + ] + }, + "SigningConfig": { + "description": "The app sigining configuration.", + "type": "object", + "required": [ + "pubkey" + ], + "properties": { + "pubkey": { + "description": "Signature public key.", + "type": "string" + } + } + }, + "DebianConfig": { + "description": "The Linux debian configuration.", + "type": "object", + "properties": { + "depends": { + "description": "the list of debian dependencies.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "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 ```", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, + "WixConfig": { + "description": "The wix format configuration", + "type": "object", + "properties": { + "languages": { + "description": "The app languages to build. See .", + "default": [ + [ + "en-US", + { + "localePath": null + } + ] + ], + "allOf": [ + { + "$ref": "#/definitions/WixLanguages" + } + ] + }, + "template": { + "description": "By default, the packager uses an internal template. This option allows you to define your own wix file.", + "type": [ + "string", + "null" + ] + }, + "fragmentPaths": { + "description": "A list of paths to .wxs files with WiX fragments to use.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "componentGroupRefs": { + "description": "The ComponentGroup element ids you want to reference from the fragments.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "componentRefs": { + "description": "The Component element ids you want to reference from the fragments.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "featureGroupRefs": { + "description": "The FeatureGroup element ids you want to reference from the fragments.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "featureRefs": { + "description": "The Feature element ids you want to reference from the fragments.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "mergeRefs": { + "description": "The Merge element ids you want to reference from the fragments.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "enableElevatedUpdateTask": { + "description": "Create an elevated update task within Windows Task Scheduler.", + "default": false, + "type": "boolean" + }, + "bannerPath": { + "description": "Path to a bitmap file to use as the installation user interface banner. This bitmap will appear at the top of all but the first page of the installer.\n\nThe required dimensions are 493px × 58px.", + "type": [ + "string", + "null" + ] + }, + "dialogImagePath": { + "description": "Path to a bitmap file to use on the installation user interface dialogs. It is used on the welcome and completion dialogs. The required dimensions are 493px × 312px.", + "type": [ + "string", + "null" + ] + }, + "fipsCompliant": { + "description": "Enables FIPS compliant algorithms.", + "default": false, + "type": "boolean" + } + }, + "additionalProperties": false + }, + "WixLanguages": { + "description": "The languages to build using WiX.", + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/WixLanguageConfig" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "WixLanguageConfig": { + "description": "Configuration for a target language for the WiX build.", + "type": "object", + "properties": { + "localePath": { + "description": "The path to a locale (`.wxl`) file. See .", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, + "NsisConfig": { + "description": "The NSIS format configuration.", + "type": "object", + "properties": { + "template": { + "description": "A custom .nsi template to use.", + "type": [ + "string", + "null" + ] + }, + "headerImage": { + "description": "The path to a bitmap file to display on the header of installers pages.\n\nThe recommended dimensions are 150px x 57px.", + "type": [ + "string", + "null" + ] + }, + "sidebarImage": { + "description": "The path to a bitmap file for the Welcome page and the Finish page.\n\nThe recommended dimensions are 164px x 314px.", + "type": [ + "string", + "null" + ] + }, + "installerIcon": { + "description": "The path to an icon file used as the installer icon.", + "type": [ + "string", + "null" + ] + }, + "installMode": { + "description": "Whether the installation will be for all users or just the current user.", + "default": "currentUser", + "allOf": [ + { + "$ref": "#/definitions/NSISInstallerMode" + } + ] + }, + "languages": { + "description": "A list of installer languages. By default the OS language is used. If the OS language is not in the list of languages, the first language will be used. To allow the user to select the language, set `display_language_selector` to `true`.\n\nSee for the complete list of languages.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "customLanguageFiles": { + "description": "An key-value pair where the key is the language and the value is the path to a custom `.nsi` file that holds the translated text for tauri's custom messages.\n\nSee for an example `.nsi` file.\n\n**Note**: the key must be a valid NSIS language and it must be added to [`NsisConfig`]languages array,", + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "string" + } + }, + "displayLanguageSelector": { + "description": "Whether to display a language selector dialog before the installer and uninstaller windows are rendered or not. By default the OS language is selected, with a fallback to the first language in the `languages` array.", + "default": false, + "type": "boolean" + } + }, + "additionalProperties": false + }, + "NSISInstallerMode": { + "description": "Install Modes for the NSIS installer.", + "oneOf": [ + { + "description": "Default mode for the installer.\n\nInstall the app by default in a directory that doesn't require Administrator access.\n\nInstaller metadata will be saved under the `HKCU` registry path.", + "type": "string", + "enum": [ + "currentUser" + ] + }, + { + "description": "Install the app by default in the `Program Files` folder directory requires Administrator access for the installation.\n\nInstaller metadata will be saved under the `HKLM` registry path.", + "type": "string", + "enum": [ + "perMachine" + ] + }, + { + "description": "Combines both modes and allows the user to choose at install time whether to install for the current user or per machine. Note that this mode will require Administrator access even if the user wants to install it for the current user only.\n\nInstaller metadata will be saved under the `HKLM` or `HKCU` registry path based on the user's choice.", + "type": "string", + "enum": [ + "both" + ] + } + ] + }, + "MacOsConfig": { + "description": "The macOS configuration.", + "type": "object", + "properties": { + "frameworks": { + "description": "MacOS frameworks that need to be packaged with the app.\n\nEach string can either be the name of a framework (without the `.framework` extension, e.g. `\"SDL2\"`), in which case we will search for that framework in the standard install locations (`~/Library/Frameworks/`, `/Library/Frameworks/`, and `/Network/Library/Frameworks/`), or a path to a specific framework bundle (e.g. `./data/frameworks/SDL2.framework`). Note that this setting just makes cargo-packager copy the specified frameworks into the OS X app bundle (under `Foobar.app/Contents/Frameworks/`); you are still responsible for:\n\n- arranging for the compiled binary to link against those frameworks (e.g. by emitting lines like `cargo:rustc-link-lib=framework=SDL2` from your `build.rs` script)\n\n- embedding the correct rpath in your binary (e.g. by running `install_name_tool -add_rpath \"@executable_path/../Frameworks\" path/to/binary` after compiling)", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "minimumSystemVersion": { + "description": "A version string indicating the minimum MacOS version that the packaged app supports (e.g. `\"10.11\"`). If you are using this config field, you may also want have your `build.rs` script emit `cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET=10.11`.", + "type": [ + "string", + "null" + ] + }, + "exceptionDomain": { + "description": "The exception domain to use on the macOS .app package.\n\nThis allows communication to the outside world e.g. a web server you're shipping.", + "type": [ + "string", + "null" + ] + }, + "signingIdentity": { + "description": "Code signing identity.", + "type": [ + "string", + "null" + ] + }, + "providerShortName": { + "description": "Provider short name for notarization.", + "type": [ + "string", + "null" + ] + }, + "entitlements": { + "description": "Path to the entitlements.plist file.", + "type": [ + "string", + "null" + ] + }, + "infoPlistPath": { + "description": "Path to the Info.plist file for the package.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, + "WindowsConfig": { + "description": "The Windows configuration.", + "type": "object", + "properties": { + "digestAlgorithm": { + "description": "The file digest algorithm to use for creating file signatures. Required for code signing. SHA-256 is recommended.", + "type": [ + "string", + "null" + ] + }, + "certificateThumbprint": { + "description": "The SHA1 hash of the signing certificate.", + "type": [ + "string", + "null" + ] + }, + "timestampUrl": { + "description": "Server to use during timestamping.", + "type": [ + "string", + "null" + ] + }, + "tsp": { + "description": "Whether to use Time-Stamp Protocol (TSP, a.k.a. RFC 3161) for the timestamp server. Your code signing provider may use a TSP timestamp server, like e.g. SSL.com does. If so, enable TSP by setting to true.", + "default": false, + "type": "boolean" + }, + "allowDowngrades": { + "description": "Validates a second app installation, blocking the user from installing an older version if set to `false`.\n\nFor instance, if `1.2.1` is installed, the user won't be able to install app version `1.2.0` or `1.1.5`.\n\nThe default value of this flag is `true`.", + "default": true, + "type": "boolean" + } + }, + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index a3dd93f5..9d0f4ff9 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -454,10 +454,8 @@ impl Default for LogLevel { #[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct Binary { - /// Name as given in the Cargo.toml - pub name: String, - /// Path to the main source file of the binary. - pub path: PathBuf, + /// File name and without `.exe` on Windows + pub filename: String, /// Whether this is the main binary or not #[serde(default)] pub main: bool, @@ -518,12 +516,12 @@ pub struct Config { /// /// if not present, we'll use the PackageType list for the target OS. pub formats: Option>, - /// the directory where the packages will be placed. + /// the directory where the `binaries` exist and where the packages will be placed. #[serde(default, alias = "out-dir", alias = "out_dir")] pub out_dir: PathBuf, - /// The target triple. - #[serde(default, alias = "target-triple", alias = "target_triple")] - pub target_triple: String, + /// The target triple. Defaults to the current OS target triple. + #[serde(alias = "target-triple", alias = "target_triple")] + pub target_triple: Option, /// the package's product name, for example "My Awesome App". #[serde(default, alias = "product-name", alias = "product_name")] pub product_name: String, diff --git a/crates/packager/Cargo.toml b/crates/packager/Cargo.toml index edecc8f3..821e4ae3 100644 --- a/crates/packager/Cargo.toml +++ b/crates/packager/Cargo.toml @@ -18,17 +18,18 @@ schemars.workspace = true [dependencies] cargo-packager-config.workspace = true thiserror.workspace = true -log = { version = "0.4", features = ["kv_unstable", "kv_unstable_std"] } serde.workspace = true serde_json.workspace = true dunce.workspace = true -cargo_metadata = "0.18" -env_logger = { version = "0.10", optional = true } +dirs.workspace = true +semver.workspace = true clap = { workspace = true, optional = true } -dirs = "5.0" +log = { workspace = true, features = ["kv_unstable", "kv_unstable_std"] } +toml = "0.7" +env_logger = { version = "0.10", optional = true } +cargo_metadata = "0.17" ureq = "2.7" hex = "0.4" -semver = "1" sha1 = "0.10" sha2 = "0.10" zip = { version = "0.6", default-features = false, features = ["deflate"] } diff --git a/crates/packager/build.rs b/crates/packager/build.rs index f0e7fbb7..938cdeef 100644 --- a/crates/packager/build.rs +++ b/crates/packager/build.rs @@ -9,7 +9,9 @@ pub fn main() -> Result<(), Box> { let schema = schemars::schema_for!(cargo_packager_config::Config); let schema_str = serde_json::to_string_pretty(&schema).unwrap(); let crate_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR")?); - let mut schema_file = BufWriter::new(File::create(crate_dir.join("schema.json"))?); - write!(schema_file, "{schema_str}")?; + for file in ["schema.json", "../config/schema.json"] { + let mut schema_file = BufWriter::new(File::create(crate_dir.join(file))?); + write!(schema_file, "{schema_str}")?; + } Ok(()) } diff --git a/crates/packager/schema.json b/crates/packager/schema.json index eb5a973b..e06ec112 100644 --- a/crates/packager/schema.json +++ b/crates/packager/schema.json @@ -57,14 +57,16 @@ } }, "outDir": { - "description": "the directory where the packages will be placed.", + "description": "the directory where the `binaries` exist and where the packages will be placed.", "default": "", "type": "string" }, "targetTriple": { - "description": "The target triple.", - "default": "", - "type": "string" + "description": "The target triple. Defaults to the current OS target triple.", + "type": [ + "string", + "null" + ] }, "productName": { "description": "the package's product name, for example \"My Awesome App\".", @@ -442,16 +444,11 @@ "description": "A binary to package within the final package.", "type": "object", "required": [ - "name", - "path" + "filename" ], "properties": { - "name": { - "description": "Name as given in the Cargo.toml", - "type": "string" - }, - "path": { - "description": "Path to the main source file of the binary.", + "filename": { + "description": "File name and without `.exe` on Windows", "type": "string" }, "main": { diff --git a/crates/packager/src/config.rs b/crates/packager/src/config.rs index d4ff5d2f..cdd26da7 100644 --- a/crates/packager/src/config.rs +++ b/crates/packager/src/config.rs @@ -2,6 +2,8 @@ use std::path::{Path, PathBuf}; pub use cargo_packager_config::*; +use crate::util; + #[derive(Debug, Clone)] pub(crate) struct Resource { pub src: PathBuf, @@ -17,7 +19,9 @@ pub trait ConfigExt { fn wix(&self) -> Option<&WixConfig>; /// Returns the debian specific configuration fn deb(&self) -> Option<&DebianConfig>; - /// Returns the architecture for the binary being packaged (e.g. "arm", "x86" or "x86_64"). + /// Returns the target triple for the package to be built (e.g. "aarch64-unknown-linux-gnu"). + fn target_triple(&self) -> String; + /// Returns the architecture for the package to be built (e.g. "arm", "x86" or "x86_64"). fn target_arch(&self) -> crate::Result<&str>; /// Returns the path to the specified binary. fn binary_path(&self, binary: &Binary) -> PathBuf; @@ -25,6 +29,8 @@ pub trait ConfigExt { fn identifier(&self) -> &str; /// Returns the package publisher fn publisher(&self) -> String; + /// Returns the out dir + fn out_dir(&self) -> PathBuf; } impl ConfigExt for Config { @@ -44,26 +50,31 @@ impl ConfigExt for Config { self.deb.as_ref() } + fn target_triple(&self) -> String { + self.target_triple + .clone() + .unwrap_or_else(|| util::target_triple().unwrap()) + } + fn target_arch(&self) -> crate::Result<&str> { - Ok(if self.target_triple.starts_with("x86_64") { + let target = self.target_triple(); + Ok(if target.starts_with("x86_64") { "x86_64" - } else if self.target_triple.starts_with('i') { + } else if target.starts_with('i') { "x86" - } else if self.target_triple.starts_with("arm") { + } else if target.starts_with("arm") { "arm" - } else if self.target_triple.starts_with("aarch64") { + } else if target.starts_with("aarch64") { "aarch64" - } else if self.target_triple.starts_with("universal") { + } else if target.starts_with("universal") { "universal" } else { - return Err(crate::Error::UnexpectedTargetTriple( - self.target_triple.clone(), - )); + return Err(crate::Error::UnexpectedTargetTriple(target.clone())); }) } fn binary_path(&self, binary: &Binary) -> PathBuf { - self.out_dir.join(&binary.name) + self.out_dir().join(&binary.filename) } fn identifier(&self) -> &str { @@ -76,15 +87,19 @@ impl ConfigExt for Config { .clone() .unwrap_or_else(|| identifier.split('.').nth(1).unwrap_or(identifier).into()) } + + fn out_dir(&self) -> PathBuf { + dunce::canonicalize(&self.out_dir).unwrap_or_else(|_| self.out_dir.clone()) + } } pub(crate) trait ConfigExtInternal { fn main_binary(&self) -> crate::Result<&Binary>; fn main_binary_name(&self) -> crate::Result<&String>; - fn resources(&self) -> Option>; + fn resources(&self) -> crate::Result>; fn find_ico(&self) -> Option; fn copy_resources(&self, path: &Path) -> crate::Result<()>; - fn copy_binaries(&self, path: &Path) -> crate::Result<()>; + fn copy_external_binaries(&self, path: &Path) -> crate::Result<()>; } impl ConfigExtInternal for Config { @@ -99,49 +114,48 @@ impl ConfigExtInternal for Config { self.binaries .iter() .find(|bin| bin.main) - .map(|b| &b.name) + .map(|b| &b.filename) .ok_or_else(|| crate::Error::MainBinaryNotFound) } - fn resources(&self) -> Option> { - self.resources.as_ref().map(|resources| { + fn resources(&self) -> crate::Result> { + if let Some(resources) = &self.resources { let mut out = Vec::new(); - let cwd = std::env::current_dir().expect("failed to get current directory"); + let cwd = std::env::current_dir()?; + let cwd = dunce::simplified(&cwd); match resources { Resources::List(l) => { for resource in l { - out.extend(glob::glob(resource).unwrap().filter_map(|src| { - src.ok().and_then(|src| { - use relative_path::PathExt; - let src = - dunce::canonicalize(src).expect("failed to canonicalize path"); - let target = src.relative_to(&cwd); - target.ok().map(|target| Resource { - src, - target: target.to_path(""), - }) + for src in glob::glob(resource).unwrap() { + use relative_path::PathExt; + let src = src?; + let src = dunce::canonicalize(src)?; + let target = src.relative_to(&cwd)?; + out.push(Resource { + src, + target: target.to_path(""), }) - })); + } } } Resources::Map(m) => { for (src, target) in m.iter() { - out.extend(glob::glob(src).unwrap().filter_map(|src| { - src.ok().map(|src| { - let src = - dunce::canonicalize(src).expect("failed to canonicalize path"); - let target = PathBuf::from(target).join( - src.file_name() - .expect("Failed to get filename of a resource file"), - ); - Resource { src, target } - }) - })) + for src in glob::glob(src).unwrap() { + let src = src?; + let src = dunce::canonicalize(src)?; + let target = PathBuf::from(target).join( + src.file_name() + .expect("Failed to get filename of a resource file"), + ); + out.push(Resource { src, target }); + } } } } - out - }) + Ok(out) + } else { + Ok(vec![]) + } } fn find_ico(&self) -> Option { @@ -161,25 +175,23 @@ impl ConfigExtInternal for Config { } fn copy_resources(&self, path: &Path) -> crate::Result<()> { - if let Some(resources) = self.resources() { - for resource in resources { - let dest = path.join(resource.target); - std::fs::create_dir_all(dest.parent().ok_or(crate::Error::ParentDirNotFound)?)?; - std::fs::copy(resource.src, dest)?; - } + for resource in self.resources()? { + let dest = path.join(resource.target); + std::fs::create_dir_all(dest.parent().ok_or(crate::Error::ParentDirNotFound)?)?; + std::fs::copy(resource.src, dest)?; } Ok(()) } - fn copy_binaries(&self, path: &Path) -> crate::Result<()> { + fn copy_external_binaries(&self, path: &Path) -> crate::Result<()> { if let Some(external_binaries) = &self.external_binaries { for src in external_binaries { - let src = PathBuf::from(src); + let src = dunce::canonicalize(PathBuf::from(src))?; let dest = path.join( src.file_name() .expect("failed to extract external binary filename") .to_string_lossy() - .replace(&format!("-{}", self.target_triple), ""), + .replace(&format!("-{}", self.target_triple()), ""), ); std::fs::create_dir_all(dest.parent().ok_or(crate::Error::ParentDirNotFound)?)?; std::fs::copy(src, dest)?; diff --git a/crates/packager/src/deb/mod.rs b/crates/packager/src/deb/mod.rs index 30ef3274..524ad27a 100644 --- a/crates/packager/src/deb/mod.rs +++ b/crates/packager/src/deb/mod.rs @@ -133,7 +133,7 @@ pub fn generate_data(config: &Config, package_dir: &Path) -> crate::Result crate::Result crate::Result> { let package_base_name = format!("{}_{}_{}", config.main_binary_name()?, config.version, arch); let package_name = format!("{package_base_name}.deb"); - let base_dir = config.out_dir.join("deb"); + let base_dir = config.out_dir().join("deb"); let package_dir = base_dir.join(&package_base_name); if package_dir.exists() { std::fs::remove_dir_all(&package_dir)?; } - let package_path = base_dir.join(&package_name); + let package_path = config.out_dir().join(&package_name); log::info!(action = "Pckaging"; "{} ({})", package_name, package_path.display()); diff --git a/crates/packager/src/error.rs b/crates/packager/src/error.rs index 3dc71512..50665365 100644 --- a/crates/packager/src/error.rs +++ b/crates/packager/src/error.rs @@ -7,9 +7,12 @@ pub enum Error { /// Error while reading cargo metadata. #[error("Failed to read cargo metadata: {0}")] Metadata(#[from] cargo_metadata::Error), - /// Config parsing error. + /// JSON Config parsing error. #[error("Failed to parse config: {0}")] - ParseError(#[from] serde_json::Error), + JSONConfigParseError(#[from] serde_json::Error), + /// TOML Config parsing error. + #[error("Failed to parse config: {0}")] + TOMLConfigParseError(#[from] toml::de::Error), /// Target triple architecture error #[error("Unable to determine target-architecture")] Architecture, @@ -110,6 +113,12 @@ pub enum Error { /// Path prefix strip error. #[error(transparent)] StripPrefixError(#[from] std::path::StripPrefixError), + /// std::process::Command program failed + #[error("Command failed")] + CommandFailed, + /// Relative paths errors + #[error(transparent)] + RelativeToError(#[from] relative_path::RelativeToError), } /// Convenient type alias of Result type for cargo-packager. diff --git a/crates/packager/src/lib.rs b/crates/packager/src/lib.rs index 0172a20b..0f8b0961 100644 --- a/crates/packager/src/lib.rs +++ b/crates/packager/src/lib.rs @@ -72,23 +72,13 @@ fn cross_command(script: &str) -> Command { cmd.arg("/S").arg("/C").arg(script); #[cfg(not(windows))] let mut cmd = Command::new("sh"); + cmd.current_dir(dunce::canonicalize(std::env::current_dir().unwrap()).unwrap()); #[cfg(not(windows))] cmd.arg("-c").arg(script); cmd } pub fn package(config: &Config) -> Result> { - let target_os = config - .target_triple - .split('-') - .nth(2) - .unwrap_or(std::env::consts::OS) - .replace("darwin", "macos"); - - if target_os != std::env::consts::OS { - log:: warn!("Cross-platform compilation is experimental and does not support all features. Please use a matching host system for full compatibility."); - } - let mut packages = Vec::new(); let formats = config diff --git a/crates/packager/src/main.rs b/crates/packager/src/main.rs index 83b1a745..05c57e58 100644 --- a/crates/packager/src/main.rs +++ b/crates/packager/src/main.rs @@ -1,4 +1,7 @@ -use std::{io::Write, path::PathBuf}; +use std::{ + io::Write, + path::{Path, PathBuf}, +}; use cargo_packager::{ config::{Binary, Config}, @@ -9,9 +12,64 @@ use clap::{ArgAction, CommandFactory, FromArgMatches, Parser}; use env_logger::fmt::Color; use log::{log_enabled, Level}; -fn load_configs_from_cwd(profile: &str, cli: &Cli) -> Result> { +fn parse_config_file>(path: P) -> crate::Result, Config)>> { + let path = path.as_ref().to_path_buf().canonicalize()?; + let content = std::fs::read_to_string(&path)?; + let configs = match path.extension().and_then(|e| e.to_str()) { + Some("toml") => { + if let Ok(cs) = toml::from_str::>(&content) { + cs.into_iter().map(|c| (Some(path.clone()), c)).collect() + } else { + vec![(Some(path), toml::from_str::(&content)?)] + } + } + _ => { + if let Ok(cs) = serde_json::from_str::>(&content) { + cs.into_iter().map(|c| (Some(path.clone()), c)).collect() + } else { + vec![(Some(path), serde_json::from_str::(&content)?)] + } + } + }; + + Ok(configs) +} + +fn find_config_files() -> Vec { + let opts = glob::MatchOptions { + case_sensitive: false, + ..Default::default() + }; + + [ + glob::glob_with("**/packager.toml", opts) + .unwrap() + .flatten() + .collect::>(), + glob::glob_with("**/packager.json", opts) + .unwrap() + .flatten() + .collect::>(), + ] + .concat() +} + +fn load_configs_from_cargo_workspace( + release: bool, + profile: Option, + manifest_path: Option, + packages: Option>, +) -> Result, Config)>> { + let profile = if release { + "release" + } else if let Some(profile) = &profile { + profile.as_str() + } else { + "debug" + }; + let mut metadata_cmd = cargo_metadata::MetadataCommand::new(); - if let Some(manifest_path) = &cli.manifest_path { + if let Some(manifest_path) = &manifest_path { metadata_cmd.manifest_path(manifest_path); } let metadata = metadata_cmd.exec()?; @@ -19,8 +77,7 @@ fn load_configs_from_cwd(profile: &str, cli: &Cli) -> Result Result Result LogLevel::Info, - 1 => LogLevel::Debug, - 2.. => LogLevel::Trace, - }); - } if config.license_file.is_none() { config.license_file = package .license_file @@ -75,15 +119,17 @@ fn load_configs_from_cwd(profile: &str, cli: &Cli) -> Result>(); for target in &targets { config.binaries.push(Binary { - name: target.name.clone(), - path: target.src_path.as_std_path().to_owned(), + filename: target.name.clone(), main: match targets.len() { 1 => true, _ => target.name == package.name, }, }) } - configs.push((package.manifest_path.as_std_path().to_path_buf(), config)); + configs.push(( + Some(package.manifest_path.as_std_path().to_path_buf()), + config, + )); } } @@ -103,43 +149,92 @@ pub(crate) struct Cli { /// Enables verbose logging #[clap(short, long, global = true, action = ArgAction::Count)] verbose: u8, - /// Package your app in release mode. + /// Specify the package fromats to build. + #[clap(long, value_enum)] + formats: Option>, + /// Specify a configuration to read, which could be a JSON file, + /// TOML file, or a raw JSON string. By default, cargo-pacakger + /// looks for `{p,P}ackager.{toml,json}` and + /// `[package.metadata.packager]` in `Cargo.toml` files. #[clap(short, long)] + config: Option, + + /// Package the release version of your app. + /// Ignored when `--config` is used. + #[clap(short, long, group = "cargo-profile")] release: bool, /// Specify the cargo profile to use for packaging your app. - #[clap(long)] + /// Ignored when `--config` is used. + #[clap(long, group = "cargo-profile")] profile: Option, - /// Specify the packages to build. + /// Specify the cargo packages in a workspace to create. + /// Ignored when `--config` is used. #[clap(short, long)] packages: Option>, /// Specify the manifest path to use for reading the configuration. + /// Ignored when `--config` is used. #[clap(long)] - manifest_path: Option, - /// Specify the package fromats to build. - #[clap(long, value_enum)] - formats: Option>, + manifest_path: Option, } fn try_run(cli: Cli) -> Result<()> { use std::fmt::Write; - let profile = if cli.release { - "release" - } else if let Some(profile) = &cli.profile { - profile.as_str() - } else { - "debug" + let mut configs = match cli.config { + // if a raw json object + Some(c) if c.starts_with("{") => vec![(None, serde_json::from_str::(&c)?)], + // if a raw json array + Some(c) if c.starts_with("[") => serde_json::from_str::>(&c)? + .into_iter() + .map(|c| (None, c)) + .collect(), + // if a path to config file + Some(c) => parse_config_file(c)?, + // fallback to config files and cargo workspaces configs + _ => [ + find_config_files() + .into_iter() + .filter_map(|c| parse_config_file(c).ok()) + .collect::>() + .concat(), + load_configs_from_cargo_workspace( + cli.release, + cli.profile, + cli.manifest_path, + cli.packages, + )?, + ] + .concat(), }; + if configs.is_empty() { + log::warn!("Couldn't detect a valid configuration file! Nothing to do here.") + } + + for (_, config) in &mut configs { + if let Some(formats) = &cli.formats { + config.formats.replace(formats.clone()); + } + + if config.log_level.is_none() { + config.log_level.replace(match cli.verbose { + 0 => LogLevel::Info, + 1 => LogLevel::Debug, + 2.. => LogLevel::Trace, + }); + } + } + let mut outputs = Vec::new(); - for (manifest_path, config) in load_configs_from_cwd(profile, &cli)? { - // change the directory to the manifest being built - // so paths are read relative to it - std::env::set_current_dir( - manifest_path - .parent() - .ok_or(cargo_packager::Error::ParentDirNotFound)?, - )?; + for (config_dir, config) in configs { + if let Some(path) = config_dir { + // change the directory to the manifest being built + // so paths are read relative to it + std::env::set_current_dir( + path.parent() + .ok_or(cargo_packager::Error::ParentDirNotFound)?, + )?; + } // create the packages outputs.extend(package(&config)?); diff --git a/crates/packager/src/nsis/installer.nsi b/crates/packager/src/nsis/installer.nsi index 2b2ff24a..9c8c921d 100644 --- a/crates/packager/src/nsis/installer.nsi +++ b/crates/packager/src/nsis/installer.nsi @@ -578,11 +578,11 @@ Section Uninstall ; Remove start menu shortcut !insertmacro MUI_STARTMENU_GETFOLDER Application $AppStartMenuFolder - Delete "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk" + Delete "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk" RMDir "$SMPROGRAMS\$AppStartMenuFolder" ; Remove desktop shortcuts - Delete "$DESKTOP\${MAINBINARYNAME}.lnk" + Delete "$DESKTOP\${PRODUCTNAME}.lnk" ; Remove registry information for add/remove programs !if "${INSTALLMODE}" == "both" @@ -620,12 +620,12 @@ Function SkipIfPassive FunctionEnd Function CreateDesktopShortcut - CreateShortcut "$DESKTOP\${MAINBINARYNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe" - ApplicationID::Set "$DESKTOP\${MAINBINARYNAME}.lnk" "${BUNDLEID}" + CreateShortcut "$DESKTOP\${PRODUCTNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe" + ApplicationID::Set "$DESKTOP\${PRODUCTNAME}.lnk" "${BUNDLEID}" FunctionEnd Function CreateStartMenuShortcut CreateDirectory "$SMPROGRAMS\$AppStartMenuFolder" - CreateShortcut "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe" - ApplicationID::Set "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk" "${BUNDLEID}" + CreateShortcut "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe" + ApplicationID::Set "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk" "${BUNDLEID}" FunctionEnd diff --git a/crates/packager/src/nsis/mod.rs b/crates/packager/src/nsis/mod.rs index 2b67984d..5d721ae3 100644 --- a/crates/packager/src/nsis/mod.rs +++ b/crates/packager/src/nsis/mod.rs @@ -44,25 +44,23 @@ const NSIS_REQUIRED_FILES: &[&str] = &[ /// BTreeMap type ResourcesMap = BTreeMap; -fn generate_resource_data(config: &Config) -> ResourcesMap { +fn generate_resource_data(config: &Config) -> crate::Result { let mut resources_map = ResourcesMap::new(); - if let Some(resources) = config.resources() { - for resource in resources { - resources_map.insert( - resource.src, - ( - resource - .target - .parent() - .map(|p| p.to_string_lossy().to_string()) - .unwrap_or_default(), - resource.target, - ), - ); - } + for resource in config.resources()? { + resources_map.insert( + resource.src, + ( + resource + .target + .parent() + .map(|p| p.to_string_lossy().to_string()) + .unwrap_or_default(), + resource.target, + ), + ); } - resources_map + Ok(resources_map) } /// BTreeMap @@ -78,7 +76,7 @@ fn generate_binaries_data(config: &Config) -> crate::Result { .file_name() .expect("failed to extract external binary filename") .to_string_lossy() - .replace(&format!("-{}", config.target_triple), ""); + .replace(&format!("-{}", config.target_triple()), ""); binaries.insert(binary_path, dest_filename); } } @@ -267,7 +265,7 @@ fn build_nsis_app_installer( #[cfg(not(target_os = "windows"))] log::warn!("Code signing is currently only supported on Windows hosts, skipping signing the main binary..."); - let output_path = config.out_dir.join("nsis").join(arch); + let output_path = config.out_dir().join("nsis").join(arch); if output_path.exists() { std::fs::remove_dir_all(&output_path)?; } @@ -379,7 +377,7 @@ fn build_nsis_app_installer( .iter() .find(|bin| bin.main) .ok_or_else(|| crate::Error::MainBinaryNotFound)?; - data.insert("main_binary_name", to_json(&main_binary.name)); + data.insert("main_binary_name", to_json(&main_binary.filename)); data.insert( "main_binary_path", to_json(config.binary_path(main_binary).with_extension("exe")), @@ -392,7 +390,7 @@ fn build_nsis_app_installer( let out_file = "nsis-output.exe"; data.insert("out_file", to_json(out_file)); - let resources = generate_resource_data(config); + let resources = generate_resource_data(config)?; data.insert("resources", to_json(resources)); let binaries = generate_binaries_data(config)?; @@ -447,12 +445,11 @@ fn build_nsis_app_installer( } } - let package_base_name = format!("{}_{}_{}-setup", main_binary.name, config.version, arch,); - let nsis_output_path = output_path.join(out_file); - let nsis_installer_path = config - .out_dir - .join(format!("package/nsis/{}.exe", package_base_name)); + let nsis_installer_path = config.out_dir().join(format!( + "{}_{}_{}-setup.exe", + main_binary.filename, config.version, arch + )); std::fs::create_dir_all(nsis_installer_path.parent().unwrap())?; log::info!(action = "Running"; "makensis.exe to produce {}", util::display_path(&nsis_installer_path)); @@ -477,7 +474,7 @@ fn build_nsis_app_installer( .output() .map_err(|e| crate::Error::NsisFailed(e.to_string()))?; - util::log_if_needed(log_level, output); + util::log_if_needed_and_error_out(log_level, output)?; std::fs::rename(nsis_output_path, &nsis_installer_path)?; diff --git a/crates/packager/src/util.rs b/crates/packager/src/util.rs index 54360378..c7a8977c 100644 --- a/crates/packager/src/util.rs +++ b/crates/packager/src/util.rs @@ -178,7 +178,10 @@ pub(crate) fn os_bitness() -> crate::Result { ) } -pub(crate) fn log_if_needed(log_level: LogLevel, output: Output) { +pub(crate) fn log_if_needed_and_error_out( + log_level: LogLevel, + output: Output, +) -> crate::Result<()> { if output.status.success() && !output.stdout.is_empty() && log_level >= LogLevel::Debug { log::debug!(action = "stdout"; "{}", String::from_utf8_lossy(&output.stdout)) } else if !output.status.success() && log_level >= LogLevel::Error { @@ -192,8 +195,12 @@ pub(crate) fn log_if_needed(log_level: LogLevel, output: Output) { } else { &output.stdout }; - log::error!(action = action; "{}", String::from_utf8_lossy(output)) + log::error!(action = action; "{}", String::from_utf8_lossy(output)); + + return Err(crate::Error::CommandFailed); } + + Ok(()) } /// Returns true if the path has a filename indicating that it is a high-density diff --git a/crates/packager/src/wix/main.wxs b/crates/packager/src/wix/main.wxs index dfbb963d..617853fa 100644 --- a/crates/packager/src/wix/main.wxs +++ b/crates/packager/src/wix/main.wxs @@ -180,16 +180,6 @@ - {{#each merge_modules as |msm| ~}} - - - - - - - - {{/each~}} - crate::Result> { .file_name() .expect("failed to extract external binary filename") .to_string_lossy() - .replace(&format!("-{}", config.target_triple), ""); + .replace(&format!("-{}", config.target_triple()), ""); let dest = tmp_dir.join(&dest_filename); std::fs::copy(binary_path, &dest)?; @@ -164,7 +164,7 @@ fn generate_binaries_data(config: &Config) -> crate::Result> { .into_string() .expect("failed to read binary path"), id: regex - .replace_all(&bin.name.replace('-', "_"), "") + .replace_all(&&bin.filename.replace('-', "_"), "") .to_string(), }) } @@ -250,81 +250,79 @@ type ResourceMap = BTreeMap; /// Generates the data required for the resource on wix fn generate_resource_data(config: &Config) -> crate::Result { let mut resources_map = ResourceMap::new(); - if let Some(resources) = config.resources() { - for resource in resources { - let resource_entry = ResourceFile { - id: format!("I{}", Uuid::new_v4().as_simple()), - guid: Uuid::new_v4().to_string(), - path: resource.src, - }; - - // split the resource path directories - let components_count = resource.target.components().count(); - let directories = resource - .target - .components() - .take(components_count - 1) // the last component is the file - .collect::>(); - - // transform the directory structure to a chained vec structure - let first_directory = directories - .first() - .map(|d| d.as_os_str().to_string_lossy().into_owned()) - .unwrap_or_else(String::new); - - if !resources_map.contains_key(&first_directory) { - resources_map.insert( - first_directory.clone(), - ResourceDirectory { - path: first_directory.clone(), - name: first_directory.clone(), - directories: vec![], - files: vec![], - }, - ); - } + for resource in config.resources()? { + let resource_entry = ResourceFile { + id: format!("I{}", Uuid::new_v4().as_simple()), + guid: Uuid::new_v4().to_string(), + path: resource.src, + }; - let mut directory_entry = resources_map - .get_mut(&first_directory) - .expect("Unable to handle resources"); + // split the resource path directories + let components_count = resource.target.components().count(); + let directories = resource + .target + .components() + .take(components_count - 1) // the last component is the file + .collect::>(); + + // transform the directory structure to a chained vec structure + let first_directory = directories + .first() + .map(|d| d.as_os_str().to_string_lossy().into_owned()) + .unwrap_or_else(String::new); + + if !resources_map.contains_key(&first_directory) { + resources_map.insert( + first_directory.clone(), + ResourceDirectory { + path: first_directory.clone(), + name: first_directory.clone(), + directories: vec![], + files: vec![], + }, + ); + } - let mut path = String::new(); - // the first component is already parsed on `first_directory` so we skip(1) - for directory in directories.into_iter().skip(1) { - let directory_name = directory - .as_os_str() - .to_os_string() - .into_string() - .expect("failed to read resource folder name"); - path.push_str(directory_name.as_str()); - path.push(std::path::MAIN_SEPARATOR); - - let index = directory_entry - .directories - .iter() - .position(|f| f.path == path); - match index { - Some(i) => directory_entry = directory_entry.directories.get_mut(i).unwrap(), - None => { - directory_entry.directories.push(ResourceDirectory { - path: path.clone(), - name: directory_name, - directories: vec![], - files: vec![], - }); - directory_entry = directory_entry.directories.iter_mut().last().unwrap(); - } + let mut directory_entry = resources_map + .get_mut(&first_directory) + .expect("Unable to handle resources"); + + let mut path = String::new(); + // the first component is already parsed on `first_directory` so we skip(1) + for directory in directories.into_iter().skip(1) { + let directory_name = directory + .as_os_str() + .to_os_string() + .into_string() + .expect("failed to read resource folder name"); + path.push_str(directory_name.as_str()); + path.push(std::path::MAIN_SEPARATOR); + + let index = directory_entry + .directories + .iter() + .position(|f| f.path == path); + match index { + Some(i) => directory_entry = directory_entry.directories.get_mut(i).unwrap(), + None => { + directory_entry.directories.push(ResourceDirectory { + path: path.clone(), + name: directory_name, + directories: vec![], + files: vec![], + }); + directory_entry = directory_entry.directories.iter_mut().last().unwrap(); } } - directory_entry.add_file(resource_entry); } + directory_entry.add_file(resource_entry); } let mut dlls = Vec::new(); for dll in glob::glob( config - .out_dir + .out_dir() .join("*.dll") .to_string_lossy() .to_string() @@ -359,36 +357,10 @@ struct MergeModule { path: String, } -fn get_merge_modules(config: &Config) -> crate::Result> { - let mut merge_modules = Vec::new(); - let regex = Regex::new(r"[^\w\d\.]")?; - for msm in glob::glob( - config - .out_dir - .join("*.msm") - .to_string_lossy() - .to_string() - .as_str(), - )? { - let path = msm?; - let filename = path - .file_name() - .expect("failed to extract merge module filename") - .to_os_string() - .into_string() - .expect("failed to convert merge module filename to string"); - merge_modules.push(MergeModule { - name: regex.replace_all(&filename, "").to_string(), - path: path.to_string_lossy().to_string(), - }); - } - Ok(merge_modules) -} - /// Copies the icon to the binary path, under the `resources` folder, /// and returns the path to the file. fn copy_icon(config: &Config, filename: &str, path: &Path) -> crate::Result { - let resource_dir = config.out_dir.join("resources"); + let resource_dir = config.out_dir().join("resources"); std::fs::create_dir_all(&resource_dir)?; let icon_target_path = resource_dir.join(filename); let icon_path = std::env::current_dir()?.join(path); @@ -451,7 +423,7 @@ fn run_candle( .output() .map_err(|e| crate::Error::WixFailed("candle.exe".into(), e.to_string()))?; - util::log_if_needed(log_level, output); + util::log_if_needed_and_error_out(log_level, output)?; Ok(()) } @@ -486,7 +458,7 @@ fn run_light( .output() .map_err(|e| crate::Error::WixFailed("light.exe".into(), e.to_string()))?; - log_if_needed(log_level, output); + util::log_if_needed_and_error_out(log_level, output)?; Ok(()) } @@ -520,7 +492,7 @@ fn build_wix_app_installer( sign::try_sign(&app_exe_source.with_extension("exe"), config)?; - let output_path = config.out_dir.join("wix").join(arch); + let output_path = config.out_dir().join("wix").join(arch); if output_path.exists() { std::fs::remove_dir_all(&output_path)?; @@ -546,7 +518,7 @@ fn build_wix_app_installer( "#, license_contents.replace('\n', "\\par ") ); - let rtf_output_path = config.out_dir.join("wix").join("LICENSE.rtf"); + let rtf_output_path = config.out_dir().join("wix").join("LICENSE.rtf"); std::fs::write(&rtf_output_path, license_rtf)?; data.insert("license", to_json(rtf_output_path)); } @@ -560,7 +532,7 @@ fn build_wix_app_installer( data.insert("manufacturer", to_json(manufacturer)); let upgrade_code = Uuid::new_v5( &Uuid::NAMESPACE_DNS, - format!("{}.app.x64", &main_binary.name).as_bytes(), + format!("{}.app.x64", &main_binary.filename).as_bytes(), ) .to_string(); @@ -592,8 +564,7 @@ fn build_wix_app_installer( data.insert("resources", to_json(resources_wix_string)); data.insert("resource_file_ids", to_json(files_ids)); - let merge_modules = get_merge_modules(config)?; - data.insert("merge_modules", to_json(merge_modules)); + // TODO: how to choose to include merge modules data.insert( "app_exe_source", @@ -716,9 +687,9 @@ fn build_wix_app_installer( } let main_wxs_path = output_path.join("main.wxs"); - std::fs::write(main_wxs_path, handlebars.render("main.wxs", &data)?)?; + std::fs::write(&main_wxs_path, handlebars.render("main.wxs", &data)?)?; - let mut candle_inputs = vec![("main.wxs".into(), Vec::new())]; + let mut candle_inputs = vec![(main_wxs_path, Vec::new())]; let current_dir = std::env::current_dir()?; let extension_regex = Regex::new("\"http://schemas.microsoft.com/wix/(\\w+)\"")?; @@ -823,9 +794,9 @@ fn build_wix_app_installer( "*.wixobj".into(), ]; let msi_output_path = output_path.join("output.msi"); - let msi_path = config.out_dir.join(format!( - "bundle/wix/{}_{}_{}_{}.msi", - main_binary.name, app_version, arch, language + let msi_path = config.out_dir().join(format!( + "{}_{}_{}_{}.msi", + main_binary.filename, app_version, arch, language )); std::fs::create_dir_all(msi_path.parent().unwrap())?; diff --git a/examples/deno/.gitignore b/examples/deno/.gitignore new file mode 100644 index 00000000..3e221292 --- /dev/null +++ b/examples/deno/.gitignore @@ -0,0 +1 @@ +/dist \ No newline at end of file diff --git a/examples/deno/README.md b/examples/deno/README.md new file mode 100644 index 00000000..663b376a --- /dev/null +++ b/examples/deno/README.md @@ -0,0 +1,7 @@ +## Deno example + +1. install `deno` first https://deno.land/manual/getting_started/installation +2. package the app + ```sh + cargo r -p cargo-packager -- -c packager.json + ``` diff --git a/examples/deno/deno-example.js b/examples/deno/deno-example.js new file mode 100644 index 00000000..019c0f4b --- /dev/null +++ b/examples/deno/deno-example.js @@ -0,0 +1 @@ +console.log("Hello World!"); diff --git a/examples/deno/packager.json b/examples/deno/packager.json new file mode 100644 index 00000000..71e9f5e8 --- /dev/null +++ b/examples/deno/packager.json @@ -0,0 +1,10 @@ +{ + "$schema": "../../crates/config/schema.json", + "outDir": "./dist", + "beforePackagingCommand": "deno compile deno-example.js --output dist/deno-example", + "productName": "Deno example", + "version": "0.0.0", + "identifier": "com.deno.example", + "resources": ["deno-example.js", "README.md"], + "binaries": [{ "filename": "deno-example", "main": true }] +} diff --git a/examples/dioxus/Cargo.toml b/examples/dioxus/Cargo.toml index 008c0634..f227fc78 100644 --- a/examples/dioxus/Cargo.toml +++ b/examples/dioxus/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" before-packaging-command = "dx build --platform desktop --release" product-name = "Dioxus example" identifier = "com.dioxus.example" -resources = ["src/**/*"] +resources = ["src/**/*", "Cargo.toml", "README.md"] [package.metadata.packager.deb] depends = ["libgtk-3-0", "libwebkit2gtk-4.1-0", "libayatana-appindicator3-1"] diff --git a/examples/dioxus/README.md b/examples/dioxus/README.md index a2d26886..c253eca4 100644 --- a/examples/dioxus/README.md +++ b/examples/dioxus/README.md @@ -1,13 +1,13 @@ ## Dioxus example -You need to install `dioxus-cli` first +1. install `dioxus-cli` first -```sh -cargo install dioxus-cli --locked -``` + ```sh + cargo install dioxus-cli --locked + ``` -then package the app +2. package the app -```sh -cargo r -p cargo-packager -- -p dioxus-example --relase -``` + ```sh + cargo r -p cargo-packager -- -p dioxus-example --relase + ``` diff --git a/examples/tauri/Cargo.toml b/examples/tauri/Cargo.toml index 0249c456..8d8825a7 100644 --- a/examples/tauri/Cargo.toml +++ b/examples/tauri/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" before-packaging-command = "cargo tauri build" product-name = "Tauri example" identifier = "com.tauri.example" -resources = ["src/**/*"] +resources = ["src/**/*", "Cargo.toml", "README.md"] icons = [ "icons/32x32.png", "icons/128x128.png", diff --git a/examples/tauri/README.md b/examples/tauri/README.md index 96233f56..19102bf0 100644 --- a/examples/tauri/README.md +++ b/examples/tauri/README.md @@ -1,13 +1,13 @@ ## Tauri example -You need to install `tauri-cli` first +1. install `tauri-cli` first -```sh -cargo install tauri-cli --version 2.0.0-alpha.10 -``` + ```sh + cargo install tauri-cli --version 2.0.0-alpha.10 + ``` -then package the app +2. package the app -```sh -cargo r -p cargo-packager -- -p tauri-example --relase -``` + ```sh + cargo r -p cargo-packager -- -p tauri-example --relase + ```