[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Integration with Software Heritage
From: |
Ludovic Courtès |
Subject: |
Integration with Software Heritage |
Date: |
Fri, 16 Nov 2018 17:36:43 +0100 |
User-agent: |
Gnus/5.13 (Gnus v5.13) Emacs/26.1 (gnu/linux) |
Hello Guix!
I’ve been playing with the HTTP API of Software Heritage to see how we
could use it as a mirror for the source of our packages. The attached
file provides bindings to that API, which allow you to do things like
looking up an “origin” (source code URL):
scheme@(guix swh)> (lookup-origin "https://github.com/guix-mirror/guix/")
$84 = #<<origin> id: 52181937 visits-url: "/api/1/origin/52181937/visits/"
type: "git" url: "https://github.com/guix-mirror/guix">
looking up a tag (which first means looking up its origin and then the
“visits” of that origin):
scheme@(guix swh)> (lookup-origin-revision
"https://github.com/guix-mirror/guix/" "v0.8")
$85 = #<<revision> id: "44941798d222901b8f381b3210957d880b90a2fc" date:
#<date nanosecond: 0 second: 38 minute: 9 hour: 22 day: 17 month: 11 year: 2014
zone-offset: 3600> directory: "f2b347a9eede13c32e298cc694fdfeb51709e065"
directory-url: "/api/1/directory/f2b347a9eede13c32e298cc694fdfeb51709e065/">
looking up a “revision” (typically a Git commit):
scheme@(guix swh)> (lookup-revision
"c8855b991880897b2658dc90164e29c96e2aeb3a")
$87 = #<<revision> id: "c8855b991880897b2658dc90164e29c96e2aeb3a" date:
#<date nanosecond: 0 second: 11 minute: 10 hour: 12 day: 4 month: 11 year: 2015
zone-offset: 3600> directory: "e8e7646053738dac1d94cdf1d47e17cfa460fbbc"
directory-url: "/api/1/directory/e8e7646053738dac1d94cdf1d47e17cfa460fbbc/">
looking up “content” (such as a Git tree or, in some cases, a tarball):
scheme@(guile-user)> ,use(guix swh)
scheme@(guile-user)> ,use(gnu packages base)
scheme@(guile-user)> ,use(guix)
scheme@(guile-user)> (lookup-content (origin-sha256
(package-source coreutils))
"sha256")
$88 = #<<content> checksums: (("sha1" . #vu8(99 213 34 244 48 240 249 252 236
167 30 75 82 180 20 153 149 49 145 92)) ("sha256" . #vu8(146 208 250 28 49 28
172 239 168 152 83 189 181 60 98 244 17 12 223 218 56 32 52 107 89 203 208 152
244 15 149 94)) ("sha1_git" . #vu8(187 96 21 252 21 195 11 238 235 180 110 110
169 57 214 11 161 46 216 44)) ("blake2s256" . #vu8(251 8 178 16 97 158 173 248
72 66 132 183 201 41 123 8 162 98 69 4 215 121 223 44 213 83 144 13 35 93 234
178))) data-url:
"/api/1/content/sha256:92d0fa1c311cacefa89853bdb53c62f4110cdfda3820346b59cbd098f40f955e/raw/"
file-type-url:
"/api/1/content/sha256:92d0fa1c311cacefa89853bdb53c62f4110cdfda3820346b59cbd098f40f955e/filetype/"
language-url:
"/api/1/content/sha256:92d0fa1c311cacefa89853bdb53c62f4110cdfda3820346b59cbd098f40f955e/language/"
length: 5286588 license-url:
"/api/1/content/sha256:92d0fa1c311cacefa89853bdb53c62f4110cdfda3820346b59cbd098f40f955e/license/">
I haven’t yet implemented “directory” retrieval. There are several
issues we discussed over the last few days on swh-devel (some of which
were already fixed!) so this is still work in progress:
https://sympa.inria.fr/sympa/arc/swh-devel/2018-11/
Stefano opened an issue on their tracker to track progress on Guix
package coverage:
https://forge.softwareheritage.org/T1352
One of the proposal Stefano made is to write a “lister” and/or a
“loader” for Guix packages, which is a snippet of Python code that would
run inside Software Heritage so that it can regularly grab the list of
packages that it needs to archive:
https://sympa.inria.fr/sympa/arc/swh-devel/2018-11/msg00019.html
If you’re willing to give it a stab or to play with other aspects of the
archive, you’re very much welcome to join!
Ludo’.
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2018 Ludovic Courtès <address@hidden>
;;;
;;; This file is part of GNU Guix.
;;;
;;; GNU Guix is free software; you can redistribute it and/or modify it
;;; under the terms of the GNU General Public License as published by
;;; the Free Software Foundation; either version 3 of the License, or (at
;;; your option) any later version.
;;;
;;; GNU Guix is distributed in the hope that it will be useful, but
;;; WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;;; GNU General Public License for more details.
;;;
;;; You should have received a copy of the GNU General Public License
;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
(define-module (guix swh)
#:use-module (guix http-client)
#:use-module (guix base16)
#:use-module (web client)
#:use-module (web response)
#:use-module (json)
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-9)
#:use-module (srfi srfi-11)
#:use-module (ice-9 match)
#:use-module (ice-9 regex)
#:export (origin?
origin-id
origin-type
origin-url
origin-visits
lookup-origin
visit?
visit-date
visit-origin
visit-url
visit-snapshot-url
visit-status
visit-number
visit-snapshot
branch?
branch-name
branch-target
release?
release-id
release-name
release-message
release-target
revision?
revision-id
revision-date
revision-directory
lookup-revision
lookup-origin-revision
content?
content-checksums
content-data-url
content-length
lookup-content))
;;; Commentary:
;;;
;;; This module provides bindings to the HTTP interface of Software Heritage.
;;; It allows you to browse the archive, look up revisions (such as SHA1
;;; commit IDs), "origins" (code hosting URLs), content (files), etc. See
;;; <https://archive.softwareheritage.org/api/> for more information.
;;;
;;; Code:
(define %swh-base-url
;; Presumably we won't need to change it.
"https://archive.softwareheritage.org")
(define (swh-url path . rest)
(define url
(string-append %swh-base-url path
(string-join rest "/" 'prefix)))
;; Ensure there's a trailing slash or we get a redirect.
(if (string-suffix? "/" url)
url
(string-append url "/")))
(define-syntax-rule (define-json-reader json->record ctor spec ...)
"Define JSON->RECORD as a procedure that converts a JSON representation,
read from a port, string, or hash table, into a record created by CTOR and
following SPEC, a series of field specifications."
(define (json->record input)
(let ((table (cond ((port? input)
(json->scm input))
((string? input)
(json-string->scm input))
((hash-table? input)
input))))
(let-syntax ((extract-field (syntax-rules ()
((_ table (field key json->value))
(json->value (hash-ref table key)))
((_ table (field key))
(hash-ref table key))
((_ table (field))
(hash-ref table
(symbol->string 'field))))))
(ctor (extract-field table spec) ...)))))
(define-syntax-rule (define-json-mapping rtd ctor pred json->record
(field getter spec ...) ...)
"Define RTD as a record type with the given FIELDs and GETTERs, à la SRFI-9,
and define JSON->RECORD as a conversion from JSON to a record of this type."
(begin
(define-record-type rtd
(ctor field ...)
pred
(field getter) ...)
(define-json-reader json->record ctor
(field spec ...) ...)))
(define %date-regexp
;; Match strings like "2014-11-17T22:09:38+01:00" or
;; "2018-09-30T23:20:07.815449+00:00"".
(make-regexp
"^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})((\\.[0-9]+)?)([+-][0-9]{2}):([0-9]{2})$"))
(define (string->date* str)
"Return a SRFI-19 date parsed from STR, a date string as returned by
Software Heritage."
;; We can't use 'string->date' because of the timezone format: SWH returns
;; "+01:00" when the '~z' template expects "+0100". So we roll our own!
(or (and=> (regexp-exec %date-regexp str)
(lambda (match)
(define (ref n)
(string->number (match:substring match n)))
(make-date (let ((ns (match:substring match 8)))
(if ns
(string->number (string-drop ns 1))
0))
(ref 6) (ref 5) (ref 4)
(ref 3) (ref 2) (ref 1)
(+ (* 3600 (ref 9)) ;time zone
(if (< (ref 9) 0)
(- (ref 10))
(ref 10))))))
str)) ;oops!
(define* (call url decode #:optional (method http-get)
#:key (false-if-404? #t))
"Invoke the endpoint at URL using METHOD. Decode the resulting JSON body
using DECODE, a one-argument procedure that takes an input port. When
FALSE-IF-404? is true, return #f upon 404 responses."
(let*-values (((response port)
(method url #:streaming? #t)))
;; See <https://archive.softwareheritage.org/api/#rate-limiting>.
(match (assq-ref (response-headers response) 'x-ratelimit-remaining)
(#f #t)
((? (compose zero? string->number))
(error "rate limit reached!"))
(_ #t))
(cond ((= 200 (response-code response))
(let ((result (decode port)))
(close-port port)
result))
((and false-if-404?
(= 404 (response-code response)))
(close-port port)
#f)
(else
(close-port port)
(throw 'swh-error url response)))))
(define-syntax define-query
(syntax-rules (path)
"Define a procedure that performs a Software Heritage query."
((_ (name args ...) docstring (path components ...)
json->value)
(define (name args ...)
docstring
(call (swh-url components ...) json->value)))))
;;
<https://archive.softwareheritage.org/api/1/origin/git/url/https://github.com/guix-mirror/guix/>
(define-json-mapping <origin> make-origin origin?
json->origin
(id origin-id)
(visits-url origin-visits-url "origin_visits_url")
(type origin-type)
(url origin-url))
;; <https://archive.softwareheritage.org/api/1/origin/52181937/visits/>
(define-json-mapping <visit> make-visit visit?
json->visit
(date visit-date "date" string->date*)
(origin visit-origin)
(url visit-url "origin_visit_url")
(snapshot-url visit-snapshot-url "snapshot_url")
(status visit-status)
(number visit-number "visit"))
;;
<https://archive.softwareheritage.org/api/1/snapshot/4334c3ed4bb208604ed780d8687fe523837f1bd1/>
(define-json-mapping <snapshot> make-snapshot snapshot?
json->snapshot
(branches snapshot-branches "branches" json->branches))
;; This is used for the "branches" field of snapshots.
(define-record-type <branch>
(make-branch name target-type target-url)
branch?
(name branch-name)
(target-type branch-target-type) ;release | revision
(target-url branch-target-url))
(define (json->branches branches)
(hash-map->list (lambda (key value)
(make-branch key
(string->symbol
(hash-ref value "target_type"))
(hash-ref value "target_url")))
branches))
;;
<https://archive.softwareheritage.org/api/1/release/1f44934fb6e2cefccbecd4fa347025349fa9ff76/>
(define-json-mapping <release> make-release release?
json->release
(id release-id)
(name release-name)
(message release-message)
(target-type release-target-type "target_type" string->symbol)
(target-url release-target-url "target_url"))
;;
<https://archive.softwareheritage.org/api/1/revision/359fdda40f754bbf1b5dc261e7427b75463b59be/>
(define-json-mapping <revision> make-revision revision?
json->revision
(id revision-id)
(date revision-date "date" string->date*)
(directory revision-directory)
(directory-url revision-directory-url "directory_url"))
;; <https://archive.softwareheritage.org/api/1/content/>
(define-json-mapping <content> make-content content?
json->content
(checksums content-checksums "checksums" json->checksums)
(data-url content-data-url "data_url")
(file-type-url content-file-type-url "filetype_url")
(language-url content-language-url "language_url")
(length content-length)
(license-url content-license-url "license_url"))
(define (json->checksums checksums)
(hash-map->list (lambda (key value)
(cons key (base16-string->bytevector value)))
checksums))
;; <https://archive.softwareheritage.org/api/1/origin/save/>
(define-json-mapping <save-reply> make-save-reply save-reply?
json->save-reply
(origin-url save-reply-origin-url "origin_url")
(origin-type save-reply-origin-type "origin_type")
(request-date save-reply-request-date "save_request_date"
string->date*)
(request-status save-reply-request-status "save_request_status"
string->symbol)
(task-status save-reply-task-status "save_task_status"
(match-lambda
("not created" 'not-created)
((? string? str) (string->symbol str)))))
;;;
;;; RPCs.
;;;
(define-query (lookup-origin url)
"Return an origin for URL."
(path "/api/1/origin/git/url" url)
json->origin)
(define-query (lookup-content hash type)
"Return a content for HASH, of the given TYPE--e.g., \"sha256\"."
(path "/api/1/content"
(string-append type ":"
(bytevector->base16-string hash)))
json->content)
(define-query (lookup-revision id)
"Return the revision with the given ID, typically a Git commit SHA1."
(path "/api/1/revision" id)
json->revision)
(define (origin-visits origin)
"Return the list of visits of ORIGIN, a record as returned by
'lookup-origin'."
(map json->visit
(json->scm (http-fetch (swh-url (origin-visits-url origin))))))
(define (visit-snapshot visit)
"Return the snapshot corresponding to VISIT."
(json->snapshot (http-fetch (swh-url (visit-snapshot-url visit)))))
(define (branch-target branch)
"Return the target of BRANCH, either a <revision> or a <release>."
(match (branch-target-type branch)
('release
(json->release
(http-fetch (swh-url (branch-target-url branch)))))
('revision
(json->revision
(http-fetch (swh-url (branch-target-url branch)))))))
(define (lookup-origin-revision url tag)
"Return a <revision> corresponding to the given TAG for the repository
coming from URL. Example:
(lookup-origin-release \"https://github.com/guix-mirror/guix/\" \"v0.8\")
=> #<<revision> id: \"44941…\" …>
The information is based on the latest visit of URL available. Return #f if
URL could not be found."
(match (lookup-origin url)
(#f #f)
(origin
(match (origin-visits origin)
((visit . _)
(let ((snapshot (visit-snapshot visit)))
(match (and=> (find (lambda (branch)
(string=? (string-append "refs/tags/" tag)
(branch-name branch)))
(snapshot-branches snapshot))
branch-target)
((? release? release)
(release-target release))
((? revision? revision)
revision)
(#f ;tag not found
#f))))
(()
#f)))))
(define (release-target release)
"Return the revision that is the target of RELEASE."
(match (release-target-type release)
('revision
(json->revision (http-fetch (swh-url (release-target-url release)))))))
(define (save-origin url #:optional (type "git"))
"Request URL to be saved."
(call (swh-url "/api/1/origin/save" type "url" url) json->save-reply
http-post))
(define-query (save-origin-status url type)
"Return the status of a /save request for URL and TYPE (e.g., \"git\")."
(path "/api/origin/save" type "url" url)
json->save-reply)
signature.asc
Description: PGP signature
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- Integration with Software Heritage,
Ludovic Courtès <=