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

creating projects/solutions from scratch #61

Closed mitchcapper closed 2 months ago

mitchcapper commented 2 months ago

Wanted to try and create a basic project/solution but ran into a good bit of difficulty.

Below is the code I tried, I had to fallback to MS to generate the project itself and then still had problems.

While there is no example of creating a project like this there is an example of creating an issue from scratch here. Sadly the solution had several problems even (like missing global section).

Here is what 'worked' but required various manual fixes (including commented out versions of what didn't work). Not sure if there is a better way:

    Microsoft.Build.Locator.MSBuildLocator.RegisterDefaults();
}
public void DoExport(String baseDir, String projName, Message msg) {

    if (Directory.Exists(baseDir))
        Directory.Delete(baseDir, true);
    Directory.CreateDirectory(baseDir);
    var solutionFile = Path.Combine(baseDir, projName + ".sln");
    var header = new SlnHeader();
    header.SetFormatVersion("12.0");
    header.SetMinimumVersion("10.0.40219.1");
    var platformData = new List<IConfPlatform>() {
        new ConfigSln("Debug", "x64"),
    };

    var projects = new[] { new ProjectItem(projName, ProjectType.CsSdk, $"{projName}.csproj") };
    var raw_proj = new WProject(projects, new LProjectDependencies());
    var platformProjData = new List<IConfPlatformPrj>() {
        new ConfigPrj(projects[0].name,projects[0].pGuid,true,platformData[0] as ConfigSln),
    };
    var whandlers = new Dictionary<Type, HandlerValue>() {
        [typeof(LVisualStudioVersion)] = new HandlerValue(new WVisualStudioVersion(header)),
        [typeof(LProject)] = new HandlerValue(raw_proj),
        //[typeof(global)] = new HandlerValue(raw_proj),
        [typeof(LProjectConfigurationPlatforms)] = new HandlerValue(new WProjectConfigurationPlatforms(platformProjData)),
        [typeof(LSolutionConfigurationPlatforms)] = new HandlerValue(new WSolutionConfigurationPlatforms(platformData)),

    };
    //  xp.AddReference(typeof(JsonConverter).Assembly, true);
    using (var w = new SlnWriter(solutionFile, whandlers)) {
        w.Write([
            new Section(new LVisualStudioVersion(), null),
            new Section(new LProject(), null),
            new Section(new LProjectConfigurationPlatforms(), null),
            new Section(new LSolutionConfigurationPlatforms(), null),
        ]);

    }
    var manFixes = File.ReadAllText(solutionFile);
    manFixes = manFixes.Replace("\r", "");
    manFixes = manFixes.Replace("EndProject\n", "EndProject\nGlobal\n");
    manFixes += "\nEndGlobal\n";
    manFixes = manFixes.Replace("TestProj|", "Debug|x64");
    File.WriteAllText(solutionFile, manFixes);
    using (var sln = new Sln(solutionFile, SlnItems.Env)) {
        //var env = new XProjectEnvStub(sln.Result, cfgsln);
        var projItemConfig = new ProjectItemCfg(projects[0], sln.Result.DefaultConfig, sln.Result.ProjectConfigurationPlatforms.First().Value.First());
        //var msProj = new Microsoft.Build.Evaluation.Project(sln.Result.ProjectItems.First().fullPath);
        //var msProj = new Microsoft.Build.Evaluation.Project(Microsoft.Build.Evaluation.NewProjectFileOptions.None);
        //var msProj = Microsoft.Build.Construction.ProjectRootElement.Create(sln.Result.ProjectItems.First().fullPath,Microsoft.Build.Evaluation.NewProjectFileOptions.IncludeAllOptions);
        var msProj = Microsoft.Build.Construction.ProjectRootElement.Create(sln.Result.ProjectItems.First().fullPath, Microsoft.Build.Evaluation.NewProjectFileOptions.None);
        msProj.Sdk = "Microsoft.NET.Sdk";
        var rGroup = msProj.AddPropertyGroup();
        rGroup.AddProperty("OutputType", "EXE");
        rGroup.AddProperty("TargetFramework", "net8.0");
        rGroup.AddProperty("Platforms", "x64");
        msProj.Save();
        //msProj.ItemGroups.First().AddItem();
        //var rGroup = msProj.AddPropertyGroup();
        //msProj.AddProperty("RootNamespace", projects[0].name);
        //var proj = sln.Result.Env.LoadProjects([projItemConfig]).First();
        var proj = sln.Result.Env.LoadProjects().Single();
        //var proj = sln.Result.Env.Projects.Single();
        //sln.Result.Env.AddOrGet(msProj);
        //msProj.AddItem("Compile", @"Program.cs");
        //var proj = sln.Result.Env.LoadProjects([projItemConfig]).First();

        //}
        //using (var sln = new Sln(solutionFile, SlnItems.All)) {
        //var proj = sln.Result.Env.Projects.First();

        foreach (var dll in msg.ReferencedAssemblies) {
            var fInfo = new FileInfo(dll);
            proj.AddReference(fInfo.Name.Substring(0, fInfo.Name.Length - 4), dll, false);
        }
        var refs = proj.GetReferences();

        //refs.ForEach( imp =>  {
        //  var newProps = new RoProperties<string, net.r_eg.MvsSln.Projects.Item.Metadata>(imp.meta.Where(a=>a.Key.Equals("Private",StringComparison.CurrentCultureIgnoreCase) == false).ToDictionary());
        //  imp.meta = newProps;
        //      }
        //);//nope doesn't work

        //refs.ForEach(a=>a.meta.Remove("Private",out _));
        //proj.SetProperty("Sdk","Microsoft.NET.Sdk");
        proj.Save();

        manFixes = File.ReadAllText(msProj.FullPath);
    manFixes = manFixes.Replace("<Private>False</Private>","");
    File.WriteAllText(msProj.FullPath, manFixes);
3F commented 2 months ago

Hello,

Sadly the solution had several problems even (like missing global section).

Sorry for the inconvenience. I think the master branch already contains the fixes for the mentioned problems.

I had some other problems in my life, again, while completing a planned release in March (voiced here ~ https://mastodon.social/@github3F/111975646026491387)

Now I'll try to finish 2.7 this April:

This is already on dev/2.7 branch with updated tests ! You can also test the latest stable 1abff30 before merge (I didn't open it as PR since in fact I have mostly private reports and local tasks)

3F commented 2 months ago

ah yes, an easier way to create from scratch (empty map) is planned to review also together with 2.7, I think <_<

3F commented 2 months ago

@mitchcapper, Is there a reason for trying to remove a Private from added Reference ?

I have checked your code and all the mentioned bugs seem to have been fixed in the upcoming version 2.7. This or next week, I think, should be released together with the new features through new ISlnWhData and IProjectsToucher implementations that will help to create .sln + project files from scratch, for example (2.7+):

ConfigSln[] slnConf = [new("Debug", "x64")];
ProjectItem[] projects = [new ProjectItem(ProjectType.CsSdk, @$"{projName}\src.csproj", slnDir: baseDir)];
IConfPlatformPrj[] prjConfs = [new ConfigPrj("Debug", "x64", projects[0].pGuid, build: true, slnConf[0])];

LhDataHelper hdata = new();
hdata.SetHeader(SlnHeader.MakeDefault())
        .SetProjects(projects)
        .SetProjectConfigs(prjConfs)
        .SetSolutionConfigs(slnConf);

using(SlnWriter w = new(solutionFile, hdata))
{
    w.Options = SlnWriterOptions.CreateProjectsIfNotExist;
    w.Write();
}

using Sln sln = new(solutionFile, SlnItems.EnvWithMinimalProjects);
IXProject xp = sln.Result.Env.Projects.First();

xp.SetProperties(new Dictionary<string, string>()
{
    { "OutputType", "EXE" },
    { "TargetFramework", "net8.0" },
    { "Platforms", "x64" }
});
xp.Save();

the standard (2.x) way is also available, of course, for code from above it can be like:

Dictionary<Type, HandlerValue> whandlers = new()
{
    [typeof(LVisualStudioVersion)] = new(new WVisualStudioVersion(SlnHeader.MakeDefault())),
    [typeof(LProject)] = new(new WProject(projects)),
    [typeof(LProjectConfigurationPlatforms)] = new(new WProjectConfigurationPlatforms(prjConfs)),
    [typeof(LSolutionConfigurationPlatforms)] = new(new WSolutionConfigurationPlatforms(slnConf)),
};

using SlnWriter w = new(solutionFile, whandlers));

I'll edit my example above if anything changes before release.

Note also,

.Replace("TestProj|", "Debug|x64")

This is not a bug. You have incorrect initialization or incorrectly selected ctor (n. formatted instead of configuration + platform)

mitchcapper commented 2 months ago

Is there a reason for trying to remove a Private from added Reference ?

Create a cleaner output. Wanted to generate the same thing as if one was to manually do it in VS essentially. That way VS also decides in terms of local copy or not.

.Replace("TestProj|", "Debug|x64") This is not a bug. You have incorrect initialization or incorrectly selected ctor (n. formatted instead of configuration + platform)

Thanks, 100% could be. I was unclear on the right combo of items to get it to be happy, it seemed like a ConfigSln would work there so took it out of the platformData.

New 2.7 style looks great and straightforward!

3F commented 2 months ago

Create a cleaner output. Wanted to generate the same thing as if one was to manually do it in VS essentially. That way VS also decides in terms of local copy or not.

I added new method signatures through AddReferenceOptions enum that will help to control everything for Reference nodes. The old AddReference() (without AddReferenceOptions) has been marked as obsolete and scheduled to be removed in future versions.

For 2.7 you will be able use HidePrivate flag or some predefined option ~

Default = HideEmbedInteropTypes | HideSpecificVersion,

DefaultResolve = Default
                | ResolveAssemblyName
                | OmitArchitecture
                | OmitCultureNeutral
                | OmitPublicKeyTokenNull,

Mini = Default | HidePrivate,

MiniResolve = Mini | DefaultResolve | OmitCulture,
...

New 2.7 style looks great and straightforward!

MvsSln was based on a pluggable handlers at runtime and map to control everything (ideally).

2.7+ continues this path with new trivial wrappers that prepare data and handlers. This, unfortunately, only applies to the default set at this time, but... maybe 2.8 (well, my priority is to continue 3.0 with IeXod support and completely change IXProject ... someday)

3F commented 2 months ago

2.7 is just out; My earlier examples from drafted dev/2.7 are still valid. Please open another issue if you find something new