Skip to content

Commit bd57613

Browse files
authored
Merge pull request #1399 from nexB/1397-multiple-inputs
Support multiple input path in CLI #875 #1397 Reported-by: Nico Bucher @nicobucher Signed-off-by: Philippe Ombredanne <pombredanne@nexb.com>
2 parents 887ae13 + 96069fd commit bd57613

19 files changed

Lines changed: 760 additions & 275 deletions

File tree

etc/scripts/genlicspdx.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# Copyright (c) 2019 nexB Inc. and others. All rights reserved.
4+
# http://nexb.com and https://github.com/nexB/scancode-toolkit/
5+
# The ScanCode software is licensed under the Apache License version 2.0.
6+
# Data generated with ScanCode require an acknowledgment.
7+
# ScanCode is a trademark of nexB Inc.
8+
#
9+
# You may not use this software except in compliance with the License.
10+
# You may obtain a copy of the License at: http://apache.org/licenses/LICENSE-2.0
11+
# Unless required by applicable law or agreed to in writing, software distributed
12+
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
13+
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
14+
# specific language governing permissions and limitations under the License.
15+
#
16+
# When you publish or redistribute any data created with ScanCode or any ScanCode
17+
# derivative work, you must accompany this data with the following acknowledgment:
18+
#
19+
# Generated with ScanCode and provided on an "AS IS" BASIS, WITHOUT WARRANTIES
20+
# OR CONDITIONS OF ANY KIND, either express or implied. No content created from
21+
# ScanCode should be considered or used as legal advice. Consult an Attorney
22+
# for any legal advice.
23+
# ScanCode is a free software code scanning tool from nexB Inc. and others.
24+
# Visit https://github.com/nexB/scancode-toolkit/ for support and download.
25+
26+
from __future__ import absolute_import
27+
from __future__ import unicode_literals
28+
from __future__ import print_function
29+
30+
import os
31+
32+
import click
33+
click.disable_unicode_literals_warning = True
34+
35+
from licensedcode.models import load_licenses
36+
from scancode.cli import run_scan
37+
38+
39+
"""
40+
Generate an SPDX document for each license known in ScanCode that are not usted
41+
at SPDX.
42+
Run python genlicspdx.py -h for help.
43+
44+
NOTE: this is rather inefficient as it is starting a new command line process
45+
for each license, taking a few seconds each time.
46+
Upcomming code to call a scan function instead will be more efficient.
47+
"""
48+
49+
FOSS_CATEGORIES = set([
50+
'Copyleft',
51+
'Copyleft Limited',
52+
'Patent License',
53+
'Permissive',
54+
'Public Domain',
55+
])
56+
57+
58+
@click.command()
59+
@click.argument('license_dir',
60+
type=click.Path(file_okay=False, exists=True, writable=True,
61+
allow_dash=False, resolve_path=True),
62+
metavar='DIR')
63+
@click.option('-v', '--verbose', is_flag=True, default=False, help='Print execution messages.')
64+
@click.help_option('-h', '--help')
65+
def cli(license_dir, verbose):
66+
"""
67+
Create one SPDX tag-value document for each non-SPDX ScanCode licenses.
68+
Store these in the DIR directory
69+
"""
70+
71+
base_kwargs = dict(
72+
license=True, license_diag=True, license_text=True, info=True,
73+
strip_root=True, quiet=True, return_results=False)
74+
75+
licenses_by_key = load_licenses(with_deprecated=False)
76+
77+
78+
for i, lic in enumerate(licenses_by_key.values()):
79+
ld = lic.to_dict()
80+
81+
if lic.spdx_license_key:
82+
if verbose:
83+
click.echo(
84+
'Skipping ScanCode: {key} that is an SPDX license: {spdx_license_key}'.format(**ld))
85+
continue
86+
87+
if not lic.text_file or not os.path.exists(lic.text_file):
88+
if verbose:
89+
click.echo(
90+
'Skipping license without text: {key}'.format(**ld))
91+
continue
92+
93+
if lic.category not in FOSS_CATEGORIES:
94+
if verbose:
95+
click.echo(
96+
'Skipping non FOSS license: {key}'.format(**ld))
97+
continue
98+
99+
output = 'licenseref-scancode-{key}.spdx'.format(**ld)
100+
output = os.path.join(license_dir, output)
101+
102+
if verbose:
103+
click.echo('Creating SPDX document for license: {key}'.format(**ld))
104+
click.echo('at: {output}'.format(**locals()))
105+
106+
with open(output, 'wb') as ouput_file:
107+
kwargs = dict(input=lic.text_file, spdx_tv=ouput_file)
108+
kwargs.update(base_kwargs)
109+
run_scan(**kwargs)
110+
111+
112+
if __name__ == '__main__':
113+
cli()

etc/scripts/scancli.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#
2+
# Copyright (c) 2019 nexB Inc. and others. All rights reserved.
3+
# http://nexb.com and https://github.com/nexB/scancode-toolkit/
4+
# The ScanCode software is licensed under the Apache License version 2.0.
5+
# ScanCode is a trademark of nexB Inc.
6+
#
7+
# You may not use this software except in compliance with the License.
8+
# You may obtain a copy of the License at: http://apache.org/licenses/LICENSE-2.0
9+
# Unless required by applicable law or agreed to in writing, software distributed
10+
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
11+
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
12+
# specific language governing permissions and limitations under the License.
13+
14+
from __future__ import absolute_import
15+
from __future__ import division
16+
from __future__ import print_function
17+
from __future__ import unicode_literals
18+
19+
import json
20+
from os.path import abspath
21+
from os.path import dirname
22+
from os.path import join
23+
from os.path import normpath
24+
25+
import execnet
26+
27+
import scanserv
28+
29+
"""
30+
This is a module designed to be called from Python 2 or 3 and is the client
31+
side. See scanserv for the back server module that runs on Python 2 and runs
32+
effectively scancode.
33+
"""
34+
35+
36+
def scan(locations, deserialize=False, scancode_root_dir=None):
37+
"""
38+
Scan the list of paths at `location` and return the results as an iterable
39+
of JSON strings. If `deserialize` is True the iterable contains a python data
40+
instead.
41+
Each location is scanned independently.
42+
"""
43+
if not scancode_root_dir:
44+
scancode_root_dir = abspath(normpath(__file__))
45+
scancode_root_dir = dirname(dirname(dirname(scancode_root_dir)))
46+
python2 = join(scancode_root_dir, 'bin', 'python')
47+
spec = 'popen//python={python2}'.format(**locals())
48+
gateway = execnet.makegateway(spec) # NOQA
49+
channel = gateway.remote_exec(scanserv)
50+
51+
for location in locations:
52+
# build a mapping of options to use for this scan
53+
scan_kwargs = dict(
54+
location=location,
55+
license=True,
56+
license_text=True,
57+
license_diag=True,
58+
copyright=True,
59+
info=True,
60+
processes=0,
61+
)
62+
63+
channel.send(scan_kwargs) # execute func-call remotely
64+
results = channel.receive()
65+
if deserialize:
66+
results = json.loads(results)
67+
yield results
68+
69+
70+
if __name__ == '__main__':
71+
import sys # NOQA
72+
args = sys.argv[1:]
73+
for s in scan(args):
74+
print(s)

etc/scripts/scanserv.README

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
A simple proof of concept for Python3 remoting with execnet.
2+
3+
See ticket #1400 for more.
4+
5+
This is an example of how to call Scancode as a function from Python2 or Python3.
6+
The benefits are that when the server process has loaded the license index,
7+
and imported its modules there is no per-call import/loading penalty anymore.
8+
9+
This is using execnet which is the multiprocessing library used by
10+
py.test and therefore a rather stable and high quality engine.
11+
12+
To test, do this::
13+
14+
1. checkout scancode and run ./configure in a first shell. This is for a plain
15+
ScanCode using Python 2 that will be used as a "server".
16+
17+
2. in another shell, create a virtualenv with Python 3 in another
18+
location. Activate that venv, and `pip install simplejson execnet`
19+
20+
3. Change dir to the install scancode-toolkit/etc/scripts where the scancli.py
21+
and scancserv.py scripts are. Then run::
22+
23+
python3 scancli.py ../../NOTICE ../../setup.py
24+
25+
This will effectively make remote functions calls to the Python2
26+
scancode and gets the result in Python3 alright. It also allows to have
27+
multiple calls that reuse the same process, hence amortizing any startup
28+
costs. Here this will run two scans: one on NOTICE and another on setup.py.
29+
It could have been directories too.

etc/scripts/scanserv.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#
2+
# Copyright (c) 2019 nexB Inc. and others. All rights reserved.
3+
# http://nexb.com and https://github.com/nexB/scancode-toolkit/
4+
# The ScanCode software is licensed under the Apache License version 2.0.
5+
# ScanCode is a trademark of nexB Inc.
6+
#
7+
# You may not use this software except in compliance with the License.
8+
# You may obtain a copy of the License at: http://apache.org/licenses/LICENSE-2.0
9+
# Unless required by applicable law or agreed to in writing, software distributed
10+
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
11+
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
12+
# specific language governing permissions and limitations under the License.
13+
14+
from __future__ import absolute_import
15+
from __future__ import division
16+
from __future__ import print_function
17+
from __future__ import unicode_literals
18+
19+
"""
20+
Python2 "server side" of the scan server. In a given execnet session, this
21+
process will hold a loaded license index and can be invoked multiple times
22+
without the index load penalty on each call.
23+
"""
24+
25+
26+
def as_json(results, pretty=True):
27+
"""
28+
Return a JSON string from a `results` data structuret.
29+
"""
30+
# this is used for its ability to handle iterables as arrays.
31+
import simplejson
32+
33+
kwargs = dict(iterable_as_array=True, encoding='utf-8')
34+
if pretty:
35+
kwargs.update(dict(indent=2 * b' '))
36+
else:
37+
kwargs.update(dict(separators=(b',', b':',)))
38+
return simplejson.dumps(results, **kwargs) + b'\n'
39+
40+
41+
def run_scan(location, **kwargs):
42+
from scancode import cli
43+
pretty = kwargs.pop('pretty', True)
44+
return as_json(cli.run_scan(location, **kwargs), pretty=pretty)
45+
46+
47+
if __name__ == '__channelexec__':
48+
for kwargs in channel: # NOQA
49+
# a mapping of kwargs or a location string
50+
if isinstance(kwargs, (str, unicode)):
51+
channel.send(run_scan(kwargs)) # NOQA
52+
elif isinstance(kwargs, dict):
53+
channel.send(run_scan(**kwargs)) # NOQA
54+
else:
55+
raise Exception('Unknown arguments type: ' + repr(kwargs))

0 commit comments

Comments
 (0)