diff options
| author | ruki <[email protected]> | 2023-09-30 18:26:05 +0800 |
|---|---|---|
| committer | GitHub <[email protected]> | 2023-09-30 18:26:05 +0800 |
| commit | 23f598d853e16c25360b70a082629bdd0d35eedd (patch) | |
| tree | 165fd007f051fdc5ba623f72d026003b8f222657 | |
| parent | 1c934c2e16c05bdfee56de5c187fdcc5d94fac98 (diff) | |
| parent | f584fb389af355484c7bbb257c0c5cd77c7f6c31 (diff) | |
Merge pull request #4250 from xmake-io/links
Improve link mechanism and order
| -rw-r--r-- | CHANGELOG.md | 8 | ||||
| -rw-r--r-- | tests/modules/graph/test.lua | 56 | ||||
| -rw-r--r-- | tests/projects/c++/linkorders/src/foo.cpp | 5 | ||||
| -rw-r--r-- | tests/projects/c++/linkorders/src/foo.h | 9 | ||||
| -rw-r--r-- | tests/projects/c++/linkorders/src/main.cpp | 9 | ||||
| -rw-r--r-- | tests/projects/c++/linkorders/test.lua | 13 | ||||
| -rw-r--r-- | tests/projects/c++/linkorders/xmake.lua | 29 | ||||
| -rw-r--r-- | xmake/core/base/graph.lua | 280 | ||||
| -rw-r--r-- | xmake/core/base/interpreter.lua | 71 | ||||
| -rw-r--r-- | xmake/core/base/scopeinfo.lua | 68 | ||||
| -rw-r--r-- | xmake/core/language/language.lua | 4 | ||||
| -rw-r--r-- | xmake/core/sandbox/modules/import/core/base/graph.lua | 22 | ||||
| -rw-r--r-- | xmake/core/tool/builder.lua | 314 | ||||
| -rw-r--r-- | xmake/languages/asm/load.lua | 5 | ||||
| -rw-r--r-- | xmake/languages/c++/load.lua | 5 | ||||
| -rw-r--r-- | xmake/languages/c++/xmake.lua | 2 | ||||
| -rw-r--r-- | xmake/languages/dlang/load.lua | 5 | ||||
| -rw-r--r-- | xmake/languages/dlang/xmake.lua | 2 | ||||
| -rw-r--r-- | xmake/languages/objc++/load.lua | 5 | ||||
| -rw-r--r-- | xmake/languages/objc++/xmake.lua | 2 | ||||
| -rw-r--r-- | xmake/modules/core/tools/gcc.lua | 27 |
21 files changed, 872 insertions, 69 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 64e05701e..9a4ee05ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## master (unreleased) +### New features + +* [#4250](https://github.com/xmake-io/xmake/pull/4250): Improve link mechanism and order + ## v2.8.3 ### New features @@ -1662,6 +1666,10 @@ ## master (开发中) +### 新特性 + +* [#4250](https://github.com/xmake-io/xmake/pull/4250): 支持链接顺序调整,链接组 + ## v2.8.3 ### 新特性 diff --git a/tests/modules/graph/test.lua b/tests/modules/graph/test.lua new file mode 100644 index 000000000..63095be12 --- /dev/null +++ b/tests/modules/graph/test.lua @@ -0,0 +1,56 @@ +import("core.base.graph") + +function test_topological_sort(t) + local edges = { + {0, 5}, + {0, 2}, + {0, 1}, + {3, 6}, + {3, 5}, + {3, 4}, + {5, 4}, + {6, 4}, + {6, 0}, + {3, 2}, + {1, 4}, + } + local dag = graph.new(true) + for _, e in ipairs(edges) do + dag:add_edge(e[1], e[2]) + end + local order_path = dag:topological_sort() + local orders = {} + for i, v in ipairs(order_path) do + orders[v] = i + end + for _, e in ipairs(edges) do + t:require(orders[e[1]] < orders[e[2]]) + end + + dag = dag:reverse() + order_path = dag:topological_sort() + orders = {} + for i, v in ipairs(order_path) do + orders[v] = i + end + for _, e in ipairs(edges) do + t:require(orders[e[1]] > orders[e[2]]) + end +end + +function test_find_cycle(t) + local edges = { + {9, 1}, + {1, 6}, + {6, 0}, + {0, 1}, + {4, 5} + } + local dag = graph.new(true) + for _, e in ipairs(edges) do + dag:add_edge(e[1], e[2]) + end + local cycle = dag:find_cycle() + t:are_equal(cycle, {1, 6, 0}) +end + diff --git a/tests/projects/c++/linkorders/src/foo.cpp b/tests/projects/c++/linkorders/src/foo.cpp new file mode 100644 index 000000000..1a1fb3425 --- /dev/null +++ b/tests/projects/c++/linkorders/src/foo.cpp @@ -0,0 +1,5 @@ +#include "foo.h" + +int add(int a, int b) { + return a + b; +} diff --git a/tests/projects/c++/linkorders/src/foo.h b/tests/projects/c++/linkorders/src/foo.h new file mode 100644 index 000000000..d2506bca9 --- /dev/null +++ b/tests/projects/c++/linkorders/src/foo.h @@ -0,0 +1,9 @@ +#ifdef __cplusplus +extern "C" { +#endif + +int add(int a, int b); + +#ifdef __cplusplus +} +#endif diff --git a/tests/projects/c++/linkorders/src/main.cpp b/tests/projects/c++/linkorders/src/main.cpp new file mode 100644 index 000000000..ed1924789 --- /dev/null +++ b/tests/projects/c++/linkorders/src/main.cpp @@ -0,0 +1,9 @@ +#include "foo.h" +#include <iostream> + +using namespace std; + +int main(int argc, char** argv) { + cout << "add(1, 2) = " << add(1, 2) << endl; + return 0; +} diff --git a/tests/projects/c++/linkorders/test.lua b/tests/projects/c++/linkorders/test.lua new file mode 100644 index 000000000..704959e28 --- /dev/null +++ b/tests/projects/c++/linkorders/test.lua @@ -0,0 +1,13 @@ +function main(t) + + -- freebsd ci is slower + if is_host("bsd") then + return + end + + -- only for x86/x64, because it will take too long time on ci with arm/mips + if os.subarch():startswith("x") or os.subarch() == "i386" then + t:build() + end +end + diff --git a/tests/projects/c++/linkorders/xmake.lua b/tests/projects/c++/linkorders/xmake.lua new file mode 100644 index 000000000..755e75f51 --- /dev/null +++ b/tests/projects/c++/linkorders/xmake.lua @@ -0,0 +1,29 @@ +add_rules("mode.debug", "mode.release") + +add_requires("libpng") + +target("bar") + set_kind("shared") + add_files("src/foo.cpp") + add_linkgroups("m", "pthread", {whole = true}) + +target("foo") + set_kind("static") + add_files("src/foo.cpp") + add_packages("libpng", {public = true}) + +target("demo") + set_kind("binary") + add_deps("foo") + add_files("src/main.cpp") + if is_plat("linux", "macosx") then + add_syslinks("pthread", "m", "dl") + end + if is_plat("macosx") then + add_frameworks("Foundation", "CoreFoundation") + end + add_linkorders("framework::Foundation", "png16", "foo") + add_linkorders("dl", "linkgroup::syslib") + add_linkgroups("m", "pthread", {name = "syslib", group = true}) + + diff --git a/xmake/core/base/graph.lua b/xmake/core/base/graph.lua new file mode 100644 index 000000000..80d4566ec --- /dev/null +++ b/xmake/core/base/graph.lua @@ -0,0 +1,280 @@ +--!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 graph.lua +-- + +-- load modules +local table = require("base/table") +local object = require("base/object") + +-- define module +local graph = graph or object { _init = {"_directed"} } {true} +local edge = edge or object { _init = {"_from", "_to", "_weight"} } + +-- new edge, from -> to +function edge.new(from, to, weight) + return edge {from, to, weight or 1.0} +end + +function edge:from() + return self._from +end + +function edge:to() + return self._to +end + +function edge:other(v) + if v == self._from then + return self._to + else + return self._from + end +end + +function edge:weight() + return self._weight +end + +-- clear graph +function graph:clear() + self._vertices = {} + self._edges = {} + self._adjacent_edges = {} +end + +-- is empty? +function graph:empty() + return #self:vertices() == 0 +end + +-- is directed? +function graph:is_directed() + return self._directed +end + +-- get vertices +function graph:vertices() + return self._vertices +end + +-- get adjacent edges of the the given vertex +function graph:adjacent_edges(v) + return self._adjacent_edges[v] +end + +-- get the vertex at the given index +function graph:vertex(idx) + return self:vertices()[idx] +end + +-- has the given vertex? +function graph:has_vertex(v) + return table.contains(self:vertices(), v) +end + +-- remove the given vertex? +function graph:remove_vertex(v) + local contains = false + table.remove_if(self._vertices, function (_, item) + if item == v then + contains = true + return true + end + end) + if contains then + self._adjacent_edges[v] = nil + -- remove the adjacent edge with this vertex in the other vertices + if not self:is_directed() then + for _, w in ipairs(self:vertices()) do + local edges = self:adjacent_edges(w) + if edges then + table.remove_if(edges, function (_, e) return e:other(w) == v end) + end + end + end + end +end + +-- topological sort +function graph:topological_sort() + local visited = {} + for _, v in ipairs(self:vertices()) do + visited[v] = false + end + local order_vertices = {} + local function dfs(v) + visited[v] = true + local edges = self:adjacent_edges(v) + if edges then + for _, e in ipairs(edges) do + local w = e:other(v) + if not visited[w] then + dfs(w) + end + end + end + table.insert(order_vertices, v) + end + for _, v in ipairs(self:vertices()) do + if not visited[v] then + dfs(v) + end + end + return table.reverse(order_vertices) +end + +-- find cycle +function graph:find_cycle() + local visited = {} + local stack = {} + local cycle = {} + + local function dfs(v) + visited[v] = true + stack[v] = true + table.insert(cycle, v) + local edges = self:adjacent_edges(v) + if edges then + for _, e in ipairs(edges) do + local w = e:other(v) + if not visited[w] then + if dfs(w) then + return true + elseif stack[w] then + return true + end + elseif stack[w] then + for i = #cycle, 1, -1 do + if cycle[i] == w then + cycle = table.slice(cycle, i) + return true + end + end + end + end + end + table.remove(cycle) + stack[v] = false + return false + end + + for _, v in ipairs(self:vertices()) do + if not visited[v] then + if dfs(v) then + return cycle + end + end + end +end + +-- get edges +function graph:edges() + return self._edges +end + +-- add edge +function graph:add_edge(from, to, weight) + local e = edge.new(from, to, weight) + if not self:has_vertex(from) then + table.insert(self._vertices, from) + self._adjacent_edges[from] = {} + end + if not self:has_vertex(to) then + table.insert(self._vertices, to) + self._adjacent_edges[to] = {} + end + if self:is_directed() then + table.insert(self._adjacent_edges[e:from()], e) + else + table.insert(self._adjacent_edges[e:from()], e) + table.insert(self._adjacent_edges[e:to()], e) + end + table.insert(self._edges, e) +end + +-- has the given edge? +function graph:has_edge(from, to) + local edges = self:adjacent_edges(from) + if edges then + for _, e in ipairs(edges) do + if e:to() == to then + return true + end + end + end + return false +end + +-- clone graph +function graph:clone() + local gh = graph.new(self:is_directed()) + for _, v in ipairs(self:vertices()) do + local edges = self:adjacent_edges(v) + if edges then + for _, e in ipairs(edges) do + gh:add_edge(e:from(), e:to(), e:weight()) + end + end + end + return gh +end + +-- reverse graph +function graph:reverse() + if not self:is_directed() then + return self:clone() + end + local gh = graph.new(self:is_directed()) + for _, v in ipairs(self:vertices()) do + local edges = self:adjacent_edges(v) + if edges then + for _, e in ipairs(edges) do + gh:add_edge(e:to(), e:from(), e:weight()) + end + end + end + return gh +end + +-- dump graph +function graph:dump() + local vertices = self:vertices() + local edges = self:edges() + print(string.format("graph: %s, vertices: %d, edges: %d", self:is_directed() and "directed" or "not-directed", #vertices, #edges)) + print("vertices: ") + for _, v in ipairs(vertices) do + print(string.format(" %s", v)) + end + print("") + print("edges: ") + for _, e in ipairs(edges) do + print(string.format(" %s -> %s", e:from(), e:to())) + end +end + +-- new graph +function graph.new(directed) + local gh = graph {directed} + gh:clear() + return gh +end + +-- return module: graph +return graph + diff --git a/xmake/core/base/interpreter.lua b/xmake/core/base/interpreter.lua index 8a64aaa58..4733a3369 100644 --- a/xmake/core/base/interpreter.lua +++ b/xmake/core/base/interpreter.lua @@ -1214,6 +1214,77 @@ function interpreter:api_register_set_keyvalues(scope_kind, ...) self:_api_register_xxx_values(scope_kind, "set", implementation, ...) end +-- register api for set_groups +function interpreter:api_register_set_groups(scope_kind, ...) + + -- define implementation + local implementation = function (self, scope, name, ...) + + -- get extra config + local values = {...} + local extra_config = values[#values] + if table.is_dictionary(extra_config) then + table.remove(values) + else + extra_config = nil + end + + -- expand values + values = table.join(table.unpack(values)) + table.wrap_lock(values) + + -- save values + scope[name] = values + + -- save extra config + if extra_config then + scope["__extra_" .. name] = scope["__extra_" .. name] or {} + local extrascope = scope["__extra_" .. name] + local key = table.concat(values, "_") + extrascope[key] = extra_config + end + end + + -- register implementation + self:_api_register_xxx_values(scope_kind, "set", implementation, ...) +end + +-- register api for add_groups +function interpreter:api_register_add_groups(scope_kind, ...) + + -- define implementation + local implementation = function (self, scope, name, ...) + + -- get extra config + local values = {...} + local extra_config = values[#values] + if table.is_dictionary(extra_config) then + table.remove(values) + else + extra_config = nil + end + + -- expand values + values = table.join(table.unpack(values)) + + -- save values + scope[name] = scope[name] or {} + table.wrap_lock(values) + table.insert(scope[name], values) + + -- save extra config + if extra_config then + scope["__extra_" .. name] = scope["__extra_" .. name] or {} + local extrascope = scope["__extra_" .. name] + local key = table.concat(values, "_") + extrascope[key] = extra_config + end + end + + -- register implementation + self:_api_register_xxx_values(scope_kind, "add", implementation, ...) +end + -- register api for add_keyvalues -- -- interp:api_register_add_keyvalues("scope_kind", "name1", "name2", ...) diff --git a/xmake/core/base/scopeinfo.lua b/xmake/core/base/scopeinfo.lua index d7215917f..68d87706b 100644 --- a/xmake/core/base/scopeinfo.lua +++ b/xmake/core/base/scopeinfo.lua @@ -188,6 +188,71 @@ function _instance:_api_add_values(name, ...) end end +-- set the api groups to the scope info +function _instance:_api_set_groups(name, ...) + + -- get the scope info + local scope = self._INFO + + -- get extra config + local values = {...} + local extra_config = values[#values] + if table.is_dictionary(extra_config) then + table.remove(values) + else + extra_config = nil + end + + -- expand values + values = table.join(table.unpack(values)) + + -- save values + table.wrap_lock(values) + scope[name] = values + scope[name] = self:_api_handle(name, scope[name]) + + -- save extra config + if extra_config then + scope["__extra_" .. name] = scope["__extra_" .. name] or {} + local extrascope = scope["__extra_" .. name] + local key = table.concat(values, "_") + extrascope[key] = extra_config + end +end + +-- add the api groups to the scope info +function _instance:_api_add_groups(name, ...) + + -- get the scope info + local scope = self._INFO + + -- get extra config + local values = {...} + local extra_config = values[#values] + if table.is_dictionary(extra_config) then + table.remove(values) + else + extra_config = nil + end + + -- expand values + values = table.join(table.unpack(values)) + + -- save values + scope[name] = scope[name] or {} + table.wrap_lock(values) + table.insert(scope[name], values) + scope[name] = self:_api_handle(name, scope[name]) + + -- save extra config + if extra_config then + scope["__extra_" .. name] = scope["__extra_" .. name] or {} + local extrascope = scope["__extra_" .. name] + local key = table.concat(values, "_") + extrascope[key] = extra_config + end +end + -- set the api key-values to the scope info function _instance:_api_set_keyvalues(name, key, ...) @@ -630,6 +695,9 @@ function _instance:extraconf(name, item, key) local value = extraconf if item then value = extraconf and extraconf[item] or nil + if value == nil and extraconf and type(item) == "table" then + value = extraconf[table.concat(item, "_")] + end if value and key then value = value[key] end diff --git a/xmake/core/language/language.lua b/xmake/core/language/language.lua index 557839959..73045c933 100644 --- a/xmake/core/language/language.lua +++ b/xmake/core/language/language.lua @@ -370,17 +370,19 @@ function language.apis() if not languages then os.raise(errors) end - apis = {values = {}, paths = {}, custom = {}, dictionary = {}} + apis = {values = {}, groups = {}, paths = {}, custom = {}, dictionary = {}} for name, instance in pairs(languages) do local instance_apis = instance:get("apis") if instance_apis then table.join2(apis.values, table.wrap(instance_apis.values)) + table.join2(apis.groups, table.wrap(instance_apis.groups)) table.join2(apis.paths, table.wrap(instance_apis.paths)) table.join2(apis.custom, table.wrap(instance_apis.custom)) table.join2(apis.dictionary, table.wrap(instance_apis.dictionary)) end end apis.values = table.unique(apis.values) + apis.groups = table.unique(apis.groups) apis.paths = table.unique(apis.paths) apis.custom = table.unique(apis.custom) language._APIS = apis diff --git a/xmake/core/sandbox/modules/import/core/base/graph.lua b/xmake/core/sandbox/modules/import/core/base/graph.lua new file mode 100644 index 000000000..ba7bca86d --- /dev/null +++ b/xmake/core/sandbox/modules/import/core/base/graph.lua @@ -0,0 +1,22 @@ +--!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 graph.lua +-- + +-- return module +return require("base/graph") diff --git a/xmake/core/tool/builder.lua b/xmake/core/tool/builder.lua index 3eda6ce6a..f07fc39d6 100644 --- a/xmake/core/tool/builder.lua +++ b/xmake/core/tool/builder.lua @@ -28,6 +28,8 @@ local utils = require("base/utils") local table = require("base/table") local string = require("base/string") local option = require("base/option") +local hashset = require("base/hashset") +local graph = require("base/graph") local tool = require("tool/tool") local config = require("project/config") local sandbox = require("sandbox/sandbox") @@ -290,53 +292,76 @@ function builder:_add_flags_from_argument(flags, target, args) end}) end --- add flags from the language -function builder:_add_flags_from_language(flags, target, getters) +-- add items from getter +function builder:_add_items_from_getter(items, name, opt) + local values = opt.getter(name) + if values then + table.insert(items, {name = name, values = table.wrap(values), check = opt.check, multival = opt.multival, mapper = opt.mapper}) + end +end + +-- add items from config +function builder:_add_items_from_config(items, name, opt) + local values = config.get(name) + if values and name:endswith("dirs") then + values = path.splitenv(values) + end + if values then + table.insert(items, {name = name, values = table.wrap(values), check = opt.check, multival = opt.multival, mapper = opt.mapper}) + end +end - -- init getters - -- - -- e.g. - -- - -- target.linkdirs => flags = getters("target")("linkdirs") - -- - local getters = getters or - { - config = function (name) - local values = config.get(name) - if values and name:endswith("dirs") then - values = path.splitenv(values) - end - return values - end - , toolchain = function (name) - if target and target:type() == "target" then - return target:toolconfig(name) - else - return platform.toolconfig(name) - end - end - , target = function (name) - local results = {} - if target:type() == "target" then +-- add items from toolchain +function builder:_add_items_from_toolchain(items, name, opt) + local values + local target = opt.target + if target and target:type() == "target" then + values = target:toolconfig(name) + else + values = platform.toolconfig(name) + end + if values then + table.insert(items, {name = name, values = table.wrap(values), check = opt.check, multival = opt.multival, mapper = opt.mapper}) + end +end + +-- add items from option +function builder:_add_items_from_option(items, name, opt) + local values + local target = opt.target + if target then + values = target:get(name) + end + if values then + table.insert(items, {name = name, values = table.wrap(values), check = opt.check, multival = opt.multival, mapper = opt.mapper}) + end +end - -- get flagvalues of target with given flagname - table.join2(results, target:get(name)) +-- add items from target +function builder:_add_items_from_target(items, name, opt) + local values = {} + local target = opt.target + if target then + -- get flagvalues of target with given flagname + table.join2(values, target:get(name)) - -- get flagvalues of the attached options and packages - table.join2(results, target:get_from_opts(name)) - table.join2(results, target:get_from_pkgs(name)) + -- get flagvalues of the attached options and packages + table.join2(values, target:get_from_opts(name)) + table.join2(values, target:get_from_pkgs(name)) - -- get flagvalues (public or interface) of all dependent targets (contain packages/options) - table.join2(results, target:get_from_deps(name, {interface = true})) + -- get flagvalues (public or interface) of all dependent targets (contain packages/options) + table.join2(values, target:get_from_deps(name, {interface = true})) + end + if values and #values > 0 then + table.insert(items, {name = name, values = table.wrap(values), check = opt.check, multival = opt.multival, mapper = opt.mapper}) + end +end - elseif target:type() == "option" then - table.join2(results, target:get(name)) - end - return results - end - } +-- add flags from the language +function builder:_add_flags_from_language(flags, target, getters) - -- get name flags for builder + -- get order named items + local items = {} for _, flaginfo in ipairs(self:_nameflags()) do -- get flag info @@ -350,43 +375,196 @@ function builder:_add_flags_from_language(flags, target, getters) end end - -- get getter - local getter = getters[flagscope] - if getter then + -- get api name of tool + local apiname = flagname:gsub("^nf_", "") - -- get api name of tool - local apiname = flagname:gsub("^nf_", "") + -- use multiple values mapper if be defined in tool module + local multival = false + if apiname:endswith("s") then + if self:_tool()["nf_" .. apiname] then + multival = true + else + apiname = apiname:sub(1, #apiname - 1) + end + end - -- use multiple values mapper if be defined in tool module - local multival = false - if apiname:endswith("s") then - if self:_tool()["nf_" .. apiname] then - multival = true - else - apiname = apiname:sub(1, #apiname - 1) + -- map named flags to real flags + local mapper = self:_tool()["nf_" .. apiname] + if mapper then + local opt = {target = target, check = checkstate, multival = multival, mapper = mapper} + if getters then + local getter = getters[flagscope] + if getter then + opt.getter = getter + self:_add_items_from_getter(items, flagname, opt) end + elseif flagscope == "target" and target and target:type() == "target" then + self:_add_items_from_target(items, flagname, opt) + elseif flagscope == "target" and target and target:type() == "option" then + self:_add_items_from_option(items, flagname, opt) + elseif flagscope == "config" then + self:_add_items_from_config(items, flagname, opt) + elseif flagscope == "toolchain" then + self:_add_items_from_toolchain(items, flagname, opt) end + end + end + + -- sort links + local kind = self:kind() + if (kind == "ld" or kind == "sh") and target and target:type() == "target" then + self:_sort_links_of_items(target, items) + end - -- map named flags to real flags - local mapper = self:_tool()["nf_" .. apiname] - if mapper then - if multival then - local results = mapper(self:_tool(), table.wrap(getter(flagname)), target, self:_targetkind()) - for _, flag in ipairs(table.wrap(results)) do - if flag and flag ~= "" and (not checkstate or self:has_flags(flag)) then - table.insert(flags, flag) - end + -- get flags from the items + for _, item in ipairs(items) do + local check = item.check + local mapper = item.mapper + if item.multival then + local results = mapper(self:_tool(), item.values, target, self:_targetkind()) + for _, flag in ipairs(table.wrap(results)) do + if flag and flag ~= "" and (not check or self:has_flags(flag)) then + table.insert(flags, flag) + end + end + else + for _, flagvalue in ipairs(item.values) do + local flag = mapper(self:_tool(), flagvalue, target, self:_targetkind()) + if flag and flag ~= "" and (not check or self:has_flags(flag)) then + table.insert(flags, flag) + end + end + end + end +end + +-- sort links of items +function builder:_sort_links_of_items(target, items) + local sortlinks = false + local makegroups = false + local linkorders = table.wrap(target:get("linkorders")) + if #linkorders > 0 then + sortlinks = true + end + local linkgroups = table.wrap(target:get("linkgroups")) + local linkgroups_set = hashset.new() + if #linkgroups > 0 then + makegroups = true + for _, linkgroup in ipairs(linkgroups) do + for _, link in ipairs(linkgroup) do + linkgroups_set:insert(link) + end + end + end + + -- get all links + local links = {} + local linkgroups_map = {} + local link_mapper + local framework_mapper + local linkgroup_mapper + if sortlinks or makegroups then + table.remove_if(items, function (_, item) + local name = item.name + local removed = false + for _, value in ipairs(item.values) do + if name == "links" or name == "syslinks" then + if not linkgroups_set:has(value) then + table.insert(links, value) end - else - for _, flagvalue in ipairs(table.wrap(getter(flagname))) do - local flag = mapper(self:_tool(), flagvalue, target, self:_targetkind()) - if flag and flag ~= "" and (not checkstate or self:has_flags(flag)) then - table.insert(flags, flag) - end + link_mapper = item.mapper + removed = true + elseif name == "frameworks" then + table.insert(links, "framework::" .. value) + framework_mapper = item.mapper + removed = true + elseif name == "linkgroups" then + local key = target:extraconf("linkgroups", value, "name") or tostring(value) + table.insert(links, "linkgroup::" .. key) + linkgroups_map[key] = value + linkgroup_mapper = item.mapper + removed = true + end + end + return removed + end) + links = table.reverse_unique(links) + end + + -- sort sublinks + if sortlinks then + local gh = graph.new(true) + local from + local original_deps = {} + for _, link in ipairs(links) do + local to = link + if from and to then + original_deps[from] = to + end + from = to + end + -- we need remove cycle in original links + -- e.g. + -- original_deps: a -> b -> c -> d -> e + -- new deps: e -> b + -- graph: a -> b -> c -> d e (remove d -> e) + -- /\ | + -- | | + -- -------------- + local function remove_cycle_in_original_deps(f, t) + local k + local v = t + while v ~= f do + k = v + v = original_deps[v] + if v == nil then + break + end + end + if v == f and k ~= nil then + original_deps[k] = nil + end + end + local links_set = hashset.from(links) + for _, linkorder in ipairs(linkorders) do + local from + for _, link in ipairs(linkorder) do + if links_set:has(link) then + local to = link + if from and to then + remove_cycle_in_original_deps(from, to) + gh:add_edge(from, to) end + from = to end end end + for k, v in pairs(original_deps) do + gh:add_edge(k, v) + end + if not gh:empty() then + local cycle = gh:find_cycle() + if cycle then + utils.warning("cycle links found in add_linkorders(): %s", table.concat(cycle, " -> ")) + end + links = gh:topological_sort() + end + end + + -- re-generate links to items list + if sortlinks or makegroups then + for _, link in ipairs(links) do + if link:startswith("framework::") then + link = link:sub(12) + table.insert(items, {name = "frameworks", values = table.wrap(link), check = false, multival = false, mapper = framework_mapper}) + elseif link:startswith("linkgroup::") then + local key = link:sub(12) + local value = linkgroups_map[key] + table.insert(items, {name = "linkgroups", values = table.wrap(value), check = false, multival = false, mapper = linkgroup_mapper}) + else + table.insert(items, {name = "links", values = table.wrap(link), check = false, multival = false, mapper = link_mapper}) + end + end end end diff --git a/xmake/languages/asm/load.lua b/xmake/languages/asm/load.lua index 9d3e76659..94eaa2c51 100644 --- a/xmake/languages/asm/load.lua +++ b/xmake/languages/asm/load.lua @@ -68,6 +68,11 @@ function _get_apis() , "toolchain.add_includedirs" , "toolchain.add_sysincludedirs" } + apis.groups = { + -- target.add_xxx + "target.add_linkorders" + , "target.add_linkgroups" + } apis.paths = { -- target.add_xxx "target.add_headerfiles" diff --git a/xmake/languages/c++/load.lua b/xmake/languages/c++/load.lua index ff251e778..b35938783 100644 --- a/xmake/languages/c++/load.lua +++ b/xmake/languages/c++/load.lua @@ -90,6 +90,11 @@ function _get_apis() , "toolchain.add_sysincludedirs" , "toolchain.add_frameworkdirs" } + apis.groups = { + -- target.add_xxx + "target.add_linkorders" + , "target.add_linkgroups" + } apis.paths = { -- target.set_xxx "target.set_pcheader" diff --git a/xmake/languages/c++/xmake.lua b/xmake/languages/c++/xmake.lua index e07b9e294..be6dc8bd1 100644 --- a/xmake/languages/c++/xmake.lua +++ b/xmake/languages/c++/xmake.lua @@ -75,6 +75,7 @@ language("c++") , "toolchain.frameworkdirs" , "config.links" , "target.links" + , "target.linkgroups" , "toolchain.links" , "config.frameworks" , "target.frameworks" @@ -98,6 +99,7 @@ language("c++") , "toolchain.frameworkdirs" , "config.links" , "target.links" + , "target.linkgroups" , "toolchain.links" , "config.frameworks" , "target.frameworks" diff --git a/xmake/languages/dlang/load.lua b/xmake/languages/dlang/load.lua index 23653150c..f11fd8005 100644 --- a/xmake/languages/dlang/load.lua +++ b/xmake/languages/dlang/load.lua @@ -60,6 +60,11 @@ function _get_apis() , "toolchain.add_includedirs" , "toolchain.add_sysincludedirs" } + apis.groups = { + -- target.add_xxx + "target.add_linkorders" + , "target.add_linkgroups" + } apis.paths = { -- target.add_xxx "target.add_linkdirs" diff --git a/xmake/languages/dlang/xmake.lua b/xmake/languages/dlang/xmake.lua index 9749c0d15..d6b4be932 100644 --- a/xmake/languages/dlang/xmake.lua +++ b/xmake/languages/dlang/xmake.lua @@ -52,6 +52,7 @@ language("dlang") , "toolchain.rpathdirs" , "config.links" , "target.links" + , "target.linkgroups" , "toolchain.links" , "config.syslinks" , "target.syslinks" @@ -65,6 +66,7 @@ language("dlang") , "toolchain.linkdirs" , "config.links" , "target.links" + , "target.linkgroups" , "toolchain.links" , "config.syslinks" , "target.syslinks" diff --git a/xmake/languages/objc++/load.lua b/xmake/languages/objc++/load.lua index b41ef77f3..8f4ae8a24 100644 --- a/xmake/languages/objc++/load.lua +++ b/xmake/languages/objc++/load.lua @@ -89,6 +89,11 @@ function _get_apis() , "toolchain.add_sysincludedirs" , "toolchain.add_frameworkdirs" } + apis.groups = { + -- target.add_xxx + "target.add_linkorders" + , "target.add_linkgroups" + } apis.paths = { -- target.set_xxx "target.set_pmheader" diff --git a/xmake/languages/objc++/xmake.lua b/xmake/languages/objc++/xmake.lua index 00fae204b..e1cb6ffa5 100644 --- a/xmake/languages/objc++/xmake.lua +++ b/xmake/languages/objc++/xmake.lua @@ -93,6 +93,7 @@ language("objc++") , "toolchain.frameworkdirs" , "config.links" , "target.links" + , "target.linkgroups" , "toolchain.links" , "config.frameworks" , "target.frameworks" @@ -113,6 +114,7 @@ language("objc++") , "toolchain.frameworkdirs" , "config.links" , "target.links" + , "target.linkgroups" , "toolchain.links" , "config.frameworks" , "target.frameworks" diff --git a/xmake/modules/core/tools/gcc.lua b/xmake/modules/core/tools/gcc.lua index 93525848f..f87eb24ea 100644 --- a/xmake/modules/core/tools/gcc.lua +++ b/xmake/modules/core/tools/gcc.lua @@ -304,6 +304,33 @@ function nf_syslink(self, lib) return nf_link(self, lib) end +-- make the link group flag +function nf_linkgroup(self, linkgroup, target) + local linkflags = {} + for _, lib in ipairs(linkgroup) do + table.insert(linkflags, nf_link(self, lib)) + end + local flags = {} + if not target:is_plat("macosx", "windows", "mingw") then + local group = target:extraconf("linkgroups", linkgroup, "group") + if group then + table.join2(flags, "-Wl,--start-group", linkflags, "-Wl,--end-group") + end + local whole = target:extraconf("linkgroups", linkgroup, "whole") + if whole then + table.join2(flags, "-Wl,--whole-archive", linkflags, "-Wl,--no-whole-archive") + end + local static = target:extraconf("linkgroups", linkgroup, "static") + if static then + table.join2(flags, "-Wl,-Bstatic", linkflags, "-Wl,-Bdynamic") + end + end + if #flags == 0 then + flags = linkflags + end + return flags +end + -- make the linkdir flag function nf_linkdir(self, dir) return {"-L" .. path.translate(dir)} |
