2 Star 0 Fork 0

jeremiazhao/cmake2spec

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
cmake2spec.py 45.17 KB
一键复制 编辑 原始数据 按行查看 历史
jeremiazhao 提交于 2024-12-12 19:32 . fixed some bug when parsing rocm
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206
#!/usr/bin/python3
import typing as T
from pathlib import Path
import os
import urllib.parse
import re
import argparse
import requests
import tarfile
from jinja2 import Template
import dnf
import locale
from datetime import datetime
from scancode import api as scancode_api
'''
cmake2spec is a program that can parse CMakeLists.txt and collect infomation, then it
can generate a rpm spec file.
This program adapt to syntax of Cmake 3.30.
Basic syntax of Cmake: https://cmake.org/cmake/help/latest/manual/cmake-language.7.html
Original program: https://github.com/mesonbuild/meson/blob/master/tools/cmake2meson.py
Author: xiaoji (shouhuanxiaoji@gmail.com)
Adress: Beijing, China
'''
ENABLE_DEBUG = 0
Clists = {
"parsed": [],
"unparsed": []
}
# 定义 CMake 内置变量
def get_cmake_builtin_variables(cmake_file_path: Path) -> dict:
"""
Get Cmake built-in variable
Parameters:
cmake_file_path (Path): path of CMakeLists.txt or module file,
it is a pathlib.Path type value
Return value:
dict: a dict involved name and value of Cmake built-in variable
"""
cmake_source_dir = cmake_file_path.resolve().parent
return {
'CMAKE_SOURCE_DIR': str(cmake_source_dir),
'CMAKE_BINARY_DIR': str(cmake_source_dir),
'CMAKE_CURRENT_SOURCE_DIR': str(cmake_source_dir),
'CMAKE_CURRENT_BINARY_DIR': str(cmake_source_dir),
}
class Token:
"""
表示词法分析器生成的 Token。
属性:
tid (str): Token 的类型标识符。
value (str): Token 的值。
lineno (int): Token 所在的行号。
colno (int): Token 所在的列号。
"""
def __init__(self, tid: str, value: str, lineno: int = 0, colno: int = 0):
self.tid = tid
self.value = value
self.lineno = lineno
self.colno = colno
class Statement:
"""
表示语法分析器生成的语句。
属性:
name (str): 语句的名称(小写)。
args (list): 语句的参数列表。
"""
def __init__(self, name: str, args: list):
self.name = name.lower()
self.args = args
class Lexer:
"""
CMake 词法分析器。
属性:
token_specification (list): 词法规则列表。
"""
def __init__(self) -> None:
self.token_specification = [
# Need to be sorted longest to shortest.
('ignore', re.compile(r'[ \t]')),
# 字符串
('string', re.compile(r'"([^\\]|(\\.))*?"', re.M)),
# 标识符(identifier),包括命名变量、函数、类、模块(if/endif, foreach/endforeach)、类型等程序实体的名称
('id', re.compile('''[,-><&$%\?\[\]{}=+_0-9a-z/A-Z|@.*]+''')),
# 变量
('varexp', re.compile(r'\${[-_0-9a-z/A-Z.]+}')),
# 换行
('eol', re.compile(r'\n')),
# 注释
('comment', re.compile(r'#.*')),
('lparen', re.compile(r'\(')),
('rparen', re.compile(r'\)')),
]
def lex(self, code: str) -> T.Iterator[Token]:
"""
对输入的代码进行词法分析,生成 Token 流。
参数:
code (str): 输入的 CMake 代码。
返回:
Iterator[Token]: 生成的 Token 流。
"""
lineno = 1
line_start = 0
loc = 0
col = 0
lines = code.splitlines()
while loc < len(code):
matched = False
for (tid, reg) in self.token_specification:
mo = reg.match(code, loc)
if mo:
col = mo.start() - line_start
matched = True
loc = mo.end()
match_text = mo.group()
if tid == 'ignore':
continue
if tid == 'comment':
yield(Token('comment', match_text, lineno, col))
elif tid == 'lparen':
yield(Token('lparen', '(', lineno, col))
elif tid == 'rparen':
yield(Token('rparen', ')', lineno, col))
elif tid == 'string':
yield(Token('string', match_text[1:-1], lineno, col))
elif tid == 'id':
yield(Token('id', match_text, lineno, col))
elif tid == 'varexp':
yield(Token('varexp', match_text[2:-1], lineno, col))
elif tid == 'eol':
lineno += 1
col = 1
line_start = mo.end()
else:
raise ValueError(f'lex: unknown element {tid}')
break
if not matched:
error_line = lines[lineno - 1] if lineno <= len(lines) else ''
raise ValueError(f'Lexer got confused at line {lineno}, column {col}.\nLine content: {error_line}')
class Parser:
"""
CMake 语法分析器。
属性:
stream (Iterator[Token]): 词法分析器生成的 Token 流。
current (Token): 当前处理的 Token。
"""
def __init__(self, code: str) -> None:
self.stream = Lexer().lex(code)
self.getsym()
def getsym(self) -> None:
"""
获取下一个 Token。
"""
try:
self.current = next(self.stream)
except StopIteration:
self.current = Token('eof', '')
def accept(self, s: str) -> bool:
"""
检查当前 Token 是否为指定类型,并获取下一个 Token。
参数:
s (str): 期望的 Token 类型。
返回:
bool: 如果当前 Token 类型匹配,返回 True,否则返回 False。
"""
if self.current.tid == s:
self.getsym()
return True
return False
def expect(self, s: str) -> bool:
"""
检查当前 Token 是否为指定类型,并获取下一个 Token。如果不匹配,抛出异常。
参数:
s (str): 期望的 Token 类型。
返回:
bool: 如果当前 Token 类型匹配,返回 True。
异常:
ValueError: 如果当前 Token 类型不匹配,抛出异常。
"""
if self.accept(s):
return True
raise ValueError(f'Expecting {s} got {self.current.tid}. Line: {self.current.lineno}, Column: {self.current.colno}')
def statement(self) -> Statement:
cur = self.current
if self.accept('comment'):
return Statement('_', [cur.value])
self.accept('id')
args = []
depth = 0
while True:
if self.accept('lparen'):
depth += 1
args += self.arguments()
elif self.accept('rparen'):
depth -= 1
if depth == 0:
break
else:
break
return Statement(cur.value, args)
def arguments(self) -> T.List[T.Union[Token, T.Any]]:
args: T.List[T.Union[Token, T.Any]] = []
if self.accept('lparen'):
args.append(self.arguments())
arg = self.current
if self.accept('comment'):
rest = self.arguments()
args += rest
elif self.accept('string') \
or self.accept('varexp') \
or self.accept('id'):
args.append(arg)
rest = self.arguments()
args += rest
return args
def parse(self) -> T.Iterator[Statement]:
"""
解析整个 CMake 代码,生成语句流。
返回:
Iterator[Statement]: 生成的语句流。
"""
while not self.accept('eof'):
yield(self.statement())
def parser_to_dict(t: T.List[Statement]) -> T.Dict[dict, T.Any]:
to_dict = {}
for statement in t:
preincrement = 0
postincrement = 0
indent_unit = ' '
indent_level = 0
if statement.name == '_':
continue
if statement.name == 'message':
# 不需要解析打印信息
continue
elif statement.name == 'cmake_minimum_required':
if not "cmake_minimum_required" in to_dict:
to_dict['cmake_minimum_required'] = statement.args[1].value
if ENABLE_DEBUG:
print("----------------------")
print(statement.name)
for arg in statement.args:
print(arg.value)
elif statement.name == 'file':
# 暂时不解析file
if ENABLE_DEBUG:
print("----------------------")
print(statement.name)
for arg in statement.args:
print(arg.value)
continue
elif statement.name == 'find_file':
# 暂时不解析find_file
if ENABLE_DEBUG:
print("----------------------")
print(statement.name)
for arg in statement.args:
print(arg.value)
continue
elif statement.name == 'get_filename_component':
# get_filename_component在3.20被cmake_path()替代
if ENABLE_DEBUG:
print("----------------------")
print(statement.name)
for arg in statement.args:
print(arg.value)
continue
elif statement.name == 'include':
# 暂时不解析include
if ENABLE_DEBUG:
print("----------------------")
print(statement.name)
for arg in statement.args:
print(arg.value)
continue
elif statement.name == 'checkincludefiles':
# 暂不支持
if ENABLE_DEBUG:
print("----------------------")
print(statement.name)
for arg in statement.args:
print(arg.value)
continue
elif statement.name == 'check_symbol_exists':
# 暂不支持
if ENABLE_DEBUG:
print("----------------------")
print(statement.name)
for arg in statement.args:
print(arg.value)
continue
elif statement.name == 'add_subdirectory':
if not "add_subdirectory" in to_dict:
to_dict["add_subdirectory"] = []
to_dict["add_subdirectory"].append(statement.args[0].value)
if ENABLE_DEBUG:
print("----------------------")
print(statement.name)
print(statement.args[0].value)
elif statement.name == 'pkg_search_module' or statement.name == 'pkg_search_modules':
continue
varname = statement.args[0].value.lower()
mods = ["dependency('%s')" % i.value for i in statement.args[1:]]
if ENABLE_DEBUG:
print("----------------------")
print(statement.name)
for arg in statement.args:
print(arg.value)
elif statement.name == 'find_package':
if not "find_package" in to_dict:
to_dict["find_package"] = {}
if not statement.args[0].value in to_dict["find_package"]:
# 其他参数暂时还不支持,如果有需要可继续添加
to_dict["find_package"][statement.args[0].value] = {
"version": "",
"components": [],
"required": False
}
if len(statement.args) > 1:
find_package_args_list = []
for arg in statement.args:
find_package_args_list.append(arg.value)
# match the version, if it exist, it will be the second parameter
pattern_version = r'\d'
match_version = re.search(pattern_version, find_package_args_list[1])
if match_version:
to_dict["find_package"][statement.args[0].value]["version"] = find_package_args_list[1]
if "REQUIRED" in find_package_args_list:
to_dict["find_package"][statement.args[0].value]["required"] = True
if "COMPONENTS" in find_package_args_list:
index_components = find_package_args_list.index("COMPONENTS")
# 有可能出现COMPONENTS之后组件为空的情况
if not index_components == (len(find_package_args_list) -1):
to_dict["find_package"][statement.args[0].value]["components"] = find_package_args_list[index_components+1:]
if ENABLE_DEBUG:
print("----------------------")
print(statement.name)
for arg in statement.args:
print(arg.value)
elif statement.name == 'add_dependencies':
# 解析该字段暂时没有用
if ENABLE_DEBUG:
print("----------------------")
print(statement.name)
for arg in statement.args:
print(arg.value)
continue
elif statement.name == 'find_program':
if not "find_program" in to_dict:
to_dict["find_program"] = []
list(to_dict["find_program"]).append(statement.args[1].value)
if ENABLE_DEBUG:
print("----------------------")
print(statement.name)
for arg in statement.args:
print(arg.value)
elif statement.name == 'find_library':
if ENABLE_DEBUG:
print("----------------------")
print(statement.name)
for arg in statement.args:
print(arg.value)
continue
elif statement.name == 'add_executable':
if ENABLE_DEBUG:
print("----------------------")
print(statement.name)
for arg in statement.args:
print(arg.value)
continue
elif statement.name == 'add_library':
if statement.args[1].value == 'SHARED':
libcmd = 'shared_library'
args = [statement.args[0]] + statement.args[2:]
elif statement.args[1].value == 'STATIC':
libcmd = 'static_library'
args = [statement.args[0]] + statement.args[2:]
else:
libcmd = 'library'
args = statement.args
if ENABLE_DEBUG:
print("----------------------")
print(statement.name)
for arg in statement.args:
print(arg.value)
print(libcmd)
elif statement.name == 'add_test':
# 暂时不支持test解析
if ENABLE_DEBUG:
print("----------------------")
print(statement.name)
for arg in statement.args:
print(arg.value)
continue
elif statement.name == 'option':
# 不解析编译选项参数
if ENABLE_DEBUG:
print("----------------------")
print(statement.name)
for arg in statement.args:
print(arg.value)
continue
elif statement.name == 'target_compile_options':
# 不解析编译选项参数
if ENABLE_DEBUG:
print("----------------------")
print(statement.name)
for arg in statement.args:
print(arg.value)
continue
elif statement.name == 'project':
if not "project" in to_dict:
to_dict["find_program"] = {
"name": "",
"version": "",
"description": "",
"homepage_url": "",
"languages": []
}
if len(statement.args) == 1:
to_dict["find_program"]["name"] = statement.args[0].value
if len(statement.args) > 1:
to_dict["find_program"]["name"] = statement.args[0].value
# 简易模式
if (not statement.args[1].value == "VERSION") \
and (not statement.args[1].value == "DESCRIPTION") \
and (not statement.args[1].value == "HOMEPAGE_URL") \
and (not statement.args[1].value == "LANGUAGES"):
lang_list = statement.args[1:]
for lang in lang_list:
to_dict["find_program"]["languages"].append(lang.value)
# 详细模式
else:
project_token_arg_value_list = []
for arg in statement.args:
project_token_arg_value_list.append(arg.value)
if "LANGUAGES" in project_token_arg_value_list:
index_lang = project_token_arg_value_list.index("LANGUAGES")
to_dict["languages"] = project_token_arg_value_list[index_lang+1:]
if "VERSION" in project_token_arg_value_list:
index_version = project_token_arg_value_list.index("VERSION")
to_dict["version"] = project_token_arg_value_list[index_version+1]
if "DESCRIPTION" in project_token_arg_value_list:
index_description = project_token_arg_value_list.index("DESCRIPTION")
to_dict["version"] = project_token_arg_value_list[index_description+1]
if "HOMEPAGE_URL" in project_token_arg_value_list:
index_homepage = project_token_arg_value_list.index("HOMEPAGE_URL")
to_dict["version"] = project_token_arg_value_list[index_homepage+1]
if ENABLE_DEBUG:
print("----------------------")
print(statement.name)
for arg in statement.args:
print(arg.value)
elif statement.name == 'set':
# 如果set语句只有变量名一个参数,没有值,则表示unset动作
if not "vars" in to_dict:
to_dict["vars"] = {}
if len(statement.args) == 1:
to_dict["vars"][statement.args[0].value] = None
if len(statement.args) == 2:
if not ";" in statement.args[1].value:
to_dict["vars"][statement.args[0].value] = ""
to_dict["vars"][statement.args[0].value] = statement.args[1].value
else:
to_dict["vars"][statement.args[0].value] = []
vars_list = list(statement.args[1].value.split(";"))
to_dict["vars"][statement.args[0].value] = vars_list
if len(statement.args) > 2:
to_dict["vars"][statement.args[0].value] = []
var_args = statement.args[1:]
for arg in var_args:
to_dict["vars"][statement.args[0].value].append(arg.value)
if ENABLE_DEBUG:
print("vars set ----------------------")
print(statement.name)
for arg in statement.args:
print(arg.value)
elif statement.name == 'unset':
if not "vars" in to_dict:
to_dict["vars"] = {}
if len(statement.args) == 1:
to_dict["vars"][statement.name] = None
if ENABLE_DEBUG:
print("vars unset----------------------")
print(statement.name)
for arg in statement.args:
print(arg.value)
elif statement.name == 'list':
# 列表操作语句,暂时不支持
if ENABLE_DEBUG:
print("----------------------")
print(statement.name)
for arg in statement.args:
print(arg.value)
continue
elif statement.name == 'if':
# 暂时不支持if解析
if ENABLE_DEBUG:
postincrement = 1
try:
print("----------------------")
print(statement.name)
for arg in statement.args:
if type(arg) == "Token":
print(arg.value)
else:
print(arg)
except AttributeError: # complex if statements
line = statement.name
for arg in statement.args:
line += parser_to_dict(arg)
print("----------------------")
print(statement.name)
for arg in statement.args:
print(arg.value)
continue
elif statement.name == 'elseif':
# 暂时不支持if解析
if ENABLE_DEBUG:
preincrement = -1
postincrement = 1
try:
print("----------------------")
print(statement.name)
for arg in statement.args:
print(arg.value)
except AttributeError: # complex if statements
line = statement.name
for arg in statement.args:
line += parser_to_dict(arg)
print("----------------------")
print(statement.name)
for arg in statement.args:
print(arg.value)
continue
elif statement.name == 'else':
# 暂时不支持if解析
if ENABLE_DEBUG:
preincrement = -1
postincrement = 1
print("----------------------")
print(statement.name)
for arg in statement.args:
print(arg.value)
continue
elif statement.name == 'endif':
# 暂时不支持if解析
if ENABLE_DEBUG:
preincrement = -1
line = 'endif'
print("----------------------")
print(statement.name)
for arg in statement.args:
print(arg.value)
continue
elif statement.name == 'foreach':
# 暂时不支持foreach解析
if ENABLE_DEBUG:
print("----------------------")
print(statement.name)
for arg in statement.args:
print(arg.value)
continue
elif statement.name == 'endforeach':
# 暂时不支持foreach解析
if ENABLE_DEBUG:
print("----------------------")
print(statement.name)
for arg in statement.args:
print(arg.value)
continue
else:
# 其他语句,未来逐一解决
if ENABLE_DEBUG:
print("other vars and macros----------------------")
print(statement.name)
for arg in statement.args:
print(arg.value)
continue
indent_level += preincrement
indent_level += postincrement
return to_dict
def multipath(path):
# 尝试解析路径为 URL
url_type = "unknown"
url_result = urllib.parse.urlparse(path)
if all([url_result.scheme, url_result.netloc]):
url_type = "url"
elif os.path.isdir(path):
url_type = "dir"
elif os.path.isfile(path):
url_type = "file"
else:
url_type = "unkown"
return url_type
def download_file(url, outdir, max_trys=3) -> dict:
"""
下载文件并尝试从 Content-Disposition 获取文件名。
使用 Content-Length 验证文件完整性。
检查本地文件是否存在且完整,如果是,则不下载。
:param url: 文件的 URL 地址
:param outdir: 文件下载到的本地目录
:return: 布尔值,表示文件是否下载成功或已存在且完整
"""
file_info = {
"save_path": "",
"filename": ""
}
trys = 0
while trys < max_trys:
try:
# 发起请求
response = requests.get(url, stream=True)
# 尝试从 Content-Disposition 获取文件名
content_disposition = response.headers.get('Content-Disposition')
if content_disposition:
# 解析出文件名
file_info["filename"] = content_disposition.split('filename=')[-1].strip('"')
else:
# 从 URL 中提取文件名
file_info["filename"] = os.path.basename(url)
# 获取 Content-Length
content_length = response.headers.get('Content-Length')
if content_length:
content_length = int(content_length)
else:
# 无法获取 Content-Length,无法进行完整性验证,每次必须重新下载
content_length = 0
# 完整文件路径
file_info["save_path"] = os.path.join(outdir, file_info["filename"])
# 检查本地文件是否存在且完整
if os.path.exists(file_info["save_path"]):
if os.path.getsize(file_info["save_path"]) == content_length:
print(f"本地文件 {file_info['save_path']} 已存在且完整,无需下载。")
return file_info
# 下载
with open(file_info["save_path"], 'wb') as f:
# 使用迭代器逐块读取数据
for chunk in response.iter_content(chunk_size=8192):
if chunk: # 过滤掉keep-alive的新块
f.write(chunk)
return file_info
except Exception as e:
print(f"下载程序发生错误:{e}")
file_info["save_path"] = "error"
file_info["filename"] = "error"
return file_info
# 检测path参数的类型和内容,并进行处理
# 如果是url,则下载和解压,获取地址和目录名
# 如果是file,则解压,获取目录名
# 如果是dir,则直接获取目录名
def get_workpath(original_path) -> dict:
path_dict = {
"url": "",
"dir_path": "",
"type": multipath(original_path),
"name": "",
"version": ""
}
# url,先下载tarball,再解压tarball
if path_dict["type"] == "url":
down_path = "./"
down_abs_path = os.path.abspath(down_path)
tarball_path_dict = download_file(original_path, down_abs_path)
url_filename = tarball_path_dict["filename"]
path_dict["url"] = os.path.join(os.path.dirname(original_path), url_filename)
tarball_path = tarball_path_dict["save_path"]
path_dict["name"] = parse_tarball(os.path.basename(tarball_path))["name"]
path_dict["version"] = parse_tarball(os.path.basename(tarball_path))["version"]
if tarball_path != "error":
with tarfile.open(tarball_path, 'r') as tar:
# 遍历 tarball 中的所有成员
tar_level_one_member = []
for member in tar.getmembers():
if '/' not in member.name.lstrip('/'):
tar_level_one_member.append(member)
if len(tar_level_one_member) == 1 and tar_level_one_member[0].isdir():
tar.extractall(path=down_abs_path)
path_dict["dir_path"] = os.path.join(down_abs_path, tar_level_one_member[0].name)
else:
print("不是独立的一级目录,暂未实现")
tar.close()
# tarball,先检测是否存在已解压目录,再解压
elif path_dict["type"] == "file":
tarball_path = os.path.abspath(original_path)
path_dict["url"] = os.path.basename(tarball_path)
path_dict["name"] = parse_tarball(os.path.basename(tarball_path))["name"]
path_dict["version"] = parse_tarball(os.path.basename(tarball_path))["version"]
with tarfile.open(tarball_path, 'r') as tar:
# 遍历 tarball 中的所有成员
tar_level_one_member = []
for member in tar.getmembers():
if '/' not in member.name.lstrip('/'):
tar_level_one_member.append(member)
if len(tar_level_one_member) == 1 and tar_level_one_member[0].isdir():
tar.extractall(path=os.path.dirname(tarball_path))
path_dict["dir_path"] = os.path.join(os.path.dirname(tarball_path), tar_level_one_member[0].name)
else:
print("不是独立的一级目录,暂未实现")
tar.close()
# 指向一个目录
elif path_dict["type"] == "dir":
path_dict["dir_path"] = original_path
else:
print(original_path + "is a error path type")
path_dict["dir_path"] = ""
return path_dict
def get_workpath_nonex(original_path) -> dict:
path_dict = {
"url": "",
"dir_path": "",
"type": multipath(original_path),
"name": "",
"version": ""
}
# url,先下载tarball,再解压tarball
if path_dict["type"] == "url":
path_dict["url"] = original_path
down_path = "./"
down_abs_path = os.path.abspath(down_path)
tarball_path = download_file(original_path, down_abs_path)["save_path"]
path_dict["name"] = parse_tarball(os.path.basename(tarball_path))["name"]
path_dict["version"] = parse_tarball(os.path.basename(tarball_path))["version"]
if tarball_path != "error":
with tarfile.open(tarball_path, 'r') as tar:
# 遍历 tarball 中的所有成员
tar_level_one_member = []
for member in tar.getmembers():
if '/' not in member.name.lstrip('/'):
tar_level_one_member.append(member)
if len(tar_level_one_member) == 1 and tar_level_one_member[0].isdir():
#tar.extractall(path=down_abs_path)
path_dict["dir_path"] = os.path.join(down_abs_path, tar_level_one_member[0].name)
else:
print("不是独立的一级目录,暂未实现")
tar.close()
# tarball,先检测是否存在已解压目录,再解压
elif path_dict["type"] == "file":
tarball_path = os.path.abspath(original_path)
path_dict["name"] = parse_tarball(os.path.basename(tarball_path))["name"]
path_dict["version"] = parse_tarball(os.path.basename(tarball_path))["version"]
with tarfile.open(tarball_path, 'r') as tar:
# 遍历 tarball 中的所有成员
tar_level_one_member = []
for member in tar.getmembers():
if '/' not in member.name.lstrip('/'):
tar_level_one_member.append(member)
if len(tar_level_one_member) == 1 and tar_level_one_member[0].isdir():
#tar.extractall(path=os.path.dirname(tarball_path))
path_dict["dir_path"] = os.path.join(os.path.dirname(tarball_path), tar_level_one_member[0].name)
else:
print("不是独立的一级目录,暂未实现")
tar.close()
# 指向一个目录
elif path_dict["type"] == "dir":
path_dict["dir_path"] = original_path
else:
print(original_path + "is a error path type")
path_dict["dir_path"] = ""
return path_dict
# 从tarball中解析包名和版本号(autoconf和make构建系统需要,cmake和meson可能不需要
def parse_tarball(path):
# is_wrap tarball顶级目录内,是一个目录,还是所有文件
tarball_info = {
"name": "",
"version": "",
}
filename = os.path.basename(path)
name_pattern = re.compile(r'^([^-_]+)') # 匹配直到第一个下划线或短横线之前的所有字符作为软件名
name_match = name_pattern.match(filename)
if name_match:
software_name = name_match.group(1)
tarball_info["name"] = software_name
version_pattern =re.compile(r'([0-9]+(?:[._-][0-9]+)*)') # 匹配*.*这种版本号
version_match = version_pattern.search(filename)
if version_match:
software_version = version_match.group(0)
software_version = software_version.replace("-", ".").replace("_", ".")
tarball_info["version"] = software_version
return tarball_info
def remove_comments(statements: T.List[Statement]) -> T.List[Statement]:
"""
删除所有注释语句。
参数:
statements (List[Statement]): 包含注释的语句列表。
返回:
List[Statement]: 删除注释后的语句列表。
"""
return [stmt for stmt in statements if stmt.name != '_']
def replace_builtin_variables(statements: T.List[Statement], builtin_variables: dict) -> T.List[Statement]:
"""
替换所有 CMake 内置变量。
参数:
statements (List[Statement]): 包含变量的语句列表。
builtin_variables (dict): 包含 CMake 内置变量的字典。
返回:
List[Statement]: 替换变量后的语句列表。
"""
for stmt in statements:
for i, arg in enumerate(stmt.args):
if isinstance(arg, Token) and arg.tid == 'varexp':
if arg.value in builtin_variables:
stmt.args[i] = Token('string', builtin_variables[arg.value])
return statements
def replace_custom_variables(statements: T.List[Statement]) -> T.List[Statement]:
"""
替换所有开发者自定义的变量。
参数:
statements (List[Statement]): 包含变量的语句列表。
返回:
List[Statement]: 替换变量后的语句列表。
"""
if ENABLE_DEBUG:
print("execute function replace_custom_variables")
parsed_to_dict = parser_to_dict(statements)
if not "vars" in parsed_to_dict:
parsed_to_dict["vars"] = {}
custom_variables = parsed_to_dict["vars"]
for stmt in statements:
for i, arg in enumerate(stmt.args):
if isinstance(arg, Token) and arg.tid == 'varexp':
if arg.value in custom_variables:
stmt.args[i] = Token('string', custom_variables[arg.value])
return statements
def expand_subdirectory_files(statements: T.List[Statement], base_dir: Path) -> T.List[Statement]:
"""
展开所有 add_subdirectory 语句,递归处理被引入的文件。
参数:
statements (List[Statement]): 包含 add_subdirectory 语句的语句列表。
base_dir (Path): 当前 CMakeLists.txt 文件的目录路径。
返回:
List[Statement]: 展开 add_subdirectory 语句后的语句列表。
"""
expanded_statements = statements.copy()
parsed_to_dict = parser_to_dict(statements)
if "add_subdirectory" in parsed_to_dict:
subdirs = parsed_to_dict["add_subdirectory"]
for subdir in subdirs:
file_path = base_dir / subdir / "CMakeLists.txt"
if str(file_path.resolve()) not in Clists["unparsed"]:
Clists["unparsed"].append(str(file_path.resolve()))
if file_path.exists() and (not file_path.resolve() in Clists["parsed"]):
if file_path.is_file(): # 检查路径是否为文件
dir_path = base_dir / subdir
dir_path = dir_path.resolve()
if not file_path.resolve() in Clists["parsed"]:
include_statements = prebuild(dir_path)
expanded_statements.extend(include_statements)
parsed_to_dict["add_subdirectory"].remove(subdir)
else:
print(f"Warning: {file_path} is not a file.") # 增加调试信息
else:
print(f"Warning: {file_path} does not exist.") # 增加调试信息
return expanded_statements
return statements
# 查询许可证
def scan_license(dirpath):
max_depth = 0
license_inte_list = []
for root, _, files in os.walk(dirpath):
current_depth = os.path.relpath(root, dirpath).count(os.sep)
if max_depth < current_depth:
continue
for file in files:
file_path = os.path.join(root, file)
print("parsing license: " + file_path)
file_results = scancode_api.get_licenses(file_path)
license_spdx = file_results["detected_license_expression_spdx"]
if license_spdx != "null" and license_spdx != None:
license_inte_list.append(license_spdx)
if len(license_inte_list) == 0:
return "Unkown"
license_list = []
license_inte_list = list(set(license_inte_list))
for license_inte in license_inte_list:
license_inte_split = license_inte.split(" AND ")
for license in license_inte_split:
license = license.replace("(", "")
license = license.replace(")", "")
if license not in license_list:
license_list.append(license)
license_string = " AND ".join(license_list)
return license_string
# 通过pkg-conf查
# 通过cmake查
# 通过/usr/lib(64)/libxxx.so查
def find_package_by_name(name):
# 创建一个 DNF Base 对象
base = dnf.Base()
# 初始化 DNF Base 对象
base.read_all_repos()
# 填充sack,准备查询
base.fill_sack()
# 使用提供的文件路径查询包
query = base.sack.query()
filepath = ""
# pkg-conf查rpm包
filepath = "/lib64/pkgconfig/" + name + ".pc"
items = query.filter(file=filepath)
if items:
print(items[0].name + " way: " + "pkg-conf")
return items[0].name
filepath = "/usr/lib64/pkgconfig/" + name + ".pc"
items = query.filter(file=filepath)
if items:
print(items[0].name + " way: " + "pkg-conf")
return items[0].name
filepath = "/usr/share/pkgconfig/" + name + ".pc"
items = query.filter(file=filepath)
if items:
print(items[0].name + " way: " + "pkg-conf")
return items[0].name
# cmake查
filepath = "/usr/share/cmake/Modules/Find" + name + ".cmake"
items = query.filter(file=filepath)
if items:
print(items[0].name + " way: " + "cmake")
return items[0].name
filepath = "/usr/share/cmake/Modules/" + name + "Config.cmake"
items = query.filter(file=filepath)
if items:
print(items[0].name + " way: " + "cmake")
return items[0].name
filepath = "/usr/share/cmake/Modules/" + name + "/" + "Find" + name + ".cmake"
items = query.filter(file=filepath)
if items:
print(items[0].name + " way: " + "cmake")
return items[0].name
filepath = "/usr/share/cmake/Modules/" + name + "/" + name + "Config.cmake"
items = query.filter(file=filepath)
if items:
print(items[0].name + " way: " + "cmake")
return items[0].name
filepath = "/usr/lib64/cmake/" + name + "/" + "Find" + name + ".cmake"
items = query.filter(file=filepath)
if items:
print(items[0].name + " way: " + "cmake")
return items[0].name
filepath = "/usr/lib64/cmake/" + name + "/" + name + "Config.cmake"
items = query.filter(file=filepath)
if items:
print(items[0].name + " way: " + "cmake")
return items[0].name
# 通过/usr/lib(64)/libxxx.so查
filepath = "/usr/lib64/lib" + name + ".so"
items = query.filter(file=filepath)
if items:
print(items[0].name + " way: " + "filepath")
return items[0].name
filepath = "/usr/lib/lib" + name + ".so"
items = query.filter(file=filepath)
if items:
print(items[0].name + " way: " + "filepath")
return items[0].name
filepath = "/lib/lib" + name + ".so"
items = query.filter(file=filepath)
if items:
print(items[0].name + " way: " + "filepath")
return items[0].name
filepath = "/lib64/lib" + name + ".so"
items = query.filter(file=filepath)
if items:
print(items[0].name + " way: " + "filepath")
return items[0].name
# 没查到,原样返回
return name
def prebuild(cmake_dir_path: str) -> T.List[Statement]:
"""
解析 CMakeLists.txt 文件,生成包含解析结果的字典
参数:
cmake_source (str): CMakeLists.txt 源码
返回:
Dict[str, Any]: 包含解析结果的字典
todo:
支持module.cmake文件解析
"""
dir_path = Path(cmake_dir_path)
file_path = dir_path / 'CMakeLists.txt'
cmake_source = None
with open(file_path.resolve(), 'r') as cmakefile:
cmake_source = cmakefile.read()
print("\033[34mParsing file: " + str(dir_path.resolve()) + "/CMakeLists.txt\033[0m")
parser = Parser(cmake_source)
if not str(file_path.resolve()) in Clists["parsed"]:
Clists["parsed"].append(str(file_path.resolve()))
Clists["unparsed"] = [item for item in Clists["unparsed"] if item not in Clists["parsed"]]
statements = list(parser.parse())
# 1. 删除注释
statements = remove_comments(statements)
# 2. 获取 CMake 内置变量
builtin_variables = get_cmake_builtin_variables(file_path)
# 3. 替换 CMake 内置变量
statements = replace_builtin_variables(statements, builtin_variables)
# 4. 替换开发者自定义的变量
statements = replace_custom_variables(statements)
# 5. 展开 add_subdirectory
if "add_subdirectory" in parser_to_dict(statements):
statements = expand_subdirectory_files(statements, file_path.parent)
cmakefile.close()
return statements
def all_dict(cmake_dir_path: str) -> T.Dict[str, T.Any]:
to_dict = parser_to_dict(prebuild(cmake_dir_path))
return to_dict
def parsed_dict_vars(codes, dict):
vars_value_list={}
# 获取${...}变量
pattern = r'\$\{[^}]+\}'
if ENABLE_DEBUG:
print(f"the regex code is {codes}")
vars_list = re.findall(pattern, codes)
if len(vars_list) == 0:
return codes
for var in vars_list:
if var[2:-1] in dict["vars"]:
vars_value_list[var] = dict["vars"][var[2:-1]]
else:
vars_value_list[var] = ""
result = re.sub(pattern, lambda match: vars_value_list.get(match.group(0), match.group(0)), codes)
return result
def generate_spec(dict, path):
workpath_dict = get_workpath(path)
workpath = os.path.basename(workpath_dict["dir_path"])
source_url = workpath_dict["url"]
www_url = ""
if workpath_dict["type"] == "url":
parsed_url = urllib.parse.urlparse(source_url)
url_parts = parsed_url.path.split('/')
url_base_path = '/'.join(url_parts[0:3])
www_url = f"{parsed_url.scheme}://{parsed_url.netloc}{url_base_path}"
builddep = []
license = scan_license(workpath)
project_name = parsed_dict_vars(dict["find_program"]["name"], dict)
try:
project_name = parsed_dict_vars(dict["find_program"]["name"], dict)
except KeyError:
project_name = workpath_dict["name"]
try:
project_version = parsed_dict_vars(dict["version"], dict)
except KeyError:
project_version = workpath_dict["version"]
try:
builddep_dict = dict["find_package"]
except KeyError:
builddep_dict = []
if len(builddep_dict) > 0:
for dep in builddep_dict.keys():
depname = find_package_by_name(dep)
depversion = builddep_dict[dep]["version"]
if depversion == "":
builddep.append(depname)
else:
builddep.append(depname + ">=" + depversion)
cmake_min_version = "cmake"
if "cmake_minimum_required" in dict:
if not dict["cmake_minimum_required"] == "":
cmake_min_version = "cmake >= " + dict["cmake_minimum_required"]
builddep.append(cmake_min_version)
language_to_builddep = {
'C': 'gcc',
'CXX': 'gcc-c++'
}
if "languages" in dict:
for lang in dict["languages"]:
if not language_to_builddep[lang] in dict:
builddep.append(language_to_builddep[lang])
locale.setlocale(locale.LC_TIME, 'en_US.UTF-8')
now = datetime.now()
date_format = "%a %b %d %Y"
formatted_date = now.strftime(date_format)
script_path = os.path.realpath(__file__)
script_directory = os.path.dirname(script_path)
specpath = script_directory + "/template.spec"
f = open(specpath, "r", encoding= "utf-8")
template = Template(f.read())
result = template.render(
summary = "A useful software named " + project_name,
name = project_name,
license = license,
version = project_version,
url = www_url,
downloadurl = source_url,
cmake = cmake_min_version,
buildrequires = ', '.join(builddep),
requires = "glibc",
appsourcedir = workpath,
date = formatted_date
)
return result, project_name
if __name__ == '__main__':
p = argparse.ArgumentParser(description='Parse CMake file.')
p.add_argument('path', type=str, help='dir, tarball path, or url')
P = p.parse_args()
workdir = get_workpath(P.path)
cmake_dict = all_dict(workdir["dir_path"])
print(cmake_dict)
spec,project_name = generate_spec(cmake_dict, P.path)
print(f"gererating spec file {project_name}.spec ...")
file_path = project_name + '.spec'
with open(file_path, 'w') as file:
# 写入内容
file.write(spec)
file.close()
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/shouhuanxiaoji/cmake2spec.git
git@gitee.com:shouhuanxiaoji/cmake2spec.git
shouhuanxiaoji
cmake2spec
cmake2spec
master

搜索帮助