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