#! /usr/bin/perl
# Written by Zack Weinberg <zackw at panix.com> in 2017 and 2021.
# To the extent possible under law, Zack Weinberg has waived all
# copyright and related or neighboring rights to this work.
#
# See https://creativecommons.org/publicdomain/zero/1.0/ for further
# details.

# Generate macros that control the symbol versions of the public
# library API, from a .map.in file.
#
# See libcrypt.map.in for an explanation of the format of .map.in
# files.  See crypt-port.h for an explanation of how to use the macros
# generated by this program.
#
# Note: we currently don't support compatibility symbols that need
# a different definition from the default version.

use v5.14;    # implicit use strict, use feature ':5.14'
use warnings FATAL => 'all';
use utf8;
use open qw(:std :utf8);
no  if $] >= 5.022, warnings => 'experimental::re_strict';
use if $] >= 5.022, re       => 'strict';

use FindBin ();
use lib $FindBin::Bin;
use BuildCommon qw(
    parse_version_map_in
);

sub output {
    my ($apply_symvers, $symvers) = @_;
    my $basemap = $symvers->basemap;
    print "/* Generated from $basemap by ${FindBin::Script}.  "
        . "DO NOT EDIT.  */\n";

    print <<'EOT';

#ifndef _CRYPT_SYMBOL_VERS_H
#define _CRYPT_SYMBOL_VERS_H 1

/* For each public symbol <sym>, INCLUDE_<sym> is true if it
   has any versions above the backward compatibility minimum.
   Compatibility-only symbols are not included in the static
   library, or in the shared library when configured with
   --disable-obsolete-api.  */
#if defined PIC && ENABLE_OBSOLETE_API

EOT

    for my $sym (@{$symvers->symbols}) {
        printf "#define INCLUDE_%-*s %d\n",
            $symvers->max_symlen, $sym->name, $sym->included;
    }
    print "\n#else\n\n";
    for my $sym (@{$symvers->symbols}) {
        printf "#define INCLUDE_%-*s %d\n",
            $symvers->max_symlen, $sym->name,
            $sym->included && !$sym->compat_only;
    }

    if ($apply_symvers =~ m/no/) {
        print <<'EOT';

#endif

/* We are building this library with no symbol versioning
   enabled, so let's define all macros for SYMVER_ to do
   nothing. */
EOT
        for my $sym (@{$symvers->symbols}) {
            my $name = $sym->name;
            print "#define SYMVER_$name symver_nop()\n";
        }
        print <<'EOT';
#endif
EOT
        close STDOUT or die "write error: $!\n";
        return;
    }

    print <<'EOT';

#endif

/* When the public symbols are being given versions, they must be
   defined under a different, private name first.  */
#ifdef PIC
EOT

    for my $sym (@{$symvers->symbols}) {
        if ($sym->included) {
            printf "#define %-*s _crypt_%s\n",
                $symvers->max_symlen, $sym->name, $sym->name;
        }
    }

    print <<'EOT';
#endif

/* For each public symbol <sym> that is included, define its
   highest version as the default, and aliases at each
   compatibility version. */
EOT

    for my $sym (@{$symvers->symbols}) {
        my $name = $sym->name;
        if (!$sym->included) {
            print "#define SYMVER_$name symver_nop()\n";
            next;
        }

        my $seq = 0;
        for my $v (@{$sym->versions}) {
            if ($seq == 0) {
                print "#define SYMVER_$name \\\n";
                if ($sym->compat_only) {
                    print "  symver_compat0 (\"$name\", $name, $v)";
                } else {
                    print "  symver_default (\"$name\", $name, $v)";
                }
            } else {
                print "; \\\n"
                    . "  symver_compat ($seq, \"$name\", $name, $name, $v)";
            }
            $seq++;
        }
        print "\n";
    }

    print <<'EOT';

#endif /* crypt-symbol-vers.h */
EOT

    close STDOUT or die "write error: $!\n";
    return;
}

#
# Main
#
exit 0 if eval {
    output($ARGV[0], parse_version_map_in(@ARGV[1 .. 4]));
    1;
};

print {*STDERR} "${FindBin::Script}: $@";
exit 1;
