diff options
| author | Christian Rendina <[email protected]> | 2025-04-10 09:50:37 +0200 |
|---|---|---|
| committer | Christian Rendina <[email protected]> | 2025-04-10 09:50:37 +0200 |
| commit | 78723913d76fb8b615df34541236b8ea588d30db (patch) | |
| tree | b6b6ff550e4a2f73d94c63a61876cec31a4b3ae3 /xmake/modules | |
| parent | 2051c13f735a626f7b6fd8b98aa69f01fd9cef7e (diff) | |
| parent | fd49b7754c6709a87b5beb5526788bbc1a663965 (diff) | |
Merge branch 'dev' of https://github.com/xmake-io/xmake into dev
Diffstat (limited to 'xmake/modules')
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 - |
