Here's also my conan import implementation, if you want to take a look. Nicolas --- Makefile.am | 2 + guix/import/conan.scm | 186 ++++++++++++++++++++++++++++++++++ guix/scripts/import.scm | 1 + guix/scripts/import/conan.scm | 112 ++++++++++++++++++++ 4 files changed, 301 insertions(+) create mode 100644 guix/import/conan.scm create mode 100644 guix/scripts/import/conan.scm
diff --git a/Makefile.am b/Makefile.am index 25ae004fc5..95340989e9 100644 --- a/Makefile.am +++ b/Makefile.am @@ -294,6 +294,7 @@ MODULES = \ guix/packages.scm \ guix/import/cabal.scm \ guix/import/composer.scm \ + guix/import/conan.scm \ guix/import/cpan.scm \ guix/import/cran.scm \ guix/import/crate.scm \ @@ -354,6 +355,7 @@ MODULES = \ guix/scripts/lint.scm \ guix/scripts/challenge.scm \ guix/scripts/import/composer.scm \ + guix/scripts/import/conan.scm \ guix/scripts/import/crate.scm \ guix/scripts/import/cpan.scm \ guix/scripts/import/cran.scm \ diff --git a/guix/import/conan.scm b/guix/import/conan.scm new file mode 100644 index 0000000000..79c620921c --- /dev/null +++ b/guix/import/conan.scm @@ -0,0 +1,186 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2025 Nicolas Graves <ngra...@ngraves.fr> +;;; +;;; 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 import conan) + #:use-module (guix http-client) + #:use-module (guix import json) + #:use-module (guix import utils) + #:use-module (guix memoization) + #:use-module (guix packages) + #:use-module (ice-9 match) + #:use-module (ice-9 regex) + #:use-module (ice-9 textual-ports) + #:use-module (json) + #:use-module (srfi srfi-1) + #:use-module (srfi srfi-2) + #:use-module (srfi srfi-26) + #:use-module (srfi srfi-69) + #:export (conan->guix-package + guix-package->conan-name)) + +;;; +;;; Interface to Conan.io API (center2.conan.io/v2/). +;;; + +(define %conan-base-url + "https://center2.conan.io/v2") + +;; Conan package. +(define-json-mapping <conan-package> make-conan-package conan-package? + json->conan-package + (name conan-package-name) ;string + (version conan-package-version) ;string + (user conan-package-user) ;string + (channel conan-package-channel) ;string + (revision conan-package-revision) ;string + (files conan-package-files)) ;list of strings + +(define (conan-package-reference name version user channel) + "Return the complete package reference for the given NAME, VERSION, USER and CHANNEL." + (string-append name "/" version "@" user "/" channel)) + +(define (conan-package-reference-split reference) + "Split a package reference into its components: name, version, user, channel." + (match (string-split reference #\@) + ((single . ()) + (append (string-split single #\/) (list "_" "_"))) + ((first second . ()) + (append (string-split first #\/) (string-split second #\/))))) + +(define (conan-reference-url reference) + "Return a base REFERENCE url." + (string-join (cons* %conan-base-url "conans" + (conan-package-reference-split reference)) + "/")) + +(define (search-conan-packages name) + "Search Conan packages NAME. Returns a list of package references." + (and-let* ((url (string-append %conan-base-url "/conans/search?q=" name)) + (json (json-fetch url))) + (vector->list (assoc-ref json "results")))) + +(define (get-conan-package-latest-revision reference) + "Get the latest revision information for a package REFERENCE." + (and-let* ((url (string-append (conan-reference-url reference) "/latest")) + (json (json-fetch url))) + (assoc-ref json "revision"))) + +(define (get-conan-package-files reference revision) + "Get the list of files associated with a package REFERENCE and REVISION." + (and-let* ((url (string-append (conan-reference-url reference) + "/revisions/" revision "/files")) + (json (json-fetch url))) + (match (assoc-ref json "files") + ((? list? files) (map car files)) + (_ '())))) + +(define (get-conan-package-file reference revision filename) + "Get the content of a specific file from a package REFERENCE and REVISION." + (let* ((url (string-append (conan-reference-url reference) + "/revisions/" revision "/files/" filename))) + (http-fetch url))) + +(define (get-conan-package-conanfile reference revision) + "Get the conanfile.py content from a package REFERENCE and REVISION." + (get-conan-package-file reference revision "conanfile.py")) + +(define lookup-conan-package + (lambda* (name #:optional (version #f)) + "Look up NAME and VERSION on Conan.io and return the corresponding package +reference or #f if it was not found." + (and-let* ((references (search-conan-packages name)) + (reference (if version + (find (match-lambda + (`(,name ,version ...) #t) + (_ #f)) + references) + (first references))) + (revision (get-conan-package-latest-revision reference))) + (apply make-conan-package + (append (conan-package-reference-split reference) + (list revision + (get-conan-package-files reference revision))))))) + +;;; +;;; Converting Conan packages to Guix packages. +;;; + +(define (extract-metadata port) + "Extract some simple metadata from a conanfile.py content in PORT." + (let ((content (get-string-all port))) + + (define (parse target) + (and-let* ((found (string-match target content))) + (string-trim (match:substring found 1) #\"))) + + (list (parse "description\\s*=\\s*\\\"([^\\\"]+)\\\"") + (parse "license\\s*=\\s*\\\"([^\\\"]+)\\\"") + (parse "homepage\\s*=\\s*\\\"([^\\\"]+)\\\"")))) + +(define (conan-name->package-name name) + "Convert a Conan package name to a Guix package name." + (string-downcase name)) + +(define (guix-package->conan-name package) + "Return the Conan name of PACKAGE." + (package-name package)) + +(define* (make-conan-sexp name version #:key source-url + (sha256 (make-string 52 #\0)) + license home-page synopsis description) + "Return the `package' s-expression for a Conan package with the given NAME, +VERSION, SOURCE-URL, SHA256, LICENSE, HOME-PAGE, SYNOPSIS, and DESCRIPTION." + `(package + (name ,(conan-name->package-name name)) + (version ,version) + (source (origin + (method git-fetch) + (uri (git-reference + (url ,home-page) + (commit (string-append "v" version)))) + (sha256 + (base32 ,sha256)))) + (build-system conan-build-system) + (home-page ,home-page) + (synopsis ,synopsis) + (description ,description) + (license ,license))) + +(define* (conan->guix-package name #:key version #:allow-other-keys) + "Fetch the metadata for the Conan package NAME at VERSION from Conan.io, and +return the `package' s-expression corresponding to that package, or #f on failure." + ;; Guile currently doesn't have a native way to parse python files. + ;; We can parse (build) requirements with the same trick as in the + ;; conan-build-system (evaluating with Python, exporting to + ;; text, then reading from Guile), but that would require having python + ;; and conan in one's profile. Not sure this has been done before, + ;; skipping this for now. + (match (lookup-conan-package name version) + (($ <conan-package> name version user channel revision) + (and-let* ((reference (conan-package-reference name version user channel))) + (match (extract-metadata (get-conan-package-conanfile reference revision)) + ((description license homepage . ()) + (make-conan-sexp name version + #:source-url homepage + #:license (spdx-string->license license) + #:home-page homepage + #:synopsis description + #:description description))))))) + +;;; XXX: For the updater, we have information about versions, but not about +;;; source, or probably not better than the built-in github updater. diff --git a/guix/scripts/import.scm b/guix/scripts/import.scm index bbf31baa15..4d0fd689a5 100644 --- a/guix/scripts/import.scm +++ b/guix/scripts/import.scm @@ -50,6 +50,7 @@ (define %standard-import-options '()) ;; The list of all known importers. These are printed in order by SHOW-HELP, so ;; please keep this list alphabetically sorted! (define importers '("composer" "cpan" "cran" "crate" "egg" "elm" "elpa" + "conan" "gem" "gnu" "go" "hackage" "hexpm" "json" "minetest" "npm-binary" "opam" "pypi" "stackage" "texlive")) diff --git a/guix/scripts/import/conan.scm b/guix/scripts/import/conan.scm new file mode 100644 index 0000000000..0b6746f255 --- /dev/null +++ b/guix/scripts/import/conan.scm @@ -0,0 +1,112 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2025 Nicolas Graves <ngra...@ngraves.fr> +;;; +;;; 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 scripts import conan) + #:use-module (guix ui) + #:use-module (guix utils) + #:use-module (guix scripts) + #:use-module (guix import conan) + #:use-module (guix scripts import) + #:use-module (srfi srfi-1) + #:use-module (srfi srfi-11) + #:use-module (srfi srfi-37) + #:use-module (ice-9 match) + #:use-module (ice-9 format) + #:export (guix-import-conan)) + +;;; +;;; Command-line options. +;;; +(define %default-options + '()) + +(define (show-help) + (display (G_ "Usage: guix import conan PACKAGE-NAME +Import and convert the Conan.io package for PACKAGE-NAME.\n")) + (display (G_ " + -r, --recursive import packages recursively")) + (display (G_ " + -u, --user=USER specify the package user")) + (display (G_ " + -c, --channel=CHANNEL specify the package channel")) + (newline) + (display (G_ " + -h, --help display this help and exit")) + (display (G_ " + -V, --version display version information and exit")) + (newline) + (show-bug-report-information)) + +(define %options + ;; Specification of the command-line options. + (cons* (option '(#\h "help") #f #f + (lambda args + (show-help) + (exit 0))) + (option '(#\V "version") #f #f + (lambda args + (show-version-and-exit "guix import conan"))) + (option '(#\r "recursive") #f #f + (lambda (opt name arg result) + (alist-cons 'recursive #t result))) + (option '(#\u "user") #t #f + (lambda (opt name arg result) + (alist-cons 'user arg result))) + (option '(#\c "channel") #t #f + (lambda (opt name arg result) + (alist-cons 'channel arg result))) + %standard-import-options)) + +;;; +;;; Entry point. +;;; +(define (guix-import-conan . args) + (define (parse-options) + ;; Return the alist of option values. + (parse-command-line args %options (list %default-options) + #:build-options? #f)) + + (let* ((opts (parse-options)) + (args (filter-map (match-lambda + (('argument . value) + value) + (_ #f)) + (reverse opts)))) + (match args + ((spec) + (define-values (name version) + (package-name->name+version spec)) + (let ((version (or version (assoc-ref opts 'version)))) + (match (if (assoc-ref opts 'recursive) + (conan-recursive-import + name #:version version) + (conan->guix-package + name #:version version + #:user (assoc-ref opts 'user) + #:channel (assoc-ref opts 'channel))) + ((or #f '()) + (leave (G_ "failed to download meta-data for package '~a'~%") + (if version + (string-append name "@" version) + name))) + ((? list? sexps) sexps) + (sexp (list sexp))))) + (() + (leave (G_ "too few arguments~%"))) + ((many ...) + (leave (G_ "too many arguments~%")))))) -- 2.49.0 -- Best regards, Nicolas Graves