Merge pull request #2170 from infosiftr/fix-bashbrew-build-sorting
Move "build order" sorting functions into a common file and refactor them to use topsort properly and via a single unified function (single node per logical "thing" we're sorting, resolve tag names to canonical node names appropriately)
This commit is contained in:
commit
e4d43d8581
|
|
@ -4,11 +4,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
"pault.ag/go/topsort"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO somewhere, ensure that the Docker engine we're talking to is API version 1.22+ (Docker 1.10+)
|
// TODO somewhere, ensure that the Docker engine we're talking to is API version 1.22+ (Docker 1.10+)
|
||||||
|
|
@ -52,91 +49,6 @@ func initDefaultCachePath() string {
|
||||||
return filepath.Join(xdgCache, "bashbrew")
|
return filepath.Join(xdgCache, "bashbrew")
|
||||||
}
|
}
|
||||||
|
|
||||||
func repos(all bool, args ...string) ([]string, error) {
|
|
||||||
ret := []string{}
|
|
||||||
|
|
||||||
if all {
|
|
||||||
dir, err := os.Open(defaultLibrary)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
names, err := dir.Readdirnames(-1)
|
|
||||||
dir.Close()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
sort.Strings(names)
|
|
||||||
for _, name := range names {
|
|
||||||
ret = append(ret, filepath.Join(defaultLibrary, name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = append(ret, args...)
|
|
||||||
|
|
||||||
if len(ret) < 1 {
|
|
||||||
return nil, fmt.Errorf(`need at least one repo (either explicitly or via "--all")`)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func sortRepos(repos []string) ([]string, error) {
|
|
||||||
if noSortFlag || len(repos) <= 1 {
|
|
||||||
return repos, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
network := topsort.NewNetwork()
|
|
||||||
|
|
||||||
rs := []*Repo{}
|
|
||||||
for _, repo := range repos {
|
|
||||||
r, err := fetch(repo)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rs = append(rs, r)
|
|
||||||
network.AddNode(r.Identifier(), repo)
|
|
||||||
network.AddNode(r.RepoName, repo)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, r := range rs {
|
|
||||||
for _, entry := range r.Entries() {
|
|
||||||
from, err := r.DockerFrom(&entry)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if i := strings.IndexRune(from, ':'); i >= 0 {
|
|
||||||
// we want "repo -> repo" relations, no tags
|
|
||||||
from = from[:i]
|
|
||||||
}
|
|
||||||
if from == r.RepoName {
|
|
||||||
// "a:a -> a:b" is OK (ignore that here -- see Repo.SortedEntries for that)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// TODO somehow reconcile/avoid "a:a -> b:b, b:b -> a:c" (which will exhibit here as cyclic)
|
|
||||||
network.AddEdgeIfExists(from, r.Identifier())
|
|
||||||
network.AddEdgeIfExists(from, r.RepoName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nodes, err := network.Sort()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ret := []string{}
|
|
||||||
seen := map[string]bool{}
|
|
||||||
for _, node := range nodes {
|
|
||||||
repo := node.Value.(string)
|
|
||||||
if seen[repo] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
seen[repo] = true
|
|
||||||
ret = append(ret, repo)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
app := cli.NewApp()
|
app := cli.NewApp()
|
||||||
app.Name = "bashbrew"
|
app.Name = "bashbrew"
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,41 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker-library/go-dockerlibrary/manifest"
|
"github.com/docker-library/go-dockerlibrary/manifest"
|
||||||
"pault.ag/go/topsort"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func repos(all bool, args ...string) ([]string, error) {
|
||||||
|
ret := []string{}
|
||||||
|
|
||||||
|
if all {
|
||||||
|
dir, err := os.Open(defaultLibrary)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
names, err := dir.Readdirnames(-1)
|
||||||
|
dir.Close()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sort.Strings(names)
|
||||||
|
for _, name := range names {
|
||||||
|
ret = append(ret, filepath.Join(defaultLibrary, name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = append(ret, args...)
|
||||||
|
|
||||||
|
if len(ret) < 1 {
|
||||||
|
return nil, fmt.Errorf(`need at least one repo (either explicitly or via "--all")`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
func latestizeRepoTag(repoTag string) string {
|
func latestizeRepoTag(repoTag string) string {
|
||||||
if repoTag != "scratch" && strings.IndexRune(repoTag, ':') < 0 {
|
if repoTag != "scratch" && strings.IndexRune(repoTag, ':') < 0 {
|
||||||
return repoTag + ":latest"
|
return repoTag + ":latest"
|
||||||
|
|
@ -25,8 +54,8 @@ type Repo struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r Repo) Identifier() string {
|
func (r Repo) Identifier() string {
|
||||||
if r.TagName != "" {
|
if r.TagEntry != nil {
|
||||||
return r.RepoName + ":" + r.TagName
|
return r.EntryIdentifier(*r.TagEntry)
|
||||||
}
|
}
|
||||||
return r.RepoName
|
return r.RepoName
|
||||||
}
|
}
|
||||||
|
|
@ -35,6 +64,16 @@ func (r Repo) EntryIdentifier(entry manifest.Manifest2822Entry) string {
|
||||||
return r.RepoName + ":" + entry.Tags[0]
|
return r.RepoName + ":" + entry.Tags[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// create a new "Repo" object representing a single "Manifest2822Entry" object
|
||||||
|
func (r Repo) EntryRepo(entry *manifest.Manifest2822Entry) *Repo {
|
||||||
|
return &Repo{
|
||||||
|
RepoName: r.RepoName,
|
||||||
|
TagName: entry.Tags[0],
|
||||||
|
Manifest: r.Manifest,
|
||||||
|
TagEntry: entry,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (r Repo) SkipConstraints(entry manifest.Manifest2822Entry) bool {
|
func (r Repo) SkipConstraints(entry manifest.Manifest2822Entry) bool {
|
||||||
repoTag := r.RepoName + ":" + entry.Tags[0]
|
repoTag := r.RepoName + ":" + entry.Tags[0]
|
||||||
|
|
||||||
|
|
@ -87,51 +126,6 @@ func (r Repo) Entries() []manifest.Manifest2822Entry {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r Repo) SortedEntries() ([]manifest.Manifest2822Entry, error) {
|
|
||||||
entries := r.Entries()
|
|
||||||
|
|
||||||
if noSortFlag || len(entries) <= 1 {
|
|
||||||
return entries, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
network := topsort.NewNetwork()
|
|
||||||
|
|
||||||
for i, entry := range entries {
|
|
||||||
for _, tag := range r.Tags("", false, entry) {
|
|
||||||
network.AddNode(tag, &entries[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, entry := range entries {
|
|
||||||
from, err := r.DockerFrom(&entry)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, tag := range r.Tags("", false, entry) {
|
|
||||||
network.AddEdgeIfExists(from, tag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nodes, err := network.Sort()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
seen := map[*manifest.Manifest2822Entry]bool{}
|
|
||||||
ret := []manifest.Manifest2822Entry{}
|
|
||||||
for _, node := range nodes {
|
|
||||||
entry := node.Value.(*manifest.Manifest2822Entry)
|
|
||||||
if seen[entry] {
|
|
||||||
// TODO somehow reconcile "a:a -> b:b, b:b -> a:c"
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
ret = append(ret, *entry)
|
|
||||||
seen[entry] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r Repo) Tags(namespace string, uniq bool, entry manifest.Manifest2822Entry) []string {
|
func (r Repo) Tags(namespace string, uniq bool, entry manifest.Manifest2822Entry) []string {
|
||||||
tagRepo := path.Join(namespace, r.RepoName)
|
tagRepo := path.Join(namespace, r.RepoName)
|
||||||
ret := []string{}
|
ret := []string{}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,135 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/docker-library/go-dockerlibrary/manifest"
|
||||||
|
"pault.ag/go/topsort"
|
||||||
|
)
|
||||||
|
|
||||||
|
func sortRepos(repos []string) ([]string, error) {
|
||||||
|
rs := []*Repo{}
|
||||||
|
rsMap := map[*Repo]string{}
|
||||||
|
for _, repo := range repos {
|
||||||
|
r, err := fetch(repo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if _, ok := rsMap[r]; ok {
|
||||||
|
// if we have a duplicate, let's prefer the first
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
rs = append(rs, r)
|
||||||
|
rsMap[r] = repo
|
||||||
|
}
|
||||||
|
|
||||||
|
// short circuit if we don't have to go further
|
||||||
|
if noSortFlag || len(repos) <= 1 {
|
||||||
|
return repos, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rs, err := sortRepoObjects(rs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := []string{}
|
||||||
|
for _, r := range rs {
|
||||||
|
ret = append(ret, rsMap[r])
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Repo) SortedEntries() ([]manifest.Manifest2822Entry, error) {
|
||||||
|
entries := r.Entries()
|
||||||
|
|
||||||
|
// short circuit if we don't have to go further
|
||||||
|
if noSortFlag || len(entries) <= 1 {
|
||||||
|
return entries, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// create individual "Repo" objects for each entry in "r" so they can be sorted by the same "sortRepoObjects" function
|
||||||
|
rs := []*Repo{}
|
||||||
|
for i := range entries {
|
||||||
|
rs = append(rs, r.EntryRepo(&entries[i]))
|
||||||
|
}
|
||||||
|
|
||||||
|
rs, err := sortRepoObjects(rs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := []manifest.Manifest2822Entry{}
|
||||||
|
for _, entryR := range rs {
|
||||||
|
ret = append(ret, *entryR.TagEntry)
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortRepoObjects(rs []*Repo) ([]*Repo, error) {
|
||||||
|
// short circuit if we don't have to go further
|
||||||
|
if noSortFlag || len(rs) <= 1 {
|
||||||
|
return rs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
network := topsort.NewNetwork()
|
||||||
|
|
||||||
|
// a map of alternate tag names to the canonical "node name" for topsort purposes
|
||||||
|
canonicalNodes := map[string]string{}
|
||||||
|
canonicalRepos := map[string]*Repo{}
|
||||||
|
|
||||||
|
for _, r := range rs {
|
||||||
|
node := r.Identifier()
|
||||||
|
for _, entry := range r.Entries() {
|
||||||
|
for _, tag := range r.Tags("", false, entry) {
|
||||||
|
if canonicalRepo, ok := canonicalRepos[tag]; ok && canonicalRepo.TagName != "" {
|
||||||
|
// if we run into a duplicate, we want to prefer a specific tag over a full repo
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
canonicalNodes[tag] = node
|
||||||
|
canonicalRepos[tag] = r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
network.AddNode(node, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, r := range rs {
|
||||||
|
for _, entry := range r.Entries() {
|
||||||
|
from, err := r.DockerFrom(&entry)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
from = latestizeRepoTag(from)
|
||||||
|
|
||||||
|
fromNode, ok := canonicalNodes[from]
|
||||||
|
if !ok {
|
||||||
|
// if our FROM isn't in the list of things we're sorting, it isn't relevant in this context
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO somehow reconcile/avoid "a:a -> b:b, b:b -> a:c" (which will exhibit here as cyclic)
|
||||||
|
for _, tag := range r.Tags("", false, entry) {
|
||||||
|
if tagNode, ok := canonicalNodes[tag]; ok {
|
||||||
|
if tagNode == fromNode {
|
||||||
|
// don't be cyclic
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := network.AddEdge(fromNode, tagNode); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes, err := network.Sort()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := []*Repo{}
|
||||||
|
for _, node := range nodes {
|
||||||
|
ret = append(ret, node.Value.(*Repo))
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue