classdef ApplicationBuildManager < coder.connectivity.buildservices.IApplicationBuildStrategy
    %APPLICATIONBUILDMANAGER class to manage the build of the PIL
    %application for the target. It allows customization by way of a
    %specified coder.connectivity.buildservices.IBuildStrategy. If no
    %strategy is given the default strategy is used.
    %
    %Strategy customization is only here to support legacy connectivity
    %configurations. The default strategy is strongly recommended.

%   Copyright 2020 The MathWorks, Inc.
    
    properties (Access=private)
        BuildStrategy coder.connectivity.buildservices.IApplicationBuildStrategy = rtw.pil.ApplicationBuildManager.empty();
        
        TargetServices
        PILBuildOptions rtw.pil.SILPILBuildOptions
    end
    
    methods (Static)
        
        % Static helper function to allow reconstruction of the generated
        % executable path.
        function applicationPath = calculateApplicationExecutablePath(componentArgs, targetServices)
              
            options = rtw.pil.SILPILBuildOptions();
            options.ComponentArgs = componentArgs;
            buildManager = rtw.pil.ApplicationBuildManager();
            buildStrategy = rtw.pil.getCustomBuildStrategy(buildManager, targetServices);

            % Build the application executable
            applicationPath = buildManager.calculateApplicationPath(options, targetServices, buildStrategy(:));
        end
    end
    
    methods
                
        function this = ApplicationBuildManager(buildStrategy)
            
            if (nargin > 0)
                this.BuildStrategy = buildStrategy;
            end
        end
        
        function setTargetServices(this, targetServices)
            arguments
                this
                targetServices (1,1) coder.connectivity.targetservices.ITargetServices
            end
            
            this.TargetServices = targetServices;
        end
        
        function setPILBuildOptions(this, pilBuildOptions)
            arguments
                this
                pilBuildOptions (1,1) rtw.pil.SILPILBuildOptions
            end
            
            this.PILBuildOptions = pilBuildOptions;
        end
        
        % Build the target application using the target application build
        % info and application and target options. Optionally the build can
        % be customized by using a custom build strategy. 
        %
        % The code in this file was once found in rtw.connectivity.Builder
        % and rtw.connectivity.MakefileBuilder.
        function applicationPath = build(this, targetApplicationBuildInfo, pilBuildOptions, targetServices, buildStrategy)
            arguments
                this
                targetApplicationBuildInfo (1,1) RTW.BuildInfo
                pilBuildOptions (1,1) rtw.pil.SILPILBuildOptions
                targetServices (1,1) coder.connectivity.targetservices.ITargetServices
                buildStrategy coder.connectivity.buildservices.IApplicationBuildStrategy
            end
            
            if nargin < 5 || isempty(buildStrategy)
                buildStrategy = this;
            end 
            
            this.BuildStrategy = buildStrategy;            
            this.BuildStrategy.setTargetServices(targetServices);
            this.BuildStrategy.setPILBuildOptions(pilBuildOptions);
            
            % Dispatch build hooks attached to the model
            %
            % This gives build hooks the opportunity to update 
            % TargetApplicationFramework before it is processed.
            componentArgs = pilBuildOptions.ComponentArgs;
            clientInterface = componentArgs.getClientInterface();
            
            % Ensure that any build hooks and associated build environment variables are
            % cleaned up even in the event of an error
            buildHooksCleanup = onCleanup(@() componentArgs.setBuildHookHandles([]));            

            clientInterface.dispatchBuildHook(componentArgs, ...
                                              'before_buildApplication', ...
                                              targetApplicationBuildInfo);                                            
            % get the component buildInfo
            componentBuildInfo = this.getBuildInfoUsedToBuildComponent();
            
            % determine if componentBuildInfo has been updated since the
            % last full application build (when applicationBuildInfo was 
            % last written)
            %
            % assume out of date            
            % we always delete the application buildInfo.mat file for new
            % codegen, so if it exists, it means this is an up-to-date
            % codegen.
            componentBuildInfoUpdated = true;            
            applicationBuildInfoPath = this.getApplicationBuildInfoPath();
            if exist(applicationBuildInfoPath, 'file')                
                componentBuildInfoUpdated = false;
            end
            
            % get the component integration build info
            componentIntegrationBuildInfo = this.BuildStrategy.processComponentIntegrationBuildInfo(componentBuildInfo);
            
            % determine if targetApplicationFrameworkBuildInfo 
            % has been updated since it was last written to builderInfo         
            pilApplicationVersionString = rtw.pil.getPILVersion();            
            prevTargetApplicationBuildInfo = [];
            % load stored targetApplicationFrameworkBuildInfo
            builderInfo = this.loadApplicationBuilderInfo();
            if ~isempty(builderInfo)
               % check builderInfo version is up to date
               % when changing the content of builderInfo, update the local
               % offset of pilApplicationVersionString - see getPILVersion
               if strcmp(pilApplicationVersionString, builderInfo.pilApplicationVersionString)
                   prevTargetApplicationBuildInfo = builderInfo.targetApplicationBuildInfo;                                      
               end                                
            end
            
            % combine componentIntegrationBuildInfo &
            % targetApplicationFrameworkBuildInfo to form
            % "applicationBuildInfo" buildInfo
            [buildInfo, appFrameworkUpdated] = this.createApplicationBuildInfo(componentIntegrationBuildInfo, ...
                                                                               targetApplicationBuildInfo, ...
                                                                               prevTargetApplicationBuildInfo);                                                                           
                                                                           
            applicationBuildInfoUpdated = componentBuildInfoUpdated || appFrameworkUpdated;                        
            if applicationBuildInfoUpdated   
                % create a second application BuildInfo to be leveraged by
                % the CodeMetrics report; this time it is based on the
                % original component BuildInfo.
                %
                % This BuildInfo will contain entries for all of the
                % original component source code (relative to the component
                % build dir) + the files (with absolute paths) added by the
                % targetApplicationFrameworkBuildInfo.
                [codeMetricsAppBuildInfo, appFrameworkUpdatedTmp] = this.createApplicationBuildInfo(this.getCodeMetricsAppBuildInfo, ...
                                                                          targetApplicationBuildInfo, ...
                                                                          prevTargetApplicationBuildInfo);
                assert(isequal(appFrameworkUpdated, appFrameworkUpdatedTmp), ...
                       'appFrameworkUpdated must be consistent across multiple calls.');
                % save applicationBuildInfo
                save(applicationBuildInfoPath, 'buildInfo');                 
                % save builderInfo 
                save(this.getApplicationBuilderInfoPath(), ...
                     'pilApplicationVersionString', ...
                     'targetApplicationBuildInfo', ...
                     'codeMetricsAppBuildInfo'); 
                % full build
                this.BuildStrategy.doBuild(buildInfo);
            else
                % partial (update) build
                this.BuildStrategy.doUpdateBuild(buildInfo);
            end
            
           applicationPath = this.BuildStrategy.getApplicationPath();
        end
                
        function applicationPath = calculateApplicationPath(this, pilBuildOptions, targetServices, buildStrategy)
            arguments
                this
                pilBuildOptions (1,1) rtw.pil.SILPILBuildOptions
                targetServices (1,1) coder.connectivity.targetservices.ITargetServices
                buildStrategy coder.connectivity.buildservices.IApplicationBuildStrategy
            end
            
            if nargin < 4
                buildStrategy = this;
            end 
            
            this.BuildStrategy = buildStrategy;            
            this.BuildStrategy.setTargetServices(targetServices);
            this.BuildStrategy.setPILBuildOptions(pilBuildOptions);
            
            applicationPath = this.BuildStrategy.getApplicationPath();
        end
    end
    
    methods
        
        function [buildInfo,...
                  matFileContents,...
                  componentBuildInfoPath] = getBuildInfoUsedToBuildComponent(this)            
            % this method returns the buildInfo that was used to build
            % the algorithm component. For SIL+PWS, this returns the
            % buildInfo in the sil/hostobj folder.
            
            componentArgs = this.PILBuildOptions.ComponentArgs;
            buildInfoPath = componentArgs.getComponentCodePath();
            componentBuildInfoPath = fullfile(buildInfoPath, 'buildInfo.mat');
            [buildInfo, matFileContents] = this.getComponentBuildInfo(componentBuildInfoPath);
            
            % Ensure anchor folder is up to date (if models were built in
            % parallel, it might still point at the worker's temporary
            % folder). This update is only required until g1717707 is
            % addressed.
            [~, relativePathToAnchor] = findBuildArg(buildInfo, 'RELATIVE_PATH_TO_ANCHOR');
            startDir = fullfile(buildInfoPath, relativePathToAnchor);
            startDir = RTW.reduceRelativePath(startDir);
            buildInfo.Settings.LocalAnchorDir = startDir;            
        end  
        
       
       % Get the component buildInfo object
       function [componentBuildInfo, matFileContents] = getComponentBuildInfo(this, buildInfoPath)
           matFileContents = this.BuildStrategy.getComponentBuildInfoMATFileContents(buildInfoPath);
           componentBuildInfo = matFileContents.buildInfo;                                       
       end
       
        function applicationBuildInfoPath = getApplicationBuildInfoPath(this)
            applicationBuildInfoPath = fullfile(this.PILBuildOptions.ComponentArgs.getApplicationCodePath(), 'buildInfo.mat');
        end
        
        function builderInfo = loadApplicationBuilderInfo(this)
            builderInfoPath = this.getApplicationBuilderInfoPath();                                    
            if exist(builderInfoPath, 'file')
                % load info
                builderInfo = load(builderInfoPath);
            else
                % return empty
                builderInfo = [];
            end            
        end
        
        % define the location of builderInfo.mat
        function builderInfoPath = getApplicationBuilderInfoPath(this)
            builderInfoPath = fullfile(this.PILBuildOptions.ComponentArgs.getApplicationCodePath(), 'builderInfo.mat');
        end
                         
       function codeMetricsBuildInfoPath = getCodeMetricsAppBuildInfoPath(this)
           narginchk(1, 1);
           % default implementation
           buildInfoPath = this.PILBuildOptions.ComponentArgs.getComponentCodePathForReport;
           codeMetricsBuildInfoPath = fullfile(buildInfoPath, 'buildInfo.mat');
       end
       
       function [componentBuildInfo, matFileContents] = getCodeMetricsAppBuildInfo(this)
           codeMetricsAppBuildInfoPath = this.getCodeMetricsAppBuildInfoPath();
           matFileContents = this.BuildStrategy.getComponentBuildInfoMATFileContents(codeMetricsAppBuildInfoPath);
           componentBuildInfo = matFileContents.buildInfo;
       end
        
        function matFileContents = getComponentBuildInfoMATFileContents(this, componentBuildInfoPath)
           if ~exist(componentBuildInfoPath, 'file')
               rtw.connectivity.ProductInfo.error('target', 'MissingComponentBuildInfo', ...
                   componentBuildInfoPath, ...
                   this.PILBuildOptions.ComponentArgs.getComponentPath());
           else
               [~, matFileContents] = targets_load_buildinfo(componentBuildInfoPath);
           end
        end % BUILDER
        
        function buildInfo = processComponentIntegrationBuildInfo(this, buildInfo)
            %
            % get some build args
            %
            xilCompInfo = this.PILBuildOptions.XilCompInfo;
            componentArgs = this.PILBuildOptions.ComponentArgs;
            
            baModelRefTargetType   = buildInfo.findKeyValuePair('BuildArg','MODELREF_TARGET_TYPE');
            baRelativePathToAnchor = buildInfo.findKeyValuePair('BuildArg','RELATIVE_PATH_TO_ANCHOR');
            baModelLib             = buildInfo.findKeyValuePair('BuildArg','MODELLIB');
            % Update shared bin and lib path
            relativePathToObjDir = this.BuildStrategy.getRelativePathFromApplicationToComponent();

            % Update RELATIVE_PATH_TO_ANCHOR
            baRelativePathToAnchor.Value = RTW.reduceRelativePath(this.fullFile(relativePathToObjDir, baRelativePathToAnchor.Value));

            % Update MODELREF_TARGET_TYPE
            baModelRefTargetTypeOrigValue = baModelRefTargetType.Value;
            baModelRefTargetType.Value = 'NONE';

            % Re-anchor model reference link object paths and make the link
            % objects link-only
            for i=1:length(buildInfo.ModelRefs)
                modelRefLib = buildInfo.ModelRefs(i);
                this.updateModelRef(modelRefLib, relativePathToObjDir, ...
                     this.getFileSeparator());
            end

            % Is the component being linked into an executable being taken
            % from a top-model build or a model reference build
            topmodelbuild = strcmp( baModelRefTargetTypeOrigValue, 'NONE');

            % Link objects related to the top-most model in the XIL hierarchy 
            % must belong to the model reference library group; this ensures
            % correct ordering of the link modules in the linker command line.
            modelObjGroup = 'MDLREF_LIB';

            if topmodelbuild
                % SIL/PIL application does not need to link against certain
                % files, so we filter them from buildInfo
                % determine language extension
                targetLang = componentArgs.getParam('TargetLang');
                if strcmp(targetLang, 'C++') || strcmp(targetLang, 'C++ (Encapsulated)')
                    langExts = {'cpp'};
                else
                    langExts = {'c'};
                end
                if strcmpi(componentArgs.getParam('GenerateGPUCode'), 'CUDA')
                    langExts = [langExts, 'cu'];
                end

                % Update buildInfo now that all the arguments are ready. Note that 
                % For SIL and PWS case, we already called this updateBuildInfoToSkipFiles 
                % and updateBuildInfoToSkipFilesSilAndPWS during the build process
                % before serializing buildInfo for the model component
                rtw.pil.BuildInfoHelpers.updateBuildInfoToSkipFiles(buildInfo, langExts);

                topModelPreBuiltSrcs = buildInfo.getSourceFiles(false, false);

                appDir = componentArgs.getApplicationCodePath;
                pathToObjDir = RTW.reduceRelativePath(fullfile(appDir, relativePathToObjDir));
                topModelPreBuiltObjs = coder.internal.getMatchingObjectFiles...
                    (xilCompInfo.ToolchainInfo, topModelPreBuiltSrcs, pathToObjDir);

                % Add pre-built top model object files as model reference link objects
                this.addModelLinkObjs(buildInfo, relativePathToObjDir, topModelPreBuiltObjs, modelObjGroup);

                % MODELLIB not used for integrating a top-model component
                baModelLib.Value = '';

                % avoid issues with Generate ERT S-Function
                baGenerateERTSfun = buildInfo.findKeyValuePair('BuildArg','GENERATE_ERT_S_FUNCTION');
                baGenerateERTSfun.Value = '0';

            else
                % Add relative path for MODELLIB, modifying the extension
                % if necessary
                clientInterface = componentArgs.getClientInterface;
                modelLib = clientInterface.updateModelLibExt(baModelLib.Value);
                baModelLib.Value = this.fullFile(relativePathToObjDir, modelLib);

                % Update BuildInfo
                precompiled = true;
                linkOnly = true;
                modelObjPriorities = 0;
                lApplicationCodePath = componentArgs.getApplicationCodePath;
                modelLibAbsolutePath = this.fullFile(lApplicationCodePath, relativePathToObjDir);
                modelLibAbsolutePath = strrep(modelLibAbsolutePath, ...
                    buildInfo.Settings.LocalAnchorDir, '$(START_DIR)');
                buildInfo.addLinkObjects(modelLib, modelLibAbsolutePath,...
                    modelObjPriorities, precompiled, linkOnly, modelObjGroup);

            end

            % Process all non-link-only libraries
            libs = buildInfo.getLinkObjects;
            for i=1:length(libs)
                if ~libs(i).LinkOnly
                    % There are two types of non-LinkOnly library:
                    %   precompiled and non-precompiled.
                    %
                    % Precompiled:
                    %
                    % The behavior of precompiled libraries
                    % is deeply embedded inside the TMF's.
                    %
                    % Some of our products use precompiled libraries
                    % (e.g. phys-mod) so we must support them without any
                    % limitations.
                    %
                    % Therefore, MakefileBuilder will allow
                    % the TMF to process these libraries in the usual way.
                    % Typically, this will result in linking the PIL
                    % application against a precompiled library, as
                    % desired, however, the TMF may contain logic (e.g.
                    % ifeq ($(OPT_OPTS),$(DEFAULT_OPT_OPTS)) mechanism) that
                    % under some circumstances rebuilds the library inside
                    % the RTW build directory and again inside the PIL
                    % build directory!
                    %
                    % Non-precompiled:
                    %
                    % Standalone PIL:
                    %
                    % Non-precompiled libraries are built inside the RTW
                    % build directory.   We want to avoid rebuilding them
                    % again inside the PIL build directory.   Therefore,
                    % MakefileBuilder will mark them as LinkOnly to avoid
                    % the rules in the TMF.
                    %
                    % Model block PIL:
                    %
                    % There is no unique non-precompiled
                    % lib to link against.   To avoid confusion, we don't
                    % build it in the PIL build dir; instead we error out.
                    %
                    if ~libs(i).Precompiled
                        if topmodelbuild
                            % Only targets using TargetLibSuffix are supported.
                            %
                            % Without TargetLibSuffix, the lib suffix info is
                            % hardcoded directly into the TMF files, as is the
                            % case for our SIL host-based TMF's.
                            %
                            targetLibSuffix = buildInfo.TargetLibSuffix;
                            if isempty(targetLibSuffix)
                                % error id is contained within error message which
                                % allows us to be more specific than the generic
                                % PILImplementationError that masks the true error
                                errorId = 'MakefileBuilderNoTargetLibSuffix';
                                rtw.connectivity.ProductInfo.error('target', ...
                                    errorId, ...
                                    errorId, ...
                                    buildInfo.ModelName, ...
                                    libs(i).Name);
                            end
                            % transform to LinkOnly in order to avoid any
                            % possibility of a PIL rebuild
                            libs(i).LinkOnly = true;
                            % apply the TargetLibSuffix
                            libs(i).Name = this.fullFile([libs(i).Name targetLibSuffix]);
                            % Adjust the BuildInfo Path appropriately.
                            libs(i).Path = relativePathToObjDir;
                        else                                                      
                            clientInterface = componentArgs.getClientInterface;
                            if ciIsMATLABCodeGen(clientInterface)
                                errorId = 'MakefileBuilderMATLABCoderNoNonPrecompLib';
                                rtw.connectivity.ProductInfo.error('target', ...
                                    errorId, ...
                                    buildInfo.ModelName, ...
                                    libs(i).Name);
                            else
                                % error id is contained within error message which
                                % allows us to be more specific than the generic
                                % PILImplementationError that masks the true error
                                errorId = 'MakefileBuilderModelRefNoNonPrecompLib';
                                rtw.connectivity.ProductInfo.error('target', ...
                                    errorId, ...
                                    errorId, ...
                                    buildInfo.ModelName, ...
                                    libs(i).Name);
                            end
                            
                        end
                    end
                end
            end

            % remove all source files to make sure that we aren't
            % unknowingly relying on compile stage doing any
            % processing of them, e.g. srcs in the Sfcn group
            buildInfo.Src.Files = [];
            buildInfo.Src.Paths = [];
            % Update all relative include paths
            includePaths = buildInfo.Inc.Paths;
            for i=1:length(includePaths)
                if ~isempty(regexp(includePaths(i).Value, '^\.', 'once'))
                    % match "." or ".." at the start of a path
                    includePaths(i).Value = this.fullFile('..', includePaths(i).Value);
                end
            end

            % also skip libs and link flags that are in the "skipForSil"
            % group
            if this.PILBuildOptions.IsSILAndPWS
                rtw.pil.BuildInfoHelpers.updateBuildInfoToSkipLinkLibsAndOptions(buildInfo);
            end
        end % BUILDER
        
        function [applicationBuildInfo, appFrameworkUpdated] = createApplicationBuildInfo(this, ...
                                                                                 componentIntegrationBuildInfo, ...
                                                                                 targetApplicationFrameworkBuildInfo, ...
                                                                                 prevTargetApplicationFrameworkBuildInfo)
           % combine componentIntegrationBuildInfo &
           % targetApplicationFrameworkBuildInfo to form
           % applicationBuildInfo
           %
           % start with componentIntegrationBuildInfo and just add required
           % parts of targetApplicationFrameworkBuildInfo
           %
           % compare targetApplicationFrameworkBuildInfo with previous version 
           % as we process it
           applicationBuildInfo = componentIntegrationBuildInfo;    
           % resolve as much path information as possible
           targetApplicationFrameworkBuildInfo.updateFilePathsAndExtensions;
           %
           src = targetApplicationFrameworkBuildInfo.getSourceFiles(true, true);
           if isempty(prevTargetApplicationFrameworkBuildInfo)
              doCheck = false;
              appFrameworkUpdated = true;              
           else
              doCheck = true; 
              appFrameworkUpdated = false;              
           end           
           if doCheck && ~appFrameworkUpdated && ...
                   ~isequal(src, prevTargetApplicationFrameworkBuildInfo.getSourceFiles(true, true))
              appFrameworkUpdated = true;               
           end           
           for i=1:length(src)
               aSrc = src{i};
               this.checkPath(aSrc);               
               [p, f, e] = fileparts(aSrc);                                 
               % always add to Sfcn group (supports makefile build)
               applicationBuildInfo.addSourceFiles([f e], p, 'Sfcn');
           end
           % add framework source paths
           srcPaths = targetApplicationFrameworkBuildInfo.getSourcePaths(true);
           if doCheck && ~appFrameworkUpdated && ...
                   ~isequal(srcPaths, prevTargetApplicationFrameworkBuildInfo.getSourcePaths(true))
              appFrameworkUpdated = true;               
           end                      
           for i=1:length(srcPaths)
               srcPath = srcPaths{i};
               this.checkPath(srcPath);                              
               applicationBuildInfo.addSourcePaths(srcPath);
           end
           % add framework include files
           inc = targetApplicationFrameworkBuildInfo.getIncludeFiles(true, true);
           if doCheck && ~appFrameworkUpdated && ...
                   ~isequal(inc, prevTargetApplicationFrameworkBuildInfo.getIncludeFiles(true, true))
              appFrameworkUpdated = true;               
           end                      
           for i=1:length(inc)
               aInc = inc{i};
               this.checkPath(aInc);                              
               [p, f, e] = fileparts(aInc);                 
               applicationBuildInfo.addIncludeFiles([f e], p);
           end
           % add framework libraries
           libs = targetApplicationFrameworkBuildInfo.getLinkObjects;
           if doCheck && ~appFrameworkUpdated
              prevLibs = prevTargetApplicationFrameworkBuildInfo.getLinkObjects;
              if ~isequal(length(libs), length(prevLibs))
                 appFrameworkUpdated = true;               
              end
           end                      
           for i=1:length(libs)
               lib = libs(i);      
               % explicitly replace any tokens in lib path via formatPaths
               this.checkPath(fullfile(lib.formatPaths(lib.Path), lib.Name));                                                            
               if doCheck && ~appFrameworkUpdated
                   prevLib = prevLibs(i);
                   if ~isequal(lib.Name, prevLib.Name) || ...
                           ~isequal(lib.Path, prevLib.Path) || ...
                           ~isequal(lib.LinkOnly, prevLib.LinkOnly)
                       appFrameworkUpdated = true;
                   end
               end
               l = applicationBuildInfo.addLibraries(lib.Name, lib.Path);
               % respect LinkOnly flag
               l.LinkOnly = lib.LinkOnly;
           end
           % add framework includes
           replaceMATLABROOT = true;
           paths = targetApplicationFrameworkBuildInfo.getIncludePaths...
               (replaceMATLABROOT);           
           if doCheck && ~appFrameworkUpdated
               prevPaths = prevTargetApplicationFrameworkBuildInfo.getIncludePaths...
                   (replaceMATLABROOT);               
               if ~isequal(length(paths), length(prevPaths))
                   appFrameworkUpdated = true;
               end
           end
           for i=1:length(paths)
              p = paths{i};
              this.checkPath(p);                                                         
              if doCheck && ~appFrameworkUpdated
                  prevPath = prevPaths{i};
                  if ~isequal(p, prevPath) 
                      appFrameworkUpdated = true;
                  end
              end              
              applicationBuildInfo.addIncludePaths(p);                  
           end                         
           % add framework compile flags
           %           
           % preserve the OPTS group
           optsGroup = 'OPTS';
           optsCompileFlags = ...
               targetApplicationFrameworkBuildInfo.getCompileFlags(optsGroup);           
           if doCheck && ~appFrameworkUpdated && ...
                   ~isequal(optsCompileFlags, prevTargetApplicationFrameworkBuildInfo.getCompileFlags(optsGroup))
              appFrameworkUpdated = true;               
           end              
           applicationBuildInfo.addCompileFlags(optsCompileFlags, optsGroup);      
           % preserve the OPT_OPTS group
           optOptsGroup = 'OPT_OPTS';
           optOptsCompileFlags = targetApplicationFrameworkBuildInfo.getCompileFlags(optOptsGroup);
           if doCheck && ~appFrameworkUpdated && ...
                   ~isequal(optOptsCompileFlags, prevTargetApplicationFrameworkBuildInfo.getCompileFlags(optOptsGroup))
              appFrameworkUpdated = true;               
           end 
           applicationBuildInfo.addCompileFlags(optOptsCompileFlags, optOptsGroup);
           % process all other compile flags (exclude OPTS and OPT_OPTS)
           otherCompileFlags = targetApplicationFrameworkBuildInfo.getCompileFlags({}, ...
                                                             {optsGroup, optOptsGroup});
           if doCheck && ~appFrameworkUpdated && ...
                   ~isequal(otherCompileFlags, prevTargetApplicationFrameworkBuildInfo.getCompileFlags({}, ...
                                                             {optsGroup, optOptsGroup}))
              appFrameworkUpdated = true;               
           end                                                                                                                  
           applicationBuildInfo.addCompileFlags(otherCompileFlags);           
           
           % add framework defines
           %           
           % preserve the OPTS group
           optsGroup = 'OPTS';
           optsDefines = targetApplicationFrameworkBuildInfo.getDefines(optsGroup);           
           if doCheck && ~appFrameworkUpdated && ...
                   ~isequal(optsDefines, prevTargetApplicationFrameworkBuildInfo.getDefines(optsGroup))
              appFrameworkUpdated = true;               
           end                
           applicationBuildInfo.addDefines(optsDefines, optsGroup);           
           % preserve the OPT_OPTS group
           optOptsGroup = 'OPT_OPTS';
           optOptsDefines = targetApplicationFrameworkBuildInfo.getDefines(optOptsGroup);           
           if doCheck && ~appFrameworkUpdated && ...
                   ~isequal(optOptsDefines, prevTargetApplicationFrameworkBuildInfo.getDefines(optOptsGroup))
              appFrameworkUpdated = true;               
           end                                     
           applicationBuildInfo.addDefines(optOptsDefines, optOptsGroup);
           % process all other defines (exclude OPTS and OPT_OPTS)
           otherDefines = targetApplicationFrameworkBuildInfo.getDefines({}, ...
                                                                         {optsGroup, optOptsGroup});
           if doCheck && ~appFrameworkUpdated && ...
                   ~isequal(otherDefines, prevTargetApplicationFrameworkBuildInfo.getDefines({}, ...
                                                                         {optsGroup, optOptsGroup}))
              appFrameworkUpdated = true;               
           end 
           applicationBuildInfo.addDefines(otherDefines);           
           % add framework link flags
           %
           % preserve the OPTS group
           optsLinkFlags = targetApplicationFrameworkBuildInfo.getLinkFlags(optsGroup);
           if doCheck && ~appFrameworkUpdated && ...
                   ~isequal(optsLinkFlags, prevTargetApplicationFrameworkBuildInfo.getLinkFlags(optsGroup))
              appFrameworkUpdated = true;               
           end                                  
           applicationBuildInfo.addLinkFlags(optsLinkFlags, optsGroup);          
           % preserve the OPT_OPTS group
           optOptsLinkFlags = targetApplicationFrameworkBuildInfo.getLinkFlags(optOptsGroup);
           if doCheck && ~appFrameworkUpdated && ...
                   ~isequal(optOptsLinkFlags, prevTargetApplicationFrameworkBuildInfo.getLinkFlags(optOptsGroup))
              appFrameworkUpdated = true;               
           end   
           applicationBuildInfo.addLinkFlags(optOptsLinkFlags, optOptsGroup);          
           % process all other defines (exclude OPTS and OPT_OPTS)
           otherLinkFlags = targetApplicationFrameworkBuildInfo.getLinkFlags({}, ...
                                                                             {optsGroup, optOptsGroup});
           if doCheck && ~appFrameworkUpdated && ...
                   ~isequal(otherLinkFlags, prevTargetApplicationFrameworkBuildInfo.getLinkFlags({}, ...
                                                                             {optsGroup, optOptsGroup}))
              appFrameworkUpdated = true;               
           end   
           applicationBuildInfo.addLinkFlags(otherLinkFlags);
        end % BUILDER
                
        function doBuild(this, buildInfo, varargin)
              
            pilBuildOptions = this.PILBuildOptions;
            xilCompInfo = pilBuildOptions.XilCompInfo;
            componentArgs = pilBuildOptions.ComponentArgs;

            % set build arg for the type of target to build
            buildInfo.addBuildArgs('MAKEFILEBUILDER_TGT', '1');

            % build in app dir (location of buildInfo)
            appDir = componentArgs.getApplicationCodePath;
            startDir = cd(appDir);
            restoreStartDir = onCleanup(@()cd(startDir));

            % find template makefile tokens
            toks    =  get(buildInfo.Tokens,'Key');
            tokVals =  get(buildInfo.Tokens,'Value');

            % create an rtw_proj.tmw if required
            this.createDummyRtwProjFileIfNotExist('.');

            % Dispatch any build hooks attached to the model
            clientInterface = componentArgs.getClientInterface;
            [buildHookEnvVars, buildHookPathPrepends] = clientInterface.dispatchBuildHook...
                (componentArgs, 'before_makefilebuilder_make');

            %MATLAB Coder adds _rtw to makefile name so we
            %need to get the proper name rather than rely on default
            %behaviour.
            makefileName = tokVals{strcmp(toks,'|>MAKEFILE_NAME<|')};
            
            % By default, the executable will be generated in $(START_DIR), however, for SIL or PIL it
            % executable must be generated in the applicationFolder, i.e. the same folder as the
            % makefile. Use setOutputFolder to specify this non-default output folder.
            buildInfo.setOutputFolder('.');
            
            % Target for SIL or PIL can never be 'SIM'
            isSIMTargetType = false;  

            % Library suffix for mex compiler: non-empty only if current compiler 
            % supports mex and will be one of unix, lcc, vcx64, mingw64
            if isempty(xilCompInfo.XilMexCompInfo)
                lMexCompilerLibSuffix = '';
            else                
                lMexCompilerLibSuffix = xilCompInfo.XilMexCompInfo.toolChain;
            end
            
            % Pre-compiled library suffix
            stf = componentArgs.getParam('SystemTargetFile');
            lPrecompTargetLibSuffix = coder.internal.getPrecompiledLibSuffix...
                (pilBuildOptions.IsPurelyIntegerCode, xilCompInfo.ToolchainInfo, ...
                stf, isSIMTargetType, ...
                pilBuildOptions.LibSuffixFromConfigSet, lMexCompilerLibSuffix);
            
            if pilBuildOptions.IsSILAndPWS
                lSysTargetFile = '';
                legacyLibSuffix = '';
            else
                % System target file may be needed to determine the correct make command wrap function
                lSysTargetFile = stf;
                
                % Support for user specified TargetLibSuffix
                legacyLibSuffix = pilBuildOptions.LibSuffixFromConfigSet;
            end
            
            % GPU Coder special case
            if ~isempty(xilCompInfo.ToolchainInfo) && ...
                    contains(xilCompInfo.ToolchainInfo.Name, 'nvidia', 'IgnoreCase',true)
                % TODO(g2174045): Remove this interim workaround for GPU Coder
                lCheckStatusOnly = true;
            else
                lCheckStatusOnly = false;
            end
            
            if pilBuildOptions.ForceDebuggingEnabled
                % Append flags to ensure debug symbols will be generated
                [lBuildConfiguration, lCustomToolchainOptions] = ...
                    coder.internal.overrideBuildConfigAndOptionsForDebug...
                    (xilCompInfo.ToolchainOrTMF, xilCompInfo.BuildConfiguration, ...
                     xilCompInfo.CustomToolchainOptions, buildInfo);
            else
                lBuildConfiguration = xilCompInfo.BuildConfiguration;
                lCustomToolchainOptions = xilCompInfo.CustomToolchainOptions;
            end
            
            % Match the model name specified inside buildInfo
            lBuildName = componentArgs.getComponentCodeName();

            % Set the build folder
            buildInfo.ComponentBuildFolder = appDir;

            % Compile
            codebuild(buildInfo, 'BuildMethod', xilCompInfo.ToolchainOrTMF, ...
                'AllComponents', false, ...
                'BuildName', lBuildName, ...
                'RTWVerbose', strcmp(componentArgs.getParam('RTWVerbose'), 'on'), ...
                'checkStatusOnly', lCheckStatusOnly, ...
                'buildHookEnvVars', buildHookEnvVars, ...
                'buildHookPathPrepends', buildHookPathPrepends, ...
                'makefileName', makefileName, ...
                'BuildVariant', 'STANDALONE_EXECUTABLE', ...
                'isMATLABCodeGen', ciIsMATLABCodeGen(clientInterface), ...
                'sysTargetFile', lSysTargetFile, ...
                'LegacyTargetLibSuffix', legacyLibSuffix, ...
                'PrecompiledLibSuffix', lPrecompTargetLibSuffix, ...
                'BuildConfiguration', lBuildConfiguration, ...
                'CustomToolchainOptions', lCustomToolchainOptions, ...
                'UseFileChecksums', false, ... % TODO(g2041768) use checksums instead of timestamps
                pilBuildOptions.CodeBuildParams{:});

            % Dispatch any build hooks attached to the model
            clientInterface.dispatchBuildHook(componentArgs, ...
                                              'after_makefilebuilder_make', ...
                                              buildInfo);
        end % BUILDER
        
        function doUpdateBuild(this, applicationBuildInfo)
          this.doBuild(applicationBuildInfo);
        end % BUILDER
        
        function exe = getApplicationPath(this)
            
           exe = this.BuildStrategy.getExecutableFileExtension();
           % add "." if required
           if ~isempty(exe)
              if exe(1) ~= '.'
                 exe = ['.' exe];
              end
           end
           
            % default location
            exe = fullfile(this.PILBuildOptions.ComponentArgs.getApplicationCodePath(), ...
                           [this.PILBuildOptions.ComponentArgs.getComponentCodeName() ...
                            exe]);
        end % BUILDER
        
        function componenetRelativePath = getRelativePathFromApplicationToComponent(this)            

            componentArgs = this.PILBuildOptions.ComponentArgs;
            
            lComponentCodePath = componentArgs.getComponentCodePath;
            lApplicationCodePath = componentArgs.getApplicationCodePath;

            lPathFromApplicationToComponent = '';

            while ~strncmp(lApplicationCodePath, lComponentCodePath, length(lApplicationCodePath))
                lApplicationCodePath = fileparts(lApplicationCodePath);
                lPathFromApplicationToComponent = fullfile(lPathFromApplicationToComponent, '..');
            end

            nMatch = length(lApplicationCodePath);

            lPathFromApplicationToComponent = ...
                fullfile(lPathFromApplicationToComponent, lComponentCodePath(nMatch+2:end));

            componenetRelativePath = strrep(lPathFromApplicationToComponent,...
                filesep, this.getFileSeparator());
        end % MAKEFILEBUILDER
        
        % Get the file separator for the target toolchain. 
        function fileSeparator = getFileSeparator(this)
            
            xilCompInfo = this.PILBuildOptions.XilCompInfo;
            tcInfo = xilCompInfo.ToolchainInfo;
            if isempty(tcInfo)
                % No ToolchainInfo for legacy TMF builds
                fileSeparator = filesep;
            else
                % For minGW the file separator should be '/', which is obtained
                % from tcInfo.getDirective('FileSeparator'). Instead of treating
                % minGW as a special case we replace the current filesep with
                % the one obtained from the toolchain for all the toolchains.
                fileSeparator = tcInfo.getDirective('FileSeparator');
                assert(~isempty(fileSeparator));
            end
        end % MAKEFILEBUILDER
        
        function fullFileResult = fullFile(this, varargin)
            fullFileResult = strrep(fullfile(varargin{:}), filesep, ...
                this.getFileSeparator());
        end % MAKEFILEBUILDER
        
        function exeExtension = getExecutableFileExtension(this)
            
            % By default try and find the executable file extension from
            % the toolchain info, if there is one. In the future we should
            % get this from the target service. This function is overridden
            % by rtw.connectivity.MakefileBuilder.
            xilCompInfo = this.PILBuildOptions.XilCompInfo;
            if isempty(xilCompInfo) || isempty(xilCompInfo.ToolchainInfo)
                error('We need the executable extension from the Toolchain');
            else
                linker = xilCompInfo.ToolchainInfo.getBuildTool('Linker');
                b = linker.FileExtensions;
                c = b.getValue('Executable');
                exeExtension = c.getValue();
            end
        end % MAKEFILEBUILDER
    end
    
    methods (Access=private)
        
        function createDummyRtwProjFileIfNotExist(~, buildFolder)
            % create an rtw_proj.tmw if required
            rtwProjFile=fullfile(buildFolder, 'rtw_proj.tmw');
            if ~exist(rtwProjFile,'file')
                fid = fopen(rtwProjFile,'w');
                fprintf(fid,'This file is generated by the Real-Time Workshop');
                fclose(fid);
            end
        end
                    
            
        function componentBuildInfoPath = getComponentBuildInfoPath(this)
           narginchk(1, 1);
           % default implementation                                
           buildInfoPath = this.PILBuildOptions.ComponentArgs.getComponentCodePath;
           componentBuildInfoPath = fullfile(buildInfoPath, 'buildInfo.mat');
        end   
        
        function addModelLinkObjs(~, buildInfo, relativePathToObjDir, topModelPreBuiltObjs, ...
                                  modelObjGroup)

            linkObjPaths = repmat({relativePathToObjDir}, size(topModelPreBuiltObjs));

            % Specify high priority (relative to default priority
            % 1000) for main model objects so that they appear ahead of
            % libraries
            % Note that even though we specify a high priority for adding
            % the main model objs, they will still not appear correctly on
            % the link line (they will still appear after the model ref
            % libs). Geck g886533 will sort this issue out. For now, we are
            % adding the main model objs to a special group
            % PREBUILT_OBJECTS and have some code in BTI to push files in
            % this group higher on the link line.
            modelObjPriorities = 0;

            % add as precompiled and link only
            precompiled = true;
            linkOnly = true;
            buildInfo.addLinkObjects(topModelPreBuiltObjs, linkObjPaths,...
                modelObjPriorities, precompiled, linkOnly, modelObjGroup);
        end
        
        function updateModelRef(~, modelRefLib, relativePathToObjDir, ...
                                 lFileSepFromBuildInfo)

            % Re-anchor paths, if necessary
            if coder.make.internal.isRelativePath(modelRefLib.Path)
                % If we have a relative path, we need to update it
                updatedModelRefLibPath = ...
                    strrep(fullfile(relativePathToObjDir, modelRefLib.Path), ...
                           filesep, lFileSepFromBuildInfo);
            else
                % We have an absolute path and have not copied
                % the file, so we need to pass it through unmodified.
                updatedModelRefLibPath = modelRefLib.Path;
            end
            modelRefLib.Path = coder.make.internal.transformPaths...
                (updatedModelRefLibPath,'pathType','full','ignoreErrors',true);

            % Ensure SIL/PIL never rebuilds the model reference library: set
            % precompiled and link-only
            modelRefLib.Precompiled = true;
            modelRefLib.LinkOnly = true;
        end
        
        function checkPath(this, p)
            % allow only absolute files / paths and files / paths 
            % that actually exist
            if ~this.isAbsPath({p})
                DAStudio.error('Connectivity:target:ApplicationFrameworkRelativePath', p);
            end
            if ~exist(p, 'file') % also matches directories
                DAStudio.error('Connectivity:target:ApplicationFrameworkMissingFile', p);
            end
        end        
        
        function isAbs = isAbsPath(~, filePath)
            %GETABSPATH returns true if the path is absolute
            isAbs=false(size(filePath));
            expr='^(([A-Za-z]:(\\|\/))|(/)|(\\\\[A-Za-z])|(//[A-Za-z]))';
            for i=1:length(isAbs)
                match=regexp(filePath{i},expr,...
                    'once','lineanchors');
                if ~isempty(match)
                    isAbs(i)=true;
                else 
                    isAbs(i)=false;
                end
            end
        end
    end
end

