]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - configure
Fix using std::cout instead of errstr when a port fails to bind.
[user/henk/code/inspircd.git] / configure
1 #!/usr/bin/env perl
2 #
3 # InspIRCd -- Internet Relay Chat Daemon
4 #
5 #   Copyright (C) 2020 Nicole Kleinhoff <ilbelkyr@shalture.org>
6 #   Copyright (C) 2020 Daniel Vassdal <shutter@canternet.org>
7 #   Copyright (C) 2019 Matt Schatz <genius3000@g3k.solutions>
8 #   Copyright (C) 2019 Anatole Denis <natolumin@rezel.net>
9 #   Copyright (C) 2013-2021 Sadie Powell <sadie@witchery.services>
10 #   Copyright (C) 2012, 2019 Robby <robby@chatbelgie.be>
11 #   Copyright (C) 2012 ChrisTX <xpipe@hotmail.de>
12 #   Copyright (C) 2010 Daniel De Graaf <danieldg@inspircd.org>
13 #   Copyright (C) 2008 Thomas Stagner <aquanight@inspircd.org>
14 #   Copyright (C) 2008 Robin Burchell <robin+git@viroteck.net>
15 #   Copyright (C) 2007 Dennis Friis <peavey@inspircd.org>
16 #   Copyright (C) 2006-2008 Craig Edwards <brain@inspircd.org>
17 #
18 # This file is part of InspIRCd.  InspIRCd is free software: you can
19 # redistribute it and/or modify it under the terms of the GNU General Public
20 # License as published by the Free Software Foundation, version 2.
21 #
22 # This program is distributed in the hope that it will be useful, but WITHOUT
23 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
24 # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
25 # details.
26 #
27 # You should have received a copy of the GNU General Public License
28 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
29 #
30
31
32 use v5.10.0;
33 use strict;
34 use warnings FATAL => qw(all);
35
36 use Cwd                   qw(getcwd);
37 use File::Basename        qw(basename);
38 use File::Copy            ();
39 use File::Spec::Functions qw(abs2rel catfile catdir rel2abs);
40 use FindBin               qw($RealDir);
41 use Getopt::Long          qw(GetOptions);
42 use POSIX                 qw(getgid getuid);
43
44 use lib $RealDir;
45 use make::common;
46 use make::configure;
47 use make::console;
48 use make::directive;
49
50 my ($opt_binary_dir,
51     $opt_config_dir,
52     $opt_data_dir,
53     $opt_development,
54     $opt_disable_auto_extras,
55     $opt_disable_interactive,
56     $opt_distribution_label,
57     $opt_example_dir,
58     $opt_gid,
59     $opt_log_dir,
60     $opt_manual_dir,
61     $opt_module_dir,
62     $opt_portable,
63     $opt_prefix,
64     $opt_runtime_dir,
65     $opt_script_dir,
66     $opt_socketengine,
67     $opt_system,
68     $opt_uid);
69
70 sub list_extras ();
71
72 sub enable_extras (@);
73
74 sub disable_extras (@);
75
76 my @opt_enableextras;
77 my @opt_disableextras;
78
79 exit 1 unless GetOptions(
80         'clean'  => \&cmd_clean,
81         'help'   => \&cmd_help,
82         'update' => \&cmd_update,
83
84         'binary-dir=s'         => \$opt_binary_dir,
85         'config-dir=s'         => \$opt_config_dir,
86         'data-dir=s'           => \$opt_data_dir,
87         'development'          => \$opt_development,
88         'disable-auto-extras'  => \$opt_disable_auto_extras,
89         'disable-interactive'  => \$opt_disable_interactive,
90         'distribution-label=s' => \$opt_distribution_label,
91         'example-dir=s'        => \$opt_example_dir,
92         'gid=s'                => \$opt_gid,
93         'log-dir=s'            => \$opt_log_dir,
94         'manual-dir=s'         => \$opt_manual_dir,
95         'module-dir=s'         => \$opt_module_dir,
96         'portable'             => \$opt_portable,
97         'prefix=s'             => \$opt_prefix,
98         'runtime-dir=s'        => \$opt_runtime_dir,
99         'script-dir=s'         => \$opt_script_dir,
100         'socketengine=s'       => \$opt_socketengine,
101         'system'               => \$opt_system,
102         'uid=s'                => \$opt_uid,
103
104         # TODO: when the modulemanager rewrite is done these should be removed.
105         'disable-extras=s@' => \@opt_disableextras,
106         'enable-extras=s@'  => \@opt_enableextras,
107         'list-extras'       => sub { list_extras; exit 0; },
108 );
109
110 if (scalar(@opt_enableextras) + scalar(@opt_disableextras) > 0) {
111         @opt_enableextras = grep { /\S/ } split /[, ]+/, join(',', @opt_enableextras);
112         @opt_disableextras = grep { /\S/ } split /[, ]+/, join(',', @opt_disableextras);
113         enable_extras(@opt_enableextras);
114         disable_extras(@opt_disableextras);
115         list_extras;
116         print "Remember: YOU are responsible for making sure any libraries needed have been installed!\n";
117         exit 0;
118 }
119
120 our $interactive = !(
121         !-t STDIN ||
122         !-t STDOUT ||
123         defined $opt_binary_dir ||
124         defined $opt_config_dir ||
125         defined $opt_data_dir ||
126         defined $opt_development ||
127         defined $opt_disable_auto_extras ||
128         defined $opt_disable_interactive ||
129         defined $opt_distribution_label ||
130         defined $opt_example_dir ||
131         defined $opt_gid ||
132         defined $opt_log_dir ||
133         defined $opt_manual_dir ||
134         defined $opt_module_dir ||
135         defined $opt_portable ||
136         defined $opt_prefix ||
137         defined $opt_runtime_dir ||
138         defined $opt_script_dir ||
139         defined $opt_socketengine ||
140         defined $opt_system ||
141         defined $opt_uid
142 );
143
144 my %version = get_version $opt_distribution_label;
145 say console_format "<|BOLD Configuring InspIRCd $version{FULL} on $^O.|>";
146
147 my %config;
148 if ($interactive) {
149         %config = read_config_file(CONFIGURE_CACHE_FILE);
150         run_test abs2rel(CONFIGURE_CACHE_FILE, $RealDir), %config;
151         if (!defined $config{VERSION}) {
152                 $config{VERSION} = CONFIGURE_CACHE_VERSION;
153         } elsif ($config{VERSION} != CONFIGURE_CACHE_VERSION) {
154                 print_warning "ignoring contents of ${\CONFIGURE_CACHE_FILE} as it was generated by an incompatible version of $0!";
155                 %config = ('VERSION', CONFIGURE_CACHE_VERSION);
156         }
157 }
158
159 $config{CXX} = find_compiler($config{CXX} // $ENV{CXX});
160 unless ($config{CXX}) {
161         say 'A suitable C++ compiler could not be detected on your system!';
162         unless ($interactive) {
163                 say 'Set the CXX environment variable to the path to a C++ compiler binary if this is incorrect.';
164                 exit 1;
165         }
166         until ($config{CXX}) {
167                 my $compiler_path = prompt_string 1, 'Please enter the path to a C++ compiler binary:', 'c++';
168                 $config{CXX} = find_compiler $compiler_path;
169         }
170 }
171 my %compiler = get_compiler_info($config{CXX});
172
173 $config{HAS_ARC4RANDOM_BUF} = run_test 'arc4random_buf()', test_file($config{CXX}, 'arc4random_buf.cpp');
174 $config{HAS_CLOCK_GETTIME} = run_test 'clock_gettime()', test_file($config{CXX}, 'clock_gettime.cpp', $^O eq 'darwin' ? undef : '-lrt');
175 $config{HAS_EVENTFD} = run_test 'eventfd()', test_file($config{CXX}, 'eventfd.cpp');
176
177 my @socketengines;
178 push @socketengines, 'epoll'  if run_test 'epoll', test_header $config{CXX}, 'sys/epoll.h';
179 push @socketengines, 'kqueue' if run_test 'kqueue', test_file $config{CXX}, 'kqueue.cpp';
180 push @socketengines, 'poll'   if run_test 'poll', test_header $config{CXX}, 'poll.h';
181 push @socketengines, 'select';
182
183 if (defined $opt_socketengine) {
184         unless (grep { $_ eq $opt_socketengine } @socketengines) {
185                 my $reason = -f "src/socketengines/socketengine_$opt_socketengine.cpp" ? 'is not available on this platform' : 'does not exist';
186                 print_error "The socket engine you requested ($opt_socketengine) $reason!",
187                         'Available socket engines are:',
188                         map { "  * $_" } @socketengines;
189         }
190 }
191 $config{SOCKETENGINE} = $opt_socketengine // $socketengines[0];
192
193 if (defined $opt_portable) {
194         print_error '--portable and --system can not be used together!' if defined $opt_system;
195         $config{DESTDIR}     = catfile $RealDir, 'run', '';
196         $config{BASE_DIR}    = $opt_prefix      // '';
197         $config{BINARY_DIR}  = $opt_binary_dir  // 'bin';
198         $config{CONFIG_DIR}  = $opt_config_dir  // 'conf';
199         $config{DATA_DIR}    = $opt_data_dir    // 'data';
200         $config{EXAMPLE_DIR} = $opt_example_dir // catdir $config{CONFIG_DIR}, 'examples';
201         $config{LOG_DIR}     = $opt_log_dir     // 'logs';
202         $config{MANUAL_DIR}  = $opt_manual_dir  // 'manuals';
203         $config{MODULE_DIR}  = $opt_module_dir  // 'modules';
204         $config{RUNTIME_DIR} = $opt_runtime_dir // $config{DATA_DIR};
205         $config{SCRIPT_DIR}  = $opt_script_dir  // $config{BASE_DIR};
206 } elsif (defined $opt_system) {
207         $config{BASE_DIR}    = $opt_prefix      // '/';
208         $config{BINARY_DIR}  = $opt_binary_dir  // '/usr/sbin';
209         $config{CONFIG_DIR}  = $opt_config_dir  // '/etc/inspircd';
210         $config{DATA_DIR}    = $opt_data_dir    // '/var/lib/inspircd';
211         $config{EXAMPLE_DIR} = $opt_example_dir // '/usr/share/doc/inspircd';
212         $config{LOG_DIR}     = $opt_log_dir     // '/var/log/inspircd';
213         $config{MANUAL_DIR}  = $opt_manual_dir  // '/usr/share/man/man1';
214         $config{MODULE_DIR}  = $opt_module_dir  // '/usr/lib/inspircd';
215         $config{RUNTIME_DIR} = $opt_runtime_dir // '/var/run';
216         $config{SCRIPT_DIR}  = $opt_script_dir  // '/usr/share/inspircd';
217 } else {
218         $config{BASE_DIR}    = rel2abs $opt_prefix // $config{BASE_DIR}    // catdir $RealDir,            'run';
219         $config{BINARY_DIR}  = $opt_binary_dir     // $config{BINARY_DIR}  // catdir $config{BASE_DIR},   'bin';
220         $config{CONFIG_DIR}  = $opt_config_dir     // $config{CONFIG_DIR}  // catdir $config{BASE_DIR},   'conf';
221         $config{DATA_DIR}    = $opt_data_dir       // $config{DATA_DIR}    // catdir $config{BASE_DIR},   'data';
222         $config{EXAMPLE_DIR} = $opt_example_dir    // $config{EXAMPLE_DIR} // catdir $config{CONFIG_DIR}, 'examples';
223         $config{LOG_DIR}     = $opt_log_dir        // $config{LOG_DIR}     // catdir $config{BASE_DIR},   'logs';
224         $config{MANUAL_DIR}  = $opt_manual_dir     // $config{MANUAL_DIR}  // catdir $config{BASE_DIR},   'manuals';
225         $config{MODULE_DIR}  = $opt_module_dir     // $config{MODULE_DIR}  // catdir $config{BASE_DIR},   'modules';
226         $config{RUNTIME_DIR} = $opt_runtime_dir    // $config{RUNTIME_DIR} // $config{DATA_DIR};
227         $config{SCRIPT_DIR}  = $opt_script_dir     // $config{SCRIPT_DIR}  // $config{BASE_DIR};
228 }
229
230 # Parse --gid=123 or --gid=foo and extract the group id.
231 my @group;
232 if (defined $opt_gid) {
233         @group = $opt_gid =~ /^\d+$/ ? getgrgid($opt_gid) : getgrnam($opt_gid);
234         print_error "there is no '$opt_gid' group on this system!" unless @group;
235 } else {
236         @group = $opt_system ? getgrnam('irc') : getgrgid($config{GID} // getgid());
237         print_error "you need to specify a group to run as using '--gid [id|name]'!" unless @group;
238         unless ($group[2]) {
239                 print_warning <<"EOW";
240 You are building as the privileged $group[0] group and have not specified
241 an unprivileged group to run InspIRCd as.
242
243 This is almost never what you should do. You should probably either create a new
244 unprivileged user/group to build and run as or pass the '--gid [id|name]' flag
245 to specify an unprivileged group to run as.
246 EOW
247                 if (!prompt_bool $interactive, "Are you sure you want to build as the $group[0] group?", 0) {
248                         # PACKAGERS: You do not need to delete this check. Use `--gid $(id -g)` or `--gid 0` instead.
249                         say STDERR "If you are sure you want to build as the $group[0] group pass the --gid $group[2] flag." unless $interactive;
250                         exit 1;
251                 }
252         }
253 }
254 $config{GROUP} = $group[0];
255 $config{GID}   = $group[2];
256
257 # Parse --uid=123 or --uid=foo and extract the user id.
258 my @user;
259 if (defined $opt_uid) {
260         @user = $opt_uid =~ /^\d+$/ ? getpwuid($opt_uid) : getpwnam($opt_uid);
261         print_error "there is no '$opt_uid' user on this system!" unless @user;
262 } else {
263         @user = $opt_system ? getpwnam('irc') : getpwuid($config{UID} // getuid());
264         print_error "you need to specify a user to run as using '--uid [id|name]'!" unless @user;
265         unless ($user[2]) {
266                 print_warning <<"EOW";
267 You are building as the privileged $user[0] user and have not specified
268 an unprivileged user to run InspIRCd as.
269
270 This is almost never what you should do. You should probably either create a new
271 unprivileged user/group to build and run as or pass the '--uid [id|name]' flag
272 to specify an unprivileged user to run as.
273 EOW
274                 if (!prompt_bool $interactive, "Are you sure you want to build as the $user[0] user?", 0) {
275                         # PACKAGERS: You do not need to delete this check. Use `--uid $(id -u)` or `--uid 0` instead.
276                         say STDERR "If you are sure you want to build as the $user[0] user pass the --uid $user[2] flag." unless $interactive;
277                         exit 1;
278                 }
279         }
280 }
281 $config{USER} = $user[0];
282 $config{UID}  = $user[2];
283
284 # Warn the user about clock drifting when running on OpenVZ.
285 if (-e '/proc/user_beancounters' || -e '/proc/vz/vzaquota') {
286         print_warning <<'EOW';
287 You are building InspIRCd inside of an OpenVZ container. If you
288 plan to use InspIRCd in this container then you should make sure that NTP is
289 configured on the Hardware Node. Failure to do so may result in clock drifting!
290 EOW
291 }
292
293 # Warn the user about OpenBSD shipping incredibly broken compilers/linkers.
294 if ($^O eq 'openbsd') {
295         print_warning <<'EOW';
296 You are building InspIRCd on OpenBSD. The C++ compilers and linkers
297 that OpenBSD ship are incredibly broken. You may have strange linker errors
298 and crashes. Please consider using a different OS like FreeBSD/NetBSD instead.
299 EOW
300 }
301
302 # Warn about Perl versions that will not be supported in the future.
303 if ($^V lt 'v5.26.0') {
304         print_warning <<"EOW";
305 You are building InspIRCd with Perl $^V. This is very old and will
306 not be supported by the next major version of InspIRCd. Please consider updating
307 to Perl v5.26 or newer.
308 EOW
309 }
310
311 # Warn about compiler versions that will not be supported in the future.
312 my %future_compilers = (
313         AppleClang => version->parse('10.0'),
314         Clang      => version->parse('5.0'),
315         GCC        => version->parse('7.0'),
316 );
317 if (exists $future_compilers{$compiler{NAME}} && $compiler{VERSION} lt $future_compilers{$compiler{NAME}}) {
318         print_warning <<"EOW";
319 You are building InspIRCd with $compiler{NAME} v$compiler{VERSION}. This is very old and
320 will not be supported by the next major version of InspIRCd. Please consider
321 updating to $compiler{NAME} v$future_compilers{$compiler{NAME}} or newer.
322 EOW
323 }
324
325 # Check that the user actually wants this version.
326 if (defined $version{REAL_LABEL}) {
327         print_warning <<'EOW';
328 You are building a development version. This contains code which has
329 not been tested as heavily and may contain various faults which could seriously
330 affect the running of your server. It is recommended that you use a stable
331 version instead.
332
333 You can obtain the latest stable version from https://www.inspircd.org or by
334 running `<|GREEN git checkout $(git describe --abbrev=0 --tags insp3)|>` if you are
335 installing from Git.
336 EOW
337         if (!prompt_bool $interactive, 'I understand this warning and want to continue anyway.', $opt_development // 0) {
338                 say STDERR 'If you understand this warning and still want to continue pass the --development flag.' unless $interactive;
339                 exit 1;
340         }
341 }
342
343 # Configure directory settings.
344 my $question = <<"EOQ";
345 Currently, InspIRCd is configured with the following paths:
346
347 <|BOLD Binary:|> $config{BINARY_DIR}
348 <|BOLD Config:|> $config{CONFIG_DIR}
349 <|BOLD Data:|>   $config{DATA_DIR}
350 <|BOLD Log:|>    $config{LOG_DIR}
351 <|BOLD Manual:|> $config{MANUAL_DIR}
352 <|BOLD Module:|> $config{MODULE_DIR}
353 <|BOLD Script:|> $config{SCRIPT_DIR}
354
355 Do you want to change these settings?
356 EOQ
357 if (prompt_bool $interactive, $question, 0) {
358         my $original_base_dir = $config{BASE_DIR};
359         $config{BASE_DIR} = prompt_dir $interactive, 'In what directory do you wish to install the InspIRCd base?', $config{BASE_DIR};
360         for my $key (qw(BINARY_DIR CONFIG_DIR DATA_DIR LOG_DIR MANUAL_DIR MODULE_DIR SCRIPT_DIR)) {
361                 $config{$key} =~ s/^\Q$original_base_dir\E/$config{BASE_DIR}/;
362         }
363         $config{BINARY_DIR} = prompt_dir $interactive, 'In what directory should the InspIRCd binary be placed?', $config{BINARY_DIR};
364         $config{CONFIG_DIR} = prompt_dir $interactive, 'In what directory are configuration files to be stored?', $config{CONFIG_DIR};
365         $config{DATA_DIR}   = prompt_dir $interactive, 'In what directory are variable data files to be stored?', $config{DATA_DIR};
366         $config{LOG_DIR}    = prompt_dir $interactive, 'In what directory are log files to be stored?',           $config{LOG_DIR};
367         $config{MANUAL_DIR} = prompt_dir $interactive, 'In what directory are manual pages to be placed?',        $config{MANUAL_DIR};
368         $config{MODULE_DIR} = prompt_dir $interactive, 'In what directory are modules to be placed?',             $config{MODULE_DIR};
369         $config{SCRIPT_DIR} = prompt_dir $interactive, 'In what directory are scripts to be placed?',             $config{SCRIPT_DIR};
370         $config{EXAMPLE_DIR} = $config{CONFIG_DIR} . '/examples';
371         $config{RUNTIME_DIR} = $config{DATA_DIR};
372 }
373
374 # Configure module settings.
375 $question = <<'EOQ';
376 Currently, InspIRCd is configured to automatically enable all available extra modules.
377
378 Would you like to enable extra modules manually?
379 EOQ
380 if (prompt_bool $interactive, $question, 0) {
381         for my $extra (<$RealDir/src/modules/extra/m_*.cpp>) {
382                 my $module_name = module_shrink $extra;
383                 if (prompt_bool $interactive, "Would you like to enable the <|BOLD $module_name|> module?", 0) {
384                         enable_extras $module_name;
385                 }
386         }
387 } elsif (!defined $opt_disable_auto_extras) {
388         # TODO: finish modulemanager rewrite and replace this code with:
389         # system './modulemanager', 'enable', '--auto';
390         my %modules = (
391                 # Missing: m_ldap, m_regex_stdlib, m_ssl_mbedtls
392                 'm_argon2.cpp'          => 'pkg-config --exists libargon2',
393                 'm_geo_maxmind.cpp'     => 'pkg-config --exists libmaxminddb',
394                 'm_mysql.cpp'           => 'mysql_config --version',
395                 'm_pgsql.cpp'           => 'pg_config --version',
396                 'm_regex_pcre.cpp'      => 'pcre-config --version',
397                 'm_regex_posix.cpp'     => undef,
398                 'm_regex_re2.cpp'       => 'pkg-config --exists re2',
399                 'm_regex_tre.cpp'       => 'pkg-config --exists tre',
400                 'm_sqlite3.cpp'         => 'pkg-config --exists sqlite3',
401                 'm_ssl_gnutls.cpp'      => 'pkg-config --exists gnutls',
402                 'm_ssl_openssl.cpp'     => 'pkg-config --exists openssl',
403                 'm_sslrehashsignal.cpp' => undef,
404         );
405         while (my ($module, $command) = each %modules) {
406                 unless (defined $command && system "$command 1>/dev/null 2>/dev/null") {
407                         enable_extras $module;
408                 }
409         }
410 }
411
412 # Generate SSL certificates.
413 $question = <<EOQ;
414 Would you like to generate a self-signed SSL certificate now? This certificate
415 can be used for testing but <|BOLD should not|> be used on a production network.
416
417 Note: you can get a <|BOLD free|> CA-signed certificate from Let's Encrypt. See
418 https://letsencrypt.org/getting-started/ for more details.
419 EOQ
420
421 if (<$RealDir/src/modules/m_ssl_*.cpp>) {
422         if (prompt_bool $interactive, $question, $interactive) {
423                 create_directory CONFIGURE_DIRECTORY, 0750 or print_error "unable to create ${\CONFIGURE_DIRECTORY}: $!";
424                 system './tools/genssl', 'auto', CONFIGURE_DIRECTORY;
425         } else {
426                 my @pems = <${\CONFIGURE_DIRECTORY}/{cert,csr,dhparams,key}.pem>;
427                 $question = <<EOQ;
428 The following self-signed files were previously generated and will be installed
429 when you run Make. Do you want to delete them?
430
431   * ${\join "\n  * ", @pems}
432 EOQ
433                 if (@pems && prompt_bool $interactive, $question, 0) {
434                         unlink @pems;
435                 }
436         }
437 } elsif (!defined $opt_disable_auto_extras) {
438         print_warning <<"EOM";
439 You are building without enabling any SSL modules. This is not
440 recommended as SSL greatly enhances the security and privacy of your IRC server
441 and in a future version will be <|BOLD required|> for linking servers.
442
443 Please read the following documentation pages on how to enable SSL support:
444
445 GnuTLS (recommended): https://docs.inspircd.org/3/modules/ssl_gnutls
446 mbedTLS:              https://docs.inspircd.org/3/modules/ssl_mbedtls
447 OpenSSL:              https://docs.inspircd.org/3/modules/ssl_openssl
448 EOM
449 }
450
451 # Cache the distribution label so that its not lost when --update is run.
452 $config{DISTRIBUTION} = $opt_distribution_label if $opt_distribution_label;
453
454 write_configure_cache %config;
455 parse_templates \%config, \%compiler, \%version;
456
457 print console_format <<"EOM";
458
459 Configuration is complete! You have chosen to build with the following settings:
460
461 <|GREEN Compiler:|>
462   <|GREEN Binary:|>  $config{CXX}
463   <|GREEN Name:|>    $compiler{NAME}
464   <|GREEN Version:|> $compiler{VERSION}
465
466 <|GREEN Extra Modules:|>
467 EOM
468
469 for my $file (<$RealDir/src/modules/m_*>) {
470         say "  * ${\module_shrink $file}" if -l $file;
471 }
472
473 my @makeargs;
474 push @makeargs, "-C${\abs2rel $RealDir}" unless getcwd eq $RealDir;
475 push @makeargs, "-j${\(get_cpu_count() + 1)}";
476
477 print console_format <<"EOM";
478
479 <|GREEN Paths:|>
480   <|GREEN Binary:|>  $config{BINARY_DIR}
481   <|GREEN Config:|>  $config{CONFIG_DIR}
482   <|GREEN Data:|>    $config{DATA_DIR}
483   <|GREEN Example:|> $config{EXAMPLE_DIR}
484   <|GREEN Log:|>     $config{LOG_DIR}
485   <|GREEN Manual:|>  $config{MANUAL_DIR}
486   <|GREEN Module:|>  $config{MODULE_DIR}
487   <|GREEN Runtime:|> $config{RUNTIME_DIR}
488   <|GREEN Script:|>  $config{SCRIPT_DIR}
489
490 <|GREEN Execution Group:|> $config{GROUP} ($config{GID})
491 <|GREEN Execution User:|>  $config{USER} ($config{UID})
492 <|GREEN Socket Engine:|>   $config{SOCKETENGINE}
493
494 To build with these settings run '<|GREEN make ${\join ' ', @makeargs} install|>' now.
495
496 EOM
497
498 # Routine to list out the extra/ modules that have been enabled.
499 # Note: when getting any filenames out and comparing, it's important to lc it if the
500 # file system is not case-sensitive (== Epoc, MacOS, OS/2 (incl DOS/DJGPP), VMS, Win32
501 # (incl NetWare, Symbian)). Cygwin may or may not be case-sensitive, depending on
502 # configuration, however, File::Spec does not currently tell us (it assumes Unix behavior).
503 sub list_extras () {
504         use File::Spec;
505         # @_ not used
506         my $srcdir = File::Spec->catdir("src", "modules");
507         my $abs_srcdir = File::Spec->rel2abs($srcdir);
508         local $_;
509         my $dd;
510         opendir $dd, File::Spec->catdir($abs_srcdir, "extra") or die (File::Spec->catdir($abs_srcdir, "extra") . ": $!\n");
511         my @extras = map { File::Spec->case_tolerant() ? lc($_) : $_ } (readdir($dd));
512         closedir $dd;
513         undef $dd;
514         opendir $dd, $abs_srcdir or die "$abs_srcdir: $!\n";
515         my @sources = map { File::Spec->case_tolerant() ? lc($_) : $_ } (readdir($dd));
516         closedir $dd;
517         undef $dd;
518         my $maxlen = (sort { $b <=> $a } (map { length module_shrink $_ } (@extras)))[0];
519         my %extras = ();
520 EXTRA:  for my $extra (@extras) {
521                 next if (File::Spec->curdir() eq $extra || File::Spec->updir() eq $extra);
522                 my $abs_extra = File::Spec->catfile($abs_srcdir, "extra", $extra);
523                 my $abs_source = File::Spec->catfile($abs_srcdir, $extra);
524                 next unless ($extra =~ m/\.(cpp|h)$/ || (-d $abs_extra)); # C++ Source/Header, or directory
525                 if (-l $abs_source) {
526                         # Symlink, is it in the right place?
527                         my $targ = readlink($abs_source);
528                         my $abs_targ = File::Spec->rel2abs($targ, $abs_srcdir);
529                         if ($abs_targ eq $abs_extra) {
530                                 $extras{$extra} = "\e[32;1menabled\e[0m";
531                         } else {
532                                 $extras{$extra} = sprintf("\e[31;1mwrong symlink target (%s)\e[0m", $abs_targ);
533                         }
534                 } elsif (-e $abs_source) {
535                         my ($devext, $inoext) = stat($abs_extra);
536                         my ($devsrc, $inosrc, undef, $lnksrc) = stat($abs_source);
537                         if ($lnksrc > 1) {
538                                 if ($devsrc == $devext && $inosrc == $inoext) {
539                                         $extras{$extra} = "\e[32;1menabled\e[0m";
540                                 } else {
541                                         $extras{$extra} = sprintf("\e[31;1mwrong hardlink target (%d:%d)\e[0m", $devsrc, $inosrc);
542                                 }
543                         } else {
544                                 open my $extfd, "<", $abs_extra;
545                                 open my $srcfd, "<", $abs_source;
546                                 local $/ = undef;
547                                 if (scalar(<$extfd>) eq scalar(<$srcfd>)) {
548                                         $extras{$extra} = "\e[32;1menabled\e[0m";
549                                 } else {
550                                         $extras{$extra} = sprintf("\e[31;1mout of synch (re-copy)\e[0m");
551                                 }
552                         }
553                 } else {
554                         $extras{$extra} = "\e[33;1mdisabled\e[0m";
555                 }
556         }
557         # Now let's add dependency info
558         for my $extra (keys(%extras)) {
559                 next unless $extras{$extra} =~ m/enabled/; # only process enabled extras.
560                 my $abs_extra = File::Spec->catfile($abs_srcdir, "extra", $extra);
561                 my @deps = split /\s+/, get_directive($abs_extra, 'ModDep', '');
562                 for my $dep (@deps) {
563                         if (exists($extras{$dep})) {
564                                 my $ref = \$extras{$dep}; # Take reference.
565                                 if ($$ref !~ m/needed by/) {
566                                         # First dependency found.
567                                         if ($$ref =~ m/enabled/) {
568                                                 $$ref .= " (needed by \e[32;1m$extra\e[0m";
569                                         } else {
570                                                 $$ref =~ s/\e\[.*?m//g; # Strip out previous coloring. Will be set in bold+red+blink later.
571                                                 $$ref .= " (needed by \e[0;32;1;5m$extra\e[0;31;1;5m";
572                                         }
573                                 } else {
574                                         if ($$ref =~ m/enabled/) {
575                                                 $$ref .= ", \e[32;1m$extra\e[0m";
576                                         } else {
577                                                 $$ref .= ", \e[0;32;1;5m$extra\e[0;31;1;5m";
578                                         }
579                                 }
580                         }
581                 }
582         }
583         for my $extra (sort {$a cmp $b} keys(%extras)) {
584                 my $text = $extras{$extra};
585                 if ($text =~ m/needed by/ && $text !~ m/enabled/) {
586                         printf "\e[31;1;5m%-*s = %s%s\e[0m\n", $maxlen, module_shrink($extra), $text, ($text =~ m/needed by/ ? ")" : "");
587                 } else {
588                         printf "%-*s = %s%s\n", $maxlen, module_shrink($extra), $text, ($text =~ m/needed by/ ? "\e[0m)" : "");
589                 }
590         }
591         return keys(%extras) if wantarray; # Can be used by manage_extras.
592 }
593
594 sub enable_extras(@) {
595         my $moduledir = catdir $RealDir, 'src', 'modules';
596         my $extradir = catdir $moduledir, 'extra';
597
598         for my $extra (@_) {
599                 my $shortname = module_shrink $extra;
600                 my $extrafile = module_expand $extra;
601
602                 my $extrapath = catfile $extradir, $extrafile;
603                 if (!-f $extrapath) {
604                         print_error "<|GREEN $extra|> is not an extra module!";
605                 }
606
607                 my $modulepath = catfile $moduledir, $extrafile;
608                 if (-l $modulepath) {
609                         if (readlink($modulepath) ne $extrapath) {
610                                 unlink $modulepath; # Remove the dead symlink.
611                         } else {
612                                 next; # Module is already enabled.
613                         }
614                 }
615
616                 if (-e $modulepath) {
617                         print_error "unable to symlink <|GREEN ${\abs2rel $modulepath}|> to <|GREEN ${\abs2rel $extrapath}|>: the target exists and is not a symlink.";
618                 } else {
619                         say console_format "Enabling the <|GREEN $shortname|> module ...";
620                         symlink $extrapath, $modulepath or print_error "unable to symlink <|GREEN ${\abs2rel $modulepath}|> to <|GREEN ${\abs2rel $extrapath}|>: $!";
621                 }
622         }
623 }
624
625 sub disable_extras(@) {
626         my $moduledir = catdir $RealDir, 'src', 'modules';
627         my $extradir = catdir $moduledir, 'extra';
628
629         for my $extra (@_) {
630                 my $shortname = module_shrink $extra;
631                 my $extrafile = module_expand $extra;
632
633                 my $modulepath = catfile $moduledir, $extrafile;
634                 my $extrapath = catfile $extradir, $extrafile;
635                 if (!-e $modulepath && !-e $extrapath) {
636                         print_error "the <|GREEN $shortname|> module does not exist!";
637                 } elsif (!-e $modulepath && -e $extrapath) {
638                         print_error "the <|GREEN $shortname|> module is not currently enabled!";
639                 } elsif ((-e $modulepath && !-e $extrapath) || !-l $modulepath) {
640                         print_error "the <|GREEN $shortname|> module is not an extra module!";
641                 } else {
642                         say console_format "Disabling the <|GREEN $shortname|> module ...";
643                         unlink $modulepath or print_error "unable to unlink <|GREEN $extrapath|>: $!";
644                 }
645         }
646 }