1# Copyright (C) 2001-2015, Parrot Foundation.
2
3=head1 NAME
4
5Parrot::Configure::Compiler - C-Related methods for configuration and more
6
7=head1 DESCRIPTION
8
9The Parrot::Configure::Compiler module provides methods inherited by
10Parrot::Configure which prepare and/or run C programs during configure probes.
11Other methods from this module will be used to generate makefiles and other
12files.  Template entries of the form C<@key@> will be replaced with C<key>'s
13value from the currently created configuration system's data.
14
15Beware that Parrot::Config is not available at configure time.
16
17=head2 Methods
18
19=over 4
20
21=cut
22
23package Parrot::Configure::Compiler;
24
25use strict;
26use warnings;
27
28use Carp;
29use File::Spec ();
30use lib ('lib');
31use Parrot::Configure::Utils qw(
32    prompt copy_if_diff move_if_diff integrate
33    capture_output check_progs _slurp
34    _run_command _build_compile_command
35    move_if_diff
36);
37use Parrot::BuildUtil qw( add_to_generated );
38
39# report the makefile and lineno
40sub makecroak {
41    my ($conf, $error) = @_;
42    my ($file, $line) = ($conf->{_compiler_file}, $conf->{_compiler_line});
43    die "$error at $file line $line\n";
44}
45
46our %file_types_info = (
47    makefile => {
48        comment_type    => '#',
49    },
50    c => {
51        comment_type    => '/*',
52    },
53    pmc => {
54        comment_type    => '/*',
55    },
56    perl => {
57        comment_type    => '#',
58    },
59    pir => {
60        comment_type    => '#',
61    },
62);
63
64=item C<cc_gen()>
65
66    $conf->cc_gen($source)
67
68Generates F<test_$$.c> from the specified source file.
69
70=cut
71
72sub cc_gen {
73    my $conf   = shift;
74    my $source = shift;
75
76    $conf->genfile( $source, "test_$$.c", file_type => 'c' );
77}
78
79=item C<cc_build()>
80
81    $conf->cc_build($cc_args, $link_args)
82
83These items are used from current config settings:
84
85  $cc, $ccflags, $ldout, $o, $link, $linkflags, $cc_exe_out, $exe, $libs
86
87Calls the compiler and linker on F<test_$$.c>.
88
89Returns the last error code.
90
91=cut
92
93sub cc_build {
94    my $conf = shift;
95    my ( $cc_args, $link_args ) = @_;
96
97    $cc_args   = '' unless defined $cc_args;
98    $link_args = '' unless defined $link_args;
99
100    my $verbose = $conf->options->get('verbose');
101
102    my ( $cc, $ccflags, $ldout, $o, $link, $linkflags, $cc_exe_out, $exe, $libs ) =
103        $conf->data->get(qw(cc ccflags ld_out o link linkflags cc_exe_out exe libs));
104
105    # unique test file name for parallel builds
106    my $test            = 'test_' . $$;
107    my $compile_command = _build_compile_command( $cc, $ccflags, $cc_args );
108    my $compile_result  = _run_command( $compile_command, "$test.cco", "$test.cco", $verbose );
109
110    if ($compile_result) {
111        confess "C compiler failed (see $test.cco)";
112        return $compile_result;
113    }
114
115    my $link_result =
116        _run_command( "$link $linkflags $test$o $link_args ${cc_exe_out}${test}${exe}  $libs",
117        "$test.ldo", "$test.ldo", $verbose )
118        and confess "Linker failed (see $test.ldo)";
119    if ($link_result) {
120        return $link_result;
121    }
122    return;
123}
124
125=item C<cc_run()>
126
127    $conf->cc_run();
128
129Calls the F<test> (or F<test.exe>) executable. Any output is directed to
130F<test.out>.
131
132Returns the captured stdout, and in array context with the additional exit code.
133
134=cut
135
136sub cc_run {
137    my $conf = shift;
138    my $exe      = $conf->data->get('exe');
139    my $slash    = $conf->data->get('slash');
140    my $verbose  = $conf->options->get('verbose');
141    my $test     = 'test_' . $$;
142    my $test_exe = ".${slash}${test}${exe}";
143
144    my $run_error;
145    if ( defined( $_[0] ) && length( $_[0] ) ) {
146        local $" = ' ';
147        $run_error = _run_command( "$test_exe @_", "./$test.out", undef, $verbose );
148    }
149    else {
150        $run_error = _run_command( $test_exe, "./$test.out", undef, $verbose );
151    }
152
153    my $output = _slurp("./$test.out");
154    return wantarray ? ($output, $run_error) : $output;
155}
156
157=item C<cc_run_capture()>
158
159    $conf->cc_run_capture();
160
161Same as C<cc_run()> except that warnings and errors are also directed to
162F<test.out>.
163
164Returns the captured stdout combined with stderr, and in array context
165with the additional exit code.
166
167=cut
168
169sub cc_run_capture {
170    my $conf    = shift;
171    my $exe     = $conf->data->get('exe');
172    my $slash   = $conf->data->get('slash');
173    my $verbose = $conf->options->get('verbose');
174    my $test    = 'test_' . $$;
175    my $run_error;
176
177    if ( defined( $_[0] ) && length( $_[0] ) ) {
178        local $" = ' ';
179        $run_error = _run_command( ".${slash}$test${exe} @_", "./$test.out", "./$test.out", $verbose );
180    }
181    else {
182        $run_error = _run_command( ".${slash}$test${exe}", "./$test.out", "./$test.out", $verbose );
183    }
184
185    my $output = _slurp("./$test.out");
186    return wantarray ? ($output, $run_error) : $output;
187}
188
189=item C<cc_clean()>
190
191    $conf->cc_clean();
192
193Cleans up all files in the root folder that match the glob F<test.*>.
194
195=cut
196
197sub cc_clean {    ## no critic Subroutines::RequireFinalReturn
198    my $conf = shift;
199    unlink map "test_${$}$_", qw( .c .cco .ldo .out ),
200        $conf->data->get(qw( o exe )),
201        # MSVC
202        ($^O eq 'MSWin32' ? qw( .exe.manifest .ilk .pdb ) : ());
203}
204
205=item C<shebang_mod()>
206
207    $conf->shebang_mod($source, $target);
208
209Takes the specified source file, replacing entries like C<@key@> with
210C<key>'s value from the configuration system's data, and writes the results
211to specified target file. The replacement is only done in the first line of
212the file normally to set the shebang value accordingly.
213
214=cut
215
216sub shebang_mod {
217    my $conf = shift;
218    my ( $source, $target ) = @_;
219
220    open my $in,  '<', $source       or die "Can't open $source: $!";
221    open my $out, '>', "${target}_tmp" or die "Can't open ${target}_tmp: $!";
222
223    my $line = <$in>;
224
225    # interpolate @foo@ values
226    $line =~ s{ \@ (\w+) \@ }{
227        if(defined(my $val=$conf->data->get($1))) {
228            $val;
229        }
230        else {
231            warn "value for '\@$1\@' in $source is undef";
232            '';
233        }
234    }egx;
235
236    print $out $line;
237
238    while ( my $line = <$in> ) {
239        print $out $line;
240    }
241
242    close($in)  or die "Can't close $source: $!";
243    close($out) or die "Can't close $target: $!";
244
245    move_if_diff( "${target}_tmp", $target );
246}
247
248=item C<genfile()>
249
250    $conf->genfile($source, $target, %options);
251
252Takes the specified source file, replacing entries like C<@key@> with
253C<key>'s value from the configuration system's data, and writes the results
254to specified target file.
255
256If a C<::> is present in the C<@key@>, the replaced value will first try to
257use the full key, but if that is not present, the key up to the C<::> is used.
258For example, if C<@cc_warn::src/embed.c@> is used, and that key doesn't
259exist, the fallback key would be C<@cc_warn@>.
260
261Respects the following options when manipulating files (Note: most of the
262replacement syntax assumes the source text is on a single line.)
263
264=over 4
265
266=item file_type
267
268If set to a C<makefile>, C<c> or C<perl> value, C<comment_type> will be set to
269corresponding value.  Moreover, when set to a C<makefile> value, it will
270enable C<conditioned_lines>.
271
272Its value will be detected automatically by target file name unless you set
273it to a special value C<none>.
274
275=item conditioned_lines #IF #UNLESS #ELSIF #ELSE
276
277If conditioned_lines is true, then lines beginning in C<#IF>, C<#UNLESS>,
278C<#ELSIF>, and C<#ELSE> are evaluated conditionally, and the content after the
279C<:> is included or excluded, depending on the evaluation of the expression.
280
281Lines beginning with C<#IF(expr):> are skipped if the expr condition is false,
282otherwise the content after the C<:> is inserted. Lines beginning with
283C<#UNLESS(expr):> are skipped if the expr condition is true, otherwise the
284content after the C<:> is inserted. Lines beginning with C<#ELSIF(expr):> or
285C<#ELSE:> are evaluated if the preceding C<#IF(expr):> evaluated to false.
286
287A condition C<expr> may be:
288
289=over 4
290
291=item *
292
293A single key, which is true if a config key is true,
294
295=item *
296
297Equal to the platform name or the osname - case-sensitive,
298
299=item *
300
301A C<key==value> expression, which is true if the config key has the expected
302value, or
303
304=item *
305
306A logical combination of C<|>, C<OR>, C<&>, C<AND>, C<!>, C<NOT>.
307
308=back
309
310A key must only consist of the characters C<A-Z a-z 0-9 _ ->, and is checked
311case-sensitively against the configuration key or the platform name. Truth is
312defined as any value that is not C<0>, an empty string, or C<undef>.
313
314The value in C<key==value> expressions may not contain spaces. Quotes in
315values are not supported.
316
317The word ops C<AND>, C<OR> and C<NOT> are case-insensitive. C<!> and C<NOT>
318bind closer than C<&>, C<AND>, C<|>, and C<OR>. The order of precedence for
319C<AND> and C<OR> is undefined.
320
321
322For instance:
323
324  #IF(win32): src/atomic/gcc_x86$(O)
325
326will be included if the platform is win32.
327
328  #IF(cpuarch==i386): src/atomic/gcc_x86$(O)
329
330will be included if the value of the config key "cpuarch" is "i386".
331
332  #IF(cpuarch==i386): src/atomic/gcc_x86$(O)
333  #ELSIF(cpuarch==sparcv9): src/atomic/sparc_v9.s
334  #ELSE:
335
336will include " src/atomic/gcc_x86$(O)" if the config key "cpuarch" is
337ste to "i386", will include " src/atomic/sparc_v9.s" instead if
338"cpuarch" is set to "sparcv9", and will include an empty line otherwise.
339
340  #IF(win32 and glut and not cygwin):
341
342will be used on "win32" and if "glut" is defined, but not on "cygwin".
343
344=item comment_type
345
346This option takes has two possible values, C<#> or C</*>. If present and
347set to one of these two values, the generated file will contain a
348generated header that is commented out appropriately.
349
350=item ignore_pattern
351
352A regular expression. Any lines in the file matching this expression are
353ignored when determining if the target file has changed (and should therefore
354be overwritten with a new copy).
355
356=item feature_file
357
358When feature_file is set to a true value, a lines beginning with C<#perl>
359forces the remaining lines of the file to be evaluated as perl code. Before
360this evaluation occurs, any substitution of @@ values is performed on the
361original text.
362
363=item expand_gmake_syntax
364
365If set to a true value, then certain types of I<gmake> syntax will be expanded
366into their full equivalents. For example:
367
368 $(wildcard PATTERN)
369
370Will be replaced B<at config time> with the list of files that match this
371pattern. Note! Be very careful when determining whether or not to disable
372this expansion during config time and letting I<gmake> evaluate these: the
373config system itself may change state of the filesystem, causing the
374directives to expand differently depending on when they're run. Another
375potential issue to consider there is that most makefiles, while generated
376from the root directory, are B<run> from a subdirectory. So relative path names
377become an issue.
378
379The I<gmake> replacements are done repeatedly on a single line, so nested
380syntax works ok.
381
382=over 4
383
384=item addprefix
385
386=item basename
387
388=item wildcard
389
390=item notdir
391
392=back
393
394=item replace_slashes
395
396If set to a true value, then filenames on a win32 platform will get their
397forward slashes '/' automatically converted to '\'.
398win32 can handle forward slashes finer (since XP), but cmd.exe tries to
399detect /c and more as argument.
400If you ssh into cygwin for remote testing, or for smoker cronjobs
401calling cmd /c "nmake" or dmake or mingw32-make will fail.
402
403For example:
404
405    POD2MAN          = C:\perl516\perl\bin/pod2man
406    CC_INC           = -I./include -I./include/pmc
407    DEV_TOOLS_DIR = tools/dev
408    $(PERL) $(DEV_TOOLS_DIR)/headerizer.pl
409=>
410    POD2MAN          = C:\perl516\perl\bin\pod2man
411    CC_INC           = -I.\include -I.\include\pmc
412    DEV_TOOLS_DIR = tools\dev
413    $(PERL) $(DEV_TOOLS_DIR)\headerizer.pl
414
415=back
416
417=cut
418
419sub genfile {
420    my $conf = shift;
421    my ( $source, $target, %options ) = @_;
422
423    my $calling_sub = (caller(1))[3] || q{};
424
425    open my $in,  '<', $source       or die "Can't open $source: $!";
426    open my $out, '>', "${target}_tmp" or die "Can't open ${target}_tmp: $!";
427
428    if ( !exists $options{file_type}) {
429        if ( $target =~ m/makefile$/i || $target =~ m/\.mak/) {
430            $options{file_type} = 'makefile';
431        }
432        elsif ($target =~ m/\.p[lm]$/i ) {
433            $options{file_type} = 'perl';
434        }
435        elsif ($target =~ m/\.[hc]$/ ) {
436            $options{file_type} = 'c';
437        }
438        elsif ($target =~ m/\.pmc$/ ) {
439            $options{file_type} = 'pmc';
440        }
441        elsif ($target =~ m/\.pir$/ ) {
442            $options{file_type} = 'pir';
443        }
444    } elsif ( $options{file_type} eq 'none' ) {
445        delete $options{file_type};
446    }
447
448    if ( $options{file_type} ) {
449        unless ( exists $file_types_info{$options{file_type}} ) {
450            die "Unknown file_type '$options{file_type}'";
451        }
452        unless ( exists $options{comment_type} ) {
453            $options{comment_type} =
454                $file_types_info{$options{file_type}}{comment_type};
455        }
456        if ( $options{file_type} eq 'makefile' ) {
457            $options{conditioned_lines} = 1;
458            $options{replace_slashes} = 1 if $^O eq 'MSWin32';
459        }
460    }
461
462    if ( $options{comment_type} ) {
463        my @comment = ( 'ex: set ro: -*- buffer-read-only:t -*-',
464            'DO NOT EDIT THIS FILE',
465            'Generated by ' . __PACKAGE__ . " from $source" );
466
467        if ( $options{comment_type} eq '#' ) {
468            foreach my $line (@comment) {
469                $line = "# $line\n";
470            }
471        }
472        elsif ( $options{comment_type} eq '/*' ) {
473            foreach my $line (@comment) {
474                $line = " * $line\n";
475            }
476            $comment[0]  =~ s{^}{/*\n};     # '/*'
477            $comment[-1] =~ s{$}{\n */};    # ' */'
478        }
479        else {
480            die "Unknown comment type '$options{comment_type}'";
481        }
482        print {$out} @comment, "\n"; # extra newline after header
483    }
484
485    if ($target eq 'CFLAGS') {
486        $options{conditioned_lines} = 1;
487    }
488
489    if ( $options{manifest} ) {
490        add_to_generated( $target, @{$options{manifest}} );
491    }
492    elsif ($target !~ /^(cc_gen|test_)/) {
493        add_to_generated( $target, "[]" );
494    }
495
496    # this loop can not be implemented as a foreach loop as the body
497    # is dependent on <IN> being evaluated lazily
498
499    $conf->{_compiler_file} = $source;
500    my $former_truth = -1;
501  LINE:
502    while ( my $line = <$in> ) {
503        $conf->{_compiler_line} = $.;
504
505        # everything after the line starting with #perl is eval'ed
506        if ( $line =~ /^#perl/ && $options{feature_file} ) {
507
508            # OUT was/is used at the output filehandle in eval'ed scripts
509            # e.g. feature.pl or feature_h.in
510            no warnings 'once';
511            local *OUT = $out;
512            use warnings;
513            my $text = do { local $/; <$in> };
514
515            # interpolate @foo@ values
516            $text =~ s{ \@ (\w+) \@ }{\$conf->data->get("$1")}gx;
517            eval $text;
518            croak $@ if $@;
519            last LINE;
520        }
521        if ( $options{conditioned_lines} ) {
522            my ($op, $expr, $rest);
523            # allow multiple keys and nested parens here
524            if (($op,$expr,$rest)=($line =~ m/^#(IF|UNLESS|ELSIF)\((.+)\):(.*)/s)) {
525                if (($op eq 'ELSIF') and $former_truth) {
526                    next LINE;  # no useless check if former IF was true
527                }
528                my $truth = cond_eval($conf, $expr);
529                if ($op eq 'IF') {
530                    $former_truth = $truth;
531                    next LINE unless $truth;
532                }
533                elsif ($op eq 'UNLESS') {
534                    $former_truth = !$truth;
535                    next LINE if $truth;
536                }
537                elsif ($op eq 'ELSIF') {
538                    $former_truth = $truth;
539                    next LINE unless $truth;
540                }
541                $line = $rest;
542            }
543            elsif ( $former_truth != -1 and $line =~ m/^#ELSE:(.*)/s ) {
544                next LINE if $former_truth;
545                $line = $1;
546            }
547            else { # reset
548                $former_truth = -1; # ELSE must immediately follow a conditional.
549            }
550        }
551
552        # interpolate gmake-ish expansions..
553        if ( $options{expand_gmake_syntax} ) {
554            my $any_gmake;
555        GMAKES:
556            $any_gmake = 0;
557
558            if (
559                $line =~ s{\$ \( wildcard \s+ ([^)]+) \)}{
560                join (' ', glob $1)
561            }egx
562                )
563            {
564                $any_gmake++;
565            }
566
567            if (
568                $line =~ s{\$ \( notdir \s+ ([^)]+) \)}{
569                join (' ',
570                    map { (File::Spec->splitpath($_))[2] }
571                        split(' ', $1)
572                )
573            }egx
574                )
575            {
576                $any_gmake++;
577            }
578
579            # documented as removing any .-based suffix
580            if (
581                $line =~ s{\$ \( basename \s+ ([^)]+) \)}{
582                join (' ',
583                    map {
584                        my @split = File::Spec->splitpath($_);
585                        $split[2] =~ s/\.[^.]*$//;
586                        File::Spec->catpath(@split);
587                    } split(' ', $1)
588                )
589            }egx
590                )
591            {
592                $any_gmake++;
593            }
594
595            if (
596                $line =~ s{\$ \( addprefix \s+ ([^,]+) \s* , \s* ([^)]+) \)}{
597                my ($prefix,$list) = ($1, $2);
598                join (' ',
599                    map { $_ = $prefix . $_ }
600                        split(' ', $list)
601                )
602            }egx
603                )
604            {
605                $any_gmake++;
606            }
607
608            # we might have only gotten the innermost expression. try again.
609            goto GMAKES if $any_gmake;
610        }
611
612        # interpolate @foo@ values
613        $line =~ s{ \@ (\w+) \@ }{
614            if(defined(my $val=$conf->data->get($1))) {
615                $val;
616            }
617            else {
618                warn "value for '\@$1\@' in $source is undef";
619                '';
620            }
621        }egx;
622
623        # interpolate @foo::bar@ values
624        $line =~ s{ \@ (\w+) :: ([^\@]+) \@ }{
625            my $full = $1 . '::' . $2;
626            my $base = $1;
627            if(defined(my $val=$conf->data->get($full))) {
628                $val;
629            }
630            elsif(defined($val=$conf->data->get($base))) {
631                $val;
632            }
633            else {
634                warn "value for '\@$full\@' in $source is undef, no fallback";
635                '';
636            }
637        }egx;
638
639        my $warn_replace_slashes;
640        if ( $options{replace_slashes} and $^O eq 'MSWin32') {
641            # warn "option replace_slashes currently ignored\n" unless $warn_replace_slashes++;
642            $line =~ s{/}{\\}g;
643        }
644
645        print $out $line;
646    }
647
648    close($in)  or die "Can't close $source: $!";
649    close($out) or die "Can't close $target: $!";
650
651    move_if_diff( "${target}_tmp", $target, $options{ignore_pattern} );
652}
653
654# Return the next subexpression from the expression in $_[0]
655# and remove it from the input expression.
656# Allowed chars: A-Z a-z 0-9 _ -, so let's take [-\w].
657# E.g. "(not win32 and has_glut)"
658#        => not win32 => has_glut
659#      "(!win32&has_glut)|cygwin"   - perl-style
660#        !win32&has_glut => !win32 => &has_glut => |cygwin
661sub next_expr {
662    my $s = $_[0];
663    return "" unless $s;
664    # start of a subexpression?
665    if ($s =~ /^\((.+)\)\s*(.*)/) {     # longest match to matching closing paren
666        $_[0] = $2 ? $2 : "";           # modify the 2nd arg
667        return $1;
668    }
669    else {
670        $s =~ s/^\s+//;                 # left-trim to make it more robust
671        if ($s =~ /^([-\w=]+)\s*(.*)?/) { # shortest match to next non-word char
672            # start with word expr
673            $_[0] = $2 ? $2 : "";       # modify the 2nd arg expr in the caller
674            return $1;
675        }
676        else {
677            # special case: start with non-word op (perl-syntax only)
678            $s =~ /^([|&!])\s*(.*)?/;   # shortest match to next word char
679            $_[0] = $2 ? $2 : "";       # modify the 2nd arg expr in the caller
680            return $1;
681        }
682    }
683}
684
685# Checks the logical truth of the hash value: exists and not empty.
686# Also check the platform name, the 'osname' key, if the hash key does not exist.
687# Also check for key==value, like #IF(ld==gcc)
688sub cond_eval_single {
689    my $conf = $_[0];
690    my $key  = $_[1];
691    return unless defined $key;
692    if ($key =~ /^([-\w]+)==(.+)$/) {
693        return ($2 eq $conf->data->get($1));
694    }
695    else {
696        return exists($conf->data->{c}->{$key})
697            ? ($conf->data()->get($key) ? 1 : 0)
698            : $key eq $conf->data()->get('osname');
699    }
700}
701
702# Recursively evaluate boolean expressions with multiple keys and | & ! ops.
703# Order of precedence: Just "!" and "NOT" binds tighter than AND and OR.
704# There's no precedence for AND over OR defined, just left to right.
705sub cond_eval {
706    my $conf = $_[0];
707    my $expr = $_[1];
708    my @count = split /[\s!&|\(]+/, $expr; # optimizable with tr
709    if (@count > 1) { # multiple keys: recurse into
710        my $truth = 0;
711        my $prevtruth = 0;
712        my $key = next_expr($expr);
713        my $op  = '';
714      LOOP:
715        while ($key) {
716            if (($key eq '!') or (uc($key) eq 'NOT')) {
717                # bind next key immediately
718                $op = 'NOT';
719                $key = next_expr($expr);
720            }
721            elsif ($truth and ($op eq 'OR')) {
722                # true OR: => true
723                last LOOP;
724            }
725            $prevtruth = $truth;
726            if (!$truth and ($op eq 'AND')) { # false AND: => false, skip rest
727                last LOOP;
728            }
729            $truth = cond_eval($conf, $key);
730            if ($op eq 'NOT') { # NOT *: invert
731                $truth = $truth ? 0 : 1;
732            }
733            elsif ($op eq 'AND' and !$truth) { # * AND false: => false
734                last LOOP;
735            }
736            # * OR false => * (keep $truth). true OR * already handled before
737            my $prevexpr = $expr;
738            $op  = next_expr($expr);
739            if ($op) {
740                if ($op eq '|' or uc($op) eq 'OR') {
741                    $op = 'OR';
742                }
743                elsif ($op eq '&' or uc($op) eq 'AND') {
744                    $op = 'AND';
745                }
746                elsif ($op eq '!' or uc($op) eq 'NOT') {
747                    $op = 'NOT';
748                }
749                else {
750                    makecroak($conf, "invalid op \"$op\" in \"$_[1]\" at \"$prevexpr\"");
751                }
752                $key = next_expr($expr);
753            }
754            elsif ($prevexpr) {
755                makecroak($conf, "Makefile conditional syntax error: missing op in \"$_[1]\" at \"$prevexpr\"");
756            }
757            else {
758                last LOOP; # end of expr, nothing left
759            }
760            if ($prevexpr eq $expr) {
761                makecroak($conf, "Makefile conditional parser error in \"$_[1]\" at \"$prevexpr\"");
762            }
763        }
764        return $truth;
765    }
766    cond_eval_single($conf, $expr);
767}
768
769=item C<append_configure_log($path)>
770
771    $conf->append_configure_log($path)
772
773Adds $path to F<MANIFEST_configure.generated>.
774
775Deprecated, there is no F<MANIFEST_configure.generated> anymore.
776Replaced by C<add_to_generated()>.
777
778=back
779
780=cut
781
782sub append_configure_log {
783    my $conf = shift;
784    my $target = shift;
785    if ( $conf->{active_configuration} ) {
786        warn "append_configure_log() is DEPRECATED and ignored. Use add_to_generated() instead";
787
788        my $generated_log = 'MANIFEST_configure.generated';
789        open my $GEN, '>>', $generated_log
790            or die "Can't open $generated_log for appending: $!";
791        print $GEN "$target\n";
792        close $GEN or die "Can't close $generated_log after appending: $!";
793    }
794}
795
796=head1 SEE ALSO
797
798=over 4
799
800=item F<docs/configuration.pod>
801
802=back
803
804=cut
805
8061;
807
808# Local Variables:
809#   mode: cperl
810#   cperl-indent-level: 4
811#   fill-column: 100
812# End:
813# vim: expandtab shiftwidth=4:
814