diff options
Diffstat (limited to 'xmake/core/project/addons.lua')
| -rw-r--r-- | xmake/core/project/addons.lua | 228 |
1 files changed, 228 insertions, 0 deletions
diff --git a/xmake/core/project/addons.lua b/xmake/core/project/addons.lua new file mode 100644 index 000000000..169b7d033 --- /dev/null +++ b/xmake/core/project/addons.lua @@ -0,0 +1,228 @@ +--!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, Xmake Open Source Community. +-- +-- @author ruki +-- @file addons.lua +-- + +-- define module +local addons = addons or {} + +-- load modules +local os = require("base/os") +local io = require("base/io") +local path = require("base/path") +local table = require("base/table") +local semver = require("base/semver") +local addon = require("package/addon") + +-- the file which declares the addons of a project, e.g. <projectdir>/xmake-addons.lua +-- +-- it's loaded before the project file, so that the addons are always installed when +-- we load the project, e.g. includes("@addon/esp32-devel/board") +-- +-- e.g. +-- add_addons("esp32-devel 1.0.x", "serial-tools") +-- add_repositories("myrepo [email protected]:me/myrepo.git") +-- +function addons.filename() + return "xmake-addons.lua" +end + +function addons.file(projectdir) + return path.join(projectdir or os.projectdir(), addons.filename()) +end + +-- the lock file of the declared addons, e.g. <projectdir>/xmake-addons.lock +-- +-- @note it's independent of `xmake-requires.lock`, the addons are always locked, +-- they provide the build scripts and we should never change them silently +-- +function addons.lockfile(projectdir) + return path.join(projectdir or os.projectdir(), "xmake-addons.lock") +end + +-- get the format version of the addons lock file +-- +-- @see xmake/core/project/project.lua, project.requireslock_version() +-- +function addons.lockfile_version() + return "1.0" +end + +-- get the apis of the addons file +-- +-- @note it only declares which addons this project needs, the addon resources +-- are always referenced from the project file, e.g. add_rules("@addon/esp32-devel/app") +-- +function addons.apis() + return { + values = { + "add_addons" + -- the repositories which provide them, e.g. add_repositories("myrepo [email protected]:me/myrepo.git") + , "add_repositories" + } + } +end + +-- the interpreter of the addons file +function addons._interpreter() + local interp = addons._INTERPRETER + if interp == nil then + -- we need to load it lazily, the interpreter also depends on the addon module + local interpreter = require("base/interpreter") + interp = interpreter.new() + interp:api_define(addons.apis()) + -- the sub-projects declare their addons in this file too, + -- e.g. includes("sub") -> sub/xmake-addons.lua + interp:includes_rootfilename_set(addons.filename()) + -- and we cannot reference the addons here, they have not been installed yet, + -- e.g. includes("@addon/esp32-devel/board") + interp:includes_references_set(false, "please move it to the project file(xmake.lua)!") + addons._INTERPRETER = interp + end + return interp +end + +-- load the declared addons of the given project directory +-- +-- @return the addons information and errors, it will be nil if this project declares nothing, +-- e.g. {addons = {"esp32-devel 1.0.x"}, addons_extra = {...}} +-- +function addons.load(projectdir) + local filepath = addons.file(projectdir) + if not os.isfile(filepath) then + return + end + + -- enter the project directory, the include paths are relative to it, + -- e.g. includes("sub") + local oldir, errors = os.cd(path.directory(filepath)) + if not oldir then + return nil, errors + end + + local rootinfo + local interp = addons._interpreter() + local ok, errors = interp:load(filepath) + if ok then + rootinfo, errors = interp:make("root", true, true) + end + os.cd(oldir) + if not rootinfo then + return nil, errors + end + + local addonsinfo = {addons = table.wrap(rootinfo:get("addons")), + addons_extra = rootinfo:extraconf("addons"), + repositories = table.wrap(rootinfo:get("repositories"))} + + -- check the declared addons + local declared = {} + for _, requirestr in ipairs(addonsinfo.addons) do + + -- this file is loaded before the addons are installed, so it cannot reference them + if requirestr:startswith("@") then + return nil, string.format("%s: cannot reference the addon resources(%s) here, please move it to the project file(xmake.lua)!", + filepath, requirestr) + end + + local name = addons.requirename(requirestr) + if name == "addon" or name == "self" then + return nil, string.format("%s: the addon name(%s) is reserved by xmake for the addon references, please rename it!", + filepath, name) + end + if name == "." or name == ".." or name:find("[/\\:]") then + return nil, string.format("%s: invalid addon name(%s)!", filepath, name) + end + + -- we can only install one version of an addon for a project + if declared[name] then + return nil, string.format("%s: the addon(%s) is declared twice, e.g. `%s` and `%s`, please merge them!", + filepath, name, declared[name], requirestr) + end + declared[name] = requirestr + end + return addonsinfo +end + +-- split the given declaration into the name and the version range +-- +-- @note the version range can contain spaces, so everything after the name belongs to it, +-- e.g. "esp32-devel 1.0.x", "esp32-devel >=1.0 <2.0", "esp32-devel master || >1.4", +-- @see xmake/modules/private/utils/package.lua +-- +function addons.requirename(requirestr) + local splitinfo = requirestr:split("%s+", {limit = 2}) + return splitinfo[1], splitinfo[2] +end + +-- is the locked version still valid for the given declaration? +-- +-- @note the declaration is authoritative, the lock only pins a version inside it, +-- so we need to resolve it again if the user has changed the declared version +-- +function addons.locked_valid(requirestr, lockinfo) + if not lockinfo or not lockinfo.version then + return false + end + local _, range = addons.requirename(requirestr) + if range then + -- @note we can only compare the semantic versions, e.g. the local addons are always `latest` + return semver.is_valid(lockinfo.version) and semver.satisfies(lockinfo.version, range) + end + return true +end + +-- get the locked addons, e.g. {["esp32-devel"] = {version = "1.0.3"}} +function addons.locked(projectdir) + local lockfile = addons.lockfile(projectdir) + if os.isfile(lockfile) then + local lockinfo = io.load(lockfile) + if lockinfo then + lockinfo.__meta__ = nil + return lockinfo + end + end +end + +-- are all the declared addons installed already? +-- +-- @note we need to check it in-process for every command which loads the project, +-- so we only check the locked versions here, the installer will resolve them again +-- +function addons.satisfied(addonsinfo, projectdir) + local locked = addons.locked(projectdir) + if not locked then + return false + end + local installed = addon.addons() + for _, requirestr in ipairs(addonsinfo.addons) do + local name = addons.requirename(requirestr) + local lockinfo = locked[name] + if not addons.locked_valid(requirestr, lockinfo) then + return false + end + local addoninfo = installed[addon.dirname(name)] + if not addoninfo or addoninfo.version ~= lockinfo.version then + return false + end + end + return true +end + +-- return module +return addons |
