summaryrefslogtreecommitdiff
path: root/xmake/modules
diff options
context:
space:
mode:
authorChristian Rendina <[email protected]>2025-04-10 09:50:37 +0200
committerChristian Rendina <[email protected]>2025-04-10 09:50:37 +0200
commit78723913d76fb8b615df34541236b8ea588d30db (patch)
treeb6b6ff550e4a2f73d94c63a61876cec31a4b3ae3 /xmake/modules
parent2051c13f735a626f7b6fd8b98aa69f01fd9cef7e (diff)
parentfd49b7754c6709a87b5beb5526788bbc1a663965 (diff)
Merge branch 'dev' of https://github.com/xmake-io/xmake into dev
Diffstat (limited to 'xmake/modules')
-rw-r--r--xmake/modules/async/jobgraph.lua263
-rw-r--r--xmake/modules/async/runjobs.lua8
-rw-r--r--xmake/modules/cli/amalgamate.lua2
-rw-r--r--xmake/modules/detect/sdks/find_emsdk.lua8
-rw-r--r--xmake/modules/package/manager/cmake/find_package.lua8
-rw-r--r--xmake/modules/package/tools/autoconf.lua1
-rw-r--r--xmake/modules/private/action/build/build_binary.lua38
-rw-r--r--xmake/modules/private/action/build/build_moduleonly.lua (renamed from xmake/modules/private/diagnosis/dump_buildjobs.lua)12
-rw-r--r--xmake/modules/private/action/build/build_object.lua26
-rw-r--r--xmake/modules/private/action/build/build_shared.lua26
-rw-r--r--xmake/modules/private/action/build/build_static.lua26
-rw-r--r--xmake/modules/private/action/build/link_objects.lua72
-rw-r--r--xmake/modules/private/action/build/object.lua28
-rw-r--r--xmake/modules/private/action/build/pcheader.lua6
-rw-r--r--xmake/modules/private/action/build/prepare_files.lua27
-rw-r--r--xmake/modules/private/action/build/target.lua786
-rw-r--r--xmake/modules/private/action/require/impl/package.lua2
-rw-r--r--xmake/modules/private/diagnosis/dump_targets.lua74
-rw-r--r--xmake/modules/private/utils/rule.lua72
-rw-r--r--xmake/modules/private/utils/rule_groups.lua95
20 files changed, 1395 insertions, 185 deletions
diff --git a/xmake/modules/async/jobgraph.lua b/xmake/modules/async/jobgraph.lua
new file mode 100644
index 000000000..1ed5873b7
--- /dev/null
+++ b/xmake/modules/async/jobgraph.lua
@@ -0,0 +1,263 @@
+--!A cross-platform build utility based on Lua
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-- Copyright (C) 2015-present, TBOOX Open Source Group.
+--
+-- @author ruki
+-- @file jobgraph.lua
+--
+
+-- imports
+import("core.base.object")
+import("core.base.list")
+import("core.base.graph")
+import("core.base.hashset")
+
+-- define module
+local jobqueue = jobqueue or object {_init = {"_jobgraph", "_dag"}}
+local jobgraph = jobgraph or object {_init = {"_name", "_jobs", "_size", "_dag", "_groups"}}
+
+-- remove the finished job
+function jobqueue:remove(job)
+ local dag = self._dag
+ dag:partial_topo_sort_remove(job)
+end
+
+-- get a free job from the job queue
+function jobqueue:getfree()
+ local dag = self._dag
+::continue::
+ local freejob, has_cycle = dag:partial_topo_sort_next()
+ if has_cycle then
+ local names = {}
+ local cycle = dag:find_cycle()
+ if cycle then
+ for _, job in ipairs(cycle) do
+ table.insert(names, job.name)
+ end
+ table.insert(names, names[1])
+ end
+ raise("%s: circular job dependency detected!\n%s", self._jobgraph, table.concat(names, "\n -> "))
+ end
+ -- if it's a fake job, we need to skip it and continue to get the next job
+ if freejob and not freejob.run then
+ dag:partial_topo_sort_remove(freejob)
+ goto continue
+ end
+ return freejob
+end
+
+-- add a job to the jobgraph
+--
+-- e.g.
+-- jobgraph:add("xxx", function (index, total, opt)
+-- end)
+--
+-- @param name the job name
+-- @param run the job run command/script
+-- @param opt the job options, e.g. {groups = {"xxx"}}
+--
+function jobgraph:add(name, run, opt)
+ opt = opt or {}
+ local dag = self._dag
+ local jobs = self._jobs
+ if not jobs[name] then
+ local job = {name = name, run = run, distcc = opt.distcc}
+ jobs[name] = job
+ dag:add_vertex(job)
+ self._size = self._size + 1
+
+ if self._current_groups or opt.groups then
+ local job_groups = table.join(self._current_groups or {}, opt.groups)
+ for _, group_name in ipairs(job_groups) do
+ local groups = self._groups[group_name]
+ if not groups then
+ groups = {}
+ self._groups[group_name] = groups
+ end
+ table.insert(groups, job)
+ end
+ end
+ else
+ raise("job(%s): has already been added!", name)
+ end
+end
+
+-- remove a given job
+function jobgraph:remove(name)
+ local dag = self._dag
+ local jobs = self._jobs
+ local job = jobs[name]
+ if job then
+ assert(self._size > 0)
+ jobs[name] = nil
+ dag:remove_vertex(job)
+ self._size = self._size - 1
+ end
+end
+
+-- has the given job or group?
+function jobgraph:has(name)
+ return (self._jobs[name] or self._groups[name]) ~= nil
+end
+
+-- enter group to add jobs
+--
+-- e.g.
+-- jobgraph:group("foo", function ()
+-- jobgraph:add("job1", function (index, total, opt)
+-- TODO
+-- end)
+-- jobgraph:add("job2", function (index, total, opt)
+-- TODO
+-- end)
+-- end)
+function jobgraph:group(name, callback)
+ local current_groups = self._current_groups
+ if current_groups == nil then
+ current_groups = {}
+ self._current_groups = current_groups
+ end
+ table.insert(current_groups, name)
+ callback()
+ table.remove(current_groups)
+end
+
+-- add job orders, e.g. add_orders(a, b, c, ...): a -> b -> c, ...
+--
+-- and it supports nil, e.g add_orders("foo", nil, "bar", ...)
+-- and it also supports to add orders list, e.g. add_orders(orders)
+--
+function jobgraph:add_orders(...)
+ local prev
+ local prev_is_group
+ local prev_name
+ local dag = self._dag
+ local jobs = self._jobs
+ local groups = self._groups
+ local orders = table.pack(...)
+ local count = orders.n
+ if count == 1 and type(orders[1]) == "table" then
+ orders = orders[1]
+ count = #orders
+ end
+ for i = 1, count do
+ local name = orders[i]
+ if name then
+ local curr_is_group = false
+ local curr = jobs[name]
+ if not curr then
+ curr = groups[name]
+ curr_is_group = true
+ end
+ assert(curr, "job(%s) not found in jobgraph(%s)", name, self)
+ if prev then
+ if prev_is_group and curr_is_group then
+ -- we use a bridge job as a node to bridge the two groups.
+ local bridge = {from_group = prev_name, to_group = name}
+ for _, job in ipairs(prev) do
+ if not dag:has_edge(job, bridge) then
+ dag:add_edge(job, bridge)
+ end
+ end
+ for _, job in ipairs(curr) do
+ if not dag:has_edge(bridge, job) then
+ dag:add_edge(bridge, job)
+ end
+ end
+ elseif curr_is_group then
+ for _, job in ipairs(curr) do
+ if not dag:has_edge(prev, job) then
+ dag:add_edge(prev, job)
+ end
+ end
+ elseif prev_is_group then
+ for _, job in ipairs(prev) do
+ if not dag:has_edge(job, curr) then
+ dag:add_edge(job, curr)
+ end
+ end
+ else
+ if not dag:has_edge(prev, curr) then
+ dag:add_edge(prev, curr)
+ end
+ end
+ end
+ prev = curr
+ prev_is_group = curr_is_group
+ prev_name = name
+ end
+ end
+end
+
+-- build a job queue
+function jobgraph:build()
+ local dag = self._dag
+ dag:partial_topo_sort_reset()
+ return jobqueue {self, dag}
+end
+
+-- get jobs
+function jobgraph:jobs()
+ return self._jobs
+end
+
+-- get jobgraph name
+function jobgraph:name()
+ return self._name
+end
+
+-- get job size
+function jobgraph:size()
+ return self._size
+end
+
+-- is empty?
+function jobgraph:empty()
+ return self:size() == 0
+end
+
+-- dump jobgraph
+function jobgraph:dump()
+ print("================================ %s ================================", self)
+ for _, node in ipairs(self._dag:vertices()) do
+ debug.setmetatable(node, {__tostring = function (v)
+ if v.from_group and v.to_group then
+ return string.format("${dim}bridge<%s, %s>${clear}", v.from_group, v.to_group)
+ end
+ return string.format("${color.dump.string_quote}%s${clear}", v.name)
+ end})
+ end
+ self._dag:dump()
+
+ print("")
+ print("groups:")
+ for name, jobs in pairs(self._groups) do
+ print(" group(%s):", name)
+ for _, job in ipairs(jobs) do
+ cprint(" %s", job)
+ end
+ end
+ print("")
+end
+
+-- tostring
+function jobgraph:__tostring()
+ return string.format("<jobgraph:%s/%d>", self:name() or "anonymous", self:size())
+end
+
+-- new a jobgraph
+function new(name)
+ return jobgraph {name, {}, 0, graph.new(true), {}}
+end
diff --git a/xmake/modules/async/runjobs.lua b/xmake/modules/async/runjobs.lua
index 0c2a6f366..adf68018d 100644
--- a/xmake/modules/async/runjobs.lua
+++ b/xmake/modules/async/runjobs.lua
@@ -64,6 +64,11 @@ function main(name, jobs, opt)
local group_name = name
local jobs_cb = type(jobs) == "function" and jobs or nil
assert(timeout < 60000, "runjobs: invalid timeout!")
+
+ -- build jobs queue
+ if type(jobs) == "table" and jobs.build then
+ jobs = jobs:build()
+ end
assert(jobs, "runjobs: no jobs!")
-- show waiting tips?
@@ -158,6 +163,7 @@ function main(name, jobs, opt)
local abort_errors
local progress_wrapper = {}
local job_pending
+ local progress_factor = opt.progress_factor or 1.0
progress_wrapper.current = function ()
return count
end
@@ -166,7 +172,7 @@ function main(name, jobs, opt)
end
progress_wrapper.percent = function ()
if total and total > 0 then
- return math.floor((count * 100) / total)
+ return math.floor((count * progress_factor * 100) / total)
else
return 0
end
diff --git a/xmake/modules/cli/amalgamate.lua b/xmake/modules/cli/amalgamate.lua
index 8c7ec6fd7..d0a5ba40c 100644
--- a/xmake/modules/cli/amalgamate.lua
+++ b/xmake/modules/cli/amalgamate.lua
@@ -96,7 +96,7 @@ function _generate_file(target, inputpaths, outputpath, uniqueid)
_generate_include_graph(target, inputpaths, gh, {})
-- sort file paths and remove root path
- local filepaths = gh:topological_sort()
+ local filepaths = gh:topo_sort()
table.remove(filepaths, 1)
-- generate amalgamate file
diff --git a/xmake/modules/detect/sdks/find_emsdk.lua b/xmake/modules/detect/sdks/find_emsdk.lua
index cbc6998b2..ec8553692 100644
--- a/xmake/modules/detect/sdks/find_emsdk.lua
+++ b/xmake/modules/detect/sdks/find_emsdk.lua
@@ -33,10 +33,17 @@ function _find_emsdkdir(sdkdir)
table.insert(paths, sdkdir)
end
table.insert(paths, "$(env EMSDK)")
+ if is_host("linux") then
+ table.join2(paths, {"/usr/share/emscripten/", "/usr/lib/emscripten/"})
+ end
local emsdk = find_file("emsdk.py", paths, {suffixes = subdirs})
if emsdk then
return path.directory(emsdk)
end
+ local emcc_py = find_file("emcc.py", paths, {suffixes = subdirs})
+ if emcc_py then
+ return path.directory(emcc_py)
+ end
end
-- find emsdk
@@ -53,6 +60,7 @@ function _find_emsdk(sdkdir)
local subdirs = {}
table.insert(subdirs, path.join("*", "emscripten"))
local emcc = find_file("emcc", sdkdir, {suffixes = subdirs})
+ emcc = emcc or find_file("emcc.py", sdkdir)
if emcc then
emscripten = path.directory(emcc)
end
diff --git a/xmake/modules/package/manager/cmake/find_package.lua b/xmake/modules/package/manager/cmake/find_package.lua
index 6c3a9f194..4c71291d7 100644
--- a/xmake/modules/package/manager/cmake/find_package.lua
+++ b/xmake/modules/package/manager/cmake/find_package.lua
@@ -83,6 +83,14 @@ function _find_package(cmake, name, opt)
cmakefile:print("list(APPEND CMAKE_MODULE_PATH \"%s\")", (moduledir:gsub("\\", "/")))
end
end
+ -- https://github.com/xmake-io/xmake/issues/6296
+ local prefixdirs = configs.prefixdirs or opt.prefixdirs
+ if prefixdirs then
+ for _, prefixdir in ipairs(prefixdirs) do
+ cmakefile:print("list(APPEND CMAKE_PREFIX_PATH \"%s\")", (prefixdir:gsub("\\", "/")))
+ end
+ end
+
-- e.g. set(Boost_USE_STATIC_LIB ON)
local presets = configs.presets or opt.presets
if presets then
diff --git a/xmake/modules/package/tools/autoconf.lua b/xmake/modules/package/tools/autoconf.lua
index 8d312a76a..821ab759f 100644
--- a/xmake/modules/package/tools/autoconf.lua
+++ b/xmake/modules/package/tools/autoconf.lua
@@ -434,6 +434,7 @@ function buildenvs(package, opt)
if is_host("windows") then
envs.CC = _translate_windows_bin_path(envs.CC)
+ envs.CXX = _translate_windows_bin_path(envs.CXX)
envs.AS = _translate_windows_bin_path(envs.AS)
envs.AR = _translate_windows_bin_path(envs.AR)
envs.LD = _translate_windows_bin_path(envs.LD)
diff --git a/xmake/modules/private/action/build/build_binary.lua b/xmake/modules/private/action/build/build_binary.lua
new file mode 100644
index 000000000..3863b8550
--- /dev/null
+++ b/xmake/modules/private/action/build/build_binary.lua
@@ -0,0 +1,38 @@
+--!A cross-platform build utility based on Lua
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-- Copyright (C) 2015-present, TBOOX Open Source Group.
+--
+-- @author ruki
+-- @file build_binary.lua
+--
+
+-- imports
+import("build_object")
+import("private.action.build.target", {alias = "target_buildutils"})
+
+function main(jobgraph, target, opt)
+ local objects_group = target:fullname() .. "/objects"
+ local jobsize = jobgraph:size()
+ jobgraph:group(objects_group, function ()
+ build_object(jobgraph, target, opt)
+ end)
+ if jobgraph:size() > jobsize then
+ local link_group = target:fullname() .. "/link"
+ jobgraph:group(link_group, function ()
+ target_buildutils.add_linkjobs(jobgraph, target, opt)
+ end)
+ jobgraph:add_orders(objects_group, link_group)
+ end
+end
diff --git a/xmake/modules/private/diagnosis/dump_buildjobs.lua b/xmake/modules/private/action/build/build_moduleonly.lua
index 16083a40b..32c07375e 100644
--- a/xmake/modules/private/diagnosis/dump_buildjobs.lua
+++ b/xmake/modules/private/action/build/build_moduleonly.lua
@@ -15,16 +15,12 @@
-- Copyright (C) 2015-present, TBOOX Open Source Group.
--
-- @author ruki
--- @file dump_buildjobs.lua
+-- @file build_moduleonly.lua
--
-- imports
-import("core.project.config")
-import("actions.build.build", {rootdir = os.programdir()})
+import("build_object")
--- dump the build jobs, e.g. xmake l private.diagnosis.dump_buildjobs [targetname]
-function main(targetname)
- config.load()
- print(build.get_batchjobs(targetname))
+function main(jobgraph, target, opt)
+ build_object(jobgraph, target, opt)
end
-
diff --git a/xmake/modules/private/action/build/build_object.lua b/xmake/modules/private/action/build/build_object.lua
new file mode 100644
index 000000000..08f82092b
--- /dev/null
+++ b/xmake/modules/private/action/build/build_object.lua
@@ -0,0 +1,26 @@
+--!A cross-platform build utility based on Lua
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-- Copyright (C) 2015-present, TBOOX Open Source Group.
+--
+-- @author ruki
+-- @file build_object.lua
+--
+
+-- imports
+import("private.action.build.target", {alias = "target_buildutils"})
+
+function main(jobgraph, target, opt)
+ target_buildutils.add_filejobs(jobgraph, target, opt)
+end
diff --git a/xmake/modules/private/action/build/build_shared.lua b/xmake/modules/private/action/build/build_shared.lua
new file mode 100644
index 000000000..9223093b4
--- /dev/null
+++ b/xmake/modules/private/action/build/build_shared.lua
@@ -0,0 +1,26 @@
+--!A cross-platform build utility based on Lua
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-- Copyright (C) 2015-present, TBOOX Open Source Group.
+--
+-- @author ruki
+-- @file build_shared.lua
+--
+
+-- imports
+import("build_binary")
+
+function main(jobgraph, target, opt)
+ build_binary(jobgraph, target, opt)
+end
diff --git a/xmake/modules/private/action/build/build_static.lua b/xmake/modules/private/action/build/build_static.lua
new file mode 100644
index 000000000..6d47394ed
--- /dev/null
+++ b/xmake/modules/private/action/build/build_static.lua
@@ -0,0 +1,26 @@
+--!A cross-platform build utility based on Lua
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-- Copyright (C) 2015-present, TBOOX Open Source Group.
+--
+-- @author ruki
+-- @file build_static.lua
+--
+
+-- imports
+import("build_binary")
+
+function main(jobgraph, target, opt)
+ build_binary(jobgraph, target, opt)
+end
diff --git a/xmake/modules/private/action/build/link_objects.lua b/xmake/modules/private/action/build/link_objects.lua
new file mode 100644
index 000000000..396896d00
--- /dev/null
+++ b/xmake/modules/private/action/build/link_objects.lua
@@ -0,0 +1,72 @@
+--!A cross-platform build utility based on Lua
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-- Copyright (C) 2015-present, TBOOX Open Source Group.
+--
+-- @author ruki
+-- @file link_objects.lua
+--
+
+-- imports
+import("core.base.option")
+import("core.tool.linker")
+import("core.tool.compiler")
+import("core.project.depend")
+import("utils.progress")
+import("build_object")
+import("private.action.build.target", {alias = "target_buildutils"})
+
+-- do link target
+function _do_link_target(target, opt)
+ local linkinst = linker.load(target:kind(), target:sourcekinds(), {target = target})
+ local linkflags = linkinst:linkflags({target = target})
+
+ -- need build this target?
+ local depfiles = target_buildutils.get_linkdepfiles(target)
+ local dryrun = option.get("dry-run")
+ local depvalues = {linkinst:program(), linkflags}
+ depend.on_changed(function ()
+ local filename = target:filename()
+ if target:namespace() then
+ filename = target:namespace() .. "::" .. filename
+ end
+ progress.show(opt.progress, "${color.build.target}linking.$(mode) %s", filename)
+
+ local targetfile = target:targetfile()
+ local objectfiles = target:objectfiles()
+ local verbose = option.get("verbose")
+ if verbose then
+ -- show the full link command with raw arguments, it will expand @xxx.args for msvc/link on windows
+ print(linkinst:linkcmd(objectfiles, targetfile, {linkflags = linkflags, rawargs = true}))
+ end
+
+ if not dryrun then
+ assert(linkinst:link(objectfiles, targetfile, {linkflags = linkflags}))
+ end
+ end, {dependfile = target:dependfile(),
+ lastmtime = os.mtime(target:targetfile()),
+ changed = target:is_rebuilt() or option.get("linkonly"),
+ values = depvalues, files = depfiles, dryrun = dryrun})
+end
+
+function main(jobgraph, target, opt)
+ opt = opt or {}
+ local buildcmds = opt.buildcmds
+ local linkjob = target:fullname() .. "/link_objects"
+ jobgraph:add(linkjob, function (index, total, opt)
+ if not buildcmds then
+ _do_link_target(target, opt)
+ end
+ end)
+end
diff --git a/xmake/modules/private/action/build/object.lua b/xmake/modules/private/action/build/object.lua
index ae6bf9f4a..a8b66aa54 100644
--- a/xmake/modules/private/action/build/object.lua
+++ b/xmake/modules/private/action/build/object.lua
@@ -143,8 +143,8 @@ function build(target, sourcebatch, opt)
end
end
--- add batch jobs to build the source files
-function main(target, batchjobs, sourcebatch, opt)
+-- add build jobs to batchjobs
+function _add_batchjobs(target, batchjobs, sourcebatch, opt)
local rootjob = opt.rootjob
for i = 1, #sourcebatch.sourcefiles do
local sourcefile = sourcebatch.sourcefiles[i]
@@ -157,3 +157,27 @@ function main(target, batchjobs, sourcebatch, opt)
end, {rootjob = rootjob, distcc = opt.distcc})
end
end
+
+-- add build jobs to jobgraph
+function _add_jobgraph(target, jobgraph, sourcebatch, opt)
+ for i = 1, #sourcebatch.sourcefiles do
+ local sourcefile = sourcebatch.sourcefiles[i]
+ local objectfile = sourcebatch.objectfiles[i]
+ local dependfile = sourcebatch.dependfiles[i]
+ local sourcekind = assert(sourcebatch.sourcekind, "%s: sourcekind not found!", sourcefile)
+ local jobname = target:fullname() .. "/obj/" .. sourcefile
+ jobgraph:add(jobname, function (index, total, jobopt)
+ local build_opt = table.join({objectfile = objectfile, dependfile = dependfile, sourcekind = sourcekind, progress = jobopt.progress}, opt)
+ build_object(target, sourcefile, build_opt)
+ end, {distcc = opt.distcc})
+ end
+end
+
+function main(target, jobgraph, sourcebatch, opt)
+ opt = opt or {}
+ if jobgraph.add_orders then
+ _add_jobgraph(target, jobgraph, sourcebatch, opt)
+ else
+ _add_batchjobs(target, jobgraph, sourcebatch, opt)
+ end
+end
diff --git a/xmake/modules/private/action/build/pcheader.lua b/xmake/modules/private/action/build/pcheader.lua
index 20e857d7f..ec06c8fb8 100644
--- a/xmake/modules/private/action/build/pcheader.lua
+++ b/xmake/modules/private/action/build/pcheader.lua
@@ -20,7 +20,7 @@
-- imports
import("core.language.language")
-import("object")
+import("object", {alias = "build_objects"})
function config(target, langkind, opt)
local pcheaderfile = target:pcheaderfile(langkind)
@@ -55,7 +55,7 @@ function config(target, langkind, opt)
end
-- add batch jobs to build the precompiled header file
-function build(target, langkind, opt)
+function build(target, jobgraph, langkind, opt)
local pcheaderfile = target:pcheaderfile(langkind)
if pcheaderfile then
local sourcefile = pcheaderfile
@@ -63,6 +63,6 @@ function build(target, langkind, opt)
local dependfile = target:dependfile(objectfile)
local sourcekind = language.langkinds()[langkind]
local sourcebatch = {sourcekind = sourcekind, sourcefiles = {sourcefile}, objectfiles = {objectfile}, dependfiles = {dependfile}}
- object.build(target, sourcebatch, opt)
+ build_objects(target, jobgraph, sourcebatch, opt)
end
end
diff --git a/xmake/modules/private/action/build/prepare_files.lua b/xmake/modules/private/action/build/prepare_files.lua
new file mode 100644
index 000000000..9f0f47ef0
--- /dev/null
+++ b/xmake/modules/private/action/build/prepare_files.lua
@@ -0,0 +1,27 @@
+--!A cross-platform build utility based on Lua
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-- Copyright (C) 2015-present, TBOOX Open Source Group.
+--
+-- @author ruki
+-- @file prepare_files.lua
+--
+
+-- imports
+import("core.base.option")
+import("private.action.build.target", {alias = "target_buildutils"})
+
+function main(jobgraph, target, opt)
+ target_buildutils.add_filejobs(jobgraph, target, opt)
+end
diff --git a/xmake/modules/private/action/build/target.lua b/xmake/modules/private/action/build/target.lua
new file mode 100644
index 000000000..c302408ac
--- /dev/null
+++ b/xmake/modules/private/action/build/target.lua
@@ -0,0 +1,786 @@
+--!A cross-platform build utility based on Lua
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-- Copyright (C) 2015-present, TBOOX Open Source Group.
+--
+-- @author ruki
+-- @file target.lua
+--
+
+-- imports
+import("core.base.option")
+import("core.base.hashset")
+import("core.project.rule")
+import("core.project.config")
+import("core.project.project")
+import("async.runjobs", {alias = "async_runjobs"})
+import("async.jobgraph", {alias = "async_jobgraph"})
+import("private.utils.batchcmds")
+import("private.utils.rule", {alias = "rule_utils"})
+
+-- clean target for rebuilding
+function _clean_target(target)
+ if target:targetfile() then
+ os.tryrm(target:symbolfile())
+ os.tryrm(target:targetfile())
+ end
+end
+
+-- match source files
+function _match_sourcefiles(sourcefile, filepatterns)
+ for _, filepattern in ipairs(filepatterns) do
+ if sourcefile:match(filepattern.pattern) == sourcefile then
+ if filepattern.excludes then
+ if filepattern.rootdir and sourcefile:startswith(filepattern.rootdir) then
+ sourcefile = sourcefile:sub(#filepattern.rootdir + 2)
+ end
+ for _, exclude in ipairs(filepattern.excludes) do
+ if sourcefile:match(exclude) == sourcefile then
+ return false
+ end
+ end
+ end
+ return true
+ end
+ end
+end
+
+-- match sourcebatches
+function _match_sourcebatches(target, filepatterns)
+ local newbatches = {}
+ local sourcecount = 0
+ for rulename, sourcebatch in pairs(target:sourcebatches()) do
+ local objectfiles = sourcebatch.objectfiles
+ local dependfiles = sourcebatch.dependfiles
+ local sourcekind = sourcebatch.sourcekind
+ for idx, sourcefile in ipairs(sourcebatch.sourcefiles) do
+ if _match_sourcefiles(sourcefile, filepatterns) then
+ local newbatch = newbatches[rulename]
+ if not newbatch then
+ newbatch = {}
+ newbatch.sourcekind = sourcekind
+ newbatch.rulename = rulename
+ newbatch.sourcefiles = {}
+ end
+ table.insert(newbatch.sourcefiles, sourcefile)
+ if objectfiles then
+ newbatch.objectfiles = newbatch.objectfiles or {}
+ table.insert(newbatch.objectfiles, objectfiles[idx])
+ end
+ if dependfiles then
+ newbatch.dependfiles = newbatch.dependfiles or {}
+ table.insert(newbatch.dependfiles, dependfiles[idx])
+ end
+ newbatches[rulename] = newbatch
+ sourcecount = sourcecount + 1
+ end
+ end
+ end
+ if sourcecount > 0 then
+ return newbatches
+ end
+end
+
+-- add targetjobs and deps orders
+function _add_targetjobs_orders(jobgraph, target, dep, opt)
+ local jobname, jobname_dep
+ local job_kind = opt.job_kind
+ if dep:policy("build.fence") or dep:policy("build.across_targets_in_parallel") == false then
+ jobname = string.format("%s/begin_%s", target:fullname(), job_kind)
+ jobname_dep = string.format("%s/end_%s", dep:fullname(), job_kind)
+ -- build.across_targets_in_parallel is deprecated
+ if dep:policy("build.across_targets_in_parallel") == false then
+ wprint("policy(\"build.across_targets_in_parallel\") has been deprecated, please use policy(\"build.fence\") instead of it.")
+ end
+ elseif job_kind == "build" then
+ jobname = target:fullname() .. "/link"
+ jobname_dep = dep:fullname() .. "/link"
+ if not jobgraph:has(jobname) then
+ jobname = string.format("%s/begin_%s", target:fullname(), job_kind)
+ end
+ if not jobgraph:has(jobname_dep) then
+ jobname_dep = string.format("%s/end_%s", dep:fullname(), job_kind)
+ end
+ end
+ if jobname and jobname_dep and jobgraph:has(jobname) and jobgraph:has(jobname_dep) then
+ jobgraph:add_orders(jobname_dep, jobname)
+ end
+end
+
+-- add target jobs for the builtin script
+function add_targetjobs_for_builtin_script(jobgraph, target, opt)
+ opt = opt or {}
+ local job_kind = opt.job_kind
+ if target:is_static() or target:is_binary() or target:is_shared() or target:is_object() or target:is_moduleonly() then
+ if job_kind == "prepare" then
+ import("private.action.build.prepare_files", {anonymous = true})(jobgraph, target, opt)
+ elseif job_kind == "link" then
+ import("private.action.build.link_objects", {anonymous = true})(jobgraph, target, opt)
+ else
+ import("private.action.build.build_" .. target:kind(), {anonymous = true})(jobgraph, target, opt)
+ end
+ end
+end
+
+-- add target jobs for the given script
+function add_targetjobs_for_script(jobgraph, target, instance, opt)
+ opt = opt or {}
+ local has_script = false
+ local buildcmds = opt.buildcmds
+ local job_prefix = target:fullname()
+ if target == instance then
+ job_prefix = job_prefix .. "/target"
+ else
+ job_prefix = job_prefix .. "/rule/" .. instance:fullname()
+ end
+
+ -- call script
+ if not has_script and not buildcmds then
+ local script_name = opt.script_name
+ local script = instance:script(script_name)
+ if script then
+ -- call custom script with jobgraph
+ -- e.g.
+ --
+ -- target("test")
+ -- on_build(function (target, jobgraph, opt)
+ -- end, {jobgraph = true})
+ if instance:extraconf(script_name, "jobgraph") then
+ script(target, jobgraph)
+ elseif instance:extraconf(script_name, "batch") then
+ wprint("%s.%s: the batch mode is deprecated, please use jobgraph mode instead of it, or disable `build.jobgraph` policy to use it.", instance:fullname(), script_name)
+ else
+ -- call custom script directly
+ -- e.g.
+ --
+ -- target("test")
+ -- on_build(function (target, opt)
+ -- end)
+ local jobname = string.format("%s/%s", job_prefix, script_name)
+ jobgraph:add(jobname, function (index, total, opt)
+ script(target, {progress = opt.progress})
+ end)
+ end
+ has_script = true
+ end
+ end
+
+ -- call command script
+ -- e.g.
+ --
+ -- target("test")
+ -- on_buildcmd(function (target, batchcmds, opt)
+ -- end)
+ if not has_script then
+ local scriptcmd_name = opt.scriptcmd_name
+ local scriptcmd = instance:script(scriptcmd_name)
+ if scriptcmd then
+ local jobname = string.format("%s/%s", job_prefix, scriptcmd_name)
+ jobgraph:add(jobname, function (index, total, opt)
+ if buildcmds then
+ -- only generate cmds and do not run them, use cases: e.g. project generator
+ scriptcmd(target, buildcmds, {progress = opt.progress})
+ else
+ local batchcmds_ = batchcmds.new({target = target})
+ scriptcmd(target, batchcmds_, {progress = opt.progress})
+ batchcmds_:runcmds({changed = target:is_rebuilt(), dryrun = option.get("dry-run")})
+ end
+ end)
+ has_script = true
+ end
+ end
+ return has_script
+end
+
+-- add target jobs with the given stage
+-- stage: before, after or ""
+function add_targetjobs_with_stage(jobgraph, target, stage, opt)
+ opt = opt or {}
+ local job_kind = opt.job_kind
+ local ignored_rules = opt.ignored_rules
+
+ -- the group name, e.g. foo/after_prepare, bar/before_build
+ local group_name = string.format("%s/%s_%s", target:fullname(), stage ~= "" and stage or "on", job_kind)
+
+ -- the script name, e.g. before/after_prepare, before/after_build
+ local script_name = stage ~= "" and (job_kind .. "_" .. stage) or job_kind
+
+ -- the command script name, e.g. before/after_preparecmd, before/after_buildcmd
+ local scriptcmd_name = stage ~= "" and (job_kind .. "cmd_" .. stage) or (job_kind .. "cmd")
+
+ -- call target and rules script
+ local instances = {target}
+ for _, ruleinst in ipairs(target:orderules()) do
+ -- we only ignore some builtin rules, so we need not to use fullname.
+ if not ignored_rules or not ignored_rules:has(ruleinst:name()) then
+ table.insert(instances, ruleinst)
+ end
+ end
+ local jobsize = jobgraph:size()
+ jobgraph:group(group_name, function ()
+ local has_script = false
+ local script_opt = {
+ script_name = script_name,
+ scriptcmd_name = scriptcmd_name,
+ buildcmds = opt.buildcmds
+ }
+ for _, instance in ipairs(instances) do
+ -- we need to use this group to sort rule scripts with add_orders
+ local script_group = group_name .. "/" .. instance:fullname()
+ jobgraph:group(script_group, function ()
+ if add_targetjobs_for_script(jobgraph, target, instance, script_opt) then
+ has_script = true
+ end
+ end)
+ -- if custom target.on_build/prepare exists, we need to ignore all scripts in rules
+ if has_script and instance == target and stage == "" then
+ break
+ end
+ end
+
+ -- call builtin script, e.g. on_prepare, on_build, ...
+ if not has_script and stage == "" then
+ add_targetjobs_for_builtin_script(jobgraph, target, opt)
+ end
+ end)
+
+ -- no any new jobs
+ if jobgraph:size() == jobsize then
+ return
+ end
+
+ -- sort build rules
+ rule_utils.build_orders_in_jobgraph(jobgraph, target, instances, {root_group = group_name})
+ return group_name
+end
+
+-- add target jobs for the given target
+function add_targetjobs(jobgraph, target, opt)
+ opt = opt or {}
+ if not target:is_enabled() then
+ return
+ end
+
+ local pkgenvs = _g.pkgenvs
+ if pkgenvs == nil then
+ pkgenvs = {}
+ _g.pkgenvs = pkgenvs
+ end
+
+ local buildcmds = opt.buildcmds
+ local job_kind = opt.job_kind
+ local job_begin = string.format("%s/begin_%s", target:fullname(), job_kind)
+ local job_end = string.format("%s/end_%s", target:fullname(), job_kind)
+ jobgraph:add(job_begin, function (index, total, opt)
+ if buildcmds then
+ return
+ end
+
+ -- enter package environments
+ -- https://github.com/xmake-io/xmake/issues/4033
+ --
+ -- maybe mixing envs isn't a great solution,
+ -- but it's the most efficient compromise compared to setting envs in every on_build_file.
+ --
+ if target:pkgenvs() then
+ pkgenvs.oldenvs = pkgenvs.oldenvs or os.getenvs()
+ pkgenvs.newenvs = pkgenvs.newenvs or {}
+ pkgenvs.newenvs[target] = target:pkgenvs()
+ local newenvs = pkgenvs.oldenvs
+ for _, envs in pairs(pkgenvs.newenvs) do
+ newenvs = os.joinenvs(envs, newenvs)
+ end
+ os.setenvs(newenvs)
+ end
+
+ -- clean target first if rebuild
+ if job_kind == "prepare" and target:is_rebuilt() and not option.get("dry-run") then
+ _clean_target(target)
+ end
+ end)
+
+ jobgraph:add(job_end, function (index, total, opt)
+ if buildcmds then
+ return
+ end
+
+ -- restore environments
+ if target:pkgenvs() then
+ pkgenvs.oldenvs = pkgenvs.oldenvs or os.getenvs()
+ pkgenvs.newenvs = pkgenvs.newenvs or {}
+ pkgenvs.newenvs[target] = nil
+ local newenvs = pkgenvs.oldenvs
+ for _, envs in pairs(pkgenvs.newenvs) do
+ newenvs = os.joinenvs(envs, newenvs)
+ end
+ os.setenvs(newenvs)
+ end
+ end)
+
+ -- add jobs with target stage, e.g. begin -> before_xxx -> on_xxx -> after_xxx
+ local with_stages = opt.with_stages
+ local group, group_before, group_after
+ if not with_stages or with_stages:has("on") then
+ group = add_targetjobs_with_stage(jobgraph, target, "", opt)
+ end
+ if not with_stages or with_stages:has("before") then
+ group_before = add_targetjobs_with_stage(jobgraph, target, "before", opt)
+ end
+ if not with_stages or with_stages:has("after") then
+ group_after = add_targetjobs_with_stage(jobgraph, target, "after", opt)
+ end
+ jobgraph:add_orders(job_begin, group_before, group, group_after, job_end)
+end
+
+-- add target jobs for the given target and deps
+function add_targetjobs_and_deps(jobgraph, target, targetrefs, opt)
+ local targetname = target:fullname()
+ if not targetrefs[targetname] then
+ targetrefs[targetname] = target
+ add_targetjobs(jobgraph, target, opt)
+ for _, depname in ipairs(target:get("deps")) do
+ local dep = project.target(depname, {namespace = target:namespace()})
+ add_targetjobs_and_deps(jobgraph, dep, targetrefs, opt)
+ _add_targetjobs_orders(jobgraph, target, dep, opt)
+ end
+ end
+end
+
+-- get target jobs
+function get_targetjobs(targets_root, opt)
+ local jobgraph = async_jobgraph.new(opt.job_kind)
+ local targetrefs = {}
+ for _, target in ipairs(targets_root) do
+ add_targetjobs_and_deps(jobgraph, target, targetrefs, opt)
+ end
+ return jobgraph
+end
+
+-- add file jobs for the given script
+function add_filejobs_for_script(jobgraph, target, instance, sourcebatch, opt)
+ opt = opt or {}
+ local has_script = false
+ local buildcmds = opt.buildcmds
+ local job_prefix = target:fullname()
+ local file_group = sourcebatch.rulename
+ if target == instance then
+ job_prefix = job_prefix .. "/target/" .. file_group
+ else
+ job_prefix = job_prefix .. "/rule/" .. file_group
+ end
+
+ -- call script files
+ if not has_script and not buildcmds then
+ local script_files_name = opt.script_files_name
+ local script_files = instance:script(script_files_name)
+ if script_files then
+ -- call custom script with jobgraph
+ -- e.g.
+ --
+ -- target("test")
+ -- on_build_files(function (target, jobgraph, sourcebatch, opt)
+ -- end, {jobgraph = true})
+ local distcc = instance:extraconf(script_files_name, "distcc")
+ if instance:extraconf(script_files_name, "jobgraph") then
+ script_files(target, jobgraph, sourcebatch, {distcc = distcc})
+ elseif instance:extraconf(script_files_name, "batch") then
+ wprint("%s.%s: the batch mode is deprecated, please use jobgraph mode instead of it, or disable `build.jobgraph` policy to use it.",
+ instance:fullname(), script_files_name)
+ else
+ -- call custom script directly
+ -- e.g.
+ --
+ -- target("test")
+ -- on_build_files(function (target, sourcebatch, opt)
+ -- end)
+ local jobname = string.format("%s/%s", job_prefix, script_files_name)
+ jobgraph:add(jobname, function (index, total, opt)
+ script_files(target, sourcebatch, {progress = opt.progress, distcc = distcc})
+ end)
+ end
+ has_script = true
+ end
+ end
+
+ -- call script file
+ if not has_script and not buildcmds then
+ local script_file_name = opt.script_file_name
+ local script_file = instance:script(script_file_name)
+ if script_file then
+ -- call custom script with jobgraph
+ -- e.g.
+ --
+ -- target("test")
+ -- on_build_file(function (target, jobgraph, sourcefile, opt)
+ -- end, {jobgraph = true})
+ local distcc = instance:extraconf(script_file_name, "distcc")
+ if instance:extraconf(script_file_name, "jobgraph") then
+ local sourcekind = sourcebatch.sourcekind
+ for _, sourcefile in ipairs(sourcebatch.sourcefiles) do
+ script_file(target, jobgraph, sourcefile, {sourcekind = sourcekind, distcc = distcc})
+ end
+ elseif instance:extraconf(script_file_name, "batch") then
+ wprint("%s.%s: the batch mode is deprecated, please use jobgraph mode instead of it, or disable `build.jobgraph` policy to use it.",
+ instance:fullname(), script_file_name)
+ else
+ -- call custom script directly
+ -- e.g.
+ --
+ -- target("test")
+ -- on_build_file(function (target, sourcefile, opt)
+ -- end)
+ local jobname = string.format("%s/%s", job_prefix, script_file_name)
+ jobgraph:add(jobname, function (index, total, opt)
+ local sourcekind = sourcebatch.sourcekind
+ for _, sourcefile in ipairs(sourcebatch.sourcefiles) do
+ script_file(target, sourcefile, {progress = opt.progress, sourcekind = sourcekind, distcc = distcc})
+ end
+ end)
+ end
+ has_script = true
+ end
+ end
+
+ -- call command script files
+ -- e.g.
+ --
+ -- target("test")
+ -- on_buildcmd_files(function (target, batchcmds, sourcebatch, opt)
+ -- end)
+ if not has_script then
+ local scriptcmd_files_name = opt.scriptcmd_files_name
+ local scriptcmd_files = instance:script(scriptcmd_files_name)
+ if scriptcmd_files then
+ local distcc = instance:extraconf(scriptcmd_files_name, "distcc")
+ local jobname = string.format("%s/%s", job_prefix, scriptcmd_files_name)
+ jobgraph:add(jobname, function (index, total, opt)
+ -- only generate cmds and do not run them, use cases: e.g. project generator
+ if buildcmds then
+ scriptcmd_files(target, buildcmds, sourcebatch, {progress = opt.progress})
+ else
+ local batchcmds_ = batchcmds.new({target = target})
+ scriptcmd_files(target, batchcmds_, sourcebatch, {progress = opt.progress, distcc = distcc})
+ batchcmds_:runcmds({changed = target:is_rebuilt(), dryrun = option.get("dry-run")})
+ end
+ end)
+ has_script = true
+ end
+ end
+
+ -- call command script file
+ -- e.g.
+ --
+ -- target("test")
+ -- on_buildcmd_file(function (target, batchcmds, sourcefile, opt)
+ -- end)
+ if not has_script then
+ local scriptcmd_file_name = opt.scriptcmd_file_name
+ local scriptcmd_file = instance:script(scriptcmd_file_name)
+ if scriptcmd_file then
+ local distcc = instance:extraconf(scriptcmd_file_name, "distcc")
+ local jobname = string.format("%s/%s", job_prefix, scriptcmd_file_name)
+ jobgraph:add(jobname, function (index, total, opt)
+ -- only generate cmds and do not run them, use cases: e.g. project generator
+ if buildcmds then
+ local sourcekind = sourcebatch.sourcekind
+ for _, sourcefile in ipairs(sourcebatch.sourcefiles) do
+ scriptcmd_file(target, buildcmds, sourcefile, {progress = opt.progress, sourcekind = sourcekind})
+ end
+ else
+ local batchcmds_ = batchcmds.new({target = target})
+ local sourcekind = sourcebatch.sourcekind
+ for _, sourcefile in ipairs(sourcebatch.sourcefiles) do
+ scriptcmd_file(target, batchcmds_, sourcefile, {progress = opt.progress, sourcekind = sourcekind, distcc = distcc})
+ end
+ batchcmds_:runcmds({changed = target:is_rebuilt(), dryrun = option.get("dry-run")})
+ end
+ end)
+ has_script = true
+ end
+ end
+ return has_script
+end
+
+-- add file jobs with the given stage
+-- stage: before, after or ""
+--
+function add_filejobs_with_stage(jobgraph, target, sourcebatches, stage, opt)
+ opt = opt or {}
+ local buildcmds = opt.buildcmds
+ local ignored_rules = opt.ignored_rules
+ local job_kind = opt.job_kind
+ local job_kind_file = job_kind .. "_file"
+ local job_kind_files = job_kind .. "_files"
+ local job_kindcmd_file = job_kind .. "cmd_file"
+ local job_kindcmd_files = job_kind .. "cmd_files"
+
+ -- the group name, e.g. foo/after_prepare_files, bar/before_build_files
+ local group_name = string.format("%s/%s_%s_files", target:fullname(), stage ~= "" and stage or "on", job_kind)
+
+ -- the script name, e.g. before/after_prepare_files, before/after_build_files
+ local script_file_name = stage ~= "" and (job_kind_file .. "_" .. stage) or job_kind_file
+ local script_files_name = stage ~= "" and (job_kind_files .. "_" .. stage) or job_kind_files
+
+ -- the command script name, e.g. before/after_preparecmd_files, before/after_buildcmd_files
+ local scriptcmd_file_name = stage ~= "" and (job_kindcmd_file .. "_" .. stage) or job_kindcmd_file
+ local scriptcmd_files_name = stage ~= "" and (job_kindcmd_files .. "_" .. stage) or job_kindcmd_files
+
+ -- build sourcebatches map
+ local instances = {target}
+ local sourcebatches_map = {}
+ local sourcebatches_for_target = {}
+ for _, sourcebatch in pairs(sourcebatches) do
+ local rulename = sourcebatch.rulename
+ if rulename then
+ -- we only ignore some builtin rules, so we need not to use fullname.
+ local ruleinst = rule_utils.get_rule(target, rulename)
+ if not ignored_rules or not ignored_rules:has(ruleinst:name()) then
+ sourcebatches_map[ruleinst] = sourcebatch
+ -- avoid duplicate scripts being called twice in the target,
+ -- we just build sourcebatch with on_build_files scripts
+ --
+ -- for example, c++.build and c++.build.modules.builder rules have same sourcefiles,
+ -- but we just build it for c++.build
+ --
+ -- @see https://github.com/xmake-io/xmake/issues/3171
+ --
+ if ruleinst:script("build_file") or ruleinst:script("build_files") then
+ table.insert(sourcebatches_for_target, sourcebatch)
+ end
+ table.insert(instances, ruleinst)
+ end
+ else
+ table.insert(sourcebatches_for_target, sourcebatch)
+ end
+ end
+
+ -- call target and rules script
+ local jobsize = jobgraph:size()
+ jobgraph:group(group_name, function ()
+ local script_opt = {
+ script_file_name = script_file_name,
+ script_files_name = script_files_name,
+ scriptcmd_file_name = scriptcmd_file_name,
+ scriptcmd_files_name = scriptcmd_files_name,
+ buildcmds = buildcmds
+ }
+ local has_target_script = false
+ for _, instance in ipairs(instances) do
+ -- we need to use this group to sort rule scripts with add_orders
+ local script_group = group_name .. "/" .. instance:fullname()
+ jobgraph:group(script_group, function ()
+ if instance == target then
+ for _, sourcebatch in ipairs(sourcebatches_for_target) do
+ local has_script = add_filejobs_for_script(jobgraph, target, instance, sourcebatch, script_opt)
+ -- if custom target.on_build_file[s] exists, we need to ignore all scripts in rules
+ if has_script and stage == "" then
+ has_target_script = true
+ end
+ end
+ elseif not has_target_script then -- rule
+ local sourcebatch = sourcebatches_map[instance]
+ if sourcebatch then
+ add_filejobs_for_script(jobgraph, target, instance, sourcebatch, script_opt)
+ end
+ end
+ end)
+ end
+ end)
+
+ -- no any new jobs
+ if jobgraph:size() == jobsize then
+ return
+ end
+
+ -- sort build rules
+ rule_utils.build_orders_in_jobgraph(jobgraph, target, instances, {root_group = group_name})
+ return group_name
+end
+
+-- add file jobs for the given target
+function add_filejobs(jobgraph, target, opt)
+ opt = opt or {}
+ if not target:is_enabled() then
+ return
+ end
+
+ -- get sourcebatches
+ local sourcebatches
+ local filepatterns = opt.filepatterns
+ if filepatterns then
+ sourcebatches = _match_sourcebatches(target, filepatterns)
+ else
+ sourcebatches = target:sourcebatches()
+ end
+
+ -- add file jobs with target stage, e.g. before_xxx_files -> on_xxx_files -> after_xxx_files
+ local with_stages = opt.with_stages
+ local group, group_before, group_after
+ if not with_stages or with_stages:has("on") then
+ group = add_filejobs_with_stage(jobgraph, target, sourcebatches, "", opt)
+ end
+ if not with_stages or with_stages:has("before") then
+ group_before = add_filejobs_with_stage(jobgraph, target, sourcebatches, "before", opt)
+ end
+ if not with_stages or with_stages:has("after") then
+ group_after = add_filejobs_with_stage(jobgraph, target, sourcebatches, "after", opt)
+ end
+ jobgraph:add_orders(group_before, group, group_after)
+end
+
+-- add file jobs for the given target and deps
+function add_filejobs_and_deps(jobgraph, target, targetrefs, opt)
+ local targetname = target:fullname()
+ if not targetrefs[targetname] then
+ targetrefs[targetname] = target
+ add_filejobs(jobgraph, target, opt)
+ for _, depname in ipairs(target:get("deps")) do
+ local dep = project.target(depname, {namespace = target:namespace()})
+ add_filejobs_and_deps(jobgraph, dep, targetrefs, opt)
+ end
+ end
+end
+
+-- get files jobs
+function get_filejobs(targets_root, opt)
+ local jobgraph = async_jobgraph.new(opt.job_kind)
+ local targetrefs = {}
+ for _, target in ipairs(targets_root) do
+ add_filejobs_and_deps(jobgraph, target, targetrefs, opt)
+ end
+ return jobgraph
+end
+
+-- add link jobs for the given target
+function add_linkjobs(jobgraph, target, opt)
+ opt = table.clone(opt or {})
+ opt.job_kind = "link"
+ local with_stages = opt.with_stages
+ local group, group_before, group_after
+ if not with_stages or with_stages:has("on") then
+ group = add_targetjobs_with_stage(jobgraph, target, "", opt)
+ end
+ if not with_stages or with_stages:has("before") then
+ group_before = add_targetjobs_with_stage(jobgraph, target, "before", opt)
+ end
+ if not with_stages or with_stages:has("after") then
+ group_after = add_targetjobs_with_stage(jobgraph, target, "after", opt)
+ end
+ jobgraph:add_orders(group_before, group, group_after)
+end
+
+-- get link depfiles
+function get_linkdepfiles(target)
+ local extrafiles = {}
+ for _, dep in ipairs(target:orderdeps()) do
+ if dep:kind() == "static" then
+ table.insert(extrafiles, dep:targetfile())
+ end
+ end
+ local linkdepfiles = target:data("linkdepfiles")
+ if linkdepfiles then
+ table.join2(extrafiles, linkdepfiles)
+ end
+ local objectfiles = target:objectfiles()
+ local depfiles = objectfiles
+ if #extrafiles > 0 then
+ depfiles = table.join(objectfiles, extrafiles)
+ end
+ return depfiles
+end
+
+-- get all root targets
+function get_root_targets(targetnames, opt)
+ opt = opt or {}
+
+ -- get root targets
+ local targets_root = {}
+ if targetnames then
+ for _, targetname in ipairs(table.wrap(targetnames)) do
+ local target = project.target(targetname)
+ if target then
+ table.insert(targets_root, target)
+ if option.get("rebuild") then
+ target:data_set("rebuilt", true)
+ if not option.get("shallow") then
+ for _, dep in ipairs(target:orderdeps()) do
+ dep:data_set("rebuilt", true)
+ end
+ end
+ end
+ end
+ end
+ else
+ local group_pattern = opt.group_pattern
+ local depset = hashset.new()
+ local targets = {}
+ for _, target in ipairs(project.ordertargets()) do
+ if target:is_enabled() then
+ local group = target:get("group")
+ if (target:is_default() and not group_pattern) or option.get("all") or (group_pattern and group and group:match(group_pattern)) then
+ for _, depname in ipairs(target:get("deps")) do
+ depset:insert(depname)
+ end
+ table.insert(targets, target)
+ end
+ end
+ end
+ for _, target in ipairs(targets) do
+ if not depset:has(target:name()) then
+ table.insert(targets_root, target)
+ end
+ if option.get("rebuild") then
+ target:data_set("rebuilt", true)
+ end
+ end
+ end
+ return targets_root
+end
+
+-- run target-level jobs, e.g. on_prepare, on_build, ...
+function run_targetjobs(targets_root, opt)
+ opt = opt or {}
+ local job_kind = opt.job_kind
+ local jobgraph = get_targetjobs(targets_root, opt)
+ if jobgraph and not jobgraph:empty() then
+ local curdir = os.curdir()
+ async_runjobs(job_kind, jobgraph, {on_exit = function (errors)
+ import("utils.progress")
+ if errors and progress.showing_without_scroll() then
+ print("")
+ end
+ end, comax = option.get("jobs") or 1, curdir = curdir, distcc = opt.distcc, progress_factor = opt.progress_factor})
+ os.cd(curdir)
+ return true
+ end
+end
+
+-- run files-level jobs, e.g. on_prepare_files, on_build_files, ...
+function run_filejobs(targets_root, opt)
+ opt = opt or {}
+ local job_kind = opt.job_kind
+ local jobgraph = get_filejobs(targets_root, opt)
+ if jobgraph and not jobgraph:empty() then
+ local curdir = os.curdir()
+ async_runjobs(job_kind, jobgraph, {on_exit = function (errors)
+ import("utils.progress")
+ if errors and progress.showing_without_scroll() then
+ print("")
+ end
+ end, comax = option.get("jobs") or 1, curdir = curdir, distcc = opt.distcc, progress_factor = opt.progress_factor})
+ os.cd(curdir)
+ return true
+ end
+end
+
diff --git a/xmake/modules/private/action/require/impl/package.lua b/xmake/modules/private/action/require/impl/package.lua
index 87547ab3f..02fac7a3e 100644
--- a/xmake/modules/private/action/require/impl/package.lua
+++ b/xmake/modules/private/action/require/impl/package.lua
@@ -222,7 +222,7 @@ function _load_require(require_str, requires_extra, opt)
-- check require options
local extra_options = hashset.of("plat", "arch", "kind", "host", "targetos",
"alias", "group", "system", "option", "default", "optional", "debug",
- "verify", "external", "private", "build", "configs", "version")
+ "verify", "external", "private", "build", "configs", "version", "public")
for name, value in pairs(require_extra) do
if not extra_options:has(name) then
wprint("add_requires(\"%s\") has unknown option: {%s=%s}!", require_str, name, tostring(value))
diff --git a/xmake/modules/private/diagnosis/dump_targets.lua b/xmake/modules/private/diagnosis/dump_targets.lua
deleted file mode 100644
index c992a28f0..000000000
--- a/xmake/modules/private/diagnosis/dump_targets.lua
+++ /dev/null
@@ -1,74 +0,0 @@
---!A cross-platform build utility based on Lua
---
--- Licensed under the Apache License, Version 2.0 (the "License");
--- you may not use this file except in compliance with the License.
--- You may obtain a copy of the License at
---
--- http://www.apache.org/licenses/LICENSE-2.0
---
--- Unless required by applicable law or agreed to in writing, software
--- distributed under the License is distributed on an "AS IS" BASIS,
--- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
--- See the License for the specific language governing permissions and
--- limitations under the License.
---
--- Copyright (C) 2015-present, TBOOX Open Source Group.
---
--- @author ruki
--- @file dump_targets.lua
---
-
--- imports
-import("core.base.hashset")
-import("core.project.config")
-import("core.project.project")
-
--- get targets
-function _get_targets(targetname)
-
- -- get targets
- local targets = {}
- if targetname then
- table.insert(targets, project.target(targetname))
- else
- for _, target in pairs(project.targets()) do
- table.insert(targets, target)
- end
- end
- return targets
-end
-
--- dump the build jobs, e.g. xmake l private.diagnosis.dump_buildjobs [targetname]
-function main(targetname)
- config.load()
- for _, target in ipairs(_get_targets(targetname)) do
- cprint("${bright}target(%s):${clear} %s", target:name(), target:kind())
- local deps = target:get("deps")
- if deps then
- cprint(" ${color.dump.string}deps:")
- cprint(" ${yellow}->${clear} %s", table.concat(table.wrap(deps), ", "))
- end
- local options = {}
- for _, optname in ipairs(target:get("options")) do
- if not optname:startswith("__") then
- table.insert(options, optname)
- end
- end
- if #options > 0 then
- cprint(" ${color.dump.string}options:")
- cprint(" ${yellow}->${clear} %s", table.concat(table.wrap(options), ", "))
- end
- local packages = target:get("packages")
- if packages then
- cprint(" ${color.dump.string}packages:")
- cprint(" ${yellow}->${clear} %s", table.concat(table.wrap(packages), ", "))
- end
- local rules = target:get("rules")
- if rules then
- cprint(" ${color.dump.string}rules:")
- cprint(" ${yellow}->${clear} %s", table.concat(table.wrap(rules), ", "))
- end
- print("")
- end
-end
-
diff --git a/xmake/modules/private/utils/rule.lua b/xmake/modules/private/utils/rule.lua
new file mode 100644
index 000000000..ed6aec451
--- /dev/null
+++ b/xmake/modules/private/utils/rule.lua
@@ -0,0 +1,72 @@
+--!A cross-platform build utility based on Lua
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-- Copyright (C) 2015-present, TBOOX Open Source Group.
+--
+-- @author ruki
+-- @file rule.lua
+--
+
+-- imports
+import("core.base.option")
+import("core.project.rule")
+import("core.project.config")
+import("core.project.project")
+
+-- get rule
+-- @note we need to get rule from target first, because we maybe will inject and replace builtin rule in target
+function get_rule(target, rulename)
+ local ruleinst = assert(target:rule(rulename) or project.rule(rulename, {namespace = target:namespace()}) or
+ rule.rule(rulename), "unknown rule: %s", rulename)
+ return ruleinst
+end
+
+-- build rules orders in jobgraph, we need to add rule job with groups
+--
+-- like this:
+-- @code
+-- local root_group = ""
+-- for _, ruleinst in ipairs(rules) do
+-- local script_group = root_group .. "/" .. ruleinst:fullname()
+-- jobgraph:group(script_group, function ()
+-- jobgraph:add("xxx", function (index, total, opt)
+-- -- call rule script
+-- end)
+-- end)
+-- end
+--
+function build_orders_in_jobgraph(jobgraph, target, rules, opt)
+ opt = opt or {}
+ local root_group = assert(opt.root_group)
+ for _, ruleinst in ipairs(rules) do
+ local orders = table.wrap(ruleinst:get("orders"))
+ if #orders > 0 then
+ for _, order in ipairs(orders) do
+ local joborders = {}
+ for _, rulename in ipairs(order) do
+ -- we need to use fullname to support namespace
+ local ruleinst = get_rule(target, rulename)
+ local script_group = root_group .. "/" .. ruleinst:fullname()
+ if jobgraph:has(script_group) then
+ table.insert(joborders, script_group)
+ end
+ end
+ if #joborders > 0 then
+ jobgraph:add_orders(joborders)
+ end
+ end
+ end
+ end
+end
+
diff --git a/xmake/modules/private/utils/rule_groups.lua b/xmake/modules/private/utils/rule_groups.lua
deleted file mode 100644
index d64a75d62..000000000
--- a/xmake/modules/private/utils/rule_groups.lua
+++ /dev/null
@@ -1,95 +0,0 @@
---!A cross-platform build utility based on Lua
---
--- Licensed under the Apache License, Version 2.0 (the "License");
--- you may not use this file except in compliance with the License.
--- You may obtain a copy of the License at
---
--- http://www.apache.org/licenses/LICENSE-2.0
---
--- Unless required by applicable law or agreed to in writing, software
--- distributed under the License is distributed on an "AS IS" BASIS,
--- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
--- See the License for the specific language governing permissions and
--- limitations under the License.
---
--- Copyright (C) 2015-present, TBOOX Open Source Group.
---
--- @author ruki
--- @file rule_groups.lua
---
-
--- imports
-import("core.base.option")
-import("core.project.rule")
-import("core.project.config")
-import("core.project.project")
-
--- get rule
--- @note we need to get rule from target first, because we maybe will inject and replace builtin rule in target
-function get_rule(target, rulename)
- local ruleinst = assert(target:rule(rulename) or project.rule(rulename, {namespace = target:namespace()}) or
- rule.rule(rulename), "unknown rule: %s", rulename)
- return ruleinst
-end
-
--- get max depth of rule
-function _get_rule_max_depth(target, ruleinst, depth)
- local max_depth = depth
- for _, depname in ipairs(ruleinst:get("deps")) do
- local dep = get_rule(target, depname)
- local dep_depth = depth
- if ruleinst:extraconf("deps", depname, "order") then
- dep_depth = dep_depth + 1
- end
- local cur_depth = _get_rule_max_depth(target, dep, dep_depth)
- if cur_depth > max_depth then
- max_depth = cur_depth
- end
- end
- return max_depth
-end
-
--- build sourcebatch groups for target
-function _build_sourcebatch_groups_for_target(groups, target, sourcebatches)
- local group = groups[1]
- for _, sourcebatch in pairs(sourcebatches) do
- local rulename = assert(sourcebatch.rulename, "unknown rule for sourcebatch!")
- local item = group[rulename] or {}
- item.target = target
- item.sourcebatch = sourcebatch
- group[rulename] = item
- end
-end
-
--- build sourcebatch groups for rules
-function _build_sourcebatch_groups_for_rules(groups, target, sourcebatches)
- for _, sourcebatch in pairs(sourcebatches) do
- local rulename = assert(sourcebatch.rulename, "unknown rule for sourcebatch!")
- local ruleinst = get_rule(target, rulename)
- local depth = _get_rule_max_depth(target, ruleinst, 1)
- local group = groups[depth]
- if group == nil then
- group = {}
- groups[depth] = group
- end
- local item = group[rulename] or {}
- item.rule = ruleinst
- item.sourcebatch = sourcebatch
- group[rulename] = item
- end
-end
-
--- build sourcebatch groups by rule dependencies order, e.g. `add_deps("qt.ui", {order = true})`
---
--- @see https://github.com/xmake-io/xmake/issues/2814
---
-function build_sourcebatch_groups(target, sourcebatches)
- local groups = {{}}
- _build_sourcebatch_groups_for_target(groups, target, sourcebatches)
- _build_sourcebatch_groups_for_rules(groups, target, sourcebatches)
- if #groups > 0 then
- groups = table.reverse(groups)
- end
- return groups
-end
-