import argparse import http.client import json import logging import os import subprocess import urllib.parse from bblayers.action import ActionPlugin logger = logging.getLogger('bitbake-layers') def plugin_init(plugins): return LayerIndexPlugin() class LayerIndexPlugin(ActionPlugin): """Subcommands for interacting with the layer index. This class inherits ActionPlugin to get do_add_layer. """ def get_json_data(self, apiurl): proxy_settings = os.environ.get("http_proxy", None) conn = None _parsedurl = urllib.parse.urlparse(apiurl) path = _parsedurl.path query = _parsedurl.query def parse_url(url): parsedurl = urllib.parse.urlparse(url) if parsedurl.netloc[0] == '[': host, port = parsedurl.netloc[1:].split(']', 1) if ':' in port: port = port.rsplit(':', 1)[1] else: port = None else: if parsedurl.netloc.count(':') == 1: (host, port) = parsedurl.netloc.split(":") else: host = parsedurl.netloc port = None return (host, 80 if port is None else int(port)) if proxy_settings is None: host, port = parse_url(apiurl) conn = http.client.HTTPConnection(host, port) conn.request("GET", path + "?" + query) else: host, port = parse_url(proxy_settings) conn = http.client.HTTPConnection(host, port) conn.request("GET", apiurl) r = conn.getresponse() if r.status != 200: raise Exception("Failed to read " + path + ": %d %s" % (r.status, r.reason)) return json.loads(r.read()) def get_layer_deps(self, layername, layeritems, layerbranches, layerdependencies, branchnum, selfname=False): def layeritems_info_id(items_name, layeritems): litems_id = None for li in layeritems: if li['name'] == items_name: litems_id = li['id'] break return litems_id def layerbranches_info(items_id, layerbranches): lbranch = {} for lb in layerbranches: if lb['layer'] == items_id and lb['branch'] == branchnum: lbranch['id'] = lb['id'] lbranch['vcs_subdir'] = lb['vcs_subdir'] break return lbranch def layerdependencies_info(lb_id, layerdependencies): ld_deps = [] for ld in layerdependencies: if ld['layerbranch'] == lb_id and not ld['dependency'] in ld_deps: ld_deps.append(ld['dependency']) if not ld_deps: logger.error("The dependency of layerDependencies is not found.") return ld_deps def layeritems_info_name_subdir(items_id, layeritems): litems = {} for li in layeritems: if li['id'] == items_id: litems['vcs_url'] = li['vcs_url'] litems['name'] = li['name'] break return litems if selfname: selfid = layeritems_info_id(layername, layeritems) lbinfo = layerbranches_info(selfid, layerbranches) if lbinfo: selfsubdir = lbinfo['vcs_subdir'] else: logger.error("%s is not found in the specified branch" % layername) return selfurl = layeritems_info_name_subdir(selfid, layeritems)['vcs_url'] if selfurl: return selfurl, selfsubdir else: logger.error("Cannot get layer %s git repo and subdir" % layername) return ldict = {} itemsid = layeritems_info_id(layername, layeritems) if not itemsid: return layername, None lbid = layerbranches_info(itemsid, layerbranches) if lbid: lbid = layerbranches_info(itemsid, layerbranches)['id'] else: logger.error("%s is not found in the specified branch" % layername) return None, None for dependency in layerdependencies_info(lbid, layerdependencies): lname = layeritems_info_name_subdir(dependency, layeritems)['name'] lurl = layeritems_info_name_subdir(dependency, layeritems)['vcs_url'] lsubdir = layerbranches_info(dependency, layerbranches)['vcs_subdir'] ldict[lname] = lurl, lsubdir return None, ldict def get_fetch_layer(self, fetchdir, url, subdir, fetch_layer): layername = self.get_layer_name(url) if os.path.splitext(layername)[1] == '.git': layername = os.path.splitext(layername)[0] repodir = os.path.join(fetchdir, layername) layerdir = os.path.join(repodir, subdir) if not os.path.exists(repodir): if fetch_layer: result = subprocess.call('git clone %s %s' % (url, repodir), shell = True) if result: logger.error("Failed to download %s" % url) return None, None else: return layername, layerdir else: logger.plain("Repository %s needs to be fetched" % url) return layername, layerdir elif os.path.exists(layerdir): return layername, layerdir else: logger.error("%s is not in %s" % (url, subdir)) return None, None def do_layerindex_fetch(self, args): """Fetches a layer from a layer index along with its dependent layers, and adds them to conf/bblayers.conf. """ apiurl = self.tinfoil.config_data.getVar('BBLAYERS_LAYERINDEX_URL', True) if not apiurl: logger.error("Cannot get BBLAYERS_LAYERINDEX_URL") return 1 else: if apiurl[-1] != '/': apiurl += '/' apiurl += "api/" apilinks = self.get_json_data(apiurl) branches = self.get_json_data(apilinks['branches']) branchnum = 0 for branch in branches: if branch['name'] == args.branch: branchnum = branch['id'] break if branchnum == 0: validbranches = ', '.join([branch['name'] for branch in branches]) logger.error('Invalid layer branch name "%s". Valid branches: %s' % (args.branch, validbranches)) return 1 ignore_layers = [] for collection in self.tinfoil.config_data.getVar('BBFILE_COLLECTIONS', True).split(): lname = self.tinfoil.config_data.getVar('BBLAYERS_LAYERINDEX_NAME_%s' % collection, True) if lname: ignore_layers.append(lname) if args.ignore: ignore_layers.extend(args.ignore.split(',')) layeritems = self.get_json_data(apilinks['layerItems']) layerbranches = self.get_json_data(apilinks['layerBranches']) layerdependencies = self.get_json_data(apilinks['layerDependencies']) invaluenames = [] repourls = {} printlayers = [] def query_dependencies(layers, layeritems, layerbranches, layerdependencies, branchnum): depslayer = [] for layername in layers: invaluename, layerdict = self.get_layer_deps(layername, layeritems, layerbranches, layerdependencies, branchnum) if layerdict: repourls[layername] = self.get_layer_deps(layername, layeritems, layerbranches, layerdependencies, branchnum, selfname=True) for layer in layerdict: if not layer in ignore_layers: depslayer.append(layer) printlayers.append((layername, layer, layerdict[layer][0], layerdict[layer][1])) if not layer in ignore_layers and not layer in repourls: repourls[layer] = (layerdict[layer][0], layerdict[layer][1]) if invaluename and not invaluename in invaluenames: invaluenames.append(invaluename) return depslayer depslayers = query_dependencies(args.layername, layeritems, layerbranches, layerdependencies, branchnum) while depslayers: depslayer = query_dependencies(depslayers, layeritems, layerbranches, layerdependencies, branchnum) depslayers = depslayer if invaluenames: for invaluename in invaluenames: logger.error('Layer "%s" not found in layer index' % invaluename) return 1 logger.plain("%s %s %s %s" % ("Layer".ljust(19), "Required by".ljust(19), "Git repository".ljust(54), "Subdirectory")) logger.plain('=' * 115) for layername in args.layername: layerurl = repourls[layername] logger.plain("%s %s %s %s" % (layername.ljust(20), '-'.ljust(20), layerurl[0].ljust(55), layerurl[1])) printedlayers = [] for layer, dependency, gitrepo, subdirectory in printlayers: if dependency in printedlayers: continue logger.plain("%s %s %s %s" % (dependency.ljust(20), layer.ljust(20), gitrepo.ljust(55), subdirectory)) printedlayers.append(dependency) if repourls: fetchdir = self.tinfoil.config_data.getVar('BBLAYERS_FETCH_DIR', True) if not fetchdir: logger.error("Cannot get BBLAYERS_FETCH_DIR") return 1 if not os.path.exists(fetchdir): os.makedirs(fetchdir) addlayers = [] for repourl, subdir in repourls.values(): name, layerdir = self.get_fetch_layer(fetchdir, repourl, subdir, not args.show_only) if not name: # Error already shown return 1 addlayers.append((subdir, name, layerdir)) if not args.show_only: for subdir, name, layerdir in set(addlayers): if os.path.exists(layerdir): if subdir: logger.plain("Adding layer \"%s\" to conf/bblayers.conf" % subdir) else: logger.plain("Adding layer \"%s\" to conf/bblayers.conf" % name) localargs = argparse.Namespace() localargs.layerdir = layerdir self.do_add_layer(localargs) else: break def do_layerindex_show_depends(self, args): """Find layer dependencies from layer index. """ args.show_only = True args.ignore = [] self.do_layerindex_fetch(args) def register_commands(self, sp): parser_layerindex_fetch = self.add_command(sp, 'layerindex-fetch', self.do_layerindex_fetch) parser_layerindex_fetch.add_argument('-n', '--show-only', help='show dependencies and do nothing else', action='store_true') parser_layerindex_fetch.add_argument('-b', '--branch', help='branch name to fetch (default %(default)s)', default='master') 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') parser_layerindex_fetch.add_argument('layername', nargs='+', help='layer to fetch') parser_layerindex_show_depends = self.add_command(sp, 'layerindex-show-depends', self.do_layerindex_show_depends) parser_layerindex_show_depends.add_argument('-b', '--branch', help='branch name to fetch (default %(default)s)', default='master') parser_layerindex_show_depends.add_argument('layername', nargs='+', help='layer to query')