diff options
| -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 | 
