xref: /OK3568_Linux_fs/buildroot/utils/scanpypi (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1#!/usr/bin/env python3
2"""
3
4Utility for building Buildroot packages for existing PyPI packages
5
6Any package built by scanpypi should be manually checked for
7errors.
8"""
9import argparse
10import json
11import sys
12import os
13import shutil
14import tarfile
15import zipfile
16import errno
17import hashlib
18import re
19import textwrap
20import tempfile
21import imp
22from functools import wraps
23import six.moves.urllib.request
24import six.moves.urllib.error
25import six.moves.urllib.parse
26from six.moves import map
27from six.moves import zip
28from six.moves import input
29if six.PY2:
30    import StringIO
31else:
32    import io
33
34BUF_SIZE = 65536
35
36try:
37    import spdx_lookup as liclookup
38except ImportError:
39    # spdx_lookup is not installed
40    print('spdx_lookup module is not installed. This can lead to an '
41          'inaccurate licence detection. Please install it via\n'
42          'pip install spdx_lookup')
43    liclookup = None
44
45
46def setup_decorator(func, method):
47    """
48    Decorator for distutils.core.setup and setuptools.setup.
49    Puts the arguments with which setup is called as a dict
50    Add key 'method' which should be either 'setuptools' or 'distutils'.
51
52    Keyword arguments:
53    func -- either setuptools.setup or distutils.core.setup
54    method -- either 'setuptools' or 'distutils'
55    """
56
57    @wraps(func)
58    def closure(*args, **kwargs):
59        # Any python packages calls its setup function to be installed.
60        # Argument 'name' of this setup function is the package's name
61        BuildrootPackage.setup_args[kwargs['name']] = kwargs
62        BuildrootPackage.setup_args[kwargs['name']]['method'] = method
63    return closure
64
65# monkey patch
66import setuptools  # noqa E402
67setuptools.setup = setup_decorator(setuptools.setup, 'setuptools')
68import distutils   # noqa E402
69distutils.core.setup = setup_decorator(setuptools.setup, 'distutils')
70
71
72def find_file_upper_case(filenames, path='./'):
73    """
74    List generator:
75    Recursively find files that matches one of the specified filenames.
76    Returns a relative path starting with path argument.
77
78    Keyword arguments:
79    filenames -- List of filenames to be found
80    path -- Path to the directory to search
81    """
82    for root, dirs, files in os.walk(path):
83        for file in files:
84            if file.upper() in filenames:
85                yield (os.path.join(root, file))
86
87
88def pkg_buildroot_name(pkg_name):
89    """
90    Returns the Buildroot package name for the PyPI package pkg_name.
91    Remove all non alphanumeric characters except -
92    Also lowers the name and adds 'python-' suffix
93
94    Keyword arguments:
95    pkg_name -- String to rename
96    """
97    name = re.sub(r'[^\w-]', '', pkg_name.lower())
98    name = name.replace('_', '-')
99    prefix = 'python-'
100    pattern = re.compile(r'^(?!' + prefix + ')(.+?)$')
101    name = pattern.sub(r'python-\1', name)
102    return name
103
104
105class DownloadFailed(Exception):
106    pass
107
108
109class BuildrootPackage():
110    """This class's methods are not meant to be used individually please
111    use them in the correct order:
112
113    __init__
114
115    download_package
116
117    extract_package
118
119    load_module
120
121    get_requirements
122
123    create_package_mk
124
125    create_hash_file
126
127    create_config_in
128
129    """
130    setup_args = {}
131
132    def __init__(self, real_name, pkg_folder):
133        self.real_name = real_name
134        self.buildroot_name = pkg_buildroot_name(self.real_name)
135        self.pkg_dir = os.path.join(pkg_folder, self.buildroot_name)
136        self.mk_name = self.buildroot_name.upper().replace('-', '_')
137        self.as_string = None
138        self.md5_sum = None
139        self.metadata = None
140        self.metadata_name = None
141        self.metadata_url = None
142        self.pkg_req = None
143        self.setup_metadata = None
144        self.tmp_extract = None
145        self.used_url = None
146        self.filename = None
147        self.url = None
148        self.version = None
149        self.license_files = []
150
151    def fetch_package_info(self):
152        """
153        Fetch a package's metadata from the python package index
154        """
155        self.metadata_url = 'https://pypi.org/pypi/{pkg}/json'.format(
156            pkg=self.real_name)
157        try:
158            pkg_json = six.moves.urllib.request.urlopen(self.metadata_url).read().decode()
159        except six.moves.urllib.error.HTTPError as error:
160            print('ERROR:', error.getcode(), error.msg, file=sys.stderr)
161            print('ERROR: Could not find package {pkg}.\n'
162                  'Check syntax inside the python package index:\n'
163                  'https://pypi.python.org/pypi/ '
164                  .format(pkg=self.real_name))
165            raise
166        except six.moves.urllib.error.URLError:
167            print('ERROR: Could not find package {pkg}.\n'
168                  'Check syntax inside the python package index:\n'
169                  'https://pypi.python.org/pypi/ '
170                  .format(pkg=self.real_name))
171            raise
172        self.metadata = json.loads(pkg_json)
173        self.version = self.metadata['info']['version']
174        self.metadata_name = self.metadata['info']['name']
175
176    def download_package(self):
177        """
178        Download a package using metadata from pypi
179        """
180        download = None
181        try:
182            self.metadata['urls'][0]['filename']
183        except IndexError:
184            print(
185                'Non-conventional package, ',
186                'please check carefully after creation')
187            self.metadata['urls'] = [{
188                'packagetype': 'sdist',
189                'url': self.metadata['info']['download_url'],
190                'digests': None}]
191            # In this case, we can't get the name of the downloaded file
192            # from the pypi api, so we need to find it, this should work
193            urlpath = six.moves.urllib.parse.urlparse(
194                self.metadata['info']['download_url']).path
195            # urlparse().path give something like
196            # /path/to/file-version.tar.gz
197            # We use basename to remove /path/to
198            self.metadata['urls'][0]['filename'] = os.path.basename(urlpath)
199        for download_url in self.metadata['urls']:
200            if 'bdist' in download_url['packagetype']:
201                continue
202            try:
203                print('Downloading package {pkg} from {url}...'.format(
204                    pkg=self.real_name, url=download_url['url']))
205                download = six.moves.urllib.request.urlopen(download_url['url'])
206            except six.moves.urllib.error.HTTPError as http_error:
207                download = http_error
208            else:
209                self.used_url = download_url
210                self.as_string = download.read()
211                if not download_url['digests']['md5']:
212                    break
213                self.md5_sum = hashlib.md5(self.as_string).hexdigest()
214                if self.md5_sum == download_url['digests']['md5']:
215                    break
216
217        if download is None:
218            raise DownloadFailed('Failed to download package {pkg}: '
219                                 'No source archive available'
220                                 .format(pkg=self.real_name))
221        elif download.__class__ == six.moves.urllib.error.HTTPError:
222            raise download
223
224        self.filename = self.used_url['filename']
225        self.url = self.used_url['url']
226
227    def check_archive(self, members):
228        """
229        Check archive content before extracting
230
231        Keyword arguments:
232        members -- list of archive members
233        """
234        # Protect against https://github.com/snyk/zip-slip-vulnerability
235        # Older python versions do not validate that the extracted files are
236        # inside the target directory. Detect and error out on evil paths
237        evil = [e for e in members if os.path.relpath(e).startswith(('/', '..'))]
238        if evil:
239            print('ERROR: Refusing to extract {} with suspicious members {}'.format(
240                self.filename, evil))
241            sys.exit(1)
242
243    def extract_package(self, tmp_path):
244        """
245        Extract the package contents into a directrory
246
247        Keyword arguments:
248        tmp_path -- directory where you want the package to be extracted
249        """
250        if six.PY2:
251            as_file = StringIO.StringIO(self.as_string)
252        else:
253            as_file = io.BytesIO(self.as_string)
254        if self.filename[-3:] == 'zip':
255            with zipfile.ZipFile(as_file) as as_zipfile:
256                tmp_pkg = os.path.join(tmp_path, self.buildroot_name)
257                try:
258                    os.makedirs(tmp_pkg)
259                except OSError as exception:
260                    if exception.errno != errno.EEXIST:
261                        print("ERROR: ", exception.strerror, file=sys.stderr)
262                        return
263                    print('WARNING:', exception.strerror, file=sys.stderr)
264                    print('Removing {pkg}...'.format(pkg=tmp_pkg))
265                    shutil.rmtree(tmp_pkg)
266                    os.makedirs(tmp_pkg)
267                self.check_archive(as_zipfile.namelist())
268                as_zipfile.extractall(tmp_pkg)
269                pkg_filename = self.filename.split(".zip")[0]
270        else:
271            with tarfile.open(fileobj=as_file) as as_tarfile:
272                tmp_pkg = os.path.join(tmp_path, self.buildroot_name)
273                try:
274                    os.makedirs(tmp_pkg)
275                except OSError as exception:
276                    if exception.errno != errno.EEXIST:
277                        print("ERROR: ", exception.strerror, file=sys.stderr)
278                        return
279                    print('WARNING:', exception.strerror, file=sys.stderr)
280                    print('Removing {pkg}...'.format(pkg=tmp_pkg))
281                    shutil.rmtree(tmp_pkg)
282                    os.makedirs(tmp_pkg)
283                self.check_archive(as_tarfile.getnames())
284                as_tarfile.extractall(tmp_pkg)
285                pkg_filename = self.filename.split(".tar")[0]
286
287        tmp_extract = '{folder}/{name}'
288        self.tmp_extract = tmp_extract.format(
289            folder=tmp_pkg,
290            name=pkg_filename)
291
292    def load_setup(self):
293        """
294        Loads the corresponding setup and store its metadata
295        """
296        current_dir = os.getcwd()
297        os.chdir(self.tmp_extract)
298        sys.path.insert(0, self.tmp_extract)
299        s_file, s_path, s_desc = imp.find_module('setup', [self.tmp_extract])
300        imp.load_module('__main__', s_file, s_path, s_desc)
301        if self.metadata_name in self.setup_args:
302            pass
303        elif self.metadata_name.replace('_', '-') in self.setup_args:
304            self.metadata_name = self.metadata_name.replace('_', '-')
305        elif self.metadata_name.replace('-', '_') in self.setup_args:
306            self.metadata_name = self.metadata_name.replace('-', '_')
307        try:
308            self.setup_metadata = self.setup_args[self.metadata_name]
309        except KeyError:
310            # This means setup was not called
311            print('ERROR: Could not determine package metadata for {pkg}.\n'
312                  .format(pkg=self.real_name))
313            raise
314        os.chdir(current_dir)
315        sys.path.remove(self.tmp_extract)
316
317    def get_requirements(self, pkg_folder):
318        """
319        Retrieve dependencies from the metadata found in the setup.py script of
320        a pypi package.
321
322        Keyword Arguments:
323        pkg_folder -- location of the already created packages
324        """
325        if 'install_requires' not in self.setup_metadata:
326            self.pkg_req = None
327            return set()
328        self.pkg_req = self.setup_metadata['install_requires']
329        self.pkg_req = [re.sub(r'([-.\w]+).*', r'\1', req)
330                        for req in self.pkg_req]
331
332        # get rid of commented lines and also strip the package strings
333        self.pkg_req = [item.strip() for item in self.pkg_req
334                        if len(item) > 0 and item[0] != '#']
335
336        req_not_found = self.pkg_req
337        self.pkg_req = list(map(pkg_buildroot_name, self.pkg_req))
338        pkg_tuples = list(zip(req_not_found, self.pkg_req))
339        # pkg_tuples is a list of tuples that looks like
340        # ('werkzeug','python-werkzeug') because I need both when checking if
341        # dependencies already exist or are already in the download list
342        req_not_found = set(
343            pkg[0] for pkg in pkg_tuples
344            if not os.path.isdir(pkg[1])
345            )
346        return req_not_found
347
348    def __create_mk_header(self):
349        """
350        Create the header of the <package_name>.mk file
351        """
352        header = ['#' * 80 + '\n']
353        header.append('#\n')
354        header.append('# {name}\n'.format(name=self.buildroot_name))
355        header.append('#\n')
356        header.append('#' * 80 + '\n')
357        header.append('\n')
358        return header
359
360    def __create_mk_download_info(self):
361        """
362        Create the lines refering to the download information of the
363        <package_name>.mk file
364        """
365        lines = []
366        version_line = '{name}_VERSION = {version}\n'.format(
367            name=self.mk_name,
368            version=self.version)
369        lines.append(version_line)
370
371        if self.buildroot_name != self.real_name:
372            targz = self.filename.replace(
373                self.version,
374                '$({name}_VERSION)'.format(name=self.mk_name))
375            targz_line = '{name}_SOURCE = {filename}\n'.format(
376                name=self.mk_name,
377                filename=targz)
378            lines.append(targz_line)
379
380        if self.filename not in self.url:
381            # Sometimes the filename is in the url, sometimes it's not
382            site_url = self.url
383        else:
384            site_url = self.url[:self.url.find(self.filename)]
385        site_line = '{name}_SITE = {url}'.format(name=self.mk_name,
386                                                 url=site_url)
387        site_line = site_line.rstrip('/') + '\n'
388        lines.append(site_line)
389        return lines
390
391    def __create_mk_setup(self):
392        """
393        Create the line refering to the setup method of the package of the
394        <package_name>.mk file
395
396        There are two things you can use to make an installer
397        for a python package: distutils or setuptools
398        distutils comes with python but does not support dependencies.
399        distutils is mostly still there for backward support.
400        setuptools is what smart people use,
401        but it is not shipped with python :(
402        """
403        lines = []
404        setup_type_line = '{name}_SETUP_TYPE = {method}\n'.format(
405            name=self.mk_name,
406            method=self.setup_metadata['method'])
407        lines.append(setup_type_line)
408        return lines
409
410    def __get_license_names(self, license_files):
411        """
412        Try to determine the related license name.
413
414        There are two possibilities. Either the script tries to
415        get license name from package's metadata or, if spdx_lookup
416        package is available, the script compares license files with
417        SPDX database.
418        """
419        license_line = ''
420        if liclookup is None:
421            license_dict = {
422                'Apache Software License': 'Apache-2.0',
423                'BSD License': 'FIXME: please specify the exact BSD version',
424                'European Union Public Licence 1.0': 'EUPL-1.0',
425                'European Union Public Licence 1.1': 'EUPL-1.1',
426                "GNU General Public License": "GPL",
427                "GNU General Public License v2": "GPL-2.0",
428                "GNU General Public License v2 or later": "GPL-2.0+",
429                "GNU General Public License v3": "GPL-3.0",
430                "GNU General Public License v3 or later": "GPL-3.0+",
431                "GNU Lesser General Public License v2": "LGPL-2.1",
432                "GNU Lesser General Public License v2 or later": "LGPL-2.1+",
433                "GNU Lesser General Public License v3": "LGPL-3.0",
434                "GNU Lesser General Public License v3 or later": "LGPL-3.0+",
435                "GNU Library or Lesser General Public License": "LGPL-2.0",
436                "ISC License": "ISC",
437                "MIT License": "MIT",
438                "Mozilla Public License 1.0": "MPL-1.0",
439                "Mozilla Public License 1.1": "MPL-1.1",
440                "Mozilla Public License 2.0": "MPL-2.0",
441                "Zope Public License": "ZPL"
442                }
443            regexp = re.compile(r'^License :* *.* *:+ (.*)( \(.*\))?$')
444            classifiers_licenses = [regexp.sub(r"\1", lic)
445                                    for lic in self.metadata['info']['classifiers']
446                                    if regexp.match(lic)]
447            licenses = [license_dict[x] if x in license_dict else x for x in classifiers_licenses]
448            if not len(licenses):
449                print('WARNING: License has been set to "{license}". It is most'
450                      ' likely wrong, please change it if need be'.format(
451                          license=', '.join(licenses)))
452                licenses = [self.metadata['info']['license']]
453            licenses = set(licenses)
454            license_line = '{name}_LICENSE = {license}\n'.format(
455                name=self.mk_name,
456                license=', '.join(licenses))
457        else:
458            license_names = []
459            for license_file in license_files:
460                with open(license_file) as lic_file:
461                    match = liclookup.match(lic_file.read())
462                if match is not None and match.confidence >= 90.0:
463                    license_names.append(match.license.id)
464                else:
465                    license_names.append("FIXME: license id couldn't be detected")
466            license_names = set(license_names)
467
468            if len(license_names) > 0:
469                license_line = ('{name}_LICENSE ='
470                                ' {names}\n'.format(
471                                    name=self.mk_name,
472                                    names=', '.join(license_names)))
473
474        return license_line
475
476    def __create_mk_license(self):
477        """
478        Create the lines referring to the package's license informations of the
479        <package_name>.mk file
480
481        The license's files are found by searching the package (case insensitive)
482        for files named license, license.txt etc. If more than one license file
483        is found, the user is asked to select which ones he wants to use.
484        """
485        lines = []
486
487        filenames = ['LICENCE', 'LICENSE', 'LICENSE.MD', 'LICENSE.RST',
488                     'LICENSE.TXT', 'COPYING', 'COPYING.TXT']
489        self.license_files = list(find_file_upper_case(filenames, self.tmp_extract))
490
491        lines.append(self.__get_license_names(self.license_files))
492
493        license_files = [license.replace(self.tmp_extract, '')[1:]
494                         for license in self.license_files]
495        if len(license_files) > 0:
496            if len(license_files) > 1:
497                print('More than one file found for license:',
498                      ', '.join(license_files))
499            license_files = [filename
500                             for index, filename in enumerate(license_files)]
501            license_file_line = ('{name}_LICENSE_FILES ='
502                                 ' {files}\n'.format(
503                                     name=self.mk_name,
504                                     files=' '.join(license_files)))
505            lines.append(license_file_line)
506        else:
507            print('WARNING: No license file found,'
508                  ' please specify it manually afterwards')
509            license_file_line = '# No license file found\n'
510
511        return lines
512
513    def __create_mk_requirements(self):
514        """
515        Create the lines referring to the dependencies of the of the
516        <package_name>.mk file
517
518        Keyword Arguments:
519        pkg_name -- name of the package
520        pkg_req -- dependencies of the package
521        """
522        lines = []
523        dependencies_line = ('{name}_DEPENDENCIES ='
524                             ' {reqs}\n'.format(
525                                 name=self.mk_name,
526                                 reqs=' '.join(self.pkg_req)))
527        lines.append(dependencies_line)
528        return lines
529
530    def create_package_mk(self):
531        """
532        Create the lines corresponding to the <package_name>.mk file
533        """
534        pkg_mk = '{name}.mk'.format(name=self.buildroot_name)
535        path_to_mk = os.path.join(self.pkg_dir, pkg_mk)
536        print('Creating {file}...'.format(file=path_to_mk))
537        lines = self.__create_mk_header()
538        lines += self.__create_mk_download_info()
539        lines += self.__create_mk_setup()
540        lines += self.__create_mk_license()
541
542        lines.append('\n')
543        lines.append('$(eval $(python-package))')
544        lines.append('\n')
545        with open(path_to_mk, 'w') as mk_file:
546            mk_file.writelines(lines)
547
548    def create_hash_file(self):
549        """
550        Create the lines corresponding to the <package_name>.hash files
551        """
552        pkg_hash = '{name}.hash'.format(name=self.buildroot_name)
553        path_to_hash = os.path.join(self.pkg_dir, pkg_hash)
554        print('Creating {filename}...'.format(filename=path_to_hash))
555        lines = []
556        if self.used_url['digests']['md5'] and self.used_url['digests']['sha256']:
557            hash_header = '# md5, sha256 from {url}\n'.format(
558                url=self.metadata_url)
559            lines.append(hash_header)
560            hash_line = '{method}  {digest}  {filename}\n'.format(
561                method='md5',
562                digest=self.used_url['digests']['md5'],
563                filename=self.filename)
564            lines.append(hash_line)
565            hash_line = '{method}  {digest}  {filename}\n'.format(
566                method='sha256',
567                digest=self.used_url['digests']['sha256'],
568                filename=self.filename)
569            lines.append(hash_line)
570
571        if self.license_files:
572            lines.append('# Locally computed sha256 checksums\n')
573        for license_file in self.license_files:
574            sha256 = hashlib.sha256()
575            with open(license_file, 'rb') as lic_f:
576                while True:
577                    data = lic_f.read(BUF_SIZE)
578                    if not data:
579                        break
580                    sha256.update(data)
581            hash_line = '{method}  {digest}  {filename}\n'.format(
582                method='sha256',
583                digest=sha256.hexdigest(),
584                filename=license_file.replace(self.tmp_extract, '')[1:])
585            lines.append(hash_line)
586
587        with open(path_to_hash, 'w') as hash_file:
588            hash_file.writelines(lines)
589
590    def create_config_in(self):
591        """
592        Creates the Config.in file of a package
593        """
594        path_to_config = os.path.join(self.pkg_dir, 'Config.in')
595        print('Creating {file}...'.format(file=path_to_config))
596        lines = []
597        config_line = 'config BR2_PACKAGE_{name}\n'.format(
598            name=self.mk_name)
599        lines.append(config_line)
600
601        bool_line = '\tbool "{name}"\n'.format(name=self.buildroot_name)
602        lines.append(bool_line)
603        if self.pkg_req:
604            self.pkg_req.sort()
605            for dep in self.pkg_req:
606                dep_line = '\tselect BR2_PACKAGE_{req} # runtime\n'.format(
607                    req=dep.upper().replace('-', '_'))
608                lines.append(dep_line)
609
610        lines.append('\thelp\n')
611
612        help_lines = textwrap.wrap(self.metadata['info']['summary'], 62,
613                                   initial_indent='\t  ',
614                                   subsequent_indent='\t  ')
615
616        # make sure a help text is terminated with a full stop
617        if help_lines[-1][-1] != '.':
618            help_lines[-1] += '.'
619
620        # \t + two spaces is 3 char long
621        help_lines.append('')
622        help_lines.append('\t  ' + self.metadata['info']['home_page'])
623        help_lines = [x + '\n' for x in help_lines]
624        lines += help_lines
625
626        with open(path_to_config, 'w') as config_file:
627            config_file.writelines(lines)
628
629
630def main():
631    # Building the parser
632    parser = argparse.ArgumentParser(
633        description="Creates buildroot packages from the metadata of "
634                    "an existing PyPI packages and include it "
635                    "in menuconfig")
636    parser.add_argument("packages",
637                        help="list of packages to be created",
638                        nargs='+')
639    parser.add_argument("-o", "--output",
640                        help="""
641                        Output directory for packages.
642                        Default is ./package
643                        """,
644                        default='./package')
645
646    args = parser.parse_args()
647    packages = list(set(args.packages))
648
649    # tmp_path is where we'll extract the files later
650    tmp_prefix = 'scanpypi-'
651    pkg_folder = args.output
652    tmp_path = tempfile.mkdtemp(prefix=tmp_prefix)
653    try:
654        for real_pkg_name in packages:
655            package = BuildrootPackage(real_pkg_name, pkg_folder)
656            print('buildroot package name for {}:'.format(package.real_name),
657                  package.buildroot_name)
658            # First we download the package
659            # Most of the info we need can only be found inside the package
660            print('Package:', package.buildroot_name)
661            print('Fetching package', package.real_name)
662            try:
663                package.fetch_package_info()
664            except (six.moves.urllib.error.URLError, six.moves.urllib.error.HTTPError):
665                continue
666            if package.metadata_name.lower() == 'setuptools':
667                # setuptools imports itself, that does not work very well
668                # with the monkey path at the begining
669                print('Error: setuptools cannot be built using scanPyPI')
670                continue
671
672            try:
673                package.download_package()
674            except six.moves.urllib.error.HTTPError as error:
675                print('Error: {code} {reason}'.format(code=error.code,
676                                                      reason=error.reason))
677                print('Error downloading package :', package.buildroot_name)
678                print()
679                continue
680
681            # extract the tarball
682            try:
683                package.extract_package(tmp_path)
684            except (tarfile.ReadError, zipfile.BadZipfile):
685                print('Error extracting package {}'.format(package.real_name))
686                print()
687                continue
688
689            # Loading the package install info from the package
690            try:
691                package.load_setup()
692            except ImportError as err:
693                if 'buildutils' in err.message:
694                    print('This package needs buildutils')
695                else:
696                    raise
697                continue
698            except (AttributeError, KeyError) as error:
699                print('Error: Could not install package {pkg}: {error}'.format(
700                    pkg=package.real_name, error=error))
701                continue
702
703            # Package requirement are an argument of the setup function
704            req_not_found = package.get_requirements(pkg_folder)
705            req_not_found = req_not_found.difference(packages)
706
707            packages += req_not_found
708            if req_not_found:
709                print('Added packages \'{pkgs}\' as dependencies of {pkg}'
710                      .format(pkgs=", ".join(req_not_found),
711                              pkg=package.buildroot_name))
712            print('Checking if package {name} already exists...'.format(
713                name=package.pkg_dir))
714            try:
715                os.makedirs(package.pkg_dir)
716            except OSError as exception:
717                if exception.errno != errno.EEXIST:
718                    print("ERROR: ", exception.message, file=sys.stderr)
719                    continue
720                print('Error: Package {name} already exists'
721                      .format(name=package.pkg_dir))
722                del_pkg = input(
723                    'Do you want to delete existing package ? [y/N]')
724                if del_pkg.lower() == 'y':
725                    shutil.rmtree(package.pkg_dir)
726                    os.makedirs(package.pkg_dir)
727                else:
728                    continue
729            package.create_package_mk()
730
731            package.create_hash_file()
732
733            package.create_config_in()
734            print("NOTE: Remember to also make an update to the DEVELOPERS file")
735            print("      and include an entry for the pkg in packages/Config.in")
736            print()
737            # printing an empty line for visual confort
738    finally:
739        shutil.rmtree(tmp_path)
740
741
742if __name__ == "__main__":
743    main()
744