blob: f4181a8e5f109434e7606ea64d341f9498eb962b [file] [log] [blame]
# -*- coding: utf-8 -*-
# Copyright 2021 The LUCI Authors. All rights reserved.
# Use of this source code is governed under the Apache License, Version 2.0
# that can be found in the LICENSE file.
import contextlib
import logging
import sys
from google.protobuf import json_format as jsonpb
from PB.recipe_engine.internal.commands.deps import deps
from ...recipe_deps import RecipeDeps # for type info
from ...exceptions import RecipeUsageError
def extract_module_names(obj):
for repo, name in obj.normalized_DEPS.values():
yield '%s/%s' % (repo, name)
def py_compat(py_compat_str):
if py_compat_str is None:
return deps.CANNOT_RUN
if py_compat_str == 'PY3':
return deps.PYTHON3_ONLY
if py_compat_str == 'PY2+3':
return deps.PYTHON2_AND_PYTHON3
return deps.PYTHON2_ONLY
def load_recipes_modules(rd, target, include_test_recipes, include_dependants):
recipes = []
mod_names = set()
if target == '*':
recipes = rd.main_repo.recipes.values()
for mod in rd.main_repo.modules.values():
recipes.extend(mod.recipes.values())
mod_name = set(rd.main_repo.modules.keys())
else:
is_module = False
if '::' in target:
target_repo, target_name = target.split('::', 1)
else:
is_module = True
target_repo, target_name = target.split('/', 1)
if not target_repo:
target_repo = rd.main_repo_id
if not is_module:
base = rd.repos[target_repo]
if ':' in target_name:
mod, target_name = target_name.split(':', 1)
base = base.modules[mod]
recipes.append(base.recipes[target_name])
else:
# Check that this module actually exists. This raises
# UnknownRecipeModule if target_name doesn't exist.
_ = rd.repos[target_repo].modules[target_name]
mod_names.add('%s/%s' % (target_repo, target_name))
if include_dependants:
mod = (target_repo, target_name)
for repo in rd.repos.values():
if repo.name == target_repo or target_repo in repo.simple_cfg.deps:
for recipe in repo.recipes.values():
if (not include_test_recipes and (
':examples/' in recipe.name
or ':tests/' in recipe.name)):
continue
if mod in recipe.normalized_DEPS.values():
recipes.append(recipe)
for recipe in recipes:
mod_names.update(extract_module_names(recipe))
return recipes, mod_names
def process_modules(ret, rd, mod_names):
processed_mod_names = set()
while mod_names:
full_mod_name = mod_names.pop()
processed_mod_names.add(full_mod_name)
repo, mod_name = full_mod_name.split('/', 1)
mod = rd.repos[repo].modules[mod_name]
mRecord = ret.modules[full_mod_name]
mRecord.repo = repo
mRecord.name = mod_name
mRecord.claimed_py3_status = py_compat(mod.python_version_compatibility)
mRecord.effective_py3_status = py_compat(mod.effective_python_compatibility)
mods = set(extract_module_names(mod))
mRecord.deps.extend(mods)
mod_names.update(mods - processed_mod_names)
cfg = mod.repo.recipes_cfg_pb2
if cfg.canonical_repo_url:
mRecord.url = '%s/+/HEAD/%s' % (
cfg.canonical_repo_url,
mod.relpath,
)
def process_recipes(ret, recipes):
for recipe in recipes:
rRecord = ret.recipes[recipe.full_name]
rRecord.repo = recipe.repo.name
rRecord.name = recipe.name
rRecord.is_recipe = True
rRecord.claimed_py3_status = py_compat(recipe.python_version_compatibility)
rRecord.effective_py3_status = py_compat(
recipe.effective_python_compatibility)
rRecord.deps.extend(extract_module_names(recipe))
cfg = recipe.repo.recipes_cfg_pb2
if cfg.canonical_repo_url:
rRecord.url = '%s/+/HEAD/%s' % (
cfg.canonical_repo_url,
recipe.relpath,
)
def output_cli(ret):
to_emoji = {
deps.CANNOT_RUN: '💀',
deps.PYTHON2_ONLY: '❌',
deps.PYTHON2_AND_PYTHON3: '✅',
deps.PYTHON3_ONLY: '🦄',
}
print("recipes:")
for _, recipe in sorted(ret.recipes.items()):
print(" %s %s %s::%s - %s" % (
to_emoji[recipe.claimed_py3_status],
to_emoji[recipe.effective_py3_status],
recipe.repo, recipe.name, recipe.url))
print()
print("modules:")
for _, module in sorted(ret.modules.items()):
print(" %s %s %s/%s - %s" % (
to_emoji[module.claimed_py3_status],
to_emoji[module.effective_py3_status],
module.repo, module.name, module.url))
def output_json(fd, ret):
fd.write(jsonpb.MessageToJson(
ret,
preserving_proto_field_name=True,
indent=2, sort_keys=True,
))
fd.write('\n')
def main(args):
rd = args.recipe_deps # type: RecipeDeps
try:
recipes, mod_names = load_recipes_modules(
rd, args.recipe_or_module, args.include_test_recipes,
args.include_dependants)
except RecipeUsageError as ex:
print('{}: {}'.format(type(ex).__name__, ex), file=sys.stderr)
return 1
ret = deps.Deps()
process_recipes(ret, recipes)
process_modules(ret, rd, mod_names)
if not args.json_output:
output_cli(ret)
return
if args.json_output:
out = args.json_output
output_json(sys.stdout if out == '-' else open(out, 'wb'), ret)