solution level tasks

Hi,

I haven't been able to find a way to hook tasks in at the solution level. I was hoping for something as easy as with projects (where you just hook into the project file xml).

What's the best way to accomplish this To be a bit more specific, there are things I want to happen before anything gets built and things I want to happen after everything gets built. All the examples I've hunted down seem to make modifications at the project level.

I assume I could create a Proj that uses the MSBuild task to build the solution and add whatever tasks I want before and after, but I'm hoping to find a way to do this while remaining inside visual studio (so that they are triggered when I hit build within visual studio).

thanks,
Orlando



Answer this question

solution level tasks

  • Mike Droney MSFT

    Did you ever get this sorted out

  • Mike Hackett

    As the whole msbuild system is extensible it would have been nice to have extension points (start, end, startproj, endproj etc) in the generated sln msbuild file for the time being (until the sln itself becomes a msbuild.proj file).

    But can't you mimic the behaviour by doing an import in each of your project files to a solution.targets file. Then override BeforeClean( ) in each of your projectfile, call a target (ie SolutionStart) in solution.targets and have the SolutionStart target produce an Output.
    As far as I know MSBuild will not rebuild a target if it has already been build (if the Output is present), so during a build the SolutionStart target will only be run once.

    Disclaimer: I didn't try this, it is just a thought...


  • Bryce Covert

    Hey,

    Has anyone thought of including an arbratary msbuild project in the solution, then making all other projects dependant on it for the pre build, and making another where it is dependant on all projects as a post


  • BartoszBien

    This is very disappointing, when will the VS.NET team fix this so that studio can just be pointed to a "targets" file   Or at least allow us to associate a .targets file with a solution and say "use this for my solution builds "

    It's a MUCH needed freature to allow building on the command line and vs.net off of a single msbuild file.

  • NateThornton

    You build each solution twice, is'nt it

    first:

    Sayed Ibrahim Hashimi wrote:

    ...

    <!-- Have MSBuild emit the solution -->

    <MSBuild Projects="@(SlnFiles)" Targets="Build"/>

    second:

    Sayed Ibrahim Hashimi wrote:

    ...

    <Target Name="CoreBuildSolution">

    <MSBuild Projects="$(theSolution)" Targets="Build"/>

    </Target>

    It semms to me that call of ValidateSolutionConfiguration is better to create .sln.proj files.

    Alex


  • JNadal

    Ok - first of all, great question.

    There is a short answer and a long answer.  The short answer to your exact question is that unfortunately, it's not possible. Sad

    However, you may be interested in a small trick for your bag of tricks.  It is entirely possible to do from the command line, if you generate the msbuild project that represents your solution.  On the command line, set an environment variable called MSBuildEmitSolution and set it to 1.  Then run msbuild on the solution file.  This will generate a project file that represents the solution, that you can then build from the command line.

    The reason why I said it's not possible is because this file will not behave in the IDE exactly as a solution would. 

    I am sorry I don't have a better answer for you.  Authoring a separate project file that invokes msbuild on the solution (like you pointed out) also only works on the command line.

    Faisal Mohamood
    MSBuild Team

  • Reinout Waelput

    Great solution Alexey!

    I took the idea you gave and used it for inspiration to write a powershell utility for doing all this work:
    Here is a link to my page describing it.


  • Prayag Gandhi

    Unfortunately msbuildemitsolution is working not so well: it’s loosing some projects from my solution file. So I’ve wrote simple C# code to generate project, preprocess and build it

    It's part of special service, that builds solutions immediately after svn commit.

    using System;

    using System.Collections.Generic;

    using System.Collections.Specialized;

    using System.IO;

    using System.Security.AccessControl;

    using System.Text;

    using System.Text.RegularExpressions;

    using System.Xml;

    using Microsoft.Build.BuildEngine;

    using Microsoft.Build.Utilities;

    using Microsoft.Win32;

    namespace Parus.Build.Tasks

    {

    public abstract class TornadoBase : ITask

    {

    private static readonly string s_MakeHtml = "MakeHtmlLog.xslt";

    protected abstract TornadoBaseConfiguration Config { get;}

    protected abstract string TaskLabel { get;}

    protected string MailList

    {

    get { return Config.MailList; }

    }

    protected abstract string MsBuildMainTask { get; }

    protected string Solution

    {

    get

    {

    return Path.Combine(SourceDir, Config.Solution);

    }

    }

    protected string SourceDir

    {

    get { return Path.Combine(Config.SrcDir, m_Branch.Replace(@"/", @"\")); }

    }

    protected string LogDir

    {

    get { return Config.LogDir; }

    }

    protected abstract string ReleaseDir { get; }

    protected string KeyFile

    {

    get { return Config.KeyFile; }

    }

    protected abstract void MainBuildParams();

    protected abstract string Configuration { get; }

    protected abstract string Platform { get; }

    private StringDictionary m_Properties;

    protected StringDictionary Properties

    {

    get

    {

    if (null == m_Properties)

    {

    m_Properties = new StringDictionary();

    foreach (string key in Config.ProjectProperties.AllKeys)

    m_Properties.Add(key, Config.ProjectProperties[key].Value);

    }

    return m_Properties;

    }

    }

    protected QueueItem m_Item;

    protected string m_Branch;

    #region IBuildTask Members

    protected Target m_InitialTarget;

    protected bool DoBuild()

    {

    try

    {

    string newFile = Path.ChangeExtension(Solution, ".proj");

    string parusTargets = ModuleProvider.GetModule("Parus.targets");

    string taskConditionPattern =

    string.Format(

    @"^\s*\(\s*'\$\(Configuration\)'\s*==\s*'{0}'\s*\)\s*and\s*\(\s*'\$\(Platform\)'\s*==\s*'{1}'\s*\)\s*$",

    Configuration, Platform);

    Regex taskConditionTest =

    new Regex(taskConditionPattern,

    RegexOptions.Singleline | RegexOptions.Compiled);

    string targetNamePattern = @"^(.+\Smile{0,1}Rebuild$";

    Regex targetNameTest =

    new Regex(targetNamePattern,

    RegexOptions.Singleline | RegexOptions.Compiled);

    List<string> csProjects = new List<string>();

    List<Target> targets = new List<Target>();

    Engine engine = new Engine(

    ToolLocationHelper.GetPathToDotNetFramework(

    TargetDotNetFrameworkVersion.Version20));

    Project solution = new Project(engine);

    XmlDocument log = new XmlDocument();

    log.LoadXml(m_SvnLog);

    solution.Load(Solution);

    solution.DefaultTargets = MsBuildMainTask;

    solution.AddNewImport(parusTargets, null);

    AppendHostSpecificProps(solution);

    foreach (Target target in solution.Targets)

    {

    targets.Add(target);

    }

    for (int i = targets.Count - 1; i >= 0; i--)

    {

    if (!targetNameTest.IsMatch(targetsIdea.Name))

    {

    solution.Targets.RemoveTarget(targetsIdea);

    targets.RemoveAt(i);

    }

    else

    {

    List<BuildTask> tasks = new List<BuildTask>();

    foreach (BuildTask task in targetsIdea)

    {

    tasks.Add(task);

    }

    for (int j = tasks.Count - 1; j >= 0; j--)

    {

    if (!string.IsNullOrEmpty(tasks[j].Condition))

    {

    if (!taskConditionTest.IsMatch(tasks[j].Condition))

    {

    targetsIdea.RemoveTask(tasks[j]);

    tasks.RemoveAt(j);

    }

    else

    {

    if (tasks[j].Name == "MSBuild")

    {

    string csproj = tasks[j].GetParameterValue("Projects");

    if (Path.GetExtension(csproj) == ".csproj")

    {

    string oldProp = tasks[j].GetParameterValue("Properties");

    string newProp = "";

    if (Properties.ContainsKey(""))

    newProp = Properties[""];

    if (Properties.ContainsKey(csproj))

    newProp = Properties[csproj];

    tasks[j].SetParameterValue("Properties",

    string.Format("{0}; {1}", oldProp, newProp));

    csProjects.Add(csproj);

    }

    }

    }

    }

    }

    }

    }

    BuildItemGroup ig = solution.AddNewItemGroup();

    foreach (string project in csProjects)

    {

    ig.AddNewItem("SolutionProjectsList", project, false);

    ProcessCSProject(engine,

    Path.Combine(Path.GetDirectoryName(Solution), project));

    }

    m_InitialTarget = solution.Targets.AddNewTarget("SvnInfoTarget");

    InitialWarning("SVN_REVISION", log.SelectSingleNode("/log/logentry/@revision").InnerText);

    InitialWarning("SVN_AUTHOR", log.SelectSingleNode("/log/logentry/author").InnerText);

    InitialWarning("SVN_DATE", log.SelectSingleNode("/log/logentry/date").InnerText);

    InitialWarning("SVN_MSG", log.SelectSingleNode("/log/logentry/msg").InnerText);

    foreach (XmlNode node in log.SelectNodes("/log/logentry/paths/path"))

    {

    InitialWarning("SVN_PATH", node.InnerText);

    }

    solution.InitialTargets = m_InitialTarget.Name;

    MainBuildParams();

    solution.Save(newFile);

    //build

    XmlLogger xmlLogger = new XmlLogger();

    xmlLogger.Parameters = XmlLog;

    engine.RegisterLogger(xmlLogger);

    solution.SetProperty("Configuration", Configuration, "1==1");

    solution.SetProperty("Platform", Platform, "1==1");

    bool res = solution.Build(MsBuildMainTask);

    engine.UnregisterAllLoggers();

    return res;

    }

    catch (Exception e)

    {

    Logger.WriteLine(VerbosityLevel.Quiet, e.ToString());

    return false;

    }

    }

    private static void AppendHostSpecificProps(Project solution)

    {

    BuildPropertyGroup props = solution.AddNewPropertyGroup(false);

    string installDir = GetDevEnvironmentPath();

    if (installDir != null)

    {

    props.AddNewProperty("DevEnvDir", installDir);

    }

    }

    private static string GetDevEnvironmentPath()

    {

    try

    {

    RegistryKey installKey = Registry.LocalMachine.OpenSubKey(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\8.0",

    RegistryKeyPermissionCheck.Default,

    RegistryRights.ReadKey | RegistryRights.QueryValues);

    string installDir = installKey.GetValue("InstallDir") as string;

    if (installDir != null && Directory.Exists(installDir))

    {

    if (!installDir.EndsWith(new string(Path.DirectorySeparatorChar, 1)))

    installDir += Path.DirectorySeparatorChar;

    return installDir;

    }

    }

    catch //

    {

    }

    return null;

    }

    protected void InitialProperty(string name, string value)

    {

    BuildTask prop;

    prop = m_InitialTarget.AddNewTask("CreateProperty");

    prop.SetParameterValue("Value", value);

    prop.AddOutputProperty("Value", name);

    }

    protected void InitialWarning(string key, string value)

    {

    BuildTask warn;

    warn = m_InitialTarget.AddNewTask("Warning");

    warn.SetParameterValue("Code", key);

    warn.SetParameterValue("Text",value);

    }

    protected virtual void ProcessCSProject(Engine engine, string project)

    {

    }

    protected string m_SvnLog;

    public string XmlLog

    {

    get { return Path.Combine(LogDir, m_Item.Revision + ".xml"); }

    }

    public string HtmLog

    {

    get { return Path.Combine(LogDir, m_Item.Revision + ".htm"); }

    }

    public bool Execute(QueueItem item)

    {

    bool res;

    string subj;

    m_Item = item;

    try

    {

    if (!Directory.Exists(LogDir))

    Directory.CreateDirectory(LogDir);

    // delete log files

    File.Delete(XmlLog);

    File.Delete(HtmLog);

    // find branch

    m_Branch = BuildHelper.GetBranch(m_Item.Repository, m_Item.Revision);

    // clear working directory

    BuildHelper.CleanWorkDir(SourceDir, m_Item.Revision);

    // get revision info

    m_SvnLog = BuildHelper.StartSvn(string.Format(

    "log --xml -v -r {0} {1}",

    m_Item.Revision,

    SourceDir));

    // main build

    res = DoBuild();

    string svnLogFile = Path.GetTempFileName();

    using (StreamWriter logWriter = new StreamWriter(svnLogFile))

    {

    logWriter.WriteLine(m_SvnLog);

    logWriter.Flush();

    logWriter.Close();

    }

    // make HTML log

    StringDictionary makeHtmlParams = new StringDictionary();

    makeHtmlParams.Add("svnlog", svnLogFile);

    BuildHelper.Xslt(

    XmlLog,

    ModuleProvider.GetModule(s_MakeHtml),

    HtmLog,

    null);

    File.Delete(svnLogFile);

    // renew revision comment

    subj = res "OK" : "Fails";

    BuildHelper.UpdateSvnLog(m_Item.Repository, m_Item.Revision, subj);

    }

    catch (Exception e)

    {

    res = false;

    subj = "Fails";

    using (

    StreamWriter htmlWriter = new StreamWriter(HtmLog, false, Encoding.UTF8))

    {

    htmlWriter.WriteLine("<html><h1>Build {0}</h1><h2>{1}</h2>",

    m_Item.Revision, e.Message);

    foreach (

    string s in

    e.ToString().Split(new string[] {"\n"}, StringSplitOptions.None))

    {

    htmlWriter.WriteLine("<p>{0}</p>", s);

    }

    htmlWriter.WriteLine("</html>");

    htmlWriter.Flush();

    htmlWriter.Close();

    }

    }

    try

    {

    File.Copy(HtmLog, Path.Combine(LogDir, "Default.htm"), true);

    BuildHelper.Alert(HtmLog, MailList, string.Format("{0} {1} ({2}): {3} {4}",TaskLabel, m_Item.Revision, m_Branch, subj, m_Item.Comment));

    if (res)

    OnSuccessBuild();

    else

    OnUnsuccessBuild();

    }

    catch

    {

    res = false;

    }

    return res;

    }

    protected StringDictionary m_MainBuildParams;

    protected virtual void OnUnsuccessBuild()

    {

    }

    protected virtual void OnSuccessBuild()

    {

    }

    #endregion

    }

    }


  • Javier_Uy

    Here is an all MSBuild solution:
    By using Keith Hills SetEnvVar task we can write an MSBuild file to drive the whole process, and we can place customizations within this file.
    Place this project file in the same folder that contains the solution file(s) that you want to build.

    <!--=======================================================================================

    MSBuild file which can be used to inject steps into the building of solution files

    ===========================================================================================-->

    <Project InitialTargets="SetMSBuildEmit" DefaultTargets="BuildSolution" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

    <PropertyGroup>

    <!-- Location that holds all needed assemblies for the shared tasks -->

    <SharedTasksDir>..\tasks\</SharedTasksDir>

    <!-- Assembly that contains the custom tasks -->

    <EnvAssemblyFilename>Sedodream.MSBuild.dll</EnvAssemblyFilename>

    <!-- Where is the solution file(s) located -->

    <BuildSolutionDir>.\</BuildSolutionDir>

    </PropertyGroup>

    <ItemGroup>

    <!-- Item for solutions file(s) -->

    <SlnFiles Include="$(BuildSolutionDir)*.sln"/>

    </ItemGroup>

    <!--Let MSBuild know where to find our tasks-->

    <UsingTask AssemblyFile="$(SharedTasksDir)$(EnvAssemblyFilename)" TaskName="SetEnvVar"/>

    <!--

    Set the envrionment variable msbuildemit solution to 1 to create the msbuild

    project file that represents the solution.

    -->

    <Target Name="SetMSBuildEmit">

    <SetEnvVar Variable="msbuildemitsolution" Value="1"/>

    </Target>

    <Target Name="BuildSolution" DependsOnTargets="SetMSBuildEmit">

    <!-- Have MSBuild emit the solution -->

    <MSBuild Projects="@(SlnFiles)" Targets="Build"/>

    <!-- Create a new item to pick up the newly generated file -->

    <CreateItem Include="*.sln.proj">

    <Output TaskParameter="Include" ItemName="SolutionMSBuildFiles"/>

    </CreateItem>

    <!--

    Call MSBuild for each solution file. Pass the property:

    theSolution=SOLUTION_FILE_TO_BUILD.

    In the DoBuildSolution we use this to determine which file to build.

    -->

    <MSBuild Projects="$(MSBuildProjectFile)"

    Targets="DoBuildSolution"

    Properties="@(SolutionMSBuildFiles->'theSolution=%(Filename)%(Extension)')"/>

    </Target>

    <!--============================================================================================

    Content used when this file is invoked above

    ================================================================================================-->

    <!--

    Define dependencies for the solution build. This can be extended like other XXXDependsOn Properties

    -->

    <PropertyGroup>

    <DoBuildSolutionDependsOn>

    BeforeDoBuildSolution;

    CoreBuildSolution;

    AfterDoBuildSolution

    </DoBuildSolutionDependsOn>

    </PropertyGroup>

    <Target Name="DoBuildSolution" DependsOnTargets="$(DoBuildSolutionDependsOn)" />

    <Target Name="BeforeDoBuildSolution">

    <Message Text="**************Before building your solution*****************" Importance="high"/>

    </Target>

    <!--

    Here is where we actually build the solution passed to us.

    -->

    <Target Name="CoreBuildSolution">

    <MSBuild Projects="$(theSolution)" Targets="Build"/>

    </Target>

    <Target Name="AfterDoBuildSolution">

    <Message Text="##############After building your solution##################" Importance="high"/>

    </Target>

    </Project>

    Then invoke msbuild.exe on this file with:

    >msbuild.exe SolutionBuild.proj /t:BuildSolution

    You can place customizations in BeforeDoBuildSolution and AfterDoBuildSolution. These targets should be called for every solution in that folder, and you can change the SlnFiles to include more if you want. I've only used this to build 1 solution file, but I assume you can build multiple with this.

    More detailed info at my blog: www.sedodream.com for those who are interested. I'm very interested in hearing feedback regarding this. I know that several people have asked about this in the past.

    Sayed Ibrahim Hashimi
    www.sedodream.com


  • AlucardJC

    Hi!

    This is my way to solve problem. I'm using the bat file like this


    set msbuildemitsolution=1
    rem shortest task only to generate proj file from solution
    msbuild solution.sln /t:ValidateSolutionConfiguration
    set msbuildemitsolution=
    rem xslt transformation of project
    msxsl solution.sln.proj style.xslt -o solution.proj
    msbuild solution.proj /t:Rebuild

    The result of xslt transformation looks like this:

    <Project DefaultTargets="Build" InitialTargets="ValidateSolutionConfiguration" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    ...
    <!-- inserted by xslt -->
    <Target Name="MyTargeBeforeRebuildSolution">
    ...
    </Target>

    <!-- inserted by xslt -->
    <Target Name="MyTargeAfterRebuildSolution">
    ...
    </Target>
    <Target Name="Rebuild" Condition="'$(CurrentSolutionConfigurationContents)' != ''">
    <!-- inserted by xslt -->
    <CallTarget Targets="MyTargetBeforeRebuildSolution" />
    <!-- original line -->
    <CallTarget Targets="Project1:Rebuild;Project2:Rebuild;" RunEachTargetSeparately="true" />
    <!-- inserted by xslt -->
    <CallTarget Targets="MyTargeAfterRebuildSolution" />
    </Target>
    </Project>


  • Bespi

    I have tried the msbuildemit trick and looked at the resulting proj file -- this isn't quite what I want. Perhaps someone can point me in the right direction I am new to msbuild, but I have done a lot of reading and I am not finding what I need.

    I have a solution file with 30 projects. The projects are not organized in some systematic and convinient way -- they are all over the place so no assumption can be made about the project tree structure. What I want is the ability to get to the list of project references (as specified in the solution file) so that I can recursively get them from VSS I don't want to explicitly hardcode each referenced project in my msbuild file. If the projects are already referenced in the solution, I shouldn't have to re-enter them again in the .proj file, correct Unfortunately, the proj file generated from the msbuildemit=1 approach has all the VSS info stripped out of it. Once the referenced projects are retrieved from VSS, I'd like to be able to build them -- again, without having to explicitly list them one by one. Is that possible

    THank you


  • Brian_t

    OCardoso wrote:

    I haven't been able to find a way to hook tasks in at the solution level. I was hoping for something as easy as with projects (where you just hook into the project file xml).

    What's the best way to accomplish this To be a bit more specific, there are things I want to happen before anything gets built and things I want to happen after everything gets built. All the examples I've hunted down seem to make modifications at the project level.

    I have an alternative that allows you to do this, you can find it here. Although, it does require an import at the project level.

    Using it you can hook into the solution level build process and execute tasks at the start and then execute tasks at the end (of the solution build). You can also set and get state for the build that is accessible from all projects.

    OCardoso wrote:

    I assume I could create a Proj that uses the MSBuild task to build the solution and add whatever tasks I want before and after, but I'm hoping to find a way to do this while remaining inside visual studio (so that they are triggered when I hit build within visual studio).


    My option works from within the IDE as well.

    Check it out and give me feedback!

  • jirkan

    Alexey ,

    I try to build your code but I get 3 build errors:

    TornadoBaseConfiguration is undefinded

    QueueItem is undefinde,

    another thing can you tell me what this code exactly do and how to run it

    Thanks,

    Nabeel.


  • sally8377

    This is a good point, I'll update this later tonight.
    Also another enhancement I'll add is the ability to call other targets besides build.

    Thanks,
    Sayed Ibrahim Hashimi
    www.sedodream.com

  • solution level tasks