local lfs = require("lfs")
local catalogueURL = "https://raw.githubusercontent.com/sile-typesetter/sile-packages/master/packages.lua"
local packageHome = tostring(SYSTEM_SILE_PATH) .. "/packagemanager/"
local catalogueHome = packageHome .. "catalogue.lua"
local installedCatalogue = packageHome .. "installed.lua"
local http = require("ssl.https")
local recentlyUpdated = false
local recentlyReloaded = false
local origcpath = package.cpath local origpath = package.path
SILE.PackageManager = {
installed = {},
Catalogue = {}
}
local _deprecated = function ()
SU.deprecated("SILE.PackageManager", nil, "0.13.2", "0.15.0", [[
The built in SILE package manager has been completely deprecated. In its place
SILE can now load classes, packages, and other resources installed via
LuaRocks. Any SILE package may be published on LuaRocks.org or any private
repository. Rocks may be installed to the host system root filesystem, a user
directory, or a custom location. Please see the SILE manual for usage
instructions. Package authors especially can review the template repository
on GitHub for how to create a package.
]])
end
local function loadInSandbox(untrusted_code)
_deprecated()
if _ENV then local env = {}
local untrusted_function, message = load(untrusted_code, nil, 't', env)
if not untrusted_function then return nil, message end
return pcall(untrusted_function)
else
if untrusted_code:byte(1) == 27 then return nil, "binary bytecode prohibited" end
local untrusted_function, message = load(untrusted_code)
if not untrusted_function then return nil, message end
setfenv(untrusted_function, env)
return pcall(untrusted_function)
end
end
local function dumpTable(tbl)
_deprecated()
if type(tbl) == 'table' then
local str = '{ '
for k, v in pairs(tbl) do
if type(k) ~= 'number' then k = '"'..k..'"' end
str = str .. '['..k..'] = ' .. dumpTable(v) .. ','
end
return str .. '} '
else
return '"' .. tostring(tbl) .. '"'
end
end
local function fixupPaths()
local paths = ""
local cpaths = ""
for pkg, _ in pairs(SILE.PackageManager.installed) do
_deprecated()
paths = paths .. packageHome .. pkg .. '/?.lua;'
cpaths = cpaths .. packageHome .. pkg .. "/?."..SHARED_LIB_EXT.. ";"
end
if paths:len() >= 1 then package.path = paths .. ";" .. origpath end
if cpaths:len() >= 1 then package.cpath = cpaths .. ";" .. origcpath end
end
local function saveInstalled()
_deprecated()
local dump = dumpTable(SILE.PackageManager.installed)
local file, err = io.open(installedCatalogue, "w")
if err then
SU.error("Could not write installed package list at"..installedCatalogue..": "..err)
end
file:write("return "..dump)
file:close()
fixupPaths()
end
local function updateCatalogue ()
if not lfs.attributes(packageHome) then
if not lfs.mkdir(packageHome) then
SU.error("Error making package manager home directory: "..packageHome)
end
end
print("Loading catalogue from "..catalogueURL)
local result, statuscode, _ = http.request(catalogueURL)
if statuscode ~= 200 then
SU.error("Could not load catalogue from "..catalogueURL..": "..statuscode)
end
local file, err = io.open(catalogueHome, "w")
if err then
SU.error("Could not write package catalogue at"..catalogueHome..": "..err)
end
print("Writing "..(#result).." bytes to "..catalogueHome)
file:write(result)
file:close()
recentlyUpdated = true
recentlyReloaded = false
end
local function loadInstalledCatalogue()
local file = io.open(installedCatalogue, "r")
if file ~= nil then
local contents = file:read("*all")
local success, res = loadInSandbox(contents)
if not success then
SU.error("Error loading installed package list: "..res)
end
SILE.PackageManager.installed = res
end
end
local function reloadCatalogue()
local file = io.open(catalogueHome, "r")
if file ~= nil then
local contents = file:read("*all")
local success, res = loadInSandbox(contents)
if not success then
SU.error("Error loading package catalogue: "..res)
end
SILE.PackageManager.Catalogue = res
end
loadInstalledCatalogue()
print("Package catalogue reloaded")
recentlyReloaded = true
end
function updatePackage(packageName, branch)
_deprecated()
local target = packageHome .. packageName
if SILE.PackageManager.installed[packageName] == branch and branch ~= "master" then
print("Nothing to do!")
return true
end
local cwd = lfs.currentdir()
local _, err = lfs.chdir(target)
if err then
SU.warn("Package directory "..target.." went away! Trying again...")
SILE.PackageManager.installed[packageName] = nil
saveInstalled()
installPackage(packageName)
end
local ret = os.execute("git pull")
if not ret then
SU.error("Error updating repository for package "..packageName..": "..ret)
end
ret = os.execute("git checkout "..branch)
if not ret then
SU.error("Error updating repository for package "..packageName..": "..ret)
end
lfs.chdir(cwd)
SILE.PackageManager.installed[packageName] = branch
saveInstalled()
end
function installPackage(packageName)
_deprecated()
if not recentlyUpdated then updateCatalogue() end
if not recentlyReloaded then reloadCatalogue() end
if not SILE.PackageManager.Catalogue[packageName] then
SU.error("Can't install "..packageName..": package not known")
end
local metadata = SILE.PackageManager.Catalogue[packageName]
if metadata.depends then
for _, pkg in ipairs(metadata.depends) do
if not SILE.PackageManager.installed[pkg] then
print(packageName.." requires "..pkg..", installing that...")
installPackage(pkg)
end
end
end
if metadata.repository then
local branch = metadata.version or "master"
local target = packageHome .. packageName
if lfs.attributes(target) then
updatePackage(packageName, branch)
else
local ret = os.execute("git clone -c advice.detachedHead=false -b "..branch.." "..metadata.repository.." "..target)
if not ret then SU.error("Error cloning repository for package "..packageName..": "..ret)
end
end
SILE.PackageManager.installed[packageName] = branch
saveInstalled()
end
end
loadInstalledCatalogue()
fixupPaths()