We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Sync raises IncompatibleRequirements even when environment markers indicate the incompatible requirements to be irrelevant for the current platform
IMO this is simply more of #206, and I've already demonstrated the problem there. But it's been a month and no one has re-opened, so I'm opening this more specific issue to address the problem.

Here's an example `dev-requirements.txt` from the `plumbum` project:

```python
pytest
pytest-cov
pytest-mock
idna<2.8 ; python_version < '2.7'
pycparser<2.18 ; python_version < '2.7'
paramiko<2.4 ; python_version < '2.7'
paramiko ; python_version >= '2.7'
setuptools
wheel ; python_version >= '2.7'
psutil
```

```bash
pip-sync dev-requirements.txt
```

Identical output whether in a Python `3.7.4` or `2.7.16` env:

```
Incompatible requirements found: paramiko (from -r dev-requirements.txt (line 7)) and paramiko<2.4 (from -r dev-requirements.txt (line 6))
```

No packages end up installed aside from `pip-tools` and its deps.

#### Environment Versions

1. Arch Linux
1. Python version: `3.7.4`
1. pip version:  `19.2.3`
1. pip-tools version: `4.1.0`

#### Steps to replicate

```bash
echo "paramiko==2.4.0 ; python_version < '2.7'" > mark.txt
echo "paramiko==2.6.0 ; python_version >= '2.7'" >> mark.txt
pip-sync mark.txt
```

Note that this works:

```bash
pip install --no-deps -r mark.txt
```

#### Expected result

`pip-sync` should ignore non-matching requirements when environment markers are present.

#### Actual result

`pip-sync` checks for conflicts as if it wants to install requirements for all platforms.

#### Further notes

```bash
mv mark.txt mark.in
pip-compile --no-header mark.in
```

```python
asn1crypto==1.0.1         # via cryptography
bcrypt==3.1.7             # via paramiko
cffi==1.12.3              # via bcrypt, cryptography, pynacl
cryptography==2.7         # via paramiko
paramiko==2.6.0 ; python_version >= "2.7"
pycparser==2.19           # via cffi
pynacl==1.3.0             # via paramiko
six==1.12.0               # via bcrypt, cryptography, pynacl
```

Currently, compiling such an in-file will only include the compile-time platform's matching reqs. This hides the issue under discussion, and arguably means it's not a bug. But I believe it is generally desired for pip-sync to honor environment markers, as evidenced by the contents of #206 (closed, but not solved), #600 (merged), #459 (replaced), #460 (merged), #518 (open 2yrs), #563 (open 2yrs), #585 (open 2yrs), #896 (open), etc.

This is probably even more relevant for working with a single python version across different platforms.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `piptools/sync.py`
Content:
```
1 import collections
2 import os
3 import sys
4 import tempfile
5 from subprocess import check_call  # nosec
6 
7 from pip._internal.commands.freeze import DEV_PKGS
8 from pip._internal.utils.compat import stdlib_pkgs
9 
10 from . import click
11 from .exceptions import IncompatibleRequirements
12 from .utils import (
13     flat_map,
14     format_requirement,
15     get_hashes_from_ireq,
16     is_url_requirement,
17     key_from_ireq,
18     key_from_req,
19 )
20 
21 PACKAGES_TO_IGNORE = (
22     ["-markerlib", "pip", "pip-tools", "pip-review", "pkg-resources"]
23     + list(stdlib_pkgs)
24     + list(DEV_PKGS)
25 )
26 
27 
28 def dependency_tree(installed_keys, root_key):
29     """
30     Calculate the dependency tree for the package `root_key` and return
31     a collection of all its dependencies.  Uses a DFS traversal algorithm.
32 
33     `installed_keys` should be a {key: requirement} mapping, e.g.
34         {'django': from_line('django==1.8')}
35     `root_key` should be the key to return the dependency tree for.
36     """
37     dependencies = set()
38     queue = collections.deque()
39 
40     if root_key in installed_keys:
41         dep = installed_keys[root_key]
42         queue.append(dep)
43 
44     while queue:
45         v = queue.popleft()
46         key = key_from_req(v)
47         if key in dependencies:
48             continue
49 
50         dependencies.add(key)
51 
52         for dep_specifier in v.requires():
53             dep_name = key_from_req(dep_specifier)
54             if dep_name in installed_keys:
55                 dep = installed_keys[dep_name]
56 
57                 if dep_specifier.specifier.contains(dep.version):
58                     queue.append(dep)
59 
60     return dependencies
61 
62 
63 def get_dists_to_ignore(installed):
64     """
65     Returns a collection of package names to ignore when performing pip-sync,
66     based on the currently installed environment.  For example, when pip-tools
67     is installed in the local environment, it should be ignored, including all
68     of its dependencies (e.g. click).  When pip-tools is not installed
69     locally, click should also be installed/uninstalled depending on the given
70     requirements.
71     """
72     installed_keys = {key_from_req(r): r for r in installed}
73     return list(
74         flat_map(lambda req: dependency_tree(installed_keys, req), PACKAGES_TO_IGNORE)
75     )
76 
77 
78 def merge(requirements, ignore_conflicts):
79     by_key = {}
80 
81     for ireq in requirements:
82         # Limitation: URL requirements are merged by precise string match, so
83         # "file:///example.zip#egg=example", "file:///example.zip", and
84         # "example==1.0" will not merge with each other
85         key = key_from_ireq(ireq)
86 
87         if not ignore_conflicts:
88             existing_ireq = by_key.get(key)
89             if existing_ireq:
90                 # NOTE: We check equality here since we can assume that the
91                 # requirements are all pinned
92                 if ireq.specifier != existing_ireq.specifier:
93                     raise IncompatibleRequirements(ireq, existing_ireq)
94 
95         # TODO: Always pick the largest specifier in case of a conflict
96         by_key[key] = ireq
97     return by_key.values()
98 
99 
100 def diff_key_from_ireq(ireq):
101     """
102     Calculate a key for comparing a compiled requirement with installed modules.
103     For URL requirements, only provide a useful key if the url includes
104     #egg=name==version, which will set ireq.req.name and ireq.specifier.
105     Otherwise return ireq.link so the key will not match and the package will
106     reinstall. Reinstall is necessary to ensure that packages will reinstall
107     if the URL is changed but the version is not.
108     """
109     if is_url_requirement(ireq):
110         if (
111             ireq.req
112             and (getattr(ireq.req, "key", None) or getattr(ireq.req, "name", None))
113             and ireq.specifier
114         ):
115             return key_from_ireq(ireq)
116         return str(ireq.link)
117     return key_from_ireq(ireq)
118 
119 
120 def diff(compiled_requirements, installed_dists):
121     """
122     Calculate which packages should be installed or uninstalled, given a set
123     of compiled requirements and a list of currently installed modules.
124     """
125     requirements_lut = {diff_key_from_ireq(r): r for r in compiled_requirements}
126 
127     satisfied = set()  # holds keys
128     to_install = set()  # holds InstallRequirement objects
129     to_uninstall = set()  # holds keys
130 
131     pkgs_to_ignore = get_dists_to_ignore(installed_dists)
132     for dist in installed_dists:
133         key = key_from_req(dist)
134         if key not in requirements_lut or not requirements_lut[key].match_markers():
135             to_uninstall.add(key)
136         elif requirements_lut[key].specifier.contains(dist.version):
137             satisfied.add(key)
138 
139     for key, requirement in requirements_lut.items():
140         if key not in satisfied and requirement.match_markers():
141             to_install.add(requirement)
142 
143     # Make sure to not uninstall any packages that should be ignored
144     to_uninstall -= set(pkgs_to_ignore)
145 
146     return (to_install, to_uninstall)
147 
148 
149 def sync(
150     to_install,
151     to_uninstall,
152     verbose=False,
153     dry_run=False,
154     install_flags=None,
155     ask=False,
156 ):
157     """
158     Install and uninstalls the given sets of modules.
159     """
160     if not to_uninstall and not to_install:
161         if verbose:
162             click.echo("Everything up-to-date")
163         return 0
164 
165     pip_flags = []
166     if not verbose:
167         pip_flags += ["-q"]
168 
169     if ask:
170         dry_run = True
171 
172     if dry_run:
173         if to_uninstall:
174             click.echo("Would uninstall:")
175             for pkg in to_uninstall:
176                 click.echo("  {}".format(pkg))
177 
178         if to_install:
179             click.echo("Would install:")
180             for ireq in to_install:
181                 click.echo("  {}".format(format_requirement(ireq)))
182 
183     if ask and click.confirm("Would you like to proceed with these changes?"):
184         dry_run = False
185 
186     if not dry_run:
187         if to_uninstall:
188             check_call(  # nosec
189                 [sys.executable, "-m", "pip", "uninstall", "-y"]
190                 + pip_flags
191                 + sorted(to_uninstall)
192             )
193 
194         if to_install:
195             if install_flags is None:
196                 install_flags = []
197             # prepare requirement lines
198             req_lines = []
199             for ireq in sorted(to_install, key=key_from_ireq):
200                 ireq_hashes = get_hashes_from_ireq(ireq)
201                 req_lines.append(format_requirement(ireq, hashes=ireq_hashes))
202 
203             # save requirement lines to a temporary file
204             tmp_req_file = tempfile.NamedTemporaryFile(mode="wt", delete=False)
205             tmp_req_file.write("\n".join(req_lines))
206             tmp_req_file.close()
207 
208             try:
209                 check_call(  # nosec
210                     [sys.executable, "-m", "pip", "install", "-r", tmp_req_file.name]
211                     + pip_flags
212                     + install_flags
213                 )
214             finally:
215                 os.unlink(tmp_req_file.name)
216 
217     return 0
218
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/piptools/sync.py b/piptools/sync.py
--- a/piptools/sync.py
+++ b/piptools/sync.py
@@ -82,18 +82,19 @@
         # Limitation: URL requirements are merged by precise string match, so
         # "file:///example.zip#egg=example", "file:///example.zip", and
         # "example==1.0" will not merge with each other
-        key = key_from_ireq(ireq)
-
-        if not ignore_conflicts:
-            existing_ireq = by_key.get(key)
-            if existing_ireq:
-                # NOTE: We check equality here since we can assume that the
-                # requirements are all pinned
-                if ireq.specifier != existing_ireq.specifier:
-                    raise IncompatibleRequirements(ireq, existing_ireq)
-
-        # TODO: Always pick the largest specifier in case of a conflict
-        by_key[key] = ireq
+        if ireq.match_markers():
+            key = key_from_ireq(ireq)
+
+            if not ignore_conflicts:
+                existing_ireq = by_key.get(key)
+                if existing_ireq:
+                    # NOTE: We check equality here since we can assume that the
+                    # requirements are all pinned
+                    if ireq.specifier != existing_ireq.specifier:
+                        raise IncompatibleRequirements(ireq, existing_ireq)
+
+            # TODO: Always pick the largest specifier in case of a conflict
+            by_key[key] = ireq
     return by_key.values()

{"golden_diff": "diff --git a/piptools/sync.py b/piptools/sync.py\n--- a/piptools/sync.py\n+++ b/piptools/sync.py\n@@ -82,18 +82,19 @@\n         # Limitation: URL requirements are merged by precise string match, so\n         # \"file:///example.zip#egg=example\", \"file:///example.zip\", and\n         # \"example==1.0\" will not merge with each other\n-        key = key_from_ireq(ireq)\n-\n-        if not ignore_conflicts:\n-            existing_ireq = by_key.get(key)\n-            if existing_ireq:\n-                # NOTE: We check equality here since we can assume that the\n-                # requirements are all pinned\n-                if ireq.specifier != existing_ireq.specifier:\n-                    raise IncompatibleRequirements(ireq, existing_ireq)\n-\n-        # TODO: Always pick the largest specifier in case of a conflict\n-        by_key[key] = ireq\n+        if ireq.match_markers():\n+            key = key_from_ireq(ireq)\n+\n+            if not ignore_conflicts:\n+                existing_ireq = by_key.get(key)\n+                if existing_ireq:\n+                    # NOTE: We check equality here since we can assume that the\n+                    # requirements are all pinned\n+                    if ireq.specifier != existing_ireq.specifier:\n+                        raise IncompatibleRequirements(ireq, existing_ireq)\n+\n+            # TODO: Always pick the largest specifier in case of a conflict\n+            by_key[key] = ireq\n     return by_key.values()\n", "issue": "Sync raises IncompatibleRequirements even when environment markers indicate the incompatible requirements to be irrelevant for the current platform\nIMO this is simply more of #206, and I've already demonstrated the problem there. But it's been a month and no one has re-opened, so I'm opening this more specific issue to address the problem.\r\n\r\nHere's an example `dev-requirements.txt` from the `plumbum` project:\r\n\r\n```python\r\npytest\r\npytest-cov\r\npytest-mock\r\nidna<2.8 ; python_version < '2.7'\r\npycparser<2.18 ; python_version < '2.7'\r\nparamiko<2.4 ; python_version < '2.7'\r\nparamiko ; python_version >= '2.7'\r\nsetuptools\r\nwheel ; python_version >= '2.7'\r\npsutil\r\n```\r\n\r\n```bash\r\npip-sync dev-requirements.txt\r\n```\r\n\r\nIdentical output whether in a Python `3.7.4` or `2.7.16` env:\r\n\r\n```\r\nIncompatible requirements found: paramiko (from -r dev-requirements.txt (line 7)) and paramiko<2.4 (from -r dev-requirements.txt (line 6))\r\n```\r\n\r\nNo packages end up installed aside from `pip-tools` and its deps.\r\n\r\n#### Environment Versions\r\n\r\n1. Arch Linux\r\n1. Python version: `3.7.4`\r\n1. pip version:  `19.2.3`\r\n1. pip-tools version: `4.1.0`\r\n\r\n#### Steps to replicate\r\n\r\n```bash\r\necho \"paramiko==2.4.0 ; python_version < '2.7'\" > mark.txt\r\necho \"paramiko==2.6.0 ; python_version >= '2.7'\" >> mark.txt\r\npip-sync mark.txt\r\n```\r\n\r\nNote that this works:\r\n\r\n```bash\r\npip install --no-deps -r mark.txt\r\n```\r\n\r\n#### Expected result\r\n\r\n`pip-sync` should ignore non-matching requirements when environment markers are present.\r\n\r\n#### Actual result\r\n\r\n`pip-sync` checks for conflicts as if it wants to install requirements for all platforms.\r\n\r\n#### Further notes\r\n\r\n```bash\r\nmv mark.txt mark.in\r\npip-compile --no-header mark.in\r\n```\r\n\r\n```python\r\nasn1crypto==1.0.1         # via cryptography\r\nbcrypt==3.1.7             # via paramiko\r\ncffi==1.12.3              # via bcrypt, cryptography, pynacl\r\ncryptography==2.7         # via paramiko\r\nparamiko==2.6.0 ; python_version >= \"2.7\"\r\npycparser==2.19           # via cffi\r\npynacl==1.3.0             # via paramiko\r\nsix==1.12.0               # via bcrypt, cryptography, pynacl\r\n```\r\n\r\nCurrently, compiling such an in-file will only include the compile-time platform's matching reqs. This hides the issue under discussion, and arguably means it's not a bug. But I believe it is generally desired for pip-sync to honor environment markers, as evidenced by the contents of #206 (closed, but not solved), #600 (merged), #459 (replaced), #460 (merged), #518 (open 2yrs), #563 (open 2yrs), #585 (open 2yrs), #896 (open), etc.\r\n\r\nThis is probably even more relevant for working with a single python version across different platforms.\n", "before_files": [{"content": "import collections\nimport os\nimport sys\nimport tempfile\nfrom subprocess import check_call  # nosec\n\nfrom pip._internal.commands.freeze import DEV_PKGS\nfrom pip._internal.utils.compat import stdlib_pkgs\n\nfrom . import click\nfrom .exceptions import IncompatibleRequirements\nfrom .utils import (\n    flat_map,\n    format_requirement,\n    get_hashes_from_ireq,\n    is_url_requirement,\n    key_from_ireq,\n    key_from_req,\n)\n\nPACKAGES_TO_IGNORE = (\n    [\"-markerlib\", \"pip\", \"pip-tools\", \"pip-review\", \"pkg-resources\"]\n    + list(stdlib_pkgs)\n    + list(DEV_PKGS)\n)\n\n\ndef dependency_tree(installed_keys, root_key):\n    \"\"\"\n    Calculate the dependency tree for the package `root_key` and return\n    a collection of all its dependencies.  Uses a DFS traversal algorithm.\n\n    `installed_keys` should be a {key: requirement} mapping, e.g.\n        {'django': from_line('django==1.8')}\n    `root_key` should be the key to return the dependency tree for.\n    \"\"\"\n    dependencies = set()\n    queue = collections.deque()\n\n    if root_key in installed_keys:\n        dep = installed_keys[root_key]\n        queue.append(dep)\n\n    while queue:\n        v = queue.popleft()\n        key = key_from_req(v)\n        if key in dependencies:\n            continue\n\n        dependencies.add(key)\n\n        for dep_specifier in v.requires():\n            dep_name = key_from_req(dep_specifier)\n            if dep_name in installed_keys:\n                dep = installed_keys[dep_name]\n\n                if dep_specifier.specifier.contains(dep.version):\n                    queue.append(dep)\n\n    return dependencies\n\n\ndef get_dists_to_ignore(installed):\n    \"\"\"\n    Returns a collection of package names to ignore when performing pip-sync,\n    based on the currently installed environment.  For example, when pip-tools\n    is installed in the local environment, it should be ignored, including all\n    of its dependencies (e.g. click).  When pip-tools is not installed\n    locally, click should also be installed/uninstalled depending on the given\n    requirements.\n    \"\"\"\n    installed_keys = {key_from_req(r): r for r in installed}\n    return list(\n        flat_map(lambda req: dependency_tree(installed_keys, req), PACKAGES_TO_IGNORE)\n    )\n\n\ndef merge(requirements, ignore_conflicts):\n    by_key = {}\n\n    for ireq in requirements:\n        # Limitation: URL requirements are merged by precise string match, so\n        # \"file:///example.zip#egg=example\", \"file:///example.zip\", and\n        # \"example==1.0\" will not merge with each other\n        key = key_from_ireq(ireq)\n\n        if not ignore_conflicts:\n            existing_ireq = by_key.get(key)\n            if existing_ireq:\n                # NOTE: We check equality here since we can assume that the\n                # requirements are all pinned\n                if ireq.specifier != existing_ireq.specifier:\n                    raise IncompatibleRequirements(ireq, existing_ireq)\n\n        # TODO: Always pick the largest specifier in case of a conflict\n        by_key[key] = ireq\n    return by_key.values()\n\n\ndef diff_key_from_ireq(ireq):\n    \"\"\"\n    Calculate a key for comparing a compiled requirement with installed modules.\n    For URL requirements, only provide a useful key if the url includes\n    #egg=name==version, which will set ireq.req.name and ireq.specifier.\n    Otherwise return ireq.link so the key will not match and the package will\n    reinstall. Reinstall is necessary to ensure that packages will reinstall\n    if the URL is changed but the version is not.\n    \"\"\"\n    if is_url_requirement(ireq):\n        if (\n            ireq.req\n            and (getattr(ireq.req, \"key\", None) or getattr(ireq.req, \"name\", None))\n            and ireq.specifier\n        ):\n            return key_from_ireq(ireq)\n        return str(ireq.link)\n    return key_from_ireq(ireq)\n\n\ndef diff(compiled_requirements, installed_dists):\n    \"\"\"\n    Calculate which packages should be installed or uninstalled, given a set\n    of compiled requirements and a list of currently installed modules.\n    \"\"\"\n    requirements_lut = {diff_key_from_ireq(r): r for r in compiled_requirements}\n\n    satisfied = set()  # holds keys\n    to_install = set()  # holds InstallRequirement objects\n    to_uninstall = set()  # holds keys\n\n    pkgs_to_ignore = get_dists_to_ignore(installed_dists)\n    for dist in installed_dists:\n        key = key_from_req(dist)\n        if key not in requirements_lut or not requirements_lut[key].match_markers():\n            to_uninstall.add(key)\n        elif requirements_lut[key].specifier.contains(dist.version):\n            satisfied.add(key)\n\n    for key, requirement in requirements_lut.items():\n        if key not in satisfied and requirement.match_markers():\n            to_install.add(requirement)\n\n    # Make sure to not uninstall any packages that should be ignored\n    to_uninstall -= set(pkgs_to_ignore)\n\n    return (to_install, to_uninstall)\n\n\ndef sync(\n    to_install,\n    to_uninstall,\n    verbose=False,\n    dry_run=False,\n    install_flags=None,\n    ask=False,\n):\n    \"\"\"\n    Install and uninstalls the given sets of modules.\n    \"\"\"\n    if not to_uninstall and not to_install:\n        if verbose:\n            click.echo(\"Everything up-to-date\")\n        return 0\n\n    pip_flags = []\n    if not verbose:\n        pip_flags += [\"-q\"]\n\n    if ask:\n        dry_run = True\n\n    if dry_run:\n        if to_uninstall:\n            click.echo(\"Would uninstall:\")\n            for pkg in to_uninstall:\n                click.echo(\"  {}\".format(pkg))\n\n        if to_install:\n            click.echo(\"Would install:\")\n            for ireq in to_install:\n                click.echo(\"  {}\".format(format_requirement(ireq)))\n\n    if ask and click.confirm(\"Would you like to proceed with these changes?\"):\n        dry_run = False\n\n    if not dry_run:\n        if to_uninstall:\n            check_call(  # nosec\n                [sys.executable, \"-m\", \"pip\", \"uninstall\", \"-y\"]\n                + pip_flags\n                + sorted(to_uninstall)\n            )\n\n        if to_install:\n            if install_flags is None:\n                install_flags = []\n            # prepare requirement lines\n            req_lines = []\n            for ireq in sorted(to_install, key=key_from_ireq):\n                ireq_hashes = get_hashes_from_ireq(ireq)\n                req_lines.append(format_requirement(ireq, hashes=ireq_hashes))\n\n            # save requirement lines to a temporary file\n            tmp_req_file = tempfile.NamedTemporaryFile(mode=\"wt\", delete=False)\n            tmp_req_file.write(\"\\n\".join(req_lines))\n            tmp_req_file.close()\n\n            try:\n                check_call(  # nosec\n                    [sys.executable, \"-m\", \"pip\", \"install\", \"-r\", tmp_req_file.name]\n                    + pip_flags\n                    + install_flags\n                )\n            finally:\n                os.unlink(tmp_req_file.name)\n\n    return 0\n", "path": "piptools/sync.py"}], "after_files": [{"content": "import collections\nimport os\nimport sys\nimport tempfile\nfrom subprocess import check_call  # nosec\n\nfrom pip._internal.commands.freeze import DEV_PKGS\nfrom pip._internal.utils.compat import stdlib_pkgs\n\nfrom . import click\nfrom .exceptions import IncompatibleRequirements\nfrom .utils import (\n    flat_map,\n    format_requirement,\n    get_hashes_from_ireq,\n    is_url_requirement,\n    key_from_ireq,\n    key_from_req,\n)\n\nPACKAGES_TO_IGNORE = (\n    [\"-markerlib\", \"pip\", \"pip-tools\", \"pip-review\", \"pkg-resources\"]\n    + list(stdlib_pkgs)\n    + list(DEV_PKGS)\n)\n\n\ndef dependency_tree(installed_keys, root_key):\n    \"\"\"\n    Calculate the dependency tree for the package `root_key` and return\n    a collection of all its dependencies.  Uses a DFS traversal algorithm.\n\n    `installed_keys` should be a {key: requirement} mapping, e.g.\n        {'django': from_line('django==1.8')}\n    `root_key` should be the key to return the dependency tree for.\n    \"\"\"\n    dependencies = set()\n    queue = collections.deque()\n\n    if root_key in installed_keys:\n        dep = installed_keys[root_key]\n        queue.append(dep)\n\n    while queue:\n        v = queue.popleft()\n        key = key_from_req(v)\n        if key in dependencies:\n            continue\n\n        dependencies.add(key)\n\n        for dep_specifier in v.requires():\n            dep_name = key_from_req(dep_specifier)\n            if dep_name in installed_keys:\n                dep = installed_keys[dep_name]\n\n                if dep_specifier.specifier.contains(dep.version):\n                    queue.append(dep)\n\n    return dependencies\n\n\ndef get_dists_to_ignore(installed):\n    \"\"\"\n    Returns a collection of package names to ignore when performing pip-sync,\n    based on the currently installed environment.  For example, when pip-tools\n    is installed in the local environment, it should be ignored, including all\n    of its dependencies (e.g. click).  When pip-tools is not installed\n    locally, click should also be installed/uninstalled depending on the given\n    requirements.\n    \"\"\"\n    installed_keys = {key_from_req(r): r for r in installed}\n    return list(\n        flat_map(lambda req: dependency_tree(installed_keys, req), PACKAGES_TO_IGNORE)\n    )\n\n\ndef merge(requirements, ignore_conflicts):\n    by_key = {}\n\n    for ireq in requirements:\n        # Limitation: URL requirements are merged by precise string match, so\n        # \"file:///example.zip#egg=example\", \"file:///example.zip\", and\n        # \"example==1.0\" will not merge with each other\n        if ireq.match_markers():\n            key = key_from_ireq(ireq)\n\n            if not ignore_conflicts:\n                existing_ireq = by_key.get(key)\n                if existing_ireq:\n                    # NOTE: We check equality here since we can assume that the\n                    # requirements are all pinned\n                    if ireq.specifier != existing_ireq.specifier:\n                        raise IncompatibleRequirements(ireq, existing_ireq)\n\n            # TODO: Always pick the largest specifier in case of a conflict\n            by_key[key] = ireq\n    return by_key.values()\n\n\ndef diff_key_from_ireq(ireq):\n    \"\"\"\n    Calculate a key for comparing a compiled requirement with installed modules.\n    For URL requirements, only provide a useful key if the url includes\n    #egg=name==version, which will set ireq.req.name and ireq.specifier.\n    Otherwise return ireq.link so the key will not match and the package will\n    reinstall. Reinstall is necessary to ensure that packages will reinstall\n    if the URL is changed but the version is not.\n    \"\"\"\n    if is_url_requirement(ireq):\n        if (\n            ireq.req\n            and (getattr(ireq.req, \"key\", None) or getattr(ireq.req, \"name\", None))\n            and ireq.specifier\n        ):\n            return key_from_ireq(ireq)\n        return str(ireq.link)\n    return key_from_ireq(ireq)\n\n\ndef diff(compiled_requirements, installed_dists):\n    \"\"\"\n    Calculate which packages should be installed or uninstalled, given a set\n    of compiled requirements and a list of currently installed modules.\n    \"\"\"\n    requirements_lut = {diff_key_from_ireq(r): r for r in compiled_requirements}\n\n    satisfied = set()  # holds keys\n    to_install = set()  # holds InstallRequirement objects\n    to_uninstall = set()  # holds keys\n\n    pkgs_to_ignore = get_dists_to_ignore(installed_dists)\n    for dist in installed_dists:\n        key = key_from_req(dist)\n        if key not in requirements_lut or not requirements_lut[key].match_markers():\n            to_uninstall.add(key)\n        elif requirements_lut[key].specifier.contains(dist.version):\n            satisfied.add(key)\n\n    for key, requirement in requirements_lut.items():\n        if key not in satisfied and requirement.match_markers():\n            to_install.add(requirement)\n\n    # Make sure to not uninstall any packages that should be ignored\n    to_uninstall -= set(pkgs_to_ignore)\n\n    return (to_install, to_uninstall)\n\n\ndef sync(\n    to_install,\n    to_uninstall,\n    verbose=False,\n    dry_run=False,\n    install_flags=None,\n    ask=False,\n):\n    \"\"\"\n    Install and uninstalls the given sets of modules.\n    \"\"\"\n    if not to_uninstall and not to_install:\n        if verbose:\n            click.echo(\"Everything up-to-date\")\n        return 0\n\n    pip_flags = []\n    if not verbose:\n        pip_flags += [\"-q\"]\n\n    if ask:\n        dry_run = True\n\n    if dry_run:\n        if to_uninstall:\n            click.echo(\"Would uninstall:\")\n            for pkg in to_uninstall:\n                click.echo(\"  {}\".format(pkg))\n\n        if to_install:\n            click.echo(\"Would install:\")\n            for ireq in to_install:\n                click.echo(\"  {}\".format(format_requirement(ireq)))\n\n    if ask and click.confirm(\"Would you like to proceed with these changes?\"):\n        dry_run = False\n\n    if not dry_run:\n        if to_uninstall:\n            check_call(  # nosec\n                [sys.executable, \"-m\", \"pip\", \"uninstall\", \"-y\"]\n                + pip_flags\n                + sorted(to_uninstall)\n            )\n\n        if to_install:\n            if install_flags is None:\n                install_flags = []\n            # prepare requirement lines\n            req_lines = []\n            for ireq in sorted(to_install, key=key_from_ireq):\n                ireq_hashes = get_hashes_from_ireq(ireq)\n                req_lines.append(format_requirement(ireq, hashes=ireq_hashes))\n\n            # save requirement lines to a temporary file\n            tmp_req_file = tempfile.NamedTemporaryFile(mode=\"wt\", delete=False)\n            tmp_req_file.write(\"\\n\".join(req_lines))\n            tmp_req_file.close()\n\n            try:\n                check_call(  # nosec\n                    [sys.executable, \"-m\", \"pip\", \"install\", \"-r\", tmp_req_file.name]\n                    + pip_flags\n                    + install_flags\n                )\n            finally:\n                os.unlink(tmp_req_file.name)\n\n    return 0\n", "path": "piptools/sync.py"}]}