;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2015-2022 Ludovic Courtès ;;; Copyright © 2016 Chris Marusich ;;; Copyright © 2020 Jan (janneke) Nieuwenhuizen ;;; Copyright © 2020, 2021 Ricardo Wurmus ;;; Copyright © 2021 raid5atemyhomework ;;; Copyright © 2020 Christine Lemmer-Webber ;;; Copyright © 2020, 2021 Brice Waegeneire ;;; ;;; 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 wi
aboutsummaryrefslogtreecommitdiff
blob: ed2580679a6225fdaad10b49501e6ad5fa261916 (about) (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
Continuation of the SOURCE_DATE_EPOCH patch.

Patch adapted from upstream source repository:

https://gcc.gnu.org/git/?p=gcc.git;a=commitdiff;h=dfa5c0d3f3e23e4fdb14857a42de376d9ff8601c

From dfa5c0d3f3e23e4fdb14857a42de376d9ff8601c Mon Sep 17 00:00:00 2001
From: doko <doko@138bc75d-0d04-0410-961f-82ee72b054a4>
Date: Wed, 1 Jun 2016 16:42:41 +0000
Subject: [PATCH] gcc/c-family/ChangeLog:

diff --git a/gcc/c-family/c-common.c b/gcc/c-family/c-common.c
index 6f0898a38d7..efbc78ef218 100644
--- a/gcc/c-family/c-common.c
+++ b/gcc/c-family/c-common.c
@@ -12321,8 +12321,9 @@ pointer_to_zero_sized_aggr_p (tree t)
 /* Read SOURCE_DATE_EPOCH from environment to have a deterministic
    timestamp to replace embedded current dates to get reproducible
    results.  Returns -1 if SOURCE_DATE_EPOCH is not defined.  */
+
 time_t
-get_source_date_epoch ()
+cb_get_source_date_epoch (cpp_reader *pfile ATTRIBUTE_UNUSED)
 {
   char *source_date_epoch;
   long long epoch;
@@ -12334,19 +12335,14 @@ get_source_date_epoch ()
 
   errno = 0;
   epoch = strtoll (source_date_epoch, &endptr, 10);
-  if ((errno == ERANGE && (epoch == LLONG_MAX || epoch == LLONG_MIN))
-      || (errno != 0 && epoch == 0))
-    fatal_error (UNKNOWN_LOCATION, "environment variable $SOURCE_DATE_EPOCH: "
-		 "strtoll: %s\n", xstrerror(errno));
-  if (endptr == source_date_epoch)
-    fatal_error (UNKNOWN_LOCATION, "environment variable $SOURCE_DATE_EPOCH: "
-		 "no digits were found: %s\n", endptr);
-  if (*endptr != '\0')
-    fatal_error (UNKNOWN_LOCATION, "environment variable $SOURCE_DATE_EPOCH: "
-		 "trailing garbage: %s\n", endptr);
-  if (epoch < 0)
-    fatal_error (UNKNOWN_LOCATION, "environment variable $SOURCE_DATE_EPOCH: "
-		 "value must be nonnegative: %lld \n", epoch);
+  if (errno != 0 || endptr == source_date_epoch || *endptr != '\0'
+      || epoch < 0 || epoch > MAX_SOURCE_DATE_EPOCH)
+    {
+      error_at (input_location, "environment variable SOURCE_DATE_EPOCH must "
+	        "expand to a non-negative integer less than or equal to %wd",
+		MAX_SOURCE_DATE_EPOCH);
+      return (time_t) -1;
+    }
 
   return (time_t) epoch;
 }
diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index ba0a5d7df50..977ae9df5ea 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -1063,6 +1063,16 @@ extern vec<tree, va_gc> *make_tree_vector_copy (const vec<tree, va_gc> *);
    c_register_builtin_type.  */
 extern GTY(()) tree registered_builtin_types;
 
+/* Read SOURCE_DATE_EPOCH from environment to have a deterministic
+   timestamp to replace embedded current dates to get reproducible
+   results.  Returns -1 if SOURCE_DATE_EPOCH is not defined.  */
+extern time_t cb_get_source_date_epoch (cpp_reader *pfile);
+
+/* The value (as a unix timestamp) corresponds to date
+   "Dec 31 9999 23:59:59 UTC", which is the latest date that __DATE__ and
+   __TIME__ can store.  */
+#define MAX_SOURCE_DATE_EPOCH HOST_WIDE_INT_C (253402300799)
+
 /* In c-gimplify.c  */
 extern void c_genericize (tree);
 extern int c_gimplify_expr (tree *, gimple_seq *, gimple_seq *);
@@ -1438,9 +1448,4 @@ extern tree cilk_for_number_of_iterations (tree);
 extern bool check_no_cilk (tree, const char *, const char *,
 		           location_t loc = UNKNOWN_LOCATION);
 
-/* Read SOURCE_DATE_EPOCH from environment to have a deterministic
-   timestamp to replace embedded current dates to get reproducible
-   results.  Returns -1 if SOURCE_DATE_EPOCH is not defined.  */
-extern time_t get_source_date_epoch (void);
-
 #endif /* ! GCC_C_COMMON_H */
diff --git a/gcc/c-family/c-lex.c b/gcc/c-family/c-lex.c
index e68471b9d2b..3f78073f640 100644
--- a/gcc/c-family/c-lex.c
+++ b/gcc/c-family/c-lex.c
@@ -97,6 +97,7 @@ init_c_lex (void)
   cb->valid_pch = c_common_valid_pch;
   cb->read_pch = c_common_read_pch;
   cb->has_attribute = c_common_has_attribute;
+  cb->get_source_date_epoch = cb_get_source_date_epoch;
 
   /* Set the debug callbacks if we can use them.  */
   if ((debug_info_level == DINFO_LEVEL_VERBOSE
@@ -402,9 +403,6 @@ c_lex_with_flags (tree *value, location_t *loc, unsigned char *cpp_flags,
   enum cpp_ttype type;
   unsigned char add_flags = 0;
   enum overflow_type overflow = OT_NONE;
-  time_t source_date_epoch = get_source_date_epoch ();
-
-  cpp_init_source_date_epoch (parse_in, source_date_epoch);
 
   timevar_push (TV_CPP);
  retry:
diff --git a/gcc/doc/cppenv.texi b/gcc/doc/cppenv.texi
index 3b5317beb53..7b4cf6adc11 100644
--- a/gcc/doc/cppenv.texi
+++ b/gcc/doc/cppenv.texi
@@ -81,7 +81,6 @@ main input file is omitted.
 @end ifclear
 
 @item SOURCE_DATE_EPOCH
-
 If this variable is set, its value specifies a UNIX timestamp to be
 used in replacement of the current date and time in the @code{__DATE__}
 and @code{__TIME__} macros, so that the embedded timestamps become
@@ -89,8 +88,9 @@ reproducible.
 
 The value of @env{SOURCE_DATE_EPOCH} must be a UNIX timestamp,
 defined as the number of seconds (excluding leap seconds) since
-01 Jan 1970 00:00:00 represented in ASCII, identical to the output of
-@samp{@command{date +%s}}.
+01 Jan 1970 00:00:00 represented in ASCII; identical to the output of
+@samp{@command{date +%s}} on GNU/Linux and other systems that support the
+@code{%s} extension in the @code{date} command.
 
 The value should be a known timestamp such as the last modification
 time of the source or package and it should be set by the build
diff --git a/gcc/gcc.c b/gcc/gcc.c
index d956c36b151..2709f295734 100644
--- a/gcc/gcc.c
+++ b/gcc/gcc.c
@@ -3328,6 +3328,29 @@ save_switch (const char *opt, size_t n_args, const char *const *args,
   n_switches++;
 }
 
+/* Set the SOURCE_DATE_EPOCH environment variable to the current time if it is
+   not set already.  */
+
+static void
+set_source_date_epoch_envvar ()
+{
+  /* Array size is 21 = ceil(log_10(2^64)) + 1 to hold string representations
+     of 64 bit integers.  */
+  char source_date_epoch[21];
+  time_t tt;
+
+  errno = 0;
+  tt = time (NULL);
+  if (tt < (time_t) 0 || errno != 0)
+    tt = (time_t) 0;
+
+  snprintf (source_date_epoch, 21, "%llu", (unsigned long long) tt);
+  /* Using setenv instead of xputenv because we want the variable to remain
+     after finalizing so that it's still set in the second run when using
+     -fcompare-debug.  */
+  setenv ("SOURCE_DATE_EPOCH", source_date_epoch, 0);
+}
+
 /* Handle an option DECODED that is unknown to the option-processing
    machinery.  */
 
@@ -3628,6 +3651,7 @@ driver_handle_option (struct gcc_options *opts,
       else
 	compare_debug_opt = arg;
       save_switch (compare_debug_replacement_opt, 0, NULL, validated, true);
+      set_source_date_epoch_envvar ();
       return true;
 
     case OPT_fdiagnostics_color_:
diff --git a/gcc/testsuite/gcc.dg/cpp/source_date_epoch-1.c b/gcc/testsuite/gcc.dg/cpp/source_date_epoch-1.c
new file mode 100644
index 00000000000..f6aa1a360ff
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/cpp/source_date_epoch-1.c
@@ -0,0 +1,11 @@
+/* { dg-do run } */
+/* { dg-set-compiler-env-var SOURCE_DATE_EPOCH "630333296" } */
+
+int
+main(void)
+{
+  __builtin_printf ("%s %s\n", __DATE__, __TIME__);
+  return 0;
+}
+
+/* { dg-output "^Dec 22 1989 12:34:56\n$" } */
diff --git a/gcc/testsuite/gcc.dg/cpp/source_date_epoch-2.c b/gcc/testsuite/gcc.dg/cpp/source_date_epoch-2.c
new file mode 100644
index 00000000000..ae18362ae87
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/cpp/source_date_epoch-2.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-set-compiler-env-var SOURCE_DATE_EPOCH "AAA" } */
+
+/* Make sure that SOURCE_DATE_EPOCH is only parsed once */
+
+int
+main(void)
+{
+  __builtin_printf ("%s %s\n", __DATE__, __TIME__); /* { dg-error "SOURCE_DATE_EPOCH must expand" } */
+  __builtin_printf ("%s %s\n", __DATE__, __TIME__);
+  return 0;
+}
diff --git a/gcc/testsuite/lib/gcc-dg.exp b/gcc/testsuite/lib/gcc-dg.exp
index 4fa433d9954..7656b2254a1 100644
--- a/gcc/testsuite/lib/gcc-dg.exp
+++ b/gcc/testsuite/lib/gcc-dg.exp
@@ -324,6 +324,38 @@ proc restore-target-env-var { } {
     }
 }
 
+proc dg-set-compiler-env-var { args } {
+    global set_compiler_env_var
+    global saved_compiler_env_var
+    if { [llength $args] != 3 } {
+	error "dg-set-compiler-env-var: need two arguments"
+	return
+    }
+    set var [lindex $args 1]
+    set value [lindex $args 2]
+    if [info exists ::env($var)] {
+      lappend saved_compiler_env_var [list $var 1 $::env($var)]
+    } else {
+      lappend saved_compiler_env_var [list $var 0]
+    }
+    setenv $var $value
+    lappend set_compiler_env_var [list $var $value]
+}
+
+proc restore-compiler-env-var { } {
+    global saved_compiler_env_var
+    for { set env_vari [llength $saved_compiler_env_var] } {
+          [incr env_vari -1] >= 0 } {} {
+	set env_var [lindex $saved_compiler_env_var $env_vari]
+	set var [lindex $env_var 0]
+	if [lindex $env_var 1] {
+	    setenv $var [lindex $env_var 2]
+	} else {
+	    unsetenv $var
+	}
+    }
+}
+
 # Utility routines.
 
 #
@@ -785,6 +817,11 @@ if { [info procs saved-dg-test] == [list] } {
 	if [info exists set_target_env_var] {
 	    unset set_target_env_var
 	}
+	if [info exists set_compiler_env_var] {
+	    restore-compiler-env-var
+	    unset set_compiler_env_var
+	    unset saved_compiler_env_var
+	}
 	unset_timeout_vars
 	if [info exists compiler_conditional_xfail_data] {
 	    unset compiler_conditional_xfail_data
diff --git a/libcpp/include/cpplib.h b/libcpp/include/cpplib.h
index 7a5481219be..867aeebc39f 100644
--- a/libcpp/include/cpplib.h
+++ b/libcpp/include/cpplib.h
@@ -585,6 +585,9 @@ struct cpp_callbacks
 
   /* Callback that can change a user builtin into normal macro.  */
   bool (*user_builtin_macro) (cpp_reader *, cpp_hashnode *);
+
+  /* Callback to parse SOURCE_DATE_EPOCH from environment.  */
+  time_t (*get_source_date_epoch) (cpp_reader *);
 };
 
 #ifdef VMS
@@ -775,9 +778,6 @@ extern void cpp_init_special_builtins (cpp_reader *);
 /* Set up built-ins like __FILE__.  */
 extern void cpp_init_builtins (cpp_reader *, int);
 
-/* Initialize the source_date_epoch value.  */
-extern void cpp_init_source_date_epoch (cpp_reader *, time_t);
-
 /* This is called after options have been parsed, and partially
    processed.  */
 extern void cpp_post_options (cpp_reader *);
diff --git a/libcpp/init.c b/libcpp/init.c
index a8d00f4628b..61c9bbbf945 100644
--- a/libcpp/init.c
+++ b/libcpp/init.c
@@ -254,6 +254,9 @@ cpp_create_reader (enum c_lang lang, cpp_hash_table *table,
   /* Do not force token locations by default.  */
   pfile->forced_token_location_p = NULL;
 
+  /* Initialize source_date_epoch to -2 (not yet set).  */
+  pfile->source_date_epoch = (time_t) -2;
+
   /* The expression parser stack.  */
   _cpp_expand_op_stack (pfile);
 
@@ -530,13 +533,6 @@ cpp_init_builtins (cpp_reader *pfile, int hosted)
     _cpp_define_builtin (pfile, "__OBJC__ 1");
 }
 
-/* Initialize the source_date_epoch value.  */
-void
-cpp_init_source_date_epoch (cpp_reader *pfile, time_t source_date_epoch)
-{
-  pfile->source_date_epoch = source_date_epoch; 
-}
-
 /* Sanity-checks are dependent on command-line options, so it is
    called as a subroutine of cpp_read_main_file ().  */
 #if ENABLE_CHECKING
diff --git a/libcpp/internal.h b/libcpp/internal.h
index 8507eba1747..226ae328e76 100644
--- a/libcpp/internal.h
+++ b/libcpp/internal.h
@@ -503,7 +503,8 @@ struct cpp_reader
   const unsigned char *time;
 
   /* Externally set timestamp to replace current date and time useful for
-     reproducibility.  */
+     reproducibility.  It should be initialized to -2 (not yet set) and
+     set to -1 to disable it or to a non-negative value to enable it.  */
   time_t source_date_epoch;
 
   /* EOF token, and a token forcing paste avoidance.  */
diff --git a/libcpp/macro.c b/libcpp/macro.c
index 3f3b278e97d..756c7c6e0c6 100644
--- a/libcpp/macro.c
+++ b/libcpp/macro.c
@@ -351,9 +351,13 @@ _cpp_builtin_macro_text (cpp_reader *pfile, cpp_hashnode *node)
 	  struct tm *tb = NULL;
 
 	  /* Set a reproducible timestamp for __DATE__ and __TIME__ macro
-	     usage if SOURCE_DATE_EPOCH is defined.  */
-	  if (pfile->source_date_epoch != (time_t) -1)
-	     tb = gmtime (&pfile->source_date_epoch);
+	     if SOURCE_DATE_EPOCH is defined.  */
+	  if (pfile->source_date_epoch == (time_t) -2
+	      && pfile->cb.get_source_date_epoch != NULL)
+	    pfile->source_date_epoch = pfile->cb.get_source_date_epoch (pfile);
+
+	  if (pfile->source_date_epoch >= (time_t) 0)
+	    tb = gmtime (&pfile->source_date_epoch);
 	  else
 	    {
 	      /* (time_t) -1 is a legitimate value for "number of seconds
-- 
2.11.0

(guix build syscalls) (ice-9 match) (system repl repl) (srfi srfi-1) (srfi srfi-26)) (boot-hurd-system))))) (define (hurd-rc-entry rc) "Return, as a monadic value, an entry for the RC script in the system directory." (mlet %store-monad ((rc (lower-object rc))) (return `(("rc" ,rc))))) (define hurd-startup-service-type ;; The service that creates the initial SYSTEM/rc startup file. (service-type (name 'startup) (extensions (list (service-extension system-service-type hurd-rc-entry))) (default-value %hurd-rc-script) (description "This service creates an @file{rc} script in the system; that script is responsible for booting the Hurd."))) (define %hurd-startup-service ;; The service that produces the RC script. (service hurd-startup-service-type %hurd-rc-script)) (define special-files-service-type ;; Service to install "special files" such as /bin/sh and /usr/bin/env. (service-type (name 'special-files) (extensions (list (service-extension activation-service-type (lambda (files) #~(activate-special-files '#$files))))) (compose concatenate) (extend append) (description "Add special files to the root file system---e.g., @file{/usr/bin/env}."))) (define (extra-special-file file target) "Use TARGET as the \"special file\" FILE. For example, TARGET might be (file-append coreutils \"/bin/env\") and FILE could be \"/usr/bin/env\"." (simple-service (string->symbol (string-append "special-file-" file)) special-files-service-type `((,file ,target)))) (define (etc-directory service) "Return the directory for SERVICE, a service of type ETC-SERVICE-TYPE." (files->etc-directory (service-value service))) (define (files->etc-directory files) (define (assert-no-duplicates files) (let loop ((files files) (seen (set))) (match files (() #t) (((file _) rest ...) (when (set-contains? seen file) (raise (formatted-message (G_ "duplicate '~a' entry for /etc") file))) (loop rest (set-insert file seen)))))) ;; Detect duplicates early instead of letting them through, eventually ;; leading to a build failure of "etc.drv". (assert-no-duplicates files) (file-union "etc" files)) (define (etc-entry files) "Return an entry for the /etc directory consisting of FILES in the system directory." (with-monad %store-monad (return `(("etc" ,(files->etc-directory files)))))) (define etc-service-type (service-type (name 'etc) (extensions (list (service-extension activation-service-type (lambda (files) (let ((etc (files->etc-directory files))) #~(activate-etc #$etc)))) (service-extension system-service-type etc-entry))) (compose concatenate) (extend append) (description "Populate the @file{/etc} directory."))) (define (etc-service files) "Return a new service of ETC-SERVICE-TYPE that populates /etc with FILES. FILES must be a list of name/file-like object pairs." (service etc-service-type files)) (define (setuid-program->activation-gexp programs) "Return an activation gexp for setuid-program from PROGRAMS." (let ((programs (map (lambda (program) ;; FIXME This is really ugly, I didn't managed to use ;; "inherit" (let ((program-name (setuid-program-program program)) (setuid? (setuid-program-setuid? program)) (setgid? (setuid-program-setgid? program)) (user (setuid-program-user program)) (group (setuid-program-group program)) ) #~(setuid-program (setuid? #$setuid?) (setgid? #$setgid?) (user #$user) (group #$group) (program #$program-name)))) programs))) (with-imported-modules (source-module-closure '((gnu system setuid))) #~(begin (use-modules (gnu system setuid)) (activate-setuid-programs (list #$@programs)))))) (define setuid-program-service-type (service-type (name 'setuid-program) (extensions (list (service-extension activation-service-type setuid-program->activation-gexp))) (compose concatenate) (extend (lambda (config extensions) (append config extensions))) (description "Populate @file{/run/setuid-programs} with the specified executables, making them setuid and/or setgid."))) (define (packages->profile-entry packages) "Return a system entry for the profile containing PACKAGES." ;; XXX: 'mlet' is needed here for one reason: to get the proper ;; '%current-target' and '%current-target-system' bindings when ;; 'packages->manifest' is called, and thus when the 'package-inputs' ;; etc. procedures are called on PACKAGES. That way, conditionals in those ;; inputs see the "correct" value of these two parameters. See ;; . (mlet %store-monad ((_ (current-target-system))) (return `(("profile" ,(profile (content (packages->manifest (delete-duplicates packages eq?))))))))) (define profile-service-type ;; The service that populates the system's profile---i.e., ;; /run/current-system/profile. It is extended by package lists. (service-type (name 'profile) (extensions (list (service-extension system-service-type packages->profile-entry))) (compose concatenate) (extend append) (description "This is the @dfn{system profile}, available as @file{/run/current-system/profile}. It contains packages that the sysadmin wants to be globally available to all the system users."))) (define (firmware->activation-gexp firmware) "Return a gexp to make the packages listed in FIRMWARE loadable by the kernel." (let ((directory (directory-union "firmware" firmware))) ;; Tell the kernel where firmware is. #~(activate-firmware (string-append #$directory "/lib/firmware")))) (define firmware-service-type ;; The service that collects firmware. (service-type (name 'firmware) (extensions (list (service-extension activation-service-type firmware->activation-gexp))) (compose concatenate) (extend append) (description "Make ``firmware'' files loadable by the operating system kernel. Firmware may then be uploaded to some of the machine's devices, such as Wifi cards."))) (define (gc-roots->system-entry roots) "Return an entry in the system's output containing symlinks to ROOTS." (mlet %store-monad ((entry (gexp->derivation "gc-roots" #~(let ((roots '#$roots)) (mkdir #$output) (chdir #$output) (for-each symlink roots (map number->string (iota (length roots)))))))) (return (if (null? roots) '() `(("gc-roots" ,entry)))))) (define gc-root-service-type ;; A service to associate extra garbage-collector roots to the system. This ;; is a simple hack that guarantees that the system retains references to ;; the given list of roots. Roots must be "lowerable" objects like ;; packages, or derivations. (service-type (name 'gc-roots) (extensions (list (service-extension system-service-type gc-roots->system-entry))) (compose concatenate) (extend append) (description "Register garbage-collector roots---i.e., store items that will not be reclaimed by the garbage collector.") (default-value '()))) ;; Configuration for the Linux kernel builder. (define-record-type* linux-builder-configuration make-linux-builder-configuration linux-builder-configuration? this-linux-builder-configuration (kernel linux-builder-configuration-kernel) ; package (modules linux-builder-configuration-modules (default '()))) ; list of packages (define (package-for-kernel target-kernel module-package) "Return a package like MODULE-PACKAGE, adapted for TARGET-KERNEL, if possible (that is if there's a LINUX keyword argument in the build system)." (package (inherit module-package) (arguments (substitute-keyword-arguments (package-arguments module-package) ((#:linux kernel #f) target-kernel))))) (define (linux-builder-configuration->system-entry config) "Return the kernel entry of the 'system' directory." (let* ((kernel (linux-builder-configuration-kernel config)) (modules (linux-builder-configuration-modules config)) (kernel (profile (content (packages->manifest (cons kernel (map (lambda (module) (cond ((package? module) (package-for-kernel kernel module)) ;; support (,package "kernel-module-output") ((and (list? module) (package? (car module))) (cons (package-for-kernel kernel (car module)) (cdr module))) (else module))) modules)))) (hooks (list linux-module-database))))) (with-monad %store-monad (return `(("kernel" ,kernel)))))) (define linux-builder-service-type (service-type (name 'linux-builder) (extensions (list (service-extension system-service-type linux-builder-configuration->system-entry))) (default-value '()) (compose identity) (extend (lambda (config modifiers) (if (null? modifiers) config ((apply compose modifiers) config)))) (description "Builds the linux-libre kernel profile, containing the kernel itself and any linux-loadable kernel modules. This can be extended with a function that accepts the current configuration and returns a new configuration."))) (define (linux-loadable-module-builder-modifier modules) "Extends linux-builder-service-type by appending the given MODULES to the configuration of linux-builder-service-type." (lambda (config) (linux-builder-configuration (inherit config) (modules (append (linux-builder-configuration-modules config) modules))))) (define linux-loadable-module-service-type (service-type (name 'linux-loadable-modules) (extensions (list (service-extension linux-builder-service-type linux-loadable-module-builder-modifier))) (default-value '()) (compose concatenate) (extend append) (description "Adds packages and package outputs as modules included in the booted linux-libre profile. Other services can extend this service type to add particular modules to the set of linux-loadable modules."))) ;;; ;;; Service folding. ;;; (define-condition-type &missing-target-service-error &service-error missing-target-service-error? (service missing-target-service-error-service) (target-type missing-target-service-error-target-type)) (define-condition-type &ambiguous-target-service-error &service-error ambiguous-target-service-error? (service ambiguous-target-service-error-service) (target-type ambiguous-target-service-error-target-type)) (define (missing-target-error service target-type) (raise (condition (&missing-target-service-error (service service) (target-type target-type)) (&message (message (format #f (G_ "no target of type '~a' for service '~a'") (service-type-name target-type) (service-type-name (service-kind service)))))))) (define (service-back-edges services) "Return a procedure that, when passed a , returns the list of objects that depend on it." (define (add-edges service edges) (define (add-edge extension edges) (let ((target-type (service-extension-target extension))) (match (filter (lambda (service) (eq? (service-kind service) target-type)) services) ((target) (vhash-consq target service edges)) (() (missing-target-error service target-type)) (x (raise (condition (&ambiguous-target-service-error (service service) (target-type target-type)) (&message (message (format #f (G_ "more than one target service of type '~a'") (service-type-name target-type)))))))))) (fold add-edge edges (service-type-extensions (service-kind service)))) (let ((edges (fold add-edges vlist-null services))) (lambda (node) (reverse (vhash-foldq* cons '() node edges))))) (define (instantiate-missing-services services) "Return SERVICES, a list, augmented with any services targeted by extensions and missing from SERVICES. Only service types with a default value can be instantiated; other missing services lead to a '&missing-target-service-error'." (define (adjust-service-list svc result instances) (fold2 (lambda (extension result instances) (define target-type (service-extension-target extension)) (match (vhash-assq target-type instances) (#f (let ((default (service-type-default-value target-type))) (if (eq? &no-default-value default) (missing-target-error svc target-type) (let ((new (service target-type))) (values (cons new result) (vhash-consq target-type new instances)))))) (_ (values result instances)))) result instances (service-type-extensions (service-kind svc)))) (let loop ((services services)) (define instances (fold (lambda (service result) (vhash-consq (service-kind service) service result)) vlist-null services)) (define adjusted (fold2 adjust-service-list services instances services)) ;; If we instantiated services, they might in turn depend on missing ;; services. Loop until we've reached fixed point. (if (= (length adjusted) (vlist-length instances)) adjusted (loop adjusted)))) (define* (fold-services services #:key (target-type system-service-type)) "Fold SERVICES by propagating their extensions down to the root of type TARGET-TYPE; return the root service adjusted accordingly." (define dependents (service-back-edges services)) (define (matching-extension target) (let ((target (service-kind target))) (match-lambda (($ type) (eq? type target))))) (define (apply-extension target) (lambda (service) (match (find (matching-extension target) (service-type-extensions (service-kind service))) (($ _ compute) (compute (service-value service)))))) (match (filter (lambda (service) (eq? (service-kind service) target-type)) services) ((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 (make-compound-condition (condition (&missing-target-service-error (service #f) (target-type target-type))) (formatted-message (G_ "service of type '~a' not found") (service-type-name target-type))))) (x (raise (condition (&ambiguous-target-service-error (service #f) (target-type target-type)) (&message (message (format #f (G_ "more than one target service of type '~a'") (service-type-name target-type))))))))) ;;; services.scm ends here.