#!/usr/bin/env perl
#*************************************************************************
# Copyright (c) 2020 UChicago Argonne LLC, as Operator of Argonne
#     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 warnings;

use Getopt::Std;
$Getopt::Std::STANDARD_HELP_VERSION = 1;

use Pod::Usage;

=head1 NAME

makeAPIheader.pl - Create a header for marking API symbols import/export

=head1 SYNOPSIS

B<makeAPIheader.pl> [B<-h>] [B<-o> path/fileB<API.h>] stem

=head1 DESCRIPTION

Creates a C/C++ header file containing macro definitions for C<STEM_API> and
C<epicsStdCall> which on Windows will expand to the appropriate C<__declspec>
and C<__stdcall> keywords respectively, and on GCC to a C<visibility>
attribute and nothing.

=head1 OPTIONS

B<makeAPIheader.pl> understands the following options:

=over 4

=item B<-h>

Help, display this document as text.

=item B<-o> path/fileB<API.h>

Pathname to the output file to be created. Must end with C<API.h>.

=back

If no output filename is set, the name will be generated by appending
C<API.h> to the I<stem> argument.

The I<stem> used must be a legal C identifier, starting with a letter or
underscore followed by any combination of digits, letters and underscores.

=head1 PURPOSE

The generated header and the macros in it replace the C<epicsShare> macros
that were defined in the C<shareLib.h> header, avoiding the need for shared
library implementation code to define the C<epicsExportSharedSymbols> macro in
between the import and export headers. The order of including header files no
longer matters when using this approach.

For libraries that contain EPICS record, device, driver or link support and use
the epicsExport.h macros to publish the associated support entry table for the
IOC to locate, switching from shareLib.h to a generated API header file is not
recommended. The old approach is simpler in these cases.

=head1 USING WITH EPICS

In a Makefile that is building a DLL or shared library, set the variable
C<API_HEADER> to the name of the API header file to be generated. This name
must start with a legal C identifier and end with C<API.h>. The C identifier
part preceeding the C<API.h> is referred to here as the I<stem> for this
header file, and should be a short name in lower case or camelCase. For
example the stem used in the example here is C<libCom>:

    # Generate our library API header file
    API_HEADER += libComAPI.h

The Makefile also needs to find the API stem given the name of the library.
These may be different, as shown in our example since the libCom API actually
gets used by a library whose formal name is just "Com". This relationship is
indicated by setting the C<library_API> variable to the API stem, like this:

    # Library to build:
    LIBRARY = Com
    # API stem for the Com library
    Com_API = libCom

Then in each header file that declares a function, global variable or C++
class or method to be exported by the library, include the generated header
file and then decorate those declarations with the all-uppercase keyword
C<STEM_API> as in these examples:

    LIBCOM_API void epicsExit(int status);
    LIBCOM_API int asCheckClientIP;
    class LIBCOM_API epicsTime { ... }
    LIBCOM_API virtual ~fdManager ();

The generated header file also defines a second macro C<epicsStdCall> which on
Windows expands to C<__stdcall> to indicate the calling convention for this
routine. When used, this macro should be placed between the return type and
the routine name, like this:

    LIBCOM_API int epicsStdCall iocshCmd(const char *cmd);

It is possible to build more than one shared library in the same Makefile,
although each C or C++ source file can only be included in one library. Just
repeat the above instructions, using different stems for each library.

=cut

our ($opt_o, $opt_h);

sub HELP_MESSAGE {
    pod2usage(-exitval => 2, -verbose => $opt_h ? 2 : 0);
}

HELP_MESSAGE() if !getopts('ho:') || $opt_h || @ARGV != 1;

my $stem = shift @ARGV;
die "makeAPIheader.pl: API stem '$stem' is not a legal C identifier\n"
    unless $stem =~ m/^ [A-Za-z_][0-9A-Za-z_]* $/x;

my $outfile = defined($opt_o) ? $opt_o : "${stem}API.h";

die "makeAPIheader.pl: Output filename must end with 'API.h'\n"
    unless $outfile =~ m/API\.h$/;

my $STEM = uc $stem;
my $guard = "INC_${stem}API_H";

open my $o, '>', $outfile or
    die "makeAPIheader.pl: Can't create $outfile: $!\n";

$SIG{__DIE__} = sub {
    die @_ if $^S;  # Ignore eval deaths
    close $o;
    unlink $outfile;
};

print $o <<"__EOF__";
/* This is a generated file, do not edit! */

#ifndef $guard
#define $guard

#if defined(_WIN32) || defined(__CYGWIN__)

#  if !defined(epicsStdCall)
#    define epicsStdCall __stdcall
#  endif

#  if defined(BUILDING_${stem}_API) && defined(EPICS_BUILD_DLL)
/* Building library as dll */
#    define ${STEM}_API __declspec(dllexport)
#  elif !defined(BUILDING_${stem}_API) && defined(EPICS_CALL_DLL)
/* Calling library in dll form */
#    define ${STEM}_API __declspec(dllimport)
#  endif

#elif __GNUC__ >= 4
#  define ${STEM}_API __attribute__ ((visibility("default")))
#endif

#if !defined(${STEM}_API)
#  define ${STEM}_API
#endif

#if !defined(epicsStdCall)
#  define epicsStdCall
#endif

#endif /* $guard */

__EOF__

close $o;


=head1 COPYRIGHT AND LICENSE

Copyright (C) 2020 UChicago Argonne LLC, as Operator of Argonne National
Laboratory.

This software is distributed under the terms of the EPICS Open License.

=cut
