# standards-version -- lintian check script -*- perl -*-

# Copyright (C) 1998 Christian Schwarz and Richard Braakman
# Copyright (C) 2008-2009 Russ Allbery
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, you can find it on the World Wide
# Web at http://www.gnu.org/copyleft/gpl.html, or write to the Free
# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
# MA 02110-1301, USA.

package Lintian::standards_version;
use strict;

use POSIX qw(strftime);

use Lintian::Data;
use Tags;
use Util;

our $STANDARDS = Lintian::Data->new('standards-version/release-dates', '\s+');

# In addition to the normal Lintian::Data structure, we also want a list of
# all standards and their release dates so that we can check things like the
# release date of the standard released after the one a package declared.  Do
# that by pulling all data out of the Lintian::Data structure and sorting it
# by release date.  We can also use this to get the current standards version.
our @STANDARDS = sort { $b->[1] <=> $a->[1] }
    map { [ $_, $STANDARDS->value($_) ] } $STANDARDS->all;
our $CURRENT   = $STANDARDS[0][0];
our @CURRENT   = split(/\./, $CURRENT);

sub run {

my $pkg = shift;
my $type = shift;
my $info = shift;

# udebs aren't required to conform to policy, so they don't need
# Standards-Version. (If they have it, though, it should be valid.)
my $version = $info->field('standards-version');
my $pkgs = $info->binaries;
my $all_udeb = 1;
foreach my $bin_type (values %$pkgs) {
    if ($bin_type ne 'udeb') {
        $all_udeb = 0;
        last;
    }
}
if (not defined $version) {
    tag 'no-standards-version-field' unless $all_udeb;
    return 0;
}

# Check basic syntax and strip off the fourth digit.  People are allowed to
# include the fourth digit if they want, but it indicates a non-normative
# change in Policy and is therefore meaningless in the Standards-Version
# field.
unless ($version =~ m/^\s*(\d+\.\d+\.\d+)(?:\.\d+)?\s*$/) {
    tag 'invalid-standards-version', $version;
    return 0;
}
my $stdver = $1;
my ($major, $minor, $patch) = $stdver =~ m/^(\d+)\.(\d+)\.(\d+)/;

# To do some date checking, we have to get the package date from the changelog
# file.  If we can't find the changelog file, assume that the package was
# released today, since that activates the most tags.
my $changes = $info->changelog;
my $pkgdate;
if (defined $changes) {
    my ($entry) = $changes->data;
    $pkgdate = ($entry && $entry->Timestamp) ? $entry->Timestamp : time;
} else {
    $pkgdate = time;
}

# Check for packages dated prior to the date of release of the standards
# version with which they claim to comply.
if ($STANDARDS->known($stdver) && $STANDARDS->value($stdver) > $pkgdate) {
    my $package = strftime('%Y-%m-%d', gmtime $pkgdate);
    my $release = strftime('%Y-%m-%d', gmtime $STANDARDS->value($stdver));
    tag 'timewarp-standards-version', "($package < $release)";
}

my $tag = "$version (current is $CURRENT)";
if (not $STANDARDS->known($stdver)) {
    # Unknown standards version.  Perhaps newer?
    if (   ($major > $CURRENT[0])
        or ($major == $CURRENT[0] and $minor > $CURRENT[1])
        or ($major == $CURRENT[0] and $minor == $CURRENT[1]
            and $patch > $CURRENT[2])) {
        tag 'newer-standards-version', $tag;
    } else {
        tag 'invalid-standards-version', $version;
    }
} elsif ($stdver eq $CURRENT) {
    # Current standard.  Nothing more to check.
    return 0;
} else {
    # Otherwise, we need to see if the standard that this package declares is
    # both new enough to not be ancient and was the current standard at the
    # time the package was uploaded.
    #
    # A given standards version is considered obsolete if the version
    # following it has been out for at least two years (so the current version
    # is never obsolete).
    my $obsdate = time;
    for my $index (0 .. $#STANDARDS) {
        if ($STANDARDS[$index][0] eq $stdver) {
            $obsdate = $STANDARDS[$index - 1][1] if $index > 0;
            last;
        }
    }
    if ($obsdate + (60 * 60 * 24 * 365 * 2) < time) {
        tag 'ancient-standards-version', $tag;
    } else {
        # We have to get the package date from the changelog file.  If we
        # can't find the changelog file, always issue the tag.
        my $changes = $info->changelog;
        if (not defined $changes) {
            tag 'out-of-date-standards-version', $tag;
            return 0;
        }
        my ($entry) = $changes->data;
        my $timestamp = ($entry && $entry->Timestamp) ? $entry->Timestamp : 0;
        for my $standard (@STANDARDS) {
            last if $standard->[0] eq $stdver;
            if ($standard->[1] < $timestamp) {
                tag 'out-of-date-standards-version', $tag;
                last;
            }
        }
    }
}

}

1;

# Local Variables:
# indent-tabs-mode: nil
# cperl-indent-level: 4
# End:
# vim: syntax=perl sw=4 sts=4 ts=4 et shiftround
