summaryrefslogtreecommitdiff
path: root/xmake/modules/private/action/require
diff options
context:
space:
mode:
authorruki <[email protected]>2021-02-12 23:00:24 +0800
committerruki <[email protected]>2021-02-12 23:00:24 +0800
commit5cd7e9ab5bccd3bf469eb5da49f70085a9753696 (patch)
treee2be745258683c23af1aa578d7314a246159dccb /xmake/modules/private/action/require
parentcc7678dfae7d945b34b8abc70f18e26b8045fd9e (diff)
move require action modules
Diffstat (limited to 'xmake/modules/private/action/require')
-rw-r--r--xmake/modules/private/action/require/clean.lua103
-rw-r--r--xmake/modules/private/action/require/export.lua71
-rw-r--r--xmake/modules/private/action/require/fetch.lua94
-rw-r--r--xmake/modules/private/action/require/impl/actions/download.lua268
-rw-r--r--xmake/modules/private/action/require/impl/actions/download_resources.lua96
-rw-r--r--xmake/modules/private/action/require/impl/actions/install.lua273
-rw-r--r--xmake/modules/private/action/require/impl/actions/patch_sources.lua112
-rw-r--r--xmake/modules/private/action/require/impl/actions/test.lua59
-rw-r--r--xmake/modules/private/action/require/impl/environment.lua73
-rw-r--r--xmake/modules/private/action/require/impl/package.lua1106
-rw-r--r--xmake/modules/private/action/require/impl/packagenv.lua81
-rw-r--r--xmake/modules/private/action/require/impl/repository.lua113
-rw-r--r--xmake/modules/private/action/require/impl/utils/filter.lua146
-rw-r--r--xmake/modules/private/action/require/impl/utils/get_requires.lua57
-rw-r--r--xmake/modules/private/action/require/impl/utils/url_filename.lua25
-rw-r--r--xmake/modules/private/action/require/info.lua261
-rw-r--r--xmake/modules/private/action/require/install.lua209
-rw-r--r--xmake/modules/private/action/require/list.lua93
-rw-r--r--xmake/modules/private/action/require/scan.lua90
-rw-r--r--xmake/modules/private/action/require/search.lua65
-rw-r--r--xmake/modules/private/action/require/uninstall.lua68
21 files changed, 3463 insertions, 0 deletions
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
+