use strict;
use File::Basename;
use File::Glob ':glob';
my $bad = 0;
$0 = basename($0, ".pl");
my $usage = "/path/to/dylib /glob/path/to/headers decl-prefix [-arch <arch>] [-sdk sdkname] [/path/to/project~dst]";
my $lib_arg = shift || die "$usage";
die "$usage" unless ($lib_arg =~ /^\//);
my $headers_arg = shift || die "$usage";
my $export_arg = shift || die "$usage";
my $arch = "x86_64";
if ($ARGV[0] eq "-arch") {
shift;
$arch = shift || die "$0: -arch requires an architecture";
}
my $sdk = "system";
if ($ARGV[0] eq "-sdk") {
shift;
$sdk = shift || die "$0: -sdk requires an SDK name";
}
my $root = shift || "";
my $lib_path = "$root$lib_arg";
die "$0: file not found: $lib_path\n" unless -e $lib_path;
my %symbols;
my @symbollines = `nm -arch $arch '$lib_path'`;
die "$0: nm failed: (arch $arch) $lib_path\n" if ($?);
for my $line (@symbollines) {
chomp $line;
(my $type, my $name) = ($line =~ /^[[:xdigit:]]*\s+(.) (.*)$/);
if ($type =~ /^[A-TV-Z]$/) {
$symbols{$name} = 1;
} else {
}
}
for my $symbol (keys %symbols) {
if ($symbol =~ /^__Z/) {
print "BAD: C++ export '$symbol'\n"; $bad++;
}
}
my @archnames = ("x86_64", "i386", "arm", "armv6", "armv7");
my %archOBJC1 = (i386 => 1);
my %archLP64 = (x86_64 => 1);
my @archparams;
my $OBJC1 = ($archOBJC1{$arch} && $sdk !~ /^iphonesimulator/);
if ($OBJC1) {
push @archparams, "-U__OBJC2__";
} else {
push @archparams, "-D__OBJC2__=1";
}
if ($archLP64{$arch}) { push @archparams, "-D__LP64__=1"; }
else { push @archparams, "-U__LP64__"; }
for my $archname (@archnames) {
if ($archname eq $arch) {
push @archparams, "-D__${archname}__=1";
push @archparams, "-D__$archname=1";
} else {
push @archparams, "-U__${archname}__";
push @archparams, "-U__$archname";
}
}
push @archparams, "-DTARGET_OS_WIN32=0";
push @archparams, "-DTARGET_OS_EMBEDDED=0";
push @archparams, "-DTARGET_OS_IPHONE=0";
push @archparams, "-DTARGET_OS_MAC=1";
my $unifdef_cmd = "/usr/bin/unifdef " . join(" ", @archparams);
my @cdecls;
my @classdecls;
for my $header_path(bsd_glob("$root$headers_arg",GLOB_BRACE)) {
my $header;
open($header, "$unifdef_cmd < '$header_path' |");
my $header_contents = join("", <$header>);
push @cdecls, ($header_contents =~ /^\s*$export_arg\s+([^;]*)/msg);
push @classdecls, ($header_contents =~ /^([^\n]*\n\s*\@interface\s+[^(\n]+\n)/mg);
}
my %declarations;
for my $cdecl (@cdecls) {
$cdecl =~ s/\n/ /mg;
my $avail = undef;
my $cdecl2;
($cdecl2, $avail) = ($cdecl =~ /^(.*)\s+(__OSX_AVAILABLE_\w+\([a-zA-Z0-9_, ]+\))\s*$/) if (!defined $avail);
($cdecl2, $avail) = ($cdecl =~ /^(.*)\s+(AVAILABLE_MAC_OS_X_VERSION_\w+)\s*$/) if (!defined $avail);
($cdecl2, $avail) = ($cdecl =~ /^(.*)\s+(OBJC2_UNAVAILABLE)\s*$/) if (!defined $avail);
($cdecl2, $avail) = ($cdecl =~ /^(.*)\s+(OBJC_GC_UNAVAILABLE)\s*$/) if (!defined $avail);
($cdecl2, $avail) = ($cdecl =~ /^(.*)\s+(OBJC_ARC_UNAVAILABLE)\s*$/) if (!defined $avail);
($cdecl2, $avail) = ($cdecl =~ /^(.*)\s+(OBJC_HASH_AVAILABILITY)\s*$/) if (!defined $avail);
($cdecl2, $avail) = ($cdecl =~ /^(.*)\s+(OBJC_MAP_AVAILABILITY)\s*$/) if (!defined $avail);
($cdecl2, $avail) = ($cdecl =~ /^(.*)\s+(UNAVAILABLE_ATTRIBUTE)\s*$/) if (!defined $avail);
$cdecl2 = $cdecl if (!defined $cdecl2);
my $name = undef;
($name) = ($cdecl2 =~ /^[^(]*\(\s*\*\s*(\w+)\s*\)/) if (!defined $name);
($name) = ($cdecl2 =~ /(\w+)\s*\(/) if (!defined $name);
($name) = ($cdecl2 =~ /(\w+)\s*(?:\[\d*\]\s*)*$/) if (!defined $name);
if (!defined $name) {
print "BAD: unintellible declaration:\n $cdecl\n"; $bad++;
} elsif (!defined $avail) {
print "BAD: no availability on declaration of '$name':\n $cdecl\n"; $bad++;
}
if ($avail eq "UNAVAILABLE_ATTRIBUTE")
{
$declarations{$name} = "unavailable";
} elsif ($avail eq "OBJC2_UNAVAILABLE" && ! $OBJC1) {
} else {
$declarations{"_$name"} = "available";
}
}
for my $classdecl (@classdecls) {
$classdecl =~ s/\n/ /mg;
my $avail = undef;
my $classdecl2;
($avail, $classdecl2) = ($classdecl =~ /^\s*(__OSX_AVAILABLE_\w+\([a-zA-Z0-9_, ]+\))\s*(.*)$/) if (!defined $avail);
($avail, $classdecl2) = ($classdecl =~ /^\s*(AVAILABLE_MAC_OS_X_VERSION_\w+)\s*(.*)$/) if (!defined $avail);
($avail, $classdecl2) = ($classdecl =~ /^\s*(OBJC2_UNAVAILABLE)\s*(.*)$/) if (!defined $avail);
($avail, $classdecl2) = ($classdecl =~ /^\s*(OBJC_HASH_AVAILABILITY)\s*(.*)$/) if (!defined $avail);
($avail, $classdecl2) = ($classdecl =~ /^\s*(OBJC_MAP_AVAILABILITY)\s*(.*)$/) if (!defined $avail);
($avail, $classdecl2) = ($classdecl =~ /^\s*(UNAVAILABLE_ATTRIBUTE)\s*(.*)$/) if (!defined $avail);
$classdecl2 = $classdecl if (!defined $classdecl2);
my $name = undef;
($name) = ($classdecl2 =~ /\@interface\s+(\w+)/);
if (!defined $name) {
print "BAD: unintellible declaration:\n $classdecl\n"; $bad++;
} elsif (!defined $avail) {
print "BAD: no availability on declaration of '$name':\n $classdecl\n"; $bad++;
}
my $availability;
if ($avail eq "UNAVAILABLE_ATTRIBUTE") {
$availability = "unavailable";
} elsif ($avail eq "OBJC2_UNAVAILABLE" && ! $OBJC1) {
$availability = undef;
} else {
$availability = "available";
}
if (! $OBJC1) {
$declarations{"_OBJC_CLASS_\$_$name"} = $availability;
$declarations{"_OBJC_METACLASS_\$_$name"} = $availability;
$declarations{"_OBJC_IVAR_\$_$name.isa"} = $availability if ($name eq "Object");
} else {
$declarations{".objc_class_name_$name"} = $availability;
}
}
my @missing_symbols;
for my $name (keys %symbols) {
my $avail = $declarations{$name};
if ($avail eq "unavailable" || !defined $avail) {
push @missing_symbols, $name;
}
}
for my $symbol (sort @missing_symbols) {
print "BAD: symbol $symbol has no export declaration\n"; $bad++;
}
my @missing_decls;
for my $name (keys %declarations) {
my $avail = $declarations{$name};
my $hasSymbol = exists $symbols{$name};
if ($avail ne "unavailable" && !$hasSymbol) {
push @missing_decls, $name;
}
}
for my $decl (sort @missing_decls) {
print "BAD: declaration $decl has no exported symbol\n"; $bad++;
}
print "OK: verify-exports\n" unless $bad;
exit ($bad ? 1 : 0);