1 /*****************************************************************************/
5 /* Main module for the cl65 compile and link utility */
9 /* (C) 1999-2000 Ullrich von Bassewitz */
11 /* D-70597 Stuttgart */
12 /* EMail: uz@musoftware.de */
15 /* This software is provided 'as-is', without any expressed or implied */
16 /* warranty. In no event will the authors be held liable for any damages */
17 /* arising from the use of this software. */
19 /* Permission is granted to anyone to use this software for any purpose, */
20 /* including commercial applications, and to alter it and redistribute it */
21 /* freely, subject to the following restrictions: */
23 /* 1. The origin of this software must not be misrepresented; you must not */
24 /* claim that you wrote the original software. If you use this software */
25 /* in a product, an acknowledgment in the product documentation would be */
26 /* appreciated but is not required. */
27 /* 2. Altered source versions must be plainly marked as such, and must not */
28 /* be misrepresented as being the original software. */
29 /* 3. This notice may not be removed or altered from any source */
32 /*****************************************************************************/
42 # include <process.h> /* DOS, OS/2 and Windows */
44 # include "spawn.h" /* All others */
47 #include "../common/version.h"
55 /*****************************************************************************/
57 /*****************************************************************************/
61 /* Struct that describes a command */
62 typedef struct CmdDesc_ CmdDesc;
64 char* Name; /* The command name */
66 unsigned ArgCount; /* Count of arguments */
67 unsigned ArgMax; /* Maximum count of arguments */
68 char** Args; /* The arguments */
70 unsigned FileCount; /* Count of files to translate */
71 unsigned FileMax; /* Maximum count of files */
72 char** Files; /* The files */
75 /* Command descriptors for the different programs */
76 static CmdDesc CC65 = { 0, 0, 0, 0, 0, 0, 0 };
77 static CmdDesc CA65 = { 0, 0, 0, 0, 0, 0, 0 };
78 static CmdDesc LD65 = { 0, 0, 0, 0, 0, 0, 0 };
89 /* Default file type, used if type unknown */
90 static unsigned DefaultFileType = FILETYPE_UNKNOWN;
92 /* Variables controlling the steps we're doing */
93 static int DontLink = 0;
94 static int DontAssemble = 0;
96 /* The name of the output file, NULL if none given */
97 static const char* OutputName = 0;
99 /* The name of the linker configuration file if given */
100 static const char* LinkerConfig = 0;
102 /* The name of the first input file. This will be used to construct the
103 * executable file name if no explicit name is given.
105 static const char* FirstInput = 0;
107 /* The target system */
112 TGT_ATARI = TGT_FIRSTREAL,
123 } Target = TGT_UNKNOWN;
125 /* Names of the target systems sorted by target name */
126 static const char* TargetNames [] = {
140 /* Name of the crt0 object file and the runtime library */
141 static char* TargetCRT0 = 0;
142 static char* TargetLib = 0;
146 /*****************************************************************************/
147 /* String handling */
148 /*****************************************************************************/
152 static const char* FindExt (const char* Name)
153 /* Return a pointer to the file extension in Name or NULL if there is none */
157 /* Get the length of the name */
158 unsigned Len = strlen (Name);
163 /* Get a pointer to the last character */
166 /* Search for the dot, beware of subdirectories */
167 while (S >= Name && *S != '.' && *S != '\\' && *S != '/') {
171 /* Did we find an extension? */
181 static char* ForceExt (const char* Name, const char* Ext)
182 /* Return a new filename with the new extension */
185 const char* P = FindExt (Name);
187 /* No dot, add the extension */
188 Out = Xmalloc (strlen (Name) + strlen (Ext) + 1);
192 Out = Xmalloc (P - Name + strlen (Ext) + 1);
193 memcpy (Out, Name, P - Name);
194 strcpy (Out + (P - Name), Ext);
201 /*****************************************************************************/
202 /* Determine a file type */
203 /*****************************************************************************/
207 static unsigned GetFileType (const char* File)
208 /* Determine the type of the given file */
210 /* Table mapping extensions to file types */
211 static const struct {
215 { ".c", FILETYPE_C },
216 { ".s", FILETYPE_ASM },
217 { ".asm", FILETYPE_ASM },
218 { ".o", FILETYPE_OBJ },
219 { ".obj", FILETYPE_OBJ },
220 { ".a", FILETYPE_LIB },
221 { ".lib", FILETYPE_LIB },
226 /* Determine the file type by the extension */
227 const char* Ext = FindExt (File);
229 /* Do we have an extension? */
231 return DefaultFileType;
234 /* Check for known extensions */
235 for (I = 0; I < sizeof (FileTypes) / sizeof (FileTypes [0]); ++I) {
236 if (strcmp (FileTypes [I].Ext, Ext) == 0) {
238 return FileTypes [I].Type;
242 /* Not found, return the default */
243 return DefaultFileType;
248 /*****************************************************************************/
249 /* Command structure handling */
250 /*****************************************************************************/
254 static void CmdAddArg (CmdDesc* Cmd, const char* Arg)
255 /* Add a new argument to the command */
257 /* Expand the argument vector if needed */
258 if (Cmd->ArgCount == Cmd->ArgMax) {
259 unsigned NewMax = Cmd->ArgMax + 10;
260 char** NewArgs = Xmalloc (NewMax * sizeof (char*));
261 memcpy (NewArgs, Cmd->Args, Cmd->ArgMax * sizeof (char*));
264 Cmd->ArgMax = NewMax;
267 /* Add a copy of the new argument, allow a NULL pointer */
269 Cmd->Args [Cmd->ArgCount++] = StrDup (Arg);
271 Cmd->Args [Cmd->ArgCount++] = 0;
277 static void CmdDelArgs (CmdDesc* Cmd, unsigned LastValid)
278 /* Remove all arguments with an index greater than LastValid */
280 while (Cmd->ArgCount > LastValid) {
282 Xfree (Cmd->Args [Cmd->ArgCount]);
283 Cmd->Args [Cmd->ArgCount] = 0;
289 static void CmdAddFile (CmdDesc* Cmd, const char* File)
290 /* Add a new file to the command */
292 /* Expand the file vector if needed */
293 if (Cmd->FileCount == Cmd->FileMax) {
294 unsigned NewMax = Cmd->FileMax + 10;
295 char** NewFiles = Xmalloc (NewMax * sizeof (char*));
296 memcpy (NewFiles, Cmd->Files, Cmd->FileMax * sizeof (char*));
298 Cmd->Files = NewFiles;
299 Cmd->FileMax = NewMax;
302 /* Add a copy of the file name, allow a NULL pointer */
304 Cmd->Files [Cmd->FileCount++] = StrDup (File);
306 Cmd->Files [Cmd->FileCount++] = 0;
312 static void CmdInit (CmdDesc* Cmd, const char* Path)
313 /* Initialize the command using the given path to the executable */
315 /* Remember the command */
316 Cmd->Name = StrDup (Path);
318 /* Use the command name as first argument */
319 CmdAddArg (Cmd, Path);
324 static void CmdSetOutput (CmdDesc* Cmd, const char* File)
325 /* Set the output file in a command desc */
327 CmdAddArg (Cmd, "-o");
328 CmdAddArg (Cmd, File);
333 static void CmdSetTarget (CmdDesc* Cmd, int Target)
334 /* Set the output file in a command desc */
336 if (Target == TGT_UNKNOWN) {
337 /* Use C64 as default */
341 if (Target != TGT_NONE) {
342 CmdAddArg (Cmd, "-t");
343 CmdAddArg (Cmd, TargetNames[Target]);
349 /*****************************************************************************/
350 /* Target handling */
351 /*****************************************************************************/
355 static int MapTarget (const char* Name)
356 /* Map a target name to a system code. Abort on errors */
360 /* Check for a numeric target */
361 if (isdigit (*Name)) {
362 int Target = atoi (Name);
363 if (Target >= 0 && Target < TGT_COUNT) {
368 /* Check for a target string */
369 for (I = 0; I < TGT_COUNT; ++I) {
370 if (strcmp (TargetNames [I], Name) == 0) {
376 Error ("No such target system: `%s'", Name);
377 return -1; /* Not reached */
382 static void SetTargetFiles (void)
383 /* Set the target system files */
385 /* Determine the names of the default startup and library file */
386 if (Target >= TGT_FIRSTREAL) {
388 /* Get a pointer to the system name and its length */
389 const char* TargetName = TargetNames [Target];
390 unsigned TargetNameLen = strlen (TargetName);
392 /* Set the startup file */
393 TargetCRT0 = Xmalloc (TargetNameLen + 2 + 1);
394 strcpy (TargetCRT0, TargetName);
395 strcat (TargetCRT0, ".o");
397 /* Set the library file */
398 TargetLib = Xmalloc (TargetNameLen + 4 + 1);
399 strcpy (TargetLib, TargetName);
400 strcat (TargetLib, ".lib");
407 static void SetTargetByName (const char* Name)
408 /* Set the target system by name */
410 Target = MapTarget (Name);
416 /*****************************************************************************/
418 /*****************************************************************************/
422 static void ExecProgram (CmdDesc* Cmd)
423 /* Execute a subprocess with the given name/parameters. Exit on errors. */
425 /* Call the program */
426 int Status = spawnvp (P_WAIT, Cmd->Name, Cmd->Args);
428 /* Check the result code */
430 /* Error executing the program */
431 Error ("Cannot execute `%s': %s", Cmd->Name, strerror (errno));
432 } else if (Status != 0) {
433 /* Called program had an error */
440 static void Link (void)
441 /* Link the resulting executable */
445 /* If we have a linker config file given, set the linker config file.
446 * Otherwise set the target system.
449 CmdAddArg (&LD65, "-C");
450 CmdAddArg (&LD65, LinkerConfig);
452 if (Target == TGT_UNKNOWN) {
453 /* Use c64 instead */
457 CmdSetTarget (&LD65, Target);
460 /* Since linking is always the final step, if we have an output file name
461 * given, set it here. If we don't have an explicit output name given,
462 * try to build one from the name of the first input file.
466 CmdAddArg (&LD65, "-o");
467 CmdAddArg (&LD65, OutputName);
469 } else if (FirstInput && FindExt (FirstInput)) { /* Only if ext present! */
471 char* Output = ForceExt (FirstInput, "");
472 CmdAddArg (&LD65, "-o");
473 CmdAddArg (&LD65, Output);
478 /* If we have a startup file, add its name as a parameter */
480 CmdAddArg (&LD65, TargetCRT0);
483 /* Add all object files as parameters */
484 for (I = 0; I < LD65.FileCount; ++I) {
485 CmdAddArg (&LD65, LD65.Files [I]);
488 /* Add the system runtime library */
490 CmdAddArg (&LD65, TargetLib);
493 /* Terminate the argument list with a NULL pointer */
494 CmdAddArg (&LD65, 0);
496 /* Call the linker */
502 static void Assemble (const char* File)
503 /* Assemble the given file */
505 /* Remember the current assembler argument count */
506 unsigned ArgCount = CA65.ArgCount;
508 /* If we won't link, this is the final step. In this case, set the
511 if (DontLink && OutputName) {
512 CmdSetOutput (&CA65, OutputName);
514 /* The object file name will be the name of the source file
515 * with .s replaced by ".o". Add this file to the list of
518 char* ObjName = ForceExt (File, ".o");
519 CmdAddFile (&LD65, ObjName);
523 /* Add the file as argument for the assembler */
524 CmdAddArg (&CA65, File);
526 /* Add a NULL pointer to terminate the argument list */
527 CmdAddArg (&CA65, 0);
529 /* Run the assembler */
532 /* Remove the excess arguments */
533 CmdDelArgs (&CA65, ArgCount);
538 static void Compile (const char* File)
539 /* Compile the given file */
543 /* Remember the current assembler argument count */
544 unsigned ArgCount = CC65.ArgCount;
546 /* Set the target system */
547 CmdSetTarget (&CC65, Target);
549 /* If we won't link, this is the final step. In this case, set the
552 if (DontAssemble && OutputName) {
553 CmdSetOutput (&CC65, OutputName);
555 /* The assembler file name will be the name of the source file
556 * with .c replaced by ".s".
558 AsmName = ForceExt (File, ".s");
561 /* Add the file as argument for the compiler */
562 CmdAddArg (&CC65, File);
564 /* Add a NULL pointer to terminate the argument list */
565 CmdAddArg (&CC65, 0);
567 /* Run the compiler */
570 /* Remove the excess arguments */
571 CmdDelArgs (&CC65, ArgCount);
573 /* If this is not the final step, assemble the generated file, then
578 if (remove (AsmName) < 0) {
579 Warning ("Cannot remove temporary file `%s': %s",
580 AsmName, strerror (errno));
588 /*****************************************************************************/
590 /*****************************************************************************/
594 static void Usage (void)
595 /* Print usage information and exit */
598 "Usage: %s [options] file\n"
600 " -A\t\t\tStrict ANSI mode\n"
601 " -C name\t\tUse linker config file\n"
602 " -Cl\t\t\tMake local variables static\n"
603 " -D sym[=defn]\t\tDefine a preprocessor symbol\n"
604 " -I dir\t\tSet a compiler include directory path\n"
605 " -Ln name\t\tCreate a VICE label file\n"
606 " -O\t\t\tOptimize code\n"
607 " -Oi\t\t\tOptimize code, inline functions\n"
608 " -Or\t\t\tOptimize code, honour the register keyword\n"
609 " -Os\t\t\tOptimize code, inline known C funtions\n"
610 " -S\t\t\tCompile but don't assemble and link\n"
611 " -V\t\t\tPrint the version number\n"
612 " -W\t\t\tSuppress warnings\n"
613 " -c\t\t\tCompiler and assemble but don't link\n"
614 " -d\t\t\tDebug mode\n"
615 " -g\t\t\tAdd debug info\n"
616 " -h\t\t\tHelp (this text)\n"
617 " -m name\t\tCreate a map file\n"
618 " -o name\t\tName the output file\n"
619 " -t sys\t\tSet the target system\n"
620 " -v\t\t\tVerbose mode\n"
621 " -vm\t\t\tVerbose map file\n"
624 " --ansi\t\tStrict ANSI mode\n"
625 " --asm-include-dir dir\tSet an assembler include directory\n"
626 " --debug\t\tDebug mode\n"
627 " --debug-info\t\tAdd debug info\n"
628 " --help\t\tHelp (this text)\n"
629 " --include-dir dir\tSet a compiler include directory path\n"
630 " --version\t\tPrint the version number\n"
631 " --target sys\t\tSet the target system\n"
632 " --verbose\t\tVerbose mode\n",
638 static const char* GetArg (int* ArgNum, char* argv [], unsigned Len)
639 /* Get an option argument */
641 const char* Arg = argv [*ArgNum];
642 if (Arg [Len] != '\0') {
643 /* Argument appended */
646 /* Separate argument */
647 Arg = argv [*ArgNum + 1];
649 /* End of arguments */
650 fprintf (stderr, "Option requires an argument: %s\n", argv [*ArgNum]);
660 static void UnknownOption (const char* Arg)
661 /* Print an error about a wrong argument */
663 Error ("Unknown option: `%s', use -h for help", Arg);
668 static void NeedArg (const char* Arg)
669 /* Print an error about a missing option argument and exit. */
671 Error ("Option requires an argument: %s\n", Arg);
676 static void OptAnsi (const char* Opt)
677 /* Strict ANSI mode (compiler) */
679 CmdAddArg (&CC65, "-A");
684 static void OptAsmIncludeDir (const char* Opt, const char* Arg)
685 /* Include directory (assembler) */
690 CmdAddArg (&CA65, "-I");
691 CmdAddArg (&CA65, Arg);
696 static void OptDebug (const char* Opt)
697 /* Debug mode (compiler) */
699 CmdAddArg (&CC65, "-d");
704 static void OptDebugInfo (const char* Opt)
705 /* Debug Info - add to compiler and assembler */
707 CmdAddArg (&CC65, "-g");
708 CmdAddArg (&CA65, "-g");
713 static void OptHelp (const char* Opt)
714 /* Print help - cl65 */
722 static void OptIncludeDir (const char* Opt, const char* Arg)
723 /* Include directory (compiler) */
728 CmdAddArg (&CC65, "-I");
729 CmdAddArg (&CC65, Arg);
734 static void OptTarget (const char* Opt, const char* Arg)
735 /* Set the target system */
740 SetTargetByName (Arg);
745 static void OptVerbose (const char* Opt)
746 /* Verbose mode (compiler, assembler, linker) */
748 CmdAddArg (&CC65, "-v");
749 CmdAddArg (&CA65, "-v");
750 CmdAddArg (&LD65, "-v");
755 static void OptVersion (const char* Opt)
756 /* Print version number */
759 "cl65 V%u.%u.%u - (C) Copyright 1998-2000 Ullrich von Bassewitz\n",
760 VER_MAJOR, VER_MINOR, VER_PATCH);
765 static void LongOption (int* ArgNum, char* argv [])
766 /* Handle a long command line option */
768 const char* Opt = argv [*ArgNum];
769 const char* Arg = argv [*ArgNum+1];
771 if (strcmp (Opt, "--ansi") == 0) {
773 } else if (strcmp (Opt, "--asm-include-dir") == 0) {
774 OptAsmIncludeDir (Opt, Arg);
776 } else if (strcmp (Opt, "--debug") == 0) {
778 } else if (strcmp (Opt, "--debug-info") == 0) {
780 } else if (strcmp (Opt, "--help") == 0) {
782 } else if (strcmp (Opt, "--include-dir") == 0) {
783 OptIncludeDir (Opt, Arg);
785 } else if (strcmp (Opt, "--target") == 0) {
786 OptTarget (Opt, Arg);
788 } else if (strcmp (Opt, "--verbose") == 0) {
790 } else if (strcmp (Opt, "--version") == 0) {
799 int main (int argc, char* argv [])
800 /* Utility main program */
804 /* Initialize the command descriptors */
805 CmdInit (&CC65, "cc65");
806 CmdInit (&CA65, "ca65");
807 CmdInit (&LD65, "ld65");
809 /* Check the parameters */
813 /* Get the argument */
814 const char* Arg = argv [I];
816 /* Check for an option */
817 if (Arg [0] == '-') {
822 LongOption (&I, argv);
826 /* Strict ANSI mode (compiler) */
831 if (Arg[2] == 'l' && Arg[3] == '\0') {
832 /* Make local variables static */
833 CmdAddArg (&CC65, "-Cl");
835 /* Specify linker config file */
836 LinkerConfig = GetArg (&I, argv, 2);
841 /* Define a preprocessor symbol (compiler) */
842 CmdAddArg (&CC65, "-D");
843 CmdAddArg (&CC65, GetArg (&I, argv, 2));
847 /* Include directory (compiler) */
848 OptIncludeDir (Arg, GetArg (&I, argv, 2));
853 /* VICE label file (linker) */
854 CmdAddArg (&LD65, "-Ln");
855 CmdAddArg (&LD65, GetArg (&I, argv, 3));
862 /* Optimize code (compiler, also covers -Oi and others) */
863 CmdAddArg (&CC65, Arg);
867 /* Dont assemble and link the created files */
868 DontLink = DontAssemble = 1;
872 /* Include source as comment (compiler) */
873 CmdAddArg (&CC65, "-T");
877 /* Print version number */
882 /* Suppress warnings - compiler and assembler */
883 CmdAddArg (&CC65, "-W");
884 CmdAddArg (&CA65, "-W");
885 CmdAddArg (&CA65, "0");
889 /* Don't link the resulting files */
894 /* Debug mode (compiler) */
899 /* Debugging - add to compiler and assembler */
905 /* Print help - cl65 */
910 /* Create a map file (linker) */
911 CmdAddArg (&LD65, "-m");
912 CmdAddArg (&LD65, GetArg (&I, argv, 2));
916 /* Name the output file */
917 OutputName = GetArg (&I, argv, 2);
921 /* Set target system - compiler and linker */
922 OptTarget (Arg, GetArg (&I, argv, 2));
926 if (Arg [2] == 'm') {
927 /* Verbose map file (linker) */
928 CmdAddArg (&LD65, "-vm");
930 /* Verbose mode (compiler, assembler, linker) */
940 /* Remember the first file name */
941 if (FirstInput == 0) {
945 /* Determine the file type by the extension */
946 switch (GetFileType (Arg)) {
949 /* Compile the file */
954 /* Assemble the file */
962 /* Add to the linker files */
963 CmdAddFile (&LD65, Arg);
967 Error ("Don't know what to do with `%s'", Arg);
977 /* Check if we had any input files */
978 if (FirstInput == 0) {
979 Warning ("No input files");
982 /* Link the given files if requested and if we have any */
983 if (DontLink == 0 && LD65.FileCount > 0) {
987 /* Return an apropriate exit code */