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