From af4fa99601681b83273d03daf4bf131fcd0cb1e2 Mon Sep 17 00:00:00 2001 From: Eric Arellano Date: Fri, 30 Jul 2021 13:26:53 -0700 Subject: [PATCH] [WIP] Add experimental lockfile support to IPython, Pytest, and Pylint --- 3rdparty/python/lockfiles/ipython.txt | 68 ++++++ 3rdparty/python/lockfiles/pytest.txt | 201 ++++++++++++++++++ build-support/bin/generate_all_lockfiles.py | 16 +- pants.toml | 4 + .../backend/experimental/python/lockfile.py | 4 +- .../backend/python/lint/bandit/subsystem.py | 11 +- .../backend/python/lint/black/subsystem.py | 2 +- .../backend/python/lint/flake8/subsystem.py | 11 +- src/python/pants/backend/python/register.py | 4 +- .../backend/python/subsystems/ipython.py | 62 +++++- .../python/subsystems/ipython_lockfile.txt | 68 ++++++ .../backend/python/subsystems/ipython_test.py | 72 +++++++ .../pants/backend/python/subsystems/pytest.py | 62 +++++- .../backend/python/subsystems/pytest_test.py | 98 +++++++++ .../interpreter_constraints_test.py | 7 + 15 files changed, 680 insertions(+), 10 deletions(-) create mode 100644 3rdparty/python/lockfiles/ipython.txt create mode 100644 3rdparty/python/lockfiles/pytest.txt create mode 100644 src/python/pants/backend/python/subsystems/ipython_lockfile.txt create mode 100644 src/python/pants/backend/python/subsystems/ipython_test.py create mode 100644 src/python/pants/backend/python/subsystems/pytest_test.py diff --git a/3rdparty/python/lockfiles/ipython.txt b/3rdparty/python/lockfiles/ipython.txt new file mode 100644 index 000000000000..88a72c78f044 --- /dev/null +++ b/3rdparty/python/lockfiles/ipython.txt @@ -0,0 +1,68 @@ +# +# This file is autogenerated by pip-compile with python 3.9 +# To update, run: +# +# pip-compile --allow-unsafe --generate-hashes --output-file=3rdparty/python/lockfiles/ipython.txt reqs.txt +# +appnope==0.1.2 \ + --hash=sha256:93aa393e9d6c54c5cd570ccadd8edad61ea0c4b9ea7a01409020c9aa019eb442 \ + --hash=sha256:dd83cd4b5b460958838f6eb3000c660b1f9caf2a5b1de4264e941512f603258a + # via ipython +backcall==0.2.0 \ + --hash=sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e \ + --hash=sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255 + # via ipython +decorator==5.0.9 \ + --hash=sha256:6e5c199c16f7a9f0e3a61a4a54b3d27e7dad0dbdde92b944426cb20914376323 \ + --hash=sha256:72ecfba4320a893c53f9706bebb2d55c270c1e51a28789361aa93e4a21319ed5 + # via ipython +ipython==7.16.1 \ + --hash=sha256:2dbcc8c27ca7d3cfe4fcdff7f45b27f9a8d3edfa70ff8024a71c7a8eb5f09d64 \ + --hash=sha256:9f4fcb31d3b2c533333893b9172264e4821c1ac91839500f31bd43f2c59b3ccf + # via -r reqs.txt +ipython-genutils==0.2.0 \ + --hash=sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8 \ + --hash=sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8 + # via traitlets +jedi==0.18.0 \ + --hash=sha256:18456d83f65f400ab0c2d3319e48520420ef43b23a086fdc05dff34132f0fb93 \ + --hash=sha256:92550a404bad8afed881a137ec9a461fed49eca661414be45059329614ed0707 + # via ipython +parso==0.8.2 \ + --hash=sha256:12b83492c6239ce32ff5eed6d3639d6a536170723c6f3f1506869f1ace413398 \ + --hash=sha256:a8c4922db71e4fdb90e0d0bc6e50f9b273d3397925e5e60a717e719201778d22 + # via jedi +pexpect==4.8.0 \ + --hash=sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937 \ + --hash=sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c + # via ipython +pickleshare==0.7.5 \ + --hash=sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca \ + --hash=sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56 + # via ipython +prompt-toolkit==3.0.19 \ + --hash=sha256:08360ee3a3148bdb5163621709ee322ec34fc4375099afa4bbf751e9b7b7fa4f \ + --hash=sha256:7089d8d2938043508aa9420ec18ce0922885304cddae87fb96eebca942299f88 + # via ipython +ptyprocess==0.7.0 \ + --hash=sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35 \ + --hash=sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220 + # via pexpect +pygments==2.9.0 \ + --hash=sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f \ + --hash=sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e + # via ipython +traitlets==5.0.5 \ + --hash=sha256:178f4ce988f69189f7e523337a3e11d91c786ded9360174a3d9ca83e79bc5396 \ + --hash=sha256:69ff3f9d5351f31a7ad80443c2674b7099df13cc41fc5fa6e2f6d3b0330b0426 + # via ipython +wcwidth==0.2.5 \ + --hash=sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784 \ + --hash=sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83 + # via prompt-toolkit + +# The following packages are considered to be unsafe in a requirements file: +setuptools==57.4.0 \ + --hash=sha256:6bac238ffdf24e8806c61440e755192470352850f3419a52f26ffe0a1a64f465 \ + --hash=sha256:a49230977aa6cfb9d933614d2f7b79036e9945c4cdd7583163f4e920b83418d6 + # via ipython diff --git a/3rdparty/python/lockfiles/pytest.txt b/3rdparty/python/lockfiles/pytest.txt new file mode 100644 index 000000000000..6109b63dfde5 --- /dev/null +++ b/3rdparty/python/lockfiles/pytest.txt @@ -0,0 +1,201 @@ +# --- BEGIN PANTS LOCKFILE METADATA: DO NOT EDIT OR REMOVE --- +# invalidation digest: ce1562366b0a92f46cf854f8fce4cd1851a962eb65d35da85641e4ce83d56c65 +# --- END PANTS LOCKFILE METADATA --- +# +# This file is autogenerated by pip-compile with python 3.9 +# To update, run: +# +# pip-compile --allow-unsafe --generate-hashes --output-file=3rdparty/python/lockfiles/pytest.txt reqs.txt +# +appnope==0.1.2 \ + --hash=sha256:93aa393e9d6c54c5cd570ccadd8edad61ea0c4b9ea7a01409020c9aa019eb442 \ + --hash=sha256:dd83cd4b5b460958838f6eb3000c660b1f9caf2a5b1de4264e941512f603258a + # via ipython +attrs==21.2.0 \ + --hash=sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1 \ + --hash=sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb + # via pytest +backcall==0.2.0 \ + --hash=sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e \ + --hash=sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255 + # via ipython +coverage==5.5 \ + --hash=sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c \ + --hash=sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6 \ + --hash=sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45 \ + --hash=sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a \ + --hash=sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03 \ + --hash=sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529 \ + --hash=sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a \ + --hash=sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a \ + --hash=sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2 \ + --hash=sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6 \ + --hash=sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759 \ + --hash=sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53 \ + --hash=sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a \ + --hash=sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4 \ + --hash=sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff \ + --hash=sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502 \ + --hash=sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793 \ + --hash=sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb \ + --hash=sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905 \ + --hash=sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821 \ + --hash=sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b \ + --hash=sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81 \ + --hash=sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0 \ + --hash=sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b \ + --hash=sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3 \ + --hash=sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184 \ + --hash=sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701 \ + --hash=sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a \ + --hash=sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82 \ + --hash=sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638 \ + --hash=sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5 \ + --hash=sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083 \ + --hash=sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6 \ + --hash=sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90 \ + --hash=sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465 \ + --hash=sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a \ + --hash=sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3 \ + --hash=sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e \ + --hash=sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066 \ + --hash=sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf \ + --hash=sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b \ + --hash=sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae \ + --hash=sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669 \ + --hash=sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873 \ + --hash=sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b \ + --hash=sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6 \ + --hash=sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb \ + --hash=sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160 \ + --hash=sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c \ + --hash=sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079 \ + --hash=sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d \ + --hash=sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6 + # via pytest-cov +decorator==5.0.9 \ + --hash=sha256:6e5c199c16f7a9f0e3a61a4a54b3d27e7dad0dbdde92b944426cb20914376323 \ + --hash=sha256:72ecfba4320a893c53f9706bebb2d55c270c1e51a28789361aa93e4a21319ed5 + # via + # ipdb + # ipython +icdiff==2.0.3 \ + --hash=sha256:230fc488b9e434127a97193672303b0d7ae75c5e362530ebcdf6d7f534b2444a + # via pytest-icdiff +iniconfig==1.1.1 \ + --hash=sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3 \ + --hash=sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32 + # via pytest +ipdb==0.13.9 \ + --hash=sha256:951bd9a64731c444fd907a5ce268543020086a697f6be08f7cc2c9a752a278c5 + # via -r reqs.txt +ipython==7.26.0 \ + --hash=sha256:0cff04bb042800129348701f7bd68a430a844e8fb193979c08f6c99f28bb735e \ + --hash=sha256:892743b65c21ed72b806a3a602cca408520b3200b89d1924f4b3d2cdb3692362 + # via ipdb +ipython-genutils==0.2.0 \ + --hash=sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8 \ + --hash=sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8 + # via traitlets +jedi==0.18.0 \ + --hash=sha256:18456d83f65f400ab0c2d3319e48520420ef43b23a086fdc05dff34132f0fb93 \ + --hash=sha256:92550a404bad8afed881a137ec9a461fed49eca661414be45059329614ed0707 + # via ipython +matplotlib-inline==0.1.2 \ + --hash=sha256:5cf1176f554abb4fa98cb362aa2b55c500147e4bdbb07e3fda359143e1da0811 \ + --hash=sha256:f41d5ff73c9f5385775d5c0bc13b424535c8402fe70ea8210f93e11f3683993e + # via ipython +packaging==21.0 \ + --hash=sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7 \ + --hash=sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14 + # via pytest +parso==0.8.2 \ + --hash=sha256:12b83492c6239ce32ff5eed6d3639d6a536170723c6f3f1506869f1ace413398 \ + --hash=sha256:a8c4922db71e4fdb90e0d0bc6e50f9b273d3397925e5e60a717e719201778d22 + # via jedi +pexpect==4.8.0 \ + --hash=sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937 \ + --hash=sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c + # via ipython +pickleshare==0.7.5 \ + --hash=sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca \ + --hash=sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56 + # via ipython +pluggy==0.13.1 \ + --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ + --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d + # via pytest +pprintpp==0.4.0 \ + --hash=sha256:b6b4dcdd0c0c0d75e4d7b2f21a9e933e5b2ce62b26e1a54537f9651ae5a5c01d \ + --hash=sha256:ea826108e2c7f49dc6d66c752973c3fc9749142a798d6b254e1e301cfdbc6403 + # via pytest-icdiff +prompt-toolkit==3.0.19 \ + --hash=sha256:08360ee3a3148bdb5163621709ee322ec34fc4375099afa4bbf751e9b7b7fa4f \ + --hash=sha256:7089d8d2938043508aa9420ec18ce0922885304cddae87fb96eebca942299f88 + # via ipython +ptyprocess==0.7.0 \ + --hash=sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35 \ + --hash=sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220 + # via pexpect +py==1.10.0 \ + --hash=sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3 \ + --hash=sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a + # via pytest +pygments==2.9.0 \ + --hash=sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f \ + --hash=sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e + # via + # -r reqs.txt + # ipython +pyparsing==2.4.7 \ + --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ + --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b + # via packaging +pytest==6.2.4 \ + --hash=sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b \ + --hash=sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890 + # via + # -r reqs.txt + # pytest-cov + # pytest-html + # pytest-icdiff + # pytest-metadata +pytest-cov==2.11.1 \ + --hash=sha256:359952d9d39b9f822d9d29324483e7ba04a3a17dd7d05aa6beb7ea01e359e5f7 \ + --hash=sha256:bdb9fdb0b85a7cc825269a4c56b48ccaa5c7e365054b6038772c32ddcdc969da + # via -r reqs.txt +pytest-html==3.1.1 \ + --hash=sha256:3ee1cf319c913d19fe53aeb0bc400e7b0bc2dbeb477553733db1dad12eb75ee3 \ + --hash=sha256:b7f82f123936a3f4d2950bc993c2c1ca09ce262c9ae12f9ac763a2401380b455 + # via -r reqs.txt +pytest-icdiff==0.5 \ + --hash=sha256:3a14097f4385665cb04330e6ae09a3dd430375f717e94482af6944470ad5f100 + # via -r reqs.txt +pytest-metadata==1.11.0 \ + --hash=sha256:576055b8336dd4a9006dd2a47615f76f2f8c30ab12b1b1c039d99e834583523f \ + --hash=sha256:71b506d49d34e539cc3cfdb7ce2c5f072bea5c953320002c95968e0238f8ecf1 + # via pytest-html +toml==0.10.2 \ + --hash=sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b \ + --hash=sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f + # via + # ipdb + # pytest +traitlets==5.0.5 \ + --hash=sha256:178f4ce988f69189f7e523337a3e11d91c786ded9360174a3d9ca83e79bc5396 \ + --hash=sha256:69ff3f9d5351f31a7ad80443c2674b7099df13cc41fc5fa6e2f6d3b0330b0426 + # via + # ipython + # matplotlib-inline +wcwidth==0.2.5 \ + --hash=sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784 \ + --hash=sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83 + # via prompt-toolkit + +# The following packages are considered to be unsafe in a requirements file: +setuptools==57.4.0 \ + --hash=sha256:6bac238ffdf24e8806c61440e755192470352850f3419a52f26ffe0a1a64f465 \ + --hash=sha256:a49230977aa6cfb9d933614d2f7b79036e9945c4cdd7583163f4e920b83418d6 + # via + # ipdb + # ipython diff --git a/build-support/bin/generate_all_lockfiles.py b/build-support/bin/generate_all_lockfiles.py index 112b49e798aa..2493a6b1254b 100644 --- a/build-support/bin/generate_all_lockfiles.py +++ b/build-support/bin/generate_all_lockfiles.py @@ -21,6 +21,8 @@ from pants.backend.python.lint.flake8.subsystem import Flake8 from pants.backend.python.lint.isort.subsystem import Isort from pants.backend.python.lint.yapf.subsystem import Yapf +from pants.backend.python.subsystems.ipython import IPython +from pants.backend.python.subsystems.pytest import PyTest from pants.backend.python.subsystems.setuptools import Setuptools from pants.python.python_setup import PythonSetup @@ -37,8 +39,8 @@ def validate_python_installed(binary: str) -> None: def main() -> None: # TODO(#12314): Pants should automatically validate that the necessary Python interpreters # are used. This is a temporary measure. - for v in ("3.6", "3.7", "3.8", "3.9"): - validate_python_installed(f"python{v}") + # for v in ("3.6", "3.7", "3.8", "3.9"): + # validate_python_installed(f"python{v}") # First, generate what we internally use. logger.info("First, generating lockfiles for internal usage") @@ -47,7 +49,7 @@ def main() -> None: "./pants", "--concurrent", "--tag='-lockfile_ignore'", - "lock", + # "lock", "tool-lock", "::", ], @@ -92,6 +94,10 @@ def main() -> None: f"--yapf-extra-requirements={repr(Yapf.default_extra_requirements)}", f"--yapf-interpreter-constraints={repr(Yapf.default_interpreter_constraints)}", f"--yapf-experimental-lockfile={Yapf.default_lockfile_path}", + # IPython. + f"--ipython-version={IPython.default_version}", + f"--ipython-extra-requirements={repr(IPython.default_extra_requirements)}", + f"--ipython-experimental-lockfile={IPython.default_lockfile_path}", # Setuptools. f"--setuptools-version={Setuptools.default_version}", f"--setuptools-extra-requirements={repr(Setuptools.default_extra_requirements)}", @@ -108,6 +114,10 @@ def main() -> None: f"--lambdex-extra-requirements={repr(Lambdex.default_extra_requirements)}", f"--lambdex-interpreter-constraints={repr(Lambdex.default_interpreter_constraints)}", f"--lambdex-experimental-lockfile={Lambdex.default_lockfile_path}", + # Pytest + f"--pytest-version={PyTest.default_version}", + f"--pytest-extra-requirements={repr(PyTest.default_extra_requirements)}", + f"--pytest-experimental-lockfile={PyTest.default_lockfile_path}", # Coverage.py f"--coverage-py-version={CoverageSubsystem.default_version}", f"--coverage-py-extra-requirements={repr(CoverageSubsystem.default_extra_requirements)}", diff --git a/pants.toml b/pants.toml index c9f6d0f79d19..333ac2001a68 100644 --- a/pants.toml +++ b/pants.toml @@ -74,6 +74,9 @@ root_patterns = [ experimental_lockfile = "3rdparty/python/lockfiles/user_reqs.txt" interpreter_constraints = [">=3.7,<3.10"] +[ipython] +experimental_lockfile = "3rdparty/python/lockfiles/ipython.txt" + [black] experimental_lockfile = "3rdparty/python/lockfiles/black.txt" @@ -156,6 +159,7 @@ extra_requirements.add = [ "pytest-icdiff", "pygments", ] +experimental_lockfile = "3rdparty/python/lockfiles/pytest.txt" timeout_default = 60 [test] diff --git a/src/python/pants/backend/experimental/python/lockfile.py b/src/python/pants/backend/experimental/python/lockfile.py index 45827104fcf0..7db81b2f8572 100644 --- a/src/python/pants/backend/experimental/python/lockfile.py +++ b/src/python/pants/backend/experimental/python/lockfile.py @@ -294,7 +294,9 @@ async def generate_all_tool_lockfiles( for req in requests if req.dest not in {"", ""} ) - merged_digest = await Get(Digest, MergeDigests(res.digest for res in results)) + merged_digest = await Get( + Digest, MergeDigests(res.digest for res in results if "pytest" in res.path) + ) workspace.write_digest(merged_digest) for result in results: logger.info(f"Wrote lockfile to {result.path}") diff --git a/src/python/pants/backend/python/lint/bandit/subsystem.py b/src/python/pants/backend/python/lint/bandit/subsystem.py index 13e77345d4f3..a9460e7ff60d 100644 --- a/src/python/pants/backend/python/lint/bandit/subsystem.py +++ b/src/python/pants/backend/python/lint/bandit/subsystem.py @@ -97,12 +97,21 @@ class BanditLockfileSentinel(PythonToolLockfileSentinel): @rule( - desc="Determine all Python interpreter versions used by Bandit in your project", + desc=( + "Determine all Python interpreter versions used by Bandit in your project (for lockfile " + "usage)" + ), level=LogLevel.DEBUG, ) async def setup_bandit_lockfile( _: BanditLockfileSentinel, bandit: Bandit, python_setup: PythonSetup ) -> PythonLockfileRequest: + # While Bandit will run in partitions, we need a single lockfile that works with every + # partition. + # + # This ORs all unique interpreter constraints. When paired with + # `InterpreterConstraints.partition_by_major_minor_versions`, the net effect is that every + # possible Python interpreter used will be covered. all_build_targets = await Get(UnexpandedTargets, AddressSpecs([DescendantAddresses("")])) unique_constraints = { InterpreterConstraints.create_from_compatibility_fields( diff --git a/src/python/pants/backend/python/lint/black/subsystem.py b/src/python/pants/backend/python/lint/black/subsystem.py index f54c5e13a303..73f7901d4839 100644 --- a/src/python/pants/backend/python/lint/black/subsystem.py +++ b/src/python/pants/backend/python/lint/black/subsystem.py @@ -115,7 +115,7 @@ class BlackLockfileSentinel(PythonToolLockfileSentinel): @rule( - desc="Determine if Black should use Python 3.8+", + desc="Determine if Black should use Python 3.8+ (for lockfile usage)", level=LogLevel.DEBUG, ) async def setup_black_lockfile( diff --git a/src/python/pants/backend/python/lint/flake8/subsystem.py b/src/python/pants/backend/python/lint/flake8/subsystem.py index af98b7047ea4..1d9919b68e8b 100644 --- a/src/python/pants/backend/python/lint/flake8/subsystem.py +++ b/src/python/pants/backend/python/lint/flake8/subsystem.py @@ -114,12 +114,21 @@ class Flake8LockfileSentinel(PythonToolLockfileSentinel): @rule( - desc="Determine all Python interpreter versions used by Flake8 in your project", + desc=( + "Determine all Python interpreter versions used by Flake8 in your project (for lockfile " + "usage)" + ), level=LogLevel.DEBUG, ) async def setup_flake8_lockfile( _: Flake8LockfileSentinel, flake8: Flake8, python_setup: PythonSetup ) -> PythonLockfileRequest: + # While Flake8 will run in partitions, we need a single lockfile that works with every + # partition. + # + # This ORs all unique interpreter constraints. When paired with + # `InterpreterConstraints.partition_by_major_minor_versions`, the net effect is that every + # possible Python interpreter used will be covered. all_build_targets = await Get(UnexpandedTargets, AddressSpecs([DescendantAddresses("")])) unique_constraints = { InterpreterConstraints.create_from_compatibility_fields( diff --git a/src/python/pants/backend/python/register.py b/src/python/pants/backend/python/register.py index 7d93ef84190a..1ff17676b874 100644 --- a/src/python/pants/backend/python/register.py +++ b/src/python/pants/backend/python/register.py @@ -22,7 +22,7 @@ from pants.backend.python.macros.poetry_requirements import PoetryRequirements from pants.backend.python.macros.python_artifact import PythonArtifact from pants.backend.python.macros.python_requirements import PythonRequirements -from pants.backend.python.subsystems import python_native_code, setuptools +from pants.backend.python.subsystems import ipython, pytest, python_native_code, setuptools from pants.backend.python.target_types import ( PexBinary, PythonDistribution, @@ -73,6 +73,8 @@ def rules(): *target_types_rules.rules(), *setup_py.rules(), *setuptools.rules(), + *ipython.rules(), + *pytest.rules(), ) diff --git a/src/python/pants/backend/python/subsystems/ipython.py b/src/python/pants/backend/python/subsystems/ipython.py index f7ddc3adbca5..4aa74c2c6be8 100644 --- a/src/python/pants/backend/python/subsystems/ipython.py +++ b/src/python/pants/backend/python/subsystems/ipython.py @@ -1,8 +1,23 @@ # Copyright 2020 Pants project contributors (see CONTRIBUTORS.md). # Licensed under the Apache License, Version 2.0 (see LICENSE). +from __future__ import annotations + +import itertools +from pants.base.specs import AddressSpecs, DescendantAddresses +from pants.backend.experimental.python.lockfile import ( + PythonLockfileRequest, + PythonToolLockfileSentinel, +) from pants.backend.python.subsystems.python_tool_base import PythonToolBase -from pants.backend.python.target_types import ConsoleScript +from pants.backend.python.target_types import ConsoleScript, InterpreterConstraintsField +from pants.backend.python.util_rules.interpreter_constraints import InterpreterConstraints +from pants.util.docutil import git_url +from pants.engine.rules import Get, collect_rules, rule +from pants.engine.target import UnexpandedTargets +from pants.engine.unions import UnionRule +from pants.python.python_setup import PythonSetup +from pants.util.logging import LogLevel class IPython(PythonToolBase): @@ -12,6 +27,11 @@ class IPython(PythonToolBase): default_version = "ipython==7.16.1" # The last version to support Python 3.6. default_main = ConsoleScript("ipython") + register_lockfile = True + default_lockfile_resource = ("pants.backend.python.subsystems", "ipython_lockfile.txt") + default_lockfile_path = "src/python/pants/backend/python/subsystems/ipython_lockfile.txt" + default_lockfile_url = git_url(default_lockfile_path) + @classmethod def register_options(cls, register): super().register_options(register) @@ -27,3 +47,43 @@ def register_options(cls, register): "then you will need to set this to False, and you may have issues with imports " "from your CWD shading the hermetic environment.", ) + + +class IPythonLockfileSentinel(PythonToolLockfileSentinel): + pass + + +@rule( + desc=( + "Determine all Python interpreter versions used by iPython in your project (for lockfile " + "usage)" + ), + level=LogLevel.DEBUG, +) +async def setup_ipython_lockfile( + _: IPythonLockfileSentinel, ipython: IPython, python_setup: PythonSetup +) -> PythonLockfileRequest: + # IPython is often run against the whole repo (`./pants repl ::`), but it is possible to run + # on subsets of the codebase with disjoint interpreter constraints, such as + # `./pants repl py2::` and then `./pants repl py3::`. Still, even with those subsets possible, + # we need a single lockfile that works with all possible Python interpreters in use. + # + # This ORs all unique interpreter constraints. When paired with + # `InterpreterConstraints.partition_by_major_minor_versions`, the net effect is that + # every possible Python interpreter used will be covered. + all_build_targets = await Get(UnexpandedTargets, AddressSpecs([DescendantAddresses("")])) + unique_constraints = { + InterpreterConstraints.create_from_compatibility_fields( + [tgt[InterpreterConstraintsField]], python_setup + ) + for tgt in all_build_targets + if tgt.has_field(InterpreterConstraintsField) + } + constraints = InterpreterConstraints(itertools.chain.from_iterable(unique_constraints)) + return PythonLockfileRequest.from_tool( + ipython, constraints or InterpreterConstraints(python_setup.interpreter_constraints) + ) + + +def rules(): + return (*collect_rules(), UnionRule(PythonToolLockfileSentinel, IPythonLockfileSentinel)) diff --git a/src/python/pants/backend/python/subsystems/ipython_lockfile.txt b/src/python/pants/backend/python/subsystems/ipython_lockfile.txt new file mode 100644 index 000000000000..a74c4eac1937 --- /dev/null +++ b/src/python/pants/backend/python/subsystems/ipython_lockfile.txt @@ -0,0 +1,68 @@ +# +# This file is autogenerated by pip-compile with python 3.8 +# To update, run: +# +# pip-compile --allow-unsafe --generate-hashes --output-file=src/python/pants/backend/python/subsystems/ipython_lockfile.txt reqs.txt +# +appnope==0.1.2 \ + --hash=sha256:93aa393e9d6c54c5cd570ccadd8edad61ea0c4b9ea7a01409020c9aa019eb442 \ + --hash=sha256:dd83cd4b5b460958838f6eb3000c660b1f9caf2a5b1de4264e941512f603258a + # via ipython +backcall==0.2.0 \ + --hash=sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e \ + --hash=sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255 + # via ipython +decorator==5.0.9 \ + --hash=sha256:6e5c199c16f7a9f0e3a61a4a54b3d27e7dad0dbdde92b944426cb20914376323 \ + --hash=sha256:72ecfba4320a893c53f9706bebb2d55c270c1e51a28789361aa93e4a21319ed5 + # via ipython +ipython==7.16.1 \ + --hash=sha256:2dbcc8c27ca7d3cfe4fcdff7f45b27f9a8d3edfa70ff8024a71c7a8eb5f09d64 \ + --hash=sha256:9f4fcb31d3b2c533333893b9172264e4821c1ac91839500f31bd43f2c59b3ccf + # via -r reqs.txt +ipython-genutils==0.2.0 \ + --hash=sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8 \ + --hash=sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8 + # via traitlets +jedi==0.18.0 \ + --hash=sha256:18456d83f65f400ab0c2d3319e48520420ef43b23a086fdc05dff34132f0fb93 \ + --hash=sha256:92550a404bad8afed881a137ec9a461fed49eca661414be45059329614ed0707 + # via ipython +parso==0.8.2 \ + --hash=sha256:12b83492c6239ce32ff5eed6d3639d6a536170723c6f3f1506869f1ace413398 \ + --hash=sha256:a8c4922db71e4fdb90e0d0bc6e50f9b273d3397925e5e60a717e719201778d22 + # via jedi +pexpect==4.8.0 \ + --hash=sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937 \ + --hash=sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c + # via ipython +pickleshare==0.7.5 \ + --hash=sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca \ + --hash=sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56 + # via ipython +prompt-toolkit==3.0.19 \ + --hash=sha256:08360ee3a3148bdb5163621709ee322ec34fc4375099afa4bbf751e9b7b7fa4f \ + --hash=sha256:7089d8d2938043508aa9420ec18ce0922885304cddae87fb96eebca942299f88 + # via ipython +ptyprocess==0.7.0 \ + --hash=sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35 \ + --hash=sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220 + # via pexpect +pygments==2.9.0 \ + --hash=sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f \ + --hash=sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e + # via ipython +traitlets==5.0.5 \ + --hash=sha256:178f4ce988f69189f7e523337a3e11d91c786ded9360174a3d9ca83e79bc5396 \ + --hash=sha256:69ff3f9d5351f31a7ad80443c2674b7099df13cc41fc5fa6e2f6d3b0330b0426 + # via ipython +wcwidth==0.2.5 \ + --hash=sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784 \ + --hash=sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83 + # via prompt-toolkit + +# The following packages are considered to be unsafe in a requirements file: +setuptools==57.4.0 \ + --hash=sha256:6bac238ffdf24e8806c61440e755192470352850f3419a52f26ffe0a1a64f465 \ + --hash=sha256:a49230977aa6cfb9d933614d2f7b79036e9945c4cdd7583163f4e920b83418d6 + # via ipython diff --git a/src/python/pants/backend/python/subsystems/ipython_test.py b/src/python/pants/backend/python/subsystems/ipython_test.py new file mode 100644 index 000000000000..bc2cb492e706 --- /dev/null +++ b/src/python/pants/backend/python/subsystems/ipython_test.py @@ -0,0 +1,72 @@ +# Copyright 2021 Pants project contributors (see CONTRIBUTORS.md). +# Licensed under the Apache License, Version 2.0 (see LICENSE). + +from __future__ import annotations + +from textwrap import dedent + +from pants.backend.experimental.python.lockfile import PythonLockfileRequest +from pants.backend.python.subsystems.ipython import IPythonLockfileSentinel +from pants.backend.python.subsystems.ipython import rules as subsystem_rules +from pants.backend.python.target_types import PythonLibrary +from pants.backend.python.util_rules.interpreter_constraints import InterpreterConstraints +from pants.core.target_types import GenericTarget +from pants.testutil.rule_runner import QueryRule, RuleRunner + + +def test_setup_lockfile_interpreter_constraints() -> None: + rule_runner = RuleRunner( + rules=[*subsystem_rules(), QueryRule(PythonLockfileRequest, [IPythonLockfileSentinel])], + target_types=[PythonLibrary, GenericTarget], + ) + + global_constraint = "==3.9.*" + rule_runner.set_options( + [], env={"PANTS_PYTHON_SETUP_INTERPRETER_CONSTRAINTS": f"['{global_constraint}']"} + ) + + def assert_ics(build_file: str, expected: list[str]) -> None: + rule_runner.write_files({"project/BUILD": build_file}) + lockfile_request = rule_runner.request(PythonLockfileRequest, [IPythonLockfileSentinel()]) + assert lockfile_request.interpreter_constraints == InterpreterConstraints(expected) + + assert_ics("python_library()", [global_constraint]) + assert_ics("python_library(interpreter_constraints=['==2.7.*'])", ["==2.7.*"]) + assert_ics( + "python_library(interpreter_constraints=['==2.7.*', '==3.5.*'])", ["==2.7.*", "==3.5.*"] + ) + + # If no Python targets in repo, fall back to global python-setup constraints. + assert_ics("target()", [global_constraint]) + + # If there are multiple distinct ICs in the repo, we OR them. Even though the user might AND + # them by running `./pants repl ::`, they could also run on more precise subsets like + # `./pants repl py2::` and then `./pants repl py3::` + assert_ics( + dedent( + """\ + python_library(name='a', interpreter_constraints=['==2.7.*']) + python_library(name='b', interpreter_constraints=['==3.5.*']) + """ + ), + ["==2.7.*", "==3.5.*"], + ) + assert_ics( + dedent( + """\ + python_library(name='a', interpreter_constraints=['==2.7.*', '==3.5.*']) + python_library(name='b', interpreter_constraints=['>=3.5']) + """ + ), + ["==2.7.*", "==3.5.*", ">=3.5"], + ) + assert_ics( + dedent( + """\ + python_library(name='a') + python_library(name='b', interpreter_constraints=['==2.7.*']) + python_library(name='c', interpreter_constraints=['>=3.6']) + """ + ), + ["==2.7.*", global_constraint, ">=3.6"], + ) diff --git a/src/python/pants/backend/python/subsystems/pytest.py b/src/python/pants/backend/python/subsystems/pytest.py index 512b3c71ef51..793a8de9575e 100644 --- a/src/python/pants/backend/python/subsystems/pytest.py +++ b/src/python/pants/backend/python/subsystems/pytest.py @@ -3,14 +3,27 @@ from __future__ import annotations +import itertools import os.path from typing import Iterable, cast +from pants.backend.experimental.python.lockfile import ( + PythonLockfileRequest, + PythonToolLockfileSentinel, +) from pants.backend.python.subsystems.python_tool_base import PythonToolBase -from pants.backend.python.target_types import ConsoleScript +from pants.backend.python.target_types import ConsoleScript, PythonTestsSources +from pants.backend.python.util_rules.interpreter_constraints import InterpreterConstraints from pants.base.deprecated import resolve_conflicting_options +from pants.base.specs import AddressSpecs, DescendantAddresses from pants.core.util_rules.config_files import ConfigFilesRequest +from pants.engine.rules import Get, MultiGet, collect_rules, rule +from pants.engine.target import TransitiveTargets, TransitiveTargetsRequest, UnexpandedTargets +from pants.engine.unions import UnionRule from pants.option.custom_types import shell_str +from pants.python.python_setup import PythonSetup +from pants.util.docutil import git_url +from pants.util.logging import LogLevel class PyTest(PythonToolBase): @@ -27,6 +40,11 @@ class PyTest(PythonToolBase): default_main = ConsoleScript("pytest") + register_lockfile = True + default_lockfile_resource = ("pants.backend.python.subsystems", "pytest_lockfile.txt") + default_lockfile_path = "src/python/pants/backend/python/subsystems/pytest_lockfile.txt" + default_lockfile_url = git_url(default_lockfile_path) + @classmethod def register_options(cls, register): super().register_options(register) @@ -157,3 +175,45 @@ def config_request(self, dirs: Iterable[str]) -> ConfigFilesRequest: check_existence=check_existence, check_content=check_content, ) + + +class PytestLockfileSentinel(PythonToolLockfileSentinel): + pass + + +@rule( + desc=( + "Determine all Python interpreter versions used by Pytest in your project (for " + "lockfile usage)" + ), + level=LogLevel.DEBUG, +) +async def setup_pytest_lockfile( + _: PytestLockfileSentinel, pytest: PyTest, python_setup: PythonSetup +) -> PythonLockfileRequest: + # Even though we run each python_tests target in isolation, we need a single lockfile that + # works with them all (and their transitive deps). + # + # This first computes the constraints for each individual `python_tests` targets + # (which will AND across each target in the closure). Then, it ORs all unique resulting + # interpreter constraints. When paired with + # `InterpreterConstraints.partition_by_major_minor_versions`, the net effect is that + # every possible Python interpreter used will be covered. + all_build_targets = await Get(UnexpandedTargets, AddressSpecs([DescendantAddresses("")])) + transitive_targets_per_test = await MultiGet( + Get(TransitiveTargets, TransitiveTargetsRequest([tgt.address])) + for tgt in all_build_targets + if tgt.has_field(PythonTestsSources) + ) + unique_constraints = { + InterpreterConstraints.create_from_targets(transitive_targets.closure, python_setup) + for transitive_targets in transitive_targets_per_test + } + constraints = InterpreterConstraints(itertools.chain.from_iterable(unique_constraints)) + return PythonLockfileRequest.from_tool( + pytest, constraints or InterpreterConstraints(python_setup.interpreter_constraints) + ) + + +def rules(): + return (*collect_rules(), UnionRule(PythonToolLockfileSentinel, PytestLockfileSentinel)) diff --git a/src/python/pants/backend/python/subsystems/pytest_test.py b/src/python/pants/backend/python/subsystems/pytest_test.py new file mode 100644 index 000000000000..3e871a24469a --- /dev/null +++ b/src/python/pants/backend/python/subsystems/pytest_test.py @@ -0,0 +1,98 @@ +# Copyright 2021 Pants project contributors (see CONTRIBUTORS.md). +# Licensed under the Apache License, Version 2.0 (see LICENSE). + +from __future__ import annotations + +from textwrap import dedent + +from pants.backend.experimental.python.lockfile import PythonLockfileRequest +from pants.backend.python.subsystems.pytest import PytestLockfileSentinel +from pants.backend.python.subsystems.pytest import rules as subsystem_rules +from pants.backend.python.target_types import PythonLibrary, PythonTests +from pants.backend.python.util_rules.interpreter_constraints import InterpreterConstraints +from pants.core.target_types import GenericTarget +from pants.testutil.rule_runner import QueryRule, RuleRunner + + +def test_setup_lockfile_interpreter_constraints() -> None: + rule_runner = RuleRunner( + rules=[*subsystem_rules(), QueryRule(PythonLockfileRequest, [PytestLockfileSentinel])], + target_types=[PythonLibrary, PythonTests, GenericTarget], + ) + + global_constraint = "==3.9.*" + rule_runner.set_options( + [], env={"PANTS_PYTHON_SETUP_INTERPRETER_CONSTRAINTS": f"['{global_constraint}']"} + ) + + def assert_ics(build_file: str, expected: list[str]) -> None: + rule_runner.write_files({"project/BUILD": build_file}) + lockfile_request = rule_runner.request(PythonLockfileRequest, [PytestLockfileSentinel()]) + assert lockfile_request.interpreter_constraints == InterpreterConstraints(expected) + + assert_ics("python_tests()", [global_constraint]) + assert_ics("python_tests(interpreter_constraints=['==2.7.*'])", ["==2.7.*"]) + assert_ics( + "python_tests(interpreter_constraints=['==2.7.*', '==3.5.*'])", ["==2.7.*", "==3.5.*"] + ) + + # If no Python targets in repo, fall back to global python-setup constraints. + assert_ics("target()", [global_constraint]) + + # Only care about `python_tests` and their transitive deps, not unused `python_library`s. + assert_ics("python_library(interpreter_constraints=['==2.7.*'])", [global_constraint]) + + # If there are multiple distinct ICs in the repo, we OR them because the lockfile needs to be + # compatible with every target. + assert_ics( + dedent( + """\ + python_tests(name='a', interpreter_constraints=['==2.7.*']) + python_tests(name='b', interpreter_constraints=['==3.5.*']) + """ + ), + ["==2.7.*", "==3.5.*"], + ) + assert_ics( + dedent( + """\ + python_tests(name='a', interpreter_constraints=['==2.7.*', '==3.5.*']) + python_tests(name='b', interpreter_constraints=['>=3.5']) + """ + ), + ["==2.7.*", "==3.5.*", ">=3.5"], + ) + assert_ics( + dedent( + """\ + python_tests(name='a') + python_tests(name='b', interpreter_constraints=['==2.7.*']) + python_tests(name='c', interpreter_constraints=['>=3.6']) + """ + ), + ["==2.7.*", global_constraint, ">=3.6"], + ) + + # Also consider transitive deps. They should be ANDed within each python_tests's transitive + # closure like normal, but then ORed across each python_tests closure. + assert_ics( + dedent( + """\ + python_library(name='lib', interpreter_constraints=['==2.7.*', '==3.6.*']) + python_tests(name='tests', dependencies=[":lib"], interpreter_constraints=['==2.7.*']) + """ + ), + ["==2.7.*", "==2.7.*,==3.6.*"], + ) + assert_ics( + dedent( + """\ + python_library(name='lib1', interpreter_constraints=['==2.7.*', '==3.6.*']) + python_tests(name='tests1', dependencies=[":lib1"], interpreter_constraints=['==2.7.*']) + + python_library(name='lib2', interpreter_constraints=['>=3.7']) + python_tests(name='tests2', dependencies=[":lib2"], interpreter_constraints=['==3.8.*']) + """ + ), + ["==2.7.*", "==2.7.*,==3.6.*", ">=3.7,==3.8.*"], + ) diff --git a/src/python/pants/backend/python/util_rules/interpreter_constraints_test.py b/src/python/pants/backend/python/util_rules/interpreter_constraints_test.py index 87542b3ce8b0..94b37c5df2af 100644 --- a/src/python/pants/backend/python/util_rules/interpreter_constraints_test.py +++ b/src/python/pants/backend/python/util_rules/interpreter_constraints_test.py @@ -316,6 +316,13 @@ def test_not_in_contiguous_range(nums, expected) -> None: ([">=3.7.5,<3.8", ">=3.9.5,<3.10"], [[">=3.7.5,<3.8"], [">=3.9.5,<3.10"]]), (["==2.7.*", ">=3.6,<3.8"], [["==2.7.*"], ["==3.6.*"], ["==3.7.*"]]), ([">=2.7.4,<2.7.7", "==2.7.5"], [[">=2.7.4,<=2.7.6"]]), + # Input constraints have multiple elements, and those elements are for multiple + # interpreters. + ([">=3.7,<3.9", "==3.8.6"], [["==3.7.*"], ["==3.8.*"]]), + ([">=3.7,<3.9", ">=3.8,<=3.9.1"], [["==3.7.*"], ["==3.8.*"], [">=3.9.0,<=3.9.1"]]), + # Unsatisfiable input constraints. + (["==2.7.*,==3.6.*"], []), + (["==2.7.*,==3.6.*", "==3.7.*"], [["==3.7.*"]]), ), ) def test_partition_by_major_minor_version(