Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,10 @@ Options:
-f, --format <Json|Plain|Slnf|Traversal> Output format, if --output is specified format will be derived from file extension. Otherwise this defaults to 'plain'
-o, --out, --output Output file, if not set stdout will be used
--ignore-changed-file Ignore changes in specific files. If these files are a part of the build evaluation process they will still be evaluated, however these files will be considered unchanged by the diff process []
--log-level <Critical|Debug|Error|Information|None|Trace|Warning> Set the log level for the command [default: Information]
--log-level <Critical|Debug|Error|Information|None|Trace|Warning> Set the log level for the command [default: Warning]
--msbuild-traversal-version Set the version of the Microsoft.Build.Traversal SDK when using traversal output format
--exclude-projects Exclude projects from the output, can be matched multiple times, supports glob patterns
--include-projects Include only projects matching the specified patterns, can be matched multiple times, supports glob patterns
```

The cli should have some sensible defaults, so you can run it without any arguments and get a list of projects that have
Expand All @@ -66,7 +68,7 @@ dotnet-proj-diff --base main --head feature/new-feature
dotnet-proj-diff --base HEAD | dotnet test

# Test all changed test projects in test/ directory
dotnet-proj-diff | grep 'test/' | dotnet test
dotnet-proj-diff --include-projects test/**/*.csproj | dotnet test
```

## CI/CD Integration examples
Expand Down
3 changes: 0 additions & 3 deletions src/dotnet-proj-diff/IConsole.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ namespace ProjectDiff.Tool;

public interface IConsole
{
TextWriter Error { get; }
TextWriter Out { get; }

Stream OpenStandardOutput();
string WorkingDirectory { get; }
}
4 changes: 3 additions & 1 deletion src/dotnet-proj-diff/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

MSBuildLocator.RegisterDefaults();

var tool = ProjectDiffTool.BuildCli(new SystemConsole());
var tool = ProjectDiffTool.BuildCli(
new SystemConsole()
);

return await tool.InvokeAsync(args);
56 changes: 49 additions & 7 deletions src/dotnet-proj-diff/ProjectDiffCommand.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.CommandLine;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.FileSystemGlobbing;
using Microsoft.Extensions.Logging;
using ProjectDiff.Core;
using ProjectDiff.Core.Entrypoints;
Expand Down Expand Up @@ -128,6 +129,20 @@ public sealed class ProjectDiffCommand : RootCommand
Description = "Set the version of the Microsoft.Build.Traversal SDK when using traversal output format",
};

private static readonly Option<string[]> ExcludeProjectsOption = new("--exclude-projects")
{
Arity = ArgumentArity.ZeroOrMore,
Description = "Exclude projects from the output, can be matched multiple times, supports glob patterns",
};

private static readonly Option<string[]> IncludeProjectsOption = new("--include-projects")
{
Arity = ArgumentArity.ZeroOrMore,
Description =
"Include only projects matching the specified patterns, can be matched multiple times, supports glob patterns"
};


private readonly IConsole _console;


Expand All @@ -149,6 +164,8 @@ public ProjectDiffCommand(IConsole console)
Options.Add(IgnoreChangedFilesOption);
Options.Add(LogLevelOption);
Options.Add(MicrosoftBuildTraversalVersionOption);
Options.Add(ExcludeProjectsOption);
Options.Add(IncludeProjectsOption);
SetAction(ExecuteAsync);
}

Expand All @@ -159,17 +176,19 @@ private Task<int> ExecuteAsync(ParseResult parseResult, CancellationToken cancel
Format = parseResult.GetValue(Format),
Output = parseResult.GetValue(OutputOption),
Solution = parseResult.GetValue(SolutionOption),
BaseRef = parseResult.GetRequiredValue(BaseCommitOption),
BaseRef = parseResult.GetValue(BaseCommitOption) ?? "HEAD",
HeadRef = parseResult.GetValue(HeadCommitOption),
MergeBase = parseResult.GetValue(MergeBaseOption),
IncludeDeleted = parseResult.GetValue(IncludeDeleted),
IncludeModified = parseResult.GetValue(IncludeModified),
IncludeAdded = parseResult.GetValue(IncludeAdded),
IncludeReferencing = parseResult.GetValue(IncludeReferencing),
AbsolutePaths = parseResult.GetValue(AbsolutePaths),
IgnoreChangedFile = parseResult.GetRequiredValue(IgnoreChangedFilesOption),
IgnoreChangedFile = parseResult.GetValue(IgnoreChangedFilesOption) ?? [],
LogLevel = parseResult.GetValue(LogLevelOption),
MicrosoftBuildTraversalVersion = parseResult.GetValue(MicrosoftBuildTraversalVersionOption)
MicrosoftBuildTraversalVersion = parseResult.GetValue(MicrosoftBuildTraversalVersionOption),
ExcludeProjects = parseResult.GetValue(ExcludeProjectsOption) ?? [],
IncludeProjects = parseResult.GetValue(IncludeProjectsOption) ?? []
};

return ExecuteCoreAsync(settings, cancellationToken);
Expand All @@ -183,7 +202,7 @@ CancellationToken cancellationToken
using var loggerFactory = LoggerFactory.Create(x =>
{
x.AddConsole(c => c.LogToStandardErrorThreshold = LogLevel.Trace); // Log everything to stderr
x.AddSimpleConsole(x => x.IncludeScopes = true);
x.AddSimpleConsole(c => c.IncludeScopes = true);
x.SetMinimumLevel(settings.LogLevel);
}
);
Expand Down Expand Up @@ -245,6 +264,19 @@ CancellationToken cancellationToken
return 1;
}

var matcher = new Matcher();
if (settings.IncludeProjects.Length > 0)
{
matcher.AddIncludePatterns(settings.IncludeProjects);
}
else
{
matcher.AddInclude("**/*")
.AddInclude("*");
}


matcher.AddExcludePatterns(settings.ExcludeProjects);
var projects = result.Projects
.Where(ShouldInclude)
.ToList();
Expand All @@ -255,7 +287,7 @@ CancellationToken cancellationToken
logger.LogDebug(
"Diff projects: {Projects}",
projects.Select(it => new
{ it.Path, it.Status, ReferencedProjects = string.Join(',', it.ReferencedProjects) }
{ it.Path, it.Status, ReferencedProjects = string.Join(',', it.ReferencedProjects) }
)
);
}
Expand All @@ -272,15 +304,25 @@ await formatter.WriteAsync(

return 0;

bool ShouldInclude(DiffProject project) =>
project.Status switch
bool ShouldInclude(DiffProject project)
{
var shouldIncludeStatus = project.Status switch
{
DiffStatus.Removed when settings.IncludeDeleted => true,
DiffStatus.Added when settings.IncludeAdded => true,
DiffStatus.Modified when settings.IncludeModified => true,
DiffStatus.ReferenceChanged when settings.IncludeReferencing => true,
_ => false
};
if (!shouldIncludeStatus)
{
return false;
}


var matchResult = matcher.Match(_console.WorkingDirectory, project.Path);
return matchResult.HasMatches;
}
}

private static IOutputFormatter GetFormatter(OutputFormat format, ProjectDiffSettings settings) => format switch
Expand Down
2 changes: 2 additions & 0 deletions src/dotnet-proj-diff/ProjectDiffSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,6 @@ public sealed class ProjectDiffSettings
public required FileInfo[] IgnoreChangedFile { get; init; } = [];
public string? MicrosoftBuildTraversalVersion { get; init; }
public LogLevel LogLevel { get; init; } = LogLevel.Information;
public required string[] ExcludeProjects { get; init; }
public required string[] IncludeProjects { get; init; }
}
17 changes: 12 additions & 5 deletions src/dotnet-proj-diff/ProjectDiffTool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,19 @@ namespace ProjectDiff.Tool;

public static class ProjectDiffTool
{
public static CommandLineConfiguration BuildCli(IConsole console)
public static CommandLineConfiguration BuildCli(IConsole console, TextWriter? stderr = null, TextWriter? stdout = null)
{
return new CommandLineConfiguration(new ProjectDiffCommand(console))
var cli = new CommandLineConfiguration(new ProjectDiffCommand(console));
if (stderr is not null)
{
Error = console.Error,
Output = console.Out,
};
cli.Error = stderr;
}

if (stdout is not null)
{
cli.Output = stdout;
}

return cli;
}
}
3 changes: 0 additions & 3 deletions src/dotnet-proj-diff/SystemConsole.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ namespace ProjectDiff.Tool;

public sealed class SystemConsole : IConsole
{
public TextWriter Error { get; } = Console.Error;
public TextWriter Out { get; } = Console.Out;

public Stream OpenStandardOutput() => Console.OpenStandardOutput();
public string WorkingDirectory { get; } = Directory.GetCurrentDirectory();
}
1 change: 1 addition & 0 deletions src/dotnet-proj-diff/dotnet-proj-diff.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.Build.Locator" Version="1.9.1" />
<PackageReference Include="Microsoft.Extensions.FileSystemGlobbing" Version="9.0.7" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.7" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0">
<PrivateAssets>all</PrivateAssets>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[
{
path: Sample/Sample.csproj,
name: Sample,
status: Modified
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[
{
path: Sample/Sample.csproj,
name: Sample,
status: Modified
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[
{
path: Sample/Sample.csproj,
name: Sample,
status: Modified
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[
{
path: Sample/Sample.csproj,
name: Sample,
status: Modified
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[
{
path: Sample/Sample.csproj,
name: Sample,
status: Modified
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[
{
path: Sample/Sample.csproj,
name: Sample,
status: Modified
}
]
62 changes: 58 additions & 4 deletions test/ProjectDiff.Tests/Tool/ProjectDiffTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -229,11 +229,64 @@ await repo.UpdateSolutionAsync(
x.RemoveProject(proj);
}
);
var output = await ExecuteAndReadStdout(repo, $"--solution={sln}");
var output = await ExecuteAndReadStdout(repo, "--solution", sln);

await VerifyJson(output);
}

[Theory]
[InlineData("Sample/*.csproj")]
[InlineData("Sample/Sample.csproj")]
[InlineData("Sample/**")]
public async Task IncludeProjects(string includePattern)
{
using var repo = await TestRepository.SetupAsync(static r =>
{
r.CreateDirectory("Sample");
r.CreateDirectory("Tests");

r.CreateProject("Sample/Sample.csproj");
r.CreateProject(
"Tests/Tests.csproj",
p => p.AddItem("ProjectReference", @"..\Sample\Sample.csproj")
);
return Task.CompletedTask;
}
);
await repo.WriteAllTextAsync("Sample/MyClass.cs", "// Some new content");

var output = await ExecuteAndReadStdout(repo, $"--include-projects={includePattern}");

await VerifyJson(output)
.UseParameters(includePattern);
}

[Theory]
[InlineData("Tests/Tests.csproj")]
[InlineData("Tests/**")]
[InlineData("Tests/*.csproj")]
public async Task ExcludeProjects(string excludePattern)
{
using var repo = await TestRepository.SetupAsync(static r =>
{
r.CreateDirectory("Sample");
r.CreateDirectory("Tests");

r.CreateProject("Sample/Sample.csproj");
r.CreateProject(
"Tests/Tests.csproj",
p => p.AddItem("ProjectReference", @"..\Sample\Sample.csproj")
);
return Task.CompletedTask;
}
);
await repo.WriteAllTextAsync("Sample/MyClass.cs", "// Some new content");

var output = await ExecuteAndReadStdout(repo, $"--exclude-projects={excludePattern}");

await VerifyJson(output)
.UseParameters(excludePattern);
}

[Fact]
public async Task DetectsAddedProjects()
Expand Down Expand Up @@ -303,14 +356,15 @@ params string[] args
"--log-level=Error",
"--format=json",
];
var stderr = new StringWriter();
var console = new TestConsole(repository.WorkingDirectory);

var cli = ProjectDiffTool.BuildCli(console);
var cli = ProjectDiffTool.BuildCli(console, stderr: stderr);
var exitCode = await cli.InvokeAsync([.. args, .. defaultArgs]);
if (exitCode != 0)
{
var stderr = console.GetStandardError();
Assert.Fail($"Program exited with exit code {exitCode}: {stderr}");
var error = stderr.ToString();
Assert.Fail($"Program exited with exit code {exitCode}: {error}");
}

return console.GetStandardOutput();
Expand Down
Loading
Loading