Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
33 changes: 33 additions & 0 deletions cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
alpm "github.com/Jguer/dyalpm"
"github.com/leonelquinteros/gotext"

"github.com/Jguer/yay/v12/pkg/chroot"
"github.com/Jguer/yay/v12/pkg/completion"
"github.com/Jguer/yay/v12/pkg/db"
"github.com/Jguer/yay/v12/pkg/download"
Expand Down Expand Up @@ -146,6 +147,38 @@ func handleCmd(ctx context.Context, run *runtime.Runtime,
run.CmdBuilder.SudoLoop()
}

// Full chroot handling (mirrors paru behaviour): create/update/run inside chroot
if run.Cfg.Chroot {
// lazy import of chroot package
c := &chroot.Chroot{
Sudo: run.Cfg.SudoBin,
Path: run.Cfg.ChrootDir,
PacmanConf: run.Cfg.PacmanConf,
MakepkgConf: run.Cfg.MakepkgConf,
MFlags: strings.Fields(run.Cfg.MFlags),
Ro: []string{},
Rw: run.PacmanConf.CacheDir,
RootPkgs: run.Cfg.RootChrootPkgs,
}

if cmdArgs.ExistsArg("p", "print") {
run.Logger.Println(run.Cfg.ChrootDir)
return nil
}

if !c.Exists() {
if err := c.Create(); err != nil {
return err
}
}

if cmdArgs.ExistsArg("u", "sysupgrade") {
if err := c.Update(); err != nil {
return err
}
}
}

switch cmdArgs.Op {
case "V", "version":
handleVersion(run.Logger)
Expand Down
1 change: 1 addition & 0 deletions local_install.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ func installLocalPKGBUILD(
grapher := dep.NewGrapher(dbExecutor, aurCache, false, settings.NoConfirm,
cmdArgs.ExistsDouble("d", "nodeps"), noCheck, cmdArgs.ExistsArg("needed"),
run.Logger.Child("grapher"))
grapher.SetChrootMode(run.Cfg.Chroot)
graph, err := grapher.GraphFromSrcInfos(ctx, nil, srcInfos)
if err != nil {
return err
Expand Down
150 changes: 150 additions & 0 deletions pkg/chroot/chroot.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package chroot

import (
"bufio"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
)

type Chroot struct {
Sudo string
Path string
PacmanConf string
MakepkgConf string
MFlags []string
Ro []string
Rw []string
RootPkgs []string
}

func (c *Chroot) Exists() bool {
p := filepath.Join(c.Path, "root")
return p != "" && fileExists(p)
}

func fileExists(p string) bool {
_, err := os.Stat(p)
return err == nil
}

func (c *Chroot) writePacmanConfTmp() (string, error) {
f, err := os.CreateTemp("/tmp", "pacman.conf.*")
if err != nil {
return "", err
}
defer f.Close()

in, err := os.Open(c.PacmanConf)
if err != nil {
return "", err
}
defer in.Close()

// copy, but filter DBPath lines which may break pacstrap/mkarchroot
scanner := bufio.NewScanner(in)
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "DBPath") {
continue
}
if _, err := f.WriteString(line + "\n"); err != nil {
return "", err
}
}
if err := scanner.Err(); err != nil {
return "", err
}

return f.Name(), nil
}

func (c *Chroot) Create() error {
// create base dir
args := []string{"install", "-dm755", c.Path}
if c.Sudo != "" {
cmd := exec.Command(c.Sudo, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return err
}
} else {
if err := os.MkdirAll(c.Path, 0o755); err != nil {
return err
}
}

tmp, err := c.writePacmanConfTmp()
if err != nil {
return err
}

dir := filepath.Join(c.Path, "root")

// mkarchroot -C tmp [-M makepkg_conf] dir [pkgs...]
mkargs := []string{"-C", tmp}
if c.MakepkgConf != "" {
mkargs = append(mkargs, "-M", c.MakepkgConf)
}
mkargs = append(mkargs, dir)
// ensure at least one package is provided (mkarchroot requires it)
if len(c.RootPkgs) == 0 {
c.RootPkgs = []string{"base-devel"}
}
mkargs = append(mkargs, c.RootPkgs...)

var cmd *exec.Cmd
if c.Sudo != "" {
cmd = exec.Command(c.Sudo, append([]string{"mkarchroot"}, mkargs...)...)
} else {
cmd = exec.Command("mkarchroot", mkargs...)
}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("mkarchroot failed: %w", err)
}

return nil
}

func (c *Chroot) Update() error {
// Run pacman -Syu inside the chroot using the same Run helper so
// the chroot path and pacman.conf handling are applied consistently.
return c.Run([]string{"pacman", "-Syu", "--noconfirm"})
}

func (c *Chroot) Run(args []string) error {
tmp, err := c.writePacmanConfTmp()
if err != nil {
return err
}

cmdArgs := []string{"arch-nspawn", "-C", tmp, "-M", c.MakepkgConf, filepath.Join(c.Path, "root")}

for _, f := range c.Ro {
cmdArgs = append(cmdArgs, "--bind-ro", f)
}
for _, f := range c.Rw {
cmdArgs = append(cmdArgs, "--bind", f)
}

cmdArgs = append(cmdArgs, args...)

var cmd *exec.Cmd
// If a privilege elevator is configured, run via it, otherwise run arch-nspawn directly
if c.Sudo != "" {
cmd = exec.Command(c.Sudo, cmdArgs...)
} else {
cmd = exec.Command(cmdArgs[0], cmdArgs[1:]...)
}

cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

return cmd.Run()
}
32 changes: 32 additions & 0 deletions pkg/dep/dep_graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ type Grapher struct {
noDeps bool // If true, the graph will not include dependencies
noCheckDeps bool // If true, the graph will not include check dependencies
needed bool // If true, the graph will only include packages that are not installed
// chrootMode forces locally-installed AUR makedeps back into the dep graph so they
// are rebuilt and can be injected into the clean chroot via -I <archive>.
chrootMode bool
}

func NewGrapher(dbExecutor db.Executor, aurCache aurc.QueryClient,
Expand All @@ -125,6 +128,14 @@ func NewGrapher(dbExecutor db.Executor, aurCache aurc.QueryClient,
}
}

// SetChrootMode enables chroot-aware dependency resolution. In this mode,
// locally-installed AUR packages (those not present in any sync repo) are not
// treated as satisfied: they are re-queried from AUR and added to the build
// graph so that fresh archives can be produced and injected into the chroot.
func (g *Grapher) SetChrootMode(enabled bool) {
g.chrootMode = enabled
}

func NewGraph() *topo.Graph[string, *InstallInfo] {
return topo.New[string, *InstallInfo]()
}
Expand Down Expand Up @@ -667,6 +678,14 @@ func (g *Grapher) addNodes(
}
}

// In chroot mode, a locally-installed package that is NOT present in any
// sync repo is likely an AUR package. The clean chroot won't have it, so
// we must rebuild it and inject the archive via -I. Skip the "satisfied"
// shortcut and let the package fall through to the AUR lookup below.
if g.chrootMode && g.dbExecutor.SyncSatisfier(depString) == nil {
continue
}

targetsToFind.Remove(depString)
}

Expand Down Expand Up @@ -736,6 +755,19 @@ func (g *Grapher) addNodes(
// Add missing to graph
for _, depString := range targetsToFind.ToSlice() {
depName, mod, ver := splitDep(depString)

// In chroot mode we may have let locally-installed non-repo packages fall
// through the installed/sync checks hoping the AUR lookup would pick them
// up. If AUR lookup also missed them (e.g. they are from an unofficial
// repo), treat them as satisfied rather than blocking the entire install.
if g.chrootMode && g.dbExecutor.LocalSatisfierExists(depString) {
g.logger.Warnln(gotext.Get(
"chroot: %s is locally installed but not found in repos or AUR; "+
"it may be missing from the chroot", depName))

continue
}

// no dep found. add as missing
if err := graph.DependOn(depName, parentPkgName); err != nil {
g.logger.Warnln("missing dep warn:", depString, parentPkgName, err)
Expand Down
5 changes: 5 additions & 0 deletions pkg/settings/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,11 @@ func (c *Configuration) handleOption(option, value string) bool {
c.MakepkgConf = ""
case "pacman":
c.PacmanBin = value
// chroot option
case "chroot":
c.Chroot = boolValue
case "chrootdir":
c.ChrootDir = value
case "git":
c.GitBin = value
case "gpg":
Expand Down
8 changes: 8 additions & 0 deletions pkg/settings/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ type Configuration struct {
SaveConfig bool `json:"-" lua:"-"`
Mode parser.TargetMode `json:"-" lua:"-"`
ReBuild parser.RebuildMode `json:"rebuild" lua:"rebuild"`

Chroot bool `json:"chroot"`
ChrootDir string `json:"chrootdir"`
RootChrootPkgs []string `json:"root_chroot_pkgs"`
}

// SaveConfig writes yay config to file.
Expand Down Expand Up @@ -119,6 +123,7 @@ func (c *Configuration) expandEnv() {
c.MakepkgConf = expandEnvOrHome(c.MakepkgConf)
c.PacmanBin = expandEnvOrHome(c.PacmanBin)
c.PacmanConf = expandEnvOrHome(c.PacmanConf)
c.ChrootDir = expandEnvOrHome(c.ChrootDir)
c.GpgFlags = os.ExpandEnv(c.GpgFlags)
c.MFlags = os.ExpandEnv(c.MFlags)
c.GitFlags = os.ExpandEnv(c.GitFlags)
Expand Down Expand Up @@ -202,6 +207,9 @@ func DefaultConfig(version string) *Configuration {
PacmanBin: "pacman",
PGPFetch: true,
PacmanConf: "/etc/pacman.conf",
Chroot: false,
ChrootDir: "/var/lib/aurbuild",
RootChrootPkgs: []string{"base-devel"},
GpgFlags: "",
MFlags: "",
GitFlags: "",
Expand Down
4 changes: 4 additions & 0 deletions pkg/settings/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,9 @@ func isArg(arg string) bool {
case "singlelineresults":
case "doublelineresults":
case "separatesources":
// chroot option
case "chroot":
case "chrootdir":
default:
return false
}
Expand Down Expand Up @@ -532,6 +535,7 @@ func hasParam(arg string) bool {
case "completioninterval":
case "sortby":
case "searchby":
case "chrootdir":
default:
return false
}
Expand Down
Loading