diff --git a/aur_install.go b/aur_install.go index e74e2d58..52ae1450 100644 --- a/aur_install.go +++ b/aur_install.go @@ -27,10 +27,14 @@ type ( exeCmd exe.ICmdBuilder vcsStore vcs.Store targetMode parser.TargetMode + downloadOnly bool } ) -func NewInstaller(dbExecutor db.Executor, exeCmd exe.ICmdBuilder, vcsStore vcs.Store, targetMode parser.TargetMode) *Installer { +func NewInstaller(dbExecutor db.Executor, + exeCmd exe.ICmdBuilder, vcsStore vcs.Store, targetMode parser.TargetMode, + downloadOnly bool, +) *Installer { return &Installer{ dbExecutor: dbExecutor, postInstallHooks: []PostInstallHookFunc{}, @@ -38,6 +42,7 @@ func NewInstaller(dbExecutor db.Executor, exeCmd exe.ICmdBuilder, vcsStore vcs.S exeCmd: exeCmd, vcsStore: vcsStore, targetMode: targetMode, + downloadOnly: downloadOnly, } } @@ -229,7 +234,7 @@ func (installer *Installer) buildPkg(ctx context.Context, } switch { - case needed && installer.pkgsAreAlreadyInstalled(pkgdests, pkgVersion): + case needed && installer.pkgsAreAlreadyInstalled(pkgdests, pkgVersion) || installer.downloadOnly: args = []string{"-c", "--nobuild", "--noextract", "--ignorearch"} pkgdests = map[string]string{} text.Warnln(gotext.Get("%s is up to date -- skipping", text.Cyan(base+"-"+pkgVersion))) @@ -250,6 +255,10 @@ func (installer *Installer) buildPkg(ctx context.Context, return nil, errMake } + if installer.downloadOnly { + return map[string]string{}, nil + } + return pkgdests, nil } diff --git a/aur_install_test.go b/aur_install_test.go index 708f0cc0..637a1d2e 100644 --- a/aur_install_test.go +++ b/aur_install_test.go @@ -127,7 +127,7 @@ func TestInstaller_InstallNeeded(t *testing.T) { cmdBuilder.Runner = mockRunner - installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, parser.ModeAny) + installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, parser.ModeAny, false) cmdArgs := parser.MakeArguments() cmdArgs.AddArg("needed") @@ -401,7 +401,7 @@ func TestInstaller_InstallMixedSourcesAndLayers(t *testing.T) { cmdBuilder.Runner = mockRunner - installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, parser.ModeAny) + installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, parser.ModeAny, false) cmdArgs := parser.MakeArguments() cmdArgs.AddTarget("yay") @@ -454,7 +454,7 @@ func TestInstaller_RunPostHooks(t *testing.T) { cmdBuilder.Runner = mockRunner - installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, parser.ModeAny) + installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, parser.ModeAny, false) called := false hook := func(ctx context.Context) error { @@ -555,7 +555,7 @@ func TestInstaller_CompileFailed(t *testing.T) { cmdBuilder.Runner = mockRunner - installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, parser.ModeAny) + installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, parser.ModeAny, false) cmdArgs := parser.MakeArguments() cmdArgs.AddArg("needed") @@ -713,7 +713,7 @@ func TestInstaller_InstallSplitPackage(t *testing.T) { cmdBuilder.Runner = mockRunner - installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, parser.ModeAny) + installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, parser.ModeAny, false) cmdArgs := parser.MakeArguments() cmdArgs.AddTarget("jellyfin") @@ -750,3 +750,152 @@ func TestInstaller_InstallSplitPackage(t *testing.T) { }) } } + +func TestInstaller_InstallDownloadOnly(t *testing.T) { + t.Parallel() + + makepkgBin := t.TempDir() + "/makepkg" + pacmanBin := t.TempDir() + "/pacman" + f, err := os.OpenFile(makepkgBin, os.O_RDONLY|os.O_CREATE, 0o755) + require.NoError(t, err) + require.NoError(t, f.Close()) + + f, err = os.OpenFile(pacmanBin, os.O_RDONLY|os.O_CREATE, 0o755) + require.NoError(t, err) + require.NoError(t, f.Close()) + + type testCase struct { + desc string + isInstalled bool + isBuilt bool + wantShow []string + wantCapture []string + } + + testCases := []testCase{ + { + desc: "not installed and not built", + isInstalled: false, + isBuilt: false, + wantShow: []string{ + "makepkg --nobuild -fC --ignorearch", + "makepkg -c --nobuild --noextract --ignorearch", + }, + wantCapture: []string{"makepkg --packagelist"}, + }, + { + desc: "not installed and built", + isInstalled: false, + isBuilt: true, + wantShow: []string{ + "makepkg --nobuild -fC --ignorearch", + "makepkg -c --nobuild --noextract --ignorearch", + }, + wantCapture: []string{"makepkg --packagelist"}, + }, + { + desc: "installed", + isInstalled: true, + isBuilt: false, + wantShow: []string{ + "makepkg --nobuild -fC --ignorearch", + "makepkg -c --nobuild --noextract --ignorearch", + }, + wantCapture: []string{"makepkg --packagelist"}, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.desc, func(td *testing.T) { + tmpDir := td.TempDir() + pkgTar := tmpDir + "/yay-91.0.0-1-x86_64.pkg.tar.zst" + + captureOverride := func(cmd *exec.Cmd) (stdout string, stderr string, err error) { + return pkgTar, "", nil + } + + i := 0 + showOverride := func(cmd *exec.Cmd) error { + i++ + if i == 2 { + if !tc.isBuilt { + f, err := os.OpenFile(pkgTar, os.O_RDONLY|os.O_CREATE, 0o666) + require.NoError(td, err) + require.NoError(td, f.Close()) + } + } + return nil + } + + // create a mock file + if tc.isBuilt { + f, err := os.OpenFile(pkgTar, os.O_RDONLY|os.O_CREATE, 0o666) + require.NoError(td, err) + require.NoError(td, f.Close()) + } + + isCorrectInstalledOverride := func(string, string) bool { + return tc.isInstalled + } + + mockDB := &mock.DBExecutor{IsCorrectVersionInstalledFn: isCorrectInstalledOverride} + mockRunner := &exe.MockRunner{CaptureFn: captureOverride, ShowFn: showOverride} + cmdBuilder := &exe.CmdBuilder{ + MakepkgBin: makepkgBin, + SudoBin: "su", + PacmanBin: pacmanBin, + Runner: mockRunner, + SudoLoopEnabled: false, + } + + cmdBuilder.Runner = mockRunner + + installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, parser.ModeAny, true) + + cmdArgs := parser.MakeArguments() + cmdArgs.AddTarget("yay") + + pkgBuildDirs := map[string]string{ + "yay": tmpDir, + } + + targets := []map[string]*dep.InstallInfo{ + { + "yay": { + Source: dep.AUR, + Reason: dep.Explicit, + Version: "91.0.0-1", + SrcinfoPath: ptrString(tmpDir + "/.SRCINFO"), + AURBase: ptrString("yay"), + }, + }, + } + + errI := installer.Install(context.Background(), cmdArgs, targets, pkgBuildDirs) + require.NoError(td, errI) + + require.Len(td, mockRunner.ShowCalls, len(tc.wantShow)) + require.Len(td, mockRunner.CaptureCalls, len(tc.wantCapture)) + require.Empty(td, installer.failedAndIgnored) + + for i, call := range mockRunner.ShowCalls { + show := call.Args[0].(*exec.Cmd).String() + show = strings.ReplaceAll(show, tmpDir, "/testdir") // replace the temp dir with a static path + show = strings.ReplaceAll(show, makepkgBin, "makepkg") + show = strings.ReplaceAll(show, pacmanBin, "pacman") + + // options are in a different order on different systems and on CI root user is used + assert.Subset(td, strings.Split(show, " "), strings.Split(tc.wantShow[i], " "), show) + } + + for i, call := range mockRunner.CaptureCalls { + capture := call.Args[0].(*exec.Cmd).String() + capture = strings.ReplaceAll(capture, tmpDir, "/testdir") // replace the temp dir with a static path + capture = strings.ReplaceAll(capture, makepkgBin, "makepkg") + capture = strings.ReplaceAll(capture, pacmanBin, "pacman") + assert.Subset(td, strings.Split(capture, " "), strings.Split(tc.wantCapture[i], " "), capture) + } + }) + } +} diff --git a/sync.go b/sync.go index 7b9cafcd..447a49e0 100644 --- a/sync.go +++ b/sync.go @@ -104,7 +104,9 @@ func (o *OperationService) Run(ctx context.Context, return nil } preparer := NewPreparer(o.dbExecutor, o.cfg.Runtime.CmdBuilder, o.cfg) - installer := NewInstaller(o.dbExecutor, o.cfg.Runtime.CmdBuilder, o.cfg.Runtime.VCSStore, o.cfg.Runtime.Mode) + installer := NewInstaller(o.dbExecutor, o.cfg.Runtime.CmdBuilder, + o.cfg.Runtime.VCSStore, o.cfg.Runtime.Mode, + cmdArgs.ExistsArg("w", "downloadonly")) pkgBuildDirs, errInstall := preparer.Run(ctx, os.Stdout, targets) if errInstall != nil { @@ -156,8 +158,10 @@ func (o *OperationService) Run(ctx context.Context, multiErr.Add(err) } - if err := srcInfo.UpdateVCSStore(ctx, targets, installer.failedAndIgnored); err != nil { - text.Warnln(err) + if !cmdArgs.ExistsArg("w", "downloadonly") { + if err := srcInfo.UpdateVCSStore(ctx, targets, installer.failedAndIgnored); err != nil { + text.Warnln(err) + } } if err := installer.RunPostInstallHooks(ctx); err != nil {