package AutomakeWorkspaceCreator; # ************************************************************ # Description : A Automake Workspace (Makefile) creator # Author : J.T. Conklin & Steve Huston # Create Date : 5/13/2002 # ************************************************************ # ************************************************************ # Pragmas # ************************************************************ use strict; use File::Copy; use AutomakeProjectCreator; use MakePropertyBase; use WorkspaceCreator; use WorkspaceHelper; use vars qw(@ISA); @ISA = qw(MakePropertyBase WorkspaceCreator); # ************************************************************ # Data Section # ************************************************************ my $acfile = 'configure.ac'; my $acmfile = 'configure.ac.Makefiles'; # ************************************************************ # Subroutine Section # ************************************************************ sub compare_output { return 1; } ## Can't cache as some intermediate project files are deleted ## and must be regenerated if a project is regenerated. sub default_cacheok { return 0; } sub files_are_different { my($self, $old, $new) = @_; my $diff = 1; if (-r $old) { my $lh = new FileHandle(); my $rh = new FileHandle(); if (open($lh, $old)) { if (open($rh, $new)) { my $done = 0; my $lline; my $rline; $diff = 0; do { $lline = <$lh>; $rline = <$rh>; if (defined $lline) { if (defined $rline) { $lline =~ s/#.*//; $rline =~ s/#.*//; $diff = 1 if ($lline ne $rline); } else { $done = 1; } } else { $diff = 1 if (defined $rline); $done = 1; } } while(!$done && !$diff); close($rh); } close($lh); } } return $diff; } sub workspace_file_name { return $_[0]->get_modified_workspace_name('Makefile', '.am'); } sub workspace_per_project { #my $self = shift; return 1; } sub pre_workspace { my($self, $fh) = @_; my $crlf = $self->crlf(); $self->print_workspace_comment($fh, '## Process this file with automake to create Makefile.in', $crlf, '##', $crlf, '## ', '$', 'Id', '$', $crlf, '##', $crlf, '## This file was generated by MPC. Any changes made directly to', $crlf, '## this file will be lost the next time it is generated.', $crlf, '##', $crlf, '## MPC Command:', $crlf, '## ', $self->create_command_line_string($0, @ARGV), $crlf, $crlf); } sub write_comps { my($self, $fh, $creator, $toplevel) = @_; my $projects = $self->get_projects(); my @list = $self->sort_dependencies($projects); my $crlf = $self->crlf(); my %unique; my @dirs; my @locals; my %proj_dir_seen; my $have_subdirs = 0; my $outdir = $self->get_outdir(); my $cond = '--'; ## This step writes a configure.ac.Makefiles list into the starting ## directory. The list contains of all the Makefiles generated down ## the tree. configure.ac can include this to get an up-to-date list ## of all the involved Makefiles. my $mfh; my $makefile; if ($toplevel) { my $need_acmfile = 1; if (! -e "$outdir/$acfile") { my $acfh = new FileHandle(); if (open($acfh, ">$outdir/$acfile")) { print $acfh "AC_INIT(", $self->get_workspace_name(), ", 1.0)$crlf", "AM_INIT_AUTOMAKE([1.9])$crlf", $crlf, "AC_PROG_CXX$crlf", "AC_PROG_CXXCPP$crlf", "AC_PROG_LIBTOOL$crlf", $crlf; my $fp = $creator->get_feature_parser(); my $features = $fp->get_names(); my %assoc = %{$self->get_associated_projects()}; foreach my $feature (sort @$features) { print $acfh 'AM_CONDITIONAL(BUILD_', uc($feature), ', ', ($fp->get_value($feature) ? 'true' : 'false'), ')', $crlf; delete $assoc{$feature}; } foreach my $akey (keys %assoc) { print $acfh 'AM_CONDITIONAL(BUILD_', uc($akey), ', true)', $crlf if ($akey ne $cond); } print $acfh $crlf, "m4_include([$acmfile])$crlf", $crlf, "AC_OUTPUT$crlf"; close($acfh); } } else { $self->information("$acfile already exists."); $need_acmfile = !$self->edit_config_ac("$outdir/$acfile", \@list); } if ($need_acmfile) { unlink("$outdir/$acmfile"); $mfh = new FileHandle(); open($mfh, ">$outdir/$acmfile"); ## The top-level is never listed as a dependency, so it needs to be ## added explicitly. $makefile = $self->mpc_basename($self->get_current_output_name()); $makefile =~ s/\.am$//; print $mfh "AC_CONFIG_FILES([ $makefile ])$crlf"; $proj_dir_seen{'.'} = 1; } } ## If we're writing a configure.ac.Makefiles file, every seen project ## goes into it. Since we only write this at the starting directory ## level, it'll include all projects processed at this level and below. foreach my $dep (@list) { if ($mfh) { ## There should be a Makefile at each level, but it's not a project, ## it's a workspace; therefore, it's not in the list of projects. ## Since we're consolidating all the project files into one workspace ## Makefile.am per directory level, be sure to add that Makefile.am ## entry at each level there's a project dependency. my $dep_dir = $self->mpc_dirname($dep); if (!defined $proj_dir_seen{$dep_dir}) { $proj_dir_seen{$dep_dir} = 1; ## If there are directory levels between project-containing ## directories (for example, at this time in ## ACE_wrappers/apps/JAWS/server, there are no projects at the ## apps or apps/JAWS level) we need to insert the Makefile ## entries for the levels without projects. They won't be listed ## in @list but are needed for make to traverse intervening directory ## levels down to where the project(s) to build are. my @dirs = split /\//, $dep_dir; my $inter_dir = ""; foreach my $dep (@dirs) { $inter_dir .= $dep; if (!defined $proj_dir_seen{$inter_dir}) { $proj_dir_seen{$inter_dir} = 1; print $mfh "AC_CONFIG_FILES([ $inter_dir/$makefile ])$crlf"; } $inter_dir .= '/'; } print $mfh "AC_CONFIG_FILES([ $dep_dir/$makefile ])$crlf"; } } ## Get a unique list of next-level directories for SUBDIRS. ## To make sure we keep the dependencies correct, insert the '.' for ## any local projects in the proper place. Remember if any subdirs ## are seen to know if we need a SUBDIRS entry generated. my $dir = $self->get_first_level_directory($dep); if (!defined $unique{$dir}) { $unique{$dir} = 1; unshift(@dirs, $dir); } if ($dir eq '.') { ## At each directory level, each project is written into a separate ## Makefile..am file. To bring these back into the build ## process, they'll be sucked back into the workspace Makefile.am file. ## Remember which ones to pull in at this level. unshift(@locals, $dep); } else { $have_subdirs = 1; } } close($mfh) if ($mfh); # The Makefile..am files append values to build target macros # for each program/library to build. When using conditionals, however, # a plain empty assignment is done outside the conditional to be sure # that each append can be done regardless of the condition test. Because # automake fails if the first isn't a plain assignment, we need to resolve # these situations when combining the files. The code below makes sure # that there's always a plain assignment, whether it's one outside a # conditional or the first append is changed to a simple assignment. # # We should consider extending this to support all macros that match # automake's uniform naming convention. A true perl wizard probably # would be able to do this in a single line of code. my %seen; my %conditional_targets; my %unconditional_targets; my %first_instance_unconditional; my $installable_headers; my $installable_pkgconfig; my $includedir; my $project_name; my $status = 1; my $errorString; ## To avoid unnecessarily emitting blank assignments, rip through the ## Makefile..am files and check for conditions. if (@locals) { my $pfh = new FileHandle(); foreach my $local (reverse @locals) { if ($local =~ /Makefile\.(.*)\.am/) { $project_name = $1; } else { $project_name = 'nobase'; } if (open($pfh, "$outdir/$local")) { my $in_condition = 0; my $regok = $self->escape_regex_special($project_name); my $inc_pattern = $regok . '_include_HEADERS'; my $pkg_pattern = $regok . '_pkginclude_HEADERS'; while (<$pfh>) { # Don't look at comments next if (/^#/); $in_condition++ if (/^if\s*/); $in_condition-- if (/^endif\s*/); if ( /(^[a-zA-Z][a-zA-Z0-9_]*_(PROGRAMS|LIBRARIES|LTLIBRARIES|LISP|PYTHON|JAVA|SCRIPTS|DATA|SOURCES|HEADERS|MANS|TEXINFOS|LIBADD|LDADD|DEPENDENCIES))\s*\+=\s*/ || /(^CLEANFILES)\s*\+=\s*/ || /(^EXTRA_DIST)\s*\+=\s*/ ) { if ($in_condition) { $conditional_targets{$1}++; } else { if (! $seen{$1} ) { $first_instance_unconditional{$1} = 1; } $unconditional_targets{$1}++; } $seen{$1} = 1; $installable_pkgconfig= 1 if (/^pkgconfig_DATA/); $installable_headers = 1 if (/^$inc_pattern\s*\+=\s*/ || /^$pkg_pattern\s*\+=\s*/); } elsif (/includedir\s*=\s*(.*)/) { $includedir = $1; } } close($pfh); $in_condition = 0; } else { $errorString = "Unable to open $local for reading."; $status = 0; last; } } } # # Clear seen hash # %seen = (); ## Print out the Makefile.am. my $wsHelper = WorkspaceHelper::get($self); my $convert_header_name; if ($status && ((!defined $includedir && $installable_headers) || $installable_pkgconfig)) { if (!defined $includedir && $installable_headers) { my $incdir = $wsHelper->modify_value('includedir', $self->get_includedir()); if ($incdir ne '') { print $fh "includedir = \@includedir\@$incdir$crlf"; $convert_header_name = 1; } } if ($installable_pkgconfig) { print $fh "pkgconfigdir = \@libdir\@/pkgconfig$crlf"; } print $fh $crlf; } if ($status && @locals) { ($status, $errorString) = $wsHelper->write_settings($self, $fh, @locals); } ## Create the SUBDIRS setting. If there are associated projects, then ## we will also set up conditionals for it as well. if ($status && $have_subdirs == 1) { my $assoc = $self->get_associated_projects(); my @aorder; my %afiles; my $entry = " \\$crlf "; print $fh 'SUBDIRS ='; foreach my $dir (reverse @dirs) { my $found; foreach my $akey (keys %$assoc) { if (defined $$assoc{$akey}->{$dir}) { if ($akey eq $cond) { if ($toplevel) { print $fh $entry, '@', $dir, '@'; $found = 1; } } else { push(@aorder, $akey); push(@{$afiles{$akey}}, $dir); $found = 1; } last; } elsif ($toplevel && defined $$assoc{$akey}->{uc($dir)} && $akey eq $cond) { print $fh $entry, '@', uc($dir), '@'; $found = 1; last; } } print $fh $entry, $dir if (!$found); } print $fh $crlf; my $second = 1; foreach my $aorder (@aorder) { if (defined $afiles{$aorder}) { $second = undef; print $fh $crlf, 'if BUILD_', uc($aorder), "\n", 'SUBDIRS +='; foreach my $afile (@{$afiles{$aorder}}) { print $fh " $afile"; } delete $afiles{$aorder}; print $fh $crlf, 'endif', $crlf; } } print $fh $crlf if ($second); } ## Now, for each target used in a conditional, emit a blank assignment ## and mark that we've seen that target to avoid changing the += to = ## as the individual files are pulled in. if ($status && %conditional_targets) { my $primary; my $count; while ( ($primary, $count) = each %conditional_targets) { if (! $first_instance_unconditional{$primary} && ($unconditional_targets{$primary} || ($count > 1))) { print $fh "$primary =$crlf"; $seen{$primary} = 1; } } print $fh $crlf; } ## Take the local Makefile..am files and insert each one here, ## then delete it. if ($status && @locals) { my $pfh = new FileHandle(); my $liblocs = $self->get_lib_locations(); my $here = $self->getcwd(); my $start = $self->getstartdir(); my %explicit; foreach my $local (reverse @locals) { if (open($pfh, "$outdir/$local")) { print $fh "## $local", $crlf; my $look_for_libs = 0; my $prev_line; my $in_explicit; while (<$pfh>) { # Don't emit comments next if (/^#/); # Check for explicit targets if ($in_explicit) { if (/^\t/) { next; } else { $in_explicit = undef; } } elsif ($prev_line !~ /\\$/ && /^([\w\/\.\-\s]+):/) { my $target = $1; $target =~ s/^\s+//; $target =~ s/\s+$//; if (defined $explicit{$target}) { $in_explicit = 1; next; } else { $explicit{$target} = 1; } } if ($convert_header_name) { if ($local =~ /Makefile\.(.*)\.am/) { $project_name = $1; } else { $project_name = 'nobase'; } my $regok = $self->escape_regex_special($project_name); my $inc_pattern = $regok . '_include_HEADERS'; my $pkg_pattern = $regok . '_pkginclude_HEADERS'; if (/^$inc_pattern\s*\+=\s*/ || /^$pkg_pattern\s*\+=\s*/) { $_ =~ s/^$regok/nobase/; } } if ( /(^[a-zA-Z][a-zA-Z0-9_]*_(PROGRAMS|LIBRARIES|LTLIBRARIES|LISP|PYTHON|JAVA|SCRIPTS|DATA|SOURCES|HEADERS|MANS|TEXINFOS|LIBADD|LDADD|DEPENDENCIES))\s*\+=\s*/ || /(^CLEANFILES)\s*\+=\s*/ || /(^EXTRA_DIST)\s*\+=\s*/ ) { if (!defined ($seen{$1})) { $seen{$1} = 1; s/\+=/=/; } } ## This scheme relies on automake.mpd emitting the 'la' libs first. ## Look for all the libXXXX.la, find out where they are located ## relative to the start of the MPC run, and relocate the reference ## to that location under $top_builddir. Unless the referred-to ## library is in the current directory, then leave it undecorated ## so the automake-generated dependency orders the build correctly. if ($look_for_libs) { my @libs = /\s+(lib(\w+).la)/gm; my $libcount = @libs / 2; for(my $i = 0; $i < $libcount; ++$i) { my $libfile = $libs[$i * 2]; my $libname = $libs[$i * 2 + 1]; my $reldir = $$liblocs{$libname}; ## If we could not find a relative directory for this ## library, it may be that it is a decorated library name. ## We will search for an approximate match. if (!defined $reldir) { my $tmpname = $libname; while($tmpname ne '') { $tmpname = substr($tmpname, 0, length($tmpname) - 1); if (defined $$liblocs{$tmpname}) { $reldir = $$liblocs{$tmpname}; $self->warning("Relative directory for $libname " . "was approximated with $tmpname."); last; } } } if (defined $reldir) { my $append = ($reldir eq '' ? '' : "/$reldir"); if ("$start$append" ne $here) { my $mod = $wsHelper->modify_libpath($_, $reldir, $libfile); if (defined $mod) { $_ = $mod; } else { s/$libfile/\$(top_builddir)$append\/$libfile/; } } } else { my $mod = $wsHelper->modify_libpath($_, $reldir, $libfile); if (defined $mod) { $_ = $mod; } else { $self->warning("No reldir found for $libname ($libfile)."); } } } $look_for_libs = 0 if ($libcount == 0); } $look_for_libs = 1 if (/_LDADD = \\$/ || /_LIBADD = \\$/); ## I have introduced a one line delay so that I can simplify ## the automake template. If our current line is empty, then ## we will remove the trailing backslash before printing the ## previous line. Automake is horribly unforgiving so we must ## avoid this situation at all cost. if (defined $prev_line) { $prev_line =~ s/\s*\\$// if ($_ =~ /^\s*$/); print $fh $prev_line; } $prev_line = $_; } ## The one line delay requires that we print out the previous ## line (if there was one) when we reach the end of the file. if (defined $prev_line) { $prev_line =~ s/\s*\\$//; print $fh $prev_line; } close($pfh); unlink("$outdir/$local"); print $fh $crlf; } else { $errorString = "Unable to open $local for reading."; $status = 0; last; } } } ## If this is the top-level Makefile.am, it needs the directives to pass ## autoconf/automake flags down the tree when running autoconf. ## *** This may be too closely tied to how we have things set up in ACE, ## even though it's recommended practice. *** if ($status && $toplevel) { my $m4inc = '-I m4'; print $fh $crlf, 'ACLOCAL = @ACLOCAL@', $crlf, 'ACLOCAL_AMFLAGS = ', (defined $wsHelper ? $wsHelper->modify_value('amflags', $m4inc) : $m4inc), $crlf, 'AUTOMAKE_OPTIONS = foreign', $crlf, $crlf, (defined $wsHelper ? $wsHelper->modify_value('extra', '') : ''); } ## Finish up with the cleanup specs. if ($status && @locals) { ## There is no reason to emit this if there are no local targets. ## An argument could be made that it shouldn't be emitted in any ## case because it could be handled by CLEANFILES or a verbatim ## clause. print $fh '## Clean up template repositories, etc.', $crlf, 'clean-local:', $crlf, "\t-rm -f *~ *.bak *.rpo *.sym lib*.*_pure_* core core.*", $crlf, "\t-rm -f gcctemp.c gcctemp so_locations *.ics", $crlf, "\t-rm -rf cxx_repository ptrepository ti_files", $crlf, "\t-rm -rf templateregistry ir.out", $crlf, "\t-rm -rf ptrepository SunWS_cache Templates.DB", $crlf; } return $status, $errorString; } sub get_includedir { my $self = shift; my $value = $self->getcwd(); my $start = $self->getstartdir(); ## Take off the starting directory $value =~ s/\Q$start\E//; return $value; } sub edit_config_ac { my($self, $file, $files) = @_; my $fh = new FileHandle(); my $status = 0; if (open($fh, $file)) { my $crlf = $self->crlf(); my @in; my @lines; my $assoc = $self->get_associated_projects(); my $indent = ''; my %proj_dir_seen; my $in_config_files = 0; while(<$fh>) { my $line = $_; push(@lines, $line); ## Remove comments and trailing space $line =~ s/\bdnl\s+.*//; $line =~ s/\s+$//; if ($line eq '') { } elsif ($line =~ /^\s*if\s+test\s+["]?([^"]+)["]?\s*=\s*\w+;\s*then/) { ## Entering an if test, save the name my $name = $1; $name =~ s/\s+$//; $name =~ s/.*_build_//; push(@in, $name); } elsif ($line =~ /^\s*if\s+test\s+-d\s+(.+);\s*then/) { ## Entering an if test -d, save the name my $name = $1; $name =~ s/\s+$//; $name =~ s/\$srcdir\///; push(@in, $name); } elsif ($line =~ /^\s*fi$/) { pop(@in); } elsif ($line =~ /^(\s*AC_CONFIG_FILES\s*\(\s*\[)/) { ## Entering an AC_CONFIG_FILES section, start ignoring the entries pop(@lines); push(@lines, "$1\n"); $indent = ' '; if ($lines[$#lines] =~ /^(\s+)/) { $indent .= $1; } $in_config_files = 1; } elsif ($in_config_files) { if ($line =~ /(.*)\]\s*\).*/) { ## We've reached the end of the AC_CONFIG_FILES section my $olast = pop(@lines); if ($olast =~ /^[^\s]+(\s*\]\s*\).*)/) { $olast = $1; } ## Add in the Makefiles for this configuration if ($#in < 0 && !defined $proj_dir_seen{'.'}) { push(@lines, $indent . 'Makefile' . $crlf); $proj_dir_seen{'.'} = 1; } foreach my $dep (@$files) { ## First things first, see if we've already seen this ## project's directory. If we have, then there's nothing ## else we need to do with it. my $dep_dir = $self->mpc_dirname($dep); if (!defined $proj_dir_seen{$dep_dir}) { my $ok = 1; my @dirs = split(/\//, $dep_dir); my $base = $dep; if ($base =~ s/\/.*//) { my $found = 0; foreach my $akey (keys %$assoc) { if (defined $$assoc{$akey}->{$base} || defined $$assoc{$akey}->{uc($base)}) { if ($#in >= 0) { if (index($base, $in[0]) >= 0) { if ($#in >= 1) { $found = 1; for(my $i = 0; $i <= $#in; $i++) { if (!defined $dirs[$i] || index($dirs[$i], $in[$i]) < 0) { $found = 0; last; } } } else { ## We need to see into the future here. :-) ## If the second element of @dirs matches an ## association key, we'll guess that there will ## be a "build" section devoted to it. if (!defined $dirs[1] || !defined $$assoc{$dirs[1]}) { $found = 1; } } } } else { $found = 1; } last; } } if ($#in >= 0) { $ok = $found; } else { $ok = !$found; } } if ($ok) { $proj_dir_seen{$dep_dir} = 1; my $inter_dir = ''; foreach my $dep (@dirs) { $inter_dir .= $dep; if (!defined $proj_dir_seen{$inter_dir}) { $proj_dir_seen{$inter_dir} = 1; push(@lines, $indent . $inter_dir . "/Makefile$crlf"); } $inter_dir .= '/'; } push(@lines, $indent . $dep_dir . "/Makefile$crlf"); } } } push(@lines, $olast); $in_config_files = 0; } else { ## Ignore the entry pop(@lines); } } } close($fh); ## Make a backup and create the new file my $backup = $file . '.bak'; if (copy($file, $backup)) { my @buf = stat($file); if (defined $buf[8] && defined $buf[9]) { utime($buf[8], $buf[9], $backup); } if (open($fh, ">$file")) { foreach my $line (@lines) { print $fh $line; } close($fh); $status = 1; } } else { $self->warning("Unable to create backup file: $backup"); } } return $status; } 1;