From 30609922aed667e0fcb887fe8c2d59d209cf978a Mon Sep 17 00:00:00 2001 From: Lukas Gasselsberger | alu-one Date: Thu, 10 Sep 2026 14:47:39 +0200 Subject: [PATCH 1/3] Fix the powershell script by not using `Join-Path` --- build.ps1 | 4 ++-- src/Fallout.Cli/Commands/SetupCommand.cs | 2 ++ src/Fallout.Cli/templates/build.ps1 | 5 +++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/build.ps1 b/build.ps1 index 3ea0b34c1..7b826045f 100644 --- a/build.ps1 +++ b/build.ps1 @@ -13,9 +13,9 @@ $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent # CONFIGURATION ########################################################################### -$TempDirectory = Join-Path $PSScriptRoot '.fallout/temp' +$TempDirectory = "$PSScriptRoot\\.fallout\temp" -$DotNetGlobalFile = Join-Path $PSScriptRoot 'global.json' +$DotNetGlobalFile = "$PSScriptRoot\global.json" $DotNetInstallUrl = "https://dot.net/v1/dotnet-install.ps1" $DotNetChannel = "STS" diff --git a/src/Fallout.Cli/Commands/SetupCommand.cs b/src/Fallout.Cli/Commands/SetupCommand.cs index 5c879a814..e59cf199b 100644 --- a/src/Fallout.Cli/Commands/SetupCommand.cs +++ b/src/Fallout.Cli/Commands/SetupCommand.cs @@ -156,6 +156,8 @@ public async Task ExecuteAsync(string[] args, AbsolutePath rootDirectory, A new { RootDirectory = buildDirectory.GetWinRelativePathTo(rootDirectory), + BuildDirectory = buildProjectRelativeDirectory, + BuildProjectName = buildProjectName, ScriptDirectory = buildDirectory.GetWinRelativePathTo(WorkingDirectory), TargetFramework = TARGET_FRAMEWORK, FalloutVersion = falloutVersion, diff --git a/src/Fallout.Cli/templates/build.ps1 b/src/Fallout.Cli/templates/build.ps1 index f66198b3e..65454f721 100644 --- a/src/Fallout.Cli/templates/build.ps1 +++ b/src/Fallout.Cli/templates/build.ps1 @@ -13,9 +13,10 @@ $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent # CONFIGURATION ########################################################################### -$TempDirectory = Join-Path $PSScriptRoot '_ROOT_DIRECTORY_/.fallout/temp' +$BuildProjectFile = "$PSScriptRoot\_BUILD_DIRECTORY_\_BUILD_PROJECT_NAME_.csproj" +$TempDirectory = "$PSScriptRoot\\.fallout\temp" -$DotNetGlobalFile = Join-Path $PSScriptRoot '_ROOT_DIRECTORY_/global.json' +$DotNetGlobalFile = "$PSScriptRoot\global.json" $DotNetInstallUrl = "https://dot.net/v1/dotnet-install.ps1" $DotNetChannel = "STS" From 0a5a1914506453310aaa0d002fc50438d90c887c Mon Sep 17 00:00:00 2001 From: Lukas Gasselsberger | alu-one Date: Thu, 17 Sep 2026 11:50:14 +0200 Subject: [PATCH 2/3] Make a glob file search for a build variable in order to find the build project --- src/Fallout.Cli/Commands/AddPackageCommand.cs | 46 +++++++++- src/Fallout.Cli/templates/build.ps1 | 1 - .../Commands/AddPackageCommandSpecs.cs | 91 +++++++++++++++++++ 3 files changed, 134 insertions(+), 4 deletions(-) create mode 100644 tests/Fallout.Cli.Specs/Commands/AddPackageCommandSpecs.cs diff --git a/src/Fallout.Cli/Commands/AddPackageCommand.cs b/src/Fallout.Cli/Commands/AddPackageCommand.cs index d0258a33d..fffe67520 100644 --- a/src/Fallout.Cli/Commands/AddPackageCommand.cs +++ b/src/Fallout.Cli/Commands/AddPackageCommand.cs @@ -1,3 +1,4 @@ +using System.IO; using System.Linq; using System.Threading.Tasks; using Fallout.Common; @@ -6,13 +7,14 @@ using Fallout.Common.Tooling; using Fallout.Common.Tools.DotNet; using Fallout.Solutions; +using Microsoft.Build.Evaluation; namespace Fallout.Cli.Commands; /// /// fallout :add-package: adds (or upgrades) a NuGet package reference in the build project. /// -internal sealed class AddPackageCommand(IConfigurationReader configuration, IPackageManager packages) : IFalloutCommand +internal sealed class AddPackageCommand(IPackageManager packages) : IFalloutCommand { public string Name => "add-package"; @@ -31,8 +33,7 @@ await NuGetVersionResolver.GetLatestVersion(packageId, includePrereleases: false .ToString()) .NotNull("packageVersion != null"); - var configuration1 = configuration.Read(buildScript, evaluate: true); - var buildProjectFile = configuration1[ConfigurationReader.BuildProjectFileKey]; + var buildProjectFile = FindBuildProject(rootDirectory); Host.Information($"Installing {packageId}/{packageVersion} to {buildProjectFile} ..."); packages.AddOrReplacePackage(packageId, packageVersion, PackageManager.DownloadType, buildProjectFile); DotNetTasks.DotNet($"restore {buildProjectFile}"); @@ -49,4 +50,43 @@ await NuGetVersionResolver.GetLatestVersion(packageId, includePrereleases: false Host.Information($"Done installing {packageId}/{packageVersion} to {buildProjectFile}"); return 0; } + + internal static AbsolutePath FindBuildProject(AbsolutePath rootDirectory) + { + var buildProject = rootDirectory.GlobFiles("**/*.csproj") + .Where(x => HasMatchingRootDirectory(x, rootDirectory)) + .OrderBy(x => x.ToString().Length) + .FirstOrDefault(); + + Assert.True(buildProject != null, + $"Could not find a build project with a FalloutRootDirectory property pointing to '{rootDirectory}'."); + + return buildProject; + } + + private static bool HasMatchingRootDirectory(AbsolutePath projectFile, AbsolutePath rootDirectory) + { + ProjectProperty rootDirectoryProperty; + try + { + rootDirectoryProperty = ProjectModelTasks.ParseProject(projectFile).NotNull() + .GetProperty("FalloutRootDirectory"); + } + catch + { + return false; + } + + if (string.IsNullOrWhiteSpace(rootDirectoryProperty?.EvaluatedValue)) + { + return false; + } + + var configuredRootDirectory = rootDirectoryProperty.EvaluatedValue; + var resolvedRootDirectory = Path.IsPathRooted(configuredRootDirectory) + ? (AbsolutePath)configuredRootDirectory + : projectFile.Parent / configuredRootDirectory; + + return resolvedRootDirectory == rootDirectory; + } } diff --git a/src/Fallout.Cli/templates/build.ps1 b/src/Fallout.Cli/templates/build.ps1 index 65454f721..d755854f5 100644 --- a/src/Fallout.Cli/templates/build.ps1 +++ b/src/Fallout.Cli/templates/build.ps1 @@ -13,7 +13,6 @@ $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent # CONFIGURATION ########################################################################### -$BuildProjectFile = "$PSScriptRoot\_BUILD_DIRECTORY_\_BUILD_PROJECT_NAME_.csproj" $TempDirectory = "$PSScriptRoot\\.fallout\temp" $DotNetGlobalFile = "$PSScriptRoot\global.json" diff --git a/tests/Fallout.Cli.Specs/Commands/AddPackageCommandSpecs.cs b/tests/Fallout.Cli.Specs/Commands/AddPackageCommandSpecs.cs new file mode 100644 index 000000000..c5b48d100 --- /dev/null +++ b/tests/Fallout.Cli.Specs/Commands/AddPackageCommandSpecs.cs @@ -0,0 +1,91 @@ +using System; +using System.IO; +using Fallout.Cli.Commands; +using Fallout.Common.IO; +using FluentAssertions; +using Xunit; + +namespace Fallout.Cli.Specs.Commands; + +public class AddPackageCommandSpecs +{ + [Fact] + public void Project_with_root_directory_property_is_selected() + { + // Arrange + using var root = TempRoot.Create(); + root.WriteProject("src/app.csproj", ""); + var expected = root.WriteProject("build/custom.csproj", + ".."); + + // Act + var actual = AddPackageCommand.FindBuildProject(root.Path); + + // Assert + actual.Should().Be(expected); + } + + [Fact] + public void Missing_root_directory_property_gives_clear_error() + { + // Arrange + using var root = TempRoot.Create(); + root.WriteProject("build/build.csproj", + ""); + + // Act + var action = () => AddPackageCommand.FindBuildProject(root.Path); + + // Assert + action.Should().Throw() + .WithMessage("*Could not find a build project*FalloutRootDirectory*"); + } + + [Fact] + public void Project_with_shortest_path_is_selected() + { + // Arrange + using var root = TempRoot.Create(); + root.WriteProject("build/nested/first.csproj", + "../.."); + var expected = root.WriteProject("build/second.csproj", + ".."); + + // Act + var actual = AddPackageCommand.FindBuildProject(root.Path); + + // Assert + actual.Should().Be(expected); + } + + private sealed class TempRoot : IDisposable + { + public AbsolutePath Path { get; } + + private TempRoot(AbsolutePath path) => Path = path; + + public static TempRoot Create() + { + var path = (AbsolutePath)System.IO.Path.Combine( + System.IO.Path.GetTempPath(), "fallout-add-package-" + Guid.NewGuid().ToString("N")); + path.CreateDirectory(); + return new TempRoot(path); + } + + public AbsolutePath WriteProject(string relativePath, string content) + { + var project = Path / relativePath; + project.Parent.CreateDirectory(); + File.WriteAllText(project, content); + return project; + } + + public void Dispose() + { + if (Directory.Exists(Path)) + { + Directory.Delete(Path, recursive: true); + } + } + } +} From 839c236544151f7797dbb8d17bbca6749bebd2ab Mon Sep 17 00:00:00 2001 From: ITaluone <44049228+ITaluone@users.noreply.github.com> Date: Thu, 17 Sep 2026 13:51:18 +0200 Subject: [PATCH 3/3] Use forward slashes in build.ps1 Co-authored-by: ITaluone <44049228+ITaluone@users.noreply.github.com> --- build.ps1 | 4 ++-- src/Fallout.Cli/templates/build.ps1 | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.ps1 b/build.ps1 index 7b826045f..1b9b1e56d 100644 --- a/build.ps1 +++ b/build.ps1 @@ -13,9 +13,9 @@ $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent # CONFIGURATION ########################################################################### -$TempDirectory = "$PSScriptRoot\\.fallout\temp" +$TempDirectory = "$PSScriptRoot/.fallout/temp" -$DotNetGlobalFile = "$PSScriptRoot\global.json" +$DotNetGlobalFile = "$PSScriptRoot/global.json" $DotNetInstallUrl = "https://dot.net/v1/dotnet-install.ps1" $DotNetChannel = "STS" diff --git a/src/Fallout.Cli/templates/build.ps1 b/src/Fallout.Cli/templates/build.ps1 index d755854f5..95d31a787 100644 --- a/src/Fallout.Cli/templates/build.ps1 +++ b/src/Fallout.Cli/templates/build.ps1 @@ -13,9 +13,9 @@ $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent # CONFIGURATION ########################################################################### -$TempDirectory = "$PSScriptRoot\\.fallout\temp" +$TempDirectory = "$PSScriptRoot/.fallout/temp" -$DotNetGlobalFile = "$PSScriptRoot\global.json" +$DotNetGlobalFile = "$PSScriptRoot/global.json" $DotNetInstallUrl = "https://dot.net/v1/dotnet-install.ps1" $DotNetChannel = "STS"