diff --git a/src/Cli/dotnet/Commands/Build/BuildCommand.cs b/src/Cli/dotnet/Commands/Build/BuildCommand.cs
index c97289de5f88..cde474ec73c2 100644
--- a/src/Cli/dotnet/Commands/Build/BuildCommand.cs
+++ b/src/Cli/dotnet/Commands/Build/BuildCommand.cs
@@ -13,7 +13,7 @@ public static class BuildCommand
{
public static CommandBase FromArgs(string[] args, string? msbuildPath = null)
{
- var parseResult = Parser.Parse(["dotnet", "build", ..args]);
+ var parseResult = Parser.Parse(["dotnet", "build", .. args]);
return FromParseResult(parseResult, msbuildPath);
}
diff --git a/src/Cli/dotnet/Commands/Clean/CleanCommand.cs b/src/Cli/dotnet/Commands/Clean/CleanCommand.cs
index ee22ae1d3900..d1772e247a4f 100644
--- a/src/Cli/dotnet/Commands/Clean/CleanCommand.cs
+++ b/src/Cli/dotnet/Commands/Clean/CleanCommand.cs
@@ -13,7 +13,7 @@ public sealed class CleanCommand(MSBuildArgs msbuildArgs, string? msbuildPath =
{
public static CommandBase FromArgs(string[] args, string? msbuildPath = null)
{
- var result = Parser.Parse(["dotnet", "clean", ..args]);
+ var result = Parser.Parse(["dotnet", "clean", .. args]);
return FromParseResult(result, msbuildPath);
}
diff --git a/src/Cli/dotnet/Commands/CliCommandStrings.resx b/src/Cli/dotnet/Commands/CliCommandStrings.resx
index c1d47a523067..6aada0de96d0 100644
--- a/src/Cli/dotnet/Commands/CliCommandStrings.resx
+++ b/src/Cli/dotnet/Commands/CliCommandStrings.resx
@@ -1102,6 +1102,9 @@ Your project targets multiple frameworks. Specify which framework to run using '
The --solution-folder and --in-root options cannot be used together; use only one of the options.
+
+ Solution filter files (.slnf) do not support the --in-root or --solution-folder options.
+
Solution Folder(s)
diff --git a/src/Cli/dotnet/Commands/Hidden/Complete/CompleteCommand.cs b/src/Cli/dotnet/Commands/Hidden/Complete/CompleteCommand.cs
index f1b14ac9eeda..564dc3ceaedc 100644
--- a/src/Cli/dotnet/Commands/Hidden/Complete/CompleteCommand.cs
+++ b/src/Cli/dotnet/Commands/Hidden/Complete/CompleteCommand.cs
@@ -19,7 +19,7 @@ public static int Run(ParseResult parseResult)
public static int RunWithReporter(string[] args, IReporter reporter)
{
- var result = Parser.Parse(["dotnet", "complete", ..args]);
+ var result = Parser.Parse(["dotnet", "complete", .. args]);
return RunWithReporter(result, reporter);
}
diff --git a/src/Cli/dotnet/Commands/Hidden/InternalReportInstallSuccess/InternalReportInstallSuccessCommand.cs b/src/Cli/dotnet/Commands/Hidden/InternalReportInstallSuccess/InternalReportInstallSuccessCommand.cs
index 7bf9f15236a3..c6a6d87ff9df 100644
--- a/src/Cli/dotnet/Commands/Hidden/InternalReportInstallSuccess/InternalReportInstallSuccessCommand.cs
+++ b/src/Cli/dotnet/Commands/Hidden/InternalReportInstallSuccess/InternalReportInstallSuccessCommand.cs
@@ -26,7 +26,7 @@ public static int Run(ParseResult parseResult)
public static void ProcessInputAndSendTelemetry(string[] args, ITelemetry telemetry)
{
- var result = Parser.Parse(["dotnet", "internal-reportinstallsuccess", ..args]);
+ var result = Parser.Parse(["dotnet", "internal-reportinstallsuccess", .. args]);
ProcessInputAndSendTelemetry(result, telemetry);
}
diff --git a/src/Cli/dotnet/Commands/MSBuild/MSBuildCommand.cs b/src/Cli/dotnet/Commands/MSBuild/MSBuildCommand.cs
index ff5d7e4a6066..57a922005fc2 100644
--- a/src/Cli/dotnet/Commands/MSBuild/MSBuildCommand.cs
+++ b/src/Cli/dotnet/Commands/MSBuild/MSBuildCommand.cs
@@ -25,7 +25,7 @@ public class MSBuildCommand(
{
public static MSBuildCommand FromArgs(string[] args, string? msbuildPath = null)
{
- var result = Parser.Parse(["dotnet", "msbuild", ..args]);
+ var result = Parser.Parse(["dotnet", "msbuild", .. args]);
return FromParseResult(result, msbuildPath);
}
diff --git a/src/Cli/dotnet/Commands/Pack/PackCommand.cs b/src/Cli/dotnet/Commands/Pack/PackCommand.cs
index bba916eb775f..ab7d0ba0542b 100644
--- a/src/Cli/dotnet/Commands/Pack/PackCommand.cs
+++ b/src/Cli/dotnet/Commands/Pack/PackCommand.cs
@@ -25,7 +25,7 @@ public class PackCommand(
{
public static CommandBase FromArgs(string[] args, string? msbuildPath = null)
{
- var parseResult = Parser.Parse(["dotnet", "pack", ..args]);
+ var parseResult = Parser.Parse(["dotnet", "pack", .. args]);
return FromParseResult(parseResult, msbuildPath);
}
@@ -97,14 +97,14 @@ public static int RunPackCommand(ParseResult parseResult)
if (args.Count != 1)
{
- Console.Error.WriteLine(CliStrings.PackCmd_OneNuspecAllowed);
+ Console.Error.WriteLine(CliStrings.PackCmd_OneNuspecAllowed);
return 1;
}
var nuspecPath = args[0];
var packArgs = new PackArgs()
- {
+ {
Logger = new NuGetConsoleLogger(),
Exclude = new List(),
OutputDirectory = parseResult.GetValue(definition.OutputOption),
diff --git a/src/Cli/dotnet/Commands/Publish/PublishCommand.cs b/src/Cli/dotnet/Commands/Publish/PublishCommand.cs
index db75885e6da0..bb68394da143 100644
--- a/src/Cli/dotnet/Commands/Publish/PublishCommand.cs
+++ b/src/Cli/dotnet/Commands/Publish/PublishCommand.cs
@@ -22,7 +22,7 @@ private PublishCommand(
public static CommandBase FromArgs(string[] args, string? msbuildPath = null)
{
- var parseResult = Parser.Parse(["dotnet", "publish", ..args]);
+ var parseResult = Parser.Parse(["dotnet", "publish", .. args]);
return FromParseResult(parseResult);
}
diff --git a/src/Cli/dotnet/Commands/Restore/RestoreCommand.cs b/src/Cli/dotnet/Commands/Restore/RestoreCommand.cs
index a79d38066986..2743d1ffe048 100644
--- a/src/Cli/dotnet/Commands/Restore/RestoreCommand.cs
+++ b/src/Cli/dotnet/Commands/Restore/RestoreCommand.cs
@@ -13,7 +13,7 @@ public static class RestoreCommand
{
public static CommandBase FromArgs(string[] args, string? msbuildPath = null)
{
- var result = Parser.Parse(["dotnet", "restore", ..args]);
+ var result = Parser.Parse(["dotnet", "restore", .. args]);
return FromParseResult(result, msbuildPath);
}
diff --git a/src/Cli/dotnet/Commands/Restore/RestoringCommand.cs b/src/Cli/dotnet/Commands/Restore/RestoringCommand.cs
index bf75c95e16a6..f87701357182 100644
--- a/src/Cli/dotnet/Commands/Restore/RestoringCommand.cs
+++ b/src/Cli/dotnet/Commands/Restore/RestoringCommand.cs
@@ -37,7 +37,7 @@ public RestoringCommand(
string? msbuildPath = null,
string? userProfileDir = null,
bool? advertiseWorkloadUpdates = null)
- : base(GetCommandArguments(msbuildArgs, noRestore), msbuildPath)
+ : base(GetCommandArguments(msbuildArgs, noRestore), msbuildPath)
{
userProfileDir = CliFolderPathCalculator.DotnetUserProfileFolderPath;
Task.Run(() => WorkloadManifestUpdater.BackgroundUpdateAdvertisingManifestsAsync(userProfileDir));
@@ -118,13 +118,13 @@ private static MSBuildArgs GetCommandArguments(
ReadOnlyDictionary restoreProperties =
msbuildArgs.GlobalProperties?
.Where(kvp => !IsPropertyExcludedFromRestore(kvp.Key))?
- .ToDictionary(kvp => kvp.Key, kvp => kvp.Value, StringComparer.OrdinalIgnoreCase) is { } filteredList ? new(filteredList): ReadOnlyDictionary.Empty;
+ .ToDictionary(kvp => kvp.Key, kvp => kvp.Value, StringComparer.OrdinalIgnoreCase) is { } filteredList ? new(filteredList) : ReadOnlyDictionary.Empty;
var restoreMSBuildArgs =
MSBuildArgs.FromProperties(RestoreOptimizationProperties)
.CloneWithAdditionalTargets("Restore")
.CloneWithExplicitArgs([.. newArgumentsToAdd, .. existingArgumentsToForward])
.CloneWithAdditionalProperties(restoreProperties);
- if (msbuildArgs.Verbosity is {} verbosity)
+ if (msbuildArgs.Verbosity is { } verbosity)
{
restoreMSBuildArgs = restoreMSBuildArgs.CloneWithVerbosity(verbosity);
}
@@ -171,7 +171,7 @@ private static bool HasPropertyToExcludeFromRestore(MSBuildArgs msbuildArgs)
private static readonly List FlagsThatTriggerSilentSeparateRestore = [.. ComputeFlags(FlagsThatTriggerSilentRestore)];
- private static readonly List PropertiesToExcludeFromSeparateRestore = [ .. PropertiesToExcludeFromRestore ];
+ private static readonly List PropertiesToExcludeFromSeparateRestore = [.. PropertiesToExcludeFromRestore];
///
/// We investigate the arguments we're about to send to a separate restore call and filter out
diff --git a/src/Cli/dotnet/Commands/Solution/Add/SolutionAddCommand.cs b/src/Cli/dotnet/Commands/Solution/Add/SolutionAddCommand.cs
index ee372bd0b269..d2804cc0111b 100644
--- a/src/Cli/dotnet/Commands/Solution/Add/SolutionAddCommand.cs
+++ b/src/Cli/dotnet/Commands/Solution/Add/SolutionAddCommand.cs
@@ -46,7 +46,7 @@ public SolutionAddCommand(ParseResult parseResult)
_solutionFolderPath = parseResult.GetValue(Definition.SolutionFolderOption);
_includeReferences = parseResult.GetValue(Definition.IncludeReferencesOption);
SolutionArgumentValidator.ParseAndValidateArguments(_fileOrDirectory, _projects, SolutionArgumentValidator.CommandType.Add, _inRoot, _solutionFolderPath);
- _solutionFileFullPath = SlnFileFactory.GetSolutionFileFullPath(_fileOrDirectory);
+ _solutionFileFullPath = SlnFileFactory.GetSolutionFileFullPath(_fileOrDirectory, includeSolutionFilterFiles: true);
}
public override int Execute()
@@ -59,14 +59,22 @@ public override int Execute()
// Get project paths from the command line arguments
PathUtility.EnsureAllPathsExist(_projects, CliStrings.CouldNotFindProjectOrDirectory, true);
- IEnumerable fullProjectPaths = _projects.Select(project =>
+ List fullProjectPaths = _projects.Select(project =>
{
var fullPath = Path.GetFullPath(project);
return Directory.Exists(fullPath) ? MsbuildProject.GetProjectFileFromDirectory(fullPath) : fullPath;
- });
+ }).ToList();
- // Add projects to the solution
- AddProjectsToSolutionAsync(fullProjectPaths, CancellationToken.None).GetAwaiter().GetResult();
+ // Check if we're working with a solution filter file
+ if (_solutionFileFullPath.HasExtension(SlnfFileHelper.SlnfExtension))
+ {
+ AddProjectsToSolutionFilter(fullProjectPaths);
+ }
+ else
+ {
+ // Add projects to the solution
+ AddProjectsToSolutionAsync(fullProjectPaths, CancellationToken.None).GetAwaiter().GetResult();
+ }
return 0;
}
@@ -233,4 +241,68 @@ private void AddProject(SolutionModel solution, string fullProjectPath, ISolutio
}
}
}
+
+ private void AddProjectsToSolutionFilter(IEnumerable projectPaths)
+ {
+ // Solution filter files don't support --in-root or --solution-folder options
+ if (_inRoot || !string.IsNullOrEmpty(_solutionFolderPath))
+ {
+ throw new GracefulException(CliCommandStrings.SolutionFilterDoesNotSupportFolderOptions);
+ }
+
+ // Load the filtered solution to get the parent solution path and existing projects
+ SolutionModel filteredSolution = SlnFileFactory.CreateFromFilteredSolutionFile(_solutionFileFullPath);
+ string parentSolutionPath = filteredSolution.Description!; // The parent solution path is stored in Description
+
+ // Load the parent solution to validate projects exist in it
+ SolutionModel parentSolution = SlnFileFactory.CreateFromFileOrDirectory(parentSolutionPath);
+
+ // Get existing projects in the filter (already normalized to OS separator by CreateFromFilteredSolutionFile)
+ var existingProjects = filteredSolution.SolutionProjects.Select(p => p.FilePath).ToHashSet();
+
+ // Get solution-relative paths for new projects
+ var newProjects = ValidateAndGetNewProjects(projectPaths, parentSolution, parentSolutionPath, existingProjects);
+
+ // Add new projects to the existing list and save
+ var allProjects = existingProjects.Concat(newProjects).OrderBy(p => p);
+ SlnfFileHelper.SaveSolutionFilter(_solutionFileFullPath, parentSolutionPath, allProjects);
+ }
+
+ private List ValidateAndGetNewProjects(
+ IEnumerable projectPaths,
+ SolutionModel parentSolution,
+ string parentSolutionPath,
+ HashSet existingProjects)
+ {
+ var newProjects = new List();
+ string parentSolutionDirectory = Path.GetDirectoryName(parentSolutionPath) ?? string.Empty;
+
+ foreach (var projectPath in projectPaths)
+ {
+ string parentSolutionRelativePath = Path.GetRelativePath(parentSolutionDirectory, projectPath);
+
+ // Normalize to OS separator for consistent comparison
+ parentSolutionRelativePath = SlnfFileHelper.NormalizePathSeparatorsToOS(parentSolutionRelativePath);
+
+ // Check if project exists in parent solution
+ var projectInParent = parentSolution.FindProject(parentSolutionRelativePath);
+ if (projectInParent is null)
+ {
+ Reporter.Error.WriteLine(CliStrings.ProjectNotFoundInTheSolution, parentSolutionRelativePath, parentSolutionPath);
+ continue;
+ }
+
+ // Check if project is already in the filter
+ if (existingProjects.Contains(parentSolutionRelativePath))
+ {
+ Reporter.Output.WriteLine(CliStrings.SolutionAlreadyContainsProject, _solutionFileFullPath, parentSolutionRelativePath);
+ continue;
+ }
+
+ newProjects.Add(parentSolutionRelativePath);
+ Reporter.Output.WriteLine(CliStrings.ProjectAddedToTheSolution, parentSolutionRelativePath);
+ }
+
+ return newProjects;
+ }
}
diff --git a/src/Cli/dotnet/Commands/Solution/Migrate/SolutionMigrateCommand.cs b/src/Cli/dotnet/Commands/Solution/Migrate/SolutionMigrateCommand.cs
index 9e32962ad7cc..47ef19bdf6dc 100644
--- a/src/Cli/dotnet/Commands/Solution/Migrate/SolutionMigrateCommand.cs
+++ b/src/Cli/dotnet/Commands/Solution/Migrate/SolutionMigrateCommand.cs
@@ -35,7 +35,9 @@ public override int Execute()
{
ConvertToSlnxAsync(slnFileFullPath, slnxFileFullPath, CancellationToken.None).Wait();
return 0;
- } catch (Exception ex) {
+ }
+ catch (Exception ex)
+ {
throw new GracefulException(ex.Message, ex);
}
}
diff --git a/src/Cli/dotnet/Commands/Solution/Remove/SolutionRemoveCommand.cs b/src/Cli/dotnet/Commands/Solution/Remove/SolutionRemoveCommand.cs
index 28a3e8c26315..8e18861ec3ac 100644
--- a/src/Cli/dotnet/Commands/Solution/Remove/SolutionRemoveCommand.cs
+++ b/src/Cli/dotnet/Commands/Solution/Remove/SolutionRemoveCommand.cs
@@ -27,7 +27,7 @@ public SolutionRemoveCommand(ParseResult parseResult)
public override int Execute()
{
- string solutionFileFullPath = SlnFileFactory.GetSolutionFileFullPath(_fileOrDirectory);
+ string solutionFileFullPath = SlnFileFactory.GetSolutionFileFullPath(_fileOrDirectory, includeSolutionFilterFiles: true);
if (_projects.Count == 0)
{
throw new GracefulException(CliStrings.SpecifyAtLeastOneProjectToRemove);
@@ -43,7 +43,15 @@ public override int Execute()
? MsbuildProject.GetProjectFileFromDirectory(p)
: p));
- RemoveProjectsAsync(solutionFileFullPath, relativeProjectPaths, CancellationToken.None).GetAwaiter().GetResult();
+ // Check if we're working with a solution filter file
+ if (solutionFileFullPath.HasExtension(SlnfFileHelper.SlnfExtension))
+ {
+ RemoveProjectsFromSolutionFilter(solutionFileFullPath, relativeProjectPaths);
+ }
+ else
+ {
+ RemoveProjectsAsync(solutionFileFullPath, relativeProjectPaths, CancellationToken.None).GetAwaiter().GetResult();
+ }
return 0;
}
catch (Exception ex) when (ex is not GracefulException)
@@ -124,10 +132,41 @@ private static async Task RemoveProjectsAsync(string solutionFileFullPath, IEnum
{
solution.RemoveFolder(folder);
// After removal, adjust index and continue to avoid skipping folders after removal
- i--;
+ i--;
}
}
await serializer.SaveAsync(solutionFileFullPath, solution, cancellationToken);
}
+
+ private static void RemoveProjectsFromSolutionFilter(string slnfFileFullPath, IEnumerable projectPaths)
+ {
+ // Load the filtered solution to get the parent solution path and existing projects
+ SolutionModel filteredSolution = SlnFileFactory.CreateFromFilteredSolutionFile(slnfFileFullPath);
+ string parentSolutionPath = filteredSolution.Description!; // The parent solution path is stored in Description
+
+ // Get existing projects in the filter
+ // Use case-insensitive comparer on Windows for file path comparison
+ var existingProjects = filteredSolution.SolutionProjects.Select(p => p.FilePath).ToHashSet();
+
+ // Remove specified projects
+ foreach (var projectPath in projectPaths)
+ {
+ // Normalize the path to be relative to parent solution
+ string normalizedPath = projectPath;
+
+ // Try to find and remove the project
+ if (existingProjects.Remove(normalizedPath))
+ {
+ Reporter.Output.WriteLine(CliStrings.ProjectRemovedFromTheSolution, normalizedPath);
+ }
+ else
+ {
+ Reporter.Output.WriteLine(CliStrings.ProjectNotFoundInTheSolution, normalizedPath);
+ }
+ }
+
+ // Save updated filter
+ SlnfFileHelper.SaveSolutionFilter(slnfFileFullPath, parentSolutionPath, existingProjects.OrderBy(p => p));
+ }
}
diff --git a/src/Cli/dotnet/Commands/Test/MTP/TestApplicationActionQueue.cs b/src/Cli/dotnet/Commands/Test/MTP/TestApplicationActionQueue.cs
index 41ee319317e7..4496703ace28 100644
--- a/src/Cli/dotnet/Commands/Test/MTP/TestApplicationActionQueue.cs
+++ b/src/Cli/dotnet/Commands/Test/MTP/TestApplicationActionQueue.cs
@@ -78,7 +78,7 @@ private async Task Read(BuildOptions buildOptions, TestOptions testOptions, Term
{
result = ExitCode.GenericFailure;
}
-
+
lock (_lock)
{
if (_aggregateExitCode is null)
diff --git a/src/Cli/dotnet/Commands/Test/VSTest/TestCommand.cs b/src/Cli/dotnet/Commands/Test/VSTest/TestCommand.cs
index 2dc2bc8b6906..8ec4d74f8564 100644
--- a/src/Cli/dotnet/Commands/Test/VSTest/TestCommand.cs
+++ b/src/Cli/dotnet/Commands/Test/VSTest/TestCommand.cs
@@ -179,7 +179,7 @@ private static int ForwardToVSTestConsole(ParseResult parseResult, string[] args
public static TestCommand FromArgs(string[] args, string? testSessionCorrelationId = null, string? msbuildPath = null)
{
- var parseResult = Parser.Parse(["dotnet", "test", ..args]);
+ var parseResult = Parser.Parse(["dotnet", "test", .. args]);
// settings parameters are after -- (including --), these should not be considered by the parser
string[] settings = [.. args.SkipWhile(a => a != "--")];
@@ -266,7 +266,8 @@ private static TestCommand FromParseResult(ParseResult result, string[] settings
Dictionary variables = VSTestForwardingApp.GetVSTestRootVariables();
- foreach (var (rootVariableName, rootValue) in variables) {
+ foreach (var (rootVariableName, rootValue) in variables)
+ {
testCommand.EnvironmentVariable(rootVariableName, rootValue);
VSTestTrace.SafeWriteTrace(() => $"Root variable set {rootVariableName}:{rootValue}");
}
diff --git a/src/Cli/dotnet/Commands/Test/VSTest/VSTestForwardingApp.cs b/src/Cli/dotnet/Commands/Test/VSTest/VSTestForwardingApp.cs
index fb81e15466f9..26a021485c97 100644
--- a/src/Cli/dotnet/Commands/Test/VSTest/VSTestForwardingApp.cs
+++ b/src/Cli/dotnet/Commands/Test/VSTest/VSTestForwardingApp.cs
@@ -20,7 +20,7 @@ public VSTestForwardingApp(IEnumerable argsToForward)
WithEnvironmentVariable(rootVariableName, rootValue);
VSTestTrace.SafeWriteTrace(() => $"Root variable set {rootVariableName}:{rootValue}");
}
-
+
VSTestTrace.SafeWriteTrace(() => $"Forwarding to '{GetVSTestExePath()}' with args \"{argsToForward?.Aggregate((a, b) => $"{a} | {b}")}\"");
}
diff --git a/src/Cli/dotnet/Commands/Tool/List/ToolListJsonHelper.cs b/src/Cli/dotnet/Commands/Tool/List/ToolListJsonHelper.cs
index 4f5f01726fa2..e45176ae4a1b 100644
--- a/src/Cli/dotnet/Commands/Tool/List/ToolListJsonHelper.cs
+++ b/src/Cli/dotnet/Commands/Tool/List/ToolListJsonHelper.cs
@@ -10,12 +10,12 @@ namespace Microsoft.DotNet.Cli.Commands.Tool.List;
internal sealed class VersionedDataContract
{
- ///
- /// The version of the JSON format for dotnet tool list.
- ///
+ ///
+ /// The version of the JSON format for dotnet tool list.
+ ///
[JsonPropertyName("version")]
public int Version { get; init; } = 1;
-
+
[JsonPropertyName("data")]
public required TContract Data { get; init; }
}
@@ -24,10 +24,10 @@ internal class ToolListJsonContract
{
[JsonPropertyName("packageId")]
public required string PackageId { get; init; }
-
+
[JsonPropertyName("version")]
public required string Version { get; init; }
-
+
[JsonPropertyName("commands")]
public required string[] Commands { get; init; }
}
diff --git a/src/Cli/dotnet/Commands/Tool/Restore/ToolPackageRestorer.cs b/src/Cli/dotnet/Commands/Tool/Restore/ToolPackageRestorer.cs
index b1c3b3f4ed52..1377a97cb006 100644
--- a/src/Cli/dotnet/Commands/Tool/Restore/ToolPackageRestorer.cs
+++ b/src/Cli/dotnet/Commands/Tool/Restore/ToolPackageRestorer.cs
@@ -109,7 +109,7 @@ private static bool ManifestCommandMatchesActualInPackage(
IReadOnlyList toolPackageCommands)
{
ToolCommandName[] commandsFromPackage = [.. toolPackageCommands.Select(t => t.Name)];
-return !commandsFromManifest.Any(cmd => !commandsFromPackage.Contains(cmd)) && !commandsFromPackage.Any(cmd => !commandsFromManifest.Contains(cmd));
+ return !commandsFromManifest.Any(cmd => !commandsFromPackage.Contains(cmd)) && !commandsFromPackage.Any(cmd => !commandsFromManifest.Contains(cmd));
}
public bool PackageHasBeenRestored(
diff --git a/src/Cli/dotnet/Commands/Tool/Store/StoreCommand.cs b/src/Cli/dotnet/Commands/Tool/Store/StoreCommand.cs
index 512480da7163..1345eb26f2c7 100644
--- a/src/Cli/dotnet/Commands/Tool/Store/StoreCommand.cs
+++ b/src/Cli/dotnet/Commands/Tool/Store/StoreCommand.cs
@@ -20,7 +20,7 @@ private StoreCommand(IEnumerable msbuildArgs, string msbuildPath = null)
public static StoreCommand FromArgs(string[] args, string msbuildPath = null)
{
- var result = Parser.Parse(["dotnet", "store", ..args]);
+ var result = Parser.Parse(["dotnet", "store", .. args]);
return FromParseResult(result, msbuildPath);
}
diff --git a/src/Cli/dotnet/Commands/Tool/Uninstall/ToolUninstallGlobalOrToolPathCommand.cs b/src/Cli/dotnet/Commands/Tool/Uninstall/ToolUninstallGlobalOrToolPathCommand.cs
index b523f4d47f4d..f8a02960c380 100644
--- a/src/Cli/dotnet/Commands/Tool/Uninstall/ToolUninstallGlobalOrToolPathCommand.cs
+++ b/src/Cli/dotnet/Commands/Tool/Uninstall/ToolUninstallGlobalOrToolPathCommand.cs
@@ -70,7 +70,7 @@ public override int Execute()
TransactionalAction.Run(() =>
{
shellShimRepository.RemoveShim(package.Command);
-
+
toolPackageUninstaller.Uninstall(package.PackageDirectory);
});
diff --git a/src/Cli/dotnet/Commands/Tool/Update/ToolUpdateGlobalOrToolPathCommand.cs b/src/Cli/dotnet/Commands/Tool/Update/ToolUpdateGlobalOrToolPathCommand.cs
index b8880fbe9e61..0afc696e73ae 100644
--- a/src/Cli/dotnet/Commands/Tool/Update/ToolUpdateGlobalOrToolPathCommand.cs
+++ b/src/Cli/dotnet/Commands/Tool/Update/ToolUpdateGlobalOrToolPathCommand.cs
@@ -4,12 +4,12 @@
#nullable disable
using System.CommandLine;
+using Microsoft.DotNet.Cli.Commands.Tool.Install;
+using Microsoft.DotNet.Cli.ShellShim;
+using Microsoft.DotNet.Cli.ToolPackage;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.Extensions.EnvironmentAbstractions;
-using Microsoft.DotNet.Cli.ToolPackage;
using CreateShellShimRepository = Microsoft.DotNet.Cli.Commands.Tool.Install.CreateShellShimRepository;
-using Microsoft.DotNet.Cli.ShellShim;
-using Microsoft.DotNet.Cli.Commands.Tool.Install;
namespace Microsoft.DotNet.Cli.Commands.Tool.Update;
diff --git a/src/Cli/dotnet/Commands/Workload/History/WorkloadHistoryCommand.cs b/src/Cli/dotnet/Commands/Workload/History/WorkloadHistoryCommand.cs
index b4eb5e84b389..dbc522dd4ff1 100644
--- a/src/Cli/dotnet/Commands/Workload/History/WorkloadHistoryCommand.cs
+++ b/src/Cli/dotnet/Commands/Workload/History/WorkloadHistoryCommand.cs
@@ -4,11 +4,11 @@
#nullable disable
using System.CommandLine;
+using Microsoft.Deployment.DotNet.Releases;
+using Microsoft.DotNet.Cli.Commands.Workload.Install;
using Microsoft.DotNet.Cli.NuGetPackageDownloader;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.NET.Sdk.WorkloadManifestReader;
-using Microsoft.Deployment.DotNet.Releases;
-using Microsoft.DotNet.Cli.Commands.Workload.Install;
namespace Microsoft.DotNet.Cli.Commands.Workload.History;
diff --git a/src/Cli/dotnet/Commands/Workload/Restore/WorkloadRestoreCommand.cs b/src/Cli/dotnet/Commands/Workload/Restore/WorkloadRestoreCommand.cs
index b605a34d3caa..8451a9de5457 100644
--- a/src/Cli/dotnet/Commands/Workload/Restore/WorkloadRestoreCommand.cs
+++ b/src/Cli/dotnet/Commands/Workload/Restore/WorkloadRestoreCommand.cs
@@ -63,7 +63,7 @@ public override int Execute()
});
workloadInstaller.Shutdown();
-
+
return 0;
}
diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.cs.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.cs.xlf
index aca54ec20616..44c673cf91c0 100644
--- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.cs.xlf
+++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.cs.xlf
@@ -1631,6 +1631,11 @@ Cílem projektu je více architektur. Pomocí parametru {0} určete, která arch
.slnx soubor {0} byl vygenerován.
+
+ Solution filter files (.slnf) do not support the --in-root or --solution-folder options.
+ Solution filter files (.slnf) do not support the --in-root or --solution-folder options.
+
+
The --solution-folder and --in-root options cannot be used together; use only one of the options.
Parametry --solution-folder a --in-root nejdou použít společně; použijte jenom jeden z nich.
diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.de.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.de.xlf
index 553a73dc90fc..0719d287f094 100644
--- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.de.xlf
+++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.de.xlf
@@ -1631,6 +1631,11 @@ Ihr Projekt verwendet mehrere Zielframeworks. Geben Sie über "{0}" an, welches
Die SLNX-Datei "{0}" wurde generiert.
+
+ Solution filter files (.slnf) do not support the --in-root or --solution-folder options.
+ Solution filter files (.slnf) do not support the --in-root or --solution-folder options.
+
+
The --solution-folder and --in-root options cannot be used together; use only one of the options.
Die Optionen "--solution-folder" und "--in-root" können nicht zusammen verwendet werden; verwenden Sie nur eine der Optionen.
diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.es.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.es.xlf
index 91c32ff85daa..47c50cd46876 100644
--- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.es.xlf
+++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.es.xlf
@@ -1631,6 +1631,11 @@ Su proyecto tiene como destino varias plataformas. Especifique la que quiere usa
Archivo .slnx {0} generado.
+
+ Solution filter files (.slnf) do not support the --in-root or --solution-folder options.
+ Solution filter files (.slnf) do not support the --in-root or --solution-folder options.
+
+
The --solution-folder and --in-root options cannot be used together; use only one of the options.
Las opciones --in-root y --solution-folder no se pueden usar juntas. Utilice solo una de ellas.
diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.fr.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.fr.xlf
index 63eae5969a2f..c2eceda0a1fb 100644
--- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.fr.xlf
+++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.fr.xlf
@@ -1631,6 +1631,11 @@ Votre projet cible plusieurs frameworks. Spécifiez le framework à exécuter à
Fichier .slnx {0} généré.
+
+ Solution filter files (.slnf) do not support the --in-root or --solution-folder options.
+ Solution filter files (.slnf) do not support the --in-root or --solution-folder options.
+
+
The --solution-folder and --in-root options cannot be used together; use only one of the options.
N'utilisez pas en même temps les options --solution-folder et --in-root. Utilisez uniquement l'une des deux options.
diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.it.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.it.xlf
index 916c4b11c60e..f64197fa190d 100644
--- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.it.xlf
+++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.it.xlf
@@ -1631,6 +1631,11 @@ Il progetto è destinato a più framework. Specificare il framework da eseguire
File .slnx {0} generato.
+
+ Solution filter files (.slnf) do not support the --in-root or --solution-folder options.
+ Solution filter files (.slnf) do not support the --in-root or --solution-folder options.
+
+
The --solution-folder and --in-root options cannot be used together; use only one of the options.
Non è possibile usare contemporaneamente le opzioni --solution-folder e --in-root. Usare una sola delle opzioni.
diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ja.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ja.xlf
index 0f07011cd52d..1eea643b54f1 100644
--- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ja.xlf
+++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ja.xlf
@@ -1631,6 +1631,11 @@ Your project targets multiple frameworks. Specify which framework to run using '
.slnx ファイル {0} が生成されました。
+
+ Solution filter files (.slnf) do not support the --in-root or --solution-folder options.
+ Solution filter files (.slnf) do not support the --in-root or --solution-folder options.
+
+
The --solution-folder and --in-root options cannot be used together; use only one of the options.
--solution-folder オプションと --in-root オプションを一緒に使用することはできません。いずれかのオプションだけを使用します。
diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ko.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ko.xlf
index 20b68d1a3182..7ab4346d6bd4 100644
--- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ko.xlf
+++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ko.xlf
@@ -1631,6 +1631,11 @@ Your project targets multiple frameworks. Specify which framework to run using '
.slnx 파일 {0}이(가) 생성되었습니다.
+
+ Solution filter files (.slnf) do not support the --in-root or --solution-folder options.
+ Solution filter files (.slnf) do not support the --in-root or --solution-folder options.
+
+
The --solution-folder and --in-root options cannot be used together; use only one of the options.
--solution-folder와 --in-root 옵션을 함께 사용할 수 없습니다. 옵션을 하나만 사용하세요.
diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pl.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pl.xlf
index dda65dfdf1e3..9d224a5f711b 100644
--- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pl.xlf
+++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pl.xlf
@@ -1631,6 +1631,11 @@ Projekt ma wiele platform docelowych. Określ platformę do uruchomienia przy u
Wygenerowano plik .slnx {0}.
+
+ Solution filter files (.slnf) do not support the --in-root or --solution-folder options.
+ Solution filter files (.slnf) do not support the --in-root or --solution-folder options.
+
+
The --solution-folder and --in-root options cannot be used together; use only one of the options.
Opcji --solution-folder i --in-root nie można używać razem; użyj tylko jednej z tych opcji.
diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pt-BR.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pt-BR.xlf
index c32d0c5c5c99..8c6a47bde7c1 100644
--- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pt-BR.xlf
+++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.pt-BR.xlf
@@ -1631,6 +1631,11 @@ Ele tem diversas estruturas como destino. Especifique que estrutura executar usa
Arquivo .slnx {0} gerado.
+
+ Solution filter files (.slnf) do not support the --in-root or --solution-folder options.
+ Solution filter files (.slnf) do not support the --in-root or --solution-folder options.
+
+
The --solution-folder and --in-root options cannot be used together; use only one of the options.
As opções --solution-folder e --in-root não podem ser usadas juntas. Use somente uma das opções.
diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ru.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ru.xlf
index 4d108ae99863..959a58cded78 100644
--- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ru.xlf
+++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.ru.xlf
@@ -1631,6 +1631,11 @@ Your project targets multiple frameworks. Specify which framework to run using '
Файл SLNX {0} создан.
+
+ Solution filter files (.slnf) do not support the --in-root or --solution-folder options.
+ Solution filter files (.slnf) do not support the --in-root or --solution-folder options.
+
+
The --solution-folder and --in-root options cannot be used together; use only one of the options.
Параметры --solution-folder и --in-root options не могут быть использованы одновременно; оставьте только один из параметров.
diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.tr.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.tr.xlf
index 5ec84a882619..55109daa7e69 100644
--- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.tr.xlf
+++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.tr.xlf
@@ -1631,6 +1631,11 @@ Projeniz birden fazla Framework'ü hedefliyor. '{0}' kullanarak hangi Framework'
.slnx dosyası {0} oluşturuldu.
+
+ Solution filter files (.slnf) do not support the --in-root or --solution-folder options.
+ Solution filter files (.slnf) do not support the --in-root or --solution-folder options.
+
+
The --solution-folder and --in-root options cannot be used together; use only one of the options.
--solution-folder ve --in-root seçenekleri birlikte kullanılamaz; seçeneklerden yalnızca birini kullanın.
diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hans.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hans.xlf
index f1756cdf26a6..74a02b86242e 100644
--- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hans.xlf
+++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hans.xlf
@@ -1631,6 +1631,11 @@ Your project targets multiple frameworks. Specify which framework to run using '
已生成 .slnx 文件 {0}。
+
+ Solution filter files (.slnf) do not support the --in-root or --solution-folder options.
+ Solution filter files (.slnf) do not support the --in-root or --solution-folder options.
+
+
The --solution-folder and --in-root options cannot be used together; use only one of the options.
--solution-folder 和 --in-root 选项不能一起使用;请仅使用其中一个选项。
diff --git a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hant.xlf b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hant.xlf
index e739b31721f7..29d35fb26239 100644
--- a/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hant.xlf
+++ b/src/Cli/dotnet/Commands/xlf/CliCommandStrings.zh-Hant.xlf
@@ -1631,6 +1631,11 @@ Your project targets multiple frameworks. Specify which framework to run using '
產生的 .slnx 檔案 {0}。
+
+ Solution filter files (.slnf) do not support the --in-root or --solution-folder options.
+ Solution filter files (.slnf) do not support the --in-root or --solution-folder options.
+
+
The --solution-folder and --in-root options cannot be used together; use only one of the options.
不可同時使用 --solution-folder 和 --in-root 選項; 請只使用其中一個選項。
diff --git a/src/Cli/dotnet/DotNetCommandFactory.cs b/src/Cli/dotnet/DotNetCommandFactory.cs
index ea5eb912e8f6..dcb70b05e6c9 100644
--- a/src/Cli/dotnet/DotNetCommandFactory.cs
+++ b/src/Cli/dotnet/DotNetCommandFactory.cs
@@ -38,7 +38,7 @@ private static bool TryGetBuiltInCommand(string commandName, out Func Parser.Invoke([commandName, ..args]);
+ commandFunc = (args) => Parser.Invoke([commandName, .. args]);
return true;
}
commandFunc = null;
diff --git a/src/Cli/dotnet/NugetPackageDownloader/INuGetPackageDownloader.cs b/src/Cli/dotnet/NugetPackageDownloader/INuGetPackageDownloader.cs
index a5e54ba06bb9..0c606c61dbf7 100644
--- a/src/Cli/dotnet/NugetPackageDownloader/INuGetPackageDownloader.cs
+++ b/src/Cli/dotnet/NugetPackageDownloader/INuGetPackageDownloader.cs
@@ -43,4 +43,4 @@ Task GetBestPackageVersionAsync(PackageId packageId,
Task<(NuGetVersion version, PackageSource source)> GetBestPackageVersionAndSourceAsync(PackageId packageId,
VersionRange versionRange,
PackageSourceLocation packageSourceLocation = null);
-}
+}
diff --git a/src/Cli/dotnet/NugetPackageDownloader/NuGetPackageDownloader.cs b/src/Cli/dotnet/NugetPackageDownloader/NuGetPackageDownloader.cs
index a311e88c646d..a0ce16fe6d0b 100644
--- a/src/Cli/dotnet/NugetPackageDownloader/NuGetPackageDownloader.cs
+++ b/src/Cli/dotnet/NugetPackageDownloader/NuGetPackageDownloader.cs
@@ -75,7 +75,7 @@ public NuGetPackageDownloader(
_retryTimer = timer;
_sourceRepositories = new();
// If windows or env variable is set, verify signatures
- _verifySignatures = verifySignatures && (OperatingSystem.IsWindows() ? true
+ _verifySignatures = verifySignatures && (OperatingSystem.IsWindows() ? true
: bool.TryParse(Environment.GetEnvironmentVariable(NuGetSignatureVerificationEnabler.DotNetNuGetSignatureVerification), out var shouldVerifySignature) ? shouldVerifySignature : OperatingSystem.IsLinux());
_cacheSettings = new SourceCacheContext
@@ -122,7 +122,7 @@ public async Task DownloadPackageAsync(PackageId packageId,
throw new ArgumentException($"Package download folder must be specified either via {nameof(NuGetPackageDownloader)} constructor or via {nameof(downloadFolder)} method argument.");
}
var pathResolver = new VersionFolderPathResolver(resolvedDownloadFolder);
-
+
string nupkgPath = pathResolver.GetPackageFilePath(packageId.ToString(), resolvedPackageVersion);
Directory.CreateDirectory(Path.GetDirectoryName(nupkgPath));
diff --git a/src/Cli/dotnet/ReleasePropertyProjectLocator.cs b/src/Cli/dotnet/ReleasePropertyProjectLocator.cs
index 582eaf14d36d..7755fa463787 100644
--- a/src/Cli/dotnet/ReleasePropertyProjectLocator.cs
+++ b/src/Cli/dotnet/ReleasePropertyProjectLocator.cs
@@ -216,7 +216,8 @@ public readonly struct DependentCommandOptions(IEnumerable? slnOrProject
{
return projectData;
}
- };
+ }
+ ;
return null;
}
diff --git a/src/Cli/dotnet/SlnFileFactory.cs b/src/Cli/dotnet/SlnFileFactory.cs
index 788752df3cff..723195a66e36 100644
--- a/src/Cli/dotnet/SlnFileFactory.cs
+++ b/src/Cli/dotnet/SlnFileFactory.cs
@@ -91,6 +91,8 @@ public static SolutionModel CreateFromFilteredSolutionFile(string filteredSoluti
{
JsonElement root = JsonDocument.Parse(File.ReadAllText(filteredSolutionPath)).RootElement;
originalSolutionPath = Uri.UnescapeDataString(root.GetProperty("solution").GetProperty("path").GetString());
+ // Normalize path separators to OS-specific for cross-platform compatibility
+ originalSolutionPath = SlnfFileHelper.NormalizePathSeparatorsToOS(originalSolutionPath);
filteredSolutionProjectPaths = [.. root.GetProperty("solution").GetProperty("projects").EnumerateArray().Select(p => p.GetString())];
originalSolutionPathAbsolute = Path.GetFullPath(originalSolutionPath, Path.GetDirectoryName(filteredSolutionPath));
}
diff --git a/src/Cli/dotnet/SlnfFileHelper.cs b/src/Cli/dotnet/SlnfFileHelper.cs
new file mode 100644
index 000000000000..87d93b75d3d5
--- /dev/null
+++ b/src/Cli/dotnet/SlnfFileHelper.cs
@@ -0,0 +1,131 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable disable
+
+using System;
+using System.IO;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Microsoft.DotNet.Cli.Utils;
+
+namespace Microsoft.DotNet.Cli;
+
+///
+/// Utilities for working with solution filter (.slnf) files
+///
+public static class SlnfFileHelper
+{
+ ///
+ /// File extension for solution filter files
+ ///
+ public const string SlnfExtension = ".slnf";
+
+ ///
+ /// Normalizes path separators from backslashes to the OS-specific directory separator
+ ///
+ /// The path to normalize
+ /// Path with OS-specific separators
+ public static string NormalizePathSeparatorsToOS(string path)
+ {
+ return path.Replace('\\', Path.DirectorySeparatorChar).Replace('/', Path.DirectorySeparatorChar);
+ }
+
+ ///
+ /// Normalizes path separators to backslashes (as used in .slnf files)
+ ///
+ /// The path to normalize
+ /// Path with backslash separators
+ public static string NormalizePathSeparatorsToBackslash(string path)
+ {
+ return path.Replace(Path.DirectorySeparatorChar, '\\');
+ }
+
+ private class SlnfSolution
+ {
+ [JsonPropertyName("path")]
+ public string Path { get; set; }
+
+ [JsonPropertyName("projects")]
+ public List Projects { get; set; } = new();
+ }
+
+ private class SlnfRoot
+ {
+ [JsonPropertyName("solution")]
+ public SlnfSolution Solution { get; set; } = new();
+ }
+
+ ///
+ /// Creates a new solution filter file
+ ///
+ /// Path to the solution filter file to create
+ /// Path to the parent solution file
+ /// List of project paths to include (relative to the parent solution)
+ public static void CreateSolutionFilter(string slnfPath, string parentSolutionPath, IEnumerable projects = null)
+ {
+ var slnfDirectory = Path.GetDirectoryName(Path.GetFullPath(slnfPath)) ?? string.Empty;
+ var parentSolutionFullPath = Path.GetFullPath(parentSolutionPath, slnfDirectory);
+ var relativeSolutionPath = Path.GetRelativePath(slnfDirectory, parentSolutionFullPath);
+
+ // Normalize path separators to backslashes (as per slnf format)
+ relativeSolutionPath = NormalizePathSeparatorsToBackslash(relativeSolutionPath);
+
+ var root = new SlnfRoot
+ {
+ Solution = new SlnfSolution
+ {
+ Path = relativeSolutionPath,
+ Projects = projects?.Select(NormalizePathSeparatorsToBackslash).ToList() ?? new List()
+ }
+ };
+
+ var options = new JsonSerializerOptions
+ {
+ WriteIndented = true,
+ DefaultIgnoreCondition = JsonIgnoreCondition.Never
+ };
+
+ var json = JsonSerializer.Serialize(root, options);
+ File.WriteAllText(slnfPath, json);
+ }
+
+ ///
+ /// Saves a solution filter file with the given projects
+ ///
+ /// Path to the solution filter file
+ /// Path to the parent solution (stored in the slnf file)
+ /// List of project paths (relative to the parent solution)
+ public static void SaveSolutionFilter(string slnfPath, string parentSolutionPath, IEnumerable projects)
+ {
+ var slnfDirectory = Path.GetDirectoryName(Path.GetFullPath(slnfPath)) ?? string.Empty;
+
+ // Normalize the parent solution path to be relative to the slnf file
+ var relativeSolutionPath = parentSolutionPath;
+ if (Path.IsPathRooted(parentSolutionPath))
+ {
+ relativeSolutionPath = Path.GetRelativePath(slnfDirectory, parentSolutionPath);
+ }
+
+ // Normalize path separators to backslashes (as per slnf format)
+ relativeSolutionPath = NormalizePathSeparatorsToBackslash(relativeSolutionPath);
+
+ var root = new SlnfRoot
+ {
+ Solution = new SlnfSolution
+ {
+ Path = relativeSolutionPath,
+ Projects = projects.Select(NormalizePathSeparatorsToBackslash).ToList()
+ }
+ };
+
+ var options = new JsonSerializerOptions
+ {
+ WriteIndented = true,
+ DefaultIgnoreCondition = JsonIgnoreCondition.Never
+ };
+
+ var json = JsonSerializer.Serialize(root, options);
+ File.WriteAllText(slnfPath, json);
+ }
+}
diff --git a/src/Cli/dotnet/Telemetry/DevDeviceIDGetter.cs b/src/Cli/dotnet/Telemetry/DevDeviceIDGetter.cs
index 015af6723629..7960deb22cc7 100644
--- a/src/Cli/dotnet/Telemetry/DevDeviceIDGetter.cs
+++ b/src/Cli/dotnet/Telemetry/DevDeviceIDGetter.cs
@@ -85,11 +85,11 @@ private static void CacheDeviceId(string deviceId)
// Cache device Id in Windows registry matching the OS architecture
using (RegistryKey baseKey = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64))
{
- using(var key = baseKey.CreateSubKey(@"SOFTWARE\Microsoft\DeveloperTools"))
+ using (var key = baseKey.CreateSubKey(@"SOFTWARE\Microsoft\DeveloperTools"))
{
if (key != null)
{
- key.SetValue("deviceid", deviceId);
+ key.SetValue("deviceid", deviceId);
}
}
}
diff --git a/src/Cli/dotnet/Telemetry/Telemetry.cs b/src/Cli/dotnet/Telemetry/Telemetry.cs
index d9c3a59bd8a1..38f0d1c7ca19 100644
--- a/src/Cli/dotnet/Telemetry/Telemetry.cs
+++ b/src/Cli/dotnet/Telemetry/Telemetry.cs
@@ -258,6 +258,6 @@ static IDictionary Combine(IDictionary
{
eventMeasurements[measurement.Key] = measurement.Value;
}
- return eventMeasurements;
- }
+ return eventMeasurements;
+ }
}
diff --git a/src/Cli/dotnet/ToolPackage/ToolConfiguration.cs b/src/Cli/dotnet/ToolPackage/ToolConfiguration.cs
index 641c8c583a7c..9da8558f5384 100644
--- a/src/Cli/dotnet/ToolPackage/ToolConfiguration.cs
+++ b/src/Cli/dotnet/ToolPackage/ToolConfiguration.cs
@@ -62,7 +62,7 @@ private static void EnsureNoLeadingDot(string commandName)
}
}
-
+
public string CommandName { get; }
public string ToolAssemblyEntryPoint { get; }
diff --git a/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/dotnetcli.host.json b/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/dotnetcli.host.json
new file mode 100644
index 000000000000..6bc180f304f1
--- /dev/null
+++ b/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/dotnetcli.host.json
@@ -0,0 +1,9 @@
+{
+ "$schema": "http://json.schemastore.org/dotnetcli.host",
+ "symbolInfo": {
+ "ParentSolution": {
+ "longName": "parent-solution",
+ "shortName": "s"
+ }
+ }
+}
diff --git a/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/localize/templatestrings.cs.json b/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/localize/templatestrings.cs.json
new file mode 100644
index 000000000000..535e0d7b8229
--- /dev/null
+++ b/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/localize/templatestrings.cs.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "Solution Filter File",
+ "description": "Create a solution filter file that references a parent solution",
+ "symbols/ParentSolution/displayName": "Parent solution file",
+ "symbols/ParentSolution/description": "The parent solution file (sln or slnx) that this filter references (default: same name with .slnx extension)."
+}
\ No newline at end of file
diff --git a/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/localize/templatestrings.de.json b/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/localize/templatestrings.de.json
new file mode 100644
index 000000000000..535e0d7b8229
--- /dev/null
+++ b/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/localize/templatestrings.de.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "Solution Filter File",
+ "description": "Create a solution filter file that references a parent solution",
+ "symbols/ParentSolution/displayName": "Parent solution file",
+ "symbols/ParentSolution/description": "The parent solution file (sln or slnx) that this filter references (default: same name with .slnx extension)."
+}
\ No newline at end of file
diff --git a/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/localize/templatestrings.en.json b/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/localize/templatestrings.en.json
new file mode 100644
index 000000000000..535e0d7b8229
--- /dev/null
+++ b/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/localize/templatestrings.en.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "Solution Filter File",
+ "description": "Create a solution filter file that references a parent solution",
+ "symbols/ParentSolution/displayName": "Parent solution file",
+ "symbols/ParentSolution/description": "The parent solution file (sln or slnx) that this filter references (default: same name with .slnx extension)."
+}
\ No newline at end of file
diff --git a/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/localize/templatestrings.es.json b/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/localize/templatestrings.es.json
new file mode 100644
index 000000000000..535e0d7b8229
--- /dev/null
+++ b/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/localize/templatestrings.es.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "Solution Filter File",
+ "description": "Create a solution filter file that references a parent solution",
+ "symbols/ParentSolution/displayName": "Parent solution file",
+ "symbols/ParentSolution/description": "The parent solution file (sln or slnx) that this filter references (default: same name with .slnx extension)."
+}
\ No newline at end of file
diff --git a/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/localize/templatestrings.fr.json b/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/localize/templatestrings.fr.json
new file mode 100644
index 000000000000..535e0d7b8229
--- /dev/null
+++ b/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/localize/templatestrings.fr.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "Solution Filter File",
+ "description": "Create a solution filter file that references a parent solution",
+ "symbols/ParentSolution/displayName": "Parent solution file",
+ "symbols/ParentSolution/description": "The parent solution file (sln or slnx) that this filter references (default: same name with .slnx extension)."
+}
\ No newline at end of file
diff --git a/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/localize/templatestrings.it.json b/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/localize/templatestrings.it.json
new file mode 100644
index 000000000000..535e0d7b8229
--- /dev/null
+++ b/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/localize/templatestrings.it.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "Solution Filter File",
+ "description": "Create a solution filter file that references a parent solution",
+ "symbols/ParentSolution/displayName": "Parent solution file",
+ "symbols/ParentSolution/description": "The parent solution file (sln or slnx) that this filter references (default: same name with .slnx extension)."
+}
\ No newline at end of file
diff --git a/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/localize/templatestrings.ja.json b/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/localize/templatestrings.ja.json
new file mode 100644
index 000000000000..535e0d7b8229
--- /dev/null
+++ b/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/localize/templatestrings.ja.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "Solution Filter File",
+ "description": "Create a solution filter file that references a parent solution",
+ "symbols/ParentSolution/displayName": "Parent solution file",
+ "symbols/ParentSolution/description": "The parent solution file (sln or slnx) that this filter references (default: same name with .slnx extension)."
+}
\ No newline at end of file
diff --git a/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/localize/templatestrings.ko.json b/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/localize/templatestrings.ko.json
new file mode 100644
index 000000000000..535e0d7b8229
--- /dev/null
+++ b/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/localize/templatestrings.ko.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "Solution Filter File",
+ "description": "Create a solution filter file that references a parent solution",
+ "symbols/ParentSolution/displayName": "Parent solution file",
+ "symbols/ParentSolution/description": "The parent solution file (sln or slnx) that this filter references (default: same name with .slnx extension)."
+}
\ No newline at end of file
diff --git a/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/localize/templatestrings.pl.json b/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/localize/templatestrings.pl.json
new file mode 100644
index 000000000000..535e0d7b8229
--- /dev/null
+++ b/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/localize/templatestrings.pl.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "Solution Filter File",
+ "description": "Create a solution filter file that references a parent solution",
+ "symbols/ParentSolution/displayName": "Parent solution file",
+ "symbols/ParentSolution/description": "The parent solution file (sln or slnx) that this filter references (default: same name with .slnx extension)."
+}
\ No newline at end of file
diff --git a/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/localize/templatestrings.pt-BR.json b/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/localize/templatestrings.pt-BR.json
new file mode 100644
index 000000000000..535e0d7b8229
--- /dev/null
+++ b/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/localize/templatestrings.pt-BR.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "Solution Filter File",
+ "description": "Create a solution filter file that references a parent solution",
+ "symbols/ParentSolution/displayName": "Parent solution file",
+ "symbols/ParentSolution/description": "The parent solution file (sln or slnx) that this filter references (default: same name with .slnx extension)."
+}
\ No newline at end of file
diff --git a/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/localize/templatestrings.ru.json b/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/localize/templatestrings.ru.json
new file mode 100644
index 000000000000..535e0d7b8229
--- /dev/null
+++ b/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/localize/templatestrings.ru.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "Solution Filter File",
+ "description": "Create a solution filter file that references a parent solution",
+ "symbols/ParentSolution/displayName": "Parent solution file",
+ "symbols/ParentSolution/description": "The parent solution file (sln or slnx) that this filter references (default: same name with .slnx extension)."
+}
\ No newline at end of file
diff --git a/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/localize/templatestrings.tr.json b/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/localize/templatestrings.tr.json
new file mode 100644
index 000000000000..535e0d7b8229
--- /dev/null
+++ b/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/localize/templatestrings.tr.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "Solution Filter File",
+ "description": "Create a solution filter file that references a parent solution",
+ "symbols/ParentSolution/displayName": "Parent solution file",
+ "symbols/ParentSolution/description": "The parent solution file (sln or slnx) that this filter references (default: same name with .slnx extension)."
+}
\ No newline at end of file
diff --git a/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/localize/templatestrings.zh-Hans.json b/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/localize/templatestrings.zh-Hans.json
new file mode 100644
index 000000000000..535e0d7b8229
--- /dev/null
+++ b/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/localize/templatestrings.zh-Hans.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "Solution Filter File",
+ "description": "Create a solution filter file that references a parent solution",
+ "symbols/ParentSolution/displayName": "Parent solution file",
+ "symbols/ParentSolution/description": "The parent solution file (sln or slnx) that this filter references (default: same name with .slnx extension)."
+}
\ No newline at end of file
diff --git a/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/localize/templatestrings.zh-Hant.json b/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/localize/templatestrings.zh-Hant.json
new file mode 100644
index 000000000000..535e0d7b8229
--- /dev/null
+++ b/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/localize/templatestrings.zh-Hant.json
@@ -0,0 +1,7 @@
+{
+ "author": "Microsoft",
+ "name": "Solution Filter File",
+ "description": "Create a solution filter file that references a parent solution",
+ "symbols/ParentSolution/displayName": "Parent solution file",
+ "symbols/ParentSolution/description": "The parent solution file (sln or slnx) that this filter references (default: same name with .slnx extension)."
+}
\ No newline at end of file
diff --git a/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/template.json b/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/template.json
new file mode 100644
index 000000000000..7f33956aab45
--- /dev/null
+++ b/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/.template.config/template.json
@@ -0,0 +1,29 @@
+{
+ "$schema": "http://json.schemastore.org/template",
+ "author": "Microsoft",
+ "classifications": [
+ "Solution"
+ ],
+ "name": "Solution Filter File",
+ "generatorVersions": "[1.0.0.0-*)",
+ "description": "Create a solution filter file that references a parent solution",
+ "groupIdentity": "ItemSolutionFilter",
+ "precedence": "100",
+ "identity": "Microsoft.Standard.QuickStarts.SolutionFilter",
+ "shortName": [
+ "slnf",
+ "solutionfilter"
+ ],
+ "sourceName": "SolutionFilter1",
+ "symbols": {
+ "ParentSolution": {
+ "type": "parameter",
+ "displayName": "Parent solution file",
+ "description": "The parent solution file (sln or slnx) that this filter references (default: same name with .slnx extension).",
+ "datatype": "string",
+ "defaultValue": "SolutionFilter1.slnx",
+ "replaces": "SolutionFilter1.slnx"
+ }
+ },
+ "defaultName": "SolutionFilter1"
+}
diff --git a/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/SolutionFilter1.slnf b/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/SolutionFilter1.slnf
new file mode 100644
index 000000000000..d633922fb216
--- /dev/null
+++ b/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/SolutionFilter/SolutionFilter1.slnf
@@ -0,0 +1,6 @@
+{
+ "solution": {
+ "path": "SolutionFilter1.slnx",
+ "projects": []
+ }
+}
diff --git a/test/Microsoft.TemplateEngine.Cli.UnitTests/ParserTests/Approvals/TabCompletionTests.Create_GetAllSuggestions.verified.txt b/test/Microsoft.TemplateEngine.Cli.UnitTests/ParserTests/Approvals/TabCompletionTests.Create_GetAllSuggestions.verified.txt
index a6396361b7ce..2d74588d6f19 100644
--- a/test/Microsoft.TemplateEngine.Cli.UnitTests/ParserTests/Approvals/TabCompletionTests.Create_GetAllSuggestions.verified.txt
+++ b/test/Microsoft.TemplateEngine.Cli.UnitTests/ParserTests/Approvals/TabCompletionTests.Create_GetAllSuggestions.verified.txt
@@ -118,6 +118,13 @@
InsertText: sln,
Documentation: Create an empty solution containing no projects
},
+ {
+ Label: slnf,
+ Kind: Value,
+ SortText: slnf,
+ InsertText: slnf,
+ Documentation: Create a solution filter file that references a parent solution
+ },
{
Label: tool-manifest,
Kind: Value,
diff --git a/test/Microsoft.TemplateEngine.Cli.UnitTests/ParserTests/Approvals/TabCompletionTests.RootCommand_GetAllSuggestions.verified.txt b/test/Microsoft.TemplateEngine.Cli.UnitTests/ParserTests/Approvals/TabCompletionTests.RootCommand_GetAllSuggestions.verified.txt
index 1afda941c784..a18e0e26302c 100644
--- a/test/Microsoft.TemplateEngine.Cli.UnitTests/ParserTests/Approvals/TabCompletionTests.RootCommand_GetAllSuggestions.verified.txt
+++ b/test/Microsoft.TemplateEngine.Cli.UnitTests/ParserTests/Approvals/TabCompletionTests.RootCommand_GetAllSuggestions.verified.txt
@@ -118,6 +118,13 @@
InsertText: sln,
Documentation: Create an empty solution containing no projects
},
+ {
+ Label: slnf,
+ Kind: Value,
+ SortText: slnf,
+ InsertText: slnf,
+ Documentation: Create a solution filter file that references a parent solution
+ },
{
Label: tool-manifest,
Kind: Value,
diff --git a/test/TestAssets/TestProjects/TestAppWithSlnfFiles/App.slnf b/test/TestAssets/TestProjects/TestAppWithSlnfFiles/App.slnf
new file mode 100644
index 000000000000..34cef9585f66
--- /dev/null
+++ b/test/TestAssets/TestProjects/TestAppWithSlnfFiles/App.slnf
@@ -0,0 +1,8 @@
+{
+ "solution": {
+ "path": "App.slnx",
+ "projects": [
+ "src\\App\\App.csproj"
+ ]
+ }
+}
diff --git a/test/TestAssets/TestProjects/TestAppWithSlnfFiles/App.slnx b/test/TestAssets/TestProjects/TestAppWithSlnfFiles/App.slnx
new file mode 100644
index 000000000000..54df61baa606
--- /dev/null
+++ b/test/TestAssets/TestProjects/TestAppWithSlnfFiles/App.slnx
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/test/TestAssets/TestProjects/TestAppWithSlnfFiles/src/App/App.csproj b/test/TestAssets/TestProjects/TestAppWithSlnfFiles/src/App/App.csproj
new file mode 100644
index 000000000000..0361aa8cd36d
--- /dev/null
+++ b/test/TestAssets/TestProjects/TestAppWithSlnfFiles/src/App/App.csproj
@@ -0,0 +1,6 @@
+
+
+ Exe
+ net9.0
+
+
diff --git a/test/TestAssets/TestProjects/TestAppWithSlnfFiles/src/Lib/Lib.csproj b/test/TestAssets/TestProjects/TestAppWithSlnfFiles/src/Lib/Lib.csproj
new file mode 100644
index 000000000000..3043227ce00b
--- /dev/null
+++ b/test/TestAssets/TestProjects/TestAppWithSlnfFiles/src/Lib/Lib.csproj
@@ -0,0 +1,5 @@
+
+
+ net9.0
+
+
diff --git a/test/TestAssets/TestProjects/TestAppWithSlnfFiles/test/AppTests/AppTests.csproj b/test/TestAssets/TestProjects/TestAppWithSlnfFiles/test/AppTests/AppTests.csproj
new file mode 100644
index 000000000000..eb91b2d48523
--- /dev/null
+++ b/test/TestAssets/TestProjects/TestAppWithSlnfFiles/test/AppTests/AppTests.csproj
@@ -0,0 +1,9 @@
+
+
+ net9.0
+
+
+
+
+
+
diff --git a/test/dotnet-new.IntegrationTests/Approvals/AllCommonItemsCreate.-o#Solution-Filter-File#-n#item#--parent-solution#Parent.slnx.verified/Solution-Filter-File/item.slnf b/test/dotnet-new.IntegrationTests/Approvals/AllCommonItemsCreate.-o#Solution-Filter-File#-n#item#--parent-solution#Parent.slnx.verified/Solution-Filter-File/item.slnf
new file mode 100644
index 000000000000..e66e7dc8b150
--- /dev/null
+++ b/test/dotnet-new.IntegrationTests/Approvals/AllCommonItemsCreate.-o#Solution-Filter-File#-n#item#--parent-solution#Parent.slnx.verified/Solution-Filter-File/item.slnf
@@ -0,0 +1,6 @@
+{
+ "solution": {
+ "path": "Parent.slnx",
+ "projects": []
+ }
+}
diff --git a/test/dotnet-new.IntegrationTests/Approvals/AllCommonItemsCreate.-o#Solution-Filter-File#-n#item#--parent-solution#Parent.slnx.verified/std-streams/stdout.txt b/test/dotnet-new.IntegrationTests/Approvals/AllCommonItemsCreate.-o#Solution-Filter-File#-n#item#--parent-solution#Parent.slnx.verified/std-streams/stdout.txt
new file mode 100644
index 000000000000..70cab17a4b13
--- /dev/null
+++ b/test/dotnet-new.IntegrationTests/Approvals/AllCommonItemsCreate.-o#Solution-Filter-File#-n#item#--parent-solution#Parent.slnx.verified/std-streams/stdout.txt
@@ -0,0 +1 @@
+The template "%TEMPLATE_NAME%" was created successfully.
\ No newline at end of file
diff --git a/test/dotnet-new.IntegrationTests/Approvals/AllCommonItemsCreate.-o#Solution-Filter-File#-n#item#-s#Parent.slnx.verified/Solution-Filter-File/item.slnf b/test/dotnet-new.IntegrationTests/Approvals/AllCommonItemsCreate.-o#Solution-Filter-File#-n#item#-s#Parent.slnx.verified/Solution-Filter-File/item.slnf
new file mode 100644
index 000000000000..e66e7dc8b150
--- /dev/null
+++ b/test/dotnet-new.IntegrationTests/Approvals/AllCommonItemsCreate.-o#Solution-Filter-File#-n#item#-s#Parent.slnx.verified/Solution-Filter-File/item.slnf
@@ -0,0 +1,6 @@
+{
+ "solution": {
+ "path": "Parent.slnx",
+ "projects": []
+ }
+}
diff --git a/test/dotnet-new.IntegrationTests/Approvals/AllCommonItemsCreate.-o#Solution-Filter-File#-n#item#-s#Parent.slnx.verified/std-streams/stdout.txt b/test/dotnet-new.IntegrationTests/Approvals/AllCommonItemsCreate.-o#Solution-Filter-File#-n#item#-s#Parent.slnx.verified/std-streams/stdout.txt
new file mode 100644
index 000000000000..70cab17a4b13
--- /dev/null
+++ b/test/dotnet-new.IntegrationTests/Approvals/AllCommonItemsCreate.-o#Solution-Filter-File#-n#item#-s#Parent.slnx.verified/std-streams/stdout.txt
@@ -0,0 +1 @@
+The template "%TEMPLATE_NAME%" was created successfully.
\ No newline at end of file
diff --git a/test/dotnet-new.IntegrationTests/Approvals/DotnetNewCompleteTests.CanDoTabCompletion.Linux.verified.txt b/test/dotnet-new.IntegrationTests/Approvals/DotnetNewCompleteTests.CanDoTabCompletion.Linux.verified.txt
index 58f8f94f7231..e8f07f91d9b2 100644
--- a/test/dotnet-new.IntegrationTests/Approvals/DotnetNewCompleteTests.CanDoTabCompletion.Linux.verified.txt
+++ b/test/dotnet-new.IntegrationTests/Approvals/DotnetNewCompleteTests.CanDoTabCompletion.Linux.verified.txt
@@ -25,6 +25,7 @@ proto
razorclasslib
razorcomponent
sln
+slnf
tool-manifest
view
viewimports
diff --git a/test/dotnet-new.IntegrationTests/Approvals/DotnetNewCompleteTests.CanDoTabCompletion.OSX.verified.txt b/test/dotnet-new.IntegrationTests/Approvals/DotnetNewCompleteTests.CanDoTabCompletion.OSX.verified.txt
index 58f8f94f7231..e8f07f91d9b2 100644
--- a/test/dotnet-new.IntegrationTests/Approvals/DotnetNewCompleteTests.CanDoTabCompletion.OSX.verified.txt
+++ b/test/dotnet-new.IntegrationTests/Approvals/DotnetNewCompleteTests.CanDoTabCompletion.OSX.verified.txt
@@ -25,6 +25,7 @@ proto
razorclasslib
razorcomponent
sln
+slnf
tool-manifest
view
viewimports
diff --git a/test/dotnet-new.IntegrationTests/Approvals/DotnetNewCompleteTests.CanDoTabCompletion.Windows.verified.txt b/test/dotnet-new.IntegrationTests/Approvals/DotnetNewCompleteTests.CanDoTabCompletion.Windows.verified.txt
index 6f90bdf0dd15..fdb015c97bef 100644
--- a/test/dotnet-new.IntegrationTests/Approvals/DotnetNewCompleteTests.CanDoTabCompletion.Windows.verified.txt
+++ b/test/dotnet-new.IntegrationTests/Approvals/DotnetNewCompleteTests.CanDoTabCompletion.Windows.verified.txt
@@ -25,6 +25,7 @@ proto
razorclasslib
razorcomponent
sln
+slnf
tool-manifest
view
viewimports
diff --git a/test/dotnet-new.IntegrationTests/CommonTemplatesTests.cs b/test/dotnet-new.IntegrationTests/CommonTemplatesTests.cs
index ad2d5e54b48c..af33ea66d105 100644
--- a/test/dotnet-new.IntegrationTests/CommonTemplatesTests.cs
+++ b/test/dotnet-new.IntegrationTests/CommonTemplatesTests.cs
@@ -46,6 +46,9 @@ public CommonTemplatesTests(SharedHomeDirectory fixture, ITestOutputHelper log)
[InlineData("Solution File", "sln", new[] { "--format", "sln" })]
[InlineData("Solution File", "sln", new[] { "--format", "slnx" })]
[InlineData("Solution File", "solution", null)]
+ [InlineData("Solution Filter File", "slnf", new[] { "--parent-solution", "Parent.slnx" })]
+ [InlineData("Solution Filter File", "slnf", new[] { "-s", "Parent.slnx" })]
+ [InlineData("Solution Filter File", "solutionfilter", new[] { "--parent-solution", "Parent.slnx" })]
[InlineData("Dotnet local tool manifest file", "tool-manifest", null)]
[InlineData("Web Config", "webconfig", null)]
[InlineData("EditorConfig file", "editorconfig", null)]
diff --git a/test/dotnet.Tests/CommandTests/Solution/Add/GivenDotnetSlnAdd.cs b/test/dotnet.Tests/CommandTests/Solution/Add/GivenDotnetSlnAdd.cs
index 8694f54a8533..891270085e31 100644
--- a/test/dotnet.Tests/CommandTests/Solution/Add/GivenDotnetSlnAdd.cs
+++ b/test/dotnet.Tests/CommandTests/Solution/Add/GivenDotnetSlnAdd.cs
@@ -1353,5 +1353,71 @@ private string GetSolutionFileTemplateContents(string templateFileName)
.Path;
return File.ReadAllText(Path.Join(templateContentDirectory, templateFileName));
}
+
+ // SLNF TESTS
+ [Theory]
+ [InlineData("sln")]
+ [InlineData("solution")]
+ public void WhenAddingProjectToSlnfItAddsOnlyIfInParentSolution(string solutionCommand)
+ {
+ var projectDirectory = _testAssetsManager
+ .CopyTestAsset("TestAppWithSlnfFiles", identifier: $"GivenDotnetSlnAdd-Slnf-{solutionCommand}")
+ .WithSource()
+ .Path;
+
+ var slnfFullPath = Path.Combine(projectDirectory, "App.slnf");
+
+ // Try to add Lib project which is in parent solution
+ var cmd = new DotnetCommand(Log)
+ .WithWorkingDirectory(projectDirectory)
+ .Execute(solutionCommand, "App.slnf", "add", Path.Combine("src", "Lib", "Lib.csproj"));
+ cmd.Should().Pass();
+ cmd.StdOut.Should().Contain(string.Format(CliStrings.ProjectAddedToTheSolution, Path.Combine("src", "Lib", "Lib.csproj")));
+
+ // Verify the project was added to the slnf file
+ var slnfContent = File.ReadAllText(slnfFullPath);
+ slnfContent.Should().Contain("src\\\\Lib\\\\Lib.csproj");
+ }
+
+ [Theory]
+ [InlineData("sln")]
+ [InlineData("solution")]
+ public void WhenRemovingProjectFromSlnfItRemovesSuccessfully(string solutionCommand)
+ {
+ var projectDirectory = _testAssetsManager
+ .CopyTestAsset("TestAppWithSlnfFiles", identifier: $"GivenDotnetSlnAdd-SlnfRemove-{solutionCommand}")
+ .WithSource()
+ .Path;
+
+ var slnfFullPath = Path.Combine(projectDirectory, "App.slnf");
+
+ // Remove the App project from the filter
+ var cmd = new DotnetCommand(Log)
+ .WithWorkingDirectory(projectDirectory)
+ .Execute(solutionCommand, "App.slnf", "remove", Path.Combine("src", "App", "App.csproj"));
+ cmd.Should().Pass();
+ cmd.StdOut.Should().Contain(string.Format(CliStrings.ProjectRemovedFromTheSolution, Path.Combine("src", "App", "App.csproj")));
+
+ // Verify the project was removed from the slnf file
+ var slnfContent = File.ReadAllText(slnfFullPath);
+ slnfContent.Should().NotContain("src\\\\App\\\\App.csproj");
+ }
+
+ [Theory]
+ [InlineData("sln")]
+ [InlineData("solution")]
+ public void WhenAddingProjectToSlnfWithInRootOptionItErrors(string solutionCommand)
+ {
+ var projectDirectory = _testAssetsManager
+ .CopyTestAsset("TestAppWithSlnfFiles", identifier: $"GivenDotnetSlnAdd-SlnfInRoot-{solutionCommand}")
+ .WithSource()
+ .Path;
+
+ var cmd = new DotnetCommand(Log)
+ .WithWorkingDirectory(projectDirectory)
+ .Execute(solutionCommand, "App.slnf", "add", "--in-root", Path.Combine("src", "Lib", "Lib.csproj"));
+ cmd.Should().Fail();
+ cmd.StdErr.Should().Contain(CliCommandStrings.SolutionFilterDoesNotSupportFolderOptions);
+ }
}
}