LinuxQuestions.org
Did you know LQ has a Linux Hardware Compatibility List?
Go Back   LinuxQuestions.org > Blogs > rainbowsally
User Name
Password

Notices

Rate this Entry

Computer Mad Science: The Anatomy Of A Makefile Part 2

Posted 01-28-2012 at 01:24 PM by rainbowsally
Updated 04-09-2012 at 05:44 AM by rainbowsally (updated makefile creator posted)

Note: There's a much more full featured and easily customized makefile creator here. Many features can be added without even recompiling it.
http://www.linuxquestions.org/questi...eration-34648/

For historical reasons we'll keep this blog entry for a while, I guess. -rs

------------------

Features:
  • Example: building an editable makefile creator.

CHANGELOG:
  • Feb 5, 2012, corrected reversed params in strstr() call.
  • Feb 9, 2012, added a newline between stuff in the write_rules code so the makeifle easier to read. The "TODO" is still there for the user to fancy up (add help and version switches, etc.) but this one I'll do.

So you thought you loved cmake, qmake, automake. Heh heh... okay.

But you might be surprised at how easily we can create our own makefiles with just a handful of restrictions. And with an editor those restrictions are replaced by new capabilities you probably never imagined possible.

As we saw in Part 1, makefiles create files as needed by the rules section.

Let us create a simple makefile generator. It's a simple generator. I didn't say the makefiles would be simple. :-) And though it's fairly trivial to adapt this to making static or dynamic libs and comping for 64 or 32 bits, we'll just let gcc/g++ do what they do with a minimum of interferance.

[On the subject of 64 or 32 bits, if you are 64 bit user and don't want the 32 bit libs change the '-m32' switch to '-m64' in the source code or edit the output makefile to chage it. 32 bit apps are easier to share, but if you don't want to add more commandline switches and you really don't want the 32 bit build, you have the power now.]

Basically, we want a list of input files. Those will come from a directory named 'src'. And we want to create a list of object files which we'll place in a directory named 'o'.

And after all the sources are compiled to objects we'll link them.

That's three steps, not counting whatever I forgot. :-)

And we'll do it in C++ for speed and strong type safety (to eliminate most of the coding errors we're likely to make) and convert it to straight C later if you like. In other words we aren't going to create any "classes" or anything.

Once this is built it can create it's own makefile so you can recompile it typing only 'make clean' and 'make'.

Ready?

Create a project directory and a subdirectory named 'src'. Place in src the slist.cpp and slist.h files from "beyond bash part 1" back a couple of blog entries and add this file along with them.

[Don't worry about TABs for this, the creator handles all that unless you edit the output Makefile.]

file: src/makefile-creator.cpp
purpose: make makefiles so we can study how they work and who knows, maybe even use 'em. ;-)
Code:
// makefile-creator, generates editable, makefile for single binary 
// executables written in C or C++.

#include <stdio.h>    // printf, sprintf, etc.
#include <stdlib.h>   // exit()
#include <unistd.h>   // tons of stuff
#include <malloc.h>   // memory allocation
#include <string.h>   // strings and block comparisons and moves

#include "slist.h"

void dbg(){}          // a breakpoint for kdbg.exec

// returns error code
int usage(int errcode);

// these return 0 on success else error
int create_dirs(const char* srcdir, const char* objdir, const char* bindir);
int read_srclist(char*** psrclist, const char* srcdir);
int read_hdrlist(char*** phdrlist, const char* srcdir);
void create_objlist(char*** pobjlist, char** srclist);
int write_makefile(
  const char* bindir, const char* out_name, 
  const char* compiler_name,	
  const char* srcdir, char** srclist, char** hdrlist,
  const char* objdir, char** objlist);


const char* appname;
const char* HERE; // initial dir

int main(int argc, char** argv)
{
  dbg();
  
  int offset = 0;
  int chk;
  // TODO: handle this error flag when true
  int err = 0;
  appname = basename(argv[0]);
  HERE = getenv("PWD");
  
  const char* compiler_name;
  
  // syntax check
  if(argc < 2)
  	return usage(1);
  
  // switch check, only allows optional switch as first param
  if(argc > 2)
  {
// CORRECTED THIS -rs
//   if(strstr("++", argv[1])) 
    if(strstr(argv[1], "++"))
       compiler_name = "g++";
    else
       compiler_name = "gcc";
    offset++;
  }	
  
  // list of sources, headers and objects, names only,
  // paths will be added later to simplify string stuff
  char** srclist = slist_new();
  char** hdrlist = slist_new();
  char** objlist = slist_new();
  
  // output file name
  const char* out_name = argv[1 + offset];
  
  // other vars for the makefile: SRCDIR, OBJDIR, BINDIR
  const char* srcdir = "src";
  const char* objdir = "o";
  const char* bindir = ".";
  
  // init dirs if they don't exist yet.
  err = create_dirs(srcdir, objdir, bindir);
  
  err = read_srclist(&srclist, srcdir);
chk = slist_count(srclist);
  err = read_hdrlist(&hdrlist, srcdir);
chk = slist_count(hdrlist);
  create_objlist(&objlist, srclist);
  
  err = write_makefile(bindir, out_name, compiler_name,	srcdir, srclist, hdrlist, objdir, objlist);
  	
  return err;
}


int usage(int errcode)
{
  /* usage_str.txt converted with txt2cstr */
  const char* usage_str =
  "\n"
  "Usage: %s [c|c++] <filename>\n"
  "  where filename is the name of the output file.\n"
  "\n"
  "  Place all your sources in a directory named 'src' and choose\n"
  "  a c or c++ type of build (default = c) and your makefile will \n"
  "  be created, ready to 'make' or edit.\n"
  "\n"
  ;
  
  printf(usage_str, appname);
  return errcode;

}

// let's cheat and use a shell command for this so we don't have
// to mess with mode bits.
int create_dirs(const char* srcdir, const char* objdir, const char* bindir)
{
  int err;
  char buf[256];
  sprintf(buf, "mkdir -p %s", srcdir);
  err = system(buf);
  sprintf(buf, "mkdir -p %s", objdir);
  err |= system(buf);
  sprintf(buf, "mkdir -p %s", bindir);
  err |= system(buf);
  return err;
}

// let's cheat and use 'find' to get the file names.
int read_srclist(char*** psrclist, const char* srcdir)
{
  int err = 0;
  
  if(chdir(srcdir)) return 1;
  // find src -type f -name *.c -o -name *.cxx -o -name *.cpp 
  char syscmd[256];
  sprintf(syscmd, "find * -maxdepth 0 -type f -name '*.c' -o -name '*.cxx' -o -name '*.cpp'");
  err = slist_readPipe(psrclist, syscmd);
  chdir(HERE);
  return err;
}

int read_hdrlist(char*** phdrlist, const char* srcdir)
{
  int err = 0;
  if(chdir(srcdir)) return 1;
  // find src -type f -name *.c -o -name *.cxx -o -name *.cpp 
  char syscmd[256];
  sprintf(syscmd, "find * -maxdepth 0 -type f -name '*.h' -o -name '*.hxx' -o -name '*.hpp'");
  err = slist_readPipe(phdrlist, syscmd);
  chdir(HERE);
  return err;
}

void create_objlist(char*** pobjlist, char** srclist)
{
  int i; // loop counter
  char tmp[256]; // temp string
  char* p;       // scrath pointer
  char* filename;
  for(i = 0; i < slist_count(srclist); i++)
  {
  	filename = srclist[i];
  	// change ext from .* to .o
  	strcpy(tmp, filename);
  	p = strrchr(tmp, '.');
  	strcpy(p, ".o");
  	
  	// tmp now = objdir/filename.o
  	slist_append(pobjlist, tmp);
  }	
}

void write_makehdr(FILE* fp) {
fprintf(fp, 
"## Makefile created with 'makefile-creator'\n\n");
}

void assign_outname(FILE* fp, const char* outname) {
fprintf(fp, 
"## The outputf file name not including path\n"
"OUTNAME = %s\n\n", outname);
}

void assign_dirs(FILE* fp, const char* bindir, const char* srcdir, const char* objdir) {
fprintf(fp, 
"## The directories for sources, (temp) objects, and binary output(s)\n"
"BINDIR = %s\n"
"SRCDIR = %s\n"
"OBJDIR = %s\n\n", bindir, srcdir, objdir);
}


void assign_compiler(FILE* fp, const char* compiler_name) {
fprintf(fp, 
"## What COMPILE should do.\n"
"COMPILE = %s -m32 -c -o\n"
"CFLAGS = # -O2 or -g3\n"
"INCLUDE = -I/usr/include -I./$(SRCDIR)\n\n", compiler_name);
}

// compiler name = linker name here.
void assign_linker(FILE* fp, const char* linker_name) {
fprintf(fp,
"## What LINK should do.\n"
"LINK = %s -m32 -o\n"
"LDFLAGS = # -lc\n"
"LIB = -L/usr/lib\n\n", linker_name);
}

void write_list(FILE* fp, const char* dir, char** list) {
  int i;
  for(i = 0; i < slist_count(list); i++) {
  	fprintf(fp, "  %s/%s \\\n", dir, list[i]);
  }
}

void assign_lists(FILE* fp, const char* srcdir, char** srclist, char** hdrlist, const char* objdir, char** objlist) {
fprintf(fp, 
"## The full path to the output file\n"
"MAIN = $(BINDIR)/$(OUTNAME)\n"
"\n"
"# MODIFY BELOW THIS LINE WITH GREAT CARE!\n"
"# ---------------------------------------\n"
"\n");

  const char* endlist = "  #############\n\n";
  
  fprintf(fp,"SRC = \\\n");
  write_list(fp, srcdir, srclist);
  fprintf(fp, endlist);	
  
  fprintf(fp,"HDR = \\\n");
  write_list(fp, srcdir, hdrlist);
  fprintf(fp, endlist);	
  
  fprintf(fp,"OBJ = \\\n");
  write_list(fp, objdir, objlist);
  fprintf(fp, endlist);
}

void write_rules(FILE* fp, char** srclist, char** objlist)
{
int i; // loop counter
fprintf(fp, "################################## Rules Section\n");
fprintf(fp, "\n");
fprintf(fp, "all: $(MAIN) $(HDR) $(OBJ) $(SRC)\n");
fprintf(fp, "\n");
fprintf(fp, "$(MAIN): $(OBJ) $(HDR) $(SRC)\n");
fprintf(fp, "\t@echo\n");
fprintf(fp, "\t@echo \"Linking $(OUTNAME)\"\n");
fprintf(fp, "\t$(LINK) $(MAIN) $(OBJ) $(LDFLAGS) $(LIB)\n");
fprintf(fp, "\t$(POST)\n");
fprintf(fp, "\n");

  for(i = 0; i < slist_count(srclist); i++)
  {
  fprintf(fp, "$(OBJDIR)/%s: $(SRCDIR)/%s $(HDR)\n", objlist[i], srclist[i]);
  fprintf(fp, "\t@echo\n");
  fprintf(fp, "\t@echo \"Compiling %s\"\n", objlist[i]);

  // fprintf(fp, "\t$(COMPILE) $(OBJDIR)/%s $(SRCDIR)/%s $(CFLAGS) $(INCLUDE)\n",	objlist[i], srclist[i]);
  // Added an extra newline at the end of this line -rs
  fprintf(fp, "\t$(COMPILE) $(OBJDIR)/%s $(SRCDIR)/%s $(CFLAGS) $(INCLUDE)\n\n",	objlist[i], srclist[i]);

  }
}

void write_userdef(FILE* fp)
{
fprintf(fp, "################################## User Defined\n");
fprintf(fp, "\n");
fprintf(fp, "clean:\n");
fprintf(fp, "\t@rm -f $(MAIN)\n");
fprintf(fp, "\t@rm -f $(OBJ)\n");
fprintf(fp, "\t@rm -f *~ */*~ */*/*~ */*/*/*~\n");
}

int write_makefile(
const char* bindir, const char* out_name, 
const char* compiler_name,	
const char* srcdir, char** srclist, char** hdrlist,
const char* objdir, char** objlist)
{
  FILE* fp = fopen("Makefile", "r");
	if(fp) { system("cp -f Makefile Makefile-backup"); fclose(fp); }
	fp = fopen("Makefile", "w");
  if(! fp) {
		fprintf(stderr, "Can't create Makefile\n");
  	return 1;
  }
  write_makehdr(fp);
  assign_outname(fp, out_name);
  assign_dirs(fp, bindir, srcdir, objdir);
  assign_compiler(fp, compiler_name);
  assign_linker(fp, compiler_name);
  assign_lists(fp, srcdir, srclist, hdrlist, objdir, objlist);
  write_rules(fp, srclist, objlist);	
  write_userdef(fp);
  fclose(fp);
}
First time around we have to make it by hand.

Code:
mkdir -p o
g++ -c src/makefile-creator.cpp -o o/makefile-creator.o
g++ -c src/slist.cpp -o o/slist.o
g++ o/* -o makefile-creator
And that's so easy you might thing we don't even need a makefile creator.

Yes but... what if you add or remove files?

To make a new makefile with new sources just type "makefile-creator [c++] outputname".

And 'make clean' and 'make'.

Go ahead and build it, then let it write it's own makefile and see for yourself how easily you can write c and c++ applications with huge file sets you can split or combine as you see fit, and the makefile can super-easily keep up with your changes.

Or...

Want to rename the app? 'makefile-creator c++ NewName'. That's all. No need to change the names of any sources.

Open the output Makefile in an editor and notice how the dependencies are listed.

That's so if you change one source file only the associated object file will get recompiled. It's quite fast, especially noticible in large projects.

Any change to any header, on the other hand forces a full recompile of all the files as we previously mentioned in Part 1.

Copyright (C) 2012, Rainbow Sally, released under GPL.

No... that's not the "Computer Rocket Mad Science". We haven't gotten to that yet, but now we can write the makefile.

:-)
Posted in Uncategorized
Views 832 Comments 0
« Prev     Main     Next »
Total Comments 0

Comments

 

  



All times are GMT -5. The time now is 10:05 PM.

Main Menu
Advertisement

My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
identi.ca: @linuxquestions
Facebook: linuxquestions Google+: linuxquestions
Open Source Consulting | Domain Registration