Difference between revisions of "Documentation/Nightly/Developers/Tutorials/MigrationGuide/SlicerExtension"

From Slicer Wiki
Jump to: navigation, search
Line 26: Line 26:
 
<pre>
 
<pre>
 
#if ITK_VERSION_MAJOR >= 5
 
#if ITK_VERSION_MAJOR >= 5
   itk::ITK_THREAD_RETURN_VALUE
+
   itk::ITK_THREAD_RETURN_DEFAULT_VALUE
 
#else
 
#else
 
   ITK_THREAD_RETURN_VALUE
 
   ITK_THREAD_RETURN_VALUE

Revision as of 14:22, 17 April 2019

Home < Documentation < Nightly < Developers < Tutorials < MigrationGuide < SlicerExtension

Slicer Extension updates

Slicer 5.0: Supporting both ITKv4 and ITKv5

If completely removing support for ITKv4 and only supporting ITKv5 is not possible. It is possible to conditionally include code.

Here are examples originally associated with PR#1069 ITK v5 updates

#if ITK_VERSION_MAJOR >= 5
#include "itkMultiThreaderBase.h"
#else
#include "itkMultiThreader.h"
#endif
#if ITK_VERSION_MAJOR >= 5
  itk::ITK_THREAD_RETURN_TYPE
#else
  ITK_THREAD_RETURN_TYPE
#endif
#if ITK_VERSION_MAJOR >= 5
  itk::ITK_THREAD_RETURN_DEFAULT_VALUE
#else
  ITK_THREAD_RETURN_VALUE
#endif
#if ITK_VERSION_MAJOR >= 5
  this->ProcessingThreader = itk::PlatformMultiThreader::New();
#else
  this->ProcessingThreader = itk::MultiThreader::New();
#endif
#if ITK_VERSION_MAJOR >= 5
    (((itk::PlatformMultiThreader::WorkUnitInfo *)(arg))->UserData);
#else
    (((itk::MultiThreader::ThreadInfoStruct *)(arg))->UserData);
#endif
#if ITK_VERSION_MAJOR >= 5
  itk::PlatformMultiThreader::Pointer ProcessingThreader;
#else
  itk::MultiThreader::Pointer ProcessingThreader;
#endif
#if ITK_VERSION_MAJOR >= 5
  m_ThreadCount( MultiThreaderBase::GetGlobalDefaultNumberOfThreads() ),
#else
  m_ThreadCount( MultiThreader::GetGlobalDefaultNumberOfThreads() ),
#endif
#if ITK_VERSION_MAJOR >= 5
    m_And[threadId]->SetNumberOfWorkUnits( 1 );
#else
    m_And[threadId]->SetNumberOfThreads( 1 );
#endif

Slicer 5.0: Python2 to Python3

Depending on the complexity of the extension, two approaches shall be considered:

  • code base common to Slicer 4.10 and Slicer 5.0. This means backward compatibility with the latest release is maintained.
  • specific branch for each Slicer version (e.g master-4.10 and master)


Example of commits


Step 1

Understanding the scope of changes needed to support Python 3 can be done by:

  1. installing future package (see https://python-future.org/)
    Slicer_DIR=/path/to/Slicer-SuperBuild/Slicer-build
    ${Slicer_DIR}/../python-install/bin/PythonSlicer -m pip install future
    

    Note Alternatively the future package could be installed in a regular python environment and used from there.

  2. applying all fixes
      for f in `find ./ -name "*.py"`; do \
        ${Slicer_DIR}/../python-install/bin/PythonSlicer \
          --launch futurize --nobackups --write $f; \
      done
    

This first step will apply the following transformations:

lib2to3.fixes.fix_apply
lib2to3.fixes.fix_dict
lib2to3.fixes.fix_except
lib2to3.fixes.fix_exec
lib2to3.fixes.fix_exitfunc
lib2to3.fixes.fix_filter
lib2to3.fixes.fix_funcattrs
lib2to3.fixes.fix_getcwdu
lib2to3.fixes.fix_has_key
lib2to3.fixes.fix_input
lib2to3.fixes.fix_intern
lib2to3.fixes.fix_isinstance
lib2to3.fixes.fix_itertools
lib2to3.fixes.fix_itertools_imports
lib2to3.fixes.fix_long
lib2to3.fixes.fix_map
lib2to3.fixes.fix_methodattrs
lib2to3.fixes.fix_ne
lib2to3.fixes.fix_next
lib2to3.fixes.fix_nonzero
lib2to3.fixes.fix_numliterals
lib2to3.fixes.fix_operator
lib2to3.fixes.fix_paren
lib2to3.fixes.fix_raw_input
lib2to3.fixes.fix_reduce
lib2to3.fixes.fix_renamesSlicer migration guide describes how to update the code. See https://www.slicer.org/wiki/Documentation/Nightly/Developers/Tutorials/MigrationGuide/SlicerExtension#Slicer_5.0:_Python2_to_Python3
lib2to3.fixes.fix_repr
lib2to3.fixes.fix_standarderror
lib2to3.fixes.fix_sys_exc
lib2to3.fixes.fix_throw
lib2to3.fixes.fix_tuple_params
lib2to3.fixes.fix_types
lib2to3.fixes.fix_xreadlines
lib2to3.fixes.fix_zip
libfuturize.fixes.fix_absolute_import
libfuturize.fixes.fix_basestring
libfuturize.fixes.fix_cmp
libfuturize.fixes.fix_division_safe
libfuturize.fixes.fix_execfile
libfuturize.fixes.fix_future_builtins
libfuturize.fixes.fix_future_standard_library
libfuturize.fixes.fix_future_standard_library_urllib
libfuturize.fixes.fix_metaclass
libfuturize.fixes.fix_next_call
libfuturize.fixes.fix_object
libfuturize.fixes.fix_print_with_import
libfuturize.fixes.fix_raise
libfuturize.fixes.fix_unicode_keep_u
libfuturize.fixes.fix_xrange_with_import
libpasteurize.fixes.fix_newstyle

expect these two:

lib2to3.fixes.fix_idioms
lib2to3.fixes.fix_ws_comma

Note If the number of changes is large, you should consider applying each fixes independently.


Step 2

Not all changes applied automatically should be integrated. Most of the changes introducing imports from the future python package can simply be removed or removed after making use of try/except or check of sys.version_info[0].

  • Systematic conversion of keys, values or items from dictionaries do not need to be systematically converted from dict_keys/dict_values/dict_items to list. For example, this Slicer r28077 reverted some of the automatic changes applied by the future CLI.
  • Unless python classes implement the object functions next and __unicode__ specific to Python 2, nothing specific should be done and from builtins import object can be removed. Automatic removal of imports can also be automated doing:
 for f in `find ./ -name "*.py"`; do \
   sed -i '/from builtins import object/ d' $f; \
 done
  • Unless there are performances issue associated with using range in Python 2, the from builtins import range can be removed:
 for f in `find ./ -name "*.py"`; do \
   sed -i '/from builtins import range/ d' $f; \
 done
  • If there are performance issue with using range in Python 2, the following could also be done:
 import sys
 if sys.version_info[0] == 2:
   range = xrange
  • Use of aliases can also be avoided by removing
 from future import standard_library
 standard_library.install_aliases()

and instead doing something like this:

 try:
   import queue
 except ImportError:
   import Queue as queue

For a complete list aliases, see https://python-future.org/reference.html#module-future.standard_library

  • Use of old_div() function can generally be avoided by using int()

Step 3

The lib2to3.fixes.fix_idioms transformation should explicitly be applied:

  for f in `find ./ -name "*.py"`; do \
    ${Slicer_DIR}/../python-install/bin/PythonSlicer \
      --launch futurize -f lib2to3.fixes.fix_idioms --nobackups --write $f; \
  done

Slicer 4.9: Explicit initialization of CMAKE_BUILD_TYPE not needed anymore

Following r26978, calling find_package(Slicer REQUIRED) and include(${Slicer_USE_FILE}) initializes CMAKE_BUILD_TYPE and ensures the variables CMAKE_BUILD_TYPE and CMAKE_CONFIGURATION_TYPES are passed to all external projects when configuring SuperBuild based extension.

The module SlicerInitializeBuildType is automatically included in UserSlicer.

Slicer 4.9: Subversion not required anymore

Following r27060, Subversion is not required anymore.

Slicer 4.9: Support EP_GIT_PROTOCOL and use of ExternalProject_SetIfNotDefined for setting GIT_REPOSITORY, GIT_TAG and alike

Following r26957, extension may use the following convention to define GIT_REPOSITORY and GIT_TAG, this allows developer to override the value before the first configuration by setting the corresponding environment variable, or by explicitly configuring the project with that variable.

The option EP_GIT_PROTOCOL is also already set in ExternalProjectDependency module included by Slicer and its value is updated based on the <SUPERBUILD_TOPLEVEL_PROJECT>_USE_GIT_PROTOCOL option.

  ExternalProject_SetIfNotDefined(
    ${CMAKE_PROJECT_NAME}_${proj}_GIT_REPOSITORY
    "${EP_GIT_PROTOCOL}://github.com/jcfr/shape4D.git"
    QUIET
    )

  ExternalProject_SetIfNotDefined(
    ${CMAKE_PROJECT_NAME}_${proj}_GIT_TAG
    "12fef84ca2a56feffc59d8159bdadd2ce4a4138e" # slicersalt-2018-01-22-c74c766a4c
    QUIET
    )

See:

Slicer 4.9: Use ExternalProject_AlwaysConfigure to force reconfigure of inner project

Following r26551, the function ExternalProject_AlwaysConfigure may be used to ensure the inner project is always reconfigured.

Using the `BUILD_ALWAYS` option supported by ExternalProject_Add will not have the intended effect.

See https://cmake-artichoke.readthedocs.io/en/latest/ExternalProjectDependency.html#function:ExternalProject_AlwaysConfigure

Slicer 4.9: Specifying external projects to install in SuperBuild extension

Following r27267 and r27232, the following should be used to ensure the extension can also be bundled into a Slicer custom application. When bundled, the variable ${EXTENSION_NAME}_CPACK_INSTALL_CMAKE_PROJECTS is then used to update the application's list of project to install.

#-----------------------------------------------------------------------------
set(EXTENSION_CPACK_INSTALL_CMAKE_PROJECTS)
#list(APPEND EXTENSION_CPACK_INSTALL_CMAKE_PROJECTS "${Foo_DIR};Foo;RuntimeLibraries;/")
set(${EXTENSION_NAME}_CPACK_INSTALL_CMAKE_PROJECTS "${EXTENSION_CPACK_INSTALL_CMAKE_PROJECTS}" CACHE STRING "List of external projects to install" FORCE)

#-----------------------------------------------------------------------------
list(APPEND CPACK_INSTALL_CMAKE_PROJECTS "${CMAKE_BINARY_DIR};${EXTENSION_NAME};ALL;/")
list(APPEND CPACK_INSTALL_CMAKE_PROJECTS "${${EXTENSION_NAME}_CPACK_INSTALL_CMAKE_PROJECTS}")
include(${Slicer_EXTENSION_GENERATE_CONFIG})
include(${Slicer_EXTENSION_CPACK})

Slicer 4.9: Generating (Extension)Config.cmake

Initially introduced in r25944, and later improved in r25991, including ${Slicer_EXTENSION_GENERATE_CONFIG} ensure a config is generated and allow an extension to import targets from another extension by using find_package(ExtensionName REQUIRED).

[...]

include(${Slicer_EXTENSION_GENERATE_CONFIG})
include(${Slicer_EXTENSION_CPACK})

Slicer 4.9: Initializing <projectName>_BUILD_SLICER_EXTENSION option: Standalone vs Slicer extension build

The following snippet allows to automatically initialize <projectName>_BUILD_SLICER_EXTENSION to ON if Slicer_DIR is defined.

#-----------------------------------------------------------------------------
# Standalone vs Slicer extension option
#-----------------------------------------------------------------------------

# This option should be named after the project name, it corresponds to the
# option set to ON when the project is build by the Slicer Extension build
# system.

set(_default OFF)
set(_reason "${PROJECT_NAME}_BUILD_SLICER_EXTENSION is ON")
if(NOT DEFINED ${PROJECT_NAME}_BUILD_SLICER_EXTENSION AND DEFINED Slicer_DIR)
  set(_default ON)
  set(_reason "Slicer_DIR is SET")
endif()

option(${PROJECT_NAME}_BUILD_SLICER_EXTENSION "Build as a Slicer Extension" ${_default})

set(_msg "Checking if building as a Slicer extension")
message(STATUS ${_msg})
if(${PROJECT_NAME}_BUILD_SLICER_EXTENSION)
  message(STATUS "${_msg} - yes (${_reason})")
else()
  message(STATUS "${_msg} - no (${PROJECT_NAME}_BUILD_SLICER_EXTENSION is OFF)")
endif()
mark_as_superbuild(${PROJECT_NAME}_BUILD_SLICER_EXTENSION:BOOL)