FindMatlab: Rewrite module and provide a usage API

Implement a brand new FindMatlab module:

- Add support for versions and components.
- Find Matlab and its version in a more precise and multiplatform way.
- Add API to create a new mex extension with documentation.
- Add API to add matlab unit tests (with or without the unit test framework).
- Find as much as possible based on a single Matlab_ROOT_DIR cache entry
  and allow the user to change it to re-find everything.
This commit is contained in:
Raffi Enficiaud 2015-02-12 17:13:31 +01:00 committed by Brad King
parent 6390d5f5cb
commit 49c8dcf7bb
20 changed files with 1798 additions and 94 deletions

View File

@ -0,0 +1,7 @@
FindMatlab-rewrite
------------------
* The :module:`FindMatlab` module was completely rewritten. It learned
about versions and components and to find Matlab in a more precise and
multiplatform way. The module now offers APIs to create mex extensions,
documentation, and unit tests.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,92 @@
# This is an undocumented internal helper for the FindMatlab
# module ``matlab_add_unit_test`` command.
#=============================================================================
# Copyright 2014-2015 Raffi Enficiaud, Max Planck Society
#
# Distributed under the OSI-approved BSD License (the "License");
# see accompanying file Copyright.txt for details.
#
# This software is distributed WITHOUT ANY WARRANTY; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the License for more information.
#=============================================================================
# (To distribute this file outside of CMake, substitute the full
# License text for the above reference.)
# Usage: cmake
# -Dtest_timeout=180
# -Dworking_directory="."
# -Doutput_directory=
# -Dadditional_paths=""
# -Dno_unittest_framework=""
# -DMatlab_PROGRAM=matlab_exe_location
# -DMatlab_ADDITIONNAL_STARTUP_OPTIONS=""
# -Dtest_name=name_of_the_test
# -Dcmd_to_run_before_test=""
# -Dunittest_file_to_run
# -P FindMatlab_TestsRedirect.cmake
set(Matlab_UNIT_TESTS_CMD -nosplash -nojvm -nodesktop -nodisplay ${Matlab_ADDITIONNAL_STARTUP_OPTIONS})
if(WIN32)
set(Matlab_UNIT_TESTS_CMD ${Matlab_UNIT_TESTS_CMD} -wait)
endif()
if(NOT test_timeout)
set(test_timeout 180)
endif()
if(NOT cmd_to_run_before_test)
set(cmd_to_run_before_test)
endif()
get_filename_component(unittest_file_directory "${unittest_file_to_run}" DIRECTORY)
get_filename_component(unittest_file_to_run_name "${unittest_file_to_run}" NAME_WE)
set(concat_string '${unittest_file_directory}')
foreach(s IN LISTS additional_paths)
if(NOT "${s}" STREQUAL "")
set(concat_string "${concat_string}, '${s}'")
endif()
endforeach()
set(unittest_to_run "runtests('${unittest_file_to_run_name}'), exit(max([ans(1,:).Failed]))")
if(no_unittest_framework)
set(unittest_to_run "try, ${unittest_file_to_run_name}, catch err, disp('An exception has been thrown during the execution'), disp(err), disp(err.stack), exit(1), end, exit(0)")
endif()
set(Matlab_SCRIPT_TO_RUN
"addpath(${concat_string}), path, ${cmd_to_run_before_test}, ${unittest_to_run}"
)
set(Matlab_LOG_FILE "${output_directory}/${test_name}.log")
set(devnull)
if(UNIX)
set(devnull INPUT_FILE /dev/null)
elseif(WIN32)
set(devnull INPUT_FILE NUL)
endif()
execute_process(
COMMAND "${Matlab_PROGRAM}" ${Matlab_UNIT_TESTS_CMD} -logfile "${test_name}.log" -r "${Matlab_SCRIPT_TO_RUN}"
RESULT_VARIABLE res
TIMEOUT ${test_timeout}
OUTPUT_QUIET # we do not want the output twice
WORKING_DIRECTORY "${output_directory}"
${devnull}
)
if(NOT EXISTS ${Matlab_LOG_FILE})
message( FATAL_ERROR "[MATLAB] ERROR: cannot find the log file ${Matlab_LOG_FILE}")
endif()
# print the output in any case.
file(READ ${Matlab_LOG_FILE} matlab_log_content)
message("Matlab test ${name_of_the_test} output:\n${matlab_log_content}") # if we put FATAL_ERROR here, the file is indented.
if(NOT (res EQUAL 0))
message( FATAL_ERROR "[MATLAB] TEST FAILED" )
endif()

View File

@ -1256,6 +1256,12 @@ ${CMake_BINARY_DIR}/bin/cmake -DDIR=dev -P ${CMake_SOURCE_DIR}/Utilities/Release
add_subdirectory(FindJsonCpp) add_subdirectory(FindJsonCpp)
endif() endif()
# Matlab module
if(CMake_TEST_FindMatlab)
ADD_TEST_MACRO(FindMatlab.basic_checks ${CMAKE_CTEST_COMMAND} -C $<CONFIGURATION>)
ADD_TEST_MACRO(FindMatlab.versions_checks ${CMAKE_CTEST_COMMAND} -C $<CONFIGURATION>)
endif()
find_package(GTK2 QUIET) find_package(GTK2 QUIET)
if(GTK2_FOUND) if(GTK2_FOUND)
add_subdirectory(FindGTK2) add_subdirectory(FindGTK2)

View File

@ -0,0 +1,57 @@
cmake_minimum_required (VERSION 2.8.12)
enable_testing()
project(basic_checks)
set(MATLAB_FIND_DEBUG TRUE)
# the success of the following command is dependent on the current configuration:
# - on 32bits builds (cmake is building with 32 bits), it looks for 32 bits Matlab
# - on 64bits builds (cmake is building with 64 bits), it looks for 64 bits Matlab
find_package(Matlab REQUIRED COMPONENTS MX_LIBRARY MAIN_PROGRAM)
matlab_add_mex(
# target name
NAME cmake_matlab_test_wrapper1
# output name
OUTPUT_NAME cmake_matlab_mex1
SRC ${CMAKE_CURRENT_SOURCE_DIR}/../matlab_wrapper1.cpp
DOCUMENTATION ${CMAKE_CURRENT_SOURCE_DIR}/../help_text1.m.txt
)
matlab_add_unit_test(
NAME ${PROJECT_NAME}_matlabtest-1
TIMEOUT 30
UNITTEST_FILE ${CMAKE_CURRENT_SOURCE_DIR}/../cmake_matlab_unit_tests1.m
ADDITIONAL_PATH $<TARGET_FILE_DIR:cmake_matlab_test_wrapper1>
)
matlab_add_unit_test(
NAME ${PROJECT_NAME}_matlabtest-2
TIMEOUT 15
UNITTEST_FILE ${CMAKE_CURRENT_SOURCE_DIR}/../cmake_matlab_unit_tests_timeout.m
ADDITIONAL_PATH $<TARGET_FILE_DIR:cmake_matlab_test_wrapper1>
)
set_tests_properties(${PROJECT_NAME}_matlabtest-2 PROPERTIES WILL_FAIL TRUE)
# testing the test without the unittest framework of Matlab
matlab_add_unit_test(
NAME ${PROJECT_NAME}_matlabtest-3
TIMEOUT 30
NO_UNITTEST_FRAMEWORK
UNITTEST_FILE ${CMAKE_CURRENT_SOURCE_DIR}/../cmake_matlab_unit_tests2.m
ADDITIONAL_PATH $<TARGET_FILE_DIR:cmake_matlab_test_wrapper1>
)
matlab_add_unit_test(
NAME ${PROJECT_NAME}_matlabtest-4
TIMEOUT 30
NO_UNITTEST_FRAMEWORK
UNITTEST_FILE ${CMAKE_CURRENT_SOURCE_DIR}/../cmake_matlab_unit_tests3.m
ADDITIONAL_PATH $<TARGET_FILE_DIR:cmake_matlab_test_wrapper1>
)
set_tests_properties(${PROJECT_NAME}_matlabtest-4 PROPERTIES WILL_FAIL TRUE)

View File

@ -0,0 +1,33 @@
classdef cmake_matlab_unit_tests1 < matlab.unittest.TestCase
% some simple unit test for CMake Matlab wrapper
properties
end
methods (Test)
function testDummyCall(testCase)
% very simple call test
cmake_matlab_mex1(rand(3,3));
end
function testDummyCall2(testCase)
% very simple call test 2
ret = cmake_matlab_mex1(rand(3,3));
testCase.verifyEqual(size(ret), size(rand(3,3)));
testCase.verifyEqual(size(cmake_matlab_mex1(rand(4,3))), [4,3] );
end
function testFailTest(testCase)
testCase.verifyError(@() cmake_matlab_mex1(10), 'cmake_matlab:configuration');
testCase.verifyError(@() cmake_matlab_mex1([10]), 'cmake_matlab:configuration');
end
function testHelpContent(testCase)
% testing the help feature
testCase.verifySubstring(evalc('help cmake_matlab_mex1'), 'Dummy matlab extension in cmake');
end
end
end

View File

@ -0,0 +1,6 @@
ret = cmake_matlab_mex1(rand(3,3));
if(size(ret) ~= size(rand(3,3)))
error('Dimension mismatch!');
end

View File

@ -0,0 +1,5 @@
cmake_matlab_mex1(10);
% should not reach this point
exit(0);

View File

@ -0,0 +1,16 @@
classdef cmake_matlab_unit_tests_timeout < matlab.unittest.TestCase
% timeout tests
properties
end
methods (Test)
function testCallHangsShouldBeTimedOut(testCase)
cmake_matlab_mex1(rand(3,3));
disp('Will now wait.');
disp('Testing the cmake Matlab package timeout - do not kill');
pause(20); % supposed to be killed after 15s
end
end
end

View File

@ -0,0 +1,2 @@
% Dummy matlab extension in cmake
function ret = cmake_matlab_mex1(X)

View File

@ -0,0 +1,26 @@
// simple workaround to some compiler specific problems
// see http://stackoverflow.com/questions/22367516/mex-compile-error-unknown-type-name-char16-t/23281916#23281916
#include <algorithm>
#include "mex.h"
// this test should return a matrix of 10 x 10 and should check some of the arguments
void mexFunction(const int nlhs, mxArray *plhs[], const int nrhs, const mxArray *prhs[])
{
if(nrhs != 1)
{
mexErrMsgTxt("Incorrect arguments");
}
size_t dim1 = mxGetM(prhs[0]);
size_t dim2 = mxGetN(prhs[0]);
if(dim1 == 1 || dim2 == 1)
{
mexErrMsgIdAndTxt("cmake_matlab:configuration", "Incorrect arguments");
}
plhs[0] = mxCreateNumericMatrix(dim1, dim2, mxGetClassID(prhs[0]), mxREAL);
}

View File

@ -0,0 +1,52 @@
cmake_minimum_required (VERSION 2.8.12)
enable_testing()
project(versions_checks)
set(MATLAB_FIND_DEBUG TRUE)
set(MATLAB_ADDITIONAL_VERSIONS
"dummy=14.9")
# the success of the following command is dependent on the current configuration
# in this case, we are only interested in the version macros
find_package(Matlab)
if(NOT COMMAND matlab_get_version_from_release_name)
message(FATAL_ERROR "The macro matlab_get_version_from_release_name should be defined")
endif()
if(NOT COMMAND matlab_get_release_name_from_version)
message(FATAL_ERROR "The macro matlab_get_release_name_from_version should be defined")
endif()
# matlab_get_release_name_from_version
matlab_get_release_name_from_version("7.13" release_name)
if(NOT release_name STREQUAL "R2011b")
message(FATAL_ERROR "version 7.13 does not give release R2011b : '${release_name}' != R2011b")
endif()
matlab_get_release_name_from_version("14.9" release_name)
if(NOT release_name STREQUAL "dummy")
message(FATAL_ERROR "version 14.9 does not give release dummy : '${release_name}' != dummy")
endif()
matlab_get_release_name_from_version("14.10" release_name)
if(NOT release_name STREQUAL "")
message(FATAL_ERROR "version 14.10 does not give empty release: '${release_name}' != ''")
endif()
# matlab_get_version_from_release_name
matlab_get_version_from_release_name("R2011a" version)
if(NOT version STREQUAL "7.12")
message(FATAL_ERROR "Release R2011a does not give version 7.12 : '${version}' != 7.12")
endif()
matlab_get_version_from_release_name("dummy" version)
#message(FATAL_ERROR "versionversion = ${version}")
if(NOT version STREQUAL "14.9")
message(FATAL_ERROR "Release dummy does not give version 14.9 : '${version}' != 14.9")
endif()

View File

@ -211,3 +211,8 @@ if(RPMBUILD_EXECUTABLE)
endif() endif()
add_RunCMake_test(COMPILE_LANGUAGE-genex) add_RunCMake_test(COMPILE_LANGUAGE-genex)
# Matlab module related tests
if(CMake_TEST_FindMatlab)
add_RunCMake_test(FindMatlab)
endif()

View File

@ -0,0 +1,3 @@
cmake_minimum_required(VERSION 2.8.12)
include(${RunCMake_TEST}.cmake)

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,2 @@
CMake Error at .*FindMatlab.cmake:[0-9]+ \(message\):
\[MATLAB\] This functionality needs the MAIN_PROGRAM component \(not default\)

View File

@ -0,0 +1,22 @@
cmake_minimum_required (VERSION 2.8.12)
enable_testing()
project(test_should_fail)
find_package(Matlab REQUIRED COMPONENTS MX_LIBRARY)
matlab_add_mex(
# target name
NAME cmake_matlab_test_wrapper1
# output name
OUTPUT_NAME cmake_matlab_mex1
SRC ${CMAKE_CURRENT_SOURCE_DIR}/matlab_wrapper1.cpp
)
# this command should raise a FATAL_ERROR, component MAIN_PROGRAM is missing
matlab_add_unit_test(
NAME ${PROJECT_NAME}_matlabtest-1
TIMEOUT 1
UNITTEST_FILE ${CMAKE_CURRENT_SOURCE_DIR}/cmake_matlab_unit_tests2.m
ADDITIONAL_PATH $<TARGET_FILE_DIR:cmake_matlab_test_wrapper1>
)

View File

@ -0,0 +1,3 @@
include(RunCMake)
run_cmake(MatlabTest1)

View File

@ -0,0 +1,6 @@
ret = cmake_matlab_mex1(rand(3,3));
if(size(ret) ~= size(rand(3,3)))
error('Dimension mismatch!');
end

View File

@ -0,0 +1,26 @@
// simple workaround to some compiler specific problems
// see http://stackoverflow.com/questions/22367516/mex-compile-error-unknown-type-name-char16-t/23281916#23281916
#include <algorithm>
#include "mex.h"
// this test should return a matrix of 10 x 10 and should check some of the arguments
void mexFunction(const int nlhs, mxArray *plhs[], const int nrhs, const mxArray *prhs[])
{
if(nrhs != 1)
{
mexErrMsgTxt("Incorrect arguments");
}
size_t dim1 = mxGetM(prhs[0]);
size_t dim2 = mxGetN(prhs[0]);
if(dim1 == 1 || dim2 == 1)
{
mexErrMsgIdAndTxt("cmake_matlab:configuration", "Incorrect arguments");
}
plhs[0] = mxCreateNumericMatrix(dim1, dim2, mxGetClassID(prhs[0]), mxREAL);
}