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,170 @@
--
-- action.lua
-- Work with the list of registered actions.
-- Copyright (c) 2002-2009 Jason Perkins and the Premake project
--
premake.action = { }
--
-- The list of registered actions.
--
premake.action.list = { }
--
-- Register a new action.
--
-- @param a
-- The new action object.
--
function premake.action.add(a)
-- validate the action object, at least a little bit
local missing
for _, field in ipairs({"description", "trigger"}) do
if (not a[field]) then
missing = field
end
end
if (missing) then
error("action needs a " .. missing, 3)
end
-- add it to the master list
premake.action.list[a.trigger] = a
end
--
-- Trigger an action.
--
-- @param name
-- The name of the action to be triggered.
-- @returns
-- None.
--
function premake.action.call(name)
local a = premake.action.list[name]
for sln in premake.solution.each() do
if a.onsolution then
a.onsolution(sln)
end
if sln.postsolutioncallbacks then
for _,cb in ipairs(sln.postsolutioncallbacks) do
cb(sln)
end
end
for prj in premake.solution.eachproject(sln) do
if a.onproject then
a.onproject(prj)
end
if prj.postprojectcallbacks then
for _,cb in ipairs(prj.postprojectcallbacks) do
cb(prj)
end
end
end
end
if a.execute then
a.execute()
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 premake.action.current()
return premake.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 premake.action.get(name)
return premake.action.list[name]
end
--
-- Iterator for the list of actions.
--
function premake.action.each()
-- sort the list by trigger
local keys = { }
for _, action in pairs(premake.action.list) do
table.insert(keys, action.trigger)
end
table.sort(keys)
local i = 0
return function()
i = i + 1
return premake.action.list[keys[i]]
end
end
--
-- Activates a particular action.
--
-- @param name
-- The name of the action to activate.
--
function premake.action.set(name)
_ACTION = name
-- Some actions imply a particular operating system
local action = premake.action.get(name)
if action then
_OS = action.os or _OS
end
end
--
-- Determines if an action supports a particular language or target type.
--
-- @param action
-- The action to test.
-- @param feature
-- The feature to check, either a programming language or a target type.
-- @returns
-- True if the feature is supported, false otherwise.
--
function premake.action.supports(action, feature)
if not action then
return false
end
if action.valid_languages then
if table.contains(action.valid_languages, feature) then
return true
end
end
if action.valid_kinds then
if table.contains(action.valid_kinds, feature) then
return true
end
end
return false
end

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,865 @@
--
-- base/bake.lua
--
-- Takes all the configuration information provided by the project scripts
-- and stored in the solution->project->block hierarchy and flattens it all
-- down into one object per configuration. These objects are cached with the
-- project, and can be retrieved by calling the getconfig() or eachconfig().
--
-- Copyright (c) 2008-2011 Jason Perkins and the Premake project
--
premake.bake = { }
local bake = premake.bake
-- do not copy these fields into the configurations
local nocopy =
{
blocks = true,
keywords = true,
projects = true,
__configs = true,
}
-- do not cascade these fields from projects to configurations
local nocascade =
{
makesettings = true,
}
-- leave these paths as absolute, rather than converting to project relative
local keeprelative =
{
basedir = true,
location = true,
}
--
-- Returns a list of all of the active terms from the current environment.
-- See the docs for configuration() for more information about the terms.
--
function premake.getactiveterms(obj)
-- While the `obj` argument is not used in this function, it should
-- remain accepted so users can override this function if need be, and
-- still have access to the context (solution/project).
local terms = { _action = _ACTION:lower(), os = os.get() }
-- add option keys or values
for key, value in pairs(_OPTIONS) do
if value ~= "" then
table.insert(terms, value:lower())
else
table.insert(terms, key:lower())
end
end
return terms
end
--
-- Test a single configuration block keyword against a list of terms.
-- The terms are a mix of key/value pairs. The keyword is tested against
-- the values; on a match, the corresponding key is returned. This
-- enables testing for required values in iskeywordsmatch(), below.
--
function premake.iskeywordmatch(keyword, terms)
-- is it negated?
if keyword:startswith("not ") then
return not premake.iskeywordmatch(keyword:sub(5), terms)
end
for _, pattern in ipairs(keyword:explode(" or ")) do
for termkey, term in pairs(terms) do
if term:match(pattern) == term then
return termkey
end
end
end
end
--
-- Checks a set of configuration block keywords against a list of terms.
-- The required flag is used by the file configurations: only blocks
-- with a term that explictly matches the filename get applied; more
-- general blocks are skipped over (since they were already applied at
-- the config level).
--
function premake.iskeywordsmatch(keywords, terms)
local hasrequired = false
for _, keyword in ipairs(keywords) do
local matched = premake.iskeywordmatch(keyword, terms)
if not matched then
return false
end
if matched == "required" then
hasrequired = true
end
end
if terms.required and not hasrequired then
return false
else
return true
end
end
--
-- Converts path fields from absolute to location-relative paths.
--
-- @param location
-- The base location, paths will be relative to this directory.
-- @param obj
-- The object containing the fields to be adjusted.
--
local function adjustpaths(location, obj)
function adjustpathlist(list)
for i, p in ipairs(list) do
list[i] = path.getrelative(location, p)
end
end
if obj.allfiles ~= nil then
adjustpathlist(obj.allfiles)
end
for name, value in pairs(obj) do
local field = premake.fields[name]
if field and value and not keeprelative[name] then
if field.kind == "path" then
obj[name] = path.getrelative(location, value)
elseif field.kind == "dirlist" or field.kind == "filelist" then
adjustpathlist(value)
elseif field.kind == "keypath" then
for k,v in pairs(value) do
adjustpathlist(v)
end
end
end
end
end
local function removevalue(tbl, remove)
for index, item in ipairs(tbl) do
if item == remove then
table.remove(tbl, index)
break
end
end
end
local function removevalues(tbl, removes)
for k, v in pairs(tbl) do
for _, pattern in ipairs(removes) do
if pattern == tbl[k] then
if type(k) == "number" then
table.remove(tbl, k)
else
tbl[k] = nil
end
break
end
end
end
end
--
-- Merge all of the fields from one object into another. String values are overwritten,
-- while list values are merged. Fields listed in premake.nocopy are skipped.
--
-- @param dest
-- The destination object, to contain the merged settings.
-- @param src
-- The source object, containing the settings to added to the destination.
--
local function mergefield(kind, dest, src, mergecopiestotail)
local tbl = dest or { }
if kind == "keyvalue" or kind == "keypath" then
for key, value in pairs(src) do
tbl[key] = mergefield("list", tbl[key], value, mergecopiestotail)
end
else
for _, item in ipairs(src) do
if tbl[item] then
if mergecopiestotail then
removevalue(tbl, item)
table.insert(tbl, item)
tbl[item] = item
end
else
table.insert(tbl, item)
tbl[item] = item
end
end
end
return tbl
end
local function mergeobject(dest, src)
-- if there's nothing to add, quick out
if not src then
return
end
for fieldname, value in pairs(src) do
if not nocopy[fieldname] then
-- fields that are included in the API are merged...
local field = premake.fields[fieldname]
if field then
if type(value) == "table" then
dest[fieldname] = mergefield(field.kind, dest[fieldname], value, field.mergecopiestotail)
if src.removes then
removes = src.removes[fieldname]
if removes then
removevalues(dest[fieldname], removes)
end
end
else
dest[fieldname] = value
end
-- ...everything else is just copied as-is
else
dest[fieldname] = value
end
end
end
end
--
-- Merges the settings from a solution's or project's list of configuration blocks,
-- for all blocks that match the provided set of environment terms.
--
-- @param dest
-- The destination object, to contain the merged settings.
-- @param obj
-- The solution or project object being collapsed.
-- @param basis
-- "Root" level settings, from the solution, which act as a starting point for
-- all of the collapsed settings built during this call.
-- @param terms
-- A list of keywords to filter the configuration blocks; only those that
-- match will be included in the destination.
-- @param cfgname
-- The name of the configuration being collapsed. May be nil.
-- @param pltname
-- The name of the platform being collapsed. May be nil.
--
local function merge(dest, obj, basis, terms, cfgname, pltname)
-- the configuration key is the merged configuration and platform names
local key = cfgname or ""
pltname = pltname or "Native"
if pltname ~= "Native" then
key = key .. pltname
end
-- add the configuration and platform to the block filter terms
terms.config = (cfgname or ""):lower()
terms.platform = pltname:lower()
-- build the configuration base by merging the solution and project level settings
local cfg = {}
mergeobject(cfg, basis[key])
adjustpaths(obj.location, cfg)
mergeobject(cfg, obj)
-- add `kind` to the filter terms
if (cfg.kind) then
terms['kind']=cfg.kind:lower()
end
-- now add in any blocks that match the filter terms
for _, blk in ipairs(obj.blocks) do
if (premake.iskeywordsmatch(blk.keywords, terms))then
mergeobject(cfg, blk)
if (cfg.kind and not cfg.terms.kind) then
cfg.terms['kind'] = cfg.kind:lower()
terms['kind'] = cfg.kind:lower()
end
end
end
-- package it all up and add it to the result set
cfg.name = cfgname
cfg.platform = pltname
for k,v in pairs(terms) do
cfg.terms[k] =v
end
dest[key] = cfg
end
--
-- Collapse a solution or project object down to a canonical set of configuration settings,
-- keyed by configuration block/platform pairs, and taking into account the current
-- environment settings.
--
-- @param obj
-- The solution or project to be collapsed.
-- @param basis
-- "Root" level settings, from the solution, which act as a starting point for
-- all of the collapsed settings built during this call.
-- @returns
-- The collapsed list of settings, keyed by configuration block/platform pair.
--
local function collapse(obj, basis)
local result = {}
basis = basis or {}
-- find the solution, which contains the configuration and platform lists
local sln = obj.solution or obj
-- build a set of configuration filter terms; only those configuration blocks
-- with a matching set of keywords will be included in the merged results
local terms = premake.getactiveterms(obj)
-- build a project-level configuration.
merge(result, obj, basis, terms)--this adjusts terms
-- now build configurations for each build config/platform pair
for _, cfgname in ipairs(sln.configurations) do
local terms_local = {}
for k,v in pairs(terms)do terms_local[k]=v end
merge(result, obj, basis, terms_local, cfgname, "Native")--terms cam also be adjusted here
for _, pltname in ipairs(sln.platforms or {}) do
if pltname ~= "Native" then
merge(result, obj, basis,terms_local, cfgname, pltname)--terms also here
end
end
end
return result
end
--
-- Computes a unique objects directory for every configuration, using the
-- following choices:
-- [1] -> the objects directory as set in the project of config
-- [2] -> [1] + the platform name
-- [3] -> [2] + the configuration name
-- [4] -> [3] + the project name
--
local function builduniquedirs()
local num_variations = 4
-- Start by listing out each possible object directory for each configuration.
-- Keep a count of how many times each path gets used across the session.
local cfg_dirs = {}
local hit_counts = {}
for sln in premake.solution.each() do
for _, prj in ipairs(sln.projects) do
for _, cfg in pairs(prj.__configs) do
local dirs = { }
dirs[1] = path.getabsolute(path.join(cfg.location, cfg.objdir or cfg.project.objdir or "obj"))
dirs[2] = path.join(dirs[1], iif(cfg.platform == "Native", "", cfg.platform))
dirs[3] = path.join(dirs[2], cfg.name)
dirs[4] = path.join(dirs[3], cfg.project.name)
cfg_dirs[cfg] = dirs
-- configurations other than the root should bias toward a more
-- description path, including the platform or config name
local start = iif(cfg.name, 2, 1)
for v = start, num_variations do
local d = dirs[v]
hit_counts[d] = (hit_counts[d] or 0) + 1
end
end
end
end
-- Now assign an object directory to each configuration, skipping those
-- that are in use somewhere else in the session
for sln in premake.solution.each() do
for _, prj in ipairs(sln.projects) do
for _, cfg in pairs(prj.__configs) do
local dir
local start = iif(cfg.name, 2, 1)
for v = start, iif(cfg.flags.SingleOutputDir==true,num_variations-1,num_variations) do
dir = cfg_dirs[cfg][v]
if hit_counts[dir] == 1 then break end
end
cfg.objectsdir = path.getrelative(cfg.location, dir)
end
end
end
end
--
-- Pre-computes the build and link targets for a configuration.
--
local function buildtargets()
for sln in premake.solution.each() do
for _, prj in ipairs(sln.projects) do
for _, cfg in pairs(prj.__configs) do
-- determine which conventions the target should follow for this config
local pathstyle = premake.getpathstyle(cfg)
local namestyle = premake.getnamestyle(cfg)
-- build the targets
cfg.buildtarget = premake.gettarget(cfg, "build", pathstyle, namestyle, cfg.system)
cfg.linktarget = premake.gettarget(cfg, "link", pathstyle, namestyle, cfg.system)
if pathstyle == "windows" then
cfg.objectsdir = path.translate(cfg.objectsdir, "\\")
end
end
end
end
end
local function getCfgKind(cfg)
if(cfg.kind) then
return cfg.kind;
end
if(cfg.project.__configs[""] and cfg.project.__configs[""].kind) then
return cfg.project.__configs[""].kind;
end
return nil
end
local function getprojrec(dstArray, foundList, cfg, cfgname, searchField, bLinkage)
if(not cfg) then return end
local foundUsePrjs = {};
for _, useName in ipairs(cfg[searchField]) do
local testName = useName:lower();
if((not foundList[testName])) then
local theProj = nil;
local theUseProj = nil;
for _, prj in ipairs(cfg.project.solution.projects) do
if (prj.name:lower() == testName) then
if(prj.usage) then
theUseProj = prj;
else
theProj = prj;
end
end
end
--Must connect to a usage project.
if(theUseProj) then
foundList[testName] = true;
local prjEntry = {
name = testName,
proj = theProj,
usageProj = theUseProj,
bLinkageOnly = bLinkage,
};
dstArray[testName] = prjEntry;
table.insert(foundUsePrjs, theUseProj);
end
end
end
for _, usePrj in ipairs(foundUsePrjs) do
--Links can only recurse through static libraries.
if((searchField ~= "links") or
(getCfgKind(usePrj.__configs[cfgname]) == "StaticLib")) then
getprojrec(dstArray, foundList, usePrj.__configs[cfgname],
cfgname, searchField, bLinkage);
end
end
end
--
-- This function will recursively get all projects that the given configuration has in its "uses"
-- field. The return values are a list of tables. Each table in that list contains the following:
-- name = The lowercase name of the project.
-- proj = The project. Can be nil if it is usage-only.
-- usageProj = The usage project. Can't be nil, as using a project that has no
-- usage project is not put into the list.
-- bLinkageOnly = If this is true, then only the linkage information should be copied.
-- The recursion will only look at the "uses" field on *usage* projects.
-- This function will also add projects to the list that are mentioned in the "links"
-- field of usage projects. These will only copy linker information, but they will recurse.
-- through other "links" fields.
--
local function getprojectsconnections(cfg, cfgname)
local dstArray = {};
local foundList = {};
foundList[cfg.project.name:lower()] = true;
--First, follow the uses recursively.
getprojrec(dstArray, foundList, cfg, cfgname, "uses", false);
--Next, go through all of the usage projects and recursively get their links.
--But only if they're not already there. Get the links as linkage-only.
local linkArray = {};
for prjName, prjEntry in pairs(dstArray) do
getprojrec(linkArray, foundList, prjEntry.usageProj.__configs[cfgname], cfgname,
"links", true);
end
--Copy from linkArray into dstArray.
for prjName, prjEntry in pairs(linkArray) do
dstArray[prjName] = prjEntry;
end
return dstArray;
end
local function isnameofproj(cfg, strName)
local sln = cfg.project.solution;
local strTest = strName:lower();
for prjIx, prj in ipairs(sln.projects) do
if (prj.name:lower() == strTest) then
return true;
end
end
return false;
end
--
-- Copies the field from dstCfg to srcCfg.
--
local function copydependentfield(srcCfg, dstCfg, strSrcField)
local srcField = premake.fields[strSrcField];
local strDstField = strSrcField;
if type(srcCfg[strSrcField]) == "table" then
--handle paths.
if (srcField.kind == "dirlist" or srcField.kind == "filelist") and
(not keeprelative[strSrcField]) then
for i,p in ipairs(srcCfg[strSrcField]) do
table.insert(dstCfg[strDstField],
path.rebase(p, srcCfg.project.location, dstCfg.project.location))
end
else
if(strSrcField == "links") then
for i,p in ipairs(srcCfg[strSrcField]) do
if(not isnameofproj(dstCfg, p)) then
table.insert(dstCfg[strDstField], p)
else
printf("Failed to copy '%s' from proj '%s'.",
p, srcCfg.project.name);
end
end
else
for i,p in ipairs(srcCfg[strSrcField]) do
table.insert(dstCfg[strDstField], p)
end
end
end
else
if(srcField.kind == "path" and (not keeprelative[strSrcField])) then
dstCfg[strDstField] = path.rebase(srcCfg[strSrcField],
prj.location, dstCfg.project.location);
else
dstCfg[strDstField] = srcCfg[strSrcField];
end
end
end
--
-- This function will take the list of project entries and apply their usage project data
-- to the given configuration. It will copy compiling information for the projects that are
-- not listed as linkage-only. It will copy the linking information for projects only if
-- the source project is not a static library. It won't copy linking information
-- if the project is in this solution; instead it will add that project to the configuration's
-- links field, expecting that Premake will handle the rest.
--
local function copyusagedata(cfg, cfgname, linkToProjs)
local myPrj = cfg.project;
local bIsStaticLib = (getCfgKind(cfg) == "StaticLib");
for prjName, prjEntry in pairs(linkToProjs) do
local srcPrj = prjEntry.usageProj;
local srcCfg = srcPrj.__configs[cfgname];
for name, field in pairs(premake.fields) do
if(srcCfg[name]) then
if(field.usagecopy) then
if(not prjEntry.bLinkageOnly) then
copydependentfield(srcCfg, cfg, name)
end
elseif(field.linkagecopy) then
--Copy the linkage data if we're building a non-static thing
--and this is a pure usage project. If it's not pure-usage, then
--we will simply put the project's name in the links field later.
if((not bIsStaticLib) and (not prjEntry.proj)) then
copydependentfield(srcCfg, cfg, name)
end
end
end
end
if((not bIsStaticLib) and prjEntry.proj) then
table.insert(cfg.links, prjEntry.proj.name);
end
end
end
--
-- Build an inverse dictionary of literal vpaths for fast lookup
--
local function inverseliteralvpaths()
for sln in premake.solution.each() do
for _,prj in ipairs(sln.projects) do
prj.inversevpaths = {}
for replacement, patterns in pairs(prj.vpaths or {}) do
for _, pattern in ipairs(patterns) do
if string.find(pattern, "*") == nil then
prj.inversevpaths[pattern] = replacement
end
end
end
end
end
end
--
-- Main function, controls the process of flattening the configurations.
--
function premake.bake.buildconfigs()
-- convert project path fields to be relative to project location
for sln in premake.solution.each() do
for _, prj in ipairs(sln.projects) do
prj.location = prj.location or sln.location or prj.basedir
adjustpaths(prj.location, prj)
for _, blk in ipairs(prj.blocks) do
adjustpaths(prj.location, blk)
end
end
sln.location = sln.location or sln.basedir
end
-- convert paths for imported projects to be relative to solution location
for sln in premake.solution.each() do
for _, iprj in ipairs(sln.importedprojects) do
iprj.location = path.getabsolute(iprj.location)
end
end
inverseliteralvpaths()
-- collapse configuration blocks, so that there is only one block per build
-- configuration/platform pair, filtered to the current operating environment
for sln in premake.solution.each() do
local basis = collapse(sln)
for _, prj in ipairs(sln.projects) do
prj.__configs = collapse(prj, basis)
for _, cfg in pairs(prj.__configs) do
bake.postprocess(prj, cfg)
end
end
end
-- This loop finds the projects that a configuration is connected to
-- via its "uses" field. It will then copy any usage project information from that
-- usage project to the configuration in question.
for sln in premake.solution.each() do
for prjIx, prj in ipairs(sln.projects) do
if(not prj.usage) then
for cfgname, cfg in pairs(prj.__configs) do
local usesPrjs = getprojectsconnections(cfg, cfgname);
copyusagedata(cfg, cfgname, usesPrjs)
end
end
end
end
-- mark all configurations that have been removed via their removes table.
for sln in premake.solution.each() do
for prjIx, prj in ipairs(sln.projects) do
for cfgName, cfg in pairs(prj.__configs) do
cfg.build = true
local removes = nil
if cfg.removes ~= nil then
removes = cfg.removes["platforms"];
end
if removes ~= nil then
for _,p in ipairs(removes) do
if p == cfg.platform then
cfg.build = false
end
end
end
end
end
end
-- Remove all usage projects.
for sln in premake.solution.each() do
local removeList = {};
for index, prj in ipairs(sln.projects) do
if(prj.usage) then
table.insert(removeList, 1, index); --Add in reverse order.
end
end
for _, index in ipairs(removeList) do
table.remove(sln.projects, index);
end
end
-- assign unique object directories to each configuration
builduniquedirs()
-- walk it again and build the targets and unique directories
buildtargets(cfg)
end
--
-- Post-process a project configuration, applying path fix-ups and other adjustments
-- to the "raw" setting data pulled from the project script.
--
-- @param prj
-- The project object which contains the configuration.
-- @param cfg
-- The configuration object to be fixed up.
--
function premake.bake.postprocess(prj, cfg)
cfg.project = prj
cfg.shortname = premake.getconfigname(cfg.name, cfg.platform, true)
cfg.longname = premake.getconfigname(cfg.name, cfg.platform)
-- set the project location, if not already set
cfg.location = cfg.location or cfg.basedir
-- figure out the target system
local platform = premake.platforms[cfg.platform]
if platform.iscrosscompiler then
cfg.system = cfg.platform
else
cfg.system = os.get()
end
-- adjust the kind as required by the target system
if cfg.kind == "Bundle"
and _ACTION ~= "gmake"
and (_ACTION ~= "ninja" and (not prj.options or not prj.options.SkipBundling))
and not _ACTION:match("xcode[0-9]") then
cfg.kind = "SharedLib"
end
if cfg.kind == "SharedLib" and platform.nosharedlibs then
cfg.kind = "StaticLib"
end
local removefiles = cfg.removefiles
if _ACTION == 'gmake' or _ACTION == 'ninja' then
removefiles = table.join(removefiles, cfg.excludes)
end
-- build a table of removed files, indexed by file name
local removefilesDict = {}
for _, fname in ipairs(removefiles) do
removefilesDict[fname] = true
end
-- remove excluded files from the file list
local files = {}
for _, fname in ipairs(cfg.files) do
if removefilesDict[fname] == nil then
table.insert(files, fname)
end
end
cfg.files = files
-- remove excluded files from the project's allfiles list, and
-- un-duplify it
local allfiles = {}
local allfilesDict = {}
if cfg.allfiles ~= nil then
for _, fname in ipairs(cfg.allfiles) do
if allfilesDict[fname] == nil then
if removefilesDict[fname] == nil then
allfilesDict[fname] = true
table.insert(allfiles, fname)
end
end
end
end
cfg.allfiles = allfiles
-- fixup the data
for name, field in pairs(premake.fields) do
-- re-key flag fields for faster lookups
if field.isflags then
local values = cfg[name]
for _, flag in ipairs(values) do values[flag] = true end
end
end
-- build configuration objects for all files
-- TODO: can I build this as a tree instead, and avoid the extra
-- step of building it later?
local cfgfields = {
{"__fileconfigs", cfg.files},
{"__allfileconfigs", cfg.allfiles},
}
for _, cfgfield in ipairs(cfgfields) do
local fieldname = cfgfield[1]
local field = cfgfield[2]
cfg[fieldname] = { }
for _, fname in ipairs(field) do
local fcfg = {}
-- Only do this if the script has called enablefilelevelconfig()
if premake._filelevelconfig then
cfg.terms.required = fname:lower()
for _, blk in ipairs(cfg.project.blocks) do
-- BK - `iskeywordsmatch` call is super slow for large projects...
if (premake.iskeywordsmatch(blk.keywords, cfg.terms)) then
mergeobject(fcfg, blk)
end
end
end
-- add indexed by name and integer
-- TODO: when everything is converted to trees I won't need
-- to index by name any longer
fcfg.name = fname
cfg[fieldname][fname] = fcfg
table.insert(cfg[fieldname], fcfg)
end
end
end

View File

@@ -0,0 +1,103 @@
--
-- cmdline.lua
-- Functions to define and handle command line actions and options.
-- Copyright (c) 2002-2011 Jason Perkins and the Premake project
--
--
-- Built-in command line options
--
newoption
{
trigger = "cc",
value = "VALUE",
description = "Choose a C/C++ compiler set",
allowed = {
{ "gcc", "GNU GCC (gcc/g++)" },
{ "ow", "OpenWatcom" },
{ "ghs", "Green Hills Software" },
}
}
newoption
{
trigger = "dotnet",
value = "VALUE",
description = "Choose a .NET compiler set",
allowed = {
{ "msnet", "Microsoft .NET (csc)" },
{ "mono", "Novell Mono (mcs)" },
{ "pnet", "Portable.NET (cscc)" },
}
}
newoption
{
trigger = "file",
value = "FILE",
description = "Read FILE as a Premake script; default is 'premake4.lua'"
}
newoption
{
trigger = "help",
description = "Display this information"
}
newoption
{
trigger = "os",
value = "VALUE",
description = "Generate files for a different operating system",
allowed = {
{ "bsd", "OpenBSD, NetBSD, or FreeBSD" },
{ "linux", "Linux" },
{ "macosx", "Apple Mac OS X" },
{ "solaris", "Solaris" },
{ "windows", "Microsoft Windows" },
}
}
newoption
{
trigger = "platform",
value = "VALUE",
description = "Add target architecture (if supported by action)",
allowed = {
{ "x32", "32-bit" },
{ "x64", "64-bit" },
{ "universal", "Mac OS X Universal, 32- and 64-bit" },
{ "universal32", "Mac OS X Universal, 32-bit only" },
{ "universal64", "Mac OS X Universal, 64-bit only" },
{ "ps3", "Playstation 3" },
{ "orbis", "Playstation 4" },
{ "xbox360", "Xbox 360" },
{ "durango", "Xbox One" },
{ "ARM", "ARM" },
{ "ARM64", "ARM64" },
{ "PowerPC", "PowerPC" },
{ "nx32", "Nintendo Switch, 32-bit only" },
{ "nx64", "Nintendo Switch, 64-bit only" },
}
}
newoption
{
trigger = "scripts",
value = "path",
description = "Search for additional scripts on the given path"
}
newoption
{
trigger = "debug-profiler",
description = "GENie script generation profiler."
}
newoption
{
trigger = "version",
description = "Display version information"
}

View File

@@ -0,0 +1,107 @@
--
-- configs.lua
--
-- Functions for working with configuration objects (which can include
-- projects and solutions).
--
-- Copyright (c) 2008-2011 Jason Perkins and the Premake project
--
premake.config = { }
local config = premake.config
--
-- 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 premake.config.isdebugbuild(cfg)
-- If any of the specific runtime flags are set
if cfg.flags.DebugRuntime then
return true
end
if cfg.flags.ReleaseRuntime then
return false
end
-- If any of the optimize flags are set, it's a release a build
if cfg.flags.Optimize or cfg.flags.OptimizeSize or cfg.flags.OptimizeSpeed then
return false
end
-- If symbols are not defined, it's a release build
if not cfg.flags.Symbols then
return false
end
return true
end
--
-- Return an iterator over each file included in this configuration.
--
function premake.config.eachfile(cfg)
local i = 0
local t = cfg.files
return function ()
i = i + 1
if (i <= #t) then
local fcfg = cfg.__fileconfigs[t[i]]
fcfg.vpath = premake.project.getvpath(cfg.project, fcfg.name)
return fcfg
end
end
end
--
-- Determines if this configuration can be linked incrementally.
--
function premake.config.isincrementallink(cfg)
if cfg.kind == "StaticLib" then
return false
end
return not config.islinkeroptimizedbuild(cfg.flags) and not cfg.flags.NoIncrementalLink
end
--
-- Determine if this configuration uses one of the optimize flags.
--
function premake.config.isoptimizedbuild(flags)
return flags.Optimize or flags.OptimizeSize or flags.OptimizeSpeed
end
--
-- Determine if this configuration uses one of the optimize flags.
-- Optimized builds get different treatment, such as full linking
-- instead of incremental.
--
function premake.config.islinkeroptimizedbuild(flags)
return config.isoptimizedbuild(flags) and not flags.NoOptimizeLink
end
--
-- Determines if this configuration uses edit and continue.
--
function premake.config.iseditandcontinue(cfg)
if cfg.flags.NoEditAndContinue
or cfg.flags.Managed
or (cfg.kind ~= "StaticLib" and not config.isincrementallink(cfg))
or config.islinkeroptimizedbuild(cfg.flags) then
return false
end
return true
end

View File

@@ -0,0 +1,209 @@
--
-- globals.lua
-- Global tables and variables, replacements and extensions to Lua's global functions.
-- Copyright (c) 2002-2009 Jason Perkins and the Premake project
--
-- A top-level namespace for support functions
premake = { }
-- The list of supported platforms; also update list in cmdline.lua
premake.platforms =
{
Native =
{
cfgsuffix = "",
},
x32 =
{
cfgsuffix = "32",
},
x64 =
{
cfgsuffix = "64",
},
Universal =
{
cfgsuffix = "univ",
},
Universal32 =
{
cfgsuffix = "univ32",
},
Universal64 =
{
cfgsuffix = "univ64",
},
PS3 =
{
cfgsuffix = "ps3",
iscrosscompiler = true,
nosharedlibs = true,
namestyle = "PS3",
},
WiiDev =
{
cfgsuffix = "wii",
iscrosscompiler = true,
namestyle = "PS3",
},
Xbox360 =
{
cfgsuffix = "xbox360",
iscrosscompiler = true,
namestyle = "windows",
},
PowerPC =
{
cfgsuffix = "ppc",
iscrosscompiler = true,
},
ARM =
{
cfgsuffix = "ARM",
iscrosscompiler = true,
},
ARM64 =
{
cfgsuffix = "ARM64",
iscrosscompiler = true,
},
Orbis =
{
cfgsuffix = "orbis",
iscrosscompiler = true,
namestyle = "Orbis",
},
Durango =
{
cfgsuffix = "durango",
iscrosscompiler = true,
nosharedlibs = true,
namestyle = "windows",
},
TegraAndroid =
{
cfgsuffix = "tegraandroid",
iscrosscompiler = true,
namestyle = "TegraAndroid",
},
NX32 =
{
cfgsuffix = "nx32",
iscrosscompiler = true,
namestyle = "NX",
},
NX64 =
{
cfgsuffix = "nx64",
iscrosscompiler = true,
namestyle = "NX",
},
Emscripten =
{
cfgsuffix = "emscripten",
iscrosscompiler = true,
nosharedlibs = true,
namestyle = "Emscripten",
},
}
--
-- A replacement for Lua's built-in dofile() function, this one sets the
-- current working directory to the script's location, enabling script-relative
-- referencing of other files and resources.
--
local builtin_dofile = dofile
function dofile(fname)
-- remember the current working directory and file; I'll restore it shortly
local oldcwd = os.getcwd()
local oldfile = _SCRIPT
-- if the file doesn't exist, check the search path
if (not os.isfile(fname)) then
local path = os.pathsearch(fname, _OPTIONS["scripts"], os.getenv("PREMAKE_PATH"))
if (path) then
fname = path.."/"..fname
end
end
-- use the absolute path to the script file, to avoid any file name
-- ambiguity if an error should arise
_SCRIPT = path.getabsolute(fname)
-- switch the working directory to the new script location
local newcwd = path.getdirectory(_SCRIPT)
os.chdir(newcwd)
-- run the chunk. How can I catch variable return values?
local a, b, c, d, e, f = builtin_dofile(_SCRIPT)
-- restore the previous working directory when done
_SCRIPT = oldfile
os.chdir(oldcwd)
return a, b, c, d, e, f
end
--
-- "Immediate If" - returns one of the two values depending on the value of expr.
--
function iif(expr, trueval, falseval)
if (expr) then
return trueval
else
return falseval
end
end
--
-- A shortcut for including another build file, often used for projects.
--
function include(fname)
local dir, name = premake.findDefaultScript(fname, false)
if dir ~= nil then
return dofile(dir .. "/" .. name)
end
return nil
end
--
-- A shortcut for printing formatted output.
--
function printf(msg, ...)
local arg={...}
print(string.format(msg, table.unpack(arg)))
end
--
-- An extended type API to identify project object types by reading the
-- "__type" field from the metatable.
--
function typex(t)
local mt = getmetatable(t)
if (mt) then
if (mt.__type) then
return mt.__type
end
end
return type(t)
end

View File

@@ -0,0 +1,48 @@
--
-- help.lua
-- User help, displayed on /help option.
-- Copyright (c) 2002-2008 Jason Perkins and the Premake project
--
function premake.showhelp()
-- display the basic usage
printf("")
printf("Usage: genie [options] action [arguments]")
printf("")
-- display all options
printf("OPTIONS")
printf("")
for option in premake.option.each() 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(" --%-15s %s", trigger, description)
if (option.allowed) then
for _, value in ipairs(option.allowed) do
printf(" %-14s %s", value[1], value[2])
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://github.com/bkaradzic/genie")
end

View File

@@ -0,0 +1,291 @@
-- Copyright (c) 2013 Enrique García Cota
--
-- Permission is hereby granted, free of charge, to any person obtaining a
-- copy of this 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 this 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.
-- Apostrophizes the string if it has quotes, but not aphostrophes
-- Otherwise, it returns a regular quoted string
local function smartQuote(str)
if str:match('"') and not str:match("'") then
return "'" .. str .. "'"
end
return '"' .. str:gsub('"', '\\"') .. '"'
end
local controlCharsTranslation = {
["\a"] = "\\a", ["\b"] = "\\b", ["\f"] = "\\f", ["\n"] = "\\n",
["\r"] = "\\r", ["\t"] = "\\t", ["\v"] = "\\v"
}
local function escapeChar(c) return controlCharsTranslation[c] end
local function escape(str)
local result = str:gsub("\\", "\\\\"):gsub("(%c)", escapeChar)
return result
end
local function isIdentifier(str)
return type(str) == 'string' and str:match( "^[_%a][_%a%d]*$" )
end
local function isArrayKey(k, length)
return type(k) == 'number' and 1 <= k and k <= length
end
local function isDictionaryKey(k, length)
return not isArrayKey(k, length)
end
local defaultTypeOrders = {
['number'] = 1, ['boolean'] = 2, ['string'] = 3, ['table'] = 4,
['function'] = 5, ['userdata'] = 6, ['thread'] = 7
}
local function sortKeys(a, b)
local ta, tb = type(a), type(b)
-- strings and numbers are sorted numerically/alphabetically
if ta == tb and (ta == 'string' or ta == 'number') then return a < b end
local dta, dtb = defaultTypeOrders[ta], defaultTypeOrders[tb]
-- Two default types are compared according to the defaultTypeOrders table
if dta and dtb then return defaultTypeOrders[ta] < defaultTypeOrders[tb]
elseif dta then return true -- default types before custom ones
elseif dtb then return false -- custom types after default ones
end
-- custom types are sorted out alphabetically
return ta < tb
end
local function getDictionaryKeys(t)
local keys, length = {}, #t
for k,_ in pairs(t) do
if isDictionaryKey(k, length) then table.insert(keys, k) end
end
table.sort(keys, sortKeys)
return keys
end
local function getToStringResultSafely(t, mt)
local __tostring = type(mt) == 'table' and rawget(mt, '__tostring')
local str, ok
if type(__tostring) == 'function' then
ok, str = pcall(__tostring, t)
str = ok and str or 'error: ' .. tostring(str)
end
if type(str) == 'string' and #str > 0 then return str end
end
local maxIdsMetaTable = {
__index = function(self, typeName)
rawset(self, typeName, 0)
return 0
end
}
local idsMetaTable = {
__index = function (self, typeName)
local col = setmetatable({}, {__mode = "kv"})
rawset(self, typeName, col)
return col
end
}
local function countTableAppearances(t, tableAppearances)
tableAppearances = tableAppearances or setmetatable({}, {__mode = "k"})
if type(t) == 'table' then
if not tableAppearances[t] then
tableAppearances[t] = 1
for k,v in pairs(t) do
countTableAppearances(k, tableAppearances)
countTableAppearances(v, tableAppearances)
end
countTableAppearances(getmetatable(t), tableAppearances)
else
tableAppearances[t] = tableAppearances[t] + 1
end
end
return tableAppearances
end
local function parse_filter(filter)
if type(filter) == 'function' then return filter end
-- not a function, so it must be a table or table-like
filter = type(filter) == 'table' and filter or {filter}
local dictionary = {}
for _,v in pairs(filter) do dictionary[v] = true end
return function(x) return dictionary[x] end
end
local function makePath(path, key)
local newPath, len = {}, #path
for i=1, len do newPath[i] = path[i] end
newPath[len+1] = key
return newPath
end
-------------------------------------------------------------------
function inspect(rootObject, options)
options = options or {}
local depth = options.depth or math.huge
local filter = parse_filter(options.filter or {})
local tableAppearances = countTableAppearances(rootObject)
local buffer = {}
local maxIds = setmetatable({}, maxIdsMetaTable)
local ids = setmetatable({}, idsMetaTable)
local level = 0
local blen = 0 -- buffer length
local function puts(...)
local args = {...}
for i=1, #args do
blen = blen + 1
buffer[blen] = tostring(args[i])
end
end
local function down(f)
level = level + 1
f()
level = level - 1
end
local function tabify()
puts("\n", string.rep(" ", level))
end
local function commaControl(needsComma)
if needsComma then puts(',') end
return true
end
local function alreadyVisited(v)
return ids[type(v)][v] ~= nil
end
local function getId(v)
local tv = type(v)
local id = ids[tv][v]
if not id then
id = maxIds[tv] + 1
maxIds[tv] = id
ids[tv][v] = id
end
return id
end
local putValue -- forward declaration that needs to go before putTable & putKey
local function putKey(k)
if isIdentifier(k) then return puts(k) end
puts( "[" )
putValue(k, {})
puts("]")
end
local function putTable(t, path)
if alreadyVisited(t) then
puts('<table ', getId(t), '>')
elseif level >= depth then
puts('{...}')
else
if tableAppearances[t] > 1 then puts('<', getId(t), '>') end
local dictKeys = getDictionaryKeys(t)
local length = #t
local mt = getmetatable(t)
local to_string_result = getToStringResultSafely(t, mt)
puts('{')
down(function()
if to_string_result then
puts(' -- ', escape(to_string_result))
if length >= 1 then tabify() end -- tabify the array values
end
local needsComma = false
for i=1, length do
needsComma = commaControl(needsComma)
puts(' ')
putValue(t[i], makePath(path, i))
end
for _,k in ipairs(dictKeys) do
needsComma = commaControl(needsComma)
tabify()
putKey(k)
puts(' = ')
putValue(t[k], makePath(path, k))
end
if mt then
needsComma = commaControl(needsComma)
tabify()
puts('<metatable> = ')
putValue(mt, makePath(path, '<metatable>'))
end
end)
if #dictKeys > 0 or mt then -- dictionary table. Justify closing }
tabify()
elseif length > 0 then -- array tables have one extra space before closing }
puts(' ')
end
puts('}')
end
end
-- putvalue is forward-declared before putTable & putKey
putValue = function(v, path)
if filter(v, path) then
puts('<filtered>')
else
local tv = type(v)
if tv == 'string' then
puts(smartQuote(escape(v)))
elseif tv == 'number' or tv == 'boolean' or tv == 'nil' then
puts(tostring(v))
elseif tv == 'table' then
putTable(v, path)
else
puts('<',tv,' ',getId(v),'>')
end
end
end
putValue(rootObject, {})
return table.concat(buffer)
end
function printtable(name, table)
print("table: ", name, inspect(table), "\n")
end
function printstack()
print(debug.traceback(), "\n")
end

View File

@@ -0,0 +1,136 @@
--
-- io.lua
-- Additions to the I/O namespace.
-- Copyright (c) 2008-2009 Jason Perkins and the Premake project
--
io.eol = "\n"
io.indent = "\t"
io.indentLevel = 0
-- default escaper function
local function _escaper(v) return v end
_esc = _escaper
--
-- Prepare to capture the output from all subsequent calls to io.printf(),
-- used for automated testing of the generators. Returns the previously
-- captured text.
--
function io.capture()
local prev = io.captured
io.captured = ''
return prev
end
--
-- Returns the captured text and stops capturing, optionally restoring a
-- previous capture.
--
function io.endcapture(restore)
local captured = io.captured
io.captured = restore
return captured
end
--
-- Open an overload of the io.open() function, which will create any missing
-- subdirectories in the filename if "mode" is set to writeable.
--
local builtin_open = io.open
function io.open(fname, mode)
if (mode) then
if (mode:find("w")) then
local dir = path.getdirectory(fname)
ok, err = os.mkdir(dir)
if (not ok) then
error(err, 0)
end
end
end
return builtin_open(fname, mode)
end
--
-- A shortcut for printing formatted output to an output stream.
--
function io.printf(msg, ...)
local arg={...}
if not io.eol then
io.eol = "\n"
end
if not io.indent then
io.indent = "\t"
end
if type(msg) == "number" then
s = string.rep(io.indent, msg) .. string.format(table.unpack(arg))
else
s = string.format(msg, table.unpack(arg))
end
if io.captured then
io.captured = io.captured .. s .. io.eol
else
io.write(s)
io.write(io.eol)
end
end
--
-- Write a formatted string to the exported file, after passing all
-- arguments (except for the first, which is the formatting string)
-- through io.esc().
--
function io.xprintf(msg, ...)
local arg = {...}
for i = 1, #arg do
arg[i] = io.esc(arg[i])
end
io.printf(msg, unpack(arg))
end
--
-- Handle escaping of strings for various outputs
--
function io.esc(value)
if type(value) == "table" then
local result = {}
local n = #value
for i = 1, n do
table.insert(result, io.esc(value[i]))
end
return result
end
return _esc(value or "")
end
--
-- Set a new string escaping function
--
function io.escaper(func)
_esc = func or _escaper
end
--
-- Because I use io.printf() so often in the generators, create a terse shortcut
-- for it. This saves me typing, and also reduces the size of the executable.
--
_p = io.printf
_x = io.xprintf

View File

@@ -0,0 +1,16 @@
iter = {}
-- sortByKeys iterates over the table where the keys are in sort order
function iter.sortByKeys(arr, f)
local a = table.keys(arr)
table.sort(a, f)
local i = 0
return function()
i = i + 1
if a[i] ~= nil then
return a[i], arr[a[i]]
end
end
end

View File

@@ -0,0 +1,113 @@
--
-- option.lua
-- Work with the list of registered options.
-- Copyright (c) 2002-2009 Jason Perkins and the Premake project
--
premake.option = { }
--
-- The list of registered options.
--
premake.option.list = { }
--
-- Register a new option.
--
-- @param opt
-- The new option object.
--
function premake.option.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
premake.option.list[opt.trigger] = opt
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 premake.option.get(name)
return premake.option.list[name]
end
--
-- Iterator for the list of options.
--
function premake.option.each()
-- sort the list by trigger
local keys = { }
for _, option in pairs(premake.option.list) do
table.insert(keys, option.trigger)
end
table.sort(keys)
local i = 0
return function()
i = i + 1
return premake.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 premake.option.validate(values)
for key, value in pairs(values) do
-- does this option exist
local opt = premake.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,298 @@
--
-- os.lua
-- Additions to the OS namespace.
-- Copyright (c) 2002-2011 Jason Perkins and the Premake project
--
--
-- Same as os.execute(), but accepts string formatting arguments.
--
function os.executef(cmd, ...)
local arg={...}
cmd = string.format(cmd, table.unpack(arg))
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 = { }
local file = io.open(conf_file)
-- Handle missing ld.so.conf (BSDs) gracefully
if file == nil then
return dirs
end
for line in file:lines() 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
function os.findlib(libname)
local path, formats
-- assemble a search path, depending on the platform
if os.is("windows") then
formats = { "%s.dll", "%s" }
path = os.getenv("PATH")
else
if os.is("macosx") then
formats = { "lib%s.dylib", "%s.dylib" }
path = os.getenv("DYLD_LIBRARY_PATH")
else
formats = { "lib%s.so", "%s.so" }
path = os.getenv("LD_LIBRARY_PATH") or ""
for _, v in ipairs(parse_ld_so_conf("/etc/ld.so.conf")) do
path = path .. ":" .. v
end
end
table.insert(formats, "%s")
path = path or ""
if os.is64bit() then
path = path .. ":/lib64:/usr/lib64/:usr/local/lib64"
end
path = path .. ":/lib:/usr/lib:/usr/local/lib"
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
--
-- Retrieve the current operating system ID string.
--
function os.get()
return _OPTIONS.os or _OS
end
--
-- Check the current operating system; may be set with the /os command line flag.
--
function os.is(id)
return (os.get():lower() == id:lower())
end
--
-- Determine if the current system is running a 64-bit architecture
--
local _64BitHostTypes = {
"x86_64",
"ia64",
"amd64",
"ppc64",
"powerpc64",
"sparc64",
"arm64"
}
function os.is64bit()
-- Call the native code implementation. If this returns true then
-- we're 64-bit, otherwise do more checking locally
if (os._is64bit()) then
return true
end
-- Identify the system
local arch = ""
if _OS == "windows" then
arch = os.getenv("PROCESSOR_ARCHITECTURE")
elseif _OS == "macosx" then
arch = os.outputof("echo $HOSTTYPE")
else
arch = os.outputof("uname -m")
end
if nil ~= arch then
-- Check our known 64-bit identifiers
arch = arch:lower()
for _, hosttype in ipairs(_64BitHostTypes) do
if arch:find(hosttype) then
return true
end
end
end
return false
end
--
-- The os.matchdirs() and os.matchfiles() functions
--
local function domatch(result, mask, wantfiles)
-- need to remove extraneous path info from the mask to ensure a match
-- against the paths returned by the OS. Haven't come up with a good
-- way to do it yet, so will handle cases as they come up
if mask:startswith("./") then
mask = mask:sub(3)
end
-- strip off any leading directory information to find out
-- where the search should take place
local basedir = mask
local starpos = mask:find("%*")
if starpos then
basedir = basedir:sub(1, starpos - 1)
end
basedir = path.getdirectory(basedir)
if (basedir == ".") then basedir = "" end
-- recurse into subdirectories?
local recurse = mask:find("**", nil, true)
-- convert mask to a Lua pattern
mask = path.wildcards(mask)
local function matchwalker(basedir)
local wildcard = path.join(basedir, "*")
-- retrieve files from OS and test against mask
local m = os.matchstart(wildcard)
while (os.matchnext(m)) do
local isfile = os.matchisfile(m)
if ((wantfiles and isfile) or (not wantfiles and not isfile)) then
local fname = path.join(basedir, os.matchname(m))
if fname:match(mask) == fname then
table.insert(result, fname)
end
end
end
os.matchdone(m)
-- check subdirectories
if recurse then
m = os.matchstart(wildcard)
while (os.matchnext(m)) do
if not os.matchisfile(m) then
local dirname = os.matchname(m)
matchwalker(path.join(basedir, dirname))
end
end
os.matchdone(m)
end
end
matchwalker(basedir)
end
function os.matchdirs(...)
local arg={...}
local result = { }
for _, mask in ipairs(arg) do
domatch(result, mask, false)
end
return result
end
function os.matchfiles(...)
local arg={...}
local result = { }
for _, mask in ipairs(arg) do
domatch(result, mask, true)
end
return result
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)
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.
--
function os.outputof(cmd)
local pipe = io.popen(cmd)
local result = pipe:read('*a')
pipe:close()
return result
end
--
-- Remove a directory, along with any contained files or subdirectories.
--
local builtin_rmdir = os.rmdir
function os.rmdir(p)
-- recursively remove subdirectories
local dirs = os.matchdirs(p .. "/*")
for _, dname in ipairs(dirs) do
os.rmdir(dname)
end
-- remove any files
local files = os.matchfiles(p .. "/*")
for _, fname in ipairs(files) do
os.remove(fname)
end
-- remove this directory
builtin_rmdir(p)
end

View File

@@ -0,0 +1,400 @@
--
-- path.lua
-- Path manipulation functions.
-- Copyright (c) 2002-2010 Jason Perkins and the Premake project
--
--
-- 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 path, without any extension.
--
function path.removeext(name)
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)
local i = p:findlast(".", true)
if (i) then
return p:sub(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 the common base directory of two paths.
--
function path.getcommonbasedir(a, b)
a = path.getdirectory(a)..'/'
b = path.getdirectory(b)..'/'
-- find the common leading directories
local idx = 0
while (true) do
local tst = a:find('/', idx + 1, true)
if tst then
if a:sub(1,tst) == b:sub(1,tst) then
idx = tst
else
break
end
else
break
end
end
-- idx is the index of the last sep before path string 'a' ran out or didn't match.
local result = ''
if idx > 1 then
result = a:sub(1, idx - 1) -- Remove the trailing slash to be consistent with other functions.
end
return result
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 a C/C++ source code file. This check
-- is 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.iscfile(fname)
return path.hasextension(fname, { ".c", ".m" })
end
function path.iscppfile(fname)
return path.hasextension(fname, { ".cc", ".cpp", ".cxx", ".c++", ".c", ".m", ".mm" })
end
function path.iscxfile(fname)
return path.hasextension(fname, ".cx")
end
function path.isobjcfile(fname)
return path.hasextension(fname, { ".m", ".mm" })
end
function path.iscppheader(fname)
return path.hasextension(fname, { ".h", ".hh", ".hpp", ".hxx" })
end
function path.iscppmodule(fname)
return path.hasextension(fname, { ".ixx", ".cppm" })
end
function path.isappxmanifest(fname)
return path.hasextension(fname, ".appxmanifest")
end
function path.isandroidbuildfile(fname)
return path.getname(fname) == "AndroidManifest.xml"
end
function path.isnatvis(fname)
return path.hasextension(fname, ".natvis")
end
function path.isasmfile(fname)
return path.hasextension(fname, { ".asm", ".s", ".S" })
end
function path.isvalafile(fname)
return path.hasextension(fname, ".vala")
end
function path.isgresource(fname)
local ending = ".gresource.xml"
return ending == "" or fname:sub(-#ending) == ending
end
function path.isswiftfile(fname)
return path.hasextension(fname, ".swift")
end
function path.issourcefile(fname)
return path.iscfile(fname)
or path.iscppfile(fname)
or path.iscxfile(fname)
or path.isasmfile(fname)
or path.isvalafile(fname)
or path.isswiftfile(fname)
end
function path.issourcefilevs(fname)
return path.hasextension(fname, { ".cc", ".cpp", ".cxx", ".c++", ".c" })
or path.iscxfile(fname)
or path.iscppmodule(fname)
end
--
-- Returns true if the filename represents a compiled object file. This check
-- is used to support object files in the "files" list for archiving.
--
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 image file.
--
function path.isimagefile(fname)
local extensions = { ".png" }
local ext = path.getextension(fname):lower()
return table.contains(extensions, ext)
end
--
-- Join one or more pieces of a path together into a single path.
--
-- @param ...
-- One or more path strings.
-- @return
-- The joined path.
--
function path.join(...)
local arg={...}
local numargs = select("#", ...)
if numargs == 0 then
return "";
end
local allparts = {}
for i = numargs, 1, -1 do
local part = select(i, ...)
if part and #part > 0 and part ~= "." then
-- trim off trailing slashes
while part:endswith("/") do
part = part:sub(1, -2)
end
table.insert(allparts, 1, part)
if path.isabsolute(part) then
break
end
end
end
return table.concat(allparts, "/")
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
--
-- Convert the separators in a path from one form to another. If `sep`
-- is nil, then a platform-specific separator is used.
--
function path.translate(p, sep)
if (type(p) == "table") then
local result = { }
for _, value in ipairs(p) do
table.insert(result, path.translate(value))
end
return result
else
if (not sep) then
if (os.is("windows")) then
sep = "\\"
else
sep = "/"
end
end
local result = p:gsub("[/\\]", sep)
return result
end
end
--
-- 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.
--
function path.wildcards(pattern)
-- Escape characters that have special meanings in Lua patterns
pattern = pattern:gsub("([%+%.%-%^%$%(%)%%])", "%%%1")
-- Replace wildcard patterns with special placeholders so I don't
-- have competing star replacements to worry about
pattern = pattern:gsub("%*%*", "\001")
pattern = pattern:gsub("%*", "\002")
-- Replace the placeholders with their Lua patterns
pattern = pattern:gsub("\001", ".*")
pattern = pattern:gsub("\002", "[^/]*")
return pattern
end
--
-- remove any dot ("./", "../") patterns from the start of the path
--
function path.trimdots(p)
local changed
repeat
changed = true
if p:startswith("./") then
p = p:sub(3)
elseif p:startswith("../") then
p = p:sub(4)
else
changed = false
end
until not changed
return p
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

View File

@@ -0,0 +1,120 @@
--
-- premake.lua
-- High-level processing functions.
-- Copyright (c) 2002-2009 Jason Perkins and the Premake project
--
premake._filelevelconfig = false
premake._checkgenerate = true
--
-- Open a file for output, and call a function to actually do the writing.
-- Used by the actions to generate solution and project files.
--
-- @param obj
-- A solution or project object; will be based to the callback function.
-- @param filename
-- The output filename; see the docs for premake.project.getfilename()
-- for the expected format.
-- @param callback
-- The function responsible for writing the file, should take a solution
-- or project as a parameters.
--
function premake.generate(obj, filename, callback)
local prev = io.capture()
local abort = (callback(obj) == false)
local new = io.endcapture(prev)
if abort then
premake.stats.num_skipped = premake.stats.num_skipped + 1
return
end
filename = premake.project.getfilename(obj, filename)
if (premake._checkgenerate) then
local delta = false
local f, err = io.open(filename, "rb")
if (not f) then
if string.find(err, "No such file or directory") then
delta = true
else
error(err, 0)
end
else
local existing = f:read("*all")
if existing ~= new then
delta = true
end
f:close()
end
if delta then
printf("Generating %q", filename)
local f, err = io.open(filename, "wb")
if (not f) then
error(err, 0)
end
f:write(new)
f:close()
premake.stats.num_generated = premake.stats.num_generated + 1
else
-- printf("Skipping %s as its contents would not change.", filename)
premake.stats.num_skipped = premake.stats.num_skipped + 1
end
else
printf("Generating %q", filename)
local f, err = io.open(filename, "wb")
if (not f) then
error(err, 0)
end
f:write(new)
f:close()
premake.stats.num_generated = premake.stats.num_generated + 1
end
end
--
-- Finds a valid premake build file in the specified directory
-- Used by both the main genie process, and include commands
--
-- @param dir
-- The path in which to start looking for the script
-- @param search_upwards
-- When the script was not found in the specified directory, does the
-- script need to look upwards in the file system
--
function premake.findDefaultScript(dir, search_upwards)
search_upwards = search_upwards or true
local last = ""
while dir ~= last do
for _, name in ipairs({ "genie.lua", "solution.lua", "premake4.lua" }) do
local script0 = dir .. "/" .. name
if (os.isfile(script0)) then
return dir, name
end
local script1 = dir .. "/scripts/" .. name
if (os.isfile(script1)) then
return dir .. "/scripts/", name
end
end
last = dir
dir = path.getabsolute(dir .. "/..")
if dir == "." or not search_upwards then break end
end
return nil, nil
end

View File

@@ -0,0 +1,613 @@
--
-- http://lua-users.org/wiki/PepperfishProfiler
--
-- == Introduction ==
--
-- Note that this requires os.clock(), debug.sethook(),
-- and debug.getinfo() or your equivalent replacements to
-- be available if this is an embedded application.
--
-- Example usage:
--
-- profiler = newProfiler()
-- profiler:start()
--
-- < call some functions that take time >
--
-- profiler:stop()
--
-- local outfile = io.open( "profile.txt", "w+" )
-- profiler:report( outfile )
-- outfile:close()
--
-- == Optionally choosing profiling method ==
--
-- The rest of this comment can be ignored if you merely want a good profiler.
--
-- newProfiler(method, sampledelay):
--
-- If method is omitted or "time", will profile based on real performance.
-- optionally, frequency can be provided to control the number of opcodes
-- per profiling tick. By default this is 100000, which (on my system) provides
-- one tick approximately every 2ms and reduces system performance by about 10%.
-- This can be reduced to increase accuracy at the cost of performance, or
-- increased for the opposite effect.
--
-- If method is "call", will profile based on function calls. Frequency is
-- ignored.
--
--
-- "time" may bias profiling somewhat towards large areas with "simple opcodes",
-- as the profiling function (which introduces a certain amount of unavoidable
-- overhead) will be called more often. This can be minimized by using a larger
-- sample delay - the default should leave any error largely overshadowed by
-- statistical noise. With a delay of 1000 I was able to achieve inaccuray of
-- approximately 25%. Increasing the delay to 100000 left inaccuracy below my
-- testing error.
--
-- "call" may bias profiling heavily towards areas with many function calls.
-- Testing found a degenerate case giving a figure inaccurate by approximately
-- 20,000%. (Yes, a multiple of 200.) This is, however, more directly comparable
-- to common profilers (such as gprof) and also gives accurate function call
-- counts, which cannot be retrieved from "time".
--
-- I strongly recommend "time" mode, and it is now the default.
--
-- == History ==
--
-- 2008-09-16 - Time-based profiling and conversion to Lua 5.1
-- by Ben Wilhelm ( zorba-pepperfish@pavlovian.net ).
-- Added the ability to optionally choose profiling methods, along with a new
-- profiling method.
--
-- Converted to Lua 5, a few improvements, and
-- additional documentation by Tom Spilman ( tom@sickheadgames.com )
--
-- Additional corrections and tidying by original author
-- Daniel Silverstone ( dsilvers@pepperfish.net )
--
-- == Status ==
--
-- Daniel Silverstone is no longer using this code, and judging by how long it's
-- been waiting for Lua 5.1 support, I don't think Tom Spilman is either. I'm
-- perfectly willing to take on maintenance, so if you have problems or
-- questions, go ahead and email me :)
-- -- Ben Wilhelm ( zorba-pepperfish@pavlovian.net ) '
--
-- == Copyright ==
--
-- Lua profiler - Copyright Pepperfish 2002,2003,2004
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this 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, 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 this 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.
--
--
-- All profiler related stuff is stored in the top level table '_profiler'
--
_profiler = {}
--
-- newProfiler() creates a new profiler object for managing
-- the profiler and storing state. Note that only one profiler
-- object can be executing at one time.
--
function newProfiler(variant, sampledelay)
if _profiler.running then
print("Profiler already running.")
return
end
variant = variant or "time"
if variant ~= "time" and variant ~= "call" then
print("Profiler method must be 'time' or 'call'.")
return
end
local newprof = {}
for k,v in pairs(_profiler) do
newprof[k] = v
end
newprof.variant = variant
newprof.sampledelay = sampledelay or 100000
return newprof
end
--
-- This function starts the profiler. It will do nothing
-- if this (or any other) profiler is already running.
--
function _profiler.start(self)
if _profiler.running then
return
end
-- Start the profiler. This begins by setting up internal profiler state
_profiler.running = self
self.rawstats = {}
self.callstack = {}
if self.variant == "time" then
self.lastclock = os.clock()
debug.sethook( _profiler_hook_wrapper_by_time, "", self.sampledelay )
elseif self.variant == "call" then
debug.sethook( _profiler_hook_wrapper_by_call, "cr" )
else
print("Profiler method must be 'time' or 'call'.")
sys.exit(1)
end
end
--
-- This function stops the profiler. It will do nothing
-- if a profiler is not running, and nothing if it isn't
-- the currently running profiler.
--
function _profiler.stop(self)
if _profiler.running ~= self then
return
end
-- Stop the profiler.
debug.sethook( nil )
_profiler.running = nil
end
--
-- Simple wrapper to handle the hook. You should not
-- be calling this directly. Duplicated to reduce overhead.
--
function _profiler_hook_wrapper_by_call(action)
if _profiler.running == nil then
debug.sethook( nil )
end
_profiler.running:_internal_profile_by_call(action)
end
function _profiler_hook_wrapper_by_time(action)
if _profiler.running == nil then
debug.sethook( nil )
end
_profiler.running:_internal_profile_by_time(action)
end
--
-- This is the main by-function-call function of the profiler and should not
-- be called except by the hook wrapper
--
function _profiler._internal_profile_by_call(self,action)
-- Since we can obtain the 'function' for the item we've had call us, we
-- can use that...
local caller_info = debug.getinfo( 3 )
if caller_info == nil then
print "No caller_info"
return
end
--SHG_LOG("[_profiler._internal_profile] "..(caller_info.name or "<nil>"))
-- Retrieve the most recent activation record...
local latest_ar = nil
if table.getn(self.callstack) > 0 then
latest_ar = self.callstack[table.getn(self.callstack)]
end
-- Are we allowed to profile this function?
local should_not_profile = 0
for k,v in pairs(self.prevented_functions) do
if k == caller_info.func then
should_not_profile = v
end
end
-- Also check the top activation record...
if latest_ar then
if latest_ar.should_not_profile == 2 then
should_not_profile = 2
end
end
-- Now then, are we in 'call' or 'return' ?
-- print("Profile:", caller_info.name, "SNP:", should_not_profile,
-- "Action:", action )
if action == "call" then
-- Making a call...
local this_ar = {}
this_ar.should_not_profile = should_not_profile
this_ar.parent_ar = latest_ar
this_ar.anon_child = 0
this_ar.name_child = 0
this_ar.children = {}
this_ar.children_time = {}
this_ar.clock_start = os.clock()
-- Last thing to do on a call is to insert this onto the ar stack...
table.insert( self.callstack, this_ar )
else
local this_ar = latest_ar
if this_ar == nil then
return -- No point in doing anything if no upper activation record
end
-- Right, calculate the time in this function...
this_ar.clock_end = os.clock()
this_ar.this_time = this_ar.clock_end - this_ar.clock_start
-- Now, if we have a parent, update its call info...
if this_ar.parent_ar then
this_ar.parent_ar.children[caller_info.func] =
(this_ar.parent_ar.children[caller_info.func] or 0) + 1
this_ar.parent_ar.children_time[caller_info.func] =
(this_ar.parent_ar.children_time[caller_info.func] or 0 ) +
this_ar.this_time
if caller_info.name == nil then
this_ar.parent_ar.anon_child =
this_ar.parent_ar.anon_child + this_ar.this_time
else
this_ar.parent_ar.name_child =
this_ar.parent_ar.name_child + this_ar.this_time
end
end
-- Now if we're meant to record information about ourselves, do so...
if this_ar.should_not_profile == 0 then
local inforec = self:_get_func_rec(caller_info.func,1)
inforec.count = inforec.count + 1
inforec.time = inforec.time + this_ar.this_time
inforec.anon_child_time = inforec.anon_child_time + this_ar.anon_child
inforec.name_child_time = inforec.name_child_time + this_ar.name_child
inforec.func_info = caller_info
for k,v in pairs(this_ar.children) do
inforec.children[k] = (inforec.children[k] or 0) + v
inforec.children_time[k] =
(inforec.children_time[k] or 0) + this_ar.children_time[k]
end
end
-- Last thing to do on return is to drop the last activation record...
table.remove( self.callstack, table.getn( self.callstack ) )
end
end
--
-- This is the main by-time internal function of the profiler and should not
-- be called except by the hook wrapper
--
function _profiler._internal_profile_by_time(self,action)
-- we do this first so we add the minimum amount of extra time to this call
local timetaken = os.clock() - self.lastclock
local depth = 3
local at_top = true
local last_caller
local caller = debug.getinfo(depth)
while caller do
if not caller.func then caller.func = "(tail call)" end
if self.prevented_functions[caller.func] == nil then
local info = self:_get_func_rec(caller.func, 1, caller)
info.count = info.count + 1
info.time = info.time + timetaken
if last_caller then
-- we're not the head, so update the "children" times also
if last_caller.name then
info.name_child_time = info.name_child_time + timetaken
else
info.anon_child_time = info.anon_child_time + timetaken
end
info.children[last_caller.func] =
(info.children[last_caller.func] or 0) + 1
info.children_time[last_caller.func] =
(info.children_time[last_caller.func] or 0) + timetaken
end
end
depth = depth + 1
last_caller = caller
caller = debug.getinfo(depth)
end
self.lastclock = os.clock()
end
--
-- This returns a (possibly empty) function record for
-- the specified function. It is for internal profiler use.
--
function _profiler._get_func_rec(self,func,force,info)
-- Find the function ref for 'func' (if force and not present, create one)
local ret = self.rawstats[func]
if ret == nil and force ~= 1 then
return nil
end
if ret == nil then
-- Build a new function statistics table
ret = {}
ret.func = func
ret.count = 0
ret.time = 0
ret.anon_child_time = 0
ret.name_child_time = 0
ret.children = {}
ret.children_time = {}
ret.func_info = info
self.rawstats[func] = ret
end
return ret
end
--
-- This writes a profile report to the output file object. If
-- sort_by_total_time is nil or false the output is sorted by
-- the function time minus the time in it's children.
--
function _profiler.report( self, outfile, sort_by_total_time )
outfile:write
[[Lua Profile output created by profiler.lua. Copyright Pepperfish 2002+
]]
-- This is pretty awful.
local terms = {}
if self.variant == "time" then
terms.capitalized = "Sample"
terms.single = "sample"
terms.pastverb = "sampled"
elseif self.variant == "call" then
terms.capitalized = "Call"
terms.single = "call"
terms.pastverb = "called"
else
assert(false)
end
local total_time = 0
local ordering = {}
for func,record in pairs(self.rawstats) do
table.insert(ordering, func)
end
if sort_by_total_time then
table.sort( ordering,
function(a,b) return self.rawstats[a].time > self.rawstats[b].time end
)
else
table.sort( ordering,
function(a,b)
local arec = self.rawstats[a]
local brec = self.rawstats[b]
local atime = arec.time - (arec.anon_child_time + arec.name_child_time)
local btime = brec.time - (brec.anon_child_time + brec.name_child_time)
return atime > btime
end
)
end
for i=1,#ordering do
local func = ordering[i]
local record = self.rawstats[func]
local thisfuncname = " " .. self:_pretty_name(func) .. " "
if string.len( thisfuncname ) < 42 then
thisfuncname = string.rep( "-", math.floor((42 - string.len(thisfuncname))/2) ) .. thisfuncname
thisfuncname = thisfuncname .. string.rep( "-", 42 - string.len(thisfuncname) )
end
total_time = total_time + ( record.time - ( record.anon_child_time +
record.name_child_time ) )
outfile:write( string.rep( "-", 19 ) .. thisfuncname ..
string.rep( "-", 19 ) .. "\n" )
outfile:write( terms.capitalized.." count: " ..
string.format( "%4d", record.count ) .. "\n" )
outfile:write( "Time spend total: " ..
string.format( "%4.3f", record.time ) .. "s\n" )
outfile:write( "Time spent in children: " ..
string.format("%4.3f",record.anon_child_time+record.name_child_time) ..
"s\n" )
local timeinself =
record.time - (record.anon_child_time + record.name_child_time)
outfile:write( "Time spent in self: " ..
string.format("%4.3f", timeinself) .. "s\n" )
outfile:write( "Time spent per " .. terms.single .. ": " ..
string.format("%4.5f", record.time/record.count) ..
"s/" .. terms.single .. "\n" )
outfile:write( "Time spent in self per "..terms.single..": " ..
string.format( "%4.5f", timeinself/record.count ) .. "s/" ..
terms.single.."\n" )
-- Report on each child in the form
-- Child <funcname> called n times and took a.bs
local added_blank = 0
for k,v in pairs(record.children) do
if self.prevented_functions[k] == nil or
self.prevented_functions[k] == 0
then
if added_blank == 0 then
outfile:write( "\n" ) -- extra separation line
added_blank = 1
end
outfile:write( "Child " .. self:_pretty_name(k) ..
string.rep( " ", 41-string.len(self:_pretty_name(k)) ) .. " " ..
terms.pastverb.." " .. string.format("%6d", v) )
outfile:write( " times. Took " ..
string.format("%4.2f", record.children_time[k] ) .. "s\n" )
end
end
outfile:write( "\n" ) -- extra separation line
outfile:flush()
end
outfile:write( "\n\n" )
outfile:write( "Total time spent in profiled functions: " ..
string.format("%5.3g",total_time) .. "s\n" )
outfile:write( [[
END
]] )
outfile:flush()
end
--
-- This writes the profile to the output file object as
-- loadable Lua source.
--
function _profiler.lua_report(self,outfile)
-- Purpose: Write out the entire raw state in a cross-referenceable form.
local ordering = {}
local functonum = {}
for func,record in pairs(self.rawstats) do
table.insert(ordering, func)
functonum[func] = table.getn(ordering)
end
outfile:write(
"-- Profile generated by profiler.lua Copyright Pepperfish 2002+\n\n" )
outfile:write( "-- Function names\nfuncnames = {}\n" )
for i=1,table.getn(ordering) do
local thisfunc = ordering[i]
outfile:write( "funcnames[" .. i .. "] = " ..
string.format("%q", self:_pretty_name(thisfunc)) .. "\n" )
end
outfile:write( "\n" )
outfile:write( "-- Function times\nfunctimes = {}\n" )
for i=1,table.getn(ordering) do
local thisfunc = ordering[i]
local record = self.rawstats[thisfunc]
outfile:write( "functimes[" .. i .. "] = { " )
outfile:write( "tot=" .. record.time .. ", " )
outfile:write( "achild=" .. record.anon_child_time .. ", " )
outfile:write( "nchild=" .. record.name_child_time .. ", " )
outfile:write( "count=" .. record.count .. " }\n" )
end
outfile:write( "\n" )
outfile:write( "-- Child links\nchildren = {}\n" )
for i=1,table.getn(ordering) do
local thisfunc = ordering[i]
local record = self.rawstats[thisfunc]
outfile:write( "children[" .. i .. "] = { " )
for k,v in pairs(record.children) do
if functonum[k] then -- non-recorded functions will be ignored now
outfile:write( functonum[k] .. ", " )
end
end
outfile:write( "}\n" )
end
outfile:write( "\n" )
outfile:write( "-- Child call counts\nchildcounts = {}\n" )
for i=1,table.getn(ordering) do
local thisfunc = ordering[i]
local record = self.rawstats[thisfunc]
outfile:write( "children[" .. i .. "] = { " )
for k,v in record.children do
if functonum[k] then -- non-recorded functions will be ignored now
outfile:write( v .. ", " )
end
end
outfile:write( "}\n" )
end
outfile:write( "\n" )
outfile:write( "-- Child call time\nchildtimes = {}\n" )
for i=1,table.getn(ordering) do
local thisfunc = ordering[i]
local record = self.rawstats[thisfunc];
outfile:write( "children[" .. i .. "] = { " )
for k,v in pairs(record.children) do
if functonum[k] then -- non-recorded functions will be ignored now
outfile:write( record.children_time[k] .. ", " )
end
end
outfile:write( "}\n" )
end
outfile:write( "\n\n-- That is all.\n\n" )
outfile:flush()
end
-- Internal function to calculate a pretty name for the profile output
function _profiler._pretty_name(self,func)
-- Only the data collected during the actual
-- run seems to be correct.... why?
local info = self.rawstats[ func ].func_info
-- local info = debug.getinfo( func )
local name = ""
if info.what == "Lua" then
name = "L:"
end
if info.what == "C" then
name = "C:"
end
if info.what == "main" then
name = " :"
end
if info.name == nil then
name = name .. "<"..tostring(func) .. ">"
else
name = name .. info.name
end
if info.source then
name = name
else
if info.what == "C" then
name = name .. "@?"
else
name = name .. "@<string>"
end
end
name = name .. ":"
if info.what == "C" then
name = name .. "?"
else
name = name .. info.linedefined
end
return name
end
--
-- This allows you to specify functions which you do
-- not want profiled. Setting level to 1 keeps the
-- function from being profiled. Setting level to 2
-- keeps both the function and its children from
-- being profiled.
--
-- BUG: 2 will probably act exactly like 1 in "time" mode.
-- If anyone cares, let me (zorba) know and it can be fixed.
--
function _profiler.prevent(self, func, level)
self.prevented_functions[func] = (level or 1)
end
_profiler.prevented_functions = {
[_profiler.start] = 2,
[_profiler.stop] = 2,
[_profiler._internal_profile_by_time] = 2,
[_profiler._internal_profile_by_call] = 2,
[_profiler_hook_wrapper_by_time] = 2,
[_profiler_hook_wrapper_by_call] = 2,
[_profiler.prevent] = 2,
[_profiler._get_func_rec] = 2,
[_profiler.report] = 2,
[_profiler.lua_report] = 2,
[_profiler._pretty_name] = 2
}

View File

@@ -0,0 +1,775 @@
--
-- project.lua
-- Functions for working with the project data.
-- Copyright (c) 2002 Jason Perkins and the Premake project
--
premake.project = { }
--
-- Create a tree from a project's list of files, representing the filesystem hierarchy.
--
-- @param prj
-- The project containing the files to map.
-- @returns
-- A new tree object containing a corresponding filesystem hierarchy. The root node
-- contains a reference back to the original project: prj = tr.project.
--
function premake.project.buildsourcetree(prj, allfiles)
local tr = premake.tree.new(prj.name)
tr.project = prj
local isvpath
local function onadd(node)
node.isvpath = isvpath
end
for fcfg in premake.project.eachfile(prj, allfiles) do
isvpath = (fcfg.name ~= fcfg.vpath)
local node = premake.tree.add(tr, fcfg.vpath, onadd)
node.cfg = fcfg
end
premake.tree.sort(tr)
return tr
end
--
-- Returns an iterator for a set of build configuration settings. If a platform is
-- specified, settings specific to that platform and build configuration pair are
-- returned.
--
function premake.eachconfig(prj, platform)
-- I probably have the project root config, rather than the actual project
if prj.project then prj = prj.project end
local cfgs = prj.solution.configurations
local i = 0
return function ()
i = i + 1
if i <= #cfgs then
return premake.getconfig(prj, cfgs[i], platform)
end
end
end
--
-- Iterator for a project's files; returns a file configuration object.
--
function premake.project.eachfile(prj, allfiles)
-- project root config contains the file config list
if not prj.project then prj = premake.getconfig(prj) end
local i = 0
local t = iif(allfiles, prj.allfiles, prj.files)
local c = iif(allfiles, prj.__allfileconfigs, prj.__fileconfigs)
return function ()
i = i + 1
if (i <= #t) then
local fcfg = c[t[i]]
fcfg.vpath = premake.project.getvpath(prj, fcfg.name)
return fcfg
end
end
end
--
-- Apply XML escaping to a value.
--
function premake.esc(value)
if (type(value) == "table") then
local result = { }
for _,v in ipairs(value) do
table.insert(result, premake.esc(v))
end
return result
else
value = value:gsub('&', "&amp;")
value = value:gsub('"', "&quot;")
value = value:gsub("'", "&apos;")
value = value:gsub('<', "&lt;")
value = value:gsub('>', "&gt;")
value = value:gsub('\r', "&#x0D;")
value = value:gsub('\n', "&#x0A;")
return value
end
end
--
-- Given a map of supported platform identifiers, filters the solution's list
-- of platforms to match. A map takes the form of a table like:
--
-- { x32 = "Win32", x64 = "x64" }
--
-- Only platforms that are listed in both the solution and the map will be
-- included in the results. An optional default platform may also be specified;
-- if the result set would otherwise be empty this platform will be used.
--
function premake.filterplatforms(sln, map, default)
local result = { }
local keys = { }
if sln.platforms then
for _, p in ipairs(sln.platforms) do
if map[p] and not table.contains(keys, map[p]) then
table.insert(result, p)
table.insert(keys, map[p])
end
end
end
if #result == 0 and default then
table.insert(result, default)
end
return result
end
--
-- Locate a project by name; case insensitive.
--
function premake.findproject(name)
for sln in premake.solution.each() do
for prj in premake.solution.eachproject(sln) do
if (prj.name == name) then
return prj
end
end
end
end
--
-- Locate a file in a project with a given extension; used to locate "special"
-- items such as Windows .def files.
--
function premake.findfile(prj, extension)
for _, fname in ipairs(prj.files) do
if fname:endswith(extension) then return fname end
end
end
--
-- Retrieve a configuration for a given project/configuration pairing.
-- @param prj
-- The project to query.
-- @param cfgname
-- The target build configuration; only settings applicable to this configuration
-- will be returned. May be nil to retrieve project-wide settings.
-- @param pltname
-- The target platform; only settings applicable to this platform will be returned.
-- May be nil to retrieve platform-independent settings.
-- @returns
-- A configuration object containing all the settings for the given platform/build
-- configuration pair.
--
function premake.getconfig(prj, cfgname, pltname)
-- might have the root configuration, rather than the actual project
prj = prj.project or prj
-- if platform is not included in the solution, use general settings instead
if pltname == "Native" or not table.contains(prj.solution.platforms or {}, pltname) then
pltname = nil
end
local key = (cfgname or "")
if pltname then key = key .. pltname end
return prj.__configs[key]
end
--
-- Build a name from a build configuration/platform pair. The short name
-- is good for makefiles or anywhere a user will have to type it in. The
-- long name is more readable.
--
function premake.getconfigname(cfgname, platform, useshortname)
if cfgname then
local name = cfgname
if platform and platform ~= "Native" then
if useshortname then
name = name .. premake.platforms[platform].cfgsuffix
else
name = name .. "|" .. platform
end
end
return iif(useshortname, name:lower(), name)
end
end
--
-- Returns a list of sibling projects on which the specified project depends.
-- This is used to list dependencies within a solution or workspace. Must
-- consider all configurations because Visual Studio does not support per-config
-- project dependencies.
--
-- @param prj
-- The project to query.
-- @returns
-- A list of dependent projects, as an array of objects.
--
function premake.getdependencies(prj)
-- make sure I've got the project and not root config
prj = prj.project or prj
local results = { }
for _, cfg in pairs(prj.__configs) do
for _, link in ipairs(cfg.links) do
local dep = premake.findproject(link)
if dep and not table.contains(results, dep) then
table.insert(results, dep)
end
end
end
return results
end
--
-- Uses a pattern to format the basename of a file (i.e. without path).
--
-- @param prjname
-- A project name (string) to use.
-- @param pattern
-- A naming pattern. The sequence "%%" will be replaced by the
-- project name.
-- @returns
-- A filename (basename only) matching the specified pattern, without
-- path components.
--
function premake.project.getbasename(prjname, pattern)
return pattern:gsub("%%%%", prjname)
end
--
-- Uses information from a project (or solution) to format a filename.
--
-- @param prj
-- A project or solution object with the file naming information.
-- @param pattern
-- A naming pattern. The sequence "%%" will be replaced by the
-- project name.
-- @returns
-- A filename matching the specified pattern, with a relative path
-- from the current directory to the project location.
--
function premake.project.getfilename(prj, pattern)
local fname = premake.project.getbasename(prj.name, pattern)
fname = path.join(prj.location, fname)
return path.getrelative(os.getcwd(), fname)
end
--
-- Returns a list of link targets. Kind may be one of:
-- siblings - linkable sibling projects
-- system - system (non-sibling) libraries
-- dependencies - all sibling dependencies, including non-linkable
-- all - return everything
--
-- Part may be 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
--
function premake.getlinks(cfg, kind, part)
-- if I'm building a list of link directories, include libdirs
local result = iif (part == "directory" and kind == "all", cfg.libdirs, {})
-- am I getting links for a configuration or a project?
local cfgname = iif(cfg.name == cfg.project.name, "", cfg.name)
-- how should files be named?
local pathstyle = premake.getpathstyle(cfg)
local namestyle = premake.getnamestyle(cfg)
local function canlink(source, target)
if (target.kind ~= "SharedLib" and target.kind ~= "StaticLib") then
return false
end
if premake.iscppproject(source) then
return premake.iscppproject(target)
elseif premake.isdotnetproject(source) then
return premake.isdotnetproject(target)
elseif premake.isswiftproject(source) then
return premake.isswiftproject(source) or premake.iscppproject(source)
end
end
for _, link in ipairs(cfg.links) do
local item
-- is this a sibling project?
local prj = premake.findproject(link)
if prj and kind ~= "system" then
local prjcfg = premake.getconfig(prj, cfgname, cfg.platform)
if kind == "dependencies" or canlink(cfg, prjcfg) then
if (part == "directory") then
item = path.rebase(prjcfg.linktarget.directory, prjcfg.location, cfg.location)
elseif (part == "basename") then
item = prjcfg.linktarget.basename
elseif (part == "fullpath") then
item = path.rebase(prjcfg.linktarget.fullpath, prjcfg.location, cfg.location)
elseif (part == "object") then
item = prjcfg
end
end
elseif not prj and (kind == "system" or kind == "all") then
if (part == "directory") then
item = path.getdirectory(link)
elseif (part == "fullpath") then
item = link
if namestyle == "windows" then
if premake.iscppproject(cfg) then
item = item .. ".lib"
elseif premake.isdotnetproject(cfg) then
item = item .. ".dll"
end
end
elseif part == "name" then
item = path.getname(link)
elseif part == "basename" then
item = path.getbasename(link)
else
item = link
end
if item:find("/", nil, true) then
item = path.getrelative(cfg.project.location, item)
end
end
if item then
if pathstyle == "windows" and part ~= "object" then
item = path.translate(item, "\\")
end
if not table.contains(result, item) then
table.insert(result, item)
end
end
end
return result
end
--
-- Gets the name style for a configuration, indicating what kind of prefix,
-- extensions, etc. should be used in target file names.
--
-- @param cfg
-- The configuration to check.
-- @returns
-- The target naming style, one of "windows", "posix", or "PS3".
--
function premake.getnamestyle(cfg)
return premake.platforms[cfg.platform].namestyle or premake.gettool(cfg).namestyle or "posix"
end
--
-- Gets the path style for a configuration, indicating what kind of path separator
-- should be used in target file names.
--
-- @param cfg
-- The configuration to check.
-- @returns
-- The target path style, one of "windows" or "posix".
--
function premake.getpathstyle(cfg)
if premake.action.current().os == "windows" then
return "windows"
else
return "posix"
end
end
--
-- Assembles a target for a particular tool/system/configuration.
--
-- @param cfg
-- The configuration to be targeted.
-- @param direction
-- One of 'build' for the build target, or 'link' for the linking target.
-- @param pathstyle
-- The path format, one of "windows" or "posix". This comes from the current
-- action: Visual Studio uses "windows", GMake uses "posix", etc.
-- @param namestyle
-- The file naming style, one of "windows" or "posix". This comes from the
-- current tool: GCC uses "posix", MSC uses "windows", etc.
-- @param system
-- The target operating system, which can modify the naming style. For example,
-- shared libraries on Mac OS X use a ".dylib" extension.
-- @returns
-- An object with these fields:
-- 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
-- prefix - the file name prefix
-- suffix - the file name suffix
-- fullpath - directory, name, and extension
-- bundlepath - the relative path and file name of the bundle
--
function premake.gettarget(cfg, direction, pathstyle, namestyle, system)
if system == "bsd" then
system = "linux"
end
-- Fix things up based on the current system
local kind = cfg.kind
if premake.iscppproject(cfg) or premake.isvalaproject(cfg) then
-- On Windows, shared libraries link against a static import library
if (namestyle == "windows" or system == "windows")
and kind == "SharedLib" and direction == "link"
and not cfg.flags.NoImportLib
then
kind = "StaticLib"
end
-- Posix name conventions only apply to static libs on windows (by user request)
if namestyle == "posix" and system == "windows" and kind ~= "StaticLib" then
namestyle = "windows"
end
end
-- Initialize the target components
local field = "build"
if direction == "link" and cfg.kind == "SharedLib" then
field = "implib"
end
local name = cfg[field.."name"] or cfg.targetname or cfg.project.name
local dir = cfg[field.."dir"] or cfg.targetdir or path.getrelative(cfg.location, cfg.basedir)
local subdir = cfg[field.."subdir"] or cfg.targetsubdir or "."
local prefix = ""
local suffix = ""
local ext = ""
local bundlepath, bundlename
-- targetpath/targetsubdir/bundlepath/prefix..name..suffix..ext
dir = path.join(dir, subdir)
if namestyle == "windows" then
if kind == "ConsoleApp" or kind == "WindowedApp" then
ext = ".exe"
elseif kind == "SharedLib" then
ext = ".dll"
elseif kind == "StaticLib" then
ext = ".lib"
end
elseif namestyle == "posix" then
if kind == "WindowedApp" and system == "macosx" and not cfg.options.SkipBundling then
bundlename = name .. ".app"
bundlepath = path.join(dir, bundlename)
dir = path.join(bundlepath, "Contents/MacOS")
elseif (kind == "ConsoleApp" or kind == "WindowedApp") and system == "os2" then
ext = ".exe"
elseif kind == "SharedLib" then
prefix = "lib"
ext = iif(system == "macosx", ".dylib", ".so")
elseif kind == "StaticLib" then
prefix = "lib"
ext = ".a"
end
elseif namestyle == "PS3" then
if kind == "ConsoleApp" or kind == "WindowedApp" then
ext = ".elf"
elseif kind == "StaticLib" then
prefix = "lib"
ext = ".a"
end
elseif namestyle == "Orbis" then
if kind == "ConsoleApp" or kind == "WindowedApp" then
ext = ".elf"
elseif kind == "StaticLib" then
prefix = "lib"
ext = ".a"
elseif kind == "SharedLib" then
ext = ".prx"
end
elseif namestyle == "TegraAndroid" then
-- the .so->.apk happens later for Application types
if kind == "ConsoleApp" or kind == "WindowedApp" or kind == "SharedLib" then
prefix = "lib"
ext = ".so"
elseif kind == "StaticLib" then
prefix = "lib"
ext = ".a"
end
elseif namestyle == "NX" then
-- NOTE: it would be cleaner to just output $(TargetExt) for all cases, but
-- there is logic elsewhere that assumes a '.' to be present in target name
-- such that it can reverse engineer the extension set here.
if kind == "ConsoleApp" or kind == "WindowedApp" then
ext = ".nspd_root"
elseif kind == "StaticLib" then
ext = ".a"
elseif kind == "SharedLib" then
ext = ".nro"
end
elseif namestyle == "Emscripten" then
if kind == "ConsoleApp" or kind == "WindowedApp" then
ext = ".html"
elseif kind == "StaticLib" then
ext = ".bc"
elseif kind == "SharedLib" then
ext = ".js"
end
end
prefix = cfg[field.."prefix"] or cfg.targetprefix or prefix
suffix = cfg[field.."suffix"] or cfg.targetsuffix or suffix
ext = cfg[field.."extension"] or cfg.targetextension or ext
-- build the results object
local result = { }
result.basename = name .. suffix
result.name = prefix .. name .. suffix .. ext
result.directory = dir
result.subdirectory = subdir
result.prefix = prefix
result.suffix = suffix
result.fullpath = path.join(result.directory, result.name)
result.bundlepath = bundlepath or result.fullpath
if pathstyle == "windows" then
result.directory = path.translate(result.directory, "\\")
result.subdirectory = path.translate(result.subdirectory, "\\")
result.fullpath = path.translate(result.fullpath, "\\")
end
return result
end
--
-- Return the appropriate tool interface, based on the target language and
-- any relevant command-line options.
--
function premake.gettool(prj)
if premake.iscppproject(prj) then
if _OPTIONS.cc then
return premake[_OPTIONS.cc]
end
local action = premake.action.current()
if action.valid_tools then
return premake[action.valid_tools.cc[1]]
end
return premake.gcc
elseif premake.isdotnetproject(prj) then
return premake.dotnet
elseif premake.isswiftproject(prj) then
return premake.swift
else
return premake.valac
end
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 premake.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()
-- First check for an exact match from the inverse vpaths
if prj.inversevpaths and prj.inversevpaths[abspath] then
return path.join(prj.inversevpaths[abspath], fname)
end
-- Look for matching patterns
local matches = {}
for replacement, patterns in pairs(prj.vpaths or {}) 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
table.insert(matches, path.join(stem, leaf))
end
end
end
if #matches > 0 then
-- for the sake of determinism, return the first alphabetically
table.sort(matches)
vpath = matches[1]
end
return path.trimdots(vpath)
end
--
-- Returns true if the solution contains at least one C/C++ project.
--
function premake.hascppproject(sln)
for prj in premake.solution.eachproject(sln) do
if premake.iscppproject(prj) then
return true
end
end
end
--
-- Returns true if the solution contains at least one .NET project.
--
function premake.hasdotnetproject(sln)
for prj in premake.solution.eachproject(sln) do
if premake.isdotnetproject(prj) then
return true
end
end
end
--
-- Returns true if the project use the C language.
--
function premake.project.iscproject(prj)
local language = prj.language or prj.solution.language
return language == "C"
end
--
-- Returns true if the project uses a C/C++ language.
--
function premake.iscppproject(prj)
local language = prj.language or prj.solution.language
return (language == "C" or language == "C++")
end
--
-- Returns true if the project uses a .NET language.
--
function premake.isdotnetproject(prj)
local language = prj.language or prj.solution.language
return (language == "C#")
end
--
-- Returns true if the project uses the Vala language.
--
function premake.isvalaproject(prj)
local language = prj.language or prj.solution.language
return (language == "Vala")
end
--
-- Returns true if the project uses the Swift language.
--
function premake.isswiftproject(prj)
local language = prj.language or prj.solution.language
return (language == "Swift")
end

View File

@@ -0,0 +1,77 @@
Set_mt = {}
function Set(t)
local set = {}
for k, l in ipairs(t) do
set[l] = true
end
setmetatable(set, Set_mt)
return set
end
function Set_mt.union(a, b)
local res = Set{}
for k in pairs(a) do res[k] = true end
for k in pairs(b) do res[k] = true end
return res
end
function Set_mt.intersection(a, b)
local res = Set{}
for k in pairs(a) do
res[k] = b[k]
end
return res
end
local function get_cache(a)
if not rawget(a, "__cache") then
rawset(a, "__cache", Set_mt.totable(a))
end
return rawget(a, "__cache")
end
function Set_mt.len(a)
return #get_cache(a)
end
function Set_mt.implode(a, sep)
return table.concat(get_cache(a), sep)
end
function Set_mt.totable(a)
local res = {}
for k in pairs(a) do
table.insert(res, k)
end
return res
end
function Set_mt.difference(a, b)
if getmetatable(b) ~= Set_mt then
if type(b) ~= "table" then
error(type(b).." is not a Set or table")
end
b=Set(b)
end
local res = Set{}
for k in pairs(a) do
res[k] = not b[k] or nil
end
rawset(res, "__cache", nil)
return res
end
function Set_mt.__index(a, i)
if type(i) == "number" then
return get_cache(a)[i]
end
return Set_mt[i] or rawget(a, i)
end
Set_mt.__add = Set_mt.union
Set_mt.__mul = Set_mt.intersection
Set_mt.__sub = Set_mt.difference
Set_mt.__len = Set_mt.len
Set_mt.__concat = Set_mt.implode

View File

@@ -0,0 +1,148 @@
--
-- solution.lua
-- Work with the list of solutions loaded from the script.
-- Copyright (c) 2002-2009 Jason Perkins and the Premake project
--
premake.solution = { }
-- The list of defined solutions (which contain projects, etc.)
premake.solution.list = { }
--
-- Create a new solution and add it to the session.
--
-- @param name
-- The new solution's name.
--
function premake.solution.new(name)
local sln = { }
-- add to master list keyed by both name and index
table.insert(premake.solution.list, sln)
premake.solution.list[name] = sln
-- attach a type descriptor
setmetatable(sln, { __type="solution" })
sln.name = name
sln.basedir = os.getcwd()
sln.projects = { }
sln.blocks = { }
sln.configurations = { }
sln.groups = { }
sln.importedprojects = { }
return sln
end
--
-- Iterate over the collection of solutions in a session.
--
-- @returns
-- An iterator function.
--
function premake.solution.each()
local i = 0
return function ()
i = i + 1
if i <= #premake.solution.list then
return premake.solution.list[i]
end
end
end
--
-- Iterate over the projects of a solution.
--
-- @param sln
-- The solution.
-- @returns
-- An iterator function.
--
function premake.solution.eachproject(sln)
local i = 0
return function ()
i = i + 1
if (i <= #sln.projects) then
return premake.solution.getproject(sln, i)
end
end
end
--
-- Iterate over the groups of a solution
--
-- @param sln
-- The solution.
-- @returns
-- An iterator function.
function premake.solution.eachgroup(sln)
local i = 0
return function()
i = i + 1
if(i <= #sln.groups) then
return premake.solution.getgroup(sln, i)
end
end
end
--
-- Retrieve a solution by name or index.
--
-- @param key
-- The solution key, either a string name or integer index.
-- @returns
-- The solution with the provided key.
--
function premake.solution.get(key)
return premake.solution.list[key]
end
--
-- Retrieve the project at a particular index.
--
-- @param sln
-- The solution.
-- @param idx
-- An index into the array of projects.
-- @returns
-- The project at the given index.
--
function premake.solution.getproject(sln, idx)
-- retrieve the root configuration of the project, with all of
-- the global (not configuration specific) settings collapsed
local prj = sln.projects[idx]
local cfg = premake.getconfig(prj)
-- root configuration doesn't have a name; use the project's
cfg.name = prj.name
return cfg
end
--
-- Retrieve the group at a particular index
--
-- @param sln
-- The solution.
-- @param idx
-- The index into the array of groups
-- @returns
-- The group at the given index
function premake.solution.getgroup(sln, idx)
local grp = sln.groups[idx]
return grp
end

View File

@@ -0,0 +1,62 @@
--
-- string.lua
-- Additions to Lua's built-in string functions.
-- Copyright (c) 2002-2008 Jason Perkins and the Premake project
--
--
-- 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)
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
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
local term = nil
repeat
local next, nextterm = s:find(pattern, curr + 1, plain)
if (next) then
curr = next
term = nextterm
end
until (not next)
if (curr > 0) then
return curr, term
end
end
--
-- Returns true if `haystack` starts with the sequence `needle`.
--
function string.startswith(haystack, needle)
return (haystack:find(needle, 1, true) == 1)
end
--
-- remove leading and trailing whitespace from string.
--
function string.trim(s)
return (s:gsub("^%s*(.-)%s*$", "%1"))
end

View File

@@ -0,0 +1,260 @@
--
-- table.lua
-- Additions to Lua's built-in table functions.
-- Copyright (c) 2002-2008 Jason Perkins and the Premake project
--
--
-- 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
function table.icontains(t, value)
for _, v in ipairs(t) do
if v == value then return true end
end
return false
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
--
-- Flattens a hierarchy of tables into a single array containing all
-- of the values.
--
function table.flatten(arr)
local result = { }
local function flatten(arr)
for _, v in ipairs(arr) do
if type(v) == "table" then
flatten(v)
else
table.insert(result, v)
end
end
end
flatten(arr)
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
--
-- Inserts a value of 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 type(values) == "table" then
for _, value in ipairs(values) do
table.insertflat(tbl, value)
end
else
table.insert(tbl, values)
end
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 arg={...}
local result = { }
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
--
-- Return an iterator over key/value pairs in a table, sorted by key.
--
function table.sortedpairs(t)
local keys = table.keys(t)
local i = 0
table.sort(keys)
return function()
i = i + 1
if keys[i] == nil then
return nil
end
return keys[i], t[keys[i]]
end
end
--
-- Adds the key-value associations from one table into another
-- and returns the resulting merged table.
--
function table.merge(...)
local arg={...}
local result = { }
for _,t in ipairs(arg) do
if type(t) == "table" then
for k,v in pairs(t) do
result[k] = v
end
else
error("invalid value")
end
end
return result
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)
local result = { }
for _, value in ipairs(arr) do
local tvalue
if type(translation) == "function" then
tvalue = translation(value)
else
tvalue = translation[value]
end
if (tvalue) then
table.insert(result, tvalue)
end
end
return result
end
--
-- reverse table order
--
function table.reverse(arr)
for i=1, math.floor(#arr / 2) do
arr[i], arr[#arr - i + 1] = arr[#arr - i + 1], arr[i]
end
return arr
end
--
-- reverse table order
--
function table.arglist(arg, value)
if #value > 0 then
local args = {}
for _, val in ipairs(value) do
table.insert(args, string.format("%s %s", arg, val))
end
return table.concat(args, " ")
else
return ""
end
end

View File

@@ -0,0 +1,231 @@
--
-- tree.lua
-- Functions for working with the source code tree.
-- Copyright (c) 2009 Jason Perkins and the Premake project
--
premake.tree = { }
local tree = premake.tree
--
-- Create a new tree.
--
-- @param n
-- The name of the tree, applied to the root node (optional).
--
function premake.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 onaddfunc
-- A function to call when a new node is added to the tree. Receives the
-- new node as an argument.
-- @returns
-- The new tree node.
--
function premake.tree.add(tr, p, onaddfunc)
-- Special case "." refers to the current node
if p == "." then
return tr
end
if 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), onaddfunc)
-- Another special case, ".." refers to the parent node and doesn't create anything
local childname = path.getname(p)
if childname == ".." then
return parentnode
end
-- Create the child if necessary. If two children with the same name appear
-- at the same level, make sure they have the same path to prevent conflicts
-- i.e. ../Common and ../../Common can both appear at the top of the tree
-- yet they have different paths (Bug #3016050)
local childnode = parentnode.children[childname]
if not childnode or childnode.path ~= p then
childnode = tree.insert(parentnode, tree.new(childname))
childnode.path = p
if onaddfunc then
onaddfunc(childnode)
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 premake.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 premake.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
--
-- Remove a node from a tree.
--
-- @param node
-- The node to remove.
--
function premake.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.
--
function premake.tree.sort(tr)
tree.traverse(tr, {
onnode = function(node)
table.sort(node.children, function(a,b)
return a.name < b.name
end)
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 premake.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

View File

@@ -0,0 +1,91 @@
--
-- validate.lua
-- Tests to validate the run-time environment before starting the action.
-- Copyright (c) 2002-2009 Jason Perkins and the Premake project
--
--
-- Performs a sanity check of all of the solutions and projects
-- in the session to be sure they meet some minimum requirements.
--
function premake.checkprojects()
local action = premake.action.current()
for sln in premake.solution.each() do
-- every solution must have at least one project
if (#sln.projects == 0) then
return nil, "solution '" .. sln.name .. "' needs at least one project"
end
-- every solution must provide a list of configurations
if (#sln.configurations == 0) then
return nil, "solution '" .. sln.name .. "' needs configurations"
end
for prj in premake.solution.eachproject(sln) do
-- every project must have a language
if (not prj.language) then
return nil, "project '" ..prj.name .. "' needs a language"
end
-- and the action must support it
if (action.valid_languages) then
if (not table.contains(action.valid_languages, prj.language)) then
return nil, "the " .. action.shortname .. " action does not support " .. prj.language .. " projects"
end
end
for cfg in premake.eachconfig(prj) do
-- every config must have a kind
if (not cfg.kind) then
return nil, "project '" ..prj.name .. "' needs a kind in configuration '" .. cfg.name .. "'"
end
-- and the action must support it
if (action.valid_kinds) then
if (not table.contains(action.valid_kinds, cfg.kind)) then
return nil, "the " .. action.shortname .. " action does not support " .. cfg.kind .. " projects"
end
end
end
-- some actions have custom validation logic
if action.oncheckproject then
action.oncheckproject(prj)
end
end
end
return true
end
--
-- Check the specified tools (/cc, /dotnet, etc.) against the current action
-- to make sure they are compatible and supported.
--
function premake.checktools()
local action = premake.action.current()
if (not action.valid_tools) then
return true
end
for tool, values in pairs(action.valid_tools) do
if (_OPTIONS[tool]) then
if (not table.contains(values, _OPTIONS[tool])) then
return nil, "the " .. action.shortname .. " action does not support /" .. tool .. "=" .. _OPTIONS[tool] .. " (yet)"
end
else
_OPTIONS[tool] = values[1]
end
end
return true
end