1from __future__ import absolute_import
2
3import logging
4import os
5
6from pip._internal.cli import cmdoptions
7from pip._internal.cli.cmdoptions import make_target_python
8from pip._internal.cli.req_command import RequirementCommand, with_cleanup
9from pip._internal.cli.status_codes import SUCCESS
10from pip._internal.req.req_tracker import get_requirement_tracker
11from pip._internal.utils.misc import ensure_dir, normalize_path, write_output
12from pip._internal.utils.temp_dir import TempDirectory
13from pip._internal.utils.typing import MYPY_CHECK_RUNNING
14
15if MYPY_CHECK_RUNNING:
16    from optparse import Values
17    from typing import List
18
19logger = logging.getLogger(__name__)
20
21
22class DownloadCommand(RequirementCommand):
23    """
24    Download packages from:
25
26    - PyPI (and other indexes) using requirement specifiers.
27    - VCS project urls.
28    - Local project directories.
29    - Local or remote source archives.
30
31    pip also supports downloading from "requirements files", which provide
32    an easy way to specify a whole environment to be downloaded.
33    """
34
35    usage = """
36      %prog [options] <requirement specifier> [package-index-options] ...
37      %prog [options] -r <requirements file> [package-index-options] ...
38      %prog [options] <vcs project url> ...
39      %prog [options] <local project path> ...
40      %prog [options] <archive url/path> ..."""
41
42    def add_options(self):
43        # type: () -> None
44        self.cmd_opts.add_option(cmdoptions.constraints())
45        self.cmd_opts.add_option(cmdoptions.requirements())
46        self.cmd_opts.add_option(cmdoptions.build_dir())
47        self.cmd_opts.add_option(cmdoptions.no_deps())
48        self.cmd_opts.add_option(cmdoptions.global_options())
49        self.cmd_opts.add_option(cmdoptions.no_binary())
50        self.cmd_opts.add_option(cmdoptions.only_binary())
51        self.cmd_opts.add_option(cmdoptions.prefer_binary())
52        self.cmd_opts.add_option(cmdoptions.src())
53        self.cmd_opts.add_option(cmdoptions.pre())
54        self.cmd_opts.add_option(cmdoptions.require_hashes())
55        self.cmd_opts.add_option(cmdoptions.progress_bar())
56        self.cmd_opts.add_option(cmdoptions.no_build_isolation())
57        self.cmd_opts.add_option(cmdoptions.use_pep517())
58        self.cmd_opts.add_option(cmdoptions.no_use_pep517())
59
60        self.cmd_opts.add_option(
61            '-d', '--dest', '--destination-dir', '--destination-directory',
62            dest='download_dir',
63            metavar='dir',
64            default=os.curdir,
65            help=("Download packages into <dir>."),
66        )
67
68        cmdoptions.add_target_python_options(self.cmd_opts)
69
70        index_opts = cmdoptions.make_option_group(
71            cmdoptions.index_group,
72            self.parser,
73        )
74
75        self.parser.insert_option_group(0, index_opts)
76        self.parser.insert_option_group(0, self.cmd_opts)
77
78    @with_cleanup
79    def run(self, options, args):
80        # type: (Values, List[str]) -> int
81
82        options.ignore_installed = True
83        # editable doesn't really make sense for `pip download`, but the bowels
84        # of the RequirementSet code require that property.
85        options.editables = []
86
87        cmdoptions.check_dist_restriction(options)
88
89        options.download_dir = normalize_path(options.download_dir)
90        ensure_dir(options.download_dir)
91
92        session = self.get_default_session(options)
93
94        target_python = make_target_python(options)
95        finder = self._build_package_finder(
96            options=options,
97            session=session,
98            target_python=target_python,
99        )
100
101        req_tracker = self.enter_context(get_requirement_tracker())
102
103        directory = TempDirectory(
104            delete=not options.no_clean,
105            kind="download",
106            globally_managed=True,
107        )
108
109        reqs = self.get_requirements(args, options, finder, session)
110
111        preparer = self.make_requirement_preparer(
112            temp_build_dir=directory,
113            options=options,
114            req_tracker=req_tracker,
115            session=session,
116            finder=finder,
117            download_dir=options.download_dir,
118            use_user_site=False,
119        )
120
121        resolver = self.make_resolver(
122            preparer=preparer,
123            finder=finder,
124            options=options,
125            py_version_info=options.python_version,
126        )
127
128        self.trace_basic_info(finder)
129
130        requirement_set = resolver.resolve(
131            reqs, check_supported_wheels=True
132        )
133
134        downloaded = []  # type: List[str]
135        for req in requirement_set.requirements.values():
136            if req.satisfied_by is None:
137                assert req.name is not None
138                preparer.save_linked_requirement(req)
139                downloaded.append(req.name)
140        if downloaded:
141            write_output('Successfully downloaded %s', ' '.join(downloaded))
142
143        return SUCCESS
144