From ddfc77db15a4bedc2fde71a70491fe401032a52c Mon Sep 17 00:00:00 2001 From: Ilya Shurumov Date: Thu, 10 Jun 2021 13:43:39 +0600 Subject: [PATCH] - update premake usage module --- src_rebuild/premake_modules/usage/stack.lua | 79 ++ src_rebuild/premake_modules/usage/usage.lua | 776 +++++++++++--------- 2 files changed, 502 insertions(+), 353 deletions(-) create mode 100644 src_rebuild/premake_modules/usage/stack.lua diff --git a/src_rebuild/premake_modules/usage/stack.lua b/src_rebuild/premake_modules/usage/stack.lua new file mode 100644 index 00000000..92b47ee5 --- /dev/null +++ b/src_rebuild/premake_modules/usage/stack.lua @@ -0,0 +1,79 @@ +-- Stack Table +-- Uses a table as stack, use :push(value) and
:pop() +-- Lua 5.1 compatible + +unpack = table.unpack or unpack + +-- GLOBAL +Stack = {} + +-- Create a Table with stack functions +function Stack:Create() + + -- stack table + local t = {} + -- entry table + t._et = {} + + -- push a value on to the stack + function t:push(...) + if ... then + local targs = {...} + -- add values + for _,v in ipairs(targs) do + table.insert(self._et, v) + end + end + end + + -- pop a value from the stack + function t:pop(num) + + -- get num values from stack + local num = num or 1 + + -- return table + local entries = {} + + -- get values into entries + for i = 1, num do + -- get last entry + if #self._et ~= 0 then + table.insert(entries, self._et[#self._et]) + -- remove last value + table.remove(self._et) + else + break + end + end + -- return unpacked entries + return unpack(entries) + end + + -- get entries + function t:getn() + return #self._et + end + + -- get entry form index + function t:get(index) + return self._et[index] + end + + -- list values + function t:list() + for i,v in pairs(self._et) do + print(i, v) + end + end + -- list values + function t:find_value(value) + for i,v in pairs(self._et) do + if v == value then + return i + end + end + return nil + end + return t +end \ No newline at end of file diff --git a/src_rebuild/premake_modules/usage/usage.lua b/src_rebuild/premake_modules/usage/usage.lua index 56ad594e..d3ecc327 100644 --- a/src_rebuild/premake_modules/usage/usage.lua +++ b/src_rebuild/premake_modules/usage/usage.lua @@ -21,7 +21,7 @@ -- -- project "app" -- kind "ConsoleApp" --- uses "core_lib" +-- uses "core" -- -- "core" will link vendor and openssh and define 'USING_VENDOR' -- "app" will link 'core' and define 'USING_VENDOR' @@ -44,49 +44,33 @@ -- Using a project will link the project and resolves nested uses, -- in addition to any declared usage. -- --- TO CONSIDER: not sure this is needed anymore. --- 'uses' can filter down to 'linkoptions' or 'buildoptions' --- uses "linkoptions:opengl" --- uses "buildoptions:opengl" --- link options are defined as: links, linkoptions, libdirs. --- buildoptions are defined as anything that isn't a link option. --- TO CONSIDER: separate api, uses_linkoptions/uses_buildoptions? --- --- The context of a 'usage' is the ultimate target context it is being --- used in. --- --- usage "external_lib" --- links "external_lib-%{cfg.buildcfg}" --- --- Filters work as you'd expect: --- --- usage "debug_stuff" --- defines "DEBUG_STUFF" --- --- project "shared" --- kind "SharedLib" --- filter "configurations:debug" --- uses "debug_stuff" --- --- project "app" --- kind "ConsoleApp" --- uses "shared" -- DEBUG_STUFF will be defined in the debug configuration. --- --- Todo --- ---- --- --- Cyclic reference detection. We are allowed to use a project more than --- once, (eg. from different configurations) but not along the same call path. --- Decide what to do with diamond shaped uses graphs, include twice, or --- first use? --- --- Handle block insertion ordering and 'removes' correctly --- +-- "uses" do not respect configurations/filters due to havy optimizations +-- "usage" can contain configuration filters + + require 'stack' local p = premake local oven = p.oven local context = p.context + local keyLinks = "links" + local field = p.field + local fieldLinks = field.get(keyLinks) + + newoption { + trigger = "usage-optimization-off", + description = "Turn off optimization for usage module. Useful for debuging dependencies (50% slower generation)." + } + + local optimization = (_OPTIONS["usage-optimization-off"] == nil) + +--- +-- Two stacks which stores actual order of projects and usages +-- Projects stack and uses stack are aligned +-- + local usesStack = Stack:Create() + local sourcesStack = Stack:Create() + -- -- 'uses' api -- @@ -122,383 +106,469 @@ p.api.register { return nil end --- --- Given the current usage filter, does the new filter fit into it? +--- +-- Push usage and source project name on stack +-- * if "usage" is already on stack it mean that "usage" in this configuration was in use, -- - local function compatibleUsage(currentUsageFilter, newUsageFilter) - if newUsageFilter and currentUsageFilter then - if currentUsageFilter ~= newUsageFilter then - return false - end + local printUsesStack + local function pushUsage( usageName, sourceName ) + + -- search for uses + local usesStackIndex = usesStack:find_value(usageName) + + if usesStackIndex == nil then + verbosef("{PUSH} USES_STACK [%s]: %s", sourceName, usageName) + + -- push on stack + usesStack:push(usageName) + sourcesStack:push(sourceName) + else + verbosef("{EXIST} USES_STACK [%s]: %s", sourceName, usageName) + end - return true end - --- --- resolveUsage +--- +-- Pop usage and source name form stack -- - local resolveAllUsesInBlock + local function popUsage( count ) + local count = count or 1 + for i = 1, count do - local function resolveUsage( targetProject, targetBlock, - usage, usageName, usageFilter, - inheritedCriteria ) + -- pop form stack + local sourceName = sourcesStack:pop() + local usageName = usesStack:pop() - verbosef("\nProject %s is using usage %s %s\n", - targetProject.name, usage.name, usageFilter or "" ) + verbosef("{POP} USES_STACK [%s]: %s", sourceName, usageName) + end + end - -- A map for how to copy the usage blocks into the target project. - -- Each entry in the map creates a new block. +-- +-- During baking we can start new bake so we have to stash current stack +-- + local stackStash = Stack:Create() - local propagateMap = {} +--- +-- During "bake" process it is posible to call another "bake", +-- to starts new bake we have to stash current uses stack and create new empty +--- - local commonFields = { "_criteria", "_basedir", "uses" } - local linkFields = { "links", "linkoptions", "libdirs" } - - if usageFilter == nil then - - -- We don't have a filter. - -- Create one block with the build options, - -- and another with only the links options that is filtered - -- for just link targets. - - table.insert( propagateMap, { - usageFilter = "buildoptions", - excludeFields = linkFields - } ) - - table.insert( propagateMap, { - terms = {'kind:not StaticLib'}, - usageFilter = "linkoptions", - includeFields = table.join( commonFields, linkFields ) - } ) - else - - -- Respect the filter we have, and create one block with - -- either build or linkoptions - - if usageFilter == "buildoptions" then - table.insert( propagateMap, { - usageFilter = "buildoptions", - excludeFields = linkFields - } ) + local function stashStack() + if _OPTIONS["verbose"] then + for i , sourceName in ipairs(sourcesStack._et) do + verbosef("{STASH} USES_STACK [%s]: %s", sourceName, usesStack:get(i)) end + end + stackStash:push( {["sourcesStack"] = sourcesStack, ["usesStack"] = usesStack} ) + sourcesStack = Stack:Create() + usesStack = Stack:Create() + end - if usageFilter == "linkoptions" then - table.insert( propagateMap, { - usageFilter = "linkoptions", - includeFields = table.join( commonFields, linkFields ) - } ) +--- +-- After "bake" previous uses stack have to be recreated +--- + + local function unstashStack() + local stash = stackStash:pop() + sourcesStack = stash.sourcesStack + usesStack = stash.usesStack + if _OPTIONS["verbose"] then + for i , sourceName in ipairs(sourcesStack._et) do + verbosef("{UNSTASH} USES_STACK [%s]: %s", sourceName, usesStack:get(i)) end + end + end +-- +-- Print stack +-- + + printUsesStack = function() + -- reconstructed full stack from stashed and current stacks + local fullStack = {} + -- for formating we need max length of sourceName, it will be first column + local maxSourceNameLen = 1 + + -- get full stack form stashed stacks. form oldest to newest + for _, stacks in ipairs(stackStash._et) do + if stacks.sourcesStack:getn() > 0 then + for i, sourceName in ipairs(stacks.sourcesStack._et) do + local len = sourceName:len() + if len > maxSourceNameLen then + maxSourceNameLen = len + end + table.insert(fullStack, { ["sourceName"] = sourceName, ["usageName"] = stacks.usesStack:get(i) } ) + end + end end + -- get stack form currect "bake" + for i, sourceName in ipairs(sourcesStack._et) do + local len = sourceName:len() + if len > maxSourceNameLen then + maxSourceNameLen = len + end + table.insert(fullStack, { ["sourceName"] = sourceName, ["usageName"] = usesStack:get(i) } ) + end + + -- print full stack in three columns + -- SOURCE: ACRION: USAGE: + -- + -- Actions: + -- BAKES - mean that usage requred "bake" project + -- USES - use project or usage + print("\nUses stack:\n") + local line = string.format("%-" .. maxSourceNameLen .. "s ACRION: USAGE: \n", "SOURCE:") + print(line) + for _, entry in ipairs(fullStack) do + if not entry.sourceName:find('"bake"') then + if entry.usageName:find('"bake"') then + line = string.format("%-" .. maxSourceNameLen .. "s BAKES %s", entry.sourceName, entry.usageName:gsub('"bake" ', "")) + else + line = string.format("%-" .. maxSourceNameLen .. "s USES %s", entry.sourceName, entry.usageName) + end + print(line) + end + end + end + +--- +-- Resolve usage of targetProject.targetBlock +-- + local function resolveUsage( targetProject, targetBlock, usage ) + + + verbosef("\nProject %s is using usage %s\n", + targetProject.name, usage.name) + + -- Clone each block in the usage and insert it into the target project + for _,usageBlock in ipairs(usage.blocks) do + + -- Merge the criteria for the usage and target - for _,propagate in ipairs(propagateMap) do + -- detach fat references before deepcopy + usageOrigin = usageBlock._origin + usageBlock._origin = nil + + local newBlock = table.deepcopy(usageBlock) - verbosef("\nApplying %s to project %s from using usage %s\n", - propagate.usageFilter, targetProject.name, usage.name ) + -- attach fat references after deepcopy + newBlock._origin = usageOrigin + usageBlock._origin = usageOrigin + - for _,usageBlock in ipairs(usage.blocks) do + newBlock._criteria.patterns = table.join( + newBlock._criteria.patterns, + targetBlock._criteria.patterns ) - -- Merge the criteria for the usage, target, propagate - -- with any inherited criteria + newBlock._criteria.data = + p.criteria._compile(newBlock._criteria.patterns) - local newBlock = table.deepcopy(usageBlock) - - newBlock._criteria.patterns = table.join( - newBlock._criteria.patterns, - targetBlock._criteria.patterns ) - - if inheritedCriteria ~= nil then - newBlock._criteria.patterns = table.join( - newBlock._criteria.patterns, - inheritedCriteria.patterns ) - end - - if propagate.terms ~= nil then - local propagateCriteria = - p.criteria.new( propagate.terms ) - - newBlock._criteria.patterns = table.join( - newBlock._criteria.patterns, - propagateCriteria.patterns ) - end - - newBlock._criteria.data = - p.criteria._compile(newBlock._criteria.patterns) - - -- todo: would be nice not to do this. - -- needs to be in sync with internal block logic. - if newBlock.filename then - newBlock.filename = nil - newBlock._basedir = newBlock.basedir - newBlock.basedir = nil - end - - -- Filter the field set of the new block to the ones we - -- are propagating - - for field,v in pairs(usageBlock) do - - local reject = false - - if propagate.excludeFields ~= nil then - reject = table.contains( - propagate.excludeFields, field ) - end - - if propagate.includeFields ~= nil then - reject = reject or not table.contains( - propagate.includeFields, field ) - end - - if reject then - verbosef("ignoring %s from %s %s", - field, usage.name, propagate.usageFilter) - - newBlock[field] = nil - else - verbosef("keeping %s from %s %s", - field, usage.name, propagate.usageFilter) - end - end - - -- Insert the new block into the target project. - -- TODO: We need to insert as if at the call site, - -- and it need to deal with with 'removes' - -- merging between the two blocks. - - table.insert( targetProject.blocks, newBlock ) - - -- Recurse into the new block and resolve any 'uses' there - - resolveAllUsesInBlock( - targetProject, - newBlock, - propagate.usageFilter ) + -- todo: would be nice not to do this. + -- needs to be in sync with internal block logic. + if newBlock.filename then + newBlock.filename = nil + newBlock._basedir = newBlock.basedir + newBlock.basedir = nil end + + -- Insert the new block into the target project. + -- TODO: We need to insert as if at the call site, + -- and it need to deal with with 'removes' + -- merging between the two blocks. + table.insert( targetProject.blocks, newBlock ) + + + -- Recursion in usage is to fuzzy + if newBlock.uses then + error("Usage '" .. usage.name .. "'. Uses in usage is forbidden, move 'uses' to project.") + end + end + end + +--- +-- Insert usageName to outTable and push usageName on stack, +-- if srcProjectName and usageName are equal push is not performed. +-- This is common case and it should be in separeted function +-- + local function insertUsage( usesTable, usageName, sourceName ) + local usagePushed = 0 + -- project can use own usage and it is valid use + -- without this check we will get error about double push on stack + if usageName ~= sourceName then + pushUsage( usageName, sourceName ) + usagePushed = 1 + end + table.insert( usesTable, usageName ) + return usagePushed end - +--- +-- Manually run "bake" process on source project if need, +-- if "bake" on sourceProject is under way this mean that we have circular dependence -- --- Resolve a single 'use', with a given filter and criteria, --- into a target block of a target project --- - - local function resolveUse( targetProject, targetBlock, - usageName, usageFilter, inheritedCriteria ) - - local targetWorkspace = targetProject.solution - - local sourceProject = p.workspace.findproject( - targetWorkspace, usageName ) - - local usage = p.workspace.findusage( targetWorkspace, usageName) - - if sourceProject == nil and usage == nil then + local function bakeProject( sourceProject, targetProject ) + -- _isUsageBaking - is flag on project which informs that premake alredy starts "bake" process + -- such situation occure with circular dependence + if sourceProject._isUsageBaking then + printUsesStack() error("Use of " - .. "'" .. usageName.. "'" - .. ", is not defined as a usage or project in workspace " - .. "'" .. targetWorkspace.name .. "'") + .. "'" .. sourceProject.name .. "' in '" .. targetProject.name .. "'" + .." circular dependence!!!" ) + else + if not sourceProject._usageBaked then + verbosef("\nUsage starts baking %s\n", sourceProject.name ) + + sourceProject._isUsageBaking = true + + pushUsage( '"bake" ' .. sourceProject.name, targetProject.name ) -- source mark "bake" process on stack + if optimization then + bakeProjectUsage( sourceProject ) + else + sourceProject.ctx = p.container.bake(sourceProject) -- bake context is temporary in current version of premake5, we have to store it + sourceProject._isBaked = false -- premake5 thinks that object was not baked + end + popUsage() + + sourceProject._isUsageBaking = false + sourceProject._usageBaked = true + + verbosef("\nUsage ends baking %s\n", sourceProject.name ) + end end + end - if sourceProject ~= nil and sourceProject.name ~= targetProject.name then +--- +-- Resolve a sourceProject 'uses' into a target block of a target project +-- - verbosef("\nProject %s is using project %s\n", - targetProject.name, sourceProject.name ) + local resolveUse - -- The source project might not be baked yet, bake it now. - -- We need this for the configuration list. - -- TODO: not sure if we need to do some project configuration - -- mapping between source and target too? project.getconfig? + local function resolveUses( sourceProject, targetProject, targetBlock ) + + if optimization then + local targetProjectResolvedUses = targetProject.resolvedUses -- get table of already resolved uses for this projects + local sourceProjectResolvedUses = sourceProject.resolvedUses + local sourceProjectName = sourceProject.name + local targetBlockResolvedUsesLinks = targetBlock.resolvedUsesLinks - sourceProject = p.container.bake(sourceProject) + -- Iterate over the source project uses + for usageName,usageInfo in pairs(sourceProjectResolvedUses) do + -- project can use own usage and it is valid use + -- without this check we will get error about double push on stack + if usageName ~= usageInfo.parent then + pushUsage( '"bake" ' .. usageInfo.parent, '???' ) + pushUsage( usageName, usageInfo.parent ) + popUsage(2) + end + if not targetProjectResolvedUses[usageName] then - -- Iterate over the source project configurations and propagate - -- any 'uses' in each configuration from the source project to - -- the target project. - -- We do this per configuration so we know the 'kind' of that - -- configuration to influence what options are propagated - -- through the project. + usage = usageInfo.block + -- sourceProject include own usage + if usage then + resolveUsage( targetProject, targetBlock, usage ) + end - for configName,configCtx in pairs(sourceProject.configs) do + if usageInfo.type == "project" then + -- When referring to a project, 'uses' acts like 'links' too. + table.insert(targetBlockResolvedUsesLinks, usageName ) + end - -- If this source configuration is link target, we only want to - -- propagate buildoptions. - -- For example, if the source project is a shared library, - -- that linked with a static library, we don't also want the - -- target to link the same static library since it's already - -- linked in the source project. + targetProjectResolvedUses[usageName] = usageInfo + end + end + else + local pushCount = 0 - local configKind = context.fetchvalue(configCtx, 'kind') + -- Store feched uses for future usage + if sourceProject.ctx.usesValues == nil then + sourceProject.ctx.usesValues = context.fetchvalue( sourceProject.ctx, 'uses' ) + end - local configUsageFilter - if configKind ~= 'StaticLib' then - configUsageFilter = 'buildoptions' + local usesToResolve = {} + -- push all uses on stack before resolve, it guranty that all uses on current level are before uses are resolved + -- this is essential for duplicated uses detection + for k,v in ipairs(sourceProject.ctx.usesValues ) do + if type(v) == "table" then + for _, vName in ipairs(v) do + pushCount = pushCount + insertUsage( usesToResolve, vName, sourceProject.name ) + end + else + pushCount = pushCount + insertUsage( usesToResolve, v, sourceProject.name ) + end + end + + -- Iterate over the source project uses' + for _,v in ipairs(usesToResolve) do + if v == sourceProject.name then + if not usage then + error( "Project " .. sourceProject.name .. " used itself but declares no usage") + end + else + resolveUse( targetProject, + targetBlock, + v ) + end + end + + if pushCount > 0 then + popUsage(pushCount) + end + end + end + +--- +-- Resolve a single 'use' into a target block of a target project +-- + + resolveUse = function( targetProject, targetBlock, usageName ) + + -- get table of already resolved uses for this projects + local resolvedUses = targetProject.resolvedUses + + local usageType = "project" + local usageScriptPath = "" + -- if use already resolved SKIP it + if not resolvedUses[usageName] then + + local targetWorkspace = targetProject.solution + + local sourceProject = p.workspace.findproject( targetWorkspace, usageName ) + local usage = p.workspace.findusage( targetWorkspace, usageName ) + + if sourceProject ~= nil and sourceProject.name ~= targetProject.name then + + verbosef("\nProject %s is using project %s\n", + targetProject.name, sourceProject.name ) + + -- The source project might not be baked yet, bake it now. + -- We need this for the context. + bakeProject( sourceProject, targetProject ) + + resolveUses( sourceProject, targetProject, targetBlock ) + + -- sourceProject include own usage + if usage then + resolveUsage( targetProject, targetBlock, usage ) end - if compatibleUsage( usageFilter, configUsageFilter ) then - - verbosef("== %s %s", configName,configKind) - - -- Resolve the 'uses' in this configuration to the target. - -- Pass on this configuration as a criteria for the resolve. - - -- todo: already exists in configCtx._criteria - -- todo: should be merging with inheritedCriteria? - local configCriteria = - p.criteria.new( {'configurations:' .. configName } ) - - local configUses = context.fetchvalue( configCtx, 'uses' ) - - local usedSelf = false - - for k,v in ipairs(configUses) do - - if v == sourceProject.name then - usedSelf = true - if usage then - resolveUsage( targetProject, targetBlock, - usage, v, configUsageFilter or usageFilter, - configCriteria ) - else - error( "Project " .. sourceProject.name - .. " used itself but declares no usage") - end - else - resolveUse( targetProject, - targetBlock, - v, - configUsageFilter or usageFilter, - configCriteria ) - end - end - - -- The project did not use it's own usage - -- It is only exposing it only visible to other project. - -- Don't apply configUsageFilter - -- TO CONSIDER: not sure about that, but makes sense if we've not - -- linked internally what's specified in our usage. - - if usage and not usedSelf then - - verbosef("= Project %s config %s, did not resolve own usage to %s. Resolving now\n", - sourceProject.name, configName, targetProject.name ) - - resolveUsage( targetProject, targetBlock, - usage, usageName, usageFilter, - configCriteria ) - end - + -- When referring to a project, 'uses' acts like 'links' too. + table.insert(targetBlock.resolvedUsesLinks, usageName ) + usageType = "project" + usageScriptPath = sourceProject.script + elseif usage ~= nil then + resolveUsage( targetProject, targetBlock, usage ) + usageType = "usage" + usageScriptPath = usage.script + elseif sourceProject ~= nil then + error( "Project " .. sourceProject.name + .. " used itself but declares no usage") + else + -- throw an error on Windows and keep going on Linux + local isLinux = ( package.config:sub(1,1) == '/' ) + local messageText = "Use of " + .. "'" .. usageName.. "'" + .. ", is not defined as a usage or project in workspace " + .. "'" .. targetWorkspace.name .. "'" .. " for " .. targetProject.name + if isLinux then + print( messageText ) + return + else + error( messageText ) end end - -- When referring to a project, 'uses' acts like 'links' too. - - if usageFilter == nil or usageFilter == "linkoptions" then - - local linkBlock = targetBlock - - -- If we don't have a usageFilter, make sure this link only - -- occurs on linkable targets. - -- TODO: Functionally similar to block insertion for usages. - -- same requirements/todos/bugs, should dedupe. - - if usageFilter == nil then - linkBlock = {} - - -- todo: would be nice not to do this. - -- needs to be in sync with internal block logic. - if targetBlock.filename then - linkBlock._basedir = targetBlock.basedir - else - linkBlock._basedir = targetBlock._basedir - end - - local linkCriteria = - p.criteria.new( {"kind:not StaticLib"} ) - - linkBlock._criteria = {} - - linkBlock._criteria.patterns = table.join( - targetBlock._criteria.patterns, - linkCriteria.patterns) - - linkBlock._criteria.data = - p.criteria._compile(linkBlock._criteria.patterns) - - table.insert( targetProject.blocks, linkBlock ) - end - - local key = "links" - local field = p.field.get(key) - linkBlock[key] = p.field.store( field, - linkBlock[key], - sourceProject.name ) - end - - elseif usage ~= nil then - - resolveUsage( targetProject, targetBlock, - usage, usageName, usageFilter, - inheritedCriteria ) + resolvedUses[usageName] = { ["type"] = usageType, ["script"] = usageScriptPath, ["block"] = usage, ["parent"] = targetProject.name } end - end --- +--- -- Resolve all uses from a target block in a target project -- - function resolveAllUsesInBlock( targetProject, - targetBlock, - inheritedUsageFilter ) + local function resolveAllUsesInBlock( targetProject, targetBlock ) - if targetBlock.uses then - - for _, usageKey in ipairs(targetBlock.uses) do - - local usageName = usageKey - local usageFilter - - local separatorPos = usageKey:find(':') - if separatorPos then - usageName = usageKey:sub(separatorPos + 1) - usageFilter = usageKey:sub(0, separatorPos - 1) + local pushCount = 0 + usesToResolve = {} + + for _, usageKey in ipairs(targetBlock.uses) do + if type(usageKey) == "table" then + for _, usageName in ipairs(usageKey) do + pushCount = pushCount + insertUsage( usesToResolve, usageName, targetProject.name ) end - - if compatibleUsage(inheritedUsageFilter, usageFilter) then - resolveUse( targetProject, targetBlock, usageName, - inheritedUsageFilter or usageFilter ) - end - - end - + else + pushCount = pushCount + insertUsage( usesToResolve, usageKey, targetProject.name ) + end end + + for _, usageName in ipairs(usesToResolve) do + resolveUse( targetProject, targetBlock, usageName ) + end + if pushCount > 0 then + popUsage(pushCount) + end + end -- -- Before baking a project, resolve all the 'uses' +-- + + function bakeProjectUsage( prj ) + + -- do not resolve "uses" twice + if prj.resolvedUses == nil then + -- create table of already resolved uses for this projects + prj.resolvedUses = {} + + stashStack() + + local blocks = {} + for k, v in pairs(prj.blocks) do + blocks[k] = v + end + pushUsage( prj.name, '"bake"' ) + for _, block in pairs(blocks) do + if block.uses then + block.resolvedUsesLinks = {} + resolveAllUsesInBlock(prj, block) + -- When referring to a project, 'uses' acts like 'links' too. + block[keyLinks] = field.store( fieldLinks, block[keyLinks], fixOrder( block.resolvedUsesLinks ) ) + end + end + popUsage() + + unstashStack() + end + end + +-- +-- Put the linked projects in the right order +-- + + function fixOrder( resolvedUsesLinks ) + if next(resolvedUsesLinks) ~= nil then + local fixedResolvedUsesLinks = {} + for i = #resolvedUsesLinks, 1, -1 do + table.insert( fixedResolvedUsesLinks, resolvedUsesLinks[ i ] ) + end + return fixedResolvedUsesLinks + end + return resolvedUsesLinks + end + +-- +-- Register override -- premake.override(p.project, "bake", function(base, self) - -- Keep the list stable while we iterate and modify it - - local blocks = {} - for k, v in pairs(self.blocks) do - blocks[k] = v - end - - for _, block in pairs(blocks) do - resolveAllUsesInBlock(self, block) - end + bakeProjectUsage(self) return base(self)