/* ** MakefileData.c ** ** Contains functions that put together a MakefileData structure ** ** Written by Peter Sutton ** April, 2009 */ #include #include #include #include #include "MakefileData.h" typedef enum { ERROR, COMMENT, DEFINITION, TARGET, RULE } LineType; /***************************************************************************** ** Function prototypes - for use in this file only. Comments are with the ** function definitions. */ static void copy_word_to_rule_argv(Rule* rulePtr, char* word, int wordLen); static void extract_and_store_definition(char* line, int lineNum, MakefileData* makefilePtr); static void extract_and_store_rule(char* line, Target* target); static Target* extract_and_store_target(char* line, int lineNum, MakefileData* makefilePtr); static int find_dependency(Target* targetPtr, char* dependencyToCheck); static char* find_separator_and_next_word(char* line, char separator); static LineType line_type(char* line); static char* read_line(FILE*); /* returns next line from file, details below */ static char* skip_spaces(char*); /* finds first non-space char in string */ /****************************************************************************/ /******** EXTERNALLY VISIBLE FUNCTIONS **************************************/ /****************************************************************************/ /* free_makefile_data() ** ** Frees the MakefileData structure pointed to by makefilePtr. */ void free_makefile_data(MakefileData* makefilePtr) { int defNum, targetNum, depNum, ruleNum, argNum; Definition* defPtr; Target* targetPtr; Rule* rulePtr; for(defNum = 0; defNum < makefilePtr->numDefinitions; defNum++) { defPtr = &(makefilePtr->definitions[defNum]); free(defPtr->name); free(defPtr->value); free(defPtr); } if(makefilePtr->definitions) { free(makefilePtr->definitions); } for(targetNum = 0; targetNum < makefilePtr->numTargets; targetNum++) { targetPtr = &(makefilePtr->targets[targetNum]); free(targetPtr->fileName); for(depNum = 0; depNum < targetPtr->numDependencies; depNum++) { free(targetPtr->dependencies[depNum]); } free(targetPtr->dependencies); for(ruleNum = 0; ruleNum < targetPtr->numRules; ruleNum++) { rulePtr = &(targetPtr->rules[ruleNum]); for(argNum = 0; argNum < rulePtr->argc; argNum++) { free(rulePtr->argv[argNum]); } free(rulePtr->argv); } free(targetPtr); } if(makefilePtr->targets) { free(makefilePtr->targets); } free(makefilePtr); } /* parse_file() ** ** Argument fileHandle is assumed to be the handle of a file opened for ** reading. This function reads each line in the file, and constructs ** our representation of a 2303makefile. */ MakefileData* parse_file(FILE* fileHandle) { char* line; int lineNum, targetNum; Target* currentTarget; MakefileData* makefilePtr; makefilePtr = (MakefileData*)malloc(sizeof(MakefileData)); makefilePtr->numDefinitions = 0; makefilePtr->definitions = NULL; makefilePtr->numTargets = 0; makefilePtr->targets = NULL; lineNum = 0; currentTarget = NULL; while((line = read_line(fileHandle))) { lineNum++; /* Identify the type of line */ switch(line_type(line)) { case COMMENT: currentTarget = NULL; break; /* Ignore the line */ case DEFINITION: extract_and_store_definition(line, lineNum, makefilePtr); currentTarget = NULL; break; case TARGET: currentTarget = extract_and_store_target(line, lineNum, makefilePtr); break; case RULE: /* Rules are only allowed immediately after other rules or ** targets - in other words, we must have a current ** target. */ if(currentTarget) { extract_and_store_rule(line, currentTarget); break; } /* else, we drop through to the default(error) case */ default: /* Must be an ERROR */ fprintf(stderr, "Error: Line %d: Invalid line\n", lineNum); free_makefile_data(makefilePtr); exit(1); } } /* Check we have at least one target */ if(makefilePtr->numTargets == 0) { fprintf(stderr, "Error: File must contain at least one target\n"); free_makefile_data(makefilePtr); exit(1); } /* Check each target has at least one rule */ for(targetNum = 0; targetNum < makefilePtr->numTargets; targetNum++) { if(makefilePtr->targets[targetNum].numRules == 0) { fprintf(stderr, "Error: Target %s does not have any rules\n", makefilePtr->targets[targetNum].fileName); exit(1); } } return makefilePtr; } /* print_makefile_data() ** ** Print out the given makefile structure. All definitions are printed ** first, followed by the targets and their rules. */ void print_makefile_data(MakefileData* makefilePtr) { int defNum, targetNum, depNum, ruleNum, argNum; Definition* defPtr; Target* targetPtr; Rule* rulePtr; printf("# Definitions\n"); for(defNum = 0; defNum < makefilePtr->numDefinitions; defNum++) { defPtr = &(makefilePtr->definitions[defNum]); printf("%s = %s\n", defPtr->name, defPtr->value); } printf("\n# Targets and their dependencies\n"); for(targetNum = 0; targetNum < makefilePtr->numTargets; targetNum++) { targetPtr = &(makefilePtr->targets[targetNum]); if(targetPtr->numDependencies) { for(depNum = 0; depNum < targetPtr->numDependencies; depNum++) { printf("%s : %s\n", targetPtr->fileName, targetPtr->dependencies[depNum]); } } else { printf("%s :\n", targetPtr->fileName); } for(ruleNum = 0; ruleNum < targetPtr->numRules; ruleNum++) { rulePtr = &(targetPtr->rules[ruleNum]); for(argNum = 0; argNum < rulePtr->argc; argNum++) { if(argNum == 0) { printf("\t"); } else { printf(" "); } printf("%s", rulePtr->argv[argNum]); } printf("\n"); } /* Blank line between targets */ printf("\n"); } } /* find_target() ** ** Checks whether the target filename already exists, and if so, returns ** a pointer to its structure. If not, returns a null pointer. */ Target* find_target(MakefileData* makefilePtr, char* targetFileName) { int i; for(i = 0; i < makefilePtr->numTargets; i++) { if(strcmp(makefilePtr->targets[i].fileName, targetFileName) == 0) { /* Found match */ return &(makefilePtr->targets[i]); } } return NULL; } /* find_definition() ** ** Checks whether the definition and if so, returns a pointer to the value. ** If not, returns a null pointer. */ char* find_definition(MakefileData* makefilePtr, char* defName) { int i; for(i = 0; i < makefilePtr->numDefinitions; i++) { if(strcmp(makefilePtr->definitions[i].name, defName) == 0) { /* Found match */ return makefilePtr->definitions[i].value; } } return NULL; } /****************************************************************************/ /********* INTENALLY VISIBLE FUNCTIONS **************************************/ /****************************************************************************/ /* copy_word_to_rule_argv() ** ** Copy the given word to the next argv slot for the given rule. */ static void copy_word_to_rule_argv(Rule* rulePtr, char* word, int wordLen) { char* arg; arg = malloc(wordLen + 1); strncpy(arg, word, wordLen); /* Null terminate the copied word */ arg[wordLen] = '\0'; /* Make argv array bigger - allow space for this arg & null arg */ rulePtr->argv = (char**)realloc(rulePtr->argv, sizeof(char*) * (rulePtr->argc + 2)); rulePtr->argv[rulePtr->argc] = arg; rulePtr->argc++; rulePtr->argv[rulePtr->argc] = NULL; } /* extract_and_store_definition() ** ** Finds the definition on the line and adds the ** definition to our makefile data structure. If an error occurs, we print a ** message and exit. NOTE: it is known that there is an equals sign in the ** line. */ static void extract_and_store_definition(char* line, int lineNum, MakefileData* makefilePtr) { Definition* defPtr; char* name = line; char* value = find_separator_and_next_word(line, '='); if(!value) { fprintf(stderr, "Error: Line %d: Invalid line\n", lineNum); exit(1); } /* Makefile sure the name isn't already defined. */ if(find_definition(makefilePtr, name)) { fprintf(stderr, "Error: Line %d: Name %s already defined\n", lineNum, name); exit(1); } /* Store the definition */ makefilePtr->definitions = realloc(makefilePtr->definitions, sizeof(Definition) * (makefilePtr->numDefinitions + 1)); defPtr = &(makefilePtr->definitions[makefilePtr->numDefinitions]); defPtr->name = name; /* We don't copy this, since it is in memory allocated ** just for this line. Memory is too big, but it ** doesn't matter. */ defPtr->value = malloc(strlen(value) + 1); strcpy(defPtr->value, value); makefilePtr->numDefinitions++; } /* extract_and_store_rule() ** ** Line begins with a single tab and contains a rule (sequence of whitespace ** separated words). We extract the words from the line and store them as the ** next rule for the given target. No errors are possible. */ static void extract_and_store_rule(char* line, Target* target) { Rule* rulePtr; int wordLen; char* word; /* Create room for the rule in the target structure */ target->rules = (Rule*)realloc(target->rules, sizeof(Rule)*(target->numRules + 1)); rulePtr = &(target->rules[target->numRules]); target->numRules++; rulePtr->argc = 0; rulePtr->argv = (char**)malloc(sizeof(char*)); rulePtr->argv[0] = NULL; /* argv is always null terminated */ /* Skip over the tab character */ line++; while(*line) { word = line; wordLen = 0; /* Skip over the characters in the word - terminate at null or space */ while(*line && !isspace(*line)) { line++; wordLen++; } copy_word_to_rule_argv(rulePtr, word, wordLen); line = skip_spaces(line); } } /* Finds the target on the line and adds it to our makefile structure. ** If an error occurs, we print an error message and exit. NOTE: it is known ** that there is a colon on the line. */ static Target* extract_and_store_target(char* line, int lineNum, MakefileData* makefilePtr) { Target* targetPtr; char** depPtr; char* lineCursor; char* fileName = line; char* dependsOn = find_separator_and_next_word(line, ':'); if(!dependsOn) { fprintf(stderr, "Error: Line %d: Invalid line\n", lineNum); exit(1); } /* Check there are no whitespace characters in the dependsOn string. ** If there are, this is an error. */ lineCursor = dependsOn; while(*lineCursor) { if(isspace(*lineCursor)) { fprintf(stderr, "Error: Line %d: Invalid line\n", lineNum); exit(1); } lineCursor++; } /* Store the target. We check if it already exists first. */ targetPtr = find_target(makefilePtr, fileName); if(!targetPtr) { /* Doesn't exist - create space for it */ makefilePtr->targets = realloc(makefilePtr->targets, sizeof(Target) * (makefilePtr->numTargets + 1)); targetPtr = &(makefilePtr->targets[makefilePtr->numTargets]); targetPtr->fileName = fileName; targetPtr->numDependencies = 0; targetPtr->dependencies = NULL; targetPtr->numRules = 0; targetPtr->rules = NULL; makefilePtr->numTargets++; } /* If there is a dependency specified (i.e. string is not empty) and ** we don't already have this dependency listed, copy the dependency ** into the target structure */ if(dependsOn[0] && !find_dependency(targetPtr, dependsOn)) { targetPtr->dependencies = realloc(targetPtr->dependencies, sizeof(char*) * (targetPtr->numDependencies + 1)); depPtr = &(targetPtr->dependencies[targetPtr->numDependencies]); *depPtr = malloc(strlen(dependsOn) + 1); strcpy(*depPtr, dependsOn); targetPtr->numDependencies++; } return targetPtr; } /* find_dependency() ** ** Check to see whether the given name is already listed as a dependency of ** the given target. Return 1 if so, 0 otherwise. */ static int find_dependency(Target* targetPtr, char* dependencyToCheck) { int i; for(i = 0; i < targetPtr->numDependencies; i++) { if(strcmp(targetPtr->dependencies[i], dependencyToCheck) == 0) { return 1; } } return 0; } /* find_separator_and_next_word() ** ** Traverses line until whitespace or the given separator is found. (There ** must be at least one non-whitespace, non-separator character first.) The ** found whitespace or separator character is converted to a null, and the ** line is further traversed until any following whitespace characters are ** consumed. Returns a pointer to the first non-whitespace character following ** the separator. If the initial search is terminated by whitespace characters ** and the next non-whitespace character is not the separator, OR there is not ** at least one non-whitespace, non-separator character first, then a NULL ** pointer is returned. (e.g. this is the case when there are two "words" ** before the separator. It is known that there is exactly one separator on ** the line. */ static char* find_separator_and_next_word(char* line, char separator) { /* Check first character */ if(isspace(*line) || *line == separator) { /* This is an error */ return NULL; } /* Traverse line until we find whitespace or separator */ while(!isspace(*line) && *line != separator) { line++; } if(*line == separator) { /* We found the separator */ *line = '\0'; /* null out the separator */ } else { /* else, we must have found whitespace first - null out the first ** character and traverse further whitespace */ *line = '\0'; line++; line = skip_spaces(line); /* Must now have the separator - otherwise error */ if(*line != separator) { return NULL; } } /* Skip over the separator position */ line++; /* Are now at the point immediately after the separator - skip any further ** spaces and return a pointer to the first non-whitespace charactor. */ line = skip_spaces(line); return line; } /* line_type() ** ** Identify the type of line - returns one of ** COMMENT, DEFINITION, TARGET, RULE, ERROR. */ static LineType line_type(char* line) { char* lineStart; char* equalsPos; char* colonPos; lineStart = skip_spaces(line); if(*lineStart == '\0' || *lineStart == '#') { /* Line is blank or contains a comment */ return COMMENT; } else if(!isspace(line[0])) { /* First character on line is not whitespace - must be either ** a definition line or a target line. A definition line will ** contain a single equals sign, a target line will contain a ** single colon. Any line which contains ** - no colon or equals ** - two colons ** - two equals ** - both a colon and an equals ** is invalid. */ /* Find the positions of the leftmost : and = in the line. We'll ** make sure there is only one of these and they match the position ** of the rightmost : or = in the line. */ colonPos = strchr(line, ':'); equalsPos = strchr(line, '='); if(colonPos && !equalsPos && strrchr(line, ':') == colonPos) { return TARGET; } else if(equalsPos && !colonPos && strrchr(line, '=') == equalsPos) { return DEFINITION; } } else if(line[0] == '\t' && !isspace(line[1])) { /* Line begins with a tab character followed by non-whitespace. ** This must be a rule. */ return RULE; } /* If we get here, we couldn't identify the type of the line - this ** must be an error. This will happen if a non-comment, non-blank ** line begins with whitespace but it isn't a tab character followed ** by a non-space character. This will also happen if a line starts ** with a non-space character but doesn't have a single colon or single ** equals sign on it. */ return ERROR; } /* read_line() ** ** Reads a line from the given file and returns a pointer to a space ** specifially allocated to hold it. Returns 0 if no lines remaining ** (e.g. end of file). Newline and any trailing whitespace characters ** are removed prior to returning line. Lines can be of any length. ** (Last line in file may omit the newline.) */ static char* read_line(FILE* file) { char* line; int lineLength; line=0; lineLength = 0; /* Keep reading bits of text and add them to the current line until ** we reach the end of line. We allocate space for 128 bytes beyond the ** current length and try and read up to that number of characters. */ while(1) { line = (char*)realloc(line, lineLength+128); /* Attempt to read line from file and append to current line. */ if(!fgets(line+lineLength, 128, file)) { /* fgets returned null - EOF or error condition */ if(feof(file)) { /* EOF encountered. If we have already have data for this ** line, return it. (There will be no newline on the end - if ** there was, we would not be here. We only get here if we ** reach EOF without reading any bytes.) If we don't have ** data, i.e. last line is empty, just free the memory and ** return. */ if(lineLength) { /* Line is complete - break out and return it */ break; } else { free(line); return 0; } } else { /* Error condition - we won't continue */ perror("Error encountered reading file"); exit(1); } } /* If the line has a newline on the end, we've found the end of the ** line. Otherwise, keep reading to see if there is more data on ** the line. */ lineLength = strlen(line); if(lineLength && line[lineLength-1] == '\n') { line[--lineLength] = '\0'; break; } } /* Strip off any trailing whitespace */ while(lineLength && isspace(line[lineLength-1])) { line[--lineLength] = '\0'; } /* Return the line we have read */ return line; } /* skip_spaces() ** ** Finds the first non space character in the given string ** and returns a pointer to that character (within the ** supplied string). If no non-space character found then ** returns a pointer to the null character terminating the string */ static char* skip_spaces(char* line) { char* linePtr; linePtr = line; while(*linePtr && isspace(*linePtr)) { linePtr++; } return linePtr; }