Chapter 11. Contributing, Bug Reports, and Getting Help

You’d Like Some Help

Problem

You’re stuck on something in Lift and you’d like some help.

Solution

Ask a question on the Lift mailing list.

Discussion

You will find some information about Lift on StackOverflow, Quora, and elsewhere, but the mailing list is the place to go to get help and support. You can search the archive to see if your question has already been addressed, but questions are very welcome as it helps the Lift community understand what users need and what might be causing problems or need explanation. Much of what you’re reading here originates from questions that have been asked on the mailing list.

New members to the mailing list are moderated to help reduce spam. This means the first time you post, your message may take a few hours to show up.

See Also

The Lift community is one of the things that makes Lift what it is. Please take a look at http://liftweb.net/community before posting: you’ll get the best response with a polite question.

If you need paid consulting, development, or SLA-backed support, there’s a list of organizations on the wiki.

How to Report Bugs

Problem

You’ve found a bug and want to report it.

Solution

Discuss your findings on the Lift mailing list, describing what you’re seeing, and what you’d expected to see.

By all means look at the existing tickets to see if your issue is there or recently fixed, but please do not raise a ticket unless asked to do so by a Lift committer on the mailing list.

Discussion

 

If Lift is not behaving as you expect, please ask questions about what you’re seeing. The ideal form of these questions is "When I do X, my Lift app does Y, but I expect it to do Z, why?" This provides a set of language to discuss your application and the way that Lift responds to requests. Perhaps there’s a way of improving Lift. Perhaps there’s a concept that’s different in Lift than you might be used to. Perhaps there’s a documentation issue that can help bridge the gap between what Lift is doing and what you expect it to do. Most importantly, just because Lift is behaving differently than you expect it to, it’s not necessarily a bug in Lift.

 
  -- David Pollack http://bit.ly/lift-expects

A great way to get help with the issue you have, or get a bug fixed, is to produce a small example to illustrate the problem and post it on GitHub. The key is to provide instructions so someone can run the example and see exactly what you see without having to jump through hoops.

See Also

You’ll find a list of tickets at http://ticket.liftweb.net/.

If you’re asked to, or want to, post example code, please follow the guide.

If you’ve found a bug, and you’re asked to create a ticket, the main thing to do is include a link to the mailing list discussion of the issue, as described in Creating tickets.

Contributing Small Code Changes and ScalaDoc

Problem

You have a small change or ScalaDoc improvement you’d like incorporated into Lift.

Solution

You can issue pull requests against the Lift source, providing your change meets one of the following requirements:

  • It’s a change to a comment.
  • It’s example code.
  • It’s a small change, enhancement, or bug fix to Lift.

Your pull request must include an edit to add your signature to the bottom of the contributors.md file.

Discussion

Historically, Lift had a strict contributor policy of simply not accepting any code contributions except from committers who had signed an agreement to assign copyright to the Lift project. This allowed corporations wanting to adopt Lift to do so without litigation concerns.

The safety is still there, now via the requirement for a signature on the contributors file, which reads:

By submitting this pull request which includes my name and email address (the email address may be in a non-robot readable format), I agree that the entirety of the contribution is my own original work, that there are no prior claims on this work including, but not limited to, any agreements I may have with my employer or other contracts, and that I license this work under an Apache 2.0 license.

What’s a small change? That’s a good question, and if you’re unsure, talk about your proposed change on the mailing list.

See Also

This contribution policy was introduced in November 2012.

The contributors.md file is found on GitHub.

The Lift source is on GitHub. The framework project is probably the one you want, although you’ll also find Git repositories for examples and Lift websites there.

GitHub provides an introduction to pull requests.

“Sharing Code in Modules” describes how to share code of any size via Lift modules.

Contributing Documentation

Problem

You’d like to contribute documentation to Lift.

Solution

Update or add to the Lift wiki.

You’ll need to sign in, or create a free account, with Assembla, the company that hosts the wiki. You then need to become a watcher of the Lift wiki, which is offered as a link on the top right of the Lift wiki page. As a watcher, you can edit pages and create new pages.

Discussion

If you’re unsure about a change you’d like to make, just ask for feedback on the Lift mailing list.

One limitation of the watcher role on Assembla is that you cannot move pages. If you create a new page in the wrong section, or want to reorganise pages, you’ll need to ask on the Lift mailing list for someone with permissions to do that for you.

See Also

The markup format for the wiki pages is Textile.

How to Add a New Recipe to This Cookbook

Problem

You’d like to add a section or chapter to this cookbook.

Solution

If you’re comfortable using Git, you can fork the repository and send a pull request.

Alternatively, download a template file, write your recipe, and email it to the Lift mailing list.

You can find the template file on GitHub.

Discussion

Anything you’ve puzzled over, or things that have surprised you, impressed you, or are nonobvious are great topics for recipes. Improvements, discussions, and clarifications of existing recipes are welcome, too.

The cookbook is structured using a markup language called AsciiDoc. If you’re familiar with Markdown or Textile, you’ll find similarities. For the cookbook, you only need to know about section headings, source code formatting, and links. Examples of all of these are in the template.asciidoc file.

To find out where to make a change, you need to know that each chapter is a separate file, and each recipe is a section in that file.

Licensing

We ask contributors the following:

  • You agree to license your work (including the words you write, the code you use, and any images) to us under the Creative Commons Attribution, Non Commercial, No Derivatives license.
  • You assert that the work is your own, or you have the necessary permission for the work.

To keep things simple, all author royalties from this book are given to charity.

See Also

The source to this book is on GitHub.

The AsciiDoc cheatsheet is a quick way to get into AsciiDoc, but if you need more, the AsciiDoc home page has the details.

GitHub provides an introduction to pull requests.

“Contributing Documentation” describes other ways to contribute documentation to Lift.

Sharing Code in Modules

Problem

You have code you’d like to share between Lift projects or with the community.

Solution

Create a Lift module, and then reference the module from your Lift projects.

As an example, let’s create a module to embed the snowstorm snowfall effect on every page in your Lift web application (please don’t do this).

There’s nothing special about modules: they are coded, packaged, and used like any other dependency. What makes them possible is the exposure of extension points via LiftRules. The main convention is to have an init method that Lift applications can use to initialise your module.

For our snowstorm, we’re going to package some JavaScript and inject the script onto every page.

Starting with the lift_blank template downloaded from liftweb.net, we can remove all the source and HTML files, as this won’t be a runnable Lift application in itself. However, it will leave us with the regular Lift structure and build configuration.

Our module will need the snowstorm JavaScript file copied as resources/toserve/snowstorm.js. This will place the JavaScript file on the classpath of our Lift application.

The final piece of the module is to ensure the JavaScript is included on every page:

package net.liftmodules.snowstorm

import net.liftweb.http._

object Snowstorm {

 def init() : Unit = {

  ResourceServer.allow {
     case "snowstorm.js" :: Nil => true
  }

  def addSnow(s: LiftSession, r: Req) = S.putInHead(
    <script type="text/javascript" src="/classpath/snowstorm.js"></script> )

  LiftSession.onBeginServicing = addSnow _ :: LiftSession.onBeginServicing

 }

}

Here we are plugging into Lift’s processing pipeline and adding the required JavaScript to the head of every page.

We modify build.sbt to give the module a name, organisation, and version number. We also can remove many of the dependencies and the web plugin as we only depend on the web API elements of Lift:

name := "snowstorm"

version := "1.0.0"

organization := "net.liftmodules"

scalaVersion := "2.9.1"

resolvers ++= Seq(
   "snapshots" at "http://oss.sonatype.org/content/repositories/snapshots",
   "releases" at "http://oss.sonatype.org/content/repositories/releases"
)

scalacOptions ++= Seq("-deprecation", "-unchecked")

libraryDependencies ++= {
  val liftVersion = "2.5"
  Seq(
    "net.liftweb" %% "lift-webkit"  % liftVersion  % "compile"
  )
}

We can publish this plugin to the repository on disk by starting SBT and typing:

publish-local

With our module built and published, we can now include it in our Lift applications. To do that, modify the Lift application’s build.sbt to reference this new "snowstorm" dependency:

libraryDependencies ++= {
  val liftVersion = "2.5"
  Seq(
  ...
  "net.liftmodules" %% "snowstorm" % "1.0.0",
  ...

In our Lift application’s Boot.scala, we finally initialise the plugin:

import net.liftmodules.snowstorm.Snowstorm
Snowstorm.init()

When we run our Lift application, white snow will be falling on every page, supplied by the module.

Discussion

The module is self contained: there’s no need for users to copy JavaScript files around or modify their templates. To achieve that, we’ve made use of ResourceServer. When we reference the JavaScript file via /classpath/snowstorm.js, Lift will attempt to locate snowstorm.js from the classpath. This is what we want for our Lift application, because snowstorm.js will be inside the module JAR file.

However, we do not want to expose all files on the classpath to anyone visiting our application. To avoid that, Lift looks for resources inside a toserve folder, which for our purposes means files and folders inside src/main/resources/toserve. You can think of /classpath meaning toserve (although, you can change those values via LiftRules.resourceServerPath and ResourceServer.baseResourceLocation).

As a further precaution, you need to explicitly allow access to these resources. That’s done with:

ResourceServer.allow {
  case "snowstorm.js" :: Nil => true
}

We’re just always returning true for anyone who asks for this resource, but we could dynamically control access here if we wanted.

S.putInHead adds the JavaScript to the head of a page and is triggered on every page by LiftSession.onBeginServicing (also discussed in “Running Code When Sessions Are Created (or Destroyed)”). We could make use of Req here to restrict the snowstorm to particular pages, but we’re adding it to every page.

Hopefully you can see that anything you can do in a Lift application you can probably turn into a Lift module. A typical approach might be to have functionality in a Lift application, and then factor out settings in Boot into a module init method. For example, if you wanted to provide REST services as a module, that would be possible and is an approach taken by the Lift PayPal module.

Making your module available

If you do want your module to be used by a wider audience, you need to publish it to a public repository, such as Sonatype or CloudBees. You’ll also want to keep your module up-to-date with Lift releases. There are a few conventions around this.

One convention is to include the Lift "edition" as part of the module name. For example, version 1.0.0 of your foo module for Lift 2.5 would have the name foo_2.5. This makes it clear your module is compatible with Lift for all of 2.5, including milestones, release candidates, snapshots, and final releases. It also means you need to publish your module only once, at least until Lift 2.6 or 3.0 is available.

One way to manage updates is to make a modification to your module build to allow the Lift version number to change. This makes it possible to automate the build when new versions of Lift are released. To do that, create project/LiftModule.scala in your module:

import sbt._
import sbt.Keys._

object LiftModuleBuild extends Build {

  val liftVersion = SettingKey[String]("liftVersion",
    "Full version number of the Lift Web Framework")

  val liftEdition = SettingKey[String]("liftEdition",
    "Lift Edition (short version number to append to artifact name)")

  val project = Project("LiftModule", file("."))
}

This defines a setting to control the Lift version number. You use it in your module build.sbt like this:

name := "snowstorm"

organization := "net.liftmodules"

version := "1.0.0-SNAPSHOT"

liftVersion <<= liftVersion ?? "2.5-SNAPSHOT"

liftEdition <<= liftVersion apply { _.substring(0,3) }

name <<= (name, liftEdition) { (n, e) =>  n + "_" + e }

...

libraryDependencies <++= liftVersion { v =>
  "net.liftweb" %% "lift-webkit" % v % "provided" ::
  Nil
}

Note the "provided" configuration for Lift in the build file. This means that when your module is used, the version of Lift’s WebKit will be the version provided by the application being built by the person using your module.

What the previous code gives you is a way for your module to depend on Lift ("2.5"), without locking your module into a specific release if it happens to be built with "2.5-SNAPSHOT." By using a setting of liftVersion, we can control the version via a script for all modules. This is what we do to publish a range of Lift modules after each Lift release, as described on the Lift wiki.

When your module is built, don’t forget to announce it on the Lift mailing list.

Debugging your module

When working on a module and testing it in a Lift application, it would be a chore to have to publish your module each time you changed it. Fortunately, SBT allows your Lift application to depend on the source of a module. To use this, change your Lift project to remove the dependency on the published module and instead add a local dependency by creating project/LocalModuleDev.scala:

import sbt._
object LocalModuleDev extends Build {
  lazy val root = Project("", file(".")) dependsOn(snow)
  lazy val snow = ProjectRef(uri("../snowstorm"), "LiftModule")
}

We are assuming that the snowstorm source can be found at ../snowstorm relative to the Lift application we are using. With this in place, when you build your Lift project, SBT will automatically compile and depend on changes in the local snowstorm module.

See Also

Originally, Lift included a set of modules, but these have been separated out to individual projects. The Lift contributor policy outlined in “Contributing Small Code Changes and ScalaDoc” doesn’t apply to Lift modules: you’re free to contribute to these modules as you would any other open source project.

The Lift wiki pages for modules can be reached via http://liftmodules.net.

The Snowstorm project has been "setting CPUs on fire worldwide every winter since 2003," and the module developed for this recipe is on GitHub.

To publish to Sonatype, take a look at their guide. CloudBees offers an open source repository.

There are other ways to structure common code between Lift applications. For an example that uses SBT modules and Git, see the mailing list discussion on "Modularize Lift Applications".