Repository: axodotdev/cargo-dist (rust)

Issue:
Homebrew: improve license field handling
While Homebrew uses SPDX licenses, it doesn't parse the full SPDX grammar. Rather, it [uses a Ruby DSL](https://docs.brew.sh/License-Guidelines#complex-spdx-license-expressions) to express the relationships between licenses. Cargo, meanwhile, uses SPDX strings directly, so that's what we pass through to the Homebrew formula. This is mostly harmless: it means Homebrew won't be able to provide detailed information about the license, but there's very little harm to it. If we felt like it though, we could use the [spdx crate](https://crates.io/crates/spdx) to parse the field from Cargo.toml and output the specific format that Homebrew expects. It's a nice-to-have, but not essential.

What we do now:

```ruby
license "MIT OR Apache-2.0"
```

What Homebrew expects:

```ruby
license any_of: ["MIT", "Apache-2.0"]
```

Pull request description:
Attempt conversion of SPDX license expressions to Homebrew DSL
Resolves #820.

## Problem
Currently, when publishing to a Homebrew tap, whatever string is in the crate's Cargo.toml license field (usually a SPDX license expression) gets passed into `license` in the Homebrew formula. However, rather than using SPDX syntax, Homebrew has its own ruby syntax for specifying licenses. So, when passed an SPDX expression, Homebrew:
- Cannot provide as detailed information about the package's licensing.
- Throws errors when linting the formula with `brew audit`/`brew test-bot`.

## Solution
- Use the `spdx` crate to parse the SPDX license expression.
- Iterate through the resulting token stream to construct a string matching Homebrew's license domain-specific language.
- Pass the result into Homebrew's `license` command, falling back to the original license string if there are issues parsing.

## Scope
- This PR just handles OR/AND operators, as the `spdx` crate has not yet implemented v3 of the spec. This means that the WITH operator is not yet covered, and neither are lower-case versions of the operators.
  - `spdx` issue for implementing spec v3 is [here](https://github.com/EmbarkStudios/spdx/issues/68).
- This PR formats everything on one line- in combination with [PR 1340](https://github.com/axodotdev/cargo-dist/pull/1340) this shouldn't be an issue, as it will be auto-formatted.

## Testing
I added tests for the conversion utility function, tested the output in a Homebrew formula, and I inspected and updated the test snapshots. This PR could certainly use some additional testing from someone with deeper familiarity with the project.

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 2fb913e2..e22b9d5d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -406,6 +406,7 @@ dependencies = [
  "sha2",
  "sha3",
  "similar",
+ "spdx",
  "tar",
  "temp-dir",
  "thiserror",
@@ -2541,6 +2542,15 @@ dependencies = [
  "windows-sys 0.52.0",
 ]
 
+[[package]]
+name = "spdx"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47317bbaf63785b53861e1ae2d11b80d6b624211d42cb20efcd210ee6f8a14bc"
+dependencies = [
+ "smallvec",
+]
+
 [[package]]
 name = "spin"
 version = "0.9.8"
diff --git a/Cargo.toml b/Cargo.toml
index 60a7a5a3..96033428 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -64,6 +64,7 @@ url = "2.5.0"
 parse-changelog = "0.6.8"
 schemars = "0.8.21"
 serde_yml = "0.0.10"
+spdx = "0.10.6"
 
 [workspace.metadata.release]
 shared-version = true
diff --git a/cargo-dist/Cargo.toml b/cargo-dist/Cargo.toml
index 80431035..115e784a 100644
--- a/cargo-dist/Cargo.toml
+++ b/cargo-dist/Cargo.toml
@@ -65,6 +65,7 @@ temp-dir.workspace = true
 sha3.workspace = true
 blake2.workspace = true
 serde_yml.workspace = true
+spdx.workspace = true
 
 [dev-dependencies]
 homedir.workspace = true
diff --git a/cargo-dist/src/backend/installer/homebrew.rs b/cargo-dist/src/backend/installer/homebrew.rs
index 0a64446d..044e199e 100644
--- a/cargo-dist/src/backend/installer/homebrew.rs
+++ b/cargo-dist/src/backend/installer/homebrew.rs
@@ -3,6 +3,10 @@
 use axoasset::LocalAsset;
 use cargo_dist_schema::DistManifest;
 use serde::Serialize;
+use spdx::{
+    expression::{ExprNode, Operator},
+    Expression, ParseError,
+};
 
 use super::InstallerInfo;
 use crate::{
@@ -178,9 +182,66 @@ pub fn to_class_case(app_name: &str) -> String {
     chars.iter().collect()
 }
 
+/// Converts SPDX license string into Homebrew Ruby DSL
+// Homebrew DSL reference: https://docs.brew.sh/License-Guidelines
+pub fn to_homebrew_license_format(app_license: &str) -> Result<String, ParseError> {
+    let spdx = Expression::parse(app_license)?;
+    let mut spdx = spdx.iter().peekable();
+    let mut buffer: Vec<String> = vec![];
+
+    while let Some(token) = spdx.next() {
+        match token {
+            ExprNode::Req(req) => {
+                // If token is a license, push to the buffer as-is for next operator or end.
+                let requirement = format!("\"{}\"", req.req);
+                buffer.push(requirement);
+            }
+            ExprNode::Op(op) => {
+                // If token is an operation, group operands in buffer into all_of/any_of clause.
+                // Operations are postfix, so we pop off the previous two elements and combine.
+                let second_operand = buffer.pop().expect("Operator missing first operand.");
+                let first_operand = buffer.pop().expect("Operator missing second operand.");
+                let mut combined = format!("{}, {}", first_operand, second_operand);
+
+                // If the operations that immediately follow are the same as the current operation,
+                // squash their operands into the same all_of/any_of clause.
+                while let Some(ExprNode::Op(next_op)) = spdx.peek() {
+                    if next_op != op {
+                        break;
+                    }
+                    let _ = spdx.next();
+                    let operand = buffer.pop().expect("Operator missing first operand.");
+                    combined = format!("{}, {}", operand, combined);
+                }
+
+                // Use corresponding homebrew DSL keyword and square bracket the list of licenses.
+                let operation = match op {
+                    Operator::And => "all_of",
+                    Operator::Or => "any_of",
+                };
+                let mut enclosed = format!("{operation}: [{combined}]");
+
+                // Only wrap all_of/any_of clause in brackets if it is nested within an outer clause.
+                if spdx.peek().is_some() {
+                    enclosed = format!("{{ {enclosed} }}");
+                }
+
+                // Push clause back onto the buffer, as it might be an operand in another clause.
+                buffer.push(enclosed);
+            }
+        }
+    }
+
+    // After all tokens have been iterated through, if the SPDX expression is well-formed, there
+    // should only be a single element left in the buffer: a single license or outermost clause.
+    Ok(buffer[0].clone())
+}
+
 #[cfg(test)]
 mod tests {
-    use super::to_class_case;
+    use spdx::ParseError;
+
+    use super::{to_class_case, to_homebrew_license_format};
 
     fn run_comparison(in_str: &str, expected: &str) {
         let out_str = to_class_case(in_str);
@@ -265,4 +326,99 @@ mod tests {
     fn ampersand_but_no_digit() {
         run_comparison("openssl@blah", "Openssl@blah");
     }
+
+    fn run_spdx_comparison(spdx_string: &str, homebrew_dsl: &str) {
+        let result = to_homebrew_license_format(spdx_string).unwrap();
+        assert_eq!(result, homebrew_dsl);
+    }
+
+    #[test]
+    fn spdx_single_license() {
+        run_spdx_comparison("MIT", r#""MIT""#);
+    }
+
+    #[test]
+    fn spdx_single_license_with_plus() {
+        run_spdx_comparison("Apache-2.0+", r#""Apache-2.0+""#);
+    }
+
+    #[test]
+    fn spdx_two_licenses_any() {
+        run_spdx_comparison("MIT OR 0BSD", r#"any_of: ["MIT", "0BSD"]"#);
+    }
+
+    #[test]
+    fn spdx_two_licenses_all() {
+        run_spdx_comparison("MIT AND 0BSD", r#"all_of: ["MIT", "0BSD"]"#);
+    }
+
+    #[test]
+    fn spdx_two_licenses_with_plus() {
+        run_spdx_comparison("MIT OR EPL-1.0+", r#"any_of: ["MIT", "EPL-1.0+"]"#);
+    }
+
+    #[test]
+    fn spdx_three_licenses() {
+        run_spdx_comparison(
+            "MIT OR Apache-2.0 OR CC-BY-4.0",
+            r#"any_of: ["MIT", "Apache-2.0", "CC-BY-4.0"]"#,
+        );
+    }
+
+    #[test]
+    fn spdx_three_licenses_or_and() {
+        run_spdx_comparison(
+            "MIT OR Apache-2.0 AND CC-BY-4.0",
+            r#"any_of: ["MIT", { all_of: ["Apache-2.0", "CC-BY-4.0"] }]"#,
+        );
+    }
+
+    #[test]
+    fn spdx_three_licenses_and_or() {
+        run_spdx_comparison(
+            "MIT AND Apache-2.0 OR CC-BY-4.0",
+            r#"any_of: [{ all_of: ["MIT", "Apache-2.0"] }, "CC-BY-4.0"]"#,
+        );
+    }
+
+    #[test]
+    fn spdx_parentheses() {
+        run_spdx_comparison(
+            "MIT OR (0BSD AND Zlib) OR curl",
+            r#"any_of: ["MIT", { all_of: ["0BSD", "Zlib"] }, "curl"]"#,
+        );
+    }
+
+    #[test]
+    fn spdx_nested_parentheses() {
+        run_spdx_comparison(
+            "MIT AND (Apache-2.0 OR (CC-BY-4.0 AND 0BSD))",
+            r#"all_of: ["MIT", { any_of: ["Apache-2.0", { all_of: ["CC-BY-4.0", "0BSD"] }] }]"#,
+        );
+    }
+
+    fn run_malformed_spdx(spdx_string: &str) {
+        let result = to_homebrew_license_format(spdx_string);
+        assert!(matches!(result, Err(ParseError { .. })));
+    }
+
+    #[test]
+    fn spdx_invalid_license_name() {
+        run_malformed_spdx("foo");
+    }
+
+    #[test]
+    fn spdx_invalid_just_operator() {
+        run_malformed_spdx("AND");
+    }
+
+    #[test]
+    fn spdx_invalid_dangling_operator() {
+        run_malformed_spdx("MIT OR");
+    }
+
+    #[test]
+    fn spdx_invalid_adjacent_operator() {
+        run_malformed_spdx("MIT AND OR Apache-2.0");
+    }
 }
diff --git a/cargo-dist/src/tasks.rs b/cargo-dist/src/tasks.rs
index 5bef7240..842c5a7e 100644
--- a/cargo-dist/src/tasks.rs
+++ b/cargo-dist/src/tasks.rs
@@ -65,6 +65,7 @@ use tracing::{info, warn};
 use crate::announce::{self, AnnouncementTag, TagMode};
 use crate::backend::ci::github::GithubCiInfo;
 use crate::backend::ci::CiInfo;
+use crate::backend::installer::homebrew::to_homebrew_license_format;
 use crate::config::{
     DependencyKind, DirtyMode, ExtraArtifact, GithubPermissionMap, GithubReleasePhase,
     LibraryStyle, ProductionMode, SystemDependencies,
@@ -2116,6 +2117,11 @@ impl<'pkg_graph> DistGraphBuilder<'pkg_graph> {
             release.app_desc.clone()
         };
         let app_license = release.app_license.clone();
+        let homebrew_dsl_license = app_license.as_ref().map(|app_license| {
+            // Parse SPDX license expression and convert to Homebrew's Ruby license DSL.
+            // If expression is malformed, fall back to plain input license string.
+            to_homebrew_license_format(app_license).unwrap_or(format!("\"{app_license}\""))
+        });
         let app_homepage_url = if release.app_homepage_url.is_none() {
             warn!("The Homebrew publish job is enabled but no homepage was specified\n  consider adding `homepage = ` to package in Cargo.toml");
             release.app_repository_url.clone()
@@ -2161,7 +2167,7 @@ impl<'pkg_graph> DistGraphBuilder<'pkg_graph> {
                 name: app_name,
                 formula_class: to_class_case(formula),
                 desc: app_desc,
-                license: app_license,
+                license: homebrew_dsl_license,
                 homepage: app_homepage_url,
                 tap,
                 dependencies,
diff --git a/cargo-dist/templates/installer/homebrew.rb.j2 b/cargo-dist/templates/installer/homebrew.rb.j2
index e919f09b..cff833d6 100644
--- a/cargo-dist/templates/installer/homebrew.rb.j2
+++ b/cargo-dist/templates/installer/homebrew.rb.j2
@@ -58,7 +58,7 @@ class {{ formula_class }} < Formula
   {%- endif %}
   {#- #}
   {%- if license %}
-  license "{{ license }}"
+  license {{ license }}
   {%- endif %}
   {%- if dependencies|length > 0 %}
   {% for dep in dependencies %}


Test patch:
diff --git a/cargo-dist/tests/snapshots/axolotlsay_abyss.snap b/cargo-dist/tests/snapshots/axolotlsay_abyss.snap
index 370c9354..8a85f67f 100644
--- a/cargo-dist/tests/snapshots/axolotlsay_abyss.snap
+++ b/cargo-dist/tests/snapshots/axolotlsay_abyss.snap
@@ -1157,7 +1157,7 @@ class Axolotlsay < Formula
       url "https://fake.axo.dev/faker/axolotlsay/fake-id-do-not-upload/axolotlsay-x86_64-unknown-linux-gnu.tar.gz"
     end
   end
-  license "MIT OR Apache-2.0"
+  license any_of: ["MIT", "Apache-2.0"]
 
   BINARY_ALIASES = {"aarch64-apple-darwin": {}, "x86_64-apple-darwin": {}, "x86_64-pc-windows-gnu": {}, "x86_64-unknown-linux-gnu": {}}
 
diff --git a/cargo-dist/tests/snapshots/axolotlsay_abyss_only.snap b/cargo-dist/tests/snapshots/axolotlsay_abyss_only.snap
index a6e590f3..a23ce7cd 100644
--- a/cargo-dist/tests/snapshots/axolotlsay_abyss_only.snap
+++ b/cargo-dist/tests/snapshots/axolotlsay_abyss_only.snap
@@ -1157,7 +1157,7 @@ class Axolotlsay < Formula
       url "https://fake.axo.dev/faker/axolotlsay/fake-id-do-not-upload/axolotlsay-x86_64-unknown-linux-gnu.tar.gz"
     end
   end
-  license "MIT OR Apache-2.0"
+  license any_of: ["MIT", "Apache-2.0"]
 
   BINARY_ALIASES = {"aarch64-apple-darwin": {}, "x86_64-apple-darwin": {}, "x86_64-pc-windows-gnu": {}, "x86_64-unknown-linux-gnu": {}}
 
diff --git a/cargo-dist/tests/snapshots/axolotlsay_alias.snap b/cargo-dist/tests/snapshots/axolotlsay_alias.snap
index 11b8cb09..2e1cd338 100644
--- a/cargo-dist/tests/snapshots/axolotlsay_alias.snap
+++ b/cargo-dist/tests/snapshots/axolotlsay_alias.snap
@@ -1169,7 +1169,7 @@ class Axolotlsay < Formula
       url "https://github.com/axodotdev/axolotlsay/releases/download/v0.2.2/axolotlsay-x86_64-unknown-linux-gnu.tar.gz"
     end
   end
-  license "MIT OR Apache-2.0"
+  license any_of: ["MIT", "Apache-2.0"]
 
   BINARY_ALIASES = {"aarch64-apple-darwin": {"axolotlsay": ["axolotlsay-link"]}, "x86_64-apple-darwin": {"axolotlsay": ["axolotlsay-link"]}, "x86_64-pc-windows-gnu": {"axolotlsay.exe": ["axolotlsay-link.exe"]}, "x86_64-unknown-linux-gnu": {"axolotlsay": ["axolotlsay-link"]}}
 
diff --git a/cargo-dist/tests/snapshots/axolotlsay_alias_ignores_missing_bins.snap b/cargo-dist/tests/snapshots/axolotlsay_alias_ignores_missing_bins.snap
index 5dac8e2b..651473fe 100644
--- a/cargo-dist/tests/snapshots/axolotlsay_alias_ignores_missing_bins.snap
+++ b/cargo-dist/tests/snapshots/axolotlsay_alias_ignores_missing_bins.snap
@@ -1169,7 +1169,7 @@ class Axolotlsay < Formula
       url "https://github.com/axodotdev/axolotlsay/releases/download/v0.2.2/axolotlsay-x86_64-unknown-linux-gnu.tar.gz"
     end
   end
-  license "MIT OR Apache-2.0"
+  license any_of: ["MIT", "Apache-2.0"]
 
   BINARY_ALIASES = {"aarch64-apple-darwin": {"nosuchbin": ["axolotlsay-link1", "axolotlsay-link2"]}, "x86_64-apple-darwin": {"nosuchbin": ["axolotlsay-link1", "axolotlsay-link2"]}, "x86_64-pc-windows-gnu": {"nosuchbin.exe": ["axolotlsay-link1.exe", "axolotlsay-link2.exe"]}, "x86_64-unknown-linux-gnu": {"nosuchbin": ["axolotlsay-link1", "axolotlsay-link2"]}}
 
diff --git a/cargo-dist/tests/snapshots/axolotlsay_basic.snap b/cargo-dist/tests/snapshots/axolotlsay_basic.snap
index 9675f995..33eaaf3e 100644
--- a/cargo-dist/tests/snapshots/axolotlsay_basic.snap
+++ b/cargo-dist/tests/snapshots/axolotlsay_basic.snap
@@ -1157,7 +1157,7 @@ class Axolotlsay < Formula
       url "https://github.com/axodotdev/axolotlsay/releases/download/v0.2.2/axolotlsay-x86_64-unknown-linux-gnu.tar.gz"
     end
   end
-  license "MIT OR Apache-2.0"
+  license any_of: ["MIT", "Apache-2.0"]
 
   BINARY_ALIASES = {"aarch64-apple-darwin": {}, "x86_64-apple-darwin": {}, "x86_64-pc-windows-gnu": {}, "x86_64-unknown-linux-gnu": {}}
 
diff --git a/cargo-dist/tests/snapshots/axolotlsay_basic_lies.snap b/cargo-dist/tests/snapshots/axolotlsay_basic_lies.snap
index 5d278aef..07ba4dea 100644
--- a/cargo-dist/tests/snapshots/axolotlsay_basic_lies.snap
+++ b/cargo-dist/tests/snapshots/axolotlsay_basic_lies.snap
@@ -1160,7 +1160,7 @@ class Axolotlsay < Formula
       sha256 "CENSORED"
     end
   end
-  license "MIT OR Apache-2.0"
+  license any_of: ["MIT", "Apache-2.0"]
 
   BINARY_ALIASES = {"aarch64-apple-darwin": {}, "x86_64-apple-darwin": {}, "x86_64-pc-windows-gnu": {}, "x86_64-unknown-linux-gnu": {}}
 
diff --git a/cargo-dist/tests/snapshots/axolotlsay_build_setup_steps.snap b/cargo-dist/tests/snapshots/axolotlsay_build_setup_steps.snap
index af7e62f5..7371a8fc 100644
--- a/cargo-dist/tests/snapshots/axolotlsay_build_setup_steps.snap
+++ b/cargo-dist/tests/snapshots/axolotlsay_build_setup_steps.snap
@@ -1157,7 +1157,7 @@ class Axolotlsay < Formula
       url "https://github.com/axodotdev/axolotlsay/releases/download/v0.2.2/axolotlsay-x86_64-unknown-linux-gnu.tar.gz"
     end
   end
-  license "MIT OR Apache-2.0"
+  license any_of: ["MIT", "Apache-2.0"]
 
   BINARY_ALIASES = {"aarch64-apple-darwin": {}, "x86_64-apple-darwin": {}, "x86_64-pc-windows-gnu": {}, "x86_64-unknown-linux-gnu": {}}
 
diff --git a/cargo-dist/tests/snapshots/axolotlsay_custom_formula.snap b/cargo-dist/tests/snapshots/axolotlsay_custom_formula.snap
index 023c31f7..35745e5f 100644
--- a/cargo-dist/tests/snapshots/axolotlsay_custom_formula.snap
+++ b/cargo-dist/tests/snapshots/axolotlsay_custom_formula.snap
@@ -20,7 +20,7 @@ class AxolotlBrew < Formula
       url "https://github.com/axodotdev/axolotlsay/releases/download/v0.2.2/axolotlsay-x86_64-unknown-linux-gnu.tar.gz"
     end
   end
-  license "MIT OR Apache-2.0"
+  license any_of: ["MIT", "Apache-2.0"]
 
   BINARY_ALIASES = {"aarch64-apple-darwin": {}, "x86_64-apple-darwin": {}, "x86_64-pc-windows-gnu": {}, "x86_64-unknown-linux-gnu": {}}
 
diff --git a/cargo-dist/tests/snapshots/axolotlsay_disable_source_tarball.snap b/cargo-dist/tests/snapshots/axolotlsay_disable_source_tarball.snap
index a2adc026..0a629674 100644
--- a/cargo-dist/tests/snapshots/axolotlsay_disable_source_tarball.snap
+++ b/cargo-dist/tests/snapshots/axolotlsay_disable_source_tarball.snap
@@ -1157,7 +1157,7 @@ class Axolotlsay < Formula
       url "https://github.com/axodotdev/axolotlsay/releases/download/v0.2.2/axolotlsay-x86_64-unknown-linux-gnu.tar.gz"
     end
   end
-  license "MIT OR Apache-2.0"
+  license any_of: ["MIT", "Apache-2.0"]
 
   BINARY_ALIASES = {"aarch64-apple-darwin": {}, "x86_64-apple-darwin": {}, "x86_64-pc-windows-gnu": {}, "x86_64-unknown-linux-gnu": {}}
 
diff --git a/cargo-dist/tests/snapshots/axolotlsay_edit_existing.snap b/cargo-dist/tests/snapshots/axolotlsay_edit_existing.snap
index 6724293e..3219a959 100644
--- a/cargo-dist/tests/snapshots/axolotlsay_edit_existing.snap
+++ b/cargo-dist/tests/snapshots/axolotlsay_edit_existing.snap
@@ -1157,7 +1157,7 @@ class Axolotlsay < Formula
       url "https://github.com/axodotdev/axolotlsay/releases/download/v0.2.2/axolotlsay-x86_64-unknown-linux-gnu.tar.gz"
     end
   end
-  license "MIT OR Apache-2.0"
+  license any_of: ["MIT", "Apache-2.0"]
 
   BINARY_ALIASES = {"aarch64-apple-darwin": {}, "x86_64-apple-darwin": {}, "x86_64-pc-windows-gnu": {}, "x86_64-unknown-linux-gnu": {}}
 
diff --git a/cargo-dist/tests/snapshots/axolotlsay_generic_workspace_basic.snap b/cargo-dist/tests/snapshots/axolotlsay_generic_workspace_basic.snap
index ec5ec9e4..6c9a74f7 100644
--- a/cargo-dist/tests/snapshots/axolotlsay_generic_workspace_basic.snap
+++ b/cargo-dist/tests/snapshots/axolotlsay_generic_workspace_basic.snap
@@ -2794,7 +2794,7 @@ class Axolotlsay < Formula
       url "https://github.com/axodotdev/axolotlsay-hybrid/releases/download/v0.10.2/axolotlsay-x86_64-unknown-linux-gnu.tar.xz"
     end
   end
-  license "MIT OR Apache-2.0"
+  license any_of: ["MIT", "Apache-2.0"]
 
   BINARY_ALIASES = {"aarch64-apple-darwin": {}, "x86_64-apple-darwin": {}, "x86_64-pc-windows-gnu": {}, "x86_64-unknown-linux-gnu": {}}
 
diff --git a/cargo-dist/tests/snapshots/axolotlsay_homebrew_packages.snap b/cargo-dist/tests/snapshots/axolotlsay_homebrew_packages.snap
index 2ab6269e..cfac9c6b 100644
--- a/cargo-dist/tests/snapshots/axolotlsay_homebrew_packages.snap
+++ b/cargo-dist/tests/snapshots/axolotlsay_homebrew_packages.snap
@@ -1157,7 +1157,7 @@ class Axolotlsay < Formula
       url "https://github.com/axodotdev/axolotlsay/releases/download/v0.2.2/axolotlsay-x86_64-unknown-linux-gnu.tar.gz"
     end
   end
-  license "MIT OR Apache-2.0"
+  license any_of: ["MIT", "Apache-2.0"]
 
   BINARY_ALIASES = {"aarch64-apple-darwin": {}, "x86_64-apple-darwin": {}, "x86_64-pc-windows-gnu": {}, "x86_64-unknown-linux-gnu": {}}
 
diff --git a/cargo-dist/tests/snapshots/axolotlsay_no_homebrew_publish.snap b/cargo-dist/tests/snapshots/axolotlsay_no_homebrew_publish.snap
index 935efd8b..0d9885b1 100644
--- a/cargo-dist/tests/snapshots/axolotlsay_no_homebrew_publish.snap
+++ b/cargo-dist/tests/snapshots/axolotlsay_no_homebrew_publish.snap
@@ -1157,7 +1157,7 @@ class Axolotlsay < Formula
       url "https://github.com/axodotdev/axolotlsay/releases/download/v0.2.2/axolotlsay-x86_64-unknown-linux-gnu.tar.gz"
     end
   end
-  license "MIT OR Apache-2.0"
+  license any_of: ["MIT", "Apache-2.0"]
 
   BINARY_ALIASES = {"aarch64-apple-darwin": {}, "x86_64-apple-darwin": {}, "x86_64-pc-windows-gnu": {}, "x86_64-unknown-linux-gnu": {}}
 
diff --git a/cargo-dist/tests/snapshots/axolotlsay_several_aliases.snap b/cargo-dist/tests/snapshots/axolotlsay_several_aliases.snap
index ddbfa4fe..45f54803 100644
--- a/cargo-dist/tests/snapshots/axolotlsay_several_aliases.snap
+++ b/cargo-dist/tests/snapshots/axolotlsay_several_aliases.snap
@@ -1169,7 +1169,7 @@ class Axolotlsay < Formula
       url "https://github.com/axodotdev/axolotlsay/releases/download/v0.2.2/axolotlsay-x86_64-unknown-linux-gnu.tar.gz"
     end
   end
-  license "MIT OR Apache-2.0"
+  license any_of: ["MIT", "Apache-2.0"]
 
   BINARY_ALIASES = {"aarch64-apple-darwin": {"axolotlsay": ["axolotlsay-link1", "axolotlsay-link2"]}, "x86_64-apple-darwin": {"axolotlsay": ["axolotlsay-link1", "axolotlsay-link2"]}, "x86_64-pc-windows-gnu": {"axolotlsay.exe": ["axolotlsay-link1.exe", "axolotlsay-link2.exe"]}, "x86_64-unknown-linux-gnu": {"axolotlsay": ["axolotlsay-link1", "axolotlsay-link2"]}}
 
diff --git a/cargo-dist/tests/snapshots/axolotlsay_updaters.snap b/cargo-dist/tests/snapshots/axolotlsay_updaters.snap
index 4fcd91a6..e3c33bec 100644
--- a/cargo-dist/tests/snapshots/axolotlsay_updaters.snap
+++ b/cargo-dist/tests/snapshots/axolotlsay_updaters.snap
@@ -1157,7 +1157,7 @@ class Axolotlsay < Formula
       url "https://github.com/axodotdev/axolotlsay/releases/download/v0.2.2/axolotlsay-x86_64-unknown-linux-gnu.tar.gz"
     end
   end
-  license "MIT OR Apache-2.0"
+  license any_of: ["MIT", "Apache-2.0"]
 
   BINARY_ALIASES = {"aarch64-apple-darwin": {}, "x86_64-apple-darwin": {}, "x86_64-pc-windows-gnu": {}, "x86_64-unknown-linux-gnu": {}}
 
diff --git a/cargo-dist/tests/snapshots/axolotlsay_user_global_build_job.snap b/cargo-dist/tests/snapshots/axolotlsay_user_global_build_job.snap
index 58c4427a..478d7539 100644
--- a/cargo-dist/tests/snapshots/axolotlsay_user_global_build_job.snap
+++ b/cargo-dist/tests/snapshots/axolotlsay_user_global_build_job.snap
@@ -1157,7 +1157,7 @@ class Axolotlsay < Formula
       url "https://github.com/axodotdev/axolotlsay/releases/download/v0.2.2/axolotlsay-x86_64-unknown-linux-gnu.tar.gz"
     end
   end
-  license "MIT OR Apache-2.0"
+  license any_of: ["MIT", "Apache-2.0"]
 
   BINARY_ALIASES = {"aarch64-apple-darwin": {}, "x86_64-apple-darwin": {}, "x86_64-pc-windows-gnu": {}, "x86_64-unknown-linux-gnu": {}}
 
diff --git a/cargo-dist/tests/snapshots/axolotlsay_user_host_job.snap b/cargo-dist/tests/snapshots/axolotlsay_user_host_job.snap
index 11071f75..4899475e 100644
--- a/cargo-dist/tests/snapshots/axolotlsay_user_host_job.snap
+++ b/cargo-dist/tests/snapshots/axolotlsay_user_host_job.snap
@@ -1157,7 +1157,7 @@ class Axolotlsay < Formula
       url "https://github.com/axodotdev/axolotlsay/releases/download/v0.2.2/axolotlsay-x86_64-unknown-linux-gnu.tar.gz"
     end
   end
-  license "MIT OR Apache-2.0"
+  license any_of: ["MIT", "Apache-2.0"]
 
   BINARY_ALIASES = {"aarch64-apple-darwin": {}, "x86_64-apple-darwin": {}, "x86_64-pc-windows-gnu": {}, "x86_64-unknown-linux-gnu": {}}
 
diff --git a/cargo-dist/tests/snapshots/axolotlsay_user_local_build_job.snap b/cargo-dist/tests/snapshots/axolotlsay_user_local_build_job.snap
index 1bcb4f03..043e137a 100644
--- a/cargo-dist/tests/snapshots/axolotlsay_user_local_build_job.snap
+++ b/cargo-dist/tests/snapshots/axolotlsay_user_local_build_job.snap
@@ -1157,7 +1157,7 @@ class Axolotlsay < Formula
       url "https://github.com/axodotdev/axolotlsay/releases/download/v0.2.2/axolotlsay-x86_64-unknown-linux-gnu.tar.gz"
     end
   end
-  license "MIT OR Apache-2.0"
+  license any_of: ["MIT", "Apache-2.0"]
 
   BINARY_ALIASES = {"aarch64-apple-darwin": {}, "x86_64-apple-darwin": {}, "x86_64-pc-windows-gnu": {}, "x86_64-unknown-linux-gnu": {}}
 
diff --git a/cargo-dist/tests/snapshots/axolotlsay_user_plan_job.snap b/cargo-dist/tests/snapshots/axolotlsay_user_plan_job.snap
index 8d3011f9..7e738aa7 100644
--- a/cargo-dist/tests/snapshots/axolotlsay_user_plan_job.snap
+++ b/cargo-dist/tests/snapshots/axolotlsay_user_plan_job.snap
@@ -1157,7 +1157,7 @@ class Axolotlsay < Formula
       url "https://github.com/axodotdev/axolotlsay/releases/download/v0.2.2/axolotlsay-x86_64-unknown-linux-gnu.tar.gz"
     end
   end
-  license "MIT OR Apache-2.0"
+  license any_of: ["MIT", "Apache-2.0"]
 
   BINARY_ALIASES = {"aarch64-apple-darwin": {}, "x86_64-apple-darwin": {}, "x86_64-pc-windows-gnu": {}, "x86_64-unknown-linux-gnu": {}}
 
diff --git a/cargo-dist/tests/snapshots/axolotlsay_user_publish_job.snap b/cargo-dist/tests/snapshots/axolotlsay_user_publish_job.snap
index 35692518..8b5a58f1 100644
--- a/cargo-dist/tests/snapshots/axolotlsay_user_publish_job.snap
+++ b/cargo-dist/tests/snapshots/axolotlsay_user_publish_job.snap
@@ -1157,7 +1157,7 @@ class Axolotlsay < Formula
       url "https://github.com/axodotdev/axolotlsay/releases/download/v0.2.2/axolotlsay-x86_64-unknown-linux-gnu.tar.gz"
     end
   end
-  license "MIT OR Apache-2.0"
+  license any_of: ["MIT", "Apache-2.0"]
 
   BINARY_ALIASES = {"aarch64-apple-darwin": {}, "x86_64-apple-darwin": {}, "x86_64-pc-windows-gnu": {}, "x86_64-unknown-linux-gnu": {}}
 
diff --git a/cargo-dist/tests/snapshots/install_path_cargo_home.snap b/cargo-dist/tests/snapshots/install_path_cargo_home.snap
index 1eee5fe6..4edab6f0 100644
--- a/cargo-dist/tests/snapshots/install_path_cargo_home.snap
+++ b/cargo-dist/tests/snapshots/install_path_cargo_home.snap
@@ -1157,7 +1157,7 @@ class Axolotlsay < Formula
       url "https://github.com/axodotdev/axolotlsay/releases/download/v0.2.2/axolotlsay-x86_64-unknown-linux-gnu.tar.gz"
     end
   end
-  license "MIT OR Apache-2.0"
+  license any_of: ["MIT", "Apache-2.0"]
 
   BINARY_ALIASES = {"aarch64-apple-darwin": {}, "x86_64-apple-darwin": {}, "x86_64-pc-windows-gnu": {}, "x86_64-unknown-linux-gnu": {}}
 
diff --git a/cargo-dist/tests/snapshots/install_path_env_no_subdir.snap b/cargo-dist/tests/snapshots/install_path_env_no_subdir.snap
index cb5998e3..d3749793 100644
--- a/cargo-dist/tests/snapshots/install_path_env_no_subdir.snap
+++ b/cargo-dist/tests/snapshots/install_path_env_no_subdir.snap
@@ -1140,7 +1140,7 @@ class Axolotlsay < Formula
       url "https://github.com/axodotdev/axolotlsay/releases/download/v0.2.2/axolotlsay-x86_64-unknown-linux-gnu.tar.gz"
     end
   end
-  license "MIT OR Apache-2.0"
+  license any_of: ["MIT", "Apache-2.0"]
 
   BINARY_ALIASES = {"aarch64-apple-darwin": {}, "x86_64-apple-darwin": {}, "x86_64-pc-windows-gnu": {}, "x86_64-unknown-linux-gnu": {}}
 
diff --git a/cargo-dist/tests/snapshots/install_path_env_subdir.snap b/cargo-dist/tests/snapshots/install_path_env_subdir.snap
index 996d24d1..d74cab39 100644
--- a/cargo-dist/tests/snapshots/install_path_env_subdir.snap
+++ b/cargo-dist/tests/snapshots/install_path_env_subdir.snap
@@ -1140,7 +1140,7 @@ class Axolotlsay < Formula
       url "https://github.com/axodotdev/axolotlsay/releases/download/v0.2.2/axolotlsay-x86_64-unknown-linux-gnu.tar.gz"
     end
   end
-  license "MIT OR Apache-2.0"
+  license any_of: ["MIT", "Apache-2.0"]
 
   BINARY_ALIASES = {"aarch64-apple-darwin": {}, "x86_64-apple-darwin": {}, "x86_64-pc-windows-gnu": {}, "x86_64-unknown-linux-gnu": {}}
 
diff --git a/cargo-dist/tests/snapshots/install_path_env_subdir_space.snap b/cargo-dist/tests/snapshots/install_path_env_subdir_space.snap
index c7f483a4..0a8b540c 100644
--- a/cargo-dist/tests/snapshots/install_path_env_subdir_space.snap
+++ b/cargo-dist/tests/snapshots/install_path_env_subdir_space.snap
@@ -1140,7 +1140,7 @@ class Axolotlsay < Formula
       url "https://github.com/axodotdev/axolotlsay/releases/download/v0.2.2/axolotlsay-x86_64-unknown-linux-gnu.tar.gz"
     end
   end
-  license "MIT OR Apache-2.0"
+  license any_of: ["MIT", "Apache-2.0"]
 
   BINARY_ALIASES = {"aarch64-apple-darwin": {}, "x86_64-apple-darwin": {}, "x86_64-pc-windows-gnu": {}, "x86_64-unknown-linux-gnu": {}}
 
diff --git a/cargo-dist/tests/snapshots/install_path_env_subdir_space_deeper.snap b/cargo-dist/tests/snapshots/install_path_env_subdir_space_deeper.snap
index 29d307ae..50ee60bd 100644
--- a/cargo-dist/tests/snapshots/install_path_env_subdir_space_deeper.snap
+++ b/cargo-dist/tests/snapshots/install_path_env_subdir_space_deeper.snap
@@ -1140,7 +1140,7 @@ class Axolotlsay < Formula
       url "https://github.com/axodotdev/axolotlsay/releases/download/v0.2.2/axolotlsay-x86_64-unknown-linux-gnu.tar.gz"
     end
   end
-  license "MIT OR Apache-2.0"
+  license any_of: ["MIT", "Apache-2.0"]
 
   BINARY_ALIASES = {"aarch64-apple-darwin": {}, "x86_64-apple-darwin": {}, "x86_64-pc-windows-gnu": {}, "x86_64-unknown-linux-gnu": {}}
 
diff --git a/cargo-dist/tests/snapshots/install_path_fallback_no_env_var_set.snap b/cargo-dist/tests/snapshots/install_path_fallback_no_env_var_set.snap
index 2e93f217..cba8955a 100644
--- a/cargo-dist/tests/snapshots/install_path_fallback_no_env_var_set.snap
+++ b/cargo-dist/tests/snapshots/install_path_fallback_no_env_var_set.snap
@@ -1152,7 +1152,7 @@ class Axolotlsay < Formula
       url "https://github.com/axodotdev/axolotlsay/releases/download/v0.2.2/axolotlsay-x86_64-unknown-linux-gnu.tar.gz"
     end
   end
-  license "MIT OR Apache-2.0"
+  license any_of: ["MIT", "Apache-2.0"]
 
   BINARY_ALIASES = {"aarch64-apple-darwin": {}, "x86_64-apple-darwin": {}, "x86_64-pc-windows-gnu": {}, "x86_64-unknown-linux-gnu": {}}
 
diff --git a/cargo-dist/tests/snapshots/install_path_home_subdir_deeper.snap b/cargo-dist/tests/snapshots/install_path_home_subdir_deeper.snap
index 0a57461b..93feed31 100644
--- a/cargo-dist/tests/snapshots/install_path_home_subdir_deeper.snap
+++ b/cargo-dist/tests/snapshots/install_path_home_subdir_deeper.snap
@@ -1140,7 +1140,7 @@ class Axolotlsay < Formula
       url "https://github.com/axodotdev/axolotlsay/releases/download/v0.2.2/axolotlsay-x86_64-unknown-linux-gnu.tar.gz"
     end
   end
-  license "MIT OR Apache-2.0"
+  license any_of: ["MIT", "Apache-2.0"]
 
   BINARY_ALIASES = {"aarch64-apple-darwin": {}, "x86_64-apple-darwin": {}, "x86_64-pc-windows-gnu": {}, "x86_64-unknown-linux-gnu": {}}
 
diff --git a/cargo-dist/tests/snapshots/install_path_home_subdir_min.snap b/cargo-dist/tests/snapshots/install_path_home_subdir_min.snap
index 4df93a3d..f9c806ad 100644
--- a/cargo-dist/tests/snapshots/install_path_home_subdir_min.snap
+++ b/cargo-dist/tests/snapshots/install_path_home_subdir_min.snap
@@ -1140,7 +1140,7 @@ class Axolotlsay < Formula
       url "https://github.com/axodotdev/axolotlsay/releases/download/v0.2.2/axolotlsay-x86_64-unknown-linux-gnu.tar.gz"
     end
   end
-  license "MIT OR Apache-2.0"
+  license any_of: ["MIT", "Apache-2.0"]
 
   BINARY_ALIASES = {"aarch64-apple-darwin": {}, "x86_64-apple-darwin": {}, "x86_64-pc-windows-gnu": {}, "x86_64-unknown-linux-gnu": {}}
 
diff --git a/cargo-dist/tests/snapshots/install_path_home_subdir_space.snap b/cargo-dist/tests/snapshots/install_path_home_subdir_space.snap
index 0c010930..391ffa21 100644
--- a/cargo-dist/tests/snapshots/install_path_home_subdir_space.snap
+++ b/cargo-dist/tests/snapshots/install_path_home_subdir_space.snap
@@ -1140,7 +1140,7 @@ class Axolotlsay < Formula
       url "https://github.com/axodotdev/axolotlsay/releases/download/v0.2.2/axolotlsay-x86_64-unknown-linux-gnu.tar.gz"
     end
   end
-  license "MIT OR Apache-2.0"
+  license any_of: ["MIT", "Apache-2.0"]
 
   BINARY_ALIASES = {"aarch64-apple-darwin": {}, "x86_64-apple-darwin": {}, "x86_64-pc-windows-gnu": {}, "x86_64-unknown-linux-gnu": {}}
 
diff --git a/cargo-dist/tests/snapshots/install_path_home_subdir_space_deeper.snap b/cargo-dist/tests/snapshots/install_path_home_subdir_space_deeper.snap
index 5aadfb97..714b8aba 100644
--- a/cargo-dist/tests/snapshots/install_path_home_subdir_space_deeper.snap
+++ b/cargo-dist/tests/snapshots/install_path_home_subdir_space_deeper.snap
@@ -1140,7 +1140,7 @@ class Axolotlsay < Formula
       url "https://github.com/axodotdev/axolotlsay/releases/download/v0.2.2/axolotlsay-x86_64-unknown-linux-gnu.tar.gz"
     end
   end
-  license "MIT OR Apache-2.0"
+  license any_of: ["MIT", "Apache-2.0"]
 
   BINARY_ALIASES = {"aarch64-apple-darwin": {}, "x86_64-apple-darwin": {}, "x86_64-pc-windows-gnu": {}, "x86_64-unknown-linux-gnu": {}}
 
diff --git a/cargo-dist/tests/snapshots/install_path_no_fallback_taken.snap b/cargo-dist/tests/snapshots/install_path_no_fallback_taken.snap
index 2c09cd14..5ee23212 100644
--- a/cargo-dist/tests/snapshots/install_path_no_fallback_taken.snap
+++ b/cargo-dist/tests/snapshots/install_path_no_fallback_taken.snap
@@ -1152,7 +1152,7 @@ class Axolotlsay < Formula
       url "https://github.com/axodotdev/axolotlsay/releases/download/v0.2.2/axolotlsay-x86_64-unknown-linux-gnu.tar.gz"
     end
   end
-  license "MIT OR Apache-2.0"
+  license any_of: ["MIT", "Apache-2.0"]
 
   BINARY_ALIASES = {"aarch64-apple-darwin": {}, "x86_64-apple-darwin": {}, "x86_64-pc-windows-gnu": {}, "x86_64-unknown-linux-gnu": {}}