667 lines
29 KiB
Python
667 lines
29 KiB
Python
#!/usr/bin/python3
|
||
import subprocess
|
||
import sys
|
||
import argparse
|
||
import os
|
||
import json
|
||
import platform
|
||
import re
|
||
import shutil
|
||
from datetime import datetime
|
||
|
||
|
||
def get_git_revision_hash(path=None) -> str:
|
||
if path:
|
||
# git -C <path> <command>
|
||
# Runs the command as if git was started in <path> instead of the current working directory.
|
||
# return subprocess.check_output(['git', '-C', path, 'rev-parse', 'HEAD']).decode('ascii').strip()
|
||
stdout = subprocess.run(['git', '-C', path, 'rev-parse', 'HEAD'],
|
||
check=True, capture_output=True, text=True).stdout.strip()
|
||
return stdout
|
||
else:
|
||
# return subprocess.check_output(['git', 'rev-parse', 'HEAD']).decode('ascii').strip()
|
||
stdout = subprocess.run(['git', 'rev-parse', 'HEAD'],
|
||
check=True, capture_output=True, text=True).stdout.strip()
|
||
return stdout
|
||
|
||
|
||
class DependencyType:
|
||
local = 'local'
|
||
git = 'git'
|
||
|
||
|
||
class Module:
|
||
# raw - это прочитанный json в виде словаря
|
||
def __init__(self, raw=None):
|
||
self.cmake = {'inc': [], 'src': [], 'inc_dirs': [], 'srcs': [], 'libs': [], 'uis': []}
|
||
self.dep = [] # список из объектов Dependency
|
||
self.raw = raw
|
||
|
||
if raw:
|
||
if 'cmake' in raw:
|
||
if 'inc_dirs' in raw['cmake']:
|
||
self.cmake['inc_dirs'] = raw['cmake']['inc_dirs']
|
||
if 'srcs' in raw['cmake']:
|
||
self.cmake['srcs'] = raw['cmake']['srcs']
|
||
if 'libs' in raw['cmake']:
|
||
self.cmake['libs'] = raw['cmake']['libs']
|
||
if 'uis' in raw['cmake']:
|
||
self.cmake['uis'] = raw['cmake']['uis']
|
||
if 'dep' in raw:
|
||
self.dep = [Dependency(rawDep) for rawDep in raw['dep']]
|
||
|
||
def __str__(self):
|
||
return str(self.cmake)
|
||
|
||
def __repr__(self):
|
||
return self.__str__()
|
||
|
||
def __eq__(self, other):
|
||
if not isinstance(other, type(self)):
|
||
return False
|
||
return self.cmake == other.cmake and self.dep == other.dep
|
||
|
||
|
||
class Dependency:
|
||
# raw - это словарь
|
||
def __init__(self, raw):
|
||
if raw:
|
||
if raw['type'] == DependencyType.local:
|
||
self.type = DependencyType.local
|
||
self.dir = raw.get('dir', '')
|
||
self.os = raw.get('os', '')
|
||
elif raw['type'] == DependencyType.git:
|
||
self.type = DependencyType.git
|
||
self.provider = raw.get('provider', '')
|
||
self.dir = raw.get('dir', '')
|
||
self.repo = raw.get('repo', '')
|
||
self.commit = raw.get('commit', '')
|
||
self.os = raw.get('os', '')
|
||
self.project = raw.get('project', 'false')
|
||
else:
|
||
raise ValueError('Unknown dependency ' + str(raw['type']))
|
||
|
||
def __eq__(self, other):
|
||
if self.type != other.type:
|
||
return False
|
||
|
||
if self.type == DependencyType.local:
|
||
return self.dir == other.dir
|
||
elif self.type == DependencyType.git:
|
||
return self.repo == other.repo
|
||
else:
|
||
raise ValueError('Unknown dependency' + str(self.type))
|
||
|
||
def __str__(self):
|
||
if self.type == DependencyType.local:
|
||
return 'local ' + str(self.dir)
|
||
elif self.type == DependencyType.git:
|
||
return 'git ' + str(self.repo)
|
||
else:
|
||
raise ValueError('Unknown dependency' + str(self.type))
|
||
|
||
|
||
class ModularProject:
|
||
# получить домашнюю директорию и приклеить к ней путь к modular_builder/config.json
|
||
userConfigFile = os.path.join(os.path.expanduser('~'), '.modular_builder', 'config.json')
|
||
|
||
def __init__(self):
|
||
self.clear()
|
||
self.initDir()
|
||
self.loadConfigs()
|
||
|
||
def clear(self):
|
||
self.loadedModules = []
|
||
self.rules = {}
|
||
|
||
def initDir(self):
|
||
if not os.path.exists('MODULES'):
|
||
print('No MODULES folder, creating...')
|
||
os.mkdir('MODULES')
|
||
print('Done')
|
||
|
||
def loadConfigs(self):
|
||
if os.path.exists(self.userConfigFile):
|
||
print('Has config file at ' + str(self.userConfigFile) + ', loading...')
|
||
self.loadConfig(self.userConfigFile)
|
||
print('Done')
|
||
else:
|
||
print('No config file at ' + str(self.userConfigFile))
|
||
|
||
# в config.json лежит примерно это:
|
||
# {
|
||
# "git_providers:" {
|
||
# "rospron_modules": "http"//git...blabla.../rosprom_modules"
|
||
# }
|
||
# }
|
||
def loadConfig(self, config_path):
|
||
config = json.load(open(config_path, encoding='utf-8'))
|
||
self.gitProviders = config['git_providers']
|
||
|
||
def loadJsonAtPath(self, path): # path - путь до modular.json
|
||
print('loading json', path)
|
||
try:
|
||
with open(path, encoding='utf-8') as f:
|
||
result = Module(json.load(f))
|
||
return result
|
||
except json.JSONDecodeError:
|
||
print('Error while reading json file at path:', path, file=sys.stderr)
|
||
print('Try fixing JSON file and run modular.py again', file=sys.stderr)
|
||
raise json.JSONDecodeError
|
||
|
||
def downloadableModulePath(self, dependency):
|
||
if dependency.type == DependencyType.git:
|
||
if dependency.project == 'true':
|
||
return dependency.repo
|
||
else:
|
||
return os.path.join('MODULES', dependency.repo)
|
||
raise ValueError('UnknownDependency')
|
||
|
||
def isGitModuleCloned(self, dependency):
|
||
return os.path.exists(self.downloadableModulePath(dependency))
|
||
|
||
def isModuleSystemAppropriate(self, dependency):
|
||
if not dependency.os: # если ОС не указана для модуля, то грузим его всегда
|
||
return True
|
||
elif dependency.os.lower() == 'windows':
|
||
print('Module only for Windows')
|
||
return platform.system().lower() == 'windows'
|
||
elif dependency.os.lower() == 'linux':
|
||
print('Module only for Linux')
|
||
return platform.system().lower() == 'linux'
|
||
else:
|
||
print('Unknown OS:', dependency.os)
|
||
print('Dependency will be uploaded anyway')
|
||
return True
|
||
|
||
def cloneGitDependency(self, dependency):
|
||
path = self.downloadableModulePath(dependency)
|
||
clone_cmd = 'git clone ' + str(self.gitProviders[dependency.provider]) + '/' + str(dependency.repo) + ' ' + str(
|
||
path)
|
||
print('Clone with', clone_cmd)
|
||
os.system(clone_cmd) # выполняет любые поддерживаемые команды в командной строке
|
||
|
||
if dependency.commit:
|
||
checkout_cmd = f'git -C {path} checkout {dependency.commit}'
|
||
# git -C <path> <command>
|
||
# Runs the command as if git was started in <path> instead of the current working directory.
|
||
print(f'Checkout with {checkout_cmd}')
|
||
os.system(checkout_cmd) # выполняет любые поддерживаемые команды в командной строке
|
||
|
||
def loadDependency(self, dependency):
|
||
if self.checkDependencyCollision(dependency):
|
||
return []
|
||
print('\nLoading module', dependency)
|
||
if dependency.type == DependencyType.local:
|
||
return self.loadDependencyFromLocal(dependency)
|
||
elif dependency.type == DependencyType.git:
|
||
return self.loadDependencyFromGit(dependency)
|
||
else:
|
||
raise ValueError('Unknown module')
|
||
|
||
def loadDependencyFromLocal(self, dependency):
|
||
if self.isModuleSystemAppropriate(dependency):
|
||
return self.loadDependencyFromDir(dependency.dir)
|
||
|
||
def loadDependencyFromGit(self, dependency):
|
||
if self.isModuleSystemAppropriate(dependency):
|
||
if not self.isGitModuleCloned(dependency):
|
||
self.cloneGitDependency(dependency)
|
||
return self.loadDependencyFromDir(self.downloadableModulePath(dependency))
|
||
|
||
def loadDependencyFromDir(self, modular_dir):
|
||
modular_json_path = os.path.join(modular_dir, 'modular.json')
|
||
|
||
if not os.path.exists(modular_json_path):
|
||
print('Directory in not a module:', modular_dir)
|
||
return []
|
||
|
||
# module_relative - объект класса Module, внутри него в списке dep хранятся прочитанные из json объекты Dependency
|
||
module_relative = self.loadJsonAtPath(modular_json_path)
|
||
project_relative = Module()
|
||
|
||
print('module_relative:', module_relative)
|
||
print(module_relative.cmake)
|
||
|
||
result_inc_dirs = self.addRootToPaths(modular_dir, module_relative.cmake.get('inc_dirs', ''))
|
||
project_relative.cmake['inc_dirs'] = result_inc_dirs
|
||
result_srcs = self.addRootToPaths(modular_dir, module_relative.cmake.get('srcs', ''))
|
||
project_relative.cmake['srcs'] = result_srcs
|
||
result_libs = self.addRootToPaths(modular_dir, module_relative.cmake.get('libs', ''))
|
||
project_relative.cmake['libs'] = result_libs
|
||
result_uis = self.addRootToPaths(modular_dir, module_relative.cmake.get('uis', ''))
|
||
project_relative.cmake['uis'] = result_uis
|
||
|
||
# После выполнения строки ниже в project_relative.dep окажется просто
|
||
# список Dependency, который и был в module_relative.dep.
|
||
# (Это список модулей, от которых текущий модуль зависит. Их надо будет докачать)
|
||
project_relative.dep = self.addRootToDeps(modular_dir, module_relative.dep)
|
||
|
||
sub_modules = []
|
||
for dependency in project_relative.dep:
|
||
loaded_dependencies = self.loadDependency(dependency)
|
||
if loaded_dependencies:
|
||
# sub_modules.extend(loaded_dependencies)
|
||
# чтобы не было повторений
|
||
for dep_loaded in loaded_dependencies:
|
||
if dep_loaded not in sub_modules:
|
||
sub_modules.append(dep_loaded)
|
||
else:
|
||
print('Nothing to download, continuing')
|
||
|
||
sub_modules.append(project_relative)
|
||
print('sub_modules', sub_modules)
|
||
return sub_modules
|
||
|
||
def addRootToPaths(self, root, paths):
|
||
if not paths:
|
||
return []
|
||
result = []
|
||
for pth in paths:
|
||
if os.path.isabs(pth): # если путь абсолютный
|
||
result.append(pth)
|
||
else:
|
||
if pth.startswith('./'): # если текущий путь начинается с текущей папки, то убери ее
|
||
pth = pth[2:]
|
||
result.append(os.path.join(root, pth))
|
||
return result
|
||
|
||
# эта функция возвращает просто список объектов Dependency
|
||
def addRootToDeps(self, root, deps):
|
||
result = []
|
||
for dep in deps:
|
||
if dep.type == DependencyType.local:
|
||
if not os.path.isabs(dep.dir):
|
||
dep.dir = os.path.join(root, dep.dir)
|
||
result.append(dep)
|
||
return result
|
||
|
||
def download(self):
|
||
# self.modules - это список из объектов Dependency
|
||
self.modules = self.loadDependency(Dependency({'type': 'local', 'dir': './'}))
|
||
|
||
print('modules: ')
|
||
print(self.modules)
|
||
|
||
def replace_slashes(self, path):
|
||
return path.replace('\\', '/')
|
||
|
||
def genCmake(self):
|
||
cmakeText = '''function(add_sources FILE_LIST FILES_PATH)
|
||
file(GLOB_RECURSE ADD_FILES_LIST ${FILES_PATH})
|
||
list(APPEND ${FILE_LIST} ${ADD_FILES_LIST})
|
||
set(${FILE_LIST} ${${FILE_LIST}} PARENT_SCOPE)
|
||
endfunction()\n\n'''
|
||
|
||
for module in self.modules:
|
||
for inc in module.cmake['inc_dirs']:
|
||
cmakeText += 'include_directories("' + str(inc) + '")\n'
|
||
cmakeText += '\n'
|
||
|
||
for module in self.modules:
|
||
for src in module.cmake['srcs']:
|
||
cmakeText += 'add_sources(SOURCES "' + str(src) + '")\n'
|
||
cmakeText += '\n'
|
||
|
||
for module in self.modules:
|
||
for lib in module.cmake['libs']:
|
||
cmakeText += 'add_sources(LIBRARIES "' + str(lib) + '")\n'
|
||
cmakeText += '\n'
|
||
|
||
for module in self.modules:
|
||
for ui in module.cmake['uis']:
|
||
cmakeText += 'add_sources(UIS "' + str(ui) + '")\n'
|
||
cmakeText += '\n'
|
||
|
||
# заменяем обратный слеш на прямой, так как винда склеивает своими слешами, но их не поинмает компилятор:
|
||
cmakeText = self.replace_slashes(cmakeText)
|
||
print(cmakeText)
|
||
with open('modular.cmake', 'w', encoding='utf-8') as file:
|
||
file.write(cmakeText)
|
||
|
||
def genCmakeForLibrify(self):
|
||
other_path, project_name = os.path.split(os.getcwd())
|
||
cmakeText = '''cmake_minimum_required(VERSION 3.17)
|
||
project(''' + project_name + ''')
|
||
set(CMAKE_C_STANDARD 11)
|
||
|
||
function(add_sources FILE_LIST FILES_PATH)
|
||
file(GLOB ADD_FILES_LIST ${FILES_PATH})
|
||
list(APPEND ${FILE_LIST} ${ADD_FILES_LIST})
|
||
set(${FILE_LIST} ${${FILE_LIST}} PARENT_SCOPE)
|
||
endfunction()\n\n'''
|
||
|
||
for module in self.modules:
|
||
for inc in module.cmake['inc_dirs']:
|
||
cmakeText += 'include_directories("' + str(inc) + '")\n'
|
||
cmakeText += '\n'
|
||
|
||
module = self.modules[-1] # берем только главный (корневой) модуль
|
||
for src in module.cmake['srcs']:
|
||
cmakeText += 'add_sources(SOURCES "' + str(src) + '")\n'
|
||
cmakeText += '\n'
|
||
|
||
for module in self.modules:
|
||
for lib in module.cmake['libs']:
|
||
cmakeText += 'add_sources(LIBRARIES "' + str(lib) + '")\n'
|
||
cmakeText += '\n'
|
||
|
||
for module in self.modules:
|
||
for ui in module.cmake['uis']:
|
||
cmakeText += 'add_sources(UIS "' + str(ui) + '")\n'
|
||
cmakeText += '\n'
|
||
|
||
cmakeText += 'add_library(' + project_name + ' ${SOURCES})\n'
|
||
|
||
# заменяем обратный слеш на прямой, так как винда склеивает своими слешами, но их не поинмает компилятор:
|
||
cmakeText = self.replace_slashes(cmakeText)
|
||
print(cmakeText)
|
||
with open('modular.cmake', 'w', encoding='utf-8') as file:
|
||
file.write(cmakeText)
|
||
|
||
def checkDependencyCollision(self, dependency):
|
||
"""Возвращает True, если зависимость dependency уже есть среди загруженных модулей"""
|
||
# TODO проверка не работает, в self.loadedModules ничего не добавляется, коллизии происходят, ну и пофиг...
|
||
return any([loaded == dependency for loaded in self.loadedModules])
|
||
|
||
# def genEclipseXmlPart(self) - эта функция использоваться не будет
|
||
# def genExp32Cmake(self) - эта функция использоваться не будет
|
||
|
||
def load_rules(self, config_path):
|
||
try:
|
||
rules = json.load(open(config_path, encoding='utf-8'))
|
||
except json.JSONDecodeError:
|
||
print('Error while reading json file at path:', config_path, file=sys.stderr)
|
||
print('Try fixing JSON file and run modular.py again', file=sys.stderr)
|
||
raise json.JSONDecodeError
|
||
if 'replacements' not in rules:
|
||
rules['replacements'] = {}
|
||
if 'filetypes' not in rules:
|
||
rules['filetypes'] = [".c", ".cpp", ".h"] # если типы файлов не указали, будем менять только в этих
|
||
if 'remove_comments' not in rules:
|
||
rules['remove_comments'] = False
|
||
if 'librify' not in rules:
|
||
rules['librify'] = []
|
||
if 'ignore' not in rules:
|
||
rules['ignore'] = []
|
||
|
||
self.rules = rules
|
||
|
||
def replacements_and_comments_deletion(self):
|
||
project_path = '.'
|
||
|
||
def remove_comments_in_text(string):
|
||
pattern = r"(\".*?\"|\'.*?\')|(/\*.*?\*/|//[^\r\n]*$)"
|
||
# первая группа захватывает строки в кавычках (двойных или одинарных)
|
||
# вторая группа захватывает комментарии (
|
||
regex = re.compile(pattern, re.MULTILINE | re.DOTALL)
|
||
|
||
def _replacer(match):
|
||
# если вторая группа (захватывающая комменты) не None,
|
||
# значит, мы захватили не закавыченные (нормальные) комменты
|
||
if match.group(2) is not None:
|
||
return "" # возвращаем пустую строку для удаления коммента
|
||
else: # иначе возвращаем 1-ую группу
|
||
return match.group(1) # поймали "закавыченную" строку
|
||
|
||
return regex.sub(_replacer, string)
|
||
|
||
def remove_comments_in_file(filename):
|
||
with open(filename, 'r') as f:
|
||
content = f.read()
|
||
content_new = remove_comments_in_text(content)
|
||
with open(filename, 'w') as f:
|
||
f.write(content_new)
|
||
|
||
def should_exclude_folder(project_path, dirpath, rules):
|
||
folder_fullpath = os.path.normpath(dirpath)
|
||
return any([folder_fullpath == os.path.normpath(os.path.join(project_path, ignore_path))
|
||
for ignore_path in rules['ignore']])
|
||
|
||
def replace_occurrences_in_file(filename, rules):
|
||
with open(filename, 'r') as f:
|
||
content = f.read()
|
||
content_new = content
|
||
for (old_name, new_name) in rules['replacements'].items():
|
||
content_new = re.sub(old_name, new_name, content_new)
|
||
with open(filename, 'w') as f:
|
||
f.write(content_new)
|
||
|
||
for (dirpath, dirnames, filenames) in os.walk(project_path):
|
||
# print('*****', dirpath, dirnames, filenames)
|
||
if should_exclude_folder(project_path, dirpath, self.rules):
|
||
# если папка входит в игнорируемые, то пропускаем
|
||
print('Исключена папка:', dirpath)
|
||
continue
|
||
for file in filenames:
|
||
filename, file_extension = os.path.splitext(file)
|
||
if file_extension not in self.rules['filetypes']:
|
||
# если расширение не входит в список заданных, то пропускаем этот файт
|
||
continue
|
||
fullpath = os.path.join(dirpath, file)
|
||
print(fullpath)
|
||
if self.rules['remove_comments']:
|
||
remove_comments_in_file(fullpath)
|
||
replace_occurrences_in_file(fullpath, self.rules)
|
||
|
||
def libraryfication(self):
|
||
initial_wd = os.getcwd()
|
||
"""Собирает библиотеки из модулей в папках, указанных в self.rules["librify"] """
|
||
if not os.path.exists('SHRINK'):
|
||
print('No SHRINK folder, creating...')
|
||
os.mkdir('SHRINK')
|
||
print('Done')
|
||
else:
|
||
print(
|
||
'SHRINK folder already exists. Stopping. \n(If you want to generate libs, delete SHRINK folder first)')
|
||
return
|
||
|
||
cmake_lists_text = '''include(../arch.cmake)
|
||
|
||
function(add_sources FILE_LIST FILES_PATH)
|
||
file(GLOB_RECURSE ADD_FILES_LIST ${FILES_PATH})
|
||
list(APPEND ${FILE_LIST} ${ADD_FILES_LIST})
|
||
endfunction()
|
||
|
||
include(modular.cmake)
|
||
'''
|
||
|
||
for directory in self.rules["librify"]:
|
||
if not os.path.exists(directory):
|
||
print('Error! Directory from shrink.json does not exists:', directory)
|
||
raise FileNotFoundError
|
||
os.chdir(directory) # переходим в папку к этому модулю
|
||
|
||
library_name = directory.split('/')[-1]
|
||
|
||
config = json.load(open('modular.json', encoding='utf-8'))
|
||
for src in config['cmake']['srcs']:
|
||
cmake_lists_text += f'add_sources({library_name}_SOURCES "' + '../MODULES/' + library_name + '/' + src + '")\n'
|
||
|
||
cmake_lists_text += 'add_library(' + library_name + ' ${' + library_name + '_SOURCES})'
|
||
os.chdir(initial_wd) # в конце не забываем вернуться в корневую папку проекта
|
||
|
||
os.chdir('SHRINK')
|
||
with open('CMakeLists.txt', 'w', encoding='utf-8') as file:
|
||
file.write(cmake_lists_text)
|
||
|
||
with open('modular.json', 'w') as new_modular:
|
||
modular_text = '''{
|
||
"dep": [
|
||
{
|
||
"type": "local",
|
||
"dir": "../"
|
||
}
|
||
]
|
||
}'''
|
||
new_modular.write(modular_text)
|
||
result_run = subprocess.run(["modular.py", "-c"])
|
||
os.mkdir('build')
|
||
os.chdir('build')
|
||
result_run = subprocess.run(["cmake", ".."])
|
||
if result_run.returncode != 0:
|
||
print('cmake not successful. Stop')
|
||
raise RuntimeError('Previous cmake not successful. Stop')
|
||
result_run = subprocess.run(["make"])
|
||
if result_run.returncode != 0:
|
||
print('make not successful. Stop')
|
||
raise RuntimeError('Previous make not successful. Stop')
|
||
# print('Result code of cmake:', result_run.returncode)
|
||
os.chdir('..')
|
||
os.chdir(initial_wd)
|
||
self.copy_all_libs_to_libs_folder()
|
||
|
||
def copy_all_libs_to_libs_folder(self):
|
||
current_wd = os.path.abspath(os.getcwd())
|
||
try:
|
||
if os.path.exists('libs'):
|
||
print('libs already exists')
|
||
else:
|
||
os.mkdir('libs')
|
||
dest_path = os.path.join(current_wd, 'libs')
|
||
os.chdir('SHRINK')
|
||
os.chdir('build')
|
||
|
||
for (dirpath, dirnames, filenames) in os.walk('.'):
|
||
for file in filenames:
|
||
if file.endswith(".a"):
|
||
source_file_path = os.path.join(dirpath, file)
|
||
shutil.copy(source_file_path, dest_path)
|
||
|
||
except FileNotFoundError as e:
|
||
print('Couldn\'t copy .a libs, error occurred!')
|
||
print(e)
|
||
os.chdir(current_wd) # возвращаемся в исходную папку
|
||
|
||
def delete_sources_of_libraries(self):
|
||
current_wd = os.path.abspath(os.getcwd())
|
||
for module_directory in self.rules["librify"]:
|
||
if not os.path.exists(module_directory):
|
||
print('Error! Directory from shrink.json does not exists:', module_directory)
|
||
raise FileNotFoundError
|
||
os.chdir(module_directory)
|
||
config = json.load(open('modular.json', encoding='utf-8'))
|
||
needed_dirs = config['cmake']['inc_dirs']
|
||
all_dirs_files = [name for name in os.listdir(os.getcwd()) if
|
||
os.path.isdir(os.path.join(os.getcwd(), name))]
|
||
print('all files:', all_dirs_files)
|
||
for dir in all_dirs_files:
|
||
if dir not in needed_dirs:
|
||
print(f'remove dir {dir} in {os.path.join(current_wd, module_directory)}')
|
||
shutil.rmtree(dir)
|
||
os.chdir(current_wd)
|
||
|
||
def delete_all_modular_json_files(self):
|
||
print('Starting to delete all modular.json...')
|
||
for (dirpath, dirnames, filenames) in os.walk('.'):
|
||
for file in filenames:
|
||
if str(file) == 'modular.json':
|
||
os.remove(os.path.join(dirpath, file))
|
||
print('All modular.json files deleted from the project!')
|
||
|
||
def shrink(self):
|
||
print('Start shrinking...')
|
||
self.load_rules('shrink.json')
|
||
self.replacements_and_comments_deletion()
|
||
self.libraryfication()
|
||
self.delete_sources_of_libraries()
|
||
self.delete_all_modular_json_files()
|
||
|
||
print('Done shrinking')
|
||
|
||
def create_release(self, tag=None):
|
||
"""
|
||
Сохраняет информацию о текущих версиях каждого из модулей в качестве релиза.
|
||
В результате в корне проекта создается файл с именем <dd.mm.yyyy_hh.mm[_tag]_release>
|
||
:param tag:
|
||
:return:
|
||
"""
|
||
# without_duplicates = []
|
||
# for module in self.modules:
|
||
# if module not in without_duplicates:
|
||
# without_duplicates.append(module)
|
||
# print(without_duplicates)
|
||
dependencies_without_duplicated = []
|
||
for module in self.modules:
|
||
if module.dep:
|
||
for dep in module.dep:
|
||
if dep not in dependencies_without_duplicated:
|
||
dependencies_without_duplicated.append(dep)
|
||
# print(dependencies_without_duplicated)
|
||
|
||
all_dependencies_for_project = {"dep": []}
|
||
for dep in dependencies_without_duplicated:
|
||
try:
|
||
path = os.path.join('MODULES', dep.repo)
|
||
commit_hash = get_git_revision_hash(path)
|
||
dep_json = {
|
||
"type": dep.type,
|
||
"provider": dep.provider,
|
||
"repo": dep.repo,
|
||
"commit": commit_hash
|
||
}
|
||
all_dependencies_for_project['dep'].append(dep_json)
|
||
except AttributeError as e:
|
||
pass # игнорим: 'Dependency' object has no attribute 'repo' (dep=local ./APP)
|
||
except Exception as e:
|
||
print(f'Случилось испключение: {e} (dep={dep})')
|
||
# print(all_dependencies_for_project)
|
||
|
||
release_file_name = datetime.today().strftime('%d.%m.%Y_%H.%M') # <dd.mm.yyyy_hh.mm_release[_tag]>
|
||
if tag:
|
||
release_file_name += '_' + tag
|
||
release_file_name += '_release.json'
|
||
with open(release_file_name, 'w', encoding='utf-8') as output_file:
|
||
json.dump(all_dependencies_for_project, output_file, ensure_ascii=False, indent=2)
|
||
print(f'Создан файл релиза: {release_file_name}')
|
||
|
||
def checkout_release(self, json_name):
|
||
# with open(json_name, encoding='utf-8') as f:
|
||
# release_json = json.load(f)
|
||
print('Начинаем переход на указанные коммиты в релизе...')
|
||
module = self.loadJsonAtPath(json_name)
|
||
for dependency in module.dep:
|
||
if dependency.commit:
|
||
path = self.downloadableModulePath(dependency)
|
||
checkout_cmd = f'git -C {path} checkout {dependency.commit}'
|
||
# git -C <path> <command>
|
||
# Runs the command as if git was started in <path> instead of the current working directory.
|
||
print(f'Checkout with {checkout_cmd}')
|
||
os.system(checkout_cmd) # выполняет любые поддерживаемые команды в командной строке
|
||
print(f'Вернули состояние проекта к релизной версии {json_name}')
|
||
|
||
|
||
parser = argparse.ArgumentParser()
|
||
parser.add_argument('-c', '--cmake', action='store_true', help='require generate cmake from modules')
|
||
parser.add_argument('-s', '--shrink', action='store_true',
|
||
help='Согласно правилам из shrink.json выполняет замены по RegEx и при "remove_comments": true удаляет все комментарии из кода')
|
||
parser.add_argument('-d', '--download', action='store_true', help='require download submodules')
|
||
parser.add_argument('wdir', help='path to working directory', type=str, default='./', nargs='?')
|
||
parser.add_argument('-r', '--release', action='store_true',
|
||
help='Отмечает текущее состояние проекта как релизное, создает соответствующий состоянию json')
|
||
parser.add_argument('--checkout', type=str,
|
||
help='Переходит в указанный релиз (выполняет checkout). Для релиза должен быть заранее создан релизный json')
|
||
argv = parser.parse_args()
|
||
|
||
print('working directory', argv.wdir)
|
||
|
||
os.chdir(argv.wdir) # изменить текущую рабочую директорию
|
||
|
||
project = ModularProject()
|
||
|
||
if argv.download or argv.cmake or argv.shrink:
|
||
project.download()
|
||
if argv.cmake:
|
||
project.genCmake()
|
||
if argv.shrink:
|
||
project.shrink()
|
||
if argv.release:
|
||
print('release')
|
||
project.download()
|
||
project.create_release()
|
||
if argv.checkout:
|
||
project.download()
|
||
project.checkout_release(argv.checkout)
|
||
# TODO: после checkout проекты будут в состоянии detached HEAH (оторванный указатель HEAD),
|
||
# и надо уметь какой-то командой возвращаться в исходную позицию (с учитыванием commit, указанного в самом проекте...)
|