
Al desarrollar complementos para aplicaciones CAD ( AutoCAD, Revit y Renga) con el tiempo surge un problema: se lanzan nuevas versiones de los programas, cambia su API y es necesario crear nuevas versiones de los complementos.
Cuando solo tienes un complemento o eres un principiante autodidacta en esto, puedes simplemente hacer una copia del proyecto, cambiar las partes necesarias y compilar una nueva versión del complemento. Por lo tanto, cualquier cambio posterior en el código resultará en un aumento significativo del esfuerzo requerido.
A medida que acumules experiencia y conocimientos, encontrarás varias formas de automatizar este proceso. He recorrido este camino y quiero contarte cómo he llegado a donde estoy y cuán conveniente es.
Primero, analicemos un método que es evidente y que he utilizado durante mucho tiempo.
Enlaces a archivos del proyecto
Y para que todo sea simple, visual y claro, describiré todo utilizando un ejemplo abstracto de desarrollo de un complemento.
Abramos Visual Studio (tengo la versión Community 2019. Y sí, en español) y crearemos una nueva solución. La llamaremos MiSuperPluginParaRevit

Vamos a crear un complemento para Revit para las versiones 2015-2020. Por lo tanto, crearé un nuevo proyecto en la solución (Biblioteca de Clases Net Framework) y lo llamaré MiSuperPluginParaRevit_2015

Necesitamos añadir referencias al API de Revit. Por supuesto, podemos agregar referencias a archivos locales (tendremos que instalar todos los SDK necesarios o todas las versiones de Revit), pero iremos directamente por el camino correcto y conectaremos el paquete NuGet. Puedes encontrar una gran cantidad de paquetes, pero yo utilizaré los míos.
Después de conectar el paquete, hacemos clic derecho en el ítem “Enlacesy elegimos en el menú la opción “Mover packages.config a PackageReference…»

Si de repente te entra el pánico en este momento, porque en la ventana de propiedades del paquete no hay un ítem importante “Copiar localmenteque necesitamos configurar en el valor false, no debes entrar en pánico: vamos a la carpeta del proyecto, abrimos el archivo con la extensión .csproj en un editor que te resulte cómodo (yo uso Notepad++) y buscamos allí la entrada sobre nuestro paquete. Se ve así ahora:
1.0.0Le agregamos la propiedad runtime. Así quedará:
1.0.0
runtimeAhora, al construir el proyecto, los archivos del paquete no se copiarán en la carpeta de salida.
Sigamos adelante: supongamos que nuestro plugin utilizará algo de la API de Revit, que ha cambiado con el tiempo con el lanzamiento de nuevas versiones. O simplemente necesitamos cambiar algo en el código según la versión de Revit para la cual estamos desarrollando el plugin. Para manejar estas diferencias en el código, utilizaremos símbolos de compilación condicional. Abramos las propiedades del proyecto, pasemos a la pestaña "Compilación" y en el campo "Símbolos de compilación condicional" escribimos R2015.

Tenga en cuenta que debe agregar el símbolo tanto para la configuración de Depuración como para la configuración de Liberación.
Y mientras estamos en la ventana de propiedades, pasemos directamente a la pestaña "Aplicación" y en el campo "Espacio de nombres predeterminado" eliminamos el sufijo _2015, para que nuestro espacio de nombres sea universal e independiente del nombre del ensamblado:

En mi caso, en el producto final, los plugins de todas las versiones se agrupan en una sola carpeta, por lo que los nombres de los ensamblados permanecen con un sufijo del tipo _20xx. Pero puede eliminar el sufijo también del nombre del ensamblado, si se prevé que los archivos se ubiquen en diferentes carpetas.
Pasamos al código del archivo Class1.cs e imitamos allí un cierto código teniendo en cuenta las diferentes versiones de Revit:
namespace MySuperPluginForRevit
{
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
[Regeneration(RegenerationOption.Manual)]
[Transaction(TransactionMode.Manual)]
public class Class1 : IExternalCommand
{
public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
#if R2015
TaskDialog.Show("ModPlus", "Hola Revit 2015");
#elif R2016
TaskDialog.Show("ModPlus", "Hola Revit 2016");
#elif R2017
TaskDialog.Show("ModPlus", "Hola Revit 2017");
#elif R2018
TaskDialog.Show("ModPlus", "Hola Revit 2018");
#elif R2019
TaskDialog.Show("ModPlus", "Hola Revit 2019");
#elif R2020
TaskDialog.Show("ModPlus", "Hola Revit 2020");
#endif
return Result.Succeeded;
}
}
}He considerado todas las versiones de Revit posteriores a la versión 2015 (que eran las disponibles en el momento de escribir este artículo) y he considerado la existencia de los símbolos de compilación condicional, que se crean con el mismo patrón.
Pasamos a la joya principal. Creamos un nuevo proyecto en nuestra solución, pero esta vez para la versión del plugin para Revit 2016. Repetimos todas las acciones descritas anteriormente, reemplazando el número 2015 con el número 2016. Pero el archivo Class1.cs del nuevo proyecto lo eliminamos.

El archivo con el código necesario – Class1.cs – ya lo tenemos y solo necesitamos vincularlo en el nuevo proyecto. Hay dos formas de vincular archivos:
- Largo – hacemos clic derecho en el proyecto y seleccionamos «Agregar» -> «Elemento existente», en la ventana que se abre buscamos el archivo necesario y en lugar de la opción «Agregar» seleccionamos la opción «Agregar como enlace»

- Corto – directamente en el explorador de soluciones seleccionamos el archivo necesario (o incluso varios archivos. También se pueden arrastrar carpetas enteras) y los arrastramos al nuevo proyecto mientras mantenemos presionada la tecla Alt. Al arrastrar, verás que al mantener presionada la tecla Alt, el cursor del mouse cambiará de signo más a flecha.
UPD: He introducido un poco de confusión en este párrafo — para mover varios archivos hay que mantener presionado Shift+Alt!
Después de realizar este procedimiento, tendremos en el segundo proyecto un archivo Class1.cs con el ícono correspondiente (flecha azul):

Al editar el código en la ventana del editor, también puedes seleccionar en el contexto de qué proyecto mostrar el código, lo que te permitirá ver y editar el código con diferentes símbolos de compilación condicional:

Siguiendo este esquema, creamos todos los demás proyectos (2017-2020). Consejo: si arrastras archivos en el explorador de soluciones no desde el proyecto base, sino desde el proyecto donde ya están insertados como enlace, ¡no es necesario mantener presionada la tecla Alt!
La opción descrita es bastante buena hasta el momento en que agregamos una nueva versión del complemento o nuevos archivos al proyecto; todo esto se vuelve muy complicado. Y recientemente me di cuenta de cómo resolver todo esto con un solo proyecto y pasamos al segundo método.
La magia de las configuraciones
Al leer hasta aquí, puedes exclamar «¿Y para qué describiste el primer método, si el artículo trata directamente del segundo?!». Lo describí todo para que quede más claro para qué necesitamos los símbolos de compilación condicional y en qué lugares nuestros proyectos difieren. Y ahora nos resulta más claro cuáles son las diferencias que debemos implementar, dejando solo un proyecto.
Y para que todo sea más evidente, no crearemos un nuevo proyecto, sino que haremos cambios en nuestro proyecto actual, creado de la primera manera.
Así que, primero eliminamos de la solución todos los proyectos, excepto el principal (que contiene los archivos directamente). Es decir, proyectos para las versiones 2016-2020. Abrimos la carpeta de la solución y eliminamos las carpetas de esos proyectos.
Nos ha quedado en la solución un solo proyecto — MiSuperPluginParaRevit_2015. Abrimos sus propiedades y:
- En la pestaña «Aplicación» eliminamos el sufijo del nombre del ensamblaje _2015 (más adelante se aclarará por qué)
- En la pestaña «Compilación» eliminamos el símbolo de compilación condicional R2015 del campo correspondiente
Nota: en la última versión de Visual Studio hay un error: los símbolos de compilación condicional no se muestran en la ventana de propiedades del proyecto, aunque están presentes. Si observas este error, necesitarás eliminarlos manualmente del archivo .csproj. Sin embargo, aún nos queda trabajar en él, así que sigamos leyendo.
Renombramos el proyecto en el explorador de soluciones, eliminando el sufijo _2015 y luego eliminamos el proyecto de la solución. Esto es necesario para mantener el orden y la sensación de los perfeccionistas. Abrimos la carpeta de nuestra solución, renombramos la carpeta del proyecto de la misma manera y cargamos el proyecto de nuevo en la solución.
Abrimos el administrador de configuraciones. La configuración Release en principio no nos será necesaria, así que la eliminamos. Creamos nuevas configuraciones con nombres ya familiares R2015, R2016, …, R2020. Ten en cuenta que no es necesario copiar parámetros de otras configuraciones ni crear configuraciones del proyecto:

Vamos a la carpeta del proyecto y abrimos el archivo con la extensión .csproj en un editor que te resulte cómodo. Por cierto, también se puede abrir en Visual Studio, hay que descargar el proyecto y luego en el menú contextual habrá una opción necesaria:

Editar en Visual Studio es incluso preferible, ya que el editor alinea y ofrece sugerencias.
En el archivo veremos los elementos – en la parte superior está el general, seguido de los que tienen condiciones. Estos elementos establecen las propiedades del proyecto durante su compilación. El primer elemento, que no tiene condiciones, establece propiedades generales, y los elementos con condiciones, respectivamente, cambian algunas propiedades según las configuraciones.
Pasamos al elemento general (el primero) PropertyGroup y vemos la propiedad AssemblyName – este es el nombre de la ensambladura y debe estar sin sufijo _2015. Si hay un sufijo, lo eliminamos.
Encontramos el elemento con la condición
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">No lo necesitamos – lo eliminamos.
El elemento con la condición
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">será necesario para trabajar en la fase de desarrollo y depuración del código. Puedes cambiar sus propiedades según tus necesidades, asignar diferentes rutas de salida, cambiar símbolos de compilación condicional, etc.
Ahora creamos nuevos elementos PropertyGroup para nuestras configuraciones. En estos elementos, basta con establecer cuatro propiedades:
- OutputPath – carpeta de salida. Yo establezco el valor estándar binR20xx
- DefineConstants – símbolos de compilación condicional. Debe establecer un valor TRACE;R20xx
- TargetFrameworkVersion – versión de la plataforma. Para diferentes versiones de la API de Revit, es necesario establecer diferentes plataformas.
- AssemblyName – nombre del ensamblado (es decir, el nombre del archivo). Puedes escribir directamente el nombre del ensamblado necesario, pero para mayor universalidad, recomiendo escribir el valor $(AssemblyName)_20xx. Para ello, anteriormente eliminamos el sufijo del nombre del ensamblado
La característica más importante de todos estos elementos es que se pueden copiar fácilmente a otros proyectos sin ningún cambio. Más adelante en el artículo adjuntaré todo el contenido del archivo .csproj.
Bien, hemos entendido las propiedades del proyecto, eso no es difícil. Pero, ¿qué hacer con las bibliotecas vinculadas (paquetes NuGet)? Si miramos más adelante, veremos que las bibliotecas vinculadas se definen en los elementos . Pero aquí está el problema: este elemento no maneja correctamente las condiciones, como el elemento PropertyGroup. Puede que sea incluso un error de Visual Studio, pero si se definen varios elementos ItemGroup con condiciones de configuración, y dentro se insertan diferentes referencias a paquetes NuGet, entonces al cambiar la configuración, se vinculan al proyecto todos los paquetes especificados.
El elemento , que opera según la lógica que conocemos, nos ayuda. if-then-else.
Usando el elemento Choose, definimos diferentes paquetes NuGet para diferentes configuraciones:
Todo el contenido del csproj
Debug
AnyCPU
{5AD738D6-4122-4E76-B865-BE7CE0F6B3EB}
Library
Properties
MySuperPluginForRevit
MySuperPluginForRevit
v4.5
512
true
true
full
false
binDebug
DEBUG;R2015
prompt
4
binR2015
TRACE;R2015
v4.5
$(AssemblyName)_2015
binR2016
TRACE;R2016
v4.5
$(AssemblyName)_2016
binR2017
TRACE;R2017
v4.5.2
$(AssemblyName)_2017
binR2018
TRACE;R2018
v4.5.2
$(AssemblyName)_2018
binR2019
TRACE;R2019
v4.7
$(AssemblyName)_2019
binR2020
TRACE;R2020
v4.7
$(AssemblyName)_2020
1.0.0
runtime
1.0.0
runtime
1.0.0
runtime
1.0.0
runtime
1.0.0
runtime
1.0.0
runtimeTenga en cuenta que en una de las condiciones mencioné dos configuraciones a través de O. Así se conectará el paquete necesario durante la configuración Depuración.
. Y aquí tenemos casi todo perfecto. Cargamos de vuelta el proyecto, activamos la configuración que necesitamos, y en el menú contextual de la solución (no del proyecto) seleccionamos "Restaurar todos los paquetes NuGet" y vemos cómo cambian nuestros paquetes.

Y aquí en este punto me quedé atascado; para compilar todas las configuraciones a la vez podríamos utilizar la compilación por lotes (menú "Compilación» -> «Compilación por lotes"), pero al cambiar configuraciones no hay una restauración automática de paquetes. Y tampoco ocurre al compilar el proyecto, aunque, en teoría, debería. No encontré soluciones a este problema con las herramientas estándar. Y probablemente también sea un error de Visual Studio.
Por lo tanto, se decidió utilizar un sistema de construcción automatizada especial para la compilación por lotes . En realidad no quería hacerlo, ya que considero que es innecesario en el desarrollo de complementos, pero por el momento no veo otra solución. Y para la pregunta "¿Por qué Nuke?" la respuesta es sencilla: lo usamos en el trabajo.
Así que, vamos a la carpeta de nuestra solución (no del proyecto), mantenemos presionada la tecla Shift y hacemos clic con el botón derecho en un espacio vacío en la carpeta; en el menú contextual seleccionamos "Abrir ventana de PowerShell aquí».

Si no tiene instalado nuke, primero escriba el comando
dotnet tool install Nuke.GlobalTool --globalAhora escriba el comando nuke y se le pedirá que configure nuke para el proyecto actual. No sé cómo sería correcto escribir esto en ruso – en inglés lo dirían Could not find .nuke file. Do you want to setup a build? [y/n]
Presionamos la tecla Y y a continuación estará el apartado de configuración. Necesitamos la opción más sencilla utilizando MSBuild, por lo que respondemos como en la captura de pantalla:

Pasemos a Visual Studio, que nos pedirá reiniciar la solución, ya que se ha añadido un nuevo proyecto. Reiniciamos la solución y vemos que tenemos un proyecto build donde solo nos interesa un archivo – Build.cs

Abrimos este archivo y escribimos un script para compilar el proyecto con todas las configuraciones. O podemos usar mi script, que puedes editar a tu gusto:
using System.IO;
using Nuke.Common;
using Nuke.Common.Execution;
using Nuke.Common.ProjectModel;
using Nuke.Common.Tools.MSBuild;
using static Nuke.Common.Tools.MSBuild.MSBuildTasks;
[CheckBuildProjectConfigurations]
[UnsetVisualStudioEnvironmentVariables]
class Build : NukeBuild
{
public static int Main () => Execute(x => x.Compile);
[Solution] readonly Solution Solution;
// Si el nombre de la solución y el nombre del proyecto (plugin) son diferentes, indique el nombre del proyecto (plugin) aquí
string PluginName => Solution.Name;
Target Compile => _ => _
.Executes(() =>
{
var project = Solution.GetProject(PluginName);
if (project == null)
throw new FileNotFoundException("¡No encontrado!");
var build = new List();
foreach (var (_, c) in project.Configurations)
{
var configuration = c.Split("|")[0];
if (configuration == "Debug" || build.Contains(configuration))
continue;
Logger.Normal($"Configuración: {configuration}");
build.Add(configuration);
MSBuild(_ => _
.SetProjectFile(project.Path)
.SetConfiguration(configuration)
.SetTargets("Restore"));
MSBuild(_ => _
.SetProjectFile(project.Path)
.SetConfiguration(configuration)
.SetTargets("Rebuild"));
}
});
}Regresamos a la ventana de PowerShell y escribimos de nuevo el comando nuke (se puede escribir el comando nuke con la especificación requerida Objetivo. Pero tenemos uno Objetivo, que se ejecuta por defecto). Al presionar la tecla Enter, nos sentiremos como verdaderos hackers, ya que, como en las películas, se iniciará la compilación automática de nuestro proyecto bajo diferentes configuraciones.
Por cierto, se puede usar PowerShell directamente desde Visual Studio (menú “Vista» -> «Otras ventanas» -> «Consola del administrador de paquetes“), pero todo será en blanco y negro, lo cual no es muy conveniente.
Con esto concluyo mi artículo. Estoy seguro de que con la opción para AutoCAD podrás resolverlo por tu cuenta. Espero que el material presentado aquí encuentre sus “clientes”.
¡Gracias por su atención!
Fuente: habr.com
