function pctRunOnAll(varargin)
%pctRunOnAll run a command on client and all workers in a parallel pool
%  pctRunOnAll allows you to specify commands to run on the client and all
%  the workers in a parallel pool. This is useful if there are setup or
%  configuration changes that you want to do on both the client and all of
%  the workers.
%  
%
%  SYNTAX
%
%  pctRunOnAll COMMAND runs the specified command on the client and all the
%  workers and prints any command line output back to this command window.
%  The requested COMMAND runs in the base workspace of the workers and does
%  not have any return variables.
%
%  EXAMPLES
%
%  Clear all loaded functions on client and all workers
%    pctRunOnAll clear functions
%
%  Change directory on client and all workers to the project directory
%    pctRunOnAll cd /opt/projects/c1456
%
%  Add a few directories to the paths of the client and all workers
%    pctRunOnAll addpath({'/usr/local/path1' '/usr/local/path2'})
%
%   See also : parpool 

%   Copyright 2007-2020 The MathWorks, Inc.

% Convert any string inputs to character vectors
[varargin{:}] = convertStringsToChars(varargin{:});

command = iParseRunOnAllArgs(varargin{:});

pool = gcp('nocreate');
if isempty(pool) || ~pool.Connected
    error(message('parallel:convenience:RunOnAllPoolNotRunning', command));
end

fut = parfevalOnAll(pool, @iRunCommand, 0, command);
err = '';
try
    evalin('base', command);
catch err
end

complete = false;
% Crude progressive output display - we need to keep track of which is the
% next character in the diary output for each worker that we need to display.
nextChar = []; % Will be initialized once we know how many diaries we expect
while ~complete
    complete = wait(fut, 'finished', 1);

    % Get the diaries and display anything we haven't displayed yet.
    diaries = fut.Diary;
    if ~isempty(diaries)
        if isempty(nextChar)
            % Only now do we know how many diaries we will get.
            nextChar = ones(1, numel(diaries));
        end

        for idx = 1:numel(diaries)
            toDisp = diaries{idx}(nextChar(idx):end);
            fprintf('%s', toDisp);
            nextChar(idx) = 1 + numel(diaries{idx}); %#ok<AGROW> incorrect analysis.
        end
    end
    
    % Only test to see if the session is failing if the future isn't yet complete.
    if ~complete && ~pool.Connected
        error(message('parallel:convenience:RunOnAllPoolShutdown'));
    end
end

% Note: pctRunOnAll only terminates with an error for client errors.
if ~isempty(err)
    rethrow(err)
end

end
% -------------------------------------------------------------------------
%
% -------------------------------------------------------------------------
function command = iParseRunOnAllArgs(varargin)
% iParseRunOnAllArgs checks that we can create a single string from
% varargin
if numel(varargin) == 0
    command = '';
    return
end
% Ensure that all elements of varargin are strings 1xN
if (all(cellfun(@ischar, varargin) &  ...
        cellfun(@(C) size(C, 2), varargin) == cellfun(@numel, varargin)))
    command = sprintf('%s ', varargin{:});
else
    error(message('parallel:convenience:RunOnAllInvalidInput'))
end
end
% -------------------------------------------------------------------------
%
% -------------------------------------------------------------------------
function iRunCommand(command)
% For backwards-compatibility, we must run pctRunOnAll things in "mwmpi" mode,
% but only when MPI is initialized. (The old implementation had a latent bug
% in that it would run in mwmpi mode even if MPI was not yet initialized).
if mpiInitialized
    pctPreRemoteEvaluation('mwmpi');
end
try
    evalin('base', command);
    err = [];
catch err
end

parallel.internal.pool.interPPromptFcn();
if ~isempty(err)
    rethrow(err);
end
end
