build/autoconf/make-makefile
author Nicholas Nethercote <nnethercote@mozilla.com>
Wed, 18 Jul 2012 17:38:10 -0700
changeset 105208 61d052e202c8ca786b42f7f9116c2619d5a45fe6
parent 98529 f4157e8c410708d76703f19e4dfb61859bfe32d8
permissions -rwxr-xr-x
Bug 647367 - Sequester jshash.{h,cpp} in js/jsd/. r=luke.

#!/usr/bin/env perl
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

##----------------------------##
##---] CORE/CPAN INCLUDES [---##
##----------------------------##
use strict;
use warnings;
use Getopt::Long;

use Benchmark;
use Cwd;
use File::Basename;
use File::Copy;
use File::Path      qw{mkpath};

##-------------------##
##---]  EXPORTS  [---##
##-------------------##
our $VERSION = qw(2.0);

##--------------------##
##---]  INCLUDES  [---##
##--------------------##

##############################################################
# pymake: special case path handling for windows cmd shell.
#   if invoked by cmd.exe and msys-perl is in play
#     $0 may contain a drive letter
#     modules use-or-expect msys/unix paths
#     adjust $0 => C:/foo => /c/foo so string tests and
#     manipulation can by applied properly.
##############################################################
sub BEGIN
{
    if ($^O eq 'msys' && $ENV{PATH} =~ m!\w:/!)
    {
	$0 =~ s!^(\w):!/$1!;
    }
    eval 'use FindBin';
    die $@ if ($@);
}

use lib $FindBin::Bin;
use makemakefile;

##-------------------##
##---]  GLOBALS  [---##
##-------------------##
my %argv;

my $t0 = Benchmark->new();
sub END
{
    if ($argv{bench})
    {
        my $t1 = Benchmark->new();
        my $delta = timediff($t1, $t0);
        print STDERR timestr($delta), "\n";
    }
}

##----------------##
##---]  MAIN  [---##
##----------------##
umask 0;

my $debug = $argv{debug} || 0;

my $pwdcmd = ($^O eq 'msys') ? 'pwd -W' : 'pwd';

# Determine various tree path variables
#
my ($topsrcdir, $ptopsrcdir, $depth, @makefiles) = parse_arguments(@ARGV);

my $object_fullpath = `$pwdcmd`; # Cwd::getcwd()
chdir $depth;
my $object_root = `$pwdcmd`;  # Cwd::getcwd()
chomp $object_fullpath;
chomp $object_root;

# $source_subdir is the path from the object root to where
#    'make-makefile' was called. For example, if make-makefile was
#    called from "mozilla/gfx/src", then $source_subdir would be
#    "gfx/src/".
my $source_subdir = "$object_fullpath/";
my $quoted_object_root = quotemeta($object_root);
$source_subdir =~ s|^$quoted_object_root/||;

# Prefix makefiles with $source_subdir so that paths
# will be relative to the top of the object tree.
#
my $makefile;
for $makefile (@makefiles) { # dead code ?
  $makefile = "$source_subdir$makefile";
}

# Find the path to the source directory based on how 'make-makefile'
#  was invoked. The path is either relative to the object directory
#  or an absolute path.
my $given_srcdir = find_srcdir($topsrcdir, $depth);
my $pgiven_srcdir = find_srcdir($ptopsrcdir, $depth);

if ($debug) {
  warn "object_fullpath = $object_fullpath\n";
  warn "object_root     = $object_root\n";
  warn "source_subdir   = $source_subdir\n";
  warn "makefiles       = @makefiles\n";
  warn "given_srcdir    = $given_srcdir\n";
}

my @errors;
my @unhandled = update_makefiles_legacy($given_srcdir, $pgiven_srcdir, @makefiles);
push(@errors, $@) if ($@);

run_config_status(@unhandled);
push(@errors, $@) if ($@ && $argv{'no-warnings'});

exit scalar(@errors);

# end of Main
############################################################

###########################################################################
# find_depth: Pull the value of DEPTH out of a Makefile (or Makefile.in)
###########################################################################
sub find_depth {
  my $depth = '';
  open(MAKEFILE, "<$_[0]") || die "Unable to open $_[0]: $!\n";
  while (<MAKEFILE>) {
    next unless /^DEPTH\s*=\s*(\..*)/;
    $depth = $1;
    last;
  }
  close MAKEFILE;
  return $depth;
}

###########################################################################
## Intent: Parse command line arguments and assign values
###########################################################################
sub parse_arguments {
  my @args = @_;
  my @makefiles = ();

  my @arglist = qw(badtokens! bench
                   chdir=s
                   debug
                   depth|d=s
                   enhanced
                   obj=s top|t=s ptop|p=s
                   src=s dst=s
                   );
  unless(GetOptions(\%argv, @arglist))
  {
      my $script = join('/', $FindBin::RealBin, $FindBin::Script);
      system("perldoc $script </dev/null");
      exit
  }
  @args = @ARGV;

  my $topsrcdir = $argv{top} || '';
  if (! $topsrcdir)
  {
      $topsrcdir = $argv{top} = getTopDir();
  }

  my $ptopsrcdir ||= $argv{ptop} || $topsrcdir || '';

  ## Init --no- switch values
  foreach my $var (qw(badtokens exclusions warnings))
  {
      $argv{"no-${var}"} = $argv{$var} || 0;
  }
  # Propogate parsed arguments for module use [--debug, --verbose]
  while (my($k, $v) = each %argv)
  {
      $main::argv{$k} = $v;
  }

  if ($argv{chdir})
  {
      chdir $argv{chdir} || die "chdir $argv{chdir} failed: $!";
  }

  ##############################################################
  ## Arguments allowing make-makefile to be invoked from $topsrc
  ##############################################################
  if (!$argv{top} || !$argv{obj})
  {
  }
  ## Limit access to container makefiles for now
  elsif ($argv{enhanced})
  {
      my @errors;

      ## iterate over @ARGV to preserve original filename for 'unhandled'
      my @files = map{ getRelPath($_) } @ARGV;

      my $top = getTopDir();
      my $obj = getObjDir();

      mkdirr(map{ "$obj/$_" } @files);
      push(@errors, $@) if ($@); # legacy behavior: do not exit with status

      my $exclude = join('/', $FindBin::RealBin, $FindBin::Script);
      $exclude .= '.excl'; # $argv{exclude}
      my %exclude = getExclusions($exclude);
      my @unhandled;
      foreach my $relpath (@files)
      {
          my $rel = join('/', $relpath, 'Makefile.in');
          my $mf = join('/', $top, $rel);
          next if ($exclude{$rel});
          print STDERR " ** relpath=[$relpath], mf=[$mf]\n" if ($main::argv{debug});

          my $rc = updateMakefiles($relpath, {depth=>$depth, obj=>$obj, top=>$top});
          if ($@)
          {
            push(@errors, $@);
          }
          elsif ($rc eq 'badtokens')
          {
            push(@unhandled, $mf);
          }
      }

      run_config_status(@unhandled);
      push(@errors, $@) if ($@ && $argv{'no-warnings'});
      exit scalar(@errors);
  }


  my $depth = $argv{depth} || '';
  if (! $depth)
  {
      foreach my $fyl (@args)
      {
          if (my $tmp = find_depth($fyl))
          {
              $depth = $tmp;
              last;
          }
      }
  }

  if (! $depth) {
    # Use $(DEPTH) in the Makefile or Makefile.in to determine the depth
    if (-e "Makefile.in") {
      $depth = find_depth("Makefile.in");
    } elsif (-e "Makefile") {
      $depth = find_depth("Makefile");
    } elsif (-e "../Makefile") {
      $depth = "../".find_depth("../Makefile");
      $depth =~ s/\/\.$//;
    } else {
      warn "Unable to determine depth (e.g. ../..) to root of objdir tree.\n";
      die  "No Makefile(.in) present. Try running with '-d <depth>'\n";
    }
  } 

  # Build the list of makefiles to generate
  #
  @makefiles = ();
  while (@args)
  {
      next unless my $makefile = shift @args;
      $makefile =~ s/\.in$//;
      $makefile =~ s/\/$//;
      $makefile =~ /Makefile$/
        or $makefile =~ /^\.\//
        or $makefile .= "/Makefile";
    push @makefiles, "$makefile";
  }
  @makefiles = "Makefile" unless @makefiles;

  return ($topsrcdir, $ptopsrcdir, $depth, @makefiles);
}

# Find the top of the source directory
# (Assuming that the executable is in $top_srcdir/build/autoconf)
sub find_srcdir {
  my ($ac_given_srcdir, $depth) = @_;

  if ($debug) {
    print "ac_given_srcdir = $ac_given_srcdir\n";
    print "depth           = $depth\n";
  }
  if ($ac_given_srcdir =~ /^\./ and $depth ne '.') {
    my $quoted_depth = quotemeta($depth);
    $ac_given_srcdir =~ s|^$quoted_depth/?||;
  }
  if ($debug) {
    print "ac_given_srcdir = $ac_given_srcdir\n";
  }
  $ac_given_srcdir = '.' if $ac_given_srcdir eq '';
  return $ac_given_srcdir;
}

1;
###########################################################################
## perldoc 
###########################################################################
__END__

=head1 NAME

make-makefile - Generate a Makefile from a F<Makefile.in> template

=head1 SYNOPSIS

make-makefile [--top t] [--obj o] [--depth d] foo/bar/Makefile.in tans/fans/Makefile foo/bar

=head1 DESCRIPTION

Given options and makefile path arguments determine path to the template
F<Makefile.in> beneath a source directory and path to generated F<Makefile>
beneath $MOZ_OBJDIR.  DEPTH from destination directory to the 'root' will
also be determined.  F<Makefile.in> will be read in, template strings of the
gorm @token@ will be replaced with derived values and a generated makefile
will be written out as F<Makefile>.

Makefile DEPTH= can be determined in a few different ways:
  o The string C<DEPTH=../../..> may be embedded within F<Makefile.in>.
  o Search parent directories for F<Makefile.in> and use it to assign the child.


=head2 Option List

=over 4

=item --chdir

Move to this directory before doing anything else

=item -d, --depth

Explicitly specify the relative path from directory containing Makefile.in
to the top sandbox directory.  memory/makefile, DEPTH=../.., js/src/config, DEPTH=..

=item --enhanced

Use alternate/simplified path construction when options --top and --obj are
passed.  This feature will be used by container makefiles to support makefile
generation while cd'd into the sandbox top directory.

=item -t, --top

Path the root of a development sandbox.

=item --obj

Path to object directory where generated makefile will be written ($MOZ_OBJDIR).

=item --ptop

Print top source dir

=back


=head2 Options List DEBUG

=over 4

=item --bench

Enable script benchmarking, report elapsed runtime.

=item --debug

Enable script debug mode.

=back


=head2 Options List --NO-

=over 4

=item --no-badtokens (wip)

Handle unexpanded @token@ makefile tokens as an error condition.
Do not rely on system(config.status) to externally supply values.

=item --no-excludes

Ignore file entries on the exclusion list, generate everything.

=item --no-warnings

Warnings are handled as an error condition.

=back


=head2 Examples

=over 4

=item * make-makefile -t /mozilla/nightly -d . memory/mozalloc

cd $MOZ_OBJDIR;
--top and --depth are explicitly set for generting memory/mozalloc/Makefile.

=item * make-makefile -t /mozilla/nightly -d ../../../.. html5lib_tree_construction/Makefile

cd $MOZ_OBJDIR/parser/htmlparser/tests/mochitest

--top and --depth are explicitly set for generting a makefile from within
a subdirectory of $MOZ_OBJDIR

=item * make-makefile --top /mozilla/nightly --obj /mozilla/nightly/obj memory/mozalloc

With --top and --obj explicitly set generate $MOZ_OBJDIR/memory/mozalloc/Makefile
while sitting in the sandbox root.

=back


=head2 Work In Progress

=over 4

=item --no-badtokens

Fail on unexpanded @foo@ makefile tokens.  Any tokens that can be expanded
directly by make-makefile will avoid config.status shell overhead.

=item Depth from delta(--obj, --top)

If DEPTH= has not been embedded within a makefile the value could
be set directly if --top and --obj are specified and the paths overlap.

=back


=head1 SEE ALSO

L<config/rules.mk>

=cut