def OnSave(self, event=None): # Get data to write to control file control = self.GetCtrlInfo() save_dialog = GetFileSaveDialog(GetMainWindow(), GT(u'Save Control Information')) save_dialog.SetFilename(u'control') if ShowDialog(save_dialog): # Be sure not to strip trailing newline (dpkg is picky) WriteFile(save_dialog.GetPath(), control, noStrip=u'\n')
def UpdateDistNamesCache(unstable=True, obsolete=False, generic=False): global FILE_distnames debian_distnames = _get_debian_distnames(unstable, obsolete, generic) ubuntu_distnames = _get_ubuntu_distnames(unstable, obsolete) mint_distnames = _get_mint_distnames() section_debian = u'[DEBIAN]\n{}'.format(u'\n'.join(debian_distnames)) section_ubuntu = u'[UBUNTU]\n{}'.format(u'\n'.join(ubuntu_distnames)) section_mint = u'[LINUX MINT]\n{}'.format(u'\n'.join(mint_distnames)) return WriteFile( FILE_distnames, u'\n\n'.join( (section_debian, section_ubuntu, section_mint)))
def SaveIt(path): # Gather data from different pages data = ( GetPage(pgid.CONTROL).GetSaveData(), GetPage(pgid.FILES).GetSaveData(), GetPage(pgid.SCRIPTS).GetSaveData(), GetPage(pgid.CHANGELOG).GetSaveData(), GetPage(pgid.COPYRIGHT).GetSaveData(), GetPage(pgid.MENU).GetSaveData(), GetPage(pgid.BUILD).GetSaveData(), ) # Create a backup of the project overwrite = False if os.path.isfile(path): backup = u'{}.backup'.format(path) shutil.copy(path, backup) overwrite = True # This try statement can be removed when unicode support is enabled try: WriteFile( path, u'[DEBREATE-{}]\n{}'.format(VERSION_string, u'\n'.join(data))) if overwrite: os.remove(backup) except UnicodeEncodeError: detail1 = GT( u'Unfortunately Debreate does not support unicode yet.') detail2 = GT( u'Remove any non-ASCII characters from your project.') ShowErrorDialog(GT(u'Save failed'), u'{}\n{}'.format(detail1, detail2), title=GT(u'Unicode Error')) if overwrite: os.remove(path) # Restore project backup shutil.move(backup, path)
def OnExportLauncher(self, event=None): Logger.Debug(__name__, u'Export launcher ...') # Get data to write to control file menu_data = self.GetLauncherInfo().encode(u'utf-8') dia = wx.FileDialog(GetMainWindow(), GT(u'Save Launcher'), os.getcwd(), style=wx.FD_SAVE | wx.FD_CHANGE_DIR | wx.FD_OVERWRITE_PROMPT) if ShowDialog(dia): path = dia.GetPath() # Create a backup file overwrite = False if os.path.isfile(path): backup = u'{}.backup'.format(path) shutil.copy(path, backup) overwrite = True try: WriteFile(path, menu_data) if overwrite: os.remove(backup) except UnicodeEncodeError: detail1 = GT( u'Unfortunately Debreate does not support unicode yet.') detail2 = GT( u'Remove any non-ASCII characters from your project.') ShowErrorDialog(GT(u'Save failed'), u'{}\n{}'.format(detail1, detail2), title=GT(u'Unicode Error')) os.remove(path) # Restore from backup shutil.move(backup, path)
def WriteConfig(k_name, k_value, conf=default_config, sectLabel=None): conf_dir = os.path.dirname(conf) if not os.path.isdir(conf_dir): if os.path.exists(conf_dir): print(u'{}: {}: {}'.format( GT(u'Error'), GT(u'Cannot create config directory, file exists'), conf_dir)) return ConfCode.ERR_WRITE os.makedirs(conf_dir) # Only write pre-defined keys if k_name not in default_config_values: print(u'{}: {}: {}'.format(GT(u'Error'), GT(u'Configuration key not found'), k_name)) return ConfCode.KEY_NOT_DEFINED # Make sure we are writing the correct type k_value = default_config_values[k_name][0](k_value) if k_value == None: print(u'{}: {}: {}'.format( GT(u'Error'), GT(u'Wrong value type for configuration key'), k_name)) return ConfCode.WRONG_TYPE # tuple is the only type we need to format if isinstance(k_value, tuple): k_value = u'{},{}'.format(GS(k_value[0]), GS(k_value[1])) else: k_value = GS(k_value) conf_text = wx.EmptyString # Save current config to buffer if os.path.exists(conf): if not os.path.isfile(conf): print(u'{}: {}: {}'.format( GT(u'Error'), GT(u'Cannot open config for writing, directory exists'), conf)) return ConfCode.ERR_WRITE conf_text = ReadFile(conf) # FIXME: ReadFile returns None type if config file exists but is empty if conf_text == None: conf_text = u'' else: conf_text = u'[CONFIG-{}.{}]'.format(GS(config_version[0]), GS(config_version[1])) conf_lines = conf_text.split(u'\n') key_exists = False for L in conf_lines: l_index = conf_lines.index(L) if u'=' in L: key = L.split(u'=')[0] if k_name == key: key_exists = True conf_lines[l_index] = u'{}={}'.format(k_name, k_value) if not key_exists: conf_lines.append(u'{}={}'.format(k_name, k_value)) conf_text = u'\n'.join(conf_lines) if TextIsEmpty(conf_text): print(u'{}: {}'.format(GT(u'Warning'), GT(u'Not writing empty text to configuration'))) return ConfCode.ERR_WRITE # Actual writing to configuration WriteFile(conf, conf_text) if os.path.isfile(conf): return ConfCode.SUCCESS return ConfCode.ERR_WRITE
def WriteMD5(stage_dir, parent=None): CMD_md5sum = GetExecutable(u'md5sum') # Show an error if the 'md5sum' command does not exist # This is only a failsafe & should never actually occur if not CMD_md5sum: if not parent: parent = GetMainWindow() md5_label = GetField(pgid.BUILD, chkid.MD5).GetLabel() err_msg1 = GT(u'The "md5sum" command was not found on the system.') err_msg2 = GT(u'Uncheck the "{}" box.').format(md5_label) err_msg3 = GT( u'Please report this error to one of the following addresses:') err_url1 = u'https://github.com/AntumDeluge/debreate/issues' err_url2 = u'https://sourceforge.net/p/debreate/bugs/' Logger.Error( __name__, u'{} {} {}\n\t{}\n\t{}'.format(err_msg1, err_msg2, err_msg3, err_url1, err_url2)) md5_error = ErrorDialog(parent, text=u'{}\n{}\n\n{}'.format( err_msg1, err_msg2, err_msg3)) md5_error.AddURL(err_url1) md5_error.AddURL(err_url2) md5_error.ShowModal() return None temp_list = [] md5_list = [] # Final list used to write the md5sum file for ROOT, DIRS, FILES in os.walk(stage_dir): # Ignore the 'DEBIAN' directory if os.path.basename(ROOT) == u'DEBIAN': continue for F in FILES: F = u'{}/{}'.format(ROOT, F) md5 = GetCommandOutput(CMD_md5sum, (u'-t', F)) Logger.Debug(__name__, u'WriteMD5: GetCommandOutput: {}'.format(md5)) temp_list.append(md5) for item in temp_list: # Remove [stage_dir] from the path name in the md5sum so that it has a # true unix path # e.g., instead of "/myfolder_temp/usr/local/bin", "/usr/local/bin" sum_split = item.split(u'{}/'.format(stage_dir)) sum_join = u''.join(sum_split) md5_list.append(sum_join) # Create the md5sums file in the "DEBIAN" directory # NOTE: lintian ignores the last character of the file, so should end with newline character (\n) return WriteFile(u'{}/DEBIAN/md5sums'.format(stage_dir), u'{}\n'.format(u'\n'.join(md5_list)), u'\n')
def Build(self, task_list, build_path, filename): # Declare this here in case of error before progress dialog created build_progress = None try: # Other mandatory tasks that will be processed mandatory_tasks = ( u'stage', u'install_size', u'control', u'build', ) # Add other mandatory tasks for T in mandatory_tasks: task_list[T] = None task_count = len(task_list) # Add each file for updating progress dialog if u'files' in task_list: task_count += len(task_list[u'files']) # Add each script for updating progress dialog if u'scripts' in task_list: task_count += len(task_list[u'scripts']) if DebugEnabled(): task_msg = GT(u'Total tasks: {}').format(task_count) print(u'DEBUG: [{}] {}'.format(__name__, task_msg)) for T in task_list: print(u'\t{}'.format(T)) create_changelog = u'changelog' in task_list create_copyright = u'copyright' in task_list pg_control = GetPage(pgid.CONTROL) pg_menu = GetPage(pgid.MENU) stage_dir = u'{}/{}__dbp__'.format(build_path, filename) if os.path.isdir(u'{}/DEBIAN'.format(stage_dir)): try: shutil.rmtree(stage_dir) except OSError: ShowErrorDialog( GT(u'Could not free stage directory: {}').format( stage_dir), title=GT(u'Cannot Continue')) return (dbrerrno.EEXIST, None) # Actual path to new .deb deb = u'"{}/{}.deb"'.format(build_path, filename) progress = 0 task_msg = GT(u'Preparing build tree') Logger.Debug(__name__, task_msg) wx.Yield() build_progress = ProgressDialog( GetMainWindow(), GT(u'Building'), task_msg, maximum=task_count, style=PD_DEFAULT_STYLE | wx.PD_ELAPSED_TIME | wx.PD_ESTIMATED_TIME | wx.PD_CAN_ABORT) DIR_debian = ConcatPaths((stage_dir, u'DEBIAN')) # Make a fresh build tree os.makedirs(DIR_debian) progress += 1 if build_progress.WasCancelled(): build_progress.Destroy() return (dbrerrno.ECNCLD, None) def UpdateProgress(current_task, message=None): task_eval = u'{} / {}'.format(current_task, task_count) if message: Logger.Debug(__name__, u'{} ({})'.format(message, task_eval)) wx.Yield() build_progress.Update(current_task, message) return wx.Yield() build_progress.Update(current_task) # *** Files *** # if u'files' in task_list: UpdateProgress(progress, GT(u'Copying files')) no_follow_link = GetField(GetPage(pgid.FILES), chkid.SYMLINK).IsChecked() # TODO: move this into a file functions module def _copy(f_src, f_tgt, exe=False): # NOTE: Python 3 appears to have follow_symlinks option for shutil.copy # FIXME: copying nested symbolic link may not work if os.path.isdir(f_src): if os.path.islink(f_src) and no_follow_link: Logger.Debug( __name__, u'Adding directory symbolic link to stage: {}'. format(f_tgt)) os.symlink(os.readlink(f_src), f_tgt) else: Logger.Debug( __name__, u'Adding directory to stage: {}'.format(f_tgt)) shutil.copytree(f_src, f_tgt) os.chmod(f_tgt, 0o0755) elif os.path.isfile(f_src): if os.path.islink(f_src) and no_follow_link: Logger.Debug( __name__, u'Adding file symbolic link to stage: {}'. format(f_tgt)) os.symlink(os.readlink(f_src), f_tgt) else: if exe: Logger.Debug( __name__, u'Adding executable to stage: {}'.format( f_tgt)) else: Logger.Debug( __name__, u'Adding file to stage: {}'.format(f_tgt)) shutil.copy(f_src, f_tgt) # Set FILE permissions if exe: os.chmod(f_tgt, 0o0755) else: os.chmod(f_tgt, 0o0644) files_data = task_list[u'files'] for FILE in files_data: file_defs = FILE.split(u' -> ') source_file = file_defs[0] target_file = u'{}{}/{}'.format(stage_dir, file_defs[2], file_defs[1]) target_dir = os.path.dirname(target_file) if not os.path.isdir(target_dir): os.makedirs(target_dir) # Remove asteriks from exectuables exe = False if source_file[-1] == u'*': exe = True source_file = source_file[:-1] _copy( source_file, u'{}/{}'.format(target_dir, os.path.basename(source_file)), exe) # Individual files progress += 1 UpdateProgress(progress) # Entire file task progress += 1 if build_progress.WasCancelled(): build_progress.Destroy() return (dbrerrno.ECNCLD, None) # *** Strip files ***# # FIXME: Needs only be run if 'files' step is used if u'strip' in task_list: UpdateProgress(progress, GT(u'Stripping binaries')) for ROOT, DIRS, FILES in os.walk(stage_dir): #@UnusedVariable for F in FILES: # Don't check files in DEBIAN directory if ROOT != DIR_debian: F = ConcatPaths((ROOT, F)) if FileUnstripped(F): Logger.Debug(__name__, u'Unstripped file: {}'.format(F)) # FIXME: Strip command should be set as class member? ExecuteCommand(GetExecutable(u'strip'), F) progress += 1 if build_progress.WasCancelled(): build_progress.Destroy() return (dbrerrno.ECNCLD, None) package = GetField(pg_control, inputid.PACKAGE).GetValue() # Make sure that the directory is available in which to place documentation if create_changelog or create_copyright: doc_dir = u'{}/usr/share/doc/{}'.format(stage_dir, package) if not os.path.isdir(doc_dir): os.makedirs(doc_dir) # *** Changelog *** # if create_changelog: UpdateProgress(progress, GT(u'Creating changelog')) # If changelog will be installed to default directory changelog_target = task_list[u'changelog'][0] if changelog_target == u'STANDARD': changelog_target = ConcatPaths( (u'{}/usr/share/doc'.format(stage_dir), package)) else: changelog_target = ConcatPaths( (stage_dir, changelog_target)) if not os.path.isdir(changelog_target): os.makedirs(changelog_target) WriteFile(u'{}/changelog'.format(changelog_target), task_list[u'changelog'][1]) CMD_gzip = GetExecutable(u'gzip') if CMD_gzip: UpdateProgress(progress, GT(u'Compressing changelog')) c = u'{} -n --best "{}/changelog"'.format( CMD_gzip, changelog_target) clog_status = commands.getstatusoutput(c.encode(u'utf-8')) if clog_status[0]: ShowErrorDialog(GT(u'Could not compress changelog'), clog_status[1], warn=True, title=GT(u'Warning')) progress += 1 if build_progress.WasCancelled(): build_progress.Destroy() return (dbrerrno.ECNCLD, None) # *** Copyright *** # if create_copyright: UpdateProgress(progress, GT(u'Creating copyright')) WriteFile( u'{}/usr/share/doc/{}/copyright'.format( stage_dir, package), task_list[u'copyright']) progress += 1 if build_progress.WasCancelled(): build_progress.Destroy() return (dbrerrno.ECNCLD, None) # Characters that should not be in filenames invalid_chars = (u' ', u'/') # *** Menu launcher *** # if u'launcher' in task_list: UpdateProgress(progress, GT(u'Creating menu launcher')) # This might be changed later to set a custom directory menu_dir = u'{}/usr/share/applications'.format(stage_dir) menu_filename = pg_menu.GetOutputFilename() # Remove invalid characters from filename for char in invalid_chars: menu_filename = menu_filename.replace(char, u'_') if not os.path.isdir(menu_dir): os.makedirs(menu_dir) WriteFile(u'{}/{}.desktop'.format(menu_dir, menu_filename), task_list[u'launcher']) progress += 1 if build_progress.WasCancelled(): build_progress.Destroy() return (dbrerrno.ECNCLD, None) # *** md5sums file *** # # Good practice to create hashes before populating DEBIAN directory if u'md5sums' in task_list: UpdateProgress(progress, GT(u'Creating md5sums')) if not WriteMD5(stage_dir, parent=build_progress): # Couldn't call md5sum command build_progress.Cancel() progress += 1 if build_progress.WasCancelled(): build_progress.Destroy() return (dbrerrno.ECNCLD, None) # *** Scripts *** # if u'scripts' in task_list: UpdateProgress(progress, GT(u'Creating scripts')) scripts = task_list[u'scripts'] for SCRIPT in scripts: script_name = SCRIPT script_text = scripts[SCRIPT] script_filename = ConcatPaths( (stage_dir, u'DEBIAN', script_name)) WriteFile(script_filename, script_text) # Make sure scipt path is wrapped in quotes to avoid whitespace errors os.chmod(script_filename, 0755) os.system((u'chmod +x "{}"'.format(script_filename))) # Individual scripts progress += 1 UpdateProgress(progress) # Entire script task progress += 1 if build_progress.WasCancelled(): build_progress.Destroy() return (dbrerrno.ECNCLD, None) # *** Control file *** # UpdateProgress(progress, GT(u'Getting installed size')) # Get installed-size installed_size = os.popen( (u'du -hsk "{}"'.format(stage_dir))).readlines() installed_size = installed_size[0].split(u'\t') installed_size = installed_size[0] # Insert Installed-Size into control file control_data = pg_control.Get().split(u'\n') control_data.insert(2, u'Installed-Size: {}'.format(installed_size)) progress += 1 if build_progress.WasCancelled(): build_progress.Destroy() return (dbrerrno.ECNCLD, None) # Create final control file UpdateProgress(progress, GT(u'Creating control file')) # dpkg fails if there is no newline at end of file control_data = u'\n'.join(control_data).strip(u'\n') # Ensure there is only one empty trailing newline # Two '\n' to show physical empty line, but not required # Perhaps because string is not null terminated??? control_data = u'{}\n\n'.format(control_data) WriteFile(u'{}/DEBIAN/control'.format(stage_dir), control_data, noStrip=u'\n') progress += 1 if build_progress.WasCancelled(): build_progress.Destroy() return (dbrerrno.ECNCLD, None) # *** Final build *** # UpdateProgress(progress, GT(u'Running dpkg')) working_dir = os.path.split(stage_dir)[0] c_tree = os.path.split(stage_dir)[1] deb_package = u'{}.deb'.format(filename) # Move the working directory becuase dpkg seems to have problems with spaces in path os.chdir(working_dir) # HACK to fix file/dir permissions for ROOT, DIRS, FILES in os.walk(stage_dir): for D in DIRS: D = u'{}/{}'.format(ROOT, D) os.chmod(D, 0o0755) for F in FILES: F = u'{}/{}'.format(ROOT, F) if os.access(F, os.X_OK): os.chmod(F, 0o0755) else: os.chmod(F, 0o0644) # FIXME: Should check for working fakeroot & dpkg-deb executables build_status = commands.getstatusoutput( (u'{} {} -b "{}" "{}"'.format(GetExecutable(u'fakeroot'), GetExecutable(u'dpkg-deb'), c_tree, deb_package))) progress += 1 if build_progress.WasCancelled(): build_progress.Destroy() return (dbrerrno.ECNCLD, None) # *** Delete staged directory *** # if u'rmstage' in task_list: UpdateProgress(progress, GT(u'Removing temp directory')) try: shutil.rmtree(stage_dir) except OSError: ShowErrorDialog(GT( u'An error occurred when trying to delete the build tree' ), parent=build_progress) progress += 1 if build_progress.WasCancelled(): build_progress.Destroy() return (dbrerrno.ECNCLD, None) # *** ERROR CHECK if u'lintian' in task_list: UpdateProgress(progress, GT(u'Checking package for errors')) # FIXME: Should be set as class memeber? CMD_lintian = GetExecutable(u'lintian') errors = commands.getoutput((u'{} {}'.format(CMD_lintian, deb))) if errors != wx.EmptyString: e1 = GT(u'Lintian found some issues with the package.') e2 = GT(u'Details saved to {}').format(filename) WriteFile(u'{}/{}.lintian'.format(build_path, filename), errors) DetailedMessageDialog(build_progress, GT(u'Lintian Errors'), ICON_INFORMATION, u'{}\n{}.lintian'.format(e1, e2), errors).ShowModal() progress += 1 # Close progress dialog wx.Yield() build_progress.Update(progress) build_progress.Destroy() # Build completed successfullly if not build_status[0]: return (dbrerrno.SUCCESS, deb_package) if PY_VER_MAJ <= 2: # Unicode decoder has trouble with certain characters. Replace any # non-decodable characters with � (0xFFFD). build_output = list(build_status[1]) # String & unicode string incompatibilities index = 0 for C in build_output: try: GS(C) except UnicodeDecodeError: build_output[index] = u'�' index += 1 build_status = (build_status[0], u''.join(build_output)) # Build failed return (build_status[0], build_status[1]) except: if build_progress: build_progress.Destroy() return (dbrerrno.EUNKNOWN, traceback.format_exc())