[[PageOutline]] = !TolPackage: Un sistema de desarrollo modular en red = == Objetivos == El objetivo principal de este sistema es que los usuarios finales que no colaboran en el desarrollo de las librerías de uso más o menos genérico, no tengan que preocuparse de dónde se encuentra el código fuente de las mismas ni tenga que tener instalados programas para compartir código como CSV o SVN. El usuario sólo necesita saber una URL remota o un camino local donde se encuentran los paquetes y el nombre de los que necesita. El sistema se ocupa de instalar todo lo necesario de forma automática si se desea o manualmente si se trata de procesos críticos. El otro gran objetivo es fomentar la creación de utilidades concretas que se puedan cargar atómicamente sin necesidad de compilar cantidades ingentes de código del cual luego se usa sólo una pequeña parte la mayor parte de las veces. El usuario no necesita saber qué paquetes dependen de cuáles sino que son ellos mismos quienes se autogestionan. También resulta de vital importancia permitir que cada paquete evolucione sin excesivas ataduras de compatibilidad hacia atrás para que pueda progresar pero sin dejar tirados a los usuarios que por motivos de seguridad no pueden arriesgarse a actualizarse cada poco tiempo. Por último, debe ser muy fácil de usar. Una vez cargado, el paquete es siempre global aunque se haya llamado desde un ámbito local puesto que tratándose de herramientas de uso general no tiene sentido tener tantos miramientos. Como contrapartida, un paquete no se puede descargar de la memoria una vez que ha sido cargado, pero eso tampoco es problema puesto que no ocupan un espacio significativo en comparación con los datos de los problemas que pueden resolver. == Arquitectura == Un paquete es un tipo de NameBlock especial que se carga con la orden {{{ #Require nombre_de_paquete; }}} y que se lee directamente de un archivo OIS almacenado previamente en un directorio local predeterminado por el sistema al que llamaremos almacén del cliente y que es común para todas las instalaciones de TOL que haya en una misma máquina. Un repositorio es un sitio web o un directorio local al que llamaremos también almacén servidor, donde se encuentra disponible una colección de paquetes ya preparados para que los usuarios puedan descargarlos a su almacén de cliente. Los paquetes disponibles en repositorios remotos o locales se instalan, actualizan y manejarlos mediante las utilidades de {{{StdLib::TolPackage::Client}}} Las utilidades para la creación de repositorios se encuentran en {{{StdLib::TolPackage::Server}}} == Creación y uso de paquetes == Un paquete se crea como un NameBlock pero con una serie de restricciones de obligado cumplimiento === Estructura === * Debe crearse en un fichero principal del mismo nombre con extensión .tol y dentro de un directorio raíz llamado igual. El fichero principal puede cargar con #Embed los archivos auxiliares que necesite con nombre libre pero ubicación paralela o en subdirectorios del raíz. * Los miembros obligatorios del NameBlock de un paquete son: [[BR]] {{{ Text _.autodoc.name = "NombreDelPaquete"; Text _.autodoc.brief = "Descripción cortísima en una línea"; Text _.autodoc.description = "Descripción detallada"; Text _.autodoc.url = "http://.../"; Set _.autodoc.keys = [["Palabras","Clave", ...]]; Set _.autodoc.authors = [[ "fulanito@mail.tal", "menganito@mail.cual"]]; Text _.autodoc.minTolVersion = Copy(TolReleaseId); Real _.autodoc.version.high = 1; Real _.autodoc.version.low = 1; Set _.autodoc.dependencies = [["paquete1","paquete2", ...]]; }}} * Cuando un paquete requiera para su ejecución de recursos ajenos a TOL será necesario añadir un nuevo miembro que será un conjunto de conjuntos de caminos a los directorios raíces de cada uno de los recursos. Los que sean independientes de la plataforma se pueden poner en un elemento {{{crossPlatform}}} y los que sí dependan en uno con el nombre de la plataforma. Los nombres de los recursos han de ser únicos dentro de cada plataforma específica y serán lógicamente los mismos en todas ellas, pues los recursos deberían estar disponibles en cualquiera de ellas, aunque podría haber excepciones si no se encuentra la forma de implementarlo en alguna. Los recursos multi-plataforma deben tener nombres únicos distintos de los de las específicas pues en el almacén de cliente convivirán con ellos en un mismo directorio. [[BR]] {{{ Set _.autodoc.nonTolResources = { [[ Set crossPlatform = { [[ Text resource_cross_1="./....", Text resource_cross_2="./....", ... ]] }, Set win32_x86 = { [[ Text resource_1="./....", Text resource_2="./....", ... ]] }, Set linux_x86 = { [[ Text resource_1="./....", Text resource_2="./....", ... ]] }, ... ]] }; }}} === Control de versiones === El objetivo del control de versiones es que en el repositorio se puedan almacenar todas las versiones binarias de cada paquete que hayan sido publicadas de forma histórica, pues sólo de esta forma se puede asegurar que cada usuario pueda utilizar la versión compatible con todo su sistema, y que pueda a lo largo del tiempo, y accediendo a las novedades que le interesen fijando su propio ritmo de actualización, sin tener que estar siempre a la última, lo cual puede ser peligroso en sistemas de mantenimiento de producción en tiempo real, ni tampoco atándose a una versión congelada que no admite mejora ninguna. * El nombre del archivo en el que se almacena una versión concreta de un paquete se obtiene como la concatenación del nombre del paquete, que debe coincidir con el miembro redundante {{{_.autodoc.name}}}, seguido de un punto, el número {{{_.autodoc.version.high}}}, otro punto y el número {{{_.autodoc.version.low}}}. * El nombre del NameBlock sin embargo es el mismo siempre, a lo largo de toda la evolución del paquete, por lo que se invocará sin usar los números de versión, salvo en la orden {{{#Require}}}, si se desea cargar una versión concreta. * No es posible por tanto cargar dos versiones distintas de un mismo paquete en la misma sesión TOL. * Sí que es posible cargar diferentes versiones de un mismo NameBlock en una misma máquina en sesiones de TOL distintas, coincidentes o no en el tiempo. * No puede haber paquetes con el mismo nombre de NameBlock ni en el mismo ni en distinto repositorio, pues todos los paquetes compartirán un mismo almacén de cliente vengan del repositorio que vengan. El sistema de instalación y mantenimiento de paquetes debe avisar si existen conflictos entre distintos repositorios. * De forma opcional el paquete puede incorporar en _.autodoc.versionControl información adicional sobre el mecanismo de control de versiones utilizado en su desarrollo (SVN, CVS, ...) que indique cómo recuperar el código fuente exacto con el que fue construido el paquete. Esta información podría usarse de forma manual y sólo en caso de tener que rehacer un paquete antiguo que se hubiera perdido o que se quisiera modificar por un problema grave, y que fuera requerido en algún proceso de vital importancia. Llegados a este caso habría que plantearse si el paquete modificado debe sobreescribir el existente en el repositorio o se debe usar sólo de forma local donde se precise. Para el caso de SVN existe una forma muy sencilla de definir este campo opcional [[BR]] {{{ Text _.autodoc.versionControl = AvoidErr.NonDecAct(OSSvnInfo(".")); }}} ==== Nomenclatura ==== Aunque no hay ninguna normativa general al respecto, salvo en el caso del repositorio público oficial de TOL en el que serán de obligado cumplimiento, se aconseja tomar las siguientes medidas con respecto a la nomenclatura de los paquetes: * Los nombres de los paquetes deberían seguir el estilo CamelCase que permite una mayor claridad y una mejor organización de los mismos si se reservan partículas con significado preestablecido. * Aumentar el primer número de forma secuencial progresiva de uno en uno, empezando desde el 1, reservando el 0 para versiones de pruebas no publicadas. Sólo se debería modificar si se da un cambio, o una acumulación de cambios, que suponga alteraciones importantes en el modo de uso o las capacidades del paquete. * Aumentar el segundo número cada vez que se modifica el paquete en cualquier cosa que pueda ser incompatible con lo anterior en cualquier aspecto por pequeño que sea. Si no se modificara al publicarlo sobreescribirá el anterior y será imposible que los usuarios utilicen la versión eliminada si por cualquier motivo la nueva no les sirve. * Cuando se aumenta el primer número se debería reiniciar el segundo. * Al segundo dígito se le puede intentar dotar de cierta semántica propia del paquete o de u proyecto o departamento, obligando a que tengan más o menos cifras. Por ejemplo, podrían ser de la forma XxYy, con X,Y=1..9; x,y=0..9; significando Yy para cierto tipo de cambios menores o más frecuentes y Xx para otros de mayor calado. * Si se produce un cambio meramente ornamental, de documentación, o que resuelva un problema menor que no pueda tener ningún tipo de efecto secundario se puede sobreescribir el paquete publicado Esto puede ser recomendable cuando un paquete es muy pesado para no derrochar demasiado espacio en el repositorio. ==== Dependencias ==== * Cada versión de un paquete requiere de una versión mínima de TOL dada por el miembro _.autodoc.minTolVersion que asegura su funcionamiento, aunque podría ser que funcionara con versiones anteriores, sin que sea fácil asegurarlo a ciencia cierta. Es de vital importancia revisar este campo antes de publicar una nueva versión de un paquete si ha habido cambios en la versión de TOL que pudieran afectarle y que hubieran ocurrido desde la publicación de la última versión del paquete. En principio sólo es preciso actualizar {{{_.autodoc.minTolVersion}}} si se hace uso de alguna nueva capacidad de TOL, lo cual no siempre es trivial conocer. En caso de duda es mejor actualizar de más que de menos. * Cuando un paquete requiere de otros se debe incluir las correspondientes sentencias #Require antes de la declaración del primer miembro. * Cuando el {{{#Require}}} no menciona la versión concreta del paquete que se desea cargar entonces se utilizará la versión más moderna existente localmente que sea compatible con la versión del TOL con el que se esté trabajando. * Si se solicita una versión concreta, entonces se debe cargar previamente cada uno de los paquetes requeridos directa o indirectamente. * Téngase en cuenta que no es posible cargar versiones concretas de paquetes que tengan requerimientos de diferentes versiones de un mismo paquete. * La definición del paquete siempre debe usar #Require sin especificar una versión concreta. * Obsérvese que los paquetes requeridos se deben especificar por duplicado: primero en los #Require sin comillas y luego en Set _.autodoc.dependencies entre comillas pues esta información la necesita el gestor de repositorios. * No está permitido el requerimiento cíclico directo ni indirecto entre paquetes, es decir, si A requiere a B directa o indirectamente B no puede requerir a A ni directa ni indirectamente. El sistema gestor de repositorios caería en un ciclo infinito y no hay forma de detectarlo luego es responsabilidad de los desarrolladores del repositorio el evitarlo. === Código declarativo === * Puesto que el NameBlock de un paquete se almacena como un módulo OZA, está terminantemente prohibido que un NameBlock ejecute acciones de ningún tipo durante su creación. Es decir, las declaraciones de los miembros no pueden tener efectos secundarios como * Cambiar parámetros globales de TOL [[BR]] {{{ PutEditor, PutLanguage, PutDefaultDates, PutDumpFile, PutTableRealFormat, PutTableDateFormat, PutRealFormat, PutDateFormat }}} * Modificar variables con [[BR]] {{{ PutValue, :=, PutName, PutSerDat, PutMatDat, PutVMatDat, PutVMatBlock, PutCoef SetIndexByName, Append }}} * Llamar al sistema operativo [[BR]] {{{ FileDelete, FileRename, FileCat, MkDir, System, ShellExecute, WinSystem, ChildProcess, WinRmtSystem, WinRmtKill, WinRmtProcessAlive }}} * Llamar a funciones del interfaz [[BR]] {{{ Tcl_Eval, Tcl_EvalEx }}} * Abrir ficheros [[BR]] {{{ ShowFile,WriteFile,AppendFile MatWriteFile,MatAppendFile,VMatPrint BDTFile,BSTFile,BMTFile,StatFile,BSIFile FOpen,FGetText,FPutText,FEof,FFlush,FClose }}} * Abrir conexiones a la base de datos o a cualquier otro mecanismo de consulta. {{{ DBOpen,DBActivate,DBGetOpened,DBClose,DBExecQuery, DBSeries,DBSeriesColumn,DBSeriesTable, DBMatrix,DBTable,DBCreateSeriesTable,DBTableColumn, BDBExtract,BDBSaveAs,BDBSeries, BDBClassify,BDBSortAndSave,BDBSort, BDBCell,BDBRead,BDBTable,BDBReg,BDBLine, BDBFieldPos,BDBClose,BDBOpen }}} * Uso de {{{MakeGlobal}}} (De hecho no debería usarse nunca) * Como excepción a lo anterior sí está permitido el uso de {{{ PutStructure }}} ya que se emplea a menudo como mecanismo de documentación interna del código fuente. * Si un paquete necesita ejecutar alguna acción antes de ser utilizado deberá tener un método [[BR]] {{{ Real StartActions(Real void) { ... }; }}} [[BR]] el cual será llamado justo después de ser cargado por el #Require por vez primera. En el caso de que el paquete contenga recursos ajenos a TOL, el método {{{StartActions}}} será quien se ocupe de terminar de inicializar lo que corresponda a esos recursos. Para ello sólo necesita saber el path local que se obtiene llamando a [[BR]] {{{ Text TolPackage::Client::LocalResourcePath(Text package.version, Text resource) }}} * Excepcionalmente, cuando se sabe positivamente que una acción no va a tener efectos secundarios y es necesario efectuarla durante la propia creación del NameBlock y no durante su inicialización puede recurrirse a la función global {{{AvoidErr.NonDecAct}}}. En principio debería restringirse a temas puramente documentales como es el caso de {{{_.autodoc.versionControl}}} === Documentación y chequeo de calidad === * Cada paquete debe estar dotado de documentación autocontenida de manera que cualquier usuario pueda aprender a usarlo por sí mismo. Queda por ver si esa documentación ha de ser WIKI, HTML, PDF, ASCII, o si puede ser opcional el formato. La documentación estará ubicada en cualquier caso dentro de un directorio doc colgando directamente de la raíz del paquete. * También debe tener una batería de tests estándar en un directorio test colgando directamente de la raíz del paquete. En [source:/tolp/OfficialTolArchiveNetwork] pueden verse ejemplos de paquetes. Cada subdirectorio es un paquete. == Creación de repositorios == Un repositorio es un sistema de almacenamiento remoto de cada una de las versiones publicadas históricamente de un grupo de paquetes gestionados por una misma organización que centralice el trabajo de uno o varios grupos de desarrolladores. El sistema debe tener un protocolo de acceso vía URL que sumunistrará todo lo necesario para la instalación y mantenimiento de paquetes. * Información general del propio repositorio: * Nombre. * Descripción. * Responsables. * Información sobre los paquetes: * Información auxiliar de una versión de un paquete. * Listado de todos los paquetes existentes. * Listado de las versiones disponibles de cada paquete. * Tabla de información auxiliar de todas las versiones de cada paquete. * Nombre de la última versión de un paquete * Nombre de la última versión de un paquete compatible con cierta versión de TOL * Listado de paquetes requeridos directamente o indirectamente por un paquete dado * Descarga de paquetes: * Archivo en formato comprimido para la descarga manual. * Archivo en formato ASCII codificado en Base64 para la descarga automática. Debería haber un comité o alguien responsable de probar todos los paquetes de un repositorio antes de publicar las actualizaciones de los paquetes para asegurar la compatibilidad conjunta de todos ellos.