aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--SPEC.md40
-rw-r--r--src/lpm.lua113
-rw-r--r--t/run.lua12
3 files changed, 95 insertions, 70 deletions
diff --git a/SPEC.md b/SPEC.md
index 6ed778c..8a2964b 100644
--- a/SPEC.md
+++ b/SPEC.md
@@ -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
diff --git a/t/run.lua b/t/run.lua
index c3babcf..3834db3 100644
--- a/t/run.lua
+++ b/t/run.lua
@@ -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()