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-2024 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;

% Track if we start from default build options rather than a pre-populated
% coder.make.BuildOpts object either as an argument, or de-serialized from
% a MAT file.
startedFromDefaultBuildOptions = false;

% Track which build options have been specified/overridden as name-value
% pair arguments.
specifiedBuildOptions = {};

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;
        startedFromDefaultBuildOptions = true;

        % Apply build options specified by the caller
        if ~isempty(varargin)
            specifiedBuildOptions = i_applyBuildOptsFromNameValues(buildOptsTop, varargin);
        end
    end
end

% Start Instrumenting codebuild
lCodebuildInstrument = buildOptsTop.CodebuildProfileFcns;
lCodebuildInstrument('codebuild', true);

if ~isempty(buildInfo.DisallowedBuildVariants) && ...
        any(buildOptsTop.BuildVariant==buildInfo.DisallowedBuildVariants)
        msg = message('coder_compile:CoderCompile:DisallowedBuildVariant', ...
            string(buildOptsTop.BuildVariant), ...
            join(string(buildInfo.DisallowedBuildVariants), ', '));
        error(msg.Identifier, '%s', string(msg));
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

if coder.make.internal.featureOn('BuildInfoRootFolders')
    % Remove no longer used Root Folders
    if ~isempty(buildInfo.Settings.RootFolders)
        coder.make.internal.clearUnusedRootFolders(buildInfo);
    end
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)

% Load child components, including shared libraries and referenced models.
% This is an optimization so that we don't have to load and unload
% referenced buildInfo each time we call method such as getLinkFlags that
% must query them.
% TODO(g2446267) skip loading buildInfo hierarchy if building a static library.
loadChildBuildInfos(buildInfo);

% Convert the build method to a toolchain info or TMF properties object.
% This an expensive operation and performing it here for the top component
% avoids incurring the same expense for each child component.
[buildOptsTop.BuildMethod, toolchainInfo] = i_resolveBuildMethod(buildOptsTop.generateCodeOnly, buildOptsTop.BuildMethod);

% Make sure that the BuildConfiguration is valid for the current toolchain
buildOptsTop.BuildConfiguration = i_resolveBuildConfiguration( ...
    buildOptsTop.BuildMethod, ...
    buildOptsTop.BuildConfiguration, ...
    startedFromDefaultBuildOptions, ...
    specifiedBuildOptions);

% Determine the code build strategy (Classic Makefiles, CMake, etc.)
strategy = coder.make.internal.CodeBuildStrategy.create(buildOptsTop);

% If the toolchain supports building mex, make sure the mex options are set
buildOptsTop.MexCompInfo = strategy.resolveMexCompInfo(buildOptsTop.MexCompInfo, toolchainInfo);

% Set up compiler environment (for MSVC)
environmentCleanupFcn = i_setupEnvironment...
    (buildOptsTop.MexCompInfo, buildOptsTop.BuildMethod);

% 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
[libsToBuild, buildTopComponent] = ...
    i_getLibsToBuild(buildInfo, buildOptsTop.ComponentsToBuild);

lRootFolder = buildInfo.Settings.LocalAnchorDir;

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

% Check if MinGW is being used with a build directory that contains
% characters outside of the system encoding and error out if that is the
% case.
coder.make.internal.checkForUnicodePathWithMinGW(...
    buildOptsTop.MexCompInfo, lBuildFolderFullPath, buildOptsTop.generateCodeOnly);

% Check if attempting to use MinGW or LCC with a build directory
% containing a space on a machine without 8dot3 enabled
coder.make.internal.checkForBuildFolderWithSpaceMinGW(...
    buildOptsTop.MexCompInfo, lBuildFolderFullPath);

% Prepare to build libraries
if ~isempty(libsToBuild)
    libBuildResults = coder.make.MakeResult.empty(0, length(libsToBuild));
    
    % For the existence check, always use build results for the static
    % library (in the case of a mex target this differs from the final
    % product for each component)
    libBuildResultsForPoller = coder.make.MakeResult.empty(0, length(libsToBuild));
    
    [libsToBuildPaths, libsToBuildNames] = i_getLibPaths(libsToBuild, lRootFolder);
    lUpdatedLibPoller = coder.make.internal.UpdatedLibArchivePoller...
        (libsToBuildPaths, libsToBuildNames);
end


% Run any initialization steps for the code build strategy.
strategy.initialize(buildInfo, buildOptsTop, toolchainInfo);

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
        
    localStrategy = strategy.getLocalStrategy(libBuildOptsUpdated);
    isTopLevel = false;
    libModelRefTargetType = i_getModelRefTargetType(libBuildInfo, libBuildOptsUpdated, isTopLevel);
    [libBuildResults(i), libBuildResultsForPoller(i)] = ...
        localStrategy.build(libBuildInfo, ...
        libsToBuildPaths{i}, ...
        libBuildOptsUpdated, ...
        libModelRefTargetType, ...
        toolchainInfo, ...
        isTopLevel);

    % Serialize the compileInfo.mat file
    if ~isempty(libBuildResults(i).CompileInfo)
        saveInfoFile(libBuildResults(i).CompileInfo);
    end
end

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

if buildTopComponent
    localStrategy = strategy.getLocalStrategy(buildOptsTop);
    isTopLevel = true;
    modelRefTargetType = i_getModelRefTargetType(buildInfo, buildOptsTop, isTopLevel);
    lMakeResult = localStrategy.build(buildInfo, ...
        lBuildFolderFullPath, ...
        buildOptsTop, ...
        modelRefTargetType, ...
        toolchainInfo, ...
        isTopLevel);

    buildOptsTop.PostCodeBuildAction(buildInfo, buildOptsTop);
else
    lMakeResult = coder.make.MakeResult('', '', []);
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)
    addChildMakeResults(lMakeResult, libBuildResults)
end

% Serialize the compileInfo.mat file
if ~isempty(lMakeResult.CompileInfo)
    saveInfoFile(lMakeResult.CompileInfo);
end

% Restore environment settings
delete(environmentCleanupFcn)

% End of codebuild Instrumentation
lCodebuildInstrument('codebuild', false);


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 = {
        'ComponentsToBuild'
        'BuildActions'
        'BuildConfiguration'
        'BuildMethod'
        'BuildName'
        'BuildVariant'
        'ClientInfo'
        'ClientChecksum'
        'CMakeConfigureOnly'
        'CMakePackages'
        'CMakeListsEmitter'
        'CMakeListsPreEmitHook'
        'CoderTargetAuxMakeContent'
        'CompileErrorDiagnosticFunctions'
        'CodebuildProfileFcns'
        'CopyBuildInfo'
        'CustomToolchainOptions'
        'DispHook'
        'LegacyTargetLibSuffix'
        'MakefileSelfDependency'
        'ModelRefLibPathFormat'
        'NeedObjectLibForCMakeExecutableTarget'
        'PrecompiledLibSuffix'
        'RTWVerbose'
        'SuppressExe'
        'TargetLang'
        'TargetLangExt'
        'UseFileChecksums'
        'buildHookEnvVars'
        'buildHookPathPrepends'
        'checkStatusOnly'
        'forceCompile'                % Required subject to g2041630
        'generateCodeOnly'
        '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.
% Note that "p.UsingDefaults" will not contain parameters where the
% parameter has been explicitly specified with the default value.
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
% (resolved for the case of partial matches).
specifiedBuildOptions = notUsingDefaults;

% Convert the build method to an object if it's a toolchain or TMF build
function [buildMethodResolved, toolchainInfo] = i_resolveBuildMethod(isGenCodeonly, buildMethod_in)

wasBuildMethodEmpty = isempty(buildMethod_in);

% If build method is empty, use default toolchain for the current platform
if wasBuildMethodEmpty
    buildMethod_in = coder.make.internal.getDefaultToolchain('InstalledOrFirstSupported', false);
end

buildMethodResolved = buildMethod_in;
[toolchainInfo, lTMFProperties] = ...
    coder.make.internal.resolveToolchainOrTMF(buildMethod_in);
if ~isempty(lTMFProperties)
    buildMethodResolved = lTMFProperties;
elseif ~isempty(toolchainInfo)
    buildMethodResolved = toolchainInfo;
end

% DisallowLCC feature is specific for Windows platform so following errors
% only reachable if on Windows

if ~isGenCodeonly && ispc && ~isempty(toolchainInfo)
    % toolchainInfo obj can be used to check if using LCC toolchain and error out in positive case.

    isFeatDisallowLCCOn = matlab.internal.feature('DisallowLCC');
    shouldDisallowLCC = isFeatDisallowLCCOn ...
        && any(strcmpi(toolchainInfo.Alias, 'LCC-x'));

    % Erroring out when using LCC toolchain with codebuild and DisallowLCC feature is On
    if shouldDisallowLCC
        if ~isempty(lTMFProperties) % LCC is used as toolchain + TMF build
            % Suggest using a different TMF with no LCC
            error(message('coder_compile:CoderCompile:TMF_LccDeprecatedError', buildMethod_in));
        else % Toolchain Build
            if wasBuildMethodEmpty
                % Suggest to INSTALL since no mex comp available
                error(message('coder_compile:CoderCompile:noSupportedCompilerSuggestMinGW'));
            else % covers the manual selection of LCC
                % Suggest selecting a different toolchain since LCC has been specified as the build toolchain
                error(message('coder_compile:CoderCompile:LccDeprecatedError'));
            end
        end
    end
end




function environmentCleanupFcn = i_setupEnvironment...
    (mexCompInfo, buildMethod)

environmentCleanupFcn = onCleanup(@()[]);
if ispc && coder.make.internal.isFactoryMsvc(buildMethod)
    if ~isempty(mexCompInfo)
        % Apply environment settings for MSVC
        mexCompDetails = mexCompInfo.comp.Details;
        environmentCleanupFcn = coder.make.internal.applyMsvcEnvironment(mexCompDetails);
    end
end

% Determine which components must be built
function [libsToBuild, buildTopComponent] = i_getLibsToBuild(buildInfo, componentsToBuild)

if isempty(componentsToBuild)
    % Building all components
    buildTopComponent = true;
    linkablesRequested = {}; % Build all libraries
else
    buildTopComponent = any(strcmp(componentsToBuild, buildInfo.ComponentName));
    linkablesRequested = setdiff(componentsToBuild, buildInfo.ComponentName, 'stable');
    if isempty(linkablesRequested)
        libsToBuild = {};
        return
    end
end

linkables = [buildInfo.LinkObj(end:-1:1); buildInfo.ModelRefs(end:-1:1)];
hasBuildInfoIdx = ~cellfun(@isempty, {linkables.BuildInfoHandle});
buildableLinkables = linkables(hasBuildInfoIdx);
if isempty(buildableLinkables)
    linkableNames = {};
else
    buildInfoHandles = [buildableLinkables.BuildInfoHandle];
    linkableNames = {buildInfoHandles(:).ComponentName};
end

% Filter according to setting of ComponentsToBuild argument
if ~isempty(componentsToBuild)
    missingComponents = setdiff(linkablesRequested, linkableNames);
    if ~isempty(missingComponents)
        missingComponents = join(string(missingComponents), ', ');
        msg = sprintf(['Cannot build components %s. The requested ' ...
            'ComponentsToBuild must each correspond to a ComponentName ' ...
            'in the buildInfo hierarchy.'], missingComponents);
        assert(false, msg);
    end
    [~, ia] = intersect(linkableNames, linkablesRequested, 'stable');
else
    % Building all components
    ia = true(size(linkableNames));
end
keepIdx = false(size(buildableLinkables));
keepIdx(ia) = true;

% Filter according to setting of RTW.BuildInfoLinkObj > NeverRebuild
neverRebuildIdx = [buildableLinkables.NeverRebuild];
keepIdx(neverRebuildIdx) = false;

libsToBuild = buildableLinkables(keepIdx);

% TODO(g2446267) remove any child linkables as not required when building 
% a static library. At present they are included in the checksum 
% incurring some performance overhead.


function buildConfiguration = i_resolveBuildConfiguration(buildMethod, ...
    buildConfiguration_in, ...
    startedFromDefaultBuildOptions, ...
    specifiedBuildOptions)
% For toolchain builds, select a valid default build configuration for 
% the specified build method if:
%
%   * A populated coder.make.BuildOpts was not provided
%   * The user has not explicitly specified a BuildConfiguration
%   * A toolchain (or CMake) based build method is being used
%   * The default Build Configuration from coder.make.BuildOpts is not a
%     valid build configuration for the selected toolchain
%   * The toolchain has one or more valid build configurations.
%
buildConfiguration = buildConfiguration_in;
if startedFromDefaultBuildOptions && ...
        ~ismember('BuildConfiguration', specifiedBuildOptions) && ...
        coder.make.internal.isToolchainObject(buildMethod)

    % (Convention for BuildConfigurations is that an exact case-sensitive
    % match is required for the build configuration name.)
    validBuildConfigurations = buildMethod.getBuildConfigurations();
    if ~ismember(buildConfiguration_in, validBuildConfigurations) && ...
            ~isempty(validBuildConfigurations)
        buildConfiguration = validBuildConfigurations{1};
    end
end

function modelReferenceTargetType = i_getModelRefTargetType(buildInfo, buildOpts, isTopLevel)

if isTopLevel
    % Target type for final product
    [~, argErtSfcn] = findBuildArg(buildInfo, 'GENERATE_ERT_S_FUNCTION');
    isERTSFcn = strcmp(argErtSfcn, '1');
else
    isERTSFcn = false;
end
modelReferenceTargetType = coder.make.internal.getModelRefTargetType...
    (buildOpts.BuildVariant, isERTSFcn);