1*4882a593Smuzhiyun# Copyright (C) 2016-2018 Wind River Systems, Inc. 2*4882a593Smuzhiyun# 3*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only 4*4882a593Smuzhiyun# 5*4882a593Smuzhiyun 6*4882a593Smuzhiyunimport logging 7*4882a593Smuzhiyunimport os 8*4882a593Smuzhiyun 9*4882a593Smuzhiyunfrom collections import defaultdict 10*4882a593Smuzhiyun 11*4882a593Smuzhiyunfrom urllib.parse import unquote, urlparse 12*4882a593Smuzhiyun 13*4882a593Smuzhiyunimport layerindexlib 14*4882a593Smuzhiyun 15*4882a593Smuzhiyunimport layerindexlib.plugin 16*4882a593Smuzhiyun 17*4882a593Smuzhiyunlogger = logging.getLogger('BitBake.layerindexlib.cooker') 18*4882a593Smuzhiyun 19*4882a593Smuzhiyunimport bb.utils 20*4882a593Smuzhiyun 21*4882a593Smuzhiyundef plugin_init(plugins): 22*4882a593Smuzhiyun return CookerPlugin() 23*4882a593Smuzhiyun 24*4882a593Smuzhiyunclass CookerPlugin(layerindexlib.plugin.IndexPlugin): 25*4882a593Smuzhiyun def __init__(self): 26*4882a593Smuzhiyun self.type = "cooker" 27*4882a593Smuzhiyun 28*4882a593Smuzhiyun self.server_connection = None 29*4882a593Smuzhiyun self.ui_module = None 30*4882a593Smuzhiyun self.server = None 31*4882a593Smuzhiyun 32*4882a593Smuzhiyun def _run_command(self, command, path, default=None): 33*4882a593Smuzhiyun try: 34*4882a593Smuzhiyun result, _ = bb.process.run(command, cwd=path) 35*4882a593Smuzhiyun result = result.strip() 36*4882a593Smuzhiyun except bb.process.ExecutionError: 37*4882a593Smuzhiyun result = default 38*4882a593Smuzhiyun return result 39*4882a593Smuzhiyun 40*4882a593Smuzhiyun def _handle_git_remote(self, remote): 41*4882a593Smuzhiyun if "://" not in remote: 42*4882a593Smuzhiyun if ':' in remote: 43*4882a593Smuzhiyun # This is assumed to be ssh 44*4882a593Smuzhiyun remote = "ssh://" + remote 45*4882a593Smuzhiyun else: 46*4882a593Smuzhiyun # This is assumed to be a file path 47*4882a593Smuzhiyun remote = "file://" + remote 48*4882a593Smuzhiyun return remote 49*4882a593Smuzhiyun 50*4882a593Smuzhiyun def _get_bitbake_info(self): 51*4882a593Smuzhiyun """Return a tuple of bitbake information""" 52*4882a593Smuzhiyun 53*4882a593Smuzhiyun # Our path SHOULD be .../bitbake/lib/layerindex/cooker.py 54*4882a593Smuzhiyun bb_path = os.path.dirname(__file__) # .../bitbake/lib/layerindex/cooker.py 55*4882a593Smuzhiyun bb_path = os.path.dirname(bb_path) # .../bitbake/lib/layerindex 56*4882a593Smuzhiyun bb_path = os.path.dirname(bb_path) # .../bitbake/lib 57*4882a593Smuzhiyun bb_path = os.path.dirname(bb_path) # .../bitbake 58*4882a593Smuzhiyun bb_path = self._run_command('git rev-parse --show-toplevel', os.path.dirname(__file__), default=bb_path) 59*4882a593Smuzhiyun bb_branch = self._run_command('git rev-parse --abbrev-ref HEAD', bb_path, default="<unknown>") 60*4882a593Smuzhiyun bb_rev = self._run_command('git rev-parse HEAD', bb_path, default="<unknown>") 61*4882a593Smuzhiyun for remotes in self._run_command('git remote -v', bb_path, default="").split("\n"): 62*4882a593Smuzhiyun remote = remotes.split("\t")[1].split(" ")[0] 63*4882a593Smuzhiyun if "(fetch)" == remotes.split("\t")[1].split(" ")[1]: 64*4882a593Smuzhiyun bb_remote = self._handle_git_remote(remote) 65*4882a593Smuzhiyun break 66*4882a593Smuzhiyun else: 67*4882a593Smuzhiyun bb_remote = self._handle_git_remote(bb_path) 68*4882a593Smuzhiyun 69*4882a593Smuzhiyun return (bb_remote, bb_branch, bb_rev, bb_path) 70*4882a593Smuzhiyun 71*4882a593Smuzhiyun def _load_bblayers(self, branches=None): 72*4882a593Smuzhiyun """Load the BBLAYERS and related collection information""" 73*4882a593Smuzhiyun 74*4882a593Smuzhiyun d = self.layerindex.data 75*4882a593Smuzhiyun 76*4882a593Smuzhiyun if not branches: 77*4882a593Smuzhiyun raise layerindexlib.LayerIndexFetchError("No branches specified for _load_bblayers!") 78*4882a593Smuzhiyun 79*4882a593Smuzhiyun index = layerindexlib.LayerIndexObj() 80*4882a593Smuzhiyun 81*4882a593Smuzhiyun branchId = 0 82*4882a593Smuzhiyun index.branches = {} 83*4882a593Smuzhiyun 84*4882a593Smuzhiyun layerItemId = 0 85*4882a593Smuzhiyun index.layerItems = {} 86*4882a593Smuzhiyun 87*4882a593Smuzhiyun layerBranchId = 0 88*4882a593Smuzhiyun index.layerBranches = {} 89*4882a593Smuzhiyun 90*4882a593Smuzhiyun bblayers = d.getVar('BBLAYERS').split() 91*4882a593Smuzhiyun 92*4882a593Smuzhiyun if not bblayers: 93*4882a593Smuzhiyun # It's blank! Nothing to process... 94*4882a593Smuzhiyun return index 95*4882a593Smuzhiyun 96*4882a593Smuzhiyun collections = d.getVar('BBFILE_COLLECTIONS') 97*4882a593Smuzhiyun layerconfs = d.varhistory.get_variable_items_files('BBFILE_COLLECTIONS') 98*4882a593Smuzhiyun bbfile_collections = {layer: os.path.dirname(os.path.dirname(path)) for layer, path in layerconfs.items()} 99*4882a593Smuzhiyun 100*4882a593Smuzhiyun (_, bb_branch, _, _) = self._get_bitbake_info() 101*4882a593Smuzhiyun 102*4882a593Smuzhiyun for branch in branches: 103*4882a593Smuzhiyun branchId += 1 104*4882a593Smuzhiyun index.branches[branchId] = layerindexlib.Branch(index, None) 105*4882a593Smuzhiyun index.branches[branchId].define_data(branchId, branch, bb_branch) 106*4882a593Smuzhiyun 107*4882a593Smuzhiyun for entry in collections.split(): 108*4882a593Smuzhiyun layerpath = entry 109*4882a593Smuzhiyun if entry in bbfile_collections: 110*4882a593Smuzhiyun layerpath = bbfile_collections[entry] 111*4882a593Smuzhiyun 112*4882a593Smuzhiyun layername = d.getVar('BBLAYERS_LAYERINDEX_NAME_%s' % entry) or os.path.basename(layerpath) 113*4882a593Smuzhiyun layerversion = d.getVar('LAYERVERSION_%s' % entry) or "" 114*4882a593Smuzhiyun layerurl = self._handle_git_remote(layerpath) 115*4882a593Smuzhiyun 116*4882a593Smuzhiyun layersubdir = "" 117*4882a593Smuzhiyun layerrev = "<unknown>" 118*4882a593Smuzhiyun layerbranch = "<unknown>" 119*4882a593Smuzhiyun 120*4882a593Smuzhiyun if os.path.isdir(layerpath): 121*4882a593Smuzhiyun layerbasepath = self._run_command('git rev-parse --show-toplevel', layerpath, default=layerpath) 122*4882a593Smuzhiyun if os.path.abspath(layerpath) != os.path.abspath(layerbasepath): 123*4882a593Smuzhiyun layersubdir = os.path.abspath(layerpath)[len(layerbasepath) + 1:] 124*4882a593Smuzhiyun 125*4882a593Smuzhiyun layerbranch = self._run_command('git rev-parse --abbrev-ref HEAD', layerpath, default="<unknown>") 126*4882a593Smuzhiyun layerrev = self._run_command('git rev-parse HEAD', layerpath, default="<unknown>") 127*4882a593Smuzhiyun 128*4882a593Smuzhiyun for remotes in self._run_command('git remote -v', layerpath, default="").split("\n"): 129*4882a593Smuzhiyun if not remotes: 130*4882a593Smuzhiyun layerurl = self._handle_git_remote(layerpath) 131*4882a593Smuzhiyun else: 132*4882a593Smuzhiyun remote = remotes.split("\t")[1].split(" ")[0] 133*4882a593Smuzhiyun if "(fetch)" == remotes.split("\t")[1].split(" ")[1]: 134*4882a593Smuzhiyun layerurl = self._handle_git_remote(remote) 135*4882a593Smuzhiyun break 136*4882a593Smuzhiyun 137*4882a593Smuzhiyun layerItemId += 1 138*4882a593Smuzhiyun index.layerItems[layerItemId] = layerindexlib.LayerItem(index, None) 139*4882a593Smuzhiyun index.layerItems[layerItemId].define_data(layerItemId, layername, description=layerpath, vcs_url=layerurl) 140*4882a593Smuzhiyun 141*4882a593Smuzhiyun for branchId in index.branches: 142*4882a593Smuzhiyun layerBranchId += 1 143*4882a593Smuzhiyun index.layerBranches[layerBranchId] = layerindexlib.LayerBranch(index, None) 144*4882a593Smuzhiyun index.layerBranches[layerBranchId].define_data(layerBranchId, entry, layerversion, layerItemId, branchId, 145*4882a593Smuzhiyun vcs_subdir=layersubdir, vcs_last_rev=layerrev, actual_branch=layerbranch) 146*4882a593Smuzhiyun 147*4882a593Smuzhiyun return index 148*4882a593Smuzhiyun 149*4882a593Smuzhiyun 150*4882a593Smuzhiyun def load_index(self, url, load): 151*4882a593Smuzhiyun """ 152*4882a593Smuzhiyun Fetches layer information from a build configuration. 153*4882a593Smuzhiyun 154*4882a593Smuzhiyun The return value is a dictionary containing API, 155*4882a593Smuzhiyun layer, branch, dependency, recipe, machine, distro, information. 156*4882a593Smuzhiyun 157*4882a593Smuzhiyun url type should be 'cooker'. 158*4882a593Smuzhiyun url path is ignored 159*4882a593Smuzhiyun """ 160*4882a593Smuzhiyun 161*4882a593Smuzhiyun up = urlparse(url) 162*4882a593Smuzhiyun 163*4882a593Smuzhiyun if up.scheme != 'cooker': 164*4882a593Smuzhiyun raise layerindexlib.plugin.LayerIndexPluginUrlError(self.type, url) 165*4882a593Smuzhiyun 166*4882a593Smuzhiyun d = self.layerindex.data 167*4882a593Smuzhiyun 168*4882a593Smuzhiyun params = self.layerindex._parse_params(up.params) 169*4882a593Smuzhiyun 170*4882a593Smuzhiyun # Only reason to pass a branch is to emulate them... 171*4882a593Smuzhiyun if 'branch' in params: 172*4882a593Smuzhiyun branches = params['branch'].split(',') 173*4882a593Smuzhiyun else: 174*4882a593Smuzhiyun branches = ['HEAD'] 175*4882a593Smuzhiyun 176*4882a593Smuzhiyun logger.debug("Loading cooker data branches %s" % branches) 177*4882a593Smuzhiyun 178*4882a593Smuzhiyun index = self._load_bblayers(branches=branches) 179*4882a593Smuzhiyun 180*4882a593Smuzhiyun index.config = {} 181*4882a593Smuzhiyun index.config['TYPE'] = self.type 182*4882a593Smuzhiyun index.config['URL'] = url 183*4882a593Smuzhiyun 184*4882a593Smuzhiyun if 'desc' in params: 185*4882a593Smuzhiyun index.config['DESCRIPTION'] = unquote(params['desc']) 186*4882a593Smuzhiyun else: 187*4882a593Smuzhiyun index.config['DESCRIPTION'] = 'local' 188*4882a593Smuzhiyun 189*4882a593Smuzhiyun if 'cache' in params: 190*4882a593Smuzhiyun index.config['CACHE'] = params['cache'] 191*4882a593Smuzhiyun 192*4882a593Smuzhiyun index.config['BRANCH'] = branches 193*4882a593Smuzhiyun 194*4882a593Smuzhiyun # ("layerDependencies", layerindexlib.LayerDependency) 195*4882a593Smuzhiyun layerDependencyId = 0 196*4882a593Smuzhiyun if "layerDependencies" in load: 197*4882a593Smuzhiyun index.layerDependencies = {} 198*4882a593Smuzhiyun for layerBranchId in index.layerBranches: 199*4882a593Smuzhiyun branchName = index.layerBranches[layerBranchId].branch.name 200*4882a593Smuzhiyun collection = index.layerBranches[layerBranchId].collection 201*4882a593Smuzhiyun 202*4882a593Smuzhiyun def add_dependency(layerDependencyId, index, deps, required): 203*4882a593Smuzhiyun try: 204*4882a593Smuzhiyun depDict = bb.utils.explode_dep_versions2(deps) 205*4882a593Smuzhiyun except bb.utils.VersionStringException as vse: 206*4882a593Smuzhiyun bb.fatal('Error parsing LAYERDEPENDS_%s: %s' % (collection, str(vse))) 207*4882a593Smuzhiyun 208*4882a593Smuzhiyun for dep, oplist in list(depDict.items()): 209*4882a593Smuzhiyun # We need to search ourselves, so use the _ version... 210*4882a593Smuzhiyun depLayerBranch = index.find_collection(dep, branches=[branchName]) 211*4882a593Smuzhiyun if not depLayerBranch: 212*4882a593Smuzhiyun # Missing dependency?! 213*4882a593Smuzhiyun logger.error('Missing dependency %s (%s)' % (dep, branchName)) 214*4882a593Smuzhiyun continue 215*4882a593Smuzhiyun 216*4882a593Smuzhiyun # We assume that the oplist matches... 217*4882a593Smuzhiyun layerDependencyId += 1 218*4882a593Smuzhiyun layerDependency = layerindexlib.LayerDependency(index, None) 219*4882a593Smuzhiyun layerDependency.define_data(id=layerDependencyId, 220*4882a593Smuzhiyun required=required, layerbranch=layerBranchId, 221*4882a593Smuzhiyun dependency=depLayerBranch.layer_id) 222*4882a593Smuzhiyun 223*4882a593Smuzhiyun logger.debug('%s requires %s' % (layerDependency.layer.name, layerDependency.dependency.name)) 224*4882a593Smuzhiyun index.add_element("layerDependencies", [layerDependency]) 225*4882a593Smuzhiyun 226*4882a593Smuzhiyun return layerDependencyId 227*4882a593Smuzhiyun 228*4882a593Smuzhiyun deps = d.getVar("LAYERDEPENDS_%s" % collection) 229*4882a593Smuzhiyun if deps: 230*4882a593Smuzhiyun layerDependencyId = add_dependency(layerDependencyId, index, deps, True) 231*4882a593Smuzhiyun 232*4882a593Smuzhiyun deps = d.getVar("LAYERRECOMMENDS_%s" % collection) 233*4882a593Smuzhiyun if deps: 234*4882a593Smuzhiyun layerDependencyId = add_dependency(layerDependencyId, index, deps, False) 235*4882a593Smuzhiyun 236*4882a593Smuzhiyun # Need to load recipes here (requires cooker access) 237*4882a593Smuzhiyun recipeId = 0 238*4882a593Smuzhiyun ## TODO: NOT IMPLEMENTED 239*4882a593Smuzhiyun # The code following this is an example of what needs to be 240*4882a593Smuzhiyun # implemented. However, it does not work as-is. 241*4882a593Smuzhiyun if False and 'recipes' in load: 242*4882a593Smuzhiyun index.recipes = {} 243*4882a593Smuzhiyun 244*4882a593Smuzhiyun ret = self.ui_module.main(self.server_connection.connection, self.server_connection.events, config_params) 245*4882a593Smuzhiyun 246*4882a593Smuzhiyun all_versions = self._run_command('allProviders') 247*4882a593Smuzhiyun 248*4882a593Smuzhiyun all_versions_list = defaultdict(list, all_versions) 249*4882a593Smuzhiyun for pn in all_versions_list: 250*4882a593Smuzhiyun for ((pe, pv, pr), fpath) in all_versions_list[pn]: 251*4882a593Smuzhiyun realfn = bb.cache.virtualfn2realfn(fpath) 252*4882a593Smuzhiyun 253*4882a593Smuzhiyun filepath = os.path.dirname(realfn[0]) 254*4882a593Smuzhiyun filename = os.path.basename(realfn[0]) 255*4882a593Smuzhiyun 256*4882a593Smuzhiyun # This is all HORRIBLY slow, and likely unnecessary 257*4882a593Smuzhiyun #dscon = self._run_command('parseRecipeFile', fpath, False, []) 258*4882a593Smuzhiyun #connector = myDataStoreConnector(self, dscon.dsindex) 259*4882a593Smuzhiyun #recipe_data = bb.data.init() 260*4882a593Smuzhiyun #recipe_data.setVar('_remote_data', connector) 261*4882a593Smuzhiyun 262*4882a593Smuzhiyun #summary = recipe_data.getVar('SUMMARY') 263*4882a593Smuzhiyun #description = recipe_data.getVar('DESCRIPTION') 264*4882a593Smuzhiyun #section = recipe_data.getVar('SECTION') 265*4882a593Smuzhiyun #license = recipe_data.getVar('LICENSE') 266*4882a593Smuzhiyun #homepage = recipe_data.getVar('HOMEPAGE') 267*4882a593Smuzhiyun #bugtracker = recipe_data.getVar('BUGTRACKER') 268*4882a593Smuzhiyun #provides = recipe_data.getVar('PROVIDES') 269*4882a593Smuzhiyun 270*4882a593Smuzhiyun layer = bb.utils.get_file_layer(realfn[0], self.config_data) 271*4882a593Smuzhiyun 272*4882a593Smuzhiyun depBranchId = collection[layer] 273*4882a593Smuzhiyun 274*4882a593Smuzhiyun recipeId += 1 275*4882a593Smuzhiyun recipe = layerindexlib.Recipe(index, None) 276*4882a593Smuzhiyun recipe.define_data(id=recipeId, 277*4882a593Smuzhiyun filename=filename, filepath=filepath, 278*4882a593Smuzhiyun pn=pn, pv=pv, 279*4882a593Smuzhiyun summary=pn, description=pn, section='?', 280*4882a593Smuzhiyun license='?', homepage='?', bugtracker='?', 281*4882a593Smuzhiyun provides='?', bbclassextend='?', inherits='?', 282*4882a593Smuzhiyun disallowed='?', layerbranch=depBranchId) 283*4882a593Smuzhiyun 284*4882a593Smuzhiyun index = addElement("recipes", [recipe], index) 285*4882a593Smuzhiyun 286*4882a593Smuzhiyun # ("machines", layerindexlib.Machine) 287*4882a593Smuzhiyun machineId = 0 288*4882a593Smuzhiyun if 'machines' in load: 289*4882a593Smuzhiyun index.machines = {} 290*4882a593Smuzhiyun 291*4882a593Smuzhiyun for layerBranchId in index.layerBranches: 292*4882a593Smuzhiyun # load_bblayers uses the description to cache the actual path... 293*4882a593Smuzhiyun machine_path = index.layerBranches[layerBranchId].layer.description 294*4882a593Smuzhiyun machine_path = os.path.join(machine_path, 'conf/machine') 295*4882a593Smuzhiyun if os.path.isdir(machine_path): 296*4882a593Smuzhiyun for (dirpath, _, filenames) in os.walk(machine_path): 297*4882a593Smuzhiyun # Ignore subdirs... 298*4882a593Smuzhiyun if not dirpath.endswith('conf/machine'): 299*4882a593Smuzhiyun continue 300*4882a593Smuzhiyun for fname in filenames: 301*4882a593Smuzhiyun if fname.endswith('.conf'): 302*4882a593Smuzhiyun machineId += 1 303*4882a593Smuzhiyun machine = layerindexlib.Machine(index, None) 304*4882a593Smuzhiyun machine.define_data(id=machineId, name=fname[:-5], 305*4882a593Smuzhiyun description=fname[:-5], 306*4882a593Smuzhiyun layerbranch=index.layerBranches[layerBranchId]) 307*4882a593Smuzhiyun 308*4882a593Smuzhiyun index.add_element("machines", [machine]) 309*4882a593Smuzhiyun 310*4882a593Smuzhiyun # ("distros", layerindexlib.Distro) 311*4882a593Smuzhiyun distroId = 0 312*4882a593Smuzhiyun if 'distros' in load: 313*4882a593Smuzhiyun index.distros = {} 314*4882a593Smuzhiyun 315*4882a593Smuzhiyun for layerBranchId in index.layerBranches: 316*4882a593Smuzhiyun # load_bblayers uses the description to cache the actual path... 317*4882a593Smuzhiyun distro_path = index.layerBranches[layerBranchId].layer.description 318*4882a593Smuzhiyun distro_path = os.path.join(distro_path, 'conf/distro') 319*4882a593Smuzhiyun if os.path.isdir(distro_path): 320*4882a593Smuzhiyun for (dirpath, _, filenames) in os.walk(distro_path): 321*4882a593Smuzhiyun # Ignore subdirs... 322*4882a593Smuzhiyun if not dirpath.endswith('conf/distro'): 323*4882a593Smuzhiyun continue 324*4882a593Smuzhiyun for fname in filenames: 325*4882a593Smuzhiyun if fname.endswith('.conf'): 326*4882a593Smuzhiyun distroId += 1 327*4882a593Smuzhiyun distro = layerindexlib.Distro(index, None) 328*4882a593Smuzhiyun distro.define_data(id=distroId, name=fname[:-5], 329*4882a593Smuzhiyun description=fname[:-5], 330*4882a593Smuzhiyun layerbranch=index.layerBranches[layerBranchId]) 331*4882a593Smuzhiyun 332*4882a593Smuzhiyun index.add_element("distros", [distro]) 333*4882a593Smuzhiyun 334*4882a593Smuzhiyun return index 335