<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>chernando</title><link href="/" rel="alternate"></link><link href="/feeds/all.atom.xml" rel="self"></link><id>/</id><updated>2011-10-16T17:22:00+02:00</updated><entry><title>PHP, PEAR y PHPUnit para Yii en Mac OS X Lion</title><link href="/desarrollo/php-pear-y-phpunit-para-yii-en-mac-os-x-lion/" rel="alternate"></link><updated>2011-10-16T17:22:00+02:00</updated><author><name>Carlos Hernando</name></author><id>tag:,2011-10-16:desarrollo/php-pear-y-phpunit-para-yii-en-mac-os-x-lion/</id><summary type="html">&lt;p&gt;He tenido que montar un entorno de desarrollo para Yii, un framework de
PHP, en mi Mac OS X Lion. Como he tenido que hacer varios apaños he
recopilado todos los ajustes en esta entrada.&lt;/p&gt;
&lt;p&gt;Antes de nada enumero las versiones del software utilizadas:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Mac OS X 10.7.2&lt;/li&gt;
&lt;li&gt;PHP 5.3.6&lt;/li&gt;
&lt;li&gt;PEAR 1.9.4&lt;/li&gt;
&lt;li&gt;PHPUnit 3.5.15&lt;/li&gt;
&lt;li&gt;Selenium Server 2.8.0&lt;/li&gt;
&lt;li&gt;Yii 1.1.8&lt;/li&gt;
&lt;li&gt;Firefox 7.0.1&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;La mayoría de las acciones que comento han de realizarse con permisos
de administrador.&lt;/p&gt;
&lt;div class="section" id="activar-el-servidor-web-con-php"&gt;
&lt;h2&gt;Activar el servidor web con PHP&lt;/h2&gt;
&lt;p&gt;Mac OS X Lion incluye Apache con soporte para PHP. Sin embargo es
necesario activarlo. Para ello en &lt;em&gt;/etc/apache2/httpd.conf&lt;/em&gt;
descomentamos:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="nb"&gt;LoadModule&lt;/span&gt; php5_module libexec/apache2/libphp5.so
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Y en &lt;em&gt;/etc&lt;/em&gt; copiamos el fichero de configuración de fábrica:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;#&lt;/span&gt; cp /etc/php.ini.default /etc/php.ini
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Nota&lt;/strong&gt;: personalmente prefiero que mis entornos de desarrollo y
pruebas sean lo más privados posibles. Por ello modifico el &lt;em&gt;Listen&lt;/em&gt; de
&lt;em&gt;/etc/apache2/httpd.conf&lt;/em&gt; a&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="nb"&gt;Listen&lt;/span&gt; &lt;span class="m"&gt;127.0.0.1&lt;/span&gt;:80
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Solamente resta activar el servidor web desde &lt;em&gt;System Preferences /
Sharing / Web Sharing&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;El directorio que se publica como página principal está ubicado en
&lt;em&gt;/Library/WebServer/Documents/&lt;/em&gt; podemos utilizar directamente esta
carpeta o bien utilizar carpetas personales (
&lt;a class="reference external" href="http://localhost/"&gt;http://localhost/&lt;/a&gt;&lt;strong&gt;~usuario&lt;/strong&gt;/ ). La opción de carpetas personales busca
el directorio &lt;em&gt;Sites&lt;/em&gt; en el home del usuario, que se puede crear a
partir del cuadro de configuración anterior.&lt;/p&gt;
&lt;p&gt;Para probar que todo está funcionando lo más recomendable es publicar
un fichero .php con un getinfo():&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="nb"&gt;phpinfo&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;&lt;span class="x"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="instalar-pear-y-phpunit"&gt;
&lt;h2&gt;Instalar PEAR y PHPUnit&lt;/h2&gt;
&lt;p&gt;En las versiones anteriores de Mac OS X PEAR está directamente
instalado, en Lion es necesario hacer un paso previo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;#&lt;/span&gt; php /usr/lib/php/install-pear-nozlib.phar
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Con esto ya tenemos PEAR instalado. Actualizamos:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;#&lt;/span&gt; pear upgrade-all
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;configuramos el autodiscover (para hacernos las cosas más fáciles) e
instalamos PHPUnit:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;#&lt;/span&gt; pear config-set auto_discover 1
&lt;span class="gp"&gt;#&lt;/span&gt; pear install pear.phpunit.de/PHPUnit
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Solamente nos falta un detalle más, añadir el software instalado por
PEAR a la configuración de PHP. Añadimos al &lt;em&gt;/etc/php.ini&lt;/em&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="x"&gt;include_path = &amp;quot;/usr/lib/php/pear/&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Para comprobar que todo va bien comprobamos la versión de PHPUnit&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;$&lt;/span&gt; phpunit --version
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="montar-y-ejecutar-las-pruebas-de-un-proyecto-yii"&gt;
&lt;h2&gt;Montar y ejecutar las pruebas de un proyecto Yii&lt;/h2&gt;
&lt;p&gt;A partir de aquí lidiaremos con los problemas que surgen con Yii.
Primero nos descargamos Yii y creamos un proyecto de prueba:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;$&lt;/span&gt; yii/framework/yiic webapp demo
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="section" id="problema-con-date-timezone"&gt;
&lt;h3&gt;Problema con date.timezone&lt;/h3&gt;
&lt;p&gt;Al acceder a la demo puede que nos muestre un mensaje de error como el
siguiente:&lt;/p&gt;
&lt;blockquote&gt;
It is not safe to rely on the system's timezone settings. You are
*required* to use the date.timezone setting or the
date_default_timezone_set() function.&lt;/blockquote&gt;
&lt;p&gt;La solución más sencilla es configurar en &lt;em&gt;/etc/php.ini&lt;/em&gt; el timezone,
por ejemplo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="x"&gt;date.timezone = &amp;quot;Europe/Madrid&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="problema-con-selenium-arrancar-firefox"&gt;
&lt;h3&gt;Problema con Selenium, arrancar Firefox&lt;/h3&gt;
&lt;p&gt;Configuramos la URL para las pruebas en el fichero
&lt;em&gt;protected/tests/WebTestCase.php&lt;/em&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="x"&gt;define(&amp;#39;TEST_BASE_URL&amp;#39;,&amp;#39;http://localhost/~chernando/demo/index-test.php/&amp;#39;);&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Descargamos Selenium y lo dejamos ejecutando:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;$&lt;/span&gt; java -jar selenium-server-standalone-2.8.0.jar
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;e intentamos lanzar las pruebas funcionales del proyecto yii:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;demo/protected/tests/
&lt;span class="gp"&gt;$&lt;/span&gt; phpunit functional/SiteTest.php
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Lo más probable es que falle el lanzamiento de iexplorer (bastante
razonable en Mac OS X ;) ) o que no encuentre firefox-bin ya que no está
en el path. Para solucionarlos editamos &lt;em&gt;protected/tests/phpunit.xml&lt;/em&gt;,
eliminamos:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="nt"&gt;&amp;lt;browser&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Internet Explorer&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;browser=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;*iexplore&amp;quot;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;y modificamos la entrada del Firefox a nuestro, en mi caso es el
siguiente:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="nt"&gt;&amp;lt;browser&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Firefox&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;browser=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;*firefox /Users/chernando/Applications/Firefox.app/Contents/MacOS/firefox-bin&amp;quot;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Con esto podremos ejecutar las pruebas funcionales.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="problema-con-selenium-fallan-los-test"&gt;
&lt;h3&gt;Problema con Selenium, fallan los test&lt;/h3&gt;
&lt;p&gt;Después de llegar hasta aquí me sorprendió bastante que de los tres
tests que vienen de fábrica fallaran dos ;) Concretamente estos dos
fallos:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Body cannot be blank.&lt;/p&gt;
&lt;p&gt;Failed asserting that &amp;lt;boolean:false&amp;gt; is true.&lt;/p&gt;
&lt;p&gt;Password cannot be blank.&lt;/p&gt;
&lt;p&gt;Failed asserting that &amp;lt;boolean:false&amp;gt; is true.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Por suerte es un problema conocido y ya está corregido en el
repositorio.&lt;/p&gt;
&lt;p&gt;El problema es que no se espera a que se ejecuten las validaciones con
JavaScript. El &lt;a class="reference external" href="http://code.google.com/p/yii/source/diff?spec=svn3383&amp;amp;r=3383&amp;amp;format=side&amp;amp;path=/trunk/framework/cli/views/webapp/protected/tests/functional/SiteTest.php"&gt;parche publicado&lt;/a&gt; corrige un par de líneas en el
fichero &lt;em&gt;protected/tests/functional/SiteTest.php&lt;/em&gt; que cambia los dos
&lt;em&gt;assert&lt;/em&gt; que fallan por &lt;em&gt;waitFor&lt;/em&gt;, consiguiendo el comportamiento
esperado.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
</summary></entry><entry><title>Homebrew, gestión de paquetes en Mac OS X</title><link href="/mac/homebrew-gestion-de-paquetes-en-mac-os-x/" rel="alternate"></link><updated>2011-09-22T15:15:00+02:00</updated><author><name>Carlos Hernando</name></author><id>tag:,2011-09-22:mac/homebrew-gestion-de-paquetes-en-mac-os-x/</id><summary type="html">&lt;p&gt;&lt;a class="reference external" href="http://mxcl.github.com/homebrew/"&gt;Homebrew&lt;/a&gt; es un sistema de gestión de paquetes de software para Mac OS
X como podría ser un apt en sistemas GNU/Linux pero sin distribución de
paquetes precompilados.&lt;/p&gt;
&lt;div class="section" id="gestion-de-paquetes-en-mac-os-x"&gt;
&lt;h2&gt;Gestión de paquetes en Mac OS X&lt;/h2&gt;
&lt;p&gt;Apple propone el App Store como sistema para obtener software y
mantenerlo actualizado. Evidentemente solo las aplicaciones publicadas
en el Apple Store pueden ser mantenidas de esta manera. Y aún así la
oferta es bastante limitada si nos fijamos en software Unix.&lt;/p&gt;
&lt;p&gt;Otras opciones disponibles son &lt;a class="reference external" href="http://www.macports.org/"&gt;MacPorts&lt;/a&gt; o &lt;a class="reference external" href="http://www.finkproject.org/"&gt;Fink&lt;/a&gt;. Todos ellas
tienen un planteamiento parecido y diferente al mismo tiempo. MacPorts y
Fink plantean mantener todo el software a parte de la base de Mac OS X
mientras que Homebrew prefiere utilizar al máximo la base propuesta por
el sistema y construir únicamente lo que falte.&lt;/p&gt;
&lt;p&gt;Este planteamiento tiene sus ventajas y sus inconvenientes:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Al necesitar menos dependencias externas es más rápido.&lt;/li&gt;
&lt;li&gt;Al utilizar las dependencias del sistema las cosas se pueden romper
cuando el sistema las actualice.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Por otro lado MacPorts y Fink, aunque más laboriosos tienen un entorno
más controlado y por tanto más estable.&lt;/p&gt;
&lt;p&gt;En resumen, depende de las necesidades de cada uno. En este artículo
veremos el uso de habitual de Homebrew que probablemente sea el más
ligero para empezar a probar.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="requisitos"&gt;
&lt;h2&gt;Requisitos&lt;/h2&gt;
&lt;p&gt;Necesitamos el entorno de compilación de Mac OS X. Aunque algunas
utilidades vienen ya instaladas de fábrica en el sistema, es
recomendable (y en algunos casos indispensable) instalar Xcode. Lo
podemos encontrar en el App Store.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="instalacion-de-homebrew"&gt;
&lt;h2&gt;Instalación de Homebrew&lt;/h2&gt;
&lt;p&gt;Desde la web oficial podemos acceder a la &lt;a class="reference external" href="https://github.com/mxcl/homebrew/wiki/installation"&gt;guía de instalación&lt;/a&gt;. En la
práctica se reduce a ejecutar (a fecha de este artículo):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;$&lt;/span&gt; /usr/bin/ruby -e &lt;span class="s2"&gt;&amp;quot;$(curl -fsSL https://raw.github.com/gist/323731)&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;La instalación se realiza en &lt;em&gt;/usr/local&lt;/em&gt; por lo que o bien lo
ejecutamos como administrador o bien nos damos de permisos para escribir
en ese directorio.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="actualizar-el-listado-de-formulas"&gt;
&lt;h2&gt;Actualizar el listado de fórmulas&lt;/h2&gt;
&lt;p&gt;Homebrew utiliza el concepto de fórmula. Este concepto transmite la
idea de cómo se obtiene un programa, es decir, sus dependencias y sus
pasos de compilación.&lt;/p&gt;
&lt;p&gt;El listado de fórmulas se mantiene en los repositorios de &lt;a class="reference external" href="https://github.com/"&gt;GitHub&lt;/a&gt;.
Esto nos permite crear forks del proyecto, añadir nuestras fórmulas y
demás. Veamos cómo se actualiza el listado:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;$&lt;/span&gt; brew update
&lt;span class="go"&gt;remote: Counting objects: 141, done.&lt;/span&gt;
&lt;span class="go"&gt;remote: Compressing objects: 100% (63/63), done.&lt;/span&gt;
&lt;span class="go"&gt;remote: Total 122 (delta 93), reused 88 (delta 59)&lt;/span&gt;
&lt;span class="go"&gt;Receiving objects: 100% (122/122), 13.71 KiB, done.&lt;/span&gt;
&lt;span class="go"&gt;Resolving deltas: 100% (93/93), completed with 18 local objects.&lt;/span&gt;
&lt;span class="go"&gt;From http://github.com/mxcl/homebrew&lt;/span&gt;
&lt;span class="go"&gt;   f34cda7..09ad71b  master     -&amp;gt; origin/master&lt;/span&gt;
&lt;span class="go"&gt;Updated Homebrew from f34cda74 to 09ad71bd.&lt;/span&gt;
&lt;span class="go"&gt;[...]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Es la salida típica de git. A partir de ahí veremos un resumen de las
nuevas fórmulas añadidas y de las actualizadas.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="buscar-formulas"&gt;
&lt;h2&gt;Buscar fórmulas&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;brew search PATRÓN&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="mostrar-informacion-de-una-formula"&gt;
&lt;h2&gt;Mostrar información de una fórmula&lt;/h2&gt;
&lt;p&gt;Tenemos la opción clásica:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;brew info FÓRMULA&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;y la curiosa opción de visitar la página del proyecto:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;brew home FÓRMULA&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="instalacion-de-formulas"&gt;
&lt;h2&gt;Instalación de fórmulas&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;brew install FÓRMULA&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="listar-formulas-antiguas"&gt;
&lt;h2&gt;Listar fórmulas antiguas&lt;/h2&gt;
&lt;p&gt;En el proceso de actualización del listado de fórmulas se detectarán
nuevas versiones de fórmulas ya instaladas:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;brew outdated&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="actualizacion-de-paquetes"&gt;
&lt;h2&gt;Actualización de paquetes&lt;/h2&gt;
&lt;p&gt;Tenemos dos opciones&lt;/p&gt;
&lt;p&gt;&lt;em&gt;brew upgrade&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;que actualiza todos las fórmulas con nuevas versiones o&lt;/p&gt;
&lt;p&gt;&lt;em&gt;brew upgrade FÓRMULA&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;que actualiza únicamente la fórmula especificada.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="eliminacion-de-versiones-antiguas"&gt;
&lt;h2&gt;Eliminación de versiones antiguas&lt;/h2&gt;
&lt;p&gt;Una curiosidad de Homebrew es que no desinstala versiones anticuadas
para ello usamos:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;brew cleanup&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="crear-nuevas-formulas"&gt;
&lt;h2&gt;Crear nuevas fórmulas&lt;/h2&gt;
&lt;p&gt;Es bastante sencillo crear una fórmula nueva. Aunque el &lt;a class="reference external" href="https://github.com/mxcl/homebrew/wiki/Formula-Cookbook"&gt;Formula Cookbook&lt;/a&gt;
es bastante claro, veremos brevemente los paso principales:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;em&gt;brew create URL&lt;/em&gt; (con la URL de la descarga)&lt;/li&gt;
&lt;li&gt;Editar el fichero, creado automáticamente, en
&lt;em&gt;/usr/local/Library/Formula/FÓRMULA.rb&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Añadir las dependencias con depends_on.&lt;/li&gt;
&lt;li&gt;Añadir los comandos system necesarios para la compilación&lt;/li&gt;
&lt;li&gt;Probar la fórmula con &lt;em&gt;brew install -vd FÓRMULA&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="mas-informacion"&gt;
&lt;h2&gt;Más información&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="http://mxcl.github.com/homebrew/"&gt;Homebrew&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/mxcl/homebrew/wiki/FAQ"&gt;Homebrew FAQ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/mxcl/homebrew/wiki/Formula-Cookbook"&gt;Homebrew Formula Cookbook&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
</summary></entry><entry><title>NTP Sincronización de tiempos</title><link href="/unix/ntp-sincronizacion-de-tiempos/" rel="alternate"></link><updated>2011-09-15T15:15:00+02:00</updated><author><name>Carlos Hernando</name></author><id>tag:,2011-09-15:unix/ntp-sincronizacion-de-tiempos/</id><summary type="html">&lt;p&gt;Uno de los principios que debemos mantener en nuestros equipos es
mantenerlos en hora. En algunos es un requisito, por ejemplo sistemas de
autenticación dependientes del tiempo, y en otros para mantener la
cordura, por ejemplo para realizar un seguimiento de logs en distintas
máquinas.&lt;/p&gt;
&lt;p&gt;La solución es el uso de &lt;a class="reference external" href="http://en.wikipedia.org/wiki/Network_Time_Protocol"&gt;NTP&lt;/a&gt; (Network Time Protocol) que nos permite
sincronizar los relojes de nuestros equipos y mantenerlos en hora con el
paso del tiempo. Vamos a ver las configuraciones típicas usando
&lt;em&gt;ntpdate&lt;/em&gt; y el demonio &lt;em&gt;ntpd&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Pero antes un breve comentario sobre el &lt;a class="reference external" href="http://en.wikipedia.org/wiki/Network_Time_Protocol#Clock_strata"&gt;stratum&lt;/a&gt;. El número del
stratum nos indica el número de pasos a los que estamos de un reloj
atómico (nivel 0). Los relojes atómicos son muy precisos (no como los
relojes de cuarzo) pero también muy caros, por lo que es habitual que se
utilicen &lt;a class="reference external" href="http://en.wikipedia.org/wiki/GPS_clock#GPS_clocks"&gt;relojes de GPS&lt;/a&gt; como origen.&lt;/p&gt;
&lt;p&gt;Existen multitud de servidores públicos de NTP. Una iniciativa que
mantiene un pool abierto y que además realiza balanceo es
&lt;a class="reference external" href="http://www.pool.ntp.org/"&gt;NTP pool project&lt;/a&gt;. Para este artículo utilizaremos de ejemplo
&lt;em&gt;2.es.pool.ntp.org&lt;/em&gt;.&lt;/p&gt;
&lt;div class="section" id="ntpdate-para-equipos-itinerantes"&gt;
&lt;h2&gt;&lt;em&gt;ntpdate&lt;/em&gt;, para equipos itinerantes&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;ntpdate&lt;/em&gt; está pensando para utilizarse una sola vez. Lo lanzamos,
obtiene el tiempo del servidor remoto y sincroniza la hora local. Por
ejemplo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;#&lt;/span&gt; ntpdate 2.es.pool.ntp.org
&lt;span class="go"&gt;14 Sep 12:18:03 ntpdate[3359]: adjust time server 158.227.98.15 offset 0.000969 sec&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;El uso más habitual es para equipos portátiles u ordenadores que no
tienen conexión permanente a Internet. Los equipos esperan a tener
conexión y efectúan la sincronización.&lt;/p&gt;
&lt;p&gt;Un error habitual es que el socket esté en uso, normalmente por el
demonio &lt;em&gt;ntpd&lt;/em&gt;, que exige cerrar primero &lt;em&gt;ntpd&lt;/em&gt; y luego lanzar
&lt;em&gt;ntpdate&lt;/em&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;#&lt;/span&gt; ntpdate 2.es.pool.ntp.org
&lt;span class="go"&gt;14 Sep 12:21:57 ntpdate[3392]: the NTP socket is in use, exiting&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;La configuración de &lt;em&gt;ntpdate&lt;/em&gt; como servicio es muy sencilla, podemos
echar un vistazo en &lt;em&gt;/etc/default/ntpdate&lt;/em&gt; (sistemas Debian).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="demonio-ntpd-equipos-con-conexion-permanente"&gt;
&lt;h2&gt;Demonio &lt;em&gt;ntpd&lt;/em&gt;, equipos con conexión permanente&lt;/h2&gt;
&lt;p&gt;Si el equipo está conectado la gran parte del tiempo lo recomendable es
utilizar el demonio &lt;em&gt;ntpd&lt;/em&gt;. Evidentemente se encargará de mantener el
equipo sincronizado con el paso del tiempo.&lt;/p&gt;
&lt;p&gt;Existe una diferencia importante entre &lt;em&gt;ntpdate&lt;/em&gt; y &lt;em&gt;ntpd&lt;/em&gt;. &lt;em&gt;ntpd&lt;/em&gt;
realiza un ajuste sutil sobre la velocidad del reloj para compensar la
deriva natural del reloj (drift). Esto tiene una repercusión: el tiempo
es continuo (obviemos cuestiones metafísicas ;) ).&lt;/p&gt;
&lt;p&gt;Por ejemplo imaginemos que nuestro equipo está 5 minutos atrasado.
&lt;em&gt;ntpd&lt;/em&gt; intentará que el reloj “corra” hasta llegar a la sincronización
sin saltarse ni un segundo (simplemente “cuenta los segundos más
rápido”). &lt;em&gt;ntpdate&lt;/em&gt; por otro lado al ver que la diferencia es mayor a
medio segundo directamente “saltará” en el tiempo fijando la hora actual
inmediatamente.&lt;/p&gt;
&lt;p&gt;La práctica recomendada es arrancar el equipo con &lt;em&gt;ntpdate&lt;/em&gt; y dejar que
el demonio &lt;em&gt;ntpd&lt;/em&gt; se haga cargo de mantener la hora.&lt;/p&gt;
&lt;p&gt;La configuración es un poco más elaborada, ya que podemos configurarlo
como servidor de NTP. El fichero de configuración &lt;em&gt;/etc/ntp.conf&lt;/em&gt; está
muy bien comentado y básicamente cambiando las directivas &lt;em&gt;server&lt;/em&gt; por
servidores locales (o del &lt;a class="reference external" href="http://www.pool.ntp.org/"&gt;NTP pool project&lt;/a&gt;) sería suficiente.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="prestando-servicio-interno"&gt;
&lt;h2&gt;Prestando servicio interno&lt;/h2&gt;
&lt;p&gt;Si queremos sincronizar varios equipos dentro de nuestra red haremos
que un par de equipos se sincronicen con servidores externos y al mismo
tiempo presten ese servicio a nivel local. De esta forma, incluso si se
cae la conexión, todos los equipos estarán sincronizados contra la misma
fuente de referencia. Además es más educado y consume menos recursos :)&lt;/p&gt;
&lt;p&gt;Para ello simplemente es necesario revisar las directivas &lt;em&gt;restrict&lt;/em&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="broadcast"&gt;
&lt;h2&gt;Broadcast&lt;/h2&gt;
&lt;p&gt;La última modalidad es la sincronización por broadcast. En esta
configuración un servidor local de NTP emite de forma regular la hora
actual, los equipos que escuchen actualizan la hora. Es una
configuración muy típica para desplegar equipos con conexión a red como
impresoras, cámaras y dispositivos similares.&lt;/p&gt;
&lt;p&gt;Para configurar al servidor para que emita hay que hacer uso de la
directiva &lt;em&gt;broadcast&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Para hacer que un equipo escuche las emisiones la directiva es
&lt;em&gt;broadcastclient&lt;/em&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="mas-informacion"&gt;
&lt;h2&gt;Más información&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="http://www.eecis.udel.edu/~mills/ntp/html/index.html"&gt;The Network Time Protocol (NTP) Distribution&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://www.pool.ntp.org/"&gt;NTP pool project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://www.debianadmin.com/ntp-server-and-client-configuration-in-debian.html"&gt;NTP Server and Client Configuration in debian&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://wiki.debian.org/NTP"&gt;NTP Debian Wiki&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://ocubom.wordpress.com/2008/06/18/sincronizacion-del-reloj-en-windows/"&gt;ocubom - Sincronización del reloj en Windows&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
</summary></entry><entry><title>Python generators</title><link href="/python/python-generators/" rel="alternate"></link><updated>2011-09-01T15:15:00+02:00</updated><author><name>Carlos Hernando</name></author><id>tag:,2011-09-01:python/python-generators/</id><summary type="html">&lt;p&gt;Todos conocemos las listas de Python mientras que sus hermanos los
generadores son menos conocidos. Este breve artículo es un recordatorio
:-)&lt;/p&gt;
&lt;div class="section" id="el-concepto-de-generador"&gt;
&lt;h2&gt;El concepto de generador&lt;/h2&gt;
&lt;p&gt;La idea de los &lt;a class="reference external" href="http://docs.python.org/tutorial/classes.html#generators"&gt;Generators&lt;/a&gt; se basa en dos conceptos: los iteradores y
la evaluación perezosa.&lt;/p&gt;
&lt;p&gt;Python utiliza el concepto de &lt;a class="reference external" href="http://docs.python.org/tutorial/classes.html#iterators"&gt;iterador&lt;/a&gt; de una forma muy natural, por
ejemplo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;elementos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Por debajo el intérprete lo que hace es crear un iterador mediante el
método &lt;em&gt;iter()&lt;/em&gt; y avanzar posiciones con el método &lt;em&gt;next()&lt;/em&gt; del iterador
devuelto. Cualquier objeto que cubra estos métodos puede ser tratado
como un iterador y por tanto trabajar de forma transparente con el resto
de la API.&lt;/p&gt;
&lt;p&gt;El segundo concepto es la &lt;a class="reference external" href="http://en.wikipedia.org/wiki/Lazy_evaluation"&gt;evaluación perezosa&lt;/a&gt; (lazy evaluation), que
en pocas palabras significa que una expresión se resuelve únicamente
cuando se la necesita, no antes.&lt;/p&gt;
&lt;p&gt;Uniendo ambos conceptos tenemos el generador: una estructura, sobre la
que podemos iterar, cuyos componentes se van obteniendo según se va
avanzando.&lt;/p&gt;
&lt;p&gt;La diferencia fundamental con la lista es que la lista tiene una
&lt;a class="reference external" href="http://en.wikipedia.org/wiki/Eager_evaluation"&gt;evaluación ansiosa/codiciosa&lt;/a&gt; (eager evaluation). Esto implica que
cuando queremos manejar una lista de 10 elementos la lista necesita
disponer de esos 10 elementos en el momento de su declaración. Sin
embargo el generador solamente necesita saber cómo generar el siguiente
valor por lo que no necesita ni reservar espacio ni conocer a priori
ningún elemento.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="declaracion-de-un-metodo-generador"&gt;
&lt;h2&gt;Declaración de un método generador&lt;/h2&gt;
&lt;p&gt;Un método que utilice la palabra reservada &lt;em&gt;yield&lt;/em&gt; se trata como un
generador. &lt;em&gt;yield&lt;/em&gt; puede devolver un valor, como si fuera un &lt;em&gt;return&lt;/em&gt;
(cuidado, &lt;em&gt;return&lt;/em&gt; se utiliza para terminar el generador).&lt;/p&gt;
&lt;p&gt;Por ejemplo definimos la función &lt;em&gt;naturales()&lt;/em&gt; que devuelve un iterador
de todos los números naturales:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;naturales&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
        &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;naturales&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="c"&gt;# &amp;lt;generator object naturales at 0x10f1a3050&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;# 1&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;# 2&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Vemos que la primera ventaja de la evaluación perezosa es que podemos
tratar estructuras infinitas.&lt;/p&gt;
&lt;p&gt;Emulemos el método &lt;em&gt;enumerate(list)&lt;/em&gt; nativo de Python utilizando el
generador como una lista (o mejor dicho su iterador):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="n"&gt;ls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;a&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;b&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;c&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="nb"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;naturales&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;ls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;# [(1, &amp;#39;a&amp;#39;), (2, &amp;#39;b&amp;#39;), (3, &amp;#39;c&amp;#39;)]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="declaracion-de-una-expresion-generadora"&gt;
&lt;h2&gt;Declaración de una expresión generadora&lt;/h2&gt;
&lt;p&gt;Otra forma de declarar un generador es exactamente igual que una
expresión de lista pero con paréntesis. Por ejemplo los números pares:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c"&gt;# [0, 2, 4, 6, 8]&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;# &amp;lt;generator object &amp;lt;genexpr&amp;gt; at 0x10f1a3190&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;En este caso vemos que la lista está completa mientras que el generador
se queda a la espera. A partir de este ejemplo podemos definir otro
generador de números naturales pares:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;naturales&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;# 2&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;# 4&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Es importante encadenar generadores con generadores, en caso contrario
la evaluación de la lista obligaría el recorrido completo del generador.
Si la estructura fuera infinita, como el ejemplo, provocaría la muerte
del intérprete:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;naturales&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusiones"&gt;
&lt;h2&gt;Conclusiones&lt;/h2&gt;
&lt;p&gt;Evidentemente utilizar los generadores no es la solución para todos los
problemas.&lt;/p&gt;
&lt;p&gt;Hay unos escenarios concretos en los que merece la pena considerados:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Estructuras infinitas.&lt;/li&gt;
&lt;li&gt;Estructuras que ocupen mucha memoria, reducimos así el espacio de
memoria a reservar.&lt;/li&gt;
&lt;li&gt;La generación de los elementos es costosa, podemos retrasar su
cálculo hasta el último momento.&lt;/li&gt;
&lt;li&gt;Sabemos que la estructura no se va a consumir completamente, una
mezcla de las dos anteriores.&lt;/li&gt;
&lt;li&gt;Cuando trabajamos con otros generadores.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="mas-informacion"&gt;
&lt;h2&gt;Más información&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="http://docs.python.org/tutorial/classes.html#generators"&gt;Python Tutorial - Generators&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://www.python.org/dev/peps/pep-0255/"&gt;PEP 255 -- Simple Generators&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://stackoverflow.com/questions/102535/what-can-you-use-python-generator-functions-for"&gt;What can you use Python generator functions for?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://www.dabeaz.com/generators/"&gt;Generator Tricks for Systems Programmers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
</summary></entry><entry><title>Servidor de proyectos con Subversion, Trac, Apache (y LDAP)</title><link href="/desarrollo/servidor-de-proyectos-con-subversion-trac-apache-y-ldap/" rel="alternate"></link><updated>2008-07-20T13:29:00+02:00</updated><author><name>Carlos Hernando</name></author><id>tag:,2008-07-20:desarrollo/servidor-de-proyectos-con-subversion-trac-apache-y-ldap/</id><summary type="html">&lt;p&gt;En esta entrada vamos a montar un servidor para la gestión de proyectos.
Para ello utilizaremos un sistema de control de versiones
(&lt;a class="reference external" href="http://subversion.tigris.org/"&gt;Subversion&lt;/a&gt;), un sistema de gestión de incidencias (&lt;a class="reference external" href="http://trac.edgewall.org/"&gt;Trac&lt;/a&gt;) y un
sistema de autenticación compartido, para ello utilizaremos &lt;a class="reference external" href="http://httpd.apache.org/"&gt;Apache&lt;/a&gt; y
alguno de sus métodos de autenticación como por ejemplo &lt;a class="reference external" href="http://es.wikipedia.org/wiki/LDAP"&gt;LDAP&lt;/a&gt; (válido
si es necesario utilizar las cuentas de un &lt;a class="reference external" href="http://es.wikipedia.org/wiki/Directorio_activo"&gt;Directorio Activo&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Aunque existen soluciones más elaboradas y más integradas, como el
software de &lt;a class="reference external" href="http://sourceforge.net/"&gt;SourceForge.net&lt;/a&gt;, este planteamiento permite montar un
servidor a medida, pudiendo alterar cualquiera de sus elementos, y en mi
opinión más sencillo de mantener.&lt;/p&gt;
&lt;p&gt;Nos basaremos en una instalación mínima de Ubuntu Server 8.04 (por lo
que no disponemos ni de Subversion 1.5 ni de Trac 0.11) con la idea de
montar un servidor preparado para mantener varios proyectos.&lt;/p&gt;
&lt;div class="section" id="instalacion-y-configuracion-de-subversion"&gt;
&lt;h2&gt;Instalación y configuración de Subversion&lt;/h2&gt;
&lt;p&gt;Para empezar instalaremos subversion, también es recomendable
subversion-tools por los scripts adicionales que incorpora, y preparamos
un repositorio de prueba:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;#&lt;/span&gt; apt-get install subversion subversion-tools
&lt;span class="gp"&gt;#&lt;/span&gt; mkdir /srv/svn
&lt;span class="gp"&gt;#&lt;/span&gt; svnadmin create /srv/svn/proyecto
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Aprovechamos ahora para crear una estructura básica dentro del
repositorio, esto nos servirá en las pruebas para ver si realmente
podemos acceder al repositorio:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;#&lt;/span&gt; svn co file:///srv/svn/proyecto
&lt;span class="gp"&gt;#&lt;/span&gt; svn mkdir proyecto/&lt;span class="o"&gt;{&lt;/span&gt;branches,tags,trunk&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="gp"&gt;#&lt;/span&gt; svn ci -m &lt;span class="s1"&gt;&amp;#39;Estructura Inicial&amp;#39;&lt;/span&gt; proyecto
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="instalacion-y-configuracion-de-trac"&gt;
&lt;h2&gt;Instalación y configuración de Trac&lt;/h2&gt;
&lt;p&gt;Instalaremos y configuraremos mínimamente un proyecto de Trac para el
repositorio que acabamos de crear:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;#&lt;/span&gt; apt-get install trac
&lt;span class="gp"&gt;#&lt;/span&gt; mkdir /srv/trac
&lt;span class="gp"&gt;#&lt;/span&gt; trac-admin /srv/trac/proyecto initenv
&lt;span class="go"&gt;(Opciones sugeridas)&lt;/span&gt;
&lt;span class="go"&gt;Path to repository [/path/to/repos]&amp;gt; /srv/svn/proyecto&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Es el momento de comprobar que trac y su unión con el repositorio de
subversion funcionan correctamente, para ello lanzaremos el servidor
incluido en trac:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;#&lt;/span&gt; tracd -p 80 /srv/trac/proyecto
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Abriendo la URL &lt;a class="reference external" href="http://localhost:80/"&gt;http://localhost:80/&lt;/a&gt; deberíamos ver disponible nuestro
proyecto, &amp;quot;&lt;em&gt;My Project&lt;/em&gt;&amp;quot;, y comprobamos que la función de &amp;quot;&lt;em&gt;Browse
Source&lt;/em&gt;&amp;quot; funciona correctamente.&lt;/p&gt;
&lt;p&gt;Por el momento nada nuevo, paremos tracd y sigamos.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="instalacion-y-configuracion-de-apache"&gt;
&lt;h2&gt;Instalación y configuración de Apache&lt;/h2&gt;
&lt;p&gt;Optamos por enganchar Trac con &lt;a class="reference external" href="http://www.modpython.org/"&gt;mod_python&lt;/a&gt; así que lo más sencillo
es instalar el paquete de mod_python y que instale apache por sus
dependencias:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;#&lt;/span&gt; apt-get install libapache2-mod-python
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Bien, ahora editamos la configuración para que Apache pase las
peticiones que vayan a &lt;strong&gt;/trac&lt;/strong&gt; a nuestro conjunto de proyectos en
&lt;strong&gt;/srv/trac&lt;/strong&gt;. Editando el fichero
&lt;strong&gt;/etc/apache2/sites-available/default&lt;/strong&gt; añadimos antes del cierre de
&lt;em&gt;&amp;lt;/VirtualHost&amp;gt;&lt;/em&gt; lo siguiente:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="nt"&gt;&amp;lt;Location&lt;/span&gt; &lt;span class="s"&gt;/trac&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nb"&gt;SetHandler&lt;/span&gt; mod_python
 &lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nb"&gt;PythonInterpreter&lt;/span&gt; main_interpreter
  &lt;span class="nb"&gt;PythonHandler&lt;/span&gt; trac.web.modpython_frontend
  &lt;span class="nb"&gt;PythonOption&lt;/span&gt; TracEnvParentDir &lt;span class="sx"&gt;/srv/trac&lt;/span&gt;
  &lt;span class="nb"&gt;PythonOption&lt;/span&gt; TracUriRoot &lt;span class="sx"&gt;/trac&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Location&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Forzamos la recarga de la configuración de Apache:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;#&lt;/span&gt; /etc/init.d/apache2 reload
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Hacemos una prueba con el navegador en &lt;a class="reference external" href="http://localhost/trac/"&gt;http://localhost/trac/&lt;/a&gt; que
debería mostrarnos un error por falta de permisos de escritura. Como
vamos a dejar a Apache como gestor de los proyectos es necesario darle
los permisos que necesita:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;#&lt;/span&gt; chown -R www-data.www-data /srv/trac/proyecto
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Con esto todo debería funcionar exactamente igual que con la prueba
realizada con tracd. Vamos ahora a mostrar el repositorio desde Apache.&lt;/p&gt;
&lt;p&gt;Subversion trabaja con Apache haciendo uso de &lt;a class="reference external" href="http://es.wikipedia.org/wiki/WebDAV"&gt;WebDAV&lt;/a&gt; así que
instalamos el módulo necesario:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;#&lt;/span&gt; apt-get install libapache2-svn
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Añadimos la configuración necesaria en el fichero
&lt;strong&gt;/etc/apache2/mods-available/dav_svn.conf&lt;/strong&gt;, podéis descomentar las
opciones si os resulta más cómodo. En cualquier caso la configuración
debe quedar de la siguiente manera:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="nt"&gt;&amp;lt;Location&lt;/span&gt; &lt;span class="s"&gt;/svn&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nb"&gt;DAV&lt;/span&gt; svn
  &lt;span class="nb"&gt;SVNParentPath&lt;/span&gt; &lt;span class="sx"&gt;/srv/svn&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Location&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;De nuevo, forzamos la recarga de la configuración de Apache y
comprobamos que &lt;a class="reference external" href="http://localhost/svn/proyecto"&gt;http://localhost/svn/proyecto&lt;/a&gt; muestra el proyecto y que
podemos navegar dentro de él. Si probáis &lt;a class="reference external" href="http://localhost/svn/"&gt;http://localhost/svn/&lt;/a&gt; os dará
un error, ya que en este caso no existe un listado de proyectos
disponibles como hacía Trac.&lt;/p&gt;
&lt;p&gt;Igualmente que en Trac, si Apache es el gestor del repositorio es
necesario que tenga permisos de escritura. En este caso vamos a ceder
completamente el control a Apache:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;#&lt;/span&gt; chown -R www-data.www-data /srv/svn/proyecto
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Ahora mismo disponemos de un sistema completamente funcional en el que
no se exige ningún tipo de autenticación. En el caso de Trac no se puede
hacer login y en el caso de Subversion ni siquiera se pide. Si queréis
verlo en podéis hacer la siguiente prueba:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;#&lt;/span&gt; svn co http://localhost/svn/proyecto/trunk
&lt;span class="gp"&gt;#&lt;/span&gt; touch trunk/README.txt
&lt;span class="gp"&gt;#&lt;/span&gt; svn add trunk/README.txt
&lt;span class="gp"&gt;#&lt;/span&gt; svn ci -m &lt;span class="s2"&gt;&amp;quot;Fichero leame&amp;quot;&lt;/span&gt; trunk
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Si comprobamos el historial, &lt;em&gt;svn log trunk/README.txt&lt;/em&gt;, podremos ver
que no hay ningún usuario responsable del commit. En ningún momento se
nos ha pedido identificarnos, ya que hay permisos de lectura y escritura
para todo el mundo, así que podemos bajarnos el contenido del
repositorio y los commit son anónimos.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="autenticando-usuarios"&gt;
&lt;h2&gt;Autenticando usuarios&lt;/h2&gt;
&lt;p&gt;Empecemos con lo más sencillo, usuarios válidos de un fichero htpasswd,
podéis leer algo más en otro de mis artículos sobre &lt;a class="reference external" href="http://chernando.eu/doc/apache/"&gt;ficheros
.htpasswd&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;#&lt;/span&gt; htpasswd -c /etc/apache2/users.conf chernando
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Editamos Trac para soportar un login centralizado añadiendo un nuevo
&lt;em&gt;location&lt;/em&gt; a &lt;em&gt;default&lt;/em&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="nt"&gt;&amp;lt;Location&lt;/span&gt; &lt;span class="s"&gt;/trac/*/login&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nb"&gt;AuthType&lt;/span&gt; Basic
  &lt;span class="nb"&gt;AuthName&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Trac Projects&amp;quot;&lt;/span&gt;
  &lt;span class="nb"&gt;AuthUserFile&lt;/span&gt; &lt;span class="sx"&gt;/etc/apache2/users.conf&lt;/span&gt;
  &lt;span class="nb"&gt;Require&lt;/span&gt; valid-user
&lt;span class="nt"&gt;&amp;lt;/Location&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Forzando la recarga de Apache ya disponemos de la función &amp;quot;login&amp;quot; en
Trac. Para el repositorio vamos a dejar el acceso de lectura para todo
el mundo y limitar el acceso de escritura a los usuarios registrados
añadiendo a la configuración de WebDAV:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="nb"&gt;AuthType&lt;/span&gt; Basic AuthName &lt;span class="s2"&gt;&amp;quot;Subversion Repository&amp;quot;&lt;/span&gt;
&lt;span class="nb"&gt;AuthUserFile&lt;/span&gt; &lt;span class="sx"&gt;/etc/apache2/users.conf&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;LimitExcept&lt;/span&gt; &lt;span class="s"&gt;GET PROPFIND OPTIONS REPORT&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nb"&gt;Require&lt;/span&gt; valid-user
&lt;span class="nt"&gt;&amp;lt;/LimitExcept&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Una vez más recargando Apache ahora podemos bajar y actualizar un
repositorio pero necesitaremos identificarnos para subir cambios al
repositorio. Probad a añadir un nuevo fichero y comprobaréis que ahora
se exige un usuario y password válidos.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="rizando-el-rizo-autenticando-contra-un-ldap"&gt;
&lt;h2&gt;Rizando el rizo, autenticando contra un LDAP&lt;/h2&gt;
&lt;p&gt;En el caso de disponer de un sistema de autenticación centralizada, por
ejemplo LDAP o un Directorio Activo con el servicio LDAP activo, podemos
delegar toda la carga de la gestión de usuarios dejando nuestro servidor
de proyectos completamente &amp;quot;inhabitado&amp;quot;.&lt;/p&gt;
&lt;p&gt;Para ello lo único que necesitamos es cambiar ambas configuraciones. En
primer lugar habilitamos los módulos necesarios:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;#&lt;/span&gt; a2enmod authnz_ldap
&lt;span class="go"&gt;(esto debería habilitar el módulo ldap por dependencias)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Y configuramos ambas secciones de autenticación. Primero eliminamos
&lt;em&gt;AuthUserFile&lt;/em&gt; que ya no es necesaria y después añadimos:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="nb"&gt;AuthBasicProvider&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;ldap&amp;quot;&lt;/span&gt;
&lt;span class="nb"&gt;AuthLDAPURL&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;ldap://127.0.0.1/dc=chernando,dc=eu?uid?sub?(objectClass=inetOrgPerson)&amp;quot;&lt;/span&gt;
&lt;span class="nb"&gt;authzldapauthoritative&lt;/span&gt; &lt;span class="k"&gt;Off&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Podéis ver más detalles en
&lt;a class="reference external" href="http://trac.edgewall.org/wiki/TracModPython"&gt;http://trac.edgewall.org/wiki/TracModPython&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="ampliaciones-que-pueden-hacerse-a-partir-de-aqui"&gt;
&lt;h2&gt;Ampliaciones que pueden hacerse a partir de aquí&lt;/h2&gt;
&lt;p&gt;En esta entrada he intentado introducir el menor ruido posible, tanto
en comandos como software a instalar, por lo que hay ciertas mejoras que
se han quedado en el tintero. Por ejemplo:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Configurar Apache para hacer uso de SSL, muy necesario ya que hasta
el momento todas las negociaciones con Apache van en texto claro.&lt;/li&gt;
&lt;li&gt;Establecer limitaciones en el acceso de los repositorios (y en
secciones de los mismos) haciendo uso de authz.&lt;/li&gt;
&lt;li&gt;Configurar un sistema de correo, que permita notificar todo tipo de
eventos: nuevos tickets, cambios en el repositorio...&lt;/li&gt;
&lt;li&gt;Integrar Subversion con Trac, por ejemplo permitir que un commit
cierre o añada información a un ticket de Trac.&lt;/li&gt;
&lt;li&gt;Utilizar la última versión de Subversion, 1.5, por su mejora en la
gestión de merge de ramas.&lt;/li&gt;
&lt;li&gt;Utilizar la última versión de Trac, 0.11, por las mejoras en el
interfaz y en la gestión del flujo de trabajo asociado a un ticket.&lt;/li&gt;
&lt;li&gt;Ampliar el sistema incluyendo otros servicios: listas de correo,
servidor de integración continua...&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
</summary></entry><entry><title>Apache Ant, primeros pasos creando un build.xml</title><link href="/desarrollo/apache-ant-primeros-pasos-creando-un-buildxml/" rel="alternate"></link><updated>2008-03-02T18:34:00+01:00</updated><author><name>Carlos Hernando</name></author><id>tag:,2008-03-02:desarrollo/apache-ant-primeros-pasos-creando-un-buildxml/</id><summary type="html">&lt;p&gt;Uno de los inconvenientes en la construcción de software con más de dos
ficheros (cualquier cosa que se salga de un &amp;quot;Hello world!&amp;quot;) es el
proceso de compilación: orden de compilación de ficheros, dependencias,
rutas de librerías internas, etc... En esta entrada vamos a dar los
primeros pasos en una de las soluciones más comunes para proyectos Java:
&lt;a class="reference external" href="http://ant.apache.org/"&gt;Apache Ant&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Para los impacientes: al final de la entrada encontraréis el build.xml
con todo lo explicado que seguramente os sirva tal cuál.&lt;/p&gt;
&lt;p&gt;En primer lugar creamos un fichero build.xml en el raíz de nuestro
proyecto y definimos su nombre:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="nt"&gt;&amp;lt;project&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Proyecto&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/project&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Ant, al igual que otras herramientas de construcción, se basa en el
concepto de objetivos o &lt;em&gt;targets&lt;/em&gt; cuya definición engloba tanto las
dependencias previas como los pasos a seguir para conseguirlo.&lt;/p&gt;
&lt;p&gt;Vamos a comenzar definiendo un objetivo de preparación llamado &lt;em&gt;init&lt;/em&gt;
que será el encargado de crear un directorio &lt;em&gt;classes&lt;/em&gt; donde guardaremos
los ficheros &lt;em&gt;.class&lt;/em&gt; resultantes de la compilación y el directorio
&lt;em&gt;build&lt;/em&gt; para el .*jar* final. Para ello basta incluir dentro de
&amp;lt;project&amp;gt; las siguientes líneas:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="nt"&gt;&amp;lt;target&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;init&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;mkdir&lt;/span&gt; &lt;span class="na"&gt;dir=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;classes&amp;quot;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;mkdir&lt;/span&gt; &lt;span class="na"&gt;dir=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;build&amp;quot;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/target&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Como podemos ver los objetivos se delimitan con etiquetas &lt;strong&gt;&amp;lt;target&amp;gt;&lt;/strong&gt;
y un nombre. Dentro de ellos se enumeran los pasos que se han de seguir
para alcanzar el objetivo, en este caso ha de crear directorios.&lt;/p&gt;
&lt;p&gt;Si queremos alcanzar el objetivo &lt;em&gt;init&lt;/em&gt; basta con realizar:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;$&lt;/span&gt; ant init
&lt;span class="go"&gt;Buildfile: build.xml&lt;/span&gt;

&lt;span class="go"&gt;init:&lt;/span&gt;
&lt;span class="go"&gt;    [mkdir] Created dir: /home/chernando/proyecto/classes&lt;/span&gt;
&lt;span class="go"&gt;    [mkdir] Created dir: /home/chernando/proyecto/build&lt;/span&gt;

&lt;span class="go"&gt;BUILD SUCCESSFUL&lt;/span&gt;
&lt;span class="go"&gt;Total time: 0 seconds&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Es hora de compilar nuestro proyecto, vamos a definir el objetivo
&lt;em&gt;compile&lt;/em&gt;. Ahora bien, la compilación depende de la creación del
directorio &lt;em&gt;classes&lt;/em&gt; que se realiza objetivo anterior. Con esto en
cuenta basta con incluir:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="nt"&gt;&amp;lt;target&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;compile&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;depends=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;init&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;javac&lt;/span&gt; &lt;span class="na"&gt;srcdir=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;src&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;destdir=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;classes&amp;quot;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/target&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;La dependencia se fija en la declaración del &lt;em&gt;target&lt;/em&gt; de tal manera que
se garantiza su cumplimiento antes de comenzarla. Nuestro código está en
el directorio &lt;em&gt;src&lt;/em&gt; y el resultado de la compilación se lleva al
directorio &lt;em&gt;classes&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Importante notar que esta vez estamos usando &lt;strong&gt;&amp;lt;javac&amp;gt;&lt;/strong&gt; esto es lo que
Ant se denomina tarea. Hay muchas tareas predefinidas, consultad el
&lt;a class="reference external" href="http://ant.apache.org/manual/index.html"&gt;manual de ant&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Con nuestro proyecto compilado vamos a generar el .*jar* que
distribuiremos haciendo uso de un nuevo objetivo llamado &lt;em&gt;build&lt;/em&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="nt"&gt;&amp;lt;target&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;build&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;depends=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;compile&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;jar&lt;/span&gt; &lt;span class="na"&gt;destfile=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;build/proyecto.jar&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;basedir=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;classes&amp;quot;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/target&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;En este caso dependemos de los frutos de &lt;em&gt;compile&lt;/em&gt; y utilizamos la
tarea &lt;em&gt;jar&lt;/em&gt; que se encarga de empaquetar todo el contenido del
directorio &lt;em&gt;classes&lt;/em&gt; en el fichero &lt;em&gt;proyecto.jar&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Finalmente incluiremos un nuevo objetivo para limpiar todo el entorno,
el objetivo &lt;em&gt;clean&lt;/em&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="nt"&gt;&amp;lt;target&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;clean&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;delete&lt;/span&gt; &lt;span class="na"&gt;dir=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;classes&amp;quot;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;delete&lt;/span&gt; &lt;span class="na"&gt;dir=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;build&amp;quot;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/target&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;A estas alturas es fácil entender que lo único que realiza es eliminar
los directorios de trabajo dejando el entorno limpio del proceso de
compilación.&lt;/p&gt;
&lt;p&gt;Resumiendo nuestro fichero build.xml es:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt; &lt;span class="nt"&gt;&amp;lt;project&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Proyecto&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;target&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;init&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;mkdir&lt;/span&gt; &lt;span class="na"&gt;dir=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;classes&amp;quot;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;mkdir&lt;/span&gt; &lt;span class="na"&gt;dir=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;build&amp;quot;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/target&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;target&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;compile&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;depends=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;init&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;javac&lt;/span&gt; &lt;span class="na"&gt;srcdir=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;src&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;destdir=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;classes&amp;quot;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/target&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;target&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;build&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;depends=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;compile&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;jar&lt;/span&gt; &lt;span class="na"&gt;destfile=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;build/proyecto.jar&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;basedir=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;classes&amp;quot;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/target&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;target&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;clean&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;delete&lt;/span&gt; &lt;span class="na"&gt;dir=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;classes&amp;quot;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;delete&lt;/span&gt; &lt;span class="na"&gt;dir=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;build&amp;quot;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/target&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/project&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Y hasta aquí este minitutorial sobre el uso de ant :-)&lt;/p&gt;
</summary></entry><entry><title>Cifrando una partición con cryptsetup</title><link href="/seguridad/cifrando-una-particion-con-cryptsetup/" rel="alternate"></link><updated>2007-09-17T18:50:00+02:00</updated><author><name>Carlos Hernando</name></author><id>tag:,2007-09-17:seguridad/cifrando-una-particion-con-cryptsetup/</id><summary type="html">&lt;p&gt;Disponemos de un ordenador portátil (en este caso concreto una Kubuntu
Gutsy) con una partición destinada a &lt;em&gt;/home&lt;/em&gt; que queremos cifrar.&lt;/p&gt;
&lt;p&gt;Esta solución no es de las más seguras, quedando por tratar temas
importantes como el cifrado de swap, controlar la integridad del sistema
y el correcto manejo de los ficheros temporales.&lt;/p&gt;
&lt;p&gt;Los pasos son los siguientes:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Ciframos la partición física con &lt;a class="reference external" href="http://www.saout.de/misc/dm-crypt/"&gt;crypsetup&lt;/a&gt; y &lt;a class="reference external" href="http://luks.endorphin.org/"&gt;LUKS&lt;/a&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;#&lt;/span&gt; cryptsetup luksFormat /dev/sda6
&lt;/pre&gt;&lt;/div&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Mapeamos lógicamente el dispositivo descifrado:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;#&lt;/span&gt; cryptsetup luksOpen /dev/sda6 home
&lt;/pre&gt;&lt;/div&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Trabajamos como si fuera una partición normal:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;#&lt;/span&gt; mkfs.ext3 /dev/mapper/home
&lt;span class="gp"&gt;#&lt;/span&gt; mount -t ext3 /dev/mapper/home /home
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Para realizar el montaje automático de la partición en el arranque es
necesario modificar los ficheros &lt;em&gt;/etc/crypttab&lt;/em&gt; y &lt;em&gt;/etc/fstab&lt;/em&gt;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;crypttab:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;# &amp;lt;target name&amp;gt; &amp;lt;source device&amp;gt;         &amp;lt;key file&amp;gt;      &amp;lt;options&amp;gt;

home    /dev/sda6       none    luks
&lt;/pre&gt;&lt;/div&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;fstab:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;/dev/mapper/home        /home   ext3    defaults        0       2
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Con la configuración mostrada se pedirá la contraseña en el momento del
arranque.&lt;/p&gt;
&lt;p&gt;Podéis encontrar ejemplos más interesantes en la documentación de LUKS
o simplemente buscando por Google.&lt;/p&gt;
</summary></entry><entry><title>Trac, gestión de proyectos</title><link href="/desarrollo/trac-gestion-de-proyectos/" rel="alternate"></link><updated>2007-07-10T19:38:00+02:00</updated><author><name>Carlos Hernando</name></author><id>tag:,2007-07-10:desarrollo/trac-gestion-de-proyectos/</id><summary type="html">&lt;p&gt;&lt;a class="reference external" href="http://trac.edgewall.org/"&gt;Trac&lt;/a&gt; es una herramienta de gestión de proyectos, principalmente de
software, que unifica un sistema wiki con un sistema de seguimiento
(issue tracking) con claras ventajas a la hora de trabajar con un
repositorio de subversion.&lt;/p&gt;
&lt;p&gt;Esta entrada constituye una breve guía de iniciación a Trac.&lt;/p&gt;
&lt;div class="section" id="instalacion"&gt;
&lt;h2&gt;Instalación&lt;/h2&gt;
&lt;p&gt;Una vez instalado el programa (utilizando el gestor de paquetes según
la distribución utilizada) disponemos de varios métodos para ponerlo en
funcionamiento: como un CGI, con Apache y mod_python o con el servidor
que viene incluido. Para esta guía utilizaré el servidor incluido.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="creacion-de-un-nuevo-entorno"&gt;
&lt;h2&gt;Creación de un nuevo entorno&lt;/h2&gt;
&lt;p&gt;Haciendo uso de trac-admin podemos crear un nuevo entorno:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;$&lt;/span&gt; trac-admin /srv/trac/proyecto initenv

&lt;span class="go"&gt;Creating a new Trac environment at /home/chernando/trac/proyecto&lt;/span&gt;

&lt;span class="go"&gt;Trac will first ask a few questions about your environment&lt;/span&gt;
&lt;span class="go"&gt;in order to initalize and prepare the project database.&lt;/span&gt;

&lt;span class="go"&gt;Please enter the name of your project.&lt;/span&gt;
&lt;span class="go"&gt;This name will be used in page titles and descriptions.&lt;/span&gt;

&lt;span class="go"&gt;Project Name [My Project]&amp;gt; Proyecto&lt;/span&gt;

&lt;span class="go"&gt;Please specify the connection string for the database to use.&lt;/span&gt;
&lt;span class="go"&gt;By default, a local SQLite database is created in the environment&lt;/span&gt;
&lt;span class="go"&gt;directory. It is also possible to use an already existing&lt;/span&gt;
&lt;span class="go"&gt;PostgreSQL database (check the Trac documentation for the exact&lt;/span&gt;
&lt;span class="go"&gt;connection string syntax).&lt;/span&gt;

&lt;span class="go"&gt;Database connection string [sqlite:db/trac.db]&amp;gt;&lt;/span&gt;

&lt;span class="go"&gt;Please specify the type of version control system,&lt;/span&gt;
&lt;span class="go"&gt;By default, it will be svn.&lt;/span&gt;

&lt;span class="go"&gt;If you don&amp;#39;t want to use Trac with version control integration,&lt;/span&gt;
&lt;span class="go"&gt;choose the default here and don&amp;#39;t specify a repository directory.&lt;/span&gt;
&lt;span class="go"&gt;in the next question.&lt;/span&gt;

&lt;span class="go"&gt;Repository type [svn]&amp;gt;&lt;/span&gt;

&lt;span class="go"&gt;Please specify the absolute path to the version control&lt;/span&gt;
&lt;span class="go"&gt;repository, or leave it blank to use Trac without a repository.&lt;/span&gt;
&lt;span class="go"&gt;You can also set the repository location later.&lt;/span&gt;

&lt;span class="go"&gt;Path to repository [/path/to/repos]&amp;gt; /srv/svn/proyecto&lt;/span&gt;

&lt;span class="go"&gt;Please enter location of Trac page templates.&lt;/span&gt;
&lt;span class="go"&gt;Default is the location of the site-wide templates installed with Trac.&lt;/span&gt;

&lt;span class="go"&gt;Templates directory [/usr/share/trac/templates]&amp;gt;&lt;/span&gt;
&lt;span class="go"&gt;...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Con esto tendremos una mínima configuración funcional de Trac para
nuestro proyecto.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="comprobacion-rapida-con-el-servidor-incluido"&gt;
&lt;h2&gt;Comprobación rápida con el servidor incluido&lt;/h2&gt;
&lt;p&gt;Para comprobar el aspecto que van teniendo las cosas utilizaremos el
servidor incluido en el puerto 8000:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;$&lt;/span&gt; tracd -p 8000 /srv/trac/proyecto
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Abriendo un navegador en &lt;a class="reference external" href="http://localhost:8000/"&gt;http://localhost:8000/&lt;/a&gt; podremos contemplar el
entorno por defecto. Aprovechando el objetivo didáctico de este ejemplo
os animo a que juguéis con la wiki y sistema de tickets.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="personalizacion-del-entorno"&gt;
&lt;h2&gt;Personalización del entorno&lt;/h2&gt;
&lt;p&gt;En el fichero proyecto/conf/trac.ini podréis encontrar cosas como
[header_logo] que os permitirá cambiar el logotipo o como [project]
para fijar el nombre, URLs relacionadas y demás.&lt;/p&gt;
&lt;p&gt;Además en el directorio proyecto/templates podéis añadir vuestras
propias plantillas que tendrán preferencia sobre las del sistema.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="ajustando-detalles-sobre-nuestro-proyecto-componentes-e-hitos"&gt;
&lt;h2&gt;Ajustando detalles sobre nuestro proyecto: componentes e hitos&lt;/h2&gt;
&lt;p&gt;trac-admin crea una serie de componentes y de hitos genéricos que con
toda seguridad no nos servirán para nuestro proyecto. Para gestionar
estos detalles utilizaremos trac-admin de nuevo.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;$&lt;/span&gt; trac-admin /srv/trac/proyecto
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Con esto tendremos una shell interactiva con la que podremos ajustar
los detalles que necesitemos. Por ejemplo para cambiar los componentes
podemos realizar:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt; component list

&lt;span class="go"&gt;Name        Owner&lt;/span&gt;
&lt;span class="go"&gt;--------------------&lt;/span&gt;
&lt;span class="go"&gt;component1  somebody&lt;/span&gt;
&lt;span class="go"&gt;component2  somebody&lt;/span&gt;

&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt; component rename component1 interfaz
&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt; component chown interfaz ana
&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt; component rename component2 datos
&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt; component chown datos beatriz
&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt; component add nucleo
&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt; component add nucleo carlos
&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt; component list

&lt;span class="go"&gt;Name      Owner&lt;/span&gt;
&lt;span class="go"&gt;-----------------&lt;/span&gt;
&lt;span class="go"&gt;datos     beatriz&lt;/span&gt;
&lt;span class="go"&gt;interfaz  ana&lt;/span&gt;
&lt;span class="go"&gt;nucleo    carlos&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Con esto ya tenemos creados 3 componentes (ajustando el responsable de
cada componente) que quedarán reflejados en el sistema de tickets.
Veamos ahora modificación de los hitos:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt; milestone list

&lt;span class="go"&gt;Name        Due  Completed&lt;/span&gt;
&lt;span class="go"&gt;--------------------------&lt;/span&gt;
&lt;span class="go"&gt;milestone1&lt;/span&gt;
&lt;span class="go"&gt;milestone2&lt;/span&gt;
&lt;span class="go"&gt;milestone3&lt;/span&gt;
&lt;span class="go"&gt;milestone4&lt;/span&gt;

&lt;span class="go"&gt;Trac [/tmp/trac/proyecto]&amp;gt; milestone rename milestone1 0.1&lt;/span&gt;
&lt;span class="go"&gt;Trac [/tmp/trac/proyecto]&amp;gt; milestone rename milestone2 0.2&lt;/span&gt;
&lt;span class="go"&gt;Trac [/tmp/trac/proyecto]&amp;gt; milestone rename milestone3 1.0&lt;/span&gt;
&lt;span class="go"&gt;Trac [/tmp/trac/proyecto]&amp;gt; milestone rename milestone4 2.0&lt;/span&gt;
&lt;span class="go"&gt;Trac [/tmp/trac/proyecto]&amp;gt; milestone list&lt;/span&gt;

&lt;span class="go"&gt;Name  Due  Completed&lt;/span&gt;
&lt;span class="go"&gt;--------------------&lt;/span&gt;
&lt;span class="go"&gt;0.1&lt;/span&gt;
&lt;span class="go"&gt;0.2&lt;/span&gt;
&lt;span class="go"&gt;1.0&lt;/span&gt;
&lt;span class="go"&gt;2.0&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Ajustemos las fechas de entrega de cada hito:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="go"&gt;Trac [/tmp/trac/proyecto]&amp;gt; milestone due 0.1 2007-07-01&lt;/span&gt;
&lt;span class="go"&gt;Trac [/tmp/trac/proyecto]&amp;gt; milestone due 0.2 2007-09-01&lt;/span&gt;
&lt;span class="go"&gt;Trac [/tmp/trac/proyecto]&amp;gt; milestone due 1.0 2008-01-01&lt;/span&gt;
&lt;span class="go"&gt;Trac [/tmp/trac/proyecto]&amp;gt; milestone due 2.0 2009-01-01&lt;/span&gt;
&lt;span class="go"&gt;Trac [/tmp/trac/proyecto]&amp;gt; milestone list&lt;/span&gt;

&lt;span class="go"&gt;Name  Due         Completed&lt;/span&gt;
&lt;span class="go"&gt;---------------------------&lt;/span&gt;
&lt;span class="go"&gt;0.1   2007-07-01&lt;/span&gt;
&lt;span class="go"&gt;0.2   2007-09-01&lt;/span&gt;
&lt;span class="go"&gt;1.0   2008-01-01&lt;/span&gt;
&lt;span class="go"&gt;2.0   2009-01-01&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Una vez completado un hito podremos utilizar el comando milestone
completed:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt; milestone completed 0.1 now
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Podéis curiosear más opciones haciendo uso del comando help dentro de
la shell.&lt;/p&gt;
&lt;/div&gt;
</summary></entry><entry><title>CVS: Manejando ramas</title><link href="/desarrollo/cvs-manejando-ramas/" rel="alternate"></link><updated>2007-03-07T19:32:00+01:00</updated><author><name>Carlos Hernando</name></author><id>tag:,2007-03-07:desarrollo/cvs-manejando-ramas/</id><summary type="html">&lt;p&gt;Hace algunos años publiqué una breve &lt;a class="reference external" href="http://chernando.eu/doc/cvs/"&gt;guía práctica de CVS&lt;/a&gt;,
incompleta. Aprovechando que tengo algo de tiempo he decidido tachar
este punto de mi lista de tareas :-)&lt;/p&gt;
&lt;div class="section" id="creando-una-nueva-rama"&gt;
&lt;h2&gt;Creando una nueva rama&lt;/h2&gt;
&lt;p&gt;Antes de nada hemos de recordar que necesitamos una versión etiquetada,
por ejemplo 'v1_2', sobre la que vamos a trabajar. Comenzamos
extrayendo la versión a nuestro entorno de trabajo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;$&lt;/span&gt; cvs checkout -d proyecto-1.2 -r v1_2 proyecto&lt;span class="sb"&gt;``&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Procedamos a crear la rama propiamente dicha:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;proyecto-1.2
&lt;span class="gp"&gt;$&lt;/span&gt; cvs tag -b v1_2-branch&lt;span class="sb"&gt;``&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;El nombre de la rama es un gusto personal, yo prefiero añadir &lt;em&gt;-branch&lt;/em&gt;
aunque he visto &lt;em&gt;-bugfixes&lt;/em&gt; o combinaciones de ambos.&lt;/p&gt;
&lt;p&gt;Con estos simples pasos ya disponemos de una rama de desarrollo a la
que podemos acceder, por ejemplo para hacer un cambio a la rama:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="nb"&gt;pwd&lt;/span&gt;  /home/chernando/proyecto
&lt;span class="gp"&gt;$&lt;/span&gt; cvs up -r v1_2-branch&lt;span class="sb"&gt;``&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Si revisamos el estado de 'main.c' podemos ver que está incluido en una
rama:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;$&lt;/span&gt; cvs -q status main.c
&lt;span class="go"&gt;==================================================================&lt;/span&gt;
&lt;span class="go"&gt;Working revision:    1.1.2.1 2007-03-07 19:06:39 +0100&lt;/span&gt;
&lt;span class="go"&gt;Repository revision: 1.1.2.1 /tmp/repositorio/proyecto/main.c,v&lt;/span&gt;
&lt;span class="go"&gt;Commit Identifier:   QwJwh9fAP56fxb9s&lt;/span&gt;
&lt;span class="go"&gt;Sticky Tag:          v1_2-branch (branch: 1.1.2)&lt;/span&gt;
&lt;span class="go"&gt;Sticky Date:         (none)&lt;/span&gt;
&lt;span class="go"&gt;Sticky Options:      (none)``&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Importante fijarse en el cambio en la numeración, a partir de ahora en
vez de avanzar con 1.*X*, se avanza con 1.*N*.*X*, siendo &lt;em&gt;N&lt;/em&gt; la
numeración de la rama.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="mezclando-dos-ramas"&gt;
&lt;h2&gt;Mezclando dos ramas&lt;/h2&gt;
&lt;p&gt;Bien, supongamos que queremos traernos cambios realizados desde una
rama,'v1_2-branch', a la principal. Muy fácil, haremos como si
forzáramos un cambio de una versión anterior pero especificando la rama
en vez de la revisión:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;$&lt;/span&gt; cvs update -j v1_2-branch main.c
&lt;span class="go"&gt;RCS file: /tmp/repositorio/proyecto/main.c,v&lt;/span&gt;
&lt;span class="go"&gt;retrieving revision 1.1&lt;/span&gt;
&lt;span class="go"&gt;retrieving revision 1.1.2.1&lt;/span&gt;
&lt;span class="go"&gt;Merging differences between 1.1 and 1.1.2.1 into main.c&lt;/span&gt;
&lt;span class="gp"&gt;$&lt;/span&gt; cvs commit -m &lt;span class="s1"&gt;&amp;#39;Cambios desde la rama v1.2&amp;#39;&lt;/span&gt; main.c&lt;span class="sb"&gt;``&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Hay un problema: no podemos hacer esta jugada dos veces seguidas sin
dar conflictos :-)&lt;/p&gt;
&lt;p&gt;Supongamos que queremos volver a traernos un cambio desde la rama
'v1_2-branch' con el comando &lt;em&gt;update&lt;/em&gt;. El diferencial de cambios para
hacer la mezcla se hace desde la base de la derivación de la rama, si ya
hemos aplicado parte de ese diferencial en una actualización anterior
provocará irremediablemente un conflicto.&lt;/p&gt;
&lt;p&gt;La solución para evitar el engorro de trabajar con conflictos es
utilizar una referencia de la rama justo después de traer los cambios.
Dos opciones principales:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Etiquetar la rama cada vez que se traen cambios de ellas, 'v1_2-
branch-fixes-1'.&lt;/li&gt;
&lt;li&gt;Utilizar las nociones de tiempo de CVS, 'v1_2-branch:3 days ago'.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;La idea intuitiva es la de señalar en el historial de la rama dos
marcas de tiempo, la última que se llevo a la principal y el actual de
la rama, y aplicar los cambios que se realizaron entre esos dos
instantes en la rama en la principal. Por ejemplo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;$&lt;/span&gt; cvs update -j v1_2-branch-fixes-1 -j v1_2branch&lt;span class="sb"&gt;``&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
</summary></entry></feed>
