github.com/Benchkram/bob@v0.0.0-20220321080157-7c8f3876e225/bob/bobfile/bobfile.go (about) 1 package bobfile 2 3 import ( 4 "bytes" 5 "fmt" 6 "io/ioutil" 7 "path/filepath" 8 "strings" 9 10 "github.com/Benchkram/bob/pkg/usererror" 11 12 "github.com/hashicorp/go-version" 13 "github.com/pkg/errors" 14 15 "gopkg.in/yaml.v3" 16 17 "github.com/Benchkram/errz" 18 19 "github.com/Benchkram/bob/bob/global" 20 "github.com/Benchkram/bob/bobrun" 21 "github.com/Benchkram/bob/bobtask" 22 "github.com/Benchkram/bob/pkg/file" 23 ) 24 25 var ( 26 defaultIgnores = fmt.Sprintf("!%s\n!%s", 27 global.BobWorkspaceFile, 28 filepath.Join(global.BobCacheDir, "*"), 29 ) 30 ) 31 32 var ( 33 ErrNotImplemented = fmt.Errorf("Not implemented") 34 ErrBobfileNotFound = fmt.Errorf("Could not find a Bobfile") 35 ErrHashesFileDoesNotExist = fmt.Errorf("Hashes file does not exist") 36 ErrTaskHashDoesNotExist = fmt.Errorf("Task hash does not exist") 37 ErrBobfileExists = fmt.Errorf("Bobfile exists") 38 ErrTaskDoesNotExist = fmt.Errorf("Task does not exist") 39 ErrDuplicateTaskName = fmt.Errorf("duplicate task name") 40 ErrSelfReference = fmt.Errorf("self reference") 41 42 ErrInvalidRunType = fmt.Errorf("Invalid run type") 43 ) 44 45 type Bobfile struct { 46 Version string `yaml:"version,omitempty"` 47 48 Variables VariableMap 49 50 // BTasks build tasks 51 BTasks bobtask.Map `yaml:"build"` 52 // RTasks run tasks 53 RTasks bobrun.RunMap `yaml:"run"` 54 55 // Parent directory of the Bobfile. 56 // Populated through BobfileRead(). 57 dir string 58 59 bobfiles []*Bobfile 60 } 61 62 func NewBobfile() *Bobfile { 63 b := &Bobfile{ 64 Variables: make(VariableMap), 65 BTasks: make(bobtask.Map), 66 RTasks: make(bobrun.RunMap), 67 } 68 return b 69 } 70 71 func (b *Bobfile) SetBobfiles(bobs []*Bobfile) { 72 b.bobfiles = bobs 73 } 74 75 func (b *Bobfile) Bobfiles() []*Bobfile { 76 return b.bobfiles 77 } 78 79 // bobfileRead reads a bobfile and intializes private fields. 80 func bobfileRead(dir string) (_ *Bobfile, err error) { 81 defer errz.Recover(&err) 82 83 bobfilePath := filepath.Join(dir, global.BobFileName) 84 85 if !file.Exists(bobfilePath) { 86 return nil, ErrBobfileNotFound 87 } 88 bin, err := ioutil.ReadFile(bobfilePath) 89 errz.Fatal(err, "Failed to read config file") 90 91 bobfile := &Bobfile{ 92 dir: dir, 93 } 94 95 err = yaml.Unmarshal(bin, bobfile) 96 if err != nil { 97 return nil, usererror.Wrapm(err, "YAML unmarshal failed") 98 } 99 100 if bobfile.Variables == nil { 101 bobfile.Variables = VariableMap{} 102 } 103 104 if bobfile.BTasks == nil { 105 bobfile.BTasks = bobtask.Map{} 106 } 107 108 if bobfile.RTasks == nil { 109 bobfile.RTasks = bobrun.RunMap{} 110 } 111 112 // Assure tasks are initialized with their defaults 113 for key, task := range bobfile.BTasks { 114 task.SetDir(bobfile.dir) 115 task.SetName(key) 116 117 task.InputDirty = fmt.Sprintf("%s\n%s", task.InputDirty, defaultIgnores) 118 119 // Make sure a task is correctly initialised. 120 // TODO: All unitialised must be initialised or get default values. 121 // This means switching to pointer types for most members. 122 task.SetEnv([]string{}) 123 task.SetRebuildStrategy(bobtask.RebuildOnChange) 124 125 // TODO: todoproject 126 task.SetProject(dir) 127 128 // initialize docker registry for task 129 task.SetDockerRegistryClient() 130 131 bobfile.BTasks[key] = task 132 } 133 134 // Assure runs are initialized with their defaults 135 for key, run := range bobfile.RTasks { 136 run.SetDir(bobfile.dir) 137 run.SetName(key) 138 139 bobfile.RTasks[key] = run 140 } 141 142 return bobfile, nil 143 } 144 145 // BobfileRead read from a bobfile. 146 // Calls sanitize on the result. 147 func BobfileRead(dir string) (_ *Bobfile, err error) { 148 defer errz.Recover(&err) 149 150 b, err := bobfileRead(dir) 151 errz.Fatal(err) 152 153 err = b.Validate() 154 errz.Fatal(err) 155 156 return b, b.BTasks.Sanitize() 157 } 158 159 // Validate makes sure no task depends on itself (self-reference) or has the same name as another task 160 func (b *Bobfile) Validate() (err error) { 161 if b.Version != "" { 162 _, err = version.NewVersion(b.Version) 163 if err != nil { 164 return fmt.Errorf("invalid version '%s' (%s)", b.Version, b.Dir()) 165 } 166 } 167 168 // use for duplicate names validation 169 names := map[string]bool{} 170 171 for name, task := range b.BTasks { 172 // validate no duplicate name 173 if names[name] { 174 return errors.WithMessage(ErrDuplicateTaskName, name) 175 } 176 177 names[name] = true 178 179 // validate no self-reference 180 for _, dep := range task.DependsOn { 181 if name == dep { 182 return errors.WithMessage(ErrSelfReference, name) 183 } 184 } 185 } 186 187 for name, run := range b.RTasks { 188 // validate no duplicate name 189 if names[name] { 190 return errors.WithMessage(ErrDuplicateTaskName, name) 191 } 192 193 names[name] = true 194 195 // validate no self-reference 196 for _, dep := range run.DependsOn { 197 if name == dep { 198 return errors.WithMessage(ErrSelfReference, name) 199 } 200 } 201 } 202 203 return nil 204 } 205 206 func (b *Bobfile) BobfileSave(dir string) (err error) { 207 defer errz.Recover(&err) 208 209 buf := bytes.NewBuffer([]byte{}) 210 211 encoder := yaml.NewEncoder(buf) 212 encoder.SetIndent(2) 213 defer encoder.Close() 214 215 err = encoder.Encode(b) 216 errz.Fatal(err) 217 218 return ioutil.WriteFile(filepath.Join(dir, global.BobFileName), buf.Bytes(), 0664) 219 } 220 221 func (b *Bobfile) Dir() string { 222 return b.dir 223 } 224 225 func CreateDummyBobfile(dir string, overwrite bool) (err error) { 226 // Prevent accidential bobfile override 227 if file.Exists(global.BobFileName) && !overwrite { 228 return ErrBobfileExists 229 } 230 231 bobfile := NewBobfile() 232 233 bobfile.BTasks[global.DefaultBuildTask] = bobtask.Task{ 234 InputDirty: "./main.go", 235 CmdDirty: "go build -o run", 236 TargetDirty: "run", 237 } 238 return bobfile.BobfileSave(dir) 239 } 240 241 func IsBobfile(file string) bool { 242 return strings.Contains(filepath.Base(file), global.BobFileName) 243 }