Handle companion imports without a filesystem base

This commit is contained in:
2026-11-09 20:38:55 +00:00
parent e47960d377
commit b93cbb0639
5 changed files with 187 additions and 1 deletions

View File

@@ -21,6 +21,7 @@ type tierAcquisitionState struct {
type inputAcquisitionRequest struct {
input loadInput
resolved *resolvedFilesystemContext
withoutBase bool
role sourceVersionRole
directiveContext rootDirectiveContext
protectionRule *VersionRule
@@ -34,6 +35,9 @@ type importAcquisitionRequest struct {
}
func acquireInput(request inputAcquisitionRequest, policy acquisitionPolicy, state *acquisitionState, tier *tierAcquisitionState) (assembledInput, []Diagnostic) {
if request.withoutBase && request.input.mainRelative.RequireBase {
return assembledInput{}, []Diagnostic{optionDiagnostic(CodeOptionsInvalid, "main-relative input requires a filesystem base")}
}
source := request.input.source
destination := request.input.destination
accounting := state.accounting
@@ -102,7 +106,12 @@ func acquireInput(request inputAcquisitionRequest, policy acquisitionPolicy, sta
return input, diagnostics
}
request.input.source = selectedSource
if request.input.imports && selectedSource.kind != sourceNone {
if request.input.imports && selectedSource.kind != sourceNone && request.withoutBase {
input, diagnostics = acquireContextlessInput(input, request, versionProcessing, policy)
if len(diagnostics) != 0 {
return input, diagnostics
}
} else if request.input.imports && selectedSource.kind != sourceNone {
importRequest := importAcquisitionRequest{input: request, main: parsed, processing: versionProcessing}
input.composition, input.sources, input.resolvedFilesystem, diagnostics = acquireImportedInput(importRequest, &policy, state, tier)
if len(diagnostics) != 0 {

View File

@@ -0,0 +1,86 @@
package engine
import "go.maximumdirect.net/configkit/internal/document"
func acquireContextlessInput(input assembledInput, request inputAcquisitionRequest, processing *sourceVersionProcessingResult, policy acquisitionPolicy) (assembledInput, []Diagnostic) {
parsed := input.parsed
var body *document.Node
var declarations []importDeclarationSpec
var sequences []sequenceDeclarationSpec
var diagnostics []Diagnostic
if processing != nil {
body, declarations = processing.payload, processing.imports
sequences = cloneSequenceDeclarations(processing.sequences)
} else {
body, declarations, diagnostics = splitImportDirectives(parsed.document)
}
destination := request.input.destination
localDestination := destination
if request.input.placementMode == PlacementModeSequenceItem {
localDestination = Path{}
}
if len(diagnostics) == 0 {
var policies []resolvedImportPolicy
policies, diagnostics = prepareImportDeclarations(declarations, policy.importDefaults, localDestination)
if len(diagnostics) == 0 {
for i, spec := range declarations {
if !policies[i].required || spec.selector == importSelectorPath && len(spec.paths) == 0 {
continue
}
site := spec.fieldSites["path"]
if spec.selector == importSelectorPath && len(spec.selectorSites) != 0 {
site = spec.selectorSites[0]
}
if spec.selector == importSelectorDirectory {
site = spec.fieldSites["dir"]
}
if spec.selector == importSelectorGlob {
site = spec.fieldSites["glob"]
}
diagnostics = []Diagnostic{{Code: CodeImportMissing, Severity: SeverityError, Path: site.path.clone(), Message: "import cannot be selected because no filesystem base is available", Primary: cloneLocation(&site.location)}}
break
}
}
}
if len(diagnostics) != 0 {
if request.input.placementMode == PlacementModeSequenceItem {
diagnostics = prependCompositionDiagnosticPaths(diagnostics, destination.Index(0))
}
return input, diagnostics
}
key := importKeyForMainSource(parsed.source)
info := cloneSourceInfo(*parsed.source)
accounting := input.accounting
accounting.sources++
accounting.bytes += parsed.bytes
accounting.nodes += countInputNodes(parsed.document)
limits := policy.importLimits
if accounting.sources > limits.sources || accounting.bytes > limits.bytes || accounting.nodes > limits.nodes {
return input, []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate input limit", nil)}
}
contribution := acquiredContribution{source: info, payload: body, identity: key, role: importRoleMain, scopeOwner: key, processing: processing, sequenceDeclarations: sequences}
var scope acquisitionScope
if request.input.placementMode == PlacementModeSequenceItem {
contribution.document = body
local := acquisitionScope{owner: key, contributions: []acquiredContribution{contribution}}
scope = acquisitionScope{owner: key, destination: destination.clone(), contributions: []acquiredContribution{{source: info, payload: body, identity: key, role: importRoleMain, scopeOwner: key, destination: destination.clone(), processing: processing, sequenceDeclarations: sequences, pendingItem: &local}}}
if diagnostic := reservePendingItemPlacements(&scope, limits, accounting); diagnostic != nil {
return input, []Diagnostic{*diagnostic}
}
} else {
_, placed, err := placeDocument(body, destination, body.Location, maxDocumentDepth, maxDocumentNodes)
if err != nil {
return input, []Diagnostic{sourceDiagnostic(CodeSourceLimitExceeded, info.ID, "configuration exceeds the source depth or node limit after destination placement")}
}
accounting.nodes += destinationWrapperNodeCount(destination)
if accounting.nodes > limits.nodes {
return input, []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate node limit", nil)}
}
contribution.document, contribution.destination = placed, destination.clone()
scope = acquisitionScope{owner: key, destination: destination.clone(), contributions: []acquiredContribution{contribution}}
}
input.document = body
input.composition = &composedDocument{acquisition: []acquisitionScope{scope}, importGraph: newImportInclusionGraph(), inputBytes: accounting.bytes, inputNodes: accounting.nodes}
input.acquisition = cloneAcquisitionScopes(input.composition.acquisition)
return input, nil
}

View File

@@ -292,6 +292,7 @@ func acquireUnderlayGroupScopes(group loadGroup, index int, rule *VersionRule, s
acquired, diagnostics := acquireInput(inputAcquisitionRequest{
input: contribution, role: sourceVersionContribution,
resolved: selected[inputIndex],
withoutBase: contribution.mainRelative != nil && contribution.filesystem == nil && selected[inputIndex] == nil,
directiveContext: rootDirectiveContextLayered, protectionRule: rule,
}, policy, state, tier)
for _, source := range acquired.sources {