#!/usr/bin/env python3 # SPDX-License-Identifier: LicenseRef-PD-hp OR CC0-1.0 OR 0BSD OR MIT-0 OR MIT deps = { 'attr':(), # XXX: fails under musl 'bash':{'xz'}, 'benchmarks':{'pcre2'}, 'binutils':{'pkgconf','xz','zlib','zstd'}, 'bison':{'m4'}, 'brotli':(), 'bzip2':(), 'bzip3':(), 'check':{'pkgconf'}, 'cmake':{'libarchive','curl','expat','ncurses','openssl'}, 'coreutils':{'openssl'}, 'cpython':{'ncurses','zlib','bzip2','xz','openssl'}, 'curl':{'zstd','zlib','openssl','nghttp2'}, 'dash':(), 'diffutils':(), 'elfutils':{'bzip2','zlib','zstd','m4','pkgconf'}, # XXX: fails under musl 'emacs':{'git','ncurses','sqlite','zlib'}, 'expat':(), 'ffi':(), 'gettext':{'ncurses','xz'}, 'git':{'expat','pcre2','curl','tcl'}, 'gmp':{'m4'}, 'grep':{'pcre2'}, 'icu':{'pkgconf'}, 'jpeg-6b':(), 'libarchive':{'pkgconf','bzip2','zstd'}, 'libcap':(), 'libedit':{'pkgconf','ncurses'}, 'libevent':{'openssl'}, 'libpipeline':{'check','pkgconf'}, 'libuev':(), 'libuv':{'pkgconf'}, # 'libxml2':(), # needs specifically python 3.11? 'lua':(), 'lz4':(), 'm4':(), 'make':(), 'mg':{'pkgconf','ncurses'}, 'ncurses':{'pkgconf'}, 'nghttp2':{'pkgconf','openssl','libuev'}, 'openssh':{'pkgconf','openssl','zlib'}, 'openssl':{'zstd','zlib'}, 'pcre':(), 'pcre2':{'sed'}, 'perl':{'zlib','bzip2'}, 'pkgconf':(), 'procps':{'ncurses','pkgconf'}, 'quickjs':(), 'sed':{'coreutils'}, # true from coreutils allows sed to detect valgrind issue 'shadow':{'pkgconf','attr'}, 'simdutf':(), 'sqlite':{'icu','tcl'}, 'tcl':{'zlib'}, # 'texinfo':{'ncurses'}, # msgfmt breaks vim 'tmux':{'pkgconf','libevent','ncurses'}, 'toybox':(), # 'util_linux':{'ncurses'}, 'vim':{'lua','ncurses'}, 'wg14_signals':(), 'xml_parser':('perl','expat'), 'xz':(), 'zlib':(), 'zsh':{'ncurses','pcre2'}, 'zstd':(), } import os import sys import subprocess import multiprocessing try: threads = len(os.sched_getaffinity(0)) except: threads = multiprocessing.cpu_count() threads = os.getenv('THREADS',default=threads) threads = int(threads) if threads < 1: threads = 1 todo = set(package for package in deps) done = set() q = multiprocessing.Queue() package2process = {} package2threads = {} threadsused = set() os.makedirs(f'build/logs',exist_ok=True) def threadfmt(threads): result = [] lastrange = None for t in sorted(threads): if lastrange is None: lastrange = t,t continue if t == lastrange[1]+1: lastrange = lastrange[0],t continue if lastrange[0] == lastrange[1]: result += [f'{lastrange[0]}'] else: result += [f'{lastrange[0]}-{lastrange[1]}'] lastrange = t,t if lastrange[0] == lastrange[1]: result += [f'{lastrange[0]}'] else: result += [f'{lastrange[0]}-{lastrange[1]}'] return ','.join(result) dryrun = True def doit(package,packagethreads): with open(f'build/logs/{package}.log','w') as f: if dryrun: status = 0 else: child = subprocess.run(['taskset','-c',threadfmt(packagethreads),f'./build_{package}.sh'],stdout=f,stderr=f) status = child.returncode f.write(f'return code {status}') q.put((package,status)) def readytodo(package): return all(dep in done for dep in deps.get(package,[])) while len(todo) > 0 or len(threadsused) > 0: print(f'packages in progress: [{" ".join(sorted(package2process))}] and not started yet: [{" ".join(sorted(todo))}]') if len(todo) > 0 and len(threadsused) < threads: # XXX: use depth and package size to guide sorting todonow = [package for package in sorted(todo) if readytodo(package)] if len(todonow) > 0: threadsnow = [set() for j in range(len(todonow))] pos = 0 for j in range(threads): # XXX: need some heuristics to limit jobs per process here if j not in threadsused: threadsnow[pos].add(j) pos = (pos+1)%len(todonow) for package,packagethreads in zip(todonow,threadsnow): print(f'starting {package} using threads {threadfmt(packagethreads)}') process = multiprocessing.Process(target=doit,args=(package,packagethreads)) process.start() package2process[package] = process package2threads[package] = packagethreads assert threadsused.isdisjoint(packagethreads) threadsused = threadsused.union(packagethreads) todo.remove(package) continue assert len(threadsused) > 0 # XXX: check for cycles at top package,status = q.get() packagethreads = package2threads[package] description = 'stopped' if status else 'finished' print(f'{description} {package} status {status} using threads {threadfmt(packagethreads)}') sys.stdout.flush() package2process[package].join() del package2process[package] threadsused -= packagethreads done.add(package)