diff options
author | Adam Harrison <adamdharrison@gmail.com> | 2023-02-19 15:54:40 -0500 |
---|---|---|
committer | Adam Harrison <adamdharrison@gmail.com> | 2023-02-19 15:54:40 -0500 |
commit | 73bc54c260e2681ea0173582eb32a36c139bb851 (patch) | |
tree | 0e35f7d77d948b6d81e645a7180b57358b7989e0 | |
parent | 2336be353fc4da8a9a45d474a1cc94db64e03293 (diff) | |
download | lite-xl-plugin-manager-0.99996.tar.gz lite-xl-plugin-manager-0.99996.zip |
Added in ability to select multiple arches.v0.99996
-rw-r--r-- | SPEC.md | 40 | ||||
-rw-r--r-- | src/lpm.lua | 113 | ||||
-rw-r--r-- | t/run.lua | 12 |
3 files changed, 95 insertions, 70 deletions
@@ -15,11 +15,11 @@ the form of a git remote url, i.e. `<url>:<ref>`. An example would be: ## Addons -Addons are the primary objects specified in this specification. An addon +Addons are the primary objects specified in this specification. An addon consists of a series of metadata, the path to the addon in this repository, -or its location on a remote repository, or a publically accessible URL,and a -set of files to be downloaded with the plugin (usually releases, but can be -data files, or fonts, or anything else). +or its location on a remote repository, or a publically accessible URL,and a +set of files to be downloaded with the plugin (usually releases, but can be +data files, or fonts, or anything else). Addons can specify optionally specify a type, which determines where they're installed. Currently three types are supported: @@ -28,10 +28,10 @@ installed. Currently three types are supported: * `plugin` * `color` -Addons are further classified into two organizational categories. -`singleton` addons, and `complex` addons. Addons are listed a `singleton` -if and only if they consist of exactly one file, have an empty or absent -`files` specification, and do not specify a `remote`. Singleton addons +Addons are further classified into two organizational categories. +`singleton` addons, and `complex` addons. Addons are listed a `singleton` +if and only if they consist of exactly one file, have an empty or absent +`files` specification, and do not specify a `remote`. Singleton addons consist of exactly one `.lua` file, named after the addon. Complex addons are contained within a folder, and have an `init.lua` or `init.so` file that loads other components within it. @@ -44,9 +44,9 @@ Fields that are required are bolded. * **`id`**: The semantic id of the addon, a string only containing `[a-z0-9\-_]`. * **`version`**: The addon's semantic version. A string that can contains `[0-9\.]`. -* **`mod_version`**: The mod_version this addon is compatible with. +* **`mod_version`**: The mod_version this addon is compatible with. A string that can contain `[0-9\.]`. If `type` is `library`, this field is optional. -* `type`: An optional string that specifies the addon type. Valid values are `"plugin"` +* `type`: An optional string that specifies the addon type. Valid values are `"plugin"` `"library"`, or `color`. Defaults to `"plugin"`. * `name`: The optional name of the addon. * `description`: An optional english-language description of the addon. @@ -54,7 +54,7 @@ Fields that are required are bolded. this addon provides. Can be used as a dependency. * `remote`: Optional. Specifies an https git link wheree this addon is located. If present, denotes a **stub**. -* `dependencies`: Optionally a hash of dependencies required, or optional +* `dependencies`: Optionally a hash of dependencies required, or optional for this addon. * `conflicts`: An optional hash of addons which conflict with this one, in the same format as `dependencies`. @@ -75,7 +75,7 @@ in `extra` that some plugin managers/displays will use are: ### Dependencies -Depedencies are specified in an object, with the key being the `id` of the +Depedencies are specified in an object, with the key being the `id` of the addon depended upon, or a `provides` alias. Dependency values are an object which contain the following keys: @@ -86,23 +86,23 @@ Dependency values are an object which contain the following keys: ### Stubs If a addon likes, it can specify a particular `remote`; a publically acessible -git repository, accessed via HTTPS, pinned at a specific commit to be used as a -source for its data. In that case, the package manager must download the repository, +git repository, accessed via HTTPS, pinned at a specific commit to be used as a +source for its data. In that case, the package manager must download the repository, and interpret the manifest file found there to determine the addon's metadata. -This is known as a stub. +This is known as a stub. ### Files Files are objects that contain at least two keys, `url`, and `checksum`. They -can also optionally contain the `arch` and `path` keys. +can also optionally contain the `arch` and `path` keys. * `url` represents the URL to grab the particular file from. -* `checksum` is the sha256hex checksum for the file. If `"SKIP"` is specified, the +* `checksum` is the sha256hex checksum for the file. If `"SKIP"` is specified, the check is skipped. This is fine for development purposes, but any publically accessible manifest, should specify a checksum. * `arch` is the lite-xl/clang architecture tuple that the file is relevant for. - if omitted, file is to be assumed to be valid for all arhcitectures. + if omitted, file is to be assumed to be valid for all arhcitectures. Can be an array. * `path` is the location to install this file inside the addon's directory. If a file is an archive, of either `.zip` or `.tar.gz`, it will automatically @@ -120,7 +120,7 @@ repository. Lite-XLs has the following metadata, as well as a `files` array. ### Files The files array is identical to that of the `files` array under `addons`. -Conventionally, there should be a single file per architecture that is a +Conventionally, there should be a single file per architecture that is a `.tar.gz` or `.zip` containing all necessary files for `lite-xl` to run. ## Version Specifiers @@ -141,7 +141,7 @@ that any version greater than `0.1` can be used. "path": "plugins/plugin_manager", # The path to the plugin in this repository. "mod_version": "3", # The mod_version this plugin corresponds to. "provides": [ # A list of small strings that represent functionalities this plugin provides. - "plugin-manager" + "plugin-manager" ], "files": [ # A list of files (usually binaries) this plugin requires to function. { diff --git a/src/lpm.lua b/src/lpm.lua index c1924a0..7af1f0f 100644 --- a/src/lpm.lua +++ b/src/lpm.lua @@ -501,13 +501,13 @@ local function error_handler(err) local message = e and err:sub(e + 3) or err if JSON then if VERBOSE then - io.stderr:write(json.encode({ error = err, actions = actions, warnings = warnings, traceback = debug.traceback() }) .. "\n") + io.stderr:write(json.encode({ error = err, actions = actions, warnings = warnings, traceback = debug.traceback(nil, 2) }) .. "\n") else io.stderr:write(json.encode({ error = message or err, actions = actions, warnings = warnings }) .. "\n") end else if err then io.stderr:write((not VERBOSE and message or err) .. "\n") end - if VERBOSE then io.stderr:write(debug.traceback() .. "\n") end + if VERBOSE then io.stderr:write(debug.traceback(nil, 2) .. "\n") end end status = -1 end @@ -760,22 +760,34 @@ function Addon:install(bottle, installing) elseif self.organization == 'complex' then common.copy(self.local_path, temporary_install_path) end - for i,file in ipairs(self.files or {}) do - if not file.arch or file.arch == ARCH then - if not NO_INSTALL_OPTIONAL and (not file.optional or prompt(common.basename(file.url) .. " is an optional dependency of " .. self.id .. ". Should we install it?")) then - if not file.checksum then error("requires a checksum") end - local path = install_path .. PATHSEP .. (file.path or common.basename(file.url)) - local temporary_path = temporary_install_path .. PATHSEP .. (file.path or common.basename(file.url)) - common.get(file.url, temporary_path, file.checksum, write_progress_bar) - local basename = common.basename(path) - if basename:find("%.zip$") or basename:find("%.tar%.gz$") then - log_action("Extracting file " .. basename .. " in " .. install_path) - system.extract(temporary_path, temporary_install_path) - else - if file.arch then system.chmod(temporary_path, 448) end -- chmod any ARCH tagged file to rwx------- + + + local has_arched_files = #common.grep(self.files or {}, function(e) return e.arch end) > 0 + for _, arch in ipairs(ARCH) do + local has_one_file = false + for _, file in ipairs(self.files or {}) do + local file_arch = file.arch and type(file.arch) == "string" and { file.arch } or file.arch + if not file.arch or #common.grep(file_arch, function(e) return e == arch end) > 0 then + if file.arch then has_one_file = true end + if not NO_INSTALL_OPTIONAL and (not file.optional or prompt(common.basename(file.url) .. " is an optional dependency of " .. self.id .. ". Should we install it?")) then + if not file.checksum then error("requires a checksum") end + local path = install_path .. PATHSEP .. (file.path or common.basename(file.url)) + local temporary_path = temporary_install_path .. PATHSEP .. (file.path or common.basename(file.url)) + common.get(file.url, temporary_path, file.checksum, write_progress_bar) + local basename = common.basename(path) + if basename:find("%.zip$") or basename:find("%.tar%.gz$") then + log_action("Extracting file " .. basename .. " in " .. install_path) + system.extract(temporary_path, temporary_install_path) + else + if file.arch and file.arch ~= "*" then system.chmod(temporary_path, 448) end -- chmod any ARCH tagged file to rwx------- + end end end end + + if has_arched_files and not has_one_file and (not self.arch or (self.arch ~= "*" and #common.grep(self.arch, function(a) return a == arch end) == 0)) then + error("Addon " .. self.id .. " does not support arch " .. arch) + end end end) bottle:invalidate_cache() @@ -785,9 +797,11 @@ function Addon:install(bottle, installing) else if POST and self.post then common.chdir(temporary_install_path, function() - if type(self.post) == "table" and not self.post[ARCH] then error("can't find post command for arch " .. ARCH) end - local code = os.system(type(self.post) == "table" and self.post[ARCH] or self.post) ~= 0 - if code ~= 0 then error("post step failed with error code " .. code) end + for i, arch in ipairs(ARCH) do + if type(self.post) == "table" and not self.post[ARCH] then error("can't find post command for arch " .. ARCH) end + local code = os.system(type(self.post) == "table" and self.post[ARCH] or self.post) ~= 0 + if code ~= 0 then error("post step failed with error code " .. code) end + end end) end common.rmrf(install_path) @@ -1057,12 +1071,17 @@ function LiteXL.new(repository, metadata) files = {} }, metadata), LiteXL) self.hash = system.hash((repository and repository:url() or "") .. "-" .. metadata.version .. common.join("", common.map(self.files, function(f) return f.checksum end))) - self.local_path = self:is_local() and self.path or (CACHEDIR .. PATHSEP .. "lite_xls" .. PATHSEP .. ARCH .. PATHSEP .. self.version .. PATHSEP .. self.hash) - self.binary_path = self.binary_path or (self.local_path .. PATHSEP .. "lite-xl") + self.local_path = self:is_local() and self.path or (CACHEDIR .. PATHSEP .. "lite_xls" .. PATHSEP .. self.version .. PATHSEP .. self.hash) + self.binary_path = self.binary_path or { } self.datadir_path = self.datadir_path or (self.local_path .. PATHSEP .. "data") return self end +function LiteXL:get_binary_path(arch) + if self.binary_path and self.binary_path[arch or _G.ARCH] then return self.binary_path[arch or _G.ARCH] end + return self.local_path .. PATHSEP .. "lite-xl." .. (arch or _G.ARCH) +end + function LiteXL:is_system() return system_bottle and system_bottle.lite_xl == self end function LiteXL:is_local() return not self.repository and self.path end function LiteXL:is_compatible(addon) return not addon.mod_version or compatible_modversion(self.mod_version, addon.mod_version) end @@ -1082,14 +1101,14 @@ function LiteXL:install() system.chmod(self.local_path .. PATHSEP .. "lite-xl", 448) -- chmod to rwx------- common.copy(datadir, self.local_path .. PATHSEP .. "data") elseif self.path and not self.repository then -- local repository - system.symlink(self.binary_path, self.path .. PATHSEP .. "lite_xl") + system.symlink(self:get_binary_path(), self.local_path .. PATHSEP .. "lite_xl") else if self.remote then system.init(self.local_path, self.remote) common.reset(self.local_path, self.commit or self.branch) end for i,file in ipairs(self.files or {}) do - if file.arch and file.arch == ARCH then + if file.arch and common.grep(ARCH, function(e) return e == file.arch end)[1] then if not file.checksum then error("requires a checksum") end local basename = common.basename(file.url) local archive = basename:find("%.zip$") or basename:find("%.tar%.gz$") @@ -1349,10 +1368,12 @@ local function get_lite_xl(version) end local function lpm_lite_xl_save() - common.mkdirp(CACHEDIR .. PATHSEP .. ARCH .. PATHSEP .. "lite_xls") - common.write(CACHEDIR .. PATHSEP .. ARCH .. PATHSEP .. "lite_xls" .. PATHSEP .. "locals.json", - json.encode(common.map(common.grep(lite_xls, function(l) return l:is_local() and not l:is_system() end), function(l) return { version = l.version, mod_version = l.mod_version, path = l.path } end)) - ) + for i, arch in ipairs(ARCH) do + common.mkdirp(CACHEDIR .. PATHSEP .. arch .. PATHSEP .. "lite_xls") + common.write(CACHEDIR .. PATHSEP .. arch .. PATHSEP .. "lite_xls" .. PATHSEP .. "locals.json", + json.encode(common.map(common.grep(lite_xls, function(l) return l:is_local() and not l:is_system() and l.arch == arch end), function(l) return { version = l.version, mod_version = l.mod_version, path = l.path } end)) + ) + end end local function lpm_lite_xl_add(version, path) @@ -1360,7 +1381,7 @@ local function lpm_lite_xl_add(version, path) if not path then error("requires a path") end if not system.stat(path .. PATHSEP .. "lite-xl") then error("can't find " .. path .. PATHSEP .. "lite-xl") end if not system.stat(path .. PATHSEP .. "data") then error("can't find " .. path .. PATHSEP .. "data") end - table.insert(lite_xls, LiteXL.new(nil, { version = version, path = path:gsub(PATHSEP .. "$", ""), mod_version = MOD_VERSION or LATEST_MOD_VERSION })) + table.insert(lite_xls, LiteXL.new(nil, { version = version, path = path:gsub(PATHSEP .. "$", ""), arch = ARCH[1], mod_version = MOD_VERSION or LATEST_MOD_VERSION })) lpm_lite_xl_save() end @@ -1385,7 +1406,7 @@ local function lpm_lite_xl_switch(version, target) if not lite_xl:is_installed() then log_action("Installing lite-xl " .. lite_xl.version) lite_xl:install() end local stat = system.stat(target) if stat and stat.symlink then os.remove(target) end - system.symlink(lite_xl.binary_path, target) + system.symlink(lite_xl:get_binary_path(), target) if not common.path('lite-xl') then os.remove(target) error(target .. " is not on your $PATH; please supply a target that can be found on your $PATH, called `lite-xl`.") @@ -1411,7 +1432,7 @@ local function lpm_lite_xl_list() status = (lite_xl:is_installed() or lite_xl:is_system()) and (lite_xl:is_local() and "local" or "installed") or "available", local_path = lite_xl:is_installed() and lite_xl.local_path or nil, datadir_path = lite_xl:is_installed() and lite_xl.datadir_path or nil, - binary_path = lite_xl:is_installed() and lite_xl.binary_path or nil + binary_path = lite_xl:is_installed() and lite_xl:get_binary_path() or nil }) max_version = math.max(max_version, #lite_xl.version) end @@ -1592,15 +1613,6 @@ local function lpm_addon_upgrade() end local function lpm_purge() - -- local path = common.path("lite-xl") - -- if path then - -- local lite_xl = get_lite_xl("system") - -- if lite_xl then - -- os.remove(path) - -- system.symlink(lite_xl:get_binary_path(), target) - -- log_action("Reset lite-xl symlink to system.") - -- end - -- end log_action("Removed " .. CACHEDIR .. ".") common.rmrf(CACHEDIR) end @@ -1615,14 +1627,19 @@ local function parse_arguments(arguments, options) if not flag_type then error("unknown flag --" .. option) end if flag_type == "flag" then args[option] = true - elseif flag_type == "string" or flag_type == "number" then + elseif flag_type == "string" or flag_type == "number" or flag_type == "array" then if not value or value == "" then if i == #arguments then error("option " .. option .. " requires a " .. flag_type) end value = arguments[i+1] i = i + 1 end if flag_type == "number" and tonumber(flag_type) == nil then error("option " .. option .. " should be a number") end - args[option] = value + if flag_type == "array" then + args[option] = args[option] or {} + table.insert(args[option], value) + else + args[option] = value + end end else table.insert(args, arguments[i]) @@ -1674,7 +1691,7 @@ xpcall(function() local ARGS = parse_arguments(ARGV, { json = "flag", userdir = "string", cachedir = "string", version = "flag", verbose = "flag", quiet = "flag", version = "flag", ["mod-version"] = "string", remotes = "flag", help = "flag", - remotes = "flag", ["ssl-certs"] = "string", force = "flag", arch = "string", ["assume-yes"] = "flag", + remotes = "flag", ["ssl-certs"] = "string", force = "flag", arch = "array", ["assume-yes"] = "flag", ["install-optional"] = "flag", datadir = "string", binary = "string", trace = "flag", -- filtration flags author = "string", tag = "string", stub = "string", dependency = "string", status = "string", @@ -1853,7 +1870,7 @@ not commonly used publically. DATADIR = common.normalize_path(ARGS["datadir"]) BINARY = common.normalize_path(ARGS["binary"]) NO_INSTALL_OPTIONAL = ARGS["no-install-optional"] - ARCH = ARGS["arch"] or _G.ARCH + ARCH = ARGS["arch"] or { _G.ARCH } ASSUME_YES = ARGS["assume-yes"] or FORCE MOD_VERSION = ARGS["mod-version"] or os.getenv("LPM_MODVERSION") if MOD_VERSION == "any" then MOD_VERSION = nil end @@ -2010,9 +2027,11 @@ not commonly used publically. repositories[#repositories]:parse_manifest() end end - if system.stat(CACHEDIR .. PATHSEP .. "lite_xls" .. PATHSEP .. ARCH .. PATHSEP .. "locals.json") then - for i, lite_xl in ipairs(json.decode(common.read(CACHEDIR .. PATHSEP .. "lite_xls" .. PATHSEP .. ARCH .. PATHSEP .. "locals.json"))) do - table.insert(lite_xls, LiteXL.new(nil, { version = lite_xl.version, mod_version = lite_xl.mod_version, path = lite_xl.path, tags = { "local" } })) + for i, arch in ipairs(ARCH) do + if system.stat(CACHEDIR .. PATHSEP .. "lite_xls" .. PATHSEP .. arch .. PATHSEP .. "locals.json") then + for i, lite_xl in ipairs(json.decode(common.read(CACHEDIR .. PATHSEP .. "lite_xls" .. PATHSEP .. ARCH .. PATHSEP .. "locals.json"))) do + table.insert(lite_xls, LiteXL.new(nil, { version = lite_xl.version, mod_version = lite_xl.mod_version, arch = arch, path = lite_xl.path, tags = { "local" } })) + end end end local lite_xl_binary = BINARY or common.path("lite-xl") @@ -2026,10 +2045,10 @@ not commonly used publically. local system_lite_xl = common.first(common.concat(common.flat_map(repositories, function(r) return r.lite_xls end), lite_xls), function(lite_xl) return lite_xl.local_path == directory end) if not system_lite_xl then system_lite_xl = common.first(lite_xls, function(e) return e.version == "system" end) - if system_lite_xl then error("can't find existing system lite (does " .. system_lite_xl.binary_path .. " exist? was it moved?); run `lpm purge`, or specify --binary and --datadir.") end + if system_lite_xl then error("can't find existing system lite (does " .. system_lite_xl:get_binary_path() .. " exist? was it moved?); run `lpm purge`, or specify --binary and --datadir.") end local lite_xl_datadirs = { DATADIR, directory:find(PATHSEP .. "bin$") and common.dirname(directory .. PATHSEP .. "share" .. PATHSEP .. "lite-xl"), directory .. PATHSEP .. "data" } local lite_xl_datadir = common.first(lite_xl_datadirs, function(p) return p and system.stat(p) end) - system_lite_xl = LiteXL.new(nil, { path = directory, datadir_path = lite_xl_datadir, binary_path = lite_xl_binary, mod_version = MOD_VERSION or LATEST_MOD_VERSION, version = "system", tags = { "system", "local" } }) + system_lite_xl = LiteXL.new(nil, { path = directory, datadir_path = lite_xl_datadir, binary_path = { [_G.ARCH] = lite_xl_binary }, mod_version = MOD_VERSION or LATEST_MOD_VERSION, version = "system", tags = { "system", "local" } }) table.insert(lite_xls, system_lite_xl) lpm_lite_xl_save() else @@ -104,6 +104,12 @@ local tests = { ["09_misc_commands"] = function() lpm("update") lpm("upgrade") + end, + ["10_install_multiarch"] = function() + lpm("install plugin_manager --arch x86_64-windows --arch x86_64-linux") + assert_exists(userdir .. "/plugins/plugin_manager/lpm.x86_64-linux") + assert_exists(userdir .. "/plugins/plugin_manager/lpm.x86_64-windows.exe") + assert_exists(userdir .. "/plugins/plugin_manager/init.lua") end } @@ -122,14 +128,14 @@ local function run_tests(tests, arg) local fail_count = 0 local names = {} if #arg == 0 then - for k,v in pairs(tests) do table.insert(names, k) end + for k,v in pairs(tests) do table.insert(names, k) end else names = arg end table.sort(names) local max_name = 0 os.execute("rm -rf " .. tmpdir .. "/lpmtest && mkdir -p " .. tmpdir .. "/lpmtest"); - for i,k in ipairs(names) do max_name = math.max(max_name, #k) end + for i,k in ipairs(names) do max_name = math.max(max_name, #k) end for i,k in ipairs(names) do local v = tests[k] if fast then @@ -144,7 +150,7 @@ local function run_tests(tests, arg) if last_command then print("Last Command: " .. last_command) if last_command_result then - print(json.encode(last_command_result)) + print(json.encode(last_command_result)) end end print() |