Skip to content

Commit f7115be

Browse files
someone13574maxdeviantmikayla-maki
authored
Add Flatpak build system and support (#12006)
ping #6687 This is the third iteration of this PR ([v2 here](#11949)) and uses a different approach to the first two (the process wrapper lib was a maintainability nightmare). While the first two attempted to spawn the necessary processes using flatpak-spawn and host-spawn from the app inside the sandbox, this version first spawns the cli binary which then restart's itself *outside* of the sandbox using flatpak-spawn. The restarted cli process than can call the bundled app binary normally, with no need for flatpak-spawn because it is already outside of the sandbox. This is done instead of keeping the cli in the sandbox because ipc becomes very difficult and broken when trying to do it across the sandbox. Gnome software (example using nightly channel and release notes generated using the script): <img src="https://github.com/zed-industries/zed/assets/81528246/6391d217-0f44-4638-9569-88c46e5fc4ba" width="600"/> TODO in this PR: - [x] Bundle libs. - [x] Cleanup release note converter. Future work: - [ ] Auto-update dialog - [ ] Flatpak auto-update (complete 'Auto-update dialog' first) - [ ] Experimental [bundle](https://docs.flatpak.org/en/latest/single-file-bundles.html) releases for feedback (?). *(?) = Maybe / Request for feedback* Release Notes: - N/A --------- Co-authored-by: Marshall Bowers <[email protected]> Co-authored-by: Mikayla Maki <[email protected]>
1 parent 7e3ab9a commit f7115be

File tree

16 files changed

+463
-18
lines changed

16 files changed

+463
-18
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
/script/node_modules
88
/crates/theme/schemas/theme.json
99
/crates/collab/seed.json
10+
/crates/zed/resources/flatpak/flatpak-cargo-sources.json
11+
/dev.zed.Zed*.json
1012
/assets/*licenses.md
1113
**/venv
1214
.build
@@ -25,3 +27,4 @@ DerivedData/
2527
.blob_store
2628
.vscode
2729
.wrangler
30+
.flatpak-builder

crates/auto_update/src/auto_update.rs

+10
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,16 @@ impl AutoUpdater {
342342
}
343343

344344
async fn update(this: Model<Self>, mut cx: AsyncAppContext) -> Result<()> {
345+
// Skip auto-update for flatpaks
346+
#[cfg(target_os = "linux")]
347+
if matches!(std::env::var("ZED_IS_FLATPAK_INSTALL"), Ok(_)) {
348+
this.update(&mut cx, |this, cx| {
349+
this.status = AutoUpdateStatus::Idle;
350+
cx.notify();
351+
})?;
352+
return Ok(());
353+
}
354+
345355
let (client, current_version) = this.read_with(&cx, |this, _| {
346356
(this.http_client.clone(), this.current_version)
347357
})?;

crates/cli/src/main.rs

+118
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,13 @@ fn parse_path_with_position(
6060
}
6161

6262
fn main() -> Result<()> {
63+
// Exit flatpak sandbox if needed
64+
#[cfg(target_os = "linux")]
65+
{
66+
flatpak::try_restart_to_host();
67+
flatpak::ld_extra_libs();
68+
}
69+
6370
// Intercept version designators
6471
#[cfg(target_os = "macos")]
6572
if let Some(channel) = std::env::args().nth(1).filter(|arg| arg.starts_with("--")) {
@@ -72,6 +79,9 @@ fn main() -> Result<()> {
7279
}
7380
let args = Args::parse();
7481

82+
#[cfg(target_os = "linux")]
83+
let args = flatpak::set_bin_if_no_escape(args);
84+
7585
let app = Detect::detect(args.zed.as_deref()).context("Bundle detection")?;
7686

7787
if args.version {
@@ -275,6 +285,114 @@ mod linux {
275285
}
276286
}
277287

288+
#[cfg(target_os = "linux")]
289+
mod flatpak {
290+
use std::ffi::OsString;
291+
use std::path::PathBuf;
292+
use std::process::Command;
293+
use std::{env, process};
294+
295+
const EXTRA_LIB_ENV_NAME: &'static str = "ZED_FLATPAK_LIB_PATH";
296+
const NO_ESCAPE_ENV_NAME: &'static str = "ZED_FLATPAK_NO_ESCAPE";
297+
298+
/// Adds bundled libraries to LD_LIBRARY_PATH if running under flatpak
299+
pub fn ld_extra_libs() {
300+
let mut paths = if let Ok(paths) = env::var("LD_LIBRARY_PATH") {
301+
env::split_paths(&paths).collect()
302+
} else {
303+
Vec::new()
304+
};
305+
306+
if let Ok(extra_path) = env::var(EXTRA_LIB_ENV_NAME) {
307+
paths.push(extra_path.into());
308+
}
309+
310+
env::set_var("LD_LIBRARY_PATH", env::join_paths(paths).unwrap());
311+
}
312+
313+
/// Restarts outside of the sandbox if currently running within it
314+
pub fn try_restart_to_host() {
315+
if let Some(flatpak_dir) = get_flatpak_dir() {
316+
let mut args = vec!["/usr/bin/flatpak-spawn".into(), "--host".into()];
317+
args.append(&mut get_xdg_env_args());
318+
args.push("--env=ZED_IS_FLATPAK_INSTALL=1".into());
319+
args.push(
320+
format!(
321+
"--env={EXTRA_LIB_ENV_NAME}={}",
322+
flatpak_dir.join("lib").to_str().unwrap()
323+
)
324+
.into(),
325+
);
326+
args.push(flatpak_dir.join("bin").join("zed").into());
327+
328+
let mut is_app_location_set = false;
329+
for arg in &env::args_os().collect::<Vec<_>>()[1..] {
330+
args.push(arg.clone());
331+
is_app_location_set |= arg == "--zed";
332+
}
333+
334+
if !is_app_location_set {
335+
args.push("--zed".into());
336+
args.push(flatpak_dir.join("bin").join("zed-app").into());
337+
}
338+
339+
let error = exec::execvp("/usr/bin/flatpak-spawn", args);
340+
eprintln!("failed restart cli on host: {:?}", error);
341+
process::exit(1);
342+
}
343+
}
344+
345+
pub fn set_bin_if_no_escape(mut args: super::Args) -> super::Args {
346+
if env::var(NO_ESCAPE_ENV_NAME).is_ok()
347+
&& env::var("FLATPAK_ID").map_or(false, |id| id.starts_with("dev.zed.Zed"))
348+
{
349+
if args.zed.is_none() {
350+
args.zed = Some("/app/bin/zed-app".into());
351+
env::set_var("ZED_IS_FLATPAK_INSTALL", "1");
352+
}
353+
}
354+
args
355+
}
356+
357+
fn get_flatpak_dir() -> Option<PathBuf> {
358+
if env::var(NO_ESCAPE_ENV_NAME).is_ok() {
359+
return None;
360+
}
361+
362+
if let Ok(flatpak_id) = env::var("FLATPAK_ID") {
363+
if !flatpak_id.starts_with("dev.zed.Zed") {
364+
return None;
365+
}
366+
367+
let install_dir = Command::new("/usr/bin/flatpak-spawn")
368+
.arg("--host")
369+
.arg("flatpak")
370+
.arg("info")
371+
.arg("--show-location")
372+
.arg(flatpak_id)
373+
.output()
374+
.unwrap();
375+
let install_dir = PathBuf::from(String::from_utf8(install_dir.stdout).unwrap().trim());
376+
Some(install_dir.join("files"))
377+
} else {
378+
None
379+
}
380+
}
381+
382+
fn get_xdg_env_args() -> Vec<OsString> {
383+
let xdg_keys = [
384+
"XDG_DATA_HOME",
385+
"XDG_CONFIG_HOME",
386+
"XDG_CACHE_HOME",
387+
"XDG_STATE_HOME",
388+
];
389+
env::vars()
390+
.filter(|(key, _)| xdg_keys.contains(&key.as_str()))
391+
.map(|(key, val)| format!("--env=FLATPAK_{}={}", key, val).into())
392+
.collect()
393+
}
394+
}
395+
278396
// todo("windows")
279397
#[cfg(target_os = "windows")]
280398
mod windows {

crates/util/src/paths.rs

+15-9
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@ lazy_static::lazy_static! {
1313
.expect("failed to determine RoamingAppData directory")
1414
.join("Zed")
1515
} else if cfg!(target_os = "linux") {
16-
dirs::config_dir()
17-
.expect("failed to determine XDG_CONFIG_HOME directory")
18-
.join("zed")
16+
if let Ok(flatpak_xdg_config) = std::env::var("FLATPAK_XDG_CONFIG_HOME") {
17+
flatpak_xdg_config.into()
18+
} else {
19+
dirs::config_dir().expect("failed to determine XDG_CONFIG_HOME directory")
20+
}.join("zed")
1921
} else {
2022
HOME.join(".config").join("zed")
2123
};
@@ -39,9 +41,11 @@ lazy_static::lazy_static! {
3941
pub static ref SUPPORT_DIR: PathBuf = if cfg!(target_os = "macos") {
4042
HOME.join("Library/Application Support/Zed")
4143
} else if cfg!(target_os = "linux") {
42-
dirs::data_local_dir()
43-
.expect("failed to determine XDG_DATA_DIR directory")
44-
.join("zed")
44+
if let Ok(flatpak_xdg_data) = std::env::var("FLATPAK_XDG_DATA_HOME") {
45+
flatpak_xdg_data.into()
46+
} else {
47+
dirs::data_local_dir().expect("failed to determine XDG_DATA_HOME directory")
48+
}.join("zed")
4549
} else if cfg!(target_os = "windows") {
4650
dirs::data_local_dir()
4751
.expect("failed to determine LocalAppData directory")
@@ -80,9 +84,11 @@ lazy_static::lazy_static! {
8084
.expect("failed to determine LocalAppData directory")
8185
.join("Zed")
8286
} else if cfg!(target_os = "linux") {
83-
dirs::cache_dir()
84-
.expect("failed to determine XDG_CACHE_HOME directory")
85-
.join("zed")
87+
if let Ok(flatpak_xdg_cache) = std::env::var("FLATPAK_XDG_CACHE_HOME") {
88+
flatpak_xdg_cache.into()
89+
} else {
90+
dirs::cache_dir().expect("failed to determine XDG_CACHE_HOME directory")
91+
}.join("zed")
8692
} else {
8793
HOME.join(".cache").join("zed")
8894
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
{
2+
"id": "$APP_ID",
3+
"runtime": "org.freedesktop.Platform",
4+
"runtime-version": "23.08",
5+
"sdk": "org.freedesktop.Sdk",
6+
"sdk-extensions": [
7+
"org.freedesktop.Sdk.Extension.rust-stable"
8+
],
9+
"command": "zed",
10+
"finish-args": [
11+
"--talk-name=org.freedesktop.Flatpak",
12+
"--device=dri",
13+
"--share=ipc",
14+
"--share=network",
15+
"--socket=wayland",
16+
"--socket=fallback-x11",
17+
"--socket=pulseaudio",
18+
"--filesystem=host"
19+
],
20+
"build-options": {
21+
"append-path": "/usr/lib/sdk/rust-stable/bin"
22+
},
23+
"modules": [
24+
{
25+
"name": "zed",
26+
"buildsystem": "simple",
27+
"build-options": {
28+
"env": {
29+
"APP_ID": "$APP_ID",
30+
"APP_ICON": "$APP_ID",
31+
"APP_NAME": "$APP_NAME",
32+
"BRANDING_LIGHT": "$BRANDING_LIGHT",
33+
"BRANDING_DARK": "$BRANDING_DARK",
34+
"APP_ARGS": "--foreground",
35+
"DO_STARTUP_NOTIFY": "false"
36+
}
37+
},
38+
"build-commands": [
39+
"install -Dm644 $ICON_FILE.png /app/share/icons/hicolor/512x512/apps/$APP_ID.png",
40+
"envsubst < zed.desktop.in > zed.desktop && install -Dm644 zed.desktop /app/share/applications/$APP_ID.desktop",
41+
"envsubst < flatpak/zed.metainfo.xml.in > zed.metainfo.xml && install -Dm644 zed.metainfo.xml /app/share/metainfo/$APP_ID.metainfo.xml",
42+
"sed -i -e '/@release_info@/{r flatpak/release-info/$CHANNEL' -e 'd}' /app/share/metainfo/$APP_ID.metainfo.xml",
43+
"install -Dm755 bin/cli /app/bin/zed",
44+
"install -Dm755 bin/zed /app/bin/zed-app",
45+
"install -Dm755 lib/* -t /app/lib"
46+
],
47+
"sources": [
48+
{
49+
"type": "archive",
50+
"path": "./target/release/$ARCHIVE"
51+
},
52+
{
53+
"type": "dir",
54+
"path": "./crates/zed/resources"
55+
}
56+
]
57+
}
58+
]
59+
}

crates/zed/resources/flatpak/release-info/dev

Whitespace-only changes.

crates/zed/resources/flatpak/release-info/nightly

Whitespace-only changes.

crates/zed/resources/flatpak/release-info/preview

Whitespace-only changes.

crates/zed/resources/flatpak/release-info/stable

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<component type="desktop-application">
3+
<id>$APP_ID</id>
4+
<metadata_license>MIT</metadata_license>
5+
<project_license>AGPL-3.0-or-later and Apache-2.0 and GPL-3.0-or-later</project_license>
6+
7+
<name>$APP_NAME</name>
8+
<summary>High-performance, multiplayer code editor</summary>
9+
<developer id="dev.zed">
10+
<name translate="no">Zed Industries, Inc.</name>
11+
</developer>
12+
<description>
13+
<p>
14+
Productive coding starts with a tool that stays out of your way. Zed blends the power of an IDE with the speed of a lightweight editor for productivity you can feel under your fingertips.
15+
</p>
16+
<p>Features:</p>
17+
<ul>
18+
<li>Performance: Efficiently uses every CPU core and your GPU for instant startup, quick file loading, and responsive keystrokes.</li>
19+
<li>Language-aware: Maintains a syntax tree for precise highlighting, and auto-indent, with LSP support for autocompletion and refactoring.</li>
20+
<li>Collaboration: Real-time editing and navigation for multiple developers in a shared workspace.</li>
21+
<li>AI Integration: Integrates GitHub Copilot and GPT-4 for natural language code generation.</li>
22+
</ul>
23+
</description>
24+
25+
<launchable type="desktop-id">$APP_ID.desktop</launchable>
26+
27+
<branding>
28+
<color type="primary" scheme_preference="light">$BRANDING_LIGHT</color>
29+
<color type="primary" scheme_preference="dark">$BRANDING_DARK</color>
30+
</branding>
31+
32+
<content_rating type="oars-1.1">
33+
<content_attribute id="social-chat">intense</content_attribute>
34+
<content_attribute id="social-audio">intense</content_attribute>
35+
</content_rating>
36+
37+
<url type="homepage">https://zed.dev</url>
38+
<url type="bugtracker">https://github.com/zed-industries/zed/issues</url>
39+
<url type="faq">https://zed.dev/faq</url>
40+
<url type="help">https://zed.dev/docs/getting-started</url>
41+
<url type="contact">https://zed.dev/docs/feedback-and-support</url>
42+
<url type="vcs-browser">https://github.com/zed-industries/zed</url>
43+
<url type="contribute">https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md</url>
44+
45+
<supports>
46+
<internet>offline-only</internet>
47+
</supports>
48+
<recommends>
49+
<control>pointing</control>
50+
<control>keyboard</control>
51+
<display_length compare="ge">768</display_length>
52+
</recommends>
53+
54+
<screenshots>
55+
<screenshot type="default">
56+
<caption>Zed with a large project open, showing language server and gitblame support</caption>
57+
<image type="source" width="1122" height="859" xml:lang="en">https://zed.dev/img/flatpak/flatpak-1.png</image>
58+
</screenshot>
59+
<screenshot>
60+
<caption>Zed with a file open and a channel message thread in the right sidebar</caption>
61+
<image type="source" width="1122" height="859" xml:lang="en">https://zed.dev/img/flatpak/flatpak-2.png</image>
62+
</screenshot>
63+
<screenshot>
64+
<caption>Example of a channel's shared document</caption>
65+
<image type="source" width="1122" height="859" xml:lang="en">https://zed.dev/img/flatpak/flatpak-3.png</image>
66+
</screenshot>
67+
<screenshot>
68+
<caption>Zed's extension list</caption>
69+
<image type="source" width="1122" height="859" xml:lang="en">https://zed.dev/img/flatpak/flatpak-4.png</image>
70+
</screenshot>
71+
<screenshot>
72+
<caption>Theme switcher UI and example theme</caption>
73+
<image type="source" width="1122" height="859" xml:lang="en">https://zed.dev/img/flatpak/flatpak-5.png</image>
74+
</screenshot>
75+
</screenshots>
76+
77+
<releases>
78+
@release_info@
79+
</releases>
80+
</component>
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
[Desktop Entry]
22
Version=1.0
33
Type=Application
4-
Name=Zed
4+
Name=$APP_NAME
55
GenericName=Text Editor
66
Comment=A high-performance, multiplayer code editor.
77
TryExec=zed
8-
StartupNotify=true
9-
Exec=zed
10-
Icon=zed
11-
Categories=TextEditor;Development;IDE;
8+
StartupNotify=$DO_STARTUP_NOTIFY
9+
Exec=zed $APP_ARGS
10+
Icon=$APP_ICON
11+
Categories=Utility;TextEditor;Development;IDE;
1212
Keywords=zed;
1313
MimeType=text/plain;inode/directory;

docs/src/development/linux.md

+12
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,18 @@ cargo test --workspace
7070

7171
Zed has basic support for both modes. The mode is selected at runtime. If you're on wayland and want to run in X11 mode, you can set `WAYLAND_DISPLAY='' cargo run` to do so.
7272

73+
## Flatpak
74+
75+
> [!WARNING]
76+
> Zed's current Flatpak integration simply exits the sandbox on startup. Workflows that rely on Flatpak's sandboxing may not work as expected.
77+
78+
To build & install the Flatpak package locally follow the steps below:
79+
80+
1. Install Flatpak for your distribution as outlined [here](https://flathub.org/setup).
81+
2. Run the `script/flatpak/deps` script to install the required dependencies.
82+
3. Run `script/flatpak/bundle-flatpak`.
83+
4. Now the package has been installed and has a bundle available at `target/release/{app-id}.flatpak`.
84+
7385
## Troubleshooting
7486

7587
### Can't compile zed

0 commit comments

Comments
 (0)