Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug]: Check for determining "project file" does not correctly work for solution filter files #9587

Closed
stefan-schweiger opened this issue Jan 3, 2024 · 4 comments
Labels
bug help wanted Issues that the core team doesn't plan to work on, but would accept a PR for. Comment to claim. Priority:3 Work that is nice to have triaged

Comments

@stefan-schweiger
Copy link

Issue Description

When running dotnet build in a folder which only contains a .slnf the commands won't work because no valid "project file" can be determined, but if you run dotnet build MyFilter.slnf the commands correctly work.

Steps to Reproduce

Create a solution with the following structure and run dotnet build in the folder ServiceA

/
├─ ServiceA/
│  ├─ ServiceA.QueryHandling/
│  │  ├─ ServiceA.QueryHandling.csproj
│  ├─ ServiceA.slnf
├─ MySolution.sln

Expected Behavior

The build should correctly pick up the solution filter file and work like it would in the / and ServiceA.QueryHandling folders.

Actual Behavior

If you run it in the ServiceA folder you get this error:
MSB1003: Specify a project or solution file. The current working directory does not contain a project or solution file.

Analysis

I think the problem is on line 3422 here that it only looks for *.sln files and therefore never picks up *.slnf files, so it should probably be changed to *.sln* to actually correctly pick up the solution filter files.

msbuild/src/MSBuild/XMake.cs

Lines 3422 to 3497 in 5d663a0

string[] potentialSolutionFiles = getFiles(projectDirectory ?? ".", "*.sln");
List<string> actualSolutionFiles = new List<string>();
List<string> solutionFilterFiles = new List<string>();
if (potentialSolutionFiles != null)
{
foreach (string s in potentialSolutionFiles)
{
if (!extensionsToIgnore.Contains(Path.GetExtension(s)))
{
if (FileUtilities.IsSolutionFilterFilename(s))
{
solutionFilterFiles.Add(s);
}
else if (FileUtilities.IsSolutionFilename(s))
{
actualSolutionFiles.Add(s);
}
}
}
}
// If there is exactly 1 project file and exactly 1 solution file
if (actualProjectFiles.Count == 1 && actualSolutionFiles.Count == 1)
{
// Grab the name of both project and solution without extensions
string solutionName = Path.GetFileNameWithoutExtension(actualSolutionFiles[0]);
string projectName = Path.GetFileNameWithoutExtension(actualProjectFiles[0]);
// Compare the names and error if they are not identical
InitializationException.VerifyThrow(string.Equals(solutionName, projectName, StringComparison.OrdinalIgnoreCase), projectDirectory == null ? "AmbiguousProjectError" : "AmbiguousProjectDirectoryError", null, projectDirectory);
projectFile = actualSolutionFiles[0];
}
// If there is more than one solution file in the current directory we have no idea which one to use
else if (actualSolutionFiles.Count > 1)
{
InitializationException.VerifyThrow(false, projectDirectory == null ? "AmbiguousProjectError" : "AmbiguousProjectDirectoryError", null, projectDirectory, false);
}
// If there is more than one project file in the current directory we may be able to figure it out
else if (actualProjectFiles.Count > 1)
{
// We have more than one project, it is ambiguous at the moment
bool isAmbiguousProject = true;
// If there are exactly two projects and one of them is a .proj use that one and ignore the other
if (actualProjectFiles.Count == 2)
{
string firstPotentialProjectExtension = Path.GetExtension(actualProjectFiles[0]);
string secondPotentialProjectExtension = Path.GetExtension(actualProjectFiles[1]);
// If the two projects have the same extension we can't decide which one to pick
if (!string.Equals(firstPotentialProjectExtension, secondPotentialProjectExtension, StringComparison.OrdinalIgnoreCase))
{
// Check to see if the first project is the proj, if it is use it
if (string.Equals(firstPotentialProjectExtension, ".proj", StringComparison.OrdinalIgnoreCase))
{
projectFile = actualProjectFiles[0];
// We have made a decision
isAmbiguousProject = false;
}
// If the first project is not the proj check to see if the second one is the proj, if so use it
else if (string.Equals(secondPotentialProjectExtension, ".proj", StringComparison.OrdinalIgnoreCase))
{
projectFile = actualProjectFiles[1];
// We have made a decision
isAmbiguousProject = false;
}
}
}
InitializationException.VerifyThrow(!isAmbiguousProject, projectDirectory == null ? "AmbiguousProjectError" : "AmbiguousProjectDirectoryError", null, projectDirectory);
}
// if there are no project, solution filter, or solution files in the directory, we can't build
else if (actualProjectFiles.Count == 0 &&
actualSolutionFiles.Count == 0 &&
solutionFilterFiles.Count == 0)
{
InitializationException.Throw("MissingProjectError", null, null, false);
}

Versions & Configurations

MSBuild version 17.8.3+195e7f5a3 for .NET
17.8.3.51904%

dotnet 8.0.100

macOS 14.2.1 (23C71)

@stefan-schweiger
Copy link
Author

Also I think the "mock" code here

internal string[] GetFiles(string path, string searchPattern)
{
List<string> fileNamesToReturn = new List<string>();
foreach (string file in _directoryFileNameList)
{
if (string.Equals(searchPattern, "*.sln", StringComparison.OrdinalIgnoreCase))
{
if (FileUtilities.IsSolutionFilename(file))
{
fileNamesToReturn.Add(file);
}
}
else if (string.Equals(searchPattern, "*.*proj", StringComparison.OrdinalIgnoreCase))
{
if (Path.GetExtension(file).Contains("proj"))
{
fileNamesToReturn.Add(file);
}
}
}
return fileNamesToReturn.ToArray();
}
}

for this test does not correctly work

[Theory]
[InlineData(new[] { "my.proj", "my.sln", "my.slnf" }, "my.sln")]
[InlineData(new[] { "abc.proj", "bcd.csproj", "slnf.slnf", "other.slnf" }, "abc.proj")]
[InlineData(new[] { "abc.sln", "slnf.slnf", "abc.slnf" }, "abc.sln")]
[InlineData(new[] { "abc.csproj", "abc.slnf", "not.slnf" }, "abc.csproj")]
[InlineData(new[] { "abc.slnf" }, "abc.slnf")]
public void TestDefaultBuildWithSolutionFilter(string[] projects, string answer)
{
string[] extensionsToIgnore = Array.Empty<string>();
IgnoreProjectExtensionsHelper projectHelper = new IgnoreProjectExtensionsHelper(projects);
MSBuildApp.ProcessProjectSwitch(Array.Empty<string>(), extensionsToIgnore, projectHelper.GetFiles).ShouldBe(answer, StringCompareShould.IgnoreCase);
}

@AR-May AR-May added triaged help wanted Issues that the core team doesn't plan to work on, but would accept a PR for. Comment to claim. Priority:3 Work that is nice to have and removed needs-triage Have yet to determine what bucket this goes in. labels Mar 14, 2024
@AR-May
Copy link
Member

AR-May commented Mar 14, 2024

Team triage: @stefan-schweiger your analysis looks good, if you are interested, please send a PR fixing that.

@jrdodds
Copy link
Contributor

jrdodds commented Nov 8, 2024

This bug has been fixed by Feature Request #10266 and PR #10794. The PR implemented the changes identified by @stefan-schweiger. I confirmed by testing and reviewing the code.

I believe this issue can be closed.

@rainersigwald
Copy link
Member

Thanks @jrdodds!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug help wanted Issues that the core team doesn't plan to work on, but would accept a PR for. Comment to claim. Priority:3 Work that is nice to have triaged
Projects
None yet
Development

No branches or pull requests

4 participants