visual studio references herramientas cuadro contraer community codigo barra c# wpf visual-studio code-generation customtool

c# - references - Cómo ocultar los archivos generados por la herramienta personalizada en Visual Studio



roslyn c# (4)

Me gustaría que los archivos generados por mi herramienta personalizada estén ocultos, pero no puedo encontrar ninguna documentación sobre cómo se hace esto.

Un ejemplo de lo que estoy buscando es el código WPF detrás de los archivos. Estos archivos no se muestran en la vista del proyecto de Visual Studio, pero se compilan con el proyecto y están disponibles en IntelliSense. El código WPF detrás de los archivos (Window1.gics, por ejemplo), se genera mediante una herramienta personalizada.



La única forma que conozco de hacerlo es agregar el archivo generado para tener una dependencia del archivo que desea que se oculte detrás, en el archivo de proyecto.

Por ejemplo:

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

Si eliminó el elemento DependentUpon, el archivo aparece al lado del otro archivo en lugar de detrás de él ... ¿cómo agrega su generador los archivos? ¿Puede explicarnos el caso de uso y cómo le gustaría que funcione?


La solución es crear un destino que agregue sus archivos al grupo de elementos de compilación en lugar de agregarlos explícitamente en su archivo .csproj. De esa manera, Intellisense los verá y los compilará en su ejecutable, pero no se mostrarán en Visual Studio.

Ejemplo simple

También debe asegurarse de que su destino se agregue a la propiedad CoreCompileDependsOn para que se ejecute antes de que se ejecute el compilador.

Aquí hay un ejemplo extremadamente simple:

<PropertyGroup> <CoreCompileDependsOn>$(CoreCompileDependsOn);AddToolOutput</CoreCompileDependsOn> </PropertyGroup> <Target Name="AddToolOutput"> <ItemGroup> <Compile Include="HiddenFile.cs" /> </ItemGroup> </Target>

Si agrega esto al final de su archivo .csproj (justo antes de </Project> ), su "HiddenFile.cs" se incluirá en su compilación aunque no aparezca en Visual Studio.

Usando un archivo .targets separado

En lugar de colocar esto directamente en su archivo .csproj, generalmente lo ubicaría en un archivo .targets separado rodeado de:

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

e importe en su archivo .csproj con <Import Project="MyTool.targets"> . Se recomienda un archivo .targets incluso para casos puntuales, ya que separa su código personalizado de las cosas en .csproj que mantiene Visual Studio.

Construyendo el (los) nombre (s) de archivo generado (s)

Si está creando una herramienta generalizada y / o utilizando un archivo .targets separado, es probable que no desee enumerar explícitamente cada archivo oculto. En su lugar, desea generar los nombres de archivos ocultos de otras configuraciones en el proyecto. Por ejemplo, si desea que todos los archivos de recursos tengan archivos generados por herramientas correspondientes en el directorio "obj", su destino sería:

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

La propiedad "IntermediateOutputPath" es lo que todos conocemos como el directorio "obj", pero si el usuario final de su .targets lo ha personalizado, sus archivos intermedios aún se encontrarán en el mismo lugar. Si prefiere que sus archivos generados estén en el directorio principal del proyecto y no en el directorio "obj", puede dejarlo desactivado.

¿Si desea que su herramienta personalizada procese solo algunos de los archivos de un tipo de elemento existente? Por ejemplo, es posible que desee generar archivos para todos los archivos de Página y Recursos con una extensión ".xyz".

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

Tenga en cuenta que no puede usar la sintaxis de metadatos como% (Extensión) en un grupo de elementos de nivel superior, pero puede hacerlo dentro de un objetivo.

Usando un tipo de elemento personalizado (también conocido como Build Action)

Lo anterior procesa los archivos que tienen un tipo de elemento existente como Página, Recurso o Compilación (Visual Studio llama a esto "Acción de compilación"). Si sus elementos son un nuevo tipo de archivo, puede utilizar su propio tipo de elemento personalizado. Por ejemplo, si sus archivos de entrada se llaman archivos "Xyz", su archivo de proyecto puede definir "Xyz" como un tipo de elemento válido:

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

después de lo cual Visual Studio le permitirá seleccionar "Xyz" en la Acción de compilación en las propiedades del archivo, lo que resultará en que se agregue a su .csproj:

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

Ahora puede usar el tipo de elemento "Xyz" para crear los nombres de archivo para el resultado de la herramienta, tal como lo hicimos anteriormente con el tipo de elemento "Recurso":

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

Al usar un tipo de elemento personalizado, puede hacer que sus elementos también sean manejados por mecanismos integrados asignándolos a otro tipo de elemento (también conocido como Acción de creación). Esto es útil si sus archivos "Xyz" son realmente archivos .cs o .xaml o si deben hacerse.

EmbeddedResources. Por ejemplo, puede hacer que todos los archivos con "Build Action" de Xyz también se compilen:

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

O si sus archivos fuente "Xyz" deberían almacenarse como recursos incrustados, puede expresarlo de esta manera:

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

Tenga en cuenta que el segundo ejemplo no funcionará si lo coloca dentro del destino, ya que el objetivo no se evalúa hasta justo antes de la compilación del núcleo. Para que esto funcione dentro de un objetivo, debe incluir el nombre del objetivo en la propiedad PrepareForBuildDependsOn en lugar de CoreCompileDependsOn.

Invocando su generador de código personalizado desde MSBuild

Habiendo llegado tan lejos como para crear un archivo .targets, podría considerar invocar su herramienta directamente desde MSBuild en lugar de usar un evento de precompilación separado o el mecanismo defectuoso de "Herramienta personalizada" de Visual Studio.

Para hacer esto:

  1. Cree un proyecto de biblioteca de clases con una referencia a Microsoft.Build.Framework
  2. Agregue el código para implementar su generador de código personalizado
  3. Agregue una clase que implemente ITask y, en el método Execute, llame a su generador de código personalizado
  4. Agregue un elemento UsingTask a su archivo .targets y, en su destino, agregue una llamada a su nueva tarea

Aquí está todo lo que necesita para implementar 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 } }

Y aquí está el elemento UsingTask y un destino que lo llama:

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

Tenga en cuenta que el elemento Salida de este objetivo coloca la lista de archivos de salida directamente en Compilar, por lo que no es necesario utilizar un ItemGroup separado para hacer esto.

Cómo el antiguo mecanismo de "Herramienta personalizada" es defectuoso y por qué no usarlo

Una nota sobre el mecanismo de "Herramienta personalizada" de Visual Studio: En NET Framework 1.x no teníamos MSBuild, así que tuvimos que confiar en Visual Studio para construir nuestros proyectos. Para obtener Intellisense en el código generado, Visual Studio tenía un mecanismo llamado "Herramienta personalizada" que se puede configurar en la ventana Propiedades de un archivo. El mecanismo fue fundamentalmente defectuoso en varias formas, por lo que fue reemplazado por los objetivos de MSBuild. Algunos de los problemas con la característica "Herramienta personalizada" fueron:

  1. Una "Herramienta personalizada" construye el archivo generado cada vez que el archivo se edita y guarda, no cuando se compila el proyecto. Esto significa que cualquier cosa que modifique el archivo externamente (como un sistema de control de revisión) no actualiza el archivo generado y con frecuencia obtiene un código obsoleto en su archivo ejecutable.
  2. La salida de una "Herramienta personalizada" debía enviarse con su árbol de origen a menos que su destinatario también tuviera Visual Studio y su "Herramienta personalizada".
  3. La "Herramienta personalizada" tenía que estar instalada en el registro y simplemente no podía ser referenciada desde el archivo del proyecto.
  4. La salida de la "Herramienta personalizada" no se almacena en el directorio "obj".

Si está utilizando la antigua característica "Herramienta personalizada", le recomiendo encarecidamente que cambie a usar una tarea de MSBuild. Funciona bien con Intellisense y le permite compilar su proyecto sin siquiera instalar Visual Studio (todo lo que necesita es NET Framework).

¿Cuándo se ejecutará tu tarea de compilación personalizada?

En general, su tarea de compilación personalizada se ejecutará:

  • En segundo plano, cuando Visual Studio abre la solución, si el archivo generado no está actualizado
  • En segundo plano, cada vez que guarde uno de los archivos de entrada en Visual Studio
  • Cada vez que compile, si el archivo generado no está actualizado
  • Cada vez que reconstruyes

Ser más preciso:

  1. Una compilación incremental de IntelliSense se ejecuta cuando se inicia Visual Studio y cada vez que se guarda un archivo dentro de Visual Studio. Esto ejecutará su generador si falta el archivo de salida, cualquiera de los archivos de entrada es más nuevo que la salida del generador.
  2. Una compilación incremental regular se ejecuta cada vez que usa cualquier comando "Crear" o "Ejecutar" en Visual Studio (incluidas las opciones de menú y presionar F5), o cuando ejecuta "MSBuild" desde la línea de comandos. Al igual que la compilación incremental de IntelliSense, también ejecutará su generador solo si el archivo generado no está actualizado
  3. Una compilación completa regular se ejecuta cada vez que usa cualquiera de los comandos "Reconstruir" en Visual Studio, o cuando ejecuta "MSBuild / t: Rebuild" desde la línea de comandos. Siempre ejecutará su generador si hay entradas o salidas.

Es posible que desee forzar a su generador para que se ejecute en otros momentos, como cuando cambia una variable de entorno, o forzarlo para que se ejecute de forma síncrona en lugar de en segundo plano.

  • Para hacer que el generador se vuelva a ejecutar incluso cuando no haya cambiado ningún archivo de entrada, la mejor manera es agregar una entrada adicional a su destino, que es un archivo de entrada ficticio almacenado en el directorio "obj". Luego, cada vez que cambie una variable de entorno o algún ajuste externo que obligue a la herramienta del generador a volver a ejecutarse, simplemente toque este archivo (es decir, créelo o actualice su fecha de modificación).

  • Para forzar que el generador se ejecute de forma sincrónica en lugar de esperar a que IntelliSense lo ejecute en segundo plano, solo use MSBuild para construir su objetivo particular. Esto podría ser tan simple como ejecutar "MSBuild / t: GenerateToolOutput", o VSIP puede proporcionar una forma integrada de llamar a destinos de compilación personalizados. Alternativamente, simplemente puede invocar el comando Construir y esperar a que se complete.

Tenga en cuenta que "Archivos de entrada" en esta sección se refiere a lo que se indica en el atributo "Entradas" del elemento de destino.

Notas finales

Puede recibir advertencias de Visual Studio de que no sabe si confiar en el archivo .targets de su herramienta personalizada. Para solucionar este problema, agréguelo a la clave de registro HKEY_LOCAL_MACHINE / SOFTWARE / Microsoft / VisualStudio / 9.0 / MSBuild / SafeImports.

Aquí hay un resumen de cómo se vería un archivo .targets real con todas las piezas en su lugar:

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

Por favor, avíseme si tiene alguna pregunta o hay algo aquí que no entendió.