backend/Directory.Build.props
Rama main · réplica de lectura del repositorio original.
<Project>
<!-- The build contract for the .NET stack, and for nothing else. MSBuild walks up from a project directory until it
finds a Directory.Build.props, so a copy at the repository root would govern every project in the repository
rather than this stack's; sitting here, it governs backend/src, backend/tests, and backend/tools and reaches
nothing beside them. The version is the one property every stack has to agree on, so it is imported from the
root rather than declared here — see Version.props. -->
<Import Project="$(MSBuildThisFileDirectory)../Version.props" />
<!-- The project's own licensing and attribution, declared once so no project restates or contradicts it. The root
LICENSE carries the unmodified AGPL-3.0 text GitHub's license detection matches, the root NOTICE carries the
original-author attribution AGPL-3.0 section 7(b) permits a distribution to require be preserved, and the values
below are the machine-readable form of both: PackageLicenseExpression is the SPDX slot MSBuild publishes, and
the assembly metadata puts the same identifier into what actually ships, since nothing here is packed. The
identifier is the -only arm rather than -or-later, so nothing is granted under a licence version not yet
written. -->
<PropertyGroup>
<Product>MailFathom</Product>
<Authors>Krzysztof Kasprowicz</Authors>
<Company>Krzysztof Kasprowicz</Company>
<Copyright>Copyright © 2026 Krzysztof Kasprowicz</Copyright>
<PackageLicenseExpression>AGPL-3.0-only</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/fathomise/MailFathom</PackageProjectUrl>
<RepositoryUrl>https://github.com/fathomise/MailFathom</RepositoryUrl>
<RepositoryType>git</RepositoryType>
</PropertyGroup>
<ItemGroup>
<AssemblyMetadata Include="SPDX-License-Identifier" Value="AGPL-3.0-only" />
</ItemGroup>
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<AssemblyName>MailFathom.$(MSBuildProjectName)</AssemblyName>
<RootNamespace>MailFathom.$(MSBuildProjectName)</RootNamespace>
<RunAnalyzersDuringBuild>true</RunAnalyzersDuringBuild>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>preview</LangVersion>
<Deterministic>true</Deterministic>
<ContinuousIntegrationBuild Condition="'$(CI)' == 'true'">true</ContinuousIntegrationBuild>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<AnalysisLevel>latest</AnalysisLevel>
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<IsPackable>false</IsPackable>
<NoWarn>$(NoWarn);IDE0290;IDE0079</NoWarn>
</PropertyGroup>
<!-- scripts/verify-fast.sh builds with SkipPipelineOnlyAnalyzers=true and leaves two rules to the full gate and CI,
which never set it: CA2000, two fifths of every analyzer second the solution spends, and IDE0055, whose
whitespace the loop repairs before it builds. Appending the
rules to NoWarn would leave their analyzers running, because .editorconfig names their severity for every C#
file; the analyzer configuration file beside this one sits nearer the sources, outranks .editorconfig, and stops
the analyzers altogether. -->
<ItemGroup Condition="'$(SkipPipelineOnlyAnalyzers)' == 'true'">
<EditorConfigFiles Include="$(MSBuildThisFileDirectory)without-pipeline-only-analyzers.editorconfig" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
<PackageReference Include="Roslynator.Analyzers" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
<PackageReference Include="Roslynator.CodeAnalysis.Analyzers" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
<PackageReference Include="Roslynator.Formatting.Analyzers" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Publish'">
<Optimize>true</Optimize>
</PropertyGroup>
<!-- Directory.Packages.props pins the direct versions; it says nothing about the transitive closure those versions
pull in, which is the larger half of what actually gets restored. Each project's packages.lock.json records that
closure with the resolved version and a content hash per package, so a republished package cannot change what a
pinned version means, and a dependency bump shows every transitive move in the pull request diff. The lock files
are committed. Restore rewrites them whenever a version changes, and the verification scripts and continuous
integration restore in locked mode, which fails instead of rewriting: regenerate deliberately with a
force-evaluate restore as part of the change that moves a pin, as `docs/operations/local-development.md`
describes.
The two projects carrying the Aspire orchestration graph are excluded. Aspire.AppHost.Sdk adds
Aspire.Dashboard.Sdk.<rid> and Aspire.Hosting.Orchestration.<rid> as references chosen from
NETCoreSdkRuntimeIdentifier, so that part of the graph is a property of the machine running restore rather than
of this repository: a lock file recorded on one platform names packages no other platform asks for, and locked
mode there fails with NU1004 before a build can start. IntegrationTests has to follow AppHost because it
references the project and inherits those packages transitively, and a lock file cannot exclude a subtree.
Nothing is lost that matters: both ship nowhere, and their versions stay pinned centrally like every other
project's.
Cli is not among them, and keeping it out of the exclusion is what the trim analyzer had to give way for. Those
analyzers put Microsoft.NET.ILLink.Tasks in the ordinary restore graph, and the SDK ships that package in its own
library-packs directory, so a lock file recorded here carries the SDK copy's content hash and a restore that
fetches the feed copy fails with NU1403. The analyzers run at publish instead, where the trimmer runs anyway and
where its warnings are what a release reads. A shipped project keeps its lock file. -->
<PropertyGroup>
<RestorePackagesWithLockFile Condition="'$(MSBuildProjectName)' != 'AppHost' And '$(MSBuildProjectName)' != 'IntegrationTests'">true</RestorePackagesWithLockFile>
</PropertyGroup>
<!-- This file sits one directory below the repository root, so it locates the root by walking up rather than by
searching for a marker file above it. The definition before that looked for .gitignore, which the container
build context deliberately does not carry: the property then resolved to nothing and the LICENSE and NOTICE
items that read it silently matched no file. -->
<PropertyGroup>
<RepositoryRoot>$([MSBuild]::NormalizeDirectory('$(MSBuildThisFileDirectory)..'))</RepositoryRoot>
</PropertyGroup>
<ItemGroup>
<AssemblyAttribute Include="System.CLSCompliantAttribute">
<_Parameter1>false</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="$(RepositoryRoot).config/BannedSymbols.txt" />
</ItemGroup>
<PropertyGroup>
<IsUnitTestProject Condition="$(MSBuildProjectName.EndsWith('.UnitTests'))">true</IsUnitTestProject>
<IsIntegrationTestProject Condition="'$(MSBuildProjectName)' == 'IntegrationTests'">true</IsIntegrationTestProject>
<IsEvaluationProject Condition="'$(MSBuildProjectName)' == 'Evaluations'">true</IsEvaluationProject>
</PropertyGroup>
<ItemGroup Condition="'$(IsUnitTestProject)' != 'true' And '$(IsIntegrationTestProject)' != 'true' And '$(IsEvaluationProject)' != 'true' And '$(MSBuildProjectName)' != 'Benchmarks'">
<AdditionalFiles Include="$(RepositoryRoot).config/BannedSymbols.Production.txt" />
</ItemGroup>
<ItemGroup Condition="'$(IsUnitTestProject)' == 'true' Or '$(IsIntegrationTestProject)' == 'true' Or '$(IsEvaluationProject)' == 'true'">
<PackageReference Include="xunit.analyzers" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
</ItemGroup>
<PropertyGroup Condition="'$(IsUnitTestProject)' == 'true' Or '$(IsIntegrationTestProject)' == 'true' Or '$(IsEvaluationProject)' == 'true'">
<IsPackable>false</IsPackable>
<OutputType>Exe</OutputType>
<UseMicrosoftTestingPlatformRunner>true</UseMicrosoftTestingPlatformRunner>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<!-- Test names use underscores to encode the member, scenario, and expected behavior. -->
<NoWarn>$(NoWarn);CS1591;CA1707</NoWarn>
</PropertyGroup>
<ItemGroup Condition="'$(IsUnitTestProject)' == 'true'">
<PackageReference Include="coverlet.MTP" PrivateAssets="all" />
<!-- Microsoft Testing Platform reads the collector configuration from beside the test executable, so the link name
rather than the source path is what has to stay `testconfig.json`. -->
<None Include="$(RepositoryRoot).config/testconfig.json" Link="testconfig.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<PropertyGroup Condition="'$(IsUnitTestProject)' == 'true'">
<IsTestProject>true</IsTestProject>
<!-- Empty scaffold test projects are allowed until the first behavior slice adds real unit tests. -->
<TestingPlatformCommandLineArguments>$(TestingPlatformCommandLineArguments) --ignore-exit-code 8</TestingPlatformCommandLineArguments>
</PropertyGroup>
<ItemGroup Condition="'$(IsIntegrationTestProject)' == 'true'">
<!-- The same collector as the unit suites, pointed at the opposite scope by the project's own testconfig.json. Its
report is informational and separate: it never merges into the aggregate the 85% gate reads, and it enforces no
threshold of its own. -->
<PackageReference Include="coverlet.MTP" PrivateAssets="all" />
</ItemGroup>
<PropertyGroup Condition="'$(IsIntegrationTestProject)' == 'true'">
<!-- A solution-wide `dotnet test` discovers its test projects by this property, which xunit.v3 otherwise sets, so
opting out is what keeps a suite that needs Docker and a PostgreSQL container out of the fast loop and out of
the coverage gate. Being discovered would also feed this suite's coverage into the aggregate that gate
enforces, which is exactly the mixing the two scopes exist to prevent. The project stays in the solution
regardless, so it is built, analyzed, and formatted by exactly the same gates as everything else;
scripts/run-integration-tests.sh is what runs it. -->
<IsTestingPlatformApplication>false</IsTestingPlatformApplication>
</PropertyGroup>
<PropertyGroup Condition="'$(IsEvaluationProject)' == 'true'">
<!-- Out of discovery for the reason the integration suite is, and a stronger one: a scenario there calls a paid
provider, so neither gate may start it even by accident. It takes no collector either, because what it measures
is what a model answered rather than which lines ran. scripts/run-ai-evaluations.sh is what runs it. -->
<IsTestingPlatformApplication>false</IsTestingPlatformApplication>
</PropertyGroup>
</Project>