def drop_arch_line(line): global architecture_included if line.strip() == INCLUDE_LINE: architecture_included = True return line (variable, variable_matches, value_matches, hard) = is_dpkg_architecture_line(line) if not variable_matches: return line if not value_matches: # The assignment is different; let's include # the mk file *before* this assignment if not architecture_included and variable_matches: architecture_included = True return [INCLUDE_LINE, line] else: return line if hard: lineno = -1 # TODO(jelmer): Pass this up fixed_lintian_tag('source', 'debian-rules-sets-dpkg-architecture-variable', info='%s (line %d)' % (variable, lineno)) if not architecture_included: architecture_included = True return INCLUDE_LINE return None
def replace_set_e(path): lines = [] try: with open(path, 'rb') as f: lines = list(f.readlines()) except (FileNotFoundError, IsADirectoryError): return if b'set -e\n' in lines: return # TODO(jelmer): Handle -e in combination with other flags. if lines[0] != b"#!/bin/sh -e\n": return lines[0] = b'#!/bin/sh\n' for i, line in enumerate(lines[1:]): if line == b'set -e\n': return if (not (line.startswith(b'#') or line == b'\n') or line.strip() == b'#DEBHELPER#'): fixed_lintian_tag( 'source', 'maintainer-script-without-set-e', os.path.basename(path)) if not lines[i-1].strip(): lines.insert(i, b'set -e\n') lines.insert(i+1, b'\n') else: lines.insert(i, b'\n') lines.insert(i+1, b'set -e\n') break with open(path, 'wb') as f: f.writelines(lines)
def update_assignment_kind(line): global architecture_included, message if line.strip() == INCLUDE_LINE: architecture_included = True return line (variable, variable_matches, value_matches, hard) = is_dpkg_architecture_line(line) if not variable_matches: return line if not value_matches: return line if not hard: if architecture_included: return None else: # 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)
def fix_close_colon(package, m): global certainty bugno = int(m.group('bug')) (valid, bug_certainty) = check_bug(package, bugno) if meets_minimum_certainty(bug_certainty) and valid: certainty = min_certainty([certainty, bug_certainty]) fixed_lintian_tag( 'all', "possible-missing-colon-in-closes", info='%s #%d' % (m.group('closes'), bugno)) return '%s: #%d' % (m.group('closes'), bugno) else: return m.group(0)
def fix_close_typo(package, m): global certainty bugno = int(m.group('bug')) (valid, bug_certainty) = check_bug(package, bugno) if meets_minimum_certainty(bug_certainty) and valid: certainty = min_certainty([certainty, bug_certainty]) fixed_lintian_tag( 'all', 'misspelled-closes-bug', info='#%s' % m.group('bug')) return '%ss: #%s' % (m.group('close'), m.group('bug')) else: return m.group(0)
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
def migrate_from_obsolete_infra(control): vcs_type, vcs_url, unused_subpath = get_vcs_info(control) if vcs_type is None: return if not is_on_obsolete_host(vcs_url): return package = control["Source"] maintainer_email = parseaddr(control["Maintainer"])[1] old_vcs_browser = control.get('Vcs-Browser') old_vcs_type = vcs_type old_vcs_url = vcs_url try: (vcs_type, vcs_url, vcs_browser) = find_new_urls( vcs_type, vcs_url, package, maintainer_email, net_access=net_access_allowed()) except NewRepositoryURLUnknown: return fixed_lintian_tag( 'source', "vcs-obsolete-in-debian-infrastructure", info='vcs-%s %s' % (old_vcs_type.lower(), old_vcs_url)) if (("Vcs-Cvs" in control and re.match( r"\@(?:cvs\.alioth|anonscm)\.debian\.org:/cvsroot/", control["Vcs-Cvs"])) or ("Vcs-Svn" in control and "viewvc" in control["Vcs-Browser"])): fixed_lintian_tag( 'source', "vcs-field-bitrotted", info='%s %s' % (old_vcs_url or '', old_vcs_browser or '')) for hdr in ["Vcs-Git", "Vcs-Bzr", "Vcs-Hg", "Vcs-Svn"]: if hdr == "Vcs-" + vcs_type: continue try: del control[hdr] except KeyError: pass control["Vcs-" + vcs_type] = vcs_url if vcs_browser is not None: control["Vcs-Browser"] = vcs_browser else: try: del control["Vcs-Browser"] except KeyError: pass
def replace_symlink_path(synopsis, m): path = m.group(0) waslink = os.path.islink(path) synopsis = SYNOPSIS_ALIAS.get(synopsis, synopsis) newpath = '/usr/share/common-licenses/' + synopsis.rstrip('+') if not os.path.exists(newpath) or os.path.islink(newpath): 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
def obsolete_field(copyright): for (old_name, new_name, field_name, multi_line) in renames: try: value = copyright.header[old_name] except KeyError: pass else: if value: if multi_line: setattr(copyright.header, field_name, getattr(copyright.header, field_name) + (value, )) else: setattr(copyright.header, field_name, value) applied_renames.append((old_name, new_name)) del copyright.header[old_name] fixed_lintian_tag('source', 'obsolete-field-in-dep5-copyright', info='%s %s' % (old_name, new_name))
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)]))
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)
def replace_full_license(para): license = para.license license_matched = find_common_license_from_fulltext(license.text) if license_matched is None: if os.path.exists(os.path.join(COMMON_LICENSES_DIR, license.synopsis)): warn('A common license shortname (%s) is used, but license ' 'text not recognized.' % license.synopsis) return # The full license text was found. Replace it with a blurb. canonical_id = canonical_license_id(license.synopsis) for shortname, blurb in _BLURB.items(): if canonical_id == canonical_license_id(shortname): break else: if license.synopsis in SPDX_RENAMES: renames[license.synopsis] = SPDX_RENAMES[license.synopsis] return else: warn('Found full license text for %s, but unknown synopsis %s (%s)' % (license_matched, license.synopsis, canonical_id)) return if license_matched == 'Apache-2.0': fixed_lintian_tag('source', 'copyright-file-contains-full-apache-2-license', info=()) if license_matched.startswith('GFDL-'): fixed_lintian_tag('source', 'copyright-file-contains-full-gfdl-license', info=()) if license_matched.startswith('GPL-'): fixed_lintian_tag('source', 'copyright-file-contains-full-gpl-license', info=()) para.license = License(license.synopsis, blurb) return license_matched
def reference_common_license(para): license = para.license common_license = find_common_license_from_blurb(license.text) if not common_license: return if COMMON_LICENSES_DIR in license.text: return if para.comment is not None and COMMON_LICENSES_DIR in para.comment: return if 'X-Comment' in para and COMMON_LICENSES_DIR in para['X-Comment']: return if 'License-Reference' in para: return para.license = License( license.synopsis, license.text + '\n\n' + debian_file_reference( FULL_LICENSE_NAME.get(common_license, common_license), common_license)) if common_license in ('Apache-2.0', 'Apache-2'): fixed_lintian_tag('source', 'copyright-not-using-common-license-for-apache2', info=()) elif common_license.startswith('GPL-'): fixed_lintian_tag('source', 'copyright-not-using-common-license-for-gpl', info=()) elif common_license.startswith('LGPL-'): fixed_lintian_tag('source', 'copyright-not-using-common-license-for-lgpl', info=()) elif common_license.startswith('GFDL-'): fixed_lintian_tag('source', 'copyright-not-using-common-license-for-gfdl', info=()) fixed_lintian_tag('source', 'copyright-does-not-refer-to-common-license-file', info=()) if license.synopsis != common_license: renames[license.synopsis] = common_license return common_license
def fix_header_license_references(copyright): if not copyright.header.license: return if not copyright.header.license.text: return used_licenses = set() seen_licenses = set() for files_paragraph in copyright.all_files_paragraphs(): if not files_paragraph.license: continue used_licenses.add(files_paragraph.license.synopsis) if files_paragraph.license.text: seen_licenses.add(files_paragraph.license.synopsis) for license_paragraph in copyright.all_license_paragraphs(): seen_licenses.add(license_paragraph.license.synopsis) for missing in used_licenses - seen_licenses: if copyright.header.license.synopsis == missing: copyright.add_license_paragraph( LicenseParagraph.create(copyright.header.license)) fixed_lintian_tag('source', 'dep5-file-paragraph-references-header-paragraph', '%s (line XX)' % (copyright.header.license.synopsis, )) return copyright.header.license
'debian/upstream-signing-key.pgp' ]: if os.path.exists(p): outlines: List[bytes] = [] key: Optional[List[bytes]] = None with open(p, 'rb') as f: inlines = list(f.readlines()) for line in inlines: if line.strip() == KEY_BLOCK_START: key = [line] elif line.strip() == KEY_BLOCK_END and key is not None: key.append(line) try: outlines.extend( minimize_key_block(b''.join(key)).splitlines(True)) except GpgMissing: sys.exit(2) key = None elif key is not None: key.append(line) else: outlines.append(line) if key: raise Exception('Key block without end') if inlines != outlines: fixed_lintian_tag('source', 'public-upstream-key-not-minimal') with open(p, 'wb') as f: f.writelines(outlines) report_result("Re-export upstream signing key without extra signatures.")
certainty = 'certain' message = "Remove listed license files (%s) from copyright." # regex taken from /usr/share/lintian/checks/debian/copyright.pm re_license = re.compile(r'(^|/)(COPYING[^/]*|LICENSE)$') try: 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)
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)
from lintian_brush.fixer import control, report_result, fixed_lintian_tag compat_version = get_debhelper_compat_level() if compat_version is None or compat_version <= 11: # N/A sys.exit(0) added = set() # Add Pre-Depends: ${misc:Pre-Depends} iff: # - a package has both a init script and a (ubuntu | systemd) unit with control as updater: for binary in updater.binaries: name = binary["Package"] if not os.path.exists('debian/%s.init' % name): continue if not (os.path.exists('debian/%s.service' % name) or os.path.exists('debian/%s.upstart' % name)): continue if "${misc:Pre-Depends}" in binary.get("Pre-Depends", ""): continue binary["Pre-Depends"] = add_dependency(binary.get("Pre-Depends", ""), "${misc:Pre-Depends}") added.add(name) fixed_lintian_tag(updater.source, 'skip-systemd-native-flag-missing-pre-depends') report_result("Add missing Pre-Depends: ${misc:Pre-Depends} in %s." % ", ".join(sorted(added)))
#!/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")
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)])) editor.yaml.constructor.flatten_mapping = flatten_mapping try: with editor: if isinstance(editor.code, dict): pass elif isinstance(editor.code, list): if len(editor.code) == 1: editor.code = editor.code[0] fixed_lintian_tag('source', 'upstream-metadata-not-yaml-mapping') report_result( 'Use YAML mapping in debian/upstream/metadata.') elif all( [isinstance(m, dict) and len(m) == 1 for m in editor.code]): old = editor.code editor.code = {} for entry in old: editor.code.update(entry) fixed_lintian_tag( 'source', 'upstream-metadata-not-yaml-mapping') report_result( 'Use YAML mapping in debian/upstream/metadata.') except ruamel.yaml.composer.ComposerError: ranges = []
#!/usr/bin/python3 import os import sys from typing import Optional from debmutate.control import ensure_some_version from lintian_brush.fixer import control, report_result, fixed_lintian_tag, is_debcargo_package if is_debcargo_package(): sys.exit(0) format: Optional[str] try: with open('debian/source/format', 'r') as f: format = f.read().strip() except FileNotFoundError: format = None if format != '3.0 (quilt)' and os.path.exists('debian/patches/series'): with control as updater: updater.source['Build-Depends'] = ensure_some_version( updater.source.get('Build-Depends', ''), 'quilt') if updater.changed: fixed_lintian_tag(updater.source, 'quilt-series-but-no-build-dep') report_result('Add missing dependency on quilt.')
try: with Deb822Editor('debian/copyright') as updater: for paragraph in updater.paragraphs: for field in paragraph: if field in valid_field_names: continue if (field.startswith('X-') and field[2:] in valid_field_names): if field[2:] in paragraph: warn('Both %s and %s exist.' % (field, field[2:])) continue value = paragraph[field] del paragraph[field] paragraph[field[2:]] = value typo_fixed.add((field, field[2:])) fixed_lintian_tag('source', 'field-name-typo-in-dep5-copyright', '%s (line XX)' % field) 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)) fixed_lintian_tag( 'source', 'field-name-typo-in-dep5-copyright', '%s (line XX)' % field)
#!/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.systemd import systemd_service_files, SystemdServiceEditor from lintian_brush.fixer import report_result, fixed_lintian_tag for path in systemd_service_files(): with SystemdServiceEditor(path) as updater: unit = updater.file['Unit'] try: unit.rename('BindTo', 'BindsTo') except KeyError: pass else: fixed_lintian_tag( 'source', 'systemd-service-file-refers-to-obsolete-bindto', path) report_result('Rename BindTo key to BindsTo in systemd files.')
try: regexes = get_name_section_mappings() except FileNotFoundError: # lintian not installed? sys.exit(2) default_section = None fixed = [] with control as updater: default_section = updater.source.get("Section") for binary in updater.binaries: expected_section = find_expected_section(regexes, binary["Package"]) section = binary.get("Section", default_section) if expected_section and expected_section != section: fixed.append(('binary package %s' % binary["Package"], section, expected_section)) binary["Section"] = expected_section fixed_lintian_tag(binary, 'wrong-section-according-to-package-name', info='%s => %s' % (binary['Package'], expected_section)) # TODO(jelmer): If there is only a single binary package without section, just # set the section of the source package? report_result("Fix sections for %s." % ', '.join(['%s (%s => %s)' % v for v in fixed]), certainty=CERTAINTY)
from lintian_brush.fixer import ( package_is_native, report_result, fixed_lintian_tag, opinionated, ) import os import sys if not package_is_native(): # Nothing to do sys.exit(0) if not opinionated(): sys.exit(0) try: os.unlink('debian/upstream/metadata') except FileNotFoundError: sys.exit(0) else: fixed_lintian_tag('source', 'upstream-metadata-in-native-source', ()) if os.listdir('debian/upstream') == []: os.rmdir('debian/upstream') report_result( 'Remove debian/upstream/metadata in native source package.', certainty='certain')
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)
#!/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))
continue DEPRECATED_RESTRICTIONS.append(line.strip()) except FileNotFoundError: sys.exit(2) with Deb822Editor('debian/tests/control') as updater: for paragraph in updater.paragraphs: restrictions = paragraph.get('Restrictions', '').split(',') if restrictions == ['']: continue to_delete = [] for i, restriction in enumerate(list(restrictions)): if restriction.strip() in DEPRECATED_RESTRICTIONS: to_delete.append(restriction.strip()) fixed_lintian_tag( 'source', 'obsolete-runtime-tests-restriction', '%s in line XX' % restriction.strip()) if to_delete: removed_restrictions.extend(to_delete) paragraph['Restrictions'] = delete_from_list( paragraph['Restrictions'], to_delete) if not paragraph['Restrictions'].strip(): del paragraph['Restrictions'] if 'needs-recommends' in removed_restrictions: # This is Certainty: possible, since the package may actually rely on the # (direct? indirect?) recommends, in which case we'd want to add them to # Depends. certainty = 'possible' else: