Module: dfmc-linker
Author: keith and jonathan
Copyright:    Original Code is Copyright (c) 1995-2004 Functional Objects, Inc.
              All rights reserved.
License:      Functional Objects Library Public License Version 1.0
Dual-license: GNU Lesser General Public License
Warranty:     Distributed WITHOUT WARRANTY OF ANY KIND

//// Platform-specific makefile configuration.

// We need to generate different makefiles for Unix and Windows since
// they're incompatible for various reasons. In particular, we want
// to use !include with nmake, and there's not always a way to get
// that ignored in Unix makes.

define abstract compiler-open class <makefile-target> (<object>) end;

define compiler-open generic source-suffix
  (back-end :: <back-end>, t :: <makefile-target>);
define compiler-open generic object-suffix
  (back-end :: <back-end>, t :: <makefile-target>);
define compiler-open generic resource-suffix
  (back-end :: <back-end>, t :: <makefile-target>);

define compiler-open generic output-basename
  (back-end :: <back-end>, target :: <makefile-target>, basename :: <string>) => (basename);

define compiler-open generic emit-target-makefile
  (back-end :: <back-end>, t :: <makefile-target>,
   makefile-name, library-description, units,
   #key executable, base-address, linker-options, c-source-files,
	c-header-files, c-object-files, rc-files, c-libraries);

define method makefile-unit-name (t :: <makefile-target>)
  "Makefile"
end method;

define method makefile-target (desc :: <library-description>)
  makefile-target-using-os-name(library-description-os-name(desc))
end method;


define method source-unit (t :: <makefile-target>, unit)
  let b = current-back-end();
  concatenate(output-basename(b, t, unit), ".", source-suffix(b, t));
end method;

define method object-unit (t :: <makefile-target>, unit)
  let b = current-back-end();
  platform-specific-concatenate
    (t, output-basename(b, t, unit), ".", object-suffix(b, t));
end method;

define method object-unit-name (t :: <makefile-target>, unit)
  let b = current-back-end();
  concatenate(output-basename(b, t, unit), ".", object-suffix(b, t));
end method;

define compiler-open generic additional-platform-libraries (t, for-app?);
define compiler-open generic library-unit-name (t, unit);
define compiler-open generic library-unit-link-ref (t, ld, unit);
define compiler-open generic runtime-unit-link-ref (t, unit);

define compiler-open generic makefile-target-using-os-name (name);
define compiler-open generic makefile-variable-ref (t, name);
define compiler-open generic makefile-linker-option-massage (t, name);
define compiler-open generic makefile-rule-separator (t);
define compiler-open generic makefile-pathname (t, #rest components);
define compiler-open generic makefile-target-pathname-ref (t);

define method makefile-rule-separator (t)
  ": "
end method makefile-rule-separator;

define method makefile-variable-ref (t, name)
  concatenate("$(", name, ")")
end method makefile-variable-ref;

define method makefile-linker-option-massage (t, name)
  name
end method makefile-linker-option-massage;

define method makefile-target-pathname-ref (t)
  "$@"
end method makefile-target-pathname-ref;

define function makefile-command(t, command, #rest arguments)
  let expansion
    = with-output-to-string (line)
	let variable = make(limited(<stretchy-vector>, of: <byte-character>));
	let inref? = #f;
	for (c in command)
	  select (c)
	    '{' => 
	      variable.size := 0;
	      inref? := #t;
	    '}' =>
	      write(line, makefile-variable-ref(t, variable));
	      inref? := #f;
	    otherwise =>
	      if (inref?)
		add!(variable, c)
	      else
		write-element(line, c)
	      end;
	  end
	end
      end;
  apply(format-to-string, expansion, arguments)
end function makefile-command;

define function personal-library? (lib :: <library-description>)
  library-description-personal?(lib)
end function;

define method runtime-unit-link-ref (t, unit)
  library-unit-link-ref(t, #f, unit)
end method runtime-unit-link-ref;

define method library-unit-directory (t, lib :: false-or(<library-description>))
  if (lib & personal-library?(lib))
    makefile-variable-ref(t, "OPEN_DYLAN_USER_LIB")
  else
    makefile-variable-ref(t, "OPEN_DYLAN_RELEASE_LIB")
  end if;
end method;

define method library-unit-ref (t, lib, unit)
  makefile-pathname(t,
		    library-unit-directory(t, lib), 
		    library-unit-name(t, unit));
end method;

define method executable-unit (t, unit)
  platform-specific-concatenate(t, unit, ".", executable-suffix(t));
end method;

define method executable-unit-ref (t, unit)
  makefile-pathname(t,
		    makefile-variable-ref(t, "OPEN_DYLAN_USER_BIN"),
		    executable-unit(t, unit));
end method;

define method link-target-separator (t) " " end;

define method makefile-program-setups (t :: <makefile-target>) #() end;


/// UNIX (GNU or vendor make) Makefile specifics ...

define class <unix-makefile-target> (<makefile-target>) end;

define method additional-platform-libraries 
    (t :: <unix-makefile-target>, for-app? :: <boolean>)
  #("-lm")
end method;

define method library-unit-name (t :: <unix-makefile-target>, unit)
  concatenate("lib_", unit, ".", library-suffix(t));
end method;

define method library-unit-link-ref (t :: <unix-makefile-target>, lib, unit)
  library-unit-ref(t, lib, unit)
  // concatenate("-l_", unit);
end method;

define method makefile-pathname (t :: <unix-makefile-target>, #rest components)
  let almost = reduce(rcurry(concatenate, "/"), "", components);
  copy-sequence(almost, end: size(almost) - 1)
end method makefile-pathname;

define method library-suffix (t :: <unix-makefile-target>) 
  "$(OPEN_DYLAN_LIB_SUFFIX)"
end method;

define method makefile-program-setups (t :: <unix-makefile-target>) 
  list("OPEN_DYLAN_RELEASE_BIN=$(OPEN_DYLAN_RELEASE_INSTALL)/bin",
       "OPEN_DYLAN_RELEASE_INCLUDE=$(OPEN_DYLAN_RELEASE_INSTALL)/include",
       "OPEN_DYLAN_RELEASE_LIB=$(OPEN_DYLAN_RELEASE_INSTALL)/lib",
       "OPEN_DYLAN_USER_BIN=$(OPEN_DYLAN_USER_INSTALL)/bin",
       "OPEN_DYLAN_USER_INCLUDE=$(OPEN_DYLAN_USER_INSTALL)/include",
       "OPEN_DYLAN_USER_LIB=$(OPEN_DYLAN_USER_INSTALL)/lib",
       "",
       "CC = gcc",
       "MKDIR = mkdir",
       "CREATE = touch",
       "INSTALLOBJ = @ exit 0; ",
       "INSTALLLIB = cp -p",
       "UNINSTALL = rm -f",
       "LINKAPP = gcc $(OPEN_DYLAN_LINKAPP_FLAGS) -L$(OPEN_DYLAN_USER_LIB) -L$(OPEN_DYLAN_RELEASE_LIB) -o",
       "LINKLIB = $(OPEN_DYLAN_LD) $(OPEN_DYLAN_LINKLIB_FLAGS) -L$(OPEN_DYLAN_USER_LIB) -L$(OPEN_DYLAN_RELEASE_LIB) -o",
       "CFLAGS = $(OPEN_DYLAN_C_FLAGS) -I$(OPEN_DYLAN_RELEASE_INCLUDE)");
end method;

define method object-unit-name (t :: <unix-makefile-target>, unit)
  object-unit(t, unit);
end method;

define method executable-unit (t :: <unix-makefile-target>, unit)
  platform-specific(t, unit);
end method;

define method make-install-command 
    (target :: <unix-makefile-target>, name) => (makefile-command :: <string>)
  format-to-string("(cd ../%s ; $(MAKE) install)", name);
end method;

define method relative-makefile-unit-ref
    (target :: <unix-makefile-target>, name) => (makefile-unit :: <string>)
  format-to-string("../%s/Makefile", name);
end method;

define method compiler-args-for-source (t :: <unix-makefile-target>, name)
  format-to-string("-c %s", name)
end method;

define method compiler-args-for-object (t :: <unix-makefile-target>, name)
  format-to-string("-o %s", name)
end method;

define constant *unix-makefile-target* = make(<unix-makefile-target>);

define method makefile-target-using-os-name (name)
  *unix-makefile-target*
end method;


/// Win32 (nmake) Makefile specifics ...

define class <win32-makefile-target> (<makefile-target>) end;

define method additional-platform-libraries
    (t :: <win32-makefile-target>, for-app? :: <boolean>)
  #()
end method;

define method library-unit-name (t :: <win32-makefile-target>, unit)
  concatenate(unit, ".", library-suffix(t));
end method;

/// Uses the LIB search path to find the library ...
define method library-unit-link-ref (t :: <win32-makefile-target>, lib, unit)
  library-unit-name(t, unit)
end method;

define method makefile-pathname (t :: <win32-makefile-target>, #rest components)
  let almost = reduce(rcurry(concatenate, "\\"), "", components);
  copy-sequence(almost, end: size(almost) - 1)
end method makefile-pathname;

define method library-suffix (t :: <win32-makefile-target>) "lib" end;
define method executable-suffix (t :: <win32-makefile-target>) "exe" end;

define method link-target-separator (t :: <win32-makefile-target>) "" end;

define method makefile-program-setups (t :: <win32-makefile-target>) 
  list("OPEN_DYLAN_RELEASE_BIN=$(OPEN_DYLAN_RELEASE_INSTALL)\\bin",
       "OPEN_DYLAN_RELEASE_INCLUDE=$(OPEN_DYLAN_RELEASE_INSTALL)\\include",
       "OPEN_DYLAN_RELEASE_LIB=$(OPEN_DYLAN_RELEASE_INSTALL)\\lib",
       "OPEN_DYLAN_USER_BIN=$(OPEN_DYLAN_USER_INSTALL)\\bin",
       "OPEN_DYLAN_USER_INCLUDE=$(OPEN_DYLAN_USER_INSTALL)\\include",
       "OPEN_DYLAN_USER_LIB=$(OPEN_DYLAN_USER_INSTALL)\\lib",
       "",
       "APPVER = 4.0",
       "TARGETOS = BOTH",
       "",
       "!include <win32.mak>",
       "",
       "INSTALLOBJ = rem",
       "INSTALLLIB = copy",
       "UNINSTALL = - del /q /f",
       "MKDIR = mkdir",
       "CREATE = touch",
       "",
       "LIB = $(OPEN_DYLAN_USER_LIB);$(OPEN_DYLAN_RELEASE_LIB);$(LIB)",
       "LINKLIB = $(implib) /nologo /out:",
       "LINKAPP = $(link) $(conlflags) $(ldebug) $(conlibsmt) /nologo /out:",
       "",
       // W2 suppresses the warnings about unused labels and local variables.
       // It will, however, generate a warning for each file that /W2 overrides /W3.  (Sigh)
       "CFLAGS = $(cflags) $(cvarsmt) $(cdebug) /W2 /I$(OPEN_DYLAN_RELEASE_INCLUDE) $(OPEN_DYLAN_C_FLAGS)",
       "RFLAGS = $(rcflags) $(rcvars)"
       );
end method;

define method make-install-command
    (target :: <win32-makefile-target>, name) => (makefile-command :: <string>)
  format-to-string("(pushd ..\\%s & $(MAKE) install & popd)", name);
end method;

define method relative-makefile-unit-ref
    (target :: <win32-makefile-target>, name) => (makefile-unit :: <string>)
  format-to-string("..\\%s\\Makefile", name);
end method;

define method compiler-args-for-source (t :: <win32-makefile-target>, name)
  name
end method;

define method compiler-args-for-object (t :: <win32-makefile-target>, name)
  ""
end method;

define constant *win32-makefile-target* = make(<win32-makefile-target>);

define method makefile-target-using-os-name (name == #"win32")
   *win32-makefile-target*
end method;


/// MacOS (MPW) Makefile specifics ...

define class <MPW-makefile-target> (<makefile-target>) end;

define method makefile-rule-separator (t :: <MPW-makefile-target>)
  " \<C4> "			// Option-F (i.e., the "folder" character)
end method makefile-rule-separator;

define method makefile-variable-ref (t :: <MPW-makefile-target>, name)
  concatenate("{", name, "}")
end method makefile-variable-ref;

define method makefile-linker-option-massage (t :: <MPW-makefile-target>, name)
  let name-length = size(name);
  if (name-length > 3 & name[0] = '$' & name[1] = '(' & name[name-length - 1] = ')')
    makefile-variable-ref(t, copy-sequence(name, start: 2, end: name-length - 1))
  else
    name
  end
end method makefile-linker-option-massage;

///---*** NOTE: CWPro5 and/or Carbon will cause changes!
define method additional-platform-libraries
    (t :: <MPW-makefile-target>, for-app? :: <boolean>)
  list("-l\"MSL C.PPC.DLL\"",
       if (for-app?)
	 "-l\"MSL AppRuntime.Lib\""
       else
	 "-l\"MSL ShLibRuntime.Lib\""
       end,
       "-l\"MSL RuntimePPC.DLL\"",
       "-lInterfaceLib",
       "-lMathLib")
end method additional-platform-libraries;

define method library-unit-name (t :: <MPW-makefile-target>, unit)
  concatenate(unit, library-suffix(t));
end method library-unit-name;

define method library-unit-link-ref (t :: <MPW-makefile-target>, lib, unit)
  concatenate("-l", library-unit-name(t, unit))
end method library-unit-link-ref;

define method runtime-unit-link-ref  (t :: <MPW-makefile-target>, unit)
  let unit = select (unit by \=)
	       "run-time" => "DylanRuntime";
	       "gc"       => "BoehmGC";
	       otherwise  => unit
	     end;
  library-unit-link-ref(t, #f, unit)
end method runtime-unit-link-ref;

// Macintosh pathnames frequently contain spaces so we need to quote them appropriately.
// Further, the MPW convention is to include the trailing directory delimiter (:) in the
// value of variables corresponding to directories (e.g., OPEN_DYLAN_USER_LIB) so
// we can't unconditionally add the delimiter after each component.  (SIGH)
define method makefile-pathname (t :: <MPW-makefile-target>, #rest components)
  let almost
    = with-output-to-string (path)
	for (component in components)
	  if (component[0] = '{')
	    format(path, "\"%s\"", component)
	  elseif (position(component, ' ') | position(component, '\t'))
	    format(path, "\"%s\":", component)
	  else
	    format(path, "%s:", component)
	  end
	end
      end;
  if (almost[size(almost) - 1] = ':')
    copy-sequence(almost, end: size(almost) - 1)
  else
    almost
  end
end method makefile-pathname;

define method library-suffix (t :: <MPW-makefile-target>) "Lib" end;
define method executable-suffix (t :: <MPW-makefile-target>) #f end;

define method executable-unit (t :: <MPW-makefile-target>, unit)
  platform-specific(t, unit);
end method executable-unit;

define method makefile-program-setups (t :: <MPW-makefile-target>) 
  list("OPEN_DYLAN_RELEASE_BIN     = {OPEN_DYLAN_RELEASE_INSTALL}Bin:",
       "OPEN_DYLAN_RELEASE_INCLUDE = {OPEN_DYLAN_RELEASE_INSTALL}Include:",
       "OPEN_DYLAN_RELEASE_LIB     = {OPEN_DYLAN_RELEASE_INSTALL}Lib:",
       "OPEN_DYLAN_USER_BIN     = {OPEN_DYLAN_USER_INSTALL}Bin:",
       "OPEN_DYLAN_USER_INCLUDE = {OPEN_DYLAN_USER_INSTALL}Include:",
       "OPEN_DYLAN_USER_LIB     = {OPEN_DYLAN_USER_INSTALL}Lib:",
       "",
       "INSTALLOBJ = Echo Built",	//---*** NOTE: Can we do something that's silent???
       "INSTALLLIB = Duplicate -y",
       "UNINSTALL = Delete -c -ac -i",
       "MKDIR = NewFolder",
       "CREATE = Echo >",
       "",
       //---*** NOTE: There's a bug in CodeWarrior's Link tool that causes it to
       //---*** NOTE: reverse the value of the file type (-t) and creator (-c) options!
       "LINKSTUB = MWLinkPPC -xm stublibrary   -t 'buts' -c ' SPM'"
	 " -fragname {LIBNAME} -@export {LIBNAME}.exp"
	 " -L\"{OPEN_DYLAN_USER_LIB}\" -L\"{OPEN_DYLAN_RELEASE_LIB}\""
	 " -L\"{MWPPCLibraries}\" -L\"{SharedLibraries}\""
	 " -warn -o",
       "LINKLIB  = MWLinkPPC -xm sharedlibrary -t 'blhs' -c 'lyDH'"
	 " -fragname {LIBNAME} -@export {LIBNAME}.exp -sym fullpath"
	 " -L\"{OPEN_DYLAN_USER_LIB}\" -L\"{OPEN_DYLAN_RELEASE_LIB}\""
	 " -L\"{MWPPCLibraries}\" -L\"{SharedLibraries}\""
	 " -warn -o",
       "LINKAPP  = MWLinkPPC -xm application -sym fullpath"
	 " -stacksize 128 -sizemin 8192 -sizemax 8192"
	 " -L\"{OPEN_DYLAN_USER_LIB}\" -L\"{OPEN_DYLAN_RELEASE_LIB}\""
	 " -L\"{MWPPCLibraries}\" -L\"{SharedLibraries}\""
	 " -warn -o",
       "libcmt = -l\"MSL C.PPC.DLL\"",
       "",
       "GCDATASTART = ",
       "GCDATAEND   = ",
       "",
       "CC = MWCPPC",
       "CFLAGS = -sym full -I\"{OPEN_DYLAN_RELEASE_INCLUDE}\""
	 " -w nounusedarg,noimplicitconv,noemptydecl -nomapcr {OPEN_DYLAN_C_FLAGS}",
       "RC = Rez",
       "RFLAGS = "
       );
end method makefile-program-setups;

define method makefile-target-pathname-ref (t :: <MPW-makefile-target>)
  "{Targ}"
end method makefile-target-pathname-ref;

// This is quite messy as MPW doesn't have pushd/popd and, further, Make 
// doesn't actually run the commands, it simply echoes them!  (The user
// is supposed to select the commands in the Worksheet and then execute them.)
define method make-install-command
    (target :: <MPW-makefile-target>, name) => (makefile-command :: <string>)
  format-to-string("Set OldDir `Directory`\n"
		     "\tDirectory ::%s\n"
		     "\tMake install > \"{TempFolder}\"%s\n"
		     "\t\"{TempFolder}\"%s\n"
		     "\tDirectory \"{OldDir}\"\n"
		     "\tUnset OldDir",
		   name, name, name)
end method make-install-command;

define method relative-makefile-unit-ref
    (target :: <MPW-makefile-target>, name) => (makefile-unit :: <string>)
  format-to-string("\"::%s:Makefile\"", name);
end method relative-makefile-unit-ref;

define method compiler-args-for-source (t :: <MPW-makefile-target>, name)
  name
end method compiler-args-for-source;

define method compiler-args-for-object (t :: <MPW-makefile-target>, name)
  format-to-string("-o %s", name)
end method compiler-args-for-object;

define constant *MPW-makefile-target* = make(<MPW-makefile-target>);

define method makefile-target-using-os-name (name == #"carbon")
  *MPW-makefile-target*
end method makefile-target-using-os-name;


//// Generic makefile generation.

/// Generate a list of all C libraries used by this Dylan library and all used
/// Dylan libraries.  Do our best to avoid duplicate entries in the list and to
/// put those entries of the "deeper" used libraries before those of the "shallower"
/// libraries.  (I.e., if A uses B and C, B uses D, and C uses D and E, the order of
/// C libraries should be from D, B, E, C, A.)
define function all-c-libraries (library-description) => (c-libraries :: <sequence>)
  let all-c-libraries = make(<stretchy-vector>);
  let processed-lds = make(<table>);
  local method recurse (ld)
    unless (element(processed-lds, ld, default: #f))
      let lds = directly-used-library-descriptions(ld);
      for (ld in lds using backward-iteration-protocol)
        recurse(ld)
      end;
      element(processed-lds, ld) := #t;
      let c-libraries
        = apply(ldbs-c-libraries, library-description-build-settings(ld));
      for (c-library in c-libraries)
        add-new!(all-c-libraries, c-library, test: \=);
      end;
    end;
  end method recurse;
  recurse(library-description);
  as(<list>, all-c-libraries)
end function all-c-libraries;

// This is a define method and out here rather than a local method in
// the above to work around an emulator bug in #all-keys handling.
define method ldbs-c-libraries 
    (#key c-libraries = #(), #all-keys) => (c-libraries :: <sequence>)
  c-libraries
end method ldbs-c-libraries;

define method emit-makefile 
    (library-description, units, #rest build-keys, #key, #all-keys)
  let target = makefile-target(library-description);
  local method doit (#key executable = #f,
			  base-address = #f,
			  linker-options = #(),
			  c-source-files = #(),
			  c-header-files = #(),
			  c-object-files = #(),
			  rc-files = #(),
			  c-libraries = #())
	  let c-source-files = map(locator-name, c-source-files);
	  let c-header-files = map(locator-name, c-header-files);
	  let c-object-files = map(locator-name, c-object-files);
	  let rc-files = map(locator-name, rc-files);
	  emit-target-makefile(current-back-end(), target,
			       makefile-unit-name(target),
			       library-description, units, 
			       executable: executable,
			       base-address: base-address,
			       linker-options: 
				 map(curry(makefile-linker-option-massage, target),
				     linker-options),
			       c-source-files: c-source-files,
			       c-header-files: c-header-files,
			       c-object-files: c-object-files,
			       rc-files: rc-files,
			       c-libraries: 
				 map(curry(makefile-linker-option-massage, target),
				     all-c-libraries(library-description)))
	end method doit;
  apply(doit, build-keys)
end method emit-makefile;

//// High level utilities.

define method emit-makefile-program-setups (t, stream)
  format(stream, "# Application setup.\n\n");
  let setups = makefile-program-setups(t);
  for (setup in setups)
    format(stream, "%s\n", setup);
  end;
end method;

define method backend-object-file-name (t, source-file-locator)
  let backend-locator = as(<file-locator>, source-file-locator);
  as(<string>,
     merge-locators(make(<file-locator>, base: locator-base(backend-locator),
			                 extension: object-suffix(current-back-end(), t)),
		    backend-locator))
end;

define method backend-resource-file-name (t, source-file-locator)
  let backend-locator = as(<file-locator>, source-file-locator);
  as(<string>,
     merge-locators(make(<file-locator>, base: locator-base(backend-locator),
			                 extension: resource-suffix(current-back-end(), t)),
		    backend-locator))
end;

/// Emits makefile dependencies for any non-Dylan source files included in the project.
define method emit-makefile-object-dependencies
    (back-end, t, stream, sources, objects, objects-names, compilers, dependencies)
  format(stream, "# Object dependencies.\n\n");
  for (source in sources, object in objects, object-name in objects-names,
       compiler in compilers, dependency in dependencies)
    emit-makefile-rule
      (t, stream, object, concatenate(list(source), dependency),
       format-to-string("%s %s %s", 
                        compiler,
			compiler-args-for-source(t, source),
                        compiler-args-for-object(t, object-name)),
       makefile-command(t,
			"{INSTALLOBJ} %s %s",
			object-name, makefile-target-pathname-ref(t)));
    format(stream, "\n");
  end;
  format(stream, "\n");
end method emit-makefile-object-dependencies;

//// Low level utilities.

define method emit-makefile-header (stream)
  format(stream, "# Machine-generated Makefile - do not edit.\n\n");
end method;

define method emit-makefile-separator (stream)
  format(stream, "\n");
end method;

define method emit-makefile-footer (stream)
  format(stream, "\n# eof\n");
end method;

define method emit-makefile-definition (stream, name, units)
  format(stream, "%s = ", name);
  emit-makefile-units(stream, units);
  format(stream, "\n");
end method;

define method emit-makefile-rule (t, stream, target, units, #rest operations)
  format(stream, "%s%s", target, makefile-rule-separator(t));
  emit-makefile-units(stream, units);
  format(stream, "\n");
  for (operation in operations)
    format(stream, "\t%s\n", operation);
  end for;
end method;

define method emit-makefile-units (stream, units)
  for (unit in units) format(stream, "%s ", unit) end;
end method;

define method source-units (t, units)
  map(curry(source-unit, t), units)
end method;

define method object-units (t, units)
  map(curry(object-unit, t), units)
end method;

define method object-units-names (t, units)
  map(curry(object-unit-name, t), units)
end method;

define method glue-unit (unit)
  "_glue"
end method;

define method main-unit (unit)
  "_main"
end method;

define method platform-specific (t, name)
  name
end method;

define method platform-specific-concatenate (t, #rest names)
  apply(concatenate, names)
end method;

// eof



syntax highlighted by Code2HTML, v. 0.9.1