meta/utils.pm (411 lines of code) (raw):

#!/usr/bin/perl # # Copyright (c) 2014 Microsoft Open Technologies, Inc. # # Licensed 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 # # THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR # CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT # LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS # FOR A PARTICULAR PURPOSE, MERCHANTABILITY OR NON-INFRINGEMENT. # # See the Apache Version 2.0 License for specific language governing # permissions and limitations under the License. # # Microsoft would like to thank the following companies for their review and # assistance with these files: Intel Corporation, Mellanox Technologies Ltd, # Dell Products, L.P., Facebook, Inc., Marvell International Ltd. # # @file utils.pm # # @brief This module defines SAI Metadata Utils Parser # package utils; use strict; use warnings; use diagnostics; use Term::ANSIColor; require Exporter; our $NUMBER_REGEX = '(?:-?\d+|0x[A-F0-9]+)'; our $errors = 0; our $warnings = 0; our $HEADER_CONTENT = ""; our $SOURCE_CONTENT = ""; our $TEST_CONTENT = ""; our $SWIG_CONTENT = ""; my $identLevel = 0; sub GetIdent { my $content = shift; return "" if $content =~ /\\$/; return " " if $content =~ /^\s*_(In|Out)/; return " " x --$identLevel if $content =~ /^\s*%?}/; return " " x $identLevel++ if $content =~ /{$/; return " " x $identLevel; } sub WriteHeader { my $content = shift; my $ident = GetIdent($content); my $line = $ident . $content . "\n"; $line = "\n" if $content eq ""; $HEADER_CONTENT .= $line; } sub WriteSource { my $content = shift; my $ident = GetIdent($content); my $line = $ident . $content . "\n"; $line = "\n" if $content eq ""; $SOURCE_CONTENT .= $line; } sub WriteTest { my $content = shift; my $ident = ""; # TODO tests should have it's own ident, since it's different file GetIdent($content); $TEST_CONTENT .= $ident . $content . "\n"; } sub WriteSwig { my $content = shift; my $ident = GetIdent($content); $SWIG_CONTENT .= $ident . $content . "\n"; } sub WriteSourceSectionComment { my $content = shift; WriteSource "\n/* $content */\n"; } sub WriteSectionComment { my $content = shift; WriteHeader "\n/* $content */\n"; WriteSource "\n/* $content */\n"; } sub GetCallerInfo { return "" if not defined $main::optionShowLogCaller; my ($package, $filename, $line, $sub) = caller(1); my $logLine = $line; ($package, $filename, $line, $sub) = caller(2); return "$sub($logLine): "; } sub LogDebug { my $sub = GetCallerInfo(); print color('bright_blue') . "$sub@_" . color('reset') . "\n" if $main::optionPrintDebug; } sub LogInfo { my $sub = GetCallerInfo(); print color('bright_green') . "$sub@_" . color('reset') . "\n"; } sub LogWarning { my $sub = GetCallerInfo(); $warnings++; print color('bright_yellow') . "WARNING: $sub@_" . color('reset') . "\n"; } sub LogError { my $sub = GetCallerInfo(); $errors++; print color('bright_red') . "ERROR: $sub@_" . color('reset') . "\n"; } sub WriteFile { my ($file, $content) = @_; open (F, ">", $file) or die "$0: open $file $!"; print F $content; close F; } sub GetHeaderFiles { my $dir = shift; $dir = $main::INCLUDE_DIR if not defined $dir; opendir(my $dh, $dir) or die "Can't opendir $dir: $!"; my @headers = grep { /^sai\w*\.h$/ and -f "$dir/$_" } readdir($dh); closedir $dh; return sort @headers; } sub GetMetaSourceFiles { my $dir = shift; $dir = "." if not defined $dir; opendir(my $dh, $dir) or die "Can't opendir $dir: $!"; my @sources = grep { /^sai\w*\.(c|cpp)$/ and -f "$dir/$_" } readdir($dh); closedir $dh; return sort @sources; } sub GetMetaHeaderFiles { return GetHeaderFiles("."); } sub GetExperimentalHeaderFiles { return GetHeaderFiles($main::EXPERIMENTAL_DIR); } sub GetFilesByRegex { my ($dir,$regex) = @_; $dir = $main::INCLUDE_DIR if not defined $dir; opendir(my $dh, $dir) or die "Can't opendir $dir: $!"; my @files = grep { /$regex/ and -f "$dir/$_" } readdir($dh); closedir $dh; return sort @files; } sub GetMetadataSourceFiles { my $dir = "."; my @sources; push @sources, GetFilesByRegex($dir, '^\w+\.(pm|pl|h|cpp|c|sh)$'); push @sources, GetFilesByRegex($dir, '^Makefile$'); return @sources; } sub ReadHeaderFile { my $file = shift; local $/ = undef; # first search file in meta directory my $filename = $file; $filename = "$main::INCLUDE_DIR/$file" if not -e $filename; $filename = "$main::EXPERIMENTAL_DIR/$file" if not -e $filename; open FILE, $filename or die "Couldn't open file $filename: $!"; binmode FILE; my $string = <FILE>; close FILE; return $string; } sub GetNonObjectIdStructNames { my %structs; my @headers = (GetHeaderFiles(), GetExperimentalHeaderFiles()); # TODO must support experimental extensions for my $header (@headers) { my $data = ReadHeaderFile($header); # TODO there should be better way to extract those while ($data =~ /sai_(?:create|set)_\w+.+?\n.+const\s+(sai_(\w+)_t)/gim) { my $name = $1; my $rawname = $2; $structs{$name} = $rawname; if (not $name =~ /_entry_t$/) { LogError "non object id struct name '$name'; should end on _entry_t"; next; } } } return sort values %structs; } sub GetNonObjectIdStructNamesWithBulkApi { my %structs; my @headers = (GetHeaderFiles(), GetExperimentalHeaderFiles()); for my $header (@headers) { my $data = ReadHeaderFile($header); # TODO there should be better way to extract those while ($data =~ /sai_bulk_(?:create|remove|set|get)_(\w+_entry)_fn/gm) { my $name = $1; $structs{$name} = $name; } } return sort values %structs; } sub GetStructLists { my $data = ReadHeaderFile("$main::INCLUDE_DIR/saitypes.h"); my %StructLists = (); my @lines = split/\n/,$data; for my $line (@lines) { next if not $line =~ /typedef\s+struct\s+_(sai_\w+_list_t)/; $StructLists{$1} = $1; } return %StructLists; } sub IsSpecialObject { my $objectType = shift; return ($objectType eq "SAI_OBJECT_TYPE_FDB_FLUSH" or $objectType eq "SAI_OBJECT_TYPE_HOSTIF_PACKET"); } sub SanityCheckContent { # since we generate so much metadata now # lets put some primitive sanity check # if everything we generated is fine my $testCount = @test::TESTNAMES; if ($testCount < 5) { LogError "there should be at least 5 test defined, got $testCount"; } my $metaHeaderSize = 127588 * 0.99; my $metaSourceSize = 5190419 * 0.99; my $metaTestSize = 195323 * 0.99; if (length($HEADER_CONTENT) < $metaHeaderSize) { LogError "generated saimetadata.h size is too small"; } if (length($SOURCE_CONTENT) < $metaSourceSize) { LogError "generated saimetadata.c size is too small"; } if (length($TEST_CONTENT) < $metaTestSize) { LogError "generated saimetadatatest.c size is too small"; } } sub WriteMetaDataFiles { SanityCheckContent(); exit 1 if ($warnings > 0 or $errors > 0); WriteFile("saimetadata.h", $HEADER_CONTENT); WriteFile("saimetadata.c", $SOURCE_CONTENT); WriteFile("saimetadatatest.c", $TEST_CONTENT); WriteFile("saiswig.i", $SWIG_CONTENT); } sub GetStructKeysInOrder { my $structRef = shift; my @values = (); for my $key (keys %$structRef) { $values[$structRef->{$key}->{idx}] = $key; } return @values; } sub Trim { my $value = shift; $value =~ s/\s+/ /g; $value =~ s/^\s*//; $value =~ s/\s*$//; return $value; } sub ExitOnErrors { return if $errors == 0; LogError "please correct all $errors error(s) before continue"; exit 1; } sub ExitOnErrorsOrWarnings { return if $errors == 0 and $warnings == 0; LogError "please correct all $errors error(s) and all $warnings warnings before continue"; exit 1; } sub ProcessEnumInitializers { # # This function attempts to figure out enum integers values during paring # time in similar way as C compiler would do. Because SAI community agreed # that enum grouping is more beneficial then ordering enums, then enum # values could be not sorted any more. But if we figure out integers # values, we could perform stable sort at this parser level, and generate # enums metadata where enum values are sorted. # my ($arr_ref, $ini_ref, $enumTypeName, $SAI_DEFINES_REF) = @_; return if $enumTypeName =~ /_extensions_t$/; # ignore initializers on extensions if (scalar(@$arr_ref) != scalar(@$ini_ref)) { LogError "attr array not matching initializers array on $enumTypeName"; return; } #return if grep (/<</, @$ini_ref); # skip shifted flags enum my $previousEnumValue = -1; my $idx = 0; # using reference here, will cause update $ini inside initializer table # reference and that's what we want for my $ini (@$ini_ref) { if ($ini eq "") { $previousEnumValue += 1; $ini = sprintf("0x%08x", $previousEnumValue); } elsif ($ini =~ /^= (0x[0-9a-f]{8})$/) { $previousEnumValue = hex($1); $ini = sprintf("0x%08x", $previousEnumValue); } elsif ($ini =~ /^=\s+(\d+)$/) { $previousEnumValue = hex($1); $ini = sprintf("0x%08x", $previousEnumValue); } elsif ($ini =~ /= (SAI_\w+)$/) { for my $i (0..$idx) { if ($$arr_ref[$i] eq $1) { $ini = @$ini_ref[$i]; $previousEnumValue = hex($ini); last; } } LogError "initializer $ini not found on $enumTypeName before $$arr_ref[$idx]" if not $ini =~ /^0x/; } elsif ($ini =~ /^= (SAI_\w+) \+ (SAI_\w+)$/) # special case SAI_ACL_USER_DEFINED_FIELD_ATTR_ID_RANGE { # this case is in form: = (sai enum value) + (sai define) my $first = $1; my $val = $SAI_DEFINES_REF->{$2}; if (not defined $val) { LogError "Value $2 not defined using #define directive"; } elsif (not $val =~ /^0x[0-9a-f]+$/i) { LogError "$val not in hex format 0xYY"; } else { for my $i (0..$idx) { if ($$arr_ref[$i] eq $first) { $ini = sprintf("0x%08x", hex(@$ini_ref[$i]) + hex($val)); $previousEnumValue = hex($ini); last; } } LogError "initializer $ini not found on $enumTypeName before $$arr_ref[$idx]" if not $ini =~ /^0x/; } } elsif ($ini =~/^= (SAI_\w+) \+ (\d+)$/) { my $first = $1; my $val = $2; for my $i (0..$idx) { if ($$arr_ref[$i] eq $first) { $ini = sprintf("0x%08x", hex(@$ini_ref[$i]) + $val); $previousEnumValue = hex($ini); last; } } LogError "initializer $ini not found on $enumTypeName before $$arr_ref[$idx]" if not $ini =~ /^0x/; } elsif ($ini =~/^= (SAI_\w+) \+ (0x[0-9a-f]{1,8})$/) { my $first = $1; my $val = $2; for my $i (0..$idx) { if ($$arr_ref[$i] eq $first) { $ini = sprintf("0x%08x", hex(@$ini_ref[$i]) + hex($val)); $previousEnumValue = hex($ini); last; } } LogError "initializer $ini not found on $enumTypeName before $$arr_ref[$idx]" if not $ini =~ /^0x/; } elsif ($ini =~ /^= \(?(\d+) << (\d+)\)?$/) { $previousEnumValue = $1 << $2; $ini = sprintf("0x%08x", $previousEnumValue); } else { LogError "not supported initializer '$ini' on $$arr_ref[$idx], FIXME"; } $idx++; } # in final form all initializers must be hex numbers 8 digits long, since # they will be used in stable sort if (scalar(grep (/^0x[0-9a-f]{8}$/, @$ini_ref)) != scalar(@$ini_ref)) { LogError "wrong initializers on $enumTypeName: @$ini_ref"; return; } my $before = "@$arr_ref"; my @joined = (); for my $idx (0..$#$arr_ref) { push @joined, "$$ini_ref[$idx]$$arr_ref[$idx]"; # format is: 0x00000000SAI_ } my @sorted = sort { substr($a, 0, 10) cmp substr($b, 0, 10) } @joined; s/^0x[0-9a-f]{8}SAI/SAI/i for @sorted; my $after = "@sorted"; return if $after eq $before; LogDebug "Need sort initalizers for $enumTypeName"; @$arr_ref = (); push @$arr_ref, @sorted; } BEGIN { our @ISA = qw(Exporter); our @EXPORT = qw/ LogDebug LogInfo LogWarning LogError WriteFile GetHeaderFiles GetMetaHeaderFiles GetExperimentalHeaderFiles GetMetadataSourceFiles ReadHeaderFile GetMetaSourceFiles GetNonObjectIdStructNames GetNonObjectIdStructNamesWithBulkApi IsSpecialObject GetStructLists GetStructKeysInOrder Trim ExitOnErrors ExitOnErrorsOrWarnings ProcessEnumInitializers WriteHeader WriteSource WriteTest WriteSwig WriteMetaDataFiles WriteSectionComment WriteSourceSectionComment $errors $warnings $NUMBER_REGEX $HEADER_CONTENT $SOURCE_CONTENT $TEST_CONTENT /; } 1;