initial commit
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.vscode/
|
9
arutils/arutils.files.json
Normal file
9
arutils/arutils.files.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"files": [
|
||||
[
|
||||
"lib/arutils/",
|
||||
"/usr/lib/arutils"
|
||||
]
|
||||
],
|
||||
"config": []
|
||||
}
|
9
arutils/arutils.manifest
Normal file
9
arutils/arutils.manifest
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
["manifestVersion"] = "1.0",
|
||||
["package"] = "arutils",
|
||||
["version"] = "1.0.0"
|
||||
["name"] = "arutils",
|
||||
["repo"] = "tree/master/arutils",
|
||||
["description"] = "Misc utilities functions",
|
||||
["authors"] = "AR2000AR"
|
||||
}
|
5
arutils/lib/arutils/init.lua
Normal file
5
arutils/lib/arutils/init.lua
Normal file
@@ -0,0 +1,5 @@
|
||||
package.path = package.path .. ";/usr/lib/?.lua;/usr/lib/?/init.lua"
|
||||
local utils = {}
|
||||
utils.parse = require "utils.parse"
|
||||
|
||||
return utils
|
32
arutils/lib/arutils/parse.lua
Normal file
32
arutils/lib/arutils/parse.lua
Normal file
@@ -0,0 +1,32 @@
|
||||
--https://github.com/MightyPirates/OpenComputers/blob/master-MC1.7.10/src/main/resources/assets/opencomputers/loot/openos/lib/shell.lua
|
||||
local function parse(...)
|
||||
local params = table.pack(...)
|
||||
local args = {}
|
||||
local options = {}
|
||||
local doneWithOptions = false
|
||||
for i = 1, params.n do
|
||||
local param = params[i]
|
||||
if not doneWithOptions and type(param) == "string" then
|
||||
if param == "--" then
|
||||
doneWithOptions = true -- stop processing options at `--`
|
||||
elseif param:sub(1, 2) == "--" then
|
||||
local key, value = param:match("%-%-(.-)=(.*)")
|
||||
if not key then
|
||||
key, value = param:sub(3), true
|
||||
end
|
||||
options[key] = value
|
||||
elseif param:sub(1, 1) == "-" and param ~= "-" then
|
||||
for j = 2, utf8.len(param) do
|
||||
options[string.sub(param, j, j)] = true
|
||||
end
|
||||
else
|
||||
table.insert(args, param)
|
||||
end
|
||||
else
|
||||
table.insert(args, param)
|
||||
end
|
||||
end
|
||||
return args, options
|
||||
end
|
||||
|
||||
return parse
|
81
pm/README.md
Normal file
81
pm/README.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# pm Package Manager
|
||||
Package manager for OpenOS. Use tar archive as package container
|
||||
|
||||
## Installation
|
||||
- run `oppm install pm`
|
||||
|
||||
or
|
||||
|
||||
Create a installation floppy. Installation and script can be found [here](../pm_installer/).
|
||||
|
||||
## Usage
|
||||
|
||||
**Install a package :**\
|
||||
`pm install [--dry-run] [--allow-same-version] package.tar`\
|
||||
**Uninstall a package**\
|
||||
`pm uninstall [--purge] [--dry-run] pakageName`\
|
||||
**List installed packages :**\
|
||||
`pm list-installed [--include-removed]`\
|
||||
|
||||
## Package format
|
||||
### File tree
|
||||
A package is a tar archive with the following data structure
|
||||
```
|
||||
/
|
||||
|---DATA
|
||||
|---CONTROL
|
||||
|---manifest
|
||||
```
|
||||
The `DATA` folder contain all files installed by the package. The `DATA` folder is the `/` of the OS similar of how `install` work.
|
||||
### Manifest file
|
||||
The manifest file describe the package. It is a serialization compatible file.
|
||||
```
|
||||
{
|
||||
manifestVersion = "1.0",
|
||||
package = "example",
|
||||
dependencies = {
|
||||
["neededpackage"] = "=1.0"
|
||||
},
|
||||
configFiles = {
|
||||
"/etc/example.conf"
|
||||
}, --list configurations files that need to be left on update / uninstallation of the package
|
||||
name = "Example Package",
|
||||
version = "1.0.0",
|
||||
description = "Package description",
|
||||
authors = "AR2000AR",
|
||||
note = "Extra bit of information",
|
||||
hidden = false,
|
||||
repo = "https://github.com/AR2000AR/openComputers_codes"
|
||||
}
|
||||
```
|
||||
### manifestVersion :
|
||||
The manifest file version. Currently `1.0`
|
||||
### package :
|
||||
The package's name. Different from the display name.
|
||||
### dependencies :
|
||||
The package's dependencies. The key is a package name, and value a version. Version is in the format `[>=]major.minor.patch`. `minor` and `patch` can be omitted.
|
||||
### configFiles :
|
||||
A table of all configurations files. They will not be overridden on update or removed by default during uninstallation.
|
||||
### name :
|
||||
The display name
|
||||
### version :
|
||||
The package's version. Version is in the format `major.minor.patch`. `minor` and `patch` can be omitted.\
|
||||
A other valid version number is `"oppm"` for oppm packages without a version number
|
||||
### description :
|
||||
Package's description
|
||||
### note :
|
||||
Extra information about the package
|
||||
### authors :
|
||||
List of authors
|
||||
### hidden :
|
||||
Hide the package from package managers's install candidate list
|
||||
### repo :
|
||||
URL to the source code
|
||||
|
||||
## Packaging a application
|
||||
### Manually
|
||||
- Create a folder with the same file structure as describe above
|
||||
- Write the package manifest's file
|
||||
- Create a tar archive with the tool of your choice. For example, while being the the folder, do `tar -c -f ../mypackage.tar *`
|
||||
### From a cloned oppm repo
|
||||
- Call the [repoPackager.py](tools/repoPackager.py) from the terminal while in the repository. If the default settings don't fit your need, call it with the `-h` option to see what can be changed.
|
324
pm/bin/pm.lua
Normal file
324
pm/bin/pm.lua
Normal file
@@ -0,0 +1,324 @@
|
||||
package.path = package.path .. ";/usr/lib/?.lua;/usr/lib/?/init.lua"
|
||||
local expect = require "cc.expect"
|
||||
local checkArg = expect.expect
|
||||
local tar = require("tar")
|
||||
local pm = require("libpm")
|
||||
local parse = require "arutils.parse"
|
||||
|
||||
local RM_BLACKLIST = {
|
||||
["/bin/"] = true,
|
||||
["/boot/"] = true,
|
||||
["/dev/"] = true,
|
||||
["/etc/"] = true,
|
||||
["/etc/rc.d/"] = true,
|
||||
["/home/"] = true,
|
||||
["/lib/"] = true,
|
||||
["/mnt/"] = true,
|
||||
["/tmp/"] = true,
|
||||
["/usr/"] = true,
|
||||
["/usr/lib/"] = true,
|
||||
["/usr/bin/"] = true,
|
||||
}
|
||||
|
||||
local args, opts = parse(...) --TODO : parse args
|
||||
|
||||
local f = string.format
|
||||
|
||||
local function printf(...)
|
||||
print(f(...))
|
||||
end
|
||||
|
||||
local function printHelp()
|
||||
printf("pm [opts] <mode> [args]")
|
||||
printf("mode :")
|
||||
printf("\tinstall <packageFile>")
|
||||
printf("\tuninstall <packageName>")
|
||||
printf("\tinfo <packageName>|<packageFile>")
|
||||
printf("\tlist-installed")
|
||||
printf("opts :")
|
||||
printf("\t--purge : remove configuration files")
|
||||
printf("\t--dry-run : do not really perform the operation")
|
||||
printf("\t--include-removed : also list non completly removed packages")
|
||||
printf("\t--allow-same-version : allow a package of the same version to be used for to update the installed version")
|
||||
end
|
||||
|
||||
local function isConfigPath(configPaths, test)
|
||||
for path, _ in pairs(configPaths) do
|
||||
if (string.match(test, "^" .. path)) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
---Extract the package tar and return the extracted path
|
||||
---@param packagePath string
|
||||
---@return boolean? ok, string? reason
|
||||
local function extractPackage(packagePath)
|
||||
checkArg(1, packagePath, "string")
|
||||
if (opts["dry-run"]) then return true, nil end
|
||||
printf("Extracting : %s", packagePath)
|
||||
return tar.extract(packagePath, "/", false, "DATA/", nil, "DATA/")
|
||||
end
|
||||
|
||||
local function rm(...)
|
||||
for _, v in pairs({...}) do
|
||||
if (opts["dry-run"]) then
|
||||
print("rm " .. v)
|
||||
else
|
||||
fs.delete(v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local rmdir = rm
|
||||
|
||||
---Return true if version number a is higher than b. Still return true when equals
|
||||
---@param a string
|
||||
---@param b string
|
||||
---@return boolean
|
||||
local function compareVersion(a, b)
|
||||
local aVersion = a:gmatch("%d+")
|
||||
local bVersion = b:gmatch("%d+")
|
||||
while true do
|
||||
local vA = aVersion()
|
||||
local vB = bVersion()
|
||||
if vA == nil and vB == nil then return true end
|
||||
vA = tonumber(vA)
|
||||
vB = tonumber(vB)
|
||||
if vA == nil then vA = 0 end
|
||||
if vB == nil then vB = 0 end
|
||||
if (vA > vB) then return true elseif (vA < vB) then return false end
|
||||
end
|
||||
end
|
||||
|
||||
--=============================================================================
|
||||
|
||||
local mode = table.remove(args, 1)
|
||||
|
||||
if (mode == "info") then
|
||||
local manifest
|
||||
if (args[1]:match("%.tar$")) then
|
||||
args[1] = shell.resolve(args[1])
|
||||
local r, rr
|
||||
manifest, r, rr = pm.getManifestFromPackage(args[1])
|
||||
if (not manifest) then
|
||||
printf("Error : %s, %s", r, rr or "")
|
||||
goto exit
|
||||
end
|
||||
else
|
||||
if (pm.isInstalled(args[1])) then
|
||||
manifest = pm.getManifestFromInstalled(args[1])
|
||||
else
|
||||
printf("Could not find package named %q", args[1])
|
||||
goto exit
|
||||
end
|
||||
end
|
||||
|
||||
printf("Name : %s (%s)", manifest.name, manifest.package)
|
||||
printf("Version : %s", manifest.version)
|
||||
if (manifest.description) then printf("Description : \n%s", manifest.description) end
|
||||
if (manifest.note) then printf("Note :\n%s", manifest.note) end
|
||||
if (manifest.authors) then printf("Authors : %s", manifest.authors) end
|
||||
if (manifest.dependencies) then
|
||||
print("Dependencies :")
|
||||
for name, version in pairs(manifest.dependencies) do
|
||||
printf("%s (%s)", name, version)
|
||||
end
|
||||
end
|
||||
goto exit
|
||||
elseif (mode == "list-installed") then
|
||||
for name, manifest in pairs(pm.getInstalled(opts["include-removed"])) do
|
||||
if (opts["include-removed"]) then
|
||||
local installed, removed = pm.isInstalled(name)
|
||||
local label = "installed"
|
||||
if (not installed and removed) then label = "config remaining" end
|
||||
printf("%s (%s) [%s]", name, manifest.version, label)
|
||||
else
|
||||
printf("%s (%s)", name, manifest.version)
|
||||
end
|
||||
end
|
||||
elseif (mode == "install") then
|
||||
--get the pacakge file absolute path
|
||||
args[1] = shell.resolve(args[1])
|
||||
|
||||
--get the manifest from the package
|
||||
local manifest, r, rr = pm.getManifestFromPackage(args[1])
|
||||
if (not manifest) then
|
||||
printf("Invalid package")
|
||||
goto exit
|
||||
end
|
||||
|
||||
--check if the package is already installed
|
||||
if (pm.isInstalled(manifest.package)) then
|
||||
local currentManifest = pm.getManifestFromInstalled(manifest.package)
|
||||
if (manifest.version == "oppm" or currentManifest.version == "oppm") then
|
||||
--do nothing. We force reinstallation for oppm since there is no version number
|
||||
elseif (manifest.version == currentManifest.version and not opts["allow-same-version"]) then
|
||||
printf("Package %q is already installed", manifest.package)
|
||||
goto exit
|
||||
elseif (compareVersion(currentManifest.version, manifest.version) and not opts["allow-same-version"]) then --downgrade
|
||||
printf("Cannot downgrade package %q", manifest.package)
|
||||
goto exit
|
||||
end
|
||||
end
|
||||
|
||||
--check the pacakge's dependencies
|
||||
if (manifest.dependencies) then
|
||||
for dep, version in pairs(manifest.dependencies) do
|
||||
if (not pm.isInstalled(dep)) then
|
||||
printf("Missing dependencie : %s (%s)", dep, version)
|
||||
goto exit
|
||||
end
|
||||
local compType = version:match("^[<>=]") or ">"
|
||||
version = version:match("%d.*$")
|
||||
local installedVersion = pm.getManifestFromInstalled(dep).version
|
||||
if (version) then
|
||||
if (installedVersion == "oppm") then
|
||||
printf("Warning : %s is using a oppm version. Cannot determine real installed version", dep)
|
||||
else
|
||||
if (compType == "=") then
|
||||
if (not version == installedVersion) then
|
||||
printf("Package %s require %s version %s but %s is installed", manifest.package, dep, version, installedVersion)
|
||||
goto exit
|
||||
end
|
||||
elseif (compType == "<") then
|
||||
if (compareVersion(version, installedVersion)) then
|
||||
printf("Package %s require %s version %s but %s is installed", manifest.package, dep, version, installedVersion)
|
||||
goto exit
|
||||
end
|
||||
else
|
||||
if (compareVersion(version, installedVersion) and version ~= installedVersion) then
|
||||
printf("Package %s require %s version %s but %s is installed", manifest.package, dep, version, installedVersion)
|
||||
goto exit
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--make the values the keys for easier test later
|
||||
local configFiles = {}
|
||||
if (manifest.configFiles) then
|
||||
for _, file in pairs(manifest.configFiles) do
|
||||
configFiles[file] = true
|
||||
end
|
||||
end
|
||||
|
||||
local installedFiles = {}
|
||||
if (pm.isInstalled(manifest.package)) then
|
||||
local installedFileListFile = assert(io.open(f("/etc/pm/info/%s.files", manifest.package)))
|
||||
for file in installedFileListFile:lines() do
|
||||
installedFiles[file] = true
|
||||
end
|
||||
installedFileListFile:close()
|
||||
end
|
||||
|
||||
--check that no file not from the package get overwriten
|
||||
for _, header in pairs(assert(tar.list(args[1]))) do
|
||||
if (header.name:match("^DATA/") and header.typeflag == "file") then
|
||||
local destination = header.name:sub(#("DATA/"))
|
||||
if (not installedFiles[destination] and (fs.exists(destination) and not isConfigPath(configFiles, destination))) then
|
||||
printf("\27[37mFile already exists %s\27[m", destination)
|
||||
goto exit
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (pm.isInstalled(manifest.package)) then
|
||||
local currentManifest = pm.getManifestFromInstalled(manifest.package)
|
||||
printf("Installing %s (%s) over %s (%s)", manifest.package, manifest.version, currentManifest.package, currentManifest.version)
|
||||
else
|
||||
printf("Installing : %s (%s)", manifest.package, manifest.version)
|
||||
end
|
||||
|
||||
--uninstall old version. It's easier than to check wich file need to be deleted
|
||||
if (pm.isInstalled(manifest.package)) then
|
||||
--print("Unistalling currently installed version")
|
||||
shell.run(f("pm uninstall %q --no-dependencies-check", manifest.package))
|
||||
end
|
||||
|
||||
--extract the files in the correct path
|
||||
local extracted, reason = extractPackage(args[1])
|
||||
if (not extracted) then
|
||||
printf("\27[37m%s\27[m", reason or "Unkown error")
|
||||
--TODO : revert installation
|
||||
goto exit
|
||||
end
|
||||
|
||||
--save the package info and file list
|
||||
if (not opts["dry-run"]) then
|
||||
fs.makeDir("/etc/pm/info/")
|
||||
local listFile = assert(io.open(f("/etc/pm/info/%s.files", manifest.package), "w"))
|
||||
for _, header in pairs(assert(tar.list(args[1]))) do
|
||||
if (header.name:match("^DATA")) then
|
||||
local name = header.name:match("DATA(.*)")
|
||||
if (name and name ~= "") then listFile:write(name .. "\n") end
|
||||
end
|
||||
end
|
||||
listFile:close()
|
||||
assert(tar.extract(args[1], "/tmp/pm/", true, "CONTROL/manifest", nil, "CONTROL/"))
|
||||
fs.delete(f("/etc/pm/info/%s.manifest", manifest.package))
|
||||
fs.move("/tmp/pm/manifest", f("/etc/pm/info/%s.manifest", manifest.package))
|
||||
end
|
||||
|
||||
--remove the tmp folder
|
||||
--cleanup(extracted)
|
||||
elseif (mode == "uninstall") then
|
||||
--check if the package exists
|
||||
if (not pm.isInstalled(args[1])) then
|
||||
printf("Package %q is not installed", args[1])
|
||||
goto exit
|
||||
end
|
||||
|
||||
local manifest = pm.getManifestFromInstalled(args[1])
|
||||
|
||||
--check dep
|
||||
|
||||
if (not opts["no-dependencies-check"]) then
|
||||
local cantUninstall, dep = pm.checkDependant(args[1])
|
||||
if (cantUninstall) then
|
||||
printf("Cannot uninstall %s. One or more package (%s) depend on it.", args[1], dep)
|
||||
goto exit
|
||||
end
|
||||
end
|
||||
|
||||
printf("Uninstalling : %s", args[1])
|
||||
|
||||
--make the values the keys for easier test later
|
||||
local configFiles = {}
|
||||
if (manifest.configFiles) then
|
||||
for _, file in pairs(manifest.configFiles) do
|
||||
configFiles[file] = true
|
||||
end
|
||||
end
|
||||
|
||||
local fileListFile = assert(io.open(f("/etc/pm/info/%s.files", args[1])))
|
||||
local dirs = {}
|
||||
--delete the files
|
||||
for path in fileListFile:lines() do
|
||||
if (not isConfigPath(configFiles, path) or opts.purge) then
|
||||
if (not fs.isDir(path)) then
|
||||
rm(f("%q", path))
|
||||
else
|
||||
if (path and path ~= "/" and path ~= "") then
|
||||
table.insert(dirs, 1, path)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
fileListFile:close()
|
||||
--delete empty directory left behind
|
||||
for _, dir in pairs(dirs) do
|
||||
if (not RM_BLACKLIST[dir]) then
|
||||
rmdir(f("%q", dir))
|
||||
end
|
||||
end
|
||||
rm(f("%q", f("/etc/pm/info/%s.files", args[1])))
|
||||
if (opts.purge) then rm(f("%q", f("/etc/pm/info/%s.manifest", args[1]))) end
|
||||
else
|
||||
printHelp()
|
||||
goto exit
|
||||
end
|
||||
::exit::
|
BIN
pm/example.tar
Normal file
BIN
pm/example.tar
Normal file
Binary file not shown.
17
pm/examplePackage/CONTROL/manifest
Normal file
17
pm/examplePackage/CONTROL/manifest
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
manifestVersion = "1.0",
|
||||
package = "example",
|
||||
dependencies = {
|
||||
["neededpackage"] = "=1.0"
|
||||
},
|
||||
configFiles = {
|
||||
"/etc/example.conf"
|
||||
}, --list configurations files that need to be left on update / uninstallation of the package
|
||||
name = "Example Package",
|
||||
version = "1.0.0",
|
||||
description = "Package description",
|
||||
authors = "AR2000AR",
|
||||
note = "Extra bit of information",
|
||||
hidden = false,
|
||||
repo = "https://github.com/AR2000AR/openComputers_codes"
|
||||
}
|
1
pm/examplePackage/DATA/usr/share/example/README.txt
Normal file
1
pm/examplePackage/DATA/usr/share/example/README.txt
Normal file
@@ -0,0 +1 @@
|
||||
This is a example package
|
124
pm/lib/libpm.lua
Normal file
124
pm/lib/libpm.lua
Normal file
@@ -0,0 +1,124 @@
|
||||
package.path = package.path .. ";/usr/lib/?.lua;/usr/lib/?/init.lua"
|
||||
local expect = require "cc.expect"
|
||||
local checkArg = expect.expect
|
||||
local tar = require("tar")
|
||||
|
||||
---@class manifest
|
||||
---@field package string
|
||||
---@field dependencies table<string,string>
|
||||
---@field configFiles table<number,string>
|
||||
---@field name string
|
||||
---@field version string
|
||||
---@field description string
|
||||
---@field authors string
|
||||
---@field note string
|
||||
---@field hidden string
|
||||
---@field repo string
|
||||
---@field archiveName string
|
||||
|
||||
local function cleanup(path)
|
||||
fs.delete(path)
|
||||
end
|
||||
|
||||
local f = string.format
|
||||
|
||||
local function printf(...)
|
||||
print(f(...))
|
||||
end
|
||||
|
||||
---@class libpm
|
||||
local pm = {}
|
||||
|
||||
---@return manifest
|
||||
function pm.getManifestFromInstalled(package)
|
||||
local file = assert(io.open(f("/etc/pm/info/%s.manifest", package)))
|
||||
---@type manifest
|
||||
local currentManifest = textutils.unserialize(file:read("a"))
|
||||
file:close()
|
||||
return currentManifest
|
||||
end
|
||||
|
||||
---Get the manifest from the package
|
||||
---@param packagePath string
|
||||
---@return manifest? manifest
|
||||
---@return string? reason
|
||||
---@return string? parserError
|
||||
function pm.getManifestFromPackage(packagePath)
|
||||
checkArg(1, packagePath, "string")
|
||||
local tmpPath = "/tmp/pm/"
|
||||
if (not fs.exists(packagePath)) then return nil, "Invalid path" end
|
||||
fs.makeDir("/tmp/pm/")
|
||||
repeat
|
||||
tmpPath = "/tmp/pm/" .. math.floor(math.random() * 10E9)
|
||||
until not fs.isDir(tmpPath)
|
||||
fs.makeDir(tmpPath)
|
||||
local ok, reason = tar.extract(packagePath, tmpPath, true, "CONTROL/manifest", nil, "CONTROL/")
|
||||
local filepath = tmpPath .. "/manifest"
|
||||
|
||||
if (not filepath) then
|
||||
return nil, f("Invalid package format")
|
||||
end
|
||||
local manifestFile = assert(io.open(filepath, "r"))
|
||||
local manifest
|
||||
manifest, reason = textutils.unserialize(manifestFile:read("a"))
|
||||
if (not manifest) then
|
||||
return nil, f("Invalid package manifest. Could not parse"), reason
|
||||
end
|
||||
cleanup(tmpPath)
|
||||
return manifest
|
||||
end
|
||||
|
||||
---get the list of installed packages
|
||||
---@param includeNonPurged? boolean
|
||||
---@return table<string,manifest>
|
||||
function pm.getInstalled(includeNonPurged)
|
||||
checkArg(1, includeNonPurged, 'boolean', 'nil')
|
||||
local prefix = "%.files$"
|
||||
if (includeNonPurged) then prefix = "%.manifest$" end
|
||||
local installed = {}
|
||||
if (not fs.exists("/etc/pm/info")) then fs.makeDir("/etc/pm/info") end
|
||||
for _, file in pairs(fs.list("/etc/pm/info/")) do
|
||||
local packageName = file:match("(.+)" .. prefix)
|
||||
if (packageName) then
|
||||
installed[packageName] = pm.getManifestFromInstalled(packageName)
|
||||
end
|
||||
end
|
||||
return installed
|
||||
end
|
||||
|
||||
---@param package string
|
||||
---@return boolean installed, boolean notPurged
|
||||
function pm.isInstalled(package)
|
||||
local installed = fs.exists(f("/etc/pm/info/%s.files", package))
|
||||
local notPurged = fs.exists(f("/etc/pm/info/%s.manifest", package))
|
||||
return installed and notPurged, notPurged
|
||||
end
|
||||
|
||||
---check if a installed package depend of the package
|
||||
---@return boolean,string?
|
||||
function pm.checkDependant(package)
|
||||
printf("Checking for package dependant of %s", package)
|
||||
for pkg, manifest in pairs(pm.getInstalled(false)) do
|
||||
---@cast pkg string
|
||||
---@cast manifest manifest
|
||||
if (manifest.dependencies and manifest.dependencies[package]) then
|
||||
return true, pkg
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
---Get the list of package that depend on the provided package
|
||||
---@param package string
|
||||
---@return table
|
||||
function pm.getDependantOf(package)
|
||||
local dep = {}
|
||||
for installedPackageName, installedPackageManifest in pairs(pm.getInstalled(false)) do
|
||||
if (installedPackageManifest.dependencies and installedPackageManifest.dependencies[package]) then
|
||||
table.insert(dep, installedPackageName)
|
||||
end
|
||||
end
|
||||
return dep
|
||||
end
|
||||
|
||||
return pm
|
13
pm/pm.files.json
Normal file
13
pm/pm.files.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"files": [
|
||||
[
|
||||
"bin/pm.lua",
|
||||
"/usr/bin/"
|
||||
],
|
||||
[
|
||||
"lib/libpm.lua",
|
||||
"/usr/lib/"
|
||||
]
|
||||
],
|
||||
"config": []
|
||||
}
|
13
pm/pm.manifest
Normal file
13
pm/pm.manifest
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
["manifestVersion"] = "1.0",
|
||||
["package"] = "pm",
|
||||
["version"] = "1.0.0"
|
||||
["name"] = "pm Package Manager",
|
||||
["repo"] = "tree/master/pm",
|
||||
["description"] = "Package manager for OpenOS",
|
||||
["authors"] = "AR2000AR",
|
||||
["dependencies"] = {
|
||||
["libtar"] = "1.0.0",
|
||||
["usrpath"] = "1.0.0",
|
||||
}
|
||||
}
|
261
pm/tools/oppmPackager.py
Executable file
261
pm/tools/oppmPackager.py
Executable file
@@ -0,0 +1,261 @@
|
||||
#!/bin/python3
|
||||
import contextlib
|
||||
import re
|
||||
import sys
|
||||
import tarfile
|
||||
from datetime import datetime
|
||||
from io import BytesIO
|
||||
from os import environ
|
||||
from pathlib import Path
|
||||
from pprint import pprint
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
import github
|
||||
from github.ContentFile import ContentFile
|
||||
import pause
|
||||
import requests
|
||||
from slpp import slpp as lua
|
||||
from tqdm import tqdm
|
||||
from tqdm.contrib import DummyTqdmFile
|
||||
|
||||
# ==============================================================================
|
||||
OPPM_REPOS_LIST_URL = "https://raw.githubusercontent.com/OpenPrograms/openprograms.github.io/master/repos.cfg"
|
||||
OUT_DIR = Path(Path.home(), "tmp/oppmPackages")
|
||||
OUT_DIR.mkdir(exist_ok=True)
|
||||
|
||||
# ==============================================================================
|
||||
|
||||
|
||||
class MalformedPackage(Exception):
|
||||
"""Error raised when a oppm package's manifest look malformed"""
|
||||
pass
|
||||
|
||||
|
||||
class UnsuportedFileURI(MalformedPackage):
|
||||
pass
|
||||
|
||||
|
||||
class UnsuportedDependanceName(MalformedPackage):
|
||||
pass
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def std_out_err_redirect_tqdm():
|
||||
"""Trick to allow multipes progress bars and text output"""
|
||||
orig_out_err = sys.stdout, sys.stderr
|
||||
try:
|
||||
sys.stdout, sys.stderr = map(DummyTqdmFile, orig_out_err)
|
||||
yield orig_out_err[0]
|
||||
# Relay exceptions
|
||||
except Exception as exc:
|
||||
raise exc
|
||||
# Always restore sys.stdout/err if necessary
|
||||
finally:
|
||||
sys.stdout, sys.stderr = orig_out_err
|
||||
|
||||
# ==============================================================================
|
||||
|
||||
|
||||
def filterReposIfRepo(pair):
|
||||
"""Remove repos with no "repo" field"""
|
||||
name, repo = pair
|
||||
return "repo" in repo
|
||||
|
||||
|
||||
def filterReposIfPrograms(pair):
|
||||
"""Remove repos with no "programs" field"""
|
||||
name, repo = pair
|
||||
return "programs" in repo
|
||||
|
||||
|
||||
def rateLimitPause(githubInstance: github.Github):
|
||||
"""Wait until github's api rate limit reset"""
|
||||
if (githubInstance.rate_limiting[0] == 0):
|
||||
until = datetime.fromtimestamp(githubInstance.rate_limiting_resettime)
|
||||
print(f"Hit rate limit. Waiting until {until.time()}")
|
||||
pause.until(githubInstance.rate_limiting_resettime)
|
||||
|
||||
|
||||
def removeMetadata(tarObject: tarfile.TarInfo):
|
||||
"""Remove mtime from the tarObject"""
|
||||
tarObject.mtime = 0
|
||||
return tarObject
|
||||
|
||||
|
||||
def getGithubFileAsString(fileObj: ContentFile) -> str:
|
||||
"""Get the string content of the github file"""
|
||||
if (fileObj.content):
|
||||
return fileObj.content
|
||||
else:
|
||||
with requests.get(fileObj.download_url) as response:
|
||||
return response.text
|
||||
|
||||
|
||||
def makePackage(packageName: str, packageInfo: dict, repoPath: str, outputDirectory: str | Path = './packages/'):
|
||||
with TemporaryDirectory(prefix="packager.") as tmpDir:
|
||||
repo = githubAPI.get_repo(repoPath)
|
||||
Path(tmpDir+"/CONTROL/").mkdir(exist_ok=True)
|
||||
Path(tmpDir+"/DATA/").mkdir(exist_ok=True)
|
||||
|
||||
# build the manifest
|
||||
manifest = {}
|
||||
manifest["manifestVersion"] = "1.0"
|
||||
manifest["package"] = packageName
|
||||
if "version" in packageInfo:
|
||||
manifest["version"] = packageInfo["version"]
|
||||
else:
|
||||
manifest["version"] = "oppm"
|
||||
if "name" in packageInfo:
|
||||
manifest["name"] = packageInfo["name"]
|
||||
if "repo" in packageInfo:
|
||||
manifest["repo"] = packageInfo["repo"]
|
||||
if "description" in packageInfo:
|
||||
manifest["description"] = packageInfo["description"]
|
||||
if "note" in packageInfo:
|
||||
manifest["note"] = packageInfo["note"]
|
||||
if "authors" in packageInfo:
|
||||
manifest["authors"] = packageInfo["authors"]
|
||||
if "dependencies" in packageInfo:
|
||||
for dep in packageInfo["dependencies"]:
|
||||
if not "dependencies" in manifest:
|
||||
manifest["dependencies"] = {}
|
||||
if (Path(dep).parts[0][-1] == ":"):
|
||||
# cannot handle protocols like http https
|
||||
raise UnsuportedDependanceName(
|
||||
f"Package {packageName} from {repoPath} is malformed")
|
||||
manifest["dependencies"][dep] = "oppm"
|
||||
if "repo" in packageInfo:
|
||||
url = packageInfo["repo"]
|
||||
manifest["url"] = f"https://github.com/{repoOwnerAndName}/{url}"
|
||||
|
||||
files = {}
|
||||
# copy the required files
|
||||
if "files" in packageInfo:
|
||||
for fileInfo, destination in packageInfo["files"].items():
|
||||
if (type(fileInfo) != str):
|
||||
continue
|
||||
if re.match("//", destination):
|
||||
destination = destination[1:]
|
||||
else:
|
||||
destination = "/usr"+destination
|
||||
if destination[-1] != "/":
|
||||
destination = destination+"/"
|
||||
|
||||
prefix = fileInfo[0]
|
||||
filePath = Path(*Path(fileInfo).parts[1:])
|
||||
ref = Path(fileInfo).parts[0]
|
||||
if (ref[-1] == ":"):
|
||||
# cannot handle protocols like http https
|
||||
raise UnsuportedFileURI(
|
||||
f"Package {packageName} from {repoPath} is malformed : Invalid file")
|
||||
|
||||
# check the prefix
|
||||
if (ref[0] in (":", "?")):
|
||||
ref = ref[1:]
|
||||
if (prefix == "?"): # add it to the config file list
|
||||
if not "configFiles" in manifest:
|
||||
manifest["configFiles"] = []
|
||||
configFile = Path(
|
||||
*Path(fileInfo).parts[2:])
|
||||
manifest["configFiles"].append("/"+str(configFile))
|
||||
|
||||
if (prefix == ":"): # folder
|
||||
rateLimitPause(githubAPI)
|
||||
try:
|
||||
contents = repo.get_contents(str(filePath), ref)
|
||||
except github.GithubException as e:
|
||||
raise MalformedPackage(e)
|
||||
while contents:
|
||||
file_content = contents.pop(0)
|
||||
if file_content.type == "dir":
|
||||
rateLimitPause(githubAPI)
|
||||
try:
|
||||
contents.extend(repo.get_contents(file_content.path, ref))
|
||||
except github.GithubException as e:
|
||||
raise MalformedPackage(e)
|
||||
else:
|
||||
files[file_content.path] = getGithubFileAsString(file_content)
|
||||
else: # normal file
|
||||
rateLimitPause(githubAPI)
|
||||
try:
|
||||
files[filePath] = getGithubFileAsString(repo.get_contents(str(filePath), ref))
|
||||
except github.GithubException as e:
|
||||
raise MalformedPackage(e)
|
||||
|
||||
with open(tmpDir+"/CONTROL/manifest", 'w') as file:
|
||||
file.write(lua.encode(manifest))
|
||||
|
||||
version = manifest["version"]
|
||||
# manifest["archiveName"] = f"{packageName}_({version}).tar"
|
||||
manifest["archiveName"] = f"{packageName}.tar"
|
||||
with tarfile.open(Path(outputDirectory, manifest["archiveName"]), 'w') as tar:
|
||||
tar.add(tmpDir+"/CONTROL", arcname="CONTROL",filter=removeMetadata)
|
||||
for filePath, data in files.items():
|
||||
data = data.encode()
|
||||
tinfo = tarfile.TarInfo(filePath)
|
||||
tinfo.size = len(data)
|
||||
tinfo.name = f"DATA/{filePath}"
|
||||
tinfo.mtime = 0
|
||||
tar.addfile(tinfo, BytesIO(data))
|
||||
return manifest
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
if (environ.get("GITHUB_TOKEN") == None):
|
||||
print("\033[33mNo github token found\033[m")
|
||||
else:
|
||||
print("\033[32mFound github token\033[m")
|
||||
|
||||
|
||||
githubAPI = github.Github(login_or_token=environ.get("GITHUB_TOKEN"))
|
||||
|
||||
# ==============================================================================
|
||||
with std_out_err_redirect_tqdm() as orig_stdout:
|
||||
reposList = None
|
||||
with requests.get(OPPM_REPOS_LIST_URL) as response:
|
||||
if (response.status_code == 200):
|
||||
reposList = lua.decode(response.text)
|
||||
|
||||
reposList = dict(filter(filterReposIfRepo, reposList.items()))
|
||||
|
||||
for repoName, info in tqdm(reposList.items(), unit="repo", desc="Fetching repos", file=orig_stdout):
|
||||
if ("repo" in info):
|
||||
repoGitRepo = info["repo"]
|
||||
with requests.get(f"https://raw.githubusercontent.com/{repoGitRepo}/master/programs.cfg") as response:
|
||||
if (response.status_code == 200):
|
||||
if (type(lua.decode(response.text)) == dict):
|
||||
reposList[repoName]["programs"] = lua.decode(
|
||||
response.text)
|
||||
|
||||
reposList = dict(filter(filterReposIfPrograms, reposList.items()))
|
||||
|
||||
nbPackages = 0
|
||||
for repoDisplayName, info in reposList.items():
|
||||
if ("programs" in info):
|
||||
nbPackages += len(info["programs"])
|
||||
|
||||
rateLimitPause(githubAPI)
|
||||
reposManifest = {}
|
||||
with tqdm(total=nbPackages, unit="package", desc="Building package", file=orig_stdout, position=1) as progressBar:
|
||||
for repoDisplayName, info in reposList.items():
|
||||
if ("programs" in info):
|
||||
programs = info["programs"]
|
||||
repoOwnerAndName = info["repo"]
|
||||
for package, packageInfo in tqdm(programs.items(), unit="package", desc=repoDisplayName, file=orig_stdout, position=0, leave=False):
|
||||
progressBar.set_postfix_str(f"{repoDisplayName}:{package}")
|
||||
with TemporaryDirectory(prefix="oppm-") as tmpDir:
|
||||
if (not repoOwnerAndName in reposManifest):
|
||||
reposManifest[repoOwnerAndName] = {}
|
||||
Path(OUT_DIR, repoOwnerAndName).mkdir(exist_ok=True, parents=True)
|
||||
try:
|
||||
reposManifest[repoOwnerAndName][package] = makePackage(package, packageInfo, repoOwnerAndName, Path(OUT_DIR, repoOwnerAndName))
|
||||
except MalformedPackage as e:
|
||||
print(
|
||||
f"\033[31m{repoOwnerAndName} : {package} : {e.__class__.__name__} : {e}\033[m")
|
||||
|
||||
finally:
|
||||
progressBar.update(1)
|
||||
|
||||
for repoOwnerAndName, repoManifest in reposManifest.items():
|
||||
with open(Path(OUT_DIR, repoOwnerAndName, "manifest"), "w") as repoManifestFile:
|
||||
repoManifestFile.write(lua.encode(repoManifest))
|
142
pm/tools/pmUtils.py
Executable file
142
pm/tools/pmUtils.py
Executable file
@@ -0,0 +1,142 @@
|
||||
#!/bin/python3
|
||||
import getopt
|
||||
import getopt
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
import tarfile
|
||||
import tempfile
|
||||
import json
|
||||
from glob import glob
|
||||
|
||||
from slpp import slpp as lua
|
||||
|
||||
RED = '\33[31m'
|
||||
RESET = '\33[0m'
|
||||
|
||||
|
||||
def printError(msg):
|
||||
print(f"{RED}{msg}{RESET}", file=sys.stderr)
|
||||
|
||||
|
||||
def printUsage():
|
||||
print(
|
||||
f"{sys.argv[0]} [-d|--destination <path>] [-s|--strip-comments]")
|
||||
print("\t-d|--destination <path> : path to output the pacakges. Default is \"./packages/\"")
|
||||
print("\t-s|--strip-comments : remove the comments from lua files before adding them to the archive. Line number are not affected")
|
||||
|
||||
|
||||
def removeMetadata(tarObject: tarfile.TarInfo):
|
||||
tarObject.mtime = 0
|
||||
return tarObject
|
||||
|
||||
|
||||
def makePackage(projectDir: pathlib.Path, manifestPath: pathlib.Path, filesListJsonPath: pathlib.Path, outputDirectory=pathlib.Path('./packages/')):
|
||||
global opts
|
||||
|
||||
try:
|
||||
with tempfile.TemporaryDirectory(prefix="packager.") as tmpDir:
|
||||
# parse the files
|
||||
manifest = {}
|
||||
with open(manifestPath, 'r') as manifestFile:
|
||||
manifest = lua.decode(manifestFile.read())
|
||||
parsedFilesList = {}
|
||||
with open(filesListJsonPath, 'r') as filesListFile:
|
||||
parsedFilesList = json.load(filesListFile)
|
||||
|
||||
os.mkdir(tmpDir+"/CONTROL/")
|
||||
os.mkdir(tmpDir+"/DATA/")
|
||||
if not projectDir:
|
||||
projectDir = os.getcwd()
|
||||
|
||||
# copy the required files
|
||||
if "files" in parsedFilesList:
|
||||
for (fileInfo, destination) in parsedFilesList["files"]:
|
||||
addFileToPackage(tmpDir, projectDir, fileInfo, destination)
|
||||
if "config" in parsedFilesList:
|
||||
for (fileInfo, destination) in parsedFilesList["config"]:
|
||||
addFileToPackage(tmpDir, projectDir, fileInfo, destination)
|
||||
if not "configFiles" in manifest:
|
||||
manifest["configFiles"] = []
|
||||
manifest["configFiles"].append(str(destination))
|
||||
|
||||
# write the package's manifest file
|
||||
with open(tmpDir+"/CONTROL/manifest", 'w') as file:
|
||||
file.write(lua.encode(manifest))
|
||||
|
||||
if any(item in ['-s', '--strip-comments'] for item, v in opts):
|
||||
for luaFile in glob(root_dir=tmpDir+"/DATA/", pathname="**/*.lua", recursive=True):
|
||||
os.system(f'sed -i s/^--.*// {tmpDir+"/DATA/"+luaFile}')
|
||||
|
||||
manifest["archiveName"] = f"{manifest['package']}.tar"
|
||||
with tarfile.open(pathlib.Path(outputDirectory, manifest["archiveName"]), 'w') as tar:
|
||||
tar.add(tmpDir+"/CONTROL", arcname="CONTROL",
|
||||
filter=removeMetadata)
|
||||
tar.add(tmpDir+'/DATA/', arcname="DATA", filter=removeMetadata)
|
||||
return manifest
|
||||
except Exception as e:
|
||||
printError(
|
||||
f'Failed to buid package in {projectDir} from manifest {manifestPath}')
|
||||
printError('\t'+str(e))
|
||||
|
||||
|
||||
def addFileToPackage(tmpDir, source, fileInfo, destination):
|
||||
filePath = pathlib.Path(source, fileInfo)
|
||||
destination = pathlib.Path(destination)
|
||||
if (destination.is_absolute()):
|
||||
destination = destination.relative_to("/")
|
||||
destination = pathlib.Path(tmpDir, 'DATA', destination)
|
||||
|
||||
if (filePath.is_dir()):
|
||||
shutil.copytree(filePath, destination)
|
||||
else:
|
||||
pathlib.Path(destination).mkdir(
|
||||
parents=True, exist_ok=True)
|
||||
shutil.copy(filePath, destination)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# parse arguments
|
||||
# TODO : repo manifest update mode
|
||||
global opts, args
|
||||
try:
|
||||
opts, args = getopt.gnu_getopt(sys.argv[1:], 'hd:s', [
|
||||
'destination=', 'strip-comments'])
|
||||
except getopt.GetoptError as e:
|
||||
printError(e.msg)
|
||||
exit(1)
|
||||
|
||||
# get the output dir absolute path
|
||||
outputDirectory = pathlib.Path(os.getcwd(), "packages/").absolute()
|
||||
|
||||
# check and parse cmd line arguments
|
||||
for option, value in opts:
|
||||
if option in ("-h", "--help"):
|
||||
printUsage()
|
||||
exit(0)
|
||||
elif option in ("-d", "--destination"):
|
||||
outputDirectory = pathlib.Path(value).absolute()
|
||||
|
||||
# make sure the output directory exists
|
||||
# TODO : arg to create the dir if doesnt' exists
|
||||
pathlib.Path(outputDirectory).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# look for manifest files
|
||||
repoManifest = {}
|
||||
for pmManifestPath in glob("**/*.manifest"):
|
||||
manifestPath = pathlib.Path(pmManifestPath)
|
||||
projectDir = manifestPath.parent
|
||||
projectName = manifestPath.stem
|
||||
filesJson = pathlib.Path(projectDir, f"{projectName}.files.json")
|
||||
if not filesJson.exists():
|
||||
continue
|
||||
packageManifest = makePackage(
|
||||
projectDir, manifestPath, filesJson, outputDirectory)
|
||||
|
||||
if (packageManifest):
|
||||
repoManifest[packageManifest["package"]] = packageManifest
|
||||
|
||||
with open(pathlib.Path(outputDirectory, "manifest"), "w") as repoManifestFile:
|
||||
repoManifestFile.write(lua.encode(repoManifest))
|
200
pm/tools/repoConvert.py
Executable file
200
pm/tools/repoConvert.py
Executable file
@@ -0,0 +1,200 @@
|
||||
#!/bin/python3
|
||||
from slpp import slpp as lua
|
||||
import pathlib
|
||||
import json
|
||||
import re
|
||||
from getopt import gnu_getopt as getopt
|
||||
from getopt import GetoptError
|
||||
import colorama
|
||||
import sys
|
||||
from glob import glob
|
||||
|
||||
def printe(msg):
|
||||
print(f"{colorama.Fore.RED}{msg}{colorama.Fore.RESET}", file=sys.stderr)
|
||||
|
||||
def printw(msg):
|
||||
print(f"{colorama.Fore.YELLOW}{msg}{colorama.Fore.RESET}")
|
||||
|
||||
def printHelp():
|
||||
print(f"{sys.argv[0]} [-f|--from <name>] [-t|--to <name>]")
|
||||
print('\t<name> : one of "oppm" or "pm"')
|
||||
|
||||
def manifestOppmToPm(packageName,packageInfo):
|
||||
manifest = {}
|
||||
manifest["manifestVersion"] = "1.0"
|
||||
manifest["package"] = packageName
|
||||
if "version" in packageInfo:
|
||||
manifest["version"] = packageInfo["version"]
|
||||
else:
|
||||
manifest["version"] = "oppm"
|
||||
if "name" in packageInfo:
|
||||
manifest["name"] = packageInfo["name"]
|
||||
if "repo" in packageInfo:
|
||||
manifest["repo"] = packageInfo["repo"]
|
||||
if "description" in packageInfo:
|
||||
manifest["description"] = packageInfo["description"]
|
||||
if "note" in packageInfo:
|
||||
manifest["note"] = packageInfo["note"]
|
||||
if "authors" in packageInfo:
|
||||
manifest["authors"] = packageInfo["authors"]
|
||||
if "dependencies" in packageInfo:
|
||||
for dep in packageInfo["dependencies"]:
|
||||
if not "dependencies" in manifest:
|
||||
manifest["dependencies"] = {}
|
||||
manifest["dependencies"][dep] = "oppm"
|
||||
return manifest
|
||||
|
||||
def fileListOppmToPm(packageInfo,root=""):
|
||||
out = {"files":[],"config":[]}
|
||||
if "files" in packageInfo:
|
||||
for fileInfo,destination in packageInfo["files"].items():
|
||||
if re.match("//", destination):
|
||||
destination = destination[1:]
|
||||
else:
|
||||
destination = "/usr"+destination
|
||||
if destination[-1] != "/":
|
||||
destination = destination+"/"
|
||||
prefix = fileInfo[0]
|
||||
fileInfo=pathlib.Path(*pathlib.Path(fileInfo).parts[1:])
|
||||
if(fileInfo.is_relative_to(root)):
|
||||
fileInfo=fileInfo.relative_to(root)
|
||||
out["config" if prefix=="?" else "files"].append((str(fileInfo),destination))
|
||||
return out
|
||||
|
||||
def doOppmToPm():
|
||||
with open('programs.cfg') as file:
|
||||
oppmManifest = lua.decode(file.read())
|
||||
default=pathlib.Path("manifest/")
|
||||
for pName,pInfo in oppmManifest.items():
|
||||
manifest=manifestOppmToPm(pName,pInfo)
|
||||
sourceDir = pathlib.Path(manifest["repo"]).relative_to("tree/master")
|
||||
if(sourceDir.exists() and sourceDir.is_dir()):
|
||||
out=sourceDir
|
||||
else:
|
||||
out = default
|
||||
files=fileListOppmToPm(pInfo,out)
|
||||
|
||||
with open(pathlib.Path(out,f"{pName}.manifest"),"w") as f:
|
||||
f.write(lua.encode(manifest))
|
||||
with open(pathlib.Path(out,f"{pName}.files.json"),"w") as f:
|
||||
json.dump(files,f,indent="\t")
|
||||
|
||||
def recurseConfig(folderPath:pathlib.Path,fileDst,origin=None):
|
||||
out = dict()
|
||||
origin = origin or folderPath
|
||||
dirFiles = glob(f"{str(folderPath)}/**",recursive=True)
|
||||
for configPath in dirFiles:
|
||||
configPath=pathlib.Path(configPath)
|
||||
if(configPath.is_dir()):
|
||||
out |= recurseConfig(configPath,fileDst,origin)
|
||||
else:
|
||||
dst = pathlib.Path(fileDst,folderPath)
|
||||
if(fileDst.is_relative_to("/usr")):
|
||||
dst = f"/{dst.relative_to('/usr')}"
|
||||
else:
|
||||
dst = f"/{dst}"
|
||||
out[f"?master{str(configPath)}"] = dst
|
||||
return out
|
||||
|
||||
def doPmToOppm():
|
||||
manifestFiles = glob("*/*.manifest")
|
||||
manifestFiles = [pathlib.Path(path) for path in manifestFiles]
|
||||
oppmData = dict()
|
||||
for path in manifestFiles:
|
||||
manifest = None
|
||||
with path.open() as file:
|
||||
manifest = lua.decode(file.read())
|
||||
pName=manifest['package']
|
||||
|
||||
fileListPath = pathlib.Path(path.parent,f"{pName}.files.json")
|
||||
if(not fileListPath.is_file()):
|
||||
printe(f"Could not find file list for {pName} in {str(path.parent)}")
|
||||
continue
|
||||
files=None
|
||||
with fileListPath.open() as file:
|
||||
files=json.load(file)
|
||||
|
||||
oppmData[pName] = manifest
|
||||
oppmData[pName].pop('package')
|
||||
oppmData[pName].pop('manifestVersion')
|
||||
|
||||
oppmData[pName]["files"] = dict()
|
||||
for filePaths in files["files"]:
|
||||
filePath,fileDst = filePaths[0],filePaths[1]
|
||||
filePath = pathlib.Path(path.parent,filePath)
|
||||
fileDst = pathlib.Path(fileDst)
|
||||
if(not filePath.exists()):
|
||||
printw(f"Error in the file list for {pName} : {str(filePath)} does not exists")
|
||||
|
||||
if(fileDst.is_relative_to("/usr")):
|
||||
fileDst = f"/{fileDst.relative_to('/usr')}"
|
||||
else:
|
||||
fileDst = f"/{fileDst}"
|
||||
|
||||
if(filePath.is_dir()):
|
||||
oppmData[pName]["files"][f":master/{str(filePath)}"] = fileDst
|
||||
else:
|
||||
oppmData[pName]["files"][f"master/{str(filePath)}"] = fileDst
|
||||
|
||||
for filePaths in files["config"]:
|
||||
filePath,fileDst = filePaths[0],filePaths[1]
|
||||
filePath = pathlib.Path(path.parent,filePath)
|
||||
fileDst = pathlib.Path(fileDst)
|
||||
if(not filePath.exists()):
|
||||
printw(f"Error in the file list for {pName} : {str(filePath)} does not exists")
|
||||
|
||||
if(fileDst.is_relative_to("/usr")):
|
||||
fileDst = f"/{fileDst.relative_to('/usr')}"
|
||||
else:
|
||||
fileDst = f"/{fileDst}"
|
||||
|
||||
if(filePath.is_dir()):
|
||||
oppmData[pName]["files"] |= recurseConfig(filePath,fileDst)
|
||||
else:
|
||||
oppmData[pName]["files"][f"?master/{str(filePath)}"] = fileDst
|
||||
|
||||
if("dependencies" in oppmData[pName]):
|
||||
for k in oppmData[pName]["dependencies"].keys():
|
||||
oppmData[pName]["dependencies"][k]="/"
|
||||
|
||||
fields = ("version","name","repo","description","authors","dependencies","files","note")
|
||||
data = lua.encode(oppmData)
|
||||
for field in fields:
|
||||
data=re.sub(re.escape(f'["{field}"]'),field,data)
|
||||
|
||||
with open("programs.cfg","w") as file:
|
||||
file.write(data)
|
||||
|
||||
if __name__ == '__main__':
|
||||
global opts,args
|
||||
try:
|
||||
r=re.compile("-*(.*)")
|
||||
opts = dict()
|
||||
optsList, args = getopt(sys.argv[1:], 'hf:t:', ['from=', 'to=','strip-comments'])
|
||||
for k,v in optsList:
|
||||
k=r.findall(k)[0]
|
||||
opts[k]=v
|
||||
except GetoptError as e:
|
||||
printe(e.msg)
|
||||
exit(1)
|
||||
|
||||
if("h" in opts):
|
||||
printHelp()
|
||||
exit()
|
||||
|
||||
if(not ("from" in opts or "f" in opts)):
|
||||
printHelp()
|
||||
exit(1)
|
||||
if(not ("to" in opts or "t" in opts)):
|
||||
printHelp()
|
||||
exit(1)
|
||||
|
||||
opts["from"] = opts["from"] if "from" in opts else opts["f"]
|
||||
opts["to"] = opts["to"] if "to" in opts else opts["t"]
|
||||
|
||||
if(opts["from"] == "oppm"):
|
||||
if(opts["to"] == "pm"):
|
||||
doOppmToPm()
|
||||
if(opts["from"] == "pm"):
|
||||
if(opts["to"] == "oppm"):
|
||||
doPmToOppm()
|
150
pm/tools/repoPackager.py
Executable file
150
pm/tools/repoPackager.py
Executable file
@@ -0,0 +1,150 @@
|
||||
#!/bin/python3
|
||||
import getopt
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
import tarfile
|
||||
import tempfile
|
||||
from glob import glob
|
||||
|
||||
from slpp import slpp as lua
|
||||
|
||||
RED = '\33[31m'
|
||||
RESET = '\33[0m'
|
||||
|
||||
|
||||
def printError(msg):
|
||||
print(f"{RED}{msg}{RESET}", file=sys.stderr)
|
||||
|
||||
|
||||
def printUsage():
|
||||
print(f"{sys.argv[0]} [-p|--programs <file>] [-d|--destination <path>] [-s|--strip-comments] [*packageName]")
|
||||
print("\t-p|--programs <file> : oppm's programs.cfg file. Default is \"./programs.cfg\"")
|
||||
print("\t-d|--destination <path> : path to output the pacakges. Default is \"./packages/\"")
|
||||
print("\t-s|--strip-comments : remove the comments from lua files before adding them to the archive. Line number are not affected")
|
||||
|
||||
|
||||
def removeMetadata(tarObject):
|
||||
tarObject.mtime = 0
|
||||
return tarObject
|
||||
|
||||
|
||||
def makePackage(packageInfo, source=None, outputDirectory='./packages/'):
|
||||
global opts
|
||||
with tempfile.TemporaryDirectory(prefix="packager.") as tmpDir:
|
||||
os.mkdir(tmpDir+"/CONTROL/")
|
||||
os.mkdir(tmpDir+"/DATA/")
|
||||
if not source:
|
||||
source = os.getcwd()
|
||||
|
||||
# build the manifest
|
||||
manifest = {}
|
||||
manifest["manifestVersion"] = "1.0"
|
||||
manifest["package"] = packageName
|
||||
if "version" in packageInfo:
|
||||
manifest["version"] = packageInfo["version"]
|
||||
else:
|
||||
manifest["version"] = "oppm"
|
||||
if "name" in packageInfo:
|
||||
manifest["name"] = packageInfo["name"]
|
||||
if "repo" in packageInfo:
|
||||
manifest["repo"] = packageInfo["repo"]
|
||||
if "description" in packageInfo:
|
||||
manifest["description"] = packageInfo["description"]
|
||||
if "note" in packageInfo:
|
||||
manifest["note"] = packageInfo["note"]
|
||||
if "authors" in packageInfo:
|
||||
manifest["authors"] = packageInfo["authors"]
|
||||
if "dependencies" in packageInfo:
|
||||
for dep in packageInfo["dependencies"]:
|
||||
if not "dependencies" in manifest:
|
||||
manifest["dependencies"] = {}
|
||||
manifest["dependencies"][dep] = "oppm"
|
||||
|
||||
# copy the required files
|
||||
if "files" in packageInfo:
|
||||
for fileInfo, destination in packageInfo["files"].items():
|
||||
if re.match("//", destination):
|
||||
destination = destination[1:]
|
||||
else:
|
||||
destination = "/usr"+destination
|
||||
if destination[-1] != "/":
|
||||
destination = destination+"/"
|
||||
|
||||
prefix = fileInfo[0]
|
||||
filePath = pathlib.Path(*pathlib.Path(fileInfo).parts[1:])
|
||||
filePath = pathlib.Path(source, filePath)
|
||||
if (prefix == "?"): # add it to the config file list
|
||||
if not "configFiles" in manifest:
|
||||
manifest["configFiles"] = []
|
||||
configFile = pathlib.Path(*pathlib.Path(fileInfo).parts[2:])
|
||||
manifest["configFiles"].append("/"+str(configFile))
|
||||
|
||||
destination = tmpDir+"/DATA"+destination
|
||||
|
||||
if (prefix == ":"):
|
||||
shutil.copytree(filePath, destination)
|
||||
else:
|
||||
pathlib.Path(destination).mkdir(
|
||||
parents=True, exist_ok=True)
|
||||
shutil.copy(filePath, destination)
|
||||
|
||||
#write the package's manifest file
|
||||
with open(tmpDir+"/CONTROL/manifest", 'w') as file:
|
||||
file.write(lua.encode(manifest))
|
||||
|
||||
if any(item in ['-s','--strip-comments'] for item,v in opts):
|
||||
for luaFile in glob(root_dir=tmpDir+"/DATA/",pathname="**/*.lua",recursive=True):
|
||||
os.system(f'sed -i s/^--.*// {tmpDir+"/DATA/"+luaFile}')
|
||||
|
||||
version = manifest["version"]
|
||||
# manifest["archiveName"] = f"{packageName}_({version}).tar"
|
||||
manifest["archiveName"] = f"{packageName}.tar"
|
||||
with tarfile.open(pathlib.Path(outputDirectory, manifest["archiveName"]), 'w') as tar:
|
||||
tar.add(tmpDir+"/CONTROL", arcname="CONTROL",filter=removeMetadata)
|
||||
tar.add(tmpDir+'/DATA/', arcname="DATA", filter=removeMetadata)
|
||||
return manifest
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
global opts,args
|
||||
try:
|
||||
opts, args = getopt.gnu_getopt(sys.argv[1:], 'hp:d:s', ['programs=', 'destination=','strip-comments'])
|
||||
except getopt.GetoptError as e:
|
||||
printError(e.msg)
|
||||
exit(1)
|
||||
|
||||
outputDirectory = pathlib.Path(os.getcwd(), "packages/").absolute()
|
||||
packageInfoFile = pathlib.Path(os.getcwd(), "programs.cfg").absolute()
|
||||
|
||||
for option, value in opts:
|
||||
if option in ("-h", "--help"):
|
||||
printUsage()
|
||||
exit(0)
|
||||
elif option in ("-p", "--programs"):
|
||||
packageInfoFile = pathlib.Path(value).absolute()
|
||||
elif option in ("-d", "--destination"):
|
||||
outputDirectory = pathlib.Path(value).absolute()
|
||||
|
||||
pathlib.Path(outputDirectory).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
if not os.path.isfile(packageInfoFile):
|
||||
printError(f"{packageInfoFile} not found")
|
||||
exit(1)
|
||||
|
||||
raw = None
|
||||
with open(packageInfoFile, 'r') as file:
|
||||
raw = file.read()
|
||||
|
||||
data = lua.decode(raw)
|
||||
|
||||
repoManifest = {}
|
||||
for packageName, packageInfo in data.items():
|
||||
if len(args) == 0 or packageName in args:
|
||||
packageManifest = makePackage(packageInfo, outputDirectory=outputDirectory)
|
||||
repoManifest[packageManifest["package"]] = packageManifest
|
||||
|
||||
with open(pathlib.Path(outputDirectory, "manifest"), "w") as repoManifestFile:
|
||||
repoManifestFile.write(lua.encode(repoManifest))
|
4
pm/tools/requirement.txt
Normal file
4
pm/tools/requirement.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
pause>=0.3
|
||||
SLPP>=1.2.3
|
||||
PyGithub>=1.58.1
|
||||
tqdm>=4.65.0
|
55
pm_get/README.md
Normal file
55
pm_get/README.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# pm_get
|
||||
Download and install pm packages from a repository on internet.
|
||||
|
||||
## Installation
|
||||
- run `oppm install pm`
|
||||
|
||||
or
|
||||
|
||||
- Create a installation floppy. Installation and script can be found [here](../pm_installer/).
|
||||
|
||||
## Usage :
|
||||
- `pm-get install <package>` : install the package
|
||||
- `pm-get uninstall <package> [--autoremove] [--purge]` : uninstall the package. Optionally remove configurations files and/or old dependance no longer needed
|
||||
- `pm-get autoremove` : remove no longer required dependance.
|
||||
- `pm-get update` : update the local package cache
|
||||
- `pm-get upgrade` : apply all upgrades possible"
|
||||
- `pm-get sources list` : list configured source repository
|
||||
- `pm-get sources add <url>` : add a source repository url to `/etc/pm/sources.list.d/custom.list`
|
||||
- `pm-get list` : list available packages
|
||||
- `pm-get info <package>` : get the infos about the package
|
||||
|
||||
## Files :
|
||||
- `/etc/pm/sources.list` : the main repository list
|
||||
- `/etc/pm/sources.list.d/*.list` : additional repository lists
|
||||
- `/etc/pm/autoInstalled` : list dependance installed automatically
|
||||
|
||||
## Repository :
|
||||
A repository is a collection of packages and a manifest file for the repository, accessible via http or https.
|
||||
|
||||
### File structure :
|
||||
The repository owner if free to structure it however they want. The only restriction is that the manifest file `manifest` is placed in the repository's root folder.
|
||||
|
||||
### Manifest file :
|
||||
The manifest file is a aggregation of the packages's manifest files in a table. Inside each package's manifest is added a extra field that point to the package file. See [manifest](../packages/manifest) for a example.\
|
||||
Example :
|
||||
```
|
||||
{
|
||||
["pm_get"] = {
|
||||
["manifestVersion"] = "1.0",
|
||||
["package"] = "pm_get",
|
||||
["version"] = "1.2.0",
|
||||
["name"] = "pm get",
|
||||
["repo"] = "tree/master/pm",
|
||||
["description"] = "Download and install package for pm",
|
||||
["authors"] = "AR2000AR",
|
||||
["dependencies"] = {
|
||||
["pm"] = "oppm"
|
||||
},
|
||||
["configFiles"] = {
|
||||
"/etc/pm/sources.list"
|
||||
},
|
||||
["archiveName"] = "pm_get.tar"
|
||||
}
|
||||
}
|
||||
```
|
567
pm_get/bin/pm-get.lua
Normal file
567
pm_get/bin/pm-get.lua
Normal file
@@ -0,0 +1,567 @@
|
||||
package.path = package.path .. ";/usr/lib/?.lua;/usr/lib/?/init.lua"
|
||||
local pm = require("libpm")
|
||||
local parse = require "arutils.parse"
|
||||
|
||||
--=============================================================================
|
||||
|
||||
local DOWNLOAD_DIR = "/home/.pm-get/" --can't use /tmp as some archive can be bigger than the tmpfs
|
||||
local CONFIG_DIR = "/etc/pm/" --share the config directory with the rest of the package manager
|
||||
local SOURCE_FILE = CONFIG_DIR .. "sources.list"
|
||||
local SOURCE_DIR = SOURCE_FILE .. ".d/"
|
||||
local REPO_MANIFEST_CACHE = CONFIG_DIR .. "manifests.cache"
|
||||
local AUTO_INSTALLED = CONFIG_DIR .. "autoInstalled"
|
||||
|
||||
--=============================================================================
|
||||
---@type table<string>,table<progOpts>
|
||||
local args, opts = parse(...)
|
||||
local mode = table.remove(args, 1)
|
||||
--=============================================================================
|
||||
|
||||
local reposRuntimeCache
|
||||
|
||||
--=============================================================================
|
||||
|
||||
---@alias progOpts
|
||||
---| 'autoremove'
|
||||
---| 'purge'
|
||||
---| 'allow-same-version'
|
||||
|
||||
|
||||
--=============================================================================
|
||||
|
||||
local f = string.format
|
||||
|
||||
local function printf(...)
|
||||
print(f(...))
|
||||
end
|
||||
|
||||
local function printferr(...)
|
||||
io.stderr:write(f("%s\n", f(...)))
|
||||
end
|
||||
|
||||
---Compare a with b
|
||||
---@return -1|0|1 cmp `-1 a<b, 0 a=b, 1 a>b`
|
||||
local function compareVersion(a, b)
|
||||
local aVersion = a:gmatch("%d+")
|
||||
local bVersion = b:gmatch("%d+")
|
||||
while true do
|
||||
local vA = aVersion()
|
||||
local vB = bVersion()
|
||||
if vA == nil and vB == nil then return 0 end
|
||||
vA = tonumber(vA)
|
||||
vB = tonumber(vB)
|
||||
if vA == nil then vA = 0 end
|
||||
if vB == nil then vB = 0 end
|
||||
if (vA > vB) then return 1 elseif (vA < vB) then return -1 end
|
||||
end
|
||||
end
|
||||
|
||||
local function confirm(prompt, default)
|
||||
if (opts["y"]) then return true end
|
||||
while true do
|
||||
local y = default and "Y" or "y"
|
||||
local n = default and "n" or "N"
|
||||
term.write(f("%s [%s/%s] ", prompt, y, n))
|
||||
local op = read()
|
||||
if op == false or op == nil then return false end
|
||||
if op == "" and default then return true end
|
||||
if op == "" and not default then return false end
|
||||
if op == "y" or op == "Y" then return true end
|
||||
if op == "n" or op == "N" then return false end
|
||||
end
|
||||
end
|
||||
|
||||
---Return the sources list
|
||||
---@param raw? boolean
|
||||
---@return table
|
||||
local function getSources(raw)
|
||||
local sources = {}
|
||||
local file
|
||||
if (fs.exists(SOURCE_FILE) and not fs.isDir(SOURCE_FILE)) then
|
||||
file = assert(io.open(SOURCE_FILE))
|
||||
for url in file:lines() do
|
||||
if (not url:sub(1, 1) ~= "#") then
|
||||
table.insert(sources, url)
|
||||
end
|
||||
end
|
||||
file:close()
|
||||
end
|
||||
if (fs.isDir(SOURCE_DIR)) then
|
||||
for _, fileName in pairs(fs.list(SOURCE_DIR)) do
|
||||
if (fileName:match("%.list$")) then
|
||||
file = assert(io.open(SOURCE_DIR .. fileName))
|
||||
for url in file:lines() do
|
||||
if (not url:sub(1, 1) ~= "#") then
|
||||
table.insert(sources, 1, url)
|
||||
end
|
||||
end
|
||||
file:close()
|
||||
end
|
||||
end
|
||||
end
|
||||
if (not raw) then
|
||||
for i, url in pairs(sources) do
|
||||
if (url:match("^https://github.com/")) then
|
||||
sources[i] = url:gsub("https://github.com/", "https://raw.githubusercontent.com/"):gsub("/tree/", "/"):gsub("/blob/", "/")
|
||||
end
|
||||
end
|
||||
end
|
||||
return sources
|
||||
end
|
||||
|
||||
---@return table<string,table<string,manifest>>
|
||||
local function getCachedPackageList()
|
||||
if (not reposRuntimeCache) then
|
||||
if (not fs.exists(REPO_MANIFEST_CACHE)) then
|
||||
printferr("No data. Run `pm-get upddate` or add repositorys")
|
||||
return {}
|
||||
end
|
||||
local cache = assert(io.open(REPO_MANIFEST_CACHE))
|
||||
reposRuntimeCache = textutils.unserialize(cache:read("a"))
|
||||
cache:close()
|
||||
end
|
||||
return reposRuntimeCache
|
||||
end
|
||||
|
||||
---Get a packet manifest from the cache
|
||||
---@param name string
|
||||
---@param targetRepo? string
|
||||
---@return manifest? manifest, string? originRepo
|
||||
local function getCachedPackageManifest(name, targetRepo)
|
||||
for repoName, repo in pairs(getCachedPackageList()) do
|
||||
if (not targetRepo or repoName == targetRepo) then
|
||||
if (repo[name]) then
|
||||
return repo[name], repoName
|
||||
end
|
||||
end
|
||||
end
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
---@param package string
|
||||
---@param dep? table<number,table> Current dependance list.
|
||||
---@param cleanup? boolean cleanup the dep list. Default `true`
|
||||
---@return table<number,table>? dep ordered list of dependances
|
||||
---@return string? error
|
||||
local function buildDepList(package, dep, cleanup)
|
||||
if not dep then dep = {} end
|
||||
assert(dep)
|
||||
if cleanup == nil then cleanup = true end
|
||||
local packageManifest = getCachedPackageManifest(package)
|
||||
if (not packageManifest) then return nil, string.format("Package %q cannot be found", package) end
|
||||
if (packageManifest.dependencies) then
|
||||
for dependance, requiredVersion in pairs(packageManifest.dependencies) do
|
||||
table.insert(dep, {dependance, requiredVersion})
|
||||
buildDepList(dependance, dep, false)
|
||||
end
|
||||
end
|
||||
|
||||
if (cleanup) then
|
||||
local hash = {}
|
||||
local rm = {}
|
||||
for k, v in ipairs(dep) do
|
||||
if not hash[v[1]] then
|
||||
table.insert(hash, v[1])
|
||||
else
|
||||
table.insert(rm, 1, k)
|
||||
end
|
||||
end
|
||||
for _, v in ipairs(rm) do
|
||||
table.remove(dep, v)
|
||||
end
|
||||
end
|
||||
return dep
|
||||
end
|
||||
|
||||
---Download from url and return it
|
||||
---@param url any
|
||||
---@return string? data, string? reason
|
||||
local function wget(url)
|
||||
local request, reason = http.get(url)
|
||||
if (not request) then
|
||||
return nil, reason
|
||||
end
|
||||
local data = request.readAll()
|
||||
request.close()
|
||||
return data, nil
|
||||
end
|
||||
|
||||
local function isAuto(package)
|
||||
local auto = false
|
||||
if (not fs.exists(AUTO_INSTALLED)) then return false end
|
||||
for line in io.lines(AUTO_INSTALLED) do
|
||||
if (line == package) then
|
||||
auto = true
|
||||
end
|
||||
end
|
||||
return auto
|
||||
end
|
||||
|
||||
local function markManual(package)
|
||||
local auto = {}
|
||||
if (not fs.exists(AUTO_INSTALLED)) then return end
|
||||
for line in io.lines(AUTO_INSTALLED) do
|
||||
if (line ~= package) then table.insert(auto, line) end
|
||||
end
|
||||
local autoFile = assert(io.open(AUTO_INSTALLED, 'w'))
|
||||
for _, pk in pairs(auto) do
|
||||
autoFile:write(pk .. "\n")
|
||||
end
|
||||
autoFile:close()
|
||||
end
|
||||
|
||||
local function needUpgrade(pkg)
|
||||
local remoteManifest = getCachedPackageManifest(pkg)
|
||||
if not remoteManifest then return false end
|
||||
local localManifest = pm.getManifestFromInstalled(pkg)
|
||||
if not localManifest then return false end
|
||||
if remoteManifest.version == "oppm" or localManifest == "oppm" then return true end
|
||||
if compareVersion(remoteManifest.version, localManifest.version) > 0 then return true end
|
||||
if compareVersion(remoteManifest.version, localManifest.version) == 0 and opts["allow-same-version"] then return true end
|
||||
return false
|
||||
end
|
||||
|
||||
--=============================================================================
|
||||
|
||||
---Install a package
|
||||
---@param package string
|
||||
---@param markAuto boolean
|
||||
---@return any? code
|
||||
---@return string? errorReason
|
||||
local function doInstall(package, markAuto)
|
||||
local targetManifest, repoName = getCachedPackageManifest(package)
|
||||
if (not targetManifest) then
|
||||
return nil, string.format("Cannot find package : %s", package)
|
||||
end
|
||||
--install the package
|
||||
fs.makeDir(DOWNLOAD_DIR)
|
||||
--download
|
||||
printf("Downloading : %s", package)
|
||||
local data, reason = wget(f("%s/%s", repoName, targetManifest.archiveName))
|
||||
if (not data) then
|
||||
printferr("Failed to download %s", package)
|
||||
printferr(reason)
|
||||
return -1, reason
|
||||
end
|
||||
--write downloaded archive in download dir
|
||||
io.open(f("%s/%s", DOWNLOAD_DIR, targetManifest.archiveName), "w"):write(data):close()
|
||||
--build opts for pm
|
||||
local pmOptions = ""
|
||||
if (opts["allow-same-version"]) then
|
||||
pmOptions = "--allow-same-version"
|
||||
end
|
||||
--run pm
|
||||
local _, code = shell.run(f("pm install %s %s", pmOptions, f("%s/%s", DOWNLOAD_DIR, targetManifest.archiveName)))
|
||||
--cleanup
|
||||
fs.delete(f("%s/%s", DOWNLOAD_DIR, targetManifest.archiveName))
|
||||
--mark the pacakge as auto if asked for
|
||||
if (markAuto) then
|
||||
io.open(AUTO_INSTALLED, "a"):write(targetManifest.package .. "\n"):close()
|
||||
end
|
||||
return code
|
||||
end
|
||||
|
||||
---@param packages table|string
|
||||
---@param markAuto? boolean
|
||||
---@param buildDepTree? boolean
|
||||
local function install(packages, markAuto, buildDepTree)
|
||||
if (buildDepTree == nil) then buildDepTree = true end
|
||||
if (type(packages) == "string") then packages = {packages} end
|
||||
---get the dependance list
|
||||
local depList = {}
|
||||
for _, package in pairs(packages) do
|
||||
local targetManifest, repoName = getCachedPackageManifest(package)
|
||||
--check that the packet exists
|
||||
if (not targetManifest) then
|
||||
printferr("Package %s not found", package)
|
||||
error("", 0) --TODO : exit() fix that. dirty
|
||||
end
|
||||
if (buildDepTree) then
|
||||
buildDepList(package, depList)
|
||||
end
|
||||
end
|
||||
|
||||
local toInstall = {}
|
||||
local toUpgrade = {}
|
||||
for _, dep in pairs(depList) do
|
||||
if (not pm.isInstalled(dep[1])) then
|
||||
local exists = getCachedPackageManifest(dep[1])
|
||||
if (not exists) then
|
||||
printferr("Cannot fuffil dependency : %s (%s)", dep[1], dep[2])
|
||||
return -1
|
||||
end
|
||||
table.insert(toInstall, dep[1])
|
||||
else
|
||||
--TODO : check version
|
||||
end
|
||||
end
|
||||
local display = {}
|
||||
for _, v in pairs(toInstall) do table.insert(display, v) end
|
||||
for _, v in pairs(packages) do table.insert(display, v) end
|
||||
if (#display > 0) then printf("Will be installed :\n %s", table.concat(display, ', ')) end
|
||||
if (#toUpgrade > 0) then printf("Will be updated :\n %s", table.concat(toUpgrade, ', ')) end
|
||||
printf("%d upgraded, %d newly installed", #toUpgrade, #display)
|
||||
if (#display == 0 and #toUpgrade == 0) then return end
|
||||
if not confirm("Proceed") then return end
|
||||
|
||||
if (not opts['dry-run']) then
|
||||
for _, dep in pairs(toUpgrade) do
|
||||
--TODO : upgrade
|
||||
printf("Updating dependency : %s", dep)
|
||||
error("UNIMPLEMENTED")
|
||||
end
|
||||
for _, dep in pairs(toInstall) do
|
||||
printf("Installing dependency : %s", dep)
|
||||
local code, errorReason = doInstall(dep, true)
|
||||
if (errorReason) then
|
||||
printferr("Failed to install %q. Abording", dep)
|
||||
return -1
|
||||
end
|
||||
end
|
||||
for _, package in pairs(packages) do
|
||||
printf("Installing : %s", package)
|
||||
local code, errorReason = doInstall(package, false)
|
||||
if (errorReason) then
|
||||
printferr("Failed to install %q. Abording", package)
|
||||
return -1
|
||||
end
|
||||
end
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
local function uninstall(packages)
|
||||
local toUninstall = {}
|
||||
for _, package in pairs(packages) do
|
||||
table.insert(toUninstall, package)
|
||||
end
|
||||
|
||||
printf("Will be uninstalled :\n %s", table.concat(toUninstall, ', '))
|
||||
|
||||
if not confirm("Proceed") then return end
|
||||
--uninstallation
|
||||
local options = ""
|
||||
if (opts.purge) then options = options .. "--purge" end
|
||||
shell.run(f("pm uninstall %s %s", options, args[1]))
|
||||
for _, pkg in pairs(toUninstall) do
|
||||
shell.run(f("pm uninstall %s %s", options, pkg))
|
||||
markManual(pkg)
|
||||
end
|
||||
|
||||
if (opts["autoremove"]) then
|
||||
shell.run(f("pm-get autoremove -y %s", options))
|
||||
end
|
||||
end
|
||||
|
||||
local function update()
|
||||
local repos = getSources()
|
||||
local manifests = {}
|
||||
for _, repoURL in pairs(repos) do
|
||||
local data, reason = wget(repoURL .. "/manifest")
|
||||
if (not data) then
|
||||
printferr("Could not get manifest from %s\ns", repoURL, reason)
|
||||
else
|
||||
printf("Found repository : %s", repoURL)
|
||||
local pcalled
|
||||
pcalled, data = pcall(textutils.unserialize, data)
|
||||
if (pcalled == false) then
|
||||
printferr("Invalid manifest for %s", repoURL)
|
||||
else
|
||||
manifests[repoURL] = data
|
||||
end
|
||||
end
|
||||
end
|
||||
io.open(REPO_MANIFEST_CACHE, "w"):write(textutils.serialize(manifests)):close()
|
||||
|
||||
--check if packages need updating
|
||||
local canBeUpgraded = 0
|
||||
local remotePackages = getCachedPackageList()
|
||||
local installedPackages = pm.getInstalled()
|
||||
for pkgName in pairs(installedPackages) do
|
||||
if (needUpgrade(pkgName)) then
|
||||
canBeUpgraded = canBeUpgraded + 1
|
||||
end
|
||||
end
|
||||
printf("%s package(s) can be upgraded", canBeUpgraded)
|
||||
end
|
||||
|
||||
local function printHelp()
|
||||
print("pm-get [opts] <mode> [args]")
|
||||
print("mode :")
|
||||
print("\tinstall <packageFile>")
|
||||
print("\tuninstall <packageName>")
|
||||
print("\tpruge <packageName>")
|
||||
print("\tautoremove")
|
||||
print("\tupdate : update the package cache")
|
||||
print("\tupgrade : apply all upgrades possible")
|
||||
print("\tinfo <packageName>|<packageFile>")
|
||||
print("\tlist")
|
||||
print("\tsources list|add [new source url]")
|
||||
print("opts :")
|
||||
print("\t--autoremove : also remove dependencies non longer required")
|
||||
print("\t--purge : purge removed packages")
|
||||
print("\t--allow-same-version : allow the same package version to be installed over the currently installed one")
|
||||
print("\t--installed : only list installed packages")
|
||||
end
|
||||
|
||||
|
||||
|
||||
--=============================================================================
|
||||
|
||||
if (not http and (mode == "update" or mode == "install")) then
|
||||
printferr("Http is not enabled on this world")
|
||||
goto exit
|
||||
end
|
||||
|
||||
--Remove uninstalled files from autoInstalled file
|
||||
if (fs.exists(AUTO_INSTALLED)) then
|
||||
do
|
||||
local tokeep = {}
|
||||
for pkg in io.lines(AUTO_INSTALLED) do
|
||||
if (pm.isInstalled(pkg)) then table.insert(tokeep, pkg) end
|
||||
end
|
||||
local file = assert(io.open(AUTO_INSTALLED, 'w'))
|
||||
for _, pkg in pairs(tokeep) do file:write(f("%s\n", pkg)) end
|
||||
file:close()
|
||||
end
|
||||
end
|
||||
|
||||
if (mode == "purge") then
|
||||
mode = "uninstall"
|
||||
opts["purge"] = true
|
||||
end
|
||||
|
||||
if (mode == "update") then
|
||||
print("Updating repository cache")
|
||||
update()
|
||||
elseif (mode == "list") then
|
||||
args[1] = args[1] or ".*"
|
||||
for repoName, repo in pairs(getCachedPackageList()) do
|
||||
local sortedTable = {}
|
||||
for package, _ in pairs(repo) do
|
||||
table.insert(sortedTable, package)
|
||||
end
|
||||
table.sort(sortedTable, function(a, b) return string.lower(a) < string.lower(b) end)
|
||||
for i, package in pairs(sortedTable) do
|
||||
local manifest = repo[package]
|
||||
if (package:match("^" .. args[1])) then
|
||||
local installed, notpurged = pm.isInstalled(package)
|
||||
if (not opts['installed'] or installed) then
|
||||
local lb = ""
|
||||
if (installed) then
|
||||
lb = '[installed]'
|
||||
if (isAuto(package)) then
|
||||
lb = '[installed, auto]'
|
||||
end
|
||||
elseif (notpurged) then
|
||||
lb = '[config]'
|
||||
end
|
||||
printf("%s (%s) %s", package, manifest.version, lb)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif (mode == "info") then
|
||||
local manifest, repoName = getCachedPackageManifest(args[1])
|
||||
if (not manifest) then
|
||||
printferr("Package %s not found", args[1])
|
||||
goto exit
|
||||
end
|
||||
printf("Name : %s (%s)", manifest.name, manifest.package)
|
||||
printf("Version : %s", manifest.version)
|
||||
if (manifest.description) then printf("Description : \n%s", manifest.description) end
|
||||
if (manifest.note) then printf("Note :\n%s", manifest.note) end
|
||||
if (manifest.authors) then printf("Authors : %s", manifest.authors) end
|
||||
if (manifest.dependencies) then
|
||||
print("Dependencies :")
|
||||
for name, version in pairs(manifest.dependencies) do
|
||||
printf("%s (%s)", name, version)
|
||||
end
|
||||
end
|
||||
printf("Repo : %s", repoName)
|
||||
goto exit
|
||||
elseif (mode == "install") then
|
||||
install(args)
|
||||
for _, p in pairs(args) do
|
||||
markManual(p)
|
||||
end
|
||||
goto exit
|
||||
elseif (mode == "uninstall") then
|
||||
uninstall(args)
|
||||
elseif (mode == "autoremove") then
|
||||
local oldDep = {}
|
||||
if (fs.exists(AUTO_INSTALLED)) then
|
||||
for package in io.lines(AUTO_INSTALLED) do
|
||||
if (#pm.getDependantOf(package) == 0) then
|
||||
table.insert(oldDep, package)
|
||||
end
|
||||
end
|
||||
end
|
||||
printf("Will be uninstalled :\n %s", table.concat(oldDep, ', '))
|
||||
if not confirm("Proceed") then return end
|
||||
--uninstallation
|
||||
local options = ""
|
||||
if (opts.purge) then options = options .. " --purge" end
|
||||
for _, dep in pairs(oldDep) do
|
||||
shell.run(f("pm uninstall %s %s", options, dep))
|
||||
end
|
||||
markManual(args[1])
|
||||
elseif (mode == "upgrade") then
|
||||
local installed = pm.getInstalled(false)
|
||||
local toUpgrade = {}
|
||||
if (args[1]) then
|
||||
if (pm.isInstalled(args[1])) then
|
||||
table.insert(toUpgrade, args[1])
|
||||
local manifest = assert(pm.getManifestFromInstalled(args[1]))
|
||||
if (manifest.dependencies) then
|
||||
for dep, ver in pairs(manifest.dependencies) do
|
||||
if (needUpgrade(dep)) then
|
||||
table.insert(toUpgrade, dep)
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
--TODO : pkg not installed
|
||||
end
|
||||
else
|
||||
for pkg, manifest in pairs(installed) do
|
||||
if (manifest.version == "oppm") then
|
||||
printf("Found oppm version for %q.", pkg)
|
||||
table.insert(toUpgrade, pkg)
|
||||
else
|
||||
if (needUpgrade(pkg)) then
|
||||
table.insert(toUpgrade, pkg)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
install(toUpgrade, false)
|
||||
elseif (mode == "sources") then
|
||||
if (args[1] == "list") then
|
||||
local sources = getSources()
|
||||
for _, s in pairs(sources) do
|
||||
print(s)
|
||||
end
|
||||
elseif (args[1] == "add" and args[2]) then
|
||||
local sources = getSources(true)
|
||||
local exists = false
|
||||
for _, url in pairs(sources) do
|
||||
if (url == args[2]) then
|
||||
exists = true
|
||||
printf("Found %q in the source list. It will not be added again.", args[2])
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if (not exists) then
|
||||
fs.makeDir(SOURCE_DIR)
|
||||
assert(io.open(SOURCE_DIR .. "/custom.list", "a")):write(args[2] .. "\n"):close()
|
||||
printf("Added %q to the source list", args[2])
|
||||
end
|
||||
else
|
||||
print("pm-get sources add|list")
|
||||
end
|
||||
else
|
||||
printHelp()
|
||||
end
|
||||
::exit::
|
1
pm_get/etc/pm/sources.list
Normal file
1
pm_get/etc/pm/sources.list
Normal file
@@ -0,0 +1 @@
|
||||
https://gitea.ar2000.me/AR2000/CC_monorepo/raw/branch/main/packages/
|
14
pm_get/pm_get.files.json
Normal file
14
pm_get/pm_get.files.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"files": [
|
||||
[
|
||||
"bin/pm-get.lua",
|
||||
"/usr/bin/"
|
||||
]
|
||||
],
|
||||
"config": [
|
||||
[
|
||||
"etc/pm/sources.list",
|
||||
"/etc/pm/"
|
||||
]
|
||||
]
|
||||
}
|
13
pm_get/pm_get.manifest
Normal file
13
pm_get/pm_get.manifest
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
["manifestVersion"] = "1.0",
|
||||
["package"] = "pm_get",
|
||||
["version"] = "1.0.0"
|
||||
["name"] = "pm get",
|
||||
["repo"] = "tree/master/pm_get",
|
||||
["description"] = "Download and install package for pm",
|
||||
["authors"] = "AR2000AR",
|
||||
["dependencies"] = {
|
||||
["pm"] = "1.0.0",
|
||||
["usrpath"] = "1.0.0",
|
||||
}
|
||||
}
|
64
pm_installer/install.lua
Normal file
64
pm_installer/install.lua
Normal file
@@ -0,0 +1,64 @@
|
||||
local baseURL = "http://127.0.0.1:8888"
|
||||
--wget run http://127.0.0.1:8888/pm_installer/install.lua
|
||||
local function concatPath(...) return table.concat({...}, '/') end
|
||||
---@type function
|
||||
local mkdir = fs.makeDir
|
||||
|
||||
local function wget(src, dst)
|
||||
local r, rr = http.get(src)
|
||||
if (not r) then
|
||||
error(rr, 0)
|
||||
end
|
||||
io.open(dst, 'w'):write(r.readAll()):close()
|
||||
r.close()
|
||||
end
|
||||
|
||||
local path = "/tmp/" .. math.floor(math.random() * 10E9) .. "/"
|
||||
mkdir("/tmp")
|
||||
mkdir(path)
|
||||
mkdir(concatPath(path, 'bin'))
|
||||
mkdir(concatPath(path, 'lib'))
|
||||
mkdir(concatPath(path, 'lib/arutils'))
|
||||
local files = {
|
||||
['pm_get/bin/pm-get.lua'] = 'bin/pm-get.lua',
|
||||
['pm/bin/pm.lua'] = 'bin/pm.lua',
|
||||
['pm/lib/libpm.lua'] = 'lib/libpm.lua',
|
||||
['tar/lib/tar.lua'] = 'lib/tar.lua',
|
||||
['arutils/lib/arutils/init.lua'] = 'lib/arutils/init.lua',
|
||||
['arutils/lib/arutils/parse.lua'] = 'lib/arutils/parse.lua',
|
||||
|
||||
}
|
||||
local packages = {'pm.tar', 'pm_get.tar', 'libtar.tar', 'arutils.tar', 'usrpath.tar',}
|
||||
for src, dst in pairs(files) do
|
||||
print(string.format("Downloading %q as %q", path, dst))
|
||||
wget(concatPath(baseURL, src), concatPath(path, dst))
|
||||
end
|
||||
for _, pkg in pairs(packages) do
|
||||
print(string.format("Downloading %q as %q", path, pkg))
|
||||
wget(concatPath(baseURL, "packages", pkg), concatPath(path, pkg))
|
||||
end
|
||||
|
||||
sleep(3)
|
||||
print("Installing ...")
|
||||
|
||||
--init path
|
||||
local oldPackagePath = package.path
|
||||
package.path = table.concat({oldPackagePath, string.format("%s/lib/?.lua;%s/lib/?/init.lua", path, path)}, ";")
|
||||
|
||||
local pm = loadfile(path .. "bin/pm.lua", "bt", _ENV)
|
||||
|
||||
--install
|
||||
if (pm) then
|
||||
pm("install", "--allow-same-version", path .. "libtar.tar")
|
||||
pm("install", "--allow-same-version", path .. "arutils.tar")
|
||||
pm("install", "--allow-same-version", path .. "usrpath.tar")
|
||||
pm("install", "--allow-same-version", path .. "pm.tar")
|
||||
pm("install", "--allow-same-version", path .. "pm_get.tar")
|
||||
|
||||
package.path = oldPackagePath
|
||||
shell.setPath(shell.path() .. ":/usr/bin")
|
||||
|
||||
shell.run("pm-get update")
|
||||
shell.run("pm-get upgrade")
|
||||
end
|
||||
fs.delete(path)
|
361
tar/lib/tar.lua
Normal file
361
tar/lib/tar.lua
Normal file
@@ -0,0 +1,361 @@
|
||||
local expect = require "cc.expect"
|
||||
local checkArg = expect.expect
|
||||
|
||||
---@class tarHeader
|
||||
---@field name any
|
||||
---@field mode any
|
||||
---@field uid any
|
||||
---@field gid any
|
||||
---@field size any
|
||||
---@field mtime any
|
||||
---@field chksum any
|
||||
---@field typeflag any
|
||||
---@field linkname any
|
||||
---@field magic any
|
||||
---@field version any
|
||||
---@field uname any
|
||||
---@field gname any
|
||||
---@field devmajor any
|
||||
---@field devminor any
|
||||
---@field prefix any
|
||||
|
||||
---https://github.com/luarocks/luarocks/blob/master/src/luarocks/core/dir.lua
|
||||
local function unquote(c)
|
||||
local first, last = c:sub(1, 1), c:sub(-1)
|
||||
if (first == '"' and last == '"') or
|
||||
(first == "'" and last == "'") then
|
||||
return c:sub(2, -2)
|
||||
end
|
||||
return c
|
||||
end
|
||||
local dir = {}
|
||||
function dir.path(...)
|
||||
local t = {...}
|
||||
while t[1] == "" do
|
||||
table.remove(t, 1)
|
||||
end
|
||||
for i, c in ipairs(t) do
|
||||
t[i] = unquote(c)
|
||||
end
|
||||
return (table.concat(t, "/"):gsub("([^:])/+", "%1/"):gsub("^/+", "/"):gsub("/*$", ""))
|
||||
end
|
||||
|
||||
---https://github.com/luarocks/luarocks/blob/master/src/luarocks/tools/tar.lua#L56
|
||||
--- A pure-Lua implementation of untar (unpacking .tar archives)
|
||||
local tar = {}
|
||||
|
||||
local blocksize = 512
|
||||
|
||||
local function get_typeflag(flag)
|
||||
if flag == "0" or flag == "\0" then
|
||||
return "file"
|
||||
elseif flag == "1" then
|
||||
return "link"
|
||||
elseif flag == "2" then
|
||||
return "symlink" -- "reserved" in POSIX, "symlink" in GNU
|
||||
elseif flag == "3" then
|
||||
return "character"
|
||||
elseif flag == "4" then
|
||||
return "block"
|
||||
elseif flag == "5" then
|
||||
return "directory"
|
||||
elseif flag == "6" then
|
||||
return "fifo"
|
||||
elseif flag == "7" then
|
||||
return "contiguous" -- "reserved" in POSIX, "contiguous" in GNU
|
||||
elseif flag == "x" then
|
||||
return "next file"
|
||||
elseif flag == "g" then
|
||||
return "global extended header"
|
||||
elseif flag == "L" then
|
||||
return "long name"
|
||||
elseif flag == "K" then
|
||||
return "long link name"
|
||||
end
|
||||
return "unknown"
|
||||
end
|
||||
|
||||
local function octal_to_number(octal)
|
||||
local exp = 0
|
||||
local number = 0
|
||||
octal = octal:gsub("%s", "")
|
||||
for i = #octal, 1, -1 do
|
||||
local digit = tonumber(octal:sub(i, i))
|
||||
if not digit then
|
||||
break
|
||||
end
|
||||
number = number + (digit * 8 ^ exp)
|
||||
exp = exp + 1
|
||||
end
|
||||
return number
|
||||
end
|
||||
|
||||
local function checksum_header(block)
|
||||
local sum = 256
|
||||
for i = 1, 148 do
|
||||
local b = block:byte(i) or 0
|
||||
sum = sum + b
|
||||
end
|
||||
for i = 157, 500 do
|
||||
local b = block:byte(i) or 0
|
||||
sum = sum + b
|
||||
end
|
||||
return sum
|
||||
end
|
||||
|
||||
local function nullterm(s)
|
||||
return s:match("^[^%z]*")
|
||||
end
|
||||
|
||||
local function read_header_block(block)
|
||||
local header = {}
|
||||
header.name = nullterm(block:sub(1, 100))
|
||||
header.mode = nullterm(block:sub(101, 108)):gsub(" ", "")
|
||||
header.uid = octal_to_number(nullterm(block:sub(109, 116)))
|
||||
header.gid = octal_to_number(nullterm(block:sub(117, 124)))
|
||||
header.size = octal_to_number(nullterm(block:sub(125, 136)))
|
||||
header.mtime = octal_to_number(nullterm(block:sub(137, 148)))
|
||||
header.chksum = octal_to_number(nullterm(block:sub(149, 156)))
|
||||
header.typeflag = get_typeflag(block:sub(157, 157))
|
||||
header.linkname = nullterm(block:sub(158, 257))
|
||||
header.magic = block:sub(258, 263)
|
||||
header.version = block:sub(264, 265)
|
||||
header.uname = nullterm(block:sub(266, 297))
|
||||
header.gname = nullterm(block:sub(298, 329))
|
||||
header.devmajor = octal_to_number(nullterm(block:sub(330, 337)))
|
||||
header.devminor = octal_to_number(nullterm(block:sub(338, 345)))
|
||||
header.prefix = block:sub(346, 500)
|
||||
if checksum_header(block) ~= header.chksum then
|
||||
return false, "Failed header checksum"
|
||||
end
|
||||
return header
|
||||
end
|
||||
|
||||
|
||||
function tar.untar(filename, destdir)
|
||||
checkArg(1, filename, "string")
|
||||
checkArg(2, destdir, "string")
|
||||
|
||||
local tar_handle = io.open(filename, "rb")
|
||||
if not tar_handle then return nil, "Error opening file " .. filename end
|
||||
|
||||
local long_name, long_link_name
|
||||
local ok, err
|
||||
local make_dir = fs.makeDir
|
||||
while true do
|
||||
local block
|
||||
repeat
|
||||
block = tar_handle:read(blocksize)
|
||||
until (not block) or checksum_header(block) > 256
|
||||
if not block then break end
|
||||
if #block < blocksize then
|
||||
ok, err = nil, "Invalid block size -- corrupted file?"
|
||||
break
|
||||
end
|
||||
local header
|
||||
header, err = read_header_block(block)
|
||||
if not header then
|
||||
ok = false
|
||||
break
|
||||
end
|
||||
|
||||
local file_data = tar_handle:read(math.ceil(header.size / blocksize) * blocksize):sub(1, header.size)
|
||||
|
||||
if header.typeflag == "long name" then
|
||||
long_name = nullterm(file_data)
|
||||
elseif header.typeflag == "long link name" then
|
||||
long_link_name = nullterm(file_data)
|
||||
else
|
||||
if long_name then
|
||||
header.name = long_name
|
||||
long_name = nil
|
||||
end
|
||||
if long_link_name then
|
||||
header.name = long_link_name
|
||||
long_link_name = nil
|
||||
end
|
||||
end
|
||||
local pathname = dir.path(destdir, header.name)
|
||||
if header.typeflag == "directory" then
|
||||
make_dir(pathname)
|
||||
elseif header.typeflag == "file" then
|
||||
local dirname = fs.getDir(pathname)
|
||||
if dirname ~= "" then
|
||||
make_dir(dirname)
|
||||
end
|
||||
local file_handle
|
||||
file_handle, err = io.open(pathname, "wb")
|
||||
if not file_handle then
|
||||
ok = nil
|
||||
break
|
||||
end
|
||||
file_handle:write(file_data):close()
|
||||
end
|
||||
end
|
||||
tar_handle:close()
|
||||
return ok, err
|
||||
end
|
||||
|
||||
--END OF luarocks original code
|
||||
|
||||
---Retrun the headers
|
||||
---@param filename string
|
||||
---@return table<number,tarHeader>?, string? reason
|
||||
function tar.list(filename)
|
||||
checkArg(1, filename, 'string')
|
||||
local tar_handle = io.open(filename, "rb")
|
||||
if not tar_handle then return nil, "Error opening file " .. filename end
|
||||
local ok, err
|
||||
local long_name, long_link_name
|
||||
local files = {}
|
||||
while true do
|
||||
local block
|
||||
repeat
|
||||
block = tar_handle:read(blocksize)
|
||||
until (not block) or checksum_header(block) > 256
|
||||
if not block then break end
|
||||
if #block < blocksize then
|
||||
ok, err = nil, "Invalid block size -- corrupted file?"
|
||||
break
|
||||
end
|
||||
local header
|
||||
header, err = read_header_block(block)
|
||||
if not header then
|
||||
ok = false
|
||||
break
|
||||
end
|
||||
local file_data = tar_handle:read(math.ceil(header.size / blocksize) * blocksize):sub(1, header.size)
|
||||
if header.typeflag == "long name" then
|
||||
long_name = nullterm(file_data)
|
||||
elseif header.typeflag == "long link name" then
|
||||
long_link_name = nullterm(file_data)
|
||||
else
|
||||
if long_name then
|
||||
header.name = long_name
|
||||
long_name = nil
|
||||
end
|
||||
if long_link_name then
|
||||
header.name = long_link_name
|
||||
long_link_name = nil
|
||||
end
|
||||
end
|
||||
if (header.typeflag == "file" or header.typeflag == "directory") then
|
||||
table.insert(files, header)
|
||||
end
|
||||
end
|
||||
tar_handle:close()
|
||||
return files, err
|
||||
end
|
||||
|
||||
---Extract files that match filename `fineNameInArchive:match("^"..filename)
|
||||
---@param tarname string Tar archive to work with
|
||||
---@param filename? string|table file name patern to extract
|
||||
---@param overwrite boolean overwrite existing files.
|
||||
---@param ignore? string|table file name patern to ignore
|
||||
---@param destdir string
|
||||
---@param newRoot? string
|
||||
function tar.extract(tarname, destdir, overwrite, filename, ignore, newRoot)
|
||||
checkArg(1, tarname, "string")
|
||||
checkArg(2, destdir, "string")
|
||||
checkArg(3, overwrite, "boolean")
|
||||
checkArg(4, filename, "string", "table", 'nil')
|
||||
checkArg(5, ignore, "string", "table", 'nil')
|
||||
checkArg(6, destdir, "string")
|
||||
checkArg(7, newRoot, "string", "nil")
|
||||
|
||||
---@param name string
|
||||
---@return boolean
|
||||
local function shouldExtractFile(name)
|
||||
local ok = true
|
||||
if (filename and type(filename) == "string") then
|
||||
if (not name:match("^" .. filename)) then ok = false end
|
||||
end
|
||||
if (filename and type(filename) == "table") then
|
||||
for _, fname in pairs(filename) do
|
||||
if (not name:match("^" .. fname)) then ok = false end
|
||||
end
|
||||
end
|
||||
if (ignore and type(ignore) == "string") then
|
||||
if (name:match("^" .. ignore)) then ok = false end
|
||||
end
|
||||
if (ignore and type(ignore) == "table") then
|
||||
for _, fname in pairs(ignore) do
|
||||
if (name:match("^" .. fname)) then ok = false end
|
||||
end
|
||||
end
|
||||
if (name == newRoot) then ok = false end
|
||||
return ok
|
||||
end
|
||||
|
||||
local tar_handle = io.open(tarname, "rb")
|
||||
if not tar_handle then return nil, "Error opening file " .. tarname end
|
||||
|
||||
local long_name, long_link_name
|
||||
---@type boolean?, string?
|
||||
local ok, err = true, nil
|
||||
local make_dir = fs.makeDir
|
||||
while true do
|
||||
local block
|
||||
repeat
|
||||
block = tar_handle:read(blocksize)
|
||||
until (not block) or checksum_header(block) > 256
|
||||
if not block then break end
|
||||
if #block < blocksize then
|
||||
ok, err = nil, "Invalid block size -- corrupted file?"
|
||||
break
|
||||
end
|
||||
local header
|
||||
header, err = read_header_block(block)
|
||||
if not header then
|
||||
ok = false
|
||||
break
|
||||
end
|
||||
|
||||
local file_data = tar_handle:read(math.ceil(header.size / blocksize) * blocksize):sub(1, header.size)
|
||||
|
||||
if header.typeflag == "long name" then
|
||||
long_name = nullterm(file_data)
|
||||
elseif header.typeflag == "long link name" then
|
||||
long_link_name = nullterm(file_data)
|
||||
else
|
||||
if long_name then
|
||||
header.name = long_name
|
||||
long_name = nil
|
||||
end
|
||||
if long_link_name then
|
||||
header.name = long_link_name
|
||||
long_link_name = nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
if (shouldExtractFile(header.name)) then
|
||||
local pathname = dir.path(destdir, header.name)
|
||||
if (newRoot) then --change the archive root
|
||||
pathname = pathname:gsub(newRoot, "")
|
||||
end
|
||||
|
||||
if header.typeflag == "directory" then
|
||||
if (pathname ~= newRoot) then make_dir(pathname) end
|
||||
elseif header.typeflag == "file" then
|
||||
local dirname = fs.getDir(pathname)
|
||||
if dirname ~= "" then
|
||||
make_dir(dirname)
|
||||
end
|
||||
if (overwrite or not (fs.exists(pathname) and not fs.isDir(pathname))) then
|
||||
local file_handle
|
||||
file_handle, err = io.open(pathname, "wb")
|
||||
if not file_handle then
|
||||
ok = nil
|
||||
break
|
||||
end
|
||||
file_handle:write(file_data):close()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
tar_handle:close()
|
||||
return ok, err
|
||||
end
|
||||
|
||||
return tar
|
9
tar/libtar.files.json
Normal file
9
tar/libtar.files.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"files": [
|
||||
[
|
||||
"lib/tar.lua",
|
||||
"/usr/lib/"
|
||||
]
|
||||
],
|
||||
"config": []
|
||||
}
|
9
tar/libtar.manifest
Normal file
9
tar/libtar.manifest
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
["manifestVersion"] = "1.0",
|
||||
["package"] = "libtar",
|
||||
["version"] = "1.0.0"
|
||||
["name"] = "libtar",
|
||||
["repo"] = "tree/master/tar",
|
||||
["description"] = "Librairy to extract and list the content of tar archive",
|
||||
["authors"] = "AR2000AR, luarock"
|
||||
}
|
1
usrpath/startup/usrpath.lua
Normal file
1
usrpath/startup/usrpath.lua
Normal file
@@ -0,0 +1 @@
|
||||
shell.setPath(shell.path() .. ":/usr/bin")
|
9
usrpath/usrpath.files.json
Normal file
9
usrpath/usrpath.files.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"files": [
|
||||
[
|
||||
"startup/usrpath.lua",
|
||||
"/startup/"
|
||||
]
|
||||
],
|
||||
"config": []
|
||||
}
|
9
usrpath/usrpath.manifest
Normal file
9
usrpath/usrpath.manifest
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
["manifestVersion"] = "1.0",
|
||||
["package"] = "usrpath",
|
||||
["version"] = "1.0.0"
|
||||
["name"] = "usrpath",
|
||||
["repo"] = "tree/master/usrpath",
|
||||
["description"] = "Add /usr/bin to shell.path",
|
||||
["authors"] = "AR2000AR"
|
||||
}
|
Reference in New Issue
Block a user