Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions hyperdb-bootstrap/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/).

## [Unreleased]

### Fixed

- **Download `hyperd` from the Java API bundle instead of the C++ bundle.**
Tableau's C++ `macos-arm64` zip ships an **x86_64** `hyperd` (an upstream
packaging defect), so on Apple Silicon the extracted `hyperd` only ran
under Rosetta. The Java `macos-arm64` bundle carries a native arm64
`hyperd`. The bundles share an identical URL template (only the
`java`/`cxx` token differs) and an identical internal layout
(`lib/hyper/hyperd`), so the switch is confined to the URL token and the
pinned per-platform sha256s in `hyperd-version.toml`. The other three
platforms (`macos-x86_64`, `linux-x86_64`, `windows-x86_64`) are
unaffected in architecture but now also come from the Java bundle for
consistency.

## [0.1.1] - 2026-05-13

### Added
Expand Down
12 changes: 10 additions & 2 deletions hyperdb-bootstrap/README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
# hyperdb-bootstrap

Download and install the `hyperd` executable from Tableau's Hyper C++ API
Download and install the `hyperd` executable from Tableau's Hyper Java API
release packages. Ships as both a CLI binary and a library.

The `hyperd` server isn't on crates.io — it's a prebuilt binary distributed
inside Tableau's Hyper C++ API zips at
inside Tableau's Hyper API zips at
<https://tableau.github.io/hyper-db/docs/releases>. This crate automates
the "download the right zip for your platform, extract `hyperd` out of
`lib/hyper/`, put it somewhere useful" step so contributors and CI can
bootstrap with a single command.

> **Why the Java bundle, not C++?** Tableau publishes `hyperd` inside both
> the C++ and Java API zips. The C++ `macos-arm64` zip currently ships an
> **x86_64** `hyperd` (an upstream packaging defect), so on Apple Silicon it
> would only run under Rosetta. The Java `macos-arm64` zip carries a native
> arm64 `hyperd`. The two bundles are otherwise identical for our purposes
> (same URL template, same `lib/hyper/hyperd` layout), so this crate pulls
> from the Java bundle on every platform for consistency.

## Install

```bash
Expand Down
18 changes: 12 additions & 6 deletions hyperdb-bootstrap/hyperd-version.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
# Pinned Hyper C++ API release used by hyperd-bootstrap.
# Pinned Hyper **Java** API release used by hyperd-bootstrap.
#
# We pull `hyperd` from the Java binding's bundle, NOT the C++ one: the
# C++ macos-arm64 zip ships an x86_64 `hyperd` (upstream packaging defect),
# which only runs under Rosetta on Apple Silicon. The Java macos-arm64
# bundle carries a native arm64 `hyperd`. Same URL template and same
# internal layout (lib/hyper/hyperd) — only the binding token + sha256s differ.
#
# Bump these values (and sha256s) when upgrading. Contributors without
# an override get this exact release, so reproducibility depends on it.
Expand All @@ -7,9 +13,9 @@ build_id = "r2bfd835b"

# sha256 of each platform's .zip. Omit a platform to skip verification
# for that platform (not recommended). Compute with:
# shasum -a 256 tableauhyperapi-cxx-<platform>-release-main.<version>.<build_id>.zip
# shasum -a 256 tableauhyperapi-java-<platform>-release-main.<version>.<build_id>.zip
[sha256]
"macos-arm64" = "6cec0a90c7e8ddae2b0a623d551cdab4f085b0c6e4655713d6ce164ec20dabc7"
"macos-x86_64" = "d5d3dae60ce071aed45e534fb6a4fc6c7edce47e31d664d131af9f76d9b3a2aa"
"linux-x86_64" = "50f015ea2991afb9440fc93df0520c1b3a077dab609b010cbe1ce17685f44e20"
"windows-x86_64" = "918709daae78effa67bac804f8f6f45deea11056272577b73591590e11019f17"
"macos-arm64" = "2b0fa3fefcf4eba60f052e1cb51abfc32d8c84354274513763760f9549b45991"
"macos-x86_64" = "2fb7e58a449f5902e603f46ee7a73c70c85a2ef01e0c6435a26f893c2b9ee1f0"
"linux-x86_64" = "3d3fd2104f55f7fad832470592394dc78f350a03d52e89d36c5288b202dd0bc0"
"windows-x86_64" = "9dc4851d416e0e6e00f0367ee6b45fcd676e7ba3a110d4644e3bec871b9aa1de"
34 changes: 18 additions & 16 deletions hyperdb-bootstrap/src/extract.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
// Copyright (c) 2026, Salesforce, Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0 OR MIT

//! ZIP-archive extraction for the Hyper C++ API release bundle.
//! ZIP-archive extraction for the Hyper API release bundle.
//!
//! The upstream archive nests `hyperd` plus its shared libraries inside a
//! versioned top-level directory (e.g.
//! `tableauhyperapi-cxx-macos-arm64-release-main.0.0.24457.rc36858b6/`) and
//! `tableauhyperapi-java-macos-arm64-release-main.0.0.24457.rc36858b6/`) and
//! then under `lib/hyper/` on Linux/macOS or `bin/hyper/` on Windows. This
//! module flattens both layers so downstream consumers only see the
//! `hyperd` runtime files.
//! `hyperd` runtime files. The layout is identical across the Java and C++
//! bundles, so this extractor is agnostic to which binding we download
//! (we use Java — see `url.rs` for why).

use std::fs::{self, File};
use std::io;
Expand All @@ -17,7 +19,7 @@ use std::path::{Path, PathBuf};
use crate::Error;

/// Extract everything under `lib/hyper/` (or `bin/hyper/` on Windows) from
/// the Hyper C++ API zip into `dest_dir`, flattening the wrapper prefixes
/// the Hyper API zip into `dest_dir`, flattening the wrapper prefixes
/// away. Returns the list of extracted file paths relative to `dest_dir`.
///
/// # Errors
Expand Down Expand Up @@ -87,14 +89,14 @@ pub fn extract_hyperd(zip_path: &Path, dest_dir: &Path) -> Result<Vec<PathBuf>,
}

/// Return the path stripped of a leading `lib/hyper/` or `bin/hyper/` prefix,
/// or `None` if the entry is outside those directories. The Hyper C++ API zip
/// wraps everything in a top-level `tableauhyperapi-cxx-...` directory and
/// or `None` if the entry is outside those directories. The Hyper API zip
/// wraps everything in a top-level `tableauhyperapi-<binding>-...` directory and
/// nests the runtime under `lib/hyper/` (Linux/macOS) or `bin/hyper/`
/// (Windows) inside it.
fn strip_lib_hyper_prefix(path: &Path) -> Option<PathBuf> {
let mut comps = path.components();
// Skip one optional top-level wrapper component (e.g.
// `tableauhyperapi-cxx-macos-arm64-release-main.0.0.24457.rc36858b6`)
// `tableauhyperapi-java-macos-arm64-release-main.0.0.24457.rc36858b6`)
// before looking for the `lib/hyper` or `bin/hyper` pair.
let first = comps.next()?;
let (a, b) = if first.as_os_str() == "lib" || first.as_os_str() == "bin" {
Expand Down Expand Up @@ -126,29 +128,29 @@ mod tests {
);
// Real-world form: wrapped under a versioned top-level dir (Linux/macOS).
assert_eq!(
strip_lib_hyper_prefix(Path::new("tableauhyperapi-cxx-x/lib/hyper/hyperd")),
strip_lib_hyper_prefix(Path::new("tableauhyperapi-java-x/lib/hyper/hyperd")),
Some(PathBuf::from("hyperd"))
);
assert_eq!(
strip_lib_hyper_prefix(Path::new("tableauhyperapi-cxx-x/lib/hyper/sub/a.so")),
strip_lib_hyper_prefix(Path::new("tableauhyperapi-java-x/lib/hyper/sub/a.so")),
Some(PathBuf::from("sub/a.so"))
);
// Windows uses bin/hyper/ instead of lib/hyper/.
assert_eq!(
strip_lib_hyper_prefix(Path::new("tableauhyperapi-cxx-x/bin/hyper/hyperd.exe")),
strip_lib_hyper_prefix(Path::new("tableauhyperapi-java-x/bin/hyper/hyperd.exe")),
Some(PathBuf::from("hyperd.exe"))
);
assert_eq!(
strip_lib_hyper_prefix(Path::new("tableauhyperapi-cxx-x/bin/hyper/sub/extra.dll")),
strip_lib_hyper_prefix(Path::new("tableauhyperapi-java-x/bin/hyper/sub/extra.dll")),
Some(PathBuf::from("sub/extra.dll"))
);
// Anything outside lib/hyper or bin/hyper is dropped.
assert_eq!(
strip_lib_hyper_prefix(Path::new("tableauhyperapi-cxx-x/include/foo.hpp")),
strip_lib_hyper_prefix(Path::new("tableauhyperapi-java-x/include/foo.hpp")),
None
);
assert_eq!(
strip_lib_hyper_prefix(Path::new("tableauhyperapi-cxx-x/bin/tableauhyperapi.dll")),
strip_lib_hyper_prefix(Path::new("tableauhyperapi-java-x/bin/tableauhyperapi.dll")),
None
);
assert_eq!(strip_lib_hyper_prefix(Path::new("other/file")), None);
Expand All @@ -164,11 +166,11 @@ mod tests {
let mut zw = zip::ZipWriter::new(file);
let opts = zip::write::SimpleFileOptions::default();
// Mirror the real release zip's top-level wrapper dir.
zw.start_file("tableauhyperapi-cxx-fake/lib/hyper/hyperd", opts)?;
zw.start_file("tableauhyperapi-java-fake/lib/hyper/hyperd", opts)?;
zw.write_all(b"fake hyperd")?;
zw.start_file("tableauhyperapi-cxx-fake/lib/hyper/sub/extra.txt", opts)?;
zw.start_file("tableauhyperapi-java-fake/lib/hyper/sub/extra.txt", opts)?;
zw.write_all(b"extra")?;
zw.start_file("tableauhyperapi-cxx-fake/include/ignored.hpp", opts)?;
zw.start_file("tableauhyperapi-java-fake/include/ignored.hpp", opts)?;
zw.write_all(b"nope")?;
zw.finish()?;
}
Expand Down
2 changes: 1 addition & 1 deletion hyperdb-bootstrap/src/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ fn download_and_extract(

let url = build_download_url(release, platform);
let tmp = tempfile::tempdir().map_err(|source| Error::io("creating temp dir", source))?;
let zip_path = tmp.path().join("hyperapi-cxx.zip");
let zip_path = tmp.path().join("hyperapi-java.zip");
download_and_verify(&url, release.sha256_for(platform), &zip_path)?;
extract_hyperd(&zip_path, versioned_dir)?;
Ok(())
Expand Down
6 changes: 4 additions & 2 deletions hyperdb-bootstrap/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
// Copyright (c) 2026, Salesforce, Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0 OR MIT

//! Download and install the `hyperd` executable from Tableau's Hyper C++
//! API release packages.
//! Download and install the `hyperd` executable from Tableau's Hyper Java
//! API release packages. (The Java bundle is used rather than the C++ one
//! because the C++ `macos-arm64` zip ships an x86_64 `hyperd`; see the
//! `url` module for the full rationale.)
//!
//! The crate ships both a CLI binary (`hyperd-bootstrap`) and a small
//! library. The library is blocking (no async runtime required) and has
Expand Down
2 changes: 1 addition & 1 deletion hyperdb-bootstrap/src/platform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT

//! Host-platform detection and slug encoding for the four targets that
//! Tableau's Hyper C++ API ships binaries for.
//! Tableau's Hyper API ships binaries for.

use serde::{Deserialize, Serialize};
use std::fmt;
Expand Down
12 changes: 6 additions & 6 deletions hyperdb-bootstrap/src/scrape.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use crate::Error;
const RELEASES_URL: &str = "https://tableau.github.io/hyper-db/docs/releases";

/// Fetches the public releases page and returns the newest `PinnedRelease`
/// that has a C++ download for `platform`.
/// that has a Java download for `platform`.
///
/// The returned `PinnedRelease` has an empty SHA-256 map — scraping only
/// recovers the version + build id, never a digest.
Expand Down Expand Up @@ -58,10 +58,10 @@ fn parse_latest(html: &str, platform: Platform) -> Result<PinnedRelease, Error>
"no <h3>VERSION [DATE]</h3> heading found",
))?;

// For that version, find the C++ zip for the requested platform to
// For that version, find the Java zip for the requested platform to
// recover the build id.
let href_re = Regex::new(&format!(
r"tableauhyperapi-cxx-{plat}-release-main\.{ver}\.(rc[a-z0-9]+)\.zip",
r"tableauhyperapi-java-{plat}-release-main\.{ver}\.(rc[a-z0-9]+)\.zip",
plat = regex::escape(platform.slug()),
ver = regex::escape(&version),
))
Expand All @@ -71,7 +71,7 @@ fn parse_latest(html: &str, platform: Platform) -> Result<PinnedRelease, Error>
.and_then(|c| c.get(1))
.map(|m| m.as_str().to_string())
.ok_or(Error::ScrapeFailed(
"no matching cxx zip href for scraped version",
"no matching java zip href for scraped version",
))?;

Ok(PinnedRelease {
Expand All @@ -90,8 +90,8 @@ mod tests {
let html = r#"
<h3>0.0.24457 [February 12 2026]</h3>
<ul><li>Release notes</li></ul>
<a href="https://downloads.tableau.com/tssoftware//tableauhyperapi-cxx-macos-arm64-release-main.0.0.24457.rc36858b6.zip">C++ (macOS arm64)</a>
<a href="https://downloads.tableau.com/tssoftware//tableauhyperapi-cxx-linux-x86_64-release-main.0.0.24457.rc36858b6.zip">C++ (Linux)</a>
<a href="https://downloads.tableau.com/tssoftware//tableauhyperapi-java-macos-arm64-release-main.0.0.24457.rc36858b6.zip">Java (macOS arm64)</a>
<a href="https://downloads.tableau.com/tssoftware//tableauhyperapi-java-linux-x86_64-release-main.0.0.24457.rc36858b6.zip">Java (Linux)</a>
<h3>0.0.20000 [January 1 2025]</h3>
"#;
let release = parse_latest(html, Platform::MacosArm64).unwrap();
Expand Down
16 changes: 12 additions & 4 deletions hyperdb-bootstrap/src/url.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
// Copyright (c) 2026, Salesforce, Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0 OR MIT

//! Builds canonical download URLs for Tableau's public Hyper C++ API
//! Builds canonical download URLs for Tableau's public Hyper **Java** API
//! release bundles.
//!
//! We deliberately use the Java binding's bundle rather than the C++ one:
//! the C++ `macos-arm64` zip ships an **x86_64** `hyperd` (an upstream
//! packaging defect), so on Apple Silicon it would only run under Rosetta.
//! The Java `macos-arm64` bundle carries a native arm64 `hyperd`. Both
//! bundles share the identical URL template (only the `java`/`cxx` token
//! differs) and the identical internal layout (`lib/hyper/hyperd`), so the
//! switch is confined to this token and the pinned sha256s.

use crate::platform::Platform;
use crate::release::PinnedRelease;
Expand All @@ -13,11 +21,11 @@ const BASE_URL: &str = "https://downloads.tableau.com/tssoftware";
/// combination.
///
/// The URL template matches
/// `https://downloads.tableau.com/tssoftware/tableauhyperapi-cxx-<platform>-release-main.<version>.<build_id>.zip`.
/// `https://downloads.tableau.com/tssoftware/tableauhyperapi-java-<platform>-release-main.<version>.<build_id>.zip`.
#[must_use]
pub fn build_download_url(release: &PinnedRelease, platform: Platform) -> String {
format!(
"{base}/tableauhyperapi-cxx-{plat}-release-main.{version}.{build_id}.zip",
"{base}/tableauhyperapi-java-{plat}-release-main.{version}.{build_id}.zip",
base = BASE_URL,
plat = platform.slug(),
version = release.version,
Expand All @@ -40,7 +48,7 @@ mod tests {
let url = build_download_url(&r, Platform::MacosArm64);
assert_eq!(
url,
"https://downloads.tableau.com/tssoftware/tableauhyperapi-cxx-macos-arm64-release-main.0.0.24457.rc36858b6.zip"
"https://downloads.tableau.com/tssoftware/tableauhyperapi-java-macos-arm64-release-main.0.0.24457.rc36858b6.zip"
);
}
}
2 changes: 1 addition & 1 deletion hyperdb-bootstrap/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ fn builtin_release_builds_a_valid_url() {
let r = PinnedRelease::builtin();
let url = build_download_url(&r, Platform::LinuxX86_64);
assert!(url.starts_with("https://downloads.tableau.com/tssoftware/"));
assert!(url.contains("cxx-linux-x86_64"));
assert!(url.contains("java-linux-x86_64"));
assert!(std::path::Path::new(&url)
.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("zip")));
Expand Down
Loading