Example #1
0
    def check_requires(self):
        """ Checks, if there are missing dependencies
        """
        self.notify_part('Check dependencies')
        # get current requires
        requires = self.egginfo.install_requires

        # extend it with extra requires
        if self.egginfo.extras_require:
            for ename, erequires in self.egginfo.extras_require.items():
                requires.extend(erequires)

        print ' current requirements (including extras):'
        for egg in requires:
            print '    -', egg
        print ''

        # add the requirements without extras too
        for egg in requires[:]:
            if '[' in egg:
                requires.append(egg.split('[')[0])

        self.notify_check('Its not necessary to import some default plone / zope stuff')
        failures = False
        for egg in requires:
            if egg in PACKAGE_REQUIREMENTS_INADVISABLE and egg not in TESTING_PACKAGES:
                self.notify(False, 'Maybe you should remove the requirement ' +\
                                output.colorize(egg, output.ERROR) +\
                                output.colorize('. It seems to be in a python, ' +\
                                                    'zope or plone distribution and ' +\
                                                    'those packages should not be ' +\
                                                    'set as requirement.', output.WARNING),
                            problem_level=2)
                failures = True
        if not failures:
            self.notify(True)

        self.notify_check('Check imports on python files and zcml stuff')
        propose_requires = []

        # contains ipath:file mapping of python and zcml imports
        ipath_file_mapping = {}

        # SEARCH PYTHON FILES
        # make a grep on python files
        py_grep_results = runcmd("find . -name '*.py' -exec grep -Hr 'import ' {} \;",
                                 respond=True)
        # cleanup:
        # - strip rows
        # - remove the rows with spaces (they are not realle imports)
        # - remove "as xxx"
        py_grep_results = filter(lambda row: ' ' in row,
                                 [row.strip().split(' as ')[0] for row in py_grep_results])

        for row in py_grep_results:
            file_, statement = row.split(':')
            # make a import path
            ipath = statement.replace('from ', '').replace(' import ',
                                                           '.').replace('import', '').strip()
            ipath = ipath.replace('...', '').replace('>>>', '')
            ipath_parts = ipath.split('.')

            if '#' in ipath:
                continue

            # ignore namespace imports (python internals etc)
            if len(ipath_parts) == 1:
                continue

            ipath_file_mapping[ipath] = file_

        # SEARCH ZCML FILES
        cmd = "find . -name '*.zcml' -exec grep -Hr '\(layer\|package\|for\)=' {} \;"
        zcml_grep_results = runcmd(cmd, respond=True)

        # cleanup results
        zcml_xpr = re.compile('(for|layer)="(.*?)("|$)')
        for row in zcml_grep_results:
            file_, stmt = row.split(':', 1)
            stmt = stmt.strip()
            match = zcml_xpr.search(stmt)
            if not match:
                # maybe we have a more complicated statement (e.g. multiline)
                break
            ipath = match.groups()[1].strip()

            ipath = ipath.replace('*', '').strip()

            if '#' in ipath:
                continue

            if not ipath.startswith('.'):
                ipath_file_mapping[ipath] = file_

        # for later use
        guessed_related_packages = self._get_guessed_related_packages()

        # HANDLE ALL IMPORTS
        for ipath, file_ in ipath_file_mapping.items():
            ipath_parts = ipath.split('.')
            # ignore local imports
            if ipath.startswith(scm.get_package_name('.')):
                continue

            # is it already required?
            found = False
            for egg in requires:
                if ipath.startswith(egg):
                    found = True
                    break
            if not found:
                # is it already proposed?
                for egg in propose_requires:
                    if ipath.startswith(egg):
                        found = True
                        break
            if not found:
                # is it ignored?
                for egg in PACKAGE_REQUIREMENTS_INADVISABLE + TESTING_PACKAGES:
                    if ipath.startswith(egg):
                        found = True
                        break
            if found:
                continue

            # maybe we have a module which import relatively
            module_path = os.path.join(os.path.dirname(file_),
                                       ipath_parts[0])
            if os.path.isfile(module_path + '.py') or \
                    os.path.isfile(module_path + '/__init__.py'):
                continue

            # start on level 2 and for searching egg
            guessed_egg_names = ['.'.join(ipath_parts[:i])
                                 for i, part in enumerate(ipath_parts)
                                 if i > 1]

            # does one of the eggs exist?
            found = False

            # is there package in our src directory, if we are in one?
            for egg_name in guessed_egg_names:
                if egg_name.strip() in guessed_related_packages:
                    if egg_name.strip() not in propose_requires:
                        propose_requires.append(egg_name.strip())
                    found = True
                    break

            # .. in pypi
            if not found:
                for egg_name in guessed_egg_names:
                    if len(self.find_egg_in_index(egg_name)) > 0:
                        if egg_name.strip() not in propose_requires:
                            propose_requires.append(egg_name.strip())
                        found = True
                        break

            # .. or do we have one in the svn cache?
            if not found:
                for egg_name in guessed_egg_names:
                    if scm.guess_package_url(egg_name):
                        if egg_name.strip() not in propose_requires:
                            propose_requires.append(egg_name.strip())
                        found = True
                        break

            if not found:
                print '  ', output.colorize(ipath, output.INFO), 'in', \
                    output.colorize(file_, output.INFO), \
                    output.colorize('is not covered by requirements '
                                    'and I could find a egg with such a name',
                                    output.WARNING)

        if scm.get_package_name('.') in propose_requires:
            propose_requires.remove(scm.get_package_name('.'))

        if len(propose_requires)==0:
            self.notify(True)
            return
        propose_requires.sort()
        print ''
        print '  There are some requirements missing. I propose to add these:'
        for egg in propose_requires:
            print '   ', egg
        print ''

        # try to add them automatically:
        if input.prompt_bool('Should I try to add them?'):
            rows = open('setup.py').read().split('\n')
            # find "install_requires"
            frows = filter(lambda row:row.strip().startswith('install_requires='),
                           rows)
            if len(frows) != 1:
                output.error('Somethings wrong with your setup.py: expected only '
                             'one row containing "install_requires=", but '
                             'got %i' % len(frows), exit=1)
            insert_at = rows.index(frows[0]) + 1
            for egg in propose_requires:
                rows.insert(insert_at, ' ' * 8 + "'%s'," % egg)
            file_ = open('setup.py', 'w')
            file_.write('\n'.join(rows))
            file_.close()
            self._validate_setup_py()
            scm.add_and_commit_files('setup.py: added missing dependencies',
                                     'setup.py')
            self.notify_fix_completed()
Example #2
0
    def check_manifest(self):
        """ Checks the MANIFEST.in file and gives advices. It returns if the
        release action can be continued
        """
        if self.options.release_egg_only:
            return True

        namespace = scm.get_package_name('.').split('.')[0]

        required_lines = (
            'recursive-include %s *' % namespace,
            'recursive-include docs *',
            'include *.txt',
            'global-exclude *.pyc',
            'global-exclude ._*',
            )

        unused_lines = (
            'include setup.py',
            'include README.txt',
            'include CONTRIBUTORS.txt',
            'global-exclude *pyc',
            'global-exclude *mo',
            'global-exclude *.mo',
            )

        created = False
        modified = False
        commit_message = ''

        if not os.path.isfile('MANIFEST.in'):
            output.warning('Could not find the file ./MANIFEST.in, creating one')
            f = open('MANIFEST.in', 'w')
            f.write('\n'.join(required_lines))
            f.close()
            print 'created MANIFEST.in with following content:'
            print output.colorize(open('MANIFEST.in').read(), output.INFO)
            print ''
            commit_message = 'added MANIFEST.in for %s' % scm.get_package_name('.')
            created = True

        else:
            # check the existing file
            current_lines = [x.strip() for x in open('MANIFEST.in').readlines()]
            missing_lines = [x for x in required_lines
                             if x.strip() not in current_lines]
            files_to_remove = [x for x in unused_lines
                               if x.strip() in current_lines]

            if len(missing_lines) > 0 or len(files_to_remove) > 0:
                new_lines = current_lines

                if len(missing_lines) > 0:
                    output.warning('./MANIFEST.in: added some required lines:')
                    print output.colorize('\n'.join(missing_lines), output.INFO)
                    print ''
                    new_lines += missing_lines

                if len(files_to_remove) > 0:
                    output.warning('./MANIFEST.in: removed some unused lines:')
                    print output.colorize('\n'.join(files_to_remove), output.ERROR)
                    print ''
                    new_lines = filter(lambda x:x.strip() not in files_to_remove,
                                       new_lines)

                f = open('MANIFEST.in', 'w')
                f.write('\n'.join(new_lines))
                f.close()
                commit_message = 'updated MANIFEST.in for %s' % scm.get_package_name('.')
                modified = True

        if created or modified:
            # commit it ?
            if input.prompt_bool('Would you like to commit the MANIFEST.in?'):
                if created:
                    scm.add_and_commit_files(commit_message, 'MANIFEST.in')
                    return True
                elif modified:
                    scm.commit_files(commit_message, 'MANIFEST.in')
                    return True
            return False

        else:
            return True
Example #3
0
    def check_setup_py(self):
        """setup.py checks
        """
        self.notify_part('Check setup.py')

        # MAINTAINER
        self.notify_check('Maintainer should be defined')
        maintainer = self.egginfo.get_maintainer()
        if maintainer and maintainer != 'UNKNOWN':
            self.notify(True)
        else:
            if len(filter(lambda row: row.startswith('maintainer'),
                          open('setup.py').read().split('\n'))) > 0:
                self.notify(False, 'maintainer is defined as variable but is '
                            'not used in setup call',
                            'add "maintainer=maintainer," to the setup call',
                            1, pause=False)
                if input.prompt_bool('Should I try to fix it?'):
                    rows = open('setup.py').read().split('\n')
                    file_ = open('setup.py', 'w')
                    found = False
                    for i, row in enumerate(rows):
                        file_.write(row)
                        if i != len(rows) - 1:
                            file_.write('\n')
                        if row.strip().startswith('author_email='):
                            file_.write(' ' * 6)
                            file_.write('maintainer=maintainer,')
                            file_.write('\n')
                            found = True
                    file_.close()
                    if not found:
                        output.error('Could not find keyword author_email in '
                                     'your setup.py, you have to fix it '
                                     'manually, sorry.', exit=1)
                    else:
                        self._validate_setup_py()
                        scm.add_and_commit_files(
                            'setup.py: register maintainer',
                            'setup.py')
                        self.notify_fix_completed()
                    print ''
            else:
                self.notify(False,
                            'maintainer is not defined in the egg at all',
                            'check %s on how to define a maintainer' % \
                                WIKI_PYTHON_EGGS)

        # VERSION.TXT
        self.notify_check('version.txt file exists')
        versiontxt_path = scm.get_package_name('.').replace('.', '/') + \
            '/version.txt'
        if os.path.exists(versiontxt_path) and os.path.isfile(versiontxt_path):
            self.notify(True)
        elif os.path.exists(versiontxt_path):
            self.notify(False, '%s exists but is not a file !?' % \
                            versiontxt_path,
                        'it should be a file containing the package version',
                        0)
        else:
            self.notify(False, '%s does not exist' % versiontxt_path,
                        pause=False)
            if input.prompt_bool('Should I try to fix it?'):
                version = self.egginfo.get_version()
                file_ = open(versiontxt_path, 'w')
                file_.write(version)
                file_.close()
                scm.add_and_commit_files('Added version.txt file',
                                         versiontxt_path)
                self.notify_fix_completed()
                print ''

        # VERSION
        self.notify_check('Version is taken form version.txt')
        rows = open('setup.py').read().split('\n')
        version_rows = filter(lambda row: row.startswith('version ='), rows)
        if len(version_rows) != 1:
            self.notify(False, 'I\'m confused, it seems that you have a mess '
                        'with your versions.',
                        'check %s on how to define versions properly' % \
                            WIKI_PYTHON_EGGS)
        elif not version_rows[0].startswith('version = open('):
            self.notify(False, 'I\'m guessing that the version in your '
                        'setup.py is not taken from %s' % versiontxt_path,
                        'check %s on how to define versions properly' % \
                            WIKI_PYTHON_EGGS, pause=False)
            if input.prompt_bool('Should I try to fix it?'):
                new_version_row = "version = open('%s').read().strip()" % \
                    versiontxt_path
                rows[rows.index(version_rows[0])] = new_version_row
                file_ = open('setup.py', 'w')
                file_.write('\n'.join(rows))
                file_.close()
                self._validate_setup_py()
                scm.add_and_commit_files('setup.py: using version.txt',
                                         'setup.py')
                self.notify_fix_completed()
        else:
            self.notify(True)

        # NAMESPACES
        self.notify_check('Check namespaces')
        guessed_namespaces = []
        namespace_parts = scm.get_package_name('.').split('.')
        for i, space in enumerate(namespace_parts[:-1]):
            guessed_namespaces.append('.'.join(namespace_parts[:i + 1]))
        if set(guessed_namespaces) == set(self.egginfo.namespace_packages):
            self.notify(True)
        else:
            print '  current namespaces: ', str(
                self.egginfo.namespace_packages)
            print '  expected namespaces:', str(guessed_namespaces)
            print '  package name:       ', scm.get_package_name('.')
            self.notify(False, 'I think your namespace_packages declaration '
                        'is wrong', pause=False)
            if input.prompt_bool('Should I try to fix it?'):
                guessed_namespaces.sort()
                rows = open('setup.py').read().split('\n')
                nsrows = filter(lambda x:
                                    x.strip().startswith('namespace_packages'),
                                rows)
                if len(nsrows) != 1:
                    output.error('Could not fix it: expected one and only one'
                                 ' line beginning with "namespace_packages"'
                                 ' in setup.py..',
                                 exit=True)
                else:
                    new_row = nsrows[0].split('=')[0] + '=' + \
                        str(guessed_namespaces) + ','
                    rows[rows.index(nsrows[0])] = new_row
                    file_ = open('setup.py', 'w')
                    file_.write('\n'.join(rows))
                    file_.close()
                    self._validate_setup_py()
                    scm.add_and_commit_files(
                        'setup.py: fixed namespace_packages', 'setup.py')
                    self.notify_fix_completed()

        # VARIOUS CHECKS
        self.notify_check('Various setup.py checks')
        failure = False

        # .. name
        if self.egginfo.get_name() != scm.get_package_name('.'):
            failure = True
            self.notify(False, 'Name: Expected name in setup.py to be ' + \
                            '"%s" ' % scm.get_package_name('.') + \
                            'but it was "%s"' % self.egginfo.get_name())

        # maintainer in description
        if self.egginfo.get_maintainer() not in self.egginfo.get_description():
            failure = True
            self.notify(False, 'Description: Maintainer is not defined '
                        'in description',
                        'Check out %s' % WIKI_PYTHON_EGGS, 2)

        # docs/HISTORY.txt
        setuppy = open('setup.py').read()
        if 'HISTORY.txt' not in setuppy or \
                not os.path.exists('docs/HISTORY.txt') or \
                open('docs/HISTORY.txt').read() not in \
                self.egginfo.get_long_description():
            self.notify(False, 'docs/HISTORY.txt embedded be used in '
                        'long_description',
                        'Check long_description on %s' % WIKI_PYTHON_EGGS)

        # author: use maintainer
        expected_author = '%s, 4teamwork GmbH' % self.egginfo.get_maintainer()
        if self.egginfo.get_author() != expected_author:
            failure = True
            self.notify(False, 'Author: Expected author to be "%s""' % \
                            expected_author + \
                            ' but it is "%s"' % self.egginfo.get_author(),
                        'Check out %s' % WIKI_PYTHON_EGGS, 2)

        # author email
        if self.egginfo.get_author_email() != 'mailto:[email protected]':
            failure = True
            self.notify(False, 'Author email: the email should be'
                        ' "mailto:[email protected]"',
                        'Check out %s' % WIKI_PYTHON_EGGS, 2)

        # license
        if self.egginfo.get_license() != 'GPL2':
            failure = True
            self.notify(False, 'License: the license should be "GPL2"',
                        'Check out %s' % WIKI_PYTHON_EGGS, 2)

        # .. ok?
        if not failure:
            self.notify(True)

        # run egg_info
        self.notify_check('we should be able to run `setup.py egg_info`')
        state, out, errors = self._validate_setup_py()
        if out:
            print '   ', out.replace('\n', '\n    ')
        if errors:
            print '   ', errors.replace('\n', '\n    ')
        if state == 0:
            self.notify(True)
        else:
            state.notify(False, 'Cant run `python setup.py egg_info`, see '
                         'errors above', 0)