diff -r 997c19261166 -r 9f25be3da657 imgtools/imaker/src/imaker.pl --- a/imgtools/imaker/src/imaker.pl Fri Jun 18 13:49:03 2010 +0300 +++ b/imgtools/imaker/src/imaker.pl Thu Jun 24 10:35:05 2010 +0300 @@ -11,157 +11,2183 @@ # # Contributors: # -# Description: iMaker main Perl script +# Description: iMaker main Perl script & common routines # # -$(error >>>MAKECMDGOALS=$(MAKECMDGOALS)<<<) +$(error |MAKE=$(MAKE)|MAKE_VERSION=$(MAKE_VERSION)|SHELL=$(SHELL)|MAKECMDGOALS=$(MAKECMDGOALS)|) # #!perl +#line 24 + +use subs qw(CORE::GLOBAL::die); use strict; use warnings; -use Getopt::Long qw(:config pass_through no_auto_abbrev); +use Cwd; +use Digest::MD5 qw(md5_hex); +use File::Basename; +use File::Copy; +use File::Find; +use File::Path; +use File::Spec; +use File::Temp qw(tempfile); +use POSIX qw(strftime); +use Text::ParseWords; +use Time::Local; -my $error = ""; -my $perlver; -my $start; +sub InitMkglobals(); +sub PrintEnv($); +sub Max(@); +sub Min(@); +sub Trim($;$); +sub Quote($); +sub Unquote($); +sub Int2Hex($;$); +sub Byte2Str($@); +sub Str2Byte($); +sub Str2Xml($); +sub Ascii2Uni($); +sub Uni2Ascii($); +sub GetTimestamp(); +sub Sec2Min($); +sub Wcard2Restr($); +sub Wcard2Regex($); +sub ParseCmdWords($); +sub DPrint($@); +sub Echo($$$); +sub PathConv($;$$$); +sub ParseFiles($); +sub GlobFiles($;$); +sub GetBasename($); +sub GetDirname($); +sub GetAbsDirname($;$$$); +sub GetAbsFname($;$$$); +sub GetRelFname($;$$); +sub GetWriteFname($); +sub GetFreeDrive(;$); +sub SubstDrive($$); +sub UnsubstDrive($); +sub Search($$$$$$\@\$); +sub Find($$$$$\$); +sub ChangeDir($); +sub DeleteDir($;$); +sub FindDir($$$$); +sub MakeDir($); +sub MakeChangeDir($); +sub SetWorkdir($); +sub OpenFile(*$$;$); +sub Test($); +sub CutFile($$$$$); +sub Copy($$;$); +sub CopyIby($$); +sub DeleteFile($;$); +sub FindFile($$$$); +sub HeadFile($$$); +sub TailFile($$$); +sub TypeFile($;$); +sub ReadFile($$); +sub WriteFile($$$;$$); +sub UnzipFile($$); +sub Zip($$$$@); +sub Move($$); +sub Touch($@); +sub SetLogfile($); +sub RunSystemCmd($;$$$); +sub ParseSystemCmd($$$$$); +sub GenExclfile($$$$$); +sub GenIbyfile($$$); +sub GenObyfile($$$$@); +sub GenMakefile($$$$$); +sub GenWidgetConf($$$$); +sub AddImageHeader($$$$$); +sub Sleep($); +sub FindSOSFiles($$$$); +sub CheckTool(@); +sub OpCacheInstall($$$); +sub SisInstall($$$$$$$$); +sub GetIPar(;$); +sub PEval($); +sub PeekICmd($); +sub SkipICmd(); +sub GetICmd(); +sub EndICmd(); +sub SplitStep($); +sub RunStep($); +sub RunIExtCmd($); +sub GetConfmkList(;$); +sub GetFeatvarIncdir($); +sub SetVerbose($;$); +sub CloseLog(); +sub RunIMakerCmd($$$$$@); +sub RunMakeCmd($$); +sub HandleCmdArg($); +sub HandleExtCmdArg($); +sub MenuRuncmd($); +sub Menu($); +sub Install($$$); + +use constant READBUFSIZE => 2097152; # 2 MB +use constant STARTSTR => '>>>[START]=========8<==========8<==========8<==========8<==========8<=========='; +use constant ENDSTR => '==========>8==========>8==========>8==========>8==========>8===========[END]<<<'; + +# device[VARID]==... !! +# +use constant BOOTBINARYSTATEMENT => qr/^\s*bootbinary\s*(?:=+|\s)\s*(?:"(.+?)"|(\S+))/i; + +use constant FILESPECSTATEMENT => + qr/^\s*(?:data|device|dll|extension|file|primary|secondary|variant)\S*?\s*(?:=+|\s)\s*(?:"(.+?)"|(\S+))\s+(?:"(.+?)"|(\S+))(\s+.+?)?\s*$/i; + +our ($gArgv, $gCmdcnt, @gCmdoutbuf, %gConfmkList, $gEpocdrive, $gEpocroot, $gError, $gErrwarn, $gEvalerr, + %gExportvar, $gFiltercmd, @gFindresult, $gICmd, @gIcmd, $gImakerext, $gImgtype, $gKeepgoing, @gLogbuf, + $gLogfile, %gLogfiles, $gMakecmd, @gMakeinfo, $gOutfilter, $gParamcnt, $gPrintcmd, @gReport, $gStartmk, + $gStarttime, $gStep, @gStepDur, %gStepIcmd, %gSubstdrv, $gTgterr, %gTool, $gVerbose, $gWinOS, $gWorkdir, + $gWorkdrive, @iVar); + + +############################################################################### +# + +sub InitMkglobals() +{ + $gCmdcnt = 0; + @gCmdoutbuf = (); + $gFiltercmd = qr/\S/; + @gFindresult = (); + $gICmd = ""; + @gIcmd = (); + $gImgtype = ""; + $gOutfilter = ""; + $gParamcnt = 0; + $gPrintcmd = 0; + $gStep = ""; + @gStepDur = (); + %gStepIcmd = (); + @iVar = (); # General purpose variable to be used from $(call peval,...) +} BEGIN { - ($start, $perlver) = (time(), sprintf("%vd", $^V)); + ($gArgv, $gEvalerr, $gStarttime, $gWinOS) = (scalar(@ARGV), 0, time(), $^O =~ /MSWin/i); + $_ = "default input and pattern-searching space"; + eval("use Archive::Zip qw(:ERROR_CODES)"); + eval("use constant AZ_OK => -1") if $@; + eval("use Archive::Zip::Tree"); + if ($gWinOS) { eval(" + use Win32API::File qw(:DDD_); + use Win32::File; + use constant WIN32_FILE_HIDDEN => Win32::File::HIDDEN"); + } else { eval(" + use constant DDD_REMOVE_DEFINITION => -1; + use constant WIN32_FILE_HIDDEN => -1"); + } +} + +INIT { + $gWorkdir = Cwd::cwd(); + $gWorkdrive = ($gWorkdir =~ /^([a-z]:)/i ? uc($1) : ""); + $ENV{EPOCROOT} = ($gWinOS ? "\\" : "$gWorkdir/") if !$ENV{EPOCROOT}; + $ENV{IMAKER_CMDARG} = "" if !defined($ENV{IMAKER_CMDARG}); + $ENV{IMAKER_CYGWIN} = 0 if !$ENV{IMAKER_CYGWIN}; + + InitMkglobals(); + %gConfmkList = (); + $gEpocdrive = ($ENV{EPOCROOT} =~ /^([a-z]:)/i ? uc($1) : $gWorkdrive); + ($gEpocroot = GetAbsDirname($ENV{EPOCROOT})) =~ s/\/+$//; + $gError = 0; + $gErrwarn = 0; + %gExportvar = (); $gExportvar{""} = 0; + $gKeepgoing = 0; + @gLogbuf = (); + $gLogfile = ""; + %gLogfiles = (); + $gMakecmd = ""; + @gMakeinfo = ("?", "?", "?"); + @gReport = (); + $gStartmk = 0; + %gSubstdrv = (); + $gTgterr = 0; + %gTool = (); map{ $gTool{$_} => $_ } ("cpp", "elf2e32", "interpretsis", "opcache", "unzip"); + $gVerbose = 1; + select(STDERR); $|++; select(STDOUT); $|++; - if (!@ARGV) { - warn("Warning: iMaker is running under Cygwin!\n") + + # Overload die + *CORE::GLOBAL::die = sub { + $gError = 1 if !$gEvalerr; + return if (PeekICmd("iferror") && !$gEvalerr); + CORE::die(@_) if ($gEvalerr || !$gKeepgoing); + $gErrwarn = 1; + warn(@_); + }; + + # Handler for __DIE__ signal + $SIG{__DIE__} = sub { + return if $gEvalerr; + $gErrwarn = 1; + warn(@_); + exit(1); + }; + + # Handler for __WARN__ signal + $SIG{__WARN__} = sub { + if (($gEvalerr != 1) && ($gKeepgoing < 3) && ($_[0] ne "\n")) { + select(STDERR); + my $msg = ($gStep ? "($gStep): " : "") . $_[0]; + if ($gErrwarn && ($gKeepgoing < 2)) { + DPrint(0, "*** Error: $msg") } + else { DPrint(127, "Warning: $msg") } + select(STDOUT); + } + $gErrwarn = 0; + }; + + if (!$gArgv) { + warn("iMaker is running under Cygwin!\n") if (!$ENV{IMAKER_CYGWIN} && $^O =~ /cygwin/i); - warn("Warning: iMaker uses Perl version $perlver! Recommended versions are 5.6.1 and 5.8.8.\n") - if ($perlver !~ /^5\.(6\.1|8\.8)$/); + my $perlver = sprintf("%vd", $^V); + warn("iMaker uses Perl version $perlver! Recommended versions are 5.6.1, 5.8.x and 5.10.x.\n") + if ($perlver !~ /^5\.(?:6\.1|(?:8|10)\.\d+)$/); } - unshift(@INC, defined($ENV{IMAKER_DIR}) ? $ENV{IMAKER_DIR} : ($0 =~ /^(.*)[\/\\]/ ? $1 : ".")); } -use imaker; - ############################################################################### # Main program { - if (!@ARGV) { - $ENV{CONFIGROOT} = imaker::GetAbsDirname($ENV{CONFIGROOT}); - $ENV{ITOOL_DIR} = imaker::GetAbsDirname($ENV{ITOOL_DIR}, 0, 1); - $ENV{IMAKER_DIR} = imaker::GetAbsDirname($ENV{IMAKER_DIR}, 0, 1); - $ENV{PATH} = join(";", grep(!/[\\\/]cygwin[\\\/]/i, split(/;+/, $ENV{PATH}))) - if $imaker::gWinOS && !$ENV{IMAKER_CYGWIN}; + if ($gArgv) { + my $iopt = shift(@ARGV); + print(map("$_\n", GetFeatvarIncdir("@ARGV"))), exit(0) if ($iopt eq "--incdir"); + print(map("$_\n", @ARGV)), exit(0) if ($iopt eq "--splitarg"); + die("Unknown internal imaker.pl option: `$iopt'.\n"); + } + + delete($ENV{MAKE}) if $gWinOS; + map { delete($ENV{$_}) } qw(MAKECMDGOALS MAKEFILES MAKEFLAGS MAKELEVEL MAKE_VERSION); + + $ENV{CONFIGROOT} = GetAbsDirname($ENV{CONFIGROOT} || "$gEpocroot/epoc32/rom/config"); + $ENV{ITOOL_DIR} = GetAbsDirname($ENV{ITOOL_DIR} || "$gEpocroot/epoc32/tools/rom"); + $ENV{IMAKER_DIR} = GetAbsDirname($ENV{IMAKER_DIR}); + + $ENV{IMAKER_EXPORTMK} = ""; + $ENV{IMAKER_MAKE} = ($gWinOS ? "$ENV{IMAKER_DIR}/mingw_make.exe" : $ENV{MAKE} || "make") if !$ENV{IMAKER_MAKE}; + $ENV{IMAKER_MAKESHELL} = ($ENV{COMSPEC} || "cmd.exe") if (!$ENV{IMAKER_MAKESHELL} && $gWinOS); + $ENV{IMAKER_MKCONF} = $ENV{CONFIGROOT} . ',image_conf_(.+?)\.mk$,_(?:ncp)?\d+\.mk$,1' if !$ENV{IMAKER_MKCONF}; + + my $pathsep = ($gWinOS ? ";" : ":"); + $ENV{PATH} = join(";", grep(!/[\\\/]cygwin[\\\/]/i, split(/;+/, $ENV{PATH}))) if (!$ENV{IMAKER_CYGWIN} && $gWinOS); + ($ENV{PATH} = Trim($ENV{PATH})) =~ s/"$/";/ if $gWinOS; # http://savannah.gnu.org/bugs/index.php?25412 + $ENV{PATH} = PathConv("$ENV{ITOOL_DIR}", $gWinOS) . $pathsep . PathConv("$gEpocroot/epoc32/tools", $gWinOS) . + $pathsep . ($gWinOS ? PathConv("$gEpocroot/epoc32/gcc/bin", 1) . ";" : "") . $ENV{PATH}; + + $ENV{PERL5LIB} = $ENV{IMAKER_DIR} . ($ENV{PERL5LIB} ? "$pathsep$ENV{PERL5LIB}" : ""); + + die($@) if !defined($gImakerext = do("$ENV{IMAKER_DIR}/imaker_extension.pm")) && $@; + + my ($version, $verfile) = ("", "$ENV{IMAKER_DIR}/imaker_version.mk"); + open(FILE, "<$verfile") and map { $version = $1 if /^\s*IMAKER_VERSION\s*[+:?]?=\s*(.*?)\s*$/ } ; + close(FILE); + if ($version) { DPrint(1, "$version\n") } + else { warn("Can't read iMaker version from `$verfile'.\n") } + + if ($ENV{IMAKER_CMDARG} =~ /^\s*--?(install|clean)=?(.*?)\s*$/i) { + Install(lc($1) eq "clean", "$ENV{IMAKER_DIR}/../group/bld.inf", $2); + exit(0); + } + + $gMakecmd = "$ENV{IMAKER_MAKE} -R --no-print-directory" . + ($ENV{IMAKER_MAKESHELL} ? " SHELL=\"$ENV{IMAKER_MAKESHELL}\"" : ""); + my $cmdout = qx($gMakecmd -f "$0" 2>&1); + ($cmdout = (defined($cmdout) ? $cmdout : "")) =~ s/\n+$//; + die("Can't run Make properly: `$cmdout'\n") + if ($cmdout !~ /\|MAKE=(.*?)\|MAKE_VERSION=(.*?)\|SHELL=(.*?)\|/); + @gMakeinfo = ($1, $2, $3); + warn(($gMakeinfo[1] eq "" ? "Can't resolve Make version" : "iMaker uses Make version $gMakeinfo[1]") . + ", recommended version is 3.81.\n") if ($gMakeinfo[1] !~ /^\s*3\.81/); + + RunIMakerCmd("$gMakecmd TIMESTAMP=" . GetTimestamp() . + " -I \"$ENV{CONFIGROOT}\" -f \"$ENV{IMAKER_DIR}/imaker.mk\"", $ENV{IMAKER_CMDARG}, "", 0, 0, ()); +} + + +############################################################################### +# + +sub PrintEnv($) +{ + return if !@gMakeinfo; + DPrint(shift(), "=" x 79 . "\n" . + "User : " . (getlogin() || "?") . "@" . ($ENV{HOSTNAME} || $ENV{COMPUTERNAME} || "?") . " on $^O\n" . + "Time : " . localtime() . "\n" . + "Current dir : `$gWorkdir'\n" . + "iMaker tool : `$ENV{IMAKER_TOOL}' -> `$0'\n" . + "Cmdline args: `$ENV{IMAKER_CMDARG}'\n" . + "Perl : `$^X' version " . sprintf("%vd\n", $^V) . + "PERL5LIB : `$ENV{PERL5LIB}'\n" . + "PERL5OPT : `" . (defined($ENV{PERL5OPT}) ? "$ENV{PERL5OPT}'\n" : "'\n") . + "Make : `$gMakeinfo[0]' version $gMakeinfo[1]\n" . + "Make shell : `$gMakeinfo[2]'\n" . + "EPOCROOT : `$ENV{EPOCROOT}'\n" . + "CONFIGROOT : `$ENV{CONFIGROOT}'\n" . + "PATH : `$ENV{PATH}'\n"); + @gMakeinfo = (); +} + +sub Max(@) +{ + my $max = (shift() || 0); + map { $max = $_ if $_ > $max } @_; + return($max); +} + +sub Min(@) +{ + my $min = (shift() || 0); + map { $min = $_ if $_ < $min } @_; + return($min); +} + +sub Trim($;$) +{ + (my $str = shift()) =~ s/^\s+|\s+$//g; + $str =~ s/\s+(?=\s)//g if shift(); + return($str); +} + +sub Quote($) +{ + local $_ = shift(); + return("") if !defined(); + s/\\( |n|t)/\\\\$1/g; + return($_); +} + +sub Unquote($) +{ + local $_ = shift(); + return("") if !defined(); + s/(?'"', '&'=>'&', "'"=>''', '<'=>'<', '>'=>'>'}->{$1} || $1/ge; + return($str); +} + +sub Ascii2Uni($) +{ + (local $_ = shift()) =~ s/(?".*", "?"=>"."}->{$1} || "\Q$1\E"/ge; + return($wcard); +} + +sub Wcard2Regex($) +{ + my $restr = Wcard2Restr(shift()); + return(qr/$restr/i); +} + +sub ParseCmdWords($) +{ + my $line = Trim(shift()); + $line =~ s/\\/\\\\/g if $gWinOS; + return(Text::ParseWords::parse_line('\s+', 0, $line)); +} + + +############################################################################### +# + +sub DPrint($@) +{ + my ($verbose, @outlist) = @_; + map { tr/\x00\x1F/#/ } @outlist; + print(@outlist) if !$verbose || ($verbose & $gVerbose); + push(@gLogbuf, @outlist) if ($verbose < 32) || ($verbose & $gVerbose); + return if ($gLogfile eq "" || !@gLogbuf); + print(LOG @gLogbuf); + @gLogbuf = (); +} - my ($version, $verfile) = ("", "$ENV{IMAKER_DIR}/imaker_version.mk"); - open(FILE, "<$verfile") and map { $version = $1 if /^\s*IMAKER_VERSION\s*[+:?]?=\s*(.*?)\s*$/ } ; - close(FILE); - $version and print("$version\n") or - warn("Can't read iMaker version from `$verfile'.\n"); +sub Echo($$$) +{ + return if SkipICmd(); + my ($verbose, $str) = (shift(), shift()); + DPrint($verbose, shift() ? "$str\n" : Unquote($str)); +} + + +############################################################################### +# File operations + +sub PathConv($;$$$) +{ + my $path = shift(); + if (shift()) { $path =~ tr-\/-\\- } + else { $path =~ tr-\\-\/- } + return($path) if (!$gWinOS || $path =~ /^(?:\/\/|\\\\)/); + my $drive = shift(); + return(ucfirst(($path =~ /^[a-z]:/i ? "" : ($_[0] ? $_[0] : $gWorkdrive)) . $path)) + if !$drive; + $drive = $gWorkdrive if !($drive = shift()); + $path =~ s/^$drive//i; + return($path); +} + +sub ParseFiles($) +{ + my ($file, @files) = (" " . shift() . " ", ()); + push(@files, defined($1) ? $1 : (defined($2) ? $2 : ())) while ($file =~ /\s(?:"\s*"|"+(.+?)"+|((\\\s|\S)+))(?=\s)/g); + return(@files); +} + +sub GlobFiles($;$) +{ + return(@gFindresult) if (my $file = shift()) =~ /^__find__$/i; + return(map(/[\*\?]/ ? sort({lc($a) cmp lc($b)} grep(!/[\/\\]\.\.?$/, + glob(scalar(s/\*/\{\.\*\,\*\}/g, /\s/) ? "\"$_\"" : $_))) : $_, (shift() ? $file : ParseFiles($file)))); +} + +sub GetBasename($) +{ + return((File::Basename::fileparse(shift()))[0]); +} + +sub GetDirname($) +{ + (my $dir = shift()) =~ s/^>>?(?!>)//; + return((File::Basename::fileparse($dir))[1]); +} + +sub GetAbsDirname($;$$$) +{ + (my $dir = shift()) =~ s/^>>?(?!>)//; + $dir = "." if ($dir eq ""); + my $absdir = ""; + eval { local $gEvalerr = 1; $absdir = Cwd::abs_path($dir) }; + return(PathConv($absdir || File::Spec->rel2abs($dir, + $dir !~ /^$gWorkdrive/i && $dir =~ /^([a-z]:)/i ? "$1/" : ""), shift(), shift(), shift())); +} + +sub GetAbsFname($;$$$) +{ + my $file = shift(); + return($file) if ($file eq "" || $file =~ /STD(IN|OUT|ERR)$/); + my $append = ($file =~ s/^>>(?!>)// ? ">>" : ""); + return($append . PathConv(File::Spec->catpath("", GetAbsDirname(GetDirname($file)), GetBasename($file)), shift(), shift(), shift())); +} + +sub GetRelFname($;$$) +{ + my ($file, $base) = (shift(), shift()); + my $append = ($file =~ s/^>>(?!>)// ? ">>" : ""); + ($file = PathConv(File::Spec->abs2rel($file, GetAbsDirname(defined($base) && ($base ne "") ? $base : ".")), + shift(), 1, "[a-z]:")) =~ s/^[\/\\]+//; + return("$append$file"); +} + +sub GetWriteFname($) +{ + (my $file = shift()) =~ s/^>?/>/; + return($file); +} + +sub GetFreeDrive(;$) +{ + my $drives = Win32API::File::GetLogicalDrives(); + for my $drive ("F".."Z", "A".."E") { + return("$drive:") if !($drives & (2 ** (ord($drive) - ord("A")))); + } + return("") if shift(); + die("GetFreeDrive: No free drive available.\n"); +} + +sub SubstDrive($$) +{ + my ($drive, $path) = (uc(shift()), GetAbsDirname(shift())); + DPrint(16, "SubstDrive: `$drive' => `$path'\n"); + $gSubstdrv{$drive} = 1, return if !(Win32API::File::GetLogicalDrives() & (2 ** (ord($drive) - ord("A")))) && + Win32API::File::DefineDosDevice(0, $drive, $path); + die("Can't substitute `$drive' => `$path'\n"); +} + +sub UnsubstDrive($) +{ + return if (my $drive = uc(shift())) eq ""; + DPrint(16, "UnsubstDrive: `$drive'\n"); + delete($gSubstdrv{$drive}), return if Win32API::File::DefineDosDevice(DDD_REMOVE_DEFINITION, $drive, []) && + !(Win32API::File::GetLogicalDrives() & (2 ** (ord($drive) - ord("A")))); + warn("Can't remove substituted drive `$drive'\n"); +} - my $cmdarg = " " . imaker::HandleCmdArg($ENV{IMAKER_CMDARG}) . " "; - my $makecmd = "$ENV{IMAKER_MAKE} -R --no-print-directory" . - ($ENV{IMAKER_MAKESHELL} ? " SHELL=\"$ENV{IMAKER_MAKESHELL}\"" : ""); - my $cmdout = qx($makecmd -f $0 $cmdarg 2>&1); - my $targets = ($cmdout =~ />>>MAKECMDGOALS=(.*?)<</; + DPrint(16, defined($print) ? $print : ($file =~ /^>/ ? "Write" : "Read") . "File: `$file'\n"); + return(open($fhandle, $file)) if !$binmode; + return(open($fhandle, $file) and binmode($fhandle)); +} - die("Can't run `$ENV{IMAKER_MAKE}' properly:\n$cmdout") if !defined($targets); - map { $cmdarg =~ s/\s+\Q$_\E\s+/ / } split(/\s+/, $targets); +sub Test($) +{ + if (-d(my $file = shift())) { + DPrint(16, "TestDir: `" . GetAbsDirname($file) . "'\n"); + } elsif (-f($file)) { + DPrint(16, "TestFile: `" . GetAbsFname($file) . "'\n"); + } else { + DPrint(16, "Test: `$file'\n"); + die("File or directory `$file' doesn't exist.\n"); + } +} + +sub CutFile($$$$$) +{ + my ($msg, $src, $dest, $head, $len) = @_; + my ($buf, $srctmp) = (undef, "$src.tmp"); + + OpenFile(*INFILE, $src, 1, $msg) or + die("Can't read file `$src'.\n"), return; + + my $out = GetWriteFname($head ? $dest : $srctmp); + OpenFile(*OUTFILE, $out, 1) or die("Can't write to `$out'.\n"), return; + while ($len > 0) { + read(INFILE, $buf, $len < READBUFSIZE ? $len : READBUFSIZE); + print(OUTFILE $buf); + $len -= READBUFSIZE; + } + close(OUTFILE); + + $out = GetWriteFname($head ? $srctmp : $dest); + OpenFile(*OUTFILE, $out, 1) or die("Can't write to `$out'.\n"), return; + print(OUTFILE $buf) while read(INFILE, $buf, READBUFSIZE); + close(OUTFILE); + close(INFILE); + Move($srctmp, $src); +} + +sub Copy($$;$) +{ + my ($src, $dest, $dir) = @_; + $dir = defined($dir) && $dir; + my $file = !($dir || -d($src)); + $src = ($file ? GetAbsFname($src) : GetAbsDirname($src)); + $dest = ($file ? GetAbsFname(-d($dest) ? "$dest/" . GetBasename($src) : $dest) : + GetAbsDirname($dir ? $dest : "$dest/" . GetBasename($src))); + if ($file && ($dest =~ /^>>[^>]/)) { + OpenFile(*FILE, $dest, 1, "AppendFile: `$src' => `$dest'\n") + or die("Can't append to `$dest'.\n"), return; + File::Copy::copy($src, *FILE) and + close(FILE) and return; + } + elsif ($file) { + MakeDir(GetDirname($dest)); + DPrint(16, "CopyFile: `$src' => `$dest'\n"); + warn("CopyFile: Destination file `$dest' already exists\n") if -f($dest); + File::Copy::copy($src, $dest) and return; + } else { + DPrint(16, "CopyDir: `$src' => `$dest'\n"); + return if !RunSystemCmd(!$gWinOS ? "cp \"$src\"/* \"$dest\" -frv" : + 'xcopy "' . PathConv($src, 1) . '" "' . PathConv($dest, 1) . '" /e /h /i /q /y /z', 2); + } + die("Can't copy `$src' to `$dest'.\n"); +} + +sub CopyIby($$) +{ + my ($file, $dir) = (GetAbsFname(shift()), shift()); + OpenFile(*FILE, $file, 0) or die("Can't read file `$file'.\n"), return; + map { + Copy(defined($1) ? $1 : $2, "$dir/" . (defined($3) ? $3 : $4)) if $_ =~ FILESPECSTATEMENT; + } ; + close(FILE); +} + +sub DeleteFile($;$) +{ + return if !-f(my $file = GetAbsFname(shift())); + DPrint(16, "DeleteFile: `$file'\n"); + for my $sec (0, 1, 2) { + warn("Can't delete file `$file', retrying in $sec second(s)...\n"), sleep($sec) if $sec; + unlink($file); + return if !-f($file); + } + $file = "Can't delete file `$file'.\n"; + shift() ? warn($file) : die($file); +} + +sub FindFile($$$$) +{ + my ($dir, $inclpat, $exclpat, $opt) = @_; + $opt = "" if !defined($opt); + my @find = Find($opt !~ /f/ ? $dir : GetDirname($dir), $opt !~ /f/ ? $inclpat : GetBasename($dir), + $exclpat, $opt =~ /r/, 0, local $_); + push(@gFindresult, $opt !~ /f/ ? @find : map("|$_|$inclpat", @find)); +} + +sub HeadFile($$$) +{ + my ($src, $dest, $len) = (GetAbsFname(shift()), GetAbsFname(shift()), shift()); + $len = hex($len) if $len =~ /^0x/; + CutFile("HeadFile: Cut first $len bytes from `$src' => `$dest'\n", $src, $dest, 1, $len); +} - my $tmptarg = $targets = " $targets"; - my $hptarg = 0; - while ($tmptarg =~ /(\s+(help-\S+))/g) { - $hptarg = $1, $targets =~ s/\Q$hptarg\E(.*)$/ $1$hptarg/ if $2 ne "help-config"; +sub TailFile($$$) +{ + my ($src, $dest, $len) = (GetAbsFname(shift()), GetAbsFname(shift()), shift()); + $len = hex($len) if $len =~ /^0x/; + CutFile("TailFile: Cut last $len bytes from `$src' => `$dest'\n", $src, $dest, 0, (-s($src) ? -s($src) : 0) - $len); +} + +sub TypeFile($;$) +{ + my ($file, $str, $mode) = (GetAbsFname(shift()), "", shift() || ""); + OpenFile(*FILE, $file, $mode, "TypeFile: `$file'" . + ($gOutfilter && ($mode ne "b") ? ", filter: `/$gOutfilter/i'" : "") . "\n") or + die("Can't read file `$file'.\n"), return; + DPrint(8, STARTSTR . "\n"); + read(FILE, $str, -s($file)); + if ($mode eq "b") { + DPrint(1, Byte2Str(0, map(ord(), split(//, $str)))); + } else { + $str = Uni2Ascii($str) if $mode eq "u"; + DPrint(1, map("$_\n", grep(!$gOutfilter || /$gOutfilter/i, split(/\n/, $str)))); + $gOutfilter = ""; + } + DPrint(8, ENDSTR . "\n"); + close(FILE); +} + +sub ReadFile($$) +{ + my ($file, $warn) = (GetAbsFname(shift()), shift()); + OpenFile(*RFILE, $file, 0) or + ($warn ? (warn("Can't read file `$file'.\n"), return(())) : die("Can't read file `$file'.\n")); + my @file = map(chomp() ? $_ : $_, grep(!/^\s*$/, )); + close(RFILE); + return(@file); +} + +sub WriteFile($$$;$$) +{ + my ($file, $str, $mode, $opt) = (GetAbsFname(shift()), shift(), shift() || "", shift()); + OpenFile(*WFILE, GetWriteFname($file), $mode) or + die("Can't write to `$file'.\n"), return; + if ($mode eq "b") { + my @byte = Str2Byte($str); + DPrint(64, Byte2Str($file =~ s/^>>(?!>)// ? -s($file) : 0, @byte)); + print(WFILE map(chr(), @byte)); + } else { + $opt = "" if !defined($opt); + $str = Unquote($str) if ($opt !~ /q/); + $str =~ s/(?<=\S)\/\//\//g if ($opt =~ /c/); + DPrint(16, $str) if shift(); + $str = Ascii2Uni($str) if ($mode eq "u"); + print(WFILE $str); + } + close(WFILE); +} + +sub UnzipFile($$) +{ + my ($zipfile, $dir) = (GetAbsFname(shift()), GetAbsDirname(shift())); + DPrint(16, "UnzipFile: `$zipfile'"); + Archive::Zip::setErrorHandler(sub{}); + my ($error, $zip) = (0, Archive::Zip->new()); + if ($zip->read($zipfile) != AZ_OK) { + DPrint(16, " to directory `$dir'\n"); + die("Can't read zip archive `$zipfile'.\n"); + return; + } + my @files = map($_->fileName(), grep(!$_->isDirectory(), $zip->members())); + DPrint(16, ", " . @files . " files to directory `$dir'\n"); + foreach my $file (@files) { + DPrint(16, "ExtractFile: `$dir/$file'"); + eval { local $gEvalerr = 1; $error = ($zip->extractMember($file, "$dir/$file") != AZ_OK) }; + DPrint(16, $error ? " Failed\n" : "\n"); + die("Can't extract file `$file' to directory `$dir'.\n") if $error; + $error = 0; + } +} + +sub Zip($$$$@) +{ + my ($zipfile, $dir, $opt, $prefix) = (GetAbsFname(shift()), shift(), shift(), shift()); + + $opt = (defined($opt) ? ", options: `$opt'" : ""); + $prefix = GetAbsDirname($prefix) if $prefix ne ""; + my %files = (); + foreach my $file (@_) { + my $zname = ""; + ($file, $zname) = ($1, $2) if ($file =~ /^\|(.*)\|(.*)$/); + next if !($file = (!$dir ? (-f($file) ? GetAbsFname($file) : "") : (-d($file) ? GetAbsDirname($file) : ""))); + ($zname = ($zname eq "" ? $file : (!$dir ? + GetAbsFname($zname) : GetAbsDirname($zname)))) =~ s/^(?:$gEpocroot|[a-z]:)?\/+//i; + if ($opt !~ /j/) { + $zname =~ s/^.*?\/+/$prefix\// if ($prefix ne ""); + } else { + $zname = ($dir ? "" : GetBasename($file)) if ($prefix eq "") || !s/^$prefix//; } - $hptarg = $1, $targets =~ s/\Q$hptarg\E(.*)$/ $1$hptarg/ while $tmptarg =~ /(\s+print-\S+)/g; - $targets =~ s/^\s+|\s+(?=\s)|\s$//g; + $files{lc($zname)} = [$file, $zname]; + } + + DPrint(16, ($dir ? "ZipDir: `$zipfile'$opt, " . keys(%files) . " directories" : + "ZipFile: `$zipfile'$opt, " . keys(%files) . " files") . ($prefix ? ", prefix: $prefix\n" : "\n")); + + Archive::Zip::setErrorHandler(sub{}); + my ($error, $zip) = (0, Archive::Zip->new()); + $zip->read($zipfile) if (my $ziptmp = ($zipfile =~ s/^>>(?!>)// ? "$zipfile.tmp" : "")); + $zip->zipfileComment("iMaker-generated zip archive `$zipfile'$opt."); - my $mainmk = "-f $ENV{IMAKER_DIR}/imaker.mk"; - $makecmd .= " -I " . imaker::GetAbsDirname($ENV{CONFIGROOT}, 0, 1) . " $mainmk"; + foreach my $file (sort({lc($$a[0]) cmp lc($$b[0])} values(%files))) { + DPrint(16, "Add" . ($dir ? "Dir" : "File") . ": `$$file[0]' => `$$file[1]'") if ($opt !~ /q/); + eval { + my $warn = 0; + local $gEvalerr = 1; local $SIG{__WARN__} = sub{ $warn = 1 }; + $error = ($dir ? $zip->addTree($$file[0], $$file[1]) != AZ_OK : + !$zip->addFile($$file[0], $$file[1])) || $warn; + }; + DPrint(16, $error ? " Failed\n" : "\n") if ($opt !~ /q/); + warn("Can't add " . ($dir ? "directory tree" : "file") . "`$$file[0]' to zip archive `$zipfile'.\n") if $error; + $error = 0; + } + ($zip->writeToFileNamed($ziptmp ? $ziptmp : $zipfile) == AZ_OK) or + die("Can't create zip archive `$zipfile'.\n"); + Move($ziptmp, $zipfile) if $ziptmp; +} + +sub Move($$) +{ + my ($src, $dest) = @_; + my $dir = -d($src); + $src = ($dir ? GetAbsDirname($src) : GetAbsFname($src)); + MakeDir(GetDirname($dest)); + $dest = ($dir ? GetAbsDirname($dest) : GetAbsFname($dest)); + DPrint(16, "Move" . ($dir ? "Dir" : "File") . ": `$src' => `$dest'\n"); + File::Copy::move($src, $dest) or + die("Can't move `$src' to `$dest'.\n"); +} + +sub Touch($@) +{ + my $time = (shift() =~ /^(\d\d\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/ ? + Time::Local::timelocal($6, $5, $4, $3, $2 - 1, $1 - 1900) : time); + if (@_ != 1) { + DPrint(16, "Touch: " . scalar(@_) . " files/dirs, " . + POSIX::strftime("%Y-%m-%d %H:%M:%S", localtime($time)) . "\n"); + utime($time, $time, @_) == @_ or + die("Can't touch all the " . scalar(@_) . " files/dirs.\n"); + return; + } + my $file = shift(); + my $dir = -d($file); + $file = ($dir ? GetAbsDirname($file) : GetAbsFname($file)); + DPrint(16, "Touch" . ($dir ? "Dir" : "File") . ": `$file', " . + POSIX::strftime("%Y-%m-%d %H:%M:%S", localtime($time)) . "\n"); + utime($time, $time, $file) == 1 or + die("Can't touch " . ($dir ? "directory" : "file") . " `$file'.\n"); +} + +sub SetLogfile($) +{ + return if !(my $file = GetAbsFname(shift())); + my $append = (($file =~ s/^>>(?!>)//) || exists($gLogfiles{$file}) ? ">>" : ""); + CloseLog(); + OpenFile(*LOG, GetWriteFname($file = "$append$file"), 0) or + warn("Can't log to file `$file'.\n"), return; + $gLogfiles{$gLogfiles{__prev__} = $gLogfile = $file} = 1; +} + + +############################################################################### +# + +sub RunSystemCmd($;$$$) +{ + return if ($gICmd !~ $gFiltercmd); + my ($cmd, $keepgoing, $null, $file) = @_; + DPrint(1, "$cmd\n"), return if $gPrintcmd; + local $gError = 0 if ($keepgoing = (defined($keepgoing) && ($keepgoing =~ /^[123]$/) ? $keepgoing : 0)); + local $gKeepgoing = Max($gKeepgoing, $keepgoing) if $keepgoing; + $file = (defined($file) ? GetAbsFname($file) : ""); + @gCmdoutbuf = (); + DPrint(4, local $_ = "RunSystemCmd(" . GetAbsDirname(".") . "): `$cmd'" . + ($keepgoing ? ", keep going" . ($keepgoing > 1 ? "($keepgoing)" : "") : "") . + ($file ? ", redirect to `$file'" : "") . ($null ? ", redirect stdout to null" : "") . + ($gOutfilter ? ", filter: `/$gOutfilter/i'" : "") . "\n"); + OpenFile(*CMDFILE, GetWriteFname($file), 0) or + (die("Can't write to `$file'.\n"), $file = "") if $file; + print(CMDFILE $_) if $file; + my $dur = time(); + open(CMD, "$cmd 2>&1 |"); + DPrint(8, STARTSTR . "\n"); + while ($_ = ) { + chomp(); + push(@gCmdoutbuf, $_); + next if ($gOutfilter && !/$gOutfilter/i); + DPrint(8, "$_\n") if !$null; + print(CMDFILE "$_\n") if $file; + } + close(CMD); + my $error = ($? >> 8); + close(CMDFILE) if $file; + push(@gStepDur, $dur = time() - $dur); + $gOutfilter = ""; + print(map("$_\n", @gCmdoutbuf)) if ($error && !$gKeepgoing && !$null && $gVerbose && !($gVerbose & 8)); + $dur = Sec2Min($dur); + DPrint(8, substr(ENDSTR, 0, -16) . $dur . substr(ENDSTR, length($dur) - 16) . "\n"); + die("Command `$cmd' failed ($error) in `" . GetAbsDirname(".") . "'.\n") if $error; + return($error); +} + + +############################################################################### +# + +sub ParseSystemCmd($$$$$) +{ + return if SkipICmd(); + my ($title, $inclre, $exclre, $file, $lines) = @_; + ($inclre, $exclre) = (eval("qr$inclre"), $exclre ne "" ? eval("qr$exclre") : qr/^$/); + $lines = ($lines ? $lines - 1 : 0); - foreach my $target ($hptarg || $targets eq "" ? $targets : split(/\s/, $targets)) { - ($cmdarg, $target) = imaker::Menu($makecmd, $mainmk, $cmdarg) if $target eq "menu"; - system($ENV{IMAKER_MAKECMD} = "$makecmd TIMESTAMP=" . imaker::GetTimestamp() . " $cmdarg $mainmk $target") - if $target ne "menu"; - $error = ($? >> 8) if ($? >> 8); + my @parse = (); + for (my $i = 0; $i < @gCmdoutbuf; $i++) { + next if ($gCmdoutbuf[$i] !~ $inclre); + push(@parse, join(" | ", @gCmdoutbuf[$i .. $i + $lines])) if ($gCmdoutbuf[$i] !~ $exclre); + $i += $lines; + } + return if !@parse; + if (!$file) { + DPrint(1, "$title\n", map(sprintf("%" . length(@parse) . "s", $_) . ") $parse[$_ - 1]\n", 1 .. @parse)); + } else { + WriteFile($title, join("\n", @parse), "", "q"); + } +} + + +############################################################################### +# + +sub GenExclfile($$$$$) +{ + return if SkipICmd(); + + my ($exclfile, $base, $prefix, $exclfiles, @exclfiles) = (shift(), GetAbsDirname(shift()), shift(), "", ()); + + if (!-f($exclfile)) { + WriteFile($exclfile, "", ""); + } else { + OpenFile(*FILE, $exclfile, 1) or die("Can't read file `$exclfile'.\n"), return; + read(FILE, $exclfiles, -s($exclfile)); + close(FILE); + @exclfiles = split(/\n/, Uni2Ascii($exclfiles)); + } + + my $findfiles = 0; + my @addfiles = map($_ ne "**" ? $_ : "*", grep(!(($_ eq "*") && ++$findfiles), + map(Trim(Unquote(Trim($_))), grep(!/^\s*(?:#.*)?$/, split(/(?>([^>].*)$/ && -f($1) ? "" : "// Generated `$ibyfile'") . + "\n\n/* Custom override configuration\n" . join("\n", @ibyconf) . "\n*/\n$ibystr" . + ($oride ? "OVERRIDE_END\n" : ""), "", "q"); +} + +sub GenObyfile($$$$@) +{ + return if SkipICmd(); + + my ($ibyfile, $srcdir, $subdir, $finddir) = (GetAbsFname(shift()), shift(), shift(), shift()); + my ($header, $footer, $body, %files) = ("", "", "", ()); + + foreach my $dir (split(/\s+/, $srcdir)) { + $dir = GetAbsDirname($dir); + my ($found, $total, $lines) = (0, 0, ""); + my @param = @_; + while (@param) { + my ($filepat, $format, @lines) = (shift(@param), shift(@param), ()); + $header = $format, next if $filepat =~ /^__header__$/i; + $footer = $format, next if $filepat =~ /^__footer__$/i; + foreach my $src (Find($dir, $filepat, "", $subdir, $finddir, $total)) { + next if $files{$src}; + $files{$src} = 1; + (my $line = $format) =~ s/%1/$src/g; + $line =~ s/%2/GetRelFname($src, $dir, 1)/ge; + $line =~ s/%3/GetAbsFname($src)/ge; + if ($line =~ /%4/) { + my $attrib = ""; + if ($gWinOS) { + Win32::File::GetAttributes($src, $attrib); + $attrib = (($attrib & WIN32_FILE_HIDDEN) ? "attrib=H" : ""); + } + $line =~ s/%4/$attrib/ge; + } + push(@lines, Trim($line)); + } + $found += @lines; + $lines .= "//\n// Format: `$format', " . @lines . ($finddir ? " empty directories" : " files") . + ": `$filepat'\n" . (@lines ? "//\n" . join("\n", @lines) . "\n" : ""); + } + $body .= "\n// Collected entries $found/$total from directory `$dir'" . + ($subdir ? " and subdirectories" : "") . "\n$lines"; } - #========================================================================== + my $append = ($ibyfile =~ s/^>>(?!>)// && -f($ibyfile) && ">>" || ""); + (my $fname = "__" . uc(GetBasename($ibyfile)) . "__") =~ s/\W/_/g; + my @previby = (); + + if ($append) { + OpenFile(*FILE, $ibyfile, 0) or die("Can't read file `$ibyfile'.\n"), return; + @previby = ; + close(FILE); + $previby[0] =~ s/(, collected )(\d+)( entries)$/$1.($2 + keys(%files)).$3/e; + $previby[@previby - 1] = ""; + } + WriteFile($ibyfile, join("", @previby) . ($append ? "// Appended" : "// Generated") . + " `$append$ibyfile', collected " . keys(%files) . " entries\n" . + ($append ? "" : "\n#ifndef $fname\n#define $fname\n") . + ($header ? Unquote("\\n$header\\n") : "") . $body . ($footer ? Unquote("\\n$footer\\n") : "") . + "\n#endif // $fname\n", "", "q"); +} + +sub GenWidgetConf($$$$) +{ + return if SkipICmd(); + my ($wgzini, $ini, $dir) = (shift(), GetAbsFname(shift()), GetAbsDirname(shift())); + my @ini = ($ini eq "" ? () : ReadFile($ini, 0)); + my $files = ($dir eq "" ? "" : join("\n", Find($dir, "*", '/\/(?:' . join("|", + map(GetBasename($_), ($ini, map(!/^\s*[#[]/ && /^\s*(?:"(.+?)"|(\S+))/ && + -e(local $_ = (defined($1) ? $1 : $2)) ? $_ : (), @ini)))) . ')$/i', 0, 0, local $_))); + + WriteFile($wgzini, Unquote(shift()) . + (@ini ? "# Copied lines from `$ini':\n" . join("\n", @ini) : "") . "\n" . + ($files ? (@ini ? "\n" : "") . "# Collected files from `$dir':\n$files\n" : ""), "", "q"); +} + + +############################################################################### +# + +sub GenMakefile($$$$$) +{ + return if SkipICmd(); + my ($hdrfile, $mkfile, $filter, $prepros, $assignop) = + (GetAbsFname(shift()), GetAbsFname(shift()), shift(), shift(), shift()); + ChangeDir(GetDirname($hdrfile)); + RunSystemCmd("$prepros " . GetBasename($hdrfile)); + my $maxdef = Max(map(/^\s*\#define\s+($filter)/ && length($1), @gCmdoutbuf)); + WriteFile($mkfile, join('\n', + map(/^\s*\#define\s+($filter)\s*(.*?)\s*$/ ? sprintf("%-${maxdef}s $assignop %s", $1, $2 eq "" ? 1 : $2) : (), sort(@gCmdoutbuf))) . '\n', ""); +} + + +############################################################################### +# + +sub AddImageHeader($$$$$) +{ + return if SkipICmd(); + my ($file, $hdrfile, $hdrstr, $hdrsize, $align) = + (GetAbsFname(shift()), GetAbsFname(shift()), shift(), shift(), shift()); + + $hdrstr =~ s/\/\*.*?\*\///g; + $hdrstr =~ s/,\s*$//; + WriteFile($hdrfile, $hdrstr, "b"); + die("Invalid image header size: " . sprintf("0x%X", -s($hdrfile)) . " (!=$hdrsize).\n"), return + if -s($hdrfile) ne hex($hdrsize); + + $align = Max(hex($align), hex($hdrsize)) - hex($hdrsize); + WriteFile(">>$hdrfile", ("0," x ($align - 1)) . "0", "b") if $align; + Copy($file, ">>$hdrfile") if $file ne ""; +} + + +############################################################################### +# - my ($opt_cmdfile, $opt_incdir, $opt_logfile, $opt_printcmd, $opt_step, $opt_verbose, $opt_workdir) = - ( "", "", "", 0, "", 1, "."); - Getopt::Long::GetOptions( - "cmdfile=s" => \$opt_cmdfile, - "incdir=s" => \$opt_incdir, - "logfile=s" => \$opt_logfile, - "printcmd" => \$opt_printcmd, - "step=s" => \$opt_step, - "verbose=s" => \$opt_verbose, - "workdir=s" => \$opt_workdir, - "<>" => sub { $error .= ($error ? ", `@_'" : "Unknown imaker.pl option: `@_'") }); +sub Sleep($) +{ + return if SkipICmd(); + sleep(shift()); +} + + +############################################################################### +# + +sub FindSOSFiles($$$$) +{ + return if SkipICmd(); + + my ($dirs, $imgoby, $pluglog, $opt) = @_; + my ($file, %files) = ("", ()); + local $_; + + foreach my $dir (GlobFiles($dirs)) { + my ($featvar, @pluglog) = ("", Find($dir = GetAbsDirname($dir), $pluglog, "", 1, 0, $_)); + + foreach $file (@pluglog) { + OpenFile(*FILE, $file, 0) or warn("Can't read file `$file'.\n"), last; + while () { + last if !/^.+?\.pm: Initializing; /; + $featvar = $1, last if / feature variant = `(.+)'$/; + } + close(FILE); + last if ($featvar ne ""); + } - if ($opt_incdir) { - my $bsf = ($opt_incdir =~ s/:bsf$//); - print(map("$_\n", imaker::GetFeatvarIncdir($opt_incdir, $bsf))); - exit; + foreach $file (Find($dir, $imgoby, "", 1, 0, $_)) { + OpenFile(*FILE, $file, 0) or warn("Can't read file `$file'.\n"), last; + while () { + next if ($_ !~ FILESPECSTATEMENT) && ($_ !~ BOOTBINARYSTATEMENT); + $file = GetAbsFname(defined($1) ? $1 : $2); + $files{lc($file)} = $file if !exists($files{lc($file)}); + next if ($file !~ s/\.[0-9a-f]{32}\./\./i); + $file .= (-f("$file.$featvar.vmap") ? ".$featvar.vmap" : ".vmap"); + $files{lc($file)} = $file if !exists($files{lc($file)}); + } + close(FILE); + } + + my ($incfile, $spifile, $plugfile, $patchfile) = (0, 0, 0, 0); + foreach $file (@pluglog) { + OpenFile(*FILE, $file, 0) or warn("Can't read file `$file'.\n"), last; + while () { + $incfile = 1, next if /^Finding include hierarchy from /; + $incfile = 0, next if ($incfile && /^Found \d+ different include files$/); + $spifile = 1, next if /^Finding SPI input files from /; + $spifile = 0, next if ($spifile && /^Found \d+ SPI input files$/); + $plugfile = 1, next if /^Reading (ROM|ROFS1|UDEB|UREL) files from /; + $plugfile = 0, next if ($plugfile && /^Found \d+ entries$/); + $patchfile = 1, next if /^Finding ROM-patched components$/; + $patchfile = 0, next if ($patchfile && /^Found \d+ ROM-patched components$/); + $files{lc($file)} = $file, next + if (($incfile || $spifile || $plugfile) && /`(.+)'$/ && !exists($files{lc($file = GetAbsFname($1))})); + next if (!$patchfile || !/^`(.+)'$/); + $file = GetAbsFname($1) . ".map"; + $files{lc($file)} = $file, next if -f($file); + $file =~ s/(\..*?\.map)$/\.\*$1/; + foreach (glob($file =~ /\s/ ? "\"$file\"" : $file)) { + ($file = lc()) =~ s/\.map$//; + $files{lc()} = $_, last if exists($files{$file}); + } + } + close(FILE); + } + + $dir .= "/" if $dir !~ /\/$/; + foreach $file (keys(%files)) { + delete($files{$file}) if ($file =~ /^$dir/i); + } } - $opt_verbose = imaker::SetVerbose($opt_verbose); + @gFindresult = () if (!defined($opt) || $opt !~ /a/); + push(@gFindresult, values(%files)); +} + + +############################################################################### +# + +sub CheckTool(@) +{ + return if SkipICmd(); + my ($maxtlen, $maxvlen, @tools) = (4, 9, ()); + while (@_) { + my ($tool, $vquery, $getver, $version, $md5sum) = (shift(), shift(), shift(), " -", " ?"); + if (length($vquery) > 1) { + RunSystemCmd($vquery, 3, 1); + $version = (join("\n", @gCmdoutbuf) =~ eval($getver =~ /^\// ? "qr$getver" : "qr/$getver/ims") ? + (defined($1) && defined($2) && "`$1 $2'" || defined($1) && "`$1'" || " ?") : " ?"); + } + OpenFile(*FILE, $tool, 1) and $md5sum = "`" . md5_hex() . "'"; + close(FILE); + $maxtlen = Max($maxtlen, length($tool)); + $maxvlen = Max($maxvlen, length($version)); + push(@tools, "`$tool'", $version, $md5sum); + } + $maxtlen += 2; + @_ = (" Tool", " Version", " MD5 Checksum", "-" x $maxtlen, "-" x $maxvlen, "-" x 34, @tools); + DPrint(1, sprintf("%-${maxtlen}s %-${maxvlen}s ", shift(), shift()) . shift() . "\n") while(@_); +} + + +############################################################################### +# + +sub OpCacheInstall($$$) +{ + return if SkipICmd(); + my ($ini, $conf, $tmpdir) = @_; + my %opt = (-e => "", -i => "", -m => "", -o => "", -u => ""); - imaker::DPrint(2, "=" x 79 . "\nTIME: " . localtime() . ", USER: " . getlogin() . - ", HOST: " . ($ENV{HOSTNAME} || $ENV{COMPUTERNAME} || "?") . "\n$^X (v$perlver-$^O)\n"); + foreach $conf ("opcache_config=$conf", ($ini ne "" ? grep(!/^\s*#/, ReadFile($ini, 0)) : ())) { + (local $_, my $error, my %tmpopt) = ($conf, 0, %opt); + if (!($error = !(s/^\s*opcache_config\s*[=\s]//i || s/^\s*opcache_content\s*[=\s]/-i /i))) { + my @opt = ParseCmdWords($_); + while (@opt) { + last if ($error = ((($_ = shift(@opt)) !~ /^-[eimou]$/i) || + !defined($tmpopt{$_} = shift(@opt)))); + $tmpopt{$_} =~ s/EPOCROOT/$gEpocroot/g; + } + } + die("OpCacheInstall: Invalid configuration entry: `$conf'\n"), next if $error; + %opt = %tmpopt; + } + if (-d($opt{-i})) { + $opt{-i} = GetAbsDirname($opt{-i}); + } elsif (-f($opt{-i})) { + DeleteDir($tmpdir); + MakeDir($tmpdir); + RunSystemCmd("$gTool{unzip} x -y \"" . GetAbsFname($opt{-i}) . "\"" . + " -o\"" . ($tmpdir = GetAbsDirname($tmpdir)) . "\"", 0, 1); + $opt{-i} = $tmpdir; + } + RunSystemCmd("$gTool{opcache} -u \"$opt{-u}\" -e \"$opt{-e}\" -m \"" . + GetAbsFname($opt{-m}) . "\" -i \"$opt{-i}\" -o \"" . GetAbsDirname($opt{-o}) . "\""); +} + + +############################################################################### +# + +sub SisInstall($$$$$$$$) +{ + return if SkipICmd(); + + my ($ini, $intini, $conf, $hda, $hdata, $idata, $outdir, $log) = + (GetAbsFname(shift()), GetAbsFname(shift()), shift(), GetAbsFname(shift()), + shift(), shift(), GetAbsDirname(shift()), shift()); + my %gopt = (-d => "C", -k => "5.4", -w => "info", '--ignore-err' => 0); - imaker::SetLogfile($opt_logfile); - die("$error.\n") if $error; + my %haldata = (); + map { $haldata{uc($1)} = $2 if /^\s*(\S+)\s+(\S+)\s*$/ } split(/(?>$log"); + my $errmsg = join(" | ", grep(s/^ERR\s*:\s*//, @gCmdoutbuf)); + + $_ = join(", ", map(/^INFO:\s+Installing file:\s+\w:\\sys\\bin\\(.+?.exe)\s*$/io && + ($_ = $1) && (qx($gTool{elf2e32} --dump=h --e32input "$outdir/sys/bin/$_") =~ + /^Uids:\s+.+?\s+([0-9a-f]+)\s+\(/imo) ? "$_: " . uc($1) : (), @gCmdoutbuf)); + DPrint(16, "SisInstall: `" . GetBasename($opt{-s}) . "', exe UIDs: $_\n") + if ($_ && (!($error ||= $errmsg) || $opt{'--ignore-err'})); + + warn("Installation of SIS file `$opt{-s}' failed" . ($errmsg ? ": `$errmsg'.\n" : ".\n")) + if ($gErrwarn = $error); + next if (!$error || $opt{'--ignore-err'}); + $clean = 1; + warn("Removing installation of SIS file `$opt{-s}'.\n"); + RunSystemCmd("$icmd -x $puid", 3, 1, ">>$log"); + } } - die("$error.\n") if $error; + return if !$clean; + my $i = 0; + foreach (Find($outdir, "*", "", 1, 1, $_)) { + if (($i <= $#dir) && ($_ eq $dir[$i])) { $i++ } + else { DeleteDir($_) } + } +} + + +############################################################################### +# + +sub GetIPar(;$) +{ + my $par = shift(@gIcmd); + $par = ((my $empty = !defined($par)) ? "" : PEval($par)); + $gParamcnt = 0 if shift(); + DPrint(32, "iPar: $gParamcnt. `$par'\n") if $gParamcnt && ($gICmd =~ $gFiltercmd); + $gParamcnt++; + return($empty ? undef : $par); +} - imaker::SetWorkdir($opt_workdir); - imaker::ReadICmdFile($opt_cmdfile); +sub PEval($) +{ + local $_ = shift(); + while (/\@PEVAL{.*}LAVEP\@/) { + my $start = rindex($_, '@PEVAL{', my $end = index($_, '}LAVEP@') + 7); + my ($expr, $eval, $evalerr) = (substr($_, $start + 7, $end - $start - 14), undef, ""); + eval { + local $_; + local $gEvalerr = (SkipICmd() ? 1 : 2); + $eval = eval($expr); + ($evalerr = $@) =~ s/^(.+?) at .*/$1/s; + }; +# DPrint(64, "PEval: Evaluate `$expr' = `" . (defined($eval) ? $eval : "") . "'\n"); + if (!defined($eval)) { + $eval = ""; + warn("PEval: Evaluation of `$expr' failed: $evalerr.\n") if !SkipICmd(); + } + substr($_, $start, $end - $start) = $eval; + } + return($_); +} + +sub PeekICmd($) +{ + return(defined($gIcmd[0]) && $gIcmd[0] =~ /^$_[0]$/i); +} + +sub SkipICmd() +{ + return($gPrintcmd || defined($gICmd) && ($gICmd !~ $gFiltercmd)); +} - my (@step, @stepdur) = (split(/-+/, lc($opt_step)), ()); - my ($durstr, $maxslen, $maxdlen) = ("", 6, 8); +sub GetICmd() +{ + $gICmd = GetIPar(1); + DPrint(32, "iCmd: " . ++$gCmdcnt . ". `$gICmd'\n") if defined($gICmd) && ($gICmd ne "") && ($gICmd =~ $gFiltercmd); +} + +sub EndICmd() +{ + GetICmd(), return(1) if !defined($gIcmd[0]) || PeekICmd("end"); + return(0); +} + + +############################################################################### +# + +sub SplitStep($) +{ + (my $step = shift()) =~ s/(?>?([^>].*)$/ ? $1 : $gLogfile, "f") : (), + SplitStep($gStepIcmd{"REPORT_$gStep"})) if exists($gStepIcmd{"REPORT_$gStep"}); + + foreach my $step ("INIT_$gStep", "CLEAN_$gStep", "BUILD_$gStep") { + next if (!exists($gStepIcmd{$step}) || $gStepIcmd{$step} =~ /^\s*$/); + DPrint(64, "$step = `$gStepIcmd{$step}'\n"); + @gIcmd = SplitStep($gStepIcmd{$step}); + my ($file, $iferror, @iffi) = ("", 0, ()); - foreach my $stepnum (0 .. $#step) { - $step[$stepnum] =~ /^(\w+):?([cbk\d]+)?$/; - my $step = uc($1); - $_ = (defined($2) ? $2 : ""); - my @dur = imaker::MakeStep($step, /c/, /b/, /k/, /(\d+)/ ? $1 : $opt_verbose, $opt_printcmd); - imaker::SetVerbose($opt_verbose); - my ($cmddur, $stepdur) = (0, pop(@dur)); - $durstr = imaker::Sec2Min($stepdur); - if (@dur) { - $durstr .= " ("; - foreach my $dur (@dur) { - $cmddur += $dur; - $durstr .= imaker::Sec2Min($dur) . " + "; + while (GetICmd(), defined($gICmd)) { + next if (local $_ = lc($gICmd)) eq ""; + if (/^if$/) { + push(@iffi, (my $if = GetIPar()), $gFiltercmd); + $gFiltercmd = qr/^X$/ if !$if; + } + elsif (/^else$/) { + $gFiltercmd = ($iffi[$#iffi - 1] ? qr/^X$/ : $iffi[$#iffi]); + } + elsif (/^fi$/) { + $gFiltercmd = pop(@iffi); + pop(@iffi); + } + elsif (/^(error|warning)$/) { + my ($errwarn, $msg) = (GetIPar(), GetIPar() . "\n"); + next if SkipICmd(); + die($msg) if $errwarn && /e/; + warn($msg) if $errwarn && /w/; + } + elsif (/^echo(\d+)?(-q)?$/) { + Echo((defined($1) && ($1 < 128) ? $1 : 1), GetIPar(), defined($2)); + } + elsif (/^filter$/) { + $gOutfilter = GetIPar(); + } + elsif (/^cmd(tee)?(-(k[0123]?|n)+)?$/) { + RunSystemCmd(GetIPar(), (/k(\d)/ ? int($1) : (/k/ ? 1 : 0)), /n/, /tee/ ? GetIPar() : ""); + } + elsif (/^parse(f)?(?:-(\d+))?$/) { + ParseSystemCmd(GetIPar(), GetIPar(), GetIPar(), $1, $2); + } + elsif (/^(cd|copy(dir|iby)?|del(dir)?|find(dir)?(-[afr]+)?|headb|logfile|mkcd|mkdir|move|tailb|test|touch|type[bu]?|unzip|workdir|write[bu]?(-[cq]+)?|zip(dir)?(-[jq]+)?)$/) { + my @files = GlobFiles(GetIPar()); + my $par1 = GetIPar() if /^(?:copy|find|head|move|tail|touch|(un)?zip|write)/; + my $par2 = GetIPar() if /^(?:find|head|tail|zip)/; + next if SkipICmd(); + @gFindresult = () if /find(?:dir)?(-[afr]+)?/ && (!defined($1) || ($1 !~ /a/)); + Touch($par1, @files), next if /touch/; + foreach $file (@files) { + ChangeDir($file) if /^cd/; + DeleteDir($file) if /deldir/; + FindDir($file, $par1, $par2, $1) if /finddir(-[ar]+)?/; + MakeDir($file) if /mkdir/; + MakeChangeDir($file) if /mkcd/; + SetWorkdir($file) if /workdir/; + Zip($file, 1, $1, $par2, GlobFiles($par1)) if /zipdir(-[jq]+)?/; + DeleteFile($file) if /del/; + FindFile($file, $par1, $par2, $1) if /find(-[afr]+)?$/; + HeadFile($file, $par1, $par2) if /headb/; + SetLogfile($file) if /logfile/; + TailFile($file, $par1, $par2) if /tailb/; + TypeFile($file, $1) if /type(b|u)?/; + UnzipFile($file, $par1) if /unzip/; + WriteFile($file, $par1, $1, $2) if /write(b|u)?(-[cq]+)?/; + Zip($file, 0, $1, $par2, GlobFiles($par1)) if /^zip(-[jq]+)?$/; + Copy($file, $par1, $1) if /copy(dir)?$/; + CopyIby($file, $par1) if /copyiby/; + Move($file, $par1) if /move/; + Test($file) if /test/; + } + } + elsif (/^filtercmd$/) { + $gFiltercmd = GetIPar(); + $gFiltercmd = ($gFiltercmd eq "" ? qr/\S/ : qr/$gFiltercmd/i); + } + elsif (/^genexclst$/) { + GenExclfile(GetIPar(), GetIPar(), GetIPar(), GetIPar(), GetIPar()); + } + elsif (/^geniby(-[dr]+)?$/) { + my ($opt, $iby, $dir, @par) = ($1 || "", GetIPar(), GetIPar(), ()); + push(@par, GetIPar(), GetIPar()) while !EndICmd(); + GenObyfile($iby, $dir, $opt =~ /r/, $opt =~ /d/ ? 2 : 0, @par); + } + elsif (/^genorideiby$/) { + GenIbyfile(GetIPar(), GetIPar(), GetIPar()); + } + elsif (/^genmk$/) { + GenMakefile(GetIPar(), GetIPar(), GetIPar(), GetIPar(), GetIPar()); + } + elsif (/^genwgzcfg$/) { + GenWidgetConf(GetIPar(), GetIPar(), GetIPar(), GetIPar()); + } + elsif (/^iferror$/) { + $iferror++; + $gError = 0, next if $gError; + while (defined($gIcmd[0])) { + GetICmd(), last if PeekICmd("endif") && !--$iferror; + $iferror++ if shift(@gIcmd) =~ /^iferror$/i; + } + } + elsif (/^endif$/ && $iferror--) { + } + elsif (/^imghdr$/) { + AddImageHeader(GetIPar(), GetIPar(), GetIPar(), GetIPar(), GetIPar()); + } + elsif (/^pause$/) { + DPrint(0, "Press Enter to continue...\n"); + getc(); + } + elsif (/^sleep$/) { + Sleep(GetIPar()); + } + elsif (/^sosfind(-a)?$/) { + my $opt = $1; + FindSOSFiles(GetIPar(), GetIPar(), GetIPar(), $opt); + } + elsif (/^tool-(\w+)$/) { + $gTool{$1} = GetIPar(); +# DPrint(2, "SetTool: $1: `$gTool{$1}'\n"); + } + elsif (/^toolchk$/) { + my @tools = (); + push(@tools, GetIPar(), GetIPar(), GetIPar()) while !EndICmd(); + CheckTool(@tools); + } + elsif (/^opcache$/) { + OpCacheInstall(GetIPar(), GetIPar(), GetIPar()); + } + elsif (/^sisinst$/) { + SisInstall(GetIPar(), GetIPar(), GetIPar(), GetIPar(), + GetIPar(), GetIPar(), GetIPar(), GetIPar()); + } + elsif (!$gImakerext || !RunIExtCmd($_)) { + die("Unknown iMaker command `$gICmd'.\n"); } - $durstr .= imaker::Sec2Min($stepdur - $cmddur) . ")"; + } + } + DPrint(2, "EXIT: `$gStep', duration: " . Sec2Min($dur = time() - $dur) . "\n"); + push(@gStepDur, $dur); +} + + +############################################################################### +# + +sub GetConfmkList(;$) +{ + if (!%gConfmkList) { + my ($dir, $incl, $excl, $depth) = split(/,/, $ENV{IMAKER_MKCONF}); + $dir = GetAbsDirname($dir, 0, 1, $gEpocdrive); + ($incl, $excl) = (qr/$incl/, qr/$excl/); + local $_; + DPrint(16, "FindFile: GetConfmkList: `$ENV{IMAKER_MKCONF}'"); + find(sub { $gConfmkList{$1} = $File::Find::name + if (/$incl/ && !/$excl/ && (($File::Find::name =~ tr/\///) > (($dir =~ tr/\///) + $depth))); + }, $dir); + DPrint(16, ", found " . keys(%gConfmkList) . " files\n"); + $gConfmkList{""} = "" if !%gConfmkList; + } + return(sort({lc($a) cmp lc($b)} grep($_ ne "", values(%gConfmkList)))) if shift(); +} + +sub GetFeatvarIncdir($) +{ + open(FILE, "$gEpocroot/epoc32/tools/variant/" . shift() . ".var") or + return("Invalid SBV feature variant"); + my @featdata = ; + close(FILE); + my @incdir = ("@featdata" =~ /^\s*EXTENDS\s+(.+?)\s*$/m ? GetFeatvarIncdir($1) : ()); + @incdir = () if ("@incdir" =~ /^Invalid/); + foreach (@featdata) { + next if !/^\s*ROM_INCLUDE\s+(\S+)\s+(.+?)\s*$/; + if ($1 eq "set") { @incdir = ($2) } + elsif ($1 eq "prepend") { unshift(@incdir, $2) } + elsif ($1 eq "append") { push(@incdir, $2) } + } + return(map("$_/" =~ /^$gEpocroot\// ? $_ : $gEpocroot . PathConv($_, 0, 1, $gEpocdrive), + map(PathConv($_, 0, 0, $gEpocdrive), @incdir))); +} + + +############################################################################### +# + +sub SetVerbose($;$) +{ + my $verbose = Trim(shift()); + $verbose = 127 if $verbose =~ /^debug$/i; + $gVerbose = int($1), return if ($verbose =~ /^(\d+)$/) && ($1 < 128); + $gVerbose = 1; + warn("Verbose level `$verbose' is not integer between 0 - 127\n") if !shift(); +} + +sub CloseLog() +{ + close(LOG) if $gLogfile; + $gLogfile = ""; +} + + +############################################################################### +# + +sub RunIMakerCmd($$$$$@) +{ + my ($makecmd, $cmdarg, $tgtext, $mklevel, $skipsteps, %prevtgt) = @_; + $ENV{IMAKER_MKLEVEL} = $mklevel; + + ($cmdarg, my $hptgt, my @targets) = HandleCmdArg($cmdarg); + + foreach my $tgt (@targets) { + my $skipstep = ($tgt =~ s/#$//) || $skipsteps; + (my $target = "$tgt$tgtext") =~ s/(\[\d+\])(.+)$/$2$1/; + if ($target eq "menu") { + ($cmdarg, $target) = Menu($cmdarg); + next if ($target eq "menu"); + ($cmdarg) = HandleCmdArg($cmdarg); } - $step = sprintf("%" . length(@step."") . "s", $stepnum + 1) . ". $step"; - push(@stepdur, $step, $durstr); - $maxslen = imaker::Max($maxslen, length($step)); - $maxdlen = imaker::Max($maxdlen, length($durstr)); + $prevtgt{$target =~ /^([^-]+)/ ? $1 : $target} = 1; + push(@gReport, Trim((($target !~ /^(.+)\[\d+\]$/) || ($gVerbose & 64) ? $target : $1) . + ($skipstep ? "#" : "") . " $hptgt"), -1, -$mklevel - 1); + my $tgtind = $#gReport; + my @targets = RunMakeCmd("$makecmd $cmdarg" . ($target eq "defaultgoals" ? "" : " \"$target\"") . + join("", map(" \"$_\"", split(/\s+/, $hptgt))), $skipstep); + $gReport[$tgtind - 2] .= " (intermediate)" if @targets; + $gReport[$tgtind - 1] = pop(@gStepDur); + $gReport[$tgtind] = $mklevel + 1 if !$gError; + delete(@gReport[$tgtind - 2 .. $tgtind]) if (@targets && !$gError && !($gVerbose & 64)); + map { + RunIMakerCmd($makecmd, "$cmdarg $_ $hptgt", $target =~ /(-.*)$/ ? $1 : "", $mklevel + 1, $skipstep, %prevtgt) + if !exists($prevtgt{$_}); + } @targets; + } +} + +sub RunMakeCmd($$) +{ + ($gStartmk, $gMakecmd, $gError) = (time(), Trim(shift()), 0); + my ($skipstep, $mkstart, $start, $restart, $cwd, %env) = (shift(), 0, 0, 0, Cwd::cwd(), %ENV); + my @stepdur = my @targets = (); + $ENV{IMAKER_MKRESTARTS} = -1; + + do { + InitMkglobals(); + ($gTgterr, my $printvar, my @steps) = (1, "", ()); + $ENV{IMAKER_MKRESTARTS}++; + + if ($gExportvar{""}) { + if (!$ENV{IMAKER_EXPORTMK}) { + (my $tmpfh, $ENV{IMAKER_EXPORTMK}) = File::Temp::tempfile( + File::Spec->tmpdir() . "/imaker_temp_XXXXXXXX", SUFFIX => ".mk", UNLINK => 1); + close($tmpfh); + $ENV{IMAKER_EXPORTMK} =~ tr-\\-\/-; + } + WriteFile($ENV{IMAKER_EXPORTMK}, "# Generated temporary makefile `$ENV{IMAKER_EXPORTMK}'\n" . + "ifndef __IMAKER_EXPORTMK__\n__IMAKER_EXPORTMK__ := 1\n" . + join("", map(/^([^:]+)(?:\:(.+))?$/ && !defined($2) ? "$1=$gExportvar{$_}\n" : + "ifeq (\$(filter $1,\$(TARGETNAME)),)\n$2=$gExportvar{$_}\nendif\n", + sort({($a =~ /([^:]+)$/ && uc($1)) cmp ($b =~ /([^:]+)$/ && uc($1))} + grep(!/^(?:|.*[+:?])$/, keys(%gExportvar))))) . + "else\n" . + join("", map(/^\d{3}(.+[+:?])$/ ? "$1=$gExportvar{$_}\n" : (), sort({$a cmp $b} keys(%gExportvar)))) . + "endif # __IMAKER_EXPORTMK__\n", "", "q", 1); + $gExportvar{""} = 0; + } + + open(MCMD, "$gMakecmd 2>&1 |"); + while (local $_ = ) { + chomp(); + DPrint(1, "$_\n"), next if !s/^#iMaker\x1E//; +# DPrint(64, "#iMaker#$_\n"); + + if (/^BEGIN$/) { + $mkstart = time(); + $start = $mkstart if !$start; + next; + } + if (/^STEPS=(.*)$/) { + my $steps = $1; + @steps = split(/\s+/, $steps), next if ($steps !~ s/^target://); + @targets = grep($_ ne "", map(Trim($_), split(/(?> 8); + die("Command `$gMakecmd' failed in `" . GetAbsDirname(".") . "'.\n") if ($gTgterr = $gError); + CloseLog(); + } until !$restart; + push(@gStepDur, time() - $gStartmk); + return(@targets); +} + + +############################################################################### +# + +sub HandleCmdArg($) +{ + my $cmdarg = shift(); + my $origarg = $cmdarg = (defined($cmdarg) ? $cmdarg : ""); + + my @cmdout = qx($ENV{PERL} -x $0 --splitarg $cmdarg); + die("Can't parse Make arguments: `$cmdarg'.\n") if $?; + + map { + chomp(); + s/ /\x1E/g; + s/\"/\\\"/g; + s/(\\+)$/$1$1/; + } @cmdout; + $cmdarg = " " . join(" ", @cmdout) . " "; + + if ($cmdarg =~ /^.* VERBOSE\x1E*=(\S*) /) { + (my $verbose = $1) =~ s/\x1E/ /g; + SetVerbose($verbose, 1); } - imaker::DPrint(2, "=" x 79 . "\n"); - @stepdur = ("Step", "Duration", "=" x $maxslen, "=" x $maxdlen, @stepdur, - "-" x $maxslen, "-" x $maxdlen, "Total", imaker::Sec2Min(time() - $start)); - imaker::DPrint(2, sprintf("%-${maxslen}s %-${maxdlen}s ", shift(@stepdur), shift(@stepdur)) . "\n") - while(@stepdur); + if ($cmdarg =~ /\s+--?conf=(\S*)\s+/) { + (my $prj = $1) =~ /(.*?)(?:;(.*))?$/; + ($prj, my $conf) = ($1, defined($2) ? $2 : ""); + $cmdarg =~ s/\s+--?conf=\S*\s+/ USE_CONE=mk CONE_PRJ=$prj CONE_CONF=$conf cone-pre defaultgoals /; + } + + $cmdarg = " " . HandleExtCmdArg($cmdarg) . " " if $gImakerext; + + $gMakecmd = "$ENV{IMAKER_MAKE} -f $0" . join("", map(" \"$_\"", split(/\s+/, Trim($cmdarg)))); + warn("Can't parse Make targets.\n") + if (!(my $targets = (qx($gMakecmd 2>&1) =~ /\|MAKECMDGOALS=(.*?)\|/ ? " $1 " : "")) && + ($cmdarg !~ /\s-(?:-?v(?:ersion?|ersi?|er?)?|versio\S+)\s/)); + + GetConfmkList() if + grep(!/^(help(-.+)?|print-.+)$/ || /^help-config$/, my @targets = split(/\s+/, Trim($targets))); + + my ($mkfile, $mkfiles, $hptgt) = ("", "", ""); + map { + $cmdarg =~ s/\s+\Q$_\E\s+/ /; + if (exists($gConfmkList{$_})) { + ($mkfile = $gConfmkList{$_}) =~ s/ /\x1E/g; + $mkfiles .= " -f $mkfile"; + $targets =~ s/\s+\Q$_\E\s+/ /; + } + } @targets; + $cmdarg = "$mkfiles$cmdarg"; + + map { $targets =~ s/\s\Q$_\E\s/ /; $hptgt .= " $_" } + grep(/^help-.+$/ && !/^help-config$/, @targets); + map { $targets =~ s/\s\Q$_\E\s/ /; $hptgt .= " $_" } + grep(/^print-.+$/, @targets); + $hptgt = Trim($hptgt); - imaker::CloseLog(); + if ($targets =~ s/ default(?= )//g) { + ($targets = Trim($targets)) =~ s/ /\x1E/g; + $cmdarg .= "TARGET_DEFAULT=$targets" if ($targets ne ""); + $targets = "default"; + } + @targets = ("defaultgoals@targets") if + !(@targets = map(s/\x1E/ /g ? $_ : $_, split(/\s+/, Trim($targets)))) || ("@targets" eq "#"); + + $mkfiles = ""; + while ($cmdarg =~ s/\s+(-f\s?|--(?:file?|fi?|makefile?|makefi?|make?)[=\s]|IMAKER_CONFMK\x1E*=)(\S+)\s+/ /) { + $mkfile = $2; + ($mkfile = GetAbsFname(scalar($mkfile =~ s/\x1E/ /g, $mkfile))) =~ s/ /\\\x1E/g + if ($1 !~ /^IMAKER_CONFMK/); + $mkfiles .= ($mkfiles eq "" ? "" : chr(0x1E)) . $mkfile; + } + while ($cmdarg =~ s/\s+(\S+?)\x1E*([+:?])=\x1E*(\S+?)\s+/ /) { + ($gExportvar{sprintf("%03s", ++$gExportvar{""}) . "$1$2"} = $3) =~ s/\x1E/ /g; + } + $cmdarg = join(" ", map(scalar(s/\x1E/ /g, "\"$_\""), split(/\s+/, Trim($cmdarg . + ($mkfiles eq "" && ($ENV{IMAKER_MKLEVEL} || grep(/^default$/, @targets)) ? "" : " IMAKER_CONFMK=$mkfiles"))))); + + DPrint(2, "HandleCmdArg: `$origarg' => `$cmdarg', `" . join(" ", @targets) . "', `$hptgt'\n"); + return($cmdarg, $hptgt, @targets); +} + + +############################################################################### +# + +sub MenuRuncmd($) +{ + $ENV{IMAKER_CMDARG} = shift(); + return(map(chomp() ? $_ : $_, qx($ENV{PERL} -x $0 2>&1))); } +sub Menu($) +{ + (my $cmdarg = " " . shift() . " ") =~ s/\s+"IMAKER_CONFMK="\s+/ /; + my ($prodind, $product, @product) = (0, "", ()); + my ($tgtind, $target, $tgtcols, $tgtrows, @target) = (0, "", 4, 0, ()); + my ($vartype, $varudeb, $varsym); + my $cfgfile = "./imaker_menu.cfg"; + + $cmdarg = ($cmdarg =~ /^\s*$/ ? "" : " " . Trim($cmdarg)); + open(FILE, "<$cfgfile") and + (($prodind, $tgtind, $vartype, $varudeb, $varsym) = map(chomp() ? $_ : $_, )) and close(FILE); + ($prodind, $tgtind, $vartype, $varudeb, $varsym) = + ($prodind || 0, $tgtind || 0, $vartype || "rnd", $varudeb || 0, $varsym || 0); + + while (1) { + print("\nPRODUCTS\n--------\n"); + # + if (!@product) { + @product = sort({lc($a) cmp lc($b)} grep($_ ne "", keys(%gConfmkList))); + $prodind = 0 if ($prodind > @product); + } + $product = ($prodind ? " $product[$prodind - 1]" : ""); + my $maxlen = Max(map(length($_), @product)); + map { + printf(" %" . (length(@product)) . "s) %-${maxlen}s %s\n", $_ + 1, $product[$_], $gConfmkList{$product[$_]}); + } (0 .. $#product); + print(" NO PRODUCTS FOUND!\n") if !@product; + + print("\nTARGETS\n-------\n"); + # + if (!@target) { + @target = grep(s/^== (.+) ==$/$1/, MenuRuncmd("$product PRINTCMD=0 VERBOSE=1 help-target-*-wiki")); + $tgtind = 0 if ($tgtind > @target); + $tgtrows = int($#target / $tgtcols + 1); + my $maxind = 0; + map { + if (!($_ % $tgtrows)) { + $maxind = length(Min($_ + $tgtrows, $#target + 1)) + 1; + $maxlen = Max(map(length(), @target[$_ .. Min($_ + $tgtrows - 1, $#target)])); + } + $target[$_] = sprintf("%${maxind}s) %-${maxlen}s", "t" . ($_ + 1), $target[$_]); + } (0 .. $#target); + } + ($target = ($tgtind ? $target[$tgtind - 1] : "")) =~ s/^.+?(\S+)\s*$/$1/; + foreach my $row (1 .. $tgtrows) { + foreach my $col (1 .. $tgtcols) { + my $ind = ($col - 1) * $tgtrows + $row - 1; + print(($ind < @target ? " $target[$ind]" : "") . ($col != $tgtcols ? " " : "\n")); + } + } + print(" NO TARGETS FOUND!\n") if !@target; + + print("\nCONFIGURATION\n-------------\n"); + # + print( + " Product: " . ($prodind ? $product[$prodind - 1] : "NOT SELECTED!") . "\n" . + " Target : " . ($tgtind ? $target : "NOT SELECTED!") . "\n" . + " Type : " . ucfirst($vartype) . "\n" . + " Debug : " . ($varudeb ? ($varudeb =~ /full/i ? "Full debug" : "Enabled") : "Disabled") . "\n" . + " Symbols: " . ($varsym ? "Created\n" : "Not created\n")); + + print("\nOPTIONS\n-------\n"); + # + print( + " t) Toggle type between rnd/prd/subcon\n" . + " u) Toggle debug between urel/udeb/udeb full\n" . + " s) Toggle symbol creation on/off\n" . + " r) Reset configuration\n" . + " h) Print usage information\n" . + " x) Exit\n\n" . + "Hit Enter to run: imaker$product$cmdarg TYPE=$vartype USE_UDEB=$varudeb USE_SYMGEN=$varsym $target\n"); + + print("\nSelection: "); + # + my $input = ; + ($input = (defined($input) ? $input : "?")) =~ s/^\s*(.*?)\s*$/\L$1\E/; + + if ($input =~ /^(\d+)$/ && ($1 > 0) && ($1 <= @product) && ($1 != $prodind)) { + $prodind = $1; + ($tgtind, @target) = (0, ()); + } + elsif ($input =~ /^t(\d+)$/ && ($1 > 0) && ($1 <= @target) && ($1 != $tgtind)) { + $tgtind = $1; + } + elsif ($input eq "t") { + $vartype = ($vartype =~ /rnd/i ? "prd" : ($vartype =~ /prd/i ? "subcon" : "rnd")); + } + elsif ($input eq "u") { + $varudeb = (!$varudeb ? 1 : ($varudeb !~ /full/i ? "full" : 0)); + } + elsif ($input eq "s") { + $varsym = ($varsym ? 0 : 1); + } + elsif ($input eq "r") { + ($prodind, @product) = (0, ()); + ($tgtind, @target) = (0, ()); + ($vartype, $varudeb, $varsym) = ("rnd", 0, 0); + } + elsif ($input eq "h") { + print("\nTODO: Help"); + sleep(2); + } + elsif ($input =~ /^(x|)$/) { + open(FILE, ">$cfgfile") and + print(FILE map("$_\n", ($prodind, $tgtind, $vartype, $varudeb, $varsym))) and close(FILE); + return(("", "menu")) if ($input eq "x"); + $cmdarg = "$product$cmdarg TYPE=$vartype USE_UDEB=$varudeb USE_SYMGEN=$varsym"; + $ENV{IMAKER_CMDARG} = Trim("$cmdarg $target"); + return(($cmdarg, $target eq "" ? "defaultgoals" : $target)); + } + } +} + + +############################################################################### +# + +sub Install($$$) +{ + my ($clean, $bldinf, $destdir) = @_; + my $srcdir = GetDirname($bldinf = GetAbsFname($bldinf)); + $destdir = GetAbsDirname($destdir) if $destdir; + + print(($clean ? "\nCleaning" : "\nInstalling") . " `$bldinf'" . ($destdir ? " to `$destdir'\n" : "\n")); + + my $export = 0; + foreach (grep(!/^\s*\/\//, ReadFile($bldinf, 0))) { + $export = 1, next if /^\s*PRJ_EXPORTS\s*$/i; + next if !$export; + Install($clean, "$srcdir$1", $destdir), next if /^\s*#include\s+"(.+)"\s*$/; + die("Unknown line `$_'.\n") if !/^\s*(\S+)\s+(.+?)\s*$/; + my ($src, $dest) = ("$srcdir$1", $2); + $dest = "$gEpocroot/epoc32$dest" if ($dest =~ s/^\+//); + $dest .= GetBasename($src) if ($dest =~ s/\s+\/\/$//); + ($src, $dest) = (GetAbsFname($src), GetAbsFname($dest)); + next if ($destdir && ($dest !~ /^$gEpocroot\/epoc32\/tools\//i)); + $dest = "$destdir/" . GetBasename($dest) if $destdir; + print(($clean ? "Delete" : "Copy `$src' =>") . " `$dest'\n"); + unlink($dest); + die("Deletion failed.\n") if ($clean && -e($dest)); + next if $clean; + File::Path::mkpath(GetDirname($dest)); + File::Copy::copy($src, $dest) or die("Copying failed.\n"); + chmod(0777, $dest); + } +} + + +############################################################################### +# + +END { + if (!$gArgv) { + (my $keepgoing, $gStartmk) = ($gKeepgoing, time() - $gStartmk); + $gKeepgoing = 1; + SetLogfile($gLogfiles{__prev__}) if %gLogfiles; + PrintEnv(0) if $gError; + die("Command `$gMakecmd' failed in `" . GetAbsDirname(".") . "'.\n") + if ($gTgterr && !$keepgoing); + + map { UnsubstDrive($_) } sort({$a cmp $b} keys(%gSubstdrv)); + + @gIcmd = @gReport; + (my $report, @gReport) = (2, ()); + my ($maxtlen, $maxvlen, %uniq) = (0, 0, ()); + while (@gIcmd) { + my ($tgtvar, $durval, $type) = (GetIPar(1), GetIPar(1), GetIPar(1)); + if ($type =~ /^-?\d+$/) { + push(@gReport, [$tgtvar, $durval, $type]); + ($maxtlen, %uniq) = (Max($maxtlen, length($tgtvar)), ()); + } else { + $report = 1, push(@gReport, [$tgtvar, $durval, $type]) + if ($tgtvar ne "") && !($uniq{"$tgtvar|$durval"}++); + $maxvlen = Max($maxvlen, length($tgtvar)); + } + } + + my ($tgtcnt, $warn) = (0, 0); + DPrint($report, "=" x 79 . "\n" . join("\n", map(@$_[2] =~ /^-?\d+$/ ? + ($tgtcnt++ ? "-" x 79 . "\n" : "") . + "Target: " . sprintf("%-${maxtlen}s", @$_[0]) . + " Duration: " . Sec2Min(@$_[1] < 0 ? $gStartmk : @$_[1]) . + " Status: " . (@$_[2] < 0 ? ($warn = "FAILED") : "OK") + : sprintf("%-${maxvlen}s", @$_[0]) . " = `@$_[1]'" . + ((@$_[2] =~ /^[fd]$/i) && !-e(@$_[1]) ? " - DOESN'T EXIST" : ""), @gReport)) . + (@gReport ? "\n" . "-" x 79 . "\n" : "") . + "Total duration: " . Sec2Min(time() - $gStarttime) . + " Status: " . ($gError && !$keepgoing ? "FAILED" : "OK" . + ($warn ? " (with keep-going)" : "")) . + "\n" . "=" x 79 . "\n"); + + warn("\$_ has been changed in an uncontrolled manner!\n") + if !/^default input and pattern-searching space$/; + CloseLog(); + exit(1) if ($gError && !$keepgoing); + } +} + + __END__ # OF IMAKER.PL