summaryrefslogtreecommitdiff
path: root/xmake/actions/require/impl/package.lua
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/package.lua
parentaa8567aac171d30261ecd0ec925773d7c170d86f (diff)
modify require impl modules
Diffstat (limited to 'xmake/actions/require/impl/package.lua')
-rw-r--r--xmake/actions/require/impl/package.lua544
1 files changed, 544 insertions, 0 deletions
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