Main Content

Create Custom Tasks

With the CI/CD Automation for Simulink Check support package, you can define a development and verification process for your team by adding built-in and custom tasks to your process. The support package contains several built-in tasks that you can reconfigure and use to perform steps in your process, but if you need to perform other actions or always want to use a reconfigured version of a built-in task, you can create and add custom tasks to your process model.

Depending on what you want your custom task to do, there are different approaches:

To add custom tasks to your process, you need to edit the process model file for your project. If you do not have a project or process model, see Automate and Run Tasks with Process Advisor to get started.

Custom Task That Runs Existing Script

If your custom task only needs to run an existing MATLAB script, you can edit your process model to specify which script to run by using the Action argument for the addTask function.

For example, suppose that you have a script, myScript.m, that you want a custom task to run. You can use the addTask function to add a new task to your process model. The Action argument specifies the function that the task runs. In your processmodel.m file, you can specify:

function processmodel(pm)
    % Defines the project's processmodel

    arguments
        pm padv.ProcessModel
    end

    addTask(pm,"RunMyScript", Action = @runMyScript);

end

function taskResult = runMyScript(~)
    run("myScript.m");
    taskResult = padv.TaskResult;
end
"RunMyScript" is the name for the new task. @runMyScript is the function handle for the function that you define inside the processmodel.m file. padv.TaskResult defines the results for the task.

You can run the script as a task in Process Advisor.

The task RunMyScript in the Process Advisor Tasks column

Custom Task for Specialized Functionality

To create a task that performs a custom functionality, you need to:

  1. Create a new MATLAB class.

  2. Inherit from either a built-in task or the superclass padv.Task.

  3. Specify the task name and, optionally, other task properties.

  4. Keep or override the run method that defines the action that the task performs.

Create New MATLAB Class

Create a new MATLAB class in your project.

Note that namespaces can help you organize the class definition files for your custom tasks. For example, in the root of your project you can create a folder +processLibrary with a subfolder +task and save your class in that folder.

"MyCustomTask.m" class definition file inside "+task" folder inside "+processLibrary" folder

To share your custom tasks across multiple process models in different projects, consider creating a referenced project that contains your folders and class definition files. Your main projects can then use the referenced project as a shared process library.

Choose Superclass for Custom Task

Your MATLAB class can inherit from either:

  • One of the Built-In Tasks — Use this approach when there is a built-in task that is similar to the custom task that you want to create. When you inherit from a built-in task, like padv.builtin.task.RunModelStandards, your custom task inherits the functionality of that task, but then you can override the properties and methods of the class to fit your needs. For information on the built-in tasks, see Built-In Tasks.

  • The superclass padv.Task — Use this approach if your custom task needs to perform a step that is not similar to a built-in task. padv.Task is the base class of the built-in tasks, so you must completely define the inputs, functionality, and outputs of the task.

If you are inheriting from a built-in task, you can replace the contents of your class file with the following example code. The code inherits from the built-in task padv.builtin.task.RunModelStandards, but you can replace those lines of code to inherit from a different built-in task instead.

classdef MyCustomTask < padv.builtin.task.RunModelStandards
    % task definition goes here
    methods
        function obj = MyCustomTask(options)
            arguments
                options.Name = "MyCustomTask";
                options.Title = "My Custom Task";
            end
            obj@padv.builtin.task.RunModelStandards(Name = options.Name);
            obj.Title = options.Title;
        end
    end
end

If you are inheriting from padv.Task, you can replace the contents of your class file with the following example code. The code finds the models in the project by using the iteration query padv.builtin.query.FindModels and specifies those models as task inputs by using the input query padv.builtin.query.GetIterationArtifact. The code calls the constructor of the superclass padv.Task. For information on superclass constructors, see Design Subclass Constructors.

classdef MyCustomTask < padv.Task
    methods
        function obj = MyCustomTask(options)
            arguments
                % unique identifier for task
                options.Name = "MyCustomTask";
                % artifacts the task iterates over
                options.IterationQuery = "padv.builtin.query.FindModels";
                % input artifacts for the task
                options.InputQueries = "padv.builtin.query.GetIterationArtifact";
                % where the task outputs artifacts
                options.OutputDirectory = fullfile(...
                    '$DEFAULTOUTPUTDIR$','my_custom_task_results');
            end

            % Calling constructor of superclass padv.Task
            obj@padv.Task(options.Name,...
                IterationQuery=options.IterationQuery,...
                InputQueries=options.InputQueries);
            obj.OutputDirectory = options.OutputDirectory;
        end

        function taskResult = run(obj,input)
            % "input" is a cell array of input artifacts
            % length(input) = number of input queries
            
            % class definition goes here

            % specify results from task using padv.TaskResult
            taskResult = padv.TaskResult;
            taskResult.Status = padv.TaskStatus.Pass;
            % taskResult.Status = padv.TaskStatus.Fail;
            % taskResult.Status = padv.TaskStatus.Error;
        end
    end
end

Specify Task Properties

Specify the Name property for your task and, optionally, other task properties.

You must specify a Name because the Name is the unique identifier for the task. Specifying other class properties is optional, but can help you define the task behavior. The following table lists common class properties that you often specify for a custom task. For information on other class properties, see Built-In Tasks or padv.Task. For information on how tasks and queries define your process, see Overview of Process Model.

PropertyDescription
Name (required)

Unique identifier for task

IterationQuery

Artifacts the task iterates over

By default, custom tasks run one time for the project.

InputQueries

Inputs to the task

InputDependencyQuery

Artifacts that the task inputs depend on

Typically, you specify InputDependencyQuery as padv.builtin.query.GetDependentArtifacts to get the dependent artifacts for each task input.

OutputDirectory

Directory where the task outputs artifacts

If you do not specify OutputDirectory for a custom task, the build system stores task outputs in the DefaultOutputDirectory specified by padv.ProcessModel.

Keep or Override run Method

The run method defines the action that your custom task performs. For examples of how to override the run method, see Example Custom Tasks.

Make sure to use the same method signature as the class that you inherit from. In the method signature, the input argument is a cell array that contains the input artifacts from your input queries. Each element in input corresponds to each input query that you specify.

For example, if you only specify one input query, padv.builtin.query.GetIterationArtifact, and you are iterating over each model in the project, you can use the first element of input, input{1}, to perform an action on each model in the project:

        function taskResult = run(obj,input)
            % Before the task loads models,
            % save a list of the models that are already loaded.
            loadedModels = get_param(Simulink.allBlockDiagrams(),'Name');
            
            % identify model name
            % "input" is a cell array of input artifacts
            % First input query gets iteration artifact (a model)
            model = input{1}; % get padv.Artifact from first input query
            modelName = padv.util.getModelName(model);

            % Example task that loads model and displays information
            load_system(modelName);
            disp(modelName);
            disp('Data Dictionaries:')
            disp(Simulink.data.dictionary.getOpenDictionaryPaths)
            
            % specify results from task using padv.TaskResult
            taskResult = padv.TaskResult;
            taskResult.Status = padv.TaskStatus.Pass;
            % taskResult.Status = padv.TaskStatus.Fail;
            % taskResult.Status = padv.TaskStatus.Error;

            % % Close models that were loaded by this task.
            padv.util.closeModelsLoadedByTask(...
                PreviouslyLoadedModels=loadedModels)
        end

The run method must return a padv.TaskResult object. Process Advisor uses the padv.TaskResult object to assess the status of your custom task. The task result properties Status, OutputPaths, and Values correspond to the Tasks, I/O, and Details columns in Process Advisor:

Example CodeAppearance in Process Advisor
taskResult.Status = padv.TaskStatus.Pass

Tasks column shows task with green check mark

taskResult.Status = padv.TaskStatus.Fail

Tasks column shows task with red X

taskResult.Status = padv.TaskStatus.Error

Tasks column shows task with red exclamation mark

taskResult.OutputPaths=string(...
    fullfile("PA_Results","myFile.txt"));

I/O column shows "myFile.txt" file as output

taskResult.Values.Pass = 1;
taskResult.Values.Warn = 2;
taskResult.Values.Fail = 3;

Details column with 1 passing result, 2 warning results, and 3 failing results

Additionally, you can also override the dryRun method to specify how your custom task evaluates task inputs and generates representative outputs for quick process model tests. For more information, see Dry Run Tasks to Test Process Model.

Add Custom Task to Process

Add your custom task to your process model by using the addTask function. For example, to add a custom task named MyCustomTask that is saved in a +task subfolder inside a +processLibrary folder:

function processmodel(pm)
    % Defines the project's processmodel

    arguments
        pm padv.ProcessModel
    end

    addTask(pm,processLibrary.task.MyCustomTask);

end

The custom task appears in the Tasks column in Process Advisor.

Process Advisor standalone window with MyCustomTask in the Tasks column

Example Custom Tasks

Perform Post-Processing on Task Results

You can use custom tasks to perform pre-processing or post-processing actions. For example, suppose you want to run Model Advisor and if checks generate a failure or a warning, you want the task to fail. There are no built-in tasks that perform this exact functionality by default, but the built-in task padv.builtin.task.RunModelStandards runs Model Advisor and the task fails if a check generates a failure.

You can use a custom task to create your own version of padv.builtin.task.RunModelStandards that overrides the results from the task to specify that if a Model Advisor check returns a warning, the task should also fail.

This example shows a custom task that inherits from the built-in task padv.builtin.task.RunModelStandards, overrides the input queries to use the file sampleChecks.json as the Model Advisor configuration file, and extends the run method of the built-in task to fail the task if Model Advisor returns warnings.

classdef MyRunModelStandards < padv.builtin.task.RunModelStandards
    % RunModelStandards, but use my Model Advisor configuration file
    % and fail the task when there are warnings from Model Advisor checks

    methods
        function obj = MyRunModelStandards(options)

            arguments
                options.Name = "MyRunModelStandards";
                options.Title = "My Check Modeling Standards";
            end

            obj@padv.builtin.task.RunModelStandards(Name = options.Name);
            obj.Title = options.Title;
            % specify current model (iteration artifact) and
            % Model Advisor configuration file as inputs to the task
            obj.addInputQueries([padv.builtin.query.GetIterationArtifact,...
                padv.builtin.query.FindFileWithAddress(...
                Type = 'ma_config_file',...
                Path = fullfile('tools','sampleChecks.json'))]);

        end
        
        function taskResult = run(obj,input)

            % use RunModelStandards to run Model Advisor
            taskResult = run@padv.builtin.task.RunModelStandards(obj,input);
            % If checks for a model fail, then the status will be
            % set to fail.

            % But you can extend the built-in task to specify that
            % if checks for a model generate a warning, then the
            % task status will also be set to fail.
            if taskResult.Values.Warn > 0
                taskResult.Status=padv.TaskStatus.Fail;
            end
            
        end

    end
    
end

Note

In this example, the run method of the custom task extends the run method of the built-in task by calling it from within the custom task run method. But you can also reimplement the run method for a custom task to implement your own version of the run method. For more information and common class designs, see Modify Inherited Methods.

Run Custom Task for Project

Suppose that you want to return a list of the data dictionaries in your project. There are no built-in tasks that perform this functionality, so you can create a custom task that inherits directly from the base class padv.Task and use the arguments to specify the behavior of the custom task.

classdef ListAllDataDictionaries < padv.Task

    methods
        function obj = ListAllDataDictionaries(options)

            arguments
                options.InputQueries = padv.builtin.query.FindArtifacts(...
                    ArtifactType="sl_data_dictionary_file");
                options.Name = "ListAllDataDictionaries";
            end
                inputQueries = options.InputQueries;
                obj@padv.Task(options.Name, ...
                    Title = "My Custom Task for SLDD files", ...
                    InputQueries = inputQueries, ...
                    DescriptionText = "My Custom Task for SLDD files", ...
                    Licenses={});
        end

        function taskResult = run(~, input)
            % Print names of SLDDs
            disp([input{1}.Alias]')
            taskResult = padv.TaskResult;
            taskResult.Status = padv.TaskStatus.Pass;
            taskResult.Values.Pass = 1;
        end
    end
end

In the custom task, you can find the data dictionaries in the project by using the query padv.builtin.query.FindArtifacts and specifying the query as one of the InputQueries for the task. In the run function, you can specify the action that the task performs and specify the task results, in a format that Process Advisor can recognize, by using a padv.TaskResult object. The input is a cell array of input artifacts that the build system automatically creates based on the InputQueries that you specify. In this example, the first cell in input is an array of padv.Artifact objects that represent the data dictionaries in the project. The disp function can display the aliases of the data dictionaries in the MATLAB Command Window. When you specify the task result Status, that sets the task status in the Tasks column in Process Advisor. Values.Pass sets the number of passing results in the Details column in Process Advisor.

Custom task in the Process Advisor tasks column and task output shown in the MATLAB Command Window

Specify Tool for Custom Task

When you point to a task in the Process Advisor app, you can click the ellipsis (...) to view more options. For built-in tasks, you have the option to launch a tool or multiple tools associated with the task. For example, the built-in task Check Modeling Standards allows you to directly open Model Advisor for the model that the task iteration runs on.

Check Modeling Standards task showing option to Open Model Advisor

You can associate a tool with the options menu for a task by specifying the property LaunchToolAction as a function handle that launches that tool. For example, suppose you have a custom task that runs on each model in the project and you want the task to launch Dependency Analyzer for the model. For LaunchToolAction, specify the handle to a function that launches Dependency Analyzer. The function that launches the tool has two inputs, obj and artifact, and must return a result structure with the status of the tool launch action, ToolLaunched.

function processmodel(pm)
    % Defines the project's processmodel

    arguments
        pm padv.ProcessModel
    end

    customTask = addTask(pm,"MyCustomTask",...
        IterationQuery = padv.builtin.query.FindModels,...
        InputQueries = padv.builtin.query.GetIterationArtifact,...
        LaunchToolAction=@openDependencyAnalyzer);

end

function result = openDependencyAnalyzer(obj, artifact)
    result = struct('ToolLaunched', false);    
    % handle non-model task iterations / abstract tasks
    if isempty(artifact)
        result.message = 'Open the tool for an artifact listed under the task title.';
        return;
    end    
    % identify model name
    modelName = padv.util.getModelName(artifact);    
    % open Dependency Analyzer for model
    depview(modelName)    
    result.ToolLaunched = true;
end

Specify Inputs That Can Make Task Outdated

Suppose that you want to create a custom task that analyzes specific Excel® files in your project and you want the task to become outdated when you make changes to those files. You can find the files by using the built-in query padv.builtin.query.FindArtifacts. In this example, the task uses the IncludePathRegex argument of the query to find Excel files (.xlsx) with file names that begin with HLR_. The task uses that query to define the task iterations (IterationQuery) and task inputs (InputQueries). The task iterates over these files and checks for the presence of specific sheets named StepUp and StepDown. If the Excel file has those sheets, the task passes. Otherwise, the task fails. The task automatically becomes outdated if you make a change to any of the Excel files that the query finds.

classdef CheckExcelSheetNames < padv.Task
    methods
        function obj = CheckExcelSheetNames(options)
            arguments
                % unique identifier for task
                options.Name = "CheckExcelSheetNames";
                % artifacts the task iterates over
                % in this case, Excel files that begin with "HLR_"
                options.IterationQuery = padv.builtin.query.FindArtifacts(...
                    IncludePathRegex = "HLR_.*\.xlsx");
                % input artifacts for the task
                % in this case, the same as the iteration artifacts
                options.InputQueries = "padv.builtin.query.GetIterationArtifact";
                % where the task outputs artifacts
                options.OutputDirectory = fullfile(...
                    '$DEFAULTOUTPUTDIR$','excel_status_results');
            end
            % Calling constructor of superclass padv.Task
            obj@padv.Task(options.Name,...
                IterationQuery=options.IterationQuery,...
                InputQueries=options.InputQueries);
            obj.OutputDirectory = options.OutputDirectory;
        end
        function taskResult = run(obj,input)
            % specify results from task using padv.TaskResult
            taskResult = padv.TaskResult;
            % Get the sheet names for the sheets in the current spreadsheet
            % "input" is a cell array of task input artifacts
            a = input{1}.ArtifactAddress;
            fa = a.getFileAddress;
            sheets = sheetnames(fa);
            % Check if sheets for both "StepUp" and "StepDown" are present in
            % the spreadsheet
            if ismember("StepUp", sheets) && ismember("StepDown", sheets)
                disp('Both the "StepUp" and "StepDown" sheets are present.');
                taskResult.Status = padv.TaskStatus.Pass;
            else
                disp('Missing "StepUp" or "StepDown" sheets.');
                taskResult.Status = padv.TaskStatus.Fail;
            end
        end
    end
end

Ignore Changes to Specific Task Outputs

You can turn off change tracking for a specific artifact by specifying the Track property of the artifact address as false. The artifact address is stored in the ArtifactAddress property of a padv.Artifact object.

For example, the following custom task inherits from the built-in task DetectDesignErrors, but overrides the run method to turn off change tracking for the output report. The custom task identifies the report by iterating over each task output, checking if the artifact has the same report format as the task, and then specifying the Track property for the artifact address.

classdef MyDetectDesignErrors < padv.builtin.task.DetectDesignErrors
    % Detect design errors, but ignore changes to generated report files
    methods

        function obj = MyDetectDesignErrors(options)
            arguments
                options.Name = "MyDetectDesignErrors";
                options.Title = "My Detect Design Errors";
            end
            obj@padv.builtin.task.DetectDesignErrors(Name = options.Name);
            obj.Title = options.Title;
        end

        function taskResult = run(obj,input)

            % use DetectDesignErrors to run Design Verifier
            taskResult = run@padv.builtin.task.DetectDesignErrors(obj,input);

            % for each task output, check if it's a report
            for i = 1:length(taskResult.OutputArtifacts)
                artifact = taskResult.OutputArtifacts(i);
                artifactAddress = artifact.ArtifactAddress;
                fileAddress = artifactAddress.getFileAddress;
                if contains(fileAddress, obj.ReportFormat, IgnoreCase=true)
                    % if the task output is a report, turn off change tracking for the report
                    artifactAddress.Track = false;
                end
            end

        end
    end
end
For more information, see Exclude Files from Change Tracking in Process Advisor.

See Also

| | | |

Related Topics