1#
2# Copyright (c) 2010-2025 Antmicro
3#
4# This file is licensed under the MIT License.
5# Full license text is available in 'licenses/MIT.txt'.
6#
7
8import os
9import zipfile
10import tempfile
11import json
12from typing import Iterable, List, TextIO, IO
13from datetime import datetime
14
15def create_coverview_archive(path: TextIO, report: Iterable[str], code_files: List[IO], coverview_dict: str) -> bool:
16    merge_config = {}
17    success = True
18    if coverview_dict is not None:
19        try:
20            merge_config = json.loads(coverview_dict)
21        except json.decoder.JSONDecodeError as e:
22            print('Malformed config JSON, will use default one:', e)
23            # Delay the failure and let the archive be generated with default contents
24            # so the time isn't wasted otherwise; the user can edit the JSON manually
25            success = False
26
27    if os.path.splitext(path.name)[1] != '.zip':
28        print('Ensure that the file will have ".zip" extension. Coverview might not handle the file properly otherwise!')
29
30    with zipfile.ZipFile(path.name, 'w') as archive:
31        # In case of very large coverage files it might be better to create a temporary file instead of in-memory string
32        archive.writestr('coverage.info', '\n'.join(line for line in report))
33        config_file = {
34            "datasets": {
35                "application": {
36                    "line": "coverage.info",
37                }
38            },
39            "title": "Coverage dashboard",
40            "commit": "",
41            "branch": "",
42            "repo": "",
43            "timestamp": "",
44            "additional": {
45                "report_timestamp": str(datetime.now()),
46            }
47        }
48        # "merge_config" will replace values from "config_file"
49        config_file = {**config_file, **merge_config}
50        archive.writestr('config.json', json.dumps(config_file))
51
52        with tempfile.TemporaryDirectory() as tmp_dir_name:
53            with open(os.path.join(tmp_dir_name, 'sources.txt'), 'w') as sources:
54                for code_file in code_files:
55                    # Remember to revert the pointer, as we might have read the file already before
56                    code_file.seek(0)
57                    sources.write(f'### FILE: {code_file.name}\n')
58                    sources.write(code_file.read())
59            archive.write(sources.name, arcname='sources.txt')
60
61        print('Created archive:', archive.filename)
62
63        return success
64