Add a command for building and installing a locally-developed Zed extension (#8781)

This PR adds an `zed: Install Local Extension` action, which lets you
select a path to a folder containing a Zed extension, and install that .
When you select a directory, the extension will be compiled (both the
Tree-sitter grammars and the Rust code for the extension itself) and
installed as a Zed extension, using a symlink.

### Details

A few dependencies are needed to build an extension:
* The Rust `wasm32-wasi` target. This is automatically installed if
needed via `rustup`.
* A wasi-preview1 adapter WASM module, for building WASM components with
Rust. This is automatically downloaded if needed from a `wasmtime`
GitHub release
* For building Tree-sitter parsers, a distribution of `wasi-sdk`. This
is automatically downloaded if needed from a `wasi-sdk` GitHub release.

The downloaded artifacts are cached in a support directory called
`Zed/extensions/build`.

### Tasks

UX

* [x] Show local extensions in the Extensions view
* [x] Provide a button for recompiling a linked extension
* [x] Make this action discoverable by adding a button for it on the
Extensions view
* [ ] Surface errors (don't just write them to the Zed log)

Packaging

* [ ] Create a separate executable that performs the extension
compilation. We'll switch the packaging system in our
[extensions](https://github.com/zed-industries/extensions) repo to use
this binary, so that there is one canonical definition of how to
build/package an extensions.

### Release Notes:

- N/A

---------

Co-authored-by: Marshall <marshall@zed.dev>
Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
This commit is contained in:
Max Brunsfeld 2024-03-06 15:35:22 -08:00 committed by GitHub
parent e273198ada
commit 675ae24964
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 1662 additions and 763 deletions

View file

@ -43,6 +43,7 @@ use std::ffi::OsStr;
#[async_trait::async_trait]
pub trait Fs: Send + Sync {
async fn create_dir(&self, path: &Path) -> Result<()>;
async fn create_symlink(&self, path: &Path, target: PathBuf) -> Result<()>;
async fn create_file(&self, path: &Path, options: CreateOptions) -> Result<()>;
async fn create_file_with(
&self,
@ -124,6 +125,16 @@ impl Fs for RealFs {
Ok(smol::fs::create_dir_all(path).await?)
}
async fn create_symlink(&self, path: &Path, target: PathBuf) -> Result<()> {
#[cfg(target_family = "unix")]
smol::fs::unix::symlink(target, path).await?;
#[cfg(target_family = "windows")]
Err(anyhow!("not supported yet on windows"))?;
Ok(())
}
async fn create_file(&self, path: &Path, options: CreateOptions) -> Result<()> {
let mut open_options = smol::fs::OpenOptions::new();
open_options.write(true).create(true);
@ -994,6 +1005,25 @@ impl Fs for FakeFs {
Ok(())
}
async fn create_symlink(&self, path: &Path, target: PathBuf) -> Result<()> {
let mut state = self.state.lock();
let file = Arc::new(Mutex::new(FakeFsEntry::Symlink { target }));
state
.write_path(path.as_ref(), move |e| match e {
btree_map::Entry::Vacant(e) => {
e.insert(file);
Ok(())
}
btree_map::Entry::Occupied(mut e) => {
*e.get_mut() = file;
Ok(())
}
})
.unwrap();
state.emit_event(&[path]);
Ok(())
}
async fn create_file_with(
&self,
path: &Path,
@ -1503,8 +1533,9 @@ mod tests {
]
);
fs.insert_symlink("/root/dir2/link-to-dir3", "./dir3".into())
.await;
fs.create_symlink("/root/dir2/link-to-dir3".as_ref(), "./dir3".into())
.await
.unwrap();
assert_eq!(
fs.canonicalize("/root/dir2/link-to-dir3".as_ref())