@@ -67,6 +67,7 @@ class WindowsError(Exception):
6767from scancode import notice
6868from scancode import print_about
6969from scancode import Scanner
70+ from scancode import results_cache
7071from scancode .help import epilog_text
7172from scancode .help import examples_text
7273from scancode .interrupt import DEFAULT_TIMEOUT
@@ -407,12 +408,18 @@ def default_processes():
407408 help_group = cliutils .MISC_GROUP , sort_order = 1000 , cls = PluggableCommandLineOption )
408409
409410@click .option (
410- " --check-version/--no-check-version" ,
411- help = " Whether to check for new versions. Defaults to true." ,
411+ ' --check-version/--no-check-version' ,
412+ help = ' Whether to check for new versions. Defaults to true.' ,
412413 default = True ,
413414 # not yet supported in Click 6.7 but added in PluggableCommandLineOption
414415 hidden = True ,
415416 help_group = cliutils .MISC_GROUP , sort_order = 1000 , cls = PluggableCommandLineOption )
417+
418+ @click .option ('--use-cached-results' ,
419+ is_flag = True ,
420+ default = False ,
421+ help = '(EXPERIMENTAL) Enables the creation, update, and usage of cached results during scan time.' ,
422+ help_group = cliutils .CORE_GROUP , sort_order = 250 , cls = PluggableCommandLineOption )
416423def scancode (
417424 ctx ,
418425 input , # NOQA
@@ -433,6 +440,7 @@ def scancode(
433440 test_error_mode ,
434441 keep_temp_files ,
435442 check_version ,
443+ use_cached_results ,
436444 echo_func = echo_stderr ,
437445 * args ,
438446 ** kwargs ,
@@ -549,6 +557,7 @@ def scancode(
549557 return_results = False ,
550558 echo_func = echo_func ,
551559 outdated = outdated ,
560+ use_cached_results = use_cached_results ,
552561 * args ,
553562 ** kwargs
554563 )
@@ -570,7 +579,7 @@ def scancode(
570579
571580
572581def run_scan (
573- input , #
582+ input , #
574583 config_file = None ,
575584 ignore = [],
576585 from_json = False ,
@@ -594,6 +603,7 @@ def run_scan(
594603 pretty_params = None ,
595604 plugin_options = plugin_options ,
596605 outdated = None ,
606+ use_cached_results = False ,
597607 * args ,
598608 ** kwargs
599609):
@@ -1006,6 +1016,7 @@ def echo_func(*_args, **_kwargs):
10061016 verbose = verbose ,
10071017 kwargs = requested_options ,
10081018 echo_func = echo_func ,
1019+ use_cached_results = use_cached_results ,
10091020 )
10101021 success = success and scan_success
10111022
@@ -1141,7 +1152,7 @@ def load_configuration_file(path):
11411152
11421153 click .echo (f"Loading env from { path } " )
11431154 try :
1144-
1155+
11451156 config_values = saneyaml .load (path .read ())
11461157 ignores = config_values .get ("ignored_patterns" , [])
11471158 except (saneyaml .YAMLError , Exception ):
@@ -1226,6 +1237,7 @@ def run_scanners(
12261237 verbose = False ,
12271238 kwargs = None ,
12281239 echo_func = echo_stderr ,
1240+ use_cached_results = False ,
12291241):
12301242 """
12311243 Run the list of `stage` ScanPlugin `plugins` on `codebase`.
@@ -1267,7 +1279,8 @@ def run_scanners(
12671279 # TODO: add CLI option to bypass cache entirely?
12681280 scan_success = scan_codebase (
12691281 codebase , scanners , processes , timeout ,
1270- with_timing = timing , progress_manager = progress_manager )
1282+ with_timing = timing , progress_manager = progress_manager ,
1283+ use_cached_results = use_cached_results )
12711284
12721285 # TODO: add progress indicator
12731286 # run the process codebase of each scan plugin (most often a no-op)
@@ -1305,6 +1318,7 @@ def scan_codebase(
13051318 with_timing = False ,
13061319 progress_manager = None ,
13071320 echo_func = echo_stderr ,
1321+ use_cached_results = False ,
13081322):
13091323 """
13101324 Run the `scanners` Scanner objects on the `codebase` Codebase. Return True
@@ -1326,15 +1340,16 @@ def scan_codebase(
13261340 """
13271341
13281342 # NOTE: we never scan directories
1329- resources = ((r .location , r .path ) for r in codebase .walk () if r .is_file )
1343+ resources = ((r .location , r .path , r . name ) for r in codebase .walk () if r .is_file )
13301344
13311345 use_threading = processes >= 0
13321346 runner = partial (
13331347 scan_resource ,
13341348 scanners = scanners ,
13351349 timeout = timeout ,
13361350 with_timing = with_timing ,
1337- with_threading = use_threading
1351+ with_threading = use_threading ,
1352+ use_cached_results = use_cached_results ,
13381353 )
13391354
13401355 if TRACE :
@@ -1462,11 +1477,12 @@ def terminate_pool_with_backoff(pool, number_of_trials=3):
14621477
14631478
14641479def scan_resource (
1465- location_path ,
1480+ location_path_name ,
14661481 scanners ,
14671482 timeout = DEFAULT_TIMEOUT ,
14681483 with_timing = False ,
14691484 with_threading = True ,
1485+ use_cached_results = False ,
14701486):
14711487 """
14721488 Given a ``location_path`` tuple pf (location, path), return a tuple of:
@@ -1489,10 +1505,11 @@ def scan_resource(
14891505 processing and threading works.
14901506 """
14911507 scan_time = time ()
1492- location , path = location_path
1508+ location , path , name = location_path_name
14931509 results = {}
14941510 scan_errors = []
14951511 timings = {} if with_timing else None
1512+ scanners_to_run = []
14961513
14971514 if not with_threading :
14981515 interruptor = fake_interruptible
@@ -1503,8 +1520,28 @@ def scan_resource(
15031520 # and start returning values. The kill timeout is otherwise there
15041521 # as a gatekeeper for runaway processes.
15051522
1523+ results_cache_index = ''
1524+ if use_cached_results :
1525+ # compute results_cache_index
1526+ results_cache_index = results_cache .compute_results_cache_index (location = location , filename = name )
1527+
1528+ # update `results` with cached data or add scanner to scanners_to_run if no
1529+ # cache data is available
1530+ for scanner in scanners :
1531+ # get resource_cache_data
1532+ resource_cache_data = results_cache .get_results_cache_data (
1533+ results_cache_index = results_cache_index ,
1534+ plugin_name = scanner .name
1535+ )
1536+ if resource_cache_data :
1537+ results .update (resource_cache_data )
1538+ else :
1539+ scanners_to_run .append (scanner )
1540+ else :
1541+ scanners_to_run = scanners
1542+
15061543 # run each scanner in sequence in its own interruptible
1507- for scanner in scanners :
1544+ for scanner in scanners_to_run :
15081545 if with_timing :
15091546 start = time ()
15101547
@@ -1523,6 +1560,12 @@ def scan_resource(
15231560 # the return value of a scanner fun MUST be a mapping
15241561 if values_mapping :
15251562 results .update (values_mapping )
1563+ if use_cached_results :
1564+ results_cache .update_results_cache_data (
1565+ results_cache_index = results_cache_index ,
1566+ plugin_name = scanner .name ,
1567+ results = values_mapping ,
1568+ )
15261569
15271570 except Exception :
15281571 msg = 'ERROR: for scanner: ' + scanner .name + ':\n ' + traceback .format_exc ()
0 commit comments