Proyectos Xojo en un MonoRepo

A continuación encontrarás traducido al Castellano un interesante artículo publicado originalmente en el Blog de Xojo y escrito por Jürg Otter. Dicho artículo nos muestra una técnica que podemos utilizar para compartir código entre múltiples proyectos mediante Xojo.

¿Estás trabajando en una solución compuesta por varios proyectos? ¿Estás trabajando en un equipo o bien como único desarrollador y quieres compartir código entre diferentes proyectos? Si es así, entonces un MonoRepo es algo que deberías de considerar.

Otros IDE tienen un concepto de solución o espacio de trabajo compuesto por varios proyectos, donde puedes manejar paquetes para compartir código entre proyectos. Esto no es tan simple como podrías pensar con Xojo. El apartado “Compartir Código” de la Documentación de Xojo explica tres formas diferentes: Copiar y Pegar entre proyectos, Exportar/Importar ítems y, como última opción, también se encuentran los Ítems Externos.

Copiar y Pegar es la forma más sencilla. La desventaja es que si haces un cambio en un ítem compartido, necesitarás copiarlo y pegarlo una y otra vez en todos los proyectos que lo estén utilizando.

Exportar/Importar ítems, así como la opción de Ítems Externos, permite guardar ítems del proyecto como Módulos o Clases en formato Binario o XML. Con un sistema de control de versiones, como puedan ser SVN o Git, uno debería decantarse por XML. Sin embargo, a muchos no les gusta el formato XML. Es un formato legible en texto plano, pero no es tan limpio si se compara con el formato de Texto de Xojo.

¿Cuál preferirías leer cuando estés mirando los cambios, diffs, conflictos?

  • A la izquierda: Formato de proyecto XML de Xojo (*.xojo_xml_project)
  • A la derecha: Formato de proyecto en Texto (*.xojo_project)

Decididamente querría utilizar el formato de proyecto en Texto de Xojo. Y quiero múltiples proyectos que compartan código, como Módulos con Extensiones de Métodos y varios Métodos de ayuda que he escrito a lo largo de los años. Las Clases con la lógica del producto pueden usarse en aplicaciones de consola, escritorio y web. Nuevamente, aquí también hay una necesidad de compartir código.

De modo que veamos como puede hacerse esto con MonoRepo. Pero, ¿qué es un MonoRepo?

MonoRepo

El nombre consiste de dos partes: “Mono” (significado: único) y “Repo” (abreviatura de Repositorio). Lo que significa: es una estrategia de desarrollo de software donde el código para múltiples proyectos se almacena en el mismo repositorio (y por tanto en la misma carpeta principal).

Ventajas:

  • Reutilización del Código: Puede abstraerse la funcionalidad en Módulos o Clases compartidos, e incluirse en múltiples proyectos sin necesidad de emplear un sistema de gestión de paquetes.
  • Commits atómicos: Cuando varios proyectos trabajan juntos, las releases necesitan estar sincronizadas con versiones que funcionen entre sí. En un MonoRepo todo está en un sitio. Una vez que el proyecto esté listo y actualizado, puedes etiquetar el estado actual. Esto incluirá el estado de todos los proyectos que tienen dependencias para la release. De modo que no hay necesidad de saber qué proyecto en qué versión de estado se ha utilizado en la versión X.
  • Colaboración: Varios desarrolladores pueden trabajar en diferentes proyectos. El código compartido puede ser mejorado por todos los desarrolladores, de lo cual también se beneficiarán otros desarrolladores (u otros proyectos).

Desventajas:

  • Control de Acceso: con repositorios divididos para cada proyecto, puede otorgarse el acceso a un proyecto en función de la necesidad. Sin embargo, un MonoRepo permite acceso de lectura a todo el software en el repositorio.
  • Almacenamiento: Un desarrollador necesita hacer un checkout de todo el MonoRepo, incluso si sólo se está trabajando en un único proyecto de entre los incluidos en el MonoRepo. En función del software desarrollado, esto puede suponer una cantidad significativa de espacio de almacenamiento “no necesario”.

Opinión personal:

Me gusta la configuración de MonoRepo debido al soporte de desarrollo multiplataforma de Xojo. Todo está contenido en un único repositorio/carpeta, todos los archivos están ubicados de forma relativa. De modo que no sólo puedes simplemente mover una carpeta MonoRepo a cualquier ubicación de tu equipo, sino también a otros equipos — funcionando con el mismo u otro sistema operativo. Luego es cuestión de hacer un checkout del repositorio de control de versiones en tu equipo de trabajo Windows y empezar a programar. Commit/Push los cambios, cambia al Mac y check-out/pull la rama actual en la que estés trabajando para hacer algunos tests específicos de la plataforma. Para mi esto es mucho más conveniente y eficiente que la Depuración Remota.

Configuración de los Proyectos Xojo

Veamos cómo podemos configurar un MonoRepo utilizando Xojo. En este ejemplo vamos a crear dos proyectos que comparten una Clase y un Módulo. Hay algunos obstáculos en el camino.

Configurar un nuevo MonoRepo

Todo lo que hacemos es crear una carpeta: “xojo-projects-monorepo”. En una situación del mundo real, tarde o temprano crearás un Repositorio de esta carpeta en el sistema de control de versiones de tu elección.

Proyecto Uno

Una vez he creado un nuevo Proyecto con el nombre “Project One”, lo primero que hay que hacer en el IDE de Xojo es:

  • Añade un nuevo ítem al proyecto: Folder
  • Nombra la carpeta conforme al proyecto, pero en un modo que quede mejor representado en el disco como una carpeta: “project_one”
  • Mueve todos los ítems del proyecto (App, Window1, MainMenuBar) a esta carpeta

Tendrá un aspecto similar a este:

A continuación, guarda este proyecto como un “Projecto Xojo” (*.xojo_project) en la “carpeta MonoRepo”:

¿Qué es lo que tenemos?

  • Project_One.xojo_uistate: Esta es la configuración de estado del IDE cuando se ha guardado el proyecto. En un CVS puedes ignorar los archivos “*.xojo_uistate”.
  • Build Automation.xojo_code: Volveremos a esto más adelante.
  • Project_One.xojo_project: El archivo principal del proyecto, conteniendo las referencias a todos los ítems incluidos en el proyecto, guardados como archivos individuales.
  • Project_One.xojo_resources: Este es un archivo binario que contiene el AppIcon y otros recursos similares.
  • Carpeta “project_one”: La carpeta que hemos creado. Dentro encontrarás los archivos correspondientes a los ítems del proyecto: App.Xojo_code, MainMenuBar.xojo_menu y Window1.xojo_window

Proyecto Dos

De forma resumida, realizaremos los mismos pasos de nuevo; sólo que esta vez para un “Project Two” con todos los elementos del proyecto en una carpeta con el nombre “project_two”.

Nuestro MonoRepo se verá como este:

Por ahora, es claro y simple. Los archivos principales del proyecto están en la carpeta raíz, con todos los ítems relacionados con el proyecto en su propia carpeta.

Pero, ¡hey!, ¿sólo un “Build Automation”? Hmmm… parece un poco desafortunado. Volveremos a ello un poco más adelante.

Código Compartido

Ahora viene la parte más interesante. Vamos a crear una Clase y un Módulo que queremos compartir en ambos proyectos.

En Proyecto Uno

  • Añade una nueva carpeta: “shared_code”
  • Añade un Módulo que contenga una Constante en la carpeta de código compartido: “MyModule”.
  • Añade una Clase que contenga un Método en la carpeta de código compartido: “MyClass”
  • Guarda el Proyecto Xojo.

El código compartido y los elementos del proyecto a compartir están guardados ahora como archivos/carpetas individuales en el disco:

Añadir Código Compartido a Proyecto Dos

Ahora queremos añadir este código compartido en “Proyecto Dos”

En Windows puedes hacer esto arrastrando y soltando: Arrastra la carpeta “shared_code” desde el Explorador de Windows al Navegador (panel izquierdo) del IDE de Xojo en “Proyecto Dos”

En macOS, sin embargo, esto no funciona como podrías esperar. Debido a un bug el archivo de proyecto se corromperá dado a que estará guardando ubicaciones erróneas. Para mostrártelo: he hecho esto y he abierto el archivo “Project_Two.xojo_project” en un Editor de Texto:

Uops… MyClass y MyModule tienen una ubicación errónea: ../../../

Como resultado, el proyecto no se cargará correctamente.

Por tanto, te recomiendo los siguientes pasos:

  • Añade las carpetas necesarias manualmente en el IDE de Xojo: En “Project Two”: Inserta Folder “project_two” ¡(el mismo nombre que se muestra en disco!).
  • Abre “Proyecto Uno”, selecciona y haz clic con el botón derecho para copiar los ítems de proyecto “MyClass” y “MyModule”
  • De vuelta en “Proyecto Dos”: pega los ítems de proyecto en la carpeta “shared_code” creada previamente
  • Cierra de nuevo “Proyecto Uno”, de modo que sólo mantengas abierto un proyecto

Así es cómo debería quedar; los ítems de proyecto compartidos estarán referenciados correctamente, de forma relativa a la carpeta raíz de MonoRepo:

¡Y listo! Ahora tenemos “MyClass” y “MyModule” disponibles en ambos proyectos. Y dado que se tratan de los mismos archivos en disco, estarán compartidos. De modo que, de ahora en adelante, cualquier cambio en el código se reflejará y usará en ambos proyectos.

Sólo tendrás que realizar estos pasos cada vez que añadas un nuevo ítem de proyecto que vayas a compartir entre los proyectos. Una vez que estén referenciados en los archivos de proyecto, estos apuntarán al mismo archivo en todos los proyectos. Esto es así porque el archivo de proyecto es el raíz de MonoRepo, y todos los ítems de proyecto añadidos estarán referenciados de forma relativa sobre dicha carpeta raíz.

De modo que no importa dónde esté ubicada la carpeta principal, y la estructura funciona correctamente para todos los Sistemas Operativos que puedes utilizar para programar con el IDE de Xojo.

Vamos a programar

Finalmente, hagamos uso de esto y empleemos el código compartido en los proyectos:

A medida que tus proyectos evolucionen puedes añadir más Clases compartidas, Módulos, Métodos, Constantes, Imágenes y añadirlos a los otros proyectos en un MonoRepo tal y como se ha explicado anteriormente.

¡Disfruta de las ventajas de tu MonoRepo y del código reutilizable!

Advertencia

Ten en cuenta que el IDE de Xojo no está diseñado para código compartido. No verá tu MonoRepo como “una solución para varios proyectos”. No se enterará de los cambios realizados más allá de un proyecto que esté abierto/cargado.

Es por ello por lo que recomiendo tener abierto sólo un proyecto al mismo tiempo, y abrir sólo un segundo proyecto para copiar y pegar nuevos ítems de proyecto en el otro proyecto.

¿Por qué? Bien, asumamos que tienes dos proyectos abiertos. Estás haciendo un cambio en “MyClass” en el “Proyecto Uno” y guardas. Dado que el IDE de Xojo con el “Proyecto Dos” abierto no se entera de este cambio, aún tendrás el estado previo en memoria; y lo más probable es que si guardas “Proyecto Dos” sustituirás el archivo con el estado que actualmente tiene “Proyecto Dos”, perdiendo por tanto los cambios realizados en el elemento “MyClass” del “Proyecto Uno”.

Una alternativa podría ser la siguiente: guarda con frecuencia el proyecto después de cualquier cambio. Nunca cambies a otro proyecto antes de guardar. Si un segundo proyecto abierto necesita ese cambio, cambia a dicho proyecto y selecciona “Revert to Saved”. Puedes hacer esto simplemente quitando la selección de un Build Target para hacer que el proyecto se marque como “con cambios”, de modo que se active dicha opción del menú. Entonces selecciona “Revert to Saved” (sin guardar los cambios).

Pero, sinceramente, es demasiado arriesgado en mi opinión. Es por ello por lo que intento trabajar sólo en un proyecto al mismo tiempo. Como siempre… la excepción confirma la regla 🙂

Grupos de Tipos de Archivo

Si tu aplicación utiliza archivos con iconos específicos, entonces lo más probable es que crees un File Type Group. En un MonoRepo has de tener en cuenta el modo en el que Xojo guarda esto. Echemos un vistazo a un ejemplo:

He creado un “File Type Group” con el nombre MyFileTypeGroup, y lo he guardado en la carpeta shared_folder. ¿Es una buena idea?

  • MyFileTypeGroup.xojo_filetypeset: Este es el archivo de texto que contiene información sobre el tipo de archivo personalizado. Está bien, pero… ¿dónde está el icono asignado?
    Si miras con más detalle en los contenidos de este archivo observarás que este referencia: Project_Two.xojo_resources
  • Recuerda… hemos visto este archivo y lo hemos descrito de esta forma: Project_Two.xojo_resources: Este es un archivo binario que contiene el AppIcon y otros recursos similares.
    De modo que contiene información binaria tanto para el AppIcon como para los iconos de FileType.

Volviendo a la cuestión, ¿ha sido buena idea guardarlo en la carpeta de código compartido?

No lo creo… bueno, funciona, incluso si lo añades a un segundo proyecto. Sin embargo, cuando se guarda el proyecto siempre referenciará al proyecto que esté abierto , y guardará la representación binaria del Icono nuevamente en otro archivo .xojo_resources. Esto deriva en cambios no deseados en ambos archivos.

De modo que mejor no hacerlo. Guarda siempre sólo un “File Type Group” por proyecto. Incluso si esto implica duplicaciones no deseadas de un elemento de proyecto que vayas a utilizar en varios proyectos.

Simplemente acepto dicho inconveniente, sabiendo que estoy utilizando Xojo y que el formato de proyecto en texto en un modo para el cual no ha sido diseñado. El resto de los beneficios lo compensan.

Compilaciones automatizadas

¿Recuerdas que queríamos echar un vistazo a esto más tarde, cuando advertimos de que sólo hay un archivo “Build Automation.xojo_code” en un MonoRepo?

La compilación automatizada estará compartida entre todos los proyectos dado que no puedes moverlo a otra ubicación. Siempre estará junto al archivo de proyecto. Para nuestro MonoRepo esto significa que será el mismo para todos los proyectos dado que tenemos todos los archivos de proyecto en la carpeta raíz del repositorio.

¿Qué podemos hacer para tener una Compilación Automatizada por proyecto? Necesitamos un modo de distinguir los proyectos en el IDE y los scripts de compilación. Lo que ha funcionado bien en nuestro caso es usar “Build Setting: Windows – Internal Name” y configurarlo a un nombre de proyecto (interno):

Scripts Pre/Post Build

En el caso de los Pasos de Compilación (Build Steps / Scripts) y Scripts externos necesitaremos añadir primero una comprobación de si queremos ejecutarlo para el proyecto que se ba a compilar:

if (PropertyValue("App.InternalName") <> "project_one") then return

Esta es la alternativa para superar esta limitación en el IDE de Xojo en el que no se tiene una Automatización de Compilación por proyecto cuando se tienen varios proyectos en la misma carpeta (lo que sucede en el caso de nuestro MonoRepo). Verás todos los Scripts en todos los proyectos. Y dado que el IDE de Xojo ejecutará todos ellos, necesitamos asegurarnos de que no lo haga en el caso de que se trate de un script para otro proyecto.

Dicho esto, ¡también es algo bueno! Obviamente esto también nos permite compartir los Scripts de Compilación entre todos o algunos de nuestros proyectos.

Esta captura de pantalla muestra un ejemplo de un Script creado para que funcione sólo con el “Proyecto Uno”:

Pasos para Copiar Archivos

No hay buenas noticias en este caso… actualmente no hay opciones para comprobar un valor “por proyecto” para saber si se debe de ejecutar un paso “Copy File”. Es un caso de “o para todos los proyectos” o nada.

Una alternativa es la de copiar archivos adicionales utilizando un Post Build Script. Y si estás utilizando el IDE de Xojo en todos los Sistemas Operativos soportados (Windows, macOS, Linux), entonces deberás de utilizar condicionales para saber el que sistema operativo se está ejecutando el IDE de Xojo y para cuál está compilando.

Los archivos a copiar pueden guardarse en un Array de String. Para saber dónde han de copiarse (relativo a la ubicación del MonoRepo, de modo que no importa la ubicación en la cual el desarrollador vuelque el repositorio):

if TargetWindows then
  sFolderResources = "%PROJECT_PATH%\resources"
elseif TargetMacOS or TargetLinux then
  sFolderResources = "$PROJECT_PATH/resources"
end if

¿Dónde copiar los archivos?

Dim sAppBuildPath As String
if TargetWindows then
  sAppBuildPath = CurrentBuildLocation
elseif TargetMacOS or TargetLinux then
  sAppBuildPath = ReplaceAll(CurrentBuildLocation, "\", "") 'don't escape Path
end if

Este ejemplo utilizará los comandos del Shell adecuados tanto en Windows como en macOS/Linux para copiar la lista de archivos:

Dim i As Integer = sCopyFiles.Ubound
while (i > -1)
  if TargetWindows then
    call DoShellCommand("copy """ + sCopyFiles(i) + """ """ + sCopyToFolder + """ /y /v", 0)
  elseif TargetMacOS or TargetLinux then
    call DoShellCommand("cp -f """ + sCopyFiles(i) + """ """ + sCopyToFolder + """", 0)
  end if
  i = i - 1
wend

Añadir un tercer proyecto

Antes de añadir un nuevo proyecto, asegúrate de haber hecho commit de todos los cambios del MonoRepo en tu sistema de control de versiones.

Así, si luego creas un nuevo proyecto, y realizas los mismos pasos descritos anteriormente (mover todos los ítems de proyecto en una nueva carpeta “project_three”) y guardas el proyecto en la carpeta raíz del MonoRepo… ¿qué crees que pasará?

Los archivos relacionados con el nuevo proyecto están en la raíz y en la carpeta creada “project_three”. Esto es lo previsible. Sin embargo, observará que los archivos de Compilación Automatizada se han sustituido. Esto es algo desafortunado, pero también espero. Se resume en el hecho de que el IDE de Xojo con tu nuevo proyecto recién creado no es consciente de lo que hayas incluido anteriormente en Build Automation. De modo que guardará el Build Automation vacío, dado que es el estado del proyecto en curso en el IDE.

No te preocupes, sólo has de salir del IDE de Xojo, y revertir los cambios en tu Build Automation desde tu sistema de control de versiones. Vuelve a abrir el proyecto y ahora tomará el estado anterior que justo acabas de revertir. Ahora es posible añadir más pasos de compilación para este tercer proyecto.

También pueden tener lugar problemas similares si vas a copiar y pegar ítems de código compartido, por ejemplo desde “project_one” al recién añadido “project_three”. Después de guardar, tu sistema de control de versiones te mostrará una buena cantidad de cambios en el código compartido; por ejemplo, propiedades guardadas como “True” cuando antes se habían guardado sin comillas. Por ello, generalmente sigo el mismo procedimiento: revertir los cambios innecesarios/no deseados en los ítems de código compartido después de copiar y pegar y guardarlos en nuevo proyecto. Esto sirve dado que no se han producido cambios “reales” en dichos archivos. Lo que importa es que los archivos estén enlazados adecuadamente en el archivo principal del proyecto correspondiente a “project_three”.

Todo lo demás

No pretendo explicar otras posibilidades avanzadas aquí. Sólo un par de situaciones con las que puedes encontrarte:

Un Método en un Módulo compartido incluye contenido Desktop, pero quieres que el resto del Módulo se utilice en una app de Consola también. Puedes hacerlo utilizando “Compilación Condicional“; ya sea en Código o seleccionando un Target (“Include In”) para la Clase/Método desde el Inspector.

Necesitas o quieres compilar diferentes proyectos con diferentes versiones de Xojo. Este podría ser el caso para un proyecto antiguo. O bien puede darse el caso de que quieras compilar para Windows utilizando una versión de Xojo antigua, mientras que quieres utilizar la versión más reciente de Xojo para macOS. En este caso, te sugiero programar empleando la versión más antigua utilizada de Xojo. Añade la funcionalidad disponible sólo en las nuevas versiones de Xojo utilizando, nuevamente, la Compilación Condicional:

#If XojoVersion >= 2021.01 Then

Incluso puedes utilizar los nuevos Manejadores de Evento (los disponibles sólo en las nuevas versiones de Xojo) utilizando AddHandler (https://docs.xojo.com/AddHandler). Por ejemplo, hemos utilizado esto con el soporte del Modo Oscuro para las compilaciones de macOS (mientras que las compilaciones de Windows aun se realizan utilizando versiones anteriores).

Sobre nuestro MonoRepo

Nuestro MonoRepo principal contiene actualmente 12 proyectos escritos en Xojo. Es una mezcla de aplicaciones Desktop y Consola, aplicaciones comerciales e internas. Se han creado utilizando múltiples desarrolladores, permitiéndonos programar o depurar el IDE de Xojo en distintos entornos de sistema operativo con facilidad.

Hemos estado utilizando esta técnica durante más de 14 años con REALbasic, REAL Studio y Xojo, utilizando siempre el formato Text Project.
Hemos estructurado nuestro MonoRepo más o menos así:

  • /
    
    /project_A
    
    /project_B

    Tal y como has visto anteriormente: todos los archivos de Proyecto y el compartido BuildAutomation en la carpeta raíz. Y cada proyecto tiene su propia carpeta de proyecto.

  • /shared

    La carpeta común utilizada por todos nuestros proyectos, no importa si el proyecto necesita todo lo que se encuentra ahí en su actual etapa de desarrollo. Es una especie de nuestra propia aportación al Framework de Xojo y que podemos utilizar en todos nuestros proyectos.

  • /shared/controls

    Tenemos subclases de cada Control (para obtener funcionalidad ampliada, correcciones y soluciones alternativas) y sólo utilizamos las subclases de los Controles en todos nuestros proyectos.

  • /shared/classes
    
    /shared/functions
    
    /shared/resources

    Lo mismo en este caso. A lo largo de los años hemos escrito varios Módulos con Extensiones personalizadas (https://docs.xojo.com/Extends) para las Clases del Framework de Xojo, funcionalidad adicional/personalizada para String/XML/JSON, además de abstraer la funcionalidad de encriptación que necesitamos, y también tenemos un conjunto de nuestros propios tipos de datos y estructuras que no están disponibles en el Framework de Xojo. Y también hay algunos recursos que utilizamos prácticamente en cada proyecto, como es el logo de la compañía o los Iconos de la Toolbar.

  • /components
    
    /components/reporting
    
    /components/webservice

    Estos componentes se utilizan en algunos proyectos, pero no en todos. Lo importante aquí es que necesitan ser funcionales por sí mismos, sin dependencias con el código de una app/proyectos. Algunos ejemplos conocidos en la comunidad Xojo pueden ser el motor de informes Shorts, o Aloe para los WebServices.

  • /build
    
    /build/configuration
    
    /build/processing

    Finalmente, pero no por ello menos importante, en esta carpeta tenemos toda la funcionalidad relacionada con la compilación/post-compilación. Aquí se incluyen ajustes de compilación por proyecto (leídos y utilizados por los scripts de post-compilación); y sí, estás en lo cierto, este podría haberse incluido en las carpetas correspondientes a cada proyecto, así como nuestros scripts de post-compilación (nuevamente ejecutados por los scripts de compilación), y que son los encargados de desplegar todas nuestras compilaciones (versiones Alphas/Betas/Final) internamente y subirlas a nuestros WebServices de actualización de la aplicación.
    Sólo puedo recomendar tener todo el material relacionado con la compilación y post-compilación bajo un sistema de control de versiones. Ya sea para una corrección de emergencia para una antigua versión (mientras que nuestra configuración/proceso de la versión actual ha cambiado de forma significativa), o para trabajar en una rama de característica en una nueva funcionalidad que requiere de cambios en el proceso de compilación (sin afectar el flujo de producción).

¡Eso es todo amigos!

Espero que esta breve introducción sobre cómo puedes compartir código entre diferentes Proyectos de Xojo mediante MonoRepo haya sido de utilidad.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *