Skip to content

Commit 757f8db

Browse files
committed
Merge 337_enhance_check_command
* Add an option to fetch license from ScanCode LicneseDB * Enhance `check` command to validate `license_expression` from ScanCode LicenseDB or DJC Signed-off-by: Chin Yeung Li <[email protected]>
2 parents 1f95519 + 16a24ca commit 757f8db

File tree

8 files changed

+145
-65
lines changed

8 files changed

+145
-65
lines changed

CHANGELOG.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
2021-xx-xx
2+
<<<<<<< HEAD
23
Release 7.0.0
4+
=======
5+
Release x.x.x
6+
>>>>>>> refs/heads/337_enhance_check_command
37

48
* Add '@' as a support character for filename #451
59
* Add support to collect redistributable sources #22
@@ -11,6 +15,7 @@
1115
* Update configuration scripts
1216
* Use readthedocs for documentation
1317
* Add Dockerfile to run aboutcode with docker
18+
* Add new option to choose extract license from ScanCode LicenseDB or DJC License Library
1419

1520
2021-04-02
1621
Release 6.0.0

docs/source/general.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,10 @@ You should start with a software inventory of your codebase in spreadsheet or JS
7979
- Optional. You can separate each identifier using " OR " and " AND " to document the relationship between multiple license identifiers, such as a choice among multiple licenses.
8080
* - license_key
8181
- ScanCode license key for the component.
82-
- Optional. gen will obtain license information from DejaCode Enterprise if the --fetch-license option is set, including the license text, in order to create and write the appropriate .LICENSE file in the .ABOUT file target directory.
82+
- Optional. gen will obtain license information from ScanCode LicenseDB or DejaCode Enterprise if the --fetch-license or --fetch-license-djc option is set, including the license text, in order to create and write the appropriate .LICENSE file in the .ABOUT file target directory.
8383
* - license_name
8484
- License name for the component.
85-
- Optional. This field will be generated if the --fetch-license option is set.
85+
- Optional. This field will be generated if the --fetch-license or --fetch-license-djc option is set.
8686
* - license file
8787
- license file name
8888
- Optional. gen will look for the file name (if a directory is specified in the --reference option) to copy that file to the .ABOUT file target directory.
@@ -223,11 +223,11 @@ Here is an example of a gen command:
223223

224224
.. code-block:: none
225225
226-
about gen --fetch-license {{your license library api key}} --reference /Users/harrypotter/myAboutFiles/ /Users/harrypotter/myAboutFiles/myProject-bom.csv /Users/harrypotter/myAboutFiles/
226+
about gen --fetch-license --reference /Users/harrypotter/myAboutFiles/ /Users/harrypotter/myAboutFiles/myProject-bom.csv /Users/harrypotter/myAboutFiles/
227227
228228
This gen example command does the following:
229229

230-
- Activates the --fetch-license option to get license text.
230+
- Activates the --fetch-license option to get license information.
231231

232232
- Activates the --reference option to get license text files and notice text files that you have specified in your software inventory to be copied next to the associated .ABOUT files when those are created.
233233

docs/source/reference.rst

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ Options
134134

135135
.. code-block:: none
136136
137+
--djc api_url api_key Validate license_expression from a DejaCode License
138+
Library API URL using the API KEY.
137139
--verbose Show all the errors and warning
138140
-h, --help Show this message and exit.
139141
@@ -154,6 +156,11 @@ Details
154156
155157
$ about check --verbose /home/project/about_files/
156158
159+
Special Notes
160+
-------------
161+
If no `--djc` option is set, the tool will default to check license_expression from
162+
ScanCode LicenseDB.
163+
157164
collect_redist_src
158165
==================
159166

@@ -249,19 +256,20 @@ Options
249256
--android Generate MODULE_LICENSE_XXX (XXX will be
250257
replaced by license key) and NOTICE as the same
251258
design as from Android.
252-
253-
--fetch-license api_url api_key Fetch licenses data from DejaCode License
259+
--fetch-license Fetch license data and text files from the
260+
ScanCode LicenseDB.
261+
--fetch-license-djc api_url api_key Fetch licenses data from DejaCode License
254262
Library and create <license>.LICENSE
255263
side-by-side with the generated .ABOUT file.
256264
The following additional options are required:
257-
265+
258266
api_url - URL to the DejaCode License Library
259267
API endpoint
260-
268+
261269
api_key - DejaCode API key
262270
Example syntax:
263-
264-
about gen --fetch-license 'api_url' 'api_key'
271+
272+
about gen --fetch-license-djc api_url api_key
265273
--reference PATH Path to a directory with reference license
266274
data and text files.
267275
-q, --quiet Do not print any error/warning.
@@ -279,44 +287,53 @@ Details
279287
.. code-block:: none
280288
281289
--android
282-
290+
283291
Create an empty file named `MODULE_LICENSE_XXX` where `XXX` is the license
284292
key and create a NOTICE file which these two files follow the design from
285293
Android Open Source Project.
286-
294+
287295
The input **must** have the license key information as this is needed to
288296
create the empty MODULE_LICENSE_XXX
289-
297+
290298
$ about gen --android LOCATION OUTPUT
291-
299+
292300
--fetch-license
293-
301+
302+
Fetch licenses text and create <license>.LICENSE side-by-side
303+
with the generated .ABOUT file using the data fetched from the the ScanCode LicenseDB.
304+
305+
The input needs to have the 'license_expression' field.
306+
307+
$ about gen --fetch-license LOCATION OUTPUT
308+
309+
--fetch-license-djc
310+
294311
Fetch licenses text from a DejaCode API, and create <license>.LICENSE side-by-side
295312
with the generated .ABOUT file using the data fetched from the DejaCode License Library.
296-
313+
297314
This option requires 2 parameters:
298315
api_url - URL to the DJE License Library.
299316
api_key - Hash key to authenticate yourself in the API.
300-
317+
301318
In addition, the input needs to have the 'license_expression' field.
302319
(Please contact nexB to get the api_* value for this feature)
303-
304-
$ about gen --fetch-license 'api_url' 'api_key' LOCATION OUTPUT
305-
320+
321+
$ about gen --fetch-license-djc 'api_url' 'api_key' LOCATION OUTPUT
322+
306323
--reference
307-
324+
308325
Copy the reference files such as 'license_files' and 'notice_files' to the
309326
generated location from the specified directory.
310-
327+
311328
For instance,
312329
the specified directory, /home/licenses_notices/, contains all the licenses and notices:
313330
/home/licenses_notices/apache2.LICENSE
314331
/home/licenses_notices/jquery.js.NOTICE
315-
332+
316333
$ about gen --reference /home/licenses_notices/ LOCATION OUTPUT
317-
334+
318335
--verbose
319-
336+
320337
This option tells the tool to show all errors found.
321338
The default behavior will only show 'CRITICAL', 'ERROR', and 'WARNING'
322339

src/attributecode/cmd.py

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
from attributecode.gen import generate as generate_about_files, load_inventory
3636
from attributecode.model import collect_inventory, get_copy_list
3737
from attributecode.model import copy_redist_src
38+
from attributecode.model import pre_process_and_fetch_license_dict
3839
from attributecode.model import write_output
3940
from attributecode.util import extract_zip
4041
from attributecode.util import filter_errors
@@ -210,9 +211,13 @@ def inventory(location, output, format, quiet, verbose): # NOQA
210211

211212
# FIXME: the CLI UX should be improved with two separate options for API key and URL
212213
@click.option('--fetch-license',
214+
is_flag=True,
215+
help='Fetch license data and text files from the ScanCode LicenseDB.')
216+
217+
@click.option('--fetch-license-djc',
213218
nargs=2,
214219
type=str,
215-
metavar='URL KEY',
220+
metavar='api_url api_key',
216221
help='Fetch license data and text files from a DejaCode License Library '
217222
'API URL using the API KEY.')
218223

@@ -230,7 +235,7 @@ def inventory(location, output, format, quiet, verbose): # NOQA
230235
help='Show all error and warning messages.')
231236

232237
@click.help_option('-h', '--help')
233-
def gen(location, output, android, fetch_license, reference, quiet, verbose):
238+
def gen(location, output, android, fetch_license, fetch_license_djc, reference, quiet, verbose):
234239
"""
235240
Given a CSV/JSON inventory, generate ABOUT files in the output location.
236241
@@ -252,6 +257,7 @@ def gen(location, output, android, fetch_license, reference, quiet, verbose):
252257
android=android,
253258
reference_dir=reference,
254259
fetch_license=fetch_license,
260+
fetch_license_djc=fetch_license_djc,
255261
)
256262

257263
errors = unique(errors)
@@ -469,20 +475,40 @@ def collect_redist_src(location, output, from_inventory, with_structures, zip, q
469475
type=click.Path(
470476
exists=True, file_okay=True, dir_okay=True, readable=True, resolve_path=True))
471477

478+
@click.option('--djc',
479+
nargs=2,
480+
type=str,
481+
metavar='api_url api_key',
482+
help='Validate license_expression from a DejaCode License Library '
483+
'API URL using the API KEY.')
484+
472485
@click.option('--verbose',
473486
is_flag=True,
474487
help='Show all error and warning messages.')
475488

476489
@click.help_option('-h', '--help')
477-
def check(location, verbose):
490+
def check(location, djc, verbose):
478491
"""
479492
Check .ABOUT file(s) at LOCATION for validity and print error messages.
480493
481494
LOCATION: Path to an ABOUT file or a directory with ABOUT files.
482495
"""
483496
print_version()
497+
api_url = ''
498+
api_key = ''
499+
if djc:
500+
# Strip the ' and " for api_url, and api_key from input
501+
api_url = djc[0].strip("'").strip('"')
502+
api_key = djc[1].strip("'").strip('"')
484503
click.echo('Checking ABOUT files...')
485-
errors, _abouts = collect_inventory(location)
504+
errors, abouts = collect_inventory(location)
505+
506+
507+
# Validate license_expression
508+
key_text_dict, errs = pre_process_and_fetch_license_dict(abouts, api_url, api_key)
509+
for e in errs:
510+
errors.append(e)
511+
486512
errors = unique(errors)
487513
severe_errors_count = report_errors(errors, quiet=False, verbose=verbose)
488514
sys.exit(severe_errors_count)

src/attributecode/gen.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ def update_about_resource(self):
222222
pass
223223

224224

225-
def generate(location, base_dir, android=None, reference_dir=None, fetch_license=False):
225+
def generate(location, base_dir, android=None, reference_dir=None, fetch_license=False, fetch_license_djc=False):
226226
"""
227227
Load ABOUT data from a CSV inventory at `location`. Write ABOUT files to
228228
base_dir. Return errors and about objects.
@@ -234,10 +234,13 @@ def generate(location, base_dir, android=None, reference_dir=None, fetch_license
234234
gen_license = False
235235
# FIXME: use two different arguments: key and url
236236
# Check if the fetch_license contains valid argument
237-
if fetch_license:
237+
if fetch_license_djc:
238238
# Strip the ' and " for api_url, and api_key from input
239-
api_url = fetch_license[0].strip("'").strip('"')
240-
api_key = fetch_license[1].strip("'").strip('"')
239+
api_url = fetch_license_djc[0].strip("'").strip('"')
240+
api_key = fetch_license_djc[1].strip("'").strip('"')
241+
gen_license = True
242+
243+
if fetch_license:
241244
gen_license = True
242245

243246
# TODO: WHY use posix??

src/attributecode/model.py

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import posixpath
3131
import traceback
3232
from itertools import zip_longest
33+
import urllib
3334
from urllib.parse import urljoin
3435
from urllib.parse import urlparse
3536
from urllib.request import urlopen
@@ -1523,48 +1524,69 @@ def save_as_csv(location, about_dicts, field_names):
15231524
def pre_process_and_fetch_license_dict(abouts, api_url, api_key):
15241525
"""
15251526
Modify a list of About data dictionaries by adding license information
1526-
fetched from the DejaCode API.
1527+
fetched from the ScanCode LicenseDB or DejaCode API.
15271528
"""
1528-
dje_uri = urlparse(api_url)
1529-
domain = '{uri.scheme}://{uri.netloc}/'.format(uri=dje_uri)
1530-
dje_lic_urn = urljoin(domain, 'urn/?urn=urn:dje:license:')
15311529
key_text_dict = {}
15321530
captured_license = []
15331531
errors = []
1532+
if api_url:
1533+
dje_uri = urlparse(api_url)
1534+
domain = '{uri.scheme}://{uri.netloc}/'.format(uri=dje_uri)
1535+
lic_urn = urljoin(domain, 'urn/?urn=urn:dje:license:')
1536+
url = api_url
1537+
else:
1538+
url = 'https://scancode-licensedb.aboutcode.org/'
15341539
if util.have_network_connection():
1535-
if not valid_api_url(api_url):
1536-
msg = u"URL not reachable. Invalid '--api_url'. License generation is skipped."
1540+
if not valid_api_url(url):
1541+
msg = u"URL not reachable. Invalid 'URL'. License generation is skipped."
15371542
errors.append(Error(ERROR, msg))
15381543
else:
15391544
msg = u'Network problem. Please check your Internet connection. License generation is skipped.'
15401545
errors.append(Error(ERROR, msg))
1546+
1547+
if errors:
1548+
return key_text_dict, errors
1549+
15411550
for about in abouts:
1542-
# No need to go through all the about objects for license extraction if we detected
1543-
# invalid '--api_key'
1551+
# No need to go through all the about objects if '--api_key' is invalid
15441552
auth_error = Error(ERROR, u"Authorization denied. Invalid '--api_key'. License generation is skipped.")
15451553
if auth_error in errors:
15461554
break
15471555
if about.license_expression.present:
15481556
special_char_in_expression, lic_list = parse_license_expression(about.license_expression.value)
15491557
if special_char_in_expression:
1550-
msg = (u"The following character(s) cannot be in the license_expression: " +
1558+
msg = (about.about_file_path + u": The following character(s) cannot be in the license_expression: " +
15511559
str(special_char_in_expression))
15521560
errors.append(Error(ERROR, msg))
15531561
else:
15541562
for lic_key in lic_list:
15551563
if not lic_key in captured_license:
1564+
lic_url = ''
1565+
license_text = ''
15561566
detail_list = []
1557-
license_name, license_key, license_text, errs = api.get_license_details_from_api(api_url, api_key, lic_key)
1558-
for e in errs:
1559-
if e not in errors:
1560-
errors.append(e)
1561-
if license_key:
1562-
captured_license.append(lic_key)
1563-
dje_lic_url = dje_lic_urn + license_key
1564-
detail_list.append(license_name)
1565-
detail_list.append(license_text)
1566-
detail_list.append(dje_lic_url)
1567-
key_text_dict[license_key] = detail_list
1567+
if api_key:
1568+
license_name, _license_key, license_text, errs = api.get_license_details_from_api(url, api_key, lic_key)
1569+
for severity, message in errs:
1570+
msg = (about.about_file_path + ": " + message)
1571+
errors.append(Error(severity, msg))
1572+
lic_url = lic_urn + lic_key
1573+
else:
1574+
license_url = url + lic_key + '.json'
1575+
license_text_url = url + lic_key + '.LICENSE'
1576+
try:
1577+
json_url = urlopen(license_url)
1578+
data = json.loads(json_url.read())
1579+
license_name = data['name']
1580+
license_text = urllib.request.urlopen(license_text_url).read().decode('utf-8')
1581+
lic_url = url + data['key'] + '.LICENSE'
1582+
except:
1583+
msg = about.about_file_path + u" : Invalid 'license': " + lic_key
1584+
errors.append(Error(ERROR, msg))
1585+
captured_license.append(lic_key)
1586+
detail_list.append(license_name)
1587+
detail_list.append(license_text)
1588+
detail_list.append(lic_url)
1589+
key_text_dict[lic_key] = detail_list
15681590
return key_text_dict, errors
15691591

15701592

@@ -1595,6 +1617,7 @@ def valid_api_url(api_url):
15951617
# This will always goes to exception as no key are provided.
15961618
# The purpose of this code is to validate the provided api_url is correct
15971619
urlopen(request)
1620+
return True
15981621
except HTTPError as http_e:
15991622
# The 403 error code is refer to "Authentication credentials were not provided.".
16001623
# This is correct as no key are provided.

tests/testdata/test_cmd/help/about_check_help.txt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,7 @@ Usage: about check [OPTIONS] LOCATION
55
LOCATION: Path to an ABOUT file or a directory with ABOUT files.
66

77
Options:
8-
--verbose Show all error and warning messages.
9-
-h, --help Show this message and exit.
8+
--djc api_url api_key Validate license_expression from a DejaCode License
9+
Library API URL using the API KEY.
10+
--verbose Show all error and warning messages.
11+
-h, --help Show this message and exit.

0 commit comments

Comments
 (0)