]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - configure
45bb0e911f68f65d20bab8b39ebbe6f513a52880
[user/henk/code/inspircd.git] / configure
1 #!/usr/bin/env perl
2
3 #
4 # InspIRCd -- Internet Relay Chat Daemon
5 #
6 #   Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org>
7 #   Copyright (C) 2007, 2009 Dennis Friis <peavey@inspircd.org>
8 #   Copyright (C) 2003, 2006-2008 Craig Edwards <craigedwards@brainbox.cc>
9 #   Copyright (C) 2006-2008 Robin Burchell <robin+git@viroteck.net>
10 #   Copyright (C) 2008 Thomas Stagner <aquanight@inspircd.org>
11 #   Copyright (C) 2007 John Brooks <john.brooks@dereferenced.net>
12 #   Copyright (C) 2006 Oliver Lupton <oliverlupton@gmail.com>
13 #   Copyright (C) 2003-2006 Craig McLure <craig@chatspike.net>
14 #
15 # This file is part of InspIRCd.  InspIRCd is free software: you can
16 # redistribute it and/or modify it under the terms of the GNU General Public
17 # License as published by the Free Software Foundation, version 2.
18 #
19 # This program is distributed in the hope that it will be useful, but WITHOUT
20 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
21 # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
22 # details.
23 #
24 # You should have received a copy of the GNU General Public License
25 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
26 #
27
28
29 BEGIN {
30         require 5.8.0;
31 }
32
33 use strict;
34 use warnings FATAL => qw(all);
35
36 use File::Copy ();
37 use File::Spec::Functions qw(rel2abs);
38 use Cwd;
39 use Getopt::Long;
40
41 use make::configure;
42 use make::utilities;
43
44 our ($opt_use_gnutls, $opt_use_openssl, $opt_nointeractive, $opt_socketengine,
45      $opt_system, $opt_uid, $opt_base_dir, $opt_config_dir, $opt_module_dir, $opt_binary_dir,
46      $opt_data_dir, $opt_log_dir);
47
48 sub list_extras ();
49
50 sub enable_extras (@);
51
52 sub disable_extras (@);
53
54 my @opt_enableextras;
55 my @opt_disableextras;
56
57 GetOptions (
58         'enable-gnutls' => \$opt_use_gnutls,
59         'system' => \$opt_system,
60         'uid=s' => \$opt_uid,
61         'enable-openssl' => \$opt_use_openssl,
62         'disable-interactive' => \$opt_nointeractive,
63         'socketengine=s' => \$opt_socketengine,
64         'prefix=s' => \$opt_base_dir,
65         'config-dir=s' => \$opt_config_dir,
66         'module-dir=s' => \$opt_module_dir,
67         'binary-dir=s' => \$opt_binary_dir,
68         'data-dir=s' => \$opt_data_dir,
69         'log-dir=s' => \$opt_log_dir,
70         'help'  => \&cmd_help,
71         'update' => \&cmd_update,
72         'clean' => \&cmd_clean,
73         'list-extras' => sub { list_extras; exit 0; }, # This, --enable-extras, and --disable-extras are for non-interactive managing.
74         'enable-extras=s@' => \@opt_enableextras, # ^
75         'disable-extras=s@' => \@opt_disableextras, # ^
76 );
77
78 if (scalar(@opt_enableextras) + scalar(@opt_disableextras) > 0) {
79         @opt_enableextras = split /,/, join(',', @opt_enableextras);
80         @opt_disableextras = split /,/, join(',', @opt_disableextras);
81         enable_extras(@opt_enableextras);
82         disable_extras(@opt_disableextras);
83         list_extras;
84         print "Remember: YOU are responsible for making sure any libraries needed have been installed!\n";
85         exit 0;
86 }
87
88 our $interactive = !(
89         (defined $opt_base_dir) ||
90         (defined $opt_config_dir) ||
91         (defined $opt_module_dir) ||
92         (defined $opt_base_dir) ||
93         (defined $opt_binary_dir) ||
94         (defined $opt_data_dir) ||
95         (defined $opt_log_dir) ||
96         (defined $opt_nointeractive) ||
97         (defined $opt_socketengine) ||
98         (defined $opt_use_openssl) ||
99         (defined $opt_system) ||
100         (defined $opt_uid) ||
101         (defined $opt_use_gnutls)
102 );
103
104 our %config = read_configure_cache();
105
106 print "Checking for cache from previous configure... ";
107 print %config ? "found\n" : "not found\n";
108
109 $config{BASE_DIR} = getcwd()."/run";
110
111 if (defined $opt_base_dir) {
112         $config{BASE_DIR} = $opt_base_dir;
113 } elsif (defined $opt_system) {
114         $config{BASE_DIR} = '/var/lib/inspircd';
115 }
116
117 if (defined $opt_system) {
118         $config{UID} = defined $opt_uid ? $opt_uid : 'ircd';
119         $config{CONFIG_DIR} = '/etc/inspircd';
120         $config{MODULE_DIR} = '/usr/lib/inspircd';
121         $config{BINARY_DIR} = '/usr/sbin/';
122         $config{DATA_DIR} = '/var/inspircd';
123         $config{LOG_DIR} = '/var/log/inspircd';
124 } else {
125         $config{UID} = defined $opt_uid ? $opt_uid : $<;
126         $config{CONFIG_DIR} = rel2abs($config{BASE_DIR}."/conf");
127         $config{MODULE_DIR} = rel2abs($config{BASE_DIR}."/modules");
128         $config{BINARY_DIR} = rel2abs($config{BASE_DIR}."/bin");
129         $config{DATA_DIR} = rel2abs($config{BASE_DIR}."/data");
130         $config{LOG_DIR} = rel2abs($config{BASE_DIR}."/logs");
131 }
132
133 if (defined $opt_config_dir) {
134         $config{CONFIG_DIR} = $opt_config_dir;
135 }
136 if (defined $opt_module_dir) {
137         $config{MODULE_DIR} = $opt_module_dir;
138 }
139 if (defined $opt_binary_dir) {
140         $config{BINARY_DIR} = $opt_binary_dir;
141 }
142 if (defined $opt_data_dir) {
143         $config{DATA_DIR} = $opt_data_dir;
144 }
145 if (defined $opt_log_dir) {
146         $config{LOG_DIR} = $opt_log_dir;
147 }
148 chomp($config{HAS_GNUTLS}   = `pkg-config --modversion gnutls 2>/dev/null`);
149 chomp($config{HAS_OPENSSL}  = `pkg-config --modversion openssl 2>/dev/null`);
150
151 chomp(our $gnutls_ver = $config{HAS_GNUTLS});
152 chomp(our $openssl_ver = $config{HAS_OPENSSL});
153 $config{USE_GNUTLS}         = 0;
154 if (defined $opt_use_gnutls)
155 {
156         $config{USE_GNUTLS} = "y";                                      # Use gnutls.
157 }
158 $config{USE_OPENSSL}    = 0;                                            # Use openssl.
159 if (defined $opt_use_openssl)
160 {
161         $config{USE_OPENSSL} = "y";
162 }
163
164 $config{CXX} = defined $ENV{CXX} && !system("$ENV{CXX} -v > /dev/null 2>&1") ? $ENV{CXX} : find_compiler();
165 if ($config{CXX} eq "") {
166         print "A C++ compiler could not be detected on your system!\n";
167         print "Set the CXX environment variable to the full path if this is incorrect.\n";
168         exit 1; 
169 }
170
171 our %cxx = get_compiler_info($config{CXX});
172 if ($cxx{UNSUPPORTED}) {
173         print "Your C++ compiler is too old to build InspIRCd!\n";
174         print "Reason: $cxx{REASON}\n";
175         exit 1;
176 }
177
178 if ($config{HAS_OPENSSL} =~ /^([-[:digit:].]+)(?:[a-z])?(?:\-[a-z][0-9])?/) {
179         $config{HAS_OPENSSL} = $1;
180 } else {
181         $config{HAS_OPENSSL} = "";
182 }
183
184 $config{HAS_CLOCK_GETTIME} = run_test 'clock_gettime()', test_file($config{CXX}, 'clock_gettime.cpp', '-lrt');
185 $config{HAS_EVENTFD} = run_test 'eventfd()', test_file($config{CXX}, 'eventfd.cpp');
186
187 if ($config{HAS_EPOLL} = run_test 'epoll', test_header($config{CXX}, 'sys/epoll.h')) {
188         $config{SOCKETENGINE} ||= 'epoll';
189 }
190
191 if ($config{HAS_KQUEUE} = run_test 'kqueue', test_file($config{CXX}, 'kqueue.cpp')) {
192         $config{SOCKETENGINE} ||= 'kqueue';
193 }
194
195 if ($config{HAS_PORTS} = run_test 'Solaris IOCP', test_header($config{CXX}, 'port.h')) {
196         $config{SOCKETENGINE} ||= 'ports';
197 }
198
199 if ($config{HAS_POLL} = run_test 'poll', test_header($config{CXX}, 'poll.h')) {
200         $config{SOCKETENGINE} ||= 'poll';
201 }
202
203 # Select is available on all platforms
204 $config{HAS_SELECT} = 1;
205 $config{SOCKETENGINE} ||= "select";
206
207 if (defined $opt_socketengine) {
208         my $cfgkey = "HAS_" . uc $opt_socketengine;
209         if ($config{$cfgkey} && -f "src/socketengines/socketengine_$opt_socketengine.cpp") {
210                 $config{SOCKETENGINE} = $opt_socketengine;
211         } else {
212                 print "Unable to use a socket engine which is not supported on this platform ($opt_socketengine)!\n";
213                 print "Available socket engines are:";
214                 foreach (<src/socketengines/socketengine_*.cpp>) {
215                         s/src\/socketengines\/socketengine_(\w+)\.cpp/$1/;
216                         print " $1" if $config{"HAS_" . uc $1};
217                 }
218                 print "\n";     
219                 exit 1;
220         }
221 }
222
223 print "Checking for libgnutls... ";
224 if (defined($config{HAS_GNUTLS}) && (($config{HAS_GNUTLS}) || ($config{HAS_GNUTLS} eq "y"))) {
225         if (defined($gnutls_ver) && ($gnutls_ver ne "")) {
226                 print "yes\n";
227                 $config{HAS_GNUTLS} = "y";
228         } else {
229                 print "no\n";
230                 $config{HAS_GNUTLS} = "n";
231         }
232 } else {
233         print "no\n";
234         $config{HAS_GNUTLS} = "n";
235 }
236
237 print "Checking for openssl... ";
238 if (defined($config{HAS_OPENSSL}) && (($config{HAS_OPENSSL}) || ($config{HAS_OPENSSL} eq "y"))) {
239         if (defined($openssl_ver) && ($openssl_ver ne "")) {
240                 print "yes\n";
241                 $config{HAS_OPENSSL} = "y";
242         } else {
243                 print "no\n";
244                 $config{HAS_OPENSSL} = "n";
245         }
246 } else {
247         print "no\n";
248         $config{HAS_OPENSSL} = "n";
249 }
250
251 if ($interactive)
252 {
253         # Clear the screen.
254         system 'tput', 'clear';
255
256         my %version = get_version();
257
258         # Display Introduction Message..
259         print <<"STOP" ;
260 Welcome to the \e[1mInspIRCd\e[0m configuration program! (\e[1minteractive mode\e[0m)
261 \e[1mPackage maintainers: Type ./configure --help for non-interactive help\e[0m
262
263 *** If you are unsure of any of these values, leave it blank for    ***
264 *** standard settings that will work, and your server will run      ***
265 *** using them. Please consult your IRC network admin if in doubt.  ***
266
267 Press \e[1m<RETURN>\e[0m to accept the default for any option, or enter
268 a new value. Please note: You will \e[1mHAVE\e[0m to read the docs
269 dir, otherwise you won't have a config file!
270
271 Your operating system is: \e[1;32m$^O\e[0m 
272 STOP
273         print "Your InspIRCd version is: \e[1;32m";
274         print "$version{MAJOR}.$version{MINOR}.$version{PATCH}+$version{LABEL}";
275         print "\e[0m\n\n";
276         print "The following compiler has been detected: \e[1;32m$cxx{NAME} $cxx{VERSION}\e[0m ($config{CXX})\n\n";
277
278         # Check that the user actually wants this version.
279         if ($version{LABEL} ne 'release') {
280                 print <<"EOW" ;
281 \e[1;31mWARNING!\e[0m You are building a development version. This contains code which has
282 not been tested as heavily and may contain various faults which could seriously
283 affect the running of your server. It is recommended that you use a stable
284 version instead.
285
286 You can obtain the latest stable version from https://github.com/inspircd/inspircd/releases
287 or by running `git checkout insp20` if you are installing from Git.
288
289 EOW
290         exit 1 unless prompt_bool(1, 'I understand this warning and want to continue anyway.', 0);
291         }
292
293         # Directory Settings..
294         my $tmpbase = $config{BASE_DIR};
295         $config{BASE_DIR} = prompt_dir(1, 'What directory do you wish to install the InspIRCd base?', $config{BASE_DIR});
296         if ($tmpbase ne $config{BASE_DIR}) {
297                 $config{CONFIG_DIR} = rel2abs($config{BASE_DIR}."/conf");
298                 $config{MODULE_DIR} = rel2abs($config{BASE_DIR}."/modules");
299                 $config{DATA_DIR} = rel2abs($config{BASE_DIR}."/data");
300                 $config{LOG_DIR} = rel2abs($config{BASE_DIR}."/logs");
301                 $config{BINARY_DIR} = rel2abs($config{BASE_DIR}."/bin");
302         }
303
304         $config{BINARY_DIR} = prompt_dir(1, 'In what directory should the InspIRCd binary be placed?', $config{BINARY_DIR});
305         $config{CONFIG_DIR} = prompt_dir(1, 'In what directory are the configuration files to be stored?', $config{CONFIG_DIR});
306         $config{DATA_DIR} = prompt_dir(1, 'In what directory are variable data files to be stored?', $config{DATA_DIR});
307         $config{LOG_DIR} = prompt_dir(1, 'In what directory are log files to be stored?', $config{LOG_DIR});
308         $config{MODULE_DIR} = prompt_dir(1, 'In what directory are the modules to be placed?', $config{MODULE_DIR});
309
310         my $chose_hiperf = 0;
311         if ($config{HAS_KQUEUE}) {
312                 $config{USE_KQUEUE} = prompt_bool(1, 'Your operating system has support for the high performance kqueue socket engine. Would you like to enable it?', 1);
313                 if ($config{USE_KQUEUE}) {
314                         $config{SOCKETENGINE} = "kqueue";
315                         $chose_hiperf = 1;
316                 }
317         }
318         if ($config{HAS_EPOLL}) {
319                 $config{USE_EPOLL} = prompt_bool(1, 'Your operating system has support for the high performance epoll socket engine. Would you like to enable it?', 1);
320                 if ($config{USE_EPOLL}) {
321                         $config{SOCKETENGINE} = "epoll";
322                         $chose_hiperf = 1;
323                 }
324         }
325         if ($config{HAS_PORTS}) {
326                 $config{USE_PORTS} = prompt_bool(1, 'Your operating system has support for the high performance IOCP socket engine. Would you like to enable it?', 1);
327                 if ($config{USE_PORTS}) {
328                         $config{SOCKETENGINE} = "ports";
329                         $chose_hiperf = 1;
330                 }
331         }
332
333         if (!$chose_hiperf && $config{HAS_POLL}) {
334                 $config{USE_POLL} = prompt_bool(1, 'Your operating system has support for the mid performance poll socket engine. Would you like to enable it?', 1);
335                 if ($config{USE_POLL}) {
336                         $config{SOCKETENGINE} = "poll";
337                 }
338         }
339         unless ($chose_hiperf || $config{USE_POLL})
340         {
341                 print "No high-performance socket engines are available, or you chose not to enable one. Defaulting to select() engine.\n\n";
342                 $config{SOCKETENGINE} = "select";
343         }
344
345         if ($config{HAS_GNUTLS} eq "y" || $config{HAS_OPENSSL} eq "y")
346         {
347                 print "Detected GnuTLS version: \e[1;32m" . $gnutls_ver . "\e[0m\n";
348                 print "Detected OpenSSL version: \e[1;32m" . $openssl_ver . "\e[0m\n\n";
349
350                 $config{USE_SSL} = prompt_bool(1, 'One or more SSL libraries detected. Would you like to enable SSL support?', 1);
351                 if ($config{USE_SSL})
352                 {
353                         if ($config{HAS_GNUTLS} eq "y")
354                         {
355                                 $config{USE_GNUTLS} = prompt_bool(1, 'Would you like to enable SSL with m_ssl_gnutls (recommended)?', 1);
356                                 if ($config{USE_GNUTLS})
357                                 {
358                                         print "Using GnuTLS SSL module.\n\n";
359                                         unlink 'src/modules/m_ssl_gnutls.cpp' if -f 'src/modules/m_ssl_gnutls.cpp';
360                                         symlink "extra/m_ssl_gnutls.cpp", "src/modules/m_ssl_gnutls.cpp" or print STDERR "Symlink failed: $!\n";
361                                 }
362                         }
363
364                         if ($config{HAS_OPENSSL} eq "y")
365                         {
366                                 $config{USE_OPENSSL} = prompt_bool(1, 'Would you like to enable SSL with m_ssl_openssl (recommended)?', 1);
367                                 if ($config{USE_OPENSSL})
368                                 {
369                                         print "Using OpenSSL SSL module.\n\n";
370                                         unlink 'src/modules/m_ssl_openssl.cpp' if -f 'src/modules/m_ssl_openssl.cpp';
371                                         symlink "extra/m_ssl_openssl.cpp", "src/modules/m_ssl_openssl.cpp" or print STDERR "Symlink failed: $!\n";
372                                 }
373                         }
374                 }
375         }
376         else
377         {
378                 print "\nCould not detect OpenSSL or GnuTLS. Make sure pkg-config is installed and\n";
379                 print "is in your path.\n\n";
380         }
381 }
382
383 # We are on a POSIX system, we can enable POSIX extras without asking
384 symlink "extra/m_regex_posix.cpp", "src/modules/m_regex_posix.cpp";
385
386 if (($config{USE_GNUTLS}) && ($config{HAS_GNUTLS} ne "y"))
387 {
388         print "Sorry, but I couldn't detect GnuTLS. Make sure pkg-config is in your path.\n";
389         exit 1;
390 }
391 if (($config{USE_OPENSSL}) && ($config{HAS_OPENSSL} ne "y"))
392 {
393         print "Sorry, but I couldn't detect OpenSSL. Make sure pkg-config is in your path.\n";
394         exit 1;
395 }
396
397 if ($config{USE_GNUTLS} || $config{USE_OPENSSL}) {
398         if (my $val = prompt_bool($interactive, 'Would you like to generate SSL certificates now?', $interactive)) {
399                 unless (-r "$config{CONFIG_DIR}/key.pem" && -r "$config{CONFIG_DIR}/cert.pem" && -r "$config{CONFIG_DIR}/dhparams.pem") {
400                         unless (system './tools/genssl auto') {
401                                 print "\nCertificate generation complete, copying to config directory... ";
402                                 File::Copy::move("key.pem", "$config{CONFIG_DIR}/key.pem") or print STDERR "Could not copy key.pem!\n";
403                                 File::Copy::move("cert.pem", "$config{CONFIG_DIR}/cert.pem") or print STDERR "Could not copy cert.pem!\n";
404                                 File::Copy::move("dhparams.pem", "$config{CONFIG_DIR}/dhparams.pem") or print STDERR "Could not copy dhparams.pem!\n";
405                                 print "Done.\n\n";
406                         }
407                 } else {
408                         print "SSL certificates found, skipping.\n\n"
409                 }
410         } else {
411                 print "Skipping SSL certificate generation in non-interactive mode.\n\n";
412         }
413 } else {
414         print "Skipping SSL Certificate generation, SSL support is not available.\n\n";
415 }
416
417 print "Writing \e[1;32m.config.cache\e[0m ...\n";
418 write_configure_cache(%config);
419 parse_templates(\%config, \%cxx);
420 dump_hash();
421
422 print "\n";
423 print "To build your server with these settings, please run '\e[1;32mmake\e[0m' now.\n";
424 if ($config{USE_GNUTLS} || $config{USE_OPENSSL}) {
425         print "Please note: for \e[1;32mSSL support\e[0m you will need to load required\n";
426         print "modules in your config. This configure script has added those modules to the\n";
427         print "build process. For more info, please refer to:\n";
428         print "\e[1;32mhttp://wiki.inspircd.org/Installation_From_Tarball\e[0m\n";
429 }
430 print "*** \e[1;32mRemember to edit your configuration files!!!\e[0m ***\n\n";
431
432 # Routine to list out the extra/ modules that have been enabled.
433 # Note: when getting any filenames out and comparing, it's important to lc it if the
434 # file system is not case-sensitive (== Epoc, MacOS, OS/2 (incl DOS/DJGPP), VMS, Win32
435 # (incl NetWare, Symbian)). Cygwin may or may not be case-sensitive, depending on
436 # configuration, however, File::Spec does not currently tell us (it assumes Unix behavior).
437 sub list_extras () {
438         use File::Spec;
439         # @_ not used
440         my $srcdir = File::Spec->catdir("src", "modules");
441         my $abs_srcdir = File::Spec->rel2abs($srcdir);
442         local $_;
443         my $dd;
444         opendir $dd, File::Spec->catdir($abs_srcdir, "extra") or die (File::Spec->catdir($abs_srcdir, "extra") . ": $!\n");
445         my @extras = map { File::Spec->case_tolerant() ? lc($_) : $_ } (readdir($dd));
446         closedir $dd;
447         undef $dd;
448         opendir $dd, $abs_srcdir or die "$abs_srcdir: $!\n";
449         my @sources = map { File::Spec->case_tolerant() ? lc($_) : $_ } (readdir($dd));
450         closedir $dd;
451         undef $dd;
452         my $maxlen = (sort { $b <=> $a } (map {length($_)} (@extras)))[0];
453         my %extras = ();
454 EXTRA:  for my $extra (@extras) {
455                 next if (File::Spec->curdir() eq $extra || File::Spec->updir() eq $extra);
456                 my $abs_extra = File::Spec->catfile($abs_srcdir, "extra", $extra);
457                 my $abs_source = File::Spec->catfile($abs_srcdir, $extra);
458                 next unless ($extra =~ m/\.(cpp|h)$/ || (-d $abs_extra)); # C++ Source/Header, or directory
459                 if (-l $abs_source) {
460                         # Symlink, is it in the right place?
461                         my $targ = readlink($abs_source);
462                         my $abs_targ = File::Spec->rel2abs($targ, $abs_srcdir);
463                         if ($abs_targ eq $abs_extra) {
464                                 $extras{$extra} = "\e[32;1menabled\e[0m";
465                         } else {
466                                 $extras{$extra} = sprintf("\e[31;1mwrong symlink target (%s)\e[0m", $abs_targ);
467                         }
468                 } elsif (-e $abs_source) {
469                         my ($devext, $inoext) = stat($abs_extra);
470                         my ($devsrc, $inosrc, undef, $lnksrc) = stat($abs_source);
471                         if ($lnksrc > 1) {
472                                 if ($devsrc == $devext && $inosrc == $inoext) {
473                                         $extras{$extra} = "\e[32;1menabled\e[0m";
474                                 } else {
475                                         $extras{$extra} = sprintf("\e[31;1mwrong hardlink target (%d:%d)\e[0m", $devsrc, $inosrc);
476                                 }
477                         } else {
478                                 open my $extfd, "<", $abs_extra;
479                                 open my $srcfd, "<", $abs_source;
480                                 local $/ = undef;
481                                 if (scalar(<$extfd>) eq scalar(<$srcfd>)) {
482                                         $extras{$extra} = "\e[32;1menabled\e[0m";
483                                 } else {
484                                         $extras{$extra} = sprintf("\e[31;1mout of synch (re-copy)\e[0m");
485                                 }
486                         }
487                 } else {
488                         $extras{$extra} = "\e[33;1mdisabled\e[0m";
489                 }
490         }
491         # Now let's add dependency info
492         for my $extra (keys(%extras)) {
493                 next unless $extras{$extra} =~ m/enabled/; # only process enabled extras.
494                 my $abs_extra = File::Spec->catfile($abs_srcdir, "extra", $extra);
495                 my @deps = split /\s+/, get_property($abs_extra, 'ModDep');
496                 for my $dep (@deps) {
497                         if (exists($extras{$dep})) {
498                                 my $ref = \$extras{$dep}; # Take reference.
499                                 if ($$ref !~ m/needed by/) {
500                                         # First dependency found.
501                                         if ($$ref =~ m/enabled/) {
502                                                 $$ref .= " (needed by \e[32;1m$extra\e[0m";
503                                         } else {
504                                                 $$ref =~ s/\e\[.*?m//g; # Strip out previous coloring. Will be set in bold+red+blink later.
505                                                 $$ref .= " (needed by \e[0;32;1;5m$extra\e[0;31;1;5m";
506                                         }
507                                 } else {
508                                         if ($$ref =~ m/enabled/) {
509                                                 $$ref .= ", \e[32;1m$extra\e[0m";
510                                         } else {
511                                                 $$ref .= ", \e[0;32;1;5m$extra\e[0;31;1;5m";
512                                         }
513                                 }
514                         }
515                 }
516         }
517         for my $extra (sort {$a cmp $b} keys(%extras)) {
518                 my $text = $extras{$extra};
519                 if ($text =~ m/needed by/ && $text !~ m/enabled/) {
520                         printf "\e[31;1;5m%-*s = %s%s\e[0m\n", $maxlen, $extra, $text, ($text =~ m/needed by/ ? ")" : "");
521                 } else {
522                         printf "%-*s = %s%s\n", $maxlen, $extra, $text, ($text =~ m/needed by/ ? "\e[0m)" : "");
523                 }
524         }
525         return keys(%extras) if wantarray; # Can be used by manage_extras.
526 }
527
528 sub enable_extras (@) {
529         my (@extras) = @_;
530         for my $extra (@extras) {
531                 my $extrapath = "src/modules/extra/$extra";
532                 if (!-e $extrapath) {
533                         print STDERR "Cannot enable \e[32;1m$extra\e[0m : No such file or directory in src/modules/extra\n";
534                         next;
535                 }
536                 my $source = "src/modules/$extra";
537                 if (-e $source) {
538                         print STDERR "Cannot enable \e[32;1m$extra\e[0m : destination in src/modules exists (might already be enabled?)\n";
539                         next;
540                 }
541                 # Get dependencies, and add them to be processed.
542                 my @deps = split /\s+/, get_property($extrapath, 'ModDep');
543                 for my $dep (@deps) {
544                         next if scalar(grep { $_ eq $dep } (@extras)) > 0; # Skip if we're going to be enabling it anyway.
545                         if (!-e "src/modules/$dep" && !-e "include/$dep") {
546                                 if (-e "src/modules/extra/$dep") {
547                                         print STDERR "Will also enable extra \e[32;1m$dep\e[0m (needed by \e[32;1m$extra\e[0m)\n";
548                                         push @extras, $dep;
549                                 } else {
550                                         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";
551                                 }
552                         }
553                 }
554                 print "Enabling $extra ... \n";
555                 symlink "extra/$extra", $source or print STDERR "$source: Cannot link to 'extra/$extra': $!\n";
556         }
557 }
558
559 sub disable_extras (@)
560 {
561         opendir my $dd, "src/modules/extra/";
562         my @files = readdir($dd);
563         closedir $dd;
564         my (@extras) = @_;
565 EXTRA:  for my $extra (@extras) {
566                 my $extrapath = "src/modules/extra/$extra";
567                 my $source = "src/modules/$extra";
568                 if (!-e $extrapath) {
569                         print STDERR "Cannot disable \e[32;1m$extra\e[0m : Is not an extra\n";
570                         next;
571                 }
572                 if ((! -l $source) || readlink($source) ne "extra/$extra") {
573                         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";
574                         next;
575                 }
576                 # Check if anything needs this.
577                 for my $file (@files) {
578                         my @deps = split /\s+/, get_property("src/modules/extra/$file", 'ModDep');
579                         # File depends on this extra...
580                         if (scalar(grep { $_ eq $extra } @deps) > 0) {
581                                 # And is both enabled and not about to be disabled.
582                                 if (-e "src/modules/$file" && scalar(grep { $_ eq $file } @extras) < 1) {
583                                         print STDERR "Cannot disable \e[32;1m$extra\e[0m : is needed by \e[32;1m$file\e[0m\n";
584                                         next EXTRA;
585                                 }
586                         }
587                 }
588                 # Now remove.
589                 print "Disabling $extra ... \n";
590                 unlink "src/modules/$extra" or print STDERR "Cannot disable \e[32;1m$extra\e[0m : $!\n";
591         }
592 }