xref: /openbmc/openbmc/poky/bitbake/lib/bb/fetch2/crate.py (revision edff49234e31f23dc79f823473c9e286a21596c1)
1# ex:ts=4:sw=4:sts=4:et
2# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
3"""
4BitBake 'Fetch' implementation for crates.io
5"""
6
7# Copyright (C) 2016 Doug Goldstein
8#
9# SPDX-License-Identifier: GPL-2.0-only
10#
11# Based on functions from the base bb module, Copyright 2003 Holger Schurig
12
13import hashlib
14import json
15import os
16import subprocess
17import bb
18from   bb.fetch2 import logger, subprocess_setup, UnpackError
19from   bb.fetch2.wget import Wget
20
21
22class Crate(Wget):
23
24    """Class to fetch crates via wget"""
25
26    def _cargo_bitbake_path(self, rootdir):
27        return os.path.join(rootdir, "cargo_home", "bitbake")
28
29    def supports(self, ud, d):
30        """
31        Check to see if a given url is for this fetcher
32        """
33        return ud.type in ['crate']
34
35    def recommends_checksum(self, urldata):
36        return True
37
38    def urldata_init(self, ud, d):
39        """
40        Sets up to download the respective crate from crates.io
41        """
42
43        if ud.type == 'crate':
44            self._crate_urldata_init(ud, d)
45
46        super(Crate, self).urldata_init(ud, d)
47
48    def _crate_urldata_init(self, ud, d):
49        """
50        Sets up the download for a crate
51        """
52
53        # URL syntax is: crate://NAME/VERSION
54        # break the URL apart by /
55        parts = ud.url.split('/')
56        if len(parts) < 5:
57            raise bb.fetch2.ParameterError("Invalid URL: Must be crate://HOST/NAME/VERSION", ud.url)
58
59        # version is expected to be the last token
60        # but ignore possible url parameters which will be used
61        # by the top fetcher class
62        version = parts[-1].split(";")[0]
63        # second to last field is name
64        name = parts[-2]
65        # host (this is to allow custom crate registries to be specified
66        host = '/'.join(parts[2:-2])
67
68        # if using upstream just fix it up nicely
69        if host == 'crates.io':
70            host = 'crates.io/api/v1/crates'
71
72        ud.url = "https://%s/%s/%s/download" % (host, name, version)
73        ud.versionsurl = "https://%s/%s/versions" % (host, name)
74        ud.parm['downloadfilename'] = "%s-%s.crate" % (name, version)
75        if 'name' not in ud.parm:
76            ud.parm['name'] = '%s-%s' % (name, version)
77
78        logger.debug2("Fetching %s to %s" % (ud.url, ud.parm['downloadfilename']))
79
80    def unpack(self, ud, rootdir, d):
81        """
82        Uses the crate to build the necessary paths for cargo to utilize it
83        """
84        if ud.type == 'crate':
85            return self._crate_unpack(ud, rootdir, d)
86        else:
87            super(Crate, self).unpack(ud, rootdir, d)
88
89    def _crate_unpack(self, ud, rootdir, d):
90        """
91        Unpacks a crate
92        """
93        thefile = ud.localpath
94
95        # possible metadata we need to write out
96        metadata = {}
97
98        # change to the rootdir to unpack but save the old working dir
99        save_cwd = os.getcwd()
100        os.chdir(rootdir)
101
102        bp = d.getVar('BP')
103        if bp == ud.parm.get('name'):
104            cmd = "tar -xz --no-same-owner -f %s" % thefile
105            ud.unpack_tracer.unpack("crate-extract", rootdir)
106        else:
107            cargo_bitbake = self._cargo_bitbake_path(rootdir)
108            ud.unpack_tracer.unpack("cargo-extract", cargo_bitbake)
109
110            cmd = "tar -xz --no-same-owner -f %s -C %s" % (thefile, cargo_bitbake)
111
112            # ensure we've got these paths made
113            bb.utils.mkdirhier(cargo_bitbake)
114
115            # generate metadata necessary
116            with open(thefile, 'rb') as f:
117                # get the SHA256 of the original tarball
118                tarhash = hashlib.sha256(f.read()).hexdigest()
119
120            metadata['files'] = {}
121            metadata['package'] = tarhash
122
123        path = d.getVar('PATH')
124        if path:
125            cmd = "PATH=\"%s\" %s" % (path, cmd)
126        bb.note("Unpacking %s to %s/" % (thefile, os.getcwd()))
127
128        ret = subprocess.call(cmd, preexec_fn=subprocess_setup, shell=True)
129
130        os.chdir(save_cwd)
131
132        if ret != 0:
133            raise UnpackError("Unpack command %s failed with return value %s" % (cmd, ret), ud.url)
134
135        # if we have metadata to write out..
136        if len(metadata) > 0:
137            cratepath = os.path.splitext(os.path.basename(thefile))[0]
138            bbpath = self._cargo_bitbake_path(rootdir)
139            mdfile = '.cargo-checksum.json'
140            mdpath = os.path.join(bbpath, cratepath, mdfile)
141            with open(mdpath, "w") as f:
142                json.dump(metadata, f)
143
144    def latest_versionstring(self, ud, d):
145        from functools import cmp_to_key
146        json_data = json.loads(self._fetch_index(ud.versionsurl, ud, d))
147        versions = [(0, i["num"], "") for i in json_data["versions"]]
148        versions = sorted(versions, key=cmp_to_key(bb.utils.vercmp))
149
150        return (versions[-1][1], "")
151