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
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,6 @@ curl -fsSL https://raw.githubusercontent.com/unhappychoice/steamfetch/main/insta
brew install unhappychoice/tap/steamfetch
```

### From crates.io

```bash
cargo install steamfetch
```

### Download Binary

Download the latest release from [GitHub Releases](https://github.com/unhappychoice/steamfetch/releases).
Expand All @@ -40,6 +34,7 @@ Download the latest release from [GitHub Releases](https://github.com/unhappycho
git clone https://github.com/unhappychoice/steamfetch.git
cd steamfetch
cargo build --release
# libsteam_api.so is automatically copied to target/release/
./target/release/steamfetch
```

Expand Down Expand Up @@ -182,6 +177,12 @@ steamfetch makes two API calls per game (player achievements + global percentage

Install and run Steam inside WSL with a GUI-enabled setup ([WSLg](https://github.com/microsoft/wslg)). Once Steam is running inside WSL, steamfetch will automatically detect it via the Native SDK. Note that Steam running on the Windows host side is not accessible from WSL.

### "libsteam_api.so: cannot open shared object file" error

The `steamfetch` binary requires `libsteam_api.so` (Linux) / `libsteam_api.dylib` (macOS) to be in the same directory as the binary. This is automatically handled by the install script, Homebrew, and GitHub Releases.

If you built from source, `cargo build` copies the library to `target/release/` automatically. If the library is missing, copy it manually next to the binary from the build output.

### How to debug issues?

Run with `steamfetch --verbose` to see detailed output including: Steam client detection status, API request URLs, HTTP status codes, retry attempts, and failure reasons. You can also try `steamfetch --demo` to verify your terminal setup without needing an API key.
Expand Down
77 changes: 75 additions & 2 deletions build.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
use std::path::PathBuf;

fn main() {
// Set rpath so the binary looks for Steam API library in the same directory
// and in ../lib/steamfetch/ (for Homebrew: bin/ -> lib/steamfetch/)
set_rpath();
copy_steam_api_library();
}

/// Set rpath so the binary looks for Steam API library in the same directory
/// and in ../lib/steamfetch/ (for Homebrew: bin/ -> lib/steamfetch/)
fn set_rpath() {
#[cfg(target_os = "linux")]
{
println!("cargo:rustc-link-arg=-Wl,-rpath,$ORIGIN");
Expand All @@ -13,3 +20,69 @@ fn main() {
println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path/../lib/steamfetch");
}
}

/// Copy libsteam_api shared library next to the output binary so it can be found at runtime.
/// This handles `cargo build` and `cargo install` cases where the library would otherwise
/// only exist deep inside the build directory.
fn copy_steam_api_library() {
let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR not set");
let out_path = PathBuf::from(&out_dir);

let lib_name = steam_api_lib_name();

// OUT_DIR is typically: target/<profile>/build/<pkg>-<hash>/out
// nth(0) = .../out
// nth(1) = .../steamfetch-<hash>
// nth(2) = .../build
// nth(3) = .../<profile> (e.g., target/release or target/debug)
let profile_dir = match out_path.ancestors().nth(3) {
Some(dir) => dir.to_path_buf(),
None => return,
};

let build_dir = profile_dir.join("build");
let lib_src = find_steam_api_lib(&build_dir, lib_name);
let lib_src = match lib_src {
Some(path) => path,
None => return,
};

let lib_dst = profile_dir.join(lib_name);
if lib_src != lib_dst {
let _ = std::fs::copy(&lib_src, &lib_dst);
}
Comment on lines +50 to +53
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Silent copy failure hides the exact problem this PR aims to fix.

If std::fs::copy fails (e.g., permissions, disk full), the build silently succeeds but the library is missing at runtime — the same user-facing error this PR addresses. Emit a cargo:warning so users get a build-time hint.

Proposed fix
     let lib_dst = profile_dir.join(lib_name);
     if lib_src != lib_dst {
-        let _ = std::fs::copy(&lib_src, &lib_dst);
+        if let Err(e) = std::fs::copy(&lib_src, &lib_dst) {
+            println!(
+                "cargo:warning=Failed to copy {} to {}: {}",
+                lib_src.display(),
+                lib_dst.display(),
+                e
+            );
+        }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let lib_dst = profile_dir.join(lib_name);
if lib_src != lib_dst {
let _ = std::fs::copy(&lib_src, &lib_dst);
}
let lib_dst = profile_dir.join(lib_name);
if lib_src != lib_dst {
if let Err(e) = std::fs::copy(&lib_src, &lib_dst) {
println!(
"cargo:warning=Failed to copy {} to {}: {}",
lib_src.display(),
lib_dst.display(),
e
);
}
}
🤖 Prompt for AI Agents
In `@build.rs` around lines 50 - 53, The current silent ignore of std::fs::copy
hides failures; replace the unused-result call with proper error handling: call
std::fs::copy(&lib_src, &lib_dst) and match its Result, and on Err emit a
build-time hint via println!("cargo:warning=...") including the error message
and the lib_src/lib_dst paths (reference symbols: lib_src, lib_dst,
std::fs::copy, and the cargo:warning mechanism) so users see why the copy failed
during cargo build.

}

fn find_steam_api_lib(build_dir: &PathBuf, lib_name: &str) -> Option<PathBuf> {
std::fs::read_dir(build_dir)
.ok()?
.filter_map(|e| e.ok())
.find_map(|entry| {
let name = entry.file_name();
let name_str = name.to_string_lossy();
if !name_str.starts_with("steamworks-sys-") {
return None;
}
let candidate = entry.path().join("out").join(lib_name);
candidate.exists().then_some(candidate)
})
}

fn steam_api_lib_name() -> &'static str {
#[cfg(target_os = "linux")]
{
"libsteam_api.so"
}
#[cfg(target_os = "macos")]
{
"libsteam_api.dylib"
}
#[cfg(target_os = "windows")]
{
"steam_api64.dll"
}
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
{
"libsteam_api.so"
}
}
12 changes: 12 additions & 0 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,18 @@ main() {
chmod +x "$INSTALL_DIR/$BINARY_NAME"
fi

# Verify Steam API library was installed
if [[ "$PLATFORM" == *"linux"* ]] && [ ! -f "$INSTALL_DIR/libsteam_api.so" ]; then
echo "WARNING: libsteam_api.so was not found in the archive."
echo "steamfetch may fail with 'cannot open shared object file' error."
echo ""
fi
if [[ "$PLATFORM" == *"apple"* ]] && [ ! -f "$INSTALL_DIR/libsteam_api.dylib" ]; then
echo "WARNING: libsteam_api.dylib was not found in the archive."
echo "steamfetch may fail with a shared library error."
echo ""
fi

echo "Successfully installed $BINARY_NAME to $INSTALL_DIR/"
echo ""
if [[ "$PLATFORM" == *"windows"* ]]; then
Expand Down