Add new make-file-executable API for extensions (#10047)

This PR adds a new function, `make-file-executable`, to the Zed
extension API that can be used to mark a given file as executable
(typically the language server binary).

This is available in v0.0.5 of the `zed_extension_api` crate.

We also reworked how we represent the various WIT versions on disk to
make it a bit clearer what the version number entails.

Release Notes:

- N/A

---------

Co-authored-by: Max <max@zed.dev>
This commit is contained in:
Marshall Bowers 2024-04-01 15:28:24 -04:00 committed by GitHub
parent 6e49a2460e
commit 8b586ef8e7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 97 additions and 46 deletions

47
Cargo.lock generated
View file

@ -12670,28 +12670,21 @@ dependencies = [
name = "zed_astro"
version = "0.0.1"
dependencies = [
"zed_extension_api 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"zed_extension_api 0.0.4",
]
[[package]]
name = "zed_csharp"
version = "0.0.1"
dependencies = [
"zed_extension_api 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"zed_extension_api 0.0.4",
]
[[package]]
name = "zed_erlang"
version = "0.0.1"
dependencies = [
"zed_extension_api 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "zed_extension_api"
version = "0.0.4"
dependencies = [
"wit-bindgen",
"zed_extension_api 0.0.4",
]
[[package]]
@ -12703,67 +12696,83 @@ dependencies = [
"wit-bindgen",
]
[[package]]
name = "zed_extension_api"
version = "0.0.5"
dependencies = [
"wit-bindgen",
]
[[package]]
name = "zed_extension_api"
version = "0.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5f4ae4e302a80591635ef9a236b35fde6fcc26cfd060e66fde4ba9f9fd394a1"
dependencies = [
"wit-bindgen",
]
[[package]]
name = "zed_gleam"
version = "0.0.2"
dependencies = [
"zed_extension_api 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"zed_extension_api 0.0.4",
]
[[package]]
name = "zed_haskell"
version = "0.0.1"
dependencies = [
"zed_extension_api 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"zed_extension_api 0.0.4",
]
[[package]]
name = "zed_php"
version = "0.0.1"
dependencies = [
"zed_extension_api 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"zed_extension_api 0.0.4",
]
[[package]]
name = "zed_prisma"
version = "0.0.1"
dependencies = [
"zed_extension_api 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"zed_extension_api 0.0.4",
]
[[package]]
name = "zed_purescript"
version = "0.0.1"
dependencies = [
"zed_extension_api 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"zed_extension_api 0.0.4",
]
[[package]]
name = "zed_svelte"
version = "0.0.1"
dependencies = [
"zed_extension_api 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"zed_extension_api 0.0.4",
]
[[package]]
name = "zed_toml"
version = "0.0.2"
dependencies = [
"zed_extension_api 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"zed_extension_api 0.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "zed_uiua"
version = "0.0.1"
dependencies = [
"zed_extension_api 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"zed_extension_api 0.0.4",
]
[[package]]
name = "zed_zig"
version = "0.0.1"
dependencies = [
"zed_extension_api 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"zed_extension_api 0.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]

View file

@ -57,8 +57,13 @@ impl LspAdapter for ExtensionLspAdapter {
.host
.path_from_extension(&self.extension.manifest.id, command.command.as_ref());
// TODO: Eventually we'll want to expose an extension API for doing this, but for
// now we just manually set the file permissions for extensions that we know need it.
// TODO: This should now be done via the `zed::make_file_executable` function in
// Zed extension API, but we're leaving these existing usages in place temporarily
// to avoid any compatibility issues between Zed and the extension versions.
//
// We can remove once the following extension versions no longer see any use:
// - toml@0.0.2
// - zig@0.0.1
if ["toml", "zig"].contains(&self.extension.manifest.id.as_ref()) {
#[cfg(not(windows))]
{

View file

@ -1,5 +1,5 @@
mod v0_0_1;
mod v0_0_4;
mod since_v0_0_1;
mod since_v0_0_4;
use super::{wasm_engine, WasmState};
use anyhow::{Context, Result};
@ -11,7 +11,7 @@ use wasmtime::{
Store,
};
use v0_0_4 as latest;
use since_v0_0_4 as latest;
pub use latest::{Command, LanguageServerConfig};
@ -30,12 +30,12 @@ fn wasi_view(state: &mut WasmState) -> &mut WasmState {
/// Returns whether the given Wasm API version is supported by the Wasm host.
pub fn is_supported_wasm_api_version(version: SemanticVersion) -> bool {
v0_0_1::VERSION <= version && version <= v0_0_4::VERSION
since_v0_0_1::MIN_VERSION <= version && version <= latest::MAX_VERSION
}
pub enum Extension {
V004(v0_0_4::Extension),
V001(v0_0_1::Extension),
V004(since_v0_0_4::Extension),
V001(since_v0_0_1::Extension),
}
impl Extension {
@ -44,17 +44,23 @@ impl Extension {
version: SemanticVersion,
component: &Component,
) -> Result<(Self, Instance)> {
if version < latest::VERSION {
let (extension, instance) =
v0_0_1::Extension::instantiate_async(store, &component, v0_0_1::linker())
.await
.context("failed to instantiate wasm extension")?;
if version < latest::MIN_VERSION {
let (extension, instance) = since_v0_0_1::Extension::instantiate_async(
store,
&component,
since_v0_0_1::linker(),
)
.await
.context("failed to instantiate wasm extension")?;
Ok((Self::V001(extension), instance))
} else {
let (extension, instance) =
v0_0_4::Extension::instantiate_async(store, &component, v0_0_4::linker())
.await
.context("failed to instantiate wasm extension")?;
let (extension, instance) = since_v0_0_4::Extension::instantiate_async(
store,
&component,
since_v0_0_4::linker(),
)
.await
.context("failed to instantiate wasm extension")?;
Ok((Self::V004(extension), instance))
}
}

View file

@ -7,11 +7,11 @@ use semantic_version::SemanticVersion;
use std::sync::{Arc, OnceLock};
use wasmtime::component::{Linker, Resource};
pub const VERSION: SemanticVersion = SemanticVersion::new(0, 0, 1);
pub const MIN_VERSION: SemanticVersion = SemanticVersion::new(0, 0, 1);
wasmtime::component::bindgen!({
async: true,
path: "../extension_api/wit/0.0.1",
path: "../extension_api/wit/since_v0.0.1",
with: {
"worktree": ExtensionWorktree,
},

View file

@ -6,6 +6,7 @@ use async_trait::async_trait;
use futures::io::BufReader;
use language::{LanguageServerBinaryStatus, LspAdapterDelegate};
use semantic_version::SemanticVersion;
use std::path::Path;
use std::{
env,
path::PathBuf,
@ -14,11 +15,12 @@ use std::{
use util::maybe;
use wasmtime::component::{Linker, Resource};
pub const VERSION: SemanticVersion = SemanticVersion::new(0, 0, 4);
pub const MIN_VERSION: SemanticVersion = SemanticVersion::new(0, 0, 4);
pub const MAX_VERSION: SemanticVersion = SemanticVersion::new(0, 0, 5);
wasmtime::component::bindgen!({
async: true,
path: "../extension_api/wit/0.0.4",
path: "../extension_api/wit/since_v0.0.4",
with: {
"worktree": ExtensionWorktree,
},
@ -274,6 +276,28 @@ impl ExtensionImports for WasmState {
.await;
convert_result(result)
}
async fn make_file_executable(&mut self, path: String) -> wasmtime::Result<Result<(), String>> {
#[allow(unused)]
let path = self
.host
.writeable_path_from_extension(&self.manifest.id, Path::new(&path))?;
#[cfg(unix)]
{
use std::fs::{self, Permissions};
use std::os::unix::fs::PermissionsExt;
return convert_result(
fs::set_permissions(&path, Permissions::from_mode(0o755)).map_err(|error| {
anyhow!("failed to set permissions for path {path:?}: {error}")
}),
);
}
#[cfg(not(unix))]
Ok(Ok(()))
}
}
fn convert_result<T>(result: Result<T>) -> wasmtime::Result<Result<T, String>> {

View file

@ -1,6 +1,6 @@
[package]
name = "zed_extension_api"
version = "0.0.4"
version = "0.0.5"
description = "APIs for creating Zed extensions in Rust"
repository = "https://github.com/zed-industries/zed"
documentation = "https://docs.rs/zed_extension_api"

View file

@ -53,7 +53,7 @@ pub static ZED_API_VERSION: [u8; 6] = *include_bytes!(concat!(env!("OUT_DIR"), "
mod wit {
wit_bindgen::generate!({
skip: ["init-extension"],
path: "./wit/0.0.4",
path: "./wit/since_v0.0.4",
});
}

View file

@ -62,9 +62,12 @@ world extension {
/// Gets the latest release for the given GitHub repository.
import latest-github-release: func(repo: string, options: github-release-options) -> result<github-release, string>;
/// Downloads a file from the given url, and saves it to the given filename within the extension's
/// Downloads a file from the given url, and saves it to the given path within the extension's
/// working directory. Extracts the file according to the given file type.
import download-file: func(url: string, output-filename: string, file-type: downloaded-file-type) -> result<_, string>;
import download-file: func(url: string, file-path: string, file-type: downloaded-file-type) -> result<_, string>;
/// Makes the file at the given path executable.
import make-file-executable: func(filepath: string) -> result<_, string>;
/// Updates the installation status for the given language server.
import set-language-server-installation-status: func(language-server-name: string, status: language-server-installation-status);

View file

@ -13,4 +13,4 @@ path = "src/toml.rs"
crate-type = ["cdylib"]
[dependencies]
zed_extension_api = "0.0.4"
zed_extension_api = "0.0.5"

View file

@ -72,6 +72,8 @@ impl TomlExtension {
)
.map_err(|err| format!("failed to download file: {err}"))?;
zed::make_file_executable(&binary_path)?;
let entries = fs::read_dir(".")
.map_err(|err| format!("failed to list working directory {err}"))?;
for entry in entries {

View file

@ -13,4 +13,4 @@ path = "src/zig.rs"
crate-type = ["cdylib"]
[dependencies]
zed_extension_api = "0.0.4"
zed_extension_api = "0.0.5"

View file

@ -78,6 +78,8 @@ impl ZigExtension {
)
.map_err(|e| format!("failed to download file: {e}"))?;
zed::make_file_executable(&binary_path)?;
let entries =
fs::read_dir(".").map_err(|e| format!("failed to list working directory {e}"))?;
for entry in entries {