X-Git-Url: https://git.hcoop.net/jackhill/guix/guix.git/blobdiff_plain/ddb4062784c66ecc0c42910b209dc80356a197ea..e80d1c1992fb60c492520d61f3a3d1fef65cfdf2:/gnu/services.scm diff --git a/gnu/services.scm b/gnu/services.scm index 7de78105ff..e7a3a95e43 100644 --- a/gnu/services.scm +++ b/gnu/services.scm @@ -25,6 +25,8 @@ #:use-module (guix profiles) #:use-module (guix discovery) #:use-module (guix combinators) + #:use-module (guix channels) + #:use-module (guix describe) #:use-module (guix sets) #:use-module (guix ui) #:use-module ((guix utils) #:select (source-properties->location)) @@ -39,6 +41,7 @@ #:use-module (srfi srfi-35) #:use-module (ice-9 vlist) #:use-module (ice-9 match) + #:autoload (ice-9 pretty-print) (pretty-print) #:export (service-extension service-extension? service-extension-target @@ -82,6 +85,7 @@ ambiguous-target-service-error-target-type system-service-type + provenance-service-type boot-service-type cleanup-service-type activation-service-type @@ -370,6 +374,89 @@ by the initrd once the root file system is mounted."))) ;; The service that produces the boot script. (service boot-service-type #t)) + +;;; +;;; Provenance tracking. +;;; + +(define (object->pretty-string obj) + "Like 'object->string', but using 'pretty-print'." + (call-with-output-string + (lambda (port) + (pretty-print obj port)))) + +(define (channel->code channel) + "Return code to build CHANNEL, ready to be dropped in a 'channels.scm' +file." + `(channel (name ',(channel-name channel)) + (url ,(channel-url channel)) + (branch ,(channel-branch channel)) + (commit ,(channel-commit channel)))) + +(define (channel->sexp channel) + "Return an sexp describing CHANNEL. The sexp is _not_ code and is meant to +be parsed by tools; it's potentially more future-proof than code." + `(channel (name ,(channel-name channel)) + (url ,(channel-url channel)) + (branch ,(channel-branch channel)) + (commit ,(channel-commit channel)))) + +(define (provenance-file channels config-file) + "Return a 'provenance' file describing CHANNELS, a list of channels, and +CONFIG-FILE, which can be either #f or a containing the OS +configuration being used." + (scheme-file "provenance" + #~(provenance + (version 0) + (channels #+@(if channels + (map channel->sexp channels) + '())) + (configuration-file #+config-file)))) + +(define (provenance-entry config-file) + "Return system entries describing the operating system provenance: the +channels in use and CONFIG-FILE, if it is true." + (define profile + (current-profile)) + + (define channels + (and=> profile profile-channels)) + + (mbegin %store-monad + (let ((config-file (cond ((string? config-file) + (local-file config-file "configuration.scm")) + ((not config-file) + #f) + (else + config-file)))) + (return `(("provenance" ,(provenance-file channels config-file)) + ,@(if channels + `(("channels.scm" + ,(plain-file "channels.scm" + (object->pretty-string + `(list + ,@(map channel->code channels)))))) + '()) + ,@(if config-file + `(("configuration.scm" ,config-file)) + '())))))) + +(define provenance-service-type + (service-type (name 'provenance) + (extensions + (list (service-extension system-service-type + provenance-entry))) + (default-value #f) ;the OS config file + (description + "Store provenance information about the system in the system +itself: the channels used when building the system, and its configuration +file, when available."))) + + +;;; +;;; Cleanup. +;;; + (define (cleanup-gexp _) "Return a gexp to clean up /tmp and similar places upon boot." (with-imported-modules '((guix build utils)) @@ -430,34 +517,34 @@ ACTIVATION-SCRIPT-TYPE." (define (activation-script gexps) "Return the system's activation script, which evaluates GEXPS." (define actions - (map (cut scheme-file "activate-service" <>) gexps)) - - (scheme-file "activate" - (with-imported-modules (source-module-closure - '((gnu build activation) - (guix build utils))) - #~(begin - (use-modules (gnu build activation) - (guix build utils)) - - ;; Make sure the user accounting database exists. If it - ;; does not exist, 'setutxent' does not create it and - ;; thus there is no accounting at all. - (close-port (open-file "/var/run/utmpx" "a0")) - - ;; Same for 'wtmp', which is populated by mingetty et - ;; al. - (mkdir-p "/var/log") - (close-port (open-file "/var/log/wtmp" "a0")) - - ;; Set up /run/current-system. Among other things this - ;; sets up locales, which the activation snippets - ;; executed below may expect. - (activate-current-system) - - ;; Run the services' activation snippets. - ;; TODO: Use 'load-compiled'. - (for-each primitive-load '#$actions))))) + (map (cut program-file "activate-service.scm" <>) gexps)) + + (program-file "activate.scm" + (with-imported-modules (source-module-closure + '((gnu build activation) + (guix build utils))) + #~(begin + (use-modules (gnu build activation) + (guix build utils)) + + ;; Make sure the user accounting database exists. If it + ;; does not exist, 'setutxent' does not create it and + ;; thus there is no accounting at all. + (close-port (open-file "/var/run/utmpx" "a0")) + + ;; Same for 'wtmp', which is populated by mingetty et + ;; al. + (mkdir-p "/var/log") + (close-port (open-file "/var/log/wtmp" "a0")) + + ;; Set up /run/current-system. Among other things this + ;; sets up locales, which the activation snippets + ;; executed below may expect. + (activate-current-system) + + ;; Run the services' activation snippets. + ;; TODO: Use 'load-compiled'. + (for-each primitive-load '#$actions))))) (define (gexps->activation-gexp gexps) "Return a gexp that runs the activation script containing GEXPS." @@ -775,18 +862,34 @@ TARGET-TYPE; return the root service adjusted accordingly." (eq? (service-kind service) target-type)) services) ((sink) - (let loop ((sink sink)) - (let* ((dependents (map loop (dependents sink))) - (extensions (map (apply-extension sink) dependents)) - (extend (service-type-extend (service-kind sink))) - (compose (service-type-compose (service-kind sink))) - (params (service-value sink))) - ;; We distinguish COMPOSE and EXTEND because PARAMS typically has a - ;; different type than the elements of EXTENSIONS. - (if extend - (service (service-kind sink) - (extend params (compose extensions))) - sink)))) + ;; Use the state monad to keep track of already-visited services in the + ;; graph and to memoize their value once folded. + (run-with-state + (let loop ((sink sink)) + (mlet %state-monad ((visited (current-state))) + (match (vhash-assq sink visited) + (#f + (mlet* %state-monad + ((dependents (mapm %state-monad loop (dependents sink))) + (visited (current-state)) + (extensions -> (map (apply-extension sink) dependents)) + (extend -> (service-type-extend (service-kind sink))) + (compose -> (service-type-compose (service-kind sink))) + (params -> (service-value sink)) + (service + -> + ;; Distinguish COMPOSE and EXTEND because PARAMS typically + ;; has a different type than the elements of EXTENSIONS. + (if extend + (service (service-kind sink) + (extend params (compose extensions))) + sink))) + (mbegin %state-monad + (set-current-state (vhash-consq sink service visited)) + (return service)))) + ((_ . service) ;SINK was already visited + (return service))))) + vlist-null)) (() (raise (condition (&missing-target-service-error