Differences between revisions 10 and 25 (spanning 15 versions)
Revision 10 as of 2011-08-13 09:34:43
Size: 8692
Editor: NielsThykier
Comment: Updated to better reflect 2.5.2
Revision 25 as of 2017-11-06 21:52:07
Size: 10627
Editor: XTaran
Comment: Mention the Severity "classification"
Deletions are marked like this. Additions are marked like this.
Line 8: Line 8:
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: 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. With Lintian 2.5.18, all frontends will automatically detect when they are run from a source tree and configure themselves accordingly. Earlier versions required manually setting LINTIAN_ROOT (or --root) for lintian to work. The following script can be used for this:
Line 12: Line 12:
# ONLY FOR 2.5.17 OR EARLIER! 2.5.18 will DTRT without this script.
Line 14: Line 15:
# With Lintian >= 2.5.13 you may want to add "--user-dirs" to the line below.
Line 19: Line 21:
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 1: (Only for 2.5.17 or earlier) 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.
Line 21: Line 23:
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. == API docs ==

Lintian does not have a stable API at the time of writing (2.5.13). That said, Lintian do have some API documentation that may be useful. This documentation can be extracted by building the "api-doc" target. In the Lintian source tree, run:

{{{#!highlight sh
$ debian/rules api-doc
}}}

This will generate the API docs in doc/api.html (e.g. sensible-browser doc/api.html/index.html). This documentation also includes tutorials (see the Lintian::Tutorial document) on writing checks.
Line 40: Line 50:
 * The special value "suite:$list". In that case $list will be read as a comma-separated list of suites and all tests in the listed suites will be run. (e.g. "suite:changes,debs,source"). This only works for the new test suite.
 * The special value "tag:$tag". In this case only tests that are designed to trigger (or not trigger) a specific tag. These tests will have the tag mentioned in their "Test-For" or "Test-Against" field. This only works with the new test suite.
Line 42: Line 54:
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.
For more information, please refer to the Lintian::Tutorial::TestSuite document in the Lintian API docs.
Line 52: Line 58:
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": 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 contains "foo" or "bar". We start by creating a "desc" file for the check, which has to be "checks/illegal.desc":
Line 64: Line 70:
Info: The package version is "foo" or "bar", which is not allowed. Info: The package version contains "foo" or "bar", which is not allowed.
Line 77: Line 83:
 * Severity (required): One of "serious", "important", "normal", "minor", "wishlist" and "pedantic". Together with Certainty, this determines the "code" of the tag. Note the profile used may overrule this value.  * Severity (required): One of "serious", "important", "normal", "minor", "wishlist", "pedantic" and "classification". Together with Certainty, this determines the "code" of the tag. Note the profile used may overrule this value.
Line 86: Line 92:
/path/to/lintian.git-dir$ LINTIAN_ROOT='.' frontend/lintian-info -t illegal-version # note with Lintian < 2.5.17, you will need to set LINTIAN_ROOT=.
/path/to/lintian.git-dir$ frontend/lintian-info -t illegal-version
Line 89: Line 96:
N: The package version is "foo" or "bar", which is not allowed. N: The package version contains "foo" or "bar", which is not allowed.
Line 110: Line 117:
  my $pkg = shift; # package name
  my $type = shift; # package type - for this check it will always be "binary"
  my $info = shift; # a Lintian::Collect object for the package
                    # - for this check it will be a Lintian::Collect::Binary
  my ($pkg, $type, $info) = @_;
  # $pkg is the
package name
  # $type is the package type - for this check it will always be "binary"
  # $info is a Lintian::Collect object for the package
  # - for this check it will be a Lintian::Collect::Binary
Line 120: Line 128:
  if (defined $version) {
     if ($version eq 'foo' || $version eq 'bar') {
  if (defined($version)) {
     if ($version =~ m/(foo|bar)/) {
Line 123: Line 131:
        tag 'illegal-version', $version;         tag 'illegal-version', $version, 'contains', $1;
Line 132: Line 140:
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): 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. The API docs for each method will generally have one line like this:
Line 135: Line 143:
# sub field Needs-Info <>
# - OR -
# sub unpacked Needs-Info unpacked
Needs-Info requirements for using field: none
  - OR -
Needs-Info requirements for using unpacked: unpacked
  - OR -
Needs-Info requirements for using binary_relation: Same as binary_field
Line 140: Line 150:
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". 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 second one, the check would need to declare a "Needs-Info: unpacked".  The third one mains that the method "binary_relation" needs "whatever the binary_field method needs" (in the API docs, this will be a link). This is mainly to avoid having to keep transitive dependencies up to date and can only be used inside the Lintian::Collect classes.
Line 144: Line 154:
/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
# note with Lintian < 2.5.17, you will need to set LINTIAN_ROOT=.
/path/to/lintian.git-dir$
frontend/lintian -i --no-override \
    -C illegal /path/to/deb/version-0foo.deb
E: somepackage: illegal-version 0foo contains foo
Line 148: Line 159:
N: The package version is "foo" or "bar", which is not allowed. N: The package version contains "foo" or "bar", which is not allowed.
Line 165: Line 176:
For more information on writing checks, please refer to the Lintian::Tutorial::WritingChecks document in the API docs. In particular, please read the section "Avoiding security issues".
Line 170: Line 183:

== perltidy & perlcritic ==

Lintian enforces a perltidy/perlcritic profile which can be found in the files `.perlcriticrc` and `.perltidyrc`, respectively. Either run perlcritic with the correct profile manually our execute the test `01-critic/checks`.

== tests to run before submitting patches ==

In addition to running the new or existing tests for the tags you worked on, make sure that the following execute successfully:

{{{
debian/rules runtests onlyrun=01-critic/checks
debian/rules runtests onlyrun=check-descs
debian/rules runtests onlyrun=implemented-tags
}}}

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. With Lintian 2.5.18, all frontends will automatically detect when they are run from a source tree and configure themselves accordingly. Earlier versions required manually setting LINTIAN_ROOT (or --root) for lintian to work. The following script can be used for this:

   1 #!/bin/sh
   2 # ONLY FOR 2.5.17 OR EARLIER!  2.5.18 will DTRT without this script.
   3 LINTIAN_ROOT="<INSERT PATH TO LINTIAN GIT DIR>"
   4 export LINTIAN_ROOT
   5 # With Lintian >= 2.5.13 you may want to add "--user-dirs" to the line below.
   6 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: (Only for 2.5.17 or earlier) 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.

API docs

Lintian does not have a stable API at the time of writing (2.5.13). That said, Lintian do have some API documentation that may be useful. This documentation can be extracted by building the "api-doc" target. In the Lintian source tree, run:

   1 $ debian/rules api-doc

This will generate the API docs in doc/api.html (e.g. sensible-browser doc/api.html/index.html). This documentation also includes tutorials (see the Lintian::Tutorial document) on writing checks.

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:

  • The name of a test, in which case that specific test is run. This works regardless if the test is in the new or in the legacy test suite.
  • The special value "suite:$list". In that case $list will be read as a comma-separated list of suites and all tests in the listed suites will be run. (e.g. "suite:changes,debs,source"). This only works for the new test suite.
  • The special value "tag:$tag". In this case only tests that are designed to trigger (or not trigger) a specific tag. These tests will have the tag mentioned in their "Test-For" or "Test-Against" field. This only works with the new test suite.
  • The name of a check, in which case all tests in t/tests that starts with $name will be run. If a legacy test happens to have the same name as a check, then this test is also run.

For more information, please refer to the Lintian::Tutorial::?TestSuite document in the Lintian API docs.

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 contains "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 contains "foo" or "bar", which is not allowed.

Let us go over the fields of the first paragraph:

  • Check-Script (required): Name of the script (same as the filename without the ".desc" extension)
  • Author (optional): Name and email of who (initially) wrote the check
  • Abbrev (required): An abbreviation of the check name; used for (e.g.) lintian -C.
  • Type (required): Comma separated list of the values "binary" (.deb), "udeb" (.udeb), "source" (.dsc) and "changes" (.changes). This determines which types of packages this check applies.
  • Info (required): Short description of what the check does; this will be added to the manpage of lintian.
  • Needs-Info (optional, not used here): The list of "collection"s this check needs. We will come back to this later.

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

  • Tag (required): name of the tag
  • Severity (required): One of "serious", "important", "normal", "minor", "wishlist", "pedantic" and "classification". Together with Certainty, this determines the "code" of the tag. Note the profile used may overrule this value.
  • Certainty (required): One of "certain", "possible" and "wild-guess". Together with Severity, this determine the "code" of the tag.
  • Info (required): The description of the tag (shown with lintian -i, lintian-info -t $tag, etc.)
  • Ref (optional, not used here): Comma separated list of references to a manual, web-page or the likes. Regular URLs and manpages (e.g. lintian(1)) works as expected. There are some named manuals (see data/output/manual-references).
  • Experimental (optional, not used here): Either "yes" or "no"; if present and set to "yes", the tag will be an experimental 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
# note with Lintian < 2.5.17, you will need to set LINTIAN_ROOT=.
/path/to/lintian.git-dir$ frontend/lintian-info -t illegal-version
E: illegal-version
N:
N:   The package version contains "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, $type, $info) = @_;
  12   # $pkg is the package name
  13   # $type is the package type - for this check it will always be "binary"
  14   # $info is a Lintian::Collect object for the package
  15   #  - for this check it will be a Lintian::Collect::Binary
  16 
  17   # Fetch a field from the control file in the control.tar.gz from the deb.
  18   # This also works on udeb, source and changes packages.
  19   # For source and changes, this will be a field from the ".dsc" and the ".changes" file
  20   my $version = $info->field('version');
  21   # Note fields are "undef" if they are not present; do not assume the presence of any field.
  22   if (defined($version)) {
  23      if ($version =~ m/(foo|bar)/) {
  24         # Issue the illegal-version tag, giving the actual version as "extra" info.
  25         tag 'illegal-version', $version, 'contains', $1;
  26      }
  27   }
  28 }
  29 
  30 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. The API docs for each method will generally have one line like this:

Needs-Info requirements for using field: none
  - OR -
Needs-Info requirements for using unpacked: unpacked
  - OR -
Needs-Info requirements for using binary_relation: Same as binary_field

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 second one, the check would need to declare a "Needs-Info: unpacked". The third one mains that the method "binary_relation" needs "whatever the binary_field method needs" (in the API docs, this will be a link). This is mainly to avoid having to keep transitive dependencies up to date and can only be used inside the Lintian::Collect classes.

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

# note with Lintian < 2.5.17, you will need to set LINTIAN_ROOT=.
/path/to/lintian.git-dir$ frontend/lintian -i --no-override \
    -C illegal /path/to/deb/version-0foo.deb
E: somepackage: illegal-version 0foo contains foo
N: 
N:    The package version contains "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.

For more information on writing checks, please refer to the Lintian::Tutorial::?WritingChecks document in the API docs. In particular, please read the section "Avoiding security issues".

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.

perltidy & perlcritic

Lintian enforces a perltidy/perlcritic profile which can be found in the files .perlcriticrc and .perltidyrc, respectively. Either run perlcritic with the correct profile manually our execute the test 01-critic/checks.

tests to run before submitting patches

In addition to running the new or existing tests for the tags you worked on, make sure that the following execute successfully:

debian/rules runtests onlyrun=01-critic/checks
debian/rules runtests onlyrun=check-descs
debian/rules runtests onlyrun=implemented-tags