#!/usr/bin/env perl # # Creates a makefile for a given OMNeT++/OMNEST model. # Assumes that .ned, .msg, .cc and .h files are in one directory (or directory tree). # The name of the program defaults to the name of the directory ('myproject'). # # Author: Andras Varga # use Cwd; use Config; $ARG0 = $0; $progname = $ARG0; #$arch = $Config{'archname'}; #$isCygwin = ($arch =~ /cygwin/i); $isWindows = defined $ENV{OS} && $ENV{OS} =~ /windows/i; $isMINGW = defined $ENV{MSYSTEM} && $ENV{MSYSTEM} =~ /mingw/i; sub makemake(); makemake(); exit 0; sub usage() { print <//\$(CONFIGNAME)/, where is the project-relative path of the current directory (for see -P). An absolute directory path can also be specified, resulting in /\$(CONFIGNAME)/. --deep Generates a "deep" Makefile. A deep Makefile will cover the whole source tree under the make directory, not just files in that directory. Directories may be excluded using -X. The include path will contain all directories in the source tree. Deep is not compatible with -r (recursive make), but -d (directory to recurse into) can still be used. -r, --recurse Causes make to recursively descend into all subdir- ectories; subdirectories are expected to contain makefiles themselves. If you need to maintain a specific order, declare dependencies in the makefrag(.vc) file. Subdirectories may be excluded using -X. See also -d. -X directory, -Xdirectory, --except directory With -r and --deep option: ignore the given directory. If the argument is a relative path, it is interpreted as relative to the current directory. Wildcards (like -X *_backups or -X */tmp) are accepted. -dsubdir, -d subdir, --subdir subdir Causes make to recursively descend into the given directory. The directory does not need to be the child of the make directory. When used with --deep, -d also implies -X for that directory. -P directory, --projectdir directory Specifies the project root directory. Any absolute path (-I, -L, object file names, etc.) that points into the project's subtree will be converted to relative, to ease compiling the project in a different directory. Defaults to first ancestor directory that contains a ".project" file. -M mode, --mode mode Selects the build mode, "debug" or "release". Defaults to "debug". This setting can still be overridden on the make command line, by specifying "MODE=debug" or "MODE=release" as an extra argument. -Dname[=value], -D name[=value], --define name[=value] Preprocessor symbol to be passed to the C++ compiler. -Kname=value, -K name=value, --makefile-define name=value Defines a makefile variable, i.e. causes name=value line to get inserted into the makefile. -n, --nolink Produce object files but do not create executable or library. Useful for models with parts in several directories. With this option, -u and -l have no effect. -s, --make-so Build shared library (.so or .dll). Useful if you want to load the model dynamically (via the load-libs= omnetpp.ini or the -l Cmdenv/Tkenv command-line option). -a, --make-lib Create static library (.a or .lib). -S, --fordll Compile C++ files for use in DLLs; see -p option. The -s (build shared library) option implies this one. -w, --withobjects Link with all object files found in -I directories, or add them to the created library. OBSOLETE. -u name, --userinterface name Selects the user interface libraries to link with. Possible values are "all", "Cmdenv", "Tkenv", "Qtenv". Defaults to "all". -Idir Additional NED and C++ include directory -Ldir Add a directory to the library path -llibrary Additional library to link against. On Unix, -lfoo will link with libfoo.so or libfoo.a; on Windows, with foo.lib. The library path (see -L) will be searched in both cases. -p symbol, -psymbol The DLL export/import symbol. It will cause -P_API to be passed to opp_msgc. Also, if target is a DLL or --fordll is specified, the _EXPORT macro will be passed to the compiler; the source code is expected to be set up so that dll-public declarations are annotated with _API, and _EXPORT causes _API to be defined as __declspec(dllexport). -i filename, --includefragment filename Append file to near end of Makefile. The file makefrag.vc (if exists) is appended automatically if no -i options are given. This option is useful if a source file (.ned, .msg or .cc) is to be generated from other files. object file or library Arguments will simply get added to the linker (or librarian) command line. Thus, wildcards and macros are accepted, and will be resolved at build time. Useful macros are \$O (the output location of this makefile), \$(PROJECT_OUTPUT_PATH) and \$(CONFIGNAME); see the generated makefiles for more. On Unix you need to single-quote them ('\$O') against getting resolved prematurely by the shell. Default output is Makefile, which you can invoke by typing "make". The contents of the makefrag file will be built into the generated makefile, letting you to override the default target, change variables, etc. END } sub makemake() { if ($isWindows && $ENV{OS} ne "Windows_NT") { error("this program can only be used on Windows NT/2000/XP, but your OS environment variable says '$ENV{OS}'\n"); } # # process command line args # @args = @ARGV; $projectDir = ""; $type = "EXE"; $target = ""; $outRoot = ""; $isDeep = 0; $isRecursive = 0; $force = 0; $defaultMode = ""; $userInterface = "ALL"; $ccExt = ""; $configFile = ""; $dllSymbol = ""; $compileForDll = 0; $ignoreNedFiles = 1; @fragmentFiles = (); @submakeDirs = (); @exceptSubdirs = (); @includeDirs = (); @libDirs = (); @libs = (); @defines = (); @makefileVariables = (); @extraArgs = (); # process arg vector while (@ARGV) { $arg = shift @ARGV; if ($arg eq "-h" || $arg eq "--help") { usage(); exit(1); } elsif ($arg eq "-f" || $arg eq "--force") { $force = 1; } elsif ($arg eq "-e" || $arg eq "--ext") { $ccExt = shift @ARGV; } elsif ($arg eq "-o") { $target = shift @ARGV; } elsif ($arg =~ /^-o/) { $target = substr($arg, 2); } elsif ($arg eq "-O" || $arg eq "--out") { $outRoot = shift @ARGV; } elsif ($arg =~ /^-O/) { $outRoot = substr($arg, 2); } elsif ($arg eq "--deep") { $isDeep = 1; } elsif ($arg eq "-r" || $arg eq "--recurse") { $isRecursive = 1; } elsif ($arg eq "-X" || $arg eq "--except") { push(@exceptSubdirs, shift @ARGV); } elsif ($arg =~ /^-X/) { my $dir = substr($arg, 2); push(@exceptSubdirs, $dir); } elsif ($arg eq "--no-deep-includes") { # kept for backward compatibility, now this is the default/only mode } elsif ($arg eq "-D" || $arg eq "--define") { push(@defines, shift @ARGV); } elsif ($arg =~ /^-D/) { my $define = substr($arg, 2); push(@defines, $define); } elsif ($arg eq "-K" || $arg eq "--makefile-define") { push(@makefileVariables, shift @ARGV); } elsif ($arg =~ /^-K/) { my $makefileVariable = substr($arg, 2); push(@makefileVariables, $makefileVariable); } elsif ($arg eq "-N" || $arg eq "--ignore-ned") { error("obsolete option $arg, please remove (dynamic NED loading is now the default)"); } elsif ($arg eq "-P" || $arg eq "--projectdir") { $projectDir = shift @ARGV; } elsif ($arg =~ /^-P/) { $projectDir = substr($arg, 2); } elsif ($arg eq "-M" || $arg eq "--mode") { $defaultMode = shift @ARGV; } elsif ($arg =~ /^-M/) { $defaultMode = substr($arg, 2); } elsif ($arg eq "-c" || $arg eq "--configfile") { error("option $arg is no longer supported, config file is located using variables (OMNETPP_CONFIGFILE or OMNETPP_ROOT), or by invoking opp_configfilepath"); } elsif ($arg eq "-d" || $arg eq "--subdir") { push(@submakeDirs, shift @ARGV); } elsif ($arg =~ /^-d/) { my $subdir = substr($arg, 2); push(@submakeDirs, $subdir); } elsif ($arg eq "-n" || $arg eq "--nolink") { $type = "NOLINK"; } elsif ($arg eq "-s" || $arg eq "--make-so") { $type = "SHAREDLIB"; } elsif ($arg eq "-a" || $arg eq "--make-lib") { $type = "STATICLIB"; } elsif ($arg eq "-S" || $arg eq "--fordll") { $compileForDll = 1; } elsif ($arg eq "-w" || $arg eq "--withobjects") { error("$progname: $arg: obsolete option, please remove"); } elsif ($arg eq "-x" || $arg eq "--notstamp") { error("$progname: $arg: obsolete option, please remove"); } elsif ($arg eq "-u" || $arg eq "--userinterface") { $userInterface = shift @ARGV; $userInterface = uc($userInterface); if ($userInterface ne "ALL" && $userInterface ne "CMDENV" && $userInterface ne "TKENV" && $userInterface ne "QTENV") { error("$progname: -u: specify All, Cmdenv, Tkenv or Qtenv"); } } elsif ($arg eq "-i" || $arg eq "--includefragment") { push(@fragmentFiles, shift @ARGV); } elsif ($arg eq "-I") { push(@includeDirs, shift @ARGV); } elsif ($arg =~ /^-I/) { my $dir = substr($arg, 2); push(@includeDirs, $dir); } elsif ($arg eq "-L") { push(@libDirs, shift @ARGV); } elsif ($arg =~ /^-L/) { my $dir = substr($arg, 2); push(@libDirs, $dir); } elsif ($arg =~ /^-l/) { my $lib = substr($arg, 2); push(@libs, $lib); } elsif ($arg eq "-p") { $dllSymbol = shift @ARGV; } elsif ($arg =~ /^-p/) { $dllSymbol = substr($arg, 2); } elsif ($arg =~ /^--meta:/) { error("$progname: --meta options not supported: they rely on information only available inside the IDE"); } elsif ($arg eq "--") { last; } else { error("unrecognized option: $arg") if ($arg =~ /^-/); push(@extraArgs, $arg); } } # process args after "--" while (@ARGV) { $arg = shift @ARGV; push(@extraArgs, $arg); } # # Prepare the variables for the template # $makefile = "Makefile"; if (-f $makefile && $force ne 1) { error("use -f to force overwriting existing $makefile"); } if ($type eq "SHAREDLIB") { $compileForDll = 1; } $folder = cwd; print "Creating $makefile in $folder...\n"; @objs = (); @generatedHeaders = (); @extraObjs = (); @ccfiles = (); @cppfiles = (); @nedfiles = (); @msgfiles = (); @msgccfiles = (); @msghfiles = (); @smfiles = (); @smccfiles = (); @smhfiles = (); @sourceDirs = (); @backslashedSourceDirs = (); # determine, and clean up format of project root dir if ($projectDir eq "") { # try to find project root directory (go up until we find a ".project" file) my $dir = cwd; $dir =~ s|\\|/|g; for (;;) { if (-f "$dir/.project") { $projectDir = $dir; last; } if ($dir =~ m|/|) { $dir =~ s|/[^/]*$||; } else { $projectDir = "."; last; } } } elsif (! -d $projectDir) { error("specified project directory \"$projectDir\" does not exist"); } if ($projectDir ne "") { $projectDir = existingDirtoCanonicalAbsolute($projectDir); #print "Project directory: $projectDir\n"; if (!($folder =~ /^\Q$projectDir\E/)) { error("current directory is not under the given project directory \"$projectDir\""); } } $projectName = $projectDir; $projectName =~ s/[\/\\]$//; # remove trailing slash/backslash $projectName =~ s/.*[\/\\]//; # keep only part after last slash/backslash $target = $target ne "" ? $target : $projectName; # target should only be a name, cannot contain relative path if ($target =~ /.*[\/\\].*/) { error("target (-o option) should only be a name, it cannot contain relative path"); } # recursive and deep do not mix if ($isDeep) { $isRecursive = 0; } $makecommand = $ENV{MAKE} ? $ENV{MAKE} : "make"; # find Makefile.inc $comspec = $ENV{COMSPEC}; $comspec =~ s!\\!\\\\!g; $configFile = `opp_configfilepath`; if ($? != 0) { error("opp_configfilepath returned nonzero exit code -- make sure it exists and is in the PATH"); } $configFile =~ s/^\s*(.*?)\s*$/$1/gs; if ($configFile eq "" || ! -f $configFile) { error("opp_configfilepath didn't return the name of an existing directory, result was \"$configFile\""); } # determine outdir (defaults to "out") if ($outRoot eq "") {$outRoot = "out";} my $outRootAbs = (!isRelative($outRoot) || $projectDir eq "") ? $outRoot : "$projectDir/$outRoot"; my $outRootRel = abs2rel($outRootAbs, $projectDir); $outDir = canonicalize("$outRootRel"); # determine subpath: the project-relative path of this folder my $subpath = cwd(); $subpath =~ s|^\Q$projectDir\E||; $subpath =~ s/^[\/\\]//; # remove the leading slash/backslash as this is a relative directory # collect source files if ($isDeep) { my @allExcludedDirs = (); push(@allExcludedDirs, $outDir); push(@allExcludedDirs, @exceptSubdirs); push(@allExcludedDirs, @submakeDirs); @sourceDirs = collectDirs(".", \@allExcludedDirs); error("too many subdirs for --deep") if (@sourceDirs > 1000); } else { @sourceDirs = (); if (isGoodDir(".", \@exceptSubdirs)) { @sourceDirs = ("."); } } foreach $f (@sourceDirs) { my $ff = ($f eq ".") ? "" : "$f/"; push(@ccfiles, glob("$ff*.cc")); push(@cppfiles, glob("$ff*.cpp")); push(@msgfiles, glob("$ff*.msg")); push(@smfiles, glob("$ff*.sm")); push(@nedfiles, glob("$ff*.ned")); } foreach my $i (@sourceDirs) { my $i2 = $i; $i2 =~ s|/|\\|g; push(@backslashedSourceDirs, $i2); } # include dirs and lib dirs (make them relative to the project dir) @tmp = @includeDirs; @includeDirs = (); foreach $i (@tmp) { push(@includeDirs, abs2rel($i,$projectDir)); } @tmp = @libDirs; @libDirs = (); foreach $i (@tmp) { push(@libDirs, abs2rel($i,$projectDir)); } # try to determine if .cc or .cpp files are used if ($ccExt eq "") { if (!@ccfiles && @cppfiles) { $ccExt = "cpp"; } elsif (@ccfiles && !@cppfiles) { $ccExt = "cc"; } elsif (@ccfiles && @cppfiles) { error("you have both .cc and .cpp files -- specify -e cc or -e cpp option to select which set of files to use"); } else { $ccExt = "cc"; # if no files, use .cc extension } } else { if ($ccExt eq "cc" && !@ccfiles && @cppfiles) { warning("you specified -e cc but you have only .cpp files!"); } if ($ccExt eq "cpp" && @ccfiles && !@cppfiles) { warning("you specified -e cpp but you have only .cc files!"); } } $objExt = "o"; $targetPrefix = ""; $targetSuffix = ""; if ($type eq "EXE") { $targetSuffix = "\$(EXE_SUFFIX)"; } elsif ($type eq "SHAREDLIB") { $targetSuffix = "\$(SHARED_LIB_SUFFIX)"; $targetPrefix = "\$(LIB_PREFIX)"; } elsif ($type eq "STATICLIB") { $targetSuffix = "\$(A_LIB_SUFFIX)"; $targetPrefix = "\$(LIB_PREFIX)"; } # prepare submakeDirs. First, check that all specified subdirs exist foreach $subdir (@submakeDirs) { if (! -d $subdir) { error("subdirectory '$subdir' does not exist"); } } if ($isRecursive) { foreach $f (glob("*")) { if (isGoodDir($f, \@exceptSubdirs)) { push(@submakeDirs, $f); } } } # create names for subdir targets @submakeNames = (); foreach $i (@submakeDirs) { my $i2 = $i; $i2 =~ s/[^a-zA-Z0-9_]/__/g; push(@submakeNames, $i2); } # process extraArgs (do ".o" <--> ".obj" translation; NOTE: we don't try to # translate library names, because libs are supposed to be given via -L/-l) @extraObjs = (); foreach $i (@extraArgs) { my $i2 = $i; $i2 =~ s/^'(.*)'$/$1/; # remove possible quotes (cmd.exe does not do it) $i2 =~ s/\.o(bj)?$/.$objExt/; push(@extraObjs, $i2); } @sources = (); push(@sources, @ccfiles) if ($ccExt eq "cc"); push(@sources, @cppfiles) if ($ccExt eq "cpp"); push(@sources, @msgfiles); push(@sources, @smfiles); push(@sources, @nedfiles) if (!$ignoreNedFiles); foreach $i (@sources) { $i =~ s/\*[^ ]*//g; $i =~ s/[^ ]*_n\.$ccExt$//g; $i =~ s/[^ ]*_m\.$ccExt$//g; $i =~ s/[^ ]*_sm\.$ccExt$//g; $i =~ s/\.ned$/_n.$objExt/g; $i =~ s/\.msg$/_m.$objExt/g; $i =~ s/\.sm$/_sm.$objExt/g; $i =~ s/\.$ccExt$/.$objExt/g; if ($i ne '') { push(@objs, "\$O/$i"); } } foreach $i (@msgfiles) { $h = $i; $h =~ s/\.msg$/_m.h/; $cc = $i; $cc =~ s/\.msg$/_m.$ccExt/; push(@generatedHeaders, $h); push(@msgccfiles, $cc); push(@msghfiles, $h); } foreach $i (@smfiles) { $h = $i; $h =~ s/\.sm$/_sm.h/; $cc = $i; $cc =~ s/\.sm$/_sm.$ccExt/; push(@generatedHeaders, $h); push(@smccfiles, $cc); push(@smhfiles, $h); } $makefrags = ""; if (@fragmentFiles) { foreach $frag (@fragmentFiles) { $makefrags .= "# inserted from file '$frag':\n"; $makefrags .= readTextFile($frag) . "\n"; } } else { $makefragFilename = "makefrag"; if (-f $makefragFilename) { $makefrags .= "# inserted from file '$makefragFilename':\n"; $makefrags .= readTextFile($makefragFilename) . "\n"; } } # defines if ($compileForDll && $dllSymbol ne "") { push(@defines, $dllSymbol."_EXPORT"); } $deps = ""; # we'll run opp_makedep afterwards # XP has 8K limit on line length, so we may have to use the inline file feature of nmake $approximateLinkerLineLength = 500 + length(quoteJoin(\@objs)) + length(quoteJoin(\@extraObjs)) + 2*length(quoteJoin(\@libs)) + 2*length(quoteJoin(\@libDirs)); $isLongLinkerLine = $approximateLinkerLineLength > 8000; # fill in template variables %m = ( "lbrace" => "{", "rbrace" => "}", "targetprefix" => $targetPrefix, "target" => $target, "targetsuffix" => $targetSuffix, "outdir" => $outDir, "subpath" => $subpath, "isdeep" => $isDeep, "progname" => "opp_makemake", "args" => join(" ", @args), "configfile" => $configFile, "cc" => $ccExt, "deps" => $deps, "exe" => $type eq "EXE", "sharedlib" => $type eq "SHAREDLIB", "staticlib" => $type eq "STATICLIB", "nolink" => $type eq "NOLINK", "defaultmode" => $defaultMode, "allenv" => ($userInterface =~ /^A/) ne "", "cmdenv" => ($userInterface =~ /^C/) ne "", "tkenv" => ($userInterface =~ /^T/) ne "", "qtenv" => ($userInterface =~ /^Q/) ne "", "extraobjs" => quoteJoin(\@extraObjs), "includepath" => prefixQuoteJoin(\@includeDirs, "-I"), "libpathdirs" => \@libDirs, "libs" => \@libs, "defines" => prefixQuoteJoin(\@defines, "-D"), "makefilevariables" => \@makefileVariables, "makecommand" => $makecommand, "makefile" => "Makefile", "makefrags" => $makefrags, "msgccfiles" => \@msgccfiles, "msghfiles" => \@msghfiles, "msgfiles" => \@msgfiles, "smccfiles" => \@smccfiles, "smhfiles" => \@smhfiles, "smfiles" => \@smfiles, "objs" => quoteJoin(\@objs), "submakedirs" => \@submakeDirs, "submakenames" => \@submakeNames, "dllsymbol" => $dllSymbol, "sourcedirs" => \@sourceDirs, "backslashedsourcedirs" => \@backslashedSourceDirs, "gcclongline" => ($isLongLinkerLine && $isWindows) ); my $content = substituteIntoTemplate(template(), \%m, "{", "}"); open(OUT, ">$makefile"); print OUT $content; close OUT; } sub collectDirs($;$); sub collectDirs($;$) { my ($dir,$exceptDirsRef) = @_; my @exceptDirs = @$exceptDirsRef; my @result = (); if (isGoodDir($dir, \@exceptDirs)) { push(@result, canonicalize($dir)); foreach $f (glob("$dir/*")) { push(@result, collectDirs($f, \@exceptDirs)); } } return @result; } sub isGoodDir($;$) { my ($dirpath,$exceptDirsRef) = @_; my @exceptDirs = @$exceptDirsRef; if (!-d $dirpath) { return 0; } # skip dot directories, also CVS, SVN, etc aux dirs my @IGNORABLE_DIRS = ("CVS", "RCS", "SCCS", "_darcs", "blib", ".git", ".svn", ".git", ".bzr", ".hg", "backups"); my $dirname = $dirpath; $dirname =~ s|^.*/||; if ($dirname =~ /^\.(.+)/ || grep(/^\Q$dirname\E$/, @IGNORABLE_DIRS)) { return 0; } # check exceptdirs. For that, we convert everything to canonical absolute path my $absDir = existingDirtoCanonicalAbsolute($dirpath); foreach my $exceptDirPattern (@exceptDirs) { foreach my $exceptDir (glob($exceptDirPattern)) { if (-d $exceptDir) { my $absExceptDir = existingDirtoCanonicalAbsolute($exceptDir); if ($absDir eq $absExceptDir) { return 0; } } } } return 1; } # # Performs template substitution. Constructs understood are: # - {foo} gets replaced by the value of "foo"; # - {bar?some text} gets replaced by "some text" if value of "bar" is true*. # - {~bar?some text} gets replaced by "some text" if value of "bar" is false* # - {bar:} only keep the rest of the line if value of "bar" is true* # - {~bar:} only keep the rest of the line if value of "bar" is false* # - {@i1:list1,i2:list2,...} ... {i1} ...{/@} parallel iteration list1, list2 etc. # * true/false are interpreted as in Perl: "" and "0" are false, everything else is true. # # Newlines inside {...} are not permitted; this allows detecting errors caused # by misplaced braces. Also, nesting is not supported. # # Lines starting with ### treated as comments and will be removed from the output # sub substituteIntoTemplate($;$;$;$); sub substituteIntoTemplate($;$;$;$) { my ($template,$mapref,$startTag,$endTag) = @_; my %map = %$mapref; $template =~ s/\n[ \t]*###.*\n/\n/g; # remove whole-line comments $template =~ s/[ \t]*###.*\n/\n/g; # remove end-line comments my $buf = ""; my $startTagLen = length($startTag); my $endTagLen = length($endTag); my $current = 0; while (1) { my $start = index($template, $startTag, $current); if ($start == -1) { last; } else { my $end = index($template, $endTag, $start); if ($end != -1) { $end += $endTagLen; my $tag = substr2($template, $start, $end); #print("processing $tag\n"); my $key = substr2($template, $start+$startTagLen, $end-$endTagLen); if (index($key, "\n") != -1) { die("template error: newline inside \"$tag\" (misplaced start/end tag?)"); } my $isLoop = substr($key, 0, 1) eq "@"; if ($isLoop) { $key = substr($key, 1); # drop "@" } my $isNegated = substr($key, 0, 1) eq "~"; if ($isNegated) { $key = substr($key, 1); # drop "~" } my $isOptLine = substr($key,-1,1) eq ":" && index($key, "?") == -1; if ($isOptLine) { $key = substr($key, 0, length($key)-1); # drop trailing ":" } my $questionmarkPos = index($key, "?"); my $substringAfterQuestionmark = $questionmarkPos == -1 ? "" : substr($key, $questionmarkPos+1); if ($questionmarkPos != -1) { $key = substr2($key, 0, $questionmarkPos); # drop "?..." from key } # determine replacement string, and possibly adjust start/end my $replacement = ""; if ($isLoop) { # basic loop syntax: {@i:list1,j:list2,...} ... {i} ... {/@} # this is parallel iteration, not nested loops! # first, find loop close tag {/@} my $loopEndTag = "$startTag/\@$endTag"; # "{/var}" my $balance = 1; my $pos = $end-1; # because we'll start with +1 while ($balance != 0) { $pos = indexOfEither($template, "$startTag\@", $loopEndTag, $pos+1); if ($pos == -1) { error("template error: missing loop end marker $loopEndTag"); } my $isStartTag = substr($template, $pos+$startTagLen,1) eq '@'; $balance += $isStartTag ? 1 : -1; } my $loopEndPos = $pos; my $loopBody = substr2($template, $end, $loopEndPos); $end = $loopEndPos + length($loopEndTag); # parse loop spec: "i:list1,j:list2,..." my @loopVars = (); my @loopLists = (); foreach $loopSpec (split(",", $key)) { if (!($loopSpec =~ / *([a-zA-Z0-9_]+) *: *([a-zA-Z0-9_]+) */)) { error("template error: syntax error in loop tag $tag, $startTag\@var1:list1,var2:list2,...$endTag expected in body"); } push(@loopVars, $1); push(@loopLists, $2); } # execute loop: iterate in parallel on all loop variables my $length = @{getFromMapAsList(\%map, $loopLists[0])}; for (my $i=0; $i<$length; $i++) { for (my $j=0; $j<@loopVars; $j++) { my $loopVarJ = $loopVars[$j]; my @loopListJ = @{getFromMapAsList(\%map, $loopLists[$j])}; if (@loopListJ != $length) { error("template error: list lengths differ in $tag"); } $map{$loopVarJ} = $loopListJ[$i]; } $replacement .= substituteIntoTemplate($loopBody, \%map, $startTag, $endTag); } # remove loop variables for (my $j=0; $j<@loopVars; $j++) { undef $map{$loopVars[$j]}; } } elsif ($isOptLine) { # replacing a whole line my $condition = getFromMapAsBool(\%map, $key); if ($isNegated ? !$condition : $condition) { # put line in: all variables OK } else { # omit line my $endLine = index($template, "\n", $end); if ($endLine == -1) { $endLine = length($template); } $replacement = ""; $end = $endLine + 1; } } elsif ($questionmarkPos != -1) { # conditional $replacement = getFromMapAsBool(\%map, $key)!=$isNegated ? $substringAfterQuestionmark : ""; } else { # plain replacement if ($isNegated) { die("template error: wrong syntax \"$tag\" (possible missing \"?\")"); } $replacement = getFromMapAsString(\%map, $key); } # do it: replace substring(start, end) with replacement, unless replacement==null $buf .= substr2($template, $current, $start); # template code up to the {...} $buf .= $replacement; $current = $end; } } } $buf .= substr($template, $current); # rest of the template $buf =~ s/ +\n/\n/sg; # remove spaces at line end $buf =~ s/\n\n\n+/\n\n/sg; # remove multiple empty lines return $buf; } sub substr2($;$;$) { my($string, $startoffset, $endoffset) = @_; return substr($string, $startoffset, $endoffset - $startoffset); } sub quoteJoin($) { my($listref) = @_; return prefixQuoteJoin($listref, ""); } sub prefixQuoteJoin($;$) { my($listref,$prefix) = @_; @list = @$listref; $sep = (@list > 5) ? " \\\n " : " "; $result = ""; foreach $i (@list) { $result .= $sep . $prefix . quote($i); } return $result eq "" ? "" : substr($result, 1); # chop off leading space } sub indexOfEither($;$;$;$) { my($template,$substring1,$substring2,$from) = @_; my $index1 = index($template, $substring1, $from); my $index2 = index($template, $substring2, $from); return $index2 if ($index1 == -1); return $index1 if ($index2 == -1); return $index1 < $index2 ? $index1 : $index2; } # for substituteIntoTemplate() sub getFromMapAsString($;$) { my($mapref,$key) = @_; my %map = %$mapref; die("template error: undefined template parameter '$key'") if (!defined($map{$key})); return $map{$key}; } # for substituteIntoTemplate() sub getFromMapAsBool($;$) { my($mapref,$key) = @_; my %map = %$mapref; die("template error: undefined template parameter '$key'") if (!defined($map{$key})); $value = $map{$key}; if (ref($value) eq 'ARRAY') { my @list = @$value; return @list; # =length; 0 if list is empty } else { return $value ? 1 : 0; } } # for substituteIntoTemplate() sub getFromMapAsList($;$) { my($mapref,$key) = @_; my %map = %$mapref; die("template error: undefined template parameter '$key'") if (!defined($map{$key})); my $value = $map{$key}; die("template error: list value expected for template parameter '$key', got '$value'") if (ref($value) ne 'ARRAY'); return $value; } # # Converts absolute path $inputpath to relative path (relative to the current # directory $referencedir), provided that both $inputpath and $referencedir are under a # "project base directory" $projectdir. Otherwise it returns the original path. # All "\" are converted to "/". # sub abs2rel($;$;$;) { my($inputpath, $projectdir, $referencedir) = @_; if (!defined($projectdir) || $projectdir eq '') { return $inputpath; } if (!defined($referencedir) || $referencedir eq '') { $referencedir = cwd; } # some normalization $inputpath =~ s|\\|/|g; $referencedir =~ s|\\|/|g; $projectdir =~ s|\\|/|g; $inputpath =~ s|/\./|/|g; $referencedir =~ s|/\./|/|g; $projectdir =~ s|/\./|/|g; $inputpath =~ s|//+|/|g; $referencedir =~ s|//+|/|g; $projectdir =~ s|//+|/|g; $referencedir =~ s|/*$|/|; $projectdir =~ s|/*$|/|; if (!($inputpath =~ /^\Q$projectdir\E/i && $referencedir =~ /^\Q$projectdir\E/i)) { return $inputpath; } while (1) { # keep cutting off common prefixes until no more if (!($inputpath =~ m|^(.*?/)|)) { last; } my $prefix = $1; if ($referencedir =~ /^\Q$prefix\E/i) { $inputpath =~ s/^\Q$prefix\E//i; $referencedir =~ s/^\Q$prefix\E//i; } else { last; } } # assemble relative path: change every directory name in $referencedir to "..", # then add $inputpath to it. $referencedir =~ s|[^/]+|..|g; my $rel = $referencedir.$inputpath; return $rel; } sub isRelative($) { # a path is absolute if it begins with "/" or "\", or contains ":/" or ":\" my($path) = @_; return !($path =~ /^[\/\\]/ || $path =~ /:[\/\\]/); } # # Convert a path to an absolute path in canonical form. # Path must point to a directory which currently exists. # sub existingDirtoCanonicalAbsolute($) { my($dir) = @_; die "argument must be an existing directory: $dir" unless (-d $dir); $old = cwd(); chdir($dir); $ret = cwd(); chdir($old); return $ret; } sub canonicalize($) { my($path) = @_; $path =~ s|\\|/|g; # backslash -> fwd slash $path =~ s|//+|/|g; # xx//xx -> xx/xx $path =~ s|(/\.)+/|/|g; # xx/././xx -> xx/xx $path =~ s|^(\./)+||s unless $path eq "./"; # ./xx -> xx $path =~ s|/$||g; # xx/ -> xx # # now we'll need to replace "//../" with "/" as many times as we can; # # unless is "." or ".." # $path .= "/" if ($path =~ /\/\.\.$/); # add "/" if it ends in "/.." # $path =~ s|/../../|/..//../|g; # prevent /../../ from matching # $path =~ s|/../../|/..//../|g; # once again, to handle /../../../ # while ($path =~ s|/[^/]+/\.\./|/|g) { # $path =~ s|//+|/|g; # $path =~ s|/../../|/..//../|g; # $path =~ s|/../../|/..//../|g; # }; # $path =~ s|//+|/|g; # xx//xx -> xx/xx return $path; } sub quote($) { my($dir) = @_; if ($dir =~ / /) {$dir = "\"$dir\"";} return $dir; } sub readTextFile($) { my($file) = @_; open(INFILE, $file) || die "cannot open $file"; defined read(INFILE, $content, 1000000) || die "cannot read $file"; return $content; } sub runprog { my $cmd = shift; if ($isWindows && !$isMINGW) { system($ENV{COMSPEC}, "/c", $cmd); } else { system($cmd); } } sub error($) { my($text) = @_; print STDERR "$progname: error: $text\n"; exit(1); } sub warning($) { my($text) = @_; print STDERR "$progname: warning: $text\n"; } sub template() { # # NOTE: the following template must be kept in sync with the file: # /ui/org.omnetpp.cdt/src/org/omnetpp/cdt/makefile/Makefile.TEMPLATE # return <<'ENDTEMPLATE' # # OMNeT++/OMNEST Makefile for {targetprefix}{target} # # This file was generated with the command: # {progname} {args} # ### Some definitions first; note that we only print them if there're going to be needed {~nolink:}# Name of target to be created (-o option) {~nolink:}TARGET = {targetprefix}{target}$(D){targetsuffix} {~nolink:}TARGET_DIR = . {~nolink:} {exe:}# User interface (uncomment one) (-u option) {exe:}{~allenv?#}USERIF_LIBS = $(ALL_ENV_LIBS) # that is, $(TKENV_LIBS) $(QTENV_LIBS) $(CMDENV_LIBS) {exe:}{~cmdenv?#}USERIF_LIBS = $(CMDENV_LIBS) {exe:}{~tkenv?#}USERIF_LIBS = $(TKENV_LIBS) {exe:}{~qtenv?#}USERIF_LIBS = $(QTENV_LIBS) {exe:} {sourcedirs:}# C++ include paths (with -I) {sourcedirs:}INCLUDE_PATH = {includepath} {sourcedirs:} {~nolink:}# Additional object and library files to link with {~nolink:}EXTRA_OBJS = {extraobjs} {~nolink:} {~nolink:}{~staticlib:}# Additional libraries (-L, -l options) {~nolink:}{~staticlib:}LIBS ={@dir:libpathdirs} $(LDFLAG_LIBPATH){dir}{/@} {@lib:libs} -l{lib}{/@} {~nolink:}{~staticlib:} # Output directory ### Note: these variables are public API (see help text), don't change PROJECT_OUTPUT_DIR = {outdir} PROJECTRELATIVE_PATH = {subpath} O = $(PROJECT_OUTPUT_DIR)/$(CONFIGNAME)/$(PROJECTRELATIVE_PATH) {sourcedirs:}# Object files for local .{cc}, .msg and .sm files {sourcedirs:}OBJS = {objs} {sourcedirs:} {sourcedirs:}# Message files {sourcedirs:}MSGFILES ={@msg:msgfiles} \ {sourcedirs:} {msg}{/@} {sourcedirs:} {sourcedirs:}# SM files {sourcedirs:}SMFILES ={@sm:smfiles} \ {sourcedirs:} {sm}{/@} {sourcedirs:} {defaultmode:}# Default mode (-M option); can be overridden with make MODE=debug (or =release) {defaultmode:}ifndef MODE {defaultmode:}MODE = {defaultmode} {defaultmode:}endif {defaultmode:} {makefilevariables:}# Other makefile variables (-K) {@d:makefilevariables}{d} {/@} #------------------------------------------------------------------------------ # Pull in OMNeT++ configuration (Makefile.inc) ifneq ("$(OMNETPP_CONFIGFILE)","") CONFIGFILE = $(OMNETPP_CONFIGFILE) else ifneq ("$(OMNETPP_ROOT)","") CONFIGFILE = $(OMNETPP_ROOT)/Makefile.inc else CONFIGFILE = $(shell opp_configfilepath) endif endif ifeq ("$(wildcard $(CONFIGFILE))","") $(error Config file '$(CONFIGFILE)' does not exist -- add the OMNeT++ bin directory to the path so that opp_configfilepath can be found, or set the OMNETPP_CONFIGFILE variable to point to Makefile.inc) endif include $(CONFIGFILE) ### # Check that MODE is valid (not enabled, to allow implementing other MODEs) ### ifneq ($(MODE),"debug") ### ifneq ($(MODE),"release") ### $(error MODE must be "debug" or "release") ### endif ### endif {~nolink:}{~staticlib:}# Simulation kernel and user interface libraries {exe:}OMNETPP_LIBS = $(OPPMAIN_LIB) $(USERIF_LIBS) $(KERNEL_LIBS) $(SYS_LIBS) {sharedlib:}OMNETPP_LIBS = -loppenvir$D $(KERNEL_LIBS) $(SYS_LIBS) {~nolink:}{~staticlib:}{libpathdirs:}ifneq ($(TOOLCHAIN_NAME),clangc2) {~nolink:}{~staticlib:}{libpathdirs:}LIBS +={@dir:libpathdirs} -Wl,-rpath,$(abspath {dir}){/@} {~nolink:}{~staticlib:}{libpathdirs:}endif {sourcedirs:}COPTS = $(CFLAGS) $(IMPORT_DEFINES) {defines} $(INCLUDE_PATH) -I$(OMNETPP_INCL_DIR) {sourcedirs:}MSGCOPTS = $(INCLUDE_PATH){dllsymbol? -P}{dllsymbol}{dllsymbol?_API} {sourcedirs:}SMCOPTS = {sourcedirs:} # we want to recompile everything if COPTS changes, # so we store COPTS into $COPTS_FILE and have object # files depend on it (except when "make depend" was called) COPTS_FILE = $O/.last-copts ifneq ("$(COPTS)","$(shell cat $(COPTS_FILE) 2>/dev/null || echo '')") $(shell $(MKPATH) "$O" && echo "$(COPTS)" >$(COPTS_FILE)) endif #------------------------------------------------------------------------------ # User-supplied makefile fragment(s) # >>> {makefrags:}{makefrags} # <<< #------------------------------------------------------------------------------ # Main target {~nolink:}all: $(TARGET_DIR)/$(TARGET) {~nolink:} ### Rules for $TARGET. Note that end product will be hardlinked into the ### TARGET_DIR (by default, the Makefile's directory); on systems that don't ### support soft links it will be copied. ### Copy binaries to their final destination from the out directory $(TARGET_DIR)/% :: $O/% @mkdir -p $(TARGET_DIR) $(Q)$(LN) $< $@ ifeq ($(TOOLCHAIN_NAME),clangc2) $(Q)-$(LN) $(<:%.dll=%.lib) $(@:%.dll=%.lib) endif {exe:}$O/$(TARGET): {sourcedirs?$(OBJS)} {submakedirs?submakedirs} $(wildcard $(EXTRA_OBJS)) {makefile} $(CONFIGFILE) {exe:} @$(MKPATH) $O {exe:} @echo Creating executable: $@ {gcclongline:}{exe:} $(Q)echo >.tmp$$$$ {sourcedirs?$(OBJS)} $(EXTRA_OBJS) && $(AR) .tmplib$$$$ @.tmp$$$$ && $(CXX) -o $O/$(TARGET) $(AS_NEEDED_OFF) $(WHOLE_ARCHIVE_ON) .tmplib$$$$ $(LIBS) $(WHOLE_ARCHIVE_OFF) $(OMNETPP_LIBS) $(LDFLAGS) && rm .tmp$$$$ && rm .tmplib$$$$ {~gcclongline:}{exe:} $(Q)$(CXX) $(LDFLAGS) -o $O/$(TARGET) {sourcedirs?$(OBJS)} $(EXTRA_OBJS) $(AS_NEEDED_OFF) $(WHOLE_ARCHIVE_ON) $(LIBS) $(WHOLE_ARCHIVE_OFF) $(OMNETPP_LIBS) {sharedlib:}$O/$(TARGET): {sourcedirs?$(OBJS)} {submakedirs?submakedirs} $(wildcard $(EXTRA_OBJS)) {makefile} $(CONFIGFILE) {sharedlib:} @$(MKPATH) $O {sharedlib:} @echo Creating shared library: $@ {gcclongline:}{sharedlib:} $(Q)echo >.tmp$$$$ {sourcedirs?$(OBJS)} $(EXTRA_OBJS) && $(AR) .tmplib$$$$ @.tmp$$$$ && $(SHLIB_LD) -o $O/$(TARGET) $(AS_NEEDED_OFF) $(WHOLE_ARCHIVE_ON) .tmplib$$$$ $(LIBS) $(WHOLE_ARCHIVE_OFF) $(OMNETPP_LIBS) $(LDFLAGS) && rm .tmp$$$$ && rm .tmplib$$$$ {~gcclongline:}{sharedlib:} $(Q)$(SHLIB_LD) -o $O/$(TARGET) {sourcedirs?$(OBJS)} $(EXTRA_OBJS) $(AS_NEEDED_OFF) $(WHOLE_ARCHIVE_ON) $(LIBS) $(WHOLE_ARCHIVE_OFF) $(OMNETPP_LIBS) $(LDFLAGS) {sharedlib:} $(Q)$(SHLIB_POSTPROCESS) $O/$(TARGET) {staticlib:}$O/$(TARGET): {sourcedirs?$(OBJS)} {submakedirs?submakedirs} $(wildcard $(EXTRA_OBJS)) {makefile} $(CONFIGFILE) {staticlib:} @$(MKPATH) $O {staticlib:} @echo Creating static library: $@ {gcclongline:}{staticlib:} $(Q)echo >.tmp$$$$ {sourcedirs?$(OBJS)} $(EXTRA_OBJS) && $(AR) $O/$(TARGET) @.tmp$$$$ && rm .tmp$$$$ {~gcclongline:}{staticlib:} $(Q)$(AR) $O/$(TARGET) {sourcedirs?$(OBJS)} $(EXTRA_OBJS) {nolink:}all: {sourcedirs?$(OBJS)} {submakedirs?submakedirs} {makefile} $(CONFIGFILE) {nolink:} @# Do nothing {submakedirs:}submakedirs: {@i:submakenames} {i}_dir{/@} {submakedirs:} .PHONY: all clean cleanall depend msgheaders smheaders {@i:submakenames} {i}{/@} {@i:submakenames}{i}: {i}_dir {/@} {submakedirs:} {@i:submakenames,dir:submakedirs}{i}_dir: cd {dir} && $(MAKE) all {/@} {sourcedirs:}.SUFFIXES: .{cc} {sourcedirs:} ### Pattern rules for cc files. {sourcedirs:}$O/%.o: %.{cc} $(COPTS_FILE) | msgheaders smheaders {sourcedirs:} @$(MKPATH) $(dir $@) {sourcedirs:} $(qecho) "$<" {sourcedirs:} $(Q)$(CXX) -c $(CXXFLAGS) $(COPTS) -o $@ $< {@dir:sourcedirs,bsdir:backslashedsourcedirs} {/@} ### Pattern rules for msg files. {sourcedirs:}%_m.{cc} %_m.h: %.msg {sourcedirs:} $(qecho) MSGC: $< {sourcedirs:} $(Q)$(MSGC) -s _m.{cc} -MD -MP -MF $O/$(basename $@).d $(MSGCOPTS) $? {sourcedirs:} {@msg:msgfiles,m_cc:msgccfiles,m_h:msghfiles} {/@} ### Pattern rules for sm files. {sourcedirs:}%_sm.{cc} %_sm.h: %.sm {sourcedirs:} $(qecho) SMC: $< {sourcedirs:} $(Q)$(SMC) -c++ -suffix {cc} $(SMCOPTS) $? {sourcedirs:} {@sm:smfiles,sm_cc:smccfiles,sm_h:smhfiles} {/@} ### Utility target for running opp_msgc; otherwise unused by this makefile msgheaders: {sourcedirs?$(MSGFILES:.msg=_m.h)} {@i:submakedirs} $(Q)cd {i} && $(MAKE) msgheaders {/@} ### Utility target for running the SMC compiler; otherwise unused by this makefile smheaders: {sourcedirs?$(SMFILES:.sm=_sm.h)} {@i:submakedirs} $(Q)cd {i} && $(MAKE) smheaders {/@} ### clean, depend, etc. clean: $(qecho) Cleaning $(TARGET) $(Q)-rm -rf $O ### must be done separately, because it fails on MinGW ('rm' is bogus) {~nolink:} $(Q)-rm -f $(TARGET_DIR)/$(TARGET) {~nolink:} $(Q)-rm -f $(TARGET_DIR)/$(TARGET:%.dll=%.lib) $(Q)-rm -f $(call opp_rwildcard, . , *_m.{cc} *_m.h *_sm.{cc} *_sm.h) {@i:submakedirs} -$(Q)cd {i} && $(MAKE) clean {/@} cleanall: $(Q)$(MAKE) -s clean MODE=release $(Q)$(MAKE) -s clean MODE=debug $(Q)-rm -rf $(PROJECT_OUTPUT_DIR) # include all dependencies -include $(OBJS:%.o=%.d) ENDTEMPLATE }