import re,subprocess import os from pathlib import Path import shutil import time from datetime import datetime from pro_svn_version import * from subprocess import check_output # 압축 필요 없음 skip_zip = False # Model Info model_info = { 'AN6000' : 'viewer.exe', 'TB4000' : 'viewer.exe', 'EMT_KOR1' : 'viewer.exe'} # TODO dll 의존성 확인 # dumpbin /IMPORTS avutil-55.dll # CRASH REPORT 생성용 keep_pdbs = [] #'TB4000', 'RD_40', 'CS_F362FW' ''' USB 인증서로 변경됨 # 인증서 저장 폴더 sign_folder = 'C:\\home\\fmviewer3\\project\\signs' # 인증 데이터 sign_model = { 'AN6000' : {'cert': '20220607-387020_mtekvision_com_chain.pfx', 'pw' : 'XIMKAS', 'desc' : 'AN6000 Viewer'}, 'MH9000' : {'cert': 'keiyo_pw_Keiyo4684.pfx', 'pw' : 'Keiyo4684', 'desc' : '4LensViewer'}, } ''' # USB 인증모델 : 인증서명 sign_model = {'EMT_KOR1' : {'name' : 'emtomega', 'pw' : '0000'}} # signtool.exe sign /f "C:\home\roadmovie\project\signs\20220607-387020_mtekvision_com_chain.pfx" /d "mtekvision" /p XIMKAS "C:\home\roadmovie\project\build-fm_viewer-Qt_5_5-Release\release\viewer.exe" reg_file_type_section = '[Registry]\n Root: HKCR; Subkey: ".tb4"; ValueData: "{#MyAppName}"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""\n \ Root: HKCR; Subkey: "{#MyAppName}"; ValueData: "Program {#MyAppName}"; Flags: uninsdeletekey; ValueType: string; ValueName: ""\n \ Root: HKCR; Subkey: "{#MyAppName}\\DefaultIcon"; ValueData: "{app}\\{#MyAppExeName},0"; ValueType: string; ValueName: ""\n \ Root: HKCR; Subkey: "{#MyAppName}\\shell\\open\\command"; ValueData: """{app}\\{#MyAppExeName}"" ""%1"""; ValueType: string; ValueName: ""\n' script_info = {'EMT_KOR1' : {'name' : 'NEXIAN Viewer', 'install_file' : 'NEXIAN_Viewer', 'short_name' : 'NEXIAN Viewer', 'short_dir' : 'NEXIAN_Viewer', 'publisher' : 'MediaHills Inc.', 'exe' : 'viewer.exe', 'group_short' : 'NEXIAN Viewer', 'dir_name' : '\\NEXIAN Viewer', 'group_long' : '\\NEXIAN Viewer', 'icon' : 'C:\\home\\fmviewer3\\project\\fm_viewer\\res\\emt_kr\\emt_kr.ico', 'app_id' : '7F35092D-5CF6-4BCB-AB0F-6C314521F6B1', 'trash' : 'C:\\home\\fmviewer3\\project\\fm_viewer\\res\\emt_kr\\trash.ico', 'language' : 'Name: "korean"; MessagesFile: "compiler:Languages\\Korean.isl"', 'reg_file_type' : 'no', 'printer_section' : '', 'registry_section' : '', 'doc' : 'NEXIAN'}, 'AN6000' : {'name' : 'YAWATA WiFiCAM Viewer', 'install_file' : 'YAWATA_WiFiCAM_Viewer', 'short_name' : 'YAWATA_WiFiCAM_Viewer', 'short_dir' : 'YAWATA_WiFiCAM_Viewer', 'publisher' : 'YAWATA', 'exe' : 'Viewer.exe', 'group_short' : 'YAWATA', 'dir_name' : '\\YAWATA', 'group_long' : '\\YAWATA', 'icon' : 'C:\\home\\fmviewer3\\project\\fm_viewer\\res\\mtek\\an6000.ico', 'app_id' : '4D8FFB28-75A7-4880-B78C-055F0E2D8CBB', 'trash' : 'C:\\home\\fmviewer3\\project\\fm_viewer\\res\\mtek\\trash.ico', 'language' : 'Name: "japanese"; MessagesFile: "compiler:Languages\\Japanese.isl"', 'reg_file_type' : 'no', 'printer_section' : '', 'registry_section' : '', 'doc' : 'ViewerDoc'}, 'TB4000' : {'name' : 'TB4000', 'install_file' : 'TB4000', 'short_name' : 'TB4000', 'short_dir' : 'TB4000', 'publisher' : 'Telebit Inc.', 'exe' : 'Viewer.exe', 'group_short' : 'TB4000', 'dir_name' : '\\TB4000', 'group_long' : '\\TB4000', 'icon' : 'C:\\home\\fmviewer3\\project\\fm_viewer\\res\\keiyo\\keiyo.ico', 'app_id' : '2B678C5F-5F0C-4689-9CD3-F01F6E6556E0', 'trash' : 'C:\\home\\fmviewer3\\project\\fm_viewer\\res\\keiyo\\trash.ico', 'language' : 'Name: "korean"; MessagesFile: "compiler:Languages\\Korean.isl"', 'reg_file_type' : 'yes', 'registry_section' : reg_file_type_section, 'printer_section' : 'Source: "{#SRCFolder}\\printsupport\\*"; DestDir: "{app}\\printsupport"; Flags: ignoreversion recursesubdirs createallsubdirs', 'doc' : 'TB4000'}, } # INNO SETUP 설치 스크립트를 직접 생성 # [Languages] # $language # Name: en; MessagesFile: "compiler:Default.isl" # Name: "japanese"; MessagesFile: "compiler:Languages\Japanese.isl" install_script = ''' #define MyAppName "$name" #define MyAppShortName "$short_name" #define MyAppPublisher "$publisher" #define MyAppExeName "$exe" #define MyAppGroup "$group_short" #define MyAppGroupLong "$group_long" #define SRCFolder "C:\\home\\fmviewer3\\project\\_build_fm\\release" #define MyAppVersion GetVersionNumbersString(SRCFolder + '\\' + MyAppExeName) [Setup] AppId={{$app_id}} AppName={#MyAppName} AppVersion={#MyAppVersion} ;AppVerName={#MyAppName} {#MyAppVersion} AppPublisher={#MyAppPublisher} DefaultDirName={commonpf}$dir_name DisableDirPage=no DefaultGroupName={code:GetGroupName} ;DisableProgramGroupPage=no OutputBaseFilename=install_$install_file_ver_{#MyAppVersion} Compression=lzma SolidCompression=yes ChangesAssociations=$reg_file_type SetupIconFile=$icon UninstallDisplayIcon={app}\\{#MyAppExeName} UninstallDisplayName="$name" DirExistsWarning=no VersionInfoCompany="" VersionInfoCopyright="" VersionInfoDescription="" VersionInfoProductName="" [Languages] $language [Tasks] ;Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: checked [Files] Source: "{#SRCFolder}\\*"; DestDir: "{app}"; Flags: ignoreversion Source: "{#SRCFolder}\\iconengines\\*"; DestDir: "{app}\\iconengines"; Flags: ignoreversion recursesubdirs createallsubdirs Source: "{#SRCFolder}\\imageformats\\*"; DestDir: "{app}\\imageformats"; Flags: ignoreversion recursesubdirs createallsubdirs Source: "{#SRCFolder}\\platforms\\*"; DestDir: "{app}\\platforms"; Flags: ignoreversion recursesubdirs createallsubdirs $printer_section Source: "$trash"; DestDir: "{app}"; Flags: ignoreversion [Icons] Name: "{userprograms}\\{#MyAppGroupLong}\\{#MyAppShortName}"; Filename: "{app}\\{#MyAppExeName}"; Name: "{userprograms}\\{#MyAppGroupLong}\\uninstall ({#MyAppShortName})"; Filename: "{uninstallexe}"; IconFilename: "{app}\\trash.ico"; Name: "{commondesktop}\\{#MyAppName}"; Filename: "{app}\\{#MyAppExeName}" Name: "{app}\\Uninstall.exe"; Filename: "{app}\\unins000.exe"; IconFilename: "{app}\\trash.ico" [UninstallDelete] Type: files; Name: "{commondocs}\\$doc\\viewersetup\\conf.ini" Type: files; Name: "{userdocs}\\$doc\\viewersetup\\conf.ini" Type: files; Name: "{userprograms}\\{#MyAppGroupLong}\\{#MyAppShortName}" Type: files; Name: "{userprograms}\\{#MyAppGroupLong}\\uninstall ({#MyAppShortName})" $registry_section [Code] function InitializeUninstall(): Boolean; var ErrorCode: Integer; begin ShellExec('open','taskkill.exe','/f /im {#MyAppExeName}','',SW_HIDE,ewNoWait,ErrorCode); ShellExec('open','tskill.exe',' {#MyAppName}','',SW_HIDE,ewNoWait,ErrorCode); result := True; end; function GetGroupName(Param: String): String; var Version: TWindowsVersion; begin GetWindowsVersionEx(Version); if (Version.Major > 6) or ((Version.Major = 6) and (Version.Minor >= 2)) then begin Result := '{#MyAppGroup}'; end else begin Result := '{#MyAppGroup}' + '\\$short_dir'; end; end; ''' japan_address_src = 'C:\\home\\fmviewer3\\script\\jpaddr\\20210120_jp_addr.bin' japan_address_db = ['XLDR_88'] # QT Project path project_root = 'C:\\home\\fmviewer3\\project\\' project_src = project_root + 'fm_viewer\\fm_viewer.pro' build_dest = project_root + '_build_fm' build_release = build_dest + '\\release' install_dest = 'C:\\home\\build_fm' zip7_path = '"C:\\Program Files\\7-Zip\\7z.exe"' # SVN 폴더의 버전 정보를 확인한다 def svnversion(src_path): p = subprocess.Popen('svnversion ' + src_path, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (stdout, stderr) = p.communicate() # eg. 546:556M return int(stdout.decode('cp949').split(':')[0]) # QT PRO 파일에서 DEFINES += RM_MODEL_SVN_VERSION=656 버전 번호를 확인한다. def proversion(src_path): with open(src_path,mode='r',encoding='utf-8') as f: for line in f: if 'RM_MODEL_SVN_VERSION' in line: print(line) #if 'RM_MODEL_SVN_VERSION' in f.read(): #return 1 return 0 # TXT 파일에서 XXX= 라인을 확인하여 리턴 def get_txt_string(src,query): with open(src,mode='r',encoding='utf-8') as f: for line in f: if query in line: return line return None # YN 확인 def askyn(message): ret = input(message + ' (Y/n) :') or 'y' return ret == 'y' or ret == 'Y' # subprocess 에서 발생한 os environment 를 보존한다 def run_bat_with_env(cmd): cmd = cmd + ' && echo ~~~~START_ENVIRONMENT_HERE~~~~ && set' env = (subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE) .stdout .read() .decode('utf-8') .splitlines()) record = False for e in env: if record: e = e.strip().split('=') os.environ[e[0]] = e[1] elif e.strip() == '~~~~START_ENVIRONMENT_HERE~~~~': record = True #print(os.environ) # 환경설정 확인해서 없으면 추가 => python running 중에만 동작함 def check_path(): #MSVC 추가 (environment variable 을 보존) msvc_bat = '"C:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VC\\vcvarsall.bat" x86' run_bat_with_env(msvc_bat) # PATH 추가 되어 있지않으면 추가 required_path_list = [ 'C:\\PROGRAM FILES (X86)\\INNO SETUP 6', 'C:\\QT\\5.5\\MSVC2010\\BIN', 'C:\\QT\\QT515\\TOOLS\\QTCREATOR\\BIN', 'C:\\QT\\QTCREATOR-4.3.1\\BIN', 'C:\\QT\\TOOLS\\QTCREATOR\\BIN', 'C:\\QT\\TOOLS\\QTCREATOR\\BIN\\JOM', 'C:\\PROGRAM FILES (X86)\\MICROSOFT VISUAL STUDIO 10.0\\VC\\BIN', 'C:\\PROGRAM FILES (X86)\\MICROSOFT SDKS\\WINDOWS\\V7.0A\\BIN', 'C:\\Program Files (x86)\\NSIS'] # 존재하지 않는 경로 제거 required_path_list = [each_path for each_path in required_path_list if exists(each_path)] # 이미 PATH 환경 변수에 포함된 경로 제거 path_list = os.environ['PATH'].split(';') for each_path in path_list: #print(each_path.upper()) if each_path.upper() in required_path_list: required_path_list.remove(each_path.upper()) if len(required_path_list) > 0: append = ';'.join(required_path_list) full_path = append + ';' + os.environ['PATH']; #print('APPEND:\t' + append) os.environ['PATH'] = full_path #print(os.environ['PATH']) # 폴더에서 모든 확장자 파일 삭제 def delete_all(path,extension): files = os.listdir( path ) for item in files: if item.upper().endswith(extension.upper()): os.remove( os.path.join( path, item ) ) # 폴더에서 prefix 로 검색해서 파일명 리턴 -> 이전 파일 가져오는 문제로 사용금지 def get_file_from_prefix(path,prefix): files = os.listdir( path ) for item in files: if item.upper().startswith(prefix.upper()): return item return None def get_exe_version(path): command_string = 'wmic datafile where name="' + path + '" get Version /value' #subprocess.call(command_string, shell=True) #out = check_output(['wmic','datafile','where','name',path,'get','Version','/value']) out = check_output(command_string) out = out.strip().decode('utf-8') if 'Version=' in out: out = out.split('=')[1] return out # ISS 파일 생성 def create_iss(model, dest): if os.path.exists(dest): os.remove(dest) info = script_info[model] iss = install_script replace_list = list(info.keys()) for each_item in replace_list: iss = iss.replace('$' + each_item,info[each_item]) with open(dest,mode='w+t',encoding='utf-8') as f: f.write(iss) # 모델 빌드 (compile + innosetup + zip) def build_model(model): start_dir = os.getcwd() # 존재할 경우 전체 삭제 후 다시 생성 clean_dir() Path(build_dest).mkdir(parents=True, exist_ok=True) # 폴더 이동 os.chdir(build_dest) command_string = 'qmake.exe ' + project_src + ' -spec win32-msvc2010 "CONFIG+=Release" "DEFINES+=' + model + '" && jom.exe qmake_all' #print(command_string) subprocess.call(command_string, shell=True) subprocess.call('jom.exe', shell=True) subprocess.call('jom.exe clean', shell=True) # 처리후에도 xxx.pdb 파일은 남아 있음 -> 제거 if model not in keep_pdbs: delete_all(build_release,'.pdb') # DLL 추가 exe_name = model_info[model] windeploy_cmd = 'windeployqt.exe --force --release --no-quick-import --no-translations ' + build_release + '//' + exe_name subprocess.call(windeploy_cmd, shell=True) vr_copy_cmd = 'copy /Y ' + project_root + 'vcredist_2010\\*.dll ' + build_release subprocess.call(vr_copy_cmd, shell=True) if model == 'TB4000' or model == 'TB5000': ffmpeg_copy_cmd = 'copy /Y ' + project_root + 'ffmpeg_dll_compact_348_telebit_enc\\bin\\av*.dll ' + build_release subprocess.call(ffmpeg_copy_cmd, shell=True) ffmpeg_copy_cmd = 'copy /Y ' + project_root + 'ffmpeg_dll_compact_348_telebit_enc\\bin\\sw*.dll ' + build_release subprocess.call(ffmpeg_copy_cmd, shell=True) ffmpeg_copy_cmd = 'copy /Y ' + project_root + 'ffmpeg_dll_compact_348_telebit_enc\\bin\\libcrypt_tb.dll ' + build_release subprocess.call(ffmpeg_copy_cmd, shell=True) vr_copy_cmd = 'copy /Y ' + project_root + 'vcredist_2019\\*.dll ' + build_release subprocess.call(vr_copy_cmd, shell=True) ssl_copy_cmd = 'copy /Y ' + project_root + 'libeay\\*.dll ' + build_release subprocess.call(ssl_copy_cmd, shell=True) tb5000_cmd = 'copy /Y ' + project_root + 'tb5000_lib\\*.dll ' + build_release subprocess.call(tb5000_cmd, shell=True) else: ffmpeg_copy_cmd = 'copy /Y ' + project_root + 'ffmpeg_dll_compact_348\\bin\\av*.dll ' + build_release subprocess.call(ffmpeg_copy_cmd, shell=True) ffmpeg_copy_cmd = 'copy /Y ' + project_root + 'ffmpeg_dll_compact_348\\bin\\sw*.dll ' + build_release subprocess.call(ffmpeg_copy_cmd, shell=True) ssl_copy_cmd = 'copy /Y ' + project_root + 'libeay\\*.dll ' + build_release subprocess.call(ssl_copy_cmd, shell=True) portaudio_copy_cmd = 'copy /Y ' + project_root + 'portaudio_197\\bin\\portaudio_x86*.dll ' + build_release subprocess.call(portaudio_copy_cmd, shell=True) webview2_copy_cmd = 'copy /Y ' + project_root + 'webview2\\webview2\\*.dll ' + build_release subprocess.call(webview2_copy_cmd, shell=True) # 일본 주소 DB 포함 if model in japan_address_db: address_copy_cmd = 'copy /Y ' + japan_address_src + ' ' + build_release + '\\jpaddr.bin' #print(address_copy_cmd) subprocess.call(address_copy_cmd, shell=True) # 버전 정보 필요 exe_path = build_release + '\\' + exe_name exe_path = exe_path.replace('\\','\\\\') # wmic 커맨드로 처리해야 하여 \\ 로 경로 전달해야함 version_string = get_exe_version(exe_path) # 필요시 SVN COMMIT svn_commit_if_needed(f'rel:{model}_{version_string}') # 인증모델 (실행파일 인증) # 구 파일 인증 방식 ''' if model in sign_model: # Microsoft SDKs 경로에 signtool.exe 존재 sign_info = sign_model[model] sign_cmd = f'signtool.exe sign /f "{sign_folder}\\{sign_info["cert"]}" /d "{sign_info["desc"]}" /p {sign_info["pw"]} "{build_release}\\{model_info[model]}"' print(f'CODESIGN {model} CMD:{sign_cmd}') subprocess.call(sign_cmd, shell=True) ''' if model in sign_model: sign_name = sign_model[model]['name'] #sign_pw = sign_model[model]['pw'] /p 옵션은 파일형식에서만 사용가능 sign_cmd = f'signtool.exe sign /n "{sign_name}" /fd sha256 /tr http://timestamp.globalsign.com/tsa/r6advanced1 /td sha256 "{build_release}\\{model_info[model]}"' subprocess.call(sign_cmd, shell=True) # INNOSETUP # "C:\Program Files (x86)\Inno Setup 6\iscc.exe" /OC:\home\build C:\home\roadmovie\project\CS_360FH.iss # ISS 파일 생성 모델 if model in script_info: innosetup_script = install_dest + '\\' + model + '.iss' create_iss(model,innosetup_script) else: innosetup_script = project_root + model + '.iss' innosetup_cmd = 'iscc.exe /O' + install_dest + ' ' + innosetup_script subprocess.call(innosetup_cmd, shell=True) # 인증모델 (설치파일 인증) ''' if model in sign_model: # Microsoft SDKs 경로에 signtool.exe 존재 sign_info = sign_model[model] # 생성된 설치파일 경로 확인 install_file_prefix = get_txt_string(innosetup_script,'OutputBaseFilename') install_file_prefix = install_file_prefix.split('=')[1].split('{')[0] #install_file_name = get_file_from_prefix(install_dest,install_file_prefix) install_file_name = install_file_prefix + version_string + '.exe' sign_cmd = f'signtool.exe sign /f "{sign_folder}\\{sign_info["cert"]}" /d "{sign_info["desc"]}" /p {sign_info["pw"]} "{install_dest}\\{install_file_name}"' subprocess.call(sign_cmd, shell=True) ''' if model in sign_model: sign_name = sign_model[model]['name'] #sign_pw = sign_model[model]['pw'] install_file_prefix = get_txt_string(innosetup_script,'OutputBaseFilename') install_file_prefix = install_file_prefix.split('=')[1].split('{')[0] #install_file_name = get_file_from_prefix(install_dest,install_file_prefix) install_file_name = install_file_prefix + version_string + '.exe' sign_cmd = f'signtool.exe sign /n "{sign_name}" /fd sha256 /tr http://timestamp.globalsign.com/tsa/r6advanced1 /td sha256 "{install_dest}\\{install_file_name}"' subprocess.call(sign_cmd, shell=True) # ISS 파일 삭제 #... if skip_zip == False: install_file_prefix = get_txt_string(innosetup_script,'OutputBaseFilename') # OutputBaseFilename=install_Cellstar_CS-61FH_Viewer_ver_{#MyAppVersion} install_file_prefix = install_file_prefix.split('=')[1].split('{')[0] #install_file_name = get_file_from_prefix(install_dest,install_file_prefix) install_file_name = install_file_prefix + version_string + '.exe' # Compress zip date_string = datetime.now().strftime("%Y%m%d") zip_file_name = install_dest + '\\' + date_string + '_' + install_file_name.replace('.exe','.zip') zip_cmd = zip7_path + ' a ' + zip_file_name + ' ' + install_dest + '\\' + install_file_name subprocess.call(zip_cmd, shell=True) # Clean # 현재 py src 폴더로 이동 os.chdir(start_dir) #print('current dir:' + os.getcwd()) clean_dir() return # 디렉토리 전체 삭제 def clean_dir(): if not os.path.exists(install_dest): Path(install_dest).mkdir(parents=True, exist_ok=True) if os.path.exists(build_dest): #print('clean dir:' + build_dest) shutil.rmtree(build_dest) # 빌드할 모델 선택 def build_menu(): print('-------------------------') print(' FM Builder ver. 0.1') print(' 2020/10/05 fmproject') print('-------------------------') print('Select a model no. to build\n') # 모델명 정렬 models = list(model_info.keys()) models.sort() # 선택 모델리스트 표시 + 전체빌드 for each_model in models: print(str(models.index(each_model)).rjust(4,' ') + ' : ' + each_model) print(' A : BUILD ALL') model_no = input("Enter number:") or exit() # 전체빌드 if model_no == 'A' or model_no == 'a': if askyn('start?') == False: print('exit') exit() start_time = time.time() for selected_model in models: build_model(selected_model) elapsed_time = time.time() - start_time elapsed_time_string = time.strftime("%H:%M:%S", time.gmtime(elapsed_time)) print('Process done:' + elapsed_time_string) elif ',' in model_no: model_no_list = model_no.split(',') if askyn('start?') == False: print('exit') exit() for each_model_no in model_no_list: model_index = int(each_model_no) selected_model = models[model_index] print('Start building.... ' + selected_model) build_model(selected_model) else: # 모델별 빌드 try: model_index = int(model_no) except ValueError: print('#ERR. input number only') exit() if len(models) <= model_index or model_index < 0: print('#ERR. invalid model number') exit() selected_model = models[model_index] print('Start building.... ' + selected_model) if askyn('start?') == False: print('exit') exit() start_time = time.time() build_model(selected_model) elapsed_time = time.time() - start_time elapsed_time_string = time.strftime("%H:%M:%S", time.gmtime(elapsed_time)) print('Process done:' + elapsed_time_string) # 폴더 열기 open_cmd = 'explorer "' + install_dest + '"' subprocess.Popen(open_cmd) check_path() build_menu() ''' # MAIN svn_path = 'C:\\home\\fmviewer3' svn_version = svnversion(svn_path) print('SVN:'+str(svn_version)) pro_path = 'C:\\home\\fmviewer3\\project\\chunho_viewer\\chunho_viewer.pro' pro_version = proversion(pro_path) print(pro_version) src_path = 'C:\\home\\fmviewer3' #print('svn info ' + src_path) svn_info = subprocess.check_output('svn info ' + src_path).decode('cp949') #utf-8 #print(svn_info) revisionNum = re.search("Revision:\s\d+", svn_info) print(revisionNum) #print (re.search(ur"Revision:\s\d+", svn_info)).group() '''