Saturday, May 23, 2009

Lift Without Maven

It is possible to run Lift without Maven. It's actually pretty easy. It's just not supported. I'll tell you how to do it.

Background

You can tell from my blog posts that I like Scala. I have wanted to try out Liftweb for a while, but was turned off by the apparent requirement of using Maven. I figure that if I do like Lift, it will be enough work to convince my company that they should switch to Scala and Lift without also having to convince them at the same time that they should switch to Maven.

The members of the Lift community seem pleased with Maven and are not interested in supporting a non-Maven workflow. It's not that Lift requires Maven - in fact, David really wants people to know that Lift does not require Maven - but at the moment it's simply not a priority for them to support a non-Maven download. Although you can't really fault a newcomer for thinking that Maven is required, given that the Getting Started page currently states that Maven 2 is a "prerequisite". If you start asking about downloading and using Lift without Maven you tend to get a lot of responses about how great Maven is and how you should really try it. This is perhaps unfortunate, because many people, myself included, are not looking for a discussion about Maven and really want to minimize the number of new tools and workflow changes necessary just to try out a piece of software. Maven may be great, but that's not what I want to hear about right now. So I decided to figure out for myself just what it would take to download, compile and run Liftweb without Maven.

I started by using the standard Maven-based approach to get the files onto my machine, then hacked until I had what I wanted. You can do the same thing (it was actually pretty easy), or if you are having trouble with Maven or you simply don't want to touch it yet, you can use the directions I give below on how to download and set up the Liftweb demo without it. So although the semi-official answer (on the Lift wiki) to how to use Lift without Maven is that you at least need to use Maven to download the initial lift project, I'm here to tell you that you can download, compile and run the demo Lift app without using Maven at all.

With Maven

The download page on the Lift web site is interesting. There is a short paragraph for each of Mac, Windows and Linux downloads. The Mac and Windows paragraphs each include a link to a download package. The Linux paragraph just says, if you have trouble, ask someone on the mailing list.

I scratched my head a bit at the lack of a download link on the Download page, but finally went back to the main page, saw the link to the Getting Started page, opened the HTML document there, and started reading how to install Liftweb.

Step 1 is to run the mvn archetype:generate command, which downloads the Lift templates that Maven uses to build and update Lift. Unfortunately, those Lift templates require version 2.0.9 of Maven, and the newest version available as a standard package on my system (Fedora 9) is Maven 2.0.4. Rather than upgrading my OS, I went to the Maven website, downloaded the latest version of Maven (2.1.0), and installed it in an alternate location on my system. I then updated my $PATH to include that alternate location so that I would be using a new enough version of Maven to work on the Lift demo.

After downloading and installing the updated version of Maven, I was able to run the mvn archetype command listed on the Getting Started page. Since I had not used Maven before on this computer, it downloaded a bunch of files - about 5MB worth. The Getting Started page does say Maven will output "several page's worth of text", so this was not entirely a surprise.

Step 2 is to run the mvn jetty:run command. This command finishes building the demo Liftweb app and starts it using Jetty listening on port 8080. When I ran this second Maven command, it downloaded another big batch of files from the Internet. The Getting Started page says that after the first step, "you've now successfully created your first project", and it just says the second step "should produce more output", implying that there is not much work to be done in that second step. However, the second step took at least 5 times as long and downloaded another 30 MB of files; that was a bit of a surprise. In total Maven took about four minutes and downloaded 35MB of files - 710 files, including 82 jar files - into my new $HOME/.m2 directory (the Maven cache), including older versions of Scala and Ant than I already had on my machine. That's not really all that much time or space for a modern computer, but it was perhaps a bit more than I was expecting would be required for a "hello world" program. And in fact, as you will see below, only a small part of those files are necessary.

Unfortunately, after downloading half the Internet and finishing the build of the Liftweb demo, it failed to run on my system. Ah, but of course: I am already running Tomcat, which is using port 8080, the same port the Liftweb demo was trying to use. No problem, I thought, I will just change the port that the Liftweb demo uses. Unfortunately, my quick attempt was unsuccessful. I searched through all the files for 8080, found it in one place, changed it to 8181, then ran mvn jetty:run again. No luck - after crunching for about ten seconds it still tried to open port 8080. Among the many messages that Maven spit out was the line "Nothing to compile - all classes are up to date", so apparently it did not notice that I had edited one of the source files. Well, no big deal, I just stopped Tomcat for a while and tried it again, and I was able to access the demo web page at localhost:8080.

So getting the Liftweb demo running using the Maven approach was pretty easy:
  1. Download and install a new version of Maven.
  2. Stop Tomcat on my machine.
  3. Run the two maven commands on the Liftweb Getting Started page.
  4. Open my web browser to http://localhost:8080 and view the hello world page.

Without Maven

As you can see from the above, getting Lift downloaded and working with Maven was, for me, no problem. But what I really wanted to do was to build a war file that I could drop into my already-running Tomcat. I started with a simple ant script to compile HelloWorld.scala, and kept adding bits and pieces to it until I was able to build a working war file. Below are the details about the final setup I used to create a war file that worked under Tomcat.

I am using a grand total of 11 files:
  • Two scala files: Boot.scala and HelloWorld.scala
  • Two html files: index.html and templates-hidden/default.html
  • Two xml files: web.xml and build.xml
  • Five jar files: lift-util.jar, lift-webkit.jar, scala-library.jar, log4j.jar, and commons-fileupload.jar
Of the above files, both scala files, both html files, web.xml and the two lift jar files came unmodified out of the Liftweb demo, the Scala library came from the standard Scala download, the other two jar files were standard downloads from Apache, and the one file I created myself was the ant build file build.xml.

My directory hierarchy looks like this:
liftdemo/ build/ All generated files go here build.xml Ant build file src/ scala/ bootstrap/liftweb/Boot.scala demo/helloworld/snippet/HelloWorld.scala webapp/ index.html web.xml templates-hidden/default.html
The five library jar files are stored elsewhere in my system and are copied in to the build directory when the war file is being created. You could create a lib directory in the liftdemo directory and drop the libraries in there if you wanted to. If you used Maven to download Lift, you can pull all of these libraries from your $HOME/.m2 Maven cache, else you can download them from their internet homes if you don't already have them somewhere on your system.

The war file liftdemo.war looks like this:
META-INF/MANIFEST.MF WEB-INF/ web.xml classes/ bootstrap/liftweb/ Boot$$anonfun$1.class Boot.class demo/helloworld/snippet/HelloWorld.class lib/ commons-fileupload-1.2.1.jar lift-util-1.0.jar lift-webkit-1.0.jar log4j-1.2.14.jar scala-library.jar index.html templates-hidden/default.html
You can download files directly from the Liftweb Maven repository without using Maven: use these direct download links for the demo sources packed in a jar file, lift-util-1.0.jar, and lift-webkit-1.0.jar. (Note that these are all version 1.0 files, so if you are reading this article well past the posting date there are probably newer versions - poke around the repository to find out what the latest version is.) Unpack the demo source jar to a temporary location, then copy the five files mentioned above into the appropriate places in the hierarchy. Download scala, log4j and commons-fileupload (if you haven't already), drop in a buld.xml file (see below), and modify the properties to fit your environment. At that point you should be able to run a simple ant command to create a war file in the build directory. Copy that war file into the webapps directory, and you should be able to see the demo page in the standard way from your web browser.

After that, you are on your own. Obviously this very simple setup does not handle dependencies like Maven does, so when those dependencies get updated, you will have to manually manage that process. And when you build up your code and start using other features, you will have to manually add additional jar files to build.xml. But for some of us, that's just what we want.

The size of the src directory listed above is under 60KB, the two liftweb jar files are under 3MB together, and the scala and other two libraries are, combined, just over 4MB. The war file weighs in at 6.3MB. The original liftweb demo download includes some tests that I did not include in my war file, so it could be that the tests use a few more of those 82 jar files that Maven downloaded.

The build file below is pretty simple. There are really only two commands necessary to build the webapp: first compile the two scala files, then build the war file. I hope this post has made it clear that you don't need Maven at all to use Lift, and that Lift webapps are satisfactorily small and simple to build and run.

Ant Build File

Here is my sample buid.xml file for building the Liftweb demo without Maven. Remember to review the properties and change them as appropriate for your environment. In particular, you will almost certainly want to change the home.dir property, and probably the jar file locations as well. If you create a lib directory in your liftdemo directory and put the five libraries there, you can simplify the build file by getting rid of home.dir and a number of the properties used to define the locations of the libraries.
<project name="liftdemo" default="war"> <target name="init" description="Initialize all properties and paths"> <property name="home.dir" value="/u/jimmc"/> <property name="build.dir" value="build"/> <property name="class.dir" value="${build.dir}/class"/> <property name="scala.src.dir" value="src/scala"/> <property name="war.build.dir" value="${build.dir}/war"/> <property name="war.lib.dir" value="${war.build.dir}/WEB-INF/lib"/> <property name="war.file" value="build/liftdemo.war"/> <property name="web.xml" value="src/webapp/web.xml"/> <!-- Location of scala files --> <property name="scala.dir" value="${home.dir}/net/scala/scala-current"/> <property name="scala-compiler.jar" value="${scala.dir}/lib/scala-compiler.jar"/> <property name="scala-library.jar" value="${scala.dir}/lib/scala-library.jar"/> <!-- Location of Liftweb files --> <property name="liftweb.dir" value="${home.dir}/net/liftweb/1.0"/> <property name="lift-util.jar" value="${liftweb.dir}/lift-util-1.0.jar"/> <property name="lift-webkit.jar" value="${liftweb.dir}/lift-webkit-1.0.jar"/> <!-- A couple of other libraries are required --> <property name="log4j.jar" value="${home.dir}/net/log4j/log4j-1.2.14.jar"/> <property name="fileupload.jar" value="${home.dir}/net/fileupload/commons-fileupload-1.2.1.jar"/> <path id="scala.classpath"> <pathelement location="${scala-compiler.jar}"/> <pathelement location="${scala-library.jar}"/> </path> <path id="compile.classpath"> <path refid="scala.classpath"/> <pathelement location="${lift-util.jar}"/> <pathelement location="${lift-webkit.jar}"/> </path> <!-- define the "scalac" ant task --> <taskdef resource="scala/tools/ant/antlib.xml"> <classpath refid="scala.classpath"/> </taskdef> </target> <target name="clean" depends="init"> <delete dir="${build.dir}"/> </target> <target name="mkdirs" depends="init"> <mkdir dir="${build.dir}"/> <mkdir dir="${class.dir}"/> </target> <target name="compile" depends="init,mkdirs" description="Compile scala files"> <scalac srcdir="${scala.src.dir}" destdir="${class.dir}" addparams="-g:vars" classpathref="compile.classpath" force="changed" deprecation="on" > <include name="**/*.scala"/> </scalac> </target> <target name="war" depends="init,compile"> <!-- First make an image directory in war.build.dir --> <copy todir="${war.build.dir}"> <fileset dir="src/webapp" includes="**/*.html"/> </copy> <mkdir dir="${war.build.dir}/WEB-INF"/> <copy todir="${war.lib.dir}" file="${scala-library.jar}"/> <copy todir="${war.lib.dir}" file="${lift-util.jar}"/> <copy todir="${war.lib.dir}" file="${lift-webkit.jar}"/> <copy todir="${war.lib.dir}" file="${log4j.jar}"/> <copy todir="${war.lib.dir}" file="${fileupload.jar}"/> <copy todir="${war.build.dir}/WEB-INF/classes"> <fileset dir="${class.dir}" includes="**/*.class"/> </copy> <!-- Put everything from our war directory into a war file --> <war destfile="${war.file}" webxml="${web.xml}" > <fileset dir="${war.build.dir}" includes="**/*"/> </war> </target> </project>

12 comments:

Chris Bouzek said...

Truly awesome! I will have to try this out.

Mateo said...

I like this too bad the Lift people insist on using Maven. It's never worked outside the box, which is ironic since automation is one of it's main goals...

Unknown said...

Thanks for posting your notes on this! They helped us create a Lift application template for publishing Lift apps to EC2 via Stax.net.

Raju Bitter said...

Thanks, nice! That was the first thing which came to my mind when I started Lift development: can we do it without Maven!

EsauCairn said...

I also had to add the Apache Commons Collections .jar for the resulting .war to run on Apache Tomcat. That's using Lift 1.1 - maybe they added a dependency?

Thanks for the information. Like you, I don't want the extra work on convincing my coworkers and boss to adapt Maven as well as Scala and Lift.

Dave said...

Did you look into SBT? It is far superior to ant or maven for Scala projects. I, too, have been unmotivated to dive into lift because of maven.

Jim McBeath said...

Dave: Yes, I have used SBT. But the whole point of this post was to reduce to a minimum the requirements for checking out Lift. I assumed the reader is familiar with the current generation of standard web development tools, and that includes Ant, so using Ant does not require thinking about anything new. In that sense, SBT is exactly like Maven: I don't care how good it is, I don't care how many people tell me it's the greatest build tool ever, for this moment in time I just want to focus on Lift.

Central Egg Command said...

Thank you so much for providing such detailed and helpful information. I am new to even using Ant and I was able to compile the example, though not run it with Tomcat. Are there any other resources you would suggest to really gain an understanding of the role of different files/conventions for using lift? I am confused about what is actually supposed to be run, and how different files get "pulled in" and used.

Jim McBeath said...

Central Egg Command: Lift is very flexible and has a lot of options. I am not an expert on Lift, but with that caveat here are my recommendations:

1. To learn the basics about Tomcat and how web app containers works, I suggest this tutorial.
2. To learn more about Lift in general, read the Getting Started guide, or the book "The Definitive Guide to Lift" (see also other links on liftweb.net).
3. If you have more questions about Lift to which you can't find answers with a google search, join or browse the Lift mailing list.

foomanchu said...

Jim, You can change the port by editing the pom.xml config file, section for plugins to look like this:


org.mortbay.jetty
maven-jetty-plugin

/
5


8181
60000

cammer said...

I just found this, and I'm elated. I actually bought a book on maven just so I could use lift, but just gave up on the whole thing, because of the time required. I might actually give lift another spin, now that I don't have to deal with the behemoth that is maven.

Unknown said...

It sounds like it would have been easier to simply build the war using maven. I find your willingness to go through a lengthy process to avoid learning one small feature of maven quite suprising. Building a war in maven is trivial, given that you worked out how to use maven to get this far, you could probably learn how to build the war in a few minutes.

Of the '85' files maven downloaded, the majority were for running maven and its plugins. Maven's very good at managing dependencies and doing lots of other stuff with very little effort. Except in rare cases, using anything else is unprofessional.