mirror of
https://github.com/AtomUI/AtomUI.git
synced 2026-10-26 11:10:34 +08:00
510 lines
25 KiB
C#
510 lines
25 KiB
C#
using System.Xml.Linq;
|
|
using Shouldly;
|
|
using Xunit;
|
|
|
|
namespace AtomUI.Generator.Tests.BuildInfrastructure;
|
|
|
|
public sealed class BuildLayoutTests
|
|
{
|
|
private static readonly string[] s_expectedNuGetBuildAssets =
|
|
[
|
|
"AtomUI.Generator.props",
|
|
"AtomUI.Toolchain.Tasks.targets",
|
|
"AtomUI.Generator.targets",
|
|
"AtomUI.Registration.targets",
|
|
"AtomUI.Registration.Framework.targets",
|
|
"AtomUI.Registration.Net8.targets",
|
|
"AtomUI.Registration.Net8.Host.targets",
|
|
"AtomUI.Registration.Net8.Transforms.targets",
|
|
"AtomUI.Registration.Native8.targets",
|
|
"AtomUI.Registration.Bridge.targets",
|
|
"AtomUI.Net8.ILLink.Task.cs",
|
|
"AtomUI.Localization.props",
|
|
"AtomUI.Localization.targets",
|
|
"AtomUI.ThemeAssets.targets",
|
|
"AtomUI.CompiledThemes.targets",
|
|
"AtomUI.Build.Tasks.Process.cs"
|
|
];
|
|
|
|
[Fact]
|
|
public void Directory_Build_Files_Import_Only_Repository_Entry_Points()
|
|
{
|
|
GetImports("Directory.Build.props")
|
|
.ShouldBe(["$(MSBuildThisFileDirectory)build/AtomUI.Repository.props"]);
|
|
GetImports("Directory.Build.targets")
|
|
.ShouldBe(["$(MSBuildThisFileDirectory)build/AtomUI.Repository.targets"]);
|
|
}
|
|
|
|
[Fact]
|
|
public void Repository_Props_Import_Configuration_And_Generator_Entry_Point()
|
|
{
|
|
const string repositoryPropsPath = "build/AtomUI.Repository.props";
|
|
GetImports(repositoryPropsPath).ShouldBe([
|
|
"$(MSBuildThisFileDirectory)Versions.props",
|
|
"$(MSBuildThisFileDirectory)ProjectDefaults.props",
|
|
"$(MSBuildThisFileDirectory)PackageMetadata.props",
|
|
"$(MSBuildThisFileDirectory)OutputPaths.props",
|
|
"$(MSBuildThisFileDirectory)PackageValidation.props",
|
|
"$(MSBuildThisFileDirectory)AtomUI.Generator.props"
|
|
]);
|
|
}
|
|
|
|
[Fact]
|
|
public void Package_Validation_Is_Opt_In_And_Mechanically_Enforced()
|
|
{
|
|
// The props file must be imported only for validation runs. An unconditional import would
|
|
// be evaluated for every project in the ordinary Debug loop, so keep it gated.
|
|
var repositoryProps = XDocument.Load(GetRepoFile("build/AtomUI.Repository.props"));
|
|
var validationImport = repositoryProps.Descendants("Import")
|
|
.Single(element =>
|
|
((string?)element.Attribute("Project") ?? string.Empty)
|
|
.EndsWith("PackageValidation.props", StringComparison.Ordinal));
|
|
var importCondition = (string?)validationImport.Attribute("Condition");
|
|
importCondition.ShouldNotBeNullOrWhiteSpace();
|
|
importCondition.ShouldContain("AtomUIPackageValidationBaselineVersion");
|
|
|
|
var validationProps = XDocument.Load(GetRepoFile("build/PackageValidation.props"));
|
|
|
|
// Opt-in: validation only engages when the release flow supplies the baseline version, so
|
|
// ordinary development builds stay fast and need no baseline packages.
|
|
var enableValidation = validationProps.Descendants("EnablePackageValidation").ShouldHaveSingleItem();
|
|
enableValidation.Value.ShouldBe("true");
|
|
((string?)enableValidation.Parent?.Attribute("Condition"))
|
|
.ShouldBe("'$(AtomUIPackageValidationBaselineVersion)' != ''");
|
|
|
|
var baselineVersion = validationProps.Descendants("PackageValidationBaselineVersion")
|
|
.ShouldHaveSingleItem();
|
|
((string?)baselineVersion.Attribute("Condition"))
|
|
.ShouldBe("'$(PackageValidationBaselineVersion)' == ''");
|
|
baselineVersion.Value.ShouldBe("$(AtomUIPackageValidationBaselineVersion)");
|
|
|
|
// Avalonia's private-API target replaces the facade with real split assemblies only before
|
|
// CoreCompile. Package validation stops at ResolveReferences, so extend the SDK's collected
|
|
// per-TFM reference paths after _GetReferencePathFromInnerProjects has populated them.
|
|
var validationDependsOn = validationProps.Descendants("RunPackageValidationDependsOn")
|
|
.ShouldHaveSingleItem();
|
|
validationDependsOn.Value.ShouldContain("AtomUIAddAvaloniaPackageValidationReferences");
|
|
validationDependsOn.Value.ShouldContain("$(RunPackageValidationDependsOn)");
|
|
|
|
var avaloniaReferencesTarget = validationProps.Descendants("Target")
|
|
.Single(element =>
|
|
(string?)element.Attribute("Name") ==
|
|
"AtomUIAddAvaloniaPackageValidationReferences");
|
|
var avaloniaTargetCondition = ((string?)avaloniaReferencesTarget.Attribute("Condition"))
|
|
.ShouldNotBeNull();
|
|
avaloniaTargetCondition.ShouldContain("AvaloniaAccessUnstablePrivateApis");
|
|
var avaloniaReferences = avaloniaReferencesTarget
|
|
.Descendants("_AtomUIAvaloniaPackageValidationReference")
|
|
.ShouldHaveSingleItem();
|
|
((string?)avaloniaReferences.Attribute("Include"))
|
|
.ShouldBe("$(NuGetPackageRoot)avalonia/$(AvaloniaVersion)/lib/$(_AtomUIAvaloniaPackageValidationFramework)/*.dll");
|
|
|
|
var packageValidationReferences = avaloniaReferencesTarget
|
|
.Descendants("PackageValidationReferencePath")
|
|
.ShouldHaveSingleItem();
|
|
((string?)packageValidationReferences.Attribute("Update"))
|
|
.ShouldBe("@(PackageValidationReferencePath)");
|
|
var referencePath = packageValidationReferences.Element("ReferencePath")
|
|
.ShouldNotBeNull()
|
|
.Value;
|
|
referencePath.ShouldContain("%(PackageValidationReferencePath.ReferencePath)");
|
|
referencePath.ShouldContain("@(_AtomUIAvaloniaPackageValidationReference->'%(FullPath)', ',')");
|
|
avaloniaReferencesTarget.Descendants("Error").ShouldHaveSingleItem();
|
|
|
|
// The release script must accept the baseline and must run both gates. ApiCompat only reads
|
|
// lib/, so verify-package-layout.ps1 is what covers tools/ and buildTransitive/.
|
|
var buildScript = File.ReadAllText(GetRepoFile("scripts/BuildNuGetPackages.ps1"));
|
|
buildScript.ShouldContain("$PackageValidationBaselineVersion");
|
|
buildScript.ShouldContain("AtomUIPackageValidationBaselineVersion=");
|
|
buildScript.ShouldContain("verification/verify-package-layout.ps1");
|
|
|
|
// Prerequisite tool projects are not published packages and must never be validated against
|
|
// a released baseline.
|
|
var prerequisiteBuild = buildScript.IndexOf(
|
|
"foreach ($project in $AtomUIReleaseBuildPrerequisiteProjects)", StringComparison.Ordinal);
|
|
var prerequisiteBlockEnd = buildScript.IndexOf(
|
|
"foreach ($project in $AtomUIReleasePackageProjects)", prerequisiteBuild, StringComparison.Ordinal);
|
|
prerequisiteBuild.ShouldBeGreaterThanOrEqualTo(0);
|
|
prerequisiteBlockEnd.ShouldBeGreaterThan(prerequisiteBuild);
|
|
buildScript[prerequisiteBuild..prerequisiteBlockEnd].ShouldNotContain("$validationArguments");
|
|
|
|
// The release workflow must expose the baseline input and forward it to the script.
|
|
var workflow = File.ReadAllText(GetRepoFile(".github/workflows/release-nuget-packages.yml"));
|
|
workflow.ShouldContain("PackageValidationBaselineVersion:");
|
|
workflow.ShouldContain("-PackageValidationBaselineVersion");
|
|
}
|
|
|
|
[Fact]
|
|
public void Repository_Targets_Reuse_The_Generator_Entry_Point()
|
|
{
|
|
var repositoryTargets = XDocument.Load(GetRepoFile("build/AtomUI.Repository.targets"));
|
|
|
|
GetImports("build/AtomUI.Repository.targets")
|
|
.ShouldBe(["$(MSBuildThisFileDirectory)AtomUI.Generator.targets"]);
|
|
GetTargetNames(repositoryTargets)
|
|
.ShouldContain("AtomUIPrepareGeneratorConsumerPackageAssets");
|
|
}
|
|
|
|
[Fact]
|
|
public void Generator_Build_Assets_Have_One_Explicit_Manifest()
|
|
{
|
|
var repositoryProps = XDocument.Load(GetRepoFile("build/AtomUI.Repository.props"));
|
|
var buildAssets = repositoryProps.Descendants("AtomUINuGetBuildAsset").ShouldHaveSingleItem();
|
|
var includes = ((string?)buildAssets.Attribute("Include"))
|
|
.ShouldNotBeNull()
|
|
.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
|
.Select(include => include.Replace("$(MSBuildThisFileDirectory)", string.Empty,
|
|
StringComparison.Ordinal))
|
|
.ToArray();
|
|
includes.ShouldBe(s_expectedNuGetBuildAssets);
|
|
buildAssets.Attribute("Exclude").ShouldBeNull();
|
|
buildAssets.Elements("PackagePath")
|
|
.ShouldHaveSingleItem()
|
|
.Value.ShouldBe("buildTransitive/%(Filename)%(Extension)");
|
|
|
|
var toolAssets = repositoryProps.Descendants("AtomUIGeneratorToolAsset").Single(element => ((string?)element.Attribute("Include"))?.Contains("AtomUI.Build.Tasks.dll", StringComparison.Ordinal) == true);
|
|
var toolIncludes = ((string?)toolAssets.Attribute("Include")).ShouldNotBeNull();
|
|
toolIncludes.ShouldContain("AtomUI.Build.Tasks.runtimeconfig.json");
|
|
toolIncludes.ShouldContain("AtomUI.Build.Tasks.dll");
|
|
toolAssets.Elements("PackagePath")
|
|
.ShouldHaveSingleItem()
|
|
.Value.ShouldBe("tools/net10.0/%(Filename)%(Extension)");
|
|
|
|
var generatorProject = XDocument.Load(GetRepoFile("src/AtomUI.Generator/AtomUI.Generator.csproj"));
|
|
generatorProject.Descendants("ProjectReference")
|
|
.ShouldNotContain(element =>
|
|
((string?)element.Attribute("Include") ?? string.Empty)
|
|
.Contains("AtomUI.Generator.LinkedPublish", StringComparison.Ordinal));
|
|
generatorProject.Descendants("None")
|
|
.ShouldContain(element =>
|
|
(string?)element.Attribute("Include") == "@(AtomUINuGetBuildAsset)");
|
|
generatorProject.Descendants("None")
|
|
.ShouldContain(element =>
|
|
(string?)element.Attribute("Include") == "@(AtomUIGeneratorToolAsset)");
|
|
|
|
var repositoryTargets = XDocument.Load(GetRepoFile("build/AtomUI.Repository.targets"));
|
|
repositoryTargets.Descendants("None")
|
|
.ShouldContain(element =>
|
|
(string?)element.Attribute("Include") == "@(AtomUINuGetBuildAsset)");
|
|
repositoryTargets.Descendants("None")
|
|
.ShouldContain(element =>
|
|
(string?)element.Attribute("Include") == "@(AtomUIGeneratorToolAsset)");
|
|
|
|
// Normal generator builds depend only on the worker. Packaging explicitly asks the
|
|
// single toolchain owner to restore/build all engine profiles before collecting files.
|
|
var reference = generatorProject.Descendants("ProjectReference").ShouldHaveSingleItem();
|
|
((string?)reference.Attribute("Include")).ShouldBe("../AtomUI.Toolchain/AtomUI.Toolchain.csproj");
|
|
((string?)reference.Attribute("ReferenceOutputAssembly")).ShouldBe("false");
|
|
((string?)reference.Attribute("PrivateAssets")).ShouldBe("all");
|
|
generatorProject.Descendants("MSBuild").ShouldBeEmpty();
|
|
var prepare = repositoryTargets.Descendants("Target")
|
|
.Single(element => (string?)element.Attribute("Name") == "AtomUIPrepareRepositoryPackageTools")
|
|
.Descendants("MSBuild").ShouldHaveSingleItem();
|
|
((string?)prepare.Attribute("Projects")).ShouldBe("$(AtomUIToolchainProject)");
|
|
((string?)prepare.Attribute("Targets")).ShouldBe("BuildToolchain");
|
|
}
|
|
|
|
[Fact]
|
|
public void NuGet_Features_Use_The_Shared_Build_Tasks_Assembly()
|
|
{
|
|
var repositoryProps = XDocument.Load(GetRepoFile("build/AtomUI.Repository.props"));
|
|
repositoryProps.Descendants("AtomUIBuildTasksAssembly")
|
|
.ShouldHaveSingleItem()
|
|
.Value.ShouldContain("AtomUI.Build.Tasks.dll");
|
|
|
|
var generatorProps = XDocument.Load(GetRepoFile("build/AtomUI.Generator.props"));
|
|
var fallback = generatorProps.Descendants("AtomUIBuildTasksAssembly").ShouldHaveSingleItem();
|
|
((string?)fallback.Attribute("Condition"))
|
|
.ShouldBe("'$(AtomUIBuildTasksAssembly)' == ''");
|
|
fallback.Value.Trim().ShouldBe(
|
|
"$(MSBuildThisFileDirectory)../tools/net10.0/AtomUI.Build.Tasks.dll");
|
|
|
|
var buildRoot = Path.Combine(GetRepositoryRoot(), "build");
|
|
var featureFiles = s_expectedNuGetBuildAssets
|
|
.Where(file => Path.GetExtension(file) == ".targets")
|
|
.Select(file => Path.Combine(buildRoot, file));
|
|
var usingTasks = featureFiles.SelectMany(file => XDocument.Load(file).Descendants("UsingTask"))
|
|
.ToArray();
|
|
usingTasks.ShouldNotBeEmpty();
|
|
usingTasks.ShouldAllBe(element =>
|
|
(string?)element.Attribute("AssemblyFile") == "$(MSBuildToolsPath)/Microsoft.Build.Tasks.Core.dll");
|
|
|
|
File.Exists(Path.Combine(buildRoot, "BuildTasks.props")).ShouldBeFalse();
|
|
var buildText = string.Join('\n', Directory.EnumerateFiles(buildRoot).Select(File.ReadAllText));
|
|
buildText.ShouldNotContain("AtomUILocalizationBuildTasksAssembly");
|
|
buildText.ShouldNotContain("AtomUILinkedRegistrationBuildTasksAssembly");
|
|
}
|
|
|
|
[Fact]
|
|
public void NuGet_Build_Tasks_Use_The_SDK_Source_Adapter()
|
|
{
|
|
var buildRoot = Path.Combine(GetRepositoryRoot(), "build");
|
|
var usingTasks = s_expectedNuGetBuildAssets
|
|
.Where(file => Path.GetExtension(file) == ".targets")
|
|
.Select(file => Path.Combine(buildRoot, file))
|
|
.SelectMany(file => XDocument.Load(file)
|
|
.Descendants()
|
|
.Where(element => element.Name.LocalName == "UsingTask"))
|
|
.Where(element =>
|
|
(string?)element.Attribute("AssemblyFile") == "$(MSBuildToolsPath)/Microsoft.Build.Tasks.Core.dll")
|
|
.ToArray();
|
|
|
|
usingTasks.ShouldNotBeEmpty();
|
|
usingTasks.ShouldAllBe(element =>
|
|
element.Attribute("Runtime") == null);
|
|
usingTasks.ShouldAllBe(element =>
|
|
(string?)element.Attribute("TaskFactory") == "RoslynCodeTaskFactory");
|
|
}
|
|
|
|
[Fact]
|
|
public void Repository_Test_Project_Flag_Has_A_Canonical_Boolean_Value()
|
|
{
|
|
var defaults = XDocument.Load(GetRepoFile("build/ProjectDefaults.props"));
|
|
var isTestProject = defaults.Descendants("IsTestProject").ShouldHaveSingleItem();
|
|
|
|
isTestProject.Value.ShouldBe("true");
|
|
}
|
|
|
|
[Fact]
|
|
public void Repository_Output_Paths_Centralize_Binaries_And_Intermediate_Files()
|
|
{
|
|
var outputPaths = XDocument.Load(GetRepoFile("build/OutputPaths.props"));
|
|
|
|
outputPaths.Descendants("PackageOutputPath")
|
|
.ShouldHaveSingleItem()
|
|
.Value.ShouldBe("$(MSBuildThisFileDirectory)../.artifacts/Nuget/$(Configuration)");
|
|
outputPaths.Descendants("OutputPathWithoutFramework")
|
|
.ShouldHaveSingleItem()
|
|
.Value.ShouldBe("$(MSBuildThisFileDirectory)../.artifacts/bin/$(Configuration)");
|
|
outputPaths.Descendants("OutputPath")
|
|
.ShouldHaveSingleItem()
|
|
.Value.ShouldBe("$(OutputPathWithoutFramework)");
|
|
outputPaths.Descendants("BaseIntermediateOutputPath")
|
|
.Where(element => element.Attribute("Condition") is null)
|
|
.ShouldHaveSingleItem()
|
|
.Value.ShouldBe(
|
|
"$(MSBuildThisFileDirectory)../.artifacts/$(MSBuildProjectName)/obj");
|
|
}
|
|
|
|
[Fact]
|
|
public void Repository_Test_Runs_Clean_VSTest_Temporary_Results()
|
|
{
|
|
var outputPaths = XDocument.Load(GetRepoFile("build/OutputPaths.props"));
|
|
outputPaths.Descendants("AtomUITestResultsRoot")
|
|
.ShouldHaveSingleItem()
|
|
.Value.ShouldBe("$(MSBuildThisFileDirectory)../.artifacts/TestResults");
|
|
outputPaths.Descendants("AtomUITestResultsDirectory")
|
|
.ShouldHaveSingleItem()
|
|
.Value.ShouldBe("$(AtomUITestResultsRoot)/$(MSBuildProjectName)");
|
|
outputPaths.Descendants("VSTestResultsDirectory")
|
|
.ShouldHaveSingleItem()
|
|
.Value.ShouldBe("$(AtomUITestResultsDirectory)");
|
|
|
|
var repositoryTargets = XDocument.Load(GetRepoFile("build/AtomUI.Repository.targets"));
|
|
AssertTestResultsCleanupTarget(
|
|
repositoryTargets,
|
|
"AtomUICleanTestResultsBeforeRun",
|
|
"BeforeTargets",
|
|
"_AtomUITestResultsDirectoryToCleanBeforeRun");
|
|
AssertTestResultsCleanupTarget(
|
|
repositoryTargets,
|
|
"AtomUICleanTestResultsAfterRun",
|
|
"AfterTargets",
|
|
"_AtomUITestResultsDirectoryToCleanAfterRun");
|
|
}
|
|
|
|
[Fact]
|
|
public void Repository_Test_Runs_Forbid_Persistent_VSTest_Dumps()
|
|
{
|
|
var outputPaths = XDocument.Load(GetRepoFile("build/OutputPaths.props"));
|
|
var hangDumpType = outputPaths.Descendants("VSTestBlameHangDumpType")
|
|
.ShouldHaveSingleItem();
|
|
hangDumpType.Value.ShouldBe("none");
|
|
((string?)hangDumpType.Attribute("Condition")).ShouldBe(
|
|
"'$(IsTestProject)' == 'true' and '$(VSTestBlameHang)' == 'true' and '$(VSTestBlameHangDumpType)' == ''");
|
|
|
|
var crashDumpType = outputPaths.Descendants("VSTestBlameCrashDumpType")
|
|
.ShouldHaveSingleItem();
|
|
crashDumpType.Value.ShouldBe("none");
|
|
((string?)crashDumpType.Attribute("Condition")).ShouldBe(
|
|
"'$(IsTestProject)' == 'true' and '$(VSTestBlameCrash)' == 'true' and '$(VSTestBlameCrashDumpType)' == ''");
|
|
|
|
var repositoryTargets = XDocument.Load(GetRepoFile("build/AtomUI.Repository.targets"));
|
|
var guardTarget = repositoryTargets.Descendants("Target")
|
|
.Single(element =>
|
|
(string?)element.Attribute("Name") ==
|
|
"AtomUIRejectPersistentVSTestDumps");
|
|
|
|
((string?)guardTarget.Attribute("BeforeTargets")).ShouldBe("VSTest");
|
|
((string?)guardTarget.Attribute("Condition")).ShouldBe("'$(IsTestProject)' == 'true'");
|
|
|
|
var errors = guardTarget.Descendants("Error").ToArray();
|
|
errors.Length.ShouldBe(2);
|
|
errors.Select(element => (string?)element.Attribute("Condition"))
|
|
.ShouldBe([
|
|
"'$(VSTestBlameHangDumpType)' == 'full' or '$(VSTestBlameHangDumpType)' == 'mini'",
|
|
"'$(VSTestBlameCrashDumpType)' == 'full' or '$(VSTestBlameCrashDumpType)' == 'mini'"
|
|
]);
|
|
errors.Select(element => (string?)element.Attribute("Text"))
|
|
.ShouldAllBe(text => text != null &&
|
|
text.Contains("must not collect persistent", StringComparison.Ordinal));
|
|
}
|
|
|
|
private static void AssertTestResultsCleanupTarget(
|
|
XDocument repositoryTargets,
|
|
string targetName,
|
|
string timingAttribute,
|
|
string itemName)
|
|
{
|
|
var cleanupTarget = repositoryTargets.Descendants("Target")
|
|
.Single(element =>
|
|
(string?)element.Attribute("Name") == targetName);
|
|
|
|
((string?)cleanupTarget.Attribute(timingAttribute)).ShouldBe("VSTest");
|
|
((string?)cleanupTarget.Attribute("Condition")).ShouldBe("'$(IsTestProject)' == 'true'");
|
|
|
|
var directoriesToClean = cleanupTarget.Descendants(itemName)
|
|
.Select(element => (string?)element.Attribute("Include"))
|
|
.ToArray();
|
|
directoriesToClean.ShouldBe([
|
|
"$(MSBuildProjectDirectory)/TestResults",
|
|
"$(AtomUITestResultsDirectory)"
|
|
]);
|
|
|
|
var removeDir = cleanupTarget.Descendants("RemoveDir").ShouldHaveSingleItem();
|
|
((string?)removeDir.Attribute("Directories")).ShouldBe($"@({itemName})");
|
|
removeDir.Attribute("ContinueOnError").ShouldBeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public void Repository_Targets_Exclude_Compiler_Generated_Files_Once()
|
|
{
|
|
const string generatedFilesPattern = "$(CompilerGeneratedFilesOutputPath)/**/*.cs";
|
|
var repositoryTargets = XDocument.Load(GetRepoFile("build/AtomUI.Repository.targets"));
|
|
var removal = repositoryTargets.Descendants("Compile")
|
|
.Where(element =>
|
|
(string?)element.Attribute("Remove") == generatedFilesPattern)
|
|
.ShouldHaveSingleItem();
|
|
|
|
((string?)removal.Parent?.Attribute("Condition"))
|
|
.ShouldBe("'$(CompilerGeneratedFilesOutputPath)' != ''");
|
|
|
|
var repositoryRoot = GetRepositoryRoot();
|
|
var projectFiles = new[] { "src", "controlgallery", "tests", "tools" }
|
|
.Select(directory => Path.Combine(repositoryRoot, directory))
|
|
.Where(Directory.Exists)
|
|
.SelectMany(directory => Directory.EnumerateFiles(
|
|
directory,
|
|
"*.csproj",
|
|
SearchOption.AllDirectories));
|
|
foreach (var projectFile in projectFiles)
|
|
{
|
|
var project = XDocument.Load(projectFile);
|
|
if (!project.Descendants("CompilerGeneratedFilesOutputPath").Any())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
project.Descendants("Compile")
|
|
.ShouldNotContain(element =>
|
|
(string?)element.Attribute("Remove") == generatedFilesPattern,
|
|
projectFile);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Repository_Default_Items_Always_Exclude_Stale_Generated_Files()
|
|
{
|
|
var projectDefaults = XDocument.Load(GetRepoFile("build/ProjectDefaults.props"));
|
|
var excludes = projectDefaults.Descendants("DefaultItemExcludes")
|
|
.ShouldHaveSingleItem()
|
|
.Value;
|
|
|
|
excludes.ShouldContain("$(MSBuildProjectDirectory)/obj/**");
|
|
excludes.ShouldContain("$(MSBuildProjectDirectory)/GeneratedFiles/**");
|
|
}
|
|
|
|
[Fact]
|
|
public void NuGet_Generator_Entry_Points_Import_Flat_Feature_Files()
|
|
{
|
|
GetImports("build/AtomUI.Generator.props").ShouldBe([
|
|
"$(MSBuildThisFileDirectory)AtomUI.Localization.props"
|
|
]);
|
|
GetImports("build/AtomUI.Generator.targets").ShouldBe([
|
|
"$(MSBuildThisFileDirectory)AtomUI.Registration.targets",
|
|
"$(MSBuildThisFileDirectory)AtomUI.Localization.targets",
|
|
"$(MSBuildThisFileDirectory)AtomUI.ThemeAssets.targets",
|
|
"$(MSBuildThisFileDirectory)AtomUI.CompiledThemes.targets"
|
|
]);
|
|
}
|
|
|
|
[Fact]
|
|
public void Localization_Targets_Own_The_Complete_Build_Integration()
|
|
{
|
|
var targets = XDocument.Load(GetRepoFile("build/AtomUI.Localization.targets"));
|
|
|
|
GetImports("build/AtomUI.Localization.targets").ShouldBe(["$(MSBuildThisFileDirectory)AtomUI.Toolchain.Tasks.targets"]);
|
|
targets.Descendants("AdditionalFiles").ShouldNotBeEmpty();
|
|
targets.Descendants("CompilerVisibleItemMetadata").ShouldNotBeEmpty();
|
|
GetTargetNames(targets).ShouldBe([
|
|
"AtomUIGetLanguageModuleSourceAssets",
|
|
"AtomUIResolveLanguageContractProjectReferences",
|
|
"AtomUIInferLanguagePackageContractPolicy",
|
|
"AtomUIGetLanguagePackProjectAssets",
|
|
"AtomUIResolveLanguagePackProjectReferences",
|
|
"AtomUIExportLanguageTemplates",
|
|
"AtomUIPrepareLanguagePackage",
|
|
"AtomUIPrepareLanguageModuleAssets"
|
|
]);
|
|
}
|
|
|
|
private static string[] GetImports(string relativePath)
|
|
{
|
|
return XDocument.Load(GetRepoFile(relativePath))
|
|
.Descendants()
|
|
.Where(element => element.Name.LocalName == "Import")
|
|
.Select(element => (string?)element.Attribute("Project"))
|
|
.Where(static path => path is not null)
|
|
.Cast<string>()
|
|
.ToArray();
|
|
}
|
|
|
|
private static string[] GetTargetNames(XDocument document)
|
|
{
|
|
return document.Descendants()
|
|
.Where(element => element.Name.LocalName == "Target")
|
|
.Select(element => (string?)element.Attribute("Name"))
|
|
.Where(static name => name is not null)
|
|
.Cast<string>()
|
|
.ToArray();
|
|
}
|
|
|
|
private static string GetRepoFile(string relativePath)
|
|
{
|
|
var repositoryRoot = GetRepositoryRoot();
|
|
var candidate = Path.Combine(repositoryRoot, relativePath);
|
|
if (File.Exists(candidate))
|
|
{
|
|
return candidate;
|
|
}
|
|
|
|
throw new FileNotFoundException(relativePath);
|
|
}
|
|
|
|
private static string GetRepositoryRoot()
|
|
{
|
|
var directory = new DirectoryInfo(AppContext.BaseDirectory);
|
|
while (directory is not null)
|
|
{
|
|
if (File.Exists(Path.Combine(directory.FullName, "AtomUI.slnx")))
|
|
{
|
|
return directory.FullName;
|
|
}
|
|
|
|
directory = directory.Parent;
|
|
}
|
|
|
|
throw new DirectoryNotFoundException("Could not locate the AtomUI repository root.");
|
|
}
|
|
}
|