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  }