3F / MvsSln

🧩 Customizable VisualStudio .sln parser with project support (.vcxproj, .csproj., …). Pluggable lightweight r/w handlers at runtime, and more …
MIT License
135 stars 27 forks source link

Odd issue only when publishing via single file - Value cannot be empty string #59

Closed Deadpikle closed 9 months ago

Deadpikle commented 9 months ago

Hi,

I'm experiencing a weird error that only happens when I publish my app as a single file. It seems to happen on deconstruction, which is odd. My goal is to load a list of solution configs and solution projects so that the user can choose an appropriate pair (config & project).

Here's the stack trace:

Unhandled exception. System.ArgumentException: The value cannot be an empty string. (Parameter 'path')
   at Microsoft.Build.Shared.BuildEnvironmentHelper.get_Instance()
   at Microsoft.Build.Utilities.Traits.get_Instance()
   at Microsoft.Build.Evaluation.ProjectCollection..ctor(IDictionary`2 globalProperties, IEnumerable`1 loggers, IEnumerable`1 remoteLoggers, ToolsetDefinitionLocations toolsetDefinitionLocations, Int32 maxNodeCount, Boolean onlyLogCriticalEvents, Boolean loadProjectsReadOnly)
   at Microsoft.Build.Evaluation.ProjectCollection..ctor(IDictionary`2 globalProperties, IEnumerable`1 loggers, IEnumerable`1 remoteLoggers, ToolsetDefinitionLocations toolsetDefinitionLocations, Int32 maxNodeCount, Boolean onlyLogCriticalEvents)
   at Microsoft.Build.Evaluation.ProjectCollection..ctor(IDictionary`2 globalProperties, IEnumerable`1 loggers, ToolsetDefinitionLocations toolsetDefinitionLocations)
   at Microsoft.Build.Evaluation.ProjectCollection..ctor(IDictionary`2 globalProperties)
   at Microsoft.Build.Evaluation.ProjectCollection..ctor()
   at Microsoft.Build.Evaluation.ProjectCollection.get_GlobalProjectCollection()
   at net.r_eg.MvsSln.Core.XProjectEnv.get_PrjCollection()
   at net.r_eg.MvsSln.Core.XProjectEnv.UnloadAll(Boolean throwIfErr)
   at net.r_eg.MvsSln.Core.IsolatedEnv.Dispose(Boolean _)
   at net.r_eg.MvsSln.Core.IsolatedEnv.Dispose()
   at net.r_eg.MvsSln.Sln.Dispose(Boolean _)
   at net.r_eg.MvsSln.Sln.Dispose()
   [MyApp stack trace here]

Here's the code in MyApp that the stack trace points to:

public static SolutionConfigPlatforms GetConfigurationsInSolution(DotnetProject solution)
{
    if (File.Exists(solution.FilePath))
    {
        using (var sln = new Sln(solution.FilePath, SlnItems.SolutionConfPlatforms | SlnItems.Env))
        {
            var output = new SolutionConfigPlatforms();
            output.DefaultConfig = sln.Result.DefaultConfig.Configuration;
            output.DefaultPlatform = sln.Result.DefaultConfig.Platform;
            foreach (var config in sln.Result.ProjectConfigs)
            {
                output.AddConfig(config.Configuration);
                output.AddPlatform(config.Platform);
            }
            return output; // <----- stack trace points here
        }
    }
    return new SolutionConfigPlatforms();
}

SolutionConfigPlatforms is a simple model defined as:

    class SolutionConfigPlatforms
    {
        public List<string> Configs { get; set; }
        public List<string> Platforms { get; set; }
        public string DefaultConfig { get; set; } // e.g. "Debug"
        public string DefaultPlatform { get; set; } // e.g. "AnyCPU"
         ...
     }

SLN:

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29709.97
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyApp.WPF", "MyApp.WPF\MyApp.WPF.csproj", "{83A590A7-F126-4FB2-8C94-4DF0ED16D5A5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyApp.Tests", "MyApp.Tests\MyApp.Tests.csproj", "{1F4289D8-3E67-46B1-B6B5-5368880F8969}"
EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "MyApp.Shared", "MyApp.Shared\MyApp.Shared.shproj", "{9A68665F-F039-49F2-903D-95EFBDA36C60}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyApp.Avalonia", "MyApp.Avalonia\MyApp.Avalonia.csproj", "{C5C70FF0-E9ED-43AE-9CBC-1BF8CF60F37C}"
EndProject
Global
    GlobalSection(SharedMSBuildProjectFiles) = preSolution
        MyApp.Shared\MyApp.Shared.projitems*{83a590a7-f126-4fb2-8c94-4df0ed16d5a5}*SharedItemsImports = 5
        MyApp.Shared\MyApp.Shared.projitems*{9a68665f-f039-49f2-903d-95efbda36c60}*SharedItemsImports = 13
        MyApp.Shared\MyApp.Shared.projitems*{c5c70ff0-e9ed-43ae-9cbc-1bf8cf60f37c}*SharedItemsImports = 5
    EndGlobalSection
    GlobalSection(SolutionConfigurationPlatforms) = preSolution
        Debug|Any CPU = Debug|Any CPU
        Release|Any CPU = Release|Any CPU
    EndGlobalSection
    GlobalSection(ProjectConfigurationPlatforms) = postSolution
        {83A590A7-F126-4FB2-8C94-4DF0ED16D5A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
        {83A590A7-F126-4FB2-8C94-4DF0ED16D5A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
        {83A590A7-F126-4FB2-8C94-4DF0ED16D5A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
        {83A590A7-F126-4FB2-8C94-4DF0ED16D5A5}.Release|Any CPU.Build.0 = Release|Any CPU
        {1F4289D8-3E67-46B1-B6B5-5368880F8969}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
        {1F4289D8-3E67-46B1-B6B5-5368880F8969}.Debug|Any CPU.Build.0 = Debug|Any CPU
        {1F4289D8-3E67-46B1-B6B5-5368880F8969}.Release|Any CPU.ActiveCfg = Release|Any CPU
        {1F4289D8-3E67-46B1-B6B5-5368880F8969}.Release|Any CPU.Build.0 = Release|Any CPU
        {C5C70FF0-E9ED-43AE-9CBC-1BF8CF60F37C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
        {C5C70FF0-E9ED-43AE-9CBC-1BF8CF60F37C}.Debug|Any CPU.Build.0 = Debug|Any CPU
        {C5C70FF0-E9ED-43AE-9CBC-1BF8CF60F37C}.Release|Any CPU.ActiveCfg = Release|Any CPU
        {C5C70FF0-E9ED-43AE-9CBC-1BF8CF60F37C}.Release|Any CPU.Build.0 = Release|Any CPU
    EndGlobalSection
    GlobalSection(SolutionProperties) = preSolution
        HideSolutionNode = FALSE
    EndGlobalSection
    GlobalSection(ExtensibilityGlobals) = postSolution
        SolutionGuid = {01061048-C1F2-454F-9EA3-831A961CB95F}
    EndGlobalSection
EndGlobal

Note that this code runs on a background thread.

I haven't yet done the dirty work of trying to create a small minimal sample due to running out of time now, but am posting this in hopes that someone may know the answer or what to look at sooner than I will have time to dig in deeper. The issue seems to be deeper in MS-provided code, but am not sure if something is being utilized wrong.

Environment: macOS 12.6.9 .NET Version: .NET 8 RC 1 (this did seem to happen on .NET 7, too)

Build command: dotnet publish "/Users/[name]/Documents/Projects/[Name].csproj" --configuration "Release" --framework "net8.0" --runtime "osx-x64" /p:PublishSingleFile=true --self-contained True

Any help is appreciated!

Thanks~

3F commented 9 months ago

Hello,

First of all, Env flag is not needed in your code above. To fix the odd behavior, try this:

SlnItems.SolutionConfPlatforms | SlnItems.ProjectConfPlatforms

However,

The real problem is a little deeper if you really need to use SlnItems.Env because MvsSln v2 (#23) partially relies on GlobalProjectCollection (msbuild) which can cause problems like yours (depends on environment).

That's why in the past MvsSln v2.4 introduced XProjectEnv in addition to IsolatedEnv, see #17

In other words,

Modern MvsSln provides two implementation of IXProjectEnv (IsolatedEnv is the default if raised SlnItems.Env). And if your environment also relies on GlobalProjectCollection, you need to consider to implement you custom disposing if needed at all.

Here you can find an example of applied custom disposing between two completely different environments Visual Studio as a VSIX plugin and MSBuild for CI https://github.com/3F/vsSolutionBuildEvent/pull/53

Deadpikle commented 9 months ago

First of all, Env flag is not needed in your code above. To fix the odd behavior, try this:

This fixed the problem up real quick. Thank you for your reply and the additional info on how to fix things should I need SlnItems.Env in the future.

Appreciate it — and your library! 😄