Здравствуйте, LWhisper, Вы писали:
LW>Всем привет!
LW>Подскажите — в какое место можно воткнуть генерацию кода по шаблону (в нашем случае это M4) таким образом, чтобы для этого не приходилось вручную пересобирать проект.
Я для подобного знаю 2 варианта:
—
Visual Studio Single-File Generators. Решение чисто Design-Time (и работающее сугубо в VS). Генератор срабатывает в тот момент, когда вы в студии меняете файл, на который "повесили" генератор. Сгенерированный файл добавляется как дочерний к вашему (т.е. он будет частью проекта).
Вариант с кучей недостатков. Например, нужно менять файлы сугубо в студии, отлаживать сложно, ...
— MSBuild generator (на самом деле у этого способа я не знаю официального названия и официального описания тоже не видел).
Собственно, я предлагаю использовать именно второй вариант. Он вроде как достаточно подробно описан
тут, но если в 2-х словах то выглядтит так:
— вы создает MSBuild task, которая умеет генерировать вам нужные артефакты (пусть это будет XMLDialogGeneratorTask в сборке ExternalDSLSamples.dll)
— для использования этой Task делаете специальный Target файл (пусть, например, он называется XMLDialogGeneratorTask.targets).
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Подключаем задачу -->
<UsingTask AssemblyFile="ExternalDSLSamples.dll" TaskName="XMLDialogGeneratorTask" />
<!-- Эта основная target, которая будет использоваться дле генерации.
Из важного - она берет исходные файлы из Items с именем XmlDialog.
А полученные на выходе файлы добавляет к Items Compile
-->
<Target Name="XMLDialogGenerator">
<XMLDialogGeneratorTask ModelFiles="@(XmlDialog)" OutFolder="obj\$(Configuration)" Namespace="$(RootNamespace)">
<Output TaskParameter="ResultFiles" ItemName="Compile" />
</XMLDialogGeneratorTask>
</Target>
<!-- Это нужно чтобы в Visual Studio можно было для нашего файла выбрать Build Actions с именем XmlDialog -->
<ItemGroup>
<AvailableItemName Include="XmlDialog"/>
</ItemGroup>
<!-- А это мы нашу target встариваем в общий процесс сборки -->
<PropertyGroup>
<CoreCompileDependsOn>$(CoreCompileDependsOn);XMLDialogGenerator</CoreCompileDependsOn>
</PropertyGroup>
</Project>
— теперь, открываем файл нашего проекта и делаем там такие изменения:
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Тут что-то от основного проекта -->
<!-- Добавляем наш target -->
<Import Project="..\XMLDialogGeneratorTask.targets" />
<ItemGroup>
<!-- Прописываем наш файл, который должна обрабаотать задача генерации -->
<XmlDialog Include="DialogSample.xml">
<!-- Указываем, что при изменении этого файла нужно вызвать нашу target
(у Microsoft) здесь часто стоит MSBuild:Compile, но вроде это излишне -->
<Generator>MSBuild:XMLDialogGenerator</Generator>
</XmlDialog>
</ItemGroup>
</Project>
Собственно, при открытии вашего проекта студия вызовет перекомпиляцию и благодаят тому, что мы встроились в процесс сборки файлы сгенерируются и попадут в компиляцию.
Со сборкой вручную тоже всё понятно.
А вот при изменении шаблонов будет срабатывать наша target.
И да, все навигации по коду и IntelliSense — работают (по крайне мере должны — надеюсь, что в Roslyn они это не поломали).
P.S. За давностью лет я уже что-то мог позабыть — так что посмотрите статью, на которую я ссылаюсь ну и в целом поищите аналогичные решения.
P.P.S. Это решение несколько идет в разрез с вашими исходными требованиями, а именно, что сгенерированные файлы
не добавляются в проект — они подключаются только в процесс сборки.
Но мне кажется такой подход более правильным — автосгенерированные файлы всё равно никто не будет править (исправления потеряются при первой же правке шаблона), а наличие такого сгененрированного кода больше сбивает с толку... Ну а если нужно посмотреть, что же нагенерировалось, то достаточно открыть папку, куда мы сохраняем файлы и взглянуть.
Здравствуйте, Михаил Романов, Вы писали:
МР>Собственно, я предлагаю использовать именно второй вариант. Он вроде как достаточно подробно описан тут, но если в 2-х словах то выглядтит так:
МР>- вы создает MSBuild task, которая умеет генерировать вам нужные артефакты (пусть это будет XMLDialogGeneratorTask в сборке ExternalDSLSamples.dll)
Огромное спасибо! Именно то, что искал!
Здравствуйте, Михаил Романов, Вы писали:
МР> <Target Name="XMLDialogGenerator">
МР> <XMLDialogGeneratorTask ModelFiles="@(XmlDialog)" OutFolder="obj\$(Configuration)" Namespace="$(RootNamespace)">
МР> <Output TaskParameter="ResultFiles" ItemName="Compile" />
МР> </XMLDialogGeneratorTask>
МР> </Target>
А как можно продиагностировать работу этого механизма? (VS2017)
<Target Name="M4CSharpGenerator">
<M4GenerateCSharp InputFilePaths="@(M4CSharp)" OutputDirectoryPath="obj\$(Configuration)">
<Output TaskParameter="OutputFilePaths" ItemName="Compile" />
</M4GenerateCSharp>
</Target>
Файлы генерируются в Output складываются,
[Output] public String[] OutputFilePaths { get; set; }
определен.
Но ничего не происходит. Компиляция проходит, ошибок нет, следов файла в сборке тоже.
Здравствуйте, LWhisper, Вы писали:
LW>Файлы генерируются в Output складываются, [Output] public String[] OutputFilePaths { get; set; }
определен.
LW>Но ничего не происходит. Компиляция проходит, ошибок нет, следов файла в сборке тоже.
Если я не ошибаюсь, то Output параметры, которые будут заноситься в Items должны быть не строками, а ITaskItem.
Т.е. что-то типа (очень упрощенно):
public class MyTask : ITask
{
public IBuildEngine BuildEngine { get; set; }
public ITaskHost HostObject { get; set; }
[Output]
public ITaskItem[] OutFiles { get; set; }
public bool Execute()
{
var result = new TaskItem[1];
result[0] = new TaskItem() { ItemSpec = @"\obj\Debug\MyFile.cs" };
OutFiles = result.OfType<ITaskItem>().ToArray();
return true;
}
}
Для диагностики, по идее, должен подойти обычный лог от MSBuild. На сколько я помню он там достаточно четко показывает, что было на входе и выходе задачи, а также как менялись Properties и Items.
Но я вам рекомендую попробовать
MSBuild Structured Log — формат этих логово сейчас "из коробки" поддерживается в MSBuild (в том, который идет с VS 2017 — точно).
Очень уж удобно смотреть что и где делал каждая задача (обычный линейный лог для этого куда хуже подходит).
Там в принципе расписано как утилитой пользоваться — можно запустить сборку проекта прямо из вьюера или сначала собрать MSBuild-ом с ключем /bl, а потом посмотреть результат
Здравствуйте, Михаил Романов, Вы писали:
Спасибо! Попробую!
Вот при запуске билда из-под лога он почему-то решительно отказывается запускать генерацию файлов.
И пугает страшными строчками:
P
roperty reassignment: $(CoreCompileDependsOn)="_ComputeNonExistentFileProperty;ResolveCodeAnalysisRuleSet" (previous value: "M4CSharpGenerator;") at C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\MSBuild\15.0\Bin\Microsoft.CSharp.CurrentVersion.targets (166,9)
Вот так взял и выпилил мою зависимость.
Здравствуйте, LWhisper, Вы писали:
LW>Вот так взял и выпилил мою зависимость.
Это, похоже я немного вас обманул.
Target-файлы, которые делают инъекции в процесс сборки, должны подключаться
после тех файлов, в которых этот процесс описывается. В данном случае — после Microsoft.CSharp.targets.
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Тут что-то от основного проекта -->
<ItemGroup>
<!-- Прописываем наш файл, который должна обрабаотать задача генерации -->
<XmlDialog Include="DialogSample.xml">
<!-- Указываем, что при изменении этого файла нужно вызвать нашу target
(у Microsoft) здесь часто стоит MSBuild:Compile, но вроде это излишне -->
<Generator>MSBuild:XMLDialogGenerator</Generator>
</XmlDialog>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- Лучше всё же тут -->
<Import Project="..\XMLDialogGeneratorTask.targets" />
</Project>