function lMakeResult = codebuild(buildInfo_in, varargin)
% CODEBUILD compiles code specified by an RTW.BuildInfo object
% 
%   CODEBUILD(BUILDFOLDER) loads the file BUILDFOLDER/buildInfo.mat, which
%   contains a build information object that specifies source code. The
%   function compiles the source code by using build options that are also
%   specified in buildInfo.mat.
% 
%   CODEBUILD(BUILDFOLDER, name1, value1, ...) loads the file
%   BUILDFOLDER/buildInfo.mat. The function compiles the source code by
%   using build options specified by name-value pairs that override
%   corresponding options in buildInfo.mat.
% 
%   CODEBUILD(BUILDINFO, name1, value1, ...) compiles the code specified in
%   BUILDINFO, an RTW.BuildInfo object, using options specified by
%   the name-value pair arguments.
%
%   Allowed argument names:
%
%       BuildMethod - Specify a template makefile, registered Toolchain, or
%       literal string 'CMake'.
%
%       BuildVariant - Specify an enumeration for the final product type,
%       for example, STATIC_LIBRARY or STANDALONE_EXECUTABLE.
   
    
% Copyright 2012-2020 The MathWorks, Inc.

% Convert any string input to char
buildInfo = convertStringsToChars(buildInfo_in);

if ~ischar(buildInfo) && ~isa(buildInfo, 'RTW.BuildInfo')
    msg = message('coder_compile:CoderCompile:CodebuildInvalidBuildInfo');
    error(msg.Identifier, '%s', string(msg));    
end

if nargin > 1
    [varargin{:}] = convertStringsToChars(varargin{:});
end

% By default, child components inherit build options from those specified
% for the top component. In this default mode, options such as BuildMethod, 
% BuildConfiguration are always consistent across the hierarchy.
inheritBuildOptionsFromTop = true;

if length(varargin)==1 && isa(varargin{1}, 'coder.make.BuildOpts')
    % Build opts are supplied directly as an argument
    buildOptsTop = varargin{1};
else
    if ischar(buildInfo)
        % Check that the argument resolves to a file and load it
        [buildInfo, buildOptsTop] = i_loadBuildInfo(buildInfo);
        
        % Apply build options specified by the caller, if any
        specifiedBuildOptions = i_applyBuildOptsFromNameValues(buildOptsTop, varargin);
        
        % Identify build options specified by the caller that can override
        % build options in a child component
        overrideBuildOptions = ...
            setdiff(specifiedBuildOptions, coder.make.BuildOpts.NoOverrideOpts);
            
        if isempty(overrideBuildOptions)
            % If codebuild was invoked with the first argument specifying a
            % buildInfo.mat and if there are no build options that override
            % the corresponding child build option: each component in the
            % hierarchy will be built using options specific to that
            % component. The options for each component are obtained
            % directly its buildInfo.mat. In this mode, options such as
            % BuildMethod, BuildConfiguration may differ between components
            % and are not necessarily consistent across the hierarchy.
            inheritBuildOptionsFromTop = false;
        end
    else
        % Start with default build options
        buildOptsTop = coder.make.BuildOpts;
        
        % Apply build options specified by the caller
        if ~isempty(varargin)
            i_applyBuildOptsFromNameValues(buildOptsTop, varargin);
        end
    end
end
    
% If build method is empty, use default toolchain for the current platform
if isempty(buildOptsTop.BuildMethod)
    mexCompInfo = coder.make.internal.getMexCompilerInfo();
    if ~isempty(mexCompInfo)
        compilerString = mexCompInfo.compStr;
        buildOptsTop.BuildMethod = ...
            coder.make.internal.getToolchainNameFromRegistry(compilerString);
    end
end


if buildOptsTop.CopyBuildInfo
    % This transient property must be copied over
    lComponentBuildFolder = buildInfo.ComponentBuildFolder; 
    
    % Accept a small performance penalty by making a deep copy of buildInfo;
    % this ensures that the buildInfo supplied by the caller is not 
    % modified in any way
    buildInfo = coder.make.internal.cloneBuildInfo(buildInfo);
    buildInfo.ComponentBuildFolder = lComponentBuildFolder;
end

% Ensure that root folder, component build folder and relative path
% to anchor are all consistent
if isempty(buildInfo.Settings.LocalAnchorDir)
    setRootFolder(buildInfo, pwd);
end
coder.make.internal.syncRelativePathToAnchor(buildInfo)

if buildOptsTop.AllComponents
    % Load child components, including shared libraries and referenced models
    loadReferencedModels = true;
    coder.make.internal.loadChildBuildInfos(buildInfo, loadReferencedModels);
end

% Check if the BuildMethod is CMake and issue a warning if it conflicts with 
% a registered toolchain
isCMake = coder.make.internal.buildMethodIsCMake(buildOptsTop.BuildMethod);

% Map of link objects to be linked by the top component
% Skip linking them when generating CMakeLists.txt for child components  
topModelLinkObjs = [buildInfo.ModelRefs; buildInfo.getLinkObjects];
if isempty(topModelLinkObjs)
    cmakeLinkObjsToSkip = containers.Map();
else
    cmakeLinkObjsToSkip = containers.Map({topModelLinkObjs.Name},...
        coder.make.internal.formatPathsForCMake({topModelLinkObjs.Path}));    
end
% Identify child components to build: components must be built
% in the same order that would apply if built directly within
% a code generation, model-reference-hierarchy context
% TODO(g2194341): test point to lock down correct order for building child components
buildableLinkObjs = [buildInfo.LinkObj(end:-1:1); buildInfo.ModelRefs(end:-1:1)];
if ~isempty(buildableLinkObjs)
    hasBuildInfoIdx = {buildableLinkObjs.BuildInfoHandle};
    hasBuildInfoIdx = ~cellfun(@isempty, hasBuildInfoIdx);
else
    hasBuildInfoIdx = [];
end
libsToBuild = buildableLinkObjs(hasBuildInfoIdx);

lRootFolder = buildInfo.Settings.LocalAnchorDir;

% Prepare to build libraries
if ~isempty(libsToBuild)
    libBuildResults = coder.make.MakeResult.empty(0, length(libsToBuild));
    [libsToBuildPaths, libsToBuildNames] = i_getLibPaths(libsToBuild, lRootFolder);
    lUpdatedLibPoller = coder.make.internal.UpdatedLibArchivePoller...
        (libsToBuildPaths, libsToBuildNames);
end

% Map to cache the link objects CMake target names
% The map avoids having to re-compute the target names when generating the
% CMakeLists.txt for the top level buildInfo
cmakeTargetNames = containers.Map;

for i=1:length(libsToBuild)
    libBuildInfo = libsToBuild(i).BuildInfoHandle;
    libBuildOptsOriginal = libsToBuild(i).BuildOptsHandle;

    if inheritBuildOptionsFromTop
        % Some build options from the top component override build options
        % originating from the child component
        libBuildOptsUpdated = coder.make.internal.buildOptsApplyParentOverrides...
            (libBuildOptsOriginal, buildOptsTop);
    else
        % Build options are obtained from this component's buildInfo.mat
        libBuildOptsUpdated = libBuildOptsOriginal;
    end

    if isempty(libBuildOptsUpdated.BuildName)
        libBuildOptsUpdated.BuildName = libBuildInfo.ComponentName;
    end
        
    if isCMake
        [libBuildResults(i), targetName] = coder.make.internal.writeCMakeLists...
            (libsToBuildPaths{i}, libBuildInfo, ...
            'BuildName', libBuildOptsUpdated.BuildName, ...
            'BuildVariant', libBuildOptsUpdated.BuildVariant, ...
            'LinkObjsToSkip', cmakeLinkObjsToSkip, ...
            'IsTopLevel', false);
        cmakeTargetNames(libsToBuild(i).Name) = targetName;
    else
        
        libBuildResults(i) = RTW.genMakefileAndBuild...
            (libsToBuildPaths{i},...
            libBuildInfo, libBuildOptsUpdated);
    end
end

if ~isCMake
    % Poll for existence of updated copy of all libraries that
    % were re-compiled
    if ~isempty(libsToBuild)
        pollForUpdatedLibArchives(lUpdatedLibPoller, libBuildResults);
    end
end

% Full path to build folder for topmost component
lBuildFolderFullPath = fullfile(lRootFolder, buildInfo.ComponentBuildFolder);

if isCMake
    lMakeResult = coder.make.internal.writeCMakeLists(lBuildFolderFullPath, buildInfo, ...
        'BuildName', buildOptsTop.BuildName, ...
        'BuildVariant', buildOptsTop.BuildVariant, ...
        'GenerateChildComponents', true, ...
        'CMakeTargetNames', cmakeTargetNames,...
        'IsTopLevel', true);
else    
    lMakeResult = RTW.genMakefileAndBuild...
        (lBuildFolderFullPath, buildInfo, buildOptsTop);
end
% Add the child make results so that lMakeResult can show the log
% for all child components as well as the top component
if ~isempty(libsToBuild)
    setChildMakeResults(lMakeResult, libBuildResults)
end



function [libsToBuildPaths, libsToBuildNames] = i_getLibPaths(libsToBuild, lRootFolder)

libsToBuildPaths = {libsToBuild.Path};
libsToBuildPaths = regexprep(libsToBuildPaths, ...
    [regexptranslate('escape', '$(START_DIR)') '(\\|\/)?'], '');
libsToBuildPaths = fullfile(lRootFolder, libsToBuildPaths);

libsToBuildNames = {libsToBuild.Name};



function i_validateParam(name, value)
% Create a buildOpts for validation purposes
persistent lBuildOpts
if isempty(lBuildOpts)
    lBuildOpts = coder.make.BuildOpts;
end
originalValue = lBuildOpts.(name);

% Validate the parameter value
lBuildOpts.(name) = value;

% Restore the original value to avoid retaining any
% handle that may prevent cleanup elsewhere
lBuildOpts.(name) = originalValue;



% Check that buildInfoFolder argument is valid and load buildInfo,
% buildOpts from the file
function [buildInfo, buildOpts] = i_loadBuildInfo(buildInfoFolder)

if isfolder(buildInfoFolder)
    buildInfoFile = fullfile(buildInfoFolder, 'buildInfo.mat');
    if ~isfile(buildInfoFile)
        msg = message('coder_compile:CoderCompile:CodebuildInvalidFolder', ...
            buildInfoFolder);
        error(msg.Identifier, '%s', string(msg));
    end
elseif isfile(buildInfoFolder)
    buildInfoFile = buildInfoFolder;
else
    msg = message('coder_compile:CoderCompile:CodebuildNonExistentFolder', ...
        buildInfoFolder);
    error(msg.Identifier, '%s', string(msg));
end

validateBuildInfoContent = true;
[buildInfo, buildOpts] = coder.make.internal.loadBuildInfo...
    (buildInfoFile, validateBuildInfoContent);
        


% Convert varargin passed to codebuild to a build options object
function specifiedBuildOptions = i_applyBuildOptsFromNameValues(buildOpts, nameValueArgs)

% The input parser is expensive to create, therefore place it in a persistent variable
persistent p

if isempty(p)
    % Obtain default parameter values from coder.make.BuildOpts
    defaultOpts = coder.make.BuildOpts;
    
    p = inputParser;

    params = {
        'AllComponents'
        'BuildActions'
        'BuildConfiguration'
        'BuildMethod'
        'BuildName'
        'BuildVariant'
        'ClientChecksum'
        'CoderTargetAuxMakeContent'
        'CompileErrorDiagnosticFunctions'
        'CompileProfileFcn'
        'CopyBuildInfo'
        'CustomToolchainOptions'
        'DispHook'
        'LegacyTargetLibSuffix'
        'MakefileSelfDependency'
        'ModelRefLibPathFormat'
        'PrecompiledLibSuffix'
        'RTWVerbose'
        'SuppressExe'
        'TargetLang'
        'TargetLangExt'
        'UseFileChecksums'
        'buildHookEnvVars'
        'buildHookPathPrepends'
        'checkStatusOnly'
        'forceCompile'                % Required subject to g2041630
        'generateCodeOnly'
        'hasBuildHookOrSnifferBuild'  % Will be removed, g2059563
        'isCpp'
        'isMATLABCodeGen'
        'makeCommandPrepend'
        'makefileName'
        'ncstates'
        'sysTargetFile'
        };
    
    for i=1:length(params)
        addParameter(p, params{i}, defaultOpts.(params{i}), @(x)i_validateParam(params{i}, x));
    end

end

% Clear inputParser on exiting this function. This avoids persisting any
% object handles in p.Results. This is important otherwise it prevents
% these objects from being cleaned up, even when all other handles are
% deleted.
pCleanup = onCleanup(@()parse(p));

% Parse nameValueArgs
parse(p, nameValueArgs{:});

% Apply input parser results to the build opts object
notUsingDefaults = setdiff(p.Parameters, p.UsingDefaults);
for i=1:length(notUsingDefaults)
    parameter = notUsingDefaults{i};
    buildOpts.(parameter) = p.Results.(parameter);
end

% Input parser results are now captured in a build opts object
delete(pCleanup);

% Return names of build options that were specified by the caller of codebuild
specifiedBuildOptions = nameValueArgs(1:2:end);
