-- -- HAMSTER_LIBS.LUA Copyright (c) 2003-05, asko.kauppi@sci.fi -- -- Generic file system and other tool scripts, usable with or without the -- main Hamster build system (nothing build specific should be herein). -- -- Usage: -- lua [-w] [-e WINCE=true] -l hamster_libs ... -- -- The 'WINCE=true' override can be used to inform that WinCE cross compilation -- is about to happen. -- -- License: GPL (see license.txt) -- -- This program is free software; you can redistribute it and/or modify -- it under the terms of the GNU General Public License as published by -- the Free Software Foundation; either version 2 of the License, or -- (at your option) any later version. -- -- This program is distributed in the hope that it will be useful, -- but WITHOUT ANY WARRANTY; without even the implied warranty of -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -- GNU General Public License for more details. -- -- You should have received a copy of the GNU General Public License -- along with this program; if not, write to the Free Software -- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -- -- Dependencies: -- none (Lua 5.0 or later) -- -- Author: -- asko.kauppi@sci.fi -- -- Note: ... -- -- To-do: ... -- local tmp,_ local m= { _info= { MODULE= "Hamster libs - file system & OS tools etc.", AUTHOR= "asko.kauppi@sci.fi", PLATFORM= "OS X, Linux, Win32, Net/FreeBSD", COPYRIGHT= "Copyright (c) 2003-05, Asko Kauppi", RELEASE= "20050116", -- release date (version) LICENSE= "GPL" } } -- Module config: -- local IGNORE_N= true -- true: ignore 'n' field in tables (heritage of Lua 4) -- false: assume there's no special 'n' at all local ASSUME= assert -- Useful when needing just a single return value from i.e. 'string.find()': -- function first(x) return x end function second(_,x) return x end function third(_,_,x) return x end ASSUME( first(1,2,3,4)==1 ) ASSUME( second(1,2,3,4)==2 ) ASSUME( third(1,2,3,4)==3 ) function skip2(_,_, ...) return unpack(arg) end ASSUME( skip2(1,2,3)==3 ) -- Check that all key names of 'tbl' are appropriate: -- local function ASSUME_FLAGS( tbl, lookup ) -- for key,_ in pairs(tbl) do if type(key)=="number" then -- skip 1..N fields elseif not lookup[key] then -- faster this way! error( "Unknown table key: "..key ) end end end -- Lua < 5.1w1 had 'io.popen' even if it wouldn't work: -- local USE_POPEN= io.popen and pcall( io.popen, "echo" ) or false local function stderr_print( ... ) io.stderr:write( unpack(arg), "\n" ) end --== SYSTEM DETECTION ==-- rawset( _G, "HOST", false ) local uname if os.getenv("WINDIR") then HOST= "win32" -- elseif USE_POPEN then local f= io.popen "uname" uname= f:read'*a' f:close() if string.sub( uname,-1 )=='\n' then uname= string.sub( uname, 1,-2 ) -- remove terminating newline end if string.find(uname,"BSD") then -- "NetBSD"/"FreeBSD" HOST= "bsd" else HOST= string.lower(uname) -- "linux"/"darwin"/"qnx" end else HOST= os.getenv("OSTYPE") or -- "darwin" (if no popen)/"msys"/.. error "Unknown OS! (or 'io.popen()' not enabled)" end -- Makefile may have preset 'WINCE=true' for us -- rawset( _G, "WINCE", rawget(_G,"WINCE") and {} or false ) if WINCE then -- -- WinCE/PocketPC compilation (eVC++ 4.0) has following env.vars defined: -- PLATFORM="Pocket PC 2003" -- WCEROOT=... -- SDKROOT=... -- OSVERSION="wce420" -- TARGETCPU="x86"/"emulator"/"armvt4"/... -- ASSUME( os.getenv("WCEROOT") ) WINCE.platform= ASSUME( os.getenv("PLATFORM") ) -- "STANDARDSDK" local tmp= string.lower( os.getenv("OSVERSION") ) -- "wce400" WINCE.ver= ASSUME( tonumber( third(string.find(tmp,"wce(%d+)")) ) ) local target= ASSUME( string.lower(os.getenv("TARGETCPU")) ) -- "armv4" WINCE[ target ]= true -- exact type (.armv4/.mipsii/.sh3/.x86/.emulator/..) if string.find( target, "arm" ) then -- 'armv4[i/t]' WINCE.arm= true WINCE.cpu= "arm" elseif string.find( target, "mips" ) then -- 'mips16'/'mipsii[_fp]'/'mips_iv[_fp]' WINCE.mips= true WINCE.cpu= "mips" elseif string.find( target, "sh" ) then -- 'sh3'/'sh4' WINCE.sh= true WINCE.cpu= WINCE.sh3 and "sh3" or "sh4" elseif string.find( target, "x86" ) or -- 'x86' string.find( target, "emul" ) then -- 'emulator' WINCE.cpu= "x86" WINCE.emul= WINCE.emulator --alias else error( "Unknown WinCE cpu: "..target ) end end -- Makefile may have preset 'WIN32=true' (cross compilation on OS X / Linux host) -- rawset( _G, "WIN32", rawget(_G,"WIN32") and { msys=true } or false ) if WINCE then WIN32= false -- WinCE target (we use Win32 for 98-XP side, only) elseif HOST=="msys" then WIN32= { msys=true } -- cross compilation, or Win32/MSYS (unix-like) elseif os.getenv("WINDIR") then WIN32= {} -- native Win32 end -- Note: When using MSYS, 'os.execute()' still passes the commands to Win32, -- so only the commands used in compilation should be Unix-like. -- local WIN32_MSYS= WIN32 and WIN32.msys -- Win32 MSYS or cross-compiling (Linux or OS X host) rawset( _G, "HOST_WIN32", HOST=="win32" ) rawset( _G, "HOST_WIN32_NATIVE", HOST_WIN32 and (not WIN32_MSYS) or false ) -- really on Win98-XP rawset( _G, "HOST_WIN32_MSYS", HOST_WIN32 and WIN32_MSYS or false ) -- MSYS, not cross compilation if not USE_POPEN then -- ASSUME( not HOST_WIN32_MSYS, "Need 'popen' support for running on MSYS - sorry!" ) -- -- Otherwise, 'os.execute >tmpname' etc. won't work, since 'os.execute' -- does not see the MSYS unix-like file structure (/tmp/ etc.) stderr_print "WARNING: not using 'io.popen()' (but we'll manage..)\n" end rawset( _G, "DARWIN", false ) rawset( _G, "LINUX", false ) rawset( _G, "BSD", false ) rawset( _G, "QNX", false ) if not WIN32 then -- skip if cross compiling if HOST=="darwin" then DARWIN= {} elseif HOST=="linux" then LINUX= {} -- Detect if Scratchbox being used, and which processor. -- local f= io.open( "/targets/links/scratchbox.config", "r" ) if f then local tmp= f:read'*a' -- all at once f:close() local str= third( string.find( tmp, "SBOX_CPU=(.-)%s" ) ) -- "arm"/"powerpc"/"i386"/"i686" if str=="arm" then LINUX.arm= true elseif str=="powerpc" then LINUX.ppc= true elseif string.find( str, "i[36]86" ) then -- "i386"/"i686" LINUX.x86= true else error( "Unknown scratchbox CPU: ".. str ) end else -- TBD: prepare for native PPC/ARM compilations -- LINUX.x86= true end elseif HOST=="bsd" then -- TBD: prepare for non-x86 compilations -- BSD= { x86=true, netbsd=(uname=="NetBSD"), freebsd=(uname=="FreeBSD") } elseif HOST=="qnx" then QNX= { x86= (os.getenv("PROCESSOR")=='x86') } end end -- Clear text target name (summary of the above) -- rawset( _G, "TARGET", false ) TARGET= (WINCE and "WinCE/".. string.lower(os.getenv("TARGETCPU"))) or (WIN32 and (WIN32.msys and "win32/msys" or "win32")) or (LINUX and ((LINUX.x86 and "linux/x86") or (LINUX.ppc and "linux/ppc") or (LINUX.arm and "linux/arm") or "linux")) or (BSD and BSD.x86 and "BSD/i386") or HOST -- QNX, Darwin.. -- Due to Gentoo emerge sandboxing, we avoid "rt" unless really required: -- local _OPEN_RT= HOST_WIN32_NATIVE and "rt" or "r" local _OPEN_WT= HOST_WIN32_NATIVE and "wt" or "w" -- Make sure debug stuff really gets on screen: -- if BSD or HOST_WIN32_MSYS then local _print= print local _write= io.write function print(...) _print( unpack(arg) ); io.flush() end function io.write(...) _write( unpack(arg) ); io.flush() end end -- Finding out Lua engine we're running on (gosh, wouldn't it be worth having such -- value declared for us, by the engine?!) -- local tmp= ASSUME(_VERSION) -- "Lua 5.1 (work3)" (hopefully) local major, minor= skip2 (string.find( tmp, "^Lua (%d+)%.(%d+)" )) local beta= skip2 (string.find( tmp, "%(beta(%d+)%)" )) local work= skip2 (string.find( tmp, "%(work(%d+)%)" )) -- Real release (5.1) must be > betas & works have been if (not beta) and (not work) then beta= 9 work= 9 else beta= tonumber(beta) or 0 work= tonumber(work) or 0 ASSUME( beta<9 and work<9 ) end ASSUME( major and minor ) local LUA_VER= tonumber(major) + tonumber(minor)*0.1 + beta*0.01 + work*0.001 -- i.e. 5.103 --== DEBUG FUNCTIONS ==-- ----- -- void= Loc_Dump( tbl [,lev] ) -- local function Loc_Dump( tbl, lev ) -- lev= lev or 0 if not tbl then stderr_print "(nil)" return -- elseif type(tbl)=="table" then -- local pre, pre2 local items= false pre= string.rep(" ", lev) for key,val in pairs(tbl) do -- items= true pre2= pre..key.." : " if type(val)=='table' then stderr_print( pre2.."table" ) if lev >= 0 then -- recurse Loc_Dump( val, lev+1 ) end elseif type(val)=='string' then stderr_print( pre2.."\t'"..val.."'" ) else stderr_print( pre2,val ) end end if not items then stderr_print( pre.."(empty)" ) end else stderr_print( "("..type(tbl)..") ".. tbl ) end end rawset( _G, "dump", Loc_Dump ) ----- -- Call tracing (debugging only!) -- local function my_callhook( reason ) -- "call" -- local info= debug.getinfo( 2 ) -- -- .source '@Scripts/globals.lua' -- .what 'Lua'/'C'/'main' -- .func function -- .short_src 'Scripts/globals.lua' -- .currentline -1 -- .namewhat '' -- .linedefined 39 -- .nups 2 local name= (info.name) or "" local str= " -> "..(info.source).." "..(info.linedefined).." "..name stderr_print(str) end -- -- style: 'c' = function calls -- 'r' = returns -- 'l' = lines -- local function TRACE_ON( style ) debug.sethook( my_callhook, style or "c" ) end local function TRACE_OFF() debug.sethook( nil ) end --== BASIC FUNCTIONS ==-- -- local function Loc_FileExists( fn ) local fh= io.open(fn,"r") if fh then fh:close(); return true end return false end -- local function Loc_ReadAll( fn ) local f= io.open( fn, _OPEN_RT ) if not f then return nil end local str= f:read'*a' -- read all at once f:close() return str end ----- -- int= getn_over_holes( tbl ) -- -- Returns the REAL max 'n' index, even if the table has holes in it. -- -- int= getn_stop_at_holes( tbl ) -- -- Returns the FIRST nil index -1 (= end of contiguous indices) -- -- Note: Lua <= 5.1-work0 'table.getn()' always works as 'getn_stop_at_holes'. -- Lua 5.1-work1 'table.getn()' might skip holes _in_some_cases_ -- (but not all). -- local getn_orig= assert( table.getn ) -- use only when known there are no holes local function getn_over_holes( tbl ) local n= 0 for k,v in pairs(tbl) do if IGNORE_N and k=='n' then -- skip 'n' (ancient) else local i= tonumber(k) ASSUME( i, "Nonnumeric index: "..k ) if i>n then n=i end end end return n end local getn_stop_at_holes if LUA_VER >= 5.101 then -- 5.1w1 and later getn_stop_at_holes= function( tbl ) local n= getn_orig(tbl) -- over holes (or not..) for i=1,n do if tbl[i]==nil then -- allow 'false' return i-1 end end return n end else getn_stop_at_holes= getn_orig end ----- -- tbl2= Loc_TblCopy( tbl/any [,filter_func(v)] ) -- -- The filter func is fed each item at a time. If it returns 'nil', that -- item will not be copied further. -- local function Loc_TblCopy( tbl1, filter_func ) -- if not tbl1 then return nil end -- convert scalars to table: if type(tbl1)~="table" then tbl1={tbl1} end local tbl2= {} if not filter_func then -- faster this way for k,v in pairs(tbl1) do tbl2[k]= v end else for k,v in pairs(tbl1) do tbl2[k]= filter_func(v) -- 'nil's get removed, 'false's pass on end end return tbl2 end ----- -- tbl2= Loc_TblCopy_i( tbl/any [,filter_func(v)] ) -- -- Same as 'TblCopy' but goes through 1..N and does not leave -- holes in the returned table (indices change if items are discarded). -- local function Loc_TblCopy_i( tbl1, filter_func ) -- if not tbl1 then return nil end -- convert scalars to table: if type(tbl1)~="table" then tbl1={tbl1} end local tbl2= {} for i=1,getn_over_holes(tbl1) do -- ipairs would stop at holes -- local v= tbl1[i] if v~=nil then -- skip holes if not filter_func then table.insert( tbl2, v ) else local tmp= filter_func(v) if tmp~=nil then -- skip if 'nil' (passes 'false') table.insert( tbl2, tmp ) end end end end return tbl2 end -- local function Loc_TableWithoutNils( tbl ) -- ASSUME( type(tbl)=="table" ) return ( getn_over_holes(tbl) == getn_stop_at_holes(tbl) ) end -- Safety precaution to see when we SHOULDN'T use 'table.getn()' or 'ipairs()' -- -- Note: These are defined global, so that also mistakes in build scripts -- shall be trapped. -- if LUA_VER >= 5.103 then -- didn't work with 5.0 (eternal loop) -- local ipairs_orig= assert(ipairs) ipairs= function(tbl) -- if not Loc_TableWithoutNils(tbl) then dump(tbl); error "Hole in table - do not use 'ipairs()'!" end return ipairs_orig(tbl) end end function table.getn( tbl ) -- if not Loc_TableWithoutNils(tbl) then dump(tbl); error "Hole in table - do not use 'table.getn()'!" end return getn_orig(tbl) end --== MISC FUNCTIONS ==-- ----- -- Loc_WaitSec( [secs_uint] ) -- -- Purpose: Delaying execution for a while. -- -- We'd like to do ms-resolution timing, but native Lua 5.0 does not offer -- good means for this. 'os.clock()' shows CPU time (not real time) whereas -- 'os.time()' only has 1 sec resolution. What to do?!? -- local function Loc_WaitSec( secs ) -- secs= secs or 1 --default local t0= os.time() if type(t0)=="string" then -- LuaX 'USE_FLOAT' returns a string -- t0= tonumber( string.sub(t0,-4) ) -- least sign. numbers (0..9999) local t1= t0 while( t1-t0 < secs+1 ) do t1= tonumber( string.sub(os.time(),-4) ) if t1n) then return false -- has holes elseif _false_as_nils and v==false then return false -- has false (treat as a hole) end end return true end ASSUME( Loc_IsFlatTableWithoutNils( { 1, nil, "foo" } ) == false ) ----- -- local function Loc_CleanDuplicates_onsite( tbl ) -- if type(tbl)~="table" then return end -- nil, string or number (ok!) local n= getn_over_holes(tbl) for i=1,n do for j=i+1,n do if tbl[j]==tbl[i] then -- DUPLICATES! tbl[j]= nil end end end end ----- -- [keyed_tbl], [indexed_tbl]= Loc_TblSplit( tbl ) -- -- Splits a table to those items with a numeric (integer) key, and those -- with other (string) keys. -- -- Note: Returns 'nil' instead of empty tables. -- local function Loc_TblSplit( tbl ) -- local keyed_tbl= {} local indexed_tbl= {} if not tbl then return nil end for k,v in pairs(tbl) do -- if type(k) == "number" then indexed_tbl[k]= v elseif IGNORE_N and k=="n" then -- ignore 'n' fields else keyed_tbl[k]= v end end if Loc_IsEmptyTable(keyed_tbl) then keyed_tbl= nil end if Loc_IsEmptyTable(indexed_tbl) then indexed_tbl= nil end return keyed_tbl, indexed_tbl end ----- -- tbl= Loc_TblFlatten( val [,force_copy_bool] ) -- -- Returns a table without subtables (flattens contents into the main table). -- Also removes any 'nil' or 'false' items. -- -- Params: 'force_copy' may be used for ensuring the returned table is always -- a local copy of the provided one (even if they were identical). -- This is useful if the caller wants to later modify the contents. -- -- Note: Tables are expected to be just containers (index values don't matter) -- but order is maintained (important for 'append' and 'prepend' functions). -- local function Loc_TblFlatten( val, force_copy ) -- if not val then return nil end if type(val) ~= "table" then -- make scalars into a table return {val} end -- If already flat, skip making another table (optimization) -- if not force_copy then if IGNORE_N and val.n then -- make a copy elseif Loc_IsFlatTableWithoutNils(val,true) then -- also falses are found return val end end -- Find out largest index (note: there may be holes in the table!) -- local ret= {} for i= 1,getn_over_holes(val) do -- local v= val[i] if not v then -- skip holes and 'false's elseif type(v)=="table" then for _,vv in ipairs( Loc_TblFlatten(v,force_copy) ) do -- keep order! table.insert( ret, vv ) end else table.insert( ret, v ) end end return ret end ASSUME( getn_orig( Loc_TblFlatten( { 1,2,{3,4;n=2},5} ) ) == 5 ) ASSUME( getn_orig( Loc_TblFlatten( { 'a','b',[4]='c' } ) ) == 3 ) --== STRING FUNCTIONS ==-- ----- -- str= Loc_QuoteIfSpaces(str) -- -- Add quotes so that a file or pathname (with spaces) can be used in OS commands. -- local function Loc_QuoteIfSpaces( str ) -- return string.find( str, ' ' ) and ('"'..str..'"') or str end ----- -- [str/tbl]= Loc_EscSpaces( [str/tbl] ) -- local function Loc_EscSpaces( val ) -- if not val then return nil end if type(val)=="table" then return Loc_TblCopy( val, Loc_EscSpaces ) -- filter end return string.gsub( val, ' ', "\\ " ) end ----- -- [str]= Loc_TblToStr( val_str/num/tbl [,delim_str='\n' [,esc_spaces [,quote_spaces] ] ] ) -- -- This is like 'table.concat()' but: -- - allows 'pass-through' for num/string (non-table) entries -- - allows integrated escaping for spaces within filenames etc. -- local function Loc_TblToStr( val, delim, esc_spaces, quote_spaces ) -- if not val then return val end if type(val)~="table" then val= { val } end ASSUME( Loc_IsFlatTableWithoutNils(val) ) if esc_spaces then val= Loc_TblCopy( val, Loc_EscSpaces ) elseif quote_spaces then val= Loc_TblCopy( val, Loc_QuoteIfSpaces ) end return table.concat( val, delim ) end ----- -- Returns a table of the lines in 'str'. -- local function Loc_Lines( str ) local ret= {} -- 0 lines for l in string.gfind(str, "[^\n]+") do table.insert( ret, l ) end return ret end --== FILE SYSTEM FUNCTIONS ==-- ----- -- bool= Loc_IsSlashTerminated( str ) -- local function Loc_IsSlashTerminated( str ) -- local c= string.sub( str, -1 ) return (c=='/') or (c=='\\') end ----- -- str/tbl= Loc_MakeSlashTerminated( str/tbl ) -- local function Loc_MakeSlashTerminated( val ) -- if not val then return nil end if type(val)=="table" then return Loc_TblCopy( val, Loc_MakeSlashTerminated ) end if Loc_IsSlashTerminated(val) then return val else return val..(HOST_WIN32_NATIVE and '\\' or '/') end end ----- -- [tbl]= Loc_PathAsTable( [path] ) -- -- Returns an OS search path in table form (each directory as a separate entry). -- -- Note: Each entry is slash terminated, to make concatenation with filenames easy. -- local function Loc_PathAsTable( path ) -- local tbl= {} local sep= HOST_WIN32_NATIVE and ";" or ":" path= path or os.getenv( "PATH" ) if not path then return nil end -- Split the path at ';' (windows) or ':' (posix) -- while( path and path~="" ) do -- local head,tail= skip2( string.find( path, "(.-)"..sep.."(.*)" ) ) local v= head or path if v~="" then -- skip possible empty parts (";;") table.insert( tbl, Loc_MakeSlashTerminated(v) ) end path= tail end return tbl end ----- -- [str]= FindFileInPath( fname_str [,path_tbl] ) -- local function Loc_FindFileInPath( fname, path_tbl ) -- path_tbl= path_tbl or Loc_PathAsTable() -- default PATH for _,v in ipairs(path_tbl) do -- local candidate= v..fname if Loc_FileExists(candidate) then return candidate end end return nil end ----- -- fn_str [,ext_str]= Loc_StripSuffix( fn_str ) -- local function Loc_StripSuffix( str ) -- if not str then return nil end local fn,ext= skip2 (string.find( str, "(.*)%.(.*)" )) if fn then return fn,ext else return str end end ASSUME( Loc_StripSuffix( "a.b.c.d" ) == "a.b.c" ) ASSUME( Loc_StripSuffix( "no_sfx" ) == "no_sfx" ) ----- -- path_str, fn_str= Loc_PathSplit( path_and_filename ) -- local function Loc_PathSplit( str ) -- if not str then return nil end local path,fn= skip2 (string.find( str, "(.*[/\\])(.*)" )) if path then if fn=="" then fn=nil end return path,fn else return nil,str -- just filename end end local function Loc_PathOnly( str ) return first( Loc_PathSplit(str) ) end local function Loc_FilenameOnly( str ) return second( Loc_PathSplit(str) ) end local p,r p,r= Loc_PathSplit( "a/b/c/d" ) ASSUME( (p=="a/b/c/") and (r=="d") ) p,r= Loc_PathSplit( "a\\b\\c\\d" ) ASSUME( (p=="a\\b\\c\\") and (r=="d") ) p,r= Loc_PathSplit( "a\\b\\c\\d\\" ) ASSUME( (p=="a\\b\\c\\d\\") and (r==nil) ) p,r= Loc_PathSplit( "a_b_c_d" ) ASSUME( (p==nil) and (r=="a_b_c_d") ) ----- -- str= Loc_RemoveTerminatingSlash( str ) -- local function Loc_RemoveTerminatingSlash( str ) -- local last= string.sub(str,-1) if (last=='/') or (last=='\\') then return string.sub(str,1,-2) else return str end end -- local Loc_Win32Slashes= HOST_WIN32_NATIVE and function(str) return string.gsub( str, '/', '\\' ) end or function(str) return str end -- -- Maps 'C:\...' type paths to MSYS ('/C/...') -- local function Loc_Win32PathToMSYS( str ) -- if not HOST_WIN32_MSYS then return str -- pass-through end local drive,path= skip2 (string.find( str, "^(.)%:(.+)" )) if drive then -- "C:\..." -> "/C/..." str= '/'..drive..path end return string.gsub( str, '\\', '/' ) end local Loc_DirExists1 ----- -- name_str= Loc_TmpName( [dirname_str] ) -- -- Returns a (unique) name for a temporary file (not opened). -- -- Note: This function tries to mend the shortcomings of 'os.tmpname()' -- and 'os.tmpfile()' (which does not provide the name). -- local magic1= string.sub( os.time(), -6 ) -- startup time in secs (last N digits) local magic2= 0 -- counter m._tmpdir= nil -- shared with 'hamster_core' local tmpname_known= {} -- dirs known to exist local function Loc_TmpName( dirname ) -- magic2= magic2 + 1 -- times we've called this routine local magic= "hm"..magic1..magic2..".tmp" if dirname then -- Return a merged filename, do not check for existance (the dir -- might not be there) -- return Loc_MakeSlashTerminated(dirname)..magic end if (not dirname) and m._tmpdir then -- anyone used 'objdir' lately? -- if tmpname_known[m._tmpdir] or Loc_DirExists1(m._tmpdir) then dirname= m._tmpdir end end if not dirname then -- still not; try system dirs -- if HOST_WIN32_NATIVE then dirname= Loc_MakeSlashTerminated( os.getenv("TEMP") or os.getenv("TMP") ) ASSUME( dirname, "TEMP env.dir not set!" ) -- elseif HOST_WIN32_MSYS then dirname= "/tmp/" else dirname= "/tmp/" -- Linux, BSD, OS X, Win32 cross compile.. end if not tmpname_known[dirname] then -- ASSUME( Loc_DirExists1(dirname), "Temporary directory '"..dirname.."' not there!" ) end end tmpname_known[dirname]= true local fn= dirname..magic -- Final check: the file should never already exist: -- ASSUME( not Loc_FileExists(fn) ) return fn end ----- -- bool= Loc_DirExists1( dirname ) -- -- Unlike 'DirExists2()', this function only checks that the dir actually -- exists, not that we can access its contents or create new files in it. -- -- Note: In many cases, it may be better to use 'FileExists()' and check -- for the existance of a known (expected) file within the dir. -- This will also check that we have read ability to the contents. -- Loc_DirExists1= function( dirname ) -- ASSUME( dirname ) local cmd if HOST_WIN32_MSYS then -- Win32 MSYS -- -- MSYS has 'test' but from 'os.execute()' it does not seem to work. -- cmd= "ls -1 -f ".. Loc_QuoteIfSpaces( Loc_Win32PathToMSYS(dirname) ) .." 2> NUL > NUL" -- elseif HOST_WIN32 then -- Win32 native -- cmd= "dir /AD ".. Loc_QuoteIfSpaces( string.gsub( dirname, '/', '\\' ) ) .." 2>NUL >NUL" else -- others (OS X, Linux, BSD, Win32 cross compile) -- cmd= "test -d ".. Loc_QuoteIfSpaces(dirname) end rc= os.execute(cmd) return rc==0 end ASSUME( not Loc_DirExists1( "nosuchdir!" ) ) -- selftest ASSUME( Loc_DirExists1( HOST_WIN32 and os.getenv("WINDIR") or "/usr" ) ) ----- -- bool= DirExists2( dir_name ) -- -- This also checks that we have writing ability to the dir. -- local function Loc_DirExists2( dirname ) -- ASSUME( Loc_IsSlashTerminated(dirname) ) -- STORM AND THUNDER! (or whatever Capt.Haddock would say..) -- MSYS returns a _valid_ file handle even when '/usr/local/' -- (or any other dir) is not there! Gosh.. -- if HOST_WIN32_MSYS then -- Does not check access rights but then again, MSYS users -- probably have no restrictions. -- return Loc_DirExists1( dirname ) end local fn= Loc_TmpName(dirname) local f= io.open( fn, 'w' ) if not f then return false end -- did not exist (or not write access) f:close() os.remove( fn ) return true -- dir existed end local Loc_Mkdir ----- -- bool= DirExists3( dir_name [,create_bool / "r[ead]"/"c[reate]"/"w[rite]"] ) -- -- This also checks that we have writing ability to the dir (and may try -- to create it). -- local known_to_exist= {} -- lookup tbl of already created dirs local function Loc_DirExists3( dirname, flag ) -- dirname= Loc_MakeSlashTerminated( dirname ) local create if flag==true then create= true -- elseif flag then ASSUME( type(flag)=="string" ) local c= string.sub(flag,1,1) if c=='r' then return Loc_DirExists1( dirname ) -- just check it's there (may be read-only) elseif c=='c' then create= true elseif c=='w' then -- default (check we have write access) else error( "Unknown flag: "..flag ) end end if known_to_exist[dirname] then return true -- speedup, no file system checking necessary end local ret= Loc_DirExists2(dirname) if (not ret) and create then Loc_Mkdir(dirname) -- try to create ret= Loc_DirExists2(dirname) -- did we succeed? end if ret then known_to_exist[dirname]= true -- we won't test that twice end return ret end ----- -- rc_int [,stdout_str, stderr_str]= Loc_Execute( cmd_str [,quiet_int(1/2/3)=0] ) -- -- Note: On MSYS, 'os.execute' sees the _native_ Win32 file system whereas -- 'io.popen' sees the MSYS unix-like system (and commands). -- local function Loc_Execute( cmd, quiet ) -- quiet= quiet or 0 -- nul mask local rc,sout,serr -- NetBSD has some problem (didn't get it fixed) with io.popen() -- if HOST_WIN32 or (BSD and BSD.netbsd) or (not USE_POPEN) then -- local tmp= Loc_TmpName() local tmp1= tmp.."1" local tmp2= tmp.."2" local lookup= { [0]="", " > "..tmp1, " 2> "..tmp2, " 2> "..tmp2.." > "..tmp1 } rc= os.execute(cmd..lookup[quiet]) if quiet==1 or quiet==3 then sout= Loc_ReadAll(tmp1) end if quiet>=2 then serr= Loc_ReadAll(tmp2) end os.remove(tmp1) os.remove(tmp2) else ASSUME( USE_POPEN and (not HOST_WIN32) ) -- local tmp_fn if quiet>=2 then -- no stderr tmp_fn= Loc_TmpName() cmd= cmd.." 2> "..tmp_fn end -- We need some output to get the return code: -- cmd= cmd.." ; echo RC=$?" local f= io.popen( cmd ) ASSUME(f) -- seems to be valid, even if cmd failed local str= f:read'*a' f:close() -- By searching at the very end of the string, we avoid clashes -- with whatever the command itself spit out. -- local s1,s2= skip2( string.find( str, "(.*)RC=(%d+)%s*$" ) ) rc= ASSUME( tonumber(s2) ) if quiet==1 or quiet==3 then sout= s1 else io.write(s1) -- os.execute() would have shown the output end if tmp_fn then serr= Loc_ReadAll(tmp_fn) os.remove(tmp_fn) end end -- Remove terminating newline (if any) from stdout: eases one-line analysis -- if sout then sout= string.gsub( sout, "%s+$", "" ) -- remove any white space at the end end --print( cmd, rc, sout, serr ) return rc, sout,serr end ----- -- void= ParentDir( dir_name ) -- -- Returns name of the parent or 'nil' for root level. -- local function Loc_ParentDir( str ) -- local daddy -- Remove the terminating slash, see if there's any other left: -- ASSUME( Loc_IsSlashTerminated(str) ) daddy,_= Loc_PathSplit( string.sub( str, 1,-2 ) ) if WIN32 and daddy then -- special root detection if string.find( daddy, ".%:$" ) then return nil -- just drive name end end return daddy -- 'nil' for root end ----- -- void= Mkdir( dirname_str ) -- -- Makes sure that the target directory exists. -- Loc_Mkdir= function( dirname ) -- if not dirname then return end -- root met ASSUME( Loc_IsSlashTerminated(dirname) ) if not Loc_DirExists1( dirname ) then -- local cmd -- MSYS needs to use 'install' to create '/usr/..' -- Also others may be most effective with it? -- if HOST_WIN32_NATIVE then dirname= Loc_Win32Slashes(dirname) Loc_Mkdir( Loc_ParentDir(dirname) ) cmd= "mkdir "..Loc_QuoteIfSpaces( dirname ) -- elseif QNX then cmd= "mkdir "..Loc_QuoteIfSpaces(dirname) else cmd= "install -d "..Loc_QuoteIfSpaces(dirname) end local rc= Loc_Execute( cmd ) ASSUME( rc==0, "Unable to create '"..dirname.."'!" ) ASSUME( Loc_DirExists2( dirname ) ) -- should be there now end end ----- -- bool= Loc_CreateFile( fn_str, contents_str [,chmod_str] ) -- local function Loc_CreateFile( fn, str, chmod ) -- -- stupid MSYS, stupid..! if HOST_WIN32_MSYS and (string.sub(fn,1,1)=='/') then -- local tmp= Loc_TmpName("./") ASSUME( string.sub(tmp,1,1)~='/' ) Loc_CreateFile( tmp, str ) -- in current dir we can -- this did not work: --local cmd= "mv "..tmp.." "..Loc_QuoteIfSpaces(fn) local cmd= "install "..tmp.." "..Loc_QuoteIfSpaces(fn).. " && rm "..tmp print(cmd) Loc_Execute( cmd, 0 ) -- DON'T use 'os.execute()' ASSUME( not Loc_FileExists(tmp) ) -- was moved away if chmod then -- 'install' gives rwxr-xr-x ASSUME( chmod=='+x', "chmod r/w not implemented for MSYS." ) end return true end -- Normal OSes (pheew...) -- local fh= io.open( fn, _OPEN_WT ) if not fh then return false -- unable to create! end fh:write( str ) fh:close() if chmod and (not HOST_WIN32) then -- chmod ignored on Win32 local cmd= "chmod "..chmod.." ".. Loc_QuoteIfSpaces(fn) local rc= Loc_Execute( cmd ) ASSUME(rc==0) end return true end ----- -- Loc_UpdateFile( fn_str, contents_str ) -- -- Creates a file if it doesn't exist, or if its contents differ -- from those required. -- local function Loc_UpdateFile( fn, str ) -- local fh= io.open( fn, "r" ) if fh then -- File exists, are contents the same? -- local str2= fh:read'*a' fh:close() if str2==str then return -- was the same (no write necessary) end end local ok= Loc_CreateFile( fn, str ) ASSUME(ok) end ----- -- str= Loc_CopyCmd( target_str, source_str [,update_bool] ) -- local function Loc_CopyCmd( target, source, update ) -- local cmd if HOST_WIN32_NATIVE then -- without the '>nul', xcopy says "1 File(s) copied" all over the place! cmd= update and "xcopy /RYD >nul" or "xcopy /RY >nul" elseif update then if LINUX or HOST_WIN32 then cmd= "cp -u" -- Linux, MSYS.. else -- Darwin, BSD.. error( "Don't know how to 'copy update' on "..HOST.."!" ) end else -- regular copy cmd= "cp" end cmd= cmd.." "..Loc_QuoteIfSpaces( Loc_Win32Slashes(source) ).. " "..Loc_QuoteIfSpaces( Loc_Win32Slashes(target) ) return cmd end ----- -- str= Loc_RemoveCmd( filename_str ) -- local function Loc_RemoveCmd( fname ) -- fname= Loc_QuoteIfSpaces(fname) return HOST_WIN32_NATIVE and ("del "..Loc_Win32Slashes(fname)) or ("rm "..fname) end ----- -- void= Loc_ForcedRemove( filename_str [,echo_bool] ) -- local function Loc_ForcedRemove( fn, echo ) -- if not Loc_FileExists(fn) then return end if echo then -- not the command we use, but does not matter.. :) print( Loc_RemoveCmd(fn) ) end local err= second( os.remove(fn) ) if err then error( "removing '"..fn.."' failed: "..err ) end end ----- -- str= Loc_MkdirCmd( path_str ) -- local function Loc_MkdirCmd( path ) -- ASSUME( Loc_IsSlashTerminated(path) ) path= string.sub(path,1,-2) -- eat terminating slash -- MSYS seems to need hyphens at least around dotted pathnames (use them always) -- if HOST_WIN32_MSYS then return "mkdir \""..path.."\"" else return "mkdir "..Loc_QuoteIfSpaces( Loc_Win32Slashes(path) ) end end ----- -- str= Loc_RmdirCmd( path_str ) -- local function Loc_RmdirCmd( path ) -- ASSUME( Loc_IsSlashTerminated(path) ) return (HOST_WIN32_NATIVE and "rmdir /S /Q" or "rm -rf").. -- oops, be careful..! " "..Loc_QuoteIfSpaces( Loc_Win32Slashes( string.sub(path,1,-2) ) ) end ----- -- str= Loc_GetCWD() -- -- Returns the current working directory. -- local function Loc_GetCWD() -- if HOST_WIN32_NOMSYS then error "Not implemented for Win32." else str= Loc_ShellCommand( "pwd" ) ASSUME(str) return str end end ----- -- tbl= Loc_DirCmd( cmd_str, dirname_str ) -- local function Loc_DirCmd( cmd, dirname ) -- local rc,str= Loc_Execute( cmd.." "..Loc_QuoteIfSpaces(dirname), 1 ) if rc~=0 then return {} end ASSUME(str) -- str="" means no files return Loc_Lines(str) end ----- -- [tbl]= Loc_DirList1( dirname_str [,dirs_only_bool] ) -- -- Returns a list of the contents of the directory (or 'nil' if no -- such directory exists). -- -- Directory entries are marked with a terminating slash ('/'). -- -- Note: For consistancy (and portability of applications) even WIN32 -- paths are returned using forward slashes ('/'). -- local function Loc_DirList1( dirname, dirs_only ) -- dirname= Loc_MakeSlashTerminated(dirname) if not Loc_DirExists1(dirname) then return nil end local ret= {} if HOST_WIN32_NATIVE then -- -- "/AD" limits to directories only -- "/B" causes no headers & footers, just filenames (is it already in Win95?) -- local lookup= {} dirname= Loc_Win32Slashes(dirname) local tbl= Loc_DirCmd( "dir /AD /B", dirname ) -- only dirs -- Add slash to every dir line -- for i,v in ipairs(tbl) do table.insert( ret, (string.gsub(v,'\\','/')..'/') ) lookup[v]= true end if not dirs_only then -- Add non-dirs, too? -- tbl= Loc_DirCmd( "dir /B", dirname ) -- dirs & files for i,v in ipairs(tbl) do if not lookup[v] then -- not in the dirs table table.insert( ret, (string.gsub(v,'\\','/')) ) end end end else -- Linux, OS X, BSD, Win32/MSYS... if HOST_WIN32_MSYS then dirname= Loc_Win32PathToMSYS( dirname ) end -- "-1" causes all output in a single column -- "-F" adds slash after directory names (and some more postfixes that -- we might need to filter away) -- ret= Loc_TblCopy_i( Loc_DirCmd( "ls -1 -F", dirname ), function(v) -- filter local c= string.sub(v,-1) -- last char if v=="./" or v=="../" then -- QNX lists these (skip) return nil elseif c=='/' then return v -- dirs are always welcome :) elseif dirs_only then return nil -- skip non-dir entries away elseif c=='*' then -- executables return string.sub( v, 1,-2 ) elseif c=='@' then -- symbolic links return nil else return v end end ) end return ret end ----- -- bool= MatchMask( fn_str, mask_str ) -- -- Notes: 'fn' may (or may not) contain a path part (which is ignored). -- 'mask' follows the normal OS-level '*' and '?' syntax. -- local function Loc_MatchMask( fn, mask ) -- local fn= Loc_FilenameOnly( fn ) if not fn then return false end -- "dirname/" (not a file) -- change the mask so it can be used with 'string.find()' -- mask= string.gsub( mask, '%.', '%%.' ) -- '.' -> '%.' mask= string.gsub( mask, '*', '%.+' ) -- '*' -> '.+' mask= string.gsub( mask, '?', '.' ) -- '?' -> '.' if WIN32 then -- ignore case on Win32 fn= string.upper(fn) mask= string.upper(mask) end return string.find( fn, "^"..mask.."$" ) and true or false end ASSUME( Loc_MatchMask( "mypath/myfile.bat", "*.bat" ) ) ASSUME( Loc_MatchMask( "mypath\\myfile.bat", "*.bat" ) ) ASSUME( Loc_MatchMask( "anything", "*" ) ) ASSUME( not Loc_MatchMask( "somthing.dot", "*.bat" ) ) ASSUME( not Loc_MatchMask( "somthing.dot.more", "*.dot" ) ) ----- -- [tbl]= DirList2( path_and_mask_str [,recurse_limit [,dot_char]] ) -- -- List directory contents recursively, only filenames returned. -- -- 'recurse_limit'=0: no recursion, same as 'DirList()' -- =1: dive in once, no more. -- default: recurse endlessly -- -- 'dot_char': character to print to console while collecting data. -- local function Loc_DirList2( path_and_mask, recurse_limit, dot ) -- local tbl= nil local path, mask= Loc_PathSplit( path_and_mask ) path= path or "" mask= mask or "*" if dot then -- getting here recursively gives many dots.. io.stderr:write(dot) end local tmp= Loc_DirList1(path) -- both files & dirs if not tmp then return nil end for _,v in ipairs(tmp) do -- 1..N (keep order) local entry= nil if string.sub( v, -1 ) == '/' then -- a dir? if recurse_limit==0 then -- done, no more diving.. skip. else entry= Loc_DirList2( path..v..mask, -- recurse recurse_limit and (recurse_limit-1), dot ) end else if Loc_MatchMask( v, mask ) then entry= path..v end end if entry then tbl= tbl or {} table.insert( tbl, entry ) end end return Loc_TblFlatten(tbl) end ----- -- bool= CopyFile( target_str, source_str [,opts_tbl] ) -- -- Instantly copy a file from 'source' to 'target'. -- -- Params: 'source' is a valid filename (that should exist) -- 'target' is a filename or dirname (slash terminated) -- 'opts_tbl.echo' true/false -- 'opts_tbl.update' true/false -- -- Returns: 'true' if the operation succeeded -- 'false' if not (no such file, no privileges, ...) -- local function Loc_CopyFile( target, source, opts ) -- opts= opts or {} -- Note: If 'target' is a dirname, we don't need to merge the -- strings since copy (and cp) will do allright. -- local cmd= Loc_CopyCmd( target, source, opts.update ) if opts.echo then --print(cmd) print( " "..Loc_Win32Slashes(source).." -> "..Loc_Win32Slashes(target) ) -- "cp -v" format (almost) end -- Create the target dir if it's in our power.. -- if not Loc_DirExists3( Loc_PathOnly(target), true ) then return false -- couldn't create! end -- Avoid 'os.execute' for MSYS, since it won't see the Unix-like paths. -- local rc= Loc_Execute( cmd, 1 ) -- quiet, but show error messages return (rc==0) end --== EXTRAS ==-- ----- -- [tbl]= ConfigFlags( prog_str, flags_str ) -- -- Usage: i.e. 'CPPFLAGS= ConfigFlags( "sdl", "--cflags" )' -- -- Note: On some systems (NetBSD, at least) "pkg-config" is used instead -- of separate config shell scripts (this keeps /usr/bin cleaner). -- We do transparent support for this - callers don't need to know. -- local function Loc_ConfigFlags_( prog, flags ) -- local rc, sout= Loc_Execute( prog.."-config "..flags, 3 ) -- ssh.. if rc~=0 then -- failed to run - perhaps 'pkg-config' will work? -- -- For pkg-config, "--version" gives its version (who cares?) and "--modversion" -- the modules'. -- flags= string.gsub( flags, "%-%-version", "%-%-modversion" ) cmd= "pkg-config "..prog.." "..flags rc, sout= Loc_Execute( cmd, 3 ) -- again, ssh.. :) if rc~=0 then -- still no luck? return nil end end local ret= {} for l in string.gfind(sout,"[^%s]+") do table.insert( ret, l ) end -- If only a single value (s.a. "1.2.8") return as string -- if not ret[2] then return ret[1] -- pure meat :) else return ret end end local config_cache= {} -- speed up by remembering local function Loc_ConfigFlags( prog, flags ) -- ASSUME( flags, "Hey, you forgot the flags! (old style now depricated)" ) local key= prog.."-"..flags if config_cache[key]~=nil then return config_cache[key] -- we'd asked that before! else local ret= Loc_ConfigFlags_(prog,flags) -- real work config_cache[key]= ret or false return ret end end ----- -- [fn_tbl]= Loc_FindRuntime( filename_str/tbl ) -- -- Returns the dependencies (library names) of 'filename_str' that are NOT -- part of system defaults. -- local function Loc_FindRuntime( files, _lookup ) -- _lookup= _lookup or {} local ret= nil if type(files)=="table" then -- wrapper to eliminate common entries -- for _,fn in Loc_TblFlatten(files) do -- local tmp= Loc_FindRuntime(fn,_lookup) if tmp then ret= ret or {} table.insert( ret, tmp ) end end return Loc_TblFlatten(ret) end local fn= files ASSUME( type(fn)=="string" ) ASSUME( LINUX or DARWIN or BSD, "Unknown OS!" ) local cmd= ( (DARWIN and "otool -L") or "ldd" ).." "..fn -- Linux: " libm.so.6 => /lib/libm.so.6 (0x40346000)" -- NetBSD: " -lSDL_mixer-1.2.2 => /usr/pkg/lib/libSDL_mixer-1.2.so.2" -- Darwin: " /usr/local/lib/libaedGUI-0.1.8a.1.dylib (compatibility ver..." -- FreeBSD: (same as Linux) -- --[[ local pattern= ( LINUX and ".+ => (.-) %(" ) or --( BSD and ".+ => (.+)" ) or ( NETBSD and ".+ => (.+)" ) or ( FREEBSD and ".+ => (.-) %(" ) or ( DARWIN and "%s+(.-) %(" ) ]]-- local pattern= DARWIN and "%s+(.-) %(" or ".+ =>%s+(.-)[%s$]" -- Linux, *BSD, ... (assuming no spaces in filename) local rc,sout= Loc_Execute( cmd, 1 ) ASSUME( rc==0, "Unable to run: "..cmd ) for _,line in Loc_Lines(sout) do -- local fn2= third( string.find( line, pattern ) ) if fn2 then -- if DARWIN then -- Darwin: "/System/Library/Frameworks/..." and -- "/usr/lib/..." (i.e. libSystem, libz) are always there. -- if string.find( fn2, "^/System/Library/Frameworks/" ) or string.find( fn2, "^/usr/lib/" ) then fn2= nil -- skip end elseif BSD then if string.find( fn2, "^/usr/lib/" ) or string.find( fn2, "^/usr/X11R6" ) then fn2= nil -- skip end elseif LINUX then -- Linux: "/lib/" (i.e. libpthread, libc, libm, ..) -- if string.find( fn2, "^/lib/" ) then fn2= nil -- skip end else ASSERT( false ) -- Who's here?! end if fn2 and (not _lookup[fn2]) then -- ret= ret or {} table.insert( ret, fn2 ) _lookup[fn2]= true -- don't repeat this one if DARWIN then -- recursive dependencies? local tmp= Loc_FindRuntime( fn2, _lookup ) if tmp then table.insert( ret, tmp ) end end end end end return Loc_TblFlatten(ret) -- Darwin may have subtables end ----- -- bool= CreateZipArchive( target_str, source_str/tbl ) -- -- Create a .zip archive if necessary tools are available. -- local function Loc_CreateZipArchive( target, source ) -- target= Loc_EscSpaces(target) source= Loc_TblToStr( source, ' ', true ) -- esc spaces -> '\ ' local cmd if DARWIN then -- Requires StuffIt command line utilities (commercial) cmd= "stuff -f zip -o -n "..target.." "..source else error "CreateZipArchive currently not implemented for this OS." end print(cmd) local rc= os.execute( cmd ) return (rc==0) -- true/false end ----- -- bool= CreateDmgArchive( target_str, source_str [,opts_tbl] ) -- -- Create an OS X .dmg disk image. -- -- 'source': Directory name, where contents are copied from. -- -- 'opts': -- .size (uint) -- MAXIMUM size for the files to be copied (in MB). -- Use something like 2x the real size, since the image -- will be compressed anyways, and empty bits won't take -- any space. -- -- .label (str) -- Volume label, visible when the image is opened. -- -- .rsrc (bool=true) -- Copy resource forks (icon position etc.) too? -- -- The output image file will be in UDZO format (gzip compressed). -- local function Loc_CreateDmgArchive( target, source_dir, opts ) -- local cmd, rc, sout, my_dev ASSUME( DARWIN, "CreateDmgArchive only for OS X." ) ASSUME( Loc_DirExists1( source_dir ), "No such dir: "..source_dir ) opts= opts or {} local size= opts.size or 10 -- MB local label= opts.label or "NoName" -- must have some label local format= "HFS+" local rsrc= not (opts.rsrc==false) -- nil -> true if not string.find(target,"%.dmg$") then target= target..".dmg" end target= Loc_EscSpaces(target) Loc_ForcedRemove( target ) local tmp_target= string.gsub( target, "%.dmg", "%.tmp%.dmg" ) -- uncompressed, temporary dmg local label_esc= Loc_EscSpaces(label) -- Gracefully remove old tempfile, if any (may happen if script is broken -- and then restarted). -- if Loc_FileExists(tmp_target) then os.remove(tmp_target) ASSUME( not Loc_FileExists(tmp_target), "Unable to remove: "..tmp_target ) end -- print "\n* Creating r/w (uncompressed) disk image..." cmd= "hdiutil create -fs "..format.. " -size "..size.."m".. " -volname "..label_esc.. " "..tmp_target print(cmd) rc= os.execute(cmd) ASSUME(rc==0) -- print "\n* Mounting the image..." cmd= "hdid "..tmp_target print(cmd) local rc,sout= Loc_Execute(cmd,1) -- catch stdout -- lines: "/dev/disk2s2 Apple_HFS /Volumes/VolName" -- for _,v in Loc_Lines(out) do --print(v) local dev,vol= skip2 (string.find( v, "([%w_/]+)%s.+/Volumes/(.+)" )) if vol==label then my_dev= dev -- this is important! (now we know the device node) end end ASSUME( my_dev, "Unable to find the device node!" ) -- print "\n* Copying contents.." cmd= "ditto "..(rsrc and "-rsrcFork " or "")..source_dir.." /Volumes/"..label_esc print(cmd) rc= os.execute(cmd) ASSUME(rc==0) -- print "\n* Unmounting the image.." cmd= "hdiutil detach "..my_dev -- Note: keep a little pause here, to avoid "failed to unmount due to error 16 (dissenter -1)" -- error messages. (could also do a retry loop?) -- Loc_WaitSec() -- 1-2 sec pause print(cmd) rc= os.execute(cmd) ASSUME(rc==0) -- print "\n* Compressing the image.." cmd= "hdiutil convert -format UDZO -o "..target.." "..tmp_target print(cmd) rc= os.execute(cmd) ASSUME(rc==0) Loc_ForcedRemove( tmp_target, true ) -- We're done! ;) -- ASSUME( Loc_FileExists(target) ) return true end -- local function Loc_RemoveDuplicates( tbl ) -- local lookup={} local ret= {} if not tbl then return nil end ASSUME( Loc_IsFlatTableWithoutNils(tbl) ) for _,v in ipairs(tbl) do if lookup[v] then -- just skip (is a duplicate) else lookup[v]= true table.insert( ret, v ) end end return ret end --- -- Public API, used either by Hamster and/or third party applications -- linking to 'hamster_libs'. -- m.FileExists= ASSUME( Loc_FileExists ) m.DirExists= ASSUME( Loc_DirExists3 ) m.MakeSlashTerminated= ASSUME( Loc_MakeSlashTerminated ) m.PathAsTable= ASSUME( Loc_PathAsTable ) m.RemoveDuplicates= ASSUME( Loc_RemoveDuplicates ) m.TblFlatten= ASSUME( Loc_TblFlatten ) m.IsEmptyTable= ASSUME( Loc_IsEmptyTable ) m.IsFlatTableWithoutNils= ASSUME( Loc_IsFlatTableWithoutNils ) m.IdenticalContents= ASSUME( Loc_IdenticalContents ) m.CleanDuplicates_onsite= ASSUME( Loc_CleanDuplicates_onsite ) m.TblSplit= ASSUME( Loc_TblSplit ) m.PathSplit= ASSUME( Loc_PathSplit ) m.DirList= ASSUME( Loc_DirList1 ) m.TmpName= ASSUME( Loc_TmpName ) m.GetCWD= ASSUME( Loc_GetCWD ) m.MatchMask= ASSUME( Loc_MatchMask ) m.DirList2= ASSUME( Loc_DirList2 ) m.CreateFile= ASSUME( Loc_CreateFile ) m.UpdateFile= ASSUME( Loc_UpdateFile ) m.CopyFile= ASSUME( Loc_CopyFile ) m.FindFileInPath= ASSUME( Loc_FindFileInPath ) m.CreateZipArchive= ASSUME( Loc_CreateZipArchive ) m.Execute= ASSUME( Loc_Execute ) m.ConfigFlags= ASSUME( Loc_ConfigFlags ) m.CreateDmgArchive= ASSUME( Loc_CreateDmgArchive ) m.TblCopy= ASSUME( Loc_TblCopy ) m.TblCopy_i= ASSUME( Loc_TblCopy_i ) m.FindRuntime= ASSUME( Loc_FindRuntime ) m.FilenameOnly= ASSUME( Loc_FilenameOnly ) m.PathOnly= ASSUME( Loc_PathOnly ) m.StripSuffix= ASSUME( Loc_StripSuffix ) m.Win32Slashes= ASSUME( Loc_Win32Slashes ) m.RemoveTerminatingSlash= ASSUME( Loc_RemoveTerminatingSlash ) m.QuoteIfSpaces= ASSUME( Loc_QuoteIfSpaces ) m.EscSpaces= ASSUME( Loc_EscSpaces ) m.RemoveCmd= ASSUME( Loc_RemoveCmd ) m.TblToStr= ASSUME( Loc_TblToStr ) m.IsSlashTerminated= ASSUME( Loc_IsSlashTerminated ) m.CopyCmd= ASSUME( Loc_CopyCmd ) m._OPEN_RT= ASSUME( _OPEN_RT ) m._OPEN_WT= ASSUME( _OPEN_WT ) m.ShellCommand= function(cmd) -- backwards compatibility (why change customer scripts?) -- local rc,sout= Loc_Execute(cmd,1) if rc~=0 then return nil else return sout end end if not rawget(_G,"arg") then -- arg==nil: loaded as '-l hamster_libs' rawset(_G,"hamster",m) else return m -- loaded with 'dofile' end