1*4882a593Smuzhiyun# 2*4882a593Smuzhiyun# Copyright BitBake Contributors 3*4882a593Smuzhiyun# 4*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only 5*4882a593Smuzhiyun# 6*4882a593Smuzhiyun 7*4882a593Smuzhiyunimport layerindexlib 8*4882a593Smuzhiyun 9*4882a593Smuzhiyunimport argparse 10*4882a593Smuzhiyunimport logging 11*4882a593Smuzhiyunimport os 12*4882a593Smuzhiyunimport subprocess 13*4882a593Smuzhiyun 14*4882a593Smuzhiyunfrom bblayers.action import ActionPlugin 15*4882a593Smuzhiyun 16*4882a593Smuzhiyunlogger = logging.getLogger('bitbake-layers') 17*4882a593Smuzhiyun 18*4882a593Smuzhiyun 19*4882a593Smuzhiyundef plugin_init(plugins): 20*4882a593Smuzhiyun return LayerIndexPlugin() 21*4882a593Smuzhiyun 22*4882a593Smuzhiyun 23*4882a593Smuzhiyunclass LayerIndexPlugin(ActionPlugin): 24*4882a593Smuzhiyun """Subcommands for interacting with the layer index. 25*4882a593Smuzhiyun 26*4882a593Smuzhiyun This class inherits ActionPlugin to get do_add_layer. 27*4882a593Smuzhiyun """ 28*4882a593Smuzhiyun 29*4882a593Smuzhiyun def get_fetch_layer(self, fetchdir, url, subdir, fetch_layer, branch, shallow=False): 30*4882a593Smuzhiyun layername = self.get_layer_name(url) 31*4882a593Smuzhiyun if os.path.splitext(layername)[1] == '.git': 32*4882a593Smuzhiyun layername = os.path.splitext(layername)[0] 33*4882a593Smuzhiyun repodir = os.path.join(fetchdir, layername) 34*4882a593Smuzhiyun layerdir = os.path.join(repodir, subdir) 35*4882a593Smuzhiyun if not os.path.exists(repodir): 36*4882a593Smuzhiyun if fetch_layer: 37*4882a593Smuzhiyun cmd = ['git', 'clone'] 38*4882a593Smuzhiyun if shallow: 39*4882a593Smuzhiyun cmd.extend(['--depth', '1']) 40*4882a593Smuzhiyun if branch: 41*4882a593Smuzhiyun cmd.extend(['-b' , branch]) 42*4882a593Smuzhiyun cmd.extend([url, repodir]) 43*4882a593Smuzhiyun result = subprocess.call(cmd) 44*4882a593Smuzhiyun if result: 45*4882a593Smuzhiyun logger.error("Failed to download %s (%s)" % (url, branch)) 46*4882a593Smuzhiyun return None, None, None 47*4882a593Smuzhiyun else: 48*4882a593Smuzhiyun return subdir, layername, layerdir 49*4882a593Smuzhiyun else: 50*4882a593Smuzhiyun logger.plain("Repository %s needs to be fetched" % url) 51*4882a593Smuzhiyun return subdir, layername, layerdir 52*4882a593Smuzhiyun elif os.path.exists(layerdir): 53*4882a593Smuzhiyun return subdir, layername, layerdir 54*4882a593Smuzhiyun else: 55*4882a593Smuzhiyun logger.error("%s is not in %s" % (url, subdir)) 56*4882a593Smuzhiyun return None, None, None 57*4882a593Smuzhiyun 58*4882a593Smuzhiyun def do_layerindex_fetch(self, args): 59*4882a593Smuzhiyun """Fetches a layer from a layer index along with its dependent layers, and adds them to conf/bblayers.conf. 60*4882a593Smuzhiyun""" 61*4882a593Smuzhiyun 62*4882a593Smuzhiyun def _construct_url(baseurls, branches): 63*4882a593Smuzhiyun urls = [] 64*4882a593Smuzhiyun for baseurl in baseurls: 65*4882a593Smuzhiyun if baseurl[-1] != '/': 66*4882a593Smuzhiyun baseurl += '/' 67*4882a593Smuzhiyun 68*4882a593Smuzhiyun if not baseurl.startswith('cooker'): 69*4882a593Smuzhiyun baseurl += "api/" 70*4882a593Smuzhiyun 71*4882a593Smuzhiyun if branches: 72*4882a593Smuzhiyun baseurl += ";branch=%s" % ','.join(branches) 73*4882a593Smuzhiyun 74*4882a593Smuzhiyun urls.append(baseurl) 75*4882a593Smuzhiyun 76*4882a593Smuzhiyun return urls 77*4882a593Smuzhiyun 78*4882a593Smuzhiyun 79*4882a593Smuzhiyun # Set the default... 80*4882a593Smuzhiyun if args.branch: 81*4882a593Smuzhiyun branches = [args.branch] 82*4882a593Smuzhiyun else: 83*4882a593Smuzhiyun branches = (self.tinfoil.config_data.getVar('LAYERSERIES_CORENAMES') or 'master').split() 84*4882a593Smuzhiyun logger.debug('Trying branches: %s' % branches) 85*4882a593Smuzhiyun 86*4882a593Smuzhiyun ignore_layers = [] 87*4882a593Smuzhiyun if args.ignore: 88*4882a593Smuzhiyun ignore_layers.extend(args.ignore.split(',')) 89*4882a593Smuzhiyun 90*4882a593Smuzhiyun # Load the cooker DB 91*4882a593Smuzhiyun cookerIndex = layerindexlib.LayerIndex(self.tinfoil.config_data) 92*4882a593Smuzhiyun cookerIndex.load_layerindex('cooker://', load='layerDependencies') 93*4882a593Smuzhiyun 94*4882a593Smuzhiyun # Fast path, check if we already have what has been requested! 95*4882a593Smuzhiyun (dependencies, invalidnames) = cookerIndex.find_dependencies(names=args.layername, ignores=ignore_layers) 96*4882a593Smuzhiyun if not args.show_only and not invalidnames: 97*4882a593Smuzhiyun logger.plain("You already have the requested layer(s): %s" % args.layername) 98*4882a593Smuzhiyun return 0 99*4882a593Smuzhiyun 100*4882a593Smuzhiyun # The information to show is already in the cookerIndex 101*4882a593Smuzhiyun if invalidnames: 102*4882a593Smuzhiyun # General URL to use to access the layer index 103*4882a593Smuzhiyun # While there is ONE right now, we're expect users could enter several 104*4882a593Smuzhiyun apiurl = self.tinfoil.config_data.getVar('BBLAYERS_LAYERINDEX_URL').split() 105*4882a593Smuzhiyun if not apiurl: 106*4882a593Smuzhiyun logger.error("Cannot get BBLAYERS_LAYERINDEX_URL") 107*4882a593Smuzhiyun return 1 108*4882a593Smuzhiyun 109*4882a593Smuzhiyun remoteIndex = layerindexlib.LayerIndex(self.tinfoil.config_data) 110*4882a593Smuzhiyun 111*4882a593Smuzhiyun for remoteurl in _construct_url(apiurl, branches): 112*4882a593Smuzhiyun logger.plain("Loading %s..." % remoteurl) 113*4882a593Smuzhiyun remoteIndex.load_layerindex(remoteurl) 114*4882a593Smuzhiyun 115*4882a593Smuzhiyun if remoteIndex.is_empty(): 116*4882a593Smuzhiyun logger.error("Remote layer index %s is empty for branches %s" % (apiurl, branches)) 117*4882a593Smuzhiyun return 1 118*4882a593Smuzhiyun 119*4882a593Smuzhiyun lIndex = cookerIndex + remoteIndex 120*4882a593Smuzhiyun 121*4882a593Smuzhiyun (dependencies, invalidnames) = lIndex.find_dependencies(names=args.layername, ignores=ignore_layers) 122*4882a593Smuzhiyun 123*4882a593Smuzhiyun if invalidnames: 124*4882a593Smuzhiyun for invaluename in invalidnames: 125*4882a593Smuzhiyun logger.error('Layer "%s" not found in layer index' % invaluename) 126*4882a593Smuzhiyun return 1 127*4882a593Smuzhiyun 128*4882a593Smuzhiyun logger.plain("%s %s %s" % ("Layer".ljust(49), "Git repository (branch)".ljust(54), "Subdirectory")) 129*4882a593Smuzhiyun logger.plain('=' * 125) 130*4882a593Smuzhiyun 131*4882a593Smuzhiyun for deplayerbranch in dependencies: 132*4882a593Smuzhiyun layerBranch = dependencies[deplayerbranch][0] 133*4882a593Smuzhiyun 134*4882a593Smuzhiyun # TODO: Determine display behavior 135*4882a593Smuzhiyun # This is the local content, uncomment to hide local 136*4882a593Smuzhiyun # layers from the display. 137*4882a593Smuzhiyun #if layerBranch.index.config['TYPE'] == 'cooker': 138*4882a593Smuzhiyun # continue 139*4882a593Smuzhiyun 140*4882a593Smuzhiyun layerDeps = dependencies[deplayerbranch][1:] 141*4882a593Smuzhiyun 142*4882a593Smuzhiyun requiredby = [] 143*4882a593Smuzhiyun recommendedby = [] 144*4882a593Smuzhiyun for dep in layerDeps: 145*4882a593Smuzhiyun if dep.required: 146*4882a593Smuzhiyun requiredby.append(dep.layer.name) 147*4882a593Smuzhiyun else: 148*4882a593Smuzhiyun recommendedby.append(dep.layer.name) 149*4882a593Smuzhiyun 150*4882a593Smuzhiyun logger.plain('%s %s %s' % (("%s:%s:%s" % 151*4882a593Smuzhiyun (layerBranch.index.config['DESCRIPTION'], 152*4882a593Smuzhiyun layerBranch.branch.name, 153*4882a593Smuzhiyun layerBranch.layer.name)).ljust(50), 154*4882a593Smuzhiyun ("%s (%s)" % (layerBranch.layer.vcs_url, 155*4882a593Smuzhiyun layerBranch.actual_branch)).ljust(55), 156*4882a593Smuzhiyun layerBranch.vcs_subdir 157*4882a593Smuzhiyun )) 158*4882a593Smuzhiyun if requiredby: 159*4882a593Smuzhiyun logger.plain(' required by: %s' % ' '.join(requiredby)) 160*4882a593Smuzhiyun if recommendedby: 161*4882a593Smuzhiyun logger.plain(' recommended by: %s' % ' '.join(recommendedby)) 162*4882a593Smuzhiyun 163*4882a593Smuzhiyun if dependencies: 164*4882a593Smuzhiyun if args.fetchdir: 165*4882a593Smuzhiyun fetchdir = args.fetchdir 166*4882a593Smuzhiyun else: 167*4882a593Smuzhiyun fetchdir = self.tinfoil.config_data.getVar('BBLAYERS_FETCH_DIR') 168*4882a593Smuzhiyun if not fetchdir: 169*4882a593Smuzhiyun logger.error("Cannot get BBLAYERS_FETCH_DIR") 170*4882a593Smuzhiyun return 1 171*4882a593Smuzhiyun 172*4882a593Smuzhiyun if not os.path.exists(fetchdir): 173*4882a593Smuzhiyun os.makedirs(fetchdir) 174*4882a593Smuzhiyun 175*4882a593Smuzhiyun addlayers = [] 176*4882a593Smuzhiyun 177*4882a593Smuzhiyun for deplayerbranch in dependencies: 178*4882a593Smuzhiyun layerBranch = dependencies[deplayerbranch][0] 179*4882a593Smuzhiyun 180*4882a593Smuzhiyun if layerBranch.index.config['TYPE'] == 'cooker': 181*4882a593Smuzhiyun # Anything loaded via cooker is already local, skip it 182*4882a593Smuzhiyun continue 183*4882a593Smuzhiyun 184*4882a593Smuzhiyun subdir, name, layerdir = self.get_fetch_layer(fetchdir, 185*4882a593Smuzhiyun layerBranch.layer.vcs_url, 186*4882a593Smuzhiyun layerBranch.vcs_subdir, 187*4882a593Smuzhiyun not args.show_only, 188*4882a593Smuzhiyun layerBranch.actual_branch, 189*4882a593Smuzhiyun args.shallow) 190*4882a593Smuzhiyun if not name: 191*4882a593Smuzhiyun # Error already shown 192*4882a593Smuzhiyun return 1 193*4882a593Smuzhiyun addlayers.append((subdir, name, layerdir)) 194*4882a593Smuzhiyun if not args.show_only: 195*4882a593Smuzhiyun localargs = argparse.Namespace() 196*4882a593Smuzhiyun localargs.layerdir = [] 197*4882a593Smuzhiyun localargs.force = args.force 198*4882a593Smuzhiyun for subdir, name, layerdir in addlayers: 199*4882a593Smuzhiyun if os.path.exists(layerdir): 200*4882a593Smuzhiyun if subdir: 201*4882a593Smuzhiyun logger.plain("Adding layer \"%s\" (%s) to conf/bblayers.conf" % (subdir, layerdir)) 202*4882a593Smuzhiyun else: 203*4882a593Smuzhiyun logger.plain("Adding layer \"%s\" (%s) to conf/bblayers.conf" % (name, layerdir)) 204*4882a593Smuzhiyun localargs.layerdir.append(layerdir) 205*4882a593Smuzhiyun else: 206*4882a593Smuzhiyun break 207*4882a593Smuzhiyun 208*4882a593Smuzhiyun if localargs.layerdir: 209*4882a593Smuzhiyun self.do_add_layer(localargs) 210*4882a593Smuzhiyun 211*4882a593Smuzhiyun def do_layerindex_show_depends(self, args): 212*4882a593Smuzhiyun """Find layer dependencies from layer index. 213*4882a593Smuzhiyun""" 214*4882a593Smuzhiyun args.show_only = True 215*4882a593Smuzhiyun args.ignore = [] 216*4882a593Smuzhiyun args.fetchdir = "" 217*4882a593Smuzhiyun args.shallow = True 218*4882a593Smuzhiyun self.do_layerindex_fetch(args) 219*4882a593Smuzhiyun 220*4882a593Smuzhiyun def register_commands(self, sp): 221*4882a593Smuzhiyun parser_layerindex_fetch = self.add_command(sp, 'layerindex-fetch', self.do_layerindex_fetch, parserecipes=False) 222*4882a593Smuzhiyun parser_layerindex_fetch.add_argument('-n', '--show-only', help='show dependencies and do nothing else', action='store_true') 223*4882a593Smuzhiyun parser_layerindex_fetch.add_argument('-b', '--branch', help='branch name to fetch') 224*4882a593Smuzhiyun parser_layerindex_fetch.add_argument('-s', '--shallow', help='do only shallow clones (--depth=1)', action='store_true') 225*4882a593Smuzhiyun parser_layerindex_fetch.add_argument('-i', '--ignore', help='assume the specified layers do not need to be fetched/added (separate multiple layers with commas, no spaces)', metavar='LAYER') 226*4882a593Smuzhiyun parser_layerindex_fetch.add_argument('-f', '--fetchdir', help='directory to fetch the layer(s) into (will be created if it does not exist)') 227*4882a593Smuzhiyun parser_layerindex_fetch.add_argument('layername', nargs='+', help='layer to fetch') 228*4882a593Smuzhiyun 229*4882a593Smuzhiyun parser_layerindex_show_depends = self.add_command(sp, 'layerindex-show-depends', self.do_layerindex_show_depends, parserecipes=False) 230*4882a593Smuzhiyun parser_layerindex_show_depends.add_argument('-b', '--branch', help='branch name to fetch') 231*4882a593Smuzhiyun parser_layerindex_show_depends.add_argument('layername', nargs='+', help='layer to query') 232