diff --git a/python/openapi/src/com/jetbrains/python/packaging/PyRequirement.java b/python/openapi/src/com/jetbrains/python/packaging/PyRequirement.java index 0399ca4e8472..c948f041c737 100644 --- a/python/openapi/src/com/jetbrains/python/packaging/PyRequirement.java +++ b/python/openapi/src/com/jetbrains/python/packaging/PyRequirement.java @@ -299,6 +299,7 @@ public class PyRequirement { * @deprecated This method will be removed in 2018.2. */ @NotNull + @Deprecated public PyRequirement withVersionComparator(@NotNull Comparator comparator) { return new PyRequirement(myName, ContainerUtil.map(myVersionSpecs, spec -> spec.withVersionComparator(comparator)), myInstallOptions, myExtras); diff --git a/python/openapi/src/com/jetbrains/python/packaging/requirement/PyRequirementVersionSpec.java b/python/openapi/src/com/jetbrains/python/packaging/requirement/PyRequirementVersionSpec.java index 989e2b7f088b..49e642440023 100644 --- a/python/openapi/src/com/jetbrains/python/packaging/requirement/PyRequirementVersionSpec.java +++ b/python/openapi/src/com/jetbrains/python/packaging/requirement/PyRequirementVersionSpec.java @@ -129,8 +129,12 @@ public class PyRequirementVersionSpec { case COMPATIBLE: Objects.requireNonNull(myParsedVersion); - return new PyRequirementVersionSpec(PyRequirementRelation.GTE, myParsedVersion).matches(version) && - new PyRequirementVersionSpec(PyRequirementRelation.EQ, toEqPartOfCompatibleRelation(myParsedVersion)).matches(version); + return new PyRequirementVersionSpec(PyRequirementRelation.GTE, myParsedVersion) + .withVersionComparator(myVersionComparator) + .matches(version) && + new PyRequirementVersionSpec(PyRequirementRelation.EQ, toEqPartOfCompatibleRelation(myParsedVersion)) + .withVersionComparator(myVersionComparator) + .matches(version); case STR_EQ: return version.equals(myVersion); default: @@ -159,10 +163,10 @@ public class PyRequirementVersionSpec { @NotNull private static PyRequirementVersion toEqPartOfCompatibleRelation(@NotNull PyRequirementVersion version) { final String release = version.getRelease(); - final int lastPoint = release.lastIndexOf("."); + final int lastPoint = release.lastIndexOf('.'); if (lastPoint == -1) return version; - return new PyRequirementVersion(version.getEpoch(), release.substring(0, lastPoint) + "*", null, null, null, null); + return new PyRequirementVersion(version.getEpoch(), release.substring(0, lastPoint + 1) + "*", null, null, null, null); } } diff --git a/python/src/com/jetbrains/python/packaging/PyCondaPackageManagerImpl.java b/python/src/com/jetbrains/python/packaging/PyCondaPackageManagerImpl.java index 41075ca72a3f..8e960094d591 100644 --- a/python/src/com/jetbrains/python/packaging/PyCondaPackageManagerImpl.java +++ b/python/src/com/jetbrains/python/packaging/PyCondaPackageManagerImpl.java @@ -172,7 +172,7 @@ public class PyCondaPackageManagerImpl extends PyPackageManagerImpl { if (fields.size() >= 4) { final String requiresLine = fields.get(3); final String requiresSpec = StringUtil.join(StringUtil.split(requiresLine, ":"), "\n"); - requirements.addAll(PyRequirement.fromText(requiresSpec)); + requirements.addAll(PyPackageUtil.fix(PyRequirement.fromText(requiresSpec))); } if (!"Python".equals(name)) { packages.add(new PyPackage(name, version, "", requirements)); diff --git a/python/src/com/jetbrains/python/packaging/PyPackageManagerImpl.java b/python/src/com/jetbrains/python/packaging/PyPackageManagerImpl.java index 32fc9b9540bf..902263281c1c 100644 --- a/python/src/com/jetbrains/python/packaging/PyPackageManagerImpl.java +++ b/python/src/com/jetbrains/python/packaging/PyPackageManagerImpl.java @@ -23,7 +23,6 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.*; import com.intellij.openapi.vfs.newvfs.BulkFileListener; import com.intellij.openapi.vfs.newvfs.events.VFileEvent; -import com.intellij.util.containers.ContainerUtil; import com.intellij.util.messages.MessageBusConnection; import com.intellij.util.net.HttpConfigurable; import com.jetbrains.python.PythonHelpersLocator; @@ -356,8 +355,7 @@ public class PyPackageManagerImpl extends PyPackageManager { @NotNull @Override public List parseRequirements(@NotNull String text) { - return ContainerUtil.map(PyRequirement.fromText(text), - req -> req.withVersionComparator(PyPackageVersionComparator.getSTR_COMPARATOR())); + return PyPackageUtil.fix(PyRequirement.fromText(text)); } // public List refreshAndGetPackagesIfNotInProgress(boolean alwaysRefresh) throws ExecutionException @@ -526,7 +524,7 @@ public class PyPackageManagerImpl extends PyPackageManager { if (fields.size() >= 4) { final String requiresLine = fields.get(3); final String requiresSpec = StringUtil.join(StringUtil.split(requiresLine, ":"), "\n"); - requirements.addAll(PyRequirement.fromText(requiresSpec)); + requirements.addAll(PyPackageUtil.fix(PyRequirement.fromText(requiresSpec))); } if (!"Python".equals(name)) { packages.add(new PyPackage(name, version, location, requirements)); diff --git a/python/src/com/jetbrains/python/packaging/PyPackageUtil.java b/python/src/com/jetbrains/python/packaging/PyPackageUtil.java index c29f7dc1efc4..8e1cc3aa67d8 100644 --- a/python/src/com/jetbrains/python/packaging/PyPackageUtil.java +++ b/python/src/com/jetbrains/python/packaging/PyPackageUtil.java @@ -36,6 +36,7 @@ import com.intellij.openapi.vfs.VirtualFileVisitor; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; +import com.intellij.util.containers.ContainerUtil; import com.jetbrains.python.PyBundle; import com.jetbrains.python.PyNames; import com.jetbrains.python.codeInsight.controlflow.ScopeOwner; @@ -155,19 +156,39 @@ public class PyPackageUtil { private static List getSetupPyRequiresFromArguments(@NotNull Module module, @NotNull PyCallExpression setupCall, @NotNull String... argumentNames) { - return PyRequirement.fromText( - Stream - .of(argumentNames) - .map(setupCall::getKeywordArgument) - .map(requires -> resolveRequiresValue(module, requires)) - .filter(requires -> requires != null) - .flatMap(requires -> Stream.of(requires.getElements())) - .filter(PyStringLiteralExpression.class::isInstance) - .map(requirement -> ((PyStringLiteralExpression)requirement).getStringValue()) - .collect(Collectors.joining("\n")) + return fix( + PyRequirement.fromText( + Stream + .of(argumentNames) + .map(setupCall::getKeywordArgument) + .map(requires -> resolveRequiresValue(module, requires)) + .filter(requires -> requires != null) + .flatMap(requires -> Stream.of(requires.getElements())) + .filter(PyStringLiteralExpression.class::isInstance) + .map(requirement -> ((PyStringLiteralExpression)requirement).getStringValue()) + .collect(Collectors.joining("\n")) + ) ); } + /** + * @deprecated This method will be removed in 2018.2. + */ + @NotNull + @Deprecated + public static PyRequirement fix(@NotNull PyRequirement requirement) { + return requirement.withVersionComparator(PyPackageVersionComparator.getSTR_COMPARATOR()); + } + + /** + * @deprecated This method will be removed in 2018.2. + */ + @NotNull + @Deprecated + public static List fix(@NotNull List requirements) { + return ContainerUtil.map(requirements, PyPackageUtil::fix); + } + @NotNull private static List mergeSetupPyRequirements(@NotNull List requirementsFromRequires, @NotNull List requirementsFromLinks) { @@ -363,7 +384,7 @@ public class PyPackageUtil { public static List getRequirementsFromTxt(@NotNull Module module) { final VirtualFile requirementsTxt = findRequirementsTxt(module); if (requirementsTxt != null) { - return PyRequirement.fromFile(requirementsTxt); + return fix(PyRequirement.fromFile(requirementsTxt)); } return null; } diff --git a/python/testSrc/com/jetbrains/python/packaging/PyPackageTest.java b/python/testSrc/com/jetbrains/python/packaging/PyPackageTest.java index 1a0fc0603c8b..68055d1cc028 100644 --- a/python/testSrc/com/jetbrains/python/packaging/PyPackageTest.java +++ b/python/testSrc/com/jetbrains/python/packaging/PyPackageTest.java @@ -40,7 +40,7 @@ public class PyPackageTest extends TestCase { @NotNull private static PyRequirement createRequirement(@NotNull String options) { - final PyRequirement requirement = PyRequirement.fromLine(options); + final PyRequirement requirement = PyPackageUtil.fix(PyRequirement.fromLine(options)); assertNotNull(requirement); return requirement; } diff --git a/python/testSrc/com/jetbrains/python/packaging/PyRequirementTest.java b/python/testSrc/com/jetbrains/python/packaging/PyRequirementTest.java index b2cd8af80e79..364bbcc15e06 100644 --- a/python/testSrc/com/jetbrains/python/packaging/PyRequirementTest.java +++ b/python/testSrc/com/jetbrains/python/packaging/PyRequirementTest.java @@ -17,6 +17,8 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import static com.jetbrains.python.packaging.PyPackageUtil.fix; + /** * @author vlan */ @@ -2244,7 +2246,7 @@ public class PyRequirementTest extends PyTestCase { // PY-6355 public void testTrailingZeroesInVersion() { - final PyRequirement req = PyRequirement.fromLine("foo==0.8.0"); + final PyRequirement req = fix(PyRequirement.fromLine("foo==0.8.0")); final PyPackage pkg = new PyPackage("foo", "0.8", null, Collections.emptyList()); assertNotNull(req); assertEquals(pkg, req.match(Collections.singletonList(pkg))); @@ -2252,7 +2254,7 @@ public class PyRequirementTest extends PyTestCase { // PY-6438 public void testUnderscoreMatchesDash() { - final PyRequirement req = PyRequirement.fromLine("pyramid_zcml"); + final PyRequirement req = fix(PyRequirement.fromLine("pyramid_zcml")); final PyPackage pkg = new PyPackage("pyramid-zcml", "0.1", null, Collections.emptyList()); assertNotNull(req); assertEquals(pkg, req.match(Collections.singletonList(pkg))); @@ -2260,7 +2262,7 @@ public class PyRequirementTest extends PyTestCase { // PY-20242 public void testVersionInterpretedAsString() { - final PyRequirement req = PyRequirement.fromLine("foo===version"); + final PyRequirement req = fix(PyRequirement.fromLine("foo===version")); final PyPackage pkg = new PyPackage("foo", "version", null, Collections.emptyList()); assertNotNull(req); assertEquals(pkg, req.match(Collections.singletonList(pkg))); @@ -2271,11 +2273,11 @@ public class PyRequirementTest extends PyTestCase { final PyPackage firstPackageWithLocalVersion = new PyPackage("foo", "1.0+foo0100", null, Collections.emptyList()); final PyPackage secondPackageWithLocalVersion = new PyPackage("foo", "1.0+foo0101", null, Collections.emptyList()); - final PyRequirement requirement = PyRequirement.fromLine("foo==1.0"); + final PyRequirement requirement = fix(PyRequirement.fromLine("foo==1.0")); assertEquals(firstPackageWithLocalVersion, requirement.match(Collections.singletonList(firstPackageWithLocalVersion))); assertEquals(secondPackageWithLocalVersion, requirement.match(Collections.singletonList(secondPackageWithLocalVersion))); - final PyRequirement requirementWithLocalVersion = PyRequirement.fromLine("foo==1.0+foo0100"); + final PyRequirement requirementWithLocalVersion = fix(PyRequirement.fromLine("foo==1.0+foo0100")); assertEquals(firstPackageWithLocalVersion, requirementWithLocalVersion.match(Collections.singletonList(firstPackageWithLocalVersion))); assertNull(requirementWithLocalVersion.match(Collections.singletonList(secondPackageWithLocalVersion))); } @@ -2283,7 +2285,7 @@ public class PyRequirementTest extends PyTestCase { // https://www.python.org/dev/peps/pep-0440/#version-matching // PY-22275 public void testMatchingStar() { - final PyRequirement requirement = PyRequirement.fromLine("foo==1.1.*"); + final PyRequirement requirement = fix(PyRequirement.fromLine("foo==1.1.*")); final PyPackage release = new PyPackage("foo", "1.1.2", null, Collections.emptyList()); final PyPackage pre = new PyPackage("foo", "1.1.2a1", null, Collections.emptyList()); final PyPackage post = new PyPackage("foo", "1.1.2.post1", null, Collections.emptyList()); @@ -2296,7 +2298,7 @@ public class PyRequirementTest extends PyTestCase { assertEquals(dev, requirement.match(Collections.singletonList(dev))); assertEquals(localVersion, requirement.match(Collections.singletonList(localVersion))); - final PyRequirement negativeRequirement = PyRequirement.fromLine("foo!=1.1.*"); + final PyRequirement negativeRequirement = fix(PyRequirement.fromLine("foo!=1.1.*")); final PyPackage negativeRelease = new PyPackage("foo", "1.2.2", null, Collections.emptyList()); final PyPackage negativePre = new PyPackage("foo", "1.2.2a1", null, Collections.emptyList()); final PyPackage negativePost = new PyPackage("foo", "1.2.2.post1", null, Collections.emptyList()); @@ -2314,7 +2316,7 @@ public class PyRequirementTest extends PyTestCase { // https://www.python.org/dev/peps/pep-0440/#compatible-release // PY-20522 public void testMatchingCompatible() { - final PyRequirement requirement = PyRequirement.fromLine("foo~=2.2"); + final PyRequirement requirement = fix(PyRequirement.fromLine("foo~=2.2")); final PyPackage release = new PyPackage("foo", "2.3", null, Collections.emptyList()); final PyPackage pre = new PyPackage("foo", "2.3a1", null, Collections.emptyList()); final PyPackage post = new PyPackage("foo", "2.3.post1", null, Collections.emptyList()); @@ -2327,14 +2329,14 @@ public class PyRequirementTest extends PyTestCase { assertEquals(dev, requirement.match(Collections.singletonList(dev))); assertEquals(localVersion, requirement.match(Collections.singletonList(localVersion))); - final PyRequirement moreModernRequirement = PyRequirement.fromLine("foo~=2.4"); + final PyRequirement moreModernRequirement = fix(PyRequirement.fromLine("foo~=2.4")); assertNull(moreModernRequirement.match(Arrays.asList(release, pre, post, dev, localVersion))); } // https://www.python.org/dev/peps/pep-0440/#compatible-release // PY-20522 public void testMatchingCompatibleWithTrailingZero() { - final PyRequirement requirement = PyRequirement.fromLine("foo~=2.20.0"); + final PyRequirement requirement = fix(PyRequirement.fromLine("foo~=2.20.0")); final PyPackage release = new PyPackage("foo", "2.20.3", null, Collections.emptyList()); final PyPackage pre = new PyPackage("foo", "2.20.3a1", null, Collections.emptyList()); final PyPackage post = new PyPackage("foo", "2.20.3.post1", null, Collections.emptyList()); @@ -2347,10 +2349,21 @@ public class PyRequirementTest extends PyTestCase { assertEquals(dev, requirement.match(Collections.singletonList(dev))); assertEquals(localVersion, requirement.match(Collections.singletonList(localVersion))); - final PyRequirement moreModernRequirement = PyRequirement.fromLine("foo~=2.21.0"); + final PyRequirement moreModernRequirement = fix(PyRequirement.fromLine("foo~=2.21.0")); assertNull(moreModernRequirement.match(Arrays.asList(release, pre, post, dev, localVersion))); } + // PY-27076 + public void testMatchingAsteriskAndCompatibleWithTwoTrailingZeros() { + final PyRequirement requirement1 = fix(PyRequirement.fromLine("social-auth-app-django==2.0.*")); + final PyRequirement requirement2 = fix(PyRequirement.fromLine("social-auth-app-django~=2.0.0")); + + final PyPackage pkg = new PyPackage("social-auth-app-django", "2.0.0", null, Collections.emptyList()); + + assertEquals(pkg, requirement1.match(Collections.singletonList(pkg))); + assertEquals(pkg, requirement2.match(Collections.singletonList(pkg))); + } + // OPTIONS public void testOptions() { assertEmpty(