##============================================================================ ## The contents of this file are covered by the Viskores license. See ## LICENSE.txt for details. ## ## By contributing to this file, all contributors agree to the Developer ## Certificate of Origin Version 1.1 (DCO 1.1) as stated in DCO.txt. ##============================================================================ ##============================================================================ ## Copyright (c) Kitware, Inc. ## All rights reserved. ## See LICENSE.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 above copyright notice for more information. ##============================================================================ ## This CMake script checks source files for the appropriate VISKORES license ## statement, which is stored in Viskores_SOURCE_DIR/CMake/ViskoresLicenseStatement.txt. ## To run this script, execute CMake as follows: ## ## cmake -DViskores_SOURCE_DIR= -P /CMake/VISKORESCheckLicense.cmake ## cmake_minimum_required(VERSION 3.12) set(FILES_TO_CHECK *.txt *.cmake *.h *.h.in *.hxx *.cxx *.cu *.py *.sh *.ps1 Dockerfile *.yaml *.yml ) set(EXCEPTIONS LICENSE.txt DCO.txt README.txt docs/users-guide/requirements.txt ) if (NOT Viskores_SOURCE_DIR) message(SEND_ERROR "Viskores_SOURCE_DIR not defined.") endif (NOT Viskores_SOURCE_DIR) set(license_statement_file ${Viskores_SOURCE_DIR}/CMake/ViskoresLicenseStatement.txt) if (NOT EXISTS ${license_statement_file}) message(SEND_ERROR "Cannot find VISKORESLicenseStatement.txt.") endif (NOT EXISTS ${license_statement_file}) set(license_file ${Viskores_SOURCE_DIR}/DCO.txt) if (NOT EXISTS ${license_file}) message(SEND_ERROR "Cannot find LICENSE.txt.") endif (NOT EXISTS ${license_file}) # Get a list of third party files (with different licenses) from the # license file. file(STRINGS ${license_file} license_lines) list(FIND license_lines "- - - - - - - - - - - - - - - - - - - - - - - - do not remove this line" separator_index ) math(EXPR begin_index "${separator_index} + 1") list(LENGTH license_lines license_file_length) math(EXPR end_index "${license_file_length} - 1") foreach (index RANGE ${begin_index} ${end_index}) list(GET license_lines ${index} tpl_file) set(EXCEPTIONS ${EXCEPTIONS} ${tpl_file}) endforeach(index) # if the build directory is in the source directory, exclude generated build # files find_path(BUILD_DIR CMakeCache.txt .) get_filename_component(abs_build_dir ${BUILD_DIR} ABSOLUTE) get_filename_component(build_dir_name ${abs_build_dir} NAME) set(EXCEPTIONS ${EXCEPTIONS} ${build_dir_name}/*) message("License Check Exceptions: ${EXCEPTIONS}") # Gets the current year (if possible). function (get_year var) string(TIMESTAMP result "%Y") set(${var} "${result}" PARENT_SCOPE) endfunction (get_year) set(license_file_year 2014) get_year(current_year) # Escapes ';' characters (list delimiters) and splits the given string into # a list of its lines without newlines. function (list_of_lines var string) string(REGEX REPLACE ";" "\\\\;" conditioned_string "${string}") string(REGEX REPLACE "\n" ";" conditioned_string "${conditioned_string}") set(${var} "${conditioned_string}" PARENT_SCOPE) endfunction (list_of_lines) # Read in license statement file. file(READ ${license_statement_file} LICENSE_STATEMENT) # Remove trailing whitespace and ending lines. They are sometimes hard to # see or remove in editors. string(REGEX REPLACE "[ \t]*\n" "\n" LICENSE_STATEMENT "${LICENSE_STATEMENT}") string(REGEX REPLACE "\n+$" "" LICENSE_STATEMENT "${LICENSE_STATEMENT}") # Get a list of lines in the license statement. list_of_lines(LICENSE_LINE_LIST "${LICENSE_STATEMENT}") # Comment regular expression characters that we want to match literally. string(REPLACE "." "\\." LICENSE_LINE_LIST "${LICENSE_LINE_LIST}") string(REPLACE "(" "\\(" LICENSE_LINE_LIST "${LICENSE_LINE_LIST}") string(REPLACE ")" "\\)" LICENSE_LINE_LIST "${LICENSE_LINE_LIST}") # Introduce regular expression for years we want to be generic. string(REPLACE "${license_file_year}" "20[0-9][0-9]" LICENSE_LINE_LIST "${LICENSE_LINE_LIST}" ) # Replace year in LICENSE_STATEMENT with current year. string(REPLACE "${license_file_year}" "${current_year}" LICENSE_STATEMENT "${LICENSE_STATEMENT}" ) # Print an error concerning the missing license in the given file. function(missing_license filename comment_prefix) message("${filename} does not have the appropriate license statement:\n") # Condition the license statement string(REPLACE "\n" "\n${comment_prefix} " comment_license "${LICENSE_STATEMENT}" ) set(comment_license "${comment_prefix} ${comment_license}") string(REPLACE "\n${comment_prefix} \n" "\n${comment_prefix}\n" comment_license "${comment_license}" ) message("${comment_prefix}=============================================================================") message("${comment_prefix}") message("${comment_license}") message("${comment_prefix}") message("${comment_prefix}=============================================================================\n") message(SEND_ERROR "Please add the previous statement to the beginning of ${filename}" ) endfunction(missing_license) # Get an appropriate beginning line comment for the given filename. function(get_comment_prefix var filename) get_filename_component(name "${filename}" NAME) if(name MATCHES "\\.(cmake|py|sh|ps1|yaml|yml)$" OR name STREQUAL "CMakeLists.txt" OR name STREQUAL "Dockerfile" ) set(${var} "##" PARENT_SCOPE) elseif (name MATCHES "\\.(h|h\\.in|hxx|cxx|cu)$") set(${var} "//" PARENT_SCOPE) elseif (name MATCHES "\\.txt$") set(${var} "" PARENT_SCOPE) else () message(FATAL_ERROR "Could not identify file type of `${name}`.") endif () endfunction(get_comment_prefix) # Check the given file for the appropriate license statement. function(check_license filename) get_comment_prefix(comment_prefix "${filename}") # Read in the first 2000 characters of the file and split into lines. # This is roughly equivalent to the file STRINGS command except that we # also escape semicolons (list separators) in the input, which the file # STRINGS command does not currently do. file(READ "${filename}" header_contents LIMIT 2000) list_of_lines(header_lines "${header_contents}") set(printed) # Check each license line. foreach (license_line IN LISTS LICENSE_LINE_LIST) set(match) # My original algorithm tried to check the order by removing items from # header_lines as they were encountered. Unfortunately, CMake 2.8's # list REMOVE_AT command removed the escaping on the ; in one of the # header_line's items and cause the compare to fail. foreach (header_line IN LISTS header_lines) if (license_line) string(REGEX MATCH "^${comment_prefix}[ \t]*${license_line}[ \t]*$" match "${header_line}" ) else (license_line) if (NOT header_line) set(match TRUE) endif (NOT header_line) endif (license_line) if (match) break() endif (match) endforeach (header_line) if (NOT match AND NOT printed) message(STATUS "Could not find match for `${license_line}'") missing_license("${filename}" "${comment_prefix}") set(printed TRUE) endif (NOT match AND NOT printed) endforeach (license_line) endfunction(check_license) foreach (glob_expression ${FILES_TO_CHECK}) file(GLOB_RECURSE file_list RELATIVE "${Viskores_SOURCE_DIR}" "${Viskores_SOURCE_DIR}/${glob_expression}" ) foreach (file ${file_list}) set(skip) foreach(exception ${EXCEPTIONS}) if(file MATCHES "^${exception}(/.*)?$") # This file is an exception set(skip TRUE) endif(file MATCHES "^${exception}(/.*)?$") endforeach(exception) if (NOT skip) check_license("${Viskores_SOURCE_DIR}/${file}") endif (NOT skip) endforeach (file) endforeach (glob_expression)