Skip to content

Commit

Permalink
vendor: bump pyproject.nix
Browse files Browse the repository at this point in the history
  • Loading branch information
adisbladis committed Oct 26, 2023
1 parent 40192db commit 15185ad
Show file tree
Hide file tree
Showing 23 changed files with 396 additions and 97 deletions.
10 changes: 5 additions & 5 deletions default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
let
inherit (poetryLib) isCompatible readTOML;

pyproject-nix = import ./vendor/pyproject.nix { inherit lib; };
pyproject-nix = import ./vendor/pyproject.nix { inherit pkgs lib; };

# Name normalization
inherit (pyproject-nix.pypa) normalizePackageName;
inherit (pyproject-nix.lib.pypa) normalizePackageName;
normalizePackageSet = lib.attrsets.mapAttrs' (name: value: lib.attrsets.nameValuePair (normalizePackageName name) value);

# Map SPDX identifiers to license names
Expand Down Expand Up @@ -172,7 +172,7 @@ lib.makeScope pkgs.newScope (self: {
in
lib.listToAttrs (lib.mapAttrsToList (n: v: { name = normalizePackageName n; value = v; }) lockfiles);

pep508Env = pyproject-nix.pep508.mkEnviron python;
pep508Env = pyproject-nix.lib.pep508.mkEnviron python;

# Filter packages by their PEP508 markers & pyproject interpreter version
partitions =
Expand All @@ -181,9 +181,9 @@ lib.makeScope pkgs.newScope (self: {
if pkgMeta ? marker then
(
let
marker = pyproject-nix.pep508.parseMarkers pkgMeta.marker;
marker = pyproject-nix.lib.pep508.parseMarkers pkgMeta.marker;
in
pyproject-nix.pep508.evalMarkers pep508Env marker
pyproject-nix.lib.pep508.evalMarkers pep508Env marker
) else true && isCompatible (poetryLib.getPythonVersion python) pkgMeta.python-versions;
in
lib.partition supportsPythonVersion poetryLock.package;
Expand Down
2 changes: 1 addition & 1 deletion editable.nix
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
, pyproject-nix
}:
let
name = pyproject-nix.pypa.normalizePackageName pyProject.tool.poetry.name;
name = pyproject-nix.lib.pypa.normalizePackageName pyProject.tool.poetry.name;

# Just enough standard PKG-INFO fields for an editable installation
pkgInfoFields = {
Expand Down
6 changes: 3 additions & 3 deletions mk-poetry-dep.nix
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ pythonPackages.callPackage
}@args:
let
inherit (python) stdenv;
inherit (pyproject-nix.pypa) normalizePackageName;
inherit (pyproject-nix.lib.pypa) normalizePackageName;
inherit (poetryLib) isCompatible getManyLinuxDeps fetchFromLegacy fetchFromPypi;

inherit (import ./pep425.nix {
Expand Down Expand Up @@ -145,15 +145,15 @@ pythonPackages.callPackage
pep508Markers = v.markers or "";
in
compat constraints && (if pep508Markers == "" then true else
(pyproject-nix.pep508.evalMarkers
(pyproject-nix.lib.pep508.evalMarkers
(pep508Env // {
extra = {
# All extras are always enabled
type = "extra";
value = lib.attrNames extras;
};
})
(pyproject-nix.pep508.parseMarkers pep508Markers)))
(pyproject-nix.lib.pep508.parseMarkers pep508Markers)))
)
dependencies
);
Expand Down
2 changes: 1 addition & 1 deletion pep425.nix
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{ lib, stdenv, python, pyproject-nix, isLinux ? stdenv.isLinux }:
let
inherit (lib.strings) escapeRegex hasPrefix hasSuffix hasInfix splitString removeSuffix;
targetMachine = pyproject-nix.pep599.manyLinuxTargetMachines.${stdenv.targetPlatform.parsed.cpu.name};
targetMachine = pyproject-nix.lib.pep599.manyLinuxTargetMachines.${stdenv.targetPlatform.parsed.cpu.name};

pythonVer =
let
Expand Down
27 changes: 5 additions & 22 deletions vendor/pyproject.nix/default.nix
Original file line number Diff line number Diff line change
@@ -1,22 +1,5 @@
{ lib }:
let
inherit (builtins) mapAttrs;
inherit (lib) fix;
in

fix (self: mapAttrs (_: path: import path ({ inherit lib; } // self)) {
pip = ./pip.nix;
pypa = ./pypa.nix;
project = ./project.nix;
renderers = ./renderers.nix;
validators = ./validators.nix;
poetry = ./poetry.nix;

pep427 = ./pep427.nix;
pep440 = ./pep440.nix;
pep508 = ./pep508.nix;
pep518 = ./pep518.nix;
pep599 = ./pep599.nix;
pep600 = ./pep600.nix;
pep621 = ./pep621.nix;
})
{ pkgs, lib }:
{
lib = import ./lib { inherit lib; };
fetchers = import ./fetchers { inherit pkgs lib; };
}
122 changes: 122 additions & 0 deletions vendor/pyproject.nix/fetchers/default.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
{ pkgs
, lib
,
}:
let
inherit (builtins) substring filter head nixPath;
inherit (lib) toLower;

# Predict URL from the PyPI index.
# Args:
# pname: package name
# file: filename including extension
# hash: SRI hash
# kind: Language implementation and version tag
predictURLFromPypi =
{
# package name
pname
, # filename including extension
file
, # Language implementation and version tag
kind
,
}: "https://files.pythonhosted.org/packages/${kind}/${toLower (substring 0 1 file)}/${pname}/${file}";
in
lib.mapAttrs (_: func: lib.makeOverridable func) {
/*
Fetch from the PyPI index.
At first we try to fetch the predicated URL but if that fails we
will use the Pypi API to determine the correct URL.
Type: fetchFromPypi :: AttrSet -> derivation
*/
fetchFromPypi =
{
# package name
pname
, # filename including extension
file
, # the version string of the dependency
version
, # SRI hash
hash
, # Language implementation and version tag
kind
, # Options to pass to `curl`
curlOpts ? ""
,
}:
let
predictedURL = predictURLFromPypi { inherit pname file kind; };
in
pkgs.stdenvNoCC.mkDerivation {
name = file;
nativeBuildInputs = [
pkgs.buildPackages.curl
pkgs.buildPackages.jq
];
isWheel = lib.strings.hasSuffix "whl" file;
system = "builtin";

preferLocalBuild = true;
impureEnvVars =
lib.fetchers.proxyImpureEnvVars
++ [
"NIX_CURL_FLAGS"
];

inherit pname file version curlOpts predictedURL;

builder = ./fetch-from-pypi.sh;

outputHashMode = "flat";
outputHashAlgo = "sha256";
outputHash = hash;

passthru = {
urls = [ predictedURL ]; # retain compatibility with nixpkgs' fetchurl
};
};

/*
Fetch from the PyPI legacy API.
Some repositories (such as Devpi) expose the Pypi legacy API (https://warehouse.pypa.io/api-reference/legacy.html).
Type: fetchFromLegacy :: AttrSet -> derivation
*/
fetchFromLegacy =
{
# package name
pname
, # URL to package index
url
, # filename including extension
file
, # SRI hash
hash
,
}:
let
pathParts = filter ({ prefix, path }: "NETRC" == prefix) nixPath; # deadnix: skip
netrc_file =
if (pathParts != [ ])
then (head pathParts).path
else "";
in
pkgs.runCommand file
{
nativeBuildInputs = [ pkgs.buildPackages.python3 ];
impureEnvVars = lib.fetchers.proxyImpureEnvVars;
outputHashMode = "flat";
outputHashAlgo = "sha256";
outputHash = hash;
NETRC = netrc_file;
passthru.isWheel = lib.strings.hasSuffix "whl" file;
} ''
python ${./fetch-from-legacy.py} ${url} ${pname} ${file}
mv ${file} $out
'';
}
137 changes: 137 additions & 0 deletions vendor/pyproject.nix/fetchers/fetch-from-legacy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# Some repositories (such as Devpi) expose the Pypi legacy API
# (https://warehouse.pypa.io/api-reference/legacy.html).
#
# Note it is not possible to use pip
# https://discuss.python.org/t/pip-download-just-the-source-packages-no-building-no-metadata-etc/4651/12

import netrc
import os
import shutil
import ssl
import sys
import urllib.request
from html.parser import HTMLParser
from os.path import normpath
from typing import Optional
from urllib.parse import urlparse, urlunparse


# Parse the legacy index page to extract the href and package names
class Pep503(HTMLParser):
def __init__(self) -> None:
super().__init__()
self.sources: dict[str, str] = {}
self.url: Optional[str] = None
self.name: Optional[str] = None

def handle_data(self, data: str) -> None:
if self.url is not None:
self.name = data

def handle_starttag(self, tag: str, attrs: list[tuple[str, Optional[str]]]) -> None:
if tag == "a":
for name, value in attrs:
if name == "href":
self.url = value

def handle_endtag(self, tag: str) -> None:
if self.url is not None:
if not self.name:
raise ValueError("Name not set")

self.sources[self.name] = self.url
self.url = None


url = sys.argv[1]
package_name = sys.argv[2]
index_url = url + "/" + package_name + "/"
package_filename = sys.argv[3]

# Parse username and password for this host from the netrc file if given.
username: Optional[str] = None
password: Optional[str] = None
if os.environ["NETRC"]:
netrc_obj = netrc.netrc(os.environ["NETRC"])
host = urlparse(index_url).netloc
# Strip port number if present
if ":" in host:
host = host.split(":")[0]
authenticators = netrc_obj.authenticators(host)
if authenticators:
username, _, password = authenticators

print("Reading index %s" % index_url)

context = ssl.create_default_context()
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE

# Extract out username/password from index_url, if present.
parsed_url = urlparse(index_url)
username = parsed_url.username or username
password = parsed_url.password or password
index_url = parsed_url._replace(netloc=parsed_url.netloc.rpartition("@")[-1]).geturl()

req = urllib.request.Request(index_url)
if username and password:
import base64

password_b64 = base64.b64encode(":".join((username, password)).encode()).decode("utf-8")
req.add_header("Authorization", "Basic {}".format(password_b64))
response = urllib.request.urlopen(req, context=context)
index = response.read()

parser = Pep503()
parser.feed(str(index, "utf-8"))
if package_filename not in parser.sources:
print("The file %s has not be found in the index %s" % (package_filename, index_url))
exit(1)

package_file = open(package_filename, "wb")
# Sometimes the href is a relative or absolute path within the index's domain.
indicated_url = urlparse(parser.sources[package_filename])
if indicated_url.netloc == "":
parsed_url = urlparse(index_url)

if indicated_url.path.startswith("/"):
# An absolute path within the index's domain.
path = parser.sources[package_filename]
else:
# A relative path.
path = parsed_url.path + "/" + parser.sources[package_filename]

package_url = urlunparse(
(
parsed_url.scheme,
parsed_url.netloc,
path,
None,
None,
None,
)
)
else:
package_url = parser.sources[package_filename]

# Handle urls containing "../"
parsed_url = urlparse(package_url)
real_package_url = urlunparse(
(
parsed_url.scheme,
parsed_url.netloc,
normpath(parsed_url.path),
parsed_url.params,
parsed_url.query,
parsed_url.fragment,
)
)
print("Downloading %s" % real_package_url)

req = urllib.request.Request(real_package_url)
if username and password:
req.add_unredirected_header("Authorization", "Basic {}".format(password_b64))
response = urllib.request.urlopen(req, context=context)

with response as r:
shutil.copyfileobj(r, package_file)
27 changes: 27 additions & 0 deletions vendor/pyproject.nix/fetchers/fetch-from-pypi.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/usr/bin/env bash

# shellcheck disable=SC1091,SC2154
source "$stdenv/setup"
set -euo pipefail

curl="curl \
--location \
--max-redirs 20 \
--retry 2 \
--disable-epsv \
--cookie-jar cookies \
--insecure \
--speed-time 5 \
--progress-bar \
--fail \
$curlOpts \
$NIX_CURL_FLAGS"

echo "Trying to fetch with predicted URL: $predictedURL"

$curl "$predictedURL" --output "$out" && exit 0

echo "Predicted URL '$predictedURL' failed, querying pypi.org"
$curl "https://pypi.org/pypi/$pname/json" | jq -r ".releases.\"$version\"[] | select(.filename == \"$file\") | .url" > url
url=$(cat url)
$curl "$url" --output "$out"
Loading

0 comments on commit 15185ad

Please sign in to comment.