aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/module/ECMCheckOutboundLicense.rst1
-rw-r--r--find-modules/FindReuseTool.cmake23
-rw-r--r--modules/ECMCheckOutboundLicense.cmake153
-rwxr-xr-xmodules/check-outbound-license.py139
-rw-r--r--tests/CMakeLists.txt5
-rw-r--r--tests/ECMCheckOutboundLicenseTest/CMakeLists.txt88
-rw-r--r--tests/ECMCheckOutboundLicenseTest/run_test.cmake.config0
-rw-r--r--tests/ECMCheckOutboundLicenseTest/testdata/BSD-2-Clause.cpp6
-rw-r--r--tests/ECMCheckOutboundLicenseTest/testdata/GPL-2.0-only.cpp6
-rw-r--r--tests/ECMCheckOutboundLicenseTest/testdata/GPL-2.0-or-later.cpp6
-rw-r--r--tests/ECMCheckOutboundLicenseTest/testdata/GPL-3.0-only.cpp6
-rw-r--r--tests/ECMCheckOutboundLicenseTest/testdata/LGPL-2.1-or-later.cpp6
-rw-r--r--tests/ECMCheckOutboundLicenseTest/testdata/LGPL-3.0-only.cpp6
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