mirror of
https://github.com/astral-sh/uv.git
synced 2025-09-26 20:19:08 +00:00
Add hashes to pip-compile
output (#894)
## Summary Adds hashes to `pip-compile` output, though we don't actually check those hashes in `pip-sync` yet. Closes https://github.com/astral-sh/puffin/issues/131.
This commit is contained in:
parent
1629141d67
commit
06039e1293
9 changed files with 281 additions and 37 deletions
|
@ -22,7 +22,9 @@ use puffin_dispatch::BuildDispatch;
|
||||||
use puffin_installer::Downloader;
|
use puffin_installer::Downloader;
|
||||||
use puffin_interpreter::{Interpreter, PythonVersion};
|
use puffin_interpreter::{Interpreter, PythonVersion};
|
||||||
use puffin_normalize::ExtraName;
|
use puffin_normalize::ExtraName;
|
||||||
use puffin_resolver::{Manifest, PreReleaseMode, ResolutionMode, ResolutionOptions, Resolver};
|
use puffin_resolver::{
|
||||||
|
DisplayResolutionGraph, Manifest, PreReleaseMode, ResolutionMode, ResolutionOptions, Resolver,
|
||||||
|
};
|
||||||
use puffin_traits::SetupPyStrategy;
|
use puffin_traits::SetupPyStrategy;
|
||||||
use requirements_txt::EditableRequirement;
|
use requirements_txt::EditableRequirement;
|
||||||
|
|
||||||
|
@ -44,6 +46,7 @@ pub(crate) async fn pip_compile(
|
||||||
resolution_mode: ResolutionMode,
|
resolution_mode: ResolutionMode,
|
||||||
prerelease_mode: PreReleaseMode,
|
prerelease_mode: PreReleaseMode,
|
||||||
upgrade_mode: UpgradeMode,
|
upgrade_mode: UpgradeMode,
|
||||||
|
generate_hashes: bool,
|
||||||
index_urls: IndexUrls,
|
index_urls: IndexUrls,
|
||||||
setup_py: SetupPyStrategy,
|
setup_py: SetupPyStrategy,
|
||||||
no_build: bool,
|
no_build: bool,
|
||||||
|
@ -275,7 +278,11 @@ pub(crate) async fn pip_compile(
|
||||||
"{}",
|
"{}",
|
||||||
format!("# puffin {}", env::args().skip(1).join(" ")).green()
|
format!("# puffin {}", env::args().skip(1).join(" ")).green()
|
||||||
)?;
|
)?;
|
||||||
write!(writer, "{resolution}")?;
|
write!(
|
||||||
|
writer,
|
||||||
|
"{}",
|
||||||
|
DisplayResolutionGraph::new(&resolution, generate_hashes)
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(ExitStatus::Success)
|
Ok(ExitStatus::Success)
|
||||||
}
|
}
|
||||||
|
|
|
@ -167,6 +167,10 @@ struct PipCompileArgs {
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
upgrade: bool,
|
upgrade: bool,
|
||||||
|
|
||||||
|
/// Include distribution hashes in the output file.
|
||||||
|
#[clap(long)]
|
||||||
|
generate_hashes: bool,
|
||||||
|
|
||||||
/// Use legacy `setuptools` behavior when building source distributions without a
|
/// Use legacy `setuptools` behavior when building source distributions without a
|
||||||
/// `pyproject.toml`.
|
/// `pyproject.toml`.
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
|
@ -511,6 +515,7 @@ async fn inner() -> Result<ExitStatus> {
|
||||||
args.resolution,
|
args.resolution,
|
||||||
args.prerelease,
|
args.prerelease,
|
||||||
args.upgrade.into(),
|
args.upgrade.into(),
|
||||||
|
args.generate_hashes,
|
||||||
index_urls,
|
index_urls,
|
||||||
if args.legacy_setup_py {
|
if args.legacy_setup_py {
|
||||||
SetupPyStrategy::Setuptools
|
SetupPyStrategy::Setuptools
|
||||||
|
|
|
@ -2911,3 +2911,127 @@ fn compile_legacy_sdist_setuptools() -> Result<()> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Include hashes in the generated output.
|
||||||
|
#[test]
|
||||||
|
fn generate_hashes() -> Result<()> {
|
||||||
|
let temp_dir = TempDir::new()?;
|
||||||
|
let cache_dir = TempDir::new()?;
|
||||||
|
let venv = create_venv_py312(&temp_dir, &cache_dir);
|
||||||
|
|
||||||
|
let requirements_in = temp_dir.child("requirements.in");
|
||||||
|
requirements_in.write_str("flask==3.0.0")?;
|
||||||
|
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => INSTA_FILTERS.to_vec()
|
||||||
|
}, {
|
||||||
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.arg("pip-compile")
|
||||||
|
.arg("requirements.in")
|
||||||
|
.arg("--generate-hashes")
|
||||||
|
.arg("--cache-dir")
|
||||||
|
.arg(cache_dir.path())
|
||||||
|
.arg("--exclude-newer")
|
||||||
|
.arg(EXCLUDE_NEWER)
|
||||||
|
.env("VIRTUAL_ENV", venv.as_os_str())
|
||||||
|
.current_dir(&temp_dir), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
# This file was autogenerated by Puffin v0.0.1 via the following command:
|
||||||
|
# puffin pip-compile requirements.in --generate-hashes --cache-dir [CACHE_DIR]
|
||||||
|
blinker==1.7.0 \
|
||||||
|
--hash=sha256:c3f865d4d54db7abc53758a01601cf343fe55b84c1de4e3fa910e420b438d5b9 \
|
||||||
|
--hash=sha256:e6820ff6fa4e4d1d8e2747c2283749c3f547e4fee112b98555cdcdae32996182
|
||||||
|
# via flask
|
||||||
|
click==8.1.7 \
|
||||||
|
--hash=sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28 \
|
||||||
|
--hash=sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de
|
||||||
|
# via flask
|
||||||
|
flask==3.0.0 \
|
||||||
|
--hash=sha256:21128f47e4e3b9d597a3e8521a329bf56909b690fcc3fa3e477725aa81367638 \
|
||||||
|
--hash=sha256:cfadcdb638b609361d29ec22360d6070a77d7463dcb3ab08d2c2f2f168845f58
|
||||||
|
itsdangerous==2.1.2 \
|
||||||
|
--hash=sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44 \
|
||||||
|
--hash=sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a
|
||||||
|
# via flask
|
||||||
|
jinja2==3.1.2 \
|
||||||
|
--hash=sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852 \
|
||||||
|
--hash=sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61
|
||||||
|
# via flask
|
||||||
|
markupsafe==2.1.3 \
|
||||||
|
--hash=sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e \
|
||||||
|
--hash=sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e \
|
||||||
|
--hash=sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431 \
|
||||||
|
--hash=sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686 \
|
||||||
|
--hash=sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c \
|
||||||
|
--hash=sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559 \
|
||||||
|
--hash=sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc \
|
||||||
|
--hash=sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb \
|
||||||
|
--hash=sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939 \
|
||||||
|
--hash=sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c \
|
||||||
|
--hash=sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0 \
|
||||||
|
--hash=sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4 \
|
||||||
|
--hash=sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9 \
|
||||||
|
--hash=sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575 \
|
||||||
|
--hash=sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba \
|
||||||
|
--hash=sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d \
|
||||||
|
--hash=sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd \
|
||||||
|
--hash=sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3 \
|
||||||
|
--hash=sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00 \
|
||||||
|
--hash=sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155 \
|
||||||
|
--hash=sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac \
|
||||||
|
--hash=sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52 \
|
||||||
|
--hash=sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f \
|
||||||
|
--hash=sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8 \
|
||||||
|
--hash=sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b \
|
||||||
|
--hash=sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007 \
|
||||||
|
--hash=sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24 \
|
||||||
|
--hash=sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea \
|
||||||
|
--hash=sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198 \
|
||||||
|
--hash=sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0 \
|
||||||
|
--hash=sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee \
|
||||||
|
--hash=sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be \
|
||||||
|
--hash=sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2 \
|
||||||
|
--hash=sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1 \
|
||||||
|
--hash=sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707 \
|
||||||
|
--hash=sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6 \
|
||||||
|
--hash=sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c \
|
||||||
|
--hash=sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58 \
|
||||||
|
--hash=sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823 \
|
||||||
|
--hash=sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779 \
|
||||||
|
--hash=sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636 \
|
||||||
|
--hash=sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c \
|
||||||
|
--hash=sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad \
|
||||||
|
--hash=sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee \
|
||||||
|
--hash=sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc \
|
||||||
|
--hash=sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2 \
|
||||||
|
--hash=sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48 \
|
||||||
|
--hash=sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7 \
|
||||||
|
--hash=sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e \
|
||||||
|
--hash=sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b \
|
||||||
|
--hash=sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa \
|
||||||
|
--hash=sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5 \
|
||||||
|
--hash=sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e \
|
||||||
|
--hash=sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb \
|
||||||
|
--hash=sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9 \
|
||||||
|
--hash=sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57 \
|
||||||
|
--hash=sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc \
|
||||||
|
--hash=sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc \
|
||||||
|
--hash=sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2 \
|
||||||
|
--hash=sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11
|
||||||
|
# via
|
||||||
|
# jinja2
|
||||||
|
# werkzeug
|
||||||
|
werkzeug==3.0.1 \
|
||||||
|
--hash=sha256:507e811ecea72b18a404947aded4b3390e1db8f826b494d76550ef45bb3b1dcc \
|
||||||
|
--hash=sha256:90a285dc0e42ad56b34e696398b8122ee4c681833fb35b8334a095d82c56da10
|
||||||
|
# via flask
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 7 packages in [TIME]
|
||||||
|
"###);
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ pub use error::ResolveError;
|
||||||
pub use finder::{DistFinder, Reporter as FinderReporter};
|
pub use finder::{DistFinder, Reporter as FinderReporter};
|
||||||
pub use manifest::Manifest;
|
pub use manifest::Manifest;
|
||||||
pub use prerelease_mode::PreReleaseMode;
|
pub use prerelease_mode::PreReleaseMode;
|
||||||
pub use resolution::{Diagnostic, ResolutionGraph};
|
pub use resolution::{Diagnostic, DisplayResolutionGraph, ResolutionGraph};
|
||||||
pub use resolution_mode::ResolutionMode;
|
pub use resolution_mode::ResolutionMode;
|
||||||
pub use resolution_options::ResolutionOptions;
|
pub use resolution_options::ResolutionOptions;
|
||||||
pub use resolver::{BuildId, Reporter as ResolverReporter, Resolver, ResolverProvider};
|
pub use resolver::{BuildId, Reporter as ResolverReporter, Resolver, ResolverProvider};
|
||||||
|
|
|
@ -15,10 +15,11 @@ use pep440_rs::Version;
|
||||||
use pep508_rs::{Requirement, VerbatimUrl};
|
use pep508_rs::{Requirement, VerbatimUrl};
|
||||||
use puffin_normalize::{ExtraName, PackageName};
|
use puffin_normalize::{ExtraName, PackageName};
|
||||||
use puffin_traits::OnceMap;
|
use puffin_traits::OnceMap;
|
||||||
use pypi_types::Metadata21;
|
use pypi_types::{Hashes, Metadata21};
|
||||||
|
|
||||||
use crate::pins::FilePins;
|
use crate::pins::FilePins;
|
||||||
use crate::pubgrub::{PubGrubDistribution, PubGrubPackage, PubGrubPriority, PubGrubVersion};
|
use crate::pubgrub::{PubGrubDistribution, PubGrubPackage, PubGrubPriority, PubGrubVersion};
|
||||||
|
use crate::version_map::VersionMap;
|
||||||
use crate::ResolveError;
|
use crate::ResolveError;
|
||||||
|
|
||||||
/// A complete resolution graph in which every node represents a pinned package and every edge
|
/// A complete resolution graph in which every node represents a pinned package and every edge
|
||||||
|
@ -27,6 +28,8 @@ use crate::ResolveError;
|
||||||
pub struct ResolutionGraph {
|
pub struct ResolutionGraph {
|
||||||
/// The underlying graph.
|
/// The underlying graph.
|
||||||
petgraph: petgraph::graph::Graph<Dist, Range<PubGrubVersion>, petgraph::Directed>,
|
petgraph: petgraph::graph::Graph<Dist, Range<PubGrubVersion>, petgraph::Directed>,
|
||||||
|
/// The metadata for every distribution in this resolution.
|
||||||
|
hashes: FxHashMap<PackageName, Vec<Hashes>>,
|
||||||
/// The set of editable requirements in this resolution.
|
/// The set of editable requirements in this resolution.
|
||||||
editables: FxHashMap<PackageName, (LocalEditable, Metadata21)>,
|
editables: FxHashMap<PackageName, (LocalEditable, Metadata21)>,
|
||||||
/// Any diagnostics that were encountered while building the graph.
|
/// Any diagnostics that were encountered while building the graph.
|
||||||
|
@ -38,6 +41,7 @@ impl ResolutionGraph {
|
||||||
pub(crate) fn from_state(
|
pub(crate) fn from_state(
|
||||||
selection: &SelectedDependencies<PubGrubPackage, PubGrubVersion>,
|
selection: &SelectedDependencies<PubGrubPackage, PubGrubVersion>,
|
||||||
pins: &FilePins,
|
pins: &FilePins,
|
||||||
|
packages: &OnceMap<PackageName, VersionMap>,
|
||||||
distributions: &OnceMap<PackageId, Metadata21>,
|
distributions: &OnceMap<PackageId, Metadata21>,
|
||||||
redirects: &OnceMap<Url, Url>,
|
redirects: &OnceMap<Url, Url>,
|
||||||
state: &State<PubGrubPackage, Range<PubGrubVersion>, PubGrubPriority>,
|
state: &State<PubGrubPackage, Range<PubGrubVersion>, PubGrubPriority>,
|
||||||
|
@ -46,6 +50,8 @@ impl ResolutionGraph {
|
||||||
// TODO(charlie): petgraph is a really heavy and unnecessary dependency here. We should
|
// TODO(charlie): petgraph is a really heavy and unnecessary dependency here. We should
|
||||||
// write our own graph, given that our requirements are so simple.
|
// write our own graph, given that our requirements are so simple.
|
||||||
let mut petgraph = petgraph::graph::Graph::with_capacity(selection.len(), selection.len());
|
let mut petgraph = petgraph::graph::Graph::with_capacity(selection.len(), selection.len());
|
||||||
|
let mut hashes =
|
||||||
|
FxHashMap::with_capacity_and_hasher(selection.len(), BuildHasherDefault::default());
|
||||||
let mut diagnostics = Vec::new();
|
let mut diagnostics = Vec::new();
|
||||||
|
|
||||||
// Add every package to the graph.
|
// Add every package to the graph.
|
||||||
|
@ -54,16 +60,28 @@ impl ResolutionGraph {
|
||||||
for (package, version) in selection {
|
for (package, version) in selection {
|
||||||
match package {
|
match package {
|
||||||
PubGrubPackage::Package(package_name, None, None) => {
|
PubGrubPackage::Package(package_name, None, None) => {
|
||||||
let version = Version::from(version.clone());
|
// Create the distribution.
|
||||||
let pinned_package = pins
|
let pinned_package = pins
|
||||||
.get(package_name, &version)
|
.get(package_name, &Version::from(version.clone()))
|
||||||
.expect("Every package should be pinned")
|
.expect("Every package should be pinned")
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
|
// Add its hashes to the index.
|
||||||
|
if let Some(entry) = packages.get(package_name) {
|
||||||
|
let version_map = entry.value();
|
||||||
|
hashes.insert(package_name.clone(), {
|
||||||
|
let mut hashes = version_map.hashes(version);
|
||||||
|
hashes.sort_unstable();
|
||||||
|
hashes
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the distribution to the graph.
|
||||||
let index = petgraph.add_node(pinned_package);
|
let index = petgraph.add_node(pinned_package);
|
||||||
inverse.insert(package_name, index);
|
inverse.insert(package_name, index);
|
||||||
}
|
}
|
||||||
PubGrubPackage::Package(package_name, None, Some(url)) => {
|
PubGrubPackage::Package(package_name, None, Some(url)) => {
|
||||||
|
// Create the distribution.
|
||||||
let pinned_package = if let Some((editable, _)) = editables.get(package_name) {
|
let pinned_package = if let Some((editable, _)) = editables.get(package_name) {
|
||||||
Dist::from_editable(package_name.clone(), editable.clone())?
|
Dist::from_editable(package_name.clone(), editable.clone())?
|
||||||
} else {
|
} else {
|
||||||
|
@ -74,6 +92,17 @@ impl ResolutionGraph {
|
||||||
Dist::from_url(package_name.clone(), url)?
|
Dist::from_url(package_name.clone(), url)?
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Add its hashes to the index.
|
||||||
|
if let Some(entry) = packages.get(package_name) {
|
||||||
|
let version_map = entry.value();
|
||||||
|
hashes.insert(package_name.clone(), {
|
||||||
|
let mut hashes = version_map.hashes(version);
|
||||||
|
hashes.sort_unstable();
|
||||||
|
hashes
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the distribution to the graph.
|
||||||
let index = petgraph.add_node(pinned_package);
|
let index = petgraph.add_node(pinned_package);
|
||||||
inverse.insert(package_name, index);
|
inverse.insert(package_name, index);
|
||||||
}
|
}
|
||||||
|
@ -156,6 +185,7 @@ impl ResolutionGraph {
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
petgraph,
|
petgraph,
|
||||||
|
hashes,
|
||||||
editables,
|
editables,
|
||||||
diagnostics,
|
diagnostics,
|
||||||
})
|
})
|
||||||
|
@ -194,29 +224,74 @@ impl ResolutionGraph {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A [`std::fmt::Display`] implementation for the resolution graph.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DisplayResolutionGraph<'a> {
|
||||||
|
/// The underlying graph.
|
||||||
|
resolution: &'a ResolutionGraph,
|
||||||
|
/// Whether to include hashes in the output.
|
||||||
|
show_hashes: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> DisplayResolutionGraph<'a> {
|
||||||
|
/// Create a new [`DisplayResolutionGraph`] for the given graph.
|
||||||
|
pub fn new(underlying: &'a ResolutionGraph, show_hashes: bool) -> DisplayResolutionGraph<'a> {
|
||||||
|
Self {
|
||||||
|
resolution: underlying,
|
||||||
|
show_hashes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a ResolutionGraph> for DisplayResolutionGraph<'a> {
|
||||||
|
fn from(resolution: &'a ResolutionGraph) -> Self {
|
||||||
|
Self::new(resolution, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Write the graph in the `{name}=={version}` format of requirements.txt that pip uses.
|
/// Write the graph in the `{name}=={version}` format of requirements.txt that pip uses.
|
||||||
impl std::fmt::Display for ResolutionGraph {
|
impl std::fmt::Display for DisplayResolutionGraph<'_> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
// Collect and sort all packages.
|
// Collect and sort all packages.
|
||||||
let mut nodes = self
|
let mut nodes = self
|
||||||
|
.resolution
|
||||||
.petgraph
|
.petgraph
|
||||||
.node_indices()
|
.node_indices()
|
||||||
.map(|node| (node, &self.petgraph[node]))
|
.map(|node| (node, &self.resolution.petgraph[node]))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
nodes.sort_unstable_by_key(|(_, package)| package.name());
|
nodes.sort_unstable_by_key(|(_, package)| package.name());
|
||||||
|
|
||||||
// Print out the dependency graph.
|
// Print out the dependency graph.
|
||||||
for (index, package) in nodes {
|
for (index, package) in nodes {
|
||||||
if let Some((editable, _)) = self.editables.get(package.name()) {
|
// Display the node itself.
|
||||||
writeln!(f, "-e {}", editable.verbatim())?;
|
if let Some((editable, _)) = self.resolution.editables.get(package.name()) {
|
||||||
|
write!(f, "-e {}", editable.verbatim())?;
|
||||||
} else {
|
} else {
|
||||||
writeln!(f, "{}", package.verbatim())?;
|
write!(f, "{}", package.verbatim())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Display the distribution hashes, if any.
|
||||||
|
if self.show_hashes {
|
||||||
|
if let Some(hashes) = self
|
||||||
|
.resolution
|
||||||
|
.hashes
|
||||||
|
.get(package.name())
|
||||||
|
.filter(|hashes| !hashes.is_empty())
|
||||||
|
{
|
||||||
|
for hash in hashes {
|
||||||
|
writeln!(f, " \\")?;
|
||||||
|
write!(f, " --hash={hash}")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeln!(f)?;
|
||||||
|
|
||||||
|
// Display all dependencies.
|
||||||
let mut edges = self
|
let mut edges = self
|
||||||
|
.resolution
|
||||||
.petgraph
|
.petgraph
|
||||||
.edges_directed(index, Direction::Incoming)
|
.edges_directed(index, Direction::Incoming)
|
||||||
.map(|edge| &self.petgraph[edge.source()])
|
.map(|edge| &self.resolution.petgraph[edge.source()])
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
edges.sort_unstable_by_key(|package| package.name());
|
edges.sort_unstable_by_key(|package| package.name());
|
||||||
|
|
||||||
|
|
|
@ -260,6 +260,7 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> {
|
||||||
return ResolutionGraph::from_state(
|
return ResolutionGraph::from_state(
|
||||||
&selection,
|
&selection,
|
||||||
&pins,
|
&pins,
|
||||||
|
&self.index.packages,
|
||||||
&self.index.distributions,
|
&self.index.distributions,
|
||||||
&self.index.redirects,
|
&self.index.redirects,
|
||||||
&state,
|
&state,
|
||||||
|
|
|
@ -11,7 +11,7 @@ use platform_tags::{TagPriority, Tags};
|
||||||
use puffin_client::SimpleMetadata;
|
use puffin_client::SimpleMetadata;
|
||||||
use puffin_normalize::PackageName;
|
use puffin_normalize::PackageName;
|
||||||
use puffin_warnings::warn_user_once;
|
use puffin_warnings::warn_user_once;
|
||||||
use pypi_types::{BaseUrl, Yanked};
|
use pypi_types::{BaseUrl, Hashes, Yanked};
|
||||||
|
|
||||||
use crate::pubgrub::PubGrubVersion;
|
use crate::pubgrub::PubGrubVersion;
|
||||||
use crate::python_requirement::PythonRequirement;
|
use crate::python_requirement::PythonRequirement;
|
||||||
|
@ -42,7 +42,7 @@ impl VersionMap {
|
||||||
for (version, files) in metadata {
|
for (version, files) in metadata {
|
||||||
for (filename, file) in files.all() {
|
for (filename, file) in files.all() {
|
||||||
// Support resolving as if it were an earlier timestamp, at least as long files have
|
// Support resolving as if it were an earlier timestamp, at least as long files have
|
||||||
// upload time information
|
// upload time information.
|
||||||
if let Some(exclude_newer) = exclude_newer {
|
if let Some(exclude_newer) = exclude_newer {
|
||||||
match file.upload_time.as_ref() {
|
match file.upload_time.as_ref() {
|
||||||
Some(upload_time) if upload_time >= exclude_newer => {
|
Some(upload_time) if upload_time >= exclude_newer => {
|
||||||
|
@ -50,9 +50,8 @@ impl VersionMap {
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
warn_user_once!(
|
warn_user_once!(
|
||||||
"{} is missing an upload date, but user provided: {}",
|
"{} is missing an upload date, but user provided: {exclude_newer}",
|
||||||
file.filename,
|
file.filename,
|
||||||
exclude_newer,
|
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -69,7 +68,9 @@ impl VersionMap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prioritize amongst all available files.
|
||||||
let requires_python = file.requires_python.clone();
|
let requires_python = file.requires_python.clone();
|
||||||
|
let hash = file.hashes.clone();
|
||||||
match filename {
|
match filename {
|
||||||
DistFilename::WheelFilename(filename) => {
|
DistFilename::WheelFilename(filename) => {
|
||||||
// To be compatible, the wheel must both have compatible tags _and_ have a
|
// To be compatible, the wheel must both have compatible tags _and_ have a
|
||||||
|
@ -92,12 +93,13 @@ impl VersionMap {
|
||||||
Entry::Occupied(mut entry) => {
|
Entry::Occupied(mut entry) => {
|
||||||
entry
|
entry
|
||||||
.get_mut()
|
.get_mut()
|
||||||
.insert_built(dist, requires_python, priority);
|
.insert_built(dist, requires_python, hash, priority);
|
||||||
}
|
}
|
||||||
Entry::Vacant(entry) => {
|
Entry::Vacant(entry) => {
|
||||||
entry.insert(PrioritizedDistribution::from_built(
|
entry.insert(PrioritizedDistribution::from_built(
|
||||||
dist,
|
dist,
|
||||||
requires_python,
|
requires_python,
|
||||||
|
hash,
|
||||||
priority,
|
priority,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -113,12 +115,13 @@ impl VersionMap {
|
||||||
);
|
);
|
||||||
match version_map.entry(version.clone().into()) {
|
match version_map.entry(version.clone().into()) {
|
||||||
Entry::Occupied(mut entry) => {
|
Entry::Occupied(mut entry) => {
|
||||||
entry.get_mut().insert_source(dist, requires_python);
|
entry.get_mut().insert_source(dist, requires_python, hash);
|
||||||
}
|
}
|
||||||
Entry::Vacant(entry) => {
|
Entry::Vacant(entry) => {
|
||||||
entry.insert(PrioritizedDistribution::from_source(
|
entry.insert(PrioritizedDistribution::from_source(
|
||||||
dist,
|
dist,
|
||||||
requires_python,
|
requires_python,
|
||||||
|
hash,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -143,6 +146,14 @@ impl VersionMap {
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|(version, file)| Some((version, file.get()?)))
|
.filter_map(|(version, file)| Some((version, file.get()?)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the [`Hashes`] for the given version, if any.
|
||||||
|
pub(crate) fn hashes(&self, version: &PubGrubVersion) -> Vec<Hashes> {
|
||||||
|
self.0
|
||||||
|
.get(version)
|
||||||
|
.map(|file| file.hashes.clone())
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attach its requires-python to a [`Dist`], since downstream needs this information to filter
|
/// Attach its requires-python to a [`Dist`], since downstream needs this information to filter
|
||||||
|
@ -161,6 +172,8 @@ struct PrioritizedDistribution {
|
||||||
compatible_wheel: Option<(DistRequiresPython, TagPriority)>,
|
compatible_wheel: Option<(DistRequiresPython, TagPriority)>,
|
||||||
/// An arbitrary, platform-incompatible wheel for the package version.
|
/// An arbitrary, platform-incompatible wheel for the package version.
|
||||||
incompatible_wheel: Option<DistRequiresPython>,
|
incompatible_wheel: Option<DistRequiresPython>,
|
||||||
|
/// The hashes for each distribution.
|
||||||
|
hashes: Vec<Hashes>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PrioritizedDistribution {
|
impl PrioritizedDistribution {
|
||||||
|
@ -168,6 +181,7 @@ impl PrioritizedDistribution {
|
||||||
fn from_built(
|
fn from_built(
|
||||||
dist: Dist,
|
dist: Dist,
|
||||||
requires_python: Option<VersionSpecifiers>,
|
requires_python: Option<VersionSpecifiers>,
|
||||||
|
hash: Hashes,
|
||||||
priority: Option<TagPriority>,
|
priority: Option<TagPriority>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
if let Some(priority) = priority {
|
if let Some(priority) = priority {
|
||||||
|
@ -182,6 +196,7 @@ impl PrioritizedDistribution {
|
||||||
priority,
|
priority,
|
||||||
)),
|
)),
|
||||||
incompatible_wheel: None,
|
incompatible_wheel: None,
|
||||||
|
hashes: vec![hash],
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Self {
|
Self {
|
||||||
|
@ -191,12 +206,13 @@ impl PrioritizedDistribution {
|
||||||
dist,
|
dist,
|
||||||
requires_python,
|
requires_python,
|
||||||
}),
|
}),
|
||||||
|
hashes: vec![hash],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new [`PrioritizedDistribution`] from the given source distribution.
|
/// Create a new [`PrioritizedDistribution`] from the given source distribution.
|
||||||
fn from_source(dist: Dist, requires_python: Option<VersionSpecifiers>) -> Self {
|
fn from_source(dist: Dist, requires_python: Option<VersionSpecifiers>, hash: Hashes) -> Self {
|
||||||
Self {
|
Self {
|
||||||
source: Some(DistRequiresPython {
|
source: Some(DistRequiresPython {
|
||||||
dist,
|
dist,
|
||||||
|
@ -204,6 +220,7 @@ impl PrioritizedDistribution {
|
||||||
}),
|
}),
|
||||||
compatible_wheel: None,
|
compatible_wheel: None,
|
||||||
incompatible_wheel: None,
|
incompatible_wheel: None,
|
||||||
|
hashes: vec![hash],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,6 +229,7 @@ impl PrioritizedDistribution {
|
||||||
&mut self,
|
&mut self,
|
||||||
dist: Dist,
|
dist: Dist,
|
||||||
requires_python: Option<VersionSpecifiers>,
|
requires_python: Option<VersionSpecifiers>,
|
||||||
|
hash: Hashes,
|
||||||
priority: Option<TagPriority>,
|
priority: Option<TagPriority>,
|
||||||
) {
|
) {
|
||||||
// Prefer the highest-priority, platform-compatible wheel.
|
// Prefer the highest-priority, platform-compatible wheel.
|
||||||
|
@ -241,16 +259,23 @@ impl PrioritizedDistribution {
|
||||||
requires_python,
|
requires_python,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
self.hashes.push(hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert the given source distribution into the [`PrioritizedDistribution`].
|
/// Insert the given source distribution into the [`PrioritizedDistribution`].
|
||||||
fn insert_source(&mut self, dist: Dist, requires_python: Option<VersionSpecifiers>) {
|
fn insert_source(
|
||||||
|
&mut self,
|
||||||
|
dist: Dist,
|
||||||
|
requires_python: Option<VersionSpecifiers>,
|
||||||
|
hash: Hashes,
|
||||||
|
) {
|
||||||
if self.source.is_none() {
|
if self.source.is_none() {
|
||||||
self.source = Some(DistRequiresPython {
|
self.source = Some(DistRequiresPython {
|
||||||
dist,
|
dist,
|
||||||
requires_python,
|
requires_python,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
self.hashes.push(hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the highest-priority distribution for the package version, if any.
|
/// Return the highest-priority distribution for the package version, if any.
|
||||||
|
|
|
@ -18,7 +18,8 @@ use puffin_cache::Cache;
|
||||||
use puffin_client::RegistryClientBuilder;
|
use puffin_client::RegistryClientBuilder;
|
||||||
use puffin_interpreter::{Interpreter, Virtualenv};
|
use puffin_interpreter::{Interpreter, Virtualenv};
|
||||||
use puffin_resolver::{
|
use puffin_resolver::{
|
||||||
Manifest, PreReleaseMode, ResolutionGraph, ResolutionMode, ResolutionOptions, Resolver,
|
DisplayResolutionGraph, Manifest, PreReleaseMode, ResolutionGraph, ResolutionMode,
|
||||||
|
ResolutionOptions, Resolver,
|
||||||
};
|
};
|
||||||
use puffin_traits::{BuildContext, BuildKind, SetupPyStrategy, SourceBuildTrait};
|
use puffin_traits::{BuildContext, BuildKind, SetupPyStrategy, SourceBuildTrait};
|
||||||
|
|
||||||
|
@ -140,7 +141,7 @@ async fn black() -> Result<()> {
|
||||||
|
|
||||||
let resolution = resolve(manifest, options, &MARKERS_311, &TAGS_311).await?;
|
let resolution = resolve(manifest, options, &MARKERS_311, &TAGS_311).await?;
|
||||||
|
|
||||||
assert_snapshot!(resolution, @r###"
|
assert_snapshot!(DisplayResolutionGraph::from(&resolution), @r###"
|
||||||
black==23.9.1
|
black==23.9.1
|
||||||
click==8.1.7
|
click==8.1.7
|
||||||
# via black
|
# via black
|
||||||
|
@ -170,7 +171,7 @@ async fn black_colorama() -> Result<()> {
|
||||||
|
|
||||||
let resolution = resolve(manifest, options, &MARKERS_311, &TAGS_311).await?;
|
let resolution = resolve(manifest, options, &MARKERS_311, &TAGS_311).await?;
|
||||||
|
|
||||||
assert_snapshot!(resolution, @r###"
|
assert_snapshot!(DisplayResolutionGraph::from(&resolution), @r###"
|
||||||
black==23.9.1
|
black==23.9.1
|
||||||
click==8.1.7
|
click==8.1.7
|
||||||
# via black
|
# via black
|
||||||
|
@ -202,7 +203,7 @@ async fn black_tensorboard() -> Result<()> {
|
||||||
|
|
||||||
let resolution = resolve(manifest, options, &MARKERS_311, &TAGS_311).await?;
|
let resolution = resolve(manifest, options, &MARKERS_311, &TAGS_311).await?;
|
||||||
|
|
||||||
assert_snapshot!(resolution, @r###"
|
assert_snapshot!(DisplayResolutionGraph::from(&resolution), @r###"
|
||||||
black==23.9.1
|
black==23.9.1
|
||||||
click==8.1.7
|
click==8.1.7
|
||||||
# via black
|
# via black
|
||||||
|
@ -230,7 +231,7 @@ async fn black_python_310() -> Result<()> {
|
||||||
|
|
||||||
let resolution = resolve(manifest, options, &MARKERS_310, &TAGS_310).await?;
|
let resolution = resolve(manifest, options, &MARKERS_310, &TAGS_310).await?;
|
||||||
|
|
||||||
assert_snapshot!(resolution, @r###"
|
assert_snapshot!(DisplayResolutionGraph::from(&resolution), @r###"
|
||||||
black==23.9.1
|
black==23.9.1
|
||||||
click==8.1.7
|
click==8.1.7
|
||||||
# via black
|
# via black
|
||||||
|
@ -271,7 +272,7 @@ async fn black_mypy_extensions() -> Result<()> {
|
||||||
|
|
||||||
let resolution = resolve(manifest, options, &MARKERS_311, &TAGS_311).await?;
|
let resolution = resolve(manifest, options, &MARKERS_311, &TAGS_311).await?;
|
||||||
|
|
||||||
assert_snapshot!(resolution, @r###"
|
assert_snapshot!(DisplayResolutionGraph::from(&resolution), @r###"
|
||||||
black==23.9.1
|
black==23.9.1
|
||||||
click==8.1.7
|
click==8.1.7
|
||||||
# via black
|
# via black
|
||||||
|
@ -308,7 +309,7 @@ async fn black_mypy_extensions_extra() -> Result<()> {
|
||||||
|
|
||||||
let resolution = resolve(manifest, options, &MARKERS_311, &TAGS_311).await?;
|
let resolution = resolve(manifest, options, &MARKERS_311, &TAGS_311).await?;
|
||||||
|
|
||||||
assert_snapshot!(resolution, @r###"
|
assert_snapshot!(DisplayResolutionGraph::from(&resolution), @r###"
|
||||||
black==23.9.1
|
black==23.9.1
|
||||||
click==8.1.7
|
click==8.1.7
|
||||||
# via black
|
# via black
|
||||||
|
@ -345,7 +346,7 @@ async fn black_flake8() -> Result<()> {
|
||||||
|
|
||||||
let resolution = resolve(manifest, options, &MARKERS_311, &TAGS_311).await?;
|
let resolution = resolve(manifest, options, &MARKERS_311, &TAGS_311).await?;
|
||||||
|
|
||||||
assert_snapshot!(resolution, @r###"
|
assert_snapshot!(DisplayResolutionGraph::from(&resolution), @r###"
|
||||||
black==23.9.1
|
black==23.9.1
|
||||||
click==8.1.7
|
click==8.1.7
|
||||||
# via black
|
# via black
|
||||||
|
@ -373,7 +374,7 @@ async fn black_lowest() -> Result<()> {
|
||||||
|
|
||||||
let resolution = resolve(manifest, options, &MARKERS_311, &TAGS_311).await?;
|
let resolution = resolve(manifest, options, &MARKERS_311, &TAGS_311).await?;
|
||||||
|
|
||||||
assert_snapshot!(resolution, @r###"
|
assert_snapshot!(DisplayResolutionGraph::from(&resolution), @r###"
|
||||||
black==22.1.0
|
black==22.1.0
|
||||||
click==8.0.0
|
click==8.0.0
|
||||||
# via black
|
# via black
|
||||||
|
@ -401,7 +402,7 @@ async fn black_lowest_direct() -> Result<()> {
|
||||||
|
|
||||||
let resolution = resolve(manifest, options, &MARKERS_311, &TAGS_311).await?;
|
let resolution = resolve(manifest, options, &MARKERS_311, &TAGS_311).await?;
|
||||||
|
|
||||||
assert_snapshot!(resolution, @r###"
|
assert_snapshot!(DisplayResolutionGraph::from(&resolution), @r###"
|
||||||
black==22.1.0
|
black==22.1.0
|
||||||
click==8.1.7
|
click==8.1.7
|
||||||
# via black
|
# via black
|
||||||
|
@ -436,7 +437,7 @@ async fn black_respect_preference() -> Result<()> {
|
||||||
|
|
||||||
let resolution = resolve(manifest, options, &MARKERS_311, &TAGS_311).await?;
|
let resolution = resolve(manifest, options, &MARKERS_311, &TAGS_311).await?;
|
||||||
|
|
||||||
assert_snapshot!(resolution, @r###"
|
assert_snapshot!(DisplayResolutionGraph::from(&resolution), @r###"
|
||||||
black==23.9.0
|
black==23.9.0
|
||||||
click==8.1.7
|
click==8.1.7
|
||||||
# via black
|
# via black
|
||||||
|
@ -471,7 +472,7 @@ async fn black_ignore_preference() -> Result<()> {
|
||||||
|
|
||||||
let resolution = resolve(manifest, options, &MARKERS_311, &TAGS_311).await?;
|
let resolution = resolve(manifest, options, &MARKERS_311, &TAGS_311).await?;
|
||||||
|
|
||||||
assert_snapshot!(resolution, @r###"
|
assert_snapshot!(DisplayResolutionGraph::from(&resolution), @r###"
|
||||||
black==23.9.1
|
black==23.9.1
|
||||||
click==8.1.7
|
click==8.1.7
|
||||||
# via black
|
# via black
|
||||||
|
@ -543,7 +544,7 @@ async fn pylint_disallow_prerelease() -> Result<()> {
|
||||||
|
|
||||||
let resolution = resolve(manifest, options, &MARKERS_311, &TAGS_311).await?;
|
let resolution = resolve(manifest, options, &MARKERS_311, &TAGS_311).await?;
|
||||||
|
|
||||||
assert_snapshot!(resolution, @r###"
|
assert_snapshot!(DisplayResolutionGraph::from(&resolution), @r###"
|
||||||
astroid==3.0.1
|
astroid==3.0.1
|
||||||
# via pylint
|
# via pylint
|
||||||
isort==5.12.0
|
isort==5.12.0
|
||||||
|
@ -567,7 +568,7 @@ async fn pylint_allow_prerelease() -> Result<()> {
|
||||||
|
|
||||||
let resolution = resolve(manifest, options, &MARKERS_311, &TAGS_311).await?;
|
let resolution = resolve(manifest, options, &MARKERS_311, &TAGS_311).await?;
|
||||||
|
|
||||||
assert_snapshot!(resolution, @r###"
|
assert_snapshot!(DisplayResolutionGraph::from(&resolution), @r###"
|
||||||
astroid==3.0.1
|
astroid==3.0.1
|
||||||
# via pylint
|
# via pylint
|
||||||
isort==6.0.0b2
|
isort==6.0.0b2
|
||||||
|
@ -594,7 +595,7 @@ async fn pylint_allow_explicit_prerelease_without_marker() -> Result<()> {
|
||||||
|
|
||||||
let resolution = resolve(manifest, options, &MARKERS_311, &TAGS_311).await?;
|
let resolution = resolve(manifest, options, &MARKERS_311, &TAGS_311).await?;
|
||||||
|
|
||||||
assert_snapshot!(resolution, @r###"
|
assert_snapshot!(DisplayResolutionGraph::from(&resolution), @r###"
|
||||||
astroid==3.0.1
|
astroid==3.0.1
|
||||||
# via pylint
|
# via pylint
|
||||||
isort==5.12.0
|
isort==5.12.0
|
||||||
|
@ -621,7 +622,7 @@ async fn pylint_allow_explicit_prerelease_with_marker() -> Result<()> {
|
||||||
|
|
||||||
let resolution = resolve(manifest, options, &MARKERS_311, &TAGS_311).await?;
|
let resolution = resolve(manifest, options, &MARKERS_311, &TAGS_311).await?;
|
||||||
|
|
||||||
assert_snapshot!(resolution, @r###"
|
assert_snapshot!(DisplayResolutionGraph::from(&resolution), @r###"
|
||||||
astroid==3.0.1
|
astroid==3.0.1
|
||||||
# via pylint
|
# via pylint
|
||||||
isort==6.0.0b2
|
isort==6.0.0b2
|
||||||
|
|
|
@ -85,8 +85,14 @@ impl Yanked {
|
||||||
///
|
///
|
||||||
/// PEP 691 says multiple hashes can be included and the interpretation is left to the client, we
|
/// PEP 691 says multiple hashes can be included and the interpretation is left to the client, we
|
||||||
/// only support SHA 256 atm.
|
/// only support SHA 256 atm.
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
||||||
pub struct Hashes {
|
pub struct Hashes {
|
||||||
// TODO(charlie): Hashes should be optional.
|
// TODO(charlie): Hashes should be optional.
|
||||||
pub sha256: String,
|
pub sha256: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Hashes {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "sha256:{}", self.sha256)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue