Schedule ordinary import contributions by dependency
This commit is contained in:
@@ -148,6 +148,42 @@ func (graph importInclusionGraph) scheduleCycle(closingEdge int, location Locati
|
||||
}, true
|
||||
}
|
||||
|
||||
func scheduleOrdinaryContributions(scope acquisitionScope, graph importInclusionGraph) acquisitionScope {
|
||||
positions := make(map[importNodeKey]int, len(scope.contributions))
|
||||
slots := make([]int, 0, len(scope.contributions))
|
||||
keys := make([]dependencyKey, 0, len(scope.contributions))
|
||||
for index, contribution := range scope.contributions {
|
||||
if contribution.scopeOwner != scope.owner || (contribution.role != importRoleMain && contribution.role != importRoleMapping) {
|
||||
continue
|
||||
}
|
||||
positions[contribution.identity] = len(keys)
|
||||
slots = append(slots, index)
|
||||
keys = append(keys, contribution.identity.dependencyKey())
|
||||
}
|
||||
if len(keys) < 2 {
|
||||
return scope
|
||||
}
|
||||
|
||||
edges := make([]dependencyEdge, 0, len(graph.edges))
|
||||
for _, edge := range graph.edges {
|
||||
importer, importerOK := positions[edge.importer]
|
||||
imported, importedOK := positions[edge.imported]
|
||||
if importerOK && importedOK {
|
||||
edges = append(edges, dependencyEdge{prerequisite: imported, dependent: importer})
|
||||
}
|
||||
}
|
||||
order, _ := scheduleDependencies(keys, edges)
|
||||
if len(order) != len(keys) {
|
||||
return scope
|
||||
}
|
||||
scheduled := scope
|
||||
scheduled.contributions = append([]acquiredContribution(nil), scope.contributions...)
|
||||
for position, scheduledIndex := range order {
|
||||
scheduled.contributions[slots[position]] = scope.contributions[slots[scheduledIndex]]
|
||||
}
|
||||
return scheduled
|
||||
}
|
||||
|
||||
func importKeyFor(access *filesystemAccess, canonical string, synthetic bool) importNodeKey {
|
||||
kind := "host"
|
||||
resourceKind := "file"
|
||||
|
||||
@@ -220,6 +220,9 @@ func loadImportedWithAccessTrackedAndProcessingPolicy(access *filesystemAccess,
|
||||
}
|
||||
}
|
||||
if len(diagnostics) == 0 {
|
||||
if !mainIsItem {
|
||||
state.scope = scheduleOrdinaryContributions(state.scope, state.importGraph)
|
||||
}
|
||||
diagnostics = state.composePendingItems(&state.scope)
|
||||
}
|
||||
|
||||
@@ -256,7 +259,7 @@ func loadImportedWithAccessTrackedAndProcessingPolicy(access *filesystemAccess,
|
||||
protectionRule = &scoped
|
||||
}
|
||||
}
|
||||
if diagnostic := importedVersionClaim(documents, completed, protectionRule, 0); diagnostic != nil {
|
||||
if diagnostic := importedVersionClaim(documents, localScope, completed, protectionRule); diagnostic != nil {
|
||||
if rule != nil {
|
||||
diagnostic.Path = rule.Path.clone()
|
||||
}
|
||||
@@ -297,7 +300,7 @@ func loadImportedWithAccessTrackedAndProcessingPolicy(access *filesystemAccess,
|
||||
}
|
||||
composed, diagnostics := composeDocumentsWithPolicy(policy, documents...)
|
||||
if composed != nil {
|
||||
if diagnostic := importedVersionClaim(documents, composed, rule, 0); diagnostic != nil {
|
||||
if diagnostic := importedVersionClaim(documents, state.scope, composed, rule); diagnostic != nil {
|
||||
return nil, cloneSourceInfos(state.sources), []Diagnostic{*diagnostic}
|
||||
}
|
||||
}
|
||||
@@ -618,10 +621,17 @@ func (s *importTraversal) composePendingItems(scope *acquisitionScope) []Diagnos
|
||||
return nil
|
||||
}
|
||||
|
||||
func importedVersionClaim(documents []*document.Node, composed *composedDocument, rule *VersionRule, mainIndex int) *Diagnostic {
|
||||
func importedVersionClaim(documents []*document.Node, scope acquisitionScope, composed *composedDocument, rule *VersionRule) *Diagnostic {
|
||||
if composed == nil || rule == nil {
|
||||
return nil
|
||||
}
|
||||
mainIndex := -1
|
||||
for index, contribution := range scope.contributions {
|
||||
if contribution.role == importRoleMain {
|
||||
mainIndex = index
|
||||
break
|
||||
}
|
||||
}
|
||||
ranges := indexSequenceRanges(composed.sequenceRanges)
|
||||
for index, contribution := range documents {
|
||||
if index == mainIndex {
|
||||
|
||||
@@ -91,6 +91,80 @@ settings:
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadOrdinaryImportsUseDependencyReadyCompositionOrder(t *testing.T) {
|
||||
type task struct {
|
||||
ID string `yaml:"id"`
|
||||
}
|
||||
type config struct {
|
||||
Tasks []task `yaml:"tasks"`
|
||||
}
|
||||
directory := t.TempDir()
|
||||
mainPath := writeImportFile(t, directory, "main.yml", `configkit:
|
||||
imports:
|
||||
- path: z-parent.yml
|
||||
- path: a-independent.yml
|
||||
tasks: [{id: main}]
|
||||
`)
|
||||
zParentPath := writeImportFile(t, directory, "z-parent.yml", `configkit:
|
||||
imports:
|
||||
- path: b-dependency.yml
|
||||
tasks: [{id: z-parent}]
|
||||
`)
|
||||
aIndependentPath := writeImportFile(t, directory, "a-independent.yml", "tasks: [{id: a-independent}]\n")
|
||||
bDependencyPath := writeImportFile(t, directory, "b-dependency.yml", "tasks: [{id: b-dependency}]\n")
|
||||
|
||||
input, diagnostics := assembleBaseInput[config](File(mainPath), Options[config]{
|
||||
Imports: true,
|
||||
sequencePolicy: sequencePolicy{identityFields: map[string]string{"/tasks": "id"}},
|
||||
})
|
||||
if len(diagnostics) != 0 {
|
||||
t.Fatalf("assemble imports: %#v", diagnostics)
|
||||
}
|
||||
tasks, ok := documentAtPath(input.document, Keys("tasks"))
|
||||
if !ok || tasks.Kind != document.Sequence {
|
||||
t.Fatalf("tasks document = %#v, %v", tasks, ok)
|
||||
}
|
||||
wantOrder := []string{"a-independent", "b-dependency", "z-parent", "main"}
|
||||
if len(tasks.Sequence) != len(wantOrder) {
|
||||
t.Fatalf("tasks = %#v", tasks.Sequence)
|
||||
}
|
||||
for index, want := range wantOrder {
|
||||
id, found := documentAtPath(tasks, Path{}.Index(index).Key("id"))
|
||||
if !found || id.Text != want {
|
||||
t.Errorf("task %d ID = %#v, want %q", index, id, want)
|
||||
}
|
||||
}
|
||||
wantAcquisition := []string{
|
||||
canonicalTestPath(t, mainPath), canonicalTestPath(t, zParentPath),
|
||||
canonicalTestPath(t, bDependencyPath), canonicalTestPath(t, aIndependentPath),
|
||||
}
|
||||
for index, want := range wantAcquisition {
|
||||
if got := input.sources[index].Path; got != want {
|
||||
t.Errorf("acquired source %d = %q, want %q", index, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAcquiredContextsUseSelectedFileWhenSourceRecordsAreReordered(t *testing.T) {
|
||||
directory := t.TempDir()
|
||||
mainPath := writeImportFile(t, directory, "main.yml", "settings: {root: main}\n")
|
||||
importedPath := writeImportFile(t, directory, "nested/imported.yml", "settings: {nested: imported}\n")
|
||||
sources := []SourceInfo{
|
||||
{ID: "source-2", Path: canonicalTestPath(t, importedPath), Kind: SourceKindFile},
|
||||
{ID: "source-1", Path: canonicalTestPath(t, mainPath), Kind: SourceKindFile},
|
||||
}
|
||||
contexts, diagnostic := contextsForAcquiredSources(File(mainPath), nil, nil, sources)
|
||||
if diagnostic != nil {
|
||||
t.Fatalf("source contexts: %#v", diagnostic)
|
||||
}
|
||||
if got := contexts["source-1"].BaseDir; got != "." {
|
||||
t.Errorf("main source base = %q, want root-relative current directory", got)
|
||||
}
|
||||
if got := contexts["source-2"].BaseDir; got != "nested" {
|
||||
t.Errorf("imported source base = %q, want nested", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadImportsFromProfilesDirectoryAsBaseInput(t *testing.T) {
|
||||
directory := t.TempDir()
|
||||
mainPath := writeImportFile(t, directory, "main.yml", "configkit: {imports: [{path: profiles/shared.yml}]}\n")
|
||||
|
||||
@@ -161,7 +161,7 @@ func TestRegisteredSequencePolicyAcrossLoadingLifecycle(t *testing.T) {
|
||||
mainPath := writeImportFile(t, directory, "main.yml", "configkit: {imports: [{path: fragment.yml}]}\nitems: [{id: authored}]\n")
|
||||
writeImportFile(t, directory, "fragment.yml", "items: [{id: imported}]\n")
|
||||
result, err := testLoad[config](File(mainPath), Options[config]{Imports: true, Definition: definition})
|
||||
if err != nil || len(result.Config.Items) != 2 || result.Config.Items[1].ID != "imported" {
|
||||
if err != nil || len(result.Config.Items) != 2 || result.Config.Items[0].ID != "imported" || result.Config.Items[1].ID != "authored" {
|
||||
t.Fatalf("registered imported list = %#v, %v", result, err)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -23,9 +23,16 @@ func contextsForAcquiredSources(source Source, context *FilesystemContext, baseS
|
||||
filesystem = context.FS.namespace.filesystem
|
||||
rootPath, basePath = context.Root, context.BaseDir
|
||||
} else if source.kind == sourceFile {
|
||||
filePath := sources[0].Path
|
||||
filePath := source.path
|
||||
if baseSource != nil {
|
||||
filePath = baseSource.Path
|
||||
} else {
|
||||
canonical, err := canonicalExistingPath(filePath)
|
||||
if err != nil {
|
||||
value := importDiagnostic(CodeImportInvalid, "selected source could not be canonicalized", nil)
|
||||
return nil, &value
|
||||
}
|
||||
filePath = canonical
|
||||
}
|
||||
basePath = filepath.Dir(filePath)
|
||||
rootPath = basePath
|
||||
|
||||
Reference in New Issue
Block a user