Configure Ivy to dynamically download and install Ant extensions

We all know that Apache Ant is a popular Java build management system. But, in many cases Ant alone is not enough for extended build tasks and we have to install Ant extensions or plugins. For example, tasks like – “loop through all the files available in a directory”, can be done quite easily using Ant-Contrib extentions. For this, we have to download ant-contrib.jar to a directory, say /usr/share/ant-extensions/lib and create a task definition in the Ant project file.

<taskdef resource="net/sf/antcontrib/antlib.xml">
    <classpath>
        <pathelement location="/usr/share/ant-extensions/lib/ant-contrib.jar"/>
  </classpath>
</taskdef>

Complete Ant project file may look like:-

<project name="List files" default="list-files" basedir=".">
    <property name="ant-contrib-file" value="/usr/share/ant-extensions/lib/ant-contrib-1.0b3.jar"/>
    <property name="dir-to-list" value="/usr/share/libs"/>
 
    <target name="load-ant-contrib" description="Load ant-contrib Ant tasks">
        <taskdef resource="net/sf/antcontrib/antlib.xml">
            <classpath>
                <pathelement location="${ant-contrib-file}"/>
            </classpath>
        </taskdef>
        <property name="ant.contrib.loaded" value="true"/>
    </target>
 
    <target name="list-files" depends="load-ant-contrib" description="List jar files in a directory">
        <for param="file">
            <fileset dir="${dir-to-list}" includes="*.jar"/>
            <sequential>
                <echo message="@{file}"/>
            </sequential>
        </for>
    </target>
</project>

In the above example, we have to manually manage the dependencies and their versions. Well, there is a better solution using Apache Ivy dependency manager wherein Ivy can automatically download Ant dependencies from maven repository. We just have to specify the name of the dependency along with it’s version in the Ant project file.

Ivy module dependencies can be described in an xml file called ivy.xml. ivy.xml file contains the description of the dependencies of a module, its published artifacts and its configurations. Sample ivy.xml file with only ant-contrib dependency may look like:-

<ivy-module version="2.0">
    <info organisation="my-org" module="my-module1"/>
    <dependencies>
        <dependency org="ant-contrib" name="ant-contrib" rev="1.0b3"/>
  </dependencies>
</ivy-module>

We can also create this xml using echoxml Ant task:-

<echoxml file="ivy.xml">
    <ivy-module version="2.0">
        <info organisation="my-org" module="my-module1"/>
        <dependencies>
            <dependency org="ant-contrib" name="ant-contrib" rev="1.0b3"/>
        </dependencies>
    </ivy-module>
</echoxml>

We can specifiy multiple dependencies in ivy.xml and Ivy will make that dependencies available in the Ant task. The only jar file we have to include in the Ant task is ivy.jar, all other dependencies will be managed automatically by Ivy. We can also download ivy.jar dynamically using the Ant get Task:-

<get src="http://repo1.maven.org/maven2/org/apache/ivy/ivy/2.4.0/ivy-2.4.0.jar"
     dest="${user.home}/.ivy2/jars"
     usetimestamp="true"/>

At this point Ivy module dependencies has been configured. Ivy provides retrieve task, which then reads from ivy.xml and copies resolved dependencies in configurable file system location. Configure artifactproperty followed by retrieve task which then sets loaded dependency in Ant property, which will be required to load dependencies using taskdef Ant task. retrieve and artifactproperty task can be configured as:-

<ivy:retrieve/>
<ivy:artifactproperty name="[module].[artifact]" value="lib/[artifact]-[revision].[ext]"/>

The ant-contrib jar file will be loaded in Ant property ant-contrib.ant-contrib. Ivy not needed after this, now you can just load ant-contrib task using taskdef Ant task like this:-

<taskdef resource="net/sf/antcontrib/antlib.xml">
    <classpath>
        <pathelement location="${ant-contrib.ant-contrib}"/>
    </classpath>
</taskdef>

With all the above things tied in together, the complete Ant(v1.9.4) project file looks like:-

<project name="Ivy dependency example" default="list-files" basedir="." xmlns:ivy="antlib:org.apache.ivy.ant">
    <property name="ivy.install.version" value="2.4.0"/>
    <property name="ivy.jar.dir" location="${user.home}/.ivy2/jars"/>
    <property name="ivy.jar.file" location="${ivy.jar.dir}/ivy-${ivy.install.version}.jar"/>
    <property name="dir-to-list" value="/usr/share/libs"/>
 
    <target name="download-ivy" unless="ivy.downloaded" description="Download Ivy jar files">
        <mkdir dir="${ivy.jar.dir}"/>
        <get src="http://repo1.maven.org/maven2/org/apache/ivy/ivy/${ivy.install.version}/ivy-${ivy.install.version}.jar"
             dest="${ivy.jar.file}"
             usetimestamp="true"/>
    </target>
 
    <target name="check-ivy-downloaded" description="Check if Ivy is already downloaded">
        <condition property="ivy.downloaded">
            <and>
                <available file="${ivy.jar.file}"/>
            </and>
        </condition>
    </target>
 
    <target name="load-ivy" depends="check-ivy-downloaded,download-ivy" unless="ivy.loaded" description="Load Ivy Ant tasks">
        <path id="ivy.lib.path">
            <fileset dir="${ivy.jar.dir}" includes="*.jar"/>
        </path>
        <taskdef resource="org/apache/ivy/ant/antlib.xml" uri="antlib:org.apache.ivy.ant" classpathref="ivy.lib.path"/>
                <echoxml file="ivy.xml">
                        <ivy-module version="2.0">
                                <info organisation="my-org" module="my-module1"/>
                                <dependencies>
                                        <dependency org="ant-contrib" name="ant-contrib" rev="1.0b3" transitive="false"/>
                                </dependencies>
                        </ivy-module>
                </echoxml>
        <property name="ivy.loaded" value="true"/>
    </target>
 
    <target name="retrieve" depends="load-ivy" description="Retrieve dependencies">
        <ivy:retrieve/>
        <ivy:artifactproperty name="[module].[artifact]" value="lib/[artifact]-[revision].[ext]"/>
    </target>
 
    <target name="load-ant-contrib" depends="retrieve" unless="ant.contrib.loaded" description="Load ant-contrib Ant tasks">
        <taskdef resource="net/sf/antcontrib/antlib.xml">
            <classpath>
                <pathelement location="${ant-contrib.ant-contrib}"/>
            </classpath>
        </taskdef>
        <property name="ant.contrib.loaded" value="true"/>
    </target>
 
    <target name="list-files" depends="load-ant-contrib" description="List jar files in a directory">
        <for param="file">
            <fileset dir="${dir-to-list}" includes="*.jar"/>
            <sequential>
                <echo message="@{file}"/>
            </sequential>
        </for>
    </target>
</project>

Adding Bower to ASP .NET Project – Visual Studio Code

This is the third article in my series of using Visual Studio Code to create ASP .NET MVC Projects. Here we will be adding support for bower packages.

The default code generated by dotnet new mvc makes use of the following JavaScript libraries:-

Recall, from my previous article on adding the files to Git that I ignored these files via .gitignore. But, when you run your application in production by compiling the source, packaging it and deploying it, these files will be missing as they were not committed to Git. Instead, these must be downloaded from the Bower repository.

So, let us add a bower.json to the root first.

{
  "name": "hello-world-project",
  "private": true,
  "dependencies": {
    "bootstrap": "3.3.7",
    "jquery": "2.2.0",
    "jquery-validation": "1.14.0",
    "jquery-validation-unobtrusive": "3.2.6"
  }
}

Normally bower installs the packages to bower_components folder. But, in ASP .NET Core these static files should go inside wwwroot/lib so that they are publicly available. So, we need to create a .bowerrc file in the root with the following content:-

{
  "directory": "wwwroot/lib"
}

Let us test out now that bower is able to install these pacakages we declared as dependencies for our project.

So, first delete all files & folders under wwwroot/lib. Now, go to the terminal Ctrl + ` and do a bower install.

bower : The term 'bower' is not recognized as the name of a cmdlet, function, script file, or operable program.

This is because we don’t have bower installed on this virgin Virtual Machine (VVM, that sounds like a genre for something).

Trying to install bower using npm would fail as well because we don’t have NodeJS installed. Should I have mentioned this in the pre-requisites category? Nah, sometimes I like things to fail so that you get a deeper understanding of the inner workings.

So, why do we need bower? Because we are building an ASP .NET application which will have some frontend JavaScript libraries. Bower is a repository/package manager for frontend JavaScript libraries. Bower itself is written in NodeJS so we need that too.

This is key here. We have chosen ASP .NET Core for building our Web Application, yet we need the alternative to it – NodeJS (which by the way we are not going to use to run our web application). I hope the dependency is clear.

So, head over to NodeJS’s website and download the latest stable version. Once you have installed it, restart VS Code because even though NodeJS has been installed and added to the path, the commands node and npm will not be available to you until you reload VSCode.

Again go back to the VSCode terminal and try executing npm -v. This time your command should succeed and you should get an output such as 5.6.0, depending upon your version of node.

Install bower globally using npm install -g bower. You will get an output such as this:-

npm WARN deprecated bower@1.8.2: ...psst! Your project can stop working at any moment because its dependencies can change. Prevent this by migrating to Yarn: https://bower.io/blog/2017/how-to-migrate-away-from-bower/
C:\Users\Administrator\AppData\Roaming\npm\bower -> C:\Users\Administrator\AppData\Roaming\npm\node_modules\bower\bin\bower
+ bower@1.8.2
added 1 package in 12.109s

Now do a bower install to see if the JavaScript libraries we deleted from wwwroot/lib can now be installed by bower.

You will get an output like this.

PS C:\Users\Administrator\hello-world-project> bower install
bower jquery-validation-unobtrusive#3.2.6           cached https://github.com/aspnet/jquery-validation-unobtrusive.git#3.2.6
bower jquery-validation-unobtrusive#3.2.6         validate 3.2.6 against https://github.com/aspnet/jquery-validation-unobtrusive.git#3.2.6
bower bootstrap#3.3.7                               cached https://github.com/twbs/bootstrap.git#3.3.7
bower bootstrap#3.3.7                             validate 3.3.7 against https://github.com/twbs/bootstrap.git#3.3.7
bower jquery#2.2.0                                  cached https://github.com/jquery/jquery-dist.git#2.2.0
bower jquery#2.2.0                                validate 2.2.0 against https://github.com/jquery/jquery-dist.git#2.2.0
bower jquery-validation#1.14.0                      cached https://github.com/jzaefferer/jquery-validation.git#1.14.0
bower jquery-validation#1.14.0                    validate 1.14.0 against https://github.com/jzaefferer/jquery-validation.git#1.14.0
bower jquery-validation#1.14.0                     install jquery-validation#1.14.0
bower jquery-validation-unobtrusive#3.2.6          install jquery-validation-unobtrusive#3.2.6
bower bootstrap#3.3.7                              install bootstrap#3.3.7
bower jquery#2.2.0                                 install jquery#2.2.0
 
jquery-validation#1.14.0 wwwroot\lib\jquery-validation
??? jquery#2.2.0
 
jquery-validation-unobtrusive#3.2.6 wwwroot\lib\jquery-validation-unobtrusive
??? jquery#2.2.0
??? jquery-validation#1.14.0
 
bootstrap#3.3.7 wwwroot\lib\bootstrap
??? jquery#2.2.0
 
jquery#2.2.0 wwwroot\lib\jquery

If you go back to the wwwroot/lib folder, you should see the bootstrap, jquery, jquery-validation and jquery-validation-unobtrusive packages again.

Let’s commit the two files bower.json and .bowerrc now with a message like Added Bower.