Skip to content

Commit 0038bd9

Browse files
author
Ben Morgan
committed
Fix recursive download and dependency ordering
This involves rewriting substantial potions of the pacman/graph package. This resolves issue #33.
1 parent 35c0527 commit 0038bd9

File tree

378 files changed

+74261
-2100
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

378 files changed

+74261
-2100
lines changed

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ Repoctl Releases
44
## Version 0.20 (unreleased)
55

66
- New: `search` command added.
7+
- New `down` command learned `-n` (`--dry-run`) option.
78
- Update: `new config` command now backs up existing configuration files.
89
- Update: `github.com/goulash/pacman` dependency moved into repository.
910
- Bugfix: `version` command does not show entire configuration.
1011
- Bugfix: issue #46, do not panic or print errors with large repos.
12+
- Bugfix: issue #33, recursive download and dependency resolution broken.
1113

1214
## Version 0.19 (25 October 2019)
1315
This release fixes several bugs and adds support for signatures and Zst

cmd/repoctl/down.go

Lines changed: 71 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@ import (
88
"fmt"
99
"os"
1010

11+
"github.com/cassava/repoctl/pacman/aur"
1112
"github.com/cassava/repoctl/pacman/graph"
1213
"github.com/cassava/repoctl/pacman/pkgutil"
1314
"github.com/spf13/cobra"
1415
)
1516

1617
var (
1718
downDest string
19+
downDryRun bool
1820
downClobber bool
1921
downExtract bool
2022
downUpgrades bool
@@ -27,6 +29,7 @@ func init() {
2729
MainCmd.AddCommand(downCmd)
2830

2931
downCmd.Flags().StringVarP(&downDest, "dest", "d", "", "output directory for tarballs")
32+
downCmd.Flags().BoolVarP(&downDryRun, "dry-run", "n", false, "don't download any packages")
3033
downCmd.Flags().BoolVarP(&downClobber, "clobber", "l", false, "delete conflicting files and folders")
3134
downCmd.Flags().BoolVarP(&downExtract, "extract", "e", true, "extract the downloaded tarballs")
3235
downCmd.Flags().BoolVarP(&downUpgrades, "upgrades", "u", false, "download tarballs for all upgrades")
@@ -40,13 +43,38 @@ var downCmd = &cobra.Command{
4043
Aliases: []string{"download"},
4144
Short: "download and extract tarballs from AUR",
4245
Long: `Download and extract tarballs from AUR for given packages.
43-
Alternatively, all packages, or those with updates can be downloaded.
44-
Options specified are additive, not exclusive.
4546
46-
By default, tarballs are deleted after being extracted, and are placed
47-
in the current directory.
47+
Alternatively, all packages, or those with updates can be downloaded.
48+
Options specified are additive, not exclusive.
49+
50+
By default, tarballs are deleted after being extracted, and are placed
51+
in the current directory.
52+
53+
Packages can also be downloaded recursively, and the list that these
54+
dependencies should be built can be saved. For example, to download
55+
all updates to the repository and build them in approximately the
56+
correct order:
57+
58+
repoctl down -o build-order.txt -u
59+
for pkg in $(cat build-order.txt); do
60+
(
61+
cd $pkg
62+
makepkg -si
63+
ok=$?
64+
if $ok; then
65+
repoctl add -m *.pkg.tar*
66+
cd ..
67+
rm -rf $pkg
68+
fi
69+
)
70+
done
71+
72+
Caveat: Automatic dependency resolution does not currently handle version
73+
resolution or library specifications, as noted in the Arch wiki at:
74+
https://wiki.archlinux.org/index.php/PKGBUILD#Dependencies
4875
`,
49-
Example: ` repoctl down -u`,
76+
Example: ` repoctl down -u
77+
repoctl down -o build-order.txt -u`,
5078
RunE: func(cmd *cobra.Command, args []string) error {
5179
// First, populate the initial list of packages to download.
5280
var list []string
@@ -70,28 +98,51 @@ in the current directory.
7098

7199
// If no dependencies are wanted, then get to it right away:
72100
if !downRecurse && downOrder == "" {
101+
// There's not much point to a try run here, but we should respect
102+
// the option nevertheless.
103+
if downDryRun {
104+
return nil
105+
}
73106
return Repo.Download(nil, downDest, downExtract, downClobber, list...)
74107
}
75108

76-
// Otherwise, get the dependencies:
77-
g, err := Repo.DependencyGraph(nil, list...)
109+
// Otherwise, get the dependency list and download the packages:
110+
aps, err := downDependencies(list)
78111
if err != nil {
79112
return err
80113
}
81-
_, aps, ups := graph.Dependencies(g)
82-
if downOrder != "" {
83-
f, err := os.Create(downOrder)
84-
if err != nil {
85-
return err
86-
}
87-
for _, p := range aps {
88-
fmt.Fprintln(f, p.Name)
89-
}
90-
f.Close()
91-
}
92-
for _, u := range ups {
93-
fmt.Fprintf(os.Stderr, "warning: unknown package %s\n", u)
114+
// Don't download any packages if dry run is activated.
115+
if downDryRun {
116+
return nil
94117
}
95118
return Repo.DownloadPackages(nil, aps, downDest, downExtract, downClobber)
96119
},
97120
}
121+
122+
func downDependencies(packages []string) (aur.Packages, error) {
123+
g, err := Repo.DependencyGraph(nil, packages...)
124+
if err != nil {
125+
return nil, err
126+
}
127+
_, aps, ups := graph.Dependencies(g)
128+
if downOrder != "" {
129+
f, err := os.Create(downOrder)
130+
if err != nil {
131+
return nil, err
132+
}
133+
134+
for i := len(aps); i != 0; i-- {
135+
fmt.Fprintln(f, aps[i-1].Name)
136+
}
137+
f.Close()
138+
}
139+
for _, u := range ups {
140+
fmt.Fprintf(os.Stderr, "warning: unknown package %s\n", u)
141+
iter := g.To(g.NodeWithName(u).ID())
142+
for iter.Next() {
143+
node := iter.Node().(*graph.Node)
144+
fmt.Fprintf(os.Stderr, " required by: %s\n", node.PkgName())
145+
}
146+
}
147+
return aps, nil
148+
}

pacman/graph/factory.go

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ package graph
22

33
import (
44
"os"
5+
"regexp"
56

6-
"github.com/goulash/errs"
77
"github.com/cassava/repoctl/pacman"
88
"github.com/cassava/repoctl/pacman/aur"
9+
"github.com/goulash/errs"
910
)
1011

1112
// A Factory creates a dependency graph given a set of packages.
@@ -38,6 +39,7 @@ type Factory struct {
3839
// as a leaf in the graph, since we assume that pacman can resolve those
3940
// dependencies.
4041
func NewFactory(ignoreRepos ...string) (*Factory, error) {
42+
re := regexp.MustCompile(`(=|>|<).*$`)
4143
f := Factory{
4244
skipInstalled: false,
4345
truncate: false,
@@ -46,6 +48,16 @@ func NewFactory(ignoreRepos ...string) (*Factory, error) {
4648
deps := make([]string, 0, len(p.PkgDepends())+len(p.PkgMakeDepends()))
4749
deps = append(deps, p.PkgDepends()...)
4850
deps = append(deps, p.PkgMakeDepends()...)
51+
for i, p := range deps {
52+
// Quote from: https://wiki.archlinux.org/index.php/PKGBUILD
53+
// > Version restrictions can be specified with comparison
54+
// > operators, e.g. depends=('foobar>=1.8.0'); if multiple
55+
// > restrictions are needed, the dependency can be repeated for
56+
// > each, e.g. depends=('foobar>=1.8.0' 'foobar<2.0.0').
57+
if re.MatchString(p) {
58+
deps[i] = re.ReplaceAllLiteralString(p, "")
59+
}
60+
}
4961
return deps
5062
},
5163
}
@@ -132,8 +144,8 @@ func (f *Factory) NewGraph(pkgs aur.Packages) (*Graph, error) {
132144
}
133145

134146
// As long as we have new packages to process, continue.
135-
for len(lst) == 0 {
136-
new := make([]*Node, 0)
147+
for len(lst) != 0 {
148+
discovered := make([]*Node, 0)
137149
unavailable := make(map[string]bool, 0)
138150
pending := make(map[string][]*Node)
139151

@@ -157,8 +169,9 @@ func (f *Factory) NewGraph(pkgs aur.Packages) (*Graph, error) {
157169
u := g.NewNode(p)
158170
if !f.truncate {
159171
// Process this package for dependencies
160-
new = append(new, u)
172+
discovered = append(discovered, u)
161173
}
174+
g.AddNode(u)
162175
g.AddEdgeFromTo(v, u)
163176
continue
164177
}
@@ -172,17 +185,17 @@ func (f *Factory) NewGraph(pkgs aur.Packages) (*Graph, error) {
172185
}
173186

174187
// This may be called for AUR or unknown packages
175-
addFetchedPkg := func(p pacman.AnyPackage) {
188+
addFetchedPkg := func(p pacman.AnyPackage) *Node {
176189
u := g.NewNode(p)
177-
new = append(new, u)
178190
g.AddNode(u)
179191
for _, v := range pending[u.PkgName()] {
180192
g.AddEdgeFromTo(v, u)
181193
}
194+
return u
182195
}
183196

184197
// Get all unavailable packages from AUR:
185-
fromAUR := make([]string, len(unavailable))
198+
fromAUR := make([]string, 0, len(unavailable))
186199
for k := range unavailable {
187200
fromAUR = append(fromAUR, k)
188201
}
@@ -207,10 +220,11 @@ func (f *Factory) NewGraph(pkgs aur.Packages) (*Graph, error) {
207220
}
208221
}
209222
for _, p := range pkgs {
210-
addFetchedPkg(p)
223+
u := addFetchedPkg(p)
224+
discovered = append(discovered, u)
211225
}
212226

213-
lst = new
227+
lst = discovered
214228
}
215229

216230
return g, nil

pacman/graph/functions.go

Lines changed: 3 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
package graph
22

33
import (
4-
"github.com/gonum/graph"
5-
"github.com/gonum/graph/traverse"
64
"github.com/cassava/repoctl/pacman"
75
"github.com/cassava/repoctl/pacman/aur"
6+
7+
"gonum.org/v1/gonum/graph/topo"
88
)
99

1010
// Dependencies returns a list of all dependencies in the graph,
@@ -15,7 +15,7 @@ func Dependencies(g *Graph) (pacman.Packages, aur.Packages, []string) {
1515
ups := make([]string, 0)
1616

1717
names := make(map[string]bool)
18-
nodes := AllNodesBottomUp(g)
18+
nodes, _ := topo.Sort(g)
1919
for _, vn := range nodes {
2020
n := vn.(*Node)
2121
if names[n.PkgName()] {
@@ -38,69 +38,3 @@ func Dependencies(g *Graph) (pacman.Packages, aur.Packages, []string) {
3838
}
3939
return rps, aps, ups
4040
}
41-
42-
// Roots returns all the root nodes for a directed graph.
43-
func Roots(g graph.Directed) []graph.Node {
44-
roots := make([]graph.Node, 0)
45-
for _, n := range g.Nodes() {
46-
if g.To(n) == nil || len(g.To(n)) == 0 {
47-
roots = append(roots, n)
48-
}
49-
}
50-
return roots
51-
}
52-
53-
// NodesBottomUp returns the subtree from the bottom levels upwards to the root.
54-
// The nodes may appear multiple times however.
55-
func NodesBottomUp(g graph.Directed, root graph.Node) []graph.Node {
56-
nodes := make([]graph.Node, 0)
57-
nodes = append(nodes, root)
58-
bfs := traverse.BreadthFirst{}
59-
bfs.Walk(g, root, func(v graph.Node, _ int) bool {
60-
nodes = append(nodes, v)
61-
return true
62-
})
63-
64-
// Reverse the list
65-
sz := len(nodes)
66-
last := sz - 1
67-
for i := 0; i < sz/2; i++ {
68-
tmp := nodes[i]
69-
nodes[i] = nodes[last-i]
70-
nodes[last-i] = tmp
71-
}
72-
return uniqueNodes(nodes)
73-
}
74-
75-
// AllNodesBottomUp returns for all roots the nodes bottom-up.
76-
func AllNodesBottomUp(g graph.Directed) []graph.Node {
77-
nodes := make([]graph.Node, 0)
78-
for _, root := range Roots(g) {
79-
nodes = append(nodes, NodesBottomUp(g, root)...)
80-
}
81-
return uniqueNodes(nodes)
82-
}
83-
84-
func uniqueStrings(list []string) []string {
85-
xs := make(map[string]bool)
86-
lst := make([]string, 0, len(list))
87-
for _, x := range list {
88-
if !xs[x] {
89-
xs[x] = true
90-
lst = append(lst, x)
91-
}
92-
}
93-
return lst
94-
}
95-
96-
func uniqueNodes(list []graph.Node) []graph.Node {
97-
xs := make(map[int]bool)
98-
lst := make([]graph.Node, 0, len(list))
99-
for _, x := range list {
100-
if !xs[x.ID()] {
101-
xs[x.ID()] = true
102-
lst = append(lst, x)
103-
}
104-
}
105-
return lst
106-
}

0 commit comments

Comments
 (0)