#!/usr/bin/python3.9 # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # Copyright (c) 2010, Oracle and/or it's affiliates. All rights reserved. # # # bass-o-matic.py # A simple program to enumerate components in the userland gate and report # on dependency related information. # import os import sys import re import glob import subprocess import argparse import logging import json import multiprocessing try: from scandir import walk except ImportError: from os import walk from bass.component import Component logger = logging.getLogger('bass-o-matic') # Locate SCM directories containing Userland components by searching from # from a supplied top of tree for .p5m files. Once a .p5m file is located, # that directory is added to the list and no children are searched. def FindComponentPaths(path, debug=False, subdir='components', incremental=False, begin_commit=None, end_commit=None): expression = re.compile(r'.+\.p5m$', re.IGNORECASE) paths = [] if debug: logger.debug('searching %s for component directories', path) workspace_path = os.path.join(path, subdir) if incremental: cmd = ['git', '--no-pager', 'log', '--diff-filter=AMR', '--name-only', '--pretty=format:', '..'.join([begin_commit, end_commit])] proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=workspace_path, universal_newlines=True ) stdout, stderr = proc.communicate() if debug: if proc.returncode != 0: logger.debug('exit: %d, %s', proc.returncode, stderr) for line in stdout.splitlines(): line = line.rstrip() # git output might contain empty lines, so we skip them. if not line: continue # Every time component is added, modified or moved, Makefile has to be # present. However, this does not yet guarantee that the line is a # real component. filename = os.path.basename(line) dirname = os.path.dirname(line).rsplit(subdir + '/')[-1] if filename == 'Makefile': if glob.glob(os.path.join(workspace_path, dirname, '*.p5m')) and \ not os.path.isfile(os.path.join(workspace_path, dirname, 'pkg5.ignore')): paths.append(dirname) # Some components are using SCM checkout as a source code download method and # COMPONENT_REVISION is not bumped. With this, we will never rebuild them. # In order to rebuild them, we will look for such components and build them # every run. These components are located in openindiana category and we care # only about that category. One exception to this rule is meta-packages/history # component, which holds obsoleted components. We add it to paths manually for # that reason. cmd = ['git', 'grep', '-l', 'GIT_REPO *='] proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=workspace_path, universal_newlines=True ) stdout, stderr = proc.communicate() if debug: if proc.returncode != 0: logger.debug('exit: %d, %s', proc.returncode, stderr) for line in stdout.splitlines(): line = line.rstrip() # Only 'openindiana' category. category = line.split('/')[0] if category == 'openindiana': continue filename = os.path.basename(line) dirname = os.path.dirname(line) if filename == 'Makefile': if glob.glob(os.path.join(workspace_path, dirname, '*.p5m')) and \ not os.path.isfile(os.path.join(workspace_path, dirname, 'pkg5.ignore')): paths.append(os.path.dirname(line)) # Add meta-packages/history only if we build the main repository, where # subdir is equal to 'components'. if subdir == 'components': paths.append('meta-packages/history') # Add encumbered/meta-packages/history only if we build the encumbered repository if subdir == 'components/encumbered': paths.append('encumbered/meta-packages/history') paths = list(set(paths)) else: for dirpath, dirnames, filenames in walk(workspace_path): for name in filenames: if expression.match(name): if not os.path.isfile(os.path.join( dirpath, 'pkg5.ignore')): if debug: logger.debug('found %s', dirpath) paths.append(dirpath) del dirnames[:] break return sorted(paths) def main(): sys.stdout.flush() components = {} COMPONENTS_ALLOWED_PATHS = ['paths', 'dirs'] COMPONENTS_ALLOWED_FMRIS = ['fmris'] COMPONENTS_ALLOWED_DEPENDENCIES = ['dependencies'] COMPONENTS_ALLOWED_KEYWORDS = COMPONENTS_ALLOWED_PATHS + COMPONENTS_ALLOWED_FMRIS + COMPONENTS_ALLOWED_DEPENDENCIES parser = argparse.ArgumentParser() parser.add_argument('-w', '--workspace', default=os.getenv('WS_TOP'), help='Path to workspace') parser.add_argument('-l', '--components', default=None, choices=COMPONENTS_ALLOWED_KEYWORDS) parser.add_argument('--make', help='Makefile target to invoke') parser.add_argument('--pkg5', action='store_true',help='Invoke generation of metadata') parser.add_argument('--subdir', default='components', help='Directory holding components') parser.add_argument('-d', '--debug', action='store_true', default=False) parser.add_argument('--begin-commit', default=os.getenv('GIT_PREVIOUS_SUCCESSFUL_COMMIT', 'HEAD~1')) parser.add_argument('--end-commit', default='HEAD') args = parser.parse_args() workspace = args.workspace components_arg = args.components make_arg = args.make pkg5_arg = args.pkg5 subdir = args.subdir begin_commit = args.begin_commit end_commit = args.end_commit debug = args.debug log_level = logging.WARNING if debug: log_level = logging.DEBUG logging.basicConfig(level=log_level, format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s',) if make_arg: MAKE=os.getenv("MAKE","gmake") # https://www.gnu.org/software/make/manual/html_node/POSIX-Jobserver.html JOBFLAGS=re.match('.* (--jobserver-auth=([0-9]+),([0-9]+)) ?.*',os.getenv("MAKEFLAGS","")) if JOBFLAGS: JOBFDS=( JOBFLAGS.group(2), JOBFLAGS.group(3) ) JOBFLAGS=[JOBFLAGS.group(1)] else: JOBFDS=() JOBFLAGS=[] proc = subprocess.Popen([MAKE, '-s'] + [make_arg] + JOBFLAGS,pass_fds=JOBFDS) rc = proc.wait() sys.exit(rc) if pkg5_arg: component_path = os.getcwd().split(os.path.join(workspace, subdir))[-1].replace('/', '', 1) # the component may not be built directly but as a dependency of another component if os.path.isfile(os.path.join( os.getcwd(), 'pkg5.ignore')): sys.exit(0) component_pkg5 = os.path.join( os.getcwd(), 'pkg5') if os.path.isfile(component_pkg5): os.remove(component_pkg5) Component(FindComponentPaths(path=workspace, debug=debug, subdir=os.path.join(subdir, component_path))[0]) sys.exit(0) incremental = False if os.getenv('BASS_O_MATIC_MODE') == 'incremental': incremental = True if incremental: component_paths = FindComponentPaths(path=workspace, debug=debug, subdir=subdir, incremental=incremental, begin_commit=begin_commit, end_commit=end_commit) else: component_paths = FindComponentPaths(path=workspace, debug=debug, subdir=subdir) if components_arg: if components_arg in COMPONENTS_ALLOWED_PATHS: for path in component_paths: print('{0}'.format(path)) elif components_arg in COMPONENTS_ALLOWED_FMRIS: pool = multiprocessing.Pool(processes=multiprocessing.cpu_count()) components = pool.map(Component, component_paths) for component in components: for fmri in component.supplied_packages: print('{0}'.format(fmri)) elif components_arg in COMPONENTS_ALLOWED_DEPENDENCIES: dependencies = {} pool = multiprocessing.Pool(processes=multiprocessing.cpu_count()) components = pool.map(Component, component_paths) with open(os.path.join(workspace, subdir, 'mapping.json'), "r") as f: data = json.loads(f.read()) component_path = {} for entry in data: component_path[entry['fmri']] = entry['path'] for component in components: selfpath = component.path.split(os.path.join(workspace, subdir))[-1].replace('/', '', 1) # Some packages from OpenSolaris only exist in binary form in the pkg repository paths = set([component_path.get(i, "https://pkg.openindiana.org/hipster") for i in component.required_packages]) # Remove self from the set of paths it depends on paths.discard(selfpath) dependencies[selfpath] = sorted(list(paths)) dependencies_file = os.path.join(workspace, subdir, 'dependencies.json') with open(dependencies_file, 'w') as f: f.write(json.dumps(dependencies, sort_keys=True, indent=4)) sys.exit(0) sys.exit(1) if __name__ == '__main__': main()