diff --git a/CHANGELOG.md b/CHANGELOG.md index 78ab9e98bf..272b74db06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -67,6 +67,8 @@ END_UNRELEASED_TEMPLATE {#v0-0-0-added} ### Added +* (windows) Full venv support for Windows is available. Set + {obj}`--venvs_site_packages=yes` to enable. * (runfiles) Added a pathlib-compatible API: {obj}`Runfiles.root()` Fixes [#3296](https://github.com/bazel-contrib/rules_python/issues/3296). * (toolchains) `3.13.12`, `3.14.3` Python toolchain from [20260325] release. diff --git a/docs/conf.py b/docs/conf.py index 541d99ef7a..ef7b66acfa 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -95,13 +95,6 @@ "pypi-dependencies.html": "pypi/index.html", } -# Adapted from the template code: -# https://github.com/readthedocs/readthedocs.org/blob/main/readthedocs/doc_builder/templates/doc_builder/conf.py.tmpl -if os.environ.get("READTHEDOCS") == "True": - # Must come first because it can interfere with other extensions, according - # to the original conf.py template comments - extensions.insert(0, "readthedocs_ext.readthedocs") - exclude_patterns = ["_includes/*"] templates_path = ["_templates"] primary_domain = None # The default is 'py', which we don't make much use of diff --git a/docs/pyproject.toml b/docs/pyproject.toml index f4bbbaf35a..f0ca3928ff 100644 --- a/docs/pyproject.toml +++ b/docs/pyproject.toml @@ -16,4 +16,5 @@ dependencies = [ "pefile", "pyelftools", "macholib", + "markupsafe", ] diff --git a/docs/requirements.txt b/docs/requirements.txt index aee3e0a0a3..6397f0a7f1 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -2,10 +2,14 @@ # bazel run //docs:requirements.update --index-url https://pypi.org/simple -absl-py==2.3.1 \ +absl-py==2.3.1 ; python_full_version < '3.10' \ --hash=sha256:a97820526f7fbfd2ec1bce83f3f25e3a14840dac0d8e02a0b71cd75db3f77fc9 \ --hash=sha256:eeecf07f0c2a93ace0772c92e596ace6d3d3996c042b2128459aaae2a76de11d # via rules-python-docs (docs/pyproject.toml) +absl-py==2.4.0 ; python_full_version >= '3.10' \ + --hash=sha256:88476fd881ca8aab94ffa78b7b6c632a782ab3ba1cd19c9bd423abc4fb4cd28d \ + --hash=sha256:8c6af82722b35cf71e0f4d1d47dcaebfff286e27110a99fc359349b247dfb5d4 + # via rules-python-docs (docs/pyproject.toml) alabaster==0.7.16 ; python_full_version < '3.10' \ --hash=sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65 \ --hash=sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92 @@ -14,125 +18,186 @@ alabaster==1.0.0 ; python_full_version >= '3.10' \ --hash=sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e \ --hash=sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b # via sphinx -altgraph==0.17.4 \ - --hash=sha256:1b5afbb98f6c4dcadb2e2ae6ab9fa994bbb8c1d75f4fa96d340f9437ae454406 \ - --hash=sha256:642743b4750de17e655e6711601b077bc6598dbfa3ba5fa2b2a35ce12b508dff +altgraph==0.17.5 \ + --hash=sha256:c87b395dd12fabde9c99573a9749d67da8d29ef9de0125c7f536699b4a9bc9e7 \ + --hash=sha256:f3a22400bce1b0c701683820ac4f3b159cd301acab067c51c653e06961600597 # via macholib astroid==3.3.11 \ --hash=sha256:1e5a5011af2920c7c67a53f65d536d65bfa7116feeaf2354d8b94f29573bb0ce \ --hash=sha256:54c760ae8322ece1abd213057c4b5bba7c49818853fc901ef09719a60dbf9dec # via sphinx-autodoc2 -babel==2.17.0 \ - --hash=sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d \ - --hash=sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2 +babel==2.18.0 \ + --hash=sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d \ + --hash=sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35 # via sphinx -certifi==2025.10.5 \ - --hash=sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de \ - --hash=sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43 +certifi==2026.2.25 \ + --hash=sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa \ + --hash=sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7 # via requests -charset-normalizer==3.4.3 \ - --hash=sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91 \ - --hash=sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0 \ - --hash=sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154 \ - --hash=sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601 \ - --hash=sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884 \ - --hash=sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07 \ - --hash=sha256:0f2be7e0cf7754b9a30eb01f4295cc3d4358a479843b31f328afd210e2c7598c \ - --hash=sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64 \ - --hash=sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe \ - --hash=sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f \ - --hash=sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432 \ - --hash=sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc \ - --hash=sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa \ - --hash=sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9 \ - --hash=sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae \ - --hash=sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19 \ - --hash=sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d \ - --hash=sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e \ - --hash=sha256:252098c8c7a873e17dd696ed98bbe91dbacd571da4b87df3736768efa7a792e4 \ - --hash=sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7 \ - --hash=sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312 \ - --hash=sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92 \ - --hash=sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31 \ - --hash=sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c \ - --hash=sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f \ - --hash=sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99 \ - --hash=sha256:3653fad4fe3ed447a596ae8638b437f827234f01a8cd801842e43f3d0a6b281b \ - --hash=sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15 \ - --hash=sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392 \ - --hash=sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f \ - --hash=sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8 \ - --hash=sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491 \ - --hash=sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0 \ - --hash=sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc \ - --hash=sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0 \ - --hash=sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f \ - --hash=sha256:5b413b0b1bfd94dbf4023ad6945889f374cd24e3f62de58d6bb102c4d9ae534a \ - --hash=sha256:5d8d01eac18c423815ed4f4a2ec3b439d654e55ee4ad610e153cf02faf67ea40 \ - --hash=sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927 \ - --hash=sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849 \ - --hash=sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce \ - --hash=sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14 \ - --hash=sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05 \ - --hash=sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c \ - --hash=sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c \ - --hash=sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a \ - --hash=sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc \ - --hash=sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34 \ - --hash=sha256:8999f965f922ae054125286faf9f11bc6932184b93011d138925a1773830bbe9 \ - --hash=sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096 \ - --hash=sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14 \ - --hash=sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30 \ - --hash=sha256:a2d08ac246bb48479170408d6c19f6385fa743e7157d716e144cad849b2dd94b \ - --hash=sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b \ - --hash=sha256:b5e3b2d152e74e100a9e9573837aba24aab611d39428ded46f4e4022ea7d1942 \ - --hash=sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db \ - --hash=sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5 \ - --hash=sha256:c60e092517a73c632ec38e290eba714e9627abe9d301c8c8a12ec32c314a2a4b \ - --hash=sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce \ - --hash=sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669 \ - --hash=sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0 \ - --hash=sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018 \ - --hash=sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93 \ - --hash=sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe \ - --hash=sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049 \ - --hash=sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a \ - --hash=sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef \ - --hash=sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2 \ - --hash=sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca \ - --hash=sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16 \ - --hash=sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f \ - --hash=sha256:d95bfb53c211b57198bb91c46dd5a2d8018b3af446583aab40074bf7988401cb \ - --hash=sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1 \ - --hash=sha256:ec557499516fc90fd374bf2e32349a2887a876fbf162c160e3c01b6849eaf557 \ - --hash=sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37 \ - --hash=sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7 \ - --hash=sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72 \ - --hash=sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c \ - --hash=sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9 +charset-normalizer==3.4.7 \ + --hash=sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc \ + --hash=sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c \ + --hash=sha256:07d9e39b01743c3717745f4c530a6349eadbfa043c7577eef86c502c15df2c67 \ + --hash=sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4 \ + --hash=sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0 \ + --hash=sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c \ + --hash=sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5 \ + --hash=sha256:12a6fff75f6bc66711b73a2f0addfc4c8c15a20e805146a02d147a318962c444 \ + --hash=sha256:12d8baf840cc7889b37c7c770f478adea7adce3dcb3944d02ec87508e2dcf153 \ + --hash=sha256:14265bfe1f09498b9d8ec91e9ec9fa52775edf90fcbde092b25f4a33d444fea9 \ + --hash=sha256:16d971e29578a5e97d7117866d15889a4a07befe0e87e703ed63cd90cb348c01 \ + --hash=sha256:177a0ba5f0211d488e295aaf82707237e331c24788d8d76c96c5a41594723217 \ + --hash=sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b \ + --hash=sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c \ + --hash=sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a \ + --hash=sha256:1dc8b0ea451d6e69735094606991f32867807881400f808a106ee1d963c46a83 \ + --hash=sha256:1efde3cae86c8c273f1eb3b287be7d8499420cf2fe7585c41d370d3e790054a5 \ + --hash=sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7 \ + --hash=sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb \ + --hash=sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c \ + --hash=sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1 \ + --hash=sha256:2cd4a60d0e2fb04537162c62bbbb4182f53541fe0ede35cdf270a1c1e723cc42 \ + --hash=sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab \ + --hash=sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df \ + --hash=sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e \ + --hash=sha256:320ade88cfb846b8cd6b4ddf5ee9e80ee0c1f52401f2456b84ae1ae6a1a5f207 \ + --hash=sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18 \ + --hash=sha256:36836d6ff945a00b88ba1e4572d721e60b5b8c98c155d465f56ad19d68f23734 \ + --hash=sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38 \ + --hash=sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110 \ + --hash=sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18 \ + --hash=sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44 \ + --hash=sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d \ + --hash=sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48 \ + --hash=sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e \ + --hash=sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5 \ + --hash=sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d \ + --hash=sha256:4e5163c14bffd570ef2affbfdd77bba66383890797df43dc8b4cc7d6f500bf53 \ + --hash=sha256:511ef87c8aec0783e08ac18565a16d435372bc1ac25a91e6ac7f5ef2b0bff790 \ + --hash=sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c \ + --hash=sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b \ + --hash=sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116 \ + --hash=sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d \ + --hash=sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10 \ + --hash=sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6 \ + --hash=sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2 \ + --hash=sha256:6370e8686f662e6a3941ee48ed4742317cafbe5707e36406e9df792cdb535776 \ + --hash=sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a \ + --hash=sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265 \ + --hash=sha256:66671f93accb62ed07da56613636f3641f1a12c13046ce91ffc923721f23c008 \ + --hash=sha256:6696b7688f54f5af4462118f0bfa7c1621eeb87154f77fa04b9295ce7a8f2943 \ + --hash=sha256:6785f414ae0f3c733c437e0f3929197934f526d19dfaa75e18fdb4f94c6fb374 \ + --hash=sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246 \ + --hash=sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e \ + --hash=sha256:6e0d51f618228538a3e8f46bd246f87a6cd030565e015803691603f55e12afb5 \ + --hash=sha256:6ed74185b2db44f41ef35fd1617c5888e59792da9bbc9190d6c7300617182616 \ + --hash=sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15 \ + --hash=sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41 \ + --hash=sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960 \ + --hash=sha256:750e02e074872a3fad7f233b47734166440af3cdea0add3e95163110816d6752 \ + --hash=sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e \ + --hash=sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72 \ + --hash=sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7 \ + --hash=sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8 \ + --hash=sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b \ + --hash=sha256:813c0e0132266c08eb87469a642cb30aaff57c5f426255419572aaeceeaa7bf4 \ + --hash=sha256:82b271f5137d07749f7bf32f70b17ab6eaabedd297e75dce75081a24f76eb545 \ + --hash=sha256:84c018e49c3bf790f9c2771c45e9313a08c2c2a6342b162cd650258b57817706 \ + --hash=sha256:8751d2787c9131302398b11e6c8068053dcb55d5a8964e114b6e196cf16cb366 \ + --hash=sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb \ + --hash=sha256:87fad7d9ba98c86bcb41b2dc8dbb326619be2562af1f8ff50776a39e55721c5a \ + --hash=sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e \ + --hash=sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00 \ + --hash=sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f \ + --hash=sha256:94e1885b270625a9a828c9793b4d52a64445299baa1fea5a173bf1d3dd9a1a5a \ + --hash=sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1 \ + --hash=sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66 \ + --hash=sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356 \ + --hash=sha256:a6c5863edfbe888d9eff9c8b8087354e27618d9da76425c119293f11712a6319 \ + --hash=sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4 \ + --hash=sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad \ + --hash=sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d \ + --hash=sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5 \ + --hash=sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7 \ + --hash=sha256:aef65cd602a6d0e0ff6f9930fcb1c8fec60dd2cfcb6facaf4bdb0e5873042db0 \ + --hash=sha256:af21eb4409a119e365397b2adbaca4c9ccab56543a65d5dbd9f920d6ac29f686 \ + --hash=sha256:b14b2d9dac08e28bb8046a1a0434b1750eb221c8f5b87a68f4fa11a6f97b5e34 \ + --hash=sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49 \ + --hash=sha256:bb8cc7534f51d9a017b93e3e85b260924f909601c3df002bcdb58ddb4dc41a5c \ + --hash=sha256:bc17a677b21b3502a21f66a8cc64f5bfad4df8a0b8434d661666f8ce90ac3af1 \ + --hash=sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e \ + --hash=sha256:bd9b23791fe793e4968dba0c447e12f78e425c59fc0e3b97f6450f4781f3ee60 \ + --hash=sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0 \ + --hash=sha256:c0f081d69a6e58272819b70288d3221a6ee64b98df852631c80f293514d3b274 \ + --hash=sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d \ + --hash=sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0 \ + --hash=sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae \ + --hash=sha256:c593052c465475e64bbfe5dbd81680f64a67fdc752c56d7a0ae205dc8aeefe0f \ + --hash=sha256:cdd68a1fb318e290a2077696b7eb7a21a49163c455979c639bf5a5dcdc46617d \ + --hash=sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe \ + --hash=sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3 \ + --hash=sha256:cf29836da5119f3c8a8a70667b0ef5fdca3bb12f80fd06487cfa575b3909b393 \ + --hash=sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1 \ + --hash=sha256:d560742f3c0d62afaccf9f41fe485ed69bd7661a241f86a3ef0f0fb8b1a397af \ + --hash=sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44 \ + --hash=sha256:d61f00a0869d77422d9b2aba989e2d24afa6ffd552af442e0e58de4f35ea6d00 \ + --hash=sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c \ + --hash=sha256:dca4bbc466a95ba9c0234ef56d7dd9509f63da22274589ebd4ed7f1f4d4c54e3 \ + --hash=sha256:dd915403e231e6b1809fe9b6d9fc55cf8fb5e02765ac625d9cd623342a7905d7 \ + --hash=sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd \ + --hash=sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e \ + --hash=sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b \ + --hash=sha256:e17b8d5d6a8c47c85e68ca8379def1303fd360c3e22093a807cd34a71cd082b8 \ + --hash=sha256:e5f4d355f0a2b1a31bc3edec6795b46324349c9cb25eed068049e4f472fb4259 \ + --hash=sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859 \ + --hash=sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46 \ + --hash=sha256:e80c8378d8f3d83cd3164da1ad2df9e37a666cdde7b1cb2298ed0b558064be30 \ + --hash=sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b \ + --hash=sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46 \ + --hash=sha256:ed065083d0898c9d5b4bbec7b026fd755ff7454e6e8b73a67f8c744b13986e24 \ + --hash=sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a \ + --hash=sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24 \ + --hash=sha256:f22dec1690b584cea26fade98b2435c132c1b5f68e39f5a0b7627cd7ae31f1dc \ + --hash=sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215 \ + --hash=sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063 \ + --hash=sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832 \ + --hash=sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6 \ + --hash=sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79 \ + --hash=sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464 # via requests colorama==0.4.6 ; sys_platform == 'win32' \ --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 # via sphinx -docutils==0.21.2 \ +docutils==0.21.2 ; python_full_version < '3.11' \ --hash=sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f \ --hash=sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2 # via # myst-parser # sphinx # sphinx-rtd-theme -idna==3.10 \ - --hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \ - --hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3 +docutils==0.22.4 ; python_full_version >= '3.11' \ + --hash=sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968 \ + --hash=sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de + # via + # myst-parser + # sphinx + # sphinx-rtd-theme +idna==3.11 \ + --hash=sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea \ + --hash=sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902 # via requests -imagesize==1.4.1 \ - --hash=sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b \ - --hash=sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a +imagesize==1.5.0 ; python_full_version < '3.10' \ + --hash=sha256:32677681b3f434c2cb496f00e89c5a291247b35b1f527589909e008057da5899 \ + --hash=sha256:8bfc5363a7f2133a89f0098451e0bcb1cd71aba4dc02bbcecb39d99d40e1b94f + # via sphinx +imagesize==2.0.0 ; python_full_version >= '3.10' \ + --hash=sha256:5667c5bbb57ab3f1fa4bc366f4fbc971db3d5ed011fd2715fd8001f782718d96 \ + --hash=sha256:8e8358c4a05c304f1fccf7ff96f036e7243a189e9e42e90851993c558cfe9ee3 # via sphinx -importlib-metadata==8.7.0 ; python_full_version < '3.10' \ - --hash=sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000 \ - --hash=sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd +importlib-metadata==8.7.1 ; python_full_version < '3.10' \ + --hash=sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb \ + --hash=sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151 # via sphinx jinja2==3.1.6 \ --hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \ @@ -141,16 +206,22 @@ jinja2==3.1.6 \ # myst-parser # readthedocs-sphinx-ext # sphinx -macholib==1.16.3 \ - --hash=sha256:07ae9e15e8e4cd9a788013d81f5908b3609aa76f9b1421bae9c4d7606ec86a30 \ - --hash=sha256:0e315d7583d38b8c77e815b1ecbdbf504a8258d8b3e17b61165c6feb60d18f2c +macholib==1.16.4 \ + --hash=sha256:da1a3fa8266e30f0ce7e97c6a54eefaae8edd1e5f86f3eb8b95457cae90265ea \ + --hash=sha256:f408c93ab2e995cd2c46e34fe328b130404be143469e41bc366c807448979362 # via rules-python-docs (docs/pyproject.toml) -markdown-it-py==3.0.0 \ +markdown-it-py==3.0.0 ; python_full_version < '3.11' \ --hash=sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1 \ --hash=sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb # via # mdit-py-plugins # myst-parser +markdown-it-py==4.0.0 ; python_full_version >= '3.11' \ + --hash=sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147 \ + --hash=sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3 + # via + # mdit-py-plugins + # myst-parser markupsafe==3.0.3 \ --hash=sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f \ --hash=sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a \ @@ -241,7 +312,9 @@ markupsafe==3.0.3 \ --hash=sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc \ --hash=sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a \ --hash=sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50 - # via jinja2 + # via + # rules-python-docs (docs/pyproject.toml) + # jinja2 mdit-py-plugins==0.4.2 ; python_full_version < '3.10' \ --hash=sha256:0c673c3f889399a33b95e88d2f0d111b4447bdfea7f237dab2d488f459835636 \ --hash=sha256:5f2cd1fdb606ddf152d37ec30e46101a60512bc0e5fa1a7002c36647b09e26b5 @@ -258,13 +331,17 @@ myst-parser==3.0.1 ; python_full_version < '3.10' \ --hash=sha256:6457aaa33a5d474aca678b8ead9b3dc298e89c68e67012e73146ea6fd54babf1 \ --hash=sha256:88f0cb406cb363b077d176b51c476f62d60604d68a8dcdf4832e080441301a87 # via rules-python-docs (docs/pyproject.toml) -myst-parser==4.0.1 ; python_full_version >= '3.10' \ +myst-parser==4.0.1 ; python_full_version == '3.10.*' \ --hash=sha256:5cfea715e4f3574138aecbf7d54132296bfd72bb614d31168f48c477a830a7c4 \ --hash=sha256:9134e88959ec3b5780aedf8a99680ea242869d012e8821db3126d427edc9c95d # via rules-python-docs (docs/pyproject.toml) -packaging==25.0 \ - --hash=sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 \ - --hash=sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f +myst-parser==5.0.0 ; python_full_version >= '3.11' \ + --hash=sha256:ab31e516024918296e169139072b81592336f2fef55b8986aa31c9f04b5f7211 \ + --hash=sha256:f6f231452c56e8baa662cc352c548158f6a16fcbd6e3800fc594978002b94f3a + # via rules-python-docs (docs/pyproject.toml) +packaging==26.1 \ + --hash=sha256:5d9c0669c6285e491e0ced2eee587eaf67b670d94a19e94e3984a481aba6802f \ + --hash=sha256:f042152b681c4bfac5cae2742a55e103d27ab2ec0f3d88037136b6bfe7c9c5de # via # readthedocs-sphinx-ext # sphinx @@ -276,9 +353,9 @@ pyelftools==0.32 \ --hash=sha256:013df952a006db5e138b1edf6d8a68ecc50630adbd0d83a2d41e7f846163d738 \ --hash=sha256:6de90ee7b8263e740c8715a925382d4099b354f29ac48ea40d840cf7aa14ace5 # via rules-python-docs (docs/pyproject.toml) -pygments==2.19.2 \ - --hash=sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887 \ - --hash=sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b +pygments==2.20.0 \ + --hash=sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f \ + --hash=sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176 # via sphinx pyyaml==6.0.3 \ --hash=sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c \ @@ -359,15 +436,21 @@ readthedocs-sphinx-ext==2.2.5 \ --hash=sha256:ee5fd5b99db9f0c180b2396cbce528aa36671951b9526bb0272dbfce5517bd27 \ --hash=sha256:f8c56184ea011c972dd45a90122568587cc85b0127bc9cf064d17c68bc809daa # via rules-python-docs (docs/pyproject.toml) -requests==2.33.0 \ - --hash=sha256:3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b \ - --hash=sha256:c7ebc5e8b0f21837386ad0e1c8fe8b829fa5f544d8df3b2253bff14ef29d7652 +requests==2.32.5 ; python_full_version < '3.10' \ + --hash=sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6 \ + --hash=sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf # via # readthedocs-sphinx-ext # sphinx -roman-numerals-py==3.1.0 ; python_full_version >= '3.11' \ - --hash=sha256:9da2ad2fb670bcf24e81070ceb3be72f6c11c440d73bd579fbeca1e9f330954c \ - --hash=sha256:be4bf804f083a4ce001b5eb7e3c0862479d10f94c936f6c4e5f250aa5ff5bd2d +requests==2.33.1 ; python_full_version >= '3.10' \ + --hash=sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517 \ + --hash=sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a + # via + # readthedocs-sphinx-ext + # sphinx +roman-numerals==4.1.0 ; python_full_version >= '3.11' \ + --hash=sha256:1af8b147eb1405d5839e78aeb93131690495fe9da5c91856cb33ad55a7f1e5b2 \ + --hash=sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7 # via sphinx snowballstemmer==3.0.1 \ --hash=sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064 \ @@ -391,9 +474,18 @@ sphinx==8.1.3 ; python_full_version == '3.10.*' \ # sphinx-reredirects # sphinx-rtd-theme # sphinxcontrib-jquery -sphinx==8.2.3 ; python_full_version >= '3.11' \ - --hash=sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348 \ - --hash=sha256:4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3 +sphinx==9.0.4 ; python_full_version == '3.11.*' \ + --hash=sha256:594ef59d042972abbc581d8baa577404abe4e6c3b04ef61bd7fc2acbd51f3fa3 \ + --hash=sha256:5bebc595a5e943ea248b99c13814c1c5e10b3ece718976824ffa7959ff95fffb + # via + # rules-python-docs (docs/pyproject.toml) + # myst-parser + # sphinx-reredirects + # sphinx-rtd-theme + # sphinxcontrib-jquery +sphinx==9.1.0 ; python_full_version >= '3.12' \ + --hash=sha256:7741722357dd75f8190766926071fed3bdc211c74dd2d7d4df5404da95930ddb \ + --hash=sha256:c84fdd4e782504495fe4f2c0b3413d6c2bf388589bb352d439b2a3bb99991978 # via # rules-python-docs (docs/pyproject.toml) # myst-parser @@ -408,13 +500,13 @@ sphinx-reredirects==0.1.6 ; python_full_version < '3.11' \ --hash=sha256:c491cba545f67be9697508727818d8626626366245ae64456fe29f37e9bbea64 \ --hash=sha256:efd50c766fbc5bf40cd5148e10c00f2c00d143027de5c5e48beece93cc40eeea # via rules-python-docs (docs/pyproject.toml) -sphinx-reredirects==1.0.0 ; python_full_version >= '3.11' \ - --hash=sha256:1d0102710a8f633c6c885f940f440f7195ada675c1739976f0135790747dea06 \ - --hash=sha256:7c9bada9f1330489fcf4c7297a2d6da2a49ca4877d3f42d1388ae1de1019bf5c +sphinx-reredirects==1.1.0 ; python_full_version >= '3.11' \ + --hash=sha256:4b5692273c72cd2d4d917f4c6f87d5919e4d6114a752d4be033f7f5f6310efd9 \ + --hash=sha256:fb9b195335ab14b43f8273287d0c7eeb637ba6c56c66581c11b47202f6718b29 # via rules-python-docs (docs/pyproject.toml) -sphinx-rtd-theme==3.0.2 \ - --hash=sha256:422ccc750c3a3a311de4ae327e82affdaf59eb695ba4936538552f3b00f4ee13 \ - --hash=sha256:b7457bc25dda723b20b086a670b9953c859eab60a2a03ee8eb2bb23e176e5f85 +sphinx-rtd-theme==3.1.0 \ + --hash=sha256:1785824ae8e6632060490f67cf3a72d404a85d2d9fc26bce3619944de5682b89 \ + --hash=sha256:b44276f2c276e909239a4f6c955aa667aaafeb78597923b1c60babc76db78e4c # via rules-python-docs (docs/pyproject.toml) sphinxcontrib-applehelp==2.0.0 \ --hash=sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1 \ @@ -444,49 +536,54 @@ sphinxcontrib-serializinghtml==2.0.0 \ --hash=sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331 \ --hash=sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d # via sphinx -tomli==2.3.0 ; python_full_version < '3.11' \ - --hash=sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456 \ - --hash=sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845 \ - --hash=sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999 \ - --hash=sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0 \ - --hash=sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878 \ - --hash=sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf \ - --hash=sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3 \ - --hash=sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be \ - --hash=sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52 \ - --hash=sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b \ - --hash=sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67 \ - --hash=sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549 \ - --hash=sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba \ - --hash=sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22 \ - --hash=sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c \ - --hash=sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f \ - --hash=sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6 \ - --hash=sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba \ - --hash=sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45 \ - --hash=sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f \ - --hash=sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77 \ - --hash=sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606 \ - --hash=sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441 \ - --hash=sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0 \ - --hash=sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f \ - --hash=sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530 \ - --hash=sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05 \ - --hash=sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8 \ - --hash=sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005 \ - --hash=sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879 \ - --hash=sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae \ - --hash=sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc \ - --hash=sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b \ - --hash=sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b \ - --hash=sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e \ - --hash=sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf \ - --hash=sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac \ - --hash=sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8 \ - --hash=sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b \ - --hash=sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf \ - --hash=sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463 \ - --hash=sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876 +tomli==2.4.1 ; python_full_version < '3.11' \ + --hash=sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853 \ + --hash=sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe \ + --hash=sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5 \ + --hash=sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d \ + --hash=sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd \ + --hash=sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26 \ + --hash=sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54 \ + --hash=sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6 \ + --hash=sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c \ + --hash=sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a \ + --hash=sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd \ + --hash=sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f \ + --hash=sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5 \ + --hash=sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9 \ + --hash=sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662 \ + --hash=sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9 \ + --hash=sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1 \ + --hash=sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585 \ + --hash=sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e \ + --hash=sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c \ + --hash=sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41 \ + --hash=sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f \ + --hash=sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085 \ + --hash=sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15 \ + --hash=sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7 \ + --hash=sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c \ + --hash=sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36 \ + --hash=sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076 \ + --hash=sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac \ + --hash=sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8 \ + --hash=sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232 \ + --hash=sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece \ + --hash=sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a \ + --hash=sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897 \ + --hash=sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d \ + --hash=sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4 \ + --hash=sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917 \ + --hash=sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396 \ + --hash=sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a \ + --hash=sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc \ + --hash=sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba \ + --hash=sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f \ + --hash=sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257 \ + --hash=sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30 \ + --hash=sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf \ + --hash=sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9 \ + --hash=sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049 # via # sphinx # sphinx-autodoc2 @@ -501,7 +598,7 @@ urllib3==2.6.3 \ --hash=sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed \ --hash=sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4 # via requests -zipp==3.23.0 ; python_full_version < '3.10' \ - --hash=sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e \ - --hash=sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166 +zipp==3.23.1 ; python_full_version < '3.10' \ + --hash=sha256:0b3596c50a5c700c9cb40ba8d86d9f2cc4807e9bedb06bcdf7fac85633e444dc \ + --hash=sha256:32120e378d32cd9714ad503c1d024619063ec28aad2248dc6672ad13edfa5110 # via importlib-metadata diff --git a/python/private/attributes.bzl b/python/private/attributes.bzl index 0892fe3111..beaa7a6a73 100644 --- a/python/private/attributes.bzl +++ b/python/private/attributes.bzl @@ -542,6 +542,14 @@ AGNOSTIC_TEST_ATTRS = _init_agnostic_test_attrs() # but still accept Python source-agnostic settings. AGNOSTIC_BINARY_ATTRS = dicts.add(AGNOSTIC_EXECUTABLE_ATTRS) +WINDOWS_CONSTRAINTS_ATTRS = { + "_windows_constraints": lambda: attrb.LabelList( + default = [ + "@platforms//os:windows", + ], + ), +} + # Attribute names common to all Python rules COMMON_ATTR_NAMES = [ "compatible_with", diff --git a/python/private/common.bzl b/python/private/common.bzl index 6d9e0e3c84..7e8c6decf3 100644 --- a/python/private/common.bzl +++ b/python/private/common.bzl @@ -49,6 +49,31 @@ BUILTIN_BUILD_PYTHON_ZIP = [] if config.bazel_10_or_later else [ "//command_line_option:build_python_zip", ] +# Not an actual provider. Provider used for memory efficiency. +# buildifier: disable=name-conventions +ExplicitSymlink = provider( + doc = """ +A runfile that should be created as a symlink pointing to a specific location. + +This is only needed on Windows, where Bazel doesn't preserve declare_symlink +with relative paths. This is basically manually captures what using +declare_symlink(), symlink() and runfiles like so would capture: + +``` +link = declare_symlink(...) +link_to_path = relative_path(from=link, to=target) +symlink(output=link, target_path=link_to_path) +runfiles.add([link, target]) +``` +""", + fields = { + "files": "depset[File] of files that should be included if this symlink is used", + "link_to_path": "Path the symlink should point to", + "runfiles_path": "runfiles-root-relative path for the symlink", + "venv_path": "venv-root-relative path for the symlink", + }, +) + def maybe_builtin_build_python_zip(value, settings = None): settings = settings or {} if not config.bazel_10_or_later: @@ -460,6 +485,17 @@ def target_platform_has_any_constraint(ctx, constraints): return True return False +def is_windows_platform(ctx): + """Check if target platform is windows. + + Args: + ctx: rule context. + + Returns: + True if target platform is windows. + """ + return target_platform_has_any_constraint(ctx, ctx.attr._windows_constraints) + def relative_path(from_, to): """Compute a relative path from one path to another. diff --git a/python/private/py_executable.bzl b/python/private/py_executable.bzl index 1cb7c9593c..375030ce91 100644 --- a/python/private/py_executable.bzl +++ b/python/private/py_executable.bzl @@ -30,12 +30,14 @@ load( "PrecompileAttr", "PycCollectionAttr", "REQUIRED_EXEC_GROUP_BUILDERS", + "WINDOWS_CONSTRAINTS_ATTRS", "apply_config_settings_attr", ) load(":builders.bzl", "builders") load(":cc_helper.bzl", "cc_helper") load( ":common.bzl", + "ExplicitSymlink", "collect_cc_info", "collect_deps", "collect_imports", @@ -49,10 +51,10 @@ load( "csv", "filter_to_py_srcs", "is_bool", + "is_windows_platform", "maybe_create_repo_mapping", "relative_path", "runfiles_root_path", - "target_platform_has_any_constraint", ) load(":common_labels.bzl", "labels") load(":flags.bzl", "BootstrapImplFlag", "VenvsUseDeclareSymlinkFlag", "read_possibly_native_flag") @@ -73,30 +75,6 @@ _EXTERNAL_PATH_PREFIX = "external" _ZIP_RUNFILES_DIRECTORY_NAME = "runfiles" _INIT_PY = "__init__.py" -# buildifier: disable=name-conventions -ExplicitSymlink = provider( - doc = """ -A runfile that should be created as a symlink pointing to a specific location. - -This is only needed on Windows, where Bazel doesn't preserve declare_symlink -with relative paths. This is basically manually captures what using -declare_symlink(), symlink() and runfiles like so would capture: - -``` -link = declare_symlink(...) -link_to_path = relative_path(from=link, to=target) -symlink(output=link, target_path=link_to_path) -runfiles.add([link, target]) -``` -""", - fields = { - "files": "depset[File] of files that should be included if this symlink is used", - "link_to_path": "Path the symlink should point to", - "runfiles_path": "runfiles-root-relative path for the symlink", - "venv_path": "venv-root-relative path for the symlink", - }, -) - # Non-Google-specific attributes for executables # These attributes are for rules that accept Python sources. EXECUTABLE_ATTRS = dicts.add( @@ -104,6 +82,7 @@ EXECUTABLE_ATTRS = dicts.add( AGNOSTIC_EXECUTABLE_ATTRS, PY_SRCS_ATTRS, IMPORTS_ATTRS, + WINDOWS_CONSTRAINTS_ATTRS, # starlark flags attributes { "_build_python_zip_flag": attr.label(default = "//python/config_settings:build_python_zip"), @@ -257,11 +236,6 @@ accepting arbitrary Python versions. default = labels.VENVS_USE_DECLARE_SYMLINK, providers = [BuildSettingInfo], ), - "_windows_constraints": lambda: attrb.LabelList( - default = [ - "@platforms//os:windows", - ], - ), "_zipper": lambda: attrb.Label( cfg = "exec", executable = True, @@ -323,7 +297,7 @@ def _create_executable( extra_deps): _ = is_test, cc_details, native_deps_details # @unused - is_windows = target_platform_has_any_constraint(ctx, ctx.attr._windows_constraints) + is_windows = is_windows_platform(ctx) if is_windows: if not executable.extension == "exe": @@ -447,14 +421,14 @@ WARNING: Target: {} use_zip_file = build_zip_enabled, python_binary_path = runtime_details.executable_interpreter_path, ) - if not build_zip_enabled: - # On Windows, the main executable has an "exe" extension, so - # here we re-use the un-extensioned name for the bootstrap output. - bootstrap_output = ctx.actions.declare_file(base_executable_name) - # The launcher looks for the non-zip executable next to - # itself, so add it to the default outputs. - extra_default_outputs.append(bootstrap_output) + # On Windows, the main executable has an "exe" extension, so + # here we re-use the un-extensioned name for the bootstrap output. + bootstrap_output = ctx.actions.declare_file(base_executable_name) + + # The launcher looks for the non-zip executable next to + # itself, so add it to the default outputs. + extra_default_outputs.append(bootstrap_output) if should_create_executable_zip: if bootstrap_output != None: @@ -516,6 +490,9 @@ WARNING: Target: {} # runfiles; runfiles for the app itself (e.g its deps, but no Python # runtime files) app_runfiles = app_runfiles.build(ctx), + # depset[ExplicitSymlink]None; symlinks that should be created in + # the venv to augment app_runfiles + venv_app_symlinks = venv.lib_symlinks if venv else None, # File|None; the venv `bin/python3` file, if any. venv_python_exe = venv.interpreter if venv else None, # runfiles|None; runfiles in the venv for the interpreter @@ -561,7 +538,7 @@ def _create_venv(ctx, output_prefix, imports, runtime_details, add_runfiles_root else: interpreter_actual_path = runtime.interpreter_path - is_windows = target_platform_has_any_constraint(ctx, ctx.attr._windows_constraints) + is_windows = is_windows_platform(ctx) if is_windows: venv_details = _create_venv_windows( ctx, @@ -644,6 +621,7 @@ def _create_venv(ctx, output_prefix, imports, runtime_details, add_runfiles_root lib_runfiles = ctx.runfiles( root_symlinks = venv_app_files.runfiles_symlinks, ), + lib_symlinks = venv_app_files.explicit_symlinks, ) def _create_venv_unixy(ctx, *, venv_ctx_rel_root, runtime, interpreter_actual_path): @@ -937,9 +915,12 @@ def _create_stage1_bootstrap( } computed_subs = ctx.actions.template_dict() if venv: + runtime_venv_symlinks = depset( + transitive = [venv.interpreter_symlinks, venv.lib_symlinks], + ) computed_subs.add_joined( "%runtime_venv_symlinks%", - venv.interpreter_symlinks, + runtime_venv_symlinks, join_with = "\n", map_each = _map_runtime_venv_symlink, ) @@ -1250,8 +1231,6 @@ def py_executable_base_impl(ctx, *, semantics, is_test, inherited_environment = ) )) - app_runfiles = exec_result.app_runfiles - providers = [] _add_provider_default_info( @@ -1265,13 +1244,14 @@ def py_executable_base_impl(ctx, *, semantics, is_test, inherited_environment = _add_provider_run_environment_info(providers, ctx, inherited_environment) _add_provider_py_executable_info( providers, - app_runfiles = app_runfiles, + app_runfiles = exec_result.app_runfiles, build_data_file = runfiles_details.build_data_file, interpreter_args = ctx.attr.interpreter_args, interpreter_path = runtime_details.executable_interpreter_path, main_py = main_py, runfiles_without_exe = runfiles_details.runfiles_without_exe, stage2_bootstrap = exec_result.stage2_bootstrap, + venv_app_symlinks = exec_result.venv_app_symlinks, venv_interpreter_runfiles = exec_result.venv_interpreter_runfiles, venv_interpreter_symlinks = exec_result.venv_interpreter_symlinks, venv_python_exe = exec_result.venv_python_exe, @@ -1313,7 +1293,7 @@ def _validate_executable(ctx): ).format(ctx.attr.main, ctx.attr.main_module)) def _declare_executable_file(ctx): - if target_platform_has_any_constraint(ctx, ctx.attr._windows_constraints): + if is_windows_platform(ctx): executable = ctx.actions.declare_file(ctx.label.name + ".exe") else: executable = ctx.actions.declare_file(ctx.label.name) @@ -1882,6 +1862,7 @@ def _add_provider_py_executable_info( main_py, runfiles_without_exe, stage2_bootstrap, + venv_app_symlinks, venv_interpreter_runfiles, venv_interpreter_symlinks, venv_python_exe): @@ -1896,6 +1877,8 @@ def _add_provider_py_executable_info( main_py: File; the main .py entry point. runfiles_without_exe: runfiles; the default runfiles, but without the executable. stage2_bootstrap: File; the stage 2 bootstrap script. + venv_app_symlinks: depset[ExplicitSymlink]; symlinks to create for the + venv that are the application (deps, etc). venv_interpreter_runfiles: runfiles; runfiles specific to the interpreter for the venv. venv_interpreter_symlinks: depset[ExplicitSymlink]; interpreter-specific symlinks to create for the venv. venv_python_exe: File; the python executable in the venv. @@ -1908,6 +1891,7 @@ def _add_provider_py_executable_info( main = main_py, runfiles_without_exe = runfiles_without_exe, stage2_bootstrap = stage2_bootstrap, + venv_app_symlinks = venv_app_symlinks, venv_interpreter_runfiles = venv_interpreter_runfiles, venv_interpreter_symlinks = venv_interpreter_symlinks, venv_python_exe = venv_python_exe, diff --git a/python/private/py_executable_info.bzl b/python/private/py_executable_info.bzl index a076715bd0..9e9baea421 100644 --- a/python/private/py_executable_info.bzl +++ b/python/private/py_executable_info.bzl @@ -77,6 +77,19 @@ implementation isn't being used. :::{versionadded} 1.9.0 ::: +""", + "venv_app_symlinks": """ +:type: depset[ExplicitSymlink] | None + +Symlinks that are specific to the application within the venv (e.g. +dependencies). + +Only used with Windows for files that would have used `declare_symlink()` +to create relative symlinks. These may overlap with paths in runfiles; it's +up to the consumer to determine how to handle such overlaps. + +:::{versionadded} VERSION_NEXT_FEATURE +::: """, "venv_interpreter_runfiles": """ :type: runfiles | None diff --git a/python/private/python_bootstrap_template.txt b/python/private/python_bootstrap_template.txt index accc11e0df..0d28aff311 100644 --- a/python/private/python_bootstrap_template.txt +++ b/python/private/python_bootstrap_template.txt @@ -7,7 +7,7 @@ from __future__ import print_function # Generated file from @rules_python//python/private:python_bootstrap_template.txt -from os.path import dirname, join, basename, normpath +from os.path import abspath, dirname, join, basename, normpath import os import shutil import subprocess @@ -95,6 +95,7 @@ else: IS_WINDOWS = os.name == "nt" BIN_DIR_NAME = "bin" if not IS_WINDOWS else "Scripts" +LIB_DIR_NAME = "lib" if not IS_WINDOWS else "Lib" # Windows APIs can be picky about slashes depending on the context, # so convert to backslashes to avoid any issues. @@ -147,15 +148,15 @@ def get_windows_path_with_unc_prefix(path): if path.startswith(unicode_prefix): return path - # os.path.abspath returns a normalized absolute path - return unicode_prefix + os.path.abspath(path) + # abspath returns a normalized absolute path + return unicode_prefix + abspath(path) def search_path(name): """Finds a file in a given search path.""" search_path = os.getenv('PATH', os.defpath).split(os.pathsep) for directory in search_path: if directory: - path = os.path.join(directory, name) + path = join(directory, name) if os.path.isfile(path) and os.access(path, os.X_OK): return path return None @@ -206,7 +207,7 @@ def find_binary(runfiles_root, bin_name): # Use normpath() to convert slashes to os.sep on Windows. elif os.sep in os.path.normpath(bin_name): # Case 3: Path is relative to the repo root. - return os.path.join(runfiles_root, bin_name) + return join(runfiles_root, bin_name) else: # Case 4: Path has to be looked up in the search path. return search_path(bin_name) @@ -226,7 +227,7 @@ def find_runfiles_root(main_rel_path): runfiles_dir = runfiles_manifest_file[:-9] # Be defensive: the runfiles dir should contain our main entry point. If # it doesn't, then it must not be our runfiles directory. - if runfiles_dir and os.path.exists(os.path.join(runfiles_dir, main_rel_path)): + if runfiles_dir and os.path.exists(join(runfiles_dir, main_rel_path)): return runfiles_dir # Clear RUNFILES_DIR & RUNFILES_MANIFEST_FILE since the runfiles dir was @@ -244,7 +245,7 @@ def find_runfiles_root(main_rel_path): stub_filename = stub_filename.replace("/", os.sep) if not os.path.isabs(stub_filename): - stub_filename = os.path.join(os.getcwd(), stub_filename) + stub_filename = join(os.getcwd(), stub_filename) while True: runfiles_root = stub_filename + ('.exe' if IS_WINDOWS else '') + '.runfiles' @@ -262,7 +263,7 @@ def find_runfiles_root(main_rel_path): if os.path.isabs(target): stub_filename = target else: - stub_filename = os.path.join(os.path.dirname(stub_filename), target) + stub_filename = join(os.path.dirname(stub_filename), target) raise AssertionError('Cannot find .runfiles directory for %s' % sys.argv[0]) @@ -285,7 +286,7 @@ def extract_zip(zip_path, dest_dir): zf.extract(info, dest_dir) # UNC-prefixed paths must be absolute/normalized. See # https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file#maximum-path-length-limitation - file_path = os.path.abspath(os.path.join(dest_dir, info.filename)) + file_path = abspath(join(dest_dir, info.filename)) # The Unix st_mode bits (see "man 7 inode") are stored in the upper 16 # bits of external_attr. attrs = info.external_attr >> 16 @@ -306,7 +307,27 @@ def create_runfiles_root(): extract_zip(os.path.dirname(__file__), temp_dir) # IMPORTANT: Later code does `rm -fr` on dirname(runfiles_root) -- it's # important that deletion code be in sync with this directory structure - return os.path.join(temp_dir, 'runfiles') + return join(temp_dir, 'runfiles') + +def _symlink_tree(link_from, link_to): + # Ensure the source is an absolute path to simplify symlinking + link_to_root = abspath(link_to) + link_from_root = abspath(link_from) + + # This is non-optimal because it recreates the entire + # venv site-packages tree (as opposed to finding a highest-common + # directory to symlink). But its easy and understandable. + for root, dirs, files in os.walk(link_to_root): + rel_path = os.path.relpath(root, link_to_root) + link_from_dir = join(link_from_root, rel_path) + + if not os.path.exists(link_from_dir): + os.makedirs(link_from_dir) + + for name in files: + link_to = join(root, name) + link_from = join(link_from_dir, name) + _symlink_exist_ok(from_=link_from, to=link_to) def _create_venv(runfiles_root, delete_dirs): rel_runfiles_venv = dirname(dirname(PYTHON_BINARY)) @@ -343,14 +364,14 @@ print(site.getsitepackages(["{venv_src}"])[-1]) output = output.strip().split("\n") python_exe_actual = output[0] python_home = output[1] if IS_WINDOWS else None - venv_site_packages = output[2] - os.makedirs(dirname(venv_site_packages), exist_ok=True) - runfiles_venv_site_packages = join(runfiles_venv, VENV_REL_SITE_PACKAGES) + venv_lib = output[2] + os.makedirs(dirname(venv_lib), exist_ok=True) + runfiles_venv_lib = join(runfiles_venv, VENV_REL_SITE_PACKAGES) else: # On unixy, Python can find home based on the symlink. python_home = dirname(python_exe_actual) if IS_WINDOWS else None - venv_site_packages = join(venv, "lib") - runfiles_venv_site_packages = join(runfiles_venv, "lib") + venv_lib = join(venv, LIB_DIR_NAME) + runfiles_venv_lib = join(runfiles_venv, LIB_DIR_NAME) venv_bin = join(venv, BIN_DIR_NAME) try: @@ -384,14 +405,16 @@ print(site.getsitepackages(["{venv_src}"])[-1]) target = join(runfiles_venv_bin, f_basename) _symlink_exist_ok(from_=venv_path, to=target) + # Recreate correct relative symlinks for venv_rel_path, link_to_rf_path in RUNTIME_VENV_SYMLINKS.items(): venv_abs_path = join(venv, venv_rel_path) - link_to = normpath(join(runfiles_venv, link_to_rf_path)) + link_to = normpath(join(runfiles_root, link_to_rf_path)) os.makedirs(dirname(venv_abs_path), exist_ok=True) _symlink_exist_ok(from_=venv_abs_path, to=link_to) - _symlink_exist_ok(from_=join(venv, "lib"), to=join(runfiles_venv, "lib")) - _symlink_exist_ok(from_=venv_site_packages, to=runfiles_venv_site_packages) + # Do this last to handle non-relative symlinks artifacts + if os.path.exists(runfiles_venv_lib): + _symlink_tree(link_from=venv_lib, link_to=runfiles_venv_lib) if IS_WINDOWS: print_verbose("create_venv: pyvenv.cfg home: ", python_home) @@ -436,7 +459,7 @@ def runfiles_envvar(runfiles_root): # Normally .runfiles_manifest and MANIFEST are both present, but the # former will be missing for zip-based builds or if someone copies the # runfiles tree elsewhere. - runfiles = os.path.join(runfiles_root, 'MANIFEST') + runfiles = join(runfiles_root, 'MANIFEST') if os.path.exists(runfiles): return ('RUNFILES_MANIFEST_FILE', runfiles) @@ -500,7 +523,7 @@ def execute_file(python_program, main_filename, args, env, runfiles_root, if delete_dirs: for delete_dir in delete_dirs: - print_verbose("rmtree:", delete_dir) + print_verbose("cleanup: rmtree:", delete_dir) shutil.rmtree(delete_dir, True) sys.exit(ret_code) @@ -520,13 +543,17 @@ def _run_execv(python_program, argv, env): def _symlink_exist_ok(*, from_, to): try: - os.symlink(to, from_) + # On Windows, symlinks have to be told whether they're + # pointing to a file or directory. Python is supposed to auto-detect + # this, but in practice, this doesn't reliably happen. + os.symlink(to, from_, target_is_directory=os.path.isdir(to)) except FileExistsError: pass def main(): print_verbose("sys.version:", sys.version) + print_verbose("sys.executable:", sys.executable) print_verbose("initial argv:", values=sys.argv) print_verbose("initial cwd:", os.getcwd()) print_verbose("initial environ:", mapping=os.environ) @@ -580,7 +607,7 @@ def main(): # See: https://docs.python.org/3.11/using/cmdline.html#envvar-PYTHONSAFEPATH new_env['PYTHONSAFEPATH'] = '1' - main_filename = os.path.join(runfiles_root, main_rel_path) + main_filename = join(runfiles_root, main_rel_path) main_filename = get_windows_path_with_unc_prefix(main_filename) assert os.path.exists(main_filename), \ 'Cannot exec() %r: file not found.' % main_filename @@ -616,7 +643,7 @@ def main(): # change directory to the right runfiles directory. # (So that the data files are accessible) if os.environ.get('RUN_UNDER_RUNFILES') == '1': - workspace = os.path.join(runfiles_root, WORKSPACE_NAME) + workspace = join(runfiles_root, WORKSPACE_NAME) try: sys.stdout.flush() diff --git a/python/private/site_init_template.py b/python/private/site_init_template.py index 97d16b71c2..e4d501bfd5 100644 --- a/python/private/site_init_template.py +++ b/python/private/site_init_template.py @@ -127,20 +127,19 @@ def _search_path(name): def _setup_sys_path(): - """Perform Bazel/binary specific sys.path setup. - - """ + """Perform Bazel/binary specific sys.path setup.""" + _print_verbose("site init: initial sys.path:\n", "\n".join(sys.path)) seen = set(sys.path) python_path_entries = [] - def _maybe_add_path(path): + def _maybe_add_path(path, reason): if path in seen: return path = _get_windows_path_with_unc_prefix(path) if _is_windows(): path = path.replace("/", os.sep) - _print_verbose("append sys.path:", path) + _print_verbose("append sys.path:", reason, ":", path) sys.path.append(path) seen.add(path) @@ -153,11 +152,11 @@ def _maybe_add_path(path): # For temporary compatibility with the original system_python bootstrap # behavior, it is conditionally added for that boostrap mode. if _ADD_RUNFILES_ROOT_TO_SYS_PATH: - _maybe_add_path(_RUNFILES_ROOT) + _maybe_add_path(_RUNFILES_ROOT, "runfiles-root") for rel_path in _IMPORTS_STR.split(":"): abs_path = os.path.join(_RUNFILES_ROOT, rel_path) - _maybe_add_path(abs_path) + _maybe_add_path(abs_path, "imports-strs") if _IMPORT_ALL: repo_dirs = sorted( @@ -165,9 +164,9 @@ def _maybe_add_path(path): ) for d in repo_dirs: if os.path.isdir(d): - _maybe_add_path(d) + _maybe_add_path(d, "import-all") else: - _maybe_add_path(os.path.join(_RUNFILES_ROOT, _WORKSPACE_NAME)) + _maybe_add_path(os.path.join(_RUNFILES_ROOT, _WORKSPACE_NAME), "workspace-root") # COVERAGE_DIR is set if coverage is enabled and instrumentation is configured # for something, though it could be another program executing this one or @@ -199,7 +198,7 @@ def _maybe_add_path(path): # it with the directory of the program it starts. Our actual sys.path[0] is # the runfiles directory, which must not be replaced. # CoverageScript.do_execute() undoes this sys.path[0] setting. - _maybe_add_path(coverage_dir) + _maybe_add_path(coverage_dir, "coverage-dir") coverage_setup = True else: _print_verbose_coverage( diff --git a/python/private/stage2_bootstrap_template.py b/python/private/stage2_bootstrap_template.py index dec356d3ac..2a7be67c13 100644 --- a/python/private/stage2_bootstrap_template.py +++ b/python/private/stage2_bootstrap_template.py @@ -53,6 +53,22 @@ # ===== Template substitutions end ===== +IS_WINDOWS = os.name == "nt" +IS_VERBOSE = bool(os.environ.get("RULES_PYTHON_BOOTSTRAP_VERBOSE")) + +# Windows APIs can be picky about slashes depending on the context, +# so convert to backslashes to avoid any issues. +# Related: some logic checks path strings, which needs uniform separators. +if IS_WINDOWS: + + def norm_slashes(s): + return s.replace("/", "\\") + + MAIN_PATH = norm_slashes(MAIN_PATH) + VENV_ROOT = norm_slashes(VENV_ROOT) + VENV_SITE_PACKAGES = norm_slashes(VENV_SITE_PACKAGES) + BUILD_DATA_FILE = norm_slashes(BUILD_DATA_FILE) + class BazelBinaryInfoModule(types.ModuleType): BUILD_DATA_FILE = BUILD_DATA_FILE @@ -84,8 +100,6 @@ def get_build_data(self): sys.modules["bazel_binary_info"] = BazelBinaryInfoModule("bazel_binary_info") -IS_WINDOWS = os.name == "nt" - def get_windows_path_with_unc_prefix(path): path = path.strip() @@ -124,32 +138,29 @@ def get_windows_path_with_unc_prefix(path): return unicode_prefix + os.path.abspath(path) -def is_verbose(): - return bool(os.environ.get("RULES_PYTHON_BOOTSTRAP_VERBOSE")) - - def print_verbose(*args, mapping=None, values=None): - if is_verbose(): - if mapping is not None: - for key, value in sorted((mapping or {}).items()): - print( - "bootstrap: stage 2:", - *args, - f"{key}={value!r}", - file=sys.stderr, - flush=True, - ) - elif values is not None: - for i, v in enumerate(values): - print( - "bootstrap: stage 2:", - *args, - f"[{i}] {v!r}", - file=sys.stderr, - flush=True, - ) - else: - print("bootstrap: stage 2:", *args, file=sys.stderr, flush=True) + if not IS_VERBOSE: + return + if mapping is not None: + for key, value in sorted((mapping or {}).items()): + print( + "bootstrap: stage 2:", + *args, + f"{key}={value!r}", + file=sys.stderr, + flush=True, + ) + elif values is not None: + for i, v in enumerate(values): + print( + "bootstrap: stage 2:", + *args, + f"[{i}] {v!r}", + file=sys.stderr, + flush=True, + ) + else: + print("bootstrap: stage 2:", *args, file=sys.stderr, flush=True) def print_verbose_coverage(*args): @@ -160,7 +171,7 @@ def print_verbose_coverage(*args): def is_verbose_coverage(): """Returns True if VERBOSE_COVERAGE is non-empty in the environment.""" - return os.environ.get("VERBOSE_COVERAGE") or is_verbose() + return os.environ.get("VERBOSE_COVERAGE") or IS_VERBOSE def find_runfiles_root(main_rel_path): @@ -419,12 +430,32 @@ def _maybe_collect_coverage(enable): def _add_site_packages(site_packages): + if sys.prefix != sys.base_prefix: + venv_root = sys.prefix + os.sep + saw_venv_site_packages = False + else: + venv_root = None + saw_venv_site_packages = True first_global_offset = len(sys.path) for i, p in enumerate(sys.path): + # Handle the Windows venv case: when a temporary directory is created + # for the venv, we want the build-time venv in runfiles to come after + # the venv site-packages directory. + if venv_root: + is_venv_path = (p + os.sep).startswith(venv_root) + if is_venv_path: + if p.endswith("site-packages"): + saw_venv_site_packages = True + is_after_venv_site_packages = False + else: + is_after_venv_site_packages = saw_venv_site_packages + else: + is_after_venv_site_packages = True + # We assume the first *-packages is the runtime's. # *-packages is matched because Debian may use dist-packages # instead of site-packages. - if p.endswith("-packages"): + if p.endswith("-packages") and is_after_venv_site_packages: first_global_offset = i break prev_len = len(sys.path) diff --git a/python/private/venv_runfiles.bzl b/python/private/venv_runfiles.bzl index a492181c88..6daf0d4e5c 100644 --- a/python/private/venv_runfiles.bzl +++ b/python/private/venv_runfiles.bzl @@ -3,7 +3,9 @@ load("@bazel_skylib//lib:paths.bzl", "paths") load( ":common.bzl", + "ExplicitSymlink", "is_file", + "is_windows_platform", "relative_path", "runfiles_root_path", ) @@ -58,6 +60,14 @@ def create_venv_app_files(ctx, deps, venv_dir_map): link_map = build_link_map(ctx, entries) venv_files = [] runfiles_symlinks = {} + explicit_symlinks = [] + + is_windows = is_windows_platform(ctx) + + ctx_rf_path = paths.join( + ctx.label.repo_name or ctx.workspace_name, + ctx.label.package, + ) for kind, kind_map in link_map.items(): base = venv_dir_map[kind] @@ -70,7 +80,7 @@ def create_venv_app_files(ctx, deps, venv_dir_map): symlink_from = paths.join(runfile_prefix, ctx.label.package, bin_venv_path) runfiles_symlinks[symlink_from] = link_to - else: + elif not is_windows: venv_link = ctx.actions.declare_symlink(bin_venv_path) venv_link_rf_path = runfiles_root_path(ctx, venv_link.short_path) rel_path = relative_path( @@ -81,10 +91,20 @@ def create_venv_app_files(ctx, deps, venv_dir_map): ) ctx.actions.symlink(output = venv_link, target_path = rel_path) venv_files.append(venv_link) + else: + rf_path = paths.join(ctx_rf_path, bin_venv_path) + _, _, venv_path = bin_venv_path.partition(".venv/") + explicit_symlinks.append(ExplicitSymlink( + runfiles_path = rf_path, + venv_path = venv_path, + link_to_path = link_to, + files = depset(), + )) return struct( venv_files = venv_files, runfiles_symlinks = runfiles_symlinks, + explicit_symlinks = depset(explicit_symlinks), ) # Visible for testing diff --git a/python/private/zipapp/py_zipapp_rule.bzl b/python/private/zipapp/py_zipapp_rule.bzl index f26b050165..4d31c99311 100644 --- a/python/private/zipapp/py_zipapp_rule.bzl +++ b/python/private/zipapp/py_zipapp_rule.bzl @@ -4,7 +4,16 @@ load("@bazel_skylib//lib:paths.bzl", "paths") load("@rules_python_internal//:rules_python_config.bzl", rp_config = "config") load("//python/private:attributes.bzl", "apply_config_settings_attr") load("//python/private:builders.bzl", "builders") -load("//python/private:common.bzl", "BUILTIN_BUILD_PYTHON_ZIP", "actions_run", "create_windows_exe_launcher", "maybe_builtin_build_python_zip", "maybe_create_repo_mapping", "runfiles_root_path", "target_platform_has_any_constraint") +load( + "//python/private:common.bzl", + "BUILTIN_BUILD_PYTHON_ZIP", + "actions_run", + "create_windows_exe_launcher", + "is_windows_platform", + "maybe_builtin_build_python_zip", + "maybe_create_repo_mapping", + "runfiles_root_path", +) load("//python/private:common_labels.bzl", "labels") load("//python/private:py_executable_info.bzl", "PyExecutableInfo") load("//python/private:py_internal.bzl", "py_internal") @@ -135,18 +144,22 @@ def _create_zip(ctx, py_runtime, py_executable, stage2_bootstrap): runfiles = runfiles.build(ctx) + explicit_symlinks = depset(transitive = [ + py_executable.venv_interpreter_symlinks, + py_executable.venv_app_symlinks, + ]) zip_main = _create_zipapp_main_py( ctx, py_runtime, py_executable, stage2_bootstrap, runfiles, - py_executable.venv_interpreter_symlinks, + explicit_symlinks, ) inputs = builders.DepsetBuilder() manifest.add("regular|0|__main__.py|{}".format(zip_main.path)) inputs.add(zip_main) - _build_manifest(ctx, manifest, runfiles, py_executable.venv_interpreter_symlinks, inputs) + _build_manifest(ctx, manifest, runfiles, explicit_symlinks, inputs) zipper_args = ctx.actions.args() zipper_args.add(output) @@ -159,7 +172,7 @@ def _create_zip(ctx, py_runtime, py_executable, stage2_bootstrap): zipper_args.add(ctx.attr.compression, format = "--compression=%s") zipper_args.add("--runfiles-dir=runfiles") - is_windows = target_platform_has_any_constraint(ctx, ctx.attr._windows_constraints) + is_windows = is_windows_platform(ctx) zipper_args.add("\\" if is_windows else "/", format = "--target-platform-pathsep=%s") actions_run( @@ -230,7 +243,7 @@ def _py_zipapp_executable_impl(ctx): zip_file = _create_zip(ctx, py_runtime, py_executable, stage2_bootstrap) if ctx.attr.executable: - if target_platform_has_any_constraint(ctx, ctx.attr._windows_constraints): + if is_windows_platform(ctx): executable = ctx.actions.declare_file(ctx.label.name + ".exe") # The zipapp is an opaque zip file, so the Bazel Python launcher doesn't diff --git a/python/private/zipapp/zip_main_template.py b/python/private/zipapp/zip_main_template.py index 6d12bc9c08..06ed19f1a5 100644 --- a/python/private/zipapp/zip_main_template.py +++ b/python/private/zipapp/zip_main_template.py @@ -29,7 +29,7 @@ import subprocess import tempfile import zipfile -from os.path import basename, dirname, join +from os.path import basename, dirname, join, normpath # runfiles-root-relative path _STAGE2_BOOTSTRAP = "%stage2_bootstrap%" @@ -47,8 +47,6 @@ IS_WINDOWS = os.name == "nt" -EXTRACT_ROOT = os.environ.get("RULES_PYTHON_EXTRACT_ROOT") - # Change the paths with Unix-style forward slashes to backslashes for Windows. # Windows usually transparently rewrites them, but e.g. `\\?\` paths require # backslashes to be properly understood by Windows APIs. @@ -65,9 +63,11 @@ def norm_slashes(s): EXTRACT_DIR = norm_slashes(EXTRACT_DIR) EXTRACT_ROOT = norm_slashes(EXTRACT_ROOT) +IS_VERBOSE = bool(os.environ.get("RULES_PYTHON_BOOTSTRAP_VERBOSE")) + def print_verbose(*args, mapping=None, values=None): - if not bool(os.environ.get("RULES_PYTHON_BOOTSTRAP_VERBOSE")): + if not IS_VERBOSE: return if mapping is not None: for key, value in sorted((mapping or {}).items()): @@ -150,7 +150,7 @@ def find_binary(runfiles_root, bin_name): # Case 2: Absolute path. return bin_name # Use normpath() to convert slashes to os.sep on Windows. - elif os.sep in os.path.normpath(bin_name): + elif os.sep in normpath(bin_name): # Case 3: Path is relative to the repo root. return join(runfiles_root, bin_name) else: @@ -194,7 +194,19 @@ def extract_zip(zip_path, dest_dir): with open(file_path, "r") as f: target = f.read() os.remove(file_path) - os.symlink(target, file_path) + if IS_WINDOWS: + entry_path = normpath(join(dirname(info.filename), target)) + # Zip lookup uses forward slashes, target has backslashes. + entry_path = entry_path.replace("\\", "/") + try: + target_is_directory = zf.getinfo(entry_path).is_dir() + except KeyError: + # Directories aren't stored in zips, so a missing + # target means it points to a directory. + target_is_directory = True + else: + target_is_directory = False + os.symlink(target, file_path, target_is_directory=target_is_directory) # Of those, we set the lower 12 bits, which are the # file mode bits (since the file type bits can't be set by chmod anyway). elif attrs != 0: # Rumor has it these can be 0 for zips created on Windows. @@ -214,7 +226,9 @@ def create_runfiles_root(): extract_root = get_windows_path_with_unc_prefix(extract_root) else: extract_root = tempfile.mkdtemp("", "Bazel.runfiles_") + extract_zip(dirname(__file__), extract_root) + print_verbose("extracted to:", extract_root) # IMPORTANT: Later code does `rm -fr` on dirname(runfiles_root) -- it's # important that deletion code be in sync with this directory structure return join(extract_root, "runfiles") @@ -314,10 +328,10 @@ def finish_venv_setup(runfiles_root): def main(): print_verbose("running zip main bootstrap") + print_verbose("initial sys.version:", sys.version) + print_verbose("initial sys.executable:", sys.executable) print_verbose("initial argv:", values=sys.argv) print_verbose("initial environ:", mapping=os.environ) - print_verbose("initial sys.executable:", sys.executable) - print_verbose("initial sys.version:", sys.version) print_verbose("stage2_bootstrap:", _STAGE2_BOOTSTRAP) print_verbose("python_binary_venv:", _PYTHON_BINARY_VENV) print_verbose("python_binary_actual:", _PYTHON_BINARY_ACTUAL) diff --git a/tests/py_zipapp/BUILD.bazel b/tests/py_zipapp/BUILD.bazel index fc1809b69f..a68448d964 100644 --- a/tests/py_zipapp/BUILD.bazel +++ b/tests/py_zipapp/BUILD.bazel @@ -4,24 +4,25 @@ load("//python:py_library.bzl", "py_library") load("//python:py_test.bzl", "py_test") load("//python/private:bzlmod_enabled.bzl", "BZLMOD_ENABLED") # buildifier: disable=bzl-visibility load("//python/zipapp:py_zipapp_binary.bzl", "py_zipapp_binary") -load("//tests/support:support.bzl", "NOT_WINDOWS") py_binary( name = "venv_bin", srcs = ["main.py"], config_settings = { - "//python/config_settings:bootstrap_impl": "script", "//python/config_settings:venvs_site_packages": "yes", - }, + } | select({ + "@platforms//os:windows": {}, + "//conditions:default": { + "//python/config_settings:bootstrap_impl": "script", + }, + }), main = "main.py", - target_compatible_with = NOT_WINDOWS, - deps = [":some_dep"], + deps = [":bin_deps"], ) py_zipapp_binary( name = "venv_zipapp", binary = ":venv_bin", - target_compatible_with = NOT_WINDOWS, ) py_test( @@ -32,7 +33,6 @@ py_test( "BZLMOD_ENABLED": str(int(BZLMOD_ENABLED)), "TEST_ZIPAPP": "$(location :venv_zipapp)", }, - target_compatible_with = NOT_WINDOWS, ) # Create the app with a supported level of compression @@ -40,7 +40,6 @@ py_zipapp_binary( name = "venv_zipapp_compressed", binary = ":venv_bin", compression = "4", - target_compatible_with = NOT_WINDOWS, ) py_test( @@ -53,7 +52,6 @@ py_test( "TEST_ZIPAPP": "$(location :venv_zipapp_compressed)", }, main = "venv_zipapp_test.py", - target_compatible_with = NOT_WINDOWS, ) py_binary( @@ -64,7 +62,7 @@ py_binary( "//python/config_settings:venvs_site_packages": "no", }, main = "main.py", - deps = [":some_dep"], + deps = [":bin_deps"], ) py_zipapp_binary( @@ -95,9 +93,24 @@ sh_test( toolchains = ["//python:current_py_toolchain"], ) +py_library( + name = "bin_deps", + deps = [ + ":pkgdep", + ":some_dep", + ], +) + py_library( name = "some_dep", srcs = ["some_dep.py"], experimental_venvs_site_packages = "//python/config_settings:venvs_site_packages", imports = ["."], ) + +py_library( + name = "pkgdep", + srcs = glob(["site-packages/**"]), + experimental_venvs_site_packages = "//python/config_settings:venvs_site_packages", + imports = ["site-packages"], +) diff --git a/tests/py_zipapp/main.py b/tests/py_zipapp/main.py index 8e67ec9fae..5770170d2c 100644 --- a/tests/py_zipapp/main.py +++ b/tests/py_zipapp/main.py @@ -7,13 +7,16 @@ def main(): import some_dep print(f"dep: {some_dep}") - except ImportError: + + import pkgdep.pkgmod + + print(f"dep: {pkgdep.pkgmod}") + except ImportError as e: import sys - print("Failed to import `some_dep`", file=sys.stderr) - print("sys.path:", file=sys.stderr) - for i, x in enumerate(sys.path): - print(i, x, file=sys.stderr) + e.add_note( + "Failed to import a dependency.\n" + "sys.path:\n" + "\n".join(sys.path) + ) raise diff --git a/tests/py_zipapp/site-packages/pkgdep/__init__.py b/tests/py_zipapp/site-packages/pkgdep/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/py_zipapp/site-packages/pkgdep/pkgmod.py b/tests/py_zipapp/site-packages/pkgdep/pkgmod.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/py_zipapp/system_python_zipapp_test.py b/tests/py_zipapp/system_python_zipapp_test.py index ec0f837135..79cd142c4d 100644 --- a/tests/py_zipapp/system_python_zipapp_test.py +++ b/tests/py_zipapp/system_python_zipapp_test.py @@ -11,7 +11,20 @@ def test_zipapp_runnable(self): self.assertTrue(os.path.exists(zipapp_path)) self.assertTrue(os.path.isfile(zipapp_path)) - output = subprocess.check_output([zipapp_path]).decode("utf-8").strip() + try: + output = ( + subprocess.check_output([zipapp_path], stderr=subprocess.STDOUT) + .decode("utf-8") + .strip() + ) + except subprocess.CalledProcessError as e: + self.fail( + "exit code: {}\n" + " command: {}\n" + "===== stdout/stderr start ==={}===== stdout/stderr end ====".format( + e.returncode, e.cmd, e.output.decode("utf-8") + ) + ) self.assertIn("Hello from zipapp", output) self.assertIn("dep:", output) diff --git a/tests/py_zipapp/venv_zipapp_test.py b/tests/py_zipapp/venv_zipapp_test.py index fec4a544bd..bd26d533a3 100644 --- a/tests/py_zipapp/venv_zipapp_test.py +++ b/tests/py_zipapp/venv_zipapp_test.py @@ -82,20 +82,22 @@ def test_zipapp_structure(self): if self._is_bzlmod_enabled(): self.assertIn("runfiles/_repo_mapping", namelist) - self.assertHasPathMatchingSuffix(namelist, "/pyvenv.cfg") - - # The venv directory name depends on the target name, so find it - # by looking for pyvenv.cfg. - venv_config = next( - (name for name in namelist if name.endswith("/pyvenv.cfg")), None - ) - self.assertIsNotNone(venv_config) - - venv_root = os.path.dirname(venv_config) - - # Verify bin/python3 exists and is a symlink - python_bin = f"{venv_root}/bin/python3" - self.assertZipEntryIsSymlink(zf, python_bin) + # On Windows, pyvenv.cfg and bin/python3 are generated at runtime. + if os.name != "nt": + self.assertHasPathMatchingSuffix(namelist, "/pyvenv.cfg") + + # The venv directory name depends on the target name, so find it + # by looking for pyvenv.cfg. + venv_config = next( + (name for name in namelist if name.endswith("/pyvenv.cfg")), None + ) + self.assertIsNotNone(venv_config) + + venv_root = os.path.dirname(venv_config) + + # Verify bin/python3 exists and is a symlink + python_bin = f"{venv_root}/bin/python3" + self.assertZipEntryIsSymlink(zf, python_bin) # Verify _bazel_site_init.py exists in site-packages self.assertHasPathMatchingSuffix( diff --git a/tests/venv_site_packages_libs/BUILD.bazel b/tests/venv_site_packages_libs/BUILD.bazel index e573dc6da6..56f0eb0909 100644 --- a/tests/venv_site_packages_libs/BUILD.bazel +++ b/tests/venv_site_packages_libs/BUILD.bazel @@ -1,11 +1,6 @@ load("@rules_shell//shell:sh_test.bzl", "sh_test") load("//python:py_library.bzl", "py_library") load("//tests/support:py_reconfig.bzl", "py_reconfig_test") -load( - "//tests/support:support.bzl", - "NOT_WINDOWS", - "SUPPORTS_BOOTSTRAP_SCRIPT", -) py_library( name = "user_lib", @@ -31,7 +26,6 @@ sh_test( env = { "VENV_BIN": "$(rootpath @other//:venv_bin)", }, - target_compatible_with = NOT_WINDOWS, ) py_reconfig_test( @@ -39,7 +33,6 @@ py_reconfig_test( srcs = ["bin.py"], bootstrap_impl = "script", main = "bin.py", - target_compatible_with = SUPPORTS_BOOTSTRAP_SCRIPT, venvs_site_packages = "yes", deps = [ ":closer_lib", @@ -58,13 +51,20 @@ py_reconfig_test( py_reconfig_test( name = "shared_lib_loading_test", srcs = ["shared_lib_loading_test.py"], - bootstrap_impl = "script", + bootstrap_impl = select({ + "@platforms//os:windows": "system_python", + "//conditions:default": "script", + }), main = "shared_lib_loading_test.py", - target_compatible_with = NOT_WINDOWS, venvs_site_packages = "yes", - deps = [ - "//tests/venv_site_packages_libs/ext_with_libs", - "@dev_pip//macholib", - "@dev_pip//pyelftools", - ], + deps = select({ + "@platforms//os:windows": [ + "@dev_pip//markupsafe", + ], + "//conditions:default": [ + "//tests/venv_site_packages_libs/ext_with_libs", + "@dev_pip//macholib", + "@dev_pip//pyelftools", + ], + }), ) diff --git a/tests/venv_site_packages_libs/bin.py b/tests/venv_site_packages_libs/bin.py index c075f4fc65..439a964906 100644 --- a/tests/venv_site_packages_libs/bin.py +++ b/tests/venv_site_packages_libs/bin.py @@ -22,8 +22,10 @@ def assert_imported_from_venv(self, module_name): self.assertTrue( module.__file__.startswith(self.venv), f"\n{module_name} was imported, but not from the venv.\n" - + f"venv : {self.venv}\n" - + f"actual: {module.__file__}", + + f" venv: {self.venv}\n" + + f"module file: {module.__file__}\n" + + "sys.path:\n" + + "\n".join(sys.path), ) return module diff --git a/tests/venv_site_packages_libs/shared_lib_loading_test.py b/tests/venv_site_packages_libs/shared_lib_loading_test.py index 2b58f8571c..a3f7bfcd5a 100644 --- a/tests/venv_site_packages_libs/shared_lib_loading_test.py +++ b/tests/venv_site_packages_libs/shared_lib_loading_test.py @@ -1,10 +1,21 @@ import importlib.util import os +import sys import unittest +from pathlib import Path -from elftools.elf.elffile import ELFFile -from macholib import mach_o -from macholib.MachO import MachO +# Optional imports for ELF/Mach-O analysis +if os.name == "posix" and sys.platform != "darwin": + from elftools.elf.elffile import ELFFile +else: + ELFFile = None + +if sys.platform == "darwin": + from macholib import mach_o + from macholib.MachO import MachO +else: + mach_o = None + MachO = None ELF_MAGIC = b"\x7fELF" MACHO_MAGICS = ( @@ -16,7 +27,14 @@ class SharedLibLoadingTest(unittest.TestCase): - def test_shared_library_linking(self): + def setUp(self): + super().setUp() + if sys.prefix == sys.base_prefix: + raise AssertionError("Not running under a venv") + self.venv = Path(sys.prefix) + + @unittest.skipIf(os.name == "nt", "Tests Unix-specific extension loading") + def test_shared_library_linking_unix(self): try: import ext_with_libs.adder except ImportError as e: @@ -53,6 +71,41 @@ def test_shared_library_linking(self): # Check the function works regardless of format. self.assertEqual(ext_with_libs.adder.do_add(), 2) + @unittest.skipUnless(os.name == "nt", "Tests Windows-specific extension loading") + def test_shared_library_loading_windows(self): + # We import markupsafe._speedups (a .cp311-win_amd64.pyd extension) + try: + import markupsafe._speedups + + module = markupsafe._speedups + except ImportError as e: + self.fail( + f"Failed to import markupsafe._speedups: {e}\n" + + "sys.path:\n" + + "\n".join(sys.path) + ) + + # Verify it's in the venv + # Normalize paths for Windows comparison. + # We DON'T use resolve() here because we want to see the path Python used, + # which should be within the venv's site-packages (even if it's a symlink). + actual_file = str(Path(module.__file__)).lower() + expected_prefix = str(self.venv).lower() + + self.assertTrue( + actual_file.startswith(expected_prefix), + f"Module {module.__name__} not loaded from venv.\n" + f"Venv: {expected_prefix}\n" + f"Module file: {actual_file}\n" + f"sys.path:\n" + "\n".join(sys.path), + ) + + # Verify it's a shared library (.pyd) + self.assertTrue( + actual_file.endswith(".pyd"), + f"Expected .pyd extension, got {module.__file__}", + ) + def _get_linking_info(self, path): """Parses a shared library and returns its rpaths and dependencies.""" path = os.path.realpath(path)