file-based apps: define deterministic conflict handling for non-duplicate #: directives in multi-file composition
- Dominant language
- C#
- Stars
- 3.2k
- Forks
- 1.3k
- PR merge metrics
- PR metrics pending
Description
### Is your feature request related to a problem? Please describe.
Using `#:include` for multi-file file-based apps exposes a second class of problems beyond duplicate include inputs:
- same `#:package` with different versions
- same `#:property` with different values
- conflicting `#:sdk` values
These are not simple duplicates. They are semantic conflicts.
Today, some of these cases still build/run, often with warnings or behavior that appears to depend on evaluation/import order. That makes reusable multi-file composition fragile and hard to reason about.
---
### Current observed behavior
In local experiments with multi-file file-based apps:
1. `#:package` same package, different versions
Example: `Newtonsoft.Json@13.0.3` vs `@12.0.3`
Typical result: `NU1504`, but build/run may still succeed.
2. `#:property` same property, different values
Example: `PublishAot=false` vs `PublishAot=true`, or conflicting `DefineConstants`
Typical result: often no hard error, and the effective result may appear overwrite-like.
3. `#:sdk` conflicting values
Example: `Microsoft.NET.Sdk.Web` vs `Microsoft.NET.Sdk`
Typical result: `MSB4011`, but some builds/runs may still succeed.
This leaves users in an awkward state: the system is permissive enough that some conflicting cases still work, but not deterministic enough to rely on that behavior.
---
### Why this matters
`#:include` makes file-based apps meaningfully multi-file, but multi-file composition needs predictable directive semantics.
Without deterministic conflict handling, users are pushed toward:
- manual pre-processing,
- scenario-specific entry generation,
- or ejecting to a full `.csproj` earlier than they would like.
That weakens the lightweight composition story of file-based apps.
---
### Describe the solution you'd like
A good next step would be to define a deterministic conflict handling model for **non-duplicate directives** in multi-file composition.
In particular:
1. **Conflict classification**
- clearly define which non-duplicate conflicts are:
- safe to merge
- warning-level but deterministic
- hard errors / fail-fast
2. **Entry-file-directed behavior**
- the entry file should remain the primary place for concrete global directive intent
- included files can still be reusable, but unsafe conflicts should not silently resolve by incidental processing order
3. **Explicit merge rules where supported**
- if some directive kinds or property kinds are intended to support merging, document those cases explicitly
- otherwise prefer fail-fast over accidental precedence
4. **Build-time conditional directive participation**
- conditional participation of directives would help avoid forcing mutually incompatible directives into the same effective closure
5. **Clear escalation path**
- lightweight same-closure reuse stays with `#:include`
- when a real boundary is needed, a stronger boundary model such as `#:ref` / separate project reference becomes the appropriate direction
---
### What I am not asking for
I am **not** asking for:
- runtime conditional inclusion inside normal C# control flow
- a separate full project system on top of file-based apps
- a complex per-module compatibility declaration language
In fact, requiring every included file to fully declare package/property compatibility would create too much authoring burden.
A better direction seems to be:
- keep the model entry-file-first
- define deterministic conflict handling
- add conditional directive participation where appropriate
- use stronger boundaries when unification is not appropriate
---
### Temporary mitigations today
In current preview SDK usage, the practical mitigations seem to be:
- keep global / concrete directives concentrated in the entry file
- keep included files as lightweight and non-global as possible
- treat current “build succeeds anyway” conflict behavior as non-contractual
- use `NoWarn=CS2002` only as a temporary mitigation for duplicate include warnings
- use separate project / process boundaries when a single include closure is not a good fit
Also, although the current spec already describes `#:ref` as a stronger-boundary direction, in my local `11.0.100-preview.3` SDK it is not yet recognized by the parser, so it is not currently available as a practical workaround in this environment.
---
### Related issues / spec context
- Duplicate include deduplication: [#54080](https://github.com/dotnet/sdk/issues/54080)
- Directive extensions / conditional directives discussion: [#52532](https://github.com/dotnet/sdk/issues/52532)
- Current spec: [`dotnet-run-file.md` ](https://github.com/dotnet/sdk/blob/main/documentation/general/dotnet-run-file.md)
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.