Эта небольшая статья поможет:

  • Ознакомиться с событийной моделью построения проектов и решений MS Visual Studio;
  • Понять, как получить поддержку Command-Line режима devenv.exe для VSPackage (где он изначально не предусмотрен);
  • Понять, как эмулировать схожую модель событий от MSBuild Tools и транслировать на главный плагин;
  • Узнать, как работать по приоритетной подписке;
  • Узнать варианты получения контекста построения при обработке событий Visual Studio / MSBuild Tools;
  • Узнать об оценке MSBuild Property & MSBuild Property Functions;
  • Получить общие сведения межмодульного взаимодействия на слое абстракции для разнородных компонентов системы.

Синопсис


Мне довольно часто приходиться заниматься автоматизацией тех или иных процессов, поэтому не мудрено, что часть решений рано или поздно коснулись и Visual Studio.

На самом деле, эта статья, или даже заметка — результат рабочего и уже давно написанного плагина, который еще года 2 назад являлся лишь побочным продуктом при работе над одним проектом на C++. Однако мой дебют на Хабрахабре будет, пожалуй, с этого.

Недавно ко мне обратился со схожими потребностями (те, которые изначально был призван решать плагин) парень из DevDiv. В попытках решить его проблему весьма кастомной автоматизации стало понятным, что все-таки крайне необходимо осветить некоторые аспекты решения и взаимодействия между VS & MSBuild. Ведь этого материала изначально не было и вроде бы до сих пор нет в публичном доступе.

Предупреждение


Перед тем как начать, пожалуйста, обратите внимание, что материал не для пользователей Visual Studio, а для разработчиков. И призван в первую очередь — осветить некоторые аспекты решений и взаимодействия для указанных компонентов, если кто-то столкнулся со схожими задачами для собственных разработок. К примеру, как взаимодействовать с devenv.exe или как работать с приоритетной подпиской, и все что указано в списке выше.

О проблемах


Изначальная потребность, для нас обоих, была и есть работа с Solution-Context в рамках событийной модели студии.
Те, кто работал с Visual Studio, конечно же, должны знать о Pre/Post-build событиях уровня проектов. Однако, они не в полной мере удовлетворяют потребности управления проектами для всего решения, в особенности, когда этих проектов несколько десятков и т.п., что типично прежде всего для C++ проектов.

Есть несколько способов как решить подобную задачу. К примеру, выше упомянутый, сейчас осуществляет переход от решения, когда в Solution есть проект, от которого зависят все остальные.

Так почему MS до сих пор не выделит подобный контекст для своей IDE?

На самом деле, все тоже самое, что и с форматом .sln, который до сих пор представляет собой записки охотника, нежели валидный xml документ с файлами проектов, или что-то более гибкое и изящное.
Совместимость? Вероятно, однако ломать это надо было еще с приходом мажорных изменений в VS2010.

Так и с solution-context. Просто так поднять события на верхний уровень не выйдет, т.к. понадобится решить задачу по управлению msbuild данными и многое другое для одного единого контекста, а дописывать это в свой загруженный таймлайн особо никто и не спешит.

Обработка событий проектов и решений MS Visual Studio (VS2010, VS2012, VS2013, VS2015)


Для начала давайте рассмотрим вариант с EnvDTE. Для нашей задачи взор прежде всего устремляется на BuildEvents, который предоставляет доступную подписку на базовые события, такие как OnBuildBegin, OnBuildProjConfigBegin и тому подобное. То есть они и реализованы в виде публичных events.

Однако нет, в нашем случае они сработают слишком поздно, когда пойдет оповещение всех своих слушателей DTE. Нам же необходимо получить управление над ситуацией так скоро, насколько это возможно в нашем пакете.

К счастью, в VS выделяют приоритетной слой на обработку подобных вещей, они то и будут выполняются в первую очередь со всеми подобными вещами.

В общем виде — это такая же классика observer'а, реализованная посредством Advise методов для конкретных сервисов. Но обо всем по порядку.

Прежде всего нам необходимо обратить свое внимание на Microsoft.VisualStudio.Shell.Interop. Именно он поможет работать с базовыми «build-событиями» VS и прочими уровня solution, а именно — IVsUpdateSolutionEvents:

Для работы с вышеупомянутыми потребностями достаточно рассмотреть только базовый IVsUpdateSolutionEvents2, который доступен вплоть до VS2010. На самом деле желателен IVsUpdateSolutionEvents4 для работы с контекстом построения, однако он доступен только для VS2012 и старше.

Примечание: Для полноценной работы также скорее всего потребуется IVsSolutionEvents, но этот момент не рассматривается этой статьей. Если все-таки потребуется кому-либо, могу показать базовые приемы для обслуживания этого слоя.

Итак, нам необходимо имплементировать следующие базовые вещи интерфейса IVsUpdateSolutionEvents2:

// должны получить управление до того как начнется любой build-action
// и этой последний шанс отменить построение
int UpdateSolution_Begin(ref int pfCancelUpdate);

// вызывается для любого отмененного построения т.е. Cancel / Abort - пользователем или самой студией
int UpdateSolution_Cancel();

// вызывается при любой окончательной остановке построения.
// Обратите внимание, что вызов будет как завершение для нормального построения, так и после Cancel/Abort, т.е.:
//  * Begin -> Done
//  * Begin -> Cancel -> Done
int UpdateSolution_Done(int fSucceeded, int fModified, int fCancelCommand);

Все эти методы прежде всего и подходят для реализации уровня solution-context.
По проектам у нас предусмотрено:

int UpdateProjectCfg_Begin(IVsHierarchy pHierProj, IVsCfg pCfgProj, IVsCfg pCfgSln, uint dwAction, ref int pfCancel);
int UpdateProjectCfg_Done(IVsHierarchy pHierProj, IVsCfg pCfgProj, IVsCfg pCfgSln, uint dwAction, int fSuccess, int fCancel);

Которые будут вызываться для каждого, конфигурация которого должна пойти на построение. Обратите внимание на pHierProj аргумент, это гибкий способ ссылаться в любое место откуда пришло управление.

Базовая имплементация вашим пакетом не означает, что VS будет готова работать с IVsUpdateSolutionEvents. Как и было сказано выше, нам необходимо зарегистрировать нашего слушателя (кто имплементирует этот интерфейс):

public sealed class vsSolutionBuildEventPackage: Package, IVsSolutionEvents, IVsUpdateSolutionEvents2
{
...
    public int UpdateSolution_Begin(ref int pfCancelUpdate)
    {
        return VSConstants.S_OK;
    }
...
}

Сделать это необходимо с Advise методами, так для IVsUpdateSolutionEvents2 предусмотрен — AdviseUpdateSolutionEvents.

Пример регистрации:

/// <summary>
/// For IVsUpdateSolutionEvents2 events
/// http://msdn.microsoft.com/en-us/library/microsoft.visualstudio.shell.interop.ivssolutionbuildmanager2.aspx
/// </summary>
private IVsSolutionBuildManager2 spSolutionBM;

/// <summary>
/// Contains the cookie for advising IVsSolutionBuildManager2 / IVsSolutionBuildManager
/// http://msdn.microsoft.com/en-us/library/bb141335.aspx
/// </summary>
private uint _pdwCookieSolutionBM;
...
spSolutionBM = (IVsSolutionBuildManager2)ServiceProvider.GlobalProvider.GetService(typeof(SVsSolutionBuildManager));
spSolutionBM.AdviseUpdateSolutionEvents(this, out _pdwCookieSolutionBM);

Следует заметить, что получить доступ к сервису SVsSolutionBuildManager можно и другими путями. Используйте то, что удобно.

spSolutionBM, в примере выше, должен быть частью класса для протекции от GC.

Вот теперь мы готовы слушать в первых рядах. Важно также понимать, что мы не самые первые, но нам это и не нужно.

Поддержка Command-Line режима


Немудрено, что у кого-то возникла потребность работать с devenv.exe (точнее devenv.com, т.к. именно он занимается обработкой в консольном режиме) с нашим плагином. Однако VSPackages не имеет возможностей работать хоть как-то в таком режиме! Соответственно плагин просто останется неактивным, если мы попробуем построить решение в консоли как
devenv "D:\App1\App1.sln" /Rebuild Debug

Мне этот вопрос в первые был задан в Q/A галерее и, честно говоря, у меня не возникало ранее таких потребностей или даже желаний (т.к. можно работать с msbuild.exe и, кроме того, сервера автоматизации все равно будут предоставлять у себя ограниченную среду без всего такого).

Однако, как мы видим, потребности у каждого из нас разные, тем более если это часть VS, то непорядок, надо поддержать.

Позднее в рамках поддержки CI серверов я занялся подобным вопросом с успешным его решением. То есть всю событийную модель проектов и решения мы все-таки можем обрабатывать в VSPackage! Кто бы что не утверждал:


"C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\devenv" "D:\App1.sln" /Rebuild Debug
"C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\devenv" "D:\App1.sln" verbosity:diagnostic /Build Release
[?]
Однако…
  • Решение предполагает использование Add-Ins обертки для трансляции событий;
  • Именно Add-Ins предоставляют command-line режим, точнее VS готова работать с ним в этом режиме;
  • Они же имеют точно такую же модель, поэтому нам нет ничего проще просто имплементировать старый добрый IVsUpdateSolutionEvents2 и произвести регистрацию как и для VSPackage в точке:

void OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom);

И произвести перенаправление на главную библиотеку, т.е., например:

...
public int UpdateSolution_Begin(ref int pfCancelUpdate)
{
    return library.Event.onPre(ref pfCancelUpdate);
}

public int UpdateProjectCfg_Begin(IVsHierarchy pHierProj, IVsCfg pCfgProj, IVsCfg pCfgSln, uint dwAction, ref int pfCancel)
{
    return library.Event.onProjectPre(pHierProj, pCfgProj, pCfgSln, dwAction, ref pfCancel);
}        
...

Но, как всем давно известно — the Add-ins are deprecated in Visual Studio 2013. То есть такой трюк будет работать для VS2010, VS2012 и VS2013. Для планируемой VS2015 такие игры не пройдут.

Об этом я уже писал на MS Connect Issue #1075033, однако можете попрощаться для тех, кому это было важно. VS2015 уже в RC, а задачка simply closed.

Идем дальше.

Эмуляция схожей модели событий от MSBuild tools


Начнем с того, что MSBuild tools — безусловно более мощный инструмент и выше указанные проблемы никто и знать не должен. Мы можем спокойно обращаться с $(Configuration) & $(Platform) уровня solution, можем работать с targets и т.п Однако решение должно быть единым и мы не должны замечать разницу работы между VS IDE и построениями на CI/Special Build Servers. Поэтому рассмотрим возможность работать с вышеуказанными событиями в рамках msbuild tools.

Нам недоступен ни DTE2-context, ни сервисы для регистрации событий, ничего такого, чтобы можно было общаться с VS, ну а чего вы еще ждали. Да, конечно, мы можем получить, например, тот же DTE2-context c GetActiveObject, который в свою очередь использует:

HRESULT GetActiveObject(
  _In_       REFCLSID rclsid,
  _Reserved_ void     *pvReserved,
  _Out_      IUnknown **ppunk
);

[?]
Т.е. например так:

(EnvDTE80.DTE2)System.Runtime.InteropServices.Marshal.GetActiveObject("VisualStudio.DTE.10.0");

Но это все будет работать только при наличии запущенного экземпляра IDE студии, что не представляет возможным для ограниченных сред, например, для CI.

Поэтому предлагаю получить управление msbuild.exe посредством регистрации разрабатываемого транслятора как логгера. Для этого нам необходимо работать с Microsoft.Build.Framework.

Действительно, базовый IEventSource способен обеспечить все наши основные потребности:

public class EventManager: Logger
{
    ...
    public override void Initialize(IEventSource evt)
    {
        ...
    }
}

Однако и тут есть ряд особенностей, которые необходимо знать.
  • IEventSource.BuildStarted не подойдет, т.к. он срабатывает слишком рано, точнее, для того чтобы получить данные проекта, который должен будет обрабатываться, нам придется подождать. В VS — контекст входа обрабатывается с IVsSolutionEvents;
  • Поэтому для PRE событий представляется возможным использовать только IEventSource.ProjectStarted, а он же в свою очередь должен использоваться для событий уровня проектов.

Дело в том, что обработчик — ProjectStartedEventHandler рассылает также ProjectStartedEventArgs в качестве аргумента и .sln файл для работы с ним мы можем отследить, например, так:

evt.ProjectStarted  += new ProjectStartedEventHandler((object sender, ProjectStartedEventArgs e){
    e.ProjectFile; // should be .sln ! 
    ...
});

Т.е. вариант может быть с ожиданием пока не придет .sln на обработку, а он в свою очередь должен быть до прохождения файлов проектов.

Мучения на этом только начинаются, т.к. для работы вам скорее всего понадобится работать с данными .sln, включая загрузку проектов для получения доступа к msbuild движку где-либо еще — этому может быть пример evaluation св-во msbuild и т.п.

Для работы с .sln файлами, увы, ничего нормального вы не увидите, точнее вот варианты, выбирайте на любой вкус:
  • Либо использовать устаревший т.е. он помечен как obsolete Microsoft.Build.BuildEngine.Project. При этому учитываете, что если вы не используете Microsoft.Build.BuildEngine, то это доп. ненужный reference для вашего проекта.
  • Либо использовать рефлексию на internal методы — Microsoft.Build.BuildEngine.Shared, например:
    -> void ParseProject(string firstLine)
    -> void ParseFirstProjectLine(string firstLine, ProjectInSolution proj)
    -> crackProjectLine -> PROJECTNAME + RELATIVEPATH
  • Либо написать собственный небольшой парсер по примеру того же ParseProject.

Выбор небогатый, однако как и говорилось выше, .sln в непонятном виде уже достаточно давно…

Идем дальше. В проекте может понадобиться оценка свойств и функций msbuild (MSBuild Property Functions) и т.п., ну а как же без них...

Если вам потребуется подобное, то для того, чтобы с этим работать, необходимо подготовить msbuild движок, а его необходимо сначала инициировать. Так как мы работаем с изолированной средой, необходимо также от msbuild.exe передать свойства, с которым он был иницирован.

Если вы используете .NET 4.0, вам придется это сделать вручную, т.е. вам необходимо определить Configuration, Platform, SolutionDir и т.п. для обработчика. А вот для платформы .NET 4.5 доступен ProjectStartedEventArgs.GlobalProperties.

После того, как изолированная среда полностью проиниализирована, мы, наконец, можем обрабатывать события проектов. Однако мы работаем с msbuild и мы работаем с targets. Поэтому вам необходимо подписаться на TargetStarted.

Для трансляции нам устроят PreBuildEvent и PostBuildEvent от полученного TargetName, например:

protected void onTargetStarted(object sender, TargetStartedEventArgs e)
{
    switch(e.TargetName) { 
        case "PreBuildEvent": {
            ...
            return;
        }
    }
}

Но и тут не все гладко. Дело в том, что мы не можем сослаться на проект, который сейчас обрабатывается с TargetStartedEventArgs, однако мы можем получить BuildEventContext, от которого можно получить ProjectInstanceId, а он в свою очередь существует и для ProjectStarted, т.е. мы можем просто запомнить ProjectId и далее ссылаться на него везде, где доступен BuildEventContext, например, просто:

projects[e.ProjectId] = new Project() {
    Name            = properties["ProjectName"],
    File            = e.ProjectFile,
    Properties      = properties
};

Собственно, теперь вы можете полностью транслировать событийную модель в VSPackage с изолированной средой и работать как есть:


"C:\Program Files (x86)\MSBuild\12.0\bin\msbuild.exe" "app.sln" /l:"<логгер>.dll" /m:12 /t:Rebuild /p:Configuration=<cfg> /p:Platform=<platform>

Получения контекста построения


На самом деле здесь есть ряд проблем или особенностей, которые хотелось тоже осветить. Но в целом это слишком специфично именно для нашего проекта, а для вашего случая может вообще ничего подобного и не понадобиться, поэтому можно пропустить.

Так как наш плагин работает с событиями построения, не трудно догадаться, что может потребоваться и получение типа построения, с которым все это началось. Но для VS с этим не все так просто.

Вы уже знаете и понимаете, что мы работаем по приоритетной подписке, и у нас не получится использовать что-то типа:

_buildEvents.OnBuildBegin += new _dispBuildEvents_OnBuildBeginEventHandler((vsBuildScope Scope, vsBuildAction Action) => {
    buildType = (BuildType)Action;
});

Потому что это слишком поздно.

Вы можете попробовать работу с IVsUpdateSolutionEvents4, однако как я и написал в вопросе и ответе, только если вы не планируете совместимсоть со старыми версиями.

На тот момент выбор остановился на перехвате комманд от VS IDE, например:

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

        if(GuidList.VSStd97CmdID == guid || GuidList.VSStd2KCmdID == guid) {
            _c.updateContext((BuildType)id);
        }

});

Не особо нравится, лучше использовать IVsUpdateSolutionEvents4, но единственный рабочий вариант для совместимости версий и сохранения приоритетной обработки, до чего смог додуматься на тот момент. Да и обрадовать другими решениями никто пока не спешит.

Межмодульное взаимодействие


Ну здесь на самом деле ничего примечательного не будет, все обыденно, однако в рамках статьи небольшой description.

Как вы уже успели заметить, компоненты разносторонние и даже различные по своей модели обработки событий. На примере моего плагина это выглядит следующим образом:

image
Лист того, что там изображено, можно найти тут.

Основные моменты для ваших работ:
  • Выделяем общий публичный интерфейс на уровне проектов, для того, чтобы все необходимые компоненты могли предоставлять по нему реализацию. Так делает Shell.Interop, так делает EnvDTE и т.п.;
  • Определяем, где будет располагаться главное ядро программы. Тот, кто и будет ответственнен за единую обработку данных;
  • Определяем где-нибудь загрузчик библиотеки. Это может быть также как у нас внешний Provider и т.п.;
  • Каждый из компонентов, соответсвенно, самостоятельно определяет, как ему подключаться к тому, где он будет использоваться, и в общем виде должен заниматься лишь пересылкой и трансляцией, если нужно, обрабатываемых данных. (полагаю, пример как работать с внешними lib приводить не нужно; вы можете найти либо в моих исходниках, либо вообще где угодно, это за рамками статьи).

Тут есть важное замечание. Наш плагин изначально не предполагалось использовать в таком объеме, поэтому ядро системы располагается в VSPackage.

Да, это удобно не разделять на отдельные компоненты, но чем это плохо? Дело в том, что моему VSPackage на схеме выше, приходиться тянуть за собой тяжелые зависимости на EnvDTE & EnvDTE80 (которые, в свою очередь, имеют еще более экзотические связи). Все это может, и скорее всего будет отсутствовать на серверах непрерывной интеграции, т.к. прежде всего поставляется стандартный msbuild tools, и другое подобное. То есть, не забывайте о легких самодостаточных компонентах и прозрачных интерфейсах. Я же пока не спешу с разделением, т.к. все это время, и оно пока того не стоит, он ведь и был изначально VSPackage решением, как-то так.

Доступ и оценка MSBuild Properties & MSBuild Property Functions


Последний момент, который я хочу рассмотреть — это evaluation свойств. Вообще доступ к properties может происходить несколькими вариантами, например:

А также другие.

Наиболее гибкий вариант — использовать Microsoft.Build.Evaluation, т.к. здесь наиболее простой способ получить оценку средствами msbuild движка и работать с проектами, например:

public virtual string evaluate(string unevaluated, string projectName = null)
{
    ...
    lock(_lock)
    {
        try {
            ...
            project.SetProperty(container, Scripts.Tokens.characters(_wrapProperty(ref unevaluated)));
            return project.GetProperty(container).EvaluatedValue;
        }
        finally {
            project.RemoveProperty(project.GetProperty(container));
        }
    }
}

Однако, как уже можно было понять, для solution-context вам придется решать, как работать с данными от проектов. Я лично реализовал доп. расширение синтаксиса.

То есть предварительно все-таки приходится производить разбор средствами своего анализатора, а далее направлять подготовленные данные, с которыми уже справится оригинальный движок msbuild.

Заключение


Собственно, это первая заметка на Хабрахабр. Надеюсь, не последняя -_-.

В материале рассмотрены ключевые моменты, чтобы понять, что нужно делать, какие варианты решений существуют, и с какими проблемами и особенностями придется столкнуться по работе с событийной моделью построений в VS, а также его связанных компонентах devenv & MSBuild tools, на примере реального рабочего решения.

Детали реализации вы сможете найти в исходниках, а также получить доп. информацию в комментариях позднее, если будут вопросы.

Пишите об ошибках и неточностях, подправлю, если будут… было весьма неудобно без markdown.

upd.


В моих попытках донести до комментаторов, что:
Материал описывает — способы работы с указанными элементами, а также решение проблем при их использовании в описанных обстоятельствах. Для того, чтобы разработчик VS смог ознакомиться с существующими решениями, и применить полученные знанния при разработке — чего угодно и для чего угодно своего… увы не увенчались успехом.

Обсуждаем исключительно — способы решения проблем с помощью этих разработок для конечного пользователя, а также вообще пытаемся решить абстрактные проблемы, которые никак не были представлены или детально описаны — решаем для того чтобы решить, не ознакомившись с предметной областью.

В виду исключительности образовавшейся аудитории, продолжать комментировать в данной статье, не представлю целесообразным.
Однако, для тех кому все-таки нужна помощь по теме, вероятно, смогу проконслутировать по мере свободного времени. Контакты в G+.

upd2. Targets & Map of projects.


Небольшой бонус, пока я поддерживаю материал, для тех кто хочет, но не может использовать IVsUpdateSolutionEvents2 / IVsUpdateSolutionEvents в силу различных обстоятельств.

Обсуждая проблему событий 'уровня solution' (для простоты, продолжаем ссылаться на такую формулировку) с обратившемся ко мне человеком, я также рассмотрел возможность работы с targets взамен реализации указанных интерфейсов, т.к. у него это было явно лишним звеном, от которого следует избавиться. Ну, пробуем добиться некоторого эквивалента.

К тому же, если вам просто нравиться что-то подобное — MSBuild: Extending the solution build, (но подобный вариант может работать только при построении от msbuild.exe и не от VS IDE...)
  • VS IDE вообще не рассматривает .sln файл при построении. Она формирует конечные цели из своего загруженного окружения с EnvDTE и т.п.
  • Файл .sln рассматривается только msbuild.exe — т.е. перед построением он автоматом формирует .metaproj (в памяти, по умолчанию), который содержит что и когда будет строиться, включая общий targets под все проекты если существует. Например:

...
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\SolutionFile\ImportAfter\*" Condition="'$(ImportByWildcardBeforeSolution)' != 'false' and exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\SolutionFile\ImportAfter')" />
<Import Project="D:\tmp\p\after.Sample.sln.targets" Condition="exists('D:\tmp\p\after.Sample.sln.targets')" />
<Target Name="Build" />
<Target Name="Rebuild" />
<Target Name="Clean" />
<Target Name="Publish" />

  • При этом .metaproj не рассматривается VS IDE, т.к. у него уже свои сформированные цели.
  • А также мы не знаем, что и когда вообще происходит, т.к. проекты строяться все по отдельности с заранее определенными целями VS.

т.е. для работы с общим targets под build-операции от VS IDE, мы можем использовать только файлы проектов (имеется ввиду без модификации/расширения VS) с некоторым ограничением.

Рассматриваем, соответственно, ситуацию общего решения, или когда мы можем не знать вообще о проектах которые будут в solution у конечного пользователя.

В качестве такого ограничения можно рассматривать карту проектов (есть еще как минимум 2 варианта, однако только этот показал себя более или менее стабильным, поэтому бонус только про него):


...
<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>
...

В общем виде, формируя подобную карту, мы теперь знаем что и когда должно быть (формируется с ProjectsMap, детект с ScopeDetectFirst и ScopeDetectLast соответсвенно), т.е.:
  • Просто автоматически добавьте (например с NuGet events и т.п.) ваш общий .targets файл во все проекты, например:
    <Import Project="..\<SolutionFile>.targets" />
  • Далее, с ограничением в виде карты проектов, теперь мы сможем добиться следующего выполнения (для Clean, Build, Rebuild и т.д. целей):
    • «до начала первого проекта»
    • «после окончания последнего проекта»

Это безопасно для всех или большинства случаев (изменение порядка сборки или удаление некоторых проектов из решения, все должно отработать нормально). Однако! этот вариант имеет ряд неудобств в виде обслуживания импорта на стадии инициализации, а также при добавлении новых проектов. Это неудобно, но вариант для IVsUpdateSolutionEvents.

К тому же я отметил потенциальную проблему с Unload projects (когда IDE позволяет пользователю временно выгружать проект из решения) в заголовке — однако, в большинстве случаев это будет допустимый вариант, т.к. этот unload временный, и ошибка справедлива в некотором виде, поэтому ее можно допустить (к тому же, проблему все-таки можно решить при желании).

Касательно удобства, спорный момент… в ваших продуктах VSPackage также может оказаться лишним звеном, и как вариант вы можете рассмотреть что-то подобное…
Однако, что касается надежности, я все-таки выбрал бы реализацию IVsUpdateSolutionEvents с VSPackage! т.к. подобное минимум стандартизировано, и соответственно предсказуемо на различных версиях.

Дополнительные ссылки по теме


Комментарии (29)


  1. mayorovp
    15.05.2015 23:32

    Прочитал статью два раза, но так и не понял: какая же задача решалась настолько сложным способом?..


    1. DragonFire
      16.05.2015 00:20

      Тоже прочитал статью два раза, несколько сумбурно написано… Хотя с предметной областью знаком, тоже занимаюсь написанием плагинов к студии…


      1. r-eg Автор
        16.05.2015 01:04

        хм, подскажите где и что улучшить, изменю, или добавлю пояснения…
        возможно что-то детализировать, или подкорректировать формулировки и направления ветра


    1. r-eg Автор
      16.05.2015 00:38

      см. «О проблемах» — самое первое предложение:

      Изначальная потребность… работа с Solution-Context в рамках событийной модели студии.

      т.е. контекст решения (solution-context) по этому вопросу никак не предусмотрен штатными средствами IDE Visual Studio.
      Соответсвенно, все Pre/Build события доступны только в рамках проекта, и если вам требуется что-то обслуживать, предположим единожды для 70 проектов в solution, то появляется задачка…

      а также, это косвенно связано с подобными вопросами:
      … with VS may be you know — how to find out (from the project or targets file) what is currently selected solution configuration? Not project configuration, but solution configuration! When solution and projects have the same Debug/Release configurations everything is trivial, but in our case solution may have custom configurations which simply don't have corresponding matching configurations in any project.

      и другие.
      Часть решений можно также рассматривать здесь, кстати — stackoverflow.com/q/2295454

      Важное замечание — в этом материале рассмотрена не одна проблема, и выше не предлагается конечное решение, просто все описано как продолжение одной истории :) Если нужно конечно решение, см. плагин в ссылках.

      Так, на примере развитие базовой модели для этого плагина, требовалась унификация обработки между различными компонентами для изолированных сред (таких как CI и т.п.), поэтому появились задачи на MSBuild Tools и консольный devenv.com, а сним работать через VSPackage просто так нельзя, но есть варианты… и т.п.

      Поэтому, для тех у кого схожие потребности(приоритетная событийная модель, взаимодействие с msbuild.exe, взаимодействие с devenv.exe/.com и все что выше), могут быстро решить свои проблемы уже для своих плагинов и прочих решений, в том объеме, в котором это требуется.

      Обратите также внимание, что статья не для пользователей Visual Studio, а для разработчиков, как и указано в заголовке. Если же требуется конечное обобщенное общее решение, см. плагин в галереи


      1. mayorovp
        16.05.2015 00:43

        Ничего подобного, это не проблема, не задача и не потребность, а путь решения.


        1. r-eg Автор
          16.05.2015 00:46

          … а путь решения.

          Вы про что?

          VS — тоже по сути путь решения… или что имеется ввиду о_О


          1. mayorovp
            16.05.2015 00:48

            Для чего вам нужна «Работа с Solution-Context в рамках событийной модели студии.»? Мне вот почему-то оно ни разу не понадобилось. Вот мне и интересно — а для чего вообще такое делают?


            1. r-eg Автор
              16.05.2015 00:56
              +1

              ну, так в чем проблема, Вам значит и не надо :) а проблемы надо решать по мере поступления!

              Мне вот почему-то оно ни разу не понадобилось.

              2 примера в комментарии выше… под обслуживанием имелось ввиду то, что необходимо выполнить для всех проектов сразу и т.п.
              Вы же понимаете пример, когда люди создают empty project в solution и делают ~70 проектов зависимых только от него?


              1. mayorovp
                16.05.2015 01:06

                То есть до создания пустого проекта было 70 независимых проектов в одном решении? Нет, мне такого никогда не понять :)

                У меня в каждом решении всегда есть обычный проект, от которого зависят остальные. Называется обычно как-нибудь вроде (MyPrefix).Common


                1. r-eg Автор
                  16.05.2015 01:19

                  :) а где я написал «независимых»? если они в одном solution, значит они там не просто так…

                  и кстати, вот это выше:

                  … When solution and projects have the same Debug/Release configurations everything is trivial, but in our case solution may have custom configurations which simply don't have corresponding matching configurations in any project....

                  это чел. объясняет мне в личной переписке реальный пример их хард кастомного решения, так что чудеса и не такие бывают :)
                  т.е. нет, тут еще ничего страшного, там детали куда более прикольные

                  к примеру, мое решение было взято за основу, однако у них более жесткие вещи и к примеру им требуется нечто вроде коробочного решения для подконтрольных им разработчиков, которые разбросаны по всему миру… ну как я понял

                  мне вот лично явно не требовалось то, что сейчас нужно им… так что кому что, мое дело рассказать как этого добиться


                  1. r-eg Автор
                    16.05.2015 02:52

                    редактировать свои сообщения запрещено? или и тут бонусы нужны
                    ____

                    ах да, касательно доп. примеров, когда и что может понадобиться из этого набора…
                    собственно, плагин(который уже для обычных юзеров) в пример и забыл привести :)

                    В общем, некоторый набор опций, который может быть достигнут, кратко показан в виде небольших примеров:

                    * Обзорно в галереи
                    * В wiki / Examples

                    например, можете поискать чего-нибудь там, однако, учитывайте, что например материал — Errors.Stop build не означает, что подобное реализуется исключительно на приоритетном уровне в самых верхах UpdateSolution_Begin — нет, это можно реализовать разными путями, тут особых проблем нет, а вот нумерация версий, может быть как раз одним из частных примеров и т.п.


                  1. mayorovp
                    16.05.2015 10:49

                    :) а где я написал «независимых»? если они в одном solution, значит они там не просто так…

                    Вы же понимаете пример, когда люди создают empty project в solution и делают ~70 проектов зависимых только от него?


                    1. r-eg Автор
                      16.05.2015 13:11
                      -1

                      Это один из способов решения проблем Solution-wide pre-build event

                      Когда в solution есть X проект, от которого зависят Y проекты. Этот проект может не производит никаких binaries, а заниматься лишь некоторым обслуживанием которые будут используются при сборке Y проектов. По сути, такой метод может служить для обработки события начала построения именно в нужном контексте для всех Y проектов и т.п.


                      1. mayorovp
                        16.05.2015 13:33

                        Убираем этот проект — и получаем, что до его создания у нас в решении было 70 независимых проектов.


                        1. r-eg Автор
                          16.05.2015 13:38

                          это не мое решение! это не моя идея! это пример как поступают люди! я вот, например, как раз плагин по этим поводам решил в паблик отправить, чтобы как раз устранить подобные проблемы или неудобства

                          вы как вообще читаете?


                          1. mayorovp
                            16.05.2015 13:40

                            Ну и как ваш плагин позволит устранить группирование в одном решении 70 независимых проектов?!

                            вы как вообще читаете?


                            1. r-eg Автор
                              16.05.2015 13:50

                              :( Так ведь не о группировки речь идет. Вы ведь используете Pre/Build events для проектов? ну а предположим вам надо событие на пару-тройку проектов сразу, или сразу для всех…


                              1. mayorovp
                                16.05.2015 17:34

                                Если надо изменить процесс построения некоторых проектов — я просто открою их в редакторе XML и изменю что мне надо.

                                А если надо сделать что-то для решения целиком — то kekekeks привел ниже классную ссылку на куда более простое решение, чем у вас сделано.


                                1. r-eg Автор
                                  16.05.2015 19:46

                                  ребят, огорчаете… в том Q/A есть ответ с такой же ссылкой — stackoverflow.com/a/5720489 которую приводит kekekeks

                                  чего еще нужно то ?! я ведь эти же решения вам и привел в пример!
                                  Я не считал и не считаю удобным вариант с зависимыми проектами, я об этом ни разу не упомянул.

                                  Вы вообще можете не пользоваться IDE для построения, используйте msbuild! что вам мешает ?! ведь он более гибок! добавьте необходимые таргеты и забудьте все как страшный сон…

                                  И сколько уже можно, Здесь не рассматриваются и не предлагаются конечные решения! — это для разработчиков, а вы описываете и требуете примеры для обычных пользователей VS. Прочтите «Предупреждение» что я добавил в материал.

                                  Если вам нужно обсуждение плагина, а также обсуждение вариантов — Solution-wide pre-build event, и вообще какими еще кто пользуется, то быть может лучше создать другую статью специально под это обсуждение ?!


                                  1. mayorovp
                                    16.05.2015 21:02

                                    Почему разработчики не могут воспользоваться теми же самыми механизмами, что и обычные пользователи VS?

                                    И зачем идти сложным путем, когда есть простой?


                                    1. r-eg Автор
                                      16.05.2015 21:25
                                      -1

                                      Зачем вообще использовать IVsUpdateSolutionEvents, Shell.Interop, EnvDTE, Microsoft.Build.Evaluation… ума не приложу o_o
                                      да кто только додумался до распространения VSSDK?

                                      Ведь, — «зачем идти сложным путем, когда есть простой ?»


                      1. kekekeks
                        16.05.2015 13:39
                        +1

                        И чем плохо вот это решение?


                        1. r-eg Автор
                          16.05.2015 13:54
                          -1

                          а где такое сказано? я ведь и ссылочку привел на Solution-wide pre-build event где как раз и приводиться как вариант



  1. SVVer
    16.05.2015 09:44

    Может быть, стоит привести конкретный пример, в котором возникает проблема, которую Вы решаете? Где-нибудь в начале статьи?


    1. kekekeks
      16.05.2015 12:45
      +1

      Есть проблема, которую автор создаёт. Невозможность работы «плугина» на билд-сервере без студии.


      1. r-eg Автор
        16.05.2015 13:35

        Невозможность работы «плугина» на билд-сервере без студии.

        это одна из… что появилась позднее. Да все правильно, это уже было в рамках того плагина в ссылках.
        Однако изначально это solution-context, ну например этот комментарий, а также другие приведенные
        Этот плагин был отправлен в паблик и доведен до такого состояния, в котором он находиться сейчас, уже в рамках предоставления какого-то общего повторного решения. Опять-таки для тех кто в нем нуждается.

        У каждого свой workflow и не всегда по его воли! есть вообще своеобразные «коробочные решения», те что предоставляют подконтрольным подразделениям…

        Но еще раз этот, материал призван в первую очередь — осветить некоторые аспекты решения и взаимодействия для разработчиков, для тех кому это понадобиться, т.е. столкнулся с задачей, например как взаимодействовать с devenv.exe или как работать с приоритетной подпиской, и все что выше.


    1. r-eg Автор
      16.05.2015 13:16

      ok, Какие-нибудь из приведенных в моих комментариях подойдут? Однако, прошу еще раз заметить, что здесь описаны способы работы для разработчиков с описанными компонентами VS(ну назовем их так), а не конечные способы решения проблем… как и написано

      … необходимо осветить некоторые аспекты решения и взаимодействия ...


  1. excoder
    16.05.2015 15:35

    Вопрос такой, насколько всё это будет актуально в dnx в vs-2015? Как я понял, новым project.json можно делать мультитаргет, и я могу затаргетить одновременно dnx4.5, asp.net5.0, и net46. Хранить старые проекты и солюшен мне уже не надо, а json-файлы имхо гораздо удобнее чем oldschool old-ms-style xml проекты.