Skip to content

Commit 24aae22

Browse files
authored
Merge pull request #2828 from nexB/license-improvement-winter-2022
License improvement winter 2022
2 parents a0e576a + 18e9bc5 commit 24aae22

1,067 files changed

Lines changed: 10624 additions & 3972 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.rst

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ Important API changes:
4040
column to "path". The "copyright_holder" has been ranmed to "holder"
4141

4242

43+
Development environment changes:
44+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
45+
46+
- The license cache consistency is not checked anymore when you are using a Git
47+
checkout. The SCANCODE_DEV_MODE tag file has been removed entirely. Use
48+
instead the --reindex-licenses option to rebuild the license index.
49+
50+
4351
Copyright detection:
4452
~~~~~~~~~~~~~~~~~~~~
4553

@@ -107,6 +115,19 @@ License detection:
107115
by the word "license" and assimilated are now filtered as false matches.
108116

109117

118+
- The new --licenses-reference option adds a new "licenses_reference" top
119+
level attribute to a scan when using the JSON and YAML outputs. This contains
120+
all the details and the full text of every licenses seen in a file or
121+
package license expression of a scan. This can be added added after the fact
122+
using the --from-json option.
123+
124+
- New experimental support for non-English licenses. Use the command
125+
./scancode --reindex-licenses-for-all-languages to index all known non-English
126+
licenses and rules. From that point on, they will be detected. Because of this
127+
some licenses that were not tagged with their languages are now correctly
128+
tagged and they may not be detected unless you activate this new indexing
129+
feature.
130+
110131
Package detection:
111132
~~~~~~~~~~~~~~~~~~
112133

configure

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,12 @@ CFG_BIN_DIR=$CFG_ROOT_DIR/$VIRTUALENV_DIR/bin
5252

5353
# Find packages from the local thirdparty directory or from thirdparty.aboutcode.org
5454
if [ -f "$CFG_ROOT_DIR/thirdparty" ]; then
55-
PIP_EXTRA_ARGS="--find-links $CFG_ROOT_DIR/thirdparty "
55+
# offline mode
56+
PIP_EXTRA_ARGS="--no-index --find-links $CFG_ROOT_DIR/thirdparty "
57+
else
58+
# online mode
59+
PIP_EXTRA_ARGS="$PIP_EXTRA_ARGS --index https://thirdparty.aboutcode.org/pypi/simple"
5660
fi
57-
PIP_EXTRA_ARGS="$PIP_EXTRA_ARGS --find-links https://thirdparty.aboutcode.org/pypi"
5861

5962

6063
################################
@@ -163,9 +166,7 @@ install_packages() {
163166

164167
################################
165168
# Main command line entry point
166-
CFG_DEV_MODE=0
167169
CFG_REQUIREMENTS=$REQUIREMENTS
168-
NO_INDEX="--no-index"
169170

170171
# We are using getopts to parse option arguments that start with "-"
171172
while getopts :-: optchar; do
@@ -175,7 +176,7 @@ while getopts :-: optchar; do
175176
help ) cli_help;;
176177
clean ) clean;;
177178
dev ) CFG_REQUIREMENTS="$DEV_REQUIREMENTS" && CFG_DEV_MODE=1;;
178-
init ) NO_INDEX="";;
179+
init ) PIP_EXTRA_ARGS="$PIP_EXTRA_ARGS --extra-index-url https://pypi.org/simple/";;
179180
esac;;
180181
esac
181182
done

etc/scripts/fix_thirdparty.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
@click.option(
4545
"--strip-classifiers",
4646
is_flag=True,
47-
help="Remove danglingf classifiers",
47+
help="Remove dangling PyPI classifiers",
4848
)
4949
@click.help_option("-h", "--help")
5050
def fix_thirdparty_dir(

etc/scripts/gen_pypi_simple.py

Lines changed: 53 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -69,22 +69,22 @@ def get_package_name_from_filename(filename, normalize=True):
6969
Optionally ``normalize`` the name according to distribution name rules.
7070
Raise an ``InvalidDistributionFilename`` if the ``filename`` is invalid::
7171
72+
>>> get_package_name_from_filename("aboutcode_toolkit-5.1.0-py2.py3-none-any.whl")
73+
'aboutcode-toolkit'
74+
>>> get_package_name_from_filename("boolean.py-3.7-py2.py3-none-any.whl")
75+
'boolean-py'
76+
>>> get_package_name_from_filename("boolean.py-3.7.tar.gz")
77+
'boolean-py'
7278
>>> get_package_name_from_filename("foo-1.2.3_rc1.tar.gz")
7379
'foo'
74-
>>> get_package_name_from_filename("foo-bar-1.2-py27-none-any.whl")
80+
>>> get_package_name_from_filename("foo_bar-1.2-py27-none-any.whl")
7581
'foo-bar'
82+
>>> get_package_name_from_filename("foo.py-1.2-py27-none-any.whl")
83+
'foo-py'
7684
>>> get_package_name_from_filename("Cython-0.17.2-cp26-none-linux_x86_64.whl")
7785
'cython'
7886
>>> get_package_name_from_filename("python_ldap-2.4.19-cp27-none-macosx_10_10_x86_64.whl")
7987
'python-ldap'
80-
>>> get_package_name_from_filename("foo.whl")
81-
Traceback (most recent call last):
82-
...
83-
InvalidDistributionFilename: ...
84-
>>> get_package_name_from_filename("foo.png")
85-
Traceback (most recent call last):
86-
...
87-
InvalidFilePackageName: ...
8888
"""
8989
if not filename or not filename.endswith(dist_exts):
9090
raise InvalidDistributionFilename(filename)
@@ -133,15 +133,30 @@ def get_package_name_from_filename(filename, normalize=True):
133133
raise InvalidDistributionFilename(filename)
134134

135135
if normalize:
136-
name = name.lower().replace("_", "-")
136+
name = normalize_name(name)
137137
return name
138138

139139

140-
def build_pypi_index(directory, write_index=False):
140+
def normalize_name(name):
141141
"""
142-
Using a ``directory`` directory of wheels and sdists, create the a PyPI simple
143-
directory index at ``directory``/simple/ populated with the proper PyPI simple
144-
index directory structure crafted using symlinks.
142+
Return a normalized package name per PEP503, and copied from
143+
https://www.python.org/dev/peps/pep-0503/#id4
144+
"""
145+
return name and re.sub(r"[-_.]+", "-", name).lower() or name
146+
147+
148+
def normalize_name_plain(name):
149+
"""
150+
Return a normalized package name, but do not replace dots
151+
"""
152+
return name and re.sub(r"[-_]+", "-", name).lower() or name
153+
154+
155+
def build_pypi_index(directory):
156+
"""
157+
Using a ``directory`` directory of wheels and sdists, create the a PyPI
158+
simple directory index at ``directory``/simple/ populated with the proper
159+
PyPI simple index directory structure crafted using symlinks.
145160
146161
WARNING: The ``directory``/simple/ directory is removed if it exists.
147162
"""
@@ -154,11 +169,15 @@ def build_pypi_index(directory, write_index=False):
154169

155170
index_dir.mkdir(parents=True)
156171

157-
if write_index:
158-
simple_html_index = [
159-
"<html><head><title>PyPI Simple Index</title>",
160-
"<meta name='api-version' value='2' /></head><body>",
161-
]
172+
simple_html_index = [
173+
"<html>"
174+
"<head>"
175+
"<title>PyPI Simple Index</title>",
176+
'<meta charset="UTF-8">'
177+
'<meta name="api-version" value="2" />'
178+
"</head>"
179+
"<body>",
180+
]
162181

163182
package_names = set()
164183
for pkg_file in directory.iterdir():
@@ -172,26 +191,30 @@ def build_pypi_index(directory, write_index=False):
172191
):
173192
continue
174193

175-
pkg_name = get_package_name_from_filename(pkg_filename)
176-
pkg_index_dir = index_dir / pkg_name
194+
original_name = get_package_name_from_filename(pkg_filename, normalize=False)
195+
pkg_dir_name = normalize_name(original_name)
196+
pkg_link_name = normalize_name_plain(original_name)
197+
198+
pkg_index_dir = index_dir / pkg_dir_name
177199
pkg_index_dir.mkdir(parents=True, exist_ok=True)
178200
pkg_indexed_file = pkg_index_dir / pkg_filename
179201
link_target = Path("../..") / pkg_filename
180202
pkg_indexed_file.symlink_to(link_target)
181203

182-
if write_index and pkg_name not in package_names:
183-
esc_name = escape(pkg_name)
184-
simple_html_index.append(f'<a href="{esc_name}/">{esc_name}</a><br/>')
185-
package_names.add(pkg_name)
204+
if pkg_link_name not in package_names:
205+
esc_dir = escape(pkg_dir_name)
206+
esc_link = escape(pkg_link_name)
207+
208+
simple_html_index.append(f'<a href="{esc_dir}/">{esc_link}</a><br/>')
209+
package_names.add(pkg_link_name)
186210

187-
if write_index:
188-
simple_html_index.append("</body></html>")
189-
index_html = index_dir / "index.html"
190-
index_html.write_text("\n".join(simple_html_index))
211+
simple_html_index.append("</body></html>")
212+
index_html = index_dir / "index.html"
213+
index_html.write_text("\n".join(simple_html_index))
191214

192215

193216
if __name__ == "__main__":
194217
import sys
195218

196219
pkg_dir = sys.argv[1]
197-
build_pypi_index(pkg_dir, True)
220+
build_pypi_index(pkg_dir)

etc/scripts/licenses/buildrules.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -156,11 +156,11 @@ def all_rule_by_tokens():
156156
try:
157157
rule_tokens[tuple(rule.tokens())] = rule.identifier
158158
except Exception as e:
159-
df=(' file://' + rule.data_file)
160-
tf=(' file://' + rule.text_file)
159+
df = f" file://{rule.data_file}"
160+
tf = f" file://{rule.text_file}"
161161
raise Exception(
162-
f'Failed to to get tokens from rule:: {rule.identifier}\n'
163-
f'{df}\n{tf}'
162+
f"Failed to to get tokens from rule:: {rule.identifier}\n"
163+
f"{df}\n{tf}"
164164
) from e
165165
return rule_tokens
166166

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# Copyright (c) nexB Inc. and others. All rights reserved.
4+
# ScanCode is a trademark of nexB Inc.
5+
# SPDX-License-Identifier: Apache-2.0
6+
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
7+
# See https://github.com/nexB/scancode-toolkit for support or download.
8+
# See https://aboutcode.org for more information about nexB OSS projects.
9+
#
10+
11+
import click
12+
13+
from licensedcode.cache import get_licenses_by_spdx_key
14+
15+
import synclic
16+
17+
"""
18+
A script to generate license detection rules from lists of SPDX
19+
licenses for their name or id/name combos.
20+
21+
It is common to see SPDX license names and ids used for licensing documentation.
22+
23+
Here we fetch the latest SPDX licenses list and generate rules for each
24+
license id/name, name and a few other related combinations.
25+
"""
26+
27+
TRACE = False
28+
29+
template = """----------------------------------------
30+
license_expression: {key}
31+
relevance: 100
32+
{is_license}: yes
33+
minimum_coverage: 100
34+
is_continuous: yes
35+
notes: Rule based on an SPDX license identifier and name
36+
---
37+
{text}
38+
"""
39+
40+
41+
@click.command()
42+
@click.argument(
43+
# 'A buildrules-formatted file used to generate new licenses rules.')
44+
"output",
45+
type=click.Path(),
46+
metavar="FILE",
47+
)
48+
@click.help_option("-h", "--help")
49+
def cli(output):
50+
"""
51+
Generate ScanCode license detection rules from a list of SPDX
52+
license. Save these in FILE for use with buildrules.
53+
54+
The `spdx` directory is used as a temp store for fetched SPDX licenses.
55+
"""
56+
57+
licenses_by_spdx_key = get_licenses_by_spdx_key(
58+
licenses=None,
59+
include_deprecated=False,
60+
lowercase_keys=False,
61+
include_other_spdx_license_keys=True,
62+
)
63+
64+
spdx_source = synclic.SpdxSource(external_base_dir=None)
65+
spdx_data = list(spdx_source.fetch_spdx_licenses())
66+
67+
messages = []
68+
with open(output, "w") as o:
69+
for spdx in spdx_data:
70+
is_exception = "licenseExceptionId" in spdx
71+
spdx_key = spdx.get("licenseId") or spdx.get("licenseExceptionId")
72+
name = spdx["name"]
73+
lic = licenses_by_spdx_key.get(spdx_key)
74+
if not lic:
75+
print(
76+
"--> Skipping SPDX license unknown in ScanCode:",
77+
spdx_key,
78+
)
79+
continue
80+
for rule in build_rules(lic.key, spdx_key, name, is_exception):
81+
o.write(rule)
82+
83+
o.write("----------------------------------------\n")
84+
85+
for msg in messages:
86+
print(*msg)
87+
88+
89+
def build_rules(key, spdx_key, name, is_exception=False):
90+
yield template.format(
91+
key=key,
92+
is_license="is_license_reference",
93+
text=name,
94+
)
95+
96+
yield template.format(
97+
key=key,
98+
is_license="is_license_reference",
99+
text=f"name: {name}",
100+
)
101+
102+
yield template.format(
103+
key=key,
104+
is_license="is_license_reference",
105+
text=f"{spdx_key} {name}",
106+
)
107+
108+
yield template.format(
109+
key=key,
110+
is_license="is_license_reference",
111+
text=f"{name} {spdx_key}",
112+
)
113+
114+
yield template.format(
115+
key=key,
116+
is_license="is_license_tag",
117+
text=f"{spdx_key} {name}",
118+
)
119+
120+
yield template.format(
121+
key=key,
122+
is_license="is_license_tag",
123+
text=f"license: {spdx_key}",
124+
)
125+
126+
yield template.format(
127+
key=key,
128+
is_license="is_license_tag",
129+
text=f"license: {name}",
130+
)
131+
132+
if is_exception:
133+
yield template.format(
134+
key=key,
135+
is_license="is_license_tag",
136+
text=f"licenseExceptionId: {spdx_key}",
137+
)
138+
else:
139+
yield template.format(
140+
key=key,
141+
is_license="is_license_tag",
142+
text=f"licenseId: {spdx_key}",
143+
)
144+
145+
146+
if __name__ == "__main__":
147+
cli()

0 commit comments

Comments
 (0)