diff --git a/Cargo.lock b/Cargo.lock index 0240af5621a..ef57c42ae4e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -407,7 +407,7 @@ dependencies = [ [[package]] name = "cargo-credential-libsecret" -version = "0.4.15" +version = "0.5.0" dependencies = [ "anyhow", "cargo-credential", diff --git a/Cargo.toml b/Cargo.toml index da3d6bf9a04..489905638f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ blake3 = "1.5.5" build-rs = { version = "0.3.1", path = "crates/build-rs" } cargo = { path = "" } cargo-credential = { version = "0.4.2", path = "credential/cargo-credential" } -cargo-credential-libsecret = { version = "0.4.15", path = "credential/cargo-credential-libsecret" } +cargo-credential-libsecret = { version = "0.5.0", path = "credential/cargo-credential-libsecret" } cargo-credential-macos-keychain = { version = "0.4.15", path = "credential/cargo-credential-macos-keychain" } cargo-credential-wincred = { version = "0.4.15", path = "credential/cargo-credential-wincred" } cargo-platform = { path = "crates/cargo-platform", version = "0.3.0" } diff --git a/credential/cargo-credential-libsecret/Cargo.toml b/credential/cargo-credential-libsecret/Cargo.toml index 53cd0ea60f4..a691996db6d 100644 --- a/credential/cargo-credential-libsecret/Cargo.toml +++ b/credential/cargo-credential-libsecret/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cargo-credential-libsecret" -version = "0.4.15" +version = "0.5.0" rust-version = "1.87" # MSRV:1 edition.workspace = true license.workspace = true diff --git a/credential/cargo-credential-libsecret/src/lib.rs b/credential/cargo-credential-libsecret/src/lib.rs index 0d10fb83689..684f0f8da37 100644 --- a/credential/cargo-credential-libsecret/src/lib.rs +++ b/credential/cargo-credential-libsecret/src/lib.rs @@ -83,7 +83,9 @@ mod linux { ... ) -> *mut gchar; - pub struct LibSecretCredential; + pub struct LibSecretCredential { + libsecret: Library, + } fn label(index_url: &str) -> CString { CString::new(format!("cargo-registry:{}", index_url)).unwrap() @@ -105,31 +107,41 @@ mod linux { } } - impl Credential for LibSecretCredential { + impl LibSecretCredential { + pub fn new() -> Result { + // Dynamically load libsecret to avoid users needing to install + // additional -dev packages when building this provider. + let libsecret = unsafe { Library::new("libsecret-1.so.0") }.context( + "failed to load libsecret: try installing the `libsecret` \ + or `libsecret-1-0` package with the system package manager", + )?; + Ok(Self { libsecret }) + } + } + + impl Credential for &LibSecretCredential { fn perform( &self, registry: &RegistryInfo<'_>, action: &Action<'_>, _args: &[&str], ) -> Result { - // Dynamically load libsecret to avoid users needing to install - // additional -dev packages when building this provider. - let lib; let secret_password_lookup_sync: Symbol<'_, SecretPasswordLookupSync>; let secret_password_store_sync: Symbol<'_, SecretPasswordStoreSync>; let secret_password_clear_sync: Symbol<'_, SecretPasswordClearSync>; unsafe { - lib = Library::new("libsecret-1.so.0").context( - "failed to load libsecret: try installing the `libsecret` \ - or `libsecret-1-0` package with the system package manager", - )?; - secret_password_lookup_sync = lib + secret_password_lookup_sync = self + .libsecret .get(b"secret_password_lookup_sync\0") .map_err(Box::new)?; - secret_password_store_sync = - lib.get(b"secret_password_store_sync\0").map_err(Box::new)?; - secret_password_clear_sync = - lib.get(b"secret_password_clear_sync\0").map_err(Box::new)?; + secret_password_store_sync = self + .libsecret + .get(b"secret_password_store_sync\0") + .map_err(Box::new)?; + secret_password_clear_sync = self + .libsecret + .get(b"secret_password_clear_sync\0") + .map_err(Box::new)?; } let index_url_c = CString::new(registry.index_url).unwrap(); diff --git a/src/cargo/util/auth/mod.rs b/src/cargo/util/auth/mod.rs index 2dc66f56241..0ce9d414084 100644 --- a/src/cargo/util/auth/mod.rs +++ b/src/cargo/util/auth/mod.rs @@ -508,6 +508,27 @@ static BUILT_IN_PROVIDERS: &[&'static str] = &[ "cargo:libsecret", ]; +/// Retrieves a cached instance of `LibSecretCredential`. +/// Must be cached to avoid repeated load/unload cycles, which are not supported by `glib`. +#[cfg(target_os = "linux")] +fn get_credential_libsecret( +) -> CargoResult<&'static cargo_credential_libsecret::LibSecretCredential> { + static CARGO_CREDENTIAL_LIBSECRET: std::sync::OnceLock< + cargo_credential_libsecret::LibSecretCredential, + > = std::sync::OnceLock::new(); + // Unfortunately `get_or_try_init` is not yet stable. This workaround is not threadsafe but + // loading libsecret twice will only temporary increment the ref counter, which is decrement + // again when `drop` is called. + match CARGO_CREDENTIAL_LIBSECRET.get() { + Some(lib) => Ok(lib), + None => { + let _ = CARGO_CREDENTIAL_LIBSECRET + .set(cargo_credential_libsecret::LibSecretCredential::new()?); + Ok(CARGO_CREDENTIAL_LIBSECRET.get().unwrap()) + } + } +} + fn credential_action( gctx: &GlobalContext, sid: &SourceId, @@ -545,7 +566,7 @@ fn credential_action( #[cfg(target_os = "macos")] "cargo:macos-keychain" => Box::new(cargo_credential_macos_keychain::MacKeychain {}), #[cfg(target_os = "linux")] - "cargo:libsecret" => Box::new(cargo_credential_libsecret::LibSecretCredential {}), + "cargo:libsecret" => Box::new(get_credential_libsecret()?), name if BUILT_IN_PROVIDERS.contains(&name) => { Box::new(cargo_credential::UnsupportedCredential {}) }