From 8fc809d03920e8e1215ea4b1e2e079fd9f6ac546 Mon Sep 17 00:00:00 2001 From: Melody Horn Date: Thu, 25 Mar 2021 18:56:31 -0600 Subject: start using mypy in strict mode --- yapymake/makefile/__init__.py | 92 ++++++++++++++++++++++++------------------- 1 file changed, 52 insertions(+), 40 deletions(-) (limited to 'yapymake/makefile/__init__.py') diff --git a/yapymake/makefile/__init__.py b/yapymake/makefile/__init__.py index 7a2adfe..70b61d8 100644 --- a/yapymake/makefile/__init__.py +++ b/yapymake/makefile/__init__.py @@ -32,7 +32,8 @@ class Makefile: if args.builtin_rules: self._inference_rules += BUILTIN_INFERENCE_RULES - self._macros.update(BUILTIN_MACROS) + for k, v in BUILTIN_MACROS.items(): + self._macros[k] = (MacroSource.Builtin, TokenString.text(v)) for target in BUILTIN_TARGETS: self._targets[target.name] = target @@ -47,7 +48,7 @@ class Makefile: # TODO either discern command line vs MAKEFLAGS or don't pretend we can self._macros[name] = (MacroSource.CommandLine, TokenString.text(value)) - def read(self, file: TextIO): + def read(self, file: TextIO) -> None: lines_iter: PeekableIterator[str] = PeekableIterator(iter(file)) for line in lines_iter: # handle escaped newlines (POSIX says these are different in command lines (which we handle later) and @@ -101,21 +102,23 @@ class Makefile: if line_type == 'rule': # > Target entries are specified by a -separated, non-null list of targets, then a , then # > a -separated, possibly empty list of prerequisites. - targets, after_colon = line_tokens.split_once(':') - targets = self.expand_macros(targets).split() + colon_split = line_tokens.split_once(':') + assert colon_split is not None + targets_tokens, after_colon = colon_split + targets = self.expand_macros(targets_tokens).split() # > Text following a , if any, and all following lines that begin with a , are makefile # > command lines to be executed to update the target. semicolon_split = after_colon.split_once(';') if semicolon_split is None: prerequisites = self.expand_macros(after_colon).split() - commands = [] + command_token_strings = [] else: - prerequisites, commands = semicolon_split - prerequisites = self.expand_macros(prerequisites).split() - commands = [commands] + prerequisite_tokens, command_tokens = semicolon_split + prerequisites = self.expand_macros(prerequisite_tokens).split() + command_token_strings = [command_tokens] while lines_iter.peek().startswith('\t'): - commands.append(tokenize(next(lines_iter).lstrip('\t'))) - commands = [CommandLine(c) for c in commands] + command_token_strings.append(tokenize(next(lines_iter).lstrip('\t'))) + commands = [CommandLine(c) for c in command_token_strings] # we don't know yet if it's a target rule or an inference rule match = re.fullmatch(r'(?P(\.[^/.]+)?)(?P\.[^/.]+)', targets[0]) @@ -139,17 +142,19 @@ class Makefile: elif line_type == 'macro': # > The macro named string1 is defined as having the value of string2, where string2 is defined as all # > characters, if any, after the ... - name, value = line_tokens.split_once('=') + equals_split = line_tokens.split_once('=') + assert equals_split is not None + name_tokens, value = equals_split # > up to a comment character ( '#' ) or an unescaped . comment_split = value.split_once('#') if comment_split is not None: value, _ = comment_split # > Any characters immediately before or after the shall be ignored. - name.rstrip() + name_tokens.rstrip() value.lstrip() # > Macros in the string before the in a macro definition shall be evaluated when the # > macro assignment is made. - name = self.expand_macros(name) + name = self.expand_macros(name_tokens) # > Macros defined in the makefile(s) shall override macro definitions that occur before them in the # > makefile(s) and macro definitions from source 4. If the -e option is not specified, macros defined # > in the makefile(s) shall override macro definitions from source 3. Macros defined in the makefile(s) @@ -172,47 +177,50 @@ class Makefile: internal_macro = len(macro_name) in [1, 2] and macro_name[0] in '@?<*' and \ macro_name[1:] in ['', 'D', 'F'] if internal_macro: + assert current_target is not None if macro_name[0] == '@': # > The $@ shall evaluate to the full target name of the current target, or the archive filename # > part of a library archive target. It shall be evaluated for both target and inference rules. - macro_value = [current_target.name] + macro_pieces = [current_target.name] elif macro_name[0] == '?': # > The $? macro shall evaluate to the list of prerequisites that are newer than the current # > target. It shall be evaluated for both target and inference rules. - macro_value = [p for p in current_target.prerequisites if self.target(p).newer_than(current_target)] + macro_pieces = [p for p in current_target.prerequisites if self.target(p).newer_than(current_target)] elif macro_name[0] == '<': # > In an inference rule, the $< macro shall evaluate to the filename whose existence allowed # > the inference rule to be chosen for the target. In the .DEFAULT rule, the $< macro shall # > evaluate to the current target name. - macro_value = current_target.prerequisites + macro_pieces = current_target.prerequisites elif macro_name[0] == '*': # > The $* macro shall evaluate to the current target name with its suffix deleted. - macro_value = [str(PurePath(current_target.name).with_suffix(''))] + macro_pieces = [str(PurePath(current_target.name).with_suffix(''))] else: # this shouldn't happen - macro_value = [] + macro_pieces = [] if macro_name[1:] == 'D': - macro_value = [str(PurePath(x).parent) for x in macro_value] + macro_pieces = [str(PurePath(x).parent) for x in macro_pieces] elif macro_name[1:] == 'F': - macro_value = [str(PurePath(x).name) for x in macro_value] + macro_pieces = [str(PurePath(x).name) for x in macro_pieces] - macro_value = TokenString.text(' '.join(macro_value)) + macro_tokens = TokenString.text(' '.join(macro_pieces)) else: - _, macro_value = self._macros[this_token.name] - macro_value = self.expand_macros(macro_value, current_target) + _, macro_tokens = self._macros[this_token.name] + macro_value = self.expand_macros(macro_tokens, current_target) if this_token.replacement is not None: replaced, replacement = (self.expand_macros(t, current_target) for t in this_token.replacement) macro_value = re.sub(re.escape(replaced) + r'\b', replacement, macro_value) return macro_value + else: + raise TypeError('unexpected token type!') return ''.join(expand_one(t) for t in text) def special_target(self, name: str) -> Optional['Target']: return self._targets.get(name, None) - def special_target_has_prereq(self, target: str, name: str) -> bool: - target = self.special_target(target) + def special_target_has_prereq(self, target_name: str, name: str) -> bool: + target = self.special_target(target_name) if target is None: return False return len(target.prerequisites) == 0 or name in target.prerequisites @@ -269,6 +277,8 @@ class Target: path = self._path() if path.exists(): return path.stat().st_mtime + else: + return None def newer_than(self, other: 'Target') -> Optional[bool]: self_mtime = self.modified_time() @@ -279,6 +289,8 @@ class Target: return True elif other_mtime is None and other.already_updated and other.name in self.prerequisites: return False + else: + return None def is_up_to_date(self, file: Makefile) -> bool: if self.already_updated: @@ -287,14 +299,14 @@ class Target: newer_than_all_dependencies = all(self.newer_than(file.target(other)) for other in self.prerequisites) return exists and newer_than_all_dependencies - def update(self, file: Makefile): + def update(self, file: Makefile) -> None: for prerequisite in self.prerequisites: file.target(prerequisite).update(file) if not self.is_up_to_date(file): self.execute_commands(file) self.already_updated = True - def execute_commands(self, file: Makefile): + def execute_commands(self, file: Makefile) -> None: for command in self.commands: command.execute(file, self) @@ -333,7 +345,7 @@ class CommandLine: first_token.text = first_token.text[1:] self.execution_line = TokenString(list((first_token, *tokens_iter))) - def execute(self, file: Makefile, current_target: 'Target'): + def execute(self, file: Makefile, current_target: 'Target') -> None: # POSIX: # > If the command prefix contains a , or the -i option is present, or the special target .IGNORE # > has either the current target as a prerequisite or has no prerequisites, any error found while executing @@ -415,18 +427,18 @@ BUILTIN_INFERENCE_RULES = [ ]), ] BUILTIN_MACROS = { - 'MAKE': TokenString.text('make'), - 'AR': TokenString.text('ar'), - 'ARFLAGS': TokenString.text('-rv'), - 'YACC': TokenString.text('yacc'), - 'YFLAGS': TokenString.text(''), - 'LEX': TokenString.text('lex'), - 'LFLAGS': TokenString.text(''), - 'LDFLAGS': TokenString.text(''), - 'CC': TokenString.text('c99'), - 'CFLAGS': TokenString.text('-O 1'), - 'FC': TokenString.text('fort77'), - 'FFLAGS': TokenString.text('-O 1'), + 'MAKE': 'make', + 'AR': 'ar', + 'ARFLAGS': '-rv', + 'YACC': 'yacc', + 'YFLAGS': '', + 'LEX': 'lex', + 'LFLAGS': '', + 'LDFLAGS': '', + 'CC': 'c99', + 'CFLAGS': '-O 1', + 'FC': 'fort77', + 'FFLAGS': '-O 1', } BUILTIN_TARGETS = [ Target('.SUFFIXES', ['.o', '.c', '.y', '.l', '.a', '.sh', '.f'], []), -- cgit v1.2.3