15f9996aaSopenharmony_ci#!/usr/bin/env python
25f9996aaSopenharmony_ci# -*- coding: utf-8 -*-
35f9996aaSopenharmony_ci# Copyright (c) 2021 Huawei Device Co., Ltd.
45f9996aaSopenharmony_ci# Licensed under the Apache License, Version 2.0 (the "License");
55f9996aaSopenharmony_ci# you may not use this file except in compliance with the License.
65f9996aaSopenharmony_ci# You may obtain a copy of the License at
75f9996aaSopenharmony_ci#
85f9996aaSopenharmony_ci#     http://www.apache.org/licenses/LICENSE-2.0
95f9996aaSopenharmony_ci#
105f9996aaSopenharmony_ci# Unless required by applicable law or agreed to in writing, software
115f9996aaSopenharmony_ci# distributed under the License is distributed on an "AS IS" BASIS,
125f9996aaSopenharmony_ci# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
135f9996aaSopenharmony_ci# See the License for the specific language governing permissions and
145f9996aaSopenharmony_ci# limitations under the License.
155f9996aaSopenharmony_ci
165f9996aaSopenharmony_ciimport shutil
175f9996aaSopenharmony_ciimport os
185f9996aaSopenharmony_ciimport hashlib
195f9996aaSopenharmony_ciimport json
205f9996aaSopenharmony_ciimport http.client as client
215f9996aaSopenharmony_cifrom . import build_utils
225f9996aaSopenharmony_ci
235f9996aaSopenharmony_ci
245f9996aaSopenharmony_ciclass Storage():
255f9996aaSopenharmony_ci    def __init__(self):
265f9996aaSopenharmony_ci        pass
275f9996aaSopenharmony_ci
285f9996aaSopenharmony_ci    @classmethod
295f9996aaSopenharmony_ci    def retrieve_object(cls, cache_artifact, obj):
305f9996aaSopenharmony_ci        possible_dir_cache_artifact = '{}.directory'.format(cache_artifact)
315f9996aaSopenharmony_ci
325f9996aaSopenharmony_ci        if os.path.exists(cache_artifact):
335f9996aaSopenharmony_ci            os.makedirs(os.path.dirname(obj), exist_ok=True)
345f9996aaSopenharmony_ci            shutil.copyfile(cache_artifact, obj)
355f9996aaSopenharmony_ci            os.utime(cache_artifact)
365f9996aaSopenharmony_ci            if pycache_debug_enable:
375f9996aaSopenharmony_ci                print('Retrieve {} from cache'.format(obj))
385f9996aaSopenharmony_ci        elif os.path.exists(possible_dir_cache_artifact):
395f9996aaSopenharmony_ci            # Extract zip archive if it's cache artifact for directory.
405f9996aaSopenharmony_ci            os.makedirs(obj, exist_ok=True)
415f9996aaSopenharmony_ci            build_utils.extract_all(possible_dir_cache_artifact,
425f9996aaSopenharmony_ci                                   obj,
435f9996aaSopenharmony_ci                                   no_clobber=False)
445f9996aaSopenharmony_ci            os.utime(possible_dir_cache_artifact)
455f9996aaSopenharmony_ci            if pycache_debug_enable:
465f9996aaSopenharmony_ci                print('Extract {} from cache'.format(obj))
475f9996aaSopenharmony_ci        else:
485f9996aaSopenharmony_ci            if pycache_debug_enable:
495f9996aaSopenharmony_ci                print('Failed to retrieve {} from cache'.format(obj))
505f9996aaSopenharmony_ci            return 0
515f9996aaSopenharmony_ci        return 1
525f9996aaSopenharmony_ci
535f9996aaSopenharmony_ci    @classmethod
545f9996aaSopenharmony_ci    def add_object(cls, cache_artifact, obj):
555f9996aaSopenharmony_ci        cache_dir = os.path.dirname(cache_artifact)
565f9996aaSopenharmony_ci        os.makedirs(cache_dir, exist_ok=True)
575f9996aaSopenharmony_ci
585f9996aaSopenharmony_ci        if not os.path.exists(obj):
595f9996aaSopenharmony_ci            return
605f9996aaSopenharmony_ci        # If path is directory, store an zip archive.
615f9996aaSopenharmony_ci        if os.path.isdir(obj):
625f9996aaSopenharmony_ci            dir_cache_artifact = '{}.directory'.format(cache_artifact)
635f9996aaSopenharmony_ci            build_utils.zip_dir(dir_cache_artifact, obj)
645f9996aaSopenharmony_ci            if pycache_debug_enable:
655f9996aaSopenharmony_ci                print("archive {} to {}".format(obj, dir_cache_artifact))
665f9996aaSopenharmony_ci        else:
675f9996aaSopenharmony_ci            shutil.copyfile(obj, cache_artifact)
685f9996aaSopenharmony_ci            if pycache_debug_enable:
695f9996aaSopenharmony_ci                print("copying {} to {}".format(obj, cache_artifact))
705f9996aaSopenharmony_ci
715f9996aaSopenharmony_ci
725f9996aaSopenharmony_ciclass PyCache():
735f9996aaSopenharmony_ci    def __init__(self, cache_dir=None):
745f9996aaSopenharmony_ci        cache_dir = os.environ.get('PYCACHE_DIR')
755f9996aaSopenharmony_ci        if cache_dir:
765f9996aaSopenharmony_ci            self.pycache_dir = cache_dir
775f9996aaSopenharmony_ci        else:
785f9996aaSopenharmony_ci            raise Exception('Error: failed to get PYCACHE_DIR')
795f9996aaSopenharmony_ci        self.storage = Storage()
805f9996aaSopenharmony_ci
815f9996aaSopenharmony_ci    @classmethod
825f9996aaSopenharmony_ci    def cache_key(cls, path):
835f9996aaSopenharmony_ci        sha256 = hashlib.sha256()
845f9996aaSopenharmony_ci        sha256.update(path.encode())
855f9996aaSopenharmony_ci        return sha256.hexdigest()
865f9996aaSopenharmony_ci
875f9996aaSopenharmony_ci    def retrieve(self, output_paths, prefix=''):
885f9996aaSopenharmony_ci        for path in output_paths:
895f9996aaSopenharmony_ci            _, cache_artifact = self.descend_directory('{}{}'.format(
905f9996aaSopenharmony_ci                prefix, path))
915f9996aaSopenharmony_ci            result = self.storage.retrieve_object(cache_artifact, path)
925f9996aaSopenharmony_ci            if not result:
935f9996aaSopenharmony_ci                return result
945f9996aaSopenharmony_ci
955f9996aaSopenharmony_ci        try:
965f9996aaSopenharmony_ci            self.report_cache_stat('cache_hit')
975f9996aaSopenharmony_ci        except:  # noqa: E722 pylint: disable=bare-except
985f9996aaSopenharmony_ci            pass
995f9996aaSopenharmony_ci        return 1
1005f9996aaSopenharmony_ci
1015f9996aaSopenharmony_ci    def save(self, output_paths, prefix=''):
1025f9996aaSopenharmony_ci        for path in output_paths:
1035f9996aaSopenharmony_ci            _, cache_artifact = self.descend_directory('{}{}'.format(
1045f9996aaSopenharmony_ci                prefix, path))
1055f9996aaSopenharmony_ci            self.storage.add_object(cache_artifact, path)
1065f9996aaSopenharmony_ci
1075f9996aaSopenharmony_ci    def report_cache_stat(self, hit_or_miss):
1085f9996aaSopenharmony_ci        pyd_server, pyd_port = self.get_pyd()
1095f9996aaSopenharmony_ci        conn = client.HTTPConnection(pyd_server, pyd_port)
1105f9996aaSopenharmony_ci        conn.request(hit_or_miss, '/')
1115f9996aaSopenharmony_ci        conn.close()
1125f9996aaSopenharmony_ci
1135f9996aaSopenharmony_ci    def get_pyd(self):
1145f9996aaSopenharmony_ci        daemon_config_file = '{}/.config'.format(self.pycache_dir)
1155f9996aaSopenharmony_ci        if not os.path.exists(daemon_config_file):
1165f9996aaSopenharmony_ci            raise Exception('Warning: no pycache daemon process exists.')
1175f9996aaSopenharmony_ci        with open(daemon_config_file, 'r') as jsonfile:
1185f9996aaSopenharmony_ci            data = json.load(jsonfile)
1195f9996aaSopenharmony_ci            return data.get('host'), data.get('port')
1205f9996aaSopenharmony_ci
1215f9996aaSopenharmony_ci    def descend_directory(self, path):
1225f9996aaSopenharmony_ci        digest = self.cache_key(path)
1235f9996aaSopenharmony_ci        cache_dir = os.path.join(self.pycache_dir, digest[:2])
1245f9996aaSopenharmony_ci        return cache_dir, os.path.join(cache_dir, digest[2:])
1255f9996aaSopenharmony_ci
1265f9996aaSopenharmony_ci    # Manifest file to record inputs/outputs/commands.
1275f9996aaSopenharmony_ci    def get_manifest_path(self, path):
1285f9996aaSopenharmony_ci        manifest_dir, manifest_file = self.descend_directory(path)
1295f9996aaSopenharmony_ci        os.makedirs(manifest_dir, exist_ok=True)
1305f9996aaSopenharmony_ci        return manifest_file
1315f9996aaSopenharmony_ci
1325f9996aaSopenharmony_ci
1335f9996aaSopenharmony_cipycache_enabled = (os.environ.get('PYCACHE_DIR') is not None)
1345f9996aaSopenharmony_cipycache_debug_enable = int(os.environ.get('PRINT_BUILD_EXPLANATIONS', 0))
1355f9996aaSopenharmony_ciif pycache_enabled:
1365f9996aaSopenharmony_ci    pycache = PyCache()
1375f9996aaSopenharmony_cielse:
1385f9996aaSopenharmony_ci    pycache = None  # pylint: disable=invalid-name
139