Автоматическая генерация исходников без ручной сборки проекта
От: LWhisper  
Дата: 06.07.18 12:37
Оценка:
Всем привет!

Подскажите — в какое место можно воткнуть генерацию кода по шаблону (в нашем случае это M4) таким образом, чтобы для этого не приходилось вручную пересобирать проект.

Я себе это вижу так — кастомная MSBuild-таска, которая стартится при попытке разрешить зависимость, которая проверяет, что файл шаблона изменился и заново генерирует дабавленные в проект исходники. Плюс запускает FSWatcher, который отслеживает изменения в этом файле.

Но меня не покидает ощущение того, что это велосипед и существует более простое и изящное решение, которое работает из коробки.
Повторюсь, что вариант со сборкой проекта не подходит — у человека не должно быть неизвестных типов при первом открытии проекта для резолва которых ему необходимо пересобрать проект.
visual studio msbuild t4 m4
Re: Автоматическая генерация исходников без ручной сборки пр
От: Михаил Романов Удмуртия https://mihailromanov.wordpress.com/
Дата: 06.07.18 15:08
Оценка: 172 (7)
Здравствуйте, 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. Это решение несколько идет в разрез с вашими исходными требованиями, а именно, что сгенерированные файлы не добавляются в проект — они подключаются только в процесс сборки.
Но мне кажется такой подход более правильным — автосгенерированные файлы всё равно никто не будет править (исправления потеряются при первой же правке шаблона), а наличие такого сгененрированного кода больше сбивает с толку... Ну а если нужно посмотреть, что же нагенерировалось, то достаточно открыть папку, куда мы сохраняем файлы и взглянуть.
Отредактировано 06.07.2018 15:15 Михаил Романов . Предыдущая версия .
Re[2]: Автоматическая генерация исходников без ручной сборки пр
От: LWhisper  
Дата: 06.07.18 17:11
Оценка:
Здравствуйте, Михаил Романов, Вы писали:

МР>Собственно, я предлагаю использовать именно второй вариант. Он вроде как достаточно подробно описан тут, но если в 2-х словах то выглядтит так:

МР>- вы создает MSBuild task, которая умеет генерировать вам нужные артефакты (пусть это будет XMLDialogGeneratorTask в сборке ExternalDSLSamples.dll)

Огромное спасибо! Именно то, что искал!
Re[2]: Автоматическая генерация исходников без ручной сборки пр
От: LWhisper  
Дата: 13.07.18 10:29
Оценка:
Здравствуйте, Михаил Романов, Вы писали:

МР> <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; }
определен.
Но ничего не происходит. Компиляция проходит, ошибок нет, следов файла в сборке тоже.
Re[3]: Автоматическая генерация исходников без ручной сборки пр
От: Михаил Романов Удмуртия https://mihailromanov.wordpress.com/
Дата: 13.07.18 13:09
Оценка: +1
Здравствуйте, 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, а потом посмотреть результат
Re[4]: Автоматическая генерация исходников без ручной сборки пр
От: LWhisper  
Дата: 17.07.18 17:05
Оценка:
Здравствуйте, Михаил Романов, Вы писали:

Спасибо! Попробую!
Вот при запуске билда из-под лога он почему-то решительно отказывается запускать генерацию файлов.

И пугает страшными строчками:
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)

Вот так взял и выпилил мою зависимость.
Re[5]: Автоматическая генерация исходников без ручной сборки пр
От: Михаил Романов Удмуртия https://mihailromanov.wordpress.com/
Дата: 18.07.18 02:27
Оценка: 4 (1)
Здравствуйте, 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>
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.