Skip to content

Commit

Permalink
feat(installers) add Linux support to Homebrew (#561)
Browse files Browse the repository at this point in the history
  • Loading branch information
dsully authored Nov 27, 2023
1 parent 99e6a77 commit 71a9a02
Show file tree
Hide file tree
Showing 20 changed files with 433 additions and 142 deletions.
48 changes: 36 additions & 12 deletions cargo-dist/src/backend/installer/homebrew.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,22 @@ pub struct HomebrewInstallerInfo {
pub desc: Option<String>,
/// A GitHub repository to write the formula to, in owner/name format
pub tap: Option<String>,
/// AMD64 artifact
pub x86_64: Option<ExecutableZipFragment>,
/// sha256 of AMD64 artifact
pub x86_64_sha256: Option<String>,
/// ARM64 artifact
pub arm64: Option<ExecutableZipFragment>,
/// sha256 of ARM64 artifact
pub arm64_sha256: Option<String>,
/// macOS AMD64 artifact
pub x86_64_macos: Option<ExecutableZipFragment>,
/// sha256 of macOS AMD64 artifact
pub x86_64_macos_sha256: Option<String>,
/// macOS ARM64 artifact
pub arm64_macos: Option<ExecutableZipFragment>,
/// sha256 of macOS ARM64 artifact
pub arm64_macos_sha256: Option<String>,
/// Linux AMD64 artifact
pub x86_64_linux: Option<ExecutableZipFragment>,
/// sha256 of Linux AMD64 artifact
pub x86_64_linux_sha256: Option<String>,
/// Linux ARM64 artifact
pub arm64_linux: Option<ExecutableZipFragment>,
/// sha256 of Linux ARM64 artifact
pub arm64_linux_sha256: Option<String>,
/// Generic installer info
pub inner: InstallerInfo,
/// Additional packages to specify as dependencies
Expand All @@ -62,18 +70,34 @@ pub(crate) fn write_homebrew_formula(

// Generate sha256 as late as possible; the artifacts might not exist
// earlier to do that.
if let Some(arm64_ref) = &info.arm64 {
if let Some(arm64_ref) = &info.arm64_macos {
let path = Utf8PathBuf::from(&graph.dist_dir).join(&arm64_ref.id);
if path.exists() {
let sha256 = generate_checksum(&crate::config::ChecksumStyle::Sha256, &path)?;
info.arm64_sha256 = Some(sha256);
info.arm64_macos_sha256 = Some(sha256);
}
}
if let Some(x86_64_ref) = &info.x86_64 {
if let Some(x86_64_ref) = &info.x86_64_macos {
let path = Utf8PathBuf::from(&graph.dist_dir).join(&x86_64_ref.id);
if path.exists() {
let sha256 = generate_checksum(&crate::config::ChecksumStyle::Sha256, &path)?;
info.x86_64_sha256 = Some(sha256);
info.x86_64_macos_sha256 = Some(sha256);
}
}

// Linuxbrew
if let Some(arm64_ref) = &info.arm64_linux {
let path = Utf8PathBuf::from(&graph.dist_dir).join(&arm64_ref.id);
if path.exists() {
let sha256 = generate_checksum(&crate::config::ChecksumStyle::Sha256, &path)?;
info.arm64_linux_sha256 = Some(sha256);
}
}
if let Some(x86_64_ref) = &info.x86_64_linux {
let path = Utf8PathBuf::from(&graph.dist_dir).join(&x86_64_ref.id);
if path.exists() {
let sha256 = generate_checksum(&crate::config::ChecksumStyle::Sha256, &path)?;
info.x86_64_linux_sha256 = Some(sha256);
}
}

Expand Down
122 changes: 107 additions & 15 deletions cargo-dist/src/tasks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1463,16 +1463,34 @@ impl<'pkg_graph> DistGraphBuilder<'pkg_graph> {
let hint = format!("brew install {}", install_target);
let desc = "Install prebuilt binaries via Homebrew".to_owned();

// If they have an x64 macos build but not an arm64 one, add a fallback entry
// to try to install x64 on arm64 and let rosetta2 deal with it.
// If they have an x64 macOS build but not an arm64 one, add a fallback entry
// to try to install x64 on arm64 and let Rosetta 2 deal with it.
//
// (This isn't strictly correct because rosetta2 isn't installed by default
// on macos, and the auto-installer only triggers for "real" apps, and not CLIs.
// (This isn't strictly correct because Rosetta 2 isn't installed by default
// on macOS, and the auto-installer only triggers for "real" apps, and not CLIs.
// Still, we think this is better than not trying at all.)
const X64_MACOS: &str = "x86_64-apple-darwin";
const ARM64_MACOS: &str = "aarch64-apple-darwin";
const ARM64_GNU: &str = "aarch64-unknown-linux-gnu";
const ARM64_MUSL: &str = "aarch64-unknown-linux-musl";
const X64_GNU: &str = "x86_64-unknown-linux-gnu";
const X64_MUSL: &str = "x86_64-unknown-linux-musl";
const X64_MUSL_STATIC: &str = "x86_64-unknown-linux-musl-static";
const X64_MUSL_DYNAMIC: &str = "x86_64-unknown-linux-musl-dynamic";
const ARM64_MUSL_STATIC: &str = "aarch64-unknown-linux-musl-static";
const ARM64_MUSL_DYNAMIC: &str = "aarch64-unknown-linux-musl-dynamic";

let mut has_x64_apple = false;
let mut has_arm_apple = false;
let mut has_x86_gnu_linux = false;
let mut has_arm_gnu_linux = false;
let mut has_x86_static_musl_linux = false;
let mut has_arm_static_musl_linux = false;

// Currently always false, someday these builds will exist
let has_x86_dynamic_musl_linux = false;
let has_arm_dynamic_musl_linux = false;

for &variant_idx in &release.variants {
let variant = self.variant(variant_idx);
let target = &variant.target;
Expand All @@ -1482,28 +1500,52 @@ impl<'pkg_graph> DistGraphBuilder<'pkg_graph> {
if target == ARM64_MACOS {
has_arm_apple = true;
}
if target == X64_GNU {
has_x86_gnu_linux = true;
}
if target == ARM64_GNU {
has_arm_gnu_linux = true;
}
if target == X64_MUSL {
has_x86_static_musl_linux = true;
}
if target == ARM64_MUSL {
has_arm_static_musl_linux = true;
}
}

let do_rosetta_fallback = has_x64_apple && !has_arm_apple;
let do_x86_gnu_to_musl_fallback = !has_x86_gnu_linux && has_x86_static_musl_linux;
let do_arm_gnu_to_musl_fallback = !has_arm_gnu_linux && has_arm_static_musl_linux;
let do_x86_musl_to_musl_fallback = has_x86_static_musl_linux && !has_x86_dynamic_musl_linux;
let do_arm_musl_to_musl_fallback = has_arm_static_musl_linux && !has_arm_dynamic_musl_linux;

let mut arm64 = None;
let mut x86_64 = None;
let mut arm64_macos = None;
let mut x86_64_macos = None;
let mut arm64_linux = None;
let mut x86_64_linux = None;

// Gather up the bundles the installer supports
let mut artifacts = vec![];
let mut target_triples = SortedSet::new();

for &variant_idx in &release.variants {
let variant = self.variant(variant_idx);
let target = &variant.target;
if target.contains("windows") || target.contains("linux-gnu") {

if target.contains("windows") {
continue;
}

// Compute the artifact zip this variant *would* make *if* it were built
// FIXME: this is a kind of hacky workaround for the fact that we don't have a good
// way to add artifacts to the graph and then say "ok but don't build it".
let (artifact, binaries) =
self.make_executable_zip_for_variant(to_release, variant_idx);

target_triples.insert(target.clone());
let fragment = ExecutableZipFragment {

let mut fragment = ExecutableZipFragment {
id: artifact.id,
target_triples: artifact.target_triples,
zip_style: artifact.archive.as_ref().unwrap().zip_style,
Expand All @@ -1514,19 +1556,65 @@ impl<'pkg_graph> DistGraphBuilder<'pkg_graph> {
};

if target == X64_MACOS {
x86_64 = Some(fragment.clone());
x86_64_macos = Some(fragment.clone());
}

if target == ARM64_MACOS {
arm64 = Some(fragment.clone());
arm64_macos = Some(fragment.clone());
}

if target == X64_GNU {
x86_64_linux = Some(fragment.clone());
}

if target == ARM64_GNU {
arm64_linux = Some(fragment.clone());
}

if do_rosetta_fallback && target == X64_MACOS {
// Copy the info but respecify it to be arm64 macos
let mut arm_fragment = fragment.clone();
arm_fragment.target_triples = vec![ARM64_MACOS.to_owned()];
artifacts.push(arm_fragment.clone());
arm64 = Some(arm_fragment);
arm64_macos = Some(arm_fragment);
}

// musl-static is actually kind of a fake triple we've invented
// to let us specify which is which; we want to ensure it exists
// for the installer to act on
if target == X64_MUSL {
fragment.target_triples = vec![X64_MUSL_STATIC.to_owned()];
}

if target == ARM64_MUSL {
fragment.target_triples = vec![ARM64_MUSL_STATIC.to_owned()];
}

// Copy the info but lie that it's actually glibc
if do_x86_gnu_to_musl_fallback && target == X64_MUSL {
let mut musl_fragment = fragment.clone();
musl_fragment.target_triples = vec![X64_GNU.to_owned()];
artifacts.push(musl_fragment);
}

if do_x86_musl_to_musl_fallback && target == X64_MUSL {
let mut musl_fragment = fragment.clone();
musl_fragment.target_triples = vec![X64_MUSL_DYNAMIC.to_owned()];
artifacts.push(musl_fragment);
}

if do_arm_gnu_to_musl_fallback && target == ARM64_MUSL {
let mut musl_fragment = fragment.clone();
musl_fragment.target_triples = vec![ARM64_GNU.to_owned()];
artifacts.push(musl_fragment);
}

if do_arm_musl_to_musl_fallback && target == ARM64_MUSL {
let mut musl_fragment = fragment.clone();
musl_fragment.target_triples = vec![ARM64_MUSL_DYNAMIC.to_owned()];
artifacts.push(musl_fragment);
}

artifacts.push(fragment);
}
if artifacts.is_empty() {
Expand Down Expand Up @@ -1567,10 +1655,14 @@ impl<'pkg_graph> DistGraphBuilder<'pkg_graph> {
required_binaries: FastMap::new(),
checksum: None,
kind: ArtifactKind::Installer(InstallerImpl::Homebrew(HomebrewInstallerInfo {
arm64,
arm64_sha256: None,
x86_64,
x86_64_sha256: None,
arm64_macos,
arm64_macos_sha256: None,
x86_64_macos,
x86_64_macos_sha256: None,
arm64_linux,
arm64_linux_sha256: None,
x86_64_linux,
x86_64_linux_sha256: None,
name: app_name,
formula_class: formula_name,
desc: app_desc,
Expand Down
68 changes: 49 additions & 19 deletions cargo-dist/templates/installer/homebrew.rb.j2
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,56 @@ class {{ formula_class }} < Formula
{%- if homepage %}
homepage "{{ homepage }}"
{%- endif %}
version "{{ inner.app_version }}"
{#- #}
{#- If arm64/x86_64 builds are the same, skip the Hardware::CPU.type if statement #}
{%- if arm64.id == x86_64.id %}
url "{{ inner.base_url }}/{{ arm64.id }}"
{%- if arm64_sha256 %}
sha256 "{{ arm64_sha256 }}"
{%- if arm64_macos.id or x86_64_macos.id %}
on_macos do
{#- If arm64/x86_64 builds are the same, skip the Hardware::CPU.type if statement #}
{%- if arm64_macos.id == x86_64_macos.id %}
url "{{ inner.base_url }}/{{ arm64_macos.id }}"
{%- if arm64_macos_sha256 %}
sha256 "{{ arm64_macos_sha256 }}"
{%- endif %}
{%- else %}
{%- if arm64_macos.id %}
on_arm do
url "{{ inner.base_url }}/{{ arm64_macos.id }}"
{%- if arm64_macos_sha256 %}
sha256 "{{ arm64_macos_sha256 }}"
{%- endif %}
end
{%- endif %}
{%- if x86_64_macos.id %}
on_intel do
url "{{ inner.base_url }}/{{ x86_64_macos.id }}"
{%- if x86_64_macos_sha256 %}
sha256 "{{ x86_64_macos_sha256 }}"
{%- endif %}
end
{%- endif %}
{%- endif %}
end
{%- endif %}
{%- else %}
if Hardware::CPU.type == :arm
url "{{ inner.base_url }}/{{ arm64.id }}"
{%- if arm64_sha256 %}
sha256 "{{ arm64_sha256 }}"
{#- #}
{%- if arm64_linux.id or x86_64_linux.id %}
on_linux do
{%- if arm64_linux.id %}
on_arm do
url "{{ inner.base_url }}/{{ arm64_linux.id }}"
{%- if arm64_linux %}
sha256 "{{ arm64_linux_sha256 }}"
{%- endif %}
end
{%- endif %}
else
url "{{ inner.base_url }}/{{ x86_64.id }}"
{%- if x86_64_sha256 %}
sha256 "{{ x86_64_sha256 }}"
{%- if x86_64_linux.id %}
on_intel do
url "{{ inner.base_url }}/{{ x86_64_linux.id }}"
{%- if x86_64_linux_sha256 %}
sha256 "{{ x86_64_linux_sha256 }}"
{%- endif %}
end
{%- endif %}
end
version "{{ inner.app_version }}"
{%- endif %}
{#- #}
{%- if license %}
Expand All @@ -38,13 +68,13 @@ class {{ formula_class }} < Formula

def install
{#- Like the URL case above, write out a single install line in the case that the binary artifacts are the same across architectures #}
{%- if arm64.binaries == x86_64.binaries %}
bin.install {% for binary in arm64.binaries %}"{{ binary }}"{{ ", " if not loop.last else "" }}{% endfor %}
{%- if arm64_macos.binaries == x86_64_macos.binaries %}
bin.install {% for binary in arm64_macos.binaries %}"{{ binary }}"{{ ", " if not loop.last else "" }}{% endfor %}
{%- else %}
if Hardware::CPU.type == :arm
bin.install {% for binary in arm64.binaries %}"{{ binary }}"{{ ", " if not loop.last else "" }}{% endfor %}
bin.install {% for binary in arm64_macos.binaries %}"{{ binary }}"{{ ", " if not loop.last else "" }}{% endfor %}
else
bin.install {% for binary in x86_64.binaries %}"{{ binary }}"{{ ", " if not loop.last else "" }}{% endfor %}
bin.install {% for binary in x86_64_macos.binaries %}"{{ binary }}"{{ ", " if not loop.last else "" }}{% endfor %}
end
{%- endif %}

Expand Down
21 changes: 15 additions & 6 deletions cargo-dist/tests/snapshots/akaikatana_basic.snap
Original file line number Diff line number Diff line change
Expand Up @@ -720,12 +720,20 @@ download_binary_and_run_installer "$@" || exit 1

================ formula.rb ================
class AkaikatanaRepack < Formula
if Hardware::CPU.type == :arm
url "https://github.com/mistydemeo/akaikatana-repack/releases/download/v0.2.0/akaikatana-repack-aarch64-apple-darwin.tar.xz"
else
url "https://github.com/mistydemeo/akaikatana-repack/releases/download/v0.2.0/akaikatana-repack-x86_64-apple-darwin.tar.xz"
end
version "0.2.0"
on_macos do
on_arm do
url "https://github.com/mistydemeo/akaikatana-repack/releases/download/v0.2.0/akaikatana-repack-aarch64-apple-darwin.tar.xz"
end
on_intel do
url "https://github.com/mistydemeo/akaikatana-repack/releases/download/v0.2.0/akaikatana-repack-x86_64-apple-darwin.tar.xz"
end
end
on_linux do
on_intel do
url "https://github.com/mistydemeo/akaikatana-repack/releases/download/v0.2.0/akaikatana-repack-x86_64-unknown-linux-gnu.tar.xz"
end
end
license "GPL-2.0-or-later"

def install
Expand Down Expand Up @@ -1255,7 +1263,8 @@ Install-Binary "$Args"
"kind": "installer",
"target_triples": [
"aarch64-apple-darwin",
"x86_64-apple-darwin"
"x86_64-apple-darwin",
"x86_64-unknown-linux-gnu"
],
"install_hint": "brew install mistydemeo/homebrew-formulae/akaikatana-repack",
"description": "Install prebuilt binaries via Homebrew"
Expand Down
Loading

0 comments on commit 71a9a02

Please sign in to comment.