1*51e46c7aSKees Cook#!/usr/bin/env python 2*51e46c7aSKees Cook# SPDX-License-Identifier: GPL-2.0+ 3*51e46c7aSKees Cook# 4*51e46c7aSKees Cook# This determines how many parallel tasks "make" is expecting, as it is 5*51e46c7aSKees Cook# not exposed via an special variables, reserves them all, runs a subprocess 6*51e46c7aSKees Cook# with PARALLELISM environment variable set, and releases the jobs back again. 7*51e46c7aSKees Cook# 8*51e46c7aSKees Cook# https://www.gnu.org/software/make/manual/html_node/POSIX-Jobserver.html#POSIX-Jobserver 9*51e46c7aSKees Cookfrom __future__ import print_function 10*51e46c7aSKees Cookimport os, sys, errno 11*51e46c7aSKees Cookimport subprocess 12*51e46c7aSKees Cook 13*51e46c7aSKees Cook# Extract and prepare jobserver file descriptors from envirnoment. 14*51e46c7aSKees Cookclaim = 0 15*51e46c7aSKees Cookjobs = b"" 16*51e46c7aSKees Cooktry: 17*51e46c7aSKees Cook # Fetch the make environment options. 18*51e46c7aSKees Cook flags = os.environ['MAKEFLAGS'] 19*51e46c7aSKees Cook 20*51e46c7aSKees Cook # Look for "--jobserver=R,W" 21*51e46c7aSKees Cook # Note that GNU Make has used --jobserver-fds and --jobserver-auth 22*51e46c7aSKees Cook # so this handles all of them. 23*51e46c7aSKees Cook opts = [x for x in flags.split(" ") if x.startswith("--jobserver")] 24*51e46c7aSKees Cook 25*51e46c7aSKees Cook # Parse out R,W file descriptor numbers and set them nonblocking. 26*51e46c7aSKees Cook fds = opts[0].split("=", 1)[1] 27*51e46c7aSKees Cook reader, writer = [int(x) for x in fds.split(",", 1)] 28*51e46c7aSKees Cook # Open a private copy of reader to avoid setting nonblocking 29*51e46c7aSKees Cook # on an unexpecting process with the same reader fd. 30*51e46c7aSKees Cook reader = os.open("/proc/self/fd/%d" % (reader), 31*51e46c7aSKees Cook os.O_RDONLY | os.O_NONBLOCK) 32*51e46c7aSKees Cook 33*51e46c7aSKees Cook # Read out as many jobserver slots as possible. 34*51e46c7aSKees Cook while True: 35*51e46c7aSKees Cook try: 36*51e46c7aSKees Cook slot = os.read(reader, 8) 37*51e46c7aSKees Cook jobs += slot 38*51e46c7aSKees Cook except (OSError, IOError) as e: 39*51e46c7aSKees Cook if e.errno == errno.EWOULDBLOCK: 40*51e46c7aSKees Cook # Stop at the end of the jobserver queue. 41*51e46c7aSKees Cook break 42*51e46c7aSKees Cook # If something went wrong, give back the jobs. 43*51e46c7aSKees Cook if len(jobs): 44*51e46c7aSKees Cook os.write(writer, jobs) 45*51e46c7aSKees Cook raise e 46*51e46c7aSKees Cook # Add a bump for our caller's reserveration, since we're just going 47*51e46c7aSKees Cook # to sit here blocked on our child. 48*51e46c7aSKees Cook claim = len(jobs) + 1 49*51e46c7aSKees Cookexcept (KeyError, IndexError, ValueError, OSError, IOError) as e: 50*51e46c7aSKees Cook # Any missing environment strings or bad fds should result in just 51*51e46c7aSKees Cook # not being parallel. 52*51e46c7aSKees Cook pass 53*51e46c7aSKees Cook 54*51e46c7aSKees Cook# We can only claim parallelism if there was a jobserver (i.e. a top-level 55*51e46c7aSKees Cook# "-jN" argument) and there were no other failures. Otherwise leave out the 56*51e46c7aSKees Cook# environment variable and let the child figure out what is best. 57*51e46c7aSKees Cookif claim > 0: 58*51e46c7aSKees Cook os.environ['PARALLELISM'] = '%d' % (claim) 59*51e46c7aSKees Cook 60*51e46c7aSKees Cookrc = subprocess.call(sys.argv[1:]) 61*51e46c7aSKees Cook 62*51e46c7aSKees Cook# Return all the reserved slots. 63*51e46c7aSKees Cookif len(jobs): 64*51e46c7aSKees Cook os.write(writer, jobs) 65*51e46c7aSKees Cook 66*51e46c7aSKees Cooksys.exit(rc) 67