diff options
author | Andreas Cord-Landwehr <cordlandwehr@kde.org> | 2020-09-26 10:32:56 +0000 |
---|---|---|
committer | Andreas Cord-Landwehr <cordlandwehr@kde.org> | 2020-09-26 10:32:56 +0000 |
commit | 7e73f1a972f13699c3c2cb80ad50c834b8a8464e (patch) | |
tree | 56c80b957ed9f6dd3f5130011ed866ae6512daaf | |
parent | 250932795701e8c6f88bf150dcf4d3668c3173c7 (diff) | |
download | extra-cmake-modules-7e73f1a972f13699c3c2cb80ad50c834b8a8464e.tar.gz extra-cmake-modules-7e73f1a972f13699c3c2cb80ad50c834b8a8464e.tar.bz2 |
Introduce plausibility check for outbound licenes
When creating a library or executable, several source files are combined
into a binary artifact that has an outbound license of its own. This test
generator allows to check if the combined source files are compatible
with the desired outbound license.
Requirements for using these tests:
- input source files must contain the SPDX-License-Information tag
- python3 must be available
- the "reuse spdx" tool must be available
-rw-r--r-- | docs/module/ECMCheckOutboundLicense.rst | 1 | ||||
-rw-r--r-- | find-modules/FindReuseTool.cmake | 23 | ||||
-rw-r--r-- | modules/ECMCheckOutboundLicense.cmake | 153 | ||||
-rwxr-xr-x | modules/check-outbound-license.py | 139 | ||||
-rw-r--r-- | tests/CMakeLists.txt | 5 | ||||
-rw-r--r-- | tests/ECMCheckOutboundLicenseTest/CMakeLists.txt | 88 | ||||
-rw-r--r-- | tests/ECMCheckOutboundLicenseTest/run_test.cmake.config | 0 | ||||
-rw-r--r-- | tests/ECMCheckOutboundLicenseTest/testdata/BSD-2-Clause.cpp | 6 | ||||
-rw-r--r-- | tests/ECMCheckOutboundLicenseTest/testdata/GPL-2.0-only.cpp | 6 | ||||
-rw-r--r-- | tests/ECMCheckOutboundLicenseTest/testdata/GPL-2.0-or-later.cpp | 6 | ||||
-rw-r--r-- | tests/ECMCheckOutboundLicenseTest/testdata/GPL-3.0-only.cpp | 6 | ||||
-rw-r--r-- | tests/ECMCheckOutboundLicenseTest/testdata/LGPL-2.1-or-later.cpp | 6 | ||||
-rw-r--r-- | tests/ECMCheckOutboundLicenseTest/testdata/LGPL-3.0-only.cpp | 6 |
13 files changed, 445 insertions, 0 deletions
diff --git a/docs/module/ECMCheckOutboundLicense.rst b/docs/module/ECMCheckOutboundLicense.rst new file mode 100644 index 00000000..3af46757 --- /dev/null +++ b/docs/module/ECMCheckOutboundLicense.rst @@ -0,0 +1 @@ +.. ecm-module:: ../../modules/ECMCheckOutboundLicense.cmake diff --git a/find-modules/FindReuseTool.cmake b/find-modules/FindReuseTool.cmake new file mode 100644 index 00000000..9377f49e --- /dev/null +++ b/find-modules/FindReuseTool.cmake @@ -0,0 +1,23 @@ +# WARNING: FOR ECM-INTERNAL USE ONLY, DO NOT USE IN OWN PROJECTS +# THIS FILE MIGHT DISAPPEAR IN FUTURE VERSIONS OF ECM. + +# Finds the REUSE Tool by FSFE: https://github.com/fsfe/reuse-tool +# +# REUSE_TOOL_FOUND - True if REUSE tool is found. +# REUSE_TOOL_EXECUTABLE - Path to executable + +#============================================================================= +# SPDX-FileCopyrightText: 2020 Andreas Cord-Landwehr <cordlandwehr@kde.org> +# +# SPDX-License-Identifier: BSD-3-Clause +#============================================================================= + +find_program(REUSETOOL_EXECUTABLE NAMES reuse) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(ReuseTool + FOUND_VAR + REUSETOOL_FOUND + REQUIRED_VARS + REUSE_TOOL_EXECUTABLE +) diff --git a/modules/ECMCheckOutboundLicense.cmake b/modules/ECMCheckOutboundLicense.cmake new file mode 100644 index 00000000..63e7ca01 --- /dev/null +++ b/modules/ECMCheckOutboundLicense.cmake @@ -0,0 +1,153 @@ +#.rst: +# ECMCheckOutboundLicense +# ----------------------- +# +# Assert that source file licenses are compatible with a desired outbound license +# of a compiled binary artifact (e.g., library, plugin or application). +# +# This module provides the ``ecm_check_outbound_license`` function that +# generates unit tests for checking the compatibility of license statements. +# The license statements in all tested files are required to be added by using +# the SPDX marker ``SPDX-License-Identifier``. +# +# During the CMake configuration of the project, a temporary license bill of +# materials (BOM) in SPDX format is generated by calling the REUSE tool +# (see <https://reuse.software>). That BOM is parsed and license computations +# based on an internal compatibility matrix are performed. +# +# Preconditions for using this module: +# * All tested input source files must contain the SPDX-License-Identifier tag. +# * Python3 must be available. +# * The REUSE tool must be available, which generates the bill-of-materials +# by running ``reuse spdx`` on the tested directory. +# +# When this module is included, a ``SKIP_LICENSE_TESTS`` option is added (default +# OFF). Turning this option on skips the generation of license tests, which might +# be convenient if licenses shall not be tested in all build configurations. +# +# :: +# +# ecm_check_outbound_license(LICENSES <outbound-licenses> +# FILES <source-files> +# [TEST_NAME <name>] +# [WILL_FAIL]) +# +# This method adds a custom unit test to ensure the specified outbound license to be +# compatible with the specified license headers. Note that a convenient way is to use +# the CMake GLOB command of the FILE function. +# +# ``LICENSES`` : List of one or multiple outbound license regarding which the compatibility +# of the source code files shall be tested. Currently, the following values +# are supported (values are SPDX registry identifiers): +# * MIT +# * BSD-2-Clause +# * BSD-3-Clause +# * LGPL-2.0-only +# * LGPL-2.1-only +# * LGPL-3.0-only +# * GPL-2.0-only +# * GPL-3.0-only +# +# ``FILES:`` : List of source files that contain valid SPDX-License-Identifier markers. +# The paths can be relative to the CMake file that generates the test case +# or be absolute paths. +# +# ``TEST_NAME`` : Optional parameter that defines the name of the generated test case. +# If no name is defined, the relative path to the test directory with appended +# license name is used. Every test has ``licensecheck_`` as prefix. +# +# ``WILL_FAIL`` : Optional parameter that inverts the test result. This parameter is usually only +# used for tests of the module. +# +# Since 5.75.0 + +#============================================================================= +# SPDX-FileCopyrightText: 2020 Andreas Cord-Landwehr <cordlandwehr@kde.org> +# SPDX-License-Identifier: BSD-3-Clause + +option(SKIP_LICENSE_TESTS "Skip outbound license tests" OFF) + +find_package(Python3) +set_package_properties(Python3 PROPERTIES + PURPOSE "Required to run tests of module ECMCheckOutboundLicense" + TYPE OPTIONAL +) +find_package(ReuseTool) +set_package_properties(ReuseTool PROPERTIES + PURPOSE "Required to run tests of module ECMCheckOutboundLicense" + TYPE OPTIONAL +) + +if (NOT SKIP_LICENSE_TESTS AND NOT REUSETOOL_FOUND) + add_feature_info(SPDX_LICENSE_TESTING FALSE "Automatic license testing based on SPDX definitions (requires reuse tool)") + message(WARNING "Reuse tool not found, skipping test generation") +else() + add_feature_info(SPDX_LICENSE_TESTING TRUE "Automatic license testing based on SPDX definitions (requires reuse tool)") +endif() + +set(SPDX_BOM_OUTPUT "${CMAKE_BINARY_DIR}/spdx.txt") + +# test fixture for generating SPDX bill of materials +if(SKIP_LICENSE_TESTS OR NOT REUSETOOL_FOUND) + message(STATUS "Skipping execution of outbound license tests.") +else() + add_test( + NAME generate_spdx_bom + COMMAND reuse spdx -o ${SPDX_BOM_OUTPUT} + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + ) + set_tests_properties(generate_spdx_bom PROPERTIES FIXTURES_SETUP SPDX_BOM) +endif() + +function(ecm_check_outbound_license) + if(SKIP_LICENSE_TESTS OR NOT REUSETOOL_FOUND) + return() + endif() + + set(_options WILL_FAIL) + set(_oneValueArgs TEST_NAME) + set(_multiValueArgs LICENSES FILES) + cmake_parse_arguments(ARG "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN} ) + + if(NOT ARG_LICENSES) + message(FATAL_ERROR "No LICENSES argument given to ecm_check_outbound_license") + endif() + + if(NOT ARG_FILES) + message(WARNING "No FILES argument given to ecm_check_outbound_license") + return() + endif() + + if(NOT ARG_TEST_NAME) + # compute test name based on licensecheck_<relative-path>_<licence> if not name given + string(REPLACE "${CMAKE_SOURCE_DIR}/" "" TEMP_TEST_NAME "${CMAKE_CURRENT_SOURCE_DIR}_${ARG_LICENSE}") + string(MAKE_C_IDENTIFIER ${TEMP_TEST_NAME} ARG_TEST_NAME) + endif() + + # generate file with list of relative file paths + string(REPLACE "${CMAKE_BINARY_DIR}/" "" RELATIVE_PREFIX_PATH ${CMAKE_CURRENT_BINARY_DIR}) + set(OUTPUT_FILE ${CMAKE_BINARY_DIR}/licensecheck_${ARG_TEST_NAME}.txt) + message(STATUS "Generate test input file: ${OUTPUT_FILE}") + file(REMOVE ${OUTPUT_FILE}) + foreach(_file ${ARG_FILES}) + # check script expects files to start with "./", which must be relative to CMAKE_SOURCE_DIR + if (IS_ABSOLUTE ${_file}) + string(REPLACE ${CMAKE_SOURCE_DIR} "." TEMPORARY_PATH ${_file}) + file(APPEND ${OUTPUT_FILE} "${TEMPORARY_PATH}\n") + else() + file(APPEND ${OUTPUT_FILE} "./${RELATIVE_PREFIX_PATH}/${_file}\n") + endif() + endforeach() + + file(COPY ${ECM_MODULE_DIR}/check-outbound-license.py DESTINATION ${CMAKE_BINARY_DIR}) + + foreach(_license ${ARG_LICENSES}) + string(MAKE_C_IDENTIFIER ${_license} LICENSE_ID) + add_test( + NAME licensecheck_${ARG_TEST_NAME}_${LICENSE_ID} + COMMAND python3 ${CMAKE_BINARY_DIR}/check-outbound-license.py -l ${_license} -s ${SPDX_BOM_OUTPUT} -i ${OUTPUT_FILE} + ) + set_tests_properties(licensecheck_${ARG_TEST_NAME}_${LICENSE_ID} PROPERTIES FIXTURES_REQUIRED SPDX_BOM) + set_tests_properties(licensecheck_${ARG_TEST_NAME}_${LICENSE_ID} PROPERTIES WILL_FAIL ${ARG_WILL_FAIL}) + endforeach() +endfunction() diff --git a/modules/check-outbound-license.py b/modules/check-outbound-license.py new file mode 100755 index 00000000..b8c1ef23 --- /dev/null +++ b/modules/check-outbound-license.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: 2020 Andreas Cord-Landwehr <cordlandwehr@kde.org> +# SPDX-License-Identifier: BSD-3-Clause + +# key : outbound license identifier +# values : list of acceptable licenses that are compatible with outbound license +compatibilityMatrix = { + "MIT": [ + "CC0-1.0", + "MIT"], + "BSD-2-Clause": [ + "CC0-1.0", + "MIT", + "BSD-2-Clause"], + "BSD-3-Clause": [ + "CC0-1.0", + "MIT", + "BSD-2-Clause", + "BSD-3-Clause"], + "LGPL-2.0-only": [ + "CC0-1.0", + "LGPL-2.0-only", + "LGPL-2.0-or-later", + "MIT", + "BSD-2-Clause", + "BSD-3-Clause", + "bzip2-1.0.6"], + "LGPL-2.1-only": [ + "CC0-1.0", + "LGPL-2.0-or-later", + "LGPL-2.1-only", + "LGPL-2.1-or-later", + "MIT", + "BSD-2-Clause", + "BSD-3-Clause", + "bzip2-1.0.6"], + "LGPL-3.0-only": [ + "CC0-1.0", + "LGPL-2.0-or-later", + "LGPL-2.1-or-later", + "LGPL-3.0-only", + "LGPL-3.0-or-later", + "MIT", + "BSD-2-Clause", + "BSD-3-Clause", + "bzip2-1.0.6"], + "GPL-2.0-only": [ + "CC0-1.0", + "LGPL-2.0-only", + "LGPL-2.1-only", + "LGPL-2.0-or-later", + "LGPL-2.1-or-later", + "GPL-2.0-only", + "GPL-2.0-or-later", + "MIT", + "BSD-2-Clause", + "BSD-3-Clause", + "bzip2-1.0.6"], + "GPL-3.0-only": [ + "CC0-1.0", + "LGPL-2.0-or-later", + "LGPL-2.1-or-later", + "LGPL-3.0-only", + "LGPL-3.0-or-later", + "GPL-2.0-or-later", + "GPL-3.0-only", + "GPL-3.0-or-later", + "MIT", + "BSD-2-Clause", + "BSD-3-Clause", + "bzip2-1.0.6"] +} + +def check_outbound_license(license, files, spdxDictionary): + """ + Asserts that the list of source files @p files, when combined into + a library or executable, can be delivered under the combined license @p license . + """ + print("Checking Target License: " + license) + if not license in compatibilityMatrix: + print("Error: unknown license selected") + return False + + allLicensesAreCompatible = True + + for sourceFile in files: + compatible = False + sourceFileStripped = sourceFile.strip() + for fileLicense in spdxDictionary[sourceFileStripped]: + if fileLicense in compatibilityMatrix[license]: + compatible = True + print("OK " + sourceFileStripped + " : " + fileLicense) + if not compatible: + allLicensesAreCompatible = False + print("-- " + sourceFileStripped + " : ( " + ", ".join([str(i) for i in spdxDictionary[sourceFileStripped]]) + " )") + return allLicensesAreCompatible + +if __name__ == '__main__': + print("Parsing SPDX BOM file") + import sys + import argparse + + # parse commands + parser = argparse.ArgumentParser() + parser.add_argument("-l", "--license", help="set outbound license to test") + parser.add_argument("-s", "--spdx", help="spdx bill-of-materials file") + parser.add_argument("-i", "--input", help="input file with list of source files to test") + args = parser.parse_args() + + # TODO check if required arguments are present and give meaningful feedback + + # collect name and licenses from SPDX blocks + spdxDictionary = {} + fileName = "" + licenses = [] + f = open(args.spdx, "r") + for line in f: + if line.startswith("FileName:"): + # strip "FileName: " + # thus name expected to start with "./", which is relative to CMAKE_SOURCE_DIR + fileName = line[10:].strip() + if line.startswith("LicenseInfoInFile:"): + licenses.append(line[19:].strip()) + if line == '' or line == "\n": + spdxDictionary[fileName] = licenses + fileName = "" + licenses = [] + f.close(); + + # read file with list of test files + f = open(args.input, "r") + testfiles = f.readlines() + f.close() + + if check_outbound_license(args.license, testfiles, spdxDictionary) == True: + sys.exit(0); + + # in any other case, return error code + sys.exit(1) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 866df5bb..1901ca16 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -60,6 +60,11 @@ endmacro() list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/find-modules) +# enforce finding of ReuseTool in parent scope of module, beause +# only here the module path can point into the source directory +find_package(ReuseTool) +add_subdirectory(ECMCheckOutboundLicenseTest) + # Skip if PyQt not available find_file(SIP_Qt5Core_Mod_FILE NAMES QtCoremod.sip diff --git a/tests/ECMCheckOutboundLicenseTest/CMakeLists.txt b/tests/ECMCheckOutboundLicenseTest/CMakeLists.txt new file mode 100644 index 00000000..1aab9a58 --- /dev/null +++ b/tests/ECMCheckOutboundLicenseTest/CMakeLists.txt @@ -0,0 +1,88 @@ +set(ECM_MODULE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../modules) +set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../modules) +include(ECMCheckOutboundLicense) + +# check relative and absolute input paths +ecm_check_outbound_license( +LICENSES LGPL-2.1-only +TEST_NAME absolute-path-handling +FILES + testdata/BSD-2-Clause.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/testdata/LGPL-2.1-or-later.cpp +) + +# check test case generation wihout TEST_NAME statement +ecm_check_outbound_license( +LICENSES LGPL-2.1-only +FILES + testdata/BSD-2-Clause.cpp +) + +# check multi license case +ecm_check_outbound_license( +LICENSES LGPL-2.1-only GPL-3.0-only +TEST_NAME multiple-licenses +FILES + testdata/BSD-2-Clause.cpp +) + + +# check for valid LGPL-2.1-only +ecm_check_outbound_license( +LICENSES LGPL-2.1-only +TEST_NAME valid-LGPL-2.1-only +FILES + testdata/BSD-2-Clause.cpp + testdata/LGPL-2.1-or-later.cpp +) + +# check for valid LGPL-3.0-only +ecm_check_outbound_license( +LICENSES LGPL-3.0-only +TEST_NAME valid-LGPL-3.0-only +FILES + testdata/BSD-2-Clause.cpp + testdata/LGPL-2.1-or-later.cpp + testdata/LGPL-3.0-only.cpp +) + +# check for valid GPL-2.0-only +ecm_check_outbound_license( +LICENSES GPL-2.0-only +TEST_NAME valid-GPL-2.0-only +FILES + testdata/BSD-2-Clause.cpp + testdata/LGPL-2.1-or-later.cpp + testdata/GPL-2.0-only.cpp +) + +# check for valid GPL-3.0-only +ecm_check_outbound_license( +LICENSES GPL-3.0-only +TEST_NAME valid-GPL-3.0-only +FILES + testdata/BSD-2-Clause.cpp + testdata/LGPL-2.1-or-later.cpp + testdata/LGPL-3.0-only.cpp + testdata/GPL-3.0-only.cpp +) + +# check for invalid GPL-3.0-only +ecm_check_outbound_license( +LICENSES LGPL-2.1-only +TEST_NAME invalid-LGPL-2.1-only +FILES + testdata/LGPL-2.0-only.cpp + testdata/GPL-3.0-only.cpp +WILL_FAIL +) + +# check for invalid GPL-3.0-only +ecm_check_outbound_license( +LICENSES GPL-3.0-only +TEST_NAME invalid-GPL-3.0-only +FILES + testdata/GPL-2.0-only.cpp + testdata/GPL-3.0-only.cpp +WILL_FAIL +) diff --git a/tests/ECMCheckOutboundLicenseTest/run_test.cmake.config b/tests/ECMCheckOutboundLicenseTest/run_test.cmake.config new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/tests/ECMCheckOutboundLicenseTest/run_test.cmake.config diff --git a/tests/ECMCheckOutboundLicenseTest/testdata/BSD-2-Clause.cpp b/tests/ECMCheckOutboundLicenseTest/testdata/BSD-2-Clause.cpp new file mode 100644 index 00000000..18c14530 --- /dev/null +++ b/tests/ECMCheckOutboundLicenseTest/testdata/BSD-2-Clause.cpp @@ -0,0 +1,6 @@ +/* + SPDX-FileCopyrightText: 2020 Jane Doe <nomail@example.com> + SPDX-License-Identifier: BSD-2-Clause +*/ + +// nothing in here, it is only a test diff --git a/tests/ECMCheckOutboundLicenseTest/testdata/GPL-2.0-only.cpp b/tests/ECMCheckOutboundLicenseTest/testdata/GPL-2.0-only.cpp new file mode 100644 index 00000000..7c227947 --- /dev/null +++ b/tests/ECMCheckOutboundLicenseTest/testdata/GPL-2.0-only.cpp @@ -0,0 +1,6 @@ +/* + SPDX-FileCopyrightText: 2020 John Doe <nomail@example.com> + SPDX-License-Identifier: GPL-2.0-only +*/ + +// nothing in here, it is only a test diff --git a/tests/ECMCheckOutboundLicenseTest/testdata/GPL-2.0-or-later.cpp b/tests/ECMCheckOutboundLicenseTest/testdata/GPL-2.0-or-later.cpp new file mode 100644 index 00000000..58769c51 --- /dev/null +++ b/tests/ECMCheckOutboundLicenseTest/testdata/GPL-2.0-or-later.cpp @@ -0,0 +1,6 @@ +/* + SPDX-FileCopyrightText: 2020 John Doe <nomail@example.com> + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +// nothing in here, it is only a test diff --git a/tests/ECMCheckOutboundLicenseTest/testdata/GPL-3.0-only.cpp b/tests/ECMCheckOutboundLicenseTest/testdata/GPL-3.0-only.cpp new file mode 100644 index 00000000..8261c2ba --- /dev/null +++ b/tests/ECMCheckOutboundLicenseTest/testdata/GPL-3.0-only.cpp @@ -0,0 +1,6 @@ +/* + SPDX-FileCopyrightText: 2020 John Doe <nomail@example.com> + SPDX-License-Identifier: GPL-3.0-only +*/ + +// nothing in here, it is only a test diff --git a/tests/ECMCheckOutboundLicenseTest/testdata/LGPL-2.1-or-later.cpp b/tests/ECMCheckOutboundLicenseTest/testdata/LGPL-2.1-or-later.cpp new file mode 100644 index 00000000..44b52a38 --- /dev/null +++ b/tests/ECMCheckOutboundLicenseTest/testdata/LGPL-2.1-or-later.cpp @@ -0,0 +1,6 @@ +/* + SPDX-FileCopyrightText: 2020 John Doe <nomail@example.com> + SPDX-License-Identifier: LGPL-2.1-or-later +*/ + +// nothing in here, it is only a test diff --git a/tests/ECMCheckOutboundLicenseTest/testdata/LGPL-3.0-only.cpp b/tests/ECMCheckOutboundLicenseTest/testdata/LGPL-3.0-only.cpp new file mode 100644 index 00000000..3ffd899b --- /dev/null +++ b/tests/ECMCheckOutboundLicenseTest/testdata/LGPL-3.0-only.cpp @@ -0,0 +1,6 @@ +/* + SPDX-FileCopyrightText: 2020 Jane Doe <nomail@example.com> + SPDX-License-Identifier: LGPL-3.0-only +*/ + +// nothing in here, it is only a test |