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