def flatten_mapping(node): if not isinstance(node, MappingNode): return by_key = {} for i, (k, v) in enumerate(node.value): by_key.setdefault(k.value, []).append((i, v)) to_remove = [] for k, vs in by_key.items(): if len(vs) == 1: continue if k in SEQUENCE_FIELDS: if not isinstance(vs[0], SequenceNode): node.value[vs[0][0]] = ( node.value[vs[0][0]][0], SequenceNode( tag='tag:yaml.org,2002:seq', value=[vs[0][1]], start_mark=node.value[vs[0][0]][1].start_mark, end_mark=node.value[vs[0][0]][1].end_mark)) primary = node.value[vs[0][0]][1] for i, v in vs[1:]: if isinstance(v, SequenceNode): primary.value.extend(v.value) elif isinstance(v, MappingNode): primary.value.append(v) else: primary.value.append(v) to_remove.append((i, k)) else: # Preserve the first value. # TODO(jelmer): Make a more informed choice. for (i, v) in vs[1:]: to_remove.append((i, k)) if not to_remove: return for i, k in sorted(to_remove, reverse=True): editor.force_rewrite() del node.value[i] fixed_lintian_tag('source', 'upstream-metadata-yaml-invalid') report_result('Remove duplicate values for fields %s ' 'in debian/upstream/metadata.' % ', '.join([k for (i, k) in sorted(to_remove)]))
from lintian_brush.patches import rules_find_patches_directory def drop_quilt_with(line, target): newline = dh_invoke_drop_with(line, b'quilt') if line != newline: issue = LintianIssue( 'source', 'dh-quilt-addon-but-quilt-source-format', 'dh ... --with quilt (line XX)') if issue.should_fix(): issue.report_fixed() else: newline = line return newline try: with open('debian/source/format', 'r') as f: if f.read().strip() == '3.0 (quilt)': with RulesEditor() as updater: if rules_find_patches_directory( updater.makefile) in ('debian/patches', None): updater.legacy_update(command_line_cb=drop_quilt_with) except FileNotFoundError: pass report_result( "Don't specify --with=quilt, since package uses " "'3.0 (quilt)' source format.")
def update_timestamp(p, ts): import time ls = [] with open(p, 'rb') as f: for line in f: if line.startswith(b'"POT-Creation-Date: '): tp = time.gmtime(ts) line = b'"POT-Creation-Date: %s\\n"\n' % (time.strftime( "%Y-%m-%d %H:%M+0000", tp).encode()) ls.append(line) with open(p, 'wb') as f: f.writelines(ls) if 'DEBCONF_GETTEXTIZE_TIMESTAMP' in os.environ: old_hashes = read_hashes() debconf_updatepo() new_hashes = read_hashes() for p in old_hashes: if old_hashes[p] == new_hashes[p]: continue update_timestamp(p, int(os.environ['DEBCONF_GETTEXTIZE_TIMESTAMP'])) else: debconf_updatepo() issue.report_fixed() report_result('Run debconf-updatepo after template changes.')
files = group.files.get_patterns() else: files = group.files.sorted_members() if group.copyrights: holders = '\n '.join(group.copyrights.sorted_members()) else: holders = 'Unknown' paragraph = FilesParagraph.create(list(files), holders, License(group.license)) comments = group.get_comments() if comments: paragraph.comment = comments c.add_files_paragraph(paragraph) # Print license paragraphs for key in sorted(licenses): license_ = DecopyLicense.get(key) paragraph = LicenseParagraph.create(License(license_.name)) paragraph.comment = "Add the corresponding license text here" c.add_license_paragraph(paragraph) with open('debian/copyright', 'w') as f: c.dump(f) fixed_lintian_tag('source', 'no-copyright-file') report_result('Create a debian/copyright file.', certainty=CERTAINTY)
def fix_spaces(copyright): for paragraph in copyright.all_paragraphs(): if not paragraph.license: continue if ' ' not in paragraph.license.synopsis: continue ors = paragraph.license.synopsis.replace(' | ', ' or ').split(' or ') names = [] for name in ors: if name.lower() in RENAMES: name = RENAMES[name.lower()] elif name.replace(' ', '-').lower() in REPLACE_SPACES: name = name.replace(' ', '-') names.append(name) newsynopsis = ' or '.join(names) if newsynopsis != paragraph.license.synopsis: fixed_lintian_tag('source', 'space-in-std-shortname-in-dep5-copyright', '%s (line XX)' % paragraph.license.synopsis) paragraph.license = License(newsynopsis, paragraph.license.text) try: with CopyrightEditor() as updater: fix_spaces(updater.copyright) except (FileNotFoundError, NotMachineReadableError): pass report_result('Replace spaces in short license names with dashes.')
import sys from debmutate.debhelper import ( ensure_minimum_debhelper_version, read_debhelper_compat_file, ) from lintian_brush.fixer import ( control, report_result, fixed_lintian_tag, ) # Debian source package is not obliged to contain `debian/compat'. # Firstly, it may not use debhelper; secondly it may use modern # `debhelper-compat' dependency style. try: minimum_version = read_debhelper_compat_file('debian/compat') except FileNotFoundError: sys.exit(0) with control as updater: if ensure_minimum_debhelper_version(updater.source, "%s~" % minimum_version): fixed_lintian_tag('source', 'no-versioned-debhelper-prerequisite', info='%d' % minimum_version) report_result("Bump debhelper dependency to >= %s, since that's what is " "used in debian/compat." % minimum_version)
'upstream-metadata-missing-repository') filter_by_tag(editor.code, changed, ['Bug-Database', 'Bug-Submit'], 'upstream-metadata-missing-bug-tracking') # A change that just says the "Name" field is a bit silly if set(changed.keys()) - set(ADDON_ONLY_FIELDS) == set(['Name']): sys.exit(0) if not os.path.exists('debian/upstream/metadata'): missing_file_issue.report_fixed() update_ordered_dict(editor.code, [(k, v.value) for (k, v) in changed.items()], key=upstream_metadata_sort_key) # If there are only add-on-only fields, then just remove the file. if not (set(editor.code.keys()) - set(ADDON_ONLY_FIELDS)): editor.code.clear() if editor.code and not os.path.isdir('debian/upstream'): os.makedirs('debian/upstream', exist_ok=True) # TODO(jelmer): Add note about other origin fields? fields = [('%s (from %s)' % (v.field, v.origin)) if v.origin == './configure' else v.field for k, v in sorted(changed.items())] report_result('Set upstream metadata fields: %s.' % ', '.join(sorted(fields)), certainty=achieved_certainty)
return path if not newpath.startswith(path + '-'): return path updated.add(synopsis) if waslink: fixed_lintian_tag('all', 'copyright-refers-to-symlink-license', info=path.lstrip('/')) fixed_lintian_tag('all', 'copyright-refers-to-versionless-license-file', info=path.lstrip('/')) return newpath try: with CopyrightEditor() as updater: for para in updater.copyright.all_paragraphs(): license = para.license if not license or not license.text: continue changed_text = re.sub( '/usr/share/common-licenses/([A-Za-z0-9-.]+)', partial(replace_symlink_path, license.synopsis), license.text) if changed_text != license.text: para.license = License(license.synopsis, changed_text) except (FileNotFoundError, NotMachineReadableError): pass report_result('Refer to specific version of license %s.' % ', '.join(sorted(updated)))
# Nothing wrong here return line lineno = -1 # TODO(jelmer): Pass this up fixed_lintian_tag('source', 'debian-rules-sets-dpkg-architecture-variable', info='%s (line %d)' % (variable, lineno)) if architecture_included: message = 'Rely on existing architecture.mk include.' return None return re.sub(b'([:?]?=)', b'?=', line) if opinionated(): try: mf = Makefile.from_path('debian/rules') except FileNotFoundError: pass else: for line in mf.contents: if (isinstance(line, bytes) and is_dpkg_architecture_line(line)[1:3] == (True, True)): update_rules(global_line_cb=drop_arch_line) message = ( 'Rely on pre-initialized dpkg-architecture variables.') break else: message = 'Use ?= for assignments to architecture variables.' update_rules(global_line_cb=update_assignment_kind) report_result(message)
#!/usr/bin/python3 from lintian_brush.fixer import report_result, LintianIssue, control with control as editor: for binary in control.binaries: try: desc = binary['Description'] except KeyError: # Uhm, okay. continue lines = desc.splitlines(True) issue = LintianIssue(binary, 'extended-description-contains-empty-paragraph', ()) if not issue.should_fix(): continue if len(lines) > 1 and lines[1] == ' .\n': del lines[1] else: continue binary['Description'] = ''.join(lines) issue.report_fixed() report_result('Remove empty leading paragraph in Description.')
#!/usr/bin/python3 from debmutate.control import drop_dependency from debmutate.debhelper import ensure_minimum_debhelper_version from lintian_brush.fixer import control, report_result, LintianIssue with control as updater: for field in [ "Build-Depends", "Build-Depends-Indep", "Build-Depends-Arch" ]: try: old_build_depends = updater.source[field] except KeyError: continue issue = LintianIssue(updater.source, 'build-depends-on-obsolete-package', info='%s: %s' % (field.lower(), 'dh-systemd')) if not issue.should_fix(): continue updater.source[field] = drop_dependency(old_build_depends, "dh-systemd") if old_build_depends != updater.source[field]: ensure_minimum_debhelper_version(updater.source, "9.20160709") issue.report_fixed() report_result( "Depend on newer debhelper (>= 9.20160709) rather than dh-systemd.")
#!/usr/bin/python3 from lintian_brush.fixer import control, report_result, LintianIssue from lintian_brush.vcs import canonicalize_vcs_url fields = set() with control as updater: for name in updater.source: if not name.startswith("Vcs-"): continue new_value = canonicalize_vcs_url( name[len("Vcs-"):], updater.source[name]) if new_value != updater.source[name]: issue = LintianIssue( updater.source, 'vcs-field-not-canonical', '%s %s' % (updater.source[name], new_value)) if issue.should_fix(): issue.report_fixed() updater.source[name] = new_value fields.add(name) report_result("Use canonical URL in " + ', '.join(sorted(fields)) + '.')
if not meets_minimum_certainty(CERTAINTY): sys.exit(2) with control: sequences = list(get_sequences(control_editor=control)) if 'nodejs' not in sequences: sys.exit(2) issue = LintianIssue(control.source, 'pkg-js-tools-test-is-missing', 'debian/tests/pkg-js/test') if issue.should_fix(): if os.path.exists("test/node.js"): os.makedirs("debian/tests", exist_ok=True) with open("debian/tests/pkg-js/test", "w") as f: f.write("mocha test/node.js\n") control.source["Build-Depends"] = ensure_some_version( control.source.get("Build-Depends", ''), "mocha <!nocheck>") issue.report_fixed() elif os.path.exists("test.js"): os.makedirs("debian/tests/pkg-js", exist_ok=True) with open("debian/tests/pkg-js/test", "w") as f: f.write("tape test.js\n") control.source["Build-Depends"] = ensure_some_version( control.source.get("Build-Depends", ''), "node-tape <!nocheck>") issue.report_fixed() report_result('Add autopkgtest for node.', certainty=CERTAINTY)
from lintian_brush.fixer import report_result, LintianIssue try: with Deb822Editor(path='debian/copyright') as updater: for paragraph in updater.paragraphs: if 'Files' not in paragraph: continue if ',' not in paragraph['Files']: continue if '{' in paragraph['Files']: # Bash-style expansion? continue issue = LintianIssue('source', 'comma-separated-files-in-dep5-copyright', 'paragraph at line XX') if issue.should_fix(): paragraph['Files'] = '\n' + '\n'.join( ' ' + entry.strip() for entry in paragraph['Files'].split(',')) issue.report_fixed() except FileNotFoundError: pass except ValueError: # Not a machine-readable copyright file pass report_result( "debian/copyright: Replace commas with whitespace to separate items " "in Files paragraph.")
#!/usr/bin/python3 from debmutate.control import ( get_relation, add_dependency, drop_dependency, ) from lintian_brush.fixer import control, report_result with control as updater: try: pos, old = get_relation(updater.source.get('Build-Depends-Indep', ''), 'debhelper-compat') except KeyError: pass else: updater.source['Build-Depends'] = add_dependency( updater.source.get('Build-Depends', ''), old) updater.source['Build-Depends-Indep'] = drop_dependency( updater.source.get('Build-Depends-Indep', ''), 'debhelper-compat') if not updater.source['Build-Depends-Indep'].strip(): del updater.source['Build-Depends-Indep'] report_result( 'Move debhelper-compat from Build-Depends-Indep to Build-Depends.')
#!/usr/bin/python3 from lintian_brush.fixer import control, report_result, fixed_lintian_tag from lintian_brush.vcs import determine_browser_url with control as updater: if "Vcs-Browser" not in updater.source: try: vcs_git = updater.source["Vcs-Git"] except KeyError: pass else: browser_url = determine_browser_url('git', vcs_git) if browser_url is not None: updater.source["Vcs-Browser"] = browser_url fixed_lintian_tag(updater.source, 'missing-vcs-browser-field', info='Vcs-Git %s' % vcs_git) report_result("debian/control: Add Vcs-Browser field")
if field in valid_field_names: continue for option in valid_field_names: if distance(field, option) == 1: value = paragraph[field] del paragraph[field] paragraph[option] = value if option.lower() == field.lower(): case_fixed.add((field, option)) else: typo_fixed.add((field, option)) break except FileNotFoundError: sys.exit(0) if case_fixed: kind = 'case' + ('s' if len(case_fixed) > 1 else '') else: kind = '' if typo_fixed: if case_fixed: kind += ' and ' kind += 'typo' + ('s' if len(typo_fixed) > 1 else '') fixed_str = ', '.join( ['%s => %s' % (old, new) for (old, new) in sorted(list(case_fixed) + list(typo_fixed))]) report_result( 'Fix field name %s in debian/tests/control (%s).' % (kind, fixed_str))
#!/usr/bin/python3 from lintian_brush.fixer import report_result, fixed_lintian_tag from lintian_brush.systemd import (systemd_service_files, SystemdServiceEditor, Undefined) for path in systemd_service_files(): with SystemdServiceEditor(path) as updater: old_pidfile = updater.file['Service']['PIDFile'] if isinstance(old_pidfile, str): new_pidfile = old_pidfile.replace("/var/run/", "/run/") updater.file['Service']['PIDFile'] = new_pidfile fixed_lintian_tag('source', 'systemd-service-file-refers-to-var-run', (path, 'PIDFile', old_pidfile)) for key in updater.file['Service']: val = updater.file['Service'][key] if isinstance(old_pidfile, Undefined) or old_pidfile not in val: continue updater.file['Service'][key] = val.replace(old_pidfile, new_pidfile) report_result("Replace /var/run with /run for the Service PIDFile.")
#!/usr/bin/python3 from lintian_brush.fixer import control, report_result, LintianIssue with control as updater: for key in list(updater.source): if not key.startswith('XS-Vcs-'): continue issue = LintianIssue(updater.source, 'adopted-extended-field', info=key) if not issue.should_fix(): continue updater.source[key[3:]] = updater.source[key] del updater.source[key] issue.report_fixed() report_result( "Remove unnecessary XS- prefix for Vcs- fields in debian/control.")
#!/usr/bin/python3 from debmutate.control import drop_dependency from lintian_brush.fixer import control, report_result, LintianIssue with control as updater: for field in ['Build-Depends', 'Build-Depends-Indep']: try: updater.source[field] = drop_dependency(updater.source[field], "build-essential") except KeyError: pass else: issue = LintianIssue(updater.source, 'build-depends-on-build-essential', field) # TODO(jelmer): Check overrides issue.report_fixed() report_result("Drop unnecessary dependency on build-essential.")
if not has_keys and needed_keys: issue = LintianIssue('source', 'debian-watch-file-pubkey-file-is-missing', ()) if issue.should_fix(): if not os.path.isdir('debian/upstream'): os.mkdir('debian/upstream') with open('debian/upstream/signing-key.asc', 'wb') as f: missing_keys = [] for fpr in needed_keys: key = c.key_export_minimal(fpr) if not key: missing_keys.append(fpr) f.write(key) if missing_keys: fetch_keys(missing_keys) for fpr in missing_keys: key = c.key_export_minimal(fpr) if not key: warn('Unable to export key %s' % (fpr, )) sys.exit(0) f.write(key) issue.report_fixed() if description is None: description = "Add upstream signing keys (%s)." % ( ', '.join(missing_keys)) if description: report_result(description)
value = paragraph[field] del paragraph[field] paragraph[option] = value if option.lower() == field.lower(): case_fixed.add((field, option)) else: typo_fixed.add((field, option)) fixed_lintian_tag( 'source', 'field-name-typo-in-dep5-copyright', '%s (line XX)' % field) break except FileNotFoundError: pass if case_fixed: kind = 'case' + ('s' if len(case_fixed) > 1 else '') else: kind = '' if typo_fixed: if case_fixed: kind += ' and ' kind += 'typo' + ('s' if len(typo_fixed) > 1 else '') fixed_str = ', '.join([ '%s => %s' % (old, new) for (old, new) in sorted(list(case_fixed) + list(typo_fixed)) ]) report_result('Fix field name %s in debian/copyright (%s).' % (kind, fixed_str))
with CopyrightEditor() as updater: for paragraph in updater.copyright.all_files_paragraphs(): files: List[str] = list() # access the private member because of #960278 for f in paragraph._RestrictedWrapper__data['Files'].splitlines(): if re_license.search(f.strip()): deleted.add(f.strip()) fixed_lintian_tag( 'source', 'license-file-listed-in-debian-copyright', info=f.strip()) else: if files: files.append(f) else: # First line, should not start with whitespaces. files.append(f.strip()) files_entry = "\n".join(files) if not files_entry.strip(): updater.remove(paragraph) elif files_entry != paragraph._RestrictedWrapper__data['Files']: paragraph._RestrictedWrapper__data['Files'] = files_entry if not deleted: sys.exit(0) except (FileNotFoundError, NotMachineReadableError): pass else: report_result( message % ', '.join(sorted(deleted)), certainty=certainty)
with control as updater: for para in updater.paragraphs: for k, v in para.items(): if not v.strip(): if para.get("Package"): issue = LintianIssue(updater.source, 'debian-control-has-empty-field', info='field "%s" in package %s' % (k, para['Package'])) if not issue.should_fix(): continue issue.report_fixed() packages.append(para["Package"]) else: issue = LintianIssue( updater.source, 'debian-control-has-empty-field', info='field "%s" in source paragraph' % (k, )) if not issue.should_fix(): continue issue.report_fixed() fields.append(k) del para[k] report_result("debian/control: Remove empty control field%s %s%s." % ( "s" if len(fields) > 1 else "", ", ".join(fields), (" in package %s" % ', '.join(packages)) if packages else "", ))
#!/usr/bin/python3 import os import sys from lintian_brush.fixer import ( opinionated, report_result, ) if not opinionated(): sys.exit(0) try: with open('debian/patches/series', 'r') as f: if not f.read().strip(): os.unlink('debian/patches/series') except FileNotFoundError: pass report_result('Remove empty debian/patches/series.')
#!/usr/bin/python3 from debmutate.copyright import CopyrightEditor, NotMachineReadableError from lintian_brush.fixer import report_result, LintianIssue try: with CopyrightEditor() as editor: files_i = 0 for i, paragraph in enumerate(editor.copyright.all_files_paragraphs()): if "Files" in paragraph: if paragraph["Files"] == "*" and files_i > 0: issue = LintianIssue( 'source', 'global-files-wildcard-not-first-paragraph-in-' 'dep5-copyright') if issue.should_fix(): editor.insert(0, editor.pop(i)) issue.report_fixed() files_i += 1 except (FileNotFoundError, NotMachineReadableError): pass report_result('Make "Files: *" paragraph the first in the copyright file.')
for name in extra_defined: for paragraph in updater.copyright.all_paragraphs(): if not paragraph.license: continue if paragraph.license.synopsis == name: continue if paragraph.license.text and name in paragraph.license.text: certainty = 'possible' if paragraph.comment and name in paragraph.comment: certainty = 'possible' if extra_defined and not extra_used: for paragraph in list(updater.copyright.all_paragraphs()): if not paragraph.license: continue issue = LintianIssue( 'source', 'unused-license-paragraph-in-dep5-copyright', info=paragraph.license.synopsis.lower()) if not issue.should_fix(): continue if paragraph.license.synopsis in extra_defined: issue.report_fixed() updater.remove(paragraph) except (FileNotFoundError, NotMachineReadableError): pass else: report_result( "Remove unused license definitions for %s." % ', '.join(extra_defined), certainty=certainty)
#!/usr/bin/python3 import sys from debmutate.control import drop_dependency from lintian_brush.fixer import control, report_result, fixed_lintian_tag try: with open('debian/rules', 'rb') as f: for line in f: if b'/usr/share/cdbs/' in line: uses_cdbs = True break else: uses_cdbs = False except FileNotFoundError: # Unsure whether it actually needs cdbs sys.exit(2) if not uses_cdbs: with control as updater: new_depends = drop_dependency(updater.source.get("Build-Depends", ""), "cdbs") if new_depends != updater.source['Build-Depends']: fixed_lintian_tag(updater.source, 'unused-build-dependency-on-cdbs', '') updater.source["Build-Depends"] = new_depends if not updater.source["Build-Depends"]: del updater.source["Build-Depends"] report_result("Drop unused build-dependency on cdbs.")
#!/usr/bin/python3 import os from lintian_brush.fixer import report_result, fixed_lintian_tag removed = [] for name in os.listdir('debian'): if name.endswith('.linda-overrides'): os.unlink(os.path.join('debian', name)) removed.append(name) fixed_lintian_tag( 'source', 'package-contains-linda-override', 'usr/share/linda/overrides/%s' % name[:-len('.linda-overrides')]) report_result('Remove obsolete linda overrides: ' + ', '.join(removed))
def wrap_block(changelog, i): new_changes = wrap_block_lines(changelog[i].changes()) if new_changes != changelog[i].changes(): if i == 0: for lineno, change in enumerate(changelog[i].changes(), 2): if len(change) <= WIDTH: continue # Lintian only warns about the first block. fixed_lintian_tag( 'source', 'debian-changelog-line-too-long', info='line %d' % lineno) changelog[i]._changes = new_changes updated.append(changelog[i].version) return True return False with ChangelogEditor() as updater: if 'CHANGELOG_THOROUGH' not in os.environ: wrap_block(updater.changelog, 0) else: for i in range(len(updater.changelog)): wrap_block(updater.changelog, i) report_result( 'Wrap long lines in changelog entries: %s.' % ( ', '.join([str(v) for v in updated])))