classdef SILPILBlock < handle
    %SILPILBlock interfaces the SIL/PIL block to the shared infrastructure.
    %
    %   This is an undocumented class. Its methods and properties are likely to
    %   change without warning from one release to the next.

    %   Copyright 2010-2020 The MathWorks, Inc.

    properties (SetAccess = 'private', GetAccess = 'private')
        % block handle
        Block;
        XILBlockPath;
        FunctionPath = '';  % If non-empty, this is the absolute path to
                            % the folder containing the SIL/PIL S-function
                            % binary for cross release SIL/PIL blocks.
    end

    methods
        function this = SILPILBlock(hBlock)
            % validate arg
            rtw.connectivity.Utils.validateArg(hBlock, ...
                'double');
            this.Block = hBlock;

            this.XILBlockPath = this.calculateXILBlockPath();
        end

        function hBlock = getHandle(this)
            % get the block handle
            hBlock = this.Block;
        end

        function value = getParam(this, param)
            value = get_param(this.getHandle, param);
        end

        function setParam(this, varargin)
            set_param(this.getHandle, varargin{:});
        end

        function isSIL = isSIL(this)
            %ISSIL: Returns boolean indicating whether block is in SIL or PIL simulation mode

            isSIL = strcmp(this.getParam('SimulationMode'), 'SIL');
        end

        function isTopModelPIL = isTopModelPIL(this)
            % Returns boolean indicating whether block is Top-Model
            % SIL/PIL context.

            isTopModelPIL = strcmp(this.getParam('IsTopModelPIL'), 'on');
        end

        function xrelIsReusable = isXRelReusable(this)
            xrelIsReusable = strcmpi(this.getParam('XRelIsReusable'), 'on');
        end

        function pilInterface = getPILInterface(this, displayConfigHyperlink, usingResourcesDir, ...
                                                      lXilCompInfo, isSILMode, targetServices, xilBlockPath)
            % GETPILINTERFACE - Returns the PIL Interface object associated with this block.
            %
            % pilInterface = getPILInterface
            %
            if nargin < 7
                xilBlockPath = {};
                % the SILPILInterface BlockPath is used by the 
                % executionProfileCallback
            end

            xrelSourceFile = this.getParam('XRelSourceFile');
            if strcmpi(this.getParam('XRelIsTopModelCode'), 'on')
                xrelIsTopModelCode = true;
            else
                xrelIsTopModelCode = false;
            end
            
            [anchorDir, codeDir] = this.findBuildDirs(usingResourcesDir);
            xrelDWorkTypes = eval(this.getParam('XRelDWorkTypes'));
            xrelDWorkNames = eval(this.getParam('XRelDWorkNames'));
            xrelOrderedParamNames = eval(this.getParam('XRelOrderedParamNames'));

            % Deal with the case where XRelOrderedParamNames is not set
            % correctly (e.g., loaded from saved model created with older
            % version of library)
            if ~iscell(xrelOrderedParamNames)
                xrelOrderedParamNames = {};
            end

            if ~this.isSIL && displayConfigHyperlink
                % display config info
                link = targets_hyperlink_manager...
                    ('new', ...
                    targetServices.getName(), ...
                    @disp, ...
                    targetServices);
                disp(DAStudio.message('PIL:pil:SILPILBlockConnectivityConfig', codeDir, link));
            end

            % check for existing SILPILInterface
            pilInterface = this.getPILData(this.getHandle);
            if isempty(pilInterface) || ~isa(pilInterface, 'rtw.pil.SILPILInterface')
                createPILInterface = true;
            else
                createPILInterface = false;
            end

            if ~createPILInterface
                % Does PILInterface need updating?
                if ~pilInterface.isUpToDate(codeDir, ...
                                            targetServices)
                    createPILInterface = true;
                end
            end

            % check owner and create a new PIL interface object if changed
            %
            % the PIL block copyfcn updates the owner of the PIL interface to the new
            % block in order to copy out of the untitled model nicely
            if ~createPILInterface
                currentOwner = pilInterface.getOwner;
                if currentOwner ~= this.getSILPILInterfaceOwner
                    createPILInterface = true;
                end
            end

            if createPILInterface
                % create an instance of the interface class
                inTheLoopType = rtw.pil.InTheLoopType.Block;
                clientInterface = coder.connectivity.SimulinkInterface;
                pilInterface = rtw.pil.SILPILInterface(anchorDir, ...
                                                       codeDir, ...
                                                       inTheLoopType, ...
                                                       clientInterface, ...
                                                       targetServices, lXilCompInfo, isSILMode,...
                                                       'XRelSourceFile',xrelSourceFile,...
                                                       'XRelIsTopModelCode',xrelIsTopModelCode,...
                                                       'XRelIsReusable', this.isXRelReusable, ...
                                                       'XRelDWorkTypes', xrelDWorkTypes, ...
                                                       'XRelDWorkNames', xrelDWorkNames, ...
                                                       'XRelOrderedParamNames', xrelOrderedParamNames, ...
                                                       'UsingResourcesDir',usingResourcesDir, ...
                                                       'Owner', this.getSILPILInterfaceOwner,...
                                                       'BlockPath', xilBlockPath);
                % set the PILData
                handler = coder.connectivity.PILBlockPILDataHandler;
                handler.setPILData(this.getHandle, pilInterface);
            end
        end

        function blockInit(this, pilInterface)
            %BLOCKINIT - initialize the PIL block
            silPILWrapperUtils = pilInterface.getSILPILWrapperUtils;
            % set the name of the PIL s-function and the list of workspace
            % params - set together to maintain consistency
            
            % restore access to CodeDescriptor as the following calls need
            % access to the componentInterface:
            % - getWorkspaceParameters
            % - getMaskPortLabels
            silPILWrapperUtils.restoreAccessToCodeDescriptorObjs;

            
            % note that these s-function parameters are not being
            % serialized to the model file (we set them on the fly),
            % therefore we set them first,
            % otherwise the s-function throws errors about the invalid
            % "system" s-function
            this.setParam('FunctionName', pilInterface.getPILSfunction, ...
                'Parameters', this.getWorkspaceParameters(silPILWrapperUtils), ...
                'FunctionPath', this.FunctionPath);

            % set the ComponentPath
            this.setParam('ComponentPath', pilInterface.getFullComponentPath);
            % update the mask related params
            this.setParam('MaskPortLabels', this.getMaskPortLabels(silPILWrapperUtils));

        end

        function modelInitCallback(this, pilInterface, lDefaultMexCompilerKey)
            %MODELINITCALLBACK - model init callback for the PIL block
            try
                lRTWGenSettings = get_param(bdroot(this.getHandle),'RTWGenSettings');
                isNormalSimOrUpdate = isempty(lRTWGenSettings);
                isTopModelAcceleratorBuild = ...
                    isfield(lRTWGenSettings, 'AccelIsERTTarget');
                isSimulation = isTopModelAcceleratorBuild || isNormalSimOrUpdate;
                usingResourcesDir = this.isUsingResourcesDir(this.getHandle());
                this.findBuildDirs(usingResourcesDir);

                if pilInterface.isXRelBlock && ~isSimulation
                    % error out during code-generation if a cross-release
                    % block is inside a conditional subsystem that resets
                    % its states
                    this.errorIfConditionalPortsResetStates();
                end

                % display an informative message if the block is not part of a Top-Model
                % SIL/PIL simulation
                if ~this.isTopModelPIL && isSimulation
                    if this.isSIL
                        simulationMode = 'SIL';
                    else
                        simulationMode = 'PIL';
                    end
                    link = this.getHyperlinkCmdToHiliteSystem;
                    disp(DAStudio.message('PIL:pil:SILPILBlockPreparingToStart', simulationMode, link));
                end

                % make sure the correct s-function will be used
                [sfunName, ~, sfunBinDir] = pilInterface.getPILSfunction;
                % add mexext
                sfunName = [sfunName '.' mexext];

                if isempty(this.FunctionPath)
                    actualSfun = which(sfunName);
                else
                    actualSfun = fullfile(this.FunctionPath, sfunName);
                end
                requiredSfun = fullfile(sfunBinDir, sfunName);

                if isempty(actualSfun) || ~exist(requiredSfun, 'file')
                    rtw.pil.ProductInfo.error('pil', ...
                                              'SILPILBlockMissingSfunction', ...
                                              requiredSfun);
                end

                % both actualSfun & requiredSfun exist so we can
                % transformPaths to a normalised form for comparison
                actualSfun = coder.make.internal.transformPaths(actualSfun, 'pathType', 'full');
                requiredSfun = coder.make.internal.transformPaths(requiredSfun, 'pathType', 'full');
                if ~strcmp(actualSfun, requiredSfun)
                    rtw.pil.ProductInfo.error('pil', ...
                                              'SILPILBlockInvalidSfunction', ...
                                              sfunName, ...
                                              actualSfun, ...
                                              requiredSfun, ...
                                              requiredSfun);
                end

                % the wrapper should initially be built by pil_block_configure
                % when creating the block.
                %
                % if the wrapper is newer than CodeInfo then we can safely
                % recreate it if required (e.g. if in future we allow the
                % Connectivity Config to change) without being concerned that the
                % generated code has been updated, potentially causing us
                % wrapper generation problems (e.g. certain validation checks
                % are only performed during initial code generation!).
                %
                % Otherwise, we must error out and force regeneration of
                % the block.
                if ~this.isTopModelPIL % already done in pil_block_configure
                    %Check Embedded Coder is installed
                    rtw.pil.checkEmbeddedCoderInstalled();

                    if pilInterface.isXRelBlock
                        pilInterface.checkWrapperIsNewerThanExportedCode;
                    else % regular SILPILBlocks
                        pilInterface.checkWrapperIsNewerThanCodeInfo;

                        lXilCompInfo = pilInterface.getSILPILWrapperUtils().getComponentArgs().getXilCompInfo();

                        isPILBlockConfigure = false;
                        buildSFunctionOnly = pilInterface.IsUpdateModelReferenceTargets;
                        buildWrapper(pilInterface, isPILBlockConfigure, ...
                            buildSFunctionOnly, lXilCompInfo, lDefaultMexCompilerKey);
                    end
                    % initialize the block
                    this.blockInit(pilInterface);
                end
            catch eModelInit
                % clear mask related params in case an error occurs before
                % blockInit gets chance to set them correctly.
                %
                % if the s-function doesn't exist then these setParam's may
                % fail
                try
                    this.setParam('MaskPortLabels', '');
                    this.setParam('ComponentPath', '');
                catch eClear
                    if ~strcmp(eClear.identifier, 'Simulink:Engine:SFcnDoesNotExist')
                        % don't lose the original problem
                        eClear = eClear.addCause(eModelInit);
                        throw(eClear);
                    end
                end
                rethrow(eModelInit);
            end
            if isSimulation && ~pilInterface.isXRelBlock
                % bring the application up to date
                lXilCompInfo = pilInterface.getSILPILWrapperUtils().getComponentArgs().getXilCompInfo();
                buildApplication(pilInterface, lXilCompInfo, lDefaultMexCompilerKey);
            end
        end

        function createResources(this, xilInterface)
            % create a $ModuleName_$Ver_xil_resources folder containing the
            % artifacts necessary to support the cross-release block
            % relocation

            codeDir = this.getParam('CodeDir');
            suffix = this.getXRelSfunSuffix;
            subdirname = strtok(suffix, '_');

            % the relocatable folder will be at the same level of the
            % original codegen folder (i.e. $ModuleName_$Ver_ert_rtw)
            relFolderSuffix = this.getResourcesFolderSuffix;
            resourcesDir = fullfile(codeDir, '..', ...
            	[this.getParam('Name') suffix relFolderSuffix]);
            resourcesXilSubDir = fullfile(resourcesDir, subdirname);
            mkdir(resourcesXilSubDir);

            % get the xil application buildInfo.mat
            componentArgs = xilInterface.getSILPILWrapperUtils().getComponentArgs();
            buildInfo = fullfile(componentArgs.getComponentCodePath(), 'buildInfo.mat');
            % get the xil application executable
            executable = xilInterface.ApplicationExecutable;
            % get the binfo.mat of the model that we used to build the block
            binfo = coderprivate.getBinfoMATFileAndCodeName(codeDir);
            % get the codeinfo.mat of the model that we used to build the block
            codeInfo = xilInterface.getCodeInfoPath;
            % get the Code Descriptor of the model used to build the
            % block
            codeDescriptor = xilInterface.getCodeDescriptorPath;
            % get the MF0 file for the model used to build the block
            mf0File = fullfile(fileparts(binfo), 'CompileInfo.xml');

            sources = { executable, buildInfo, binfo, codeInfo, codeDescriptor, mf0File };

            % get file names and extensions
            [~ , buildInfoName, buildInfoExt] = fileparts(buildInfo);
            [~ , executableName, executableExt] = fileparts(executable);
            [~ , binfoName, binfoExt] = fileparts(binfo);
            [~ , codeInfoName, codeInfoExt] = fileparts(codeInfo);
            [~, codeDescriptorName, codeDescriptorExt] = fileparts(codeDescriptor);
            [~, mf0FileName, mf0FileExt] = fileparts(mf0File);
            % at this point the executableName is '$ModuleName_$Ver', we
            % need to update it to match the sfunction name
            executableName = [executableName suffix];

            destinations = {...
                fullfile(resourcesXilSubDir, [executableName executableExt]),...
                fullfile(resourcesXilSubDir, [buildInfoName buildInfoExt]),...
                fullfile(resourcesDir, [binfoName binfoExt]),...
                fullfile(resourcesDir, [codeInfoName codeInfoExt]),...
                fullfile(resourcesDir, [codeDescriptorName codeDescriptorExt]), ...
                fullfile(resourcesDir, [mf0FileName mf0FileExt]), ...
                };

            % copy sources to destinations
            for idx = 1:numel(sources)
                copyfile(sources{idx}, destinations{idx});
            end

            if ~ispc
                % make sure that the executable has permission to execute
                fileattrib(...
                    fullfile(resourcesXilSubDir, [executableName executableExt]),'+x');
            end
        end
    end

    methods (Static)

        function usingResourcesDir = isUsingResourcesDir(block)
            xrelSourceFile = get_param(block, 'XRelSourceFile');
            if isempty(xrelSourceFile)
                usingResourcesDir = false;
            else
                usingResourcesDir = true;
            end
        end

        function suffix = getResourcesFolderSuffix()
            suffix = '_resources';
        end

        % SIL/PIL block CopyFcn callback
        function copyFcnCallback(block)
            % update the owner of the PIL interface object to
            % the new block
            silPILBlock = rtw.pil.SILPILBlock(get_param(block,'handle'));
            silPILBlock.updateOwner;
        end

        % SIL/PIL block Initialization called from the Simulink plugin
        function SILBlockInitialization(blockh)
            block = getfullname(blockh);
            lRTWGenSettings = get_param(bdroot(block), 'RTWGenSettings');

            % Non-empty RTWGenSettings indicates that code generation is underway
            isCodeGenScenario = ~isempty(lRTWGenSettings);

            % Determine if code generation underway for top-model accelerator
            lTopModelAndAcceleratorBuild = isCodeGenScenario && ...
                isfield(lRTWGenSettings, 'AccelIsERTTarget');


            silPILBlock = rtw.pil.SILPILBlock(get_param(block,'handle'));

            [isRtwgenCall, lCachedDefaultMexCompilerInfo] = ...
                coder.internal.CompInfoCacheForRtwgen.getRtwgenCompInfoCache;
            if isRtwgenCall
                % pilblock/tpil_negative
                lDefaultMexCompInfo = lCachedDefaultMexCompilerInfo;
            else
                % Determine default compiler information, based on default mex configuration.
                % The DefaultCompInfo object must be instantiated only at the beginning
                % of the build and before any change of pwd. If default mex setup is
                % queried later in the build process after changing pwd it will not
                % pick up any mex configuration file in the original pwd, tBuildHookBuildFolder.
                lDefaultMexCompInfo = coder.internal.DefaultCompInfo.createDefaultCompInfo;
            end

            if isCodeGenScenario && ~lTopModelAndAcceleratorBuild
                % It's possible that an rtwbuild is underway and XilBuildHook has not yet been
                % called. In this case, pilInterface might not be available from block user data
                % therefore we must calculate it.

                % Determine if we're using a cross-release XIL block
                usingResourcesDir = rtw.pil.SILPILBlock.isUsingResourcesDir(block);

                % Determine XIL compiler info
                xilBlockInstance = rtw.pil.SILPILBlock(get_param(block,'handle'));
                lConfigSet = i_checkBinfoAndLoadConfigSet(usingResourcesDir, xilBlockInstance);
                isSilAndPws = xilBlockInstance.isSIL && ...
                        strcmp(get_param(lConfigSet, 'PortableWordSizes'), 'on');
                lXilCompInfo = coder.internal.utils.XilCompInfo...
                    .slCreateXilCompInfo...
                    (lConfigSet, lDefaultMexCompInfo, isSilAndPws);
                
                clientInterface = coder.connectivity.SimulinkInterface;
                configInterface = clientInterface.createConfigInterface(...
                    lConfigSet, get_param(block, 'Name'));
                options = coder.connectivity.VerificationOptions(xilBlockInstance.isSIL);
                targetServices = coder.connectivity.createTargetServices(configInterface, options, lXilCompInfo.XilMexCompInfo);
                pilInterface = silPILBlock.getPILInterface(true, usingResourcesDir, lXilCompInfo, xilBlockInstance.isSIL, targetServices);
            else
                % At this point, the block must have a pilInterface with valid
                % XIL compiler info because SILBlockInitialization is called after XILBuildHook
                pilInterface = rtw.pil.SILPILBlock.getPILData(blockh);
            end

            assert(~isempty(pilInterface), 'Must have been initialized in XILBuildHook')

            if isCodeGenScenario && ~lTopModelAndAcceleratorBuild
                refBlock = get_param(block, 'ReferenceBlock');
                assert(strcmp(refBlock, 'pil_lib/PIL Block'),...
                       'Expect a PIL Block');

                % Determine if we're using a cross-release XIL block
                isXRelBlock = pilInterface.isXRelBlock;
                if isXRelBlock
                    rtw.pil.SILPILBlock.xrelBlockChecks(block, lRTWGenSettings);
                else
                    % For non-cross release blocks, when generating
                    % code, the only supported mode for a XIL block is
                    % Accelerator. Additionally, the XIL block must be
                    % inside the  top model and not a referenced model.
                    % See g448198.
                    DAStudio.error('PIL:pil:XILBlockCodeGenError', block);
                end
            end

            % call the modelInitCallback            
            modelInitCallback(silPILBlock, pilInterface, ...
                lDefaultMexCompInfo.DefaultMexCompilerKey);
        end


        function xrelBlockChecks(block, lRTWGenSettings)
            % Checks specific to cross-release blocks

            % Error for rapid accel build
            lTopModelAndRaccelBuild = isfield(lRTWGenSettings, 'IsRSim') ...
                && strcmp(lRTWGenSettings.IsRSim, 'yes');
            if lTopModelAndRaccelBuild
                DAStudio.error('PIL:xrelimport:XRelBlockRapidAccelError', block);
            end

            % Determine if we're in a model reference code generation/
            % simulation and error for accelerator and rapid accelerator
            % modes
            lModelReferenceBuild = isfield(lRTWGenSettings, 'mdlRefRelativeSimDir');
            if lModelReferenceBuild
                modelReferenceTargetType = get_param(bdroot(block), 'ModelReferenceTargetType');
                % If a top model SIL build of a model containing a
                % referenced model containing the XIL block, then the 'RTW'
                % target type is used.
                if strcmp(modelReferenceTargetType, 'SIM')
                    % We cannot support simulation using the 'SIM' target.
                    %
                    % However, for a normal mode top model with a model
                    % block configured for SIL, the 'SIM' target is used
                    % during diagram update, and then a second SIL/PIL
                    % build is used for the actual simulation.  This is
                    % supported.
                    isAccelOrRaccel = rtw.pil.SILPILBlock.isBlockAccelOrRaccel(bdroot(block));
                    if isAccelOrRaccel
                        DAStudio.error('PIL:xrelimport:XRelBlockCodeGenError');
                    end
                end
            end
        end

        % Called from NormalModeCompileManager.cpp to initialize state
        % (e.g. model reference hierarchy block paths - passed as array of 
        % handles instead of cell array) for SIL/PIL blocks.
        function XILBuildHook(iXilBlockPaths)

            % Retrieve default mex compiler info from cache if available
            [isRtwgenCall, lDefaultMexCompilerInfo] = ...
                coder.internal.CompInfoCacheForRtwgen.getRtwgenCompInfoCache;
            if ~isRtwgenCall
                % Not being called from rtwgen: need to determine default
                % compiler information
                lDefaultMexCompilerInfo = ...
                    coder.internal.DefaultCompInfo.createDefaultCompInfo;
            end

            nBlocks = numel(iXilBlockPaths);
            xrelFunctionNames = cell(1, nBlocks);
            for i=1:nBlocks
                xilBlockModelPath = iXilBlockPaths{i};
                xilBlockParentModelPath = xilBlockModelPath(1:end-1);
                xilBlock = xilBlockModelPath(end);
                xilBlockInstance = rtw.pil.SILPILBlock(get_param(xilBlock,'handle'));
                if ~xilBlockInstance.isTopModelPIL
                    usingResourcesDir = rtw.pil.SILPILBlock.isUsingResourcesDir(xilBlock);

                    lConfigSet = i_checkBinfoAndLoadConfigSet(usingResourcesDir, xilBlockInstance);
                    isSilAndPws = xilBlockInstance.isSIL && ...
                        strcmp(get_param(lConfigSet, 'PortableWordSizes'), 'on');

                    % Determine XIL compiler info
                    lXilCompInfo = coder.internal.utils.XilCompInfo...
                        .slCreateXilCompInfo...
                        (lConfigSet, lDefaultMexCompilerInfo, isSilAndPws);
                    if isempty(xilBlockParentModelPath)
                        xilBlockPath = {xilBlockInstance.XILBlockPath};
                    else
                        xilBlockPath = getfullname(xilBlockParentModelPath);
                        if ~iscell(xilBlockPath)
                            xilBlockPath = {xilBlockPath};
                        end
                        xilBlockPath{end+1} = xilBlockInstance.XILBlockPath; %#ok<AGROW>
                    end
                    
                    clientInterface = coder.connectivity.SimulinkInterface;
                    configInterface = clientInterface.createConfigInterface(...
                        lConfigSet, get(xilBlock, 'Name'));
                    options = coder.connectivity.VerificationOptions(xilBlockInstance.isSIL);

                    targetServices = coder.connectivity.createTargetServices(configInterface, options, lXilCompInfo.XilMexCompInfo);
                    pilInterface = xilBlockInstance.getPILInterface(true, usingResourcesDir, ...
                        lXilCompInfo, xilBlockInstance.isSIL, targetServices, xilBlockPath);
                    
                    isXRelBlock = pilInterface.isXRelBlock;
                    if isXRelBlock
                        % Use the S-function name as a unique identifier
                        % and verify that only reusable cross release
                        % blocks are used in multiple instance.
                        %
                        % Used the cached S-function name rather than
                        % "FunctionName" from the underlying S-function
                        % block, as that gets reset when the model is
                        % saved.
                        xrelFunctionNames{i} = get_param(xilBlock, 'XILSFunction');
                        isXRelReusable = xilBlockInstance.isXRelReusable;
                        if ~isXRelReusable && i > 1 % strcmp will throw error for i=1
                            isXRelAlreadyUsed = strcmp(xrelFunctionNames(1:i-1), xrelFunctionNames{i});
                            if any(isXRelAlreadyUsed)
                                xrelSourceFile = get_param(xilBlock, 'XRelSourceFile');
                                % Find where the block was previously used
                                xilBlockPrevIdx = find(isXRelAlreadyUsed, 1, 'first');
                                iXilBlockPathStr = iXilBlockPaths{xilBlockPrevIdx};
                                if ~isscalar(iXilBlockPathStr)
                                    iXilBlockPathStr = iXilBlockPathStr(end);
                                end
                                xilBlockPrev = getfullname(iXilBlockPathStr);
                                DAStudio.error('PIL:xrelimport:RepeatedXRelXILBlock', xrelSourceFile, getfullname(xilBlock), xilBlockPrev);
                            end
                        end
                    end
                end
            end
        end
        
        function pilData = getPILData(pilBlock)
            % return a copy of PILData
            handler = coder.connectivity.PILBlockPILDataHandler;
            pilData = handler.getPILData(pilBlock);
        end
    end

    methods (Access = 'private')

        function suffix = getXRelSfunSuffix(this)
            if this.isSIL
                suffix = '_sil';
            else
                suffix = '_pil';
            end
        end

        function [anchorDir, codeDir] = findBuildDirs(this, usingResourcesDir)
            % get the relocatable folder if the block is using the resources
            % dir, otherwise return the value of the original build dir
            % stored on the block

            if usingResourcesDir
            % if it is a cross-release block and we already built the wrapper

                % search the sfunction associated to the block
                sfunctionName = this.getParam('XILSFunction');
                sfunctionFileName = [sfunctionName '.' mexext];

                % Need to have an exception here for the cross release
                % resources dir (not the same as a PIL block resources dir)
                xrelResourcesDir = this.getXrelResourcesDir(true);
                if isempty(xrelResourcesDir)
                    sfunctionFullName = which(sfunctionFileName);
                    this.FunctionPath = '';
                else
                    sfunctionFullName = fullfile(xrelResourcesDir, sfunctionFileName);
                    % Fall-back to using search path if not found in
                    % resources directory location - this is useful for
                    % during the compilation of the temporary model during
                    % cross release import as well as for backwards
                    % compatibility with previous releases.
                    if exist(sfunctionFullName, 'file') == 2
                        this.FunctionPath = xrelResourcesDir;
                    else
                        sfunctionFullName = which(sfunctionFileName);
                        this.FunctionPath = '';
                    end
                end


                if isempty(sfunctionFullName)
                    if isempty(xrelResourcesDir)
                        % Default error if not using the xrelResourcesDir
                        rtw.pil.ProductInfo.error('xrelimport', ...
                            'XRelBlockMissingSfunction', ...
                            sfunctionFileName);
                    else
                        % We need an error that mentions the resources dir
                        rtw.pil.ProductInfo.error('xrelimport', ...
                            'XRelBlockRsrcDirMissingSfunction', ...
                            sfunctionFileName, xrelResourcesDir);
                    end
                end


                % search the other artifacts
                sfunPath = fileparts(sfunctionFullName);
                info = [];
                relFolderSuffix = this.getResourcesFolderSuffix;
                codeDir = fullfile(sfunPath, ...
                    [this.getParam('XILSFunction') relFolderSuffix]);
                if ~exist(codeDir,'dir')
                    info = sprintf('%s\n\t- %s', info, ...
                        [this.getParam('XILSFunction') relFolderSuffix]);
                end
                tlcFile = [this.getParam('XILSFunction') '.tlc'];
                if ~exist(fullfile(sfunPath,tlcFile),'file')
                    info = sprintf('%s\n\t- %s', info, tlcFile);
                end
                makecfgFile = [sfunctionName '_makecfg.m'];
                if ~exist(fullfile(sfunPath, makecfgFile),'file')
                    info = sprintf('%s\n\t- %s', info, makecfgFile);
                end
                if ~isempty(info)
                    info = sprintf('%s\n', info);
                    rtw.pil.ProductInfo.error('xrelimport', ...
                                        'XRelBlockMissingResources',...
                                        sfunPath, info, sfunctionFileName);
                end

                anchorDir = sfunPath;
            else
                anchorDir = this.getParam('AnchorDir');
                codeDir = this.getParam('CodeDir');
            end
        end


        function MaskPortLabels = getMaskPortLabels(this, silPILWrapperUtils)
            MaskPortLabels = '';
            % add port labels as defined in the Interface Description
            %
            % use codeInfoUtils to handle export functions
            codeInfoUtils = silPILWrapperUtils.getCodeInfoUtils;
            codeInfo = codeInfoUtils.getCodeInfo;
            if codeInfoUtils.isExportFunctions
                inportLabels = this.processExportFunctionsPorts(codeInfoUtils);
            else
                % process input ports
                inports = codeInfo.Inports;
                inportLabels = this.processPorts(inports, 'input');
            end
            % process output ports
            outportLabels = this.processPorts(codeInfo.Outports, 'output');
            % combine
            MaskPortLabels = [MaskPortLabels inportLabels outportLabels];
        end

        function workspaceParams = getWorkspaceParameters(this, silPILWrapperUtils)  %#ok<INUSL>
            % default is no workspace params
            workspaceParams = '';
            % use codeInfoUtils to determine the workspace parameters
            indices = silPILWrapperUtils.getSupportedTunableWorkspaceParams;
            codeInfo = silPILWrapperUtils.getCodeInfo;
            params = codeInfo.Parameters(indices);
            numParams = length(params);
            for paramIdx=1:numParams
                param = params(paramIdx);
                if isempty(workspaceParams)
                    addComma = '';
                else
                    addComma = ', ';
                end
                workspaceParams = [workspaceParams addComma param.GraphicalName];                                                    %#ok<AGROW>
            end
        end

        function portLabelStr = processExportFunctionsPorts(this, codeInfoUtils)
            % create the "port_label" strings for export functions inports
            portLabelStr = [];
            numPorts = codeInfoUtils.getNumExportFunctionsInports;
            for portIdx = 1:numPorts
                % see if port maps to an OutputFunction
                outputFcn = codeInfoUtils.getExportFunctionsCodeInfoOutputFunction(portIdx);
                if ~isempty(outputFcn)
                    % empty codeInfoData => optimized function
                    if isempty(outputFcn.codeInfoData)
                        % no information about this port
                        portname = '';
                    else
                        % export functions fcn inport
                        portname = outputFcn.codeInfoData.Prototype.Name;
                    end
                end
                % see if port maps to an Inport
                inport = codeInfoUtils.getExportFunctionsCodeInfoInport(portIdx);
                if ~isempty(inport)
                    % regular I/O port
                    portname = inport.codeInfoData.GraphicalName;
                end
                portLabelStr = this.updatePortLabelStr(portLabelStr, 'input', portIdx, portname);
            end
        end

        function portLabelStr = processPorts(this, ports, type)
            % create the "port_label" strings for a set of input or
            % output records in the Interface Description
            portLabelStr = [];
            % process inputs
            for portIdx=1:length(ports)
                % Get portname
                port = ports(portIdx);
                portname = port.GraphicalName;
                portLabelStr = this.updatePortLabelStr(portLabelStr, type, portIdx, portname);
            end
        end

        function portLabelStr = updatePortLabelStr(this, portLabelStr, ...
                type, ...
                portIdx, ...
                portname)  %#ok<INUSL>
            % update the port label string with a new port label
            portname = strrep(portname, newline, ' ');
            portname = strrep(portname, '''', '''''');  % Escape quotes
            portLabelStr = [portLabelStr ...
                'port_label(''' type ''',' ...
                int2str(portIdx) ',''' portname ''')' ...
                newline];
        end

        function blockPath = calculateXILBlockPath(this)
            % determine whether the block is part of an
            % ssGenSfunPost wrapper subsystem
            parent = this.getParam('Parent');
            if strcmp(get_param(parent, 'Type'), 'block') && ...
                    strcmp(get_param(parent, 'BlockType'), 'SubSystem') && ...
                    strcmp(get_param(parent, 'MaskDescription'), 'PIL:pil:XILBlockMaskDesc')
                blockPath = parent;
            else
                blockName = this.getParam('Name');
                % escape any /'s
                blockName = strrep(blockName, '/', '//');
                blockPath = [parent '/' blockName];
            end
            % handle new lines, which are equivalent to spaces
            blockPath = strrep(blockPath, newline, ' ');
        end

        function errorIfConditionalPortsResetStates(this)
            % look for conditional ports in the subsystems above this block
            % and error out if any conditional port resets its states

            % look at parent subsystem
            parentSS = this.getParam('Parent');
            conditionalPorts = coder.internal.utils.findUnsupportedConditionalPorts(parentSS);
            loc_errorIfResetStates(conditionalPorts);


            function loc_errorIfResetStates(ports)
                if ~isempty(ports)
                    for idx = 1:numel(ports)
                        port_type = get_param(ports{idx},'BlockType');
                        switch port_type
                            case {'EnablePort' 'TriggerPort'}
                                param_name = 'StatesWhenEnabling';
                            case 'ActionPort'
                                param_name = 'InitializeStates';
                            case {'ForIterator' 'WhileIterator'}
                                param_name = 'ResetStates';
                            otherwise
                                assert(false, 'Unexpected port type!');
                        end

                        if strcmpi(get_param(ports{idx}, param_name), 'reset')
                            % for trigger ports error out only if the trigger
                            % type is set to function-call

                            link = this.getHyperlinkCmdToHiliteSystem;
                            if strcmp(port_type, 'TriggerPort')
                                if strcmp(get_param(ports{idx}, 'TriggerType'), 'function-call')
                                    DAStudio.error('PIL:xrelimport:UnsupportedResetStates', link);
                                end
                            else
                                DAStudio.error('PIL:xrelimport:UnsupportedResetStates', link);
                            end
                        elseif strcmpi(get_param(ports{idx}, param_name), 'inherit')
                            % need to look up in the hierarchy for the parent
                            % port that defines the behavior
                            parent_subsys = get_param(parentSS, 'Parent');
                            loc_errorIfResetStates(...
                                coder.internal.utils.findUnsupportedConditionalPorts(parent_subsys));
                        end
                    end
                end
            end

        end

        function hyperlink_cmd = getHyperlinkCmdToHiliteSystem(this)
            % get an hyperlink to the 'hilite_system' command on this block

            commandText = ['hilite_system(''' this.XILBlockPath ''');'];
            hyperlink_cmd = targets_hyperlink_manager('new', this.XILBlockPath, commandText);
        end

        function xrelResourcesDir = getXrelResourcesDir(this, isXRelBlock)
            % Function to determine whether this block should be using a
            % cross release resources directory or not.
            %
            % If not return an empty string.
            %
            if isXRelBlock
                cs = getActiveConfigSet(bdroot(this.Block));
                % First find the cross release resources folder
                % parameterized by Simulink.FileGenControl
                xrelResourcesDir = coder.internal.xrel.ModelRefSubModelFilePathHelper.getXrelResourcesDir(cs);
                sfunctionName = this.getParam('XILSFunction');
                sfunctionFileName = [sfunctionName '.' mexext];
                sfunctionFullName = fullfile(xrelResourcesDir, sfunctionFileName);
                if exist(sfunctionFullName, 'file') == 2
                    % If the S-function exists, then this is the one we
                    % shall use.
                    return;
                else
                    % If not, check folders on the search path to see if
                    % we can find a cross release resources folder containing
                    % the required S-function, and use that.
                    altResourcesDir = coder.internal.xrel.ModelRefSubModelFilePathHelper.resolveXrelResourcesFolderFromPath(sfunctionName);
                    if ~isempty(altResourcesDir)
                        xrelResourcesDir = altResourcesDir;
                    end
                end
            else
                xrelResourcesDir = '';
            end
        end
        
        function owner = getSILPILInterfaceOwner(this)
            if slfeature('XilNoWrapper')
                % the owner is used as key to save the interface in 
                % coder.connectivity.MATLABSILPILInterfaceStore, which is 
                % a map<char,any>, so we cannot use the handle directly as a key
                owner = get_param(this.getHandle,'Name');
            else
                owner = this.getHandle;
            end
        end
        
        function updateOwner(this)
            %UPDATEOWNER - update the owner of the PIL interface object to this block
            
            pilInterface = this.getPILData(this.getHandle);
            if ~isempty(pilInterface)
                pilInterface.setOwner(this.getSILPILInterfaceOwner);
            end
        end
    end

    methods (Static, Access = private)
        function isAccelOrRaccel = isBlockAccelOrRaccel(lModelName)
            % ISBLOCKACCELORRACCEL
            %
            % Determine whether any of the following are true:
            %   * The top model of the hierarchy is in accelerator mode
            %   * The top model of the hierarchy is in rapid accelerator mode
            %           (We need to do this via RTWGenSettings rather than
            %           using get_param on the top model, because in
            %           parallel builds we cannot guarantee that the top
            %           model will be open.)
            %   * The current model ref is in accelerator mode
            h = coder.internal.ModelCodegenMgr.getInstance(lModelName);
            assert(isa(h, 'coder.internal.ModelCodegenMgr'));
            isAccelOrRaccel = ...
                any(ismember(h.MdlRefSimModeForXRelBlock, {'Accelerator', 'accelerator', 'rapid-accelerator'}));
        end
    end

    methods (Static, Access = public)
        function lXrelModelArgNames = getModelArgNames(hBlock)
            % GETMODELARGNAMES
            lXrelModelArgs = get(hBlock, 'XrelModelArgs');
            if isempty(lXrelModelArgs)
                % Split on '' gives a 1 element cell array with first element empty
                lXrelModelArgNames = {};
            else
                lXrelModelArgNames = split(lXrelModelArgs, ',');
            end
        end
    end
end


function lConfigSet = i_checkBinfoAndLoadConfigSet(usingResourcesDir, xilBlockInstance)
% Obtain configset from binfo file

    clientInterface = coder.connectivity.SimulinkInterface;
    [~, codeDir] = xilBlockInstance.findBuildDirs(usingResourcesDir);

    % basic validation
    if isempty(codeDir) || ...
            isempty(getParam(xilBlockInstance, 'ConfigName'))
        rtw.pil.ProductInfo.error('pil', 'SILPILBlockBadConfiguration');
    end

    lIsXRelBlock = ~isempty(xilBlockInstance.getParam('XRelSourceFile'));
    inTheLoopType = rtw.pil.InTheLoopType.Block;

    % load binfo
    if lIsXRelBlock && usingResourcesDir
        % load binfo from the relocatable _resources folder
        [~, ~, lInfoStruct] = ...
            initializeInfoStruct(clientInterface, codeDir, inTheLoopType, usingResourcesDir);
    else
        try
            [~, ~, lInfoStruct] = ...
                initializeInfoStruct(clientInterface, codeDir, inTheLoopType, usingResourcesDir);
        catch origE
            % construct standard error with origE as cause
            [newId, newMessage] = rtw.pil.ProductInfo.message('pilverification', 'ErrorGeneratedCodeIncomplete');
            newException = MException(newId, ...
                                      newMessage);
            newException = newException.addCause(origE);
            throw(newException);
        end
    end

    lConfigSet = lInfoStruct.configSet;
end
