diff --git a/manifests/manifests.go b/manifests/manifests.go index 65a4c76..8e51539 100644 --- a/manifests/manifests.go +++ b/manifests/manifests.go @@ -8,9 +8,28 @@ import ( "strings" ) -func validDependency(components []string, dependency string) bool { +// Kind of the dependency (enum) +type Kind int + +// Weak dependency is one, changes to which cause rebuild of its dependents, +// but dependents builds can run in parallel with its build +var Weak Kind = 1 + +// Strong dependency is one, changes to which cause rebuild of its dependentes, +// and the dependent builds can only be run when its build successfully finishes +var Strong Kind = 2 + +// Dependency holds information about the dependency relationship of one +// component with another. Dependencies hav a string name and a kind, which +// can be Weak or Strong +type Dependency struct { + name string + kind Kind +} + +func validDependency(components []string, dependency Dependency) bool { for _, c := range components { - if c == dependency { + if c == dependency.name { return true } } @@ -18,52 +37,90 @@ func validDependency(components []string, dependency string) bool { return false } -func Read(manifestPaths []string, dependOnSelf bool) ([]string, map[string][]string, []error) { - dependencies := make(map[string][]string, len(manifestPaths)) +func readDependency(line string) (Dependency, error) { + dep := strings.TrimRight(strings.TrimSpace(line), "/") + + // blank line or comment + if len(dep) < 1 || dep[0] == '#' { + return Dependency{}, nil + } + + // strong dependency + if dep[0] == '!' { + return Dependency{dep[1:], Strong}, nil + } + + return Dependency{dep, Weak}, nil +} + +func ReadManifest(path string) (string, []Dependency, []error) { + dependencies := make([]Dependency, 0) + errors := make([]error, 0) + + file, err := os.Open(path) + if err != nil { + return "", nil, []error{fmt.Errorf("cannot open dependency manifest %s: %s", path, err)} + } + + dir, _ := filepath.Split(path) + component := strings.TrimRight(dir, "/") + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + dep, err := readDependency(scanner.Text()) + + if err != nil { + errors = append(errors, err) + continue + } + + // comment or blank line + if dep.name == "" { + continue + } + + dependencies = append(dependencies, dep) + } + + err = scanner.Err() + if err != nil { + return "", nil, []error{fmt.Errorf("cannot read dependency manifest %s: %s", path, err)} + } + + return component, dependencies, nil +} + +func Read(manifestPaths []string, dependOnSelf bool) ([]string, map[string][]Dependency, []error) { + dependencies := make(map[string][]Dependency, len(manifestPaths)) components := make([]string, 0) errors := []error{} for _, manifest := range manifestPaths { - file, err := os.Open(manifest) + component, deps, err := ReadManifest(manifest) if err != nil { - return nil, nil, []error{fmt.Errorf("cannot open dependency manifest %s: %s", manifest, err)} + errors = append(errors, err...) + continue } - dir, _ := filepath.Split(manifest) - component := strings.TrimRight(dir, "/") - components = append(components, component) - if dependOnSelf { - dependencies[component] = []string{component} - } else { - dependencies[component] = []string{} + deps = append([]Dependency{Dependency{component, Weak}}, deps...) } - scanner := bufio.NewScanner(file) - for scanner.Scan() { - dep := strings.TrimRight(strings.TrimSpace(scanner.Text()), "/") - if len(dep) > 0 && dep[0] != '#' { - dependencies[component] = append(dependencies[component], dep) - } - } - - err = scanner.Err() - if err != nil { - return nil, nil, []error{fmt.Errorf("cannot read dependency manifest %s: %s", manifest, err)} - } + components = append(components, component) + dependencies[component] = deps } // validate dependencies for manifest, deps := range dependencies { for _, dep := range deps { if !validDependency(components, dep) { - errors = append(errors, fmt.Errorf("unknown dependency '%s' of '%s'", dep, manifest)) + errors = append(errors, fmt.Errorf("unknown dependency '%s' of '%s'", dep.name, manifest)) } } } if len(errors) > 0 { - return components, dependencies, errors + return nil, nil, errors } return components, dependencies, nil diff --git a/manifests/manifests_test.go b/manifests/manifests_test.go index 0ab0d09..f6996e9 100644 --- a/manifests/manifests_test.go +++ b/manifests/manifests_test.go @@ -27,7 +27,7 @@ func Test_Read(t *testing.T) { cwd string pattern string want []string - want1 map[string][]string + want1 map[string][]Dependency wantErr bool }{ { @@ -44,15 +44,15 @@ func Test_Read(t *testing.T) { "libs/lib3", "stack1", }, - map[string][]string{ - "app1": []string{"app1", "libs/lib1", "libs/lib2"}, - "app2": []string{"app2", "libs/lib2", "libs/lib3"}, - "app3": []string{"app3", "libs/lib3"}, - "app4": []string{"app4"}, - "libs/lib1": []string{"libs/lib1", "libs/lib3"}, - "libs/lib2": []string{"libs/lib2", "libs/lib3"}, - "libs/lib3": []string{"libs/lib3"}, - "stack1": []string{"stack1", "app1", "app2", "app3"}, + map[string][]Dependency{ + "app1": []Dependency{{"app1", Weak}, {"libs/lib1", Weak}, {"libs/lib2", Weak}}, + "app2": []Dependency{{"app2", Weak}, {"libs/lib2", Weak}, {"libs/lib3", Weak}}, + "app3": []Dependency{{"app3", Weak}, {"libs/lib3", Weak}}, + "app4": []Dependency{{"app4", Weak}}, + "libs/lib1": []Dependency{{"libs/lib1", Weak}, {"libs/lib3", Weak}}, + "libs/lib2": []Dependency{{"libs/lib2", Weak}, {"libs/lib3", Weak}}, + "libs/lib3": []Dependency{{"libs/lib3", Weak}}, + "stack1": []Dependency{{"stack1", Weak}, {"app1", Strong}, {"app2", Strong}, {"app3", Strong}}, }, false, }, diff --git a/test/fixtures/manifests-test/stack1/Dependencies b/test/fixtures/manifests-test/stack1/Dependencies index b6e9ff0..d0c1b6d 100644 --- a/test/fixtures/manifests-test/stack1/Dependencies +++ b/test/fixtures/manifests-test/stack1/Dependencies @@ -1,6 +1,6 @@ # frontend -app1 +!app1 # backend -app2 -app3 \ No newline at end of file +!app2 +!app3