summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorruki <[email protected]>2022-10-20 11:20:37 +0800
committerGitHub <[email protected]>2022-10-20 11:20:37 +0800
commit5e10a17b2602d756bb918b75ebb9d4fa3f9e47ea (patch)
treee12a91a696b62f011113caae5907fe7be39b4b31
parente6affabd6e20bbb179fea1f315414142cef650e3 (diff)
parent2183a9d1ac3d63351a36b5a855c7fce02405d374 (diff)
Merge pull request #2932 from xmake-io/components
Add package components support
-rw-r--r--.github/workflows/archlinux.yml1
-rw-r--r--.github/workflows/fedora.yml1
-rw-r--r--.github/workflows/linux.yml1
-rw-r--r--.github/workflows/linux_luajit.yml1
-rwxr-xr-xtests/projects/package/components/src/graphics.cpp8
-rw-r--r--tests/projects/package/components/src/main.cpp14
-rwxr-xr-xtests/projects/package/components/src/network.cpp14
-rw-r--r--tests/projects/package/components/test.lua12
-rw-r--r--tests/projects/package/components/xmake.lua237
-rw-r--r--xmake/core/base/table.lua2
-rw-r--r--xmake/core/package/component.lua148
-rw-r--r--xmake/core/package/package.lua160
-rw-r--r--xmake/core/project/package.lua65
-rw-r--r--xmake/core/project/target.lua35
-rw-r--r--xmake/modules/lib/detect/find_package.lua5
-rw-r--r--xmake/modules/package/manager/brew/find_package.lua59
-rw-r--r--xmake/modules/package/manager/xmake/find_package.lua76
-rw-r--r--xmake/modules/private/action/require/impl/package.lua5
-rw-r--r--xmake/modules/private/action/require/impl/register_packages.lua26
-rw-r--r--xmake/modules/private/action/require/info.lua15
20 files changed, 818 insertions, 67 deletions
diff --git a/.github/workflows/archlinux.yml b/.github/workflows/archlinux.yml
index ae183eb5f..69ad61a81 100644
--- a/.github/workflows/archlinux.yml
+++ b/.github/workflows/archlinux.yml
@@ -19,6 +19,7 @@ jobs:
- name: Prepare build tools
run: |
pacman -Sy --noconfirm --needed glibc git base-devel perl make unzip
+ pacman -Sy --noconfirm --needed mesa gcc-fortran glu
git config --global --add safe.directory /__w/xmake/xmake
- uses: actions/checkout@v2
diff --git a/.github/workflows/fedora.yml b/.github/workflows/fedora.yml
index 9a6a0d50d..eb2617523 100644
--- a/.github/workflows/fedora.yml
+++ b/.github/workflows/fedora.yml
@@ -20,6 +20,7 @@ jobs:
run: |
uname -a
dnf -y install @development-tools @rpm-development-tools
+ dnf -y install mesa-libGL-devel mesa-libGLU-devel
dnf -y install copr-cli make gcc-c++
dnf -y install perl
dnf -y upgrade git
diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml
index 12d053b0f..0f56cd79f 100644
--- a/.github/workflows/linux.yml
+++ b/.github/workflows/linux.yml
@@ -31,6 +31,7 @@ jobs:
run: |
sudo apt update
sudo apt install -y ruby ruby-dev rubygems build-essential llvm
+ sudo apt install -y libgl1-mesa-dev libglu1-mesa-dev
- name: Tests
run: |
diff --git a/.github/workflows/linux_luajit.yml b/.github/workflows/linux_luajit.yml
index 3c84921b0..86192e983 100644
--- a/.github/workflows/linux_luajit.yml
+++ b/.github/workflows/linux_luajit.yml
@@ -32,6 +32,7 @@ jobs:
- name: Prepare
run: |
sudo apt install -y build-essential llvm
+ sudo apt install -y libgl1-mesa-dev libglu1-mesa-dev
- name: Tests
run: |
diff --git a/tests/projects/package/components/src/graphics.cpp b/tests/projects/package/components/src/graphics.cpp
new file mode 100755
index 000000000..bc081a517
--- /dev/null
+++ b/tests/projects/package/components/src/graphics.cpp
@@ -0,0 +1,8 @@
+#include <SFML/Graphics.hpp>
+
+extern "C" {
+void graphics() {
+ sf::Text text;
+ text.setString("Hello world");
+}
+}
diff --git a/tests/projects/package/components/src/main.cpp b/tests/projects/package/components/src/main.cpp
new file mode 100644
index 000000000..bf0ad1553
--- /dev/null
+++ b/tests/projects/package/components/src/main.cpp
@@ -0,0 +1,14 @@
+#include <SFML/Graphics.hpp>
+#include <SFML/Network.hpp>
+
+extern "C" {
+void network();
+void graphics();
+}
+
+int main(int argc, char** argv) {
+ network();
+ graphics();
+ return 0;
+}
+
diff --git a/tests/projects/package/components/src/network.cpp b/tests/projects/package/components/src/network.cpp
new file mode 100755
index 000000000..503fd5e31
--- /dev/null
+++ b/tests/projects/package/components/src/network.cpp
@@ -0,0 +1,14 @@
+#include <SFML/Network.hpp>
+
+extern "C" {
+void network() {
+ sf::UdpSocket socket;
+ socket.bind(54000);
+
+ char data[100];
+ std::size_t received;
+ sf::IpAddress sender;
+ unsigned short port;
+ socket.receive(data, 100, received, sender, port);
+}
+}
diff --git a/tests/projects/package/components/test.lua b/tests/projects/package/components/test.lua
new file mode 100644
index 000000000..4964fecaa
--- /dev/null
+++ b/tests/projects/package/components/test.lua
@@ -0,0 +1,12 @@
+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/package/components/xmake.lua b/tests/projects/package/components/xmake.lua
new file mode 100644
index 000000000..6cb7aebd8
--- /dev/null
+++ b/tests/projects/package/components/xmake.lua
@@ -0,0 +1,237 @@
+add_rules("mode.debug", "mode.release")
+
+add_requires("sfml")
+
+target("graphics")
+ set_kind("static")
+ add_files("src/graphics.cpp")
+ add_packages("sfml", {components = "graphics", public = true})
+
+target("network")
+ set_kind("static")
+ add_files("src/network.cpp")
+ add_packages("sfml", {components = "network", public = true})
+
+target("test")
+ set_kind("binary")
+ add_files("src/main.cpp")
+ add_deps("graphics", "network")
+
+package("sfml")
+
+ set_homepage("https://www.sfml-dev.org")
+ set_description("Simple and Fast Multimedia Library")
+
+ if is_plat("windows", "linux") then
+ set_urls("https://www.sfml-dev.org/files/SFML-$(version)-sources.zip")
+ add_urls("https://github.com/SFML/SFML/releases/download/$(version)/SFML-$(version)-sources.zip")
+ add_versions("2.5.1", "bf1e0643acb92369b24572b703473af60bac82caf5af61e77c063b779471bb7f")
+ elseif is_plat("macosx") then
+ if is_arch("x64", "x86_64") then
+ set_urls("https://www.sfml-dev.org/files/SFML-$(version)-macOS-clang.tar.gz")
+ add_versions("2.5.1", "6af0f14fbd41dc038a00d7709f26fb66bb7ccdfe6187657ef0ef8cba578dcf14")
+
+ add_configs("debug", {builtin = true, description = "Enable debug symbols.", default = false, type = "boolean", readonly = true})
+ add_configs("shared", {description = "Build shared library.", default = true, type = "boolean", readonly = true})
+ end
+ elseif is_plat("mingw") then
+ if is_arch("x64", "x86_64") then
+ set_urls("https://www.sfml-dev.org/files/SFML-$(version)-windows-gcc-7.3.0-mingw-64-bit.zip")
+ add_versions("2.5.1", "671e786f1af934c488cb22c634251c8c8bd441c709b4ef7bc6bbe227b2a28560")
+ elseif is_arch("x86", "i386") then
+ set_urls("https://www.sfml-dev.org/files/SFML-$(version)-windows-gcc-7.3.0-mingw-32-bit.zip")
+ add_versions("2.5.1", "92d864c9c9094dc9d91e0006d66784f25ac900a8ee23c3f79db626de46a1d9d8")
+ end
+ end
+
+ if is_plat("linux") then
+ add_syslinks("pthread")
+ end
+
+ add_configs("graphics", {description = "Use the graphics module", default = true, type = "boolean"})
+ add_configs("window", {description = "Use the window module", default = true, type = "boolean"})
+ add_configs("audio", {description = "Use the audio module", default = true, type = "boolean"})
+ add_configs("network", {description = "Use the network module", default = true, type = "boolean"})
+ if is_plat("windows", "mingw") then
+ add_configs("main", {description = "Link to the sfml-main library", default = true, type = "boolean"})
+ end
+
+ if is_plat("macosx") then
+ add_extsources("brew::sfml/sfml-all")
+ end
+
+ on_component("graphics", function (package, component)
+ local e = package:config("shared") and "" or "-s"
+ component:add("links", "sfml-graphics" .. e)
+ if package:is_plat("windows", "mingw") and not package:config("shared") then
+ component:add("links", "freetype")
+ component:add("syslinks", "opengl32", "gdi32", "user32", "advapi32")
+ end
+ component:add("deps", "window", "system")
+ component:add("extsources", "brew::sfml/sfml-graphics")
+ end)
+
+ on_component("window", function (package, component)
+ local e = package:config("shared") and "" or "-s"
+ component:add("links", "sfml-window" .. e)
+ if package:is_plat("windows", "mingw") and not package:config("shared") then
+ component:add("syslinks", "opengl32", "gdi32", "user32", "advapi32")
+ end
+ component:add("deps", "system")
+ component:add("extsources", "brew::sfml/sfml-window")
+ end)
+
+ on_component("audio", function (package, component)
+ local e = package:config("shared") and "" or "-s"
+ component:add("links", "sfml-audio" .. e)
+ if package:is_plat("windows", "mingw") and not package:config("shared") then
+ component:add("links", "openal32", "flac", "vorbisenc", "vorbisfile", "vorbis", "ogg")
+ end
+ component:add("deps", "system")
+ component:add("extsources", "brew::sfml/sfml-audio")
+ end)
+
+ on_component("network", function (package, component)
+ local e = package:config("shared") and "" or "-s"
+ component:add("links", "sfml-network" .. e)
+ if package:is_plat("windows", "mingw") and not package:config("shared") then
+ component:add("syslinks", "ws2_32")
+ end
+ component:add("deps", "system")
+ component:add("extsources", "brew::sfml/sfml-network")
+ component:add("extsources", "apt::sfml-network")
+ end)
+
+ on_component("system", function (package, component)
+ local e = package:config("shared") and "" or "-s"
+ component:add("links", "sfml-system" .. e)
+ if package:is_plat("windows", "mingw") then
+ component:add("syslinks", "winmm")
+ end
+ if package:is_plat("windows", "mingw") and package:config("main") then
+ component:add("deps", "main")
+ end
+ component:add("extsources", "brew::sfml/sfml-system")
+ end)
+
+ on_component("main", function (package, component)
+ if package:is_plat("windows", "mingw") then
+ local main_module = "sfml-main"
+ if package:debug() then
+ main_module = main_module .. "-d"
+ end
+ component:add("links", main_module)
+ end
+ end)
+
+ on_load("windows", "linux", "macosx", "mingw", function (package)
+ if package:is_plat("windows", "linux") then
+ package:add("deps", "cmake")
+ end
+
+ if not package:config("shared") then
+ package:add("defines", "SFML_STATIC")
+ end
+
+ if package:is_plat("linux") then
+ if package:config("window") or package:config("graphics") then
+ package:add("deps", "libx11", "libxext", "libxrandr", "libxrender", "freetype", "eudev")
+ package:add("deps", "opengl", "glx", {optional = true})
+ end
+ if package:config("audio") then
+ package:add("deps", "libogg", "libflac", "libvorbis", "openal-soft")
+ end
+ end
+ package:add("components", "system")
+ for _, component in ipairs({"graphics", "window", "audio", "network"}) do
+ if package:config(component) then
+ package:add("components", component)
+ end
+ end
+ if package:is_plat("windows", "mingw") and package:config("main") then
+ package:add("components", "main")
+ end
+ end)
+
+ on_install("windows", "linux", function (package)
+ local configs = {"-DSFML_BUILD_DOC=OFF", "-DSFML_BUILD_EXAMPLES=OFF"}
+ table.insert(configs, "-DCMAKE_BUILD_TYPE=" .. (package:debug() and "Debug" or "Release"))
+ if package:config("shared") then
+ table.insert(configs, "-DBUILD_SHARED_LIBS=ON")
+ else
+ table.insert(configs, "-DBUILD_SHARED_LIBS=OFF")
+ if package:is_plat("windows") and package:config("vs_runtime"):startswith("MT") then
+ table.insert(configs, "-DSFML_USE_STATIC_STD_LIBS=ON")
+ end
+ end
+ local packagedeps
+ if package:is_plat("linux") and package:config("shared") then
+ io.replace("src/SFML/Graphics/CMakeLists.txt", "target_link_libraries(sfml-graphics PRIVATE X11)",
+ "target_link_libraries(sfml-graphics PRIVATE X11 Xext Xrender)", {plain = true})
+ packagedeps = {"libxext", "libxrender"}
+ end
+ table.insert(configs, "-DSFML_BUILD_AUDIO=" .. (package:config("audio") and "ON" or "OFF"))
+ table.insert(configs, "-DSFML_BUILD_GRAPHICS=" .. (package:config("graphics") and "ON" or "OFF"))
+ table.insert(configs, "-DSFML_BUILD_WINDOW=" .. (package:config("window") and "ON" or "OFF"))
+ table.insert(configs, "-DSFML_BUILD_NETWORK=" .. (package:config("network") and "ON" or "OFF"))
+ import("package.tools.cmake").install(package, configs, {packagedeps = packagedeps})
+ end)
+
+ on_install("macosx", "mingw", function (package)
+ os.cp("lib", package:installdir())
+ os.cp("include", package:installdir())
+ if package:is_plat("mingw") then
+ os.cp("bin/*", package:installdir("lib"), {rootdir = "bin"})
+ end
+ end)
+
+ on_test(function (package)
+ assert(package:check_cxxsnippets({test = [[
+ void test(int args, char** argv) {
+ sf::Clock c;
+ c.restart();
+ }
+ ]]}, {includes = "SFML/System.hpp"}))
+ if package:config("graphics") then
+ assert(package:check_cxxsnippets({test = [[
+ void test(int args, char** argv) {
+ sf::Text text;
+ text.setString("Hello world");
+ }
+ ]]}, {includes = "SFML/Graphics.hpp"}))
+ end
+ if package:config("window") or package:config("graphics") then
+ assert(package:check_cxxsnippets({test = [[
+ void test(int args, char** argv) {
+ sf::Window window(sf::VideoMode(1280, 720), "Title");
+
+ sf::Event event;
+ window.pollEvent(event);
+ }
+ ]]}, {includes = "SFML/Window.hpp"}))
+ end
+ if package:config("audio") then
+ assert(package:check_cxxsnippets({test = [[
+ void test(int args, char** argv) {
+ sf::Music music;
+ music.openFromFile("music.ogg");
+ music.play();
+ }
+ ]]}, {includes = "SFML/Audio.hpp"}))
+ end
+ if package:config("network") then
+ assert(package:check_cxxsnippets({test = [[
+ void test(int args, char** argv) {
+ sf::UdpSocket socket;
+ socket.bind(54000);
+
+ char data[100];
+ std::size_t received;
+ sf::IpAddress sender;
+ unsigned short port;
+ socket.receive(data, 100, received, sender, port);
+ }
+ ]]}, {includes = "SFML/Network.hpp"}))
+ end
+ end)
+package_end()
diff --git a/xmake/core/base/table.lua b/xmake/core/base/table.lua
index 5e04b0256..a360f7b42 100644
--- a/xmake/core/base/table.lua
+++ b/xmake/core/base/table.lua
@@ -155,7 +155,7 @@ end
-- clone table
function table.clone(self)
- local result
+ local result = self
if type(self) == "table" then
result = {}
for k, v in pairs(self) do
diff --git a/xmake/core/package/component.lua b/xmake/core/package/component.lua
new file mode 100644
index 000000000..780fa06f5
--- /dev/null
+++ b/xmake/core/package/component.lua
@@ -0,0 +1,148 @@
+--!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 component.lua
+--
+
+-- define module
+local component = component or {}
+local _instance = _instance or {}
+
+-- load modules
+local os = require("base/os")
+local io = require("base/io")
+local path = require("base/path")
+local utils = require("base/utils")
+local table = require("base/table")
+local option = require("base/option")
+local hashset = require("base/hashset")
+local scopeinfo = require("base/scopeinfo")
+local interpreter = require("base/interpreter")
+local language = require("language/language")
+local sandbox = require("sandbox/sandbox")
+
+-- new an instance
+function _instance.new(name, opt)
+ opt = opt or {}
+ local instance = table.inherit(_instance)
+ instance._NAME = name
+ instance._INFO = scopeinfo.new("component", {}, {interpreter = component._interpreter()})
+ instance._PACKAGE = opt.package
+ return instance
+end
+
+-- get the component name
+function _instance:name()
+ return self._NAME
+end
+
+-- get the type: component
+function _instance:type()
+ return "component"
+end
+
+-- get the it's package
+function _instance:package()
+ return self._PACKAGE
+end
+
+-- get the component configuration
+function _instance:get(name)
+ return self._INFO:get(name)
+end
+
+-- set the value to the component info
+function _instance:set(name, ...)
+ self._INFO:apival_set(name, ...)
+end
+
+-- add the value to the component info
+function _instance:add(name, ...)
+ self._INFO:apival_add(name, ...)
+end
+
+-- get the extra configuration
+function _instance:extraconf(name, item, key)
+ local conf = self._INFO:extraconf(name, item, key)
+ if conf == nil and self:base() then
+ conf = self:base():extraconf(name, item, key)
+ end
+ return conf
+end
+
+-- set the extra configuration
+function _instance:extraconf_set(name, item, key, value)
+ return self._INFO:extraconf_set(name, item, key, value)
+end
+
+-- get on_component script
+function _instance:_on_component()
+ local script = self:package():get("component")
+ local result = nil
+ if type(script) == "function" then
+ result = script
+ elseif type(script) == "table" then
+ result = script[self:name()]
+ result = result or script["__generic__"]
+ end
+ return result
+end
+
+-- load this component
+function _instance:_load()
+ local loaded = self._LOADED
+ if not loaded then
+ local script = self:_on_component()
+ if script then
+ local ok, errors = sandbox.load(script, self:package(), self)
+ if not ok then
+ os.raise("load component(%s) failed, %s", self:name(), errors or "unknown errors")
+ end
+ end
+ self._LOADED = true
+ end
+end
+
+-- the interpreter
+function component._interpreter()
+ local interp = component._INTERPRETER
+ if not interp then
+ interp = interpreter.new()
+ interp:api_define(component.apis())
+ interp:api_define(language.apis())
+ component._INTERPRETER = interp
+ end
+ return interp
+end
+
+-- get component apis
+function component.apis()
+ return {
+ values = {
+ -- component.add_xxx
+ "component.add_extsources"
+ }
+ }
+end
+
+-- new component
+function component.new(name, opt)
+ return _instance.new(name, opt)
+end
+
+-- return module
+return component
diff --git a/xmake/core/package/package.lua b/xmake/core/package/package.lua
index 131784172..224a8fa37 100644
--- a/xmake/core/package/package.lua
+++ b/xmake/core/package/package.lua
@@ -9,7 +9,7 @@
-- 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 package governing permissions and
+-- See the License for the specific language governing permissions and
-- limitations under the License.
--
-- Copyright (C) 2015-present, TBOOX Open Source Group.
@@ -41,6 +41,7 @@ local config = require("project/config")
local policy = require("project/policy")
local platform = require("platform/platform")
local platform_menu = require("platform/menu")
+local component = require("package/component")
local language = require("language/language")
local language_menu = require("language/menu")
local sandbox = require("sandbox/sandbox")
@@ -294,6 +295,19 @@ function _instance:artifacts_set(artifacts_info)
package:set(k, v)
end
end
+ if manifest.components then
+ local vars = manifest.components.vars
+ if vars then
+ for component_name, component_vars in pairs(vars) do
+ local comp = package:component(component_name)
+ if comp then
+ for k, v in pairs(component_vars) do
+ comp:set(k, v)
+ end
+ end
+ end
+ end
+ end
if manifest.envs then
local envs = self:_rawenvs()
for k, v in pairs(manifest.envs) do
@@ -779,19 +793,34 @@ function _instance:manifest_save()
end
end
- -- save variables
- local vars = {}
+ -- save global variables and component variables
+ local vars
+ local components
local apis = language.apis()
for _, apiname in ipairs(table.join(apis.values, apis.paths)) do
if apiname:startswith("package.add_") or apiname:startswith("package.set_") then
local name = apiname:sub(13)
local value = self:get(name)
if value ~= nil then
+ vars = vars or {}
vars[name] = value
end
+ for _, component_name in ipairs(table.wrap(self:get("components"))) do
+ local comp = self:component(component_name)
+ if comp then
+ local component_value = comp:get(name)
+ if component_value ~= nil then
+ components = components or {}
+ components.vars = components.vars or {}
+ components.vars[component_name] = components.vars[component_name] or {}
+ components.vars[component_name][name] = component_value
+ end
+ end
+ end
end
end
manifest.vars = vars
+ manifest.components = components
-- save repository
local repo = self:repo()
@@ -1453,12 +1482,21 @@ function _instance:_fetch_library(opt)
end
end
if fetchinfo then
+ local components_base = fetchinfo.components and fetchinfo.components.__base
if opt.external then
fetchinfo.sysincludedirs = fetchinfo.sysincludedirs or fetchinfo.includedirs
fetchinfo.includedirs = nil
+ if components_base then
+ components_base.sysincludedirs = components_base.sysincludedirs or components_base.includedirs
+ components_base.includedirs = nil
+ end
else
fetchinfo.includedirs = fetchinfo.includedirs or fetchinfo.sysincludedirs
fetchinfo.sysincludedirs = nil
+ if components_base then
+ components_base.includedirs = components_base.includedirs or components_base.sysincludedirs
+ components_base.sysincludedirs = nil
+ end
end
end
if fetchinfo and option.get("verbose") then
@@ -1474,7 +1512,17 @@ function _instance:_fetch_library(opt)
end
table.insert(fetchnames, self:name())
for _, fetchname in ipairs(fetchnames) do
- fetchinfo = self:find_package(fetchname, opt)
+ local components_extsources = {}
+ for name, comp in pairs(self:components()) do
+ for _, extsource in ipairs(table.wrap(comp:get("extsources"))) do
+ local extsource_info = extsource:split("::")
+ if fetchname:split("::")[1] == extsource_info[1] then
+ components_extsources[name] = extsource_info[2]
+ break
+ end
+ end
+ end
+ fetchinfo = self:find_package(fetchname, table.join(opt, {components_extsources = components_extsources}))
if fetchinfo then
break
end
@@ -1519,6 +1567,8 @@ function _instance:find_package(name, opt)
plat = self:plat(),
arch = self:arch(),
configs = table.join(self:configs(), opt.configs),
+ components = self:components_orderlist(),
+ components_extsources = opt.components_extsources,
buildhash = self:buildhash(), -- for xmake package or 3rd package manager, e.g. go:: ..
cachekey = opt.cachekey or "fetch_package_system",
external = opt.external,
@@ -1754,6 +1804,81 @@ function _instance:resourcedir(name)
end
end
+-- get the given package component
+function _instance:component(name)
+ return self:components()[name]
+end
+
+-- get package components
+--
+-- .e.g. add_components("graphics", "windows")
+--
+function _instance:components()
+ local components = self._COMPONENTS
+ if not components then
+ components = {}
+ for _, name in ipairs(table.wrap(self:get("components"))) do
+ components[name] = component.new(name, {package = self})
+ end
+ self._COMPONENTS = components
+ end
+ return components
+end
+
+-- get package dependencies of components
+--
+-- @see https://github.com/xmake-io/xmake/issues/2636#issuecomment-1284787681
+--
+-- @code
+-- add_components("graphics", {deps = "window"})
+-- @endcode
+--
+-- or
+--
+-- @code
+-- on_component(function (package, component))
+-- component:add("deps", "window")
+-- end)
+-- @endcode
+--
+function _instance:components_deps()
+ local components_deps = self._COMPONENTS_DEPS
+ if not components_deps then
+ components_deps = {}
+ for _, name in ipairs(table.wrap(self:get("components"))) do
+ components_deps[name] = self:extraconf("components", name, "deps") or self:component(name):get("deps")
+ end
+ self._COMPONENTS_DEPS = component_deps
+ end
+ return components_deps
+end
+
+-- get package components list with dependencies order
+function _instance:components_orderlist()
+ local components_orderlist = self._COMPONENTS_ORDERLIST
+ if not components_orderlist then
+ components_orderlist = {}
+ for _, name in ipairs(table.wrap(self:get("components"))) do
+ table.insert(components_orderlist, name)
+ table.join2(components_orderlist, self:_sort_componentdeps(name))
+ end
+ components_orderlist = table.reverse_unique(components_orderlist)
+ self._COMPONENTS_ORDERLIST = components_orderlist
+ end
+ return components_orderlist
+end
+
+-- sort component deps
+function _instance:_sort_componentdeps(name)
+ local orderdeps = {}
+ local plaindeps = self:components_deps() and self:components_deps()[name]
+ for _, dep in ipairs(table.wrap(plaindeps)) do
+ table.insert(orderdeps, dep)
+ table.join2(orderdeps, self:_sort_componentdeps(dep))
+ end
+ return orderdeps
+end
+
-- generate lto configs
function _instance:_generate_lto_configs(sourcekind)
@@ -1973,26 +2098,13 @@ end
-- the interpreter
function package._interpreter()
-
- -- the interpreter has been initialized? return it directly
- if package._INTERPRETER then
- return package._INTERPRETER
+ local interp = package._INTERPRETER
+ if not interp then
+ interp = interpreter.new()
+ interp:api_define(package.apis())
+ interp:api_define(language.apis())
+ package._INTERPRETER = interp
end
-
- -- init interpreter
- local interp = interpreter.new()
- assert(interp)
-
- -- define apis
- interp:api_define(package.apis())
-
- -- define apis for language
- interp:api_define(language.apis())
-
- -- save interpreter
- package._INTERPRETER = interp
-
- -- ok?
return interp
end
@@ -2074,6 +2186,7 @@ function package.apis()
, "package.add_imports"
, "package.add_configs"
, "package.add_extsources"
+ , "package.add_components"
}
, script =
{
@@ -2083,6 +2196,7 @@ function package.apis()
, "package.on_download"
, "package.on_install"
, "package.on_test"
+ , "package.on_component"
}
, keyvalues =
{
diff --git a/xmake/core/project/package.lua b/xmake/core/project/package.lua
index 4917e7c20..5db816e29 100644
--- a/xmake/core/project/package.lua
+++ b/xmake/core/project/package.lua
@@ -23,16 +23,17 @@ local package = {}
local _instance = _instance or {}
-- load modules
-local io = require("base/io")
-local os = require("base/os")
-local path = require("base/path")
-local table = require("base/table")
-local utils = require("base/utils")
-local semver = require("base/semver")
-local rule = require("project/rule")
-local config = require("project/config")
-local sandbox = require("sandbox/sandbox")
-local localcache = require("cache/localcache")
+local io = require("base/io")
+local os = require("base/os")
+local path = require("base/path")
+local table = require("base/table")
+local utils = require("base/utils")
+local semver = require("base/semver")
+local rule = require("project/rule")
+local config = require("project/config")
+local sandbox = require("sandbox/sandbox")
+local localcache = require("cache/localcache")
+local instance_deps = require("base/private/instance_deps")
-- save the requires info to the cache
function _instance:save()
@@ -50,6 +51,7 @@ function _instance:clear()
end
end
end
+ self._COMPONENT_DEPS = nil
end
-- dump this package
@@ -115,6 +117,38 @@ function _instance:libraryfiles()
return self:get("libfiles")
end
+-- get components
+function _instance:components()
+ return self:get("components")
+end
+
+-- get components list with link order
+function _instance:components_orderlist()
+ return self:get("__components_orderlist")
+end
+
+-- get the dependencies of components
+function _instance:components_deps()
+ return self:get("__components_deps")
+end
+
+-- get order dependencies of the given component
+function _instance:component_orderdeps(name)
+ local component_orderdeps = self._COMPONENT_ORDERDEPS
+ if not component_orderdeps then
+ component_orderdeps = {}
+ self._COMPONENT_ORDERDEPS = component_orderdeps
+ end
+
+ -- expand dependencies
+ local orderdeps = component_orderdeps[name]
+ if not orderdeps then
+ orderdeps = table.reverse_unique(self:_sort_componentdeps(name))
+ component_orderdeps[name] = orderdeps
+ end
+ return orderdeps
+end
+
-- get the extra info from the given name
function _instance:extra(name)
local extrainfo = self:extrainfo()
@@ -211,6 +245,17 @@ function _instance:rules()
return rules
end
+-- sort component deps
+function _instance:_sort_componentdeps(name)
+ local orderdeps = {}
+ local plaindeps = self:components_deps() and self:components_deps()[name]
+ for _, dep in ipairs(table.wrap(plaindeps)) do
+ table.insert(orderdeps, dep)
+ table.join2(orderdeps, self:_sort_componentdeps(dep))
+ end
+ return orderdeps
+end
+
-- get cache
function package._cache()
return localcache.cache("package")
diff --git a/xmake/core/project/target.lua b/xmake/core/project/target.lua
index e9f986c3a..9fc02ff19 100644
--- a/xmake/core/project/target.lua
+++ b/xmake/core/project/target.lua
@@ -414,13 +414,38 @@ end
function _instance:get_from_pkgs(name, opt)
local values = {}
for _, pkg in ipairs(self:orderpkgs(opt)) do
- -- uses them instead of the builtin configs if exists extra package config
- -- e.g. `add_packages("xxx", {links = "xxx"})`
local configinfo = self:pkgconfig(pkg:name())
- if configinfo and configinfo[name] then
- table.join2(values, configinfo[name])
+ -- get values from package components
+ -- e.g. `add_packages("sfml", {components = {"graphics", "window"}})`
+ if configinfo and configinfo.components and pkg:components() then
+ local components_enabled = hashset.new()
+ for _, comp in ipairs(table.wrap(configinfo.components)) do
+ components_enabled:insert(comp)
+ for _, dep in ipairs(table.wrap(pkg:component_orderdeps(comp))) do
+ components_enabled:insert(dep)
+ end
+ end
+ components_enabled:insert("__base")
+ -- if we can't find the values from the component, we need to fall back to __base to find them.
+ -- it contains some common values of all components
+ local components = table.wrap(pkg:components())
+ for _, component_name in ipairs(table.join(pkg:components_orderlist(), "__base")) do
+ if components_enabled:has(component_name) then
+ local info = components[component_name]
+ if info then
+ table.join2(values, info[name])
+ else
+ local components_str = table.concat(table.wrap(configinfo.components), ", ")
+ utils.warning("unknown component(%s) in add_packages(%s, {components = {%s}})", component_name, pkg:name(), components_str)
+ end
+ end
+ end
+ -- get values instead of the builtin configs if exists extra package config
+ -- e.g. `add_packages("xxx", {links = "xxx"})`
+ elseif configinfo and configinfo[name] then
+ table.join2(values, configinfo[name])
else
- -- uses the builtin package configs
+ -- get values from the builtin package configs
table.join2(values, pkg:get(name))
end
end
diff --git a/xmake/modules/lib/detect/find_package.lua b/xmake/modules/lib/detect/find_package.lua
index d3c9e57d1..dc1c92848 100644
--- a/xmake/modules/lib/detect/find_package.lua
+++ b/xmake/modules/lib/detect/find_package.lua
@@ -112,6 +112,11 @@ function main(name, opt)
if result and result.includedirs and opt.external then
result.sysincludedirs = result.includedirs
result.includedirs = nil
+ local components_base = result.components and result.components.__base
+ if components_base then
+ components_base.sysincludedirs = components_base.includedirs
+ components_base.includedirs = nil
+ end
end
-- cache result
diff --git a/xmake/modules/package/manager/brew/find_package.lua b/xmake/modules/package/manager/brew/find_package.lua
index 9706bfd8e..4c0061bb8 100644
--- a/xmake/modules/package/manager/brew/find_package.lua
+++ b/xmake/modules/package/manager/brew/find_package.lua
@@ -47,19 +47,10 @@ function _brew_pkg_rootdir()
return brew_pkg_rootdir or nil
end
--- find package from the brew package manager
---
--- @param name the package name, e.g. zlib, pcre/libpcre16
--- @param opt the options, e.g. {verbose = true, version = "1.12.x")
---
-function main(name, opt)
-
- -- find the prefix directory of brew
- local brew_pkg_rootdir = _brew_pkg_rootdir()
- if not brew_pkg_rootdir then
- return
- end
- brew_pkg_rootdir = path.join(brew_pkg_rootdir, opt.plat == "macosx" and "Cellar" or "opt")
+-- find package from pkg-config
+function _find_package_from_pkgconfig(name, opt)
+ opt = opt or {}
+ local brew_pkg_rootdir = opt.brew_pkg_rootdir
-- parse name, e.g. pcre/libpcre16
local nameinfo = name:split('/')
@@ -92,9 +83,51 @@ function main(name, opt)
end
end
end
+ return result
+end
+
+-- find package from the brew package manager
+--
+-- @param name the package name, e.g. zlib, pcre/libpcre16
+-- @param opt the options, e.g. {verbose = true, version = "1.12.x")
+--
+function main(name, opt)
+
+ -- find the prefix directory of brew
+ opt = opt or {}
+ local brew_pkg_rootdir = _brew_pkg_rootdir()
+ if not brew_pkg_rootdir then
+ return
+ end
+ brew_pkg_rootdir = path.join(brew_pkg_rootdir, opt.plat == "macosx" and "Cellar" or "opt")
+
+ -- find package from pkg-config
+ local result = _find_package_from_pkgconfig(name,
+ table.join(opt, {brew_pkg_rootdir = brew_pkg_rootdir}))
+
+ -- find components
+ local components
+ local components_extsources = opt.components_extsources
+ for _, comp in ipairs(opt.components) do
+ local extsource = components_extsources and components_extsources[comp]
+ if extsource then
+ local component_result = _find_package_from_pkgconfig(extsource,
+ table.join(opt, {brew_pkg_rootdir = brew_pkg_rootdir}))
+ if component_result then
+ components = components or {}
+ components[comp] = component_result
+ end
+ end
+ end
+ if components then
+ result = result or {}
+ result.components = components
+ components.__base = {}
+ end
-- find package from xxx/lib, xxx/include
if not result then
+ local nameinfo = name:split('/')
local pkgdir = find_path("lib", path.join(brew_pkg_rootdir, nameinfo[1], "*"))
if pkgdir then
local links = {}
diff --git a/xmake/modules/package/manager/xmake/find_package.lua b/xmake/modules/package/manager/xmake/find_package.lua
index 8ba890591..10c58436a 100644
--- a/xmake/modules/package/manager/xmake/find_package.lua
+++ b/xmake/modules/package/manager/xmake/find_package.lua
@@ -28,6 +28,20 @@ import("core.language.language")
import("lib.detect.find_file")
import("lib.detect.find_library")
+-- deduplicate values
+function _deduplicate_values(values)
+ for _, k in ipairs(table.keys(values)) do
+ local v = values[k]
+ if type(v) == "table" then
+ if k == "links" or k == "syslinks" or k == "frameworks" then
+ values[k] = table.reverse_unique(v)
+ else
+ values[k] = table.unique(v)
+ end
+ end
+ end
+end
+
-- find package from the repository (maybe only include and no links)
function _find_package_from_repo(name, opt)
@@ -76,8 +90,21 @@ function _find_package_from_repo(name, opt)
-- get links and link directories
local links = {}
local linkdirs = {}
+ local components = opt.components
if vars.links then
table.join2(links, vars.links)
+ elseif components and manifest.components then
+ -- get links from components
+ local vars = manifest.components.vars
+ if vars then
+ for _, component_name in ipairs(components) do
+ local component_vars = vars[component_name]
+ if component_vars and component_vars.links then
+ table.join2(links, component_vars.links)
+ end
+ end
+ end
+ links = table.reverse_unique(links)
else
-- we scan links automatically
local found = false
@@ -151,31 +178,54 @@ function _find_package_from_repo(name, opt)
result.libfiles = table.join(result.libfiles or {}, path.join(libinfo.linkdir, libinfo.filename))
end
end
- if result.links then
- result.links = table.unique(result.links)
- end
if result.libfiles then
- result.libfiles = table.unique(table.join(result.libfiles, libfiles))
+ result.libfiles = table.join(result.libfiles, libfiles)
end
-- inherit the other prefix variables
+ local components_base = {includedirs = table.clone(result.includedirs), linkdirs = table.clone(result.linkdirs)}
for name, values in pairs(vars) do
if name ~= "links" and name ~= "linkdirs" and name ~= "includedirs" then
result[name] = values
+ components_base[name] = table.clone(values)
end
end
- -- update the project references file
- if result then
- local projectdir = os.projectdir()
- if projectdir and os.isdir(projectdir) then
- local references_file = path.join(installdir, "references.txt")
- local references = os.isfile(references_file) and io.load(references_file) or {}
- references[projectdir] = os.date("%y%m%d")
- io.save(references_file, references)
+ -- get component values
+ if components and manifest.components then
+ local vars = manifest.components.vars
+ if vars then
+ _deduplicate_values(components_base)
+ result.components = result.components or {}
+ result.components.__base = components_base
+ for _, component_name in ipairs(components) do
+ local comp = vars[component_name]
+ if comp then
+ result.components[component_name] = comp
+
+ -- merge component values to root
+ for k, v in pairs(comp) do
+ if k ~= "links" then
+ result[k] = table.join(result[k] or {}, v)
+ end
+ end
+ end
+ end
end
end
+ -- deduplicate result
+ _deduplicate_values(result)
+
+ -- update the project references file
+ local projectdir = os.projectdir()
+ if projectdir and os.isdir(projectdir) then
+ local references_file = path.join(installdir, "references.txt")
+ local references = os.isfile(references_file) and io.load(references_file) or {}
+ references[projectdir] = os.date("%y%m%d")
+ io.save(references_file, references)
+ end
+
-- get version and license
result.version = manifest.version or path.filename(path.directory(path.directory(manifest_file)))
result.license = manifest.license
@@ -269,8 +319,6 @@ function _find_package_from_packagedirs(name, opt)
result[infoname] = packageinfo:get(infoname)
end
end
-
- -- ok?
return result
end
diff --git a/xmake/modules/private/action/require/impl/package.lua b/xmake/modules/private/action/require/impl/package.lua
index 0608a3960..450096c1b 100644
--- a/xmake/modules/private/action/require/impl/package.lua
+++ b/xmake/modules/private/action/require/impl/package.lua
@@ -832,6 +832,11 @@ function _load_package(packagename, requireinfo, opt)
on_load(package)
end
+ -- load all components
+ for _, component in pairs(package:components()) do
+ component:_load()
+ end
+
-- load environments from the manifest to enable the environments of on_install()
package:envs_load()
diff --git a/xmake/modules/private/action/require/impl/register_packages.lua b/xmake/modules/private/action/require/impl/register_packages.lua
index 1bedc2969..4b92b75d8 100644
--- a/xmake/modules/private/action/require/impl/register_packages.lua
+++ b/xmake/modules/private/action/require/impl/register_packages.lua
@@ -34,7 +34,7 @@ end
-- libs: includedirs, links, linkdirs ...
function _register_required_package_libs(instance, required_package, is_deps)
if instance:is_library() then
- local fetchinfo = instance:fetch()
+ local fetchinfo = table.clone(instance:fetch())
if fetchinfo then
fetchinfo.name = nil
if is_deps then
@@ -50,7 +50,31 @@ function _register_required_package_libs(instance, required_package, is_deps)
fetchinfo.static = nil
fetchinfo.shared = nil
end
+
+ -- merge into the root values
+ local components = fetchinfo.components
+ fetchinfo.components = nil
required_package:add(fetchinfo)
+
+ -- save components list and dependencies
+ if components then
+ required_package:set("__components_deps", instance:components_deps())
+ required_package:set("__components_orderlist", instance:components_orderlist())
+ end
+
+ -- merge into the components values
+ local required_components = required_package:get("components")
+ if required_components then
+ fetchinfo.libfiles = nil
+ local components_base = required_components.__base or {}
+ for k, v in pairs(fetchinfo) do
+ local values = table.wrap(components_base[k])
+ components_base[k] = table.unwrap(table.unique(table.join(values, v)))
+ end
+ required_components.__base = components_base
+ else
+ required_package:set("components", components)
+ end
end
end
end
diff --git a/xmake/modules/private/action/require/info.lua b/xmake/modules/private/action/require/info.lua
index 7a6c096c6..270316ac9 100644
--- a/xmake/modules/private/action/require/info.lua
+++ b/xmake/modules/private/action/require/info.lua
@@ -246,6 +246,21 @@ function main(requires_raw)
end
end
+ -- show components
+ local components = instance:get("components")
+ if components then
+ cprint(" -> ${color.dump.string_quote}components${clear}: ")
+ for _, comp in ipairs(components) do
+ cprintf(" -> ${cyan}%s${clear}: ", comp)
+ local plaindeps = instance:extraconf("components", comp, "deps")
+ if plaindeps then
+ print("%s", table.concat(table.wrap(plaindeps), ", "))
+ else
+ print("")
+ end
+ end
+ end
+
-- show references
local references = instance:references()
if references then