diff --git a/Cargo.lock b/Cargo.lock index d38ea4835..adff7a353 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4927,6 +4927,7 @@ dependencies = [ "uv-distribution", "uv-distribution-types", "uv-fs", + "uv-normalize", "uv-pep440", "uv-pep508", "uv-pypi-types", diff --git a/_typos.toml b/_typos.toml index 4db314325..4af054d17 100644 --- a/_typos.toml +++ b/_typos.toml @@ -3,6 +3,7 @@ extend-exclude = [ "**/snapshots/", "ecosystem/**", "scripts/**/*.in", + "crates/uv-build-frontend/src/pipreqs/mapping", ] ignore-hidden = false diff --git a/crates/uv-build-frontend/Cargo.toml b/crates/uv-build-frontend/Cargo.toml index d06240090..381aa2d81 100644 --- a/crates/uv-build-frontend/Cargo.toml +++ b/crates/uv-build-frontend/Cargo.toml @@ -22,6 +22,7 @@ uv-configuration = { workspace = true } uv-distribution = { workspace = true } uv-distribution-types = { workspace = true } uv-fs = { workspace = true } +uv-normalize = { workspace = true } uv-pep440 = { workspace = true } uv-pep508 = { workspace = true } uv-pypi-types = { workspace = true } diff --git a/crates/uv-build-frontend/src/error.rs b/crates/uv-build-frontend/src/error.rs index 572480970..24bfa972a 100644 --- a/crates/uv-build-frontend/src/error.rs +++ b/crates/uv-build-frontend/src/error.rs @@ -46,9 +46,10 @@ static LD_NOT_FOUND_RE: LazyLock = LazyLock::new(|| { static WHEEL_NOT_FOUND_RE: LazyLock = LazyLock::new(|| Regex::new(r"error: invalid command 'bdist_wheel'").unwrap()); -/// e.g. `ModuleNotFoundError: No module named 'torch'` -static TORCH_NOT_FOUND_RE: LazyLock = - LazyLock::new(|| Regex::new(r"ModuleNotFoundError: No module named 'torch'").unwrap()); +/// e.g. `ModuleNotFoundError` +static MODULE_NOT_FOUND: LazyLock = LazyLock::new(|| { + Regex::new("ModuleNotFoundError: No module named ['\"]([^'\"]+)['\"]").unwrap() +}); /// e.g. `ModuleNotFoundError: No module named 'distutils'` static DISTUTILS_NOT_FOUND_RE: LazyLock = @@ -130,6 +131,59 @@ pub struct MissingHeaderCause { version_id: Option, } +/// Extract the package name from a version specifier string. +/// Uses PEP 508 naming rules but more lenient for hinting purposes. +fn extract_package_name(version_id: &str) -> &str { + // https://peps.python.org/pep-0508/#names + // ^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$ with re.IGNORECASE + // Since we're only using this for a hint, we're more lenient than what we would be doing if this was used for parsing + let end = version_id + .char_indices() + .take_while(|(_, char)| matches!(char, 'A'..='Z' | 'a'..='z' | '0'..='9' | '.' | '-' | '_')) + .last() + .map_or(0, |(i, c)| i + c.len_utf8()); + + if end == 0 { + version_id + } else { + &version_id[..end] + } +} + +/// Write a hint about missing build dependencies. +fn hint_build_dependency( + f: &mut std::fmt::Formatter<'_>, + display_name: &str, + package_name: &str, + package: &str, +) -> std::fmt::Result { + let table_key = if package_name.contains('.') { + format!("\"{package_name}\"") + } else { + package_name.to_string() + }; + write!( + f, + "This error likely indicates that `{}` depends on `{}`, but doesn't declare it as a build dependency. \ + If `{}` is a first-party package, consider adding `{}` to its `{}`. \ + Otherwise, either add it to your `pyproject.toml` under:\n\ + \n\ + [tool.uv.extra-build-dependencies]\n\ + {} = [\"{}\"]\n\ + \n\ + or `{}` into the environment and re-run with `{}`.", + display_name.cyan(), + package.cyan(), + package_name.cyan(), + package.cyan(), + "build-system.requires".green(), + table_key.cyan(), + package.cyan(), + format!("uv pip install {package}").green(), + "--no-build-isolation".green(), + ) +} + impl Display for MissingHeaderCause { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match &self.missing_library { @@ -190,29 +244,15 @@ impl Display for MissingHeaderCause { if let (Some(package_name), Some(package_version)) = (&self.package_name, &self.package_version) { - write!( + hint_build_dependency( f, - "This error likely indicates that `{}` depends on `{}`, but doesn't declare it as a build dependency. If `{}` is a first-party package, consider adding `{}` to its `{}`. Otherwise, `{}` into the environment and re-run with `{}`.", - format!("{package_name}@{package_version}").cyan(), - package.cyan(), - package_name.cyan(), - package.cyan(), - "build-system.requires".green(), - format!("uv pip install {package}").green(), - "--no-build-isolation".green(), + &format!("{package_name}@{package_version}"), + package_name.as_str(), + package, ) } else if let Some(version_id) = &self.version_id { - write!( - f, - "This error likely indicates that `{}` depends on `{}`, but doesn't declare it as a build dependency. If `{}` is a first-party package, consider adding `{}` to its `{}`. Otherwise, `{}` into the environment and re-run with `{}`.", - version_id.cyan(), - package.cyan(), - version_id.cyan(), - package.cyan(), - "build-system.requires".green(), - format!("uv pip install {package}").green(), - "--no-build-isolation".green(), - ) + let package_name = extract_package_name(version_id); + hint_build_dependency(f, package_name, package_name, package) } else { write!( f, @@ -347,13 +387,22 @@ impl Error { Some(MissingLibrary::Linker(library.to_string())) } else if WHEEL_NOT_FOUND_RE.is_match(line.trim()) { Some(MissingLibrary::BuildDependency("wheel".to_string())) - } else if TORCH_NOT_FOUND_RE.is_match(line.trim()) { - Some(MissingLibrary::BuildDependency("torch".to_string())) } else if DISTUTILS_NOT_FOUND_RE.is_match(line.trim()) { Some(MissingLibrary::DeprecatedModule( "distutils".to_string(), Version::new([3, 12]), )) + } else if let Some(caps) = MODULE_NOT_FOUND.captures(line.trim()) { + if let Some(module_match) = caps.get(1) { + let module_name = module_match.as_str(); + let package_name = match crate::pipreqs::MODULE_MAPPING.lookup(module_name) { + Some(package) => package.to_string(), + None => module_name.to_string(), + }; + Some(MissingLibrary::BuildDependency(package_name)) + } else { + None + } } else { None } @@ -565,7 +614,7 @@ mod test { .to_string() .replace("exit status: ", "exit code: "); let formatted = anstream::adapter::strip_str(&formatted); - insta::assert_snapshot!(formatted, @r###" + insta::assert_snapshot!(formatted, @r#" Failed building wheel through setup.py (exit code: 0) [stderr] @@ -576,8 +625,13 @@ mod test { error: invalid command 'bdist_wheel' - hint: This error likely indicates that `pygraphviz-1.11` depends on `wheel`, but doesn't declare it as a build dependency. If `pygraphviz-1.11` is a first-party package, consider adding `wheel` to its `build-system.requires`. Otherwise, `uv pip install wheel` into the environment and re-run with `--no-build-isolation`. - "###); + hint: This error likely indicates that `pygraphviz-1.11` depends on `wheel`, but doesn't declare it as a build dependency. If `pygraphviz-1.11` is a first-party package, consider adding `wheel` to its `build-system.requires`. Otherwise, either add it to your `pyproject.toml` under: + + [tool.uv.extra-build-dependencies] + "pygraphviz-1.11" = ["wheel"] + + or `uv pip install wheel` into the environment and re-run with `--no-build-isolation`. + "#); } #[test] diff --git a/crates/uv-build-frontend/src/lib.rs b/crates/uv-build-frontend/src/lib.rs index 86fff042e..20d7c7cd3 100644 --- a/crates/uv-build-frontend/src/lib.rs +++ b/crates/uv-build-frontend/src/lib.rs @@ -3,6 +3,7 @@ //! mod error; +mod pipreqs; use std::borrow::Cow; use std::ffi::OsString; diff --git a/crates/uv-build-frontend/src/pipreqs.rs b/crates/uv-build-frontend/src/pipreqs.rs new file mode 100644 index 000000000..65f4e05e9 --- /dev/null +++ b/crates/uv-build-frontend/src/pipreqs.rs @@ -0,0 +1,32 @@ +use std::str::FromStr; +use std::sync::LazyLock; + +use rustc_hash::FxHashMap; +use uv_normalize::PackageName; + +/// A mapping from module name to PyPI package name. +pub(crate) struct ModuleMap<'a>(FxHashMap<&'a str, PackageName>); + +impl<'a> ModuleMap<'a> { + /// Generate a [`ModuleMap`] from a string representation, encoded in `${module}:{package}` format. + fn from_str(source: &'a str) -> Self { + let mut mapping = FxHashMap::default(); + for line in source.lines() { + if let Some((module, package)) = line.split_once(':') { + let module = module.trim(); + let package = PackageName::from_str(package.trim()).unwrap(); + mapping.insert(module, package); + } + } + Self(mapping) + } + + /// Look up a PyPI package name for a given module name. + pub(crate) fn lookup(&self, module: &str) -> Option<&PackageName> { + self.0.get(module) + } +} + +/// A mapping from module name to PyPI package name. +pub(crate) static MODULE_MAPPING: LazyLock = + LazyLock::new(|| ModuleMap::from_str(include_str!("pipreqs/mapping"))); diff --git a/crates/uv-build-frontend/src/pipreqs/LICENSE b/crates/uv-build-frontend/src/pipreqs/LICENSE new file mode 100644 index 000000000..8dada3eda --- /dev/null +++ b/crates/uv-build-frontend/src/pipreqs/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/crates/uv-build-frontend/src/pipreqs/mapping b/crates/uv-build-frontend/src/pipreqs/mapping new file mode 100644 index 000000000..8edacdd6d --- /dev/null +++ b/crates/uv-build-frontend/src/pipreqs/mapping @@ -0,0 +1,1156 @@ +AFQ:pyAFQ +AG_fft_tools:agpy +ANSI:pexpect +Adafruit:Adafruit_Libraries +App:Zope2 +Asterisk:py_Asterisk +BB_jekyll_hook:bitbucket_jekyll_hook +Banzai:Banzai_NGS +BeautifulSoupTests:BeautifulSoup +BioSQL:biopython +BuildbotStatusShields:BuildbotEightStatusShields +ComputedAttribute:ExtensionClass +constraint:python-constraint +Crypto:pycryptodome +Cryptodome:pycryptodomex +FSM:pexpect +FiftyOneDegrees:51degrees_mobile_detector_v3_wrapper +functional:pyfunctional +GeoBaseMain:GeoBasesDev +GeoBases:GeoBasesDev +Globals:Zope2 +HelpSys:Zope2 +IPython:ipython +Kittens:astro_kittens +Levenshtein:python_Levenshtein +Lifetime:Zope2 +MethodObject:ExtensionClass +MySQLdb:MySQL-python +OFS:Zope2 +OpenGL:PyOpenGL +OpenSSL:pyOpenSSL +PIL:Pillow +Products:Zope2 +PyWCSTools:astLib +Pyxides:astro_pyxis +QtCore:PySide +S3:s3cmd +SCons:pystick +speech_recognition:SpeechRecognition +Shared:Zope2 +Signals:Zope2 +Stemmer:PyStemmer +Testing:Zope2 +TopZooTools:topzootools +TreeDisplay:DocumentTemplate +WorkingWithDocumentConversion:aspose_pdf_java_for_python +ZPublisher:Zope2 +ZServer:Zope2 +ZTUtils:Zope2 +aadb:auto_adjust_display_brightness +abakaffe:abakaffe_cli +abiosgaming:abiosgaming.py +abiquo:abiquo_api +abl:abl.cssprocessor +abl:abl.robot +abl:abl.util +abl:abl.vpath +abo:abo_generator +abris_transform:abris +abstract:abstract.jwrotator +abu:abu.admin +ac_flask:AC_Flask_HipChat +acg:anikom15 +acme:acme.dchat +acme:acme.hello +acted:acted.projects +action:ActionServer +actionbar:actionbar.panel +activehomed:afn +activepapers:ActivePapers.Py +address_book:address_book_lansry +adi:adi.commons +adi:adi.devgen +adi:adi.fullscreen +adi:adi.init +adi:adi.playlist +adi:adi.samplecontent +adi:adi.slickstyle +adi:adi.suite +adi:adi.trash +adict:aDict2 +aditam:aditam.agent +aditam:aditam.core +adiumsh:adium_sh +adjector:AdjectorClient +adjector:AdjectorTracPlugin +adkit:Banner_Ad_Toolkit +admin_tools:django_admin_tools +adminishcategories:adminish_categories +adminsortable:django_admin_sortable +adspygoogle:adspygoogle.adwords +advancedcaching:agtl +adytum:Adytum_PyMonitor +affinitic:affinitic.docpyflakes +affinitic:affinitic.recipe.fakezope2eggs +affinitic:affinitic.simplecookiecuttr +affinitic:affinitic.verifyinterface +affinitic:affinitic.zamqp +afpy:afpy.xap +agatesql:agate_sql +ageliaco:ageliaco.recipe.csvconfig +agent_http:agent.http +agora:Agora_Client +agora:Agora_Fountain +agora:Agora_Fragment +agora:Agora_Planner +agora:Agora_Service_Provider +agoraplex:agoraplex.themes.sphinx +agsci:agsci.blognewsletter +agx:agx.core +agx:agx.dev +agx:agx.generator.buildout +agx:agx.generator.dexterity +agx:agx.generator.generator +agx:agx.generator.plone +agx:agx.generator.pyegg +agx:agx.generator.sql +agx:agx.generator.uml +agx:agx.generator.zca +agx:agx.transform.uml2fs +agx:agx.transform.xmi2uml +aimes:aimes.bundle +aimes:aimes.skeleton +aio:aio.app +aio:aio.config +aio:aio.core +aio:aio.signals +aiohs2:aio_hs2 +aioroutes:aio_routes +aios3:aio_s3 +airbrake:airbrake_flask +airship:airship_icloud +airship:airship_steamcloud +airflow:apache-airflow +akamai:edgegrid_python +alation:alation_api +alba_client:alba_client_python +alburnum:alburnum_maas_client +alchemist:alchemist.audit +alchemist:alchemist.security +alchemist:alchemist.traversal +alchemist:alchemist.ui +alchemyapi:alchemyapi_python +alerta:alerta_server +alexandria_upload:Alexandria_Upload_Utils +alibaba:alibaba_python_sdk +aliyun:aliyun_python_sdk +aliyuncli:alicloudcli +aliyunsdkacs:aliyun_python_sdk_acs +aliyunsdkbatchcompute:aliyun_python_sdk_batchcompute +aliyunsdkbsn:aliyun_python_sdk_bsn +aliyunsdkbss:aliyun_python_sdk_bss +aliyunsdkcdn:aliyun_python_sdk_cdn +aliyunsdkcms:aliyun_python_sdk_cms +aliyunsdkcore:aliyun_python_sdk_core +aliyunsdkcrm:aliyun_python_sdk_crm +aliyunsdkcs:aliyun_python_sdk_cs +aliyunsdkdrds:aliyun_python_sdk_drds +aliyunsdkecs:aliyun_python_sdk_ecs +aliyunsdkess:aliyun_python_sdk_ess +aliyunsdkft:aliyun_python_sdk_ft +aliyunsdkmts:aliyun_python_sdk_mts +aliyunsdkocs:aliyun_python_sdk_ocs +aliyunsdkoms:aliyun_python_sdk_oms +aliyunsdkossadmin:aliyun_python_sdk_ossadmin +aliyunsdkr-kvstore:aliyun_python_sdk_r_kvstore +aliyunsdkram:aliyun_python_sdk_ram +aliyunsdkrds:aliyun_python_sdk_rds +aliyunsdkrisk:aliyun_python_sdk_risk +aliyunsdkros:aliyun_python_sdk_ros +aliyunsdkslb:aliyun_python_sdk_slb +aliyunsdksts:aliyun_python_sdk_sts +aliyunsdkubsms:aliyun_python_sdk_ubsms +aliyunsdkyundun:aliyun_python_sdk_yundun +allattachments:AllAttachmentsMacro +allocine:allocine_wrapper +allowedsites:django_allowedsites +alm:alm.solrindex +aloft:aloft.py +alpacalib:alpaca +alphabetic:alphabetic_simple +alphasms:alphasms_client +altered:altered.states +alterootheme:alterootheme.busycity +alterootheme:alterootheme.intensesimplicity +alterootheme:alterootheme.lazydays +alurinium:alurinium_image_processing +alxlib:alx +amara3:amara3_iri +amara3:amara3_xml +amazon:AmazonAPIWrapper +amazon:python_amazon_simple_product_api +ambikesh1349-1:ambikesh1349_1 +ambilight:AmbilightParty +amifs:amifs_core +amiorganizer:ami_organizer +amitu:amitu.lipy +amitu:amitu_putils +amitu:amitu_websocket_client +amitu:amitu_zutils +amltlearn:AMLT_learn +amocrm:amocrm_api +amqpdispatcher:amqp_dispatcher +amqpstorm:AMQP_Storm +analytics:analytics_python +analyzedir:AnalyzeDirectory +ancientsolutions:ancientsolutions_crypttools +anderson_paginator:anderson.paginator +android_clean_app:android_resource_remover +anel_power_control:AnelPowerControl +angus:angus_sdk_python +annalist_root:Annalist +annogesiclib:ANNOgesic +ansible-role-apply:ansible_role_apply +ansibledebugger:ansible_playbook_debugger +ansibledocgen:ansible_docgen +ansibleflow:ansible_flow +ansibleinventorygrapher:ansible_inventory_grapher +ansiblelint:ansible_lint +ansiblerolesgraph:ansible_roles_graph +ansibletools:ansible_tools +anthill:anthill.exampletheme +anthill:anthill.skinner +anthill:anthill.tal.macrorenderer +anthrax:AnthraxDojoFrontend +anthrax:AnthraxHTMLInput +anthrax:AnthraxImage +antisphinx:antiweb +antispoofing:antispoofing.evaluation +antlr4:antlr4_python2_runtime +antlr4:antlr4_python3_runtime +antlr4:antlr4_python_alt +anybox:anybox.buildbot.openerp +anybox:anybox.nose.odoo +anybox:anybox.paster.odoo +anybox:anybox.paster.openerp +anybox:anybox.recipe.sysdeps +anybox:anybox.scripts.odoo +apiclient:google_api_python_client +apitools:google_apitools +apm:arpm +app_data:django_appdata +appconf:django_appconf +appd:AppDynamicsDownloader +appd:AppDynamicsREST +appdynamics_bindeps:appdynamics_bindeps_linux_x64 +appdynamics_bindeps:appdynamics_bindeps_linux_x86 +appdynamics_bindeps:appdynamics_bindeps_osx_x64 +appdynamics_proxysupport:appdynamics_proxysupport_linux_x64 +appdynamics_proxysupport:appdynamics_proxysupport_linux_x86 +appdynamics_proxysupport:appdynamics_proxysupport_osx_x64 +appium:Appium_Python_Client +appliapps:applibase +appserver:broadwick +archetypes:archetypes.kss +archetypes:archetypes.multilingual +archetypes:archetypes.schemaextender +arm:ansible_role_manager +armor:armor_api +armstrong:armstrong.apps.related_content +armstrong:armstrong.apps.series +armstrong:armstrong.cli +armstrong:armstrong.core.arm_access +armstrong:armstrong.core.arm_layout +armstrong:armstrong.core.arm_sections +armstrong:armstrong.core.arm_wells +armstrong:armstrong.dev +armstrong:armstrong.esi +armstrong:armstrong.hatband +armstrong:armstrong.templates.standard +armstrong:armstrong.utils.backends +armstrong:armstrong.utils.celery +arstecnica:arstecnica.raccoon.autobahn +arstecnica:arstecnica.sqlalchemy.async +article-downloader:article_downloader +artifactcli:artifact_cli +arvados:arvados_python_client +arvados_cwl:arvados_cwl_runner +arvnodeman:arvados_node_manager +asana_to_github:AsanaToGithub +asciibinary:AsciiBinaryConverter +asd:AdvancedSearchDiscovery +askbot:askbot_tuan +askbot:askbot_tuanpa +asnhistory:asnhistory_redis +aspen_jinja2_renderer:aspen_jinja2 +aspen_tornado_engine:aspen_tornado +asprise_ocr_api:asprise_ocr_sdk_python_api +aspy:aspy.refactor_imports +aspy:aspy.yaml +asterisk:asterisk_ami +asts:add_asts +asymmetricbase:asymmetricbase.enum +asymmetricbase:asymmetricbase.fields +asymmetricbase:asymmetricbase.logging +asymmetricbase:asymmetricbase.utils +asyncirc:asyncio_irc +asyncmongoorm:asyncmongoorm_je +asyncssh:asyncssh_unofficial +athletelist:athletelistyy +atm:automium +atmosphere:atmosphere_python_client +atom:gdata +atomic:AtomicWrite +atomisator:atomisator.db +atomisator:atomisator.enhancers +atomisator:atomisator.feed +atomisator:atomisator.indexer +atomisator:atomisator.outputs +atomisator:atomisator.parser +atomisator:atomisator.readers +atreal:atreal.cmfeditions.unlocker +atreal:atreal.filestorage.common +atreal:atreal.layouts +atreal:atreal.mailservices +atreal:atreal.massloader +atreal:atreal.monkeyplone +atreal:atreal.override.albumview +atreal:atreal.richfile.preview +atreal:atreal.richfile.qualifier +atreal:atreal.usersinout +atsim:atsim.potentials +attractsdk:attract_sdk +audio:audio.bitstream +audio:audio.coders +audio:audio.filters +audio:audio.fourier +audio:audio.frames +audio:audio.lp +audio:audio.psychoacoustics +audio:audio.quantizers +audio:audio.shrink +audio:audio.wave +aufrefer:auf_refer +auslfe:auslfe.formonline.content +auspost:auspost_apis +auth0:auth0_python +auth_server_client:AuthServerClient +authorize:AuthorizeSauce +authzpolicy:AuthzPolicyPlugin +autobahn:autobahn_rce +avatar:geonode_avatar +awebview:android_webview +azure:azure_common +azure:azure_mgmt_common +azure:azure_mgmt_compute +azure:azure_mgmt_network +azure:azure_mgmt_nspkg +azure:azure_mgmt_resource +azure:azure_mgmt_storage +azure:azure_nspkg +azure:azure_servicebus +azure:azure_servicemanagement_legacy +azure:azure_storage +b2gcommands:b2g_commands +b2gperf:b2gperf_v1.3 +b2gperf:b2gperf_v1.4 +b2gperf:b2gperf_v2.0 +b2gperf:b2gperf_v2.1 +b2gperf:b2gperf_v2.2 +b2gpopulate:b2gpopulate_v1.3 +b2gpopulate:b2gpopulate_v1.4 +b2gpopulate:b2gpopulate_v2.0 +b2gpopulate:b2gpopulate_v2.1 +b2gpopulate:b2gpopulate_v2.2 +b3j0f:b3j0f.annotation +b3j0f:b3j0f.aop +b3j0f:b3j0f.conf +b3j0f:b3j0f.sync +b3j0f:b3j0f.utils +babel:Babel +babelglade:BabelGladeExtractor +backplane:backplane2_pyclient +backport_abcoll:backport_collections +backports:backports.functools_lru_cache +backports:backports.inspect +backports:backports.pbkdf2 +backports:backports.shutil_get_terminal_size +backports:backports.socketpair +backports:backports.ssl +backports:backports.ssl_match_hostname +backports:backports.statistics +badgekit:badgekit_api_client +badlinks:BadLinksPlugin +bael:bael.project +baidu:baidupy +balrog:buildtools +baluhn:baluhn_redux +bamboo:bamboo.pantrybell +bamboo:bamboo.scaffold +bamboo:bamboo.setuptools_version +bamboo:bamboo_data +bamboo:bamboo_server +bambu:bambu_codemirror +bambu:bambu_dataportability +bambu:bambu_enqueue +bambu:bambu_faq +bambu:bambu_ffmpeg +bambu:bambu_grids +bambu:bambu_international +bambu:bambu_jwplayer +bambu:bambu_minidetect +bambu:bambu_navigation +bambu:bambu_notifications +bambu:bambu_payments +bambu:bambu_pusher +bambu:bambu_saas +bambu:bambu_sites +banana:Bananas +banana:banana.maya +bang:bangtext +barcode:barcode_generator +bark:bark_ssg +barking_owl:BarkingOwl +bart:bart_py +basalt:basalt_tasks +base62:base_62 +basemap:basemap_Jim +bash:bash_toolbelt +bashutils:Python_Bash_Utils +basic_http:BasicHttp +basil:basil_daq +batchapps:azure_batch_apps +bcrypt:python_bcrypt +beaker:Beaker +beetsplug:beets +begin:begins +benchit:bench_it +beproud:beproud.utils +bfillings:burrito_fillings +bigjob:BigJob +billboard:billboard.py +binstar_build_client:anaconda_build +binstar_client:anaconda_client +biocommons:biocommons.dev +birdhousebuilder:birdhousebuilder.recipe.conda +birdhousebuilder:birdhousebuilder.recipe.docker +birdhousebuilder:birdhousebuilder.recipe.redis +birdhousebuilder:birdhousebuilder.recipe.supervisor +blender26-meshio:pymeshio +bootstrap:BigJob +borg:borg.localrole +bow:bagofwords +bpdb:bpython +bqapi:bisque_api +braces:django_braces +briefscaster:briefs_caster +brisa_media_server/plugins:brisa_media_server_plugins +brkt_requests:brkt_sdk +broadcastlogging:broadcast_logging +brocadetool:brocade_tool +bronto:bronto_python +brownie:Brownie +browsermobproxy:browsermob_proxy +brubeckmysql:brubeck_mysql +brubeckoauth:brubeck_oauth +brubeckservice:brubeck_service +brubeckuploader:brubeck_uploader +bs4:beautifulsoup4 +bson:pymongo +bst:bst.pygasus.core +bst:bst.pygasus.datamanager +bst:bst.pygasus.demo +bst:bst.pygasus.i18n +bst:bst.pygasus.resources +bst:bst.pygasus.scaffolding +bst:bst.pygasus.security +bst:bst.pygasus.session +bst:bst.pygasus.wsgi +btable:btable_py +btapi:bananatag_api +btceapi:btce_api +btcebot:btce_bot +btsync:btsync.py +buck:buck.pprint +bud:bud.nospam +budy:budy_api +buffer:buffer_alpaca +buggd:bug.gd +bugle:bugle_sites +bugspots:bug_spots +bugzilla:python_bugzilla +bugzscout:bugzscout_py +buildTools:ajk_ios_buildTools +buildnotifylib:BuildNotify +buildout:buildout.bootstrap +buildout:buildout.disablessl +buildout:buildout.dumppickedversions +buildout:buildout.dumppickedversions2 +buildout:buildout.dumprequirements +buildout:buildout.eggnest +buildout:buildout.eggscleaner +buildout:buildout.eggsdirectories +buildout:buildout.eggtractor +buildout:buildout.extensionscripts +buildout:buildout.locallib +buildout:buildout.packagename +buildout:buildout.recipe.isolation +buildout:buildout.removeaddledeggs +buildout:buildout.requirements +buildout:buildout.sanitycheck +buildout:buildout.sendpickedversions +buildout:buildout.threatlevel +buildout:buildout.umask +buildout:buildout.variables +buildslave:buildbot_slave +builtins:pies2overrides +bumper:bumper_lib +bumple:bumple_downloader +bundesliga:bundesliga_cli +bundlemaker:bundlemanager +burpui:burp_ui +busyflow:busyflow.pivotal +buttercms-django:buttercms_django +buzz:buzz_python_client +bvc:buildout_versions_checker +bvggrabber:bvg_grabber +byond:BYONDTools +bzETL:Bugzilla_ETL +bzlib:bugzillatools +bzrlib:bzr +bzrlib:bzr_automirror +bzrlib:bzr_bash_completion +bzrlib:bzr_colo +bzrlib:bzr_killtrailing +bzrlib:bzr_pqm +c2c:c2c.cssmin +c2c:c2c.recipe.closurecompile +c2c:c2c.recipe.cssmin +c2c:c2c.recipe.jarfile +c2c:c2c.recipe.msgfmt +c2c:c2c.recipe.pkgversions +c2c:c2c.sqlalchemy.rest +c2c:c2c.versions +c2c_recipe_facts:c2c.recipe.facts +cabalgata:cabalgata_silla_de_montar +cabalgata:cabalgata_zookeeper +cache_utils:django_cache_utils +captcha:django_recaptcha +cartridge:Cartridge +cassandra:cassandra_driver +cassandralauncher:CassandraLauncher +cc42:42qucc +cerberus:Cerberus +cfnlint:cfn-lint +chameleon:Chameleon +charmtools:charm_tools +chef:PyChef +chip8:c8d +cjson:python_cjson +classytags:django_classy_tags +cloghandler:ConcurrentLogHandler +clonevirtualenv:virtualenv_clone +cloud-insight:al_cloudinsight +cloud_admin:adminapi +cloudservers:python_cloudservers +clusterconsole:cerebrod +clustersitter:cerebrod +cms:django_cms +colander:ba_colander +colors:ansicolors +compile:bf_lc3 +compose:docker_compose +compressor:django_compressor +concurrent:futures +configargparse:ConfigArgParse +configparser:pies2overrides +contracts:PyContracts +coordination:BigJob +copyreg:pies2overrides +corebio:weblogo +couchapp:Couchapp +couchdb:CouchDB +couchdbcurl:couchdb_python_curl +courseradownloader:coursera_dl +cow:cow_framework +creole:python_creole +creoleparser:Creoleparser +crispy_forms:django_crispy_forms +cronlog:python_crontab +crontab:python_crontab +ctff:tff +cups:pycups +curator:elasticsearch_curator +curl:pycurl +cv2:opencv-python +daemon:python_daemon +dare:DARE +dateutil:python_dateutil +dawg:DAWG +deb822:python_debian +debian:python_debian +decouple:python-decouple +demo:webunit +demosongs:PySynth +deployer:juju_deployer +depot:filedepot +devtools:tg.devtools +dgis:2gis +dhtmlparser:pyDHTMLParser +digitalocean:python_digitalocean +discord:discord.py +distribute_setup:ez_setup +distutils2:Distutils2 +django:Django +django_hstore:amitu_hstore +djangobower:django_bower +djcelery:django_celery +djkombu:django_kombu +djorm_pgarray:djorm_ext_pgarray +dns:dnspython +docgen:ansible_docgenerator +docker:docker_py +dogpile:dogpile.cache +dogpile:dogpile.core +dogshell:dogapi +dot_parser:pydot +dot_parser:pydot2 +dot_parser:pydot3k +dotenv:python-dotenv +dpkt:dpkt_fix +dsml:python_ldap +durationfield:django_durationfield +dzclient:datazilla +easybuild:easybuild_framework +editor:python_editor +elasticluster:azure_elasticluster +elasticluster:azure_elasticluster_current +elftools:pyelftools +elixir:Elixir +em:empy +emlib:empy +enchant:pyenchant +encutils:cssutils +engineio:python_engineio +enum:enum34 +ephem:pyephem +errorreporter:abl.errorreporter +esplot:beaker_es_plot +example:adrest +examples:tweepy +ez_setup:pycassa +fabfile:Fabric +fabric:Fabric +faker:Faker +fdpexpect:pexpect +fedora:python_fedora +fias:ailove_django_fias +fiftyone_degrees:51degrees_mobile_detector +five:five.customerize +five:five.globalrequest +five:five.intid +five:five.localsitemanager +five:five.pt +flasher:android_flasher +flask:Flask +flask_frozen:Frozen_Flask +flask_redis:Flask_And_Redis +flaskext:Flask_Bcrypt +flvscreen:vnc2flv +followit:django_followit +forge:pyforge +formencode:FormEncode +formtools:django_formtools +fourch:4ch +franz:allegrordf +freetype:freetype_py +frontmatter:python_frontmatter +ftpcloudfs:ftp_cloudfs +funtests:librabbitmq +fuse:fusepy +fuzzy:Fuzzy +gabbi:tiddlyweb +gen_3dwallet:3d_wallet_generator +gendimen:android_gendimen +genshi:Genshi +geohash:python_geohash +geonode:GeoNode +geoserver:gsconfig +geraldo:Geraldo +getenv:django_getenv +geventwebsocket:gevent_websocket +gflags:python_gflags +git:GitPython +github:PyGithub +github3:github3.py +gitpy:git_py +globusonline:globusonline_transfer_api_client +google:protobuf +googleapiclient:google_api_python_client +grace-dizmo:grace_dizmo +grammar:anovelmous_grammar +grapheneapi:graphenelib +greplin:scales +gridfs:pymongo +grokcore:grokcore.component +gslib:gsutil +hamcrest:PyHamcrest +harpy:HARPy +hawk:PyHawk_with_a_single_extra_commit +haystack:django_haystack +hgext:mercurial +hggit:hg_git +hglib:python_hglib +ho:pisa +hola:amarokHola +hoover:Hoover +hostlist:python_hostlist +html:pies2overrides +htmloutput:nosehtmloutput +http:pies2overrides +hvad:django_hvad +hydra:hydra-core +i99fix:199Fix +igraph:python_igraph +imdb:IMDbPY +impala:impyla +inmemorystorage:ambition_inmemorystorage +ipaddress:backport_ipaddress +jaraco:jaraco.timing +jaraco:jaraco.util +jinja2:Jinja2 +jiracli:jira_cli +johnny:johnny_cache +jose:python_jose +jpgrid:python_geohash +jpiarea:python_geohash +jpype:JPype1 +jpypex:JPype1 +jsonfield:django_jsonfield +jstools:aino_jstools +jupyterpip:jupyter_pip +jwt:PyJWT +kazoo:asana_kazoo +kernprof:line_profiler +keyczar:python_keyczar +keyedcache:django_keyedcache +keystoneclient:python_keystoneclient +kickstarter:kickstart +krbv:krbV +kss:kss.core +kuyruk:Kuyruk +langconv:AdvancedLangConv +lava:lava_utils_interface +lazr:lazr.authentication +lazr:lazr.restfulclient +lazr:lazr.uri +ldap:python_ldap +ldaplib:adpasswd +ldapurl:python_ldap +ldif:python_ldap +lib2or3:2or3 +lib3to2:3to2 +libaito:Aito +libbe:bugs_everywhere +libbucket:bucket +libcloud:apache_libcloud +libfuturize:future +libgenerateDS:generateDS +libmproxy:mitmproxy +libpasteurize:future +libsvm:7lk_ocr_deploy +lisa:lisa_server +loadingandsaving:aspose_words_java_for_python +locust:locustio +logbook:Logbook +logentries:buildbot_status_logentries +logilab:logilab_mtconverter +machineconsole:cerebrod +machinesitter:cerebrod +magic:python_magic +mako:Mako +manifestparser:ManifestDestiny +marionette:marionette_client +markdown:Markdown +marks:pytest_marks +markupsafe:MarkupSafe +mavnative:pymavlink +memcache:python_memcached +metacomm:AllPairs +metaphone:Metafone +metlog:metlog_py +mezzanine:Mezzanine +migrate:sqlalchemy_migrate +mimeparse:python_mimeparse +minitage:minitage.paste +minitage:minitage.recipe.common +missingdrawables:android_missingdrawables +mixfiles:PySynth +mkfreq:PySynth +mkrst_themes:2lazy2rest +mockredis:mockredispy +modargs:python_modargs +model_utils:django_model_utils +models:asposebarcode +models:asposestorage +moksha:moksha.common +moksha:moksha.hub +moksha:moksha.wsgi +moneyed:py_moneyed +mongoalchemy:MongoAlchemy +monthdelta:MonthDelta +mopidy:Mopidy +mopytools:MoPyTools +mptt:django_mptt +mpv:python-mpv +mrbob:mr.bob +msgpack:msgpack_python +mutations:aino_mutations +mws:amazon_mws +mysql:mysql_connector_repackaged +native_tags:django_native_tags +ndg:ndg_httpsclient +nereid:trytond_nereid +nested:baojinhuan +nester:Amauri +nester:abofly +nester:bssm_pythonSig +novaclient:python_novaclient +oauth2_provider:alauda_django_oauth +oauth2client:oauth2client +odf:odfpy +ometa:Parsley +openid:python_openid +opensearchsdk:ali_opensearch +oslo_i18n:oslo.i18n +oslo_serialization:oslo.serialization +oslo_utils:oslo.utils +oss:alioss +oss:aliyun_python_sdk_oss +oss:aliyunoss +output:cashew +owslib:OWSLib +packetdiag:nwdiag +paho:paho_mqtt +paintstore:django_paintstore +parler:django_parler +past:future +paste:PasteScript +path:forked_path +path:path.py +patricia:patricia-trie +paver:Paver +peak:ProxyTypes +picasso:anderson.picasso +picklefield:django-picklefield +pilot:BigJob +pivotal:pivotal_py +play_wav:PySynth +playhouse:peewee +plivoxml:plivo +plone:plone.alterego +plone:plone.api +plone:plone.app.blob +plone:plone.app.collection +plone:plone.app.content +plone:plone.app.contentlisting +plone:plone.app.contentmenu +plone:plone.app.contentrules +plone:plone.app.contenttypes +plone:plone.app.controlpanel +plone:plone.app.customerize +plone:plone.app.dexterity +plone:plone.app.discussion +plone:plone.app.event +plone:plone.app.folder +plone:plone.app.i18n +plone:plone.app.imaging +plone:plone.app.intid +plone:plone.app.layout +plone:plone.app.linkintegrity +plone:plone.app.locales +plone:plone.app.lockingbehavior +plone:plone.app.multilingual +plone:plone.app.portlets +plone:plone.app.querystring +plone:plone.app.redirector +plone:plone.app.registry +plone:plone.app.relationfield +plone:plone.app.textfield +plone:plone.app.theming +plone:plone.app.users +plone:plone.app.uuid +plone:plone.app.versioningbehavior +plone:plone.app.viewletmanager +plone:plone.app.vocabularies +plone:plone.app.widgets +plone:plone.app.workflow +plone:plone.app.z3cform +plone:plone.autoform +plone:plone.batching +plone:plone.behavior +plone:plone.browserlayer +plone:plone.caching +plone:plone.contentrules +plone:plone.dexterity +plone:plone.event +plone:plone.folder +plone:plone.formwidget.namedfile +plone:plone.formwidget.recurrence +plone:plone.i18n +plone:plone.indexer +plone:plone.intelligenttext +plone:plone.keyring +plone:plone.locking +plone:plone.memoize +plone:plone.namedfile +plone:plone.outputfilters +plone:plone.portlet.collection +plone:plone.portlet.static +plone:plone.portlets +plone:plone.protect +plone:plone.recipe.zope2install +plone:plone.registry +plone:plone.resource +plone:plone.resourceeditor +plone:plone.rfc822 +plone:plone.scale +plone:plone.schema +plone:plone.schemaeditor +plone:plone.session +plone:plone.stringinterp +plone:plone.subrequest +plone:plone.supermodel +plone:plone.synchronize +plone:plone.theme +plone:plone.transformchain +plone:plone.uuid +plone:plone.z3cform +plonetheme:plonetheme.barceloneta +png:pypng +polymorphic:django_polymorphic +postmark:python_postmark +powerprompt:bash_powerprompt +prefetch:django-prefetch +printList:AndrewList +progressbar:progressbar2 +progressbar:progressbar33 +provider:django_oauth2_provider +puresasl:pure_sasl +pwiz:peewee +pxssh:pexpect +py7zlib:pylzma +pyAMI:pyAMI_core +pyarsespyder:arsespyder +pyasdf:asdf +pyaspell:aspell_python_ctypes +pybb:pybbm +pybloomfilter:pybloomfiltermmap +pyccuracy:Pyccuracy +pyck:PyCK +pycrfsuite:python_crfsuite +pydispatch:PyDispatcher +pygeolib:pygeocoder +pygments:Pygments +pygraph:python_graph_core +pyjon:pyjon.utils +pyjsonrpc:python_jsonrpc +pykka:Pykka +pylogo:PyLogo +pylons:adhocracy_Pylons +pymagic:libmagic +pymycraawler:Amalwebcrawler +pynma:AbakaffeNotifier +pyphen:Pyphen +pyrimaa:AEI +pysideuic:PySide +pysqlite2:adhocracy_pysqlite +pysqlite2:pysqlite +pysynth_b:PySynth +pysynth_beeper:PySynth +pysynth_c:PySynth +pysynth_d:PySynth +pysynth_e:PySynth +pysynth_p:PySynth +pysynth_s:PySynth +pysynth_samp:PySynth +pythongettext:python_gettext +pythonjsonlogger:python_json_logger +pyutilib:PyUtilib +pywintypes:pywin32 +pyximport:Cython +qs:qserve +quadtree:python_geohash +queue:future +quickapi:django_quickapi +quickunit:nose_quickunit +rackdiag:nwdiag +radical:radical.pilot +radical:radical.utils +reStructuredText:Zope2 +readability:readability_lxml +readline:gnureadline +recaptcha_works:django_recaptcha_works +relstorage:RelStorage +reportapi:django_reportapi +reprlib:pies2overrides +requests:Requests +requirements:requirements_parser +rest_framework:djangorestframework +restclient:py_restclient +retrial:async_retrial +reversion:django_reversion +rhaptos2:rhaptos2.common +robot:robotframework +robots:django_robots +rosdep2:rosdep +rsbackends:RSFile +ruamel:ruamel.base +s2repoze:pysaml2 +saga:saga_python +saml2:pysaml2 +samtranslator:aws-sam-translator +sass:libsass +sassc:libsass +sasstests:libsass +sassutils:libsass +sayhi:alex_sayhi +scalrtools:scalr +scikits:scikits.talkbox +scratch:scratchpy +screen:pexpect +scss:pyScss +sdict:dict.sorted +sdk_updater:android_sdk_updater +sekizai:django_sekizai +sendfile:pysendfile +serial:pyserial +setuputils:astor +shapefile:pyshp +shapely:Shapely +sika:ahonya_sika +singleton:pysingleton +sittercommon:cerebrod +skbio:scikit_bio +sklearn:scikit_learn +slack:slackclient +slugify:unicode_slugify +slugify:python-slugify +smarkets:smk_python_sdk +snappy:ctypes_snappy +socketio:python-socketio +socketserver:pies2overrides +sockjs:sockjs_tornado +socks:SocksiPy_branch +solr:solrpy +solution:Solution +sorl:sorl_thumbnail +south:South +sphinx:Sphinx +sphinx_pypi_upload:ATD_document +sphinxcontrib:sphinxcontrib_programoutput +sqlalchemy:SQLAlchemy +src:atlas +src:auto_mix_prep +stats_toolkit:bw_stats_toolkit +statsd:dogstatsd_python +stdnum:python_stdnum +stoneagehtml:StoneageHTML +storages:django_storages +stubout:mox +suds:suds_jurko +swiftclient:python_swiftclient +sx:pisa +tabix:pytabix +taggit:django_taggit +tasksitter:cerebrod +tastypie:django_tastypie +teamcity:teamcity_messages +telebot:pyTelegramBotAPI +telegram:python-telegram-bot +tempita:Tempita +tenjin:Tenjin +termstyle:python_termstyle +test:pytabix +thclient:treeherder_client +threaded_multihost:django_threaded_multihost +threecolor:3color_Press +tidylib:pytidylib +tkinter:future +tlw:3lwg +toredis:toredis_fork +tornadoredis:tornado_redis +tower_cli:ansible_tower_cli +trac:Trac +tracopt:Trac +translation_helper:android_localization_helper +treebeard:django_treebeard +trytond:trytond_stock +tsuru:tsuru_circus +tvrage:python_tvrage +tw2:tw2.core +tw2:tw2.d3 +tw2:tw2.dynforms +tw2:tw2.excanvas +tw2:tw2.forms +tw2:tw2.jit +tw2:tw2.jqplugins.flot +tw2:tw2.jqplugins.gritter +tw2:tw2.jqplugins.ui +tw2:tw2.jquery +tw2:tw2.sqla +twisted:Twisted +twitter:python_twitter +txclib:transifex_client +u115:115wangpan +unidecode:Unidecode +universe:ansible_universe +usb:pyusb +useless:useless.pipes +userpass:auth_userpass +utilities:automakesetup.py +utkik:aino_utkik +uwsgidecorators:uWSGI +valentine:ab +validate:configobj +version:chartio +virtualenvapi:ar_virtualenv_api +vyatta:brocade_plugins +webdav:Zope2 +weblogolib:weblogo +webob:WebOb +websocket:websocket_client +webtest:WebTest +werkzeug:Werkzeug +wheezy:wheezy.caching +wheezy:wheezy.core +wheezy:wheezy.http +wikklytext:tiddlywebwiki +winreg:future +winrm:pywinrm +workflow:Alfred_Workflow +wsmeext:WSME +wtforms:WTForms +wtfpeewee:wtf_peewee +xdg:pyxdg +xdist:pytest_xdist +xmldsig:pysaml2 +xmlenc:pysaml2 +xmlrpc:pies2overrides +xmpp:xmpppy +xstatic:XStatic_Font_Awesome +xstatic:XStatic_jQuery +xstatic:XStatic_jquery_ui +yaml:PyYAML +z3c:z3c.autoinclude +z3c:z3c.caching +z3c:z3c.form +z3c:z3c.formwidget.query +z3c:z3c.objpath +z3c:z3c.pt +z3c:z3c.relationfield +z3c:z3c.traverser +z3c:z3c.zcmlhook +zmq:pyzmq +zopyx:zopyx.textindexng3 diff --git a/crates/uv/tests/it/pip_install.rs b/crates/uv/tests/it/pip_install.rs index 2103abc1d..80172125c 100644 --- a/crates/uv/tests/it/pip_install.rs +++ b/crates/uv/tests/it/pip_install.rs @@ -4954,7 +4954,7 @@ fn no_build_isolation() -> Result<()> { uv_snapshot!(context.filters(), context.pip_install() .arg("-r") .arg("requirements.in") - .arg("--no-build-isolation"), @r###" + .arg("--no-build-isolation"), @r#" success: false exit_code: 1 ----- stdout ----- @@ -4969,8 +4969,13 @@ fn no_build_isolation() -> Result<()> { File "", line 8, in ModuleNotFoundError: No module named 'setuptools' - hint: This usually indicates a problem with the package or the build environment. - "### + hint: This error likely indicates that `anyio` depends on `setuptools`, but doesn't declare it as a build dependency. If `anyio` is a first-party package, consider adding `setuptools` to its `build-system.requires`. Otherwise, either add it to your `pyproject.toml` under: + + [tool.uv.extra-build-dependencies] + anyio = ["setuptools"] + + or `uv pip install setuptools` into the environment and re-run with `--no-build-isolation`. + "# ); // Install `setuptools` and `wheel`. @@ -5022,7 +5027,7 @@ fn respect_no_build_isolation_env_var() -> Result<()> { uv_snapshot!(context.filters(), context.pip_install() .arg("-r") .arg("requirements.in") - .env(EnvVars::UV_NO_BUILD_ISOLATION, "yes"), @r###" + .env(EnvVars::UV_NO_BUILD_ISOLATION, "yes"), @r#" success: false exit_code: 1 ----- stdout ----- @@ -5037,8 +5042,13 @@ fn respect_no_build_isolation_env_var() -> Result<()> { File "", line 8, in ModuleNotFoundError: No module named 'setuptools' - hint: This usually indicates a problem with the package or the build environment. - "### + hint: This error likely indicates that `anyio` depends on `setuptools`, but doesn't declare it as a build dependency. If `anyio` is a first-party package, consider adding `setuptools` to its `build-system.requires`. Otherwise, either add it to your `pyproject.toml` under: + + [tool.uv.extra-build-dependencies] + anyio = ["setuptools"] + + or `uv pip install setuptools` into the environment and re-run with `--no-build-isolation`. + "# ); // Install `setuptools` and `wheel`. @@ -8663,7 +8673,7 @@ fn install_build_isolation_package() -> Result<()> { uv_snapshot!(context.filters(), context.pip_install() .arg("--no-build-isolation-package") .arg("iniconfig") - .arg(package.path()), @r###" + .arg(package.path()), @r#" success: false exit_code: 1 ----- stdout ----- @@ -8678,8 +8688,13 @@ fn install_build_isolation_package() -> Result<()> { File "", line 8, in ModuleNotFoundError: No module named 'hatchling' - hint: This usually indicates a problem with the package or the build environment. - "### + hint: This error likely indicates that `iniconfig` depends on `hatchling`, but doesn't declare it as a build dependency. If `iniconfig` is a first-party package, consider adding `hatchling` to its `build-system.requires`. Otherwise, either add it to your `pyproject.toml` under: + + [tool.uv.extra-build-dependencies] + iniconfig = ["hatchling"] + + or `uv pip install hatchling` into the environment and re-run with `--no-build-isolation`. + "# ); // Install `hatchinling`, `hatch-vs` for iniconfig diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index c63bc154f..c0e9dfb18 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -1410,7 +1410,12 @@ fn sync_build_isolation_package() -> Result<()> { File "", line 8, in ModuleNotFoundError: No module named 'hatchling' - hint: This usually indicates a problem with the package or the build environment. + hint: This error likely indicates that `source-distribution` depends on `hatchling`, but doesn't declare it as a build dependency. If `source-distribution` is a first-party package, consider adding `hatchling` to its `build-system.requires`. Otherwise, either add it to your `pyproject.toml` under: + + [tool.uv.extra-build-dependencies] + source-distribution = ["hatchling"] + + or `uv pip install hatchling` into the environment and re-run with `--no-build-isolation`. help: `source-distribution` was included because `project` (v0.1.0) depends on `source-distribution` "#); @@ -1500,7 +1505,12 @@ fn sync_build_isolation_extra() -> Result<()> { File "", line 8, in ModuleNotFoundError: No module named 'hatchling' - hint: This usually indicates a problem with the package or the build environment. + hint: This error likely indicates that `source-distribution` depends on `hatchling`, but doesn't declare it as a build dependency. If `source-distribution` is a first-party package, consider adding `hatchling` to its `build-system.requires`. Otherwise, either add it to your `pyproject.toml` under: + + [tool.uv.extra-build-dependencies] + source-distribution = ["hatchling"] + + or `uv pip install hatchling` into the environment and re-run with `--no-build-isolation`. help: `source-distribution` was included because `project[compile]` (v0.1.0) depends on `source-distribution` "#); @@ -1521,7 +1531,12 @@ fn sync_build_isolation_extra() -> Result<()> { File "", line 8, in ModuleNotFoundError: No module named 'hatchling' - hint: This usually indicates a problem with the package or the build environment. + hint: This error likely indicates that `source-distribution` depends on `hatchling`, but doesn't declare it as a build dependency. If `source-distribution` is a first-party package, consider adding `hatchling` to its `build-system.requires`. Otherwise, either add it to your `pyproject.toml` under: + + [tool.uv.extra-build-dependencies] + source-distribution = ["hatchling"] + + or `uv pip install hatchling` into the environment and re-run with `--no-build-isolation`. help: `source-distribution` was included because `project[compile]` (v0.1.0) depends on `source-distribution` "#); @@ -2328,6 +2343,176 @@ fn sync_extra_build_dependencies_sources_from_child() -> Result<()> { Ok(()) } +#[test] +fn sync_build_dependencies_module_error_hints() -> Result<()> { + let context = TestContext::new("3.12").with_filtered_counts(); + + // Write a test package that arbitrarily requires `anyio` at build time + let child = context.temp_dir.child("child"); + child.create_dir_all()?; + let child_pyproject_toml = child.child("pyproject.toml"); + child_pyproject_toml.write_str(indoc! {r#" + [project] + name = "child" + version = "0.1.0" + requires-python = ">=3.9" + + [build-system] + requires = ["hatchling"] + backend-path = ["."] + build-backend = "build_backend" + "#})?; + let build_backend = child.child("build_backend.py"); + build_backend.write_str(indoc! {r" + import sys + + from hatchling.build import * + import anyio + "})?; + child.child("src/child/__init__.py").touch()?; + + let parent = &context.temp_dir; + let pyproject_toml = parent.child("pyproject.toml"); + pyproject_toml.write_str(indoc! {r#" + [project] + name = "parent" + version = "0.1.0" + requires-python = ">=3.9" + dependencies = ["child"] + + [tool.uv.sources] + child = { path = "child" } + "#})?; + + context.venv().arg("--clear").assert().success(); + // Running `uv sync` should fail due to missing build-dependencies + uv_snapshot!(context.filters(), context.sync(), @r#" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + Resolved [N] packages in [TIME] + × Failed to build `child @ file://[TEMP_DIR]/child` + ├─▶ The build backend returned an error + ╰─▶ Call to `build_backend.build_wheel` failed (exit status: 1) + + [stderr] + Traceback (most recent call last): + File "", line 8, in + File "[TEMP_DIR]/child/build_backend.py", line 4, in + import anyio + ModuleNotFoundError: No module named 'anyio' + + hint: This error likely indicates that `child@0.1.0` depends on `anyio`, but doesn't declare it as a build dependency. If `child` is a first-party package, consider adding `anyio` to its `build-system.requires`. Otherwise, either add it to your `pyproject.toml` under: + + [tool.uv.extra-build-dependencies] + child = ["anyio"] + + or `uv pip install anyio` into the environment and re-run with `--no-build-isolation`. + help: `child` was included because `parent` (v0.1.0) depends on `child` + "#); + + // Adding `extra-build-dependencies` should solve the issue + pyproject_toml.write_str(indoc! {r#" + [project] + name = "parent" + version = "0.1.0" + requires-python = ">=3.9" + dependencies = ["child"] + + [tool.uv.sources] + child = { path = "child" } + + [tool.uv.extra-build-dependencies] + child = ["anyio"] + "#})?; + + context.venv().arg("--clear").assert().success(); + uv_snapshot!(context.filters(), context.sync(), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features extra-build-dependencies` to disable this warning. + Resolved [N] packages in [TIME] + Prepared [N] packages in [TIME] + Installed [N] packages in [TIME] + + child==0.1.0 (from file://[TEMP_DIR]/child) + "); + + // Assert pipreqs module name to package name lookup works. + build_backend.write_str(indoc! {r" + import sys + + from hatchling.build import * + import anyio + import sklearn + "})?; + + context.venv().arg("--clear").assert().success(); + // Running `uv sync` should fail due to missing build-dependencies + uv_snapshot!(context.filters(), context.sync().arg("--reinstall"), @r#" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features extra-build-dependencies` to disable this warning. + Resolved [N] packages in [TIME] + × Failed to build `child @ file://[TEMP_DIR]/child` + ├─▶ The build backend returned an error + ╰─▶ Call to `build_backend.build_wheel` failed (exit status: 1) + + [stderr] + Traceback (most recent call last): + File "", line 8, in + File "[TEMP_DIR]/child/build_backend.py", line 5, in + import sklearn + ModuleNotFoundError: No module named 'sklearn' + + hint: This error likely indicates that `child@0.1.0` depends on `scikit-learn`, but doesn't declare it as a build dependency. If `child` is a first-party package, consider adding `scikit-learn` to its `build-system.requires`. Otherwise, either add it to your `pyproject.toml` under: + + [tool.uv.extra-build-dependencies] + child = ["scikit-learn"] + + or `uv pip install scikit-learn` into the environment and re-run with `--no-build-isolation`. + help: `child` was included because `parent` (v0.1.0) depends on `child` + "#); + + // Adding `extra-build-dependencies` should solve the issue + pyproject_toml.write_str(indoc! {r#" + [project] + name = "parent" + version = "0.1.0" + requires-python = ">=3.9" + dependencies = ["child"] + + [tool.uv.sources] + child = { path = "child" } + + [tool.uv.extra-build-dependencies] + child = ["anyio", "scikit-learn"] + "#})?; + + context.venv().arg("--clear").assert().success(); + uv_snapshot!(context.filters(), context.sync(), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features extra-build-dependencies` to disable this warning. + Resolved [N] packages in [TIME] + Prepared [N] packages in [TIME] + Installed [N] packages in [TIME] + + child==0.1.0 (from file://[TEMP_DIR]/child) + "); + + Ok(()) +} + /// Avoid using incompatible versions for build dependencies that are also part of the resolved /// environment. This is a very subtle issue, but: when locking, we don't enforce platform /// compatibility. So, if we reuse the resolver state to install, and the install itself has to