The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
# $Id: Gcc.pm,v 1.41 2016/09/06 19:59:45 pfeiffer Exp $

=head1 NAME

Mpp::CommandParser::Gcc - makepp command parser for gcc or cc

=head1 DESCRIPTION

Parses a gcc compile command for implicit dependencies.

This class is readily subclassed for similar things, such as the
Embedded SQL preprocessor/compiler.

=cut

use strict;
package Mpp::CommandParser::Gcc;

use Mpp::CommandParser;
our @ISA = 'Mpp::CommandParser';

use Mpp::Text;
use Mpp::File;

*factory = \&Mpp::Subs::p_gcc_compilation;

sub new {
  my $self = &Mpp::CommandParser::new;
  require Mpp::Scanner::C;
  $self->{SCANNER} = new Mpp::Scanner::C($self->rule, $self->dir);
  $self;
}

sub new_no_gcc {
  my $self = &new;
  undef $self->{xNO_GCC};
  $self;
}

sub set_default_signature_method {
  my( $self, $leave_comments ) = @_;
  $self->rule->set_signature_class( $leave_comments ? 'md5' : 'C', 1 );
}

# The subclass can override these. Don't start doing any actual scanning here,
# because the signature method isn't necessarily set yet.
*parse_opt = $Mpp::Text::N[0]; # Ignore unknown option.
sub parse_arg {
  #my( undef, $arg, undef, $files ) = @_;

  push @{$_[3]}, $_[1]
    if is_cpp_source_name $_[1];
}


my( $no_link, $leave_comments, $nostdinc, $static );


my %opt =
  (c => \$no_link,
   E => \$no_link,
   S => \$no_link,
   C => \$leave_comments,
   nostdinc => \$nostdinc,
   static => \$static);

my %info_string = (user => 'INCLUDES',
		   sys => 'SYSTEM_INCLUDES',
		   lib => 'LIBS');
my @static_suffix_list = qw(.a);
my @suffix_list = qw(.la .so .sa .a .sl);
sub tags {
  my $scanner = $_[0]{SCANNER};
  $scanner->should_find( 'user' );
  $scanner->info_string( \%info_string );
  $scanner->add_include_suffix_list( lib => $_[1] == 1 ? \@static_suffix_list : \@suffix_list )
    if $_[1];
}
sub xparse_command {
  my( $self, $command, $setenv ) = @_;

  my $dir = $self->dir;
  my $scanner = $self->{SCANNER};
  my $conditional = $scanner->{CONDITIONAL};

  my @prefiles;
  my @files;
  my $idash; # saw -I-
  my @incdirs;
  my @idirafter;
  my $iprefix = '';
  my @libs;
  my @obj_files;
  my @cpp_opts;
  my $file_regexp = $self->input_filename_regexp($command->[0]);
  my $real_lib;

  $no_link = $leave_comments = $nostdinc = $static = 0;
  my( $cmd, @words ) = @$command;
  $cmd =~ s@.*/@@ || Mpp::is_windows > 1 && $cmd =~ s/.*\\//;
  my $icc = $cmd =~ /^ic[cl](?:\.exe)?$/;
  local $_;
  while( defined( $_ = shift @words )) {
    if( !s/^-// ) {
      if( /\.(?:[ls]?[oa]|s(?:l|o\.[\d.]+)|obj|dll)$/ ) { # object file?
	if( /[\*\?\[]/ ) {		# wildcard?
 # TBD: Why is this disabled?  Probably because zglob finds more than the Shell will.  Need chdir & glob.
 if(0) {
	  require Mpp::Glob;
	  push @obj_files,
	    Mpp::Glob::zglob($_, $self->dirinfo);
 }
	} else {
	  push @obj_files, $_;
	}
      } elsif($file_regexp && /$file_regexp/) {
        push @files, $_;
      } else {
	$self->parse_arg( $_, \@words, \@files );
      }
    } elsif( $opt{$_} ) {
      ${$opt{$_}} = 1;
    } elsif( s/^I// ) {
      $_ ||= shift @words;
      if( $_ eq '-' ) {
	$idash = 1;
	$scanner->add_include_dir( user => $_ )
	  for splice @incdirs;
      } else {
	push @incdirs, $_;
      }
    } elsif( s/^i(?:quot(e)|syste(m)|dirafte(r)|(nclude|macros)|prefi(x)|withprefix(before)?)// ) {
      my $val = $_ || shift @words; # yes, value can be glued :-{
      if( $1 || $2 ) {		# -iquote, -isystem
	$scanner->add_include_dir( $2 ? 'sys' : 'user', $val );
      } elsif( $3 ) { 		# -idirafter
	push @idirafter, $val;
      } elsif( $4 ) {		# -include or -imacros
	push @prefiles, $val;
      } elsif( $5 ) { 		# -iprefix
	$iprefix = $val;
      } elsif( $6 ) { 		# -iwithprefixbefore
	push @incdirs, $iprefix . $val;
      } else {			# -iwithprefix
	push @idirafter, $iprefix . $val;
      }
    } elsif( s/^L// ) {
      $scanner->add_include_dir( lib => $_ || shift @words );
    } elsif( s/^l// ) {
      $_ ||= shift @words;
      if( s/^:// ) {		# colon means actual lib name follows
	$real_lib = 1;
	push @libs, $_;
      } else {
	push @libs, "lib$_";
      }
    } elsif( s/^D// ) {
      $_ ||= shift @words;
      if( /^(\w+)=(.*)/ ) {
	$scanner->set_var( $1, $2 );
      } else {
	$scanner->set_var( $_, 1 );
      }
    } elsif( s/^U// ) {
      $scanner->set_var( $_ || shift @words, undef );
    } elsif( $icc && /^op(?:t-|enmp)/ ) {
    } elsif( s/^o// ) {
      $self->add_target( $_ || shift @words );
    } else {
      push @cpp_opts, "-$_" if $conditional;	# collect non parsed options for preprocessor
      $self->parse_opt( $_, \@words, \@files );
    }
  }

  $self->set_default_signature_method( $leave_comments );

  $scanner->add_include_dir( user => undef ) unless $idash;

  $self->tags( $no_link ? 0 : $static ? 1 : 2 );

  $scanner->add_include_dir( user => 'in CPATH' ) unless exists $self->{xNO_GCC};
  $scanner->add_include_dir( user => 'in :sys' );
  $scanner->add_include_dir( sys => $_ )
    for @incdirs;
  if( $nostdinc ) {
    $scanner->should_find( 'sys' );
  } else {
    require Mpp::Subs;
    $scanner->add_include_dir( sys => $_ )
      for @Mpp::Subs::system_include_dirs;
  }
  $scanner->add_include_dir( sys => absolute_filename file_info $_, $self->{RULE}{MAKEFILE}{CWD} )
    for @idirafter;
  my $context = $scanner->get_context if @files > 1;
  my $var;
  for( @files ) {
    my $file_end = case_sensitive_filenames ? lc substr $_, -4 : substr $_, -4;
    my $cplusplus = $file_end =~ /\.(?:c(?:c|xx|pp|\+\+)|C)$/;
    $scanner->reset( $context, $cplusplus ) if $context;
    unless( $var ) {
      $var = exists $self->{xNO_GCC} ?
	Mpp::is_windows && $cmd =~ /\bcl(?:.cl)?$/i && 'in ;INCLUDE' :
	$cplusplus ? 'in CPLUS_INCLUDE_PATH' : 'in C_INCLUDE_PATH';
      $scanner->add_include_dir( sys => $var, 1 ) if $var;
    }

    $self->xset_preproc_vars( $cmd, \@cpp_opts, $cplusplus ) if $conditional;
    # scan each file included on command line with  set of preproc. vars
    $scanner->scan_file( $self, c => $_ ) or return undef
      for @prefiles, $_;
  }

  unless( $no_link ) {
    $scanner->add_include_suffix( lib => '' )
      if $real_lib;
    $scanner->add_include_dir( lib => $_ )
      for @Mpp::Subs::system_lib_dirs;
    my $var = exists $self->{xNO_GCC} ?
      Mpp::is_windows && $cmd =~ /\bcl(?:.cl)?$/i && 'in ;LIB' :
      'in LIBRARY_PATH';
    $scanner->add_include_dir( lib => $var ) if $var;

    $self->add_simple_dependency( $_ )
      for @obj_files;
    return 1 unless @libs;
    my $tag = $scanner->get_tagname( 'lib' );
    $self->add_simple_dependency( $scanner->find( undef, 'lib', $_ ), $tag, undef, $_ )
      for @libs;
  }
  return 1;
}

sub _set_def {
  my ($self, $key, $value) = @_;
  $self->{SCANNER}->set_var( $key => $value )
    unless exists $self->{SCANNER}{VARS}{$key};
}
my %var_cache;
sub xset_preproc_vars {
  my( $self, $cmd, $opts, $cplusplus ) = @_;

  my $ran;
  unless( exists $self->{xNO_GCC} ) {
    #use preprocessor
    $cmd = Mpp::Text::join_with_protection $cmd,
      '-E', '-dM', '-x', $cplusplus ? 'c++' : 'c', '/dev/null',
      @$opts;
    if( $var_cache{$cmd} ) {
      my %copy = %{$var_cache{$cmd}};
      $self->{SCANNER}{VARS} = \%copy;
      return;
    }
#now call preprocessor to get rest of variables, but do not
#override variables which were already set up via -D and -U
    local $_;
    open my $cpp, '-|', $cmd or die;
    while( <$cpp> ) {
      next if(/^\#\s+\d/);	# gcc 3.4 reports an explicit line number
      # The second space is optional, because gcc doesn't always print it
      # for certain built-in macros such as __FILE__, and when that happens
      # the value is the empty string.
      if( /^\#define (\S+) ?(.*)/ ) {
	my ($name, $val)=($1, $2);
	_set_def $self, $name, $val;
      } else {
	chomp;
	warn "$cmd produced unparsable `$_'";
      }
    }
    $ran = close $cpp or
      warn "Preprocessor exited with status $? from opts: $cmd\n";
  }
  unless( $ran ) { # very basic setup any compiler/OS
    _set_def $self, __STDC__ => 1 unless grep { $_ eq '-traditional' } @$opts;
    _set_def $self, __GNUC__ => 1 unless exists $self->{xNO_GCC} || grep { $_ eq '-no-gcc' } @$opts;
    _set_def $self, __cplusplus => 1 if $cplusplus;
  }
  unless( exists $self->{xNO_GCC} ) {
    my %copy = %{$self->{SCANNER}{VARS}};
    $var_cache{$cmd} = \%copy;
  }
  return;
}
1;