/* * make.i - $Id$ * create or update a Makefile for a yorick package (compiled extension) */ func make (path, template=) /* DOCUMENT make * or make, path * or make, path, template=1 * or make, path, template="Makefile_name" * creates or updates the Makefile for a compiled package (plugin). * If PATH is given, it is the directory containing the source code * for the package; by default, PATH is ".", the current working * directory. * * With the template= keyword, a template Makefile is written, which * you can use as a starting point, filling in all the necessary * information by hand. In this case, make makes no further checks * on the contents of the directory. In the first form, the name of * the Makefile template is "Makefile", but if the template= keyword * is a string, that name is used instead. The template Makefile * will not function at all until you edit it. * * Alternatively, without the template= keyword, make looks at the * contents of the directory, and fills in as much of the Makefile * as it can using what it sees. There are two cases: * 1. A Makefile (or makefile) already exists. Provided the file * contains a Y_MAKEDIR= line, make merely updates Y_MAKEDIR, * Y_EXE, and Y_EXE_PKGS to reflect this running yorick, and * attempts no other modifiactions of the existing Makefile. * 2. No such Makefile exists. Then make writes a template Makefile, * and reads all the .i interpreted source files, .c and .h C source * files, and .f or .F or .m Fortran source files in the directory, * filling in as much of the Makefile as possible. In simple cases, * the resulting Makefile will be perfectly adequate. (If there * is any Fortran source, however, it will never be adequate.) * * In case 2, the following steps are taken: * A. Any .i files containing lines of the form * plug_in, "pkname" * or * if (!is_void(plug_in)) plug_in, "pkgname" * are identified as the "package include file(s)" which define * the interpreted API for the package. The make function takes * the name of the package from the "pkgname" in these lines. * At least one .i file must contain at least one plug_in line; * at most one "pkgname" must occur in all the plug_in lines in * all the .i files in the directory. This information is used * to fill in the PKG_NAME macro and the PKG_I macro in the * Makefile (the latter becomes a list of every .i file containing * a plug_in statement). * B. All .c, .f, .F, or .m files are assumed to be required compiled * source, and the OBJS macro in the Makefile is set to a list of * all corresponding .o object files. * C. If there are any .h C header files in the directory, every .c * C source file is scanned for an include directive which reads * the header. An approriate dependency line for each .o file which * is discovered to depend on local .h files in this manner. * These steps will be sufficient to produce a finished Makefile in * many simple cases; an example is given in the extend/ directory of * the yorick distribution (a compiled complementary error function). * * However, it is impossible for yorick to automatically discover * complicated Makefile rules your package may require. The most * common example is dependency on a third party library, which * requires a -l loader option, and very possibly a -L option as * well. You need to add these flags to PKG_DEPLIBS in the Makefile * by hand. If you want such a package to be buildable by non-experts, * you will need to provide a configure script in order to fill in * the required information across a wide variety of platforms. This * can be exceptionally challenging, which is why yorick itself has * no dependencies beyond what is guaranteed to be present on every * system where it can run (libc, libm, and, on Unix systems, libX11). * * Once the Makefile has been built it becomes part of the source code * of your package, and should not be removed. (You may want it to * have an include directive to get the results of any configure script * which must be run, however.) Thereafter, use * yorick -batch make.i * (or simply make in a running yorick) * to set Y_MAKEDIR, Y_EXE, and Y_EXE_PKGS appropriately for the * particular platform where you are building (case 1 above). * * Read Y_HOME/Makepkg for a description of the various make targets * available. In a nutshell: * make debug builds a debugging version (never a plugin) * make builds a release version (a plugin if possible) * make install installs the release version (may require root) * make clean removes all results, leaving only original source * * SEE ALSO: plug_in, autoload, cd */ { if (!path) path = "."; else if (strglob("*/", path)) path = strpart(path, 1:-1); files = lsdir(path); path = path + "/"; mkfile = make_file; make_subst, mkfile; if (!is_void(template) && template) { if (structof(template)==string && strlen(template)) name = template; else name = "Makefile"; if (anyof(files == name)) { for (i=0 ; ; i++) { bakname = i? swrite(format=name+"-%ld.bak",i) : name+".bak"; if (noneof(files==bakname)) break; } rename, path+name, path+bakname; } write, create(path+name), format="%s\n", linesize=65535, mkfile; write, path+name, format="created template %s\n"; return; } /* check for existing Makefile * if present and not obsolete, just update Y_MAKEDIR, Y_EXE, and quit * else move to .obs and proceed to create a new Makefile */ name = anyof(files == "Makefile"); if (name || anyof(files == "makefile")) { name = (name? "Makefile" : "makefile"); line = rdfile(path+name); i = make_subst(line); if (!numberof(i)) { write, name, name, format= "***WARNING*** %s is obsolete, moving to %s.obs\n"; if (anyof(files == name+".obs")) { for (i=1 ; ; i++) if (noneof(files==swrite(format=name+"-%ld.obs",i))) break; rename, path+name+".obs", swrite(format=path+name+"-%ld.obs",i); } rename, path+name, path+name+".obs"; } else { for (i=0 ; ; i++) { bakname = i? swrite(format=name+"-%ld.bak",i) : name+".bak"; if (noneof(files==bakname)) break; } rename, path+name, path+bakname; write, create(path+name), format="%s\n", linesize=65535, line; remove, path+bakname; write, path+name, format="updated %s\n"; return; } } else { name = "Makefile"; } /* look for plug_in command to identify package include files * get the package name from any plug_in command(s) found, plus * the PKG_I list */ i = files(where(strglob("*.i", files))); if (numberof(i)) i = i(where(i!="check.i")); if (numberof(i)) { ipkg = array(string, numberof(i)); for (k=1 ; k<=numberof(i) ; k++) { line = open(path+i(k), "r", 1); if (is_void(line)) continue; line = rdfile(line); list = strfind("plug_in", line); line = line(where(list(2,) > 0)); if (numberof(line)) { optif = "^[ \t]*(if[ \t]*\\(.+\\)[ \t]*)?"; list = strgrep(optif+"plug_in[ \t]*,[ \t]*[\"](.+)[\"]", line, sub=2); line = strpart(line, list); line = line(where(line)); if (!numberof(line) || anyof(line(1)!=line)) write, i(k), format= "***WARNING*** skipping %s: multiple plug_in commands\n"; else ipkg(k) = line(1); /* could look for PROTOTYPE FORTRAN or EXTERNAL FORTRAN * in order to judge if fortran code present, but this is * not definitive since it could be called from C */ } } list = where(ipkg); i = i(list); ipkg = ipkg(list); if (numberof(i)) { if (numberof(ipkg)>1 && anyof(ipkg(1)!=ipkg)) { write, ipkg(1), format= "***WARNING*** defining package %s, but plug_in commands\n"; write, sum(" "+ipkg(2:0)), format= "***WARNING*** also use packages%s\n"; } ipkg = ipkg(1); } } if (!numberof(i)) error, "***FATAL*** no plug_in command in any .i file in this directory"; mkfile(where(strglob("PKG_NAME=*",mkfile))) = pnm = "PKG_NAME="+ipkg; mkfile(where(strglob("PKG_I=*",mkfile))) = inm = "PKG_I="+strpart(sum(i+" "),1:-1); /* look for #include statements in the .c files * which refer to .h files in this directory, * and generate .o file dependencies accordingly */ c = files(where(strglob("*.c", files))); if (numberof(c)) c = c(where((c!="yinit.c")&(c!="ywrap.c"))); h = files(where(strglob("*.h", files))); if (numberof(h)) { h = h(sort(h)); hh = indgen(numberof(h)); if (numberof(c)) cdep = array(pointer, numberof(c)); for (k=1 ; k<=numberof(c) ; k++) { line = open(path+c(k), "r", 1); if (is_void(line)) continue; line = rdfile(line); list = strgrep("#[ \t]*include[ \t]*[<\"](.+)[\">]", line, sub=1); line = strpart(line, list); list = where(line); if (!numberof(list)) continue; line = line(list); line = line(sort(line)); if (numberof(list) > 1) { list = grow([string(0)], line); line = line(where(list(2:0) != list(1:-1))); } ii = grow(array(0,numberof(line)), hh); line = grow(line, h); list = sort(line); line = line(list); ii = ii(list); list = where(line(2:0) == line(1:-1)); if (!numberof(list)) continue; cdep(k) = &h( max(ii(list), ii(list+1)) ); } list = where(cdep); chdep = c(list); cdep = cdep(list); if (numberof(chdep)) { for (k=1 ; k<=numberof(chdep) ; k++) chdep(k) = strpart(chdep(k),1:-1) + "o:" + sum(" "+(*cdep(k))); } mkfile = grow(mkfile(1:-1), chdep, [""], mkfile(0:0)); } if (!no_fort) { fort = files(where(strglob("*.[fFm]", files))); if (numberof(fort) && !fortran_supported) { write, format="***WARNING*** fortran source not fully supported%s","\n"; grow, c, fort; } } if (!numberof(c)) error, "***FATAL*** no C (or Fortran) source files in this directory"; /* generate the OBJS= line, assuming all source files are * to become part of the package */ objnm = strpart(c,1:-1)+"o "; if (!dimsof(objnm)(1)) objnm = [objnm]; objs = array(string, numberof(objnm)); for (k=1 ; ; k++) { lobjs = strlen(objnm)(psum); j = max(sum(lobjs<70), 1); objs(k) = objnm(sum:1:j); if (j == numberof(objnm)) break; objnm = objnm(j+1:0); } objs = objs(1:k); objs(1) = "OBJS="+objs(1); if (k > 1) { objs(2:k) = " "+objs(2:k); objs(1:k-1) += "\\"; } objs(0) = strpart(objs(0),1:-1); k = where(strglob("OBJS=*", mkfile))(1); mkfile = grow(mkfile(1:k-1), objs, mkfile(k+1:0)); write, create(path+name), format="%s\n", linesize=65535, mkfile; write, ((path!="./")?path:"")+name, format="created %s\n"; write, "automatically generated make macros and dependencies:"; write, format="%s\n", pnm; write, format="%s\n", inm; write, format="%s\n", objs; if (!is_void(chdep)) write, format="%s\n", chdep; write, "edit "+name+" by hand to provide PKG_DEPLIBS or other changes"; } func make_subst (line) { i = where(strglob("Y_MAKEDIR=*", line)); if (numberof(i)) { j = i(1); i = where(strglob("Y_EXE=*", line)); if (numberof(i)) { i = i(1); } else { /* file probably corrupt */ i = j+1; line = grow(line(1:j), string(0), line(i:0)); } k = where(strglob("Y_EXE_PKGS=*", line)); if (numberof(k)) { k = k(1); } else { /* file probably corrupt */ k = i+1; line = grow(line(1:i), string(0), line(k:0)); } pkgs = get_pkgnames(0); pkgs = pkgs(where(pkgs!="yor")); if (!numberof(pkgs)) pkgs = ""; else pkgs = strpart(sum(pkgs+" "), 1:-1); exename = get_argv()(1); if (strglob("*.EXE", exename)) strcase,0, exename; line(j) = "Y_MAKEDIR=" + make_despace(strpart(Y_HOME,1:-1)); line(i) = "Y_EXE=" + make_despace(exename); line(k) = "Y_EXE_PKGS=" + make_despace(pkgs); i = k; k = where(strglob("Y_EXE_HOME=*", line)); if (numberof(i)) { k = k(1); } else { /* file probably corrupt */ k = i+1; line = grow(line(1:i), string(0), line(k:0)); } line(k) = "Y_EXE_HOME=" + make_despace(strpart(Y_HOME,1:-1)); i = k; k = where(strglob("Y_EXE_SITE=*", line)); if (numberof(i)) { k = k(1); } else { /* file probably corrupt */ k = i+1; line = grow(line(1:i), string(0), line(k:0)); } line(k) = "Y_EXE_SITE=" + make_despace(strpart(Y_SITE,1:-1)); } return i; } func make_despace (name) { /* backspace escape blanks in path names */ return streplace(name, strfind(" ",name,n=256), "\\ "); } /* here is the yorick package Makefile template * fill in PKG_NAME, PKG_I, OBJS, and optionally * PKG_DEPLIBS, PKG_CFLAGS, EXTRA_PKGS, PKG_CLEAN, etc. * * yorick -batch make.i * fills in Y_MAKEDIR, Y_EXE * or creates a Makefile if none is present */ make_file = ["Y_MAKEDIR=### Y_HOME (directory containing Makepkg)", "Y_EXE=### Y_LAUNCH/yorick (path to yorick executable)", "Y_EXE_PKGS=### packages statically loaded into Y_EXE", "Y_EXE_HOME=### Y_HOME for Y_EXE", "Y_EXE_SITE=### Y_SITE for Y_EXE", "", "# ----------------------------------------------------- optimization flags", "", "COPT=$(COPT_DEFAULT)", "TGT=$(DEFAULT_TGT)", "", "# ------------------------------------------------ macros for this package", "", "PKG_NAME=### name of this package", "PKG_I=### pkg.i file(s) defining interpreted API for this package", "", "OBJS=### list of .o files (library modules) for this package", "", "# change to give the executable a name other than yorick", "PKG_EXENAME=yorick", "", "# PKG_DEPLIBS=-Lsomedir -lsomelib for dependencies of this package", "PKG_DEPLIBS=", "# set compiler or loader (rare) flags specific to this package", "PKG_CFLAGS=", "PKG_LDFLAGS=", "", "# list of additional package names you want in PKG_EXENAME", "# (typically Y_EXE_PKGS should be first here)", "EXTRA_PKGS=$(Y_EXE_PKGS)", "", "# list of additional files for clean", "PKG_CLEAN=", "", "# autoload file for this package, if any", "PKG_I_START=", "# non-pkg.i include files for this package, if any", "PKG_I_EXTRA=", "", "# -------------------------------- standard targets and rules (in Makepkg)", "", "# set macros Makepkg uses in target and dependency names", "# DLL_TARGETS, LIB_TARGETS, EXE_TARGETS", "# are any additional targets (defined below) prerequisite to", "# the plugin library, archive library, and executable, respectively", "PKG_I_DEPS=$(PKG_I)", "Y_DISTMAKE=distmake", "", "include $(Y_MAKEDIR)/Make.cfg", "include $(Y_MAKEDIR)/Makepkg", "include $(Y_MAKEDIR)/Make$(TGT)", "", "# override macros Makepkg sets for rules and other macros", "# Y_HOME and Y_SITE in Make.cfg may not be correct (e.g.- relocatable)", "Y_HOME=$(Y_EXE_HOME)", "Y_SITE=$(Y_EXE_SITE)", "", "# reduce chance of yorick-1.5 corrupting this Makefile", "MAKE_TEMPLATE = protect-against-1.5", "", "# ------------------------------------- targets and rules for this package", "", "# simple example:", "#myfunc.o: myapi.h", "# more complex example (also consider using PKG_CFLAGS above):", "#myfunc.o: myapi.h myfunc.c", "# $(CC) $(CPPFLAGS) $(CFLAGS) -DMY_SWITCH -o $@ -c myfunc.c", "", "# -------------------------------------------------------- end of Makefile" ]; if (batch()) { if (numberof(get_argv()) > 1) error, "yorick -batch make.i no longer accepts any parameters"; make; quit; }