Un constructor de proyectos incremental es un objeto que manipula los recursos de un proyecto de forma determinada. Los constructores de proyectos incrementales se utilizan a menudo para aplicar una transformación a un recurso con el fin de producir un recurso o un artefacto de otro tipo.
Los conectores contribuyen suministrando constructores de proyectos incrementales a la plataforma para implementar transformaciones de recursos especializadas. Por ejemplo, las herramientas de desarrollo Java (JDT) definen un constructor de proyectos incremental que compila un archivo fuente Java en un archivo de clase cada vez que se añade o se modifica un archivo de un proyecto Java. También realiza el seguimiento de los archivos dependientes y los recompila cuando es necesario.
Desde el punto de vista de una API, la plataforma define dos tipos básicos de construcciones:
Las construcciones incrementales se generan con un delta de cambios de recursos. El delta refleja el efecto neto de todos los cambios realizados en los recursos desde la última vez que el constructor construyó el proyecto. Este delta es similar al utilizado en los eventos de cambio de recurso.
El usuario puede limpiar periódicamente los proyectos para forzar una reconstrucción de un proyecto completo la próxima vez que se realice una construcción incremental en dicho proyecto. La limpieza de un proyecto elimina la información de construcción, como por ejemplo los marcadores de problemas y los archivos de clase.
La mejor manera de describir los constructores es mediante ejemplos. El compilador Java de JDT se controla mediante un constructor de proyectos Java incremental que recompila los archivos de un proyecto que se ven afectados por los cambios. Cuando se desencadena una construcción completa (o una construcción incremental después de una limpieza), se compilan todos los archivos .java del proyecto. Los problemas de compilación que se encuentren se añaden como marcadores de problemas en los archivos .java afectados. Cuando se desencadena una construcción incremental, el constructor recompila de manera selectiva los archivos .java que se hayan añadido, cambiado o sufrido otro tipo de alteración y estén descritos en el delta de recursos, y además actualiza los marcadores de problemas según sea necesario. Los archivos .class o los marcadores que hayan dejado de ser adecuados se eliminan.
La construcción incremental proporciona ventajas de rendimiento obvias en el caso de los proyectos que tengan cientos o miles de recursos, cuando la mayoría de ellos no haya sufrido cambios en ningún momento.
El reto técnico para la construcción incremental es determinar de manera exacta qué se debe reconstruir. Por ejemplo, el estado interno mantenido por el constructor Java incluye aspectos como un gráfico de dependencias y una lista de los problemas de compilación que se han notificado. Esta información se utiliza durante una construcción incremental para identificar qué clases deben recompilarse como respuesta a un cambio en un recurso Java.
Aunque la estructura básica de la construcción se define en la plataforma, el trabajo real se efectúa en el código del constructor. Los patrones para implementar constructores incrementales complejos no están dentro del ámbito de lo que se trata aquí, ya que la implementación depende del diseño específico del constructor.
El constructor se puede invocar explícitamente de una de las siguientes maneras:
En la práctica, el usuario del entorno de trabajo desencadena una construcción seleccionando los mandatos correspondientes en el menú del navegador de recursos.
La plataforma también invoca los constructores de proyectos incrementales durante una construcción automática. Si están habilitadas, las construcciones automáticas se ejecutan siempre que cambia el área de trabajo.
El punto de extensión org.eclipse.core.resources.builders permite contribuir suministrando un constructor de proyectos incremental a la plataforma. Los siguientes códigos XML muestran cómo haría el conector com.example.builders hipotético para contribuir con un constructor de proyectos incremental.
<extension id="mybuilder" name="Mi constructor de ejemplo" point="org.eclipse.core.resources.builders"> <builder <run class="com.example.builders.BuilderExample"> <parameter name="optimize" value="true" /> <parameter name="comment" value="Builder comment" /> </run> </builder> </extension>
La clase (atributo class) identificada en el punto de extensión debe ampliar la clase IncrementalProjectBuilder de la plataforma.
public class BuilderExample extends IncrementalProjectBuilder { IProject[] build(int kind, Map args, IProgressMonitor monitor) throws CoreException { // añadir aquí la lógica de la construcción return null; } protected void startupOnInitialize() { // añadir aquí la lógica de inicialización del constructor } protected void clean(IProgressMonitor monitor) { // añadir aquí la lógica de limpieza del constructor } }
El proceso de construcción empieza por el método build(), que incluye información sobre el tipo de construcción que se ha solicitado. La construcción tiene uno de los siguientes valores:
Si se ha solicitado una construcción incremental, se proporciona un delta de recursos para describir los cambios realizados en los recursos desde la última construcción. El siguiente fragmento de código define con más precisión el método build().
protected IProject[] build(int kind, Map args, IProgressMonitor monitor throws CoreException { if (kind == IncrementalProjectBuilder.FULL_BUILD) { fullBuild(monitor); } else { IResourceDelta delta = getDelta(getProject()); if (delta == null) { fullBuild(monitor); } else { incrementalBuild(delta, monitor); } } return null; }
A veces, al construir el proyecto "X", el constructor necesita información sobre los cambios realizados en algún otro proyecto "Y". (Por ejemplo, cuando una clase Java de X implementa una interfaz proporcionada en Y). Mientras se construye X, se puede obtener un delta de Y llamando al método getDelta(Y). Para garantizar que la plataforma puede proporcionar deltas, el constructor de X debe haber declarado la dependencia entre X e Y devolviendo una matriz que contenga Y desde una llamada build() anterior. Si un constructor no tiene ninguna dependencia, basta con que devuelva el valor null. Consulte la sección correspondiente a IncrementalProjectBuilder para obtener más información.
La lógica necesaria para procesar una petición de construcción completa es específica del conector. Esto puede implicar que se tengan que visitar todos los recursos del proyecto o incluso examinar otros proyectos si existen dependencias entre proyectos. En el fragmento de código siguiente se sugiere cómo podría implementarse una construcción completa.
protected void fullBuild(final IProgressMonitor monitor) throws CoreException { try { getProject().accept(new MyBuildVisitor()); } catch (CoreException e) { } }
El visitante de la construcción debe efectuar la construcción del recurso específico (y devolver el valor true para seguir visitando todos los recursos hijo).
class MyBuildVisitor implements IResourceVisitor { public boolean visit(IResource res) { //construir el recurso especificado. //devolver el valor true para seguir visitando los hijos. return true; } }
El proceso de visita continúa hasta que se ha visitado todo el árbol de recursos.
Al realizar una construcción incremental, el constructor trabaja con un delta de cambio de recursos, en lugar de hacerlo con un árbol de recursos completo.
protected void incrementalBuild(IResourceDelta delta, IProgressMonitor monitor) throws CoreException { // el visitante realiza el trabajo. delta.accept(new MyBuildDeltaVisitor()); }
El proceso de visita continúa hasta que se ha visitado todo el árbol del delta de recursos. La naturaleza específica de los cambios es similar a la descrita en el tema Implementar un escuchador de cambios de recursos. Una diferencia importante es que con los constructores incrementales de proyectos, se trabaja con un delta de recursos basado en un determinado proyecto, no en toda el área de trabajo.
El entorno de trabajo permite a los usuarios limpiar un proyecto o conjunto de proyectos antes de iniciar una construcción. Esta función permite al usuario forzar una reconstrucción desde cero sólo en determinados proyectos. Los constructores deben implementar este método para borrar los marcadores de problemas y los recursos derivados del proyecto.
Para hacer que un constructor esté disponible para un proyecto dado, hay que incluirlo en la especificación de construcción del proyecto. La especificación de construcción de un proyecto es una lista de los mandatos que se han de ejecutar secuencialmente cuando se construye el proyecto. Cada mandato nombra un solo constructor de proyectos incremental.
NOTA: El nombre del constructor en un mandato de construir es el ID totalmente calificado de la extensión del constructor. El ID totalmente calificado de una extensión se crea combinando el ID del conector con el ID de extensión simple del archivo plugin.xml. Por ejemplo, un constructor que tenga el ID de extensión simple "mybuilder" en el conector "com.example.builders" tendría el nombre "com.example.builders.mybuilder"
El siguiente fragmento de código añade un constructor nuevo como primer constructor de la lista de constructores existente.
final String BUILDER_ID = "com.example.builders.mybuilder"; IProjectDescription desc = project.getDescription(); ICommand[] commands = desc.getBuildSpec(); boolean found = false; for (int i = 0; i < commands.length; ++i) { if (commands[i].getBuilderName().equals(BUILDER_ID)) { found = true; break; } } if (!found) { //añadir el constructor al proyecto ICommand command = desc.newCommand(); command.setBuilderName(BUILDER_ID); ICommand[] newCommands = new ICommand[commands.length + 1]; // Añadirlo antes que los otros constructores. System.arraycopy(commands, 0, newCommands, 1, commands.length); newCommands[0] = command; desc.setBuildSpec(newCommands); project.setDescription(desc, null); }
La configuración del constructor de un proyecto solo se realiza una vez, que suele ser durante el proceso de creación del proyecto.