From a3f75b56d5dab9c43d442b1aefc29605121f1c9d Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Wed, 11 Mar 2026 21:41:37 +0900 Subject: [PATCH 1/4] feat: support executing .jar --- pkg/controller/exec/exec.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pkg/controller/exec/exec.go b/pkg/controller/exec/exec.go index 02873da9a..19971f49c 100644 --- a/pkg/controller/exec/exec.go +++ b/pkg/controller/exec/exec.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "log/slog" + "strings" "time" "github.com/aquaproj/aqua/v2/pkg/checksum" @@ -69,8 +70,16 @@ func (c *Controller) Exec(ctx context.Context, logger *slog.Logger, param *confi if err := c.updateTimestamp(findResult.Package); err != nil { slogerr.WithError(logger, err).Warn("update the last used datetime") } + exePath, args := c.wrapExec(findResult.ExePath, args...) - return c.execCommandWithRetry(ctx, logger, findResult.ExePath, exeName, args...) + return c.execCommandWithRetry(ctx, logger, exePath, exeName, args...) +} + +func (c *Controller) wrapExec(exePath string, args ...string) (string, []string) { + if !strings.HasSuffix(exePath, ".jar") { + return exePath, args + } + return "java", append([]string{"-jar", exePath}, args...) } func (c *Controller) updateTimestamp(pkg *config.Package) error { From b3053cb9b030748513effe11cf8e5887390698da Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Wed, 11 Mar 2026 21:56:47 +0900 Subject: [PATCH 2/4] test: add test cases for wrapExec Co-Authored-By: Claude Opus 4.6 --- pkg/controller/exec/exec.go | 4 +- pkg/controller/exec/exec_internal_test.go | 46 +++++++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/pkg/controller/exec/exec.go b/pkg/controller/exec/exec.go index 19971f49c..40ad6ca21 100644 --- a/pkg/controller/exec/exec.go +++ b/pkg/controller/exec/exec.go @@ -70,12 +70,12 @@ func (c *Controller) Exec(ctx context.Context, logger *slog.Logger, param *confi if err := c.updateTimestamp(findResult.Package); err != nil { slogerr.WithError(logger, err).Warn("update the last used datetime") } - exePath, args := c.wrapExec(findResult.ExePath, args...) + exePath, args := wrapExec(findResult.ExePath, args...) return c.execCommandWithRetry(ctx, logger, exePath, exeName, args...) } -func (c *Controller) wrapExec(exePath string, args ...string) (string, []string) { +func wrapExec(exePath string, args ...string) (string, []string) { if !strings.HasSuffix(exePath, ".jar") { return exePath, args } diff --git a/pkg/controller/exec/exec_internal_test.go b/pkg/controller/exec/exec_internal_test.go index cd5a40326..c3e6f431c 100644 --- a/pkg/controller/exec/exec_internal_test.go +++ b/pkg/controller/exec/exec_internal_test.go @@ -3,6 +3,7 @@ package exec import ( "log/slog" "os" + "slices" "testing" "github.com/aquaproj/aqua/v2/pkg/osexec" @@ -43,3 +44,48 @@ func TestController_execCommand(t *testing.T) { }) } } + +func Test_wrapExec(t *testing.T) { + t.Parallel() + data := []struct { + title string + exePath string + args []string + expExePath string + expArgs []string + }{ + { + title: "non jar", + exePath: "/usr/bin/foo", + args: []string{"--help"}, + expExePath: "/usr/bin/foo", + expArgs: []string{"--help"}, + }, + { + title: "jar", + exePath: "/path/to/app.jar", + args: []string{"arg1"}, + expExePath: "java", + expArgs: []string{"-jar", "/path/to/app.jar", "arg1"}, + }, + { + title: "jar without args", + exePath: "/path/to/app.jar", + args: []string{}, + expExePath: "java", + expArgs: []string{"-jar", "/path/to/app.jar"}, + }, + } + for _, d := range data { + t.Run(d.title, func(t *testing.T) { + t.Parallel() + exePath, args := wrapExec(d.exePath, d.args...) + if exePath != d.expExePath { + t.Fatalf("exePath = %s, want %s", exePath, d.expExePath) + } + if !slices.Equal(args, d.expArgs) { + t.Fatalf("args = %v, want %v", args, d.expArgs) + } + }) + } +} From c4c6b92f73e68d55296b7d7e8aaf5837968e0a1b Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Wed, 11 Mar 2026 23:29:55 +0900 Subject: [PATCH 3/4] fix: convert java to the absolute path for unix.Exec unix.Exec's first argument must be an absolute path. --- pkg/controller/exec/controller.go | 3 ++ pkg/controller/exec/exec.go | 19 +++++++++--- pkg/controller/exec/exec_internal_test.go | 38 ++++++++++++++++++++--- 3 files changed, 51 insertions(+), 9 deletions(-) diff --git a/pkg/controller/exec/controller.go b/pkg/controller/exec/controller.go index 1bffdf499..53aa4c2b8 100644 --- a/pkg/controller/exec/controller.go +++ b/pkg/controller/exec/controller.go @@ -5,6 +5,7 @@ import ( "io" "log/slog" "os" + "os/exec" "runtime" "time" @@ -28,6 +29,7 @@ type Controller struct { policyReader PolicyReader enabledXSysExec bool vacuum Vacuum + lookPath func(exeName string) (string, error) } type Vacuum interface { @@ -50,6 +52,7 @@ func New(pkgInstaller Installer, whichCtrl WhichController, executor Executor, o fs: fs, policyReader: policyReader, vacuum: vacuum, + lookPath: exec.LookPath, } } diff --git a/pkg/controller/exec/exec.go b/pkg/controller/exec/exec.go index 40ad6ca21..c02b311cc 100644 --- a/pkg/controller/exec/exec.go +++ b/pkg/controller/exec/exec.go @@ -70,16 +70,27 @@ func (c *Controller) Exec(ctx context.Context, logger *slog.Logger, param *confi if err := c.updateTimestamp(findResult.Package); err != nil { slogerr.WithError(logger, err).Warn("update the last used datetime") } - exePath, args := wrapExec(findResult.ExePath, args...) + exeName, exePath, args, err := c.wrapExec(exeName, findResult.ExePath, args...) + if err != nil { + return err + } return c.execCommandWithRetry(ctx, logger, exePath, exeName, args...) } -func wrapExec(exePath string, args ...string) (string, []string) { +func (c *Controller) wrapExec(exeName, exePath string, args ...string) (string, string, []string, error) { + return wrapExec(c.lookPath, exeName, exePath, args...) +} + +func wrapExec(lookPath func(exeName string) (string, error), exeName, exePath string, args ...string) (string, string, []string, error) { if !strings.HasSuffix(exePath, ".jar") { - return exePath, args + return exeName, exePath, args, nil + } + p, err := lookPath("java") + if err != nil { + return "", "", nil, fmt.Errorf("look up java to execute jar: %w", err) } - return "java", append([]string{"-jar", exePath}, args...) + return "java", p, append([]string{"-jar", exePath}, args...), nil } func (c *Controller) updateTimestamp(pkg *config.Package) error { diff --git a/pkg/controller/exec/exec_internal_test.go b/pkg/controller/exec/exec_internal_test.go index c3e6f431c..d407309e3 100644 --- a/pkg/controller/exec/exec_internal_test.go +++ b/pkg/controller/exec/exec_internal_test.go @@ -49,37 +49,65 @@ func Test_wrapExec(t *testing.T) { t.Parallel() data := []struct { title string + lookPath func(exeName string) (string, error) + exeName string exePath string args []string + expExeName string expExePath string expArgs []string + isErr bool }{ { title: "non jar", + exeName: "foo", exePath: "/usr/bin/foo", args: []string{"--help"}, + expExeName: "foo", expExePath: "/usr/bin/foo", expArgs: []string{"--help"}, }, { - title: "jar", + title: "jar", + lookPath: func(string) (string, error) { + return "/usr/bin/java", nil + }, + exeName: "app", exePath: "/path/to/app.jar", args: []string{"arg1"}, - expExePath: "java", + expExeName: "java", + expExePath: "/usr/bin/java", expArgs: []string{"-jar", "/path/to/app.jar", "arg1"}, }, { - title: "jar without args", + title: "jar without args", + lookPath: func(string) (string, error) { + return "/usr/bin/java", nil + }, + exeName: "app", exePath: "/path/to/app.jar", args: []string{}, - expExePath: "java", + expExeName: "java", + expExePath: "/usr/bin/java", expArgs: []string{"-jar", "/path/to/app.jar"}, }, } for _, d := range data { t.Run(d.title, func(t *testing.T) { t.Parallel() - exePath, args := wrapExec(d.exePath, d.args...) + exeName, exePath, args, err := wrapExec(d.lookPath, d.exeName, d.exePath, d.args...) + if err != nil { + if !d.isErr { + t.Fatalf("unexpected error: %v", err) + } + return + } + if d.isErr { + t.Fatalf("expected error, but got none") + } + if exeName != d.expExeName { + t.Fatalf("exeName = %s, want %s", exeName, d.expExeName) + } if exePath != d.expExePath { t.Fatalf("exePath = %s, want %s", exePath, d.expExePath) } From 8520a49535e33cf57e4322f587c6eb65474e6d25 Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Wed, 11 Mar 2026 23:40:04 +0900 Subject: [PATCH 4/4] refactor: ignore a lint error --- pkg/controller/exec/exec_internal_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/controller/exec/exec_internal_test.go b/pkg/controller/exec/exec_internal_test.go index d407309e3..6102e55f2 100644 --- a/pkg/controller/exec/exec_internal_test.go +++ b/pkg/controller/exec/exec_internal_test.go @@ -45,7 +45,7 @@ func TestController_execCommand(t *testing.T) { } } -func Test_wrapExec(t *testing.T) { +func Test_wrapExec(t *testing.T) { //nolint:funlen t.Parallel() data := []struct { title string