commit:     675a6b1602c6665428126b780aa2ab959c1e9f52
Author:     Sam James <sam <AT> gentoo <DOT> org>
AuthorDate: Mon Aug  4 11:18:55 2025 +0000
Commit:     Sam James <sam <AT> gentoo <DOT> org>
CommitDate: Mon Aug  4 11:18:55 2025 +0000
URL:        https://gitweb.gentoo.org/proj/locale-gen.git/commit/?id=675a6b16

Revert "Initial commit of Perl-based locale-gen"

This reverts commit e9a590ba0804d0ade9fe842076ab66b7057b8c36.

Kerin will reapply w/ a detailed commit message shortly.

Signed-off-by: Sam James <sam <AT> gentoo.org>

 locale-gen | 984 +++++++++++++++++++++++++++----------------------------------
 1 file changed, 427 insertions(+), 557 deletions(-)

diff --git a/locale-gen b/locale-gen
index c2e622a..d0e109e 100755
--- a/locale-gen
+++ b/locale-gen
@@ -1,581 +1,451 @@
-#!/usr/bin/perl
+#!/bin/bash
 
-# locale-gen
 #
-# Generates a glibc locale archive from templates, potentially limiting itself
-# to a set of locales defined by the admin, typically within /etc/locale.gen.
-
-use v5.36;
-
-use Errno qw(ENOENT);
-use Fcntl qw(SEEK_SET);
-use File::Spec::Functions qw(canonpath catfile catdir splitpath);
-use File::Temp qw(tempdir);
-use Getopt::Long ();
-use JSON::PP ();
-use POSIX qw(LC_ALL setlocale);
+# Based upon Debian's locale-gen, fetched from glibc_2.3.6-7.diff.gz, but 
completely rewritten.
+#
 
-# Formally stable as of v5.40; sufficiently functional in both v5.36 and v5.38.
-use experimental qw(try);
+# NB: Bash-4.0+ required.  We use += and ${var,,} features.
 
-# Determine the basename of the presently compiling script.
-my $PROGRAM;
-BEGIN { $PROGRAM = (splitpath(__FILE__))[-1]; }
+unset POSIXLY_CORRECT IFS
+umask 0022
 
-my $VERSION = '3.0';
+argv0=${0##*/}
 
-my $DEFERRED_SIGNAL = '';
-my $PID = $$;
-my $TEMPDIR;
+EPREFIX="@GENTOO_PORTAGE_EPREFIX@"
+if [[ ${EPREFIX} == "@"GENTOO_PORTAGE_EPREFIX"@" ]] ; then
+       EPREFIX=""
+fi
 
-# For the C locale to be in effect can be a consequence of the user's chosen
-# locale not yet being available. That being the case, unset all environment
-# variables pertaining to locale handling for the benefit of any subprocesses.
-if (setlocale(LC_ALL) eq 'C') {
-       delete @ENV{ grep +( m/^(LANG\z|LC_)/ ), keys %ENV };
+FUNCTIONS_SH="/lib/gentoo/functions.sh"
+source "${EPREFIX}"${FUNCTIONS_SH} || {
+       echo "${argv0}: Could not source ${FUNCTIONS_SH}!" 1>&2
+       exit 1
 }
 
-# Unset BASH_ENV for security reasons. Even as sh(1), bash acts upon it. Unset
-# CDPATH also, for it is nothing but a liability in a non-interactive context.
-delete @ENV{'BASH_ENV', 'CDPATH'};
-
-{
-       # Determine the locale directory, as reported by localedef(1).
-       my $locale_dir = get_locale_dir();
-
-       # Infer the path of a Gentoo Prefix environment, if any.
-       my $gentoo_prefix = detect_gentoo_prefix($locale_dir);
-       if (length $gentoo_prefix) {
-               $locale_dir =~ s/^\Q$gentoo_prefix//;
-       }
-
-       # Collect any supported options and option-arguments.
-       my %opt = parse_opts($gentoo_prefix, @ARGV);
-       my $prefix = $opt{'prefix'} // $gentoo_prefix;
-
-       # A proxy check is justified because compilation may take a long time.
-       my $archive_dir = catdir($prefix, $locale_dir);
-       if (! utime undef, undef, $archive_dir) {
-               die "$PROGRAM: Aborting because UID $> can't modify 
'$archive_dir': $!\n";
-       }
-
-       # Honour the --quiet option.
-       if ($opt{'quiet'} && ! open *STDOUT, '>/dev/null') {
-               die "Can't direct STDOUT to /dev/null: $!";
-       }
-
-       # Ensure that the C.UTF-8 locale is made available.
-       my @locales = ([ 'C', 'UTF-8', 'C.UTF-8' ]);
-
-       # Compose a list of up to two configuration files to be read.
-       my @config_files;
-       if (exists $opt{'config'}) {
-               push @config_files, $opt{'config'};
-       } else {
-               push @config_files, (
-                       catfile($prefix, '/etc', 'locale.gen'),
-                       catfile($prefix, '/usr/share/i18n', 'SUPPORTED')
-               );
-       }
-
-       # Collect the locales that are being requested for installation.
-       push @locales, read_config($prefix, @config_files);
-
-       # Compose a dictionary of installed locales for the --update option.
-       my %installed_by;
-       if ($opt{'update'}) {
-               # If localedef(1) originates from a Gentoo Prefix environment,
-               # the prefix will already have been hard-coded by the utility.
-               my $explicit_prefix = length $gentoo_prefix ? undef : $prefix;
-               %installed_by = map +( $_ => 1 ), 
list_locales($explicit_prefix);
-       }
-
-       # Filter out locales that are duplicates or that are already installed.
-       my %requested_by;
-       my $i = 0;
-       while ($i <= $#locales) {
-               my $canonical = $locales[$i][2];
-               my $normal = normalize($canonical);
-               if ($requested_by{$normal}++ || $installed_by{$normal}) {
-                       splice @locales, $i, 1;
-               } else {
-                       ++$i;
-               }
-       }
-
-       # If a non-actionable update was requested, proceed no further.
-       if (! @locales) {
-               print "All of the requested locales are presently installed.\n";
-               exit 0;
-       }
-
-       # Create a temporary directory and switch to it.
-       enter_tempdir($prefix);
-
-       # Compile the selected locales.
-       generate_locales($prefix, $opt{'jobs'}, @locales);
-
-       # Integrate the newly compiled locales into the system's locale archive.
-       generate_archive($prefix, $locale_dir, $opt{'update'}, map +( $_->[2] 
), @locales);
-
-       my $total = scalar @locales;
-       printf "Successfully installed %d locale%s.\n", $total, plural($total);
-}
+COMPILED_LOCALES=""
 
-sub get_locale_dir () {
-       my $stdout = qx{ localedef --help };
-       if ($? == 0 && $stdout =~ m/\hlocale path\h*:\s+(\/[^:]+)/) {
-               return canonpath($1);
-       } else {
-               die "Can't parse locale directory from localedef(1) output: $!";
-       }
-}
+show_usage() {
+       cat <<-EOF
+       Usage: ${HILITE}${argv0}${NORMAL} ${GOOD}[options]${NORMAL} -- 
${GOOD}[localedef options]${NORMAL}
 
-sub detect_gentoo_prefix ($path) {
-       if ($path !~ s/\/usr\/lib\/locale\z//) {
-               die "Can't handle unexpected locale directory of '$path'";
-       } elsif (length $path && -e "$path/etc/gentoo-release") {
-               return $path;
-       } else {
-               return '';
-       }
-}
+       Generate locales based upon the config file /etc/locale.gen.
 
-sub parse_opts ($known_prefix, @args) {
-       my @options = (
-               [ 'config|c=s' => "The file containing the chosen locales 
(default: $known_prefix/etc/locale.gen)" ],
-               [ 'all|A'      => 'Select all locales, ignoring the config 
file' ],
-               [ 'update|u'   => 'Skip any chosen locales that are already 
installed', ],
-               [ 'jobs|j=i'   => 'Maximum number of localedef(1) instances to 
run in parallel' ],
-               [ 'prefix|p=s' => 'The prefix of the root filesystem' ],
-               [ 'quiet|q'    => 'Only show errors' ],
-               [ 'version|V'  => 'Output version information and exit' ],
-               [ 'help|h'     => 'Display this help and exit' ]
-       );
-
-       # Parse the provided arguments.
-       my $parser = Getopt::Long::Parser->new;
-       $parser->configure(qw(posix_default bundling_values no_ignore_case));
-       my %opt;
-       {
-               # Decorate option validation errors while also not permitting
-               # for more than one to be reported.
-               local $SIG{'__WARN__'} = sub ($error) { die "$PROGRAM: $error" 
};
-               $parser->getoptionsfromarray(\@args, \%opt, map +( $_->[0] ), 
@options);
-       }
-
-       # If either --help or --version was specified, exclusively attend to it.
-       if ($opt{'help'}) {
-               show_usage(@options);
-               exit;
-       } elsif ($opt{'version'}) {
-               show_version();
-               exit;
-       }
-
-       # Validate the options and option-arguments.
-       if ($opt{'all'} && exists $opt{'config'}) {
-               die "$PROGRAM: The --all and --config options are mutually 
exclusive\n";
-       } elsif (exists $opt{'prefix'} && length $known_prefix && ! 
is_eq_file($known_prefix, $opt{'prefix'})) {
-               die "$PROGRAM: The --prefix option specifies a path contrary to 
a detected Gentoo Prefix\n";
-       }
-
-       # Assign values for unspecified options that need them.
-       if (! exists $opt{'jobs'} || $opt{'jobs'} < 1) {
-               $opt{'jobs'} = get_nprocs() || 1;
-       }
-       if ($opt{'all'}) {
-               $opt{'config'} = catfile($opt{'prefix'} // $known_prefix, 
'/usr/share/i18n', 'SUPPORTED');
-       } elsif (exists $opt{'config'} && $opt{'config'} eq '-') {
-               $opt{'config'} = '/dev/stdin';
-       }
-
-       return %opt;
-}
+       ${HILITE}Options:${NORMAL}
+           ${GOOD}-k, --keep${NORMAL}               Don't nuke existing locales
+           ${GOOD}-d, --destdir <dir>${NORMAL}      Use locale data in 
specified DESTDIR tree
+           ${GOOD}-c, --config <config>${NORMAL}    Use specified config 
instead of default locale.gen
+           ${GOOD}-l, --list${NORMAL}               List all the locales to be 
generated
+           ${GOOD}-a, --ask${NORMAL}                Ask before generating each 
locale
+           ${GOOD}-A, --all${NORMAL}                Pretend the locale list 
contains all locales
+           ${GOOD}-u, --update${NORMAL}             Only generate locales that 
are missing
+           ${GOOD}-G, --generate <locale>${NORMAL}  Generate specified locale 
(one shot; implies -k -u)
+           ${GOOD}-j, --jobs <num>${NORMAL}         Number of locales to 
generate at a time (parallel)
+           ${GOOD}-q, --quiet${NORMAL}              Only show errors
+           ${GOOD}-V, --version${NORMAL}            Meaningless version 
information
+           ${GOOD}-h, --help${NORMAL}               Show this help cruft
 
-sub show_usage (@options) {
-       print "Usage: locale-gen [OPTION]...\n\n";
-       my $pipe;
-       if (! open $pipe, "| column -t -s \037") {
-               exit 1;
-       }
-       for my $row (@options) {
-               my ($spec, $description) = $row->@*;
-               my ($long, $short) = split /[|=]/, $spec;
-               printf {$pipe} "-%s, --%s\037%s\n", $short, $long, $description;
-       }
-}
+       ${HILITE}Localedef Options:${NORMAL}
+           By default, ${GOOD}${LOCALEDEF_OPTS}${NORMAL} is passed to 
localedef.
 
-sub show_version () {
-       print <<~EOF;
-       locale-gen $VERSION
-       Copyright 2024 Kerin Millar <kfm\@plushkava.net>
-       License GPL-2.0-only 
<https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html>
+       For more info, see the ${HILITE}locale-gen${NORMAL}(1) and 
${HILITE}locale.gen${NORMAL}(8) manpages.
        EOF
+       [[ $# -eq 0 ]] && exit 0
+       echo ""
+       eerror "Unknown option '$1'"
+       exit 1
 }
 
-sub list_locales ($prefix) {
-       if (! defined(my $pid = open my $pipe, '-|')) {
-               die "Can't fork: $!";
-       } elsif ($pid == 0) {
-               run_localedef($prefix, '--list-archive');
-       } else {
-               chomp(my @locales = readline $pipe);
-               if (-1 == waitpid($pid, 0) || $? != 0) {
-                       die "$PROGRAM: Can't obtain a list of the presently 
installed locales\n";
-               }
-               return @locales;
-       }
-}
-
-sub normalize ($canonical) {
-       if (2 == (my ($locale, $charmap) = split /\./, $canonical, 3)) {
-               # en_US.UTF-8 => en_US.utf8; en_US.ISO-8859-1 => en_US.iso88591
-               return join '.', $locale, lc($charmap =~ s/-//gr);
-       } else {
-               die "Can't normalize " . render_printable($canonical);
-       }
-}
-
-sub read_config ($prefix, @paths) {
-       # Compose a dictionary of locale names known to be valid.
-       my %locale_by = map +( $_ => 1 ), get_valid_locales($prefix);
-
-       # Compose a dictionary of character maps known to be valid.
-       my %charmap_by = map +( $_ => 1 ), get_valid_charmaps($prefix);
-
-       # Iterate over the given paths and return the first non-empty list of
-       # valid locale declarations that can be found among them, if any.
-       for my $i (keys @paths) {
-               my $path = $paths[$i];
-               try {
-                       my $fh = fopen($path);
-                       $! = 0;
-                       if (my @locales = parse_config($fh, $path, \%locale_by, 
\%charmap_by)) {
-                               return @locales;
-                       }
-               } catch ($e) {
-                       # Disregard open errors concerning non-existent files
-                       # unless there are no more paths to be tried. Validation
-                       # errors shall also be propagated here.
-                       if ($! != ENOENT || $i == $#paths) {
-                               die $e;
-                       }
-               }
-       }
-
-       # For no locales to have been discovered at this point is exceptional.
-       my $path_list = render_printable(scalar @paths == 1 ? $paths[0] : 
\@paths);
-       die "$PROGRAM: No locale declarations were found within $path_list\n";
-}
-
-sub get_valid_locales ($prefix) {
-       my $top = local $ENV{'TOP'} = catdir($prefix, 
'/usr/share/i18n/locales');
-       my @paths = qx{ cd -- "\$TOP" && find . ! -path . -prune ! -path '*\n*' 
-type f -exec grep -lxF LC_IDENTIFICATION {} + };
-       if ($? != 0 || ! @paths) {
-               die "$PROGRAM: Failed to compose a list of valid locale names 
from '$top'\n";
-       }
-       chomp @paths;
-       return map +( (splitpath($_))[-1] ), @paths;
-}
-
-sub get_valid_charmaps ($prefix) {
-       my $top = catdir($prefix, '/usr/share/i18n/charmaps');
-       if (! opendir my $dh, $top) {
-               die "$PROGRAM: Can't open '$top' for reading: $!\n";
-       } elsif (! (my @names = map +( -f "$top/$_" ? s/\.gz\z//r : () ), 
readdir $dh)) {
-               die "$PROGRAM: Failed to compose a list of valid character maps 
from '$top'\n";
-       } else {
-               return @names;
-       }
-}
-
-sub parse_config ($fh, $path, $locale_by, $charmap_by) {
-       # Set up a helper routine to throw for validation errors.
-       my $thrower = sub ($error, $line) {
-               die sprintf "%s: %s at %s[%d]: %s\n",
-                       $PROGRAM, $error, $path, $., render_printable($line);
-       };
-
-       my @locales;
-       while (my $line = readline $fh) {
-               # Skip comments and blank lines. Note that \h will match only " 
" and
-               # "\t", since the input stream is not being subjected to any 
decoding.
-               next if $line =~ m/^\h*($|#)/;
-
-               # Expect for two fields, separated by horizontal whitespace.
-               my @fields;
-               chomp $line;
-               if (2 != (@fields = split /\h+/, trim_line($line), 3)) {
-                       $thrower->('Malformed locale declaration', $line);
-               }
-
-               # Extract the specified locale and character map. Upon success,
-               # a canonicalised representation of the locale is also returned.
-               my ($locale, $charmap, $canonical) = parse_entry(@fields);
-
-               # Validate both locale and character map before accepting.
-               if (! $locale_by->{$locale}) {
-                       $thrower->('Invalid locale', $line);
-               } elsif (! $charmap_by->{$charmap}) {
-                       $thrower->('Invalid/mismatching charmap', $line);
-               } else {
-                       push @locales, [ $locale, $charmap, $canonical ];
-               }
-       }
-
-       return @locales;
-}
-
-sub parse_entry ($locale, $charmap) {
-       my $canonical;
-       if (2 == (my @fields = split /@/, $locale, 3)) {
-               # de_DE@euro ISO-8859-15 => de_DE.ISO-8859-15@euro
-               $canonical = sprintf '%s.%s@%s', $fields[0], $charmap, 
$fields[1];
-       } elsif (2 == (@fields = split /\./, $locale, 3)) {
-               # en_US.UTF-8 UTF-8 => en_US.UTF-8
-               $locale = $fields[0];
-               $canonical = "$locale.$charmap";
-               if ($fields[1] ne $charmap) {
-                       $charmap = '';
-               }
-       } elsif (1 == @fields) {
-               # en_US ISO-8859-1 => en_US.ISO-8859-1
-               $canonical = "$locale.$charmap";
-       }
-       return $locale, $charmap, $canonical;
-}
-
-sub enter_tempdir ($prefix) {
-       # Given that /tmp might be a tmpfs, prefer /var/tmp so as to avoid
-       # undue memory pressure.
-       my $dir = catdir($prefix, '/var/tmp');
-       if (! -d $dir) {
-               $dir = File::Spec->tmpdir;
-       }
-       $TEMPDIR = tempdir('locale-gen.XXXXXXXXXX', 'DIR' => $dir);
-       if (! chdir $TEMPDIR) {
-               die "$PROGRAM: Can't chdir to '$TEMPDIR': $!\n";
-       }
+show_version() {
+       echo "locale-gen-2.xxx"
+       exit 0
 }
 
-sub generate_locales ($prefix, $workers, @locales) {
-       # Trap SIGINT and SIGTERM so that they may be handled gracefully.
-       my $handler = sub ($signal) { $DEFERRED_SIGNAL ||= $signal };
-       local @SIG{'INT', 'TERM'} = ($handler, $handler);
-
-       my $total = scalar @locales;
-       printf "Compiling %d locale definition file%s with %d worker%s ...\n",
-               $total, plural($total), $workers, plural($workers);
-
-       my $num_width = length $total;
-       my %status_by;
-       for my $i (keys @locales) {
-               # Ensure that the number of concurrent workers is bounded.
-               if ($i >= $workers) {
-                       my $pid = wait;
-                       last if 0 != ($status_by{$pid} = $?);
-               }
-
-               my ($locale, $charmap, $canonical) = $locales[$i]->@*;
-               printf "[%*d/%d] Compiling locale: %s\n",
-                       $num_width, $i + 1, $total, $canonical;
-
-               # Fork and execute localedef(1) for locale compilation.
-               if (! defined(my $pid = fork)) {
-                       warn "Can't fork: $!";
-                       last;
-               } elsif ($pid == 0) {
-                       @SIG{'INT', 'TERM'} = ('DEFAULT', 'DEFAULT');
-                       compile_locale($locale, $charmap, $canonical);
-               }
-       } continue {
-               last if $DEFERRED_SIGNAL;
-       }
-
-       # Reap any subprocesses that remain.
-       if ($workers > 1) {
-               print "Waiting for active workers to finish their jobs ...\n";
-       }
-       while (-1 != (my $pid = wait)) {
-               $status_by{$pid} = $?;
-       }
-
-       # Abort if any of the collected status codes are found to be non-zero.
-       # In the case that one subprocess was interrupted by a signal while
-       # another exited non-zero, the resulting diagnostic shall allude to the
-       # signal. Such determinism is achieved by sorting the values.
-       for my $status (sort { $a <=> $b } values %status_by) {
-               throw_child_error('localedef', $status);
-       }
-
-       if ($DEFERRED_SIGNAL) {
-               # The signal shall be propagated by the END block.
-               exit;
-       } elsif (scalar %status_by != $total) {
-               die "$PROGRAM: Aborting because not all of the selected locales 
were compiled\n";
-       }
-}
-
-sub compile_locale ($locale, $charmap, $canonical) {
-       my $output_dir = "./$canonical";
-       my @args = ('--no-archive', '-i', $locale, '-f', $charmap, '--', 
$output_dir);
-       run_localedef(undef, @args);
-}
-       
-sub generate_archive ($prefix, $locale_dir, $do_update, @canonicals) {
-       # Create the temporary subdir that will contain the new locale archive.
-       my $output_dir = catdir('.', $prefix, $locale_dir);
-       run('mkdir', '-p', '--', $output_dir);
-
-       # Determine the eventual destination path of the archive.
-       my $final_path = catfile($prefix, $locale_dir, 'locale-archive');
-       printf "The location of the archive shall be %s.\n", 
render_printable($final_path);
-
-       # If --update was specified, make a copy of the existing archive.
-       if ($do_update && -e $final_path) {
-               run('cp', '--', $final_path, "$output_dir/");
-       }
-
-       # Integrate all of the compiled locales into the new locale archive.
-       my $total = scalar @canonicals;
-       printf "Adding %d locale%s to the locale archive ...\n", $total, 
plural($total);
-       my $stderr = fopen('stderr.log', '+>');
-       redirect_stderr($stderr, sub {
-               my @args = ('--quiet', '--add-to-archive', '--replace', '--', 
@canonicals);
-               run_localedef('.', @args);
-       });
-
-       # Propagate the diagnostics and errors raised by localedef(1), if any.
-       seek $stderr, 0, SEEK_SET;
-       my $i = 0;
-       while (my $line = readline $stderr) {
-               warn $line;
-               ++$i;
-       }
-       close $stderr;
-
-       # Check the status code first.
-       throw_child_error('localedef');
-
-       # Sadly, the exit status of GNU localedef(1) is nigh on useless in the
-       # case that the --add-to-archive option is provided. If anything was
-       # printed to STDERR at all, act as if the utility had exited 1.
-       if ($i > 0) {
-               throw_child_error('localedef', 1 << 8);
-       }
-
-       # The process of replacing the old archive must not be interrupted.
-       local @SIG{'INT', 'TERM'} = ('IGNORE', 'IGNORE');
-
-       # Move the newly minted archive into the appropriate filesystem. Use
-       # mv(1), since there is a chance of crossing a filesystem boundary.
-       my $interim_path = "$final_path.$$";
-       run('mv', '--', catfile($output_dir, 'locale-archive'), $interim_path);
-
-       # Atomically replace the old archive.
-       if (! rename $interim_path, $final_path) {
-               {
-                       local $!;
-                       unlink $interim_path;
-               }
-               die "$PROGRAM: Can't rename '$interim_path' to '$final_path': 
$!\n";
-       }
-}
-
-sub run_localedef ($prefix, @args) {
-       # Incorporate the --prefix option, if requested.
-       if (length $prefix) {
-               unshift @args, '--prefix', $prefix;
-       }
-
-       # Prevent the --verbose option from being potentially implied.
-       delete local $ENV{'POSIXLY_CORRECT'};
-
-       # Execute localedef(1). Don't fork if doing so from a child process.
-       my @cmd = ('localedef', @args);
-       if ($$ == $PID) {
-               system @cmd;
-       } elsif (! exec @cmd) {
-               exit 1;
-       }
-}
-
-sub fopen ($path, $mode = '<') {
-       if (! open my $fh, $mode, $path) {
-               die "$PROGRAM: Can't open '$path': $!\n";
-       } elsif (! -f $fh && canonpath($path) !~ m/^\/dev\/(null|stdin)\z/) {
-               die "$PROGRAM: Won't open '$path' because it is not a regular 
file\n";
-       } else {
-               return $fh;
-       }
-}
-
-sub get_nprocs () {
-       chomp(my $nproc = qx{ { nproc || getconf _NPROCESSORS_CONF; } 
2>/dev/null });
-       return $nproc;
-}
-
-sub is_eq_file ($path1, $path2) {
-       # The -ef primary is standard as of POSIX.1-2024.
-       local @ENV{'PATH1', 'PATH2'} = ($path1, $path2);
-       return 0 == system q{ test "$PATH1" -ef "$PATH2" };
-}
-
-sub plural ($int) {
-       return $int == 1 ? '' : 's';
-}
+LOCALEDEF_OPTS=""
+KEEP=""
+DESTDIR=""
+CONFIG=""
+JUST_LIST=""
+ASK=""
+ALL=""
+UPDATE=""
+GENERATE=""
+JOBS_MAX=""
+QUIET=0
+SET_X=""
+LOCALE_ARCHIVE=true
+INPLACE_GLIBC=""
+while [[ $# -gt 0 ]] ; do
+       case $1 in
+               --inplace-glibc)           INPLACE_GLIBC=$1;;
+               -k|--keep|--keep-existing) KEEP=$1;;
+               -d|--destdir)              shift; DESTDIR=$1; unset ROOT;;
+               -c|--config)               shift; CONFIG=$1;;
+               -l|--list)                 JUST_LIST=$1;;
+               -a|--ask)                  ASK=$1;;
+               -A|--all)                  ALL=$1;;
+               -u|--update)               UPDATE=$1;;
+               -G|--generate)             shift; GENERATE=$1;;
+               -j|--jobs)                 shift; JOBS_MAX=$(( $1 ));;
+               -j*)                       : $(( JOBS_MAX = ${1#-j} ));;
+               -q|--quiet)                : $(( ++QUIET ));;
+               -x|--debug)                SET_X="true";;
+               -V|--version)              show_version;;
+               -h|--help)                 show_usage;;
+               --)                        shift; LOCALEDEF_OPTS=$*; break;;
+               *)                         show_usage $1;;
+       esac
+       shift
+done
+
+if [[ -n ${COMPILED_LOCALES} ]] ; then
+       ewarn "All locales have been installed and registered by the package 
manager. If you"
+       ewarn "rebuild the locale archive now, file integrity tools may show it 
as corrupted."
+       ewarn "This is not really a big problem, but a better solution is to 
disable"
+       ewarn "USE=compile-locales and re-install glibc if you dont need all 
locales."
+       echo
+fi
+
+if [[ -z ${JOBS_MAX} ]] ; then
+       JOBS_MAX=$(getconf _NPROCESSORS_ONLN 2>/dev/null)
+       : "${JOBS_MAX:=1}"
+fi
+[[ ${JOBS_MAX} -lt 1 ]] && JOBS_MAX=1
+[[ -n ${SET_X} ]] && set -x
+: "${KEEP:=${JUST_LIST}}"
+[[ -n ${GENERATE} ]] && UPDATE="true" && KEEP="true"
+
+: "${ROOT:=/}"
+ROOT="${ROOT%/}/"
+
+if [[ ${ROOT} != "/" ]] ; then
+       eerror "Sorry, but ROOT is not supported."
+       exit 0
+fi
+
+: "${EROOT:=${EPREFIX}/}"
+if [[ ${EROOT} != "/" ]] ; then
+       einfo "Using locale.gen from ${EROOT%/}/etc/"
+fi
+
+if [[ -n ${DESTDIR} ]] ; then
+       DESTDIR="${DESTDIR%/}/"
+       einfo "Building locales in DESTDIR '${DESTDIR}'"
+else
+       DESTDIR="${EROOT%/}/"
+fi
+
+: "${CONFIG:=${EROOT%/}/etc/locale.gen}"
+LOCALES=${DESTDIR}usr/share/i18n/locales
+CHARMAPS=${DESTDIR}usr/share/i18n/charmaps
+SUPPORTED=${DESTDIR}usr/share/i18n/SUPPORTED
+ALIAS=${DESTDIR}usr/share/locale/locale.alias
 
-sub redirect_stderr ($stderr, $callback) {
-       if (! open my $old_stderr, '>&', *STDERR) {
-               die "Can't dup STDERR to a new file descriptor: $!";
-       } elsif (! open *STDERR, '>&', $stderr) {
-               my $fileno = fileno $stderr;
-               die "Can't dup file descriptor #$fileno to STDERR: $!";
-       } else {
-               $callback->();
-               open *STDERR, '>&=', $old_stderr;
-       }
-}
-
-sub render_printable ($value) {
-       my $coder = JSON::PP->new->ascii->space_after;
-       return $coder->encode($value);
-}
-
-sub run ($cmd, @args) {
-       system $cmd, @args;
-       throw_child_error($cmd);
-}
-
-sub throw_child_error ($cmd, $status = $?) {
-       if ($status == -1) {
-               # The program could not be started. Since Perl will already
-               # have printed a warning, no supplemental diagnostic is needed.
-               exit 1;
-       } elsif ($status != 0) {
-               my $printable_cmd = render_printable($cmd);
-               my $fate = ($status & 0x7F) ? 'interrupted by a signal' : 
'unsuccessful';
-               die "$PROGRAM: Aborting because the execution of $printable_cmd 
was $fate\n";
-       }
+#
+# Grab any user options in their config file
+options=$(sed -n \
+       -e '/^[[:space:]]*#%/s:^[[:space:]]*#%[[:space:]]*::p'\
+       "${CONFIG}" 2>/dev/null
+)
+IFS=$'\n'
+for option in ${options} ; do
+       case ${option} in
+               no-locale-archive)
+                       LOCALE_ARCHIVE=false
+                       ;;
+               *)
+                       ewarn "Unrecognized option '${option}'"
+                       ;;
+       esac
+done
+unset IFS
+
+[[ -n ${ALL} ]] && CONFIG=${SUPPORTED}
+
+# Extract the location of the locale dir on the fly as `localedef --help` has:
+#                        locale path    : /usr/lib64/locale:/usr/share/i18n
+# For long paths, the line may get wrapped into two, in which case space (' ') 
is replaced
+# by newline (\n).
+LOCALEDIR=$(LC_ALL="C" "${DESTDIR}"usr/bin/localedef --help | sed -n -r 
'/locale path/{N;s|.*:[ \n](.*):/.*|\1|;p}')
+LOCALEDIR="${DESTDIR}${LOCALEDIR#${EPREFIX}}"
+if [[ $? -ne 0 ]] || [[ -z ${LOCALEDIR} ]] || [[ ${LOCALEDIR} != 
${DESTDIR}/usr/lib*/locale ]] ; then
+       eerror "Unable to parse the output of your localedef utility." 1>&2
+       eerror "File a bug about this issue and include the output of 
'localedef --help'." 1>&2
+       exit 1
+fi
+
+# Only generate locales the user specified before falling back to the config.
+locales_to_generate=${GENERATE}
+
+if [[ -z ${locales_to_generate} ]] && [[ -e ${CONFIG} ]] ; then
+       locales_to_generate=$(sed \
+               -e 's:#.*::' \
+               -e '/^[[:space:]]*$/d' \
+               "${CONFIG}" | sort)
+       # Sanity check to make sure people did not duplicate entries. #550884
+       # The first column must be unique specifically. #235555
+       dup_locales_to_generate=$(
+               echo "${locales_to_generate}" | \
+                       awk '{ if ($1 == last) { print lastline; print; } else 
{ lastline = $0; last = $1; } }')
+       if [[ -n ${dup_locales_to_generate} ]] ; then
+               ewarn "These locales have been duplicated in your 
config:\n${dup_locales_to_generate}"
+               ewarn "Some might be filtered, but you must fix it."
+               locales_to_generate=$(echo "${locales_to_generate}" | uniq)
+       fi
+fi
+
+# Transform the name in locales.gen to the name used when storing the locale 
data in
+# /usr/lib/locale/.  This normalize algo is taken out of the glibc localedef 
source:
+# 
https://sourceware.org/git/?p=glibc.git;a=blob;f=locale/programs/localedef.c;hb=glibc-2.34#l562
+normalize() {
+       if [[ $1 == *.* ]] ; then
+               local ret=${1##*.}
+               ret=${ret,,}
+               echo "${1%%.*}.${ret//-}"
+       else
+               echo "$1"
+       fi
 }
 
-sub trim_line ($line) {
-       return $line =~ s/^\h+|\h+$//gr;
+# These funky sed's are based on the stuff in glibc's localedata/Makefile
+# Basically we want to rewrite the display like so:
+# <locale without a . or @>.<charmap>[@extra stuff after the @ in the locale]
+# en_US       ISO-8859-1  -> en_US.ISO-8859-1
+# en_US.UTF-8 UTF-8       -> en_US.UTF-8
+# de_DE@euro  ISO-8859-15 -> de_DE.ISO-8859-15@euro
+locales_disp=$(echo "${locales_to_generate}" | sed \
+       -e '   /@/   
s:[[:space:]]*\([^@[:space:]]*\)\([^[:space:]]*\)[[:space:]]\+\([^[:space:]]*\):\1.\3\2:'
 \
+       -e 
'/^[^@]*$/s:[[:space:]]*\([^.[:space:]]*\)\([^[:space:]]*\)[[:space:]]\+\([^[:space:]]*\):\1.\3:')
+
+# Now check the normalized version for C.UTF-8, and add it if not present
+if [[ -z ${locales_to_generate} ]] ; then
+       if [[ -z ${JUST_LIST} ]] ; then
+               [[ ${QUIET} -eq 0 ]] && \
+                       ewarn "No locales to generate found, keeping archive 
but ensuring C.UTF-8 is present"
+               KEEP=1
+               UPDATE=1
+               locales_disp='C.UTF-8'
+               locales_to_generate='C.UTF-8 UTF-8'
+       fi
+else
+       if echo ${locales_disp} | grep -vqi 'C.UTF-8' ; then
+               locales_to_generate=$(echo "${locales_to_generate}" ; echo -n 
'C.UTF-8 UTF-8')
+               locales_disp=$(echo "${locales_disp}" ; echo -n 'C.UTF-8')
+       fi
+fi
+
+mkdir -p "${LOCALEDIR}"
+if [[ -z ${KEEP} && -z ${UPDATE} ]] ; then
+       # Remove all old locale dir and locale-archive before generating new
+       # locale data.  Scrubbing via update is done elsewhere.
+       rm -rf "${LOCALEDIR}"/* &> /dev/null || true
+fi
+
+eval declare -a locales_disp=(${locales_disp})
+eval declare -a locales_to_generate=(${locales_to_generate})
+total=$(( ${#locales_to_generate[*]} / 2 ))
+
+[[ ${QUIET} -eq 0 ]] && [[ -z ${JUST_LIST} ]] && \
+einfo "Generating ${total} locales (this might take a while) with ${JOBS_MAX} 
jobs"
+
+if [[ -n ${UPDATE} ]] ; then
+       # normalize newlines into spaces
+       existing_locales=" $(echo $(locale -a 2>/dev/null)) "
+fi
+
+generate_locale() {
+       local output=""
+
+       if [[ -z ${ASK} ]] && [[ ${QUIET} -eq 0 ]] ; then
+               output=" (${cnt_fmt}/${total}) Generating ${disp}"
+       fi
+
+       if [[ $(( JOB_IDX_E - JOB_IDX_S )) == ${JOBS_MAX} ]] ; then
+               wait ${JOB_PIDS[$(( JOB_IDX_S++ ))]}
+               JOB_RETS+=( $? )
+       fi
+       (
+               # Accumulate all the output in one go so the parallel
+               # jobs don't tromp on each other
+               x=$(
+                       [[ -n ${output} ]] && ebegin "${output}"
+                       # In most cases, localedef can just use the system 
glibc.
+                       # However, if we are within a major glibc upgrade, this 
may fail
+                       # in src_* phases since the new localedef links against 
the new
+                       # glibc, but the new glibc is not installed yet...
+                       if [[ -z ${INPLACE_GLIBC} ]] ; then
+                               "${DESTDIR}"usr/bin/localedef ${LOCALEDEF_OPTS} 
\
+                                       --no-archive \
+                                       -i "${input}" \
+                                       -f "${charmap}" \
+                                       -A "${ALIAS}" \
+                                       --prefix "${DESTDIR%${EPREFIX}/}/" \
+                                       "${locale}" 2>&1
+                       else
+                               # We assume that the current directory is 
"${ED}"/$(get_libdir),
+                               # see the glibc ebuild, function 
glibc_sanity_check(), for why.
+                               LC_ALL=C ./ld-*.so --library-path . \
+                                       "${DESTDIR}"usr/bin/localedef 
${LOCALEDEF_OPTS} \
+                                       --no-archive \
+                                       -i "${input}" \
+                                       -f "${charmap}" \
+                                       -A "${ALIAS}" \
+                                       --prefix "${DESTDIR%${EPREFIX}/}/" \
+                                       "${locale}" 2>&1
+                       fi
+                       ret=$?
+                       [[ -n ${output} ]] && eend ${ret}
+                       exit ${ret}
+               )
+               ret=$?
+               if [[ -n ${output} ]] ; then
+                       echo "${x}"
+               elif [[ ${ret} -ne 0 ]] ; then
+                       eerror "${disp}: ${x}"
+               fi
+
+               if [[ ${ret} -ne 0 && ${locale} == */* ]] ; then
+                       ewarn "Perhaps you meant to use a space instead of a / 
in your config file ?"
+               fi
+               exit ${ret}
+       ) &
+       JOB_PIDS+=( $! )
+       : $(( ++JOB_IDX_E ))
 }
 
-END {
-       if ($$ == $PID) {
-               if (length $TEMPDIR) {
-                       local $?;
-                       system 'rm', '-r', '--', $TEMPDIR;
-               }
-
-               # The default SIGINT and SIGTERM handlers are suppressed by
-               # generate_locales. The former is especially important, per
-               # http://www.cons.org/cracauer/sigint.html.
-               if ($DEFERRED_SIGNAL) {
-                       kill $DEFERRED_SIGNAL, $$;
-               }
-       }
-}
+JOB_PIDS=()
+JOB_RETS=()
+JOB_IDX_S=0
+JOB_IDX_E=0
+cnt=0
+lidx=0
+# Keep track of (normalized) locales generated in case the request has 
different inputs that
+# normalize down to the same value.  We trim $existing_locales as we go for 
later use which
+# prevents its direct use.
+generated_locales=" "
+while [[ -n ${locales_to_generate[${lidx}]} ]] ; do
+       : $(( ++cnt ))
+       locale=${locales_to_generate[$((lidx++))]}
+       charmap=${locales_to_generate[$((lidx++))]}
+
+       # XXX: if we wanted to, we could check existence of
+       #      ${LOCALES}/${locale} and ${CHARMAPS}/${charmap}
+       #      this would fail for things like "en_US.UTF8", but
+       #      in that case we could fall back to checking the
+       #      SUPPORTED file ... then again, the localedef
+       #      below will abort nicely for us ...
+       if [[ -z ${locale} || -z ${charmap} ]] ; then
+               eerror "Bad entry in locale.gen: '${locale} ${charmap}'; 
skipping"
+               continue
+       fi
+
+       disp=${locales_disp[$(( cnt - 1 ))]}
+
+       normalized_locale=$(normalize ${locale})
+       if [[ ${generated_locales} == *" ${normalized_locale} "* ]] ; then
+               already_generated="true"
+       else
+               already_generated="false"
+       fi
+       generated_locales+="${normalized_locale} "
+       if ${already_generated} || \
+          [[ -n ${UPDATE} && ${existing_locales} == *" ${normalized_locale} "* 
]] ; then
+               existing_locales=${existing_locales/ ${normalized_locale} / }
+               if [[ ${QUIET} -eq 0 ]] ; then
+                       cnt_fmt=$(printf "%${#total}i" ${cnt})
+                       einfo " (${cnt_fmt}/${total}) Skipping ${disp}"
+               fi
+               continue
+       fi
+
+       # If the locale is like 'en_US.UTF8', then we really want 'en_US'
+       if [[ -f ${LOCALES}/${locale} ]] ; then
+               input=${locale}
+       else
+               input=${locale%%.*}
+       fi
+
+       if [[ -z ${JUST_LIST} ]] ; then
+               # Format the output for the question/status
+               cnt_fmt=$(printf "%${#total}i" ${cnt})
+               if [[ -n ${ASK} ]] ; then
+                       einfon " (${cnt_fmt}/${total}) Generate ${disp} ? (Y/n) 
"
+                       read user_answer
+                       [[ ${user_answer} == [nN]* ]] && continue
+               fi
+               generate_locale
+       else
+               echo "${disp}"
+       fi
+done
+
+for (( i = JOB_IDX_S; i < JOB_IDX_E; ++i )) ; do
+       wait ${JOB_PIDS[i]}
+       JOB_RETS+=( $? )
+done
+ret=$(( 0 ${JOB_RETS[@]/#/+} ))
+
+[[ ${QUIET} -eq 0 ]] && [[ -z ${JUST_LIST} ]] && \
+einfo "Generation complete"
+
+if ${LOCALE_ARCHIVE} && [[ -z ${JUST_LIST} ]] ; then
+       # need to check that at least one locale has to be added
+       if [[ $(echo "${LOCALEDIR}"/*/) != "${LOCALEDIR}"'/*/' ]] ; then
+               [[ ${QUIET} -eq 0 ]] && ebegin "Adding locales to archive"
+               # The pattern ends with / on purpose: we don't care about files 
(like
+               # locale-archive) in the locale subdir, and we definitely don't 
want to
+               # delete them!
+               for LOC in "${LOCALEDIR}"/*/; do
+                       LOC=${LOC%/} # Strip trailing /, since localedef 
doesn't like it
+                       x=$(
+                               # In most cases, localedef can just use the 
system glibc.
+                               # However, if we are within a major glibc 
upgrade, this may fail
+                               # in src_* phases since the new localedef links 
against the new
+                               # glibc, but the new glibc is not installed 
yet...
+                               if [[ -z ${INPLACE_GLIBC} ]] ; then
+                                       "${DESTDIR}"usr/bin/localedef \
+                                               --add-to-archive "${LOC}" \
+                                               --replace \
+                                               --prefix 
"${DESTDIR%${EPREFIX}/}/"
+                               else
+                                       # We assume that the current directory 
is "${ED}"/$(get_libdir),
+                                       # see the glibc ebuild, function 
glibc_sanity_check(), for why.
+                                       LC_ALL=C ./ld-*.so --library-path . \
+                                               "${DESTDIR}"usr/bin/localedef \
+                                               --add-to-archive "${LOC}" \
+                                               --replace \
+                                               --prefix 
"${DESTDIR%${EPREFIX}/}/"
+                               fi
+                               ret=$?
+                               if [[ -n ${output} ]] ; then
+                                       echo "${x}"
+                               elif [[ ${ret} -ne 0 ]] ; then
+                                       eerror "${disp}: ${x}"
+                               fi
+                               if [[ $ret -eq 0 ]]; then
+                                       rm -r "${LOC}"
+                               fi
+                               exit ${ret}
+                       )
+               done
+               [[ ${QUIET} -eq 0 ]] && eend ${ret}
+       elif [[ ${QUIET} -eq 0 ]] ; then
+               einfo "No locales are to be added to the archive."
+       fi
+fi
+
+# Remove locales that existed but were not requested
+if [[ -n ${UPDATE} ]] && [[ -z ${JUST_LIST} ]] ; then
+       # Ignore these pseudo locales
+       existing_locales=${existing_locales/ C / }
+       existing_locales=${existing_locales/ POSIX / }
+       if [[ -n ${existing_locales// } ]] ; then
+               if [[ -z ${KEEP} ]] ; then
+                       [[ ${QUIET} -eq 0 ]] && einfo "Scrubbing old 
locales:"${existing_locales}
+                       cd "${LOCALEDIR}" && rm -rf ${existing_locales}
+               else
+                       [[ ${QUIET} -eq 0 ]] && einfo "Keeping old 
locales:"${existing_locales}
+               fi
+       fi
+fi
+
+exit ${ret}


Reply via email to