diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 0e990b715a83..b9eb3306634e 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,5 +1,7 @@ // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: // https://github.com/microsoft/vscode-dev-containers/tree/v0.194.0/containers/cpp +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Erlang/OTP and contributors { "name": "Erlang/OTP", "build": { diff --git a/.dir-locals.el b/.dir-locals.el index b6586cba03d1..c6adb8d6e0fc 100644 --- a/.dir-locals.el +++ b/.dir-locals.el @@ -1,4 +1,7 @@ ;; Project-wide Emacs settings +;; SPDX-License-Identifier: Apache-2.0 +;; SPDX-FileCopyrightText: 2016 Magnus Henoch + ( (erlang-mode (indent-tabs-mode . nil)) (autoconf-mode (indent-tabs-mode . nil)) diff --git a/.editorconfig b/.editorconfig index e9d6ce9c8903..49604744ccb0 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,6 +3,9 @@ ; ; To use this from emacs install the editorconfig package +; SPDX-License-Identifier: Apache-2.0 +; SPDX-FileCopyrightText: Erlang/OTP and contributors + root = true [*] diff --git a/.gitpod.yml b/.gitpod.yml index 74a5155ec342..825572d2dc1c 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Erlang/OTP and contributors image: file: .github/dockerfiles/Dockerfile.ubuntu-base context: .github diff --git a/.mailmap b/.mailmap index 66d5af2f6ac9..4de6e273ffc7 100644 --- a/.mailmap +++ b/.mailmap @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Erlang/OTP and contributors diff --git a/.ort.yml b/.ort.yml new file mode 100644 index 000000000000..9106213c8dfa --- /dev/null +++ b/.ort.yml @@ -0,0 +1,770 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: 2024 Ericsson AB + +excludes: + paths: + - pattern: ".ort/**/*" + reason: "TEST_OF" + comment: "This directory is not relevant." + + - pattern: ".ort.yml" + reason: "OTHER" + comment: "This directory is not relevant." + + - pattern: ".github/**/*" + reason: "OTHER" + comment: "This directory is not relevant." + + - pattern: "**/.gitignore" + reason: "OTHER" + comment: "This directory is not relevant." + + - pattern: ".gitattributes" + reason: "OTHER" + comment: "This file is not present in a release." + + - pattern: ".gitignore" + reason: "OTHER" + comment: "This file is not present in a release." + +# Curations are used to fix wrongly detected licenses +curations: + license_findings: + - path: "bootstrap/bin/no_dot_erlang.boot" + reason: "NOT_DETECTED" + comment: >- + Boot file has cannot have comments to apply license. + concluded_license: "Apache-2.0" + + - path: "bootstrap/bin/start.boot" + reason: "NOT_DETECTED" + comment: >- + Boot file has cannot have comments to apply license. + concluded_license: "Apache-2.0" + + - path: "bootstrap/bin/start_clean.boot" + reason: "NOT_DETECTED" + comment: >- + Boot file has cannot have comments to apply license. + concluded_license: "Apache-2.0" + + - path: "bootstrap/lib/compiler/ebin/beam_opcodes.beam" + reason: "NOT_DETECTED" + comment: >- + Part of auto-generated primary bootstrap, and cannot have comments. + concluded_license: "Apache-2.0" + + - path: "OTP_VERSION" + reason: "NOT_DETECTED" + comment: >- + This file does not admit comments to add a license. + concluded_license: "Apache-2.0" + + - path: "scripts/scan-code.escript" + reason: "CODE" + comment: >- + The script contains licenses names in source code, and the + scanner considers them as its license. + concluded_license: "Apache-2.0" + + - path: "CONTRIBUTING.md" + reason: "DOCUMENTATION_OF" + comment: >- + This file contains a written Developer Certificate of Origin license + in writing, and the scanner considers it its license + concluded_license: "Apache-2.0" + + - path: "lib/stdlib/test/json_SUITE_data/**/*" + reason: "NOT_DETECTED" + comment: >- + The `json` tests have MIT license as they are tested as part of + an existing json test suite, https://github.com/nst/JSONTestSuite. + concluded_license: "MIT" + + - path: "lib/stdlib/test/re_SUITE_data/**/*" + reason: "NOT_DETECTED" + comment: >- + The tests are believed to be part of PCRE's test suite. + concluded_license: "BSD-3-Clause" + + - path: "lib/common_test/priv/jquery.tablesorter.min.js" + reason: "NOT_DETECTED" + comment: >- + The minified version does not contain its license in writing. + concluded_license: "MIT OR GPL-2.0-only" + + - path: ".ort.yml" + reason: "CODE" + comment: >- + The file mentions licenses and confuses the scanner. + concluded_license: "Unlicense" + + - path: "erts/doc/notes.md" + reason: "DOCUMENTATION_OF" + comment: >- + The scanner incorrectly categorises the file with a + NOASSERTION OR Apache-2.0 when only the latter applies. + concluded_license: "Apache-2.0" + + - path: "erts/autoconf/config.guess" + reason: "CODE" + comment: >- + Wrong license detected as NOASSERTION OR GPL-3.0-or-later + WITH Autoconf-exception-generic-3.0 when only the latter applies. + concluded_license: "GPL-3.0-or-later WITH Autoconf-exception-generic-3.0" + + - path: "erts/autoconf/config.sub" + reason: "CODE" + comment: >- + Wrong license detected as NOASSERTION OR GPL-3.0-or-later + WITH Autoconf-exception-generic-3.0 when only the latter applies. + concluded_license: "GPL-3.0-or-later WITH Autoconf-exception-generic-3.0" + + - path: "lib/common_test/test_server/config.guess" + reason: "CODE" + comment: >- + Wrong license detected as NOASSERTION OR GPL-3.0-or-later + WITH Autoconf-exception-generic-3.0 when only the latter applies. + concluded_license: "GPL-3.0-or-later WITH Autoconf-exception-generic-3.0" + + - path: "lib/common_test/test_server/config.sub" + reason: "CODE" + comment: >- + Wrong license detected as NOASSERTION OR GPL-3.0-or-later + WITH Autoconf-exception-generic-3.0 when only the latter applies. + concluded_license: "GPL-3.0-or-later WITH Autoconf-exception-generic-3.0" + + - path: "make/autoconf/config.guess" + reason: "CODE" + comment: >- + Wrong license detected as NOASSERTION OR GPL-3.0-or-later + WITH Autoconf-exception-generic-3.0 when only the latter applies. + concluded_license: "GPL-3.0-or-later WITH Autoconf-exception-generic-3.0" + + - path: "make/autoconf/config.sub" + reason: "CODE" + comment: >- + Wrong license detected as NOASSERTION OR GPL-3.0-or-later + WITH Autoconf-exception-generic-3.0 when only the latter applies. + concluded_license: "GPL-3.0-or-later WITH Autoconf-exception-generic-3.0" + + - path: "erts/autoconf/install-sh" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises the license. + concluded_license: "MIT" + + - path: ".ort/license-classifications.yml" + reason: "CODE" + comment: >- + The file contains multiple license names + concluded_license: "Unlicense" + + - path: "bootstrap/lib/stdlib/ebin/erl_id_trans.beam" + reason: "CODE" + comment: >- + The file cannot contain license comments + conclude_license: "Apache-2.0" + + - path: "bootstrap/lib/stdlib/ebin/erl_parse.beam" + reason: "CODE" + comment: >- + The file cannot contain license comments + conclude_license: "Apache-2.0" + + - path: "erts/emulator/asmjit.version" + reason: "DOCUMENTATION_OF" + comment: >- + The file cannot contain license comments + conclude_license: "Apache-2.0" + + - path: "erts/emulator/asmjit/core/support.h" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises the license + concluded_license: "Zlib" + + - path: "lib/megaco/test/megaco_test_msg_v3_lib.erl" + reason: "CODE" + comment: >- + The file contains variables that resemble license names + conclude_license: "Apache-2.0" + + - path: "lib/megaco/test/megaco_test_msg_v2_lib.erl" + reason: "CODE" + comment: >- + The file contains variables that resemble license names + conclude_license: "Apache-2.0" + + - path: "lib/megaco/test/megaco_test_msg_v1_lib.erl" + reason: "CODE" + comment: >- + The file contains variables that resemble license names + conclude_license: "Apache-2.0" + + - path: "lib/megaco/test/megaco_codec_v3_SUITE.erl" + reason: "CODE" + comment: >- + The file contains variables that resemble license names + conclude_license: "Apache-2.0" + + - path: "erts/emulator/pcre/LICENCE" + reason: "INCORRECT" + comment: >- + The license was incorrectly categorised + concluded_license: "BSD-3-Clause" + + - path: "erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/README.md" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises the license + concluded_license: "Unlicense" + + - path: "lib/common_test/test_server/install-sh" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises the license + concluded_license: "MIT" + + - path: "lib/inets/examples/server_root/icons/README" + reason: "DOCUMENTATION_OF" + comment: >- + The scanner incorrectly categorises the license + concluded_license: "Unlicense" + + - path: "lib/inets/test/httpd_test_data/server_root/icons/README" + reason: "DOCUMENTATION_OF" + comment: >- + The scanner incorrectly categorises the license + concluded_license: "Unlicense" + + - path: "erts/emulator/zlib/inffast.c" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises this as a dual license + concluded_license: "Zlib" + + - path: "erts/emulator/zlib/zlib.mk" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises this as a dual license + concluded_license: "Apache-2.0" + + - path: "make/autoconf/install-sh" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises this license + concluded_license: "MIT" + + - path: "lib/dialyzer/doc/notes.md" + reason: "DOCUMENTATION_OF" + comment: >- + The scanner incorrectly categorises this license + concluded_license: "Apache-2.0" + + - path: "lib/tools/emacs/internal_doc/emacs.sgml" + reason: "DOCUMENTATION_OF" + comment: >- + The scanner incorrectly categorises this license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/edoc/include/edoc_doclet.hrl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/edoc/src/edoc.erl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/edoc/src/edoc.hrl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/edoc/src/edoc_data.erl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/edoc/src/edoc_doclet.erl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/edoc/src/edoc_doclet_chunks.erl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/edoc/src/edoc_extract.erl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/edoc/src/edoc_layout.erl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/edoc/src/edoc_layout_chunks.erl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/edoc/src/edoc_lib.erl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/edoc/src/edoc_macros.erl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/edoc/src/edoc_parser.yrl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/edoc/src/edoc_refs.erl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/edoc/src/edoc_report.erl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/edoc/src/edoc_run.erl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/edoc/src/edoc_tags.erl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/edoc/src/edoc_types.erl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/edoc/src/edoc_types.hrl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/edoc/src/edoc_wiki.erl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/eunit/include/eunit.hrl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/eunit/src/eunit.erl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/eunit/src/eunit_autoexport.erl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/eunit/src/eunit_data.erl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/eunit/src/eunit_internal.hrl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/eunit/src/eunit_lib.erl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/eunit/src/eunit_listener.erl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/eunit/src/eunit_proc.erl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/eunit/src/eunit_serial.erl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/eunit/src/eunit_server.erl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/eunit/src/eunit_striptests.erl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/eunit/src/eunit_surefire.erl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/eunit/src/eunit_test.erl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/eunit/src/eunit_tests.erl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/eunit/src/eunit_tty.erl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/eunit/test/eunit_test_listener.erl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/syntax_tools/src/epp_dodger.erl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/syntax_tools/src/erl_comment_scan.erl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/syntax_tools/src/erl_prettypr.erl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/syntax_tools/src/erl_recomment.erl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/syntax_tools/src/erl_syntax.erl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/syntax_tools/src/erl_syntax_lib.erl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/syntax_tools/src/merl.erl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/syntax_tools/src/merl_tests.erl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/syntax_tools/src/merl_transform.erl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/syntax_tools/src/prettypr.erl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises a dual license + concluded_license: "Apache-2.0 OR LGPL-2.1-or-later" + + - path: "lib/diameter/doc/notes.md" + reason: "NOT_DETECTED" + comment: >- + The scanner categorises as NOASSERTION when it is Apache-2.0 + concluded_license: "Apache-2.0" + + - path: "lib/ftp/doc/notes.md" + reason: "NOT_DETECTED" + comment: >- + The scanner categorises as NOASSERTION when it is Apache-2.0 + concluded_license: "Apache-2.0" + + - path: "lib/inets/doc/notes.md" + reason: "NOT_DETECTED" + comment: >- + The scanner categorises as NOASSERTION when it is Apache-2.0 + concluded_license: "Apache-2.0" + + - path: "lib/kernel/doc/notes.md" + reason: "NOT_DETECTED" + comment: >- + The scanner categorises as NOASSERTION when it is Apache-2.0 + concluded_license: "Apache-2.0" + + - path: "lib/kernel/test/zlib_SUITE_data/zipdoc" + reason: "INCORRECT" + comment: "We do not know this license" + concluded_license: "NONE" + + - path: "lib/snmp/mibs/OTP-REG.mib" + reason: "NOT_DETECTED" + comment: >- + The scanner categorises as NOASSERTION when it is Apache-2.0 + concluded_license: "Apache-2.0" + + - path: "lib/snmp/mibs/OTP-SNMPEA-MIB.mib" + reason: "NOT_DETECTED" + comment: >- + The scanner categorises as NOASSERTION when it is Apache-2.0 + concluded_license: "Apache-2.0" + + - path: "lib/snmp/mibs/OTP-TC.mib" + reason: "NOT_DETECTED" + comment: >- + The scanner categorises as NOASSERTION when it is Apache-2.0 + concluded_license: "Apache-2.0" + + - path: "lib/ssh/doc/notes.md" + reason: "NOT_DETECTED" + comment: >- + The scanner categorises as NOASSERTION when it is Apache-2.0 + concluded_license: "Apache-2.0" + + - path: "lib/ssl/doc/notes.md" + reason: "NOT_DETECTED" + comment: >- + The scanner categorises as NOASSERTION when it is Apache-2.0 + concluded_license: "Apache-2.0" + + - path: "lib/stdlib/doc/notes.md" + reason: "NOT_DETECTED" + comment: >- + The scanner categorises as NOASSERTION when it is Apache-2.0 + concluded_license: "Apache-2.0" + + - path: "lib/tftp/doc/notes.md" + reason: "NOT_DETECTED" + comment: >- + The scanner categorises as NOASSERTION when it is Apache-2.0 + concluded_license: "Apache-2.0" + + - path: "lib/wx/api_gen/wx_gen_doc.erl" + reason: "NOT_DETECTED" + comment: >- + The scanner categorises as NOASSERTION when it is Apache-2.0 + concluded_license: "Apache-2.0" + + - path: "erts/emulator/test/hello_SUITE_data/hello.erl" + reason: "CODE" + comment: >- + There are variables that resemble license names + concluded_license: "Apache-2.0" + + # + # + # COPYRIGHT + # + # + - path: "system/COPYRIGHT" + reason: "DOCUMENTATION_OF" + comment: >- + The scanner mixes licenses written in text with its actual license + concluded_license: "Apache-2.0" + + # + # + # ryu is dual license OR + # + # + - path: "erts/emulator/ryu/common.h" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises the license + concluded_license: "Apache-2.0 OR BSL-1.0" + + - path: "erts/emulator/ryu/d2s.c" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises the license + concluded_license: "Apache-2.0 WITH LLVM-exception OR BSL-1.0 WITH LLVM-exception" + + - path: "erts/emulator/ryu/d2s_full_table.h" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises the license + concluded_license: "Apache-2.0 OR BSL-1.0" + + - path: "erts/emulator/ryu/d2s_intrinsics.h" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises the license + concluded_license: "Apache-2.0 OR BSL-1.0" + + - path: "erts/emulator/ryu/digit_table.h" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises the license + concluded_license: "Apache-2.0 OR BSL-1.0" + + - path: "erts/emulator/ryu/ryu.h" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises the license + concluded_license: "Apache-2.0 OR BSL-1.0" + + - path: "erts/emulator/test/estone_SUITE.erl" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises the license + concluded_license: "Apache-2.0" + + - path: "erts/emulator/pcre/LICENCE" + reason: "DOCUMENTATION_OF" + comment: >- + The scanner incorrectly reads the license + concluded_license: "BSD-3-Clause" + + - path: "erts/emulator/pcre/README.pcre_update.md" + reason: "DOCUMENTATION_OF" + comment: >- + The scanner incorrectly categorises the license + concluded_license: "BSD-3-Clause" + + - path: "lib/snmp/mibs/SNMP-USM-HMAC-SHA2-MIB.mib" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises the license + concluded_license: "BSD-2-Clause" + + - path: "erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/README.md" + reason: "DOCUMENTATION_OF" + comment: >- + The scanner incorrectly categorises the license + concluded_license: "Unlicense" + + - path: "lib/erl_interface/src/openssl/include/openssl_local/md5.h" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises the license + concluded_license: "Apache-2.0" + + - path: "lib/erl_interface/src/openssl/include/crypto/md32_common.h" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises the license + concluded_license: "Apache-2.0" + + - path: "lib/erl_interface/src/openssl/crypto/md5/md5_local.h" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises the license + concluded_license: "Apache-2.0" + + - path: "lib/erl_interface/src/openssl/crypto/md5/md5_dgst.c" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises the license + concluded_license: "Apache-2.0" + + - path: "lib/erl_interface/src/openssl/README" + reason: "DOCUMENTATION_OF" + comment: >- + The scanner incorrectly categorises the license + concluded_license: "Apache-2.0" + + - path: "lib/crypto/doc/guides/licenses.md" + reason: "INCORRECT" + comment: >- + The scanner incorrectly categorises the license + concluded_license: "Apache-2.0" + + - path: "erts/emulator/openssl/include/openssl_local/md5.h" + reason: "DOCUMENTATION_OF" + comment: >- + The scanner mixes up the copyright and license notice + concluded_license: "Apache-2.0" + + - path: "erts/emulator/openssl/include/crypto/md32_common.h" + reason: "DOCUMENTATION_OF" + comment: >- + The scanner mixes up the copyright and license notice + concluded_license: "Apache-2.0" + + - path: "erts/emulator/openssl/crypto/md5/md5_local.h" + reason: "DOCUMENTATION_OF" + comment: >- + The scanner mixes up the copyright and license notice + concluded_license: "Apache-2.0" + + - path: "erts/emulator/openssl/crypto/md5/md5_dgst.c" + reason: "DOCUMENTATION_OF" + comment: >- + The scanner mixes up the copyright and license notice + concluded_license: "Apache-2.0" + + - path: "erts/emulator/openssl/README" + reason: "DOCUMENTATION_OF" + comment: >- + The scanner mixes up the copyright and license notice + concluded_license: "Apache-2.0" diff --git a/.ort/COPYRIGHT.md.ftl b/.ort/COPYRIGHT.md.ftl new file mode 100644 index 000000000000..74eebb3f494b --- /dev/null +++ b/.ort/COPYRIGHT.md.ftl @@ -0,0 +1,93 @@ +[#-- + Copyright (C) 2020 The ORT Project Authors (see ) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + SPDX-License-Identifier: Apache-2.0 + License-Filename: LICENSE +--] +[#-- + The notice file generated by this template consists of the following sections: + + * The licenses and associated copyrights for all projects and packages merged into a single list. + + Archived license files and excluded projects and packages are ignored. +--] +[#assign noticeCategoryName = "include-in-notice-file"] +[#-- Add the licenses of the projects. --] +[#if projects?has_content] +This project contains or depends on third-party software components pursuant to the following licenses: +${projects} +---- + [#assign isFirst = true] + [#-- + Merge the licenses and copyrights of all projects and packages into a single list. The licenses are filtered + using LicenseView.CONCLUDED_OR_DECLARED_AND_DETECTED. This is the default view which ignores declared and + detected licenses if a license conclusion for a package was made. For projects this is the same as + LicenseView.ALL, because projects cannot have concluded licenses. If copyrights were detected for a concluded + license those statements are kept. Also filter all licenses that are configured not to be included in notice + files. + --] + [#assign mergedLicenses = helper.filterForCategory( + helper.mergeLicenses(projects + packages, LicenseView.CONCLUDED_OR_DECLARED_AND_DETECTED), + noticeCategoryName + )] + [#list mergedLicenses as resolvedLicense] + [#assign licenseName = resolvedLicense.license.simpleLicense()] + [#assign licenseText = licenseTextProvider.getLicenseText(licenseName)!] + [#assign locations = resolvedLicense.locations] + + + [#assign paths = resolvedLicense.locations?map(resolvedLicenseLocation -> resolvedLicenseLocation.location.path)] + [#assign uniquePaths = [] ] + [#list paths as path] + [#if ! uniquePaths?seq_contains(path)] + [#assign uniquePaths = uniquePaths + [path] ] + [/#if] + [/#list] + [#assign uniquePaths = uniquePaths?sort] + + + [#if !licenseText?has_content][#continue][/#if] + [#if isFirst] + [#assign isFirst = false] + [#else] +---- + [/#if] + [#assign copyrights = resolvedLicense.getCopyrights()] +* Info: + * SPDX-License-Identifier: ${resolvedLicense.license.simpleLicense()} + [#if resolvedLicense.license.getLicenseUrl()?has_content ] + * License URL: ${resolvedLicense.license.getLicenseUrl()!} + [/#if] + * OTP Location: + [#list uniquePaths as path] + - ${path} + [/#list] + + [#if copyrights?has_content ] + * COPYRIGHTS + %CopyrightBegin% + [#list copyrights as c] + - ${c} + [/#list] + %CopyrightEnd% + [/#if] + + [#assign exceptionName = resolvedLicense.license.exception()!] + [#assign exceptionText = licenseTextProvider.getLicenseText(exceptionName)!] + [#if exceptionText?has_content] + ${exceptionText} + [/#if] + [/#list] +[/#if] diff --git a/.ort/evaluator.rules.kts b/.ort/evaluator.rules.kts new file mode 100644 index 000000000000..fad0bf282b17 --- /dev/null +++ b/.ort/evaluator.rules.kts @@ -0,0 +1,401 @@ +/* + * Copyright (C) 2019 The ORT Project Authors (see ) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * License-Filename: LICENSE + */ + +/******************************************************* + * Example OSS Review Toolkit (ORT) .rules.kts file * + * * + * Note this file only contains example how to write * + * rules. It's recommended you consult your own legal * + * when writing your own rules. * + *******************************************************/ + +/** + * Import the license classifications from license-classifications.yml. + */ + +val permissiveLicenses = licenseClassifications.licensesByCategory["permissive"].orEmpty() + +val copyleftLicenses = licenseClassifications.licensesByCategory["copyleft"].orEmpty() + +val copyleftLimitedLicenses = licenseClassifications.licensesByCategory["copyleft-limited"].orEmpty() + +val publicDomainLicenses = licenseClassifications.licensesByCategory["public-domain"].orEmpty() + +val exceptionLicenses = licenseClassifications.licensesByCategory["exceptions"].orEmpty() + +// The complete set of licenses covered by policy rules. +val handledLicenses = listOf( + permissiveLicenses, + publicDomainLicenses, + copyleftLicenses, + copyleftLimitedLicenses, + exceptionLicenses +).flatten().let { + it.getDuplicates().let { duplicates -> + require(duplicates.isEmpty()) { + "The classifications for the following licenses overlap: $duplicates" + } + } + + it.toSet() +} + +/** + * Return the Markdown-formatted text to aid users with resolving violations. + */ +fun PackageRule.howToFixDefault() = """ + A text written in MarkDown to help users resolve policy violations + which may link to additional resources. + """.trimIndent() + +/** + * Set of matchers to help keep policy rules easy to understand + */ + +fun PackageRule.LicenseRule.isHandled() = + object : RuleMatcher { + override val description = "isHandled($license)" + + override fun matches() = + license in handledLicenses && ("-exception" !in license.toString() || " WITH " in license.toString()) + } + +fun PackageRule.LicenseRule.isCopyleft() = + object : RuleMatcher { + override val description = "isCopyleft($license)" + + override fun matches() = license in copyleftLicenses + } + +fun PackageRule.LicenseRule.isCopyleftLimited() = + object : RuleMatcher { + override val description = "isCopyleftLimited($license)" + + override fun matches() : Boolean { + // println("License: ${license}") + return license in copyleftLimitedLicenses + } + } + +fun PackageRule.LicenseRule.isExceptional() = + object : RuleMatcher { + override val description = "isExceptional($license)" + + override fun matches() = license in exceptionLicenses + } + +/** + * Example policy rules + */ + +fun RuleSet.unhandledLicenseRule() = packageRule("UNHANDLED_LICENSE") { + // Do not trigger this rule on packages that have been excluded in the .ort.yml. + require { + -isExcluded() + } + + // Define a rule that is executed for each license of the package. + licenseRule("UNHANDLED_LICENSE", LicenseView.CONCLUDED_OR_DECLARED_AND_DETECTED) { + require { + -isExcluded() + -isHandled() + -isExceptional() + } + + // Throw an error message including guidance how to fix the issue. + error( + "The license $license is currently not covered by policy rules. " + + "The license was ${licenseSource.name.lowercase()} in package " + + "${pkg.metadata.id.toCoordinates()}.", + howToFixDefault() + ) + } +} + +fun RuleSet.unmappedDeclaredLicenseRule() = packageRule("UNMAPPED_DECLARED_LICENSE") { + require { + -isExcluded() + } + + resolvedLicenseInfo.licenseInfo.declaredLicenseInfo.processed.unmapped.forEach { unmappedLicense -> + warning( + "The declared license '$unmappedLicense' could not be mapped to a valid license or parsed as an SPDX " + + "expression. The license was found in package ${pkg.metadata.id.toCoordinates()}.", + howToFixDefault() + ) + } +} + +fun RuleSet.copyleftInSourceRule() = packageRule("COPYLEFT_IN_SOURCE") { + require { + -isExcluded() + } + + licenseRule("COPYLEFT_IN_SOURCE", LicenseView.CONCLUDED_OR_DECLARED_AND_DETECTED) { + require { + -isExcluded() + +isCopyleft() + } + + val message = if (licenseSource == LicenseSource.DETECTED) { + "The ScanCode copyleft categorized license $license was ${licenseSource.name.lowercase()} " + + "in package ${pkg.metadata.id.toCoordinates()}." + } else { + "The package ${pkg.metadata.id.toCoordinates()} has the ${licenseSource.name.lowercase()} ScanCode copyleft " + + "catalogized license $license." + } + + error(message, howToFixDefault()) + } +} + +fun RuleSet.copyleftInSourceLimitedRule() = packageRule("COPYLEFT_LIMITED_IN_SOURCE") { + require { + -isExcluded() + } + + // println("${ortResult}") + + licenseRule("COPYLEFT_LIMITED_IN_SOURCE", LicenseView.CONCLUDED_OR_DECLARED_OR_DETECTED) { + require { + -isExcluded() + +isCopyleftLimited() + } + + val licenseSourceName = licenseSource.name.lowercase() + val message = if (licenseSource == LicenseSource.DETECTED) { + if (pkg.metadata.id.type == "Unmanaged") { + "The ScanCode copyleft-limited categorized license $license was $licenseSourceName in package " + + "${pkg.metadata.id.toCoordinates()}." + } else { + "The ScanCode copyleft-limited categorized license $license was $licenseSourceName in package " + + "${pkg.metadata.id.toCoordinates()}." + } + } else { + "The package ${pkg.metadata.id.toCoordinates()} has the $licenseSourceName ScanCode copyleft-limited " + + "categorized license $license." + } + error(message, howToFixDefault()) + } +} + +fun RuleSet.dependencyInProjectSourceRule() = projectSourceRule("DEPENDENCY_IN_PROJECT_SOURCE_RULE") { + val denyDirPatterns = listOf( + "**/node_modules" to setOf("NPM", "Yarn", "PNPM"), + "**/vendor" to setOf("GoMod") + ) + + denyDirPatterns.forEach { (pattern, packageManagers) -> + val offendingDirs = projectSourceFindDirectories(pattern) + + if (offendingDirs.isNotEmpty()) { + issue( + Severity.ERROR, + "The directories ${offendingDirs.joinToString()} belong to the package manager(s) " + + "${packageManagers.joinToString()} and must not be committed.", + "Please delete the directories: ${offendingDirs.joinToString()}." + ) + } + } +} + +fun RuleSet.vulnerabilityInPackageRule() = packageRule("VULNERABILITY_IN_PACKAGE") { + require { + -isExcluded() + +hasVulnerability() + } + + issue( + Severity.WARNING, + "The package ${pkg.metadata.id.toCoordinates()} has a vulnerability", + howToFixDefault() + ) +} + +fun RuleSet.highSeverityVulnerabilityInPackageRule() = packageRule("HIGH_SEVERITY_VULNERABILITY_IN_PACKAGE") { + val scoreThreshold = 5.0f + val scoringSystem = "CVSS:3.1" + + require { + -isExcluded() + +hasVulnerability(scoreThreshold, scoringSystem) + } + + issue( + Severity.ERROR, + "The package ${pkg.metadata.id.toCoordinates()} has a vulnerability with $scoringSystem severity > " + + "${scoreThreshold}", + howToFixDefault() + ) +} + +fun RuleSet.copyleftInDependencyRule() = dependencyRule("COPYLEFT_IN_DEPENDENCY") { + licenseRule("COPYLEFT_IN_DEPENDENCY", LicenseView.CONCLUDED_OR_DECLARED_OR_DETECTED) { + require { + +isCopyleft() + } + + issue( + Severity.ERROR, + "The project ${project.id.toCoordinates()} has a dependency licensed under the ScanCode " + + "copyleft categorized license $license.", + howToFixDefault() + ) + } +} + +fun RuleSet.copyleftLimitedInDependencyRule() = dependencyRule("COPYLEFT_LIMITED_IN_DEPENDENCY_RULE") { + require { + +isAtTreeLevel(0) + +isStaticallyLinked() + } + + licenseRule("COPYLEFT_LIMITED_IN_DEPENDENCY_RULE", LicenseView.CONCLUDED_OR_DECLARED_OR_DETECTED) { + require { + +isCopyleftLimited() + } + + // Use issue() instead of error() if you want to set the severity. + issue( + Severity.WARNING, + "The project ${project.id.toCoordinates()} has a statically linked direct dependency licensed " + + "under the ScanCode copyleft-left categorized license $license.", + howToFixDefault() + ) + } +} + +fun RuleSet.deprecatedScopeExcludeReasonInOrtYmlRule() = ortResultRule("DEPRECATED_SCOPE_EXCLUDE_REASON_IN_ORT_YML") { + val reasons = ortResult.repository.config.excludes.scopes.mapTo(mutableSetOf()) { it.reason } + + @Suppress("DEPRECATION") + val deprecatedReasons = setOf(ScopeExcludeReason.TEST_TOOL_OF) + + reasons.intersect(deprecatedReasons).forEach { offendingReason -> + warning( + "The repository configuration is using the deprecated scope exclude reason '$offendingReason'.", + "Please use only non-deprecated scope exclude reasons, see " + + "https://github.com/oss-review-toolkit/ort/blob/main/model/src/main/" + + "kotlin/config/ScopeExcludeReason.kt." + ) + } +} + +fun RuleSet.missingCiConfigurationRule() = projectSourceRule("MISSING_CI_CONFIGURATION") { + require { + -AnyOf( + projectSourceHasFile( + ".appveyor.yml", + ".bitbucket-pipelines.yml", + ".gitlab-ci.yml", + ".travis.yml" + ), + projectSourceHasDirectory( + ".circleci", + ".github/workflows" + ) + ) + } + + error( + message = "This project does not have any known CI configuration files.", + howToFix = "Please setup a CI. If you already have setup a CI and the error persists, please contact support." + ) +} + +fun RuleSet.missingContributingFileRule() = projectSourceRule("MISSING_CONTRIBUTING_FILE") { + require { + -projectSourceHasFile("CONTRIBUTING.md") + } + + error("The project's code repository does not contain the file 'CONTRIBUTING.md'.") +} + +fun RuleSet.missingReadmeFileRule() = projectSourceRule("MISSING_README_FILE") { + require { + -projectSourceHasFile("README.md") + } + + error("The project's code repository does not contain the file 'README.md'.") +} + +fun RuleSet.missingReadmeFileLicenseSectionRule() = projectSourceRule("MISSING_README_FILE_LICENSE_SECTION") { + require { + +projectSourceHasFile("README.md") + -projectSourceHasFileWithContent(".*^#{1,2} License$.*", "README.md") + } + + error( + message = "The file 'README.md' is missing a \"License\" section.", + howToFix = "Please add a \"License\" section to the file 'README.md'." + ) +} + +fun RuleSet.wrongLicenseInLicenseFileRule() = projectSourceRule("WRONG_LICENSE_IN_LICENSE_FILE_RULE") { + require { + +projectSourceHasFile("LICENSE") + } + + val allowedRootLicenses = setOf("Apackage-2.0", "MIT") + val detectedRootLicenses = projectSourceGetDetectedLicensesByFilePath("LICENSE").values.flatten().toSet() + val wrongLicenses = detectedRootLicenses - allowedRootLicenses + + if (wrongLicenses.isNotEmpty()) { + error( + message = "The file 'LICENSE' contains the following disallowed licenses ${wrongLicenses.joinToString()}.", + howToFix = "Please use only the following allowed licenses: ${allowedRootLicenses.joinToString()}." + ) + } else if (detectedRootLicenses.isEmpty()) { + error( + message = "The file 'LICENSE' does not contain any license which is not allowed.", + howToFix = "Please use one of the following allowed licenses: ${allowedRootLicenses.joinToString()}." + ) + } +} + +/** + * The set of policy rules. + */ +val ruleSet = ruleSet(ortResult, licenseInfoResolver, resolutionProvider) { + // Rules which get executed for each package: + unhandledLicenseRule() + unmappedDeclaredLicenseRule() + copyleftInSourceRule() + copyleftInSourceLimitedRule() + vulnerabilityInPackageRule() + highSeverityVulnerabilityInPackageRule() + + // Rules which get executed for each dependency (of any project): + copyleftInDependencyRule() + copyleftLimitedInDependencyRule() + + // Rules which get executed once: + deprecatedScopeExcludeReasonInOrtYmlRule() + + // Prior to open sourcing use case rules (which get executed once): + dependencyInProjectSourceRule() + missingCiConfigurationRule() + missingContributingFileRule() + missingReadmeFileRule() + missingReadmeFileLicenseSectionRule() + wrongLicenseInLicenseFileRule() +} + +// Populate the list of policy rule violations to return. +ruleViolations += ruleSet.violations diff --git a/.ort/license-classifications.yml b/.ort/license-classifications.yml new file mode 100644 index 000000000000..d1b9755e7be9 --- /dev/null +++ b/.ort/license-classifications.yml @@ -0,0 +1,384 @@ +--- +# Example license-classifications.yml based on categorization from +# https://github.com/aboutcode-org/scancode-toolkit/commit/ed644e4 +# +categories: +- name: "copyleft" +- name: "strong-copyleft" +- name: "copyleft-limited" +- name: "permissive" + description: "Licenses with permissive obligations." +- name: "public-domain" +- name: "include-in-notice-file" + description: >- + This category is checked by templates used by the ORT report generator. The licenses associated with this + category are included into NOTICE files. +- name: "include-source-code-offer-in-notice-file" + description: >- + A marker category that indicates that the licenses assigned to it require that the source code of the packages + needs to be provided. +- name: "exceptions" + description: >- + Licenses that OTP has checked to be ok. + +categorizations: +- id: "AGPL-1.0" + categories: + - "copyleft" + - "include-in-notice-file" + - "include-source-code-offer-in-notice-file" +- id: "AGPL-1.0-only" + categories: + - "copyleft" + - "include-in-notice-file" + - "include-source-code-offer-in-notice-file" +- id: "AGPL-1.0-or-later" + categories: + - "copyleft" + - "include-in-notice-file" + - "include-source-code-offer-in-notice-file" +- id: "AGPL-3.0" + categories: + - "copyleft" + - "include-in-notice-file" + - "include-source-code-offer-in-notice-file" +- id: "AGPL-3.0-only" + categories: + - "copyleft" + - "include-in-notice-file" + - "include-source-code-offer-in-notice-file" +- id: "AGPL-3.0-or-later" + categories: + - "copyleft" + - "include-in-notice-file" + - "include-source-code-offer-in-notice-file" +- id: "Apache-2.0" + categories: + - "permissive" + - "include-in-notice-file" +- id: "TCL" + categories: + - "permissive" + - "include-in-notice-file" +- id: "LGPL-2.0-or-later WITH WxWindows-exception-3.1" + categories: + - "exceptions" + - "include-in-notice-file" +- id: "GPL-3.0-or-later WITH Autoconf-exception-generic-3.0" + categories: + - "exceptions" + - "include-in-notice-file" +- id: "IETF-Trust" + categories: + - "exceptions" + - "include-in-notice-file" +- id: "ErlPL-1.1" + categories: + - "exceptions" + - "include-in-notice-file" +- id: "BSL-1.0" + categories: + - "permissive" + - "include-in-notice-file" +- id: "FSFUL" + categories: + - "permissive" + - "include-in-notice-file" +- id: "Public-Domain" + categories: + - "permissive" + - "include-in-notice-file" +- id: "Artistic-1.0" + categories: + - "copyleft-limited" + - "include-in-notice-file" +- id: "Artistic-2.0" + categories: + - "copyleft-limited" + - "include-in-notice-file" +- id: "BSD-1-Clause" + categories: + - "permissive" + - "include-in-notice-file" +- id: "BSD-2-Clause" + categories: + - "permissive" + - "include-in-notice-file" +- id: "BSD-2-Clause-FreeBSD" + categories: + - "permissive" + - "include-in-notice-file" +- id: "BSD-2-Clause-NetBSD" + categories: + - "permissive" + - "include-in-notice-file" +- id: "BSD-3-Clause" + categories: + - "permissive" + - "include-in-notice-file" +- id: "BSD-4-Clause" + categories: + - "permissive" + - "include-in-notice-file" +- id: "BSD-4-Clause-UC" + categories: + - "permissive" + - "include-in-notice-file" +- id: "CC-BY-1.0" + categories: + - "permissive" + - "include-in-notice-file" +- id: "CC-BY-2.0" + categories: + - "permissive" + - "include-in-notice-file" +- id: "CC-BY-2.5" + categories: + - "permissive" + - "include-in-notice-file" +- id: "CC-BY-3.0" + categories: + - "permissive" + - "include-in-notice-file" +- id: "CC-BY-4.0" + categories: + - "permissive" + - "include-in-notice-file" +- id: "CC0-1.0" + categories: + - "public-domain" + - "include-in-notice-file" +- id: "CDDL-1.0" + categories: + - "copyleft-limited" + - "include-in-notice-file" +- id: "CDDL-1.1" + categories: + - "copyleft-limited" + - "include-in-notice-file" +- id: "EPL-1.0" + categories: + - "copyleft-limited" + - "include-in-notice-file" +- id: "EPL-2.0" + categories: + - "copyleft-limited" + - "include-in-notice-file" +- id: "EUPL-1.1" + categories: + - "copyleft-limited" + - "include-in-notice-file" +- id: "EUPL-1.2" + categories: + - "copyleft-limited" + - "include-in-notice-file" +- id: "GPL-1.0+" + categories: + - "copyleft" + - "include-in-notice-file" + - "include-source-code-offer-in-notice-file" +- id: "GPL-1.0-only" + categories: + - "copyleft" + - "include-in-notice-file" + - "include-source-code-offer-in-notice-file" +- id: "GPL-1.0-or-later" + categories: + - "copyleft" + - "include-in-notice-file" + - "include-source-code-offer-in-notice-file" +- id: "GPL-2.0" + categories: + - "copyleft" + - "include-in-notice-file" + - "include-source-code-offer-in-notice-file" +- id: "GPL-2.0+" + categories: + - "copyleft" + - "include-in-notice-file" + - "include-source-code-offer-in-notice-file" +- id: "GPL-2.0-only" + categories: + - "copyleft" + - "include-in-notice-file" + - "include-source-code-offer-in-notice-file" +- id: "GPL-2.0-only WITH Classpath-exception-2.0" + categories: + - "copyleft-limited" + - "include-in-notice-file" + - "include-source-code-offer-in-notice-file" +- id: "GPL-2.0-only WITH Font-exception-2.0" + categories: + - "copyleft-limited" + - "include-in-notice-file" + - "include-source-code-offer-in-notice-file" +- id: "GPL-2.0-or-later" + categories: + - "copyleft" + - "include-in-notice-file" + - "include-source-code-offer-in-notice-file" +- id: "GPL-3.0" + categories: + - "copyleft" + - "include-in-notice-file" + - "include-source-code-offer-in-notice-file" +- id: "GPL-3.0+" + categories: + - "copyleft" + - "include-in-notice-file" + - "include-source-code-offer-in-notice-file" +- id: "GPL-3.0-only" + categories: + - "copyleft" + - "include-in-notice-file" + - "include-source-code-offer-in-notice-file" +- id: "GPL-3.0-or-later" + categories: + - "copyleft" + - "include-in-notice-file" + - "include-source-code-offer-in-notice-file" +- id: "JSON" + categories: + - "permissive" + - "include-in-notice-file" +- id: "LGPL-2.0-only" + categories: + - "copyleft-limited" + - "include-in-notice-file" +- id: "LGPL-2.0-or-later" + categories: + - "copyleft-limited" + - "include-in-notice-file" + - "include-source-code-offer-in-notice-file" +- id: "LGPL-2.1-only" + categories: + - "copyleft-limited" + - "include-in-notice-file" + - "include-source-code-offer-in-notice-file" +- id: "LGPL-2.1-or-later" + categories: + - "copyleft-limited" + - "include-in-notice-file" + - "include-source-code-offer-in-notice-file" +- id: "LGPL-3.0-only" + categories: + - "copyleft-limited" + - "include-in-notice-file" + - "include-source-code-offer-in-notice-file" +- id: "LGPL-3.0-or-later" + categories: + - "copyleft-limited" + - "include-in-notice-file" + - "include-source-code-offer-in-notice-file" +- id: "Libpng" + categories: + - "permissive" + - "include-in-notice-file" +- id: "MIT" + categories: + - "permissive" + - "include-in-notice-file" +- id: "MIT-0" + categories: + - "permissive" + - "include-in-notice-file" +- id: "MIT-feh" + categories: + - "permissive" + - "include-in-notice-file" +- id: "MPL-1.0" + categories: + - "exceptions" + - "include-in-notice-file" +- id: "MPL-1.1" + categories: + - "exceptions" + - "include-in-notice-file" +- id: "MPL-2.0" + categories: + - "exceptions" + - "include-in-notice-file" +- id: "MS-PL" + categories: + - "permissive" + - "include-in-notice-file" +- id: "MS-RL" + categories: + - "copyleft-limited" + - "include-in-notice-file" +- id: "ODbL-1.0" + categories: + - "copyleft" + - "include-in-notice-file" +- id: "OFL-1.0" + categories: + - "permissive" + - "include-in-notice-file" +- id: "OFL-1.1" + categories: + - "permissive" + - "include-in-notice-file" +- id: "OpenSSL" + categories: + - "permissive" + - "include-in-notice-file" +- id: "PSF" + categories: + - "strong-copyleft" + - "include-in-notice-file" +- id: "Python-2.0" + categories: + - "permissive" + - "include-in-notice-file" +- id: "Ruby" + categories: + - "copyleft-limited" + - "include-in-notice-file" +- id: "SAX-PD" + categories: + - "public-domain" + - "include-in-notice-file" +- id: "Unlicense" + categories: + - "public-domain" + - "include-in-notice-file" +- id: "W3C" + categories: + - "permissive" + - "include-in-notice-file" +- id: "WTFPL" + categories: + - "permissive" + - "include-in-notice-file" +- id: "X11" + categories: + - "permissive" + - "include-in-notice-file" +- id: "Zlib" + categories: + - "permissive" + - "include-in-notice-file" +- id: "bzip2-1.0.5" + categories: + - "permissive" + - "include-in-notice-file" +- id: "bzip2-1.0.6" + categories: + - "permissive" + - "include-in-notice-file" +- id: "curl" + categories: + - "permissive" + - "include-in-notice-file" +- id: "W3C-19980720" + categories: + - "permissive" + - "include-in-notice-file" +- id: "LicenseRef-scancode-w3c-docs-19990405" + categories: + - "permissive" + - "include-in-notice-file" +- id: "LicenseRef-scancode-unicode" + categories: + - "permissive" + - "include-in-notice-file" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 344b6c5065a4..a11f4e8489d4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,3 +1,6 @@ + + + # Contributing to Erlang/OTP 1. [License](#license) diff --git a/HOWTO/BENCHMARKS.md b/HOWTO/BENCHMARKS.md index b82f57ed24c9..7af5c0fa0df2 100644 --- a/HOWTO/BENCHMARKS.md +++ b/HOWTO/BENCHMARKS.md @@ -1,3 +1,6 @@ + + + Benchmarking Erlang/OTP ======================= diff --git a/HOWTO/BOOTSTRAP.md b/HOWTO/BOOTSTRAP.md index e719f93c73d8..f6952782d846 100644 --- a/HOWTO/BOOTSTRAP.md +++ b/HOWTO/BOOTSTRAP.md @@ -1,3 +1,6 @@ + + + Notes about prebuilt beam files under version control ===================================================== diff --git a/HOWTO/DEPRECATE.md b/HOWTO/DEPRECATE.md index e292ca4c7602..e26bbdee825c 100644 --- a/HOWTO/DEPRECATE.md +++ b/HOWTO/DEPRECATE.md @@ -1,3 +1,6 @@ + + + # Deprecate This HOWTO shows how to deprecate functionality from Erlang/OTP. diff --git a/HOWTO/DEVELOPMENT.md b/HOWTO/DEVELOPMENT.md index 102842020580..de78a2d8a21a 100644 --- a/HOWTO/DEVELOPMENT.md +++ b/HOWTO/DEVELOPMENT.md @@ -1,3 +1,6 @@ + + + # Developing Erlang/OTP The Erlang/OTP development repository is quite large and the make system diff --git a/HOWTO/DOCUMENTATION.md b/HOWTO/DOCUMENTATION.md index eb787f758f44..4be461e3cd2a 100644 --- a/HOWTO/DOCUMENTATION.md +++ b/HOWTO/DOCUMENTATION.md @@ -1,3 +1,6 @@ + + + # Documentation in Erlang/OTP This HOWTO describes how documentation of the Erlang/OTP project works. If you diff --git a/HOWTO/INSTALL-CROSS.md b/HOWTO/INSTALL-CROSS.md index 853b1b9f2c27..ff18f3e1dfb8 100644 --- a/HOWTO/INSTALL-CROSS.md +++ b/HOWTO/INSTALL-CROSS.md @@ -1,3 +1,6 @@ + + + Cross Compiling Erlang/OTP ========================== diff --git a/HOWTO/INSTALL-RASPBERRYPI3.md b/HOWTO/INSTALL-RASPBERRYPI3.md index 77fdc67f552c..76d8ec895da7 100644 --- a/HOWTO/INSTALL-RASPBERRYPI3.md +++ b/HOWTO/INSTALL-RASPBERRYPI3.md @@ -1,3 +1,6 @@ + + + # Cross Compiling Erlang/OTP - Raspberry Pi 3 diff --git a/HOWTO/INSTALL-WIN32-OLD.md b/HOWTO/INSTALL-WIN32-OLD.md index d2a0125ac775..347e7cd5f11a 100644 --- a/HOWTO/INSTALL-WIN32-OLD.md +++ b/HOWTO/INSTALL-WIN32-OLD.md @@ -1,3 +1,6 @@ + + + How to Build Erlang/OTP on Windows ================================== diff --git a/HOWTO/INSTALL-WIN32.md b/HOWTO/INSTALL-WIN32.md index 70d22d464324..05d63b793c1a 100644 --- a/HOWTO/INSTALL-WIN32.md +++ b/HOWTO/INSTALL-WIN32.md @@ -1,3 +1,6 @@ + + + Building Erlang/OTP on Windows ============================== diff --git a/HOWTO/INSTALL.md b/HOWTO/INSTALL.md index ecd8b4402023..752ee9914a81 100644 --- a/HOWTO/INSTALL.md +++ b/HOWTO/INSTALL.md @@ -1,3 +1,6 @@ + + + Building and Installing Erlang/OTP ================================== diff --git a/HOWTO/OTP-PATCH-APPLY.md b/HOWTO/OTP-PATCH-APPLY.md index fd3c23a26a11..b7492705695f 100644 --- a/HOWTO/OTP-PATCH-APPLY.md +++ b/HOWTO/OTP-PATCH-APPLY.md @@ -1,3 +1,6 @@ + + + Patching OTP Applications ========================= diff --git a/HOWTO/RELEASE-NOTES.md b/HOWTO/RELEASE-NOTES.md index 80871d127055..49698903add7 100644 --- a/HOWTO/RELEASE-NOTES.md +++ b/HOWTO/RELEASE-NOTES.md @@ -1,3 +1,6 @@ + + + # Writing release notes This HOWTO gives advice on how to write release notes. diff --git a/HOWTO/TESTING.md b/HOWTO/TESTING.md index c6bad4f65c35..659b6d4515e0 100644 --- a/HOWTO/TESTING.md +++ b/HOWTO/TESTING.md @@ -1,3 +1,6 @@ + + + Testing Erlang/OTP ================== diff --git a/SECURITY.md b/SECURITY.md index ffb1d6ec216d..81b92cbc15b4 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,3 +1,8 @@ + + # Security Policy ## Reporting a Vulnerability and/or Security Issues diff --git a/bootstrap/lib/kernel/include/eep48.hrl b/bootstrap/lib/kernel/include/eep48.hrl index 2ce9a1430a1d..95e0ef7a568b 100644 --- a/bootstrap/lib/kernel/include/eep48.hrl +++ b/bootstrap/lib/kernel/include/eep48.hrl @@ -1,3 +1,5 @@ +%% SPDX-License-Identifier: Apache-2.0 +%% SPDX-FileCopyrightText: 2024 Ericsson AB -define(NATIVE_FORMAT,<<"application/erlang+html">>). -define(CURR_DOC_VERSION, {1,0,0}). -record(docs_v1, {anno, diff --git a/erlang_ls.config b/erlang_ls.config index a695bbc92a43..ccd954173b65 100644 --- a/erlang_ls.config +++ b/erlang_ls.config @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: 2024 Ericsson AB apps_dirs: - "lib/*" - "erts/preloaded" diff --git a/erts/autoconf/README.md b/erts/autoconf/README.md index 4643e61f496a..a699a2c0657c 100644 --- a/erts/autoconf/README.md +++ b/erts/autoconf/README.md @@ -1,3 +1,6 @@ + + + All files in this directory except for the README.md files are copies of primary files located in the `$ERL_TOP/make/autoconf` directory. Files in this directory are updated automatically when executing diff --git a/erts/doc/docs.exs b/erts/doc/docs.exs index c9a5a44fa871..07d15a1eca50 100644 --- a/erts/doc/docs.exs +++ b/erts/doc/docs.exs @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: 2024 Ericsson AB [ annotations_for_docs: fn md -> diff --git a/erts/doc/src/erlang_port_info.md b/erts/doc/src/erlang_port_info.md index e4f7bfa1e759..657190b88616 100644 --- a/erts/doc/src/erlang_port_info.md +++ b/erts/doc/src/erlang_port_info.md @@ -1,3 +1,6 @@ + + + Returns information about `Port`. If the port identified by `Port` is not open, `undefined` is returned. If the port is closed and the calling process was previously linked to the port, the exit signal from the port is guaranteed to be delivered before `port_info/2` returns `undefined`. diff --git a/erts/emulator/asan/asan_logs_to_html b/erts/emulator/asan/asan_logs_to_html index 1526a73ce3ca..8ad4127cab00 100755 --- a/erts/emulator/asan/asan_logs_to_html +++ b/erts/emulator/asan/asan_logs_to_html @@ -1,6 +1,9 @@ #!/usr/bin/env escript %% -*- erlang -*- +%% SPDX-License-Identifier: Apache-2.0 +%% SPDX-FileCopyrightText: 2024 Ericsson AB + %% Parse address sanitizer log files generated from test runs with %% with environment variables ASAN_LOG_DIR and TS_RUN_EMU=asan set. diff --git a/erts/emulator/asan/suppress b/erts/emulator/asan/suppress index 71db30c040cc..ae20b70d8b00 100644 --- a/erts/emulator/asan/suppress +++ b/erts/emulator/asan/suppress @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: 2024 Ericsson AB + # Block passed to sigaltstack() leak:sys_thread_init_signal_stack diff --git a/erts/emulator/beam/jit/.clang-format b/erts/emulator/beam/jit/.clang-format index 288f8e2418f8..574407afe4e9 100644 --- a/erts/emulator/beam/jit/.clang-format +++ b/erts/emulator/beam/jit/.clang-format @@ -1,4 +1,6 @@ --- +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: 2024 Ericsson AB # BasedOnStyle: LLVM AccessModifierOffset: -4 AlignConsecutiveMacros: false diff --git a/erts/emulator/internal_doc/AutomaticYieldingOfCCode.md b/erts/emulator/internal_doc/AutomaticYieldingOfCCode.md index e20cc3de75ae..1577a03306f1 100644 --- a/erts/emulator/internal_doc/AutomaticYieldingOfCCode.md +++ b/erts/emulator/internal_doc/AutomaticYieldingOfCCode.md @@ -1,3 +1,6 @@ + + + Automatic Yielding of C Code ============================ diff --git a/erts/emulator/internal_doc/CarrierMigration.md b/erts/emulator/internal_doc/CarrierMigration.md index 40f6031ca872..cfd7c5b1204a 100644 --- a/erts/emulator/internal_doc/CarrierMigration.md +++ b/erts/emulator/internal_doc/CarrierMigration.md @@ -1,3 +1,6 @@ + + + Carrier Migration ================= diff --git a/erts/emulator/internal_doc/CodeLoading.md b/erts/emulator/internal_doc/CodeLoading.md index 83d3d8048bba..ceaf3982929b 100644 --- a/erts/emulator/internal_doc/CodeLoading.md +++ b/erts/emulator/internal_doc/CodeLoading.md @@ -1,3 +1,6 @@ + + + Non-blocking code loading ========================= diff --git a/erts/emulator/internal_doc/CountingInstructions.md b/erts/emulator/internal_doc/CountingInstructions.md index d4b1213d0097..93441ae7b69d 100644 --- a/erts/emulator/internal_doc/CountingInstructions.md +++ b/erts/emulator/internal_doc/CountingInstructions.md @@ -1,3 +1,6 @@ + + + Counting Instructions ===================== diff --git a/erts/emulator/internal_doc/DelayedDealloc.md b/erts/emulator/internal_doc/DelayedDealloc.md index 8a86a70b1073..1bbe27075bc5 100644 --- a/erts/emulator/internal_doc/DelayedDealloc.md +++ b/erts/emulator/internal_doc/DelayedDealloc.md @@ -1,3 +1,6 @@ + + + Delayed Dealloc =============== @@ -172,4 +175,4 @@ allocator instances where messages were allocated. By the introduction of the delayed dealloc feature, we got a speedup of between 25-45%, depending on configuration of the benchmark, when running on a relatively new machine with an Intel i7 quad core processor with -hyper-threading using 8 schedulers. \ No newline at end of file +hyper-threading using 8 schedulers. diff --git a/erts/emulator/internal_doc/GarbageCollection.md b/erts/emulator/internal_doc/GarbageCollection.md index af1b9849baf2..4119d6e90c4a 100644 --- a/erts/emulator/internal_doc/GarbageCollection.md +++ b/erts/emulator/internal_doc/GarbageCollection.md @@ -1,3 +1,6 @@ + + + # Erlang Garbage Collector Erlang manages dynamic memory with a [tracing garbage collector](https://en.wikipedia.org/wiki/Tracing_garbage_collection). More precisely a per process generational semi-space copying collector using Cheney's copy collection algorithm together with a global large object space. (See C. J. Cheney in [References](#references).) diff --git a/erts/emulator/internal_doc/PTables.md b/erts/emulator/internal_doc/PTables.md index c9eaefe938b3..55529a8555aa 100644 --- a/erts/emulator/internal_doc/PTables.md +++ b/erts/emulator/internal_doc/PTables.md @@ -1,3 +1,6 @@ + + + Process and Port Tables ======================= diff --git a/erts/emulator/internal_doc/PortSignals.md b/erts/emulator/internal_doc/PortSignals.md index f2490152caed..729af6bd93a4 100644 --- a/erts/emulator/internal_doc/PortSignals.md +++ b/erts/emulator/internal_doc/PortSignals.md @@ -1,3 +1,6 @@ + + + Port Signals ============ @@ -264,4 +267,4 @@ port the improvements can be much larger, but the scenario with one process contending with I/O is the most common one. The benchmarks were run on a relatively new machine with an Intel i7 -quad core processor with hyper-threading using 8 schedulers. \ No newline at end of file +quad core processor with hyper-threading using 8 schedulers. diff --git a/erts/emulator/internal_doc/ProcessManagementOptimizations.md b/erts/emulator/internal_doc/ProcessManagementOptimizations.md index 9e83633beffc..948df28ab90d 100644 --- a/erts/emulator/internal_doc/ProcessManagementOptimizations.md +++ b/erts/emulator/internal_doc/ProcessManagementOptimizations.md @@ -1,3 +1,6 @@ + + + Process Management Optimizations ================================ @@ -169,4 +172,4 @@ succeeding in migrating, or trying to migrate processes which is a scenario which we wanted to optimize. By the introduction of these improvements, we got a speedup of 25-35% when running this benchmark on a relatively new machine with an Intel i7 quad core processor with -hyper-threading using 8 schedulers. \ No newline at end of file +hyper-threading using 8 schedulers. diff --git a/erts/emulator/internal_doc/SuperCarrier.md b/erts/emulator/internal_doc/SuperCarrier.md index 55ac5a67afc8..57de5aa9571a 100644 --- a/erts/emulator/internal_doc/SuperCarrier.md +++ b/erts/emulator/internal_doc/SuperCarrier.md @@ -1,3 +1,6 @@ + + + Super Carrier ============= diff --git a/erts/emulator/internal_doc/ThreadProgress.md b/erts/emulator/internal_doc/ThreadProgress.md index a48b25010480..0008b14793ed 100644 --- a/erts/emulator/internal_doc/ThreadProgress.md +++ b/erts/emulator/internal_doc/ThreadProgress.md @@ -1,3 +1,6 @@ + + + Thread Progress =============== diff --git a/erts/emulator/internal_doc/Tracing.md b/erts/emulator/internal_doc/Tracing.md index bbe3ce1dfbe2..6f9e9c15e435 100644 --- a/erts/emulator/internal_doc/Tracing.md +++ b/erts/emulator/internal_doc/Tracing.md @@ -1,3 +1,6 @@ + + + # Tracing ## Implementation diff --git a/erts/emulator/internal_doc/beam_makeops.md b/erts/emulator/internal_doc/beam_makeops.md index f873bc29476c..1cd2096a1a0a 100644 --- a/erts/emulator/internal_doc/beam_makeops.md +++ b/erts/emulator/internal_doc/beam_makeops.md @@ -1,3 +1,6 @@ + + + The beam\_makeops script ======================= diff --git a/erts/preloaded/src/erl_tracer.erl b/erts/preloaded/src/erl_tracer.erl index 2796b3016416..6acde2ef22af 100644 --- a/erts/preloaded/src/erl_tracer.erl +++ b/erts/preloaded/src/erl_tracer.erl @@ -1,4 +1,7 @@ -module(erl_tracer). +%% SPDX-License-Identifier: Apache-2.0 +%% SPDX-FileCopyrightText: 2024 Erlang/OTP and contributors + -moduledoc """ Erlang tracer behavior. diff --git a/lib/diameter/doc/notes.md b/lib/diameter/doc/notes.md index d0fe0dab5e0b..0448142a689d 100644 --- a/lib/diameter/doc/notes.md +++ b/lib/diameter/doc/notes.md @@ -1,4 +1,8 @@ + + This directory contains a *very* small part of OpenSSL. Currently only parts of the OpenSSL MD5 implementation. diff --git a/lib/kernel/include/logger.hrl b/lib/kernel/include/logger.hrl index bd17f7efc411..3cc9121ffdba 100644 --- a/lib/kernel/include/logger.hrl +++ b/lib/kernel/include/logger.hrl @@ -1,3 +1,5 @@ +%% SPDX-License-Identifier: Apache-2.0 +%% SPDX-FileCopyrightText: 2024 Ericsson AB -ifndef(LOGGER_HRL). -define(LOGGER_HRL,true). -define(LOG_EMERGENCY(A),?DO_LOG(emergency,[A])). diff --git a/lib/stdlib/src/erl_abstract_code.erl b/lib/stdlib/src/erl_abstract_code.erl index 0882d65ea332..7f0a53a17465 100644 --- a/lib/stdlib/src/erl_abstract_code.erl +++ b/lib/stdlib/src/erl_abstract_code.erl @@ -1,3 +1,6 @@ +%% SPDX-License-Identifier: Apache-2.0 +%% SPDX-FileCopyrightText: 2024 Ericsson AB + -module(erl_abstract_code). -moduledoc false. -export([debug_info/4]). diff --git a/lib/stdlib/src/otp_internal.erl b/lib/stdlib/src/otp_internal.erl index 9714a51b8b40..a678a7983e42 100644 --- a/lib/stdlib/src/otp_internal.erl +++ b/lib/stdlib/src/otp_internal.erl @@ -1,6 +1,9 @@ %% %% WARNING: DO NOT EDIT THIS FILE. %% +%% SPDX-License-Identifier: Apache-2.0 +%% SPDX-FileCopyrightText: Erlang/OTP and contributors +%% %% This file was auto-generated from attributes in the source %% code. %% diff --git a/scripts/otp_compliance.escript b/scripts/otp_compliance.escript new file mode 100755 index 000000000000..689c5cc77ab6 --- /dev/null +++ b/scripts/otp_compliance.escript @@ -0,0 +1,593 @@ +#!/home/erfeafr/Code/otp/bin/escript +%% -*- erlang -*- + +-define(default_classified_result, "scan-result-classified.json"). +-define(default_scan_result, "scan-result.json"). +-define(diff_classified_result, "scan-result-diff.json"). + + +-mode(compile). + +%% +%% These commands generate a classification of files per license. +%% This output is not shown when using `ort`. +%% +%% classify: +%% takes as input a scan of ort and returns a json file containing +%% as keys the licenses and as values the files under those licenses. +%% +%% diff: +%% performs a diff of existing classification file against +%% other classification files. this is useful to guarantee that +%% files that had license X had not unexpectedly been reported differently. +%% +%% detect: +%% given a scan-result from ORT, it detects files without license +%% and writes them into disk. +%% +%% check: +%% given a recent scan-result from ORT (possibly from PR), and an +%% existing file with known files without licenses (from prev. commit), +%% calculate if new files without licenses have been added to the repo. +%% +%% +%% USE OF COMMANDS +%% +%% The commands `classify` and `diff` are useful for exploring the licenses. +%% ORT does not report in an easy way which files have been attached to which licenses, +%% unless one generates a report. At the time, we cannot generate an SBOM, +%% so we are in the dark. +%% +%% The commands `detect` and `check` can be used in CI/CD to +%% prevent entering new files with unknown license. In the normal case, +%% the `detect` command only needs to be issued once in the repo. +%% Once we keep track of this file, the command is not needed anymore, +%% as the list of files with no license should not grow, and only +%% the `check` command should be executed in the CI/CD. +%% +%% + +main(Args) -> + argparse:run(Args, cli(), #{progname => otp_compliance}). + +cli() -> + #{ commands => + #{"sbom" => + #{ help => """ + Contains useful commands to fix an ORT generated source SBOM. + + """, + commands => + #{"otp-info" => + #{ help => + """ + Adds information missing in ORT's Erlang/OTP source SBOM + - Add homepage + - Fixes license of `*.beam` files + - Fixes project name + + """, + arguments => [ sbom_option(), + input_option(?default_scan_result) ], + handler => fun sbom_otp/1 + }}}, + "explore" => + #{ help => """ + Explore license data. + Useful to figure out the mapping files-to-licenses. + + """, + commands => + #{"classify" => + #{ help => + """ + Classify files by their license group. + - Input file expects a scan-result from ORT. + - Output file shows mapping between licenses and files. + The output file can be fed to the `explore diff` command. + + """, + arguments => [ input_option(?default_scan_result), + output_option(?default_classified_result), + apply_excludes(), + apply_curations() ], + handler => fun classify/1}, + "diff" => + #{ help => + """ + Compare against previous license results. + - Input file should be the output of the `classify` command for input and base files. + - Output returns a summary of additions and deletions per license. + + """, + arguments => [ input_option(?default_classified_result), + base_file(), + output_option(?diff_classified_result) ], + handler => fun diff/1}, + + "git-author" => + #{help => "Track author of the commit", + handler => fun git_author/1}, + + "detect-beam-license" => + #{help => """ + Detects the license of a beam file, and updates the scan result. + - Input file expects classified results from `classify` command. + - Base file expects output file from `no_license` command. + - Output file expects scan-result file from ORT. + """, + arguments => [input_option(?default_classified_result), + base_file(), + output_option(?default_scan_result)], + handler => fun detect_beam_license/1} + } + }, + "compliance" => + #{ help => """ + Commands to enforce compliance policy towards unlicensed files. + """, + commands => + #{"detect" => + #{ help => + """ + Detects unlicensed files. + - Input file expects a scan-result from ORT. + - Output file is a list of files without license. + The output file can be fed to the `compliance check` command. + + """, + arguments => [ input_option(?default_scan_result), + output_option(), + apply_excludes() ], + handler => fun detect_no_license/1}, + "check" => + #{ help => + """ + Checks that no new unlicensed files have been added. + - Input file expects scan-result from ORT. + - Base file expects output file from `no_license` command. + + """, + arguments => [ input_option(?default_scan_result), + base_file(), + apply_excludes(), + output_option() ], + handler => fun check_no_license/1}}}}}. + +%% +%% Options +%% +input_option(Default) -> + #{name => input_file, + type => binary, + default => Default, + long => "-input-file"}. + +sbom_option() -> + #{name => sbom_file, + type => binary, + default => "bom.spdx.json", + long => "-sbom-file"}. + +output_option(Default) -> + #{name => output_file, + type => binary, + default => Default, + long => "-output-file"}. + +output_option() -> + #{name => output_file, + type => binary, + required => true, + long => "-output-file"}. + +apply_excludes() -> + #{name => exclude, + type => boolean, + short => $e, + default => true, + long => "-apply-excludes"}. + +apply_curations() -> + #{name => curations, + type => boolean, + short => $c, + default => true, + long => "-apply-curations"}. + +base_file() -> + #{name => base_file, + type => binary, + long => "-base-file"}. + +%% +%% Commands +%% + +git_author(_) -> + File = "no_license.json", + Unlicense = decode(File), + Result = lists:map(fun (Path) -> + %% https://stackoverflow.com/questions/27028486/how-to-execute-system-command-in-erlang-and-get-results-using-oscmd-1 + Command = "git log --reverse --format=format:\"%h -- %an-%as\" -- " ++ binary_to_list(Path) ++ " | head -n1", + Port = open_port({spawn, Command}, [stream, in, eof, hide, exit_status]), + [Path, get_data(Port, [])] + end, Unlicense), + ok = file:write_file("no_license_with_author.json", Result). + +get_data(Port, Sofar) -> + receive + {Port, {data, Bytes}} -> + get_data(Port, [Sofar|Bytes]); + {Port, eof} -> + Port ! {self(), close}, + receive + {Port, closed} -> + true + end, + receive + {'EXIT', Port, _} -> + ok + after 1 -> % force context switch + ok + end, + lists:flatten(Sofar) + end. + +%% TODO: missing step: yq -p yaml -o json ../otp/bom.spdx.yml > ../otp/bom.spdx.json +%% i.e., convert yml to json +sbom_otp(#{sbom_file := SbomFile}=Input) -> + Sbom = decode(SbomFile), + Licenses = path_to_license(Input), + Copyrights = path_to_copyright(Input, Licenses), + + Fixes = [{fun fix_name/2, ~"Erlang/OTP"}, + {fun fix_supplier/2, ~"Organization: Ericsson AB"}, + {fun fix_download_location/2, ~"https://github.com/erlang/otp/releases"}, + {fun fix_beam_licenses/2, {Licenses, Copyrights}} ], + Spdx = lists:foldl(fun ({Fun, Data}, Acc) -> Fun(Data, Acc) end, Sbom, Fixes), + file:write_file(SbomFile, json:encode(Spdx)). + +fix_name(Name, Sbom) -> + Sbom#{ ~"name" := Name}. + +fix_supplier(Name, #{~"packages" := [ Packages ] }=Sbom) -> + Packages1 = Packages#{~"supplier" := Name }, + Sbom#{~"packages" := [Packages1]}. + +fix_download_location(Url, #{~"packages" := [ Packages ] }=Sbom) -> + Packages1 = Packages#{~"downloadLocation" := Url }, + Sbom#{~"packages" := [ Packages1 ]}. + +fix_beam_licenses(LicensesAndCopyrights, + #{ ~"packages" := [Package], + ~"files" := Files}=Sbom) -> + Package1 = Package#{ ~"homepage" := ~"https://www.erlang.org", + ~"licenseConcluded" := ~"Apache-2.0"}, + Files1= lists:map( + fun (SPDX) -> + try + %% _ = case SPDX of + %% #{~"fileName" := <<"bootstrap/lib/stdlib/ebin/otp_internal.beam">>} -> + %% X = fix_beam_spdx_license(<<"lib/stdlib/src/otp_internal.erl">>, LicensesAndCopyrights, SPDX), + %% io:format("~p~n~p~n", [X, LicensesAndCopyrights]); + %% _ -> + %% ok + %% end, + case SPDX of + #{~"fileName" := <<"bootstrap/lib/compiler/ebin/", Filename/binary>>} -> + [File, _] = binary:split(Filename, ~".beam"), + fix_beam_spdx_license(<<"lib/compiler/src/", File/binary, ".erl">>, LicensesAndCopyrights, SPDX); + #{~"fileName" := <<"bootstrap/lib/kernel/ebin/",Filename/binary>>} -> + [File, _] = binary:split(Filename, ~".beam"), + fix_beam_spdx_license(<<"lib/kernel/src/", File/binary, ".erl">>, LicensesAndCopyrights, SPDX); + #{~"fileName" := <<"bootstrap/lib/stdlib/ebin/",Filename/binary>>} -> + [File, _] = binary:split(Filename, ~".beam"), + fix_beam_spdx_license(<<"lib/stdlib/src/", File/binary, ".erl">>, LicensesAndCopyrights, SPDX); + #{~"fileName" := <<"erts/preloaded/ebin/",Filename/binary>>} -> + [File, _] = binary:split(Filename, ~".beam"), + fix_beam_spdx_license(<<"erts/preloaded/src/", File/binary, ".erl">>, LicensesAndCopyrights, SPDX); + #{~"fileName" := <<"erts/emulator/internal_doc/",Filename/binary>>} -> + [File, _] = binary:split(Filename, ~".md"), + fix_beam_spdx_license(<<"erts/preloaded/src/", File/binary, ".md">>, LicensesAndCopyrights, SPDX); + _ -> + fix_spdx_license(SPDX) + end + catch + _:_ -> + fix_spdx_license(SPDX) + end + end, Files), + Sbom#{ ~"files" := Files1, ~"packages" := [Package1]}. + +%% fixes spdx license of beam files +fix_beam_spdx_license(Path, {Licenses, Copyrights}, SPDX) -> + License = maps:get(Path, Licenses, ~"NONE"), + Copyright = maps:get(Path, Copyrights, ~"NONE"), + SPDX#{ ~"copyrightText" := Copyright, + ~"licenseConcluded" := License }. + +%% fixes spdx license of non-beam files +fix_spdx_license(#{~"licenseInfoInFiles" := [License]}=SPDX) -> + SPDX#{ ~"licenseConcluded" := License }; +fix_spdx_license(SPDX) -> + SPDX. + + + +%% Given an input file, returns a mapping of +%% #{filepath => license} for each file path towards its license. +path_to_license(Input) -> + ClassifyInput = Input#{ exclude => true, curations => true}, + ClassifyLicense = group_by_licenses(ClassifyInput), + maps:fold(fun (K, Vs, Acc) -> + maps:merge(maps:from_keys(Vs, K), Acc) + end, #{}, ClassifyLicense). + +path_to_copyright(Input, _Licenses) -> + ClassifyInput = Input#{ exclude => true}, + ClassifyCopyright = group_by_copyrights(ClassifyInput), + maps:fold(fun (K, Vs, Acc) -> + maps:merge(maps:from_keys(Vs, K), Acc) + end, #{}, ClassifyCopyright). + +classify(#{output_file := Output}=Input) -> + R = group_by_licenses(Input), + ok = file:write_file(Output, json:encode(R)). + +group_by_licenses(#{input_file := Filename, + exclude := ApplyExclude, + curations := ApplyCuration}) -> + Json = decode(Filename), + Excludes = apply_excludes(Json, ApplyExclude), + Curations = apply_curations(Json, ApplyCuration), + + Licenses = licenses(scan_results(Json)), + lists:foldl(fun (License, Acc) -> + group_by_license(Excludes, Curations, License, Acc) + end, #{}, Licenses). + +group_by_copyrights(#{input_file := Filename, + exclude := ApplyExclude}) -> + Json = decode(Filename), + Excludes = apply_excludes(Json, ApplyExclude), + Copyrights = copyrights(scan_results(Json)), + lists:foldl(fun (Copyright, Acc) -> + group_by_copyright(Excludes, Copyright, Acc) + end, #{}, Copyrights). + + +apply_excludes(Json, ApplyExclude) -> + Excludes = excludes(Json), + onlyif([], ApplyExclude, fun () -> convert_excludes(Excludes) end). + +apply_curations(Json, ApplyCuration) -> + Curations = curations(Json), + onlyif([], ApplyCuration, fun () -> Curations end). + +diff(#{input_file := InputFile, base_file := BaseFile, output_file := Output}) -> + Input = decode(InputFile), + Base = decode(BaseFile), + KeyList = maps:keys(Input) ++ maps:keys(Base), + KeySet = sets:from_list(KeyList), + Data = sets:fold(fun(Key, Acc) -> set_difference(Key, Input, Base, Acc) end, #{}, KeySet), + file:write_file(Output, json:encode(Data)). + +detect_beam_license(#{input_file := ClassifyFile, + base_file := NoLicenseFile, + output_file := _OutputFile}) -> + Input = decode(ClassifyFile), + NoLicense = decode(NoLicenseFile), + + %% Create DB from filename => license + DB = maps:fold(fun (License, Files, Acc) -> + FileNames = lists:map(fun extract_module_name/1, Files), + M = maps:from_keys(FileNames, License), + maps:merge(M, Acc) + end, #{}, Input), + + %% Returns {detected licenses, new unlicensed} + {DB1, _Unknown} = + lists:foldl(fun (BinName, {LicenseMap, Unlicensed}) + when is_map(LicenseMap), is_list(Unlicensed) -> + Name = extract_module_name(BinName), + Name1 = lists:flatten(string:replace(Name, ".beam", ".erl")), + case maps:get(Name1, DB, badkey) of + badkey -> + {LicenseMap, [BinName | Unlicensed]}; + License -> + {LicenseMap#{BinName => License}, Unlicensed} + end + end, {#{}, []}, NoLicense), + %% updates unlicensed list of files + %% file:write_file(NoLicenseFile, json:encode(Unknown)), + %% TODO: updates the classify files + update_classify_file(Input, DB1), + %% TODO: updates the scan files + %% io:format("Result: ~p~nCount: ~p~n", [{length(maps:keys(DB1)), maps:keys(DB1)}, length(Unknown)]), + ok. + +-spec update_classify_file(JSON :: map(), #{Path :: binary() => License :: binary()}) -> ok. +update_classify_file(Json, DB) -> + NewMap = maps:fold(fun (Path, License, Acc) -> + case maps:get(License, Acc, badkey) of + badkey -> + Acc#{License => [Path]}; + Value -> + Acc#{License := [Path | Value]} + end + end, #{}, DB), + Result = maps:merge_with(fun (_Key, Val1, Val2) -> + Val1 ++ Val2 + end, Json, NewMap), + ok. + +extract_module_name(Bin) when is_binary(Bin) -> + S = binary_to_list(Bin), + string:reverse(hd(string:split(string:reverse(S), "/"))). + +detect_no_license(#{input_file := InputFile, + output_file := OutputFile, + exclude := ApplyExcludes}) -> + Input = decode(InputFile), + SortedResult = compute_unlicense_files(Input, ApplyExcludes), + file:write_file(OutputFile, json:encode(SortedResult)). + +compute_unlicense_files(Input, ApplyExcludes) -> + Licenses = licenses(scan_results(Input)), + + PathsWithLicense = + lists:foldl(fun (#{<<"location">> := #{<<"path">> := Path}}, Acc) -> + sets:add_element(Path, Acc) + end, sets:new(), Licenses), + + %% Get all files, incluiding those without license + Files = files_from_scanner(Input), + AllPaths = + lists:foldl(fun (#{<<"path">> := Path}, Acc) -> + sets:add_element(Path, Acc) + end, sets:new(), Files), + + %% Paths without license + PathsWithoutLicense = sets:to_list(sets:subtract(AllPaths, PathsWithLicense)), + + %% Excluded files that should be ignored + Excludes = excludes(Input), + ExcludeRegex = onlyif([], ApplyExcludes, fun () -> convert_excludes(Excludes) end), + Result = lists:foldl(fun(Path, Acc) -> + case exclude_path(Path, ExcludeRegex) of + true -> + Acc; + false -> + [Path | Acc] + end + end, [], PathsWithoutLicense), + lists:sort(Result). + +check_no_license(#{input_file := InputFile, + base_file := BaseFile, + exclude := ApplyExcludes, + output_file := OutputFile}) -> + UnlicenseNew = compute_unlicense_files(decode(InputFile), ApplyExcludes), + Unlicense = decode(BaseFile), + UnlicenseSet = sets:from_list(Unlicense), + UnlicenseNewSet = sets:from_list(UnlicenseNew), + Result = sets:to_list(sets:subtract(UnlicenseNewSet, UnlicenseSet)), + file:write_file(OutputFile, json:encode(Result)). + + +%% +%% Helper functions +%% + +excludes(Input) -> + try + #{<<"repository">> := + #{<<"config">> := + #{<<"excludes">> := #{<<"paths">> := Excludes}}}} = Input, + Excludes + catch + _:_ -> + [] + end. + + +curations(Input) -> + #{<<"repository">> := + #{<<"config">> := + #{<<"curations">> := #{<<"license_findings">> := Curations}}}} = Input, + Curations. + +scan_results(Input) -> + #{<<"scanner">> := #{<<"scan_results">> := [ScanResults]}} = Input, + ScanResults. + +licenses(Input) -> + #{<<"summary">> := #{<<"licenses">> := Licenses}} = Input, + Licenses. + +copyrights(Input) -> + #{<<"summary">> := #{<<"copyrights">> := Copyrights}} = Input, + Copyrights. + + +files_from_scanner(Input) -> + #{<<"scanner">> := #{<<"files">> := [#{<<"files">> := Files}]}} = Input, + Files. + +set_difference(Key, Input, Base, Acc) -> + InputValues = sets:from_list(maps:get(Key, Input, [])), + BaseValues = sets:from_list(maps:get(Key, Base, [])), + Additions = sets:subtract(InputValues, BaseValues), + Deletions = sets:subtract(BaseValues, InputValues), + Acc#{Key => #{addition => sets:to_list(Additions), deletions => sets:to_list(Deletions)}}. + +onlyif(_Default, true, Command) -> Command(); +onlyif(Default, false, _Command) -> Default. + +decode(Filename) -> + {ok, Bin} = file:read_file(Filename), + json:decode(Bin). + +group_by_license(ExcludeRegexes, Curations, License, Acc) -> + #{<<"license">> := LicenseName, <<"location">> := Location} = License, + #{<<"path">> := Path, <<"start_line">> := _StartLine, <<"end_line">> := _EndLine} = Location, + maybe + false ?= exclude_path(Path, ExcludeRegexes), + LicenseName1 = curated_path_license(LicenseName, Path, Curations), + case maps:get(LicenseName1, Acc, []) of + [] -> + Acc#{LicenseName1 => [Path]}; + Ls -> + Ls1 = case lists:search(fun(X) -> X == Path end, Ls) of + false -> [Path | Ls]; + _ -> Ls + end, + Acc#{LicenseName1 => Ls1} + end + else + _ -> + Acc + end. + +group_by_copyright(ExcludeRegexes, Copyright, Acc) -> + #{<<"statement">> := CopyrightSt, <<"location">> := Location} = Copyright, + #{<<"path">> := Path, <<"start_line">> := _StartLine, <<"end_line">> := _EndLine} = Location, + maybe + false ?= exclude_path(Path, ExcludeRegexes), + case maps:get(CopyrightSt, Acc, []) of + [] -> + Acc#{CopyrightSt => [Path]}; + Ls -> + Ls1 = case lists:search(fun(X) -> X == Path end, Ls) of + false -> [Path | Ls]; + _ -> Ls + end, + Acc#{CopyrightSt => Ls1} + end + else + _ -> + Acc + end. + +convert_excludes(Excludes) -> + lists:map(fun (#{<<"pattern">> := Pattern}) -> + Pattern1 = re:replace(Pattern, <<"\\.">>, <<"\\\\.">>, [global, {return, binary}]), + re:replace(Pattern1, <<"\\*\\*">>, <<".*">>, [global, {return, binary}]) + end, Excludes). + +exclude_path(_Path, []) -> + false; +exclude_path(Path, ExcludeRegexes) -> + lists:any(fun (Regex) -> + case re:run(Path, Regex) of + {match, _} -> true; + _ -> false + end + end, ExcludeRegexes). + +curated_path_license(Name, _Path, []) -> Name; +curated_path_license(_Name, Path, [#{<<"path">> := Path}=Cur | _Curations]) -> + maps:get(<<"concluded_license">>, Cur); +curated_path_license(Name, Path, [_Cur | Curations]) -> + curated_path_license(Name, Path, Curations).