openpkg/lint-spec.pl

Mon, 28 Jan 2013 17:37:18 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Mon, 28 Jan 2013 17:37:18 +0100
changeset 758
a2c6460cfb16
permissions
-rw-r--r--

Correct socket error reporting improvement with IPv6 portable code,
after helpful recommendation by Saúl Ibarra Corretgé on OSips devlist.

michael@428 1 ##
michael@428 2 ## lint-spec.pl -- OpenPKG *.spec File Checker
michael@428 3 ## Copyright (c) 2000-2012 OpenPKG GmbH <http://openpkg.com/>
michael@428 4 ##
michael@428 5 ## This software is property of the OpenPKG GmbH, DE MUC HRB 160208.
michael@428 6 ## All rights reserved. Licenses which grant limited permission to use,
michael@428 7 ## copy, modify and distribute this software are available from the
michael@428 8 ## OpenPKG GmbH.
michael@428 9 ##
michael@428 10 ## THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
michael@428 11 ## WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
michael@428 12 ## MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
michael@428 13 ## IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR
michael@428 14 ## CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
michael@428 15 ## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
michael@428 16 ## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
michael@428 17 ## USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
michael@428 18 ## ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
michael@428 19 ## OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
michael@428 20 ## OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
michael@428 21 ## SUCH DAMAGE.
michael@428 22 ##
michael@428 23
michael@428 24 # Perl run-time requirement
michael@428 25 require 5;
michael@428 26 BEGIN {
michael@428 27 eval "use Getopt::Long; use IO;";
michael@428 28 if ($@) {
michael@428 29 print STDERR
michael@428 30 "lint-spec: ERROR: This command requires a full-size Perl installation!\n" .
michael@428 31 "lint-spec: HINT: Install OpenPKG \"perl\" package to use this command.\n";
michael@428 32 exit(1);
michael@428 33 }
michael@428 34 }
michael@428 35
michael@428 36 # OpenPKG instance prefix
michael@428 37 my $my_prefix = $ENV{'OPENPKG_PREFIX'};
michael@428 38 delete $ENV{'OPENPKG_PREFIX'};
michael@428 39
michael@428 40 # program information
michael@428 41 my $progname = "lint-spec";
michael@428 42 my $progvers = "1.0.0";
michael@428 43
michael@428 44 # parameters (defaults)
michael@428 45 my $version = 0;
michael@428 46 my $verbose = 0;
michael@428 47 my $help = 0;
michael@428 48 my $check = 'all';
michael@428 49 my $tmpdir = ($ENV{TMPDIR} || $ENV{TEMPDIR} || "/tmp") . "/$progname";
michael@428 50 my $rpm = "$my_prefix/bin/openpkg rpm";
michael@428 51
michael@428 52 # exception handling support
michael@428 53 $SIG{__DIE__} = sub {
michael@428 54 my ($err) = @_;
michael@428 55 $err =~ s|\s+at\s+.*||s if (not $verbose);
michael@428 56 print STDERR "$progname:ERROR: $err ". ($! ? "($!)" : "") . "\n";
michael@428 57 exit(1);
michael@428 58 };
michael@428 59
michael@428 60 # command line parsing
michael@428 61 Getopt::Long::Configure("bundling");
michael@428 62 my $result = GetOptions(
michael@428 63 'V|version' => \$version,
michael@428 64 'v|verbose' => \$verbose,
michael@428 65 'h|help' => \$help,
michael@428 66 'c|check=s' => \$check,
michael@428 67 't|tmpdir=s' => \$tmpdir,
michael@428 68 'r|rpm=s' => \$rpm,
michael@428 69 ) || die "option parsing failed";
michael@428 70 if ($help) {
michael@428 71 print "Usage: $progname [options] [SPECFILE ...]\n" .
michael@428 72 "Available options:\n" .
michael@428 73 " -v,--verbose enable verbose run-time mode\n" .
michael@428 74 " -h,--help print out this usage page\n" .
michael@428 75 " -c,--check=CHECKS select checks to perform (default='all')\n" .
michael@428 76 " -r,--rpm=FILE filesystem path to RPM program\n" .
michael@428 77 " -t,--tmpdir=PATH filesystem path to temporary directory\n" .
michael@428 78 " -V,--version print program version\n";
michael@428 79 exit(0);
michael@428 80 }
michael@428 81 if ($version) {
michael@428 82 print "OpenPKG $progname $progvers\n";
michael@428 83 exit(0);
michael@428 84 }
michael@428 85
michael@428 86 # verbose message printing
michael@428 87 sub msg_verbose {
michael@428 88 my ($msg) = @_;
michael@428 89 print STDERR "$msg\n" if ($verbose);
michael@428 90 }
michael@428 91
michael@428 92 # warning message printing
michael@428 93 sub msg_warning {
michael@428 94 my ($msg) = @_;
michael@428 95 print STDERR "$progname:WARNING: $msg\n";
michael@428 96 }
michael@428 97
michael@428 98 # error message printing
michael@428 99 sub msg_error {
michael@428 100 my ($msg) = @_;
michael@428 101 print STDERR "$progname:ERROR: $msg\n";
michael@428 102 }
michael@428 103
michael@428 104 # determine check list
michael@428 105 my @check_list = (qw(
michael@428 106 blank
michael@428 107 comment
michael@428 108 license
michael@428 109 header
michael@428 110 section
michael@428 111 preproc
michael@428 112 script
michael@428 113 global
michael@428 114 sources
michael@428 115 digest
michael@428 116 ));
michael@428 117 my @checks = ();
michael@428 118 if ($check eq 'all') {
michael@428 119 @checks = @check_list;
michael@428 120 }
michael@428 121 else {
michael@428 122 foreach my $c (split(/,/, $check)) {
michael@428 123 if (not grep($c, @check_list)) {
michael@428 124 die "invalid check \"$c\"";
michael@428 125 }
michael@428 126 push(@checks, $c);
michael@428 127 }
michael@428 128 }
michael@428 129
michael@428 130 # global return code
michael@428 131 $main::GRC = 0;
michael@428 132
michael@428 133 # iterate over all .spec files
michael@428 134 foreach my $filename (@ARGV) {
michael@428 135 my $io = new IO::File "<$filename"
michael@428 136 or die "unable to open file \"$filename\" for reading";
michael@428 137 my $spec; { local $/ = undef; $spec = <$io>; }
michael@428 138 $io->close;
michael@428 139 foreach my $check (@checks) {
michael@428 140 eval "\&check_$check(\$filename, \$spec);";
michael@428 141 }
michael@428 142 }
michael@428 143
michael@428 144 # die gracefully
michael@428 145 exit($main::GRC);
michael@428 146
michael@428 147 ## _________________________________________________________________
michael@428 148 ##
michael@428 149 ## COMMON SUBROUTINES
michael@428 150 ## _________________________________________________________________
michael@428 151 ##
michael@428 152
michael@428 153 sub lines {
michael@428 154 my ($txt) = @_;
michael@428 155 my $l = 0;
michael@428 156 $txt =~ s|\n|$l++, ''|sge;
michael@428 157 return $l;
michael@428 158 }
michael@428 159
michael@428 160 sub lint_message {
michael@428 161 my ($type, $file, $done, $this, $msg) = @_;
michael@428 162 if (defined($done) and defined($this)) {
michael@428 163 my $start = &lines($done) + 1;
michael@428 164 my $end = $start + &lines($this);
michael@428 165 my $pos = $start;
michael@428 166 $pos .= "-". $end if ($end > $start);
michael@428 167 printf("%s:%s: %s:%s: %s\n", $progname, $type, $file, $pos, $msg);
michael@428 168 }
michael@428 169 else {
michael@428 170 printf("%s:%s: %s: %s\n", $progname, $type, $file, $msg);
michael@428 171 }
michael@428 172 }
michael@428 173
michael@428 174 sub lint_warning {
michael@428 175 my ($file, $done, $this, $msg) = @_;
michael@428 176 &lint_message("WARNING", $file, $done, $this, $msg);
michael@428 177 $main::GRC = 1 if ($main::GRC < 1);
michael@428 178 }
michael@428 179
michael@428 180 sub lint_error {
michael@428 181 my ($file, $done, $this, $msg) = @_;
michael@428 182 &lint_message("ERROR", $file, $done, $this, $msg);
michael@428 183 $main::GRC = 2 if ($main::GRC < 2);
michael@428 184 }
michael@428 185
michael@428 186 ## _________________________________________________________________
michael@428 187 ##
michael@428 188 ## CHECK "blank": whitespace and blank lines
michael@428 189 ## _________________________________________________________________
michael@428 190 ##
michael@428 191
michael@428 192 sub check_blank {
michael@428 193 my ($file, $spec) = @_;
michael@428 194
michael@428 195 # check for CR-LF combination
michael@428 196 my $done = ''; my $this = ''; my $todo = $spec;
michael@428 197 while ($todo =~ m/\r\n/s) {
michael@428 198 $done .= $`; $this = $&; $todo = $';
michael@428 199 &lint_warning($file, $done, $this, "carriage-return (CR, 0x0d) line-feed (NL, 0x0a) combination (expected just line-feed)");
michael@428 200 $done .= $this;
michael@428 201 }
michael@428 202
michael@428 203 # check for multiple blank lines
michael@428 204 $done = ''; $this = ''; $todo = $spec;
michael@428 205 while ($todo =~ m/(\r?\n[ \t]*){3,}/s) {
michael@428 206 $done .= $`; $this = $&; $todo = $';
michael@428 207 &lint_warning($file, $done, $this, "multiple subsequent blank lines (expected single blank line)");
michael@428 208 $done .= $this;
michael@428 209 }
michael@428 210
michael@428 211 # check for trailing whitespaces
michael@428 212 $done = ''; $this = ''; $todo = $spec;
michael@428 213 while ($todo =~ m/[ \t]+\r?\n/s) {
michael@428 214 $done .= $`; $this = $&; $todo = $';
michael@428 215 if ($done eq '' or $done =~ m|\n$|s) {
michael@428 216 &lint_warning($file, $done, $this, "whitespace on empty line (expected none)");
michael@428 217 }
michael@428 218 else {
michael@428 219 &lint_warning($file, $done, $this, "trailing whitespace (expected none)");
michael@428 220 }
michael@428 221 $done .= $this;
michael@428 222 }
michael@428 223
michael@428 224 # check for bogus line continuations
michael@428 225 $done = ''; $this = ''; $todo = $spec;
michael@428 226 while ($todo =~ m/\\[ \t]*\r?\n(?=[ \t]*\r?\n)/s) {
michael@428 227 $done .= $`; $this = $&; $todo = $';
michael@428 228 &lint_warning($file, $done, $this, "bogus line continuation for following empty line (expect no line continuation)");
michael@428 229 $done .= $this;
michael@428 230 }
michael@428 231
michael@428 232 # check for leading whitespaces before line continuations
michael@428 233 $done = ''; $this = ''; $todo = $spec;
michael@428 234 while ($todo =~ m/[ \t]{2,}\\[ \t]*\r?\n/s) {
michael@428 235 $done .= $`; $this = $&; $todo = $';
michael@428 236 &lint_warning($file, $done, $this, "multiple leading whitespace before line continuation (expected just a single space)");
michael@428 237 $done .= $this;
michael@428 238 }
michael@428 239
michael@428 240 # check for leading tabs
michael@428 241 $done = ''; $this = ''; $todo = $spec;
michael@428 242 while ($todo =~ m/^ *\t+ *[^ \t]/m) {
michael@428 243 $done .= $`; $this = $&; $todo = $';
michael@428 244 &lint_warning($file, $done, $this, "leading tabs (expected spaces)");
michael@428 245 $done .= $this;
michael@428 246 }
michael@428 247
michael@428 248 # check for mandatory/wished trailing blank line
michael@428 249 if ($spec !~ m|\n\n$|) {
michael@428 250 &lint_warning($file, $spec, "", "mandatory/wished trailing blank line missing (expected one)");
michael@428 251 }
michael@428 252 }
michael@428 253
michael@428 254 ## _________________________________________________________________
michael@428 255 ##
michael@428 256 ## CHECK "comment": sharp-comments
michael@428 257 ## _________________________________________________________________
michael@428 258 ##
michael@428 259
michael@428 260 sub check_comment {
michael@428 261 my ($file, $spec) = @_;
michael@428 262
michael@428 263 # check for comment indentation and contents
michael@428 264 my $done = ''; my $this = ''; my $todo = $spec;
michael@428 265 while ($todo =~ m/^([ \t]*)(#+)([ \t]*)(.*?)$/m) {
michael@428 266 $done .= $`; $this = $&; $todo = $';
michael@428 267 my ($lead, $sharp, $pad, $text) = ($1, $2, $3, $4);
michael@428 268 if (length($lead) % 2 != 0) {
michael@428 269 &lint_warning($file, $done, $this, "incorrect comment indentation (expected a multiple of 2 spaces)");
michael@428 270 }
michael@428 271 if (length($lead) > 1 && length($sharp) > 1) {
michael@428 272 &lint_warning($file, $done, $this, "indented comment has introduced with multiple sharps (expected single sharp character)");
michael@428 273 }
michael@428 274 if (length($pad.$text) > 0 && length($sharp.$pad) % 4 != 0) {
michael@428 275 &lint_warning($file, $done, $this, "incorrect comment text padding (expected a multiple of 4 sharps or spaces)");
michael@428 276 }
michael@428 277 if (length($pad) == 0 && length($text) > 0) {
michael@428 278 &lint_warning($file, $done, $this, "missing leading space before comment text (expected padding spaces)");
michael@428 279 }
michael@428 280 if (length($pad) > 0 && length($text) == 0) {
michael@428 281 &lint_warning($file, $done, $this, "empty comment text (expected a reasonable text)");
michael@428 282 }
michael@428 283 $done .= $this;
michael@428 284 }
michael@428 285
michael@428 286 # check for comment contents only
michael@428 287 if ($file !~ m|openpkg\.spec$|) {
michael@428 288 $done = ''; $this = ''; $todo = $spec;
michael@428 289 while ($todo =~ m/\n\n[ \t]*#[ \t]+([^\n]+)(?!\n([ \t]*#))/s) {
michael@428 290 $done .= $`; $this = $&; $todo = $';
michael@428 291 my $text = $1;
michael@428 292 if (length($text) > 0 and $text =~ m|^[A-Z][^A-Z]|) {
michael@428 293 &lint_warning($file, $done, $this, "comment text starts with upper-case letter (expected lower-case letter)");
michael@428 294 }
michael@428 295 $done .= $this;
michael@428 296 }
michael@428 297 }
michael@428 298 }
michael@428 299
michael@428 300 ## _________________________________________________________________
michael@428 301 ##
michael@428 302 ## CHECK "license": license header
michael@428 303 ## _________________________________________________________________
michael@428 304 ##
michael@428 305
michael@428 306 sub check_license {
michael@428 307 my ($file, $spec) = @_;
michael@428 308
michael@428 309 my $name = "[a-z][a-z0-9-]*";
michael@428 310 if ($file =~ m|^.*/([a-z][a-z0-9-]*)\.spec$|) {
michael@428 311 $name = $1;
michael@428 312 }
michael@428 313 elsif ($file =~ m|^([a-z][a-z0-9-]*)\.spec$|) {
michael@428 314 $name = $1;
michael@428 315 }
michael@428 316 my $re = "";
michael@428 317 $re .= "##\\n";
michael@428 318 $re .= "## $name\\.spec -- OpenPKG RPM Package Specification\\n";
michael@428 319 $re .= "## Copyright \\(c\\) 200[0-9]-2012 OpenPKG Foundation e\.V\. <http://openpkg\\.net/>\\n";
michael@428 320 $re .= "##\\n";
michael@428 321 $re .= "## Permission to use, copy, modify, and distribute this software for\\n";
michael@428 322 $re .= "## any purpose with or without fee is hereby granted, provided that\\n";
michael@428 323 $re .= "## the above copyright notice and this permission notice appear in all\\n";
michael@428 324 $re .= "## copies\\.\\n";
michael@428 325 $re .= "##\\n";
michael@428 326 $re .= "## THIS SOFTWARE IS PROVIDED \\`\\`AS IS'' AND ANY EXPRESSED OR IMPLIED\\n";
michael@428 327 $re .= "## WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\\n";
michael@428 328 $re .= "## MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED\\.\\n";
michael@428 329 $re .= "## IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR\\n";
michael@428 330 $re .= "## CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\\n";
michael@428 331 $re .= "## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES \\(INCLUDING, BUT NOT\\n";
michael@428 332 $re .= "## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF\\n";
michael@428 333 $re .= "## USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION\\) HOWEVER CAUSED AND\\n";
michael@428 334 $re .= "## ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\\n";
michael@428 335 $re .= "## OR TORT \\(INCLUDING NEGLIGENCE OR OTHERWISE\\) ARISING IN ANY WAY OUT\\n";
michael@428 336 $re .= "## OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\\n";
michael@428 337 $re .= "## SUCH DAMAGE\\.\\n";
michael@428 338 $re .= "##\\n";
michael@428 339 if ($name ne "openpkg" and $spec !~ m|^$re|os) {
michael@428 340 &lint_warning($file, "", "", "invalid license header");
michael@428 341 }
michael@428 342 }
michael@428 343
michael@428 344 ## _________________________________________________________________
michael@428 345 ##
michael@428 346 ## CHECK "header": RPM headers
michael@428 347 ## _________________________________________________________________
michael@428 348 ##
michael@428 349
michael@428 350 sub check_header {
michael@428 351 my ($file, $spec) = @_;
michael@428 352
michael@428 353 my @headers = (qw(
michael@428 354 m:Name:^[a-z][a-z0-9-]*$
michael@428 355 m:Summary:^[A-Z]\S*(\s+([A-Z]\S*|of|for|from|in|at|on|\(\S+\)))*
michael@428 356 m:URL:^((https?|ftp)://.+|-)$
michael@428 357 m:Vendor:.+
michael@428 358 m:Packager:^(OpenPKG\sFoundation\se\.V\.|OpenPKG\sGmbH)$
michael@428 359 m:Distribution:^(OpenPKG|OpenPKG\sCommunity|OpenPKG\sEnterprise)$
michael@428 360 m:Class:^(BOOT|CORE|BASE|PLUS|EVAL|JUNK|PRIV)$
michael@428 361 m:Group:^[A-Z][a-zA-Z0-9]+$
michael@428 362 m:License:.+
michael@428 363 m:Version:^[^-]+$
michael@428 364 m:Release:^(E?([1-9]\.)?20[0-9][0-9](0[1-9]|1[0-2])(0[1-9]|[1-2][0-9]|3[01])|E?[1-9]\.[0-9]\.\d+|%\{[^\}]+\})$
michael@428 365 o:Source\d+:^((https?|ftp)://.+|[^/]+)$
michael@428 366 o:Patch\d+:^((https?|ftp)://.+|[^/]+)$
michael@428 367 o:Prefix:^%{l_prefix}$
michael@428 368 o:BuildRoot:^%{l_buildroot}$
michael@428 369 m:BuildPreReq:^(((,\s+)?digest\\(sha1:\%\{(SOURCE|PATCH)\d+\}\\)\s+=\s+[0-9a-fA-F]+)+|(OpenPKG,\sopenpkg\s>=\s\S+)?((,\s)?([a-z][a-z0-9-]*(\:\:with_[a-z][a-z0-9_]+)?|[A-Z][A-Z0-9-]*)(\s(>=?|==?|<=?|!=)\s\S+)?)+)$
michael@428 370 m:PreReq:^(OpenPKG,\sopenpkg\s>=\s\S+)?((,\s)?([a-z][a-z0-9-]*(\:\:with_[a-z][a-z0-9_]+)?|[A-Z][A-Z0-9-]*)(\s(>=?|==?|<=?|!=)\s\S+)?)+$
michael@428 371 o:AutoReq:^no$
michael@428 372 o:AutoReqProv:^no$
michael@428 373 o:Provides:^((,\s)?([a-z][a-z0-9-]*(\:\:with_[a-z][a-z0-9_]+)?|[A-Z][A-Z0-9-]*)(\s==?\s\S+)?)+$
michael@428 374 o:Conflicts:^((,\s)?([a-z][a-z0-9-]*(\:\:with_[a-z][a-z0-9_]+)?|[A-Z][A-Z0-9-]*)(\s(>=?|==?|<=?|!=)\s\S+)?)+$
michael@428 375 ));
michael@428 376 my @headers_def = (qw(
michael@428 377 Summary:^Badly\sPackaged\sProgram$
michael@428 378 Vendor:^John\sDoe$
michael@428 379 Group:^Unknown$
michael@428 380 License:^DoePL$
michael@428 381 ));
michael@428 382 my @headers_mult = (qw(
michael@428 383 BuildPreReq PreReq Provides Conflicts
michael@428 384 ));
michael@428 385
michael@428 386 my @seen = ();
michael@428 387 my %count = ();
michael@428 388 my $done = ''; my $this = ''; my $todo = $spec;
michael@428 389 while ($todo =~ m/^(\S+):([ \t]*)(.*?)$/m) {
michael@428 390 $done .= $`; $this = $&; $todo = $';
michael@428 391 my ($header, $pad, $value) = ($1, $2, $3);
michael@428 392
michael@428 393 # check for layouting
michael@428 394 if (length($value) == 0) {
michael@428 395 &lint_error($file, $done, $this, "empty RPM header value");
michael@428 396 }
michael@428 397 if (length($header.":".$pad) != 14) {
michael@428 398 &lint_warning($file, $done, $this, "invalid RPM header name/value padding (expected value at column 15)");
michael@428 399 }
michael@428 400
michael@428 401 # check for valid header name and value
michael@428 402 if (not grep { $header =~ m|^$_$|s } map { m/^[^:]:([^:]+):/, $1 } @headers) {
michael@428 403 &lint_error($file, $done, $this, "invalid RPM header name \"$header\"");
michael@428 404 }
michael@428 405 else {
michael@428 406 my $hn = quotemeta((grep { $header =~ m|^$_$|s } map { m/^[^:]+:([^:]+):/, $1 } @headers)[0]);
michael@428 407 my $re = (map { m/^[^:]+:${hn}:(.+)$/s } @headers)[0];
michael@428 408 my $re_match = $re;
michael@428 409 $re_match =~ s|^\(|(?:|sg;
michael@428 410 $re_match =~ s|([^\\])\(|\1(?:|sg;
michael@428 411 if ( not ($file =~ m|openpkg\.spec$| and $header eq 'Provides' and $value eq '%{l_prefix}')
michael@428 412 and $value !~ m|${re_match}|s) {
michael@428 413 &lint_warning($file, $done, $this, "RPM header \"$header\": " .
michael@428 414 "invalid value \"$value\" (expected to match \"$re\")");
michael@428 415 }
michael@428 416 my $re_def = (map { m/^${hn}:(.+)$/s } @headers_def)[0];
michael@428 417 if (defined($re_def)) {
michael@428 418 my $re_def_match = $re_def;
michael@428 419 $re_def_match =~ s|^\(|(?:|sg;
michael@428 420 $re_def_match =~ s|([^\\])\(|\1(?:|sg;
michael@428 421 if ($value =~ m|${re_def_match}|s) {
michael@428 422 &lint_warning($file, $done, $this, "RPM header \"$header\": " .
michael@428 423 "default value \"$value\" (expected to match \"$re\", except for this)");
michael@428 424 }
michael@428 425 }
michael@428 426 if ($header =~ m/^Vendor/ and $value =~ m/et\sal/ and $value !~ m/et\sal\./) {
michael@428 427 &lint_warning($file, $done, $this, "RPM header \"$header\": " .
michael@428 428 "has value \"$value\" (expected \"et al.\" with dot)");
michael@428 429 }
michael@428 430 }
michael@428 431 push(@seen, $header);
michael@428 432 $count{$header}++;
michael@428 433 $done .= $this;
michael@428 434 }
michael@428 435
michael@428 436 # check for existence of mandatory headers
michael@428 437 foreach my $header (@headers) {
michael@428 438 my ($type, $name, $regex) = split(/:/, $header, 3);
michael@428 439 if ($type eq 'm') {
michael@428 440 if ($file =~ m|openpkg\.spec$| and
michael@428 441 ($name eq "BuildPreReq" or $name eq "PreReq")) {
michael@428 442 # the bootstrap package is an obvious exception
michael@428 443 next;
michael@428 444 }
michael@428 445 if (not grep(/^$name$/, @seen)) {
michael@428 446 &lint_warning($file, undef, undef, "mandatory RPM header \"$name\" not found");
michael@428 447 }
michael@428 448 }
michael@428 449 }
michael@428 450
michael@428 451 # check for multiple occurrence headers
michael@428 452 foreach my $seen (@seen) {
michael@428 453 if ($count{$seen} > 1 and not (grep { $_ eq $seen } @headers_mult)) {
michael@428 454 &lint_error($file, undef, undef, "RPM header \"$seen\" occurs multiple times (expected just once)");
michael@428 455 }
michael@428 456 }
michael@428 457 }
michael@428 458
michael@428 459 ## _________________________________________________________________
michael@428 460 ##
michael@428 461 ## CHECK "section": RPM sections
michael@428 462 ## _________________________________________________________________
michael@428 463 ##
michael@428 464
michael@428 465 sub check_section {
michael@428 466 my ($file, $spec) = @_;
michael@428 467
michael@428 468 my $require = qq{
michael@428 469 (%define,)*
michael@428 470 Name:,
michael@428 471 Summary:,
michael@428 472 URL:,
michael@428 473 Vendor:,
michael@428 474 Packager:,
michael@428 475 Distribution:,
michael@428 476 Class:,
michael@428 477 Group:,
michael@428 478 License:,
michael@428 479 Version:,
michael@428 480 Release:,
michael@428 481 (%option,)*
michael@428 482 (%define,|%undefine,)*
michael@428 483 (Source\\d+:,)*
michael@428 484 (Patch\\d+:,)*
michael@428 485 (%NoSource,)*
michael@428 486 (%NoPatch,)*
michael@428 487 (Prefix:,)?
michael@428 488 (BuildRoot:,)?
michael@428 489 ((BuildPreReq:,)
michael@428 490 |(PreReq:,))*
michael@428 491 (AutoReq:,)?
michael@428 492 (AutoReqProv:,)?
michael@428 493 (Provides:,)*
michael@428 494 (Conflicts:,)*
michael@428 495 %description,
michael@428 496 (%track,)?
michael@428 497 %prep,
michael@428 498 %build,
michael@428 499 %install,
michael@428 500 (%check,)?
michael@428 501 %files,
michael@428 502 %clean,
michael@428 503 (%pre,)?
michael@428 504 (%post,)?
michael@428 505 (%preun,)?
michael@428 506 (%postun,)?
michael@428 507 (%trigger,)?
michael@428 508 (%triggerin,)?
michael@428 509 (%triggerun,)?
michael@428 510 (%triggerpostun,)?
michael@428 511 (%verifyscript,)?
michael@428 512 };
michael@428 513
michael@428 514 # check for order of headers
michael@428 515 my $sections = "";
michael@428 516 my $done = ''; my $this = ''; my $todo = $spec;
michael@428 517 while ($todo =~ m/^(\S+:|%\S+).*$/m) {
michael@428 518 $done .= $`; $this = $&; $todo = $';
michael@428 519 my $section = $1;
michael@428 520 next if ($section =~ m/^%(if|else|endif)/);
michael@428 521 $sections .= "$section,";
michael@428 522 $done .= $this;
michael@428 523 }
michael@428 524 my $regex = $require;
michael@428 525 $regex =~ s|\s+||sg;
michael@428 526 if ($sections !~ m/^$regex$/s) {
michael@428 527 $regex =~ s|,| |sg;
michael@428 528 &lint_error($file, undef, undef, "invalid RPM section order: $sections (expected \"$regex\")");
michael@428 529 }
michael@428 530 }
michael@428 531
michael@428 532 ## _________________________________________________________________
michael@428 533 ##
michael@428 534 ## CHECK "preproc": RPM macro pre-processor
michael@428 535 ## _________________________________________________________________
michael@428 536 ##
michael@428 537
michael@428 538 sub check_preproc {
michael@428 539 my ($file, $spec) = @_;
michael@428 540
michael@428 541 my $done = ''; my $this = ''; my $todo = $spec;
michael@428 542 while ($todo =~ m/^(%(?:if|else|endif|define|undefine|option))(.*)$/m) {
michael@428 543 $done .= $`; $this = $&; $todo = $';
michael@428 544 my ($cmd, $args) = ($1, $2);
michael@428 545 if ($cmd eq '%if') {
michael@428 546 # FIXME: either try to really parse the boolean expression
michael@428 547 # FIXME: or at least try to guess its correct syntax
michael@428 548 }
michael@428 549 elsif ($cmd eq '%else' or $cmd eq '%endif') {
michael@428 550 if (length($args) > 0) {
michael@428 551 &lint_warning($file, $done, $this, "garbage after pre-processor directive " .
michael@428 552 "\"$cmd\" (expected no arguments)");
michael@428 553 }
michael@428 554 }
michael@428 555 elsif ($cmd eq '%undefine') {
michael@428 556 if ($args =~ m|^\s+(\S+)\s*$|) {
michael@428 557 my $var = $1;
michael@428 558 if ($var !~ m/^(V|with)_[a-z][a-zA-Z0-9_]*$/) {
michael@428 559 &lint_warning($file, $done, $this, "unusually named macro: \"$var\" " .
michael@428 560 "(expected \"(V|with)_[a-z][a-zA-Z0-9_]*\")");
michael@428 561 next;
michael@428 562 }
michael@428 563 }
michael@428 564 else {
michael@428 565 &lint_error($file, $done, $this, "invalid number of arguments to pre-processor " .
michael@428 566 "directive \"$cmd\" (expected exactly 1 argument)");
michael@428 567 }
michael@428 568 }
michael@428 569 elsif ($cmd eq '%define' or $cmd eq '%option') {
michael@428 570 if ($args =~ m|^\s+(\S+)\s+(.*)$|) {
michael@428 571 my ($var, $val) = ($1, $2);
michael@428 572 if ($var !~ m/^(V|with)_[a-z][a-zA-Z0-9_]*$/) {
michael@428 573 &lint_warning($file, $done, $this, "unusually named macro: \"$var\" " .
michael@428 574 "(expected \"(V|with)_[a-z][a-zA-Z0-9_]*\")");
michael@428 575 next;
michael@428 576 }
michael@428 577 if (length($val) == 0) {
michael@428 578 &lint_error($file, $done, $this, "empty macro value");
michael@428 579 }
michael@428 580 }
michael@428 581 else {
michael@428 582 &lint_error($file, $done, $this, "invalid number of arguments to pre-processor " .
michael@428 583 "directive \"$cmd\" (expected exactly 2 arguments)");
michael@428 584 }
michael@428 585 }
michael@428 586 $done .= $this;
michael@428 587 }
michael@428 588
michael@428 589 # check correct if/endif nesting
michael@428 590 my @stack = ();
michael@428 591 $done = ''; $this = ''; $todo = $spec;
michael@428 592 while ($todo =~ m/^(%(?:if|else|endif|define|undefine|option)).*$/m) {
michael@428 593 $done .= $`; $this = $&; $todo = $';
michael@428 594 my $directive = $1;
michael@428 595 if ($directive eq '%if') {
michael@428 596 push(@stack, &lines($done . $this));
michael@428 597 }
michael@428 598 elsif ($directive eq '%endif') {
michael@428 599 if (@stack == 0) {
michael@428 600 &lint_error($file, $done, $this, "found \%endif without corresponding opening \%if");
michael@428 601 last;
michael@428 602 }
michael@428 603 pop(@stack);
michael@428 604 }
michael@428 605 $done .= $this;
michael@428 606 }
michael@428 607 my $line;
michael@428 608 while (defined($line = pop(@stack))) {
michael@428 609 &lint_error($file, undef, undef, "\%if at line $line never closed by \%endif");
michael@428 610 }
michael@428 611
michael@428 612 # check for indented preprocessor constructs
michael@428 613 $done = ''; $this = ''; $todo = $spec;
michael@428 614 while ($todo =~ m/^[ \t]+(%(?:if|else|endif|define|undefine|option)).*$/m) {
michael@428 615 $done .= $`; $this = $&; $todo = $';
michael@428 616 my $directive = $1;
michael@428 617 &lint_error($file, $done, $this, "found indented \"$directive\" preprocessor directive (expected no indentation)");
michael@428 618 $done .= $this;
michael@428 619 }
michael@428 620 }
michael@428 621
michael@428 622 ## _________________________________________________________________
michael@428 623 ##
michael@428 624 ## CHECK "script": shell scripts
michael@428 625 ## _________________________________________________________________
michael@428 626 ##
michael@428 627
michael@428 628 sub check_script {
michael@428 629 my ($file, $spec) = @_;
michael@428 630
michael@428 631 my $done = ''; my $this = ''; my $todo = $spec;
michael@428 632 while ($todo =~ m/(\%(?:description|prep|build|install|check|files|clean|pre|post|preun|postun|trigger|triggerin|triggerun|triggerpostun|verifyscript))([^\n]*)\n(.*?\n)(?=\%(?:description|prep|build|install|check|files|clean|pre|post|preun|postun|trigger|triggerin|triggerun|triggerpostun|verifyscript)|$)/s) {
michael@428 633 $done .= $`; $this = $&; $todo = $';
michael@428 634 my ($section, $args, $script) = ($1, $2, $3);
michael@428 635
michael@428 636 # perform checks for a single script section
michael@428 637 &check_script_section($file, $done, $this, $section, $args, $script);
michael@428 638
michael@428 639 $done .= $this;
michael@428 640 }
michael@428 641 }
michael@428 642
michael@428 643 sub check_script_section {
michael@428 644 my ($file, $outer_done, $outer_this, $section, $args, $script) = @_;
michael@428 645
michael@428 646 # skip plain-text/non-scripting section %description
michael@428 647 return if ($section eq '%description');
michael@428 648
michael@428 649 # remove comment contents
michael@428 650 $outer_this =~ s|^[ \t]*#[^\n]*||mg;
michael@428 651
michael@428 652 # check shell redirections
michael@428 653 my $done = $outer_done; my $this = ''; my $todo = $outer_this;
michael@428 654 while ( $todo =~ m/[ \t]+(\d+)?[><][ \t]+\S+/s
michael@428 655 or $todo =~ m/[ \t]+[><](\&\d+)?[ \t]+\S+/s) {
michael@428 656 $done .= $`; $this = $&; $todo = $';
michael@428 657 &lint_warning($file, $done, $this, "section $section: whitespace after shell redirection (expected none)");
michael@428 658 $done .= $this;
michael@428 659 }
michael@428 660
michael@428 661 # utility function: extract a single shell command
michael@428 662 sub command_extract {
michael@428 663 my ($script) = @_;
michael@428 664 my $cmd = '';
michael@428 665 while ($script ne '') {
michael@428 666 $script =~ s/^([ \t]*'[^']*')/ $cmd .= $1, ''/se && next;
michael@428 667 $script =~ s/^([ \t]*"[^"]*")/ $cmd .= $1, ''/se && next;
michael@428 668 $script =~ s/^([ \t]*[^ \t;\)\\\r\n]+)/$cmd .= $1, ''/se && next;
michael@428 669 $script =~ s/^([ \t]*\\[ \t]*\r?\n)/ $cmd .= $1, ''/se && next;
michael@428 670 last;
michael@428 671 }
michael@428 672 return ($cmd, $script);
michael@428 673 }
michael@428 674
michael@428 675 # utility function: join a multi-line command
michael@428 676 sub multiline_join {
michael@428 677 my ($cmd) = @_;
michael@428 678 $cmd =~ s/([ \t]*\\[ \t]*\r?\n[ \t]*)/ /sg;
michael@428 679 return $cmd;
michael@428 680 }
michael@428 681
michael@428 682 # utility function: split command into arguments
michael@428 683 sub shell_tokenize {
michael@428 684 my ($cmd) = @_;
michael@428 685 my @cmd = ();
michael@428 686 while ($cmd ne '') {
michael@428 687 $cmd =~ s/^\s*('[^']*')/push(@cmd, $1), ''/se && next;
michael@428 688 $cmd =~ s/^\s*("[^"]*")/push(@cmd, $1), ''/se && next;
michael@428 689 $cmd =~ s/^\s*(\S+)/ push(@cmd, $1), ''/se && next;
michael@428 690 $cmd =~ s/^\s*$/ ''/se && last;
michael@428 691 }
michael@428 692 return @cmd;
michael@428 693 }
michael@428 694
michael@428 695 # check filesystem path style
michael@428 696 $done = $outer_done; $this = ''; $todo = $outer_this;
michael@428 697 while ($todo =~ m/\%\{l_shtool\}\s+(\w+)\s+/s) {
michael@428 698 $done .= $`; $this = $&; $todo = $';
michael@428 699 ($this, $todo) = &command_extract($this . $todo);
michael@428 700
michael@428 701 # check for shtool options with no space before argument
michael@428 702 my $subthis = $this;
michael@428 703 $subthis =~ s/\%{[A-Za-z][A-Za-z0-9_]*}//sg;
michael@428 704 $subthis =~ s/\%{[A-Za-z][A-Za-z0-9_]*(?:\s+[^}]+?)?}//sg;
michael@428 705 # remove content of quoted arguments as they might contain unrelated things to catch
michael@428 706 $subthis =~ s/'[^']*'/ARG/sg;
michael@428 707 $subthis =~ s/"[^"]*"/ARG/sg;
michael@428 708 $subthis =~ s/`[^`]*`/ARG/sg;
michael@428 709 # remove us completely if line ends with a quote as this indicates we are a quoted argument to ourselfs
michael@428 710 $subthis =~ s/[^']*'$//s;
michael@428 711 $subthis =~ s/[^"]*"$//s;
michael@428 712 $subthis =~ s/[^`]*`$//s;
michael@428 713 $subthis =~ s/[^']*' \\\n//s;
michael@428 714 $subthis =~ s/[^"]*" \\\n//s;
michael@428 715 $subthis =~ s/[^`]*` \\\n//s;
michael@428 716 # catch command termination by semicolon, pipe, or, and;
michael@428 717 $subthis =~ s/[;|&].*$//s;
michael@428 718 if ($subthis =~ m/\s-[a-zA-Z]\S/) {
michael@428 719 &lint_warning($file, $done, $this, "found use of shtool option with space omitted before argument");
michael@428 720 }
michael@428 721
michael@428 722 # openpkg-rc is special because does bootstrap things
michael@428 723 last if ($file =~ m|openpkg-rc\.spec$|);
michael@428 724
michael@428 725 my @cmd = &shell_tokenize(&multiline_join($this));
michael@428 726 if ($cmd[1] eq 'mkdir') {
michael@428 727 # join flags with their arguments
michael@428 728 for (my $i = 2; $i <= $#cmd; $i++) {
michael@428 729 if ($cmd[$i] eq '-m') {
michael@428 730 splice(@cmd, $i, 2, $cmd[$i].$cmd[$i+1]);
michael@428 731 }
michael@428 732 }
michael@428 733 # check paths
michael@428 734 for (my $i = $#cmd; $i > 1 and $cmd[$i] !~ m|^-| and $cmd[$i] !~ m|^\d+$|; $i--) {
michael@428 735 if ($cmd[$i] =~ m|/$|) {
michael@428 736 &lint_warning($file, $done, $this, "section $section: superfluous trailing slash on " .
michael@428 737 "created path in \"shtool mkdir\" command (expected none)");
michael@428 738 }
michael@428 739 }
michael@428 740 }
michael@428 741 elsif ($cmd[1] eq 'install') {
michael@428 742 # join flags with their arguments
michael@428 743 for (my $i = 2; $i <= $#cmd; $i++) {
michael@428 744 if ($cmd[$i] =~ m/-(e|m|o|g)$/) {
michael@428 745 splice(@cmd, $i, 2, $cmd[$i].$cmd[$i+1]);
michael@428 746 }
michael@428 747 elsif ($cmd[$i] eq '%{SOURCE') {
michael@428 748 splice(@cmd, $i, 2, $cmd[$i].$cmd[$i+1]);
michael@428 749 }
michael@428 750 elsif ($cmd[$i] eq '%{l_value') {
michael@428 751 while ($i < $#cmd and $cmd[$i+1] !~ m|\}$|s) {
michael@428 752 splice(@cmd, $i, 2, $cmd[$i].$cmd[$i+1]);
michael@428 753 }
michael@428 754 splice(@cmd, $i, 2);
michael@428 755 $i--;
michael@428 756 }
michael@428 757 }
michael@428 758
michael@428 759 # determine last path argument
michael@428 760 my $i = $#cmd; $i-- while ($i > 1 and $cmd[$i] !~ m|^-| and $cmd[$i] !~ m|^\d+$|);
michael@428 761
michael@428 762 # check paths
michael@428 763 if (($#cmd - $i) > 2 and $cmd[-1] !~ m|/$|) {
michael@428 764 # simple case: multiple sources require target to be a directory
michael@428 765 &lint_warning($file, $done, $this, "section $section: missing trailing slash on " .
michael@428 766 "destination path in \"shtool install\" command (expected one) 1");
michael@428 767 }
michael@428 768 elsif (($#cmd - $i) == 2 and $cmd[-1] !~ m|/$|) {
michael@428 769 # complex case: single source, so we can check only known destination paths
michael@428 770 if ( $cmd[-1] =~ m/\%\{l_prefix\}\/(bin|cgi|include|info|lib|pub|sbin)$/
michael@428 771 or $cmd[-1] =~ m/\%\{l_prefix\}\/(etc|libexec|man|share|var)(\/[^\/]+)?$/) {
michael@428 772 &lint_warning($file, $done, $this, "section $section: missing trailing slash on " .
michael@428 773 "destination path in \"shtool install\" command (expected one) 2");
michael@428 774 }
michael@428 775 }
michael@428 776 else {
michael@428 777 # special case: if any path contains '*', then globbing might occur
michael@428 778 for (my $i = 2; $i <= $#cmd; $i++) {
michael@428 779 if ($cmd[$i] =~ m/\*/ and $cmd[-1] !~ m|/$|) {
michael@428 780 &lint_warning($file, $done, $this, "section $section: missing trailing slash on " .
michael@428 781 "destination path in \"shtool install\" command (expected one) 3");
michael@428 782 last;
michael@428 783 }
michael@428 784 }
michael@428 785 }
michael@428 786 }
michael@428 787 $done .= $this;
michael@428 788 }
michael@428 789
michael@428 790 # check for redundant 'export PATH' statements
michael@428 791 #$done = $outer_done; $this = ''; $todo = $outer_this;
michael@428 792 #while ($todo =~ m/\bexport[ \t]+([a-zA-Z_][a-zA-Z0-9_]*[ \t]+)*PATH\b/s) {
michael@428 793 # $done .= $`; $this = $&; $todo = $';
michael@428 794 # &lint_warning($file, $done, $this, "section $section: redundant \"export PATH\" statement (expected none)");
michael@428 795 # $done .= $this;
michael@428 796 #}
michael@428 797
michael@428 798 # check for obsolete cpp build flags
michael@428 799 $done = $outer_done; $this = ''; $todo = $outer_this;
michael@428 800 while ($todo =~ m/-I\%\{l_prefix\}\/include(\/([^ \t"';]+?))?[ \t"';]/s) {
michael@428 801 $done .= $`; $this = $&; $todo = $';
michael@428 802 my ($subdir) = $2;
michael@428 803 &lint_warning($file, $done, $this, "section $section: hard-coded C pre-processor path option " .
michael@428 804 "(use \"%{l_cppflags".($subdir ? " $subdir" : "")."}\" instead)");
michael@428 805 $done .= $this;
michael@428 806 }
michael@428 807
michael@428 808 # check for obsolete ld build flags
michael@428 809 $done = $outer_done; $this = ''; $todo = $outer_this;
michael@428 810 while ($todo =~ m/-L\%\{l_prefix\}\/lib(\/([^ \t"';]+?))?[ \t"';]/s) {
michael@428 811 $done .= $`; $this = $&; $todo = $';
michael@428 812 my ($subdir) = $2;
michael@428 813 &lint_warning($file, $done, $this, "section $section: hard-coded linker path option " .
michael@428 814 "(use \"%{l_ldflags".($subdir ? " $subdir" : "")."}\" instead)");
michael@428 815 $done .= $this;
michael@428 816 }
michael@428 817
michael@428 818 # check for "raw" tool usage
michael@428 819 if ($section ne '%description' and $file !~ m|openpkg\.spec$|) {
michael@428 820 foreach my $token (&shell_tokenize($outer_this)) {
michael@428 821 if ($token =~ m/^(rpmtool|shtool|curl|bash|gzip|bzip2|tar|cc|cxx|make|patch)$/s) {
michael@428 822 &lint_warning($file, undef, undef, "section $section: raw usage of standard tool \"$token\"" .
michael@428 823 " (use \"%{l_${token}}\" instead)");
michael@428 824 }
michael@428 825 }
michael@428 826 }
michael@428 827
michael@428 828 # check for setup and patch macro usage
michael@428 829 if ($section eq '%prep') {
michael@428 830 $done = $outer_done; $this = ''; $todo = $outer_this;
michael@428 831 my @tokens = &shell_tokenize($outer_this);
michael@428 832 while ($todo =~ m/([^\n]*)\n/s) {
michael@428 833 $done .= $`; $this = $&; $todo = $';
michael@428 834 if ($this =~ m/^ *%setup.* .*$/) {
michael@428 835 &lint_warning($file, undef, undef, "section $section: multiple spaces in \"\%setup\" macro" .
michael@428 836 " (reduce to single space)");
michael@428 837 }
michael@428 838 if ($this =~ m/^ *%setup.*-[ab]$/ and grep(/^\%setup+$/, @tokens) == 1) {
michael@428 839 &lint_warning($file, undef, undef, "section $section: -[ab] option superflous for single \"\%setup\" macro" .
michael@428 840 " (remove it)");
michael@428 841 }
michael@428 842 if ($this =~ m/^ *%patch.* .*$/) {
michael@428 843 &lint_warning($file, undef, undef, "section $section: multiple spaces in \"\%patch\" macro" .
michael@428 844 " (reduce to single space)");
michael@428 845 }
michael@428 846 if ($this =~ m/^ *%patch.*-p +[01]/) {
michael@428 847 &lint_warning($file, undef, undef, "section $section: syntactically wrong space in \"\%patch\" macro" .
michael@428 848 " (use -pN)");
michael@428 849 }
michael@428 850 if ($this =~ m/^ *%patch.*-P 0\s*$/ and grep(/^\%patch+$/, @tokens) == 1) {
michael@428 851 &lint_warning($file, undef, undef, "section $section: syntactically wrong space in \"\%patch\" macro" .
michael@428 852 " (omit -P 0 for single patch)");
michael@428 853 }
michael@428 854 if ($this =~ m/^ *%patch.*-P [^0-9]/ and grep(/^\%patch+$/, @tokens) > 1) {
michael@428 855 &lint_warning($file, undef, undef, "section $section: syntactically wrong space in \"\%patch\" macro" .
michael@428 856 " (use -P for multiple patches)");
michael@428 857 }
michael@428 858 $done .= $this;
michael@428 859 }
michael@428 860 if (grep(/^\%setup\d+$/, @tokens)) {
michael@428 861 &lint_warning($file, undef, undef, "section $section: numbered \"\%setup\" macro" .
michael@428 862 " (do not use %setup directly followed by a number, replace with %setup ... -[ab])");
michael@428 863 }
michael@428 864 if (grep(/^\%patch\d+$/, @tokens)) {
michael@428 865 &lint_warning($file, undef, undef, "section $section: numbered \"\%patch\" macro" .
michael@428 866 " (do not use %patch directly followed by a number, replace with %patch ... -P)");
michael@428 867 }
michael@428 868 }
michael@428 869
michael@428 870 # check for hard-coded prefix in %pre[un],%post[un] scripts
michael@428 871 if ($section =~ m/^\%(preun|postun|pre|post)$/s and $file !~ m|openpkg\.spec$|) {
michael@428 872 $done = $outer_done; $this = ''; $todo = $outer_this;
michael@428 873 while ($todo =~ m/\%\{l_prefix\}/s) {
michael@428 874 $done .= $`; $this = $&; $todo = $';
michael@428 875 &lint_warning($file, $done, $this, "section $section: hard-coded prefix (\%{l_prefix}) found " .
michael@428 876 "(use \"\$RPM_INSTALL_PREFIX\" to allow relocation)");
michael@428 877 $done .= $this;
michael@428 878 }
michael@428 879 }
michael@428 880
michael@428 881 # check for sub-shell parenthesis style
michael@428 882 # (this is such complicated because the Bourne Shell has a
michael@428 883 # construct "case <value> in <pattern> ) <script> ;; ... esac"
michael@428 884 # where the closing parenthesis makes heavy problems for our
michael@428 885 # check. So we first have to get rid of this. Unfortunately this
michael@428 886 # is again not easy because there exists nested(!) case/esac
michael@428 887 # constructs. Hence, we have to use a small recursive descent
michael@428 888 # parser which replaces the ")" in case/esac constructs with
michael@428 889 # "PCLOSE". A similar thing is done with string literals, although
michael@428 890 # here the job is more trivial).
michael@428 891 if ($section !~ m/^\%files$/s) {
michael@428 892 $done = $outer_done; $this = ''; $todo = $outer_this;
michael@428 893 $todo =~ s/`[^`]*`/STRING/sg;
michael@428 894 $todo =~ s/'[^']*'/STRING/sg;
michael@428 895 $todo =~ s/"[^"]*"/STRING/sg;
michael@428 896 $todo = (&parse_sh("", $todo))[0];
michael@428 897 sub parse_sh {
michael@428 898 my ($done, $todo) = @_;
michael@428 899 while ($todo =~ m/^(.*?)(case|;;)/s) {
michael@428 900 if ($2 eq 'case') {
michael@428 901 ($done, $todo) = &parse_case($done.$1, $2.$');
michael@428 902 }
michael@428 903 else {
michael@428 904 ($done, $todo) = ($done.$1, $2.$');
michael@428 905 return ($done, $todo);
michael@428 906 }
michael@428 907 }
michael@428 908 ($done, $todo) = ($done . $todo, "");
michael@428 909 return ($done, $todo);
michael@428 910 }
michael@428 911 sub parse_case {
michael@428 912 my ($done, $todo) = @_;
michael@428 913 $todo =~ m|^\s*case\s+\S+\s+in\b|s or return (undef, undef);
michael@428 914 ($done, $todo) = ($done . $&, $');
michael@428 915 while ($todo =~ m|^(\s*[^)\n]+)(\))|s) {
michael@428 916 ($done, $todo) = ($done . $1 . "PCLOSE", $');
michael@428 917 ($done, $todo) = &parse_sh($done, $todo) or return (undef, undef);
michael@428 918 $todo =~ m|^\s*;;|s or return (undef, undef);
michael@428 919 ($done, $todo) = ($done . $&, $');
michael@428 920 }
michael@428 921 $todo =~ m|^\s*esac|s or return (undef, undef);
michael@428 922 ($done, $todo) = ($done . $&, $');
michael@428 923 return ($done, $todo);
michael@428 924 }
michael@428 925 if ($file !~ m|openpkg\.spec$| and $todo eq '') {
michael@428 926 &lint_warning($file, $outer_done, $outer_this,
michael@428 927 "unable to correctly parse case/esac constructs in shell-script" .
michael@428 928 " (sub-shell parenthesis check skipped)");
michael@428 929 }
michael@428 930 else {
michael@428 931 while ($todo =~ m/\)[ \t]*\n/s) {
michael@428 932 $done .= $`; $this = $&; $todo = $';
michael@428 933 &lint_warning($file, $done, $this, "problematic sub-shell construct without error handling " .
michael@428 934 "(use \"...) || exit \$?\" to make sure it correctly exits on nested failure)");
michael@428 935 $done .= $this;
michael@428 936 }
michael@428 937 }
michael@428 938 }
michael@428 939 }
michael@428 940
michael@428 941 ## _________________________________________________________________
michael@428 942 ##
michael@428 943 ## CHECK "global": globals
michael@428 944 ## _________________________________________________________________
michael@428 945 ##
michael@428 946
michael@428 947 sub check_global {
michael@428 948 my ($file, $spec) = @_;
michael@428 949
michael@428 950 # check for deprecated use of %{name} macro
michael@428 951 my $done = ''; my $this = ''; my $todo = $spec;
michael@428 952 while ($todo =~ m/\%\{name\}/s) {
michael@428 953 $done .= $`; $this = $&; $todo = $';
michael@428 954 &lint_warning($file, $done, $this, "deprecated usage of %{name} macro (expected none)");
michael@428 955 $done .= $this;
michael@428 956 }
michael@428 957
michael@428 958 # FIXME: activate later
michael@428 959 # check for over-long lines
michael@428 960 #$done = ''; $this = ''; $todo = $spec;
michael@428 961 #while ($todo =~ m/([^\n]{80,})\n/s) {
michael@428 962 # $done .= $`; $this = $&; $todo = $';
michael@428 963 # &lint_warning($file, $done, $this, "over-long line with ".sprintf("%d", length($1))." characters " .
michael@428 964 # "(expected maximum of 79 characters)");
michael@428 965 # $done .= $this;
michael@428 966 #}
michael@428 967
michael@428 968 # check for "shtool install" with -e on same line
michael@428 969 my $done = ''; my $this = ''; my $todo = $spec;
michael@428 970 while ($todo =~ m/%{l_shtool} install[^\n]*-e/s) {
michael@428 971 $done .= $`; $this = $&; $todo = $';
michael@428 972 &lint_warning($file, $done, $this, "found -e option of \"shtool install\" on same line (expected separate lines)");
michael@428 973 $done .= $this;
michael@428 974 }
michael@428 975
michael@428 976 # check for "shtool install" with multiple -e on same line
michael@428 977 my $done = ''; my $this = ''; my $todo = $spec;
michael@428 978 while ($todo =~ m/%{l_shtool} install[^\n]+\\\n([^\n]*?-e[^\n]*?-e[^\n]*\\\n)+/s) {
michael@428 979 $done .= $`; $this = $&; $todo = $';
michael@428 980 &lint_warning($file, $done, $this, "found multiple -e options of \"shtool install\" on same line (expected separate lines)");
michael@428 981 $done .= $this;
michael@428 982 }
michael@428 983
michael@428 984 # check for deprecated use of "shtool install|subst -e @l_...@ %{l_...}"
michael@428 985 if ($file !~ m|openpkg-tool|) {
michael@428 986 my $done = ''; my $this = ''; my $todo = $spec;
michael@428 987 while ($todo =~ m/%{l_shtool} (install|subst)(?:\\\n|[^\n])*?-e(?:\\\n|[^\n])*?\@l_(prefix|[smrn](usr|grp|uid|gid))\@[^n]+%{l_\2}/s) {
michael@428 988 $done .= $`; $this = $&; $todo = $';
michael@428 989 &lint_warning($file, $done, $this, "deprecated usage of \"shtool $1 -e \@l_$2\@\" (expected use of %{l_value -s -a})");
michael@428 990 $done .= $this;
michael@428 991 }
michael@428 992 }
michael@428 993
michael@428 994 # check for constant "root" in %attr
michael@428 995 my $done = ''; my $this = ''; my $todo = $spec;
michael@428 996 while ($todo =~ m/%attr\(\s*(\d+)\s*,\s*root\s*,/s) {
michael@428 997 $done .= $`; $this = $&; $todo = $';
michael@428 998 &lint_warning($file, $done, $this, "found constant \"root\" in %attr (expected variable %{l_susr})");
michael@428 999 $done .= $this;
michael@428 1000 }
michael@428 1001
michael@428 1002 # check for valid RPM macro usage
michael@428 1003 if ($file !~ m|^(.+/)?openpkg\.spec$|s) {
michael@428 1004 my $ok = `$rpm --eval '\%dump' 2>&1 | egrep "^-*[0-9][0-9]*: l_" | sed -e 's;^[^:]*: *\\(l_[a-zA-Z0-9_]*\\).*;\\1;' | sort -u`;
michael@428 1005 my @ok = split(/\n/, $ok);
michael@428 1006 my $todo = $spec;
michael@428 1007 $todo =~ s|^([a-zA-Z][a-zA-Z0-9]+):\s+(.+)$|push(@ok, lc($1)), ''|mge;
michael@428 1008 $todo =~ s/^\%(?:define|option)\s+(\S+)\s+.+$/push(@ok, $1), ''/mge;
michael@428 1009 my $done = ''; my $this = ''; my $todo = $spec;
michael@428 1010 while ($todo =~ m|\%\{([a-zA-Z][a-zA-Z0-9_]+)|s) {
michael@428 1011 $done .= $`; $this = $&; $todo = $';
michael@428 1012 my $name = $1;
michael@428 1013 next if ($name =~ m/^(SOURCE|PATCH)\d*$/ or $name =~ m/^(expand|echo|warn|error)$/);
michael@428 1014 if (not grep(/^\Q$name\E$/, @ok)) {
michael@428 1015 &lint_error($file, $done, $this, "unknown macro \"\%{$name}\"");
michael@428 1016 }
michael@428 1017 $done .= $this;
michael@428 1018 }
michael@428 1019 }
michael@428 1020 }
michael@428 1021
michael@428 1022 ## _________________________________________________________________
michael@428 1023 ##
michael@428 1024 ## CHECK "sources": source file consistency
michael@428 1025 ## _________________________________________________________________
michael@428 1026 ##
michael@428 1027
michael@428 1028 sub check_sources {
michael@428 1029 my ($file, $spec) = @_;
michael@428 1030
michael@428 1031 ##
michael@428 1032 ## PREPARATION
michael@428 1033 ##
michael@428 1034
michael@428 1035 my $D = {};
michael@428 1036 my $S = {};
michael@428 1037 my $i = 99;
michael@428 1038
michael@428 1039 # determine defines values
michael@428 1040 my $done = ''; my $this = ''; my $todo = $spec;
michael@428 1041 while ($todo =~ m/^\%(define|option)\s+(\S+)\s+(.+)\s*$/m) {
michael@428 1042 $done .= $`; $this = $&; $todo = $';
michael@428 1043 my ($var, $val) = ($2, $3);
michael@428 1044 $val =~ s|(\%\{([a-zA-Z_][a-zA-Z0-9_]*)\})|defined($D->{$2}) ? $D->{$2} : $1|sge;
michael@428 1045 $D->{$var} = $val;
michael@428 1046 $done .= $this;
michael@428 1047 }
michael@428 1048 $done = ''; $this = ''; $todo = $spec;
michael@428 1049 while ($todo =~ m/^([a-zA-Z][a-zA-Z0-9_]+):\s+(.*)$/m) {
michael@428 1050 $done .= $`; $this = $&; $todo = $';
michael@428 1051 my ($var, $val) = (lc($1), $2);
michael@428 1052 $val =~ s|(\%\{([a-zA-Z_][a-zA-Z0-9_]*)\})|defined($D->{$2}) ? $D->{$2} : $1|sge;
michael@428 1053 $D->{$var} = $val;
michael@428 1054 $done .= $this;
michael@428 1055 }
michael@428 1056
michael@428 1057 # expand define values (allow one level of nesting)
michael@428 1058 $spec =~ s|(\%\{([a-zA-Z_][a-zA-Z0-9_]*)\})|defined($D->{$2}) ? $D->{$2} : $1|sge;
michael@428 1059 $spec =~ s|(\%\{([a-zA-Z_][a-zA-Z0-9_]*)\})|defined($D->{$2}) ? $D->{$2} : $1|sge;
michael@428 1060
michael@428 1061 # parse Source# and Patch# headers
michael@428 1062 $done = ''; $this = ''; $todo = $spec;
michael@428 1063 while ($todo =~ m/^(Source|Patch)(\d+):[ \t]*(.*?)$/m) {
michael@428 1064 $done .= $`; $this = $&; $todo = $';
michael@428 1065 my ($type, $num, $file) = (lc($1), $2, $3);
michael@428 1066 my $url = "";
michael@428 1067 if ($file =~ m|^(.+/)([^/]+)$|s) {
michael@428 1068 ($url, $file) = ($1, $2);
michael@428 1069 }
michael@428 1070 if (not defined($S->{"$type$num"})) {
michael@428 1071 $S->{"$type$num"} = {
michael@428 1072 -type => $type,
michael@428 1073 -num => $num,
michael@428 1074 -url => $url,
michael@428 1075 -file => $file,
michael@428 1076 -refhdr => 1,
michael@428 1077 -refcmd => 0,
michael@428 1078 -refmac => 0,
michael@428 1079 -refvar => 0,
michael@428 1080 };
michael@428 1081 }
michael@428 1082 else {
michael@428 1083 $S->{"$type$num"}->{-refhdr}++;
michael@428 1084 }
michael@428 1085 $done .= $this;
michael@428 1086 }
michael@428 1087
michael@428 1088 # parse %setup and %patch commands
michael@428 1089 $done = ''; $this = ''; $todo = $spec;
michael@428 1090 $todo =~ s/^(\s*\%patch)((?:\s+-\S+|\s+-d\s+\S+)*)((?:\s+\d+)+)\s*$/&expand_patch($1, $2, $3)/mge;
michael@428 1091 sub expand_patch {
michael@428 1092 my ($cmd, $opts, $nums) = @_;
michael@428 1093 my $cmds = '';
michael@428 1094 $cmds .= "${cmd}0${opts}\n" if ($opts !~ m|\s+-P|s);
michael@428 1095 $opts =~ s|\s+-P||s;
michael@428 1096 while ($nums =~ s|^\s+(\d+)||) {
michael@428 1097 my $num = $1;
michael@428 1098 $cmds .= "${cmd}${num}${opts}\n";
michael@428 1099 }
michael@428 1100 return $cmds;
michael@428 1101 }
michael@428 1102 while ($todo =~ m/^\s*\%(setup|patch)(\d+)?(\s+.*)?$/m) {
michael@428 1103 $done .= $`; $this = $&; $todo = $';
michael@428 1104 my ($type, $num, $opts) = ($1, $2, $3);
michael@428 1105 $type = 'source' if ($type eq 'setup');
michael@428 1106 my $num_force = '';
michael@428 1107 if ($opts =~ m/\s-(?:a|b)\s*(\d+)/s) {
michael@428 1108 $num_force = $1;
michael@428 1109 }
michael@428 1110 if ($num ne '' and $num_force ne '' and $num ne $num_force) {
michael@428 1111 &lint_warning($file, $done, $this, "macro and enforced $type number conflict: $num <-> $num_force" .
michael@428 1112 " (expected either just enforced number or make them match)");
michael@428 1113 }
michael@428 1114 $num = $num_force if ($num_force ne '');
michael@428 1115 $num = 0 if ($num eq '');
michael@428 1116 next if ($opts =~ m|\s-T|s and $num == 0);
michael@428 1117 if (not defined($S->{"$type$num"})) {
michael@428 1118 $S->{"$type$num"} = {
michael@428 1119 -type => $type,
michael@428 1120 -num => $num,
michael@428 1121 -url => "",
michael@428 1122 -file => "",
michael@428 1123 -refhdr => 0,
michael@428 1124 -refcmd => 1,
michael@428 1125 -refmac => 0,
michael@428 1126 -refvar => 0,
michael@428 1127 };
michael@428 1128 }
michael@428 1129 else {
michael@428 1130 $S->{"$type$num"}->{-refcmd}++;
michael@428 1131 }
michael@428 1132 $done .= $this;
michael@428 1133 }
michael@428 1134
michael@428 1135 # parse %{SOURCE#} and %{PATCH#} macros
michael@428 1136 $done = ''; $this = ''; $todo = $spec;
michael@428 1137 while ($todo =~ m/\%\{(SOURCE|PATCH)(\d+)\}/m) {
michael@428 1138 $done .= $`; $this = $&; $todo = $';
michael@428 1139 my ($type, $num) = (lc($1), $2);
michael@428 1140 if (not defined($S->{"$type$num"})) {
michael@428 1141 $S->{"$type$num"} = {
michael@428 1142 -type => $type,
michael@428 1143 -num => $num,
michael@428 1144 -url => "",
michael@428 1145 -file => "",
michael@428 1146 -refhdr => 0,
michael@428 1147 -refcmd => 0,
michael@428 1148 -refmac => 1,
michael@428 1149 -refvar => 0,
michael@428 1150 };
michael@428 1151 }
michael@428 1152 else {
michael@428 1153 $S->{"$type$num"}->{-refmac}++;
michael@428 1154 }
michael@428 1155 $done .= $this;
michael@428 1156 }
michael@428 1157
michael@428 1158 # parse %{SOURCE ...} and %{PATCH ...} macros
michael@428 1159 $done = ''; $this = ''; $todo = $spec;
michael@428 1160 while ($todo =~ m/\%\{(SOURCE|PATCH)\s+([^\s}]+)\}/m) {
michael@428 1161 $done .= $`; $this = $&; $todo = $';
michael@428 1162 my ($type, $file) = (lc($1), $2);
michael@428 1163 my $key = "";
michael@428 1164 foreach my $src (keys(%{$S})) {
michael@428 1165 if ($S->{$src}->{-type} eq $type and $S->{$src}->{-file} eq $file) {
michael@428 1166 $key = $src;
michael@428 1167 }
michael@428 1168 }
michael@428 1169 if ($key eq '') {
michael@428 1170 $S->{"$type$i"} = {
michael@428 1171 -type => $type,
michael@428 1172 -num => $i,
michael@428 1173 -url => "",
michael@428 1174 -file => $file,
michael@428 1175 -refhdr => 0,
michael@428 1176 -refcmd => 0,
michael@428 1177 -refmac => 1,
michael@428 1178 -refvar => 0,
michael@428 1179 };
michael@428 1180 $i++;
michael@428 1181 }
michael@428 1182 else {
michael@428 1183 $S->{$key}->{-refmac}++;
michael@428 1184 }
michael@428 1185 $done .= $this;
michael@428 1186 }
michael@428 1187
michael@428 1188 # parse $RPM_SOURCE_DIR based references
michael@428 1189 $done = ''; $this = ''; $todo = $spec;
michael@428 1190 while ($todo =~ m/\$RPM_SOURCE_DIR\/([^\s;"']+)/m) {
michael@428 1191 $done .= $`; $this = $&; $todo = $';
michael@428 1192 my ($file) = ($1);
michael@428 1193 my $key = "";
michael@428 1194 foreach my $src (keys(%{$S})) {
michael@428 1195 if ($S->{$src}->{-file} eq $file) {
michael@428 1196 $key = $src;
michael@428 1197 }
michael@428 1198 }
michael@428 1199 if ($key eq '') {
michael@428 1200 $S->{"source$i"} = {
michael@428 1201 -type => "source",
michael@428 1202 -num => $i,
michael@428 1203 -url => "",
michael@428 1204 -file => $file,
michael@428 1205 -refhdr => 0,
michael@428 1206 -refcmd => 0,
michael@428 1207 -refmac => 0,
michael@428 1208 -refvar => 1,
michael@428 1209 };
michael@428 1210 $i++;
michael@428 1211 }
michael@428 1212 else {
michael@428 1213 $S->{$key}->{-refmac}++;
michael@428 1214 }
michael@428 1215 $done .= $this;
michael@428 1216 }
michael@428 1217
michael@428 1218 ##
michael@428 1219 ## CHECKING
michael@428 1220 ##
michael@428 1221
michael@428 1222 # check reference counts
michael@428 1223 foreach my $src (%{$S}) {
michael@428 1224 my $s = $S->{$src};
michael@428 1225 my $str_hdr = ($s->{-type} eq 'source' ? 'Source' : 'Patch' ) . $s->{-num};
michael@428 1226 my $str_cmd = ($s->{-type} eq 'source' ? '%setup' : '%patch') . $s->{-num};
michael@428 1227 my $str_mac = ($s->{-type} eq 'source' ? '%{SOURCE ...}' : '%{PATCH ...}');
michael@428 1228 my $str_var = "\$RPM_SOURCE_DIR";
michael@428 1229 if ($file !~ m/\b(openpkg-.*|openpkg|vim|ncurses|openssh)\.spec$/) {
michael@428 1230 if ($s->{-refhdr} > 0) {
michael@428 1231 if ($s->{-refcmd} == 0 and $s->{-refmac} == 0 and $s->{-refvar} == 0) {
michael@428 1232 &lint_error($file, undef, undef, "source \"$str_hdr\" defined (file $s->{-file}), but never used" .
michael@428 1233 " (expected at least one reference via \"$str_cmd\", \"$str_mac\" or \"$str_var\")");
michael@428 1234 }
michael@428 1235 elsif ($s->{-refcmd} > 1) {
michael@428 1236 &lint_error($file, undef, undef, "source \"$str_hdr\" defined (file $s->{-file}), but used multiple times" .
michael@428 1237 " (expected just one reference via \"$str_cmd\")");
michael@428 1238 }
michael@428 1239 }
michael@428 1240 else {
michael@428 1241 if ($s->{-refcmd}+$s->{-refmac}+$s->{-refvar} > 0) {
michael@428 1242 &lint_error($file, undef, undef, "source \"$str_hdr\" not defined, but used ".
michael@428 1243 ($s->{-file} ? "(file ".$s->{-file}.")" : "").
michael@428 1244 " (expected at least one definition via \"$str_hdr\")");
michael@428 1245 }
michael@428 1246 }
michael@428 1247 }
michael@428 1248 }
michael@428 1249
michael@428 1250 # check for existing of sources on filesystem
michael@428 1251 my $name = $file;
michael@428 1252 $name =~ s|.*/([^/]+)$|$1|s;
michael@428 1253 $name =~ s|\.spec$||s;
michael@428 1254 my $cvsdir = `$rpm --define 'name $name' --eval '%{_specdir}'`;
michael@428 1255 $cvsdir =~ s|\n$||s;
michael@428 1256 $cvsdir =~ s|/+$||s;
michael@428 1257 my $dstdir = `$rpm --define 'name $name' --eval '%{_sourcedir}'`;
michael@428 1258 $dstdir =~ s|\n$||s;
michael@428 1259 $dstdir =~ s|/+$||s;
michael@428 1260 foreach my $src (%{$S}) {
michael@428 1261 my $s = $S->{$src};
michael@428 1262 next if (not $s->{-refhdr});
michael@428 1263 if ($s->{-url} eq '' and not -f $cvsdir."/".$s->{-file} and not -f $dstdir."/".$s->{-file}) {
michael@428 1264 &lint_error($file, undef, undef, "source \"$s->{-file}\" neither found in SRC nor DST locations (you have to provide it)");
michael@428 1265 }
michael@428 1266 elsif ($s->{-url} eq '' and not -f $cvsdir."/".$s->{-file} and -f $dstdir."/".$s->{-file}) {
michael@428 1267 &lint_error($file, undef, undef, "source \"$s->{-file}\" not found in SRC, but in DST (expected it in SRC or want URL)");
michael@428 1268 }
michael@428 1269 elsif ($s->{-url} ne '' and not -f $cvsdir."/".$s->{-file} and not -f $dstdir."/".$s->{-file}) {
michael@428 1270 &lint_error($file, undef, undef, "source \"$s->{-file}\" neither found in SRC nor DST locations (you have to download it)");
michael@428 1271 }
michael@428 1272 elsif ($s->{-url} ne '' and -f $cvsdir."/".$s->{-file} and not -f $dstdir."/".$s->{-file}) {
michael@428 1273 &lint_error($file, undef, undef, "source \"$s->{-file}\" not found in DST, but in SRC (expected it in DST or want URL)");
michael@428 1274 }
michael@428 1275 #if ($s->{-url} ne '' and $s->{-file} !~ m/\d/) {
michael@428 1276 # &lint_warning($file, undef, undef, "vendor source \"$s->{-file}\" not versioned");
michael@428 1277 #}
michael@428 1278 }
michael@428 1279
michael@428 1280 # check for patch file naming
michael@428 1281 foreach my $src (%{$S}) {
michael@428 1282 my $s = $S->{$src};
michael@428 1283 next if (not $s->{-refhdr});
michael@428 1284 if ( $s->{-url} eq '' and $s->{-type} eq 'patch'
michael@428 1285 and $s->{-file} !~ m|^${name}\.patch(\.[^./]+)?$|) {
michael@428 1286 &lint_warning($file, undef, undef, "non-canonical patch filename \"$s->{-file}\"" .
michael@428 1287 " (expected to match \"${name}\\.patch(\\.[^./]+)?\$\")");
michael@428 1288 }
michael@428 1289 }
michael@428 1290 }
michael@428 1291
michael@428 1292 ## _________________________________________________________________
michael@428 1293 ##
michael@428 1294 ## CHECK "digest": digest consistency
michael@428 1295 ## _________________________________________________________________
michael@428 1296 ##
michael@428 1297
michael@428 1298 sub check_digest {
michael@428 1299 my ($file, $spec) = @_;
michael@428 1300
michael@428 1301 ##
michael@428 1302 ## PREPARATION
michael@428 1303 ##
michael@428 1304
michael@428 1305 my $D = {};
michael@428 1306 my $S = {};
michael@428 1307
michael@428 1308 # determine defines values
michael@428 1309 my $done = ''; my $this = ''; my $todo = $spec;
michael@428 1310 while ($todo =~ m/^\%(define|option)\s+(\S+)\s+(.+)\s*$/m) {
michael@428 1311 $done .= $`; $this = $&; $todo = $';
michael@428 1312 my ($var, $val) = ($2, $3);
michael@428 1313 $val =~ s|(\%\{([a-zA-Z_][a-zA-Z0-9_]*)\})|defined($D->{$2}) ? $D->{$2} : $1|sge;
michael@428 1314 $D->{$var} = $val;
michael@428 1315 $done .= $this;
michael@428 1316 }
michael@428 1317 $done = ''; $this = ''; $todo = $spec;
michael@428 1318 while ($todo =~ m/^([a-zA-Z][a-zA-Z0-9_]+):\s+(.*)$/m) {
michael@428 1319 $done .= $`; $this = $&; $todo = $';
michael@428 1320 my ($var, $val) = (lc($1), $2);
michael@428 1321 $val =~ s|(\%\{([a-zA-Z_][a-zA-Z0-9_]*)\})|defined($D->{$2}) ? $D->{$2} : $1|sge;
michael@428 1322 $D->{$var} = $val;
michael@428 1323 $done .= $this;
michael@428 1324 }
michael@428 1325
michael@428 1326 # expand define values (allow one level of nesting)
michael@428 1327 $spec =~ s|(\%\{([a-zA-Z_][a-zA-Z0-9_]*)\})|defined($D->{$2}) ? $D->{$2} : $1|sge;
michael@428 1328 $spec =~ s|(\%\{([a-zA-Z_][a-zA-Z0-9_]*)\})|defined($D->{$2}) ? $D->{$2} : $1|sge;
michael@428 1329
michael@428 1330 # determine locations
michael@428 1331 my $name = $file;
michael@428 1332 $name =~ s|.*/([^/]+)$|$1|s;
michael@428 1333 $name =~ s|\.spec$||s;
michael@428 1334 my $srcdir = `$rpm --define 'name $name' --eval '%{_specdir}'`;
michael@428 1335 $srcdir =~ s|\n$||s;
michael@428 1336 $srcdir =~ s|/+$||s;
michael@428 1337 my $dstdir = `$rpm --define 'name $name' --eval '%{_sourcedir}'`;
michael@428 1338 $dstdir =~ s|\n$||s;
michael@428 1339 $dstdir =~ s|/+$||s;
michael@428 1340
michael@428 1341 # parse Source# and Patch# headers
michael@428 1342 $done = ''; $this = ''; $todo = $spec;
michael@428 1343 while ($todo =~ m/^(Source|Patch)(\d+):[ \t]*(.*?)$/m) {
michael@428 1344 $done .= $`; $this = $&; $todo = $';
michael@428 1345 my ($type, $num, $url) = (lc($1), $2, $3);
michael@428 1346 my $file;
michael@428 1347 if ($url =~ m|^.+/([^/]+)$|s) {
michael@428 1348 $file = "$dstdir/$1";
michael@428 1349 }
michael@428 1350 else {
michael@428 1351 $file = "$srcdir/$url";
michael@428 1352 }
michael@428 1353 if (not defined($S->{"$type$num"})) {
michael@428 1354 $S->{"$type$num"} = {
michael@428 1355 -type => $type,
michael@428 1356 -num => $num,
michael@428 1357 -url => $url,
michael@428 1358 -file => $file
michael@428 1359 };
michael@428 1360 }
michael@428 1361 else {
michael@428 1362 $S->{"$type$num"}->{-refhdr}++;
michael@428 1363 }
michael@428 1364 $done .= $this;
michael@428 1365 }
michael@428 1366
michael@428 1367 # parse BuildPreReq headers
michael@428 1368 $done = ''; $this = ''; $todo = $spec;
michael@428 1369 while ($todo =~ m/^BuildPreReq:[ \t]+digest\(sha1:\%\{(.+?)\}\)[ \t]+=[ \t]+([0-9a-fA-F]+)$/m) {
michael@428 1370 $done .= $`; $this = $&; $todo = $';
michael@428 1371 my ($macro, $sha1) = ($1, $2);
michael@428 1372
michael@428 1373 my $url = $S->{lc($macro)}->{-url};
michael@428 1374 my $filename = $S->{lc($macro)}->{-file};
michael@428 1375 my $basename = $filename;
michael@428 1376 $basename =~ s/^.+\/([^\/]+)$/$1/s;
michael@428 1377
michael@428 1378 my $sha1_real = `$openssl sha1 $filename 2>/dev/null`;
michael@428 1379 $sha1_real =~ s/^SHA1\s*\(.+?\)\s*=\s*([0-9a-fA-F]+)\s*$/$1/s;
michael@428 1380
michael@428 1381 if (lc($sha1) ne lc($sha1_real)) {
michael@428 1382 &lint_error($file, undef, undef, "source \"$basename\" (\%{$macro}) has a digest(sha1) requirement of \"$sha1\" (expected \"$sha1_real\")");
michael@428 1383 }
michael@428 1384
michael@428 1385 $done .= $this;
michael@428 1386 }
michael@428 1387 }
michael@428 1388

mercurial