From 550e914c089f5245a8aadf7cbe94bc075ed5e072 Mon Sep 17 00:00:00 2001
From: Lee Rowlands <lee.rowlands@previousnext.com.au>
Date: Mon, 22 Oct 2018 15:21:49 +1000
Subject: [PATCH] Issue #2677532 by alexpott, Mile23, kim.pepper, dawehner,
 andypost, Eric_A, xjm, Mixologic, larowlan, phenaproxima, markcarver,
 borisson_: Move drupal_check_incompatibility() functionality to a new
 Dependency class and Version component

---
 core/composer.json                            |   4 +-
 core/includes/common.inc                      |   6 +-
 core/includes/install.inc                     |  12 +-
 .../Drupal/Component/Version/Constraint.php   | 135 +++++++
 core/lib/Drupal/Component/Version/LICENSE.txt | 339 ++++++++++++++++++
 core/lib/Drupal/Component/Version/README.txt  |  12 +
 core/lib/Drupal/Component/Version/TESTING.txt |  18 +
 .../Drupal/Component/Version/composer.json    |  15 +
 core/lib/Drupal/Core/Extension/Dependency.php | 193 ++++++++++
 .../Core/Extension/ModuleExtensionList.php    |   2 +-
 .../Drupal/Core/Extension/ModuleHandler.php   |  66 +---
 .../system/src/Form/ModulesListForm.php       |  10 +-
 core/modules/system/system.install            |   9 +-
 core/modules/system/system.module             |   4 +-
 .../update_script_test.module                 |  14 +
 .../Functional/Update/UpdateScriptTest.php    |  13 +
 .../Common/DrupalCheckIncompatibilityTest.php |  47 +++
 .../Component/Version/ConstraintTest.php      | 107 ++++++
 .../Drupal/Tests/ComposerIntegrationTest.php  |   1 +
 .../Tests/Core/Extension/DependencyTest.php   | 103 ++++++
 .../Extension/DeprecatedModuleHandlerTest.php | 143 ++++++++
 .../Core/Extension/ModuleHandlerTest.php      |  33 --
 22 files changed, 1185 insertions(+), 101 deletions(-)
 create mode 100644 core/lib/Drupal/Component/Version/Constraint.php
 create mode 100644 core/lib/Drupal/Component/Version/LICENSE.txt
 create mode 100644 core/lib/Drupal/Component/Version/README.txt
 create mode 100644 core/lib/Drupal/Component/Version/TESTING.txt
 create mode 100644 core/lib/Drupal/Component/Version/composer.json
 create mode 100644 core/lib/Drupal/Core/Extension/Dependency.php
 create mode 100644 core/tests/Drupal/KernelTests/Core/Common/DrupalCheckIncompatibilityTest.php
 create mode 100644 core/tests/Drupal/Tests/Component/Version/ConstraintTest.php
 create mode 100644 core/tests/Drupal/Tests/Core/Extension/DependencyTest.php
 create mode 100644 core/tests/Drupal/Tests/Core/Extension/DeprecatedModuleHandlerTest.php

diff --git a/core/composer.json b/core/composer.json
index 37120ae8ceec..abc5c3f7135d 100644
--- a/core/composer.json
+++ b/core/composer.json
@@ -109,6 +109,7 @@
         "drupal/core-transliteration": "self.version",
         "drupal/core-utility": "self.version",
         "drupal/core-uuid": "self.version",
+        "drupal/core-version": "self.version",
         "drupal/datetime": "self.version",
         "drupal/datetime_range": "self.version",
         "drupal/dblog": "self.version",
@@ -196,7 +197,8 @@
                 "core/lib/Drupal/Component/Serialization/composer.json",
                 "core/lib/Drupal/Component/Transliteration/composer.json",
                 "core/lib/Drupal/Component/Utility/composer.json",
-                "core/lib/Drupal/Component/Uuid/composer.json"
+                "core/lib/Drupal/Component/Uuid/composer.json",
+                "core/lib/Drupal/Component/Version/composer.json"
             ],
             "recurse": false,
             "replace": false,
diff --git a/core/includes/common.inc b/core/includes/common.inc
index bd9a90735da8..8adbf3be1b2a 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -1214,9 +1214,13 @@ function debug($data, $label = NULL, $print_r = TRUE) {
  *   NULL if compatible, otherwise the original dependency version string that
  *   caused the incompatibility.
  *
- * @see \Drupal\Core\Extension\ModuleHandler::parseDependency()
+ * @deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. Use
+ *   \Drupal\Core\Extension\Dependency::isCompatible() instead.
+ *
+ * @see https://www.drupal.org/node/2756875
  */
 function drupal_check_incompatibility($v, $current_version) {
+  @trigger_error(__FUNCTION__ . '() is deprecated. Use \Drupal\Core\Extension\Dependency::isCompatible() instead. See https://www.drupal.org/node/2756875', E_USER_DEPRECATED);
   if (!empty($v['versions'])) {
     foreach ($v['versions'] as $required_version) {
       if ((isset($required_version['op']) && !version_compare($current_version, $required_version['version'], $required_version['op']))) {
diff --git a/core/includes/install.inc b/core/includes/install.inc
index 03dea88c331f..6ce2d81c6d00 100644
--- a/core/includes/install.inc
+++ b/core/includes/install.inc
@@ -5,6 +5,7 @@
  * API functions for installing modules and themes.
  */
 
+use Drupal\Core\Extension\Dependency;
 use Drupal\Component\Utility\Unicode;
 use Symfony\Component\HttpFoundation\RedirectResponse;
 use Drupal\Component\Utility\Crypt;
@@ -1120,15 +1121,14 @@ function install_profile_info($profile, $langcode = 'en') {
     $info = \Drupal::service('info_parser')->parse("$profile_path/$profile.info.yml");
     $info += $defaults;
 
+    $dependency_name_function = function ($dependency) {
+      return Dependency::createFromString($dependency)->getName();
+    };
     // Convert dependencies in [project:module] format.
-    $info['dependencies'] = array_map(function ($dependency) {
-      return ModuleHandler::parseDependency($dependency)['name'];
-    }, $info['dependencies']);
+    $info['dependencies'] = array_map($dependency_name_function, $info['dependencies']);
 
     // Convert install key in [project:module] format.
-    $info['install'] = array_map(function ($dependency) {
-      return ModuleHandler::parseDependency($dependency)['name'];
-    }, $info['install']);
+    $info['install'] = array_map($dependency_name_function, $info['install']);
 
     // drupal_required_modules() includes the current profile as a dependency.
     // Remove that dependency, since a module cannot depend on itself.
diff --git a/core/lib/Drupal/Component/Version/Constraint.php b/core/lib/Drupal/Component/Version/Constraint.php
new file mode 100644
index 000000000000..cbf72704e0d6
--- /dev/null
+++ b/core/lib/Drupal/Component/Version/Constraint.php
@@ -0,0 +1,135 @@
+<?php
+
+namespace Drupal\Component\Version;
+
+/**
+ * A value object representing a Drupal version constraint.
+ */
+class Constraint {
+
+  /**
+   * The constraint represented as a string. For example '>=8.x-5.x'.
+   *
+   * @var string
+   */
+  protected $constraint;
+
+  /**
+   * A list of associative arrays representing the constraint.
+   *
+   * Each containing the keys:
+   *  - 'op': can be one of: '=', '==', '!=', '<>', '<', '<=', '>', or '>='.
+   *  - 'version': A complete version, e.g. '4.5-beta3'.
+   *
+   * @var array[]
+   */
+  protected $constraintArray = [];
+
+  /**
+   * Constraint constructor.
+   *
+   * @param string $constraint
+   *   The constraint string to create the object from. For example, '>8.x-1.1'.
+   * @param string $core_compatibility
+   *   Core compatibility declared for the current version of Drupal core.
+   *   Normally this is set to \Drupal::CORE_COMPATIBILITY by the caller.
+   */
+  public function __construct($constraint, $core_compatibility) {
+    $this->constraint = $constraint;
+    $this->parseConstraint($constraint, $core_compatibility);
+  }
+
+  /**
+   * Gets the constraint as a string.
+   *
+   * Can be used in the UI for reporting incompatibilities.
+   *
+   * @return string
+   *   The constraint as a string.
+   */
+  public function __toString() {
+    return $this->constraint;
+  }
+
+  /**
+   * A list of associative arrays representing the constraint.
+   *
+   * Each containing the keys:
+   *  - 'op': can be one of: '=', '==', '!=', '<>', '<', '<=', '>', or '>='.
+   *  - 'version': A complete version, e.g. '4.5-beta3'.
+   *
+   * @return array[]
+   *   The constraint represented as an array.
+   *
+   * @deprecated in Drupal 8.7.0, will be removed before Drupal 9.0.0.
+   *   Only exists to provide a backwards compatibility layer.
+   *
+   * @see https://www.drupal.org/node/2756875
+   */
+  public function toArray() {
+    @trigger_error(sprintf('%s() only exists to provide a backwards compatibility layer. See https://www.drupal.org/node/2756875', __METHOD__), E_USER_DEPRECATED);
+    return $this->constraintArray;
+  }
+
+  /**
+   * Determines if the provided version is satisfied by this constraint.
+   *
+   * @param string $version
+   *   The version to check, for example '4.2'.
+   *
+   * @return bool
+   *   TRUE if the provided version is satisfied by this constraint, FALSE if
+   *   not.
+   */
+  public function isCompatible($version) {
+    foreach ($this->constraintArray as $constraint) {
+      if (!version_compare($version, $constraint['version'], $constraint['op'])) {
+        return FALSE;
+      }
+    }
+    return TRUE;
+  }
+
+  /**
+   * Parses a constraint string.
+   *
+   * @param string $constraint_string
+   *   The constraint string to parse.
+   * @param string $core_compatibility
+   *   Core compatibility declared for the current version of Drupal core.
+   *   Normally this is set to \Drupal::CORE_COMPATIBILITY by the caller.
+   */
+  private function parseConstraint($constraint_string, $core_compatibility) {
+    // We use named subpatterns and support every op that version_compare
+    // supports. Also, op is optional and defaults to equals.
+    $p_op = '(?<operation>!=|==|=|<|<=|>|>=|<>)?';
+    // Core version is always optional: 8.x-2.x and 2.x is treated the same.
+    $p_core = '(?:' . preg_quote($core_compatibility) . '-)?';
+    $p_major = '(?<major>\d+)';
+    // By setting the minor version to x, branches can be matched.
+    $p_minor = '(?<minor>(?:\d+|x)(?:-[A-Za-z]+\d+)?)';
+    foreach (explode(',', $constraint_string) as $constraint) {
+      if (preg_match("/^\s*$p_op\s*$p_core$p_major\.$p_minor/", $constraint, $matches)) {
+        $op = !empty($matches['operation']) ? $matches['operation'] : '=';
+        if ($matches['minor'] == 'x') {
+          // Drupal considers "2.x" to mean any version that begins with
+          // "2" (e.g. 2.0, 2.9 are all "2.x"). PHP's version_compare(),
+          // on the other hand, treats "x" as a string; so to
+          // version_compare(), "2.x" is considered less than 2.0. This
+          // means that >=2.x and <2.x are handled by version_compare()
+          // as we need, but > and <= are not.
+          if ($op == '>' || $op == '<=') {
+            $matches['major']++;
+          }
+          // Equivalence can be checked by adding two restrictions.
+          if ($op == '=' || $op == '==') {
+            $this->constraintArray[] = ['op' => '<', 'version' => ($matches['major'] + 1) . '.x'];
+            $op = '>=';
+          }
+        }
+        $this->constraintArray[] = ['op' => $op, 'version' => $matches['major'] . '.' . $matches['minor']];
+      }
+    }
+  }
+
+}
diff --git a/core/lib/Drupal/Component/Version/LICENSE.txt b/core/lib/Drupal/Component/Version/LICENSE.txt
new file mode 100644
index 000000000000..94fb84639c4b
--- /dev/null
+++ b/core/lib/Drupal/Component/Version/LICENSE.txt
@@ -0,0 +1,339 @@
+        GNU GENERAL PUBLIC LICENSE
+           Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+          Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+        GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+          NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+         END OF TERMS AND CONDITIONS
+
+      How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/core/lib/Drupal/Component/Version/README.txt b/core/lib/Drupal/Component/Version/README.txt
new file mode 100644
index 000000000000..a62aa79ed4fc
--- /dev/null
+++ b/core/lib/Drupal/Component/Version/README.txt
@@ -0,0 +1,12 @@
+The Drupal Version Component
+
+Thanks for using this Drupal component.
+
+You can participate in its development on Drupal.org, through our issue system:
+https://www.drupal.org/project/issues/drupal
+
+You can get the full Drupal repo here:
+https://www.drupal.org/project/drupal/git-instructions
+
+You can browse the full Drupal repo here:
+http://cgit.drupalcode.org/drupal
diff --git a/core/lib/Drupal/Component/Version/TESTING.txt b/core/lib/Drupal/Component/Version/TESTING.txt
new file mode 100644
index 000000000000..adc5fcd252c7
--- /dev/null
+++ b/core/lib/Drupal/Component/Version/TESTING.txt
@@ -0,0 +1,18 @@
+HOW-TO: Test this Drupal component
+
+In order to test this component, you'll need to get the entire Drupal repo and
+run the tests there.
+
+You'll find the tests under core/tests/Drupal/Tests/Component.
+
+You can get the full Drupal repo here:
+https://www.drupal.org/project/drupal/git-instructions
+
+You can find more information about running PHPUnit tests with Drupal here:
+https://www.drupal.org/node/2116263
+
+Each component in the Drupal\Component namespace has its own annotated test
+group. You can use this group to run only the tests for this component. Like
+this:
+
+$ ./vendor/bin/phpunit -c core --group Version
diff --git a/core/lib/Drupal/Component/Version/composer.json b/core/lib/Drupal/Component/Version/composer.json
new file mode 100644
index 000000000000..146cbe3aed35
--- /dev/null
+++ b/core/lib/Drupal/Component/Version/composer.json
@@ -0,0 +1,15 @@
+{
+  "name": "drupal/core-version",
+  "description": "Utility classes for process Drupal specific version information.",
+  "keywords": ["drupal"],
+  "homepage": "https://www.drupal.org/project/drupal",
+  "license": "GPL-2.0-or-later",
+  "require": {
+    "php": ">=5.5.9"
+  },
+  "autoload": {
+    "psr-4": {
+      "Drupal\\Component\\Version\\": ""
+    }
+  }
+}
diff --git a/core/lib/Drupal/Core/Extension/Dependency.php b/core/lib/Drupal/Core/Extension/Dependency.php
new file mode 100644
index 000000000000..0c745b593d20
--- /dev/null
+++ b/core/lib/Drupal/Core/Extension/Dependency.php
@@ -0,0 +1,193 @@
+<?php
+
+namespace Drupal\Core\Extension;
+
+use Drupal\Component\Version\Constraint;
+
+/**
+ * A value object representing dependency information.
+ *
+ * This class implements \ArrayAccess to provide a backwards compatibility layer
+ * for Drupal 8.x. This will be removed before Drupal 9.x.
+ *
+ * @see https://www.drupal.org/node/2756875
+ */
+class Dependency implements \ArrayAccess {
+
+  /**
+   * The name of the dependency.
+   *
+   * @var string
+   */
+  protected $name;
+
+  /**
+   * The project namespace for the dependency.
+   *
+   * @var string
+   */
+  protected $project;
+
+  /**
+   * The constraint string.
+   *
+   * @var \Drupal\Component\Version\Constraint
+   */
+  protected $constraintString;
+
+  /**
+   * The Constraint object from the constraint string.
+   *
+   * @var \Drupal\Component\Version\Constraint
+   */
+  protected $constraint;
+
+  /**
+   * Dependency constructor.
+   *
+   * @param string $name
+   *   The name of the dependency.
+   * @param string $project
+   *   The project namespace for the dependency.
+   * @param string $constraint
+   *   The constraint string. For example, '>8.x-1.1'.
+   */
+  public function __construct($name, $project, $constraint) {
+    $this->name = $name;
+    $this->project = $project;
+    $this->constraintString = $constraint;
+  }
+
+  /**
+   * Gets the dependency's name.
+   *
+   * @return string
+   *   The dependency's name.
+   */
+  public function getName() {
+    return $this->name;
+  }
+
+  /**
+   * Gets the dependency's project namespace.
+   *
+   * @return string
+   *   The dependency's project namespace.
+   */
+  public function getProject() {
+    return $this->project;
+  }
+
+  /**
+   * Gets constraint string from the dependency.
+   *
+   * @return string
+   *   The constraint string.
+   */
+  public function getConstraintString() {
+    return $this->constraintString;
+  }
+
+  /**
+   * Gets the Constraint object.
+   *
+   * @return \Drupal\Component\Version\Constraint
+   *   The Constraint object.
+   */
+  protected function getConstraint() {
+    if (!$this->constraint) {
+      $this->constraint = new Constraint($this->constraintString, \Drupal::CORE_COMPATIBILITY);
+    }
+    return $this->constraint;
+  }
+
+  /**
+   * Determines if the provided version is compatible with this dependency.
+   *
+   * @param string $version
+   *   The version to check, for example '4.2'.
+   *
+   * @return bool
+   *   TRUE if compatible with the provided version, FALSE if not.
+   */
+  public function isCompatible($version) {
+    return $this->getConstraint()->isCompatible($version);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function offsetExists($offset) {
+    @trigger_error(sprintf('Array access to %s properties is deprecated. Use accessor methods instead. See https://www.drupal.org/node/2756875', __CLASS__), E_USER_DEPRECATED);
+    return in_array($offset, ['name', 'project', 'original_version', 'versions'], TRUE);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function offsetGet($offset) {
+    switch ($offset) {
+      case 'name':
+        @trigger_error(sprintf('Array access to the %s name property is deprecated. Use %s::getName() instead. See https://www.drupal.org/node/2756875', __CLASS__, __CLASS__), E_USER_DEPRECATED);
+        return $this->getName();
+
+      case 'project':
+        @trigger_error(sprintf('Array access to the %s project property is deprecated. Use %s::getProject() instead. See https://www.drupal.org/node/2756875', __CLASS__, __CLASS__), E_USER_DEPRECATED);
+        return $this->getProject();
+
+      case 'original_version':
+        @trigger_error(sprintf('Array access to the %s original_version property is deprecated. Use %s::getConstraintString() instead. See https://www.drupal.org/node/2756875', __CLASS__, __CLASS__), E_USER_DEPRECATED);
+        $constraint = $this->getConstraintString();
+        if ($constraint) {
+          $constraint = ' (' . $constraint . ')';
+        }
+        return $constraint;
+
+      case 'versions':
+        @trigger_error(sprintf('Array access to the %s versions property is deprecated. See https://www.drupal.org/node/2756875', __CLASS__), E_USER_DEPRECATED);
+        return $this->getConstraint()->toArray();
+    }
+    throw new \InvalidArgumentException("The $offset key is not supported");
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function offsetSet($offset, $value) {
+    throw new \BadMethodCallException(sprintf('%s() is not supported', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function offsetUnset($offset) {
+    throw new \BadMethodCallException(sprintf('%s() is not supported', __METHOD__));
+  }
+
+  /**
+   * Creates a new instance of this class from a dependency string.
+   *
+   * @param string $dependency
+   *   A dependency string, which specifies a module or theme dependency, and
+   *   optionally the project it comes from and a constraint string that
+   *   determines the versions that are supported. Supported formats include:
+   *   - 'module'
+   *   - 'project:module'
+   *   - 'project:module (>=version, <=version)'.
+   *
+   * @return static
+   */
+  public static function createFromString($dependency) {
+    if (strpos($dependency, ':') !== FALSE) {
+      list($project, $dependency) = explode(':', $dependency);
+    }
+    else {
+      $project = '';
+    }
+    $parts = explode('(', $dependency, 2);
+    $name = trim($parts[0]);
+    $version_string = isset($parts[1]) ? rtrim($parts[1], ") ") : '';
+    return new static($name, $project, $version_string);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Extension/ModuleExtensionList.php b/core/lib/Drupal/Core/Extension/ModuleExtensionList.php
index 01fcf5909529..7db535b350ed 100644
--- a/core/lib/Drupal/Core/Extension/ModuleExtensionList.php
+++ b/core/lib/Drupal/Core/Extension/ModuleExtensionList.php
@@ -214,7 +214,7 @@ protected function getInstalledExtensionNames() {
   protected function ensureRequiredDependencies(Extension $module, array $modules = []) {
     if (!empty($module->info['required'])) {
       foreach ($module->info['dependencies'] as $dependency) {
-        $dependency_name = ModuleHandler::parseDependency($dependency)['name'];
+        $dependency_name = Dependency::createFromString($dependency)->getName();
         if (!isset($modules[$dependency_name]->info['required'])) {
           $modules[$dependency_name]->info['required'] = TRUE;
           $modules[$dependency_name]->info['explanation'] = $this->t('Dependency of required module @module', ['@module' => $module->info['name']]);
diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php
index 8d43a857dd4c..07b3b9107987 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php
@@ -225,8 +225,8 @@ public function buildModuleDependencies(array $modules) {
       $graph[$module->getName()]['edges'] = [];
       if (isset($module->info['dependencies']) && is_array($module->info['dependencies'])) {
         foreach ($module->info['dependencies'] as $dependency) {
-          $dependency_data = static::parseDependency($dependency);
-          $graph[$module->getName()]['edges'][$dependency_data['name']] = $dependency_data;
+          $dependency_data = Dependency::createFromString($dependency);
+          $graph[$module->getName()]['edges'][$dependency_data->getName()] = $dependency_data;
         }
       }
     }
@@ -697,15 +697,15 @@ protected function verifyImplementations(&$implementations, $hook) {
   /**
    * Parses a dependency for comparison by drupal_check_incompatibility().
    *
-   * @param $dependency
+   * @param string $dependency
    *   A dependency string, which specifies a module dependency, and optionally
    *   the project it comes from and versions that are supported. Supported
    *   formats include:
    *   - 'module'
    *   - 'project:module'
-   *   - 'project:module (>=version, version)'
+   *   - 'project:module (>=version, version)'.
    *
-   * @return
+   * @return array
    *   An associative array with three keys:
    *   - 'name' includes the name of the thing to depend on (e.g. 'foo').
    *   - 'original_version' contains the original version string (which can be
@@ -715,51 +715,21 @@ protected function verifyImplementations(&$implementations, $hook) {
    *     '<=', '>', or '>='. 'version' is one piece like '4.5-beta3'.
    *   Callers should pass this structure to drupal_check_incompatibility().
    *
-   * @see drupal_check_incompatibility()
+   * @deprecated in Drupal 8.7.0, will be removed before Drupal 9.0.0.
+   *   Use \Drupal\Core\Extension\Dependency::createFromString() instead.
+   *
+   * @see https://www.drupal.org/node/2756875
    */
   public static function parseDependency($dependency) {
-    $value = [];
-    // Split out the optional project name.
-    if (strpos($dependency, ':') !== FALSE) {
-      list($project_name, $dependency) = explode(':', $dependency);
-      $value['project'] = $project_name;
-    }
-    // We use named subpatterns and support every op that version_compare
-    // supports. Also, op is optional and defaults to equals.
-    $p_op = '(?<operation>!=|==|=|<|<=|>|>=|<>)?';
-    // Core version is always optional: 8.x-2.x and 2.x is treated the same.
-    $p_core = '(?:' . preg_quote(\Drupal::CORE_COMPATIBILITY) . '-)?';
-    $p_major = '(?<major>\d+)';
-    // By setting the minor version to x, branches can be matched.
-    $p_minor = '(?<minor>(?:\d+|x)(?:-[A-Za-z]+\d+)?)';
-    $parts = explode('(', $dependency, 2);
-    $value['name'] = trim($parts[0]);
-    if (isset($parts[1])) {
-      $value['original_version'] = ' (' . $parts[1];
-      foreach (explode(',', $parts[1]) as $version) {
-        if (preg_match("/^\s*$p_op\s*$p_core$p_major\.$p_minor/", $version, $matches)) {
-          $op = !empty($matches['operation']) ? $matches['operation'] : '=';
-          if ($matches['minor'] == 'x') {
-            // Drupal considers "2.x" to mean any version that begins with
-            // "2" (e.g. 2.0, 2.9 are all "2.x"). PHP's version_compare(),
-            // on the other hand, treats "x" as a string; so to
-            // version_compare(), "2.x" is considered less than 2.0. This
-            // means that >=2.x and <2.x are handled by version_compare()
-            // as we need, but > and <= are not.
-            if ($op == '>' || $op == '<=') {
-              $matches['major']++;
-            }
-            // Equivalence can be checked by adding two restrictions.
-            if ($op == '=' || $op == '==') {
-              $value['versions'][] = ['op' => '<', 'version' => ($matches['major'] + 1) . '.x'];
-              $op = '>=';
-            }
-          }
-          $value['versions'][] = ['op' => $op, 'version' => $matches['major'] . '.' . $matches['minor']];
-        }
-      }
-    }
-    return $value;
+    @trigger_error(__METHOD__ . ' is deprecated. Use \Drupal\Core\Extension\Dependency::createFromString() instead. See https://www.drupal.org/node/2756875', E_USER_DEPRECATED);
+    $dependency = Dependency::createFromString($dependency);
+    $result = [
+      'name' => $dependency->getName(),
+      'project' => $dependency->getProject(),
+      'original_version' => $dependency['original_version'],
+      'versions' => $dependency['versions'],
+    ];
+    return array_filter($result);
   }
 
   /**
diff --git a/core/modules/system/src/Form/ModulesListForm.php b/core/modules/system/src/Form/ModulesListForm.php
index ef0b240ecffc..c2ade11b7a6d 100644
--- a/core/modules/system/src/Form/ModulesListForm.php
+++ b/core/modules/system/src/Form/ModulesListForm.php
@@ -307,7 +307,8 @@ protected function buildRow(array $modules, Extension $module, $distribution) {
     }
 
     // If this module requires other modules, add them to the array.
-    foreach ($module->requires as $dependency => $version) {
+    /** @var \Drupal\Core\Extension\Dependency $dependency_object */
+    foreach ($module->requires as $dependency => $dependency_object) {
       if (!isset($modules[$dependency])) {
         $row['#requires'][$dependency] = $this->t('@module (<span class="admin-missing">missing</span>)', ['@module' => Unicode::ucfirst($dependency)]);
         $row['enable']['#disabled'] = TRUE;
@@ -317,9 +318,10 @@ protected function buildRow(array $modules, Extension $module, $distribution) {
         $name = $modules[$dependency]->info['name'];
         // Disable the module's checkbox if it is incompatible with the
         // dependency's version.
-        if ($incompatible_version = drupal_check_incompatibility($version, str_replace(\Drupal::CORE_COMPATIBILITY . '-', '', $modules[$dependency]->info['version']))) {
-          $row['#requires'][$dependency] = $this->t('@module (<span class="admin-missing">incompatible with</span> version @version)', [
-            '@module' => $name . $incompatible_version,
+        if (!$dependency_object->isCompatible(str_replace(\Drupal::CORE_COMPATIBILITY . '-', '', $modules[$dependency]->info['version']))) {
+          $row['#requires'][$dependency] = $this->t('@module (@constraint) (<span class="admin-missing">incompatible with</span> version @version)', [
+            '@module' => $name,
+            '@constraint' => $dependency_object->getConstraintString(),
             '@version' => $modules[$dependency]->info['version'],
           ]);
           $row['enable']['#disabled'] = TRUE;
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index 6eb8c1200569..2cddcbfff963 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -803,8 +803,9 @@ function system_requirements($phase) {
         $requirements['php']['severity'] = REQUIREMENT_ERROR;
       }
       // Check the module's required modules.
+      /** @var \Drupal\Core\Extension\Dependency $requirement */
       foreach ($file->requires as $requirement) {
-        $required_module = $requirement['name'];
+        $required_module = $requirement->getName();
         // Check if the module exists.
         if (!isset($files[$required_module])) {
           $requirements["$module-$required_module"] = [
@@ -819,13 +820,11 @@ function system_requirements($phase) {
         $required_file = $files[$required_module];
         $required_name = $required_file->info['name'];
         $version = str_replace(\Drupal::CORE_COMPATIBILITY . '-', '', $required_file->info['version']);
-        $compatibility = drupal_check_incompatibility($requirement, $version);
-        if ($compatibility) {
-          $compatibility = rtrim(substr($compatibility, 2), ')');
+        if (!$requirement->isCompatible($version)) {
           $requirements["$module-$required_module"] = [
             'title' => t('Unresolved dependency'),
             'description' => t('@name requires this module and version. Currently using @required_name version @version', ['@name' => $name, '@required_name' => $required_name, '@version' => $version]),
-            'value' => t('@required_name (Version @compatibility required)', ['@required_name' => $required_name, '@compatibility' => $compatibility]),
+            'value' => t('@required_name (Version @compatibility required)', ['@required_name' => $required_name, '@compatibility' => $requirement->getConstraintString()]),
             'severity' => REQUIREMENT_ERROR,
           ];
           continue;
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index e82e1ffd4824..ac27414f8567 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -5,6 +5,7 @@
  * Configuration system that lets administrators modify the workings of the site.
  */
 
+use Drupal\Core\Extension\Dependency;
 use Drupal\Component\Render\PlainTextOutput;
 use Drupal\Component\Utility\UrlHelper;
 use Drupal\Core\Asset\AttachedAssetsInterface;
@@ -21,7 +22,6 @@
 use Drupal\Core\Routing\StackedRouteMatchInterface;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Menu\MenuTreeParameters;
-use Drupal\Core\Extension\ModuleHandler;
 use Drupal\Core\Url;
 use Drupal\Core\Block\BlockPluginInterface;
 use Drupal\user\UserInterface;
@@ -1014,7 +1014,7 @@ function _system_rebuild_module_data_ensure_required($module, &$modules) {
   @trigger_error("_system_rebuild_module_data_ensure_required() is deprecated in Drupal 8.5.0 and will be removed before Drupal 9.0.0. This function is no longer used in Drupal core. See https://www.drupal.org/node/2709919", E_USER_DEPRECATED);
   if (!empty($module->info['required'])) {
     foreach ($module->info['dependencies'] as $dependency) {
-      $dependency_name = ModuleHandler::parseDependency($dependency)['name'];
+      $dependency_name = Dependency::createFromString($dependency)->getName();
       if (!isset($modules[$dependency_name]->info['required'])) {
         $modules[$dependency_name]->info['required'] = TRUE;
         $modules[$dependency_name]->info['explanation'] = t('Dependency of required module @module', ['@module' => $module->info['name']]);
diff --git a/core/modules/system/tests/modules/update_script_test/update_script_test.module b/core/modules/system/tests/modules/update_script_test/update_script_test.module
index 096f91251596..26472239e487 100644
--- a/core/modules/system/tests/modules/update_script_test/update_script_test.module
+++ b/core/modules/system/tests/modules/update_script_test/update_script_test.module
@@ -5,6 +5,8 @@
  * This file provides testing functionality for update.php.
  */
 
+use Drupal\Core\Extension\Extension;
+
 /**
  * Implements hook_cache_flush().
  *
@@ -16,3 +18,15 @@
 function update_script_test_cache_flush() {
   \Drupal::messenger()->addStatus(t('hook_cache_flush() invoked for update_script_test.module.'));
 }
+
+/**
+ * Implements hook_system_info_alter().
+ */
+function update_script_test_system_info_alter(array &$info, Extension $file, $type) {
+  $new_info = \Drupal::state()->get('update_script_test.system_info_alter');
+  if ($new_info) {
+    if ($file->getName() == 'update_script_test') {
+      $info = $new_info + $info;
+    }
+  }
+}
diff --git a/core/modules/system/tests/src/Functional/Update/UpdateScriptTest.php b/core/modules/system/tests/src/Functional/Update/UpdateScriptTest.php
index 29d4d08e238b..8f538795ef7d 100644
--- a/core/modules/system/tests/src/Functional/Update/UpdateScriptTest.php
+++ b/core/modules/system/tests/src/Functional/Update/UpdateScriptTest.php
@@ -142,6 +142,19 @@ public function testRequirements() {
     $this->assertText('This is a requirements error provided by the update_script_test module.');
     $this->clickLink('try again');
     $this->assertText('This is a requirements error provided by the update_script_test module.');
+
+    // Ensure that changes to a module's requirements that would cause errors
+    // are displayed correctly.
+    $update_script_test_config->set('requirement_type', REQUIREMENT_OK)->save();
+    \Drupal::state()->set('update_script_test.system_info_alter', ['dependencies' => ['a_module_that_does_not_exist']]);
+    $this->drupalGet($this->updateUrl, ['external' => TRUE]);
+    $this->assertSession()->responseContains('a_module_that_does_not_exist (Missing)');
+    $this->assertSession()->responseContains('Update script test requires this module.');
+
+    \Drupal::state()->set('update_script_test.system_info_alter', ['dependencies' => ['node (<7.x-0.0-dev)']]);
+    $this->drupalGet($this->updateUrl, ['external' => TRUE]);
+    $this->assertSession()->assertEscaped('Node (Version <7.x-0.0-dev required)');
+    $this->assertSession()->responseContains('Update script test requires this module and version. Currently using Node version ' . \Drupal::VERSION);
   }
 
   /**
diff --git a/core/tests/Drupal/KernelTests/Core/Common/DrupalCheckIncompatibilityTest.php b/core/tests/Drupal/KernelTests/Core/Common/DrupalCheckIncompatibilityTest.php
new file mode 100644
index 000000000000..bc53e907c134
--- /dev/null
+++ b/core/tests/Drupal/KernelTests/Core/Common/DrupalCheckIncompatibilityTest.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace Drupal\KernelTests\Core\Common;
+
+use Drupal\Core\Extension\Dependency;
+use Drupal\KernelTests\KernelTestBase;
+
+/**
+ * Parse a predefined amount of bytes and compare the output with the expected
+ * value.
+ *
+ * @group Common
+ * @group legacy
+ */
+class DrupalCheckIncompatibilityTest extends KernelTestBase {
+
+  /**
+   * Tests drupal_check_incompatibility().
+   *
+   * @dataProvider providerDrupalCheckIncompatibility
+   * @expectedDeprecation drupal_check_incompatibility() is deprecated. Use \Drupal\Core\Extension\Dependency::isCompatible() instead. See https://www.drupal.org/node/2756875
+   */
+  public function testDrupalCheckIncompatibility($version_info, $version_to_check, $result) {
+    $this->assertSame($result, drupal_check_incompatibility($version_info, $version_to_check));
+  }
+
+  /**
+   * Data provider for testDrupalCheckIncompatibility.
+   */
+  public function providerDrupalCheckIncompatibility() {
+    $module_data = [
+      'name' => 'views_ui',
+      'original_version' => ' (8.x-1.0)',
+      'versions' => [['op' => '=', 'version' => '1.0']],
+    ];
+
+    $data = [];
+    $data['is compatible'] = [$module_data, '1.0', NULL];
+    $data['not compatible'] = [$module_data, '1.1', ' (8.x-1.0)'];
+    // Prove that the BC layer using ArrayAccess works with
+    // drupal_check_incompatibility().
+    $dependency = new Dependency('views', 'drupal', '8.x-1.2');
+    $data['dependency object'] = [$dependency, '1.1', ' (8.x-1.2)'];
+    return $data;
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Component/Version/ConstraintTest.php b/core/tests/Drupal/Tests/Component/Version/ConstraintTest.php
new file mode 100644
index 000000000000..f80ad800757b
--- /dev/null
+++ b/core/tests/Drupal/Tests/Component/Version/ConstraintTest.php
@@ -0,0 +1,107 @@
+<?php
+
+namespace Drupal\Tests\Component\Version;
+
+use Drupal\Component\Version\Constraint;
+use PHPUnit\Framework\TestCase;
+
+/**
+ * @coversDefaultClass \Drupal\Component\Version\Constraint
+ * @group Version
+ */
+class ConstraintTest extends TestCase {
+
+  /**
+   * @covers ::isCompatible
+   * @dataProvider providerIsCompatible
+   */
+  public function testIsCompatible(Constraint $version_info, $current_version, $result) {
+    $this->assertSame($result, $version_info->isCompatible($current_version));
+  }
+
+  /**
+   * Provider for testIsCompatible.
+   */
+  public function providerIsCompatible() {
+    $tests = [];
+
+    $tests['no-dependencies'] = [new Constraint('', '8.x'), '8.1.x', TRUE];
+
+    // Stable version.
+    $stable = new Constraint('8.x-1.0', '8.x');
+    $tests['(=8.x-1.0)-1.0'] = [$stable, '1.0', TRUE];
+    $tests['(=8.x-1.0)-1.1'] = [$stable, '1.1', FALSE];
+    $tests['(=8.x-1.0)-0.9'] = [$stable, '0.9', FALSE];
+
+    // Alpha version.
+    $alpha = new Constraint('8.x-1.1-alpha12', '8.x');
+    $tests['(8.x-1.1-alpha12)-alpha12'] = [$alpha, '1.1-alpha12', TRUE];
+    $tests['(8.x-1.1-alpha12)-alpha10'] = [$alpha, '1.1-alpha10', FALSE];
+    $tests['(8.x-1.1-alpha12)-beta1'] = [$alpha, '1.1-beta1', FALSE];
+
+    // Beta version.
+    $beta = new Constraint('8.x-1.1-beta8', '8.x');
+    $tests['(8.x-1.1-beta8)-beta8'] = [$beta, '1.1-beta8', TRUE];
+    $tests['(8.x-1.1-beta8)-beta4'] = [$beta, '1.1-beta4', FALSE];
+
+    // RC version.
+    $rc = new Constraint('8.x-1.1-rc11', '8.x');
+    $tests['(8.x-1.1-rc11)-rc11'] = [$rc, '1.1-rc11', TRUE];
+    $tests['(8.x-1.1-rc11)-rc2'] = [$rc, '1.1-rc2', FALSE];
+
+    // Test greater than.
+    $greater = new Constraint('>8.x-1.x', '8.x');
+    $tests['(>8.x-1.x)-2.0'] = [$greater, '2.0', TRUE];
+    $tests['(>8.x-1.x)-1.1'] = [$greater, '1.1', FALSE];
+    $tests['(>8.x-1.x)-0.9'] = [$greater, '0.9', FALSE];
+
+    // Test greater than or equal.
+    $greater_or_equal = new Constraint('>=8.x-1.0', '8.x');
+    $tests['(>=8.x-1.0)-1.1'] = [$greater_or_equal, '1.1', TRUE];
+    $tests['(>=8.x-1.0)-1.0'] = [$greater_or_equal, '1.0', TRUE];
+    $tests['(>=8.x-1.1)-1.0'] = [new Constraint('>=8.x-1.1', '8.x'), '1.0', FALSE];
+
+    // Test less than.
+    $less = new Constraint('<8.x-1.1', '8.x');
+    $tests['(<8.x-1.1)-1.1'] = [$less, '1.1', FALSE];
+    $tests['(<8.x-1.1)-1.1'] = [$less, '1.0', TRUE];
+    $tests['(<8.x-1.0)-1.0'] = [new Constraint('<8.x-1.0', '8.x'), '1.1', FALSE];
+
+    // Test less than or equal.
+    $less_or_equal = new Constraint('<= 8.x-1.x', '8.x');
+    $tests['(<= 8.x-1.x)-2.0'] = [$less_or_equal, '2.0', FALSE];
+    $tests['(<= 8.x-1.x)-1.9'] = [$less_or_equal, '1.9', TRUE];
+    $tests['(<= 8.x-1.x)-1.1'] = [$less_or_equal, '1.1', TRUE];
+    $tests['(<= 8.x-1.x)-0.9'] = [$less_or_equal, '0.9', TRUE];
+
+    // Test greater than and less than.
+    $less_and_greater = new Constraint('<8.x-4.x,>8.x-1.x', '8.x');
+    $tests['(<8.x-4.x,>8.x-1.x)-4.0'] = [$less_and_greater, '4.0', FALSE];
+    $tests['(<8.x-4.x,>8.x-1.x)-3.9'] = [$less_and_greater, '3.9', TRUE];
+    $tests['(<8.x-4.x,>8.x-1.x)-2.1'] = [$less_and_greater, '2.1', TRUE];
+    $tests['(<8.x-4.x,>8.x-1.x)-1.9'] = [$less_and_greater, '1.9', FALSE];
+
+    // Test a nonsensical greater than and less than - no compatible versions.
+    $less_and_greater = new Constraint('>8.x-4.x,<8.x-1.x', '8.x');
+    $tests['(<8.x-4.x,>8.x-1.x)-4.0'] = [$less_and_greater, '4.0', FALSE];
+    $tests['(<8.x-4.x,>8.x-1.x)-3.9'] = [$less_and_greater, '3.9', FALSE];
+    $tests['(<8.x-4.x,>8.x-1.x)-2.1'] = [$less_and_greater, '2.1', FALSE];
+    $tests['(<8.x-4.x,>8.x-1.x)-1.9'] = [$less_and_greater, '1.9', FALSE];
+
+    return $tests;
+  }
+
+  /**
+   * @covers ::toArray
+   * @group legacy
+   * @expectedDeprecation Drupal\Component\Version\Constraint::toArray() only exists to provide a backwards compatibility layer. See https://www.drupal.org/node/2756875
+   */
+  public function testToArray() {
+    $constraint = new Constraint('<8.x-4.x,>8.x-1.x', '8.x');
+    $this->assertSame([
+      ['op' => '<', 'version' => '4.x'],
+      ['op' => '>', 'version' => '2.x'],
+    ], $constraint->toArray());
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/ComposerIntegrationTest.php b/core/tests/Drupal/Tests/ComposerIntegrationTest.php
index 8ee025a809c8..2d34dcb74c8e 100644
--- a/core/tests/Drupal/Tests/ComposerIntegrationTest.php
+++ b/core/tests/Drupal/Tests/ComposerIntegrationTest.php
@@ -77,6 +77,7 @@ protected function getPaths() {
       $this->root . '/core/lib/Drupal/Component/Transliteration',
       $this->root . '/core/lib/Drupal/Component/Utility',
       $this->root . '/core/lib/Drupal/Component/Uuid',
+      $this->root . '/core/lib/Drupal/Component/Version',
     ];
   }
 
diff --git a/core/tests/Drupal/Tests/Core/Extension/DependencyTest.php b/core/tests/Drupal/Tests/Core/Extension/DependencyTest.php
new file mode 100644
index 000000000000..374a16afc6a9
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Extension/DependencyTest.php
@@ -0,0 +1,103 @@
+<?php
+
+namespace Drupal\Tests\Core\Extension;
+
+use Drupal\Core\Extension\Dependency;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Extension\Dependency
+ * @group Extension
+ */
+class DependencyTest extends UnitTestCase {
+
+  /**
+   * @covers ::createFromString
+   * @dataProvider providerCreateFromString
+   */
+  public function testCreateFromString($string, $expected_name, $expected_project, $expected_constraint) {
+    $dependency = Dependency::createFromString($string);
+    $this->assertSame($expected_name, $dependency->getName());
+    $this->assertSame($expected_project, $dependency->getProject());
+    $this->assertSame($expected_constraint, $dependency->getConstraintString());
+  }
+
+  /**
+   * Data provider for testCreateFromString.
+   */
+  public function providerCreateFromString() {
+    $tests = [];
+    $tests['module_name_only'] = ['views', 'views', '', ''];
+    $tests['module_and_project_names'] = ['drupal:views', 'views', 'drupal', ''];
+    $tests['module_and_constraint'] = ['views (<8.x-3.1)', 'views', '', '<8.x-3.1'];
+    $tests['module_and_project_names_and_constraint'] = ['drupal:views (>8.x-1.1)', 'views', 'drupal', '>8.x-1.1'];
+    return $tests;
+  }
+
+  /**
+   * @covers ::isCompatible
+   */
+  public function testIsCompatible() {
+    $dependency = new Dependency('paragraphs_demo', 'paragraphs', '>8.x-1.1');
+    $this->assertFalse($dependency->isCompatible('1.1'));
+    $this->assertTrue($dependency->isCompatible('1.2'));
+  }
+
+  /**
+   * @covers ::offsetExists
+   * @group legacy
+   * @expectedDeprecation Array access to Drupal\Core\Extension\Dependency properties is deprecated. Use accessor methods instead. See https://www.drupal.org/node/2756875
+   */
+  public function testOffsetTest() {
+    $dependency = new Dependency('views', 'drupal', '>8.x-1.1');
+    $this->assertTrue(isset($dependency['name']));
+    $this->assertFalse(isset($dependency['foo']));
+  }
+
+  /**
+   * @covers ::offsetGet
+   * @group legacy
+   * @expectedDeprecation Array access to the Drupal\Core\Extension\Dependency name property is deprecated. Use Drupal\Core\Extension\Dependency::getName() instead. See https://www.drupal.org/node/2756875
+   * @expectedDeprecation Array access to the Drupal\Core\Extension\Dependency project property is deprecated. Use Drupal\Core\Extension\Dependency::getProject() instead. See https://www.drupal.org/node/2756875
+   * @expectedDeprecation Array access to the Drupal\Core\Extension\Dependency original_version property is deprecated. Use Drupal\Core\Extension\Dependency::getConstraintString() instead. See https://www.drupal.org/node/2756875
+   * @expectedDeprecation Array access to the Drupal\Core\Extension\Dependency versions property is deprecated. See https://www.drupal.org/node/2756875
+   */
+  public function testOffsetGet() {
+    $dependency = new Dependency('views', 'drupal', '>8.x-1.1');
+    $this->assertSame('views', $dependency['name']);
+    $this->assertSame('drupal', $dependency['project']);
+    $this->assertSame(' (>8.x-1.1)', $dependency['original_version']);
+    $this->assertSame([['op' => '>', 'version' => '1.1']], $dependency['versions']);
+  }
+
+  /**
+   * @covers ::offsetGet
+   * @group legacy
+   */
+  public function testOffsetGetException() {
+    $dependency = new Dependency('views', 'drupal', '>8.x-1.1');
+    $this->setExpectedException(\InvalidArgumentException::class, 'The does_not_exist key is not supported');
+    $dependency['does_not_exist'];
+  }
+
+  /**
+   * @covers ::offsetUnset
+   * @group legacy
+   */
+  public function testOffsetUnset() {
+    $dependency = new Dependency('views', 'drupal', '>8.x-1.1');
+    $this->setExpectedException(\BadMethodCallException::class, 'Drupal\Core\Extension\Dependency::offsetUnset() is not supported');
+    unset($dependency['name']);
+  }
+
+  /**
+   * @covers ::offsetSet
+   * @group legacy
+   */
+  public function testOffsetSet() {
+    $dependency = new Dependency('views', 'drupal', '>8.x-1.1');
+    $this->setExpectedException(\BadMethodCallException::class, 'Drupal\Core\Extension\Dependency::offsetSet() is not supported');
+    $dependency['name'] = 'foo';
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Extension/DeprecatedModuleHandlerTest.php b/core/tests/Drupal/Tests/Core/Extension/DeprecatedModuleHandlerTest.php
new file mode 100644
index 000000000000..480ec5f0eead
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Extension/DeprecatedModuleHandlerTest.php
@@ -0,0 +1,143 @@
+<?php
+
+namespace Drupal\Tests\Core\Extension;
+
+use Drupal\Core\Extension\ModuleHandler;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Extension\ModuleHandler
+ * @runTestsInSeparateProcesses
+ *
+ * @group Extension
+ * @group legacy
+ */
+class DeprecatedModuleHandlerTest extends UnitTestCase {
+
+  /**
+   * @dataProvider dependencyProvider
+   * @covers ::parseDependency
+   * @expectedDeprecation Drupal\Core\Extension\ModuleHandler::parseDependency is deprecated. Use \Drupal\Core\Extension\Dependency::createFromString() instead. See https://www.drupal.org/node/2756875
+   * @expectedDeprecation Array access to the Drupal\Core\Extension\Dependency original_version property is deprecated. Use Drupal\Core\Extension\Dependency::getConstraintString() instead. See https://www.drupal.org/node/2756875
+   * @expectedDeprecation Array access to the Drupal\Core\Extension\Dependency versions property is deprecated. See https://www.drupal.org/node/2756875
+   * @expectedDeprecation Drupal\Component\Version\Constraint::toArray() only exists to provide a backwards compatibility layer. See https://www.drupal.org/node/2756875
+   */
+  public function testDependencyParsing($dependency, $expected) {
+    $version = ModuleHandler::parseDependency($dependency);
+    $this->assertEquals($expected, $version);
+  }
+
+  /**
+   * Provider for testing dependency parsing.
+   */
+  public function dependencyProvider() {
+    return [
+      ['system', ['name' => 'system']],
+      ['taxonomy', ['name' => 'taxonomy']],
+      ['views', ['name' => 'views']],
+      [
+        'views_ui(8.x-1.0)',
+        [
+          'name' => 'views_ui',
+          'original_version' => ' (8.x-1.0)',
+          'versions' => [['op' => '=', 'version' => '1.0']],
+        ],
+      ],
+      /* @todo Not supported? Fix this in
+         https://www.drupal.org/project/drupal/issues/3001344.
+      [
+        'views_ui(8.x-1.1-beta)',
+        [
+          'name' => 'views_ui',
+          'original_version' => ' (8.x-1.1-beta)',
+          'versions' => [['op' => '=', 'version' => '1.1-beta']],
+        ],
+      ],*/
+      [
+        'views_ui(8.x-1.1-alpha12)',
+        [
+          'name' => 'views_ui',
+          'original_version' => ' (8.x-1.1-alpha12)',
+          'versions' => [['op' => '=', 'version' => '1.1-alpha12']],
+        ],
+      ],
+      [
+        'views_ui(8.x-1.1-beta8)',
+        [
+          'name' => 'views_ui',
+          'original_version' => ' (8.x-1.1-beta8)',
+          'versions' => [['op' => '=', 'version' => '1.1-beta8']],
+        ],
+      ],
+      [
+        'views_ui(8.x-1.1-rc11)',
+        [
+          'name' => 'views_ui',
+          'original_version' => ' (8.x-1.1-rc11)',
+          'versions' => [['op' => '=', 'version' => '1.1-rc11']],
+        ],
+      ],
+      [
+        'views_ui(8.x-1.12)',
+        [
+          'name' => 'views_ui',
+          'original_version' => ' (8.x-1.12)',
+          'versions' => [['op' => '=', 'version' => '1.12']],
+        ],
+      ],
+      [
+        'views_ui(8.x-1.x)',
+        [
+          'name' => 'views_ui',
+          'original_version' => ' (8.x-1.x)',
+          'versions' => [
+            ['op' => '<', 'version' => '2.x'],
+            ['op' => '>=', 'version' => '1.x'],
+          ],
+        ],
+      ],
+      [
+        'views_ui( <= 8.x-1.x)',
+        [
+          'name' => 'views_ui',
+          'original_version' => ' ( <= 8.x-1.x)',
+          'versions' => [['op' => '<=', 'version' => '2.x']],
+        ],
+      ],
+      [
+        'views_ui(<= 8.x-1.x)',
+        [
+          'name' => 'views_ui',
+          'original_version' => ' (<= 8.x-1.x)',
+          'versions' => [['op' => '<=', 'version' => '2.x']],
+        ],
+      ],
+      [
+        'views_ui( <=8.x-1.x)',
+        [
+          'name' => 'views_ui',
+          'original_version' => ' ( <=8.x-1.x)',
+          'versions' => [['op' => '<=', 'version' => '2.x']],
+        ],
+      ],
+      [
+        'views_ui(>8.x-1.x)',
+        [
+          'name' => 'views_ui',
+          'original_version' => ' (>8.x-1.x)',
+          'versions' => [['op' => '>', 'version' => '2.x']],
+        ],
+      ],
+      [
+        'drupal:views_ui(>8.x-1.x)',
+        [
+          'project' => 'drupal',
+          'name' => 'views_ui',
+          'original_version' => ' (>8.x-1.x)',
+          'versions' => [['op' => '>', 'version' => '2.x']],
+        ],
+      ],
+    ];
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Extension/ModuleHandlerTest.php b/core/tests/Drupal/Tests/Core/Extension/ModuleHandlerTest.php
index 3adaf13507a3..9ab05d8cc2ed 100644
--- a/core/tests/Drupal/Tests/Core/Extension/ModuleHandlerTest.php
+++ b/core/tests/Drupal/Tests/Core/Extension/ModuleHandlerTest.php
@@ -496,39 +496,6 @@ public function testResetImplementations() {
     $module_handler->getImplementations('hook');
   }
 
-  /**
-   * @dataProvider dependencyProvider
-   * @covers ::parseDependency
-   */
-  public function testDependencyParsing($dependency, $expected) {
-    $version = ModuleHandler::parseDependency($dependency);
-    $this->assertEquals($expected, $version);
-  }
-
-  /**
-   * Provider for testing dependency parsing.
-   */
-  public function dependencyProvider() {
-    return [
-      ['system', ['name' => 'system']],
-      ['taxonomy', ['name' => 'taxonomy']],
-      ['views', ['name' => 'views']],
-      ['views_ui(8.x-1.0)', ['name' => 'views_ui', 'original_version' => ' (8.x-1.0)', 'versions' => [['op' => '=', 'version' => '1.0']]]],
-      // Not supported?.
-      // array('views_ui(8.x-1.1-beta)', array('name' => 'views_ui', 'original_version' => ' (8.x-1.1-beta)', 'versions' => array(array('op' => '=', 'version' => '1.1-beta')))),
-      ['views_ui(8.x-1.1-alpha12)', ['name' => 'views_ui', 'original_version' => ' (8.x-1.1-alpha12)', 'versions' => [['op' => '=', 'version' => '1.1-alpha12']]]],
-      ['views_ui(8.x-1.1-beta8)', ['name' => 'views_ui', 'original_version' => ' (8.x-1.1-beta8)', 'versions' => [['op' => '=', 'version' => '1.1-beta8']]]],
-      ['views_ui(8.x-1.1-rc11)', ['name' => 'views_ui', 'original_version' => ' (8.x-1.1-rc11)', 'versions' => [['op' => '=', 'version' => '1.1-rc11']]]],
-      ['views_ui(8.x-1.12)', ['name' => 'views_ui', 'original_version' => ' (8.x-1.12)', 'versions' => [['op' => '=', 'version' => '1.12']]]],
-      ['views_ui(8.x-1.x)', ['name' => 'views_ui', 'original_version' => ' (8.x-1.x)', 'versions' => [['op' => '<', 'version' => '2.x'], ['op' => '>=', 'version' => '1.x']]]],
-      ['views_ui( <= 8.x-1.x)', ['name' => 'views_ui', 'original_version' => ' ( <= 8.x-1.x)', 'versions' => [['op' => '<=', 'version' => '2.x']]]],
-      ['views_ui(<= 8.x-1.x)', ['name' => 'views_ui', 'original_version' => ' (<= 8.x-1.x)', 'versions' => [['op' => '<=', 'version' => '2.x']]]],
-      ['views_ui( <=8.x-1.x)', ['name' => 'views_ui', 'original_version' => ' ( <=8.x-1.x)', 'versions' => [['op' => '<=', 'version' => '2.x']]]],
-      ['views_ui(>8.x-1.x)', ['name' => 'views_ui', 'original_version' => ' (>8.x-1.x)', 'versions' => [['op' => '>', 'version' => '2.x']]]],
-      ['drupal:views_ui(>8.x-1.x)', ['project' => 'drupal', 'name' => 'views_ui', 'original_version' => ' (>8.x-1.x)', 'versions' => [['op' => '>', 'version' => '2.x']]]],
-    ];
-  }
-
   /**
    * @covers ::getModuleDirectories
    */
-- 
GitLab