native/build/api.pl (216 lines of code) (raw):

#!/usr/bin/perl # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ----------------------------------------------------------------------------- # Compare the native API with native methods in the Java API. # ----------------------------------------------------------------------------- # ----------------------------------------------------------------------------- # TODO: # - use method name plus return type plus args as key instead of # only method name # ----------------------------------------------------------------------------- # use strict; # Modules needed, should all be standard # Commandline option parsing use Getopt::Std; # dirname function use File::Basename; # Traverse through directory structure and execute # callback for each file use File::Find; # Platform agnostic concatenation of directory and file names use File::Spec::Functions; ################### BEGIN VARIABLES WHICH MUST BE MAINTAINED ##################### # Script version, printed via getopts with "--version" our $VERSION = '1.0'; # Sub directories containing c native API and Java classes. # Path relative to tcnative main project directory. my $C_NATIVE_SUBDIR = 'native'; my $JAVA_API_SUBDIR = 'java'; # Macro used for marking C native API my $C_NATIVE_DECLARATION = 'TCN_IMPLEMENT_CALL'; # Macro used for marking C native API arguments my $C_NATIVE_ARGUMENTS = 'TCN_STDARGS'; ################### END VARIABLES WHICH MUST BE MAINTAINED ##################### # Global data variables # Print verbose output? my $verbose = 0; # Signature structures my %cSignature; my %javaSignature; # Usage/Help sub HELP_MESSAGE { my $fh = shift; print $fh "Usage:: $0 [ -h ] [ -v ] [ -d DIRECTORY ]\n"; print $fh " -h: Print help\n"; print $fh " -v: Verbose output\n"; print $fh " -d DIRECTORY: Path to tcnative project main directory.\n"; print $fh " Default is '..\\..' relative to the script directory \n"; } # Parse arguments: # -h: help # -v: verbose # -d: tcnative project main directory $Getopt::Std::STANDARD_HELP_VERSION = 1; our ($opt_h, $opt_v, $opt_d); getopts('hvd:'); if ($opt_h) { HELP_MESSAGE(*STDOUT); exit 1; } if ($opt_v) { $verbose = 1; } my $directory = $opt_d; if (!defined($directory) || $directory eq '') { $directory = dirname($0); $directory = catfile($directory, ('..', '..')); } if (! -d $directory) { print STDERR "Invalid tcnative project directory '$directory' - Aborting!\n"; exit 1; } sub mapTypes { my $types = shift; # Replace "Array" by "[]" $types =~ s/Array/\\[\\]/g; # Remove leading "j" from scalar types $types =~ s/j(byte|short|int|long|boolean)/$1/g; # Replace string type $types =~ s/jstring/String/g; # Replace object type with pattern $types =~ s/jobject/([A-Z][A-Za-z0-9]*|[a-z]+\\[\\])/g; return $types; } # Extract C native API from one file sub cNativeApi { # Only check C source files return if $_ !~ /\.c$/; my $file = $_; my ($m, $signature, $type, $args, $ret, $class, $method); print "Checking C API file $file\n" if $verbose; open(IN, "<$file") or do { print "ERROR: Ignoring file '$file', could not open file for read: $!\n"; return; }; while(<IN>) { # Example declaration: #TCN_IMPLEMENT_CALL(jlong, Socket, create)(TCN_STDARGS, jint family, # jint type, jint protocol, # jlong pool) if ($_ =~ /^\s*$C_NATIVE_DECLARATION\s*/o) { chomp(); $signature = $_; # Concat next line until signature is complete while($signature !~ /\([^\)]*\)\s*\([^\)]*\)/ && $signature !~ /;/ && ($_ = <IN>)) { chomp(); $signature .= $_; } # Strip C-style comments. See: "perldoc -q comment" $signature =~ s#/\*[^*]*\*+([^/*][^*]*\*+)*/|("(\\.|[^"\\])*"|'(\\.|[^'\\])*'|.[^/"'\\]*)#defined $2 ? $2 : ""#gse; # Save one declaration if ($signature =~ /^\s*$C_NATIVE_DECLARATION\s*\(([^\)]*)\)\s*\(([^\)]*)\)/) { ($type, $args) = ($1, $2); # Normalize return type and method name # Collapse multiple spaces $type =~ s/\s+/ /g; # Remove spaces around commas $type =~ s/, /,/g; $type =~ s/ ,/,/g; # Trim spaces at start and end $type =~ s/^ //; $type =~ s/ $//; ($ret, $class, $method) = split(/,/, $type); # Map return type $ret = mapTypes($ret); # Normalize argument list # Collapse multiple spaces $args =~ s/\s+/ /g; # Remove spaces around commas $args =~ s/, /,/g; $args =~ s/ ,/,/g; # Remove argument names, only types are needed $args =~ s/ [^, ]+//g; # Trim spaces at start and end $args =~ s/^ //; $args =~ s/ $//; # Remove leading JNI arguments macro $args =~ s/^$C_NATIVE_ARGUMENTS,?//o; # Map argument list types $args = mapTypes($args); $m = $cSignature{$class}; if (!defined($m)) { $m = $cSignature{$class} = {}; } if (exists($m->{$method}) && ($m->{$method}->{return} ne $ret || $m->{$method}->{args} ne $args)) { print "ERROR: C native file $file method $method in class $class will be overwritten!"; print "\tOld signature: '$m->{$method}->{return} $m->{$method}->{method}($m->{$method}->{args})\n"; print "\tNew signature: '$ret $method($args)\n"; } # XXX Use method plus ret plus args as key instead $m = $m->{$method} = {}; $m->{method} = $method; $m->{return} = $ret; $m->{args} = $args; print "\tFound C API in $file class $class: '$m->{return} $m->{method}($m->{args})'\n" if $verbose; } else { print "ERROR: Incomplete C signature in file $file ignored in '$signature'"; } } } close(IN); } # Extract native methods in Java API from one file sub javaNativeApi { # Only check Java source files return if $_ !~ /\.java$/; my $file = $_; my ($m, $signature, $type, $args, $ret, $class, $method); print "Checking Java API file $file\n" if $verbose; open(IN, "<$_") or do { print "ERROR: Ignoring file '$file', could not open file for read: $!\n"; return; }; $class = $file; $class =~ s/\.java$//; while(<IN>) { # Example declaration #public static native long uid(String username, long p) # throws Error; if ($_ =~ /^\s*((public|protected|private)\s+)?([^\(]*)\(/ && ($type = $3) && $type =~ /\snative\s/) { chomp(); $signature = $_; # Concat next line until signature is complete while($signature !~ /\([^\)]*\)/ && $signature !~ /;/ && ($_ = <IN>)) { chomp(); $signature .= $_; } # Save one declaration if ($signature =~ /^\s*([^\(]+)\(([^\)]*)\)/) { ($type, $args) = ($1, $2); # Normalize return type and method name # Remove unused specifiers $type =~ s/\b(public|protected|private)\b//g; $type =~ s/\bnative\b//g; $type =~ s/\bstatic\b//g; # Collapse multiple spaces $type =~ s/\s+/ /g; # Trim spaces at start and end $type =~ s/^ //; $type =~ s/ $//; ($ret, $method) = split(/\s+/, $type); # Normalize argument list # Collapse multiple spaces $args =~ s/\s+/ /g; # Remove spaces around commas $args =~ s/, /,/g; $args =~ s/ ,/,/g; # Remove spaces in front of array "[]" $args =~ s/ \[\]/[]/g; # Remove argument names, only types are needed $args =~ s/ [^, ]+//g; # Trim spaces at start and end $args =~ s/^ //; $args =~ s/ $//; $m = $javaSignature{$class}; if (!defined($m)) { $m = $javaSignature{$class} = {}; } if (exists($m->{$method})) { print "ERROR: Java file $file method $method in class $class will be overwritten!"; print "\tOld signature: '$m->{$method}->{return} $m->{$method}->{method}($m->{$method}->{args})\n"; print "\tNew signature: '$ret $method($args)\n"; } # XXX Use method plus ret plus args as key instead $m = $m->{$method} = {}; $m->{method} = $method; $m->{return} = $ret; $m->{args} = $args; print "\tFound native Java API in $file class $class: '$m->{return} $m->{method}($m->{args})'\n" if $verbose; } else { print "ERROR: Incomplete Java signature in file $file ignored in '$signature'"; } } } close(IN); } # Search for C native API find(\&cNativeApi, catfile($directory, $C_NATIVE_SUBDIR)); # Search for native methods in Java API find(\&javaNativeApi, catfile($directory, $JAVA_API_SUBDIR)); print "Comparing C and Java APIs...\n"; # Check whether all native methods have correct Java counterparts for my $class (sort keys %cSignature) { my $c = $cSignature{$class}; my $j = $javaSignature{$class}; if (!defined($j)) { print "ERROR: No Java API defined for class '$class'\n"; for my $method (sort keys %{$c}) { my $m = $c->{$method}; print "\tMissing Java method: $m->{return} $m->{method}($m->{args})\n"; } } else { for my $method (sort keys %{$c}) { my $m = $c->{$method}; if (!exists($j->{$method})) { print "ERROR: Missing Java method in class '$class': $m->{return} $m->{method}($m->{args})\n"; } else { my $n = $j->{$method}; if ($n->{return} !~ /^$m->{return}$/ || $n->{args} !~ /^$m->{args}$/) { print "ERROR: Incompatible API in class '$class'!\n"; print "\tC signature: $m->{return} $m->{method}($m->{args})\n"; print "\tJava signature: $n->{return} $n->{method}($n->{args})\n"; } delete($j->{$method}); } } } } # Check whether any native Java API methods remain for my $class (sort keys %javaSignature) { my $j = $javaSignature{$class}; my $c = $cSignature{$class}; if (!defined($c)) { print "ERROR: No C API defined for class '$class'!\n"; for my $method (sort keys %{$j}) { my $m = $j->{$method}; print "\tMissing C method: $m->{return} $m->{method}($m->{args})\n"; } } else { for my $method (sort keys %{$j}) { my $m = $j->{$method}; if (!exists($c->{$method})) { print "ERROR: Missing C method in class '$class': $m->{return} $m->{method}($m->{args})\n"; } else { my $n = $c->{$method}; if ($m->{return} !~ /^$n->{return}$/ || $m->{args} !~ /^$n->{args}$/) { print "ERROR: Incompatible API in class '$class'!\n"; print "\tC signature: $n->{return} $n->{method}($n->{args})\n"; print "\tJava signature: $m->{return} $m->{method}($m->{args})\n"; } delete($c->{$method}); } } } } print "Done.\n";