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