github.com/Benchkram/bob@v0.0.0-20220321080157-7c8f3876e225/bob/aggregate.go (about)

     1  package bob
     2  
     3  import (
     4  	"fmt"
     5  	"path/filepath"
     6  	"strings"
     7  
     8  	"github.com/Benchkram/bob/bobtask"
     9  	"github.com/Benchkram/bob/pkg/usererror"
    10  
    11  	"github.com/logrusorgru/aurora"
    12  
    13  	"github.com/Benchkram/errz"
    14  
    15  	"github.com/Benchkram/bob/bob/bobfile"
    16  	"github.com/Benchkram/bob/pkg/filepathutil"
    17  	"github.com/hashicorp/go-version"
    18  )
    19  
    20  // find bobfiles recursively.
    21  func (b *B) find() (bobfiles []string, err error) {
    22  	defer errz.Recover(&err)
    23  
    24  	list, err := filepathutil.ListRecursive(b.dir)
    25  	errz.Fatal(err)
    26  
    27  	for _, file := range list {
    28  		if bobfile.IsBobfile(file) {
    29  			bobfiles = append(bobfiles, file)
    30  		}
    31  	}
    32  
    33  	return bobfiles, nil
    34  }
    35  
    36  func (b *B) PrintVersionCompatibility(bobfile *bobfile.Bobfile) {
    37  	binVersion, _ := version.NewVersion(Version)
    38  
    39  	for _, boblet := range bobfile.Bobfiles() {
    40  		if boblet.Version != "" {
    41  			bobletVersion, _ := version.NewVersion(boblet.Version)
    42  
    43  			if binVersion.Core().Segments64()[0] != bobletVersion.Core().Segments64()[0] {
    44  				fmt.Println(aurora.Red(fmt.Sprintf("Warning: major version mismatch: Your bobfile's major version (%s, '%s') is different from the CLI version (%s). This might lead to unexpected errors.", boblet.Version, boblet.Dir(), binVersion)).String())
    45  				continue
    46  			}
    47  
    48  			if binVersion.LessThan(bobletVersion) {
    49  				fmt.Println(aurora.Red(fmt.Sprintf("Warning: possible version incompatibility: Your bobfile's version (%s, '%s') is higher than the CLI version (%s). Some features might not work as expected.", boblet.Version, boblet.Dir(), binVersion)).String())
    50  				continue
    51  			}
    52  		}
    53  	}
    54  }
    55  
    56  // Aggregate determine and read Bobfiles recursively into memory
    57  // and returns a single Bobfile containing all tasks & runs.
    58  func (b *B) Aggregate() (aggregate *bobfile.Bobfile, err error) {
    59  	defer errz.Recover(&err)
    60  
    61  	bobfiles, err := b.find()
    62  	errz.Fatal(err)
    63  
    64  	// Read & Find Bobfiles
    65  	bobs := []*bobfile.Bobfile{}
    66  	for _, bf := range bobfiles {
    67  		boblet, err := bobfile.BobfileRead(filepath.Dir(bf))
    68  		errz.Fatal(err)
    69  
    70  		if boblet.Dir() == b.dir {
    71  			aggregate = boblet
    72  		}
    73  
    74  		for variable, value := range boblet.Variables {
    75  			for key, task := range boblet.BTasks {
    76  				// TODO: Create and use envvar sanitizer
    77  
    78  				task.AddEnvironment(strings.ToUpper(variable), value)
    79  
    80  				boblet.BTasks[key] = task
    81  			}
    82  		}
    83  
    84  		bobs = append(bobs, boblet)
    85  	}
    86  
    87  	if aggregate == nil {
    88  		return nil, usererror.Wrap(ErrCouldNotFindTopLevelBobfile)
    89  	}
    90  
    91  	aggregate.SetBobfiles(bobs)
    92  
    93  	// Merge tasks into one Bobfile
    94  	for _, bobfile := range bobs {
    95  		// Skip the aggregate
    96  		if bobfile.Dir() == aggregate.Dir() {
    97  			continue
    98  		}
    99  
   100  		for taskname, task := range bobfile.BTasks {
   101  			dir := bobfile.Dir()
   102  
   103  			// Use a relative path as task prefix.
   104  			prefix := strings.TrimPrefix(dir, b.dir)
   105  			taskname := addTaskPrefix(prefix, taskname)
   106  
   107  			// fmt.Printf("aggreagted [dir:%s, bdir:%s prefix:%s] taskname %s\n", prefix, dir, b.dir, taskname)
   108  
   109  			// Alter the taskname.
   110  			task.SetName(taskname)
   111  
   112  			// Rewrite dependent tasks to global scope.
   113  			dependsOn := []string{}
   114  			for _, dependentTask := range task.DependsOn {
   115  				dependsOn = append(dependsOn, addTaskPrefix(prefix, dependentTask))
   116  			}
   117  			task.DependsOn = dependsOn
   118  
   119  			aggregate.BTasks[taskname] = task
   120  		}
   121  	}
   122  
   123  	// Merge runs into one Bobfile
   124  	for _, bobfile := range bobs {
   125  		// Skip the aggregate
   126  		if bobfile.Dir() == aggregate.Dir() {
   127  			continue
   128  		}
   129  
   130  		for runname, run := range bobfile.RTasks {
   131  			dir := bobfile.Dir()
   132  
   133  			// Use a relative path as task prefix.
   134  			prefix := strings.TrimPrefix(dir, b.dir)
   135  			name := addTaskPrefix(prefix, runname)
   136  
   137  			// Alter the runname.
   138  			run.SetName(name)
   139  
   140  			// Rewrite dependents to global scope.
   141  			dependsOn := []string{}
   142  			for _, dependent := range run.DependsOn {
   143  				dependsOn = append(dependsOn, addTaskPrefix(prefix, dependent))
   144  			}
   145  			run.DependsOn = dependsOn
   146  
   147  			aggregate.RTasks[name] = run
   148  		}
   149  	}
   150  
   151  	// TODO: Gather missing tasks from remote  & Unpack?
   152  
   153  	// Gather environment from dependent tasks.
   154  	//
   155  	// Each export is translated into environment variables named:
   156  	//   `second-level/openapi => SECOND_LEVEL_OPENAPI`
   157  	// hyphens`-` are translated to underscores`_`.
   158  	//
   159  	// The file is prefixed with all paths to make it relative to dir of the the top Bobfile:
   160  	//   `openapi.yaml => sencond-level/openapi.yaml`
   161  	//
   162  	// TODO: Exports should be part of a packed file and should be evaluated when running a playbook or at least after Unpack().
   163  	// Looks like this is the wrong place to presume that all child tasks are comming from child bobfiles
   164  	// must exist.
   165  	for i, task := range aggregate.BTasks {
   166  		for _, dependentTaskName := range task.DependsOn {
   167  
   168  			dependentTask, ok := aggregate.BTasks[dependentTaskName]
   169  			if !ok {
   170  				return nil, ErrTaskDoesNotExist
   171  			}
   172  
   173  			for exportname, export := range dependentTask.Exports {
   174  				// fmt.Printf("Task %s exports %s\n", dependentTaskName, export)
   175  
   176  				envvar := taskNameToEnvironment(dependentTaskName, exportname)
   177  
   178  				value := filepath.Join(dependentTask.Dir(), string(export))
   179  
   180  				// Make the path relative to the aggregates dir.
   181  				dir := aggregate.Dir()
   182  				if !strings.HasSuffix(dir, "/") {
   183  					dir = dir + "/"
   184  				}
   185  				value = strings.TrimPrefix(value, dir)
   186  
   187  				// println(envvar, value)
   188  
   189  				task.AddEnvironment(envvar, value)
   190  
   191  				aggregate.BTasks[i] = task
   192  			}
   193  		}
   194  	}
   195  
   196  	// Assure tasks are correctly initialised.
   197  	for i, task := range aggregate.BTasks {
   198  		task.WithLocalstore(b.local)
   199  		task.WithBuildinfoStore(b.buildInfoStore)
   200  		task.SetBuilder(b.dir) // TODO: todoproject, use project name instead of dir
   201  
   202  		// a task must always-rebuild when caching is disabled
   203  		if !b.enableCaching {
   204  			task.SetRebuildStrategy(bobtask.RebuildAlways)
   205  		}
   206  		aggregate.BTasks[i] = task
   207  	}
   208  
   209  	return aggregate, aggregate.Verify()
   210  }
   211  
   212  func addTaskPrefix(prefix, taskname string) string {
   213  	taskname = filepath.Join(prefix, taskname)
   214  	taskname = strings.TrimPrefix(taskname, "/")
   215  	return taskname
   216  }
   217  
   218  // taskNameToEnvironment
   219  //
   220  // Each taskname is translated into environment variables like:
   221  //   `second-level/openapi_exportname => SECOND_LEVEL_OPENAPI_EXPORTNAME`
   222  // Hyphens`-` are translated to underscores`_`.
   223  func taskNameToEnvironment(taskname string, exportname string) string {
   224  
   225  	splits := strings.Split(taskname, "/")
   226  	splits = append(splits, exportname)
   227  
   228  	envvar := strings.Join(splits, "_")
   229  	envvar = strings.ReplaceAll(envvar, "-", "_")
   230  	envvar = strings.ReplaceAll(envvar, ".", "_")
   231  	envvar = strings.ToUpper(envvar)
   232  
   233  	return envvar
   234  }