From patchwork Fri Sep 3 05:29:46 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Sarah Morgensen X-Patchwork-Id: 32534 Return-Path: X-Original-To: patchwork@mira.cbaines.net Delivered-To: patchwork@mira.cbaines.net Received: by mira.cbaines.net (Postfix, from userid 113) id 00E0827BBE3; Fri, 3 Sep 2021 06:30:28 +0100 (BST) X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on mira.cbaines.net X-Spam-Level: X-Spam-Status: No, score=-2.8 required=5.0 tests=BAYES_00,DKIM_SIGNED, MAILING_LIST_MULTI,RCVD_IN_MSPIKE_H2,SPF_HELO_PASS,T_DKIM_INVALID, URIBL_BLOCKED autolearn=unavailable autolearn_force=no version=3.4.2 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mira.cbaines.net (Postfix) with ESMTPS id A2F4027BBE1 for ; Fri, 3 Sep 2021 06:30:27 +0100 (BST) Received: from localhost ([::1]:60682 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1mM1mU-0007Ci-QH for patchwork@mira.cbaines.net; Fri, 03 Sep 2021 01:30:26 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:58878) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1mM1m6-0007CI-S4 for guix-patches@gnu.org; Fri, 03 Sep 2021 01:30:02 -0400 Received: from debbugs.gnu.org ([209.51.188.43]:59230) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1mM1m6-00073v-KN for guix-patches@gnu.org; Fri, 03 Sep 2021 01:30:02 -0400 Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1mM1m6-0005GL-5S for guix-patches@gnu.org; Fri, 03 Sep 2021 01:30:02 -0400 X-Loop: help-debbugs@gnu.org Subject: [bug#50350] [PATCH core-updates] utils: Add helpers for list environment variables. Resent-From: Sarah Morgensen Original-Sender: "Debbugs-submit" Resent-CC: guix-patches@gnu.org Resent-Date: Fri, 03 Sep 2021 05:30:02 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: report 50350 X-GNU-PR-Package: guix-patches X-GNU-PR-Keywords: patch To: 50350@debbugs.gnu.org X-Debbugs-Original-To: guix-patches@gnu.org Received: via spool by submit@debbugs.gnu.org id=B.163064700020186 (code B ref -1); Fri, 03 Sep 2021 05:30:02 +0000 Received: (at submit) by debbugs.gnu.org; 3 Sep 2021 05:30:00 +0000 Received: from localhost ([127.0.0.1]:42543 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1mM1m3-0005FV-Of for submit@debbugs.gnu.org; Fri, 03 Sep 2021 01:30:00 -0400 Received: from lists.gnu.org ([209.51.188.17]:35854) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1mM1m1-0005FM-14 for submit@debbugs.gnu.org; Fri, 03 Sep 2021 01:29:58 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:58858) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1mM1m0-0007Bn-R2 for guix-patches@gnu.org; Fri, 03 Sep 2021 01:29:56 -0400 Received: from out0.migadu.com ([94.23.1.103]:25584) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1mM1lw-0006sS-Ds for guix-patches@gnu.org; Fri, 03 Sep 2021 01:29:56 -0400 X-Report-Abuse: Please report any abuse attempt to abuse@migadu.com and include these headers. DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mgsn.dev; s=key1; t=1630646988; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=iQC+mZYN4Rsdj+aDnx39efA31j+S0LX28mc7cOdntl8=; b=BqNDgTvCsOPXpEARgWmNnV0M5IZ35RLdD7yXgz2CCx7BU9xLi91TfNFoRq1A9SdE86E/wY taHTySylJA9nxBYbCso4B2barBfh8tDUGbpD7I9Rs2WzpT31ieWLNBnp8zaGdKLNhdD67V O+w/K2ezcUpISgZnpRgeVvN8/UgwAt8= From: Sarah Morgensen Date: Thu, 2 Sep 2021 22:29:46 -0700 Message-Id: <40d8e502f754fd7bbbe3a916950a85d1f1c57073.1630646500.git.iskarian@mgsn.dev> MIME-Version: 1.0 X-Migadu-Flow: FLOW_OUT X-Migadu-Auth-User: iskarian@mgsn.dev Received-SPF: pass client-ip=94.23.1.103; envelope-from=iskarian@mgsn.dev; helo=out0.migadu.com X-Spam_score_int: -23 X-Spam_score: -2.4 X-Spam_bar: -- X-Spam_report: (-2.4 / 5.0 requ) BAYES_00=-1.9, DKIM_INVALID=0.1, DKIM_SIGNED=0.1, RCVD_IN_DNSWL_LOW=-0.7, RCVD_IN_MSPIKE_H2=-0.001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-BeenThere: guix-patches@gnu.org List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: guix-patches-bounces+patchwork=mira.cbaines.net@gnu.org Sender: "Guix-patches" X-getmail-retrieved-from-mailbox: Patches Add helpers 'getenv/list', 'setenv/list', 'setenv/list-extend', and 'setenv/list-delete' for list environment variables (such as search paths). * guix/build/utils.scm (getenv/list, setenv/list) (setenv/list-extend, setenv/list-delete): New procedures. * .dir-locals.el (scheme-mode): Indent them. * tests/build-utils.scm ("getenv/list", "getenv/list: unset") ("setenv/list: ignore empty elements") ("setenv/list: unset if empty") ("setenv/list-extend: single element, prepend") ("setenv/list-extend: multiple elements, prepend") ("setenv/list-extend: multiple elements, append") ("setenv/list-delete: single deletion") ("setenv/list-delete: multiple deletions"): New tests. --- Hello Guix, I noticed that there are over 200 occurrences of this pattern in packages: --8<---------------cut here---------------start------------->8--- (setenv "PYTHONPATH" (string-append (getcwd) ":" (getenv "PYTHONPATH"))) --8<---------------cut here---------------end--------------->8--- This patch introduces some helper procedures for these kinds of cases. With this patch, instead of the above you could write: (setenv/list-extend "PYTHONPATH" (getcwd)) Sometimes you want to add to the end of the path: --8<---------------cut here---------------start------------->8--- (setenv "GEM_PATH" (string-append (getenv "GEM_PATH") ":" new-gem)) --8<---------------cut here---------------end--------------->8--- With this patch, you could write instead: (setenv/list-extend "GEM_PATH" new-gem #:prepend? #f) Adding include paths becomes much more readable in conjunction with search-input-directory, with this: --8<---------------cut here---------------start------------->8--- (setenv "CPATH" (string-append (assoc-ref inputs "libtirpc") "/include/tirpc/:" (or (getenv "CPATH") ""))) --8<---------------cut here---------------end--------------->8--- becoming this: (setenv/list-extend "CPATH" (search-input-directory "/include/tirpc")) A less common case, of removing a path: --8<---------------cut here---------------start------------->8--- (setenv "CPLUS_INCLUDE_PATH" (string-join (delete (string-append gcc "/include/c++") (string-split (getenv "CPLUS_INCLUDE_PATH") #\:)) ":")) --8<---------------cut here---------------end--------------->8--- becomes this: (setenv/list-delete "CPLUS_INCLUDE_PATH" (string-append gcc "/include/c++")) What do you all think? (Bikeshed opportunity: I'm not in love with the names. I originally named these 'setenv/path' rather than 'setenv/list', because I wanted to avoid confusion with Guix's search paths, but I'm not sure 'setenv/list' is actually more clear. I considered getenv*, setenv*, and so on, but I didn't think they were quite clear enough either. I did consider 'setenv/list-extend!' and 'setenv/list-delete!' since they do modify the env var in place, but "setenv" should already imply that. Finally, it might be better to have e.g. 'setenv/path-prepend!' and 'setenv/path-append!' rather than the single 'setenv/path-extend', but I could not settle on memorable, representative names. Using 'append' carries a connotation that you are dealing with lists, because of 'append', but it also accepts a single element. Using 'extend'/'prepend' together seems confusing to me, because I might reach for 'extend' to add to the beginning of the list if I forget about 'prepend'.) -- Sarah .dir-locals.el | 4 ++++ guix/build/utils.scm | 56 +++++++++++++++++++++++++++++++++++++++++++ tests/build-utils.scm | 53 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+) base-commit: 693d75e859150601145b7f7303f61d4f48e76927 diff --git a/.dir-locals.el b/.dir-locals.el index 919ed1d1c4..4b58220526 100644 --- a/.dir-locals.el +++ b/.dir-locals.el @@ -69,6 +69,10 @@ (eval . (put 'add-before 'scheme-indent-function 2)) (eval . (put 'add-after 'scheme-indent-function 2)) + (eval . (put 'setenv/list 'scheme-indent-function 1)) + (eval . (put 'setenv/list-extend 'scheme-indent-function 1)) + (eval . (put 'setenv/list-delete 'scheme-indent-function 1)) + (eval . (put 'modify-services 'scheme-indent-function 1)) (eval . (put 'with-directory-excursion 'scheme-indent-function 1)) (eval . (put 'with-file-lock 'scheme-indent-function 1)) diff --git a/guix/build/utils.scm b/guix/build/utils.scm index 3beb7da67a..d0ac33a64f 100644 --- a/guix/build/utils.scm +++ b/guix/build/utils.scm @@ -8,6 +8,7 @@ ;;; Copyright © 2020 Efraim Flashner ;;; Copyright © 2020, 2021 Maxim Cournoyer ;;; Copyright © 2021 Maxime Devos +;;; Copyright © 2021 Sarah Morgensen ;;; ;;; This file is part of GNU Guix. ;;; @@ -75,6 +76,11 @@ find-files false-if-file-not-found + getenv/list + setenv/list + setenv/list-extend + setenv/list-delete + search-path-as-list set-path-environment-variable search-path-as-string->list @@ -521,6 +527,56 @@ also be included. If FAIL-ON-ERROR? is true, raise an exception upon error." #f (apply throw args))))) + +;;; +;;; Multiple-valued environment variables. +;;; + +(define* (setenv/list env-var lst #:key (separator #\:)) + "Set environment variable ENV-VAR to the elements of LST separated by +SEPARATOR. Empty elements are ignored. If ENV-VAR would be set to the empty +string, unset ENV-VAR." + (let ((path (string-join (delete "" lst) (string separator)))) + (if (string-null? path) + (unsetenv env-var) + (setenv env-var path)))) + +(define* (getenv/list env-var #:key (separator #\:)) + "Return a list of the SEPARATOR-separated elements of environment variable +ENV-VAR, or the empty list if ENV-VAR is unset." + (or (and=> (getenv env-var) + (cut string-split <> separator)) + '())) + +(define* (setenv/list-extend env-var list-or-str + #:key (separator #\:) (prepend? #t)) + "Add the element(s) LIST-OR-STR to the environment variable ENV-VAR using +SEPARATOR between elements. Empty elements are ignored. Elements are placed +at the beginning if PREPEND? is #t, or at the end otherwise." + (let* ((elements (match list-or-str + ((? string? str) (list str)) + ((? list? lst) lst))) + (original (or (getenv env-var) "")) + (path-list (if prepend? + (append elements (list original)) + (cons original elements)))) + (when (not (null? elements)) + (setenv/list env-var path-list #:separator separator)))) + +(define* (setenv/list-delete env-var list-or-str #:key (separator #\:)) + "Remove the element(s) LIST-OR-STR from the SEPARATOR-separated environment +variable ENV-VAR, and set ENV-VAR to that value. If ENV-VAR would be set to +the empty string, unset ENV-VAR." + (let* ((elements (match list-or-str + ((? string? str) (list str)) + ((? list? lst) lst))) + (original (getenv/list env-var #:separator separator)) + (path-list (lset-difference string=? original elements)) + (path (string-join path-list (string separator)))) + (if (string-null? path) + (unsetenv env-var) + (setenv env-var path)))) + ;;; ;;; Search paths. diff --git a/tests/build-utils.scm b/tests/build-utils.scm index 6b131c0af8..b26bffd9a8 100644 --- a/tests/build-utils.scm +++ b/tests/build-utils.scm @@ -3,6 +3,7 @@ ;;; Copyright © 2019 Ricardo Wurmus ;;; Copyright © 2021 Maxim Cournoyer ;;; Copyright © 2021 Maxime Devos +;;; Copyright © 2021 Sarah Morgensen ;;; ;;; This file is part of GNU Guix. ;;; @@ -264,6 +265,58 @@ print('hello world')")) (lambda _ (get-string-all (current-input-port)))))))) +(test-equal "setenv/list: ignore empty elements" + "one:three" + (with-environment-variable "TEST_SETENV" #f + (setenv/list "TEST_SETENV" '("one" "" "three")) + (getenv "TEST_SETENV"))) + +(test-equal "setenv/list: unset if empty" + #f + (with-environment-variable "TEST_SETENV" #f + (setenv/list "TEST_SETENV" '()) + (getenv "TEST_SETENV"))) + +(test-equal "getenv/list" + '("one" "two" "three") + (with-environment-variable "TEST_SETENV" "one:two:three" + (getenv/list "TEST_SETENV"))) + +(test-equal "getenv/list: unset" + '() + (with-environment-variable "TEST_SETENV" #f + (getenv/list "TEST_SETENV"))) + +(test-equal "setenv/list-extend: single element, prepend" + "new:one:two" + (with-environment-variable "TEST_SETENV" "one:two" + (setenv/list-extend "TEST_SETENV" "new") + (getenv "TEST_SETENV"))) + +(test-equal "setenv/list-extend: multiple elements, prepend" + "first:second:one:two" + (with-environment-variable "TEST_SETENV" "one:two" + (setenv/list-extend "TEST_SETENV" '("first" "second")) + (getenv "TEST_SETENV"))) + +(test-equal "setenv/list-extend: multiple elements, append" + "one:two:first:second" + (with-environment-variable "TEST_SETENV" "one:two" + (setenv/list-extend "TEST_SETENV" '("first" "second") #:prepend? #f) + (getenv "TEST_SETENV"))) + +(test-equal "setenv/list-delete: single deletion" + "one:two:three" + (with-environment-variable "TEST_SETENV" "bad:one:two:bad:three:bad" + (setenv/list-delete "TEST_SETENV" "bad") + (getenv "TEST_SETENV"))) + +(test-equal "setenv/list-delete: multiple deletions" + "one:three" + (with-environment-variable "TEST_SETENV" "bad:one:two:bad:three:bad" + (setenv/list-delete "TEST_SETENV" '("bad" "two")) + (getenv "TEST_SETENV"))) + (test-equal "search-input-file: exception if not found" `((path) (file . "does-not-exist"))