#!/usr/bin/env perl #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | # / __| | | | |_) | | # | (__| |_| | _ <| |___ # \___|\___/|_| \_\_____| # # Copyright (C) Daniel Stenberg, , et al. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at https://curl.se/docs/copyright.html. # # You may opt to use, copy, modify, merge, publish, distribute and/or sell # copies of the Software, and permit persons to whom the Software is # furnished to do so, under the terms of the COPYING file. # # This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY # KIND, either express or implied. # # SPDX-License-Identifier: curl # ########################################################################### # # This script grew out of help from Przemyslaw Iskra and Balint Szilakszi # a late evening in the #curl IRC channel. # use strict; use warnings; use vars qw($Cpreprocessor); # # configurehelp perl module is generated by configure script # my $rc = eval { require configurehelp; configurehelp->import(qw( $Cpreprocessor )); 1; }; # Set default values if configure has not generated a configurehelp.pm file. # This is the case with cmake. if (!$rc) { $Cpreprocessor = 'cpp'; } # we may get the dir root pointed out my $root=$ARGV[0] || "."; # need an include directory when building out-of-tree my $i = ($ARGV[1]) ? "-I$ARGV[1] " : ''; my $verbose=0; my $summary=0; my $misses=0; my @manrefs; my @syms; my %doc; my %rem; # scanenum runs the preprocessor on curl.h so it will process all enums # included by it, which *should* be all headers sub scanenum { my ($file) = @_; open my $h_in, "-|", "$Cpreprocessor $i$file" || die "Cannot preprocess $file"; while ( <$h_in> ) { if ( /enum\s+(\S+\s+)?{/ .. /}/ ) { s/^\s+//; next unless /^CURL/; chomp; s/[,\s].*//; push @syms, $_; } } close $h_in || die "Error preprocessing $file"; } sub scanheader { my ($f)=@_; open my $h, "<", "$f"; while(<$h>) { if (/^#define ((LIB|)CURL[A-Za-z0-9_]*)/) { push @syms, $1; } } close $h; } sub scanallheaders { my $d = "$root/include/curl"; opendir(my $dh, $d) || die "Can't opendir: $!"; my @headers = grep { /.h\z/ } readdir($dh); closedir $dh; foreach my $h (@headers) { scanenum("$d/$h"); scanheader("$d/$h"); } } sub checkmanpage { my ($m) = @_; open(my $mh, "<", "$m"); my $line = 1; while(<$mh>) { # strip off formatting $_ =~ s/\\f[BPRI]//; # detect global-looking 'CURL[BLABLA]_*' symbols while(s/\W(CURL(AUTH|E|H|MOPT|OPT|SHOPT|UE|M|SSH|SSLBACKEND|HEADER|FORM|FTP|PIPE|MIMEOPT|GSSAPI|ALTSVC|PROTO|PROXY|UPART|USESSL|_READFUNC|_WRITEFUNC|_CSELECT|_FORMADD|_IPRESOLVE|_REDIR|_RTSPREQ|_TIMECOND|_VERSION)_[a-zA-Z0-9_]+)//) { my $s = $1; # skip two "special" ones if($s !~ /^(CURLE_OBSOLETE|CURLOPT_TEMPLATE)/) { push @manrefs, "$1:$m:$line"; } } $line++; } close($mh); } sub scanman3dir { my ($d) = @_; opendir(my $dh, $d) || die "Can't opendir: $!"; my @mans = grep { /.3\z/ } readdir($dh); closedir $dh; for my $m (@mans) { checkmanpage("$d/$m"); } } scanallheaders(); scanman3dir("$root/docs/libcurl"); scanman3dir("$root/docs/libcurl/opts"); open my $s, "<", "$root/docs/libcurl/symbols-in-versions"; while(<$s>) { if(/(^[^ \n]+) +(.*)/) { my ($sym, $rest)=($1, $2); if($doc{$sym}) { print "Detected duplicate symbol: $sym\n"; $misses++; next; } $doc{$sym}=$sym; my @a=split(/ +/, $rest); if($a[2]) { # this symbol is documented to have been present the last time # in this release $rem{$sym}=$a[2]; } } } close $s; my $ignored=0; for my $e (sort @syms) { # OBSOLETE - names that are just placeholders for a position where we # previously had a name, that is now removed. The OBSOLETE names should # never be used for anything. # # CURL_EXTERN - is a define used for libcurl functions that are external, # public. No app or other code should ever use it. # # CURLINC_ - defines for header dual-include prevention, ignore those. # # *_LAST and *_LASTENTRY are just prefix for the placeholders used for the # last entry in many enum series. # if($e =~ /(OBSOLETE|^CURL_EXTERN|^CURLINC_|_LAST\z|_LASTENTRY\z)/) { $ignored++; next; } if($doc{$e}) { if($verbose) { print $e."\n"; } $doc{$e}="used"; next; } else { print $e."\n"; $misses++; } } # # now scan through all symbols that were present in the symbols-in-versions # but not in the headers # # If the symbols were marked 'removed' in symbols-in-versions we don't output # anything about it since that is perfectly fine. # my $anyremoved; for my $e (sort keys %doc) { if(($doc{$e} ne "used") && !$rem{$e}) { if(!$anyremoved++) { print "Missing symbols mentioned in symbols-in-versions\n"; print "Add them to a header, or mark them as removed.\n"; } print "$e\n"; $misses++; } } my %warned; for my $r (@manrefs) { if($r =~ /^([^:]+):(.*)/) { my ($sym, $file)=($1, $2); if(!$doc{$sym} && !$warned{$sym, $file}) { print "$file: $sym is not a public symbol\n"; $warned{$sym, $file} = 1; } } } if($summary) { print "Summary:\n"; printf "%d symbols in headers (out of which %d are ignored)\n", scalar(@syms), $ignored; printf "%d symbols in headers are interesting\n", scalar(@syms)- $ignored; printf "%d symbols are listed in symbols-in-versions\n (out of which %d are listed as removed)\n", scalar(keys %doc), scalar(keys %rem); printf "%d symbols in symbols-in-versions should match the ones in headers\n", scalar(keys %doc) - scalar(keys %rem); } if($misses) { exit 0; # there are stuff to attend to! } else { print "OK\n"; }