xref: /OK3568_Linux_fs/yocto/poky/meta/lib/oe/npm_registry.py (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyunimport bb
2*4882a593Smuzhiyunimport json
3*4882a593Smuzhiyunimport subprocess
4*4882a593Smuzhiyun
5*4882a593Smuzhiyun_ALWAYS_SAFE = frozenset('ABCDEFGHIJKLMNOPQRSTUVWXYZ'
6*4882a593Smuzhiyun                         'abcdefghijklmnopqrstuvwxyz'
7*4882a593Smuzhiyun                         '0123456789'
8*4882a593Smuzhiyun                         '_.-~')
9*4882a593Smuzhiyun
10*4882a593SmuzhiyunMISSING_OK = object()
11*4882a593Smuzhiyun
12*4882a593SmuzhiyunREGISTRY = "https://registry.npmjs.org"
13*4882a593Smuzhiyun
14*4882a593Smuzhiyun# we can not use urllib.parse here because npm expects lowercase
15*4882a593Smuzhiyun# hex-chars but urllib generates uppercase ones
16*4882a593Smuzhiyundef uri_quote(s, safe = '/'):
17*4882a593Smuzhiyun    res = ""
18*4882a593Smuzhiyun    safe_set = set(safe)
19*4882a593Smuzhiyun    for c in s:
20*4882a593Smuzhiyun        if c in _ALWAYS_SAFE or c in safe_set:
21*4882a593Smuzhiyun            res += c
22*4882a593Smuzhiyun        else:
23*4882a593Smuzhiyun            res += '%%%02x' % ord(c)
24*4882a593Smuzhiyun    return res
25*4882a593Smuzhiyun
26*4882a593Smuzhiyunclass PackageJson:
27*4882a593Smuzhiyun    def __init__(self, spec):
28*4882a593Smuzhiyun        self.__spec = spec
29*4882a593Smuzhiyun
30*4882a593Smuzhiyun    @property
31*4882a593Smuzhiyun    def name(self):
32*4882a593Smuzhiyun        return self.__spec['name']
33*4882a593Smuzhiyun
34*4882a593Smuzhiyun    @property
35*4882a593Smuzhiyun    def version(self):
36*4882a593Smuzhiyun        return self.__spec['version']
37*4882a593Smuzhiyun
38*4882a593Smuzhiyun    @property
39*4882a593Smuzhiyun    def empty_manifest(self):
40*4882a593Smuzhiyun        return {
41*4882a593Smuzhiyun            'name': self.name,
42*4882a593Smuzhiyun            'description': self.__spec.get('description', ''),
43*4882a593Smuzhiyun            'versions': {},
44*4882a593Smuzhiyun        }
45*4882a593Smuzhiyun
46*4882a593Smuzhiyun    def base_filename(self):
47*4882a593Smuzhiyun        return uri_quote(self.name, safe = '@')
48*4882a593Smuzhiyun
49*4882a593Smuzhiyun    def as_manifest_entry(self, tarball_uri):
50*4882a593Smuzhiyun        res = {}
51*4882a593Smuzhiyun
52*4882a593Smuzhiyun        ## NOTE: 'npm install' requires more than basic meta information;
53*4882a593Smuzhiyun        ## e.g. it takes 'bin' from this manifest entry but not the actual
54*4882a593Smuzhiyun        ## 'package.json'
55*4882a593Smuzhiyun        for (idx,dflt) in [('name', None),
56*4882a593Smuzhiyun                           ('description', ""),
57*4882a593Smuzhiyun                           ('version', None),
58*4882a593Smuzhiyun                           ('bin', MISSING_OK),
59*4882a593Smuzhiyun                           ('man', MISSING_OK),
60*4882a593Smuzhiyun                           ('scripts', MISSING_OK),
61*4882a593Smuzhiyun                           ('directories', MISSING_OK),
62*4882a593Smuzhiyun                           ('dependencies', MISSING_OK),
63*4882a593Smuzhiyun                           ('devDependencies', MISSING_OK),
64*4882a593Smuzhiyun                           ('optionalDependencies', MISSING_OK),
65*4882a593Smuzhiyun                           ('license', "unknown")]:
66*4882a593Smuzhiyun            if idx in self.__spec:
67*4882a593Smuzhiyun                res[idx] = self.__spec[idx]
68*4882a593Smuzhiyun            elif dflt == MISSING_OK:
69*4882a593Smuzhiyun                pass
70*4882a593Smuzhiyun            elif dflt != None:
71*4882a593Smuzhiyun                res[idx] = dflt
72*4882a593Smuzhiyun            else:
73*4882a593Smuzhiyun                raise Exception("%s-%s: missing key %s" % (self.name,
74*4882a593Smuzhiyun                                                           self.version,
75*4882a593Smuzhiyun                                                           idx))
76*4882a593Smuzhiyun
77*4882a593Smuzhiyun        res['dist'] = {
78*4882a593Smuzhiyun            'tarball': tarball_uri,
79*4882a593Smuzhiyun        }
80*4882a593Smuzhiyun
81*4882a593Smuzhiyun        return res
82*4882a593Smuzhiyun
83*4882a593Smuzhiyunclass ManifestImpl:
84*4882a593Smuzhiyun    def __init__(self, base_fname, spec):
85*4882a593Smuzhiyun        self.__base = base_fname
86*4882a593Smuzhiyun        self.__spec = spec
87*4882a593Smuzhiyun
88*4882a593Smuzhiyun    def load(self):
89*4882a593Smuzhiyun        try:
90*4882a593Smuzhiyun            with open(self.filename, "r") as f:
91*4882a593Smuzhiyun                res = json.load(f)
92*4882a593Smuzhiyun        except IOError:
93*4882a593Smuzhiyun            res = self.__spec.empty_manifest
94*4882a593Smuzhiyun
95*4882a593Smuzhiyun        return res
96*4882a593Smuzhiyun
97*4882a593Smuzhiyun    def save(self, meta):
98*4882a593Smuzhiyun        with open(self.filename, "w") as f:
99*4882a593Smuzhiyun            json.dump(meta, f, indent = 2)
100*4882a593Smuzhiyun
101*4882a593Smuzhiyun    @property
102*4882a593Smuzhiyun    def filename(self):
103*4882a593Smuzhiyun        return self.__base + ".meta"
104*4882a593Smuzhiyun
105*4882a593Smuzhiyunclass Manifest:
106*4882a593Smuzhiyun    def __init__(self, base_fname, spec):
107*4882a593Smuzhiyun        self.__base = base_fname
108*4882a593Smuzhiyun        self.__spec = spec
109*4882a593Smuzhiyun        self.__lockf = None
110*4882a593Smuzhiyun        self.__impl = None
111*4882a593Smuzhiyun
112*4882a593Smuzhiyun    def __enter__(self):
113*4882a593Smuzhiyun        self.__lockf = bb.utils.lockfile(self.__base + ".lock")
114*4882a593Smuzhiyun        self.__impl  = ManifestImpl(self.__base, self.__spec)
115*4882a593Smuzhiyun        return self.__impl
116*4882a593Smuzhiyun
117*4882a593Smuzhiyun    def __exit__(self, exc_type, exc_val, exc_tb):
118*4882a593Smuzhiyun        bb.utils.unlockfile(self.__lockf)
119*4882a593Smuzhiyun
120*4882a593Smuzhiyunclass NpmCache:
121*4882a593Smuzhiyun    def __init__(self, cache):
122*4882a593Smuzhiyun        self.__cache = cache
123*4882a593Smuzhiyun
124*4882a593Smuzhiyun    @property
125*4882a593Smuzhiyun    def path(self):
126*4882a593Smuzhiyun        return self.__cache
127*4882a593Smuzhiyun
128*4882a593Smuzhiyun    def run(self, type, key, fname):
129*4882a593Smuzhiyun        subprocess.run(['oe-npm-cache', self.__cache, type, key, fname],
130*4882a593Smuzhiyun                       check = True)
131*4882a593Smuzhiyun
132*4882a593Smuzhiyunclass NpmRegistry:
133*4882a593Smuzhiyun    def __init__(self, path, cache):
134*4882a593Smuzhiyun        self.__path = path
135*4882a593Smuzhiyun        self.__cache = NpmCache(cache + '/_cacache')
136*4882a593Smuzhiyun        bb.utils.mkdirhier(self.__path)
137*4882a593Smuzhiyun        bb.utils.mkdirhier(self.__cache.path)
138*4882a593Smuzhiyun
139*4882a593Smuzhiyun    @staticmethod
140*4882a593Smuzhiyun    ## This function is critical and must match nodejs expectations
141*4882a593Smuzhiyun    def _meta_uri(spec):
142*4882a593Smuzhiyun        return REGISTRY + '/' + uri_quote(spec.name, safe = '@')
143*4882a593Smuzhiyun
144*4882a593Smuzhiyun    @staticmethod
145*4882a593Smuzhiyun    ## Exact return value does not matter; just make it look like a
146*4882a593Smuzhiyun    ## usual registry url
147*4882a593Smuzhiyun    def _tarball_uri(spec):
148*4882a593Smuzhiyun        return '%s/%s/-/%s-%s.tgz' % (REGISTRY,
149*4882a593Smuzhiyun                                      uri_quote(spec.name, safe = '@'),
150*4882a593Smuzhiyun                                      uri_quote(spec.name, safe = '@/'),
151*4882a593Smuzhiyun                                      spec.version)
152*4882a593Smuzhiyun
153*4882a593Smuzhiyun    def add_pkg(self, tarball, pkg_json):
154*4882a593Smuzhiyun        pkg_json = PackageJson(pkg_json)
155*4882a593Smuzhiyun        base = os.path.join(self.__path, pkg_json.base_filename())
156*4882a593Smuzhiyun
157*4882a593Smuzhiyun        with Manifest(base, pkg_json) as manifest:
158*4882a593Smuzhiyun            meta = manifest.load()
159*4882a593Smuzhiyun            tarball_uri = self._tarball_uri(pkg_json)
160*4882a593Smuzhiyun
161*4882a593Smuzhiyun            meta['versions'][pkg_json.version] = pkg_json.as_manifest_entry(tarball_uri)
162*4882a593Smuzhiyun
163*4882a593Smuzhiyun            manifest.save(meta)
164*4882a593Smuzhiyun
165*4882a593Smuzhiyun            ## Cache entries are a little bit dependent on the nodejs
166*4882a593Smuzhiyun            ## version; version specific cache implementation must
167*4882a593Smuzhiyun            ## mitigate differences
168*4882a593Smuzhiyun            self.__cache.run('meta', self._meta_uri(pkg_json), manifest.filename);
169*4882a593Smuzhiyun            self.__cache.run('tgz',  tarball_uri, tarball);
170