1d8d2d382SMasahiro Yamada#!/usr/bin/env python3 251e46c7aSKees Cook# SPDX-License-Identifier: GPL-2.0+ 351e46c7aSKees Cook# 4*2a14f021SMauro Carvalho Chehab# pylint: disable=C0103,C0209 5*2a14f021SMauro Carvalho Chehab# 651e46c7aSKees Cook# This determines how many parallel tasks "make" is expecting, as it is 751e46c7aSKees Cook# not exposed via an special variables, reserves them all, runs a subprocess 851e46c7aSKees Cook# with PARALLELISM environment variable set, and releases the jobs back again. 951e46c7aSKees Cook# 1051e46c7aSKees Cook# https://www.gnu.org/software/make/manual/html_node/POSIX-Jobserver.html#POSIX-Jobserver 1151e46c7aSKees Cook 12*2a14f021SMauro Carvalho Chehab""" 13*2a14f021SMauro Carvalho ChehabInteracts with the POSIX jobserver during the Kernel build time. 14*2a14f021SMauro Carvalho Chehab 15*2a14f021SMauro Carvalho ChehabA "normal" jobserver task, like the one initiated by a make subrocess would do: 16*2a14f021SMauro Carvalho Chehab 17*2a14f021SMauro Carvalho Chehab - open read/write file descriptors to communicate with the job server; 18*2a14f021SMauro Carvalho Chehab - ask for one slot by calling: 19*2a14f021SMauro Carvalho Chehab claim = os.read(reader, 1) 20*2a14f021SMauro Carvalho Chehab - when the job finshes, call: 21*2a14f021SMauro Carvalho Chehab os.write(writer, b"+") # os.write(writer, claim) 22*2a14f021SMauro Carvalho Chehab 23*2a14f021SMauro Carvalho ChehabHere, the goal is different: This script aims to get the remaining number 24*2a14f021SMauro Carvalho Chehabof slots available, using all of them to run a command which handle tasks in 25*2a14f021SMauro Carvalho Chehabparallel. To to that, it has a loop that ends only after there are no 26*2a14f021SMauro Carvalho Chehabslots left. It then increments the number by one, in order to allow a 27*2a14f021SMauro Carvalho Chehabcall equivalent to make -j$((claim+1)), e.g. having a parent make creating 28*2a14f021SMauro Carvalho Chehab$claim child to do the actual work. 29*2a14f021SMauro Carvalho Chehab 30*2a14f021SMauro Carvalho ChehabThe end goal here is to keep the total number of build tasks under the 31*2a14f021SMauro Carvalho Chehablimit established by the initial make -j$n_proc call. 32*2a14f021SMauro Carvalho Chehab""" 33*2a14f021SMauro Carvalho Chehab 34*2a14f021SMauro Carvalho Chehabimport errno 35*2a14f021SMauro Carvalho Chehabimport os 36*2a14f021SMauro Carvalho Chehabimport subprocess 37*2a14f021SMauro Carvalho Chehabimport sys 38*2a14f021SMauro Carvalho Chehab 39*2a14f021SMauro Carvalho Chehab 40*2a14f021SMauro Carvalho Chehabclass JobserverExec: 41*2a14f021SMauro Carvalho Chehab """ 42*2a14f021SMauro Carvalho Chehab Claim all slots from make using POSIX Jobserver. 43*2a14f021SMauro Carvalho Chehab 44*2a14f021SMauro Carvalho Chehab The main methods here are: 45*2a14f021SMauro Carvalho Chehab - open(): reserves all slots; 46*2a14f021SMauro Carvalho Chehab - close(): method returns all used slots back to make; 47*2a14f021SMauro Carvalho Chehab - run(): executes a command setting PARALLELISM=<available slots jobs + 1> 48*2a14f021SMauro Carvalho Chehab """ 49*2a14f021SMauro Carvalho Chehab 50*2a14f021SMauro Carvalho Chehab def __init__(self): 51*2a14f021SMauro Carvalho Chehab """Initialize internal vars""" 52*2a14f021SMauro Carvalho Chehab self.claim = 0 53*2a14f021SMauro Carvalho Chehab self.jobs = b"" 54*2a14f021SMauro Carvalho Chehab self.reader = None 55*2a14f021SMauro Carvalho Chehab self.writer = None 56*2a14f021SMauro Carvalho Chehab self.is_open = False 57*2a14f021SMauro Carvalho Chehab 58*2a14f021SMauro Carvalho Chehab def open(self): 59*2a14f021SMauro Carvalho Chehab """Reserve all available slots to be claimed later on""" 60*2a14f021SMauro Carvalho Chehab 61*2a14f021SMauro Carvalho Chehab if self.is_open: 62*2a14f021SMauro Carvalho Chehab return 63*2a14f021SMauro Carvalho Chehab 6451e46c7aSKees Cook try: 6551e46c7aSKees Cook # Fetch the make environment options. 66*2a14f021SMauro Carvalho Chehab flags = os.environ["MAKEFLAGS"] 6751e46c7aSKees Cook # Look for "--jobserver=R,W" 6851e46c7aSKees Cook # Note that GNU Make has used --jobserver-fds and --jobserver-auth 6951e46c7aSKees Cook # so this handles all of them. 7051e46c7aSKees Cook opts = [x for x in flags.split(" ") if x.startswith("--jobserver")] 7151e46c7aSKees Cook 7251e46c7aSKees Cook # Parse out R,W file descriptor numbers and set them nonblocking. 73f8f4dc76SMasahiro Yamada # If the MAKEFLAGS variable contains multiple instances of the 74f8f4dc76SMasahiro Yamada # --jobserver-auth= option, the last one is relevant. 75f8f4dc76SMasahiro Yamada fds = opts[-1].split("=", 1)[1] 768c008988SMartin Liska 77*2a14f021SMauro Carvalho Chehab # Starting with GNU Make 4.4, named pipes are used for reader 78*2a14f021SMauro Carvalho Chehab # and writer. 798c008988SMartin Liska # Example argument: --jobserver-auth=fifo:/tmp/GMfifo8134 80*2a14f021SMauro Carvalho Chehab _, _, path = fds.partition("fifo:") 818c008988SMartin Liska 828c008988SMartin Liska if path: 83*2a14f021SMauro Carvalho Chehab self.reader = os.open(path, os.O_RDONLY | os.O_NONBLOCK) 84*2a14f021SMauro Carvalho Chehab self.writer = os.open(path, os.O_WRONLY) 858c008988SMartin Liska else: 86*2a14f021SMauro Carvalho Chehab self.reader, self.writer = [int(x) for x in fds.split(",", 1)] 8751e46c7aSKees Cook # Open a private copy of reader to avoid setting nonblocking 8851e46c7aSKees Cook # on an unexpecting process with the same reader fd. 89*2a14f021SMauro Carvalho Chehab self.reader = os.open("/proc/self/fd/%d" % (self.reader), 9051e46c7aSKees Cook os.O_RDONLY | os.O_NONBLOCK) 9151e46c7aSKees Cook 92*2a14f021SMauro Carvalho Chehab # Read out as many jobserver slots as possible 9351e46c7aSKees Cook while True: 9451e46c7aSKees Cook try: 95*2a14f021SMauro Carvalho Chehab slot = os.read(self.reader, 8) 96*2a14f021SMauro Carvalho Chehab self.jobs += slot 9751e46c7aSKees Cook except (OSError, IOError) as e: 9851e46c7aSKees Cook if e.errno == errno.EWOULDBLOCK: 9951e46c7aSKees Cook # Stop at the end of the jobserver queue. 10051e46c7aSKees Cook break 10151e46c7aSKees Cook # If something went wrong, give back the jobs. 102*2a14f021SMauro Carvalho Chehab if self.jobs: 103*2a14f021SMauro Carvalho Chehab os.write(self.writer, self.jobs) 10451e46c7aSKees Cook raise e 105*2a14f021SMauro Carvalho Chehab 10651e46c7aSKees Cook # Add a bump for our caller's reserveration, since we're just going 10751e46c7aSKees Cook # to sit here blocked on our child. 108*2a14f021SMauro Carvalho Chehab self.claim = len(self.jobs) + 1 109*2a14f021SMauro Carvalho Chehab 110*2a14f021SMauro Carvalho Chehab except (KeyError, IndexError, ValueError, OSError, IOError): 11151e46c7aSKees Cook # Any missing environment strings or bad fds should result in just 11251e46c7aSKees Cook # not being parallel. 113*2a14f021SMauro Carvalho Chehab self.claim = None 11451e46c7aSKees Cook 115*2a14f021SMauro Carvalho Chehab self.is_open = True 11651e46c7aSKees Cook 117*2a14f021SMauro Carvalho Chehab def close(self): 118*2a14f021SMauro Carvalho Chehab """Return all reserved slots to Jobserver""" 119*2a14f021SMauro Carvalho Chehab 120*2a14f021SMauro Carvalho Chehab if not self.is_open: 121*2a14f021SMauro Carvalho Chehab return 12251e46c7aSKees Cook 12351e46c7aSKees Cook # Return all the reserved slots. 124*2a14f021SMauro Carvalho Chehab if len(self.jobs): 125*2a14f021SMauro Carvalho Chehab os.write(self.writer, self.jobs) 12651e46c7aSKees Cook 127*2a14f021SMauro Carvalho Chehab self.is_open = False 128*2a14f021SMauro Carvalho Chehab 129*2a14f021SMauro Carvalho Chehab def __enter__(self): 130*2a14f021SMauro Carvalho Chehab self.open() 131*2a14f021SMauro Carvalho Chehab return self 132*2a14f021SMauro Carvalho Chehab 133*2a14f021SMauro Carvalho Chehab def __exit__(self, exc_type, exc_value, exc_traceback): 134*2a14f021SMauro Carvalho Chehab self.close() 135*2a14f021SMauro Carvalho Chehab 136*2a14f021SMauro Carvalho Chehab def run(self, cmd): 137*2a14f021SMauro Carvalho Chehab """ 138*2a14f021SMauro Carvalho Chehab Run a command setting PARALLELISM env variable to the number of 139*2a14f021SMauro Carvalho Chehab available job slots (claim) + 1, e.g. it will reserve claim slots 140*2a14f021SMauro Carvalho Chehab to do the actual build work, plus one to monitor its children. 141*2a14f021SMauro Carvalho Chehab """ 142*2a14f021SMauro Carvalho Chehab self.open() # Ensure that self.claim is set 143*2a14f021SMauro Carvalho Chehab 144*2a14f021SMauro Carvalho Chehab # We can only claim parallelism if there was a jobserver (i.e. a 145*2a14f021SMauro Carvalho Chehab # top-level "-jN" argument) and there were no other failures. Otherwise 146*2a14f021SMauro Carvalho Chehab # leave out the environment variable and let the child figure out what 147*2a14f021SMauro Carvalho Chehab # is best. 148*2a14f021SMauro Carvalho Chehab if self.claim: 149*2a14f021SMauro Carvalho Chehab os.environ["PARALLELISM"] = str(self.claim) 150*2a14f021SMauro Carvalho Chehab 151*2a14f021SMauro Carvalho Chehab return subprocess.call(cmd) 152*2a14f021SMauro Carvalho Chehab 153*2a14f021SMauro Carvalho Chehab 154*2a14f021SMauro Carvalho Chehabdef main(): 155*2a14f021SMauro Carvalho Chehab """Main program""" 156*2a14f021SMauro Carvalho Chehab with JobserverExec() as jobserver: 157*2a14f021SMauro Carvalho Chehab jobserver.run(sys.argv[1:]) 158*2a14f021SMauro Carvalho Chehab 159*2a14f021SMauro Carvalho Chehab 160*2a14f021SMauro Carvalho Chehabif __name__ == "__main__": 161*2a14f021SMauro Carvalho Chehab main() 162