Changeset View
Changeset View
Standalone View
Standalone View
cmake/utils/gen-ninja-deps.py
#!/usr/bin/env python3 | #!/usr/bin/env python3 | ||||
import argparse | import argparse | ||||
import os | import os | ||||
import subprocess | import subprocess | ||||
parser = argparse.ArgumentParser(description='Produce a dep file from ninja.') | parser = argparse.ArgumentParser(description="Produce a dep file from ninja.") | ||||
parser.add_argument("--build-dir", help="The build directory.", required=True) | |||||
parser.add_argument( | parser.add_argument( | ||||
'--build-dir', | "--base-dir", | ||||
help='The build directory.', | help="The directory for which dependencies are rewriten.", | ||||
required=True) | required=True, | ||||
) | |||||
parser.add_argument("--ninja", help="The ninja executable to use.") | |||||
parser.add_argument("base_target", help="The target from the base's perspective.") | |||||
parser.add_argument( | parser.add_argument( | ||||
'--base-dir', | "targets", nargs="+", help="The target for which dependencies are extracted." | ||||
help='The directory for which dependencies are rewriten.', | ) | ||||
required=True) | parser.add_argument("--extra-deps", nargs="+", help="Extra dependencies.") | ||||
parser.add_argument('--ninja', help='The ninja executable to use.') | |||||
parser.add_argument( | |||||
'base_target', | |||||
help="The target from the base's perspective.") | |||||
parser.add_argument( | |||||
'targets', nargs='+', | |||||
help='The target for which dependencies are extracted.') | |||||
parser.add_argument( | |||||
'--extra-deps', nargs='+', | |||||
help='Extra dependencies.') | |||||
args = parser.parse_args() | args = parser.parse_args() | ||||
build_dir = os.path.abspath(args.build_dir) | build_dir = os.path.abspath(args.build_dir) | ||||
base_dir = os.path.abspath(args.base_dir) | base_dir = os.path.abspath(args.base_dir) | ||||
ninja = args.ninja | ninja = args.ninja | ||||
base_target = args.base_target | base_target = args.base_target | ||||
targets = args.targets | targets = args.targets | ||||
extra_deps = args.extra_deps | extra_deps = args.extra_deps | ||||
# Make sure we operate in the right folder. | # Make sure we operate in the right folder. | ||||
os.chdir(build_dir) | os.chdir(build_dir) | ||||
if ninja is None: | if ninja is None: | ||||
ninja = subprocess.check_output(['command', '-v', 'ninja'])[:-1] | ninja = subprocess.check_output(["command", "-v", "ninja"])[:-1] | ||||
# Construct the set of all targets | # Construct the set of all targets | ||||
all_targets = set() | all_targets = set() | ||||
doto_targets = set() | doto_targets = set() | ||||
for t in subprocess.check_output([ninja, '-t', 'targets', 'all']).splitlines(): | for t in subprocess.check_output([ninja, "-t", "targets", "all"]).splitlines(): | ||||
t, r = t.split(b':') | t, r = t.split(b":") | ||||
all_targets.add(t) | all_targets.add(t) | ||||
if r[:13] == b' C_COMPILER__' or r[:15] == b' CXX_COMPILER__': | if r[:13] == b" C_COMPILER__" or r[:15] == b" CXX_COMPILER__": | ||||
doto_targets.add(t) | doto_targets.add(t) | ||||
def parse_ninja_query(query): | def parse_ninja_query(query): | ||||
deps = {} | deps = {} | ||||
lines = query.splitlines() | lines = query.splitlines() | ||||
while len(lines): | while len(lines): | ||||
line = lines.pop(0) | line = lines.pop(0) | ||||
if line[0] == ord(' '): | if line[0] == ord(" "): | ||||
continue | continue | ||||
# We have a new target | # We have a new target | ||||
target = line.split(b':')[0] | target = line.split(b":")[0] | ||||
assert lines.pop(0)[:8] == b' input:' | assert lines.pop(0)[:8] == b" input:" | ||||
inputs = set() | inputs = set() | ||||
while True: | while True: | ||||
i = lines.pop(0) | i = lines.pop(0) | ||||
if i[:4] != b' ': | if i[:4] != b" ": | ||||
break | break | ||||
''' | """ | ||||
ninja has 3 types of input: | ninja has 3 types of input: | ||||
1. Explicit dependencies, no prefix; | 1. Explicit dependencies, no prefix; | ||||
2. Implicit dependencies, | prefix. | 2. Implicit dependencies, | prefix. | ||||
3. Order only dependencies, || prefix. | 3. Order only dependencies, || prefix. | ||||
Order only dependency do not require the target to be rebuilt | Order only dependency do not require the target to be rebuilt | ||||
and so we ignore them. | and so we ignore them. | ||||
''' | """ | ||||
i = i[4:] | i = i[4:] | ||||
if i[0] == ord('|'): | if i[0] == ord("|"): | ||||
if i[1] == ord('|'): | if i[1] == ord("|"): | ||||
# We reached the order only dependencies. | # We reached the order only dependencies. | ||||
break | break | ||||
i = i[2:] | i = i[2:] | ||||
inputs.add(i) | inputs.add(i) | ||||
deps[target] = inputs | deps[target] = inputs | ||||
return deps | return deps | ||||
def extract_deps(workset): | def extract_deps(workset): | ||||
# Recursively extract the dependencies of the target. | # Recursively extract the dependencies of the target. | ||||
deps = {} | deps = {} | ||||
while len(workset) > 0: | while len(workset) > 0: | ||||
query = subprocess.check_output([ninja, '-t', 'query'] + list(workset)) | query = subprocess.check_output([ninja, "-t", "query"] + list(workset)) | ||||
target_deps = parse_ninja_query(query) | target_deps = parse_ninja_query(query) | ||||
deps.update(target_deps) | deps.update(target_deps) | ||||
workset = set() | workset = set() | ||||
for d in target_deps.values(): | for d in target_deps.values(): | ||||
workset.update(t for t in d if t in all_targets and t not in deps) | workset.update(t for t in d if t in all_targets and t not in deps) | ||||
# Extract build time dependencies. | # Extract build time dependencies. | ||||
bt_targets = [t for t in deps if t in doto_targets] | bt_targets = [t for t in deps if t in doto_targets] | ||||
if len(bt_targets) == 0: | if len(bt_targets) == 0: | ||||
return deps | return deps | ||||
ndeps = subprocess.check_output( | ndeps = subprocess.check_output( | ||||
[ninja, '-t', 'deps'] + bt_targets, | [ninja, "-t", "deps"] + bt_targets, stderr=subprocess.DEVNULL | ||||
stderr=subprocess.DEVNULL) | ) | ||||
lines = ndeps.splitlines() | lines = ndeps.splitlines() | ||||
while len(lines) > 0: | while len(lines) > 0: | ||||
line = lines.pop(0) | line = lines.pop(0) | ||||
t, m = line.split(b':') | t, m = line.split(b":") | ||||
if m == b' deps not found': | if m == b" deps not found": | ||||
continue | continue | ||||
inputs = set() | inputs = set() | ||||
while True: | while True: | ||||
i = lines.pop(0) | i = lines.pop(0) | ||||
if i == b'': | if i == b"": | ||||
break | break | ||||
assert i[:4] == b' ' | assert i[:4] == b" " | ||||
inputs.add(i[4:]) | inputs.add(i[4:]) | ||||
deps[t] = inputs | deps[t] = inputs | ||||
return deps | return deps | ||||
base_dir = base_dir.encode() | base_dir = base_dir.encode() | ||||
def rebase_deps(deps): | def rebase_deps(deps): | ||||
rebased = {} | rebased = {} | ||||
cache = {} | cache = {} | ||||
def rebase(path): | def rebase(path): | ||||
if path in cache: | if path in cache: | ||||
return cache[path] | return cache[path] | ||||
abspath = os.path.abspath(path) | abspath = os.path.abspath(path) | ||||
newpath = path if path == abspath else os.path.relpath( | newpath = path if path == abspath else os.path.relpath(abspath, base_dir) | ||||
abspath, base_dir) | |||||
cache[path] = newpath | cache[path] = newpath | ||||
return newpath | return newpath | ||||
for t, s in deps.items(): | for t, s in deps.items(): | ||||
rebased[rebase(t)] = {rebase(d) for d in s} | rebased[rebase(t)] = {rebase(d) for d in s} | ||||
return rebased | return rebased | ||||
Show All 25 Lines |