Solution-wide pre-build event?
Asked Answered
D

7

89

I have a solution in Visual Studio which contains several projects. I'd like to run a command at the very beginning of every build - no matter which projects are involved and whether or not they are up-to-date.

Essentially I need something similar to a solution-wide pre-build event, but unfortunately VS does not appear to support these. Does anyone know an alternative way of achieving what I need?

Debut answered 19/2, 2010 at 10:13 Comment(0)
T
49

Unusual requirement. But it can be done. Add a new project to your solution, use the Visual C++ > General > Makefile Project template. Set its NMake > Build Command Line setting to the commands you want to execute. Use Project > Project Dependencies to make all other projects depend on it.

Townscape answered 19/2, 2010 at 10:31 Comment(8)
I don't recommend this, the answer below pointing to my blog post for this is the way to go. Post URL: sedodream.com/2010/10/22/MSBuildExtendingTheSolutionBuild.aspxSchnur
I don't recommend that. My solution can be done entirely in the IDE without repeatedly hacking the project file by hand. And it does not generate an "empty dll".Townscape
The fact that the IDE build and the system build use different programs is entirely maddening! Sayed's solution will only work with MSBuild, not within the IDE. However, maintaining the project dependencies has far too much overhead to be practical for a large project.Stepp
@HansPassant project files are meant (by Microsoft) to be modified "by hand", and doing so does not amount to "hacking", especially for one-time tasks like these. MSBuild project files are fully documented on MSDN, and there exists full support for adding custom functionality. Sayed here wrote the book on that.Woof
This is a terrible hack. I do not recommend thisGerminative
@SayedIbrahimHashimi Your strategy does not work inside Visual Studio. How are you recommending it in a question that starts with "I have a solution in Visual Studio..."?Coacher
Not that unusual. If you want to have a common set of tools run by every developer as a private build (to mimic a part of CI build on their workstations, it would be very convenient to attach such set of commands (e.g. FxCop, StyleCop, calculating test coverage, ...) to a whole solution once and don't worry about it again.Divot
Yes it is a hack, and it WORKS PERFECTLY. It can be 100% done using just the Visual Studio's IDE, without editing any project file with a text editor (which also looks like a hack to me), nor any additional third-party software. Thus it makes the dependencies much cleaner when newcomers try to understand the whole Solution. And that's why this is a popular and effective approach, which works both in developer's Local Machine builds and also in VSTS server builds. I hope Microsoft someday implements Solution pre/post-Build Events in the IDE.Atheistic
S
44

Short overview of my variants below

just a note: it is incomplete list of all existing (see also other answers etc.), I only support my original tricks in actual state...

summary

Notes:

  • 1 - Requires no any additional extensions. But it may work only via projects-level so we use it for emulating the our solution-level... It is hard and inconvenient for common solution, but is variant. See below.
  • 2 - The original engine of vsSolutionBuildEvent provides a few ways of unified support of the VS and msbuild.exe. A simple way the targets mode to call the after.<name>.sln.targets that available only for msbuild.exe (this does not requires additional steps, simply action). But only original engine (inc. vsCommandEvent) may allow additional scripting which support for example (7zip archiver, packing of nuget package without nuget.exe, remote servers etc.). However, it's not important for our question/problem and you can use any available option to support the solution-level if you see + above.

Variant 1: Microsoft.VisualStudio.Shell.Interop

This variant is not for simple users of VS. However, it can be useful for your complete solution etc.

You should implement, for example:

e.g:

public sealed class YourPackage: Package, IVsSolutionEvents, IVsUpdateSolutionEvents2
{
...
    public int UpdateSolution_Begin(ref int pfCancelUpdate)
    {
        //TODO:
    }
}

Then, register handler with 'Advise' methods as priority listener, i.e. for IVsUpdateSolutionEvents2 you should use the AdviseUpdateSolutionEvents

It is important, because the BuildEvents (see EnvDTE) - probably will not help and may work too late - Example

Sample with AdviseUpdateSolutionEvents:

// http://msdn.microsoft.com/en-us/library/microsoft.visualstudio.shell.interop.ivssolutionbuildmanager2.aspx
private IVsSolutionBuildManager2 sbm;

// http://msdn.microsoft.com/en-us/library/bb141335.aspx
private uint _sbmCookie;
...

sbm = (IVsSolutionBuildManager2)ServiceProvider.GlobalProvider.GetService(typeof(SVsSolutionBuildManager));
sbm.AdviseUpdateSolutionEvents(this, out _sbmCookie);

Where:

  • the sbm field should be as part of class for protection from GC.
  • to get the SVsSolutionBuildManager service is used the ServiceProvider but it can be as you need. See msdn

Now we can work with all projects at once - solution-level.

Variant 2: Targets & Map of projects.

ok, you love something like this - MSBuild: Extending the solution build, but this variant may work with build processes from msbuild.exe and not from VS IDE...

But the VS is also uses targets (Build, Rebuild, Clean, ..) in project files (*.csproj, *.vcxproj, ..) when the build-operations is started. So we can also try this, but remember:

  • The VS is also ignores amazing .sln file. It forms the all end-targets from loaded environment with EnvDTE etc.
  • The .sln should processed by msbuild.exe only as: automatically generate the .metaproj (in memory by default), which contains 'what and when' will be built. Including a common targets for all projects if exists, for example:
...
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\SolutionFile\ImportAfter\*" Condition="'$(ImportByWildcardBeforeSolution)' != 'false' and exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\SolutionFile\ImportAfter')" />
<Import Project="D:\tmp\p\after.name.sln.targets" Condition="exists('D:\tmp\p\after.name.sln.targets')" />
<Target Name="Build" />
<Target Name="Rebuild" />
<Target Name="Clean" />
<Target Name="Publish" />
  • And yes, the .metaproj is also cannot be viewed by VS IDE.

Therefore, for work with common targets from VS IDE you can use only project files with some limitation (without modification/extending of VS, it means).

And so, if you need the common solution (i.e. you may not know about projects etc. - this can be, for example, for some box solutions and similar):

  • Add your common .targets file into all your projects (it can be automatically with any tool, inc. the NuGet events etc.) , for example: <Import Project="..\<SolutionFile>.targets" />
  • Then, you should use some limitation for:
    • "only - before all projects"
    • "only - after all projects"

And for example, yes, it can be the 'Map of projects':

  • The 'Map of projects' illustrates the solution-wide PRE/POST 'events' for build-operations from Visual Studio IDE (i.e. primary from VS IDE)
...
<Target Name="_Build" BeforeTargets="Build" DependsOnTargets="ProjectsMap">
    <CallTarget Targets="_BuildPRE" Condition="$(ScopeDetectFirst)" />
    <CallTarget Targets="_BuildPOST" Condition="$(ScopeDetectLast)" />
</Target>
<Target Name="_BuildPRE">
    <!-- ... -->
</Target>
<Target Name="_BuildPOST">
    <!-- ... -->
</Target>
...

In general, we'll use the map of projects and now we know 'what and when' should happen. It's safe for all or most cases (changes of build order or removing the any projects from solution). However! you should manage <Import> section for new projects in first init. This is really inconvenient, but is also variant...

Variant 3: Plugin vsSolutionBuildEvent

Today it is the most complete solution for work with a lot of events as the Events-Catcher with a variety of advanced actions for maintenance of your projects and libraries, building processes and processes at runtime from your Visual Studio and MSBuild Tool.

Different action types for all subprojects at once in solution as Solution-Events or individually for each.

https://visualstudiogallery.msdn.microsoft.com/0d1dbfd7-ed8a-40af-ae39-281bfeca2334/

plugin - vsSolutionBuildEvent

How it works inside

If you want to use the Variant 1 above or need to see how to work with Shell.Interop, EnvDTE, IVsUpdateSolutionEvents2, MSBuild Engine etc., see here:

scheme

Variant 4. EnvDTE.CommandEvents

This variant is also not for simple users of VS. However, as for Variant 1 it can be useful for your box-solution etc.

It is not the same, but yes, it's also possible with EnvDTE.CommandEvents like in Variant 1 above.

You should already know (see above) about this solution for priority work with current type of the build action... So why not to use this as primary solution for current problem ?

_cmdEvents.BeforeExecute += (string guid, int id, object customIn, object customOut, ref bool cancelDefault) => {

    if(UnifiedTypes.Build.VSCommand.existsById(id)) {
        // ... your action
    }

};

Where: Description | guid | id |In |Out| --------------------------|---------------------------------------|-----|---|---| Started: Build Solution |{5EFC7975-14BC-11CF-9B2B-00AA00573819} | 882 | | | Started: Rebuild Solution |{5EFC7975-14BC-11CF-9B2B-00AA00573819} | 883 | | | Started: Clean Solution |{5EFC7975-14BC-11CF-9B2B-00AA00573819} | 885 | | |

http://vsce.r-eg.net/doc/Features/Solution-wide/

Moreover, optional you can suppress this commands if you need. In variant below you will see the complete solution for this way.

Variant 5. Plugin vsCommandEvent

https://visualstudiogallery.msdn.microsoft.com/ad9f19b2-04c0-46fe-9637-9a52ce4ca661/

It also presents advanced handler of most events, but unlike the first it specialized for MS Visual Studio for advanced work with all commands and output data as manager of this. Not only for projects and solutions, but also for the whole Visual studio IDE.

In general, it is the common solution of Variant 4 and you can simply override all commands above to solve this problem.

And for the same Event-Actions model like in vsSolutionBuildEvent it can be useful for most cases.

scheme

"Help me with variants"

There are open implementation for all these variants. See here and smile:

Silvey answered 19/8, 2013 at 9:57 Comment(2)
The plugin (vsSolutionBuildEvent) is the only thing I could find that worked from within VS. Huge +1 since I've been researching this for several days now, thanks!Overhasty
@Overhasty I added new variants, you can also try the new vsCommandEvent if needed. /Glad that it helped you.Silvey
M
13

We do this by adding an empty project and setting build events for this project. Then you have to give each project dependency to this empty project to make sure that it gets built everytime.

Montford answered 19/2, 2010 at 10:24 Comment(2)
Which happens to be exactly Hans Passant's answer, although you answered it before him with a little less details +1.Atheistic
I like this answer because it doesn't involve adding C++ projects that add a bunch of junk into the root of your solution. Please note, after adding this project then you'll want to manually edit its csproj file and add the following, to ensure it executes before EVERY build: <PropertyGroup> <DisableFastUpToDateCheck>true</DisableFastUpToDateCheck> </PropertyGroup>Wilhelm
C
2

For code that is being built with MSBUILD 15, the easiest way is to put a Directory.Build.targets file in the root of the path (not necessarily solution folder) under which the projects lies and customize it. This runs even when you build from within visual studio or using command prompt.

https://learn.microsoft.com/en-us/visualstudio/msbuild/customize-your-build?view=vs-2019

Cosmography answered 5/9, 2020 at 4:40 Comment(1)
This was exactly the thing I was looking for. Thanks.Mown
E
1

Another old post but inspired by @reg solution I wanted to run a simple build timer that would log the elapsed time for a solution build. I got the build events working using a powershell module which I load via the package manager console when the Visual Studio IDE starts.

So create a powershell module like BuildEvents.psm1:

<#
.SYNOPSIS
    Register solution build events

.DESCRIPTION
    Registers the OnBuildBegin and OnBuildDone events for the entire solution
    De-registers the events if called multiple times.

.EXAMPLE
    RegisterBuildEvents
#>
function RegisterBuildEvents{
  try {
    Unregister-Event -SourceIdentifier "OnBuildBegin" -Force
  } catch {
    #we don't care if this doesn't work
  }
  try {
    Unregister-Event -SourceIdentifier "OnBuildDone" -Force
  } catch {
    #we don't care if this doesn't work
  }
  $obj = [System.Runtime.InteropServices.Marshal]::CreateWrapperOfType($dte.Application.Events.BuildEvents, [EnvDTE.BuildEventsClass])
  Register-ObjectEvent -InputObject $obj -EventName OnBuildBegin -Action {
    # do stuff here on build begin
    Write-Host "Solution build started!"
  } -SourceIdentifier "OnBuildBegin"
  Register-ObjectEvent -InputObject $obj -EventName OnBuildDone -Action {
    # do stuff here on build done
    Write-Host "Solution build done!" 
  } -SourceIdentifier "OnBuildDone"
}

# export the functions from the module
export-modulemember -function RegisterBuildEvents

Import the module when the Package Manager host initialises:

  1. In the package manager console type $profile to get the location of your powershell profile
  2. Browse to that directory on disk, if there is no file there create one with the name returned by the above command (e.g. NuGet_profile.ps1)
  3. Open the file in notepad and add the following lines

    Import-Module -Name <Path to your ps module>\BuildEvents -Force
    RegisterBuildEvents
    
Ergonomics answered 23/5, 2018 at 14:16 Comment(0)
A
0

It's been a while, and some things in .Net infrastructure changed since, giving new options. Now my choice for solving this king of problem are nuget packages. I put my build steps into package which then included into every single project. Helpfully, Visual Studio package manager gives overview of packages on solution level, so it's pretty easy to check this rule.

Aircrew answered 5/1, 2018 at 18:30 Comment(0)
J
0

I know it is not a "solution"-wide, but I tried to find the any way to run script on project build, so may be helpful for people, that would like any C# script to, for example, modify solution/project files before/after build.

So, as @Hans Passant posted above - we can create new Console Application and run this .exe on build event. Please, read comments to his post, as it is only applicable in case you don't need to integrate this logic to CI/CD and etc. So usage some limited. And in general, it is not the most beautiful solution.

So, for example, we have any project (AwsLambdaProject) and you need to modify .env file inside on each build to update internal variables.

  1. create new C# Application console (in my case it's name : PreBuildEventApp)
  2. build it and open properties of your target AwsLambdaProject
  3. find build->events tab in project properties. And type there "$(SolutionDir)\PreBuild\PreBuildEventApp\bin\Debug\net6.0\PreBuildEventApp.exe -solutionPath=$(SolutionDir)"

enter image description here So here we are calling our .exe and passing there one parameter (solution folder path). *Check, that path contains your valid console app .exe file.

So, your .exe will be executed before every "AwsLambdaProject" build and modify required files.

So for example, you try to write something to file to debug it :

enter image description here

And your file will be created in AwsLambdaProject project folder, not in console project/bin folder.

Notes :

To use async inside your console app, be sure, that AwsLambdaProject contains 7.1 or higher (i use 10.0).

enter image description here

Also, you may face with issue, when first build is failed and only second time it will be succeed. It is because you need to build you console app first to have .exe in right place. You can copy it manually to any folder - it will work fine. But if you need to change code - you need to do it again.

The second way is to click to solution and click "Project Build order", there you can find your "AwsLambdaProject" and add your console app as a dependency. Now, your console app will build first and your project where you need to modify files - second.

Also, you may faced with another one issue here - build event will work only on first build or on Solution Rebuild. To call your console app on every solution build - you need to modify two projects and add there :

<PropertyGroup>    
  <DisableFastUpToDateCheck>true</DisableFastUpToDateCheck>
</PropertyGroup>

It means - your two projects will build every time, without caching. Be careful, it may increase your usual build time on solution build, depending on project size.

Hope, will help someone. Have a good day!

Jackie answered 24/7, 2022 at 14:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.