#!/usr/local/bin/perl # Authors: William Lott, Nick Kramer # rcs-header: $Header: /afs/cs/project/gwydion/dylan/src/tools/shared-misc/RCS/gen-makefile,v 1.23 97/06/02 12:11:32 ram Exp $ # Usage: gen-makefile [-pplatforms.descr] directory # # gen-makefile turns a single Makegen file into a Makefile. # gen-makefile does the bulk of the work for mk-build-tree, which # calls gen-makefile once on each Makegen in the tree. The main # reason we split gen-makefile from mk-build-tree is that gen-makefile # uses eval, so we need a fresh environment for each Makegen file to # keep them from accidentally pissing on each other. # # All Defaults files must define the following variables: $target_name # and $srcroot. Many Defaults files also define $destdir and $D2C. # (See below for a full description of all available variables) # # gen-makefile provides a variety of functions which Makegen files can # call. These functions include: # # compile_subdirs(@subdirs) # emit_library_rule($lidfile, $extradeps, $extraflags, @keywords) # emit_c_file_rule($base_file_name, @header_files) # emit_cc_file_rule($base_file_name, @header_files) # emit_melange_rule($base_file_name, @dependencies) # emit_parsergen_rule($base_file_name) # install($dest_subdir, @files) # install_from_src($dest_subdir, @files) # install_executable_from_src($dest_subdir, @files) # emit_mindycomp_rule($dylan_source) # emit_dbc_link_rule($libname, $dbc_files) # emit_mindycomp_library_rule($libname, $source_files) # makegen_include($include_file) # unknown_platform_error() # convert_path_separator($path) # # Complete documentation on these functions can be found at the end of # this file. Note that many functions can take a bunch of file names. # Some do this by accepting a single string with the filenames # separated by spaces; others accept a variable number of arguments # with one argument per filename. The former style uses a variable # beginning with a dollar sign ($), the latter style uses variables # beginning with an at (@) sign. (Ideally there'd be some # consistency, but that's asking an awful lot...) # # ### How hard could it be to use only varargs? It looks like only # the mindycomp ones violate this convention... # # gen-makefile also defines a number of global variables. Most of the # variables of interest to Defaults and Makegen writers can be found # in set_default_defaults(). Some other ones of interest: # # $target_name # The name of the target platform, which is used to calculate # %target_platform. # $host_name # The name of the host platform, which is used to calculate # %host_platform. This variable defaults to $target_name. # %target_platform # The platforms.descr entry for the target platform. Keywords # are lowercase and use underbars instead of dashes. This # variable is automatically derived from $target_name. # %target_features # The features this target platform defines. Usage: # $target_features{'some_feature'} is 1 if defined, 0 # otherwise. (Note that the feature names are lowercase and # use underbars instead of dashes) This variable is # automatically derived from %target_platform. # %host_platform # The platforms.descr entry for the host platform. Keywords # are lowercase and use underbars instead of dashes. This # variable is automatically derived from $host_name. # $srcroot # The root of the source tree. Must be an absolute path. # $buildroot # The root of the build tree. Must be an absolute path, and # it must be equivalent to the directory mk-build-tree was run # from. The purpose of this variable is to allow the user to # choose an alternate wording of the build root -- ie, # "/afs/cs/whatever" instead of "/afs/cs.cmu.edu/whatever". # $subdir # The source dir for this Makegen file is located at # "$srcroot/$subdir", and the current build dir is located at # "$buildroot/$subdir". # $destdir # The root directory that files will be copied to when "make # install" is run. a.k.a. the "exec prefix" # $bindir # $libdir # $includedir # $sysconfdir # Directories in which we install dylan executables, libraries, # include files and platforms.descr. # Defaults to $destdir/bin, $destdir/lib/dylan, $destdir/include, # $destdir/etc. # $gen_makefile # Where to run gen-makefile from for calls from makefiles. # # In addition, the d2c Makegens use the following variables: # # $stage2 # If 0, compile the compiler with Mindy. If 1, compile it # with d2c. # Top level code # # gen-makefile uses the following global variables internally: # # %all_platforms # Keys are names of platforms, value is 1 if we know of the # platform, 0 otherwise. # LIDFILE # A file handle that has special meaning in some places. # $orig_buildroot # Used to make sure $buildroot isn't changed by a Makegen. # $defaults # Full path name of the Defaults file. # $makefile # The name of the makefile we're currently generating. # $dot_obj # Shorthand for $target_platform{'object-filename-suffix'}. # $dot_exe # Shorthand for $target_platform{'executable-filename-suffix'} # $dot_lib # Shorthand for $target_platform{'library-filename-suffix'} # $lib_prefix # Shorthand for $target_platform{'library-filename-prefix'} # $lidfile_hit_eof # A global that gets around the lack of multiple return values. # $platforms_dot_descr # Full path name of "platforms.descr" # # gen-makefile also eval's into existence the following sets of variables: # # %platform_platformName # where "platformName" is the name of a platforms.descr entry. # @makefileTarget_dependencies # where "makefileTarget" is the name of a Makefile target # @makefileTarget_commands # where "makefileTarget" is the name of a Makefile target # # gen-makefile supports the following top-level makefile targets: # compile (default), install, clean, and cc_files (compiles C code # generated by d2c). $#ARGV == 0 || $#ARGV == 1 || die "Usage: $0 [-pPlatforms.descr] directory\n"; if ($ARGV[0] =~ /-p(.*)/) { $platforms_dot_descr = $1; shift(@ARGV); } else { $platforms_dot_descr = $ENV{'DYLANDIR'} . '/etc/platforms.descr'; } $#ARGV == 0 || die "Usage: $0 [-pPlatforms.descr] directory\n"; chdir($ARGV[0]) || die "Can't cd to $ARGV[0]: $!\n"; $lidfile_hit_eof = 0; do set_default_defaults(); do find_Defaults_file(); do read_Defaults_file(); do do_Makegen_file(); exit(0); # Core functionality of gen-makefile # set_default_defaults() -- internal # # Sets up the default values we use if something isn't specified in # the Defaults file. There is no return value, only a lot of side # effects to global variables. # sub set_default_defaults { # Set the default defaults. $DYLANDIR = 0; $D2C = '$(DYLANDIR)/bin/d2c'; $D2CFLAGS = '-L$(DYLANDIR)/lib/dylan'; $CFLAGS = '-g'; $CPPFLAGS = '-I$(DYLANDIR)/include'; $MELANGE = 'melange'; $PARSERGEN = 'parsergen'; $MC = 'mindycomp'; # There is no MINDYFLAGS, because that depends on the library name. $MINDY = 'mindy'; $gen_makefile = 'gen-makefile'; $install_data = "cp"; $install_program = "install"; } # find_Defaults_file() -- internal # # Finds the Defaults file and sets $defaults, $buildroot, and # $orig_buildroot appropriately. # sub find_Defaults_file { local($cwd) = `pwd`; chop($cwd); $buildroot = $cwd; $subdir = ''; # Change //d/path into d:/path on NT. Only a problem on drives # other than C:. (We can't conditionalize this because we don't # yet know which platform we're running on.) $buildroot =~ s|^//(\w)/|\1:/|; until (-e ($defaults = $buildroot . '/Defaults')) { ($buildroot =~ /^(.*)\/([^\/]+)$/) || die("Can't find Defaults and hence can't tell where " . "the root is.\n"); $subdir = $2 . '/' . $subdir; $buildroot = $1; } # Save the buildroot. $orig_buildroot = $buildroot; } # read_Defaults_file() -- internal # # Reads in the Defaults file which find_Defaults_file found, and sets # up a variety of global variables that depend on things in the # Defaults file. # sub read_Defaults_file { # Slurp in the defaults. do $defaults; die("Problem loading $defaults:\n $@\n") if $@; &read_platforms_dot_descr(); $target_name || die("Must set variable \$target_name in Defaults file\n"); $host_name = $host_name || $target_name; # $host defaults to $target %target_platform = &get_platform($target_name); %host_platform = &get_platform($host_name); # Extract features from target platform local($feature_name); for $feature_name (split(/\s+/, $target_platform{'default_features'})) { $feature_name =~ tr/-A-Z/_a-z/; $features{$feature_name} = 1; } # Create some useful shorthands (internal use only) $makefile = $host_platform{'makefile_name'}; $dot_obj = $target_platform{'object_filename_suffix'}; $dot_exe = $target_platform{'executable_filename_suffix'}; # There can be multiple library suffixes, and we only need the # preferred one. ($dot_lib) = split(/\s+/, $target_platform{'library_filename_suffix'}); $lib_prefix = $target_platform{'library_filename_prefix'}; # Check to see if buildroot changed to something else. unless ($buildroot eq $orig_buildroot) { ### On win32, this code doesn't appear to work correctly in ### that it *always* thinks the two are equivalent. Oh well, ### no harm done... local($root_inode) = (stat($buildroot))[1]; local($orig_inode) = (stat($orig_buildroot))[1]; unless ($root_inode == $orig_inode) { die("Defaults changed \$buildroot to:\n $buildroot\n" . "but that is a different directory than:\n" . " $orig_buildroot\n"); } &assert_path_is_absolute($buildroot, "\$buildroot"); $defaults = $buildroot . '/Defaults'; } # Make sure they set srcroot. And set it to an absolute pathname. $srcroot || die('$srcroot not set in Defaults.\n'); &assert_path_is_absolute($srcroot, "\$srcroot"); # Tack the subdir onto srcroot. Note: subdir is empty or ends # with a /, hence the chop. chop($srcdir = $srcroot.'/'.$subdir); # Likewise for builddir chop($builddir = $buildroot.'/'.$subdir); if ($destdir) { unless ($libdir) { $libdir = $destdir.'/lib/dylan'; } unless ($bindir) { $bindir = $destdir.'/bin'; } unless ($includedir) { $includedir = $destdir.'/include'; } unless ($sysconfdir) { $sysconfdir = $destdir.'/etc'; } } $srcroot = &convert_path_separator($srcroot); $srcdir = &convert_path_separator($srcdir); $buildroot = &convert_path_separator($buildroot); $builddir = &convert_path_separator($builddir); $destdir = &convert_path_separator($destdir); $libdir = &convert_path_separator($libdir); $bindir = &convert_path_separator($bindir); $includedir = &convert_path_separator($includedir); $sysconfdir = &convert_path_separator($sysconfdir); $MC = &convert_path_separator($MC); $MINDY = &convert_path_separator($MINDY); $D2C = &convert_path_separator($D2C); $MELANGE = &convert_path_separator($MELANGE); $PARSERGEN = &convert_path_separator($PARSERGEN); } # do_Makegen_file() -- internal # # Reads in the Makegen file and creates an appropriate Makefile. # sub do_Makegen_file { # Find the Makegen file. local($makegen) = $srcdir . '/Makegen'; (-e $makegen) || die("No $makegen\n"); # Start the makefile. open(MAKEFILE, ">,$makefile") || die("Can't open ,$makefile: $!\n"); select(MAKEFILE); print "### This makefile is machine generated. Don't expect any edits to survive.\n"; print "### Generated for target $target_name hosted by $host_name\n\n"; print "SRCROOT=$srcroot\n"; print "SRCDIR=$srcdir\n"; print 'VPATH=$(SRCDIR)' . "\n"; print "BUILDROOT=$buildroot\n"; print "BUILDDIR=$builddir\n"; # $destdir && print "DESTDIR=$destdir\n"; if ($host_platform{'make_supports_phony_targets?'}) { print "\n.PHONY: default compile install clean\n\n"; } print "default: compile\n\n"; # Slurp in the generator. do $makegen; die("Problem loading $makegen:\n $@\n") if $@; if (@files_to_clean) { push(@clean_commands, "-rm -f @files_to_clean"); } push(@GNUmakefile_dependencies, $gen_makefile, $defaults, $makegen); push(@GNUmakefile_commands, "$gen_makefile -p$platforms_dot_descr ."); &emit_rule('compile'); $destdir && do emit_rule ('install'); &emit_rule('clean'); if ($host_platform{'makefiles_can_rebuild_themselves?'}) { print $makefile, ': ', join(' ', eval('@GNUmakefile_dependencies')); print "\n"; foreach $rule (eval('@GNUmakefile_commands')) { print "\t", $rule, "\n"; } print "\n"; } &emit_rule('cc_files'); &emit_rule('dbc_only'); close(MAKEFILE); rename(",$makefile", $makefile); } # Internal utility functions # assert_path_is_absolute($path, $variable) -- internal # # Decides if the path is an absolute path; signals an error if it # isn't. On a win32 system, an absolute path must have a drive # letter. Make sure you've set %host_platform before you call this # function. $variable is used only in error messages. # sub assert_path_is_absolute { local($path, $variable) = @_; local($win32_absolute) = ($path =~ /^w:\//); local($unix_absolute) = ($path =~ /^\//); local($ok, $explanation); # We do a lot of extra work here so we can provide better error # messages. if ($win32_absolute && $host_platform{'uses_drive_letters?'}) { $ok = 1; } elsif ($unix_absolute && !$host_platform{'uses_drive_letters?'}) { $ok = 1; } elsif ($unix_absolute && $host_platform{'uses_drive_letters?'}) { $ok = 0; $explanation = "(You must include a drive letter)"; } elsif ($win32_absolute && !$host_platform{'uses_drive_letters?'}) { $ok = 0; $explanation = "(You included a drive letter on a Unix machine?!?)"; } else { # path is not absolute by anyone's definition, which doesn't # merit further explanation. $ok = 0; $explanation = ""; } $ok = 1; if (! $ok) { if ($explanation) { die "$variable is not absolute:\n $path\n$explanation\n"; } else { die "$variable is not absolute:\n $path\n"; } } } # emit_rule($target) -- internal # # Emits a rule, looking at the global state to decide what the # dependencies are and how to build the target. # sub emit_rule { local($rule) = @_; print $rule, ': ', join(' ', eval('@'.$rule.'_dependencies')), "\n"; foreach $rule (eval('@'.$rule.'_commands')) { print "\t", $rule, "\n"; } print "\n"; } # compile_subdir($subdir) -- internal # # Generates the makefile rule to visit a single subdirectory. # sub compile_subdir { local($subdir) = &convert_path_separator(@_); push(@compile_commands, sprintf($host_platform{'recursive_make_command'}, $subdir, "")); push(@install_commands, sprintf($host_platform{'recursive_make_command'}, $subdir, "install")); push(@clean_commands, sprintf($host_platform{'recursive_make_command'}, $subdir, "clean")); push(@cc_files_commands, sprintf($host_platform{'recursive_make_command'}, $subdir, "cc_files")); push(@dbc_only_commands, sprintf($host_platform{'recursive_make_command'}, $subdir, "dbc_only")); } # maybe_emit_d2c_defines() -- internal # # If they haven't already been emitted, emit the Makefile header, # which defines various Makefile variables like $(D2C). # sub maybe_emit_d2c_defines { unless ($d2c_defines) { if ($CC) { print("CC=$CC\n"); } print <) { # remember, in Perl "." is any character other than newline. # Also, Perl handles all newline hassles by turning win32 # style CRLFs into LFs (\n's) before it shows us the string. if (/^\s*$/) { # if blank line, break out of loop $lidfile_hit_eof = 0; return %contents; } elsif (m|^//.*$|) { # comment line, ignore } elsif (/^\s+(.*)$/) { # Continuation line -- part of a multi-line value $contents{$last_keyword} .= ' ' . $1; } else { /^([-A-Za-z0-9_!&*<>|^\$\%\@\?]+):\s+(.*)\s+$/ || die "Bad keyword line in $lid_file\n"; local($keyword) = $1; local($value) = $2; if ($value eq '#f' | $value eq '#F') { $value = 0; } elsif ($value eq '#t' | $value eq '#T') { $value = 1; } $keyword =~ tr/-A-Z/_a-z/; $contents{$keyword} = $value; $last_keyword = $keyword; } } $lidfile_hit_eof = 1; return %contents; } # parse_lid_file($filename) -- internal # # Reads in the LID file, and returns an associative array where the # keys are header keywords (mashed to lower case), and the values are # the header values. As a magic special case, the keyword 'files' # contains all the files in the body of the lid file. # sub parse_lid_file { local($lid_file) = @_; local(%contents); open(LIDFILE, $srcdir.'/'.$lid_file.'.lid') || die("Can't open $lid_file.lid: $!\n"); %contents = &parse_dylan_header(); # Read the filenames # a .o in the Lid file is a hack to get a foreign .o put in the archive. # Just ignore em, since there should be a seperate C rule to clean, etc. while () { if ($_ !~ m/.*$dot_obj$/) { chop; # kill newline $contents{'files'} .= " $_"; } } # replace multiple spaces with single spaces $contents{'files'} =~ s/\s+/ /g; # strip leading whitespace, which tends to screw up other parts of # gen-makefile $contents{'files'} =~ s/^\s+//; close(LIDFILE); return %contents; } # read_platforms_dot_descr() -- internal # # Read the platforms.descr file (used to be called targets.descr). # Each platform description will get it's own associative array named # %platform_platformName. (For example, %platform_x86_win32) See # parse_dylan_header for a description of the arrays. # # In addition to the per-platform arrays, there is a global # associative array %all_platforms which is used to list all known # platforms. The key is the name of a platform; the value is 1 if it # is a known platform, 0 otherwise. # sub read_platforms_dot_descr { ### Should have better way of specifying location of .descr open(LIDFILE, $platforms_dot_descr) || die("Can't open $platforms_dot_descr: $!\n"); while (! $lidfile_hit_eof) { local(%platform) = &parse_dylan_header(); local(@keywords) = keys(%platform); if ($#keywords > 0) { # if a real header, not a blank line or comment block local($platform_name) = $platform{'platform_name'}; $platform_name =~ tr/-A-Z/_a-z/; # mash to lowercase; - to _ if ($platform{'inherit_from'}) { local(%parent) = &get_platform($platform{'inherit_from'}); local($key); foreach $key (keys(%parent)) { if (! $platform{$key}) { $platform{$key} = $parent{$key}; } } } # Copy the %platforms array into the appropriate global local($eval_string) = '%' . 'platform_' . $platform_name . ' = %' . 'platform'; eval($eval_string); $all_platforms{$platform_name} = 1; } } close(LIDFILE); } # get_platform($platform_name) -- internal # # Return the platform (associative array) that corresponds to # $platform_name. This function will lowercase the platform name and # translate dashes into underbars for you. # sub get_platform { local($platform_name) = @_; $platform_name =~ tr/-A-Z/_a-z/; $all_platforms{$platform_name} || die("Unknown platform: $platform_name"); local(%platform) = eval('%platform_' . $platform_name); return %platform; } # Subroutines people can call from Makegen files. See top of file for # summary. # compile_subdirs(@subdirs) -- exported # # Generates the makefile rule to visit a bunch of subdirs subdirectory. # sub compile_subdirs { local($dir); foreach $dir (@_) { do compile_subdir($dir); } } # emit_library_rule($lidfile, $extradeps, $extraflags, @keywords) -- exported # # Emit the Makefile rule to build a Dylan library. $lidfile is the # name of the lidfile, $extradeps are dependencies in addition to the # lid file and the "obvious" dependencies (used libraries and any file # mentioned in the lid file). $extraflags are any extra flags you # want to pass to d2c, esp. "-L../somedir". $extradeps are any # additional dependencies you want to specify for the library being # built (such as $buildroot/force.timestamp). # # Keywords recognized: # # 'compile' # Compile this with d2c and add it to the list of libraries compiled by # the 'compile' makefile target. # 'install' # Adds this library/executable to the list of files installed # by the 'install' makefile target. # 'no-mindy' # By default, a target is created for dbc_only; this target # is not added to any makefile targets, but it can be manually # created by running "make dbc_only". This makes for easy debugging. # 'no-mindy' suppresses the creation of this target, which is useful # when the Mindy version of the library does not use the same source # files as the d2c version. # 'compile-mindy' # Add libname.dbc to the 'compile' makefile target (possibly in # addition to the d2c version of the library.) This flag does not make # much sense with 'no-mindy'. # 'no-d2c' # Like no-mindy, only suppress emission of d2c rules. # # The return value is a two element array. The first element is the # target which would have been used if the 'compile' flag were # specified. The second element of the array is the target which # would have been used if the 'compile-mindy' flag were specified. # This return value is most useful when bootstrapping a compiler from # Mindy, but I'm sure others can think of additional uses. # sub emit_library_rule { local($lidfilename, $extradeps, $extraflags, @keywords) = @_; do maybe_emit_d2c_defines(); local(%lidfile) = &parse_lid_file($lidfilename); local($library) = $lidfile{'library'} || die("No library: header in $lidfilename.lid.\n"); $library =~ tr/A-Z/a-z/; local($unit_prefix) = $lidfile{'unit_prefix'} || "$library"; local($executable) = $lidfile{'executable'}; local($executable_filename) = $executable . $dot_exe; local($file); for $file (split(' ', $lidfile{'files'})) { $file =~ s/\.dylan$//; # chop off ".dylan" push(@files_to_clean, "$file.c", "$file$dot_obj"); } push(@files_to_clean, "$unit_prefix-init.c", "$unit_prefix-init$dot_obj"); push(@files_to_clean, "$unit_prefix-heap.s", "$unit_prefix-heap$dot_obj"); local($d2c_target); if ($executable) { push(@files_to_clean, $executable_filename, 'heap.s', 'inits.c'); if (grep(/^compile$/, @keywords)) { push(@compile_dependencies, $executable_filename); grep(/^install$/, @keywords) && do install_executable($bindir, $executable_filename); } $d2c_target = $executable_filename; } else { local($foo_lib_du, $libfoo_a); $foo_lib_du = "$library.lib.du"; $libfoo_a = "$lib_prefix$unit_prefix$dot_lib"; push(@files_to_clean, $foo_lib_du, $libfoo_a); if (grep(/^compile$/, @keywords)) { push(@compile_dependencies, $foo_lib_du); grep(/^install$/, @keywords) && do install($libdir, $foo_lib_du, $libfoo_a); } $d2c_target = $foo_lib_du; } if (! grep(/^no-d2c$/, @keywords)) { print "$d2c_target: $srcdir/$lidfilename.lid $extradeps\n"; print "\t\$(D2C) -M \$(D2CFLAGS) $extraflags ", "$srcdir/$lidfilename.lid\n"; local ($make_name) = $host_platform{'make_command'}; local($cc_files_cmd) = "$make_name -f cc-" . $unit_prefix . "-files.mak"; push(@cc_files_commands, $cc_files_cmd); print "\ninclude $unit_prefix.dep\n\n"; unless (-e "$unit_prefix.dep") { open(DEP, ">$unit_prefix.dep") || die("Can't create $unit_prefix.dep: $!\n"); close(DEP); } } if (! grep(/^no-mindy$/, @keywords)) { &emit_mindycomp_library_rule($library, $lidfile{'files'}); } if (grep(/^compile-mindy$/, @keywords)) { push(@compile_dependencies, "$library-lib.dbc"); grep(/^install$/, @keywords) && do install($libdir, "$library-lib.dbc"); } local(@return_val); $return_val[0] = $d2c_target; $return_val[1] = "$library-lib.dbc"; return @return_val; } # emit_c_file_rule($base_file_name, @header_files) -- exported # # Emit the rule to compile a C file into an object file. Dependencies # will be the source file and all @header_files. # sub emit_c_file_rule { local ($basefile, @headers) = @_; local ($objfile) = "$basefile$dot_obj"; do maybe_emit_d2c_defines(); print "$objfile: \$(SRCDIR)/$basefile.c"; foreach $header (@headers) { print " \$(SRCDIR)/$header"; } print "\n\t\$(CC) \$(CFLAGS) \$(CPPFLAGS) -I\$(SRCDIR) "; print "-c \$(SRCDIR)/$basefile.c -o $objfile\n\n"; if ($target_name eq $host_name) { push(@compile_dependencies, $objfile); } else { push(@cc_files_dependencies, $objfile); }; push(@files_to_clean, $objfile); } # emit_cc_file_rule($base_file_name, @header_files) -- exported # # Just like emit_c_file_rule, except uses .cc as an extension instead # of .c (C++ files instead of C files). I don't think this function # is currently used by any of our Makegens. # sub emit_cc_file_rule { local ($basefile, @headers) = @_; local ($objfile) = "$basefile$dot_obj"; do maybe_emit_d2c_defines(); print "$objfile: \$(SRCDIR)/$basefile.cc"; foreach $header (@headers) { print " \$(SRCDIR)/$header"; } print "\n\t\$(CC) \$(CFLAGS) -I\$(SRCDIR) "; print "-c \$(SRCDIR)/$basefile.cc -o $objfile\n\n"; if ($target_name eq $host_name) { push(@compile_dependencies, $objfile); } else { push(@cc_files_dependencies, $objfile); }; push(@files_to_clean, $objfile); } # emit_melange_rule($base_file_name, @dependencies) -- exported # # Emits the rule to turn a Melange input (.intr) file into a .dylan # file. # sub emit_melange_rule { local ($basefile, @headers) = @_; do maybe_emit_d2c_defines(); print "\$(SRCDIR)/$basefile.dylan: \$(SRCDIR)/$basefile.intr"; foreach $header (@headers) { print " \$(SRCDIR)/$header"; } print "\n\t\$(MELANGE) -I\$(SRCDIR) "; print "\$(SRCDIR)/$basefile.intr \$(SRCDIR)/$basefile.dylan\n\n"; push(@compile_dependencies, "\$(SRCDIR)/$basefile.dylan"); push(@files_to_clean, "\$SRCDIR/$basefile.dylan"); } # emit_parsergen_rule($base_file_name) -- exported # # Emit the rule to Melange a .input file into a .dylan file. # sub emit_parsergen_rule { local ($basefile) = @_; do maybe_emit_d2c_defines(); print "\$(SRCDIR)/$basefile.dylan: \$(SRCDIR)/$basefile.input"; print "\n\t\$(PARSERGEN) "; print "\$(SRCDIR)/$basefile.input \$(SRCDIR)/$basefile.dylan\n\n"; push(@compile_dependencies, "\$(SRCDIR)/$basefile.dylan"); push(@files_to_clean, "\$(SRCDIR)/$basefile.dylan"); } # install_one_file($from_file, $to_file, $executable_permissions) -- internal # # Emits the rule to copy $from_file into $to_file when "make install" # is run. If $executable_permissions is true (non-zero), then the # installed file will have executable permissions. # # This may seem like a good candidate for moving to platforms.descr. # Unfortunately, there's a lot more going on than merely choosing the # name of the "copy file" command. # sub install_one_file { local ($from_file, $to_file, $executable_permissions) = @_; print "\n$to_file: $from_file\n"; if ($host_platform{'default_features'} =~ /compiled-for-win32/i) { # Host is a win32 platform local ($rule) = "\tcopy $from_file $to_file\n\n"; $rule =~ tr|/|\\|; # translate / to \ for MS-Windows print $rule; } elsif ($host_platform{'default_features'} =~ /compiled-for-unix/i) { if ($executable_permissions) { print "\t$install_program $from_file $to_file\n\n"; } else { print "\t$install_data $from_file $to_file\n\n"; }; } else { die("The host is neither Unix nor win32, so I don't know how\n" . "to install your files."); } push(@install_dependencies, $to_file); } # install($dest_dir, @files) -- exported # # Installs a bunch of files from the build dir into $dest_dir, normally a # subdirectory of $destdir. # sub install { local ($dest_dir, @files) = @_; local ($file, $dst); # Don't generate any install rules unless destdir is set. $destdir || return; foreach $file (@files) { $dst = "$dest_dir/$file"; &install_one_file($file, $dst, 0); } } # install_executable($dest_dir, @files) -- exported # # Installs a bunch of files from the build dir into $dest_dir, normally a # subdirectory of $destdir. Makes them executable. # sub install_executable { local ($dest_dir, @files) = @_; local ($file, $dst); # Don't generate any install rules unless destdir is set. $destdir || return; foreach $file (@files) { $dst = "$dest_dir/$file"; &install_one_file($file, $dst, 1); } } # install_from_src($dest_dir, @files) -- exported # # Installs a bunch of files from the source dir into dest_dir. Useful when you # just want to copy Perl scripts into the dest dir. # sub install_from_src { local ($dest_dir, @files) = @_; local ($file, $dst); # Don't generate any install rules unless destdir is set. $destdir || return; foreach $file (@files) { $dst = "$dest_dir/$file"; &install_one_file("$srcdir/$file", $dst, 0); } } # install_executable_from_src($dest_dir, @files) -- exported # # Like install_from_src, except makes sure the file has executable # permissions for everyone (this is not relevant on win32, only unix). # sub install_executable_from_src { local ($dest_dir, @files) = @_; local ($file, $dst); # Don't generate any install rules unless destdir is set. $destdir || return; foreach $file (@files) { $dst = "$dest_dir/$file"; &install_one_file("$srcdir/$file", $dst, 1); } } # emit_mindycomp_rule($dylan_source) -- exported # # Emits a rule to run mindycomp on the file. $dylan_source shouldn't # have an extension. If the file doesn't exist in the source, hopefully # someone will create it in the build area. # # emit_mindycomp_library_rule is the primary, perhaps even the only, # user of this function. # sub emit_mindycomp_rule { local($source, $flags) = @_; if (-e "$srcdir/$source.dylan") { $da_source = "\$(SRCDIR)/$source.dylan"; } else { $da_source = "$source.dylan"; } print "$source.dbc: $da_source\n"; print "\t\$(MC) $flags "; print "-o $source.dbc $da_source\n"; } # emit_dbc_link_rule($libname, $dbc_files) -- exported # # Links a bunch of .dbc files into a single .dbc file. # sub emit_dbc_link_rule { local($libname, $objects) = @_; print "\n$libname: $objects\n"; if ($host_platform{'use_dbclink?'}) { print "\tdbclink $libname $objects\n\n"; } else { print "\tcat $objects > $libname\n\n"; } } # emit_mindycomp_library_rule($libname, $source_files) -- exported # # Generates a rule for compiling the library with Mindy. Usually this # function is invoked via emit_library_rule, but you can call it on # your own if you want. The resulting file will be called # foo-lib.dbc, and will *not* be added to the list of compile # dependencies. Also, this command only builds libraries, not # executables. To build an executable, we need to know all the other # libraries that go into it, which is beyond the scope of this # function. # sub emit_mindycomp_library_rule { local($libname, $lid_sources) = @_; $libname =~ tr/A-Z/a-z/; local($uppercase_libname) = $libname; $uppercase_libname =~ tr/-a-z/_A-Z/; local($file, @sources, @objects); foreach $file (split(/\s+/, $lid_sources)) { $file =~ s/\.dylan$//; # chop off ".dylan" push(@sources, $file); # emit_mindycomp_rule doesn't want ".dylan" push(@objects, "$file.dbc"); push(@files_to_clean, "$file.dbc"); } print "DASH_$uppercase_libname = -l$libname\n\n"; print "$uppercase_libname", "_OBJS = ", join(" ", @objects), "\n\n"; push(@dbc_only_dependencies, "$libname-lib.dbc"); local($source); foreach $source (@sources) { &emit_mindycomp_rule($source, "\$(DASH_$uppercase_libname)"); } &emit_dbc_link_rule("$libname-lib.dbc", "\$(${uppercase_libname}_OBJS)"); } # makegen_include($include_file) -- exported # # makegen_include is like C's #include. The main reason to use # makegen_include is that it correctly handles the case where the # srcdir is not the same as the build dir (ie, it looks for the include # file in the srcdir). # # There's a certain danger of variable capture with the variable # $include_file, so don't use that variable in your included files. # sub makegen_include { local($include_file) = @_; $include_file = $srcdir . '/' . $include_file; (-e $include_file) || die("No $include_file\n"); push(@GNUmakefile_dependencies, $include_file); # Convert \ back to /, because that's how "do" likes its filenames $include_file =~ tr|\\|/|; do $include_file; } # unknown_platform_error() -- exported # # Tries to give a useful error message when a Makegen doesn't # understand the platform. # sub unknown_platform_error { die "Unknown CPU type for platform " . $target_name . "\n" . "All known features: " . $target_platform{'default_features'} . "\n"; } # convert_path_separator($path) -- exported # # Converts a path which uses / into one that uses the path separator # appropriate for the host platform. # sub convert_path_separator { local($path) = @_; local($separator) = $host_platform{'path_separator'}; $separator =~ s|\\\\|\\|g; # translate \\ into \ (Dylan escaping rules) $path =~ s|/|$separator|g; return $path; }