summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorruki <[email protected]>2023-09-30 18:26:05 +0800
committerGitHub <[email protected]>2023-09-30 18:26:05 +0800
commit23f598d853e16c25360b70a082629bdd0d35eedd (patch)
tree165fd007f051fdc5ba623f72d026003b8f222657
parent1c934c2e16c05bdfee56de5c187fdcc5d94fac98 (diff)
parentf584fb389af355484c7bbb257c0c5cd77c7f6c31 (diff)
Merge pull request #4250 from xmake-io/links
Improve link mechanism and order
-rw-r--r--CHANGELOG.md8
-rw-r--r--tests/modules/graph/test.lua56
-rw-r--r--tests/projects/c++/linkorders/src/foo.cpp5
-rw-r--r--tests/projects/c++/linkorders/src/foo.h9
-rw-r--r--tests/projects/c++/linkorders/src/main.cpp9
-rw-r--r--tests/projects/c++/linkorders/test.lua13
-rw-r--r--tests/projects/c++/linkorders/xmake.lua29
-rw-r--r--xmake/core/base/graph.lua280
-rw-r--r--xmake/core/base/interpreter.lua71
-rw-r--r--xmake/core/base/scopeinfo.lua68
-rw-r--r--xmake/core/language/language.lua4
-rw-r--r--xmake/core/sandbox/modules/import/core/base/graph.lua22
-rw-r--r--xmake/core/tool/builder.lua314
-rw-r--r--xmake/languages/asm/load.lua5
-rw-r--r--xmake/languages/c++/load.lua5
-rw-r--r--xmake/languages/c++/xmake.lua2
-rw-r--r--xmake/languages/dlang/load.lua5
-rw-r--r--xmake/languages/dlang/xmake.lua2
-rw-r--r--xmake/languages/objc++/load.lua5
-rw-r--r--xmake/languages/objc++/xmake.lua2
-rw-r--r--xmake/modules/core/tools/gcc.lua27
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)}