github.com/SAP/cloud-mta-build-tool@v1.2.27/internal/buildops/modules_deps.go (about)

     1  package buildops
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/deckarep/golang-set"
     7  	"github.com/pkg/errors"
     8  
     9  	"github.com/SAP/cloud-mta-build-tool/internal/archive"
    10  	"github.com/SAP/cloud-mta/mta"
    11  )
    12  
    13  type graphNode struct {
    14  	module string
    15  	deps   mapset.Set
    16  	index  int
    17  }
    18  
    19  // ProvideModules - provides modules in order of their dependencies
    20  func ProvideModules(source, desc string, extensions []string, wdGetter func() (string, error)) error {
    21  	loc, err := dir.Location(source, "", desc, extensions, wdGetter)
    22  	if err != nil {
    23  		return errors.Wrap(err, locFailedMsg)
    24  	}
    25  	m, err := loc.ParseFile()
    26  	if err != nil {
    27  		return err
    28  	}
    29  	modules, err := GetModulesNames(m)
    30  	if err != nil {
    31  		return err
    32  	}
    33  	// Get list of modules names
    34  	fmt.Println(modules)
    35  	return nil
    36  }
    37  
    38  // ProcessDependencies - processes module dependencies
    39  // function prepares all artifacts required for module
    40  // copying them from required modules
    41  func ProcessDependencies(mtaParser dir.IMtaParser, moduleSource dir.ISourceModule, moduleName string) error {
    42  	m, err := mtaParser.ParseFile()
    43  	if err != nil {
    44  		return err
    45  	}
    46  	module, err := m.GetModuleByName(moduleName)
    47  	if err != nil {
    48  		return err
    49  	}
    50  	requires := GetBuildRequires(module)
    51  	if len(requires) > 0 {
    52  		for _, req := range requires {
    53  			e := ProcessRequirements(moduleSource, m, &req, module.Name)
    54  			if e != nil {
    55  				return e
    56  			}
    57  		}
    58  	}
    59  	return nil
    60  }
    61  
    62  // New graphs node
    63  func newGn(module *string, deps mapset.Set, index int) *graphNode {
    64  	return &graphNode{module: *module, deps: deps, index: index}
    65  }
    66  
    67  // graphs - graph map
    68  type graphs map[string]*graphNode
    69  
    70  // GetModulesNames returns a list of module names.
    71  func GetModulesNames(m *mta.MTA) ([]string, error) {
    72  	return getModulesOrder(m)
    73  }
    74  
    75  // getModulesOrder - Provides Modules ordered according to build-parameters' dependencies
    76  func getModulesOrder(m *mta.MTA) ([]string, error) {
    77  	var graph = make(graphs)
    78  	for index, module := range m.Modules {
    79  		deps := mapset.NewSet()
    80  		requires := GetBuildRequires(module)
    81  		if len(requires) > 0 {
    82  			for _, req := range requires {
    83  				_, err := m.GetModuleByName(req.Name)
    84  				if err != nil {
    85  					return nil, err
    86  				}
    87  				deps.Add(req.Name)
    88  			}
    89  		}
    90  		graph[module.Name] = newGn(&module.Name, deps, index)
    91  	}
    92  	return resolveGraph(&graph, m)
    93  }
    94  
    95  // Resolves the dependency graphs
    96  // For resolving cyclic dependencies Kahn’s algorithm of topological sorting is used.
    97  // https://en.wikipedia.org/wiki/Topological_sorting
    98  func resolveGraph(graph *graphs, m *mta.MTA) ([]string, error) {
    99  	overleft := *graph
   100  
   101  	// Iteratively find and remove nodes from the graphs which have no dependencies.
   102  	// If at some point there are still nodes in the graphs and we cannot find
   103  	// nodes without dependencies, that means we have a circular dependency
   104  	var resolved []string
   105  	for len(overleft) != 0 {
   106  		// Get all nodes from the graphs which have no dependencies
   107  		readyNodesSet := mapset.NewSet()
   108  		readyModulesSet := mapset.NewSet()
   109  		for _, node := range overleft {
   110  			if node.deps.Cardinality() == 0 {
   111  				readyNodesSet.Add(node)
   112  				readyModulesSet.Add(node.module)
   113  			}
   114  		}
   115  		// If there aren't any ready nodes, then we have a circular dependency
   116  		if readyNodesSet.Cardinality() == 0 {
   117  			module1, module2 := provideCyclicModules(&overleft)
   118  			return nil, errors.Errorf(circularDepsMsg, module1, module2)
   119  		}
   120  		// Remove the ready nodes and add them to the resolved graphs
   121  		readyModulesIndexes := mapset.NewSet()
   122  		for node := range readyNodesSet.Iter() {
   123  			delete(overleft, node.(*graphNode).module)
   124  			readyModulesIndexes.Add(node.(*graphNode).index)
   125  		}
   126  
   127  		for index, module := range m.Modules {
   128  			if readyModulesIndexes.Contains(index) {
   129  				resolved = append(resolved, module.Name)
   130  			}
   131  		}
   132  
   133  		// remove the ready nodes from the remaining node dependencies as well
   134  		for _, node := range overleft {
   135  			node.deps = node.deps.Difference(readyModulesSet)
   136  		}
   137  	}
   138  
   139  	return resolved, nil
   140  }
   141  
   142  // provideCyclicModules - provide some of modules having cyclic dependencies
   143  func provideCyclicModules(overleft *graphs) (string, string) {
   144  	module1 := ""
   145  	module2 := ""
   146  	index := 0
   147  	for _, node := range *overleft {
   148  		if index == 0 {
   149  			module1 = node.module
   150  			index = 1
   151  		} else {
   152  			module2 = node.module
   153  			break
   154  		}
   155  	}
   156  	return module1, module2
   157  }