Hacking Lintian 101

This document aims to give a quick guide and a few hints to understanding Lintian.

Using Lintian from git

Lintian can be used directly from git; this allows developers to skip the boring build/install phase when testing the development version on real packages. To use lintian directly from git you need to set LINTIAN_ROOT (alternatively --root can be used) to the directory where the git clone is. The following script can be used for this:

   1 #!/bin/sh
   2 LINTIAN_ROOT="<INSERT PATH TO LINTIAN GIT DIR>"
   3 export LINTIAN_ROOT
   4 exec "$LINTIAN_ROOT/frontend/lintian" "$@"

Save that as run-lintian, make it executable, put it in your PATH and you are good to go.

Caveat 1: If LINTIAN_ROOT is not set, lintian will try to use the installed version of lintian, which most likely does not have the changes you would like to test.

Caveat 2: Lintian expects to have a standard locale, currently the version from git just uses the installed locale. This works fine as long as you also have Lintian installed (or if you already have the needed locale). In chroots or on minimum installs you may have to generate such a locale and set LOCPATH. Note, lintian will auto-generate this locale for running its test suite.

Running tests

There are a number of ways to run (parts of) the Lintian test suite. To run the entire test suite, simply use:

$ debian/rules runtests

It is also possible to run only a subset of the tests by using the "onlyrun" variable.

$ debian/rules runtests onlyrun=$test

Here $test can be one of:

Finally it is also possible to only check for tests that are designed to trigger (or not trigger) a specific tag. For this use:

$ debian/rules check-tag tag=$tag

In this case, the test suite will run all tests that has $tag in its Test-For or Test-Against field. This cannot be used to run a test in the legacy test suite.

Writing a new check

In this tutorial, we will write a sample check called "illegal". This check will issue the tag "illegal-name", if the version of a binary package is "foo" or "bar". We start by creating a "desc" file for the check, which has to be "checks/illegal.desc":

Check-Script: illegal
Author: Your Name <you@example.com>
Abbrev: ill
Type: binary
Info: Checks for illegal names

Tag: illegal-version
Severity: important
Certainty: certain
Info: The package version is "foo" or "bar", which is not allowed.

Let us go over the fields of the first paragraph:

One or more "Tag" paragraphs follows the first paragraph. In this example we only have one:

After filling out this file, lintian-info will recognise the "illegal-version" tag.

# note lintian-info < 2.5.2 will use "N:" instead of "E:" on the first line
/path/to/lintian.git-dir$ LINTIAN_ROOT='.' frontend/lintian-info -t illegal-version
E: illegal-version
N:
N:   The package version is "foo" or "bar", which is not allowed.
N:
N:   Severity: important, Certainty: certain
N:
N:   Check: illegal, Type: binary
N:

However, lintian does not know how to emit the tag yet. Time to write the actual check. Open "checks/illegal" and write:

   1 # This check was written by Your Name <you@example.com>
   2 #  copyright and license info etc.
   3 package Lintian::illegal;
   4 
   5 use strict;
   6 use warnings;
   7 
   8 use Lintian::Tags qw(tag);
   9 
  10 sub run {
  11   my $pkg  = shift; # package name
  12   my $type = shift; # package type - for this check it will always be "binary"
  13   my $info = shift; # a Lintian::Collect object for the package
  14                     #  - for this check it will be a Lintian::Collect::Binary
  15 
  16   # Fetch a field from the control file in the control.tar.gz from the deb.
  17   # This also works on udeb, source and changes packages.
  18   # For source and changes, this will be a field from the ".dsc" and the ".changes" file
  19   my $version = $info->field('version');
  20   # Note fields are "undef" if they are not present; do not assume the presence of any field.
  21   if (defined $version) {
  22      if ($version eq 'foo' || $version eq 'bar') {
  23         # Issue the illegal-version tag, giving the actual version as "extra" info.
  24         tag 'illegal-version', $version;
  25      }
  26   }
  27 }
  28 
  29 1;

The rationale for using the $info (Lintian::Collect) object, is that it abstracts away the underlying lab (and allows lintian to better cache things). The Lintian::Collect API is a bit behind (at time of writing) and some checks still have to directly look in the lab. The Lintian::Collect API is based on the information extracted by "collection" scripts; each method in the API will generally have one line like this (some times it is inside the method):

# sub field Needs-Info <>
#  - OR -
# sub unpacked Needs-Info unpacked

The first one declares that the method "field" requires no collections at all and the second one means that the method "unpacked" requires the "unpacked" collection to have been run. So to use the latter, the check would need to declare a "Needs-Info: unpacked".

With this lintian will be able to emit the tag, so lets try it on a deb:

/path/to/lintian.git-dir$  LINTIAN_ROOT='.' frontend/lintian -i --no-override \
    -C illegal /path/to/deb/version-foo.deb
E: deb-format-lzma: illegal-version foo
N: 
N:    The package version is "foo" or "bar", which is not allowed.
N:    
N:    Severity: important, Certainty: certain
N:    
N:    Check: illegal, Type: binary
N: 

Finally, you just need to add the check to a profile. If it should be added to the debian/main profile, just run:

/path/to/lintian.git-dir$ debian/rules profiles

Otherwise you may have to alter private/generate-profiles.pl to exclude it from debian/main and add it to the right profile.

Summing it up: In this tutorial, we created a new check by creating a "desc" file with that tag information, a perl module to check and emit the tag and finally we updated the profiles.

Writing tests (test suite)

The test-suite generally consists of building a number of packages, running Lintian on them and comparing the output to the expected results. There are 5 "sub" test suites named "scripts", "debs", "changes", "source" and "tests". The scripts sub suite consists of perl tests used to unit test parts of lintian and will not be covered here. The rest are used to test if Lintian can diagnose tags correctly.

Generally the "tests" subsuite (in t/tests) should be used. The "source", "changes" and "debs" suites are used to hand-make special packages to test edge-cases (like missing required fields). Please consult t/$suite/README for how to write a test in each of the suites.