diff options
| author | ruki <[email protected]> | 2022-10-20 11:20:37 +0800 |
|---|---|---|
| committer | GitHub <[email protected]> | 2022-10-20 11:20:37 +0800 |
| commit | 5e10a17b2602d756bb918b75ebb9d4fa3f9e47ea (patch) | |
| tree | e12a91a696b62f011113caae5907fe7be39b4b31 | |
| parent | e6affabd6e20bbb179fea1f315414142cef650e3 (diff) | |
| parent | 2183a9d1ac3d63351a36b5a855c7fce02405d374 (diff) | |
Merge pull request #2932 from xmake-io/components
Add package components support
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 |
