blob: 2895300e12388e58e0acd501d62361bf151b2da7 [file] [log] [blame]
# Copyright (c) 2021 Project CHIP Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
import os
import shutil
import tarfile
from abc import ABC, abstractmethod
from dataclasses import dataclass
@dataclass
class BuilderOptions:
# Generate a link map file
enable_link_map_file: bool = False
# Enable flashbundle generation stage
enable_flashbundle: bool = False
# Allow to wrap default build command
pw_command_launcher: str = None
# Locations where files are pre-generated
pregen_dir: str = None
@dataclass
class BuilderOutput:
source: str # Source file generated by the build
target: str # Target file to be copied to
class Builder(ABC):
"""Generic builder base class for CHIP.
Provides ability to bootstrap and copy output artifacts and subclasses can
use a generic shell runner.
"""
def __init__(self, root, runner):
self.root = os.path.abspath(root)
self._runner = runner
# Set post-init once actual build target is known
self.identifier = None
self.output_dir = None
self.options = BuilderOptions()
@abstractmethod
def generate(self):
"""Generate the build files - generally the ninja/makefiles"""
raise NotImplementedError()
@abstractmethod
def _build(self):
"""Perform an actual build"""
raise NotImplementedError()
def _bundle(self):
"""Perform an actual generating of flashbundle.
May do nothing (and builder can choose not to implement this) if
the app does not need special steps for generating flashbundle (e.g.
on Linux platform, the output ELF files can be used directly).
"""
pass
@abstractmethod
def build_outputs(self):
"""Return a list of relevant BuilderOutput objects after a build.
May use build output data (e.g. manifests), so this should be
invoked only after a build has succeeded.
"""
raise NotImplementedError()
def bundle_outputs(self):
"""Return the BuilderOutput objects in flashbundle.
Return an empty list (and builder can choose not to implement this)
if the app does not need special files as flashbundle.
May use data from _bundle(), so this should be invoked only after
_bundle() has succeeded.
"""
return []
def outputs(self):
outputs = list(self.build_outputs())
if self.options.enable_flashbundle:
outputs.extend(self.bundle_outputs())
return outputs
def build(self):
self._build()
if self.options.enable_flashbundle:
self._bundle()
def _Execute(self, cmdarray, title=None):
self._runner.Run(cmdarray, title=title)
def CompressArtifacts(self, target_file: str):
with tarfile.open(target_file, "w:gz") as tar:
for output in self.outputs():
logging.info('Adding %s into %s(%s)',
output.source, target_file, output.target)
tar.add(output.source, output.target)
def CopyArtifacts(self, target_dir: str):
for output in self.outputs():
logging.info(f'Copying {output.source} into {output.target}')
target_full_name = os.path.join(target_dir, output.target)
target_dir_full_name = os.path.dirname(target_full_name)
if not os.path.exists(target_dir_full_name):
logging.info('Creating subdirectory %s first',
target_dir_full_name)
os.makedirs(target_dir_full_name)
shutil.copyfile(output.source, target_full_name)
shutil.copymode(output.source, target_full_name)