257 lines
9.2 KiB
Python
257 lines
9.2 KiB
Python
|
#!/usr/bin/python3 -B
|
||
|
|
||
|
# Copyright 2021 The Android Open Source Project
|
||
|
#
|
||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
# you may not use this file except in compliance with the License.
|
||
|
# You may obtain a copy of the License at
|
||
|
#
|
||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||
|
#
|
||
|
# Unless required by applicable law or agreed to in writing, software
|
||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
# See the License for the specific language governing permissions and
|
||
|
# limitations under the License.
|
||
|
"""ojluni_modify_expectation is a command-line tool for modifying the EXPECTED_UPSTREAM file."""
|
||
|
|
||
|
import argparse
|
||
|
import sys
|
||
|
|
||
|
# pylint: disable=g-importing-member
|
||
|
# pylint: disable=g-multiple-import
|
||
|
from typing import (
|
||
|
Sequence,
|
||
|
List,
|
||
|
)
|
||
|
|
||
|
from common_util import (
|
||
|
ExpectedUpstreamEntry,
|
||
|
ExpectedUpstreamFile,
|
||
|
LIBCORE_DIR,
|
||
|
OpenjdkFinder,
|
||
|
OjluniFinder,
|
||
|
)
|
||
|
|
||
|
# Import git only after common_util because common_util will
|
||
|
# produce informative error
|
||
|
from git import (Commit, Repo)
|
||
|
from gitdb.exc import BadName
|
||
|
|
||
|
LIBCORE_REPO = Repo(LIBCORE_DIR.as_posix())
|
||
|
|
||
|
AUTOCOMPLETE_TAGS = [
|
||
|
'jdk7u/jdk7u40-b60',
|
||
|
'jdk8u/jdk8u121-b13',
|
||
|
'jdk8u/jdk8u60-b31',
|
||
|
'jdk9/jdk-9+181',
|
||
|
'jdk11u/jdk-11+28',
|
||
|
'jdk11u/jdk-11.0.13-ga',
|
||
|
]
|
||
|
|
||
|
|
||
|
def error_and_exit(msg: str) -> None:
|
||
|
print(f'Error: {msg}', file=sys.stderr)
|
||
|
sys.exit(1)
|
||
|
|
||
|
|
||
|
def get_commit_or_exit(git_ref: str) -> Commit:
|
||
|
try:
|
||
|
return LIBCORE_REPO.commit(git_ref)
|
||
|
except BadName as e:
|
||
|
error_and_exit(f'{e}')
|
||
|
|
||
|
|
||
|
def autocomplete_tag_or_commit(str_tag_or_commit: str) -> List[str]:
|
||
|
"""Returns a list of tags / commits matching the given partial string."""
|
||
|
if str_tag_or_commit is None:
|
||
|
str_tag_or_commit = ''
|
||
|
return list(
|
||
|
filter(lambda tag: tag.startswith(str_tag_or_commit), AUTOCOMPLETE_TAGS))
|
||
|
|
||
|
|
||
|
COMMAND_ACTIONS = ['add', 'modify', 'sort']
|
||
|
|
||
|
|
||
|
def autocomplete_action(partial_str: str) -> None:
|
||
|
result_list = list(
|
||
|
filter(lambda action: action.startswith(partial_str), COMMAND_ACTIONS))
|
||
|
print('\n'.join(result_list))
|
||
|
exit(0)
|
||
|
|
||
|
|
||
|
def main(argv: Sequence[str]) -> None:
|
||
|
is_auto_complete = len(argv) >= 2 and argv[0] == '--autocomplete'
|
||
|
# argparse can't help autocomplete subcommand. We implement this without
|
||
|
# argparse here.
|
||
|
if is_auto_complete and argv[1] == '1':
|
||
|
action = argv[2] if len(argv) >= 3 else ''
|
||
|
autocomplete_action(action)
|
||
|
|
||
|
# If it's for autocompletion, then all arguments are optional.
|
||
|
parser_nargs = '?' if is_auto_complete else 1
|
||
|
|
||
|
main_parser = argparse.ArgumentParser(
|
||
|
description='A command line tool modifying the EXPECTED_UPSTREAM file.')
|
||
|
# --autocomplete <int> is an 'int' argument because the value represents
|
||
|
# the raw index of the argument to be autocompleted received in the Shell,
|
||
|
# and this number is not always the same as the number of arguments
|
||
|
# received here, i.e. len(argv), for examples of empty value in the
|
||
|
# argument or autocompleting the middle argument, not last argument.
|
||
|
main_parser.add_argument(
|
||
|
'--autocomplete', type=int, help='flag when tabbing in command line')
|
||
|
subparsers = main_parser.add_subparsers(
|
||
|
dest='command', help='sub-command help')
|
||
|
|
||
|
add_parser = subparsers.add_parser(
|
||
|
'add', help='Add a new entry into the EXPECTED_UPSTREAM '
|
||
|
'file')
|
||
|
add_parser.add_argument(
|
||
|
'tag_or_commit',
|
||
|
nargs=parser_nargs,
|
||
|
help='A git tag or commit in the upstream-openjdkXXX branch')
|
||
|
add_parser.add_argument(
|
||
|
'class_or_source_file',
|
||
|
nargs=parser_nargs,
|
||
|
help='Fully qualified class name or upstream source path')
|
||
|
add_parser.add_argument(
|
||
|
'ojluni_path', nargs='?', help='Destination path in ojluni/')
|
||
|
|
||
|
modify_parser = subparsers.add_parser(
|
||
|
'modify', help='Modify an entry in the EXPECTED_UPSTREAM file')
|
||
|
modify_parser.add_argument(
|
||
|
'class_or_ojluni_path', nargs=parser_nargs, help='File path in ojluni/')
|
||
|
modify_parser.add_argument(
|
||
|
'tag_or_commit',
|
||
|
nargs=parser_nargs,
|
||
|
help='A git tag or commit in the upstream-openjdkXXX branch')
|
||
|
modify_parser.add_argument(
|
||
|
'source_file', nargs='?', help='A upstream source path')
|
||
|
|
||
|
subparsers.add_parser(
|
||
|
'sort', help='Sort the entries in the EXPECTED_UPSTREAM file')
|
||
|
|
||
|
args = main_parser.parse_args(argv)
|
||
|
|
||
|
expected_upstream_file = ExpectedUpstreamFile()
|
||
|
expected_entries = expected_upstream_file.read_all_entries()
|
||
|
|
||
|
if is_auto_complete:
|
||
|
no_args = args.autocomplete
|
||
|
|
||
|
autocomp_result = []
|
||
|
if args.command == 'modify':
|
||
|
if no_args == 2:
|
||
|
input_class_or_ojluni_path = args.class_or_ojluni_path
|
||
|
if input_class_or_ojluni_path is None:
|
||
|
input_class_or_ojluni_path = ''
|
||
|
|
||
|
existing_dst_paths = list(
|
||
|
map(lambda entry: entry.dst_path, expected_entries))
|
||
|
ojluni_finder: OjluniFinder = OjluniFinder(existing_dst_paths)
|
||
|
# Case 1: Treat the input as file path
|
||
|
autocomp_result += ojluni_finder.match_path_prefix(
|
||
|
input_class_or_ojluni_path)
|
||
|
|
||
|
# Case 2: Treat the input as java package / class name
|
||
|
autocomp_result += ojluni_finder.match_classname_prefix(
|
||
|
input_class_or_ojluni_path)
|
||
|
elif no_args == 3:
|
||
|
autocomp_result += autocomplete_tag_or_commit(args.tag_or_commit)
|
||
|
elif args.command == 'add':
|
||
|
if no_args == 2:
|
||
|
autocomp_result += autocomplete_tag_or_commit(args.tag_or_commit)
|
||
|
elif no_args == 3:
|
||
|
commit = get_commit_or_exit(args.tag_or_commit)
|
||
|
class_or_src_path = args.class_or_source_file
|
||
|
if class_or_src_path is None:
|
||
|
class_or_src_path = ''
|
||
|
|
||
|
openjdk_finder: OpenjdkFinder = OpenjdkFinder(commit)
|
||
|
|
||
|
matches = openjdk_finder.match_path_prefix(
|
||
|
class_or_src_path)
|
||
|
|
||
|
matches += openjdk_finder.match_classname_prefix(
|
||
|
class_or_src_path)
|
||
|
|
||
|
existing_dst_paths = set(map(lambda e: e.dst_path, expected_entries))
|
||
|
|
||
|
# Translate the class names or source paths to dst paths and exclude
|
||
|
# such matches from the auto-completion result
|
||
|
def source_not_exists(src_path_or_class: str) -> bool:
|
||
|
nonlocal existing_dst_paths, openjdk_finder
|
||
|
t_src_path = openjdk_finder.find_src_path_from_classname(
|
||
|
src_path_or_class)
|
||
|
if t_src_path is None:
|
||
|
# t_src_path is a java package. It must not in existing_dst_paths.
|
||
|
return True
|
||
|
t_dst_path = OpenjdkFinder.translate_src_path_to_ojluni_path(
|
||
|
t_src_path)
|
||
|
return t_dst_path not in existing_dst_paths
|
||
|
|
||
|
autocomp_result += list(filter(source_not_exists, matches))
|
||
|
|
||
|
print('\n'.join(autocomp_result))
|
||
|
exit(0)
|
||
|
|
||
|
if args.command == 'modify':
|
||
|
dst_class_or_file = args.class_or_ojluni_path[0]
|
||
|
dst_path = OjluniFinder.translate_from_class_name_to_ojluni_path(
|
||
|
dst_class_or_file)
|
||
|
matches = list(filter(lambda e: dst_path == e.dst_path, expected_entries))
|
||
|
if not matches:
|
||
|
error_and_exit(f'{dst_path} is not found in the EXPECTED_UPSTREAM.')
|
||
|
entry: ExpectedUpstreamEntry = matches[0]
|
||
|
str_tag_or_commit = args.tag_or_commit[0]
|
||
|
is_src_given = args.source_file is not None
|
||
|
src_path = args.source_file if is_src_given else entry.src_path
|
||
|
commit = get_commit_or_exit(str_tag_or_commit)
|
||
|
openjdk_finder: OpenjdkFinder = OpenjdkFinder(commit)
|
||
|
if openjdk_finder.has_file(src_path):
|
||
|
pass
|
||
|
elif not is_src_given:
|
||
|
guessed_src_path = openjdk_finder.find_src_path_from_ojluni_path(dst_path)
|
||
|
if guessed_src_path is None:
|
||
|
error_and_exit('[source_file] argument is required.')
|
||
|
src_path = guessed_src_path
|
||
|
else:
|
||
|
error_and_exit(f'{src_path} is not found in the {str_tag_or_commit}')
|
||
|
entry.git_ref = str_tag_or_commit
|
||
|
entry.src_path = src_path
|
||
|
expected_upstream_file.write_all_entries(expected_entries)
|
||
|
print(f'Modified the entry {entry}')
|
||
|
elif args.command == 'add':
|
||
|
class_or_src_path = args.class_or_source_file[0]
|
||
|
str_tag_or_commit = args.tag_or_commit[0]
|
||
|
commit = get_commit_or_exit(str_tag_or_commit)
|
||
|
openjdk_finder = OpenjdkFinder(commit)
|
||
|
src_path = openjdk_finder.find_src_path_from_classname(class_or_src_path)
|
||
|
if src_path is None:
|
||
|
search_paths = openjdk_finder.get_search_paths()
|
||
|
error_and_exit(f'{class_or_src_path} is not found in {commit}. '
|
||
|
f'The search paths are:\n{search_paths}')
|
||
|
ojluni_path = args.ojluni_path
|
||
|
# Guess the source path if it's not given in the argument
|
||
|
if ojluni_path is None:
|
||
|
ojluni_path = OpenjdkFinder.translate_src_path_to_ojluni_path(src_path)
|
||
|
if ojluni_path is None:
|
||
|
error_and_exit('The ojluni destination path is not given.')
|
||
|
|
||
|
matches = list(
|
||
|
filter(lambda e: ojluni_path == e.dst_path, expected_entries))
|
||
|
if matches:
|
||
|
error_and_exit(f"Can't add the file {ojluni_path} because "
|
||
|
f'{class_or_src_path} exists in the EXPECTED_UPSTREAM')
|
||
|
|
||
|
new_entry = ExpectedUpstreamEntry(ojluni_path, str_tag_or_commit, src_path)
|
||
|
expected_upstream_file.write_new_entry(new_entry, expected_entries)
|
||
|
elif args.command == 'sort':
|
||
|
expected_upstream_file.sort_and_write_all_entries(expected_entries)
|
||
|
else:
|
||
|
error_and_exit(f'Unknown subcommand: {args.command}')
|
||
|
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
main(sys.argv[1:])
|