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