#!/usr/bin/env perl #************************************************************************* # Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne # National Laboratory. # Copyright (c) 2002 The Regents of the University of California, as # Operator of Los Alamos National Laboratory. # SPDX-License-Identifier: EPICS # EPICS BASE is distributed subject to a Software License Agreement found # in file LICENSE that is included with this distribution. #************************************************************************* use strict; use FindBin qw($Bin); use lib ("$Bin/../../lib/perl"); use DBD; use DBD::Parser; use EPICS::Readfile; use EPICS::Path; use EPICS::Getopts; use Text::Wrap; our ($opt_D, @opt_I, $opt_o, $opt_l); getopts('Dlo:I@') or die "Usage: registerRecordDeviceDriver [-D] [-l] [-o out.c] [-I dir] in.dbd subname [TOP]"; my @path = map { split /[:;]/ } @opt_I; # FIXME: Broken on Win32? my ($file, $subname, $bldTop) = @ARGV; # Auto-declaration of record types is needed to build loadable modules $DBD::Parser::allowAutoDeclarations = 1; my $dbd = DBD->new(); ParseDBD($dbd, Readfile($file, "", \@path)); if ($opt_D) { # Output dependencies only my %filecount; my @uniqfiles = grep { not $filecount{$_}++ } @inputfiles; print "$opt_o: ", join(" \\\n ", @uniqfiles), "\n\n"; print map { "$_:\n" } @uniqfiles; exit 0; } $Text::Wrap::columns = 75; # Eliminate chars not allowed in C symbol names my $c_bad_ident_chars = '[^0-9A-Za-z_]'; $subname =~ s/$c_bad_ident_chars/_/g; # Process bldTop like convertRelease.pl does $bldTop = LocalPath(UnixPath($bldTop)); $bldTop =~ s/([\\"])/\\\1/g; # escape back-slashes and double-quotes # Create output file my $out; if ($opt_o) { open $out, '>', $opt_o or die "Can't create $opt_o: $!\n"; } else { $out = *STDOUT; } print $out (<< "END"); /* THIS IS A GENERATED FILE. DO NOT EDIT! */ /* Generated from $file */ #include #ifndef USE_TYPED_RSET # define USE_TYPED_RSET #endif #include "compilerDependencies.h" #include "epicsStdlib.h" #include "iocsh.h" #include "iocshRegisterCommon.h" #include "registryCommon.h" #include "recSup.h" #include "shareLib.h" END print $out (<< "END") if $opt_l; #define epicsExportSharedSymbols #include "shareLib.h" END print $out (<< "END"); extern "C" { END my %rectypes = %{$dbd->recordtypes}; my @rtypnames; my @dsets; if (%rectypes) { my @allrtypnames = sort keys %rectypes; # Record types with no fields defined are declarations, # for building shared libraries containing device support. @rtypnames = grep { scalar $rectypes{$_}->fields } @allrtypnames; if (@rtypnames) { # Declare the record support entry tables print $out wrap('epicsShareExtern typed_rset ', ' ', join(', ', map {"*pvar_rset_${_}RSET"} @rtypnames)), ";\n\n"; # Declare the RecordSizeOffset functions print $out "typedef int (*rso_func)(dbRecordType *pdbRecordType);\n"; print $out wrap('epicsShareExtern rso_func ', ' ', join(', ', map {"pvar_func_${_}RecordSizeOffset"} @rtypnames)), ";\n\n"; # List of record type names print $out "static const char * const recordTypeNames[] = {\n"; print $out wrap(' ', ' ', join(', ', map {"\"$_\""} @rtypnames)); print $out "\n};\n\n"; # List of pointers to each RSET and RecordSizeOffset function print $out "static const recordTypeLocation rtl[] = {\n"; print $out join(",\n", map { " {(struct typed_rset *)pvar_rset_${_}RSET, pvar_func_${_}RecordSizeOffset}" } @rtypnames); print $out "\n};\n\n"; } for my $rtype (@allrtypnames) { my @devices = $rectypes{$rtype}->devices; for my $dtype (@devices) { my $dset = $dtype->name; push @dsets, $dset; } } if (@dsets) { # Declare the device support entry tables print $out wrap('epicsShareExtern dset ', ' ', join(', ', map {"*pvar_dset_$_"} @dsets)), ";\n\n"; # List of dset names print $out "static const char * const deviceSupportNames[] = {\n"; print $out wrap(' ', ' ', join(', ', map {"\"$_\""} @dsets)); print $out "\n};\n\n"; # List of pointers to each dset print $out "static const dset * const devsl[] = {\n"; print $out wrap(' ', ' ', join(", ", map {"pvar_dset_$_"} @dsets)); print $out "\n};\n\n"; } } my %drivers = %{$dbd->drivers}; if (%drivers) { my @drivers = sort keys %drivers; # Declare the driver entry tables print $out wrap('epicsShareExtern drvet ', ' ', join(', ', map {"*pvar_drvet_$_"} @drivers)), ";\n\n"; # List of drvet names print $out "static const char *driverSupportNames[] = {\n"; print $out wrap(' ', ' ', join(', ', map {"\"$_\""} @drivers)); print $out "};\n\n"; # List of pointers to each drvet print $out "static struct drvet *drvsl[] = {\n"; print $out join(",\n", map {" pvar_drvet_$_"} @drivers); print $out "};\n\n"; } my %links = %{$dbd->links}; if (%links) { my @links = sort keys %links; # Declare the link interfaces print $out wrap('epicsShareExtern jlif ', ' ', join(', ', map {"*pvar_jlif_$_"} @links)), ";\n\n"; # List of pointers to each link interface print $out "static struct jlif *jlifsl[] = {\n"; print $out join(",\n", map {" pvar_jlif_$_"} @links); print $out "};\n\n"; } my @registrars = sort keys %{$dbd->registrars}; my @functions = sort keys %{$dbd->functions}; push @registrars, map {"register_func_$_"} @functions; if (@registrars) { # Declare the registrar functions print $out "typedef void (*reg_func)(void);\n"; print $out wrap('epicsShareExtern reg_func ', ' ', join(', ', map {"pvar_func_$_"} @registrars)), ";\n\n"; } my %variables = %{$dbd->variables}; if (%variables) { my @varnames = sort keys %variables; # Declare the variables for my $var (@varnames) { my $vtype = $variables{$var}->var_type; print $out "epicsShareExtern $vtype * const pvar_${vtype}_$var;\n"; } # Generate the structure for registering variables with iocsh print $out "\nstatic struct iocshVarDef vardefs[] = {\n"; for my $var (@varnames) { my $vtype = $variables{$var}->var_type; my $itype = $variables{$var}->iocshArg_type; print $out " {\"$var\", $itype, pvar_${vtype}_$var},\n"; } print $out " {NULL, iocshArgInt, NULL}\n};\n\n"; } # Now for actual registration routine print $out (<< "END"); int $subname(DBBASE *pbase) { static int executed = 0; END print $out (<< "END") if $bldTop ne ''; const char *bldTop = "$bldTop"; const char *envTop = getenv("TOP"); if (envTop && strcmp(envTop, bldTop)) { printf("Warning: IOC is booting with TOP = \\"%s\\"\\n" " but was built with TOP = \\"%s\\"\\n", envTop, bldTop); } END print $out (<< 'END'); if (!pbase) { printf("pdbbase is NULL; you must load a DBD file first.\n"); return -1; } if (executed) { printf("Warning: Registration already done.\n"); } executed = 1; END print $out (<< 'END') if %rectypes && @rtypnames; registerRecordTypes(pbase, NELEMENTS(rtl), recordTypeNames, rtl); END print $out (<< 'END') if @dsets; registerDevices(pbase, NELEMENTS(devsl), deviceSupportNames, devsl); END print $out (<< 'END') if %drivers; registerDrivers(pbase, NELEMENTS(drvsl), driverSupportNames, drvsl); END print $out (<< 'END') if %links; registerJLinks(pbase, NELEMENTS(jlifsl), jlifsl); END print $out (<< "END") for @registrars; runRegistrarOnce(pvar_func_$_); END print $out (<< 'END') if %variables; iocshRegisterVariable(vardefs); END print $out (<< "END"); return 0; } /* $subname */ static const iocshArg rrddArg0 = {"pdbbase", iocshArgPdbbase}; static const iocshArg *rrddArgs[] = {&rrddArg0}; static const iocshFuncDef rrddFuncDef = { "$subname", 1, rrddArgs, "Register the various records, devices, for this DBD.\\n\\n" "These are registered into the database given as first argument,\\n" "which should always be 'pdbbase'.\\n\\n" "Example: $subname pdbbase\\n", }; static void rrddCallFunc(const iocshArgBuf *) { iocshSetError($subname(*iocshPpdbbase)); } } // extern "C" /* * Register commands on application startup */ static int Registration() { iocshRegisterCommon(); iocshRegister(&rrddFuncDef, rrddCallFunc); return 0; } static int done EPICS_UNUSED = Registration(); END if ($opt_o) { close $out or die "Closing $opt_o failed: $!\n"; } exit 0;