1-- 2-- os.lua 3-- Additions to the OS namespace. 4-- Copyright (c) 2002-2014 Jason Perkins and the Premake project 5-- 6 7 8--- 9-- Extend Lua's built-in os.execute() with token expansion and 10-- path normalization. 11-- 12 13 premake.override(os, "execute", function(base, cmd) 14 cmd = os.translateCommands(cmd) 15 return base(cmd) 16 end) 17 18 19 20--- 21-- Same as os.execute(), but accepts string formatting arguments. 22--- 23 24 function os.executef(cmd, ...) 25 cmd = string.format(cmd, ...) 26 return os.execute(cmd) 27 end 28 29 30 31-- 32-- Scan the well-known system locations for a particular library. 33-- 34 35 local function parse_ld_so_conf(conf_file) 36 -- Linux ldconfig file parser to find system library locations 37 local first, last 38 local dirs = { } 39 for line in io.lines(conf_file) do 40 -- ignore comments 41 first = line:find("#", 1, true) 42 if first ~= nil then 43 line = line:sub(1, first - 1) 44 end 45 46 if line ~= "" then 47 -- check for include files 48 first, last = line:find("include%s+") 49 if first ~= nil then 50 -- found include glob 51 local include_glob = line:sub(last + 1) 52 local includes = os.matchfiles(include_glob) 53 for _, v in ipairs(includes) do 54 dirs = table.join(dirs, parse_ld_so_conf(v)) 55 end 56 else 57 -- found an actual ld path entry 58 table.insert(dirs, line) 59 end 60 end 61 end 62 return dirs 63 end 64 65 local function get_library_search_path() 66 local path 67 if os.istarget("windows") then 68 path = os.getenv("PATH") or "" 69 elseif os.istarget("haiku") then 70 path = os.getenv("LIBRARY_PATH") or "" 71 else 72 if os.istarget("darwin") then 73 path = os.getenv("DYLD_LIBRARY_PATH") or "" 74 else 75 path = os.getenv("LD_LIBRARY_PATH") or "" 76 77 for _, prefix in ipairs({"", "/opt"}) do 78 local conf_file = prefix .. "/etc/ld.so.conf" 79 if os.isfile(conf_file) then 80 for _, v in ipairs(parse_ld_so_conf(conf_file)) do 81 if (#path > 0) then 82 path = path .. ":" .. v 83 else 84 path = v 85 end 86 end 87 end 88 end 89 end 90 91 path = path or "" 92 local archpath = "/lib:/usr/lib:/usr/local/lib" 93 if os.is64bit() and not (os.istarget("darwin")) then 94 archpath = "/lib64:/usr/lib64/:usr/local/lib64" .. ":" .. archpath 95 end 96 if (#path > 0) then 97 path = path .. ":" .. archpath 98 else 99 path = archpath 100 end 101 end 102 103 return path 104 end 105 106 function os.findlib(libname, libdirs) 107 -- libname: library name with or without prefix and suffix 108 -- libdirs: (array or string): A set of additional search paths 109 110 local path = get_library_search_path() 111 local formats 112 113 -- assemble a search path, depending on the platform 114 if os.istarget("windows") then 115 formats = { "%s.dll", "%s" } 116 elseif os.istarget("haiku") then 117 formats = { "lib%s.so", "%s.so" } 118 else 119 if os.istarget("darwin") then 120 formats = { "lib%s.dylib", "%s.dylib" } 121 else 122 formats = { "lib%s.so", "%s.so" } 123 end 124 125 table.insert(formats, "%s") 126 end 127 128 local userpath = "" 129 130 if type(libdirs) == "string" then 131 userpath = libdirs 132 elseif type(libdirs) == "table" then 133 userpath = table.implode(libdirs, "", "", ":") 134 end 135 136 if (#userpath > 0) then 137 if (#path > 0) then 138 path = userpath .. ":" .. path 139 else 140 path = userpath 141 end 142 end 143 144 for _, fmt in ipairs(formats) do 145 local name = string.format(fmt, libname) 146 local result = os.pathsearch(name, path) 147 if result then return result end 148 end 149 end 150 151 function os.findheader(headerpath, headerdirs) 152 -- headerpath: a partial header file path 153 -- headerdirs: additional header search paths 154 155 local path = get_library_search_path() 156 157 -- replace all /lib by /include 158 path = path .. ':' 159 path = path:gsub ('/lib[0-9]*([:/])', '/include%1') 160 path = path:sub (1, #path - 1) 161 162 local userpath = "" 163 164 if type(headerdirs) == "string" then 165 userpath = headerdirs 166 elseif type(headerdirs) == "table" then 167 userpath = table.implode(headerdirs, "", "", ":") 168 end 169 170 if (#userpath > 0) then 171 if (#path > 0) then 172 path = userpath .. ":" .. path 173 else 174 path = userpath 175 end 176 end 177 178 local result = os.pathsearch (headerpath, path) 179 return result 180 end 181 182-- 183-- Retrieve the current target operating system ID string. 184-- 185 186 function os.target() 187 return _OPTIONS.os or _TARGET_OS 188 end 189 190 function os.get() 191 local caller = filelineinfo(2) 192 premake.warnOnce(caller, "os.get() is deprecated, use 'os.target()' or 'os.host()'.\n @%s\n", caller) 193 return os.target() 194 end 195 196 -- deprecate _OS 197 _G_metatable = { 198 __index = function(t, k) 199 if (k == '_OS') then 200 premake.warnOnce("_OS+get", "_OS is deprecated, use '_TARGET_OS'.") 201 return rawget(t, "_TARGET_OS") 202 else 203 return rawget(t, k) 204 end 205 end, 206 207 __newindex = function(t, k, v) 208 if (k == '_OS') then 209 premake.warnOnce("_OS+set", "_OS is deprecated, use '_TARGET_OS'.") 210 rawset(t, "_TARGET_OS", v) 211 else 212 rawset(t, k, v) 213 end 214 end 215 } 216 setmetatable(_G, _G_metatable) 217 218 219 220-- 221-- Check the current target operating system; may be set with the /os command line flag. 222-- 223 224 function os.istarget(id) 225 local tags = os.getSystemTags(os.target()) 226 return table.contains(tags, id:lower()) 227 end 228 229 function os.is(id) 230 local caller = filelineinfo(2) 231 premake.warnOnce(caller, "os.is() is deprecated, use 'os.istarget()' or 'os.ishost()'.\n @%s\n", caller) 232 return os.istarget(id) 233 end 234 235 236-- 237-- Check the current host operating system. 238-- 239 240 function os.ishost(id) 241 local tags = os.getSystemTags(os.host()) 242 return table.contains(tags, id:lower()) 243 end 244 245 246--- 247-- Determine if a directory exists on the file system, and that it is a 248-- directory and not a file. 249-- 250-- @param p 251-- The path to check. 252-- @return 253-- True if a directory exists at the given path. 254--- 255 256 premake.override(os, "isdir", function(base, p) 257 p = path.normalize(p) 258 return base(p) 259 end) 260 261 262 263--- 264-- Determine if a file exists on the file system, and that it is a 265-- file and not a directory. 266-- 267-- @param p 268-- The path to check. 269-- @return 270-- True if a file exists at the given path. 271--- 272 273 premake.override(os, "isfile", function(base, p) 274 p = path.normalize(p) 275 return base(p) 276 end) 277 278 279 280-- 281-- Determine if the current system is running a 64-bit architecture. 282-- 283 284 local _is64bit 285 286 local _64BitHostTypes = { 287 "x86_64", 288 "ia64", 289 "amd64", 290 "ppc64", 291 "powerpc64", 292 "sparc64" 293 } 294 295 function os.is64bit() 296 -- This can be expensive to compute, so cache and reuse the response 297 if _is64bit ~= nil then 298 return _is64bit 299 end 300 301 _is64bit = false 302 303 -- Call the native code implementation. If this returns true then 304 -- we're 64-bit, otherwise do more checking locally 305 if (os._is64bit()) then 306 _is64bit = true 307 else 308 -- Identify the system 309 local arch 310 if os.ishost("windows") then 311 arch = os.getenv("PROCESSOR_ARCHITECTURE") 312 elseif os.ishost("macosx") then 313 arch = os.outputof("echo $HOSTTYPE") 314 else 315 arch = os.outputof("uname -m") 316 end 317 318 -- Check our known 64-bit identifiers 319 arch = arch:lower() 320 for _, hosttype in ipairs(_64BitHostTypes) do 321 if arch:find(hosttype) then 322 _is64bit = true 323 end 324 end 325 end 326 327 return _is64bit 328 end 329 330 331--- 332-- Perform a wildcard search for files and directories. 333-- 334-- @param mask 335-- The file search pattern. Use "*" to match any part of a file or 336-- directory name, "**" to recurse into subdirectories. 337-- @return 338-- A table containing the matched file or directory names. 339--- 340 341 function os.match(mask) 342 mask = path.normalize(mask) 343 local starpos = mask:find("%*") 344 local before = path.getdirectory(starpos and mask:sub(1, starpos - 1) or mask) 345 local slashpos = starpos and mask:find("/", starpos) 346 local after = slashpos and mask:sub(slashpos + 1) 347 348 -- Only recurse for path components starting with '**': 349 local recurse = starpos and 350 mask:sub(starpos + 1, starpos + 1) == '*' and 351 (starpos == 1 or mask:sub(starpos - 1, starpos - 1) == '/') 352 353 local results = { } 354 355 if recurse then 356 local submask = mask:sub(1, starpos) .. mask:sub(starpos + 2) 357 results = os.match(submask) 358 359 local pattern = mask:sub(1, starpos) 360 local m = os.matchstart(pattern) 361 while os.matchnext(m) do 362 if not os.matchisfile(m) then 363 local matchpath = path.join(before, os.matchname(m), mask:sub(starpos)) 364 results = table.join(results, os.match(matchpath)) 365 end 366 end 367 os.matchdone(m) 368 else 369 local pattern = mask:sub(1, slashpos and slashpos - 1) 370 local m = os.matchstart(pattern) 371 while os.matchnext(m) do 372 if not (slashpos and os.matchisfile(m)) then 373 local matchpath = path.join(before, matchpath, os.matchname(m)) 374 if after then 375 results = table.join(results, os.match(path.join(matchpath, after))) 376 else 377 table.insert(results, matchpath) 378 end 379 end 380 end 381 os.matchdone(m) 382 end 383 384 return results 385 end 386 387 388--- 389-- Perform a wildcard search for directories. 390-- 391-- @param mask 392-- The search pattern. Use "*" to match any part of a directory 393-- name, "**" to recurse into subdirectories. 394-- @return 395-- A table containing the matched directory names. 396--- 397 398 function os.matchdirs(mask) 399 local results = os.match(mask) 400 for i = #results, 1, -1 do 401 if not os.isdir(results[i]) then 402 table.remove(results, i) 403 end 404 end 405 return results 406 end 407 408 409--- 410-- Perform a wildcard search for files. 411-- 412-- @param mask 413-- The search pattern. Use "*" to match any part of a file 414-- name, "**" to recurse into subdirectories. 415-- @return 416-- A table containing the matched directory names. 417--- 418 419 function os.matchfiles(mask) 420 local results = os.match(mask) 421 for i = #results, 1, -1 do 422 if not os.isfile(results[i]) then 423 table.remove(results, i) 424 end 425 end 426 return results 427 end 428 429-- 430-- An overload of the os.mkdir() function, which will create any missing 431-- subdirectories along the path. 432-- 433 434 local builtin_mkdir = os.mkdir 435 function os.mkdir(p) 436 p = path.normalize(p) 437 438 local dir = iif(p:startswith("/"), "/", "") 439 for part in p:gmatch("[^/]+") do 440 dir = dir .. part 441 442 if (part ~= "" and not path.isabsolute(part) and not os.isdir(dir)) then 443 local ok, err = builtin_mkdir(dir) 444 if (not ok) then 445 return nil, err 446 end 447 end 448 449 dir = dir .. "/" 450 end 451 452 return true 453 end 454 455 456-- 457-- Run a shell command and return the output. 458-- 459-- @param cmd Command to execute 460-- @param streams Standard stream(s) to output 461-- Must be one of 462-- - "both" (default) 463-- - "output" Return standard output stream content only 464-- - "error" Return standard error stream content only 465-- 466 467 function os.outputof(cmd, streams) 468 cmd = path.normalize(cmd) 469 streams = streams or "both" 470 local redirection 471 if streams == "both" then 472 redirection = " 2>&1" 473 elseif streams == "output" then 474 redirection = " 2>/dev/null" 475 elseif streams == "error" then 476 redirection = " 2>&1 1>/dev/null" 477 else 478 error ('Invalid stream(s) selection. "output", "error", or "both" expected.') 479 end 480 481 local pipe = io.popen(cmd .. redirection) 482 local result = pipe:read('*a') 483 local success, what, code = pipe:close() 484 if success then 485 -- chomp trailing newlines 486 if result then 487 result = string.gsub(result, "[\r\n]+$", "") 488 end 489 490 return result, code, what 491 else 492 return nil, code, what 493 end 494 end 495 496 497-- 498-- @brief An overloaded os.remove() that will be able to handle list of files, 499-- as well as wildcards for files. Uses the syntax os.matchfiles() for 500-- matching pattern wildcards. 501-- 502-- @param f A file, a wildcard, or a list of files or wildcards to be removed 503-- 504-- @return true on success, false and an appropriate error message on error 505-- 506-- @example ok, err = os.remove{"**.bak", "**.log"} 507-- if not ok then 508-- error(err) 509-- end 510-- 511 512 local builtin_remove = os.remove 513 function os.remove(f) 514 -- in case of string, just match files 515 if type(f) == "string" then 516 local p = os.matchfiles(f) 517 for _, v in pairs(p) do 518 local ok, err, code = builtin_remove(v) 519 if not ok then 520 return ok, err, code 521 end 522 end 523 if #p == 0 then 524 return nil, "Couldn't find any file matching: " .. f, 1 525 end 526 -- in case of table, match files for every table entry 527 elseif type(f) == "table" then 528 for _, v in pairs(f) do 529 local ok, err, code = os.remove(v) 530 if not ok then 531 return ok, err, code 532 end 533 end 534 end 535 536 return true 537 end 538 539 540-- 541-- Remove a directory, along with any contained files or subdirectories. 542-- 543-- @return true on success, false and an appropriate error message on error 544 545 local builtin_rmdir = os.rmdir 546 function os.rmdir(p) 547 -- recursively remove subdirectories 548 local dirs = os.matchdirs(p .. "/*") 549 for _, dname in ipairs(dirs) do 550 local ok, err = os.rmdir(dname) 551 if not ok then 552 return ok, err 553 end 554 end 555 556 -- remove any files 557 local files = os.matchfiles(p .. "/*") 558 for _, fname in ipairs(files) do 559 local ok, err = os.remove(fname) 560 if not ok then 561 return ok, err 562 end 563 end 564 565 -- remove this directory 566 return builtin_rmdir(p) 567 end 568 569 570--- 571-- Return information about a file. 572--- 573 574 premake.override(os, "stat", function(base, p) 575 p = path.normalize(p) 576 return base(p) 577 end) 578 579 580 581--- 582-- Translate command tokens into their OS or action specific equivalents. 583--- 584 585 os.commandTokens = { 586 _ = { 587 chdir = function(v) 588 return "cd " .. path.normalize(v) 589 end, 590 copy = function(v) 591 return "cp -rf " .. path.normalize(v) 592 end, 593 copyfile = function(v) 594 return "cp -f " .. path.normalize(v) 595 end, 596 copydir = function(v) 597 return "cp -rf " .. path.normalize(v) 598 end, 599 delete = function(v) 600 return "rm -f " .. path.normalize(v) 601 end, 602 echo = function(v) 603 return "echo " .. v 604 end, 605 mkdir = function(v) 606 return "mkdir -p " .. path.normalize(v) 607 end, 608 move = function(v) 609 return "mv -f " .. path.normalize(v) 610 end, 611 rmdir = function(v) 612 return "rm -rf " .. path.normalize(v) 613 end, 614 touch = function(v) 615 return "touch " .. path.normalize(v) 616 end, 617 }, 618 windows = { 619 chdir = function(v) 620 return "chdir " .. path.translate(path.normalize(v)) 621 end, 622 copy = function(v) 623 v = path.translate(path.normalize(v)) 624 625 -- Detect if there's multiple parts to the input, if there is grab the first part else grab the whole thing 626 local src = string.match(v, '^".-"') or string.match(v, '^.- ') or v 627 628 -- Strip the trailing space from the second condition so that we don't have a space between src and '\\NUL' 629 src = string.match(src, '^.*%S') 630 631 return "IF EXIST " .. src .. "\\ (xcopy /Q /E /Y /I " .. v .. " > nul) ELSE (xcopy /Q /Y /I " .. v .. " > nul)" 632 end, 633 copyfile = function(v) 634 v = path.translate(path.normalize(v)) 635 -- XCOPY doesn't have a switch to assume destination is a file when it doesn't exist. 636 -- A trailing * will suppress the prompt but requires the file extensions be the same length. 637 -- Just use COPY instead, it actually works. 638 return "copy /B /Y " .. v 639 end, 640 copydir = function(v) 641 v = path.translate(path.normalize(v)) 642 return "xcopy /Q /E /Y /I " .. v 643 end, 644 delete = function(v) 645 return "del " .. path.translate(path.normalize(v)) 646 end, 647 echo = function(v) 648 return "echo " .. v 649 end, 650 mkdir = function(v) 651 v = path.translate(path.normalize(v)) 652 return "IF NOT EXIST " .. v .. " (mkdir " .. v .. ")" 653 end, 654 move = function(v) 655 return "move /Y " .. path.translate(path.normalize(v)) 656 end, 657 rmdir = function(v) 658 return "rmdir /S /Q " .. path.translate(path.normalize(v)) 659 end, 660 touch = function(v) 661 v = path.translate(path.normalize(v)) 662 return string.format("type nul >> %s && copy /b %s+,, %s", v, v, v) 663 end, 664 } 665 } 666 667 function os.translateCommands(cmd, map) 668 map = map or os.target() 669 if type(map) == "string" then 670 map = os.commandTokens[map] or os.commandTokens["_"] 671 end 672 673 local processOne = function(cmd) 674 local i, j, prev 675 repeat 676 i, j = cmd:find("{.-}") 677 if i then 678 if i == prev then 679 break 680 end 681 682 local token = cmd:sub(i + 1, j - 1):lower() 683 local args = cmd:sub(j + 2) 684 local func = map[token] or os.commandTokens["_"][token] 685 if func then 686 cmd = cmd:sub(1, i -1) .. func(args) 687 end 688 689 prev = i 690 end 691 until i == nil 692 return cmd 693 end 694 695 if type(cmd) == "table" then 696 local result = {} 697 for i = 1, #cmd do 698 result[i] = processOne(cmd[i]) 699 end 700 return result 701 else 702 return processOne(cmd) 703 end 704 end 705 706 707 708--- 709-- Apply os slashes for decorated command paths. 710--- 711 function os.translateCommandAndPath(dir, map) 712 if map == 'windows' then 713 return path.translate(dir) 714 end 715 return dir 716 end 717 718--- 719-- Translate decorated command paths into their OS equivalents. 720--- 721 function os.translateCommandsAndPaths(cmds, basedir, location, map) 722 local translatedBaseDir = path.getrelative(location, basedir) 723 724 map = map or os.target() 725 726 local translateFunction = function(value) 727 local result = path.join(translatedBaseDir, value) 728 result = os.translateCommandAndPath(result, map) 729 if value:endswith('/') or value:endswith('\\') or -- if orginal path ends with a slash then ensure the same 730 value:endswith('/"') or value:endswith('\\"') then 731 result = result .. '/' 732 end 733 return result 734 end 735 736 local processOne = function(cmd) 737 local replaceFunction = function(value) 738 value = value:sub(3, #value - 1) 739 return '"' .. translateFunction(value) .. '"' 740 end 741 return string.gsub(cmd, "%%%[[^%]\r\n]*%]", replaceFunction) 742 end 743 744 if type(cmds) == "table" then 745 local result = {} 746 for i = 1, #cmds do 747 result[i] = processOne(cmds[i]) 748 end 749 return os.translateCommands(result, map) 750 else 751 return os.translateCommands(processOne(cmds), map) 752 end 753 end 754 755 756-- 757-- Generate a UUID. 758-- 759 760 os._uuids = {} 761 762 local builtin_uuid = os.uuid 763 function os.uuid(name) 764 local id = builtin_uuid(name) 765 if name then 766 if os._uuids[id] and os._uuids[id] ~= name then 767 premake.warnOnce(id, "UUID clash between %s and %s", os._uuids[id], name) 768 end 769 os._uuids[id] = name 770 end 771 return id 772 end 773 774 775-- 776-- Get a set of tags for different 'platforms' 777-- 778 779 os.systemTags = 780 { 781 ["aix"] = { "aix", "posix" }, 782 ["bsd"] = { "bsd", "posix" }, 783 ["haiku"] = { "haiku", "posix" }, 784 ["ios"] = { "ios", "darwin", "posix", "mobile" }, 785 ["linux"] = { "linux", "posix" }, 786 ["macosx"] = { "macosx", "darwin", "posix" }, 787 ["solaris"] = { "solaris", "posix" }, 788 ["windows"] = { "windows", "win32" }, 789 } 790 791 function os.getSystemTags(name) 792 return os.systemTags[name:lower()] or { name:lower() } 793 end 794