classdef NmakeWriter < coder.make.MakefileWriter
% Class that represents a NMAKE makefile writer.

% Copyright 2012-2019 The MathWorks, Inc.

properties
RunScriptCommandDirective
end

methods (Access = public)
% -----------------------------------------------------------------
% Constructor
% -----------------------------------------------------------------
function h = NmakeWriter(builder, buildInfo)
% Call base class constructor
h@coder.make.MakefileWriter(builder, buildInfo);

% Initialize
h.IsArtifactWrapped = 1;
h.RunScriptCommandDirective = '@cmd /C';

% Override the Builder
h.invokeBuilderOverrides(buildInfo);

end
end

methods

% -----------------------------------------------------------------
% Nmake-specific overrides to the Builder outputs
%
% In an nmake makefile,
% 1. The include paths are made part of the environment variable
% INCLUDE. Therefore, these should not be a part of the final
% compiler option string.
% 2. The libraries are made part of the environment variable
% LIB.
% 3. In order to have the above two lists become part of the
% environment, a phony target which does this should be
% generated.
% -----------------------------------------------------------------
function invokeBuilderOverrides(h, buildInfo) %#ok<INUSD>
% % Reformat the include path list. Instead of being part of the
% % compiler flags, these will be prepended to the INCLUDE
% % environment variable
% h.Parent.Components.IncludePaths = reformatComponents( ...
% h.Parent.Components.IncludePaths, 'IncludeSearchPath');
%
% % Reformat the library path list
% h.Parent.Components.Libraries = reformatComponents(...
% h.Parent.Components.Libraries, 'LibrarySearchPath');

% Remove $(INCLUDES) from the compiler flags because these will
% now be part of the INCLUDE environment variable
removeIncludePathsFromCompilerFlags(h);

% Add a phony target that pre-pends $(INCLUDES) to the
% $(INCLUDE) environment variable
addPhonyTarget();

% Overwrite the Components
h.Components = h.Parent.Components;

% -------------------------------------------------------------
% Remove the include paths from the compiler option string
% -------------------------------------------------------------
function removeIncludePathsFromCompilerFlags(h)
% If using a response file, remove $(INCLUDES) but add response file to the
% the compiler flags since the environment variables do not support supplying command files

% Determine if compiler command file is in use
lUseCompilerCommandFile = coder.make.useCompilerCommandFile...
(h.ToolchainInfo);

comps = {'C Compiler', 'C++ Compiler'};
for k=1:numel(comps)
if h.Parent.Components.ToolchainFlags.isKey(comps{k})
cCompFlags = h.Parent.Components.ToolchainFlags.getValue(comps{k});
macros = {cCompFlags.buildinfo.macro};
for i=1:numel(macros)
idx = strfind(macros{i}, '_BASIC');
if ~isempty(idx)
if lUseCompilerCommandFile
cCompFlags.buildinfo(i).value = ...
strrep(cCompFlags.buildinfo(i).value, '$(INCLUDES)', '@$(COMPILER_COMMAND_FILE)');
else
cCompFlags.buildinfo(i).value = ...
strrep(cCompFlags.buildinfo(i).value, '$(INCLUDES)', '');
end
end
end
h.Parent.Components.ToolchainFlags.setValue(comps{k}, cCompFlags);
end
end
end

% -------------------------------------------------------------
% Add a phony target that makes the include paths & libraries
% part of the environment variables INCLUDE and LIBS,
% respectively.
% -------------------------------------------------------------
function addPhonyTarget()
% Create a 'set_environment_variables' phony target
phony = h.Parent.createNewMiscellaneousTarget();
phony.Target = 'set_environment_variables';
phony.SpecialTarget = false;
phony.Dependencies = [];

% Rule to add the include paths to the INCLUDE environment
% variable.
cmd = h.Parent.createNewRule();
cmd.FullCommand = '@set INCLUDE=$(INCLUDES);$(INCLUDE)';
h.Parent.quickAppend(phony.Rules, 'Set INCLUDE variable', cmd);

% Rule to add the libraries to the LIB environment
% variable.
cmd = h.Parent.createNewRule();
cmd.FullCommand = '@set LIB=$(LIB)';
h.Parent.quickAppend(phony.Rules, 'Set LIB variable', cmd);
h.Parent.quickAppend(h.Parent.Components.PhonyTargets, phony.Target, phony);

% Add this target to 'build'
buildTarget = h.Parent.Components.PhonyTargets.getValue('build');
buildTarget.Dependencies = horzcat(phony.Target, buildTarget.Dependencies);

% Add this target to 'buildobj'
if h.Parent.isStandaloneExecutableTarget
buildobjTarget = h.Parent.Components.PhonyTargets.getValue(coder.make.Builder.BuildObjectsTarget);
buildobjTarget.Dependencies = horzcat(phony.Target, buildobjTarget.Dependencies);
end
end

end

% -----------------------------------------------------------------
% Write the include paths
% -----------------------------------------------------------------
function writeIncludePaths(h, buildInfo) %#ok<INUSD>
desc = 'INCLUDE PATHS';
macro = h.getMacro('include paths');
groups = h.Components.IncludePaths;
% --- Do not append 'include path' directive ---
h.writeGroups_SpecialFormat(desc, macro, groups);
end

% -----------------------------------------------------------------
% Write the library files
% -----------------------------------------------------------------
function writeLibraryFiles(h, buildInfo) %#ok<INUSD>
desc = 'LIBRARIES';
macro = h.getMacro('libraries');
groups = h.Components.Libraries;

% --- Do not append 'library' directive ---
if iscell(groups)
for i=1:numel(groups)
if i~=1
desc = '';
end
h.writeGroups(desc, groups{i}.macro, groups{i});
end
else
h.writeGroups(desc, macro, groups);
end
end

% -----------------------------------------------------------------
% Write the component groups in a special format
% -----------------------------------------------------------------
function writeGroups_SpecialFormat(h, desc, finalMacro, groups)
if ~isempty(desc)
h.printSectionLabel('H1', upper(desc));
end

% Form the list
if isfield(groups, 'listType') && strcmpi(groups(1).listType, 'flat')
list = groups(1).value;
else
% Write the grouped lists
for idx=1:numel(groups)
h.printAssignment( ...
groups(idx).macro, ...
h.concatenate(groups(idx).value, ';'));
end
h.printNewLine();

% Write the topmost group - string all group macros
if numel(groups) == 0
list = '';
else
all = {groups.macro};
list = coder.make.internal.tokenize(all);
end
end

% Write the final list
h.printAssignment(finalMacro, h.concatenate(list, ';'));
h.printNewLine();
end

% -----------------------------------------------------------------
% Get the commands for formulating the list of objects
% -----------------------------------------------------------------
function [macros, values] = getObjectCommands(h, buildInfo) %#ok<INUSD>
srcsMacro = h.getMacro('source files');
objsMacro = h.getMacro('objects');
objExt = h.Parent.getBuildTool('C Compiler').getFileExtension('Object');

% OBJS_CPP_UPPER = $(SRCS:.CPP=.obj)
% OBJS_CPP_LOWER = $(OBJS_CPP_UPPER:.cpp=.obj)
% OBJS_C_UPPER = $(OBJS_CPP_LOWER:.C=.obj)
% OBJS = $(OBJS_C_UPPER:.c=.obj)

macros{1} = 'OBJS_CPP_UPPER';
values{1} = sprintf('$(%s:.CPP=%s)', srcsMacro, objExt);
macros{end+1} = 'OBJS_CPP_LOWER';
values{end+1} = sprintf('$(OBJS_CPP_UPPER:.cpp=%s)', objExt);
macros{end+1} = 'OBJS_C_UPPER';
values{end+1} = sprintf('$(OBJS_CPP_LOWER:.C=%s)', objExt);
macros{end+1} = objsMacro;
values{end+1} = sprintf('$(OBJS_C_UPPER:.c=%s)', objExt);

macros{end+1} = h.getMacro('objects');
values{end+1} = sprintf('$(OBJS)');
end

% -----------------------------------------------------------------
% Syntax:
% <ECHO> "<STRING>"
% -----------------------------------------------------------------
function out = formDisplayCommand(h, str)
out = [h.RunScriptCommandDirective ' "' h.DisplayDirective ' ' str '"'];
end

% -----------------------------------------------------------------
% Print an in-place assignment
% -----------------------------------------------------------------
function printAssignmentInPlace(h, macro, values, comment)
if nargin==4
h.printComment(comment);
end
h.printLine([macro ' = ' h.concatenate(values)]);
end

% -----------------------------------------------------------------
% Print a concatenation
% -----------------------------------------------------------------
function printConcatenation(h, macro, values, comment)
if nargin==4
h.printComment(comment);
end
h.printLine([macro, ' = ', coder.make.internal.tokenize(macro), ' ', h.concatenate(values)]);
end

% -----------------------------------------------------------------
% Write the target
% -----------------------------------------------------------------
function writeSourceToObjectTarget(h, target, dependencies, commands, ...
lFileSep)
if ischar(commands)
commands = cellstr(commands);
end
% <TARGET> : <DEPENDENCIES>
[depPath, depName, depExt] = coder.make.internal.fileparts(dependencies, lFileSep);
if strcmp(depName, '%')
% If the dependency uses globbing then generate an inference
% rule
[targetPath, ~, targetExt] = coder.make.internal.fileparts(target, lFileSep);
if ~isempty(depPath)
depPath = ['{' depPath '}'];
end
if ~isempty(targetPath) && ~strcmp(targetPath, depPath)
targetPath = ['{' targetPath '}'];
end
h.printLine([depPath, depExt, targetPath, targetExt, ' :']);
else
% Print a file specific rule. With NMAKE the implied
% dependency macro is only valid with inference rules, so
% we must specify the dependencies explicitly
[commands{:}.LinkObjs] = dependencies;
h.printLine([target, ' : ', dependencies]);
end

% <TAB><RULES>
tb = h.getTab();
for i=1:numel(commands)
if isa(commands{i}, 'coder.make.Rule')
h.printRule(commands{i});
else
h.printLine([tb, commands{i}]);
end
end
h.printNewLine(2);
end

end % methods

methods (Static)
function value = transformPaths(value, platform)
value = transformPaths@coder.make.MakefileWriter(value, platform);

% Any paths that have a '#' in them have to be escaped with a
% caret '^'
value = regexprep(value, '#', '\^#');
end
end
end

% LocalWords: NMAKE Nmake nmake OBJS Toolchain buildobj globbing