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

28
задан jaws 7 June 2010 в 20:28
поделиться

4 ответа

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

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

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

Вот чрезвычайно простой пример:

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

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

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

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

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

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

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

Создание сгенерированных файлов.

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

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

Использование настраиваемого типа элемента (также известного как действие сборки)

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

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

, после чего Visual Studio позволит вам выбрать «Xyz» в действии сборки в свойствах файла. , в результате чего он будет добавлен в ваш.csproj:

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

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

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

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

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

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

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

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

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

Вызов вашего генератора пользовательского кода из MSBuild

Дойдя до создания файла .targets, вы можете рассмотреть возможность вызова своего инструмента непосредственно из MSBuild вместо использования отдельного события перед сборкой или некорректного «Custom Tool» Visual Studio механизм.

Для этого:

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

Вот все, что вам нужно для реализации 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
  }
}

А вот элемент UsingTask и цель, которая его вызывает:

<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, поэтому нет необходимости использовать для этого отдельную ItemGroup.

В чем недостатки старого механизма «Custom Tool» и почему его не использовать

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

  1. «Пользовательский инструмент» создает сгенерированный файл всякий раз, когда файл редактируется и сохраняется, а не при компиляции проекта. Это означает, что все, что изменяет файл извне (например, система контроля версий), не обновляет сгенерированный файл, и вы часто получаете устаревший код в своем исполняемом файле.
  2. Выходные данные «Настраиваемого инструмента» должны были быть отправлены вместе с деревом исходных текстов, если только у получателя не было и Visual Studio, и «Настраиваемый инструмент».
  3. «Пользовательский инструмент» должен был быть установлен в реестре, и на него нельзя было просто ссылаться из файла проекта.
  4. Вывод «Custom Tool» не сохраняется в каталоге «obj».

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

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

Обычно ваша настраиваемая задача сборки запускается:

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

Чтобы быть более точным:

  1. An IntelliSense Добавочная сборка запускается при запуске Visual Studio и каждый раз, когда любой файл сохраняется в Visual Studio. Это запустит ваш генератор, если выходной файл отсутствует, какой-либо из входных файлов новее, чем выходной файл генератора.
  2. Обычная инкрементная сборка запускается всякий раз, когда вы используете любую команду «Сборка» или «Выполнить» в Visual Studio (включая параметры меню и нажатие клавиши F5) или когда вы запускаете «MSBuild» из командной строки. Подобно инкрементной сборке IntelliSense, он также будет запускать ваш генератор только в том случае, если сгенерированный файл не обновлен
  3. Обычная полная сборка запускается всякий раз, когда вы используете любую из команд «Перестроить» в Visual Studio или когда вы запускаете » MSBuild / t: Rebuild »из командной строки. Он всегда будет запускать ваш генератор, если есть какие-либо входы или выходы.

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

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

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

Обратите внимание, что «Входные файлы» в этом разделе относятся ко всему, что указано в атрибуте «Входные данные» элемента Target.

Заключительные примечания

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>

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

62
ответ дан 28 November 2019 в 02:35
поделиться

Чтобы скрыть элементы в 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>
17
ответ дан devstuff 28 November 2019 в 02:35
поделиться

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

Например:

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

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

0
ответ дан Jason Haley 28 November 2019 в 02:35
поделиться

Я думаю, вы захотите посмотреть здесь: http://msdn.microsoft.com/en -us / library / ms171453.aspx .

В частности, раздел «Создание элементов во время выполнения».

0
ответ дан 28 November 2019 в 02:35
поделиться
Другие вопросы по тегам:

Похожие вопросы: