File Coverage

File:lib/Parrot/Configure/Compiler.pm
Coverage:90.1%

linestmtbrancondsubcode
1# Copyright (C) 2001-2012, Parrot Foundation.
2
3 - 19
=head1 NAME

Parrot::Configure::Compiler - C-Related methods for configuration and more

=head1 DESCRIPTION

The Parrot::Configure::Compiler module provides methods inherited by
Parrot::Configure which prepare and/or run C programs during compilation.
Other methods from this module will be used to generate makefiles and other
files.  Template entries of the form C<@key@> will be replaced with C<key>'s
value from the configuration system's data.

=head2 Methods

=over 4

=cut
20
21package Parrot::Configure::Compiler;
22
23
99
99
99
use strict;
24
99
99
99
use warnings;
25
26
99
99
99
use Carp;
27
99
99
99
use File::Spec ();
28
99
99
99
use lib ('lib');
29
99
use Parrot::Configure::Utils qw(
30    prompt copy_if_diff move_if_diff integrate
31    capture_output check_progs _slurp
32    _run_command _build_compile_command
33    move_if_diff
34
99
99
);
35
36# report the makefile and lineno
37sub makecroak {
38
1
    my ($conf, $error) = @_;
39
1
    my ($file, $line) = ($conf->{_compiler_file}, $conf->{_compiler_line});
40
1
    die "$error at $file line $line\n";
41}
42
43our %file_types_info = (
44    makefile => {
45        comment_type => '#',
46    },
47    c => {
48        comment_type => '/*',
49    },
50    pmc => {
51        comment_type => '/*',
52    },
53    perl => {
54        comment_type => '#',
55    },
56    pir => {
57        comment_type => '#',
58    },
59);
60
61 - 67
=item C<cc_gen()>

    $conf->cc_gen($source)

Generates F<test_$$.c> from the specified source file.

=cut
68
69sub cc_gen {
70
273
    my $conf = shift;
71
273
    my $source = shift;
72
73
273
    $conf->genfile( $source, "test_$$.c", file_type => 'c' );
74}
75
76 - 86
=item C<cc_build()>

    $conf->cc_build($cc_args, $link_args)

These items are used from current config settings:

  $cc, $ccflags, $ldout, $o, $link, $linkflags, $cc_exe_out, $exe, $libs

Calls the compiler and linker on F<test_$$.c>.

=cut
87
88sub cc_build {
89
103
    my $conf = shift;
90
103
    my ( $cc_args, $link_args ) = @_;
91
92
103
    $cc_args = '' unless defined $cc_args;
93
103
    $link_args = '' unless defined $link_args;
94
95
103
    my $verbose = $conf->options->get('verbose');
96
97
103
    my ( $cc, $ccflags, $ldout, $o, $link, $linkflags, $cc_exe_out, $exe, $libs ) =
98        $conf->data->get(qw(cc ccflags ld_out o link linkflags cc_exe_out exe libs));
99
100    # unique test file name for parallel builds
101
103
    my $test = 'test_' . $$;
102
103
    my $compile_command = _build_compile_command( $cc, $ccflags, $cc_args );
103
103
    my $compile_result = _run_command( $compile_command, "$test.cco", "$test.cco", $verbose );
104
105
103
    if ($compile_result) {
106
8
        confess "C compiler failed (see $test.cco)";
107
0
        return $compile_result;
108    }
109
110
95
    my $link_result =
111        _run_command( "$link $linkflags $test$o $link_args ${cc_exe_out}${test}${exe} $libs",
112        "$test.ldo", "$test.ldo", $verbose )
113        and confess "Linker failed (see $test.ldo)";
114
95
    if ($link_result) {
115
0
        return $link_result;
116    }
117}
118
119 - 126
=item C<cc_run()>

    $conf->cc_run();

Calls the F<test> (or F<test.exe>) executable. Any output is directed to
F<test.out>.

=cut
127
128sub cc_run {
129
87
    my $conf = shift;
130
87
    my $exe = $conf->data->get('exe');
131
87
    my $slash = $conf->data->get('slash');
132
87
    my $verbose = $conf->options->get('verbose');
133
87
    my $test = 'test_' . $$;
134
87
    my $test_exe = ".${slash}${test}${exe}";
135
136
87
    my $run_error;
137
87
    if ( defined( $_[0] ) && length( $_[0] ) ) {
138
0
        local $" = ' ';
139
0
        $run_error = _run_command( "$test_exe @_", "./$test.out", undef, $verbose );
140    }
141    else {
142
87
        $run_error = _run_command( $test_exe, "./$test.out", undef, $verbose );
143    }
144
145
87
    my $output = _slurp("./$test.out");
146
147
87
    return $output;
148}
149
150 - 157
=item C<cc_run_capture()>

    $conf->cc_run_capture();

Same as C<cc_run()> except that warnings and errors are also directed to
F<test.out>.

=cut
158
159sub cc_run_capture {
160
4
    my $conf = shift;
161
4
    my $exe = $conf->data->get('exe');
162
4
    my $slash = $conf->data->get('slash');
163
4
    my $verbose = $conf->options->get('verbose');
164
4
    my $test = 'test_' . $$;
165
166
4
    if ( defined( $_[0] ) && length( $_[0] ) ) {
167
0
        local $" = ' ';
168
0
        _run_command( ".${slash}$test${exe} @_", "./$test.out", "./$test.out", $verbose );
169    }
170    else {
171
4
        _run_command( ".${slash}$test${exe}", "./$test.out", "./$test.out", $verbose );
172    }
173
174
4
    my $output = _slurp("./$test.out");
175
176
4
    return $output;
177}
178
179 - 185
=item C<cc_clean()>

    $conf->cc_clean();

Cleans up all files in the root folder that match the glob F<test.*>.

=cut
186
187sub cc_clean { ## no critic Subroutines::RequireFinalReturn
188
272
    my $conf = shift;
189
272
    unlink map "test_${$}$_", qw( .c .cco .ldo .out ),
190        $conf->data->get(qw( o exe )),
191        # MSVC
192        qw( .exe.manifest .ilk .pdb );
193}
194
195 - 204
=item C<shebang_mod()>

    $conf->shebang_mod($source, $target);

Takes the specified source file, replacing entries like C<@key@> with
C<key>'s value from the configuration system's data, and writes the results
to specified target file. The replacement is only done in the first line of
the file normally to set the shebang value accordingly.

=cut
205
206sub shebang_mod {
207
2
    my $conf = shift;
208
2
    my ( $source, $target ) = @_;
209
210
2
    open my $in, '<', $source or die "Can't open $source: $!";
211
2
    open my $out, '>', "$target.tmp" or die "Can't open $target.tmp: $!";
212
213
2
    my $line = <$in>;
214
215    # interpolate @foo@ values
216
2
    $line =~ s{ \@ (\w+) \@ }{
217
2
        if(defined(my $val=$conf->data->get($1))) {
218
2
            $val;
219        }
220        else {
221
0
            warn "value for '\@$1\@' in $source is undef";
222
0
            '';
223        }
224    }egx;
225
226
2
    print $out $line;
227
228
2
    while ( my $line = <$in> ) {
229
1554
        print $out $line;
230    }
231
232
2
    close($in) or die "Can't close $source: $!";
233
2
    close($out) or die "Can't close $target: $!";
234
235
2
    move_if_diff( "$target.tmp", $target );
236}
237
238 - 388
=item C<genfile()>

    $conf->genfile($source, $target, %options);

Takes the specified source file, replacing entries like C<@key@> with
C<key>'s value from the configuration system's data, and writes the results
to specified target file.

If a C<::> is present in the C<@key@>, the replaced value will first try to
use the full key, but if that is not present, the key up to the C<::> is used.
For example, if C<@cc_warn::src/embed.c@> is used, and that key doesn't
exist, the fallback key would be C<@cc_warn@>.

Respects the following options when manipulating files (Note: most of the
replacement syntax assumes the source text is on a single line.)

=over 4

=item file_type

If set to a C<makefile>, C<c> or C<perl> value, C<comment_type> will be set to
corresponding value.  Moreover, when set to a C<makefile> value, it will
enable C<conditioned_lines>.

Its value will be detected automatically by target file name unless you set
it to a special value C<none>.

=item conditioned_lines #IF #UNLESS #ELSIF #ELSE

If conditioned_lines is true, then lines beginning in C<#IF>, C<#UNLESS>,
C<#ELSIF>, and C<#ELSE> are evaluated conditionally, and the content after the
C<:> is included or excluded, depending on the evaluation of the expression.

Lines beginning with C<#IF(expr):> are skipped if the expr condition is false,
otherwise the content after the C<:> is inserted. Lines beginning with
C<#UNLESS(expr):> are skipped if the expr condition is true, otherwise the
content after the C<:> is inserted. Lines beginning with C<#ELSIF(expr):> or
C<#ELSE:> are evaluated if the preceding C<#IF(expr):> evaluated to false.

A condition C<expr> may be:

=over 4

=item *

A single key, which is true if a config key is true,

=item *

Equal to the platform name or the osname - case-sensitive,

=item *

A C<key==value> expression, which is true if the config key has the expected
value, or

=item *

A logical combination of C<|>, C<OR>, C<&>, C<AND>, C<!>, C<NOT>.

=back

A key must only consist of the characters C<A-Z a-z 0-9 _ ->, and is checked
case-sensitively against the configuration key or the platform name. Truth is
defined as any value that is not C<0>, an empty string, or C<undef>.

The value in C<key==value> expressions may not contain spaces. Quotes in
values are not supported.

The word ops C<AND>, C<OR> and C<NOT> are case-insensitive. C<!> and C<NOT>
bind closer than C<&>, C<AND>, C<|>, and C<OR>. The order of precedence for
C<AND> and C<OR> is undefined.


For instance:

  #IF(win32): src/atomic/gcc_x86$(O)

will be included if the platform is win32.

  #IF(cpuarch==i386): src/atomic/gcc_x86$(O)

will be included if the value of the config key "cpuarch" is "i386".

  #IF(cpuarch==i386): src/atomic/gcc_x86$(O)
  #ELSIF(cpuarch==sparcv9): src/atomic/sparc_v9.s
  #ELSE:

will include " src/atomic/gcc_x86$(O)" if the config key "cpuarch" is
ste to "i386", will include " src/atomic/sparc_v9.s" instead if
"cpuarch" is set to "sparcv9", and will include an empty line otherwise.

  #IF(win32 and glut and not cygwin):

will be used on "win32" and if "glut" is defined, but not on "cygwin".

=item comment_type

This option takes has two possible values, C<#> or C</*>. If present and
set to one of these two values, the generated file will contain a
generated header that is commented out appropriately.

=item ignore_pattern

A regular expression. Any lines in the file matching this expression are
ignored when determining if the target file has changed (and should therefore
be overwritten with a new copy).

=item feature_file

When feature_file is set to a true value, a lines beginning with C<#perl>
forces the remaining lines of the file to be evaluated as perl code. Before
this evaluation occurs, any substitution of @@ values is performed on the
original text.

=item expand_gmake_syntax

If set to a true value, then certain types of I<gmake> syntax will be expanded
into their full equivalents. For example:

 $(wildcard PATTERN)

Will be replaced B<at config time> with the list of files that match this
pattern. Note! Be very careful when determining whether or not to disable
this expansion during config time and letting I<gmake> evaluate these: the
config system itself may change state of the filesystem, causing the
directives to expand differently depending on when they're run. Another
potential issue to consider there is that most makefiles, while generated
from the root directory, are B<run> from a subdirectory. So relative path names
become an issue.

The I<gmake> replacements are done repeatedly on a single line, so nested
syntax works ok.

=over 4

=item addprefix

=item basename

=item wildcard

=item notdir

=back

=back

=back

=cut
389
390sub genfile {
391
301
    my $conf = shift;
392
301
    my ( $source, $target, %options ) = @_;
393
394
301
    my $calling_sub = (caller(1))[3] || q{};
395
301
    if ( $calling_sub !~ /cc_gen$/ ) {
396
28
        $conf->append_configure_log($target);
397    }
398
399
301
    open my $in, '<', $source or die "Can't open $source: $!";
400
300
    open my $out, '>', "$target.tmp" or die "Can't open $target.tmp: $!";
401
402
300
    if ( !exists $options{file_type}) {
403
23
        if ( $target =~ m/makefile$/i || $target =~ m/\.mak/) {
404
9
            $options{file_type} = 'makefile';
405        }
406        elsif ($target =~ m/\.p[lm]$/i ) {
407
0
            $options{file_type} = 'perl';
408        }
409        elsif ($target =~ m/\.[hc]$/ ) {
410
3
            $options{file_type} = 'c';
411        }
412        elsif ($target =~ m/\.pmc$/ ) {
413
0
            $options{file_type} = 'pmc';
414        }
415        elsif ($target =~ m/\.pir$/ ) {
416
1
            $options{file_type} = 'pir';
417        }
418    } elsif ( $options{file_type} eq 'none' ) {
419
0
        delete $options{file_type};
420    }
421
422
300
    if ( $options{file_type} ) {
423
290
        unless ( exists $file_types_info{$options{file_type}} ) {
424
1
            die "Unknown file_type '$options{file_type}'";
425        }
426
289
        unless ( exists $options{comment_type} ) {
427
288
            $options{comment_type} =
428                $file_types_info{$options{file_type}}{comment_type};
429        }
430
289
        if ( $options{file_type} eq 'makefile' ) {
431
12
            $options{conditioned_lines} = 1;
432        }
433    }
434
435
299
    if ( $options{comment_type} ) {
436
289
        my @comment = ( 'ex: set ro:',
437            'DO NOT EDIT THIS FILE',
438            'Generated by ' . __PACKAGE__ . " from $source" );
439
440
289
        if ( $options{comment_type} eq '#' ) {
441
12
            foreach my $line (@comment) {
442
36
                $line = "# $line\n";
443            }
444        }
445        elsif ( $options{comment_type} eq '/*' ) {
446
276
            foreach my $line (@comment) {
447
828
                $line = " * $line\n";
448            }
449
276
            $comment[0] =~ s{^}{/*\n}; # '/*'
450
276
            $comment[-1] =~ s{$}{\n */}; # ' */'
451        }
452        else {
453
1
            die "Unknown comment type '$options{comment_type}'";
454        }
455
288
288
        print {$out} @comment, "\n"; # extra newline after header
456    }
457
458
298
    if ($target eq 'CFLAGS') {
459
9
        $options{conditioned_lines} = 1;
460    }
461
462    # this loop can not be implemented as a foreach loop as the body
463    # is dependent on <IN> being evaluated lazily
464
465
298
    $conf->{_compiler_file} = $source;
466
298
    my $former_truth = -1;
467  LINE:
468
298
    while ( my $line = <$in> ) {
469
15064
        $conf->{_compiler_line} = $.;
470
471        # everything after the line starting with #perl is eval'ed
472
15064
        if ( $line =~ /^#perl/ && $options{feature_file} ) {
473
474            # OUT was/is used at the output filehandle in eval'ed scripts
475            # e.g. feature.pl or feature_h.in
476
99
99
99
            no warnings 'once';
477
2
            local *OUT = $out;
478
99
99
99
            use warnings;
479
2
2
2
            my $text = do { local $/; <$in> };
480
481            # interpolate @foo@ values
482
2
            $text =~ s{ \@ (\w+) \@ }{\$conf->data->get("$1")}gx;
483
2
            eval $text;
484
2
            croak $@ if $@;
485
1
            last LINE;
486        }
487
15062
        if ( $options{conditioned_lines} ) {
488
7492
            my ($op, $expr, $rest);
489            # allow multiple keys and nested parens here
490
7492
            if (($op,$expr,$rest)=($line =~ m/^#(IF|UNLESS|ELSIF)\((.+)\):(.*)/s)) {
491
219
                if (($op eq 'ELSIF') and $former_truth) {
492
1
                    next LINE; # no useless check if former IF was true
493                }
494
218
                my $truth = cond_eval($conf, $expr);
495
217
                if ($op eq 'IF') {
496
198
                    $former_truth = $truth;
497
198
                    next LINE unless $truth;
498                }
499                elsif ($op eq 'UNLESS') {
500
16
                    $former_truth = !$truth;
501
16
                    next LINE if $truth;
502                }
503                elsif ($op eq 'ELSIF') {
504
3
                    $former_truth = $truth;
505
3
                    next LINE unless $truth;
506                }
507
84
                $line = $rest;
508            }
509            elsif ( $former_truth != -1 and $line =~ m/^#ELSE:(.*)/s ) {
510
11
                next LINE if $former_truth;
511
5
                $line = $1;
512            }
513            else { # reset
514
7262
                $former_truth = -1; # ELSE must immediately follow a conditional.
515            }
516        }
517
518        # interpolate gmake-ish expansions..
519
14921
        if ( $options{expand_gmake_syntax} ) {
520
4
            my $any_gmake;
521
8
        GMAKES:
522            $any_gmake = 0;
523
524
8
            if (
525                $line =~ s{\$ \( wildcard \s+ ([^)]+) \)}{
526
1
                join (' ', glob $1)
527            }egx
528                )
529            {
530
1
                $any_gmake++;
531            }
532
533
8
            if (
534                $line =~ s{\$ \( notdir \s+ ([^)]+) \)}{
535
3
                join (' ',
536
1
                    map { (File::Spec->splitpath($_))[2] }
537                        split(' ', $1)
538                )
539            }egx
540                )
541            {
542
1
                $any_gmake++;
543            }
544
545            # documented as removing any .-based suffix
546
8
            if (
547                $line =~ s{\$ \( basename \s+ ([^)]+) \)}{
548
3
                join (' ',
549                    map {
550
1
                        my @split = File::Spec->splitpath($_);
551
3
                        $split[2] =~ s/\.[^.]*$//;
552
3
                        File::Spec->catpath(@split);
553                    } split(' ', $1)
554                )
555            }egx
556                )
557            {
558
1
                $any_gmake++;
559            }
560
561
8
            if (
562                $line =~ s{\$ \( addprefix \s+ ([^,]+) \s* , \s* ([^)]+) \)}{
563
1
                my ($prefix,$list) = ($1, $2);
564
3
                join (' ',
565
1
                    map { $_ = $prefix . $_ }
566                        split(' ', $list)
567                )
568            }egx
569                )
570            {
571
1
                $any_gmake++;
572            }
573
574            # we might have only gotten the innermost expression. try again.
575
8
            goto GMAKES if $any_gmake;
576        }
577
578        # interpolate @foo@ values
579
14921
        $line =~ s{ \@ (\w+) \@ }{
580
637
            if(defined(my $val=$conf->data->get($1))) {
581
636
                $val;
582            }
583            else {
584
1
                warn "value for '\@$1\@' in $source is undef";
585
1
                '';
586            }
587        }egx;
588
589        # interpolate @foo::bar@ values
590
14921
        $line =~ s{ \@ (\w+) :: ([^\@]+) \@ }{
591
30
            my $full = $1 . '::' . $2;
592
30
            my $base = $1;
593
30
            if(defined(my $val=$conf->data->get($full))) {
594
9
                $val;
595            }
596            elsif(defined($val=$conf->data->get($base))) {
597
21
                $val;
598            }
599            else {
600
0
                warn "value for '\@$full\@' in $source is undef, no fallback";
601
0
                '';
602            }
603        }egx;
604
605
14921
        print $out $line;
606    }
607
608
296
    close($in) or die "Can't close $source: $!";
609
296
    close($out) or die "Can't close $target: $!";
610
611
296
    move_if_diff( "$target.tmp", $target, $options{ignore_pattern} );
612}
613
614# Return the next subexpression from the expression in $_[0]
615# and remove it from the input expression.
616# Allowed chars: A-Z a-z 0-9 _ -, so let's take [-\w].
617# E.g. "(not win32 and has_glut)"
618# => not win32 => has_glut
619# "(!win32&has_glut)|cygwin" - perl-style
620# !win32&has_glut => !win32 => &has_glut => |cygwin
621sub next_expr {
622
345
    my $s = $_[0];
623
345
    return "" unless $s;
624    # start of a subexpression?
625
297
    if ($s =~ /^\((.+)\)\s*(.*)/) { # longest match to matching closing paren
626
12
        $_[0] = $2 ? $2 : ""; # modify the 2nd arg
627
12
        return $1;
628    }
629    else {
630
285
        $s =~ s/^\s+//; # left-trim to make it more robust
631
285
        if ($s =~ /^([-\w=]+)\s*(.*)?/) { # shortest match to next non-word char
632            # start with word expr
633
260
            $_[0] = $2 ? $2 : ""; # modify the 2nd arg expr in the caller
634
260
            return $1;
635        }
636        else {
637            # special case: start with non-word op (perl-syntax only)
638
25
            $s =~ /^([|&!])\s*(.*)?/; # shortest match to next word char
639
25
            $_[0] = $2 ? $2 : ""; # modify the 2nd arg expr in the caller
640
25
            return $1;
641        }
642    }
643}
644
645# Checks the logical truth of the hash value: exists and not empty.
646# Also check the platform name, the 'osname' key, if the hash key does not exist.
647# Also check for key==value, like #IF(ld==gcc)
648sub cond_eval_single {
649
269
    my $conf = $_[0];
650
269
    my $key = $_[1];
651
269
    return unless defined $key;
652
269
    if ($key =~ /^([-\w]+)==(.+)$/) {
653
5
        return ($2 eq $conf->data->get($1));
654    }
655    else {
656
264
        return exists($conf->data->{c}->{$key})
657            ? ($conf->data()->get($key) ? 1 : 0)
658            : $key eq $conf->data()->get('osname');
659    }
660}
661
662# Recursively evaluate boolean expressions with multiple keys and | & ! ops.
663# Order of precedence: Just "!" and "NOT" binds tighter than AND and OR.
664# There's no precedence for AND over OR defined, just left to right.
665sub cond_eval {
666
363
    my $conf = $_[0];
667
363
    my $expr = $_[1];
668
363
    my @count = split /[\s!&|\(]+/, $expr; # optimizable with tr
669
363
    if (@count > 1) { # multiple keys: recurse into
670
94
        my $truth = 0;
671
94
        my $prevtruth = 0;
672
94
        my $key = next_expr($expr);
673
94
        my $op = '';
674      LOOP:
675
94
        while ($key) {
676
185
            if (($key eq '!') or (uc($key) eq 'NOT')) {
677                # bind next key immediately
678
20
                $op = 'NOT';
679
20
                $key = next_expr($expr);
680            }
681            elsif ($truth and ($op eq 'OR')) {
682                # true OR: => true
683
4
                last LOOP;
684            }
685
181
            $prevtruth = $truth;
686
181
            if (!$truth and ($op eq 'AND')) { # false AND: => false, skip rest
687
36
                last LOOP;
688            }
689
145
            $truth = cond_eval($conf, $key);
690
145
            if ($op eq 'NOT') { # NOT *: invert
691
20
                $truth = $truth ? 0 : 1;
692            }
693            elsif ($op eq 'AND' and !$truth) { # * AND false: => false
694
5
                last LOOP;
695            }
696            # * OR false => * (keep $truth). true OR * already handled before
697
140
            my $prevexpr = $expr;
698
140
            $op = next_expr($expr);
699
140
            if ($op) {
700
92
                if ($op eq '|' or uc($op) eq 'OR') {
701
29
                    $op = 'OR';
702                }
703                elsif ($op eq '&' or uc($op) eq 'AND') {
704
62
                    $op = 'AND';
705                }
706                elsif ($op eq '!' or uc($op) eq 'NOT') {
707
0
                    $op = 'NOT';
708                }
709                else {
710
1
                    makecroak($conf, "invalid op \"$op\" in \"$_[1]\" at \"$prevexpr\"");
711                }
712
91
                $key = next_expr($expr);
713            }
714            elsif ($prevexpr) {
715
0
                makecroak($conf, "Makefile conditional syntax error: missing op in \"$_[1]\" at \"$prevexpr\"");
716            }
717            else {
718
48
                last LOOP; # end of expr, nothing left
719            }
720
91
            if ($prevexpr eq $expr) {
721
0
                makecroak($conf, "Makefile conditional parser error in \"$_[1]\" at \"$prevexpr\"");
722            }
723        }
724
93
        return $truth;
725    }
726
269
    cond_eval_single($conf, $expr);
727}
728
729sub append_configure_log {
730
49
    my $conf = shift;
731
49
    my $target = shift;
732
49
    if ( $conf->{active_configuration} ) {
733
24
        my $generated_log = 'MANIFEST.configure.generated';
734
24
        open my $GEN, '>>', $generated_log
735            or die "Can't open $generated_log for appending: $!";
736
24
        print $GEN "$target\n";
737
24
        close $GEN or die "Can't close $generated_log after appending: $!";
738    }
739}
740
741 - 749
=head1 SEE ALSO

=over 4

=item F<docs/configuration.pod>

=back

=cut
750
7511;
752
753# Local Variables:
754# mode: cperl
755# cperl-indent-level: 4
756# fill-column: 100
757# End:
758# vim: expandtab shiftwidth=4: