Summary
nb install can1357/tap/omp downloads and verifies the correct macOS arm64 release asset, but treats the raw executable as a tarball and fails with tar: Error opening archive: Unrecognized archive format.
The same can1357/tap/omp formula installs normally with Homebrew; brew info shows 17.1.6 installed and linked on this machine.
Environment
- nanobrew:
v0.1.205
- macOS:
26.5.2 (25F84)
- architecture:
arm64
- nanobrew current main also inspected at
308986a79bb706b4924100ffd0f51a59c02a4f61
- tap formula inspected at
can1357/homebrew-tap@9dec8a6847a6c8607d707d9c6230d5b56051bd59
Reproduction
With no Nanobrew omp keg or install history:
$ nb install can1357/tap/omp
==> Resolving dependencies...
[183ms]
==> Installing 1 package(s) (0 already up to date):
omp 17.1.6
==> Downloading + installing 1 packages...
==> Downloading source for omp 17.1.6...
https://github.com/can1357/oh-my-pi/releases/download/v17.1.6/omp-darwin-arm64
==> Verifying SHA256...
==> Extracting source...
tar: Error opening archive: Unrecognized archive format
nb: extract command failed: error.CommandFailed
nb: omp: source build failed: error.ExtractFailed
✗ omp (source build failed)
nb: some packages failed to install
[112017ms]
failed: omp (source build failed)
nb: hint: check permissions with `nb doctor`
==> Done in 112205.4ms
The process also exited with status 0 despite reporting that the package failed to install.
Evidence / likely cause
The formula explicitly declares the release assets as uncompressed:
url "https://github.com/can1357/oh-my-pi/releases/download/v#{version}/omp-darwin-arm64",
using: :nounzip
Formula: https://github.com/can1357/homebrew-tap/blob/9dec8a6847a6c8607d707d9c6230d5b56051bd59/Formula/omp.rb#L7-L11
The file Nanobrew cached as omp-17.1.6.tar.gz is actually the expected raw executable:
$ file /opt/nanobrew/cache/tmp/omp-17.1.6.tar.gz
/opt/nanobrew/cache/tmp/omp-17.1.6.tar.gz: Mach-O 64-bit executable arm64
$ shasum -a 256 /opt/nanobrew/cache/tmp/omp-17.1.6.tar.gz
6c0c45af6c8c566ce28370c8570d9a709713277fad5420b44161e26a3d07be5d /opt/nanobrew/cache/tmp/omp-17.1.6.tar.gz
On current main:
Formula has no field for the formula URL's using: strategy:
|
pub const Formula = struct { |
|
name: []const u8, |
|
version: []const u8, |
|
revision: u32 = 0, |
|
rebuild: u32 = 0, |
|
desc: []const u8 = "", |
|
homepage: []const u8 = "", |
|
license: []const u8 = "", |
|
dependencies: []const []const u8 = &.{}, |
|
bottle_url: []const u8 = "", |
|
bottle_sha256: []const u8 = "", |
|
source_url: []const u8 = "", |
|
source_sha256: []const u8 = "", |
|
build_deps: []const []const u8 = &.{}, |
|
install_binaries: []const []const u8 = &.{}, |
|
caveats: []const u8 = "", |
|
post_install_defined: bool = false, |
- The tap parser captures
url and sha256, but not using: :nounzip:
|
// --- Top-level fields --- |
|
if (version == null) { |
|
if (extractQuotedAfter(line, "version")) |val| { |
|
version = val; |
|
} |
|
} |
|
if (desc == null) { |
|
if (extractQuotedAfter(line, "desc")) |val| { |
|
desc = val; |
|
} |
|
} |
|
if (source_url == null) { |
|
if (extractQuotedAfter(line, "url")) |val| { |
|
source_url = val; |
|
} |
|
} |
|
if (source_sha256 == null and !in_bottle) { |
|
if (extractQuotedAfter(line, "sha256")) |val| { |
|
source_sha256 = val; |
|
} |
- A URL without a known archive suffix is renamed with the default
.tar.gz suffix:
|
fn archiveSuffixFromUrl(url: []const u8) []const u8 { |
|
const path = blk: { |
|
if (std.mem.indexOfScalar(u8, url, '?')) |q| break :blk url[0..q]; |
|
break :blk url; |
|
}; |
|
const suffixes: []const []const u8 = &.{ |
|
".tar.xz", ".tar.bz2", ".tar.gz", ".txz", ".tbz2", ".tgz", ".zip", |
|
}; |
|
for (suffixes) |s| { |
|
if (path.len >= s.len and std.mem.endsWith(u8, path, s)) return s; |
|
} |
|
return ".tar.gz"; |
|
} |
- Every non-
.zip source is then passed to tar xf:
|
// 3. Extract |
|
var build_dir_buf: [512]u8 = undefined; |
|
const build_dir = std.fmt.bufPrint(&build_dir_buf, "{s}/{s}-{s}-build", .{ |
|
CACHE_TMP, formula.name, formula.version, |
|
}) catch return error.PathTooLong; |
|
|
|
printOut(lib_io, "==> Extracting source...\n", .{}); |
|
// Clean previous build dir |
|
std.Io.Dir.cwd().deleteTree(lib_io, build_dir) catch {}; |
|
std.Io.Dir.createDirAbsolute(lib_io, build_dir, .default_dir) catch {}; |
|
|
|
{ |
|
const argv: []const []const u8 = if (std.mem.endsWith(u8, tarball_path, ".zip")) |
|
&.{ "unzip", "-q", tarball_path, "-d", build_dir } |
|
else |
|
&.{ "tar", "xf", tarball_path, "-C", build_dir }; |
|
runCommand(lib_io, .inherit, argv) catch |err| { |
|
printErr(lib_io, "nb: extract command failed: {}\n", .{err}); |
|
return error.ExtractFailed; |
|
}; |
There is a second step needed for the end-to-end fix: this formula installs and renames the raw asset with:
bin.install Dir["omp-*"].first => "omp"
That non-literal install expression is currently treated as unparseable, which clears install_binaries:
|
// --- def install body: capture bin.install targets, skip everything else --- |
|
if (in_install_method) { |
|
if (endsWith(line, " do") or std.mem.eql(u8, line, "do")) { |
|
install_inner_depth += 1; |
|
continue; |
|
} |
|
if (std.mem.eql(u8, line, "end")) { |
|
if (install_inner_depth > 0) { |
|
install_inner_depth -= 1; |
|
} else { |
|
in_install_method = false; |
|
} |
|
continue; |
|
} |
|
if (isBinInstallLine(line)) { |
|
switch (extractBinInstallNames(line)) { |
|
.names => |names| for (names) |maybe_name| { |
|
const bin_name = maybe_name orelse break; |
|
try install_bins.append(alloc, try alloc.dupe(u8, bin_name)); |
|
}, |
|
.unparseable => bin_install_unparseable = true, |
|
.none => {}, |
|
} |
|
} |
|
continue; |
The generic fallback copies top-level files to the keg root, while the linker publishes executables from the keg's bin/ directory. Therefore, merely skipping tar would likely leave the executable unlinked.
Expected behavior
Nanobrew should:
- Preserve and honor
using: :nounzip for tap formula URLs.
- Stage the raw download with its upstream basename (
omp-darwin-arm64) instead of inventing a tar suffix.
- Apply the formula's install/rename semantics so the final keg contains executable
bin/omp.
- Complete successfully and make
omp available through the Nanobrew prefix.
- Return a nonzero status when installation fails.
An end-to-end regression test using this formula shape would catch both the extraction failure and the possible keg-root/unlinked follow-up failure.
Summary
nb install can1357/tap/ompdownloads and verifies the correct macOS arm64 release asset, but treats the raw executable as a tarball and fails withtar: Error opening archive: Unrecognized archive format.The same
can1357/tap/ompformula installs normally with Homebrew;brew infoshows 17.1.6 installed and linked on this machine.Environment
v0.1.20526.5.2(25F84)arm64308986a79bb706b4924100ffd0f51a59c02a4f61can1357/homebrew-tap@9dec8a6847a6c8607d707d9c6230d5b56051bd59Reproduction
With no Nanobrew
ompkeg or install history:The process also exited with status
0despite reporting that the package failed to install.Evidence / likely cause
The formula explicitly declares the release assets as uncompressed:
Formula: https://github.com/can1357/homebrew-tap/blob/9dec8a6847a6c8607d707d9c6230d5b56051bd59/Formula/omp.rb#L7-L11
The file Nanobrew cached as
omp-17.1.6.tar.gzis actually the expected raw executable:On current main:
Formulahas no field for the formula URL'susing:strategy:nanobrew/src/api/formula.zig
Lines 8 to 24 in 308986a
urlandsha256, but notusing: :nounzip:nanobrew/src/api/tap.zig
Lines 461 to 480 in 308986a
.tar.gzsuffix:nanobrew/src/build/source.zig
Lines 13 to 25 in 308986a
.zipsource is then passed totar xf:nanobrew/src/build/source.zig
Lines 110 to 129 in 308986a
There is a second step needed for the end-to-end fix: this formula installs and renames the raw asset with:
That non-literal install expression is currently treated as unparseable, which clears
install_binaries:nanobrew/src/api/tap.zig
Lines 329 to 353 in 308986a
The generic fallback copies top-level files to the keg root, while the linker publishes executables from the keg's
bin/directory. Therefore, merely skippingtarwould likely leave the executable unlinked.Expected behavior
Nanobrew should:
using: :nounzipfor tap formula URLs.omp-darwin-arm64) instead of inventing a tar suffix.bin/omp.ompavailable through the Nanobrew prefix.An end-to-end regression test using this formula shape would catch both the extraction failure and the possible keg-root/unlinked follow-up failure.