summaryrefslogtreecommitdiff
path: root/main.py
blob: 386c53fe86458f973abbc68041f1854af4b9ee80 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
#!/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__)

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.returncode)


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)
    log_cmd(cmd)

    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('Package already installed: %s', package)
            continue
        if source == 'REPOS':
            logger.debug('Would install from repo: %s', package)
            repo_pkgs.append(package)
        elif source == 'AUR':
            logger.debug('Would install from aur: %s', 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('AURACLE UNKNOWN, [%s], [%s]', line, m[4])
            sys.exit(1)

    return repo_pkgs, aur_pkgs

##################################################


def main():
    with open(get_conf('package-list')) as f:
        data = yaml.safe_load(f)

    if type(data) != dict:
        logger.fatal('Package must have a dict as root')
        sys.exit(1)

    if type(data.get('packages')) != list:
        logger.fatal('Invalid key packages in package list')
        sys.exit(1)

    pkgs = data.get('packages')

    # 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)

    if repo_pkgs:
        logger.info('Installing from the repos: %s', 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)
        log_cmd(cmd)
        m = re.match('[^:]*: (.*)', cmd.stdout)
        if not m:
            logger.error('Auracle clone had unexpected output [%s], skipping package [%s]',
                    cmd.stdout, package)
            continue
        cwd = m[1]
        env = { 'PKGDEST': pkgdest,
                'PATH': ':'.join(path),
                }
        log_cmd(subprocess.run(['makepkg', *makepkg_args, '--install', *pacman_args], env=env, cwd=cwd))

if __name__ == '__main__':
    if os.getuid() == 0:
        logger.fatal("Running as root isn't supported by makepkg, exiting")
        sys.exit(1)
    main()