123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204 |
- ;;; GNU Guix --- Functional package management for GNU
- ;;; Copyright © 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 Ludovic Courtès <ludo@gnu.org>
- ;;;
- ;;; 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 discovery)
- #:use-module (guix i18n)
- #:use-module (guix modules)
- #:use-module (guix combinators)
- #:use-module (guix build syscalls)
- #:use-module (srfi srfi-1)
- #:use-module (ice-9 match)
- #:use-module (ice-9 vlist)
- #:export (scheme-files
- scheme-modules
- scheme-modules*
- fold-modules
- all-modules
- fold-module-public-variables
- fold-module-public-variables*))
- ;;; Commentary:
- ;;;
- ;;; This module provides tools to discover Guile modules and the variables
- ;;; they export.
- ;;;
- ;;; Code:
- (define* (scheme-files directory)
- "Return the list of Scheme files found under DIRECTORY, recursively. The
- returned list is sorted in alphabetical order. Return the empty list if
- DIRECTORY is not accessible."
- (define (entry-type name properties)
- (match (assoc-ref properties 'type)
- ('unknown
- (stat:type (lstat name)))
- ((? symbol? type)
- type)))
- (define (dot-prefixed? file)
- (string-prefix? "." file))
- ;; Use 'scandir*' so we can avoid an extra 'lstat' for each entry, as
- ;; opposed to Guile's 'scandir' or 'file-system-fold'.
- (fold-right (lambda (entry result)
- (match entry
- (((? dot-prefixed?) . _)
- ;; Exclude ".", "..", and hidden files such as backups.
- result)
- ((name . properties)
- (let ((absolute (string-append directory "/" name)))
- (case (entry-type absolute properties)
- ((directory)
- (append (scheme-files absolute) result))
- ((regular)
- (if (string-suffix? ".scm" name)
- (cons absolute result)
- result))
- ((symlink)
- (cond ((string-suffix? ".scm" name)
- (cons absolute result))
- ((stat absolute #f)
- =>
- (match-lambda
- (#f result)
- ((= stat:type 'directory)
- (append (scheme-files absolute)
- result))
- (_ result)))
- (else
- result)))
- (else
- result))))))
- '()
- (catch 'system-error
- (lambda ()
- (scandir* directory))
- (lambda args
- (let ((errno (system-error-errno args)))
- (unless (= errno ENOENT)
- (format (current-error-port) ;XXX
- (G_ "cannot access `~a': ~a~%")
- directory (strerror errno)))
- '())))))
- (define* (scheme-modules directory #:optional sub-directory
- #:key (warn (const #f)))
- "Return the list of Scheme modules available under DIRECTORY.
- Optionally, narrow the search to SUB-DIRECTORY.
- WARN is called when a module could not be loaded. It is passed the module
- name and the exception key and arguments."
- (define prefix-len
- (string-length directory))
- ;; Hide Guile warnings such as "source file [...] newer than compiled" when
- ;; loading user code, unless we're hacking on Guix proper. See
- ;; <https://issues.guix.gnu.org/43747>.
- (parameterize ((current-warning-port (if (getenv "GUIX_UNINSTALLED")
- (current-warning-port)
- (%make-void-port "w"))))
- (filter-map (lambda (file)
- (let* ((relative (string-drop file prefix-len))
- (module (file-name->module-name relative)))
- (catch #t
- (lambda ()
- (resolve-interface module))
- (lambda args
- ;; Report the error, but keep going.
- (warn file module args)
- #f))))
- (scheme-files (if sub-directory
- (string-append directory "/" sub-directory)
- directory)))))
- (define* (scheme-modules* directory #:optional sub-directory)
- "Return the list of module names found under SUB-DIRECTORY in DIRECTORY.
- This is a source-only variant that does not try to load files."
- (let ((prefix (string-length directory)))
- (map (lambda (file)
- (file-name->module-name (string-drop file prefix)))
- (scheme-files (if sub-directory
- (string-append directory "/" sub-directory)
- directory)))))
- (define* (fold-modules proc init path #:key (warn (const #f)))
- "Fold over all the Scheme modules present in PATH, a list of directories.
- Call (PROC MODULE RESULT) for each module that is found."
- (fold (lambda (spec result)
- (match spec
- ((? string? directory)
- (fold proc result (scheme-modules directory #:warn warn)))
- ((directory . sub-directory)
- (fold proc result
- (scheme-modules directory sub-directory
- #:warn warn)))))
- '()
- path))
- (define* (all-modules path #:key (warn (const #f)))
- "Return the list of package modules found in PATH, a list of directories to
- search. Entries in PATH can be directory names (strings) or (DIRECTORY
- . SUB-DIRECTORY) pairs, in which case modules are searched for beneath
- SUB-DIRECTORY. Modules are listed in the order they appear on the path."
- (reverse (fold-modules cons '() path #:warn warn)))
- (define (fold-module-public-variables* proc init modules)
- "Call (PROC MODULE SYMBOL VARIABLE RESULT) for each variable exported by one
- of MODULES, using INIT as the initial value of RESULT. It is guaranteed to
- never traverse the same object twice."
- ;; Here SEEN is populated by variables; if two different variables refer to
- ;; the same object, we still let them through.
- (identity ;discard second return value
- (fold2 (lambda (module result seen)
- (fold2 (lambda (sym+var result seen)
- (match sym+var
- ((sym . var)
- (if (not (vhash-assq var seen))
- (values (proc module sym var result)
- (vhash-consq var #t seen))
- (values result seen)))))
- result
- seen
- (module-map cons module)))
- init
- vlist-null
- modules)))
- (define (fold-module-public-variables proc init modules)
- "Call (PROC OBJECT RESULT) for each variable exported by one of MODULES,
- using INIT as the initial value of RESULT. It is guaranteed to never traverse
- the same object twice."
- ;; Note: here SEEN is populated by objects, not by variables.
- (identity ; discard second return value
- (fold2 (lambda (module result seen)
- (fold2 (lambda (var result seen)
- (if (not (vhash-assq var seen))
- (values (proc var result)
- (vhash-consq var #t seen))
- (values result seen)))
- result
- seen
- (module-map (lambda (sym var)
- (false-if-exception (variable-ref var)))
- module)))
- init
- vlist-null
- modules)))
- ;;; discovery.scm ends here
|