# Copyright 2020 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Types which provide many builds and composite them into a single binary."""
import logging
import pathlib
import subprocess

import zmake.build_config as build_config
import zmake.multiproc
import zmake.util as util


def _write_dts_file(dts_file, config_header, output_bin, ro_filename, rw_filename):
    """Generate the .dts file used for binman.

    Args:
        dts_file: The dts file to write to.
        config_header: The full path to the generated autoconf.h header.
        output_bin: The full path to the binary that binman should output.
        ro_filename: The RO image file name.
        rw_filename: The RW image file name.

    Returns:
        The path to the .dts file that was generated.
    """
    dts_file.write("""
    /dts-v1/;
    #include "{config_header}"
    / {{
      #address-cells = <1>;
      #size-cells = <1>;
      binman {{
        filename = "{output_bin}";
        pad-byte = <0x1d>;
        section@0 {{
          read-only;
          offset = <CONFIG_PLATFORM_EC_PROTECTED_STORAGE_OFF>;
          size = <CONFIG_PLATFORM_EC_PROTECTED_STORAGE_SIZE>;
          blob {{
            filename = "{ro_filename}";
          }};
        }};
        section@1 {{
          offset = <CONFIG_PLATFORM_EC_WRITABLE_STORAGE_OFF>;
          size = <CONFIG_PLATFORM_EC_WRITABLE_STORAGE_SIZE>;
          blob {{
            filename = "{rw_filename}";
          }};
        }};
      }};
    }};""".format(
        output_bin=output_bin,
        config_header=config_header,
        ro_filename=ro_filename,
        rw_filename=rw_filename
    ))


class BasePacker:
    """Abstract base for all packers."""
    def __init__(self, project):
        self.project = project

    def configs(self):
        """Get all of the build configurations necessary.

        Yields:
            2-tuples of config name and a BuildConfig.
        """
        yield 'singleimage', build_config.BuildConfig()

    def pack_firmware(self, work_dir, jobclient):
        """Pack a firmware image.

        Config names from the configs generator are passed as keyword
        arguments, with each argument being set to the path of the
        build directory.

        Args:
            work_dir: A directory to write outputs and temporary files
            into.
            jobclient: A JobClient object to use.

        Yields:
            2-tuples of the path of each file in the work_dir (or any
            other directory) which should be copied into the output
            directory, and the output filename.
        """
        raise NotImplementedError('Abstract method not implemented')


class ElfPacker(BasePacker):
    """Raw proxy for ELF output of a single build."""
    def pack_firmware(self, work_dir, jobclient, singleimage):
        yield singleimage / 'zephyr' / 'zephyr.elf', 'zephyr.elf'


class RawBinPacker(BasePacker):
    """Packer for RO/RW image to generate a .bin build using FMAP."""
    def __init__(self, project):
        self.logger = logging.getLogger(self.__class__.__name__)
        super().__init__(project)

    def configs(self):
        yield 'ro', build_config.BuildConfig(kconfig_defs={'CONFIG_CROS_EC_RO': 'y'})
        yield 'rw', build_config.BuildConfig(kconfig_defs={'CONFIG_CROS_EC_RW': 'y'})

    def pack_firmware(self, work_dir, jobclient, ro, rw):
        """Pack the 'raw' binary.

        This combines the RO and RW images as specified in the Kconfig file for
        the project. For this function to work, the following config values must
        be defined:
        * CONFIG_CROS_EC_RO_MEM_OFF - The offset in bytes of the RO image from
          the start of the resulting binary.
        * CONFIG_CROS_EC_RO_SIZE - The maximum allowed size (in bytes) of the RO
          image.
        * CONFIG_CROS_EC_RW_MEM_OFF - The offset in bytes of the RW image from
          the start of the resulting binary (must be >= RO_MEM_OFF + RO_SIZE).
        * CONFIG_CROS_EC_RW_SIZE - The maximum allowed size (in bytes) of the RW
           image.

        Args:
            work_dir: The directory used for packing.
            jobclient: The client used to run subprocesses.
            ro: Directory containing the RO image build.
            rw: Directory containing the RW image build.

        Returns:
            Tuple mapping the resulting .bin file to the output filename.
        """
        work_dir = pathlib.Path(work_dir).resolve()
        ro = pathlib.Path(ro).resolve()
        rw = pathlib.Path(rw).resolve()
        dts_file_path = work_dir / 'project.dts'
        with open(dts_file_path, 'w+') as dts_file:
            _write_dts_file(
                dts_file=dts_file,
                config_header=ro / 'zephyr' / 'include' / 'generated' / 'autoconf.h',
                output_bin=work_dir / 'zephyr.bin',
                ro_filename=ro / 'zephyr' / 'zephyr.packed.bin',
                rw_filename=rw / 'zephyr' / 'zephyr.bin')

        proc = jobclient.popen(
            ['binman', '-v', '5', 'build', '-d', dts_file_path, '-m'],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            encoding='utf-8')

        zmake.multiproc.log_output(self.logger, logging.DEBUG, proc.stdout)
        zmake.multiproc.log_output(self.logger, logging.ERROR, proc.stderr)
        if proc.wait(timeout=5):
            raise OSError('Failed to run binman')

        yield work_dir / 'zephyr.bin', 'zephyr.bin'


# A dictionary mapping packer config names to classes.
packer_registry = {
    'elf': ElfPacker,
    'raw': RawBinPacker,
}

@@ -5568,16 +5568,136 @@
 yr.bin'%0A
+        yield ro / 'zephyr' / 'zephyr.elf', 'zephyr.ro.elf'%0A        yield rw / 'zephyr' / 'zephyr.elf', 'zephyr.rw.elf'%0A
 %0A%0A# A di

Add ro/rw elf to output/