Initial community commit

This commit is contained in:
Jef
2024-09-24 14:54:57 +02:00
parent 537bcbc862
commit 20d28e80a5
16810 changed files with 4640254 additions and 2 deletions

View File

@@ -0,0 +1,71 @@
--
-- _manifest.lua
-- Manage the list of built-in Premake scripts.
-- Copyright (c) 2002-2015 Jason Perkins and the Premake project
--
-- The master list of built-in scripts. Order is important! If you want to
-- build a new script into Premake, add it to this list.
return
{
-- core files
"base/_foundation.lua",
"base/string.lua",
"base/table.lua",
"base/path.lua",
"base/os.lua",
"base/io.lua",
"base/tools.lua",
"base/tree.lua",
"base/globals.lua",
"base/semver.lua",
"base/http.lua",
"base/json.lua",
"base/jsonwrapper.lua",
"base/languages.lua",
"base/term.lua",
-- configuration data
"base/field.lua",
"base/criteria.lua",
"base/detoken.lua",
"base/configset.lua",
"base/context.lua",
"base/container.lua",
-- runtime switches
"base/option.lua",
"base/action.lua",
-- project script setup
"base/api.lua",
-- project objects
"base/global.lua",
"base/workspace.lua",
"base/group.lua",
"base/project.lua",
"base/config.lua",
"base/fileconfig.lua",
"base/rule.lua",
-- project script processing
"base/oven.lua",
"base/validation.lua",
"base/premake.lua",
"base/help.lua",
-- tool APIs
"tools/dotnet.lua",
"tools/gcc.lua",
"tools/msc.lua",
"tools/snc.lua",
"tools/clang.lua",
"tools/mingw.lua",
-- Clean action
"actions/clean/_clean.lua",
"_premake_init.lua",
}

View File

@@ -0,0 +1,15 @@
--
-- _modules.lua
-- The list of core modules to preload on startup
-- Copyright (c) 2015 Jason Perkins and the Premake project
--
return {
"gmake",
"vstudio",
"xcode",
"codelite",
"gmake2",
"d",
"android",
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,398 @@
--
-- _premake_main.lua
-- Script-side entry point for the main program logic.
-- Copyright (c) 2002-2015 Jason Perkins and the Premake project
--
local shorthelp = "Type 'premake5 --help' for help"
local versionhelp = "premake5 (Premake Build Script Generator) %s"
local startTime = os.clock()
-- set a global.
_PREMAKE_STARTTIME = startTime
-- Load the collection of core scripts, required for everything else to work
local modules = dofile("_modules.lua")
local manifest = dofile("_manifest.lua")
for i = 1, #manifest do
dofile(manifest[i])
end
-- Create namespaces for myself
local p = premake
p.main = {}
local m = p.main
-- Keep a table of modules that have been preloaded, and their associated
-- "should load" test functions.
m._preloaded = {}
---
-- Add a new module loader that knows how to use the Premake paths like
-- PREMAKE_PATH and the --scripts option, and follows the module/module.lua
-- naming convention.
---
function m.installModuleLoader()
if not os.ishost('windows') then
local premakeDir = path.getdirectory(_PREMAKE_COMMAND)
package.cpath = package.cpath .. ';' .. premakeDir .. '/?.so'
end
table.insert(package.searchers, 2, m.moduleLoader)
end
function m.moduleLoader(name)
local dir = path.getdirectory(name)
local base = path.getname(name)
if dir ~= "." then
dir = dir .. "/" .. base
else
dir = base
end
local full = dir .. "/" .. base .. ".lua"
-- list of paths where to look for the module
local paths = {
".modules/" .. full,
"modules/" .. full,
full,
name .. ".lua"
}
-- If this module is being requested by an embedded script, favor embedded modules.
-- This helps prevent local scripts from interfering with release build bootstrapping.
if string.startswith(_SCRIPT_DIR, '$/') then
table.insert(paths, 1, '$/' .. full)
end
-- try to locate the module
for _, p in ipairs(paths) do
local file = os.locate(p)
if file then
local chunk, err = loadfile(file)
if chunk then
return chunk
end
if err then
return "\n\tload error " .. err
end
end
end
-- didn't find the module in supported paths, try the embedded scripts
for _, p in ipairs(paths) do
local chunk, err = loadfile("$/" .. p)
if chunk then
return chunk
end
end
end
---
-- Prepare the script environment; anything that should be done
-- before the system script gets a chance to run.
---
function m.prepareEnvironment()
math.randomseed(os.time())
_PREMAKE_DIR = path.getdirectory(_PREMAKE_COMMAND)
p.path = p.path .. ";" .. _PREMAKE_DIR .. ";" .. _MAIN_SCRIPT_DIR
end
---
-- Load the required core modules that are shipped as part of Premake and
-- expected to be present at startup. If a _preload.lua script is present,
-- that script is run and the return value (a "should load" test) is cached
-- to be called after baking is complete. Otherwise the module's main script
-- is run immediately.
---
function m.preloadModules()
for i = 1, #modules do
local name = modules[i]
local preloader = name .. "/_preload.lua"
preloader = os.locate("modules/" .. preloader) or os.locate(preloader)
if preloader then
local modulePath = path.getdirectory(preloader)
m._preloaded[modulePath] = include(preloader)
if not m._preloaded[modulePath] then
p.warn("module '%s' should return function from _preload.lua", name)
end
else
require(name)
end
end
end
---
-- Look for and run the system-wide configuration script; make sure any
-- configuration scoping gets cleared before continuing.
---
function m.runSystemScript()
dofileopt(_OPTIONS["systemscript"] or { "premake5-system.lua", "premake-system.lua" })
filter {}
end
---
-- Look for a user project script, and set up the related global
-- variables if I can find one.
---
function m.locateUserScript()
local defaults = { "premake5.lua", "premake4.lua" }
for i = 1, #defaults do
if os.isfile(defaults[i]) then
_MAIN_SCRIPT = defaults[i]
break
end
end
if not _MAIN_SCRIPT then
_MAIN_SCRIPT = defaults[1]
end
if _OPTIONS.file then
_MAIN_SCRIPT = _OPTIONS.file
end
_MAIN_SCRIPT = path.getabsolute(_MAIN_SCRIPT)
_MAIN_SCRIPT_DIR = path.getdirectory(_MAIN_SCRIPT)
end
---
-- Set the action to be performed from the command line arguments.
---
function m.prepareAction()
p.action.set(_ACTION)
-- Allow the action to initialize stuff.
local action = p.action.current()
if action then
p.action.initialize(action.trigger)
end
end
---
-- If there is a project script available, run it to get the
-- project information, available options and actions, etc.
---
function m.runUserScript()
if os.isfile(_MAIN_SCRIPT) then
dofile(_MAIN_SCRIPT)
end
end
---
-- Run the interactive prompt, if requested.
---
function m.checkInteractive()
if _OPTIONS.interactive then
debug.prompt()
end
end
---
-- Validate and process the command line options and arguments.
---
function m.processCommandLine()
-- Process special options
if (_OPTIONS["version"]) then
printf(versionhelp, _PREMAKE_VERSION)
os.exit(0)
end
if (_OPTIONS["help"]) then
p.showhelp()
os.exit(1)
end
-- Validate the command-line arguments. This has to happen after the
-- script has run to allow for project-specific options
ok, err = p.option.validate(_OPTIONS)
if not ok then
print("Error: " .. err)
os.exit(1)
end
-- If no further action is possible, show a short help message
if not _OPTIONS.interactive then
if not _ACTION then
print(shorthelp)
os.exit(1)
end
local action = p.action.current()
if not action then
print("Error: no such action '" .. _ACTION .. "'")
os.exit(1)
end
if p.action.isConfigurable() and not os.isfile(_MAIN_SCRIPT) then
print(string.format("No Premake script (%s) found!", path.getname(_MAIN_SCRIPT)))
os.exit(1)
end
end
end
---
-- Start up MobDebug and try to hook up with ZeroBrane
---
function m.tryHookDebugger()
if (_OPTIONS["debugger"]) then
print("Loading luasocket...")
require('luasocket')
print("Starting debugger...")
local mobdebug = require('mobdebug')
mobdebug.start()
end
end
---
-- Override point, for logic that should run before baking.
---
function m.preBake()
if p.action.isConfigurable() then
print("Building configurations...")
end
end
---
-- "Bake" the project information, preparing it for use by the action.
---
function m.bake()
if p.action.isConfigurable() then
p.oven.bake()
end
end
---
-- Override point, for logic that should run after baking but before
-- the configurations are validated.
---
function m.postBake()
local function shouldLoad(func)
for wks in p.global.eachWorkspace() do
for prj in p.workspace.eachproject(wks) do
for cfg in p.project.eachconfig(prj) do
if func(cfg) then
return true
end
end
end
end
end
-- any modules need to load to support this project?
for modulePath, func in pairs(m._preloaded) do
local moduleName = path.getbasename(modulePath)
if not package.loaded[moduleName] and shouldLoad(func) then
_SCRIPT_DIR = modulePath
require(moduleName)
end
end
end
---
-- Sanity check the current project setup.
---
function m.validate()
if p.action.isConfigurable() then
p.container.validate(p.api.rootContainer())
end
end
---
-- Override point, for logic that should run after validation and
-- before the action takes control.
---
function m.preAction()
local action = p.action.current()
printf("Running action '%s'...", action.trigger)
end
---
-- Hand over control to the action.
---
function m.callAction()
local action = p.action.current()
p.action.call(action.trigger)
end
---
-- Processing is complete.
---
function m.postAction()
if p.action.isConfigurable() then
local duration = math.floor((os.clock() - startTime) * 1000);
printf("Done (%dms).", duration)
end
end
--
-- Script-side program entry point.
--
m.elements = {
m.tryHookDebugger,
m.installModuleLoader,
m.locateUserScript,
m.prepareEnvironment,
m.preloadModules,
m.runSystemScript,
m.prepareAction,
m.runUserScript,
m.checkInteractive,
m.processCommandLine,
m.preBake,
m.bake,
m.postBake,
m.validate,
m.preAction,
m.callAction,
m.postAction,
}
function _premake_main()
p.callArray(m.elements)
return 0
end

View File

@@ -0,0 +1,21 @@
--
-- _clean.lua
-- The "clean" action: removes all generated files.
-- Copyright (c) 2002-2012 Jason Perkins and the Premake project
--
premake.clean = {}
--
-- Register the "clean" action.
--
newaction {
trigger = "clean",
description = "Remove all binaries and generated files",
execute = function()
print("** The clean action has not yet been ported")
end
}

View File

@@ -0,0 +1,438 @@
---
-- Base definitions required by all the other scripts.
-- @copyright 2002-2015 Jason Perkins and the Premake project
---
premake = premake or {}
premake._VERSION = _PREMAKE_VERSION
package.loaded["premake"] = premake
premake.modules = {}
premake.extensions = premake.modules
local semver = dofile('semver.lua')
local p = premake
-- Keep track of warnings that have been shown, so they don't get shown twice
local _warnings = {}
-- Keep track of aliased functions, so I can resolve to canonical names
local _aliases = {}
--
-- Define some commonly used symbols, for future-proofing.
--
premake.C = "C"
premake.C7 = "c7"
premake.CLANG = "clang"
premake.CONSOLEAPP = "ConsoleApp"
premake.CPP = "C++"
premake.CSHARP = "C#"
premake.GCC = "gcc"
premake.HAIKU = "haiku"
premake.ANDROID = "android"
premake.IOS = "ios"
premake.LINUX = "linux"
premake.MACOSX = "macosx"
premake.MAKEFILE = "Makefile"
premake.MBCS = "MBCS"
premake.NONE = "None"
premake.DEFAULT = "Default"
premake.OBJECTIVEC = "Objective-C"
premake.OBJECTIVECPP = "Objective-C++"
premake.ON = "On"
premake.OFF = "Off"
premake.POSIX = "posix"
premake.PS3 = "ps3"
premake.SHAREDITEMS = "SharedItems"
premake.SHAREDLIB = "SharedLib"
premake.STATICLIB = "StaticLib"
premake.UNICODE = "Unicode"
premake.UNIVERSAL = "universal"
premake.UTILITY = "Utility"
premake.PACKAGING = "Packaging"
premake.WINDOWEDAPP = "WindowedApp"
premake.WINDOWS = "windows"
premake.X86 = "x86"
premake.X86_64 = "x86_64"
premake.ARM = "ARM"
premake.ARM64 = "ARM64"
---
-- Provide an alias for a function in a namespace. Calls to the alias will
-- invoke the canonical function, and attempts to override the alias will
-- instead override the canonical call.
--
-- @param scope
-- The table containing the function to be overridden. Use _G for
-- global functions.
-- @param canonical
-- The name of the function to be aliased (a string value)
-- @param alias
-- The new alias for the function (another string value).
---
function p.alias(scope, canonical, alias)
scope, canonical = p.resolveAlias(scope, canonical)
if not scope[canonical] then
error("unable to alias '" .. canonical .. "'; no such function", 2)
end
_aliases[scope] = _aliases[scope] or {}
_aliases[scope][alias] = canonical
scope[alias] = function(...)
return scope[canonical](...)
end
end
---
-- Call a list of functions.
--
-- @param funcs
-- The list of functions to be called, or a function that can be called
-- to build and return the list. If this is a function, it will be called
-- with all of the additional arguments (below).
-- @param ...
-- An optional set of arguments to be passed to each of the functions as
-- as they are called.
---
function premake.callArray(funcs, ...)
if type(funcs) == "function" then
funcs = funcs(...)
end
if funcs then
for i = 1, #funcs do
funcs[i](...)
end
end
end
-- TODO: THIS IMPLEMENTATION IS GOING AWAY
function premake.callarray(namespace, array, ...)
local n = #array
for i = 1, n do
local fn = namespace[array[i]]
if not fn then
error(string.format("Unable to find function '%s'", array[i]))
end
fn(...)
end
end
---
-- Compare a version string that uses semver semantics against a
-- version comparision string. Comparisions take the form of ">=5.0" (5.0 or
-- later), "5.0" (5.0 or later), ">=5.0 <6.0" (5.0 or later but not 6.0 or
-- later).
--
-- @param version
-- The version to be tested.
-- @param checks
-- The comparision string to be evaluated.
-- @return
-- True if the comparisions pass, false if any fail.
---
function p.checkVersion(version, checks)
if not version then
return false
end
-- try to parse semver, if it fails, it's not semver compatible and we cannot compare, in which case
-- we're going to ignore the checkVersion entirely, but warn.
if not premake.isSemVer(version) then
p.warn("'" .. version .. "' is not semver compatible, and cannot be compared against '" .. checks .. "'.");
return true
end
-- now compare the semver against the checks.
local function eq(a, b) return a == b end
local function le(a, b) return a <= b end
local function lt(a, b) return a < b end
local function ge(a, b) return a >= b end
local function gt(a, b) return a > b end
local function compat(a, b) return a ^ b end
version = semver(version)
checks = string.explode(checks, " ", true)
for i = 1, #checks do
local check = checks[i]
local func
if check:startswith(">=") then
func = ge
check = check:sub(3)
elseif check:startswith(">") then
func = gt
check = check:sub(2)
elseif check:startswith("<=") then
func = le
check = check:sub(3)
elseif check:startswith("<") then
func = lt
check = check:sub(2)
elseif check:startswith("=") then
func = eq
check = check:sub(2)
elseif check:startswith("^") then
func = compat
check = check:sub(2)
else
func = ge
end
check = semver(check)
if not func(version, check) then
return false
end
end
return true
end
function premake.clearWarnings()
_warnings = {}
end
--
-- Raise an error, with a formatted message built from the provided
-- arguments.
--
-- @param message
-- The error message, which may contain string formatting tokens.
-- @param ...
-- Values to fill in the string formatting tokens.
--
function premake.error(message, ...)
error(string.format("** Error: " .. message, ...), 0)
end
--
-- Finds the correct premake script filename to be run.
--
-- @param fname
-- The filename of the script to run.
-- @return
-- The correct location and filename of the script to run.
--
function premake.findProjectScript(fname)
return os.locate(fname, fname .. ".lua", path.join(fname, "premake5.lua"), path.join(fname, "premake4.lua"))
end
---
-- "Immediate If" - returns one of the two values depending on the value
-- of the provided condition. Note that both the true and false expressions
-- will be evaluated regardless of the condition, even if only one result
-- is returned.
--
-- @param condition
-- A boolean condition, determining which value gets returned.
-- @param trueValue
-- The value to return if the condition is true.
-- @param falseValue
-- The value to return if the condition is false.
-- @return
-- One of trueValue or falseValue.
---
function iif(condition, trueValue, falseValue)
if condition then
return trueValue
else
return falseValue
end
end
---
-- Override an existing function with a new implementation; the original
-- function is passed as the first argument to the replacement when called.
--
-- @param scope
-- The table containing the function to be overridden. Use _G for
-- global functions.
-- @param name
-- The name of the function to override (a string value).
-- @param repl
-- The replacement function. The first argument to the function
-- will be the original implementation, followed by the arguments
-- passed to the original call.
---
function premake.override(scope, name, repl)
scope, name = p.resolveAlias(scope, name)
local original = scope[name]
if not original then
error("unable to override '" .. name .. "'; no such function", 2)
end
scope[name] = function(...)
return repl(original, ...)
end
-- Functions from premake.main are special in that they are fetched
-- from an array, which can be modified by system and project scripts,
-- instead of a function which would have already been called before
-- those scripts could have run. Since the array will have already
-- been evaluated by the time override() is called, the new value
-- won't be picked up as it would with the function-fetched call
-- lists. Special case the workaround for that here so everyone else
-- can just override without having to think about the difference.
if scope == premake.main then
table.replace(premake.main.elements, original, scope[name])
end
end
---
-- Find the canonical name and scope of a function, resolving any aliases.
--
-- @param scope
-- The table containing the function to be overridden. Use _G for
-- global functions.
-- @param name
-- The name of the function to resolve.
-- @return
-- The canonical scope and function name (a string value).
---
function p.resolveAlias(scope, name)
local aliases = _aliases[scope]
if aliases then
while aliases[name] do
name = aliases[name]
end
end
return scope, name
end
--
-- Display a warning, with a formatted message built from the provided
-- arguments.
--
-- @param message
-- The warning message, which may contain string formatting tokens.
-- @param ...
-- Values to fill in the string formatting tokens.
--
function premake.warn(message, ...)
message = string.format(message, ...)
if _OPTIONS.fatal then
error(message)
else
term.pushColor(term.warningColor)
io.stderr:write(string.format("** Warning: " .. message .. "\n", ...))
term.popColor();
end
end
--
-- Displays a warning just once per run.
--
-- @param key
-- A unique key to identify this warning. Subsequent warnings messages
-- using the same key will not be shown.
-- @param message
-- The warning message, which may contain string formatting tokens.
-- @param ...
-- Values to fill in the string formatting tokens.
--
function premake.warnOnce(key, message, ...)
if not _warnings[key] then
_warnings[key] = true
premake.warn(message, ...)
end
end
--
-- Display information in the term.infoColor color.
--
-- @param message
-- The info message, which may contain string formatting tokens.
-- @param ...
-- Values to fill in the string formatting tokens.
--
function premake.info(message, ...)
message = string.format(message, ...)
term.pushColor(term.infoColor)
io.stdout:write(string.format("** Info: " .. message .. "\n", ...))
term.popColor();
end
--
-- A shortcut for printing formatted output.
--
function printf(msg, ...)
print(string.format(msg, ...))
end
--
-- A shortcut for printing formatted output in verbose mode.
--
function verbosef(msg, ...)
if _OPTIONS.verbose then
print(string.format(msg, ...))
end
end
--
-- make a string from debug.getinfo information.
--
function filelineinfo(level)
local info = debug.getinfo(level+1, "Sl")
if info == nil then
return nil
end
if info.what == "C" then
return "C function"
else
local sep = iif(os.ishost('windows'), '\\', '/')
return string.format("%s(%d)", path.translate(info.short_src, sep), info.currentline)
end
end
---
-- check if version is semver.
---
function premake.isSemVer(version)
local sMajor, sMinor, sPatch, sPrereleaseAndBuild = version:match("^(%d+)%.?(%d*)%.?(%d*)(.-)$")
return (type(sMajor) == 'string')
end

View File

@@ -0,0 +1,274 @@
---
-- action.lua
-- Work with the list of registered actions.
-- Copyright (c) 2002-2015 Jason Perkins and the Premake project
---
local p = premake
p.action = {}
local action = p.action
--
-- Process the raw command line arguments from _ARGV to populate
-- the _ACTION global and _ARGS table.
--
_ACTION = nil
_ARGS = {}
for i, arg in ipairs(_ARGV) do
if not arg:startswith("/") and not arg:startswith("--") then
if not _ACTION then
_ACTION = arg
else
table.insert(_ARGS, arg)
_ARGS[arg] = arg
end
end
end
--
-- The list of registered actions. Calls to newaction() will add
-- new entries here.
--
action._list = {}
---
-- Register a new action.
--
-- @param act
-- The new action object.
---
function action.add(act)
-- validate the action object, at least a little bit
local missing
for _, field in ipairs({"description", "trigger"}) do
if not act[field] then
missing = field
end
end
if missing then
local name = act.trigger or ""
error(string.format('action "%s" needs a %s', name, missing), 3)
end
if act.os ~= nil then
p.warnOnce(act.trigger, "action '" .. act.trigger .. "' sets 'os' field, which is deprecated, use 'targetos' instead.")
act.targetos = act.os
act.os = nil
end
action._list[act.trigger] = act
end
---
-- Initialize an action.
--
-- @param name
-- The name of the action to be initialized.
---
function action.initialize(name)
local a = action._list[name]
if (a.onInitialize) then
a.onInitialize()
end
end
---
-- Trigger an action.
--
-- @param name
-- The name of the action to be triggered.
---
function action.call(name)
local a = action._list[name]
if a.onStart then
a.onStart()
end
for wks in p.global.eachWorkspace() do
local onWorkspace = a.onWorkspace or a.onSolution or a.onsolution
if onWorkspace and not wks.external then
onWorkspace(wks)
end
for prj in p.workspace.eachproject(wks) do
local onProject = a.onProject or a.onproject
if onProject and not prj.external then
onProject(prj)
end
end
end
for rule in p.global.eachRule() do
local onRule = a.onRule or a.onrule
if onRule and not rule.external then
onRule(rule)
end
end
if a.execute then
a.execute()
end
if a.onEnd then
a.onEnd()
end
end
---
-- Retrieve the current action, as determined by _ACTION.
--
-- @return
-- The current action, or nil if _ACTION is nil or does not match any action.
---
function action.current()
return action.get(_ACTION)
end
---
-- Retrieve an action by name.
--
-- @param name
-- The name of the action to retrieve.
-- @returns
-- The requested action, or nil if the action does not exist.
---
function action.get(name)
return action._list[name]
end
---
-- Iterator for the list of actions.
---
function action.each()
-- sort the list by trigger
local keys = { }
for _, act in pairs(action._list) do
table.insert(keys, act.trigger)
end
table.sort(keys)
local i = 0
return function()
i = i + 1
return action._list[keys[i]]
end
end
---
-- Determines if an action makes use of the configuration information
-- provided by the project scripts (i.e. it is an exporter) or if it
-- simply performs an action irregardless of configuration, in which
-- case the baking and validation phases can be skipped.
---
function action.isConfigurable(self)
if not self then
self = action.current() or {}
end
if self.onWorkspace or self.onSolution or self.onsolution then
return true
end
if self.onProject or self.onproject then
return true
end
return false
end
---
-- Activates a particular action.
--
-- @param name
-- The name of the action to activate.
---
function action.set(name)
_ACTION = name
-- Some actions imply a particular operating system
local act = action.get(name)
if act then
_TARGET_OS = act.targetos or _TARGET_OS
end
-- Some are implemented in standalone modules
if act and act.module then
require(act.module)
end
end
---
-- Determines if an action supports a particular language or target type.
--
-- @param feature
-- The feature to check, either a programming language or a target type.
-- @returns
-- True if the feature is supported, false otherwise.
---
function action.supports(feature)
if not feature then
return true
end
local self = action.current()
if not self then
return false
end
if not self.valid_languages and not self.valid_kinds then
return true
end
if self.valid_languages and table.contains(self.valid_languages, feature) then
return true
end
if self.valid_kinds and table.contains(self.valid_kinds, feature) then
return true
end
return false
end
--
-- Determines if an action supports a particular configuration.
-- @return
-- True if the configuration is supported, false otherwise.
--
function p.action.supportsconfig(action, cfg)
if not action then
return false
end
if action.supportsconfig then
return action.supportsconfig(cfg)
end
return true
end

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,614 @@
--
-- config.lua
-- Premake configuration object API
-- Copyright (c) 2011-2015 Jason Perkins and the Premake project
--
local p = premake
p.config = {}
local project = p.project
local config = p.config
---
-- Helper function for getlinkinfo() and gettargetinfo(); builds the
-- name parts for a configuration, for building or linking.
--
-- @param cfg
-- The configuration object being queried.
-- @param kind
-- The target kind (SharedLib, StaticLib).
-- @param field
-- One of "target" or "implib", used to locate the naming information
-- in the configuration object (i.e. targetdir, targetname, etc.)
-- @return
-- A target info object; see one of getlinkinfo() or gettargetinfo()
-- for more information.
---
function config.buildtargetinfo(cfg, kind, field)
local basedir = cfg.project.location
local targetdir
if cfg.platform then
targetdir = path.join(basedir, 'bin', cfg.platform, cfg.buildcfg)
else
targetdir = path.join(basedir, 'bin', cfg.buildcfg)
end
local directory = cfg[field.."dir"] or cfg.targetdir or targetdir
local basename = cfg[field.."name"] or cfg.targetname or cfg.project.name
local prefix = cfg[field.."prefix"] or cfg.targetprefix or ""
local suffix = cfg[field.."suffix"] or cfg.targetsuffix or ""
local extension = cfg[field.."extension"] or cfg.targetextension or ""
local bundlename = ""
local bundlepath = ""
if table.contains(os.getSystemTags(cfg.system), "darwin") and (kind == p.WINDOWEDAPP or (kind == p.SHAREDLIB and cfg.sharedlibtype)) then
bundlename = basename .. extension
bundlepath = path.join(bundlename, iif(kind == p.SHAREDLIB and cfg.sharedlibtype == "OSXFramework", "Versions/A", "Contents/MacOS"))
end
local info = {}
info.directory = directory
info.basename = basename .. suffix
info.name = prefix .. info.basename .. extension
info.extension = extension
info.abspath = path.join(directory, info.name)
info.fullpath = info.abspath
info.bundlename = bundlename
info.bundlepath = path.join(directory, bundlepath)
info.prefix = prefix
info.suffix = suffix
return info
end
---
-- Determine whether the given configuration can meaningfully link
-- against the target object.
--
-- @param cfg
-- The configuration to be tested.
-- @param target
-- The object to test against. This can be a library file name, or a
-- configuration from another project.
-- @param linkage
-- Optional. For languages or environments that support different kinds of
-- linking (i.e. Managed/CLR C++, which can link both managed and unmanaged
-- libs), which one to return. One of "unmanaged", "managed". If not
-- specified, the default for the configuration will be used.
-- @return
-- True if linking the target into the configuration makes sense.
---
function config.canLink(cfg, target, linkage)
-- Have I got a project configuration? If so, I've got some checks
-- I can do with the extra information
if type(target) ~= "string" then
-- Can't link against executables
if target.kind ~= "SharedLib" and target.kind ~= "StaticLib" then
return false
end
-- Can link mixed C++ with native projects
if cfg.language == "C++" then
if cfg.clr == p.ON then
return true
end
end
if target.language == "C++" then
if target.clr == p.ON then
return true
end
end
-- Can't link managed and unmanaged projects
local cfgManaged = project.isdotnet(cfg.project) or (cfg.clr ~= p.OFF)
local tgtManaged = project.isdotnet(target.project) or (target.clr ~= p.OFF)
return (cfgManaged == tgtManaged)
end
-- For now, I assume that everything listed in a .NET project can be
-- linked; unmanaged code is simply not supported
if project.isdotnet(cfg.project) then
return true
end
-- In C++ projects, managed dependencies must explicitly include
-- the ".dll" extension, to distinguish from unmanaged libraries
local isManaged = (path.getextension(target) == ".dll")
-- Unmanaged projects can never link managed assemblies
if isManaged and cfg.clr == p.OFF then
return false
end
-- Only allow this link it matches the requested linkage
return (isManaged) == (linkage == "managed")
end
--
-- Determines if this configuration can be linked incrementally.
--
function config.canLinkIncremental(cfg)
if cfg.kind == "StaticLib"
or config.isOptimizedBuild(cfg)
or cfg.flags.NoIncrementalLink then
return false
end
return true
end
--
-- Check a configuration for a source code file with the specified
-- extension. Used for locating special files, such as Windows
-- ".def" module definition files.
--
-- @param cfg
-- The configuration object to query.
-- @param ext
-- The file extension for which to search.
-- @return
-- The full file name if found, nil otherwise.
--
function config.findfile(cfg, ext)
for _, fname in ipairs(cfg.files) do
if fname:endswith(ext) then
return project.getrelative(cfg.project, fname)
end
end
end
---
-- Retrieve linking information for a specific configuration. That is,
-- the path information that is required to link against the library
-- built by this configuration.
--
-- @param cfg
-- The configuration object to query.
-- @return
-- A table with these values:
-- basename - the target with no directory or file extension
-- name - the target name and extension, with no directory
-- directory - relative path to the target, with no file name
-- extension - the file extension
-- prefix - the file name prefix
-- suffix - the file name suffix
-- fullpath - directory, name, and extension relative to project
-- abspath - absolute directory, name, and extension
---
function config.getlinkinfo(cfg)
-- if the configuration target is a DLL, and an import library
-- is provided, change the kind as import libraries are static.
local kind = cfg.kind
if project.isnative(cfg.project) then
if cfg.system == p.WINDOWS and kind == p.SHAREDLIB and not cfg.flags.NoImportLib then
kind = p.STATICLIB
end
end
return config.buildtargetinfo(cfg, kind, "implib")
end
--
-- Retrieve a list of link targets from a configuration.
--
-- @param cfg
-- The configuration object to query.
-- @param kind
-- The type of links to retrieve; one of:
-- siblings - linkable sibling projects
-- system - system (non-sibling) libraries
-- dependencies - all sibling dependencies, including non-linkable
-- all - return everything
-- @param part
-- How the link target should be expressed; one of:
-- name - the decorated library name with no directory
-- basename - the undecorated library name
-- directory - just the directory, no name
-- fullpath - full path with decorated name
-- object - return the project object of the dependency
-- Or, a function(original, decorated) can be supplied, in which case it
-- will be called for each matching link, providing the original value as
-- it was specified in links(), and the decorated value.
-- @param linkage
-- Optional. For languages or environments that support different kinds of
-- linking (i.e. Managed/CLR C++, which can link both managed and unmanaged
-- libs), which one to return. One of "unmanaged", "managed". If not
-- specified, the default for the configuration will be used.
-- @return
-- An array containing the requested link target information.
--
function config.getlinks(cfg, kind, part, linkage)
local result = {}
-- If I'm building a list of link directories, include libdirs
if part == "directory" then
table.foreachi(cfg.libdirs, function(dir)
table.insert(result, project.getrelative(cfg.project, dir))
end)
end
-- Iterate all of the links listed in the configuration and boil
-- them down to the requested data set
for i = 1, #cfg.links do
local link = cfg.links[i]
local item
-- Sort the links into "sibling" (is another project in this same
-- workspace) and "system" (is not part of this workspace) libraries.
local prj = p.workspace.findproject(cfg.workspace, link)
if prj and kind ~= "system" then
-- Sibling; is there a matching configuration in this project that
-- is compatible with linking to me?
local prjcfg = project.getconfig(prj, cfg.buildcfg, cfg.platform)
if prjcfg and (kind == "dependencies" or config.canLink(cfg, prjcfg)) then
-- Yes; does the caller want the whole project config or only part?
if part == "object" then
item = prjcfg
else
item = project.getrelative(cfg.project, prjcfg.linktarget.fullpath)
end
end
elseif not prj and (kind == "system" or kind == "all") then
-- Make sure this library makes sense for the requested linkage; don't
-- link managed .DLLs into unmanaged code, etc.
if config.canLink(cfg, link, linkage) then
-- if the target is listed via an explicit path (i.e. not a
-- system library or assembly), make it project-relative
item = link
if item:find("/", nil, true) then
item = project.getrelative(cfg.project, item)
end
end
end
-- If this is something I can link against, pull out the requested part
-- dont link against my self
if item and item ~= cfg then
if part == "directory" then
item = path.getdirectory(item)
if item == "." then
item = nil
end
elseif part == "name" then
item = path.getname(item)
elseif part == "basename" then
item = path.getbasename(item)
elseif type(part) == "function" then
part(link, item)
end
end
-- Add it to the list, skipping duplicates
if item and not table.contains(result, item) then
table.insert(result, item)
end
end
return result
end
--
-- Returns the list of sibling target directories
--
-- @param cfg
-- The configuration object to query.
-- @return
-- Absolute path list
--
function config.getsiblingtargetdirs(cfg)
local paths = {}
for _, sibling in ipairs(config.getlinks(cfg, "siblings", "object")) do
if (sibling.kind == p.SHAREDLIB) then
local p = sibling.linktarget.directory
if not (table.contains(paths, p)) then
table.insert(paths, p)
end
end
end
return paths
end
--
-- Determines the correct runtime library for a configuration.
--
-- @param cfg
-- The configuration object to query.
-- @return
-- A string identifying the runtime library, one of
-- StaticDebug, StaticRelease, SharedDebug, SharedRelease.
--
function config.getruntime(cfg)
if (not cfg.staticruntime or cfg.staticruntime == "Default") and not cfg.runtime then
return nil -- indicate that no runtime was explicitly selected
end
local linkage = iif(cfg.staticruntime == "On", "Static", "Shared") -- assume 'Shared' is default?
if not cfg.runtime then
return linkage .. iif(config.isDebugBuild(cfg), "Debug", "Release")
else
return linkage .. cfg.runtime
end
end
--
-- Retrieve information about a configuration's build target.
--
-- @param cfg
-- The configuration object to query.
-- @return
-- A table with these values:
-- basename - the target with no directory or file extension
-- name - the target name and extension, with no directory
-- directory - relative path to the target, with no file name
-- extension - the file extension
-- prefix - the file name prefix
-- suffix - the file name suffix
-- fullpath - directory, name, and extension, relative to project
-- abspath - absolute directory, name, and extension
-- bundlepath - the relative path and file name of the bundle
--
function config.gettargetinfo(cfg)
return config.buildtargetinfo(cfg, cfg.kind, "target")
end
---
-- Returns true if any of the files in the provided container pass the
-- provided test function.
---
function config.hasFile(self, testfn)
local files = self.files
for i = 1, #files do
if testfn(files[i]) then
return true
end
end
return false
end
--
-- Determine if the specified library or assembly reference should be copied
-- to the build's target directory. "Copy Local" is the terminology used by
-- Visual Studio C# projects for this feature.
--
-- @param cfg
-- The configuration to query. Can be a project (and will be for C#
-- projects).
-- @param linkname
-- The name of the library or assembly reference to check. This should
-- match the name as it was provided in the call to links().
-- @param default
-- The value to return if the library is not mentioned in any settings.
-- @return
-- True if the library should be copied local, false otherwise.
--
function config.isCopyLocal(cfg, linkname, default)
if cfg.flags.NoCopyLocal then
return false
end
if #cfg.copylocal > 0 then
return table.contains(cfg.copylocal, linkname)
end
return default
end
--
-- Determine if a configuration represents a "debug" or "release" build.
-- This controls the runtime library selected for Visual Studio builds
-- (and might also be useful elsewhere).
--
function config.isDebugBuild(cfg)
return cfg.symbols ~= nil and
cfg.symbols ~= p.OFF and
cfg.symbols ~= "Default" and
not config.isOptimizedBuild(cfg)
end
--
-- Determine if this configuration uses one of the optimize flags.
-- Optimized builds get different treatment, such as full linking
-- instead of incremental.
--
function config.isOptimizedBuild(cfg)
return cfg.optimize ~= nil and cfg.optimize ~= p.OFF and cfg.optimize ~= "Debug"
end
--
-- Does this configuration's list of links contain the specified
-- project? Performs a case-insensitive search for the project's
-- name in the configuration's link array.
--
-- @param cfg
-- The configuration to query.
-- @param prjName
-- The name of the project for which to search.
-- @return
-- True if the project name is found in the configuration's
-- list of links; nil otherwise.
--
function config.linksToProject(cfg, prjName)
prjName = prjName:lower()
local n = #cfg.links
for i = 1,n do
if cfg.links[i]:lower() == prjName then
return true
end
end
end
--
-- Map the values contained in the configuration to an array of flags.
--
-- @param cfg
-- The configuration to map.
-- @param mappings
-- A mapping from configuration fields and values to flags. See
-- the GCC tool interface for examples of these mappings.
-- @return
-- An array containing the translated flags.
--
function config.mapFlags(cfg, mappings)
local flags = {}
-- Helper function to append replacement values to the result
local function add(replacement)
if type(replacement) == "function" then
replacement = replacement(cfg)
end
table.insertflat(flags, replacement)
end
-- To ensure we get deterministic results that don't change as more keys
-- are added to the map, and to open the possibility to controlling the
-- application order of flags, use a prioritized list of fields to order
-- the mapping, even though it takes a little longer.
for field in p.field.eachOrdered() do
local map = mappings[field.name]
if type(map) == "function" then
map = map(cfg, mappings)
end
if map then
-- Pass each cfg value in the list through the map and append the
-- replacement, if any, to the result
local values = cfg[field.name]
if type(values) == "boolean" then
values = iif(values, "On", "Off")
end
if type(values) ~= "table" then
values = { values }
end
local foundValue = false
table.foreachi(values, function(value)
local replacement = map[value]
if replacement ~= nil then
foundValue = true
add(replacement)
end
end)
-- If no value was mapped, check to see if the map specifies a
-- default value and, if so, push that into the result
if not foundValue then
add(map._)
end
-- Finally, check for "not values", which should be added to the
-- result if the corresponding value is not present
for key, replacement in pairs(map) do
if #key > 1 and key:startswith("_") then
key = key:sub(2)
if values[key] == nil then
add(replacement)
end
end
end
end
end
return flags
end
---
-- Returns both a project configuration and a file configuration from a
-- configuration argument that could be either.
--
-- @param cfg
-- A project or file configuration object.
-- @return
-- Both a project configuration and a file configuration. If the input
-- argument is a project configuration, the file configuration value is
-- returned as nil.
---
function config.normalize(cfg)
if cfg and cfg.config ~= nil then
return cfg.config, cfg
else
return cfg, nil
end
end
---
-- Return the appropriate toolset adapter for the provided configuration,
-- or nil if no toolset is specified. If a specific version was provided,
-- returns that as a second argument.
---
function config.toolset(cfg)
if cfg.toolset then
return p.tools.canonical(cfg.toolset)
end
end

View File

@@ -0,0 +1,499 @@
--
-- base/configset.lua
--
-- A configuration set manages a collection of fields, which are organized
-- into "blocks". Each block stores a set of field-value pairs, along with
-- a list of terms which indicate the context in which those field values
-- should be applied.
--
-- Configurations use the field definitions to know what fields are available,
-- and the corresponding value types for those fields. Only fields that have
-- been registered via field.new() can be stored.
--
-- TODO: I may roll this functionality up into the container API at some
-- point. If you find yourself using or extending this code for your own
-- work give me a shout before you go too far with it so we can coordinate.
--
-- Copyright (c) 2012-2014 Jason Perkins and the Premake project
--
local p = premake
p.configset = {}
local configset = p.configset
local criteria = p.criteria
--
-- Create a new configuration set.
--
-- @param parent
-- An optional parent configuration set. If provided, the parent provides
-- a base configuration, which this set will extend.
-- @return
-- A new, empty configuration set.
--
function configset.new(parent)
local cset = {}
cset.parent = parent
cset.blocks = {}
cset.current = nil
cset.compiled = false
return cset
end
---
-- Retrieve a value from the configuration set.
--
-- This and the criteria supporting code are the inner loops of the app. Some
-- readability has been sacrificed for overall performance.
--
-- @param cset
-- The configuration set to query.
-- @param field
-- The definition of field to be queried.
-- @param filter
-- A list of lowercase context terms to use during the fetch. Only those
-- blocks with terms fully contained by this list will be considered in
-- determining the returned value. Terms should be lower case to make
-- the context filtering case-insensitive.
-- @param ctx
-- The context that will be used for detoken.expand
-- @param origin
-- The originating configset if set.
-- @return
-- The requested value.
---
function configset.fetch(cset, field, filter, ctx, origin)
filter = filter or {}
ctx = ctx or {}
if p.field.merges(field) then
return configset._fetchMerged(cset, field, filter, ctx, origin)
else
return configset._fetchDirect(cset, field, filter, ctx, origin)
end
end
function configset._dofilter(cset, block, filter)
if not filter.matcher then
return (cset.compiled or criteria.matches(block._criteria, filter))
else
return filter.matcher(cset, block, filter)
end
end
function configset._fetchDirect(cset, field, filter, ctx, origin)
-- If the originating configset hasn't been compiled, then the value will still
-- be on that configset.
if origin and origin ~= cset and not origin.compiled then
return configset._fetchDirect(origin, field, filter, ctx, origin)
end
local abspath = filter.files
local basedir
local key = field.name
local blocks = cset.blocks
local n = #blocks
for i = n, 1, -1 do
local block = blocks[i]
if not origin or block._origin == origin then
local value = block[key]
-- If the filter contains a file path, make it relative to
-- this block's basedir
if value ~= nil and abspath and not cset.compiled and block._basedir and block._basedir ~= basedir then
basedir = block._basedir
filter.files = path.getrelative(basedir, abspath)
end
if value ~= nil and configset._dofilter(cset, block, filter) then
-- If value is an object, return a copy of it so that any
-- changes later made to it by the caller won't alter the
-- original value (that was a tough bug to find)
if type(value) == "table" then
value = table.deepcopy(value)
end
-- Detoken
if field.tokens and ctx.environ then
value = p.detoken.expand(value, ctx.environ, field, ctx._basedir)
end
return value
end
end
end
filter.files = abspath
if cset.parent then
return configset._fetchDirect(cset.parent, field, filter, ctx, origin)
end
end
function configset._fetchMerged(cset, field, filter, ctx, origin)
-- If the originating configset hasn't been compiled, then the value will still
-- be on that configset.
if origin and origin ~= cset and not origin.compiled then
return configset._fetchMerged(origin, field, filter, ctx, origin)
end
local result = {}
local function remove(patterns)
for _, pattern in ipairs(patterns) do
-- Detoken
if field.tokens and ctx.environ then
pattern = p.detoken.expand(pattern, ctx.environ, field, ctx._basedir)
end
pattern = path.wildcards(pattern):lower()
local j = 1
while j <= #result do
local value = result[j]:lower()
if value:match(pattern) == value then
result[result[j]] = nil
table.remove(result, j)
else
j = j + 1
end
end
end
end
if cset.parent then
result = configset._fetchMerged(cset.parent, field, filter, ctx, origin)
end
local abspath = filter.files
local basedir
local key = field.name
local blocks = cset.blocks
local n = #blocks
for i = 1, n do
local block = blocks[i]
if not origin or block._origin == origin then
-- If the filter contains a file path, make it relative to
-- this block's basedir
if abspath and block._basedir and block._basedir ~= basedir and not cset.compiled then
basedir = block._basedir
filter.files = path.getrelative(basedir, abspath)
end
if configset._dofilter(cset, block, filter) then
if block._removes and block._removes[key] then
remove(block._removes[key])
end
local value = block[key]
-- If value is an object, return a copy of it so that any
-- changes later made to it by the caller won't alter the
-- original value (that was a tough bug to find)
if type(value) == "table" then
value = table.deepcopy(value)
end
if value then
-- Detoken
if field.tokens and ctx.environ then
value = p.detoken.expand(value, ctx.environ, field, ctx._basedir)
end
-- Translate
if field and p.field.translates(field) then
value = p.field.translate(field, value)
end
result = p.field.merge(field, result, value)
end
end
end
end
filter.files = abspath
return result
end
---
-- Create and return a metatable which allows a configuration set to act as a
-- "backing store" for a regular Lua table. Table operations that access a
-- registered field will fetch from or store to the configurations set, while
-- unknown keys are get and set to the table normally.
---
function configset.metatable(cset)
return {
__newindex = function(tbl, key, value)
local f = p.field.get(key)
if f then
local status, err = configset.store(cset, f, value)
if err then
error(err, 2)
end
else
rawset(tbl, key, value)
return value
end
end,
__index = function(tbl, key)
local f = p.field.get(key)
if f then
return configset.fetch(cset, f)
else
return nil
end
end
}
end
---
-- Create a new block of configuration field-value pairs, using a set of
-- old-style, non-prefixed context terms to control their application. This
-- approach will eventually be phased out in favor of prefixed filters;
-- see addFilter() below.
--
-- @param cset
-- The configuration set to hold the new block.
-- @param terms
-- A set of context terms to control the application of values contained
-- in the block.
-- @param basedir
-- An optional base directory; if set, filename filter tests will be made
-- relative to this basis before pattern testing.
-- @return
-- The new configuration data block.
---
function configset.addblock(cset, terms, basedir)
configset.addFilter(cset, terms, basedir, true)
return cset.current
end
---
-- Create a new block of configuration field-value pairs, using a set
-- of new-style, prefixed context terms to control their application.
--
-- @param cset
-- The configuration set to hold the new block.
-- @param terms
-- A set of terms used to control the application of the values
-- contained in the block.
-- @param basedir
-- An optional base directory. If set, filename filter tests will be
-- made relative to this base before pattern testing.
-- @param unprefixed
-- If true, uses the old, unprefixed style for filter terms. This will
-- eventually be phased out in favor of prefixed filters.
---
function configset.addFilter(cset, terms, basedir, unprefixed)
local crit, err = criteria.new(terms, unprefixed)
if not crit then
return nil, err
end
local block = {}
block._criteria = crit
block._origin = cset
if basedir then
block._basedir = basedir:lower()
end
table.insert(cset.blocks, block)
cset.current = block
return true
end
---
-- Allow calling code to save and restore a filter. Particularly useful for
-- modules.
---
function configset.getFilter(cset)
return {
_criteria = cset.current._criteria,
_basedir = cset.current._basedir
}
end
function configset.setFilter(cset, filter)
local block = {}
block._criteria = filter._criteria
block._basedir = filter._basedir
block._origin = cset
table.insert(cset.blocks, block)
cset.current = block;
end
---
-- Add a new field-value pair to the current configuration data block. The
-- data type of the field is taken into account when adding the values:
-- strings are replaced, arrays are merged, etc.
--
-- @param cset
-- The configuration set to hold the new value.
-- @param fieldname
-- The name of the field being set. The field should have already been
-- defined using the api.register() function.
-- @param value
-- The new value for the field.
-- @return
-- If successful, returns true. If an error occurred, returns nil and
-- an error message.
---
function configset.store(cset, field, value)
if not cset.current then
configset.addblock(cset, {})
end
local key = field.name
local current = cset.current
local status, result = pcall(function ()
current[key] = p.field.store(field, current[key], value)
end)
if not status then
if type(result) == "table" then
result = result.msg
end
return nil, result
end
return true
end
--
-- Remove values from a configuration set.
--
-- @param cset
-- The configuration set from which to remove.
-- @param field
-- The field holding the values to be removed.
-- @param values
-- A list of values to be removed.
--
function configset.remove(cset, field, values)
-- removes are always processed first; starting a new block here
-- ensures that they will be processed in the proper order
local block = {}
block._basedir = cset.current._basedir
block._criteria = cset.current._criteria
block._origin = cset
table.insert(cset.blocks, block)
cset.current = block
-- TODO This comment is not completely valid anymore
-- This needs work; right now it is hardcoded to only work for lists.
-- To support removing from keyed collections, I first need to figure
-- out how to move the wildcard():lower() bit into the value
-- processing call chain (i.e. that should happen somewhere inside of
-- the field.remove() call). And then I will probably need to add
-- another accessor to actually do the removing, which right now is
-- hardcoded inside of _fetchMerged(). Oh, and some of the logic in
-- api.remove() needs to get pushed down to here (or field).
values = p.field.remove(field, {}, values)
-- add a list of removed values to the block
current = cset.current
current._removes = {}
current._removes[field.name] = values
end
--
-- Check to see if a configuration set is empty; that is, it does
-- not contain any configuration blocks.
--
-- @param cset
-- The configuration set to query.
-- @return
-- True if the set does not contain any blocks.
--
function configset.empty(cset)
return (#cset.blocks == 0)
end
--
-- Compiles a new configuration set containing only the blocks which match
-- the specified criteria. Fetches against this compiled configuration set
-- may omit the context argument, resulting in faster fetches against a
-- smaller set of configuration blocks.
--
-- @param cset
-- The configuration set to query.
-- @param filter
-- A list of lowercase context terms to use during the fetch. Only those
-- blocks with terms fully contained by this list will be considered in
-- determining the returned value. Terms should be lower case to make
-- the context filtering case-insensitive.
-- @return
-- A new configuration set containing only the selected blocks, and the
-- "compiled" field set to true.
--
function configset.compile(cset, filter)
-- always start with the parent
local result
if cset.parent then
result = configset.compile(cset.parent, filter)
else
result = configset.new()
end
local blocks = cset.blocks
local n = #blocks
local abspath = filter.files
local basedir
for i = 1, n do
local block = blocks[i]
if block._origin == cset then
block._origin = result
end
-- If the filter contains a file path, make it relative to
-- this block's basedir
if abspath and block._basedir and block._basedir ~= basedir then
basedir = block._basedir
filter.files = path.getrelative(basedir, abspath)
end
if criteria.matches(block._criteria, filter) then
table.insert(result.blocks, block)
end
end
filter.files = abspath
result.compiled = true
return result
end

View File

@@ -0,0 +1,361 @@
---
-- container.lua
-- Implementation of configuration containers.
-- Copyright (c) 2014 Jason Perkins and the Premake project
---
local p = premake
p.container = {}
local container = p.container
---
-- Keep a master dictionary of container class, so they can be easily looked
-- up by name (technically you could look at premake["name"] but that is just
-- a coding convention and I don't want to count on it)
---
container.classes = {}
---
-- Define a new class of containers.
--
-- @param name
-- The name of the new container class. Used wherever the class needs to
-- be shown to the end user in a readable way.
-- @param parent (optional)
-- If this class of container is intended to be contained within another,
-- the containing class object.
-- @param extraScopes (optional)
-- Each container can hold fields scoped to itself (by putting the container's
-- class name into its scope attribute), or any of the container's children.
-- If a container can hold scopes other than these (i.e. "config"), it can
-- provide a list of those scopes in this argument.
-- @return
-- If successful, the new class descriptor object (a table). Otherwise,
-- returns nil and an error message.
---
function container.newClass(name, parent, extraScopes)
local class = p.configset.new(parent)
class.name = name
class.pluralName = name:plural()
class.containedClasses = {}
class.extraScopes = extraScopes
if parent then
table.insert(parent.containedClasses, class)
end
container.classes[name] = class
return class
end
---
-- Create a new instance of a configuration container. This is just the
-- generic base implementation, each container class will define their
-- own version.
--
-- @param parent
-- The class of container being instantiated.
-- @param name
-- The name for the new container instance.
-- @return
-- A new container instance.
---
function container.new(class, name)
local self = p.configset.new()
setmetatable(self, p.configset.metatable(self))
self.class = class
self.name = name
self.filename = name
self.script = _SCRIPT
self.basedir = os.getcwd()
self.external = false
for childClass in container.eachChildClass(class) do
self[childClass.pluralName] = {}
end
return self
end
---
-- Add a new child to an existing container instance.
--
-- @param self
-- The container instance to hold the child.
-- @param child
-- The child container instance.
---
function container.addChild(self, child)
local children = self[child.class.pluralName]
table.insert(children, child)
children[child.name] = child
child.parent = self
child[self.class.name] = self
if self.class.alias then
child[self.class.alias] = self
end
end
---
-- Process the contents of a container, which were populated by the project
-- script, in preparation for doing work on the results, such as exporting
-- project files.
---
function container.bake(self)
if self._isBaked then
return self
end
self._isBaked = true
local ctx = p.context.new(self)
for key, value in pairs(self) do
ctx[key] = value
end
local parent = self.parent
if parent then
ctx[parent.class.name] = parent
end
for class in container.eachChildClass(self.class) do
for child in container.eachChild(self, class) do
child.parent = ctx
child[self.class.name] = ctx
end
end
if type(self.class.bake) == "function" then
self.class.bake(ctx)
end
return ctx
end
function container.bakeChildren(self)
for class in container.eachChildClass(self.class) do
local children = self[class.pluralName]
for i = 1, #children do
local ctx = container.bake(children[i])
children[i] = ctx
children[ctx.name] = ctx
end
end
end
---
-- Returns true if the container can hold any of the specified field scopes.
--
-- @param class
-- The container class to test.
-- @param scope
-- A scope string (e.g. "project", "config") or an array of scope strings.
-- @return
-- True if this container can hold any of the specified scopes.
---
function container.classCanContain(class, scope)
if type(scope) == "table" then
for i = 1, #scope do
if container.classCanContain(class, scope[i]) then
return true
end
end
return false
end
-- if I have child classes, check with them first, since scopes
-- are usually specified for leaf nodes in the hierarchy
for child in container.eachChildClass(class) do
if (container.classCanContain(child, scope)) then
return true
end
end
if class.name == scope or class.alias == scope then
return true
end
-- is it in my extra scopes list?
if class.extraScopes and table.contains(class.extraScopes, scope) then
return true
end
return false
end
---
-- Return true if a container class is or inherits from the
-- specified class.
--
-- @param class
-- The container class to be tested.
-- @param scope
-- The name of the class to be checked against. If the container
-- class matches this scope (i.e. class is a project and the
-- scope is "project"), or if it is a parent object of it (i.e.
-- class is a workspace and scope is "project"), then returns
-- true.
---
function container.classIsA(class, scope)
while class do
if class.name == scope or class.alias == scope then
return true
end
class = class.parent
end
return false
end
---
-- Enumerate all of the registered child classes of a specific container class.
--
-- @param class
-- The container class to be enumerated.
-- @return
-- An iterator function for the container's child classes.
---
function container.eachChildClass(class)
local children = class.containedClasses
local i = 0
return function ()
i = i + 1
if i <= #children then
return children[i]
end
end
end
---
-- Enumerate all of the registered child instances of a specific container.
--
-- @param self
-- The container to be queried.
-- @param class
-- The class of child containers to be enumerated.
-- @return
-- An iterator function for the container's child classes.
---
function container.eachChild(self, class)
local children = self[class.pluralName]
local i = 0
return function ()
i = i + 1
if i <= #children then
return children[i]
end
end
end
---
-- Retrieve the child container with the specified class and name.
--
-- @param self
-- The container instance to query.
-- @param class
-- The class of the child container to be fetched.
-- @param name
-- The name of the child container to be fetched.
-- @return
-- The child instance if it exists, nil otherwise.
---
function container.getChild(self, class, name)
local children = self[class.pluralName]
return children[name]
end
---
-- Retrieve a container class object.
--
-- @param name
-- The name of the container class to retrieve.
-- @return
-- The container class object if it exists, nil otherwise.
---
function container.getClass(name)
return container.classes[name]
end
---
-- Determine if the container contains a child of the specified class which
-- meets the criteria of a testing function.
--
-- @param self
-- The container to be queried.
-- @param class
-- The class of the child containers to be enumerated.
-- @param func
-- A function that takes a child container as its only argument, and
-- returns true if it meets the selection criteria for the call.
-- @return
-- True if the test function returns true for any child.
---
function container.hasChild(self, class, func)
for child in container.eachChild(self, class) do
if func(child) then
return true
end
end
end
---
-- Call out to the container validation to make sure everything
-- is as it should be before handing off to the actions.
---
function container.validate(self)
if type(self.class.validate) == "function" then
self.class.validate(self)
end
end
function container.validateChildren(self)
for class in container.eachChildClass(self.class) do
local children = self[class.pluralName]
for i = 1, #children do
container.validate(children[i])
end
end
end

View File

@@ -0,0 +1,256 @@
--
-- base/context.lua
--
-- Provide a context for pulling out values from a configuration set. Each
-- context has an associated list of terms which constrain the values that
-- it will retrieve, i.e. "Windows, "Debug", "x64", and so on.
--
-- The context also provides caching for the values returned from the set.
--
-- TODO: I may roll this functionality up into the container API at some
-- point. If you find yourself using or extending this code for your own
-- work give me a shout before you go too far with it so we can coordinate.
--
-- Copyright (c) 2012-2014 Jason Perkins and the Premake project
--
local p = premake
p.context = {}
local context = p.context
local configset = p.configset
--
-- Create a new context object.
--
-- @param cfgset
-- The configuration set to provide the data from this context.
-- @param environ
-- An optional key-value environment table for token expansion; keys and
-- values provided in this table will be available for tokens to use.
-- @param filename
-- An optional filename, which will limit the fetched results to blocks
-- which specifically match the provided name.
-- @return
-- A new context object.
--
function context.new(cfgset, environ)
local ctx = {}
ctx._cfgset = cfgset
ctx.environ = environ or {}
ctx.terms = {}
-- This base directory is used when expanding path tokens encountered
-- in non-path value; such values will be made relative to this value
-- so the resulting projects will only contain relative paths. It is
-- expected that the creator of the context will set this value using
-- the setbasedir() function.
ctx._basedir = os.getcwd()
-- when a missing field is requested, fetch it from my config
-- set, and then cache the value for future lookups
setmetatable(ctx, context.__mt)
return ctx
end
--
-- Create an extended and uncached context based on another context object.
--
-- @param baseContext
-- The base context to extent
-- @param newEnvVars
-- An optional key-value environment table for token expansion; keys and
-- values provided in this table will be available for tokens to use.
-- @return
-- A new context object.
--
function context.extent(baseContext, newEnvVars)
local ctx = {}
ctx._ctx = baseContext
ctx.environ = newEnvVars or baseContext.environ
ctx.terms = {}
ctx._basedir = baseContext._basedir
setmetatable(ctx, context.__mt_uncached)
return ctx
end
---
-- Add a new key-value pair to refine the context filtering.
--
-- @param ctx
-- The context to be filtered.
-- @param key
-- The new (or an existing) key value.
-- @param value
-- The filtering value for the key.
---
function context.addFilter(ctx, key, value)
if type(value) == "table" then
for i = 1, #value do
value[i] = tostring(value[i]):lower()
end
elseif value ~= nil then
value = tostring(value):lower()
end
ctx.terms[key:lower()] = value
end
--
-- Copies the list of terms from an existing context.
--
-- @param ctx
-- The context to receive the copied terms.
-- @param src
-- The context containing the terms to copy.
--
function context.copyFilters(ctx, src)
ctx.terms = {}
for k,v in pairs(src.terms) do
ctx.terms[k] = v
end
end
--
-- Merges the list of terms from an existing context.
--
-- @param ctx
-- The context to receive the copied terms.
-- @param src
-- The context containing the terms to copy.
--
function context.mergeFilters(ctx, src)
for k, v in pairs(src.terms) do
if k == "tags" then
ctx.terms[k] = table.join(ctx.terms[k], v)
else
ctx.terms[k] = v
end
end
end
--
-- Sets the base directory for path token expansion in non-path fields; such
-- values will be made relative to this path.
--
-- @param ctx
-- The context in which to set the value.
-- @param basedir
-- The new base directory for path token expansion. This should be
-- provided as an absolute path. This may be left nil to simply fetch
-- the current base directory.
-- @return
-- The context's base directory.
--
function context.basedir(ctx, basedir)
ctx._basedir = basedir or ctx._basedir
return ctx._basedir
end
--
-- Compiles the context for better performance. The list of context terms
-- becomes locked down; any subsequent changes are ignored.
--
-- @param ctx
-- The context to compile.
--
function context.compile(ctx)
ctx._cfgset = configset.compile(ctx._cfgset, ctx.terms)
end
--
-- Check to see if a context's underlying configuration set is empty; that
-- is, it does not contain any configuration blocks.
--
-- @param ctx
-- The context to query.
-- @return
-- True if the set does not contain any blocks.
--
function context.empty(ctx)
return configset.empty(ctx._cfgset)
end
--
-- Fetch a value from underlying configuration set.
--
-- @param ctx
-- The context to query.
-- @param key
-- The property key to query.
-- @param onlylocal
-- If true, don't combine values from parent contexts.
-- @return
-- The value of the key, as determined by the configuration set. If
-- there is a corresponding Premake field, and it the field is enabled
-- for tokens, any contained tokens will be expanded.
--
function context.fetchvalue(ctx, key, onlylocal)
if not onlylocal then
local value = rawget(ctx, key)
if value ~= nil then
return value
end
end
-- The underlying configuration set will only hold registered fields.
-- If the requested key doesn't have a corresponding field, it is just
-- a regular value to be stored and fetched from the table.
local field = p.field.get(key)
if not field then
return nil
end
-- If there is a matching field, then go fetch the aggregated value
-- from my configuration set, and then cache it future lookups.
local value = configset.fetch(ctx._cfgset, field, ctx.terms, ctx, onlylocal and ctx._cfgset)
if value then
-- store the result for later lookups
rawset(ctx, key, value)
end
return value
end
context.__mt = {
__index = context.fetchvalue
}
context.__mt_uncached = {
__index = function(ctx, key)
local field = p.field.get(key)
if not field then
return nil
end
local parent = rawget(ctx, '_ctx')
return configset.fetch(parent._cfgset, field, ctx.terms, ctx, nil)
end
}

View File

@@ -0,0 +1,179 @@
--
-- criteria.lua
--
-- Stores a list of criteria terms with support for negation, conjunction,
-- and wildcard matches. Provides functions match match these criteria
-- against various contexts.
--
-- Copyright (c) 2012-2015 Jason Perkins and the Premake project
--
local p = premake
p.criteria = criteria -- criteria namespace is defined in C host
local criteria = p.criteria
--
-- These prefixes correspond to the context information built by the oven
-- during baking. In theory, any field could be used as a filter, but right
-- now only these are set.
--
criteria._validPrefixes = {
_action = true,
action = true,
architecture = true,
configurations = true,
files = true,
kind = true,
language = true,
_options = true,
options = true,
platforms = true,
sharedlibtype = true,
system = true,
toolset = true,
tags = true,
host = true,
}
--
-- Flattens a hierarchy of criteria terms into a single array containing all
-- of the values as strings in the form of "term:value1 or value2" etc.
--
function criteria.flatten(terms)
local result = {}
local function flatten(terms)
for key, value in pairs(terms) do
if type(key) == "number" then
if type(value) == "table" then
flatten(value)
elseif value then
table.insert(result, value)
end
elseif type(key) == "string" then
local word = key .. ":"
if type(value) == "table" then
local values = table.flatten(value)
word = word .. table.concat(values, " or ")
else
word = word .. value
end
table.insert(result, word)
else
error("Unknown key type in terms.")
end
end
end
flatten(terms)
return result
end
---
-- Create a new criteria object.
--
-- @param terms
-- A list of criteria terms.
-- @param unprefixed
-- If true, use the old style, unprefixed filter terms. This will
-- eventually be phased out in favor of prefixed terms only.
-- @return
-- A new criteria object.
---
function criteria.new(terms, unprefixed)
terms = criteria.flatten(terms)
-- Preprocess the list of terms for better performance in matches().
-- Each term is replaced with a pattern, with an implied AND between
-- them. Each pattern contains one or more words, with an implied OR
-- between them. A word maybe be flagged as negated, or as a wildcard
-- pattern, and may have a field prefix associated with it.
local patterns = {}
for i, term in ipairs(terms) do
term = term:lower()
local pattern = {}
local prefix = iif(unprefixed, nil, "configurations")
local words = term:explode(" or ")
for _, word in ipairs(words) do
word, prefix = criteria._word(word, prefix)
if prefix and not criteria._validPrefixes[prefix] then
return nil, string.format("Invalid field prefix '%s'", prefix)
end
-- check for field value aliases
if prefix then
local fld = p.field.get(prefix)
if fld and fld.aliases then
word[1] = fld.aliases[word[1]] or word[1]
end
end
table.insert(pattern, word)
end
table.insert(patterns, pattern)
end
-- The matching logic is written in C now for performance; compile
-- this collection of patterns to C data structures to make that
-- code easier to read and maintain.
local crit = {}
crit.patterns = patterns
crit.data = criteria._compile(patterns)
crit.terms = terms
return crit
end
function criteria._word(word, prefix)
local wildcard
local assertion = true
-- Trim off all "not" and field prefixes and check for wildcards
while (true) do
if word:startswith("not ") then
assertion = not assertion
word = word:sub(5)
else
local i = word:find(":", 1, true)
if prefix and i then
prefix = word:sub(1, i - 1)
word = word:sub(i + 1)
else
wildcard = (word:find("*", 1, true) ~= nil)
if wildcard then
word = path.wildcards(word)
end
break
end
end
end
return { word, prefix, assertion, wildcard }, prefix
end
---
-- Add a new prefix to the list of allowed values for filters. Note
-- setting a prefix on its own has no effect on the output; a filter
-- term must also be set on the corresponding context during baking.
--
-- @param prefix
-- The new prefix to be allowed.
---
function criteria.allowPrefix(prefix)
criteria._validPrefixes[prefix:lower()] = true
end

View File

@@ -0,0 +1,202 @@
--
-- detoken.lua
--
-- Expands tokens.
--
-- Copyright (c) 2011-2014 Jason Perkins and the Premake project
--
local p = premake
p.detoken = {}
local detoken = p.detoken
--
-- Expand tokens in a value.
--
-- @param value
-- The value containing the tokens to be expanded.
-- @param environ
-- An execution environment for any token expansion. This is a list of
-- key-value pairs that will be inserted as global variables into the
-- token expansion runtime environment.
-- @param field
-- The definition of the field which stores the value.
-- @param basedir
-- If provided, path tokens encountered in non-path fields (where
-- field.paths is set to false) will be made relative to this location.
-- @return
-- The value with any contained tokens expanded.
--
function detoken.expand(value, environ, field, basedir)
function expandtoken(token, e, f)
-- fetch the path variable from the action, if needed
local varMap = {}
if f.pathVars or e.overridePathVars then
local action = p.action.current()
if action then
varMap = action.pathVars or {}
end
end
-- fetch the pathVars from the enviroment.
local envMap = e.pathVars or {}
-- enable access to the global environment
setmetatable(e, {__index = _G})
local isAbs = false
local err
local result
local success
-- if the token starts with a !, don't try making it relative.
local dontMakeRelative = token:startswith('!')
if dontMakeRelative then
token = token:sub(2, -1)
end
-- If this token is in my path variable mapping tables, replace the
-- value with the one from the map. This needs to go here because
-- I don't want to make the result relative, but I don't want the
-- absolute path handling below.
local mapped = envMap[token] or varMap[token]
if mapped then
err = nil
result = mapped
if type(result) == "function" then
success, result = pcall(result, e)
if not success then
return nil, result
end
end
if (type(result) == "table") then
isAbs = result.absolute
result = result.token
else
isAbs = path.isabsolute(result)
end
else
-- convert the token into a function to execute
local func
func, err = load("return " .. token, nil, 't', e)
if not func then
return nil, "load error: " .. err
end
-- run it and get the result
success, result = pcall(func)
if not success then
err = result
result = nil
else
err = nil
result = result or ""
end
if result ~= nil then
-- ensure we got a string.
result = tostring(result)
-- If the result is an absolute path, and it is being inserted into
-- a NON-path value, I need to make it relative to the project that
-- will contain it. Otherwise I ended up with an absolute path in
-- the generated project, and it can no longer be moved around.
if path.hasdeferredjoin(result) then
result = path.resolvedeferredjoin(result)
end
isAbs = path.isabsolute(result)
if isAbs and not f.paths and basedir and not dontMakeRelative then
result = path.getrelative(basedir, result)
end
end
end
-- If the result is an absolute path, and it is being inserted into
-- a path value, place a special marker at the start of it. After
-- all results have been processed, I can look for these markers to
-- find the last absolute path expanded.
--
-- Example: the value "/home/user/myprj/%{cfg.objdir}" expands to:
-- "/home/user/myprj//home/user/myprj/obj/Debug".
--
-- By inserting a marker this becomes:
-- "/home/user/myprj/[\0]/home/user/myprj/obj/Debug".
--
-- I can now trim everything before the marker to get the right
-- result, which should always be the last absolute path specified:
-- "/home/user/myprj/obj/Debug"
if result ~= nil and isAbs and f.paths then
result = "\0" .. result
end
return result, err
end
function expandvalue(value, e, f)
if type(value) ~= "string" then
return value
end
local count
repeat
value, count = value:gsub("%%{(.-)}", function(token)
local result, err = expandtoken(token:gsub("\\", "\\\\"), e, f)
if err then
error(err .. " in token: " .. token, 0)
end
if not result then
error("Token returned nil, it may not exist: " .. token, 0)
end
return result
end)
until count == 0
-- if a path, look for a split out embedded absolute paths
if f.paths then
local i, j
repeat
i, j = value:find("\0")
if i then
value = value:sub(i + 1)
end
until not i
end
return value
end
local expand_cache = {}
function recurse(value, e, f)
if type(value) == "table" then
local res_table = {}
for k, v in pairs(value) do
if tonumber(k) ~= nil then
res_table[k] = recurse(v, e, f)
else
local nk = recurse(k, e, f)
res_table[nk] = recurse(v, e, f)
end
end
return res_table
else
local res = expand_cache[value]
if res == nil then
if type(value) == "string" and path.hasdeferredjoin(value) then
value = path.resolvedeferredjoin(value)
end
res = expandvalue(value, e, f)
expand_cache[value] = res
end
return res
end
end
return recurse(value, environ, field or {})
end

View File

@@ -0,0 +1,391 @@
---
-- base/field.lua
--
-- Fields hold a particular bit of information about a configuration, such
-- as the language of a project or the list of files it uses. Each field has
-- a particular data "kind", which describes the structure of the information
-- it holds, such a simple string, or a list of paths.
--
-- The field.* functions here manage the definition of these fields, and the
-- accessor functions required to get, set, remove, and merge their values.
--
-- Copyright (c) 2014 Jason Perkins and the Premake project
---
local p = premake
p.field = {}
local field = p.field
-- Lists to hold all of the registered fields and data kinds
field._list = {}
field._loweredList = {}
field._sortedList = nil
field._kinds = {}
-- For historical reasons
premake.fields = field._list
-- A cache for data kind accessor functions
field._accessors = {}
---
-- Register a new field.
--
-- @param f
-- A table describing the new field, with these keys:
-- name A unique string name for the field, to be used to identify
-- the field in future operations.
-- kind The kind of values that can be stored into this field. Kinds
-- can be chained together to create more complex types, such as
-- "list:string".
--
-- In addition, any custom keys set on the field description will be
-- maintained.
--
-- @return
-- A populated field object. Or nil and an error message if the field could
-- not be registered.
---
function field.new(f)
-- Translate the old approaches to data kind definitions to the new
-- one used here. These should probably be deprecated eventually.
if f.kind:startswith("key-") then
f.kind = f.kind:sub(5)
f.keyed = true
end
if f.kind:endswith("-list") then
f.kind = f.kind:sub(1, -6)
f.list = true
end
local kind = f.kind
if kind == "object" or kind == "array" then
kind = "table"
end
if f.list then
kind = "list:" .. kind
end
if f.keyed then
kind = "keyed:" .. kind
end
-- Store the translated kind with a new name, so legacy add-on code
-- can continue to work with the old value.
f._kind = kind
-- Make sure scope is always an array; don't overwrite old value
if type(f.scope) == "table" then
f.scopes = f.scope
else
f.scopes = { f.scope }
end
-- All fields must have a valid store() function
if not field.accessor(f, "store") then
return nil, "invalid field kind '" .. f._kind .. "'"
end
field._list[f.name] = f
field._loweredList[f.name:lower()] = f
field._sortedList = nil
return f
end
---
-- Remove a previously created field definition.
---
function field.unregister(f)
field._list[f.name] = nil
field._loweredList[f.name:lower()] = nil
field._sortedList = nil
end
---
-- Returns an iterator for the list of registered fields; the
-- ordering of returned results is arbitrary.
---
function field.each()
local index
return function ()
index = next(field._list, index)
return field._list[index]
end
end
---
-- Returns an iterator for the list of registered fields; the
-- results are in a prioritized order, then alphabetized.
---
function field.eachOrdered()
if not field._sortedList then
-- no priorities yet, just alpha sort
local keys = table.keys(field._list)
table.sort(keys)
field._sortedList = {}
for i = 1, #keys do
field._sortedList[i] = field._list[keys[i]]
end
end
local i = 0
return function ()
i = i + 1
return field._sortedList[i]
end
end
---
-- Register a new kind of data for field storage.
--
-- @param tag
-- A unique name of the kind; used in the kind string in new field
-- definitions (see new(), above).
-- @param settings
-- A table containing the processor functions for the new kind. If
-- nil, no change is made to the current field settings.
-- @return
-- The settings table for the specified tag.
---
function field.kind(tag, settings)
if settings then
field._kinds[tag] = settings
end
return field._kinds[tag]
end
---
-- Build an "accessor" function to process incoming values for a field. This
-- function should be an interview question.
--
-- An accessor function takes the form of:
--
-- function (field, current, value, nextAccessor)
--
-- It receives the target field, the current value of that field, and the new
-- value that has been provided by the project script. It then returns the
-- new value for the target field.
--
-- @param f
-- The field for which an accessor should be returned.
-- @param method
-- The type of accessor function required; currently this should be one of
-- "store", "remove", or "merge" though it is possible for add-on modules to
-- extend the available methods by implementing appropriate processing
-- functions.
-- @return
-- An accessor function for the field's kind and method. May return nil
-- if no processing functions are available for the given method.
---
function field.accessor(f, method)
-- Prepare a cache for accessors using this method; each encountered
-- kind only needs to be fully processed once.
field._accessors[method] = field._accessors[method] or {}
local cache = field._accessors[method]
-- Helper function recurses over each piece of the field's data kind,
-- building an accessor function for each sequence encountered. Results
-- cached from earlier calls are reused again.
local function accessorForKind(kind)
-- I'll end up with a kind of "" when I hit the end of the string
if kind == "" then
return nil
end
-- Have I already cached a result from an earlier call?
if cache[kind] then
return cache[kind]
end
-- Split off the first piece from the rest of the kind. If the
-- incoming kind is "list:key:string", thisKind will be "list"
-- and nextKind will be "key:string".
local thisKind = kind:match('(.-):') or kind
local nextKind = kind:sub(#thisKind + 2)
-- Get the processor function for this kind. Processors perform
-- data validation and storage appropriate for the data structure.
local functions = field._kinds[thisKind]
if not functions then
return nil, "Invalid field kind '" .. thisKind .. "'"
end
local processor = functions[method]
if not processor then
return nil
end
-- Now recurse to get the accessor function for the remaining parts
-- of the field's data kind. If the kind was "list:key:string", then
-- the processor function handles the "list" part, and this function
-- takes care of the "key:string" part.
local nextAccessor = accessorForKind(nextKind)
-- Now here's the magic: wrap the processor and the next accessor
-- up together into a Matryoshka doll of function calls, each call
-- handling just it's level of the kind.
accessor = function(f, current, value)
return processor(f, current, value, nextAccessor)
end
-- And cache the result so I don't have to go through that again
cache[kind] = accessor
return accessor
end
return accessorForKind(f._kind)
end
function field.compare(f, a, b)
local processor = field.accessor(f, "compare")
if processor then
return processor(f, a, b)
else
return (a == b)
end
end
---
-- Fetch a field description by name.
---
function field.get(name)
return field._list[name] or field._loweredList[name:lower()]
end
function field.merge(f, current, value)
local processor = field.accessor(f, "merge")
if processor then
return processor(f, current, value)
else
return value
end
end
---
-- Is this a field that supports merging values together? Non-merging fields
-- can simply overwrite their values, merging fields can call merge() to
-- combine two values together.
---
function field.merges(f)
return (field.accessor(f, "merge") ~= nil)
end
---
-- Retrieve a property from a field, based on it's data kind. Allows extra
-- information to be stored along with the data kind definitions; use this
-- call to find the first value in the field's data kind chain.
---
function field.property(f, tag)
local kinds = string.explode(f._kind, ":", true)
for i, kind in ipairs(kinds) do
local value = field._kinds[kind][tag]
if value ~= nil then
return value
end
end
end
---
-- Override one of the field kind accessor functions. This works just like
-- p.override(), but applies the new function to the internal field
-- description and clears the accessor caches to make sure the change gets
-- picked up by future operations.
---
function field.override(fieldName, accessorName, func)
local kind = field.kind(fieldName)
p.override(kind, accessorName, func)
field._accessors = {}
end
function field.remove(f, current, value)
local processor = field.accessor(f, "remove")
if processor then
return processor(f, current, value)
else
return value
end
end
function field.removes(f)
return (field.accessor(f, "merge") ~= nil and field.accessor(f, "remove") ~= nil)
end
function field.store(f, current, value)
local processor = field.accessor(f, "store")
if processor then
return processor(f, current, value)
else
return value
end
end
function field.translate(f, value)
local processor = field.accessor(f, "translate")
if processor then
return processor(f, value, nil)[1]
else
return value
end
end
function field.translates(f)
return (field.accessor(f, "translate") ~= nil)
end

View File

@@ -0,0 +1,300 @@
--
-- fileconfig.lua
-- The set of configuration information for a specific file.
-- Copyright (c) 2011-2014 Jason Perkins and the Premake project
--
local p = premake
p.fileconfig = {}
local fileconfig = p.fileconfig
local context = p.context
local project = p.project
--
-- A little confusing: the file configuration actually contains two objects.
-- The first object, the one that is returned by fileconfig.new() and later
-- passed back in as *the* file configuration object, contains the common
-- project-wide settings for the file. This object also contains a list of
-- "sub-configurations", one for each project configuration to which the file
-- belongs.
--
-- Internally, I'm calling the first object the "file configuration" (fcfg)
-- and the children "file sub-configurations" (fsub). To distinguish them
-- from the project configurations (cfg).
--
-- Define metatables for each of types; more info below.
--
fileconfig.fcfg_mt = {}
fileconfig.fsub_mt = {}
--
-- Create a new file configuration object.
--
-- @param fname
-- The absolute path to the file.
-- @param prj
-- The project which contains the file.
-- @return
-- A new file configuration object.
--
function fileconfig.new(fname, prj)
local environ = { }
local fcfg = context.new(prj, environ)
context.copyFilters(fcfg, prj)
context.addFilter(fcfg, "files", fname:lower())
for key, value in pairs(prj.environ) do
environ[key] = value
end
environ.file = fcfg
context.compile(fcfg)
fcfg.project = prj
fcfg.workspace = prj.workspace
fcfg.configs = {}
fcfg.abspath = fname
context.basedir(fcfg, prj.location)
-- Most of the other path properties are computed on demand
-- from the file's absolute path.
setmetatable(fcfg, fileconfig.fcfg_mt)
-- Except for the virtual path, which is expensive to compute, and
-- can be used across all the sub-configurations
local vpath = project.getvpath(prj, fname)
if vpath ~= fcfg.abspath then
fcfg.vpath = vpath
end
return fcfg
end
--
-- Associate a new project configuration with a file. It is possible for a
-- file to only appear in a subset of a project's configurations.
--
-- @param fcfg
-- The file configuration to which the project configuration should be
-- associated.
-- @param cfg
-- The project configuration to associate.
--
function fileconfig.addconfig(fcfg, cfg)
local prj = cfg.project
local wks = cfg.workspace
-- Create a new context object for this configuration-file pairing.
-- The context has the ability to pull out configuration settings
-- specific to the file.
local environ = {}
local fsub = context.new(prj, environ)
context.copyFilters(fsub, fcfg)
context.mergeFilters(fsub, cfg)
fcfg.configs[cfg] = fsub
-- set up an environment for expanding tokens contained by this file
-- configuration; based on the configuration's environment so that
-- any magic set up there gets maintained
for key, value in pairs(cfg.environ) do
environ[key] = value
end
for key, value in pairs(fcfg.environ) do
environ[key] = value
end
-- finish the setup
context.compile(fsub)
fsub.abspath = fcfg.abspath
fsub.vpath = fcfg.vpath
fsub.config = cfg
fsub.project = prj
fsub.workspace = wks
-- Set the context's base directory to the project's file system
-- location. Any path tokens which are expanded in non-path fields
-- (such as the custom build commands) will be made relative to
-- this path, ensuring a portable generated project.
context.basedir(fsub, prj.location)
return setmetatable(fsub, fileconfig.fsub_mt)
end
--
-- Retrieve the configuration settings for a particular file/project
-- configuration pairing.
--
-- @param fcfg
-- The file configuration to query.
-- @param cfg
-- The project configuration to query.
-- @return
-- The configuration context for the pairing, or nil if this project
-- configuration is not associated with this file.
--
function fileconfig.getconfig(fcfg, cfg)
return fcfg.configs[cfg]
end
--
-- Checks to see if the project or file configuration contains a
-- custom build rule.
--
-- @param cfg
-- A project or file configuration.
-- @return
-- True if the configuration contains settings for a custom
-- build rule.
--
function fileconfig.hasCustomBuildRule(fcfg)
return fcfg and (#fcfg.buildcommands > 0) and (#fcfg.buildoutputs > 0)
end
--
-- Checks to see if the file configuration contains any unique information,
-- or if it is the same as its parent configuration.
--
-- @param fcfg
-- A file configuration.
-- @return
-- True if the file configuration contains values which differ from the
-- parent project configuration, false otherwise.
--
function fileconfig.hasFileSettings(fcfg)
if not fcfg then
return false
end
for key, field in pairs(p.fields) do
if field.scopes[1] == "config" then
local value = fcfg[field.name]
if value then
if type(value) == "table" then
if #value > 0 then
return true
end
else
return true
end
end
end
end
return false
end
--
-- Rather than store pre-computed strings for all of the path variations
-- (abspath, relpath, vpath, name, etc.) for each file (there can be quite
-- a lot of them) I assign a metatable to the file configuration objects
-- that will build these values on the fly.
--
-- I am using these pseudo-properties, rather than explicit functions, to make
-- it easier to fetch them script tokens (i.e. %{file.relpath} with no need
-- for knowledge of the internal Premake APIs.
--
--
-- The indexer for the file configurations. If I have a path building function
-- to fulfill the request, call it. Else fall back to the context's own value lookups.
--
local fcfg_mt = fileconfig.fcfg_mt
fcfg_mt.__index = function(fcfg, key)
if type(fcfg_mt[key]) == "function" then
return fcfg_mt[key](fcfg)
end
return context.__mt.__index(fcfg, key)
end
--
-- The indexer for the file sub-configurations. Check for a path building
-- function first, and then fall back to the context's own value lookups.
-- TODO: Would be great if this didn't require inside knowledge of context.
--
fileconfig.fsub_mt.__index = function(fsub, key)
if type(fcfg_mt[key]) == "function" then
return fcfg_mt[key](fsub)
end
return context.__mt.__index(fsub, key)
end
--
-- And here are the path building functions.
--
function fcfg_mt.basename(fcfg)
return path.getbasename(fcfg.abspath)
end
function fcfg_mt.directory(fcfg)
return path.getdirectory(fcfg.abspath)
end
function fcfg_mt.reldirectory(fcfg)
return path.getdirectory(fcfg.relpath)
end
function fcfg_mt.name(fcfg)
return path.getname(fcfg.abspath)
end
function fcfg_mt.objname(fcfg)
if fcfg.sequence ~= nil and fcfg.sequence > 0 then
return fcfg.basename .. fcfg.sequence
else
return fcfg.basename
end
end
function fcfg_mt.path(fcfg)
return fcfg.relpath
end
function fcfg_mt.relpath(fcfg)
return project.getrelative(fcfg.project, fcfg.abspath)
end
function fcfg_mt.vpath(fcfg)
-- This only gets called if no explicit virtual path was set
return fcfg.relpath
end
function fcfg_mt.extension(fcfg)
return path.getextension(fcfg.abspath)
end

View File

@@ -0,0 +1,113 @@
---
-- global.lua
-- The global container holds workspaces and rules.
-- Copyright (c) 2014-2015 Jason Perkins and the Premake project
---
local p = premake
p.global = p.api.container("global")
local global = p.global
---
-- Create a new global container instance.
---
function global.new(name)
return p.container.new(p.global, name)
end
---
-- Bakes the global scope.
---
function global.bake(self)
p.container.bakeChildren(self)
end
---
-- Iterate over the collection of rules in a session.
--
-- @returns
-- An iterator function.
---
function global.eachRule()
local root = p.api.rootContainer()
return p.container.eachChild(root, p.rule)
end
---
-- Iterate over the collection of workspaces in a session.
--
-- @returns
-- A workspace iterator function.
---
function global.eachWorkspace()
local root = p.api.rootContainer()
return p.container.eachChild(root, p.workspace)
end
p.alias(global, "eachWorkspace", "eachSolution")
---
-- Retrieve a rule by name or index.
--
-- @param key
-- The rule key, either a string name or integer index.
-- @returns
-- The rule with the provided key.
---
function global.getRule(key)
local root = p.api.rootContainer()
return root.rules[key]
end
---
-- Retrieve the rule to applies to the provided file name, if any such
-- rule exists.
--
-- @param fname
-- The name of the file.
-- @param rules
-- A list of rule names to be included in the search. If not specified,
-- all rules will be checked.
-- @returns
-- The rule, is one has been registered, or nil.
---
function global.getRuleForFile(fname, rules)
for rule in global.eachRule() do
if not rules or table.contains(rules, rule.name) then
if path.hasextension(fname, rule.fileextension) then
return rule
end
end
end
end
---
-- Retrieve a workspace by name or index.
--
-- @param key
-- The workspace key, either a string name or integer index.
-- @returns
-- The workspace with the provided key.
---
function global.getWorkspace(key)
local root = p.api.rootContainer()
return root.workspaces[key]
end
p.alias(global, "getWorkspace", "getSolution")

View File

@@ -0,0 +1,85 @@
--
-- globals.lua
-- Replacements and extensions to Lua's global functions.
-- Copyright (c) 2002-2014 Jason Perkins and the Premake project
--
--
-- Find and execute a Lua source file present on the filesystem, but
-- continue without error if the file is not present. This is used to
-- handle optional files such as the premake-system.lua script.
--
-- @param fname
-- The name of the file to load. This may be specified as a single
-- file path or an array of file paths, in which case the first
-- file found is run.
-- @return
-- True if a file was found and executed, nil otherwise.
--
function dofileopt(fname)
if type(fname) == "string" then fname = {fname} end
for i = 1, #fname do
local found = os.locate(fname[i])
if not found then
found = os.locate(fname[i] .. ".lua")
end
if found then
dofile(found)
return true
end
end
end
---
-- Load and run an external script file, with a bit of extra logic to make
-- including projects easier. if "path" is a directory, will look for
-- path/premake5.lua. And each file is tracked, and loaded only once.
--
-- @param fname
-- The name of the directory or file to include. If a directory, will
-- automatically include the contained premake5.lua or premake4.lua
-- script at that lcoation.
---
io._includedFiles = {}
function include(fname)
local fullPath = premake.findProjectScript(fname)
fname = fullPath or fname
if not io._includedFiles[fname] then
io._includedFiles[fname] = true
return dofile(fname)
end
end
---
-- Extend require() with a second argument to specify the expected
-- version of the loaded module. Raises an error if the version criteria
-- are not met.
--
-- @param modname
-- The name of the module to load.
-- @param versions
-- An optional version criteria string; see premake.checkVersion()
-- for more information on the format.
-- @return
-- If successful, the loaded module, which is also stored into the
-- global package.loaded table.
---
premake.override(_G, "require", function(base, modname, versions)
local result, mod = pcall(base,modname)
if not result then
error(mod, 3)
end
if mod and versions and not premake.checkVersion(mod._VERSION, versions) then
error(string.format("module %s %s does not meet version criteria %s",
modname, mod._VERSION or "(none)", versions), 3)
end
return mod
end)

View File

@@ -0,0 +1,22 @@
---
-- group.lua
-- A psuedo-configuration container to represent project groups.
-- Copyright (c) 2014 Jason Perkins and the Premake project
---
local p = premake
p.group = p.api.container("group", p.workspace)
local group = p.group
---
-- Bit of a hack: prevent groups from holding any configuration data.
---
group.placeholder = true
function group.new(name)
return p.container.new(group, name)
end

View File

@@ -0,0 +1,81 @@
--
-- help.lua
-- User help, displayed on /help option.
-- Copyright (c) 2002-2013 Jason Perkins and the Premake project
--
function premake.showhelp()
-- display the basic usage
printf("Premake %s, a build script generator", _PREMAKE_VERSION)
printf(_PREMAKE_COPYRIGHT)
printf("%s %s", _VERSION, _COPYRIGHT)
printf("")
printf("Usage: premake5 [options] action [arguments]")
printf("")
-- filter all options by category.
local categories = {}
for option in premake.option.each() do
local cat = "OPTIONS - General"
if option.category then
cat = "OPTIONS - " .. option.category;
end
if categories[cat] then
table.insert(categories[cat], option)
else
categories[cat] = {option}
end
end
-- display all options
for k, options in spairs(categories) do
printf(k)
printf("")
local length = 0
for _, option in ipairs(options) do
local trigger = option.trigger
if (option.value) then trigger = trigger .. "=" .. option.value end
if (#trigger > length) then length = #trigger end
end
for _, option in ipairs(options) do
local trigger = option.trigger
local description = option.description
if (option.value) then trigger = trigger .. "=" .. option.value end
if (option.allowed) then description = description .. "; one of:" end
printf(" --%-" .. length .. "s %s", trigger, description)
if (option.allowed) then
local function compareValue(a, b)
return a[1] < b[1]
end
table.sort(option.allowed, compareValue)
for _, value in ipairs(option.allowed) do
printf(" %-" .. length-1 .. "s %s", value[1], value[2])
end
printf("")
end
end
printf("")
end
-- display all actions
printf("ACTIONS")
printf("")
for action in premake.action.each() do
printf(" %-17s %s", action.trigger, action.description)
end
printf("")
-- see more
printf("For additional information, see https://premake.github.io")
end

View File

@@ -0,0 +1,68 @@
--
-- http.lua
-- Additions to the http namespace.
-- Copyright (c) 2008-2014 Jason Perkins and the Premake project
--
if http == nil then
return
end
---
-- Simple progress bar on stdout for curl downloads.
---
function http.reportProgress(total, current)
local width = 70
local progress = math.floor(current * width / total)
if progress == width then
io.write(string.rep(' ', width + 2) .. '\r')
else
io.write('[' .. string.rep('=', progress) .. string.rep(' ', width - progress) .. ']\r')
end
end
---
-- Correctly escape parameters for use in a url.
---
function http.escapeUrlParam(param)
local url_encodings = {
[' '] = '%%20',
['!'] = '%%21',
['"'] = '%%22',
['#'] = '%%23',
['$'] = '%%24',
['&'] = '%%26',
['\''] = '%%27',
['('] = '%%28',
[')'] = '%%29',
['*'] = '%%2A',
['+'] = '%%2B',
['-'] = '%%2D',
['.'] = '%%2E',
['/'] = '%%2F',
[':'] = '%%3A',
[';'] = '%%3B',
['<'] = '%%3C',
['='] = '%%3D',
['>'] = '%%3E',
['?'] = '%%3F',
['@'] = '%%40',
['['] = '%%5B',
['\\'] = '%%5C',
[']'] = '%%5D',
['^'] = '%%5E',
['_'] = '%%5F',
['`'] = '%%60'
}
param = param:gsub('%%', '%%25')
for k,v in pairs(url_encodings) do
param = param:gsub('%' .. k, v)
end
return param
end

View File

@@ -0,0 +1,47 @@
--
-- io.lua
-- Additions to the I/O namespace.
-- Copyright (c) 2008-2014 Jason Perkins and the Premake project
--
--
-- Open an overload of the io.open() function, which will create any missing
-- subdirectories in the filename if "mode" is set to writeable.
--
premake.override(io, "open", function(base, fname, mode)
if mode and (mode:find("w") or mode:find("a")) then
local dir = path.getdirectory(fname)
ok, err = os.mkdir(dir)
if not ok then
error(err, 0)
end
end
return base(fname, mode)
end)
--
-- Write content to a new file.
--
function io.writefile(filename, content)
local file = io.open(filename, "w+b")
if file then
file:write(content)
file:close()
return true
end
end
--
-- Read content from new file.
--
function io.readfile(filename)
local file = io.open(filename, "rb")
if file then
local content = file:read("*a")
file:close()
return content
end
end

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,59 @@
--
-- jsonwrapper.lua
-- Provides JSON encoding and decoding API by wrapping a third-party JSON library
-- Copyright (c) 2017 Jason Perkins and the Premake project
--
json = {}
local implementation = dofile('json.lua')
local err
json.implementation = implementation
function implementation.assert(condition, message)
if not condition then
err = message
end
-- The JSON library we're using assumes that encode error handlers will
-- abort on error. It doesn't have the same assumption for decode error
-- handlers, but we're using this same function for both.
assert(condition, message)
end
function json.encode(value)
err = nil
local success, result = pcall(implementation.encode, implementation, value)
if not success then
return nil, err
end
return result
end
function json.encode_pretty(value)
err = nil
local success, result = pcall(implementation.encode_pretty, implementation, value)
if not success then
return nil, err
end
return result
end
function json.decode(value)
err = nil
local success, result = pcall(implementation.decode, implementation, value)
if not success then
return nil, err
end
return result
end

View File

@@ -0,0 +1,24 @@
---
-- languages.lua
-- Language helpers.
-- Copyright (c) 2002-2015 Jason Perkins and the Premake project
---
local p = premake
p.languages = {}
function p.languages.isc(value)
return value == "C";
end
function p.languages.iscpp(value)
return value == "C++";
end
function p.languages.iscsharp(value)
return value == "C#";
end
function p.languages.isfsharp(value)
return value == "F#";
end

View File

@@ -0,0 +1,174 @@
--
-- option.lua
-- Work with the list of registered options.
-- Copyright (c) 2002-2014 Jason Perkins and the Premake project
--
local p = premake
p.option = {}
local m = p.option
--
-- We can't control how people will type in the command line arguments, or how
-- project scripts will define their custom options, so case becomes an issue.
-- To mimimize issues, set up the _OPTIONS table to always use lowercase keys.
--
local _OPTIONS_metatable = {
__index = function(tbl, key)
if type(key) == "string" then
key = key:lower()
end
return rawget(tbl, key)
end,
__newindex = function(tbl, key, value)
if type(key) == "string" then
key = key:lower()
end
rawset(tbl, key, value)
end
}
_OPTIONS = {}
setmetatable(_OPTIONS, _OPTIONS_metatable)
--
-- Process the raw command line arguments from _ARGV to populate
-- the _OPTIONS table
--
for i, arg in ipairs(_ARGV) do
local key, value
local i = arg:find("=", 1, true)
if i then
key = arg:sub(1, i - 1)
value = arg:sub(i + 1)
else
key = arg
value = ""
end
if key:startswith("/") then
_OPTIONS[key:sub(2)] = value
elseif key:startswith("--") then
_OPTIONS[key:sub(3)] = value
end
end
--
-- The list of registered options. Calls to newoption() will add
-- new entries here.
--
m.list = {}
--
-- Register a new option.
--
-- @param opt
-- The new option object.
--
function m.add(opt)
-- some sanity checking
local missing
for _, field in ipairs({ "description", "trigger" }) do
if (not opt[field]) then
missing = field
end
end
if (missing) then
error("option needs a " .. missing, 3)
end
-- add it to the master list
p.option.list[opt.trigger:lower()] = opt
-- if it has a default value, set it.
if opt.default and not _OPTIONS[opt.trigger] then
_OPTIONS[opt.trigger] = opt.default
end
end
--
-- Retrieve an option by name.
--
-- @param name
-- The name of the option to retrieve.
-- @returns
-- The requested option, or nil if the option does not exist.
--
function m.get(name)
return p.option.list[name]
end
--
-- Iterator for the list of options.
--
function m.each()
-- sort the list by trigger
local keys = { }
for _, option in pairs(p.option.list) do
table.insert(keys, option.trigger)
end
table.sort(keys)
local i = 0
return function()
i = i + 1
return p.option.list[keys[i]]
end
end
--
-- Validate a list of user supplied key/value pairs against the list of registered options.
--
-- @param values
-- The list of user supplied key/value pairs.
-- @returns
--- True if the list of pairs are valid, false and an error message otherwise.
--
function m.validate(values)
for key, value in pairs(values) do
-- does this option exist
local opt = p.option.get(key)
if (not opt) then
return false, "invalid option '" .. key .. "'"
end
-- does it need a value?
if (opt.value and value == "") then
return false, "no value specified for option '" .. key .. "'"
end
-- is the value allowed?
if opt.allowed then
local found = false
for _, match in ipairs(opt.allowed) do
if match[1] == value then
found = true
break
end
end
if not found then
return false, string.format("invalid value '%s' for option '%s'", value, key)
end
end
end
return true
end

View File

@@ -0,0 +1,809 @@
--
-- os.lua
-- Additions to the OS namespace.
-- Copyright (c) 2002-2014 Jason Perkins and the Premake project
--
---
-- Extend Lua's built-in os.execute() with token expansion and
-- path normalization.
--
premake.override(os, "execute", function(base, cmd)
cmd = os.translateCommands(cmd)
return base(cmd)
end)
---
-- Same as os.execute(), but accepts string formatting arguments.
---
function os.executef(cmd, ...)
cmd = string.format(cmd, ...)
return os.execute(cmd)
end
--
-- Scan the well-known system locations for a particular library.
--
local function parse_ld_so_conf(conf_file)
-- Linux ldconfig file parser to find system library locations
local first, last
local dirs = { }
for line in io.lines(conf_file) do
-- ignore comments
first = line:find("#", 1, true)
if first ~= nil then
line = line:sub(1, first - 1)
end
if line ~= "" then
-- check for include files
first, last = line:find("include%s+")
if first ~= nil then
-- found include glob
local include_glob = line:sub(last + 1)
local includes = os.matchfiles(include_glob)
for _, v in ipairs(includes) do
dirs = table.join(dirs, parse_ld_so_conf(v))
end
else
-- found an actual ld path entry
table.insert(dirs, line)
end
end
end
return dirs
end
local function get_library_search_path()
local path
if os.istarget("windows") then
path = os.getenv("PATH") or ""
elseif os.istarget("haiku") then
path = os.getenv("LIBRARY_PATH") or ""
else
if os.istarget("darwin") then
path = os.getenv("DYLD_LIBRARY_PATH") or ""
else
path = os.getenv("LD_LIBRARY_PATH") or ""
for _, prefix in ipairs({"", "/opt"}) do
local conf_file = prefix .. "/etc/ld.so.conf"
if os.isfile(conf_file) then
for _, v in ipairs(parse_ld_so_conf(conf_file)) do
if (#path > 0) then
path = path .. ":" .. v
else
path = v
end
end
end
end
end
path = path or ""
local archpath = "/lib:/usr/lib:/usr/local/lib"
if os.is64bit() and not (os.istarget("darwin")) then
archpath = "/lib64:/usr/lib64/:usr/local/lib64" .. ":" .. archpath
end
if (#path > 0) then
path = path .. ":" .. archpath
else
path = archpath
end
end
return path
end
---
-- Attempt to locate and return the path to a shared library.
--
-- This function does not work to locate system libraries on macOS 11 or later; it may still
-- be used to locate user libraries: _"New in macOS Big Sur 11.0.1, the system ships with
-- a built-in dynamic linker cache of all system-provided libraries. As part of this change,
-- copies of dynamic libraries are no longer present on the filesystem. Code that attempts to
-- check for dynamic library presence by looking for a file at a path or enumerating a directory
-- will fail."
-- https://developer.apple.com/documentation/macos-release-notes/macos-big-sur-11_0_1-release-notes
--
-- @param libname
-- The library name with or without prefix and suffix.
-- @param libdirs
-- An array of paths to be searched.
-- @returns
-- The full path to the library if found; `nil` otherwise.
---
function os.findlib(libname, libdirs)
local path = get_library_search_path()
local formats
-- assemble a search path, depending on the platform
if os.istarget("windows") then
formats = { "%s.dll", "%s" }
elseif os.istarget("haiku") then
formats = { "lib%s.so", "%s.so" }
else
if os.istarget("darwin") then
formats = { "lib%s.dylib", "%s.dylib" }
else
formats = { "lib%s.so", "%s.so" }
end
table.insert(formats, "%s")
end
local userpath = ""
if type(libdirs) == "string" then
userpath = libdirs
elseif type(libdirs) == "table" then
userpath = table.implode(libdirs, "", "", ":")
end
if (#userpath > 0) then
if (#path > 0) then
path = userpath .. ":" .. path
else
path = userpath
end
end
for _, fmt in ipairs(formats) do
local name = string.format(fmt, libname)
local result = os.pathsearch(name, path)
if result then return result end
end
end
function os.findheader(headerpath, headerdirs)
-- headerpath: a partial header file path
-- headerdirs: additional header search paths
local path = get_library_search_path()
-- replace all /lib by /include
path = path .. ':'
path = path:gsub ('/lib[0-9]*([:/])', '/include%1')
path = path:sub (1, #path - 1)
local userpath = ""
if type(headerdirs) == "string" then
userpath = headerdirs
elseif type(headerdirs) == "table" then
userpath = table.implode(headerdirs, "", "", ":")
end
if (#userpath > 0) then
if (#path > 0) then
path = userpath .. ":" .. path
else
path = userpath
end
end
local result = os.pathsearch (headerpath, path)
return result
end
--
-- Retrieve the current target operating system ID string.
--
function os.target()
return _OPTIONS.os or _TARGET_OS
end
function os.get()
local caller = filelineinfo(2)
premake.warnOnce(caller, "os.get() is deprecated, use 'os.target()' or 'os.host()'.\n @%s\n", caller)
return os.target()
end
-- deprecate _OS
_G_metatable = {
__index = function(t, k)
if (k == '_OS') then
premake.warnOnce("_OS+get", "_OS is deprecated, use '_TARGET_OS'.")
return rawget(t, "_TARGET_OS")
else
return rawget(t, k)
end
end,
__newindex = function(t, k, v)
if (k == '_OS') then
premake.warnOnce("_OS+set", "_OS is deprecated, use '_TARGET_OS'.")
rawset(t, "_TARGET_OS", v)
else
rawset(t, k, v)
end
end
}
setmetatable(_G, _G_metatable)
--
-- Check the current target operating system; may be set with the /os command line flag.
--
function os.istarget(id)
local tags = os.getSystemTags(os.target())
return table.contains(tags, id:lower())
end
function os.is(id)
local caller = filelineinfo(2)
premake.warnOnce(caller, "os.is() is deprecated, use 'os.istarget()' or 'os.ishost()'.\n @%s\n", caller)
return os.istarget(id)
end
--
-- Check the current host operating system.
--
function os.ishost(id)
local tags = os.getSystemTags(os.host())
return table.contains(tags, id:lower())
end
---
-- Determine if a directory exists on the file system, and that it is a
-- directory and not a file.
--
-- @param p
-- The path to check.
-- @return
-- True if a directory exists at the given path.
---
premake.override(os, "isdir", function(base, p)
p = path.normalize(p)
return base(p)
end)
---
-- Determine if a file exists on the file system, and that it is a
-- file and not a directory.
--
-- @param p
-- The path to check.
-- @return
-- True if a file exists at the given path.
---
premake.override(os, "isfile", function(base, p)
p = path.normalize(p)
return base(p)
end)
--
-- Determine if the current system is running a 64-bit architecture.
--
local _is64bit
local _64BitHostTypes = {
"x86_64",
"ia64",
"amd64",
"ppc64",
"powerpc64",
"sparc64"
}
function os.is64bit()
-- This can be expensive to compute, so cache and reuse the response
if _is64bit ~= nil then
return _is64bit
end
_is64bit = false
-- Call the native code implementation. If this returns true then
-- we're 64-bit, otherwise do more checking locally
if (os._is64bit()) then
_is64bit = true
else
-- Identify the system
local arch
if os.ishost("windows") then
arch = os.getenv("PROCESSOR_ARCHITECTURE")
elseif os.ishost("macosx") then
arch = os.outputof("echo $HOSTTYPE")
else
arch = os.outputof("uname -m")
end
-- Check our known 64-bit identifiers
arch = arch:lower()
for _, hosttype in ipairs(_64BitHostTypes) do
if arch:find(hosttype) then
_is64bit = true
end
end
end
return _is64bit
end
---
-- Perform a wildcard search for files and directories.
--
-- @param mask
-- The file search pattern. Use "*" to match any part of a file or
-- directory name, "**" to recurse into subdirectories.
-- @return
-- A table containing the matched file or directory names.
---
function os.match(mask)
mask = path.normalize(mask)
local starpos = mask:find("%*")
local before = path.getdirectory(starpos and mask:sub(1, starpos - 1) or mask)
local slashpos = starpos and mask:find("/", starpos)
local after = slashpos and mask:sub(slashpos + 1)
-- Only recurse for path components starting with '**':
local recurse = starpos and
mask:sub(starpos + 1, starpos + 1) == '*' and
(starpos == 1 or mask:sub(starpos - 1, starpos - 1) == '/')
local results = { }
if recurse then
local submask = mask:sub(1, starpos) .. mask:sub(starpos + 2)
results = os.match(submask)
local pattern = mask:sub(1, starpos)
local m = os.matchstart(pattern)
while os.matchnext(m) do
if not os.matchisfile(m) then
local matchpath = path.join(before, os.matchname(m), mask:sub(starpos))
results = table.join(results, os.match(matchpath))
end
end
os.matchdone(m)
else
local pattern = mask:sub(1, slashpos and slashpos - 1)
local m = os.matchstart(pattern)
while os.matchnext(m) do
if not (slashpos and os.matchisfile(m)) then
local matchpath = path.join(before, matchpath, os.matchname(m))
if after then
results = table.join(results, os.match(path.join(matchpath, after)))
else
table.insert(results, matchpath)
end
end
end
os.matchdone(m)
end
return results
end
---
-- Perform a wildcard search for directories.
--
-- @param mask
-- The search pattern. Use "*" to match any part of a directory
-- name, "**" to recurse into subdirectories.
-- @return
-- A table containing the matched directory names.
---
function os.matchdirs(mask)
local results = os.match(mask)
for i = #results, 1, -1 do
if not os.isdir(results[i]) then
table.remove(results, i)
end
end
return results
end
---
-- Perform a wildcard search for files.
--
-- @param mask
-- The search pattern. Use "*" to match any part of a file
-- name, "**" to recurse into subdirectories.
-- @return
-- A table containing the matched directory names.
---
function os.matchfiles(mask)
local results = os.match(mask)
for i = #results, 1, -1 do
if not os.isfile(results[i]) then
table.remove(results, i)
end
end
return results
end
--
-- An overload of the os.mkdir() function, which will create any missing
-- subdirectories along the path.
--
local builtin_mkdir = os.mkdir
function os.mkdir(p)
p = path.normalize(p)
local dir = iif(p:startswith("/"), "/", "")
for part in p:gmatch("[^/]+") do
dir = dir .. part
if (part ~= "" and not path.isabsolute(part) and not os.isdir(dir)) then
local ok, err = builtin_mkdir(dir)
if (not ok) then
return nil, err
end
end
dir = dir .. "/"
end
return true
end
--
-- Run a shell command and return the output.
--
-- @param cmd Command to execute
-- @param streams Standard stream(s) to output
-- Must be one of
-- - "both" (default)
-- - "output" Return standard output stream content only
-- - "error" Return standard error stream content only
--
function os.outputof(cmd, streams)
cmd = path.normalize(cmd)
streams = streams or "both"
local redirection
if streams == "both" then
redirection = " 2>&1"
elseif streams == "output" then
redirection = " 2>/dev/null"
elseif streams == "error" then
redirection = " 2>&1 1>/dev/null"
else
error ('Invalid stream(s) selection. "output", "error", or "both" expected.')
end
local pipe = io.popen(cmd .. redirection)
local result = pipe:read('*a')
local success, what, code = pipe:close()
if success then
-- chomp trailing newlines
if result then
result = string.gsub(result, "[\r\n]+$", "")
end
return result, code, what
else
return nil, code, what
end
end
--
-- @brief An overloaded os.remove() that will be able to handle list of files,
-- as well as wildcards for files. Uses the syntax os.matchfiles() for
-- matching pattern wildcards.
--
-- @param f A file, a wildcard, or a list of files or wildcards to be removed
--
-- @return true on success, false and an appropriate error message on error
--
-- @example ok, err = os.remove{"**.bak", "**.log"}
-- if not ok then
-- error(err)
-- end
--
local builtin_remove = os.remove
function os.remove(f)
-- in case of string, just match files
if type(f) == "string" then
local p = os.matchfiles(f)
for _, v in pairs(p) do
local ok, err, code = builtin_remove(v)
if not ok then
return ok, err, code
end
end
if #p == 0 then
return nil, "Couldn't find any file matching: " .. f, 1
end
-- in case of table, match files for every table entry
elseif type(f) == "table" then
for _, v in pairs(f) do
local ok, err, code = os.remove(v)
if not ok then
return ok, err, code
end
end
end
return true
end
--
-- Remove a directory, along with any contained files or subdirectories.
--
-- @return true on success, false and an appropriate error message on error
local builtin_rmdir = os.rmdir
function os.rmdir(p)
-- recursively remove subdirectories
local dirs = os.matchdirs(p .. "/*")
for _, dname in ipairs(dirs) do
local ok, err = os.rmdir(dname)
if not ok then
return ok, err
end
end
-- remove any files
local files = os.matchfiles(p .. "/*")
for _, fname in ipairs(files) do
local ok, err = os.remove(fname)
if not ok then
return ok, err
end
end
-- remove this directory
return builtin_rmdir(p)
end
---
-- Return information about a file.
---
premake.override(os, "stat", function(base, p)
p = path.normalize(p)
return base(p)
end)
---
-- Translate command tokens into their OS or action specific equivalents.
---
os.commandTokens = {
_ = {
chdir = function(v)
return "cd " .. path.normalize(v)
end,
copy = function(v)
return "cp -rf " .. path.normalize(v)
end,
copyfile = function(v)
return "cp -f " .. path.normalize(v)
end,
copydir = function(v)
return "cp -rf " .. path.normalize(v)
end,
delete = function(v)
return "rm -f " .. path.normalize(v)
end,
echo = function(v)
return "echo " .. v
end,
mkdir = function(v)
return "mkdir -p " .. path.normalize(v)
end,
move = function(v)
return "mv -f " .. path.normalize(v)
end,
rmdir = function(v)
return "rm -rf " .. path.normalize(v)
end,
touch = function(v)
return "touch " .. path.normalize(v)
end,
},
windows = {
chdir = function(v)
return "chdir " .. path.translate(path.normalize(v))
end,
copy = function(v)
v = path.translate(path.normalize(v))
-- Detect if there's multiple parts to the input, if there is grab the first part else grab the whole thing
local src = string.match(v, '^".-"') or string.match(v, '^.- ') or v
-- Strip the trailing space from the second condition so that we don't have a space between src and '\\NUL'
src = string.match(src, '^.*%S')
return "IF EXIST " .. src .. "\\ (xcopy /Q /E /Y /I " .. v .. " > nul) ELSE (xcopy /Q /Y /I " .. v .. " > nul)"
end,
copyfile = function(v)
v = path.translate(path.normalize(v))
-- XCOPY doesn't have a switch to assume destination is a file when it doesn't exist.
-- A trailing * will suppress the prompt but requires the file extensions be the same length.
-- Just use COPY instead, it actually works.
return "copy /B /Y " .. v
end,
copydir = function(v)
v = path.translate(path.normalize(v))
return "xcopy /Q /E /Y /I " .. v
end,
delete = function(v)
return "del " .. path.translate(path.normalize(v))
end,
echo = function(v)
return "echo " .. v
end,
mkdir = function(v)
v = path.translate(path.normalize(v))
return "IF NOT EXIST " .. v .. " (mkdir " .. v .. ")"
end,
move = function(v)
return "move /Y " .. path.translate(path.normalize(v))
end,
rmdir = function(v)
return "rmdir /S /Q " .. path.translate(path.normalize(v))
end,
touch = function(v)
v = path.translate(path.normalize(v))
return string.format("type nul >> %s && copy /b %s+,, %s", v, v, v)
end,
}
}
function os.translateCommands(cmd, map)
map = map or os.target()
if type(map) == "string" then
map = os.commandTokens[map] or os.commandTokens["_"]
end
local processOne = function(cmd)
local i, j, prev
repeat
i, j = cmd:find("{.-}")
if i then
if i == prev then
break
end
local token = cmd:sub(i + 1, j - 1):lower()
local args = cmd:sub(j + 2)
local func = map[token] or os.commandTokens["_"][token]
if func then
cmd = cmd:sub(1, i -1) .. func(args)
end
prev = i
end
until i == nil
return cmd
end
if type(cmd) == "table" then
local result = {}
for i = 1, #cmd do
result[i] = processOne(cmd[i])
end
return result
else
return processOne(cmd)
end
end
---
-- Apply os slashes for decorated command paths.
---
function os.translateCommandAndPath(dir, map)
if map == 'windows' then
return path.translate(dir)
end
return dir
end
---
-- Translate decorated command paths into their OS equivalents.
---
function os.translateCommandsAndPaths(cmds, basedir, location, map)
local translatedBaseDir = path.getrelative(location, basedir)
map = map or os.target()
local translateFunction = function(value)
local result = path.join(translatedBaseDir, value)
result = os.translateCommandAndPath(result, map)
if value:endswith('/') or value:endswith('\\') or -- if orginal path ends with a slash then ensure the same
value:endswith('/"') or value:endswith('\\"') then
result = result .. '/'
end
return result
end
local processOne = function(cmd)
local replaceFunction = function(value)
value = value:sub(3, #value - 1)
return '"' .. translateFunction(value) .. '"'
end
return string.gsub(cmd, "%%%[[^%]\r\n]*%]", replaceFunction)
end
if type(cmds) == "table" then
local result = {}
for i = 1, #cmds do
result[i] = processOne(cmds[i])
end
return os.translateCommands(result, map)
else
return os.translateCommands(processOne(cmds), map)
end
end
--
-- Generate a UUID.
--
os._uuids = {}
local builtin_uuid = os.uuid
function os.uuid(name)
local id = builtin_uuid(name)
if name then
if os._uuids[id] and os._uuids[id] ~= name then
premake.warnOnce(id, "UUID clash between %s and %s", os._uuids[id], name)
end
os._uuids[id] = name
end
return id
end
--
-- Get a set of tags for different 'platforms'
--
os.systemTags =
{
["aix"] = { "aix", "posix" },
["bsd"] = { "bsd", "posix" },
["haiku"] = { "haiku", "posix" },
["ios"] = { "ios", "darwin", "posix", "mobile" },
["linux"] = { "linux", "posix" },
["macosx"] = { "macosx", "darwin", "posix" },
["solaris"] = { "solaris", "posix" },
["windows"] = { "windows", "win32" },
}
function os.getSystemTags(name)
return os.systemTags[name:lower()] or { name:lower() }
end

View File

@@ -0,0 +1,792 @@
--
-- base/oven.lua
--
-- Process the workspaces, projects, and configurations that were specified
-- by the project script, and make them suitable for use by the exporters
-- and actions. Fills in computed values (e.g. object directories) and
-- optimizes the layout of the data for faster fetches.
--
-- Copyright (c) 2002-2014 Jason Perkins and the Premake project
--
local p = premake
p.oven = {}
local oven = p.oven
local context = p.context
--
-- These fields get special treatment, "bubbling up" from the configurations
-- to the project. This allows you to express, for example: "use this config
-- map if this configuration is present in the project", and saves the step
-- of clearing the current configuration filter before creating the map.
--
p.oven.bubbledFields = {
configmap = true,
vpaths = true
}
---
-- Traverses the container hierarchy built up by the project scripts and
-- filters, merges, and munges the information based on the current runtime
-- environment in preparation for doing work on the results, like exporting
-- project files.
--
-- This call replaces the existing the container objects with their
-- processed replacements. If you are using the provided container APIs
-- (p.global.*, p.workspace.*, etc.) this will be transparent.
---
function oven.bake()
-- reset the root _isBaked state.
-- this really only affects the unit-tests, since that is the only place
-- where multiple bakes per 'exe run' happen.
local root = p.api.rootContainer()
root._isBaked = false;
p.container.bake(root)
end
function oven.bakeWorkspace(wks)
return p.container.bake(wks)
end
p.alias(oven, "bakeWorkspace", "bakeSolution")
local function addCommonContextFilters(self)
context.addFilter(self, "_ACTION", _ACTION)
context.addFilter(self, "action", _ACTION)
self.system = self.system or os.target()
context.addFilter(self, "system", os.getSystemTags(self.system))
context.addFilter(self, "host", os.getSystemTags(os.host()))
-- Add command line options to the filtering options
local options = {}
for key, value in pairs(_OPTIONS) do
local term = key
if value ~= "" then
term = term .. "=" .. tostring(value)
end
table.insert(options, term)
end
context.addFilter(self, "_OPTIONS", options)
context.addFilter(self, "options", options)
end
---
-- Bakes a specific workspace object.
---
function p.workspace.bake(self)
-- Add filtering terms to the context and then compile the results. These
-- terms describe the "operating environment"; only results contained by
-- configuration blocks which match these terms will be returned.
addCommonContextFilters(self)
-- Set up my token expansion environment
self.environ = {
wks = self,
sln = self,
}
context.compile(self)
-- Specify the workspaces's file system location; when path tokens are
-- expanded in workspace values, they will be made relative to this.
self.location = self.location or self.basedir
context.basedir(self, self.location)
-- Build a master list of configuration/platform pairs from all of the
-- projects contained by the workspace; I will need this when generating
-- workspace files in order to provide a map from workspace configurations
-- to project configurations.
self.configs = oven.bakeConfigs(self)
-- Now bake down all of the projects contained in the workspace, and
-- store that for future reference
p.container.bakeChildren(self)
-- I now have enough information to assign unique object directories
-- to each project configuration in the workspace.
oven.bakeObjDirs(self)
-- now we can post process the projects for 'buildoutputs' files
-- that have the 'compilebuildoutputs' flag
oven.addGeneratedFiles(self)
end
function oven.addGeneratedFiles(wks)
local function addGeneratedFile(cfg, source, filename)
-- mark that we have generated files.
cfg.project.hasGeneratedFiles = true
-- add generated file to the project.
local files = cfg.project._.files
local node = files[filename]
if not node then
node = p.fileconfig.new(filename, cfg.project)
files[filename] = node
table.insert(files, node)
end
-- always overwrite the dependency information.
node.dependsOn = source
node.generated = true
-- add to config if not already added.
if not p.fileconfig.getconfig(node, cfg) then
p.fileconfig.addconfig(node, cfg)
end
end
local function addFile(cfg, node)
local filecfg = p.fileconfig.getconfig(node, cfg)
if not filecfg or filecfg.flags.ExcludeFromBuild or not filecfg.compilebuildoutputs then
return
end
if p.fileconfig.hasCustomBuildRule(filecfg) then
local buildoutputs = filecfg.buildoutputs
if buildoutputs and #buildoutputs > 0 then
for _, output in ipairs(buildoutputs) do
if not path.islinkable(output) then
addGeneratedFile(cfg, node, output)
end
end
end
end
end
for prj in p.workspace.eachproject(wks) do
local files = table.shallowcopy(prj._.files)
for cfg in p.project.eachconfig(prj) do
table.foreachi(files, function(node)
addFile(cfg, node)
end)
end
-- generated files might screw up the object sequences.
if prj.hasGeneratedFiles and p.project.isnative(prj) then
oven.assignObjectSequences(prj)
end
end
end
function p.project.bake(self)
verbosef(' Baking %s...', self.name)
self.solution = self.workspace
self.global = self.workspace.global
local wks = self.workspace
-- Add filtering terms to the context to make it as specific as I can.
-- Start with the same filtering that was applied at the workspace level.
context.copyFilters(self, wks)
-- Now filter on the current system and architecture, allowing the
-- values that might already in the context to override my defaults.
self.system = self.system or os.target()
context.addFilter(self, "system", os.getSystemTags(self.system))
context.addFilter(self, "host", os.getSystemTags(os.host()))
context.addFilter(self, "architecture", self.architecture)
context.addFilter(self, "tags", self.tags)
-- The kind is a configuration level value, but if it has been set at the
-- project level allow that to influence the other project-level results.
context.addFilter(self, "kind", self.kind)
-- Allow the project object to also be treated like a configuration
self.project = self
-- Populate the token expansion environment
self.environ = {
wks = wks,
sln = wks,
prj = self,
}
-- Go ahead and distill all of that down now; this is my new project object
context.compile(self)
p.container.bakeChildren(self)
-- Set the context's base directory to the project's file system
-- location. Any path tokens which are expanded in non-path fields
-- are made relative to this, ensuring a portable generated project.
self.location = self.location or self.basedir
context.basedir(self, self.location)
-- This bit could use some work: create a canonical set of configurations
-- for the project, along with a mapping from the workspace's configurations.
-- This works, but it could probably be simplified.
local cfgs = table.fold(self.configurations or {}, self.platforms or {})
oven.bubbleFields(self, self, cfgs)
self._cfglist = oven.bakeConfigList(self, cfgs)
-- Don't allow a project-level system setting to influence the configurations
local projectSystem = self.system
self.system = nil
-- Finally, step through the list of configurations I built above and
-- bake all of those down into configuration contexts as well. Store
-- the results with the project.
self.configs = {}
for _, pairing in ipairs(self._cfglist) do
local buildcfg = pairing[1]
local platform = pairing[2]
local cfg = oven.bakeConfig(wks, self, buildcfg, platform)
if p.action.supportsconfig(p.action.current(), cfg) then
self.configs[(buildcfg or "*") .. (platform or "")] = cfg
end
end
-- Process the sub-objects that are contained by this project. The
-- configuration build stuff above really belongs in here now.
self._ = {}
self._.files = oven.bakeFiles(self)
-- If this type of project generates object files, look for files that will
-- generate object name collisions (i.e. src/hello.cpp and tests/hello.cpp
-- both create hello.o) and assign unique sequence numbers to each. I need
-- to do this up front to make sure the sequence numbers are the same for
-- all the tools, even they reorder the source file list.
if p.project.isnative(self) then
oven.assignObjectSequences(self)
end
-- at the end, restore the system, so it's usable elsewhere.
self.system = projectSystem
end
function p.rule.bake(self)
-- Add filtering terms to the context and then compile the results. These
-- terms describe the "operating environment"; only results contained by
-- configuration blocks which match these terms will be returned.
addCommonContextFilters(self)
-- Populate the token expansion environment
self.environ = {
rule = self,
}
-- Go ahead and distill all of that down now; this is my new rule object
context.compile(self)
-- sort the propertydefinition table.
table.sort(self.propertydefinition, function (a, b)
return a.name < b.name
end)
-- Set the context's base directory to the rule's file system
-- location. Any path tokens which are expanded in non-path fields
-- are made relative to this, ensuring a portable generated rule.
self.location = self.location or self.basedir
context.basedir(self, self.location)
end
--
-- Assigns a unique objects directory to every configuration of every project
-- in the workspace, taking any objdir settings into account, to ensure builds
-- from different configurations won't step on each others' object files.
-- The path is built from these choices, in order:
--
-- [1] -> the objects directory as set in the config
-- [2] -> [1] + the platform name
-- [3] -> [2] + the build configuration name
-- [4] -> [3] + the project name
--
-- @param wks
-- The workspace to process. The directories are modified inline.
--
function oven.bakeObjDirs(wks)
-- function to compute the four options for a specific configuration
local function getobjdirs(cfg)
-- the "!" prefix indicates the directory is not to be touched
local objdir = cfg.objdir or "obj"
local i = objdir:find("!", 1, true)
if i then
cfg.objdir = objdir:sub(1, i - 1) .. objdir:sub(i + 1)
return nil
end
local dirs = {}
local dir = path.getabsolute(path.join(cfg.project.location, objdir))
table.insert(dirs, dir)
if cfg.platform then
dir = path.join(dir, cfg.platform)
table.insert(dirs, dir)
end
dir = path.join(dir, cfg.buildcfg)
table.insert(dirs, dir)
dir = path.join(dir, cfg.project.name)
table.insert(dirs, dir)
return dirs
end
-- walk all of the configs in the workspace, and count the number of
-- times each obj dir gets used
local counts = {}
local configs = {}
for prj in p.workspace.eachproject(wks) do
for cfg in p.project.eachconfig(prj) do
-- get the dirs for this config, and associate them together,
-- and increment a counter for each one discovered
local dirs = getobjdirs(cfg)
if dirs then
configs[cfg] = dirs
for _, dir in ipairs(dirs or {}) do
counts[dir] = (counts[dir] or 0) + 1
end
end
end
end
-- now walk the list again, and assign the first unique value
for cfg, dirs in pairs(configs) do
for _, dir in ipairs(dirs) do
if counts[dir] == 1 then
cfg.objdir = dir
break
end
end
end
end
--
-- Create a list of workspace-level build configuration/platform pairs.
--
function oven.bakeConfigs(wks)
local buildcfgs = wks.configurations or {}
local platforms = wks.platforms or {}
local configs = {}
local pairings = table.fold(buildcfgs, platforms)
for _, pairing in ipairs(pairings) do
local cfg = oven.bakeConfig(wks, nil, pairing[1], pairing[2])
if p.action.supportsconfig(p.action.current(), cfg) then
table.insert(configs, cfg)
end
end
return configs
end
--
-- It can be useful to state "use this map if this configuration is present".
-- To allow this to happen, config maps that are specified within a project
-- configuration are allowed to "bubble up" to the top level. Currently,
-- maps are the only values that get this special behavior.
--
-- @param ctx
-- The project context information.
-- @param cset
-- The project's original configuration set, which contains the settings
-- of all the project configurations.
-- @param cfgs
-- The list of the project's build cfg/platform pairs.
--
function oven.bubbleFields(ctx, cset, cfgs)
-- build a query filter that will match any configuration name,
-- within the existing constraints of the project
local configurations = {}
local platforms = {}
for _, cfg in ipairs(cfgs) do
if cfg[1] then
table.insert(configurations, cfg[1]:lower())
end
if cfg[2] then
table.insert(platforms, cfg[2]:lower())
end
end
local terms = table.deepcopy(ctx.terms)
terms.configurations = configurations
terms.platforms = platforms
for key in pairs(oven.bubbledFields) do
local field = p.field.get(key)
if not field then
ctx[key] = rawget(ctx, key)
else
local value = p.configset.fetch(cset, field, terms, ctx)
if value then
ctx[key] = value
end
end
end
end
--
-- Builds a list of build configuration/platform pairs for a project,
-- along with a mapping between the workspace and project configurations.
--
-- @param ctx
-- The project context information.
-- @param cfgs
-- The list of the project's build cfg/platform pairs.
-- @return
-- An array of the project's build configuration/platform pairs,
-- based on any discovered mappings.
--
function oven.bakeConfigList(ctx, cfgs)
-- run them all through the project's config map
for i, cfg in ipairs(cfgs) do
cfgs[i] = p.project.mapconfig(ctx, cfg[1], cfg[2])
end
-- walk through the result and remove any duplicates
local buildcfgs = {}
local platforms = {}
for _, pairing in ipairs(cfgs) do
local buildcfg = pairing[1]
local platform = pairing[2]
if not table.contains(buildcfgs, buildcfg) then
table.insert(buildcfgs, buildcfg)
end
if platform and not table.contains(platforms, platform) then
table.insert(platforms, platform)
end
end
-- merge these de-duped lists back into pairs for the final result
return table.fold(buildcfgs, platforms)
end
---
-- Flattens out the build settings for a particular build configuration and
-- platform pairing, and returns the result.
--
-- @param wks
-- The workpace which contains the configuration data.
-- @param prj
-- The project which contains the configuration data. Can be nil.
-- @param buildcfg
-- The target build configuration, a value from configurations().
-- @param platform
-- The target platform, a value from platforms().
-- @param extraFilters
-- Optional. Any extra filter terms to use when retrieving the data for
-- this configuration
---
function oven.bakeConfig(wks, prj, buildcfg, platform, extraFilters)
-- Set the default system and architecture values; if the platform's
-- name matches a known system or architecture, use that as the default.
-- More than a convenience; this is required to work properly with
-- external Visual Studio project files.
local system = os.target()
local architecture = nil
local toolset = p.action.current().toolset
if platform then
system = p.api.checkValue(p.fields.system, platform) or system
architecture = p.api.checkValue(p.fields.architecture, platform) or architecture
toolset = p.api.checkValue(p.fields.toolset, platform) or toolset
end
-- Wrap the projects's configuration set (which contains all of the information
-- provided by the project script) with a context object. The context handles
-- the expansion of tokens, and caching of retrieved values. The environment
-- values are used when expanding tokens.
local environ = {
wks = wks,
sln = wks,
prj = prj,
}
local ctx = context.new(prj or wks, environ)
ctx.project = prj
ctx.workspace = wks
ctx.solution = wks
ctx.global = wks.global
ctx.buildcfg = buildcfg
ctx.platform = platform
ctx.action = _ACTION
-- Allow the configuration information to be accessed by tokens contained
-- within the configuration itself
environ.cfg = ctx
-- Add filtering terms to the context and then compile the results. These
-- terms describe the "operating environment"; only results contained by
-- configuration blocks which match these terms will be returned. Start
-- by copying over the top-level environment from the workspace. Don't
-- copy the project terms though, so configurations can override those.
context.copyFilters(ctx, wks)
context.addFilter(ctx, "configurations", buildcfg)
context.addFilter(ctx, "platforms", platform)
if prj then
context.addFilter(ctx, "language", prj.language)
end
-- allow the project script to override the default system
ctx.system = ctx.system or system
context.addFilter(ctx, "system", os.getSystemTags(ctx.system))
context.addFilter(ctx, "host", os.getSystemTags(os.host()))
-- allow the project script to override the default architecture
ctx.architecture = ctx.architecture or architecture
context.addFilter(ctx, "architecture", ctx.architecture)
-- allow the project script to override the default toolset
ctx.toolset = _OPTIONS.cc or ctx.toolset or toolset
context.addFilter(ctx, "toolset", ctx.toolset)
-- if a kind is set, allow that to influence the configuration
context.addFilter(ctx, "kind", ctx.kind)
-- if a sharedlibtype is set, allow that to influence the configuration
context.addFilter(ctx, "sharedlibtype", ctx.sharedlibtype)
-- if tags are set, allow that to influence the configuration
context.addFilter(ctx, "tags", ctx.tags)
-- if any extra filters were specified, can include them now
if extraFilters then
for k, v in pairs(extraFilters) do
context.addFilter(ctx, k, v)
end
end
context.compile(ctx)
ctx.location = ctx.location or prj and prj.location
context.basedir(ctx, ctx.location)
-- Fill in a few calculated for the configuration, including the long
-- and short names and the build and link target.
oven.finishConfig(ctx)
return ctx
end
--
-- Create configuration objects for each file contained in the project. This
-- collects and collates all of the values specified in the project scripts,
-- and computes extra values like the relative path and object names.
--
-- @param prj
-- The project object being baked. The project
-- @return
-- A collection of file configurations, keyed by both the absolute file
-- path and an alpha-sorted index.
--
function oven.bakeFiles(prj)
local files = {}
-- Start by building a comprehensive list of all the files contained by the
-- project. Some files may only be included in a subset of configurations so
-- I need to look at them all.
for cfg in p.project.eachconfig(prj) do
local function addFile(fname, i)
-- If this is the first time I've seen this file, start a new
-- file configuration for it. Track both by key for quick lookups
-- and indexed for ordered iteration.
local fcfg = files[fname]
if not fcfg then
fcfg = p.fileconfig.new(fname, prj)
fcfg.order = i
files[fname] = fcfg
table.insert(files, fcfg)
end
p.fileconfig.addconfig(fcfg, cfg)
end
table.foreachi(cfg.files, addFile)
-- If this project uses NuGet, we need to add the generated
-- packages.config file to the project. Is there a better place to
-- do this?
if #prj.nuget > 0 and (_ACTION < "vs2017" or p.project.iscpp(prj)) then
addFile("packages.config")
end
end
-- Alpha sort the indices, so I will get consistent results in
-- the exported project files.
table.sort(files, function(a,b)
return a.vpath < b.vpath
end)
return files
end
--
-- Assign unique sequence numbers to any source code files that would generate
-- conflicting object file names (i.e. src/hello.cpp and tests/hello.cpp both
-- create hello.o).
--
-- a file list of: src/hello.cpp, tests/hello.cpp and src/hello1.cpp also generates
-- conflicting object file names - hello1.o
function oven.uniqueSequence(f, cfg, seq, bases)
while true do
f.sequence = seq[cfg] or 0
seq[cfg] = f.sequence + 1
if f.sequence == 0 then
-- first time seeing this objname
break
end
-- getting here has changed our sequence number, but this new "basename"
-- may still collide with files that actually end with this "sequence number"
-- so we have to check the bases table now
-- objname changes with the sequence number on every loop
local lowerobj = f.objname:lower()
if not bases[lowerobj] then
-- this is the first appearance of a file that produces this objname
-- intialize the table for any future basename that matches our objname
bases[lowerobj] = {}
end
if not bases[lowerobj][cfg] then
-- not a collision
-- start a sequence for a future basename that matches our objname for this cfg
bases[lowerobj][cfg] = 1
break
end
-- else we have a objname collision, try the next sequence number
end
end
function oven.assignObjectSequences(prj)
-- Iterate over the file configurations which were prepared and cached in
-- project.bakeFiles(); find buildable files with common base file names.
local bases = {}
table.foreachi(prj._.files, function(file)
-- Only consider sources that actually generate object files
if not path.isnativefile(file.abspath) then
return
end
-- For each base file name encountered, keep a count of the number of
-- collisions that have occurred for each project configuration. Use
-- this collision count to generate the unique object file names.
local lowerbase = file.basename:lower()
if not bases[lowerbase] then
bases[lowerbase] = {}
end
local sequences = bases[lowerbase]
for cfg in p.project.eachconfig(prj) do
local fcfg = p.fileconfig.getconfig(file, cfg)
if fcfg ~= nil and not fcfg.flags.ExcludeFromBuild then
oven.uniqueSequence(fcfg, cfg, sequences, bases)
end
end
-- Makefiles don't use per-configuration object names yet; keep
-- this around until they do. At which point I might consider just
-- storing the sequence number instead of the whole object name
oven.uniqueSequence(file, prj, sequences, bases)
end)
end
--
-- Finish the baking process for a workspace or project level configurations.
-- Doesn't bake per se, just fills in some calculated values.
--
function oven.finishConfig(cfg)
-- assign human-readable names
cfg.longname = table.concat({ cfg.buildcfg, cfg.platform }, "|")
cfg.shortname = table.concat({ cfg.buildcfg, cfg.platform }, " ")
cfg.shortname = cfg.shortname:gsub(" ", "_"):lower()
cfg.name = cfg.longname
-- compute build and link targets
if cfg.project and cfg.kind then
cfg.buildtarget = p.config.gettargetinfo(cfg)
cfg.buildtarget.relpath = p.project.getrelative(cfg.project, cfg.buildtarget.abspath)
cfg.linktarget = p.config.getlinkinfo(cfg)
cfg.linktarget.relpath = p.project.getrelative(cfg.project, cfg.linktarget.abspath)
end
end

View File

@@ -0,0 +1,304 @@
--
-- path.lua
-- Path manipulation functions.
-- Copyright (c) 2002-2014 Jason Perkins and the Premake project
--
--
-- Appends a file extension to the path. Verifies that the extension
-- isn't already present, and adjusts quotes as necessary.
--
function path.appendExtension(p, ext)
-- if the extension is nil or empty, do nothing
if not ext or ext == "" then
return p
end
-- if the path ends with a quote, pull it off
local endquote
if p:endswith('"') then
p = p:sub(1, -2)
endquote = '"'
end
-- add the extension if it isn't there already
if not path.hasextension(p, ext) then
p = p .. ext
end
-- put the quote back if necessary
if endquote then
p = p .. endquote
end
return p
end
path.appendextension = path.appendExtension
--
-- Retrieve the filename portion of a path, without any extension.
--
function path.getbasename(p)
local name = path.getname(p)
local i = name:findlast(".", true)
if (i) then
return name:sub(1, i - 1)
else
return name
end
end
--
-- Retrieve the directory portion of a path, or an empty string if
-- the path does not include a directory.
--
function path.getdirectory(p)
local i = p:findlast("/", true)
if (i) then
if i > 1 then i = i - 1 end
return p:sub(1, i)
else
return "."
end
end
--
-- Retrieve the drive letter, if a Windows path.
--
function path.getdrive(p)
local ch1 = p:sub(1,1)
local ch2 = p:sub(2,2)
if ch2 == ":" then
return ch1
end
end
--
-- Retrieve the file extension.
--
function path.getextension(p)
p = path.getname(p)
local i = p:findlast(".", true)
if (i) then
return p:sub(i)
else
return ""
end
end
--
-- Remove extension from path.
--
function path.removeextension(p)
local i = p:findlast(".", true)
if (i) then
if i > 1 then i = i - 1 end
return p:sub(1, i)
else
return ""
end
end
--
-- Retrieve the filename portion of a path.
--
function path.getname(p)
local i = p:findlast("[/\\]")
if (i) then
return p:sub(i + 1)
else
return p
end
end
--
-- Returns true if the filename has a particular extension.
--
-- @param fname
-- The file name to test.
-- @param extensions
-- The extension(s) to test. Maybe be a string or table.
--
function path.hasextension(fname, extensions)
local fext = path.getextension(fname):lower()
if type(extensions) == "table" then
for _, extension in pairs(extensions) do
if fext == extension then
return true
end
end
return false
else
return (fext == extensions)
end
end
--
-- Returns true if the filename represents various source languages.
--
function path.isasmfile(fname)
return path.hasextension(fname, { ".s" })
end
function path.iscfile(fname)
return path.hasextension(fname, { ".c" })
or path.isasmfile(fname) -- is this really right?
or path.isobjcfile(fname) -- there is code that depends on this behaviour, which would need to change
end
function path.iscppfile(fname)
return path.hasextension(fname, { ".cc", ".cpp", ".cxx", ".c++" })
or path.isobjcppfile(fname) -- is this really right?
or path.iscfile(fname)
end
function path.isobjcfile(fname)
return path.hasextension(fname, { ".m" })
end
function path.isobjcppfile(fname)
return path.hasextension(fname, { ".mm" })
end
function path.iscppheader(fname)
return path.hasextension(fname, { ".h", ".hh", ".hpp", ".hxx" })
end
--
-- Returns true if the filename represents a native language source file.
-- These checks are used to prevent passing non-code files to the compiler
-- in makefiles. It is not foolproof, but it has held up well. I'm open to
-- better suggestions.
--
function path.isnativefile(fname)
return path.iscfile(fname)
or path.iscppfile(fname)
or path.isasmfile(fname)
or path.isobjcfile(fname)
or path.isobjcppfile(fname)
end
--
-- Returns true if the filename represents an OS X framework.
--
function path.isframework(fname)
return path.hasextension(fname, ".framework")
end
---
-- Is this a type of file that can be linked?
---
function path.islinkable(fname)
return path.hasextension(fname, { ".o", ".obj", ".a", ".lib", ".so" })
end
--
-- Returns true if the filename represents an object file.
--
function path.isobjectfile(fname)
return path.hasextension(fname, { ".o", ".obj" })
end
--
-- Returns true if the filename represents a Windows resource file. This check
-- is used to prevent passing non-resources to the compiler in makefiles.
--
function path.isresourcefile(fname)
return path.hasextension(fname, ".rc")
end
--
-- Returns true if the filename represents a Windows idl file.
--
function path.isidlfile(fname)
return path.hasextension(fname, ".idl")
end
--
-- Returns true if the filename represents a hlsl shader file.
--
function path.ishlslfile(fname)
return path.hasextension(fname, ".hlsl")
end
--
-- Takes a path which is relative to one location and makes it relative
-- to another location instead.
--
function path.rebase(p, oldbase, newbase)
p = path.getabsolute(path.join(oldbase, p))
p = path.getrelative(newbase, p)
return p
end
--
-- Replace the file extension.
--
function path.replaceextension(p, newext)
local ext = path.getextension(p)
if not ext then
return p
end
if #newext > 0 and not newext:findlast(".", true) then
newext = "."..newext
end
return p:match("^(.*)"..ext.."$")..newext
end
--
-- Get the default seperator for path.translate
--
function path.getDefaultSeparator()
if os.istarget('windows') then
return '\\'
else
return '/'
end
end

View File

@@ -0,0 +1,432 @@
--
-- premake.lua
-- High-level helper functions for the project exporters.
-- Copyright (c) 2002-2015 Jason Perkins and the Premake project
--
local p = premake
-- Store captured output text for later testing
local _captured
-- The string escaping function.
local _esc = function(v) return v end
-- The output settings and defaults
local _eol = "\n"
local _indentString = "\t"
local _indentLevel = 0
-- Set up the global configuration scope. There can be only one.
global("root")
---
-- Capture and store everything sent through the output stream functions
-- premake.w(), premake.x(), and premake.out(). Retrieve the captured
-- text using the premake.captured() function.
--
-- @param fn
-- A function to execute. Any output calls made during the execution
-- of the function will be captured.
-- @return
-- The captured output.
---
function premake.capture(fn)
-- start a new capture without forgetting the old one
local old = _captured
_captured = buffered.new()
-- capture
fn()
-- build the result
local captured = p.captured()
-- free the capture buffer.
buffered.close(_captured)
-- restore the old capture and done
_captured = old
return captured
end
--
-- Returns the captured text and stops capturing.
--
function premake.captured()
if _captured then
return buffered.tostring(_captured)
else
return ""
end
end
---
-- Set the output stream end-of-line sequence.
--
-- @param s
-- The string to use to mark line ends, or nil to keep the existing
-- EOL sequence.
-- @return
-- The new EOL sequence.
---
function premake.eol(s)
_eol = s or _eol
return _eol
end
---
-- Handle escaping of strings for various outputs.
--
-- @param value
-- If this is a string: escape it and return the new value. If it is an
-- array, return a new array of escaped values.
-- @return
-- If the input was a single string, returns the escaped version. If it
-- was an array, returns an corresponding array of escaped strings.
---
function premake.esc(value)
if type(value) == "table" then
local result = {}
local n = #value
for i = 1, n do
table.insert(result, p.esc(value[i]))
end
return result
end
return _esc(value or "")
end
---
-- Set a new string escaping function.
--
-- @param func
-- The new escaping function, which should take a single string argument
-- and return the escaped version of that string. If nil, uses a default
-- no-op function.
---
function premake.escaper(func)
_esc = func
if not _esc then
_esc = function (value) return value end
end
end
--
-- Returns a boolean if the file was modified
-- Open a file for output, and call a function to actually do the writing.
-- Used by the actions to generate workspace and project files.
--
-- @param obj
-- A workspace or project object; will be passed to the callback function.
-- @param ext
-- An optional extension for the generated file, with the leading dot.
-- @param callback
-- The function responsible for writing the file, should take a workspace
-- or project as a parameters.
--
function premake.generate(obj, ext, callback)
local output = p.capture(function ()
_indentLevel = 0
callback(obj)
_indentLevel = 0
end)
local fn = p.filename(obj, ext)
-- make sure output folder exists.
local dir = path.getdirectory(fn)
local ok, err = os.mkdir(dir)
if not ok then
error(err, 0)
end
local f, err = os.writefile_ifnotequal(output, fn);
if (f == 0) then
return false -- file not modified
elseif (f < 0) then
error(err, 0)
elseif (f > 0) then
printf("Generated %s...", path.getrelative(os.getcwd(), fn))
return true -- file modified
end
end
--
-- Marks a file as modified without changing its contents
--
-- @param obj
-- A workspace or project object; will be passed to the callback function.
-- @param ext
-- An optional extension for the generated file, with the leading dot.
--
function premake.touch(obj, ext)
local fn = premake.filename(obj, ext)
-- make sure output folder exists.
local dir = path.getdirectory(fn)
local ok, err = os.mkdir(dir)
if not ok then
error(err, 0)
end
local f, err = os.touchfile(fn);
if (f == 0) then
return false -- file marked as modified
elseif (f < 0) then
error(err, 0)
elseif (f > 0) then
return true -- file created
end
end
---
-- Returns the full path a file generated from any of the project
-- objects (project, workspace, rule).
--
-- @param obj
-- The project object being generated.
-- @param ext
-- An optional extension for the generated file, with the leading dot.
---
function premake.filename(obj, ext)
local fname = obj.location or obj.basedir
if ext and not ext:startswith(".") then
fname = path.join(fname, ext)
else
fname = path.join(fname, obj.filename)
if ext then
fname = fname .. ext
end
end
return path.getabsolute(fname)
end
---
-- Sets the output indentation parameters.
--
-- @param s
-- The indentation string.
-- @param i
-- The new indentation level, or nil to reset to zero.
---
function premake.indent(s, i)
_indentString = s or "\t"
_indentLevel = i or 0
end
---
-- Write a simple, unformatted string to the output stream, with no indentation
-- or end of line sequence.
---
function premake.out(s)
if not _captured then
io.write(s)
else
buffered.write(_captured, s)
end
end
---
-- Write a simple, unformatted string to the output stream, with no indentation,
-- and append the current EOL sequence.
---
function premake.outln(s)
p.out(s)
p.out(_eol or "\n")
end
---
-- Write a formatted string to the exported file, after decreasing the
-- indentation level by one.
--
-- @param i
-- If set to a number, the indentation level will be decreased by
-- this amount. If nil, the indentation level is decremented and
-- no output is written. Otherwise, pass to premake.w() as the
-- formatting string, followed by any additional arguments.
---
function premake.pop(i, ...)
if i == nil or type(i) == "number" then
_indentLevel = _indentLevel - (i or 1)
else
_indentLevel = _indentLevel - 1
p.w(i, ...)
end
end
---
-- Write a formatted string to the exported file, and increase the
-- indentation level by one.
--
-- @param i
-- If set to a number, the indentation level will be increased by
-- this amount. If nil, the indentation level is incremented and
-- no output is written. Otherwise, pass to premake.w() as the
-- formatting string, followed by any additional arguments.
---
function premake.push(i, ...)
if i == nil or type(i) == "number" then
_indentLevel = _indentLevel + (i or 1)
else
p.w(i, ...)
_indentLevel = _indentLevel + 1
end
end
---
-- Wrap the provided value in double quotes if it contains spaces, or
-- if it contains a shell variable of the form $(...).
---
function premake.quoted(value)
local q = value:find(" ", 1, true)
if not q then
q = value:find("$%(.-%)", 1)
end
if q then
value = '"' .. value .. '"'
end
return value
end
--
-- Output a UTF-8 BOM to the exported file.
--
function p.utf8()
p.out('\239\187\191')
end
---
-- Write a formatted string to the exported file, at the current
-- level of indentation, and appends an end of line sequence.
-- This gets called quite a lot, hence the very short name.
---
function premake.w(...)
if select("#", ...) > 0 then
p.outln(string.rep(_indentString or "\t", _indentLevel) .. string.format(...))
else
p.outln('');
end
end
---
-- Write a formatted string to the exported file, after passing all
-- arguments (except for the first, which is the formatting string)
-- through premake.esc().
---
function premake.x(msg, ...)
local arg = {...}
for i = 1, #arg do
arg[i] = p.esc(arg[i])
end
p.w(msg, table.unpack(arg))
end
---
-- Write a opening XML element for a UTF-8 encoded file. Used by
-- several different files for different actions, so makes sense
-- to have a common call for it.
--
-- @param upper
-- If true, the encoding is written in uppercase.
---
function premake.xmlUtf8(upper)
local encoding = iif(upper, "UTF-8", "utf-8")
p.w('<?xml version="1.0" encoding="%s"?>', encoding)
end
--
-- These are the output shortcuts that I used before switching to the
-- indentation-aware calls above. They are still in use all over the
-- place, including lots of community code, so let's keep them around.
--
-- @param i
-- This will either be a printf-style formatting string suitable
-- for passing to string.format(), OR an integer number indicating
-- the desired level of indentation. If the latter, the formatting
-- string should be the next argument in the list.
-- @param ...
-- The values necessary to fill out the formatting string tokens.
--
function _p(i, ...)
if type(i) == "number" then
_indentLevel = i
p.w(...)
else
_indentLevel = 0
p.w(i, ...)
end
end
function _x(i, ...)
local arg = {...}
for i = 2, #arg do
arg[i] = p.esc(arg[i])
end
_p(i, table.unpack(arg))
end

View File

@@ -0,0 +1,563 @@
---
-- project.lua
-- Premake project object API
-- Author Jason Perkins
-- Copyright (c) 2011-2015 Jason Perkins and the Premake project
---
local p = premake
p.project = p.api.container("project", p.workspace, { "config" })
local project = p.project
local tree = p.tree
---
-- Alias the old external() call to the new externalproject(), to distinguish
-- between it and externalrule().
---
external = externalproject
---
-- Create a new project container instance.
---
function project.new(name)
local prj = p.container.new(project, name)
prj.uuid = os.uuid(name)
if p.api.scope.group then
prj.group = p.api.scope.group.name
else
prj.group = ""
end
return prj
end
--
-- Returns an iterator function for the configuration objects contained by
-- the project. Each configuration corresponds to a build configuration/
-- platform pair (i.e. "Debug|x86") as specified in the workspace.
--
-- @param prj
-- The project object to query.
-- @return
-- An iterator function returning configuration objects.
--
function project.eachconfig(prj)
local configs = prj._cfglist
local count = #configs
-- Once the configurations are mapped into the workspace I could get
-- the same one multiple times. Make sure that doesn't happen.
local seen = {}
local i = 0
return function ()
i = i + 1
if i <= count then
local cfg = project.getconfig(prj, configs[i][1], configs[i][2])
if not seen[cfg] then
seen[cfg] = true
return cfg
else
i = i + 1
end
end
end
end
--
-- When an exact match is not available (project.getconfig() returns nil), use
-- this function to find the closest alternative.
--
-- @param prj
-- The project object to query.
-- @param buildcfg
-- The name of the build configuration on which to filter.
-- @param platform
-- Optional; the name of the platform on which to filter.
-- @return
-- A configuration object.
--
function project.findClosestMatch(prj, buildcfg, platform)
-- One or both of buildcfg and platform do not match any of the project
-- configurations, otherwise I would have had an exact match. Map them
-- separately to apply any partial rules.
buildcfg = project.mapconfig(prj, buildcfg)[1]
platform = project.mapconfig(prj, platform)[1]
-- Replace missing values with whatever is first in the list
if not table.contains(prj.configurations, buildcfg) then
buildcfg = prj.configurations[1]
end
if not table.contains(prj.platforms, platform) then
platform = prj.platforms[1]
end
-- Now I should have a workable pairing
return project.getconfig(prj, buildcfg, platform)
end
-- Retrieve the project's configuration information for a particular build
-- configuration/platform pair.
--
-- @param prj
-- The project object to query.
-- @param buildcfg
-- The name of the build configuration on which to filter.
-- @param platform
-- Optional; the name of the platform on which to filter.
-- @return
-- A configuration object.
function project.getconfig(prj, buildcfg, platform)
-- if no build configuration is specified, return the "root" project
-- configurations, which includes all configuration values that
-- weren't set with a specific configuration filter
if not buildcfg then
return prj
end
-- apply any configuration mappings
local pairing = project.mapconfig(prj, buildcfg, platform)
buildcfg = pairing[1]
platform = pairing[2]
-- look up and return the associated config
local key = (buildcfg or "*") .. (platform or "")
return prj.configs[key]
end
---
-- Returns a list of sibling projects on which the specified project depends.
-- This is used to list dependencies within a workspace. Must consider all
-- configurations because Visual Studio does not support per-config project
-- dependencies.
--
-- @param prj
-- The project to query.
-- @param mode
-- if mode == 'linkOnly', returns only siblings which are linked against (links) and skips siblings which are not (dependson).
-- if mode == 'dependOnly' returns only siblings which are depended on (dependson) and skips siblings which are not (links).
-- @return
-- A list of dependent projects, as an array of project objects.
---
function project.getdependencies(prj, mode)
if not prj.dependencies then
prj.dependencies = {}
end
local m = mode or 'all'
local result = prj.dependencies[m]
if result then
return result
end
local function add_to_project_list(cfg, depproj, result)
local dep = p.workspace.findproject(cfg.workspace, depproj)
if dep and not table.contains(result, dep) then
table.insert(result, dep)
end
end
local linkOnly = m == 'linkOnly'
local depsOnly = m == 'dependOnly'
result = {}
for cfg in project.eachconfig(prj) do
if not depsOnly then
for _, link in ipairs(cfg.links) do
if link ~= prj.name then
add_to_project_list(cfg, link, result)
end
end
end
if not linkOnly then
for _, depproj in ipairs(cfg.dependson) do
add_to_project_list(cfg, depproj, result)
end
end
end
prj.dependencies[m] = result
return result
end
--
-- Return the first configuration of a project, which is used in some
-- actions to generate project-wide defaults.
--
-- @param prj
-- The project object to query.
-- @return
-- The first configuration in a project, as would be returned by
-- eachconfig().
--
function project.getfirstconfig(prj)
local iter = project.eachconfig(prj)
local first = iter()
return first
end
--
-- Return the relative path from the project to the specified file.
--
-- @param prj
-- The project object to query.
-- @param filename
-- The file path, or an array of file paths, to convert.
-- @return
-- The relative path, or array of paths, from the project to the file.
--
function project.getrelative(prj, filename)
if type(filename) == "table" then
local result = {}
for i, name in ipairs(filename) do
result[i] = project.getrelative(prj, name)
end
return result
else
if filename then
local result = filename
if path.hasdeferredjoin(result) then
result = path.resolvedeferredjoin(result)
end
return path.getrelative(prj.location, result)
end
end
end
--
-- Create a tree from a project's list of source files.
--
-- @param prj
-- The project to query.
-- @param sorter
-- An optional comparator function for the sorting pass.
-- @return
-- A tree object containing the source file hierarchy. Leaf nodes,
-- representing the individual files, are file configuration
-- objects.
--
function project.getsourcetree(prj, sorter)
if prj._.sourcetree then
return prj._.sourcetree
end
local tr = tree.new(prj.name)
table.foreachi(prj._.files, function(fcfg)
-- if the file is a generated file, we add those in a second pass.
if fcfg.generated then
return;
end
-- The tree represents the logical source code tree to be displayed
-- in the IDE, not the physical organization of the file system. So
-- virtual paths are used when adding nodes.
-- If the project script specifies a virtual path for a file, disable
-- the logic that could trim out empty root nodes from that path. If
-- the script writer wants an empty root node they should get it.
local flags
if fcfg.vpath ~= fcfg.relpath then
flags = { trim = false }
end
-- Virtual paths can overlap, potentially putting files with the same
-- name in the same folder, even though they have different paths on
-- the underlying filesystem. The tree.add() call won't overwrite
-- existing nodes, so provide the extra logic here. Start by getting
-- the parent folder node, creating it if necessary.
local parent = tree.add(tr, path.getdirectory(fcfg.vpath), flags)
local node = tree.insert(parent, tree.new(path.getname(fcfg.vpath)))
-- Pass through value fetches to the file configuration
setmetatable(node, { __index = fcfg })
end)
table.foreachi(prj._.files, function(fcfg)
-- if the file is not a generated file, we already added them
if not fcfg.generated then
return;
end
local parent = tree.add(tr, path.getdirectory(fcfg.dependsOn.vpath))
local node = tree.insert(parent, tree.new(path.getname(fcfg.vpath)))
-- Pass through value fetches to the file configuration
setmetatable(node, { __index = fcfg })
end)
tree.trimroot(tr)
tree.sort(tr, sorter)
prj._.sourcetree = tr
return tr
end
--
-- Given a source file path, return a corresponding virtual path based on
-- the vpath entries in the project. If no matching vpath entry is found,
-- the original path is returned.
--
function project.getvpath(prj, abspath)
-- If there is no match, the result is the original filename
local vpath = abspath
-- The file's name must be maintained in the resulting path; use these
-- to make sure I don't cut off too much
local fname = path.getname(abspath)
local max = abspath:len() - fname:len()
-- Look for matching patterns. Virtual paths are stored as an array
-- for tables, each table continuing the path key, which looks up the
-- array of paths with should match against that path.
for _, vpaths in ipairs(prj.vpaths) do
for replacement, patterns in pairs(vpaths) do
for _, pattern in ipairs(patterns) do
local i = abspath:find(path.wildcards(pattern))
if i == 1 then
-- Trim out the part of the name that matched the pattern; what's
-- left is the part that gets appended to the replacement to make
-- the virtual path. So a pattern like "src/**.h" matching the
-- file src/include/hello.h, I want to trim out the src/ part,
-- leaving include/hello.h.
-- Find out where the wildcard appears in the match. If there is
-- no wildcard, the match includes the entire pattern
i = pattern:find("*", 1, true) or (pattern:len() + 1)
-- Trim, taking care to keep the actual file name intact.
local leaf
if i < max then
leaf = abspath:sub(i)
else
leaf = fname
end
if leaf:startswith("/") then
leaf = leaf:sub(2)
end
-- check for (and remove) stars in the replacement pattern.
-- If there are none, then trim all path info from the leaf
-- and use just the filename in the replacement (stars should
-- really only appear at the end; I'm cheating here)
local stem = ""
if replacement:len() > 0 then
stem, stars = replacement:gsub("%*", "")
if stars == 0 then
leaf = path.getname(leaf)
end
else
leaf = path.getname(leaf)
end
vpath = path.join(stem, leaf)
return vpath
end
end
end
end
return vpath
end
--
-- Determines if project contains a configuration meeting certain criteria.
--
-- @param prj
-- The project to query.
-- @param func
-- A test function. Takes a project configuration as an argument and
-- returns a boolean result of the test.
-- @return
-- True if the test function returned true.
--
function project.hasConfig(prj, func)
for cfg in project.eachconfig(prj) do
if func(cfg) then
return true
end
end
end
--
-- Determines if a project contains a particular source code file.
--
-- @param prj
-- The project to query.
-- @param filename
-- The absolute path to the source code file being checked.
-- @return
-- True if the file belongs to the project, in any configuration.
--
function project.hasfile(prj, filename)
return (prj._.files[filename] ~= nil)
end
--
-- Returns true if the project uses a .NET language.
--
function project.isdotnet(prj)
return
p.languages.iscsharp(prj.language) or
p.languages.isfsharp(prj.language)
end
--
-- Returns true if the project uses a C# language.
--
function project.iscsharp(prj)
return p.languages.iscsharp(prj.language)
end
--
-- Returns true if the project uses a F# language.
--
function project.isfsharp(prj)
return p.languages.isfsharp(prj.language)
end
--
-- Returns true if the project uses a cpp language.
--
function project.isc(prj)
return p.languages.isc(prj.language)
end
--
-- Returns true if the project uses a cpp language.
--
function project.iscpp(prj)
return p.languages.iscpp(prj.language)
end
--
-- Returns true if the project has uses any 'native' languages.
-- which is basically anything other then .net at this point.
-- modules like the dlang should overload this to add 'project.isd(prj)' to it.
--
function project.isnative(prj)
return project.isc(prj) or project.iscpp(prj)
end
--
-- Given a build config/platform pairing, applies any project configuration maps
-- and returns a new (or the same) pairing.
--
-- TODO: I think this could be made much simpler by building a string pattern
-- like :part1:part2: and then doing string comparisions, instead of trying to
-- iterate over variable number of table elements.
--
function project.mapconfig(prj, buildcfg, platform)
local pairing = { buildcfg, platform }
local testpattern = function(pattern, pairing, i)
local j = 1
while i <= #pairing and j <= #pattern do
local wd = path.wildcards(pattern[j])
if pairing[i]:match(wd) ~= pairing[i] then
return false
end
i = i + 1
j = j + 1
end
return true
end
local maps = prj.configmap or {}
for mi = 1, #maps do
for pattern, replacements in pairs(maps[mi]) do
if type(pattern) ~= "table" then
pattern = { pattern }
end
-- does this pattern match any part of the pair? If so,
-- replace it with the corresponding values
for i = 1, #pairing do
if testpattern(pattern, pairing, i) then
if #pattern == 1 and #replacements == 1 then
pairing[i] = replacements[1]
else
pairing = { replacements[1], replacements[2] }
end
end
end
end
end
return pairing
end
--
-- Given a project, returns requested min and max system versions.
--
function project.systemversion(prj)
if prj.systemversion ~= nil then
local values = string.explode(prj.systemversion, ":", true)
return values[1], values[2]
end
end

View File

@@ -0,0 +1,242 @@
---
-- base/rule.lua
-- Defines rule sets for generated custom rule files.
-- Copyright (c) 2014 Jason Perkins and the Premake project
---
local p = premake
p.rule = p.api.container("rule", p.global)
local rule = p.rule
---
-- Create a new rule container instance.
---
function rule.new(name)
local self = p.container.new(rule, name)
-- create a variable setting function. Do a version with lowercased
-- first letter(s) to match Premake's naming style for other calls
_G[name .. "Vars"] = function(vars)
rule.setVars(self, vars)
end
local lowerName = name:gsub("^%u+", string.lower)
_G[lowerName .. "Vars"] = _G[name .. "Vars"]
return self
end
---
-- Enumerate the property definitions for a rule.
---
function rule.eachProperty(self)
local props = self.propertydefinition
local i = 0
return function ()
i = i + 1
if i <= #props then
return props[i]
end
end
end
---
-- Find a property definition by its name.
--
-- @param name
-- The property name.
-- @returns
-- The property definition if found, nil otherwise.
---
function rule.getProperty(self, name)
local props = self.propertydefinition
for i = 1, #props do
local prop = props[i]
if prop.name == name then
return prop
end
end
end
---
-- Find the field definition for one this rule's properties. This field
-- can then be used with the api.* functions to manipulate the property's
-- values in the current configuration scope.
--
-- @param prop
-- The property definition.
-- @return
-- The field definition for the property; this will be created if it
-- does not already exist.
---
function rule.getPropertyField(self, prop)
if prop._field then
return prop._field
end
local kind = prop.kind or "string"
if kind == "list" then
kind = "list:string"
end
local fld = p.field.new {
name = "_rule_" .. self.name .. "_" .. prop.name,
scope = "config",
kind = kind,
tokens = true,
}
prop._field = fld
return fld
end
---
-- Set one or more rule variables in the current configuration scope.
--
-- @param vars
-- A key-value list of variables to set and their corresponding values.
---
function rule.setVars(self, vars)
for key, value in pairs(vars) do
local prop = rule.getProperty(self, key)
if not prop then
error (string.format("rule '%s' does not have property '%s'", self.name, key))
end
local fld = rule.getPropertyField(self, prop)
p.api.storeField(fld, value)
end
end
---
-- prepare an environment with the rule properties as global tokens,
-- according to the format specified.
--
-- @param environ
-- The environment table to fill up
-- @param format
-- The formatting to be used, ie "[%s]".
---
function rule.createEnvironment(self, format)
local environ = {}
for _, def in ipairs(self.propertydefinition) do
environ[def.name] = string.format(format, def.name)
end
return environ
end
---
-- prepare an table of pathVars with the rule properties as global tokens,
-- according to the format specified.
--
-- @param pathVars
-- The pathVars table to fill up
-- @param format
-- The formatting to be used, ie "%%(%s)".
---
function rule.preparePathVars(self, pathVars, format)
for _, def in ipairs(self.propertydefinition) do
pathVars[def.name] = { absolute = true, token = string.format(format, def.name) }
end
end
function rule.createPathVars(self, format)
local pathVars = {}
rule.preparePathVars(self, pathVars, format)
return pathVars
end
function rule.prepareEnvironment(self, environ, cfg)
local function path(cfg, value)
cfg = cfg.project or cfg
local dirs = path.translate(project.getrelative(cfg, value))
if type(dirs) == 'table' then
dirs = table.filterempty(dirs)
end
return dirs
end
local function expandRuleString(prop, value)
-- list
if type(value) == "table" then
if #value > 0 then
local switch = prop.switch or ""
if prop.separator then
return switch .. table.concat(value, prop.separator)
else
return switch .. table.concat(value, " " .. switch)
end
else
return nil
end
end
-- bool just emits the switch
if prop.switch and type(value) == "boolean" then
if value then
return prop.switch
else
return nil
end
end
-- enum
if prop.values then
local switch = prop.switch or {}
value = table.findKeyByValue(prop.values, value)
if value == nil then
return nil
end
return switch[value]
end
-- primitive
local switch = prop.switch or ""
value = tostring(value)
if #value > 0 then
return switch .. value
else
return nil
end
end
for _, prop in ipairs(self.propertydefinition) do
local fld = p.rule.getPropertyField(self, prop)
local value = cfg[fld.name]
if value ~= nil then
if fld.kind == "path" then
value = path(cfg, value)
elseif fld.kind == "list:path" then
value = path(cfg, value)
end
value = expandRuleString(prop, value)
if value ~= nil and #value > 0 then
environ[prop.name] = p.esc(value)
end
end
end
end

View File

@@ -0,0 +1,209 @@
local semver = {
_VERSION = '1.2.1',
_DESCRIPTION = 'semver for Lua',
_URL = 'https://github.com/kikito/semver.lua',
_LICENSE = [[
MIT LICENSE
Copyright (c) 2015 Enrique García Cota
Permission is hereby granted, free of charge, to any person obtaining a
copy of tother software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and tother permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
}
local function checkPositiveInteger(number, name)
assert(number >= 0, name .. ' must be a valid positive number')
assert(math.floor(number) == number, name .. ' must be an integer')
end
local function present(value)
return value and value ~= ''
end
-- splitByDot("a.bbc.d") == {"a", "bbc", "d"}
local function splitByDot(str)
str = str or ""
local t, count = {}, 0
str:gsub("([^%.]+)", function(c)
count = count + 1
t[count] = c
end)
return t
end
local function parsePrereleaseAndBuildWithSign(str)
local prereleaseWithSign, buildWithSign = str:match("^(-[^+]+)(+.+)$")
if not (prereleaseWithSign and buildWithSign) then
prereleaseWithSign = str:match("^(-.+)$")
buildWithSign = str:match("^(+.+)$")
end
assert(prereleaseWithSign or buildWithSign, ("The parameter %q must begin with + or - to denote a prerelease or a build"):format(str))
return prereleaseWithSign, buildWithSign
end
local function parsePrerelease(prereleaseWithSign)
if prereleaseWithSign then
local prerelease = prereleaseWithSign:match("^-(%w[%.%w-]*)$")
assert(prerelease, ("The prerelease %q is not a slash followed by alphanumerics, dots and slashes"):format(prereleaseWithSign))
return prerelease
end
end
local function parseBuild(buildWithSign)
if buildWithSign then
local build = buildWithSign:match("^%+(%w[%.%w-]*)$")
assert(build, ("The build %q is not a + sign followed by alphanumerics, dots and slashes"):format(buildWithSign))
return build
end
end
local function parsePrereleaseAndBuild(str)
if not present(str) then return nil, nil end
local prereleaseWithSign, buildWithSign = parsePrereleaseAndBuildWithSign(str)
local prerelease = parsePrerelease(prereleaseWithSign)
local build = parseBuild(buildWithSign)
return prerelease, build
end
local function parseVersion(str)
local sMajor, sMinor, sPatch, sPrereleaseAndBuild = str:match("^(%d+)%.?(%d*)%.?(%d*)(.-)$")
assert(type(sMajor) == 'string', ("Could not extract version number(s) from %q"):format(str))
local major, minor, patch = tonumber(sMajor), tonumber(sMinor), tonumber(sPatch)
local prerelease, build = parsePrereleaseAndBuild(sPrereleaseAndBuild)
return major, minor, patch, prerelease, build
end
-- return 0 if a == b, -1 if a < b, and 1 if a > b
local function compare(a,b)
return a == b and 0 or a < b and -1 or 1
end
local function compareIds(myId, otherId)
if myId == otherId then return 0
elseif not myId then return -1
elseif not otherId then return 1
end
local selfNumber, otherNumber = tonumber(myId), tonumber(otherId)
if selfNumber and otherNumber then -- numerical comparison
return compare(selfNumber, otherNumber)
-- numericals are always smaller than alphanums
elseif selfNumber then
return -1
elseif otherNumber then
return 1
else
return compare(myId, otherId) -- alphanumerical comparison
end
end
local function smallerIdList(myIds, otherIds)
local myLength = #myIds
local comparison
for i=1, myLength do
comparison = compareIds(myIds[i], otherIds[i])
if comparison ~= 0 then
return comparison == -1
end
-- if comparison == 0, continue loop
end
return myLength < #otherIds
end
local function smallerPrerelease(mine, other)
if mine == other or not mine then return false
elseif not other then return true
end
return smallerIdList(splitByDot(mine), splitByDot(other))
end
local methods = {}
function methods:nextMajor()
return semver(self.major + 1, 0, 0)
end
function methods:nextMinor()
return semver(self.major, self.minor + 1, 0)
end
function methods:nextPatch()
return semver(self.major, self.minor, self.patch + 1)
end
local mt = { __index = methods }
function mt:__eq(other)
return self.major == other.major and
self.minor == other.minor and
self.patch == other.patch and
self.prerelease == other.prerelease
-- notice that build is ignored for precedence in semver 2.0.0
end
function mt:__lt(other)
if self.major ~= other.major then return self.major < other.major end
if self.minor ~= other.minor then return self.minor < other.minor end
if self.patch ~= other.patch then return self.patch < other.patch end
return smallerPrerelease(self.prerelease, other.prerelease)
-- notice that build is ignored for precedence in semver 2.0.0
end
-- This works like the "pessimisstic operator" in Rubygems.
-- if a and b are versions, a ^ b means "b is backwards-compatible with a"
-- in other words, "it's safe to upgrade from a to b"
function mt:__pow(other)
if self.major == 0 then
return self == other
end
return self.major == other.major and
self.minor <= other.minor
end
function mt:__tostring()
local buffer = { ("%d.%d.%d"):format(self.major, self.minor, self.patch) }
if self.prerelease then table.insert(buffer, "-" .. self.prerelease) end
if self.build then table.insert(buffer, "+" .. self.build) end
return table.concat(buffer)
end
local function new(major, minor, patch, prerelease, build)
assert(major, "At least one parameter is needed")
if type(major) == 'string' then
major,minor,patch,prerelease,build = parseVersion(major)
end
patch = patch or 0
minor = minor or 0
checkPositiveInteger(major, "major")
checkPositiveInteger(minor, "minor")
checkPositiveInteger(patch, "patch")
local result = {major=major, minor=minor, patch=patch, prerelease=prerelease, build=build}
return setmetatable(result, mt)
end
setmetatable(semver, { __call = function(_, ...) return new(...) end })
semver._VERSION= semver(semver._VERSION)
return semver

View File

@@ -0,0 +1,103 @@
--
-- string.lua
-- Additions to Lua's built-in string functions.
-- Copyright (c) 2002-2013 Jason Perkins and the Premake project
--
--
-- Capitalize the first letter of the string.
--
function string.capitalized(self)
return self:gsub("^%l", string.upper)
end
--
-- Returns true if the string has a match for the plain specified pattern
--
function string.contains(s, match)
return string.find(s, match, 1, true) ~= nil
end
--
-- Returns an array of strings, each of which is a substring of s
-- formed by splitting on boundaries formed by `pattern`.
--
function string.explode(s, pattern, plain, maxTokens)
if (pattern == '') then return false end
local pos = 0
local arr = { }
for st,sp in function() return s:find(pattern, pos, plain) end do
table.insert(arr, s:sub(pos, st-1))
pos = sp + 1
if maxTokens ~= nil and maxTokens > 0 then
maxTokens = maxTokens - 1
if maxTokens == 0 then
break
end
end
end
table.insert(arr, s:sub(pos))
return arr
end
--
-- Find the last instance of a pattern in a string.
--
function string.findlast(s, pattern, plain)
local curr = 0
repeat
local next = s:find(pattern, curr + 1, plain)
if (next) then curr = next end
until (not next)
if (curr > 0) then
return curr
end
end
--
-- Returns the number of lines of text contained by the string.
--
function string.lines(s)
local trailing, n = s:gsub('.-\n', '')
if #trailing > 0 then
n = n + 1
end
return n
end
---
-- Return a plural version of a string.
---
function string:plural()
if self:endswith("y") then
return self:sub(1, #self - 1) .. "ies"
else
return self .. "s"
end
end
---
-- Returns the string escaped for Lua patterns.
---
function string.escapepattern(s)
return s:gsub("[%(%)%.%%%+%-%*%?%[%]%^%$]", "%%%0")
end

View File

@@ -0,0 +1,602 @@
--
-- table.lua
-- Additions to Lua's built-in table functions.
-- Copyright (c) 2002-2013 Jason Perkins and the Premake project
--
--
-- Make a copy of the indexed elements of the table.
--
function table.arraycopy(object)
local result = {}
for i, value in ipairs(object) do
result[i] = value
end
return result
end
--
-- Returns true if the table contains the specified value.
--
function table.contains(t, value)
for _,v in pairs(t) do
if (v == value) then
return true
end
end
return false
end
--
-- Make a shallow copy of a table
--
function table.shallowcopy(object)
local copy = {}
for k, v in pairs(object) do
copy[k] = v
end
return copy
end
--
-- Make a complete copy of a table, including any child tables it contains.
--
function table.deepcopy(object)
-- keep track of already seen objects to avoid loops
local seen = {}
local function copy(object)
if type(object) ~= "table" then
return object
elseif seen[object] then
return seen[object]
end
local clone = {}
seen[object] = clone
for key, value in pairs(object) do
clone[key] = copy(value)
end
setmetatable(clone, getmetatable(object))
return clone
end
return copy(object)
end
--
-- Enumerates an array of objects and returns a new table containing
-- only the value of one particular field.
--
function table.extract(arr, fname)
local result = { }
for _,v in ipairs(arr) do
table.insert(result, v[fname])
end
return result
end
--
-- Enumerates an array of objects and returns a new table containing
-- only the values satisfying the given predicate.
--
function table.filter(arr, fn)
local result = { }
table.foreachi(arr, function(val)
if fn(val) then
table.insert(result, val)
end
end)
return result
end
--
-- Flattens a hierarchy of tables into a single array containing all
-- of the values.
--
function table.flatten(arr)
local result = {}
local function flatten(arr)
local n = #arr
for i = 1, n do
local v = arr[i]
if type(v) == "table" then
flatten(v)
elseif v then
table.insert(result, v)
end
end
end
flatten(arr)
return result
end
--
-- Walk the elements of an array and call the specified function
-- for each non-nil element. This works around a "feature" of the
-- ipairs() function that stops iteration at the first nil.
--
-- @param arr
-- The array to iterate.
-- @param func
-- The function to call. The value (not the index) will be passed
-- as the only argument.
--
function table.foreachi(arr, func)
if arr then
local n = #arr
for i = 1, n do
local v = arr[i]
if v then
func(v, i)
end
end
end
end
--
-- Merge two lists into an array of objects, containing pairs
-- of values, one from each list.
--
function table.fold(list1, list2)
local result = {}
for _, item1 in ipairs(list1 or {}) do
if list2 and #list2 > 0 then
for _, item2 in ipairs(list2) do
table.insert(result, { item1, item2 })
end
else
table.insert(result, { item1 })
end
end
return result
end
--
-- Merges an array of items into a string.
--
function table.implode(arr, before, after, between)
local result = ""
for _,v in ipairs(arr) do
if (result ~= "" and between) then
result = result .. between
end
result = result .. before .. v .. after
end
return result
end
--
-- Looks for an object within an array. Returns its index if found,
-- or nil if the object could not be found.
--
function table.indexof(tbl, obj)
for k, v in ipairs(tbl) do
if v == obj then
return k
end
end
end
--
-- Looks for an object within a table. Returns the key if found,
-- or nil if the object could not be found.
--
function table.findKeyByValue(tbl, obj)
for k, v in pairs(tbl) do
if v == obj then
return k
end
end
end
---
-- Insert a new value into a table in the position after the specified
-- existing value. If the specified value does not exist in the table,
-- the new value is appended to the end of the table.
--
-- @param tbl
-- The table in which to insert.
-- @param after
-- The existing value to insert after.
-- @param value
-- The new value to insert.
--
function table.insertafter(tbl, after, value)
local i = table.indexof(tbl, after)
if i then
table.insert(tbl, i + 1, value)
else
table.insert(tbl, value)
end
end
--
-- Inserts a value or array of values into a table. If the value is
-- itself a table, its contents are enumerated and added instead. So
-- these inputs give these outputs:
--
-- "x" -> { "x" }
-- { "x", "y" } -> { "x", "y" }
-- { "x", { "y" }} -> { "x", "y" }
--
function table.insertflat(tbl, values)
if values == nil then
return
elseif type(values) == "table" then
for _, value in ipairs(values) do
table.insertflat(tbl, value)
end
else
table.insert(tbl, values)
end
return tbl
end
--
-- Inserts a value into a table as both a list item and a key-value pair.
-- Useful for set operations. Returns false if the value already exists, true otherwise.
--
function table.insertkeyed(tbl, pos, value)
if value == nil then
value = pos
pos = #tbl + 1
end
if tbl[value] ~= nil then
return false
end
table.insert(tbl, pos, value)
tbl[value] = value
return true
end
--
-- Inserts a value into a table in sorted order. Assumes that the
-- table is already sorted according to the sort function. If fn is
-- nil, the table is sorted according to the < operator.
--
function table.insertsorted(tbl, value, fn)
if value == nil then
return
else
fn = fn or function(a, b) return a < b end
local minindex = 1
local maxindex = #tbl + 1
while minindex < maxindex do
local index = minindex + ((maxindex - minindex) >> 1)
local test = tbl[index]
if fn(value, test) then
maxindex = index
else
minindex = index + 1
if not fn(test, value) then
break
end
end
end
table.insert(tbl, minindex, value)
end
return tbl
end
--
-- Returns true if the table is empty, and contains no indexed or keyed values.
--
function table.isempty(t)
return next(t) == nil
end
--
-- Adds the values from one array to the end of another and
-- returns the result.
--
function table.join(...)
local result = { }
local arg = {...}
for _,t in ipairs(arg) do
if type(t) == "table" then
for _,v in ipairs(t) do
table.insert(result, v)
end
else
table.insert(result, t)
end
end
return result
end
--
-- Return a list of all keys used in a table.
--
function table.keys(tbl)
local keys = {}
for k, _ in pairs(tbl) do
table.insert(keys, k)
end
return keys
end
--
-- Adds the key-value associations from one table into another
-- and returns the resulting merged table.
--
function table.merge(...)
local result = {}
local arg = {...}
for _,t in ipairs(arg) do
if type(t) == "table" then
for k,v in pairs(t) do
if type(result[k]) == "table" and type(v) == "table" then
result[k] = table.merge(result[k], v)
else
result[k] = v
end
end
else
error("invalid value")
end
end
return result
end
---
-- Replace all instances of `value` with `replacement` in an array. Array
-- elements are modified in place.
--
-- @param value
-- The value to be replaced.
-- @param replacement
-- The new value.
---
function table.replace(self, value, replacement)
for i = 1, #self do
if self[i] == value then
self[i] = replacement
end
end
end
--
-- Translates the values contained in array, using the specified
-- translation table, and returns the results in a new array.
--
function table.translate(arr, translation)
if not translation then return {} end
local result = {}
for i = 1, #arr do
local tvalue
if type(translation) == "function" then
tvalue = translation(arr[i])
else
tvalue = translation[arr[i]]
end
if (tvalue) then
table.insert(result, tvalue)
end
end
return result
end
--
-- Dumps a table to a string
--
function table.tostring(tab, recurse, indent)
local res = ''
if not indent then
indent = 0
end
local format_value = function(k, v, i)
formatting = string.rep("\t", i)
if k then
if type(k) == "table" then
k = '[table]'
end
formatting = formatting .. k .. ": "
end
if not v then
return formatting .. '(nil)'
elseif type(v) == "table" then
if recurse and recurse > 0 then
return formatting .. '\n' .. table.tostring(v, recurse-1, i+1)
else
return formatting .. "<table>"
end
elseif type(v) == "function" then
return formatting .. tostring(v)
elseif type(v) == "userdata" then
return formatting .. "<userdata>"
elseif type(v) == "boolean" then
if v then
return formatting .. 'true'
else
return formatting .. 'false'
end
else
return formatting .. v
end
end
if type(tab) == "table" then
local first = true
-- add the meta table.
local mt = getmetatable(tab)
if mt then
res = res .. format_value('__mt', mt, indent)
first = false
end
-- add all values.
for k, v in pairs(tab) do
if not first then
res = res .. '\n'
end
res = res .. format_value(k, v, indent)
first = false
end
else
res = res .. format_value(nil, tab, indent)
end
return res
end
--
-- Returns a copy of a list with all duplicate elements removed.
--
function table.unique(tab)
local elems = { }
local result = { }
table.foreachi(tab, function(elem)
if not elems[elem] then
table.insert(result, elem)
elems[elem] = true
end
end)
return result
end
--
-- Filters a table for empty entries. primarly useful for lists of string.
--
function table.filterempty(dirs)
return table.translate(dirs, function(val)
if val and #val > 0 then
return val
else
return nil
end
end)
end
--
-- Compares two tables.
--
function table.equals(a, b)
for k, v in pairs(a) do
if b[k] ~= v then
return false
end
end
for k, v in pairs(b) do
if a[k] ~= v then
return false
end
end
return true
end
--
-- Enumerate a table sorted by its keys.
--
function spairs(t)
-- collect the keys
local keys = {}
for k in pairs(t) do
table.insert(keys, k)
end
table.sort(keys)
-- return the iterator function
local i = 0
return function()
i = i + 1
if keys[i] then
return keys[i], t[keys[i]]
end
end
end
--
-- Intersect two arrays and return a new array
--
function table.intersect(a, b)
local result = {}
for _, v in ipairs(b) do
if table.indexof(a, v) then
table.insert(result, v)
end
end
return result
end
--
-- The difference of A and B is the set containing those elements that are in A but not in B
--
function table.difference(a, b)
local result = {}
for _, v in ipairs(a) do
if not table.indexof(b, v) then
table.insert(result, v)
end
end
return result
end

View File

@@ -0,0 +1,45 @@
--
-- term.lua
-- Additions to the 'term' namespace.
-- Copyright (c) 2017 Blizzard Entertainment and the Premake project
--
-- default colors.
term.black = 0
term.blue = 1
term.green = 2
term.cyan = 3
term.red = 4
term.purple = 5
term.brown = 6
term.lightGray = 7
term.gray = 8
term.lightBlue = 9
term.lightGreen = 10
term.lightCyan = 11
term.lightRed = 12
term.magenta = 13
term.yellow = 14
term.white = 15
-- colors for specific purpose.
term.warningColor = term.magenta
term.errorColor = term.lightRed
term.infoColor = term.lightCyan
-- color stack implementation.
term._colorStack = {}
function term.pushColor(color)
local old = term.getTextColor()
table.insert(term._colorStack, old)
term.setTextColor(color)
end
function term.popColor()
if #term._colorStack > 0 then
local color = table.remove(term._colorStack)
term.setTextColor(color)
end
end

View File

@@ -0,0 +1,61 @@
---
-- tools.lua
-- Work with Premake's collection of tool adapters.
-- Author Jason Perkins
-- Copyright (c) 2015 Jason Perkins and the Premake project
---
local p = premake
p.tools = {}
---
-- Given a toolset identifier (e.g. "gcc" or "gcc-4.8") returns the
-- corresponding tool adapter and the version, if one was provided.
--
-- @param identifier
-- A toolset identifier composed of two parts: the toolset name,
-- which should match of the name of the adapter object ("gcc",
-- "clang", etc.) in the p.tools namespace, and and optional
-- version number, separated by a dash.
--
-- To make things more intuitive for Visual Studio users, supports
-- identifiers like "v100" to represent the v100 Microsoft platform
-- toolset.
-- @return
-- If successful, returns the toolset adapter object. If a version
-- was specified as part of the identifier, that is returned as a
-- second return value. If no corresponding tool adapter exists,
-- returns nil.
---
function p.tools.normalize(identifier)
if identifier:startswith("v") then -- TODO: this should be deprecated?
identifier = 'msc-' .. identifier
end
local parts = identifier:explode("-", true, 1)
if parts[2] == nil then
return parts[1]
end
-- 'msc-100' is accepted, but the code expects 'v100'
if parts[1] == "msc" and tonumber(parts[2]:sub(1,3)) ~= nil then
parts[2] = "v" .. parts[2]
end
-- perform case-correction of the LLVM toolset
if parts[2]:startswith("llvm-vs") then
parts[2] = "LLVM-" .. parts[2]:sub(6)
end
return parts[1] .. '-' .. parts[2]
end
function p.tools.canonical(identifier)
identifier = p.tools.normalize(identifier)
local parts = identifier:explode("-", true, 1)
return p.tools[parts[1]], parts[2]
end

View File

@@ -0,0 +1,329 @@
--
-- tree.lua
-- Functions for working with the source code tree.
-- Copyright (c) 2009-2013 Jason Perkins and the Premake project
--
local p = premake
p.tree = {}
local tree = p.tree
--
-- Create a new tree.
--
-- @param n
-- The name of the tree, applied to the root node (optional).
--
function tree.new(n)
local t = {
name = n,
children = {}
}
return t
end
--
-- Add a new node to the tree, or returns the current node if it already exists.
--
-- @param tr
-- The tree to contain the new node.
-- @param p
-- The path of the new node.
-- @param extraFields
-- A table containing key-value pairs to be added to any new nodes.
-- @returns
-- The new tree node.
--
function tree.add(tr, p, extraFields)
-- Special case "." refers to the current node
if p == "." or p == "/" then
return tr
end
-- Look for the immediate parent for this new node, creating it if necessary.
-- Recurses to create as much of the tree as necessary.
local parentnode = tree.add(tr, path.getdirectory(p), extraFields)
-- Create the child if necessary
local childname = path.getname(p)
local childnode = parentnode.children[childname]
if not childnode or childnode.path ~= p then
childnode = tree.insert(parentnode, tree.new(childname))
childnode.path = p
if extraFields then
for k,v in pairs(extraFields) do
childnode[k] = v
end
end
end
return childnode
end
--
-- Insert one tree into another.
--
-- @param parent
-- The parent tree, to contain the child.
-- @param child
-- The child tree, to be inserted.
--
function tree.insert(parent, child)
table.insert(parent.children, child)
if child.name then
parent.children[child.name] = child
end
child.parent = parent
return child
end
--
-- Gets the node's relative path from it's parent. If the parent does not have
-- a path set (it is the root or other container node) returns the full node path.
--
-- @param node
-- The node to query.
--
function tree.getlocalpath(node)
if node.parent.path then
return node.name
elseif node.cfg then
return node.cfg.name
else
return node.path
end
end
--
-- Determines if the tree contains any branch nodes, or only leaves.
--
-- @param tr
-- The root node of the tree to query.
-- @return
-- True if a node below the root contains children, false otherwise.
--
function tree.hasbranches(tr)
local n = #tr.children
if n > 0 then
for i = 1, n do
if #tr.children[i].children > 0 then
return true
end
end
end
return false
end
--
-- Determines if one node is a parent if another.
--
-- @param n
-- The node being tested for parentage.
-- @param child
-- The child node being testing against.
-- @return
-- True if n is a parent of child.
--
function tree.isparent(n, child)
local p = child.parent
while p do
if p == n then
return true
end
p = p.parent
end
return false
end
--
-- Remove a node from a tree.
--
-- @param node
-- The node to remove.
--
function tree.remove(node)
local children = node.parent.children
for i = 1, #children do
if children[i] == node then
table.remove(children, i)
end
end
node.children = {}
end
--
-- Sort the nodes of a tree in-place.
--
-- @param tr
-- The tree to sort.
-- @param fn
-- An optional comparator function.
--
function tree.sort(tr, fn)
if not fn then
fn = function(a,b) return a.name < b.name end
end
tree.traverse(tr, {
onnode = function(node)
table.sort(node.children, fn)
end
}, true)
end
--
-- Traverse a tree.
--
-- @param t
-- The tree to traverse.
-- @param fn
-- A collection of callback functions, which may contain any or all of the
-- following entries. Entries are called in this order.
--
-- onnode - called on each node encountered
-- onbranchenter - called on branches, before processing children
-- onbranch - called only on branch nodes
-- onleaf - called only on leaf nodes
-- onbranchexit - called on branches, after processing children
--
-- Callbacks receive two arguments: the node being processed, and the
-- current traversal depth.
--
-- @param includeroot
-- True to include the root node in the traversal, otherwise it will be skipped.
-- @param initialdepth
-- An optional starting value for the traversal depth; defaults to zero.
--
function tree.traverse(t, fn, includeroot, initialdepth)
-- forward declare my handlers, which call each other
local donode, dochildren
-- process an individual node
donode = function(node, fn, depth)
if node.isremoved then
return
end
if fn.onnode then
fn.onnode(node, depth)
end
if #node.children > 0 then
if fn.onbranchenter then
fn.onbranchenter(node, depth)
end
if fn.onbranch then
fn.onbranch(node, depth)
end
dochildren(node, fn, depth + 1)
if fn.onbranchexit then
fn.onbranchexit(node, depth)
end
else
if fn.onleaf then
fn.onleaf(node, depth)
end
end
end
-- this goofy iterator allows nodes to be removed during the traversal
dochildren = function(parent, fn, depth)
local i = 1
while i <= #parent.children do
local node = parent.children[i]
donode(node, fn, depth)
if node == parent.children[i] then
i = i + 1
end
end
end
-- set a default initial traversal depth, if one wasn't set
if not initialdepth then
initialdepth = 0
end
if includeroot then
donode(t, fn, initialdepth)
else
dochildren(t, fn, initialdepth)
end
end
--
-- Starting at the top of the tree, remove nodes that contain only a single
-- item until I hit a node that has multiple items. This is used to remove
-- superfluous folders from the top of the source tree.
--
function tree.trimroot(tr)
local trimmed
-- start by removing single-children folders from the top of the tree
while #tr.children == 1 do
local node = tr.children[1]
-- if this node has no children (it is the last node in the tree) I'm done
if #node.children == 0 or node.trim == false then
break
end
-- remove this node from the tree, and move its children up a level
trimmed = true
local numChildren = #node.children
for i = 1, numChildren do
local child = node.children[i]
child.parent = node.parent
tr.children[i] = child
end
end
-- found the top, now remove any single-children ".." folders from here
local dotdot
local count = #tr.children
repeat
dotdot = false
for i = 1, count do
local node = tr.children[i]
if node.name == ".." and #node.children == 1 then
local child = node.children[1]
child.parent = node.parent
tr.children[i] = child
trimmed = true
dotdot = true
end
end
until not dotdot
-- if nodes were removed, adjust the paths on all remaining nodes
if trimmed then
tree.traverse(tr, {
onnode = function(node)
if node.parent.path then
node.path = path.join(node.parent.path, node.name)
else
node.path = node.name
end
end
}, false)
end
end

View File

@@ -0,0 +1,239 @@
---
-- base/validation.lua
--
-- Verify the contents of the project object before handing them off to
-- the action/exporter.
--
-- Copyright (c) 2002-2015 Jason Perkins and the Premake project
---
local p = premake
p.validation = {}
local m = p.validation
m.elements = {}
---
-- Validate the global container and all of its contents.
---
m.elements.global = function(glb)
return {
}
end
function p.global.validate(self)
p.callArray(m.elements.global, self)
p.container.validateChildren(self)
end
---
-- Validate a workspace and its projects.
---
m.elements.workspace = function(wks)
return {
m.workspaceHasConfigs,
m.uniqueProjectIds,
}
end
function p.workspace.validate(self)
p.callArray(m.elements.workspace, self)
p.container.validateChildren(self)
end
---
-- Validate a project and its configurations.
---
m.elements.project = function(prj)
return {
m.projectHasLanguage,
m.actionSupportsLanguage,
m.actionSupportsKind,
m.projectRulesExist,
m.projectValuesInScope,
}
end
function p.project.validate(self)
p.callArray(m.elements.project, self)
for cfg in p.project.eachconfig(self) do
p.config.validate(cfg)
end
end
---
-- Validate a project configuration.
---
m.elements.config = function(cfg)
return {
m.configHasKind,
m.configSupportsKind,
m.configValuesInScope,
}
end
function p.config.validate(self)
p.callArray(m.elements.config, self)
end
---
-- Validate a rule.
---
m.elements.rule = function(rule)
return {
-- TODO: fill this in
}
end
function p.rule.validate(self)
p.callArray(m.elements.rule, self)
end
---
-- Check the values stored in a configuration for values that might have
-- been set out of scope.
--
-- @param container
-- The container being validated; will only check fields which are
-- scoped to this container's class hierarchy.
-- @param expectedScope
-- The expected scope of values in this object, i.e. "project", "config".
-- Values that appear unexpectedly get checked to be sure they match up
-- with the values in the expected scope, and an error is raised if they
-- are not the same.
---
function p.config.validateScopes(self, container, expected)
for f in p.field.each() do
-- If this field scoped to the target container class? If not
-- I can skip over it (config scope applies to everything).
local scope
for i = 1, #f.scopes do
if f.scopes[i] == "config" or p.container.classIsA(container.class, f.scopes[i]) then
scope = f.scopes[i]
break
end
end
local okay = (not scope or scope == "config")
-- Skip over fields that are at or below my expected scope.
okay = okay or scope == expected
-- Skip over fields that bubble up to their parent containers anyway;
-- these can't be out of scope for that reason
okay = okay or p.oven.bubbledFields[f.name]
-- this one needs to checked
okay = okay or p.field.compare(f, self[scope][f.name], self[f.name])
-- found a problem?
if not okay then
local key = "validate." .. f.name
p.warnOnce(key, "'%s' on %s '%s' differs from %s '%s'; may be set out of scope", f.name, expected, self.name, scope, self[scope].name)
end
end
end
---------------------------------------------------------------------------
--
-- Handlers for individual checks
--
---------------------------------------------------------------------------
function m.actionSupportsKind(prj)
if not p.action.supports(prj.kind) then
p.warn("Unsupported kind '%s' used for project '%s'", prj.kind, prj.name)
end
end
function m.actionSupportsLanguage(prj)
if not p.action.supports(prj.language) then
p.warn("Unsupported language '%s' used for project '%s'", prj.language, prj.name)
end
end
function m.configHasKind(cfg)
if not cfg.kind then
p.error("Project '%s' needs a kind in configuration '%s'", cfg.project.name, cfg.name)
end
end
function m.configSupportsKind(cfg)
if not p.action.supports(cfg.kind) then
p.warn("Unsupported kind '%s' used in configuration '%s'", cfg.kind, cfg.name)
end
-- makefile configuration can only appear in C++ projects; this is the
-- default now, so should only be a problem if overridden.
if (cfg.kind == p.MAKEFILE or cfg.kind == p.NONE) and p.project.isdotnet(cfg.project) then
p.error("Project '%s' uses '%s' kind in configuration '%s'; language must not be C#", cfg.project.name, cfg.kind, cfg.name)
end
end
function m.configValuesInScope(cfg)
p.config.validateScopes(cfg, cfg.project, "config")
end
function m.projectHasLanguage(prj)
if not prj.language then
p.error("project '%s' does not have a language", prj.name)
end
end
function m.projectRulesExist(prj)
for i = 1, #prj.rules do
local rule = prj.rules[i]
if not p.global.getRule(rule) then
p.error("project '%s' uses missing rule '%s'", prj.name, rule)
end
end
end
function m.projectValuesInScope(prj)
p.config.validateScopes(prj, prj, "project")
end
function m.uniqueProjectIds(wks)
local uuids = {}
for prj in p.workspace.eachproject(wks) do
if uuids[prj.uuid] then
p.error("projects '%s' and '%s' have the same UUID", uuids[prj.uuid], prj.name)
end
uuids[prj.uuid] = prj.name
end
end
function m.workspaceHasConfigs(wks)
if not wks.configurations or #wks.configurations == 0 then
p.error("workspace '%s' does not contain any configurations", wks.name)
end
end

View File

@@ -0,0 +1,202 @@
---
-- workspace.lua
-- Work with the list of workspaces loaded from the script.
-- Copyright (c) 2002-2015 Jason Perkins and the Premake project
---
local p = premake
p.workspace = p.api.container("workspace", p.global)
local workspace = p.workspace
---
-- Switch this container's name from "solution" to "workspace"
--
-- We changed these names on 30 Jul 2015. While it might be nice to leave
-- `solution()` around for Visual Studio folks and everyone still used to the
-- old system, it would be good to eventually deprecate and remove all of
-- the other, more internal uses of "solution" and "sln". Probably including
-- all uses of container class aliases, since we probably aren't going to
-- need those again (right?).
---
p.solution = workspace
workspace.alias = "solution"
p.alias(_G, "workspace", "solution")
p.alias(_G, "externalworkspace", "externalsolution")
---
-- Create a new workspace container instance.
---
function workspace.new(name)
local wks = p.container.new(workspace, name)
return wks
end
--
-- Iterate over the configurations of a workspace.
--
-- @return
-- A configuration iteration function.
--
function workspace.eachconfig(self)
self = p.oven.bakeWorkspace(self)
local i = 0
return function()
i = i + 1
if i > #self.configs then
return nil
else
return self.configs[i]
end
end
end
--
-- Iterate over the projects of a workspace.
--
-- @return
-- An iterator function, returning project configurations.
--
function workspace.eachproject(self)
local i = 0
return function ()
i = i + 1
if i <= #self.projects then
return p.workspace.getproject(self, i)
end
end
end
--
-- Locate a project by name, case insensitive.
--
-- @param name
-- The name of the projec to find.
-- @return
-- The project object, or nil if a matching project could not be found.
--
function workspace.findproject(self, name)
name = name:lower()
for _, prj in ipairs(self.projects) do
if name == prj.name:lower() then
return prj
end
end
return nil
end
--
-- Retrieve the tree of project groups.
--
-- @return
-- The tree of project groups defined for the workspace.
--
function workspace.grouptree(self)
-- check for a previously cached tree
if self.grouptree then
return self.grouptree
end
-- build the tree of groups
local tr = p.tree.new()
for prj in workspace.eachproject(self) do
local prjpath = path.join(prj.group, prj.name)
local node = p.tree.add(tr, prjpath)
node.project = prj
end
-- assign UUIDs to each node in the tree
p.tree.traverse(tr, {
onbranch = function(node)
node.uuid = os.uuid("group:" .. node.path)
end
})
-- sort by uuid for determinism.
p.tree.sort(tr, function(a,b)
return a.name < b.name
end)
self.grouptree = tr
return tr
end
--
-- Retrieve the project configuration at a particular index.
--
-- @param idx
-- An index into the array of projects.
-- @return
-- The project configuration at the given index.
--
function workspace.getproject(self, idx)
self = p.oven.bakeWorkspace(self)
return self.projects[idx]
end
---
-- Determines if the workspace contains a project that meets certain criteria.
--
-- @param func
-- A test function. Receives a project as its only argument and returns a
-- boolean indicating whether it meets to matching criteria.
-- @return
-- True if the test function returned true.
---
function workspace.hasProject(self, func)
return p.container.hasChild(self, p.project, func)
end
--
-- Return the relative path from the solution to the specified file.
--
-- @param self
-- The workspace object to query.
-- @param filename
-- The file path, or an array of file paths, to convert.
-- @return
-- The relative path, or array of paths, from the workspace to the file.
--
function workspace.getrelative(self, filename)
if type(filename) == "table" then
local result = {}
for i, name in ipairs(filename) do
if name and #name > 0 then
table.insert(result, workspace.getrelative(self, name))
end
end
return result
else
if filename then
local result = filename
if path.hasdeferredjoin(result) then
result = path.resolvedeferredjoin(result)
end
return path.getrelative(self.location, result)
end
end
end

View File

@@ -0,0 +1,125 @@
/**
* \file buffered_io.c
* \brief provide buffered io.
* \author Copyright (c) 2014
*/
#include <stdlib.h>
#include <string.h>
#include "premake.h"
#include "buffered_io.h"
void buffer_init(Buffer* b)
{
b->capacity = 0;
b->length = 0;
b->data = NULL;
}
void buffer_destroy(Buffer* b)
{
free(b->data);
b->capacity = 0;
b->length = 0;
b->data = NULL;
}
void buffer_puts(Buffer* b, const void* ptr, size_t len)
{
char* data;
size_t required = b->length + len;
if (required > b->capacity)
{
size_t cap = b->capacity;
while (required > cap)
{
cap = (cap * 3) / 2;
if (cap <= 65536)
cap = 65536;
}
data = (char*)calloc(cap, 1);
if (b->length > 0)
{
memcpy(data, b->data, b->length);
free(b->data);
}
b->data = data;
b->capacity = cap;
}
memcpy(b->data + b->length, ptr, len);
b->length += len;
}
void buffer_printf(Buffer* b, const char *fmt, ...)
{
char text[2048];
int len;
va_list args;
va_start(args, fmt);
len = vsnprintf(text, sizeof(text) - 1, fmt, args);
va_end(args);
buffer_puts(b, text, len);
}
// -- Lua wrappers ----------------------------------------
int buffered_new(lua_State* L)
{
Buffer* b = (Buffer*)malloc(sizeof(Buffer));
buffer_init(b);
lua_pushlightuserdata(L, b);
return 1;
}
int buffered_write(lua_State* L)
{
size_t len;
const char *s = luaL_checklstring(L, 2, &len);
Buffer* b = (Buffer*)lua_touserdata(L, 1);
buffer_puts(b, s, len);
return 0;
}
int buffered_writeln(lua_State* L)
{
size_t len;
const char *s = luaL_optlstring(L, 2, NULL, &len);
Buffer* b = (Buffer*)lua_touserdata(L, 1);
if (s != NULL)
buffer_puts(b, s, len);
buffer_puts(b, "\r\n", 2);
return 0;
}
int buffered_close(lua_State* L)
{
Buffer* b = (Buffer*)lua_touserdata(L, 1);
buffer_destroy(b);
free(b);
return 0;
}
int buffered_tostring(lua_State* L)
{
Buffer* b = (Buffer*)lua_touserdata(L, 1);
size_t len = b->length;
// trim string for _eol of line at the end.
if (len > 0 && b->data[len-1] == '\n')
--len;
if (len > 0 && b->data[len-1] == '\r')
--len;
if (len > 0)
// push data into a string.
lua_pushlstring(L, b->data, len);
else
lua_pushstring(L, "");
return 1;
}

View File

@@ -0,0 +1,24 @@
/**
* \file buffered_io.h
* \brief provide buffered io.
* \author Copyright (c) 2014
*/
#ifndef buffered_io_h
#define buffered_io_h
#include <stdio.h>
typedef struct struct_Buffer
{
size_t capacity;
size_t length;
char* data;
} Buffer;
void buffer_init(Buffer* b);
void buffer_destroy(Buffer* b);
void buffer_puts(Buffer* b, const void* ptr, size_t len);
void buffer_printf(Buffer* b, const char* s, ...);
#endif

View File

@@ -0,0 +1,310 @@
/**
* \file criteria_matches.c
* \brief Determine if this criteria is met by the provided filter terms.
* \author Copyright (c) 2002-2014 Jason Perkins and the Premake project
*/
#include "premake.h"
#include <stdlib.h>
#include <string.h>
struct Word {
const char* word;
const char* prefix;
int matchesFiles;
int assertion;
int wildcard;
};
struct Pattern {
int matchesFiles;
int n;
struct Word* word;
};
struct Patterns {
int prefixed;
int filePatterns;
int n;
struct Pattern* pattern;
};
static int criteria_compilePattern(lua_State* L, struct Pattern* pattern);
int criteria_compile(lua_State* L)
{
struct Patterns* patterns;
int i, n;
/* create a Patterns object and userdata; holds a list of Pattern items */
patterns = (struct Patterns*)lua_newuserdata(L, sizeof(struct Patterns));
patterns->prefixed = 0;
patterns->filePatterns = 0;
if (luaL_newmetatable(L, "premake.criteria")) {
lua_pushstring(L, "__gc");
lua_pushcfunction(L, criteria_delete);
lua_settable(L, -3);
}
lua_setmetatable(L, -2);
/* create array to hold the incoming list of patterns */
n = (int)lua_rawlen(L, 1);
patterns->n = n;
patterns->pattern = (struct Pattern*)malloc(sizeof(struct Pattern) * n);
/* create a new pattern object for each incoming pattern */
for (i = 0; i < n; ++i) {
struct Pattern* pattern = &patterns->pattern[i];
lua_rawgeti(L, 1, i + 1);
criteria_compilePattern(L, pattern);
lua_pop(L, 1);
if (pattern->n > 0 && pattern->word[0].prefix != NULL) {
patterns->prefixed = 1;
}
if (pattern->matchesFiles) {
++patterns->filePatterns;
}
}
return 1;
}
int criteria_compilePattern(lua_State* L, struct Pattern* pattern)
{
int i, n;
/* create array to hold the incoming list of words */
n = (int)lua_rawlen(L, -1);
pattern->n = n;
pattern->word = (struct Word*)malloc(sizeof(struct Word) * n);
pattern->matchesFiles = 0;
for (i = 0; i < n; ++i) {
struct Word* word = &pattern->word[i];
word->matchesFiles = 0;
lua_rawgeti(L, -1, i + 1);
lua_rawgeti(L, -1, 1);
word->word = lua_tostring(L, -1);
lua_pop(L, 1);
lua_rawgeti(L, -1, 2);
word->prefix = lua_tostring(L, -1);
lua_pop(L, 1);
lua_rawgeti(L, -1, 3);
word->assertion = lua_toboolean(L, -1);
lua_pop(L, 1);
lua_rawgeti(L, -1, 4);
word->wildcard = lua_toboolean(L, -1);
lua_pop(L, 1);
if (word->prefix && strcmp(word->prefix, "files") == 0) {
word->matchesFiles = 1;
pattern->matchesFiles = 1;
}
lua_pop(L, 1);
}
return 0;
}
int criteria_delete(lua_State* L)
{
int i, n;
struct Patterns* patterns = (struct Patterns*)lua_touserdata(L, 1);
n = patterns->n;
for (i = 0; i < n; ++i) {
free(patterns->pattern[i].word);
}
free(patterns->pattern);
return 0;
}
static int match(lua_State* L, const char* value, struct Word* word)
{
if (word->wildcard) {
/* use string.match() to compare */
const char* result;
int matched = 0;
int top = lua_gettop(L);
lua_pushvalue(L, 4);
lua_pushstring(L, value);
lua_pushstring(L, word->word);
lua_call(L, 2, 1);
if (lua_isstring(L, -1)) {
result = lua_tostring(L, -1);
matched = (strcmp(value, result) == 0);
}
lua_settop(L, top);
return matched;
}
else {
return (strcmp(value, word->word) == 0);
}
}
/*
* Compares the value on the top of the stack to the specified word.
*/
static int testValue(lua_State* L, struct Word* word)
{
const char* value;
size_t i, n;
int result;
if (lua_istable(L, -1)) {
n = lua_rawlen(L, -1);
for (i = 1; i <= n; ++i) {
lua_rawgeti(L, -1, i);
result = testValue(L, word);
lua_pop(L, 1);
if (result) {
return 1;
}
}
return 0;
}
value = lua_tostring(L, -1);
if (value) {
return match(L, value, word);
}
return 0;
}
static int testWithPrefix(lua_State* L, struct Word* word, const char* filename, int* fileMatched)
{
int result;
if (word->matchesFiles && !filename) {
return 0;
}
lua_pushstring(L, word->prefix);
lua_rawget(L, 2);
result = testValue(L, word);
lua_pop(L, 1);
if (word->matchesFiles && result == word->assertion) {
(*fileMatched) = 1;
}
if (result) {
return word->assertion;
}
return (!word->assertion);
}
static int testNoPrefix(lua_State* L, struct Word* word, const char* filename, int* fileMatched)
{
if (filename && word->assertion && match(L, filename, word)) {
(*fileMatched) = 1;
return 1;
}
lua_pushnil(L);
while (lua_next(L, 2)) {
if (testValue(L, word)) {
lua_pop(L, 2);
return word->assertion;
}
lua_pop(L, 1);
}
return (!word->assertion);
}
int criteria_matches(lua_State* L)
{
/* stack [1] = criteria */
/* stack [2] = context */
struct Patterns* patterns;
const char* filename;
int i, j, fileMatched;
int matched = 1;
/* filename = context.files */
lua_pushstring(L, "files");
lua_rawget(L, 2);
filename = lua_tostring(L, -1);
lua_pop(L, 1);
/* fetch the patterns to be tested */
lua_pushstring(L, "data");
lua_rawget(L, 1);
patterns = (struct Patterns*)lua_touserdata(L, -1);
lua_pop(L, 1);
/* if a file is being matched, the pattern must be able to match it */
if (patterns->prefixed && filename != NULL && patterns->filePatterns == 0) {
return 0;
}
/* Cache string.match on the stack (at index 4) to save time in matches() later */
lua_getglobal(L, "string");
lua_getfield(L, -1, "match");
/* if there is no file to consider, consider it matched */
fileMatched = (filename == NULL);
/* all patterns must match to pass */
for (i = 0; matched && i < patterns->n; ++i) {
struct Pattern* pattern = &patterns->pattern[i];
/* only one word needs to match for the pattern to pass */
matched = 0;
for (j = 0; !matched && j < pattern->n; ++j) {
struct Word* word = &pattern->word[j];
if (word->prefix) {
matched = testWithPrefix(L, word, filename, &fileMatched);
}
else {
matched = testNoPrefix(L, word, filename, &fileMatched);
}
}
}
/* if a file name was provided in the context, it must be matched */
if (filename && !fileMatched) {
matched = 0;
}
lua_pushboolean(L, matched);
return 1;
}

View File

@@ -0,0 +1,207 @@
/**
* \file curl_utils.c
* \brief curl utilities for the http library.
* \author Copyright (c) 2017 Tom van Dijck, João Matos and the Premake project
*/
#ifdef PREMAKE_CURL
#include "curl_utils.h"
#include "premake.h"
#include <string.h>
int curlProgressCallback(curl_state* state, double dltotal, double dlnow, double ultotal, double ulnow)
{
lua_State* L = state->L;
(void)ultotal;
(void)ulnow;
if (dltotal == 0) return 0;
/* retrieve the lua progress callback we saved before */
lua_rawgeti(L, LUA_REGISTRYINDEX, state->RefIndex);
lua_pushnumber(L, (lua_Number)dltotal);
lua_pushnumber(L, (lua_Number)dlnow);
int ret = premake_pcall(L, 2, LUA_MULTRET);
if (ret != LUA_OK) {
printLastError(L);
return -1; // abort download
}
return 0;
}
size_t curlWriteCallback(char *ptr, size_t size, size_t nmemb, curl_state* state)
{
size_t length = size * nmemb;
buffer_puts(&state->S, ptr, length);
return length;
}
static void curl_init()
{
static int initializedHTTP = 0;
if (initializedHTTP)
return;
curl_global_init(CURL_GLOBAL_ALL);
atexit(curl_global_cleanup);
initializedHTTP = 1;
}
static void get_headers(lua_State* L, int headersIndex, struct curl_slist** headers)
{
lua_pushnil(L);
while (lua_next(L, headersIndex) != 0)
{
const char *item = luaL_checkstring(L, -1);
lua_pop(L, 1);
*headers = curl_slist_append(*headers, item);
}
}
CURL* curlRequest(lua_State* L, curl_state* state, int optionsIndex, int progressFnIndex, int headersIndex)
{
char agent[1024];
CURL* curl;
state->L = 0;
state->RefIndex = 0;
state->errorBuffer[0] = '\0';
state->headers = NULL;
buffer_init(&state->S);
curl_init();
curl = curl_easy_init();
if (!curl)
return NULL;
strcpy(agent, "Premake/");
strcat(agent, PREMAKE_VERSION);
curl_easy_setopt(curl, CURLOPT_URL, luaL_checkstring(L, 1));
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1);
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, state->errorBuffer);
curl_easy_setopt(curl, CURLOPT_USERAGENT, agent);
// check if the --insecure option was specified on the commandline.
lua_getglobal(L, "_OPTIONS");
lua_pushstring(L, "insecure");
lua_gettable(L, -2);
if (!lua_isnil(L, -1))
{
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
}
lua_pop(L, 2);
// apply all other options.
if (optionsIndex && lua_istable(L, optionsIndex))
{
lua_pushnil(L);
while (lua_next(L, optionsIndex) != 0)
{
const char* key = luaL_checkstring(L, -2);
if (!strcmp(key, "headers") && lua_istable(L, -1))
{
get_headers(L, lua_gettop(L), &state->headers);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, state->headers);
}
else if (!strcmp(key, "progress") && lua_isfunction(L, -1))
{
state->L = L;
lua_pushvalue(L, -1);
state->RefIndex = luaL_ref(L, LUA_REGISTRYINDEX);
}
else if (!strcmp(key, "userpwd") && lua_isstring(L, -1))
{
curl_easy_setopt(curl, CURLOPT_USERPWD, luaL_checkstring(L, -1));
}
else if (!strcmp(key, "username") && lua_isstring(L, -1))
{
curl_easy_setopt(curl, CURLOPT_USERNAME, luaL_checkstring(L, -1));
}
else if (!strcmp(key, "password") && lua_isstring(L, -1))
{
curl_easy_setopt(curl, CURLOPT_PASSWORD, luaL_checkstring(L, -1));
}
else if (!strcmp(key, "timeout") && lua_isnumber(L, -1))
{
curl_easy_setopt(curl, CURLOPT_TIMEOUT, (long)luaL_checknumber(L, -1));
}
else if (!strcmp(key, "timeoutms") && lua_isnumber(L, -1))
{
curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, (long)luaL_checknumber(L, -1));
}
else if (!strcmp(key, "sslverifyhost") && lua_isnumber(L, -1))
{
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, (long)luaL_checknumber(L, -1));
}
else if (!strcmp(key, "sslverifypeer") && lua_isnumber(L, -1))
{
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, (long)luaL_checknumber(L, -1));
}
else if (!strcmp(key, "proxyurl") && lua_isstring(L, -1))
{
curl_easy_setopt(curl, CURLOPT_PROXY, luaL_checkstring(L, -1));
}
// pop the value, leave the key for lua_next
lua_pop(L, 1);
}
}
else
{
if (progressFnIndex && lua_type(L, progressFnIndex) == LUA_TFUNCTION)
{
state->L = L;
lua_pushvalue(L, progressFnIndex);
state->RefIndex = luaL_ref(L, LUA_REGISTRYINDEX);
}
if (headersIndex && lua_istable(L, headersIndex))
{
get_headers(L, headersIndex, &state->headers);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, state->headers);
}
}
curl_easy_setopt(curl, CURLOPT_WRITEDATA, state);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlWriteCallback);
if (state->L != 0)
{
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, state);
curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, curlProgressCallback);
}
// clear error buffer.
state->errorBuffer[0] = 0;
return curl;
}
void curlCleanup(CURL* curl, curl_state* state)
{
if (state->headers)
{
curl_slist_free_all(state->headers);
state->headers = 0;
}
curl_easy_cleanup(curl);
}
#endif

View File

@@ -0,0 +1,36 @@
/**
* \file curl_utils.h
* \brief curl utilities for the http library.
* \author Copyright (c) 2017 Tom van Dijck, João Matos and the Premake project
*/
#ifndef curl_utils_h
#define curl_utils_h
#ifdef PREMAKE_CURL
#include "buffered_io.h"
#include "lua.h"
#define _MPRINTF_REPLACE /* use curl functions only */
#include <curl/curl.h>
#include <curl/mprintf.h>
typedef struct
{
lua_State* L;
int RefIndex;
Buffer S;
char errorBuffer[256];
struct curl_slist* headers;
} curl_state;
int curlProgressCallback(curl_state* state, double dltotal, double dlnow, double ultotal, double ulnow);
size_t curlWriteCallback(char *ptr, size_t size, size_t nmemb, curl_state* state);
CURL* curlRequest(lua_State* L, curl_state* state, int optionsIndex, int progressFnIndex, int headersIndex);
void curlCleanup(CURL* curl, curl_state* state);
#endif // PREMAKE_CURL
#endif // curl_utils_h

View File

@@ -0,0 +1,47 @@
/**
* \file debug_prompt.c
* \brief Display a prompt and enter interactive REPL mode.
* \author Copyright (c) 2014 Jason Perkins and the Premake project
*/
#include "premake.h"
/* Build on the REPL built into Lua already */
#define main lua_main
#include "lua.c"
/* Based on dotty() in lua.c */
int debug_prompt(lua_State* L)
{
int status;
const char* oldProgName = progname;
progname = NULL;
while ((status = loadline(L)) != -1) {
if (status == 0) {
status = docall(L, 0, 0);
}
report(L, status);
if (status == 0 && lua_gettop(L) > 0) { /* any result to print? */
lua_getglobal(L, "print");
lua_insert(L, 1);
if (lua_pcall(L, lua_gettop(L) - 1, 0, 0) != 0) {
l_message(progname, lua_pushfstring(L,
"error calling " LUA_QL("print") " (%s)",
lua_tostring(L, -1))
);
}
}
}
lua_settop(L, 0); /* clear stack */
fputs("\n", stdout);
fflush(stdout);
progname = oldProgName;
return 0;
}

View File

@@ -0,0 +1,76 @@
/**
* \file http_download.c
* \brief HTTP download support using libcurl.
* \author Copyright (c) 2017 Blizzard Entertainment, João Matos and the Premake project
*/
#include "premake.h"
#include "curl_utils.h"
#ifdef PREMAKE_CURL
static size_t curl_file_cb(void *ptr, size_t size, size_t nmemb, FILE *stream)
{
return fwrite(ptr, size, nmemb, stream);
}
int http_download(lua_State* L)
{
curl_state state;
CURL* curl;
CURLcode code = CURLE_FAILED_INIT;
long responseCode = 0;
FILE* fp;
const char* file = luaL_checkstring(L, 2);
fp = fopen(file, "wb");
if (!fp)
{
lua_pushstring(L, "Unable to open file.");
lua_pushnumber(L, -1);
return 2;
}
if (lua_istable(L, 3))
{
// http.download(source, destination, { options })
curl = curlRequest(L, &state, /*optionsIndex=*/3, /*progressFnIndex=*/0, /*headersIndex=*/0);
}
else
{
// backward compatible function signature
// http.download(source, destination, progressFunction, { headers })
curl = curlRequest(L, &state, /*optionsIndex=*/0, /*progressFnIndex=*/3, /*headersIndex=*/4);
}
if (curl)
{
curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_file_cb);
code = curl_easy_perform(curl);
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &responseCode);
curlCleanup(curl, &state);
}
fclose(fp);
if (code != CURLE_OK)
{
char errorBuf[1024];
snprintf(errorBuf, sizeof(errorBuf) - 1, "%s\n%s\n", curl_easy_strerror(code), state.errorBuffer);
lua_pushstring(L, errorBuf);
}
else
{
lua_pushstring(L, "OK");
}
buffer_destroy(&state.S);
lua_pushnumber(L, (lua_Number)responseCode);
return 2;
}
#endif

View File

@@ -0,0 +1,59 @@
/**
* \file http_get.c
* \brief HTTP get request support using libcurl.
* \author Copyright (c) 2017 Blizzard Entertainment, João Matos and the Premake project
*/
#include "premake.h"
#include "curl_utils.h"
#ifdef PREMAKE_CURL
int http_get(lua_State* L)
{
curl_state state;
CURL* curl;
CURLcode code = CURLE_FAILED_INIT;
long responseCode = 0;
if (lua_istable(L, 2))
{
// http.get(source, { options })
curl = curlRequest(L, &state, /*optionsIndex=*/2, /*progressFnIndex=*/0, /*headersIndex=*/0);
}
else
{
// backward compatible function signature
// http.get(source, progressFunction, { headers })
curl = curlRequest(L, &state, /*optionsIndex=*/0, /*progressFnIndex=*/2, /*headersIndex=*/3);
}
if (curl)
{
curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
code = curl_easy_perform(curl);
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &responseCode);
curlCleanup(curl, &state);
}
if (code != CURLE_OK)
{
char errorBuf[1024];
lua_pushnil(L);
snprintf(errorBuf, sizeof(errorBuf) - 1, "%s\n%s\n", curl_easy_strerror(code), state.errorBuffer);
lua_pushstring(L, errorBuf);
}
else
{
lua_pushlstring(L, state.S.data, state.S.length);
lua_pushstring(L, "OK");
}
buffer_destroy(&state.S);
lua_pushnumber(L, (lua_Number)responseCode);
return 3;
}
#endif

View File

@@ -0,0 +1,57 @@
/**
* \file http_post.c
* \brief HTTP post request support using libcurl.
* \author Copyright (c) 2017 Blizzard Entertainment, João Matos and the Premake project
*/
#include "premake.h"
#include "curl_utils.h"
#ifdef PREMAKE_CURL
int http_post(lua_State* L)
{
curl_state state;
CURL* curl;
CURLcode code = CURLE_FAILED_INIT;
long responseCode = 0;
// http.post(source, postdata, { options })
curl = curlRequest(L, &state, /*optionsIndex=*/3, /*progressFnIndex=*/0, /*headersIndex=*/0);
if (curl)
{
size_t dataSize;
const char* data = luaL_checklstring(L, 2, &dataSize);
curl_easy_setopt(curl, CURLOPT_POST, 1);
if (data && dataSize > 0)
{
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)dataSize);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data);
}
code = curl_easy_perform(curl);
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &responseCode);
curlCleanup(curl, &state);
}
if (code != CURLE_OK)
{
char errorBuf[1024];
lua_pushnil(L);
snprintf(errorBuf, sizeof(errorBuf) - 1, "%s\n%s\n", curl_easy_strerror(code), state.errorBuffer);
lua_pushstring(L, errorBuf);
}
else
{
lua_pushlstring(L, state.S.data, state.S.length);
lua_pushstring(L, "OK");
}
buffer_destroy(&state.S);
lua_pushnumber(L, (lua_Number)responseCode);
return 3;
}
#endif

View File

@@ -0,0 +1,177 @@
/**
* \file lua_auxlib.c
* \brief Modifications and extensions to Lua's library functions.
* \author Copyright (c) 2014-2017 Jason Perkins and the Premake project
*/
#include "premake.h"
static int chunk_wrapper(lua_State* L);
/* Pull in Lua's aux lib implementation, but rename luaL_loadfilex() so I
* can replace it with my own implementation. */
#define luaL_loadfilex original_luaL_loadfilex
#include "lauxlib.c"
#undef luaL_loadfilex
/**
* Extend the default implementation of luaL_loadfile() to call my chunk
* wrapper, above, before executing any scripts loaded from a file.
*/
LUALIB_API int luaL_loadfilex (lua_State* L, const char* filename, const char* mode)
{
const char* script_dir;
const char* test_name;
int bottom = lua_gettop(L);
int z = !OKAY;
/* If filename starts with "$/" then we want to load the version that
* was embedded into the executable and skip the local file system */
if (filename[0] == '$') {
z = premake_load_embedded_script(L, filename + 2); /* Skip over leading "$/" */
if (z != OKAY) {
return z;
}
}
/* If the currently running script was embedded, try to load this file
* as it if were embedded too. */
if (z != OKAY) {
lua_getglobal(L, "_SCRIPT_DIR");
script_dir = lua_tostring(L, -1);
if (script_dir && script_dir[0] == '$') {
/* Call `path.getabsolute(filename, _SCRIPT_DIR)` to resolve any
* "../" sequences in the filename */
lua_pushcfunction(L, path_getabsolute);
lua_pushstring(L, filename);
lua_pushvalue(L, -3);
lua_call(L, 2, 1);
test_name = lua_tostring(L, -1);
/* if successful, filename and chunk will be on top of stack */
z = premake_load_embedded_script(L, test_name + 2); /* Skip over leading "$/" */
/* remove test_name */
lua_remove(L, bottom + 1);
}
/* remove _SCRIPT_DIR */
lua_remove(L, bottom);
}
/* Try to locate the script on the filesystem */
if (z != OKAY) {
lua_pushcfunction(L, os_locate);
lua_pushstring(L, filename);
lua_call(L, 1, 1);
test_name = lua_tostring(L, -1);
if (test_name) {
z = original_luaL_loadfilex(L, test_name, mode);
}
/* If the file exists but errors, pass that through */
if (test_name && z != OKAY && z != LUA_ERRFILE) {
return z;
}
/* If the file didn't exist, remove the result and the test
* name from the stack before checking embedded scripts */
if (z != OKAY) {
lua_pop(L, 1);
}
}
/* Try to load from embedded scripts */
if (z != OKAY) {
z = premake_load_embedded_script(L, filename);
}
/* Either way I should have ended up with the file name followed by the
* script chunk on the stack. Turn these into a closure that will call my
* wrapper below when the loaded script needs to be executed. */
if (z == OKAY) {
lua_pushcclosure(L, chunk_wrapper, 2);
}
else if (z == LUA_ERRFILE) {
lua_pushfstring(L, "cannot open %s: No such file or directory", filename);
}
return z;
}
/**
* Execute a chunk of code previously loaded by my customized version of
* luaL_loadfile(), below. Sets the _SCRIPT global variable to the absolute
* path of the loaded chunk, and makes its enclosing directory current so
* that relative path references to other files or scripts can be used.
*/
static int chunk_wrapper(lua_State* L)
{
char cwd[PATH_MAX];
const char* filename;
char* ptr;
int i, args;
args = lua_gettop(L);
/* Remember the current _SCRIPT and working directory so I can
* restore them after this new chunk has been run. */
do_getcwd(cwd, PATH_MAX);
lua_getglobal(L, "_SCRIPT");
lua_getglobal(L, "_SCRIPT_DIR");
/* Set the new _SCRIPT variable */
lua_pushvalue(L, lua_upvalueindex(1));
lua_setglobal(L, "_SCRIPT");
/* And the new _SCRIPT_DIR variable (const cheating) */
filename = lua_tostring(L, lua_upvalueindex(1));
ptr = strrchr(filename, '/');
if (ptr) *ptr = '\0';
lua_pushlstring(L, filename, strlen(filename));
lua_setglobal(L, "_SCRIPT_DIR");
/* And make that the CWD (and fix the const cheat) */
if (filename[0] != '$') {
do_chdir(L, filename);
}
if (ptr) *ptr = '/';
/* Move the function's arguments to the top of the stack and
* execute the function created by luaL_loadfile() */
lua_pushvalue(L, lua_upvalueindex(2));
for (i = 1; i <= args; ++i) {
lua_pushvalue(L, i);
}
lua_call(L, args, LUA_MULTRET);
/* Finally, restore the previous _SCRIPT variable and working directory
* before returning control to the previously executing script. */
do_chdir(L, cwd);
lua_pushvalue(L, args + 1);
lua_setglobal(L, "_SCRIPT");
lua_pushvalue(L, args + 2);
lua_setglobal(L, "_SCRIPT_DIR");
return lua_gettop(L) - args - 2;
}

View File

@@ -0,0 +1,147 @@
/**
* \file lua_shimtable.h
* \brief Lua shim for premake binary modules.
* \author Copyright (c) 2017 Tom van Dijck and the Premake project
*/
#ifndef HEADER_lua_shimtable_H
#define HEADER_lua_shimtable_H
#include "luashim.h"
static LuaFunctionTable_t s_shimTable = {
&luaL_register,
&lua_newstate,
&lua_close,
&lua_newthread,
&lua_atpanic,
&lua_version,
&lua_absindex,
&lua_gettop,
&lua_settop,
&lua_pushvalue,
&lua_rotate,
&lua_copy,
&lua_checkstack,
&lua_xmove,
&lua_isnumber,
&lua_isstring,
&lua_iscfunction,
&lua_isinteger,
&lua_isuserdata,
&lua_type,
&lua_typename,
&lua_tonumberx,
&lua_tointegerx,
&lua_toboolean,
&lua_tolstring,
&lua_rawlen,
&lua_tocfunction,
&lua_touserdata,
&lua_tothread,
&lua_topointer,
&lua_arith,
&lua_rawequal,
&lua_compare,
&lua_pushnil,
&lua_pushnumber,
&lua_pushinteger,
&lua_pushlstring,
&lua_pushstring,
&lua_pushvfstring,
&lua_pushcclosure,
&lua_pushboolean,
&lua_pushlightuserdata,
&lua_pushthread,
&lua_getglobal,
&lua_gettable,
&lua_getfield,
&lua_geti,
&lua_rawget,
&lua_rawgeti,
&lua_rawgetp,
&lua_createtable,
&lua_newuserdata,
&lua_getmetatable,
&lua_getuservalue,
&lua_setglobal,
&lua_settable,
&lua_setfield,
&lua_seti,
&lua_rawset,
&lua_rawseti,
&lua_rawsetp,
&lua_setmetatable,
&lua_setuservalue,
&lua_callk,
&lua_pcallk,
&lua_load,
&lua_dump,
&lua_yieldk,
&lua_resume,
&lua_status,
&lua_isyieldable,
&lua_gc,
&lua_error,
&lua_next,
&lua_concat,
&lua_len,
&lua_stringtonumber,
&lua_getallocf,
&lua_setallocf,
&lua_getstack,
&lua_getinfo,
&lua_getlocal,
&lua_setlocal,
&lua_getupvalue,
&lua_setupvalue,
&lua_upvalueid,
&lua_upvaluejoin,
&lua_sethook,
&lua_gethook,
&lua_gethookmask,
&lua_gethookcount,
&luaL_checkversion_,
&luaL_getmetafield,
&luaL_callmeta,
&luaL_tolstring,
&luaL_argerror,
&luaL_checklstring,
&luaL_optlstring,
&luaL_checknumber,
&luaL_optnumber,
&luaL_checkinteger,
&luaL_optinteger,
&luaL_checkstack,
&luaL_checktype,
&luaL_checkany,
&luaL_newmetatable,
&luaL_setmetatable,
&luaL_testudata,
&luaL_checkudata,
&luaL_where,
&luaL_checkoption,
&luaL_fileresult,
&luaL_execresult,
&luaL_ref,
&luaL_unref,
&luaL_loadfilex,
&luaL_loadbufferx,
&luaL_loadstring,
&luaL_newstate,
&luaL_len,
&luaL_gsub,
&luaL_setfuncs,
&luaL_getsubtable,
&luaL_traceback,
&luaL_requiref,
&luaL_buffinit,
&luaL_prepbuffsize,
&luaL_addlstring,
&luaL_addstring,
&luaL_addvalue,
&luaL_pushresult,
&luaL_pushresultsize,
&luaL_buffinitsize,
};
#endif

View File

@@ -0,0 +1,50 @@
/**
* \file os_chdir.c
* \brief Change the current working directory.
* \author Copyright (c) 2002-2014 Jason Perkins and the Premake project
*/
#include "premake.h"
int do_chdir(lua_State* L, const char* path)
{
int z;
#if PLATFORM_WINDOWS
wchar_t wide_buffer[PATH_MAX];
if (MultiByteToWideChar(CP_UTF8, 0, path, -1, wide_buffer, PATH_MAX) == 0)
{
lua_pushstring(L, "unable to encode path");
return lua_error(L);
}
z = SetCurrentDirectoryW(wide_buffer);
#else
(void)(L); /* warning: unused parameter */
z = !chdir(path);
#endif
return z;
}
int os_chdir(lua_State* L)
{
const char* path = luaL_checkstring(L, 1);
int z = do_chdir(L, path);
if (!z)
{
lua_pushnil(L);
lua_pushfstring(L, "unable to switch to directory '%s'", path);
return 2;
}
else
{
lua_pushboolean(L, 1);
return 1;
}
}

View File

@@ -0,0 +1,46 @@
/**
* \file os_chmod.c
* \brief Change file permissions
* \author Copyright (c) 2014 Jason Perkins and the Premake project
*/
#include "premake.h"
#include <sys/stat.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#if PLATFORM_WINDOWS
#include <io.h>
#endif
int os_chmod(lua_State* L)
{
int rv;
char* endPtr;
const char* path = luaL_checkstring(L, 1);
const char* modeStr = luaL_checkstring(L, 2);
int mode = (int)strtol(modeStr, &endPtr, 8);
#if PLATFORM_WINDOWS
/* DOS-mode permissions only support the low word */
mode = mode & 0x0000ffff;
rv = _chmod(path, mode);
#else
rv = chmod(path, mode);
#endif
if (rv != 0)
{
lua_pushnil(L);
lua_pushfstring(L, "unable to set mode %o on '%s', errno %d : %s", mode, path, errno, strerror(errno));
return 2;
}
else
{
lua_pushboolean(L, 1);
return 1;
}
}

View File

@@ -0,0 +1,132 @@
/**
* \file os_comparefiles.c
* \brief Check if two files are identical.
* \author Copyright (c) 2015 Jérôme "Lynix" Leclercq and the Premake project
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "premake.h"
int os_comparefiles(lua_State* L)
{
FILE* firstFile;
FILE* secondFile;
size_t firstSize;
size_t secondSize;
size_t count;
size_t read;
char firstBuffer[4096];
char secondBuffer[4096];
const char* firstPath = luaL_checkstring(L, 1);
const char* secondPath = luaL_checkstring(L, 2);
#if PLATFORM_WINDOWS
wchar_t wide_firstPath[PATH_MAX];
if (MultiByteToWideChar(CP_UTF8, 0, firstPath, -1, wide_firstPath, PATH_MAX) == 0)
{
lua_pushnil(L);
lua_pushstring(L, "unable to encode first path");
return 2;
}
wchar_t wide_secondPath[PATH_MAX];
if (MultiByteToWideChar(CP_UTF8, 0, secondPath, -1, wide_secondPath, PATH_MAX) == 0)
{
lua_pushnil(L);
lua_pushstring(L, "unable to encode second path");
return 2;
}
firstFile = _wfopen(wide_firstPath, L"rb");
secondFile = _wfopen(wide_secondPath, L"rb");
#else
firstFile = fopen(firstPath, "rb");
secondFile = fopen(secondPath, "rb");
#endif
if (!firstFile)
{
if (secondFile)
fclose(secondFile);
lua_pushnil(L);
lua_pushstring(L, "failed to open first file");
return 2;
}
if (!secondFile)
{
fclose(firstFile);
lua_pushnil(L);
lua_pushstring(L, "failed to open second file");
return 2;
}
// check sizes.
fseek(firstFile, 0, SEEK_END);
firstSize = ftell(firstFile);
fseek(firstFile, 0, SEEK_SET);
fseek(secondFile, 0, SEEK_END);
secondSize = ftell(secondFile);
fseek(secondFile, 0, SEEK_SET);
if (firstSize != secondSize)
{
fclose(firstFile);
fclose(secondFile);
lua_pushboolean(L, 0);
return 1;
}
// compare file content
while (firstSize > 0)
{
count = firstSize > 4096 ? 4096 : firstSize;
read = fread(firstBuffer, 1, count, firstFile);
if (read != count)
{
fclose(firstFile);
fclose(secondFile);
lua_pushnil(L);
lua_pushstring(L, "failed to read first file content");
return 2;
}
read = fread(secondBuffer, 1, count, secondFile);
if (read != count)
{
fclose(firstFile);
fclose(secondFile);
lua_pushnil(L);
lua_pushstring(L, "failed to read second file content");
return 2;
}
if (memcmp(firstBuffer, secondBuffer, count) != 0)
{
fclose(firstFile);
fclose(secondFile);
lua_pushboolean(L, 0);
return 1;
}
firstSize -= count;
}
// File content match
fclose(firstFile);
fclose(secondFile);
lua_pushboolean(L, 1);
return 1;
}

View File

@@ -0,0 +1,56 @@
/**
* \file os_compile.c
* \brief Compile lua source.
* \author Copyright (c) 2002-2012 Jason Perkins and the Premake project
*/
#include "premake.h"
#include "lundump.h"
#include "lstate.h"
extern int original_luaL_loadfilex(lua_State* L, const char* filename, const char* mode);
static int writer(lua_State* L, const void* p, size_t size, void* u)
{
UNUSED(L);
return (fwrite(p, size, 1, (FILE*)u) != 1) && (size != 0);
}
int os_compile(lua_State* L)
{
const char* input = luaL_checkstring(L, 1);
const char* output = luaL_checkstring(L, 2);
lua_State* P = luaL_newstate();
if (original_luaL_loadfilex(P, input, NULL) != LUA_OK)
{
const char* msg = lua_tostring(P, -1);
if (msg == NULL)
msg = "(error with no message)";
lua_pushnil(L);
lua_pushfstring(L, "Unable to compile '%s': %s", input, msg);
lua_close(P);
return 2;
}
else
{
FILE* outputFile = (output == NULL) ? stdout : fopen(output, "wb");
if (outputFile == NULL)
{
lua_close(P);
lua_pushnil(L);
lua_pushfstring(L, "unable to write to '%s'", output);
return 2;
}
lua_dump(P, writer, outputFile, FALSE);
fclose(outputFile);
lua_close(P);
lua_pushboolean(L, 1);
return 1;
}
}

View File

@@ -0,0 +1,60 @@
/**
* \file os_copyfile.c
* \brief Copy a file from one location to another.
* \author Copyright (c) 2002-2008 Jason Perkins and the Premake project
*/
#include <stdlib.h>
#include "premake.h"
int os_copyfile(lua_State* L)
{
int z;
const char* src = luaL_checkstring(L, 1);
const char* dst = luaL_checkstring(L, 2);
#if PLATFORM_WINDOWS
wchar_t wide_src[PATH_MAX];
wchar_t wide_dst[PATH_MAX];
if (MultiByteToWideChar(CP_UTF8, 0, src, -1, wide_src, PATH_MAX) == 0)
{
lua_pushstring(L, "unable to encode source path");
return lua_error(L);
}
if (MultiByteToWideChar(CP_UTF8, 0, dst, -1, wide_dst, PATH_MAX) == 0)
{
lua_pushstring(L, "unable to encode source path");
return lua_error(L);
}
z = CopyFileW(wide_src, wide_dst, FALSE);
#else
lua_pushfstring(L, "cp \"%s\" \"%s\"", src, dst);
z = (system(lua_tostring(L, -1)) == 0);
#endif
if (!z)
{
lua_pushnil(L);
#if PLATFORM_WINDOWS
wchar_t buf[256];
FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buf, 256, NULL);
char bufA[256];
WideCharToMultiByte(CP_UTF8, 0, buf, 256, bufA, 256, 0, 0);
lua_pushfstring(L, "unable to copy file to '%s', reason: '%s'", dst, bufA);
#else
lua_pushfstring(L, "unable to copy file to '%s'", dst);
#endif
return 2;
}
else
{
lua_pushboolean(L, 1);
return 1;
}
}

View File

@@ -0,0 +1,144 @@
/**
* \file os_reg.c
* \brief Returns true if the given file exists on the file system.
* \author Copyright (c) 2002-2016 Jason Perkins and the Premake project
*/
#include "premake.h"
#if PLATFORM_WINDOWS
typedef struct RegKeyInfo
{
HKEY key;
HKEY subkey;
char * value;
} RegKeyInfo;
HKEY getRegistryKey(const char **path)
{
if (_strnicmp(*path, "HKCU:", 5) == 0) {
*path += 5;
return HKEY_CURRENT_USER;
}
if (_strnicmp(*path, "HKLM:", 5) == 0) {
*path += 5;
return HKEY_LOCAL_MACHINE;
}
if (_strnicmp(*path, "HKCR:", 5) == 0) {
*path += 5;
return HKEY_CLASSES_ROOT;
}
if (_strnicmp(*path, "HKU:", 4) == 0) {
*path += 4;
return HKEY_USERS;
}
if (_strnicmp(*path, "HKCC:", 5) == 0) {
*path += 5;
return HKEY_CURRENT_CONFIG;
}
// unsupported or invalid key prefix
return NULL;
}
static HKEY getSubkey(HKEY key, const char **path)
{
HKEY subkey;
size_t length;
char *subpath;
const char *value;
char hasValue;
if (key == NULL)
return NULL;
// skip the initial path separator
if ((*path)[0] == '\\')
(*path)++;
// make a copy of the subkey path that excludes the value name (if present)
value = strrchr(*path, '\\');
hasValue = value ? value[1] : 0;
if (hasValue) {
length = (size_t)(value - *path);
subpath = (char *)malloc(length + 1);
strncpy(subpath, *path, length);
subpath[length] = 0;
}
// no value separator means we should check the default value
else {
subpath = (char *)*path;
length = strlen(subpath);
}
// open the key for reading
if (RegOpenKeyExA(key, subpath, 0, KEY_READ, &subkey) != ERROR_SUCCESS)
subkey = NULL;
// free the subpath if one was allocated
if (hasValue)
free(subpath);
*path += length;
return subkey;
}
static char * getValue(HKEY key, const char * name)
{
DWORD length;
char * value;
if (key == NULL || name == NULL)
return NULL;
// skip the initial path separator
if (name[0] == '\\')
name++;
// query length of value
if (RegQueryValueExA(key, name, NULL, NULL, NULL, &length) != ERROR_SUCCESS)
return NULL;
// allocate room for the value and fetch it
value = (char *)malloc((size_t)length + 1);
if (RegQueryValueExA(key, name, NULL, NULL, (LPBYTE)value, &length) != ERROR_SUCCESS) {
free(value);
return NULL;
}
value[length] = 0;
return value;
}
static void fetchKeyInfo(struct RegKeyInfo *info, const char *path)
{
info->key = getRegistryKey(&path);
info->subkey = getSubkey(info->key, &path);
info->value = getValue(info->subkey, path);
}
static void releaseKeyInfo(struct RegKeyInfo *info)
{
free(info->value);
RegCloseKey(info->subkey);
}
int os_getWindowsRegistry(lua_State *L)
{
RegKeyInfo info;
fetchKeyInfo(&info, luaL_checkstring(L, 1));
lua_pushstring(L, info.value);
releaseKeyInfo(&info);
return 1;
}
#else
int os_getWindowsRegistry(lua_State *L)
{
lua_pushnil(L);
return 1;
}
#endif

View File

@@ -0,0 +1,40 @@
/**
* \file os_getcwd.c
* \brief Retrieve the current working directory.
* \author Copyright (c) 2002-2008 Jason Perkins and the Premake project
*/
#include "premake.h"
int os_getcwd(lua_State* L)
{
char buffer[0x4000];
if (do_getcwd(buffer, 0x4000)) {
lua_pushstring(L, buffer);
return 1;
}
else {
return 0;
}
}
int do_getcwd(char* buffer, size_t size)
{
int result;
#if PLATFORM_WINDOWS
wchar_t wbuffer[PATH_MAX];
result = (GetCurrentDirectoryW(PATH_MAX, wbuffer) != 0);
if (result) {
WideCharToMultiByte(CP_UTF8, 0, wbuffer, -1, buffer, (int)size, NULL, NULL);
do_translate(buffer, '/');
}
#else
result = (getcwd(buffer, size) != 0);
#endif
return result;
}

View File

@@ -0,0 +1,38 @@
/**
* \file os_getpass.c
* \brief Prompt and retrieve a password from the user.
* \author Copyright (c) 2002-2008 Jason Perkins and the Premake project
*/
#include "premake.h"
int os_getpass(lua_State* L)
{
const char* prompt = luaL_checkstring(L, 1);
#if PLATFORM_WINDOWS
HANDLE hstdout = GetStdHandle(STD_OUTPUT_HANDLE);
HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
DWORD read_chars, mode, written_chars;
char buffer[1024];
const char* newline = "\n";
WriteConsoleA(hstdout, prompt, (DWORD)strlen(prompt), &written_chars, NULL);
GetConsoleMode(hstdin, &mode);
SetConsoleMode(hstdin, ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);
ReadConsoleA(hstdin, buffer, sizeof (buffer), &read_chars, NULL);
SetConsoleMode(hstdin, mode);
WriteConsoleA(hstdout, newline, (DWORD)strlen(newline), &written_chars, NULL);
buffer[strcspn(buffer, "\r\n")] = '\0';
lua_pushstring(L, buffer);
return 1;
#else
lua_pushstring(L, getpass(prompt));
return 1;
#endif
}

View File

@@ -0,0 +1,332 @@
/**
* \file os_getversioninfo.c
* \brief Retrieve operating system version information.
* \author Copyright (c) 2011-2012 Jason Perkins and the Premake project
*/
#include "premake.h"
#include <stdlib.h>
struct OsVersionInfo
{
int majorversion;
int minorversion;
int revision;
const char* description;
int isalloc;
};
static int getversion(struct OsVersionInfo* info);
int os_getversion(lua_State* L)
{
struct OsVersionInfo info = {0, 0, 0, NULL, 0};
if (!getversion(&info))
{
return 0;
}
lua_newtable(L);
lua_pushstring(L, "majorversion");
lua_pushnumber(L, (lua_Number)info.majorversion);
lua_settable(L, -3);
lua_pushstring(L, "minorversion");
lua_pushnumber(L, (lua_Number)info.minorversion);
lua_settable(L, -3);
lua_pushstring(L, "revision");
lua_pushnumber(L, (lua_Number)info.revision);
lua_settable(L, -3);
lua_pushstring(L, "description");
lua_pushstring(L, info.description);
lua_settable(L, -3);
if (info.isalloc) {
free((void*)info.description);
}
return 1;
}
/*************************************************************/
#if defined(PLATFORM_WINDOWS)
#ifdef _MSC_VER
#pragma comment(lib, "version.lib")
#endif
int getKernelVersion(struct OsVersionInfo* info)
{
DWORD size = GetFileVersionInfoSizeA("kernel32.dll", NULL);
if (size > 0)
{
void* data = malloc(size);
if (GetFileVersionInfoA("kernel32.dll", 0, size, data))
{
void* fixedInfoPtr;
UINT fixedInfoSize;
if (VerQueryValueA(data, "\\", &fixedInfoPtr, &fixedInfoSize))
{
VS_FIXEDFILEINFO* fileInfo = (VS_FIXEDFILEINFO*)fixedInfoPtr;
info->majorversion = HIWORD(fileInfo->dwProductVersionMS);
info->minorversion = LOWORD(fileInfo->dwProductVersionMS);
info->revision = HIWORD(fileInfo->dwProductVersionLS);
return TRUE;
}
}
}
return FALSE;
}
int getversion(struct OsVersionInfo* info)
{
HKEY key;
info->description = "Windows";
// First get a friendly product name from the registry.
if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "Software\\Microsoft\\Windows NT\\CurrentVersion", 0, KEY_READ, &key) == ERROR_SUCCESS)
{
char value[512];
DWORD value_length = sizeof(value);
DWORD type;
RegQueryValueExA(key, "productName", NULL, &type, (LPBYTE)value, &value_length);
RegCloseKey(key);
if (type == REG_SZ)
{
info->description = strdup(value);
info->isalloc = 1;
}
}
// See if we can get a product version number from kernel32.dll
return getKernelVersion(info);
}
/*************************************************************/
#elif defined(PLATFORM_MACOSX)
#include <CoreFoundation/CoreFoundation.h>
#include <sys/param.h>
#include <sys/sysctl.h>
#include <string.h>
#include <stdio.h>
int getversion(struct OsVersionInfo* info)
{
const char * propertyListFilePath = "/System/Library/CoreServices/SystemVersion.plist";
Boolean fallback = TRUE;
info->description = "Mac OS";
info->majorversion = 10;
if (access (propertyListFilePath, R_OK) == 0)
{
CFPropertyListFormat format;
CFErrorRef errorDescriptor = NULL;
CFStringRef stringRef = NULL;
CFURLRef urlRef = NULL;
CFReadStreamRef streamRef = NULL;
CFPropertyListRef propertyListRef = NULL;
CFTypeID typeId = 0;
Boolean result = FALSE;
stringRef = CFStringCreateWithCStringNoCopy(
kCFAllocatorDefault,
propertyListFilePath,
kCFStringEncodingASCII,
kCFAllocatorNull);
if (stringRef == NULL)
{
goto getversion_macosx_cleanup;
};
urlRef = CFURLCreateWithFileSystemPath(
kCFAllocatorDefault,
stringRef,
kCFURLPOSIXPathStyle,
false);
if (urlRef == NULL)
{
goto getversion_macosx_cleanup;
}
streamRef = CFReadStreamCreateWithFile(
kCFAllocatorDefault,
urlRef);
if (streamRef == NULL)
{
goto getversion_macosx_cleanup;
}
result = CFReadStreamOpen (streamRef);
if (result == FALSE)
{
goto getversion_macosx_cleanup;
}
propertyListRef = CFPropertyListCreateWithStream(
kCFAllocatorDefault,
streamRef,
0,
kCFPropertyListImmutable,
&format,
&errorDescriptor);
CFReadStreamClose (streamRef);
if (!(propertyListRef && CFPropertyListIsValid(propertyListRef, format)) || errorDescriptor)
{
goto getversion_macosx_cleanup;
}
typeId = CFGetTypeID(propertyListRef);
if (typeId == CFDictionaryGetTypeID())
{
const CFDictionaryRef dictionaryRef = (const CFDictionaryRef)propertyListRef;
char versionString[128];
CFStringRef stringValueRef = NULL;
if (CFDictionaryGetValueIfPresent(dictionaryRef, CFSTR("ProductVersion"), (const void **)(&stringValueRef)))
{
CFStringGetCString(stringValueRef, &versionString[0], (CFIndex)sizeof (versionString), kCFStringEncodingASCII);
sscanf (versionString, "%d.%d.%d", &info->majorversion, &info->minorversion, &info->revision);
fallback = FALSE;
}
}
getversion_macosx_cleanup:
if (propertyListRef) CFRelease (propertyListRef);
if (streamRef) CFRelease (streamRef);
if (urlRef) CFRelease (urlRef);
if (stringRef) CFRelease (stringRef);
}
if (fallback == TRUE)
{
int mib[] = { CTL_KERN, KERN_OSRELEASE };
size_t len;
sysctl(mib, sizeof(mib) / sizeof(mib[0]), NULL, &len, NULL, 0);
char kernel_version[len];
sysctl(mib, sizeof(mib) / sizeof(mib[0]), kernel_version, &len, NULL, 0);
int kern_major;
int kern_minor;
sscanf(kernel_version, "%d.%d.%*d", &kern_major, &kern_minor);
info->minorversion = kern_major - 4;
info->revision = kern_minor;
}
if (info->majorversion == 10)
{
switch (info->minorversion)
{
case 4:
info->description = "Mac OS X Tiger";
break;
case 5:
info->description = "Mac OS X Leopard";
break;
case 6:
info->description = "Mac OS X Snow Leopard";
break;
case 7:
info->description = "OS X Lion";
break;
case 8:
info->description = "OS X Mountain Lion";
break;
case 9:
info->description = "OS X Mavericks";
case 10:
info->description = "OS X Yosemite";
case 11:
info->description = "OS X El Capitan";
break;
case 12:
info->description = "macOS Sierra";
break;
case 13:
info->description = "macOS High Sierra";
break;
case 14:
info->description = "macOS Mojave";
break;
case 15:
info->description = "macOS Catalina";
break;
}
}
return 1;
}
/*************************************************************/
#elif defined(PLATFORM_BSD) || defined(PLATFORM_LINUX) || defined(PLATFORM_SOLARIS) || defined(PLATFORM_HURD) || defined(PLATFORM_HAIKU)
#include <string.h>
#include <sys/utsname.h>
int getversion(struct OsVersionInfo* info)
{
struct utsname u;
char* ver;
info->majorversion = 0;
info->minorversion = 0;
info->revision = 0;
if (uname(&u))
{
// error
info->description = PLATFORM_STRING;
return 0;
}
#if __GLIBC__
// When using glibc, info->description gets set to u.sysname,
// but it isn't passed out of this function, so we need to copy
// the string.
info->description = malloc(strlen(u.sysname) + 1);
strcpy((char*)info->description, u.sysname);
info->isalloc = 1;
#else
info->description = u.sysname;
#endif
if ((ver = strtok(u.release, ".-")) != NULL)
{
info->majorversion = atoi(ver);
// continue parsing from the previous position
if ((ver = strtok(NULL, ".-")) != NULL)
{
info->minorversion = atoi(ver);
if ((ver = strtok(NULL, ".-")) != NULL)
info->revision = atoi(ver);
}
}
return 1;
}
/*************************************************************/
#else
int getversion(struct OsVersionInfo* info)
{
return 0;
}
#endif

View File

@@ -0,0 +1,13 @@
/**
* \file os_host.c
* \brief Get the current host OS we're executing on.
* \author Copyright (c) 2014-2017 Tom van Dijck, Jason Perkins and the Premake project
*/
#include "premake.h"
int os_host(lua_State* L)
{
lua_pushstring(L, PLATFORM_STRING);
return 1;
}

View File

@@ -0,0 +1,30 @@
/**
* \file os_is64bit.c
* \brief Native code-side checking for a 64-bit architecture.
* \author Copyright (c) 2011 Jason Perkins and the Premake project
*/
#include "premake.h"
int os_is64bit(lua_State* L)
{
// If this code returns true, then the platform is 64-bit. If it
// returns false, the platform might still be 64-bit, but more
// checking will need to be done on the Lua side of things.
#if PLATFORM_WINDOWS
typedef BOOL (WINAPI* WowFuncSig)(HANDLE, PBOOL);
WowFuncSig func = (WowFuncSig)GetProcAddress(GetModuleHandle(TEXT("kernel32")), "IsWow64Process");
if (func)
{
BOOL isWow = FALSE;
if (func(GetCurrentProcess(), &isWow))
{
lua_pushboolean(L, isWow);
return 1;
}
}
#endif
lua_pushboolean(L, 0);
return 1;
}

View File

@@ -0,0 +1,56 @@
/**
* \file os_isdir.c
* \brief Returns true if the specified directory exists.
* \author Copyright (c) 2002-2008 Jason Perkins and the Premake project
*/
#include <string.h>
#include <sys/stat.h>
#include "premake.h"
#ifdef _WIN32
#include <windows.h>
#endif
int os_isdir(lua_State* L)
{
struct stat buf;
const char* path = luaL_checkstring(L, 1);
#ifdef _WIN32
DWORD attr;
wchar_t wide_path[PATH_MAX];
if (MultiByteToWideChar(CP_UTF8, 0, path, -1, wide_path, PATH_MAX) == 0)
{
lua_pushstring(L, "unable to encode path");
return lua_error(L);
}
#endif
/* empty path is equivalent to ".", must be true */
if (strlen(path) == 0)
{
lua_pushboolean(L, 1);
}
#ifdef _WIN32
// Use Windows-specific GetFileAttributes since it deals with symbolic links.
else if ((attr = GetFileAttributesW(wide_path)) != INVALID_FILE_ATTRIBUTES)
{
int isdir = (attr & FILE_ATTRIBUTE_DIRECTORY) != 0;
lua_pushboolean(L, isdir);
}
#endif
else if (stat(path, &buf) == 0)
{
int isdir = (buf.st_mode & S_IFDIR) != 0;
lua_pushboolean(L, isdir);
}
else
{
lua_pushboolean(L, 0);
}
return 1;
}

View File

@@ -0,0 +1,48 @@
/**
* \file os_isfile.c
* \brief Returns true if the given file exists on the file system.
* \author Copyright (c) 2002-2008 Jason Perkins and the Premake project
*/
#include <sys/stat.h>
#include "premake.h"
int os_isfile(lua_State* L)
{
const char* filename = luaL_checkstring(L, 1);
lua_pushboolean(L, do_isfile(L, filename));
return 1;
}
int do_isfile(lua_State* L, const char* filename)
{
#if PLATFORM_WINDOWS
wchar_t wide_path[PATH_MAX];
DWORD attrib;
if (MultiByteToWideChar(CP_UTF8, 0, filename, -1, wide_path, PATH_MAX) == 0)
{
lua_pushstring(L, "unable to encode filepath");
return lua_error(L);
}
attrib = GetFileAttributesW(wide_path);
if (attrib != INVALID_FILE_ATTRIBUTES)
{
return (attrib & FILE_ATTRIBUTE_DIRECTORY) == 0;
}
#else
struct stat buf;
(void)(L); /* warning: unused parameter */
if (stat(filename, &buf) == 0)
{
return ((buf.st_mode & S_IFDIR) == 0);
}
#endif
return 0;
}

View File

@@ -0,0 +1,43 @@
/**
* \file os_islink.c
* \brief Returns true if the given path is a symbolic link or reparse point.
* \author Copyright (c) 2014 Jason Perkins and the Premake project
*/
#include <sys/stat.h>
#include "premake.h"
int os_islink(lua_State* L)
{
const char* path = luaL_checkstring(L, 1);
#if PLATFORM_WINDOWS
{
wchar_t wide_path[PATH_MAX];
DWORD attr;
if (MultiByteToWideChar(CP_UTF8, 0, path, -1, wide_path, PATH_MAX) == 0)
{
lua_pushstring(L, "unable to encode path");
return lua_error(L);
}
attr = GetFileAttributesW(wide_path);
if (attr != INVALID_FILE_ATTRIBUTES) {
lua_pushboolean(L, (attr & FILE_ATTRIBUTE_REPARSE_POINT) != 0);
return 1;
}
}
#else
{
struct stat buf;
if (lstat(path, &buf) == 0) {
lua_pushboolean(L, S_ISLNK(buf.st_mode));
return 1;
}
}
#endif
lua_pushboolean(L, 0);
return 1;
}

View File

@@ -0,0 +1,252 @@
/**
* \file os_reg.c
* \brief Returns true if the given file exists on the file system.
* \author Copyright (c) 2002-2016 Jason Perkins and the Premake project
*/
#include "premake.h"
#if PLATFORM_WINDOWS
typedef struct RegNodeInfo
{
const char * name;
const char * value;
DWORD valueSize;
DWORD type;
} RegNodeInfo;
typedef void (*ListCallback)(const RegNodeInfo * info, void * user);
extern HKEY getRegistryKey(const char** path);
static const char* getTypeString(DWORD type)
{
switch (type)
{
case REG_NONE: return "REG_NONE";
case REG_SZ: return "REG_SZ";
case REG_EXPAND_SZ: return "REG_EXPAND_SZ";
case REG_BINARY: return "REG_BINARY";
case REG_DWORD: return "REG_DWORD";
case REG_DWORD_BIG_ENDIAN: return "REG_DWORD_BIG_ENDIAN";
case REG_LINK: return "REG_LINK";
case REG_MULTI_SZ: return "REG_MULTI_SZ";
case REG_RESOURCE_LIST: return "REG_RESOURCE_LIST";
case REG_FULL_RESOURCE_DESCRIPTOR: return "REG_FULL_RESOURCE_DESCRIPTOR";
case REG_RESOURCE_REQUIREMENTS_LIST: return "REG_RESOURCE_REQUIREMENTS_LIST";
case REG_QWORD: return "REG_QWORD";
default: return NULL;
}
}
static HKEY openKey(const char *path)
{
HKEY key, subkey;
// check string
if (path == NULL)
return NULL;
// get HKEY
key = getRegistryKey(&path);
if (key == NULL)
return NULL;
// skip the initial path separator
if (path[0] == '\\')
path++;
// open the key for reading
if (RegOpenKeyExA(key, path, 0, KEY_READ, &subkey) != ERROR_SUCCESS)
subkey = NULL;
return subkey;
}
static int listNodes(HKEY key, ListCallback callback, void * user)
{
RegNodeInfo node;
DWORD maxSubkeyLength;
DWORD maxValueLength;
DWORD maxNameLength;
DWORD numSubkeys;
DWORD numValues;
DWORD length;
DWORD index;
char* name;
char* value;
int ok;
if (key == NULL || callback == NULL)
return 0;
// Initialize node structure
node.value = NULL;
node.valueSize = 0;
node.type = REG_NONE;
// Fetch info about key content
if (RegQueryInfoKeyA(key, NULL, NULL, NULL, &numSubkeys, &maxSubkeyLength, NULL, &numValues, &maxNameLength, &maxValueLength, NULL, NULL) != ERROR_SUCCESS)
return 0;
// Allocate name and value buffers
if (maxSubkeyLength > maxNameLength)
maxNameLength = maxSubkeyLength;
maxNameLength++;
maxValueLength++;
name = (char*)malloc((size_t)maxNameLength);
value = (char*)malloc((size_t)maxValueLength + 1);
// Iterate over subkeys
ok = 1;
node.name = name;
for (index = 0; index < numSubkeys; index++) {
length = maxNameLength;
if (RegEnumKeyExA(key, index, name, &length, NULL, NULL, NULL, NULL) != ERROR_SUCCESS) {
ok = 0;
break;
}
callback(&node, user);
}
// Iterate over values
if (ok) {
node.value = value;
for (index = 0; index < numValues; index++) {
length = maxNameLength;
node.valueSize = maxValueLength;
if (RegEnumValueA(key, index, name, &length, NULL, &node.type, (LPBYTE)value, &node.valueSize) != ERROR_SUCCESS) {
ok = 0;
break;
}
// Ensure proper termination of strings (two terminators for the REG_MULTI_SZ)
value[node.valueSize] = '\0';
value[node.valueSize + 1] = '\0';
callback(&node, user);
}
}
// Free buffers
free(name);
free(value);
return ok;
}
static void listCallback(const RegNodeInfo* info, void* user)
{
lua_State* L = (lua_State*)user;
const char* typeString;
// Insert key into the result table (keys are represented as empty tables)
if (info->value == NULL) {
lua_createtable(L, 0, 0);
lua_setfield(L, -2, info->name);
return;
}
// Values are represented as tables containing "type" and "value" records
typeString = getTypeString(info->type);
lua_createtable(L, 0, 2);
lua_pushstring(L, typeString ? typeString : "Unknown");
lua_setfield(L, -2, "type");
switch (info->type)
{
// Binary encoded values -> size defined string
case REG_NONE:
case REG_BINARY:
case REG_RESOURCE_LIST:
case REG_FULL_RESOURCE_DESCRIPTOR:
case REG_RESOURCE_REQUIREMENTS_LIST: {
lua_pushlstring(L, info->value, info->valueSize);
break;
}
// String encoded values -> zero terminated string
case REG_SZ:
case REG_EXPAND_SZ:
case REG_LINK: {
lua_pushstring(L, info->value);
break;
}
// Numbers
case REG_DWORD: {
lua_pushinteger(L, *(DWORD32*)info->value);
break;
}
case REG_DWORD_BIG_ENDIAN: {
lua_pushinteger(L, (info->value[3] << 0) | (info->value[2] << 8) | (info->value[1] << 16) | (info->value[0] << 24));
break;
}
case REG_QWORD: {
lua_pushinteger(L, *(DWORD64*)info->value);
break;
}
// Multiple strings
case REG_MULTI_SZ: {
DWORD i, j, k;
lua_newtable(L);
for (i = j = 0, k = 1; i < info->valueSize; i++)
{
if (info->value[i] != 0)
continue;
if (i == j)
break;
lua_pushlstring(L, &info->value[j], i - j);
lua_rawseti(L, -2, k);
j = i + 1;
k++;
}
break;
}
// Unknown field -> nil
default: {
lua_pushnil(L);
break;
}
}
lua_setfield(L, -2, "value");
// Complete the value subtable
lua_setfield(L, -2, info->name);
}
int os_listWindowsRegistry(lua_State* L)
{
HKEY key = openKey(luaL_checkstring(L, 1));
if (key == NULL) {
lua_pushnil(L);
return 1;
}
lua_newtable(L);
if (!listNodes(key, listCallback, (void *)L)) {
// Discard table in case of fault and push nil instead
lua_pop(L, 1);
lua_pushnil(L);
}
RegCloseKey(key);
return 1;
}
#else
int os_listWindowsRegistry(lua_State* L)
{
lua_pushnil(L);
return 1;
}
#endif

View File

@@ -0,0 +1,68 @@
/**
* \file os_locate.c
* \brief Locates files along the standard built-in search paths.
* \author Copyright (c) 2014-2015 Jason Perkins and the Premake project
*/
#include <stdlib.h>
#include "premake.h"
int do_locate(lua_State* L, const char* filename, const char* path)
{
if (do_pathsearch(L, filename, path)) {
lua_pushstring(L, "/");
lua_pushstring(L, filename);
lua_concat(L, 3);
return 1;
}
return 0;
}
int os_locate(lua_State* L)
{
const char* path;
int i;
int nArgs = lua_gettop(L);
/* Fetch premake.path */
lua_getglobal(L, "premake");
lua_getfield(L, -1, "path");
path = lua_tostring(L, -1);
for (i = 1; i <= nArgs; ++i) {
const char* name = lua_tostring(L, i);
/* Direct path to an embedded file? */
if (name[0] == '$' && name[1] == '/' && premake_find_embedded_script(name + 2)) {
lua_pushvalue(L, i);
return 1;
}
/* Direct path to file? Return as absolute path */
if (do_isfile(L, name)) {
lua_pushcfunction(L, path_getabsolute);
lua_pushvalue(L, i);
lua_call(L, 1, 1);
return 1;
}
/* do_locate(arg[i], premake.path) */
if (do_locate(L, name, path)) {
return 1;
}
/* embedded in the executable? */
if (premake_find_embedded_script(name)) {
lua_pushstring(L, "$/");
lua_pushvalue(L, i);
lua_concat(L, 2);
return 1;
}
}
return 0;
}

View File

@@ -0,0 +1,207 @@
/**
* \file os_match.c
* \brief Match files and directories.
* \author Copyright (c) 2002-2014 Jason Perkins and the Premake project
*/
#include <stdlib.h>
#include <string.h>
#include "premake.h"
#if PLATFORM_WINDOWS
typedef struct struct_MatchInfo
{
HANDLE handle;
int is_first;
WIN32_FIND_DATAW entry;
} MatchInfo;
int os_matchstart(lua_State* L)
{
const char* mask = luaL_checkstring(L, 1);
MatchInfo* m;
wchar_t wide_mask[PATH_MAX];
if (MultiByteToWideChar(CP_UTF8, 0, mask, -1, wide_mask, PATH_MAX) == 0)
{
lua_pushstring(L, "unable to encode mask");
return lua_error(L);
}
m = (MatchInfo*)malloc(sizeof(MatchInfo));
m->handle = FindFirstFileW(wide_mask, &m->entry);
m->is_first = 1;
lua_pushlightuserdata(L, m);
return 1;
}
int os_matchdone(lua_State* L)
{
MatchInfo* m = (MatchInfo*)lua_touserdata(L, 1);
if (m->handle != INVALID_HANDLE_VALUE)
FindClose(m->handle);
free(m);
return 0;
}
int os_matchname(lua_State* L)
{
MatchInfo* m = (MatchInfo*)lua_touserdata(L, 1);
char filename[PATH_MAX];
if (WideCharToMultiByte(CP_UTF8, 0, m->entry.cFileName, -1, filename, PATH_MAX, NULL, NULL) == 0)
{
lua_pushstring(L, "unable to decode filename");
return lua_error(L);
}
lua_pushstring(L, filename);
return 1;
}
int os_matchisfile(lua_State* L)
{
MatchInfo* m = (MatchInfo*)lua_touserdata(L, 1);
lua_pushboolean(L, (m->entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0);
return 1;
}
int os_matchnext(lua_State* L)
{
MatchInfo* m = (MatchInfo*)lua_touserdata(L, 1);
if (m->handle == INVALID_HANDLE_VALUE) {
return 0;
}
while (m) /* loop forever */
{
if (m->is_first)
m->is_first = 0;
else
{
if (!FindNextFileW(m->handle, &m->entry))
return 0;
}
if (wcscmp(m->entry.cFileName, L".") != 0 && wcscmp(m->entry.cFileName, L"..") != 0)
{
lua_pushboolean(L, 1);
return 1;
}
}
return 0;
}
#else
#include <dirent.h>
#include <fnmatch.h>
#include <sys/stat.h>
typedef struct struct_MatchInfo
{
DIR* handle;
struct dirent* entry;
char* path;
char* mask;
} MatchInfo;
int os_matchstart(lua_State* L)
{
const char* split;
const char* mask = luaL_checkstring(L, 1);
MatchInfo* m = (MatchInfo*)malloc(sizeof(MatchInfo));
/* split the mask into path and filename components */
split = strrchr(mask, '/');
if (split)
{
m->path = (char*)malloc(split - mask + 1);
strncpy(m->path, mask, split - mask);
m->path[split - mask] = '\0';
m->mask = (char*)malloc(mask + strlen(mask) - split);
strcpy(m->mask, split + 1);
}
else
{
m->path = (char*)malloc(2);
strcpy(m->path, ".");
m->mask = (char*)malloc(strlen(mask)+1);
strcpy(m->mask, mask);
}
m->handle = opendir(m->path);
lua_pushlightuserdata(L, m);
return 1;
}
int os_matchdone(lua_State* L)
{
MatchInfo* m = (MatchInfo*)lua_touserdata(L, 1);
if (m->handle != NULL)
closedir(m->handle);
free(m->path);
free(m->mask);
free(m);
return 0;
}
int os_matchname(lua_State* L)
{
MatchInfo* m = (MatchInfo*)lua_touserdata(L, 1);
lua_pushstring(L, m->entry->d_name);
return 1;
}
int os_matchisfile(lua_State* L)
{
MatchInfo* m = (MatchInfo*)lua_touserdata(L, 1);
#if defined(_DIRENT_HAVE_D_TYPE)
// Dirent marks symlinks as DT_LNK, not (DT_LNK|DT_DIR). The fallback handles symlinks using stat.
if (m->entry->d_type == DT_DIR)
{
lua_pushboolean(L, 0);
}
else
#endif
{
const char* fname;
lua_pushfstring(L, "%s/%s", m->path, m->entry->d_name);
fname = lua_tostring(L, -1);
lua_pop(L, 1);
lua_pushboolean(L, do_isfile(L, fname));
}
return 1;
}
int os_matchnext(lua_State* L)
{
MatchInfo* m = (MatchInfo*)lua_touserdata(L, 1);
if (m->handle == NULL) {
return 0;
}
m->entry = readdir(m->handle);
while (m->entry != NULL)
{
const char* name = m->entry->d_name;
if (strcmp(name, ".") != 0 && strcmp(name, "..") != 0)
{
if (fnmatch(m->mask, name, 0) == 0)
{
lua_pushboolean(L, 1);
return 1;
}
}
m->entry = readdir(m->handle);
}
return 0;
}
#endif

View File

@@ -0,0 +1,78 @@
/**
* \file os_mkdir.c
* \brief Create a subdirectory.
* \author Copyright (c) 2002-2008 Jason Perkins and the Premake project
*/
#include <sys/stat.h>
#include <string.h>
#include <stdio.h>
#include "premake.h"
#if PLATFORM_WINDOWS
#include <direct.h>
#include <errno.h>
#endif
int do_mkdir(const char* path)
{
struct stat sb;
char sub_path[1024];
int i, length;
// if it already exists, return.
if (stat(path, &sb) == 0)
return 1;
// find the parent folder name.
length = (int)strlen(path);
for (i = length - 1; i >= 0; --i)
{
if (path[i] == '/' || path[i] == '\\')
break;
}
// if we found one, create it.
if (i > 0)
{
memcpy(sub_path, path, i);
sub_path[i] = '\0';
#if PLATFORM_WINDOWS
if (sub_path[i - 1] == ':')
{
sub_path[i + 0] = '/';
sub_path[i + 1] = '\0';
}
#endif
if (!do_mkdir(sub_path))
return 0;
}
// now finally create the actual folder we want.
#if PLATFORM_WINDOWS
return _mkdir(path) == 0;
#else
return mkdir(path, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == 0;
#endif
}
int os_mkdir(lua_State* L)
{
const char* path = luaL_checkstring(L, 1);
int z = do_mkdir(path);
if (!z)
{
lua_pushnil(L);
lua_pushfstring(L, "unable to create directory '%s'", path);
return 2;
}
lua_pushboolean(L, 1);
return 1;
}

View File

@@ -0,0 +1,93 @@
/**
* \file os_pathsearch.c
* \brief Locates a file, given a set of search paths.
* \author Copyright (c) 2002-2015 Jason Perkins and the Premake project
*
* \note This function is required by the bootstrapping code; it must be
* implemented here in the host and not scripted.
*/
#include <string.h>
#include "premake.h"
int do_pathsearch(lua_State* L, const char* filename, const char* path)
{
do
{
const char* split;
/* look for the closest path separator ; or : */
/* can't use : on windows because it breaks on C:\path */
const char* semi = strchr(path, ';');
#if !defined(PLATFORM_WINDOWS)
const char* full = strchr(path, ':');
#else
const char* full = NULL;
#endif
if (!semi)
{
split = full;
}
else if (!full)
{
split = semi;
}
else
{
split = (semi < full) ? semi : full;
}
/* push this piece of the full search string onto the stack */
if (split)
{
lua_pushlstring(L, path, split - path);
}
else
{
lua_pushstring(L, path);
}
/* keep an extra copy around, so I can return it if I have a match */
lua_pushvalue(L, -1);
/* append the filename to make the full test path */
lua_pushstring(L, "/");
lua_pushstring(L, filename);
lua_concat(L, 3);
/* test it - if it exists, return the absolute path */
if (do_isfile(L, lua_tostring(L, -1)))
{
lua_pop(L, 1);
lua_pushcfunction(L, path_getabsolute);
lua_pushvalue(L, -2);
lua_call(L, 1, 1);
return 1;
}
/* no match, set up the next try */
lua_pop(L, 2);
path = (split) ? split + 1 : NULL;
}
while (path);
return 0;
}
int os_pathsearch(lua_State* L)
{
int i;
const char* filename = luaL_checkstring(L, 1);
for (i = 2; i <= lua_gettop(L); ++i)
{
if (lua_isnil(L, i)) continue;
if (do_pathsearch(L, filename, luaL_checkstring(L, i))) return 1;
}
return 0;
}

View File

@@ -0,0 +1,37 @@
/**
* \file os_realpath.c
* \brief Return the canonical absolute version of a given path.
* \author Copyright (c) 2014 Jason Perkins and the Premake project
*/
#include "premake.h"
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int os_realpath(lua_State* L)
{
char result[PATH_MAX];
int ok;
const char* path = luaL_checkstring(L, 1);
#if PLATFORM_POSIX
ok = (realpath(path, result) != NULL);
#elif PLATFORM_WINDOWS
ok = (_fullpath(result, path, PATH_MAX) != NULL);
#else
do_getabsolute(result, path, NULL);
ok = 1;
#endif
if (!ok) {
lua_pushnil(L);
lua_pushfstring(L, "unable to fetch real path of '%s', errno %d : %s", path, errno, strerror(errno));
return 2;
}
lua_pushstring(L, result);
return 1;
}

View File

@@ -0,0 +1,51 @@
/**
* \file os_remove.c
* \brief Remove a file on Windows.
* \author Copyright (c) 2002-2013 Jason Perkins and the Premake project
*/
#include "premake.h"
#if PLATFORM_WINDOWS
int os_remove(lua_State* L)
{
const char* filename = luaL_checkstring(L, 1);
wchar_t wide_path[PATH_MAX];
if (MultiByteToWideChar(CP_UTF8, 0, filename, -1, wide_path, PATH_MAX) == 0)
{
lua_pushstring(L, "unable to encode path");
return lua_error(L);
}
if (DeleteFileW(wide_path))
{
lua_pushboolean(L, 1);
return 1;
}
else
{
DWORD err = GetLastError();
char unicodeErr[512];
LPWSTR messageBuffer = NULL;
if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR) &messageBuffer, 0, NULL) != 0)
{
if (WideCharToMultiByte(CP_UTF8, 0, messageBuffer, -1, unicodeErr, sizeof(unicodeErr), NULL, NULL) == 0)
strcpy(unicodeErr, "failed to translate error message");
LocalFree(messageBuffer);
}
else
strcpy(unicodeErr, "failed to get error message");
lua_pushnil(L);
lua_pushfstring(L, "%s: %s", filename, unicodeErr);
lua_pushinteger(L, err);
return 3;
}
}
#endif

View File

@@ -0,0 +1,59 @@
/**
* \file os_rename.c
* \brief Rename a path on Windows.
* \author Copyright (c) 2002-2013 Jason Perkins and the Premake project
*/
#include "premake.h"
#if PLATFORM_WINDOWS
int os_rename(lua_State* L)
{
const char *fromname = luaL_checkstring(L, 1);
const char *toname = luaL_checkstring(L, 2);
wchar_t wide_frompath[PATH_MAX];
if (MultiByteToWideChar(CP_UTF8, 0, fromname, -1, wide_frompath, PATH_MAX) == 0)
{
lua_pushstring(L, "unable to encode source path");
return lua_error(L);
}
wchar_t wide_topath[PATH_MAX];
if (MultiByteToWideChar(CP_UTF8, 0, toname, -1, wide_topath, PATH_MAX) == 0)
{
lua_pushstring(L, "unable to encode dest path");
return lua_error(L);
}
if (MoveFileExW(wide_frompath, wide_topath, MOVEFILE_COPY_ALLOWED))
{
lua_pushboolean(L, 1);
return 1;
}
else
{
DWORD err = GetLastError();
char unicodeErr[512];
LPWSTR messageBuffer = NULL;
if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR) &messageBuffer, 0, NULL) != 0)
{
if (WideCharToMultiByte(CP_UTF8, 0, messageBuffer, -1, unicodeErr, sizeof(unicodeErr), NULL, NULL) == 0)
strcpy(unicodeErr, "failed to translate error message");
LocalFree(messageBuffer);
}
else
strcpy(unicodeErr, "failed to get error message");
lua_pushnil(L);
lua_pushfstring(L, "%s: %s", fromname, unicodeErr);
lua_pushinteger(L, err);
return 3;
}
}
#endif

View File

@@ -0,0 +1,40 @@
/**
* \file os_rmdir.c
* \brief Remove a subdirectory.
* \author Copyright (c) 2002-2013 Jason Perkins and the Premake project
*/
#include <stdlib.h>
#include "premake.h"
int os_rmdir(lua_State* L)
{
int z;
const char* path = luaL_checkstring(L, 1);
#if PLATFORM_WINDOWS
wchar_t wide_path[PATH_MAX];
if (MultiByteToWideChar(CP_UTF8, 0, path, -1, wide_path, PATH_MAX) == 0)
{
lua_pushstring(L, "unable to encode path");
return lua_error(L);
}
z = RemoveDirectoryW(wide_path);
#else
z = (0 == rmdir(path));
#endif
if (!z)
{
lua_pushnil(L);
lua_pushfstring(L, "unable to remove directory '%s'", path);
return 2;
}
else
{
lua_pushboolean(L, 1);
return 1;
}
}

View File

@@ -0,0 +1,60 @@
/**
* \file os_stat.c
* \brief Retrieve information about a file.
* \author Copyright (c) 2011 Jason Perkins and the Premake project
*/
#include "premake.h"
#include <sys/stat.h>
#include <errno.h>
int os_stat(lua_State* L)
{
const char* filename = luaL_checkstring(L, 1);
#if PLATFORM_WINDOWS
struct _stat s;
wchar_t wide_filename[PATH_MAX];
if (MultiByteToWideChar(CP_UTF8, 0, filename, -1, wide_filename, PATH_MAX) == 0)
{
lua_pushstring(L, "unable to encode source path");
return lua_error(L);
}
if (_wstat(wide_filename, &s) != 0)
#else
struct stat s;
if (stat(filename, &s) != 0)
#endif
{
lua_pushnil(L);
switch (errno)
{
case EACCES:
lua_pushfstring(L, "'%s' could not be accessed", filename);
break;
case ENOENT:
lua_pushfstring(L, "'%s' was not found", filename);
break;
default:
lua_pushfstring(L, "An unknown error %d occured while accessing '%s'", errno, filename);
break;
}
return 2;
}
lua_newtable(L);
lua_pushstring(L, "mtime");
lua_pushinteger(L, (lua_Integer)s.st_mtime);
lua_settable(L, -3);
lua_pushstring(L, "size");
lua_pushnumber(L, (lua_Number)s.st_size);
lua_settable(L, -3);
return 1;
}

View File

@@ -0,0 +1,146 @@
/**
* \file os_touchfile.c
* \brief markes a file as modified without changing its contents.
* \author Blizzard Entertainment (contact tvandijck@blizzard.com)
* \author Copyright (c) 2015 Jason Perkins and the Premake project
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "premake.h"
#if PLATFORM_WINDOWS
#include <io.h>
#else
#include <unistd.h>
#include <sys/types.h>
#endif
#ifndef FALSE
#define FALSE 0
#endif
#ifndef TRUE
#define TRUE 1
#endif
static int truncate_file(const char* fn)
{
FILE* file = fopen(fn, "rb");
size_t size;
file = fopen(fn, "ab");
if (file == NULL)
{
return FALSE;
}
fseek(file, 0, SEEK_END);
size = ftell(file);
// append a dummy space. There are better ways to do
// a touch, however this is a rather simple
// multiplatform method
if (fwrite(" ", 1, 1, file) != 1)
{
fclose(file);
return FALSE;
}
#if PLATFORM_WINDOWS
if (_chsize(_fileno(file), (long)size) != 0)
{
fclose(file);
return FALSE;
}
#endif
fclose(file);
#if !PLATFORM_WINDOWS
if (truncate(fn, (off_t)size) != 0)
{
return FALSE;
}
#endif
return TRUE;
}
int os_touchfile(lua_State* L)
{
FILE* file;
const char* dst = luaL_checkstring(L, 1);
// if destination exist, mark the file as modified
if (do_isfile(L, dst))
{
#if PLATFORM_WINDOWS
SYSTEMTIME systemTime;
FILETIME fileTime;
HANDLE fileHandle;
wchar_t wide_path[PATH_MAX];
if (MultiByteToWideChar(CP_UTF8, 0, dst, -1, wide_path, PATH_MAX) == 0)
{
lua_pushinteger(L, -1);
lua_pushstring(L, "unable to encode path");
return 2;
}
fileHandle = CreateFileW(wide_path, FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (fileHandle == NULL)
{
lua_pushinteger(L, -1);
lua_pushfstring(L, "unable to touch file '%s'", dst);
return 2;
}
GetSystemTime(&systemTime);
if (SystemTimeToFileTime(&systemTime, &fileTime) == 0)
{
lua_pushinteger(L, -1);
lua_pushfstring(L, "unable to touch file '%s'", dst);
return 2;
}
if (SetFileTime(fileHandle, NULL, NULL, &fileTime) == 0)
{
lua_pushinteger(L, -1);
lua_pushfstring(L, "unable to touch file '%s'", dst);
return 2;
}
lua_pushinteger(L, 0);
return 1;
#else
if (truncate_file(dst))
{
lua_pushinteger(L, 0);
return 1;
} else {
lua_pushinteger(L, -1);
lua_pushfstring(L, "unable to touch file '%s'", dst);
return 2;
}
#endif
}
#if PLATFORM_WINDOWS
wchar_t wide_path[PATH_MAX];
if (MultiByteToWideChar(CP_UTF8, 0, dst, -1, wide_path, PATH_MAX) == 0)
{
lua_pushinteger(L, -1);
lua_pushstring(L, "unable to encode path");
return 2;
}
file = _wfopen(wide_path, L"wb");
#else
file = fopen(dst, "wb");
#endif
if (file != NULL)
{
fclose(file);
lua_pushinteger(L, 1);
return 1;
}
lua_pushinteger(L, -1);
lua_pushfstring(L, "unable to open file to '%s'", dst);
return 2;
}

View File

@@ -0,0 +1,75 @@
/**
* \file os_uuid.c
* \brief Create a new UUID.
* \author Copyright (c) 2002-2012 Jason Perkins and the Premake project
*/
#include "premake.h"
#if PLATFORM_WINDOWS
#include <objbase.h>
#endif
/*
* Pull off the four lowest bytes of a value and add them to my array,
* without the help of the determinately sized C99 data types that
* are not yet universally supported.
*/
static void add(unsigned char* bytes, int offset, uint32_t value)
{
int i;
for (i = 0; i < 4; ++i)
{
bytes[offset++] = (unsigned char)(value & 0xff);
value >>= 8;
}
}
int os_uuid(lua_State* L)
{
char uuid[38];
unsigned char bytes[16];
/* If a name argument is supplied, build the UUID from that. For speed we
* are using a simple DBJ2 hashing function; if this isn't sufficient we
* can switch to a full RFC 4122 §4.3 implementation later. */
const char* name = luaL_optstring(L, 1, NULL);
if (name != NULL)
{
add(bytes, 0, do_hash(name, 0));
add(bytes, 4, do_hash(name, 'L'));
add(bytes, 8, do_hash(name, 'u'));
add(bytes, 12, do_hash(name, 'a'));
}
/* If no name is supplied, try to build one properly */
else
{
#if PLATFORM_WINDOWS
CoCreateGuid((GUID*)bytes);
#else
int result;
/* not sure how to get a UUID here, so I fake it */
FILE* rnd = fopen("/dev/urandom", "rb");
result = fread(bytes, 16, 1, rnd);
fclose(rnd);
if (!result)
{
return 0;
}
#endif
}
sprintf(uuid, "%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X",
bytes[0], bytes[1], bytes[2], bytes[3],
bytes[4], bytes[5],
bytes[6], bytes[7],
bytes[8], bytes[9],
bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15]);
lua_pushstring(L, uuid);
return 1;
}

View File

@@ -0,0 +1,109 @@
/**
* \file os_writefile_ifnotequal.c
* \brief Writes a file only if it differs with its current contents.
* \author Blizzard Entertainment (contact tvandijck@blizzard.com)
* \author Copyright (c) 2015 Jason Perkins and the Premake project
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "premake.h"
static int compare_file(const char* content, size_t length, const char* dst)
{
FILE* file;
size_t size;
size_t read;
char buffer[4096];
size_t num;
#if PLATFORM_WINDOWS
wchar_t wide_path[PATH_MAX];
if (MultiByteToWideChar(CP_UTF8, 0, dst, -1, wide_path, PATH_MAX) == 0)
return FALSE;
file = _wfopen(wide_path, L"rb");
#else
file = fopen(dst, "rb");
#endif
if (file == NULL)
{
return FALSE;
}
// check sizes.
fseek(file, 0, SEEK_END);
size = ftell(file);
fseek(file, 0, SEEK_SET);
if (length != size)
{
fclose(file);
return FALSE;
}
while (size > 0)
{
num = size > 4096 ? 4096 : size;
read = fread(buffer, 1, num, file);
if (read != num)
{
fclose (file);
return FALSE;
}
if (memcmp(content, buffer, num) != 0)
{
fclose(file);
return FALSE;
}
size -= num;
content += num;
}
fclose(file);
return TRUE;
}
int os_writefile_ifnotequal(lua_State* L)
{
FILE* file;
size_t length;
const char* content = luaL_checklstring(L, 1, &length);
const char* dst = luaL_checkstring(L, 2);
// if destination exist, and they are the same, no need to copy.
if (do_isfile(L, dst) && compare_file(content, length, dst))
{
lua_pushinteger(L, 0);
return 1;
}
#if PLATFORM_WINDOWS
wchar_t wide_path[PATH_MAX];
if (MultiByteToWideChar(CP_UTF8, 0, dst, -1, wide_path, PATH_MAX) == 0)
return FALSE;
file = _wfopen(wide_path, L"wb");
#else
file = fopen(dst, "wb");
#endif
if (file != NULL)
{
fwrite(content, 1, length, file);
fclose(file);
lua_pushinteger(L, 1);
return 1;
}
lua_pushinteger(L, -1);
lua_pushfstring(L, "unable to write file to '%s'", dst);
return 2;
}

View File

@@ -0,0 +1,105 @@
/**
* \file path_getabsolute.c
* \brief Returns an absolute version of a relative path.
* \author Copyright (c) 2002-2013 Jason Perkins and the Premake project
*/
#include "premake.h"
#include <string.h>
void do_getabsolute(char* result, const char* value, const char* relative_to)
{
int i;
char* ch;
char* prev;
char buffer[0x4000] = { '\0' };
/* if the path is not already absolute, base it on working dir */
if (!do_isabsolute(value)) {
if (relative_to) {
strcpy(buffer, relative_to);
}
else {
do_getcwd(buffer, 0x4000);
}
strcat(buffer, "/");
}
/* normalize the path */
strcat(buffer, value);
do_translate(buffer, '/');
/* process it part by part */
result[0] = '\0';
if (buffer[0] == '/') {
strcat(result, "/");
if (buffer[1] == '/') {
strcat(result, "/");
}
}
prev = NULL;
ch = strtok(buffer, "/");
while (ch) {
/* remove ".." where I can */
if (strcmp(ch, "..") == 0 && (prev == NULL || (prev[0] != '$' && prev[0] != '%' && strcmp(prev, "..") != 0))) {
i = (int)strlen(result) - 2;
while (i >= 0 && result[i] != '/') {
--i;
}
if (i >= 0) {
result[i + 1] = '\0';
}
ch = NULL;
}
/* allow everything except "." */
else if (strcmp(ch, ".") != 0) {
strcat(result, ch);
strcat(result, "/");
}
prev = ch;
ch = strtok(NULL, "/");
}
/* remove trailing slash */
i = (int)strlen(result) - 1;
if (result[i] == '/') {
result[i] = '\0';
}
}
int path_getabsolute(lua_State* L)
{
const char* relative_to;
char buffer[0x4000];
relative_to = NULL;
if (lua_gettop(L) > 1 && !lua_isnil(L,2)) {
relative_to = luaL_checkstring(L, 2);
}
if (lua_istable(L, 1)) {
int i = 0;
lua_newtable(L);
lua_pushnil(L);
while (lua_next(L, 1)) {
const char* value = luaL_checkstring(L, -1);
do_getabsolute(buffer, value, relative_to);
lua_pop(L, 1);
lua_pushstring(L, buffer);
lua_rawseti(L, -3, ++i);
}
return 1;
}
else {
const char* value = luaL_checkstring(L, 1);
do_getabsolute(buffer, value, relative_to);
lua_pushstring(L, buffer);
return 1;
}
}

View File

@@ -0,0 +1,99 @@
/**
* \file path_getrelative.c
* \brief Returns a path relative to another.
* \author Copyright (c) 2002-2013 Jason Perkins and the Premake project
*/
#include "premake.h"
#include <string.h>
#if PLATFORM_WINDOWS
#include <ctype.h>
#endif
int path_getrelative(lua_State* L)
{
int i, last, count;
char src[0x4000];
char dst[0x4000];
const char* p1 = luaL_checkstring(L, 1);
const char* p2 = luaL_checkstring(L, 2);
/* normalize the paths */
do_normalize(L, src, p1);
do_normalize(L, dst, p2);
/* same directory? */
#if PLATFORM_WINDOWS
if (_stricmp(src, dst) == 0) {
#else
if (strcmp(src, dst) == 0) {
#endif
lua_pushstring(L, ".");
return 1;
}
/* dollar macro? Can't tell what the real path might be, so treat
* as absolute. This enables paths like $(SDK_ROOT)/include to
* work as expected. */
if (dst[0] == '$') {
lua_pushstring(L, dst);
return 1;
}
/* find the common leading directories */
strcat(src, "/");
strcat(dst, "/");
last = -1;
i = 0;
#if PLATFORM_WINDOWS
while (src[i] && dst[i] && tolower(src[i]) == tolower(dst[i])) {
#else
while (src[i] && dst[i] && src[i] == dst[i]) {
#endif
if (src[i] == '/') {
last = i;
}
++i;
}
/* if I end up with just the root of the filesystem, either a single
* slash (/) or a drive letter (c:) then return the absolute path. */
if (last <= 0 || (last == 2 && src[1] == ':')) {
dst[strlen(dst) - 1] = '\0';
lua_pushstring(L, dst);
return 1;
}
/* Relative paths within a server can't climb outside the server root.
* If the paths don't share server name, return the absolute path. */
if (src[0] == '/' && src[1] == '/' && last == 1) {
dst[strlen(dst) - 1] = '\0';
lua_pushstring(L, dst);
return 1;
}
/* count remaining levels in src */
count = 0;
for (i = last + 1; src[i] != '\0'; ++i) {
if (src[i] == '/') {
++count;
}
}
/* start my result by backing out that many levels */
src[0] = '\0';
for (i = 0; i < count; ++i) {
strcat(src, "../");
}
/* append what's left */
strcat(src, dst + last + 1);
/* remove trailing slash and done */
src[strlen(src) - 1] = '\0';
lua_pushstring(L, src);
return 1;
}

View File

@@ -0,0 +1,119 @@
/**
* \file path_isabsolute.c
* \brief Determines if a path is absolute or relative.
* \author Copyright (c) 2002-2016 Jason Perkins and the Premake project
*/
#include "premake.h"
#include <ctype.h>
#include <string.h>
#include "path_isabsolute.h"
#if PLATFORM_WINDOWS
#define strncasecmp _strnicmp
#endif
int do_absolutetype(const char* path)
{
char c;
const char* closing;
size_t length;
while (path[0] == '"' || path[0] == '!')
path++;
if (path[0] == '/' || path[0] == '\\')
return JOIN_ABSOLUTE;
if (isalpha(path[0]) && path[1] == ':')
return JOIN_ABSOLUTE;
// $(foo) and %(foo)
if ((path[0] == '%' || path[0] == '$') && path[1] == '(')
{
char delimiter = path[0];
closing = strchr(path + 2, ')');
if (closing == NULL)
return JOIN_RELATIVE;
path += 2;
// special case VS macros %(filename) and %(extension) as normal text
if (delimiter == '%')
{
length = closing - path;
switch (length) {
case 8:
if (strncasecmp(path, "Filename)", length) == 0)
return JOIN_RELATIVE;
break;
case 9:
if (strncasecmp(path, "Extension)", length) == 0)
return JOIN_RELATIVE;
break;
default:
break;
}
}
// only alpha, digits, _ and . allowed inside $()
while (path < closing) {
c = *path++;
if (!isalpha(c) && !isdigit(c) && c != '_' && c != '.')
return JOIN_RELATIVE;
}
return JOIN_ABSOLUTE;
}
// $ORIGIN.
if (path[0] == '$')
return JOIN_ABSOLUTE;
// either %ORIGIN% or %{<lua code>}
if (path[0] == '%')
{
if (path[1] == '{') //${foo} need to defer join until after detokenization
{
closing = strchr(path + 2, '}');
if (closing != NULL)
return JOIN_MAYBE_ABSOLUTE;
}
// find the second closing %
path += 1;
closing = strchr(path, '%');
if (closing == NULL)
return JOIN_RELATIVE;
// need at least one character between the %%
if (path == closing)
return JOIN_RELATIVE;
// only alpha, digits and _ allowed inside %..%
while (path < closing) {
c = *path++;
if (!isalpha(c) && !isdigit(c) && c != '_')
return JOIN_RELATIVE;
}
return JOIN_ABSOLUTE;
}
return JOIN_RELATIVE;
}
int do_isabsolute(const char* path)
{
// backwards compatibility
return (do_absolutetype(path) == 1) ? 1 : 0;
}
int path_isabsolute(lua_State* L)
{
const char* path = luaL_checkstring(L, -1);
lua_pushboolean(L, do_isabsolute(path));
return 1;
}
int path_absolutetype(lua_State* L)
{
const char* path = luaL_checkstring(L, -1);
lua_pushinteger(L, do_absolutetype(path));
return 1;
}

View File

@@ -0,0 +1,9 @@
/**
* \file path_isabsolute.h
* \brief Determines if a path is absolute or relative.
* \author Copyright (c) 2002-2016 Jason Perkins and the Premake project
*/
#define JOIN_RELATIVE 0
#define JOIN_ABSOLUTE 1
#define JOIN_MAYBE_ABSOLUTE 2

View File

@@ -0,0 +1,191 @@
/**
* \file path_join.c
* \brief Join two or more pieces of a file system path.
* \author Copyright (c) 2002-2013 Jason Perkins and the Premake project
*/
#include "premake.h"
#include <string.h>
#include "path_isabsolute.h"
#define DEFERRED_JOIN_DELIMITER '\a'
char* path_join_single(char* buffer, char* ptr, const char* part, int allowDeferredJoin)
{
int absoluteType;
size_t len = strlen(part);
/* remove leading "./" */
while (strncmp(part, "./", 2) == 0) {
part += 2;
len -= 2;
}
/* remove trailing slashes */
while (len > 1 && part[len - 1] == '/') {
--len;
}
/* ignore empty segments and "." */
if (len == 0 || (len == 1 && part[0] == '.')) {
return ptr;
}
absoluteType = do_absolutetype(part);
if (!allowDeferredJoin && absoluteType == JOIN_MAYBE_ABSOLUTE)
absoluteType = JOIN_RELATIVE;
/* if I encounter an absolute path, restart my result */
switch (absoluteType) {
case JOIN_ABSOLUTE:
ptr = buffer;
break;
case JOIN_RELATIVE:
/* if source has a .. prefix then take off last dest path part
note that this doesn't guarantee a normalized result as this
code doesn't check for .. in the mid path, however .. occurring
mid path are much more likely to occur during path joins
and its faster if we handle here as we don't have to remove
substrings from the middle of the string. */
while (ptr != buffer && len >= 2 && part[0] == '.' && part[1] == '.')
{
/* locate start of previous segment */
char* start = strrchr(buffer, '/');
if (!start) {
start = buffer;
}
else {
++start;
}
/* if I hit a segment I can't trim, bail out */
if (strcmp(start, "..") == 0 /* parent dir */
|| strcmp(start, ".") == 0 /* current dir */
|| strstr(start, "**") != NULL /* recursive wildcard */
|| strchr(start, '$') != NULL) /* property expansion */
{
break;
}
/* otherwise trim segment and the ".." sequence */
if (start != buffer) {
--start;
}
*start = '\0';
ptr = start;
part += 2;
len -= 2;
if (len > 0 && part[0] == '/') {
++part;
--len;
}
}
/* if the path is already started, split parts */
if (ptr != buffer && *(ptr - 1) != '/') {
*(ptr++) = '/';
}
break;
case JOIN_MAYBE_ABSOLUTE:
*ptr = DEFERRED_JOIN_DELIMITER;
ptr++;
break;
}
/* append new part */
strncpy(ptr, part, len);
ptr += len;
*ptr = '\0';
return ptr;
}
int path_join_internal(lua_State* L, int allowDeferredJoin)
{
int i;
const char* part;
char buffer[0x4000];
char* ptr = buffer;
/* for each argument... */
int argc = lua_gettop(L);
for (i = 1; i <= argc; ++i) {
/* if next argument is nil, skip it */
if (lua_isnil(L, i)) {
continue;
}
/* grab the next argument */
part = luaL_checkstring(L, i);
ptr = path_join_single(buffer, ptr, part, allowDeferredJoin);
}
lua_pushstring(L, buffer);
return 1;
}
int path_join(lua_State* L)
{
return path_join_internal(L, 0);
}
int path_deferred_join(lua_State* L)
{
return path_join_internal(L, 1);
}
int do_path_has_deferred_join(const char* path)
{
return (strchr(path, DEFERRED_JOIN_DELIMITER) != NULL);
}
int path_has_deferred_join(lua_State* L)
{
const char* path = luaL_checkstring(L, -1);
lua_pushboolean(L, do_path_has_deferred_join(path));
return 1;
}
int path_resolve_deferred_join(lua_State* L)
{
const char* path = luaL_checkstring(L, -1);
char inBuffer[0x4000];
char outBuffer[0x4000];
char* ptr = outBuffer;
char* nextPart;
size_t len = strlen(path);
int i;
int numParts = 0;
strncpy(inBuffer, path, len);
inBuffer[len] = '\0';
char *parts[0x200];
// break up the string into parts and index the start of each part
nextPart = strchr(inBuffer, DEFERRED_JOIN_DELIMITER);
if (nextPart == NULL) // nothing to do
{
lua_pushlstring(L, inBuffer, len);
return 1;
}
parts[numParts++] = inBuffer;
while (nextPart != NULL)
{
*nextPart = '\0';
nextPart++;
parts[numParts++] = nextPart;
nextPart = strchr(nextPart, DEFERRED_JOIN_DELIMITER);
}
/* for each part... */
for (i = 0; i < numParts; ++i) {
nextPart = parts[i];
ptr = path_join_single(outBuffer, ptr, nextPart, 0);
}
lua_pushstring(L, outBuffer);
return 1;
}

View File

@@ -0,0 +1,223 @@
/**
* \file path_normalize.c
* \brief Removes any weirdness from a file system path string.
* \author Copyright (c) 2013 Jason Perkins and the Premake project
*/
#include "premake.h"
#include <ctype.h>
#include <string.h>
#define IS_SEP(__c) ((__c) == '/' || (__c) == '\\')
#define IS_QUOTE(__c) ((__c) == '\"' || (__c) == '\'')
#define IS_UPPER_ALPHA(__c) ((__c) >= 'A' && (__c) <= 'Z')
#define IS_LOWER_ALPHA(__c) ((__c) >= 'a' && (__c) <= 'z')
#define IS_ALPHA(__c) (IS_UPPER_ALPHA(__c) || IS_LOWER_ALPHA(__c))
#define IS_SPACE(__c) ((__c >= '\t' && __c <= '\r') || __c == ' ')
#define IS_WIN_ENVVAR_START(__c) (*__c == '%')
#define IS_WIN_ENVVAR_END(__c) (*__c == '%')
#define IS_VS_VAR_START(__c) (*__c == '$' && __c[1] == '(')
#define IS_VS_VAR_END(__c) (*__c == ')')
#define IS_UNIX_ENVVAR_START(__c) (*__c == '$' && __c[1] == '{')
#define IS_UNIX_ENVVAR_END(__c) (*__c == '}')
#define IS_PREMAKE_TOKEN_START(__c) (*__c == '%' && __c[1] == '{')
#define IS_PREMAKE_TOKEN_END(__c) (*__c == '}')
static void* normalize_substring(const char* srcPtr, const char* srcEnd, char* dstPtr)
{
#define IS_END(__p) (__p >= srcEnd || *__p == '\0')
#define IS_SEP_OR_END(__p) (IS_END(__p) || IS_SEP(*__p))
// Handle Windows absolute paths
if (IS_ALPHA(srcPtr[0]) && srcPtr[1] == ':')
{
*(dstPtr++) = srcPtr[0];
*(dstPtr++) = ':';
srcPtr += 2;
}
// Handle path starting with a sep (C:/ or /)
if (IS_SEP(*srcPtr))
{
++srcPtr;
*(dstPtr++) = '/';
// Handle path starting with //
if (IS_SEP(*srcPtr))
{
++srcPtr;
*(dstPtr++) = '/';
}
}
const char * const dstRoot = dstPtr;
unsigned int folderDepth = 0;
while (!IS_END(srcPtr))
{
// Skip multiple sep and "./" pattern
while (IS_SEP(*srcPtr) || (srcPtr[0] == '.' && IS_SEP_OR_END(&srcPtr[1])))
++srcPtr;
if (IS_END(srcPtr))
break;
// Handle "../ pattern"
if (srcPtr[0] == '.' && srcPtr[1] == '.' && IS_SEP_OR_END(&srcPtr[2]))
{
if (folderDepth > 0)
{
// Here dstPtr[-1] is safe as folderDepth > 0.
while (--dstPtr != dstRoot && !IS_SEP(dstPtr[-1]));
--folderDepth;
}
else
{
*(dstPtr++) = '.';
*(dstPtr++) = '.';
*(dstPtr++) = '/';
}
srcPtr += 3;
}
else
{
while (!IS_SEP_OR_END(srcPtr))
*(dstPtr++) = *(srcPtr++);
if (IS_SEP(*srcPtr))
{
*(dstPtr++) = '/';
++srcPtr;
++folderDepth;
}
}
}
// Remove trailing slash except for C:/ or / (root)
while (dstPtr != dstRoot && IS_SEP(dstPtr[-1]))
--dstPtr;
return dstPtr;
#undef IS_END
#undef IS_SEP_OR_END
}
static int skip_tokens(const char *readPtr)
{
int skipped = 0;
#define DO_SKIP_FOR(__kind)\
if (IS_ ## __kind ## _START(readPtr)) { \
do \
{ \
skipped++; \
} while (!IS_ ## __kind ## _END(readPtr++)); \
} \
// DO_SKIP_FOR
do
{
DO_SKIP_FOR(PREMAKE_TOKEN)
DO_SKIP_FOR(WIN_ENVVAR)
DO_SKIP_FOR(VS_VAR)
DO_SKIP_FOR(UNIX_ENVVAR)
} while (IS_WIN_ENVVAR_START(readPtr) ||
IS_VS_VAR_START(readPtr) ||
IS_UNIX_ENVVAR_START(readPtr) ||
IS_PREMAKE_TOKEN_START(readPtr));
return skipped;
#undef DO_SKIP_FOR
}
int path_normalize(lua_State* L)
{
const char *path = luaL_checkstring(L, 1);
const char *readPtr = path;
char buffer[0x4000] = { 0 };
char *writePtr = buffer;
const char *endPtr;
// skip leading white spaces
while (IS_SPACE(*readPtr))
++readPtr;
endPtr = readPtr;
while (*endPtr) {
int skipped = skip_tokens(readPtr);
if (skipped > 0) {
if (readPtr != path && writePtr != buffer &&
IS_SEP(readPtr[-1]) && !IS_SEP(writePtr[-1]))
{
*(writePtr++) = (readPtr[-1]);
}
while (skipped-- > 0)
*(writePtr++) = *(readPtr++);
endPtr = readPtr;
}
// find the end of sub path
while (*endPtr && !IS_SPACE(*endPtr) &&
!IS_WIN_ENVVAR_START(endPtr) &&
!IS_VS_VAR_START(endPtr) &&
!IS_UNIX_ENVVAR_START(endPtr) &&
!IS_PREMAKE_TOKEN_START(endPtr))
{
++endPtr;
}
// path is surrounded with quotes
if (readPtr != endPtr &&
IS_QUOTE(*readPtr))
{
*(writePtr++) = *(readPtr++);
}
writePtr = normalize_substring(readPtr, endPtr, writePtr);
// skip any white spaces between sub paths
while (IS_SPACE(*endPtr))
*(writePtr++) = *(endPtr++);
readPtr = endPtr;
}
// skip any trailing white spaces
while (writePtr != buffer && IS_SPACE(writePtr[-1]))
--writePtr;
*writePtr = 0;
lua_pushstring(L, buffer);
return 1;
}
/* Call the scripted path.normalize(), to allow for overrides */
void do_normalize(lua_State* L, char* buffer, const char* path)
{
int top = lua_gettop(L);
lua_getglobal(L, "path");
lua_getfield(L, -1, "normalize");
lua_pushstring(L, path);
lua_call(L, 1, 1);
path = luaL_checkstring(L, -1);
strcpy(buffer, path);
lua_settop(L, top);
}

View File

@@ -0,0 +1,65 @@
/**
* \file path_translate.c
* \brief Translates between path separators.
* \author Copyright (c) 2002-2013 Jason Perkins and the Premake project
*/
#include "premake.h"
#include <string.h>
void do_translate(char* value, const char sep)
{
char* ch;
for (ch = value; *ch != '\0'; ++ch) {
if (*ch == '/' || *ch == '\\') {
*ch = sep;
}
}
}
static void translate(char* result, const char* value, const char sep)
{
strcpy(result, value);
do_translate(result, sep);
}
int path_translate(lua_State* L)
{
const char* sep;
char buffer[0x4000];
if (lua_gettop(L) == 1) {
lua_getglobal(L, "path");
lua_getfield(L, -1, "getDefaultSeparator");
lua_call(L, 0, 1);
sep = luaL_checkstring(L, -1);
lua_pop(L, 2);
}
else {
sep = luaL_checkstring(L, 2);
}
if (lua_istable(L, 1)) {
int i = 0;
lua_newtable(L);
lua_pushnil(L);
while (lua_next(L, 1)) {
const char* value = luaL_checkstring(L, 4);
translate(buffer, value, sep[0]);
lua_pop(L, 1);
lua_pushstring(L, buffer);
lua_rawseti(L, -3, ++i);
}
return 1;
}
else {
const char* value = luaL_checkstring(L, 1);
translate(buffer, value, sep[0]);
lua_pushstring(L, buffer);
return 1;
}
}

View File

@@ -0,0 +1,80 @@
/**
* \file path_wildcards.c
* \brief Converts from a simple wildcard syntax to the corresponding Lua pattern.
* \author Copyright (c) 2015 Tom van Dijck, Jason Perkins and the Premake project
*/
#include "premake.h"
#include <string.h>
#include <stdlib.h>
/*
--Converts from a simple wildcard syntax, where * is "match any"
-- and ** is "match recursive", to the corresponding Lua pattern.
--
-- @param pattern
-- The wildcard pattern to convert.
-- @returns
-- The corresponding Lua pattern.
*/
int path_wildcards(lua_State* L)
{
size_t length, i;
const char* input;
char buffer[0x4000];
char* output;
input = luaL_checklstring(L, 1, &length);
output = buffer;
for (i = 0; i < length; ++i)
{
char c = input[i];
switch (c)
{
case '+':
case '.':
case '-':
case '^':
case '$':
case '(':
case ')':
case '%':
*(output++) = '%';
*(output++) = c;
break;
case '*':
if ((i + 1) < length && input[i + 1] == '*')
{
i++; // skip the next character.
*(output++) = '.';
*(output++) = '*';
}
else
{
*(output++) = '[';
*(output++) = '^';
*(output++) = '/';
*(output++) = ']';
*(output++) = '*';
}
break;
default:
*(output++) = c;
break;
}
if (output >= buffer + sizeof(buffer))
{
lua_pushstring(L, "Wildcards expansion too big.");
lua_error(L);
}
}
*(output++) = '\0';
lua_pushstring(L, buffer);
return 1;
}

View File

@@ -0,0 +1,705 @@
/**
* \file premake.c
* \brief Program entry point.
* \author Copyright (c) 2002-2017 Jason Perkins and the Premake project
*/
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "premake.h"
#include "lua_shimtable.h"
#if PLATFORM_MACOSX
#include <CoreFoundation/CFBundle.h>
#endif
#if PLATFORM_BSD
#include <sys/types.h>
#include <sys/sysctl.h>
#endif
#define ERROR_MESSAGE "Error: %s\n"
static void build_premake_path(lua_State* L);
static int process_arguments(lua_State* L, int argc, const char** argv);
static int run_premake_main(lua_State* L, const char* script);
/* A search path for script files */
const char* scripts_path = NULL;
/* Built-in functions */
static const luaL_Reg criteria_functions[] = {
{ "_compile", criteria_compile },
{ "_delete", criteria_delete },
{ "matches", criteria_matches },
{ NULL, NULL }
};
static const luaL_Reg debug_functions[] = {
{ "prompt", debug_prompt },
{ NULL, NULL }
};
static const luaL_Reg path_functions[] = {
{ "getabsolute", path_getabsolute },
{ "getrelative", path_getrelative },
{ "isabsolute", path_isabsolute },
{ "join", path_join },
{ "deferredjoin", path_deferred_join },
{ "hasdeferredjoin", path_has_deferred_join },
{ "resolvedeferredjoin", path_resolve_deferred_join },
{ "normalize", path_normalize },
{ "translate", path_translate },
{ "wildcards", path_wildcards },
{ NULL, NULL }
};
static const luaL_Reg os_functions[] = {
{ "chdir", os_chdir },
{ "chmod", os_chmod },
{ "comparefiles", os_comparefiles },
{ "copyfile", os_copyfile },
{ "_is64bit", os_is64bit },
{ "isdir", os_isdir },
{ "getcwd", os_getcwd },
{ "getpass", os_getpass },
{ "getWindowsRegistry", os_getWindowsRegistry },
{ "listWindowsRegistry", os_listWindowsRegistry },
{ "getversion", os_getversion },
{ "host", os_host },
{ "isfile", os_isfile },
{ "islink", os_islink },
{ "locate", os_locate },
{ "matchdone", os_matchdone },
{ "matchisfile", os_matchisfile },
{ "matchname", os_matchname },
{ "matchnext", os_matchnext },
{ "matchstart", os_matchstart },
{ "mkdir", os_mkdir },
#if PLATFORM_WINDOWS
// utf8 functions for Windows (assuming posix already handle utf8)
{"remove", os_remove },
{"rename", os_rename },
#endif
{ "pathsearch", os_pathsearch },
{ "realpath", os_realpath },
{ "rmdir", os_rmdir },
{ "stat", os_stat },
{ "uuid", os_uuid },
{ "writefile_ifnotequal", os_writefile_ifnotequal },
{ "touchfile", os_touchfile },
{ "compile", os_compile },
{ NULL, NULL }
};
static const luaL_Reg premake_functions[] = {
{ "getEmbeddedResource", premake_getEmbeddedResource },
{ NULL, NULL }
};
static const luaL_Reg string_functions[] = {
{ "endswith", string_endswith },
{ "hash", string_hash },
{ "sha1", string_sha1 },
{ "startswith", string_startswith },
{ NULL, NULL }
};
static const luaL_Reg buffered_functions[] = {
{ "new", buffered_new },
{ "write", buffered_write },
{ "writeln", buffered_writeln },
{ "tostring", buffered_tostring },
{ "close", buffered_close },
{ NULL, NULL }
};
static const luaL_Reg term_functions[] = {
{ "getTextColor", term_getTextColor },
{ "setTextColor", term_setTextColor },
{ NULL, NULL }
};
#ifdef PREMAKE_CURL
static const luaL_Reg http_functions[] = {
{ "get", http_get },
{ "post", http_post },
{ "download", http_download },
{ NULL, NULL }
};
#endif
#ifdef PREMAKE_COMPRESSION
static const luaL_Reg zip_functions[] = {
{ "extract", zip_extract },
{ NULL, NULL }
};
#endif
static void lua_getorcreate_table(lua_State *L, const char *modname)
{
luaL_getsubtable(L, LUA_REGISTRYINDEX, "_LOADED"); // get _LOADED table stack = {_LOADED}
lua_getfield(L, -1, modname); // get _LOADED[modname] stack = { _LOADED, result }
if (!lua_istable(L, -1)) // not found? stack = { _LOADED, result }
{
lua_pop(L, 1); // remove previous result stack = { _LOADED }
lua_pushglobaltable(L); // push _G onto stack stack = { _LOADED, _G }
lua_createtable(L, 0, 0); // new table for field stack = { _LOADED, _G, result }
lua_pushlstring(L, modname, strlen(modname)); // stack = { _LOADED, _G, result, modname }
lua_pushvalue(L, -2); // stack = { _LOADED, _G, result, modname, result }
lua_settable(L, -4); // _G[modname] = result stack = { _LOADED, _G, result }
lua_remove(L, -2); // remove _G from stack stack = { _LOADED, result }
lua_pushvalue(L, -1); // duplicate result stack = { _LOADED, result, result }
lua_setfield(L, -3, modname); // _LOADED[modname] = result stack = { _LOADED, result }
}
lua_remove(L, -2); // remove _LOADED from stack stack = { result }
}
void luaL_register(lua_State *L, const char *libname, const luaL_Reg *l)
{
lua_getorcreate_table(L, libname);
luaL_setfuncs(L, l, 0);
lua_pop(L, 1);
}
/**
* Initialize the Premake Lua environment.
*/
int premake_init(lua_State* L)
{
const char* value;
luaL_register(L, "premake", premake_functions);
luaL_register(L, "criteria", criteria_functions);
luaL_register(L, "debug", debug_functions);
luaL_register(L, "path", path_functions);
luaL_register(L, "os", os_functions);
luaL_register(L, "string", string_functions);
luaL_register(L, "buffered", buffered_functions);
luaL_register(L, "term", term_functions);
#ifdef PREMAKE_CURL
luaL_register(L, "http", http_functions);
#endif
#ifdef PREMAKE_COMPRESSION
luaL_register(L, "zip", zip_functions);
#endif
lua_pushlightuserdata(L, &s_shimTable);
lua_rawseti(L, LUA_REGISTRYINDEX, 0x5348494D); // equal to 'SHIM'
/* push the application metadata */
lua_pushstring(L, LUA_COPYRIGHT);
lua_setglobal(L, "_COPYRIGHT");
lua_pushstring(L, PREMAKE_VERSION);
lua_setglobal(L, "_PREMAKE_VERSION");
lua_pushstring(L, PREMAKE_COPYRIGHT);
lua_setglobal(L, "_PREMAKE_COPYRIGHT");
lua_pushstring(L, PREMAKE_PROJECT_URL);
lua_setglobal(L, "_PREMAKE_URL");
/* set the OS platform variable */
lua_pushstring(L, PLATFORM_STRING);
lua_setglobal(L, "_TARGET_OS");
/* find the user's home directory */
value = getenv("HOME");
if (!value) value = getenv("USERPROFILE");
if (!value) value = "~";
lua_pushstring(L, value);
lua_setglobal(L, "_USER_HOME_DIR");
/* publish the initial working directory */
os_getcwd(L);
lua_setglobal(L, "_WORKING_DIR");
#if !defined(PREMAKE_NO_BUILTIN_SCRIPTS)
/* let native modules initialize themselves */
registerModules(L);
#endif
return OKAY;
}
static void setErrorColor(lua_State* L)
{
int errorColor = 12;
lua_getglobal(L, "term");
lua_pushstring(L, "errorColor");
lua_gettable(L, -2);
if (!lua_isnil(L, -1))
errorColor = (int)luaL_checkinteger(L, -1);
term_doSetTextColor(errorColor);
lua_pop(L, 2);
}
void printLastError(lua_State* L)
{
const char* message = lua_tostring(L, -1);
int oldColor = term_doGetTextColor();
setErrorColor(L);
printf(ERROR_MESSAGE, message);
term_doSetTextColor(oldColor);
}
static int lua_error_handler(lua_State* L)
{
// in debug mode, show full traceback on all errors
#if !defined(NDEBUG)
lua_getglobal(L, "debug");
lua_getfield(L, -1, "traceback");
lua_remove(L, -2); // remove debug table
lua_insert(L, -2); // insert traceback function before error message
lua_pushinteger(L, 3); // push level
lua_call(L, 2, 1); // call traceback
#else
(void) L;
#endif
return 1;
}
int premake_pcall(lua_State* L, int nargs, int nresults)
{
lua_pushcfunction(L, lua_error_handler);
int error_handler_index = lua_gettop(L) - nargs - 1;
lua_insert(L, error_handler_index); // insert lua_error_handler before call parameters
int result = lua_pcall(L, nargs, nresults, error_handler_index);
lua_remove(L, error_handler_index); // remove lua_error_handler from stack
return result;
}
int premake_execute(lua_State* L, int argc, const char** argv, const char* script)
{
/* push the absolute path to the Premake executable */
lua_pushcfunction(L, path_getabsolute);
premake_locate_executable(L, argv[0]);
lua_call(L, 1, 1);
lua_setglobal(L, "_PREMAKE_COMMAND");
/* Parse the command line arguments */
if (process_arguments(L, argc, argv) != OKAY) {
return !OKAY;
}
/* Use --scripts and PREMAKE_PATH to populate premake.path */
build_premake_path(L);
/* Find and run the main Premake bootstrapping script */
if (run_premake_main(L, script) != OKAY) {
printLastError(L);
return !OKAY;
}
/* and call the main entry point */
lua_getglobal(L, "_premake_main");
if (premake_pcall(L, 0, 1) != OKAY) {
printLastError(L);
return !OKAY;
}
else {
int exitCode = (int)lua_tonumber(L, -1);
return exitCode;
}
}
/**
* Locate the Premake executable, and push its full path to the Lua stack.
* Based on:
* http://sourceforge.net/tracker/index.php?func=detail&aid=3351583&group_id=71616&atid=531880
* http://stackoverflow.com/questions/933850/how-to-find-the-location-of-the-executable-in-c
* http://stackoverflow.com/questions/1023306/finding-current-executables-path-without-proc-self-exe
*/
int premake_locate_executable(lua_State* L, const char* argv0)
{
char buffer[PATH_MAX];
const char* path = NULL;
#if PLATFORM_WINDOWS
wchar_t widebuffer[PATH_MAX];
DWORD len = GetModuleFileNameW(NULL, widebuffer, PATH_MAX);
if (len > 0)
{
WideCharToMultiByte(CP_UTF8, 0, widebuffer, len, buffer, PATH_MAX, NULL, NULL);
buffer[len] = 0;
path = buffer;
}
#endif
#if PLATFORM_MACOSX
CFURLRef bundleURL = CFBundleCopyExecutableURL(CFBundleGetMainBundle());
CFStringRef pathRef = CFURLCopyFileSystemPath(bundleURL, kCFURLPOSIXPathStyle);
if (CFStringGetCString(pathRef, buffer, PATH_MAX - 1, kCFStringEncodingUTF8))
path = buffer;
#endif
#if PLATFORM_LINUX
int len = readlink("/proc/self/exe", buffer, PATH_MAX - 1);
if (len > 0)
{
buffer[len] = 0;
path = buffer;
}
#endif
#if PLATFORM_BSD && !defined(__OpenBSD__)
int len = readlink("/proc/curproc/file", buffer, PATH_MAX - 1);
if (len < 0)
len = readlink("/proc/curproc/exe", buffer, PATH_MAX - 1);
if (len < 0)
{
int mib[4];
mib[0] = CTL_KERN;
mib[1] = KERN_PROC;
mib[2] = KERN_PROC_PATHNAME;
mib[3] = -1;
size_t cb = sizeof(buffer);
sysctl(mib, 4, buffer, &cb, NULL, 0);
len = (int)cb;
}
if (len > 0)
{
buffer[len] = 0;
path = buffer;
}
#endif
#if PLATFORM_SOLARIS
int len = readlink("/proc/self/path/a.out", buffer, PATH_MAX - 1);
if (len > 0)
{
buffer[len] = 0;
path = buffer;
}
#endif
/* As a fallback, search the PATH with argv[0] */
if (!path)
{
lua_pushcfunction(L, os_pathsearch);
lua_pushstring(L, argv0);
lua_pushstring(L, getenv("PATH"));
if (lua_pcall(L, 2, 1, 0) == OKAY && !lua_isnil(L, -1))
{
lua_pushstring(L, "/");
lua_pushstring(L, argv0);
lua_concat(L, 3);
path = lua_tostring(L, -1);
}
lua_pop(L, 1);
}
/* If all else fails, use argv[0] as-is and hope for the best */
if (!path)
{
/* make it absolute, if needed */
os_getcwd(L);
lua_pushstring(L, "/");
lua_pushstring(L, argv0);
if (!do_isabsolute(argv0)) {
lua_concat(L, 3);
}
else {
lua_pop(L, 3);
lua_pushstring(L, argv0);
}
path = lua_tostring(L, -1);
lua_pop(L, 1);
}
lua_pushstring(L, path);
return 1;
}
/**
* Checks one or more of the standard script search locations to locate the
* specified file. If found, returns the discovered path to the script on
* the top of the Lua stack.
*/
int premake_test_file(lua_State* L, const char* filename, int searchMask)
{
if (searchMask & TEST_LOCAL) {
if (do_isfile(L, filename)) {
lua_pushcfunction(L, path_getabsolute);
lua_pushstring(L, filename);
lua_call(L, 1, 1);
return OKAY;
}
}
if (scripts_path && (searchMask & TEST_SCRIPTS)) {
if (do_locate(L, filename, scripts_path)) return OKAY;
}
if (searchMask & TEST_PATH) {
const char* path = getenv("PREMAKE_PATH");
if (path && do_locate(L, filename, path)) return OKAY;
}
#if !defined(PREMAKE_NO_BUILTIN_SCRIPTS)
if ((searchMask & TEST_EMBEDDED) != 0) {
/* Try to locate a record matching the filename */
if (premake_find_embedded_script(filename) != NULL) {
lua_pushstring(L, "$/");
lua_pushstring(L, filename);
lua_concat(L, 2);
return OKAY;
}
}
#endif
return !OKAY;
}
static const char* set_scripts_path(const char* relativePath)
{
char* path = (char*)malloc(PATH_MAX);
do_getabsolute(path, relativePath, NULL);
scripts_path = path;
return scripts_path;
}
/**
* Set the premake.path variable, pulling from the --scripts argument
* and PREMAKE_PATH environment variable if present.
*/
static void build_premake_path(lua_State* L)
{
int top;
const char* value;
lua_getglobal(L, "premake");
top = lua_gettop(L);
/* Start by searching the current working directory */
lua_pushstring(L, ".");
/* The --scripts argument goes next, if present */
if (scripts_path) {
lua_pushstring(L, ";");
lua_pushstring(L, scripts_path);
}
/* Then the PREMAKE_PATH environment variable */
value = getenv("PREMAKE_PATH");
if (value) {
lua_pushstring(L, ";");
lua_pushstring(L, value);
}
/* Then in ~/.premake */
lua_pushstring(L, ";");
lua_getglobal(L, "_USER_HOME_DIR");
lua_pushstring(L, "/.premake");
/* In the user's Application Support folder */
#if defined(PLATFORM_MACOSX)
lua_pushstring(L, ";");
lua_getglobal(L, "_USER_HOME_DIR");
lua_pushstring(L, "/Library/Application Support/Premake");
#endif
/* In the /usr tree */
lua_pushstring(L, ";/usr/local/share/premake;/usr/share/premake");
/* Put it all together */
lua_concat(L, lua_gettop(L) - top);
/* Match Lua's package.path; use semicolon separators */
#if !defined(PLATFORM_WINDOWS)
lua_getglobal(L, "string");
lua_getfield(L, -1, "gsub");
lua_pushvalue(L, -3);
lua_pushstring(L, ":");
lua_pushstring(L, ";");
lua_call(L, 3, 1);
/* remove the string global table */
lua_remove(L, -2);
/* remove the previously concatonated result */
lua_remove(L, -2);
#endif
/* Store it in premake.path */
lua_setfield(L, -2, "path");
/* Remove the premake namespace table */
lua_pop(L, 1);
}
/**
* Copy all command line arguments into the script-side _ARGV global, and
* check for the presence of a /scripts=<path> argument to help locate
* the manifest if needed.
* \returns OKAY if successful.
*/
static int process_arguments(lua_State* L, int argc, const char** argv)
{
int i;
/* Copy all arguments in the _ARGV global */
lua_newtable(L);
for (i = 1; i < argc; ++i)
{
lua_pushstring(L, argv[i]);
lua_rawseti(L, -2, luaL_len(L, -2) + 1);
/* The /scripts option gets picked up here; used later to find the
* manifest and scripts later if necessary */
if (strncmp(argv[i], "/scripts=", 9) == 0)
{
argv[i] = set_scripts_path(argv[i] + 9);
}
else if (strncmp(argv[i], "--scripts=", 10) == 0)
{
argv[i] = set_scripts_path(argv[i] + 10);
}
}
lua_setglobal(L, "_ARGV");
return OKAY;
}
/**
* Find and run the main Premake bootstrapping script. The loading of the
* bootstrap and the other core scripts use a limited set of search paths
* to avoid mismatches between the native host code and the scripts
* themselves.
*/
static int run_premake_main(lua_State* L, const char* script)
{
/* Release builds want to load the embedded scripts, with --scripts
* argument allowed as an override. Debug builds will look at the
* local file system first, then fall back to embedded. */
#if defined(NDEBUG)
int z = premake_test_file(L, script,
TEST_SCRIPTS | TEST_EMBEDDED);
#else
int z = premake_test_file(L, script,
TEST_LOCAL | TEST_SCRIPTS | TEST_PATH | TEST_EMBEDDED);
#endif
/* If no embedded script can be found, release builds will then
* try to fall back to the local file system, just in case */
#if defined(NDEBUG)
if (z != OKAY) {
z = premake_test_file(L, script, TEST_LOCAL | TEST_PATH);
}
#endif
if (z == OKAY) {
const char* filename = lua_tostring(L, -1);
z = luaL_dofile(L, filename);
}
return z;
}
/**
* Locate a file in the embedded script index. If found, returns the
* contents of the file's script.
*/
const buildin_mapping* premake_find_embedded_script(const char* filename)
{
#if !defined(PREMAKE_NO_BUILTIN_SCRIPTS)
int i;
for (i = 0; builtin_scripts[i].name != NULL; ++i) {
if (strcmp(builtin_scripts[i].name, filename) == 0) {
return builtin_scripts + i;
}
}
#endif
return NULL;
}
/**
* Load a script that was previously embedded into the executable. If
* successful, a function containing the new script chunk is pushed to
* the stack, just like luaL_loadfile would do had the chunk been loaded
* from a file.
*/
int premake_load_embedded_script(lua_State* L, const char* filename)
{
#if !defined(NDEBUG)
static int warned = 0;
#endif
const buildin_mapping* chunk = premake_find_embedded_script(filename);
if (chunk == NULL) {
return LUA_ERRFILE;
}
/* Debug builds probably want to be loading scripts from the disk */
#if !defined(NDEBUG)
if (!warned) {
warned = 1;
printf("** warning: using embedded script '%s'; use /scripts argument to load from files\n", filename);
}
#endif
/* "Fully qualify" the filename by turning it into the form $/filename */
lua_pushstring(L, "$/");
lua_pushstring(L, filename);
lua_concat(L, 2);
/* Load the chunk */
return luaL_loadbuffer(L, (const char*)chunk->bytecode, chunk->length, filename);
}
/**
* Give the lua runtime raw access to embedded files.
*/
int premake_getEmbeddedResource(lua_State* L)
{
const char* filename = luaL_checkstring(L, 1);
const buildin_mapping* chunk = premake_find_embedded_script(filename);
if (chunk == NULL)
{
return 0;
}
lua_pushlstring(L, (const char*)chunk->bytecode, chunk->length);
return 1;
}

View File

@@ -0,0 +1,201 @@
/**
* \file premake.h
* \brief Program-wide constants and definitions.
* \author Copyright (c) 2002-2021 Jason Perkins and the Premake project
*/
#define lua_c
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
#include <stdlib.h>
#define PREMAKE_VERSION "5.0.0-beta1"
#define PREMAKE_COPYRIGHT "Copyright (C) 2002-2021 Jason Perkins and the Premake Project"
#define PREMAKE_PROJECT_URL "https://github.com/premake/premake-core/wiki"
/* Identify the current platform I'm not sure how to reliably detect
* Windows but since it is the most common I use it as the default */
#if defined(__linux__)
#define PLATFORM_LINUX (1)
#define PLATFORM_STRING "linux"
#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
#define PLATFORM_BSD (1)
#define PLATFORM_STRING "bsd"
#elif defined(__APPLE__) && defined(__MACH__)
#define PLATFORM_MACOSX (1)
#define PLATFORM_STRING "macosx"
#elif defined(__sun__) && defined(__svr4__)
#define PLATFORM_SOLARIS (1)
#define PLATFORM_STRING "solaris"
#elif defined(__HAIKU__)
#define PLATFORM_HAIKU (1)
#define PLATFORM_STRING "haiku"
#elif defined (_AIX)
#define PLATFORM_AIX (1)
#define PLATFORM_STRING "aix"
#elif defined (__GNU__)
#define PLATFORM_HURD (1)
#define PLATFORM_STRING "hurd"
#else
#define PLATFORM_WINDOWS (1)
#define PLATFORM_STRING "windows"
#endif
#define PLATFORM_POSIX (PLATFORM_LINUX || PLATFORM_BSD || PLATFORM_MACOSX || PLATFORM_SOLARIS || PLATFORM_HAIKU)
/* Pull in platform-specific headers required by built-in functions */
#if PLATFORM_WINDOWS
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#else
#include <unistd.h>
#endif
#include <stdint.h>
/* not all platforms define this */
#ifndef FALSE
#define FALSE 0
#endif
#ifndef TRUE
#define TRUE 1
#endif
/* Fill in any missing bits */
#ifndef PATH_MAX
#define PATH_MAX (4096)
#endif
/* A success return code */
#define OKAY (0)
/* Bitmasks for the different script file search locations */
#define TEST_LOCAL (0x01)
#define TEST_SCRIPTS (0x02)
#define TEST_PATH (0x04)
#define TEST_EMBEDDED (0x08)
/* If a /scripts argument is present, its value */
extern const char* scripts_path;
/* Bootstrapping helper functions */
int do_chdir(lua_State* L, const char* path);
uint32_t do_hash(const char* str, int seed);
void do_getabsolute(char* result, const char* value, const char* relative_to);
int do_getcwd(char* buffer, size_t size);
int do_isabsolute(const char* path);
int do_absolutetype(const char* path);
int do_isfile(lua_State* L, const char* filename);
int do_locate(lua_State* L, const char* filename, const char* path);
void do_normalize(lua_State* L, char* buffer, const char* path);
int do_pathsearch(lua_State* L, const char* filename, const char* path);
void do_translate(char* value, const char sep);
int term_doGetTextColor();
void term_doSetTextColor(int color);
void printLastError(lua_State* L);
/* Built-in functions */
int criteria_compile(lua_State* L);
int criteria_delete(lua_State* L);
int criteria_matches(lua_State* L);
int debug_prompt(lua_State* L);
int path_getabsolute(lua_State* L);
int path_getrelative(lua_State* L);
int path_isabsolute(lua_State* L);
int path_join(lua_State* L);
int path_deferred_join(lua_State* L);
int path_has_deferred_join(lua_State* L);
int path_resolve_deferred_join(lua_State* L);
int path_normalize(lua_State* L);
int path_translate(lua_State* L);
int path_wildcards(lua_State* L);
int os_chdir(lua_State* L);
int os_chmod(lua_State* L);
int os_comparefiles(lua_State* L);
int os_copyfile(lua_State* L);
int os_getcwd(lua_State* L);
int os_getpass(lua_State* L);
int os_getWindowsRegistry(lua_State* L);
int os_listWindowsRegistry(lua_State* L);
int os_getversion(lua_State* L);
int os_host(lua_State* L);
int os_is64bit(lua_State* L);
int os_isdir(lua_State* L);
int os_isfile(lua_State* L);
int os_islink(lua_State* L);
int os_locate(lua_State* L);
int os_matchdone(lua_State* L);
int os_matchisfile(lua_State* L);
int os_matchname(lua_State* L);
int os_matchnext(lua_State* L);
int os_matchstart(lua_State* L);
int os_mkdir(lua_State* L);
int os_pathsearch(lua_State* L);
int os_realpath(lua_State* L);
#if PLATFORM_WINDOWS
// utf8 versions
int os_remove(lua_State* L);
int os_rename(lua_State* L);
#endif
int os_rmdir(lua_State* L);
int os_stat(lua_State* L);
int os_uuid(lua_State* L);
int os_writefile_ifnotequal(lua_State* L);
int os_touchfile(lua_State* L);
int os_compile(lua_State* L);
int premake_getEmbeddedResource(lua_State* L);
int string_endswith(lua_State* L);
int string_hash(lua_State* L);
int string_sha1(lua_State* L);
int string_startswith(lua_State* L);
int buffered_new(lua_State* L);
int buffered_write(lua_State* L);
int buffered_writeln(lua_State* L);
int buffered_close(lua_State* L);
int buffered_tostring(lua_State* L);
int term_getTextColor(lua_State* L);
int term_setTextColor(lua_State* L);
#ifdef PREMAKE_CURL
int http_get(lua_State* L);
int http_post(lua_State* L);
int http_download(lua_State* L);
#endif
#ifdef PREMAKE_COMPRESSION
int zip_extract(lua_State* L);
#endif
#ifdef _MSC_VER
#ifndef snprintf
#define snprintf _snprintf
#endif
#endif
/* Engine interface */
typedef struct
{
const char* name;
const unsigned char* bytecode;
size_t length;
} buildin_mapping;
extern const buildin_mapping builtin_scripts[];
extern void registerModules(lua_State* L);
int premake_init(lua_State* L);
int premake_pcall(lua_State* L, int nargs, int nresults);
int premake_execute(lua_State* L, int argc, const char** argv, const char* script);
int premake_load_embedded_script(lua_State* L, const char* filename);
const buildin_mapping* premake_find_embedded_script(const char* filename);
int premake_locate_executable(lua_State* L, const char* argv0);
int premake_test_file(lua_State* L, const char* filename, int searchMask);

View File

@@ -0,0 +1,24 @@
/**
* \file premake_main.c
* \brief Program entry point.
* \author Copyright (c) 2002-2013 Jason Perkins and the Premake project
*/
#include "premake.h"
int main(int argc, const char** argv)
{
lua_State* L;
int z;
L = luaL_newstate();
luaL_openlibs(L);
z = premake_init(L);
if (z == OKAY) {
z = premake_execute(L, argc, argv, "src/_premake_main.lua");
}
lua_close(L);
return z;
}

View File

@@ -0,0 +1,16 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by resource.rc
//
#define IDI_ICON1 101
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 102
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1001
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif

View File

@@ -0,0 +1,107 @@
// Microsoft Visual C++ generated resource script.
//
#include "resource.h"
#include "premake.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// English (U.S.) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENA)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE
BEGIN
"#include ""winres.h""\r\n"
"\0"
END
3 TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Version
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 5,0,0
PRODUCTVERSION 5,0,0
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
#ifdef _DEBUG
FILEFLAGS VS_FF_DEBUG
#else
FILEFLAGS 0x0L
#endif
FILEOS VOS_NT_WINDOWS32
FILETYPE VFT_APP
FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904E4"
BEGIN
VALUE "CompanyName", "Jason Perkins and the Premake Project"
VALUE "FileDescription", "Premake5"
VALUE "FileVersion", PREMAKE_VERSION
VALUE "LegalCopyright", PREMAKE_COPYRIGHT
VALUE "OriginalFilename", "premake5.exe"
VALUE "ProductName", "Premake5"
VALUE "ProductVersion", PREMAKE_VERSION
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x0409, 1200
END
END
/////////////////////////////////////////////////////////////////////////////
//
// Icon
//
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
//IDI_ICON1 ICON ""
#endif // English (U.S.) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED

View File

@@ -0,0 +1,28 @@
/**
* \file string_endswith.c
* \brief Determines if a string ends with the given sequence.
* \author Copyright (c) 2002-2009 Jason Perkins and the Premake project
*/
#include "premake.h"
#include <string.h>
int string_endswith(lua_State* L)
{
const char* haystack = luaL_optstring(L, 1, NULL);
const char* needle = luaL_optstring(L, 2, NULL);
if (haystack && needle)
{
size_t hlen = strlen(haystack);
size_t nlen = strlen(needle);
if (hlen >= nlen)
{
lua_pushboolean(L, strcmp(haystack + hlen - nlen, needle) == 0);
return 1;
}
}
return 0;
}

View File

@@ -0,0 +1,36 @@
/**
* \file string_hash.c
* \brief Computes a hash value for a string.
* \author Copyright (c) 2012-2014 Jason Perkins and the Premake project
*/
#include "premake.h"
#include <string.h>
int string_hash(lua_State* L)
{
const char* str = luaL_checkstring(L, 1);
int seed = (int)luaL_optinteger(L, 2, 0);
lua_pushinteger(L, do_hash(str, seed));
return 1;
}
uint32_t do_hash(const char* str, int seed)
{
/* DJB2 hashing; see http://www.cse.yorku.ca/~oz/hash.html */
uint32_t hash = 5381;
if (seed != 0) {
hash = hash * 33 + seed;
}
while (*str) {
hash = hash * 33 + (*str);
str++;
}
return hash;
}

View File

@@ -0,0 +1,185 @@
#include <stdint.h>
#include <string.h>
#include "premake.h"
#define HASH_LENGTH 20
#define BLOCK_LENGTH 64
typedef struct sha1nfo {
uint32_t buffer[BLOCK_LENGTH / 4];
uint32_t state[HASH_LENGTH / 4];
uint32_t byteCount;
uint8_t bufferOffset;
uint8_t keyBuffer[BLOCK_LENGTH];
uint8_t innerHash[HASH_LENGTH];
} sha1nfo;
/* public API - prototypes - TODO: doxygen*/
void sha1_init(sha1nfo *s);
void sha1_writebyte(sha1nfo *s, uint8_t data);
void sha1_write(sha1nfo *s, const char *data, size_t len);
uint8_t* sha1_result(sha1nfo *s);
/* code */
#define SHA1_K0 0x5a827999
#define SHA1_K20 0x6ed9eba1
#define SHA1_K40 0x8f1bbcdc
#define SHA1_K60 0xca62c1d6
void sha1_init(sha1nfo *s)
{
s->state[0] = 0x67452301;
s->state[1] = 0xefcdab89;
s->state[2] = 0x98badcfe;
s->state[3] = 0x10325476;
s->state[4] = 0xc3d2e1f0;
s->byteCount = 0;
s->bufferOffset = 0;
}
uint32_t sha1_rol32(uint32_t number, uint8_t bits)
{
return ((number << bits) | (number >> (32 - bits)));
}
void sha1_hashBlock(sha1nfo *s)
{
uint8_t i;
uint32_t a, b, c, d, e, t;
a = s->state[0];
b = s->state[1];
c = s->state[2];
d = s->state[3];
e = s->state[4];
for (i = 0; i < 80; i++)
{
if (i >= 16)
{
t = s->buffer[(i + 13) & 15] ^ s->buffer[(i + 8) & 15] ^ s->buffer[(i + 2) & 15] ^ s->buffer[i & 15];
s->buffer[i & 15] = sha1_rol32(t, 1);
}
if (i < 20)
{
t = (d ^ (b & (c ^ d))) + SHA1_K0;
}
else if (i < 40)
{
t = (b ^ c ^ d) + SHA1_K20;
}
else if (i < 60)
{
t = ((b & c) | (d & (b | c))) + SHA1_K40;
}
else
{
t = (b ^ c ^ d) + SHA1_K60;
}
t += sha1_rol32(a, 5) + e + s->buffer[i & 15];
e = d;
d = c;
c = sha1_rol32(b, 30);
b = a;
a = t;
}
s->state[0] += a;
s->state[1] += b;
s->state[2] += c;
s->state[3] += d;
s->state[4] += e;
}
void sha1_addUncounted(sha1nfo *s, uint8_t data)
{
uint8_t * const b = (uint8_t*)s->buffer;
b[s->bufferOffset ^ 3] = data;
s->bufferOffset++;
if (s->bufferOffset == BLOCK_LENGTH)
{
sha1_hashBlock(s);
s->bufferOffset = 0;
}
}
void sha1_writebyte(sha1nfo *s, uint8_t data)
{
++s->byteCount;
sha1_addUncounted(s, data);
}
void sha1_write(sha1nfo *s, const char *data, size_t len)
{
for (; len--;) sha1_writebyte(s, (uint8_t)*data++);
}
void sha1_pad(sha1nfo *s)
{
// Implement SHA-1 padding (fips180-2 §5.1.1)
// Pad with 0x80 followed by 0x00 until the end of the block
sha1_addUncounted(s, 0x80);
while (s->bufferOffset != 56) sha1_addUncounted(s, 0x00);
// Append length in the last 8 bytes
sha1_addUncounted(s, 0); // We're only using 32 bit lengths
sha1_addUncounted(s, 0); // But SHA-1 supports 64 bit lengths
sha1_addUncounted(s, 0); // So zero pad the top bits
sha1_addUncounted(s, s->byteCount >> 29); // Shifting to multiply by 8
sha1_addUncounted(s, (uint8_t)(s->byteCount >> 21)); // as SHA-1 supports bitstreams as well as
sha1_addUncounted(s, (uint8_t)(s->byteCount >> 13)); // byte.
sha1_addUncounted(s, (uint8_t)(s->byteCount >> 5));
sha1_addUncounted(s, (uint8_t)(s->byteCount << 3));
}
uint8_t* sha1_result(sha1nfo *s)
{
int i;
// Pad to complete the last block
sha1_pad(s);
// Swap byte order back
for (i = 0; i < 5; i++)
{
s->state[i] =
(((s->state[i]) << 24) & 0xff000000)
| (((s->state[i]) << 8) & 0x00ff0000)
| (((s->state[i]) >> 8) & 0x0000ff00)
| (((s->state[i]) >> 24) & 0x000000ff);
}
// Return pointer to hash (20 characters)
return (uint8_t*)s->state;
}
int string_sha1(lua_State* L)
{
static const char* g_int2hex = "0123456789ABCDEF";
uint8_t* result;
size_t l;
sha1nfo s;
int i;
char output[41];
const char *str = luaL_checklstring(L, 1, &l);
if (str != NULL)
{
sha1_init(&s);
sha1_write(&s, str, l);
result = sha1_result(&s);
for (i = 0; i < 20; ++i)
{
output[i * 2 + 0] = g_int2hex[result[i] & 0x0f];
output[i * 2 + 1] = g_int2hex[result[i] >> 4];
}
output[40] = 0;
lua_pushlstring(L, output, 40);
return 1;
}
return 0;
}

View File

@@ -0,0 +1,24 @@
/**
* \file string_startswith.c
* \brief Determines if a string starts with the given sequence.
* \author Copyright (c) 2013 Jason Perkins and the Premake project
*/
#include "premake.h"
#include <string.h>
int string_startswith(lua_State* L)
{
const char* haystack = luaL_optstring(L, 1, NULL);
const char* needle = luaL_optstring(L, 2, NULL);
if (haystack && needle)
{
size_t nlen = strlen(needle);
lua_pushboolean(L, strncmp(haystack, needle, nlen) == 0);
return 1;
}
return 0;
}

View File

@@ -0,0 +1,114 @@
/**
* \file term_textColor.c
* \brief Change the foreground color of the terminal output.
* \author Copyright (c) 2017 Blizzard Entertainment and the Premake project
*/
#include "premake.h"
#if PLATFORM_WINDOWS
#include <io.h>
#endif
#if PLATFORM_POSIX
static int s_currentColor = -1;
#endif
int canUseColors()
{
#if PLATFORM_WINDOWS
return _isatty(_fileno(stdout));
#else
return isatty(1);
#endif
}
static const char *getenvOrFallback(const char *var, const char *fallback)
{
const char *value = getenv(var);
return (value != NULL) ? value : fallback;
}
static int s_shouldUseColor = -1;
static int shouldUseColors()
{
if (s_shouldUseColor < 0)
{
// CLICOLOR* documented at: http://bixense.com/clicolors/
s_shouldUseColor = ((getenvOrFallback("CLICOLOR", "1")[0] != '0') && canUseColors())
|| getenvOrFallback("CLICOLOR_FORCE", "0")[0] != '0';
}
return s_shouldUseColor;
}
int term_doGetTextColor()
{
#if PLATFORM_WINDOWS
CONSOLE_SCREEN_BUFFER_INFO info;
if (!GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info))
return -1;
return (int)info.wAttributes;
#else
return s_currentColor;
#endif
}
void term_doSetTextColor(int color)
{
#if PLATFORM_WINDOWS
if (color >= 0 && shouldUseColors())
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), (WORD)color);
SetConsoleTextAttribute(GetStdHandle(STD_ERROR_HANDLE), (WORD)color);
}
#else
// Do not output colors e.g. into a pipe, unless forced.
if (!shouldUseColors())
return;
s_currentColor = color;
const char* colorTable[] =
{
"\x1B[0;30m", // term.black = 0
"\x1B[0;34m", // term.blue = 1
"\x1B[0;32m", // term.green = 2
"\x1B[0;36m", // term.cyan = 3
"\x1B[0;31m", // term.red = 4
"\x1B[0;35m", // term.purple = 5
"\x1B[0;33m", // term.brown = 6
"\x1B[0;37m", // term.lightGray = 7
"\x1B[1;30m", // term.gray = 8
"\x1B[1;34m", // term.lightBlue = 9
"\x1B[1;32m", // term.lightGreen = 10
"\x1B[1;36m", // term.lightCyan = 11
"\x1B[1;31m", // term.lightRed = 12
"\x1B[1;35m", // term.magenta = 13
"\x1B[1;33m", // term.yellow = 14
"\x1B[1;37m", // term.white = 15
};
if (color >= 0 && color < 16)
{
fputs(colorTable[color], stdout);
} else
{
fputs("\x1B[0m", stdout);
}
#endif
}
int term_getTextColor(lua_State* L)
{
int color = term_doGetTextColor();
lua_pushinteger(L, color);
return 1;
}
int term_setTextColor(lua_State* L)
{
term_doSetTextColor((int)luaL_optinteger(L, 1, -1));
return 0;
}

View File

@@ -0,0 +1,234 @@
/**
* \file os_unzip.c
* \brief Unzip file using libzip library.
* \author battle.net -- abrunasso.int@blizzard.com
*/
#include "premake.h"
#ifdef PREMAKE_COMPRESSION
#include "zip.h"
#ifdef WIN32
#include <direct.h>
#include <io.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <string.h>
// File Attribute for Unix
#define FA_IFIFO 0010000 /* named pipe (fifo) */
#define FA_IFCHR 0020000 /* character special */
#define FA_IFDIR 0040000 /* directory */
#define FA_IFBLK 0060000 /* block special */
#define FA_IFREG 0100000 /* regular */
#define FA_IFLNK 0120000 /* symbolic link */
#define FA_IFSOCK 0140000 /* socket */
#define FA_ISUID 0004000 /* set user id on execution */
#define FA_ISGID 0002000 /* set group id on execution */
#define FA_ISTXT 0001000 /* sticky bit */
#define FA_IRWXU 0000700 /* RWX mask for owner */
#define FA_IRUSR 0000400 /* R for owner */
#define FA_IWUSR 0000200 /* W for owner */
#define FA_IXUSR 0000100 /* X for owner */
#define FA_IRWXG 0000070 /* RWX mask for group */
#define FA_IRGRP 0000040 /* R for group */
#define FA_IWGRP 0000020 /* W for group */
#define FA_IXGRP 0000010 /* X for group */
#define FA_IRWXO 0000007 /* RWX mask for other */
#define FA_IROTH 0000004 /* R for other */
#define FA_IWOTH 0000002 /* W for other */
#define FA_IXOTH 0000001 /* X for other */
#define FA_ISVTX 0001000 /* save swapped text even after use */
// ----------------------------------------------------------------------------
static int is_symlink(zip_uint8_t opsys, zip_uint32_t attrib)
{
if (opsys == ZIP_OPSYS_DOS)
return (attrib & 0x400) == 0x400; // FILE_ATTRIBUTE_REPARSE_POINT
if (opsys == ZIP_OPSYS_UNIX)
return ((attrib >> 16) & FA_IFLNK) == FA_IFLNK;
return 0;
}
static int is_directory(zip_uint8_t opsys, zip_uint32_t attrib)
{
if (opsys == ZIP_OPSYS_DOS)
return (attrib & 0x10) == 0x10; // FILE_ATTRIBUTE_DIRECTORY
if (opsys == ZIP_OPSYS_UNIX)
return ((attrib >> 16) & FA_IFDIR) == FA_IFDIR;
return 0;
}
static int write_link(const char* filename, const char* bytes, size_t count)
{
#if PLATFORM_POSIX
(void)(count);
return symlink(bytes, filename);
#else
FILE* fp = fopen(filename, "wb");
if (fp == NULL)
{
printf("Error creating file:\n %s\n", filename);
return -1;
}
fwrite(bytes, sizeof(char), count, fp);
fclose(fp);
return 0;
#endif
}
extern int do_mkdir(const char* path);
static void parse_path(const char* full_name, char* filename, char* directory)
{
const char *ssc;
size_t orig_length = strlen(full_name);
size_t l = 0;
size_t dir_size;
ssc = strstr(full_name, "/");
do
{
l = strlen(ssc) + 1;
full_name = &full_name[strlen(full_name) - l + 2];
ssc = strstr(full_name, "/");
} while (ssc);
// full_name currently pointing to beginning of filename
memcpy(filename, full_name, strlen(full_name));
filename[strlen(full_name)] = 0; // Null terminate it
dir_size = orig_length - strlen(filename);
// full_name points to beginning of original string(with directory)
full_name = &full_name[strlen(full_name) - orig_length];
// Extract directory from full name by removing filename
memcpy(directory, full_name, dir_size);
directory[dir_size] = 0;
}
static int extract(const char* src, const char* destination)
{
int err = 0;
FILE *fp = NULL;
struct zip *z_archive = zip_open(src, 0, &err);
int i;
zip_int64_t entries;
char buffer[4096];
char appended_full_name[512];
char directory[512];
char filename[512];
size_t result;
zip_int64_t bytes_read;
if (!z_archive)
{
printf("%s does not exist\n", src);
return -1;
}
for (i = 0, entries = zip_get_num_entries(z_archive, 0); i < entries; ++i)
{
zip_uint8_t opsys;
zip_uint32_t attrib;
const char* full_name;
struct zip_file* zf = zip_fopen_index(z_archive, i, 0);
if (!zf)
continue;
zip_file_get_external_attributes(z_archive, i, 0, &opsys, &attrib);
full_name = zip_get_name(z_archive, i, 0);
sprintf(appended_full_name, "%s/%s", destination, full_name);
do_translate(appended_full_name, '/');
parse_path(appended_full_name, filename, directory);
do_mkdir(directory);
// is this a symbolic link?
if (is_symlink(opsys, attrib))
{
bytes_read = zip_fread(zf, buffer, sizeof(buffer));
buffer[bytes_read] = '\0';
if (write_link(appended_full_name, buffer, (size_t)bytes_read) != 0)
{
printf(" Failed to create symbolic link [%s->%s]\n", appended_full_name, buffer);
return -1;
}
} else
{
// If blank filename it's just a directory so create it and move on
if (!is_directory(opsys, attrib) && strlen(filename) > 0)
{
// mark as read-write, so we can overwrite the file if it already exists.
chmod(appended_full_name, 0666);
fp = fopen(appended_full_name, "wb");
if (fp == NULL)
{
printf("Error creating file:\n %s\n", appended_full_name);
return -1;
}
for(;;)
{
// Read content from zipped file
bytes_read = zip_fread(zf, buffer, sizeof(buffer));
if (bytes_read == 0) break;
// Write that content to file
result = fwrite(buffer, sizeof(char), (size_t)bytes_read, fp);
// If all bytes read weren't written, report error
if (result != (size_t)bytes_read)
{
printf(" Writing data to %s failed\n %d bytes were written\n %d bytes were attempted to be written\n File may be corrupt\n",
appended_full_name, (int)result, (int)bytes_read);
return -1;
}
}
fclose(fp);
// mark read-only, but maintain the other properties.
if (opsys == ZIP_OPSYS_UNIX)
chmod(appended_full_name, (attrib >> 16) & ~0222);
else
chmod(appended_full_name, 0444);
}
}
// Cleanup
zip_fclose(zf);
}
zip_close(z_archive);
return 0;
}
int zip_extract(lua_State* L)
{
const char* src = luaL_checkstring(L, 1);
const char* dst = luaL_checkstring(L, 2);
int res = extract(src, dst);
lua_pushnumber(L, (lua_Number)res);
return 1;
}
#endif

View File

@@ -0,0 +1,336 @@
--
-- clang.lua
-- Clang toolset adapter for Premake
-- Copyright (c) 2013 Jason Perkins and the Premake project
--
local p = premake
p.tools.clang = {}
local clang = p.tools.clang
local gcc = p.tools.gcc
local config = p.config
--
-- Build a list of flags for the C preprocessor corresponding to the
-- settings in a particular project configuration.
--
-- @param cfg
-- The project configuration.
-- @return
-- An array of C preprocessor flags.
--
function clang.getcppflags(cfg)
-- Just pass through to GCC for now
local flags = gcc.getcppflags(cfg)
return flags
end
--
-- Build a list of C compiler flags corresponding to the settings in
-- a particular project configuration. These flags are exclusive
-- of the C++ compiler flags, there is no overlap.
--
-- @param cfg
-- The project configuration.
-- @return
-- An array of C compiler flags.
--
clang.shared = {
architecture = gcc.shared.architecture,
flags = gcc.shared.flags,
floatingpoint = {
Fast = "-ffast-math",
},
strictaliasing = gcc.shared.strictaliasing,
optimize = {
Off = "-O0",
On = "-O2",
Debug = "-O0",
Full = "-O3",
Size = "-Os",
Speed = "-O3",
},
pic = gcc.shared.pic,
vectorextensions = gcc.shared.vectorextensions,
isaextensions = gcc.shared.isaextensions,
warnings = gcc.shared.warnings,
symbols = gcc.shared.symbols,
unsignedchar = gcc.shared.unsignedchar,
omitframepointer = gcc.shared.omitframepointer,
compileas = gcc.shared.compileas
}
clang.cflags = table.merge(gcc.cflags, {
})
function clang.getcflags(cfg)
local shared = config.mapFlags(cfg, clang.shared)
local cflags = config.mapFlags(cfg, clang.cflags)
local flags = table.join(shared, cflags)
flags = table.join(flags, clang.getwarnings(cfg), clang.getsystemversionflags(cfg))
return flags
end
function clang.getwarnings(cfg)
return gcc.getwarnings(cfg)
end
--
-- Returns C/C++ system version related build flags
--
function clang.getsystemversionflags(cfg)
local flags = {}
if cfg.system == p.MACOSX or cfg.system == p.IOS then
local minVersion = p.project.systemversion(cfg)
if minVersion ~= nil then
local name = iif(cfg.system == p.MACOSX, "macosx", "iphoneos")
table.insert (flags, "-m" .. name .. "-version-min=" .. p.project.systemversion(cfg))
end
end
return flags
end
--
-- Build a list of C++ compiler flags corresponding to the settings
-- in a particular project configuration. These flags are exclusive
-- of the C compiler flags, there is no overlap.
--
-- @param cfg
-- The project configuration.
-- @return
-- An array of C++ compiler flags.
--
clang.cxxflags = table.merge(gcc.cxxflags, {
})
function clang.getcxxflags(cfg)
local shared = config.mapFlags(cfg, clang.shared)
local cxxflags = config.mapFlags(cfg, clang.cxxflags)
local flags = table.join(shared, cxxflags)
flags = table.join(flags, clang.getwarnings(cfg), clang.getsystemversionflags(cfg))
return flags
end
--
-- Returns a list of defined preprocessor symbols, decorated for
-- the compiler command line.
--
-- @param defines
-- An array of preprocessor symbols to define; as an array of
-- string values.
-- @return
-- An array of symbols with the appropriate flag decorations.
--
function clang.getdefines(defines)
-- Just pass through to GCC for now
local flags = gcc.getdefines(defines)
return flags
end
function clang.getundefines(undefines)
-- Just pass through to GCC for now
local flags = gcc.getundefines(undefines)
return flags
end
--
-- Returns a list of forced include files, decorated for the compiler
-- command line.
--
-- @param cfg
-- The project configuration.
-- @return
-- An array of force include files with the appropriate flags.
--
function clang.getforceincludes(cfg)
-- Just pass through to GCC for now
local flags = gcc.getforceincludes(cfg)
return flags
end
--
-- Returns a list of include file search directories, decorated for
-- the compiler command line.
--
-- @param cfg
-- The project configuration.
-- @param dirs
-- An array of include file search directories; as an array of
-- string values.
-- @return
-- An array of symbols with the appropriate flag decorations.
--
function clang.getincludedirs(cfg, dirs, sysdirs, frameworkdirs)
-- Just pass through to GCC for now
local flags = gcc.getincludedirs(cfg, dirs, sysdirs, frameworkdirs)
return flags
end
clang.getrunpathdirs = gcc.getrunpathdirs
--
-- get the right output flag.
--
function clang.getsharedlibarg(cfg)
return gcc.getsharedlibarg(cfg)
end
--
-- Build a list of linker flags corresponding to the settings in
-- a particular project configuration.
--
-- @param cfg
-- The project configuration.
-- @return
-- An array of linker flags.
--
clang.ldflags = {
architecture = {
x86 = "-m32",
x86_64 = "-m64",
},
flags = {
LinkTimeOptimization = "-flto",
},
kind = {
SharedLib = function(cfg)
local r = { clang.getsharedlibarg(cfg) }
if cfg.system == "windows" and not cfg.flags.NoImportLib then
table.insert(r, '-Wl,--out-implib="' .. cfg.linktarget.relpath .. '"')
elseif cfg.system == p.LINUX then
table.insert(r, '-Wl,-soname=' .. p.quoted(cfg.linktarget.name))
elseif table.contains(os.getSystemTags(cfg.system), "darwin") then
table.insert(r, '-Wl,-install_name,' .. p.quoted('@rpath/' .. cfg.linktarget.name))
end
return r
end,
WindowedApp = function(cfg)
if cfg.system == p.WINDOWS then return "-mwindows" end
end,
},
system = {
wii = "$(MACHDEP)",
}
}
function clang.getldflags(cfg)
local flags = config.mapFlags(cfg, clang.ldflags)
return flags
end
--
-- Build a list of additional library directories for a particular
-- project configuration, decorated for the tool command line.
--
-- @param cfg
-- The project configuration.
-- @return
-- An array of decorated additional library directories.
--
function clang.getLibraryDirectories(cfg)
-- Just pass through to GCC for now
local flags = gcc.getLibraryDirectories(cfg)
return flags
end
--
-- Build a list of libraries to be linked for a particular project
-- configuration, decorated for the linker command line.
--
-- @param cfg
-- The project configuration.
-- @param systemOnly
-- Boolean flag indicating whether to link only system libraries,
-- or system libraries and sibling projects as well.
-- @return
-- A list of libraries to link, decorated for the linker.
--
function clang.getlinks(cfg, systemonly, nogroups)
return gcc.getlinks(cfg, systemonly, nogroups)
end
--
-- Return a list of makefile-specific configuration rules. This will
-- be going away when I get a chance to overhaul these adapters.
--
-- @param cfg
-- The project configuration.
-- @return
-- A list of additional makefile rules.
--
function clang.getmakesettings(cfg)
-- Just pass through to GCC for now
local flags = gcc.getmakesettings(cfg)
return flags
end
--
-- Retrieves the executable command name for a tool, based on the
-- provided configuration and the operating environment. I will
-- be moving these into global configuration blocks when I get
-- the chance.
--
-- @param cfg
-- The configuration to query.
-- @param tool
-- The tool to fetch, one of "cc" for the C compiler, "cxx" for
-- the C++ compiler, or "ar" for the static linker.
-- @return
-- The executable command name for a tool, or nil if the system's
-- default value should be used.
--
clang.tools = {
cc = "clang",
cxx = "clang++",
ar = function(cfg) return iif(cfg.flags.LinkTimeOptimization, "llvm-ar", "ar") end
}
function clang.gettoolname(cfg, tool)
local value = clang.tools[tool]
if type(value) == "function" then
value = value(cfg)
end
return value
end

View File

@@ -0,0 +1,329 @@
--
-- dotnet.lua
-- Interface for the C# compilers, all of which are flag compatible.
-- Copyright (c) 2002-2013 Jason Perkins and the Premake project
--
local p = premake
p.tools.dotnet = {}
local dotnet = p.tools.dotnet
local project = p.project
local config = p.config
--
-- Examine the file and project configurations to glean additional
-- information about a source code in a C# project.
--
-- @param fcfg
-- The file configuration to consider.
-- @return
-- A table containing the following keys:
--
-- action: the build action for the file; one of "Compile", "Copy",
-- "EmbeddedResource", or "None".
-- subtype: an additional categorization of the file type, or nil if
-- no subtype is required.
-- dependency: a related file name, (i.e. *.Designer.cs) if appropriate
-- for the file action and subtype.
--
function dotnet.fileinfo(fcfg)
local info = {}
if (fcfg == nil) then
return info
end
local fname = fcfg.abspath
local ext = path.getextension(fname):lower()
-- Determine the build action for the file, falling back to the file
-- extension if no explicit action is available.
if fcfg.buildaction == "Compile" or ext == ".cs" or ext == ".fs" then
info.action = "Compile"
elseif fcfg.buildaction == "Embed" or ext == ".resx" then
info.action = "EmbeddedResource"
elseif fcfg.buildaction == "Copy" or ext == ".asax" or ext == ".aspx" or ext == ".dll" or ext == ".tt" then
info.action = "Content"
elseif fcfg.buildaction == "Resource" then
info.action = "Resource"
elseif ext == ".xaml" then
if fcfg.buildaction == "Application" or path.getbasename(fname) == "App" then
if fcfg.project.kind == p.SHAREDLIB then
info.action = "None"
else
info.action = "ApplicationDefinition"
end
else
info.action = "Page"
end
else
info.action = "None"
end
-- Try to work out any subtypes, based on the files in the project
if info.action == "Compile" and fname:endswith(".cs") then
if fname:endswith(".Designer.cs") then
local basename = fname:sub(1, -13)
-- Look for associated files: .resx, .settings, .cs, .xsd
local testname = basename .. ".resx"
if project.hasfile(fcfg.project, testname) then
info.AutoGen = "True"
info.DependentUpon = testname
end
testname = basename .. ".settings"
if project.hasfile(fcfg.project, testname) then
info.AutoGen = "True"
info.DependentUpon = testname
info.DesignTimeSharedInput = "True"
end
testname = basename .. ".cs"
if project.hasfile(fcfg.project, testname) then
info.AutoGen = nil
info.SubType = "Dependency"
info.DependentUpon = testname
end
testname = basename .. ".xsd"
if project.hasfile(fcfg.project, testname) then
info.AutoGen = "True"
info.DesignTime = "True"
info.DependentUpon = testname
end
elseif fname:endswith(".xaml.cs") then
info.SubType = "Code"
info.DependentUpon = fname:sub(1, -4)
else
local basename = fname:sub(1, -4)
-- Is there a matching *.xsd?
testname = basename .. ".xsd"
if project.hasfile(fcfg.project, testname) then
info.DependentUpon = testname
end
-- Is there a matching *.Designer.cs?
testname = basename .. ".Designer.cs"
if project.hasfile(fcfg.project, testname) then
info.SubType = "Form"
end
testname = basename .. ".tt"
if project.hasfile(fcfg.project, testname) then
info.AutoGen = "True"
info.DesignTime = "True"
info.DependentUpon = testname
end
end
-- Allow C# object type build actions to override the default
if fcfg.buildaction == "Component" or
fcfg.buildaction == "Form" or
fcfg.buildaction == "UserControl"
then
info.SubType = fcfg.buildaction
end
-- This flag is deprecated, will remove eventually
if fcfg.flags and fcfg.flags.Component then
info.SubType = "Component"
end
end
if info.action == "Content" then
info.CopyToOutputDirectory = "PreserveNewest"
end
if info.action == "EmbeddedResource" and fname:endswith(".resx") then
local basename = fname:sub(1, -6)
-- Is there a matching *.cs file?
local testname = basename .. ".cs"
if project.hasfile(fcfg.project, testname) then
info.DependentUpon = testname
if project.hasfile(fcfg.project, basename .. ".Designer.cs") then
info.SubType = "DesignerType"
end
else
-- Is there a matching *.Designer.cs?
testname = basename .. ".Designer.cs"
if project.hasfile(fcfg.project, testname) then
info.SubType = "Designer"
local resourceAccessGenerator = "ResXFileCodeGenerator"
if fcfg.project.resourcegenerator then
if fcfg.project.resourcegenerator == "public" then
resourceAccessGenerator = "PublicResXFileCodeGenerator"
end
end
info.Generator = resourceAccessGenerator
info.LastGenOutput = path.getname(testname)
end
end
end
if info.action == "None" and fname:endswith(".settings") then
local testname = fname:sub(1, -10) .. ".Designer.cs"
if project.hasfile(fcfg.project, testname) then
info.Generator = "SettingsSingleFileGenerator"
info.LastGenOutput = path.getname(testname)
end
end
if info.action == "Content" and fname:endswith(".tt") then
local testname = fname:sub(1, -4) .. ".cs"
if project.hasfile(fcfg.project, testname) then
info.Generator = "TextTemplatingFileGenerator"
info.LastGenOutput = path.getname(testname)
info.CopyToOutputDirectory = nil
end
end
if info.action == "None" and fname:endswith(".xsd") then
local testname = fname:sub(1, -5) .. ".Designer.cs"
if project.hasfile(fcfg.project, testname) then
info.SubType = "Designer"
info.Generator = "MSDataSetGenerator"
info.LastGenOutput = path.getname(testname)
end
end
if info.action == "None" and (fname:endswith(".xsc") or fname:endswith(".xss")) then
local testname = fname:sub(1, -5) .. ".xsd"
if project.hasfile(fcfg.project, testname) then
info.DependentUpon = testname
end
end
if fname:endswith(".xaml") then
local testname = fname .. ".cs"
if project.hasfile(fcfg.project, testname) then
info.SubType = "Designer"
info.Generator = "MSBuild:Compile"
end
end
if info.DependentUpon then
info.DependentUpon = path.getname(info.DependentUpon)
end
return info
end
--
-- Retrieves the executable command name for a tool, based on the
-- provided configuration and the operating environment.
--
-- @param cfg
-- The configuration to query.
-- @param tool
-- The tool to fetch, one of "csc" for the C# compiler, or
-- "resgen" for the resource compiler.
-- @return
-- The executable command name for a tool, or nil if the system's
-- default value should be used.
--
function dotnet.gettoolname(cfg, tool)
local compilers = {
msnet = "csc",
mono = "mcs",
pnet = "cscc",
}
if tool == "csc" then
local toolset = _OPTIONS.dotnet or "msnet"
return compilers[toolset]
else
return "resgen"
end
end
--
-- Returns a list of compiler flags, based on the supplied configuration.
--
dotnet.flags = {
clr = {
Unsafe = "/unsafe",
},
flags = {
FatalWarning = "/warnaserror",
},
optimize = {
On = "/optimize",
Size = "/optimize",
Speed = "/optimize",
},
symbols = {
On = "/debug",
}
}
function dotnet.getflags(cfg)
local flags = config.mapFlags(cfg, dotnet.flags)
-- Tells the compiler not to include the csc.rsp response file which
-- it does by default and references all the assemblies shipped with
-- the .NET Framework. VS sets this flag by default for C# projects.
table.insert(flags, '/noconfig')
if cfg.project.icon then
local fn = project.getrelative(cfg.project, cfg.project.icon)
table.insert(flags, string.format('/win32icon:"%s"', fn))
end
if #cfg.defines > 0 then
table.insert(flags, table.implode(cfg.defines, "/d:", "", " "))
end
if cfg.csversion ~= nil then
table.insert(flags, '/langversion:' .. cfg.csversion)
end
return table.join(flags, cfg.buildoptions)
end
--
-- Translates the Premake kind into the CSC kind string.
--
function dotnet.getkind(cfg)
if (cfg.kind == "ConsoleApp") then
return "Exe"
elseif (cfg.kind == "WindowedApp") then
return "WinExe"
elseif (cfg.kind == "SharedLib") then
return "Library"
else
error("invalid dotnet kind " .. cfg.kind .. ". Valid kinds are ConsoleApp, WindowsApp, SharedLib")
end
end
--
-- Returns makefile-specific configuration rules.
--
function dotnet.getmakesettings(cfg)
return nil
end

View File

@@ -0,0 +1,639 @@
---
-- gcc.lua
-- Provides GCC-specific configuration strings.
-- Copyright (c) 2002-2015 Jason Perkins and the Premake project
---
local p = premake
p.tools.gcc = {}
local gcc = p.tools.gcc
local project = p.project
local config = p.config
--
-- Returns list of C preprocessor flags for a configuration.
--
gcc.cppflags = {
system = {
haiku = "-MMD",
wii = { "-MMD", "-MP", "-I$(LIBOGC_INC)", "$(MACHDEP)" },
_ = { "-MMD", "-MP" }
}
}
function gcc.getcppflags(cfg)
local flags = config.mapFlags(cfg, gcc.cppflags)
return flags
end
--
-- Returns string to be appended to -g
--
function gcc.getdebugformat(cfg)
local flags = {
Default = "",
Dwarf = "dwarf",
SplitDwarf = "split-dwarf",
}
return flags
end
--
-- Returns list of C compiler flags for a configuration.
--
gcc.shared = {
architecture = {
x86 = "-m32",
x86_64 = "-m64",
},
flags = {
FatalCompileWarnings = "-Werror",
LinkTimeOptimization = "-flto",
ShadowedVariables = "-Wshadow",
UndefinedIdentifiers = "-Wundef",
},
floatingpoint = {
Fast = "-ffast-math",
Strict = "-ffloat-store",
},
strictaliasing = {
Off = "-fno-strict-aliasing",
Level1 = { "-fstrict-aliasing", "-Wstrict-aliasing=1" },
Level2 = { "-fstrict-aliasing", "-Wstrict-aliasing=2" },
Level3 = { "-fstrict-aliasing", "-Wstrict-aliasing=3" },
},
optimize = {
Off = "-O0",
On = "-O2",
Debug = "-Og",
Full = "-O3",
Size = "-Os",
Speed = "-O3",
},
pic = {
On = "-fPIC",
},
vectorextensions = {
AVX = "-mavx",
AVX2 = "-mavx2",
SSE = "-msse",
SSE2 = "-msse2",
SSE3 = "-msse3",
SSSE3 = "-mssse3",
["SSE4.1"] = "-msse4.1",
["SSE4.2"] = "-msse4.2",
},
isaextensions = {
MOVBE = "-mmovbe",
POPCNT = "-mpopcnt",
PCLMUL = "-mpclmul",
LZCNT = "-mlzcnt",
BMI = "-mbmi",
BMI2 = "-mbmi2",
F16C = "-mf16c",
AES = "-maes",
FMA = "-mfma",
FMA4 = "-mfma4",
RDRND = "-mrdrnd",
},
warnings = {
Off = "-w",
High = "-Wall",
Extra = {"-Wall", "-Wextra"},
Everything = "-Weverything",
},
symbols = function(cfg, mappings)
local values = gcc.getdebugformat(cfg)
local debugformat = values[cfg.debugformat] or ""
return {
On = "-g" .. debugformat,
FastLink = "-g" .. debugformat,
Full = "-g" .. debugformat,
}
end,
unsignedchar = {
On = "-funsigned-char",
Off = "-fno-unsigned-char"
},
omitframepointer = {
On = "-fomit-frame-pointer",
Off = "-fno-omit-frame-pointer"
},
compileas = {
["Objective-C"] = "-x objective-c",
["Objective-C++"] = "-x objective-c++",
}
}
gcc.cflags = {
cdialect = {
["C89"] = "-std=c89",
["C90"] = "-std=c90",
["C99"] = "-std=c99",
["C11"] = "-std=c11",
["gnu89"] = "-std=gnu89",
["gnu90"] = "-std=gnu90",
["gnu99"] = "-std=gnu99",
["gnu11"] = "-std=gnu11",
}
}
function gcc.getcflags(cfg)
local shared_flags = config.mapFlags(cfg, gcc.shared)
local cflags = config.mapFlags(cfg, gcc.cflags)
local flags = table.join(shared_flags, cflags, gcc.getsystemversionflags(cfg))
flags = table.join(flags, gcc.getwarnings(cfg))
return flags
end
function gcc.getwarnings(cfg)
local result = {}
for _, enable in ipairs(cfg.enablewarnings) do
table.insert(result, '-W' .. enable)
end
for _, disable in ipairs(cfg.disablewarnings) do
table.insert(result, '-Wno-' .. disable)
end
for _, fatal in ipairs(cfg.fatalwarnings) do
table.insert(result, '-Werror=' .. fatal)
end
return result
end
--
-- Returns C/C++ system version build flags
--
function gcc.getsystemversionflags(cfg)
local flags = {}
if cfg.system == p.MACOSX then
local minVersion = p.project.systemversion(cfg)
if minVersion ~= nil then
table.insert (flags, "-mmacosx-version-min=" .. minVersion)
end
end
return flags
end
--
-- Returns list of C++ compiler flags for a configuration.
--
gcc.cxxflags = {
exceptionhandling = {
Off = "-fno-exceptions"
},
flags = {
NoBufferSecurityCheck = "-fno-stack-protector",
},
cppdialect = {
["C++98"] = "-std=c++98",
["C++0x"] = "-std=c++0x",
["C++11"] = "-std=c++11",
["C++1y"] = "-std=c++1y",
["C++14"] = "-std=c++14",
["C++1z"] = "-std=c++1z",
["C++17"] = "-std=c++17",
["C++2a"] = "-std=c++2a",
["C++20"] = "-std=c++20",
["gnu++98"] = "-std=gnu++98",
["gnu++0x"] = "-std=gnu++0x",
["gnu++11"] = "-std=gnu++11",
["gnu++1y"] = "-std=gnu++1y",
["gnu++14"] = "-std=gnu++14",
["gnu++1z"] = "-std=gnu++1z",
["gnu++17"] = "-std=gnu++17",
["gnu++2a"] = "-std=gnu++2a",
["gnu++20"] = "-std=gnu++20",
["C++latest"] = "-std=c++20",
},
rtti = {
Off = "-fno-rtti"
},
visibility = {
Default = "-fvisibility=default",
Hidden = "-fvisibility=hidden",
Internal = "-fvisibility=internal",
Protected = "-fvisibility=protected",
},
inlinesvisibility = {
Hidden = "-fvisibility-inlines-hidden"
}
}
function gcc.getcxxflags(cfg)
local shared_flags = config.mapFlags(cfg, gcc.shared)
local cxxflags = config.mapFlags(cfg, gcc.cxxflags)
local flags = table.join(shared_flags, cxxflags)
flags = table.join(flags, gcc.getwarnings(cfg), gcc.getsystemversionflags(cfg))
return flags
end
--
-- Decorate defines for the GCC command line.
--
function gcc.getdefines(defines)
local result = {}
for _, define in ipairs(defines) do
table.insert(result, '-D' .. p.esc(define))
end
return result
end
function gcc.getundefines(undefines)
local result = {}
for _, undefine in ipairs(undefines) do
table.insert(result, '-U' .. p.esc(undefine))
end
return result
end
--
-- Returns a list of forced include files, decorated for the compiler
-- command line.
--
-- @param cfg
-- The project configuration.
-- @return
-- An array of force include files with the appropriate flags.
--
function gcc.getforceincludes(cfg)
local result = {}
table.foreachi(cfg.forceincludes, function(value)
local fn = project.getrelative(cfg.project, value)
table.insert(result, string.format('-include %s', p.quoted(fn)))
end)
return result
end
--
-- Decorate include file search paths for the GCC command line.
--
function gcc.getincludedirs(cfg, dirs, sysdirs, frameworkdirs)
local result = {}
for _, dir in ipairs(dirs) do
dir = project.getrelative(cfg.project, dir)
table.insert(result, '-I' .. p.quoted(dir))
end
if table.contains(os.getSystemTags(cfg.system), "darwin") then
for _, dir in ipairs(frameworkdirs or {}) do
dir = project.getrelative(cfg.project, dir)
table.insert(result, '-F' .. p.quoted(dir))
end
end
for _, dir in ipairs(sysdirs or {}) do
dir = project.getrelative(cfg.project, dir)
table.insert(result, '-isystem ' .. p.quoted(dir))
end
return result
end
-- relative pch file path if any
function gcc.getpch(cfg)
-- If there is no header, or if PCH has been disabled, I can early out
if not cfg.pchheader or cfg.flags.NoPCH then
return nil
end
-- Visual Studio requires the PCH header to be specified in the same way
-- it appears in the #include statements used in the source code; the PCH
-- source actual handles the compilation of the header. GCC compiles the
-- header file directly, and needs the file's actual file system path in
-- order to locate it.
-- To maximize the compatibility between the two approaches, see if I can
-- locate the specified PCH header on one of the include file search paths
-- and, if so, adjust the path automatically so the user doesn't have
-- add a conditional configuration to the project script.
local pch = cfg.pchheader
local found = false
-- test locally in the project folder first (this is the most likely location)
local testname = path.join(cfg.project.basedir, pch)
if os.isfile(testname) then
return project.getrelative(cfg.project, testname)
else
-- else scan in all include dirs.
for _, incdir in ipairs(cfg.includedirs) do
testname = path.join(incdir, pch)
if os.isfile(testname) then
return project.getrelative(cfg.project, testname)
end
end
end
return project.getrelative(cfg.project, path.getabsolute(pch))
end
--
-- Return a list of decorated rpaths
--
-- @param cfg
-- The configuration to query.
-- @param dirs
-- List of absolute paths
-- @param mode
-- Output mode
-- - "linker" (default) Linker rpath instructions
-- - "path" List of path relative to configuration target directory
--
function gcc.getrunpathdirs(cfg, dirs, mode)
local result = {}
mode = iif (mode == nil, "linker", mode)
if not (table.contains(os.getSystemTags(cfg.system), "darwin")
or (cfg.system == p.LINUX)) then
return result
end
for _, fullpath in ipairs(dirs) do
local rpath = path.getrelative(cfg.buildtarget.directory, fullpath)
if table.contains(os.getSystemTags(cfg.system), "darwin") then
rpath = "@loader_path/" .. rpath
elseif (cfg.system == p.LINUX) then
rpath = iif(rpath == ".", "", "/" .. rpath)
rpath = "$$ORIGIN" .. rpath
end
if mode == "linker" then
rpath = "-Wl,-rpath,'" .. rpath .. "'"
end
table.insert(result, rpath)
end
return result
end
--
-- get the right output flag.
--
function gcc.getsharedlibarg(cfg)
if table.contains(os.getSystemTags(cfg.system), "darwin") then
if cfg.sharedlibtype == "OSXBundle" then
return "-bundle"
elseif cfg.sharedlibtype == "XCTest" then
return "-bundle"
elseif cfg.sharedlibtype == "OSXFramework" then
return "-framework"
else
return "-dynamiclib"
end
else
return "-shared"
end
end
--
-- Return a list of LDFLAGS for a specific configuration.
--
function gcc.ldsymbols(cfg)
-- OS X has a bug, see http://lists.apple.com/archives/Darwin-dev/2006/Sep/msg00084.html
return iif(table.contains(os.getSystemTags(cfg.system), "darwin"), "-Wl,-x", "-s")
end
gcc.ldflags = {
architecture = {
x86 = "-m32",
x86_64 = "-m64",
},
flags = {
LinkTimeOptimization = "-flto",
},
kind = {
SharedLib = function(cfg)
local r = { gcc.getsharedlibarg(cfg) }
if cfg.system == p.WINDOWS and not cfg.flags.NoImportLib then
table.insert(r, '-Wl,--out-implib="' .. cfg.linktarget.relpath .. '"')
elseif cfg.system == p.LINUX then
table.insert(r, '-Wl,-soname=' .. p.quoted(cfg.linktarget.name))
elseif table.contains(os.getSystemTags(cfg.system), "darwin") then
table.insert(r, '-Wl,-install_name,' .. p.quoted('@rpath/' .. cfg.linktarget.name))
end
return r
end,
WindowedApp = function(cfg)
if cfg.system == p.WINDOWS then return "-mwindows" end
end,
},
system = {
wii = "$(MACHDEP)",
},
symbols = {
Off = gcc.ldsymbols,
Default = gcc.ldsymbols,
}
}
function gcc.getldflags(cfg)
local flags = config.mapFlags(cfg, gcc.ldflags)
return flags
end
--
-- Return a list of decorated additional libraries directories.
--
gcc.libraryDirectories = {
architecture = {
x86 = function (cfg)
local r = {}
if not table.contains(os.getSystemTags(cfg.system), "darwin") then
table.insert (r, "-L/usr/lib32")
end
return r
end,
x86_64 = function (cfg)
local r = {}
if not table.contains(os.getSystemTags(cfg.system), "darwin") then
table.insert (r, "-L/usr/lib64")
end
return r
end,
},
system = {
wii = "-L$(LIBOGC_LIB)",
}
}
function gcc.getLibraryDirectories(cfg)
local flags = {}
-- Scan the list of linked libraries. If any are referenced with
-- paths, add those to the list of library search paths. The call
-- config.getlinks() all includes cfg.libdirs.
for _, dir in ipairs(config.getlinks(cfg, "system", "directory")) do
table.insert(flags, '-L' .. p.quoted(dir))
end
if table.contains(os.getSystemTags(cfg.system), "darwin") then
for _, dir in ipairs(cfg.frameworkdirs) do
dir = project.getrelative(cfg.project, dir)
table.insert(flags, '-F' .. p.quoted(dir))
end
end
if cfg.flags.RelativeLinks then
for _, dir in ipairs(config.getlinks(cfg, "siblings", "directory")) do
local libFlag = "-L" .. p.project.getrelative(cfg.project, dir)
if not table.contains(flags, libFlag) then
table.insert(flags, libFlag)
end
end
end
for _, dir in ipairs(cfg.syslibdirs) do
table.insert(flags, '-L' .. p.quoted(dir))
end
local gccFlags = config.mapFlags(cfg, gcc.libraryDirectories)
flags = table.join(flags, gccFlags)
return flags
end
--
-- Return the list of libraries to link, decorated with flags as needed.
--
function gcc.getlinks(cfg, systemonly, nogroups)
local result = {}
if not systemonly then
if cfg.flags.RelativeLinks then
local libFiles = config.getlinks(cfg, "siblings", "basename")
for _, link in ipairs(libFiles) do
if string.startswith(link, "lib") then
link = link:sub(4)
end
table.insert(result, "-l" .. link)
end
else
-- Don't use the -l form for sibling libraries, since they may have
-- custom prefixes or extensions that will confuse the linker. Instead
-- just list out the full relative path to the library.
result = config.getlinks(cfg, "siblings", "fullpath")
end
end
-- The "-l" flag is fine for system libraries
local links = config.getlinks(cfg, "system", "fullpath")
local static_syslibs = {"-Wl,-Bstatic"}
local shared_syslibs = {}
for _, link in ipairs(links) do
if path.isframework(link) then
table.insert(result, "-framework")
table.insert(result, path.getbasename(link))
elseif path.isobjectfile(link) then
table.insert(result, link)
else
local endswith = function(s, ptrn)
return ptrn == string.sub(s, -string.len(ptrn))
end
local name = path.getname(link)
-- Check whether link mode decorator is present
if endswith(name, ":static") then
name = string.sub(name, 0, -8)
table.insert(static_syslibs, "-l" .. name)
elseif endswith(name, ":shared") then
name = string.sub(name, 0, -8)
table.insert(shared_syslibs, "-l" .. name)
else
table.insert(shared_syslibs, "-l" .. name)
end
end
end
local move = function(a1, a2)
local t = #a2
for i = 1, #a1 do a2[t + i] = a1[i] end
end
if #static_syslibs > 1 then
table.insert(static_syslibs, "-Wl,-Bdynamic")
move(static_syslibs, result)
end
move(shared_syslibs, result)
if not nogroups and #result > 1 and (cfg.linkgroups == p.ON) then
table.insert(result, 1, "-Wl,--start-group")
table.insert(result, "-Wl,--end-group")
end
return result
end
--
-- Returns makefile-specific configuration rules.
--
gcc.makesettings = {
system = {
wii = [[
ifeq ($(strip $(DEVKITPPC)),)
$(error "DEVKITPPC environment variable is not set")'
endif
include $(DEVKITPPC)/wii_rules']]
}
}
function gcc.getmakesettings(cfg)
local settings = config.mapFlags(cfg, gcc.makesettings)
return table.concat(settings)
end
--
-- Retrieves the executable command name for a tool, based on the
-- provided configuration and the operating environment.
--
-- @param cfg
-- The configuration to query.
-- @param tool
-- The tool to fetch, one of "cc" for the C compiler, "cxx" for
-- the C++ compiler, or "ar" for the static linker.
-- @return
-- The executable command name for a tool, or nil if the system's
-- default value should be used.
--
gcc.tools = {
cc = "gcc",
cxx = "g++",
ar = "ar",
rc = "windres"
}
function gcc.gettoolname(cfg, tool)
if (cfg.gccprefix and gcc.tools[tool]) or tool == "rc" then
return (cfg.gccprefix or "") .. gcc.tools[tool]
end
return nil
end

Some files were not shown because too many files have changed in this diff Show More