classdef BuildInfoLinkObj < RTW.BuildInfoBuildObj
%RTW.BuildInfoLinkObj class
%   RTW.BuildInfoLinkObj extends RTW.BuildInfoBuildObj.
%

%   Copyright 2017-2024 The MathWorks, Inc.

    properties (Constant=true)
        DefaultLinkPriority = 1000;
    end

    properties (Hidden=true, Transient=true)
        BuildInfoHandle RTW.BuildInfo = RTW.BuildInfo.empty;
        BuildOptsHandle = [];
        BuildInfoChecksum = [];
        BuildInfoDirty = false;
    end
    
    properties (SetObservable)
        Name = 'Library';
        Precompiled = false;
        LinkOnly = false;
        LinkPriority = 1000;
    end
    
    properties (Hidden=true)
        NeverRebuild = false;
        ReferencedBuildInfo (1,1) coder.make.enum.ReferencedBuildInfo = ...
            coder.make.enum.ReferencedBuildInfo.Implicit;
    end
    
    methods
        function set.BuildInfoHandle(this, val)
            this.BuildInfoHandle = val;
        end
    end
    
    methods (Hidden=true)
        
        function clearBuildInfoHandle(this)
            this.BuildInfoHandle = RTW.BuildInfo.empty;
            this.BuildOptsHandle = [];
        end
        
        function varargout = addLinkObjSourceFiles(this, varargin)
            [varargout{1:nargout}] = addFiles(this, 'source', varargin{:});
        end
        function varargout =  addLinkObjSourcePaths(this, varargin)
            [varargout{1:nargout}] = addPaths(this, 'source', varargin{:});
        end
        function varargout =  addLinkObjIncludePaths(this, varargin)
            [varargout{1:nargout}] = addPaths(this, 'include', varargin{:});
        end
        function varargout =  addLinkObjIncludeFiles(this, varargin)
            % TopTester: test/toolbox/coder/build/sl/sl/buildInfo/tBuildInfoStringSupport.m
            [varargout{1:nargout}] = addFiles(this, 'include', varargin{:});
        end
        
        function [didLoad, restoreFcn] = loadLinkObjBuildInfo(this)
            % Load buildInfo and buildOpts from the buildInfo.mat and
            % populate the corresponding properties with handles to these
            % objects

            cleanupFcnRequested = (nargout>1);
            
            if cleanupFcnRequested
                restoreFcn = onCleanup(@()[]);
            end
            
            didLoad = false;
            
            if ~isempty(this.BuildInfoHandle)
                % Already loaded
                return
            end

            % Location of buildInfo.mat for this linkable
            lPathTokenized = this.Path;
            lPath = strrep(lPathTokenized, '$(START_DIR)', this.Settings.LocalAnchorDir);
            libBuildInfo = fullfile(lPath, 'buildInfo.mat');
            
            if this.ReferencedBuildInfo == coder.make.enum.ReferencedBuildInfo.None
                % The linkable has no referenced buildInfo
                % TopTester: test/toolbox/coder/build/sl/sl/general/tSilLinkObjs.m
                % TopTester: test/toolbox/coder/compile/compile/buildInfo/tLinkObjMethods.m
                return
            elseif this.ReferencedBuildInfo == ...
                    coder.make.enum.ReferencedBuildInfo.Implicit
                % The linkable might have a referenced buildInfo, check for
                % existence of buildInfo.mat
                if ~isfile(libBuildInfo)
                    return
                end
            else
                assert(this.ReferencedBuildInfo == ...
                    coder.make.enum.ReferencedBuildInfo.Required, ...
                    'Must have valid setting for referenced buildInfo property')
                % TODO(g2704387): enforce that buildInfo.mat exists in this case
                if ~isfile(libBuildInfo)
                    % TopTester: test/toolbox/rtw/targets/ert/crossrelease/crossrelease/xrelexpimp/shared/tXrelExportRelocatedCode.m
                    return
                end
            end
            
            % Increment a counter each time a buildInfo.mat is deserialized. This
            % counter is used for performance regression testing and there must be
            % no other call site where it is incremented. It has no functional effect 
            % on behaviour of this class.
            coder.make.internal.incrementBuildInfosLoaded;
            
            [libBuildInfo, libBuildOpts] = coder.make.internal.loadBuildInfo(libBuildInfo);
            libName = regexprep(this.Name, '\..*', '', 'once');
            libBuildName = coder.make.internal.getBuildName...
                    (libBuildInfo.ComponentName, libBuildOpts);
                            
            if isstruct(libBuildOpts)
                % libBuildOpts will be a structure for artifacts from 
                % R2020a and earlier.
                %
                % For artifacts from R2019b and earlier, libBuildName may
                % just be the component name for model references, without
                % any special suffix.
                isModelRef = strcmp(this.Group, coder.make.internal.BuildInfoGroup.ModelRefGroup);
                nameIsConsistent = strcmp(libName, libBuildName) || ...
                    (isModelRef && strcmp(libName, ...
                    coder.make.internal.getFinalTargetName('MODEL_REFERENCE_CODER', libBuildName)));

                %  Don't cache libBuildOpts if it is a structure.
                libBuildOpts = [];
            else
                nameIsConsistent = strcmp(libName, libBuildName);
            end

            if ~nameIsConsistent
                if strcmp(this.Group, ...
                        coder.make.internal.BuildInfoGroup.ModelRefGroup) || ...
                        this.ReferencedBuildInfo == ...
                        coder.make.enum.ReferencedBuildInfo.Required
                    assert(false, ...
                        'Build name for referenced model buildInfo must be consistent: libName = %s, libBuildName = %s', ...
                        libName, libBuildName);
                else
                    % TODO(g2704387): enforce stricter behavior that
                    % libName and libBuildName must match in this case as
                    % well.
                    return
                end
            end
            
            % Set handle for the referenced buildInfo
            this.BuildInfoHandle = libBuildInfo;
            this.BuildOptsHandle = libBuildOpts;
            
            % The buildInfo was loaded successfully 
            didLoad = true;
            
            % Cleanup function to restore original state
            if cleanupFcnRequested
                restoreFcn = onCleanup(@()clearBuildInfoHandle(this));
            end

        end
    end

    methods 
        
        function h = BuildInfoLinkObj(Name, Path, LinkPriority, Group, Settings)
            % when the object is read in from a mat file, this is called with no args to
            %  load the class
            arguments
                Name         {mustBeText}          = '';
                Path         {mustBeText}          = '';
                LinkPriority {mustBeNumeric}       = 1000;
                Group        {mustBeText}          = '';
                Settings     RTW.BuildInfoSettings = RTW.BuildInfoSettings;
            end

            if (nargin == 0)
                return;
            end
            
            % Instantiate class
            h.Options      = RTW.BuildInfoOptions();
            h.Inc          = RTW.BuildInfoModules('Includes');
            h.Src          = RTW.BuildInfoModules('Sources');
            h.Other        = RTW.BuildInfoModules('Other');
            h.Name         = Name;
            h.LinkPriority = LinkPriority;
            h.Path         = Path;
            h.Group        = Group;
            h.Settings = Settings;
            % although this field is not displayed, we want to set it for
            % debugging purposes
            h.DisplayLabel = 'Linkable Object';
        end
    end

    methods 
        function set.Precompiled(obj,value)
            value = logical(value); %  convert to logical
            obj.Precompiled = value;
        end

        function set.LinkOnly(obj,value)
            value = logical(value); %  convert to logical
            obj.LinkOnly = value;
        end
    end

    methods
        function val = areChildrenOrdered(~)
        %ARECHILDRENORDERED - returns true if children are returned in a specific order
        %from getChildren and getHierarchicalChildren.
            val = true;
        end
        
        function findIncludeFiles(h, varargin)
        % FINDINCLUDEFILES - The findIncludeFiles function searches for include
        % files in all source and include paths in the build information object and
        % adds the files found, along with their full paths, to the build
        % information object
        %
        % Argument    description
        % buildinfo   Build information handle returned by RTW.Buildinfo.
        %
        % extPatterns (optional) A cell array of file patterns (char arrays) that
        %             conform to a specific format.  Each pattern must start with
        %             '*.' and may have an combination of '*' and alphanumeric or
        %             underscore characters.  The following are all valid patterns:
        %                *.h
        %                *.hpp
        %                *.x*
        %             if no pattern is specified, then a default pattern of '*.h' is
        %             used
        %
        % example 1:   
        %   myModelBuildInfo = RTW.BuildInfo;
        %   addSourcePaths(myModelBuildInfo,...
        %                  {'/etcproj/etclib' '/etcproj/etc/etc_build'}, 'etc');
        %   findIncludeFiles(buildInfo);
        %
        % example 2:
        %   myModelBuildInfo = RTW.BuildInfo;
        %   addSourcePaths(myModelBuildInfo,...
        %                  {'/etcproj/etclib' '/etcproj/etc/etc_build'}, 'etc');
        %   findIncludeFiles(buildInfo,{'*.h' '*.hpp' '*.zzz'});
        %
        % Please refer to the documentation for more information.
        %
            if nargin > 1
                [varargin{:}] = convertStringsToChars(varargin{:});
                % this will reduce the extensions to only those that conform to '*.ABC' and
                % *.A*' type rules where ABC and A can be any alphanumeric or underscore
                % char.
                exts = varargin{1}(~cellfun(@isempty,...
                                            regexp(varargin{1},'^\*\.[*\w]+$','ONCE')));
            else
                exts = {'*.h'};
            end
            
            if isempty(exts)
                exts = {'*.h'};
            end
            
            % first get the root include files
            paths = h.getSourcePaths(true);
            paths = [paths  h.getIncludePaths(true)];
            
            % loop through the paths and search for header files
            for i=1:length(paths)
                for j = 1:length(exts)
                    incFiles = dir(fullfile(paths{i},exts{j}));
                    
                    % we need the names separated out
                    incNames = {incFiles(:).name};
                    
                    % add them to the object
                    h.addLinkObjIncludeFiles(incNames,paths{i},'Automatic');
                end
            end
        end
        
        function val = getChildren(h)
        %GETCHILDREN - returns non-hierarchical child properties for DA Studio Explorer
            val = h.getHierarchicalChildren;
        end
        
        function val = getDisplayLabel(h)
        %GETDISPLAYLABEL - returns the display label for the class in the DAStudio
        %explorer.  The value is generally overridden by the derived classes.
            val = char(h.Name);
        end
        
        function Groups = getGroups(h)
        %GETGROUPS - returns a vector of Group labels used by this object
            srcGroups = h.Src.getGroups();
            incGroups = h.Inc.getGroups();
            Groups = RTW.unique([rtwprivate('convertToCell',incGroups);...
                                rtwprivate('convertToCell',srcGroups)]);
        end
        
        function val = getHierarchicalChildren(h)
        %GETHIERARCHICALCHILDREN - returns hierarchical child properties for DA Studio
        %explorer
            val = [h.Options h.Inc h.Src h.Other];
        end
        
        function varRoot = getMakeVarRoot(h)
        %GETMAKEVARROOT - internal use only
        % use the group label if it exists.  Strip off '_SRC_LIB' to get the actual
        % root.
            varRoot = upper(regexprep(h.Group,'_SRC_LIB$',''));
            if isempty(varRoot)
                varRoot = upper(regexprep(h.Name,'\..*$',''));
            end
            
        end
        
        function val = getPreferredProperties(h)
        %GETPREFERREDPROPERTIES - Returns the preferred properties for the DA Studio
        %Explorer
            
        % the base class has properties which should be in the
        % preferred properties list.  Since there is no way to call a
        % base class method from a derived class, call a surrogate
        % getPreferredProperties from the base class.
            base_props = h.getBaseBuildInfoClassProperties();
            additional_props = {'Path', 'LinkPriority', 'Precompiled', 'LinkOnly'};
            val = [base_props additional_props];
        end
        
        function val = isHierarchical(~)
        %ISHIERARCHICAL - returns true if this class has hierarchical child properties.
            val = true;
        end
        
        function updateFilePathsAndExtensions(h,varargin)
        %UPDATEFILEPATHSANDEXTENSIONS This function updates any file with a missing
        % path and/or extension.  By default it assumes the extension is '.c',
        % however a cell array of extensions to search for can be passed in.  The
        % extensions are tested for in the order they appear in the cell array.
            
        % set up the extension list to search.  If none is provided, then default
        % it to '.c'.
            extList = {'.c' '.C' '.cpp' '.CPP' '.s' '.S'};
            incExtList = {'.h' '.H'};
            
            if nargin > 1
                if iscell(varargin{1})
                    extList = varargin{1};
                else
                    extList = varargin(1);
                end
            end
            if nargin > 2
                if iscell(varargin{2})
                    incExtList = varargin{2};
                else
                    incExtList = varargin(2);
                end
            end
            
            h.Src.updateFilePathsAndExtensions(h.Settings, extList);
            h.Inc.updateFilePathsAndExtensions(h.Settings, incExtList);
        end
        
        function updateFileSeparator(h, fsep)
        %UPDATEFILESEPARATOR - this function updates the file separator on all paths in
        % the Build Info Object to the value specified in the function argument.  
        % The function also updates the source, include and file paths in the Settings property
            if ~ismember(fsep,{'/','\'})
                error(message('RTW:buildInfo:badFileSep'));
            end

            if ~strcmp(h.Settings.FileSep, fsep)
                h.Settings.updateFileSeparator(fsep);
            end
            
            % update the path, and include and source modules for this link
            % object.
            h.Path = regexprep(h.Path,'[\\/]',h.Settings.FileSep);
            h.Src.updateFileSeparator(h.Settings.FileSep);
            h.Inc.updateFileSeparator(h.Settings.FileSep);
            h.Other.updateFileSeparator(h.Settings.FileSep);
        end
    end

    methods (Hidden)
        
        function replacePathString(h, oldStr, newStr)
        %REPLACEPATHSTRING - undocumented internal function

        % update the include and source modules for this link object
            h.Src.replacePathString(oldStr,newStr);
            h.Inc.replacePathString(oldStr,newStr);
            h.Other.replacePathString(oldStr,newStr);
            h.Path = coder.make.internal.replacePathString(h.Path,oldStr,newStr);
        end
    end
end

