Where is the Platform and How Can I Build It?

Apache NetBeans Wiki Index

Note: These pages are being reviewed.

In versions of NetBeans prior to 6.0, two major products were available for download: the IDE and the platform. The platform is the foundation on which the IDE is built, or looking at it another way, the platform is what’s left over when you remove all the IDE features from the IDE. At any rate, the platform provides user interface components, build scripts, declarative configuration and many other features that can save you a lot of time and effort in creating your own application.

Because platform-based applications are themselves platforms that can be extended, the IDE can also be extended just as the platform can. Since you can remove features from a platform as well as add new ones, the availability of the platform and IDE let you choose between starting small and adding on (platform) or starting large and removing things (the IDE). Some feel the latter approach is better and even facing such a choice can be confusing to new users. If you’re a new user, you’d do well to heed this advice and just use the IDE as a platform. It works just as well and is a lot less trouble.

But if you’re still here, you may be asking where is the platform? Binary distributions of the platform are not being made available from version 6.0 onward (and issue #124372 filed to bring them back was closed without any reasonable explanation). So if you want a platform binary, you’ll have to create one yourself.

Building the platform is not difficult, but it’s not intuitive either. To start, you will need to download the platform source ZIP file and unpack it to some directory. Open a command prompt to that directory and change to the <pre>nbbuild</pre> subdirectory. From there, issue the following command:

   ant -Dcluster.config=platform build-platform

If you’re using Java 6, you’ll need to add an extra property:

   ant -Dcluster.config=platform build-platform -Dpermit.jdk6.builds=true

But be aware that it is not guaranteed to build under Java 6 due to language changes or compiler bugs. It is unlikely you will encounter such a problem in the platform build, though it has certainly been known to happen in the IDE build. If you find something that won’t compile under Java 6 but does compile under Java 5, file a bug report (preferably with a patch) about it so it can be corrected. Meanwhile, you can use Java 5 to compile — even when Java 6 is first in your path — by using the nbjdk.home system property to point to your Java 5 installation:

   ant -Dcluster.config=platform build-platform -Dnbjdk.home=c:/devtools/jdk/jdk-1.5.0_u15

This will build the platform into the <pre>netbeans</pre> subdirectory (i.e. {nbbuild/netbeans}). You can zip or tar up the netbeans directory to create a ZIP distribution.

It’s also possible to create platforms based on a different subset of the NetBeans project. Hints for doing this can be found here:

Why would you want to build your application on a separate platform instead of the IDE as a platform?

Using the IDE is certainly easier, but there are inherent dangers associated with developing against your own IDE as the platform. In particular, another developer on your team may have a different version of the IDE, have different modules/clusters installed or even have simply named the platform something different in the Platform Manager. This can result in a broken build or the introduction of unwanted features. It also makes doing an automated build, such as through Hudson or CruiseControl, far more difficult.

If you want to avoid these problems, you can check the platform you want to build against into source control and then set the netbeans.dest.dir and harness.dir properties in your suite’s nbproject/platform.properties file to point to the platform and harness, respectively. Building from a known version checked out from source control avoids these problems and makes it possible to historically reproduce any build. I show example values for these below:

# NOTE: You must remove the nbplatform.default line which might already exist in this file.
# Also note that editing the properties of your suite via the suite customizer (dialog)
# can add that line back in, so you'll need to watch for this and delete it again in this case.

# where the suite is located; you don't need to change this.  It exists
# to allow us to use relative paths for the other values
suite.dir=${basedir}

# the path to the NetBeans IDE or platform binary we want to build against
# (e.g. if building against the IDE, this points to the directory created when
# you unpack the IDE zip file).  this example assumes your platform directory
# is parallel to the suite directory, but you can change it to suit your needs
netbeans.dest.dir=${suite.dir}/../platform

# path to the build harness you want to use.  This is typically in the
# harness subdirectory of your platform, but you could point to a directory
# containing customized build scripts if you want to.
harness.dir=${netbeans.dest.dir}/harness

Update for NBM projects generated by NetBeans 6.7 and later

If you have generated your projects in IDE version 6.7 and later, you have to modify the above described method slightly (6.5.1 and earlier projects compile against newer platform/harness without changes). You can distinguish "newer" project by the presence of cluster.path property in nbproject/platform.properties file or simply by the fact that an attempt to build a suite with above described platform.properties results in error:

.../harness/suite.xml:60: When using cluster.path property, remove
netbeans.dest.dir, enabled.clusters and disabled.clusters properties
from platform config, they would be ignored.

In such case you have to replace netbeans.dest.dir, enabled.clusters and disabled.clusters properties with new property cluster.path, e.g.:

# NOTE: You must remove the nbplatform.default line which might already exist in this file.
# Also note that editing the properties of your suite via the suite customizer (dialog)
# can add that line back in, so you'll need to watch for this and delete it again in this case.

# where the suite is located; you don't need to change this.  It exists
# to allow us to use relative paths for the other values
suite.dir=${basedir}

# just a helper property pointing to the same location as netbeans.dest.dir did before;
# Referenced only in this properties file, has no meaning for NB harness.
platform.base=${suite.dir}/../platform

# Give a name to the platform at the relative path and define its location
# using the platform.base property we set above. You can change the value
# ('myplatform') to something more descriptive (like 'nb68'), but you
# must then change the name of the second property (e.g. from
# nbplatform.myplatform.netbeans.dest.dir to nbplatform.nb68.netbeans.dest.dir)
nbplatform.active=myplatform
nbplatform.myplatform.netbeans.dest.dir=${platform.base}

# classpath-like list of absolute or relative paths to individual clusters
# against which you want your suite to build; Note that you can use
# "bare", i.e. not numbered cluster names, which simplifies later transitions
# to newer version of the platform. E.g:
cluster.path=${platform.base}/platform:\
     ${platform.base}/ide:\
     ../otherSuite/build/cluster

# path to the build harness you want to use.  This is typically in the
# harness subdirectory of your platform, but you could point to a directory
# containing customized build scripts if you want to.
harness.dir=${platform.base}/harness

Note that the content of cluster.path is not limited to clusters from NB platform, you can add clusters from other suites, standalone modules, etc. This allows to reuse non-platform modules in several RCP apps. More on module reuse here, other details about setting up cluster.path can be found in harness/README.

Update for NBM projects generated by NetBeans 7.0 and later

Now the the platform can get downloaded automatically with some minor tweaks! This is great for usage in Continuous Integration servers like Hudson/Jenkins.

See here for more details.

Some automation anyone?

The above process is basically manual so here are some stuff I developed to automate the process:

Update the development environment

The following allows to update the development environment mentioned above that should be part of version control. (i.e. to make it work from Hudson for example)

  • Add a xml file in the suite’s root (referred as preparation.xml from now on)

Hare are its contents:

<?xml version="1.0" encoding="UTF-8"?>
<project name="XXX-Preparation" basedir=".">
    <description>Prepares the environment to build the module suite XXX.</description>
    <!--Don't modify this file unless you know what you are doing-->
    <property name="ant-contrib-filename" value="ant-contrib-1.0b3.jar"/>
    <property file="nbproject/project.properties"/>
    <property file="nbproject/platform.properties"/>

    <target name="update-platform" depends="init-netbeans">
        <for list="${cluster.path}" delimiter=":" param="cur" trim="true">
            <sequential>
                <add-core-module module="@{cur}"/>
            </sequential>
        </for>
    </target>

    <target name="unzip-compilation-env" depends="init-netbeans, init-hudson">
        <!--Hudson needs to run this task first as it gets the core modules as zip from version control-->
        <for list="${cluster.path}" delimiter=":" param="cur" trim="true">
            <sequential>
                <expand-module module="@{cur}"/>
            </sequential>
        </for>
    </target>

    <target name="update-env" depends="init-netbeans, init-hudson" description="Update the Netbeans core modules used to compile/run OIT">
        <!--Make sure that any recently added module using the IDE is also included.
        Fix it to the proper format.-->
        <mkdir dir="../netbeans/"/>
        <propertyregex property="cluster.path"
               input="${cluster.path}"
               regexp="nbplatform.active.dir"
               replace="platform.base"
               global="true"
               override="true"/>
        <replaceregexp file="nbproject/platform.properties"
                       match="nbplatform.active.dir"
                       replace="platform.base"
                       byline="true"
                       flags="g,s"/>
        <pathconvert pathsep="\;" property="folders_temp">
            <dirset dir="../netbeans/">
                <include name="*/**"/>
                <!--ignore svn and cvs files-->
                <include name="**/.svn"/>
                <include name="**/.svn/**"/>
                <include name="**/CVS"/>
                <include name="**/CVS/**"/>
                <!--Exclude the nb-plugins folder-->
                <exclude name="nb-plugins/**"/>
                <!--Exclude the root folder-->
                <exclude name="../netbeans"/>
            </dirset>
        </pathconvert>
        <antcall target="update-platform"/>
        <antcall target="unzip-compilation-env"/>
    </target>

    <macrodef name="expand-module">
        <attribute name="module"/>
        <sequential>
            <delete dir="@{module}"/>
            <unzip src="@{module}.zip" dest="@{module}"/>
        </sequential>
    </macrodef>

    <macrodef name="add-core-module">
        <attribute name="module"/>
        <sequential>
            <if>
                <equals arg1="@{module}" arg2="../netbeans/nb-plugins"/>
                <then>
                    <echo>Adding custom module @{module}</echo>
                    <available file="@{module}" type="dir" property="customdir.exists"/>
                    <if>
                        <equals arg1="${customdir.exists}" arg2="true"/>
                        <then>
                            <zip destfile="@{module}.zip" basedir="@{module}" update="true"/>
                        </then>
                    </if>
                </then>
                <else>
                    <length string="@{module}" property="@{module}.length.module" />
                    <substring text="@{module}" start="12" end="${@{module}.length.module}" property="new.module"/>
                    <echo>Adding netbeans core module ${new.module}</echo>
                    <mkdir dir="../netbeans/${new.module}/"/>
                    <delete file="../netbeans/${new.module}.zip"/>
                    <delete includeemptydirs="true">
                        <fileset dir="../netbeans/${new.module}/" includes="**/.*" defaultexcludes="false"/>
                    </delete>
                    <zip destfile="../netbeans/${new.module}.zip" basedir="${netbeans.home}\..\${new.module}" update="true"/>
                </else>
            </if>
        </sequential>
    </macrodef>

    <scriptdef name="substring" language="javascript">
        <attribute name="text" />
        <attribute name="start" />
        <attribute name="end" />
        <attribute name="property" />
     <![CDATA[
       var text = attributes.get("text");
       var start = attributes.get("start");
       var end = attributes.get("end") || text.length;
       project.setProperty(attributes.get("property"), text.substring(start, end));
     ]]>
    </scriptdef>

    <target name="check-env" depends="getAntContribJar">
        <condition property="isNetbeans">
            <not>
                <isset property="Hudson"/>
            </not>
        </condition>
    </target>

    <target name="getAntContribJar">
        <fileset id="ant-contrib-jar" dir="${suite.dir}/tools">
            <include name="ant-contrib-*.jar" />
        </fileset>
        <pathconvert property="ant-contrib-jar" refid="ant-contrib-jar" pathsep="," />
        <basename property="ant-contrib-filename" file="${ant-contrib-jar}"/>
    </target>

    <target name="init-netbeans" depends="check-env" if="isNetbeans">
        <echo>Configuring ant-contrib for Netbeans use...</echo>
        <property name="ant-contrib-loc" value="${suite.dir}/tools/${ant-contrib-filename}"/>
        <available file="${ant-contrib-loc}" property="ant-contrib.present"/>
        <fail unless="ant-contrib.present" message="The ant-contrib jar doesn't exist at: ${ant-contrib-loc}, can't build. Check your settings!" />
        <!--We are in not Hudson-->
        <taskdef resource="net/sf/antcontrib/antcontrib.properties">
            <classpath>
                <pathelement location="${ant-contrib-loc}"/>
            </classpath>
        </taskdef>
    </target>

    <target name="init-hudson" depends="check-env" unless="isNetbeans">
        <echo>Configuring ant-contrib for Hudson use...</echo>
        <!--Import Hudson environment variables-->
        <property environment="env"/>
        <property name="ant-contrib-loc" value="${env.ANT_HOME}/lib/${ant-contrib-filename}"/>
        <available file="${ant-contrib-loc}" property="ant-contrib.present"/>
        <fail unless="ant-contrib.present" message="The ant-contrib jar doesn't exist at: ${ant-contrib-loc}, can't build. Check your settings!" />
        <!--Define it. For some reason the approach in init-netbeans doesn't work in Hudson.-->
        <taskdef name="for" classname="net.sf.antcontrib.logic.ForTask">
            <classpath>
                <pathelement location="${ant-contrib-loc}"/>
            </classpath>
        </taskdef>
        <taskdef name="propertyregex" classname="net.sf.antcontrib.property.RegexTask">
            <classpath>
                <pathelement location="${ant-contrib-loc}"/>
            </classpath>
        </taskdef>
        <taskdef name="if" classname="net.sf.antcontrib.logic.IfTask">
            <classpath>
                <pathelement location="${ant-contrib-loc}"/>
            </classpath>
        </taskdef>
        <taskdef name="math" classname="net.sf.antcontrib.math.MathTask">
            <classpath>
                <pathelement location="${ant-contrib-loc}"/>
            </classpath>
        </taskdef>
        <taskdef name="var" classname="net.sf.antcontrib.property.Variable">
            <classpath>
                <pathelement location="${ant-contrib-loc}"/>
            </classpath>
        </taskdef>
    </target>
</project>

Here’s a sumary of the targets and what they do:

  • init-netbeans/init-hudson: Configures the ant-contrib lib used in other tasks. For some reason Hudson doesn’t work with the init-netbeans approach.

  • getAntContribJar: Looks in the suite’s tools folder for the ant-contrib jar file. This file name is then used by other tasks

  • check-env: Basically to decide if we’re in Netbeans or in Hudson. While in Hudson just pass the -DHudson=true parameter to the ant job. Having this variable set (not the value) tells this task that we are in Hudson.

  • update-env: The task to call. This one updates the cluster.path values in nbproject/platform.properties to set it up as mentioned in this FAQ. Why you might ask? This just takes care of updating any later addition of a module via using Netbeans and converts it to the format discussed in this FAQ. Basically no need to manually modify the nbproject/platform.properties file after the initial change!

  • update-platform: This will grab the current’s IDE modules defined in cluster.path and zip them in a netbeans folder parallel to the suite’s root folder. No need to do it manually!

  • unzip-compilation-env: this unzips the zips created in the above task to their proper place.

Keep in mind that after making the changes proposed earlier in this FAQ the project won’t work (i.e. build, run, etc) if the environment is not set.

That’s the reason of doing all this in another xml file. Attempting any of this from the suite’s build file won’t work since you are messing with the platform files it is working from.

Notes:

  • Make sure to have an ant-contrib file in <suite’s root>/tools folder for the above to work.

  • Current release of ant-contrib has an error. To fix it unpack the jar and add this entry to the net/sf/antcontrib/antcontrib.properties file in the Logic tasks section:

for=net.sf.antcontrib.logic.ForTask

See also: