├── Makefile ├── bulk ├── build.go └── bulk.go ├── cmd ├── xbps-src-lint │ └── main.go ├── xbps-src-make │ └── main.go └── xbps-src-show-vars │ └── main.go ├── go.mod ├── go.sum ├── linter ├── function.go ├── linter.go ├── misc.go ├── subpkg.go ├── variables.go └── variables_test.go ├── runtime ├── env.go ├── environ.go ├── eval.go ├── options.go ├── runtime.go ├── runtime_test.go └── shfuncs.go └── template ├── options.go └── template.go /Makefile: -------------------------------------------------------------------------------- 1 | all: check lint 2 | 3 | check: 4 | go test ./... 5 | 6 | fmt: 7 | go fmt ./... 8 | 9 | lint: 10 | golint ./... 11 | staticcheck ./.. 12 | -------------------------------------------------------------------------------- /bulk/build.go: -------------------------------------------------------------------------------- 1 | package bulk 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/Duncaen/go-xbps/pkgver" 8 | ) 9 | 10 | type Build struct { 11 | Pkgname string 12 | Deps []string 13 | } 14 | 15 | var cycle map[string]bool 16 | 17 | func init() { 18 | cycle = make(map[string]bool) 19 | } 20 | 21 | // func (b *Bulk) edgeDeps(c Config, pkgname string) []string { 22 | // print("edges of: ") 23 | // println(pkgname) 24 | // var res []string 25 | // vars, ok := b.variables[c][pkgname] 26 | // if !ok { 27 | // return res 28 | // } 29 | // for _, k := range []string{"hostmakedepends", "makedepends"} { 30 | // deps, ok := vars[k] 31 | // if !ok { 32 | // continue 33 | // } 34 | // for _, dep := range strings.Fields(deps) { 35 | // if b.isEdge(c, dep) { 36 | // res = append(res, dep) 37 | // } 38 | // res = append(res, b.edgeDeps(c, dep)...) 39 | // } 40 | // } 41 | // return res 42 | // } 43 | 44 | type explicit struct { 45 | bulk *Bulk 46 | config Config 47 | cache map[string][]string 48 | } 49 | 50 | func (e *explicit) Mainpkg(pkgname string) string { 51 | vars := e.bulk.variables[e.config][pkgname] 52 | if sourcepkg, ok := vars["sourcepkg"]; ok { 53 | return sourcepkg 54 | } 55 | return vars["pkgname"] 56 | } 57 | 58 | func (e *explicit) IsEdge(pkgname string) bool { 59 | for _, e := range e.bulk.edges { 60 | if e == pkgname { 61 | return true 62 | } 63 | } 64 | return false 65 | } 66 | 67 | func (e *explicit) Deps(pkgname string, stack []string) []string { 68 | stack = append(stack, pkgname) 69 | if cycle[pkgname] { 70 | panic(fmt.Sprintf("cycle: %s %t", pkgname, stack)) 71 | } 72 | cycle[pkgname] = true 73 | var res []string 74 | if deps, ok := e.cache[pkgname]; ok { 75 | cycle[pkgname] = false 76 | return deps 77 | } 78 | vars, ok := e.bulk.variables[e.config][pkgname] 79 | if !ok { 80 | cycle[pkgname] = false 81 | return res 82 | } 83 | var subpkgs []string 84 | if s, ok := vars["subpackages"]; ok { 85 | subpkgs = strings.Fields(s) 86 | } 87 | 88 | uniq := make(map[string]interface{}) 89 | for _, k := range []string{"hostmakedepends", "makedepends"} { 90 | deps, ok := vars[k] 91 | if !ok { 92 | continue 93 | } 94 | for _, dep := range strings.Fields(deps) { 95 | issub := false 96 | for _, sub := range subpkgs { 97 | if sub == dep { 98 | issub = true 99 | break 100 | } 101 | } 102 | if issub { 103 | continue 104 | } 105 | mainpkg := e.Mainpkg(dep) 106 | if e.IsEdge(mainpkg) { 107 | uniq[mainpkg] = nil 108 | } 109 | for _, dep := range e.Deps(mainpkg, stack) { 110 | uniq[dep] = nil 111 | } 112 | } 113 | } 114 | 115 | deps, ok := vars["depends"] 116 | if ok { 117 | for _, dep := range strings.Fields(deps) { 118 | if strings.HasPrefix(dep, "virtual?") { 119 | var err error 120 | dep = strings.TrimPrefix(dep, "virtual?") 121 | pkg, err := pkgver.Parse(dep) 122 | if err != nil { 123 | panic(err) 124 | } 125 | dep, err = e.bulk.runtime.GetVirtual(pkg.Name) 126 | if err != nil { 127 | panic(err) 128 | } 129 | } 130 | pkg, err := pkgver.Parse(dep) 131 | if err != nil { 132 | panic(err) 133 | } 134 | issub := false 135 | mainpkg := e.Mainpkg(pkg.Name) 136 | for _, sub := range subpkgs { 137 | if sub == pkg.Name { 138 | issub = true 139 | break 140 | } 141 | } 142 | if issub { 143 | continue 144 | } 145 | if e.IsEdge(mainpkg) { 146 | uniq[mainpkg] = nil 147 | } 148 | for _, dep := range e.Deps(mainpkg, stack) { 149 | uniq[dep] = nil 150 | } 151 | } 152 | } 153 | for k, _ := range uniq { 154 | res = append(res, k) 155 | } 156 | e.cache[pkgname] = res 157 | cycle[pkgname] = false 158 | return res 159 | } 160 | 161 | func (b *Bulk) Edges() []Build { 162 | var res []Build 163 | for _, c := range b.Configs { 164 | e := &explicit{b, c, make(map[string][]string)} 165 | for _, pkgname := range b.edges { 166 | b := Build{ 167 | Pkgname: pkgname, 168 | Deps: e.Deps(pkgname, []string{}), 169 | } 170 | res = append(res, b) 171 | } 172 | } 173 | return res 174 | } 175 | -------------------------------------------------------------------------------- /bulk/bulk.go: -------------------------------------------------------------------------------- 1 | package bulk 2 | 3 | import ( 4 | "log" 5 | "path" 6 | "strings" 7 | 8 | "github.com/Duncaen/go-xbps-src/runtime" 9 | "github.com/Duncaen/go-xbps-src/template" 10 | 11 | "github.com/Duncaen/go-xbps/pkgver" 12 | ) 13 | 14 | // Config represents a build configuration 15 | type Config struct { 16 | Arch string 17 | Cross string 18 | Hostdir string 19 | Masterdir string 20 | } 21 | 22 | func (c Config) String() string { 23 | if c.Cross != "" { 24 | return strings.Join([]string{c.Cross, c.Arch}, "@") 25 | } 26 | return c.Arch 27 | } 28 | 29 | type Bulk struct { 30 | Distdir string 31 | Configs []Config 32 | 33 | // parsed template cache 34 | templates map[string]*template.Template 35 | // variable cache 36 | variables map[Config]map[string]map[string]string 37 | 38 | runtime *runtime.Runtime 39 | 40 | edges []string 41 | } 42 | 43 | // New creates and initializes a new Bulk instance 44 | func New(distdir string, configs ...Config) (*Bulk, error) { 45 | runtime, err := runtime.New(distdir) 46 | if err != nil { 47 | return nil, err 48 | } 49 | variables := make(map[Config]map[string]map[string]string) 50 | for _, c := range configs { 51 | variables[c] = make(map[string]map[string]string) 52 | } 53 | return &Bulk{ 54 | Distdir: distdir, 55 | Configs: configs, 56 | templates: make(map[string]*template.Template), 57 | variables: variables, 58 | runtime: runtime, 59 | }, nil 60 | } 61 | 62 | func (b *Bulk) loadDeps(c Config, vars map[string]string) error { 63 | for _, k := range []string{"hostmakedepends", "makedepends"} { 64 | deps, ok := vars[k] 65 | if !ok { 66 | continue 67 | } 68 | for _, dep := range strings.Fields(deps) { 69 | err := b.load(c, dep) 70 | if err != nil { 71 | return err 72 | } 73 | } 74 | } 75 | if deps, ok := vars["depends"]; ok { 76 | for _, dep := range strings.Fields(deps) { 77 | if strings.HasPrefix(dep, "virtual?") { 78 | dep = strings.TrimPrefix(dep, "virtual?") 79 | pkg, err := pkgver.Parse(dep) 80 | if err != nil { 81 | return err 82 | } 83 | dep, err = b.runtime.GetVirtual(pkg.Name) 84 | if err != nil { 85 | return err 86 | } 87 | } 88 | pkg, err := pkgver.Parse(dep) 89 | if err != nil { 90 | return err 91 | } 92 | err = b.load(c, pkg.Name) 93 | if err != nil { 94 | return err 95 | } 96 | } 97 | } 98 | return nil 99 | } 100 | 101 | // load evaluates a template with the given config and returns its variables 102 | func (b *Bulk) load(c Config, pkgname string) error { 103 | // check if template with given configuration is already loaded 104 | if _, ok := b.variables[c][pkgname]; ok { 105 | return nil 106 | } 107 | // XXX: handle -32bit and -dbg correct 108 | if strings.HasSuffix(pkgname, "-32bit") { 109 | b.variables[c][pkgname] = make(map[string]string) 110 | b.variables[c][pkgname]["sourcepkg"] = strings.TrimSuffix(pkgname, "-32bit") 111 | return nil 112 | } 113 | if strings.HasSuffix(pkgname, "-dbg") { 114 | b.variables[c][pkgname] = make(map[string]string) 115 | b.variables[c][pkgname]["sourcepkg"] = strings.TrimSuffix(pkgname, "-dbg") 116 | return nil 117 | } 118 | // check if we parsed the template already 119 | t, ok := b.templates[pkgname] 120 | if !ok { 121 | var err error 122 | path := path.Join(b.Distdir, "srcpkgs", pkgname, "template") 123 | t, err = template.ParseFile(path) 124 | if err != nil { 125 | log.Print(err) 126 | return nil 127 | } 128 | b.templates[pkgname] = t 129 | log.Println("parsed template", path) 130 | } 131 | log.Printf("evaluating %q for %s\n", pkgname, c) 132 | vs, err := t.Eval(b.runtime, c.Arch, c.Cross) 133 | if err != nil { 134 | log.Print(err) 135 | return nil 136 | } 137 | for _, vars := range vs { 138 | pkgname := vars["pkgname"] 139 | b.variables[c][pkgname] = vars 140 | } 141 | 142 | // load main packages dependencies 143 | err = b.loadDeps(c, vs[0]) 144 | if err != nil { 145 | return err 146 | } 147 | 148 | return nil 149 | } 150 | 151 | func (b *Bulk) Add(pkgname string) error { 152 | for _, c := range b.Configs { 153 | if err := b.load(c, pkgname); err != nil { 154 | return err 155 | } 156 | } 157 | b.edges = append(b.edges, pkgname) 158 | return nil 159 | } 160 | -------------------------------------------------------------------------------- /cmd/xbps-src-lint/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | 8 | "github.com/Duncaen/go-xbps-src/linter" 9 | ) 10 | 11 | func main() { 12 | flag.Parse() 13 | for _, path := range flag.Args() { 14 | errs, err := linter.LintFile(path) 15 | if err != nil { 16 | log.Println(err) 17 | continue 18 | } 19 | log.Printf("Linting: %s\n", path) 20 | for _, err := range errs { 21 | fmt.Println(err) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /cmd/xbps-src-make/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "log" 9 | "os" 10 | "path" 11 | "strings" 12 | 13 | "github.com/Duncaen/go-xbps-src/bulk" 14 | ) 15 | 16 | var rules = ` 17 | TOBUILD = $(patsubst %,tobuild/%,$(PKGS)) 18 | BUILT = $(patsubst tobuild/%,built/%,$(TOBUILD)) 19 | SORT := 20 | 21 | .PHONY: all sort print_pkgs clean 22 | 23 | all: $(BUILT) 24 | @echo "[Done]" 25 | 26 | sort: 27 | @$(MAKE) SORT=1 all 28 | @[ -n "$(PKGS)" ] && mv built/* tobuild || : 29 | @echo "[Sorted]" 30 | 31 | print_pkgs: 32 | @( [ -f pkgs-sorted.txt ] && cat pkgs-sorted.txt | xargs || echo $(PKGS) ) 33 | 34 | clean: 35 | @rm -f built/* pkgs.txt pkgs-sorted.txt repo-checkvers.txt 36 | @echo "[Clean]" 37 | 38 | built/%: tobuild/% 39 | @echo "[xbps-src] ${@F}" 40 | ifdef SORT 41 | @echo ${@F} >> pkgs-sorted.txt 42 | else 43 | @( $(XSC) pkg ${@F}; rval=$$?; [ $$rval -eq 2 ] && exit 0 || exit $$rval ) 44 | endif 45 | @touch $@ 46 | @rm tobuild/${@F} 47 | 48 | ` 49 | 50 | var ( 51 | distdir = flag.String("distdir", os.ExpandEnv("${HOME}/void-packages"), "void-packages repository path") 52 | masterdir = flag.String("masterdir", "masterdir", "hostdir") 53 | hostdir = flag.String("hostdir", "hostdir", "masterdir") 54 | arch = flag.String("arch", "x86_64", "build architecture") 55 | cross = flag.String("cross", "", "cross architecture") 56 | flags = flag.String("flags", "-N -t -L -E", "xbps-src flags") 57 | ) 58 | 59 | func all(b *bulk.Bulk) error { 60 | files, err := ioutil.ReadDir(path.Join(b.Distdir, "srcpkgs")) 61 | if err != nil { 62 | return err 63 | } 64 | for _, f := range files { 65 | if f.Mode()&os.ModeSymlink != 0 { 66 | continue 67 | } 68 | err := b.Add(f.Name()) 69 | if err != nil { 70 | return err 71 | } 72 | } 73 | return nil 74 | } 75 | 76 | func main() { 77 | flag.Parse() 78 | b, err := bulk.New(*distdir, bulk.Config{ 79 | Masterdir: *masterdir, 80 | Hostdir: *hostdir, 81 | Arch: *arch, 82 | Cross: *cross, 83 | }) 84 | if err != nil { 85 | log.Fatal(err) 86 | } 87 | if flag.NArg() == 0 { 88 | if err := all(b); err != nil { 89 | log.Fatal(err) 90 | } 91 | } else { 92 | for _, pkg := range flag.Args() { 93 | if err := b.Add(pkg); err != nil { 94 | log.Fatal(err) 95 | } 96 | } 97 | } 98 | xsc := fmt.Sprintf("%s/xbps-src %s", *distdir, *flags) 99 | if *cross != "" { 100 | xsc += " -a " 101 | xsc += *cross 102 | } 103 | if *masterdir != "" { 104 | xsc += " -m " 105 | xsc += *masterdir 106 | } 107 | if *hostdir != "" { 108 | xsc += " -H " 109 | xsc += *hostdir 110 | } 111 | io.WriteString(os.Stdout, "# generated with xbps-src-make\n") 112 | fmt.Fprintf(os.Stdout, "PKGS = %s\n", strings.Join(flag.Args(), " ")) 113 | fmt.Fprintf(os.Stdout, "XSC = %s\n", xsc) 114 | io.WriteString(os.Stdout, rules) 115 | 116 | for _, bu := range b.Edges() { 117 | fmt.Fprintf(os.Stdout, "built/%s:", bu.Pkgname) 118 | for _, deppkg := range bu.Deps { 119 | fmt.Fprintf(os.Stdout, " built/%s", deppkg) 120 | } 121 | fmt.Fprint(os.Stdout, "\n") 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /cmd/xbps-src-show-vars/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "strings" 8 | 9 | "github.com/Duncaen/go-xbps-src/runtime" 10 | "github.com/Duncaen/go-xbps-src/template" 11 | ) 12 | 13 | var ( 14 | distdir = flag.String("distdir", "/home/duncan/void-packages", "distdir") 15 | arch = flag.String("arch", "x86_64", "architecture") 16 | cross = flag.String("cross", "", "cross architecture") 17 | variables = []string{ 18 | "version", 19 | "revision", 20 | "archs", 21 | "arch", 22 | "broken", 23 | "nocross", 24 | "hostmakedepends", 25 | "makedepends", 26 | "depends", 27 | } 28 | ) 29 | 30 | func main() { 31 | flag.Parse() 32 | run, err := runtime.New(*distdir) 33 | if err != nil { 34 | log.Fatal(err) 35 | } 36 | for _, path := range flag.Args() { 37 | t, err := template.ParseFile(path) 38 | if err != nil { 39 | log.Println(err) 40 | continue 41 | } 42 | log.Printf("Evaluating: %s\n", path) 43 | vs, err := t.Eval(run, *arch, *cross) 44 | if err != nil { 45 | log.Fatal(err) 46 | } 47 | for _, kv := range vs { 48 | pkgname := kv["pkgname"] 49 | for _, s := range variables { 50 | val := strings.Join(strings.Fields(kv[s]), " ") 51 | if val == "" { 52 | continue 53 | } 54 | fmt.Printf("%s: %s=%q\n", pkgname, s, val) 55 | } 56 | } 57 | // for _, sv := range vars.SubPackages { 58 | // fmt.Printf("%s: depends=%v\n", sv.PkgName, sv.Depends) 59 | // } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Duncaen/go-xbps-src 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/Duncaen/go-xbps v0.0.0-20220724153744-1f4ab16d608b 7 | github.com/kr/pretty v0.1.0 // indirect 8 | github.com/kr/pty v1.1.8 // indirect 9 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 // indirect 10 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 // indirect 11 | golang.org/x/sys v0.0.0-20190730183949-1393eb018365 // indirect 12 | golang.org/x/text v0.3.2 // indirect 13 | golang.org/x/tools v0.0.0-20190730215328-ed3277de2799 // indirect 14 | mvdan.cc/sh v2.6.4+incompatible 15 | ) 16 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Duncaen/go-xbps v0.0.0-20190626161642-914cebb42ace h1:fdOnucl2qRKzygkj9J2ra3MkvhDtANvXdBFtUcNgO8c= 2 | github.com/Duncaen/go-xbps v0.0.0-20190626161642-914cebb42ace/go.mod h1:QAnGsjHT5Xxov+QXfGGKPqn2FOHyWhErtnqU7GdiuJ8= 3 | github.com/Duncaen/go-xbps v0.0.0-20220724153744-1f4ab16d608b h1:wGpmhH4MmHkKHw2CNHXVsAxwFYJUp/u523NaeJVEubY= 4 | github.com/Duncaen/go-xbps v0.0.0-20220724153744-1f4ab16d608b/go.mod h1:QAnGsjHT5Xxov+QXfGGKPqn2FOHyWhErtnqU7GdiuJ8= 5 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 6 | github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 7 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 8 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 9 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 10 | github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= 11 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 12 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 13 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 14 | golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 15 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= 16 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 17 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 18 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 19 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 20 | golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= 21 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 22 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 23 | golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= 24 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 25 | golang.org/x/sys v0.0.0-20190730183949-1393eb018365 h1:SaXEMXhWzMJThc05vu6uh61Q245r4KaWMrsTedk0FDc= 26 | golang.org/x/sys v0.0.0-20190730183949-1393eb018365/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 27 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 28 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 29 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 30 | golang.org/x/tools v0.0.0-20190730215328-ed3277de2799/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= 31 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 32 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 33 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 34 | howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= 35 | mvdan.cc/sh v2.6.4+incompatible h1:eD6tDeh0pw+/TOTI1BBEryZ02rD2nMcFsgcvde7jffM= 36 | mvdan.cc/sh v2.6.4+incompatible/go.mod h1:IeeQbZq+x2SUGBensq/jge5lLQbS3XT2ktyp3wrt4x8= 37 | -------------------------------------------------------------------------------- /linter/function.go: -------------------------------------------------------------------------------- 1 | package linter 2 | 3 | import ( 4 | "mvdan.cc/sh/syntax" 5 | ) 6 | 7 | var defaultFns = map[string]bool{ 8 | "pre_fetch": true, 9 | "do_fetch": true, 10 | "post_fetch": true, 11 | "pre_extract": true, 12 | "do_extract": true, 13 | "post_extract": true, 14 | "pre_patch": true, 15 | "do_patch": true, 16 | "post_patch": true, 17 | "pre_configure": true, 18 | "do_configure": true, 19 | "post_configure": true, 20 | "pre_build": true, 21 | "do_build": true, 22 | "post_build": true, 23 | "pre_check": true, 24 | "do_check": true, 25 | "post_check": true, 26 | "pre_install": true, 27 | "do_install": true, 28 | "post_install": true, 29 | "do_clean": true, 30 | "pkg_install": true, 31 | } 32 | 33 | func (l *linter) function(fn *syntax.FuncDecl) { 34 | nam := fn.Name.Value 35 | if fn.RsrvWord { 36 | l.errorf(newPos(fn.Pos()), `%s: must use posix style function declaration`, nam) 37 | } 38 | if _, ok := defaultFns[nam]; ok { 39 | return 40 | } 41 | switch { 42 | case patSubPkg.MatchString(nam): 43 | case nam[0] == '_': 44 | default: 45 | l.errorf(newPos(fn.Pos()), `%s: custom function should start with '_'`, nam) 46 | } 47 | } 48 | 49 | func (l *linter) functions() { 50 | syntax.Walk(l.f, func(node syntax.Node) bool { 51 | switch x := node.(type) { 52 | case *syntax.FuncDecl: 53 | l.function(x) 54 | } 55 | return true 56 | }) 57 | } 58 | -------------------------------------------------------------------------------- /linter/linter.go: -------------------------------------------------------------------------------- 1 | // Package linter provides a linter for xbps-src template files. 2 | // 3 | // Most of the implemented checks are originally from xtools xlint 4 | // shell script. 5 | package linter 6 | 7 | import ( 8 | "bytes" 9 | "errors" 10 | "fmt" 11 | "io" 12 | "os" 13 | 14 | "mvdan.cc/sh/syntax" 15 | ) 16 | 17 | // Pos represents the position in the source (line, column). 18 | type Pos struct { 19 | line uint 20 | col uint 21 | } 22 | 23 | func newPos(p syntax.Pos) Pos { 24 | return Pos{p.Line(), p.Col()} 25 | } 26 | 27 | func (p Pos) String() string { 28 | return fmt.Sprintf("%d:%d", p.line, p.col) 29 | } 30 | 31 | // Error represents an issue the linter found. 32 | type Error struct { 33 | Pos Pos 34 | File string 35 | Msg string 36 | } 37 | 38 | func (e Error) Error() string { 39 | return fmt.Sprintf("%s:%s: %s", e.File, e.Pos, e.Msg) 40 | } 41 | 42 | func (l *linter) emit(e Error) { 43 | l.errors = append(l.errors, e) 44 | } 45 | 46 | func (l *linter) error(p Pos, msg string) { 47 | l.emit(Error{ 48 | File: l.f.Name, 49 | Pos: p, 50 | Msg: msg, 51 | }) 52 | } 53 | 54 | func (l *linter) errorf(p Pos, format string, a ...interface{}) { 55 | l.error(p, fmt.Sprintf(format, a...)) 56 | } 57 | 58 | const ( 59 | _ = 1 << iota 60 | // LintHeader flag enables header linting. 61 | LintHeader 62 | // LintFunctions flag enables function linting. 63 | LintFunctions 64 | // LintVariables flag enables variable linting. 65 | LintVariables 66 | // LintSubPackages flag enables sub package linting. 67 | LintSubPackages 68 | // LintDefault flag enables all available lint flags. 69 | LintDefault = LintHeader | LintFunctions | LintVariables | LintSubPackages 70 | ) 71 | 72 | type linter struct { 73 | errors []Error 74 | f *syntax.File 75 | flags int 76 | } 77 | 78 | func makeValue(node syntax.Node) string { 79 | buf := new(bytes.Buffer) 80 | printer := syntax.NewPrinter(syntax.Minify) 81 | printer.Print(buf, node) 82 | return buf.String() 83 | } 84 | 85 | // LintFile runs the linters specified flags on a file specified by path. 86 | func LintFile(path string, flags ...int) ([]Error, error) { 87 | r, err := os.Open(path) 88 | if err != nil { 89 | return nil, err 90 | } 91 | defer r.Close() 92 | return Lint(r, path, flags...) 93 | } 94 | 95 | // Lint runs the linters specified flags on a reader. 96 | func Lint(r io.Reader, name string, flags ...int) ([]Error, error) { 97 | parser := syntax.NewParser( 98 | syntax.KeepComments, 99 | syntax.Variant(syntax.LangBash), 100 | ) 101 | f, err := parser.Parse(r, name) 102 | if err != nil { 103 | return nil, err 104 | } 105 | if parser.Incomplete() { 106 | return nil, errors.New("inclomplete") 107 | } 108 | l := &linter{ 109 | f: f, 110 | errors: []Error{}, 111 | } 112 | flag := 0 113 | for _, f := range flags { 114 | flag |= f 115 | } 116 | if flag == 0 { 117 | flag = LintDefault 118 | } 119 | if flag&LintHeader != 0 { 120 | l.header() 121 | } 122 | if flag&LintVariables != 0 { 123 | l.variables() 124 | } 125 | if flag&LintFunctions != 0 { 126 | l.functions() 127 | } 128 | if flag&LintSubPackages != 0 { 129 | l.subpackages() 130 | } 131 | return l.errors, nil 132 | } 133 | -------------------------------------------------------------------------------- /linter/misc.go: -------------------------------------------------------------------------------- 1 | package linter 2 | 3 | import ( 4 | "regexp" 5 | ) 6 | 7 | var patHeader = regexp.MustCompile(` Template file for '[^']+'`) 8 | 9 | func (l *linter) header() { 10 | if len(l.f.Stmts) < 1 || len(l.f.Stmts[0].Comments) < 1 { 11 | l.errorf(Pos{1, 1}, `missing header`) 12 | return 13 | } 14 | if !patHeader.MatchString(l.f.Stmts[0].Comments[0].Text) { 15 | l.errorf(Pos{1, 1}, `header does not match "# Template file for ''"`) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /linter/subpkg.go: -------------------------------------------------------------------------------- 1 | package linter 2 | 3 | import ( 4 | "regexp" 5 | 6 | "mvdan.cc/sh/syntax" 7 | ) 8 | 9 | var patSubPkg = regexp.MustCompile(`_package$`) 10 | 11 | func (l *linter) subPackage(fn *syntax.FuncDecl) { 12 | shortDesc, pkgInstall, meta := false, false, false 13 | syntax.Walk(fn, func(node syntax.Node) bool { 14 | switch x := node.(type) { 15 | case *syntax.Assign: 16 | switch x.Name.Value { 17 | case "short_desc": 18 | shortDesc = true 19 | case "build_style": 20 | meta = makeValue(x.Value) == "meta" 21 | } 22 | case *syntax.FuncDecl: 23 | switch x.Name.Value { 24 | case "pkg_install": 25 | pkgInstall = true 26 | default: 27 | } 28 | } 29 | return true 30 | }) 31 | if !shortDesc { 32 | l.errorf(newPos(fn.Pos()), `sub-package '%s' missing short_desc assignment`, fn.Name.Value) 33 | } 34 | if !pkgInstall && !meta { 35 | l.errorf(newPos(fn.Pos()), `sub-package '%s' missing pkgInstall function pr build_style=meta`, fn.Name.Value) 36 | } else if meta && pkgInstall { 37 | l.errorf(newPos(fn.Pos()), `sub package '%s' has build_style=meta and a pkg_install function`, fn.Name.Value) 38 | } 39 | } 40 | 41 | func (l *linter) subpackages() { 42 | syntax.Walk(l.f, func(node syntax.Node) bool { 43 | switch x := node.(type) { 44 | case *syntax.FuncDecl: 45 | if patSubPkg.MatchString(x.Name.Value) { 46 | l.subPackage(x) 47 | return true 48 | } 49 | return false 50 | } 51 | return true 52 | }) 53 | } 54 | -------------------------------------------------------------------------------- /linter/variables.go: -------------------------------------------------------------------------------- 1 | package linter 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "strings" 7 | 8 | "mvdan.cc/sh/syntax" 9 | ) 10 | 11 | var variables = []string{ 12 | "_.*", 13 | ".*_descr", 14 | ".*_groups", 15 | ".*_homedir", 16 | ".*_pgroup", 17 | ".*_shell", 18 | "desc_option_.*", 19 | "AR", 20 | "AS", 21 | "CC", 22 | "CFLAGS", 23 | "CPP", 24 | "CPPFLAGS", 25 | "CXX", 26 | "CXXFLAGS", 27 | "GCC", 28 | "LD", 29 | "LDFLAGS", 30 | "LD_LIBRARY_PATH", 31 | "NM", 32 | "OBJCOPY", 33 | "OBJDUMP", 34 | "RANLIB", 35 | "READELF", 36 | "STRIP", 37 | "XBPS_FETCH_CMD", 38 | "allow_unknown_shlibs", 39 | "alternatives", 40 | "archs", 41 | "binfmts", 42 | "bootstrap", 43 | "broken", 44 | "build_options", 45 | "build_options_default", 46 | "build_style", 47 | "build_helper", 48 | "build_wrksrc", 49 | "changelog", 50 | "checkdepends", 51 | "checksum", 52 | "cmake_builddir", 53 | "conf_files", 54 | "configure_args", 55 | "configure_script", 56 | "conflicts", 57 | "create_wrksrc", 58 | "depends", 59 | "disable_parallel_build", 60 | "distfiles", 61 | "dkms_modules", 62 | "fetch_cmd", 63 | "font_dirs", 64 | "force_debug_pkgs", 65 | "go_build_tags", 66 | "go_get", 67 | "go_import_path", 68 | "go_ldflags", 69 | "go_package", 70 | "go_mod_mode", 71 | "homepage", 72 | "hostmakedepends", 73 | "keep_libtool_archives", 74 | "kernel_hooks_version", 75 | "lib32depends", 76 | "lib32disabled", 77 | "lib32files", 78 | "lib32mode", 79 | "lib32symlinks", 80 | "license", 81 | "maintainer", 82 | "make_build_args", 83 | "make_build_target", 84 | "make_check_args", 85 | "make_check_target", 86 | "make_cmd", 87 | "make_dirs", 88 | "make_install_args", 89 | "make_install_target", 90 | "make_use_env", 91 | "makedepends", 92 | "mutable_files", 93 | "nocross", 94 | "nodebug", 95 | "nopie", 96 | "nopie_files", 97 | "noshlibprovides", 98 | "nostrip", 99 | "nostrip_files", 100 | "noverifyrdeps", 101 | "only_for_archs", 102 | "patch_args", 103 | "pkgname", 104 | "preserve", 105 | "provides", 106 | "pycompile_dirs", 107 | "pycompile_module", 108 | "pycompile_version", 109 | "python_version", 110 | "register_shell", 111 | "replaces", 112 | "repository", 113 | "restricted", 114 | "reverts", 115 | "revision", 116 | "run_depends", 117 | "sgml_catalogs", 118 | "sgml_entries", 119 | "shlib_provides", 120 | "shlib_requires", 121 | "short_desc", 122 | "skip_extraction", 123 | "skiprdeps", 124 | "stackage", 125 | "subpackages", 126 | "system_accounts", 127 | "system_groups", 128 | "tags", 129 | "triggers", 130 | "version", 131 | "wrksrc", 132 | "xml_catalogs", 133 | "xml_entries", 134 | } 135 | 136 | var patVariables = regexp.MustCompile(fmt.Sprintf("^(%s)$", strings.Join(variables, "|"))) 137 | 138 | const ( 139 | errShortDescDot = "unwanted trailing dot in short_desc" 140 | errShortDescUpper = "short_desc should start uppercase" 141 | errShortDescArticle = "short_desc should not start with an article" 142 | errShortDescWhitespace = "short_desc should not start or end with whitespace" 143 | errShortDescLength = "short_desc should be less than 72 chars" 144 | errLicenseGPLVersion = "license GPL without version" 145 | errLicenseLGPLVersion = "license LGPL without version" 146 | errLicenseSSPL = "uses the SSPL license, which is not packageable" 147 | errRevisionZero = "revision must not be zero" 148 | errVersionInvalid = "version must not contain the characters : or -" 149 | errMaintainerMail = "maintainer needs a valid mail address" 150 | errReplacesVersion = "replaces needs depname with version" 151 | ) 152 | 153 | func errVariableName(s string) string { 154 | return fmt.Sprintf("custom variables should use _ prefix: %s", s) 155 | } 156 | 157 | var checks = []struct { 158 | name string 159 | match bool 160 | pat *regexp.Regexp 161 | err string 162 | }{ 163 | {"short_desc", true, regexp.MustCompile(`\.["']?$`), errShortDescDot}, 164 | {"short_desc", true, regexp.MustCompile(`^["']?[a-z]`), errShortDescUpper}, 165 | {"short_desc", true, regexp.MustCompile(`^["']?(An?|The) `), errShortDescArticle}, 166 | {"short_desc", true, regexp.MustCompile(`(^["']?[\t ]|[\t ]["']?$)`), errShortDescWhitespace}, 167 | {"short_desc", true, regexp.MustCompile(`^["']?.{73}`), errShortDescLength}, 168 | {"license", true, regexp.MustCompile(`[^NL]GPL[^-]`), errLicenseGPLVersion}, 169 | {"license", true, regexp.MustCompile(`LGPL[^-]`), errLicenseLGPLVersion}, 170 | {"license", true, regexp.MustCompile(`SSPL`), errLicenseSSPL}, 171 | {"revision", true, regexp.MustCompile(`^0$`), errRevisionZero}, 172 | {"version", true, regexp.MustCompile(`[:-]`), errVersionInvalid}, 173 | {"maintainer", true, regexp.MustCompile(`@users.noreply.github.com`), errMaintainerMail}, 174 | {"maintainer", false, regexp.MustCompile(`<[^>@]+@[^>]+>`), errMaintainerMail}, 175 | {"replaces", false, regexp.MustCompile(`^['"]?([\w_-]+[<>=]+[^ \t]+[ \t]*)+['"]$`), errReplacesVersion}, 176 | } 177 | 178 | func (l *linter) variable(a *syntax.Assign) { 179 | if !patVariables.MatchString(a.Name.Value) { 180 | l.error(newPos(a.Pos()), errVariableName(a.Name.Value)) 181 | return 182 | } 183 | for _, vc := range checks { 184 | if vc.name != a.Name.Value { 185 | continue 186 | } 187 | if vc.pat.MatchString(makeValue(a.Value)) == vc.match { 188 | l.error(newPos(a.Pos()), vc.err) 189 | } 190 | } 191 | switch a.Name.Value { 192 | case "nonfree": 193 | l.error(newPos(a.Pos()), "use repository=nonfree") 194 | } 195 | } 196 | 197 | var vars = map[string]bool{ 198 | "pkgname": true, 199 | "version": true, 200 | "revision": true, 201 | } 202 | 203 | func (l *linter) quotes(a *syntax.Assign) { 204 | nam := a.Name.Value 205 | if _, ok := vars[nam]; !ok { 206 | return 207 | } 208 | if len(a.Value.Parts) != 1 { 209 | goto error 210 | } 211 | switch a.Value.Parts[0].(type) { 212 | case *syntax.Lit: 213 | default: 214 | goto error 215 | } 216 | return 217 | error: 218 | l.errorf(newPos(a.Pos()), `%s must not be quoted`, nam) 219 | } 220 | 221 | func (l *linter) variables() { 222 | syntax.Walk(l.f, func(node syntax.Node) bool { 223 | switch x := node.(type) { 224 | case *syntax.FuncDecl: 225 | return false 226 | case *syntax.Assign: 227 | l.variable(x) 228 | l.quotes(x) 229 | } 230 | return true 231 | }) 232 | } 233 | -------------------------------------------------------------------------------- /linter/variables_test.go: -------------------------------------------------------------------------------- 1 | package linter 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | var tests = []struct { 9 | in string 10 | errors []string 11 | }{ 12 | {`short_desc="foo."`, []string{errShortDescDot, errShortDescUpper}}, 13 | {`short_desc="Foo."`, []string{errShortDescDot}}, 14 | {`short_desc="Foo"`, nil}, 15 | {`short_desc="An foo"`, []string{errShortDescArticle}}, 16 | {`short_desc="The foo"`, []string{errShortDescArticle}}, 17 | {`short_desc="And this is ok"`, nil}, 18 | {`short_desc=" Foo"`, []string{errShortDescWhitespace}}, 19 | {`short_desc="Foo "`, []string{errShortDescWhitespace}}, 20 | {`short_desc=" Foo "`, []string{errShortDescWhitespace}}, 21 | {`short_desc="Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar"`, []string{errShortDescLength}}, 22 | {`license="GPL"`, []string{errLicenseGPLVersion}}, 23 | {`license="GPL-2.0-or-later"`, nil}, 24 | {`license="LGPL"`, []string{errLicenseLGPLVersion}}, 25 | {`license="LGPL-2.0-or-later"`, nil}, 26 | {`license="SSPL"`, []string{errLicenseSSPL}}, 27 | {`revision=0`, []string{errRevisionZero}}, 28 | {`revision=1`, nil}, 29 | {`version=1-1`, []string{errVersionInvalid}}, 30 | {`version=1:1`, []string{errVersionInvalid}}, 31 | {`version=1.1`, nil}, 32 | {`version=1`, nil}, 33 | {`maintainer="foo bar "`, []string{errMaintainerMail}}, 34 | {`maintainer="foo bar"`, []string{errMaintainerMail}}, 35 | {`maintainer="foo bar "`, nil}, 36 | {`replaces="foo"`, []string{errReplacesVersion}}, 37 | {`replaces="foo bar"`, []string{errReplacesVersion}}, 38 | {`replaces="foo>=0 foo"`, []string{errReplacesVersion}}, 39 | {`replaces="foo>=0"`, nil}, 40 | {`replaces="foo>=0 foo>=1"`, nil}, 41 | {`foo=bar`, []string{errVariableName("foo")}}, 42 | {`_foo=bar`, nil}, 43 | } 44 | 45 | func TestVariables(t *testing.T) { 46 | for _, test := range tests { 47 | i := 0 48 | errs, err := Lint(strings.NewReader(test.in), "buffer", LintVariables) 49 | if err != nil { 50 | t.Fatal(err) 51 | } 52 | for _, err := range errs { 53 | if i < len(test.errors) && err.Msg != test.errors[i] { 54 | t.Errorf("%q returned %q, want %q", test.in, err.Msg, test.errors[i]) 55 | } 56 | i++ 57 | } 58 | if i != len(test.errors) { 59 | t.Errorf("%q returned %d errors, want %d", test.in, i, len(test.errors)) 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /runtime/env.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "mvdan.cc/sh/expand" 5 | ) 6 | 7 | // Env returns the xbps-src environment 8 | func (r *Runtime) Env(arch, cross string) Environ { 9 | var m, t expand.Variable 10 | m = expand.Variable{ 11 | Exported: true, 12 | ReadOnly: true, 13 | Value: arch, 14 | } 15 | t = m 16 | 17 | if cross != "" { 18 | t = expand.Variable{ 19 | Exported: true, 20 | ReadOnly: true, 21 | Value: cross, 22 | } 23 | } 24 | 25 | return Environ{ 26 | "XBPS_UHELPER_CMD": expand.Variable{ 27 | Exported: true, 28 | ReadOnly: true, 29 | Value: "xbps-uhelper", 30 | }, 31 | "XBPS_MACHINE": m, 32 | "XBPS_TARGET_MACHINE": t, 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /runtime/environ.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "mvdan.cc/sh/expand" 5 | ) 6 | 7 | // Environ implements expand.Environ on a map 8 | type Environ map[string]expand.Variable 9 | 10 | // Get retrieves a variable by its name 11 | func (e Environ) Get(name string) expand.Variable { 12 | if v, ok := e[name]; ok { 13 | return v 14 | } 15 | return expand.Variable{} 16 | } 17 | 18 | // Each iterates over all the currently set variables 19 | func (e Environ) Each(fn func(string, expand.Variable) bool) { 20 | for k, v := range e { 21 | fn(k, v) 22 | } 23 | } 24 | 25 | // MultiEnviron implements environment variable lookup in multiple environments 26 | type MultiEnviron []expand.Environ 27 | 28 | // Get retrieves a variable by its name from the first environ providing it 29 | func (e MultiEnviron) Get(name string) expand.Variable { 30 | z := expand.Variable{} 31 | for _, env := range e { 32 | if vr := env.Get(name); vr != z { 33 | return vr 34 | } 35 | } 36 | return expand.Variable{} 37 | } 38 | 39 | // Each iterates over all the currently set variables, in every environ 40 | func (e MultiEnviron) Each(fn func(string, expand.Variable) bool) { 41 | for _, env := range e { 42 | env.Each(fn) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /runtime/eval.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | // "io/fs" 8 | "log" 9 | "os" 10 | "strings" 11 | 12 | "mvdan.cc/sh/expand" 13 | "mvdan.cc/sh/interp" 14 | "mvdan.cc/sh/syntax" 15 | ) 16 | 17 | func limitedExec(ctx context.Context, path string, args []string) error { 18 | switch args[0] { 19 | case "vopt_if": 20 | return shVoptIf(ctx, args) 21 | case "vopt_with": 22 | case "vopt_enable": 23 | case "vopt_conflict": 24 | case "vopt_bool": 25 | case "vopt_feature": 26 | case "date": 27 | case "xbps-uhelper": 28 | case "seq": 29 | case "cut": 30 | case "grep": 31 | default: 32 | panic(args[0]) 33 | } 34 | return nil 35 | } 36 | 37 | type null struct{} 38 | 39 | func (n null) Read(b []byte) (int, error) { 40 | return 0, io.EOF 41 | } 42 | 43 | func (n null) Write(b []byte) (int, error) { 44 | return 0, io.ErrClosedPipe 45 | } 46 | 47 | func (n null) Close() error { 48 | return nil 49 | } 50 | 51 | func limitedOpen(ctx context.Context, path string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) { 52 | log.Printf("warn: open: %s: skipped\n", path) 53 | // return nil, fs.ErrPermission 54 | return null{}, nil 55 | } 56 | 57 | // evalSubPkgs 58 | func (r *Runtime) evalSubPkgs( 59 | run *interp.Runner, 60 | ctx context.Context, 61 | subpkgs ...string, 62 | ) ([]map[string]string, error) { 63 | // Clean the environment using common/environment/setup-subpkg/*.sh 64 | for _, f := range r.setupSubpkg { 65 | if err := run.Run(context.TODO(), f); err != nil { 66 | // XXX: ignore exit status? 67 | if _, ok := err.(interp.ExitStatus); !ok { 68 | return nil, fmt.Errorf("could not run: %v", err) 69 | } 70 | } 71 | } 72 | 73 | sourcepkg := run.Vars["pkgname"] 74 | run.Vars["sourcepkg"] = sourcepkg 75 | 76 | vars := make(map[string]expand.Variable) 77 | for k, v := range run.Vars { 78 | vars[k] = v 79 | } 80 | 81 | res := make([]map[string]string, len(subpkgs)) 82 | for i, subpkgname := range subpkgs { 83 | fnname := fmt.Sprintf("%s_package", subpkgname) 84 | fn, ok := run.Funcs[fnname] 85 | if !ok { 86 | return nil, fmt.Errorf("missing sub package function: %s", fnname) 87 | } 88 | 89 | run.Vars["pkgname"] = expand.Variable{Value: subpkgname} 90 | 91 | if err := run.Run(ctx, fn); err != nil { 92 | // XXX: ignore exit status? 93 | if _, ok := err.(interp.ExitStatus); !ok { 94 | return nil, fmt.Errorf("could not run: %v", err) 95 | } 96 | } 97 | 98 | res[i] = templateVars(run.Vars) 99 | 100 | // reset variables 101 | if i < len(subpkgs) { 102 | run.Vars = make(map[string]expand.Variable) 103 | for k, v := range vars { 104 | run.Vars[k] = v 105 | } 106 | } 107 | } 108 | 109 | return res, nil 110 | } 111 | 112 | func templateVars(vars map[string]expand.Variable) map[string]string { 113 | res := make(map[string]string) 114 | for k, v := range vars { 115 | // ignore variables starting with uppercase or _ 116 | if k[0] == '_' || (k[0] >= 'A' && k[0] <= 'Z') { 117 | continue 118 | } 119 | res[k] = strings.Join(strings.Fields(v.String()), " ") 120 | } 121 | return res 122 | } 123 | 124 | func getSubPackages(run *interp.Runner) []string { 125 | if subs, ok := run.Vars["subpackages"]; ok { 126 | return strings.Fields(subs.String()) 127 | } 128 | var res []string 129 | for fn, _ := range run.Funcs { 130 | if len(fn) <= len("_package") { 131 | continue 132 | } 133 | if s := fn[len(fn)-len("_package"):]; s == "_package" { 134 | res = append(res, fn[:len(fn)-len("_package")]) 135 | } 136 | } 137 | return res 138 | } 139 | 140 | // Eval evaluates a template 141 | func (r *Runtime) Eval( 142 | file *syntax.File, 143 | arch, cross string, 144 | ) ([]map[string]string, error) { 145 | 146 | env := r.Env(arch, cross) 147 | opts := make(Options) 148 | 149 | run, err := interp.New(interp.Env(MultiEnviron{env, opts})) 150 | if err != nil { 151 | return nil, err 152 | } 153 | run.Exec = limitedExec 154 | run.Open = limitedOpen 155 | 156 | // pass 1 to get options 157 | if err := run.Run(context.TODO(), file); err != nil { 158 | // XXX: ignore exit status? 159 | if _, ok := err.(interp.ExitStatus); !ok { 160 | return nil, fmt.Errorf("could not run: %s", err) 161 | } 162 | } 163 | opts.Add(run.Vars["build_options"].String()) 164 | opts.Defaults(run.Vars["build_options_default"].String()) 165 | 166 | // Add the build_style environment if set 167 | if name := run.Vars["build_style"].String(); name != "" { 168 | // check if the buildstyle has an environment script 169 | if file, ok := r.buildStyleEnv[name]; ok { 170 | if err := run.Run(context.TODO(), file); err != nil { 171 | // XXX: ignore exit status? 172 | if _, ok := err.(interp.ExitStatus); !ok { 173 | return nil, fmt.Errorf("could not run: %v", err) 174 | } 175 | } 176 | } 177 | } 178 | 179 | // pass 2 with options and buildstyle environment 180 | ctx := context.WithValue(context.Background(), "options", opts) 181 | if err := run.Run(ctx, file); err != nil { 182 | // XXX: ignore exit status? 183 | if _, ok := err.(interp.ExitStatus); !ok { 184 | return nil, fmt.Errorf("could not run: %v", err) 185 | } 186 | } 187 | 188 | var vars []map[string]string 189 | mainvars := templateVars(run.Vars) 190 | vars = append(vars, mainvars) 191 | 192 | subpkgs := getSubPackages(run) 193 | if len(subpkgs) == 0 { 194 | return vars, nil 195 | } 196 | 197 | subvars, err := r.evalSubPkgs(run, ctx, subpkgs...) 198 | if err != nil { 199 | return nil, err 200 | } 201 | mainvars["subpackages"] = strings.Join(subpkgs, " ") 202 | return append(vars, subvars...), nil 203 | 204 | } 205 | -------------------------------------------------------------------------------- /runtime/options.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "mvdan.cc/sh/expand" 8 | ) 9 | 10 | type Options map[string]bool 11 | 12 | // Defaults adds all options from str and sets them to true 13 | func (o Options) Defaults(str string) { 14 | for _, opt := range strings.Fields(str) { 15 | o[opt] = true 16 | } 17 | } 18 | 19 | // Add adds all options from str without overwriting previous set options 20 | func (o Options) Add(str string) { 21 | for _, opt := range strings.Fields(str) { 22 | if _, ok := o[opt]; ok { 23 | continue 24 | } 25 | o[opt] = false 26 | } 27 | } 28 | 29 | func (o Options) String() string { 30 | strs := make([]string, len(o)) 31 | for k, v := range o { 32 | if v { 33 | strs = append(strs, k) 34 | } else { 35 | strs = append(strs, fmt.Sprintf("~%s", k)) 36 | } 37 | } 38 | return strings.Join(strs, " ") 39 | } 40 | 41 | // Get implements the expand.Environ interface for build options. 42 | func (o Options) Get(name string) expand.Variable { 43 | opt := strings.TrimPrefix(name, "build_option_") 44 | if opt == name { 45 | return expand.Variable{} 46 | } 47 | v, ok := o[opt] 48 | if !ok { 49 | return expand.Variable{} 50 | } 51 | val := "" 52 | if v { 53 | val = "1" 54 | } 55 | return expand.Variable{ 56 | Exported: false, 57 | ReadOnly: true, 58 | Local: true, 59 | Value: val, 60 | } 61 | } 62 | 63 | // Each implements the expand.Environ interface for build options. 64 | func (o Options) Each(fn func(string, expand.Variable) bool) { 65 | for k, v := range o { 66 | val := "" 67 | if v { 68 | val = "1" 69 | } 70 | fn(fmt.Sprintf("build_option_%s", k), expand.Variable{ 71 | Exported: false, 72 | ReadOnly: true, 73 | Local: true, 74 | Value: val, 75 | }) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /runtime/runtime.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | "path" 8 | "path/filepath" 9 | "strings" 10 | 11 | "mvdan.cc/sh/syntax" 12 | ) 13 | 14 | const envFilesSubPkg = "common/environment/setup-subpkg/*.sh" 15 | const envFilesBuildStyle = "common/environment/build-style/*.sh" 16 | const virtualPkgDefaults = "etc/defaults.virtual" 17 | 18 | type Runtime struct { 19 | setupSubpkg []*syntax.File 20 | buildStyleEnv map[string]*syntax.File 21 | parser *syntax.Parser 22 | distdir string 23 | env Environ 24 | virtdefs map[string]string 25 | } 26 | 27 | // Parse parses a bash script 28 | func (r *Runtime) Parse(path string) (*syntax.File, error) { 29 | rd, err := os.Open(path) 30 | if err != nil { 31 | return nil, err 32 | } 33 | defer rd.Close() 34 | f, err := r.parser.Parse(rd, path) 35 | if err != nil { 36 | return nil, err 37 | } 38 | return f, nil 39 | } 40 | 41 | // New creates and initializes the runtime 42 | func New(distdir string) (*Runtime, error) { 43 | r := &Runtime{ 44 | distdir: distdir, 45 | parser: syntax.NewParser(syntax.Variant(syntax.LangBash)), 46 | buildStyleEnv: make(map[string]*syntax.File), 47 | } 48 | 49 | pat := path.Join(r.distdir, envFilesSubPkg) 50 | files, err := filepath.Glob(pat) 51 | if err != nil { 52 | return nil, err 53 | } 54 | if len(files) == 0 { 55 | return nil, fmt.Errorf("No files found for pattern %q", pat) 56 | } 57 | for _, path := range files { 58 | f, err := r.Parse(path) 59 | if err != nil { 60 | return nil, err 61 | } 62 | r.setupSubpkg = append(r.setupSubpkg, f) 63 | } 64 | 65 | pat = path.Join(r.distdir, envFilesBuildStyle) 66 | files, err = filepath.Glob(pat) 67 | if err != nil { 68 | return nil, err 69 | } 70 | if len(files) == 0 { 71 | return nil, fmt.Errorf("No files found for pattern %q", pat) 72 | } 73 | for _, path := range files { 74 | f, err := r.Parse(path) 75 | if err != nil { 76 | return nil, err 77 | } 78 | name := strings.TrimSuffix(filepath.Base(path), ".sh") 79 | r.buildStyleEnv[name] = f 80 | } 81 | 82 | r.virtdefs = make(map[string]string) 83 | pat = path.Join(r.distdir, virtualPkgDefaults) 84 | file, err := os.Open(pat) 85 | if err != nil { 86 | return nil, err 87 | } 88 | defer file.Close() 89 | scanner := bufio.NewScanner(file) 90 | for scanner.Scan() { 91 | line := scanner.Text() 92 | if len(line) == 0 || strings.HasPrefix(line, "#") { 93 | continue 94 | } 95 | fields := strings.Fields(line) 96 | if len(fields) != 2 { 97 | panic("invalid default virtual dependency") 98 | } 99 | r.virtdefs[fields[0]] = fields[1] 100 | } 101 | if err := scanner.Err(); err != nil { 102 | return nil, err 103 | } 104 | 105 | return r, nil 106 | } 107 | 108 | // GetVirtual 109 | func (r *Runtime) GetVirtual(pkgname string) (string, error) { 110 | if pkg, ok := r.virtdefs[pkgname]; ok { 111 | return pkg, nil 112 | } 113 | return "", fmt.Errorf("virtual package not in defaults: %s", pkgname) 114 | } 115 | -------------------------------------------------------------------------------- /runtime/runtime_test.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestNew(t *testing.T) { 8 | _, err := New("/home/duncan/void-packages") 9 | if err != nil { 10 | t.Fatal(err) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /runtime/shfuncs.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "io" 8 | 9 | "mvdan.cc/sh/interp" 10 | ) 11 | 12 | // write writes a string to a module context's stdout (linked in ctx). 13 | func write(ctx context.Context, s string) error { 14 | mod, ok := interp.FromModuleContext(ctx) 15 | if !ok { 16 | return errors.New("unable to acquire module context") 17 | } 18 | _, err := io.WriteString(mod.Stdout, s) 19 | return err 20 | } 21 | 22 | func shVoptIf(ctx context.Context, args []string) error { 23 | var opt string 24 | v := false 25 | ifTrue, ifFalse := "", "" 26 | 27 | switch len(args) { 28 | case 4: 29 | ifFalse = args[3] 30 | fallthrough 31 | case 3: 32 | opt = args[1] 33 | ifTrue = args[2] 34 | default: 35 | return errors.New("missing argument") 36 | } 37 | 38 | switch x := ctx.Value("options").(type) { 39 | case Options: 40 | var ok bool 41 | if v, ok = x[opt]; !ok { 42 | return fmt.Errorf("invalid option: %q", opt) 43 | } 44 | } 45 | 46 | if v { 47 | write(ctx, ifTrue) 48 | } else { 49 | write(ctx, ifFalse) 50 | } 51 | return nil 52 | } 53 | -------------------------------------------------------------------------------- /template/options.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "mvdan.cc/sh/expand" 8 | ) 9 | 10 | type OptionsCtxKey struct{} 11 | 12 | type Options map[string]bool 13 | 14 | func (o Options) setFromTemplateVars(opts, defs string) Options { 15 | for _, opt := range strings.Fields(defs) { 16 | o[opt] = true 17 | } 18 | for _, opt := range strings.Fields(opts) { 19 | if _, ok := o[opt]; ok { 20 | continue 21 | } 22 | o[opt] = false 23 | } 24 | return o 25 | } 26 | 27 | func (o Options) String() string { 28 | strs := make([]string, len(o)) 29 | for k, v := range o { 30 | if v { 31 | strs = append(strs, k) 32 | } else { 33 | strs = append(strs, fmt.Sprintf("~%s", k)) 34 | } 35 | } 36 | return strings.Join(strs, " ") 37 | } 38 | 39 | // Get implements the expand.Environ interface for build options. 40 | func (o Options) Get(name string) expand.Variable { 41 | opt := strings.TrimPrefix(name, "build_option_") 42 | if opt == name { 43 | return expand.Variable{} 44 | } 45 | v, ok := o[opt] 46 | if !ok { 47 | return expand.Variable{} 48 | } 49 | val := "" 50 | if v { 51 | val = "1" 52 | } 53 | return expand.Variable{ 54 | Exported: false, 55 | ReadOnly: true, 56 | Local: true, 57 | Value: val, 58 | } 59 | } 60 | 61 | // Each implements the expand.Environ interface for build options. 62 | func (o Options) Each(fn func(string, expand.Variable) bool) { 63 | for k, v := range o { 64 | val := "" 65 | if v { 66 | val = "1" 67 | } 68 | fn(fmt.Sprintf("build_option_%s", k), expand.Variable{ 69 | Exported: false, 70 | ReadOnly: true, 71 | Local: true, 72 | Value: val, 73 | }) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /template/template.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | "os" 7 | 8 | "github.com/Duncaen/go-xbps-src/runtime" 9 | 10 | "mvdan.cc/sh/syntax" 11 | ) 12 | 13 | type Template struct { 14 | file *syntax.File 15 | } 16 | 17 | // ParseFile parses the template at path 18 | func ParseFile(path string) (*Template, error) { 19 | r, err := os.Open(path) 20 | if err != nil { 21 | return nil, err 22 | } 23 | defer r.Close() 24 | return Parse(r, path) 25 | } 26 | 27 | // Parse parses a template from r 28 | func Parse(r io.Reader, name string) (*Template, error) { 29 | parser := syntax.NewParser(syntax.Variant(syntax.LangBash)) 30 | f, err := parser.Parse(r, name) 31 | if err != nil { 32 | return nil, err 33 | } 34 | if parser.Incomplete() { 35 | return nil, errors.New("inclomplete") 36 | } 37 | t := &Template{file: f} 38 | return t, nil 39 | } 40 | 41 | // Eval evaluates a template 42 | func (t *Template) Eval( 43 | runtime *runtime.Runtime, 44 | arch, cross string, 45 | ) ([]map[string]string, error) { 46 | return runtime.Eval(t.file, arch, cross) 47 | } 48 | --------------------------------------------------------------------------------