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