aboutsummaryrefslogtreecommitdiff
path: root/yapymake/makefile/__init__.py
diff options
context:
space:
mode:
Diffstat (limited to 'yapymake/makefile/__init__.py')
-rw-r--r--yapymake/makefile/__init__.py92
1 files changed, 52 insertions, 40 deletions
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 <blank>-separated, non-null list of targets, then a <colon>, then
# > a <blank>-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 <semicolon>, if any, and all following lines that begin with a <tab>, 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<s1>(\.[^/.]+)?)(?P<s2>\.[^/.]+)', 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 <equals-sign>...
- 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 <newline>.
comment_split = value.split_once('#')
if comment_split is not None:
value, _ = comment_split
# > Any <blank> characters immediately before or after the <equals-sign> shall be ignored.
- name.rstrip()
+ name_tokens.rstrip()
value.lstrip()
# > Macros in the string before the <equals-sign> 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 <hyphen-minus>, 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'], []),