pkgsrc-Changes archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

CVS commit: pkgsrc/pkgtools/url2pkg



Module Name:    pkgsrc
Committed By:   rillig
Date:           Thu Oct  3 09:37:41 UTC 2019

Modified Files:
        pkgsrc/pkgtools/url2pkg: Makefile PLIST
        pkgsrc/pkgtools/url2pkg/files: url2pkg.8
Added Files:
        pkgsrc/pkgtools/url2pkg/files: url2pkg.py url2pkg_test.py

Log Message:
pkgtools/url2pkg: update to 19.3.1

Changes since 19.3.0:

* Added an alternative url2pkg implementation in Python that will
  eventually replace the Perl implementation. Reasons are:

  * Perl is cumbersome to type with all the special characters
  * Perl does not even have a Boolean type, after all the years
  * Perl cannot check the number of arguments passed to subs
  * Python does not suffer from the above limitations
  * Python is available on as many platforms as Perl

* The two implementations will be kept in sync until all the features
  have made it into the Python implementation, and everything has
  automatic tests.

* Added the -v or --verbose command line option.


To generate a diff of this commit:
cvs rdiff -u -r1.102 -r1.103 pkgsrc/pkgtools/url2pkg/Makefile
cvs rdiff -u -r1.5 -r1.6 pkgsrc/pkgtools/url2pkg/PLIST
cvs rdiff -u -r1.12 -r1.13 pkgsrc/pkgtools/url2pkg/files/url2pkg.8
cvs rdiff -u -r0 -r1.1 pkgsrc/pkgtools/url2pkg/files/url2pkg.py \
    pkgsrc/pkgtools/url2pkg/files/url2pkg_test.py

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: pkgsrc/pkgtools/url2pkg/Makefile
diff -u pkgsrc/pkgtools/url2pkg/Makefile:1.102 pkgsrc/pkgtools/url2pkg/Makefile:1.103
--- pkgsrc/pkgtools/url2pkg/Makefile:1.102      Tue Oct  1 19:41:23 2019
+++ pkgsrc/pkgtools/url2pkg/Makefile    Thu Oct  3 09:37:40 2019
@@ -1,6 +1,6 @@
-# $NetBSD: Makefile,v 1.102 2019/10/01 19:41:23 rillig Exp $
+# $NetBSD: Makefile,v 1.103 2019/10/03 09:37:40 rillig Exp $
 
-PKGNAME=       url2pkg-19.3.0
+PKGNAME=       url2pkg-19.3.1
 CATEGORIES=    pkgtools
 
 MAINTAINER=    rillig%NetBSD.org@localhost
@@ -15,24 +15,27 @@ USE_LANGUAGES=      # none
 USE_TOOLS+=    perl:run
 AUTO_MKDIRS=   yes
 
+TEST_DEPENDS+= ${PYPKGPREFIX}-test>=0:../../devel/py-test
+
 do-extract:
        ${RUN} cd ${FILESDIR} && cp *.* ${WRKSRC}/
 
 do-test:
        ${RUN} cd ${WRKSRC} && env PKGSRCDIR=${PKGSRCDIR} perl -I. url2pkg.t
+       ${RUN} cd ${WRKSRC} && env PKGSRCDIR=${PKGSRCDIR} ${PREFIX}/bin/pytest-${PYVERSSUFFIX}
 
 .include "../../mk/bsd.prefs.mk"
 
 SUBST_CLASSES+=                up
 SUBST_STAGE.up=                do-configure
 SUBST_MESSAGE.up=      Replacing variable placeholders
-SUBST_FILES.up=                url2pkg.pl MakeMaker.pm
-SUBST_VARS.up=         MAKE PERL5 PYTHONBIN
-SUBST_SED.up+=         -e 's,@LIBDIR@,${PREFIX}/lib/url2pkg,g'
-SUBST_SED.up+=         -e 's,@PKGSRCDIR@,${BATCH:D/usr/pkgsrc:U${PKGSRCDIR}},g'
+SUBST_FILES.up=                url2pkg.pl MakeMaker.pm url2pkg.py
+SUBST_VARS.up=         MAKE PERL5 PKGSRCDIR PYTHONBIN
+SUBST_SED.up=          -e 's,@LIBDIR@,${PREFIX}/lib/url2pkg,g'
 
 do-install:
        ${INSTALL_SCRIPT} ${WRKSRC}/url2pkg.pl ${DESTDIR}${PREFIX}/bin/url2pkg
+       ${INSTALL_SCRIPT} ${WRKSRC}/url2pkg.py ${DESTDIR}${PREFIX}/bin/url2pkg-py
        ${INSTALL_MAN} ${FILESDIR}/url2pkg.8 ${DESTDIR}${PREFIX}/${PKGMANDIR}/man8
        ${INSTALL_DATA} ${WRKSRC}/Build.pm ${DESTDIR}${PREFIX}/lib/url2pkg/Module/
        ${INSTALL_DATA} ${WRKSRC}/MakeMaker.pm ${DESTDIR}${PREFIX}/lib/url2pkg/ExtUtils/

Index: pkgsrc/pkgtools/url2pkg/PLIST
diff -u pkgsrc/pkgtools/url2pkg/PLIST:1.5 pkgsrc/pkgtools/url2pkg/PLIST:1.6
--- pkgsrc/pkgtools/url2pkg/PLIST:1.5   Thu Sep 12 18:23:00 2019
+++ pkgsrc/pkgtools/url2pkg/PLIST       Thu Oct  3 09:37:40 2019
@@ -1,5 +1,6 @@
-@comment $NetBSD: PLIST,v 1.5 2019/09/12 18:23:00 rillig Exp $
+@comment $NetBSD: PLIST,v 1.6 2019/10/03 09:37:40 rillig Exp $
 bin/url2pkg
+bin/url2pkg-py
 lib/url2pkg/ExtUtils/MakeMaker.pm
 lib/url2pkg/Module/Build.pm
 lib/url2pkg/setuptools.py

Index: pkgsrc/pkgtools/url2pkg/files/url2pkg.8
diff -u pkgsrc/pkgtools/url2pkg/files/url2pkg.8:1.12 pkgsrc/pkgtools/url2pkg/files/url2pkg.8:1.13
--- pkgsrc/pkgtools/url2pkg/files/url2pkg.8:1.12        Wed Aug 22 20:48:38 2018
+++ pkgsrc/pkgtools/url2pkg/files/url2pkg.8     Thu Oct  3 09:37:41 2019
@@ -1,6 +1,6 @@
-.\"    $NetBSD: url2pkg.8,v 1.12 2018/08/22 20:48:38 maya Exp $
+.\"    $NetBSD: url2pkg.8,v 1.13 2019/10/03 09:37:41 rillig Exp $
 .\"
-.\" Copyright (c) 2001 The NetBSD Foundation, Inc.
+.\" Copyright (c) 2001, 2019 The NetBSD Foundation, Inc.
 .\" All rights reserved.
 .\"
 .\" This code is derived from software contributed to The NetBSD Foundation
@@ -27,7 +27,7 @@
 .\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 .\" POSSIBILITY OF SUCH DAMAGE.
 .\"
-.Dd May 29, 2013
+.Dd Oct 3, 2019
 .Dt URL2PKG 8
 .Os
 .Sh NAME
@@ -35,6 +35,7 @@
 .Nd Automatic pkgsrc package generator
 .Sh SYNOPSIS
 .Nm
+.Op Fl v|--verbose
 .Op Ar URL
 .Sh DESCRIPTION
 .Nm

Added files:

Index: pkgsrc/pkgtools/url2pkg/files/url2pkg.py
diff -u /dev/null pkgsrc/pkgtools/url2pkg/files/url2pkg.py:1.1
--- /dev/null   Thu Oct  3 09:37:41 2019
+++ pkgsrc/pkgtools/url2pkg/files/url2pkg.py    Thu Oct  3 09:37:41 2019
@@ -0,0 +1,859 @@
+#! @PYTHONBIN@
+# $NetBSD: url2pkg.py,v 1.1 2019/10/03 09:37:41 rillig Exp $
+
+# Copyright (c) 2019 The NetBSD Foundation, Inc.
+# All rights reserved.
+#
+# This code is derived from software contributed to The NetBSD Foundation
+# by Roland Illig.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+import getopt
+import glob
+import os
+import re
+import subprocess
+import sys
+from os.path import isfile
+from typing import Callable, Dict, Iterator, List, Optional, Sequence, Union
+
+
+def cvsid(fmt: str) -> str:
+    return fmt % ('$' + 'NetBSD$')
+
+
+class Config:
+
+    def __init__(self):
+        self.make = '@MAKE@'
+        self.libdir = '@LIBDIR@'
+        self.perl5 = '@PERL5@'
+        self.pkgsrcdir = '@PKGSRCDIR@'
+        self.pythonbin = '@PYTHONBIN@'
+        self.verbose = False
+
+
+config = Config()
+distname = ''
+
+
+def debug(fmt: str, *args):
+    if config.verbose:
+        msg = fmt % map(repr, args)
+        sys.stderr.write('url2pkg: %s\n' % msg)
+
+
+def run_editor(fname: str, lineno: int):
+    editor = os.getenv('PKGEDITOR') or os.getenv('EDITOR') or 'vi'
+
+    args: List[str] = [editor]
+    if re.search(editor, r'(^|/)(mcedit|nano|pico|vi|vim)$'):
+        args.append(f'+{lineno}')
+    args.append(fname)
+
+    code = subprocess.check_call(args)
+
+
+def generate_initial_package_Makefile_lines(url):
+    global distname
+
+    master_site = ''
+    master_sites = ''
+    distfile = ''
+    homepage = ''
+    extract_sufx = ''
+    categories = ''
+    github_project = ''
+    github_release = ''
+    dist_subdir = ''
+    pkgname_prefix = ''
+    pkgname_transform = ''
+
+    with open('../../mk/fetch/sites.mk') as sites_mk:
+        for line in sites_mk:
+            m = re.search(r'^(MASTER_SITE_.*)\+=', line)
+            if m:
+                master_site = m[1]
+                continue
+
+            m = re.search(r'^\t(.*?)(?:\s+\\)?$', line)
+            if not m:
+                continue
+
+            site = m[1]
+            if not url.startswith(site):
+                continue
+
+            rest = url[len(site):]
+            m = re.search(r'^(.+)/([^/]+)$', rest)
+            if not m:
+                master_sites = "${%s}" % master_site
+                continue
+
+            subdir, distfile = m.groups()
+
+            master_sites = '${%s:=%s/}' % (master_site, subdir)
+            if master_site == 'MASTER_SITE_SOURCEFORGE':
+                homepage = f'https://{subdir}.sourceforge.net/'
+            elif master_site == 'MASTER_SITE_GNU':
+                homepage = f'https://www.gnu.org/software/{subdir}/'
+            else:
+                homepage = url[:-len(distfile)]
+
+    m = re.search(r'^https://downloads\.sourceforge\.net/project/([^/?]+)/[^?]+/([^/?]+)(?:[?].*)?$', url)
+    if m:
+        project, filename = m.groups()
+
+        master_sites = '${MASTER_SITE_SOURCEFORGE:=%s/}' % project
+        homepage = 'https://%s.sourceforge.net/' % project
+        distfile = filename
+
+    m = re.search(r'^https://github\.com/(.+)/(.+)/archive/(.+)(\.tar\.gz|\.zip)$', url)
+    if m:
+        org, proj, tag, ext = m.groups()
+
+        github_project = proj
+        master_sites = '${MASTER_SITE_GITHUB:=%s/}' % org
+        homepage = 'https://github.com/%s/%s/' % (org, proj)
+        if github_project not in tag:
+            pkgname_prefix = '${GITHUB_PROJECT}-'
+            dist_subdir = '${GITHUB_PROJECT}'
+        distfile = tag + ext
+
+    m = re.search(r'^https://github\.com/(.+)/(.+)/releases/download/(.+)/(.+)(\.tar\.gz|\.zip)$', url)
+    if m:
+        org, proj, tag, base, ext = m.groups()
+
+        github_project = proj
+        master_sites = '${MASTER_SITE_GITHUB:=%s/}' % org
+        homepage = 'https://github.com/%s/%s/' % (org, proj)
+        if proj not in base:
+            github_project = proj
+            dist_subdir = '${GITHUB_PROJECT}'
+        github_release = '${DISTNAME}' if tag == base else tag
+        distfile = base + ext
+
+    if master_sites == '':
+        m = re.search(r'^(.*/)(.*)$', url)
+        master_sites = m[1]
+        distfile = m[2]
+        homepage = master_sites
+
+    m = re.search(r'^(.*?)((?:\.tar)?\.\w+)$', distfile)
+    if m:
+        distname, extract_sufx = m.groups()
+    else:
+        distname, extract_sufx = distfile, '# none'
+
+    m = re.search(r'^v\d', distname)
+    if m:
+        pkgname_transform = ':S,^v,,'
+    elif re.search(r'-v\d', distname) and not re.search(r'-v.*-v\d', distname):
+        pkgname_transform = ':S,-v,-,'
+
+    main_category = re.search(r'.*/([^/]+)/[^/]+$', os.getcwd())[1]
+
+    categories = main_category if main_category != 'wip' else '# TODO: add primary category'
+
+    if extract_sufx == '.tar.gz' or extract_sufx == '.gem':
+        extract_sufx = ''
+
+    pkgname = '%s${DISTNAME%s}' % (pkgname_prefix, pkgname_transform)
+    if pkgname == '${DISTNAME}':
+        pkgname = ''
+
+    maintainer = \
+        os.getenv('PKGMAINTAINER') or os.getenv('REPLYTO') \
+        or 'INSERT_YOUR_MAIL_ADDRESS_HERE'
+
+    lines = Lines()
+    lines.add(cvsid('# %s'))
+    lines.add('')
+
+    lines.add_vars(
+        Var('GITHUB_PROJECT', '=', github_project),
+        Var('DISTNAME', '=', distname),
+        Var('PKGNAME', '=', pkgname),
+        Var('CATEGORIES', '=', categories),
+        Var('MASTER_SITES', '=', master_sites),
+        Var('GITHUB_RELEASE', '=', github_release),
+        Var('EXTRACT_SUFX', '=', extract_sufx),
+        Var('DIST_SUBDIR', '=', dist_subdir),
+    )
+
+    lines.add_vars(
+        Var('MAINTAINER', '=', maintainer),
+        Var('HOMEPAGE', '=', homepage),
+        Var('COMMENT', '=', 'TODO: Short description of the package'),
+        Var('#LICENSE', '=', '# TODO: (see mk/license.mk)'),
+    )
+
+    lines.add('# url2pkg-marker (please do not remove this line.)')
+    lines.add('.include "../../mk/bsd.pkg.mk"')
+
+    return lines
+
+
+def generate_initial_package(url):
+    try:
+        os.rename('Makefile', 'Makefile-url2pkg.bak')
+    except OSError:
+        pass
+    generate_initial_package_Makefile_lines(url).write_to('Makefile')
+    Lines(cvsid('@comment %s')).write_to('PLIST')
+    Lines().write_to('DESCR')
+    run_editor('Makefile', 5)
+
+    bmake('distinfo')
+    bmake('extract')
+
+
+class Var:
+    """ A variable assignment for the package Makefile """
+
+    def __init__(self, name: str, op: str, value: str):
+        self.name = name
+        self.op = op
+        self.value = value
+
+
+def aligned(vars: List[Var]) -> List[str]:
+    relevant = list(filter(lambda v: v.value != '', vars))
+    if not relevant:
+        return []
+
+    def tabwidth(var: Var) -> int:
+        return (len(var.name) + len(var.op) + len('\t') + 7) // 8 * 8
+
+    width = max(map(tabwidth, relevant), default=0)
+
+    aligned_lines = []
+    for var in relevant:
+        tabs = '\t' * ((width - len(var.name) - len(var.op) + 7) // 8)
+        aligned_lines.append(var.name + var.op + tabs + var.value)
+
+    aligned_lines.append('')
+    return aligned_lines
+
+
+class Varassign:
+
+    def __init__(self, index: int, varname: str, op: str, indent: str,
+                 value: str, space_after_value: str, comment: str):
+        self.index = index
+        self.varname = varname
+        self.op = op
+        self.indent = indent
+        self.value = value
+        self.space_after_value = space_after_value
+        self.comment = comment
+
+
+def find_package(pkgbase: str) -> str:
+    candidates = glob.glob(config.pkgsrcdir + '/*/' + pkgbase)
+    debug('candidates for package %s are %s', pkgbase, candidates)
+    if len(candidates) != 1:
+        return ''
+    return candidates[0].replace(config.pkgsrcdir, '../..')
+
+
+def bmake(*args: str) -> None:
+    debug('running bmake %s', args)
+    subprocess.check_call([config.make] + list(args))
+
+
+def show_var(varname: str) -> str:
+    output = subprocess.check_output((config.make, 'show-var', 'VARNAME=' + varname))
+    return output.decode('utf-8').strip()
+
+
+class Lines:
+
+    def __init__(self, *lines: str) -> None:
+        self.lines = []
+        for line in lines:
+            self.add(line)
+
+    @classmethod
+    def read_from(cls, filename: str) -> 'Lines':
+        pass
+
+        lines = Lines()
+        with open(filename) as f:
+            for line in f:
+                lines.add(line.rstrip('\n'))
+        return lines
+
+    def write_to(self, filename: str):
+        with open(f'{filename}.tmp', 'w') as f:
+            f.writelines(line + '\n' for line in self.lines)
+        try:
+            os.remove(filename)
+        except OSError:
+            pass
+        os.rename(f'{filename}.tmp', filename)
+
+    def add(self, *lines: Sequence[str]):
+        for line in lines:
+            assert type(line) == str, type(line)
+            self.lines.append(line)
+
+    def add_vars(self, *vars: Var):
+        """
+        Appends the given variable assignments to the lines, aligning the
+        variable values vertically.
+        """
+        width = 0
+        for var in vars:
+            if var.value != '':
+                name_op_len = (len(var.name) + len(var.op) + len('\t') + 7) // 8 * 8
+                width = max(width, name_op_len)
+
+        if width == 0:
+            return
+
+        for var in vars:
+            if var.value != '':
+                tabs = (width - len(var.name) - len(var.op) + 7) // 8
+                self.add(var.name + var.op + '\t' * tabs + var.value)
+        self.add('')
+
+    def unique_varassign(self, varname: str) -> Optional[Varassign]:
+        varassigns = self.all_varassigns(varname)
+        return varassigns[0] if len(varassigns) == 1 else None
+
+    def all_varassigns(self, varname: str) -> Sequence[Varassign]:
+        varassigns = []
+        for (i, line) in enumerate(self.lines):
+            m = re.search(r'^(#?[\w+\-]+?)([!+:?]?=)([ \t]*)([^#\\]*?)(\s*)(#.*|)$', line)
+            if m and m[1] == varname:
+                varassigns.append(Varassign(i, m[1], m[2], m[3], m[4], m[5], m[6]))
+        return varassigns
+
+    def set(self, varname: str, new_value: str) -> bool:
+        """ Changes the value of an existing variable in the lines. """
+
+        varassign = self.unique_varassign(varname)
+        if varassign is not None:
+            self.lines[varassign.index] = varassign.varname + varassign.op + varassign.indent + new_value
+        return varassign is not None
+
+    def append(self, varname: str, value: str) -> None:
+        """ Appends to the value of an existing variable in the lines. """
+        if value == '':
+            return
+        varassign = self.unique_varassign(varname)
+        # TODO: add a test for multiple assignments
+        if varassign is not None:
+            before = ' ' if re.search(r'\S$', varassign.value) else ''
+            after = '' if varassign.comment == '' else ' '
+            self.lines[varassign.index] = \
+                varassign.varname + varassign.op + varassign.indent \
+                + varassign.value + before + value + after \
+                + varassign.comment
+        return varassign is not None
+
+    def remove(self, varname: str) -> bool:
+        """ Removes the unique variable assignment. """
+        varassign = self.unique_varassign(varname)
+        if varassign is not None:
+            self.lines.pop(varassign.index)
+        return varassign is not None
+
+    def get(self, varname: str) -> str:
+        """
+        Returns the value from the only variable assignment, or an empty
+        string.
+        """
+        varassign = self.unique_varassign(varname)
+        return varassign.value if varassign is not None else ''
+
+    def remove_if(self, varname: str, expected_value: str) -> bool:
+        """ Removes a variable assignment if its value is the expected one. """
+        for varassign in self.all_varassigns(varname):
+            if varassign.value == expected_value:
+                self.lines.pop(varassign.index)
+                return True
+        return False
+
+    def index(self, pattern: str) -> int:
+        """ Returns the first index where the pattern is found, or -1. """
+        for (i, line) in enumerate(self.lines):
+            if re.search(pattern, line):
+                return i
+        return -1
+
+
+class Adjuster:
+    """
+    The following adjust_* functions are called after the distfiles have
+    been downloaded and extracted. They inspect the extracted files
+    and adjust the variable definitions in the package Makefile.
+    """
+
+    def __init__(self):
+        # the package name, including the version number.
+        self.distname = distname
+
+        # the absolute pathname to the working directory, containing
+        # the extracted distfiles.
+        self.abs_wrkdir = ''
+
+        # the absolute pathname to a subdirectory of abs_wrkdir, typically
+        # containing package-provided Makefiles or configure scripts.
+        self.abs_wrksrc = ''
+
+        # the regular files and directories relative to abs_wrksrc.
+        self.wrksrc_files = []
+        self.wrksrc_dirs = []
+
+        """
+        The following variables may be set by the adjust_* subroutines and
+        will later appear in the package Makefile:
+        """
+
+        # categories for the package, in addition to the usual
+        # parent directory.
+        self.categories: List[str] = []
+
+        # the dependencies of the package, in the form
+        # "package>=version:../../category/package".
+        self.depends = []
+        self.build_depends = []
+        self.test_depends = []
+
+        # .include, interleaved with BUILDLINK3_API_DEPENDS.
+        # These lines are added at the bottom of the Makefile.
+        self.bl3_lines = []
+
+        # a list of pathnames relative to the package path.
+        # All these files will be included at the bottom of the Makefile.
+        self.includes = []
+
+        # a list of variable assignments that will make up the fourth
+        # paragraph of the package Makefile, where the build configuration
+        # takes place.
+        self.build_vars = []
+
+        # similar to the @build_vars, but separated by an empty line in
+        # the Makefile, thereby forming the fifth paragraph.
+        self.extra_vars = []
+
+        # variables from the initial Makefile whose values are replaced
+        self.update_vars: Dict[str, str] = {}
+
+        # these are inserted below the second paragraph in the Makefile.
+        self.todos = []
+
+        # the package name is based on DISTNAME and modified by
+        # pkgname_prefix and pkgname_transform.
+        self.pkgname_prefix = ''  # example: ${PYPKGPREFIX}-
+        self.pkgname_transform = ''  # example: :S,-v,-,
+
+        # all lines of the package Makefile, for direct modification.
+        self.makefile_lines = Lines()
+
+        self.regenerate_distinfo = False
+
+    def add_dependency(self, kind: str, pkgbase: str, constraint: str, dep_dir: str) -> None:
+        """ add_dependency('DEPENDS', 'package', '>=1', '../../category/package') """
+        if dep_dir != '' and isfile(dep_dir + '/buildlink3.mk'):
+            # TODO: add kind to bl3_lines (BUILDLINK_DEPENDS)
+            # TODO: add constraint to bl3_lines (BUILDLINK_API_DEPENDS)
+            self.bl3_lines.append('.include "%s/buildlink3.mk"' % dep_dir)
+            return
+
+        value = pkgbase + constraint + ':' + dep_dir \
+            if dep_dir != '' and isfile(dep_dir + '/Makefile') \
+            else '# TODO: {0}{1}'.format(pkgbase, constraint)
+
+        if kind == 'DEPENDS':
+            self.depends.append(value)
+        elif kind == 'BUILD_DEPENDS':
+            self.build_depends.append(value)
+        elif kind == 'TEST_DEPENDS':
+            self.test_depends.append(value)
+        else:
+            self.todos.append('dependency {0} {1}'.format(kind, value))
+
+    def read_dependencies(self, cmd: str, env: Dict[str, str], cwd: str, pkgname_prefix: str) -> None:
+        dep_lines = []
+
+        effective_env = dict(os.environ)
+        effective_env.update(env)
+
+        output = subprocess.check_output(
+            args=cmd,
+            shell=True,
+            env=effective_env,
+            cwd=cwd
+        )
+
+        for line in output.decode('utf-8').split('\n'):
+            m = re.search(r'^(\w+)\t([^\s:>]+)(>[^\s:]+|)(?::(\.\./\.\./\S+))?$', line)
+            if m:
+                dep_lines.append([m[1], m[2], m[3] or '>=0', m[4] or ''])
+                continue
+            m = re.search(r'^var\t(\S+)\t(.+)$', line)
+            if m:
+                self.update_vars[m[1]] = m[2]
+                continue
+            if line != '':
+                debug('unknown dependency line: %s', line)
+
+        for dep_line in dep_lines:
+            type, pkgbase, constraint, dir = dep_line
+
+            if dir == '' and pkgname_prefix != '':
+                dir = find_package(pkgname_prefix + pkgbase)
+                if dir != '':
+                    pkgbase = pkgname_prefix + pkgbase
+            if dir == '':
+                dir = find_package(pkgbase)
+
+            debug('add_dependency: %s %s %s %s', type, pkgbase, constraint, dir)
+            self.add_dependency(type, pkgbase, constraint, dir)
+
+    def wrksrc_find(self, what: Union[str, Callable]) -> Iterator[str]:
+        def search(f):
+            if type(what) == str:
+                return re.search(what, f)
+            return what(f)
+
+        return list(filter(search, self.wrksrc_files))
+
+    def wrksrc_isdir(self, relative_pathname: str) -> bool:
+        return isfile(self.abs_wrksrc + '/' + relative_pathname)
+
+    def wrksrc_isfile(self, relative_pathname: str) -> bool:
+        return isfile(self.abs_wrksrc + '/' + relative_pathname)
+
+    def wrksrc_open(self, relative_pathname: str):
+        return open(self.abs_wrksrc + '/' + relative_pathname)
+
+    def adjust_configure(self):
+        if not self.wrksrc_isfile('configure'):
+            return
+
+        with self.wrksrc_open('configure') as f:
+            for line in f:
+                if 'autoconf' in line or 'Free Software Foundation' in line:
+                    self.build_vars.append(Var('GNU_CONFIGURE', '=', 'yes'))
+                    return
+
+        self.build_vars.append(Var('HAS_CONFIGURE', '=', 'yes'))
+
+    def adjust_cmake(self):
+        if self.wrksrc_isfile('CMakeLists.txt'):
+            self.build_vars.append(Var('USE_CMAKE', '=', 'yes'))
+
+    def adjust_meson(self):
+        if self.wrksrc_isfile('meson.build'):
+            self.includes.append('../../devel/py-meson/build.mk')
+
+    def adjust_gconf2_schemas(self):
+        gconf2_files = self.wrksrc_find(r'schemas(?:\.in.*)$')
+        if not gconf2_files:
+            return
+        for f in gconf2_files:
+            m = re.search(r'(.*schemas)', f)
+            if m:
+                self.extra_vars.append(Var('GCONF_SCHEMAS', '+=', m[1]))
+            self.includes.append('../../devel/GConf/schemas.mk')
+
+    def adjust_libtool(self):
+        if self.wrksrc_isfile('ltconfig') or self.wrksrc_isfile('ltmain.sh'):
+            self.build_vars.append(Var('USE_LIBTOOL', '=', 'yes'))
+        if self.wrksrc_isdir('libltdl'):
+            self.includes.append('../../devel/libltdl/convenience.mk')
+
+    def adjust_perl_module_Build_PL(self):
+        """
+        Example packages:
+        devel/p5-Algorithm-CheckDigits
+        """
+        cmd = '%s -I%s -I. Build.PL' % (config.perl5, config.libdir)
+        self.read_dependencies(cmd, {}, self.abs_wrksrc, '')
+        self.build_vars.append(Var('PERL5_MODULE_TYPE', '=', 'Module::Build'))
+
+    def adjust_perl_module_Makefile_PL(self):
+        """
+        Example packages:
+        devel/p5-Algorithm-Diff (no dependencies)
+        devel/p5-Carp-Assert-More (dependencies without version numbers)
+        www/p5-HTML-Quoted (dependency with version number)
+        """
+        # To avoid fix_up_makefile error for p5-HTML-Quoted, generate Makefile first.
+        cmd1 = '%s -I. Makefile.PL < /dev/null 1>&0 2>&0' % config.perl5
+        cmd2 = '%s -I%s -I. Makefile.PL' % (config.perl5, config.libdir)
+        subprocess.call(cmd1, shell=True, cwd=self.abs_wrksrc)
+        self.read_dependencies(cmd2, {}, self.abs_wrksrc, '')
+
+    def adjust_perl_module_homepage(self, url: str) -> None:
+        if '${MASTER_SITE_PERL_CPAN:' in self.makefile_lines.get('MASTER_SITES'):
+            homepage = self.makefile_lines.get('HOMEPAGE')
+            if homepage != '' and url.startswith(homepage):
+                module_name = re.sub(r'-v?[0-9].*', '', self.distname).replace('-', '::')
+                self.makefile_lines.set('HOMEPAGE', f'https://metacpan.org/pod/{module_name}')
+
+    def adjust_perl_module(self, url: str):
+        if self.wrksrc_isfile('Build.PL'):
+            self.adjust_perl_module_Build_PL()
+        elif self.wrksrc_isfile('Makefile.PL'):
+            self.adjust_perl_module_Makefile_PL()
+        else:
+            return
+
+        packlist = re.sub(r'-v?[0-9].*', '', self.distname).replace('-', '/')
+        self.build_vars.append(Var('PERL5_PACKLIST', '=', f'auto/{packlist}/.packlist'))
+        self.includes.append('../../lang/perl5/module.mk')
+        self.pkgname_prefix = 'p5-'
+        self.categories.append('perl5')
+        self.adjust_perl_module_homepage(url)
+
+        os.unlink('PLIST')
+
+    def adjust_python_module(self):
+        """
+        Example packages:
+        devel/py-ZopeComponent (dependencies, test dependencies)
+        devel/py-gflags (uses distutils.core instead of setuptools; BSD license)
+        devel/py-gcovr (uses setuptools; BSD license)
+        """
+
+        if not self.wrksrc_isfile('setup.py'):
+            return
+
+        cmd = '%s setup.py build' % config.pythonbin
+        env = {
+            'PYTHONDONTWRITEBYTECODE': 'x',
+            'PYTHONPATH': config.libdir
+        }
+        self.read_dependencies(cmd, env, self.abs_wrksrc, 'py-')
+
+        self.pkgname_prefix = '${PYPKGPREFIX}-'
+        self.categories.append('python')
+        self.includes.append('../../lang/python/egg.mk')
+
+    def adjust_cargo(self):
+        if not self.wrksrc_isfile('Cargo.lock'):
+            return
+
+        with self.wrksrc_open('Cargo.lock') as f:
+            for line in f:
+                # "checksum cargo-package-name cargo-package-version
+                m = re.search(r'^"checksum\s(\S+)\s(\S+)', line)
+                if m:
+                    self.build_vars.append(Var('CARGO_CRATE_DEPENDS', '+=', m[1] + '-' + m[2]))
+
+        self.includes.append('../../lang/rust/cargo.mk')
+
+    def adjust_pkg_config(self):
+        def relevant(f: str) -> bool:
+            return f.endswith('.pc.in') and not f.endswith('-uninstalled.pc.in')
+
+        pkg_config_files = self.wrksrc_find(relevant)
+
+        if pkg_config_files:
+            self.build_vars.append(Var('USE_TOOLS', '+=', 'pkg-config'))
+        for f in pkg_config_files:
+            self.extra_vars.append(Var('PKGCONFIG_OVERRIDE', '+=', f))
+
+    def adjust_po(self):
+        if self.wrksrc_find(r'\.g?mo$'):
+            self.build_vars.append(Var('USE_PKGLOCALEDIR', '=', 'yes'))
+
+    def adjust_use_languages(self):
+        languages = []
+
+        if self.wrksrc_find(r'\.(c|xs)$'):
+            languages.append('c')
+        if self.wrksrc_find(r'\.(cpp|c\+\+|cxx|cc|C)$'):
+            languages.append('c++')
+        if self.wrksrc_find(r'\.f$'):
+            languages.append('fortran')
+
+        use_languages = ' '.join(languages)
+        if use_languages == '':
+            use_languages = '# none'
+        if use_languages != 'c':
+            self.build_vars.append(Var('USE_LANGUAGES', '=', use_languages))
+
+    def determine_wrksrc(self):
+        """
+        Sets abs_wrksrc depending on abs_wrkdir and the files found there.
+        """
+
+        def ignore(f: str) -> bool:
+            return f.startswith('.') \
+                   or f == 'pax_global_header' \
+                   or f == 'package.xml' \
+                   or f.endswith('.gemspec')
+
+        files = list(filter(lambda x: not ignore(x), os.listdir(self.abs_wrkdir)))
+
+        if len(files) == 1:
+            if files[0] != self.distname:
+                self.build_vars.append(Var('WRKSRC', '=', '${WRKDIR}/' + files[0]))
+            self.abs_wrksrc = self.abs_wrkdir + '/' + files[0]
+        elif len(files) == 0:
+            self.build_vars.append(Var('WRKSRC', '=', '${WRKDIR}'))
+            self.abs_wrksrc = self.abs_wrkdir
+        else:
+            wrksrc = '${WRKDIR} # More than one possibility -- please check manually.'
+            self.build_vars.append(Var('WRKSRC', '=', wrksrc))
+            self.abs_wrksrc = self.abs_wrkdir
+
+    def adjust_lines_python_module(self, lines: Lines, url: str):
+
+        initial_lines = generate_initial_package_Makefile_lines(url)
+        current_lines = Lines.read_from('Makefile')
+
+        if 'python' not in initial_lines.get('CATEGORIES'):
+            return
+        pkgbase = initial_lines.get('GITHUB_PROJECT')
+        if pkgbase == '':
+            return
+        pkgbase1 = pkgbase[:1]
+        pkgversion_norev = re.sub(r'^v', '', initial_lines.get('DISTNAME'))
+
+        # don't risk to overwrite any changes made by the package developer.
+        if '\n'.join(current_lines.lines) != '\n'.join(initial_lines.lines):
+            lines.lines.insert(-2, '# TODO: Migrate MASTER_SITES to PYPI')
+            return
+
+        tx_lines = Lines(*self.makefile_lines.lines)
+        if (tx_lines.remove('GITHUB_PROJECT')
+                and tx_lines.set('DISTNAME', '%s-%s' % (pkgbase, pkgversion_norev))
+                and tx_lines.set('PKGNAME', '${PYPKGPREFIX}-${DISTNAME}')
+                and tx_lines.set('MASTER_SITES', '${MASTER_SITE_PYPI:=%s/%s/}' % (pkgbase1, pkgbase))
+                and tx_lines.remove('DIST_SUBDIR')
+                and (tx_lines.remove_if('EXTRACT_SUFX', '.zip') or True)):
+            self.makefile_lines = tx_lines
+            self.regenerate_distinfo = True
+
+    def generate_adjusted_Makefile_lines(self, url):
+        marker_index = self.makefile_lines.index(r'^# url2pkg-marker')
+        if marker_index == -1:
+            raise Exception('ERROR: didn\'t find the url2pkg marker in the Makefile.')
+
+        lines = Lines(*self.makefile_lines.lines[: marker_index])
+
+        if lines.index(r'^PKGNAME=') == -1:
+            distname_index = lines.index(r'^DISTNAME=(\t+)')
+            if distname_index != -1:
+                pkgname_line = 'PKGNAME=\t%s${DISTNAME%s}' % (self.pkgname_prefix, self.pkgname_transform)
+                lines.lines.insert(distname_index + 1, pkgname_line)
+
+        if self.todos:
+            for todo in self.todos:
+                lines.add('# TODO: ' + todo)
+            lines.add('')
+
+        depend_vars = []
+        depend_vars.extend(map(lambda d: Var('BUILD_DEPENDS', '+=', d), self.build_depends))
+        depend_vars.extend(map(lambda d: Var('DEPENDS', '+=', d), self.depends))
+        depend_vars.extend(map(lambda d: Var('TEST_DEPENDS', '+=', d), self.test_depends))
+        lines.add_vars(*depend_vars)
+
+        lines.add_vars(*self.build_vars)
+        lines.add_vars(*self.extra_vars)
+
+        lines.add(*self.bl3_lines)
+        lines.add(*map(lambda include: '.include "%s"' % include, self.includes))
+
+        lines.add(*self.makefile_lines.lines[marker_index + 1:])
+
+        lines.append('CATEGORIES', ' '.join(self.categories))
+
+        self.adjust_lines_python_module(lines, url)
+
+        for varname in self.update_vars:
+            lines.set(varname, self.update_vars[varname])
+
+        return lines
+
+    def adjust_package_from_extracted_distfiles(self, url: str):
+
+        debug('Adjusting the Makefile')
+
+        self.abs_wrkdir = show_var('WRKDIR')
+        self.determine_wrksrc()
+        self.wrksrc_files = glob.glob(f'{self.abs_wrksrc}/**', recursive=True)
+        self.wrksrc_dirs = glob.glob(f'{self.abs_wrksrc}/**/', recursive=True)
+
+        self.makefile_lines = Lines.read_from('Makefile')
+
+        self.adjust_configure()
+        self.adjust_cmake()
+        self.adjust_meson()
+        self.adjust_gconf2_schemas()
+        self.adjust_libtool()
+        self.adjust_perl_module(url)
+        self.adjust_python_module()
+        self.adjust_cargo()
+        self.adjust_pkg_config()
+        self.adjust_po()
+        self.adjust_use_languages()
+
+        self.generate_adjusted_Makefile_lines(url).write_to('Makefile')
+
+        if self.regenerate_distinfo:
+            bmake('distinfo')
+
+
+def main():
+    global distname
+
+    if not isfile('../../mk/bsd.pkg.mk'):
+        sys.exit(f'ERROR: {sys.argv[0]} must be run from a package directory (.../pkgsrc/category/package).')
+
+    try:
+        opts, args = getopt.getopt(sys.argv[1:], 'v', ['verbose'])
+        for opt in opts:
+            if opt in ('v', 'verbose'):
+                config.verbose = True
+    except getopt.GetoptError:
+        sys.exit(f'usage: {sys.argv[0]} [-v|--verbose] [URL]')
+
+    if len(args) == 0:
+        url = input('URL: ')
+    else:
+        url = args[0]
+
+    extract_cookie = glob.glob('w*/.extract_done')
+    if not extract_cookie or not isfile('Makefile'):
+        generate_initial_package(url)
+    else:
+        distname = show_var('DISTNAME')
+
+    Adjuster().adjust_package_from_extracted_distfiles(url)
+
+    print('')
+    print('Remember to run pkglint when you\'re done.')
+    print('See ../../doc/pkgsrc.txt to get some help.')
+    print('')
+
+
+if __name__ == '__main__':
+    main()
Index: pkgsrc/pkgtools/url2pkg/files/url2pkg_test.py
diff -u /dev/null pkgsrc/pkgtools/url2pkg/files/url2pkg_test.py:1.1
--- /dev/null   Thu Oct  3 09:37:41 2019
+++ pkgsrc/pkgtools/url2pkg/files/url2pkg_test.py       Thu Oct  3 09:37:41 2019
@@ -0,0 +1,447 @@
+# $NetBSD: url2pkg_test.py,v 1.1 2019/10/03 09:37:41 rillig Exp $
+
+import os
+from typing import List
+from url2pkg import *
+
+
+def setup_function(fn):
+    config.pkgsrcdir = os.getenv('PKGSRCDIR')
+    assert config.pkgsrcdir is not None
+    os.chdir(config.pkgsrcdir + '/pkgtools/url2pkg')
+
+
+def vars(vars: List[Var]) -> List[str]:
+    return list(map(lambda var: var.name + var.op + var.value, vars))
+
+
+def test_Lines_add_vars__simple():
+    lines = Lines()
+
+    lines.add_vars(
+        Var("1", "=", "one"),
+        Var("6", "=", "six"),
+    )
+
+    assert lines.lines == [
+        "1=\tone",
+        "6=\tsix",
+        "",
+    ]
+
+
+def test_Lines_add_vars__alignment():
+    lines = Lines()
+
+    lines.add_vars(
+        Var("short", "=", "value"),
+        Var("long_name", "=", "value # comment"),
+    )
+
+    assert lines.lines == [
+        "short=\t\tvalue",
+        "long_name=\tvalue # comment",
+        "",
+    ]
+
+
+def test_Lines_add_vars__operators():
+    lines = Lines()
+
+    lines.add_vars(
+        Var("123456", "+=", "value"),
+    )
+
+    assert lines.lines == [
+        "123456+=\tvalue",
+        "",
+    ]
+
+
+def test_Lines_add_vars__empty():
+    lines = Lines("# initial")
+
+    lines.add_vars()
+
+    # No empty line is added.
+    assert lines.lines == ["# initial"]
+
+
+def test_Lines_append__not_found():
+    lines = Lines()
+
+    lines.append("VARNAME", "value")
+
+    assert lines.lines == []
+
+
+def test_Lines_append__only_comment():
+    lines = Lines("VARNAME=\t\t\t# none")
+
+    lines.append("VARNAME", "value")
+
+    assert lines.lines == ["VARNAME=\t\t\tvalue # none"]
+
+
+def test_Lines_append__value_with_comment():
+    lines = Lines("VARNAME=\tvalue # comment")
+
+    lines.append("VARNAME", "appended")
+
+    assert lines.lines == ["VARNAME=\tvalue appended # comment"]
+
+
+def test_Lines_append__value_without_comment():
+    lines = Lines("VARNAME+=\tvalue")
+
+    assert lines.append("VARNAME", "appended") == True
+
+    assert lines.lines == ["VARNAME+=\tvalue appended"]
+
+
+def test_Lines_set__previously_with_comment():
+    lines = Lines("LICENSE=\t# TODO: see mk/license.mk")
+
+    lines.set("LICENSE", "${PERL5_LICENSE}")
+
+    assert lines.lines == ["LICENSE=\t${PERL5_LICENSE}"]
+
+
+def test_Lines_set__overwrite_comment_with_comment():
+    lines = Lines("#LICENSE=\t# TODO: see mk/license.mk")
+
+    lines.set("#LICENSE", "${PERL5_LICENSE}")
+
+    assert lines.lines == ["#LICENSE=\t${PERL5_LICENSE}"]
+
+
+def test_Lines_set__not_found():
+    lines = Lines("OLD_VAR=\told value # old comment")
+
+    lines.set("NEW_VAR", "new value")
+
+    assert lines.lines == ["OLD_VAR=\told value # old comment"]
+
+
+def test_Lines_index():
+    lines = Lines("1", "2", "345")
+
+    assert lines.index("1") == 0
+    assert lines.index("2") == 1
+    assert lines.index("345") == 2
+    assert lines.index("4") == 2
+
+    assert lines.index(r'^(\d\d)\d$') == 2
+    assert lines.index(r"^\d\s\d$") == -1
+    assert lines.index(r"(\d)") == 0
+
+
+def test_Lines_get():
+    lines = Lines(
+        "VAR=value",
+        "VAR=\tvalue # comment",
+        "UNIQUE=\tunique"
+    )
+
+    assert lines.get("VAR") == ""  # too many values
+    assert lines.get("ENOENT") == ""  # no value at all
+    assert lines.get("UNIQUE") == "unique"
+
+
+def test_generate_initial_package_Makefile_lines__GitHub_archive():
+    url = "https://github.com/org/proj/archive/v1.0.0.tar.gz";
+
+    lines = generate_initial_package_Makefile_lines(url)
+
+    assert lines.lines == [
+        "# $" + "NetBSD$",
+        "",
+        "GITHUB_PROJECT=\tproj",
+        "DISTNAME=\tv1.0.0",
+        "PKGNAME=\t${GITHUB_PROJECT}-${DISTNAME:S,^v,,}",
+        "CATEGORIES=\tpkgtools",
+        "MASTER_SITES=\t${MASTER_SITE_GITHUB:=org/}",
+        "DIST_SUBDIR=\t${GITHUB_PROJECT}",
+        "",
+        "MAINTAINER=\tINSERT_YOUR_MAIL_ADDRESS_HERE",
+        "HOMEPAGE=\thttps://github.com/org/proj/";,
+        "COMMENT=\tTODO: Short description of the package",
+        "#LICENSE=\t# TODO: (see mk/license.mk)",
+        "",
+        "# url2pkg-marker (please do not remove this line.)",
+        ".include \"../../mk/bsd.pkg.mk\""
+    ]
+
+
+def test_generate_initial_package_Makefile_lines__GitHub_release_containing_project_name():
+    url = "https://github.com/org/proj/releases/download/1.0.0/proj.zip";
+
+    lines = generate_initial_package_Makefile_lines(url)
+
+    assert lines.lines == [
+        "# $" + "NetBSD$",
+        "",
+        "GITHUB_PROJECT=\tproj",
+        "DISTNAME=\tproj",
+        "CATEGORIES=\tpkgtools",
+        "MASTER_SITES=\t${MASTER_SITE_GITHUB:=org/}",
+        "GITHUB_RELEASE=\t1.0.0",
+        "EXTRACT_SUFX=\t.zip",
+        "",
+        "MAINTAINER=\tINSERT_YOUR_MAIL_ADDRESS_HERE",
+        "HOMEPAGE=\thttps://github.com/org/proj/";,
+        "COMMENT=\tTODO: Short description of the package",
+        "#LICENSE=\t# TODO: (see mk/license.mk)",
+        "",
+        "# url2pkg-marker (please do not remove this line.)",
+        ".include \"../../mk/bsd.pkg.mk\""
+    ]
+
+
+def test_generate_initial_package_Makefile_lines__GitHub_release_not_containing_project_name():
+    url = "https://github.com/org/proj/releases/download/1.0.0/data.zip";
+
+    lines = generate_initial_package_Makefile_lines(url)
+
+    assert lines.lines == [
+        "# $" + "NetBSD$",
+        "",
+        "GITHUB_PROJECT=\tproj",
+        "DISTNAME=\tdata",
+        "CATEGORIES=\tpkgtools",
+        "MASTER_SITES=\t${MASTER_SITE_GITHUB:=org/}",
+        "GITHUB_RELEASE=\t1.0.0",
+        "EXTRACT_SUFX=\t.zip",
+        "DIST_SUBDIR=\t${GITHUB_PROJECT}",
+        "",
+        "MAINTAINER=\tINSERT_YOUR_MAIL_ADDRESS_HERE",
+        "HOMEPAGE=\thttps://github.com/org/proj/";,
+        "COMMENT=\tTODO: Short description of the package",
+        "#LICENSE=\t# TODO: (see mk/license.mk)",
+        "",
+        "# url2pkg-marker (please do not remove this line.)",
+        ".include \"../../mk/bsd.pkg.mk\""
+    ]
+
+
+def test_generate_initial_package_Makefile_lines__distname_version_with_v():
+    url = "https://cpan.example.org/Algorithm-CheckDigits-v1.3.2.tar.gz";
+
+    lines = generate_initial_package_Makefile_lines(url)
+
+    assert lines.lines == [
+        "# $" + "NetBSD$",
+        "",
+        "DISTNAME=\tAlgorithm-CheckDigits-v1.3.2",
+        "PKGNAME=\t${DISTNAME:S,-v,-,}",
+        "CATEGORIES=\tpkgtools",
+        "MASTER_SITES=\thttps://cpan.example.org/";,
+        "",
+        "MAINTAINER=\tINSERT_YOUR_MAIL_ADDRESS_HERE",
+        "HOMEPAGE=\thttps://cpan.example.org/";,
+        "COMMENT=\tTODO: Short description of the package",
+        "#LICENSE=\t# TODO: (see mk/license.mk)",
+        "",
+        "# url2pkg-marker (please do not remove this line.)",
+        ".include \"../../mk/bsd.pkg.mk\""
+    ]
+
+
+def test_Adjuster_read_dependencies():
+    dep_lines = [
+        "DEPENDS\tpackage>=version:../../pkgtools/pkglint",
+        "DEPENDS\tpackage>=version:../../pkgtools/x11-links",
+        "BUILD_DEPENDS\turl2pkg>=1.0",
+        "TEST_DEPENDS\tpkglint",
+        "A line that is not a dependency at all",
+        "",
+        ""
+    ]
+    env = {"URL2PKG_DEPENDENCIES": '\n'.join(dep_lines)}
+    cmd = "printf '%s\n' \"$URL2PKG_DEPENDENCIES\""
+
+    adjuster = Adjuster()
+    adjuster.read_dependencies(cmd, env, '.', '')
+
+    assert os.getenv('URL2PKG_DEPENDENCIES') is None
+
+    assert adjuster.depends == [
+        "package>=version:../../pkgtools/pkglint"
+    ]
+    assert adjuster.bl3_lines == [
+        ".include \"../../pkgtools/x11-links/buildlink3.mk\""
+    ]
+    assert adjuster.build_depends == [
+        "url2pkg>=1.0:../../pkgtools/url2pkg"
+    ]
+    assert adjuster.test_depends == [
+        "pkglint>=0:../../pkgtools/pkglint"
+    ]
+
+
+def test_Adjuster_generate_adjusted_Makefile_lines():
+    adjuster = Adjuster()
+    adjuster.makefile_lines = Lines(
+        "# before 1",
+        "# before 2",
+        "# url2pkg-marker",
+        "# after 1",
+        "# after 2"
+    )
+
+    lines = adjuster.generate_adjusted_Makefile_lines("https://example.org/pkgname-1.0.tar.gz";)
+
+    assert lines.lines == [
+        "# before 1",
+        "# before 2",
+        "# after 1",
+        "# after 2"
+    ]
+
+
+def test_Adjuster_generate_adjusted_Makefile_lines__dependencies():
+    adjuster = Adjuster()
+    adjuster.makefile_lines.add(
+        "# $" + "NetBSD$",
+        "",
+        "# url2pkg-marker",
+        ".include \"../../mk/bsd.pkg.mk\""
+    )
+
+    # some dependencies whose directory will not be found
+    adjuster.add_dependency("DEPENDS", "depends", ">=5.0", "../../devel/depends");
+    adjuster.add_dependency("TOOL_DEPENDS", "tool-depends", ">=6.0", "../../devel/tool-depends");
+    adjuster.add_dependency("BUILD_DEPENDS", "build-depends", ">=7.0", "../../devel/build-depends");
+    adjuster.add_dependency("TEST_DEPENDS", "test-depends", ">=8.0", "../../devel/test-depends");
+    # some dependencies whose directory is explicitly given
+    adjuster.depends.append("depends>=11.0:../../devel/depends")
+    adjuster.build_depends.append("build-depends>=12.0:../../devel/build-depends")
+    adjuster.test_depends.append("test-depends>=13.0:../../devel/test-depends")
+
+    lines = adjuster.generate_adjusted_Makefile_lines("https://example.org/pkgname-1.0.tar.gz";)
+
+    assert lines.lines == [
+        "# $" + "NetBSD$",
+        "",
+        "# TODO: dependency TOOL_DEPENDS # TODO: tool-depends>=6.0",
+        "",
+        "BUILD_DEPENDS+=\t# TODO: build-depends>=7.0",
+        "BUILD_DEPENDS+=\tbuild-depends>=12.0:../../devel/build-depends",
+        "DEPENDS+=\t# TODO: depends>=5.0",
+        "DEPENDS+=\tdepends>=11.0:../../devel/depends",
+        "TEST_DEPENDS+=\t# TODO: test-depends>=8.0",
+        "TEST_DEPENDS+=\ttest-depends>=13.0:../../devel/test-depends",
+        "",
+        ".include \"../../mk/bsd.pkg.mk\""
+    ]
+
+
+def test_Adjuster_adjust_po__not_found():
+    adjuster = Adjuster()
+
+    adjuster.adjust_po()
+
+    assert adjuster.build_vars == []
+
+
+def test_Adjuster_adjust_po__found():
+    adjuster = Adjuster()
+    adjuster.wrksrc_files = ['share/locale/de.mo']
+
+    adjuster.adjust_po()
+
+    assert vars(adjuster.build_vars) == [
+        'USE_PKGLOCALEDIR=yes'
+    ]
+
+
+def test_Adjuster_adjust_use_languages__none():
+    adjuster = Adjuster()
+
+    adjuster.adjust_use_languages()
+
+    assert vars(adjuster.build_vars) == [
+        'USE_LANGUAGES=# none'
+    ]
+
+
+def test_Adjuster_adjust_use_languages__c():
+    adjuster = Adjuster()
+    adjuster.wrksrc_files = ['main.c']
+
+    adjuster.adjust_use_languages()
+
+    assert vars(adjuster.build_vars) == []
+
+
+def test_Adjuster_adjust_use_languages__c_in_subdir():
+    adjuster = Adjuster()
+    adjuster.wrksrc_files = ['subdir/main.c']
+
+    adjuster.adjust_use_languages()
+
+    assert vars(adjuster.build_vars) == []
+
+
+def test_Adjuster_adjust_use_languages__cplusplus_in_subdir():
+    adjuster = Adjuster()
+    adjuster.wrksrc_files = ['subdir/main.cpp']
+
+    adjuster.adjust_use_languages()
+
+    assert vars(adjuster.build_vars) == [
+        'USE_LANGUAGES=c++'
+    ]
+
+
+def test_Adjuster_adjust_use_languages__cplusplus_and_fortran():
+    adjuster = Adjuster()
+    adjuster.wrksrc_files = ['subdir/main.cpp', 'main.f']
+
+    adjuster.adjust_use_languages()
+
+    assert vars(adjuster.build_vars) == [
+        'USE_LANGUAGES=c++ fortran'
+    ]
+
+
+def test_Adjuster_adjust_pkg_config__none():
+    adjuster = Adjuster()
+
+    adjuster.adjust_pkg_config()
+
+    assert vars(adjuster.build_vars) == []
+    assert vars(adjuster.extra_vars) == []
+
+
+def test_Adjuster_adjust_pkg_config__pc_in():
+    adjuster = Adjuster()
+    adjuster.wrksrc_files = ['library.pc.in']
+
+    adjuster.adjust_pkg_config()
+
+    assert vars(adjuster.build_vars) == ['USE_TOOLS+=pkg-config']
+    assert vars(adjuster.extra_vars) == ['PKGCONFIG_OVERRIDE+=library.pc.in']
+
+
+def test_Adjuster_adjust_pkg_config__uninstalled_pc_in():
+    adjuster = Adjuster()
+    adjuster.wrksrc_files = ['library-uninstalled.pc.in']
+
+    adjuster.adjust_pkg_config()
+
+    assert vars(adjuster.build_vars) == []
+    assert vars(adjuster.extra_vars) == []
+
+
+def test_Adjuster_adjust_pkg_config__both():
+    adjuster = Adjuster()
+    adjuster.wrksrc_files = [
+        'library.pc.in',
+        'library-uninstalled.pc.in'
+    ]
+
+    adjuster.adjust_pkg_config()
+
+    assert vars(adjuster.build_vars) == ['USE_TOOLS+=pkg-config']
+    assert vars(adjuster.extra_vars) == ['PKGCONFIG_OVERRIDE+=library.pc.in']



Home | Main Index | Thread Index | Old Index