Exemple #1
0
def make_fake_repository(packages, root):
    """Create a new git repository that contains the given Rez packages.

    This function is a bit hacky. It creates a git repository, which
    is fine, but Rez packages create "in-memory" packages based on
    actual package.py files on-disk. So the packages need to copied
    to the folder where the repository was created and "re-queried"
    in order to be "true" developer Rez packages. If there was a way
    to easily create a developer Rez package, I'd do that. But meh,
    too much work.

    Args:
        packages (iter[:class:`rez.developer_package.DeveloperPackage`]):
            The Rez packages that will be copied into the new git repository.
        root (str): The folder on-disk that represents the top-level folder
            for every Rez package in `packages`.

    Returns:
        tuple[:class:`git.Repo`, list[:class:`rez.developer_package.DeveloperPackage`]]:
            The newly created repository + the Rez packages that got copied into it.

    """
    repository_root = os.path.join(
        tempfile.mkdtemp(suffix="_clone_repository"), "test_folder")
    os.makedirs(repository_root)

    initialized_packages = []

    for package in packages:
        package_root = finder.get_package_root(package)
        relative = os.path.relpath(package_root, root)
        destination = os.path.join(repository_root, relative)
        parent = os.path.dirname(destination)

        if not os.path.isdir(parent):
            os.makedirs(parent)

        shutil.copytree(package_root, destination)

        initialized_packages.append(
            packages_.get_developer_package(destination))

    remote_root = tempfile.mkdtemp(suffix="_remote_bare_repository")
    # `git.Repo.init` needs to build from a non-existent folder. So we `shutil.rmtree` here
    shutil.rmtree(remote_root)

    remote_root += ".git"  # bare repositories, by convention, end in ".git"
    remote = git.Repo.init(remote_root, bare=True)

    repository = git.Repo.init(repository_root)
    repository.index.add(
        [item for item in os.listdir(repository_root) if item != ".git"])
    repository.index.commit("initial commit")
    repository.create_remote("origin", url=remote.working_dir)
    origin = repository.remotes.origin
    origin.push(refspec="master:master")
    repository.heads.master.set_tracking_branch(
        origin.refs.master)  # set local "master" to track remote "master

    return repository, initialized_packages, remote_root
Exemple #2
0
def _run_and_catch(function, package):
    known_issues = (
        # :func:`is_not_a_python_package` can potentially raise any of these exceptions
        rez_exceptions.PackageNotFoundError,
        rez_exceptions.ResolvedContextError,
        # :func:`has_documentation` raises this exception
        exceptions.NoGitRepository,
    )

    try:
        return function(package), []
    except known_issues as error:
        path = finder.get_package_root(package)

        return (
            False,
            [
                exceptions.InvalidPackage(package, os.path.normpath(path),
                                          str(error))
            ],
        )
    except Exception as error:  # pylint: disable=broad-except
        path, message = worker.handle_generic_exception(error, package)

        return (
            False,
            [
                exceptions.InvalidPackage(package,
                                          path,
                                          "Generic error: " + message,
                                          full_message=str(error))
            ],
        )
Exemple #3
0
def get_repository(package):
    """Get the git repository of a Rez package.

    Args:
        package (:class:`rez.packages_.Package`):
            The object that should either have a git repository defined
            or is itself inside of a git repository.

    Raises:
        ValueError: If `package` is broken or not in a Git repository.

    Returns:
        :class:`git.Repo`:
            The git repository, if found. This function will either
            always return something or error out.

    """
    path = finder.get_package_root(package)

    if not path:
        raise ValueError(
            'Package "{package}" has no path on-disk.'.format(package=package))

    try:
        return git.Repo(path, search_parent_directories=True)
    except exc.InvalidGitRepositoryError:  # pylint: disable=no-member
        try:
            return _guess_repository_from_symlinks(path)
        except (
                RuntimeError,
                exc.InvalidGitRepositoryError,  # pylint: disable=no-member
        ):
            raise ValueError(
                'Package "{package}" in "{path}" has no Git repository.'.
                format(package=package, path=path))
    def run(cls, package, _):
        """Find a README.md file using a Rez package.

        Args:
            package (:class:`rez.packages_.DeveloperPackage`):
                The Rez package whose root directory will be used to
                search for a README file.

        Returns:
            list[:class:`.Description`]:
                If no issues are found, return an empty list. Otherwise,
                return one description of each found issue.

        """
        root = finder.get_package_root(package)

        if cls._file_name in [
                os.path.splitext(name)[0] for name in os.listdir(root)
        ]:
            return []

        code = base_checker.Code(short_name="E", long_name=cls.get_long_code())

        location = message_description.Location(
            path=root,
            row=0,
            column=0,
            text="",
        )

        return [
            message_description.Description([cls._summary],
                                            location,
                                            code=code),
        ]
def handle_generic_exception(error, package):
    """Get a printable strin for an error get the path to the Rez package that triggered it.

    This function is mainly used for handling exceptions.

    Args:
        error (:class:`Exception`):
            Some Python exception.
        package (:class:`rez.packages_.DeveloperPackage`):
            The package that triggered the error.

    Returns:
        tuple[str, str]: The found path and the exception message.

    """
    # Plugin functions can raise any exception so they must be caught, here
    path = finder.get_package_root(package)

    if hasattr(error, "message"):
        message = error.message
    else:
        message = str(error)

    if not message:
        try:
            message = error.value.short_msg
        except AttributeError:
            pass

    return path, str(message)
Exemple #6
0
    def test_source_variant(self, run_command, has_documentation):
        """Create a source (non-built) Rez package that has 1+ variants and run a command on it.

        Args:
            run_command (:class:`mock.MagicMock`):
                A replacement for the function that would normally run
                as part of the commands that run on a Rez package. If
                this function gets run, we know that this test passes.

        """
        has_documentation.side_effect = [False, True]

        packages = self._setup_test(
            run_command, package_common.make_source_variant_python_package)
        roots = list(
            set(
                inspection.get_packages_path_from_package(package)
                for package in packages))

        directory = tempfile.mkdtemp(suffix="_some_build_location")
        self.delete_item_later(directory)

        build_package = package_common.make_build_python_package(
            textwrap.dedent("""\
                name = "project_b"
                version = "2.0.0"

                revision = {
                    "push_url": "fake_git_repo",
                }
                """),
            "project_b",
            "2.0.0",
            directory,
        )
        build_package = finder.get_nearest_rez_package(
            os.path.join(build_package))
        build_root = inspection.get_packages_path_from_package(build_package)
        paths = roots + [build_root]

        with rez_configuration.patch_packages_path(paths):
            self._test(
                (
                    set(),
                    [],
                    [
                        # Important note: "project_b" doesn't actually already
                        # have documentation. It's a result of `side_effect`, above.
                        #
                        worker.Skip(
                            build_package,
                            finder.get_package_root(build_package),
                            "Python package already has documentation.",
                        )
                    ],
                ),
                paths,
            )

        self.assertEqual(1, run_command.call_count)
    def _is_definition(package, format_, search=True):
        """Check for if a package defines a Rez package file.

        Except if we've already checked the Rez package's repository
        and still haven't found a good answer, stop searching and just
        return False, to avoid a cyclic loop.

        """
        if not inspection.is_built_package(package):
            path = finder.get_package_root(package)

            try:
                packages_.get_developer_package(path, format=format_)
            except rez_exceptions.PackageMetadataError:
                return False

            return True

        if not search:
            return False

        repository = get_repository(package)
        repository_package = get_package(repository.working_dir, package.name)

        return _is_definition(repository_package, format_=format_, search=False)
    def run(cls, package, _):
        """Check a Rez package for the ``help`` attribute.

        Args:
            package (:class:`rez.packages_.DeveloperPackage`): The Rez package
                that will be checked for help links.
            _: An unused argument.

        """
        if package.help:
            return []

        summary = "The help attribute is undefined or empty"
        full = [
            summary,
            "Every Rez package should always point to some documentation.",
            "Reference: https://github.com/nerdvegas/rez/wiki/Package-Definition-Guide#help",
        ]
        root = finder.get_package_root(package)
        code = base_checker.Code(short_name="E", long_name=cls.get_long_code())
        location = message_description.Location(path=root,
                                                row=0,
                                                column=0,
                                                text="")

        return [
            message_description.Description([summary],
                                            location,
                                            code=code,
                                            full=full),
        ]
Exemple #9
0
    def _run_command_on_package(package):
        """Run the user-provided command on the given Rez package.

        Args:
            package (:class:`rez.developer_package.DeveloperPackage`):
                The Rez package that presumably is a package.yaml file
                that needs to be changed.

        Raises:
            :class:`.InvalidPackage:
                If `package` is not a package.yaml file.

        Returns:
            str: Any error message that occurred from this command, if any.

        """
        if not package.filepath.endswith(".yaml"):
            raise exceptions.InvalidPackage(
                package,
                finder.get_package_root(package),
                'Package "{package}" is not a YAML file.'.format(
                    package=package),
            )

        buffer_ = io.StringIO()
        package.print_info(format_=serialise.FileFormat.py, buf=buffer_)
        code = buffer_.getvalue()

        path = os.path.join(finder.get_package_root(package), "package.py")
        _LOGGER.info('Now creating "%s".', path)

        # Writing to DeveloperPackage objects is currently bugged.
        # So instead, we disable caching during write.
        #
        # Reference: https://github.com/nerdvegas/rez/issues/857
        #
        with filesystem.make_path_writable(
                os.path.dirname(os.path.dirname(path))):
            with serialise.open_file_for_write(path) as handler:
                handler.write(code)

        _LOGGER.info('Now removing the old "%s".', package.filepath)

        os.remove(package.filepath)

        return ""
def _has_versioned_parent_package_folder():
    """bool: Check if this unittest suite is being run from a built Rez package."""
    package = finder.get_nearest_rez_package(_CURRENT_DIRECTORY)
    root = finder.get_package_root(package)
    folder = os.path.basename(root)

    return folder == os.environ["REZ_{name}_VERSION".format(
        name=package.name.upper())]
Exemple #11
0
def _get_missing_documentation_packages(paths=None):
    packages, invalids, skips = conditional.get_default_latest_packages(
        paths=paths)
    invalids = []
    output = []

    for package in packages:
        result, invalids_ = _run_and_catch(conditional.is_not_a_python_package,
                                           package)

        if result:
            skips.append(
                worker.Skip(
                    package,
                    os.path.normpath(finder.get_package_root(package)),
                    "Rez Package does not define Python packages / modules.",
                ))

            continue

        if invalids_:
            invalids.extend(invalids_)

            continue

        result, invalids_ = _run_and_catch(conditional.has_documentation,
                                           package)

        if result:
            skips.append(
                worker.Skip(
                    package,
                    finder.get_package_root(package),
                    "Python package already has documentation.",
                ))

            continue

        if invalids_:
            invalids.extend(invalids_)

            continue

        output.append(package)

    return output, invalids, skips
Exemple #12
0
def _get_packages_which_must_be_changed(paths=None):
    """Get every Rez package that has imports to replace.

    Args:
        paths (list[str], optional):
            The directories to search for Rez package families,
            Default: :attr:`rez.config.config.packages_path`.

    Returns:
        tuple[:class:`rez.packages_.Package`, list, list]:
            All of the found Rez packages and a list of any Rez package
            that was considered invalid or any Rez packages that were
            valid but must be skipped, for some reason.

    """
    packages, invalids, skips = conditional.get_default_latest_packages(
        paths=paths)
    user_provided_namespaces = _get_user_provided_namespaces()
    expected_existing_namespaces = {old for old, _ in user_provided_namespaces}
    output = []

    for package in packages:
        if not repository_area.is_definition(package, serialise.FileFormat.py):
            skips.append(
                worker.Skip(
                    package,
                    finder.get_package_root(package),
                    "does not define a package.py file.",
                ))

            continue

        if not _needs_replacement(package, expected_existing_namespaces):
            skips.append(
                worker.Skip(
                    package,
                    finder.get_package_root(package),
                    "No namespaces need to be replaced.",
                ))

            continue

        output.append(package)

    return output, invalids, skips
Exemple #13
0
    def _run_command(package, arguments):
        """Run the user-provided command on the given Rez package.

        Args:
            package (:class:`rez.developer_package.DeveloperPackage`):
                The Rez package that will be used changed. Any command
                run by this function will do so while cd'ed into the
                directory of this package.
            arguments (:class:`argparse.Namespace`):
                The user-provided, plug-in specific arguments.
                Specifically, this should bring in

                - The command that the user wants to run, per-package
                - The name of the ticket that will be used for git branches
                - The option to "raise an exception if any error occurs"
                  or whether it's OK to continue.

        Raises:
            :class:`.CoreException`:
                If ``exit_on_error`` is enabled and the user-provided
                command fails, for any reason.

        Returns:
            str: Any error message that occurred from this command, if any.

        """
        command = 'cd "{root}";{arguments.command}'.format(
            root=finder.get_package_root(package), arguments=arguments)
        _LOGGER.debug('Command to run "%s".', command)

        process = subprocess.Popen(command,
                                   shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)
        stdout, stderr = process.communicate()
        _LOGGER.debug('stdout "%s".', stdout)

        if stderr:
            message = ('Package "{package.name}" raised an error when '
                       '"{command}" command was run. The command may '
                       "not have been added correctly.".format(
                           package=package, command=command))

            _LOGGER.error("Found error.")
            _LOGGER.error(stderr)

            if arguments.exit_on_error:
                raise exceptions.CoreException(message + "\n\n" + stderr)

            return message

        if not has_changes(package):
            return (
                'Command "{arguments.command}" ran but nothing on-disk changed. '
                "No PR is needed!".format(arguments=arguments))

        return ""
Exemple #14
0
    def run(cls, package, _):
        """Find every URL in a Rez package that points to a bad web address.

        If the Internet is down, this method returns empty and is skipped.

        Args:
            package (:class:`rez.packages_.Package`):
                A Rez package that may have a ``help`` attribute/function defined.
                This attribute will be checked for issues.
            _: An un-used argument.

        """
        if not package.help:
            return []

        if not website.is_internet_on():
            _LOGGER.warning(
                "User has no internet. Checking for help URLs will be skipped."
            )

            return []

        urls = url_help.get_invalid_help_urls(package)

        if not urls:
            return []

        urls = cls._filter_existing_paths(urls,
                                          finder.get_package_root(package))

        if not urls:
            return []

        summary = "Package help has an un-reachable URL"
        urls = sorted([(label, url) if label else url
                       for _, label, url in urls])
        full = [
            summary,
            'Found URLs are un-reachable "{urls}".'.format(urls=urls),
        ]

        row = package_parser.get_definition_row(package.filepath, "help")
        location = message_description.Location(
            path=package.filepath,
            row=row,
            column=0,
            text="",
        )
        code = base_checker.Code(short_name="D", long_name=cls.get_long_code())

        return [
            message_description.Description([summary],
                                            location,
                                            code=code,
                                            full=full),
        ]
Exemple #15
0
    def test_mix(self, run_command):
        """Run command on only to the Rez packages that need it.

        Args:
            run_command (:class:`mock.MagicMock`):
                A replacement for the function that would normally run
                as part of the commands that run on a Rez package. If
                this function gets run, we know that this test passes.

        """
        run_command.return_value = ""
        root = os.path.join(tempfile.mkdtemp(), "test_folder")
        os.makedirs(root)
        self.delete_item_later(root)

        packages = [
            package_common.make_package("project_a", root,
                                        package_common.make_source_package),
            package_common.make_package(
                "project_b",
                root,
                package_common.make_source_python_package,
                dependencies={"project_a-1+<2"},
            ),
        ]

        repository, packages, remote_root = package_common.make_fake_repository(
            packages, root)
        self.delete_item_later(repository.working_dir)
        self.delete_item_later(remote_root)

        release_path = _release_packages(packages)
        self.delete_item_later(release_path)

        package = finder.get_nearest_rez_package(
            os.path.join(release_path, "project_a", "1.0.0"))

        self._test(
            (
                set(),
                [],
                [
                    worker.Skip(
                        package,
                        finder.get_package_root(package),
                        "Rez Package does not define Python packages / modules.",
                    )
                ],
            ),
            [release_path],
        )
        self.assertEqual(1, run_command.call_count)
    def _make_generic_installed_package(self):
        package = self._make_installed_package(
            "some_package",
            textwrap.dedent(
                """\
                name = "some_package"
                version = "1.0.0"
                build_command = "echo 'foo'"
                """
            ),
        )

        return finder.get_package_root(package)
Exemple #17
0
    def test_released_dependency_missing(self, run_command):
        """Fail to resolve a package because "project_a" could not be found.

        Args:
            run_command (:class:`mock.MagicMock`):
                A replacement for the function that would normally run
                as part of the commands that run on a Rez package. If
                this function gets run, we know that this test passes.

        """
        run_command.return_value = ""
        root = os.path.join(tempfile.mkdtemp(), "test_folder")
        os.makedirs(root)
        self.delete_item_later(root)

        packages = [
            package_common.make_package(
                "project_a", root, package_common.make_source_python_package),
            package_common.make_package(
                "project_b",
                root,
                package_common.make_source_python_package,
                dependencies={"project_a-1+<2"},
            ),
        ]

        repository, packages, remote_root = package_common.make_fake_repository(
            packages, root)
        self.delete_item_later(repository.working_dir)
        self.delete_item_later(remote_root)

        package = packages[1]
        package_root = finder.get_package_root(package)

        with rez_configuration.patch_packages_path([repository.working_dir]):
            self._test(
                (
                    set(),
                    [
                        exceptions.InvalidPackage(
                            package,
                            package_root,
                            "Package could not be found: project_a-1+<2",
                        )
                    ],
                    [],
                ),
                [repository.working_dir],
            )

        self.assertEqual(1, run_command.call_count)
    def _make_test_case(self, name, text, extra_paths=None):
        """Create a Rez package that has some Python files inside of it.

        This method exists to make unittesting a bit easier to understand.

        Note:
            This method creates Python files whether `text` actually
            appends to PYTHONPATH or not. They just get ignored by
            unittests, in that case.

        Args:
            name (str):
                The name of the Rez package family to make.
            text (str):
                The source code used for the package.py file.
            extra_paths (list[str], optional):
                A list of paths used to search for Rez packages
                during the Rez resolve that this function calls.
                If no paths are given, Rez will default to
                :attr:`rez.config.packages_path` instead. Default is
                None.

        Returns:
            tuple[set[str], str]:
                The found Python files (excluding __init__.py files)
                and the parent folder where the created package.py goes.

        """
        if not extra_paths:
            extra_paths = []

        package = self._make_fake_rez_source_package(name, text)
        root = finder.get_package_root(package)
        python_root = os.path.join(root, "python")
        os.makedirs(python_root)

        open(os.path.join(python_root, "thing.py"), "w").close()
        open(os.path.join(python_root, "another.py"), "w").close()

        context = resolved_context.ResolvedContext(
            ["{package.name}==".format(package=package)],
            package_paths=[inspection.get_packages_path_from_package(package)]
            + extra_paths + config.packages_path,  # pylint: disable=no-member
        )

        paths = inspection.get_package_python_paths(
            package,
            context.get_environ().get("PYTHONPATH", "").split(os.pathsep))

        return paths, root
Exemple #19
0
def _needs_replacement(package, user_namespaces):
    """Figure out if the Rez package has Python files in it that :class:`MoveImports` can act upon.

    The logic goes like this:

    - A Rez source python package is assumed to have all its files available
        - Return whether namespaces are found
    - If the Rez python package is a released package though,
      we can't just assume all Python files are available.
        - The built Rez package may output an .egg or .whl file, for example
        - So if namespaces are found, then return True
        - If no namespaces are found, clone the package's repository and try again

    Args:
        package (:class:`rez.packages_.Package`):
            Some Rez package (source or released package) to check for Python imports.
        user_namespaces (set[str]):
            Python dot-separated namespaces which a user is trying to
            replace. If any of the found namespaces match these then
            it means `package` must have at least one of its modules
            replaced.

    Returns:
        bool:
            If any of the user-provided Python namespaces were found
            inside of the given `package`.

    """
    root = finder.get_package_root(package)
    package = finder.get_nearest_rez_package(root)

    namespaces = set()

    for path in move_break_api.expand_paths(root):
        if path == package.filepath:
            continue

        namespaces.update(move_break_api.get_namespaces(path))

    if namespaces.intersection(user_namespaces):
        return True

    if not inspection.is_built_package(package):
        return False

    repository = repository_area.get_repository(package)
    repository_package = repository_area.get_package(repository.working_dir,
                                                     package.name)

    return _needs_replacement(repository_package, user_namespaces)
    def _make_symlinkable_source_package(self):
        """Make a Rez package that builds using symlinks.

        Returns:
            :class:`rez.developer_package.DeveloperPackage`: The generated Rez package.

        """
        root = tempfile.mkdtemp(suffix="_source_root")
        self.delete_item_later(root)
        package = package_common.make_package(
            "package_name", root, package_common.make_source_python_package)
        package_root = finder.get_package_root(package)
        rezbuild_file = os.path.join(package_root, "rezbuild.py")

        with open(rezbuild_file, "w") as handler:
            handler.write(
                textwrap.dedent('''\
                    #!/usr/bin/env python
                    # -*- coding: utf-8 -*-

                    """The main module which installs Maya onto the user's system."""

                    # IMPORT STANDARD LIBRARIES
                    import os
                    import shutil
                    import sys


                    def build(source_path, install_path):
                        for folder in ("python", ):
                            destination = os.path.join(install_path, folder)

                            if os.path.isdir(destination):
                                shutil.rmtree(destination)
                            elif os.path.isfile(destination):
                                os.remove(destination)

                            source = os.path.join(source_path, folder)
                            os.symlink(source, destination)


                    if __name__ == "__main__":
                        build(
                            source_path=os.environ["REZ_BUILD_SOURCE_PATH"],
                            install_path=os.environ["REZ_BUILD_INSTALL_PATH"],
                        )
                    '''))

        return package
    def _test(self, package):
        """Get documentation URL links that are missing for a given Rez package file.

        Args:
            package (:class:`rez.packages_.Package`): The Rez package to test.

        Returns:
            set[tuple[int, str, str]]:
                The position, help command label, and URL for each invalid URL found.

        """
        self.delete_item_later(finder.get_package_root(package))
        self._fake_package_root()

        return cli.find_intersphinx_links(package.requires or [])
Exemple #22
0
    def test_recursive(self):
        """Make sure that getting plugins recursively works."""
        package = finder.get_nearest_rez_package(_CURRENT_DIRECTORY)
        found, invalids = cli._find_rez_packages(  # pylint: disable=protected-access
            finder.get_package_root(package),
            recursive=True,
        )
        found_package = next(iter((found)))

        self.assertEqual(1, len(found))
        self.assertTrue(bool(found))
        self.assertEqual(found_package.name, package.name)
        self.assertEqual(found_package.filepath, package.filepath)
        self.assertEqual(found_package.version, package.version)
        self.assertEqual(set(), invalids)
def _bump(package, increment, new_dependencies):
    rez_bump_api.bump(package, **{increment: 1})

    with open(package.filepath, "r") as handler:
        code = handler.read()

    new_code = api.add_to_attribute("requires", new_dependencies, code)

    with filesystem.make_path_writable(
            os.path.dirname(os.path.dirname(package.filepath))):
        with serialise.open_file_for_write(package.filepath) as handler:
            handler.write(new_code)

    root = finder.get_package_root(package)

    return finder.get_nearest_rez_package(root)
Exemple #24
0
def add_build_file(package, name):
    """Change a Rez package so it's "buildable" by Rez again.

    Specifically, give the package a rezbuild.py file.

    Args:
        package (:class:`rez.packages_.Package`):
            A rez-pip generated Rez package which will be modified.
        name (str): The name which will be used for the build file.
            Usually this is "rezbuild.py".

    """
    root = finder.get_package_root(package)

    with open(os.path.join(root, name), "w") as handler:
        handler.write(_get_build_command())
    def test_normal(self):
        """Release a package and make sure it is valid."""
        def _make_fake_git_repository_at_directory(root):
            repository = git.Repo.init(root)
            repository.index.add(".")
            repository.index.commit("initial commit")

        source_path = tempfile.mkdtemp(suffix="_rez_package_source_path")
        self.delete_item_later(source_path)

        with open(os.path.join(source_path, "package.py"), "w") as handler:
            handler.write(
                textwrap.dedent("""\
                    name = "some_package"
                    version = "1.0.0"
                    build_command = "echo 'foo'"
                    """))

        _make_fake_git_repository_at_directory(source_path)

        package = packages_.get_developer_package(source_path)

        options = mock.MagicMock()
        options.cmd = "release"
        options.debug = False
        options.message = "Fake release message"
        options.no_message = False
        options.process = "local"
        options.variants = None
        options.vcs = None

        parser = mock.MagicMock()
        parser.prog = "rez release"

        release_path = tempfile.mkdtemp(suffix="_rez_package_release_path")
        self.delete_item_later(release_path)

        with wurlitzer.pipes():
            creator.release(finder.get_package_root(package), options, parser,
                            release_path)

        release_package = packages_.get_developer_package(
            os.path.join(release_path, "some_package", "1.0.0"))

        self.assertIsNotNone(release_package)
    def _wrap_in_repository(self, package):
        """Copy the given Rez package into a git repository and re-return it.

        Args:
            package (:class:`rez.packages_.Package`):
                The Rez package that will be copied.

        Returns:
            :class:`rez.packages_.Package`:
                The newly copied Rez package, located inside of the git repository.

        """
        root = os.path.dirname(finder.get_package_root(package))
        repository, packages, remote_root = package_common.make_fake_repository(
            [package], root)
        self.delete_item_later(repository.working_dir)
        self.delete_item_later(remote_root)

        return packages[0]
    def run(cls, package, context):
        """Find a documentation for the Rez package.

        Args:
            package (:class:`rez.packages_.DeveloperPackage`):
                The Rez package that may or may not need documentation.
            context (:class:`.Context`):
                A data instance that knows whether `package` has a
                Python package. If there's no Python package, this
                checker is cancelled.

        Returns:
            list[:class:`.Description`]:
                If no issues are found, return an empty list. Otherwise,
                return one description of each found issue.

        """
        if not _has_context_python_package(context):
            return []

        root = finder.get_package_root(package)

        if conf_manager.get_conf_file(root):
            return []  # pragma: no cover

        summary = "No documentation found"
        full = [
            summary,
            "Consider adding documentation to your Python package.",
            "Reference: https://www.sphinx-doc.org/en/master/usage/quickstart.html",
        ]
        code = base_checker.Code(short_name="E", long_name=cls.get_long_code())
        location = message_description.Location(path=root,
                                                row=0,
                                                column=0,
                                                text="")

        return [
            message_description.Description([summary],
                                            location,
                                            code=code,
                                            full=full),
        ]
def get_repository_url(package):
    """Get the git repository location (url or file path) of a Rez package.

    Args:
        package (:class:`rez.packages_.Package`):
            The object that should either have a git repository defined
            or is itself inside of a git repository.

    Raises:
        :class:`.exceptions.NoRepositoryRemote`:
            If `package` is a valid Rez package but doesn't point to a
            remote repository.

    Returns:
        str:
            The git repository url, if found. Usually, this function
            will either always return or error out.

    """
    try:
        url = package.revision["push_url"]

        # If the package was released, it should have a URL
        if url:
            return url
    except KeyError:
        # Very old Rez packages don't have push information
        raise exceptions.InvalidPackage(
            package,
            finder.get_package_root(package),
            'Package "{package}" has no repository URL.'.format(package=package),
        )
    except (
        # Happens when `package` is not the expected type
        AttributeError,
        TypeError,
    ):
        pass

    repository = get_repository(package)

    return get_repository_url_from_repository(repository)
    def test_partial_errors(self, find_api_documentation):
        """Allow other keys in the intersphinx mapping that may not be in the found requirements."""
        dependency1 = "foo_bar"
        dependency2 = "another_one"
        url = "https://some_path.com"

        find_api_documentation.return_value = url

        existing_intersphinx = {
            "fake_environment": ("http:/some_url.com", None)
        }
        package = self._make_fake_package_with_intersphinx_mapping(
            textwrap.dedent("""\
                name = "thing"
                version = "1.0.0"
                requires = [
                    "another_one",
                    "foo_bar",
                ]
                """),
            existing_intersphinx,
        )

        dependency1 = self._make_dependency_package("foo_bar", "1.0.0")
        dependency2 = self._make_dependency_package("another_one", "2.0.0")
        config.packages_path.append(  # pylint: disable=no-member
            inspection.get_packages_path_from_package(dependency1))
        config.packages_path.append(  # pylint: disable=no-member
            inspection.get_packages_path_from_package(dependency2))

        root = finder.get_package_root(package)

        self.assertEqual(existing_intersphinx,
                         cli.get_existing_intersphinx_links(root))

        expected = {
            dependency1.name: (url, None),
            dependency2.name: (url, None),
        }
        self.assertEqual(expected,
                         cli.find_intersphinx_links(package.requires or []))
    def _test_package(self, packages):
        """Make sure that the given `packages` get "fixed" properly.

        This method works by setting the "run" command to create a file
        in the user's source Rez package. If the file exists, it ran
        successfully. If it doesn't, then the command must have failed.

        Args:
            packages (iter[:class:`rez.packages_.Package`]):
                The Rez packages to run a command on. e.g. adding documentation.

        """
        name = "{base}.txt".format(base=uuid.uuid4())
        arguments = _make_arguments(name)
        command = registry.get_command("shell")
        runner = functools.partial(command.run, arguments=arguments)

        with mock.patch(
                "rez_batch_process.core.gitter.git_registry.get_remote_adapter"
        ) as patch:
            patch.create_pull_request = lambda *args, **kwargs: None
            ran, un_ran, invalids = worker.run(runner, packages)

        self.assertEqual((set(), []), (un_ran, invalids))

        package = next(iter(ran))
        package_root = finder.get_package_root(package)
        git_repository_root = os.path.dirname(package_root)
        git_repository = git.Repo(git_repository_root)

        for branch in git_repository.branches:
            if branch.name.startswith(arguments.pull_request_name):
                branch.checkout()

                break

        test_file = os.path.join(package_root, name)

        self.assertTrue(os.path.isfile(test_file))
        self.assertFalse(inspection.is_built_package(package))