Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for VSCode based IDE inside Flatpak #1577

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
26 changes: 21 additions & 5 deletions cmd/up.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ func (cmd *UpCmd) Run(
}

// configure container ssh
var sshConfigPath string
if cmd.ConfigureSSH {
devPodHome := ""
envDevPodHome, ok := os.LookupEnv("DEVPOD_HOME")
Expand All @@ -205,7 +206,7 @@ func (cmd *UpCmd) Run(
}
setupGPGAgentForwarding := cmd.GPGAgentForwarding || devPodConfig.ContextOption(config.ContextOptionGPGAgentForwarding) == "true"

err = configureSSH(devPodConfig, client, cmd.SSHConfigPath, user, workdir, setupGPGAgentForwarding, devPodHome)
sshConfigPath, err = configureSSH(devPodConfig, client, cmd.SSHConfigPath, user, workdir, setupGPGAgentForwarding, devPodHome)
if err != nil {
return err
}
Expand Down Expand Up @@ -238,6 +239,7 @@ func (cmd *UpCmd) Run(
result.SubstitutionContext.ContainerWorkspaceFolder,
vscode.Options.GetValue(ideConfig.Options, vscode.OpenNewWindow) == "true",
vscode.FlavorStable,
sshConfigPath,
log,
)
case string(config.IDEVSCodeInsiders):
Expand All @@ -247,6 +249,7 @@ func (cmd *UpCmd) Run(
result.SubstitutionContext.ContainerWorkspaceFolder,
vscode.Options.GetValue(ideConfig.Options, vscode.OpenNewWindow) == "true",
vscode.FlavorInsiders,
sshConfigPath,
log,
)
case string(config.IDECursor):
Expand All @@ -256,6 +259,7 @@ func (cmd *UpCmd) Run(
result.SubstitutionContext.ContainerWorkspaceFolder,
vscode.Options.GetValue(ideConfig.Options, vscode.OpenNewWindow) == "true",
vscode.FlavorCursor,
sshConfigPath,
log,
)
case string(config.IDECodium):
Expand All @@ -265,6 +269,17 @@ func (cmd *UpCmd) Run(
result.SubstitutionContext.ContainerWorkspaceFolder,
vscode.Options.GetValue(ideConfig.Options, vscode.OpenNewWindow) == "true",
vscode.FlavorCodium,
sshConfigPath,
log,
)
case string(config.IDECodiumInsiders):
return vscode.Open(
ctx,
client.Workspace(),
result.SubstitutionContext.ContainerWorkspaceFolder,
vscode.Options.GetValue(ideConfig.Options, vscode.OpenNewWindow) == "true",
vscode.FlavorCodiumInsiders,
sshConfigPath,
log,
)
case string(config.IDEPositron):
Expand All @@ -274,6 +289,7 @@ func (cmd *UpCmd) Run(
result.SubstitutionContext.ContainerWorkspaceFolder,
vscode.Options.GetValue(ideConfig.Options, vscode.OpenNewWindow) == "true",
vscode.FlavorPositron,
sshConfigPath,
log,
)
case string(config.IDEOpenVSCode):
Expand Down Expand Up @@ -995,10 +1011,10 @@ func startBrowserTunnel(
return nil
}

func configureSSH(c *config.Config, client client2.BaseWorkspaceClient, sshConfigPath, user, workdir string, gpgagent bool, devPodHome string) error {
func configureSSH(c *config.Config, client client2.BaseWorkspaceClient, sshConfigPath, user, workdir string, gpgagent bool, devPodHome string) (string, error) {
path, err := devssh.ResolveSSHConfigPath(sshConfigPath)
if err != nil {
return errors.Wrap(err, "Invalid ssh config path")
return "", errors.Wrap(err, "Invalid ssh config path")
}
sshConfigPath = path

Expand All @@ -1013,10 +1029,10 @@ func configureSSH(c *config.Config, client client2.BaseWorkspaceClient, sshConfi
log.Default,
)
if err != nil {
return err
return "", err
}

return nil
return sshConfigPath, nil
}

func mergeDevPodUpOptions(baseOptions *provider2.CLIOptions) error {
Expand Down
1 change: 1 addition & 0 deletions pkg/config/ide.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@ const (
IDEPositron IDE = "positron"
IDEMarimo IDE = "marimo"
IDECodium IDE = "codium"
IDECodiumInsiders IDE = "codium-insiders"
Copy link
Member

Choose a reason for hiding this comment

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

We'll have to expose this in DevPod Desktop as well, will create a follow up PR

IDEZed IDE = "zed"
)
7 changes: 7 additions & 0 deletions pkg/ide/ideparse/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,13 @@ var AllowedIDEs = []AllowedIDE{
Icon: "https://devpod.sh/assets/codium.svg",
Experimental: true,
},
{
Name: config.IDECodiumInsiders,
DisplayName: "Codium Insiders",
Options: vscode.Options,
Icon: "https://devpod.sh/assets/codium_insiders.svg", // TODO to be uploaded
Copy link
Member

Choose a reason for hiding this comment

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

This is available now, you can remove the TODO :)

Experimental: true,
},
{
Name: config.IDEPositron,
DisplayName: "Positron",
Expand Down
132 changes: 78 additions & 54 deletions pkg/ide/vscode/open.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,24 @@ import (
"fmt"
"os/exec"
"runtime"
"slices"
"strings"

"github.com/loft-sh/devpod/pkg/command"
"github.com/loft-sh/log"
"github.com/skratchdot/open-golang/open"
)

func Open(ctx context.Context, workspace, folder string, newWindow bool, flavor Flavor, log log.Logger) error {
const (
FlatpakStable string = "com.visualstudio.code"
FlatpakInsiders string = "com.visualstudio.code.insiders"
FlatpakCodium string = "com.vscodium.codium"
FlatpakCodiumInsiders string = "com.vscodium.codium-insiders"
)

func Open(ctx context.Context, workspace, folder string, newWindow bool, flavor Flavor, sshConfigPath string, log log.Logger) error {
log.Infof("Starting %s...", flavor.DisplayName())
cliErr := openViaCLI(ctx, workspace, folder, newWindow, flavor, log)
cliErr := openViaCLI(ctx, workspace, folder, newWindow, flavor, sshConfigPath, log)
if cliErr == nil {
return nil
}
Expand All @@ -41,6 +49,8 @@ func openViaBrowser(workspace, folder string, newWindow bool, flavor Flavor, log
protocol = `positron://`
case FlavorCodium:
protocol = `codium://`
case FlavorCodiumInsiders:
protocol = `codium-insiders://`
}

openURL := protocol + `vscode-remote/ssh-remote+` + workspace + `.devpod/` + folder
Expand All @@ -58,20 +68,44 @@ func openViaBrowser(workspace, folder string, newWindow bool, flavor Flavor, log
return nil
}

func openViaCLI(ctx context.Context, workspace, folder string, newWindow bool, flavor Flavor, log log.Logger) error {
func openViaCLI(ctx context.Context, workspace, folder string, newWindow bool, flavor Flavor, sshConfigPath string, log log.Logger) error {
// try to find code cli
codePath := findCLI(flavor)
if codePath == "" {
codePath := findCLI(flavor, log)
if codePath == nil {
return fmt.Errorf("couldn't find the %s binary", flavor)
}

if codePath[0] == "flatpak" {
log.Debugf("Running with Flatpak suing the package %s.", codePath[2])
Copy link
Member

Choose a reason for hiding this comment

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

nitpick:

Suggested change
log.Debugf("Running with Flatpak suing the package %s.", codePath[2])
log.Debugf("Running with Flatpak using the package %s.", codePath[2])

out, err := exec.Command(codePath[0], "ps", "--columns=application").Output()
if err != nil {
return command.WrapCommandError(out, err)
}
splitted := strings.Split(string(out), "\n")
foundRunning := false
// Ignore the header
for _, str := range splitted[1:] {
if strings.TrimSpace(str) == codePath[2] {
foundRunning = true
break
}
}

if foundRunning {
log.Warnf("The IDE is already running via Flatpak. If you are encountering SSH connectivity issues, make sure to give read access to your SSH config file (e.g flatpak override %s --filesystem=%s) and restart your IDE.", codePath[2], sshConfigPath)
}

codePath = slices.Insert(codePath, 2, fmt.Sprintf("--filesystem=%s:ro", sshConfigPath))
Copy link
Member

Choose a reason for hiding this comment

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

Ah, I see. Nice

}

sshExtension := "ms-vscode-remote.remote-ssh"
if flavor == FlavorCodium {
if flavor == FlavorCodium || flavor == FlavorCodiumInsiders {
sshExtension = "jeanp413.open-remote-ssh"
}

// make sure ms-vscode-remote.remote-ssh is installed
out, err := exec.Command(codePath, "--list-extensions").Output()
listArgs := append(codePath, "--list-extensions")
out, err := exec.Command(listArgs[0], listArgs[1:]...).Output()
if err != nil {
return command.WrapCommandError(out, err)
}
Expand All @@ -88,9 +122,9 @@ func openViaCLI(ctx context.Context, workspace, folder string, newWindow bool, f

// install remote-ssh extension
if !found {
args := []string{"--install-extension", sshExtension}
log.Debugf("Run vscode command %s %s", codePath, strings.Join(args, " "))
out, err := exec.CommandContext(ctx, codePath, args...).Output()
args := append(codePath, "--install-extension", sshExtension)
log.Debugf("Run vscode command %s %s", args[0], strings.Join(args[1:], " "))
out, err := exec.CommandContext(ctx, args[0], args[1:]...).Output()
if err != nil {
return fmt.Errorf("install ssh extension: %w", command.WrapCommandError(out, err))
}
Expand All @@ -108,66 +142,56 @@ func openViaCLI(ctx context.Context, workspace, folder string, newWindow bool, f
}
// Needs to be separated by `=` because of windows
folderUriArg := fmt.Sprintf("--folder-uri=vscode-remote://ssh-remote+%s.devpod/%s", workspace, folder)
args = append(codePath, args...)
args = append(args, folderUriArg)
log.Debugf("Run %s command %s %s", flavor.DisplayName(), codePath, strings.Join(args, " "))
out, err = exec.CommandContext(ctx, codePath, args...).CombinedOutput()
log.Debugf("Run %s command %s %s", flavor.DisplayName(), args[0], strings.Join(args[1:], " "))
out, err = exec.CommandContext(ctx, args[0], args[1:]...).CombinedOutput()
if err != nil {
return command.WrapCommandError(out, err)
}

return nil
}

func findCLI(flavor Flavor) string {
if flavor == FlavorStable {
if command.Exists("code") {
return "code"
} else if runtime.GOOS == "darwin" && command.Exists("/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code") {
return "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code"
}

return ""
func existsInFlatpak(packageName string, log log.Logger) bool {
if err := exec.Command("flatpak", "info", packageName).Run(); err == nil {
return true
} else {
log.Debugf("Flatpak command for %s returned: %s", packageName, err)
}
return false
}

if flavor == FlavorInsiders {
if command.Exists("code-insiders") {
return "code-insiders"
} else if runtime.GOOS == "darwin" && command.Exists("/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code") {
return "/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code"
}

return ""
func getCommandArgs(execName, macOSPath, flatpakPackage string, log log.Logger) []string {
if command.Exists(execName) {
return []string{execName}
}

if flavor == FlavorCursor {
if command.Exists("cursor") {
return "cursor"
} else if runtime.GOOS == "darwin" && command.Exists("/Applications/Cursor.app/Contents/Resources/app/bin/cursor") {
return "/Applications/Cursor.app/Contents/Resources/app/bin/cursor"
}

return ""
if runtime.GOOS == "darwin" && command.Exists(macOSPath) {
return []string{macOSPath}
}

if flavor == FlavorPositron {
if command.Exists("positron") {
return "positron"
} else if runtime.GOOS == "darwin" && command.Exists("/Applications/Positron.app/Contents/Resources/app/bin/positron") {
return "/Applications/Positron.app/Contents/Resources/app/bin/positron"
}

return ""
if runtime.GOOS == "linux" && flatpakPackage != "" && command.Exists("flatpak") && existsInFlatpak(flatpakPackage, log) {
return []string{"flatpak", "run", flatpakPackage}
}

if flavor == FlavorCodium {
if command.Exists("codium") {
return "codium"
} else if runtime.GOOS == "darwin" && command.Exists("/Applications/Codium.app/Contents/Resources/app/bin/codium") {
return "/Applications/Codium.app/Contents/Resources/app/bin/codium"
}
return nil
}

return ""
func findCLI(flavor Flavor, log log.Logger) []string {
switch flavor {
case FlavorStable:
return getCommandArgs("code", "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code", FlatpakStable, log)
case FlavorInsiders:
return getCommandArgs("code-insiders", "/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code", FlatpakInsiders, log)
case FlavorCursor:
return getCommandArgs("cursor", "/Applications/Cursor.app/Contents/Resources/app/bin/cursor", "", log)
case FlavorPositron:
return getCommandArgs("positron", "/Applications/Positron.app/Contents/Resources/app/bin/positron", "", log)
case FlavorCodium:
return getCommandArgs("codium", "/Applications/Codium.app/Contents/Resources/app/bin/codium", FlatpakCodium, log)
case FlavorCodiumInsiders:
return getCommandArgs("codium-insiders", "/Applications/CodiumInsiders.app/Contents/Resources/app/bin/codium-insiders", FlatpakCodiumInsiders, log)
}

return ""
return nil
}
11 changes: 6 additions & 5 deletions pkg/ide/vscode/vscode.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,16 @@
type Flavor string

const (
FlavorStable Flavor = "stable"
FlavorInsiders Flavor = "insiders"
FlavorCursor Flavor = "cursor"
FlavorPositron Flavor = "positron"
FlavorCodium Flavor = "codium"
FlavorStable Flavor = "stable"
FlavorInsiders Flavor = "insiders"
FlavorCursor Flavor = "cursor"
FlavorPositron Flavor = "positron"
FlavorCodium Flavor = "codium"
FlavorCodiumInsiders Flavor = "codium-insiders"
)

func (f Flavor) DisplayName() string {
switch f {

Check failure on line 38 in pkg/ide/vscode/vscode.go

View workflow job for this annotation

GitHub Actions / lint

missing cases in switch of type vscode.Flavor: vscode.FlavorCodiumInsiders (exhaustive)
case FlavorStable:
return "VSCode"
case FlavorInsiders:
Expand Down Expand Up @@ -445,7 +446,7 @@
}

folderName := ".vscode-server"
switch flavor {

Check failure on line 449 in pkg/ide/vscode/vscode.go

View workflow job for this annotation

GitHub Actions / lint

missing cases in switch of type vscode.Flavor: vscode.FlavorCodiumInsiders (exhaustive)
case FlavorStable:
folderName = ".vscode-server"
case FlavorInsiders:
Expand Down
Loading