aboutsummaryrefslogtreecommitdiff
path: root/pypi/sdist-store/import.scm
diff options
context:
space:
mode:
Diffstat (limited to 'pypi/sdist-store/import.scm')
-rw-r--r--pypi/sdist-store/import.scm720
1 files changed, 720 insertions, 0 deletions
diff --git a/pypi/sdist-store/import.scm b/pypi/sdist-store/import.scm
new file mode 100644
index 0000000..b00afd0
--- /dev/null
+++ b/pypi/sdist-store/import.scm
@@ -0,0 +1,720 @@
+(define-module (pypi sdist-store import)
+ #:use-module (pyguile)
+ #:use-module (logging logger)
+ #:use-module (rnrs io ports)
+ #:use-module (ice-9 binary-ports)
+ #:use-module (ice-9 match)
+ #:use-module (ice-9 pretty-print)
+ #:use-module (ice-9 vlist)
+ #:use-module (ice-9 ftw)
+ #:use-module (ice-9 regex)
+ #:use-module ((ice-9 rdelim) #:select (read-line))
+ #:use-module (srfi srfi-1)
+ #:use-module (srfi srfi-9)
+ #:use-module (srfi srfi-26)
+ #:use-module (srfi srfi-34)
+ #:use-module (srfi srfi-35)
+ #:use-module (rnrs bytevectors)
+ #:use-module (json)
+ #:use-module (web uri)
+ #:use-module (guix ui)
+ #:use-module (guix utils)
+ #:use-module (guix records)
+ #:use-module (guix store)
+ #:use-module (guix monads)
+ #:use-module (guix import utils)
+ #:use-module ((guix download) #:prefix download:)
+ #:use-module (guix import json)
+ #:use-module (guix packages)
+ #:use-module (guix upstream)
+ #:use-module (guix licenses)
+ #:use-module (guix build-system python)
+ #:use-module ((guix derivations) #:select (derivation-file-name
+ built-derivations
+ derivation->output-path))
+ #:use-module (pypi package)
+ #:use-module (pypi requirement)
+ #:use-module (pypi api)
+ #:use-module (pypi sdist)
+ #:use-module (pypi sdist-store)
+ #:use-module (pypi requirement)
+ #:use-module (pypi dependency-solver)
+ #:use-module (pypi build-configuration-graph)
+ #:use-module (pypi sdist-store utils)
+ #:use-module (gnu packages python)
+ #:export (process-import
+
+ recursive-package-inputs
+
+ import-state
+ get-import-state-status
+ get-import-state-sdist-store
+ get-import-state-packages
+
+ import-context
+ make-import-context
+ get-import-context-python
+ get-import-context-fix-function
+ get-import-context-pypi-api-root
+
+ steps
+
+ good-status
+ good-status?
+ bad-status
+ bad-status?
+ get-bad-status-reason))
+
+(define-record-type* <import-state>
+ import-state make-import-state
+ import-state?
+ (packages get-import-state-packages)
+ (sdist-store get-import-state-sdist-store)
+ (remaining-steps get-import-state-remaining-steps)
+ (status get-import-state-status))
+
+(define-record-type* <import-context>
+ import-context make-import-context
+ import-context?
+ (python get-import-context-python)
+ (pypi-api-root get-import-context-pypi-api-root)
+ (fix-function get-import-context-fix-function))
+
+(define good-status 'good-status)
+
+(define (good-status? s) (eq? good-status s))
+
+(define-record-type <bad-status>
+ (bad-status reason)
+ bad-status?
+ (reason get-bad-status-reason))
+
+(define (check-packages-for-duplicates packages)
+ (if (not (=
+ (length packages)
+ (length (delete-duplicates packages equal?))))
+ (error "packages contains duplicate entries" packages)))
+
+(define (process-import import-context import-state intermediate-result-callback)
+ (check-packages-for-duplicates
+ (get-import-state-packages import-state))
+ (let
+ ((remaining-steps (get-import-state-remaining-steps import-state)))
+ (if (null? remaining-steps)
+ import-state
+ (let
+ ((next-step (car remaining-steps)))
+ (let*
+ ((new-state (next-step import-context import-state))
+ (new-status (get-import-state-status new-state)))
+ (intermediate-result-callback (get-import-state-sdist-store new-state))
+ (cond
+ ((good-status? new-status)
+ (process-import import-context new-state intermediate-result-callback))
+ ((bad-status? new-status)
+ new-state)
+ (else
+ (error "Unknown import status" new-status))))))))
+
+(define (create-initial-sdist-record-from-sdist name version import-context)
+ (ensure-unpacked-sdist-exists
+ name version (get-import-context-pypi-api-root import-context))
+ (let
+ ((requires (parse-requires name version)))
+ (sdist
+ (info (get-pkg-info name version))
+ (build-requires (get-sdist-build-requires name version))
+ (tests-require (get-sdist-tests-require name version))
+ (install-requires (get-sdist-install-requires requires))
+ (extras-require (get-sdist-extras-require requires))
+ (source (get-sdist-origin name version import-context)))))
+
+(define (create-initial-sdists import-context state)
+ "create-initial-sdists"
+ (log-msg 'DEBUG "create-initial-sdists")
+ (log-msg 'DEBUG (vlist->list (get-import-state-sdist-store state)))
+ (import-state
+ (inherit state)
+ (remaining-steps (cdr (get-import-state-remaining-steps state)))
+ (sdist-store
+ (let ((previous-store
+ (get-import-state-sdist-store state)))
+ (add-sdists
+ previous-store
+ (map
+ (match-lambda
+ ((name . version)
+ (create-initial-sdist-record-from-sdist name version import-context)))
+ (filter
+ (match-lambda
+ ((name . version)
+ (log-msg 'DEBUG "Checking that " name "@" version " is not already in the store")
+ (log-msg 'DEBUG (not (get-sdist previous-store name version)))
+ (not (get-sdist previous-store name version))))
+ (get-import-state-packages state))))))))
+
+(define (get-package-processor f)
+ (letrec
+ ((fn
+ (lambda (import-context state)
+ (check-steps state fn)
+ (letrec
+ ((processor
+ (lambda (import-context state packages)
+ (if (null? packages)
+ (import-state
+ (inherit state)
+ (remaining-steps (cdr (get-import-state-remaining-steps state))))
+ (let*
+ ((pkg (car packages))
+ (name (car pkg))
+ (version (cdr pkg))
+ (new-import-state
+ (f import-context state name version))
+ (new-status (get-import-state-status new-import-state)))
+ (cond
+ ((good-status? new-status)
+ (processor import-context new-import-state (cdr packages)))
+ ((bad-status? new-status)
+ new-import-state)
+ (else
+ (error "Unknown import status" new-status))))))))
+ (processor
+ import-context
+ state
+ (get-import-state-packages state))))))
+ fn))
+
+(define build-package-to-determine-build-requirements
+ (get-package-processor
+ (lambda (import-context import-state name version)
+ (log-msg 'INFO "Building " name "@" version " without tests")
+ (build-package-to-determine-requirements
+ import-context
+ import-state
+ name
+ version
+ #f
+ '()))))
+
+(define build-package-to-determine-test-requirements
+ (get-package-processor
+ (lambda (import-context import-state name version)
+ (log-msg 'INFO "Building " name "@" version " with tests")
+ (build-package-to-determine-requirements
+ import-context
+ import-state
+ name
+ version
+ #t
+ '()))))
+
+(define steps (list
+ create-initial-sdists
+ build-package-to-determine-build-requirements
+ build-package-to-determine-test-requirements))
+
+(define (get-sdist-coresponding-to-package store pkg)
+ (let*
+ ((sd (assoc-ref (package-properties pkg) 'sdist))
+ (pkginfo (sdist-info sd))
+ (name (pkg-info-name pkginfo))
+ (version (pkg-info-version pkginfo)))
+ (get-sdist
+ store
+ name
+ version)))
+
+(define (get-build-configuration-from-package pkg)
+ (let
+ ((bc
+ (assoc-ref (package-properties pkg) 'build-configuration)))
+ (if (not (build-configuration? bc))
+ (begin
+ (log-msg 'ERROR "get-build-configuration-from-package is not a build configuration"
+ bc)
+ (error)))
+ bc))
+
+(define (build-and-report-log-on-failures pkg)
+ (letrec*
+ ((inputs-plus-pkg
+ (reverse (cons pkg (reverse (recursive-package-inputs pkg)))))
+ (build-all
+ (lambda (remaining-packages)
+ (if (null? remaining-packages)
+ #t
+ (let
+ ((pkg (car remaining-packages)))
+ (let
+ ((result (build-and-return-log-on-failure pkg)))
+ (if (eq? result #t)
+ (build-all (cdr remaining-packages))
+ result)))))))
+ (build-all inputs-plus-pkg)))
+
+(define (check-steps state current-step)
+ (if
+ (not (eq? (car (get-import-state-remaining-steps state)) current-step))
+ (error "Actual step " current-step " does not equal " (car (get-import-state-remaining-steps state)))))
+
+(define (build-package-to-determine-requirements
+ import-context state name version run-tests extras)
+ (let*
+ ((sdist-store
+ (get-import-state-sdist-store state))
+ (sd
+ (get-sdist
+ sdist-store
+ name
+ version))
+ (build-configuration-graph-or-error
+ (catch
+ unsatisfied-requirements-error
+ (lambda ()
+ (requirements->build-configuration-graph
+ sdist-store
+ (list
+ (requirement
+ (name name)
+ (specifiers
+ (string-append "==" version))
+ (extras extras)
+ (run-tests run-tests)))))
+ (lambda (err unsatisfied-requirements)
+ (unsatisfiable-requirements unsatisfied-requirements)))))
+ (if (not (build-configuration-graph? build-configuration-graph-or-error))
+ (import-state
+ (inherit state)
+ (status (bad-status
+ build-configuration-graph-or-error)))
+ (let*
+ ((build-configuration-graph
+ build-configuration-graph-or-error)
+ (requirement-options-to-versions
+ (solve-build-configuration-graph
+ build-configuration-graph))
+ (pkg-or-error
+ (catch
+ unsatisfied-requirements-error
+ (lambda ()
+ (first
+ (build-configuration-graph->packages
+ build-configuration-graph
+ (package-configuration
+ (get-import-context-python import-context)
+ (get-import-context-fix-function import-context))
+ requirement-options-to-versions)))
+ (lambda (err unsatisfied-requirements)
+ (unsatisfiable-requirements unsatisfied-requirements)))))
+ (if (not (package? pkg-or-error))
+ (import-state
+ (inherit state)
+ (status (bad-status
+ pkg-or-error)))
+ (begin
+ (log-msg 'DEBUG "sdist information " (sdist-short-description sd))
+ (log-msg 'DEBUG "build-requires " (sdist-build-requires sd))
+ (log-msg 'DEBUG "test-requires " (sdist-tests-require sd))
+ (log-msg 'DEBUG "install-requires " (sdist-install-requires sd))
+ (log-msg 'DEBUG "dependencies for " (package-name pkg-or-error))
+ (log-msg 'DEBUG "native-inputs " (map car (package-native-inputs pkg-or-error)))
+ (log-msg 'DEBUG "inputs " (map car (package-inputs pkg-or-error)))
+ (log-msg 'DEBUG "propagated-inputs " (map car (package-propagated-inputs pkg-or-error)))
+ (let*
+ ((build-result (build-and-report-log-on-failures pkg-or-error)))
+ (if (eq? build-result #t)
+ state
+ (let*
+ ((pkg (car build-result))
+ (log-contents (cdr build-result))
+ (parsed-result (parse-build-log log-contents))
+ (sdist (get-sdist-coresponding-to-package
+ sdist-store
+ pkg)))
+ (if (eq? parsed-result #f)
+ (begin
+ (display "\n")
+ (display log-contents)
+ (display "\n")
+ (log-msg 'ERROR "Unable to determine build problem")
+ (error)))
+ (log-msg 'INFO "Finished building " (package-name pkg) " " (package-version pkg))
+ (log-msg 'INFO "Missing requirements: " parsed-result)
+ (if
+ (not
+ (member
+ (normalise-requirement-name
+ (pkg-info-name (sdist-info sdist)))
+ (map (match-lambda
+ ((name . version)
+ (normalise-requirement-name name)))
+ (get-import-state-packages state))))
+ (begin
+ (display "\n")
+ (display log-contents)
+ (display "\n")
+ (log-msg 'ERROR
+ "Build problem in package outside of packages that this worker is trying to build:\n"
+ " packages: " (get-import-state-packages state) "\n"
+ " build failed for: " (normalise-requirement-name (pkg-info-name (sdist-info sdist))) "\n")
+ (error)))
+ (let*
+ ((missing-requirement parsed-result))
+ (log-msg 'DEBUG "missing requirement " missing-requirement)
+ (if (member
+ (requirement-name missing-requirement)
+ (build-configuration-omitted-dependencies
+ (get-build-configuration-from-package pkg)))
+ (begin
+ (display "\n")
+ (display log-contents)
+ (display "\n")
+ (log-build-configuration-graph
+ '() build-configuration-graph)
+ (log-msg 'ERROR "Build configuration: "
+ (get-build-configuration-from-package pkg))
+ (error "Package has failed to build due to omitted dependency")))
+ (if (store-can-satisfy-requirement? sdist-store missing-requirement)
+ (begin
+ (log-msg 'DEBUG "missing requirement is in the store")
+ (if (member
+ missing-requirement
+ (if (package-has-tests-enabled pkg)
+ (sdist-tests-require sdist)
+ (sdist-build-requires sdist)))
+ (begin
+ (display "\n")
+ (display log-contents)
+ (display "\n")
+ (log-msg 'ERROR "Problem building package")
+ (log-build-configuration-graph
+ '() build-configuration-graph)
+ (log-msg 'ERROR "Build configuration: "
+ (get-build-configuration-from-package pkg))
+ (error "Cannot add duplicate requirement " missing-requirement)))
+ (let
+ ((new-state
+ (import-state
+ (inherit state)
+ (sdist-store
+ (add-sdist
+ sdist-store
+ ((if (package-has-tests-enabled pkg)
+ add-test-requirements-to-sdist
+ add-build-requirements-to-sdist)
+ sdist
+ (list missing-requirement)))))))
+ (log-msg 'DEBUG "about to call build-package-to-determine-requirements")
+ (log-msg 'DEBUG state)
+ (log-msg 'DEBUG new-state)
+ (build-package-to-determine-requirements
+ import-context
+ new-state
+ name
+ version
+ run-tests
+ extras)))
+ ; Store can't satisfy requirements, so report failure
+ (begin
+ (log-msg 'DEBUG "missing requirement is not in the store")
+ (import-state
+ (inherit state)
+ (status
+ (bad-status
+ (unsatisfiable-requirements
+ (list missing-requirement)))))))))))))))))
+
+(define (readable-package p)
+ (string-append (package-name p) "@" (package-version p)))
+
+(define (check-and-fix-dependency-ordering package-and-dependencies)
+ (if (null? package-and-dependencies)
+ '()
+ (let*
+ ((first-pair (car package-and-dependencies))
+ (pkg (car first-pair))
+ (dependencies (cdr first-pair))
+ (remaining-packages (map car (cdr package-and-dependencies))))
+ (if
+ (every
+ (lambda (dependency)
+ (not (member dependency remaining-packages)))
+ dependencies)
+ (cons
+ first-pair
+ (check-and-fix-dependency-ordering (cdr package-and-dependencies)))
+ (check-and-fix-dependency-ordering
+ (append
+ (check-and-fix-dependency-ordering (cdr package-and-dependencies))
+ (list first-pair)))))))
+
+(define (recursive-package-inputs pkg)
+ (apply
+ lset-adjoin
+ (append
+ (list eq? '())
+ (apply
+ append
+ (reverse
+ (check-and-fix-dependency-ordering
+ (map
+ (lambda (p)
+ (cons p (reverse (recursive-package-inputs p))))
+ (filter
+ (lambda (p)
+ (assoc-ref
+ (package-properties p)
+ 'sdist))
+ (map cadr (package-direct-inputs pkg))))))))))
+
+(define (get-pkg-info name version)
+ (let* ((possible-egg-info-paths
+ (search-for-egg-info (get-sdist-directory name version) 2))
+ (pkg-info-path
+ (if (> (length possible-egg-info-paths) 0)
+ (string-append (first possible-egg-info-paths)
+ "/PKG-INFO")
+ (let ((p (string-append (get-sdist-directory name version)
+ "/PKG-INFO")))
+ (if (file-exists? p)
+ p
+ #f)))))
+ (if pkg-info-path
+ (let ((data ((call-with-input-file
+ pkg-info-path
+ read-rfc822) 'headers)))
+ (pkg-info
+ (name (string-trim-both (assq-ref data 'Name)))
+ (version (string-trim-both (assq-ref data 'Version)))
+ (home-page (assq-ref data 'Home-page))
+ (synopsis (assq-ref data 'Synopsis))
+ (description "") ; TODO: Possibly use (assq-ref data 'Description))
+ (license (assq-ref data 'License))))
+ (pkg-info
+ (name name)
+ (version version)
+ (home-page "")
+ (synopsis "")
+ (description "")
+ (license "")))))
+
+(define (get-sdist-build-requires name version)
+ (if (equal? name "setuptools")
+ '()
+ `(,(requirement
+ (name "setuptools")))))
+
+(define (get-sdist-tests-require name version)
+ '())
+
+(define (get-sdist-install-requires requires)
+ (let ((install-requires
+ (assoc-ref requires '())))
+ (if install-requires
+ install-requires
+ '())))
+
+(define (get-sdist-extras-require requires)
+ (map
+ (match-lambda
+ ((name . deps)
+ (extra name deps)))
+ (filter
+ (match-lambda
+ ((name . deps)
+ (not (null? name))))
+ requires)))
+
+(define (parse-requires name version)
+ (log-msg 'DEBUG "parse-requires " name "@" version)
+ (python-import "pkg_resources")
+ (let*
+ ((possible-egg-info-directories
+ (search-for-egg-info (get-sdist-directory name version) 2)))
+ (if (= (length possible-egg-info-directories) 0)
+ '()
+ (let*
+ ((sdist-directory (get-sdist-directory name version))
+ (egg-info (first possible-egg-info-directories))
+ (path-metadata (python-apply
+ '(pkg_resources PathMetadata)
+ (list
+ sdist-directory
+ egg-info)
+ '()))
+ (distribution (python-apply
+ '(pkg_resources Distribution)
+ (list
+ sdist-directory
+ path-metadata)
+ '()))
+ (output-dep-map
+ (python-eval "lambda d: [(extra, [(d.name, str(d.specifier), list(d.extras), d.marker) for d in deps]) for extra, deps in d._dep_map.items()]" #t))
+ (dep-map (python-apply output-dep-map
+ (list
+ distribution)
+ '())))
+ (map
+ (match-lambda
+ ((extra deps)
+ (log-msg 'DEBUG extra)
+ (log-msg 'DEBUG deps)
+ (cons
+ extra
+ (let
+ ((r (map
+ requirement-tuple->requirement
+ deps)))
+ (log-msg 'DEBUG r)
+ r))))
+ dep-map)))))
+
+(define (get-sdist-origin name version import-context)
+ (let* ((data
+ (source-release
+ (pypi-fetch
+ name
+ (get-import-context-pypi-api-root import-context))
+ version))
+ (filename (assoc-ref* data "filename"))
+ (full-path (string-append (get-tmpdir) "/" filename))
+ (source-url (assoc-ref* data "url")))
+ (origin
+ (method download:url-fetch)
+ (uri source-url)
+ (sha256
+ (base32 (guix-hash-url full-path))))))
+
+(define-condition-type &missing-source-error &error
+ missing-source-error?
+ (package missing-source-error-package))
+
+(define (source-release pypi-package version)
+ (let ((releases (assoc-ref* pypi-package "releases" version)))
+ (or (find (lambda (release)
+ (string=? "sdist" (assoc-ref release "packagetype")))
+ releases)
+ (raise (condition (&missing-source-error
+ (package pypi-package)))))))
+
+; error: Could not find suitable distribution for Requirement.parse('cryptography_vectors==1.4')
+(define requirement-regex
+ (make-regexp
+ ".*Could not find suitable distribution for Requirement\\.parse\\('(.*)'\\)"))
+
+(define import-error-regex-1
+ (make-regexp
+ "ImportError: No module named '?([^'.]+)"))
+
+(define import-error-regex-2
+ (make-regexp
+ "ImportError: cannot import name '?([^'.]+)"))
+
+(define no-matching-distribution
+ (make-regexp
+ "No matching distribution found for (.*)"))
+
+; 'No module named 'hypothesis''
+(define no-module-named
+ (make-regexp
+ "'No module named '(.*)''"))
+
+(define (list->pairs lst)
+ (if (null? lst)
+ '()
+ (cons
+ (cons (car lst) (cadr lst))
+ (list->pairs (cddr lst)))))
+
+(define (package-has-tests-enabled pkg)
+ (let
+ ((argument (find
+ (match-lambda
+ ((key . value)
+ (equal? #:tests? key)))
+ (list->pairs (package-arguments pkg)))))
+ (if argument
+ (cdr argument)
+ #t))) ; tests are on by default
+
+(define (parse-build-log l)
+ (let*
+ ((lines (string-split l #\newline))
+ (matches
+ (any
+ (lambda (rgx)
+ (let
+ ((matches
+ (filter
+ regexp-match?
+ (map
+ (lambda (line)
+ (regexp-exec rgx line))
+ lines))))
+ (if (null? matches)
+ #f
+ matches)))
+ (list
+ requirement-regex
+ import-error-regex-1
+ no-matching-distribution
+ no-module-named)))
+ (requirements
+ (if (eq? matches #f)
+ '()
+ (map
+ (lambda (m) (match:substring m 1))
+ matches))))
+ (if (null? requirements)
+ #f
+ (catch
+ #t
+ (lambda ()
+ (requirement-string->requirement (car requirements)))
+ (lambda args
+ (display "\n")
+ (display l)
+ (display "\n")
+ (log-msg 'ERROR args)
+ (log-msg 'ERROR "Error parsing requirement from build log"))))))
+
+(define (build-and-return-log-on-failure pkg)
+ (let*
+ ((derivation
+ (with-store store
+ (package-derivation store pkg #:graft? #f)))
+ (built-already
+ (with-store store
+ (valid-path?
+ store (derivation->output-path derivation)))))
+ (if built-already
+ #t
+ (begin
+ (log-msg 'DEBUG derivation)
+ (catch
+ #t
+ (lambda ()
+ (parameterize ((current-build-output-port (if #f
+ (%make-void-port "w")
+ (current-error-port))))
+ (with-store store
+ (run-with-store store
+ (mbegin %store-monad
+ (built-derivations (list derivation))))))
+ #t)
+ (lambda args
+ (log-msg 'DEBUG args)
+ (log-msg 'DEBUG (derivation-file-name derivation))
+ (let* ((logf
+ (with-store store
+ (log-file store (derivation-file-name derivation))))
+ (contents
+ (begin
+ (log-msg 'DEBUG "logf " logf)
+ (call-with-input-file logf
+ (lambda (port)
+ (call-with-decompressed-port 'bzip2 port
+ (lambda (port)
+ (get-string-all port))))))))
+ (cons pkg contents))))))))