Previous Page
Next Page

19.2. Building the Favorites Product

Now let's revisit the Favorites product build script introduced in Section 2.4.2, Building with Apache Ant, on page 83 and compare that with a build script automatically generated by the plug-in development tools. Following that, enhance your build script to take into account all the new bells and whistles that have been added since the build script was introduced in Chapter 2, A Simple Plug-in Example.

19.2.1. Auto-generated build script

The Eclipse plug-in development tools can generate a build.xml file containing a skeleton of a build script. In the Navigator view, right-click on the pugin.xml file and select PDE Tools > Create Ant Build File (the ==> denotes a continuation of the previous line and must not be included).

 <project name="com.qualityeclipse.favorites"
          default="build.jars"
          basedir=".">

    <property name="basews" value="${ws}" />
    <property name="baseos" value="${os}" />
    <property name="basearch" value="${arch}" />
    <property name="basenl" value="${nl}" />

    <!-- Compiler settings. -->
    <property name="javacFailOnError" value="false" />
    <property name="javacDebugInfo" value="on" />
    <property name="javacVerbose" value="true" />
    <property name="javacSource" value="1.3" />
    <property name="javacTarget" value="1.2" />
    <property name="compilerArg" value="" />
    <path id="path_bootclasspath">
       <fileset dir="${java.home}/lib">
          <include name="*.jar" />
       </fileset>
    </path>
    <property name="bootclasspath" refid="path_bootclasspath" />

    <target name="init" depends="properties">
       <condition property="pluginTemp"
                  value="${buildTempFolder}/plugins">
          <isset property="buildTempFolder" />
       </condition>
       <property name="pluginTemp" value="${basedir}" />
       <condition property="build.result.folder"
                  value="${pluginTemp}/com.qualityeclipse.favorites">
         <isset property="buildTempFolder" />
      </condition>
      <property name="build.result.folder" value="${basedir}" />
      <property name="temp.folder" value="${basedir}/temp.folder" />
      <property name="plugin.destination" value="${basedir}" />
   </target>

  <target name="properties" if="eclipse.running">
      <property name="build.compiler"
                value="org.eclipse.jdt.core.JDTCompilerAdapter" />
  </target>

  <target name="build.update.jar"
          depends="init"
          description="Build the plug-in:
==>          com.qualityeclipse.favorites for an update site.">
      <delete dir="${temp.folder}" />
      <mkdir dir="${temp.folder}" />
      <antcall target="build.jars" />
      <antcall target="gather.bin.parts">
         <param name="destination.temp.folder"
                value="${temp.folder}/" />
      </antcall>
      <zip destfile="${plugin.destination}/
==>           com.qualityeclipse.favorites_1.0.0.jar"
           basedir="${temp.folder}/
==>           com.qualityeclipse.favorites_1.0.0"
           filesonly="false"
           whenempty="skip"
           update="false" />
      <delete dir="${temp.folder}" />
   </target>

   <target name="favorites.jar"
           depends="init"
           unless="favorites.jar"
           description="Create jar:
==>           com.qualityeclipse.favorites favorites.jar.">
      <delete dir="${temp.folder}/favorites.jar.bin" />
      <mkdir dir="${temp.folder}/favorites.jar.bin" />
      <!-- compile the source code -->
      <javac destdir="${temp.folder}/favorites.jar.bin"
             failonerror="${javacFailOnError}"
             verbose="${javacVerbose}"
             debug="${javacDebugInfo}"
             includeAntRuntime="no"
             bootclasspath="${bootclasspath}"
             source="${javacSource}"
             target="${javacTarget}">
         <compilerarg line="${compilerArg}" />
         <classpath>
            <pathelement path="..\..\plugins\
==>                         org.eclipse.core.runtime_3.1.2.jar" />
            <pathelement path="..\..\plugins\
==>                         org.eclipse.osgi_3.1.2.jar" />
            <pathelement path="..\..\plugins\
==>                         org.eclipse.core.resources_3.1.2.jar" />

            ... lots more of the same ...

         </classpath>
         <src path="src/" />
      </javac>

      <!-- Copy necessary resources -->
      <copy todir="${temp.folder}/favorites.jar.bin"
            failonerror="true"
            overwrite="false">
         <fileset dir="src/"
                  excludes="**/*.java, **/package.htm*,null" />
      </copy>
      <mkdir dir="${build.result.folder}" />
      <jar destfile="${build.result.folder}/favorites.jar"
           basedir="${temp.folder}/favorites.jar.bin" />
      <delete dir="${temp.folder}/favorites.jar.bin" />
   </target>

   <target name="favoritessrc.zip"
           depends="init"
           unless="favoritessrc.zip">
      <mkdir dir="${build.result.folder}" />
      <zip destfile="${build.result.folder}/favoritessrc.zip"
           filesonly="false"
           whenempty="skip"
           update="false">
         <fileset dir="src/" includes="**/*.java" />
      </zip>
   </target>

   <target name="build.jars"
           depends="init"
           description="Build all the jars for the plug-in:
==>           com.qualityeclipse.favorites.">
      <available property="favorites.jar"
                 file="${build.result.folder}/favorites.jar" />
      <antcall target="favorites.jar" />
   </target>

   <target name="build.sources" depends="init">
      <available property="favoritessrc.zip"
                 file="${build.result.folder}/favoritessrc.zip" />
      <antcall target="favoritessrc.zip" />
   </target>

   <target name="gather.bin.parts"
           depends="init"
           if="destination.temp.folder">
      <mkdir dir="${destination.temp.folder}/
==>             com.qualityeclipse.favorites_1.0.0" />
      <copy todir="${destination.temp.folder}/
==>             com.qualityeclipse.favorites_1.0.0"
            failonerror="true"
            overwrite="false">
         <fileset dir="${build.result.folder}"
                   includes="favorites.jar" />
      </copy>
      <copy todir="${destination.temp.folder}/
==>            com.qualityeclipse.favorites_1.0.0"
            failonerror="true"
            overwrite="false">

         <fileset dir="${basedir}"
                  includes="plugin.xml,META-INF/,favorites.jar,
==>                  icons/,plugin.properties,
==>                  about.html,about.ini,about.mappings,
==>                  about.properties, favorites.gif,
==>                  feature.gif,schema/" />
      </copy>
   </target>

   <target name="build.zips" depends="init">
   </target>

   <target name="gather.sources"
           depends="init"
           if="destination.temp.folder">
       <mkdir dir="${destination.temp.folder}/
==>              com.qualityeclipse.favorites_1.0.0" />
       <copy file="${build.result.folder}/favoritessrc.zip"
             todir="${destination.temp.folder}/
==>              com.qualityeclipse.favorites_1.0.0"
             failonerror="false"
             overwrite="false" />
    </target>

    <target name="gather.logs"
             depends="init"
             if="destination.temp.folder">
       <mkdir dir="${destination.temp.folder}/
==>              com.qualityeclipse.favorites_1.0.0" />
       <copy file="${temp.folder}/favorites.jar.bin.log"
             todir="${destination.temp.folder}/
==>              com.qualityeclipse.favorites_1.0.0"
             failonerror="false"
             overwrite="false" />
    </target>

    <target name="clean"
            depends="init"
            description="Clean the plug-in:
==>            com.qualityeclipse.favorites of all the zips,
==>            jars and logs created.">
       <delete file="${build.result.folder}/favorites.jar" />
       <delete file="${build.result.folder}/favoritessrc.zip" />
       <delete file="${plugin.destination}/
==>            com.qualityeclipse.favorites_1.0.0.jar" />
       <delete file="${plugin.destination}/
==>            com.qualityeclipse.favorites_1.0.0.zip" />
       <delete dir="${temp.folder}" />
    </target>

    <target name="refresh"
            depends="init"
            if="eclipse.running"
            description="Refresh this folder.">

       <eclipse.convertPath fileSystemPath="
==>         C:/eclipse/workspace/com.qualityeclipse.favorites"
                            property="resourcePath" />
       <eclipse.refreshLocal resource="${resourcePath}"
                             depth="infinite" />
    </target>

    <target name="zip.plugin"
            depends="init"
            description="Create a zip containing all the elements
==>            for the plug-in: com.qualityeclipse.favorites.">
       <delete dir="${temp.folder}" />
       <mkdir dir="${temp.folder}" />
       <antcall target="build.jars" />
       <antcall target="build.sources" />
       <antcall target="gather.bin.parts">
          <param name="destination.temp.folder"
                 value="${temp.folder}/" />
       </antcall>
       <antcall target="gather.sources">
          <param name="destination.temp.folder"
                 value="${temp.folder}/" />
       </antcall>
       <delete>
          <fileset dir="${temp.folder}" includes="**/*.bin.log" />
       </delete>
       <zip destfile="${plugin.destination}/
==>            com.qualityeclipse.favorites_1.0.0.zip"
            basedir="${temp.folder}"
            filesonly="true"
            whenempty="skip"
            update="false" />
       <delete dir="${temp.folder}" />
    </target>

</project>

This auto-generated build script is a bit more complicated than the Favorites build script, containing five external targets and seven internal targets (see Figure 19-6). The zip.plugin and build.update.jar targets call other targets, and all the targets depend on the init target, which in turn depends on the properties target.

Figure 19-6. The PDE-generated build script.


You would not manually edit this script, but rather use the PDE Build process (not covered here) to auto-generate and execute this and other scriptsone per plug-in, fragment, and featurethen package the result. For a more complete description of the PDE Build process, check out Chapter 24 of the Eclipse Rich Client Platform book. The PDE Build process has its advantages, but does not lend itself to building products against multiple versions of Eclipse as we wish to do, so we are going to stick with our script and enhance it over the next several sections to perform the necessary tasks.

19.2.2. Refactoring the Favorites build script

Before proceeding, you need to review the build script introduced in Section 2.4.2, Building with Apache Ant, on page 83, examining each chunk and refactoring it into separate targets, macrodefs, and build files. Each plug-in, fragment and feature should be responsible for building itself using its own build-bundle.xml script. A master build script named build-favorites.xml initializes the build process, calls the appropriate target in each build-bundle.xml script (see Figure 19-7), then packages the result. Common macrodefs are placed in the build-macros.xml file and shared by all scripts. This new structure scales well as more plug-ins, fragments and features are added and will be helpful in building the Favorites product for more than just the Eclipse 3.1 platform.

Figure 19-7. Favorites build script structure.


During the build process, scripts need a well-defined place for temporary files generated during the build process and product files that will be shipped to the customer. The ${build.root} property defines a root directory for the build process, from which other properties are derived. Developers can override these build properties to suit their own build situations (see Section 19.2.2.2, Build initialization, on page 679). If the user has defined the ${build.root} property to be /Build/QualityEclipse and not overridden any of the other build properties, then the directory structure would look something like this:

/Build/QualityEclipse   <----  ${build.root}

   product              <---- ${build.out} files ready for Customer
       Favorites_v1.0.0_for_Eclipse3.0.zip
       Favorites_v1.0.0_for_Eclipse3.1.zip

   temp                 <----  ${build.temp}

      3.0               <---- files specific to Eclipse 3.0
          com.qualityeclipse.favorites
             src        <---- preprocessed source files
             bin        <---- compiled preprocessed source files
             out        <---- jars, etc.
          com.qualityeclipse.favorites.nl1
             out
          ...

          out           <----  Eclipse 3.0 files ready for product
             plugins
               com.qualityeclipse.favorites_1.0.0
               com.qualityeclipse.favorites.nl1_1.0.0
               ...

    3.1                 <---- files specific to Eclipse 3.1
          com.qualityeclipse.favorites
             out
          com.qualityeclipse.favorites.nl1
             out
          ...
          out           <----  Eclipse 3.1 files ready for product
             plugins

       common           <---- files common to all versions
          com.qualityeclipse.favorites
             out
          com.qualityeclipse.favorites.feature
             out
          ...
          out           <---- common files ready for product
             features
                com.qualityeclipse.favorites_1.0.0
             plugins
                com.qualityeclipse.favorites.help_1.0.0

19.2.2.1. Build macros and fast fail

Create a new build-macros.xml file containing build elements shared by all build files. Over the course of this and the next several sections, macrodefs will be added to this file. Reference this new file at the top of the build-favorites.xml file and each build-bundle.xml file like this:

<?xml version="1.0" encoding="UTF-8"?>
<project default="product_all">
   <import file="../com.qualityeclipse.favorites/build-macros.xml"/>
   ...

In build scripts, as with any program, it is better to find a problem earlier rather than later; it is better for a build script to fail immediately, near the problem, so that it is obvious and can be fixed rather than failing later on (or worse not at all) making the process of finding and fixing the problem more difficult. To this end, declare a new checkfile macro in the build-macros.xml file. This new macro is used in various other macros and targets, providing a sanity check in the build process by checking for the existence of a specified file and causing the build to fail if that file is not present.

<macrodef name="checkfile">
   <attribute name="file" />
    <sequential>
     <fail message="Cannot find file: @{file}">
        <condition>
           <not>
              <available file="@{file}" />
           </not>
        </condition>
     </fail>
    </sequential>
 </macrodef>

In addition, sanity checking code is sprinkled throughout the build scripts but omitted from the following sections for brevity. For example, when a macrodef expects a property such as ${build.temp} to be defined, you add the following line near the beginning of the macrodef:

<fail unless="build.temp"/>

Sometimes a macrodef contains an attribute that should only have one of a limited range of values. For example, the @{target} attribute used in several macros in later sections should be "3.1", "3.0", or "common". In this case, add the following lines near the beginning of the macrodef.

<fail message="invalid value for target: @{target}">
   <condition>
      <not>
         <or>
            <equals arg1="@{target}" arg2="3.1" />
            <equals arg1="@{target}" arg2="3.0" />
            <equals arg1="@{target}" arg2="common" />
         </or>
      </not>
   </condition>
</fail>

19.2.2.2. Build initialization

The next step is to gather all the product build initialization, such as property assignments and directory deletion, into a single product_init target. This new product_init target will be responsible for initializing common properties used in building multiple binaries (see Section 19.2.4, Single versus multiple binaries, on page 691) and clearing out the temporary directories used during the build process to ensure a clean build.

Developers need to have the ability to build this product on their own machines, so different build properties for different developers are necessary. One approach is for each developer to modify their own local copy of this script, changing the build properties to suit the machine, but then there would be multiple versions of that same build script file, and synchronization would be complicated.

Alternatively, developers could specify build properties on the command line or in the Ant launch configuration dialog (see Section 19.1.4.5, Properties on the command line, on page 662), but then when they switched machines or changed workspaces their settings would be lost. A better approach is to have the build script load properties from a developer-specific file in a known location so that each developer can mantain their own set of build properties in the repository.

To accomplish this use the ${user.name} property, which Ant initializes to the user's account name, to read properties from a file located in the /com.qualityeclipse.favorites/build directory. This file (e.g. danrubel.properties) must define at least the ${build.root} property from which many other build-related properties are derived. Because it appears first in the build process, it, optionally, can define properties such as ${build.out} and ${build.temp}.

<target name="product_init">
   <checkfile file="build/${user.name}.properties" />
   <property file="build/${user.name}.properties" />

   <!-- At least one property must be defined -->
   <fail unless="build.root" />

   <!-- Properties derived from ${build.root} -->
   <property name="build.out" location="${build.root}/product" />
   <property name="build.temp" location="${build.root}/temp" />

   <!-- Define product info -->
   <property name="product.name" value="Favorites" />
   <property name="product.version" value="1.0.0" />

The product_init target also initializes getclasspath properties (see Section 19.2.3, Compiling during the build process, on page 689), preprocessor properties (see Section 19.2.6.3, Preprocessor, on page 695) and deletes the temporary build directory.

   <!-- GetClasspath and preprocessor initialization -->
   <property name="build.map"
             value="../com.qualityeclipse.favorites/.buildmap" />
   <property name="eclipsetools.preprocessor.version.valid"
             value="3.1,3.0"/>
   <property name="eclipsetools.preprocessor.version.source"
             value="3.1"/>

   <!-- Delete the temporary directory -->
   <delete dir="${build.temp}" />
</target>

19.2.2.3. Building multiple projects

As the Favorites product grows, you will be adding plug-ins, fragments, and features, so you need a build script that scales with your needs. To this end, split the build script along project lines with each project containing its own build-bundle.xml script. These build scripts are called by a new macrodef in the build-favorites.xml script. As new projects are added in the sections following this one, you must continue to update this macrodef with references to the build-bundle.xml script for those new projects. The order in which projects are built is important because one project can depend on build artifacts generated by another.

<macrodef name="build_bundles">
   <attribute name="target" />
   <sequential>
      <ant antfile="build-bundle.xml"
           target="@{target}"
           dir="../com.qualityeclipse.favorites" />
      <ant antfile="build-bundle.xml"
           target="@{target}"
           dir="../com.qualityeclipse.favorites.nl1" />
      <ant antfile="build-bundle.xml"
           target="@{target}"
           dir="../com.qualityeclipse.favorites.help" />
      <ant antfile="build-bundle.xml"
           target="@{target}"
           dir="../com.qualityeclipse.favorites.feature" />
   </sequential>
</macrodef>

This macro plus the zip_product macro (see below) are called from three new top-level targets in build-favorites.xml. Each target calls the build_bundles macro with a different @{target} attribute, causing the product to be built for different versions of Eclipse.

<target name="product_3.0" depends="product_init">
   <build_bundles target="build_3.0" />
   <zip_product target="3.0" />
</target>

<target name="product_3.1" depends="product_init">
   <build_bundles target="build_3.1" />
   <zip_product target="3.1" />
</target>

<target name="product_all" depends="product_init">
   <build_bundles target="build_all" />
   <zip_product target="3.1" />
   <zip_product target="3.0" />
</target>

Every build-bundle.xml file has roughly the same structure: three top-level targets called by the build_bundles macro previously mentioned plus a common target on which all three targets rely.

<?xml version="1.0" encoding="UTF-8"?>
<project default="build_all">
   <import file="../com.qualityeclipse.favorites/build-macros.xml"/>

   <target name="build_common">
      ... build processes common to all versions of Eclipse here ...
   </target>

   <target name="build_3.0" depends="build_common">
      ... build processes specific to Eclipse 3.0 here ...
   </target>

   <target name="build_3.1" depends="build_common">
      ... build processes specific to Eclipse 3.1 here ...
   </target>

   <target name="build_all" depends="build_3.1, build_3.0">
   </target>
</project>

Finally, a zip_product macro in the build-favorites.xml file, called by the top-level targets above, packages the files in Eclipse-specific files for the customer. All the files from the ${build.out}/common/out and ${build.out}/@{target}/out directories are combined to form a single deliverable for a specific version of Eclipse (the ==> in the code below denotes a continuation of the line above and must not be included).

<macrodef name="zip_product">
   <attribute name="target" />
   <attribute name="prefix"
         default="QualityEclipse/Favorites/E-@{target}/eclipse"/>

   <sequential>
      <mkdir dir="${build.out}" />
      <zip destfile="${build.out}/${product.name}
==>                  _v${product.version}_for_Eclipse@{target}.zip">
         <zipfileset dir="${build.temp}/common/out"
                     prefix="@{prefix}" />
         <zipfileset dir="${build.temp}/@{target}/out"
                     prefix="@{prefix}" />
      </zip>
   </sequential>
</macrodef>

Tip

In the zip_product macro above, the @{prefix} attribute determines the structure of the ZIP file that is created. The @{prefix} attribute default specified above is for product installation in a directory hierarchy separate from the Eclipse install directory as per the RFRS requirements (see Section 3.2.1, Link files, on page 105). For a simpler installation that does not necessitate a link file, change the @{prefix} attribute to "" (blank) so that the product can be unzipped directly into the Eclipse install directory.


19.2.2.4. Building each project

The specific elements for building each plug-in, fragment, and feature project are moved out of the build-favorites.xml file and into the appropriate build-bundle.xml file. Within each project-specific build script, the elements, such as initialization and building of shared files, common to both Eclipse 3.1 and 3.0 are placed in a build_common target while elements specific to Eclipse 3.1 are placed into a build_3.1 target.

For the com.qualityeclipse.favorites project, the common elements include things such as reading manifest information and generating the favoritessrc.zip.

<target name="build_common">
   <init_properties />
   <read_manifest />
   <read_build />
   <jar_lib target="common" lib="favoritessrc.zip">
      <fileset dir=".">
         <include name="${source.favorites.jar}" />
      </fileset>
   </jar_lib>
</target>

This target references several new macros that need to be added to the shared build-macros.xml file. The first macro referenced in the code above, init_properties, initializes properties that are common to all projects being built. In this case, the ${Bundle-Proj} property is initialized to the name of the project being built.

   <macrodef name="init_properties">
      <sequential>
         <basename file="." property="Bundle-Proj" />
      </sequential>
   </macrodef>

The next macro in build-macros.xml referenced by the build_common target, read_manifest, reads build information such as plug-in or fragment name, identifier, and version from the META-INF/MANIFEST.MF file. Because the information in the manifest is not in a form that can be read directly into Ant, you must first make a copy of the file in a temporary location, rework that copy into a more usable format, then read the copy into Ant. In the future, an Ant task could be created specifically to read build information out of a META-INF/MANIFEST.MF file.

<macrodef name="read_manifest">
   <attribute name="file" default="META-INF/MANIFEST.MF" />
   <attribute name="temp"
              default="${build.temp}/common/${Bundle-Proj}" />

   <sequential>

      <!-- Copy and rewrite the MANIFEST.MF
           so that it can be loaded as properties -->

      <mkdir dir="@{temp}" />

      <copy file="@{file}"
            tofile="@{temp}/MANIFEST.MF"
            overwrite="true" />

      <replace file="@{temp}/MANIFEST.MF">
         <replacefilter token=":=" value="=" />
         <replacefilter token=":" value="=" />
         <replacetoken>;</replacetoken>
         <replacevalue>
         </replacevalue>
       </replace>

      <!-- Load properties from the rewritten manifest -->
      <property file="@{temp}/MANIFEST.MF" />

      <!-- Rename properties for clarity -->
      <property name="Bundle-Id" value="${Bundle-SymbolicName}" />

    </sequential>
</macrodef>

Every project contains a build.properties file describing various files and directories to be included in the build. This file is convenient because Eclipse provides a nice Build Configuration editor that also appears as part of the plug-in manifest editor. The build.properties file in the com.qualityeclipse.favorites project looks something like what is shown in Figure 19-8.

Figure 19-8. Build Configuration editor.


Switching to the build.properties tab in the editor shows the properties being manipulated by the editor. In this case, the source.favorites.jar property is a comma-separated list of source directories for the favorites.jar, the output.favorites.jar property is a comma-separated list of directories containing binary files for the favorites.jar, and the bin.includes property is a comma-separated list of files and directories to be included in the plug-in itself.

source.favorites.jar = src/
output.favorites.jar = bin/
bin.includes = plugin.xml,\
               favorites.jar,\
               icons/,\
               plugin.properties,\
               about.html,\
               about.ini,\
               about.mappings,\
               about.properties,\
               favorites.gif,\
               feature.gif,\
               schema/

The various properties that can appear in this file include the following:

  • source.<library>a comma-separated list of files and directories to be included when compiling <library>. Typically, this is either the project root represented by "." or the "src/" directory.

  • exTRa.<library>a comma separated list of files and directories to be included on the classpath when compiling <library> but not in the library itself.

  • output.<library>the directory into which Eclipse compiles files specified in source.<library>. Typically this is either the project root represented by "." or the "bin/" directory.

  • bin.includesa comma-eparated list of files and directories to be included in the plug-in, fragment, or feature.

The bin.includes typically contains elements common for all versions of Eclipse. For example, the bin.includes of the various Favorites product projects have such things as:

  • Icons used by the Favorites view and model (see Chapter 7, Views)

  • preferences.ini (see Section 16.2, Externalizing Plug-in Strings, on page 578)

  • The schema files (see Section 17.2.2, Creating an extension point schema, on page 599)

  • META-INF/MANIFEST.MF, plugin.xml, and feature.xml files

  • The entire Favorites feature plug-in and related files (see Chapter 18, Features, Branding, and Updates)

  • The entire Favorites help plug-in (see Chapter 15, Implementing Help)

The read_build macro referenced in the build_common target resides in the build-macros.xml file, asserts that the build.properties file exists, and loads the properties in the build.properties file into Ant.

<macrodef name="read_build">
   <attribute name="file" default="build.properties" />
   <sequential>
      <checkfile file="@{file}" />
      <property file="@{file}" />
      <!-- Rename some properties for clarity -->
      <property name="Bundle-CommonFiles" value="${bin.includes}" />
   </sequential>
</macrodef>

Finally, the last call in the the build_common target, the jar_lib macro, gathers the source files for this project into the favoritessrc.zip file, placing that file in the ${build.temp}/@{target}/${Bundle-Proj}/out directory to be picked up later by either the jar_bundle or dir_bundle macros. As mentioned earlier, assertions for various macro inputs, such as the ${build.temp} property and the @{target} attribute, are omitted here for brevity (the ==> in the code below denotes a continuation of the previous line and must not be included).

<macrodef name="jar_lib">
   <attribute name="target" />
   <attribute name="lib" />
   <element name="content" implicit="true" />
   <sequential>
      <mkdir dir="${build.temp}/@{target}/${Bundle-Proj}/out" />
      <zip destfile="${build.temp}/@{target}
==>                                 /${Bundle-Proj}/out/@{lib}">
         <content />
      </zip>
   </sequential>
</macrodef>

The build_3.1 target in the build-bundle.xml file of the com.qualityeclipse.favorites project assembles the Eclipse 3.1-specific elements, gathers the artifacts generated by build_common, and places them all in a common location ready for inclusion in the product.

<target name="build_3.1" depends="build_common">
   <jar_bundle target="3.1" type="plugins">
      <fileset dir="." includes="META-INF/MANIFEST.MF" />
      <fileset dir="${output.favorites.jar}" />
   </jar_bundle>
</target>

Tip

When packaging a plug-in as a single JAR file, it is best to include the class files directly in that plug-in's JAR file rather than in a separate classes JAR file which is then contained in the plug-in JAR file. If you do this, then be sure to exclude the Bundle-ClassPath attribute from the META-INF/MANIFEST.MF either by editing the file directly or by removing all JAR files from the Classpath section of the Runtime page in the Plug-in manifest editor (see Figure 2-11 on page 75).


The jar_bundle macro referenced in the preceding build_3.1 target appears in the build-macros.xml file so that it can be reused by multiple build-bundle.xml scripts (the ==> in the code below denotes a continuation of the previous line and must not be included).

<macrodef name="jar_bundle">
   <attribute name="target" />
   <attribute name="type" />
   <attribute name="id" default="${Bundle-Id}" />
   <attribute name="version" default="${Bundle-Version}" />
   <element name="content" implicit="true" optional="true" />
   <sequential>
      <mkdir dir="${build.temp}/@{target}/out/@{type}" />
      <mkdir dir="${build.temp}/common/${Bundle-Proj}/out" />
      <mkdir dir="${build.temp}/@{target}/${Bundle-Proj}/out" />
      <zip destfile="${build.temp}
==>                    /@{target}/out/@{type}/@{id}_@{version}.jar">
       <zipfileset dir="." includes="${Bundle-CommonFiles}" />
       <zipfileset dir="${build.temp}/common/${Bundle-Proj}/out" />
      <zipfileset dir="${build.temp}/@{target}/${Bundle-Proj}/out"/>
       <content />
      </zip>
   </sequential>
</macrodef>

This code assembles the following elements into a single bundle JAR file (a plug-in can be deployed as a single JAR file as of Eclipse 3.1) and places that JAR in the ${build.temp}/@{target}/out file for inclusion in the product.

  • Files and directories in the ${Bundle-CommonFiles} property (same as the ${bin.includes} property)

  • Anything in ${build.temp}/common/${Bundle-Proj}/out

  • Anything in ${build.temp}/@{target}/${Bundle-Proj}/out

  • Anything specified as a fileset subelement

All the other projectscom.qualityeclipse.favorites.feature, com.qualityeclipse.favorites.help, and com.qualityeclipse.favorites.nl1are assembled in a similar fashion using their own build-bundle.xml scripts.

All this refactoring has resulted in a new and more flexible build structure that can easily be adapted to generate new binaries for other versions of Eclipse (see Figure 19-7 on page 677). Currently, the product_31 target depends on the build_31 and build_common targets in each build-bundle.xml file.

This structure scales nicely in that by adding new build-bundle.xml files you can easily build additional projects as part of the Favorites product; and by adding new product_xx and build_xx targets you can easily build additional binaries to support different versions of Eclipse. For example, in the next section new product_30 and build_30 targets are added to support Eclipse 3.0.

Tip

When installing the newly built Favorites product, be sure to adjust the link file discussed in Section 3.2.1, Link files, on page 105, from

   path=C:/QualityEclipse/Favorites

to

   path=C:/QualityEclipse/Favorites/E-3.1

so that the newly built Favorites product will be used.


19.2.3. Compiling during the build process

The current build script simply copies the Java class files produced by the Eclipse environment into the favorites.jar file. You need to compile the source files during the build process to ensure that you have a full and complete compilation, verifying the work of the Eclipse IDE. In addition, this step will be necessary if you want to produce a binary for each different version of Eclipse that is supported. In the build-bundle.xml file of the com.qualityeclipse.favorites project, modify the build_3.1 target to compile the source before packaging it rather than packaging what the Eclipse IDE has already compiled.

<target name="build_3.1" depends="build_common">
   <compile_dir target="3.1" />
   <jar_bundle target="3.1" type="plugins">
      <fileset dir="." includes="META-INF/MANIFEST.MF" />
   </jar_bundle>
</target>

The build_3.1 target above calls a new compile_dir macro that must be added to the build-macros.xml file. This new macro runs the source through the eclipsetools.preprocessor (see Section 19.2.6.3, Preprocessor, on page 695), obtains the project's classpath using the eclipsetools.getclasspath task (see Section 19.2.7, Classpath tools, on page 701), and finally compiles the source into the specified directory.

<macrodef name="compile_dir">
   <attribute name="target" />
   <attribute name="source" default="src" />
   <attribute name="destdir"
              default="${build.temp}/@{target}/${Bundle-Proj}/bin" />
   <attribute name="extraClasspath" default="" />
   <attribute name="debug" default="on" />
   <sequential>

       <eclipsetools.preprocessor
          targetVersion="@{target}"
          dir="@{source}"
          todir="${build.temp}/@{target}/${Bundle-Proj}/src" />

       <eclipsetools.getclasspath
          config="eclipse_@{target}"
          binroot="${build.temp}/@{target}"
          buildmap="${build.map}"
          property="classpath_@{target}" />

       <mkdir dir="${build.temp}/@{target}/${Bundle-Proj}/bin" />

       <javac srcdir="${build.temp}/@{target}/${Bundle-Proj}/src"
              destdir="@{destdir}"
              classpath="${classpath_@{target}};@{extraClasspath}"
              debug="@{debug}" />

      <copy todir="@{destdir}">
         <fileset dir="@{source}">
            <exclude name="**/*.class" />
            <exclude name="**/*.java" />
            <exclude name="**/Thumbs.db" />
         </fileset>
      </copy>

   </sequential>
</macrodef>

If you get a build error similar to this:

BUILD FAILED: file:    .../workspace/com.qualityeclipse.favorites/
build.xml:182:
Unable to find a javac compiler;
com.sun.tools.javac.Main is not on the classpath.
Perhaps JAVA_HOME does not point to the JDK

you may not have the JDK tools.jar file on your Ant classpath. Open the Ant > Runtime preference page (see Figure 19-9) and verify that the tools.jar file appears under Global Entries. If it does not, then select Global Entries, click Add External JARS..., navigate to your <JDK>\lib\tools.jar file, and click Open so that tools.jar is added to your Ant classpath.

Figure 19-9. The Ant > Runtime preference page showing tools.jar.


By default, the javac task uses the Java compiler found on the Ant classpath as just specified. To use the Eclipse compiler, set the build.compiler property.

<property
   name="build.compiler"
   value="org.eclipse.jdt.core.JDTCompilerAdapter"/>

19.2.4. Single versus multiple binaries

What if the public API used by your plug-in has not changed and the code compiles against each version of Eclipse successfully? Why should you ship a different binary of your product for each version of Eclipse? Given this scenario, a single binary might run correctly on different versions of Eclipse, but can you be sure? There are cases where the same source compiled against two different versions of Eclipse will produce two different binaries, each of which will only execute correctly on one version of Eclipse, not the other. For example, if your code had a method called yourMethod():

Your code:
   public void yourMethod() {
      foo(0);
   }


Eclipse 3.1:
   public void foo(int value) {
       ... some operation ...
   }

Eclipse 3.0:
   public void foo(long value) {
       ... some operation ...
   }

then yourMethod() method would compile under Eclipse 3.1 and Eclipse 3.0 without any code changes, but would the version compiled under Eclipse 3.1 run in Eclipse 3.0?

Things like this make it well worth the up-front time and effort to deliver one binary for each version of Eclipse you intend to support rather than spending that same amount of time and more later on debugging problems in the field.

19.2.5. Editing with different versions of Eclipse

Sometimes, different developers using different versions of Eclipse want to work on the same project. If the project in question does not involve any Eclipse plug-ins, then there is no problem. The problem arises when a project uses the ECLIPSE_HOME classpath variable, which is automatically managed by the environment to point to the current Eclipse installation. As a result, any project using ECLIPSE_HOME references plug-ins in the current Eclipse installation.

For example, a developer using Eclipse 3.1 will have the project compiled against the Eclipse 3.1 plug-ins, whereas someone using Rational Application Developer 6.0, which is based on Eclipse 3.0, will have that same project compiled against Eclipse 3.0 plug-ins.

One solution is to use the PDE Target Platform preference page (see Figure 19-10). Using this page, you can retarget the ECLIPSE_HOME classpath variable at a different Eclipse installation. The problem with this approach is that it does not address the problem of different projects compiled against different versions of Eclipse. With this solution, you will have all projects compiled against the same Eclipse installation.

Another solution is to never use the ECLIPSE_HOME classpath variable at all. Assuming that you want to support Eclipse 2.1, 3.0, and 3.1, install all three Eclipse versions into separate directories with names such as c:\eclipse2.1, c:\eclipse3.0, and c:\eclipse3.1.

Figure 19-10. The PDE Target Platform preference page.


Then, set up three classpath variables named ECLIPSE21_HOME, ECLIPSE30_HOME, and ECLIPSE31_HOME, which point to their respective Eclipse installations. If a project has been compiled against Eclipse 3.1, you would use ECLIPSE31_HOME rather than ECLIPSE_HOME.

With this approach, it doesn't matter which version of Eclipse is being used as the code is always compiled against one specific Eclipse version. The downside of this approach is that the PDE will not keep the plug-in manifest dependency list in sync with the project's classpath file.

19.2.6. Building against different versions of Eclipse

Many developers, especially tool developers who write code for Eclipse itself, like to use bleeding-edge code; however, large IT departments and other paying customers tend to prefer to lag the edge, using older, more established and supported software. This disparity creates a problem because you want to develop plug-ins using the latest and greatest, but your client base, the ones who pay for your development fun, may not have caught up yet. Therefore, you need to ship a product with one binary for each version of Eclipse supported.

To complicate matters, different versions of Eclipse have different public APIs. This means one set of source for each version of Eclipse, or does it? Can there be a single source base that is built into one binary for each version of Eclipse?

19.2.6.1. Code branches

One tried-and-true approach to this problem is to have one code stream or branch in the repository for each version of Eclipse you intend to support (see Figure 19-11). Another is to have separate projects, one per version of Eclipse. Unfortunately, with each of these approaches, there is no single code base, so providing the same new functionality for each version of Eclipse involves merging lots of code from one branch to another.

Figure 19-11. Code branches, or multiple projects.


19.2.6.2. Façade pattern

Another approach is a single code base with a façade pattern to handle differences between the versions of Eclipse. One project contains the common code, typically well over 95 percent of the code, accessing the Eclipse API that has not changed across the versions supported. Additional projects, one for each version of Eclipse, provide glue code for accessing just those public APIs that have changed (see Figure 19-12). This approach could produce a single binary that will provide the same functionality across different versions of Eclipse, but you could have the hidden runtime problems outlined in Section 19.2.4, Single versus multiple binaries, on page 691.

Another drawback is that related functionality and methods that should be clustered together tend to become spread out across different packages and projects due to the façade pattern being used.

Figure 19-12. Façade pattern.


19.2.6.3. Preprocessor

After trying the first two approaches, we settled on using a Java preprocesssor to handle the differences between versions of Eclipse. This way, there is a single code base that is compiled against each different version of Eclipse, verifying that the calls to Eclipse methods match the public APIs available in that version. A build process takes one code base and produces one binary for each version of Eclipse (see Figure 19-13). In contrast to the façade pattern approach, related functionality and methods tend to stay logically clumped together in the same package rather than being spread out across the product, which leads to a more maintainable product.

Figure 19-13. Preprocessor approach.


The following lines of code from Section 19.2.2.2, Build initialization, on page 679 define which versions are being targeted and which version of Eclipse contains the source. These properties are used by the eclipsetools.preprocessor Ant task to sanity check the situation and globally specify the version of Eclipse in which the source was developed.

<property name="eclipsetools.preprocessor.version.valid"
          value="3.1,3.0"/>
<property name="eclipsetools.preprocessor.version.source"
          value="3.1"/>

In the compile_dir macro discussed in Section 19.2.3, Compiling during the build process, on page 689, the eclipsetools.preprocessor is used to translate source developed for one version of Eclipse into source targeted against another version of Eclipse.

<eclipsetools.preprocessor
   targetVersion="@{target}"
   dir="@{source}"
   todir="${build.temp}/@{target}/${Bundle-Proj}/src" />

Download the Preprocessor

The <eclipsetools.preprocessor> task just mentioned is not part of Ant or Eclipse, but is an Ant extension that we wrote. We needed it to build our products, and it is freely available for anyone to use at www.qualityeclipse.com/ant/preprocessor.


Code to be translated by the preprocessor involves structured Java comments. The preprocessor statements embedded in comments expose the API call for only one version of Eclipse while hiding the different API calls for the other versions. During the build process, the source is parsed and some sections are commented while others are uncommented, all before the source is sent to the Java compiler (see Figure 19-14).

Figure 19-14. Preprocessor translating code.


For example, the code on the next page was developed against the Eclipse 3.1 environment, and thus the Eclipse 3.1-specific code is uncommented, while the Eclipse 2.1- and 3.0-specific code are commented.

In addition, differences in the plugin.xml can be handled in a similar manner. For example, the org.eclipse.ui.ide plug-in only exists in Eclipse 3.0 and 3.1, not in Eclipse 2.1.

<?xml version="1.0" encoding="UTF-8"?>
<plugin
   id="com.mycompany.myapp"
   name="%pluginName"
   version="1.0.0"
   provider-name="My Company"
   class="com.mycompany.myapp.MyPlugin">

   <requires>
      <import plugin="org.apache.xerces"/>
      <import plugin="org.eclipse.core.runtime"/>
      <import plugin="org.eclipse.core.resources"/>
      <import plugin="org.eclipse.ui"/>
      <!-- $if version >= 3.0 $ -->
      <import plugin="org.eclipse.ui.ide" optional="true"/>
      <!--$endif $ -->
   </requires>

   ... etc ...
</plugin>

Tip

If you modify the <requires> section of the plugin.xml without changing the plug-in's version number and then install it over an earlier version of that plug-in, you may need to delete some information in <eclipseInstallDir>/configuration because Eclipse caches plug-in dependency information in that directory.


19.2.6.4. Fragments

Fragments can be used to smooth out differences between versions of Eclipse (see Section 16.3, Using Fragments, on page 587 and Section 20.2.6, Using fragments, on page 714 for more about fragments). If a newer version of Eclipse has an API you want to use but an older version does not, use a fragment to insert a helper class into the older version of Eclipse, allowing you access to the necessary internal code. If a class has been added to a newer version of Eclipse that was not present in the older version, then you can back-port the entire class to the older version of Eclipse using a fragment. Be aware that your fragment may collide with someone else's fragment doing the same thing; it is safer to add uniquely named helper classes than to backport Eclipse classes.

Tip

When creating a fragment to backport functionality, be sure to adjust the Host version range attribute on the Overview page of the manifest editor (see Figure 16-12 on page 591) to include the version of Eclipse for which the functionality applies (see Section 3.3.3, Plug-in dependencies, on page 110 for examples of valid version ranges).


19.2.6.5. Favorites in Eclipse 3.0

Since you want to support Eclipse 3.0, the next step is to modify the Favorites project so that the source correctly compiles against Eclipse 3.0. First, retarget the ECLIPSE_HOME classpath variable so that it points to the Eclipse 3.0 installation (see Section 19.2.5, Editing with different versions of Eclipse, on page 692). Then, modify the code so that it properly compiles against Eclipse 3.0, placing any Eclipse 3.1-specific code in comments. Finally, revert the classpath variable so that it again points to the Eclipse 3.1 installation, and adjust the preprocessor comments accordingly.

Tip

A tool for automatically adjusting the classpath of a project between versions of Eclipse is available on the QualityEclipse Web site (www.qualityeclipse.com/tools/classpath).


When preparing the Favorites view for Eclipse 3.0, one of the issues encountered is the addHelpButtonToToolBar() method. In this case, the Eclipse 3.1 help public API differs from the Eclipse 3.0 help public API. To handle this situation, modify the addHelpButtonToToolBar() method:

private void addHelpButtonToToolBar() {
   final IWorkbenchHelpSystem helpSystem = getHelpSystem();
   ... etc ...

extracting help system access into a new getHelpSystem() method:

private IWorkbenchHelpSystem getHelpSystem() {

    /* $eclipsetools.preprocessor.if version >= 3.1 $ */
    return getSite().getWorkbenchWindow().getWorkbench()
          .getHelpSystem();

    /* $eclipsetools.preprocessor.elseif version < 3.1 $
    return org.eclipse.ui.help.WorkbenchHelpSystem30
          .getHelpSystem();

   $eclipsetools.preprocessor.endif $ */
}

In addition, because Eclipse 3.0 lacks a IWorkbenchHelpSystem interface, you should create a new fragment named com.qualityeclipse.e30f.ui.workbench to backport this functionality. Create a WorkbenchHelpSystem30 class in this fragment to access the help system interface because IWorkbench does not have a getHelpSystem() method in Eclipse 3.0.

public class WorkbenchHelpSystem30
{
   private WorkbenchHelpSystem30() {}    // No instances

   public static IWorkbenchHelpSystem getHelpSystem() {
      return new IWorkbenchHelpSystem() {
         public boolean hasHelpUI() {
            return true;
         }
         public void displayHelp() {
            WorkbenchHelp.displayHelp();
         }
         public void displaySearch() {
             // no corresponding function in Eclipse 3.0
         }
         ... etc ...
      };
   }
}

Another difference you will encounter in the FavoritesView involves trees and columns. Eclipse 3.1 added support for columns in the tree control, but Eclipse 3.0 does not have this support. Backporting functionality into SWT is tricky at best and involves platform-specific code so that option is out. Another option would be to modify the FavoritesView to use the Table-Tree controla precursor to tree columns available in Eclipse 3.0 and 3.1 (and 3.2) but deprecated in Eclipse 3.1 (and 3.2). In this case, use preprocessor code to use tree in Eclipse 3.1 and TableTree in 3.0. To minimize the amount of preprocessor code, method references and variable types are generalized whenever possible. For example, wherever you can, replace

viewer.getTree()

with

viewer.getControl()

Since you are using treeViewer in Eclipse 3.1 and TableTreeViewer in Eclipse 3.0, replace specific local variable declarations in RemovePropertiesOperation such as

TreeViewer viewer

with more general declarations that work with both TReeViewer and TableTreeViewer:

AbstractTreeViewer viewer

There is much more of the same to get the Favorites product working in Eclipse 3.0 as well as 3.1 including backporting NLS, help system, and undo framework support that are not covered here. In general we use the following approaches to smooth out differences between versions of Eclipse.

  • Backporting functionality using plug-ins and fragments.

  • Older deprecated API in newer versions of Eclipse.

  • More generalized accessors and variable declarations.

  • Preprocessor comments when all else fails.

This process of retargeting the ECLIPSE_HOME classpath variable only needs to be performed once per project to get the code to compile against a different version of Eclipse. To support multiple versions in an ongoing manner, simply code against one version of Eclipse, and then build the product to make sure that it compiles against the others, adding preprocessor statements only as necessary. In practice, this is not a big deal as only a fraction of your code base will include any preprocessor statements (typically less than one percent of the files).

The final step is to run the code through the preprocessor before it is compiled. Required preprocessor properties have already been added to the product_init target described in Section 19.2.2.2, Build initialization, on page 679; and the source before compilation in the compile_lib macro was translated in Section 19.2.3, Compiling during the build process, on page 689.

19.2.7. Classpath tools

The eclipsetools.getclasspath task introduced in Section 19.2.3, Compiling during the build process, on page 689 can modify the classpath for different versions of Eclipse after it retrieves the classpath. The ${build.map} property defined in Section 19.2.2.2, Build initialization, on page 679 points to a .buildmap file that specifies the differences between the Eclipse 3.0 and 3.1 classpaths.

<?xml version="1.0" encoding="UTF-8"?>
<buildmap>
   <config name="eclipse_3.2" marker="ECLIPSE32_HOME">
      <replace variable="ECLIPSE_HOME"
               withVariable="ECLIPSE32_HOME" />
   </config>
   <config name="eclipse_3.1" marker="ECLIPSE_HOME">
      <replace variable="ECLIPSE30_HOME"
               withVariable="ECLIPSE_HOME" />
    </config>

    <config name="eclipse_3.0" marker="ECLIPSE30_HOME">
       <replace variable="ECLIPSE21_HOME"
                withVariable="ECLIPSE30_HOME" />
    </config>
    <config name="eclipse_2.1" marker="ECLIPSE21_HOME">
       <replace variable="ECLIPSE20_HOME"
                withVariable="ECLIPSE21_HOME" />
    </config>
</buildmap>

The preceding .buildmap file defines the modifications necessary to convert the Favorites product Eclipse 3.1 classpath to a classpath compatible with other versions of Eclipse. Each configuration contains instructions for converting a classpath from an older version to a newer version of Eclipse. For example, the "eclipse_3.2" configuration specifies that the ECLIPSE_HOME classpath variable should be replaced with the ECLIPSE32_HOME classpath variable when converting an older classpath to Eclipse 3.2. When converting a classpath from a newer to an older version of Eclipse, the eclipsetools.getclasspath Ant task reverses the process.

The config element's marker attribute provides a way for the eclipsetools.getclasspath Ant task to determine which configuration is the current one. By scanning the specified project's classpath and looking for the use of classpath variables matching the specified markers, the Ant task can automatically determine which is the current configuration for a project.

The config element's name attribute provides a way to reference that configuration from within the build script. When retrieving the Favorites project's classpath in the not yet existing build_30 target, reference the "eclipse_3.0" configuration. The eclipsetools.getclasspath Ant task determines the differences between the "eclipse_3.1" and "eclipse_3.0" configurations, replaces the ECLIPSE_HOME classpath variable with the ECLIPSE30_HOME classpath variable and finally adjusts the results classpath elements to take plug-in version changes into account.

The configuration's subelements specify the changes necessary to convert a classpath for one version of Eclipse to a classpath for another version. The replace element can specify that a variable should be replaced withVariable or a project should be replaced withProject.

Download the getclasspath Task

The <eclipsetools.getclasspath> task just mentioned is not part of Ant or Eclipse, but is an Ant extension we wrote. We needed it to build our products, and it is freely available for anyone to use at www.qualityeclipse.com/ant/getclasspath.


19.2.8. Building against Eclipse 3.0

With the preprocessor and classpath tools in place, you are ready to build the Favorites project for Eclipse 3.0 by fleshing out the build_3.0 target in the Favorites project's build-bundle.xml file. In Eclipse 3.0, plug-ins must be directories, not JAR files, so the classes comprising the Favorites plug-in must be compiled and placed in a favorites.jar file (rather than in the plug-in JAR file). The classpath must also include all the backported projects and fragments for successful compilation. Add the following to the build_3.0 target.

   <compile_lib target="3.0" lib="favorites.jar" extraClasspath="
      ../com.qualityeclipse.e30f.core.runtime/bin;
      ../com.qualityeclipse.e30f.ui.workbench/bin;
      ../com.qualityeclipse.e30p.core.commands/bin;
      ../com.qualityeclipse.e30p.ui.workbench.commands/bin" />

This references a new compile_lib macro to be added to the build-macros.xml file. This convenience macro combines two other earlier defined macros.

<macrodef name="compile_lib">
   <attribute name="target" />
   <attribute name="source" default="src" />
   <attribute name="lib" />
   <attribute name="extraClasspath" default="" />
   <attribute name="debug" default="on" />
   <sequential>
      <compile_dir target="@{target}"
                   source="@{source}"
                   extraClasspath="@{extraClasspath}"
                   debug="@{debug}" />

      <jar_lib target="@{target}" lib="@{lib}">
        <fileset dir="${build.temp}/@{target}/${Bundle-Proj}/bin" />
      </jar_lib>
   </sequential>
</macrodef>

The META-INF/MANIFEST.MF file for Eclipse 3.1 does not contain a classpath declaration because the plug-in is packaged as a single JAR file. In Eclipse 3.0, the Bundle-ClassPath specification must be reintroducted so that Eclipse 3.0 can find the Favorites plug-in's classes. You must also add the backported plug-ins to the Require-Bundle specification. Because you are modifying the META-INF/MANIFEST.MF file, it must be removed from the build configuration (see Figure 19-8 on page 685) and explicitly included in both the build_3.0 and build_3.1 targets.

The following addition to the build_3.0 target modifies the META-INF/ MANIFEST.MF and copies it to ${build.temp}/3.0/${Bundle-Proj}/out so that the dir_bundle macro discussed later will include it in the build (the ==> in the code below denotes a continuation of the previous line and must not be included but lines that do not start with ==> must be entered as they appear).

    <mkdir dir="${build.temp}/3.0/${Bundle-Proj}/out" />
    <copy todir="${build.temp}/3.0/${Bundle-Proj}/out">
       <fileset dir="." includes="META-INF/MANIFEST.MF" />
    </copy>
    <replace file="${build.temp}/3.0/${Bundle-Proj}/out
==>                                           /META-INF/MANIFEST.MF">
      <replacetoken>Bundle-Localization: plugin</replacetoken>
      <replacevalue>Bundle-Localization: plugin
Bundle-ClassPath: favorites.jar</replacevalue>
   </replace>
   <replace file="${build.temp}/3.0/${Bundle-Proj}/out
==>                                           /META-INF/MANIFEST.MF">
      <replacetoken> org.eclipse.ui.ide,</replacetoken>
      <replacevalue> org.eclipse.ui.ide,
  com.qualityeclipse.e30p.core.commands,
  com.qualityeclipse.e30p.ui.workbench.commands,</replacevalue>
    </replace>

Finally, the build_3.0 target calls the dir_bundle macro.

<dir_bundle target="3.0" type="plugins" />

The dir_bundle macro located in the build-macros.xml file is very similar to the jar_bundle macro discussed earlier except that the bundle's content is placed into a directory rather than a JAR file (the ==> in the code below denotes a continuation of the previous line and must not be included).

<macrodef name="dir_bundle">
   <attribute name="target" />
   <attribute name="type" />
   <attribute name="id" default="${Bundle-Id}" />
   <attribute name="version" default="${Bundle-Version}" />
   <element name="content" implicit="true" optional="true" />
   <sequential>
      <mkdir dir="${build.temp}/@{target}/out/@{type}
==>                                          /@{id}_@{version}" />
      <mkdir dir="${build.temp}/common/${Bundle-Proj}/out" />
      <mkdir dir="${build.temp}/@{target}/${Bundle-Proj}/out" />
      <copy todir="${build.temp}/@{target}/out/@{type}
==>                                           /@{id}_@{version}">
         <fileset dir="." includes="${Bundle-CommonFiles}" />
         <fileset dir="${build.temp}/common/${Bundle-Proj}/out" />
         <fileset dir="${build.temp}/@{target}/${Bundle-Proj}/out" />
         <content />
      </copy>
   </sequential>
</macrodef>

The other projects are built in a similar fashion but not covered here. The backported plug-ins and fragments are not needed for Eclipse 3.1 and thus the build_3.1 target in their build-bundle.xml files are empty, but must appear first in the build_bundles macro discussed in Section 19.2.2.3, Building multiple projects, on page 681.

19.2.9. Retargeting source code

Using the preprocessor approach outlined in the previous sections involves source targeted at one version, but generating binaries for other versions of Eclipse. In other words, the code contains some source that is surrounded by preprocessor statements, but uncommented, for the targeted version of Eclipse, while source for the other versions of Eclipse is commented out. At some point, you'll want to change the targeted version of Eclipse from, say, Eclipse 3.0 to Eclipse 3.1. To accomplish this involves using the preprocessor itself to regenerate your source and the introduction of a new classpath tool.

The first step when retargeting the code from Eclipse 3.0 to Eclipse 3.1 involves running the preprocessor over the code to produce revised source that targets the newer version of Eclipse. This is what happens every time the build process runs, only this time, you are taking the result and checking the revised version back into the repository as the basis for new development. The capability to generate multiple binaries, one for each version of Eclipse, is not lost, only the targeted version of Eclipse has changed.

The second step involves updating the projects' Java build paths by modifying the .classpath file (see the end of Section 19.2.5, Editing with different versions of Eclipse, on page 692) to use the appropriate classpath variable.

For example, if you are moving from development in Eclipse 3.0 to Eclipse 3.1, replace all occurrences of ECLIPSE30_HOME with ECLIPSE31_HOME. In addition, you'll need to update the plug-in's directory suffix in the .classpath file to match the target version of Eclipse (e.g., plugins/org.eclipse.swt_3.0.1 changes to plugins/org.eclipse.swt_3.1.0).

Tip

A tool for automatically adjusting the classpath of a project between versions of Eclipse is available on the QualityEclipse Web site (www.qualityeclipse.com/tools/classpath).


19.2.10. Version checking

Having one product binary for each version of Eclipse solves one problem while causing another: What if the user installs the wrong binary for the version of Eclipse he or she is using?

To solve this, you can add version checking to the plug-in startup method to compare the version of Eclipse for which the binary was produced with the version of Eclipse on which the code is currently executing. If the versions don't match, the code immediately informs the user.

public static final PluginVersionIdentifier IDE_VERSION_EXPECTED =
   /* $if version == 3.1 $ */
      new PluginVersionIdentifier(3, 1, 0);
   /* $elseif version == 3.0 $
      new PluginVersionIdentifier(3, 0, 0);
   $endif $ */

public static final PluginVersionIdentifier IDE_VERSION_ACTUAL =
   new PluginVersionIdentifier(
      (String) ResourcesPlugin.getPlugin().getBundle().getHeaders()
         .get(org.osgi.framework.Constants.BUNDLE_VERSION));

public static boolean isValidProductVersionForIDE() {
   return
        IDE_VERSION_EXPECTED.getMajorComponent()
        == IDE_VERSION_ACTUAL.getMajorComponent()
   &&
         IDE_VERSION_EXPECTED.getMinorComponent()
        == IDE_VERSION_ACTUAL.getMinorComponent();
}

This code can be easily added to the FavoritesPlugin class. Once that is in place, you can modify the createPartControl(Composite) method in the FavoritesView by adding the following code to be the beginning of the method.

if (!FavoritesPlugin.isValidProductVersionForIDE()) {
   Label label = new Label(parent, SWT.NONE);
   label.setText(
      "This Favorites binary is compiled for "
          + FavoritesPlugin.IDE_VERSION_EXPECTED
          + " but being executed on "
          + FavoritesPlugin.IDE_VERSION_ACTUAL);
   return;
}

Now, if the user installs the Favorites product binary compiled for Eclipse 3.1 into Eclipse 3.0, he or she will see the message in the Favorites view as shown in Figure 19-15.

Figure 19-15. The Favorites view containing an IDE version message.


19.2.11. Building for internationalization

If you are targeting an international market and have generated a language-specific fragment project (see Section 16.3, Using Fragments, on page 587), you need to generate separate deliverables containing language-specific code and resources. For the Favorites product, generate two new files:

  • Favorites_v1.0.0_for_Eclipse3.1.zip

  • Favorites_v1.0.0_for_Eclipse3.0.zip

These files contain the contents of the com.qualityeclipse.favorites.nl1 project. Customers desiring language support for French and German would install this language-specific ZIP file in the same location as the main product.

To accomplish all this, modify the zip_product macro discussed in Section 19.2.2.3, Building multiple projects, on page 681 to exclude the com.qualityeclipse.favorites.nl1 project from the main zip and place it into a separate zip file (the ==> in the code below denotes a continuation of the previous line and must not be included).

<zip destfile="${build.out}/${product.name}_v${product.version}
==>                                      _for_Eclipse@{target}.zip">
   <zipfileset dir="${build.temp}/common/out"
               prefix="@{prefix}" />
   <zipfileset dir="${build.temp}/@{target}/out"
               prefix="@{prefix}" >
      <exclude
         name="plugins/com.qualityeclipse.favorites.nl1_*.jar" />
      <exclude
         name="plugins/com.qualityeclipse.favorites.nl1_*/**/*.*" />
   </zipfileset>
</zip>
<zip destfile="${build.out}/${product.name}_I18N_v${product.version}
==>                                        _for_Eclipse@{target}.zip">
    <zipfileset dir="${build.temp}/@{target}/out"
                prefix="@{prefix}" >
       <include
          name="plugins/com.qualityeclipse.favorites.nl1_*.jar" />
       <include
         name="plugins/com.qualityeclipse.favorites.nl1_*/**/*.*" />
    </zipfileset>
</zip>


Previous Page
Next Page