diff options
author | Denis 'GNUtoo' Carikli <GNUtoo@cyberdimension.org> | 2021-10-18 22:58:01 +0200 |
---|---|---|
committer | Denis 'GNUtoo' Carikli <GNUtoo@cyberdimension.org> | 2021-10-20 02:59:32 +0200 |
commit | f6a553a976f797d310e613e29d54b7c6570b810a (patch) | |
tree | 910c8238764368056688da71aed020844719cd65 | |
parent | f3c754121a25c2fc388355e6cc3b5c96d75277e8 (diff) | |
download | manifest-f6a553a976f797d310e613e29d54b7c6570b810a.tar.gz manifest-f6a553a976f797d310e613e29d54b7c6570b810a.tar.bz2 manifest-f6a553a976f797d310e613e29d54b7c6570b810a.zip |
Add script to check licenses
As we currently have 757 repositories, we can't review all
the code of all repositories. And unlike GNU/Linux
distributions, we don't have package definitions with
licenses.
And switching to a package manager would not only require
extensive work, but we would also need to be really sure
that that work would also need to be maintained over time.
Because of all theses constraints, and to have at least
some weak assurances that we don't bring in nonfree code
from the Android open source project, I wrote this script
to try to semi-automatically list the repositories licenses.
While it doesn't look yet into the repositories themselves,
we at least found one repository (external/dng) with a
potentially problematic license that we need to review
in more detail. For instance it has a clause that prevents
the modification of the documentation, but there is no
documentation.
There is also still 425 repositories with an unknown license
fo far:
$ ./manifest/scripts/check-licenses.py manifest/default.xml
| external/dng_sdk/LICENSE | [...]
Remaining: 331 done: 426 total: 757
Also note that this script doesn't expend the includes and
so it won't automatically check the repositories in
replicant/repositories.xml when used with default.xml.
However the repositories added or forked by Replicant are
less a concern as developers forking repositories will
probably have more probability of finding licensing issues
in a given repository than if it was handled by this script.
Signed-off-by: Denis 'GNUtoo' Carikli <GNUtoo@cyberdimension.org>
-rw-r--r-- | scripts/.gitignore | 2 | ||||
-rw-r--r-- | scripts/Makefile | 6 | ||||
-rw-r--r-- | scripts/check-licenses.py | 425 | ||||
-rwxr-xr-x | scripts/check-licenses.py~ | 34 | ||||
-rwxr-xr-x | scripts/check-licenses2.py~ | 165 | ||||
-rwxr-xr-x | scripts/manifest.py | 6 | ||||
-rw-r--r-- | scripts/tests/generate-mirror-commands/reference-mirror.sh (renamed from scripts/tests/reference-mirror.sh) | 0 |
7 files changed, 634 insertions, 4 deletions
diff --git a/scripts/.gitignore b/scripts/.gitignore index 927775a..179c022 100644 --- a/scripts/.gitignore +++ b/scripts/.gitignore @@ -1,2 +1,2 @@ __pycache__ -tests/mirror.sh +tests/generate-mirror-commands/mirror.sh diff --git a/scripts/Makefile b/scripts/Makefile index 763d794..ad28c86 100644 --- a/scripts/Makefile +++ b/scripts/Makefile @@ -1,7 +1,7 @@ .PHONY: check -check: tests/mirror.sh - cmp $< tests/reference-mirror.sh +check: tests/generate-mirror-commands/mirror.sh + cmp $< $(dir $<)reference-mirror.sh -tests/mirror.sh: ../default.xml generate-mirror-commands.py +tests/generate-mirror-commands/mirror.sh: ../default.xml generate-mirror-commands.py ./generate-mirror-commands.py $< > $@ diff --git a/scripts/check-licenses.py b/scripts/check-licenses.py new file mode 100644 index 0000000..d70807e --- /dev/null +++ b/scripts/check-licenses.py @@ -0,0 +1,425 @@ +#!/usr/bin/env python3 +# Copyright (C) 2021 Denis 'GNUtoo' Carikli <GNUtoo@cyberdimension.org> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. +import os +import re +import sh +import sys + +import manifest + +debug = False +print_headers = False +verbose = False + +_data = {} + +def has_android_free_license_filename(data, repository_path): + details = {} + found = False + for filename in data.get('files', []): + path = repository_path + os.sep + filename + + if os.path.exists(path): + details[path] = {} + details[path]['free_license'] = True + details[path]['type'] = 'license-filename' + if not found: + found = True + if not debug: + return True, details + + return found, details + +_license_filenames = { + 'license-filename': { + 'func' : has_android_free_license_filename, + 'files' : [ + 'MODULE_LICENSE_BSD_LIKE', + 'MODULE_LICENSE_APACHE2', + 'MODULE_LICENSE_BSD', + 'MODULE_LICENSE_MIT', + 'MODULE_LICENSE_BSD_APL2', + 'MODULE_LICENSE_GPL', + 'MODULE_LICENSE_PUBLIC_DOMAIN', + ], + 'sha1sums' : None, + }, +} +_data.update(_license_filenames) + +################# +# License files # +################# +license_files = [ + 'COPYING', + 'LICENSE', + 'LICENCE', + 'NIST-CONDITIONS-OF-USE', +] + +stock_license_files_checksums = [ + ############## + # Apache 2.0 # + ############## + '11d8a409876496183c8fba90eb70f25301875b67', + '5a7d7df655ba40478fae80a6abafc6afc36f9b6a', + '82f88802986ad28deac953dc06bd0bb52f3d012c', + '04f15afbbe53b69d0e4870e9308efae75efa773a', + '58853eb8199b5afe72a73a25fd8cf8c94285174b', + ######### + # Boost # + ######### + #external/catch2/LICENSE + '3cba29011be2b9d59f6204d6fa0a386b1b2dbd90', + ####### + # BSD # + ####### + # external/openssh/LICENCE, also contains + # various other permissive licenses + '0ea904438c8997ec028278e2d51a0af60ef7e698', + # external/angle/LICENSE + '37126a0eda0b30f44070f59e6833187e99a7eb83', + ################# + # BSD 2 clauses # + ################# + # external/bc/LICENSE + 'e340c40163eac1621be590383a187e06a55f7a46', + # external/neon_2_sse/LICENSE + '431443c5e39f2a2f7514b387082e5ac2877923bd', + ########################################################################### + # BSD 2 clauses + Apache2 + MIT/Expat + other weak free software licenses # + ########################################################################### + # prebuilts/maven_repo/bumptech/LICENSE + '1101898f21325ac882951894f17493b08925749a', + ################# + # BSD 3 clauses # + ################# + # prebuilts/go/linux-x86/LICENSE + 'd6a5f1ecaedd723c325a2063375b3517e808a2b5', + # external/capstone/LICENSE + '861af24907e399e873920dbbff1ea1dd73a9ba35', + # external/toolchain-utils/LICENSE + 'd9005d5cecc3c5eaa7afef623b99eb38b5cdf6ac', + # external/libsrtp2/LICENSE + 'e8a22b77b707f4d8fc8cb2f86c117fd8f7251635', + # ChromeOS authors + # external/crosvm/LICENSE, external/puffin/LICENSE + '1f0a15bbb0febb59ac19144ac84f6562770372ef', + # external/vixl/LICENCE + '94836c813237bec24a2b695dce00f85cad7cb3a5', + # modifications: "The Go Authors" + year + # external/golang-protobuf/LICENSE + 'aa9b240f558caed367795f667629ccbca28f20b2', + # external/libyuv/LICENSE + 'e9acea9922263c04ea4c231e258b25e6898ec932', + ########################### + # BSD 3 clauses + unicode # + ########################### + # external/google-breakpad/LICENSE + '86dabb511ec367808a04ba4b87c4a3e0b97765e2', + + ############### + # Expat / MIT # + ############### + # external/libepoxy/COPYING + '00f34512740377ad1f155eaa15936e472661c5e3', + # prebuilts/ktlint/LICENSE + 'da2a23609257d3856b866a33d337884785513c71', + # external/python/six/LICENSE + '5a09fe8ab2f714dc1fa204fc261eceb8d82962e8', + # external/googletest/LICENSE + '5a2314153eadadc69258a9429104cd11804ea304', + # external/fonttools/LICENSE + '3b4f969a237019242665f7adacc882c35a82c145', + # external/robolectric-shadows/LICENSE + '14e5e69109cfb70093b929e7191b6dab02b37c51', + ######## + # GPL2 # + ######## + # external/ethtool/COPYING + '4cc77b90af91e615a64ae04893fdffa7939db84c', + # external/oj-libjdwp/LICENSE + 'a4fb972c240d89131ee9e16b845cd302e0ecb05f', + ############## + # GPLv2 only # + ############## + # external/autotest/LICENSE + '86f2601747ba9f6ab13cf7eabfdfe720416965bb', + ########################################## + # GPLv2 + GPLv2||LGPL 2.1 for some parts # + ########################################## + # external/f2fs-tools/COPYING + 'a55afe19b58d728b586547acb09862752d4f1b76', + # external/libusb/COPYING + 'caeb68c46fa36651acf592771d09de7937926bb3', + # external/libfuse/LICENSE + '0028a619acd3ff14a6d35b2837bb85a3ad2bab2b', + ############### + # Imagemagick # + ############### + # external/ImageMagick/LICENSE + '1de15ef06b3465e1bb922ba9c69a2a67a0263455', + ################### + # LGPL || Apache2 # + ################### + # external/javaparser/LICENSE + 'caaf43ae64cd4674b8e99869d658870b65fd6733', + ######################## + # MPLv2 + 2 clause BSD # + ######################## + # "libkms++ is mostly under the MPLv2 license except for a snippet in + # modedb_cea.cpp which is derived from 2-clause BSD licensed files from the + # Linux kernel." + # external/libkmsxx/LICENSE + 'a4ac2bea4ed2d4726528801b8880415b70f26157', + ##################################################### + # (MPLv2 || GPLv2 || LGPLv2.1) +? ||? BSD 3 clauses # + ##################################################### + # external/chromium-libpac/LICENSE + '437ba59383a90fd0033eb7b77370eb6c399b72de', + ########### + # OFL-1.1 # + ########### + # TODO: The OFL licenses are almost all the same with some variations at the + # very begining with the font name and some minor variations, so it could be + # automatically detected with code to make the maintenance easier when + # porting Replicant to newer Android versions + # external/google-fonts/source-sans-pro/LICENSE + 'a55d8584dd1af1bd63bf607a6195d56beb8ee564', + # external/google-fonts/lato/LICENSE + '76897b37e127e2332a1a79aab2e0d6f30ccdc47a', + # external/google-fonts/arvo/LICENSE + '2ef38678dab952ed745c71e59e9a2f595cddb0f4', + # external/google-fonts/big-shoulders-text/LICENSE + '35765f09b7708df97741f83ca94978a8a742adfa', + # external/google-fonts/fraunces/LICENSE + '587180e421d550a5e4bf6a6e76275c21d486e8ef', + # external/google-fonts/arbutus-slab/LICENSE + 'd9c9ee6d894d5fd114fb317a2fda29823dd90d57', + # external/google-fonts/barlow/LICENSE + '92050ce5096f0a97a4140e5be847f25901d1f327', + # external/google-fonts/karla/LICENSE + '0c63ce44ac335ff9fab43f701affbb85475d9dda', + # external/google-fonts/lustria/LICENSE + '5d0d72fede5a8ae33cb8f75ae2c9814092ad19b2', + # external/google-fonts/rubik/LICENSE + 'bf47091f200e5c46fa3032c8a143bb197f2f6a22', + # external/google-fonts/zilla-slab/LICENSE + 'e81354927f121d3ba67ef5e48b34dc930cdeca06', + ######### + # PSF 2 # + ######### + # external/python/ipaddress/LICENSE + '3f92966b8a60875dca8d9e1d6b63ac7eb9e4178e', + ############## + # Python 2.1 # + ############## + '0c492b235e749a739628cdd18d8c860c6f70396b', + # external/python/cpython2/LICENSE + 'ee7904173585b2506078dc8dac150638f1b3e537', + # external/python/cpython3/LICENSE + 'f8ec80e0c4f67f0f460f6933dcbe99f36a82f023', + ########################## + # Unlicense || Expat/MIT # + ########################## + # external/rust/crates/byteorder/COPYING + 'dd445710e6e4caccc4f8a587a130eaeebe83f6f6', + + ####### + # X11 # + ####### + # external/shaderc/spirv-headers/LICENSE + '9a84200f47e09abfbde1a6b25028460451b23d03', +] + +modified_license_files_checksums = [ + ############## + # Apache 2.0 # + ############## + '11d8a409876496183c8fba90eb70f25301875b67', + # packages/apps/Dialer/LICENSE + # Has additional apache 2005-2008 copyright header + 'b3ca008f1479a18a234094ea7169bc2dbbd141bb', + # hardware/st/secure_element/LICENSE + '5166166f89e06525e7cddb92f37f8670b3bd628f', + # external/subsampling-scale-image-view/LICENSE + '8a16eeaa6a87c42f0d7346e1f641d060bbf1f27f', + ######### + # GPLv2 # + ######### + # libcore/LICENSE + '56423feb073860b0b2de8dd6dc9053c2e291f483', + ############# + # LGPLv 2.1 # + ############# + # prebuilts/checkstyle/LICENSE + 'afb804cc9151ceb4044111bd37da309b29e844f3', + # external/kmod/COPYING + 'f0a5efd7fe620e424bc7f8dcf809f2166ae4f372', + # external/libexif/COPYING + 'e8b46065a76eacf8b16ba95038a8dadf61dcc264', + # external/libnl/COPYING + 'acd27bde2d7451c37a297c578899e29794a3008d', + # external/libiio/LICENSE + 'adfb290d0f03d365c548d0834d3276a96665b76c', + +] + +def has_stock_free_license_file(data, repository_path): + details = {} + found = False + + for filename in data.get('files', []): + path = repository_path + os.sep + filename + + if os.path.exists(path): + sha1sum = sh.sha1sum(path).split(' ')[0] + + new_found = sha1sum in data.get('sha1sums', []) + + details[path] = {} + details[path]['free_license'] = new_found + details[path]['sha1sum'] = sha1sum + details[path]['type'] = 'stock-license-file' + + if not found: + found = new_found + if not debug: + return found, details + + return found, details + +stock_license_files = { + 'stock-license-file': { + 'func' : has_stock_free_license_file, + 'files': license_files, + 'sha1sums': stock_license_files_checksums, + }, +} +_data.update(stock_license_files) + +def has_modified_free_license_file(data, repository_path): + details = {} + found = False + + for filename in data.get('files', []): + path = repository_path + os.sep + filename + + if os.path.exists(path): + # Remove all whitespaces and line breaks + buf = open(path, 'r').read() + buf = buf.replace(' ', '') + buf = buf.replace('\r', '') + buf = buf.replace('\n', '') + + sha1sum = sh.sha1sum(_in=buf).split(' ')[0] + + new_found = sha1sum in data.get('sha1sums', []) + + details[path] = {} + details[path]['free_license'] = new_found + details[path]['sha1sum'] = sha1sum + details[path]['type'] = 'modified-license-file' + + if not found: + found = new_found + if not debug: + return found, details + + return found, details + +modified_license_files = { + 'modified-license-file': { + 'func' : has_modified_free_license_file, + 'files': license_files, + 'sha1sums': modified_license_files_checksums, + }, +} + +_data.update(modified_license_files) + +def has_free_license(data, repository_path): + found = False + details = [] + + for method, method_data in data.items(): + new_found, new_details = method_data['func'](data[method], repository_path) + details.append(new_details) + + if not found: + found = new_found + if not debug and found: + return found, details + + return found, details + +def print_todo(repository_path, details): + stock_licenses_sha1sum = None + modified_licenses_sha1sum = None + license_file_path = None + for data in details: + if len(data) == 0: + continue + for file_path, values in data.items(): + if values['type'] == 'stock-license-file': + stock_licenses_sha1sum = values['sha1sum'] + if license_file_path == None: + license_file_path = file_path + else: + assert(license_file_path == file_path) + elif values['type'] == 'modified-license-file': + modified_licenses_sha1sum = values['sha1sum'] + if license_file_path == None: + license_file_path = file_path + else: + assert(license_file_path == file_path) + + if license_file_path \ + and stock_licenses_sha1sum \ + and modified_licenses_sha1sum: + if print_headers: + print("| {} | {} | {} |".format("path", "file sha1sum", + "stripped file sha1sum")) + print("| {} | {} | {} |".format(license_file_path, + stock_licenses_sha1sum, + modified_licenses_sha1sum)) + +if __name__ == '__main__': + if len(sys.argv) != 2: + usage(sys.argv[0]) + + manifest_xml_path = sys.argv[1] + + m = manifest.Manifest(manifest_xml_path) + paths = m.list_repositories_paths() + + nr_unknown_repositories = len(paths) + for path in paths: + if verbose: + print(path) + found, details = has_free_license(_data, path) + if found: + nr_unknown_repositories -= 1 + if verbose: + print(found, details) + print() + if not found and len(details) > 0: + print_todo(path, details) + + print("Remaining: {} done: {} total: {}".format( + nr_unknown_repositories, + len(paths) - nr_unknown_repositories, + len(paths))) diff --git a/scripts/check-licenses.py~ b/scripts/check-licenses.py~ new file mode 100755 index 0000000..f3f022c --- /dev/null +++ b/scripts/check-licenses.py~ @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +# Copyright (C) 2021 Denis 'GNUtoo' Carikli <GNUtoo@cyberdimension.org> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +import sys + +import manifest + +def usage(progname): + print ('Usage:') + print ('\t{} <path/to/default.xml>'.format(progname)) + sys.exit(1) + +if __name__ == '__main__': + if len(sys.argv) != 2: + usage(sys.argv[0]) + + manifest_xml_path = sys.argv[1] + + m = manifest.Manifest(manifest_xml_path) + m.parse() + m.close() diff --git a/scripts/check-licenses2.py~ b/scripts/check-licenses2.py~ new file mode 100755 index 0000000..f1973f4 --- /dev/null +++ b/scripts/check-licenses2.py~ @@ -0,0 +1,165 @@ +#!/usr/bin/env python3 +# Copyright (C) 2021 Denis 'GNUtoo' Carikli <GNUtoo@cyberdimension.org> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +_data = {} + +def has_android_free_license_filename(data, repository_path): + pass + +_license_filenames = { + 'license-filename': { + 'func' : has_android_free_license_filename, + 'files' : [ + 'MODULE_LICENSE_BSD_LIKE', + 'MODULE_LICENSE_APACHE2', + 'MODULE_LICENSE_BSD', + 'MODULE_LICENSE_MIT', + 'MODULE_LICENSE_BSD_APL2', + 'MODULE_LICENSE_GPL', + 'MODULE_LICENSE_PUBLIC_DOMAIN', + ], + 'sha1sums' : None, + }, +} +_data.update(_license_filenames) + +################# +# License files # +################# +license_files = [ + 'COPYING', + 'LICENSE', + 'LICENCE', + 'NIST-CONDITIONS-OF-USE', +] + +stock_license_files_checksums = [ + ############## + # Apache 2.0 # + ############## + '11d8a409876496183c8fba90eb70f25301875b67', + '5a7d7df655ba40478fae80a6abafc6afc36f9b6a', + '82f88802986ad28deac953dc06bd0bb52f3d012c', + '04f15afbbe53b69d0e4870e9308efae75efa773a', + '58853eb8199b5afe72a73a25fd8cf8c94285174b', + ####### + # BSD # + ####### + # external/openssh/LICENCE, also contains + # various other permissive licenses + '0ea904438c8997ec028278e2d51a0af60ef7e698', + ############### + # Expat / MIT # + ############### + # external/libepoxy/COPYING + '00f34512740377ad1f155eaa15936e472661c5e3', + ########### + # OFL-1.1 # + ########### + # external/google-fonts/source-sans-pro/LICENSE + 'a55d8584dd1af1bd63bf607a6195d56beb8ee564', + # external/google-fonts/lato/LICENSE + '76897b37e127e2332a1a79aab2e0d6f30ccdc47a', + # external/google-fonts/arvo/LICENSE + '2ef38678dab952ed745c71e59e9a2f595cddb0f4', + # external/google-fonts/big-shoulders-text/LICENSE + '35765f09b7708df97741f83ca94978a8a742adfa', + # external/google-fonts/fraunces/LICENSE + '587180e421d550a5e4bf6a6e76275c21d486e8ef', + ############## + # Python 2.1 # + ############## + '0c492b235e749a739628cdd18d8c860c6f70396b', +] + +modified_license_files_checksums = [ + # Apache 2.0 + '11d8a409876496183c8fba90eb70f25301875b67', +] + +def has_stock_free_license_file(path): + pass + +stock_license_files = { + 'stock-license-file': { + 'func' : has_stock_free_license_file, + 'files': license_files, + 'sha1sums': stock_license_files_checksums, + }, +} +_data.update(stock_license_files) + +def has_modified_free_license_file(path): + pass + +modified_license_files = { + 'modified-license-file': { + 'func' : has_modified_free_license_file, + 'files': license_files, + 'sha1sums': modified_license_files_checksums, + }, +} + +_data.update(modified_license_files) + + +if __name__ == '__main__': + verbose = True + if len(sys.argv) != 2: + usage(sys.argv[0]) + + manifest_xml_path = sys.argv[1] + + m = manifest.Manifest(manifest_xml_path) + paths = m.list_repositories_paths() + + nr_unknown_repositories = 0 + nr_unknown_licenses = 0 + for path in paths: + l = RepositoryLicense(path) + found, details = l.has_free_license() + if not found and verbose: + nr_unknown_repositories += 1 + if len(details) > 0: + for k, v in details.items(): + printed_header = False + if v['type'] in ['stock-license-file', + 'modified-license-file']: + if not printed_header: + print("{}:".format(path)) + printed_header = True + print("- {} {}".format(k, v['sha1sum'])) + nr_unknown_licenses += 1 + # We'll handle repositories with READMEs when we + # will have handled all the licenses files + # else: + # print("- TODO: look at the READMEs") + # We'll handle these repositories when we + # will have handled all the licenses files + # else: + # print("{}".format(path)) + + if verbose: + if nr_unknown_repositories > 0 or nr_unknown_licenses > 0: + print() + print("Total:") + print("------") + if nr_unknown_repositories > 0: + print("- {} repositories to check".format(nr_unknown_repositories)) + if nr_unknown_licenses > 0: + print("- {} licenses to check".format(nr_unknown_licenses)) + + diff --git a/scripts/manifest.py b/scripts/manifest.py index d1188e6..065ee7f 100755 --- a/scripts/manifest.py +++ b/scripts/manifest.py @@ -184,5 +184,11 @@ class Manifest(object): print(command) print('') + def list_repositories_paths(self): + paths = [] + for child in self.root.iter('project'): + paths.append(self.get_project_property(child, 'path')) + return paths + def close(self): self.xml_file.close() diff --git a/scripts/tests/reference-mirror.sh b/scripts/tests/generate-mirror-commands/reference-mirror.sh index 7d9ed43..7d9ed43 100644 --- a/scripts/tests/reference-mirror.sh +++ b/scripts/tests/generate-mirror-commands/reference-mirror.sh |