--!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 select_script.lua -- -- load modules local table = require("base/table") local utils = require("base/utils") local hashset = require("base/hashset") -- match pattern, matched mode: plat|arch, excluded mode: !plat|arch function _match_pattern(pattern, plat, arch, opt) opt = opt or {} local excluded = opt.excluded local subhost = opt.subhost or os.subhost() local subarch = opt.subarch or os.subarch() local splitinfo = pattern:split("|", {strict = true, plain = true}) local pattern_plat = splitinfo[1] local pattern_arch = splitinfo[2] if pattern_plat and #pattern_plat > 0 then local matched = false local is_excluded_pattern = pattern_plat:find('!', 1, true) if excluded and is_excluded_pattern then matched = not ('!' .. plat):match('^' .. pattern_plat .. '$') elseif not is_excluded_pattern then matched = plat:match('^' .. pattern_plat .. '$') end if not matched then return false end end if pattern_arch and #pattern_arch > 0 then -- support native arch, e.g. macosx|native -- @see https://github.com/xmake-io/xmake/issues/4657 pattern_arch = pattern_arch:gsub("native", subarch) local matched = false local is_excluded_pattern = pattern_arch:find('!', 1, true) if excluded and is_excluded_pattern then matched = not ('!' .. arch):match('^' .. pattern_arch .. '$') elseif not is_excluded_pattern then matched = arch:match('^' .. pattern_arch .. '$') end if not matched then return false end end if not pattern_plat and not pattern_arch then os.raise("invalid script pattern: %s", pattern) end return true end -- match patterns function _match_patterns(patterns, plat, arch, opt) for _, pattern in ipairs(patterns) do if _match_pattern(pattern, plat, arch, opt) then return true end end end -- mattch the script pattern -- -- @note interpreter has converted pattern to a lua pattern ('*' => '.*') -- -- matched pattern: -- plat|arch@subhost|subarch -- -- e.g. -- -- `@linux` -- `@linux|x86_64` -- `@macosx,linux` -- `android@macosx,linux` -- `android|armeabi-v7a@macosx,linux` -- `android|armeabi-v7a,iphoneos@macosx,linux|x86_64` -- `android|armeabi-v7a@linux|x86_64` -- `linux|*` -- -- excluded pattern: -- !plat|!arch@!subhost|!subarch -- -- e.g. -- -- `@!linux` -- `@!linux|x86_64` -- `@!macosx,!linux` -- `!android@macosx,!linux` -- `android|!armeabi-v7a@macosx,!linux` -- `android|armeabi-v7a,!iphoneos@macosx,!linux|x86_64` -- `!android|armeabi-v7a@!linux|!x86_64` -- `!linux|*` -- function _match_script_pattern(pattern, opt) opt = opt or {} local splitinfo = pattern:split("@", {strict = true, plain = true}) local plat_part = splitinfo[1] local host_part = splitinfo[2] local plat_patterns if plat_part and #plat_part > 0 then plat_patterns = plat_part:split(",", {plain = true}) end local host_patterns if host_part and #host_part > 0 then host_patterns = host_part:split(",", {plain = true}) end local plat = opt.plat or "" local arch = opt.arch or "" local subhost = opt.subhost or os.subhost() local subarch = opt.subarch or os.subarch() if plat_patterns and #plat_patterns > 0 then if _match_patterns(plat_patterns, plat, arch, opt) then if host_patterns and #host_patterns > 0 and not _match_patterns(host_patterns, subhost, subarch, opt) then return false end return true end else if host_patterns and #host_patterns > 0 then return _match_patterns(host_patterns, subhost, subarch, opt) end end end -- match the script expression pattern -- -- e.g. -- !wasm|!arm* and !cross|!arm* -- wasm|!arm* or cross -- (!macosx and !iphoneos) or (!linux|!arm* and !cross|!arm*) -- function _match_script(pattern, opt) local idx = 0 local funcs = {} local keywords = hashset.of("and", "or") local has_logical_op = false local pattern_expr = pattern:gsub("[^%(%)%s]+", function (w) if keywords:has(w) then has_logical_op = true return end local name = "func_" .. idx local func = function () return _match_script_pattern(w, opt) end funcs[name] = func idx = idx + 1 return name .. "()" end) if has_logical_op then local script = assert(load("return (" .. pattern_expr .. ")"), "invalid pattern: " .. pattern) setfenv(script, funcs) local ok, results = utils.trycall(script) if not ok then os.raise("invalid pattern: %s, error: %s", pattern, results or "unknown") end return results else return _match_script_pattern(pattern, opt) end end -- select the matched pattern script for the current platform/architecture function select_script(scripts, opt) opt = opt or {} local result = nil if type(scripts) == "function" then result = scripts elseif type(scripts) == "table" then local script_matched for pattern, script in pairs(scripts) do if not pattern:startswith("__") and _match_script(pattern, opt) then script_matched = script break end end if not script_matched then local scripts_fallback = {} local patterns_fallback = {} local excluded_opt = table.join(opt, {excluded = true}) for pattern, script in pairs(scripts) do if not pattern:startswith("__") and _match_script(pattern, excluded_opt) then table.insert(scripts_fallback, script) table.insert(patterns_fallback, pattern) end end script_matched = scripts_fallback[1] if script_matched and #scripts_fallback > 0 then local conflict_patterns = {patterns_fallback[1]} for idx, script in ipairs(scripts_fallback) do local pattern = patterns_fallback[idx] if script ~= script_matched then table.insert(conflict_patterns, pattern) end end if #conflict_patterns > 1 then utils.warning("multiple script patterns are matched, %s", table.concat(conflict_patterns, ", ")) end end end result = script_matched or scripts["__generic__"] end return result end return select_script