Fix matching compatible and asterisk versions (PY-27076)

Use new package version comparator where possible
This commit is contained in:
Semyon Proshev
2017-12-07 16:47:45 +03:00
parent 028079e6d4
commit b0fa6d56a4
7 changed files with 69 additions and 32 deletions
@@ -299,6 +299,7 @@ public class PyRequirement {
* @deprecated This method will be removed in 2018.2.
*/
@NotNull
@Deprecated
public PyRequirement withVersionComparator(@NotNull Comparator<String> comparator) {
return new PyRequirement(myName,
ContainerUtil.map(myVersionSpecs, spec -> spec.withVersionComparator(comparator)), myInstallOptions, myExtras);
@@ -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);
}
}
@@ -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));
@@ -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<PyRequirement> parseRequirements(@NotNull String text) {
return ContainerUtil.map(PyRequirement.fromText(text),
req -> req.withVersionComparator(PyPackageVersionComparator.getSTR_COMPARATOR()));
return PyPackageUtil.fix(PyRequirement.fromText(text));
}
// public List<PyPackage> 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));
@@ -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<PyRequirement> 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<PyRequirement> fix(@NotNull List<PyRequirement> requirements) {
return ContainerUtil.map(requirements, PyPackageUtil::fix);
}
@NotNull
private static List<PyRequirement> mergeSetupPyRequirements(@NotNull List<PyRequirement> requirementsFromRequires,
@NotNull List<PyRequirement> requirementsFromLinks) {
@@ -363,7 +384,7 @@ public class PyPackageUtil {
public static List<PyRequirement> getRequirementsFromTxt(@NotNull Module module) {
final VirtualFile requirementsTxt = findRequirementsTxt(module);
if (requirementsTxt != null) {
return PyRequirement.fromFile(requirementsTxt);
return fix(PyRequirement.fromFile(requirementsTxt));
}
return null;
}
@@ -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;
}
@@ -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(