1# Copyright (C) 2022 The Android Open Source Project 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15import os 16from typing import Any, BinaryIO, Callable, Dict, List, Optional, Tuple 17 18# Limit parsing file to 1MB to maintain parity with the UI 19MAX_BYTES_LOADED = 1 * 1024 * 1024 20 21 22def file_generator(path: str): 23 with open(path, 'rb') as f: 24 yield from read_generator(f) 25 26 27def read_generator(trace: BinaryIO): 28 while True: 29 chunk = trace.read(MAX_BYTES_LOADED) 30 if not chunk: 31 break 32 yield chunk 33 34 35def merge_dicts(a: Dict[str, str], b: Dict[str, str]): 36 return {**a, **b} 37 38 39def parse_trace_uri(uri: str) -> Tuple[Optional[str], str]: 40 # This is definitely a path and not a URI 41 if uri.startswith('/') or uri.startswith('.'): 42 return None, uri 43 44 # If there's no colon, it cannot be a URI 45 idx = uri.find(':') 46 if idx == -1: 47 return None, uri 48 49 # If there is only a single character before the colon 50 # this is likely a Windows path; throw an error on other platforms 51 # to prevent single character names causing issues on Windows. 52 if idx == 1: 53 if os.name != 'nt': 54 raise Exception('Single character resolvers are not allowed') 55 return None, uri 56 57 return (uri[:idx], uri[idx + 1:]) 58 59 60def to_list(cs: Any) -> Optional[List[Any]]: 61 """Converts input into list if it is not already a list. 62 63 For resolvers that can accept list types it may happen the user inputs just 64 a single value, to make the code generic enough we would want to convert those 65 input into list of single element. It does not do anything for None or List 66 types. 67 """ 68 if cs is None or isinstance(cs, list): 69 return cs 70 return [cs] 71 72 73def _cs_list(cs: List[Any], fn: Callable[[Any], str], empty_default: str, 74 condition_sep: str) -> str: 75 """Converts list of constraints into list of clauses. 76 77 Applies function `fn` over each element in list `cs` and joins the list of 78 transformed strings with join string `condition_sep`. `empty_default` string 79 is returned incase cs is a list of length 0. 'TRUE' is returned when cs is 80 None 81 82 e.g. 83 Input: 84 cs: ['Android', 'Linux'] 85 fn: "platform = '{}'".format 86 empty_default: FALSE 87 condition_sep: 'OR' 88 OUTPUT: 89 "(platform = 'Android' OR platform = 'Linux')" 90 """ 91 if cs is None: 92 return 'TRUE' 93 if not cs: 94 return empty_default 95 return f'({condition_sep.join([fn(c) for c in cs])})' 96 97 98def and_list(cs: List[Any], fn: Callable[[Any], str], 99 empty_default: str) -> str: 100 """Converts list of constraints into list of AND clauses. 101 102 Function `fn` is applied over each element of list `cs` and joins the list of 103 transformed strings with ' AND ' string. `empty_default` string 104 is returned incase cs is a list of length 0. 'TRUE' is returned when cs is 105 None. 106 107 e.g. 108 Input: 109 cs: ['Android', 'Linux'] 110 fn: "platform != '{}'".format 111 empty_default: FALSE 112 OUTPUT: 113 "(platform != 'Android' AND platform != 'Linux')" 114 """ 115 return _cs_list(cs, fn, empty_default, ' AND ') 116 117 118def or_list(cs: List[Any], fn: Callable[[Any], str], empty_default: str) -> str: 119 """Converts list of constraints into list of OR clauses. 120 121 Similar to and_list method, just the join string is ' OR ' instead of ' AND '. 122 123 e.g. 124 Input: 125 cs: ['Android', 'Linux'] 126 fn: "platform = '{}'".format 127 empty_default: FALSE 128 OUTPUT: 129 "(platform = 'Android' OR platform = 'Linux')" 130 """ 131 return _cs_list(cs, fn, empty_default, ' OR ') 132