/* dllar - a tool to build both a .dll and an .a file
 * from a set of object (.o) files for EMX/OS2.
 *
 * Written by Andrew Zabolotny, bit@freya.etu.ru
 *
 * This program will accept a set of files on the command line.
 * All the public symbols from the .o files will be exported into
 * a .DEF file, then linker will be run (through gcc) against them to
 * build a shared library consisting of all given .o files. All libraries
 * (.a) will be first decompressed into component .o files then act as
 * described above. You can optionally give a description (-d "description")
 * which will be put into .DLL. To see the list of accepted options (as well
 * as command-line format) simply run this program without options. The .DLL
 * is built to be imported by name (there is no guarantee that new versions
 * of the library you build will have same ordinals for same symbols).
 *
 * dllar is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * dllar is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with dllar; see the file COPYING.  If not, write to the Free
 * Software Foundation, 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 */

/* To successfuly run this program you will need:
 * - Current drive should have LFN support (HPFS, ext2, network, etc)
 *   (Sometimes dllar generates filenames which won't fit 8.3 scheme)
 * - gcc
 *   (used to build the .dll)
 * - emxexp
 *   (used to create .def file from .o files)
 * - emximp
 *   (used to create .a file from .def file)
 * - GNU text utilites (cat, sort, uniq)
 *   used to process emxexp output
 * - lxlite (optional, see flag below)
 *   (used for general .dll cleanup)
 */

 flag_USE_LXLITE = 1;

 call RxFuncAdd 'SysLoadFuncs', 'RexxUtil', 'SysLoadFuncs'
 call SysLoadFuncs

 parse arg cmdLine;
 outFile = '';
 inputFiles.0 = 0;
 description = '';
 CCFLAGS = '-s -Zcrtdll';
 EXTRA_CCFLAGS = '';
 EXPORT_BY_ORDINALS = 0;
 exclude_symbols = '';
 library_flags = '';
 curDir = directory();
 curDirS = curDir;
 if (right(curDirS, 1) \= '\')
  then curDirS = curDirS||'\';

 do I = 1 to words(cmdLine)
  tmp = word(cmdLine, I);
  if left(tmp, 1) = '-'
   then select
         when abbrev('output', substr(tmp, 2), 1)
          then do
                i = i + 1;
                outFile = word(cmdLine, i);
               end;
         when abbrev('description', substr(tmp, 2), 1)
          then description = GetLongArg();
         when abbrev('flags', substr(tmp, 2), 1)
          then CCFLAGS = GetLongArg();
         when abbrev('help', substr(tmp, 2), 1)
          then call PrintHelp;
	 when abbrev('ordinals', substr(tmp, 2), 3)
	  then EXPORT_BY_ORDINALS = 1;
         when abbrev('exclude', substr(tmp, 2), 2)
	  then exclude_symbols = exclude_symbols||GetLongArg()' ';
         when abbrev('libflags', substr(tmp, 2), 4)
	  then library_flags = library_flags||GetLongArg()' ';
         when abbrev('nocrtdll', substr(tmp, 2), 5)
	  then CCFLAGS = '-s';
         otherwise
          EXTRA_CCFLAGS = EXTRA_CCFLAGS' 'tmp;
        end
   else do
         rc = SysFileTree(tmp, "files", "FO");
         if (rc = 0)
          then do J = 1 to files.0
                inputFiles.0 = inputFiles.0 + 1;
                K = inputFiles.0;
                inputFiles.K = files.J;
               end;
          else files.0 = 0;
          if (files.0 = 0)
           then do
                 say 'ERROR: No file(s) found: "'tmp'"';
                 exit 4;
                end;
        end;
 end;
 if (inputFiles.0 = 0)
  then do
        say 'dllar: no input files'
        call PrintHelp;
       end;

/* Now extract all .o files from .a files */
 do I = 1 to inputFiles.0
  if right(inputFiles.I, 2) = '.a'
   then do
         fullname = inputFiles.I;
         inputFiles.I = '$_'filespec('NAME', fullname);
	 inputFiles.I = left(inputFiles.I, length(inputFiles.I) - 2);
         '@mkdir 'inputFiles.I;
         if (rc \= 0)
          then do
                say 'Failed to create subdirectory ./'inputFiles.I;
                call CleanUp;
                exit 3;
               end;
         /* Prefix with '!' to indicate archive */
         inputFiles.I = '!'inputFiles.I;
         call doCommand('cd 'substr(inputFiles.I, 2)' & ar x 'fullname);
         call directory(curDir);
         rc = SysFileTree(substr(inputFiles.I, 2)'\*.o', 'files', 'FO');
         if (rc = 0)
          then do
                inputFiles.0 = inputFiles.0 + 1;
                K = inputFiles.0;
                inputFiles.K = substr(inputFiles.I, 2)'/*.o';
               end;
          else say 'WARNING: there are no files in archive "'substr(inputFiles.I, 2)'"';
        end;
 end;

 /* Now remove extra directory prefixes */
 do I = 1 to inputFiles.0
  if left(inputFiles.I, length(curDirS)) = curDirS
   then inputFiles.I = substr(inputFiles.I, length(curDirS) + 1);
 end;

 do_backup = 0;
 if (outFile = '')
  then do
         do_backup = 1;
         outFile = inputFiles.1;
       end;
 /* If its an archive, remove the '!' and the '$_' prefixes */
 if (left(outFile, 3) = '!$_')
  then outFile = substr(outFile, 4);
 dotpos = lastpos('.', outFile);
 if dotpos > 0
  then do
        ext = translate(substr(outFile, dotpos + 1));
        if (ext = 'DLL') | (ext = 'O') | (ext = 'A')
         then outFile = substr(outFile, 1, dotpos - 1);
       end;

 EXTRA_CCFLAGS = substr(EXTRA_CCFLAGS, 2);

 defFile = outFile'.def';
 dllFile = outFile'.dll';
 arcFile = outFile'.a';

 if (do_backup & stream(arcFile, 'C', 'query exists') \= '')
  then call doCommand('ren 'arcFile' 'outFile'_s.a');

 call SysFileDelete('$_'defFile);
 gccCmdl = '';
 do I = 1 to inputFiles.0
  if (left(inputFiles.I, 1) \= '!')
   then do
         call doCommand('emxexp -u' inputFiles.I' >>$_'defFile);
         gccCmdl = gccCmdl' 'inputFiles.I;
        end;
 end;

 call SysFileDelete(defFile);
 call lineOut defFile, 'LIBRARY 'filespec('NAME', outFile)' 'library_flags;
 if (length(description) > 0)
  then call lineOut defFile, 'DESCRIPTION "'description'"';
 call lineOut defFile, 'EXPORTS';
 call doCommand('cat $_'defFile' | sort | uniq | rxqueue');
 ordinal = 1;
 do while queued() > 0
  parse pull line;
  if (length(line) > 0) & (word(line, 1) \= ';') & (export_ok(line))
   then do
	 if EXPORT_BY_ORDINALS
	  then do
		line = line||copies('	',(71-length(line))%8)'@'ordinal' NONAME';
		ordinal = ordinal + 1;
	       end;
	 call lineOut defFile, line;
	end;
 end;
 call stream defFile, 'C', 'CLOSE';
 call doCommand('rm -f $_'defFile);

 call doCommand('gcc 'CCFLAGS' -Zdll -o 'dllFile defFile||gccCmdl' 'EXTRA_CCFLAGS);
 call doCommand('emximp -o 'arcFile defFile);
 if (flag_USE_LXLITE)
  then do
        if (EXPORT_BY_ORDINALS)
         then add_flags = '-ynd';
         else add_flags = '';
        call doCommand('lxlite -cs -t: -mrn -mln 'add_flags' 'dllFile);
       end;
 call CleanUp;
exit;

PrintHelp:
 say 'Usage: dllar [-o[utput] output_file] [-d[escription] "dll descrption"]'
 say '       [-f[lags] "CCFLAGS"] [-ord[inals]] -ex[clude] "symbol(s)"'
 say '       [-libf[lags] "{INIT|TERM}{GLOBAL|INSTANCE}"] [-nocrt[dll]]'
 say '       [*.o] [*.a]'
 say '*> "output_file" should have no extension'
 say '   If it has the .o, .a or .dll extension, it is automatically removed'
 say '   The import library name is derived from this and is set to "name".a'
 say '*> "flags" should be any set of valid GCC flags (default: -s -Zcrtdll)'
 say '   These flags will be put at the start of GCC command line'
 say '*> -ord[inals] tells dllar to export entries by ordinals. Be careful.'
 say '*> -ex[clude] defines symbols which will not be exported. You can define'
 say '   multiple symbols, for example -ex "myfunc yourfunc _GLOBAL*"'
 say '   If the last character of a symbol is "*", all symbols beginning'
 say '   with the prefix before "*" will be exclude, (see _GLOBAL* above)'
 say '*> -libf[lags] can be used to add INITGLOBAL/INITINSTANCE and/or'
 say '   TERMGLOBAL/TERMINSTANCE flags to the dynamically-linked library'
 say '*> -nocrtdll switch will disable linking the library against emx''s'
 say '   C runtime DLLs'
 say '*> All other switches (for example -L./ or -lmylib) will be passed'
 say '   unchanged to GCC at the end of command line'
 say '*> If you create a DLL from a library and you do not specify -o,'
 say '   the basename for DLL and import library will be set to library name,'
 say '   the initial library will be renamed to 'name'_s.a (_s for static)'
 say '   i.e. "dllar gcc.a" will create gcc.dll and gcc.a, and the initial'
 say '   library will be renamed into gcc_s.a'
 say '--------'
 say 'Example:'
 say '   dllar -o gcc290.dll libgcc.a -d "GNU C runtime library" -ord'
 say '    -ex "__main __ctordtor*" -libf "INITINSTANCE TERMINSTANCE"'
 call CleanUp;
exit 1;

GetLongArg:
 i = i + 1;
 _tmp_ = word(cmdLine, i);
 if (left(_tmp_, 1) = '"') | (left(_tmp_, 1) = "'")
  then do
        do while (i < words(cmdLine) &,
                  right(_tmp_, 1) \= left(_tmp_, 1))
         i = i + 1;
         if (_tmp_ = '')
           then _tmp_ = word(cmdLine, i);
           else _tmp_ = _tmp_' 'word(cmdLine, i);
        end;
        if (right(_tmp_, 1) = left(_tmp_, 1))
         then _tmp_ = substr(_tmp_, 2, length(_tmp_) - 2);
       end;
return _tmp_;

export_ok:
 procedure expose exclude_symbols;
 parse arg line;
 do i = 1 to words(exclude_symbols)
  noexport = '"'word(exclude_symbols, i);
  if right(noexport, 1) = '*'
   then noexport = left(noexport, length(noexport) - 1)
   else noexport = noexport'"';
  if pos(noexport, line) > 0
   then return 0;
 end;
return 1;

doCommand:
 parse arg _cmd_;
 say _cmd_;
 '@'_cmd_;
 if (rc \= 0)
 then do
       say 'command failed, exit code='rc;
       call CleanUp;
       exit 2;
      end;
return;

CleanUp:
 call directory(curDir);
 do i = inputFiles.0 to 1 by -1
  if left(inputFiles.I, 1) = '!'
   then call doCommand('rm -rf 'substr(inputFiles.I, 2));
 end;
return;
