meta/gensairpc.pl (421 lines of code) (raw):

#!/usr/bin/env perl # # Copyright (c) 2021 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 gensairpc.pl # # @brief This module generates RPC interface of SAI for PTF # use strict; use warnings; use diagnostics; use 5.020; use File::Spec::Functions qw(catdir catfile rel2abs); use File::Basename qw(dirname); use English qw(-no_match_vars); use Getopt::Long::Descriptive; use File::Path qw(rmtree); use Term::ANSIColor; use Cwd qw(getcwd); use Data::Dumper; use Const::Fast; use File::Copy; use Template; use Carp; use lib catdir( dirname( rel2abs(__FILE__) ), 'rpc' ); use Utils::Format; use Utils; use SAI::Function::Argument; use SAI::Struct::Member; use SAI::Function; use SAI::Typedef; use SAI::Struct; use SAI::Attrs; use SAI::Stats; use SAI::Type; # Avoid warnings related to given/when no if $] >= 5.018, warnings => 'experimental::smartmatch'; # Consts # TODO: move some of them to common package (e.g. PREFIX or RETVAL) so that they can be shared const my $PREFIX => 'sai_thrift_'; const my $GEN_DIR => 'generated'; const my $THRIFT_GEN_DIR => 'gen-cpp'; const my $COUNTER => -1; const my $RETVAL => 0; # Keep the dbg data sorted, to make it comparable $Data::Dumper::Sortkeys = 1; my $run_dir = getcwd; my $script_dir = dirname( rel2abs($PROGRAM_NAME) ); my $script_path = rel2abs(__FILE__); my $gen_dir = catdir( $script_dir, $GEN_DIR ); my $templates_dir = catdir( $ENV{PAR_TEMP} ? "$ENV{PAR_TEMP}/inc" : $script_dir, 'templates' ); # Parameters my $sai_dir = catdir( dirname($script_dir) ); #<<< ( my $args, my $usage ) = describe_options( "$PROGRAM_NAME %o", [ 'dbg', 'Print debug information in generated files, implies --dump', { default => 0 } ], [ 'experimental|e', 'Generate also experimental files', { default => 0 } ], [ 'dist', 'Create the standalone \'gensairpc\' script (experimental)', { default => 0 } ], [ 'dump|d', 'Dump all data to the file', { default => 0 } ], [ 'clean-meta|c', 'Perform clean meta before generation', { default => 0 } ], [ 'verbose|v', 'Print more details', { default => 0 } ], [ 'mandatory-attrs', 'Make mandatory attributes obligatory in sai_adapter.py', { default => 0 } ], [ 'dev-utils:s', 'Generate additional development utils within the generated code. Additional options: [=log,zero]', { default => 0 } ], [ 'adapter_logger', 'Enable the logger in sai_adapter, it will log all the method invocation.', { default => 0} ], [ 'attr-header', 'Generate additional header of attributes definitions (including object types)', { default => 0 } ], [ 'help|h', 'Print this help', { shortcircuit => 1 } ], ); #>>> if ( $args->help ) { print $usage->text; exit; } if ( $args->dist ) { Utils->dist($script_path); exit; } my $dbg = $args->dbg; my $dump = $args->dump; my $verbose = $args->verbose; $dump = 1 if $dbg; $verbose = 1 if $dump; my $experimental = $args->experimental; my $clean = $args->clean_meta; my $mandatory_attrs = $args->mandatory_attrs; my $dev_utils = ( $args->dev_utils ne q{} ? $args->dev_utils : 1 ); my $adapter_logger = ( $args->adapter_logger ne q{} ? $args->adapter_logger : 1 ); my $attr_header = $args->attr_header; # Configure SAI meta my $sai_meta_dir = catdir( $sai_dir, 'meta' ); my $sai_parse_path = catfile( $sai_meta_dir, 'parse.pl' ); -d $sai_dir or die "\"$sai_dir\" directory is invalid"; # Declare SAI meta global variables, its libraries need them our $XMLDIR = catdir( $sai_meta_dir, 'xml' ); our $INCLUDE_DIR = catdir( $sai_dir, 'inc' ); our $EXPERIMENTAL_DIR = catdir( $sai_dir, 'experimental' ); # Include SAI meta libs. The intention is that SAI meta path # can be changed by the parameter, so we cannot use 'use' our @INC; unshift @INC, $sai_meta_dir; require xmlutils; xmlutils->import; require utils; utils->import; if ($clean) { say colored( "Removing \"$GEN_DIR\" directory...", 'bold blue' ); rmtree $gen_dir; say colored( 'Cleaning SAI meta...', 'bold blue' ); system 'make clean -C ' . $sai_meta_dir; } # Generate Doxygen xml files say colored( 'Building SAI meta XML...', 'bold blue' ); system 'make xml -C ' . $sai_meta_dir; say colored( 'Parsing...', 'bold blue' ); my $data = get_definitions(); my $vars = { apis => $data->{apis}, functions => $data->{functions}, methods => $data->{methods}, structs => $data->{structs}, dbg => $dbg, mandatory_attrs => $mandatory_attrs, dev_utils => $dev_utils, adapter_logger => $adapter_logger, templates_dir => $templates_dir }; mkdir $gen_dir; if ($dump) { say colored( 'Generating sai_dbg.dump...', 'bold blue' ); open my $dump_file, '>', catfile( $gen_dir, 'sai_dbg.dump' ) or die 'Could not open the dump file'; print {$dump_file} Dumper $data; close $dump_file or die; } my $template = Template->new( ABSOLUTE => 1 ); say colored( 'Generating sai.thrift...', 'bold blue' ); $template->process( catfile( $templates_dir, 'sai.thrift.tt' ), $vars, catfile( $run_dir, 'sai.thrift' ) ) or die $template->error(); say colored( 'Generating Thrift files...', 'bold blue' ); chdir $gen_dir; # rm gen-cpp is needed since thrift don't override existing files # even if sai.thrift file changed :( system("rm -rf gen-cpp"); system('thrift -o . --gen cpp -r ../sai.thrift') == 0 or die colored("Command failed: $!", "red"); chdir $run_dir; say colored( 'Generating sai_rpc_server.cpp.tt...', 'bold blue' ); open my $skeleton, '<', catfile( $gen_dir, $THRIFT_GEN_DIR, 'sai_rpc_server.skeleton.cpp' ) or die colored( 'Thrift files not generated, probably sai.thrift is invalid', 'red' ); mkdir catdir( $gen_dir, 'templates' ); open my $server_template, '>', catfile( $gen_dir, 'templates', 'sai_rpc_server.cpp.tt' ) or die; my $fn_definitions = generate_server_template_from_skeleton( $skeleton, $server_template ); close $server_template or die; close $skeleton or die; say "$fn_definitions functions processed"; say colored( 'Generating sai_rpc_server.cpp...', 'bold blue' ); $template->process( catfile( $gen_dir, 'templates', 'sai_rpc_server.cpp.tt' ), $vars, catfile( $run_dir, 'sai_rpc_server.cpp' ) ) or die $template->error(); # say 'Formatting sai_rpc_server.cpp...' if $verbose; # Utils::Format->cpp( catfile( $gen_dir, 'sai_rpc_server.cpp' ) ); if ($attr_header) { say 'Generating gensaiattrs.c...'; $template->process( catfile( $templates_dir, 'gensaiattrs.c.tt' ), $vars, catfile( $gen_dir, 'gensaiattrs.c' ) ) or die $template->error(); say 'Builiding gensaiattrs...'; chdir $gen_dir; system "gcc -O2 -Wall -I $INCLUDE_DIR gensaiattrs.c -o gensaiattrs"; say colored( 'Generating sai_gen_attributes.h...', 'bold blue' ); system './gensaiattrs > sai_gen_attributes.h'; chdir $run_dir; } say colored( 'Generating sai_adapter.py...', 'bold blue' ); $template->process( catfile( $templates_dir, 'sai_adapter.py.tt' ), $vars, catfile( $run_dir, 'sai_adapter.py' ) ) or die $template->error(); # say 'Formatting sai_adapter.py...' if $verbose; # Utils::Format->python( catfile( $gen_dir, 'sai_adapter.py' ) ); # Thrift tools can generate the skeleton of RPC server file. # Replace it with the Template Toolkit template, so that we can # easily generate the functions content. sub generate_server_template_from_skeleton { my $skeleton = shift; my $server_template = shift; my $definitions = 0; say {$server_template} '/* AUTOGENERATED FILE! DO NOT EDIT */'; say {$server_template} '[% PROCESS "$templates_dir/sai_rpc_server_functions.tt" -%]'; say {$server_template} '[% PROCESS "$templates_dir/sai_rpc_server_helper_functions.tt" -%]'; while ( my $line = <$skeleton> ) { given ($line) { when ( /int main/ .. /}/ ) { # Ignore the main function } when (/printf[(]["](?:\w+)\\n["][)]/) { ++$definitions; # Replace "printf" with function body template say {$server_template} '[% PROCESS sai_rpc_function_body -%]'; } when (/\s(\w+)\s$PREFIX(\w+)[(]/) { # Get the return type and the function name and # set the template variable say {$server_template} "[% function_name = 'sai_$2'; ret_type = '$1'; function = functions.\$function_name -%]"; $line =~ s/_return/[% function.rpc_return.name %]_out/g; print {$server_template} $line; } when (/class /) { # Include additional files say {$server_template} '#ifdef __cplusplus'; say {$server_template} 'extern "C" {'; say {$server_template} '#endif'; say {$server_template} '#include <sai.h>'; say {$server_template} '#include <saiextensions.h>' if $experimental; say {$server_template} '#ifdef __cplusplus'; say {$server_template} '}'; say {$server_template} '#include <iostream>'; say {$server_template} '#endif'; # Define global variable before "class" print {$server_template} "\nextern sai_object_id_t switch_id;\nsai_object_id_t switch_id;\nextern sai_object_id_t gSwitchId;\n\n\n"; # Define helper functions print {$server_template} "[% PROCESS helper_functions %]\n\n\n"; print {$server_template} $line; } default { # Just print the line (if it not a "Your implementation" comment) print {$server_template} $line unless $line =~ /\/\/ Your/; } } } return $definitions; } # Find the api name within the header related to the currenct XML file. sub get_api_name { # it may happen that metadata is already generated, and SAI directory is # mounted in docker, then the location will be invalid inside docker, so # let's use only filename and inc directory my $location = shift; $location =~ s!.+/!!; # leave only filename # The hash should be static - don't open and traverse the header # if we already found the API name inside. state %api_names = ( "saimetadatalogger.h" => "common", "saimetadatatypes.h" => "common", "sai.h" => "common", "saiobject.h" => "object", # for sai_object_key_entry_t "saitypes.h" => "common" ); return $api_names{$location} if exists $api_names{$location}; my $file = "$sai_dir/inc/$location"; $file = "$sai_dir/experimental/$location" if $location =~ /experimental|extension/; open(H, '<', $file) or die "Failed to open: $file: $!"; my @lines = <H>; close H or croak; for my $line (@lines) { if ($line =~ /^typedef struct _sai_(\w+)_api_t/) { $api_names{$location} = $1; return $1; } } if ($location =~ /^sai\w*extensions.h$/) { $api_names{$location} = "common"; return "common"; } die "File $file doesn't contain api struct!"; } # The main function that parses all XML files and creates all # types definitions. sub get_definitions { my %methods_table; my %all_functions; my %all_structs; my %all_attrs; my @all_enums; my %apis; my $i = 0; # Iterate over files for ( GetSaiXmlFiles($XMLDIR) ) { my $xml = ReadXml($_); # Iterate over definitions for ( @{ $xml->{compounddef}[0]->{sectiondef} } ) { if ( $_->{kind} eq 'typedef' or $_->{kind} eq 'enum' ) { for ( @{ $_->{memberdef} } ) { my $function; my $typedef; my $object; my $struct; my $attrs; my $stats; my $api; # Get API name my $location = $_->{location}[0]->{file}; unless ($experimental) { next if $location =~ /(experimental|extension)/; } $api = get_api_name($location); $apis{$api} = {} unless ref $apis{$api} eq 'HASH'; # Populate attribute list per object next if get_object_types( $api, $apis{$api}, $_ ); next if get_attributes( $apis{$api}, $_ ); next if get_stats( $apis{$api}, $_ ); # Populate type definitions and function declarations next if get_typedef( $apis{$api}, $_, $api eq 'common' ); next if get_struct( $apis{$api}, \%all_structs, \%methods_table, $_ ); next if $api ne 'common' and get_function( $apis{$api}, \%all_functions, $_, $api ); # Add enum name to the list if ( $_->{kind} eq 'enum' ) { my $enum_name = $_->{name}[0]; $enum_name =~ s/^_//; push @all_enums, $enum_name; } } } } } my $api_list = assign_attr_types( \%apis, \@all_enums ); return { apis => $api_list, attrs => \%all_attrs, structs => \%all_structs, functions => \%all_functions, methods => \%methods_table }; } # To set or get attribute, the proper value struct field need to be used. # Obtain the name of this field and assign it to the attribute. # If the attribute is of enum type, then it is s32, otherwise the correct type # should be found within sai_attribute_value_t fields. sub get_attr_type { my $attr = shift; my $attr_types = shift; my $all_enums = shift; my $type = $attr->type->thrift_name; # First, check if we have enum return 's32' if ( $attr->type->name ~~ $all_enums ); # Try to compare types of attribute and attr value otherwise for ( @{ $attr_types->members } ) { return $_->thrift_name if ( $type eq $_->type->thrift_name ); } carp colored( "Unknown type $type of attribute " . $attr->name, 'red' ); return; } # To set or get attribute, the proper value struct field need to be used. # Obtain the name of this field and assign it to all attributes. sub assign_attr_types { my $apis = shift; my $all_enums = shift; my $attr_types; for ( @{ $apis->{common}->{structs} } ) { $attr_types = $_ if $_->name eq 'sai_attribute_value_t'; } croak unless $attr_types; for my $api ( values %{$apis} ) { for my $object ( values %{ $api->{objects} } ) { for my $attr ( $object->{attrs}->all ) { $attr->typename( get_attr_type( $attr, $attr_types, $all_enums ) ); } } } return $apis; } # Create and store the object type Enum sub get_object_types { my $api_name = shift; my $api = shift; my $enum = shift; return 0 unless SAI::Enum->validate_xml_typedef($enum) and $enum->{name}[0] =~ /object_type_t/; my $object_types = SAI::Enum->new( xml_typedef => $enum ); if ($object_types) { say "\"object_types\" added to \"$api_name\" hash" if $verbose; $api->{object_types} = $object_types; } return 1; } # Create and store the Attrs object sub get_attributes { my $api = shift; my $enum = shift; return 0 unless SAI::Attrs->validate_xml_typedef($enum); my $attrs = SAI::Attrs->new( xml_typedef => $enum ); if ( $attrs and $attrs->object ) { $api->{objects}->{ $attrs->object }->{attrs} = $attrs; } return 1; } # Create and store the Stats object sub get_stats { my $api = shift; my $enum = shift; return 0 unless SAI::Stats->validate_xml_typedef($enum); my $stats = SAI::Stats->new( xml_typedef => $enum ); if ( $stats and $stats->object ) { $api->{objects}->{ $stats->object }->{stats} = $stats; } return 1; } # Create and store the Typedef object sub get_typedef { my $api = shift; my $xml_typedef = shift; my $raw = shift; # If $raw, the we can use raw thrift types only $raw //= 0; return 0 unless SAI::Typedef->validate_xml_typedef($xml_typedef); my $typedef = SAI::Typedef->new( raw => $raw, xml_typedef => $xml_typedef, ); push @{ $api->{typedefs} }, $typedef; return 1; } # In SAI RPC server we don't call SAI functions directly - pointers # to them are stored in some structures, so we need to know their # names. sub get_method_names { my $struct = shift; my $function; my %methods; for my $method ( GetStructKeysInOrder($struct) ) { my $type = { SAI::Struct::Member->parse_xml_typedef( $struct->{$method} ) } ->{type}; if ( $type =~ /^(\w+)_fn$/ ) { $function = $1 } else { next } $methods{$function} = $method; } return \%methods; } # Create and store the Struct object. # The struct of API function pointers is an exception - just the its name. sub get_struct { my $api = shift; my $all_structs = shift; my $methods_table = shift; my $xml_typedef = shift; my @members; my $name; # Make sure we have a struct return 0 unless SAI::Struct->validate_xml_typedef($xml_typedef); $name = { SAI::Struct->parse_xml_typedef($xml_typedef) }->{name}; my %struct_def = ExtractStructInfo( $name, 'struct_' ); # If we have method table, then we don't create a structure - # just take the method names if ( $name =~ /_api_t$/ ) { my $method_names = get_method_names( \%struct_def ); %{$methods_table} = ( %{$methods_table}, %{$method_names} ); return 1; } push @members, SAI::Struct::Member->new( xml_typedef => $struct_def{$_} ) for ( GetStructKeysInOrder( \%struct_def ) ); my $struct = SAI::Struct->new( members => \@members, xml_typedef => $xml_typedef, ); push @{ $api->{structs} }, $struct; $all_structs->{ $struct->thrift_name } = $struct; return 1; } # Create and store the Function object. sub get_function { my $api = shift; my $all_functions = shift; my $definition = shift; my $api_name = shift; return 0 unless SAI::Function->validate_xml_typedef($definition); my $function = SAI::Function->new( xml_typedef => $definition ); $function->api($api_name); push @{ $api->{functions} }, $function; $all_functions->{ $function->name } = $function; return 1; } __END__