ligershark / sidewafflev2

Other
30 stars 11 forks source link

Guidance on creating multi-project template with specific structure #25

Closed giggio closed 6 years ago

giggio commented 6 years ago

I am having some trouble creating a multi-project template and I would like your recommendation on how to proceed, so I don't fight the tools.

What I want is that the final project structure on disk looks like this:

\MyProject.sln
\src\MyProject.WebApp\MyProject.WebApp.csproj
\test\MyProject.UnitTests\MyProject.UnitTests.csproj

And I want my solution to have 2 solution folders, src and test that map to the directories on disk, with each project in the correct solution folder.

Right now I created the template pack project file root folder, so the template structure looks like this:

\MyTemplate.sln
\MyTemplate.Pkg\MyTemplate.Pkg.csproj
\.template.config\
\src\MyTemplate.WebApp\MyTemplate.WebApp.csproj
\test\MyTemplate.UnitTests\MyTemplate.UnitTests.csproj

My template.json is referencing the two .csproj files correctly.

I am having all sorts of trouble because of this. If I create a new project called MyProject I am getting an extra MyProject folder created and I also get two .sln files created, one on root, the other on the MyProject directory. Only the second one actually has the solution folders. Structure ends up like this:

\MyProject.sln
\MyProject\MyProject.sln
\MyProject\src\MyProject.WebApp\MyProject.WebApp.csproj
\MyProject\test\MyProject.UnitTests\MyProject.UnitTests.csproj

So, how should I proceed? Is there any config I need to change? Thanks in advance.

Advitalitum commented 6 years ago

I have same problem. Also solution folders structure not saved in \MyProject.sln but it is in \MyProject\MyProject.sln. Solution in \MyProject\MyProject.sln is a clone of original solution. It has reference to template csproj file. \MyProject\ folder also contains folder with template project. One can exclude template folder as described in https://github.com/ligershark/sidewafflev2/issues/21, but solution \MyProject\MyProject.sln still contains reference to the template project. We can exclude the solution file from template too, but we will lose solution folder structure in \MyProject.sln.

giggio commented 6 years ago

@sayedihashimi Can you give us a hand here? Thanks!

Advitalitum commented 6 years ago

@giggio Found solution. You can create custom wizard. In .template.config\template.vstemplate place custom wizard before default wizard:

<!-- Custom Wizard -->
<WizardExtension>
  <Assembly>YourTemplate, Version=1.0.0.0, Culture=Neutral, PublicKeyToken=token</Assembly>
  <FullClassName>YourTemplate.CustomWizard</FullClassName>
</WizardExtension>
<!-- Default Wizard -->
<WizardExtension>
  <Assembly>Microsoft.VisualStudio.TemplateEngine.Wizard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</Assembly>
  <FullClassName>Microsoft.VisualStudio.TemplateEngine.Wizard.TemplateEngineWizard</FullClassName>
</WizardExtension>

Create field in custom wizard:

private string oldDestinationDirectory;

Place this to RunStarted method:

oldDestinationDirectory = replacementsDictionary["$destinationdirectory$"];
// Dirty hack here
var newDestinationDirectory = Path.Combine($"{replacementsDictionary["$destinationdirectory$"]}", @"..\");
replacementsDictionary["$destinationdirectory$"] = Path.GetFullPath(newDestinationDirectory);

in RunFinished:

Task.Run(() =>
{
    if (Directory.Exists(oldDestinationDirectory))
    {
        Directory.Delete(oldDestinationDirectory);
    }
});

You also may need to add references to System.IO and System.Threading.Tasks etc.

To exclude folder with template and original solution file you can use exclude in .template.config\template.json

"sources":[  
   {  
      "modifiers":[  
         {  
            "exclude":[  
               "TemplateFolder/**/*",
               "SolutionFile.sln"
            ]
         }
      ]
   }
]
Advitalitum commented 6 years ago

@giggio Found dirty hack for preserving folder structure in solution.

MAYBE IT IS DANGEROUS

We will preserve old solution file and open it on project creation.

Remove "SolutionFile.sln" from exclude section in .template.config\template.json. In SolutionFile.sln wrap project section with project template by #if (false), #endif :

#if (false)
Project("{FAE04EC0-301F-11D3-BF4B-00C12379EFBC}") = "YourTemplateProject", "YourTemplateProject", "{41CFBEEE-FEC0-42C3-B1AF-4BF82A132113}"
EndProject
#endif

Add two wizards. One before default wizard, one after.

<!-- BeforeWizard -->
<WizardExtension>
  <Assembly>YourTemplate, Version=1.0.0.0, Culture=Neutral, PublicKeyToken=token</Assembly>
  <FullClassName>YourTemplate.BeforeWizard </FullClassName>
</WizardExtension>
<!-- Default Wizard -->
<WizardExtension>
  <Assembly>Microsoft.VisualStudio.TemplateEngine.Wizard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</Assembly>
  <FullClassName>Microsoft.VisualStudio.TemplateEngine.Wizard.TemplateEngineWizard</FullClassName>
</WizardExtension>
<!-- AfterWizard -->
<WizardExtension>
  <Assembly>YourTemplate, Version=1.0.0.0, Culture=Neutral, PublicKeyToken=token</Assembly>
  <FullClassName>YourTemplate.AfterWizard </FullClassName>
</WizardExtension>

In Runstarted method of BeforeWizard place:

// Close new solution
var dte = (DTE) automationObject;
var solution = (Solution2) dte.Solution;
solution.Close();

// Delete old directory(in my case VS creating it) and change destination
var oldDestinationDirectory = replacementsDictionary["$destinationdirectory$"];
if (Directory.Exists(oldDestinationDirectory))
    {
        Directory.Delete(oldDestinationDirectory, true);
    }

var newDestinationDirectory = Path.Combine($"{oldDestinationDirectory}", @"..\");
replacementsDictionary["$destinationdirectory$"] = Path.GetFullPath(newDestinationDirectory);

In AfterWizard:

private DTE dte;

private string destinationDirectory;

private string solutionName;

public void RunStarted(object automationObject,
    Dictionary<string, string> replacementsDictionary,
    WizardRunKind runKind, object[] customParams)
{
    dte = (DTE) automationObject;
    destinationDirectory = replacementsDictionary["$destinationdirectory$"];
    solutionName = replacementsDictionary["$safeprojectname$"];
}

public void RunFinished()
{
    // Open "old" solution
    var solution = (Solution2) dte.Solution;
    var pathToOldSolution = Path.Combine(destinationDirectory, solutionName + ".sln");
    solution.Open(pathToOldSolution);
}

So, it works well but i don't know what kind of side-effects may appear with this approach.

sayedihashimi commented 6 years ago

I believe that this comment from a dev on the team addresses this issue. Can you take a look and let me know if that is what you're looking for?

giggio commented 6 years ago

@sayedihashimi it works, the physical folder structure is as expected. But I lost my solution folders from the original solution. Is there a way to get it back? BTW, it would be a good idea to share how to fix this, step by step, in a wiki, etc, that is discoberable. Or maybe bake the solution into Sidewaffle. @Advitalitum thanks for the help, but @sayedihashimi solution was simpler. Only one wizard. :)

sayedihashimi commented 6 years ago

@giggio Solution Folders are not supported. To create solution folders you'll need a custom wizard to handle creating those and placing projects inside of them.

codewithtyler commented 6 years ago

@giggio were you able to find a solution for this using the responses above?

giggio commented 6 years ago

@tylerbhughes yes, the response from Sayed above helped: https://github.com/ligershark/sidewafflev2/issues/25#issuecomment-376288902

codewithtyler commented 6 years ago

Great, I'll close this then since it's been resolved.

giggio commented 6 years ago

Great, but it would be nice to post a more structured guidance somewhere, probably on some docs or on the readme.

codewithtyler commented 6 years ago

@giggio Thanks for the input. We're still working on documentation. It's just a slow process.

sayedihashimi commented 6 years ago

FYI the wiki we have in this repo is editable by all users.

KKacer commented 5 years ago

@Advitalitum Did your solution work?

I want to see my solution folders As-Is in the generated projects.

How and where should we create and add these two new wizards? These should be added to the existing base SideWaffle project which will publish the template, or as an additional original VSix project, or an additional SideWaffle Vsix one?

avrahamcool commented 5 years ago

@KKacer I can confirm that this solution works. you can take a look at my VS addon here

what I did was to create a new class library beside the packager project - to hold the wizards. (I have 2 set of wizards because my addon adds 2 different multilayered projects skeletons) then have the packager project reference the wizards project.

also, in the directory of the "main" solution - I have child directories. in each one I have a solution with a complete hierarchy of projects. each one of them is bundled differently as a template.

I hope that the source code can help you get were you need.

KKacer commented 5 years ago

@avrahamcool Great! Will take a look at your project ASAP. Yeah I hope so.

KKacer commented 5 years ago

@avrahamcool Watched that, awesome! the Wizard seems to be what I wanted, the result in your final distributed VSix also looks cool and as expected. Still I have some questions related to it, maybe you would like to give me a help, Can we discuss 5 minutes regarding this in Skype/Discord, ...?

avrahamcool commented 5 years ago

Hi @KKacer I'll try to help. Let's take it to a private channel via email (can be found in my user profile)