1
0
mirror of https://github.com/Rogiel/CMakeDependency synced 2025-12-06 05:22:48 +00:00
Files
CMakeDependency/DependencyGenerator.py

375 lines
15 KiB
Python

import json, sys
from urllib.parse import urlparse
tab = " " * 4
nl_indent = "\n" + tab * 1
def target_relative_path(path, name=None, src_dir=None, bin_dir=None):
if src_dir is None:
src_dir = "${" + name + "_SOURCE_DIR}"
if bin_dir is None:
bin_dir = "${CMAKE_CURRENT_BINARY_DIR}/" + name
if path.startswith('/'):
return '${PROJECT_SOURCE_DIR}' + path
elif path.startswith(':'):
return bin_dir + '/' + path[1:]
elif path.startswith('${'):
return path
else:
if path == '.' or path == '':
return src_dir
else:
return src_dir + '/' + path
def generate_dependency_decl(name, keys):
max_key_length = 0
for (key, value) in keys.items():
max_key_length = max(max_key_length, len(key))
def align(s, n):
return ' ' * n + s
s = "import_dependency(" + name + nl_indent + (nl_indent).join(
map(lambda k: k[0] + " " + align(k[1], max_key_length - len(k[0]) + 3), keys.items())) + ")"
return s
def to_cmake_datatype(value):
def escape_str(s):
if ' ' in s:
return '"' + s.replace('"', '\\"') + '"'
return s
if isinstance(value, str):
return escape_str(value)
elif isinstance(value, bool):
return 'TRUE' if value else 'FALSE'
else:
return escape_str(str(value))
def gen_cmake_call(call, entries, glue=None, where=None):
if glue is None:
glue = ''
if where is None:
where = target_name
if isinstance(where, list):
where = " ".join(where)
discriminator = None
if isinstance(call, tuple):
discriminator = call[1]
call = call[0]
if len(entries) != 0:
print(call + "(" + (discriminator + " " if discriminator is not None else "") + where + (glue + " " if glue is not None else "") + nl_indent +
nl_indent.join(entries)
+ ")")
def generate_target_definition(name, target_name, target_base_name, public_decl_type, src_dir, target_info, headers):
def gen_entry(decl, values, normalizer=None):
if values is None or len(values) == 0:
return []
if normalizer is None:
normalizer = lambda x: x
if isinstance(values, str):
values = [values]
if isinstance(values, dict):
values = values.items()
return list(map(lambda x: (decl + " " if decl is not None else "") + normalizer(x), values))
def gen_cmake_target_attrs(call, attr_name, default_scope=public_decl_type, normalizer=None, as_system=False):
values = []
values += gen_entry(default_scope, target_info.get(attr_name), normalizer=normalizer)
values += gen_entry(public_decl_type, target_info.get("public_" + attr_name), normalizer=normalizer)
values += gen_entry('PRIVATE', target_info.get("private_" + attr_name), normalizer=normalizer)
values += gen_entry('INTERFACE', target_info.get("interface_" + attr_name), normalizer=normalizer)
gen_cmake_call(call, values, glue=' SYSTEM' if as_system else '')
def gen_cmake_target_props(call, attr_name, glue=None):
if not attr_name in target_info:
return
values = list(map(lambda x: x[0] + ' ' + to_cmake_datatype(x[1]), target_info[attr_name].items()))
gen_cmake_call(call, values, glue=glue)
def cmake_define_kv(item):
if isinstance(item, str):
return item
if item[1] is None:
return item[0]
return item[0] + '=' + to_cmake_datatype(item[1])
# gen_cmake_target_attrs('target_sources', 'srcs', suffix=src_dir, default_scope='PRIVATE')
gen_cmake_target_attrs('target_include_directories', 'includes',
normalizer=lambda x: '$<BUILD_INTERFACE:' + target_relative_path(x, name) + '>',
as_system=True)
gen_cmake_target_attrs('target_compile_definitions', 'defines', normalizer=cmake_define_kv)
gen_cmake_target_attrs('target_compile_options', 'options')
gen_cmake_target_attrs('target_compile_features', 'features')
gen_cmake_target_attrs('target_link_libraries', 'links')
gen_cmake_target_attrs('target_link_directories', 'link_directories')
gen_cmake_target_attrs('target_link_options', 'link_options')
gen_cmake_target_attrs('target_precompile_headers', 'precompile_headers')
gen_cmake_target_props('set_target_properties', 'properties', ' PROPERTIES')
gen_cmake_call("set_target_properties", [
"EXPORT_NAME " + target_base_name,
], glue=" PROPERTIES")
if "unity" in target_info and target_info["unity"]:
gen_cmake_call("set_target_properties", [
"UNITY_BUILD ON",
"UNITY_BUILD_MODE GROUP"
], glue=" PROPERTIES")
gen_cmake_call("set_source_files_properties", [
"UNITY_GROUP " + target_name
], glue=" PROPERTIES", where=srcs)
gen_cmake_call(('install', 'TARGETS'), [
'EXPORT ' + name + 'Targets',
'RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}',
'ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}',
'INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}',
'LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}',
])
# gen_cmake_call(('export', 'TARGETS'), [
# 'APPEND FILE ${PROJECT_BINARY_DIR}/'+name+'-target.cmake',
# 'NAMESPACE ' + name
# ], where=target_name)
if 'install' in target_info:
install_info = target_info['install']
def gen_install_dir(key, type):
if key in install_info:
for (dir, patterns) in install_info[key].items():
if patterns is None or patterns == True or len(patterns) == 0:
patterns = ['*.h', '*.hpp', '*.inl', '*.hxx', '*.hh']
gen_cmake_call(('install', 'DIRECTORY'), [
'TYPE ' + type,
'FILES_MATCHING'
] + list(map(lambda x: 'PATTERN '+x, patterns)), where=target_relative_path(dir, name))
gen_install_dir('includes', 'INCLUDE')
# else:
# values = gen_entry(None, target_info.get('includes'), normalizer=lambda x: target_relative_path(x, name) + '/')
# gen_cmake_call(('install', 'DIRECTORY'), [
# 'TYPE INCLUDE',
# 'FILES_MATCHING',
# 'PATTERN *.h',
# 'PATTERN *.hpp'
# ], where=values[0])
if headers is not None:
gen_cmake_call(('install', 'FILES'), [
'TYPE INCLUDE'
], where=headers)
# gen_cmake_call(call, values, glue=' SYSTEM' if as_system else '')
# print("install(TARGETS " + target_name + '''
# RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
# ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
# INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
# LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
# ''')
#
# if 'includes' in target_info:
# for include in target_info['includes']:
# print("install(DIRECTORY " + target_relative_path(include, name) + " DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}\
# FILES_MATCHING PATTERN *.h)")
def enumerate_srcs(name, target_name, srcs, suffix='_SRCS'):
if srcs is None:
return None
if isinstance(srcs, str):
srcs = [srcs]
target_name = target_name.replace('.', '_')
globs = list(filter(lambda x: '*' in x, srcs))
paths = list(filter(lambda x: not '*' in x, srcs))
paths = list(map(lambda x: target_relative_path(x, name), paths))
if len(globs) != 0:
print("file(GLOB_RECURSE " + target_name + suffix + " CONFIGURE_DEPENDS " + (nl_indent).join(
map(lambda x: target_relative_path(x, name), globs)) + ")")
paths = ["${" + target_name + suffix + "}"] + paths
return (nl_indent).join(paths)
j = json.load(sys.stdin)
for (name, dep_info) in j.items():
sys.stderr.write('Updating dependency definitions for ' + name + '\n')
src_dir = "${" + name + "_SOURCE_DIR}"
dep_args = {}
if "git" in dep_info:
git_info = dep_info["git"]
git_repo = git_info["repository"]
git_tag = git_info["tag"]
git_url_parsed = urlparse(git_repo)
if git_url_parsed.hostname == 'github.com':
parts = git_url_parsed.path.split("/")
user = parts[1]
repo = parts[2]
repo = repo[:-4] if repo.endswith('.git') else repo
dep_args["URL"] = "https://github.com/" + user + "/" + repo + "/archive/" + git_tag + ".zip"
dep_args["DOWNLOAD_NAME"] = repo + "-" + git_tag + ".zip"
else:
print('git is only supported for github repositories at the moment.', file=sys.stderr)
exit(-1)
elif "url" in dep_info:
dep_args["URL"] = " ".join(dep_info["url"])
if 'download_name' in dep_info:
dep_args["DOWNLOAD_NAME"] = dep_info['download_name']
print("# -- Dependency: " + name)
print(generate_dependency_decl(name, dep_args))
if 'include' in dep_info:
print("include(" + target_relative_path(dep_info['include'], name) + ")")
if 'configure' in dep_info:
configures = dep_info['configure']
for (conf_name, configure) in configures.items():
configure_type = 'configure_file'
if 'type' in configure:
configure_type = configure['type']
if configure_type == 'configure':
print('configure_file('
+ nl_indent + to_cmake_datatype(target_relative_path(configure['input'], name)) + ' '
+ nl_indent + to_cmake_datatype(target_relative_path(conf_name, name)) + ')')
elif configure_type == 'generate':
print('file(GENERATE'
+ (nl_indent + 'OUTPUT ' + to_cmake_datatype(target_relative_path(conf_name, name)))
+ (nl_indent + 'CONTENT ' + to_cmake_datatype(
configure['content']) if 'content' in configure else '')
+ (nl_indent + 'INPUT ' + to_cmake_datatype(
(target_relative_path(configure['input'], name))) if 'input' in configure else '')
+ (nl_indent + 'CONDITION ' + configure['condition'] if 'condition' in configure else '')
+ ')')
else:
raise RuntimeError("Unsupported configure type!")
if len(configures) != 0:
print()
target_infos = dep_info["target"]
if isinstance(target_infos, dict):
target_infos = [target_infos]
for target_n, target_info in enumerate(target_infos):
public_decl_type = "PUBLIC"
target_name = target_base_name = name
if 'name' in target_info:
target_base_name = target_info['name']
target_name = name + '.' + target_base_name
target_type = target_info["type"]
if target_type == "static":
srcs = enumerate_srcs(name, target_name, srcs=target_info["srcs"])
print("add_library(" + target_name + " STATIC " + nl_indent + srcs + ")")
if target_type == "shared":
srcs = enumerate_srcs(name, target_name, srcs=target_info["srcs"])
print("add_library(" + target_name + " SHARED " + nl_indent + srcs + ")")
if target_type == "object":
srcs = enumerate_srcs(name, target_name, srcs=target_info["srcs"])
print("add_library(" + target_name + " OBJECT" + nl_indent + srcs + ")")
elif target_type == "interface":
print("add_library(" + target_name + " INTERFACE)")
public_decl_type = "INTERFACE"
elif target_type == "imported":
print("add_library(" + target_name + " UNKNOWN IMPORTED)")
public_decl_type = "INTERFACE"
elif target_type == "subdirectory":
if "cache" in target_info:
for (var_name, value) in target_info["cache"].items():
if isinstance(value, bool):
print("set(" + var_name + " " + ("ON" if value else "OFF") + " CACHE INTERNAL \"\" FORCE)")
else:
print("set(" + var_name + " " + value + " CACHE INTERNAL \"\" FORCE)")
project_name = target_info['project_name'] if "project_name" in target_info else None
if project_name is not None:
if "include" in target_info:
print("set(CMAKE_PROJECT_" + project_name + "_INCLUDE " + to_cmake_datatype(
target_relative_path(target_info['include'], name)) + ")")
if "include_before" in target_info:
print("set(CMAKE_PROJECT_" + project_name + "_INCLUDE_BEFORE " + to_cmake_datatype(
target_relative_path(target_info['include_before'], name)) + ")")
print("add_subdirectory(${" + target_name + "_SOURCE_DIR} ${" + target_name + "_BINARY_DIR})")
elif target_type == "cmake":
print("include(${PROJECT_SOURCE_DIR}/" + target_info["file"] + ")")
elif target_type == "external":
print("include(ExternalProject)")
print("ExternalProject_Add(" + target_name)
print(nl_indent + "SOURCE_DIR " + src_dir)
for name, value in target_info["options"].items():
print(nl_indent + name + " " + to_cmake_datatype(value))
print(")")
if target_type in ["static", "interface", "object"]:
headers = enumerate_srcs(name, target_name, srcs=target_info.get("headers"), suffix='_HEADERS')
generate_target_definition(
name, target_name, target_base_name,
public_decl_type=public_decl_type,
target_info=target_info,
src_dir=src_dir,
headers=headers)
gen_cmake_call(('export', 'PACKAGE'), [], where=name)
gen_cmake_call(('install', 'EXPORT'), [
'FILE ' + name + 'Config.cmake',
'NAMESPACE ' + name + '::',
'DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/' + name,
], where=name + 'Targets')
if target_n != len(target_infos) - 1:
print()
done_nl = False
def do_nl():
global done_nl
if not done_nl:
print()
done_nl = True
if "aliases" in dep_info:
do_nl()
for (tgt, src) in dep_info["aliases"].items():
print("add_library(" + tgt + " ALIAS " + src + ")")
if "extra_cmake" in dep_info:
do_nl()
extra_cmakes = dep_info["extra_cmake"]
if isinstance(extra_cmakes, str):
extra_cmakes = [extra_cmakes]
for extra_cmake in extra_cmakes:
extra_cmake = target_relative_path(extra_cmake, name)
print("include(" + extra_cmake + ")")
print()