#! /usr/bin/env perl
# Copyright 2021-2024 The OpenSSL Project Authors. All Rights Reserved.
#
# Licensed under the Apache License 2.0 (the "License").  You may not use
# this file except in compliance with the License.  You can obtain a copy
# in the file LICENSE in the source distribution or at
# https://www.openssl.org/source/license.html

# All variables are supposed to come from Makefile, in environment variable
# form, or passed as variable assignments on the command line.
# The result is a Perl module creating the package OpenSSL::safe::installdata.

use 5.10.0;
use strict;
use warnings;
use Carp;

use File::Spec;
#use List::Util qw(pairs);
sub _pairs (@);

# These are expected to be set up as absolute directories
my @absolutes = qw(PREFIX libdir);
# These may be absolute directories, and if not, they are expected to be set up
# as subdirectories to PREFIX or LIBDIR.  The order of the pairs is important,
# since the LIBDIR subdirectories depend on the calculation of LIBDIR from
# PREFIX.
my @subdirs = _pairs (PREFIX => [ qw(BINDIR LIBDIR INCLUDEDIR APPLINKDIR) ],
                      LIBDIR => [ qw(MODULESDIR PKGCONFIGDIR
                                     CMAKECONFIGDIR) ]);
# For completeness, other expected variables
my @others = qw(VERSION LDLIBS);

my %all = ( );
foreach (@absolutes) { $all{$_} = 1 }
foreach (@subdirs) { foreach (@{$_->[1]}) { $all{$_} = 1 } }
foreach (@others) { $all{$_} = 1 }
print STDERR "DEBUG: all keys: ", join(", ", sort keys %all), "\n";

my %keys = ();
my %values = ();
foreach (@ARGV) {
    (my $k, my $v) = m|^([^=]*)=(.*)$|;
    $keys{$k} = 1;
    push @{$values{$k}}, $v;
}

# special case for LIBDIR vs libdir.
# For installations, They both get their value from ./Configure's --libdir or
# corresponding config target attribute, but LIBDIR only gets a value if the
# configuration is a relative path, while libdir always gets a value, so if
# the former doesn't have a value, we give it the latter's value, and rely
# on mechanisms further down to do the rest of the processing.
# If they're both empty, it's still fine.
print STDERR "DEBUG: LIBDIR = $values{LIBDIR}->[0], libdir = $values{libdir}->[0] => ";
$values{LIBDIR}->[0] = $values{libdir}->[0] unless $values{LIBDIR}->[0];
print STDERR "LIBDIR = $values{LIBDIR}->[0]\n";

# warn if there are missing values, and also if there are unexpected values
foreach my $k (sort keys %all) {
    warn "No value given for $k\n" unless $keys{$k};
}
foreach my $k (sort keys %keys) {
    warn "Unknown variable $k\n" unless $all{$k};
}

# This shouldn't be needed, but just in case we get relative paths that
# should be absolute, make sure they actually are.
foreach my $k (@absolutes) {
    my $v = $values{$k} || [ '.' ];
    die "Can't have more than one $k\n" if scalar @$v > 1;
    print STDERR "DEBUG: $k = $v->[0] => ";
    $v = [ map { File::Spec->rel2abs($_) } @$v ];
    $values{$k} = $v;
    print STDERR "$k = $v->[0]\n";
}

# Absolute paths for the subdir variables are computed.  This provides
# the usual form of values for names that have become norm, known as GNU
# installation paths.
# For the benefit of those that need it, the subdirectories are preserved
# as they are, using the same variable names, suffixed with '_REL_{var}',
# if they are indeed subdirectories.  The '{var}' part of the name tells
# which other variable value they are relative to.
foreach my $pair (@subdirs) {
    my ($var, $subdir_vars) = @$pair;
    foreach my $k (@$subdir_vars) {
        my $kr = "${k}_REL_${var}";
        my $v2 = $values{$k} || [ '.' ];
        $values{$k} = [];       # We're rebuilding it
        print STDERR "DEBUG: $k = ",
            (scalar @$v2 > 1 ? "[ " . join(", ", @$v2) . " ]" : $v2->[0]),
            " => ";
        foreach my $v (@$v2) {
            if (File::Spec->file_name_is_absolute($v)) {
                push @{$values{$k}}, $v;
                push @{$values{$kr}},
                    File::Spec->abs2rel($v, $values{$var}->[0]);
            } else {
                push @{$values{$kr}}, $v;
                push @{$values{$k}},
                    File::Spec->rel2abs($v, $values{$var}->[0]);
            }
        }
        print STDERR join(", ",
                          map {
                              my $v = $values{$_};
                              "$_ = " . (scalar @$v > 1
                                         ? "[ " . join(", ", @$v) . " ]"
                                         : $v->[0]);
                          } ($k, $kr)),
            "\n";
    }
}

print <<_____;
package OpenSSL::safe::installdata;

use strict;
use warnings;
use Exporter;
our \@ISA = qw(Exporter);
our \@EXPORT = qw(
_____

foreach my $k (@absolutes) {
    print "    \@$k\n";
}
foreach my $pair (@subdirs) {
    my ($var, $subdir_vars) = @$pair;
    foreach my $k (@$subdir_vars) {
        my $k2 = "${k}_REL_${var}";
        print "    \@$k \@$k2\n";
    }
}

print <<_____;
    \$VERSION \@LDLIBS
);

_____

foreach my $k (@absolutes) {
    print "our \@$k" . ' ' x (27 - length($k)) . "= ( '",
        join("', '", @{$values{$k}}),
        "' );\n";
}
foreach my $pair (@subdirs) {
    my ($var, $subdir_vars) = @$pair;
    foreach my $k (@$subdir_vars) {
        my $k2 = "${k}_REL_${var}";
        print "our \@$k" . ' ' x (27 - length($k)) . "= ( '",
            join("', '", @{$values{$k}}),
            "' );\n";
        print "our \@$k2" . ' ' x (27 - length($k2)) . "= ( '",
            join("', '", @{$values{$k2}}),
            "' );\n";
    }
}

print <<_____;
our \$VERSION                    = '$values{VERSION}->[0]';
our \@LDLIBS                     =
    # Unix and Windows use space separation, VMS uses comma separation
    \$^O eq 'VMS'
    ? split(/ *, */, '$values{LDLIBS}->[0]')
    : split(/ +/, '$values{LDLIBS}->[0]');

1;
_____

######## Helpers

# _pairs LIST
#
# This operates on an even-sized list, and returns a list of "ARRAY"
# references, each containing two items from the given LIST.
#
# It is a quick cheap reimplementation of List::Util::pairs(), a function
# we cannot use, because it only appeared in perl v5.19.3, and we claim to
# support perl versions all the way back to v5.10.

sub _pairs (@) {
    croak "Odd number of arguments" if @_ & 1;

    my @pairlist = ();

    while (@_) {
        my $x = [ shift, shift ];
        push @pairlist, $x;
    }
    return @pairlist;
}
