From 5cd7e9ab5bccd3bf469eb5da49f70085a9753696 Mon Sep 17 00:00:00 2001 From: ruki Date: Fri, 12 Feb 2021 23:00:24 +0800 Subject: move require action modules --- xmake/actions/build/statistics.lua | 2 +- xmake/actions/config/main.lua | 2 +- xmake/actions/require/main.lua | 18 +- xmake/modules/private/action/require/clean.lua | 103 ++ xmake/modules/private/action/require/export.lua | 71 ++ xmake/modules/private/action/require/fetch.lua | 94 ++ .../action/require/impl/actions/download.lua | 268 +++++ .../require/impl/actions/download_resources.lua | 96 ++ .../action/require/impl/actions/install.lua | 273 +++++ .../action/require/impl/actions/patch_sources.lua | 112 ++ .../private/action/require/impl/actions/test.lua | 59 ++ .../private/action/require/impl/environment.lua | 73 ++ .../private/action/require/impl/package.lua | 1106 ++++++++++++++++++++ .../private/action/require/impl/packagenv.lua | 81 ++ .../private/action/require/impl/repository.lua | 113 ++ .../private/action/require/impl/utils/filter.lua | 146 +++ .../action/require/impl/utils/get_requires.lua | 57 + .../action/require/impl/utils/url_filename.lua | 25 + xmake/modules/private/action/require/info.lua | 261 +++++ xmake/modules/private/action/require/install.lua | 209 ++++ xmake/modules/private/action/require/list.lua | 93 ++ xmake/modules/private/action/require/scan.lua | 90 ++ xmake/modules/private/action/require/search.lua | 65 ++ xmake/modules/private/action/require/uninstall.lua | 68 ++ xmake/modules/private/package/clean.lua | 103 -- xmake/modules/private/package/export.lua | 71 -- xmake/modules/private/package/fetch.lua | 94 -- .../private/package/impl/actions/download.lua | 268 ----- .../package/impl/actions/download_resources.lua | 96 -- .../private/package/impl/actions/install.lua | 273 ----- .../private/package/impl/actions/patch_sources.lua | 112 -- .../modules/private/package/impl/actions/test.lua | 59 -- xmake/modules/private/package/impl/environment.lua | 73 -- xmake/modules/private/package/impl/package.lua | 1106 -------------------- xmake/modules/private/package/impl/packagenv.lua | 81 -- xmake/modules/private/package/impl/repository.lua | 113 -- .../modules/private/package/impl/utils/filter.lua | 146 --- .../private/package/impl/utils/get_requires.lua | 57 - .../private/package/impl/utils/url_filename.lua | 25 - xmake/modules/private/package/info.lua | 261 ----- xmake/modules/private/package/install.lua | 209 ---- xmake/modules/private/package/list.lua | 93 -- xmake/modules/private/package/scan.lua | 90 -- xmake/modules/private/package/search.lua | 65 -- xmake/modules/private/package/uninstall.lua | 68 -- 45 files changed, 3474 insertions(+), 3474 deletions(-) create mode 100644 xmake/modules/private/action/require/clean.lua create mode 100644 xmake/modules/private/action/require/export.lua create mode 100644 xmake/modules/private/action/require/fetch.lua create mode 100644 xmake/modules/private/action/require/impl/actions/download.lua create mode 100644 xmake/modules/private/action/require/impl/actions/download_resources.lua create mode 100644 xmake/modules/private/action/require/impl/actions/install.lua create mode 100644 xmake/modules/private/action/require/impl/actions/patch_sources.lua create mode 100644 xmake/modules/private/action/require/impl/actions/test.lua create mode 100644 xmake/modules/private/action/require/impl/environment.lua create mode 100644 xmake/modules/private/action/require/impl/package.lua create mode 100644 xmake/modules/private/action/require/impl/packagenv.lua create mode 100644 xmake/modules/private/action/require/impl/repository.lua create mode 100644 xmake/modules/private/action/require/impl/utils/filter.lua create mode 100644 xmake/modules/private/action/require/impl/utils/get_requires.lua create mode 100644 xmake/modules/private/action/require/impl/utils/url_filename.lua create mode 100644 xmake/modules/private/action/require/info.lua create mode 100644 xmake/modules/private/action/require/install.lua create mode 100644 xmake/modules/private/action/require/list.lua create mode 100644 xmake/modules/private/action/require/scan.lua create mode 100644 xmake/modules/private/action/require/search.lua create mode 100644 xmake/modules/private/action/require/uninstall.lua delete mode 100644 xmake/modules/private/package/clean.lua delete mode 100644 xmake/modules/private/package/export.lua delete mode 100644 xmake/modules/private/package/fetch.lua delete mode 100644 xmake/modules/private/package/impl/actions/download.lua delete mode 100644 xmake/modules/private/package/impl/actions/download_resources.lua delete mode 100644 xmake/modules/private/package/impl/actions/install.lua delete mode 100644 xmake/modules/private/package/impl/actions/patch_sources.lua delete mode 100644 xmake/modules/private/package/impl/actions/test.lua delete mode 100644 xmake/modules/private/package/impl/environment.lua delete mode 100644 xmake/modules/private/package/impl/package.lua delete mode 100644 xmake/modules/private/package/impl/packagenv.lua delete mode 100644 xmake/modules/private/package/impl/repository.lua delete mode 100644 xmake/modules/private/package/impl/utils/filter.lua delete mode 100644 xmake/modules/private/package/impl/utils/get_requires.lua delete mode 100644 xmake/modules/private/package/impl/utils/url_filename.lua delete mode 100644 xmake/modules/private/package/info.lua delete mode 100644 xmake/modules/private/package/install.lua delete mode 100644 xmake/modules/private/package/list.lua delete mode 100644 xmake/modules/private/package/scan.lua delete mode 100644 xmake/modules/private/package/search.lua delete mode 100644 xmake/modules/private/package/uninstall.lua diff --git a/xmake/actions/build/statistics.lua b/xmake/actions/build/statistics.lua index 1f8126c83..84a2a5cc5 100644 --- a/xmake/actions/build/statistics.lua +++ b/xmake/actions/build/statistics.lua @@ -24,7 +24,7 @@ import("core.base.process") import("core.project.config") import("core.platform.platform") import("private.action.update.fetch_version") -import("private.package.impl.packagenv") +import("private.action.require.impl.packagenv") -- statistics is enabled? function _is_enabled() diff --git a/xmake/actions/config/main.lua b/xmake/actions/config/main.lua index 73b73dbea..be96692a3 100644 --- a/xmake/actions/config/main.lua +++ b/xmake/actions/config/main.lua @@ -31,7 +31,7 @@ import("scangen") import("menuconf", {alias = "menuconf_show"}) import("configfiles", {alias = "generate_configfiles"}) import("configheader", {alias = "generate_configheader"}) -import("private.package.install", {alias = "install_requires"}) +import("private.action.require.install", {alias = "install_requires"}) -- filter option function _option_filter(name) diff --git a/xmake/actions/require/main.lua b/xmake/actions/require/main.lua index 7b068ff70..f87e8c11c 100644 --- a/xmake/actions/require/main.lua +++ b/xmake/actions/require/main.lua @@ -24,15 +24,15 @@ import("core.base.task") import("core.project.config") import("core.project.project") import("core.platform.platform") -import("private.package.list") -import("private.package.scan") -import("private.package.info") -import("private.package.fetch") -import("private.package.clean") -import("private.package.search") -import("private.package.export") -import("private.package.install") -import("private.package.uninstall") +import("private.action.require.list") +import("private.action.require.scan") +import("private.action.require.info") +import("private.action.require.fetch") +import("private.action.require.clean") +import("private.action.require.search") +import("private.action.require.export") +import("private.action.require.install") +import("private.action.require.uninstall") -- -- the default repositories: diff --git a/xmake/modules/private/action/require/clean.lua b/xmake/modules/private/action/require/clean.lua new file mode 100644 index 000000000..fb93e6304 --- /dev/null +++ b/xmake/modules/private/action/require/clean.lua @@ -0,0 +1,103 @@ +--!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 clean.lua +-- + +-- imports +import("core.base.option") +import("core.package.package") +import("core.cache.localcache") + +-- clear the unused or invalid package directories +function _clear_packagedirs(packagedir) + + -- clear them + local package_name = path.filename(packagedir) + for _, versiondir in ipairs(os.dirs(path.join(packagedir, "*"))) do + local version = path.filename(versiondir) + for _, hashdir in ipairs(os.dirs(path.join(versiondir, "*"))) do + local hash = path.filename(hashdir) + local references_file = path.join(hashdir, "references.txt") + local referenced = false + local references = os.isfile(references_file) and io.load(references_file) or nil + if references then + for projectdir, refdate in pairs(references) do + if os.isdir(projectdir) then + referenced = true + break + end + end + end + local manifest_file = path.join(hashdir, "manifest.txt") + local status = nil + if os.emptydir(hashdir) then + status = "empty" + elseif not referenced then + status = "unused" + elseif not os.isfile(manifest_file) then + status = "invalid" + end + if status then + local description = string.format("remove this ${magenta}%s-%s${clear}/${yellow}%s${clear} (${red}%s${clear})", package_name, version, hash, status) + local confirm = utils.confirm({default = true, description = description}) + if confirm then + os.rm(hashdir) + end + end + end + if os.emptydir(versiondir) then + os.rm(versiondir) + end + end + if os.emptydir(packagedir) then + os.rm(packagedir) + end +end + +-- clean the given or all package caches +function main(package_names) + + -- trace + print("clearing packages ..") + + -- clear all unused packages + local installdir = package.installdir() + if package_names then + for _, package_name in ipairs(package_names) do + for _, packagedir in ipairs(os.dirs(path.join(installdir, package_name:sub(1, 1), package_name))) do + _clear_packagedirs(packagedir) + end + end + else + for _, packagedir in ipairs(os.dirs(path.join(installdir, "*", "*"))) do + _clear_packagedirs(packagedir) + end + end + + -- trace + print("clearing caches ..") + + -- clear cache directory + os.rm(package.cachedir()) + + -- clear require cache + local require_cache = localcache.cache("package") + require_cache:clear() + require_cache:save() +end + diff --git a/xmake/modules/private/action/require/export.lua b/xmake/modules/private/action/require/export.lua new file mode 100644 index 000000000..218a669c6 --- /dev/null +++ b/xmake/modules/private/action/require/export.lua @@ -0,0 +1,71 @@ +--!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 export.lua +-- + +-- imports +import("core.base.task") +import("core.base.option") +import("private.action.require.impl.package") +import("private.action.require.impl.repository") +import("private.action.require.impl.environment") +import("private.action.require.impl.utils.get_requires") + +-- export the given packages +function main(requires_raw) + + -- enter environment + environment.enter() + + -- pull all repositories first if not exists + if not repository.pulled() then + task.run("repo", {update = true}) + end + + -- get requires and extra config + local requires_extra = nil + local requires, requires_extra = get_requires(requires_raw) + if not requires or #requires == 0 then + return + end + + -- export packages + local exportdir = option.get("exportdir") + local packages = package.export_packages(requires, {requires_extra = requires_extra, exportdir = exportdir}) + for _, instance in ipairs(packages) do + print("export: %s%s ok!", instance:name(), instance:version_str() and ("-" .. instance:version_str()) or "") + end + if not packages or #packages == 0 then + cprint("${bright}packages(%s) not found, maybe they don’t exactly match the configuration.", table.concat(requires_raw, ", ")) + if os.getenv("XREPO_WORKING") then + print("please attempt to export them with `-f/--configs=` option, e.g.") + print(" - xrepo export -f \"name=value, ...\" package") + print(" - xrepo export -m debug -k shared -f \"name=value, ...\" package") + else + print("please attempt to export them with `--extra=` option, e.g.") + print(" - xmake require --export --extra=\"{configs={...}}\" package") + print(" - xmake require --export --extra=\"{debug=true,configs={shared=true}}\" package") + end + else + print("output: %s", exportdir) + end + + -- leave environment + environment.leave() +end + diff --git a/xmake/modules/private/action/require/fetch.lua b/xmake/modules/private/action/require/fetch.lua new file mode 100644 index 000000000..e64b7f0b0 --- /dev/null +++ b/xmake/modules/private/action/require/fetch.lua @@ -0,0 +1,94 @@ +--!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 fetch.lua +-- + +-- imports +import("core.base.option") +import("core.base.hashset") +import("core.project.project") +import("core.package.package", {alias = "core_package"}) +import("core.tool.linker") +import("core.tool.compiler") +import("private.action.require.impl.package") +import("private.action.require.impl.repository") +import("private.action.require.impl.utils.get_requires") + +-- fetch the given package info +function main(requires_raw) + + -- get requires and extra config + local requires_extra = nil + local requires, requires_extra = get_requires(requires_raw) + if not requires or #requires == 0 then + return + end + + -- get the fetching modes + local fetchmodes = option.get("fetch_modes") + if fetchmodes then + fetchmodes = hashset.from(fetchmodes:split(',', {plain = true})) + end + + -- fetch all packages + local fetchinfos = {} + local nodeps = not (fetchmodes and fetchmodes:has("deps")) + for _, instance in irpairs(package.load_packages(requires, {requires_extra = requires_extra, nodeps = nodeps})) do + local fetchinfo = instance:fetch({external = (fetchmodes and fetchmodes:has("external") or false)}) + if fetchinfo then + table.insert(fetchinfos, fetchinfo) + end + end + + -- show results + if #fetchinfos > 0 then + local flags = {} + if fetchmodes and fetchmodes:has("cflags") then + for _, fetchinfo in ipairs(fetchinfos) do + table.join2(flags, compiler.map_flags("cxx", "define", fetchinfo.defines)) + table.join2(flags, compiler.map_flags("cxx", "includedir", fetchinfo.includedirs)) + table.join2(flags, compiler.map_flags("cxx", "sysincludedir", fetchinfo.sysincludedirs)) + for _, cflag in ipairs(fetchinfo.cflags) do + table.insert(flags, cflag) + end + for _, cxflag in ipairs(fetchinfo.cxflags) do + table.insert(flags, cxflag) + end + for _, cxxflag in ipairs(fetchinfo.cxxflags) do + table.insert(flags, cxxflag) + end + end + end + if fetchmodes and fetchmodes:has("ldflags") then + for _, fetchinfo in ipairs(fetchinfos) do + table.join2(flags, linker.map_flags("binary", {"cxx"}, "linkdir", fetchinfo.linkdirs)) + table.join2(flags, linker.map_flags("binary", {"cxx"}, "link", fetchinfo.links)) + table.join2(flags, linker.map_flags("binary", {"cxx"}, "syslink", fetchinfo.syslinks)) + for _, ldflag in ipairs(fetchinfo.ldflags) do + table.insert(flags, ldflags) + end + end + end + if #flags > 0 then + print(os.args(flags)) + else + print(fetchinfos) + end + end +end + diff --git a/xmake/modules/private/action/require/impl/actions/download.lua b/xmake/modules/private/action/require/impl/actions/download.lua new file mode 100644 index 000000000..620ba67ec --- /dev/null +++ b/xmake/modules/private/action/require/impl/actions/download.lua @@ -0,0 +1,268 @@ +--!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 download.lua +-- + +-- imports +import("core.base.option") +import("core.base.tty") +import("core.base.hashset") +import("core.project.config") +import("core.package.package", {alias = "core_package"}) +import("lib.detect.find_file") +import("lib.detect.find_directory") +import("private.action.require.impl.utils.filter") +import("private.action.require.impl..utils.url_filename") +import("net.http") +import("devel.git") +import("utils.archive") + +-- checkout codes from git +function _checkout(package, url, sourcedir, url_alias) + + -- use previous source directory if exists + local packagedir = path.join(sourcedir, package:name()) + if os.isdir(packagedir) and + not (option.get("force") and package:branch()) then -- we need disable cache if we force to clone from the given branch + + -- clean the previous build files + git.clean({repodir = packagedir, force = true, all = true}) + -- reset the previous modified files + git.reset({repodir = packagedir, hard = true}) + tty.erase_line_to_start().cr() + return + end + + -- we can use local package from the search directories directly if network is too slow + local localdir = find_directory(package:name() .. archive.extension(url), core_package.searchdirs()) + if localdir and os.isdir(localdir) then + git.clean({repodir = localdir, force = true, all = true}) + tty.erase_line_to_start().cr() + return + end + + -- remove temporary directory + os.rm(sourcedir .. ".tmp") + + -- download package from branches? + packagedir = path.join(sourcedir .. ".tmp", package:name()) + if package:branch() then + + -- only shadow clone this branch + git.clone(url, {depth = 1, recursive = true, branch = package:branch(), outputdir = packagedir}) + + -- download package from revision or tag? + else + + -- clone whole history and tags + git.clone(url, {outputdir = packagedir, recursive = true}) + + -- attempt to checkout the given version + git.checkout(package:revision(url_alias) or package:tag() or package:version_str(), {repodir = packagedir}) + end + + -- move to source directory + os.rm(sourcedir) + os.mv(sourcedir .. ".tmp", sourcedir) + + -- trace + tty.erase_line_to_start().cr() + cprint("${yellow} => ${clear}clone %s %s .. ${color.success}${text.success}", url, package:version_str()) +end + +-- download codes from ftp/http/https +function _download(package, url, sourcedir, url_alias, url_excludes) + + -- get package file + local packagefile = url_filename(url) + + -- get sourcehash from the given url + -- + -- we need not sourcehash and skip checksum to try download it directly if no version list in package() + -- @see https://github.com/xmake-io/xmake/issues/930 + -- https://github.com/xmake-io/xmake/issues/1009 + -- + local sourcehash = package:sourcehash(url_alias) + assert(not package:verify() or not package:get("versions") or sourcehash, "cannot get source hash of %s in package(%s)", url, package:name()) + + -- the package file have been downloaded? + local cached = true + if not os.isfile(packagefile) or sourcehash ~= hash.sha256(packagefile) then + + -- no cached + cached = false + + -- attempt to remove package file first + os.tryrm(packagefile) + + -- download or copy package file + local localfile = find_file(path.filename(packagefile), core_package.searchdirs()) + if os.isfile(url) then + os.cp(url, packagefile) + elseif localfile and os.isfile(localfile) then + -- we can use local package from the search directories directly if network is too slow + os.cp(localfile, packagefile) + else + http.download(url, packagefile) + end + + -- check hash + if sourcehash and sourcehash ~= hash.sha256(packagefile) then + raise("unmatched checksum!") + end + end + + -- extract package file + os.rm(sourcedir .. ".tmp") + if archive.extract(packagefile, sourcedir .. ".tmp", {excludes = url_excludes}) then + -- move to source directory + os.rm(sourcedir) + os.mv(sourcedir .. ".tmp", sourcedir) + else + -- create an empty source directory if do not extract package file + os.tryrm(sourcedir) + os.mkdir(sourcedir) + end + + -- save original file path + package:originfile_set(path.absolute(packagefile)) + + -- trace + tty.erase_line_to_start().cr() + if not cached then + cprint("${yellow} => ${clear}download %s .. ${color.success}${text.success}", url) + end +end + +-- get sorted urls +function _urls(package) + + -- sort urls from the version source + local urls = {{}, {}} + for _, url in ipairs(package:urls()) do + if git.checkurl(url) then + table.insert(urls[1], url) + elseif not package:verify() or not package:get("versions") or package:sourcehash(package:url_alias(url)) then + table.insert(urls[2], url) + end + end + if package:gitref() then + return table.join(urls[1], urls[2]) + else + return table.join(urls[2], urls[1]) + end +end + +-- download the given package +function main(package) + + -- get working directory of this package + local workdir = package:cachedir() + + -- ensure the working directory first + os.mkdir(workdir) + + -- enter the working directory + local oldir = os.cd(workdir) + + -- lock this package + package:lock() + + -- get urls + local urls = _urls(package) + assert(#urls > 0, "cannot get url of package(%s)", package:name()) + + -- download package from urls + local ok = false + local urls_failed = {} + for idx, url in ipairs(urls) do + + -- get url alias + local url_alias = package:url_alias(url) + + -- get url excludes + local url_excludes = package:url_excludes(url) + + -- filter url + url = filter.handle(url, package) + + -- download url + ok = try + { + function () + + -- download package + local sourcedir = "source" + if git.checkurl(url) then + _checkout(package, url, sourcedir, url_alias) + else + _download(package, url, sourcedir, url_alias, url_excludes) + end + return true + end, + catch + { + function (errors) + + -- show or save the last errors + if errors and (option.get("verbose") or option.get("diagnosis")) then + cprint("${dim color.error}error: ${clear}%s", errors) + end + + -- trace + tty.erase_line_to_start().cr() + if git.checkurl(url) then + cprint("${yellow} => ${clear}clone %s %s .. ${color.failure}${text.failure}", url, package:version_str()) + else + cprint("${yellow} => ${clear}download %s .. ${color.failure}${text.failure}", url) + end + table.insert(urls_failed, url) + + -- failed? break it + if idx == #urls and not package:optional() then + if #urls_failed > 0 then + print("") + print("we can also download these packages manually:") + local searchnames = hashset.new() + for _, url_failed in ipairs(urls_failed) do + cprint(" ${yellow}- %s", url_failed) + searchnames:insert(url_filename(url_failed)) + end + cprint("to the local search directories: ${bright}%s", table.concat(table.wrap(core_package.searchdirs()), path.envsep())) + cprint(" ${bright}- %s", table.concat(searchnames:to_array(), ", ")) + cprint("and we can run `xmake g --pkg_searchdirs=/xxx` to set the search directories.") + end + raise("download failed!") + end + end + } + } + + -- ok? break it + if ok then break end + end + + -- unlock this package + package:unlock() + + -- leave working directory + os.cd(oldir) + return ok +end + + diff --git a/xmake/modules/private/action/require/impl/actions/download_resources.lua b/xmake/modules/private/action/require/impl/actions/download_resources.lua new file mode 100644 index 000000000..6447b3ea2 --- /dev/null +++ b/xmake/modules/private/action/require/impl/actions/download_resources.lua @@ -0,0 +1,96 @@ +--!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 download_resources.lua +-- + +-- imports +import("core.base.option") +import("core.package.package", {alias = "core_package"}) +import("lib.detect.find_file") +import("net.http") +import("utils.archive") + +-- download resources +function _download(package, resource_name, resource_url, resource_hash) + + -- trace + vprint("downloading resource(%s: %s) to %s-%s ..", resource_name, resource_url, package:name(), package:version_str()) + + -- get the resource file + local resource_file = assert(package:resourcefile(resource_name), "invalid resource file!") + + -- ensure lower hash + if resource_hash then + resource_hash = resource_hash:lower() + end + + -- the package file have been downloaded? + local cached = true + if option.get("force") or not os.isfile(resource_file) or resource_hash ~= hash.sha256(resource_file) then + + -- no cached + cached = false + + -- attempt to remove the previous file first + os.tryrm(resource_file) + + -- download or copy the resource file + local localfile = find_file(path.filename(resource_file), core_package.searchdirs()) + if localfile and os.isfile(localfile) then + -- we can use local resource from the search directories directly if network is too slow + os.cp(localfile, resource_file) + elseif resource_url:find(string.ipattern("https-://")) or resource_url:find(string.ipattern("ftps-://")) then + http.download(resource_url, resource_file) + else + raise("invalid resource url(%s)", resource_url) + end + + -- check hash + if resource_hash and resource_hash ~= hash.sha256(resource_file) then + raise("resource(%s): unmatched checksum!", resource_url) + end + end + + -- extract the resource file + local resourcedir = package:resourcedir(resource_name) + local resourcedir_tmp = resourcedir .. ".tmp" + os.tryrm(resourcedir_tmp) + if archive.extract(resource_file, resourcedir_tmp) then + os.tryrm(resourcedir) + os.mv(resourcedir_tmp, resourcedir) + else + os.tryrm(resourcedir_tmp) + end +end + +-- download all resources of the given package +function main(package) + + -- no resources? + local resources = package:resources() + if not resources then + return + end + + -- download all resources + for name, resourceinfo in pairs(resources) do + -- we use wrap to support urls table and only get the first url now + -- TODO maybe we will download resource from the multiple urls in the future + _download(package, name, table.wrap(resourceinfo.url)[1], resourceinfo.sha256) + end +end diff --git a/xmake/modules/private/action/require/impl/actions/install.lua b/xmake/modules/private/action/require/impl/actions/install.lua new file mode 100644 index 000000000..a9a46d49b --- /dev/null +++ b/xmake/modules/private/action/require/impl/actions/install.lua @@ -0,0 +1,273 @@ +--!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 install.lua +-- + +-- imports +import("core.base.option") +import("core.base.tty") +import("core.project.target") +import("lib.detect.find_file") +import("private.action.require.impl.actions.test") +import("private.action.require.impl.actions.patch_sources") +import("private.action.require.impl.actions.download_resources") +import("private.action.require.impl.utils.filter") + +-- patch pkgconfig if not exists +function _patch_pkgconfig(package) + + -- only binary? need not pkgconfig + if not package:is_library() then + return + end + + -- get lib/pkgconfig/*.pc file + local pkgconfigdir = path.join(package:installdir(), "lib", "pkgconfig") + local pcfile = os.isdir(pkgconfigdir) and find_file("*.pc", pkgconfigdir) or nil + if pcfile then + return + end + + -- trace + pcfile = path.join(pkgconfigdir, package:name() .. ".pc") + vprint("patching %s ..", pcfile) + + -- fetch package + local fetchinfo = package:fetchdeps() + if not fetchinfo then + return + end + + -- get libs + local libs = "" + for _, linkdir in ipairs(fetchinfo.linkdirs) do + libs = libs .. "-L" .. linkdir + end + libs = libs .. " -L${libdir}" + for _, link in ipairs(fetchinfo.links) do + libs = libs .. " -l" .. link + end + for _, link in ipairs(fetchinfo.syslinks) do + libs = libs .. " -l" .. link + end + + -- cflags + local cflags = "" + for _, includedir in ipairs(fetchinfo.includedirs) do + cflags = cflags .. "-I" .. includedir + end + cflags = cflags .. " -I${includedir}" + + -- patch a *.pc file + local file = io.open(pcfile, 'w') + if file then + file:print("prefix=%s", package:installdir()) + file:print("exec_prefix=${prefix}") + file:print("libdir=${exec_prefix}/lib") + file:print("includedir=${prefix}/include") + file:print("") + file:print("Name: %s", package:name()) + file:print("Description: %s", package:description()) + file:print("Version: %s", package:version_str()) + file:print("Libs: %s", libs) + file:print("Libs.private: ") + file:print("Cflags: %s", cflags) + file:close() + end +end + +-- install the given package +function main(package) + + -- get working directory of this package + local workdir = package:cachedir() + + -- lock this package + package:lock() + + -- enter the working directory + local oldir = nil + if #package:urls() > 0 then + -- only one root directory? skip it + local filedirs = os.filedirs(path.join(workdir, "source", "*")) + if #filedirs == 1 and os.isdir(filedirs[1]) then + oldir = os.cd(filedirs[1]) + else + oldir = os.cd(path.join(workdir, "source")) + end + end + if not oldir then + os.mkdir(workdir) + oldir = os.cd(workdir) + end + + -- init tipname + local tipname = package:name() + if package:version_str() then + tipname = tipname .. "-" .. package:version_str() + end + + -- install it + try + { + function () + + -- the package scripts + local scripts = + { + package:script("install_before") + , package:script("install") + , package:script("install_after") + } + + -- install the third-party package directly, e.g. brew::pcre2/libpcre2-8, conan::OpenSSL/1.0.2n@conan/stable + local installed_now = false + if package:is3rd() then + local script = package:script("install") + if script ~= nil then + filter.call(script, package) + end + else + + -- build and install package to the install directory + if option.get("force") or not package:manifest_load() then + + -- clean install directory first + os.tryrm(package:installdir()) + + -- enter the environments of all package dependencies + for _, dep in ipairs(package:orderdeps()) do + dep:envs_enter() + end + + -- download package resources + download_resources(package) + + -- patch source codes of package + patch_sources(package) + + -- do install + for i = 1, 3 do + local script = scripts[i] + if script ~= nil then + filter.call(script, package) + end + end + + -- leave the environments of all package dependencies + for _, dep in irpairs(package:orderdeps()) do + dep:envs_leave() + end + + -- save the package info to the manifest file + package:manifest_save() + installed_now = true + end + end + + -- enter the package environments + for _, dep in ipairs(package:orderdeps()) do + dep:envs_enter() + end + package:envs_enter() + + -- fetch package and force to flush the cache + local fetchinfo = package:fetch({force = true}) + if option.get("verbose") or option.get("diagnosis") then + print(fetchinfo) + end + assert(fetchinfo, "fetch %s failed!", tipname) + + -- this package is installed now + if installed_now then + + -- patch pkg-config files for package + _patch_pkgconfig(package) + + -- test it + test(package) + end + + -- leave the package environments + package:envs_leave() + for _, dep in irpairs(package:orderdeps()) do + dep:envs_leave() + end + + -- trace + tty.erase_line_to_start().cr() + cprint("${yellow} => ${clear}install %s %s .. ${color.success}${text.success}", package:displayname(), package:version_str() or "") + end, + + catch + { + function (errors) + + -- show or save the last errors + local errorfile = path.join(package:installdir("logs"), "install.txt") + if errors then + if (option.get("verbose") or option.get("diagnosis")) then + cprint("${dim color.error}error: ${clear}%s", errors) + else + io.writefile(errorfile, errors .. "\n") + end + end + + -- trace + tty.erase_line_to_start().cr() + cprint("${yellow} => ${clear}install %s %s .. ${color.failure}${text.failure}", package:displayname(), package:version_str() or "") + + -- leave the package environments + package:envs_leave() + + -- copy the invalid package directory to cache + local installdir = package:installdir() + if os.isdir(installdir) then + local installdir_failed = path.join(package:cachedir(), "installdir.failed") + os.tryrm(installdir_failed) + if not os.isdir(installdir_failed) then + os.cp(installdir, installdir_failed) + end + errorfile = path.join(installdir_failed, "logs", "install.txt") + end + os.tryrm(installdir) + + -- failed + if not package:requireinfo().optional then + if os.isfile(errorfile) then + print("if you want to get verbose errors, please see:") + cprint(" -> ${bright}%s", errorfile) + end + raise("install failed!") + end + end + } + } + + -- clean the empty package directory + local installdir = package:installdir() + if os.emptydir(installdir) then + os.tryrm(installdir) + end + + -- unlock this package + package:unlock() + + -- leave source codes directory + os.cd(oldir) +end diff --git a/xmake/modules/private/action/require/impl/actions/patch_sources.lua b/xmake/modules/private/action/require/impl/actions/patch_sources.lua new file mode 100644 index 000000000..110330af0 --- /dev/null +++ b/xmake/modules/private/action/require/impl/actions/patch_sources.lua @@ -0,0 +1,112 @@ +--!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 patch_sources.lua +-- + +-- imports +import("core.base.option") +import("net.http") +import("devel.git") + +-- check sha256 +function _check_sha256(patch_hash, patch_file) + local ok = (patch_hash == hash.sha256(patch_file)) + if not ok and is_host("windows") then + -- `git pull` maybe will replace lf to crlf in the patch text automatically on windows. + -- so we need attempt to fix this sha256 + -- + -- @see + -- https://github.com/xmake-io/xmake-repo/pull/67 + -- https://stackoverflow.com/questions/1967370/git-replacing-lf-with-crlf + -- + local tmpfile = os.tmpfile(patch_file) + os.cp(patch_file, tmpfile) + local content = io.readfile(tmpfile, {encoding = "binary"}) + content = content:gsub('\r\n', '\n') + io.writefile(tmpfile, content, {encoding = "binary"}) + ok = (patch_hash == hash.sha256(tmpfile)) + os.rm(tmpfile) + end + return ok +end + +-- do patch +function _patch(package, patch_url, patch_hash) + + -- trace + vprint("patching %s to %s-%s ..", patch_url, package:name(), package:version_str()) + + -- get the patch file + local patch_file = path.join(os.tmpdir(), "patches", package:name(), package:version_str(), (path.filename(patch_url):gsub("%?.+$", ""))) + + -- ensure lower hash + if patch_hash then + patch_hash = patch_hash:lower() + end + + -- the package file have been downloaded? + local cached = true + if option.get("force") or not os.isfile(patch_file) or not _check_sha256(patch_hash, patch_file) then + + -- no cached + cached = false + + -- attempt to remove the previous file first + os.tryrm(patch_file) + + -- download the patch file + if patch_url:find(string.ipattern("https-://")) or patch_url:find(string.ipattern("ftps-://")) then + http.download(patch_url, patch_file) + else + -- copy the patch file + if os.isfile(patch_url) then + os.cp(patch_url, patch_file) + else + local scriptdir = package:scriptdir() + if scriptdir and os.isfile(path.join(scriptdir, patch_url)) then + os.cp(path.join(scriptdir, patch_url), patch_file) + else + raise("patch(%s): not found!", patch_url) + end + end + end + + -- check hash + if patch_hash and not _check_sha256(patch_hash, patch_file) then + raise("patch(%s): unmatched checksum!", patch_url) + end + end + + -- apply the patch file + git.apply(patch_file) +end + +-- patch the given package +function main(package) + + -- no patches? + local patches = package:patches() + if not patches then + return + end + + -- do all patches + for _, patchinfo in ipairs(patches) do + _patch(package, patchinfo.url, patchinfo.sha256) + end +end diff --git a/xmake/modules/private/action/require/impl/actions/test.lua b/xmake/modules/private/action/require/impl/actions/test.lua new file mode 100644 index 000000000..f29910f2a --- /dev/null +++ b/xmake/modules/private/action/require/impl/actions/test.lua @@ -0,0 +1,59 @@ +--!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 test.lua +-- + +-- imports +import("core.base.option") +import("private.action.require.impl.utils.filter") + +-- test the given package +function main(package) + + -- the package scripts + local scripts = + { + package:script("test_before") + , package:script("test") + , package:script("test_after") + } + + -- enter the test directory + local testdir = path.join(os.tmpdir(), "pkgtest", package:name(), package:version_str() or "latest") + if os.isdir(testdir) then + os.tryrm(testdir) + end + if not os.isdir(testdir) then + os.mkdir(testdir) + end + local oldir = os.cd(testdir) + + -- test it + for i = 1, 3 do + local script = scripts[i] + if script ~= nil then + filter.call(script, package) + end + end + + -- restore the current directory + os.cd(oldir) + + -- remove the test directory + os.tryrm(testdir) +end diff --git a/xmake/modules/private/action/require/impl/environment.lua b/xmake/modules/private/action/require/impl/environment.lua new file mode 100644 index 000000000..e7832d6a1 --- /dev/null +++ b/xmake/modules/private/action/require/impl/environment.lua @@ -0,0 +1,73 @@ +--!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 environment.lua +-- + +-- imports +import("core.project.config") +import("core.package.package", {alias = "core_package"}) +import("lib.detect.find_tool") +import("private.action.require.impl.packagenv") +import("private.action.require.impl.package") + +-- enter environment +-- +-- ensure that we can find some basic tools: git, unzip, ... +-- +-- If these tools not exist, we will install it first. +-- +function enter() + + -- unzip or 7zip is necessary + if not find_tool("unzip") and not find_tool("7z") then + raise("unzip or 7zip not found! we need install it first") + end + + -- enter the environments of git + packagenv.enter("git") + + -- git not found? install it first + local packages = {} + if not find_tool("git") then + table.join2(packages, package.install_packages("git")) + end + + -- missing the necessary unarchivers for *.gz, *.7z? install them first, e.g. gzip, 7z, tar .. + if not ((find_tool("gzip") and find_tool("tar")) or find_tool("7z")) then + table.join2(packages, package.install_packages("7z")) + end + + -- enter the environments of installed packages + for _, instance in ipairs(packages) do + instance:envs_enter() + end + _g._PACKAGES = packages +end + +-- leave environment +function leave() + + -- leave the environments of installed packages + for _, instance in irpairs(_g._PACKAGES) do + instance:envs_leave() + end + _g._PACKAGES = nil + + -- leave the environments of git + packagenv.leave("git") +end diff --git a/xmake/modules/private/action/require/impl/package.lua b/xmake/modules/private/action/require/impl/package.lua new file mode 100644 index 000000000..a3d69a982 --- /dev/null +++ b/xmake/modules/private/action/require/impl/package.lua @@ -0,0 +1,1106 @@ +--!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 package.lua +-- + +-- imports +import("core.base.semver") +import("core.base.option") +import("core.base.global") +import("core.base.hashset") +import("core.base.scheduler") +import("core.base.tty") +import("private.async.runjobs") +import("private.utils.progress") +import("core.cache.memcache") +import("core.cache.localcache") +import("core.project.project") +import("core.package.package", {alias = "core_package"}) +import("actions.install", {alias = "action_install"}) +import("actions.download", {alias = "action_download"}) +import("devel.git") +import("net.fasturl") +import("private.action.require.impl.repository") + +-- get memcache +function _memcache() + return memcache.cache("require.impl.package") +end + +-- +-- parse require string +-- +-- basic +-- - add_requires("zlib") +-- +-- semver +-- - add_requires("tbox >=1.5.1", "zlib >=1.2.11") +-- +-- git branch/tag +-- - add_requires("zlib master") +-- +-- with the given repository +-- - add_requires("xmake-repo@tbox >=1.5.1") +-- +-- with the given configs +-- - add_requires("aaa_bbb_ccc >=1.5.1 <1.6.0", {optional = true, alias = "mypkg", debug = true}) +-- - add_requires("tbox", {config = {coroutine = true, abc = "xxx"}}) +-- +-- with namespace and the 3rd package manager +-- - add_requires("xmake::xmake-repo@tbox >=1.5.1") +-- - add_requires("vcpkg::ffmpeg") +-- - add_requires("conan::OpenSSL/1.0.2n@conan/stable") +-- - add_requires("conan::openssl/1.1.1g") -- new +-- - add_requires("brew::pcre2/libpcre2-8 10.x", {alias = "pcre2"}) +-- +-- clone as a standalone package with the different configs +-- we can install and use these three packages at the same time. +-- - add_requires("zlib") +-- - add_requires("zlib~debug", {debug = true}) +-- - add_requires("zlib~shared", {configs = {shared = true}, alias = "zlib_shared"}) +-- +-- {system = nil/true/false}: +-- nil: get local or system packages +-- true: only get system package +-- false: only get local packages +-- +-- +function _parse_require(require_str, requires_extra, parentinfo) + + -- split package and version info + local splitinfo = require_str:split('%s+') + assert(splitinfo and #splitinfo > 0, "require(\"%s\"): invalid!", require_str) + + -- get package info + local packageinfo = splitinfo[1] + + -- get version + -- + -- e.g. + -- + -- latest + -- >=1.5.1 <1.6.0 + -- master || >1.4 + -- ~1.2.3 + -- ^1.1 + -- + local version = "latest" + if #splitinfo > 1 then + version = table.concat(table.slice(splitinfo, 2), " ") + end + assert(version, "require(\"%s\"): unknown version!", require_str) + + -- require third-party packages? e.g. brew::pcre2/libpcre2-8 + local reponame = nil + local packagename = nil + if require_str:find("::", 1, true) then + packagename = packageinfo + else + + -- get repository name, package name and package url + local pos = packageinfo:lastof('@', true) + if pos then + + -- get package name + packagename = packageinfo:sub(pos + 1) + + -- get reponame + reponame = packageinfo:sub(1, pos - 1) + else + packagename = packageinfo + end + end + + -- check package name + assert(packagename, "require(\"%s\"): the package name not found!", require_str) + + -- get require extra + local require_extra = {} + if requires_extra then + require_extra = requires_extra[require_str] or {} + end + + -- get required building configurations + local require_build_configs = require_extra.configs or require_extra.config + if require_extra.debug then + require_build_configs = require_build_configs or {} + require_build_configs.debug = true + end + + -- require packge in the current host platform + if require_extra.host then + require_extra.plat = os.host() + require_extra.arch = os.arch() + end + + -- init required item + local required = {} + parentinfo = parentinfo or {} + required.packagename = packagename + required.requireinfo = + { + originstr = require_str, + reponame = reponame, + version = version, + plat = require_extra.plat, -- require package in the given platform + arch = require_extra.arch, -- require package in the given architecture + targetos = require_extra.targetos, -- require package in the given target os + kind = require_extra.kind, -- default: library, set package kind, e.g. binary, library, we can set `kind = "binary"` to only detect binary program and ignore library. + alias = require_extra.alias, -- set package alias name + group = require_extra.group, -- only uses the first package in same group + system = require_extra.system, -- default: true, we can set it to disable system package manually + option = require_extra.option, -- set and attach option + configs = require_build_configs, -- the required building configurations + default = require_extra.default, -- default: true, we can set it to disable package manually + optional = parentinfo.optional or require_extra.optional, -- default: false, inherit parentinfo.optional + verify = require_extra.verify, -- default: true, we can set false to ignore sha256sum and select any version + external = require_extra.external, -- default: true, we use sysincludedirs/-isystem instead of -I/xxx + } + return required.packagename, required.requireinfo +end + +-- load package package from system +function _load_package_from_system(packagename) + return core_package.load_from_system(packagename) +end + +-- load package package from project +function _load_package_from_project(packagename) + return core_package.load_from_project(packagename) +end + +-- load package package from repositories +function _load_package_from_repository(packagename, reponame) + local packagedir, repo = repository.packagedir(packagename, reponame) + if packagedir then + return core_package.load_from_repository(packagename, repo, packagedir) + end +end + +-- search packages from repositories +function _search_packages(name) + + local packages = {} + for _, packageinfo in ipairs(repository.searchdirs(name)) do + local package = core_package.load_from_repository(packageinfo.name, packageinfo.repo, packageinfo.packagedir) + if package then + table.insert(packages, package) + end + end + return packages +end + +-- sort package deps +-- +-- e.g. +-- +-- a.deps = b +-- b.deps = c +-- +-- orderdeps: c -> b -> a +-- +function _sort_packagedeps(package) + local orderdeps = {} + for _, dep in pairs(package:deps()) do + table.join2(orderdeps, _sort_packagedeps(dep)) + table.insert(orderdeps, dep) + end + return orderdeps +end + +-- add some builtin configurations to package +function _add_package_configurations(package) + local toolchains + if package:is_plat("cross") and package:is_library() then + -- we only can set toolchains to library package + toolchains = project.get("target.toolchains") or get_config("toolchain") + end + local vs_runtime = project.get("target.runtimes") or get_config("vs_runtime") or "MT" + package:add("configs", "debug", {builtin = true, description = "Enable debug symbols.", default = false, type = "boolean"}) + package:add("configs", "shared", {builtin = true, description = "Enable shared library.", default = false, type = "boolean"}) + package:add("configs", "cflags", {builtin = true, description = "Set the C compiler flags."}) + package:add("configs", "cxflags", {builtin = true, description = "Set the C/C++ compiler flags."}) + package:add("configs", "cxxflags", {builtin = true, description = "Set the C++ compiler flags."}) + package:add("configs", "asflags", {builtin = true, description = "Set the assembler flags."}) + package:add("configs", "pic", {builtin = true, description = "Enable the position independent code.", default = true, type = "boolean"}) + package:add("configs", "vs_runtime", {builtin = true, description = "Set vs compiler runtime.", default = vs_runtime, values = {"MT", "MTd", "MD", "MDd"}}) + package:add("configs", "toolchains", {builtin = true, description = "Set package toolchains only for cross-compilation.", default = toolchains}) +end + +-- select package version +function _select_package_version(package, requireinfo) + + -- exists urls? otherwise be phony package (only as package group) + if #package:urls() > 0 then + + -- has git url? + local has_giturl = false + for _, url in ipairs(package:urls()) do + if git.checkurl(url) then + has_giturl = true + break + end + end + + -- select package version + local source = nil + local version = nil + local require_version = requireinfo.version + local require_verify = requireinfo.verify + if (not package:get("versions") or require_verify == false) and semver.is_valid(require_version) then + -- no version list in package() or need not verify sha256sum? try selecting this version directly + -- @see https://github.com/xmake-io/xmake/issues/930 + -- https://github.com/xmake-io/xmake/issues/1009 + version = require_version + source = "versions" + elseif #package:versions() > 0 and (require_version == "latest" or require_version:find('.', 1, true)) then -- select version? + version, source = semver.select(require_version, package:versions()) + elseif has_giturl then -- select branch? + version, source = require_version ~= "latest" and require_version or "master", "branches" + else + raise("package(%s %s): not found!", package:displayname(), require_version) + end + return version, source + end +end + +-- check the configurations of packages +-- +-- package("pcre2") +-- add_configs("bitwidth", {description = "Set the code unit width.", default = "8", values = {"8", "16", "32"}}) +-- add_configs("bitwidth", {type = "number", values = {8, 16, 32}}) +-- add_configs("bitwidth", {restrict = function(value) if tonumber(value) < 100 then return true end}) +-- +function _check_package_configurations(package) + local configs_defined = {} + for _, name in ipairs(package:get("configs")) do + configs_defined[name] = package:extraconf("configs", name) or {} + end + for name, value in pairs(package:configs()) do + local conf = configs_defined[name] + if conf then + local config_type = conf.type + if config_type ~= nil and type(value) ~= config_type then + raise("package(%s %s): invalid type(%s) for config(%s), need type(%s)!", package:displayname(), package:version_str(), type(value), name, config_type) + end + if conf.values then + local found = false + for _, config_value in ipairs(conf.values) do + if tostring(value) == tostring(config_value) then + found = true + break + end + end + if not found then + raise("package(%s %s): invalid value(%s) for config(%s), please run `xmake require --info %s` to get all valid values!", package:displayname(), package:version_str(), value, name, package:name()) + end + end + if conf.restrict then + if not conf.restrict(value) then + raise("package(%s %s): invalid value(%s) for config(%s)!", package:displayname(), package:version_str(), value, name) + end + end + else + raise("package(%s %s): invalid config(%s), please run `xmake require --info %s` to get all configurations!", package:displayname(), package:version_str(), name, package:name()) + end + end +end + +-- match require path +function _match_requirepath(requirepath, requireconf) + + -- get pattern + local function _get_pattern(pattern) + pattern = pattern:gsub("([%+%.%-%^%$%(%)%%])", "%%%1") + pattern = pattern:gsub("%*%*", "\001") + pattern = pattern:gsub("%*", "\002") + pattern = pattern:gsub("\001", ".*") + pattern = pattern:gsub("\002", "[^.]*") + pattern = string.ipattern(pattern, true) + return pattern + end + + -- get the excludes + local excludes = requireconf:match("|.*$") + if excludes then excludes = excludes:split("|", {plain = true}) end + + -- do match + local pattern = requireconf:gsub("|.*$", "") + pattern = _get_pattern(pattern) + if (requirepath:match('^' .. pattern .. '$')) then + -- exclude sub-deps, e.g. "libwebp.**|cmake|autoconf" + local splitinfo = requirepath:split(".", {plain = true}) + if #splitinfo > 0 then + local name = splitinfo[#splitinfo] + for _, exclude in ipairs(excludes) do + pattern = _get_pattern(exclude) + if (name:match('^' .. pattern .. '$')) then + return false + end + end + end + return true + end +end + +-- merge requireinfo from `add_requireconfs()` +-- +-- add_requireconfs("*", {system = false, configs = {vs_runtime = "MD"}}) +-- add_requireconfs("lib*", {system = false, configs = {vs_runtime = "MD"}}) +-- add_requireconfs("libwebp", {system = false, configs = {vs_runtime = "MD"}}) +-- add_requireconfs("libpng.zlib", {system = false, override = true, configs = {cxflags = "-DTEST1"}, version = "1.2.10"}) +-- add_requireconfs("libtiff.*", {system = false, configs = {cxflags = "-DTEST2"}}) +-- add_requireconfs("libwebp.**|cmake|autoconf", {system = false, configs = {cxflags = "-DTEST3"}}) -- recursive deps +-- +function _merge_requireinfo(requireinfo, requirepath) + + -- only for project + if not os.isfile(os.projectfile()) then + return + end + + -- find requireconf from the given requirepath + local requireconf_result = {} + local requireconfs, requireconfs_extra = project.requireconfs_str() + if requireconfs then + for _, requireconf in ipairs(requireconfs) do + if _match_requirepath(requirepath, requireconf) then + local requireconf_extra = requireconfs_extra[requireconf] + table.insert(requireconf_result, {requireconf = requireconf, requireconf_extra = requireconf_extra}) + end + end + end + + -- append requireconf_extra into requireinfo + -- and the configs of add_requires have a higher priority than add_requireconfs. + -- + -- e.g. + -- add_requireconfs("*", {configs = {debug = false}}) + -- add_requires("foo", "bar", {configs = {debug = true}}) + -- + -- foo and bar will be debug mode + -- + -- we can also override the configs of add_requires + -- + -- e.g. + -- add_requires("zlib 1.2.11") + -- add_requireconfs("zlib", {override = true, version = "1.2.10"}) + -- + -- we override the version of zlib to 1.2.10 + -- + if #requireconf_result == 1 then + local requireconf_extra = requireconf_result[1].requireconf_extra + if requireconf_extra then + -- preprocess requireconf_extra, (debug, override ..) + local override = requireconf_extra.override + requireconf_extra.override = nil + if requireconf_extra.debug then + requireconf_extra.configs = requireconf_extra.configs or {} + requireconf_extra.configs.debug = true + requireconf_extra.debug = nil + end + -- append or override configs and extra options + for k, v in pairs(requireconf_extra.configs) do + requireinfo.configs = requireinfo.configs or {} + if override or requireinfo.configs[k] == nil then + requireinfo.configs[k] = v + end + end + for k, v in pairs(requireconf_extra) do + if k ~= "configs" then + if override or requireinfo[k] == nil then + requireinfo[k] = v + end + end + end + end + elseif #requireconf_result > 1 then + local confs = {} + for _, item in ipairs(requireconf_result) do + table.insert(confs, item.requireconf) + end + raise("package(%s) will match multiple add_requireconfs(%s)!", requirepath, table.concat(confs, " ")) + end +end + +-- get package key +function _get_packagekey(packagename, requireinfo, version) + local key = packagename .. "/" .. (version or requireinfo.version) + local configs = requireinfo.configs + if configs then + local configs_order = {} + for k, v in pairs(configs) do + table.insert(configs_order, k .. "=" .. tostring(v)) + end + table.sort(configs_order) + key = key .. ":" .. string.serialize(configs_order, true) + end + return key +end + +-- inherit some builtin configs of parent package if these config values are not default value +-- e.g. add_requires("libpng", {configs = {vs_runtime = "MD", pic = false}}) +-- +function _inherit_parent_configs(requireinfo, parentinfo) + local requireinfo_configs = requireinfo.configs or {} + local parentinfo_configs = parentinfo.configs or {} + if not requireinfo_configs.shared then + if requireinfo_configs.vs_runtime == nil then + requireinfo_configs.vs_runtime = parentinfo_configs.vs_runtime + end + if requireinfo_configs.pic == nil then + requireinfo_configs.pic = parentinfo_configs.pic + end + end + requireinfo.configs = requireinfo_configs +end + +-- load required packages +function _load_package(packagename, requireinfo, opt) + + -- strip trailng ~tag, e.g. zlib~debug + local displayname + if packagename:find('~', 1, true) then + displayname = packagename + packagename = packagename:gsub("~.+$", "") + requireinfo.alias = requireinfo.alias or displayname + end + + -- load package from project first + local package + if os.isfile(os.projectfile()) then + package = _load_package_from_project(packagename) + end + + -- load package from repositories + if not package then + package = _load_package_from_repository(packagename, requireinfo.reponame) + end + + -- load package from system + if not package and opt.system ~= false then + package = _load_package_from_system(packagename) + end + + -- check + assert(package, "package(%s) not found!", packagename) + + -- merge requireinfo from `add_requireconfs()` + _merge_requireinfo(requireinfo, opt.requirepath) + + -- inherit some builtin configs of parent package, e.g. vs_runtime, pic + if opt.parentinfo and package:is_library() then + _inherit_parent_configs(requireinfo, opt.parentinfo) + end + + -- select package version + local version, source = _select_package_version(package, requireinfo) + if version then + package:version_set(version, source) + end + + -- get package key + local packagekey = _get_packagekey(packagename, requireinfo, version) + + -- It exists conflict for dependent packages for each root packages? resolve it first + -- e.g. + -- add_requires("foo") -> bar -> zlib 1.2.10 + -- -> xyz -> zlib 1.2.11 or other configs + -- + -- add_requires("ddd") -> zlib + -- + -- We assume that there is no conflict between `foo` and `ddd`. + -- + -- Of course, conflicts caused by `add_packages("foo", "ddd")` + -- cannot be detected at present and can only be resolved by the user + -- + local rootkey = opt.rootkey + local packagekey_prev = _memcache():get3("packages_root", rootkey, packagename) + if packagekey_prev then + if packagekey_prev and packagekey_prev ~= packagekey then + raise("package(%s): conflict dependences with package(%s)!", packagekey, packagekey_prev) + end + end + _memcache():set3("packages_root", rootkey, packagename, packagekey) + + -- get package from cache first + local package_cached = _memcache():get2("packages", packagekey) + if package_cached then + return package_cached + end + + -- save require info + package:requireinfo_set(requireinfo) + + -- save display name + if not displayname then + local packageid = _memcache():get2("packageids", packagename) + displayname = packagename + if packageid then + displayname = displayname .. "#" .. tostring(packageid) + end + _memcache():set2("packageids", packagename, (packageid or 0) + 1) + end + package:displayname_set(displayname) + + -- disable parallelize if the package cache directory conflicts + local cachedirs = _memcache():get2("cachedirs", package:cachedir()) + if cachedirs then + package:set("parallelize", false) + end + _memcache():set2("cachedirs", package:cachedir(), true) + + -- disable parallelize if this package is toolchain? we need install toolchain package first + if package:is_toolchain() then + package:set("parallelize", false) + end + + -- add some builtin configurations to package + _add_package_configurations(package) + + -- check package configurations + _check_package_configurations(package) + + -- do load + local on_load = package:script("load") + if on_load then + on_load(package) + end + + -- load environments from the manifest to enable the environments of on_install() + package:envs_load() + + -- save this package package to cache + _memcache():set2("packages", packagekey, package) + return package +end + +-- load all required packages +function _load_packages(requires, opt) + + -- no requires? + if not requires or #requires == 0 then + return {} + end + + -- load packages + local packages = {} + for _, requireitem in ipairs(load_requires(requires, opt.requires_extra, opt)) do + + -- load package + local rootkey = opt.rootkey or requireitem.name + local requireinfo = requireitem.info + local requirepath = opt.requirepath and (opt.requirepath .. "." .. requireitem.name) or requireitem.name + local package = _load_package(requireitem.name, requireinfo, table.join(opt, {rootkey = rootkey, requirepath = requirepath})) + + -- maybe package not found and optional + if package then + + -- load dependent packages and save them first of this package + if not package._DEPS then + local deps = package:get("deps") + if deps and opt.nodeps ~= true then + + -- load dependent packages and do not load system/3rd packages for package/deps() + local packagedeps = {} + for _, dep in ipairs(_load_packages(deps, {rootkey = rootkey, + requirepath = requirepath, + requires_extra = package:extraconf("deps") or {}, + parentinfo = requireinfo, + nodeps = opt.nodeps, + system = false})) do + dep:parents_add(package) + table.insert(packages, dep) + packagedeps[dep:name()] = dep + end + package._DEPS = packagedeps + package._ORDERDEPS = table.unique(_sort_packagedeps(package)) + end + end + + -- save this package + -- @note if this root package is toolchain, we need to move it to the beginning in order to install first + if not opt.parentinfo and package:is_toolchain() then + table.insert(packages, 1, package) + else + table.insert(packages, package) + end + end + end + return packages +end + +-- sort packages urls +function _sort_packages_urls(packages) + + -- add all urls to fasturl and prepare to sort them together + for _, package in pairs(packages) do + fasturl.add(package:urls()) + end + + -- sort and update urls + for _, package in pairs(packages) do + package:urls_set(fasturl.sort(package:urls())) + end +end + +-- get package parents string +function _get_package_parents_str(package) + local parents = package:parents() + if parents then + local parentnames = {} + for _, parent in pairs(parents) do + table.insert(parentnames, parent:displayname()) + end + if #parentnames == 0 then + return + end + return table.concat(parentnames, ",") + end +end + +-- get package configs string +function _get_package_configs_str(package) + local configs = {} + if package:optional() then + table.insert(configs, "optional") + end + local requireinfo = package:requireinfo() + if requireinfo then + for k, v in pairs(requireinfo.configs) do + if type(v) == "boolean" then + table.insert(configs, k .. ":" .. (v and "y" or "n")) + else + table.insert(configs, k .. ":" .. v) + end + end + end + local parents_str = _get_package_parents_str(package) + if parents_str then + table.insert(configs, "from:" .. parents_str) + end + local configs_str = #configs > 0 and "[" .. table.concat(configs, ", ") .. "]" or "" + local limitwidth = os.getwinsize().width * 2 / 3 + if #configs_str > limitwidth then + configs_str = configs_str:sub(1, limitwidth) .. " ..)" + end + return configs_str +end + +-- get user confirm +function _get_confirm(packages) + + -- no confirmed packages? + if #packages == 0 then + return true + end + + -- get confirm + local confirm = utils.confirm({default = true, description = function () + + -- get packages for each repositories + local packages_repo = {} + local packages_group = {} + for _, package in ipairs(packages) do + -- achive packages by repository + local reponame = package:repo() and package:repo():name() or (package:isSys() and "system" or "") + if package:is3rd() then + reponame = package:name():lower():split("::")[1] + end + packages_repo[reponame] = packages_repo[reponame] or {} + table.insert(packages_repo[reponame], package) + + -- achive packages by group + local group = package:group() + if group then + packages_group[group] = packages_group[group] or {} + table.insert(packages_group[group], package) + end + end + + -- show tips + cprint("${bright color.warning}note: ${clear}try installing these packages (pass -y to skip confirm)?") + for reponame, packages in pairs(packages_repo) do + if reponame ~= "" then + print("in %s:", reponame) + end + local packages_showed = {} + for _, package in ipairs(packages) do + if not packages_showed[tostring(package)] then + local group = package:group() + if group and packages_group[group] and #packages_group[group] > 1 then + for idx, package_in_group in ipairs(packages_group[group]) do + cprint(" ${yellow}%s${clear} %s %s ${dim}%s", idx == 1 and "->" or " or", package_in_group:displayname(), package_in_group:version_str() or "", _get_package_configs_str(package_in_group)) + packages_showed[tostring(package_in_group)] = true + end + packages_group[group] = nil + else + cprint(" ${yellow}->${clear} %s %s ${dim}%s", package:displayname(), package:version_str() or "", _get_package_configs_str(package)) + packages_showed[tostring(package)] = true + end + end + end + end + end}) + return confirm +end + +-- install packages +function _install_packages(packages_install, packages_download) + + -- we need hide wait characters if is not a tty + local show_wait = io.isatty() + + -- do install + local progress_helper = show_wait and progress.new() or nil + local packages_installing = {} + local packages_downloading = {} + local packages_pending = table.copy(packages_install) + local packages_in_group = {} + local installing_count = 0 + local parallelize = true + runjobs("install_packages", function (index) + + -- fetch a new package + local package = nil + while package == nil and #packages_pending > 0 do + for idx, pkg in ipairs(packages_pending) do + + -- all dependences has been installed? we install it now + local ready = true + local dep_not_found = nil + for _, dep in ipairs(pkg:orderdeps()) do + if not dep:exists() then + ready = false + dep_not_found = dep + break + end + end + local group = pkg:group() + if ready and group then + -- this group has been installed? skip it + local group_status = packages_in_group[group] + if group_status == 1 then + table.remove(packages_pending, idx) + break + -- this group is installing? wait it + elseif group_status == 0 then + ready = false + end + end + + -- get a package with the ready status + if ready then + package = pkg + table.remove(packages_pending, idx) + break + elseif installing_count == 0 then + if #packages_pending == 1 and dep_not_found then + raise("package(%s): cannot be installed, there are dependencies(%s) that cannot be installed!", pkg:displayname(), dep_not_found:displayname()) + elseif #packages_pending == 1 then + raise("package(%s): cannot be installed!", pkg:displayname()) + end + end + end + if package == nil and #packages_pending > 0 then + scheduler.co_yield() + end + end + if package then + + -- only install the first package in same group + local group = package:group() + if not group or not packages_in_group[group] then + + -- disable parallelize? + if not package:parallelize() then + parallelize = false + end + if not parallelize then + while installing_count > 0 do + scheduler.co_yield() + end + end + installing_count = installing_count + 1 + + -- mark this group as 'installing' + if group then + packages_in_group[group] = 0 + end + + -- download this package first + local downloaded = true + if packages_download[tostring(package)] then + packages_downloading[index] = package + downloaded = action_download(package) + packages_downloading[index] = nil + end + + -- install this package + packages_installing[index] = package + if downloaded then + action_install(package) + end + packages_installing[index] = nil + + -- mark this group as 'installed' or 'failed' + if group then + packages_in_group[group] = package:exists() and 1 or -1 + end + + -- enable parallelize + parallelize = true + installing_count = installing_count - 1 + end + end + packages_installing[index] = nil + packages_downloading[index] = nil + + end, {total = #packages_install, comax = (option.get("verbose") or option.get("diagnosis")) and 1 or 4, on_timer = function (running_jobs_indices) + + -- do not print progress info if be verbose + if option.get("verbose") or not show_wait then + return + end + + -- make installing and downloading packages list + local installing = {} + local downloading = {} + for _, index in ipairs(running_jobs_indices) do + local package = packages_installing[index] + if package then + table.insert(installing, package:displayname()) + end + local package = packages_downloading[index] + if package then + table.insert(downloading, package:displayname()) + end + end + + -- get waitobjs tips + local tips = nil + local waitobjs = scheduler.co_group_waitobjs("install_packages") + if waitobjs:size() > 0 then + local names = {} + for _, obj in waitobjs:keys() do + if obj:otype() == scheduler.OT_PROC then + table.insert(names, obj:name()) + elseif obj:otype() == scheduler.OT_SOCK then + table.insert(names, "sock") + elseif obj:otype() == scheduler.OT_PIPE then + table.insert(names, "pipe") + end + end + names = table.unique(names) + if #names > 0 then + names = table.concat(names, ",") + if #names > 16 then + names = names:sub(1, 16) .. ".." + end + tips = string.format("(%d/%s)", waitobjs:size(), names) + end + end + + -- trace + progress_helper:clear() + tty.erase_line_to_start().cr() + cprintf("${yellow} => ") + if #downloading > 0 then + cprintf("downloading ${magenta}%s", table.concat(downloading, ", ")) + end + if #installing > 0 then + cprintf("%sinstalling ${magenta}%s", #downloading > 0 and ", " or "", table.concat(installing, ", ")) + end + cprintf(" .. %s", tips and ("${dim}" .. tips .. "${clear} ") or "") + progress_helper:write() + end, exit = function(errors) + if errors then + tty.erase_line_to_start().cr() + io.flush() + end + end}) +end + +-- the cache directory +function cachedir() + return path.join(global.directory(), "cache", "packages") +end + +-- load requires +function load_requires(requires, requires_extra, opt) + opt = opt or {} + local requireitems = {} + for _, require_str in ipairs(requires) do + local packagename, requireinfo = _parse_require(require_str, requires_extra, opt.parentinfo) + table.insert(requireitems, {name = packagename, info = requireinfo}) + end + return requireitems +end + +-- load all required packages +function load_packages(requires, opt) + opt = opt or {} + local unique = {} + local packages = {} + for _, package in ipairs(_load_packages(requires, opt)) do + local key = _get_packagekey(package:name(), package:requireinfo()) + if not unique[key] then + table.insert(packages, package) + unique[key] = true + end + end + return packages +end + +-- install packages +function install_packages(requires, opt) + + -- init options + opt = opt or {} + + -- load packages + local packages = load_packages(requires, opt) + + -- fetch packages (with system) from local first + runjobs("fetch_packages", function (index) + local package = packages[index] + if package and (not option.get("force") or (option.get("shallow") and package:parents())) then + package:envs_enter() + package:fetch() + package:envs_leave() + end + end, {total = #packages}) + + -- filter packages + local packages_install = {} + local packages_download = {} + local packages_unsupported = {} + for _, package in ipairs(packages) do + if not package:exists() then + if package:supported() then + if #package:urls() > 0 then + packages_download[tostring(package)] = package + end + table.insert(packages_install, package) + elseif not package:optional() then + table.insert(packages_unsupported, package) + end + end + end + + -- exists unsupported packages? + if #packages_unsupported > 0 then + -- show tips + cprint("${bright color.warning}note: ${clear}the following packages are unsupported for $(plat)/$(arch)!") + for _, package in ipairs(packages_unsupported) do + print(" -> %s %s", package:displayname(), package:version_str() or "") + end + raise() + end + + -- get user confirm + if not _get_confirm(packages_install) then + local packages_must = {} + for _, package in ipairs(packages_install) do + if not package:optional() then + table.insert(packages_must, package:displayname()) + end + end + if #packages_must > 0 then + raise("packages(%s): must be installed!", table.concat(packages_must, ", ")) + else + -- continue other actions + return + end + end + + -- sort package urls + _sort_packages_urls(packages_download) + + -- install all required packages from repositories + _install_packages(packages_install, packages_download) + + -- ok + return packages +end + +-- uninstall packages +function uninstall_packages(requires, opt) + + -- init options + opt = opt or {} + + -- do not remove dependent packages + opt.nodeps = true + + -- clear the local cache + localcache.clear() + + -- remove all packages + local packages = {} + for _, instance in ipairs(load_packages(requires, opt)) do + if os.isfile(instance:manifest_file()) then + table.insert(packages, instance) + end + os.tryrm(instance:installdir()) + end + return packages +end + +-- export packages +function export_packages(requires, opt) + + -- init options + opt = opt or {} + + -- get the export directory + local exportdir = assert(opt.exportdir) + + -- export all packages + local packages = {} + for _, instance in ipairs(load_packages(requires, opt)) do + + -- get the exported name + local name = instance:name():lower():gsub("::", "_") + if instance:version_str() then + name = name .. "_" .. instance:version_str() + end + name = name .. "_" .. instance:buildhash() + + -- export this package + if instance:fetch() then + os.cp(instance:installdir(), path.join(exportdir, name)) + table.insert(packages, instance) + end + end + return packages +end + +-- search packages +function search_packages(names) + + -- search all names + local results = {} + for _, name in ipairs(names) do + local packages = _search_packages(name) + if packages then + results[name] = packages + end + end + return results +end diff --git a/xmake/modules/private/action/require/impl/packagenv.lua b/xmake/modules/private/action/require/impl/packagenv.lua new file mode 100644 index 000000000..232670b54 --- /dev/null +++ b/xmake/modules/private/action/require/impl/packagenv.lua @@ -0,0 +1,81 @@ +--!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 packagenv.lua +-- + +-- imports +import("core.package.package", {alias = "core_package"}) + +-- enter the package environments +function _enter_package(package_name, envs, installdir) + + -- save the old environments + _g._OLDENVS = _g._OLDENVS or {} + local oldenvs = _g._OLDENVS[package_name] + if not oldenvs then + oldenvs = {} + _g._OLDENVS[package_name] = oldenvs + end + + -- add the new environments + for name, values in pairs(envs) do + oldenvs[name] = oldenvs[name] or os.getenv(name) + if name == "PATH" or name == "LD_LIBRARY_PATH" or name == "DYLD_LIBRARY_PATH" then + for _, value in ipairs(values) do + if path.is_absolute(value) then + os.addenv(name, value) + else + os.addenv(name, path.join(installdir, value)) + end + end + else + os.addenv(name, unpack(table.wrap(values))) + end + end +end + +-- leave the package environments +function _leave_package(package_name) + _g._OLDENVS = _g._OLDENVS or {} + local oldenvs = _g._OLDENVS[package_name] + if oldenvs then + for name, values in pairs(oldenvs) do + os.setenv(name, values) + end + _g._OLDENVS[package_name] = nil + end +end + +-- enter environment of the given binary packages, git, 7z, .. +function enter(...) + for _, name in ipairs({...}) do + for _, manifest_file in ipairs(os.files(path.join(core_package.installdir(), name:sub(1, 1), name, "*", "*", "manifest.txt"))) do + local manifest = io.load(manifest_file) + if manifest and manifest.plat == os.host() and manifest.arch == os.arch() then + _enter_package(name, manifest.envs, path.directory(manifest_file)) + end + end + end +end + +-- leave environment of the given binary packages, git, 7z, .. +function leave(...) + for _, name in ipairs({...}) do + _leave_package(name) + end +end diff --git a/xmake/modules/private/action/require/impl/repository.lua b/xmake/modules/private/action/require/impl/repository.lua new file mode 100644 index 000000000..6fc72d49c --- /dev/null +++ b/xmake/modules/private/action/require/impl/repository.lua @@ -0,0 +1,113 @@ +--!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 repository.lua +-- + +-- imports +import("core.package.repository") + +-- get all repositories +function repositories() + + -- get it from cache it + if _g._REPOSITORIES then + return _g._REPOSITORIES + end + + -- get all repositories (local first) + local repos = table.join(repository.repositories(false), repository.repositories(true)) + + -- save repositories + _g._REPOSITORIES = repos + + -- ok + return repos +end + +-- the remote repositories have been pulled? +function pulled() + for _, repo in ipairs(repositories()) do + -- repository not found? or xmake has been re-installed + local updatefile = path.join(repo:directory(), "updated") + if not os.isdir(repo:directory()) or (os.isfile(updatefile) and os.mtime(os.programfile()) > os.mtime(updatefile)) then + return false + end + end + return true +end + +-- get package directory from repositories +function packagedir(packagename, reponame) + + -- strip trailng ~tag, e.g. zlib~debug + if packagename:find('~', 1, true) then + packagename = packagename:gsub("~.+$", "") + end + + -- get it from cache it + local packagedirs = _g._PACKAGEDIRS or {} + local foundir = packagedirs[packagename] + if foundir then + return foundir[1], foundir[2] + end + + -- find the package directory from repositories + for _, repo in ipairs(repositories()) do + local dir = path.join(repo:directory(), "packages", packagename:sub(1, 1):lower(), packagename) + if os.isdir(dir) and (not reponame or reponame == repo:name()) then + foundir = {dir, repo} + break + end + end + + -- found? + if foundir then + + -- save package directory + packagedirs[packagename] = foundir + + -- update cache + _g._PACKAGEDIRS = packagedirs + + -- ok + return foundir[1], foundir[2] + end +end + +-- search package directories from repositories +function searchdirs(name) + + -- find the package directories from all repositories + local unique = {} + local packageinfos = {} + for _, repo in ipairs(repositories()) do + for _, file in ipairs(os.files(path.join(repo:directory(), "packages", "*", string.ipattern("*" .. name .. "*"), "xmake.lua"))) do + local dir = path.directory(file) + local subdirname = path.basename(path.directory(dir)) + if #subdirname == 1 then -- ignore l/luajit/port/xmake.lua + local packagename = path.basename(dir) + if not unique[packagename] then + table.insert(packageinfos, {name = packagename, repo = repo, packagedir = path.directory(file)}) + unique[packagename] = true + end + end + end + end + return packageinfos +end + diff --git a/xmake/modules/private/action/require/impl/utils/filter.lua b/xmake/modules/private/action/require/impl/utils/filter.lua new file mode 100644 index 000000000..11bdf70de --- /dev/null +++ b/xmake/modules/private/action/require/impl/utils/filter.lua @@ -0,0 +1,146 @@ +--!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 filter.lua +-- + +-- imports +import("core.base.filter") +import("core.base.option") +import("core.base.global") +import("core.project.config") +import("core.project.project") +import("core.sandbox.sandbox") + +-- get filter +function _filter() + + -- init filter + if _g.filter == nil then + _g.filter = filter.new() + _g.filter:register("common", function (variable) + + -- attempt to get it directly from the configure + local result = config.get(variable) + if result == nil then + + -- init maps + _g.common_maps = _g.common_maps or + { + host = os.host() + , subhost = os.subhost() + , tmpdir = function () return os.tmpdir() end + , curdir = function () return os.curdir() end + , scriptdir = function () return os.scriptdir() end + , globaldir = global.directory() + , configdir = config.directory() + , projectdir = project.directory() + , programdir = os.programdir() + } + + -- map it + result = _g.common_maps[variable] + end + + -- is script? call it + if type(result) == "function" then + result = result() + end + + -- ok? + return result + end) + end + + -- ok + return _g.filter +end + +-- the package handler +function _handler(package, strval) + + -- @note cannot cache it, because the package instance will be changed + return function (variable) + + -- init maps + local maps = + { + version = function () + if strval then + -- set_urls("https://sqlite.org/2018/sqlite-autoconf-$(version)000.tar.gz", + -- {version = function (version) return version:gsub("%.", "") end}) + local version_filter = package:url_version(strval) + if version_filter then + local v = version_filter(package:version()) + if v ~= nil then + -- may be semver version object + v = tostring(v) + end + return v + end + end + return package:version_str() + end + } + + -- get value + local result = maps[variable] + if type(result) == "function" then + result = result() + end + + -- ok? + return result + end +end + +-- attach filter to the given script and call it +function call(script, package) + + -- get sandbox filter and handlers of the given script + local sandbox_filter = sandbox.filter(script) + local sandbox_handlers = sandbox_filter:handlers() + + -- switch to the handlers of the current filter + sandbox_filter:set_handlers(_filter():handlers()) + + -- register package handler + sandbox_filter:register("package", _handler(package)) + + -- call it + script(package) + + -- restore handlers + sandbox_filter:set_handlers(sandbox_handlers) +end + +-- handle the string value of package +function handle(strval, package) + + -- register filter handler + _filter():register("package", _handler(package, strval)) + + -- handle string value + strval = _filter():handle(strval) + + -- register filter handler + _filter():register("package", nil) + + -- ok + return strval +end + diff --git a/xmake/modules/private/action/require/impl/utils/get_requires.lua b/xmake/modules/private/action/require/impl/utils/get_requires.lua new file mode 100644 index 000000000..9a0ad76c1 --- /dev/null +++ b/xmake/modules/private/action/require/impl/utils/get_requires.lua @@ -0,0 +1,57 @@ +--!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 get_requires.lua +-- + +-- imports +import("core.base.option") +import("core.project.project") + +-- get requires and extra config +function main(requires) + + -- init requires + local requires_extra = nil + if not requires then + requires, requires_extra = project.requires_str() + end + if not requires or #requires == 0 then + return + end + + -- get extra info + local extra = option.get("extra") + local extrainfo = nil + if extra then + local v, err = string.deserialize(extra) + if err then + raise(err) + else + extrainfo = v + end + end + + -- force to use the given requires extra info + if extrainfo then + requires_extra = requires_extra or {} + for _, require_str in ipairs(requires) do + requires_extra[require_str] = extrainfo + end + end + return requires, requires_extra +end diff --git a/xmake/modules/private/action/require/impl/utils/url_filename.lua b/xmake/modules/private/action/require/impl/utils/url_filename.lua new file mode 100644 index 000000000..e17e8673b --- /dev/null +++ b/xmake/modules/private/action/require/impl/utils/url_filename.lua @@ -0,0 +1,25 @@ +--!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 url_filename.lua +-- + +-- get filename from url +function main(url) + local urlpath = url:split('?', {plain = true})[1] + return path.filename(urlpath) +end diff --git a/xmake/modules/private/action/require/info.lua b/xmake/modules/private/action/require/info.lua new file mode 100644 index 000000000..37d4eb6a3 --- /dev/null +++ b/xmake/modules/private/action/require/info.lua @@ -0,0 +1,261 @@ +--!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 info.lua +-- + +-- imports +import("core.base.task") +import("core.base.option") +import("core.base.hashset") +import("core.project.project") +import("core.package.package", {alias = "core_package"}) +import("devel.git") +import("utils.archive") +import("private.action.require.impl.utils.filter") +import("private.action.require.impl.utils.url_filename") +import("private.action.require.impl.package") +import("private.action.require.impl.repository") +import("private.action.require.impl.environment") +import("private.action.require.impl.utils.get_requires") + +-- from xmake/system/remote? +function _from(instance) + local fetchinfo = instance:fetch() + if fetchinfo then + if instance:is3rd() then + return ", ${green}3rd${clear}" + elseif instance:isSys() then + return ", ${green}system${clear}" + else + return "" + end + elseif #instance:urls() > 0 then + local repo = instance:repo() + local reponame = repo and repo:name() or "unknown" + return instance:supported() and format(", ${yellow}remote${clear}(in %s)", reponame) or format(", ${yellow}remote${clear}(${red}unsupported${clear} in %s)", reponame) + elseif instance:isSys() then + return ", ${red}missing${clear}" + else + return "" + end +end + +-- get package info +function _info(instance) + local info = instance:version_str() and instance:version_str() or "no version" + info = info .. _from(instance) + info = info .. (instance:optional() and ", ${yellow}optional${clear}" or "") + return info +end + +-- show the given package info +function main(requires_raw) + + -- get requires and extra config + local requires_extra = nil + local requires, requires_extra = get_requires(requires_raw) + if not requires or #requires == 0 then + return + end + + -- enter environment + environment.enter() + + -- pull all repositories first if not exists + if not repository.pulled() then + task.run("repo", {update = true}) + end + + -- show title + print("The package info of project:") + + -- list all packages + for _, instance in ipairs(package.load_packages(requires, {requires_extra = requires_extra})) do + + -- show package name + local requireinfo = instance:requireinfo() or {} + cprint(" ${magenta}require${clear}(%s): ", requireinfo.originstr) + + -- show description + local description = instance:get("description") + if description then + cprint(" -> ${magenta}description${clear}: %s", description) + end + + -- show version + local version = instance:version_str() + if version then + cprint(" -> ${magenta}version${clear}: %s", version) + end + + -- show license + local license = instance:get("license") + if license then + cprint(" -> ${magenta}license${clear}: %s", license) + end + + -- show urls + local urls = instance:urls() + if urls and #urls > 0 then + cprint(" -> ${magenta}urls${clear}:") + for _, url in ipairs(urls) do + print(" -> %s", filter.handle(url, instance)) + if git.asgiturl(url) then + local url_alias = instance:url_alias(url) + cprint(" -> ${yellow}%s", instance:revision(url_alias) or instance:tag() or instance:version_str()) + else + local sourcehash = instance:sourcehash(instance:url_alias(url)) + if sourcehash then + cprint(" -> ${yellow}%s", sourcehash) + end + end + end + end + + -- show repository + local repo = instance:repo() + if repo then + cprint(" -> ${magenta}repo${clear}: %s %s %s", repo:name(), repo:url(), repo:branch() or "") + end + + -- show deps + local deps = instance:orderdeps() + if deps and #deps > 0 then + cprint(" -> ${magenta}deps${clear}:") + for _, dep in ipairs(deps) do + requireinfo = dep:requireinfo() or {} + cprint(" -> %s", requireinfo.originstr) + end + end + + -- show cache directory + cprint(" -> ${magenta}cachedir${clear}: %s", instance:cachedir()) + + -- show install directory + cprint(" -> ${magenta}installdir${clear}: %s", instance:installdir()) + + -- show search directories and search names + cprint(" -> ${magenta}searchdirs${clear}: %s", table.concat(table.wrap(core_package.searchdirs()), path.envsep())) + local searchnames = hashset.new() + for _, url in ipairs(instance:urls()) do + url = filter.handle(url, instance) + searchnames:insert(url_filename(url)) + end + cprint(" -> ${magenta}searchnames${clear}: %s", table.concat(searchnames:to_array(), ", ")) + + -- show fetch info + cprint(" -> ${magenta}fetchinfo${clear}: %s", _info(instance)) + local fetchinfo = instance:fetch() + if fetchinfo then + for name, info in pairs(fetchinfo) do + if type(info) ~= "table" then + info = tostring(info) + end + cprint(" -> ${magenta}%s${clear}: %s", name, table.concat(table.wrap(info), " ")) + end + end + + -- show supported platforms + local platforms = {} + local on_install = instance:get("install") + if type(on_install) == "table" then + for plat, _ in pairs(on_install) do + table.insert(platforms, plat) + end + else + table.insert(platforms, "all") + end + cprint(" -> ${magenta}platforms${clear}: %s", table.concat(platforms, ", ")) + + -- show requires + cprint(" -> ${magenta}requires${clear}:") + cprint(" -> ${cyan}plat${clear}: %s", instance:plat()) + cprint(" -> ${cyan}arch${clear}: %s", instance:arch()) + local configs_required = instance:configs() + if configs_required then + cprint(" -> ${cyan}configs${clear}:") + for name, value in pairs(configs_required) do + cprint(" -> %s: %s", name, value) + end + end + + -- show user configs + local configs_defined = instance:get("configs") + if configs_defined then + cprint(" -> ${magenta}configs${clear}:") + for _, conf in ipairs(configs_defined) do + local configs_extra = instance:extraconf("configs", conf) + if configs_extra and not configs_extra.builtin then + cprintf(" -> ${cyan}%s${clear}: ", conf) + if configs_extra.description then + printf(configs_extra.description) + end + if configs_extra.default ~= nil then + printf(" (default: %s)", configs_extra.default) + elseif configs_extra.type ~= nil and configs_extra.type ~= "string" then + printf(" (type: %s)", configs_extra.type) + end + print("") + if configs_extra.values then + cprint(" -> values: %s", string.serialize(configs_extra.values, true)) + end + end + end + end + + -- show builtin configs + local configs_defined = instance:get("configs") + if configs_defined then + cprint(" -> ${magenta}configs (builtin)${clear}:") + for _, conf in ipairs(configs_defined) do + local configs_extra = instance:extraconf("configs", conf) + if configs_extra and configs_extra.builtin then + cprintf(" -> ${cyan}%s${clear}: ", conf) + if configs_extra.description then + printf(configs_extra.description) + end + if configs_extra.default ~= nil then + printf(" (default: %s)", configs_extra.default) + elseif configs_extra.type ~= nil and configs_extra.type ~= "string" then + printf(" (type: %s)", configs_extra.type) + end + print("") + if configs_extra.values then + cprint(" -> values: %s", string.serialize(configs_extra.values, true)) + end + end + end + end + + -- show references + local references = instance:references() + if references then + cprint(" -> ${magenta}references${clear}:") + for projectdir, refdate in pairs(references) do + cprint(" -> %s: %s%s", refdate, projectdir, os.isdir(projectdir) and "" or " ${red}(not found)${clear}") + end + end + + -- end + print("") + end + + -- leave environment + environment.leave() +end + diff --git a/xmake/modules/private/action/require/install.lua b/xmake/modules/private/action/require/install.lua new file mode 100644 index 000000000..f7be8b9b9 --- /dev/null +++ b/xmake/modules/private/action/require/install.lua @@ -0,0 +1,209 @@ +--!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 package.lua +-- + +-- imports +import("core.base.option") +import("core.base.task") +import("core.project.project") +import("lib.detect.find_tool") +import("private.action.require.impl.package") +import("private.action.require.impl.repository") +import("private.action.require.impl.environment") +import("private.action.require.impl.utils.get_requires") + +-- register required package environments +-- envs: bin path for *.dll, program .. +function _register_required_package_envs(instance, envs) + for name, values in pairs(instance:envs()) do + if name == "PATH" or name == "LD_LIBRARY_PATH" or name == "DYLD_LIBRARY_PATH" then + for _, value in ipairs(values) do + envs[name] = envs[name] or {} + if path.is_absolute(value) then + table.insert(envs[name], value) + else + table.insert(envs[name], path.join(instance:installdir(), value)) + end + end + else + envs[name] = envs[name] or {} + table.join2(envs[name], values) + end + end +end + +-- register required package libraries +-- libs: includedirs, links, linkdirs ... +function _register_required_package_libs(instance, required_package, is_deps) + if instance:is_library() then + local fetchinfo = instance:fetch() + if fetchinfo then + fetchinfo.name = nil + if is_deps then + -- we need only reserve license for root package + -- + -- @note the license compatibility between the root package and + -- its dependent packages is guaranteed by the root package itself + -- + fetchinfo.license = nil + + -- we need only some infos for root package + fetchinfo.version = nil + fetchinfo.static = nil + fetchinfo.shared = nil + end + required_package:add(fetchinfo) + end + end +end + +-- register the base info of required package +function _register_required_package_base(instance, required_package) + if not instance:isSys() and not instance:is3rd() then + required_package:set("__installdir", instance:installdir()) + end +end + +-- register the required local package +function _register_required_package(instance, required_package) + + -- disable it if this package is optional and missing + if _g.optional_missing[instance:name()] then + required_package:enable(false) + else + -- clear require info first + required_package:clear() + + -- add packages info with all dependencies + local envs = {} + _register_required_package_base(instance, required_package) + _register_required_package_libs(instance, required_package) + _register_required_package_envs(instance, envs) + local orderdeps = instance:orderdeps() + if orderdeps then + local total = #orderdeps + for idx, _ in ipairs(orderdeps) do + local dep = orderdeps[total + 1 - idx] + if dep then + _register_required_package_libs(dep, required_package, true) + _register_required_package_envs(dep, envs) + end + end + end + if #table.keys(envs) > 0 then + required_package:add({envs = envs}) + end + + -- enable this require info + required_package:enable(true) + end + + -- save this require info and flush the whole cache file + required_package:save() +end + +-- register all required local packages +function _register_required_packages(packages) + local registered_in_group = {} + for _, instance in ipairs(packages) do + + -- only register the first package in same group and root packages + local group = instance:group() + if not instance:parents() and (not group or not registered_in_group[group]) then + + -- register required package + local required_package = project.required_package(instance:alias() or instance:name()) + if required_package then + _register_required_package(instance, required_package) + end + + -- mark as registered in group + if group then + registered_in_group[group] = true + end + end + end +end + +-- check missing packages +function _check_missing_packages(packages) + + -- get all missing packages + local packages_missing = {} + local optional_missing = {} + for _, instance in ipairs(packages) do + if not instance:exists() and (#instance:urls() > 0 or instance:isSys()) then + if instance:optional() then + optional_missing[instance:name()] = instance + else + table.insert(packages_missing, instance:name()) + end + end + end + + -- raise tips + if #packages_missing > 0 then + local cmd = "xmake repo -u" + if os.getenv("XREPO_WORKING") then + cmd = "xrepo update-repo" + end + raise("The packages(%s) not found, please run `%s` first!", table.concat(packages_missing, ", "), cmd) + end + + -- save the optional missing packages + _g.optional_missing = optional_missing +end + +-- install packages +function main(requires_raw) + + -- get requires and extra config + local requires_extra = nil + local requires, requires_extra = get_requires(requires_raw) + if not requires or #requires == 0 then + return + end + + -- find git + environment.enter() + local git = find_tool("git") + environment.leave() + + -- pull all repositories first if not exists + -- + -- attempt to install git from the builtin-packages first if git not found + -- + if git and not repository.pulled() then + task.run("repo", {update = true}) + end + + -- install packages + environment.enter() + local packages = package.install_packages(requires, {requires_extra = requires_extra}) + if packages then + + -- check missing packages + _check_missing_packages(packages) + + -- register all required local packages + _register_required_packages(packages) + end + environment.leave() +end + diff --git a/xmake/modules/private/action/require/list.lua b/xmake/modules/private/action/require/list.lua new file mode 100644 index 000000000..2a165111b --- /dev/null +++ b/xmake/modules/private/action/require/list.lua @@ -0,0 +1,93 @@ +--!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 list.lua +-- + +-- imports +import("core.project.project") +import("core.base.task") +import("private.action.require.impl.package") +import("private.action.require.impl.repository") +import("private.action.require.impl.environment") + +-- from xmake/system/remote? +function _from(instance) + local fetchinfo = instance:fetch() + if fetchinfo then + if instance:is3rd() then + return ", ${green}3rd${clear}" + elseif instance:isSys() then + return ", ${green}system${clear}" + else + return "" + end + elseif #instance:urls() > 0 then + return instance:supported() and format(", ${yellow}remote${clear}(in %s)", instance:repo():name()) or format(", ${yellow}remote${clear}(${red}unsupported${clear} in %s)", instance:repo():name()) + elseif instance:isSys() then + return ", ${red}missing${clear}" + else + return "" + end +end + +-- get package info +function _info(instance) + local info = instance:version_str() and instance:version_str() or "no version" + info = info .. _from(instance) + info = info .. (instance:optional() and ", ${yellow}optional${clear}" or "") + return info +end + +-- list packages +function main() + + -- list all requires + print("The package dependencies of project:") + + -- get requires + local requires, requires_extra = project.requires_str() + if not requires or #requires == 0 then + return + end + + -- enter environment + environment.enter() + + -- pull all repositories first if not exists + if not repository.pulled() then + task.run("repo", {update = true}) + end + + -- list all required packages + for _, instance in ipairs(package.load_packages(requires, {requires_extra = requires_extra})) do + cprint(" ${magenta}require${clear}(%s): %s", instance:requireinfo().originstr, _info(instance)) + for _, dep in ipairs(instance:orderdeps()) do + cprint(" -> ${magenta}dep${clear}(%s): %s", dep:requireinfo().originstr, _info(dep)) + end + local fetchinfo = instance:fetch() + if fetchinfo then + for name, info in pairs(fetchinfo) do + cprint(" -> ${magenta}%s${clear}: %s", name, table.concat(table.wrap(info), " ")) + end + end + end + + -- leave environment + environment.leave() +end + diff --git a/xmake/modules/private/action/require/scan.lua b/xmake/modules/private/action/require/scan.lua new file mode 100644 index 000000000..a038f9f9e --- /dev/null +++ b/xmake/modules/private/action/require/scan.lua @@ -0,0 +1,90 @@ +--!A cross-platform build utility based on Lua +-- +-- Licensed to the Apache Software Foundation (ASF) under one +-- or more contributor license agreements. See the NOTICE file +-- distributed with this work for additional scanrmation +-- regarding copyright ownership. The ASF licenses this file +-- to you 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 scan.lua +-- + +-- imports +import("core.base.option") +import("core.package.package") + +-- scan local package +function _scan_package(packagedir) + + -- show packages + local package_name = path.filename(packagedir) + for _, versiondir in ipairs(os.dirs(path.join(packagedir, "*"))) do + local version = path.filename(versiondir) + cprint("${magenta}%s-%s${clear}:", package_name, version) + + -- show package hash + for _, hashdir in ipairs(os.dirs(path.join(versiondir, "*"))) do + local hash = path.filename(hashdir) + local references_file = path.join(hashdir, "references.txt") + local referenced = false + local references = os.isfile(references_file) and io.load(references_file) or nil + if references then + for projectdir, refdate in pairs(references) do + if os.isdir(projectdir) then + referenced = true + break + end + end + end + local manifest_file = path.join(hashdir, "manifest.txt") + local manifest = os.isfile(manifest_file) and io.load(manifest_file) or nil + cprintf(" -> ${yellow}%s${clear}: ${green}%s, %s", hash, manifest and manifest.plat or "", manifest and manifest.arch or "") + if os.emptydir(hashdir) then + cprintf(", ${red}empty") + elseif not referenced then + cprintf(", ${red}unused") + elseif not manifest then + cprintf(", ${red}invalid") + end + print("") + if manifest and manifest.configs then + print(" -> %s", string.serialize(manifest.configs, true)) + end + end + end +end + +-- scan local packages +function main(package_names) + + -- trace + print("scanning packages ..") + + -- scan packages + local installdir = package.installdir() + if package_names then + for _, package_name in ipairs(package_names) do + for _, packagedir in ipairs(os.dirs(path.join(installdir, package_name:sub(1, 1), package_name))) do + _scan_package(packagedir) + end + end + else + for _, packagedir in ipairs(os.dirs(path.join(installdir, "*", "*"))) do + _scan_package(packagedir) + end + end +end + diff --git a/xmake/modules/private/action/require/search.lua b/xmake/modules/private/action/require/search.lua new file mode 100644 index 000000000..2637e6c36 --- /dev/null +++ b/xmake/modules/private/action/require/search.lua @@ -0,0 +1,65 @@ +--!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 search.lua +-- + +-- imports +import("core.base.task") +import("private.action.require.impl.utils.filter") +import("private.action.require.impl.package") +import("private.action.require.impl.repository") +import("private.action.require.impl.environment") + +-- search the given packages +function main(names) + + -- no names? + if not names then + return + end + + -- enter environment + environment.enter() + + -- pull all repositories first if not exists + if not repository.pulled() then + task.run("repo", {update = true}) + end + + -- show title + print("The package names:") + + -- search packages + for name, packages in pairs(package.search_packages(names)) do + if #packages > 0 then + + -- show name + print(" %s: ", name) + + -- show packages + for _, instance in ipairs(packages) do + local repo = instance:repo() + cprint(" -> ${magenta}%s${clear}: %s %s", instance:name(), instance:get("description") or "", repo and ("(in " .. repo:name() .. ")") or "") + end + end + end + + -- leave environment + environment.leave() +end + diff --git a/xmake/modules/private/action/require/uninstall.lua b/xmake/modules/private/action/require/uninstall.lua new file mode 100644 index 000000000..2d690614b --- /dev/null +++ b/xmake/modules/private/action/require/uninstall.lua @@ -0,0 +1,68 @@ +--!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 uninstall.lua +-- + +-- imports +import("core.base.task") +import("core.base.option") +import("private.action.require.impl.package") +import("private.action.require.impl.repository") +import("private.action.require.impl.environment") +import("private.action.require.impl.utils.get_requires") + +-- uninstall the given packages +function main(requires_raw) + + -- enter environment + environment.enter() + + -- pull all repositories first if not exists + if not repository.pulled() then + task.run("repo", {update = true}) + end + + -- get requires and extra config + local requires_extra = nil + local requires, requires_extra = get_requires(requires_raw) + if not requires or #requires == 0 then + return + end + + -- uninstall packages + local packages = package.uninstall_packages(requires, {requires_extra = requires_extra}) + for _, instance in ipairs(packages) do + print("uninstall: %s%s ok!", instance:name(), instance:version_str() and ("-" .. instance:version_str()) or "") + end + if not packages or #packages == 0 then + cprint("${bright}packages(%s) not found, maybe they don’t exactly match the configuration.", table.concat(requires_raw, ", ")) + if os.getenv("XREPO_WORKING") then + print("please attempt to remove them with `-f/--configs=` option, e.g.") + print(" - xrepo remove -f \"name=value, ...\" package") + print(" - xrepo remove -m debug -k shared -f \"name=value, ...\" package") + else + print("please attempt to uninstall them with `--extra=` option, e.g.") + print(" - xmake require --uninstall --extra=\"{configs={...}}\" package") + print(" - xmake require --uninstall --extra=\"{debug=true,configs={shared=true}}\" package") + end + end + + -- leave environment + environment.leave() +end + diff --git a/xmake/modules/private/package/clean.lua b/xmake/modules/private/package/clean.lua deleted file mode 100644 index fb93e6304..000000000 --- a/xmake/modules/private/package/clean.lua +++ /dev/null @@ -1,103 +0,0 @@ ---!A cross-platform build utility based on Lua --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. --- --- Copyright (C) 2015-present, TBOOX Open Source Group. --- --- @author ruki --- @file clean.lua --- - --- imports -import("core.base.option") -import("core.package.package") -import("core.cache.localcache") - --- clear the unused or invalid package directories -function _clear_packagedirs(packagedir) - - -- clear them - local package_name = path.filename(packagedir) - for _, versiondir in ipairs(os.dirs(path.join(packagedir, "*"))) do - local version = path.filename(versiondir) - for _, hashdir in ipairs(os.dirs(path.join(versiondir, "*"))) do - local hash = path.filename(hashdir) - local references_file = path.join(hashdir, "references.txt") - local referenced = false - local references = os.isfile(references_file) and io.load(references_file) or nil - if references then - for projectdir, refdate in pairs(references) do - if os.isdir(projectdir) then - referenced = true - break - end - end - end - local manifest_file = path.join(hashdir, "manifest.txt") - local status = nil - if os.emptydir(hashdir) then - status = "empty" - elseif not referenced then - status = "unused" - elseif not os.isfile(manifest_file) then - status = "invalid" - end - if status then - local description = string.format("remove this ${magenta}%s-%s${clear}/${yellow}%s${clear} (${red}%s${clear})", package_name, version, hash, status) - local confirm = utils.confirm({default = true, description = description}) - if confirm then - os.rm(hashdir) - end - end - end - if os.emptydir(versiondir) then - os.rm(versiondir) - end - end - if os.emptydir(packagedir) then - os.rm(packagedir) - end -end - --- clean the given or all package caches -function main(package_names) - - -- trace - print("clearing packages ..") - - -- clear all unused packages - local installdir = package.installdir() - if package_names then - for _, package_name in ipairs(package_names) do - for _, packagedir in ipairs(os.dirs(path.join(installdir, package_name:sub(1, 1), package_name))) do - _clear_packagedirs(packagedir) - end - end - else - for _, packagedir in ipairs(os.dirs(path.join(installdir, "*", "*"))) do - _clear_packagedirs(packagedir) - end - end - - -- trace - print("clearing caches ..") - - -- clear cache directory - os.rm(package.cachedir()) - - -- clear require cache - local require_cache = localcache.cache("package") - require_cache:clear() - require_cache:save() -end - diff --git a/xmake/modules/private/package/export.lua b/xmake/modules/private/package/export.lua deleted file mode 100644 index 3902b87f6..000000000 --- a/xmake/modules/private/package/export.lua +++ /dev/null @@ -1,71 +0,0 @@ ---!A cross-platform build utility based on Lua --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. --- --- Copyright (C) 2015-present, TBOOX Open Source Group. --- --- @author ruki --- @file export.lua --- - --- imports -import("core.base.task") -import("core.base.option") -import("private.package.impl.package") -import("private.package.impl.repository") -import("private.package.impl.environment") -import("private.package.impl.utils.get_requires") - --- export the given packages -function main(requires_raw) - - -- enter environment - environment.enter() - - -- pull all repositories first if not exists - if not repository.pulled() then - task.run("repo", {update = true}) - end - - -- get requires and extra config - local requires_extra = nil - local requires, requires_extra = get_requires(requires_raw) - if not requires or #requires == 0 then - return - end - - -- export packages - local exportdir = option.get("exportdir") - local packages = package.export_packages(requires, {requires_extra = requires_extra, exportdir = exportdir}) - for _, instance in ipairs(packages) do - print("export: %s%s ok!", instance:name(), instance:version_str() and ("-" .. instance:version_str()) or "") - end - if not packages or #packages == 0 then - cprint("${bright}packages(%s) not found, maybe they don’t exactly match the configuration.", table.concat(requires_raw, ", ")) - if os.getenv("XREPO_WORKING") then - print("please attempt to export them with `-f/--configs=` option, e.g.") - print(" - xrepo export -f \"name=value, ...\" package") - print(" - xrepo export -m debug -k shared -f \"name=value, ...\" package") - else - print("please attempt to export them with `--extra=` option, e.g.") - print(" - xmake require --export --extra=\"{configs={...}}\" package") - print(" - xmake require --export --extra=\"{debug=true,configs={shared=true}}\" package") - end - else - print("output: %s", exportdir) - end - - -- leave environment - environment.leave() -end - diff --git a/xmake/modules/private/package/fetch.lua b/xmake/modules/private/package/fetch.lua deleted file mode 100644 index f9c923916..000000000 --- a/xmake/modules/private/package/fetch.lua +++ /dev/null @@ -1,94 +0,0 @@ ---!A cross-platform build utility based on Lua --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. --- --- Copyright (C) 2015-present, TBOOX Open Source Group. --- --- @author ruki --- @file fetch.lua --- - --- imports -import("core.base.option") -import("core.base.hashset") -import("core.project.project") -import("core.package.package", {alias = "core_package"}) -import("core.tool.linker") -import("core.tool.compiler") -import("private.package.impl.package") -import("private.package.impl.repository") -import("private.package.impl.utils.get_requires") - --- fetch the given package info -function main(requires_raw) - - -- get requires and extra config - local requires_extra = nil - local requires, requires_extra = get_requires(requires_raw) - if not requires or #requires == 0 then - return - end - - -- get the fetching modes - local fetchmodes = option.get("fetch_modes") - if fetchmodes then - fetchmodes = hashset.from(fetchmodes:split(',', {plain = true})) - end - - -- fetch all packages - local fetchinfos = {} - local nodeps = not (fetchmodes and fetchmodes:has("deps")) - for _, instance in irpairs(package.load_packages(requires, {requires_extra = requires_extra, nodeps = nodeps})) do - local fetchinfo = instance:fetch({external = (fetchmodes and fetchmodes:has("external") or false)}) - if fetchinfo then - table.insert(fetchinfos, fetchinfo) - end - end - - -- show results - if #fetchinfos > 0 then - local flags = {} - if fetchmodes and fetchmodes:has("cflags") then - for _, fetchinfo in ipairs(fetchinfos) do - table.join2(flags, compiler.map_flags("cxx", "define", fetchinfo.defines)) - table.join2(flags, compiler.map_flags("cxx", "includedir", fetchinfo.includedirs)) - table.join2(flags, compiler.map_flags("cxx", "sysincludedir", fetchinfo.sysincludedirs)) - for _, cflag in ipairs(fetchinfo.cflags) do - table.insert(flags, cflag) - end - for _, cxflag in ipairs(fetchinfo.cxflags) do - table.insert(flags, cxflag) - end - for _, cxxflag in ipairs(fetchinfo.cxxflags) do - table.insert(flags, cxxflag) - end - end - end - if fetchmodes and fetchmodes:has("ldflags") then - for _, fetchinfo in ipairs(fetchinfos) do - table.join2(flags, linker.map_flags("binary", {"cxx"}, "linkdir", fetchinfo.linkdirs)) - table.join2(flags, linker.map_flags("binary", {"cxx"}, "link", fetchinfo.links)) - table.join2(flags, linker.map_flags("binary", {"cxx"}, "syslink", fetchinfo.syslinks)) - for _, ldflag in ipairs(fetchinfo.ldflags) do - table.insert(flags, ldflags) - end - end - end - if #flags > 0 then - print(os.args(flags)) - else - print(fetchinfos) - end - end -end - diff --git a/xmake/modules/private/package/impl/actions/download.lua b/xmake/modules/private/package/impl/actions/download.lua deleted file mode 100644 index 7200db0a7..000000000 --- a/xmake/modules/private/package/impl/actions/download.lua +++ /dev/null @@ -1,268 +0,0 @@ ---!A cross-platform build utility based on Lua --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. --- --- Copyright (C) 2015-present, TBOOX Open Source Group. --- --- @author ruki --- @file download.lua --- - --- imports -import("core.base.option") -import("core.base.tty") -import("core.base.hashset") -import("core.project.config") -import("core.package.package", {alias = "core_package"}) -import("lib.detect.find_file") -import("lib.detect.find_directory") -import("private.package.impl.utils.filter") -import("private.package.impl..utils.url_filename") -import("net.http") -import("devel.git") -import("utils.archive") - --- checkout codes from git -function _checkout(package, url, sourcedir, url_alias) - - -- use previous source directory if exists - local packagedir = path.join(sourcedir, package:name()) - if os.isdir(packagedir) and - not (option.get("force") and package:branch()) then -- we need disable cache if we force to clone from the given branch - - -- clean the previous build files - git.clean({repodir = packagedir, force = true, all = true}) - -- reset the previous modified files - git.reset({repodir = packagedir, hard = true}) - tty.erase_line_to_start().cr() - return - end - - -- we can use local package from the search directories directly if network is too slow - local localdir = find_directory(package:name() .. archive.extension(url), core_package.searchdirs()) - if localdir and os.isdir(localdir) then - git.clean({repodir = localdir, force = true, all = true}) - tty.erase_line_to_start().cr() - return - end - - -- remove temporary directory - os.rm(sourcedir .. ".tmp") - - -- download package from branches? - packagedir = path.join(sourcedir .. ".tmp", package:name()) - if package:branch() then - - -- only shadow clone this branch - git.clone(url, {depth = 1, recursive = true, branch = package:branch(), outputdir = packagedir}) - - -- download package from revision or tag? - else - - -- clone whole history and tags - git.clone(url, {outputdir = packagedir, recursive = true}) - - -- attempt to checkout the given version - git.checkout(package:revision(url_alias) or package:tag() or package:version_str(), {repodir = packagedir}) - end - - -- move to source directory - os.rm(sourcedir) - os.mv(sourcedir .. ".tmp", sourcedir) - - -- trace - tty.erase_line_to_start().cr() - cprint("${yellow} => ${clear}clone %s %s .. ${color.success}${text.success}", url, package:version_str()) -end - --- download codes from ftp/http/https -function _download(package, url, sourcedir, url_alias, url_excludes) - - -- get package file - local packagefile = url_filename(url) - - -- get sourcehash from the given url - -- - -- we need not sourcehash and skip checksum to try download it directly if no version list in package() - -- @see https://github.com/xmake-io/xmake/issues/930 - -- https://github.com/xmake-io/xmake/issues/1009 - -- - local sourcehash = package:sourcehash(url_alias) - assert(not package:verify() or not package:get("versions") or sourcehash, "cannot get source hash of %s in package(%s)", url, package:name()) - - -- the package file have been downloaded? - local cached = true - if not os.isfile(packagefile) or sourcehash ~= hash.sha256(packagefile) then - - -- no cached - cached = false - - -- attempt to remove package file first - os.tryrm(packagefile) - - -- download or copy package file - local localfile = find_file(path.filename(packagefile), core_package.searchdirs()) - if os.isfile(url) then - os.cp(url, packagefile) - elseif localfile and os.isfile(localfile) then - -- we can use local package from the search directories directly if network is too slow - os.cp(localfile, packagefile) - else - http.download(url, packagefile) - end - - -- check hash - if sourcehash and sourcehash ~= hash.sha256(packagefile) then - raise("unmatched checksum!") - end - end - - -- extract package file - os.rm(sourcedir .. ".tmp") - if archive.extract(packagefile, sourcedir .. ".tmp", {excludes = url_excludes}) then - -- move to source directory - os.rm(sourcedir) - os.mv(sourcedir .. ".tmp", sourcedir) - else - -- create an empty source directory if do not extract package file - os.tryrm(sourcedir) - os.mkdir(sourcedir) - end - - -- save original file path - package:originfile_set(path.absolute(packagefile)) - - -- trace - tty.erase_line_to_start().cr() - if not cached then - cprint("${yellow} => ${clear}download %s .. ${color.success}${text.success}", url) - end -end - --- get sorted urls -function _urls(package) - - -- sort urls from the version source - local urls = {{}, {}} - for _, url in ipairs(package:urls()) do - if git.checkurl(url) then - table.insert(urls[1], url) - elseif not package:verify() or not package:get("versions") or package:sourcehash(package:url_alias(url)) then - table.insert(urls[2], url) - end - end - if package:gitref() then - return table.join(urls[1], urls[2]) - else - return table.join(urls[2], urls[1]) - end -end - --- download the given package -function main(package) - - -- get working directory of this package - local workdir = package:cachedir() - - -- ensure the working directory first - os.mkdir(workdir) - - -- enter the working directory - local oldir = os.cd(workdir) - - -- lock this package - package:lock() - - -- get urls - local urls = _urls(package) - assert(#urls > 0, "cannot get url of package(%s)", package:name()) - - -- download package from urls - local ok = false - local urls_failed = {} - for idx, url in ipairs(urls) do - - -- get url alias - local url_alias = package:url_alias(url) - - -- get url excludes - local url_excludes = package:url_excludes(url) - - -- filter url - url = filter.handle(url, package) - - -- download url - ok = try - { - function () - - -- download package - local sourcedir = "source" - if git.checkurl(url) then - _checkout(package, url, sourcedir, url_alias) - else - _download(package, url, sourcedir, url_alias, url_excludes) - end - return true - end, - catch - { - function (errors) - - -- show or save the last errors - if errors and (option.get("verbose") or option.get("diagnosis")) then - cprint("${dim color.error}error: ${clear}%s", errors) - end - - -- trace - tty.erase_line_to_start().cr() - if git.checkurl(url) then - cprint("${yellow} => ${clear}clone %s %s .. ${color.failure}${text.failure}", url, package:version_str()) - else - cprint("${yellow} => ${clear}download %s .. ${color.failure}${text.failure}", url) - end - table.insert(urls_failed, url) - - -- failed? break it - if idx == #urls and not package:optional() then - if #urls_failed > 0 then - print("") - print("we can also download these packages manually:") - local searchnames = hashset.new() - for _, url_failed in ipairs(urls_failed) do - cprint(" ${yellow}- %s", url_failed) - searchnames:insert(url_filename(url_failed)) - end - cprint("to the local search directories: ${bright}%s", table.concat(table.wrap(core_package.searchdirs()), path.envsep())) - cprint(" ${bright}- %s", table.concat(searchnames:to_array(), ", ")) - cprint("and we can run `xmake g --pkg_searchdirs=/xxx` to set the search directories.") - end - raise("download failed!") - end - end - } - } - - -- ok? break it - if ok then break end - end - - -- unlock this package - package:unlock() - - -- leave working directory - os.cd(oldir) - return ok -end - - diff --git a/xmake/modules/private/package/impl/actions/download_resources.lua b/xmake/modules/private/package/impl/actions/download_resources.lua deleted file mode 100644 index 6447b3ea2..000000000 --- a/xmake/modules/private/package/impl/actions/download_resources.lua +++ /dev/null @@ -1,96 +0,0 @@ ---!A cross-platform build utility based on Lua --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. --- --- Copyright (C) 2015-present, TBOOX Open Source Group. --- --- @author ruki --- @file download_resources.lua --- - --- imports -import("core.base.option") -import("core.package.package", {alias = "core_package"}) -import("lib.detect.find_file") -import("net.http") -import("utils.archive") - --- download resources -function _download(package, resource_name, resource_url, resource_hash) - - -- trace - vprint("downloading resource(%s: %s) to %s-%s ..", resource_name, resource_url, package:name(), package:version_str()) - - -- get the resource file - local resource_file = assert(package:resourcefile(resource_name), "invalid resource file!") - - -- ensure lower hash - if resource_hash then - resource_hash = resource_hash:lower() - end - - -- the package file have been downloaded? - local cached = true - if option.get("force") or not os.isfile(resource_file) or resource_hash ~= hash.sha256(resource_file) then - - -- no cached - cached = false - - -- attempt to remove the previous file first - os.tryrm(resource_file) - - -- download or copy the resource file - local localfile = find_file(path.filename(resource_file), core_package.searchdirs()) - if localfile and os.isfile(localfile) then - -- we can use local resource from the search directories directly if network is too slow - os.cp(localfile, resource_file) - elseif resource_url:find(string.ipattern("https-://")) or resource_url:find(string.ipattern("ftps-://")) then - http.download(resource_url, resource_file) - else - raise("invalid resource url(%s)", resource_url) - end - - -- check hash - if resource_hash and resource_hash ~= hash.sha256(resource_file) then - raise("resource(%s): unmatched checksum!", resource_url) - end - end - - -- extract the resource file - local resourcedir = package:resourcedir(resource_name) - local resourcedir_tmp = resourcedir .. ".tmp" - os.tryrm(resourcedir_tmp) - if archive.extract(resource_file, resourcedir_tmp) then - os.tryrm(resourcedir) - os.mv(resourcedir_tmp, resourcedir) - else - os.tryrm(resourcedir_tmp) - end -end - --- download all resources of the given package -function main(package) - - -- no resources? - local resources = package:resources() - if not resources then - return - end - - -- download all resources - for name, resourceinfo in pairs(resources) do - -- we use wrap to support urls table and only get the first url now - -- TODO maybe we will download resource from the multiple urls in the future - _download(package, name, table.wrap(resourceinfo.url)[1], resourceinfo.sha256) - end -end diff --git a/xmake/modules/private/package/impl/actions/install.lua b/xmake/modules/private/package/impl/actions/install.lua deleted file mode 100644 index 8de1c3aa8..000000000 --- a/xmake/modules/private/package/impl/actions/install.lua +++ /dev/null @@ -1,273 +0,0 @@ ---!A cross-platform build utility based on Lua --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. --- --- Copyright (C) 2015-present, TBOOX Open Source Group. --- --- @author ruki --- @file install.lua --- - --- imports -import("core.base.option") -import("core.base.tty") -import("core.project.target") -import("lib.detect.find_file") -import("private.package.impl.actions.test") -import("private.package.impl.actions.patch_sources") -import("private.package.impl.actions.download_resources") -import("private.package.impl.utils.filter") - --- patch pkgconfig if not exists -function _patch_pkgconfig(package) - - -- only binary? need not pkgconfig - if not package:is_library() then - return - end - - -- get lib/pkgconfig/*.pc file - local pkgconfigdir = path.join(package:installdir(), "lib", "pkgconfig") - local pcfile = os.isdir(pkgconfigdir) and find_file("*.pc", pkgconfigdir) or nil - if pcfile then - return - end - - -- trace - pcfile = path.join(pkgconfigdir, package:name() .. ".pc") - vprint("patching %s ..", pcfile) - - -- fetch package - local fetchinfo = package:fetchdeps() - if not fetchinfo then - return - end - - -- get libs - local libs = "" - for _, linkdir in ipairs(fetchinfo.linkdirs) do - libs = libs .. "-L" .. linkdir - end - libs = libs .. " -L${libdir}" - for _, link in ipairs(fetchinfo.links) do - libs = libs .. " -l" .. link - end - for _, link in ipairs(fetchinfo.syslinks) do - libs = libs .. " -l" .. link - end - - -- cflags - local cflags = "" - for _, includedir in ipairs(fetchinfo.includedirs) do - cflags = cflags .. "-I" .. includedir - end - cflags = cflags .. " -I${includedir}" - - -- patch a *.pc file - local file = io.open(pcfile, 'w') - if file then - file:print("prefix=%s", package:installdir()) - file:print("exec_prefix=${prefix}") - file:print("libdir=${exec_prefix}/lib") - file:print("includedir=${prefix}/include") - file:print("") - file:print("Name: %s", package:name()) - file:print("Description: %s", package:description()) - file:print("Version: %s", package:version_str()) - file:print("Libs: %s", libs) - file:print("Libs.private: ") - file:print("Cflags: %s", cflags) - file:close() - end -end - --- install the given package -function main(package) - - -- get working directory of this package - local workdir = package:cachedir() - - -- lock this package - package:lock() - - -- enter the working directory - local oldir = nil - if #package:urls() > 0 then - -- only one root directory? skip it - local filedirs = os.filedirs(path.join(workdir, "source", "*")) - if #filedirs == 1 and os.isdir(filedirs[1]) then - oldir = os.cd(filedirs[1]) - else - oldir = os.cd(path.join(workdir, "source")) - end - end - if not oldir then - os.mkdir(workdir) - oldir = os.cd(workdir) - end - - -- init tipname - local tipname = package:name() - if package:version_str() then - tipname = tipname .. "-" .. package:version_str() - end - - -- install it - try - { - function () - - -- the package scripts - local scripts = - { - package:script("install_before") - , package:script("install") - , package:script("install_after") - } - - -- install the third-party package directly, e.g. brew::pcre2/libpcre2-8, conan::OpenSSL/1.0.2n@conan/stable - local installed_now = false - if package:is3rd() then - local script = package:script("install") - if script ~= nil then - filter.call(script, package) - end - else - - -- build and install package to the install directory - if option.get("force") or not package:manifest_load() then - - -- clean install directory first - os.tryrm(package:installdir()) - - -- enter the environments of all package dependencies - for _, dep in ipairs(package:orderdeps()) do - dep:envs_enter() - end - - -- download package resources - download_resources(package) - - -- patch source codes of package - patch_sources(package) - - -- do install - for i = 1, 3 do - local script = scripts[i] - if script ~= nil then - filter.call(script, package) - end - end - - -- leave the environments of all package dependencies - for _, dep in irpairs(package:orderdeps()) do - dep:envs_leave() - end - - -- save the package info to the manifest file - package:manifest_save() - installed_now = true - end - end - - -- enter the package environments - for _, dep in ipairs(package:orderdeps()) do - dep:envs_enter() - end - package:envs_enter() - - -- fetch package and force to flush the cache - local fetchinfo = package:fetch({force = true}) - if option.get("verbose") or option.get("diagnosis") then - print(fetchinfo) - end - assert(fetchinfo, "fetch %s failed!", tipname) - - -- this package is installed now - if installed_now then - - -- patch pkg-config files for package - _patch_pkgconfig(package) - - -- test it - test(package) - end - - -- leave the package environments - package:envs_leave() - for _, dep in irpairs(package:orderdeps()) do - dep:envs_leave() - end - - -- trace - tty.erase_line_to_start().cr() - cprint("${yellow} => ${clear}install %s %s .. ${color.success}${text.success}", package:displayname(), package:version_str() or "") - end, - - catch - { - function (errors) - - -- show or save the last errors - local errorfile = path.join(package:installdir("logs"), "install.txt") - if errors then - if (option.get("verbose") or option.get("diagnosis")) then - cprint("${dim color.error}error: ${clear}%s", errors) - else - io.writefile(errorfile, errors .. "\n") - end - end - - -- trace - tty.erase_line_to_start().cr() - cprint("${yellow} => ${clear}install %s %s .. ${color.failure}${text.failure}", package:displayname(), package:version_str() or "") - - -- leave the package environments - package:envs_leave() - - -- copy the invalid package directory to cache - local installdir = package:installdir() - if os.isdir(installdir) then - local installdir_failed = path.join(package:cachedir(), "installdir.failed") - os.tryrm(installdir_failed) - if not os.isdir(installdir_failed) then - os.cp(installdir, installdir_failed) - end - errorfile = path.join(installdir_failed, "logs", "install.txt") - end - os.tryrm(installdir) - - -- failed - if not package:requireinfo().optional then - if os.isfile(errorfile) then - print("if you want to get verbose errors, please see:") - cprint(" -> ${bright}%s", errorfile) - end - raise("install failed!") - end - end - } - } - - -- clean the empty package directory - local installdir = package:installdir() - if os.emptydir(installdir) then - os.tryrm(installdir) - end - - -- unlock this package - package:unlock() - - -- leave source codes directory - os.cd(oldir) -end diff --git a/xmake/modules/private/package/impl/actions/patch_sources.lua b/xmake/modules/private/package/impl/actions/patch_sources.lua deleted file mode 100644 index 110330af0..000000000 --- a/xmake/modules/private/package/impl/actions/patch_sources.lua +++ /dev/null @@ -1,112 +0,0 @@ ---!A cross-platform build utility based on Lua --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. --- --- Copyright (C) 2015-present, TBOOX Open Source Group. --- --- @author ruki --- @file patch_sources.lua --- - --- imports -import("core.base.option") -import("net.http") -import("devel.git") - --- check sha256 -function _check_sha256(patch_hash, patch_file) - local ok = (patch_hash == hash.sha256(patch_file)) - if not ok and is_host("windows") then - -- `git pull` maybe will replace lf to crlf in the patch text automatically on windows. - -- so we need attempt to fix this sha256 - -- - -- @see - -- https://github.com/xmake-io/xmake-repo/pull/67 - -- https://stackoverflow.com/questions/1967370/git-replacing-lf-with-crlf - -- - local tmpfile = os.tmpfile(patch_file) - os.cp(patch_file, tmpfile) - local content = io.readfile(tmpfile, {encoding = "binary"}) - content = content:gsub('\r\n', '\n') - io.writefile(tmpfile, content, {encoding = "binary"}) - ok = (patch_hash == hash.sha256(tmpfile)) - os.rm(tmpfile) - end - return ok -end - --- do patch -function _patch(package, patch_url, patch_hash) - - -- trace - vprint("patching %s to %s-%s ..", patch_url, package:name(), package:version_str()) - - -- get the patch file - local patch_file = path.join(os.tmpdir(), "patches", package:name(), package:version_str(), (path.filename(patch_url):gsub("%?.+$", ""))) - - -- ensure lower hash - if patch_hash then - patch_hash = patch_hash:lower() - end - - -- the package file have been downloaded? - local cached = true - if option.get("force") or not os.isfile(patch_file) or not _check_sha256(patch_hash, patch_file) then - - -- no cached - cached = false - - -- attempt to remove the previous file first - os.tryrm(patch_file) - - -- download the patch file - if patch_url:find(string.ipattern("https-://")) or patch_url:find(string.ipattern("ftps-://")) then - http.download(patch_url, patch_file) - else - -- copy the patch file - if os.isfile(patch_url) then - os.cp(patch_url, patch_file) - else - local scriptdir = package:scriptdir() - if scriptdir and os.isfile(path.join(scriptdir, patch_url)) then - os.cp(path.join(scriptdir, patch_url), patch_file) - else - raise("patch(%s): not found!", patch_url) - end - end - end - - -- check hash - if patch_hash and not _check_sha256(patch_hash, patch_file) then - raise("patch(%s): unmatched checksum!", patch_url) - end - end - - -- apply the patch file - git.apply(patch_file) -end - --- patch the given package -function main(package) - - -- no patches? - local patches = package:patches() - if not patches then - return - end - - -- do all patches - for _, patchinfo in ipairs(patches) do - _patch(package, patchinfo.url, patchinfo.sha256) - end -end diff --git a/xmake/modules/private/package/impl/actions/test.lua b/xmake/modules/private/package/impl/actions/test.lua deleted file mode 100644 index caf784ffb..000000000 --- a/xmake/modules/private/package/impl/actions/test.lua +++ /dev/null @@ -1,59 +0,0 @@ ---!A cross-platform build utility based on Lua --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. --- --- Copyright (C) 2015-present, TBOOX Open Source Group. --- --- @author ruki --- @file test.lua --- - --- imports -import("core.base.option") -import("private.package.impl.utils.filter") - --- test the given package -function main(package) - - -- the package scripts - local scripts = - { - package:script("test_before") - , package:script("test") - , package:script("test_after") - } - - -- enter the test directory - local testdir = path.join(os.tmpdir(), "pkgtest", package:name(), package:version_str() or "latest") - if os.isdir(testdir) then - os.tryrm(testdir) - end - if not os.isdir(testdir) then - os.mkdir(testdir) - end - local oldir = os.cd(testdir) - - -- test it - for i = 1, 3 do - local script = scripts[i] - if script ~= nil then - filter.call(script, package) - end - end - - -- restore the current directory - os.cd(oldir) - - -- remove the test directory - os.tryrm(testdir) -end diff --git a/xmake/modules/private/package/impl/environment.lua b/xmake/modules/private/package/impl/environment.lua deleted file mode 100644 index 2485e58c9..000000000 --- a/xmake/modules/private/package/impl/environment.lua +++ /dev/null @@ -1,73 +0,0 @@ ---!A cross-platform build utility based on Lua --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. --- --- Copyright (C) 2015-present, TBOOX Open Source Group. --- --- @author ruki --- @file environment.lua --- - --- imports -import("core.project.config") -import("core.package.package", {alias = "core_package"}) -import("lib.detect.find_tool") -import("private.package.impl.packagenv") -import("private.package.impl.package") - --- enter environment --- --- ensure that we can find some basic tools: git, unzip, ... --- --- If these tools not exist, we will install it first. --- -function enter() - - -- unzip or 7zip is necessary - if not find_tool("unzip") and not find_tool("7z") then - raise("unzip or 7zip not found! we need install it first") - end - - -- enter the environments of git - packagenv.enter("git") - - -- git not found? install it first - local packages = {} - if not find_tool("git") then - table.join2(packages, package.install_packages("git")) - end - - -- missing the necessary unarchivers for *.gz, *.7z? install them first, e.g. gzip, 7z, tar .. - if not ((find_tool("gzip") and find_tool("tar")) or find_tool("7z")) then - table.join2(packages, package.install_packages("7z")) - end - - -- enter the environments of installed packages - for _, instance in ipairs(packages) do - instance:envs_enter() - end - _g._PACKAGES = packages -end - --- leave environment -function leave() - - -- leave the environments of installed packages - for _, instance in irpairs(_g._PACKAGES) do - instance:envs_leave() - end - _g._PACKAGES = nil - - -- leave the environments of git - packagenv.leave("git") -end diff --git a/xmake/modules/private/package/impl/package.lua b/xmake/modules/private/package/impl/package.lua deleted file mode 100644 index dff6bd7cc..000000000 --- a/xmake/modules/private/package/impl/package.lua +++ /dev/null @@ -1,1106 +0,0 @@ ---!A cross-platform build utility based on Lua --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. --- --- Copyright (C) 2015-present, TBOOX Open Source Group. --- --- @author ruki --- @file package.lua --- - --- imports -import("core.base.semver") -import("core.base.option") -import("core.base.global") -import("core.base.hashset") -import("core.base.scheduler") -import("core.base.tty") -import("private.async.runjobs") -import("private.utils.progress") -import("core.cache.memcache") -import("core.cache.localcache") -import("core.project.project") -import("core.package.package", {alias = "core_package"}) -import("actions.install", {alias = "action_install"}) -import("actions.download", {alias = "action_download"}) -import("devel.git") -import("net.fasturl") -import("private.package.impl.repository") - --- get memcache -function _memcache() - return memcache.cache("require.impl.package") -end - --- --- parse require string --- --- basic --- - add_requires("zlib") --- --- semver --- - add_requires("tbox >=1.5.1", "zlib >=1.2.11") --- --- git branch/tag --- - add_requires("zlib master") --- --- with the given repository --- - add_requires("xmake-repo@tbox >=1.5.1") --- --- with the given configs --- - add_requires("aaa_bbb_ccc >=1.5.1 <1.6.0", {optional = true, alias = "mypkg", debug = true}) --- - add_requires("tbox", {config = {coroutine = true, abc = "xxx"}}) --- --- with namespace and the 3rd package manager --- - add_requires("xmake::xmake-repo@tbox >=1.5.1") --- - add_requires("vcpkg::ffmpeg") --- - add_requires("conan::OpenSSL/1.0.2n@conan/stable") --- - add_requires("conan::openssl/1.1.1g") -- new --- - add_requires("brew::pcre2/libpcre2-8 10.x", {alias = "pcre2"}) --- --- clone as a standalone package with the different configs --- we can install and use these three packages at the same time. --- - add_requires("zlib") --- - add_requires("zlib~debug", {debug = true}) --- - add_requires("zlib~shared", {configs = {shared = true}, alias = "zlib_shared"}) --- --- {system = nil/true/false}: --- nil: get local or system packages --- true: only get system package --- false: only get local packages --- --- -function _parse_require(require_str, requires_extra, parentinfo) - - -- split package and version info - local splitinfo = require_str:split('%s+') - assert(splitinfo and #splitinfo > 0, "require(\"%s\"): invalid!", require_str) - - -- get package info - local packageinfo = splitinfo[1] - - -- get version - -- - -- e.g. - -- - -- latest - -- >=1.5.1 <1.6.0 - -- master || >1.4 - -- ~1.2.3 - -- ^1.1 - -- - local version = "latest" - if #splitinfo > 1 then - version = table.concat(table.slice(splitinfo, 2), " ") - end - assert(version, "require(\"%s\"): unknown version!", require_str) - - -- require third-party packages? e.g. brew::pcre2/libpcre2-8 - local reponame = nil - local packagename = nil - if require_str:find("::", 1, true) then - packagename = packageinfo - else - - -- get repository name, package name and package url - local pos = packageinfo:lastof('@', true) - if pos then - - -- get package name - packagename = packageinfo:sub(pos + 1) - - -- get reponame - reponame = packageinfo:sub(1, pos - 1) - else - packagename = packageinfo - end - end - - -- check package name - assert(packagename, "require(\"%s\"): the package name not found!", require_str) - - -- get require extra - local require_extra = {} - if requires_extra then - require_extra = requires_extra[require_str] or {} - end - - -- get required building configurations - local require_build_configs = require_extra.configs or require_extra.config - if require_extra.debug then - require_build_configs = require_build_configs or {} - require_build_configs.debug = true - end - - -- require packge in the current host platform - if require_extra.host then - require_extra.plat = os.host() - require_extra.arch = os.arch() - end - - -- init required item - local required = {} - parentinfo = parentinfo or {} - required.packagename = packagename - required.requireinfo = - { - originstr = require_str, - reponame = reponame, - version = version, - plat = require_extra.plat, -- require package in the given platform - arch = require_extra.arch, -- require package in the given architecture - targetos = require_extra.targetos, -- require package in the given target os - kind = require_extra.kind, -- default: library, set package kind, e.g. binary, library, we can set `kind = "binary"` to only detect binary program and ignore library. - alias = require_extra.alias, -- set package alias name - group = require_extra.group, -- only uses the first package in same group - system = require_extra.system, -- default: true, we can set it to disable system package manually - option = require_extra.option, -- set and attach option - configs = require_build_configs, -- the required building configurations - default = require_extra.default, -- default: true, we can set it to disable package manually - optional = parentinfo.optional or require_extra.optional, -- default: false, inherit parentinfo.optional - verify = require_extra.verify, -- default: true, we can set false to ignore sha256sum and select any version - external = require_extra.external, -- default: true, we use sysincludedirs/-isystem instead of -I/xxx - } - return required.packagename, required.requireinfo -end - --- load package package from system -function _load_package_from_system(packagename) - return core_package.load_from_system(packagename) -end - --- load package package from project -function _load_package_from_project(packagename) - return core_package.load_from_project(packagename) -end - --- load package package from repositories -function _load_package_from_repository(packagename, reponame) - local packagedir, repo = repository.packagedir(packagename, reponame) - if packagedir then - return core_package.load_from_repository(packagename, repo, packagedir) - end -end - --- search packages from repositories -function _search_packages(name) - - local packages = {} - for _, packageinfo in ipairs(repository.searchdirs(name)) do - local package = core_package.load_from_repository(packageinfo.name, packageinfo.repo, packageinfo.packagedir) - if package then - table.insert(packages, package) - end - end - return packages -end - --- sort package deps --- --- e.g. --- --- a.deps = b --- b.deps = c --- --- orderdeps: c -> b -> a --- -function _sort_packagedeps(package) - local orderdeps = {} - for _, dep in pairs(package:deps()) do - table.join2(orderdeps, _sort_packagedeps(dep)) - table.insert(orderdeps, dep) - end - return orderdeps -end - --- add some builtin configurations to package -function _add_package_configurations(package) - local toolchains - if package:is_plat("cross") and package:is_library() then - -- we only can set toolchains to library package - toolchains = project.get("target.toolchains") or get_config("toolchain") - end - local vs_runtime = project.get("target.runtimes") or get_config("vs_runtime") or "MT" - package:add("configs", "debug", {builtin = true, description = "Enable debug symbols.", default = false, type = "boolean"}) - package:add("configs", "shared", {builtin = true, description = "Enable shared library.", default = false, type = "boolean"}) - package:add("configs", "cflags", {builtin = true, description = "Set the C compiler flags."}) - package:add("configs", "cxflags", {builtin = true, description = "Set the C/C++ compiler flags."}) - package:add("configs", "cxxflags", {builtin = true, description = "Set the C++ compiler flags."}) - package:add("configs", "asflags", {builtin = true, description = "Set the assembler flags."}) - package:add("configs", "pic", {builtin = true, description = "Enable the position independent code.", default = true, type = "boolean"}) - package:add("configs", "vs_runtime", {builtin = true, description = "Set vs compiler runtime.", default = vs_runtime, values = {"MT", "MTd", "MD", "MDd"}}) - package:add("configs", "toolchains", {builtin = true, description = "Set package toolchains only for cross-compilation.", default = toolchains}) -end - --- select package version -function _select_package_version(package, requireinfo) - - -- exists urls? otherwise be phony package (only as package group) - if #package:urls() > 0 then - - -- has git url? - local has_giturl = false - for _, url in ipairs(package:urls()) do - if git.checkurl(url) then - has_giturl = true - break - end - end - - -- select package version - local source = nil - local version = nil - local require_version = requireinfo.version - local require_verify = requireinfo.verify - if (not package:get("versions") or require_verify == false) and semver.is_valid(require_version) then - -- no version list in package() or need not verify sha256sum? try selecting this version directly - -- @see https://github.com/xmake-io/xmake/issues/930 - -- https://github.com/xmake-io/xmake/issues/1009 - version = require_version - source = "versions" - elseif #package:versions() > 0 and (require_version == "latest" or require_version:find('.', 1, true)) then -- select version? - version, source = semver.select(require_version, package:versions()) - elseif has_giturl then -- select branch? - version, source = require_version ~= "latest" and require_version or "master", "branches" - else - raise("package(%s %s): not found!", package:displayname(), require_version) - end - return version, source - end -end - --- check the configurations of packages --- --- package("pcre2") --- add_configs("bitwidth", {description = "Set the code unit width.", default = "8", values = {"8", "16", "32"}}) --- add_configs("bitwidth", {type = "number", values = {8, 16, 32}}) --- add_configs("bitwidth", {restrict = function(value) if tonumber(value) < 100 then return true end}) --- -function _check_package_configurations(package) - local configs_defined = {} - for _, name in ipairs(package:get("configs")) do - configs_defined[name] = package:extraconf("configs", name) or {} - end - for name, value in pairs(package:configs()) do - local conf = configs_defined[name] - if conf then - local config_type = conf.type - if config_type ~= nil and type(value) ~= config_type then - raise("package(%s %s): invalid type(%s) for config(%s), need type(%s)!", package:displayname(), package:version_str(), type(value), name, config_type) - end - if conf.values then - local found = false - for _, config_value in ipairs(conf.values) do - if tostring(value) == tostring(config_value) then - found = true - break - end - end - if not found then - raise("package(%s %s): invalid value(%s) for config(%s), please run `xmake require --info %s` to get all valid values!", package:displayname(), package:version_str(), value, name, package:name()) - end - end - if conf.restrict then - if not conf.restrict(value) then - raise("package(%s %s): invalid value(%s) for config(%s)!", package:displayname(), package:version_str(), value, name) - end - end - else - raise("package(%s %s): invalid config(%s), please run `xmake require --info %s` to get all configurations!", package:displayname(), package:version_str(), name, package:name()) - end - end -end - --- match require path -function _match_requirepath(requirepath, requireconf) - - -- get pattern - local function _get_pattern(pattern) - pattern = pattern:gsub("([%+%.%-%^%$%(%)%%])", "%%%1") - pattern = pattern:gsub("%*%*", "\001") - pattern = pattern:gsub("%*", "\002") - pattern = pattern:gsub("\001", ".*") - pattern = pattern:gsub("\002", "[^.]*") - pattern = string.ipattern(pattern, true) - return pattern - end - - -- get the excludes - local excludes = requireconf:match("|.*$") - if excludes then excludes = excludes:split("|", {plain = true}) end - - -- do match - local pattern = requireconf:gsub("|.*$", "") - pattern = _get_pattern(pattern) - if (requirepath:match('^' .. pattern .. '$')) then - -- exclude sub-deps, e.g. "libwebp.**|cmake|autoconf" - local splitinfo = requirepath:split(".", {plain = true}) - if #splitinfo > 0 then - local name = splitinfo[#splitinfo] - for _, exclude in ipairs(excludes) do - pattern = _get_pattern(exclude) - if (name:match('^' .. pattern .. '$')) then - return false - end - end - end - return true - end -end - --- merge requireinfo from `add_requireconfs()` --- --- add_requireconfs("*", {system = false, configs = {vs_runtime = "MD"}}) --- add_requireconfs("lib*", {system = false, configs = {vs_runtime = "MD"}}) --- add_requireconfs("libwebp", {system = false, configs = {vs_runtime = "MD"}}) --- add_requireconfs("libpng.zlib", {system = false, override = true, configs = {cxflags = "-DTEST1"}, version = "1.2.10"}) --- add_requireconfs("libtiff.*", {system = false, configs = {cxflags = "-DTEST2"}}) --- add_requireconfs("libwebp.**|cmake|autoconf", {system = false, configs = {cxflags = "-DTEST3"}}) -- recursive deps --- -function _merge_requireinfo(requireinfo, requirepath) - - -- only for project - if not os.isfile(os.projectfile()) then - return - end - - -- find requireconf from the given requirepath - local requireconf_result = {} - local requireconfs, requireconfs_extra = project.requireconfs_str() - if requireconfs then - for _, requireconf in ipairs(requireconfs) do - if _match_requirepath(requirepath, requireconf) then - local requireconf_extra = requireconfs_extra[requireconf] - table.insert(requireconf_result, {requireconf = requireconf, requireconf_extra = requireconf_extra}) - end - end - end - - -- append requireconf_extra into requireinfo - -- and the configs of add_requires have a higher priority than add_requireconfs. - -- - -- e.g. - -- add_requireconfs("*", {configs = {debug = false}}) - -- add_requires("foo", "bar", {configs = {debug = true}}) - -- - -- foo and bar will be debug mode - -- - -- we can also override the configs of add_requires - -- - -- e.g. - -- add_requires("zlib 1.2.11") - -- add_requireconfs("zlib", {override = true, version = "1.2.10"}) - -- - -- we override the version of zlib to 1.2.10 - -- - if #requireconf_result == 1 then - local requireconf_extra = requireconf_result[1].requireconf_extra - if requireconf_extra then - -- preprocess requireconf_extra, (debug, override ..) - local override = requireconf_extra.override - requireconf_extra.override = nil - if requireconf_extra.debug then - requireconf_extra.configs = requireconf_extra.configs or {} - requireconf_extra.configs.debug = true - requireconf_extra.debug = nil - end - -- append or override configs and extra options - for k, v in pairs(requireconf_extra.configs) do - requireinfo.configs = requireinfo.configs or {} - if override or requireinfo.configs[k] == nil then - requireinfo.configs[k] = v - end - end - for k, v in pairs(requireconf_extra) do - if k ~= "configs" then - if override or requireinfo[k] == nil then - requireinfo[k] = v - end - end - end - end - elseif #requireconf_result > 1 then - local confs = {} - for _, item in ipairs(requireconf_result) do - table.insert(confs, item.requireconf) - end - raise("package(%s) will match multiple add_requireconfs(%s)!", requirepath, table.concat(confs, " ")) - end -end - --- get package key -function _get_packagekey(packagename, requireinfo, version) - local key = packagename .. "/" .. (version or requireinfo.version) - local configs = requireinfo.configs - if configs then - local configs_order = {} - for k, v in pairs(configs) do - table.insert(configs_order, k .. "=" .. tostring(v)) - end - table.sort(configs_order) - key = key .. ":" .. string.serialize(configs_order, true) - end - return key -end - --- inherit some builtin configs of parent package if these config values are not default value --- e.g. add_requires("libpng", {configs = {vs_runtime = "MD", pic = false}}) --- -function _inherit_parent_configs(requireinfo, parentinfo) - local requireinfo_configs = requireinfo.configs or {} - local parentinfo_configs = parentinfo.configs or {} - if not requireinfo_configs.shared then - if requireinfo_configs.vs_runtime == nil then - requireinfo_configs.vs_runtime = parentinfo_configs.vs_runtime - end - if requireinfo_configs.pic == nil then - requireinfo_configs.pic = parentinfo_configs.pic - end - end - requireinfo.configs = requireinfo_configs -end - --- load required packages -function _load_package(packagename, requireinfo, opt) - - -- strip trailng ~tag, e.g. zlib~debug - local displayname - if packagename:find('~', 1, true) then - displayname = packagename - packagename = packagename:gsub("~.+$", "") - requireinfo.alias = requireinfo.alias or displayname - end - - -- load package from project first - local package - if os.isfile(os.projectfile()) then - package = _load_package_from_project(packagename) - end - - -- load package from repositories - if not package then - package = _load_package_from_repository(packagename, requireinfo.reponame) - end - - -- load package from system - if not package and opt.system ~= false then - package = _load_package_from_system(packagename) - end - - -- check - assert(package, "package(%s) not found!", packagename) - - -- merge requireinfo from `add_requireconfs()` - _merge_requireinfo(requireinfo, opt.requirepath) - - -- inherit some builtin configs of parent package, e.g. vs_runtime, pic - if opt.parentinfo and package:is_library() then - _inherit_parent_configs(requireinfo, opt.parentinfo) - end - - -- select package version - local version, source = _select_package_version(package, requireinfo) - if version then - package:version_set(version, source) - end - - -- get package key - local packagekey = _get_packagekey(packagename, requireinfo, version) - - -- It exists conflict for dependent packages for each root packages? resolve it first - -- e.g. - -- add_requires("foo") -> bar -> zlib 1.2.10 - -- -> xyz -> zlib 1.2.11 or other configs - -- - -- add_requires("ddd") -> zlib - -- - -- We assume that there is no conflict between `foo` and `ddd`. - -- - -- Of course, conflicts caused by `add_packages("foo", "ddd")` - -- cannot be detected at present and can only be resolved by the user - -- - local rootkey = opt.rootkey - local packagekey_prev = _memcache():get3("packages_root", rootkey, packagename) - if packagekey_prev then - if packagekey_prev and packagekey_prev ~= packagekey then - raise("package(%s): conflict dependences with package(%s)!", packagekey, packagekey_prev) - end - end - _memcache():set3("packages_root", rootkey, packagename, packagekey) - - -- get package from cache first - local package_cached = _memcache():get2("packages", packagekey) - if package_cached then - return package_cached - end - - -- save require info - package:requireinfo_set(requireinfo) - - -- save display name - if not displayname then - local packageid = _memcache():get2("packageids", packagename) - displayname = packagename - if packageid then - displayname = displayname .. "#" .. tostring(packageid) - end - _memcache():set2("packageids", packagename, (packageid or 0) + 1) - end - package:displayname_set(displayname) - - -- disable parallelize if the package cache directory conflicts - local cachedirs = _memcache():get2("cachedirs", package:cachedir()) - if cachedirs then - package:set("parallelize", false) - end - _memcache():set2("cachedirs", package:cachedir(), true) - - -- disable parallelize if this package is toolchain? we need install toolchain package first - if package:is_toolchain() then - package:set("parallelize", false) - end - - -- add some builtin configurations to package - _add_package_configurations(package) - - -- check package configurations - _check_package_configurations(package) - - -- do load - local on_load = package:script("load") - if on_load then - on_load(package) - end - - -- load environments from the manifest to enable the environments of on_install() - package:envs_load() - - -- save this package package to cache - _memcache():set2("packages", packagekey, package) - return package -end - --- load all required packages -function _load_packages(requires, opt) - - -- no requires? - if not requires or #requires == 0 then - return {} - end - - -- load packages - local packages = {} - for _, requireitem in ipairs(load_requires(requires, opt.requires_extra, opt)) do - - -- load package - local rootkey = opt.rootkey or requireitem.name - local requireinfo = requireitem.info - local requirepath = opt.requirepath and (opt.requirepath .. "." .. requireitem.name) or requireitem.name - local package = _load_package(requireitem.name, requireinfo, table.join(opt, {rootkey = rootkey, requirepath = requirepath})) - - -- maybe package not found and optional - if package then - - -- load dependent packages and save them first of this package - if not package._DEPS then - local deps = package:get("deps") - if deps and opt.nodeps ~= true then - - -- load dependent packages and do not load system/3rd packages for package/deps() - local packagedeps = {} - for _, dep in ipairs(_load_packages(deps, {rootkey = rootkey, - requirepath = requirepath, - requires_extra = package:extraconf("deps") or {}, - parentinfo = requireinfo, - nodeps = opt.nodeps, - system = false})) do - dep:parents_add(package) - table.insert(packages, dep) - packagedeps[dep:name()] = dep - end - package._DEPS = packagedeps - package._ORDERDEPS = table.unique(_sort_packagedeps(package)) - end - end - - -- save this package - -- @note if this root package is toolchain, we need to move it to the beginning in order to install first - if not opt.parentinfo and package:is_toolchain() then - table.insert(packages, 1, package) - else - table.insert(packages, package) - end - end - end - return packages -end - --- sort packages urls -function _sort_packages_urls(packages) - - -- add all urls to fasturl and prepare to sort them together - for _, package in pairs(packages) do - fasturl.add(package:urls()) - end - - -- sort and update urls - for _, package in pairs(packages) do - package:urls_set(fasturl.sort(package:urls())) - end -end - --- get package parents string -function _get_package_parents_str(package) - local parents = package:parents() - if parents then - local parentnames = {} - for _, parent in pairs(parents) do - table.insert(parentnames, parent:displayname()) - end - if #parentnames == 0 then - return - end - return table.concat(parentnames, ",") - end -end - --- get package configs string -function _get_package_configs_str(package) - local configs = {} - if package:optional() then - table.insert(configs, "optional") - end - local requireinfo = package:requireinfo() - if requireinfo then - for k, v in pairs(requireinfo.configs) do - if type(v) == "boolean" then - table.insert(configs, k .. ":" .. (v and "y" or "n")) - else - table.insert(configs, k .. ":" .. v) - end - end - end - local parents_str = _get_package_parents_str(package) - if parents_str then - table.insert(configs, "from:" .. parents_str) - end - local configs_str = #configs > 0 and "[" .. table.concat(configs, ", ") .. "]" or "" - local limitwidth = os.getwinsize().width * 2 / 3 - if #configs_str > limitwidth then - configs_str = configs_str:sub(1, limitwidth) .. " ..)" - end - return configs_str -end - --- get user confirm -function _get_confirm(packages) - - -- no confirmed packages? - if #packages == 0 then - return true - end - - -- get confirm - local confirm = utils.confirm({default = true, description = function () - - -- get packages for each repositories - local packages_repo = {} - local packages_group = {} - for _, package in ipairs(packages) do - -- achive packages by repository - local reponame = package:repo() and package:repo():name() or (package:isSys() and "system" or "") - if package:is3rd() then - reponame = package:name():lower():split("::")[1] - end - packages_repo[reponame] = packages_repo[reponame] or {} - table.insert(packages_repo[reponame], package) - - -- achive packages by group - local group = package:group() - if group then - packages_group[group] = packages_group[group] or {} - table.insert(packages_group[group], package) - end - end - - -- show tips - cprint("${bright color.warning}note: ${clear}try installing these packages (pass -y to skip confirm)?") - for reponame, packages in pairs(packages_repo) do - if reponame ~= "" then - print("in %s:", reponame) - end - local packages_showed = {} - for _, package in ipairs(packages) do - if not packages_showed[tostring(package)] then - local group = package:group() - if group and packages_group[group] and #packages_group[group] > 1 then - for idx, package_in_group in ipairs(packages_group[group]) do - cprint(" ${yellow}%s${clear} %s %s ${dim}%s", idx == 1 and "->" or " or", package_in_group:displayname(), package_in_group:version_str() or "", _get_package_configs_str(package_in_group)) - packages_showed[tostring(package_in_group)] = true - end - packages_group[group] = nil - else - cprint(" ${yellow}->${clear} %s %s ${dim}%s", package:displayname(), package:version_str() or "", _get_package_configs_str(package)) - packages_showed[tostring(package)] = true - end - end - end - end - end}) - return confirm -end - --- install packages -function _install_packages(packages_install, packages_download) - - -- we need hide wait characters if is not a tty - local show_wait = io.isatty() - - -- do install - local progress_helper = show_wait and progress.new() or nil - local packages_installing = {} - local packages_downloading = {} - local packages_pending = table.copy(packages_install) - local packages_in_group = {} - local installing_count = 0 - local parallelize = true - runjobs("install_packages", function (index) - - -- fetch a new package - local package = nil - while package == nil and #packages_pending > 0 do - for idx, pkg in ipairs(packages_pending) do - - -- all dependences has been installed? we install it now - local ready = true - local dep_not_found = nil - for _, dep in ipairs(pkg:orderdeps()) do - if not dep:exists() then - ready = false - dep_not_found = dep - break - end - end - local group = pkg:group() - if ready and group then - -- this group has been installed? skip it - local group_status = packages_in_group[group] - if group_status == 1 then - table.remove(packages_pending, idx) - break - -- this group is installing? wait it - elseif group_status == 0 then - ready = false - end - end - - -- get a package with the ready status - if ready then - package = pkg - table.remove(packages_pending, idx) - break - elseif installing_count == 0 then - if #packages_pending == 1 and dep_not_found then - raise("package(%s): cannot be installed, there are dependencies(%s) that cannot be installed!", pkg:displayname(), dep_not_found:displayname()) - elseif #packages_pending == 1 then - raise("package(%s): cannot be installed!", pkg:displayname()) - end - end - end - if package == nil and #packages_pending > 0 then - scheduler.co_yield() - end - end - if package then - - -- only install the first package in same group - local group = package:group() - if not group or not packages_in_group[group] then - - -- disable parallelize? - if not package:parallelize() then - parallelize = false - end - if not parallelize then - while installing_count > 0 do - scheduler.co_yield() - end - end - installing_count = installing_count + 1 - - -- mark this group as 'installing' - if group then - packages_in_group[group] = 0 - end - - -- download this package first - local downloaded = true - if packages_download[tostring(package)] then - packages_downloading[index] = package - downloaded = action_download(package) - packages_downloading[index] = nil - end - - -- install this package - packages_installing[index] = package - if downloaded then - action_install(package) - end - packages_installing[index] = nil - - -- mark this group as 'installed' or 'failed' - if group then - packages_in_group[group] = package:exists() and 1 or -1 - end - - -- enable parallelize - parallelize = true - installing_count = installing_count - 1 - end - end - packages_installing[index] = nil - packages_downloading[index] = nil - - end, {total = #packages_install, comax = (option.get("verbose") or option.get("diagnosis")) and 1 or 4, on_timer = function (running_jobs_indices) - - -- do not print progress info if be verbose - if option.get("verbose") or not show_wait then - return - end - - -- make installing and downloading packages list - local installing = {} - local downloading = {} - for _, index in ipairs(running_jobs_indices) do - local package = packages_installing[index] - if package then - table.insert(installing, package:displayname()) - end - local package = packages_downloading[index] - if package then - table.insert(downloading, package:displayname()) - end - end - - -- get waitobjs tips - local tips = nil - local waitobjs = scheduler.co_group_waitobjs("install_packages") - if waitobjs:size() > 0 then - local names = {} - for _, obj in waitobjs:keys() do - if obj:otype() == scheduler.OT_PROC then - table.insert(names, obj:name()) - elseif obj:otype() == scheduler.OT_SOCK then - table.insert(names, "sock") - elseif obj:otype() == scheduler.OT_PIPE then - table.insert(names, "pipe") - end - end - names = table.unique(names) - if #names > 0 then - names = table.concat(names, ",") - if #names > 16 then - names = names:sub(1, 16) .. ".." - end - tips = string.format("(%d/%s)", waitobjs:size(), names) - end - end - - -- trace - progress_helper:clear() - tty.erase_line_to_start().cr() - cprintf("${yellow} => ") - if #downloading > 0 then - cprintf("downloading ${magenta}%s", table.concat(downloading, ", ")) - end - if #installing > 0 then - cprintf("%sinstalling ${magenta}%s", #downloading > 0 and ", " or "", table.concat(installing, ", ")) - end - cprintf(" .. %s", tips and ("${dim}" .. tips .. "${clear} ") or "") - progress_helper:write() - end, exit = function(errors) - if errors then - tty.erase_line_to_start().cr() - io.flush() - end - end}) -end - --- the cache directory -function cachedir() - return path.join(global.directory(), "cache", "packages") -end - --- load requires -function load_requires(requires, requires_extra, opt) - opt = opt or {} - local requireitems = {} - for _, require_str in ipairs(requires) do - local packagename, requireinfo = _parse_require(require_str, requires_extra, opt.parentinfo) - table.insert(requireitems, {name = packagename, info = requireinfo}) - end - return requireitems -end - --- load all required packages -function load_packages(requires, opt) - opt = opt or {} - local unique = {} - local packages = {} - for _, package in ipairs(_load_packages(requires, opt)) do - local key = _get_packagekey(package:name(), package:requireinfo()) - if not unique[key] then - table.insert(packages, package) - unique[key] = true - end - end - return packages -end - --- install packages -function install_packages(requires, opt) - - -- init options - opt = opt or {} - - -- load packages - local packages = load_packages(requires, opt) - - -- fetch packages (with system) from local first - runjobs("fetch_packages", function (index) - local package = packages[index] - if package and (not option.get("force") or (option.get("shallow") and package:parents())) then - package:envs_enter() - package:fetch() - package:envs_leave() - end - end, {total = #packages}) - - -- filter packages - local packages_install = {} - local packages_download = {} - local packages_unsupported = {} - for _, package in ipairs(packages) do - if not package:exists() then - if package:supported() then - if #package:urls() > 0 then - packages_download[tostring(package)] = package - end - table.insert(packages_install, package) - elseif not package:optional() then - table.insert(packages_unsupported, package) - end - end - end - - -- exists unsupported packages? - if #packages_unsupported > 0 then - -- show tips - cprint("${bright color.warning}note: ${clear}the following packages are unsupported for $(plat)/$(arch)!") - for _, package in ipairs(packages_unsupported) do - print(" -> %s %s", package:displayname(), package:version_str() or "") - end - raise() - end - - -- get user confirm - if not _get_confirm(packages_install) then - local packages_must = {} - for _, package in ipairs(packages_install) do - if not package:optional() then - table.insert(packages_must, package:displayname()) - end - end - if #packages_must > 0 then - raise("packages(%s): must be installed!", table.concat(packages_must, ", ")) - else - -- continue other actions - return - end - end - - -- sort package urls - _sort_packages_urls(packages_download) - - -- install all required packages from repositories - _install_packages(packages_install, packages_download) - - -- ok - return packages -end - --- uninstall packages -function uninstall_packages(requires, opt) - - -- init options - opt = opt or {} - - -- do not remove dependent packages - opt.nodeps = true - - -- clear the local cache - localcache.clear() - - -- remove all packages - local packages = {} - for _, instance in ipairs(load_packages(requires, opt)) do - if os.isfile(instance:manifest_file()) then - table.insert(packages, instance) - end - os.tryrm(instance:installdir()) - end - return packages -end - --- export packages -function export_packages(requires, opt) - - -- init options - opt = opt or {} - - -- get the export directory - local exportdir = assert(opt.exportdir) - - -- export all packages - local packages = {} - for _, instance in ipairs(load_packages(requires, opt)) do - - -- get the exported name - local name = instance:name():lower():gsub("::", "_") - if instance:version_str() then - name = name .. "_" .. instance:version_str() - end - name = name .. "_" .. instance:buildhash() - - -- export this package - if instance:fetch() then - os.cp(instance:installdir(), path.join(exportdir, name)) - table.insert(packages, instance) - end - end - return packages -end - --- search packages -function search_packages(names) - - -- search all names - local results = {} - for _, name in ipairs(names) do - local packages = _search_packages(name) - if packages then - results[name] = packages - end - end - return results -end diff --git a/xmake/modules/private/package/impl/packagenv.lua b/xmake/modules/private/package/impl/packagenv.lua deleted file mode 100644 index 232670b54..000000000 --- a/xmake/modules/private/package/impl/packagenv.lua +++ /dev/null @@ -1,81 +0,0 @@ ---!A cross-platform build utility based on Lua --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. --- --- Copyright (C) 2015-present, TBOOX Open Source Group. --- --- @author ruki --- @file packagenv.lua --- - --- imports -import("core.package.package", {alias = "core_package"}) - --- enter the package environments -function _enter_package(package_name, envs, installdir) - - -- save the old environments - _g._OLDENVS = _g._OLDENVS or {} - local oldenvs = _g._OLDENVS[package_name] - if not oldenvs then - oldenvs = {} - _g._OLDENVS[package_name] = oldenvs - end - - -- add the new environments - for name, values in pairs(envs) do - oldenvs[name] = oldenvs[name] or os.getenv(name) - if name == "PATH" or name == "LD_LIBRARY_PATH" or name == "DYLD_LIBRARY_PATH" then - for _, value in ipairs(values) do - if path.is_absolute(value) then - os.addenv(name, value) - else - os.addenv(name, path.join(installdir, value)) - end - end - else - os.addenv(name, unpack(table.wrap(values))) - end - end -end - --- leave the package environments -function _leave_package(package_name) - _g._OLDENVS = _g._OLDENVS or {} - local oldenvs = _g._OLDENVS[package_name] - if oldenvs then - for name, values in pairs(oldenvs) do - os.setenv(name, values) - end - _g._OLDENVS[package_name] = nil - end -end - --- enter environment of the given binary packages, git, 7z, .. -function enter(...) - for _, name in ipairs({...}) do - for _, manifest_file in ipairs(os.files(path.join(core_package.installdir(), name:sub(1, 1), name, "*", "*", "manifest.txt"))) do - local manifest = io.load(manifest_file) - if manifest and manifest.plat == os.host() and manifest.arch == os.arch() then - _enter_package(name, manifest.envs, path.directory(manifest_file)) - end - end - end -end - --- leave environment of the given binary packages, git, 7z, .. -function leave(...) - for _, name in ipairs({...}) do - _leave_package(name) - end -end diff --git a/xmake/modules/private/package/impl/repository.lua b/xmake/modules/private/package/impl/repository.lua deleted file mode 100644 index 6fc72d49c..000000000 --- a/xmake/modules/private/package/impl/repository.lua +++ /dev/null @@ -1,113 +0,0 @@ ---!A cross-platform build utility based on Lua --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. --- --- Copyright (C) 2015-present, TBOOX Open Source Group. --- --- @author ruki --- @file repository.lua --- - --- imports -import("core.package.repository") - --- get all repositories -function repositories() - - -- get it from cache it - if _g._REPOSITORIES then - return _g._REPOSITORIES - end - - -- get all repositories (local first) - local repos = table.join(repository.repositories(false), repository.repositories(true)) - - -- save repositories - _g._REPOSITORIES = repos - - -- ok - return repos -end - --- the remote repositories have been pulled? -function pulled() - for _, repo in ipairs(repositories()) do - -- repository not found? or xmake has been re-installed - local updatefile = path.join(repo:directory(), "updated") - if not os.isdir(repo:directory()) or (os.isfile(updatefile) and os.mtime(os.programfile()) > os.mtime(updatefile)) then - return false - end - end - return true -end - --- get package directory from repositories -function packagedir(packagename, reponame) - - -- strip trailng ~tag, e.g. zlib~debug - if packagename:find('~', 1, true) then - packagename = packagename:gsub("~.+$", "") - end - - -- get it from cache it - local packagedirs = _g._PACKAGEDIRS or {} - local foundir = packagedirs[packagename] - if foundir then - return foundir[1], foundir[2] - end - - -- find the package directory from repositories - for _, repo in ipairs(repositories()) do - local dir = path.join(repo:directory(), "packages", packagename:sub(1, 1):lower(), packagename) - if os.isdir(dir) and (not reponame or reponame == repo:name()) then - foundir = {dir, repo} - break - end - end - - -- found? - if foundir then - - -- save package directory - packagedirs[packagename] = foundir - - -- update cache - _g._PACKAGEDIRS = packagedirs - - -- ok - return foundir[1], foundir[2] - end -end - --- search package directories from repositories -function searchdirs(name) - - -- find the package directories from all repositories - local unique = {} - local packageinfos = {} - for _, repo in ipairs(repositories()) do - for _, file in ipairs(os.files(path.join(repo:directory(), "packages", "*", string.ipattern("*" .. name .. "*"), "xmake.lua"))) do - local dir = path.directory(file) - local subdirname = path.basename(path.directory(dir)) - if #subdirname == 1 then -- ignore l/luajit/port/xmake.lua - local packagename = path.basename(dir) - if not unique[packagename] then - table.insert(packageinfos, {name = packagename, repo = repo, packagedir = path.directory(file)}) - unique[packagename] = true - end - end - end - end - return packageinfos -end - diff --git a/xmake/modules/private/package/impl/utils/filter.lua b/xmake/modules/private/package/impl/utils/filter.lua deleted file mode 100644 index 11bdf70de..000000000 --- a/xmake/modules/private/package/impl/utils/filter.lua +++ /dev/null @@ -1,146 +0,0 @@ ---!A cross-platform build utility based on Lua --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. --- --- Copyright (C) 2015-present, TBOOX Open Source Group. --- --- @author ruki --- @file filter.lua --- - --- imports -import("core.base.filter") -import("core.base.option") -import("core.base.global") -import("core.project.config") -import("core.project.project") -import("core.sandbox.sandbox") - --- get filter -function _filter() - - -- init filter - if _g.filter == nil then - _g.filter = filter.new() - _g.filter:register("common", function (variable) - - -- attempt to get it directly from the configure - local result = config.get(variable) - if result == nil then - - -- init maps - _g.common_maps = _g.common_maps or - { - host = os.host() - , subhost = os.subhost() - , tmpdir = function () return os.tmpdir() end - , curdir = function () return os.curdir() end - , scriptdir = function () return os.scriptdir() end - , globaldir = global.directory() - , configdir = config.directory() - , projectdir = project.directory() - , programdir = os.programdir() - } - - -- map it - result = _g.common_maps[variable] - end - - -- is script? call it - if type(result) == "function" then - result = result() - end - - -- ok? - return result - end) - end - - -- ok - return _g.filter -end - --- the package handler -function _handler(package, strval) - - -- @note cannot cache it, because the package instance will be changed - return function (variable) - - -- init maps - local maps = - { - version = function () - if strval then - -- set_urls("https://sqlite.org/2018/sqlite-autoconf-$(version)000.tar.gz", - -- {version = function (version) return version:gsub("%.", "") end}) - local version_filter = package:url_version(strval) - if version_filter then - local v = version_filter(package:version()) - if v ~= nil then - -- may be semver version object - v = tostring(v) - end - return v - end - end - return package:version_str() - end - } - - -- get value - local result = maps[variable] - if type(result) == "function" then - result = result() - end - - -- ok? - return result - end -end - --- attach filter to the given script and call it -function call(script, package) - - -- get sandbox filter and handlers of the given script - local sandbox_filter = sandbox.filter(script) - local sandbox_handlers = sandbox_filter:handlers() - - -- switch to the handlers of the current filter - sandbox_filter:set_handlers(_filter():handlers()) - - -- register package handler - sandbox_filter:register("package", _handler(package)) - - -- call it - script(package) - - -- restore handlers - sandbox_filter:set_handlers(sandbox_handlers) -end - --- handle the string value of package -function handle(strval, package) - - -- register filter handler - _filter():register("package", _handler(package, strval)) - - -- handle string value - strval = _filter():handle(strval) - - -- register filter handler - _filter():register("package", nil) - - -- ok - return strval -end - diff --git a/xmake/modules/private/package/impl/utils/get_requires.lua b/xmake/modules/private/package/impl/utils/get_requires.lua deleted file mode 100644 index 9a0ad76c1..000000000 --- a/xmake/modules/private/package/impl/utils/get_requires.lua +++ /dev/null @@ -1,57 +0,0 @@ ---!A cross-platform build utility based on Lua --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. --- --- Copyright (C) 2015-present, TBOOX Open Source Group. --- --- @author ruki --- @file get_requires.lua --- - --- imports -import("core.base.option") -import("core.project.project") - --- get requires and extra config -function main(requires) - - -- init requires - local requires_extra = nil - if not requires then - requires, requires_extra = project.requires_str() - end - if not requires or #requires == 0 then - return - end - - -- get extra info - local extra = option.get("extra") - local extrainfo = nil - if extra then - local v, err = string.deserialize(extra) - if err then - raise(err) - else - extrainfo = v - end - end - - -- force to use the given requires extra info - if extrainfo then - requires_extra = requires_extra or {} - for _, require_str in ipairs(requires) do - requires_extra[require_str] = extrainfo - end - end - return requires, requires_extra -end diff --git a/xmake/modules/private/package/impl/utils/url_filename.lua b/xmake/modules/private/package/impl/utils/url_filename.lua deleted file mode 100644 index e17e8673b..000000000 --- a/xmake/modules/private/package/impl/utils/url_filename.lua +++ /dev/null @@ -1,25 +0,0 @@ ---!A cross-platform build utility based on Lua --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. --- --- Copyright (C) 2015-present, TBOOX Open Source Group. --- --- @author ruki --- @file url_filename.lua --- - --- get filename from url -function main(url) - local urlpath = url:split('?', {plain = true})[1] - return path.filename(urlpath) -end diff --git a/xmake/modules/private/package/info.lua b/xmake/modules/private/package/info.lua deleted file mode 100644 index 847e0e989..000000000 --- a/xmake/modules/private/package/info.lua +++ /dev/null @@ -1,261 +0,0 @@ ---!A cross-platform build utility based on Lua --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. --- --- Copyright (C) 2015-present, TBOOX Open Source Group. --- --- @author ruki --- @file info.lua --- - --- imports -import("core.base.task") -import("core.base.option") -import("core.base.hashset") -import("core.project.project") -import("core.package.package", {alias = "core_package"}) -import("devel.git") -import("utils.archive") -import("private.package.impl.utils.filter") -import("private.package.impl.utils.url_filename") -import("private.package.impl.package") -import("private.package.impl.repository") -import("private.package.impl.environment") -import("private.package.impl.utils.get_requires") - --- from xmake/system/remote? -function _from(instance) - local fetchinfo = instance:fetch() - if fetchinfo then - if instance:is3rd() then - return ", ${green}3rd${clear}" - elseif instance:isSys() then - return ", ${green}system${clear}" - else - return "" - end - elseif #instance:urls() > 0 then - local repo = instance:repo() - local reponame = repo and repo:name() or "unknown" - return instance:supported() and format(", ${yellow}remote${clear}(in %s)", reponame) or format(", ${yellow}remote${clear}(${red}unsupported${clear} in %s)", reponame) - elseif instance:isSys() then - return ", ${red}missing${clear}" - else - return "" - end -end - --- get package info -function _info(instance) - local info = instance:version_str() and instance:version_str() or "no version" - info = info .. _from(instance) - info = info .. (instance:optional() and ", ${yellow}optional${clear}" or "") - return info -end - --- show the given package info -function main(requires_raw) - - -- get requires and extra config - local requires_extra = nil - local requires, requires_extra = get_requires(requires_raw) - if not requires or #requires == 0 then - return - end - - -- enter environment - environment.enter() - - -- pull all repositories first if not exists - if not repository.pulled() then - task.run("repo", {update = true}) - end - - -- show title - print("The package info of project:") - - -- list all packages - for _, instance in ipairs(package.load_packages(requires, {requires_extra = requires_extra})) do - - -- show package name - local requireinfo = instance:requireinfo() or {} - cprint(" ${magenta}require${clear}(%s): ", requireinfo.originstr) - - -- show description - local description = instance:get("description") - if description then - cprint(" -> ${magenta}description${clear}: %s", description) - end - - -- show version - local version = instance:version_str() - if version then - cprint(" -> ${magenta}version${clear}: %s", version) - end - - -- show license - local license = instance:get("license") - if license then - cprint(" -> ${magenta}license${clear}: %s", license) - end - - -- show urls - local urls = instance:urls() - if urls and #urls > 0 then - cprint(" -> ${magenta}urls${clear}:") - for _, url in ipairs(urls) do - print(" -> %s", filter.handle(url, instance)) - if git.asgiturl(url) then - local url_alias = instance:url_alias(url) - cprint(" -> ${yellow}%s", instance:revision(url_alias) or instance:tag() or instance:version_str()) - else - local sourcehash = instance:sourcehash(instance:url_alias(url)) - if sourcehash then - cprint(" -> ${yellow}%s", sourcehash) - end - end - end - end - - -- show repository - local repo = instance:repo() - if repo then - cprint(" -> ${magenta}repo${clear}: %s %s %s", repo:name(), repo:url(), repo:branch() or "") - end - - -- show deps - local deps = instance:orderdeps() - if deps and #deps > 0 then - cprint(" -> ${magenta}deps${clear}:") - for _, dep in ipairs(deps) do - requireinfo = dep:requireinfo() or {} - cprint(" -> %s", requireinfo.originstr) - end - end - - -- show cache directory - cprint(" -> ${magenta}cachedir${clear}: %s", instance:cachedir()) - - -- show install directory - cprint(" -> ${magenta}installdir${clear}: %s", instance:installdir()) - - -- show search directories and search names - cprint(" -> ${magenta}searchdirs${clear}: %s", table.concat(table.wrap(core_package.searchdirs()), path.envsep())) - local searchnames = hashset.new() - for _, url in ipairs(instance:urls()) do - url = filter.handle(url, instance) - searchnames:insert(url_filename(url)) - end - cprint(" -> ${magenta}searchnames${clear}: %s", table.concat(searchnames:to_array(), ", ")) - - -- show fetch info - cprint(" -> ${magenta}fetchinfo${clear}: %s", _info(instance)) - local fetchinfo = instance:fetch() - if fetchinfo then - for name, info in pairs(fetchinfo) do - if type(info) ~= "table" then - info = tostring(info) - end - cprint(" -> ${magenta}%s${clear}: %s", name, table.concat(table.wrap(info), " ")) - end - end - - -- show supported platforms - local platforms = {} - local on_install = instance:get("install") - if type(on_install) == "table" then - for plat, _ in pairs(on_install) do - table.insert(platforms, plat) - end - else - table.insert(platforms, "all") - end - cprint(" -> ${magenta}platforms${clear}: %s", table.concat(platforms, ", ")) - - -- show requires - cprint(" -> ${magenta}requires${clear}:") - cprint(" -> ${cyan}plat${clear}: %s", instance:plat()) - cprint(" -> ${cyan}arch${clear}: %s", instance:arch()) - local configs_required = instance:configs() - if configs_required then - cprint(" -> ${cyan}configs${clear}:") - for name, value in pairs(configs_required) do - cprint(" -> %s: %s", name, value) - end - end - - -- show user configs - local configs_defined = instance:get("configs") - if configs_defined then - cprint(" -> ${magenta}configs${clear}:") - for _, conf in ipairs(configs_defined) do - local configs_extra = instance:extraconf("configs", conf) - if configs_extra and not configs_extra.builtin then - cprintf(" -> ${cyan}%s${clear}: ", conf) - if configs_extra.description then - printf(configs_extra.description) - end - if configs_extra.default ~= nil then - printf(" (default: %s)", configs_extra.default) - elseif configs_extra.type ~= nil and configs_extra.type ~= "string" then - printf(" (type: %s)", configs_extra.type) - end - print("") - if configs_extra.values then - cprint(" -> values: %s", string.serialize(configs_extra.values, true)) - end - end - end - end - - -- show builtin configs - local configs_defined = instance:get("configs") - if configs_defined then - cprint(" -> ${magenta}configs (builtin)${clear}:") - for _, conf in ipairs(configs_defined) do - local configs_extra = instance:extraconf("configs", conf) - if configs_extra and configs_extra.builtin then - cprintf(" -> ${cyan}%s${clear}: ", conf) - if configs_extra.description then - printf(configs_extra.description) - end - if configs_extra.default ~= nil then - printf(" (default: %s)", configs_extra.default) - elseif configs_extra.type ~= nil and configs_extra.type ~= "string" then - printf(" (type: %s)", configs_extra.type) - end - print("") - if configs_extra.values then - cprint(" -> values: %s", string.serialize(configs_extra.values, true)) - end - end - end - end - - -- show references - local references = instance:references() - if references then - cprint(" -> ${magenta}references${clear}:") - for projectdir, refdate in pairs(references) do - cprint(" -> %s: %s%s", refdate, projectdir, os.isdir(projectdir) and "" or " ${red}(not found)${clear}") - end - end - - -- end - print("") - end - - -- leave environment - environment.leave() -end - diff --git a/xmake/modules/private/package/install.lua b/xmake/modules/private/package/install.lua deleted file mode 100644 index 4850f0c08..000000000 --- a/xmake/modules/private/package/install.lua +++ /dev/null @@ -1,209 +0,0 @@ ---!A cross-platform build utility based on Lua --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. --- --- Copyright (C) 2015-present, TBOOX Open Source Group. --- --- @author ruki --- @file package.lua --- - --- imports -import("core.base.option") -import("core.base.task") -import("core.project.project") -import("lib.detect.find_tool") -import("private.package.impl.package") -import("private.package.impl.repository") -import("private.package.impl.environment") -import("private.package.impl.utils.get_requires") - --- register required package environments --- envs: bin path for *.dll, program .. -function _register_required_package_envs(instance, envs) - for name, values in pairs(instance:envs()) do - if name == "PATH" or name == "LD_LIBRARY_PATH" or name == "DYLD_LIBRARY_PATH" then - for _, value in ipairs(values) do - envs[name] = envs[name] or {} - if path.is_absolute(value) then - table.insert(envs[name], value) - else - table.insert(envs[name], path.join(instance:installdir(), value)) - end - end - else - envs[name] = envs[name] or {} - table.join2(envs[name], values) - end - end -end - --- register required package libraries --- libs: includedirs, links, linkdirs ... -function _register_required_package_libs(instance, required_package, is_deps) - if instance:is_library() then - local fetchinfo = instance:fetch() - if fetchinfo then - fetchinfo.name = nil - if is_deps then - -- we need only reserve license for root package - -- - -- @note the license compatibility between the root package and - -- its dependent packages is guaranteed by the root package itself - -- - fetchinfo.license = nil - - -- we need only some infos for root package - fetchinfo.version = nil - fetchinfo.static = nil - fetchinfo.shared = nil - end - required_package:add(fetchinfo) - end - end -end - --- register the base info of required package -function _register_required_package_base(instance, required_package) - if not instance:isSys() and not instance:is3rd() then - required_package:set("__installdir", instance:installdir()) - end -end - --- register the required local package -function _register_required_package(instance, required_package) - - -- disable it if this package is optional and missing - if _g.optional_missing[instance:name()] then - required_package:enable(false) - else - -- clear require info first - required_package:clear() - - -- add packages info with all dependencies - local envs = {} - _register_required_package_base(instance, required_package) - _register_required_package_libs(instance, required_package) - _register_required_package_envs(instance, envs) - local orderdeps = instance:orderdeps() - if orderdeps then - local total = #orderdeps - for idx, _ in ipairs(orderdeps) do - local dep = orderdeps[total + 1 - idx] - if dep then - _register_required_package_libs(dep, required_package, true) - _register_required_package_envs(dep, envs) - end - end - end - if #table.keys(envs) > 0 then - required_package:add({envs = envs}) - end - - -- enable this require info - required_package:enable(true) - end - - -- save this require info and flush the whole cache file - required_package:save() -end - --- register all required local packages -function _register_required_packages(packages) - local registered_in_group = {} - for _, instance in ipairs(packages) do - - -- only register the first package in same group and root packages - local group = instance:group() - if not instance:parents() and (not group or not registered_in_group[group]) then - - -- register required package - local required_package = project.required_package(instance:alias() or instance:name()) - if required_package then - _register_required_package(instance, required_package) - end - - -- mark as registered in group - if group then - registered_in_group[group] = true - end - end - end -end - --- check missing packages -function _check_missing_packages(packages) - - -- get all missing packages - local packages_missing = {} - local optional_missing = {} - for _, instance in ipairs(packages) do - if not instance:exists() and (#instance:urls() > 0 or instance:isSys()) then - if instance:optional() then - optional_missing[instance:name()] = instance - else - table.insert(packages_missing, instance:name()) - end - end - end - - -- raise tips - if #packages_missing > 0 then - local cmd = "xmake repo -u" - if os.getenv("XREPO_WORKING") then - cmd = "xrepo update-repo" - end - raise("The packages(%s) not found, please run `%s` first!", table.concat(packages_missing, ", "), cmd) - end - - -- save the optional missing packages - _g.optional_missing = optional_missing -end - --- install packages -function main(requires_raw) - - -- get requires and extra config - local requires_extra = nil - local requires, requires_extra = get_requires(requires_raw) - if not requires or #requires == 0 then - return - end - - -- find git - environment.enter() - local git = find_tool("git") - environment.leave() - - -- pull all repositories first if not exists - -- - -- attempt to install git from the builtin-packages first if git not found - -- - if git and not repository.pulled() then - task.run("repo", {update = true}) - end - - -- install packages - environment.enter() - local packages = package.install_packages(requires, {requires_extra = requires_extra}) - if packages then - - -- check missing packages - _check_missing_packages(packages) - - -- register all required local packages - _register_required_packages(packages) - end - environment.leave() -end - diff --git a/xmake/modules/private/package/list.lua b/xmake/modules/private/package/list.lua deleted file mode 100644 index f9983fe04..000000000 --- a/xmake/modules/private/package/list.lua +++ /dev/null @@ -1,93 +0,0 @@ ---!A cross-platform build utility based on Lua --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. --- --- Copyright (C) 2015-present, TBOOX Open Source Group. --- --- @author ruki --- @file list.lua --- - --- imports -import("core.project.project") -import("core.base.task") -import("private.package.impl.package") -import("private.package.impl.repository") -import("private.package.impl.environment") - --- from xmake/system/remote? -function _from(instance) - local fetchinfo = instance:fetch() - if fetchinfo then - if instance:is3rd() then - return ", ${green}3rd${clear}" - elseif instance:isSys() then - return ", ${green}system${clear}" - else - return "" - end - elseif #instance:urls() > 0 then - return instance:supported() and format(", ${yellow}remote${clear}(in %s)", instance:repo():name()) or format(", ${yellow}remote${clear}(${red}unsupported${clear} in %s)", instance:repo():name()) - elseif instance:isSys() then - return ", ${red}missing${clear}" - else - return "" - end -end - --- get package info -function _info(instance) - local info = instance:version_str() and instance:version_str() or "no version" - info = info .. _from(instance) - info = info .. (instance:optional() and ", ${yellow}optional${clear}" or "") - return info -end - --- list packages -function main() - - -- list all requires - print("The package dependencies of project:") - - -- get requires - local requires, requires_extra = project.requires_str() - if not requires or #requires == 0 then - return - end - - -- enter environment - environment.enter() - - -- pull all repositories first if not exists - if not repository.pulled() then - task.run("repo", {update = true}) - end - - -- list all required packages - for _, instance in ipairs(package.load_packages(requires, {requires_extra = requires_extra})) do - cprint(" ${magenta}require${clear}(%s): %s", instance:requireinfo().originstr, _info(instance)) - for _, dep in ipairs(instance:orderdeps()) do - cprint(" -> ${magenta}dep${clear}(%s): %s", dep:requireinfo().originstr, _info(dep)) - end - local fetchinfo = instance:fetch() - if fetchinfo then - for name, info in pairs(fetchinfo) do - cprint(" -> ${magenta}%s${clear}: %s", name, table.concat(table.wrap(info), " ")) - end - end - end - - -- leave environment - environment.leave() -end - diff --git a/xmake/modules/private/package/scan.lua b/xmake/modules/private/package/scan.lua deleted file mode 100644 index a038f9f9e..000000000 --- a/xmake/modules/private/package/scan.lua +++ /dev/null @@ -1,90 +0,0 @@ ---!A cross-platform build utility based on Lua --- --- Licensed to the Apache Software Foundation (ASF) under one --- or more contributor license agreements. See the NOTICE file --- distributed with this work for additional scanrmation --- regarding copyright ownership. The ASF licenses this file --- to you 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 scan.lua --- - --- imports -import("core.base.option") -import("core.package.package") - --- scan local package -function _scan_package(packagedir) - - -- show packages - local package_name = path.filename(packagedir) - for _, versiondir in ipairs(os.dirs(path.join(packagedir, "*"))) do - local version = path.filename(versiondir) - cprint("${magenta}%s-%s${clear}:", package_name, version) - - -- show package hash - for _, hashdir in ipairs(os.dirs(path.join(versiondir, "*"))) do - local hash = path.filename(hashdir) - local references_file = path.join(hashdir, "references.txt") - local referenced = false - local references = os.isfile(references_file) and io.load(references_file) or nil - if references then - for projectdir, refdate in pairs(references) do - if os.isdir(projectdir) then - referenced = true - break - end - end - end - local manifest_file = path.join(hashdir, "manifest.txt") - local manifest = os.isfile(manifest_file) and io.load(manifest_file) or nil - cprintf(" -> ${yellow}%s${clear}: ${green}%s, %s", hash, manifest and manifest.plat or "", manifest and manifest.arch or "") - if os.emptydir(hashdir) then - cprintf(", ${red}empty") - elseif not referenced then - cprintf(", ${red}unused") - elseif not manifest then - cprintf(", ${red}invalid") - end - print("") - if manifest and manifest.configs then - print(" -> %s", string.serialize(manifest.configs, true)) - end - end - end -end - --- scan local packages -function main(package_names) - - -- trace - print("scanning packages ..") - - -- scan packages - local installdir = package.installdir() - if package_names then - for _, package_name in ipairs(package_names) do - for _, packagedir in ipairs(os.dirs(path.join(installdir, package_name:sub(1, 1), package_name))) do - _scan_package(packagedir) - end - end - else - for _, packagedir in ipairs(os.dirs(path.join(installdir, "*", "*"))) do - _scan_package(packagedir) - end - end -end - diff --git a/xmake/modules/private/package/search.lua b/xmake/modules/private/package/search.lua deleted file mode 100644 index 0c4a8b91f..000000000 --- a/xmake/modules/private/package/search.lua +++ /dev/null @@ -1,65 +0,0 @@ ---!A cross-platform build utility based on Lua --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. --- --- Copyright (C) 2015-present, TBOOX Open Source Group. --- --- @author ruki --- @file search.lua --- - --- imports -import("core.base.task") -import("private.package.impl.utils.filter") -import("private.package.impl.package") -import("private.package.impl.repository") -import("private.package.impl.environment") - --- search the given packages -function main(names) - - -- no names? - if not names then - return - end - - -- enter environment - environment.enter() - - -- pull all repositories first if not exists - if not repository.pulled() then - task.run("repo", {update = true}) - end - - -- show title - print("The package names:") - - -- search packages - for name, packages in pairs(package.search_packages(names)) do - if #packages > 0 then - - -- show name - print(" %s: ", name) - - -- show packages - for _, instance in ipairs(packages) do - local repo = instance:repo() - cprint(" -> ${magenta}%s${clear}: %s %s", instance:name(), instance:get("description") or "", repo and ("(in " .. repo:name() .. ")") or "") - end - end - end - - -- leave environment - environment.leave() -end - diff --git a/xmake/modules/private/package/uninstall.lua b/xmake/modules/private/package/uninstall.lua deleted file mode 100644 index 49c23a3cd..000000000 --- a/xmake/modules/private/package/uninstall.lua +++ /dev/null @@ -1,68 +0,0 @@ ---!A cross-platform build utility based on Lua --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. --- --- Copyright (C) 2015-present, TBOOX Open Source Group. --- --- @author ruki --- @file uninstall.lua --- - --- imports -import("core.base.task") -import("core.base.option") -import("private.package.impl.package") -import("private.package.impl.repository") -import("private.package.impl.environment") -import("private.package.impl.utils.get_requires") - --- uninstall the given packages -function main(requires_raw) - - -- enter environment - environment.enter() - - -- pull all repositories first if not exists - if not repository.pulled() then - task.run("repo", {update = true}) - end - - -- get requires and extra config - local requires_extra = nil - local requires, requires_extra = get_requires(requires_raw) - if not requires or #requires == 0 then - return - end - - -- uninstall packages - local packages = package.uninstall_packages(requires, {requires_extra = requires_extra}) - for _, instance in ipairs(packages) do - print("uninstall: %s%s ok!", instance:name(), instance:version_str() and ("-" .. instance:version_str()) or "") - end - if not packages or #packages == 0 then - cprint("${bright}packages(%s) not found, maybe they don’t exactly match the configuration.", table.concat(requires_raw, ", ")) - if os.getenv("XREPO_WORKING") then - print("please attempt to remove them with `-f/--configs=` option, e.g.") - print(" - xrepo remove -f \"name=value, ...\" package") - print(" - xrepo remove -m debug -k shared -f \"name=value, ...\" package") - else - print("please attempt to uninstall them with `--extra=` option, e.g.") - print(" - xmake require --uninstall --extra=\"{configs={...}}\" package") - print(" - xmake require --uninstall --extra=\"{debug=true,configs={shared=true}}\" package") - end - end - - -- leave environment - environment.leave() -end - -- cgit v1.3.1