blender_asset_tracer/cli/list_deps.py

153 lines
5.0 KiB
Python

# ***** BEGIN GPL LICENSE BLOCK *****
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# ***** END GPL LICENCE BLOCK *****
#
# (c) 2018, Blender Foundation - Sybren A. Stüvel
"""List dependencies of a blend file."""
import functools
import hashlib
import json
import logging
import pathlib
import sys
import time
import typing
from blender_asset_tracer import trace, bpathlib
from . import common
log = logging.getLogger(__name__)
def add_parser(subparsers):
"""Add argparser for this subcommand."""
parser = subparsers.add_parser('list', help=__doc__)
parser.set_defaults(func=cli_list)
parser.add_argument('blendfile', type=pathlib.Path)
common.add_flag(parser, 'json', help='Output as JSON instead of human-readable text')
common.add_flag(parser, 'sha256',
help='Include SHA256sums in the output. Note that those may differ from the '
'SHA256sums in a BAT-pack when paths are rewritten.')
common.add_flag(parser, 'timing', help='Include timing information in the output')
def cli_list(args):
bpath = args.blendfile
if not bpath.exists():
log.fatal('File %s does not exist', args.blendfile)
return 3
if args.json:
if args.sha256:
log.fatal('--sha256 can currently not be used in combination with --json')
if args.timing:
log.fatal('--timing can currently not be used in combination with --json')
report_json(bpath)
else:
report_text(bpath, include_sha256=args.sha256, show_timing=args.timing)
def calc_sha_sum(filepath: pathlib.Path) -> typing.Tuple[str, float]:
start = time.time()
if filepath.is_dir():
for subfile in filepath.rglob('*'):
calc_sha_sum(subfile)
duration = time.time() - start
return '-multiple-', duration
summer = hashlib.sha256()
with filepath.open('rb') as infile:
while True:
block = infile.read(32 * 1024)
if not block:
break
summer.update(block)
digest = summer.hexdigest()
duration = time.time() - start
return digest, duration
def report_text(bpath, *, include_sha256: bool, show_timing: bool):
reported_assets = set() # type: typing.Set[pathlib.Path]
last_reported_bfile = None
shorten = functools.partial(common.shorten, pathlib.Path.cwd())
time_spent_on_shasums = 0.0
start_time = time.time()
for usage in trace.deps(bpath):
filepath = usage.block.bfile.filepath.absolute()
if filepath != last_reported_bfile:
if include_sha256:
shasum, time_spent = calc_sha_sum(filepath)
time_spent_on_shasums += time_spent
print(shorten(filepath), shasum)
else:
print(shorten(filepath))
last_reported_bfile = filepath
for assetpath in usage.files():
assetpath = bpathlib.make_absolute(assetpath)
if assetpath in reported_assets:
log.debug('Already reported %s', assetpath)
continue
if include_sha256:
shasum, time_spent = calc_sha_sum(assetpath)
time_spent_on_shasums += time_spent
print(' ', shorten(assetpath), shasum)
else:
print(' ', shorten(assetpath))
reported_assets.add(assetpath)
if show_timing:
duration = time.time() - start_time
print('Spent %.2f seconds on producing this listing' % duration)
if include_sha256:
print('Spent %.2f seconds on calculating SHA sums' % time_spent_on_shasums)
percentage = time_spent_on_shasums / duration * 100
print(' (that is %d%% of the total time' % percentage)
class JSONSerialiser(json.JSONEncoder):
def default(self, o):
if isinstance(o, pathlib.Path):
return str(o)
if isinstance(o, set):
return sorted(o)
return super().default(o)
def report_json(bpath):
import collections
# Mapping from blend file to its dependencies.
report = collections.defaultdict(set)
for usage in trace.deps(bpath):
filepath = usage.block.bfile.filepath.absolute()
for assetpath in usage.files():
assetpath = assetpath.resolve()
report[str(filepath)].add(assetpath)
json.dump(report, sys.stdout, cls=JSONSerialiser, indent=4)