Make errors in option parsing actually _do_ something in interactive mode.
[sgt/puzzles] / mkfiles.pl
index 8c23127..cefd365 100755 (executable)
 #    mainly because I was too scared to go anywhere near it.
 #  - sbcsgen.pl is still run at startup.
 
-use FileHandle;
+# Other things undone:
+#  - special-define objects (foo.o[PREPROCSYMBOL]) are not
+#    supported in the mac or vcproj makefiles.
+
+use IO::Handle;
 use Cwd;
 
-open IN, "Recipe" or do {
+@filestack = ();
+$in = new IO::Handle;
+open $in, "Recipe" or do {
     # We want to deal correctly with being run from one of the
     # subdirs in the source tree. So if we can't find Recipe here,
     # try one level up.
     chdir "..";
-    open IN, "Recipe" or die "unable to open Recipe file\n";
+    open $in, "Recipe" or die "unable to open Recipe file\n";
 };
+push @filestack, $in;
 
 # HACK: One of the source files in `charset' is auto-generated by
 # sbcsgen.pl. We need to generate that _now_, before attempting
@@ -39,26 +46,50 @@ $project_name = "project"; # this is a good enough default
 %programs = (); # maps prog name + type letter to listref of objects/resources
 %groups = (); # maps group name to listref of objects/resources
 
-while (<IN>) {
+@allobjs = (); # all object file names
+
+readinput: while (1) {
+  while (not defined ($_ = <$in>)) {
+    close $in;
+    last readinput if 0 == scalar @filestack;
+    $in = pop @filestack;
+  }
+
+  chomp;
+  split;
+
   # Skip comments (unless the comments belong, for example because
   # they're part of a diversion).
   next if /^\s*#/ and !defined $divert;
 
-  chomp;
-  split;
   if ($_[0] eq "!begin" and $_[1] eq "help") { $divert = \$help; next; }
   if ($_[0] eq "!end") { $divert = undef; next; }
   if ($_[0] eq "!name") { $project_name = $_[1]; next; }
   if ($_[0] eq "!srcdir") { push @srcdirs, $_[1]; next; }
   if ($_[0] eq "!makefile" and &mfval($_[1])) { $makefiles{$_[1]}=$_[2]; next;}
+  if ($_[0] eq "!specialobj" and &mfval($_[1])) { $specialobj{$_[1]}->{$_[2]} = 1; next;}
   if ($_[0] eq "!begin") {
-      if (&mfval($_[1])) {
+      if ($_[1] =~ /^>(.*)/) {
+         $divert = \$auxfiles{$1};
+      } elsif (&mfval($_[1])) {
          $divert = \$makefile_extra{$_[1]};
-      } else {
-         $divert = \$dummy;
       }
       next;
   }
+  if ($_[0] eq "!include") {
+      @newfiles = ();
+      for ($i = 1; $i <= $#_; $i++) {
+         push @newfiles, (sort glob $_[$i]);
+      }
+      for ($i = $#newfiles; $i >= 0; $i--) {
+         $file = $newfiles[$i];
+         $f = new IO::Handle;
+         open $f, "<$file" or die "unable to open include file '$file'\n";
+         push @filestack, $f;
+      }
+      $in = $filestack[$#filestack];
+      next;
+  }
   # If we're gathering help text, keep doing so.
   if (defined $divert) { ${$divert} .= "$_\n"; next; }
   # Ignore blank lines.
@@ -73,6 +104,11 @@ while (<IN>) {
     $prog = undef;
     die "$.: unexpected + line\n" if !defined $lastlistref;
   } elsif ($_[1] eq "=") {
+    $groups{$_[0]} = [];
+    $listref = $groups{$_[0]};
+    $prog = undef;
+    shift @objs; # eat the group name
+  } elsif ($_[1] eq "+=") {
     $groups{$_[0]} = [] if !defined $groups{$_[0]};
     $listref = $groups{$_[0]};
     $prog = undef;
@@ -82,7 +118,7 @@ while (<IN>) {
     $prog = $_[0];
     shift @objs; # eat the program name
   } else {
-    die "$.: unrecognised line type\n";
+    die "$.: unrecognised line type: '$_'\n";
   }
   shift @objs; # eat the +, the = or the :
 
@@ -91,10 +127,31 @@ while (<IN>) {
     if ($groups{$i}) {
       foreach $j (@{$groups{$i}}) { unshift @objs, $j; }
     } elsif (($i eq "[G]" or $i eq "[C]" or $i eq "[M]" or
-              $i eq "[X]" or $i eq "[U]") and defined $prog) {
-      $type = substr($i,1,1);
+              $i eq "[X]" or $i eq "[U]" or $i eq "[MX]") and defined $prog) {
+      $type = substr($i,1,(length $i)-2);
     } else {
-      push @$listref, $i;
+      if ($i =~ /\?$/) {
+       # Object files with a trailing question mark are optional:
+       # the build can proceed fine without them, so we only use
+       # them if their primary source files are present.
+       $i =~ s/\?$//;
+       $i = undef unless defined &finddep($i);
+      } elsif ($i =~ /\|/) {
+       # Object file descriptions containing a vertical bar are
+       # lists of choices: we use the _first_ one whose primary
+       # source file is present.
+       @options = split /\|/, $i;
+       $j = undef;
+       foreach $k (@options) {
+         $j=$k, last if defined &finddep($k);
+       }
+       die "no alternative found for $i\n" unless defined $j;
+       $i = $j;
+      }
+      if (defined $i) {
+       push @$listref, $i;
+       push @allobjs, $i;
+      }
     }
   }
   if ($prog and $type) {
@@ -105,7 +162,40 @@ while (<IN>) {
   $lastlistref = $listref;
 }
 
-close IN;
+foreach $aux (sort keys %auxfiles) {
+    open AUX, ">$aux";
+    print AUX $auxfiles{$aux};
+    close AUX;
+}
+
+# Find object file names with predefines (in square brackets after
+# the module name), and decide on actual object names for them.
+foreach $i (@allobjs) {
+  if ($i !~ /\[/) {
+    $objname{$i} = $i;
+    $srcname{$i} = $i;
+    $usedobjname{$i} = 1;
+  }
+}
+foreach $i (@allobjs) {
+  if ($i =~ /^(.*)\[([^\]]*)/) {
+    $defs{$i} = [ split ",",$2 ];
+    $srcname{$i} = $s = $1;
+    $index = 1;
+    while (1) {
+      $maxlen = length $s;
+      $maxlen = 8 if $maxlen < 8;
+      $chop = $maxlen - length $index;
+      $chop = length $s if $chop > length $s;
+      $chop = 0 if $chop < 0;
+      $name = substr($s, 0, $chop) . $index;
+      $index++, next if $usedobjname{$name};
+      $objname{$i} = $name;
+      $usedobjname{$name} = 1;
+      last;
+    }
+  }
+}
 
 # Now retrieve the complete list of objects and resource files, and
 # construct dependency data for them. While we're here, expand the
@@ -120,25 +210,11 @@ foreach $i (@prognames) {
   @list = grep { $status = ($prev ne $_); $prev=$_; $status }
           sort @{$programs{$i}};
   $programs{$i} = [@list];
-  foreach $j (@list) {
-    # Dependencies for "x" start with "x.c".
-    # Dependencies for "x.res" start with "x.rc".
-    # Dependencies for "x.rsrc" start with "x.r".
-    # Both types of file are pushed on the list of files to scan.
-    # Libraries (.lib) don't have dependencies at all.
-    if ($j =~ /^(.*)\.res$/) {
-      $file = "$1.rc";
-      $depends{$j} = [$file];
-      push @scanlist, $file;
-    } elsif ($j =~ /^(.*)\.rsrc$/) {
-      $file = "$1.r";
-      $depends{$j} = [$file];
-      push @scanlist, $file;
-    } elsif ($j =~ /\.lib$/) {
-      # libraries don't have dependencies
-    } else {
-      $file = "$j.c";
-      $depends{$j} = [$file];
+  foreach $jj (@list) {
+    $j = $srcname{$jj};
+    $file = &finddep($j);
+    if (defined $file) {
+      $depends{$jj} = [$file];
       push @scanlist, $file;
     }
   }
@@ -208,7 +284,7 @@ sub mfval($) {
     # Returns true if the argument is a known makefile type. Otherwise,
     # prints a warning and returns false;
     if (grep { $type eq $_ }
-       ("vc","vcproj","cygwin","borland","lcc","gtk","mpw")) {
+       ("vc","vcproj","cygwin","borland","lcc","gtk","mpw","osx")) {
            return 1;
        }
     warn "$.:unknown makefile type '$type'\n";
@@ -230,24 +306,53 @@ sub dirpfx {
 
 sub findfile {
   my ($name) = @_;
-  my $dir, $i, $outdir = "";
+  my $dir;
+  my $i;
+  my $outdir = undef;
   unless (defined $findfilecache{$name}) {
     $i = 0;
     foreach $dir (@srcdirs) {
       $outdir = $dir, $i++ if -f "$dir$name";
     }
     die "multiple instances of source file $name\n" if $i > 1;
-    $findfilecache{$name} = $outdir . $name;
+    $findfilecache{$name} = (defined $outdir ? $outdir . $name : undef);
   }
   return $findfilecache{$name};
 }
 
+sub finddep {
+  my $j = shift @_;
+  my $file;
+  # Find the first dependency of an object.
+
+  # Dependencies for "x" start with "x.c" or "x.m" (depending on
+  # which one exists).
+  # Dependencies for "x.res" start with "x.rc".
+  # Dependencies for "x.rsrc" start with "x.r".
+  # Both types of file are pushed on the list of files to scan.
+  # Libraries (.lib) don't have dependencies at all.
+  if ($j =~ /^(.*)\.res$/) {
+    $file = "$1.rc";
+  } elsif ($j =~ /^(.*)\.rsrc$/) {
+    $file = "$1.r";
+  } elsif ($j !~ /\./) {
+    $file = "$j.c";
+    $file = "$j.m" unless &findfile($file);
+  } else {
+    # For everything else, we assume it's its own dependency.
+    $file = $j;
+  }
+  $file = undef unless &findfile($file);
+  return $file;
+}
+
 sub objects {
   my ($prog, $otmpl, $rtmpl, $ltmpl, $prefix, $dirsep) = @_;
   my @ret;
   my ($i, $x, $y);
   @ret = ();
-  foreach $i (@{$programs{$prog}}) {
+  foreach $ii (@{$programs{$prog}}) {
+    $i = $objname{$ii};
     $x = "";
     if ($i =~ /^(.*)\.(res|rsrc)/) {
       $y = $1;
@@ -255,7 +360,7 @@ sub objects {
     } elsif ($i =~ /^(.*)\.lib/) {
       $y = $1;
       ($x = $ltmpl) =~ s/X/$y/;
-    } else {
+    } elsif ($i !~ /\./) {
       ($x = $otmpl) =~ s/X/$i/;
     }
     push @ret, $x if $x ne "";
@@ -263,6 +368,20 @@ sub objects {
   return join " ", @ret;
 }
 
+sub special {
+  my ($prog, $suffix) = @_;
+  my @ret;
+  my ($i, $x, $y);
+  @ret = ();
+  foreach $ii (@{$programs{$prog}}) {
+    $i = $objname{$ii};
+    if (substr($i, (length $i) - (length $suffix)) eq $suffix) {
+      push @ret, $i;
+    }
+  }
+  return join " ", @ret;
+}
+
 sub splitline {
   my ($line, $width, $splitchar) = @_;
   my ($result, $len);
@@ -270,7 +389,8 @@ sub splitline {
   $splitchar = (defined $splitchar ? $splitchar : '\\');
   while (length $line > $len) {
     $line =~ /^(.{0,$len})\s(.*)$/ or $line =~ /^(.{$len,}?\s(.*)$/;
-    $result .= $1 . " ${splitchar}\n\t\t";
+    $result .= $1;
+    $result .= " ${splitchar}\n\t\t" if $2 ne '';
     $line = $2;
     $len = 60;
   }
@@ -283,7 +403,9 @@ sub deps {
   my @deps, @ret;
   @ret = ();
   $depchar ||= ':';
-  foreach $i (sort keys %depends) {
+  foreach $ii (sort keys %depends) {
+    $i = $objname{$ii};
+    next if $specialobj{$mftyp}->{$i};
     if ($i =~ /^(.*)\.(res|rsrc)/) {
       next if !defined $rtmpl;
       $y = $1;
@@ -291,13 +413,13 @@ sub deps {
     } else {
       ($x = $otmpl) =~ s/X/$i/;
     }
-    @deps = @{$depends{$i}};
+    @deps = @{$depends{$ii}};
     @deps = map {
       $_ = &findfile($_);
       s/\//$dirsep/g;
       $_ = $prefix . $_;
     } @deps;
-    push @ret, {obj => $x, deps => [@deps]};
+    push @ret, {obj => $x, deps => [@deps], defs => $defs{$ii}};
   }
   return @ret;
 }
@@ -309,7 +431,7 @@ sub prognames {
   @ret = ();
   foreach $n (@prognames) {
     ($prog, $type) = split ",", $n;
-    push @ret, $n if index($types, $type) >= 0;
+    push @ret, $n if index(":$types:", ":$type:") >= 0;
   }
   return @ret;
 }
@@ -321,7 +443,7 @@ sub progrealnames {
   @ret = ();
   foreach $n (@prognames) {
     ($prog, $type) = split ",", $n;
-    push @ret, $prog if index($types, $type) >= 0;
+    push @ret, $prog if index(":$types:", ":$type:") >= 0;
   }
   return @ret;
 }
@@ -330,7 +452,7 @@ sub manpages {
   my ($types,$suffix) = @_;
 
   # assume that all UNIX programs have a man page
-  if($suffix eq "1" && $types =~ /X/) {
+  if($suffix eq "1" && $types =~ /:X:/) {
     return map("$_.1", &progrealnames($types));
   }
   return ();
@@ -339,6 +461,7 @@ sub manpages {
 # Now we're ready to output the actual Makefiles.
 
 if (defined $makefiles{'cygwin'}) {
+    $mftyp = 'cygwin';
     $dirpfx = &dirpfx($makefiles{'cygwin'}, "/");
 
     ##-- CygWin makefile
@@ -364,24 +487,17 @@ if (defined $makefiles{'cygwin'}) {
     "# RCINC = --include-dir c:\\cygwin\\include\\\n".
     "\n".
     &splitline("CFLAGS = -mno-cygwin -Wall -O2 -D_WINDOWS -DDEBUG -DWIN32S_COMPAT".
-      " -D_NO_OLDNAMES -DNO_MULTIMON " .
+      " -D_NO_OLDNAMES -DNO_MULTIMON -DNO_HTMLHELP " .
               (join " ", map {"-I$dirpfx$_"} @srcdirs)) .
               "\n".
     "LDFLAGS = -mno-cygwin -s\n".
     &splitline("RCFLAGS = \$(RCINC) --define WIN32=1 --define _WIN32=1".
-      " --define WINVER=0x0400 --define MINGW32_FIX=1")."\n".
-    "\n".
-    ".SUFFIXES:\n".
-    "\n".
-    "%.o: %.c\n".
-    "\t\$(CC) \$(COMPAT) \$(FWHACK) \$(XFLAGS) \$(CFLAGS) -c \$<\n".
-    "\n".
-    "%.res.o: %.rc\n".
-    "\t\$(RC) \$(FWHACK) \$(RCFL) \$(RCFLAGS) \$< \$\@\n".
+      " --define WINVER=0x0400 --define MINGW32_FIX=1 " .
+       (join " ", map {"--include $dirpfx$_"} @srcdirs) )."\n".
     "\n";
-    print &splitline("all:" . join "", map { " $_.exe" } &progrealnames("GC"));
+    print &splitline("all:" . join "", map { " $_.exe" } &progrealnames("G:C"));
     print "\n\n";
-    foreach $p (&prognames("GC")) {
+    foreach $p (&prognames("G:C")) {
       ($prog, $type) = split ",", $p;
       $objstr = &objects($p, "X.o", "X.res.o", undef);
       print &splitline($prog . ".exe: " . $objstr), "\n";
@@ -394,6 +510,13 @@ if (defined $makefiles{'cygwin'}) {
     foreach $d (&deps("X.o", "X.res.o", $dirpfx, "/")) {
       print &splitline(sprintf("%s: %s", $d->{obj}, join " ", @{$d->{deps}})),
         "\n";
+      if ($d->{obj} =~ /\.res\.o$/) {
+       print "\t\$(RC) \$(FWHACK) \$(RCFL) \$(RCFLAGS) \$< \$\@\n";
+      } else {
+       $deflist = join "", map { " -D$_" } @{$d->{defs}};
+       print "\t\$(CC) \$(COMPAT) \$(FWHACK) \$(CFLAGS)" .
+           " \$(XFLAGS)$deflist -c \$< -o \$\@\n";
+      }
     }
     print "\n";
     print $makefile_extra{'cygwin'};
@@ -406,6 +529,7 @@ if (defined $makefiles{'cygwin'}) {
 
 ##-- Borland makefile
 if (defined $makefiles{'borland'}) {
+    $mftyp = 'borland';
     $dirpfx = &dirpfx($makefiles{'borland'}, "\\");
 
     %stdlibs = (  # Borland provides many Win32 API libraries intrinsically
@@ -441,26 +565,17 @@ if (defined $makefiles{'borland'}) {
     "!if !\$d(BCB)\n".
     "BCB = \$(MAKEDIR)\\..\n".
     "!endif\n".
-    "\n".
-    ".c.obj:\n".
-    &splitline("\tbcc32 -w-aus -w-ccc -w-par -w-pia \$(COMPAT) \$(FWHACK)".
-              " \$(XFLAGS) \$(CFLAGS) ".
-              (join " ", map {"-I$dirpfx$_"} @srcdirs) .
-              " /c \$*.c",69)."\n".
-    ".rc.res:\n".
-    &splitline("\tbrcc32 \$(FWHACK) \$(RCFL) -i \$(BCB)\\include -r".
-      " -DNO_WINRESRC_H -DWIN32 -D_WIN32 -DWINVER=0x0401 \$*.rc",69)."\n".
     "\n";
-    print &splitline("all:" . join "", map { " $_.exe" } &progrealnames("GC"));
+    print &splitline("all:" . join "", map { " $_.exe" } &progrealnames("G:C"));
     print "\n\n";
-    foreach $p (&prognames("GC")) {
+    foreach $p (&prognames("G:C")) {
       ($prog, $type) = split ",", $p;
       $objstr = &objects($p, "X.obj", "X.res", undef);
       print &splitline("$prog.exe: " . $objstr . " $prog.rsp"), "\n";
       my $ap = ($type eq "G") ? "-aa" : "-ap";
       print "\tilink32 $ap -Gn -L\$(BCB)\\lib \@$prog.rsp\n\n";
     }
-    foreach $p (&prognames("GC")) {
+    foreach $p (&prognames("G:C")) {
       ($prog, $type) = split ",", $p;
       print $prog, ".rsp: \$(MAKEFILE)\n";
       $objstr = &objects($p, "X.obj", undef, undef);
@@ -491,6 +606,17 @@ if (defined $makefiles{'borland'}) {
     foreach $d (&deps("X.obj", "X.res", $dirpfx, "\\")) {
       print &splitline(sprintf("%s: %s", $d->{obj}, join " ", @{$d->{deps}})),
         "\n";
+      if ($d->{obj} =~ /\.res$/) {
+       print &splitline("\tbrcc32 \$(FWHACK) \$(RCFL) " .
+                        "-i \$(BCB)\\include -r -DNO_WINRESRC_H -DWIN32".
+                        " -D_WIN32 -DWINVER=0x0401 \$*.rc",69)."\n";
+      } else {
+       $deflist = join "", map { " -D$_" } @{$d->{defs}};
+       print &splitline("\tbcc32 -w-aus -w-ccc -w-par -w-pia \$(COMPAT)" .
+                        " \$(FWHACK) \$(CFLAGS) \$(XFLAGS)$deflist ".
+                        (join " ", map {"-I$dirpfx$_"} @srcdirs) .
+                        " /o$d->{obj} /c ".$d->{deps}->[0],69)."\n";
+      }
     }
     print "\n";
     print $makefile_extra{'borland'};
@@ -509,6 +635,7 @@ if (defined $makefiles{'borland'}) {
 }
 
 if (defined $makefiles{'vc'}) {
+    $mftyp = 'vc';
     $dirpfx = &dirpfx($makefiles{'vc'}, "\\");
 
     ##-- Visual C++ makefile
@@ -527,21 +654,16 @@ if (defined $makefiles{'vc'}) {
       "# C compilation flags\n".
       "CFLAGS = /nologo /W3 /O1 /D_WINDOWS /D_WIN32_WINDOWS=0x401 /DWINVER=0x401\n".
       "LFLAGS = /incremental:no /fixed\n".
-      "\n".
-      ".c.obj:\n".
-      "\tcl \$(COMPAT) \$(FWHACK) \$(XFLAGS) \$(CFLAGS) /c \$*.c\n".
-      ".rc.res:\n".
-      "\trc \$(FWHACK) \$(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 \$*.rc\n".
       "\n";
-    print &splitline("all:" . join "", map { " $_.exe" } &progrealnames("GC"));
+    print &splitline("all:" . join "", map { " $_.exe" } &progrealnames("G:C"));
     print "\n\n";
-    foreach $p (&prognames("GC")) {
+    foreach $p (&prognames("G:C")) {
        ($prog, $type) = split ",", $p;
        $objstr = &objects($p, "X.obj", "X.res", undef);
        print &splitline("$prog.exe: " . $objstr . " $prog.rsp"), "\n";
        print "\tlink \$(LFLAGS) -out:$prog.exe -map:$prog.map \@$prog.rsp\n\n";
     }
-    foreach $p (&prognames("GC")) {
+    foreach $p (&prognames("G:C")) {
        ($prog, $type) = split ",", $p;
        print $prog, ".rsp: \$(MAKEFILE)\n";
        $objstr = &objects($p, "X.obj", "X.res", "X.lib");
@@ -563,6 +685,14 @@ if (defined $makefiles{'vc'}) {
     foreach $d (&deps("X.obj", "X.res", $dirpfx, "\\")) {
        print &splitline(sprintf("%s: %s", $d->{obj}, join " ", @{$d->{deps}})),
          "\n";
+       if ($d->{obj} =~ /\.res$/) {
+           print "\trc \$(FWHACK) \$(RCFL) -r -DWIN32 -D_WIN32 ".
+             "-DWINVER=0x0400 -fo".$d->{obj}." ".$d->{deps}->[0]."\n";
+       } else {
+           $deflist = join "", map { " /D$_" } @{$d->{defs}};
+           print "\tcl \$(COMPAT) \$(FWHACK) \$(CFLAGS) \$(XFLAGS)$deflist".
+             " /c ".$d->{deps}->[0]." /Fo$d->{obj}\n";
+       }
     }
     print "\n";
     print $makefile_extra{'vc'};
@@ -588,6 +718,7 @@ if (defined $makefiles{'vc'}) {
 }
 
 if (defined $makefiles{'vcproj'}) {
+    $mftyp = 'vcproj';
 
     $orig_dir = cwd;
 
@@ -608,7 +739,7 @@ if (defined $makefiles{'vcproj'}) {
     %all_object_deps = map {$_->{obj} => $_->{deps}} @deps;
     # Create the project files
     # Get names of all Windows projects (GUI and console)
-    my @prognames = &prognames("GC");
+    my @prognames = &prognames("G:C");
     foreach $progname (@prognames) {
        create_project(\%all_object_deps, $progname);
     }
@@ -843,6 +974,7 @@ if (defined $makefiles{'vcproj'}) {
 }
 
 if (defined $makefiles{'gtk'}) {
+    $mftyp = 'gtk';
     $dirpfx = &dirpfx($makefiles{'gtk'}, "/");
 
     ##-- X/GTK/Unix makefile
@@ -859,11 +991,17 @@ if (defined $makefiles{'gtk'}) {
     "# You can define this path to point at your tools if you need to\n".
     "# TOOLPATH = /opt/gcc/bin\n".
     "CC = \$(TOOLPATH)cc\n".
+    "# You can manually set this to `gtk-config' or `pkg-config gtk+-1.2'\n".
+    "# (depending on what works on your system) if you want to enforce\n".
+    "# building with GTK 1.2, or you can set it to `pkg-config gtk+-2.0'\n".
+    "# if you want to enforce 2.0. The default is to try 2.0 and fall back\n".
+    "# to 1.2 if it isn't found.\n".
+    "GTK_CONFIG = sh -c 'pkg-config gtk+-2.0 \$\$0 2>/dev/null || gtk-config \$\$0'\n".
     "\n".
     &splitline("CFLAGS = -O2 -Wall -Werror -g " .
               (join " ", map {"-I$dirpfx$_"} @srcdirs) .
-              " `gtk-config --cflags`")."\n".
-    "XLDFLAGS = `gtk-config --libs`\n".
+              " `\$(GTK_CONFIG) --cflags`")."\n".
+    "XLDFLAGS = `\$(GTK_CONFIG) --libs`\n".
     "ULDFLAGS =#\n".
     "INSTALL=install\n",
     "INSTALL_PROGRAM=\$(INSTALL)\n",
@@ -871,17 +1009,13 @@ if (defined $makefiles{'gtk'}) {
     "prefix=/usr/local\n",
     "exec_prefix=\$(prefix)\n",
     "bindir=\$(exec_prefix)/bin\n",
+    "gamesdir=\$(exec_prefix)/games\n",
     "mandir=\$(prefix)/man\n",
     "man1dir=\$(mandir)/man1\n",
-    "\n".
-    ".SUFFIXES:\n".
-    "\n".
-    "%.o:\n".
-    "\t\$(CC) \$(COMPAT) \$(FWHACK) \$(XFLAGS) \$(CFLAGS) -c \$<\n".
     "\n";
-    print &splitline("all:" . join "", map { " $_" } &progrealnames("XU"));
+    print &splitline("all:" . join "", map { " $_" } &progrealnames("X:U"));
     print "\n\n";
-    foreach $p (&prognames("XU")) {
+    foreach $p (&prognames("X:U")) {
       ($prog, $type) = split ",", $p;
       $objstr = &objects($p, "X.o", undef, undef);
       print &splitline($prog . ": " . $objstr), "\n";
@@ -892,15 +1026,19 @@ if (defined $makefiles{'gtk'}) {
     foreach $d (&deps("X.o", undef, $dirpfx, "/")) {
       print &splitline(sprintf("%s: %s", $d->{obj}, join " ", @{$d->{deps}})),
           "\n";
+      $deflist = join "", map { " -D$_" } @{$d->{defs}};
+      print "\t\$(CC) \$(COMPAT) \$(FWHACK) \$(CFLAGS) \$(XFLAGS)$deflist" .
+         " -c \$< -o \$\@\n";
     }
     print "\n";
     print $makefile_extra{'gtk'};
     print "\nclean:\n".
-    "\trm -f *.o". (join "", map { " $_" } &progrealnames("XU")) . "\n";
+    "\trm -f *.o". (join "", map { " $_" } &progrealnames("X:U")) . "\n";
     select STDOUT; close OUT;
 }
 
 if (defined $makefiles{'mpw'}) {
+    $mftyp = 'mpw';
     ##-- MPW Makefile
     open OUT, ">$makefiles{'mpw'}"; select OUT;
     print
@@ -1041,6 +1179,7 @@ if (defined $makefiles{'mpw'}) {
 }
 
 if (defined $makefiles{'lcc'}) {
+    $mftyp = 'lcc';
     $dirpfx = &dirpfx($makefiles{'lcc'}, "\\");
 
     ##-- lcc makefile
@@ -1064,16 +1203,10 @@ if (defined $makefiles{'lcc'}) {
       "\n".
     "\n".
     "# Get include directory for resource compiler\n".
-    "\n".
-    ".c.obj:\n".
-    &splitline("\tlcc -O -p6 \$(COMPAT) \$(FWHACK)".
-      " \$(XFLAGS) \$(CFLAGS)  \$*.c",69)."\n".
-    ".rc.res:\n".
-    &splitline("\tlrc \$(FWHACK) \$(RCFL) -r \$*.rc",69)."\n".
     "\n";
-    print &splitline("all:" . join "", map { " $_.exe" } &progrealnames("GC"));
+    print &splitline("all:" . join "", map { " $_.exe" } &progrealnames("G:C"));
     print "\n\n";
-    foreach $p (&prognames("GC")) {
+    foreach $p (&prognames("G:C")) {
       ($prog, $type) = split ",", $p;
       $objstr = &objects($p, "X.obj", "X.res", undef);
       print &splitline("$prog.exe: " . $objstr ), "\n";
@@ -1087,6 +1220,13 @@ if (defined $makefiles{'lcc'}) {
     foreach $d (&deps("X.obj", "X.res", $dirpfx, "\\")) {
       print &splitline(sprintf("%s: %s", $d->{obj}, join " ", @{$d->{deps}})),
         "\n";
+      if ($d->{obj} =~ /\.res$/) {
+       print &splitline("\tlrc \$(FWHACK) \$(RCFL) -r \$*.rc",69)."\n";
+      } else {
+       $deflist = join "", map { " -D$_" } @{$d->{defs}};
+       print &splitline("\tlcc -O -p6 \$(COMPAT) \$(FWHACK) \$(CFLAGS)".
+                        " \$(XFLAGS)$deflist ".$d->{deps}->[0]." -o \$\@",69)."\n";
+      }
     }
     print "\n";
     print $makefile_extra{'lcc'};
@@ -1097,3 +1237,81 @@ if (defined $makefiles{'lcc'}) {
 
     select STDOUT; close OUT;
 }
+
+if (defined $makefiles{'osx'}) {
+    $mftyp = 'osx';
+    $dirpfx = &dirpfx($makefiles{'osx'}, "/");
+
+    ##-- Mac OS X makefile
+    open OUT, ">$makefiles{'osx'}"; select OUT;
+    print
+    "# Makefile for $project_name under Mac OS X.\n".
+    "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n".
+    "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n";
+    # gcc command line option is -D not /D
+    ($_ = $help) =~ s/=\/D/=-D/gs;
+    print $_;
+    print
+    "CC = \$(TOOLPATH)gcc\n".
+    "\n".
+    &splitline("CFLAGS = -O2 -Wall -Werror -g " .
+              (join " ", map {"-I$dirpfx$_"} @srcdirs))."\n".
+    "LDFLAGS = -framework Cocoa\n".
+    &splitline("all:" . join "", map { " $_" } &progrealnames("MX:U")) .
+    "\n" .
+    $makefile_extra{'osx'} .
+    "\n".
+    ".SUFFIXES: .o .c .m\n".
+    "\n";
+    print "\n\n";
+    foreach $p (&prognames("MX")) {
+      ($prog, $type) = split ",", $p;
+      $objstr = &objects($p, "X.o", undef, undef);
+      $icon = &special($p, ".icns");
+      $infoplist = &special($p, "info.plist");
+      print "${prog}.app:\n\tmkdir -p \$\@\n";
+      print "${prog}.app/Contents: ${prog}.app\n\tmkdir -p \$\@\n";
+      print "${prog}.app/Contents/MacOS: ${prog}.app/Contents\n\tmkdir -p \$\@\n";
+      $targets = "${prog}.app/Contents/MacOS/$prog";
+      if (defined $icon) {
+       print "${prog}.app/Contents/Resources: ${prog}.app/Contents\n\tmkdir -p \$\@\n";
+       print "${prog}.app/Contents/Resources/${prog}.icns: ${prog}.app/Contents/Resources $icon\n\tcp $icon \$\@\n";
+       $targets .= " ${prog}.app/Contents/Resources/${prog}.icns";
+      }
+      if (defined $infoplist) {
+       print "${prog}.app/Contents/Info.plist: ${prog}.app/Contents/Resources $infoplist\n\tcp $infoplist \$\@\n";
+       $targets .= " ${prog}.app/Contents/Info.plist";
+      }
+      $targets .= " \$(${prog}_extra)";
+      print &splitline("${prog}: $targets", 69) . "\n\n";
+      print &splitline("${prog}.app/Contents/MacOS/$prog: ".
+                      "${prog}.app/Contents/MacOS " . $objstr), "\n";
+      $libstr = &objects($p, undef, undef, "-lX");
+      print &splitline("\t\$(CC)" . $mw . " \$(LDFLAGS) -o \$@ " .
+                       $objstr . " $libstr", 69), "\n\n";
+    }
+    foreach $p (&prognames("U")) {
+      ($prog, $type) = split ",", $p;
+      $objstr = &objects($p, "X.o", undef, undef);
+      print &splitline($prog . ": " . $objstr), "\n";
+      $libstr = &objects($p, undef, undef, "-lX");
+      print &splitline("\t\$(CC)" . $mw . " \$(ULDFLAGS) -o \$@ " .
+                      $objstr . " $libstr", 69), "\n\n";
+    }
+    foreach $d (&deps("X.o", undef, $dirpfx, "/")) {
+      print &splitline(sprintf("%s: %s", $d->{obj}, join " ", @{$d->{deps}})),
+          "\n";
+      $deflist = join "", map { " -D$_" } @{$d->{defs}};
+      if ($d->{deps}->[0] =~ /\.m$/) {
+       print "\t\$(CC) -x objective-c \$(COMPAT) \$(FWHACK) \$(CFLAGS)".
+           " \$(XFLAGS)$deflist -c \$< -o \$\@\n";
+      } else {
+       print "\t\$(CC) \$(COMPAT) \$(FWHACK) \$(CFLAGS) \$(XFLAGS)$deflist" .
+           " -c \$< -o \$\@\n";
+      }
+    }
+    print "\nclean:\n".
+    "\trm -f *.o *.dmg". (join "", map { " $_" } &progrealnames("U")) . "\n".
+    "\trm -rf *.app\n";
+    select STDOUT; close OUT;
+}