function lMakeResult = genMakefileAndBuild(buildFolder_in, buildInfo_in, buildOpts_in)
% GENMAKEFILEANDBUILD Creates makefile to compile code registered in buildInfo
%
%  Arguments buildInfo and templateMakefile must be present. Additionally, a series of name, value
%  pairs may be provided.

% Copyright 1994-2020 The MathWorks, Inc.

% TODO(g2181720): the following block of code is duplicated in codebuild and will be removed
% when call sites are updated

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

if coder.make.internal.isRelativePath(buildFolder_in)
        lBuildFolderFullPath = fullfile(pwd, buildFolder_in);
else
        lBuildFolderFullPath = buildFolder_in;
end    

if nargin==2
        % buildInfo_in has entire content of buildInfo.mat
        buildInfo = buildInfo_in.buildInfo;
    buildOpts_in = buildInfo_in.buildOpts;
else 
        buildInfo = buildInfo_in;
end

lToolchainOrTMF = buildOpts_in.BuildMethod;

% Capture buildArgs before we make any synchronization changes
if ~isempty(buildInfo.BuildArgs)
    lBuildArgValues = {buildInfo.BuildArgs.Value};
    lBuildArgKeys = {buildInfo.BuildArgs.Key};
else
    lBuildArgValues = {};
    lBuildArgKeys = {};
end

% Determine if current build variant generates a mex file
lBuildVariant = buildOpts_in.BuildVariant;
isMexTarget = lBuildVariant==coder.make.enum.BuildVariant.MEX_FILE;

% Derive model reference target type from build variant (with 
% special case for ERT S-function target)
[~, argErtSfcn] = findBuildArg(buildInfo, 'GENERATE_ERT_S_FUNCTION');
isERTSFcn = strcmp(argErtSfcn, '1');
lModelReferenceTargetType = coder.make.internal.getModelRefTargetType...
    (lBuildVariant, isERTSFcn);

% Synchronize buildInfo token MODELREF_TARGET_TYPE
i_syncBuildInfoTargetType(buildInfo, lModelReferenceTargetType);

% Populate required build args with default values if not already set
coder.make.internal.initBuildInfoBuildArgs(buildInfo);

% Populate required TMF tokens with default values if not already set
componentName = buildInfo.ComponentName;
coder.make.internal.initTMFTokens(buildInfo, ...
    'ModelName', componentName, ...
    'MakefileName', [componentName '.mk']);

if isempty(buildOpts_in.BuildName)
    lBuildName = componentName;
else
    lBuildName = buildOpts_in.BuildName;
end

% Suppress building executable only applies for standalone executable
lSuppressExe = buildOpts_in.SuppressExe && ...
    strcmp(lBuildVariant, 'STANDALONE_EXECUTABLE');

buildOptsInternal.('buildHookEnvVars') = buildOpts_in.buildHookEnvVars;
buildOptsInternal.('buildHookPathPrepends') = buildOpts_in.buildHookPathPrepends;
buildOptsInternal.('isMATLABCodeGen') = buildOpts_in.isMATLABCodeGen;
buildOptsInternal.('forceCompile') = buildOpts_in.forceCompile;
buildOptsInternal.('isCpp') = buildOpts_in.isCpp;
buildOptsInternal.('SuppressExe') = lSuppressExe;
buildOptsInternal.('hasBuildHookOrSnifferBuild') = buildOpts_in.hasBuildHookOrSnifferBuild;
buildOptsInternal.('checkStatusOnly') = buildOpts_in.checkStatusOnly;
buildOptsInternal.('makeCommandPrepend') = buildOpts_in.makeCommandPrepend;
buildOptsInternal.('makefileName') = buildOpts_in.makefileName;
buildOptsInternal.('sysTargetFile') = buildOpts_in.sysTargetFile;
buildOptsInternal.('TargetLangExt') = buildOpts_in.TargetLangExt;

% Arguments that are not included in buildOpts
lUserSrcs = coder.make.internal.getUserSrcs(buildInfo);
lBuildConfiguration = buildOpts_in.BuildConfiguration;
lCustomToolchainOptions = buildOpts_in.CustomToolchainOptions;

% Get updated TMF properties and the Toolchain info, if available; 
[lToolchainInfo, lTMFProperties] = coder.make.internal.resolveToolchainOrTMF(lToolchainOrTMF);

i_checkBuildConfiguration(lBuildConfiguration, lToolchainInfo, lTMFProperties);

% Extract the "BUILD" setting, needed to determine if the makefile must be run
[makeArgsRunMakefile, lMakeArgs, preCompTargets] = ...
    i_getMakeArgsRunMakefile(buildInfo, lTMFProperties);

% Determine if makefile must be run
runMakefile = ~buildOpts_in.generateCodeOnly && makeArgsRunMakefile;
buildOptsInternal.('generateCodeOnly') = ~runMakefile;


% Update buildInfo with non-default file separator; the returned
% onCleanup ensures that buildInfo is always restored to its original
% state.
restoreFileSepFcn = i_buildInfoFileSepUpdate(buildInfo, lToolchainInfo, lTMFProperties);


if buildOpts_in.ModelRefLibPathFormat ~= coder.make.internal.ModelRefLibPathFormat.NOT_SPECIFIED
    % If a value was specified via input argument, this takes precedence
    lModelRefLibPathFormat = buildOpts_in.ModelRefLibPathFormat;
elseif ~isempty(lToolchainInfo) && isAttribute(lToolchainInfo, 'PreferAbsolutePaths')
    % Toolchain has a preference for absolute paths
    lModelRefLibPathFormat = coder.make.internal.ModelRefLibPathFormat.ABSOLUTE;
else
    % Default is to use paths relative to the code generation folder
    lModelRefLibPathFormat = coder.make.internal.ModelRefLibPathFormat.RELATIVE;
end

% Get makefile dependencies
[lMakefileDependenciesTokenized, lMakefileDependenciesFullPath] = ...
    i_getMakefileDependencies(lBuildFolderFullPath);

lMakefileSelfDependency = buildOpts_in.MakefileSelfDependency;

lPrecompiledLibSuffix = buildOpts_in.PrecompiledLibSuffix;

lCoderTargetAuxMakeContent = buildOpts_in.CoderTargetAuxMakeContent;

% set up the makefile name if not provided
if isempty(buildOpts_in.makefileName)
    lMakefileName = [componentName,'.mk'];
else
    lMakefileName = buildOpts_in.makefileName;
end

% Synchronize buildInfo MAKEFILE_NAME token
i_syncMakefileName(buildInfo, lMakefileName);

% Synchronize buildInfo STANDALONE_SUPPRESS_EXE token
i_syncSuppressExe(buildInfo, lSuppressExe);

[finalTarget, outputDirTokenized] = ...
    coder.make.internal.getBuildFinalTarget...
    (buildInfo, lToolchainInfo, lBuildVariant, lBuildName, ...
    buildOpts_in.LegacyTargetLibSuffix);

finalTargetWithPath = i_getFinalTargetFullPath...
    (finalTarget, outputDirTokenized, buildInfo, lBuildFolderFullPath);

% Get checksums for files that are inputs to the compile stage
lCompileInfo = coder.make.internal.CompileInfoFile(lBuildFolderFullPath);

% Create make result object to capture log and other return values
lBuildLogger = i_getBuildLogger ...
    (buildOpts_in.RTWVerbose, lTMFProperties, buildOpts_in.DispHook);

tmfSkipChecksums = ~isempty(lTMFProperties) && ...
    strcmp(lTMFProperties.TMF_DO_NOT_USE_CHECKSUMS, '1');

toolchainSkipChecksums = ~isempty(lToolchainInfo) && ...
    isAttribute(lToolchainInfo, 'DoNotUseChecksums');

useChecksums = buildOpts_in.UseFileChecksums && ~tmfSkipChecksums && ...
    ~toolchainSkipChecksums;

if useChecksums
    
    setComponentName(lCompileInfo, buildInfo.ComponentName);
    
    % Update client checksum
    lClientChecksum = buildOpts_in.ClientChecksum;
    setClientChecksum(lCompileInfo, lClientChecksum);

    % Update config checksum
    lConfigChecksum = coder.make.internal.getCompileConfigChecksum...
        (buildOpts_in.BuildConfiguration, lBuildVariant, ...
        lToolchainInfo, lTMFProperties, ...
        lBuildArgKeys, lBuildArgValues, ...
        getCompileFlags(buildInfo), ...
        buildInfo.MakeArgs);
    setConfigChecksum(lCompileInfo, lConfigChecksum);
    
    % Update source file and include path checksums
    [lSourceFileChecksums, incPathsChecksum] = coder.make.internal.getSourceFileChecksums...
        (buildInfo, lUserSrcs);
    setSourceFileChecksum(lCompileInfo, lSourceFileChecksums);
    setIncPathsChecksum(lCompileInfo, incPathsChecksum);
    
    % Update defines checksum
    setDefinesChecksum(lCompileInfo, getDefines(buildInfo));

    % Update makefile dependency checksums
    lDependencyChecksums = coder.make.internal.getMakefileDependencyChecksums...
        (lMakefileDependenciesTokenized, lMakefileDependenciesFullPath);
    setDependencyChecksum(lCompileInfo, lDependencyChecksums);
    
    % Set value to record if makefile will be run
    setRunMakefile(lCompileInfo, runMakefile);
    
    % Update link object checksums
    [managedLinkObjChecksum, customLinkObjChecksum] = ...
        coder.make.internal.getLinkObjChecksums(buildInfo, lBuildVariant);
    
    setManagedLinkObjChecksum(lCompileInfo, managedLinkObjChecksum);
    setCustomLinkObjChecksum(lCompileInfo, customLinkObjChecksum);    

    % On error exit from compile stage, delete the file compileInfo.mat as
    % it may contain incorrect data    
    setDoCleanup(lCompileInfo);
    
    if ~isempty(lToolchainInfo)
        % Determine object code that will be generated
        objectCodeFiles = i_getObjFiles...
            (lCompileInfo, lToolchainInfo, buildOptsInternal.generateCodeOnly);
    else
        % Look for any existing object files by checking for typical
        % object file extensions
        objectCodeFiles = i_getObjFilesNoToolchain(lCompileInfo, lBuildFolderFullPath);
    end
    
    % Determine if current checksums match stored checksums
    allChecksumsMatch = i_allChecksumsMatch(lCompileInfo, lMakefileSelfDependency);
    
    lMakefileNameFullPath = fullfile(lBuildFolderFullPath, lMakefileName);
    
    if allChecksumsMatch
        
        % Check for existence of all object files
        objFilesThatMustExist = getObjFilesThatMustExist(lCompileInfo); 
        objFilesThatMustExist = fullfile(lBuildFolderFullPath, objFilesThatMustExist);        
        
        finalTargetExistsAsExpected = isfile(finalTargetWithPath) || buildOpts_in.generateCodeOnly;
        
        lMakefileExists = isfile(lMakefileNameFullPath);
        
        % Coder targets may perform additional actions that must not be
        % skipped (tRunOnBoot)
        isCoderTargetExe = ~isempty(lToolchainInfo) && ...
            ~isempty(regexp(lToolchainInfo.BuildArtifactExecutor, '^codertarget\.', 'once')) && ...
            strcmp(lBuildVariant, 'STANDALONE_EXECUTABLE');
        
        % If object files and final target exist, return early
        if all(isfile(objFilesThatMustExist)) && finalTargetExistsAsExpected && ...
                ~isCoderTargetExe && lMakefileExists
            
            checksumMsg = string(message...
                ('coder_compile:CoderCompile:SkipCompilation', ...
                regexprep(finalTargetWithPath, '^\.', ...
                regexptranslate('escape', lBuildFolderFullPath), 'once')));
            
            addToBuildLog(lBuildLogger, checksumMsg); 
            lMakeResult = coder.make.MakeResult(lBuildLogger.Log, finalTarget);
            
            % Successful invocation of compile stage: preserve content of compileInfo.mat
            clearDoCleanup(lCompileInfo);
            return;
        end
    else
        % If the compileInfo.mat did not exist: any existing makefile is
        % stale and must be removed
        if ~hasStoredChecksum(lCompileInfo) && isfile(lMakefileNameFullPath)
            delete(lMakefileNameFullPath)
        end
        
        % Remove any stale binaries
        staleObjs = getStaleObjs(lCompileInfo, lMakefileSelfDependency, objectCodeFiles);
        staleObjs = fullfile(lBuildFolderFullPath, staleObjs);
        staleObjs = staleObjs(isfile(staleObjs));
        cellfun(@delete, staleObjs);

        % px4 target generates a .mk as final target prior to invoking and independent of the compile stage
        % and this .mk file must not be removed. TODO(g2084008) eliminate the need for this workaround
        isPx4 = ~isempty(lToolchainInfo) && ...
            strcmp(lToolchainInfo.Name, 'GNU Tools for PX4 Autopilots');
        if isfile(finalTargetWithPath) && ~isPx4
            delete(finalTargetWithPath);
        end
    end
else
    % Remove any compileInfo.mat because it is stale
    deleteInfoFile(lCompileInfo);
end

% Update buildInfo with SSE4 or AVX2 or AVX512 support
if buildInfo.Settings.CompilerRequirements.supportSSE4Intrinsic
    addSimdSupport(lToolchainInfo, buildInfo, buildOpts_in.isCpp, 'SSE4');
elseif buildInfo.Settings.CompilerRequirements.supportAVXIntrinsic
    addSimdSupport(lToolchainInfo, buildInfo, buildOpts_in.isCpp, 'AVX2');
elseif buildInfo.Settings.CompilerRequirements.supportAVX512Intrinsic
    if contains( lToolchainInfo.Name, 'Microsoft Visual C++ 2017')
        DAStudio.warning('coder_compile:toolchain:LimitedSupportSimdAvx', lToolchainInfo.Name, 'AVX512');
    elseif contains( lToolchainInfo.Name, 'Microsoft Visual C++ 2015')
        DAStudio.error('coder_compile:toolchain:NotSupportSimdAvx', lToolchainInfo.Name, 'AVX512');
    end
    addSimdSupport(lToolchainInfo, buildInfo, buildOpts_in.isCpp, 'AVX512');
end

% Update buildInfo with simd OpenMP support
if buildInfo.Settings.CompilerRequirements.supportOpenMP
    coder.make.internal.addOpenMPSupport(lToolchainInfo, buildInfo, buildOpts_in.isCpp);
end

% Update buildInfo with OpenCV support
if buildInfo.Settings.CompilerRequirements.supportOpenCV
    coder.make.internal.addOpenCVSupport(lToolchainInfo, buildInfo, buildOpts_in.isCpp);
end

% Add define -DPORTABLE_WORDSIZES, if required
coder.make.internal.resolveCompilerRequirementsForPWS...
    (buildInfo, lToolchainInfo);

% For mex-compatible compilers, we need the corresponding Toolchain name
% for correct handling of system libraries
mexToolChainName = coder.make.internal.getMexToolchainName(lToolchainInfo);

% Update buildInfo with Pthread support
if buildInfo.Settings.CompilerRequirements.supportMulticorePthread
    coder.make.internal.addMulticorePthreadSupport(buildInfo);
end

% Identify the mex options file (empty if toolchain does not support building mex files)
lMexOpts = coder.make.internal.getToolchainMexOpts(lToolchainInfo);

% LCC special case
buildInfo = locUpdateBuildInfo(buildInfo, lToolchainInfo, isMexTarget, ...
    lBuildVariant);

% Compile error diagnostic functions
compileErrorDiagnosticFcns = [buildOpts_in.CompileErrorDiagnosticFunctions, ...
                    {@(compilerOutput)i_inconsistentDebugLevel(lToolchainInfo, compilerOutput)}];

% Throw an exception if any link only link object does not exist
i_validateLinkOnlyObjectsExist...
    (buildInfo, lBuildVariant, buildOptsInternal.generateCodeOnly, ...
     lToolchainInfo, buildOpts_in.LegacyTargetLibSuffix);

if isMexTarget
    % Extract mex source files from buildInfo so that they won't be
    % compiled twice
    lMexSourceFiles = i_extractMexSourceFiles(buildInfo);
    
    
    % For mex build, other than ERT S-function, remove any files in 'Sfcn'
    % group
    if ~isERTSFcn
        removeSourceFiles(buildInfo, '', {'Sfcn'});
    end
end

% Synchronize buildInfo MODELLIB token (must be done only after removing source
% files not required for a mex build.
i_syncModellib(buildInfo, finalTarget, lBuildVariant, lUserSrcs)

% cd into build folder if not already there
if ~strcmp(lBuildFolderFullPath, pwd)
    origFolder = cd(lBuildFolderFullPath);
    restoreOrigFolder = onCleanup(@()cd(origFolder));
end

if ~isempty(lTMFProperties)

    % Template makefile build must honor the debug build configuration
    i_tmfRequestDebugBuild(buildInfo, lBuildConfiguration);
    
    % Determine string generated by makefile when final product is created and 
    % when make file runs successfully
    productCreatedMessage = i_getCreatedMessage(lTMFProperties, lBuildName);    
    
    % Link objects in buildInfo with no file extension will have the static library
    % extension appended
    lStaticLibSuffix = coder.make.internal.getStaticLibSuffix ...
        (lToolchainInfo, buildOpts_in.LegacyTargetLibSuffix);
    
    [lExtraCompileOptFlags, lDownload, lMakeCmd, lWrappedMakeCmd] = ...
        invokeTMF(buildInfo,...
                             lBuildVariant, ...
                             lBuildLogger, ...
                             buildOpts_in.sysTargetFile, ...
                             buildOpts_in.buildHookEnvVars, ...
                             buildOpts_in.buildHookPathPrepends, ...
                             lSuppressExe, ...
                             buildOpts_in.forceCompile, ...
                             lMakeArgs, ...
                             lTMFProperties, ...
                             lModelRefLibPathFormat, ...
                             lMakefileSelfDependency, ...
                             mexToolChainName, ...
                             lBuildConfiguration, ...
                             lCustomToolchainOptions, ...
                             lMexOpts, ...
                             lBuildFolderFullPath, ...
                             lMakefileName, ...
                             finalTarget, ...
                             outputDirTokenized, ...
                             lMakefileDependenciesTokenized, ...
                             lUserSrcs, ...
                             preCompTargets, ...
                             lStaticLibSuffix);
    
    tmfMacros = getMacrosForBuildResult(lTMFProperties); 
    tcWriter = [];
    tcExecutor = [];

else % Toolchain build
    
    % Create the BuildTools object needed for Toolchain build
    buildInfo.BuildTools = coder.make.internal.BuildTools.getBuildToolsForToolchain...
        (lToolchainInfo, lBuildVariant);
    
    if ~isequal(buildOpts_in.BuildActions, coder.make.enum.BuildAction.NONE)
        buildInfo.BuildTools.BuildActions = buildOpts_in.BuildActions;
    end
    
    % Do not expose BuildTools field to the caller
    removeBuildTools = onCleanup(@()i_removeBuildTools(buildInfo));

    % Collect information needed by the builder
    builderArgs = locSetupBuilderArgs(buildOptsInternal, ....
        'runMakefile', true);
    
    builderArgs = [builderArgs, ...
        {'compileErrorDiagnosticFcns', compileErrorDiagnosticFcns, ...
        'ModelRefLibPathFormat', lModelRefLibPathFormat, ...
        'MakefileSelfDependency', lMakefileSelfDependency, ...
        'MakefileDependencies', lMakefileDependenciesTokenized, ...
        'PrecompiledLibSuffix', lPrecompiledLibSuffix, ...
        'ToolchainFinalTarget', finalTarget, ...
        'ToolchainOutputDir', outputDirTokenized, ...
        'CoderTargetAuxMakeContent', lCoderTargetAuxMakeContent}];

    % Before invoking the builder, make sure BuildInfo's copy of BuildOpts is
    % the latest.
    setBuildOpts(buildInfo.BuildTools, buildOptsInternal);

    % Invoke the builder
    [productCreatedMessage, lWrappedMakeCmd, lMakeCmd, tcExecutor, tcWriter] = ...
        coder.make.invokeBuilder...
        (buildInfo, lBuildConfiguration, lCustomToolchainOptions, ...
        lMakefileName, lBuildLogger, lBuildName, builderArgs{:});

    lDownload = false;
    tmfMacros = coder.make.internal.getTMFMacrosForToolchainBuild;
    
    lExtraCompileOptFlags = {};
end

if runMakefile
    lFoundCreatedMessage = ...
        i_runMakefile(lBuildName, lBuildLogger, lTMFProperties, lWrappedMakeCmd, ...
        buildOpts_in.makeCommandPrepend, lMakeCmd, ...
        tcWriter, tcExecutor, productCreatedMessage, ...
        buildInfo, compileErrorDiagnosticFcns, ...
        buildOpts_in.CompileProfileFcn);
else
    lFoundCreatedMessage = false;
end

% Capture return values in lMakeResult
lMakeResult = coder.make.MakeResult...
    (lBuildLogger.Log, finalTarget, lMakeCmd, lWrappedMakeCmd, ...
     tmfMacros, lDownload, lFoundCreatedMessage, runMakefile);

% Restore buildInfo file separator to its original state
delete(restoreFileSepFcn);

% Generate debug symbols for Mac host based toolchain
i_generateMacDebugSymbols(finalTargetWithPath, ...
        buildOpts_in.generateCodeOnly, ...
        lBuildConfiguration, ...
        lBuildVariant, ...
        lToolchainInfo, ...
        lBuildLogger, ...
        lCustomToolchainOptions, ...
        buildInfo, ...
        lTMFProperties, ...
        lSuppressExe);

% Generate mex file, if required    
if runMakefile && isMexTarget    
    % Some inputs to mexbld depend on the build variant
    [lModelLib, lMexDefines] = i_getMexBuildData(buildInfo);
    
    if isempty(lMexOpts)
        i_reportNoMexCompiler(lTMFProperties, lToolchainInfo);
    end
    
    % Determine if debug build
    isDebugBuild = i_isDebugBuild(lTMFProperties, lBuildConfiguration, ...
                                  buildInfo, lCustomToolchainOptions, ...
                                  lToolchainInfo);
    
    % Force recompilation of mex file if any checksum did not match or if
    % requested by the client
    forceMexCompile = buildOpts_in.forceCompile || ~allChecksumsMatch;
    
    % Create the mex file
    coder.make.internal.mexbld(buildInfo, lMexSourceFiles, ...
        lModelLib, lMexDefines, lModelRefLibPathFormat, mexToolChainName, ...
        lMexOpts.comp.MexOpt, forceMexCompile, lBuildLogger, lPrecompiledLibSuffix, ...
        buildOpts_in.LegacyTargetLibSuffix, lExtraCompileOptFlags, lBuildConfiguration, ...
        isDebugBuild);
    
    mexSrcFileName = lMexSourceFiles{1};
else
    mexSrcFileName = '';
end

% Update object code checksums on successful compilation
if useChecksums
    
    if isempty(lToolchainInfo) && ~buildOptsInternal.generateCodeOnly
        % Update the list of object files based on search for typical 
        % object code file extensions
        [objectCodeFiles, foundObjFiles, searchExts] = i_getObjFilesNoToolchain...
            (lCompileInfo, lBuildFolderFullPath);
    else
        % No need to search for object files because the extension is
        % specified in the toolchain definition
        foundObjFiles = true;
    end
    
    if foundObjFiles
        checksumForMissingObjFile = i_getChecksumForMissingObj(buildInfo, mexSrcFileName);
        
        i_updateObjectCodeChecksums(objectCodeFiles, lCompileInfo, ...
            checksumForMissingObjFile, buildOptsInternal.generateCodeOnly);
        
        saveInfoFile(lCompileInfo);
    else
        % Issue warning that checksums are not used
        msg = message('coder_compile:CoderCompile:CannotFindObjs', ...
            lBuildFolderFullPath, join(string(searchExts), ", "), lTMFProperties.TemplateMakefile);
        warning(msg.Identifier, '%s', string(msg))
    end
        
end
    


%==========================================================================
% Collect information needed by the BTI builder
%==========================================================================
function builderArgs = locSetupBuilderArgs(buildOpts, varargin)

builderArgs = horzcat( varargin, { ...
    'isMATLABCodeGen',  buildOpts.isMATLABCodeGen, ...
    });



function [toksOut, tokValsOut] = i_getTokensFromBuildInfo(buildInfo, lMakefileSelfDependency)

toks    =  get(buildInfo.Tokens,'Key');
tokVals =  get(buildInfo.Tokens,'Value');

% When tokens are not present make sure we have an empty cell data type
if isempty(toks)
    toks = {};
    tokVals = {};
elseif ischar(toks) && ischar(tokVals)
    % A single token is returned as a character array: for consistency, must convert
    % it to a cell array.
    toks = {toks};
    tokVals = {tokVals};
end

if ~lMakefileSelfDependency
    % If a change in the makefile must not trigger re-compilation of 
    % object code, set MAKEFILE_NAME to empty
    makefileNameIdx = strcmp(toks, '|>MAKEFILE_NAME<|');
    tokVals{makefileNameIdx} = '';
end


% Discard the MODEL_MODULES and MODEL_MODULES_OBJ tokens because they will be
% evaluated separately
toksToDiscard = {'|>MODEL_MODULES<|', '|>MODEL_MODULES_OBJ<|'};
[toksOut, idx] = setdiff(toks, toksToDiscard, 'stable');
tokValsOut = tokVals(idx);


function tokValAsLogical = i_getOnOffTokenAsLogical(toks, tokVals, tokName)
% This function may be used for token values that are guaranteed to be either
% '0' or '1'. For example, the tokens |>GENERATE_ALLOC_FCN<|,
% |>CLASSIC_INTERFACE<| always take one of these values - according to whether
% the original model parameter was set to 'off' or 'on'.

% Get token corresponding to model parameter GenerateAllocFcn
tokIdx = strcmp(toks, tokName);
if any(tokIdx)
    tokValAsLogical = tokVals(tokIdx);
    % Cell array has length one because addTMFTokens cannot give duplicates
    tokValAsLogical = str2double(tokValAsLogical{1});
    % Value true/false corresponds to original model parameter = on/off
    tokValAsLogical = logical(tokValAsLogical);
else 
    tokValAsLogical = false;
end


function [modulesUpdated, pathsUpdated] = i_removeModulesFromList(modulesToRemove, modules, modulesValPaths)
% MODULES is a cell array of compilation modules specifying source files to
% compile, including language extension e.g. 'file1.c'. MODULESTOREMOVE is a
% cell array of source files to remove. Note that MODULESTOREMOVE are specified
% with no file extension, e.g. 'file1'.

% Regular expression to match any file extension
fileExtExpr = '\.\w+';

keepIdx = true(size(modules));
for i=1:length(modulesToRemove)
    % Remove module from the list
    moduleToRemove = modulesToRemove{i};
    matchedIdx = ~strcmp...
        (regexp(modules, ['^' moduleToRemove fileExtExpr], 'once', 'match'), ...
         '');
    keepIdx(matchedIdx) = false;
end

modulesUpdated = modules(keepIdx);
pathsUpdated = modulesValPaths(keepIdx);


function skipFiles = i_filter_rt_nonfinite...
    (lGenerateAllocFcn, lClassicInterface, lTMFProperties)

% Special processing for MODEL_MODULES token: must remove rt_nonfinite in cases
% where the make file handles it explicitly
if lTMFProperties.SkipRtNonFinite && (~lGenerateAllocFcn || lClassicInterface)
    % Remove the rt_nonfinite module
    skipFiles = {'rt_nonfinite'};
else
    skipFiles = {};
end

function skipFilesExtMode = i_filter_ext_mode(buildInfo)

skipFilesExtMode={};
extModePattern = 'EXT_MODE';

% If the EXT_MODE Build Arg does exist and is set to 1, we skip the
% files in the EXT_MODE group since they are handled by the TMF
[~,extModeArgEnabled] = findBuildArg(buildInfo, extModePattern);

if strcmp(extModeArgEnabled, '1')
    srcFiles = buildInfo.Src.Files;
    for idx=1:length(srcFiles)
        if  strcmp(srcFiles(idx).Group, extModePattern)
            [~,fname,~] = fileparts(srcFiles(idx).FileName);
            skipFilesExtMode{end+1} = fname; %#ok<AGROW>
        end
    end
end

function [modelModules, modelModulesObj, modulesVal, modelModulesPaths] = i_getModelModulesTokens...
    (buildInfo, toks, tokVals, lTMFProperties, ...
     lCompilingSharedLibTarget, compatibilitySkipModules, suppressExe)

% Get token corresponding to model parameter GenerateAllocFcn
lGenerateAllocFcn = i_getOnOffTokenAsLogical...
    (toks, tokVals, '|>GENERATE_ALLOC_FCN<|');

% Get token corresponding to model parameter GRTInterface
lClassicInterface = i_getOnOffTokenAsLogical...
    (toks, tokVals, '|>CLASSIC_INTERFACE<|');

% Special processing for MODEL_MODULES token, must remove certain files that
% handled explicitly by the make file
[modulesVal, modulesValPaths] = coder.make.internal.getTMFModules(buildInfo);

skipFilesExtMode = i_filter_ext_mode(buildInfo);

% Special case filtering out rt_nonfinite
skipFilesRtNonFinite = i_filter_rt_nonfinite...
    (lGenerateAllocFcn, lClassicInterface, lTMFProperties);

% Not applicable to Toolchain
oldTMFCompatibility = ~lTMFProperties.IsSimplifiedFormat;

skipModules = coder.make.internal.getSkipModules(buildInfo, ...
    lCompilingSharedLibTarget, suppressExe, oldTMFCompatibility);

% Remove all skip files from the modules list
allSkipFiles = [compatibilitySkipModules(:)
                skipFilesExtMode(:)
                skipFilesRtNonFinite(:)
                skipModules(:)];
[modulesVal, modulesValPaths] = i_removeModulesFromList(allSkipFiles, modulesVal, modulesValPaths);

% Convert from cell array to space separated character array
modelModules = strtrim(sprintf('%s ', modulesVal{:}));
if length(unique(modulesVal)) ~= length(modulesVal)
    msg = message('coder_compile:CoderCompile:duplicateSrcFilename',...
                   modelModules);
    error(msg.Identifier, string(msg));
end
modelModulesObj = coder.make.internal.tmfChangeFileExtSrcToObj(modelModules);
modelModulesPaths = modulesValPaths;


function tmfContents = i_legacyPortableWordSizesUpdate...
    (tmfContents, buildInfo, templateMakefile)

% Always set the value of this token to '0'
tmfContents = strrep(tmfContents, '|>PORTABLE_WORDSIZES<|', '0');

% Issue a warning if we can determine that there's a mismatch with
% setting of PortableWordSizes
[~, defineSymbols] = getDefines(buildInfo);
if any(strcmp(defineSymbols, 'PORTABLE_WORDSIZES'))
    % Issue a warning that there might be an inconsistency between in the
    % setting of portable word sizes
    msg = message('coder_compile:CoderCompile:TMFHasPortableWordSizes', templateMakefile);
    error(msg.Identifier, '%s', string(msg));
end


%==========================================================================
% TMF Approach
%==========================================================================
function [lOptCompileFlags, lDownload, lMakeCmd, lWrappedMakeCmd] = ...
    invokeTMF( ...
    buildInfo, lBuildVariant, lBuildLogger, ...
    lSystemTargetFile, lBuildHookEnvVars, ...
    lBuildHookPathPrepends, ...
    lSuppressExe, ...
    forceCompile, ...
    lMakeArgs, ...
    lTMFProperties, ...
    lModelRefLibPathFormat, ...
    lMakefileSelfDependency, ...
    mexToolChainName, ...
    lBuildConfiguration, ...
    lCustomToolchainOptions, ...
    lMexOpts, ...
    lBuildFolderFullPath, ...
    lMakefileName, ...
    finalTarget, ...
    outputDir, ...
    lMakefileDependencies, ...
    lUserSrcs, ...
    preCompTargets, ...
    lStaticLibSuffix)

templateMakefile = lTMFProperties.TemplateMakefile;

% ---------------------------------------
% Begin: TMF -> makefile conversion code
% ---------------------------------------

addToBuildLog(lBuildLogger, '.');
addToBuildLog(lBuildLogger, ...
           string(message('coder_compile:CoderCompile:processTMF',templateMakefile)))

% If matlabroot contains a space, then a drive will automatically be mapped, and
% environment vars set
mlrEnvOverride = coder.make.get_MLR_override_for_pc(buildInfo);

makeArgs = [mlrEnvOverride lMakeArgs];

% Obtain the TMF content
tmfContents = lTMFProperties.TMFContent;

[~, lRelativePathToAnchor] = ...
    findBuildArg(buildInfo, 'RELATIVE_PATH_TO_ANCHOR');

isExeBuild = any(lBuildVariant==...
    [coder.make.enum.BuildVariant.STANDALONE_EXECUTABLE
    coder.make.enum.BuildVariant.SHARED_LIBRARY_TARGET
    coder.make.enum.BuildVariant.RAPID_ACCELERATOR]);
    
% Identify model reference link objects, if needed by the makefile
if isExeBuild && ~isempty(buildInfo.ModelRefs)
    lLinkLibFullPathsRel = coder.make.internal.getModelRefLinkObjs(...
        lModelRefLibPathFormat, ...
        lRelativePathToAnchor, ...
        buildInfo.ModelRefs, ...
        buildInfo.Settings.LocalAnchorDir, ...
        lStaticLibSuffix);
else
    lLinkLibFullPathsRel = {};
end

if ~isempty(lTMFProperties.Toolchain)
    makeToolFileSep = lTMFProperties.Toolchain.BuilderApplication.getDirective('FileSeparator');
else
    makeToolFileSep = buildInfo.Settings.FileSep;
end
lLinkLibsFormattedForMkfile = coder.make.internal.formatModelRefLinkObjs(lLinkLibFullPathsRel, makeToolFileSep);

% Replace the MODELREF_LINK_LIBS token
lMODELREF_LINK_LIBS = strtrim(sprintf('%s ', lLinkLibsFormattedForMkfile{:}));
tmfContents = strrep(tmfContents, '|>MODELREF_LINK_LIBS<|', lMODELREF_LINK_LIBS);

% Some compilers (LCC) require specific expansion actions
compToolChain = lTMFProperties.COMPILER_TOOL_CHAIN;

% Replace obsolete tokens with blanks
obsoleteTokens = {'>ADD_MDL_NAME_TO_GLOBALS<'};

tmfContents = regexprep(tmfContents,obsoleteTokens,'');

% Handle makefile dependency
legacyProjFile = 'rtw_proj.tmw';
if ~any(strcmp(lMakefileDependencies, legacyProjFile))
    % Remove any reference to rtw_proj.tmw if is not needed as a dependency
    tmfContents = regexprep(tmfContents, ['(?<=\s)' legacyProjFile '(?=\s)'], '');
end

% these tokens need to be replaced early on, because they may be used in macros
% such as MAKECMD.  replace them now so the macros have the correct expanded
% value
% early replacement tokens are:
%    |>MATLAB_ROOT<|
%    |>ALT_MATLAB_ROOT<|
%    |>MATLAB_BIN<|
%    |>ALT_MATLAB_BIN<|
%    |>ARCH<|

% NOTE: the first dir listed in the 'special dirs' is always matlabroot
specialDirs = buildInfo.Settings.getSpecialDirsAndTokens('includeAltNames',...
                                                  true,...
                                                  'includeEmptyNames',...
                                                  true,...
                                                  'includeRegexpPats',...
                                                  true);

% always try to replace these tokens.  These need to go in genMakefileAndBuild
% because the MAKEFILE_FILESEP macro may have been set.
matroot    = specialDirs.regexpPats{1};
altMatroot = specialDirs.regexpAltNamePats{1};
matbin     = regexptranslate('escape', [specialDirs.dirs{1}...
                    buildInfo.Settings.FileSep 'bin']);
altMatbin  = regexptranslate('escape',...
                             [specialDirs.altNames{1}...
                    buildInfo.Settings.FileSep 'bin']);

specDirs = specialDirs.regexpPats;
spcIdx = contains(specDirs,' ');
specDirs(spcIdx) = specialDirs.regexpAltNamePats(spcIdx);

% this tmf may not have a start dir token.  This is usually due to a target that
% does not (yet) support parallel model reference builds.  We need to check for
% the token, then do a global replacement of the $(START_DIR) macro after all
% the expansion is done.
tmfHasStartDirToken = contains(tmfContents,'|>START_DIR<|');

% add these to the end of the list.
toks    = ['|>MATLAB_ROOT<|';...
           '|>ALT_MATLAB_ROOT<|';...
           '|>MATLAB_BIN<|';...
           '|>ALT_MATLAB_BIN<|';...
           '|>ARCH<|';...
           specialDirs.tmfToks(2:end)']; % first special dir is matlabroot

tokVals = [matroot;...
           altMatroot;...
           matbin;...
           altMatbin;...
           computer('arch');...
           specDirs(2:end)']; % first special dir is matlabroot

% Transform paths depending on the target platform
targetPlatformIsUnix = i_platformIsUnix(lTMFProperties);
if targetPlatformIsUnix   
    tokVals = coder.make.MakefileWriter.transformPathsForUnix(tokVals);
else  
    tokVals = coder.make.MakefileWriter.transformPathsForWin(tokVals); 
end

% The tokens have '|>' and '<|' in them, which is problematic for regexp, since
% | is used for logical OR. To fix this, escape the '|' characters.
toks = regexptranslate('escape',toks);
tmfContents = regexprep(tmfContents,toks,tokVals);


if ~isempty(lSystemTargetFile)
    % the tmf may be specific to a particular System target file
    tmfSysTargetFile = lTMFProperties.TMF_SYS_TARGET_FILE;

    if ~strcmp(tmfSysTargetFile, 'any')
        if ispc
            diffSTF = ~strcmpi(tmfSysTargetFile, lSystemTargetFile);
        else
            diffSTF = ~strcmp(tmfSysTargetFile, lSystemTargetFile);
        end

        if diffSTF
            DAStudio.error('RTW:buildProcess:differentSystemTargetFile',...
                           tmfSysTargetFile,templateMakefile,...
                           lSystemTargetFile);
        end
    end
end

if lTMFProperties.IsSimplifiedFormat && ~isempty(findBuildArg(buildInfo, 'EXT_MODE'))
    % We remove the EXT_MODE Build Arg to guard against cases where the
    % user has a simplified TMF but that still has the EXT_MODE section
    buildInfo.deleteKeyValuePair('BuildArg', 'EXT_MODE');
end

% Older template makefiles may expect a value for PORTABLE_WORDSIZES
if ~lTMFProperties.IsSimplifiedFormat && contains(tmfContents, '|>PORTABLE_WORDSIZES<|')
    tmfContents =  i_legacyPortableWordSizesUpdate...
        (tmfContents, buildInfo, templateMakefile);
end

bArgs      = get(buildInfo.BuildArgs,'Key');
bArgVals   = get(buildInfo.BuildArgs,'Value');
bArgGroups = get(buildInfo.BuildArgs,'Group');

% The build arg SHARED_SRC_DIR still exists, even though it is no longer needed by the generated
% makefile. It is still required by the cross-release feature. Therefore, remove SHARED_SRC_DIR
% from the list of build args that are propagated into the makefile. Build args related to 
% packNGo must not be passed to the make file.
argsToDiscard = {...
    'SHARED_SRC_DIR', ...
    'DEFAULT_TOOLCHAIN_FOR_PACKNGO', 'DEFAULT_BUILD_VARIANT_FOR_PACKNGO'};

% Discard build args that are not required
[~, discardIdx] = intersect(bArgs, argsToDiscard);
keepIdx = true(size(bArgs));
keepIdx(discardIdx) = false;
bArgs = bArgs(keepIdx);
bArgVals = bArgVals(keepIdx);
bArgGroups = bArgGroups(keepIdx);

isMexTarget = (lBuildVariant==coder.make.enum.BuildVariant.MEX_FILE);

if isMexTarget

    % Add dependency on LIBS because libraries must be built, g1823629
    expr =  '^\s*\$\(PRODUCT\)\s*:\s*'; 
    tmfContents = regexprep(tmfContents, expr, '$0\$(LIBS) ', ...
                            'lineanchors');         
end

% Identify TMF tokens to be replaced
toksToReplaceExprs = "\|>" + bArgs + "<\|";
toksInTMFContentIdx = regexp(tmfContents, toksToReplaceExprs, 'once');
toksInTMFContentIdx = ~cellfun(@isempty, toksInTMFContentIdx);
toksInTMFContentIdx = toksInTMFContentIdx & ~strcmp(bArgVals, 'MakeArg');

% Identify corresponding replacement values; remove double-quotes because make utilities
% handle values with spaces without need for double quotes
replacementValues = strrep(bArgVals, '"', '');

% Escape special characters in the replacement text
replacementValues = regexptranslate('escape', replacementValues);

% Replace tokens in the makefile content
tmfContents = regexprep(tmfContents, toksToReplaceExprs, replacementValues);

idx = ~toksInTMFContentIdx & ...
    (~cellfun(@isempty, bArgVals) | strcmp(bArgGroups,'BUILD_TARGET') | ...
    strcmp(bArgGroups,'MakeArg'));

nbtIdx = idx & ~strcmp(bArgGroups,'BUILD_TARGET');

% Update the non-build target entries to have '=' and the value appended.
mArgList = bArgs;
mArgList(nbtIdx) = strcat(bArgs(nbtIdx),'=',bArgVals(nbtIdx));
mArgList = mArgList(idx);

field_list = '';
if ~isempty(mArgList)
    % pre-pend each value with a ' '.  note that strcat doesn't work with this
    fields = regexprep(mArgList,'(.*)',' $1');
    if ~isempty(fields)
        field_list = strcat(fields{:});
    end
end

% For an rtw_precompile_libs build, ensure that the build targets remain at the
% end of the build line. This is because for some make utilities, once a build
% target is seen on the make line, all subsequent targets are treated as build
% targets. The makeArgs will have 'PRECOMP_LIB_BUILD=1', if this is a
% pre-compile build.
makeArgs = [makeArgs field_list preCompTargets];

if contains(tmfContents, '|>ISPROTECTINGMODEL<|')
   % Remove and warn about legacy ISPROTECTINGMODEL token
   tmfContents = i_removeAndWarnLegacyContent(tmfContents, templateMakefile, ...
       '|>ISPROTECTINGMODEL<|', '\|>ISPROTECTINGMODEL<\|');
end

%------------------------------------------------------------%
% Parse BUILD and DOWNLOAD options from makeArgs if present %
%------------------------------------------------------------%
[lDownload,makeArgs] = i_getDownload(makeArgs, lTMFProperties, ...
    toksToReplaceExprs, replacementValues);

% add the OPTS values in the on the build line
defs   = buildInfo.getDefines('OPTS');
cflags = buildInfo.getCompileFlags('OPTS');
lflags = buildInfo.getLinkFlags('OPTS');
if ~isempty(defs) || ~isempty(cflags) || ~isempty(lflags)
    defs = regexprep(defs,'(.*)',' $1');
    cflags = regexprep(cflags,'(.*)',' $1');
    lflags = regexprep(lflags,'(.*)',' $1');
    opts = strcat(defs{:}, cflags{:}, lflags{:});
    % remove the leading space
    opts = opts(2:end);

    % if OPTS is already on the make line, then this should be added to the
    % existing opts.  otherwise it should just be added.  Note that (?>!_)
    % is used to only find OPTS and not OPT_OPTS
    if (~isempty(regexp(makeArgs,'(?<!_)OPTS=(("[^"]*")|(\S*))','once')))
        if ~isempty(regexp(makeArgs,'"OPTS=([^"]*)"','once'))
            % the opts could already exist in the form "OPTS=..." where the
            % whole OPTS declaration is quoted.  In this case we just need to
            % add the OPTS before the quote.
            makeArgs = regexprep(makeArgs, '("OPTS=)[^"]*"',['$1' opts '"']);
        else
            % the regexp pattern has 2 groups ($1 and $2), the first is
            % OPTS="[^"], which is everything up to the closing '"',
            % and the second group is the closing '"'.  The additional
            % opts are stuffed in between them.
            makeArgs = regexprep(makeArgs,'((?<!_)OPTS=")([^"]*)(")',...
                                 ['$1' opts '$3']);
            makeArgs = regexprep(makeArgs,'((?<!_)OPTS=)((\s)|([^"]\S*))',...
                                 ['$1"' opts '" ']);
        
        end
    else    
        opts = [' OPTS="', opts, '"'];
        makeArgs = [makeArgs, opts];
    end
end

if ~isempty(lTMFProperties.Toolchain) && strcmp(lTMFProperties.Toolchain.Alias, 'Apple-x')
    % g1964665 For clang we need to specify -mmacosx-version-min. Libraries 
    % that ship with MATLAB are compiled with this flag set to 
    % a non default value. To prevent warnings when linking against these
    % libraries, e.g. when building in accelerator mode, we must compile with
    % this flag set to the same version.
    verMinFlag = coder.make.internal.getMacOSXVersionMinFlag();
    buildArg = [' MACOSX_VERSION_MIN_FLAG="' verMinFlag '"'];
    makeArgs = [makeArgs buildArg];
end

% now add the OPT_OPTS values in the on the build line
defs = buildInfo.getDefines('OPT_OPTS');
cflags = buildInfo.getCompileFlags('OPT_OPTS');
lflags = buildInfo.getLinkFlags('OPT_OPTS');
opt_opts = '';
if ~isempty(defs) || ~isempty(cflags) || ~isempty(lflags)
    defs = regexprep(defs,'(.*)',' $1');
    cflags = regexprep(cflags,'(.*)',' $1');
    lflags = regexprep(lflags,'(.*)',' $1');
    opt_opts = strcat(defs{:}, cflags{:}, lflags{:});
    % remove the leading space
    opt_opts = opt_opts(2:end);

    % if OPT_OPTS is already on the make line, then this should be added to the
    % existing opts.  otherwise it should just be added.
    if (~isempty(regexp(makeArgs,'OPT_OPTS=(("[^"]*")|(\S*))','once')))
        if ~isempty(regexp(makeArgs,'"OPT_OPTS=([^"]*)"','once'))
            % the opt_opts could already exist in the form "OPT_OPTS=..."
            % where the whole OPTS declaration is quoted.  In this case we
            % just need to add the OPT_OPTS before the quote.
            makeArgs = regexprep(makeArgs, '("OPT_OPTS=)[^"]*"',...
                                 ['$1' opt_opts '"']);
        else
            % the regexp pattern has 2 groups ($1 and $2), the first is
            % OPT_OPTS="[^"], which is everything up to the closing '"',
            % and the second group is the closing '"'.  The additional
            % opt_opts are stuffed in between them.
            makeArgs = regexprep(makeArgs,'(OPT_OPTS=")([^"]*)(")',...
                                 ['$1' opt_opts '$3']);
            makeArgs = regexprep(makeArgs,'(OPT_OPTS=)((\s)|([^"]\S*))',...
                                 ['$1"' opt_opts '" ']);
        end
    else    
        opt_opts = [' OPT_OPTS="', opt_opts, '"'];
        makeArgs = [makeArgs, opt_opts];
    end
end

tmfContents = strrep(tmfContents, '|>BUILDARGS<|',         makeArgs);

% buildInfo defines not in the standard groups are assigned to the token
% DEFINES_OTHER
[defsOtherMacrodefs, defsOtherIdentifiers] = buildInfo.getDefines({},...
    {'Custom', 'OPTS', 'OPT_OPTS', 'OPTIMIZATION_FLAGS', 'Build Args'});

% To avoid redundancy, if a definition has a corresponding variable/token
% pair in the TMF, do not supply it through DEFINES_OTHER
% This is because most of the variables/tokens are used to supply a macro
% definition
% For ex., multiple TMFs contain
% NUMST = |>NUMST<|
% ...
% CPP_REQ_DEFINES1 = ... -DNUMST=$(NUMST) ... 

defsWithTMFVariableIdces = i_propIsHardcodedInTMF(defsOtherIdentifiers, lTMFProperties);
defsWithNoTMFVariableIdces = ~defsWithTMFVariableIdces;
defsOther = defsOtherMacrodefs(defsWithNoTMFVariableIdces);

if  contains(tmfContents,'|>DEFINES_OTHER<|')
    defsOther = strjoin(defsOther, ' ');
    tmfContents = regexprep(tmfContents,...
                            regexptranslate('escape', '|>DEFINES_OTHER<|'),...
                            regexptranslate('escape', defsOther));
else
    if any(defsWithNoTMFVariableIdces)
        % Warn if the TMF does not contain the identifiers corresponding to
        % the defines
        % See g2290725
        i_warnNoTMFTokenForDefines(defsOtherIdentifiers(defsWithNoTMFVariableIdces), lTMFProperties.TemplateMakefile,...
            tmfContents, '|>DEFINES_OTHER<|');
    end
end

% buildInfo compile flags not in the standard groups are assigned to the token
% COMPILE_FLAGS_OTHER
compileFlagsOther = buildInfo.getCompileFlags({},...
                    {'OPTS', 'OPT_OPTS', 'OPTIMIZATION_FLAGS', 'Build Args'});
if  contains(tmfContents,'|>COMPILE_FLAGS_OTHER<|')
    compileFlagsOther = strjoin(compileFlagsOther, ' ');
    tmfContents = regexprep(tmfContents,...
                            regexptranslate('escape', '|>COMPILE_FLAGS_OTHER<|'),...
                            regexptranslate('escape', compileFlagsOther));
else
    i_warnNoTMFTokenForFlags(compileFlagsOther, lTMFProperties.TemplateMakefile,...
                             tmfContents, '|>COMPILE_FLAGS_OTHER<|');
end
%The optimization flags are set 
oflags = '';
defs   = buildInfo.getDefines('OPTIMIZATION_FLAGS');
cflags = buildInfo.getCompileFlags('OPTIMIZATION_FLAGS');

% Add in optimization flags obtained via build configuration
lOptCompileFlags = i_getCompilerOptimizationFlags...
    (lTMFProperties, mexToolChainName, lBuildConfiguration, lCustomToolchainOptions);

cflags = [cflags lOptCompileFlags];

lflags = buildInfo.getLinkFlags('OPTIMIZATION_FLAGS');
if ~isempty(defs) || ~isempty(cflags) || ~isempty(lflags)
    defs = regexprep(defs,'(.*)',' $1');
    cflags = regexprep(cflags,'(.*)',' $1');
    lflags = regexprep(lflags,'(.*)',' $1');
    ignoreOptFlags = false;

    % If opt_opts was specified then a warning should be issued
    if ~isempty(opt_opts)
        ignoreOptFlags = true;
        DAStudio.warning('RTW:buildProcess:obsoleteOptSpec','OPT_OPTS');
    elseif contains(makeArgs,'MEX_OPTS')
        if isempty(regexp(makeArgs,'MEX_OPTS\s*=\s*"\s*-v\s*"','once'))
            ignoreOptFlags = true;
            DAStudio.warning('RTW:buildProcess:obsoleteOptSpec','MEX_OPTS');
        end
    elseif contains(makeArgs,'MEX_OPT_FILE')
        ignoreOptFlags = true;
        DAStudio.warning('RTW:buildProcess:obsoleteOptSpec','MEX_OPT_FILE');
    end
    if ~ignoreOptFlags
        oflags = strcat(defs{:}, cflags{:}, lflags{:});
        % remove the leading space
        oflags = oflags(2:end);
    end
    
end

tmfContents = strrep(tmfContents, '|>OPTIMIZATION_FLAGS<|',  oflags);

% put any link flags into additional link flags
additional_ldflags = '';
ldflags = getLinkFlags(buildInfo);
if ~isempty(ldflags)
    ldflags = regexprep(ldflags,'(.*)',' $1');
    additional_ldflags = strcat(ldflags{:});
end

tmfContents = strrep(tmfContents, '|>ADDITIONAL_LDFLAGS<|',additional_ldflags);

% For host-based compilers, we need to specify system libraries and paths
sysLibCompilerFlags = '';
if ~isempty(mexToolChainName)
    [sysLibs, sysLibPaths] = coder.make.internal.getFormattedSysLibInfo...
    (buildInfo, mexToolChainName);
    sysLibCompilerFlags = char(join(string([sysLibPaths sysLibs]), ' '));
end
tmfContents = strrep(tmfContents, '|>SYSTEM_LIBS<|', sysLibCompilerFlags);
 

%-----------------------------------------------------------------------------
% For all directory replacement below, replace matroot, and its alternate
% version with expandable make variables.
%-----------------------------------------------------------------------------
alt_matroot_different = ~strcmp(specialDirs.dirs{1},specialDirs.altNames{1});

%-----------------------------------------------------------------------------
% Write out the include folders
%-----------------------------------------------------------------------------

% Model reference include paths
incDirsMdlRef = getIncludePaths(buildInfo, false, {'MDLREF'});
 
% Other include paths
incDirsOther = getIncludePaths(buildInfo, false, {}, {'MDLREF'});

% Copy referenced model header files, if required
if lTMFProperties.RequiresCopyingHeaders
    headerExts = coder.make.internal.getHeaderFileExtensions(lTMFProperties.Toolchain);
    incDirsMdlRef = coder.make.internal.copyModelReferenceHeaders...
        (buildInfo.Settings.LocalAnchorDir, ...
         lRelativePathToAnchor, incDirsMdlRef, headerExts);
end

% Combined include paths
incDirs = [incDirsMdlRef incDirsOther];

% Update filesep
incDirs = regexprep(incDirs, regexptranslate('escape', filesep), ...
                    regexptranslate('escape', buildInfo.Settings.FileSep));

% Response file names
[responseFileName, linkObjectsCommandFileName, compilerCommandFileName] = ...
    coder.make.getResponseFileName(buildInfo.ComponentName);

% Write out include folders

% LCC supports linker command file but not compiler command file
lUseCompilerCommandFile = lTMFProperties.UseCompilerCommandFile;

if lUseCompilerCommandFile
    
    % Make variables must be expanded before emitting paths into the
    % command file
    incDirs = strrep(incDirs, '$(START_DIR)', ...
        buildInfo.Settings.LocalAnchorDir);
    incDirs = strrep(incDirs, '$(MATLAB_ROOT)', matlabroot);

    if coder.make.internal.pathsMustBeQuotedBasedOnFileSep(buildInfo.Settings.FileSep)
        lHandleSpacesInPath = coder.make.internal.PathHandleSpaces.WrapWithQuotes;
    else
        lHandleSpacesInPath = coder.make.internal.PathHandleSpaces.EscapeWithBackslash;
    end   

    % Handle spaces in include paths
    incDirs = coder.make.MakefileWriter.handleSpacesInPaths(incDirs, lHandleSpacesInPath);        
    
    prefix = '-I';
    coder.make.internal.createResponseFile(incDirs, compilerCommandFileName, prefix);
    
    lCompilerCommandFileNameToken = coder.make.internal.TMFProps.CompilerCommandFileNameToken;
    tmfContents = strrep(tmfContents, lCompilerCommandFileNameToken, ...
                         compilerCommandFileName);
    tmfContents = ExpandRule(tmfContents, {},    ...
                             '|>START_EXPAND_INCLUDES<|', ...
                             '|>END_EXPAND_INCLUDES<|',   ...
                             '|>EXPAND_DIR_NAME<|',...
                             specialDirs, lTMFProperties);
else
    tmfContents = ExpandRule(tmfContents, incDirs,    ...
                             '|>START_EXPAND_INCLUDES<|', ...
                             '|>END_EXPAND_INCLUDES<|',   ...
                             '|>EXPAND_DIR_NAME<|',...
                             specialDirs, lTMFProperties);
end

if contains(tmfContents, '|>START_MDLREFINC_EXPAND_INCLUDES<|')
    % Legacy tokens for model reference include paths must be removed
    tmfContents = i_removeAndWarnLegacyContent...
        (tmfContents, templateMakefile, ...
         {'|>START_MDLREFINC_EXPAND_INCLUDES<|', ...
          '|>END_MDLREFINC_EXPAND_INCLUDES<|',   ...
          '|>MODELREF_INC_PATH<|'}, ...
         '\|>START_MDLREFINC_EXPAND_INCLUDES.*?(END_MDLREFINC_EXPAND_INCLUDES<\|)');
end

if contains(tmfContents, '|>MASTER_ANCHOR_DIR<|')
   % Remove and warn about legacy MASTER_ANCHOR_DIR token
   tmfContents = i_removeAndWarnLegacyContent(tmfContents, templateMakefile, ...
       '|>MASTER_ANCHOR_DIR<|', '\|>MASTER_ANCHOR_DIR<\|');
end

%-----------------------------------------------------------------------------
% Get the sfcn modules, libraries, and object code; includes custom code.
%-----------------------------------------------------------------------------

libraryGroupsToSkip = {};

% Retrieve groups of library from buildInfo that must be inserted into the makefile
if isExeBuild
    % Need libraries for building the "NONE" target
    [sfcnLinkObjs, precompLinkObjs, nonLinkOnlyLinkObjs] = ...
        coder.make.internal.getLibTypes(buildInfo, libraryGroupsToSkip);
elseif lBuildVariant==coder.make.enum.BuildVariant.MEX_FILE
    sfcnLinkObjs = [];
    precompLinkObjs = [];
    % Since not creating a final executable, the makefile only
    % needs libraries that are not already built. Improved approach will be to create a
    % separate makefile for the non-link-only library, see g1874616.
    [~, ~, nonLinkOnlyLinkObjs] = ...
        coder.make.internal.getLibTypes(buildInfo, libraryGroupsToSkip);
else
    sfcnLinkObjs = [];
    precompLinkObjs = [];
    nonLinkOnlyLinkObjs = [];
end

% Construct a space separated list of libraries to be inserted as S_FUNCTIONS_LIB 
if ~isempty(sfcnLinkObjs)
    sfcnList = i_getSfcnList(buildInfo, sfcnLinkObjs, specialDirs, ...
        lStaticLibSuffix);
    % if this is lcc, then force the windows style filesep
    if strcmp(compToolChain,'lcc')
        sfcnList = strrep(sfcnList, '/','\');
    end
else
    sfcnList = '';
end
tmfContents = strrep(tmfContents,'|>S_FUNCTIONS_LIB<|',   sfcnList);

if contains(tmfContents, '|>S_FUNCTIONS<|')
    % Since g1835874 the S_FUNCTIONS and S_FUNCTIONS_OBJ token are removed. 
    % S_FUNCTIONS sources are handled similarly to the rest of MODULES
    tmfContents = i_removeAndWarnLegacyContent...
        (tmfContents, templateMakefile, ...
         {'|>S_FUNCTIONS<|', '|>S_FUNCTIONS_OBJ<|'},...
         '\|>S_FUNCTIONS<\||\|>S_FUNCTIONS_OBJ<\|');
end

nonLinkOnlyLibModules = {};
% Each of the library's source files (modules) must be in a cell array,
% and all of those cell arrays must be in a cell array
for i=length(nonLinkOnlyLinkObjs):-1:1
    if ~isempty(nonLinkOnlyLinkObjs(i).Src.Files)
        libModules = {nonLinkOnlyLinkObjs(i).Src.Files.FileName};
        if ~isempty(libModules)
            if ~iscell(libModules)
                libModules = {libModules};
            end
        % strip off any extension, if it exists
        nonLinkOnlyLibModules{i} = regexprep(libModules,'(.*)\.[^.]*$','$1');
        end
    end
end

precompLibModules = {};
% Each of the libs source files (modules) must be in a cell array,
% and all of those cell arrays must be in a cell array
for i=length(precompLinkObjs):-1:1
    if isempty(precompLinkObjs(i).Src.Files)
        continue
    end
    libModules = {precompLinkObjs(i).Src.Files.FileName};
    if ~isempty(libModules)
        if ~iscell(libModules)
            libModules = {libModules};
        end
        % strip off any extension, if it exists
        precompLibModules{i} = regexprep(libModules,'(.*)\.[^.]*$','$1');
    end
end

% Get pre-compiled library names and paths
[precompLibNames, precompLocs] = i_getLibNamesAndPaths(precompLinkObjs);

% Get non-link-only library names and paths
[nonLinkOnlyLibNames, nonLinkOnlyLibLocs] = i_getLibNamesAndPaths(nonLinkOnlyLinkObjs);


if (alt_matroot_different)
    nonLinkOnlyLibLocs = strrep(nonLinkOnlyLibLocs,...
                           specialDirs.altNames{1},...
                           specialDirs.toks{1});
    precompLocs   = strrep(precompLocs,...
                           specialDirs.altNames{1},...
                           specialDirs.toks{1});
end


tmfContents = ExpandNestedRule(tmfContents, nonLinkOnlyLibNames, nonLinkOnlyLibLocs, ...
                               buildInfo.TargetLibSuffix, ...
                               '|>START_EXPAND_LIBRARIES<|',   ...
                               '|>END_EXPAND_LIBRARIES<|',     ...
                               '|>EXPAND_LIBRARY_NAME<|',      ...
                               '|>EXPAND_LIBRARY_LOCATION<|',  ...
                               '|>EXPAND_LIBRARY_SUFFIX<|',  ...
                               nonLinkOnlyLibModules,                  ...
                               '|>START_EXPAND_MODULES<|',     ...
                               '|>END_EXPAND_MODULES<|',       ...
                               '|>EXPAND_MODULE_NAME<|',...
                               specialDirs, lTMFProperties);

tmfContents = ExpandNestedRule(tmfContents, precompLibNames, precompLocs, ...
                               buildInfo.TargetLibSuffix, ...
                               '|>START_PRECOMP_LIBRARIES<|', ...
                               '|>END_PRECOMP_LIBRARIES<|',   ...
                               '|>EXPAND_LIBRARY_NAME<|',     ...
                               '|>EXPAND_LIBRARY_LOCATION<|', ...
                               '|>EXPAND_LIBRARY_SUFFIX<|',  ...
                               precompLibModules,               ...
                               '|>START_EXPAND_MODULES<|',    ...
                               '|>END_EXPAND_MODULES<|',      ...
                               '|>EXPAND_MODULE_NAME<|',...
                               specialDirs, lTMFProperties);

if contains(tmfContents, '|>START_EXPAND_MODELREF_SIM_EXTRA_LIBS<|')
    % Legacy tokens for model reference SIM target libraries must be removed
    % to avoid compile error
    tmfContents = i_removeAndWarnLegacyContent...
        (tmfContents, templateMakefile, ...
         {'|>START_EXPAND_MODELREF_SIM_EXTRA_LIBS<|', ...
          '|>END_EXPAND_MODELREF_SIM_EXTRA_LIBS<|',   ...
          '|>EXPAND_MODELREF_SIM_EXTRA_LIB<|'}, ...
         ['^\s*\|>START_EXPAND_MODELREF_SIM_EXTRA_LIBS'...
          '.*?(END_EXPAND_MODELREF_SIM_EXTRA_LIBS<\|)\s*^']);
end

%--------------------------------------------------------------------%
% Setup makeCmd (we do this here because on PC we generate model.bat %
%--------------------------------------------------------------------%
makeApp = getMAKECMD(lTMFProperties);
lMakeCmd = [makeApp ' -f ' lMakefileName, ' ',makeArgs];

% Replace tokens in the make command
lMakeCmd = regexprep(lMakeCmd, toksToReplaceExprs, replacementValues);

%------------------------------------------------------------------------------
%
% Some platforms and/or targets can't just execute the raw make command. They
% may need to wrap it inside of a generated script.  E.g. on Windows we
% typically wrap the raw make command in a batch (.bat) file that sets up some
% environment variables needed during the build process. E.g. wrap_make_cmd()
% takes 'nmake -f model.mk MAT_FILE=1' as args.makeCmd, creates model.bat and
% returns 'model.bat'. UNIX typically does not need to wrap the make command so
% it comes back untouched.
%
%------------------------------------------------------------------------------
%
aWrapMakeCmdArgs.make           = makeApp; % e.g. 'nmake'
aWrapMakeCmdArgs.makeCmd        = lMakeCmd; %e.g. 'nmake -f model.mk MAT_FILE=1'
aWrapMakeCmdArgs.modelName      = buildInfo.ComponentName;
aWrapMakeCmdArgs.verbose        = isVerboseBuild(lBuildLogger);
aWrapMakeCmdArgs.sysTargetFile  = lSystemTargetFile; % e.g. 'ert.tlc'
aWrapMakeCmdArgs.TemplateMakefile    = templateMakefile;
aWrapMakeCmdArgs.mexOpts             = lMexOpts;
aWrapMakeCmdArgs.compToolChain       = compToolChain;
aWrapMakeCmdArgs.buildInfo           = buildInfo;
aWrapMakeCmdArgs.aBuildHookEnvVars = lBuildHookEnvVars;
aWrapMakeCmdArgs.aBuildHookPathPrepends = lBuildHookPathPrepends;
lWrappedMakeCmd = coder.make.internal.wrap_make_cmd(aWrapMakeCmdArgs);

% Prepare MEX_OPT_FILE token for PC platform
if  ispc && contains(tmfContents, '|>MEX_OPT_FILE<|')
    % setup_for_compiler will do the work to decide which of the
    % InstallDirmexopts to use
    mex_opt_file = buildInfo.InstallDirmexopts;
    % make sure the mex_opt_file path is using the correct filesep
    mex_opt_file = regexprep(mex_opt_file,'[\\/]', buildInfo.Settings.FileSep);
    tmfContents = strrep(tmfContents,...
                         '|>MEX_OPT_FILE<|',...
                         ['-f ' mex_opt_file]);
end

% get tokens from buildInfo
[toks, tokVals] = i_getTokensFromBuildInfo(buildInfo, lMakefileSelfDependency);

% For backwards compatibility, skip the ert_main or classic_main file if
% the .tmf file explicitly references it
if ~lTMFProperties.IsSimplifiedFormat
    compatibilitySkipModules = ...
        regexp(tmfContents, [...
        '(rt_cppclass_main(?=\.))|'...
        '(rt_malloc_main(?=\.))|'...
        '(ert_main(?=\.))|'...
        '(rt_main(?=\.))|'...
        '(rt_sim(?=\.))|'...
        '(classic_main(?=\.))'], ...
        'match');
else
    compatibilitySkipModules = {};
end

lCompilingSharedLibTarget = ( lBuildVariant == coder.make.enum.BuildVariant.SHARED_LIBRARY_TARGET );
% model modules token
[modelModules, modelModulesObj, modelModulesCellArray, modelModulesPaths] = i_getModelModulesTokens...
    (buildInfo, toks, tokVals, lTMFProperties, ...
     lCompilingSharedLibTarget, compatibilitySkipModules,...
     lSuppressExe);
toks(end+1:end+2) = {'|>MODEL_MODULES<|', '|>MODEL_MODULES_OBJ<|'};
tokVals(end+1:end+2) = {modelModules, modelModulesObj};

% For a mex target (e.g. accelerator), it may happen that there are no
% source files to be compiled for the main static library. A future
% optimization (g1761742) will be to skip generating and running
% the makefile when there are no source files.
if isempty(modelModules) && isempty(lUserSrcs)
    % Remove any rule for $(OBJS), for example:
    %     $(OBJS) : $(MAKEFILE) rtw_proj.tmw
    % this is necessary OBJS is empty and, in particular, for nmake it
    % avoids an error.
    tmfContents = regexprep(tmfContents, '\$\(OBJS\)\s*:.*?$', ...
                            '', 'lineanchors', 'once');

    % Remove all command lines including $(MODELLIB); 
    % MODELLIB is not needed for accelerator and rtwsfcn builds and for 
    % lcc and clang there is an error if we try to create it when OBJS are
    % empty
    tabChar = char(9);
    expr =  ['^' tabChar '.*\(MODELLIB\).*$'];
    tmfContents = regexprep(tmfContents, expr, '', ...
                            'lineanchors', 'dotexceptnewline');    
end

buildingMakefileBuilderTgt = i_isBuildingMakefileBuilder(buildInfo);

if buildingMakefileBuilderTgt || lSuppressExe
    i_checkTMFContainsToken(tmfContents, buildingMakefileBuilderTgt, lSuppressExe);
end
if strcmp(outputDir, '.')
    binaryArtifact = finalTarget;
else
    binaryArtifact = [outputDir buildInfo.Settings.FileSep finalTarget];
end
toks{end+1} = '|>PRODUCT<|';
tokVals{end+1} = binaryArtifact;

% Set variable SHRLIBTARGET ot '1' if building a SHARED_LIBRARY or SHARED_LIBRARY_TARGET
toks{end+1} = '|>SHRLIBTARGET<|';
tokVals{end+1} = num2str(locIsCompilingSharedLibrary(lBuildVariant));

% Build folder file separator must be transformed if make tool file separator
% does not match file separator for current platform
if ~strcmp(filesep, buildInfo.Settings.FileSep)
    lBuildFolderFullPathTransformed = strrep(lBuildFolderFullPath, filesep, ...
        buildInfo.Settings.FileSep);
else
    lBuildFolderFullPathTransformed = lBuildFolderFullPath;
end
    

% Determine if BuildDir is the folder where the makefile will be generated
% Folder where the makefile will be created
buildDir = getSourcePaths(buildInfo, false, 'BuildDir');
buildDir = regexprep(buildDir, '\$\(START_DIR\)', ...
    regexptranslate('escape', buildInfo.Settings.LocalAnchorDir));
makefileInBuildFolder = any(strcmp(buildDir, lBuildFolderFullPathTransformed));

% Expand out the source folders
if makefileInBuildFolder
    % Skip BuildDir only if make file will be generated in BuildDir, i.e. the
    % hard-coded rule for files in the current folder will take effect
    srcDirs = getSourcePaths(buildInfo, false, {}, {'StartDir', 'BuildDir'});
else
    % Always skip StartDir because the TMF has a hard-coded rule
    srcDirs = getSourcePaths(buildInfo, false, {}, 'StartDir');
end

% Finally, replace the altmatroot with the make variables, if it is different from matlabroot.
if (alt_matroot_different)
    srcDirs = strrep(srcDirs, specialDirs.altNames{1}, specialDirs.toks{1});
end

% for some codegen such as AUTOSAR, a subdir is created in the build dir for
% some sources.  If there is a space in the build dir this can cause problems
% with the makefile rules.  These paths need to be changed to a dir relative to
% the build dir.
fullBDir = coder.make.internal.transformPaths(buildInfo.getSourcePaths(true,'BuildDir'),...
                              'pathType','full');
% eml can add multiple "buildDir" entries.  Assume the first one is the master
% buildDir.
if (length(fullBDir) > 1)
    fullBDir = fullBDir(1);
end

if ispc
    useRelSubDirs = false;
else    
    useRelSubDirs = (~isempty(fullBDir) && ...
                     ~isempty(strfind(fullBDir{1}, ' ')));
end


if useRelSubDirs    
    srcDirs = buildInfo.formatPaths(srcDirs, 'replaceStartDirWithRelativePath', true);
end

srcFilesWithPaths = i_processSrcModules(buildInfo, lBuildFolderFullPathTransformed, ...
    buildDir, modelModulesPaths, modelModulesCellArray);

if coder.make.internal.pathsMustBeQuotedBasedOnFileSep(buildInfo.Settings.FileSep)
    lHandleSpacesInPath = coder.make.internal.PathHandleSpaces.WrapWithQuotes;
else
    lHandleSpacesInPath = coder.make.internal.PathHandleSpaces.EscapeWithBackslash;
end        

tmfContents = ExpandRuleWithSourceAndPath(tmfContents, srcDirs, srcFilesWithPaths, ...
                                          '|>START_EXPAND_RULES<|', ...
                                          '|>END_EXPAND_RULES<|',   ...
                                          '|>EXPAND_DIR_NAME<|',...
                                          specialDirs, ...
                                          buildInfo.Settings.FileSep,...
                                          lHandleSpacesInPath,...
                                          lTMFProperties);

% Generate linker command file with list of object files
if lTMFProperties.RequiresCommandFile
    
    if ~isempty(lUserSrcs)
        [~, f, e] = coder.make.internal.fileparts(lUserSrcs, buildInfo.Settings.FileSep);
        lUserSrcNames = strcat(f, e);
        sourceModules = [lUserSrcNames(:); modelModulesCellArray(:)];
    else
        sourceModules = modelModulesCellArray(:);
    end
    
    % Check that source code file extensions are be registered with the ToolchainInfo
    checkSourceFileExtensions = true;
    
    objectCodeFiles = coder.make.internal.replaceFileExtensions...
        (sourceModules, lTMFProperties.Toolchain, checkSourceFileExtensions);
    
    % Object files command file
    prefix = '';
    coder.make.internal.createResponseFile(objectCodeFiles, responseFileName, prefix);
end

% Generate linker command file with list of model reference library archives
lModelRefRSPFileToken = '|>MODELREF_LINK_RSPFILE_NAME<|';
if lTMFProperties.RequiresCommandFile || ...
        contains(tmfContents, lModelRefRSPFileToken)
        
    % Link libraries command file
    if ~isempty(lTMFProperties.Toolchain)
        linkTool = lTMFProperties.Toolchain.getBuildTool('Linker');
        linkObjFileSep = getDirective(linkTool, 'FileSeparator');
    else
        linkObjFileSep = buildInfo.Settings.FileSep;
    end
    lLinkLibsFormattedForRspfile = coder.make.internal.formatModelRefLinkObjs(lLinkLibFullPathsRel, linkObjFileSep);
    prefix = '';
    coder.make.internal.createResponseFile...
        (lLinkLibsFormattedForRspfile, linkObjectsCommandFileName, prefix);
    
    % Replace the MODELREF_LINK_RSPFILE_NAME token
    tmfContents = strrep(tmfContents, '|>MODELREF_LINK_RSPFILE_NAME<|', ...
                         linkObjectsCommandFileName);
    
end

% If the template makefile has token DEFINES_CUSTOM then buildInfo defines
% in the 'Custom' group are supplied via this variable
custDefsTok = {'|>DEFINES_CUSTOM<|'}; 
custDefsVal = buildInfo.getDefines...
    (coder.make.internal.BuildInfoGroup.DefinesCustomGroup);

if ~isempty(custDefsVal)
    formattedDefs = {sprintf('%s ',custDefsVal{:})};
else
    formattedDefs = {''};
end
    
toks = [toks(:); custDefsTok(:)];
tokVals = [tokVals(:); formattedDefs(:)];

if ~isempty(toks)
    % The tokens have '|>' and '<|' in them, which is problematic for regexp, since
    % | is used for logical OR. To fix this, escape the '|' characters.
    toks = regexprep(toks,'(\|)','\\|');
    % if the paths have any '\' in them, then we need to escape them
    tokVals = regexprep(tokVals,'\\','\\\\');
    tmfContents = regexprep(tmfContents,toks,tokVals);
end

if ~tmfHasStartDirToken
    % the tmf doesn't have the start dir token.  just replace the make macro
    % with the hard-coded string.
    
    hasSpace = coder.make.MakefileWriter.containsSpaces(buildInfo.Settings.LocalAnchorDir);

    if ~hasSpace
        startDir = buildInfo.Settings.LocalAnchorDir;
    else
        startDir = coder.make.internal.transformPaths(buildInfo.Settings.LocalAnchorDir);
    end
    tmfContents = strrep(tmfContents,'$(START_DIR)', startDir);

end

remainingTokens = regexp(tmfContents, '(?<==\s*)\|>\w*<\|', 'match');

% For some tokens, there is a default value
[defaultNames, defaultValues, remainingTokens] = ...
    coder.make.internal.defaultTMFTokens(remainingTokens);
for i=1:length(defaultNames)
    tmfContents = strrep(tmfContents, defaultNames{i}, defaultValues{i});
end

% Handle legacy shared source tokens
if ~isempty(remainingTokens)
    [tmfContents, remainingTokens] = i_handleLegacySharedSrcTokens...
        (buildInfo, tmfContents, templateMakefile, remainingTokens, ...
        lStaticLibSuffix);
end

if ~isempty(remainingTokens)
    % Handle any legacy, or otherwise unprocessed, TMF tokens
    tmfContents = i_handleUnusedTMFTokens...
        (remainingTokens, tmfContents, templateMakefile);
end

%==========================================================================
% end tmf -> makefile conversion code
%==========================================================================

%---------------------------------------%
% Write new model.mk makefile if needed %
%---------------------------------------%

lMakefileNameFullPath = fullfile(lBuildFolderFullPath, lMakefileName);
if ((~forceCompile) &&...
     ~isempty(dir(lMakefileNameFullPath)))
    fid = fopen(lMakefileNameFullPath,'rt');
    if fid == -1
        DAStudio.error('RTW:utility:fileIOError',...
                       lMakefileNameFullPath,...
                       'open');
    end
    oldContents = fread(fid, [1, inf], '*char');
    fclose(fid);
    generateNewMakefile = ~strcmp(oldContents,tmfContents);
else
    generateNewMakefile = true;
end

if generateNewMakefile
    coder.make.MakefileWriter.printBuffer(lMakefileNameFullPath, tmfContents);
end

if generateNewMakefile
    msg = string(message...
        ('coder_compile:CoderCompile:createdMakefile', ...
         lMakefileNameFullPath));
else
    msg = string(message...
        ('coder_compile:CoderCompile:makefileWasUpToDate', ...
         lMakefileNameFullPath));
end
addToBuildLog(lBuildLogger, msg);


function [success, lCompileResults] = i_runMakefileTMF...
    (lBuildName, ...
    lBuildLogger, lTMFProperties, lWrappedMakeCmd, ...
    makeCommandPrepend)

%-------------------------------------%
% Invoke make to build the executable %
%-------------------------------------%

host = lTMFProperties.TMF_HOST;
if ~(strcmp(host,computer) || ...
        (strcmp(host,'UNIX') && isunix) ||  ...
        (strcmp(host,'PC') && ispc) || strcmp(host,'ANY'))
    
    DAStudio.error('RTW:buildProcess:incompatibleHost');
end

msg = string(message('coder_compile:CoderCompile:buildingMsg',...
    lBuildName,...
    lWrappedMakeCmd));
addToBuildLog(lBuildLogger, msg);

% Get the tool pre-pends
if ~isempty(makeCommandPrepend)
    prepend = makeCommandPrepend;
    lWrappedMakeCmd = [prepend ' ' lWrappedMakeCmd];
end

% Call the make command
[status, lCompileResults] = coder.make.internal.system(lWrappedMakeCmd, lBuildLogger);

success = ~status;


function lFoundCreatedMessage = i_runMakefile...
    (lBuildName, lBuildLogger, lTMFProperties, lWrappedMakeCmd, ...
    makeCommandPrepend, lMakeCmd, tcWriter, ...
    tcExecutor, productCreatedMessage, buildInfo, ...
    compileErrorDiagnosticFcns, lCompileProfileFcn)

% Call profiling function with argument "true" to initiate profiling 
% of makefile invocation
lCompileProfileFcn(true);

if ~isempty(lTMFProperties)
    
    % Run makefile for TMF build
    [runMakefileSuccess, lCompileResults] = i_runMakefileTMF...
        (lBuildName, lBuildLogger, lTMFProperties, lWrappedMakeCmd, ...
        makeCommandPrepend);
    
else
    % Run makefile for Toolchain build
    addToBuildLog(lBuildLogger, ...
        string(message('coder_compile:CoderCompile:RunningBuildArtifact', ...
        lBuildName, lMakeCmd)));
    
    [runMakefileSuccess, lCompileResults] = run(tcExecutor, buildInfo);
    
    % No longer need the writer and the executor; we had to keep the
    % writer alive until now only for IDE project builds
    % (tProjectBuild)
    delete(tcWriter);
    delete(tcExecutor);
end

% Call profiling function with argument "false" to terminate profiling 
% of makefile invocation
lCompileProfileFcn(false);

% Report any compilation error
if ~runMakefileSuccess
    if isempty(lTMFProperties) || isempty(lTMFProperties.TMF_BUILD_ERROR)
        buildError = string(message('coder_compile:CoderCompile:buildError', lBuildName));
    else
        % Legacy build error for TMF builds
        buildError = i_getLegacyTMFBuildError(lBuildName, lTMFProperties);
    end
    
    me = coder.make.constructBuildException...
        (buildError, lCompileResults, compileErrorDiagnosticFcns, lBuildLogger);
    throw(me);
end

% The found created message is used to determine if the final product was
% generated (and not already up to date)
lFoundCreatedMessage = contains(lCompileResults, productCreatedMessage);


%==========================================================================
% Function: ParseMakeArgs
% Abstract:
%     Remove any ITEM=value from buildArgs and return the desired
%     value.  The parsedValue must be one of: ITEM=yes, ITEM=no, ITEM=1,
%     ITEM=0. The parsedValue returned will be the numeric value 1, 0, or
%     empty.
%==========================================================================
function [parsedValue,buildArgsOut] = ParseMakeArgs(buildArgsIn,item)

  [value,buildArgsOut] = coder.make.internal.parsestrforvar(buildArgsIn,item);
  if isempty(value)
      parsedValue = [];
  else
      switch value
        case {'yes', '1'}
          parsedValue = true;
        case {'no', '0'}
          parsedValue = false;
        otherwise
          DAStudio.error('RTW:buildProcess:invalidBuildArgs',item(1:end-1));
      end
  end


function i_checkTMFContainsToken(tmfContents, buildingMakefileBuilderTgt, buildingStandaloneSuppressExe)
  msg = [];
  if buildingMakefileBuilderTgt && isempty(regexp(tmfContents, '^\s*MAKEFILEBUILDER_TGT', 'once', 'lineanchors'))
      msg = message('coder_compile:CoderCompile:missingTMFRulesToBuildTarget', 'MAKEFILEBUILDER_TGT');
  end
  if buildingStandaloneSuppressExe && isempty(regexp(tmfContents, '^\s*STANDALONE_SUPPRESS_EXE', 'once', 'lineanchors'))
      msg = message('coder_compile:CoderCompile:missingTMFRulesToBuildTarget', 'STANDALONE_SUPPRESS_EXE');
  end
  if ~isempty(msg)
      error(msg.Identifier, string(msg));
  end

function [startPos, endPos, lengthStartToken, lengthEndToken] = i_getRulePositionsInTMF(tmfContents, startToken, endToken)

  lengthStartToken = length(startToken);
  lengthEndToken   = length(endToken);

  startPos = strfind(tmfContents,startToken);
  endPos   = strfind(tmfContents,endToken);

  numOfStart = length(startPos);
  numOfEnd   = length(endPos);
  % check for syntax error
  if numOfStart ~= numOfEnd
      if numOfStart > numOfEnd
          DAStudio.error('RTW:buildProcess:tmfSyntaxErrorMissingToken',...
                         endToken);
      else
          DAStudio.error('RTW:buildProcess:tmfSyntaxErrorMissingToken',...
                         startToken);
      end
  end
  
  for i=1:numOfStart
    if startPos(i) > endPos(i)
        DAStudio.error('RTW:buildProcess:tmfSyntaxErrorDuplicateToken',...
                       endToken,startToken);
    end
  end

function processedDirectories = i_processDirectoriesForRulesExpansion(directories, specialDirs, lTMFProperties)
% Performs character escaping and replaces START_DIR with RELATIVE_PATH_TO_ANCHOR when possible

  targetPlatformIsUnix = i_platformIsUnix(lTMFProperties);
  
  % Transform paths depending on the target platform
  if targetPlatformIsUnix      
      directories = coder.make.MakefileWriter.transformPathsForUnix(directories);
  else     
      directories = coder.make.MakefileWriter.transformPathsForWin(directories);      
  end
  
    % combine both the normal dir lists and the alt name lists to get full
    % coverage for replacing the dir roots with the makefile macro
    % (i.e. $(MATLAB_ROOT) $(START_DIR) etc.
    sDirs = [specialDirs.regexpPats, specialDirs.regexpAltNamePats];
    sToks = [specialDirs.toks, specialDirs.toks];
    
    % We unintentionally let bad modules lists exist in the makecfg dirs.
    % Anything that has the form of a cell array of cells is wrong, but we
    % need to silently swallow it so BaT can run correctly (some DSP
    % makecfgs provide a modules list of {{}}). This try-catch swallows the
    % bad modules list.
    try
        % on PC paths are case insensitive, sometimes includeDirs get lower
        % case paths.  the regexp 'ignorecase' takes care of this
        % situation.  An additional benefit of regexprep over strrep
        % on lower() is case is preserved.
        
        
        if ispc
            processedDirectories = regexprep(directories, sDirs, sToks,...
                                            'ignorecase');
        else
            processedDirectories = regexprep(directories, sDirs, sToks);
        end
    catch exc %#ok<NASGU>
        % this is a bad modules list, default it to an empty string so this
        % behaves the same way it used to.
        processedDirectories = '';
    end

    if ~isempty(processedDirectories)
        processedDirectories = locGetAltPaths(processedDirectories, specialDirs);
    end

%==========================================================================
% Function: ExpandNestedRule
%
%==========================================================================
function tmfContents = ExpandNestedRule(tmfContents, libraries, locations, ...
                                        libSuffix, startToken, endToken, ...
                                        libToken, locToken,  suffixToken, ...
                                        modules, mStartToken, mEndToken,...
                                        mToken, specialDirs, lTMFProperties)

  [startPos, endPos, lengthStartToken, lengthEndToken] = i_getRulePositionsInTMF(tmfContents, startToken, endToken);
  numOfRules = length(startPos);
  
  for i=1:numOfRules
    expandedRule = '';

    tmfRule = tmfContents(startPos(i)+lengthStartToken:endPos(i)-1);
    
    locations = locGetAltPaths(locations, specialDirs);
 
    % note that the length of locations and libraries should be the same.
    for j=1:length(libraries)
      rule = char(strrep(tmfRule, libToken, libraries(j)));
      rule = char(strrep(rule, suffixToken, libSuffix));      
      rule = char(strrep(rule, locToken, locations(j)));
      if ~isempty(modules)
          tmpRule = ExpandRule(rule, modules{j}, mStartToken,...
              mEndToken, mToken, specialDirs, lTMFProperties);
      else
          tmpRule = ExpandRule(rule, {}, mStartToken,...
              mEndToken, mToken, specialDirs, lTMFProperties);
      end
      if (~(isempty(modules) || isempty(modules{j}) || isempty(modules{j}{1})) || ...
          strcmp(tmpRule,rule))
        expandedRule = [expandedRule, tmpRule]; %#ok<AGROW>
      end
    end

    tmpStartPos = startPos(i)-1;
    tmpEndPos   = endPos(i)+lengthEndToken;
    tmfContents = [ tmfContents(1:tmpStartPos), ...
                    expandedRule, ...
                    tmfContents(tmpEndPos:end) ];
    expRuleOffset = length(expandedRule) - tmpEndPos + tmpStartPos + 1;
    for j=i:numOfRules
      startPos(j) = startPos(j) + expRuleOffset;
      endPos(j)   = endPos(j)   + expRuleOffset;
    end
  end

%==========================================================================
% Function: ExpandRule 
% Abstract:
%      This function expands the text from tmfContents between start- and
%      end-token for all directories and replaces the dirToken with the
%      current directory.
%==========================================================================
function tmfContents = ExpandRule(tmfContents, directories, ...
                                  startToken, endToken, dirToken, ...
                                  specialDirs, lTMFProperties)

  processedDirectories = i_processDirectoriesForRulesExpansion(directories, specialDirs, lTMFProperties);
  [startPos, endPos, lengthStartToken, lengthEndToken] = i_getRulePositionsInTMF(tmfContents, startToken, endToken);
  numOfRules = length(startPos);
  for i=1:numOfRules
    tmfRule = tmfContents(startPos(i)+lengthStartToken:endPos(i)-1);

    expandedRule = '';    
    for j=1:length(processedDirectories)
        expandedRule = [expandedRule, ...
                        char(strrep(tmfRule,...
                                    dirToken,...
                                    processedDirectories{j}))]; %#ok<AGROW>
    end

    tmpStartPos = startPos(i)-1;
    tmpEndPos   = endPos(i)+lengthEndToken;
    tmfContents = [ tmfContents(1:tmpStartPos), ...
                    expandedRule, ...
                    tmfContents(tmpEndPos:end) ];
    expRuleOffset = length(expandedRule) - tmpEndPos + tmpStartPos + 1;
    for j=i:numOfRules
      startPos(j) = startPos(j) + expRuleOffset;
      endPos(j)   = endPos(j)   + expRuleOffset;
    end
  end

%endfunction

function  srcFilesWithPaths = i_processSrcModules(buildInfo, makefileFolder, ...
    buildDir, srcPaths, srcFiles)
% Removes directories for which we don't need to expand rules (for example, StartDir, BuildDir)
% With g1695660 and g917475, this special case will no longer be needed

buildInfoStartDir = buildInfo.getSourcePaths(true, 'StartDir');
if ~isempty(buildInfoStartDir)
    buildInfoStartDir = buildInfoStartDir{1};
else
    buildInfoStartDir = '';
end

excludePaths = [buildDir ...
                {'', makefileFolder, ...
                buildInfoStartDir}...
                ];

% Discards paths designated by EXCLUDEPATHS from the list of source paths
expandedSrcPaths = buildInfo.formatPaths(srcPaths);
removePathIdx = ismember(expandedSrcPaths, excludePaths);
srcPaths = srcPaths(~removePathIdx);
srcFiles = srcFiles(~removePathIdx);

srcFilesWithPaths = fullfile(srcPaths, srcFiles);

%==========================================================================
% Function: ExpandRuleWithSourceAndPath 
% Abstract:
%      This function expands the text from tmfContents between start- and
%      end-token for all directories and replaces the dirToken with the
%      current directory. It also expands this same text for each file in
%      srcFilesWithPaths producing file specific rules
%==========================================================================
function tmfContents = ExpandRuleWithSourceAndPath(tmfContents, directories, srcFilesWithPaths,...
                                                  startToken, endToken, dirToken, ...
                                                  specialDirs, ...
                                                  fileSeparator,...
                                                  lHandleSpacesInPath, ...
                                                  lTMFProperties)

  isNmakeBasedTMF = contains(getMAKECMD(lTMFProperties), 'nmake');
  processedDirectories = i_processDirectoriesForRulesExpansion(directories, specialDirs, lTMFProperties);
  [srcPaths, srcFiles, srcExtensions]  = cellfun(@fileparts, srcFilesWithPaths, 'UniformOutput', false);

  [startPos, endPos, lengthStartToken, lengthEndToken] = i_getRulePositionsInTMF(tmfContents, startToken, endToken);
  numOfRules = length(startPos);
  
  targetPlatformIsUnix = i_platformIsUnix(lTMFProperties);
  
  for i=1:numOfRules
    tmfRule = tmfContents(startPos(i)+lengthStartToken:endPos(i)-1);
    expandedRule = '';    
    % Expand the rule for source paths
    for j=1:length(processedDirectories)
        expandedRule = [expandedRule, ...
                        char(strrep(tmfRule,...
                                    dirToken,...
                                    processedDirectories{j}))]; %#ok<AGROW>
    end
    
    if isNmakeBasedTMF
        % Nmake rules are of the form {|>EXPAND_DIR_NAME<|}.c.obj :
        % Parse the extensions and reverse their order to be consistent with Gmake
        extensions = regexp(tmfRule, '(\.\w*)', 'tokens');
        extensions = extensions(end:-1:1);
    else
        % Gmake rules are of the form: %.o : |>EXPAND_DIR_NAME<|/%.c
        extensions = regexp(tmfRule, '%(\.\w*)', 'tokens');
    end
    
    % Only expand rules with wildcards for the target and dependency
    if length(extensions) == 2
        tmfRuleObjExtension=extensions{1}{1};
        tmfRuleSrcExtension=extensions{2}{1};
        idxSrcExtMatchesTMFRuleSrcExt = strcmp(srcExtensions, tmfRuleSrcExtension);
        
        % Expand the rule for each source file
        for j=1:length(srcPaths)

            % Expand the rule only if the dependency and the source file have the same extension
            if idxSrcExtMatchesTMFRuleSrcExt(j)
                target = [srcFiles{j} tmfRuleObjExtension];
                dependency = [srcPaths{j} fileSeparator srcFiles{j} tmfRuleSrcExtension];
                
                % Handle spaces in the dependency
                % Testing for a space is to accommodate a tornado legacy TMF (g1857500)
                % It should no longer be required with g1858826
                if contains(dependency, ' ')
                    if lHandleSpacesInPath==coder.make.internal.PathHandleSpaces.WrapWithQuotes
                        % wrap with quotes
                        dependency = regexprep(dependency, '.*', '"$0"');
                    else
                        assert(lHandleSpacesInPath==coder.make.internal.PathHandleSpaces.EscapeWithBackslash, ...
                               'lHandleSpacesInPath must be a valid enum');
                        % escape with a backslash
                        dependency = regexprep(dependency, ' ', '\\ ');
                    end        
                end
                
                % Transform paths for Windows
                if ~targetPlatformIsUnix
                    dependency = coder.make.MakefileWriter.transformPathsForWin(dependency);                
                end
                
                % Replace rule's first line with a file-specific rule ( ex: srcFile.o: filePath/srcFile.c ) 
                newTmfRuleFirstLine = [target ' : ' dependency];
                replacedFirstLine = regexprep(tmfRule, '^.*', ...
                                              regexptranslate('escape', newTmfRuleFirstLine),'dotexceptnewline');
                
                % Nmake does not support the wildcard "$<" (in absence of globbing) 
                % Replace occurrences of "$<" with the dependency
                expandedDependency = regexprep(replacedFirstLine, '\"?\$<\"?',...
                                               regexptranslate('escape', dependency));   
                
                expandedRule = [expandedRule, expandedDependency]; %#ok
            end
        end
    end

    tmpStartPos = startPos(i)-1;
    tmpEndPos   = endPos(i)+lengthEndToken;
    tmfContents = [ tmfContents(1:tmpStartPos), ...
                    expandedRule, ...
                    tmfContents(tmpEndPos:end) ];
    expRuleOffset = length(expandedRule) - tmpEndPos + tmpStartPos + 1;
    for j=i:numOfRules
      startPos(j) = startPos(j) + expRuleOffset;
      endPos(j)   = endPos(j)   + expRuleOffset;
    end
  end

%endfunction

function targetPlatformIsUnix = i_platformIsUnix(tmfProperties)
tcInfo = tmfProperties.Toolchain;
host = tmfProperties.TMF_HOST;
targetPlatformIsUnix = strcmp(host,'UNIX') || ( ~isempty(tcInfo) && any(strcmp(tcInfo.Platform, {'glnxa64', 'maci64'})));

function lBuildLogger = i_getBuildLogger...  
    (lVerbose, lTMFProperties, lDispHook)
if ~isempty(lTMFProperties)
  % Allow makefile to override a RTW Verbose setting of 0 (off)
  makefileVerboseVal = lTMFProperties.TMF_VERBOSE_BUILD_OFF_TREATMENT;
  makefileVerboseOverride = (~isempty(makefileVerboseVal) && ...
      strcmp(makefileVerboseVal,'PRINT_OUTPUT_ALWAYS'));
  lVerbose = lVerbose || makefileVerboseOverride;
end
lBuildLogger = coder.make.BuildLogger(lVerbose, lDispHook);

%=============================================================================
% Function: locEvalMakeVar 
% modelName is needed, because it must be present in the workspace for the
% string eval
%=============================================================================
function rc = locEvalMakeVar(inp,modelName,makeVar) %#ok<INUSL>

    try
        rc = eval(inp);
    catch exc
        % the original error message is formatted with various HTML
        % formatting and possible drive letter specification.  clean it
        % up before including it
        errMsg = rtwprivate('escapeOriginalMessage',exc);
        errID  = 'RTW:buildProcess:badTMFMakeVar';
        errMsg = DAStudio.message(errID,makeVar,inp,errMsg);

        newExc = MException(errID, errMsg);
        newExc = newExc.addCause(exc);
        throw(newExc);
    end



%=============================================================================
% Function: locGetAltPaths 
%
% inputs:
%    paths
%    specialDirs
%
%
% returns:
%    rc
%
%
%=============================================================================
function rc = locGetAltPaths(rc, specialDirs)


     % combine both the normal dir lists and the alt name lists to get full
     % coverage for replacing the dir roots with the makefile macro
     % (i.e. $(MATLAB_ROOT) $(START_DIR) etc.
     sDirs = [specialDirs.regexpPats, specialDirs.regexpAltNamePats];
     sToks = [specialDirs.toks, specialDirs.toks];

     spcIdx = coder.make.MakefileWriter.containsSpaces(rc);
     % the paths may have macros such as $(MATLAB_ROOT) in them, but have a
     % space after the macro.  swap in the real value to transform the
     % path.
     rc(spcIdx) = regexprep(rc(spcIdx),...
                            regexptranslate('escape', specialDirs.toks),...
                            specialDirs.regexpPats);
     rc(spcIdx) = coder.make.internal.transformPaths(rc(spcIdx),'ignoreErrors',true);   

     % swap the macros back in
     if ispc
         rc(spcIdx) = regexprep(rc(spcIdx), sDirs, sToks,'ignorecase');
     else
         rc(spcIdx) = regexprep(rc(spcIdx), sDirs, sToks);
     end


%==========================================================================
% For LCC, an LCC-specific source file is required
%==========================================================================
function buildInfo = locUpdateBuildInfo(buildInfo, lToolchainInfo, ...
    isMexTarget, lBuildVariant)
isUsingLCC = ~isempty(lToolchainInfo) && strcmp(lToolchainInfo.Alias{1}, 'LCC-x');
if isUsingLCC
    
    isSharedLibraryTarget = locIsCompilingSharedLibrary(lBuildVariant);

    % When using LCC and the final target is shared library
    if isSharedLibraryTarget
        toolSpecificPath   = fullfile('$(MATLAB_ROOT)', 'sys', 'lcc64', 'lcc64', 'mex');
        toolSpecificSource = 'lccstub.c';
        buildInfo.addSourceFiles(toolSpecificSource, toolSpecificPath);
    end

    % When using LCC and the final target generates a MEX file, add the MEX version file to
    % buildInfo to embed the version to LCC MEX files. It is needed for LCC because LCC builds
    % a MEX file outside the mex command. Will no longer be required with g1749338.
    if isMexTarget
        mexVersionFile = 'c_mexapi_version.c';
        mexVersionPath = fullfile(matlabroot,'extern','version');
        buildInfo.addSourceFiles(mexVersionFile,mexVersionPath,'COMPILER_SRC');
    end
        
end

function isCompilingSharedLibrary = locIsCompilingSharedLibrary(lBuildVariant)
isCompilingSharedLibrary = ((lBuildVariant == coder.make.enum.BuildVariant.SHARED_LIBRARY) || ...
                            (lBuildVariant == coder.make.enum.BuildVariant.SHARED_LIBRARY_TARGET));


% For a TMF build, honor the Debug option for build configuration by 
% setting the DEBUG_BUILD build arg
function i_tmfRequestDebugBuild(buildInfo, lBuildConfiguration)
% Ensure that we build with debug symbols

if strcmp(lBuildConfiguration, 'Debug')
    % For TMF builds, set DEBUG_BUILD=1 on buildInfo
    buildInfo.deleteKeyValuePair('BuildArg', 'DEBUG_BUILD');
    buildInfo.addBuildArgs('DEBUG_BUILD', '1', 'MakeArg');
end


% Issue warning about legacy content, only if there is no special token to
% suppress the warning
function i_issueLegacyTokenWarning(tmfContents, legacyTokens, templateMakefile)

    % Check for the special comment to suppress warning about unused tokens
    foundSuppressWarningComment = contains(tmfContents, 'NO_WARN_LEGACY_TOKENS');
    
    % Issue warning
    if ~foundSuppressWarningComment
        
        legacyTokensList = join(string(legacyTokens), ', ');
        
        msg = message('coder_compile:CoderCompile:legacyTMFToken', legacyTokensList, ...
                         templateMakefile);
        warning(msg.Identifier, '%s', string(msg));
    end



% Remove legacy content and issue warning
function tmfContents = i_removeAndWarnLegacyContent...
        (tmfContents, templateMakefile, legacyTokensList, expr)

    i_issueLegacyTokenWarning(tmfContents, legacyTokensList, templateMakefile);
    
    % Remove the legacy tokens
    tmfContents = regexprep(tmfContents, expr, '', 'lineanchors');

% Determine extra source files listed in buildInfo to be compiled by the
% mex command
function extraSrcFiles = i_getSourceFilesForMex(buildInfo, mexSrcFileName, ...
    lUserSrcs, excludeGroups)

    if ~isempty(lUserSrcs)
        [lUserSrcPaths, f, e] = coder.make.internal.fileparts(lUserSrcs, buildInfo.Settings.FileSep);
        lUserSrcNames = strcat(f, e);
        addSourceFiles(buildInfo, lUserSrcNames, lUserSrcPaths, 'USER_SRCS_TEMPORARY');
    end    
    
    % The call to updateFilePathsAndExtensions potentially can be optimized, g2061854.
    updateFilePathsAndExtensions(buildInfo);
    
    % Extract source files from buildInfo
    extraSrcFilesNoPath = ...
        getSourceFiles(buildInfo, false, false, {}, excludeGroups);
    extraSrcFilesWithPath = ...
        getSourceFiles(buildInfo, true, true, {}, excludeGroups);
    
    if ~isempty(lUserSrcs)
        removeSourceFiles(buildInfo, lUserSrcNames);
    end
    
    mexSrcFileIdx = strcmp(extraSrcFilesNoPath, mexSrcFileName);
    assert(sum(mexSrcFileIdx)==1, 'Must be exactly one match');
    buildFolder = fileparts(extraSrcFilesWithPath{mexSrcFileIdx});
    
    % Identify files that are in the build folder
    inBuildFolderIdx = strcmp(fullfile(buildFolder, extraSrcFilesNoPath), ...
        extraSrcFilesWithPath);
    
    % By default include the full path
    extraSrcFiles = extraSrcFilesWithPath;
    
    % For files in the build folder, discard the path (for readability)
    extraSrcFiles(inBuildFolderIdx) = extraSrcFilesNoPath(inBuildFolderIdx);
    
    % Discard the main mex source file (because it is specified explicitly)
    extraSrcFiles(mexSrcFileIdx) = [];
    


% Determine mex build data
function [lModelLib, lMexDefines] = i_getMexBuildData(buildInfo)

% Identify the main model library
[~, lModelLib] = findBuildArg(buildInfo, 'MODELLIB');
if isempty(lModelLib)
    lModelLib = {};
else
    lModelLib = {lModelLib};
end
    
% Required define for SIM target build
lMexDefines = {'-DMDL_REF_SIM_TGT=1'};   


function sfcnList = i_getSfcnList(buildInfo, sfcnLibs, specialDirs, libExt)

    sfcnPaths  = {sfcnLibs.Path};
    libNames = {sfcnLibs.Name};    
    
    % Add library file extension if not already specified
    libNames = coder.make.internal.addLibExtIfMissing(libNames, libExt);
    
    % Add the full path, if it exists
    havePaths = ~cellfun(@isempty, sfcnPaths);
    libNames(havePaths) = strcat(sfcnPaths(havePaths),...
                                buildInfo.Settings.FileSep,...
                                libNames(havePaths));

    % Convert the cell array to a space separated char array, which will be inserted
    % into the TMF and replace any entries that have spaces with the alternate name
    libNames = locGetAltPaths(libNames, specialDirs);
    
    % If getAltPaths failed, e.g. 8.3 paths are not supported, or the path 
    % is not accessible, then some paths will still contain spaces.
    % For Nmake builds such paths must be quoted    
    if coder.make.internal.pathsMustBeQuotedBasedOnFileSep(buildInfo.Settings.FileSep)        
        spcIdx = coder.make.MakefileWriter.containsSpaces(libNames);
        lHandleSpacesInPath = coder.make.internal.PathHandleSpaces.WrapWithQuotes;
        libNames(spcIdx) = coder.make.MakefileWriter.handleSpacesInPaths(libNames(spcIdx), lHandleSpacesInPath);    
    end  
     
    sfcnList = char(join(string(libNames), " "));
    

% Get cell arrays of library names and paths
function [libNames, libPaths] = i_getLibNamesAndPaths(lLinkObjs)

if ~isempty(lLinkObjs)
    libNames = {lLinkObjs.Name};
    libPaths = {lLinkObjs.Path};
else
    libNames = {};
    libPaths = {};
end    

% Determine compiler optimization flags
function lOptCompileFlags = i_getCompilerOptimizationFlags...
    (lTMFProperties, mexToolChainName, lBuildConfiguration, lCustomToolchainOptions)

switch lBuildConfiguration
  case {'Faster Builds', 'Faster Runs', 'Debug'}
    % Compiler options for lcc, mingw64, unix, vcx64
    if ~isempty(mexToolChainName)
        % If the mex toolchain name is available we can use it
        lCompilerString = mexToolChainName;
    else
        % For legacy template makefiles, we need to derive the compiler string
        % from TMF content
        lCompilerString = lower(getCompilerString(lTMFProperties));
    end
    lOptCompileFlags = coder.make.internal.getCompilerSpecificStrings...
        (lCompilerString, lBuildConfiguration);
  case 'Specify'
    % For TMF builds, we don't distinguish between C and C++ options
    keys = {'C Compiler', 'C++ Compiler'};
    [~, matchIdx] = intersect(lCustomToolchainOptions(1:2:end), keys);
    
    % Extract compiler flags for C and C++ compilers; if flags for both C and C++ are specified both
    % sets are returned. At present, there is no way to distinguish between C and C++ flags for TMF
    % builds.
    lOptCompileFlags = lCustomToolchainOptions(2*matchIdx);
end

% Update the buildInfo file separator. Return argument is a cleanup function that
% restores buildInfo to its original state.
function restoreFileSepFcn = i_buildInfoFileSepUpdate(buildInfo, lToolchainInfo, lTMFProperties)

if ~isempty(lToolchainInfo)
    % If toolchain info available, determine file separator expected by make tool
    makeToolFileSep = getDirective(lToolchainInfo.BuilderApplication, 'FileSeparator');
    makeToolFileSep = strrep( makeToolFileSep, '\\', '\' );
else
    % If no toolchain info available, must be a legacy TMF build
    makeToolFileSep = lTMFProperties.TMF_MAKEFILE_FILESEP;
end

if ~isempty(makeToolFileSep) && ~isequal(makeToolFileSep, filesep)
    originalFileSep = buildInfo.Settings.FileSep;
    updateFileSeparator(buildInfo, makeToolFileSep);
    restoreFileSepFcn = @()updateFileSeparator(buildInfo, originalFileSep);
else
    restoreFileSepFcn = @()[];
end

restoreFileSepFcn = onCleanup(restoreFileSepFcn);




function tmfContents = i_handleUnusedTMFTokens...
    (remainingTokens, tmfContents, templateMakefile)
if ~isempty(remainingTokens)
    tokensString = join(string(remainingTokens), ', ');
    DAStudio.warning('RTW:buildProcess:unusedTMFToken', ...
                     char(tokensString), templateMakefile);
end


function [tmfContents, remainingTokens] = i_handleLegacySharedSrcTokens...
        (lBuildInfo, tmfContents, templateMakefile, remainingTokens, ...
        lStaticLibSuffix)

sharedSRCTokens = {'|>SHARED_SRC<|', '|>SHARED_SRC_DIR<|', '|>SHARED_BIN_DIR<|', ...
                   '|>SHARED_LIB<|'};
legacySharedSRCTokensFound = intersect(remainingTokens, sharedSRCTokens);
if ~isempty(legacySharedSRCTokensFound)
    
    i_issueLegacyTokenWarning...
        (tmfContents, legacySharedSRCTokensFound, templateMakefile);
    
    sharedLib = lBuildInfo.getLinkObjects('SHARED_SRC_LIB');
    [~, lSHARED_SRC_DIR] = findBuildArg(lBuildInfo, 'SHARED_SRC_DIR');
    
    % Update filesep
    lSHARED_SRC_DIR = regexprep(lSHARED_SRC_DIR, regexptranslate('escape', filesep), ...
                        regexptranslate('escape', lBuildInfo.Settings.FileSep));

    if ~isempty(sharedLib)
        [~, lRelativePathToAnchor] = findBuildArg(lBuildInfo, 'RELATIVE_PATH_TO_ANCHOR');
        lSHARED_SRC = fullfile(lSHARED_SRC_DIR, '*.c*');
        lSHARED_BIN_DIR = strrep(sharedLib.Path, '$(START_DIR)', lRelativePathToAnchor);
        % Update filesep
        lSHARED_BIN_DIR = regexprep(lSHARED_BIN_DIR, regexptranslate('escape', filesep), ...
                                    regexptranslate('escape', lBuildInfo.Settings.FileSep));
        lSHARED_LIB = fullfile(lSHARED_BIN_DIR, [sharedLib.Name lStaticLibSuffix]);
    else
        lSHARED_SRC = '';
        lSHARED_BIN_DIR = '';
        lSHARED_LIB = '';
    end
    
    tmfContents = strrep(tmfContents, '|>SHARED_SRC<|', lSHARED_SRC);
    tmfContents = strrep(tmfContents, '|>SHARED_SRC_DIR<|', lSHARED_SRC_DIR);
    tmfContents = strrep(tmfContents, '|>SHARED_BIN_DIR<|', lSHARED_BIN_DIR);
    tmfContents = strrep(tmfContents, '|>SHARED_LIB<|', lSHARED_LIB);

end

remainingTokens = setdiff(remainingTokens, sharedSRCTokens);    
    

% Get text of message generated by makefile when the final product is created
function productCreatedMessage = i_getCreatedMessage(lTMFProperties, lBuildName)

if ~isempty(lTMFProperties.TMF_BUILD_SUCCESS)
    buildSuccess = lTMFProperties.TMF_BUILD_SUCCESS;
    if buildSuccess(1) == '['
        buildSuccess = locEvalMakeVar(buildSuccess, lBuildName, 'BUILD_SUCCESS');
    end
else
    buildSuccess = '### Created';
end
        
% For template makefile builds, the final product created and build success 
% messages are the same
productCreatedMessage = buildSuccess;
        


% Determine if there was a build error
function legacyTMFBuildError = i_getLegacyTMFBuildError(lBuildName, lTMFProperties)

% Legacy build error specified in .tmf file
legacyTMFBuildError = lTMFProperties.TMF_BUILD_ERROR;
if legacyTMFBuildError(1) == '['
    
    % Replace the MODEL_NAME token
    legacyTMFBuildError = regexprep(legacyTMFBuildError, regexptranslate('escape', '|>MODEL_NAME<|'), lBuildName);
    
    legacyTMFBuildError = locEvalMakeVar...
        (legacyTMFBuildError, lBuildName, 'BUILD_ERROR');
end


% Clean up BuildTools field before returning
function i_removeBuildTools(buildInfo)
buildInfo.BuildTools = [];


% Synchronize buildInfo token MODELREF_TARGET_TYPE
function i_syncBuildInfoTargetType(buildInfo, lModelReferenceTargetType)

groupBuildArg = coder.make.internal.BuildInfoGroup.BuildArgGroup;

% Find any existing buildArg MODELREF_TARGET_TYPE
[key, value] = findBuildArg(buildInfo, 'MODELREF_TARGET_TYPE');

if isempty(key) || ~strcmp(lModelReferenceTargetType, value)
    
    % Remove any existing MODELREF_TARGET_TYPE
    if isempty(buildInfo.BuildArgs)
        keys = {};
    else
        keys = {buildInfo.BuildArgs.Key};
    end
    keepIdx = ~strcmp(keys, 'MODELREF_TARGET_TYPE');
    buildInfo.BuildArgs = buildInfo.BuildArgs(keepIdx);
   
    % Add the updated value
    addBuildArgs(buildInfo, 'MODELREF_TARGET_TYPE', lModelReferenceTargetType, ...
        groupBuildArg);
    
end

function i_syncMakefileName(buildInfo, lMakefileName)

% Find any existing buildArg MAKEFILE_NAME
[key, value] = findTMFToken(buildInfo, '|>MAKEFILE_NAME<|');

if isempty(key) || ~strcmp(lMakefileName, value)
    i_setBuildInfoToken(buildInfo, '|>MAKEFILE_NAME<|', lMakefileName, '');
end



function i_setBuildInfoArg(buildInfo, argName, value, group)
     
    % Remove any existing build arg STANDALONE_SUPPRESS_EXE
    if isempty(buildInfo.BuildArgs)
        keys = {};
    else
        keys = {buildInfo.BuildArgs.Key};
    end
    keepIdx = ~strcmp(keys, argName);
    buildInfo.BuildArgs = buildInfo.BuildArgs(keepIdx);
    
    buildInfo.addBuildArgs(argName, value, group)
    
    
    
function i_setBuildInfoToken(buildInfo, tokenName, value, group)
    
    % Remove token if it already exists
    if isempty(buildInfo.Tokens)
        keys = {};
    else
        keys = {buildInfo.Tokens.Key};
    end
    keepIdx = ~strcmp(keys, tokenName);
    buildInfo.Tokens = buildInfo.Tokens(keepIdx);
   
    % Set the value
    addTMFTokens(buildInfo, tokenName, value, group);
    

function i_syncSuppressExe(buildInfo, lSuppressExeLogical)

% Always set buildInfo token |>STANDALONE_SUPPRESS_EXE<| to '0'. The only way to 
% change it is by overriding it with a BuildArg passed via the command line. 
% This allows changing the resolved value of STANDALONE_SUPPRESS_EXE without 
% modifying the makefile (which would cause all object code to be re-compiled).
defaultValue = '0';
i_setBuildInfoToken(buildInfo, '|>STANDALONE_SUPPRESS_EXE<|', defaultValue, '');

if lSuppressExeLogical
    
    lSuppressExe = sprintf('%d', logical(lSuppressExeLogical));

    % Setting the group as MakeArg passes these build args as an argument to the
    % make tool so that any hard-coded value in the makefile is overridden
    i_setBuildInfoArg(buildInfo, 'STANDALONE_SUPPRESS_EXE', lSuppressExe, 'MakeArg');
    i_setBuildInfoArg(buildInfo, 'PRODUCT', 'ObjectModulesOnly', 'MakeArg');
    
else
    
    % Remove build args STANDALONE_SUPPRESS_EXE, PRODUCT
    [~, removeIdx] = intersect({buildInfo.BuildArgs.Key}, {'STANDALONE_SUPPRESS_EXE', 'PRODUCT'});
    buildInfo.BuildArgs(removeIdx) = [];
    
end



% Synchronize buildInfo MODELLIB token
function i_syncModellib(buildInfo, finalTarget, lBuildVariant, lUserSrcs)


if any(lBuildVariant == [...
        coder.make.enum.BuildVariant.MEX_FILE ...
        coder.make.enum.BuildVariant.MODEL_REFERENCE_CODER ...
        coder.make.enum.BuildVariant.STATIC_LIBRARY ...
        coder.make.enum.BuildVariant.SHARED_LIBRARY])
    
    % Check if buildInfo has any files that will be included in MODELLIB
    % static library
    hasModelLibSourceFiles = ~isempty ...
        (getSourceFiles(buildInfo, false, false)) || ~isempty(lUserSrcs);
    
    if hasModelLibSourceFiles
        lModelLib = finalTarget;
    else
        % No source files to compile therefore no static library
        lModelLib = '';
    end
else
    lModelLib = '';
end

% Find any existing buildArg MODELLIB
[key, value] = findBuildArg(buildInfo, 'MODELLIB');

if isempty(key) || ~strcmp(lModelLib, value)
    
    i_setBuildInfoArg(buildInfo, 'MODELLIB', lModelLib, ...
                    coder.make.internal.BuildInfoGroup.BuildArgGroup);
    
end


function value = locIsTokenOrBuildArgSet(lBuildInfo, keyname)
[~, keyValueToken] = lBuildInfo.findTMFToken(['|>' keyname '<|']);
[~, keyValueBuildArg] = lBuildInfo.findBuildArg(keyname);
value = strcmp(keyValueToken, '1') |...
    strcmp(keyValueBuildArg, '1');


function buildingMakefileBuilderTgt = i_isBuildingMakefileBuilder(lBuildInfo)

buildingMakefileBuilderTgt = false;

[~, lModelRefTargetType]   = lBuildInfo.findBuildArg('MODELREF_TARGET_TYPE');    

lMakefileBuilderTgt    = locIsTokenOrBuildArgSet(lBuildInfo, 'MAKEFILEBUILDER_TGT');

if strcmp(lModelRefTargetType, 'NONE')
    if lMakefileBuilderTgt
        % Makefile Builder Executable
        buildingMakefileBuilderTgt = true;
    end
end


function addSimdSupport(lToolchainInfo, buildInfo, isCpp, simdVersion)
if ~lToolchainInfo.hasCustomBuildConfiguration(simdVersion) || isempty(lToolchainInfo)
    if contains(simdVersion,'AVX')
        DAStudio.error('coder_compile:toolchain:NotSupportSimdAvx', lToolchainInfo.Name, simdVersion);
    else %SSE is ok with some compilers without flags(e.g. msvc)
        return;
    end
end    
if isCpp
    compileFlags = lToolchainInfo.getCustomBuildConfigurationOption(simdVersion,'C++ Compiler');
else
    compileFlags = lToolchainInfo.getCustomBuildConfigurationOption(simdVersion,'C Compiler');
end
buildInfo.addCompileFlags(compileFlags, 'TFL');
  
function i_reportNoMexCompiler(lTMFProperties, lToolchainInfo)
if ~isempty(lTMFProperties)
    DAStudio.error('Coder:buildProcess:RtwsfcnTMFNeedsMexCompiler', lTMFProperties.TemplateMakefile)
else
    DAStudio.error('Coder:buildProcess:RtwsfcnNeedsMexToolchain', lToolchainInfo.Name)
end

% Determine any makefile dependencies
function [lMakefileDependenciesTokenized, lMakefileDependenciesFullPath] = ...
    i_getMakefileDependencies(compileFolder)

% At present, rtw_proj.tmw must be in the compile folder therefore the
% tokenized form does not have a path specification
legacyProjFileTokenized = 'rtw_proj.tmw';

% If current working folder is different to compile folder, the path is
% required
legacyProjFileFullPath = fullfile(compileFolder, legacyProjFileTokenized);
if exist(legacyProjFileFullPath, 'file')==2
    % Need a makefile dependency
    lMakefileDependenciesTokenized = {legacyProjFileTokenized};
    lMakefileDependenciesFullPath = {legacyProjFileFullPath};
else
    lMakefileDependenciesTokenized = {};
    lMakefileDependenciesFullPath = {};
end

function i_inconsistentDebugLevel(lToolchainInfo, compilerOutput)

if ~isempty(lToolchainInfo) && contains(lToolchainInfo.Name, 'Microsoft') && ...
        contains(compilerOutput, '_ITERATOR_DEBUG_LEVEL')
    msg = message('coder_compile:CoderCompile:InconsistentDebugSettings');
    error(msg.Identifier, string(msg));
end

function i_checkBuildConfiguration(lBuildConfiguration, lToolchainInfo, lTMFProperties)

if isempty(lTMFProperties)
    validCfgs = [getBuildConfigurations(lToolchainInfo); {'Specify'}];
else
    validCfgs = {'Faster Builds', 'Specify', 'Faster Runs', 'Debug'};
end
if ~any(strcmp(lBuildConfiguration, validCfgs))
    validCfgs = join(string(validCfgs), ', ');
    msg = message('coder_compile:CoderCompile:Invalid_BuildConfigurationName', ...
                  lBuildConfiguration, validCfgs);
    error(msg.Identifier, string(msg));
end

function [makeArgsRunMakefile, lMakeArgs, preCompTargets] = ...
    i_getMakeArgsRunMakefile(lBuildInfo, lTMFProperties)

% Extract "BUILD" make arg from buildInfo
[makeArgsRunMakefile, lMakeArgs] = ParseMakeArgs(lBuildInfo.MakeArgs,'BUILD');

% For an rtw_precompile_libs build, split out the build targets immediately
% following PRECOMP_LIB_BUILD=1 at the end of the build line
preCompTargets = regexp(lMakeArgs, '(?<=PRECOMP_LIB_BUILD=1).*', 'once', 'match');
lMakeArgs = strrep(lMakeArgs, preCompTargets, '');

% This check has lower precedence than makeArgs such as BUILD=yes
if isempty(makeArgsRunMakefile) && ~isempty(lTMFProperties)
    makeArgsRunMakefile = lTMFProperties.TMF_BUILD;
    makeArgsRunMakefile = strcmpi(makeArgsRunMakefile, 'yes') || strcmp(makeArgsRunMakefile, '1');
end

if isempty(makeArgsRunMakefile)
    % Default
    makeArgsRunMakefile = true;
end
        
   

function checksumForMissingObjFile = i_getChecksumForMissingObj(buildInfo, mexSrcFileName)
% Determine checksum for an object file that does not exist (for example,
% when building a mex file, object files are not retained)

if ~isempty(mexSrcFileName)
    % For checksums, use the final mex file because mex does not leave all
    % the object code behind   
    mexFile = regexprep(mexSrcFileName, '(?<=\.).*$', mexext);
    [~, lRelativePathToAnchor] = ...
        findBuildArg(buildInfo, 'RELATIVE_PATH_TO_ANCHOR');
    mexFile = fullfile(lRelativePathToAnchor, mexFile);
    checksumForMissingObjFile = builtin('_coder_compile_getMd5', mexFile);
else
    checksumForMissingObjFile = [];
end


function [objectCodeFiles, foundObjs, searchExts] = ...
    i_getObjFilesNoToolchain(lCompileInfo, buildFolder)

% Candidate object file extensions
searchExts = {'.o', '.obj', '.o86', '.m86'};

d = dir(buildFolder);
names = {d.name};
foundExts = unique(regexp(names, '\..*$', 'match', 'once'));
foundExts = intersect(searchExts, foundExts);

sourceModules = getSourceFiles(lCompileInfo);

objectCodeFiles = repmat({''}, size(sourceModules));
if length(foundExts)==1
    % Found a unique object code extension
    objExt = foundExts{1};
    expectedObjectCodeFiles = regexprep(sourceModules, '\..*$', objExt, 'once');
    [~, existIdx] = intersect(expectedObjectCodeFiles, names);
    objectCodeFiles(existIdx) = expectedObjectCodeFiles(existIdx);
    foundObjs = true;
else
    foundObjs = false;
end

function idcs = i_propIsHardcodedInTMF(props, lTMFProperties)
% Returns a logical array to indicate whether each property of props
% has a corresponding TMF variable
idcs = false(size(props));
for i=1:numel(props)
    if strcmp(props{i}, 'MT')
        % In the shipping TMFs, MULTITASKING is assigned to the macro MT i.e. -DMT=$(MULTITASKING)
        prop =  'TMF_MULTITASKING';
    else
        % The other variables are assigned to macros of the same name. Ex. -DTERMFCN=$(TERMFCN)
        prop =  ['TMF_', props{i}];
    end
   idcs(i) = isprop(lTMFProperties, prop) && ~isempty(lTMFProperties.(prop));
end

function i_warnNoTMFTokenForDefines(defsOther, tmfFile, tmfContents, tmfToken)
% Called only when the TMF doesn't have the token |>DEFINES_OTHER<|
% Issues a warning if BuildInfo contains defines that are not present (hardcoded) in the TMF
defsNotInTMFIdces = cellfun(@isempty, regexp(tmfContents, defsOther, 'match'));
% Do not warn for '-DHAVESTDIO', sldrt.tmf does not hardcode/use it even if it is defined in BuildInfo
% See g2173473
defsNotInTMFIdces = defsNotInTMFIdces & ~strcmp(defsOther, 'HAVESTDIO');
if any(defsNotInTMFIdces) 
    defsNotInTMF = strjoin(defsOther(defsNotInTMFIdces), ' ');
    msg = message('coder_compile:CoderCompile:missingTMFTokenForDefines', ...
        defsNotInTMF, tmfFile, tmfToken);
    warning('RTW:buildProcess:missingTMFTokenForDefines', '%s', string(msg));
end

function i_warnNoTMFTokenForFlags(flagsOther, tmfFile, tmfContents, tmfToken)
% Called only when the TMF doesn't have the token |>COMPILE_FLAGS_OTHER<|
% Issues a warning if BuildInfo contains flags that are not present (hardcoded) in the TMF
flagsNotInTMFIdces = cellfun(@isempty, regexp(tmfContents, flagsOther, 'match'));
if any(flagsNotInTMFIdces) 
    flagsNotInTMF = strjoin(flagsOther(flagsNotInTMFIdces), ' ');
    msg = message('coder_compile:CoderCompile:missingTMFTokenForFlags', ...
        flagsNotInTMF, tmfFile, tmfToken);
    warning('RTW:buildProcess:missingTMFTokenForFlags', '%s', string(msg));
end

function objectCodeFiles = i_getObjFiles...
    (lCompileInfo, lToolchainInfo, generateCodeOnly)

% If any source file has an unknown extension, the corresponding object
% code file would be returned as empty. This is acceptable if
% generateCodeOnly==true. If generateCodeOnly==false, we must error out if
% any source file has an unrecognized extension.
checkSourceFileExtensions = ~generateCodeOnly;

% Determine object code checksums
sourceModules = getSourceFiles(lCompileInfo);
objectCodeFiles = coder.make.internal.replaceFileExtensions...
    (sourceModules, lToolchainInfo, checkSourceFileExtensions);



function i_updateObjectCodeChecksums(objectCodeFiles, lCompileInfo, ...
    checksumForMissingObjFile, generateCodeOnly)

if generateCodeOnly
    emptyIdx = cellfun(@isempty, objectCodeFiles);
    objectCodeFiles(emptyIdx) = {''};
    staleObjsIdx = isfile(objectCodeFiles);
    cellfun(@delete, objectCodeFiles(staleObjsIdx));
end

% Skip any object code files that don't exist (for example, mex doesn't
% leave object code files behind)
missingObjFileIdx = ~isfile(objectCodeFiles);

objChecksums = cell(size(objectCodeFiles));
objChecksums(missingObjFileIdx) = {checksumForMissingObjFile};

notMissingObjFileIdx = find(~missingObjFileIdx);

for i=1:length(notMissingObjFileIdx)
    idx = notMissingObjFileIdx(i);
    objChecksums{idx} = builtin('_coder_compile_getMd5', objectCodeFiles{idx});
end

objChecksumData = struct('File', objectCodeFiles, 'Value', objChecksums, ...
    'FileExists', num2cell(isfile(objectCodeFiles)));

setObjectFileChecksum(lCompileInfo, objChecksumData);


function finalTargetWithPath = i_getFinalTargetFullPath(finalTarget, outputDir, ...
    buildInfo, lBuildFolderFullPath)

if contains(outputDir, '$(RELATIVE_PATH_TO_ANCHOR)')
    outputDir = strrep(outputDir, '$(RELATIVE_PATH_TO_ANCHOR)', buildInfo.Settings.LocalAnchorDir);
end

if contains(outputDir, '$(START_DIR)')
    outputDir = strrep(outputDir, '$(START_DIR)', buildInfo.Settings.LocalAnchorDir);
end

if coder.make.internal.isRelativePath(outputDir)
    outputDir = fullfile(lBuildFolderFullPath, outputDir);
    outputDir = RTW.reduceRelativePath(outputDir);
end
finalTargetWithPath = fullfile(outputDir, finalTarget);



% Determine if all checksums match
function  allChecksumsMatch = i_allChecksumsMatch(lCompileInfo, lMakefileSelfDependency)

allChecksumsMatch = ...
    all(clientChecksumsMatch(lCompileInfo)) && ...
    configChecksumMatches(lCompileInfo, lMakefileSelfDependency) && ...
    sourceFileChecksumMatches(lCompileInfo) && ...
    managedLinkObjChecksumMatches(lCompileInfo) && ...
    customLinkObjChecksumMatches(lCompileInfo) && ...
    incPathsChecksumMatches(lCompileInfo) && ...
    definesChecksumMatches(lCompileInfo) && ...
    dependencyChecksumMatches(lCompileInfo) && ...
    getMakefileWasRunMatches(lCompileInfo);


function i_generateMacDebugSymbols(finalTargetWithPath, ...
        generateCodeOnly, lBuildConfiguration, ...
        buildVariant, toolchainInfo, lBuildLogger, ...
        lCustomToolchainOptions, ...
        buildInfo, ...
        lTMFProperties, ...
        lSuppressExe)

if ~generateCodeOnly && ...
        strcmp(buildVariant, 'STANDALONE_EXECUTABLE') && ...
        ~lSuppressExe && ...
        ~isempty(toolchainInfo) && ...
        strcmp(toolchainInfo.Platform, 'maci64') && ...
        toolchainInfo.SupportsBuildingMEXFuncs && ...
        strcmp(toolchainInfo.Alias, 'Apple-x')
    
    % Determine if debug build
    isDebugBuild = i_isDebugBuild(lTMFProperties, lBuildConfiguration, ...
                                  buildInfo, lCustomToolchainOptions, ...
                                  toolchainInfo);

    if isDebugBuild
        
    dsymtool = sprintf('dsymutil "%s" --flat', ...
        finalTargetWithPath);
    [sysStatus, sysResult] = coder.make.internal.system(dsymtool, lBuildLogger);

    if sysStatus ~= 0
            msg = message('RTW:buildProcess:FailedToGenerateMacDwarfSymbols', sysResult);
            error(msg.Identifier, '%s', string(msg));
    end
        
    end
end

function i_validateLinkOnlyObjectsExist...
        (buildInfo, buildVariant, generateCodeOnly, ...
         lToolchainInfo, lLegacyTargetLibSuffix)

% Check to see if the toolchain has opted out of the link only objs check g2177649
if ~isempty(lToolchainInfo) && isAttribute(lToolchainInfo, 'DisableLinkOnlyObjExistenceCheck')
   return; 
end

% Throw an error if any linkOnly objects are not found
linkObjs = [buildInfo.LinkObj(:); buildInfo.ModelRefs(:)];
if ~generateCodeOnly && ...
   ~isempty(linkObjs) && ...
   any(buildVariant==[coder.make.enum.BuildVariant.MEX_FILE ...
                      coder.make.enum.BuildVariant.STANDALONE_EXECUTABLE ...
                      coder.make.enum.BuildVariant.RAPID_ACCELERATOR])

    lStaticLibSuffix = coder.make.internal.getStaticLibSuffix ...
        (lToolchainInfo, lLegacyTargetLibSuffix);

    % Get the paths to LinkOnly objects
    libLinkOnlyIdx = [linkObjs.LinkOnly];
    linkOnlyObjs = linkObjs(libLinkOnlyIdx);
    libNames = {linkOnlyObjs.Name};
    
    % Add library suffix if missing
    libNames = coder.make.internal.addLibExtIfMissing...
        (libNames, lStaticLibSuffix); 
    
    paths = fullfile({linkOnlyObjs.Path}, libNames);
    % Resolve tokens in the paths
    paths = coder.make.internal.expandPathTokens(buildInfo, paths);
    
    % If any paths have unresolved tokens, e.g. some support packages use
    % custom tokens in their makefiles, filter them out
    pathsWithoutTokensIdx = cellfun(@isempty, regexp(paths, '\$\(.*\)', 'match', 'once'));
    paths = paths(pathsWithoutTokensIdx);
    
    nonExistentLibs = ~isfile(paths);
    if any(nonExistentLibs)
        msg = message('coder_compile:CoderCompile:linkOnlyObjsNotFound', strjoin(paths(nonExistentLibs), newline));
        error(msg.Identifier, '%s', string(msg));
    end
end

function [lDownload,makeArgs] = i_getDownload(makeArgs, lTMFProperties, ...
    toksToReplaceExprs, replacementValues)

[lDownload, makeArgs] = ParseMakeArgs(makeArgs,'DOWNLOAD');
% if the makeArgs don't have DOWNLOAD, check the tmf for an override
if isempty(lDownload)
    lDownload = lTMFProperties.TMF_DOWNLOAD;
    % Replace tokens in the download string
    lDownload = regexprep(lDownload, toksToReplaceExprs, replacementValues);
    if isempty(lDownload)
        lDownload=false;
    else
        lDownload=strcmpi(lDownload,'yes') || strcmp(lDownload,'1');
    end
end


function isDebugBuild = i_isDebugBuild...
        (lTMFProperties, lBuildConfiguration, buildInfo, lCustomToolchainOptions, ...
        toolchainInfo)
    if ~isempty(lTMFProperties)
        [~, isDebugBuild] = findBuildArg(buildInfo, 'DEBUG_BUILD');
        isDebugBuild = strcmp(isDebugBuild, '1');
    else
        % Check for custom toolchain options including debug flag
        if ~isempty(lCustomToolchainOptions)
            debugFlags = getDirective...
                (getBuildTool(toolchainInfo, 'C Compiler'), 'Debug');
            idxForCCompiler = find(strcmp(lCustomToolchainOptions, 'C Compiler'));
            cCompilerOpts = lCustomToolchainOptions{idxForCCompiler+1};
            customOptionsWithDebug = contains(cCompilerOpts, debugFlags);
        else
            customOptionsWithDebug = false;
        end
        isDebugBuild = strcmp(lBuildConfiguration, 'Debug') || customOptionsWithDebug;
    end
    


% Identify the mex source files and remove them from buildInfo 
function lMexSourceFiles = i_extractMexSourceFiles(buildInfo)
    
lMexSourceFiles = getSourceFiles...
    (buildInfo, false, false, ...
    coder.make.internal.BuildInfoGroup.MexSourceGroup);

% Remove the mex source files from buildInfo
removeSourceFiles(buildInfo, lMexSourceFiles);

