-- -- 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) 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) 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) -- project root config contains the file config list if not prj.project then prj = premake.getconfig(prj) end local i = 0 local t = prj.files return function () i = i + 1 if (i <= #t) then local fcfg = prj.__fileconfigs[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('&', "&") value = value:gsub('"', """) value = value:gsub("'", "'") value = value:gsub('<', "<") value = value:gsub('>', ">") value = value:gsub('\r', " ") value = value:gsub('\n', " ") 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) 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" or system == "solaris" then system = "linux" end -- Fix things up based on the current system local kind = cfg.kind if premake.iscppproject(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 prefix = "" local suffix = "" local ext = "" local bundlepath, bundlename 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" then bundlename = name .. ".app" bundlepath = path.join(dir, bundlename) dir = path.join(bundlepath, "Contents/MacOS") 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 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.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.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(cfg) if premake.iscppproject(cfg) 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 else return premake.dotnet 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() -- Look for matching patterns 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 vpath = path.join(stem, leaf) end end end -- remove any dot ("./", "../") patterns from the start of the path local changed repeat changed = true if vpath:startswith("./") then vpath = vpath:sub(3) elseif vpath:startswith("../") then vpath = vpath:sub(4) else changed = false end until not changed return 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) return prj.language == "C" end -- -- Returns true if the project uses a C/C++ language. -- function premake.iscppproject(prj) return (prj.language == "C" or prj.language == "C++") end -- -- Returns true if the project uses a .NET language. -- function premake.isdotnetproject(prj) return (prj.language == "C#") end