diff --git a/build.ps1 b/build.ps1
index 3ea0b34c1..1b9b1e56d 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/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/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..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 = Join-Path $PSScriptRoot '_ROOT_DIRECTORY_/.fallout/temp'
+$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"
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);
+ }
+ }
+ }
+}