Подтвердить что ты не робот

Как скрыть файлы, созданные пользовательским инструментом в Visual Studio

Я хотел бы, чтобы файлы, созданные моим настраиваемым инструментом, были скрыты, но я не могу найти документацию о том, как это делается.

Примером того, что я ищу, является код WPF за файлами. Эти файлы не отображаются в представлении проекта Visual Studio, но скомпилированы с проектом и доступны в IntelliSense. Код WPF за файлами (Window1.g.i.cs, например) генерируется специальным инструментом.

4b9b3361

Ответ 1

Решение состоит в том, чтобы создать Target, который добавляет ваши файлы в компиляцию ItemGroup, а не добавляет их явно в ваш .csproj файл. Таким образом Intellisense увидит их, и они будут скомпилированы в ваш исполняемый файл, но они не будут отображаться в Visual Studio.

Простой пример

Вам также необходимо убедиться, что ваша цель добавлена ​​в свойство CoreCompileDependsOn, чтобы она выполнялась до запуска компилятора.

Вот очень простой пример:

<PropertyGroup>
  <CoreCompileDependsOn>$(CoreCompileDependsOn);AddToolOutput</CoreCompileDependsOn>
</PropertyGroup>

<Target Name="AddToolOutput">
  <ItemGroup>
    <Compile Include="HiddenFile.cs" />
  </ItemGroup>
</Target>

Если вы добавите это в конец вашего файла .csproj(непосредственно перед </Project>), ваш "HiddenFile.cs" будет включен в вашу компиляцию, даже если он не отображается в Visual Studio.

Использование отдельного файла .targets

Вместо того, чтобы размещать это непосредственно в вашем файле .csproj, вы обычно помещали его в отдельный файл .targets, окруженный:

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

и импортируйте в свой .csproj с помощью <Import Project="MyTool.targets">. Файл .targets рекомендуется даже для одноразовых случаев, поскольку он отделяет ваш пользовательский код от материала в .csproj, который поддерживается Visual Studio.

Построение сгенерированных имен файлов

Если вы создаете обобщенный инструмент и/или используете отдельный файл .targets, вы, вероятно, не хотите явно перечислять каждый скрытый файл. Вместо этого вы хотите сгенерировать скрытые имена файлов из других параметров проекта. Например, если вы хотите, чтобы все файлы ресурсов имели соответствующие файлы с инструментами в каталоге "obj" , ваша Target была бы:

<Target Name="AddToolOutput">
  <ItemGroup>
    <Compile Include="@(Resource->'$(IntermediateOutputPath)%(FileName)%(Extension).g.cs')" />
  </ItemGroup>
</Target>

Свойство "IntermediateOutputPath" - это то, что мы все знаем как "obj" , но если конечный пользователь ваших .targets настроил это, ваши промежуточные файлы будут найдены в одном месте. Если вы предпочитаете, чтобы сгенерированные файлы находились в основной директории проекта, а не в каталоге "obj" , вы можете оставить это.

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

<Target Name="AddToolOutput">
  <ItemGroup>
    <MyToolFiles Include="@(Page);@(Resource)" Condition="'%(Extension)'=='.xyz' />
    <Compile Include="@(MyToolFiles->'$(IntermediateOutputPath)%(FileName)%(Extension).g.cs')"/>
  </ItemGroup>
</Target>

Обратите внимание, что вы не можете использовать синтаксис метаданных, например% (Extension), в элементе ItemGroup верхнего уровня, но вы можете сделать это в пределах Target.

Использование настраиваемого типа элемента (аналогично Build Action)

Вышеупомянутые процессы обрабатывают файлы, которые имеют существующий тип элемента, такой как страница, ресурс или компиляция (Visual Studio называет это "действие сборки" ). Если ваши элементы являются новым видом файла, вы можете использовать свой собственный тип настраиваемого элемента. Например, если ваши входные файлы называются файлами "Xyz" , ваш файл проекта может определять "Xyz" как допустимый тип элемента:

<ItemGroup>
  <AvailableItemName Include="Xyz" />
</ItemGroup>

после чего Visual Studio позволит вам выбрать "Xyz" в Action Build в свойствах файла, в результате чего это добавится в ваш .csproj:

<ItemGroup>
  <Xyz Include="Something.xyz" />
</ItemGroup>

Теперь вы можете использовать тип элемента "Xyz" для создания имен файлов для вывода инструмента, как и ранее, с типом элемента "Ресурс":

<Target Name="AddToolOutput">
  <ItemGroup>
    <Compile Include="@(Xyz->'$(IntermediateOutputPath)%(FileName)%(Extension).g.cs')" />
  </ItemGroup>
</Target>

При использовании настраиваемого типа элементов вы также можете заставить свои элементы обрабатываться встроенными механизмами, сопоставляя их с другим типом элемента (например, Build Action). Это полезно, если ваши файлы "Xyz" - это файлы .cs или .xaml, или если они должны быть сделаны

EmbeddedResources. Например, вы также можете скомпилировать все файлы с "Build Action" Xyz:

<ItemGroup>
  <Compile Include="@(Xyz)" />
</ItemGroup>

Или, если исходные файлы "Xyz" должны храниться как встроенные ресурсы, вы можете выразить это следующим образом:

<ItemGroup>
  <EmbeddedResource Include="@(Xyz)" />
</ItemGroup>

Обратите внимание, что второй пример не будет работать, если вы поместите его в цель, поскольку цель не оценивается до момента компиляции ядра. Чтобы сделать эту работу внутри Target, вы должны указать имя задачи в свойстве PrepareForBuildDependsOn вместо CoreCompileDependsOn.

Вызов собственного генератора кода из MSBuild

Подумав о том, как создать файл .targets, вы можете подумать о том, чтобы вызывать ваш инструмент непосредственно из MSBuild, а не использовать отдельное событие предварительной сборки или дефектный механизм Visual Studio с ошибкой Visual Studio.

Для этого:

  • Создайте проект библиотеки классов со ссылкой на Microsoft.Build.Framework
  • Добавьте код для реализации своего генератора кода
  • Добавьте класс, который реализует ITask, и в методе Execute вызовите свой собственный генератор кода
  • Добавить элемент UsingTask в ваш .targets файл, а в целевой добавить добавить новую задачу

Вот вам все, что вам нужно для реализации ITask:

public class GenerateCodeFromXyzFiles : ITask
{
  public IBuildEngine BuildEngine { get; set; }
  public ITaskHost HostObject { get; set; }

  public ITaskItem[] InputFiles { get; set; }
  public ITaskItem[] OutputFiles { get; set; }

  public bool Execute()
  {
    for(int i=0; i<InputFiles.Length; i++)
      File.WriteAllText(OutputFiles[i].ItemSpec,
        ProcessXyzFile(
          File.ReadAllText(InputFiles[i].ItemSpec)));
  }

  private string ProcessXyzFile(string xyzFileContents)
  {
    // Process file and return generated code
  }
}

И вот элемент UseTask и Target, который его вызывает:

<UsingTask TaskName="MyNamespace.GenerateCodeFromXyzFiles" AssemblyFile="MyTaskProject.dll" />


<Target Name="GenerateToolOutput">

  <GenerateCodeFromXyzFiles
      InputFiles="@(Xyz)"
      OutputFiles="@(Xyz->'$(IntermediateOutputPath)%(FileName)%(Extension).g.cs')">

    <Output TaskParameter="OutputFiles" ItemGroup="Compile" />

  </GenerateCodeFromXyzFiles>
</Target>

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

Как старый механизм "Custom Tool" ошибочен и почему не использовать его

Заметка о механизме Visual Studio "Пользовательский инструмент" : в .NET Framework 1.x у нас не было MSBuild, поэтому нам пришлось полагаться на Visual Studio для создания наших проектов. Чтобы получить Intellisense в сгенерированном коде, Visual Studio имеет механизм под названием "Пользовательский инструмент" , который можно установить в окне "Свойства" в файле. Механизм был принципиально испорчен несколькими способами, поэтому он был заменен целями MSBuild. Некоторые из проблем с функцией "Пользовательский инструмент" :

  • "Пользовательский инструмент" создает сгенерированный файл всякий раз, когда файл редактируется и сохраняется, а не при компиляции проекта. Это означает, что что-либо, изменяющее файл извне (например, система контроля версий), не обновляет сгенерированный файл, и вы часто получаете устаревший код в своем исполняемом файле.
  • Выход "Custom Tool" должен был быть отправлен вместе с вашим исходным деревом, если у вашего получателя также не были и Visual Studio, и ваш "Custom Tool" .
  • "Пользовательский инструмент" должен был быть установлен в реестре и не мог просто ссылаться на файл проекта.
  • Вывод "Custom Tool" не сохраняется в каталоге "obj" .

Если вы используете старую функцию "Custom Tool" , я настоятельно рекомендую вам перейти на использование задачи MSBuild. Он хорошо работает с Intellisense и позволяет вам создавать свой проект, даже не устанавливая Visual Studio (все, что вам нужно - это NET Framework).

Когда будет выполняться ваша пользовательская задача сборки?

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

  • В фоновом режиме, когда Visual Studio открывает решение, если сгенерированный файл не обновляется
  • В фоновом режиме при каждом сохранении одного из входных файлов в Visual Studio
  • Каждый раз, когда вы создаете, если сгенерированный файл не обновляется
  • Всякий раз, когда вы перестраиваете

Точнее:

  • Инкрементная сборка IntelliSense запускается при запуске Visual Studio и каждый раз, когда любой файл сохраняется в Visual Studio. Это приведет к запуску вашего генератора, если выходной файл отсутствует. Любой из входных файлов новее, чем выход генератора.
  • Регулярная инкрементная сборка запускается всякий раз, когда вы используете какие-либо команды "Build" или "Run" в Visual Studio (включая опции меню и нажатие F5) или при запуске "MSBuild" из командной строки. Подобно инкрементной сборке IntelliSense, она также будет запускать ваш генератор только в том случае, если сгенерированный файл не обновляется.
  • Регулярная полная сборка запускается всякий раз, когда вы используете какие-либо команды "Перестроить" в Visual Studio или когда вы запускаете "MSBuild/t: Rebuild" из командной строки. Он всегда будет запускать ваш генератор, если есть какие-либо входы или выходы.

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

  • Чтобы заставить генератор повторно запускаться, даже если никакие входные файлы не были изменены, лучшим способом является, как правило, добавление дополнительного ввода в вашу цель, который является фиктивным входным файлом, хранящимся в каталоге "obj" . Затем всякий раз, когда изменяется переменная окружения или некоторые внешние настройки, которые вынуждают инструмент генератора повторно запускаться, просто коснитесь этого файла (т.е. Создайте его или обновите его измененную дату).

  • Чтобы заставить генератор работать синхронно, а не ждать, пока IntelliSense запустит его в фоновом режиме, просто используйте MSBuild для создания вашей конкретной цели. Это может быть так же просто, как выполнение "MSBuild/t: GenerateToolOutput", или VSIP может обеспечить встроенный способ вызова пользовательских целей сборки. В качестве альтернативы вы можете просто вызвать команду Build и дождаться ее завершения.

Обратите внимание, что "Входные файлы" в этом разделе ссылаются на все, что указано в атрибуте "Входы" целевого элемента.

Заключительные заметки

Вы можете получать предупреждения от Visual Studio о том, что он не знает, доверять ли вам файл настраиваемого инструмента .targets. Чтобы исправить это, добавьте его в раздел реестра HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\9.0\MSBuild\SafeImports.

Вот краткое описание того, как будет выглядеть фактический файл .targets со всеми деталями:

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

  <PropertyGroup>
    <CoreCompileDependsOn>$(CoreCompileDependsOn);GenerateToolOutput</CoreCompileDependsOn>
  </PropertyGroup>

  <UsingTask TaskName="MyNamespace.GenerateCodeFromXyzFiles" AssemblyFile="MyTaskProject.dll" />


  <Target Name="GenerateToolOutput" Inputs="@(Xyz)" Outputs="@(Xyz->'$(IntermediateOutputPath)%(FileName)%(Extension).g.cs')">

    <GenerateCodeFromXyzFiles
        InputFiles="@(Xyz)"
        OutputFiles="@(Xyz->'$(IntermediateOutputPath)%(FileName)%(Extension).g.cs')">

      <Output TaskParameter="OutputFiles" ItemGroup="Compile" />

    </GenerateCodeFromXyzFiles>
  </Target>

</Project>

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

Ответ 2

Чтобы скрыть элементы из Visual Studio, добавьте свойство метаданных Visible к элементу. Метаданные InProject, очевидно, тоже делают это.

Видимый: http://msdn.microsoft.com/en-us/library/ms171468(VS.90).aspx

InProject: http://blogs.msdn.com/b/jomo_fisher/archive/2005/01/25/360302.aspx

<ItemGroup>
  <Compile Include="$(AssemblyInfoPath)">
    <!-- either: -->
    <InProject>false</InProject>
    <!-- or: -->
    <Visible>false</Visible>
  </Compile>
</ItemGroup>

Ответ 3

Единственный способ, которым я это знаю, - добавить сгенерированный файл в зависимость от файла, который вы хотите его скрывать, в файле proj.

Например:

 <ItemGroup>
    <Compile Include="test.cs" />
    <Compile Include="test.g.i.cs">
      <DependentUpon>test.cs</DependentUpon>
    </Compile>
  </ItemGroup>

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