New package manager for 1.2

This commit is contained in:
Kevin Lange 2017-07-10 18:53:52 +09:00
parent 80faf9c740
commit 364c8e2910
9 changed files with 312 additions and 294 deletions

View File

@ -1,46 +0,0 @@
#!/usr/bin/python3
import os
import sys
import toaru_package
if __name__ == "__main__":
if not os.getuid() == 0:
print(f"{sys.argv[0]}: must be root")
sys.exit(1)
toaru_package.fetch_manifest()
if len(sys.argv) < 2:
print("Available packages:")
packages = sorted(toaru_package.manifest['packages'].keys())
for k in packages:
print(f" - {k}","(installed)" if k in toaru_package.installed_packages else "")
sys.exit(0)
for arg in sys.argv[1:]:
if arg.startswith("--"):
if arg == "--dryrun":
toaru_package.dryrun = True
print("(Simulating installation.)")
if arg == "--gui":
toaru_package.is_gui = True
else:
toaru_package.process_package(arg)
toaru_package.calculate_upgrades()
for package in toaru_package.packages_to_install:
if package in toaru_package.upgrade_packages:
print(f"Upgrading {package}...")
toaru_package.install_package(package)
elif package in toaru_package.install_packages:
print(f"Installing {package}...")
toaru_package.install_package(package)
if not toaru_package.upgrade_packages and not toaru_package.install_packages:
print(f"{sys.argv[0]}: up to date")
toaru_package.write_status()

1
hdd/bin/msk Symbolic link
View File

@ -0,0 +1 @@
msk.py

View File

@ -17,8 +17,8 @@ char * __kernel_version_format = "%d.%d.%d-%s";
/* Version numbers X.Y.Z */
int __kernel_version_major = 1;
int __kernel_version_minor = 1;
int __kernel_version_lower = 2;
int __kernel_version_minor = 2;
int __kernel_version_lower = 0;
/* Kernel build suffix, which doesn't necessarily
* mean anything, but can be used to distinguish

View File

@ -1,37 +0,0 @@
#!/usr/bin/python
import tarfile
import json
import os
import signal
import sys
with tarfile.open(sys.argv[1], 'r:gz') as package:
try:
manifest_inf = package.getmember('manifest.json')
manifest_br = package.extractfile(manifest_inf)
manifest_str = manifest_br.read()
except KeyError:
print("Invalid MSK package: missing manifest")
sys.exit(1)
manifest = json.loads(manifest_str)
print(manifest['package'])
print('.'.join([str(x) for x in manifest['version']]))
print("contents:")
for member in package.getnames():
if not member.startswith('./'):
continue
print(member.replace('./','/',1))
print("Going to extract to /")
members = [member for member in package.getmembers() if member.name.startswith('./')]
package.extractall('/',members=members)
if os.path.exists('/tmp/.wallpaper.pid'):
with open('/tmp/.wallpaper.pid','r') as f:
pid = int(f.read().strip())
if pid:
os.kill(pid, signal.SIGUSR1)

0
userspace/py/bin/migrate.py Normal file → Executable file
View File

266
userspace/py/bin/msk.py Executable file
View File

@ -0,0 +1,266 @@
#!/usr/bin/python3.6
"""
Misaka Package Manager
Fetches and installs packages in compressed tarballs.
"""
import glob
import hashlib
import json
import os
import signal
import subprocess
import sys
import tarfile
server_url = 'http://toaruos.org/packages'
var_dir = '/var/msk'
manifest_path = f'{var_dir}/manifest.json'
manifest_url = f'{server_url}/manifest.json'
index_path = f'{var_dir}/index.json'
local_cache = f'{var_dir}/cache'
is_gui = False
def fetch(url, destination, check=False):
"""Fetch a package or other file from `url` and write it to `destination`."""
if is_gui:
args = ['fetch','-m','-o',destination,url]
_fetch = subprocess.Popen(args, stdout=subprocess.PIPE)
_progress = subprocess.Popen(['progress-bar.py',f'Fetching {os.path.basename(url)}...'], stdin=_fetch.stdout)
_fetch.stdout.close()
_progress.wait()
else:
args = ['fetch','-v','-o',destination,url]
subprocess.call(args)
if check:
sha = hashlib.sha512()
with open(destination,'rb') as f:
for chunk in iter(lambda: f.read(4096), b""):
sha.update(chunk)
if sha.hexdigest() != check:
return False
return True
def try_fetch(url, destination):
"""Make multiple attempts to download a file with an integrity checksum."""
attempts = 0
while attempts < 3:
if fetch(url, destination, check=True):
return True
else:
attempts += 1
return False
def extract_package(path, quiet=True):
with tarfile.open(path, 'r:gz') as package:
try:
manifest_inf = package.getmember('manifest.json')
manifest_br = package.extractfile(manifest_inf)
manifest_str = manifest_br.read()
except KeyError:
if not quiet:
print("Invalid MSK:", path)
return False
manifest = json.loads(manifest_str)
if not quiet:
print(manifest['package'])
print('.'.join([str(x) for x in manifest['version']]))
print("contents:")
for member in package.getnames():
if not member.startswith('./'):
continue
print(member.replace('./','/',1))
print("Going to extract to /")
members = [member for member in package.getmembers() if member.name.startswith('./')]
package.extractall('/',members=members)
def signal_desktop(pid_file='/tmp/.wallpaper.pid'):
if os.path.exists(pid_file):
with open(pid_file,'r') as f:
pid = int(f.read().strip())
if pid:
os.kill(pid, signal.SIGUSR1)
def version_str(version_lst):
return '.'.join([str(x) for x in version_lst])
def needs_var_dir():
if not os.path.exists(var_dir):
os.makedirs(var_dir)
def needs_local_cache():
needs_var_dir()
if not os.path.exists(local_cache):
os.makedirs(local_cache)
def fetch_manifest():
needs_var_dir()
fetch(manifest_url, manifest_path)
def get_manifest():
if not os.path.exists(manifest_path):
fetch_manifest()
with open(manifest_path, 'r') as f:
return json.loads(f.read())
def get_local_index():
if not os.path.exists(index_path):
return {}
else:
with open(index_path, 'r') as f:
return json.loads(f.read())
def commit_local_index(index):
needs_var_dir()
with open(index_path,'w') as f:
f.write(json.dumps(index))
def resolve_dependencies(packages, local_index, manifest, output=None):
if not output:
output = []
for package in packages:
for dep in manifest[package]['depends']:
if not dep in packages and dep not in output and dep not in local_index:
output = resolve_dependencies([dep], local_index, manifest, output)
output.append(package)
return output
def show_usage():
print(f"""msk - Download and install packages.
usage: {sys.argv[0]} install PACKAGE [PACKAGE ...]
{sys.argv[0]} update
{sys.argv[0]} remove PACKAGE [PACKAGE ...] (* unimplemented)
{sys.argv[0]} list
{sys.argv[0]} list-all
{sys.argv[0]} help
""")
return 1
def update_manifest():
fetch_manifest()
return 0
def remove_packages():
print("(Unimplemented)")
return 1
def fetch_package(name, manifest):
package = manifest[name]
fetch(f"{server_url}/{package['file']}", f"{local_cache}/{package['file']}", check=package['checksum'])
def install_fetched_package(name, manifest, local_index, install_candidates):
package = manifest[name]
# Extract tarball
extract_package(f"{local_cache}/{package['file']}")
# Remove the extracted package
os.remove(f"{local_cache}/{package['file']}")
# Update the index to mark the package as installed
if not name in local_index:
local_index[name] = {}
local_index[name].update(manifest[name])
if name in install_candidates:
local_index[name]['status'] = 'I' # Installed directly.
else:
if 'status' not in local_index[name]:
local_index[name]['status'] = 'i' # Installed as a dependency
def install_packages():
needs_local_cache()
local_index = get_local_index()
if local_index is None:
print("Failed to build a local index.")
return 1
manifest = get_manifest()
if manifest is None:
print("Failed to obtain manifest.")
return 1
# Verify the requested package names are valid
packages = sys.argv[2:]
install_candidates = []
for package in packages:
if package in local_index:
continue
if package not in manifest:
print("Package not found:", package)
continue
install_candidates.append(package)
# Nothing was valid or the input was empty...
if not install_candidates:
print("Nothing to install.")
return 1
# Go through each package and calculate dependency tree.
all_packages = resolve_dependencies(install_candidates, local_index, manifest)
# If the set of packages we are installing differs from what
# was requested (and valid), warn the user before continuing
# (this just means there were dependencies to also install)
if set(all_packages) != set(install_candidates):
print("Going to install:", ", ".join(all_packages))
if input("Continue? [y/N] ") not in ['y','Y','yes','YES']:
print("Stopping.")
return 1
# Download all of the requested packages
for name in all_packages:
print(f"Downloading {name}...")
fetch_package(name, manifest)
# Install the packages
for name in all_packages:
print(f"Installing {package['file']}...")
install_fetched_package(name, manifest, local_index, install_candidates)
# Commit
commit_local_index(local_index)
# Signal desktop for menu changes
signal_desktop()
return 0
def list_installed_packages():
local_index = get_local_index()
for name, package in local_index.items():
print(f"{name} {version_str(package['version'])} - {package['friendly-name']}")
return 0
def list_all_packages():
local_index = get_local_index()
manifest = get_manifest()
for name, package in manifest.items():
info = " " if name not in local_index else local_index[name]['status']
print(f"{info} {name} {version_str(package['version'])} - {package['friendly-name']}")
return 0
__commands = {
'update': update_manifest,
'help': show_usage,
'install': install_packages,
'remove': remove_packages,
'list': list_installed_packages,
'list-all': list_all_packages,
}
if __name__ == '__main__':
if len(sys.argv) < 2:
sys.exit(show_usage())
if sys.argv[1] in __commands:
sys.exit(__commands[sys.argv[1]]())
else:
print("Unrecognized command:", sys.argv[1])
sys.exit(1)

View File

@ -13,7 +13,7 @@ import cairo
import yutani
import text_region
import toaru_fonts
import toaru_package
import msk
from menu_bar import MenuBarWidget, MenuEntryAction, MenuEntrySubmenu, MenuEntryDivider, MenuWindow
from icon_cache import get_icon
@ -31,19 +31,23 @@ hilight_gradient_top = (93/255,163/255,236/255)
hilight_gradient_bottom = (56/255,137/255,220/55)
hilight_border_bottom = (47/255,106/255,167/255)
_local_index = None
_manifest = None
package_height = 50
def install(name):
toaru_package.process_package(name)
toaru_package.calculate_upgrades()
msk.needs_local_cache()
all_packages = msk.resolve_dependencies([name], _local_index, _manifest)
for package in toaru_package.packages_to_install:
if package in toaru_package.upgrade_packages or package in toaru_package.install_packages:
toaru_package.install_package(package)
for name in all_packages:
msk.fetch_package(name, _manifest)
toaru_package.write_status()
for name in all_packages:
msk.install_fetched_package(name, _manifest, _local_index, [name])
toaru_package.packages_to_install = []
toaru_package.upgrade_packages = []
toaru_package.install_packages = []
msk.commit_local_index(_local_index)
msk.signal_desktop()
class Package(object):
@ -55,17 +59,24 @@ class Package(object):
@property
def installed(self):
return self.name in toaru_package.installed_packages
return self.name in _local_index
@property
def description(self):
return _manifest[self.name]['description'].replace('\n',' ')
@property
def version(self):
a,b,c= toaru_package.manifest['packages'][self.name]['version']
a,b,c= _manifest[self.name]['version']
return f"{a}.{b}.{c}"
@property
def friendly_name(self):
return _manifest[self.name]['friendly-name']
@property
def text(self):
check = "" if not self.installed else ""
return f"{check} <b>{self.name}</b> - {self.version}"
return f"<h2><b>{self.friendly_name}</b> - {self.version}</h2>\n{self.description}"
def do_action(self):
if self.installed:
@ -135,20 +146,20 @@ class PackageManagerWindow(yutani.Window):
self.hilighted = None
def load_packages(self):
self.packages = sorted([Package(name) for name in toaru_package.manifest['packages'].keys()],key=lambda x: x.name)
self.packages = sorted([Package(name) for name in _manifest.keys()],key=lambda x: x.name)
def redraw_buf(self,clips=None):
if self.buf:
self.buf.destroy()
w = self.width - self.decorator.width()
self.buf = yutani.GraphicsBuffer(w,len(self.packages)*24)
self.buf = yutani.GraphicsBuffer(w,len(self.packages)*package_height)
surface = self.buf.get_cairo_surface()
ctx = cairo.Context(surface)
if clips:
for clip in clips:
ctx.rectangle(clip.x,clip.y,w,24)
ctx.rectangle(clip.x,clip.y,w,package_height)
ctx.clip()
ctx.rectangle(0,0,surface.get_width(),surface.get_height())
@ -160,7 +171,8 @@ class PackageManagerWindow(yutani.Window):
for f in self.packages:
f.y = offset_y
if not clips or f in clips:
tr = text_region.TextRegion(4,offset_y+4,w-4,20)
tr = text_region.TextRegion(54,offset_y+4,w-54,package_height-4)
tr.line_height = 20
if f.hilight:
gradient = cairo.LinearGradient(0,0,0,18)
gradient.add_color_stop_rgba(0.0,*hilight_gradient_top,1.0)
@ -168,26 +180,30 @@ class PackageManagerWindow(yutani.Window):
ctx.rectangle(0,offset_y+4,w,1)
ctx.set_source_rgb(*hilight_border_top)
ctx.fill()
ctx.rectangle(0,offset_y+4+20-1,w,1)
ctx.rectangle(0,offset_y+package_height-1,w,1)
ctx.set_source_rgb(*hilight_border_bottom)
ctx.fill()
ctx.save()
ctx.translate(0,offset_y+4+1)
ctx.rectangle(0,0,w,20-2)
ctx.rectangle(0,0,w,package_height-6)
ctx.set_source(gradient)
ctx.fill()
ctx.restore()
tr.font.font_color = 0xFFFFFFFF
else:
ctx.rectangle(0,offset_y+4,w,20)
ctx.rectangle(0,offset_y+4,w,package_height-4)
ctx.set_source_rgb(1,1,1)
ctx.fill()
tr.font.font_color = 0xFF000000
if f.installed:
package_icon = get_icon('package',48)
ctx.set_source_surface(package_icon,2,1+offset_y)
ctx.paint()
tr.set_richtext(f.text)
tr.set_one_line()
tr.set_ellipsis()
tr.draw(self.buf)
offset_y += 24
offset_y += package_height
def draw(self):
surface = self.get_cairo_surface()
@ -275,7 +291,7 @@ class PackageManagerWindow(yutani.Window):
for f in self.packages:
if offset_y > h: break
if y >= offset_y and y < offset_y + 24:
if y >= offset_y and y < offset_y + package_height:
if not f.hilight:
redraw.append(f)
if self.hilighted:
@ -285,7 +301,7 @@ class PackageManagerWindow(yutani.Window):
self.hilighted = f
hit = True
break
offset_y += 24
offset_y += package_height
if not hit:
if self.hilighted:
@ -316,8 +332,9 @@ if __name__ == '__main__':
d = yutani.Decor()
try:
toaru_package.fetch_manifest()
toaru_package.is_gui = True
msk.is_gui = True
_manifest = msk.get_manifest()
_local_index = msk.get_local_index()
packages = []
except:
packages = [NoPackages()]

View File

@ -13,7 +13,6 @@ import cairo
import yutani
import text_region
import toaru_fonts
import toaru_package
from color_picker import ColorPickerWindow

View File

@ -1,182 +0,0 @@
"""
Library for managing get-py packages.
"""
import json
import signal
import subprocess
import sys
import hashlib
import os
import stat
url = 'http://toaruos.org'
MANIFEST_PATH = '/tmp/.manifest.json'
INSTALLED_PATH = '/tmp/.installed.json'
installed_packages = []
packages_to_install = []
upgrade_packages = []
install_packages = []
dryrun = False
manifest = None
is_gui = False
def compare_version(left,right):
if left[0] > right[0]: return True
if left[0] == right[0] and left[1] > right[1]: return True
if left[0] == right[0] and left[1] == right[1] and left[2] > right[2]: return True
return False
def fetch_file(path, output, check=False, url=url, gui=False):
loop = 0
while loop < 3:
if gui:
args = ['fetch', '-m', '-o', output]
args.append(f'{url}/{path}')
fetch = subprocess.Popen(args, stdout=subprocess.PIPE)
progress = subprocess.Popen(['progress-bar.py',f"Fetching {path}..."], stdin=fetch.stdout)
fetch.stdout.close()
progress.wait()
else:
args = ['fetch','-o',output]
if check:
args.append('-v')
args.append(f'{url}/{path}')
subprocess.call(args)
if check:
s = hashlib.sha512()
with open(output,'rb') as f:
for chunk in iter(lambda: f.read(4096), b""):
s.update(chunk)
if s.hexdigest() != check:
if loop == 2:
print("Too many bad checksums, bailing.")
else:
print("Bad checksum, trying again...")
loop += 1
continue
break
def process_package(name):
if not name in manifest['packages']:
raise ValueError((f"Unknown package: {name}"))
package = manifest['packages'][name]
if 'deps' in package:
for dep in package['deps']:
if dep not in packages_to_install:
process_package(dep)
packages_to_install.append(name)
def install_file(file,source):
print(f"- Retrieving file {file[0]}","and checking hash" if file[2] else "")
if not dryrun:
fetch_file(file[0],file[1],file[2],source,is_gui)
def install_icon(steps):
if not os.path.exists('/usr/share/icons/external'):
subprocess.call(['mount','tmpfs','x','/usr/share/icons/external'])
if not os.path.exists('/usr/share/menus'):
subprocess.call(['mount','tmpfs','x','/usr/share/menus'])
if not steps[2] in ['accessories','games','demo','graphics','settings']:
return
if not os.path.exists(f'/usr/share/menus/{steps[2]}'):
os.mkdir(f'/usr/share/menus/{steps[2]}')
fetch_file(steps[3],f'/usr/share/icons/external/{steps[4]}.png')
with open(f'/usr/share/menus/{steps[2]}/{steps[4]}.desktop','w') as f:
f.write(f"{steps[4]},{steps[5]},{steps[1]}\n")
pid = None
if os.path.exists('/tmp/.wallpaper.pid'):
with open('/tmp/.wallpaper.pid','r') as f:
pid = int(f.read().strip())
if pid:
os.kill(pid, signal.SIGUSR1)
def run_install_step(step):
if step[0] == 'ln':
print(f"- Linking {step[2]} -> {step[1]}")
if not dryrun:
os.symlink(step[1],step[2])
elif step[0] == 'tmpfs':
print(f"- Mounting tmpfs at {step[1]}")
if not dryrun:
subprocess.call(['mount','tmpfs','x',step[1]])
elif step[0] == 'ext2':
print(f"- Mounting ext2 image {step[1]} at {step[2]}")
if not dryrun:
subprocess.call(['mount','ext2',step[1] + ',nocache',step[2]])
elif step[0] == 'chmodx':
print(f"- Making {step[1]} executable")
if not dryrun:
current = os.stat(step[1]).st_mode
os.chmod(step[1],current | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
elif step[0] == 'ungz':
print(f"- Decompressing {step[1]}")
if not dryrun:
subprocess.call(['ungz',step[1]])
elif step[0] == 'mkdir':
print(f"- Creating directory {step[1]}")
if not dryrun:
if not os.path.exists(step[1]):
os.mkdir(step[1])
elif step[0] == 'menu':
print(f"- Installing shortcut {step[1]} in {step[2]}")
if not dryrun:
install_icon(step)
else:
print("Unknown step:",step)
def install_package(name):
if not name in manifest['packages']:
print(f"Unknown package: {arg}")
sys.exit(1)
package = manifest['packages'][name]
if 'pre_steps' in package:
for step in package['pre_steps']:
run_install_step(step)
if 'files' in package:
url_source = url
if 'source' in package:
source = package['source']
if source not in manifest['sources']:
print(f"Bad source '{source}', will try locally.")
url_source = manifest['sources'][source]
for file in package['files']:
install_file(file,url_source)
if 'post_steps' in package:
for step in package['post_steps']:
run_install_step(step)
installed_packages[name] = package['version']
def calculate_upgrades():
for name in packages_to_install:
if name in installed_packages:
if compare_version(manifest['packages'][name]['version'],installed_packages[name]):
upgrade_packages.append(name)
else:
install_packages.append(name)
def write_status():
if not dryrun:
with open(INSTALLED_PATH,'w') as f:
json.dump(installed_packages, f)
def fetch_manifest():
global manifest
global installed_packages
fetch_file('manifest.json',MANIFEST_PATH)
with open(MANIFEST_PATH) as f:
manifest = json.load(f)
try:
with open(INSTALLED_PATH) as f:
installed_packages = json.load(f)
except:
installed_packages = {}
if not 'packages' in manifest:
raise ValueError("invalid manifest file")