xref: /OK3568_Linux_fs/yocto/poky/bitbake/lib/layerindexlib/restapi.py (revision 4882a59341e53eb6f0b4789bf948001014eff981)
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 json
8*4882a593Smuzhiyunimport os
9*4882a593Smuzhiyun
10*4882a593Smuzhiyunfrom urllib.parse import unquote
11*4882a593Smuzhiyunfrom urllib.parse import urlparse
12*4882a593Smuzhiyun
13*4882a593Smuzhiyunimport bb
14*4882a593Smuzhiyun
15*4882a593Smuzhiyunimport layerindexlib
16*4882a593Smuzhiyunimport layerindexlib.plugin
17*4882a593Smuzhiyun
18*4882a593Smuzhiyunlogger = logging.getLogger('BitBake.layerindexlib.restapi')
19*4882a593Smuzhiyun
20*4882a593Smuzhiyundef plugin_init(plugins):
21*4882a593Smuzhiyun    return RestApiPlugin()
22*4882a593Smuzhiyun
23*4882a593Smuzhiyunclass RestApiPlugin(layerindexlib.plugin.IndexPlugin):
24*4882a593Smuzhiyun    def __init__(self):
25*4882a593Smuzhiyun        self.type = "restapi"
26*4882a593Smuzhiyun
27*4882a593Smuzhiyun    def load_index(self, url, load):
28*4882a593Smuzhiyun        """
29*4882a593Smuzhiyun            Fetches layer information from a local or remote layer index.
30*4882a593Smuzhiyun
31*4882a593Smuzhiyun            The return value is a LayerIndexObj.
32*4882a593Smuzhiyun
33*4882a593Smuzhiyun            url is the url to the rest api of the layer index, such as:
34*4882a593Smuzhiyun            https://layers.openembedded.org/layerindex/api/
35*4882a593Smuzhiyun
36*4882a593Smuzhiyun            Or a local file...
37*4882a593Smuzhiyun        """
38*4882a593Smuzhiyun
39*4882a593Smuzhiyun        up = urlparse(url)
40*4882a593Smuzhiyun
41*4882a593Smuzhiyun        if up.scheme == 'file':
42*4882a593Smuzhiyun            return self.load_index_file(up, url, load)
43*4882a593Smuzhiyun
44*4882a593Smuzhiyun        if up.scheme == 'http' or up.scheme == 'https':
45*4882a593Smuzhiyun            return self.load_index_web(up, url, load)
46*4882a593Smuzhiyun
47*4882a593Smuzhiyun        raise layerindexlib.plugin.LayerIndexPluginUrlError(self.type, url)
48*4882a593Smuzhiyun
49*4882a593Smuzhiyun
50*4882a593Smuzhiyun    def load_index_file(self, up, url, load):
51*4882a593Smuzhiyun        """
52*4882a593Smuzhiyun            Fetches layer information from a local file or directory.
53*4882a593Smuzhiyun
54*4882a593Smuzhiyun            The return value is a LayerIndexObj.
55*4882a593Smuzhiyun
56*4882a593Smuzhiyun            ud is the parsed url to the local file or directory.
57*4882a593Smuzhiyun        """
58*4882a593Smuzhiyun        if not os.path.exists(up.path):
59*4882a593Smuzhiyun            raise FileNotFoundError(up.path)
60*4882a593Smuzhiyun
61*4882a593Smuzhiyun        index = layerindexlib.LayerIndexObj()
62*4882a593Smuzhiyun
63*4882a593Smuzhiyun        index.config = {}
64*4882a593Smuzhiyun        index.config['TYPE'] = self.type
65*4882a593Smuzhiyun        index.config['URL'] = url
66*4882a593Smuzhiyun
67*4882a593Smuzhiyun        params = self.layerindex._parse_params(up.params)
68*4882a593Smuzhiyun
69*4882a593Smuzhiyun        if 'desc' in params:
70*4882a593Smuzhiyun            index.config['DESCRIPTION'] = unquote(params['desc'])
71*4882a593Smuzhiyun        else:
72*4882a593Smuzhiyun            index.config['DESCRIPTION'] = up.path
73*4882a593Smuzhiyun
74*4882a593Smuzhiyun        if 'cache' in params:
75*4882a593Smuzhiyun            index.config['CACHE'] = params['cache']
76*4882a593Smuzhiyun
77*4882a593Smuzhiyun        if 'branch' in params:
78*4882a593Smuzhiyun            branches = params['branch'].split(',')
79*4882a593Smuzhiyun            index.config['BRANCH'] = branches
80*4882a593Smuzhiyun        else:
81*4882a593Smuzhiyun            branches = ['*']
82*4882a593Smuzhiyun
83*4882a593Smuzhiyun
84*4882a593Smuzhiyun        def load_cache(path, index, branches=[]):
85*4882a593Smuzhiyun            logger.debug('Loading json file %s' % path)
86*4882a593Smuzhiyun            with open(path, 'rt', encoding='utf-8') as f:
87*4882a593Smuzhiyun                pindex = json.load(f)
88*4882a593Smuzhiyun
89*4882a593Smuzhiyun            # Filter the branches on loaded files...
90*4882a593Smuzhiyun            newpBranch = []
91*4882a593Smuzhiyun            for branch in branches:
92*4882a593Smuzhiyun                if branch != '*':
93*4882a593Smuzhiyun                    if 'branches' in pindex:
94*4882a593Smuzhiyun                        for br in pindex['branches']:
95*4882a593Smuzhiyun                            if br['name'] == branch:
96*4882a593Smuzhiyun                                newpBranch.append(br)
97*4882a593Smuzhiyun                else:
98*4882a593Smuzhiyun                    if 'branches' in pindex:
99*4882a593Smuzhiyun                        for br in pindex['branches']:
100*4882a593Smuzhiyun                            newpBranch.append(br)
101*4882a593Smuzhiyun
102*4882a593Smuzhiyun            if newpBranch:
103*4882a593Smuzhiyun                index.add_raw_element('branches', layerindexlib.Branch, newpBranch)
104*4882a593Smuzhiyun            else:
105*4882a593Smuzhiyun                logger.debug('No matching branches (%s) in index file(s)' % branches)
106*4882a593Smuzhiyun                # No matching branches.. return nothing...
107*4882a593Smuzhiyun                return
108*4882a593Smuzhiyun
109*4882a593Smuzhiyun            for (lName, lType) in [("layerItems", layerindexlib.LayerItem),
110*4882a593Smuzhiyun                                   ("layerBranches", layerindexlib.LayerBranch),
111*4882a593Smuzhiyun                                   ("layerDependencies", layerindexlib.LayerDependency),
112*4882a593Smuzhiyun                                   ("recipes", layerindexlib.Recipe),
113*4882a593Smuzhiyun                                   ("machines", layerindexlib.Machine),
114*4882a593Smuzhiyun                                   ("distros", layerindexlib.Distro)]:
115*4882a593Smuzhiyun                if lName in pindex:
116*4882a593Smuzhiyun                    index.add_raw_element(lName, lType, pindex[lName])
117*4882a593Smuzhiyun
118*4882a593Smuzhiyun
119*4882a593Smuzhiyun        if not os.path.isdir(up.path):
120*4882a593Smuzhiyun            load_cache(up.path, index, branches)
121*4882a593Smuzhiyun            return index
122*4882a593Smuzhiyun
123*4882a593Smuzhiyun        logger.debug('Loading from dir %s...' % (up.path))
124*4882a593Smuzhiyun        for (dirpath, _, filenames) in os.walk(up.path):
125*4882a593Smuzhiyun            for filename in filenames:
126*4882a593Smuzhiyun                if not filename.endswith('.json'):
127*4882a593Smuzhiyun                    continue
128*4882a593Smuzhiyun                fpath = os.path.join(dirpath, filename)
129*4882a593Smuzhiyun                load_cache(fpath, index, branches)
130*4882a593Smuzhiyun
131*4882a593Smuzhiyun        return index
132*4882a593Smuzhiyun
133*4882a593Smuzhiyun
134*4882a593Smuzhiyun    def load_index_web(self, up, url, load):
135*4882a593Smuzhiyun        """
136*4882a593Smuzhiyun            Fetches layer information from a remote layer index.
137*4882a593Smuzhiyun
138*4882a593Smuzhiyun            The return value is a LayerIndexObj.
139*4882a593Smuzhiyun
140*4882a593Smuzhiyun            ud is the parsed url to the rest api of the layer index, such as:
141*4882a593Smuzhiyun            https://layers.openembedded.org/layerindex/api/
142*4882a593Smuzhiyun        """
143*4882a593Smuzhiyun
144*4882a593Smuzhiyun        def _get_json_response(apiurl=None, username=None, password=None, retry=True):
145*4882a593Smuzhiyun            assert apiurl is not None
146*4882a593Smuzhiyun
147*4882a593Smuzhiyun            logger.debug("fetching %s" % apiurl)
148*4882a593Smuzhiyun
149*4882a593Smuzhiyun            up = urlparse(apiurl)
150*4882a593Smuzhiyun
151*4882a593Smuzhiyun            username=up.username
152*4882a593Smuzhiyun            password=up.password
153*4882a593Smuzhiyun
154*4882a593Smuzhiyun            # Strip username/password and params
155*4882a593Smuzhiyun            if up.port:
156*4882a593Smuzhiyun                up_stripped = up._replace(params="", netloc="%s:%s" % (up.hostname, up.port))
157*4882a593Smuzhiyun            else:
158*4882a593Smuzhiyun                up_stripped = up._replace(params="", netloc=up.hostname)
159*4882a593Smuzhiyun
160*4882a593Smuzhiyun            res = self.layerindex._fetch_url(up_stripped.geturl(), username=username, password=password)
161*4882a593Smuzhiyun
162*4882a593Smuzhiyun            try:
163*4882a593Smuzhiyun                parsed = json.loads(res.read().decode('utf-8'))
164*4882a593Smuzhiyun            except ConnectionResetError:
165*4882a593Smuzhiyun                if retry:
166*4882a593Smuzhiyun                    logger.debug("%s: Connection reset by peer.  Retrying..." % url)
167*4882a593Smuzhiyun                    parsed = _get_json_response(apiurl=up_stripped.geturl(), username=username, password=password, retry=False)
168*4882a593Smuzhiyun                    logger.debug("%s: retry successful.")
169*4882a593Smuzhiyun                else:
170*4882a593Smuzhiyun                    raise layerindexlib.LayerIndexFetchError('%s: Connection reset by peer.  Is there a firewall blocking your connection?' % apiurl)
171*4882a593Smuzhiyun
172*4882a593Smuzhiyun            return parsed
173*4882a593Smuzhiyun
174*4882a593Smuzhiyun        index = layerindexlib.LayerIndexObj()
175*4882a593Smuzhiyun
176*4882a593Smuzhiyun        index.config = {}
177*4882a593Smuzhiyun        index.config['TYPE'] = self.type
178*4882a593Smuzhiyun        index.config['URL'] = url
179*4882a593Smuzhiyun
180*4882a593Smuzhiyun        params = self.layerindex._parse_params(up.params)
181*4882a593Smuzhiyun
182*4882a593Smuzhiyun        if 'desc' in params:
183*4882a593Smuzhiyun            index.config['DESCRIPTION'] = unquote(params['desc'])
184*4882a593Smuzhiyun        else:
185*4882a593Smuzhiyun            index.config['DESCRIPTION'] = up.hostname
186*4882a593Smuzhiyun
187*4882a593Smuzhiyun        if 'cache' in params:
188*4882a593Smuzhiyun            index.config['CACHE'] = params['cache']
189*4882a593Smuzhiyun
190*4882a593Smuzhiyun        if 'branch' in params:
191*4882a593Smuzhiyun            branches = params['branch'].split(',')
192*4882a593Smuzhiyun            index.config['BRANCH'] = branches
193*4882a593Smuzhiyun        else:
194*4882a593Smuzhiyun            branches = ['*']
195*4882a593Smuzhiyun
196*4882a593Smuzhiyun        try:
197*4882a593Smuzhiyun            index.apilinks = _get_json_response(apiurl=url, username=up.username, password=up.password)
198*4882a593Smuzhiyun        except Exception as e:
199*4882a593Smuzhiyun            raise layerindexlib.LayerIndexFetchError(url, e)
200*4882a593Smuzhiyun
201*4882a593Smuzhiyun        # Local raw index set...
202*4882a593Smuzhiyun        pindex = {}
203*4882a593Smuzhiyun
204*4882a593Smuzhiyun        # Load all the requested branches at the same time time,
205*4882a593Smuzhiyun        # a special branch of '*' means load all branches
206*4882a593Smuzhiyun        filter = ""
207*4882a593Smuzhiyun        if "*" not in branches:
208*4882a593Smuzhiyun            filter = "?filter=name:%s" % "OR".join(branches)
209*4882a593Smuzhiyun
210*4882a593Smuzhiyun        logger.debug("Loading %s from %s" % (branches, index.apilinks['branches']))
211*4882a593Smuzhiyun
212*4882a593Smuzhiyun        # The link won't include username/password, so pull it from the original url
213*4882a593Smuzhiyun        pindex['branches'] = _get_json_response(index.apilinks['branches'] + filter,
214*4882a593Smuzhiyun                                                    username=up.username, password=up.password)
215*4882a593Smuzhiyun        if not pindex['branches']:
216*4882a593Smuzhiyun            logger.debug("No valid branches (%s) found at url %s." % (branch, url))
217*4882a593Smuzhiyun            return index
218*4882a593Smuzhiyun        index.add_raw_element("branches", layerindexlib.Branch, pindex['branches'])
219*4882a593Smuzhiyun
220*4882a593Smuzhiyun        # Load all of the layerItems (these can not be easily filtered)
221*4882a593Smuzhiyun        logger.debug("Loading %s from %s" % ('layerItems', index.apilinks['layerItems']))
222*4882a593Smuzhiyun
223*4882a593Smuzhiyun
224*4882a593Smuzhiyun        # The link won't include username/password, so pull it from the original url
225*4882a593Smuzhiyun        pindex['layerItems'] = _get_json_response(index.apilinks['layerItems'],
226*4882a593Smuzhiyun                                                  username=up.username, password=up.password)
227*4882a593Smuzhiyun        if not pindex['layerItems']:
228*4882a593Smuzhiyun            logger.debug("No layers were found at url %s." % (url))
229*4882a593Smuzhiyun            return index
230*4882a593Smuzhiyun        index.add_raw_element("layerItems", layerindexlib.LayerItem, pindex['layerItems'])
231*4882a593Smuzhiyun
232*4882a593Smuzhiyun
233*4882a593Smuzhiyun	# From this point on load the contents for each branch.  Otherwise we
234*4882a593Smuzhiyun	# could run into a timeout.
235*4882a593Smuzhiyun        for branch in index.branches:
236*4882a593Smuzhiyun            filter = "?filter=branch__name:%s" % index.branches[branch].name
237*4882a593Smuzhiyun
238*4882a593Smuzhiyun            logger.debug("Loading %s from %s" % ('layerBranches', index.apilinks['layerBranches']))
239*4882a593Smuzhiyun
240*4882a593Smuzhiyun            # The link won't include username/password, so pull it from the original url
241*4882a593Smuzhiyun            pindex['layerBranches'] = _get_json_response(index.apilinks['layerBranches'] + filter,
242*4882a593Smuzhiyun                                                  username=up.username, password=up.password)
243*4882a593Smuzhiyun            if not pindex['layerBranches']:
244*4882a593Smuzhiyun                logger.debug("No valid layer branches (%s) found at url %s." % (branches or "*", url))
245*4882a593Smuzhiyun                return index
246*4882a593Smuzhiyun            index.add_raw_element("layerBranches", layerindexlib.LayerBranch, pindex['layerBranches'])
247*4882a593Smuzhiyun
248*4882a593Smuzhiyun
249*4882a593Smuzhiyun            # Load the rest, they all have a similar format
250*4882a593Smuzhiyun            # Note: the layer index has a few more items, we can add them if necessary
251*4882a593Smuzhiyun            # in the future.
252*4882a593Smuzhiyun            filter = "?filter=layerbranch__branch__name:%s" % index.branches[branch].name
253*4882a593Smuzhiyun            for (lName, lType) in [("layerDependencies", layerindexlib.LayerDependency),
254*4882a593Smuzhiyun                                   ("recipes", layerindexlib.Recipe),
255*4882a593Smuzhiyun                                   ("machines", layerindexlib.Machine),
256*4882a593Smuzhiyun                                   ("distros", layerindexlib.Distro)]:
257*4882a593Smuzhiyun                if lName not in load:
258*4882a593Smuzhiyun                    continue
259*4882a593Smuzhiyun                logger.debug("Loading %s from %s" % (lName, index.apilinks[lName]))
260*4882a593Smuzhiyun
261*4882a593Smuzhiyun                # The link won't include username/password, so pull it from the original url
262*4882a593Smuzhiyun                pindex[lName] = _get_json_response(index.apilinks[lName] + filter,
263*4882a593Smuzhiyun                                            username=up.username, password=up.password)
264*4882a593Smuzhiyun                index.add_raw_element(lName, lType, pindex[lName])
265*4882a593Smuzhiyun
266*4882a593Smuzhiyun        return index
267*4882a593Smuzhiyun
268*4882a593Smuzhiyun    def store_index(self, url, index):
269*4882a593Smuzhiyun        """
270*4882a593Smuzhiyun            Store layer information into a local file/dir.
271*4882a593Smuzhiyun
272*4882a593Smuzhiyun            The return value is a dictionary containing API,
273*4882a593Smuzhiyun            layer, branch, dependency, recipe, machine, distro, information.
274*4882a593Smuzhiyun
275*4882a593Smuzhiyun            ud is a parsed url to a directory or file.  If the path is a
276*4882a593Smuzhiyun            directory, we will split the files into one file per layer.
277*4882a593Smuzhiyun            If the path is to a file (exists or not) the entire DB will be
278*4882a593Smuzhiyun            dumped into that one file.
279*4882a593Smuzhiyun        """
280*4882a593Smuzhiyun
281*4882a593Smuzhiyun        up = urlparse(url)
282*4882a593Smuzhiyun
283*4882a593Smuzhiyun        if up.scheme != 'file':
284*4882a593Smuzhiyun            raise layerindexlib.plugin.LayerIndexPluginUrlError(self.type, url)
285*4882a593Smuzhiyun
286*4882a593Smuzhiyun        logger.debug("Storing to %s..." % up.path)
287*4882a593Smuzhiyun
288*4882a593Smuzhiyun        try:
289*4882a593Smuzhiyun            layerbranches = index.layerBranches
290*4882a593Smuzhiyun        except KeyError:
291*4882a593Smuzhiyun            logger.error('No layerBranches to write.')
292*4882a593Smuzhiyun            return
293*4882a593Smuzhiyun
294*4882a593Smuzhiyun
295*4882a593Smuzhiyun        def filter_item(layerbranchid, objects):
296*4882a593Smuzhiyun            filtered = []
297*4882a593Smuzhiyun            for obj in getattr(index, objects, None):
298*4882a593Smuzhiyun                try:
299*4882a593Smuzhiyun                    if getattr(index, objects)[obj].layerbranch_id == layerbranchid:
300*4882a593Smuzhiyun                       filtered.append(getattr(index, objects)[obj]._data)
301*4882a593Smuzhiyun                except AttributeError:
302*4882a593Smuzhiyun                    logger.debug('No obj.layerbranch_id: %s' % objects)
303*4882a593Smuzhiyun                    # No simple filter method, just include it...
304*4882a593Smuzhiyun                    try:
305*4882a593Smuzhiyun                        filtered.append(getattr(index, objects)[obj]._data)
306*4882a593Smuzhiyun                    except AttributeError:
307*4882a593Smuzhiyun                        logger.debug('No obj._data: %s %s' % (objects, type(obj)))
308*4882a593Smuzhiyun                        filtered.append(obj)
309*4882a593Smuzhiyun            return filtered
310*4882a593Smuzhiyun
311*4882a593Smuzhiyun
312*4882a593Smuzhiyun        # Write out to a single file.
313*4882a593Smuzhiyun        # Filter out unnecessary items, then sort as we write for determinism
314*4882a593Smuzhiyun        if not os.path.isdir(up.path):
315*4882a593Smuzhiyun            pindex = {}
316*4882a593Smuzhiyun
317*4882a593Smuzhiyun            pindex['branches'] = []
318*4882a593Smuzhiyun            pindex['layerItems'] = []
319*4882a593Smuzhiyun            pindex['layerBranches'] = []
320*4882a593Smuzhiyun
321*4882a593Smuzhiyun            for layerbranchid in layerbranches:
322*4882a593Smuzhiyun                if layerbranches[layerbranchid].branch._data not in pindex['branches']:
323*4882a593Smuzhiyun                    pindex['branches'].append(layerbranches[layerbranchid].branch._data)
324*4882a593Smuzhiyun
325*4882a593Smuzhiyun                if layerbranches[layerbranchid].layer._data not in pindex['layerItems']:
326*4882a593Smuzhiyun                    pindex['layerItems'].append(layerbranches[layerbranchid].layer._data)
327*4882a593Smuzhiyun
328*4882a593Smuzhiyun                if layerbranches[layerbranchid]._data not in pindex['layerBranches']:
329*4882a593Smuzhiyun                    pindex['layerBranches'].append(layerbranches[layerbranchid]._data)
330*4882a593Smuzhiyun
331*4882a593Smuzhiyun                for entry in index._index:
332*4882a593Smuzhiyun                    # Skip local items, apilinks and items already processed
333*4882a593Smuzhiyun                    if entry in index.config['local'] or \
334*4882a593Smuzhiyun                       entry == 'apilinks' or \
335*4882a593Smuzhiyun                       entry == 'branches' or \
336*4882a593Smuzhiyun                       entry == 'layerBranches' or \
337*4882a593Smuzhiyun                       entry == 'layerItems':
338*4882a593Smuzhiyun                        continue
339*4882a593Smuzhiyun                    if entry not in pindex:
340*4882a593Smuzhiyun                        pindex[entry] = []
341*4882a593Smuzhiyun                    pindex[entry].extend(filter_item(layerbranchid, entry))
342*4882a593Smuzhiyun
343*4882a593Smuzhiyun            bb.debug(1, 'Writing index to %s' % up.path)
344*4882a593Smuzhiyun            with open(up.path, 'wt') as f:
345*4882a593Smuzhiyun                json.dump(layerindexlib.sort_entry(pindex), f, indent=4)
346*4882a593Smuzhiyun            return
347*4882a593Smuzhiyun
348*4882a593Smuzhiyun
349*4882a593Smuzhiyun        # Write out to a directory one file per layerBranch
350*4882a593Smuzhiyun        # Prepare all layer related items, to create a minimal file.
351*4882a593Smuzhiyun        # We have to sort the entries as we write so they are deterministic
352*4882a593Smuzhiyun        for layerbranchid in layerbranches:
353*4882a593Smuzhiyun            pindex = {}
354*4882a593Smuzhiyun
355*4882a593Smuzhiyun            for entry in index._index:
356*4882a593Smuzhiyun                # Skip local items, apilinks and items already processed
357*4882a593Smuzhiyun                if entry in index.config['local'] or \
358*4882a593Smuzhiyun                   entry == 'apilinks' or \
359*4882a593Smuzhiyun                   entry == 'branches' or \
360*4882a593Smuzhiyun                   entry == 'layerBranches' or \
361*4882a593Smuzhiyun                   entry == 'layerItems':
362*4882a593Smuzhiyun                    continue
363*4882a593Smuzhiyun                pindex[entry] = filter_item(layerbranchid, entry)
364*4882a593Smuzhiyun
365*4882a593Smuzhiyun            # Add the layer we're processing as the first one...
366*4882a593Smuzhiyun            pindex['branches'] = [layerbranches[layerbranchid].branch._data]
367*4882a593Smuzhiyun            pindex['layerItems'] = [layerbranches[layerbranchid].layer._data]
368*4882a593Smuzhiyun            pindex['layerBranches'] = [layerbranches[layerbranchid]._data]
369*4882a593Smuzhiyun
370*4882a593Smuzhiyun            # We also need to include the layerbranch for any dependencies...
371*4882a593Smuzhiyun            for layerdep in pindex['layerDependencies']:
372*4882a593Smuzhiyun                layerdependency = layerindexlib.LayerDependency(index, layerdep)
373*4882a593Smuzhiyun
374*4882a593Smuzhiyun                layeritem = layerdependency.dependency
375*4882a593Smuzhiyun                layerbranch = layerdependency.dependency_layerBranch
376*4882a593Smuzhiyun
377*4882a593Smuzhiyun                # We need to avoid duplicates...
378*4882a593Smuzhiyun                if layeritem._data not in pindex['layerItems']:
379*4882a593Smuzhiyun                    pindex['layerItems'].append(layeritem._data)
380*4882a593Smuzhiyun
381*4882a593Smuzhiyun                if layerbranch._data not in pindex['layerBranches']:
382*4882a593Smuzhiyun                    pindex['layerBranches'].append(layerbranch._data)
383*4882a593Smuzhiyun
384*4882a593Smuzhiyun            # apply mirroring adjustments here....
385*4882a593Smuzhiyun
386*4882a593Smuzhiyun            fname = index.config['DESCRIPTION'] + '__' + pindex['branches'][0]['name'] + '__' + pindex['layerItems'][0]['name']
387*4882a593Smuzhiyun            fname = fname.translate(str.maketrans('/ ', '__'))
388*4882a593Smuzhiyun            fpath = os.path.join(up.path, fname)
389*4882a593Smuzhiyun
390*4882a593Smuzhiyun            bb.debug(1, 'Writing index to %s' % fpath + '.json')
391*4882a593Smuzhiyun            with open(fpath + '.json', 'wt') as f:
392*4882a593Smuzhiyun                json.dump(layerindexlib.sort_entry(pindex), f, indent=4)
393