summaryrefslogtreecommitdiff
path: root/xmake/actions/require/impl
diff options
context:
space:
mode:
authorruki <[email protected]>2018-09-30 11:32:17 +0800
committerruki <[email protected]>2018-09-30 11:32:17 +0800
commitd003fe0a57e65fe71a2853bd6a33aa9d1c42636c (patch)
tree72da8d1420f02ed84b67e7cb6dfe1fd16e4b3c36 /xmake/actions/require/impl
parentaa8567aac171d30261ecd0ec925773d7c170d86f (diff)
modify require impl modules
Diffstat (limited to 'xmake/actions/require/impl')
-rw-r--r--xmake/actions/require/impl/action/download.lua224
-rw-r--r--xmake/actions/require/impl/action/install.lua244
-rw-r--r--xmake/actions/require/impl/action/test.lua63
-rw-r--r--xmake/actions/require/impl/environment.lua53
-rw-r--r--xmake/actions/require/impl/package.lua544
-rw-r--r--xmake/actions/require/impl/repository.lua108
-rw-r--r--xmake/actions/require/impl/utils/filter.lua144
7 files changed, 1380 insertions, 0 deletions
diff --git a/xmake/actions/require/impl/action/download.lua b/xmake/actions/require/impl/action/download.lua
new file mode 100644
index 000000000..6fc0d5f3c
--- /dev/null
+++ b/xmake/actions/require/impl/action/download.lua
@@ -0,0 +1,224 @@
+--!The Make-like download 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 information
+-- 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 - 2018, TBOOX Open Source Group.
+--
+-- @author ruki
+-- @file download.lua
+--
+
+-- imports
+import("core.base.option")
+import(".utils.filter")
+import("net.http")
+import("devel.git")
+import("utils.archive")
+
+-- empty chars
+function _emptychars()
+
+ -- get left width
+ local width = os.getwinsize()["width"] or 64
+
+ -- make empty chars
+ local emptychars = ""
+ for i = 1, width do
+ emptychars = emptychars .. " "
+ end
+ return emptychars
+end
+
+-- checkout codes from git
+function _checkout(package, url, sourcedir)
+
+ -- use previous source directory if exists
+ local packagedir = path.join(sourcedir, package:name())
+ if os.isdir(packagedir) and not option.get("force") then
+
+ -- clean the previous build files
+ git.clean({repodir = packagedir, force = true})
+ return
+ end
+
+ -- remove temporary directory
+ os.rm(sourcedir .. ".tmp")
+
+ -- download package from branches?
+ packagedir = path.join(sourcedir .. ".tmp", package:name())
+ if package:version_from("branches") then
+
+ -- only shadow clone this branch
+ git.clone(url, {depth = 1, branch = package:version_str(), outputdir = packagedir})
+
+ -- download package from tags or versions?
+ else
+
+ -- clone whole history and tags
+ git.clone(url, {outputdir = packagedir})
+
+ -- attempt to checkout the given version
+ git.checkout(package:version_str(), {repodir = packagedir})
+ end
+
+ -- move to source directory
+ os.rm(sourcedir)
+ os.mv(sourcedir .. ".tmp", sourcedir)
+
+ -- trace
+ printf("\r" .. _emptychars())
+ cprint("\r${yellow} => ${clear}clone %s %s .. ${green}ok", 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 = path.filename(url)
+
+ -- get sha256
+ local sha256 = package:sha256(url_alias)
+ assert(sha256, "cannot get sha256 of %s in package(%s)", url, package:name())
+
+ -- the package file have been downloaded?
+ if option.get("force") or not os.isfile(packagefile) or sha256 ~= hash.sha256(packagefile) then
+
+ -- attempt to remove package file first
+ os.rm(packagefile)
+
+ -- download package file
+ http.download(url, packagefile)
+
+ -- check hash
+ if sha256 and sha256 ~= 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)
+ end
+
+ -- save original file path
+ package:originfile_set(path.absolute(packagefile))
+
+ -- trace
+ printf("\r" .. _emptychars())
+ cprint("\r${yellow} => ${clear}download %s .. ${green}ok", url)
+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 package:sha256(package:url_alias(url)) then
+ table.insert(urls[2], url)
+ end
+ end
+ if package:version_from("tags", "branches") 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)
+
+ -- get urls
+ local urls = _urls(package)
+ assert(#urls > 0, "cannot get url of package(%s)", package:name())
+
+ -- download package from urls
+ 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
+ local ok = try
+ {
+ function ()
+
+ -- download package
+ local sourcedir = "source"
+ if git.checkurl(url) then
+ _checkout(package, url, sourcedir)
+ else
+ _download(package, url, sourcedir, url_alias, url_excludes)
+ end
+
+ -- ok
+ return true
+ end,
+ catch
+ {
+ function (errors)
+
+ -- verbose?
+ if option.get("verbose") and errors then
+ cprint("${bright red}error: ${clear}%s", errors)
+ end
+
+ -- trace
+ printf("\r" .. _emptychars())
+ if git.checkurl(url) then
+ cprint("\r${yellow} => ${clear}clone %s %s .. ${red}failed", url, package:version_str())
+ else
+ cprint("\r${yellow} => ${clear}download %s .. ${red}failed", url)
+ end
+
+ -- failed? break it
+ if idx == #urls and not package:requireinfo().optional then
+ raise("download failed!")
+ end
+ end
+ }
+ }
+
+ -- ok? break it
+ if ok then break end
+ end
+
+ -- leave working directory
+ os.cd(oldir)
+end
+
+
diff --git a/xmake/actions/require/impl/action/install.lua b/xmake/actions/require/impl/action/install.lua
new file mode 100644
index 000000000..5beb0e8f1
--- /dev/null
+++ b/xmake/actions/require/impl/action/install.lua
@@ -0,0 +1,244 @@
+--!The Make-like install 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 information
+-- 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 - 2018, TBOOX Open Source Group.
+--
+-- @author ruki
+-- @file install.lua
+--
+
+-- imports
+import("core.base.option")
+import("core.project.target")
+import("test")
+import(".utils.filter")
+
+-- uninstall package from the prefix directory
+function uninstall_prefix(package)
+
+ -- remove the previous installed files
+ local prefixdir = package:prefixdir()
+ for _, relativefile in ipairs(package:prefixinfo().installed) do
+
+ -- trace
+ vprint("removing %s ..", relativefile)
+
+ -- remove file
+ local prefixfile = path.absolute(relativefile, prefixdir)
+ os.tryrm(prefixfile)
+
+ -- remove it if the parent directory is empty
+ local parentdir = path.directory(prefixfile)
+ while parentdir and os.isdir(parentdir) and os.emptydir(parentdir) do
+ os.tryrm(parentdir)
+ parentdir = path.directory(parentdir)
+ end
+ end
+
+ -- unregister this package
+ package:unregister()
+
+ -- remove the prefix file
+ os.tryrm(package:prefixfile())
+end
+
+-- install package to the prefix directory
+function install_prefix(package)
+
+ -- uninstall the prefix package files first
+ uninstall_prefix(package)
+
+ -- get prefix and install directory
+ local prefixdir = package:prefixdir()
+ local installdir = package:installdir()
+
+ -- scan all installed files
+ local installfiles = {}
+ if is_host("windows") then
+ if package:kind() == "binary" then
+ table.join2(installfiles, (os.files(path.join(installdir, "**"))))
+ else
+ table.join2(installfiles, (os.files(path.join(package:installdir("lib"), "**"))))
+ table.join2(installfiles, (os.files(path.join(package:installdir("include"), "**"))))
+ end
+ else
+ if package:kind() == "binary" then
+ table.join2(installfiles, (os.files(path.join(package:installdir("bin"), "*"))))
+ else
+ table.join2(installfiles, (os.files(path.join(package:installdir("lib"), "**.a"))))
+ table.join2(installfiles, (os.files(path.join(package:installdir("lib"), is_plat("macosx") and "**.dylib" or "**.so"))))
+ table.join2(installfiles, (os.files(path.join(package:installdir("lib", "pkgconfig"), "**.pc"))))
+ table.join2(installfiles, (os.filedirs(path.join(package:installdir("include"), "*"))))
+ end
+ end
+
+ -- trace
+ vprint("installing %s to %s ..", installdir, prefixdir)
+
+ -- install to the prefix directory
+ local relativefiles = {}
+ try
+ {
+ function ()
+ for _, installfile in ipairs(installfiles) do
+
+ -- get relative file
+ local relativefile = path.relative(installfile, installdir)
+
+ -- trace
+ vprint("installing %s ..", relativefile)
+
+ -- install file
+ if is_host("windows") then
+ -- copy the whole file to the prefix directory
+ os.cp(installfile, path.absolute(relativefile, prefixdir))
+ else
+ -- only link file to the prefix directory
+ os.ln(installfile, path.absolute(relativefile, prefixdir))
+ end
+
+ -- save this relative file
+ table.insert(relativefiles, relativefile)
+ end
+ end,
+ catch
+ {
+ function (errors)
+ raise(errors)
+ end
+ },
+ finally
+ {
+ function ()
+ -- save the prefix info to file
+ local prefixinfo = package:prefixinfo()
+ prefixinfo.installed = relativefiles
+ io.save(package:prefixfile(), prefixinfo)
+
+ -- register this package
+ package:register()
+ end
+ }
+ }
+end
+
+-- install the given package
+function main(package)
+
+ -- get working directory of this package
+ local workdir = package:cachedir()
+
+ -- 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
+
+ -- trace
+ cprintf("${yellow} => ${clear}installing %s .. ", tipname)
+ if option.get("verbose") then
+ print("")
+ end
+
+ -- install it
+ try
+ {
+ function ()
+
+ -- the package scripts
+ local scripts =
+ {
+ package:script("install_before")
+ , package:script("install")
+ , package:script("install_after")
+ }
+
+ -- create the install task
+ local installtask = function ()
+
+ -- clean the install directory first
+ os.tryrm(package:installdir())
+
+ -- install it
+ for i = 1, 3 do
+ local script = scripts[i]
+ if script ~= nil then
+ filter.call(script, package)
+ end
+ end
+
+ -- install to the prefix directory
+ install_prefix(package)
+
+ -- test it
+ test(package)
+ end
+
+ -- install package
+ if option.get("verbose") then
+ installtask()
+ else
+ process.asyncrun(installtask)
+ end
+
+ -- fetch package and force to flush the cache
+ assert(package:fetch({force = true}), "fetch %s failed!", tipname)
+
+ -- trace
+ cprint("${green}ok")
+ end,
+
+ catch
+ {
+ function (errors)
+
+ -- verbose?
+ if option.get("verbose") and errors then
+ cprint("${bright red}error: ${clear}%s", errors)
+ end
+
+ -- trace
+ cprint("${red}failed")
+
+ -- failed
+ if not package:requireinfo().optional then
+ raise("install failed!")
+ end
+ end
+ }
+ }
+
+ -- leave source codes directory
+ os.cd(oldir)
+end
diff --git a/xmake/actions/require/impl/action/test.lua b/xmake/actions/require/impl/action/test.lua
new file mode 100644
index 000000000..8fa9caa55
--- /dev/null
+++ b/xmake/actions/require/impl/action/test.lua
@@ -0,0 +1,63 @@
+--!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 information
+-- 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 - 2018, TBOOX Open Source Group.
+--
+-- @author ruki
+-- @file test.lua
+--
+
+-- imports
+import("core.base.option")
+import(".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 "lastest")
+ 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/actions/require/impl/environment.lua b/xmake/actions/require/impl/environment.lua
new file mode 100644
index 000000000..f340ab7eb
--- /dev/null
+++ b/xmake/actions/require/impl/environment.lua
@@ -0,0 +1,53 @@
+--!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 information
+-- 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 - 2018, TBOOX Open Source Group.
+--
+-- @author ruki
+-- @file environment.lua
+--
+
+-- imports
+import("core.project.config")
+import("core.platform.environment")
+import("lib.detect.find_tool")
+import("package")
+
+-- enter environment
+--
+-- ensure that we can find some basic tools: git, make/nmake/cmake, msbuild ...
+--
+-- If these tools not exist, we will install it first.
+--
+function enter()
+
+ -- set search pathes of toolchains
+ environment.enter("toolchains")
+
+ -- git not found? install it first
+ if not find_tool("git") then
+ package.install_packages("git")
+ end
+end
+
+-- leave environment
+function leave()
+
+ -- restore search pathes of toolchains
+ environment.leave("toolchains")
+end
diff --git a/xmake/actions/require/impl/package.lua b/xmake/actions/require/impl/package.lua
new file mode 100644
index 000000000..fc67ea232
--- /dev/null
+++ b/xmake/actions/require/impl/package.lua
@@ -0,0 +1,544 @@
+--!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 information
+-- 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 - 2018, TBOOX Open Source Group.
+--
+-- @author ruki
+-- @file package.lua
+--
+
+-- imports
+import("core.base.semver")
+import("core.base.option")
+import("core.base.global")
+import("core.project.cache")
+import("lib.detect.cache", {alias = "detectcache"})
+import("core.project.project")
+import("core.package.package", {alias = "core_package"})
+import("action")
+import("devel.git")
+import("net.fasturl")
+import("repository")
+
+--
+-- parse require string
+--
+-- add_requires("zlib")
+-- add_requires("tbox >=1.5.1", "zlib >=1.2.11")
+-- add_requires("zlib master")
+-- add_requires("xmake-repo@tbox >=1.5.1")
+-- 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"}})
+--
+-- {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)
+
+ -- get it from cache first
+ local requires = _g._REQUIRES or {}
+ local required = requires[require_str]
+ if required then
+ return required.packagename, required.requireinfo
+ end
+
+ -- 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
+ --
+ -- lastest
+ -- >=1.5.1 <1.6.0
+ -- master || >1.4
+ -- ~1.2.3
+ -- ^1.1
+ --
+ local version = "lastest"
+ if #splitinfo > 1 then
+ version = table.concat(table.slice(splitinfo, 2), " ")
+ end
+ assert(version, "require(\"%s\"): unknown version!", require_str)
+
+ -- get repository name, package name and package url
+ local reponame = nil
+ local packagename = nil
+ local pos = packageinfo:find_last('@', true)
+ if pos then
+
+ -- get package name
+ packagename = packageinfo:sub(pos + 1)
+
+ -- get reponame
+ reponame = packageinfo:sub(1, pos - 1)
+ else
+ packagename = packageinfo
+ 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
+
+ -- init required item
+ local required = {}
+ parentinfo = parentinfo or {}
+ required.packagename = packagename
+ required.requireinfo =
+ {
+ originstr = require_str,
+ reponame = reponame,
+ version = version,
+ alias = require_extra.alias, -- set package alias name
+ debug = require_extra.debug, -- uses the debug package, default: false
+ system = require_extra.system, -- default: true, we can set it to disable system package manually
+ option = require_extra.option, -- set and attach option
+ config = require_extra.config, -- the build configuration of package
+ 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
+ }
+
+ -- save this required item to cache
+ requires[require_str] = required
+ _g._REQUIRES = requires
+
+ -- ok
+ 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)
+
+ -- get package directory from the given package name
+ local packagedir, repo = repository.packagedir(packagename, reponame)
+ if packagedir then
+ -- load it
+ return core_package.load_from_repository(packagename, repo, packagedir)
+ end
+end
+
+-- load required packages
+function _load_package(packagename, requireinfo)
+
+ -- attempt to get it from cache first
+ local packages = _g._PACKAGES or {}
+ local package = packages[packagename]
+ if package then
+
+ -- satisfy required version?
+ local version_str = package:version_str()
+ if version_str and not semver.satisfies(version_str, requireinfo.version) then
+ raise("package(%s): version conflict, '%s' does not satisfy '%s'!", packagename, version_str, requireinfo.version)
+ end
+
+ -- ok
+ return package
+ end
+
+ -- load package from project first
+ package = _load_package_from_project(packagename)
+
+ -- load package from repositories
+ if not package then
+ package = _load_package_from_repository(packagename, requireinfo.reponame)
+ end
+
+ -- load package from system
+ if not package then
+ package = _load_package_from_system(packagename)
+ end
+
+ -- check
+ assert(package, "package(%s) not found!", packagename)
+
+ -- save require info to package
+ package:requireinfo_set(requireinfo)
+
+ -- save this package package to cache
+ packages[packagename] = package
+ _g._PACKAGES = packages
+
+ -- ok
+ return package
+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
+
+-- 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 packagename, requireinfo in pairs(load_requires(requires, opt.requires_extra, opt.parentinfo)) do
+
+ -- attempt to get project option about this package
+ local packageopt = project.option(packagename)
+ if packageopt == nil or packageopt:enabled() then -- this package is enabled?
+
+ -- load package package
+ local package = _load_package(packagename, requireinfo)
+
+ -- maybe package not found and optional
+ if package then
+
+ -- load dependent packages and save them first of this package
+ local deps = package:get("deps")
+ if deps and opt.nodeps ~= true then
+ local packagedeps = {}
+ for _, dep in ipairs(_load_packages(deps, {requires_extra = package:get("__extra_deps"), parentinfo = requireinfo, nodeps = opt.nodeps})) do
+ table.insert(packages, dep)
+ packagedeps[dep:name()] = dep
+ end
+ package._DEPS = packagedeps
+ package._ORDERDEPS = table.unique(_sort_packagedeps(package))
+ end
+
+ -- save this package package
+ table.insert(packages, package)
+ end
+ end
+ end
+
+ -- ok?
+ 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 ipairs(packages) do
+ fasturl.add(package:urls())
+ end
+
+ -- sort and update urls
+ for _, package in ipairs(packages) do
+ package:urls_set(fasturl.sort(package:urls()))
+ end
+end
+
+-- select packages version
+function _select_packages_version(packages)
+
+ -- sort and update urls
+ for _, package in ipairs(packages) do
+
+ -- 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 = package:requireinfo().version
+ if #package:versions() > 0 and (require_version == "lastest" 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 ~= "lastest" and require_version or "master", "branches"
+ else
+ raise("package(%s %s): not found!", package:name(), require_version)
+ end
+
+ -- save version to package
+ package:version_set(version, source)
+ end
+ end
+end
+
+-- get user confirm
+function _get_confirm(packages)
+
+ -- no confirmed packages?
+ if #packages == 0 then
+ return true
+ end
+
+ -- get confirm
+ local confirm = option.get("yes")
+ if confirm == nil then
+
+ -- show tips
+ cprint("${bright yellow}note: ${default yellow}try installing these packages (pass -y to skip confirm)?")
+ for _, package in ipairs(packages) do
+ print(" -> %s %s", package:name(), package:version_str() or "")
+ end
+ cprint("please input: y (y/n)")
+
+ -- get answer
+ io.flush()
+ local answer = io.read()
+ if answer == 'y' or answer == '' then
+ confirm = true
+ end
+ end
+
+ -- ok?
+ return confirm
+end
+
+-- the cache directory
+function cachedir()
+ return path.join(global.directory(), "cache", "packages")
+end
+
+-- load requires
+function load_requires(requires, requires_extra, parentinfo)
+
+ -- parse requires
+ local requireinfos = {}
+ for _, require_str in ipairs(requires) do
+
+ -- parse require info
+ local packagename, requireinfo = _parse_require(require_str, requires_extra, parentinfo)
+
+ -- save this required package
+ requireinfos[packagename] = requireinfo
+ end
+
+ -- ok
+ return requireinfos
+end
+
+-- load all required packages
+function load_packages(requires, opt)
+
+ -- init options
+ opt = opt or {}
+
+ -- laod all required packages recursively
+ local packages = _load_packages(requires, opt)
+
+ -- select packages version
+ _select_packages_version(packages)
+
+ -- remove repeat packages with same the package name and version
+ local unique = {}
+ local results = {}
+ for _, package in ipairs(packages) do
+ local key = package:name() .. (package:version_str() or "")
+ if not unique[key] then
+ table.insert(results, package)
+ unique[key] = true
+ end
+ end
+ return results
+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
+ if not option.get("force") then
+ process.runjobs(function (index)
+ local package = packages[index]
+ if package then
+ package:fetch()
+ end
+ end, #packages)
+ end
+
+ -- filter packages
+ local packages_install = {}
+ local packages_download = {}
+ local packages_unsupported = {}
+ for _, package in ipairs(packages) do
+ if (option.get("force") or not package:exists()) and (#package:urls() > 0 or package:script("install")) then
+ if package:supported() then
+ if #package:urls() > 0 then
+ table.insert(packages_download, 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 red}note: ${default red}the following packages are unsupported for $(plat)/$(arch)!")
+ for _, package in ipairs(packages_unsupported) do
+ print(" -> %s %s", package:name(), package:version_str() or "")
+ end
+ raise()
+ end
+
+ -- get user confirm
+ if not _get_confirm(packages_install) then
+ return
+ end
+
+ -- sort package urls
+ _sort_packages_urls(packages_download)
+
+ -- download remote packages
+ local waitindex = 0
+ local waitchars = {'\\', '|', '/', '-'}
+ process.runjobs(function (index)
+
+ local package = packages_download[index]
+ if package then
+ action.download(package)
+ end
+
+ end, #packages_download, ifelse(option.get("verbose"), 1, 4), 300, function (indices)
+
+ -- do not print progress info if be verbose
+ if option.get("verbose") then
+ return
+ end
+
+ -- update waitchar index
+ waitindex = ((waitindex + 1) % #waitchars)
+
+ -- make downloading packages list
+ local downloading = {}
+ for _, index in ipairs(indices) do
+ local package = packages_download[index]
+ if package then
+ table.insert(downloading, package:name())
+ end
+ end
+
+ -- trace
+ cprintf("\r${yellow} => ${clear}downloading %s .. %s", table.concat(downloading, ", "), waitchars[waitindex + 1])
+ io.flush()
+ end)
+
+ -- install all required packages from repositories
+ for _, package in ipairs(packages_install) do
+ action.install(package)
+ end
+
+ -- ok
+ return packages
+end
+
+-- remove packages
+function remove_packages(requires, opt)
+
+ -- init options
+ opt = opt or {}
+
+ -- do not remove dependent packages
+ opt.nodeps = true
+
+ -- clear the detect cache
+ detectcache.clear()
+
+ -- remove all packages
+ local packages = {}
+ for _, instance in ipairs(load_packages(requires, opt)) do
+ if os.isfile(instance:prefixfile()) then
+
+ -- uninstall package from the prefix directory
+ action.install.uninstall_prefix(instance)
+
+ -- remove the install files
+ os.tryrm(instance:installdir())
+
+ -- remove ok
+ 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/actions/require/impl/repository.lua b/xmake/actions/require/impl/repository.lua
new file mode 100644
index 000000000..f9fa4d3cb
--- /dev/null
+++ b/xmake/actions/require/impl/repository.lua
@@ -0,0 +1,108 @@
+--!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 information
+-- 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 - 2018, 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
+ if not os.isdir(repo:directory()) then
+ return false
+ end
+ end
+ return true
+end
+
+-- get package directory from repositories
+function packagedir(packagename, reponame)
+
+ -- 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 packagename = path.basename(path.directory(file))
+ if not unique[packagename] then
+ table.insert(packageinfos, {name = packagename, repo = repo, packagedir = path.directory(file)})
+ unique[packagename] = true
+ end
+ end
+ end
+
+ -- ok?
+ return packageinfos
+end
+
diff --git a/xmake/actions/require/impl/utils/filter.lua b/xmake/actions/require/impl/utils/filter.lua
new file mode 100644
index 000000000..209c703ba
--- /dev/null
+++ b/xmake/actions/require/impl/utils/filter.lua
@@ -0,0 +1,144 @@
+--!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 information
+-- 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 - 2018, 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()
+ , 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
+ return version_filter(package:version_str())
+ 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
+