classdef tBuildLibrary < matlab.unittest.TestCase
%tBuildLibrary testpoint that builds a static and shared library from a C source file
%The test verifies the existence of the library.
%If the toolchain or template makefile defined as buildMethod in the base
%workspace contains a C++ compiler, the same process is done for a C++ source file.
%The testpoint also build a shared library

% Copyright 2020 The MathWorks, Inc.

properties
% Toolchain info object created from the buildMethod variable
% defined in base workspace
TcInfo;
end

properties(ClassSetupParameter)
% Get the build method specified by the user in the base workspace
buildMethod = {getBuildMethod};
end

methods(TestClassSetup)
function setup(test, buildMethod)
% Get toolchainInfo object from buildMethod
test.TcInfo = coder.make.internal.resolveToolchainOrTMF(buildMethod);

end
end

methods(TestMethodSetup)
function setupWorkingFolder(test)
applyFixture(test, matlab.unittest.fixtures.WorkingFolderFixture);
end
end

methods(Test)

% Build an executable from a C source file
function buildCStaticLibrary(test, buildMethod)

% Create source file to compile
prodName = 'foo';
srcFile = [prodName '.c'];
fid = fopen(srcFile, 'w');
fwrite(fid, ['#include <stdio.h>' newline 'int foo() { volatile int a = 0; return a++; } int main() {printf("Hello World.\n");}']);
fclose(fid);

% Create the buildInfo object
lBuildInfo = i_createBuildInfo(prodName,srcFile);

% Do the build for the static library
codebuild(lBuildInfo, ...
'BuildMethod', buildMethod, ...
'BuildVariant', 'STATIC_LIBRARY');

% Verify that final products were created
compileSharedLibrary = false;
verifyFinalProduct(test, prodName,compileSharedLibrary);

end

% If buildMethod has a C++ compiler, build an executable from a
% cpp file
function buildCPPStaticLibrary(test, buildMethod)

% Determine if BuildMethod supports C++ compilation and filter
% the test out if not
test.assumeTrue(( contains(buildMethod,'.tmf') || ~isempty(test.TcInfo.BuildTools.getValue('C++ Compiler')) ) &&...
~contains(buildMethod,'lcc'),...
['Skipping C++ compilation as buildMethod does not define a C++ build tool. If you expect ' ...
buildMethod ' to support C++ compilation, see the following ' ...
'<a href="https://www.mathworks.com/help/coder/ref/coder.make.buildtool-class.html#btwlgmg">documentation</a>' ...
' on how to add a C++ build tool.']);

% Create source file to compile
prodName = 'bar';
srcFile = [prodName '.cpp'];
fid = fopen(srcFile, 'w');
fwrite(fid, ['#include <iostream>' newline 'int bar() { volatile int a = 0; return a++;} int main() {std::cout << "Hello World";return 0;}']);
fclose(fid);

% Create the buildInfo object
lBuildInfo = i_createBuildInfo(prodName,srcFile);

% Do the build for the static library
codebuild(lBuildInfo, ...
'BuildMethod', buildMethod, ...
'BuildVariant', 'STATIC_LIBRARY');

% Verify that final products were created
compileSharedLibrary = false;
verifyFinalProduct(test, prodName,compileSharedLibrary);

end

% Build an executable from a C source file
function buildCSharedLibrary(test, buildMethod)

% Determine if BuildMethod is a template makefile and filter
% the test out if it is
test.assumeTrue(~contains(buildMethod,'.tmf'),...
'Skipping the scenario of building a shared library with a template makefile.');

% Create source file to compile
prodName = 'foo';
srcFile = [prodName '.c'];
fid = fopen(srcFile, 'w');
fwrite(fid, ['#include <stdio.h>' newline 'int foo() { volatile int a = 0; return a++; } int main() {printf("Hello World.\n");}']);
fclose(fid);

% On win64, additionally need a .def file for some compilers
if ispc
fid = fopen([prodName '.def'], 'w');
fprintf(fid, '%s\n%s', 'EXPORTS', 'foo');
fclose(fid);
end

% Create the buildInfo object
lBuildInfo = i_createBuildInfo(prodName,srcFile);

% Do the build for the static library
codebuild(lBuildInfo, ...
'BuildMethod', buildMethod, ...
'BuildVariant', 'SHARED_LIBRARY');

% Verify that final products were created
compileSharedLibrary = true;
verifyFinalProduct(test, prodName,compileSharedLibrary);

end

% If buildMethod has a C++ compiler, build an executable from a
% cpp file
function buildCPPSharedLibrary(test, buildMethod)

% Determine if BuildMethod is a template makefile and filter
% the test out if it is
test.assumeTrue(~contains(buildMethod,'.tmf'),...
'Skipping the scenario of building a shared library with a template makefile.');

% Determine if BuildMethod supports C++ compilation and filter
% the test out if not
test.assumeTrue(~contains(buildMethod,'lcc','IgnoreCase',true) && ...
~isempty(test.TcInfo.BuildTools.getValue('C++ Compiler')),...
['Skipping C++ compilation as buildMethod does not define a C++ build tool. If you expect ' ...
buildMethod ' to support C++ compilation, see the following ' ...
'<a href="https://www.mathworks.com/help/coder/ref/coder.make.buildtool-class.html#btwlgmg">documentation</a>' ...
' on how to add a C++ build tool.']);

% Create source file to compile
prodName = 'bar';
srcFile = [prodName '.cpp'];
fid = fopen(srcFile, 'w');
fwrite(fid, ['#include <iostream>' newline 'int bar() { volatile int a = 0; return a++;} int main() {std::cout << "Hello World";return 0;}']);
fclose(fid);

% On win64, additionally need a .def file for some compilers
if ispc
fid = fopen([prodName '.def'], 'w');
fprintf(fid, '%s\n%s', 'EXPORTS', 'bar');
fclose(fid);
end

% Create the buildInfo object
lBuildInfo = i_createBuildInfo(prodName,srcFile);

% Do the build for the static library
codebuild(lBuildInfo, ...
'BuildMethod', buildMethod, ...
'BuildVariant', 'SHARED_LIBRARY');

% Verify that final products were created
compileSharedLibrary = true;
verifyFinalProduct(test, prodName,compileSharedLibrary);

end
end

methods(Access=private)

function verifyFinalProduct(test, prodName,compileSharedLibrary)
import matlab.unittest.constraints.IsFile;

if compileSharedLibrary
% Get library extension
sharedLibSuffix = test.TcInfo.BuildTools.getValue('Linker').getFileExtension('Shared Library');
% Get expected product name and check its existance
expectedSharedLibProduct = [prodName sharedLibSuffix];
test.verifyThat(expectedSharedLibProduct,IsFile, 'Verify that the expected shared library file exists');
else
% Get library extension
staticLibSuffix = test.TcInfo.BuildTools.getValue('Archiver').getFileExtension('Static Library');
expectedStaticLibProduct = [prodName staticLibSuffix];
% Get expected product name and check its existance
test.verifyThat(expectedStaticLibProduct,IsFile,'Verify that the expected static library file exists');
end
end
end
end

function lBuildInfo = i_createBuildInfo(prodName,srcFile)
lBuildInfo = RTW.BuildInfo;
lBuildInfo.ComponentName = prodName;
addSourcePaths(lBuildInfo, {'.'}, {'BuildDir'});
addSourceFiles(lBuildInfo, srcFile, '.');
end