summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHugo Hörnquist <hugo@lysator.liu.se>2022-01-24 19:47:38 +0100
committerHugo Hörnquist <hugo@lysator.liu.se>2022-01-24 19:47:38 +0100
commit1f261e7a14fd2618012246038f100afb957f667b (patch)
tree4c04b85dda837c4a0b23be0cfd5eeec7059729a8
downloadaur-runner-1f261e7a14fd2618012246038f100afb957f667b.tar.gz
aur-runner-1f261e7a14fd2618012246038f100afb957f667b.tar.xz
Initial commit.
-rw-r--r--TODO5
-rwxr-xr-xmain.py177
2 files changed, 182 insertions, 0 deletions
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..a4d68da
--- /dev/null
+++ b/TODO
@@ -0,0 +1,5 @@
+* Use Target
+ Possibly respect the TARGET attribute from auracle, and don't flag
+ those packages as dependencies
+* Log subprocess stdout and stderr
+ Currently only return codes are logged
diff --git a/main.py b/main.py
new file mode 100755
index 0000000..86ee9c8
--- /dev/null
+++ b/main.py
@@ -0,0 +1,177 @@
+#!/usr/bin/env python3
+
+import re
+import os
+import sys
+import subprocess
+import yaml
+import logging
+import logging.config
+
+
+def get_config_files():
+ subpath = ['aur-runner', 'config.yaml']
+ conf_files = []
+ # TODO better name for this, preferably matching the program name
+ if d := os.getenv('OVERRIDE_DIR'):
+ conf_files.append(os.path.join(d, *subpath))
+ if d := os.getenv('XDG_CONFIG_HOME'):
+ conf_files.append(os.path.join(d, *subpath))
+ if d := os.getenv('HOME'):
+ conf_files.append(os.path.join(d, '.config', *subpath))
+ if d := os.getenv('XDG_CONFIG_DIRS'):
+ for part in d.split(':'):
+ conf_files.append(os.path.join(part, *subpath))
+ conf_files.append(os.path.join('/etc/xdg', *subpath))
+ return conf_files
+
+
+default_config_yaml = """
+package-list: aur-packages.yaml
+source-file: '*virtual*'
+path:
+ - /usr/local/sbin
+ - /usr/local/bin
+ - /usr/bin
+ - /usr/bin/site_perl
+ - /usr/bin/vendor_perl
+ - /usr/bin/core_perl
+cache-dir: cache
+pkgdest-dir: dest
+logging:
+ version: 1
+ formatters:
+ detailed:
+ class: logging.Formatter
+ format: '[%(asctime)s] %(name)-15s %(levelname)-8s %(message)s'
+ datefmt: '%Y-%m-%dT%H:%M:%S'
+ handlers:
+ console:
+ class: logging.StreamHandler
+ formatter: detailed
+ level: DEBUG
+ root:
+ level: DEBUG
+ handlers:
+ - console
+"""
+
+
+default_config = yaml.safe_load(default_config_yaml)
+
+
+for conf_file in get_config_files():
+ try:
+ with open(conf_file) as f:
+ user_config = yaml.safe_load(f)
+ user_config['source-file'] = conf_file
+ break
+ except FileNotFoundError:
+ pass
+else:
+ # No config file found
+ user_config = {}
+
+
+def get_conf(key, default=None):
+ if default:
+ return user_config.get(key, default_config.get(key, default))
+ else:
+ return user_config.get(key, default_config[key])
+
+
+logging.config.dictConfig(get_conf('logging'))
+logger = logging.getLogger(__name__)
+
+
+auracle_args = ['--color=never']
+pacman_args = ['--noconfirm', '--asdeps', '--noprogressbar', '--needed']
+makepkg_args = ['--nocolor']
+
+
+def gather_packages(pkgs):
+ """
+ Figure out which packages to install, and in which order
+
+ Takes a list of package names, and returns to lists, one of
+ packages which are alreaddy available in the repos, and a list of
+ packages which needs to be fetched from the aur. Both are in
+ dependency order, and all repo packages should be assumed to be
+ required for the aur packages to be build/installed.
+ """
+ repo_pkgs = []
+ aur_pkgs = []
+ cmd = subprocess.run(['auracle', *auracle_args, 'buildorder', *pkgs], capture_output=True, text=True)
+
+ for line in cmd.stdout.split('\n'):
+ if not line: continue
+ m = re.match(r'(SATISFIED|TARGET)?(AUR|REPOS|UNKNOWN) ([^ ]*) ?(.*)', line)
+ status = m[1]
+ source = m[2]
+ package = m[3]
+ if status == 'SATISFIED':
+ logger.debug(f'Package already installed: {package}')
+ continue
+ if source == 'REPOS':
+ logger.debug(f'Would install from repo: {package}')
+ repo_pkgs.append(package)
+ elif source == 'AUR':
+ logger.debug(f'Would install from aur: {package}')
+ aur_pkgs.append(package)
+ elif source == 'UNKNOWN':
+ # auracle buildorder guile-chickadee fails with
+ # 'UNKNOWN guile-opengl guile-chickadee'
+ # Since guile-opengl is provided by guile-opengl-git, and
+ # auracle doesn't find that. The information is however there
+ # on the aur: https://aur.archlinux.org/packages/guile-chickadee/
+ logger.fatal('Something went wrong', line, m[4])
+ sys.exit(1)
+
+ return repo_pkgs, aur_pkgs
+
+##################################################
+
+
+def main():
+ with open(get_conf('package-list')) as f:
+ pkgs = yaml.safe_load(f)
+
+ if type(pkgs) != list:
+ logger.fatal('Package list is not a list')
+ os.exit(1)
+
+ # os.path.join discards earlier components whenever it finds an absolute path
+ cachedir = os.path.join(os.getcwd(), get_conf('cache-dir'))
+ pkgdest = os.path.join(os.getcwd(), get_conf('pkgdest-dir'))
+
+ for dir in [cachedir, pkgdest]:
+ try:
+ os.mkdir(dir)
+ except FileExistsError:
+ pass
+
+ path = get_conf('path')
+
+ repo_pkgs, aur_pkgs = gather_packages(pkgs)
+
+ def log_cmd(cmd):
+ if cmd.returncode == 0:
+ logger.info('%s finished without errors', cmd.args)
+ else:
+ logger.warning('%s exited with status code %s', cmd.args, cmd.retruncode)
+
+ if repo_pkgs:
+ logger.info(f'Installing from the repos: {repo_pkgs}')
+ log_cmd(subprocess.run(['sudo', 'pacman', *pacman_args, '-S', *repo_pkgs]))
+
+ for package in aur_pkgs:
+ cmd = subprocess.run(['auracle', *auracle_args, '--chdir', cachedir, 'clone', package],
+ capture_output=True, text=True)
+ cwd = re.match('[^:]*: (.*)', cmd.stdout)[1]
+ env = { 'PKGDEST': pkgdest,
+ 'PATH': ':'.join(path),
+ }
+ log_cmd(subprocess.run(['makepkg', *makepkg_args, '--install', *pacman_args], env=env, cwd=cwd))
+
+if __name__ == '__main__':
+ main()