From 043a51c0c2a025b84b0fb14c157add7236d7a526 Mon Sep 17 00:00:00 2001 From: Hartmut Goebel Date: Sun, 2 Oct 2016 18:48:56 +0200 Subject: guix: python-build-system: Fix an outdated comment. --- guix/build/python-build-system.scm | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'guix') diff --git a/guix/build/python-build-system.scm b/guix/build/python-build-system.scm index 9109fb4ac7..e906e60699 100644 --- a/guix/build/python-build-system.scm +++ b/guix/build/python-build-system.scm @@ -137,8 +137,7 @@ installed with setuptools." #t)) (define %standard-phases - ;; 'configure' and 'build' phases are not needed. Everything is done during - ;; 'install'. + ;; 'configure' phase is not needed. (modify-phases gnu:%standard-phases (add-after 'unpack 'ensure-no-mtimes-pre-1980 ensure-no-mtimes-pre-1980) (delete 'configure) -- cgit v1.2.3 From 7db40bce58e149ecb541d295e01cfbfe953d39a3 Mon Sep 17 00:00:00 2001 From: Hartmut Goebel Date: Wed, 28 Sep 2016 10:42:35 +0200 Subject: guix: build all Python packages with --single-version-externally-managed. This requires setuptools to be installed together with python, which is the case for Python 3 anyway and which we do for our build of Python 2 (see last commit). * guix/build/python-build-system.scm (install): Add "--single-version-externally-managed" and "--root=/" to params to be passed to call-setuppy. Remove thus needless manipulation of PYTHONPATH. Remove now unused argument "inputs". --- guix/build/python-build-system.scm | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) (limited to 'guix') diff --git a/guix/build/python-build-system.scm b/guix/build/python-build-system.scm index e906e60699..2424fed310 100644 --- a/guix/build/python-build-system.scm +++ b/guix/build/python-build-system.scm @@ -3,6 +3,7 @@ ;;; Copyright © 2013 Andreas Enge ;;; Copyright © 2013 Nikita Karetnikov ;;; Copyright © 2015 Mark H Weaver +;;; Copyright © 2016 Hartmut Goebel ;;; ;;; This file is part of GNU Guix. ;;; @@ -60,25 +61,15 @@ (major+minor (take components 2))) (string-join major+minor "."))) -(define* (install #:key outputs inputs (configure-flags '()) +(define* (install #:key outputs (configure-flags '()) #:allow-other-keys) "Install a given Python package." (let* ((out (assoc-ref outputs "out")) - (params (append (list (string-append "--prefix=" out)) - configure-flags)) - (python-version (get-python-version (assoc-ref inputs "python"))) - (old-path (getenv "PYTHONPATH")) - (add-path (string-append out "/lib/python" python-version - "/site-packages/"))) - ;; create the module installation directory and add it to PYTHONPATH - ;; to make setuptools happy - (mkdir-p add-path) - (setenv "PYTHONPATH" - (string-append (if old-path - (string-append old-path ":") - "") - add-path)) - (call-setuppy "install" params))) + (params (append (list (string-append "--prefix=" out) + "--single-version-externally-managed" + "--root=/") + configure-flags))) + (call-setuppy "install" params))) (define* (wrap #:key inputs outputs #:allow-other-keys) (define (list-of-files dir) -- cgit v1.2.3 From 46bcdcc287ecfc1db8b7a0429e72517f407b580d Mon Sep 17 00:00:00 2001 From: Marius Bakke Date: Thu, 29 Sep 2016 18:41:35 +0100 Subject: guix: python-build-system: Import setuptools before calling `setup.py'. This is needed for packages using "distutils" instead of "setuptools" since the former does not understand the "--single-version-externally-managed" flag. Also export __file__ since it will be unset when setup.py is called from python "exec". * guix/build/python-build-system.scm (call-setuppy): extend "python setup.py" call to import setuptools, export __file__, and call setup.py from setuptools python environment. Co-Authored-By: Hartmut Goebel --- guix/build/python-build-system.scm | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'guix') diff --git a/guix/build/python-build-system.scm b/guix/build/python-build-system.scm index 2424fed310..6086df3e82 100644 --- a/guix/build/python-build-system.scm +++ b/guix/build/python-build-system.scm @@ -36,13 +36,25 @@ ;; ;; Code: +(define setuptools-shim + ;; Run setup.py with "setuptools" being imported, which will patch + ;; "distutils". This is needed for packages using "distutils" instead of + ;; "setuptools" since the former does not understand the + ;; "--single-version-externally-managed" flag. + ;; Python code taken from pip 9.0.1 pip/utils/setuptools_build.py + (string-append + "import setuptools, tokenize;__file__='setup.py';" + "f=getattr(tokenize, 'open', open)(__file__);" + "code=f.read().replace('\\r\\n', '\\n');" + "f.close();" + "exec(compile(code, __file__, 'exec'))")) (define (call-setuppy command params) (if (file-exists? "setup.py") (begin (format #t "running \"python setup.py\" with command ~s and parameters ~s~%" command params) - (zero? (apply system* "python" "setup.py" command params))) + (zero? (apply system* "python" "-c" setuptools-shim command params))) (error "no setup.py found"))) (define* (build #:rest empty) -- cgit v1.2.3 From 5f7565d190cf380b7bae2ce12dba38aff98c4eb9 Mon Sep 17 00:00:00 2001 From: Hartmut Goebel Date: Sun, 2 Oct 2016 14:03:32 +0200 Subject: guix: python-build-system: Add option "#:use-setuptools?" (default true). * guix/build-system/python.scm (python-build): New keyword argument "#:use-setuptools?", defaulting to #t. * guix/build/python-build-system.scm (call-setup-py): New positional parameter "use-setuptools?". If false, do not use the shim-wrapper for addin setuptools. (build, check): accept keyword- parameter, and pass to call-setuppy. (install): same; if "use-setuptools?" is false, do not use options "--root" and "--single-version-externally-managed" for setup.py. * doc/guix.texi (Build Systems): Document it. --- doc/guix.texi | 5 +++++ guix/build-system/python.scm | 2 ++ guix/build/python-build-system.scm | 28 +++++++++++++++++----------- 3 files changed, 24 insertions(+), 11 deletions(-) (limited to 'guix') diff --git a/doc/guix.texi b/doc/guix.texi index 2691e24faf..5f2807654b 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -3137,6 +3137,11 @@ the @code{#:python} parameter. This is a useful way to force a package to be built for a specific version of the Python interpreter, which might be necessary if the package is only compatible with a single interpreter version. + +By default guix calls @code{setup.py} under control of +@code{setuptools}, much like @command{pip} does. Some packages are not +compatible with setuptools (and pip), thus you can disable this by +setting the @code{#:use-setuptools} parameter to @code{#f}. @end defvr @defvr {Scheme Variable} perl-build-system diff --git a/guix/build-system/python.scm b/guix/build-system/python.scm index adeceb4a89..d4d3d28f2a 100644 --- a/guix/build-system/python.scm +++ b/guix/build-system/python.scm @@ -177,6 +177,7 @@ pre-defined variants." #:key (tests? #t) (test-target "test") + (use-setuptools? #t) (configure-flags ''()) (phases '(@ (guix build python-build-system) %standard-phases)) @@ -204,6 +205,7 @@ provides a 'setup.py' file as its build system." #:system ,system #:test-target ,test-target #:tests? ,tests? + #:use-setuptools? ,use-setuptools? #:phases ,phases #:outputs %outputs #:search-paths ',(map search-path-specification->sexp diff --git a/guix/build/python-build-system.scm b/guix/build/python-build-system.scm index 6086df3e82..7ccc9386cf 100644 --- a/guix/build/python-build-system.scm +++ b/guix/build/python-build-system.scm @@ -49,22 +49,25 @@ "f.close();" "exec(compile(code, __file__, 'exec'))")) -(define (call-setuppy command params) +(define (call-setuppy command params use-setuptools?) (if (file-exists? "setup.py") (begin (format #t "running \"python setup.py\" with command ~s and parameters ~s~%" command params) - (zero? (apply system* "python" "-c" setuptools-shim command params))) + (if use-setuptools? + (zero? (apply system* "python" "-c" setuptools-shim + command params)) + (zero? (apply system* "python" "./setup.py" command params)))) (error "no setup.py found"))) -(define* (build #:rest empty) +(define* (build #:key use-setuptools? #:allow-other-keys) "Build a given Python package." - (call-setuppy "build" '())) + (call-setuppy "build" '() use-setuptools?)) -(define* (check #:key tests? test-target #:allow-other-keys) +(define* (check #:key tests? test-target use-setuptools? #:allow-other-keys) "Run the test suite of a given Python package." (if tests? - (call-setuppy test-target '()) + (call-setuppy test-target '() use-setuptools?) #t)) (define (get-python-version python) @@ -73,15 +76,18 @@ (major+minor (take components 2))) (string-join major+minor "."))) -(define* (install #:key outputs (configure-flags '()) +(define* (install #:key outputs (configure-flags '()) use-setuptools? #:allow-other-keys) "Install a given Python package." (let* ((out (assoc-ref outputs "out")) - (params (append (list (string-append "--prefix=" out) - "--single-version-externally-managed" - "--root=/") + (params (append (list (string-append "--prefix=" out)) + (if use-setuptools? + ;; distutils does not accept these flags + (list "--single-version-externally-managed" + "--root=/") + '()) configure-flags))) - (call-setuppy "install" params))) + (call-setuppy "install" params use-setuptools?))) (define* (wrap #:key inputs outputs #:allow-other-keys) (define (list-of-files dir) -- cgit v1.2.3 From a2ff4f0240f0fac484836bb8ffb2f86917369666 Mon Sep 17 00:00:00 2001 From: Hartmut Goebel Date: Sat, 8 Oct 2016 12:04:20 +0200 Subject: guix: python-build-system: Add helpers for getting and setting PYTHONPATH. * guix/build/python-build-system.scm (add-installed-pythonpath, site-packages): New exported procedures. --- guix/build/python-build-system.scm | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) (limited to 'guix') diff --git a/guix/build/python-build-system.scm b/guix/build/python-build-system.scm index 7ccc9386cf..22c4f7d38a 100644 --- a/guix/build/python-build-system.scm +++ b/guix/build/python-build-system.scm @@ -28,6 +28,8 @@ #:use-module (srfi srfi-1) #:use-module (srfi srfi-26) #:export (%standard-phases + add-installed-pythonpath + site-packages python-build)) ;; Commentary: @@ -76,6 +78,24 @@ (major+minor (take components 2))) (string-join major+minor "."))) +(define (site-packages inputs outputs) + "Return the path of the current output's Python site-package." + (let* ((out (assoc-ref outputs "out")) + (python (assoc-ref inputs "python"))) + (string-append out "/lib/python" + (get-python-version python) + "/site-packages/"))) + +(define (add-installed-pythonpath inputs outputs) + "Prepend the Python site-package of OUTPUT to PYTHONPATH. This is useful +when running checks after installing the package." + (let ((old-path (getenv "PYTHONPATH")) + (add-path (site-packages inputs outputs))) + (setenv "PYTHONPATH" + (string-append add-path + (if old-path (string-append ":" old-path) ""))) + #t)) + (define* (install #:key outputs (configure-flags '()) use-setuptools? #:allow-other-keys) "Install a given Python package." -- cgit v1.2.3 From b002f964bb3d69c77856ea7dcadfe82383050512 Mon Sep 17 00:00:00 2001 From: Hartmut Goebel Date: Fri, 7 Oct 2016 17:17:00 +0200 Subject: guix: python-build-system: Delete .egg-info file created in phase check. * guix/build/python-build-system.scm (check): Delete .egg-info dirs which did not exist prior to calling setup.py but afterwards. --- guix/build/python-build-system.scm | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'guix') diff --git a/guix/build/python-build-system.scm b/guix/build/python-build-system.scm index 22c4f7d38a..310ba8aa2e 100644 --- a/guix/build/python-build-system.scm +++ b/guix/build/python-build-system.scm @@ -69,7 +69,15 @@ (define* (check #:key tests? test-target use-setuptools? #:allow-other-keys) "Run the test suite of a given Python package." (if tests? - (call-setuppy test-target '() use-setuptools?) + ;; Running `setup.py test` creates an additional .egg-info directory in + ;; build/lib in some cases, e.g. if the source is in a sub-directory + ;; (given with `package_dir`). This will by copied to the output, too, + ;; so we need to remove. + (let ((before (find-files "build" "\\.egg-info$" #:directories? #t))) + (call-setuppy test-target '() use-setuptools?) + (let* ((after (find-files "build" "\\.egg-info$" #:directories? #t)) + (inter (lset-difference eqv? after before))) + (for-each delete-file-recursively inter))) #t)) (define (get-python-version python) -- cgit v1.2.3 From c1019287a4aab55ebffab4710b9a85b6c9f1b7ed Mon Sep 17 00:00:00 2001 From: Hartmut Goebel Date: Tue, 15 Nov 2016 16:57:21 +0100 Subject: guix: python-build-system: Add background about Python installation methods. --- guix/build/python-build-system.scm | 68 +++++++++++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) (limited to 'guix') diff --git a/guix/build/python-build-system.scm b/guix/build/python-build-system.scm index 310ba8aa2e..3f280b0ac0 100644 --- a/guix/build/python-build-system.scm +++ b/guix/build/python-build-system.scm @@ -36,7 +36,70 @@ ;; ;; Builder-side code of the standard Python package build procedure. ;; -;; Code: +;; +;; Backgound about the Python installation methods +;; +;; In Python there are different ways to install packages: distutils, +;; setuptools, easy_install and pip. All of these are sharing the file +;; setup.py, introduced with distutils in Python 2.0. The setup.py file can be +;; considered as a kind of Makefile accepting targets (or commands) like +;; "build" and "install". As of autumn 2016 the recommended way to install +;; Python packages is using pip. +;; +;; For both distutils and setuptools, running "python setup.py install" is the +;; way to install Python packages. With distutils the "install" command +;; basically copies all packages into /lib/pythonX.Y/site-packages. +;; +;; Some time later "setuptools" was established to enhance distutils. To use +;; setuptools, the developer imports setuptools in setup.py. When importing +;; setuptools, the original "install" command gets overwritten by setuptools' +;; "install" command. +;; +;; The command-line tools easy_install and pip are both capable of finding and +;; downloading the package source from PyPI (the Python Package Index). Both +;; of them import setuptools and execute the "setup.py" file under their +;; control. Thus the "setup.py" behaves as if the developer had imported +;; setuptools within setup.py - even is still using only distutils. +;; +;; Setuptools' "install" command (to be more precise: the "easy_install" +;; command which is called by "install") will put the path of the currently +;; installed version of each package and it's dependencies (as declared in +;; setup.py) into an "easy-install.pth" file. In Guix each packages gets its +;; own "site-packages" directory and thus an "easy-install.pth" of its own. +;; To avoid conflicts, the python build system renames the file to +;; .pth in the phase rename-pth-file. To ensure that Python will +;; process the .pth file, easy_install also creates a basic "site.py" in each +;; "site-packages" directory. The file is the same for all packages, thus +;; there is no need to rename it. For more information about .pth files and +;; the site module, please refere to +;; https://docs.python.org/3/library/site.html. +;; +;; The .pth files contain the file-system paths (pointing to the store) of all +;; dependencies. So the dependency is hidden in the .pth file but is not +;; visible in the file-system. Now if packages A and B both required packages +;; P, but in different versions, Guix will not detect this when installing +;; both A and B to a profile. (For details and example see +;; https://lists.gnu.org/archive/html/guix-devel/2016-10/msg01233.html.) +;; +;; Pip behaves a bit different then easy_install: it always executes +;; "setup.py" with the option "--single-version-externally-managed" set. This +;; makes setuptools' "install" command run the original "install" command +;; instead of the "easy_install" command, so no .pth file (and no site.py) +;; will be created. The "site-packages" directory only contains the package +;; and the related .egg-info directory. +;; +;; This is exactly what we need for Guix and this is what we mimic in the +;; install phase below. +;; +;; As a draw back, the magic of the .pth file of linking to the other required +;; packages is gone and these packages have now to be declared as +;; "propagated-inputs". +;; +;; Note: Importing setuptools also adds two sub-commands: "install_egg_info" +;; and "install_scripts". These sub-commands are executed even if +;; "--single-version-externally-managed" is set, thus the .egg-info directory +;; and the scripts defined in entry-points will always be created. + (define setuptools-shim ;; Run setup.py with "setuptools" being imported, which will patch @@ -149,6 +212,9 @@ when running checks after installing the package." (define* (rename-pth-file #:key name inputs outputs #:allow-other-keys) "Rename easy-install.pth to NAME.pth to avoid conflicts between packages installed with setuptools." + ;; Even if the "easy-install.pth" is not longer created, we kept this phase. + ;; There still may be packages creating an "easy-install.pth" manually for + ;; some good reason. (let* ((out (assoc-ref outputs "out")) (python (assoc-ref inputs "python")) (site-packages (string-append out "/lib/python" -- cgit v1.2.3 From 891a843d5184f696618af6fcbb9791ef6b574504 Mon Sep 17 00:00:00 2001 From: Hartmut Goebel Date: Wed, 28 Sep 2016 11:36:35 +0200 Subject: guix: Add lint-checker for packages which should be no inputs at all. Also refactor some common code into a new function. Examples for these pacakges are python(2)-setuptools and python(2)-pip, which are installed together with python itself. * guix/scripts/lint.scm (warn-if-package-has-input): New procedure. (check-inputs-should-be-native package): Use it; rename and clean-up variables. (check-inputs-should-not-be-an-input-at-all): New procedure. (%checkers) Add it. * doc/guix.texi (Python Modules): Document it. * tests/lint.scm: ("inputs: python-setuptools should not be an input at all (input)", "inputs: python-setuptools should not be an input at all (native-input)" "inputs: python-setuptools should not be an input at all (propagated-input)"): Add tests. --- doc/guix.texi | 3 ++- guix/scripts/lint.scm | 63 ++++++++++++++++++++++++++++++++++++--------------- tests/lint.scm | 34 +++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 19 deletions(-) (limited to 'guix') diff --git a/doc/guix.texi b/doc/guix.texi index 5f2807654b..40a1a8760c 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -13602,7 +13602,8 @@ following check list to determine which dependency goes where. @item We currently package Python 2 with @code{setuptools} and @code{pip} installed like Python 3.4 has per default. Thus you don't need to -specify either of these as an input. +specify either of these as an input. @command{guix lint} will warn you +if you do. @item Python dependencies required at run time go into diff --git a/guix/scripts/lint.scm b/guix/scripts/lint.scm index 6e6f550941..e68ee29e07 100644 --- a/guix/scripts/lint.scm +++ b/guix/scripts/lint.scm @@ -4,6 +4,7 @@ ;;; Copyright © 2013, 2014, 2015, 2016 Ludovic Courtès ;;; Copyright © 2015, 2016 Mathieu Lirzin ;;; Copyright © 2016 Danny Milosavljevic +;;; Copyright © 2016 Hartmut Goebel ;;; ;;; This file is part of GNU Guix. ;;; @@ -59,6 +60,7 @@ #:export (guix-lint check-description-style check-inputs-should-be-native + check-inputs-should-not-be-an-input-at-all check-patch-file-names check-synopsis-style check-derivation @@ -228,34 +230,55 @@ by two spaces; possible infraction~p at ~{~a~^, ~}") (format #f (_ "invalid description: ~s") description) 'description)))) +(define (warn-if-package-has-input linted inputs-to-check input-names message) + ;; Emit a warning MESSAGE if some of the inputs named in INPUT-NAMES are + ;; contained in INPUTS-TO-CHECK, which are assumed to be inputs of package + ;; LINTED. + (match inputs-to-check + (((labels packages . outputs) ...) + (for-each (lambda (package output) + (when (package? package) + (let ((input (string-append + (package-name package) + (if (> (length output) 0) + (string-append ":" (car output)) + "")))) + (when (member input input-names) + (emit-warning linted + (format #f (_ message) input) + 'inputs-to-check))))) + packages outputs)))) + (define (check-inputs-should-be-native package) ;; Emit a warning if some inputs of PACKAGE are likely to belong to its ;; native inputs. - (let ((linted package) + (let ((message "'~a' should probably be a native input") (inputs (package-inputs package)) - (native-inputs + (input-names '("pkg-config" "extra-cmake-modules" "glib:bin" "intltool" "itstool" "qttools"))) - (match inputs - (((labels packages . outputs) ...) - (for-each (lambda (package output) - (when (package? package) - (let ((input (string-append - (package-name package) - (if (> (length output) 0) - (string-append ":" (car output)) - "")))) - (when (member input native-inputs) - (emit-warning linted - (format #f (_ "'~a' should probably \ -be a native input") - input) - 'inputs))))) - packages outputs))))) + (warn-if-package-has-input package inputs input-names message))) + +(define (check-inputs-should-not-be-an-input-at-all package) + ;; Emit a warning if some inputs of PACKAGE are likely to should not be + ;; an input at all. + (let ((message "'~a' should probably not be an input at all") + (inputs (package-inputs package)) + (input-names + '("python-setuptools" + "python2-setuptools" + "python-pip" + "python2-pip"))) + (warn-if-package-has-input package (package-inputs package) + input-names message) + (warn-if-package-has-input package (package-native-inputs package) + input-names message) + (warn-if-package-has-input package (package-propagated-inputs package) + input-names message))) (define (package-name-regexp package) "Return a regexp that matches PACKAGE's name as a word at the beginning of a @@ -844,6 +867,10 @@ them for PACKAGE." (name 'inputs-should-be-native) (description "Identify inputs that should be native inputs") (check check-inputs-should-be-native)) + (lint-checker + (name 'inputs-should-not-be-input) + (description "Identify inputs that should be inputs at all") + (check check-inputs-should-not-be-an-input-at-all)) (lint-checker (name 'patch-file-names) (description "Validate file names and availability of patches") diff --git a/tests/lint.scm b/tests/lint.scm index fa2d19b2a6..b66cd29312 100644 --- a/tests/lint.scm +++ b/tests/lint.scm @@ -3,6 +3,7 @@ ;;; Copyright © 2014, 2015, 2016 Eric Bavier ;;; Copyright © 2014, 2015, 2016 Ludovic Courtès ;;; Copyright © 2015, 2016 Mathieu Lirzin +;;; Copyright © 2016 Hartmut Goebel ;;; ;;; This file is part of GNU Guix. ;;; @@ -33,6 +34,7 @@ #:use-module (gnu packages) #:use-module (gnu packages glib) #:use-module (gnu packages pkg-config) + #:use-module (gnu packages python) #:use-module (web server) #:use-module (web server http) #:use-module (web response) @@ -354,6 +356,38 @@ string) on HTTP requests." (check-inputs-should-be-native pkg))) "'glib:bin' should probably be a native input"))) +(test-assert + "inputs: python-setuptools should not be an input at all (input)" + (->bool + (string-contains + (with-warnings + (let ((pkg (dummy-package "x" + (inputs `(("python-setuptools" ,python-setuptools)))))) + (check-inputs-should-not-be-an-input-at-all pkg))) + "'python-setuptools' should probably not be an input at all"))) + +(test-assert + "inputs: python-setuptools should not be an input at all (native-input)" + (->bool + (string-contains + (with-warnings + (let ((pkg (dummy-package "x" + (native-inputs + `(("python-setuptools" ,python-setuptools)))))) + (check-inputs-should-not-be-an-input-at-all pkg))) + "'python-setuptools' should probably not be an input at all"))) + +(test-assert + "inputs: python-setuptools should not be an input at all (propagated-input)" + (->bool + (string-contains + (with-warnings + (let ((pkg (dummy-package "x" + (propagated-inputs + `(("python-setuptools" ,python-setuptools)))))) + (check-inputs-should-not-be-an-input-at-all pkg))) + "'python-setuptools' should probably not be an input at all"))) + (test-assert "patches: file names" (->bool (string-contains -- cgit v1.2.3 From e442246a2f5f73b2484adb340b53d3a0018636b1 Mon Sep 17 00:00:00 2001 From: Hartmut Goebel Date: Thu, 6 Oct 2016 16:52:31 +0200 Subject: lint: more packages to probably be a native input. * guix/scripts/lint.scm (check-inputs-should-be-native package): Add python packages which are typically used for testing or for building the documentation. --- guix/scripts/lint.scm | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'guix') diff --git a/guix/scripts/lint.scm b/guix/scripts/lint.scm index e68ee29e07..be29e36ce1 100644 --- a/guix/scripts/lint.scm +++ b/guix/scripts/lint.scm @@ -260,7 +260,17 @@ by two spaces; possible infraction~p at ~{~a~^, ~}") "glib:bin" "intltool" "itstool" - "qttools"))) + "qttools" + "python-coverage" "python2-coverage" + "python-cython" "python2-cython" + "python-docutils" "python2-docutils" + "python-mock" "python2-mock" + "python-nose" "python2-nose" + "python-pbr" "python2-pbr" + "python-pytest" "python2-pytest" + "python-pytest-cov" "python2-pytest-cov" + "python-setuptools-scm" "python2-setuptools-scm" + "python-sphinx" "python2-sphinx"))) (warn-if-package-has-input package inputs input-names message))) (define (check-inputs-should-not-be-an-input-at-all package) -- cgit v1.2.3 From caa9b834e75ec75e4c82d8abf8502be72c93a48e Mon Sep 17 00:00:00 2001 From: Hartmut Goebel Date: Sat, 8 Oct 2016 14:26:36 +0200 Subject: guxi: cmake-build-system: Enable output for failing test-cases. * guix/build/cmake-build-system.scm (cmake-build-system): Set environment variable CTEST_OUTPUT_ON_FAILURE to an non-empty value. --- guix/build/cmake-build-system.scm | 1 + 1 file changed, 1 insertion(+) (limited to 'guix') diff --git a/guix/build/cmake-build-system.scm b/guix/build/cmake-build-system.scm index f57622e0f4..27f2b5c872 100644 --- a/guix/build/cmake-build-system.scm +++ b/guix/build/cmake-build-system.scm @@ -66,6 +66,7 @@ (define* (check #:key (tests? #t) (parallel-tests? #t) (test-target "test") #:allow-other-keys) (let ((gnu-check (assoc-ref gnu:%standard-phases 'check))) + (setenv "CTEST_OUTPUT_ON_FAILURE" "1") (gnu-check #:tests? tests? #:test-target test-target #:parallel-tests? parallel-tests?))) -- cgit v1.2.3 From 1cd1d8a7ea43bfb99aa05c74da5430bb3d8a4309 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Sat, 26 Nov 2016 23:00:36 +0100 Subject: offload: Call 'machine-load' only once per machine. This fixes a longstanding issue where 'choose-build-machine' would make on average O(N log(N)) calls to 'machine-load', plus an extra call for the selected machine, instead of N calls. * guix/scripts/offload.scm (machine-load): Add comment. (machine-power-factor, machine-less-loaded-or-faster?): Remove. (choose-build-machine)[machines+slots]: Rename to... [machines+slots+loads]: ... this. [undecorate]: Adjust accordingly. [machine-less-loaded-or-faster?]: New procedure. Remove extra 'machine-load' call in body. --- guix/scripts/offload.scm | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) (limited to 'guix') diff --git a/guix/scripts/offload.scm b/guix/scripts/offload.scm index 2e0268020c..bc024a8701 100644 --- a/guix/scripts/offload.scm +++ b/guix/scripts/offload.scm @@ -490,6 +490,7 @@ be read." (define (machine-load machine) "Return the load of MACHINE, divided by the number of parallel builds allowed on MACHINE." + ;; Note: This procedure is costly since it creates a new SSH session. (let* ((session (open-ssh-session machine)) (pipe (open-remote-pipe* session OPEN_READ "cat" "/proc/loadavg")) @@ -510,17 +511,6 @@ allowed on MACHINE." (_ +inf.0))))) ;something's fishy about MACHINE, so avoid it -(define (machine-power-factor m) - "Return a factor that aggregates the speed and load of M. The higher the -better." - (/ (build-machine-speed m) - (+ 1 (machine-load m)))) - -(define (machine-less-loaded-or-faster? m1 m2) - "Return #t if M1 is either less loaded or faster than M2. (This relation -defines a total order on machines.)" - (> (machine-power-factor m1) (machine-power-factor m2))) - (define (machine-lock-file machine hint) "Return the name of MACHINE's lock file for HINT." (string-append %state-directory "/offload/" @@ -548,29 +538,39 @@ defines a total order on machines.)" ;; 5. Release the global machine-choice lock. (with-file-lock (machine-choice-lock-file) - (define machines+slots + (define machines+slots+loads (filter-map (lambda (machine) + ;; Call 'machine-load' from here to make sure it is called + ;; only once per machine (it is expensive). (let ((slot (acquire-build-slot machine))) - (and slot (list machine slot)))) + (and slot + (list machine slot (machine-load machine))))) machines)) (define (undecorate pred) (lambda (a b) (match a - ((machine1 slot1) + ((machine1 slot1 load1) (match b - ((machine2 slot2) - (pred machine1 machine2))))))) - - (let loop ((machines+slots - (sort machines+slots + ((machine2 slot2 load2) + (pred machine1 load1 machine2 load2))))))) + + (define (machine-less-loaded-or-faster? m1 l1 m2 l2) + ;; Return #t if M1 is either less loaded or faster than M2, with L1 + ;; being the load of M1 and L2 the load of M2. (This relation defines a + ;; total order on machines.) + (> (/ (build-machine-speed m1) (+ 1 l1)) + (/ (build-machine-speed m2) (+ 1 l2)))) + + (let loop ((machines+slots+loads + (sort machines+slots+loads (undecorate machine-less-loaded-or-faster?)))) - (match machines+slots - (((best slot) others ...) + (match machines+slots+loads + (((best slot load) others ...) ;; Return the best machine unless it's already overloaded. - (if (< (machine-load best) 2.) + (if (< load 2.) (match others - (((machines slots) ...) + (((machines slots loads) ...) ;; Release slots from the uninteresting machines. (for-each release-build-slot slots) -- cgit v1.2.3 From c062b1eb6c9d799f0015e26b14cd77eaf8d946dd Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Mon, 28 Nov 2016 22:08:17 +0100 Subject: pull: Set '%nix-instantiate' to a sensible value. Reported by ng0 . Fixes . * guix/build/pull.scm (build-guix): Replace "@NIX_INSTANTIATE@" in guix/config.scm with "nix-instantiate". --- guix/build/pull.scm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'guix') diff --git a/guix/build/pull.scm b/guix/build/pull.scm index 871bf6f535..6034e93cbf 100644 --- a/guix/build/pull.scm +++ b/guix/build/pull.scm @@ -84,7 +84,7 @@ containing the source code. Write any debugging output to DEBUG-PORT." (("@GZIP@") (string-append gzip "/bin/gzip")) (("@BZIP2@") (string-append bzip2 "/bin/bzip2")) (("@XZ@") (string-append xz "/bin/xz")) - (("@NIX_INSTANTIATE@") "")) ;remnants from the past + (("@NIX_INSTANTIATE@") "nix-instantiate")) ;for (guix import nix) ;; Augment the search path so Scheme code can be compiled. (set! %load-path (cons out %load-path)) -- cgit v1.2.3 From e9c72306fdfd6a60158918850cb25d0ff3837d16 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Tue, 29 Nov 2016 15:07:07 +0100 Subject: refresh: Warn about packages that lack an updater. * guix/upstream.scm (package-update-path): Rename to... (package-latest-release): ... this. Remove 'version>?' check. (package-latest-release*): New procedure. (package-update): Use it. * guix/scripts/refresh.scm (lookup-updater): Rename to... (lookup-updater-by-name): ... this. (warn-no-updater): New procedure. (update-package): Add #:warn? parameter and honor it. (check-for-package-update): New procedure. (guix-refresh)[warn?]: New variable. Replace inline code when UPDATE? is false with a call to 'check-for-package-update'. Pass WARN? to 'check-for-package-update' and 'update-package'. * doc/guix.texi (Invoking guix refresh): Document it. Fix a couple of typos. --- doc/guix.texi | 19 +++++++--- guix/scripts/refresh.scm | 96 ++++++++++++++++++++++++++++++------------------ guix/upstream.scm | 30 ++++++++++----- 3 files changed, 95 insertions(+), 50 deletions(-) (limited to 'guix') diff --git a/doc/guix.texi b/doc/guix.texi index ce1e5d075a..4677e5cf79 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -5250,10 +5250,19 @@ gnu/packages/gettext.scm:29:13: gettext would be upgraded from 0.18.1.1 to 0.18. gnu/packages/glib.scm:77:12: glib would be upgraded from 2.34.3 to 2.37.0 @end example -It does so by browsing the FTP directory of each package and determining -the highest version number of the source tarballs therein. The command +Alternately, one can specify packages to consider, in which case a +warning is emitted for packages that lack an updater: + +@example +$ guix refresh coreutils guile guile-ssh +gnu/packages/ssh.scm:205:2: warning: no updater for guile-ssh +gnu/packages/guile.scm:136:12: guile would be upgraded from 2.0.12 to 2.0.13 +@end example + +@command{guix refresh} browses the upstream repository of each package and determines +the highest version number of the releases therein. The command knows how to update specific types of packages: GNU packages, ELPA -packages, etc.---see the documentation for @option{--type} below. The +packages, etc.---see the documentation for @option{--type} below. There are many packages, though, for which it lacks a method to determine whether a new upstream release is available. However, the mechanism is extensible, so feel free to get in touch with us to add a new method! @@ -5293,7 +5302,7 @@ usually run from a checkout of the Guix source tree (@pxref{Running Guix Before It Is Installed}): @example -$ ./pre-inst-env guix refresh -s non-core +$ ./pre-inst-env guix refresh -s non-core -u @end example @xref{Defining Packages}, for more information on package definitions. @@ -5359,7 +5368,7 @@ In addition, @command{guix refresh} can be passed one or more package names, as in this example: @example -$ ./pre-inst-env guix refresh -u emacs idutils gcc-4.8.4 +$ ./pre-inst-env guix refresh -u emacs idutils gcc@@4.8 @end example @noindent diff --git a/guix/scripts/refresh.scm b/guix/scripts/refresh.scm index b81c69f9fe..ed28ed5fcb 100644 --- a/guix/scripts/refresh.scm +++ b/guix/scripts/refresh.scm @@ -208,7 +208,7 @@ unavailable optional dependencies such as Guile-JSON." ((guix import gem) => %gem-updater) ((guix import github) => %github-updater))) -(define (lookup-updater name) +(define (lookup-updater-by-name name) "Return the updater called NAME." (or (find (lambda (updater) (eq? name (upstream-updater-name updater))) @@ -225,31 +225,60 @@ unavailable optional dependencies such as Guile-JSON." %updaters) (exit 0)) +(define (warn-no-updater package) + (format (current-error-port) + (_ "~a: warning: no updater for ~a~%") + (location->string (package-location package)) + (package-name package))) + (define* (update-package store package updaters - #:key (key-download 'interactive)) + #:key (key-download 'interactive) warn?) "Update the source file that defines PACKAGE with the new version. KEY-DOWNLOAD specifies a download policy for missing OpenPGP keys; allowed -values: 'interactive' (default), 'always', and 'never'." - (let-values (((version tarball) - (package-update store package updaters - #:key-download key-download)) - ((loc) - (or (package-field-location package 'version) - (package-location package)))) - (when version - (if (and=> tarball file-exists?) - (begin - (format (current-error-port) - (_ "~a: ~a: updating from version ~a to version ~a...~%") - (location->string loc) - (package-name package) - (package-version package) version) - (let ((hash (call-with-input-file tarball - port-sha256))) - (update-package-source package version hash))) - (warning (_ "~a: version ~a could not be \ +values: 'interactive' (default), 'always', and 'never'. When WARN? is true, +warn about packages that have no matching updater." + (if (lookup-updater package updaters) + (let-values (((version tarball) + (package-update store package updaters + #:key-download key-download)) + ((loc) + (or (package-field-location package 'version) + (package-location package)))) + (when version + (if (and=> tarball file-exists?) + (begin + (format (current-error-port) + (_ "~a: ~a: updating from version ~a to version ~a...~%") + (location->string loc) + (package-name package) + (package-version package) version) + (let ((hash (call-with-input-file tarball + port-sha256))) + (update-package-source package version hash))) + (warning (_ "~a: version ~a could not be \ downloaded and authenticated; not updating~%") - (package-name package) version))))) + (package-name package) version)))) + (when warn? + (warn-no-updater package)))) + +(define* (check-for-package-update package #:key warn?) + "Check whether an update is available for PACKAGE and print a message. When +WARN? is true and no updater exists for PACKAGE, print a warning." + (match (package-latest-release package %updaters) + ((? upstream-source? source) + (when (version>? (upstream-source-version source) + (package-version package)) + (let ((loc (or (package-field-location package 'version) + (package-location package)))) + (format (current-error-port) + (_ "~a: ~a would be upgraded from ~a to ~a~%") + (location->string loc) + (package-name package) (package-version package) + (upstream-source-version source))))) + (#f + (when warn? + (warn-no-updater package))))) + ;;; @@ -312,7 +341,7 @@ dependent packages are rebuilt: ~{~a~^ ~}~%" ;; Return the list of updaters to use. (match (filter-map (match-lambda (('updaters . names) - (map lookup-updater names)) + (map lookup-updater-by-name names)) (_ #f)) opts) (() @@ -360,6 +389,12 @@ update would trigger a complete rebuild." (updaters (options->updaters opts)) (list-dependent? (assoc-ref opts 'list-dependent?)) (key-download (assoc-ref opts 'key-download)) + + ;; Warn about missing updaters when a package is explicitly given on + ;; the command line. + (warn? (or (assoc-ref opts 'argument) + (assoc-ref opts 'expression))) + (packages (match (filter-map (match-lambda (('argument . spec) @@ -397,22 +432,13 @@ update would trigger a complete rebuild." (%gpg-command)))) (for-each (cut update-package store <> updaters - #:key-download key-download) + #:key-download key-download + #:warn? warn?) packages) (with-monad %store-monad (return #t)))) (else - (for-each (lambda (package) - (match (package-update-path package updaters) - ((? upstream-source? source) - (let ((loc (or (package-field-location package 'version) - (package-location package)))) - (format (current-error-port) - (_ "~a: ~a would be upgraded from ~a to ~a~%") - (location->string loc) - (package-name package) (package-version package) - (upstream-source-version source)))) - (#f #f))) + (for-each (cut check-for-package-update <> #:warn? warn?) packages) (with-monad %store-monad (return #t))))))))) diff --git a/guix/upstream.scm b/guix/upstream.scm index 18157376d2..08992dc19e 100644 --- a/guix/upstream.scm +++ b/guix/upstream.scm @@ -49,8 +49,11 @@ upstream-updater-predicate upstream-updater-latest + lookup-updater + download-tarball - package-update-path + package-latest-release + package-latest-release* package-update update-package-source)) @@ -127,17 +130,24 @@ them matches." (and (pred package) latest))) updaters)) -(define (package-update-path package updaters) +(define (package-latest-release package updaters) "Return an upstream source to update PACKAGE, a object, or #f if -no update is needed or known." +none of UPDATERS matches PACKAGE. It is the caller's responsibility to ensure +that the returned source is newer than the current one." (match (lookup-updater package updaters) ((? procedure? latest-release) - (match (latest-release package) - ((and source ($ name version)) - (and (version>? version (package-version package)) - source)) - (_ #f))) - (#f #f))) + (latest-release package)) + (_ #f))) + +(define (package-latest-release* package updaters) + "Like 'package-latest-release', but ensure that the return source is newer +than that of PACKAGE." + (match (package-latest-release package updaters) + ((and source ($ name version)) + (and (version>? version (package-version package)) + source)) + (_ + #f))) (define* (download-tarball store url signature-url #:key (key-download 'interactive)) @@ -179,7 +189,7 @@ values: the item from LST1 and the item from LST2 that match PRED." PACKAGE, or #f and #f when PACKAGE is up-to-date. KEY-DOWNLOAD specifies a download policy for missing OpenPGP keys; allowed values: 'always', 'never', and 'interactive' (default)." - (match (package-update-path package updaters) + (match (package-latest-release* package updaters) (($ _ version urls signature-urls) (let*-values (((name) (package-name package)) -- cgit v1.2.3 From 3e95d88d51a63854d44cbf8c8caa47b26d81e091 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Tue, 29 Nov 2016 16:10:10 +0100 Subject: gnu-maintenance: 'latest-kde-release' honors 'upstream-name' properties. * guix/gnu-maintenance.scm (latest-kde-release): Honor the 'upstream-name' property of PACKAGE. --- guix/gnu-maintenance.scm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'guix') diff --git a/guix/gnu-maintenance.scm b/guix/gnu-maintenance.scm index 78392c9a11..6c6c0722d5 100644 --- a/guix/gnu-maintenance.scm +++ b/guix/gnu-maintenance.scm @@ -525,7 +525,8 @@ elpa.gnu.org, and all the GNOME packages." (let ((uri (string->uri (origin-uri (package-source package))))) (false-if-ftp-error (latest-ftp-release - (package-name package) + (or (assoc-ref (package-properties package) 'upstream-name) + (package-name package)) #:server "mirrors.mit.edu" #:directory (string-append "/kde" (dirname (dirname (uri-path uri)))) -- cgit v1.2.3 From 7632f7bc214b798ff3e154c2fac9a856aa9494e3 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Wed, 30 Nov 2016 15:56:55 +0100 Subject: gnu-maintenance: Factorize URL prefix predicates. * guix/gnu-maintenance.scm (url-prefix-predicate): New procedure. (gnome-package?): Rewrite in terms of 'url-prefix-predicate'. (kde-package?, xorg-package?): Remove. (%kde-updater, %xorg-updater): Use 'url-prefix-predicate'. --- guix/gnu-maintenance.scm | 70 +++++++++++++++--------------------------------- 1 file changed, 22 insertions(+), 48 deletions(-) (limited to 'guix') diff --git a/guix/gnu-maintenance.scm b/guix/gnu-maintenance.scm index 6c6c0722d5..90ca7a45e3 100644 --- a/guix/gnu-maintenance.scm +++ b/guix/gnu-maintenance.scm @@ -448,21 +448,26 @@ elpa.gnu.org, and all the GNOME packages." (not (gnome-package? package)) (gnu-package? package))) -(define (gnome-package? package) - "Return true if PACKAGE is a GNOME package, hosted on gnome.org." - (define gnome-uri? - (match-lambda - ((? string? uri) - (string-prefix? "mirror://gnome/" uri)) - (_ - #f))) - - (match (package-source package) - ((? origin? origin) - (match (origin-uri origin) - ((? gnome-uri?) #t) - (_ #f))) - (_ #f))) +(define (url-prefix-predicate prefix) + "Return a predicate that returns true when passed a package where one of its +source URLs starts with PREFIX." + (lambda (package) + (define matching-uri? + (match-lambda + ((? string? uri) + (string-prefix? prefix uri)) + (_ + #f))) + + (match (package-source package) + ((? origin? origin) + (match (origin-uri origin) + ((? matching-uri?) #t) + (_ #f))) + (_ #f)))) + +(define gnome-package? + (url-prefix-predicate "mirror://gnome/")) (define (latest-gnome-release package) "Return the latest release of PACKAGE, the name of a GNOME package." @@ -504,21 +509,6 @@ elpa.gnu.org, and all the GNOME packages." ;; checksums. #:file->signature (const #f)))) -(define (kde-package? package) - "Return true if PACKAGE is a KDE package, developed by KDE.org." - (define kde-uri? - (match-lambda - ((? string? uri) - (string-prefix? "mirror://kde/" uri)) - (_ - #f))) - - (match (package-source package) - ((? origin? origin) - (match (origin-uri origin) - ((? kde-uri?) #t) - (_ #f))) - (_ #f))) (define (latest-kde-release package) "Return the latest release of PACKAGE, the name of an KDE.org package." @@ -532,22 +522,6 @@ elpa.gnu.org, and all the GNOME packages." (string-append "/kde" (dirname (dirname (uri-path uri)))) #:file->signature (const #f))))) -(define (xorg-package? package) - "Return true if PACKAGE is an X.org package, developed by X.org." - (define xorg-uri? - (match-lambda - ((? string? uri) - (string-prefix? "mirror://xorg/" uri)) - (_ - #f))) - - (match (package-source package) - ((? origin? origin) - (match (origin-uri origin) - ((? xorg-uri?) #t) - (_ #f))) - (_ #f))) - (define (latest-xorg-release package) "Return the latest release of PACKAGE, the name of an X.org package." (let ((uri (string->uri (origin-uri (package-source package))))) @@ -576,14 +550,14 @@ elpa.gnu.org, and all the GNOME packages." (upstream-updater (name 'kde) (description "Updater for KDE packages") - (pred kde-package?) + (pred (url-prefix-predicate "mirror://kde/")) (latest latest-kde-release))) (define %xorg-updater (upstream-updater (name 'xorg) (description "Updater for X.org packages") - (pred xorg-package?) + (pred (url-prefix-predicate "mirror://xorg/")) (latest latest-xorg-release))) ;;; gnu-maintenance.scm ends here -- cgit v1.2.3 From 4e6230ec00de1090e2780130f7de3a799c626e9b Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Wed, 30 Nov 2016 16:26:08 +0100 Subject: refresh: Honor the selected updaters when '-u' isn't given. Fixes a regression introduced in e9c72306fdfd6a60158918850cb25d0ff3837d16. * guix/scripts/refresh.scm (check-for-package-update): Add 'updaters' parameter and honor it. (guix-refresh): Pass UPDATERS to 'check-for-package-update'. --- guix/scripts/refresh.scm | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'guix') diff --git a/guix/scripts/refresh.scm b/guix/scripts/refresh.scm index ed28ed5fcb..91a31a280b 100644 --- a/guix/scripts/refresh.scm +++ b/guix/scripts/refresh.scm @@ -261,10 +261,10 @@ downloaded and authenticated; not updating~%") (when warn? (warn-no-updater package)))) -(define* (check-for-package-update package #:key warn?) +(define* (check-for-package-update package updaters #:key warn?) "Check whether an update is available for PACKAGE and print a message. When WARN? is true and no updater exists for PACKAGE, print a warning." - (match (package-latest-release package %updaters) + (match (package-latest-release package updaters) ((? upstream-source? source) (when (version>? (upstream-source-version source) (package-version package)) @@ -438,7 +438,8 @@ update would trigger a complete rebuild." (with-monad %store-monad (return #t)))) (else - (for-each (cut check-for-package-update <> #:warn? warn?) + (for-each (cut check-for-package-update <> updaters + #:warn? warn?) packages) (with-monad %store-monad (return #t))))))))) -- cgit v1.2.3 From 8d5d06282e255557d3bdda1794bd3fea2c84ff59 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Wed, 30 Nov 2016 17:30:12 +0100 Subject: upstream: Properly verify signatures of uncompressed tarballs. * guix/upstream.scm (uncompressed-tarball): New procedure. (download-tarball): Use it when the basename of SIGNATURE-URL doesn't contain the basename of URL. --- guix/upstream.scm | 49 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) (limited to 'guix') diff --git a/guix/upstream.scm b/guix/upstream.scm index 08992dc19e..8685afd860 100644 --- a/guix/upstream.scm +++ b/guix/upstream.scm @@ -26,6 +26,11 @@ #:use-module (guix packages) #:use-module (guix ui) #:use-module (guix base32) + #:use-module (guix gexp) + #:use-module (guix store) + #:use-module ((guix derivations) + #:select (built-derivations derivation->output-path)) + #:use-module (guix monads) #:use-module (srfi srfi-1) #:use-module (srfi srfi-9) #:use-module (srfi srfi-11) @@ -149,6 +154,32 @@ than that of PACKAGE." (_ #f))) +(define (uncompressed-tarball name tarball) + "Return a derivation that decompresses TARBALL." + (define (ref package) + (module-ref (resolve-interface '(gnu packages compression)) + package)) + + (define compressor + (cond ((or (string-suffix? ".gz" tarball) + (string-suffix? ".tgz" tarball)) + (file-append (ref 'gzip) "/bin/gzip")) + ((string-suffix? ".bz2" tarball) + (file-append (ref 'bzip2) "/bin/bzip2")) + ((string-suffix? ".xz" tarball) + (file-append (ref 'xz) "/bin/xz")) + ((string-suffix? ".lz" tarball) + (file-append (ref 'lzip) "/bin/lzip")) + (else + (error "unknown archive type" tarball)))) + + (gexp->derivation (file-sans-extension name) + #~(begin + (copy-file #+tarball #+name) + (and (zero? (system* #+compressor "-d" #+name)) + (copy-file #+(file-sans-extension name) + #$output))))) + (define* (download-tarball store url signature-url #:key (key-download 'interactive)) "Download the tarball at URL to the store; check its OpenPGP signature at @@ -159,8 +190,22 @@ values: 'interactive' (default), 'always', and 'never'." (let ((tarball (download-to-store store url))) (if (not signature-url) tarball - (let* ((sig (download-to-store store signature-url)) - (ret (gnupg-verify* sig tarball #:key-download key-download))) + (let* ((sig (download-to-store store signature-url)) + + ;; Sometimes we get a signature over the uncompressed tarball. + ;; In that case, decompress the tarball in the store so that we + ;; can check the signature. + (data (if (string-prefix? (basename url) + (basename signature-url)) + tarball + (run-with-store store + (mlet %store-monad ((drv (uncompressed-tarball + (basename url) tarball))) + (mbegin %store-monad + (built-derivations (list drv)) + (return (derivation->output-path drv))))))) + + (ret (gnupg-verify* sig data #:key-download key-download))) (if ret tarball (begin -- cgit v1.2.3 From 2fd370e8167be9a0af9e5358757d58d1acaf02e0 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Wed, 30 Nov 2016 16:29:15 +0100 Subject: gnu-maintenance: Add kernel.org updater. * guix/gnu-maintenance.scm (latest-kernel.org-release): New procedure. (%kernel.org-updater): New variable. * guix/scripts/refresh.scm (%updaters): Add it. --- doc/guix.texi | 2 ++ guix/gnu-maintenance.scm | 26 +++++++++++++++++++++++++- guix/scripts/refresh.scm | 4 +++- 3 files changed, 30 insertions(+), 2 deletions(-) (limited to 'guix') diff --git a/doc/guix.texi b/doc/guix.texi index 53d29e45be..37bdb69b56 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -5342,6 +5342,8 @@ the updater for GNOME packages; the updater for KDE packages; @item xorg the updater for X.org packages; +@item kernel.org +the updater for packages hosted on kernel.org; @item elpa the updater for @uref{http://elpa.gnu.org/, ELPA} packages; @item cran diff --git a/guix/gnu-maintenance.scm b/guix/gnu-maintenance.scm index 90ca7a45e3..4d4bb452be 100644 --- a/guix/gnu-maintenance.scm +++ b/guix/gnu-maintenance.scm @@ -60,7 +60,8 @@ %gnu-updater %gnome-updater %kde-updater - %xorg-updater)) + %xorg-updater + %kernel.org-updater)) ;;; Commentary: ;;; @@ -532,6 +533,22 @@ source URLs starts with PREFIX." #:directory (string-append "/pub/xorg/" (dirname (uri-path uri))))))) +(define (latest-kernel.org-release package) + "Return the latest release of PACKAGE, the name of a kernel.org package." + (let ((uri (string->uri (origin-uri (package-source package))))) + (false-if-ftp-error + (latest-ftp-release + (package-name package) + #:server "ftp.free.fr" ;a mirror reachable over FTP + #:directory (string-append "/mirrors/ftp.kernel.org" + (dirname (uri-path uri))) + + ;; kernel.org provides "foo-x.y.tar.sign" files, which are signatures of + ;; the uncompressed tarball. + #:file->signature (lambda (tarball) + (string-append (file-sans-extension tarball) + ".sign")))))) + (define %gnu-updater (upstream-updater (name 'gnu) @@ -560,4 +577,11 @@ source URLs starts with PREFIX." (pred (url-prefix-predicate "mirror://xorg/")) (latest latest-xorg-release))) +(define %kernel.org-updater + (upstream-updater + (name 'kernel.org) + (description "Updater for packages hosted on kernel.org") + (pred (url-prefix-predicate "mirror://kernel.org/")) + (latest latest-kernel.org-release))) + ;;; gnu-maintenance.scm ends here diff --git a/guix/scripts/refresh.scm b/guix/scripts/refresh.scm index 91a31a280b..12a344e1a0 100644 --- a/guix/scripts/refresh.scm +++ b/guix/scripts/refresh.scm @@ -35,7 +35,8 @@ #:select (%gnu-updater %gnome-updater %kde-updater - %xorg-updater)) + %xorg-updater + %kernel.org-updater)) #:use-module (guix import elpa) #:use-module (guix import cran) #:use-module (guix import hackage) @@ -200,6 +201,7 @@ unavailable optional dependencies such as Guile-JSON." %gnome-updater %kde-updater %xorg-updater + %kernel.org-updater %elpa-updater %cran-updater %bioconductor-updater -- cgit v1.2.3 From 3676f892551d562e1a1360d79b208e687ece08c2 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Wed, 30 Nov 2016 16:45:19 +0100 Subject: refresh: '--list-updaters' shows updater coverage. * guix/scripts/refresh.scm (list-updaters-and-exit): Compute the coverage ratio of each updater and print it. Print the coverage ratio for all the updaters. * doc/guix.texi (Invoking guix refresh): Document it. --- doc/guix.texi | 3 +++ guix/scripts/refresh.scm | 27 ++++++++++++++++++++++----- 2 files changed, 25 insertions(+), 5 deletions(-) (limited to 'guix') diff --git a/doc/guix.texi b/doc/guix.texi index 37bdb69b56..3b4ba487ad 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -5394,6 +5394,9 @@ be used when passing @command{guix refresh} one or more package names: @itemx -L List available updaters and exit (see @option{--type} above.) +For each updater, display the fraction of packages it covers; at the +end, display the fraction of packages covered by all these updaters. + @item --list-dependent @itemx -l List top-level dependent packages that would need to be rebuilt as a diff --git a/guix/scripts/refresh.scm b/guix/scripts/refresh.scm index 12a344e1a0..e1ff544de0 100644 --- a/guix/scripts/refresh.scm +++ b/guix/scripts/refresh.scm @@ -220,11 +220,28 @@ unavailable optional dependencies such as Guile-JSON." (define (list-updaters-and-exit) "Display available updaters and exit." (format #t (_ "Available updaters:~%")) - (for-each (lambda (updater) - (format #t "- ~a: ~a~%" - (upstream-updater-name updater) - (_ (upstream-updater-description updater)))) - %updaters) + (newline) + + (let* ((packages (fold-packages cons '())) + (total (length packages))) + (define covered + (fold (lambda (updater covered) + (let ((matches (count (upstream-updater-predicate updater) + packages))) + ;; TRANSLATORS: The parenthetical expression here is rendered + ;; like "(42% coverage)" and denotes the fraction of packages + ;; covered by the given updater. + (format #t (_ " - ~a: ~a (~2,1f% coverage)~%") + (upstream-updater-name updater) + (_ (upstream-updater-description updater)) + (* 100. (/ matches total))) + (+ covered matches))) + 0 + %updaters)) + + (newline) + (format #t (_ "~2,1f% of the packages are covered by these updaters.~%") + (* 100. (/ covered total)))) (exit 0)) (define (warn-no-updater package) -- cgit v1.2.3 From f3cf860635841f2a17640d9a65e5f389d56470cd Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Thu, 1 Dec 2016 21:48:20 +0100 Subject: offload: Remove redundant call to 'topologically-sorted' in 'send-file'. * guix/scripts/offload.scm (send-files): Remove call to 'topologically-sorted'. --- guix/scripts/offload.scm | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'guix') diff --git a/guix/scripts/offload.scm b/guix/scripts/offload.scm index bc024a8701..1d60f65b16 100644 --- a/guix/scripts/offload.scm +++ b/guix/scripts/offload.scm @@ -429,10 +429,8 @@ be read." (define (send-files files remote) "Send the subset of FILES that's missing to REMOTE, a remote store." (with-store store - ;; Compute the subset of FILES missing on SESSION, and send them in - ;; topologically sorted order so that they can actually be imported. - (let* ((sorted (topologically-sorted store files)) - (session (channel-get-session (nix-server-socket remote))) + ;; Compute the subset of FILES missing on SESSION and send them. + (let* ((session (channel-get-session (nix-server-socket remote))) (node (make-node session)) (missing (node-eval node `(begin @@ -441,11 +439,12 @@ be read." (with-store store (remove (cut valid-path? store <>) - ',sorted))))) + ',files))))) (port (store-import-channel session))) (format #t (_ "sending ~a store files to '~a'...~%") (length missing) (session-get session 'host)) + ;; Send MISSING in topological order. (export-paths store missing port) ;; Tell the remote process that we're done. (In theory the -- cgit v1.2.3 From 0b72475301e20521e0dd9fc22881eaab4a7fe170 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Thu, 1 Dec 2016 21:49:16 +0100 Subject: offload: Warn about the lack of zlib support. * guix/scripts/offload.scm (guix-offload): Print a warning when 'zlib-support?' returns false. --- guix/scripts/offload.scm | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'guix') diff --git a/guix/scripts/offload.scm b/guix/scripts/offload.scm index 1d60f65b16..4f6de0b7a6 100644 --- a/guix/scripts/offload.scm +++ b/guix/scripts/offload.scm @@ -24,6 +24,7 @@ #:use-module (ssh popen) #:use-module (ssh dist) #:use-module (ssh dist node) + #:use-module (ssh version) #:use-module (guix config) #:use-module (guix records) #:use-module (guix store) @@ -634,6 +635,12 @@ allowed on MACHINE." (and=> (passwd:dir (getpw (getuid))) (cut setenv "HOME" <>)) + ;; We rely on protocol-level compression from libssh to optimize large data + ;; transfers. Warn if it's missing. + (unless (zlib-support?) + (warning (_ "Guile-SSH lacks zlib support")) + (warning (_ "data transfers will *not* be compressed!"))) + (match args ((system max-silent-time print-build-trace? build-timeout) (let ((max-silent-time (string->number max-silent-time)) -- cgit v1.2.3 From 6374633b9205f60ad0e7ff42bbf39e441ae2f328 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Thu, 1 Dec 2016 23:09:34 +0100 Subject: store: Increase buffering for the '%stderr-write' upcall. * guix/store.scm (process-stderr) <%stderr-write>: Pass #:buffer-size to 'dump-port'. --- guix/store.scm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'guix') diff --git a/guix/store.scm b/guix/store.scm index 689a94c636..a669011f3a 100644 --- a/guix/store.scm +++ b/guix/store.scm @@ -470,7 +470,8 @@ encoding conversion errors." ;; Write a byte stream to USER-PORT. (let* ((len (read-int p)) (m (modulo len 8))) - (dump-port p user-port len) + (dump-port p user-port len + #:buffer-size (if (<= len 16384) 16384 65536)) (unless (zero? m) ;; Consume padding, as for strings. (get-bytevector-n p (- 8 m)))) -- cgit v1.2.3 From 74afca5dcfa6f321b5523e9bae8b1aff30e9c6af Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Thu, 1 Dec 2016 23:20:18 +0100 Subject: offload: Gracefully report connection failures. * guix/scripts/offload.scm (open-ssh-session): Check the return value of 'connect!'. Call 'leave' when it's not 'ok. --- guix/scripts/offload.scm | 52 ++++++++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 24 deletions(-) (limited to 'guix') diff --git a/guix/scripts/offload.scm b/guix/scripts/offload.scm index 4f6de0b7a6..04983646eb 100644 --- a/guix/scripts/offload.scm +++ b/guix/scripts/offload.scm @@ -177,31 +177,35 @@ private key from '~a': ~a") ;; exchanging full archives. #:compression "zlib" #:compression-level 3))) - (connect! session) - - ;; Authenticate the server. XXX: Guile-SSH 0.10.1 doesn't know about - ;; ed25519 keys and 'get-key-type' returns #f in that case. - (let-values (((server) (get-server-public-key session)) - ((type key) (host-key->type+key - (build-machine-host-key machine)))) - (unless (and (or (not (get-key-type server)) - (eq? (get-key-type server) type)) - (string=? (public-key->string server) key)) - ;; Key mismatch: something's wrong. XXX: It could be that the server - ;; provided its Ed25519 key when we where expecting its RSA key. - (leave (_ "server at '~a' returned host key '~a' of type '~a' \ + (match (connect! session) + ('ok + ;; Authenticate the server. XXX: Guile-SSH 0.10.1 doesn't know about + ;; ed25519 keys and 'get-key-type' returns #f in that case. + (let-values (((server) (get-server-public-key session)) + ((type key) (host-key->type+key + (build-machine-host-key machine)))) + (unless (and (or (not (get-key-type server)) + (eq? (get-key-type server) type)) + (string=? (public-key->string server) key)) + ;; Key mismatch: something's wrong. XXX: It could be that the server + ;; provided its Ed25519 key when we where expecting its RSA key. + (leave (_ "server at '~a' returned host key '~a' of type '~a' \ instead of '~a' of type '~a'~%") - (build-machine-name machine) - (public-key->string server) (get-key-type server) - key type))) - - (let ((auth (userauth-public-key! session private))) - (unless (eq? 'success auth) - (disconnect! session) - (leave (_ "SSH public key authentication failed for '~a': ~a~%") - (build-machine-name machine) (get-error session)))) - - session)) + (build-machine-name machine) + (public-key->string server) (get-key-type server) + key type))) + + (let ((auth (userauth-public-key! session private))) + (unless (eq? 'success auth) + (disconnect! session) + (leave (_ "SSH public key authentication failed for '~a': ~a~%") + (build-machine-name machine) (get-error session)))) + + session) + (x + ;; Connection failed or timeout expired. + (leave (_ "failed to connect to '~a': ~a~%") + (build-machine-name machine) (get-error session)))))) (define* (connect-to-remote-daemon session #:optional -- cgit v1.2.3 From 463fb7d0c86fb9957c527272e6cec5ee23585366 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Thu, 1 Dec 2016 23:21:15 +0100 Subject: offload: Do not abort when a machine is unreachable. * guix/scripts/offload.scm (machine-load): Wrap 'open-ssh-session' call in 'false-if-exception'; return +inf.0 if it returns #f. --- guix/scripts/offload.scm | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) (limited to 'guix') diff --git a/guix/scripts/offload.scm b/guix/scripts/offload.scm index 04983646eb..237a9638d3 100644 --- a/guix/scripts/offload.scm +++ b/guix/scripts/offload.scm @@ -493,27 +493,30 @@ be read." (define (machine-load machine) "Return the load of MACHINE, divided by the number of parallel builds -allowed on MACHINE." +allowed on MACHINE. Return +∞ if MACHINE is unreachable." ;; Note: This procedure is costly since it creates a new SSH session. - (let* ((session (open-ssh-session machine)) - (pipe (open-remote-pipe* session OPEN_READ + (match (false-if-exception (open-ssh-session machine)) + ((? session? session) + (let* ((pipe (open-remote-pipe* session OPEN_READ "cat" "/proc/loadavg")) - (line (read-line pipe))) - (close-port pipe) - - (if (eof-object? line) - +inf.0 ;MACHINE does not respond, so assume it is infinitely loaded - (match (string-tokenize line) - ((one five fifteen . _) - (let* ((raw (string->number five)) - (jobs (build-machine-parallel-builds machine)) - (normalized (/ raw jobs))) - (format (current-error-port) "load on machine '~a' is ~s\ + (line (read-line pipe))) + (close-port pipe) + + (if (eof-object? line) + +inf.0 ;MACHINE does not respond, so assume it is infinitely loaded + (match (string-tokenize line) + ((one five fifteen . _) + (let* ((raw (string->number five)) + (jobs (build-machine-parallel-builds machine)) + (normalized (/ raw jobs))) + (format (current-error-port) "load on machine '~a' is ~s\ (normalized: ~s)~%" - (build-machine-name machine) raw normalized) - normalized)) - (_ - +inf.0))))) ;something's fishy about MACHINE, so avoid it + (build-machine-name machine) raw normalized) + normalized)) + (_ + +inf.0))))) ;something's fishy about MACHINE, so avoid it + (_ + +inf.0))) ;failed to connect to MACHINE, so avoid it (define (machine-lock-file machine hint) "Return the name of MACHINE's lock file for HINT." -- cgit v1.2.3 From 42d07286f42b82df2e4ea45e67c40da0f09f26ec Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Sun, 4 Dec 2016 00:38:30 +0100 Subject: publish: Factorize 'content-length' addition. * guix/scripts/publish.scm (with-content-length): New procedure. (http-write) : Use it. --- guix/scripts/publish.scm | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) (limited to 'guix') diff --git a/guix/scripts/publish.scm b/guix/scripts/publish.scm index 1b32f639ea..33a7b3bd42 100644 --- a/guix/scripts/publish.scm +++ b/guix/scripts/publish.scm @@ -365,6 +365,14 @@ example: \"/foo/bar\" yields '(\"foo\" \"bar\")." (response-headers response) eq?))) +(define (with-content-length response length) + "Return RESPONSE with a 'content-length' header set to LENGTH." + (set-field response (response-headers) + (alist-cons 'content-length length + (alist-delete 'content-length + (response-headers response) + eq?)))) + (define-syntax-rule (swallow-EPIPE exp ...) "Swallow EPIPE errors raised by EXP..." (catch 'system-error @@ -432,13 +440,8 @@ blocking." (call-with-input-file (utf8->string body) (lambda (input) (let* ((size (stat:size (stat input))) - (headers (alist-cons 'content-length size - (alist-delete 'content-length - (response-headers response) - eq?))) - (response (write-response (set-field response - (response-headers) - headers) + (response (write-response (with-content-length response + size) client)) (output (response-port response))) (dump-port input output) -- cgit v1.2.3 From aebaee95cc26d404a8d7b62aece77dfbddb75836 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Mon, 5 Dec 2016 18:16:04 +0100 Subject: offload: Add "test" sub-command. * guix/scripts/offload.scm (assert-node-repl, assert-node-has-guix) (nonce, assert-node-can-import, assert-node-can-export) (check-machine-availability): New procedures. (%random-state): New variable. (guix-offload): Add case for "test". * doc/guix.texi (Daemon Offload Setup): Document it. Remove obsolete bit about remote invocation of 'guix build'. --- doc/guix.texi | 25 ++++++++++++-- guix/scripts/offload.scm | 87 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 3 deletions(-) (limited to 'guix') diff --git a/doc/guix.texi b/doc/guix.texi index 47d0d7169a..4d7f96d907 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -941,9 +941,8 @@ name, and they will be scheduled on matching build machines. @end table @end deftp -The @code{guix} command must be in the search path on the build -machines, since offloading works by invoking the @code{guix archive} and -@code{guix build} commands. In addition, the Guix modules must be in +The @code{guile} command must be in the search path on the build +machines. In addition, the Guix modules must be in @code{$GUILE_LOAD_PATH} on the build machine---you can check whether this is the case by running: @@ -978,6 +977,26 @@ the master receives files from a build machine (and @i{vice versa}), its build daemon can make sure they are genuine, have not been tampered with, and that they are signed by an authorized key. +@cindex offload test +To test whether your setup is operational, run this command on the +master node: + +@example +# guix offload test +@end example + +This will attempt to connect to each of the build machines specified in +@file{/etc/guix/machines.scm}, make sure Guile and the Guix modules are +available on each machine, attempt to export to the machine and import +from it, and report any error in the process. + +If you want to test a different machine file, just specify it on the +command line: + +@example +# guix offload test machines-qualif.scm +@end example + @node Invoking guix-daemon @section Invoking @command{guix-daemon} diff --git a/guix/scripts/offload.scm b/guix/scripts/offload.scm index 237a9638d3..4d697f7d00 100644 --- a/guix/scripts/offload.scm +++ b/guix/scripts/offload.scm @@ -623,6 +623,86 @@ allowed on MACHINE. Return +∞ if MACHINE is unreachable." ;; Not now, all the machines are busy. (display "# postpone\n"))))))) + +;;; +;;; Installation tests. +;;; + +(define (assert-node-repl node name) + "Bail out if NODE is not running Guile." + (match (node-guile-version node) + (#f + (leave (_ "Guile could not be started on '~a'~%") + name)) + ((? string? version) + ;; Note: The version string already contains the word "Guile". + (info (_ "'~a' is running ~a~%") + name (node-guile-version node))))) + +(define (assert-node-has-guix node name) + "Bail out if NODE lacks the (guix) module, or if its daemon is not running." + (match (node-eval node + '(begin + (use-modules (guix)) + (with-store store + (add-text-to-store store "test" + "Hello, build machine!")))) + ((? string? str) + (info (_ "Guix is usable on '~a' (test returned ~s)~%") + name str)) + (x + (leave (_ "failed to use Guix module on '~a' (test returned ~s)~%") + name x)))) + +(define %random-state + (delay + (seed->random-state (logxor (getpid) (car (gettimeofday)))))) + +(define (nonce) + (string-append (gethostname) "-" + (number->string (random 1000000 (force %random-state))))) + +(define (assert-node-can-import node name daemon-socket) + "Bail out if NODE refuses to import our archives." + (let ((session (node-session node))) + (with-store store + (let* ((item (add-text-to-store store "export-test" (nonce))) + (remote (connect-to-remote-daemon session daemon-socket))) + (send-files (list item) remote) + (if (valid-path? remote item) + (info (_ "'~a' successfully imported '~a'~%") + name item) + (leave (_ "'~a' was not properly imported on '~a'~%") + item name)))))) + +(define (assert-node-can-export node name daemon-socket) + "Bail out if we cannot import signed archives from NODE." + (let* ((session (node-session node)) + (remote (connect-to-remote-daemon session daemon-socket)) + (item (add-text-to-store remote "import-test" (nonce))) + (port (store-export-channel session (list item)))) + (with-store store + (if (and (import-paths store port) + (valid-path? store item)) + (info (_ "successfully imported '~a' from '~a'~%") + item name) + (leave (_ "failed to import '~a' from '~a'~%") + item name))))) + +(define (check-machine-availability machine-file) + "Check that each machine in MACHINE-FILE is usable as a build machine." + (let ((machines (build-machines machine-file))) + (info (_ "testing ~a build machines defined in '~a'...~%") + (length machines) machine-file) + (let* ((names (map build-machine-name machines)) + (sockets (map build-machine-daemon-socket machines)) + (sessions (map open-ssh-session machines)) + (nodes (map make-node sessions))) + (for-each assert-node-repl nodes names) + (for-each assert-node-has-guix nodes names) + (for-each assert-node-can-import nodes names sockets) + (for-each assert-node-can-export nodes names sockets)))) + ;;; ;;; Entry point. @@ -673,6 +753,13 @@ allowed on MACHINE. Return +∞ if MACHINE is unreachable." (else (leave (_ "invalid request line: ~s~%") line))) (loop (read-line))))))) + (("test" rest ...) + (with-error-handling + (let ((file (match rest + ((file) file) + (() %machine-file) + (_ (leave (_ "wrong number of arguments~%")))))) + (check-machine-availability (or file %machine-file))))) (("--version") (show-version-and-exit "guix offload")) (("--help") -- cgit v1.2.3 From d8501b844b9816b741dd4c1e372843242ec2f950 Mon Sep 17 00:00:00 2001 From: Tobias Geerinckx-Rice Date: Mon, 5 Dec 2016 20:50:10 +0100 Subject: refresh: Indicate that PACKAGE... is optional. * guix/scripts/refresh.scm (show-help): Add brackets around PACKAGE. --- guix/scripts/refresh.scm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'guix') diff --git a/guix/scripts/refresh.scm b/guix/scripts/refresh.scm index e1ff544de0..805e4543ec 100644 --- a/guix/scripts/refresh.scm +++ b/guix/scripts/refresh.scm @@ -119,7 +119,7 @@ (show-version-and-exit "guix refresh"))))) (define (show-help) - (display (_ "Usage: guix refresh [OPTION]... PACKAGE... + (display (_ "Usage: guix refresh [OPTION]... [PACKAGE]... Update package definitions to match the latest upstream version. When PACKAGE... is given, update only the specified packages. Otherwise -- cgit v1.2.3 From 067a2e2de9e5f8437ce020c62f64e08b82af72b8 Mon Sep 17 00:00:00 2001 From: Chris Marusich Date: Fri, 2 Dec 2016 01:52:04 -0800 Subject: guix system: If the new system generation already exists, overwrite it. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes . Before that, 'guix system reconfigure' would fail if the next generation already existed. * guix/scripts/system.scm (switch-to-system): Use 'switch-symlink' instead of 'symlink'. * doc/guix.texi (Using the Configuration System, Invoking guix system): Document the behavior. Signed-off-by: Ludovic Courtès --- doc/guix.texi | 16 +++++++++++++++- guix/scripts/system.scm | 2 +- 2 files changed, 16 insertions(+), 2 deletions(-) (limited to 'guix') diff --git a/doc/guix.texi b/doc/guix.texi index 4d7f96d907..5c94a56c01 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -7174,7 +7174,15 @@ modifying or deleting previous generations. Old system generations get an entry in the GRUB boot menu, allowing you to boot them in case something went wrong with the latest generation. Reassuring, no? The @command{guix system list-generations} command lists the system -generations available on disk. +generations available on disk. It is also possible to roll back the +system via the commands @command{guix system roll-back} and +@command{guix system switch-generation}. + +Although the command @command{guix system reconfigure} will not modify +previous generations, must take care when the current generation is not +the latest (e.g., after invoking @command{guix system roll-back}), since +the operation might overwrite a later generation (@pxref{Invoking guix +system}). @unnumberedsubsubsec The Programming Interface @@ -12599,6 +12607,12 @@ currently running; if a service is currently running, it does not attempt to upgrade it since this would not be possible without stopping it first. +This command creates a new generation whose number is one greater than +the current generation (as reported by @command{guix system +list-generations}). If that generation already exists, it will be +overwritten. This behavior mirrors that of @command{guix package} +(@pxref{Invoking guix package}). + It also adds a GRUB menu entry for the new OS configuration, and moves entries for older configurations to a submenu---unless @option{--no-grub} is passed. diff --git a/guix/scripts/system.scm b/guix/scripts/system.scm index bb373a6726..144a7fd377 100644 --- a/guix/scripts/system.scm +++ b/guix/scripts/system.scm @@ -326,7 +326,7 @@ it atomically, and then run OS's activation script." (let* ((system (derivation->output-path drv)) (number (+ 1 (generation-number profile))) (generation (generation-file-name profile number))) - (symlink system generation) + (switch-symlinks generation system) (switch-symlinks profile generation) (format #t (_ "activating system...~%")) -- cgit v1.2.3 From 1d48cf948cfb825a5b080d5cbe3ba3cb69beb7c8 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Mon, 5 Dec 2016 23:15:17 +0100 Subject: offload: Make the compression methods configurable. * guix/scripts/offload.scm ()[compression] [compression-level]: New fields. (open-ssh-session): Honor them. * doc/guix.texi (Daemon Offload Setup): Document them. --- doc/guix.texi | 7 +++++++ guix/scripts/offload.scm | 10 ++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) (limited to 'guix') diff --git a/doc/guix.texi b/doc/guix.texi index 5c94a56c01..738b7fb10c 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -921,6 +921,13 @@ Port number of SSH server on the machine. The SSH private key file to use when connecting to the machine, in OpenSSH format. +@item @code{compression} (default: @code{"zlib@@openssh.com,zlib"}) +@itemx @code{compression-level} (default: @code{3}) +The SSH-level compression methods and compression level requested. + +Note that offloading relies on SSH compression to reduce bandwidth usage +when transferring files to and from build machines. + @item @code{daemon-socket} (default: @code{"/var/guix/daemon-socket/socket"}) File name of the Unix-domain socket @command{guix-daemon} is listening to on that machine. diff --git a/guix/scripts/offload.scm b/guix/scripts/offload.scm index 4d697f7d00..e20da99cbd 100644 --- a/guix/scripts/offload.scm +++ b/guix/scripts/offload.scm @@ -75,6 +75,10 @@ (private-key build-machine-private-key ; file name (default (user-openssh-private-key))) (host-key build-machine-host-key) ; string + (compression build-machine-compression ; string + (default "zlib@openssh.com,zlib")) + (compression-level build-machine-compression-level ;integer + (default 3)) (daemon-socket build-machine-daemon-socket ; string (default "/var/guix/daemon-socket/socket")) (parallel-builds build-machine-parallel-builds ; number @@ -175,8 +179,10 @@ private key from '~a': ~a") ;; We need lightweight compression when ;; exchanging full archives. - #:compression "zlib" - #:compression-level 3))) + #:compression + (build-machine-compression machine) + #:compression-level + (build-machine-compression-level machine)))) (match (connect! session) ('ok ;; Authenticate the server. XXX: Guile-SSH 0.10.1 doesn't know about -- cgit v1.2.3 From e11c42f297a4e128aa5abd11f379a250146f5cab Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Mon, 5 Dec 2016 23:25:29 +0100 Subject: offload: Fix plural of some messages. * guix/scripts/offload.scm (send-files): Use 'N_' for possibly plural message. Write "store item" instead of "store file". (retrieve-files): Likewise. --- guix/scripts/offload.scm | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'guix') diff --git a/guix/scripts/offload.scm b/guix/scripts/offload.scm index e20da99cbd..ed70f2267a 100644 --- a/guix/scripts/offload.scm +++ b/guix/scripts/offload.scm @@ -451,9 +451,11 @@ be read." (with-store store (remove (cut valid-path? store <>) ',files))))) + (count (length missing)) (port (store-import-channel session))) - (format #t (_ "sending ~a store files to '~a'...~%") - (length missing) (session-get session 'host)) + (format #t (N_ "sending ~a store item to '~a'...~%" + "sending ~a store items to '~a'...~%" count) + count (session-get session 'host)) ;; Send MISSING in topological order. (export-paths store missing port) @@ -472,9 +474,11 @@ be read." "Retrieve FILES from SESSION's store, and import them." (let* ((session (channel-get-session (nix-server-socket remote))) (host (session-get session 'host)) - (port (store-export-channel session files))) - (format #t (_ "retrieving ~a files from '~a'...~%") - (length files) host) + (port (store-export-channel session files)) + (count (length files))) + (format #t (N_ "retrieving ~a store item from '~a'...~%" + "retrieving ~a store items from '~a'...~%" count) + count host) ;; We cannot use the 'import-paths' RPC here because we already ;; hold the locks for FILES. -- cgit v1.2.3 From 0237d7971742ccfe5670455debb1feca9a491a0f Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Tue, 6 Dec 2016 00:50:08 +0100 Subject: offload: Send the build log to the right file descriptor. This fixes a regression introduced in 21531add3205e400707c8fbfd841845f9a71863a whereby the build log would no longer be sent to FD 4, thereby leading the daemon to not see the build log. * guix/scripts/offload.scm (transfer-and-offload): Parameterize CURRENT-BUILD-OUTPUT-PORT. --- guix/scripts/offload.scm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'guix') diff --git a/guix/scripts/offload.scm b/guix/scripts/offload.scm index ed70f2267a..55d4c93c64 100644 --- a/guix/scripts/offload.scm +++ b/guix/scripts/offload.scm @@ -390,7 +390,8 @@ MACHINE." ;; Use exit code 100 for a permanent build failure. The daemon ;; interprets other non-zero codes as transient build failures. (primitive-exit 100))) - (build-derivations store (list drv))) + (parameterize ((current-build-output-port (build-log-port))) + (build-derivations store (list drv)))) (retrieve-files outputs store) (format (current-error-port) "done with offloaded '~a'~%" -- cgit v1.2.3 From 8d125cfc2e5cb0825bb40893ec3e940f85f1b235 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Tue, 6 Dec 2016 01:00:11 +0100 Subject: offload: Increase the connection timeout. * guix/scripts/offload.scm (open-ssh-session): Set #:timeout to 10. --- guix/scripts/offload.scm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'guix') diff --git a/guix/scripts/offload.scm b/guix/scripts/offload.scm index 55d4c93c64..ebff11664d 100644 --- a/guix/scripts/offload.scm +++ b/guix/scripts/offload.scm @@ -173,7 +173,7 @@ private key from '~a': ~a") (session (make-session #:user (build-machine-user machine) #:host (build-machine-name machine) #:port (build-machine-port machine) - #:timeout 5 ;seconds + #:timeout 10 ;seconds ;; #:log-verbosity 'protocol #:identity (build-machine-private-key machine) -- cgit v1.2.3 From cba36e6482a39d9b7e3a61fb2251664a86cb492e Mon Sep 17 00:00:00 2001 From: Jan Nieuwenhuizen Date: Sat, 28 May 2016 17:32:04 +0200 Subject: gnu: cross-base: Add i686-w64-mingw32 target. * guix/utils.scm (mingw-target?): New function. * gnu/packages/cross-base.scm (cross-gcc-snippet): New procedure (cross-gcc): Use it. (cross-gcc-arguments, cross-gcc-patches, cross-gcc): Support MinGW. (native-libc, cross-newlib?): New functions. (cross-libc): Use cross-newlib? to support MinGW. (%gcc-include-paths, %gcc-cross-include-paths): New variables. --- gnu/packages/cross-base.scm | 309 +++++++++++++++++++++++++++++--------------- guix/utils.scm | 5 + 2 files changed, 212 insertions(+), 102 deletions(-) (limited to 'guix') diff --git a/gnu/packages/cross-base.scm b/gnu/packages/cross-base.scm index e6553dcd34..dac711472a 100644 --- a/gnu/packages/cross-base.scm +++ b/gnu/packages/cross-base.scm @@ -20,12 +20,12 @@ ;;; along with GNU Guix. If not, see . (define-module (gnu packages cross-base) - #:use-module (guix licenses) #:use-module (gnu packages) #:use-module (gnu packages gcc) #:use-module (gnu packages base) #:use-module (gnu packages linux) #:use-module (gnu packages hurd) + #:use-module (gnu packages mingw) #:use-module (guix packages) #:use-module (guix download) #:use-module (guix utils) @@ -37,13 +37,26 @@ #:use-module (ice-9 regex) #:export (cross-binutils cross-libc - cross-gcc)) + cross-gcc + cross-newlib?)) (define %xgcc ;; GCC package used as the basis for cross-compilation. It doesn't have to ;; be 'gcc' and can be a specific variant such as 'gcc-4.8'. gcc) +(define %gcc-include-paths + ;; Environment variables for header search paths. + ;; Note: See for why not 'CPATH'. + '("C_INCLUDE_PATH" + "CPLUS_INCLUDE_PATH" + "OBJC_INCLUDE_PATH" + "OBJCPLUS_INCLUDE_PATH")) + +(define %gcc-cross-include-paths + ;; Search path for target headers when cross-compiling. + (map (cut string-append "CROSS_" <>) %gcc-include-paths)) + (define (cross p target) (package (inherit p) (name (string-append (package-name p) "-cross-" target)) @@ -131,7 +144,12 @@ may be either a libc package or #f.)" "--disable-libitm" "--disable-libvtv" "--disable-libsanitizer" - ))) + )) + + ;; For a newlib (non-glibc) target + ,@(if (cross-newlib? target) + '("--with-newlib") + '())) ,(if libc flags @@ -173,12 +191,82 @@ may be either a libc package or #f.)" ;; for cross-compilers. (zero? (system* "make" "install-strip"))) ,phases)))) - (if libc + (cond + ((target-mingw? target) + `(modify-phases ,phases + (add-before + 'configure 'set-cross-path + (lambda* (#:key inputs #:allow-other-keys) + ;; Add the cross mingw headers to CROSS_C_*_INCLUDE_PATH, + ;; and remove them from C_*INCLUDE_PATH. + (let ((libc (assoc-ref inputs "libc")) + (gcc (assoc-ref inputs "gcc"))) + (define (cross? x) + (and libc (string-prefix? libc x))) + (define (unpacked-mingw-dir) + (match + (scandir + "." + (lambda (name) (string-contains name "mingw-w64"))) + ((mingw-dir) + (string-append + (getcwd) "/" mingw-dir "/mingw-w64-headers")))) + (if libc + (let ((cpath (string-append + libc "/include" + ":" libc "/i686-w64-mingw32/include"))) + (for-each (cut setenv <> cpath) + ',%gcc-cross-include-paths)) + ;; libc is false, so we are building xgcc-sans-libc + ;; Add essential headers from mingw-w64. + (let ((mingw-source (assoc-ref inputs "mingw-source"))) + (system* "tar" "xf" mingw-source) + (let ((mingw-headers (unpacked-mingw-dir))) + ;; We need _mingw.h which will gets built from + ;; _mingw.h.in by mingw-w64's configure. We + ;; cannot configure mingw-w64 until we have + ;; xgcc-sans-libc; substitute to the rescue. + (copy-file (string-append mingw-headers + "/crt/_mingw.h.in") + (string-append mingw-headers + "/crt/_mingw.h")) + (substitute* (string-append mingw-headers + "/crt/_mingw.h") + (("@MINGW_HAS_SECURE_API@") + "#define MINGW_HAS_SECURE_API 1")) + (let ((cpath + (string-append + mingw-headers "/include" + ":" mingw-headers "/crt" + ":" mingw-headers "/defaults/include"))) + (for-each (cut setenv <> cpath) + (cons + "CROSS_LIBRARY_PATH" + ',%gcc-cross-include-paths)))) + (when libc + (setenv "CROSS_LIBRARY_PATH" + (string-append + libc "/lib" + ":" libc "/i686-w64-mingw32/lib"))))) + (setenv "CPP" (string-append gcc "/bin/cpp")) + (for-each + (lambda (var) + (and=> + (getenv var) + (lambda (value) + (let* ((path (search-path-as-string->list + value)) + (native-path (list->search-path-as-string + (remove cross? path) ":"))) + (setenv var native-path))))) + (cons "LIBRARY_PATH" ',%gcc-include-paths)) + #t))))) + (libc `(alist-cons-before 'configure 'set-cross-path (lambda* (#:key inputs #:allow-other-keys) - ;; Add the cross kernel headers to CROSS_CPATH, and remove them - ;; from CPATH. + ;; Add the cross kernel headers to CROSS_CPATH, and remove + ;; them from CPATH. (let ((libc (assoc-ref inputs "libc")) (kernel (assoc-ref inputs "xkernel-headers"))) (define (cross? x) @@ -189,37 +277,40 @@ may be either a libc package or #f.)" libc "/include" ":" kernel "/include"))) (for-each (cut setenv <> cpath) - '("CROSS_C_INCLUDE_PATH" - "CROSS_CPLUS_INCLUDE_PATH" - "CROSS_OBJC_INCLUDE_PATH" - "CROSS_OBJCPLUS_INCLUDE_PATH"))) + ',%gcc-cross-include-paths)) (setenv "CROSS_LIBRARY_PATH" (string-append libc "/lib:" kernel "/lib")) ;for Hurd's libihash (for-each (lambda (var) - (and=> (getenv var) - (lambda (value) - (let* ((path (search-path-as-string->list value)) - (native-path (list->search-path-as-string - (remove cross? path) ":"))) - (setenv var native-path))))) - '("C_INCLUDE_PATH" - "CPLUS_INCLUDE_PATH" - "OBJC_INCLUDE_PATH" - "OBJCPLUS_INCLUDE_PATH" - "LIBRARY_PATH")) + (and=> + (getenv var) + (lambda (value) + (let* ((path (search-path-as-string->list value)) + (native-path (list->search-path-as-string + (remove cross? path) ":"))) + (setenv var native-path))))) + (cons "LIBRARY_PATH" ',%gcc-include-paths)) #t)) - ,phases) - phases))))))) + ,phases)) + (else phases)))))))) (define (cross-gcc-patches target) "Return GCC patches needed for TARGET." (cond ((string-prefix? "xtensa-" target) ;; Patch by Qualcomm needed to build the ath9k-htc firmware. (search-patches "ath9k-htc-firmware-gcc.patch")) + ((target-mingw? target) + (search-patches "gcc-4.9.3-mingw-gthr-default.patch")) (else '()))) +(define (cross-gcc-snippet target) + "Return GCC snippet needed for TARGET." + (cond ((target-mingw? target) + '(copy-recursively "libstdc++-v3/config/os/mingw32-w64" + "libstdc++-v3/config/os/newlib")) + (else #f))) + (define* (cross-gcc target #:optional (xbinutils (cross-binutils target)) libc) "Return a cross-compiler for TARGET, where TARGET is a GNU triplet. Use @@ -234,7 +325,10 @@ GCC that does not target a libc; otherwise, target that libc." (append (origin-patches (package-source %xgcc)) (cons (search-patch "gcc-cross-environment-variables.patch") - (cross-gcc-patches target)))))) + (cross-gcc-patches target)))) + (modules '((guix build utils))) + (snippet + (cross-gcc-snippet target)))) ;; For simplicity, use a single output. Otherwise libgcc_s & co. are not ;; found by default, etc. @@ -244,6 +338,8 @@ GCC that does not target a libc; otherwise, target that libc." `(#:implicit-inputs? #f #:modules ((guix build gnu-build-system) (guix build utils) + (ice-9 ftw) + (ice-9 match) (ice-9 regex) (srfi srfi-1) (srfi srfi-26)) @@ -264,34 +360,32 @@ GCC that does not target a libc; otherwise, target that libc." ;; Remaining inputs. ,@(let ((inputs (append (package-inputs %xgcc) (alist-delete "libc" (%final-inputs))))) - (if libc - `(("libc" ,libc) - ("xkernel-headers" ;the target headers - ,@(assoc-ref (package-propagated-inputs libc) - "kernel-headers")) - ,@inputs) - inputs)))) + (cond + ((target-mingw? target) + (if libc + `(("libc" ,mingw-w64) + ,@inputs) + `(("mingw-source" ,(package-source mingw-w64)) + ,@inputs))) + (libc + `(("libc" ,libc) + ("xkernel-headers" ;the target headers + ,@(assoc-ref (package-propagated-inputs libc) + "kernel-headers")) + ,@inputs)) + (else inputs))))) (inputs '()) ;; Only search target inputs, not host inputs. - ;; Note: See for why not 'CPATH'. - (search-paths - (list (search-path-specification - (variable "CROSS_C_INCLUDE_PATH") - (files '("include"))) - (search-path-specification - (variable "CROSS_CPLUS_INCLUDE_PATH") - (files '("include"))) - (search-path-specification - (variable "CROSS_OBJC_INCLUDE_PATH") - (files '("include"))) - (search-path-specification - (variable "CROSS_OBJCPLUS_INCLUDE_PATH") - (files '("include"))) - (search-path-specification - (variable "CROSS_LIBRARY_PATH") - (files '("lib" "lib64"))))) + (search-paths (cons (search-path-specification + (variable "CROSS_LIBRARY_PATH") + (files '("lib" "lib64"))) + (map (lambda (variable) + (search-path-specification + (variable variable) + (files '("include")))) + %gcc-cross-include-paths))) (native-search-paths '()))) (define* (cross-kernel-headers target @@ -464,61 +558,72 @@ XBINUTILS and the cross tool chain." (_ glibc/linux))) ;; Use (cross-libc-for-target ...) to determine the correct libc to use. - (let ((libc (cross-libc-for-target target))) - (package (inherit libc) - (name (string-append "glibc-cross-" target)) - (arguments - (substitute-keyword-arguments - `(;; Disable stripping (see above.) - #:strip-binaries? #f - - ;; This package is used as a target input, but it should not have - ;; the usual cross-compilation inputs since that would include - ;; itself. - #:implicit-cross-inputs? #f - ;; We need SRFI 26. - #:modules ((guix build gnu-build-system) - (guix build utils) - (srfi srfi-26)) - - ,@(package-arguments libc)) - ((#:configure-flags flags) - `(cons ,(string-append "--host=" target) - ,flags)) - ((#:phases phases) - `(alist-cons-before - 'configure 'set-cross-kernel-headers-path - (lambda* (#:key inputs #:allow-other-keys) - (let* ((kernel (assoc-ref inputs "kernel-headers")) - (cpath (string-append kernel "/include"))) - (for-each (cut setenv <> cpath) - '("CROSS_C_INCLUDE_PATH" - "CROSS_CPLUS_INCLUDE_PATH" - "CROSS_OBJC_INCLUDE_PATH" - "CROSS_OBJCPLUS_INCLUDE_PATH")) - (setenv "CROSS_LIBRARY_PATH" - (string-append kernel "/lib")) ;for Hurd's libihash - #t)) - ,phases)))) - - ;; Shadow the native "kernel-headers" because glibc's recipe expects the - ;; "kernel-headers" input to point to the right thing. - (propagated-inputs `(("kernel-headers" ,xheaders))) - - ;; FIXME: 'static-bash' should really be an input, not a native input, but - ;; to do that will require building an intermediate cross libc. - (inputs '()) - - (native-inputs `(("cross-gcc" ,xgcc) - ("cross-binutils" ,xbinutils) - ,@(if (string-match (or "i586-pc-gnu" "i586-gnu") target) - `(("cross-mig" - ,@(assoc-ref (package-native-inputs xheaders) - "cross-mig"))) - '()) - ,@(package-inputs libc) ;FIXME: static-bash - ,@(package-native-inputs libc)))))) + (if (cross-newlib? target) + (native-libc target) + (let ((libc (cross-libc-for-target target))) + (package (inherit libc) + (name (string-append "glibc-cross-" target)) + (arguments + (substitute-keyword-arguments + `(;; Disable stripping (see above.) + #:strip-binaries? #f + + ;; This package is used as a target input, but it should not have + ;; the usual cross-compilation inputs since that would include + ;; itself. + #:implicit-cross-inputs? #f + + ;; We need SRFI 26. + #:modules ((guix build gnu-build-system) + (guix build utils) + (srfi srfi-26)) + + ,@(package-arguments libc)) + ((#:configure-flags flags) + `(cons ,(string-append "--host=" target) + ,flags)) + ((#:phases phases) + `(alist-cons-before + 'configure 'set-cross-kernel-headers-path + (lambda* (#:key inputs #:allow-other-keys) + (let* ((kernel (assoc-ref inputs "kernel-headers")) + (cpath (string-append kernel "/include"))) + (for-each (cut setenv <> cpath) + '("CROSS_C_INCLUDE_PATH" + "CROSS_CPLUS_INCLUDE_PATH" + "CROSS_OBJC_INCLUDE_PATH" + "CROSS_OBJCPLUS_INCLUDE_PATH")) + (setenv "CROSS_LIBRARY_PATH" + (string-append kernel "/lib")) ;for Hurd's libihash + #t)) + ,phases)))) + + ;; Shadow the native "kernel-headers" because glibc's recipe expects the + ;; "kernel-headers" input to point to the right thing. + (propagated-inputs `(("kernel-headers" ,xheaders))) + + ;; FIXME: 'static-bash' should really be an input, not a native input, but + ;; to do that will require building an intermediate cross libc. + (inputs '()) + + (native-inputs `(("cross-gcc" ,xgcc) + ("cross-binutils" ,xbinutils) + ,@(if (string-match (or "i586-pc-gnu" "i586-gnu") target) + `(("cross-mig" + ,@(assoc-ref (package-native-inputs xheaders) + "cross-mig"))) + '()) + ,@(package-inputs libc) ;FIXME: static-bash + ,@(package-native-inputs libc))))))) + +(define (native-libc target) + (if (target-mingw? target) + mingw-w64 + glibc)) + +(define (cross-newlib? target) + (not (eq? (native-libc target) glibc))) ;;; Concrete cross tool chains are instantiated like this: diff --git a/guix/utils.scm b/guix/utils.scm index 65a2baa0a2..06f49daca8 100644 --- a/guix/utils.scm +++ b/guix/utils.scm @@ -70,6 +70,7 @@ %current-system %current-target-system package-name->name+version + target-mingw? version-compare version>? version>=? @@ -508,6 +509,10 @@ returned. Both parts must not contain any '@'." (idx (values (substring spec 0 idx) (substring spec (1+ idx)))))) +(define* (target-mingw? #:optional (target (%current-target-system))) + (and target + (string-suffix? "-mingw32" target))) + (define version-compare (let ((strverscmp (let ((sym (or (dynamic-func "strverscmp" (dynamic-link)) -- cgit v1.2.3 From f13f60cb2620433280ccb2132376b7c6d3dbc06f Mon Sep 17 00:00:00 2001 From: Manolis Ragkousis Date: Wed, 30 Nov 2016 16:49:48 +0200 Subject: gnu: make-bootstrap: Produce the correct %glibc-bootstrap-tarball for Hurd systems. * gnu/packages/make-bootstrap.scm (%glibc-bootstrap-tarball): Make it a procedure. (%glibc-stripped): Make it a procedure and move the kernel specific part from here to ... * guix/build/make-bootstrap.scm (make-stripped-libc): ... here. New file. * Makefile.am (MODULES): Add it. --- Makefile.am | 1 + gnu/packages/make-bootstrap.scm | 67 +++++++++++--------------------- guix/build/make-bootstrap.scm | 84 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 44 deletions(-) create mode 100644 guix/build/make-bootstrap.scm (limited to 'guix') diff --git a/Makefile.am b/Makefile.am index 9d62f48024..0e3ddac146 100644 --- a/Makefile.am +++ b/Makefile.am @@ -112,6 +112,7 @@ MODULES = \ guix/build/graft.scm \ guix/build/bournish.scm \ guix/build/qt-utils.scm \ + guix/build/make-bootstrap.scm \ guix/search-paths.scm \ guix/packages.scm \ guix/import/utils.scm \ diff --git a/gnu/packages/make-bootstrap.scm b/gnu/packages/make-bootstrap.scm index f31db6aaef..44a7fd3a16 100644 --- a/gnu/packages/make-bootstrap.scm +++ b/gnu/packages/make-bootstrap.scm @@ -32,6 +32,7 @@ #:use-module (gnu packages guile) #:use-module (gnu packages bdw-gc) #:use-module (gnu packages linux) + #:use-module (gnu packages hurd) #:use-module (gnu packages multiprecision) #:use-module (ice-9 match) #:use-module (srfi srfi-1) @@ -332,61 +333,39 @@ for `sh' in $PATH, and without nscd, and with static NSS modules." #t)))) (inputs `(("binutils" ,%binutils-static))))) -(define %glibc-stripped +(define (%glibc-stripped) ;; GNU libc's essential shared libraries, dynamic linker, and headers, ;; with all references to store directories stripped. As a result, ;; libc.so is unusable and need to be patched for proper relocation. + (define (hurd-triplet? triplet) + (and (string-suffix? "-gnu" triplet) + (not (string-contains triplet "linux")))) + (let ((glibc (glibc-for-bootstrap))) (package (inherit glibc) (name "glibc-stripped") (build-system trivial-build-system) (arguments - `(#:modules ((guix build utils)) + `(#:modules ((guix build utils) + (guix build make-bootstrap)) #:builder (begin - (use-modules (guix build utils)) - - (setvbuf (current-output-port) _IOLBF) - (let* ((out (assoc-ref %outputs "out")) - (libdir (string-append out "/lib")) - (incdir (string-append out "/include")) - (libc (assoc-ref %build-inputs "libc")) - (linux (assoc-ref %build-inputs "kernel-headers"))) - (mkdir-p libdir) - (for-each (lambda (file) - (let ((target (string-append libdir "/" - (basename file)))) - (copy-file file target) - (remove-store-references target))) - (find-files (string-append libc "/lib") - "^(crt.*|ld.*|lib(c|m|dl|rt|pthread|nsl|util).*\\.so(\\..*)?|libc_nonshared\\.a)$")) - - (copy-recursively (string-append libc "/include") incdir) - - ;; Copy some of the Linux-Libre headers that glibc headers - ;; refer to. - (mkdir (string-append incdir "/linux")) - (for-each (lambda (file) - (copy-file (string-append linux "/include/linux/" file) - (string-append incdir "/linux/" - (basename file)))) - '("limits.h" "errno.h" "socket.h" "kernel.h" - "sysctl.h" "param.h" "ioctl.h" "types.h" - "posix_types.h" "stddef.h")) - - (copy-recursively (string-append linux "/include/asm") - (string-append incdir "/asm")) - (copy-recursively (string-append linux "/include/asm-generic") - (string-append incdir "/asm-generic")) - - #t)))) - (inputs `(("libc" ,(let ((target (%current-target-system))) + (use-modules (guix build make-bootstrap)) + (make-stripped-libc (assoc-ref %outputs "out") + (assoc-ref %build-inputs "libc") + (assoc-ref %build-inputs "kernel-headers"))))) + (inputs `(("kernel-headers" + ,(if (or (and (%current-target-system) + (hurd-triplet? (%current-target-system))) + (string-suffix? "-hurd" (%current-system))) + gnumach-headers + linux-libre-headers)) + ("libc" ,(let ((target (%current-target-system))) (if target (glibc-for-bootstrap (parameterize ((%current-target-system #f)) (cross-libc target))) - glibc))) - ("kernel-headers" ,linux-libre-headers))) + glibc))))) ;; Only one output. (outputs '("out"))))) @@ -647,9 +626,9 @@ for `sh' in $PATH, and without nscd, and with static NSS modules." ;; A tarball with the statically-linked Binutils programs. (tarball-package %binutils-static-stripped)) -(define %glibc-bootstrap-tarball +(define (%glibc-bootstrap-tarball) ;; A tarball with GNU libc's shared libraries, dynamic linker, and headers. - (tarball-package %glibc-stripped)) + (tarball-package (%glibc-stripped))) (define %gcc-bootstrap-tarball ;; A tarball with a dynamic-linked GCC and its headers. @@ -689,7 +668,7 @@ for `sh' in $PATH, and without nscd, and with static NSS modules." (inputs `(("guile-tarball" ,%guile-bootstrap-tarball) ("gcc-tarball" ,%gcc-bootstrap-tarball) ("binutils-tarball" ,%binutils-bootstrap-tarball) - ("glibc-tarball" ,%glibc-bootstrap-tarball) + ("glibc-tarball" ,(%glibc-bootstrap-tarball)) ("coreutils&co-tarball" ,%bootstrap-binaries-tarball))) (synopsis "Tarballs containing all the bootstrap binaries") (description synopsis) diff --git a/guix/build/make-bootstrap.scm b/guix/build/make-bootstrap.scm new file mode 100644 index 0000000000..bc4c0e3d5f --- /dev/null +++ b/guix/build/make-bootstrap.scm @@ -0,0 +1,84 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2015 Manolis Fragkiskos Ragkousis +;;; Copyright © 2015 Ludovic Courtès +;;; +;;; 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 . + +(define-module (guix build make-bootstrap) + #:use-module (srfi srfi-1) + #:use-module (srfi srfi-11) + #:use-module (srfi srfi-19) + #:use-module (srfi srfi-26) + #:use-module (guix build utils) + #:export (make-stripped-libc)) + +;; Commentary: +;; +;; This module provides facilities to build the bootstrap binaries. +;; +;; Code: + +(define (make-stripped-libc output libc kernel-headers) + "Copy to OUTPUT the subset of LIBC and KERNEL-HEADERS that is needed +when producing a bootstrap libc." + + (define (copy-mach-headers output kernel-headers) + (let* ((incdir (string-append output "/include"))) + (copy-recursively (string-append libc "/include") incdir) + + (copy-recursively (string-append kernel-headers "/include/mach") + (string-append incdir "/mach")) + #t)) + + (define (copy-linux-headers output kernel-headers) + (let* ((incdir (string-append output "/include"))) + (copy-recursively (string-append libc "/include") incdir) + + ;; Copy some of the Linux-Libre headers that glibc headers + ;; refer to. + (mkdir (string-append incdir "/linux")) + (for-each (lambda (file) + (install-file (string-append kernel-headers "/include/linux/" file) + (string-append incdir "/linux"))) + '("limits.h" "errno.h" "socket.h" "kernel.h" + "sysctl.h" "param.h" "ioctl.h" "types.h" + "posix_types.h" "stddef.h")) + + (copy-recursively (string-append kernel-headers "/include/asm") + (string-append incdir "/asm")) + (copy-recursively (string-append kernel-headers "/include/asm-generic") + (string-append incdir "/asm-generic")) + #t)) + + (define %libc-object-files-rx "^(crt.*|ld.*|lib(c|m|dl|rt|pthread|nsl|\ +util).*\\.so(\\..*)?|lib(machuser|hurduser).so.*|libc(rt|)_nonshared\\.a)$") + + (setvbuf (current-output-port) _IOLBF) + (let* ((libdir (string-append output "/lib"))) + (mkdir-p libdir) + (for-each (lambda (file) + (let ((target (string-append libdir "/" + (basename file)))) + (copy-file file target) + (remove-store-references target))) + (find-files (string-append libc "/lib") %libc-object-files-rx)) + #t) + + (if (directory-exists? (string-append kernel-headers "/include/mach")) + (copy-mach-headers output kernel-headers) + (copy-linux-headers output kernel-headers))) + + -- cgit v1.2.3 From 13d5e8dae5f1282eac630dea428c5fe3dc073fb4 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Thu, 8 Dec 2016 14:03:33 +0100 Subject: store: 'open-connection' no longer raises '&nar-error' for protocol errors. * guix/store.scm (open-connection): Guard body against 'nar-error?' and re-raise as '&nix-connection-error'. * tests/store.scm ("connection handshake error"): New test. --- guix/store.scm | 53 ++++++++++++++++++++++++++++++----------------------- tests/store.scm | 9 +++++++++ 2 files changed, 39 insertions(+), 23 deletions(-) (limited to 'guix') diff --git a/guix/store.scm b/guix/store.scm index a669011f3a..49549d0771 100644 --- a/guix/store.scm +++ b/guix/store.scm @@ -374,29 +374,36 @@ space on the file system so that the garbage collector can still operate, should the disk become full. When CPU-AFFINITY is true, it must be an integer corresponding to an OS-level CPU number to which the daemon's worker process for this connection will be pinned. Return a server object." - (let ((port (or port (open-unix-domain-socket file)))) - (write-int %worker-magic-1 port) - (let ((r (read-int port))) - (and (eqv? r %worker-magic-2) - (let ((v (read-int port))) - (and (eqv? (protocol-major %protocol-version) - (protocol-major v)) - (begin - (write-int %protocol-version port) - (when (>= (protocol-minor v) 14) - (write-int (if cpu-affinity 1 0) port) - (when cpu-affinity - (write-int cpu-affinity port))) - (when (>= (protocol-minor v) 11) - (write-int (if reserve-space? 1 0) port)) - (let ((conn (%make-nix-server port - (protocol-major v) - (protocol-minor v) - (make-hash-table 100) - (make-hash-table 100)))) - (let loop ((done? (process-stderr conn))) - (or done? (process-stderr conn))) - conn)))))))) + (guard (c ((nar-error? c) + ;; One of the 'write-' or 'read-' calls below failed, but this is + ;; really a connection error. + (raise (condition + (&nix-connection-error (file (or port file)) + (errno EPROTO)) + (&message (message "build daemon handshake failed")))))) + (let ((port (or port (open-unix-domain-socket file)))) + (write-int %worker-magic-1 port) + (let ((r (read-int port))) + (and (eqv? r %worker-magic-2) + (let ((v (read-int port))) + (and (eqv? (protocol-major %protocol-version) + (protocol-major v)) + (begin + (write-int %protocol-version port) + (when (>= (protocol-minor v) 14) + (write-int (if cpu-affinity 1 0) port) + (when cpu-affinity + (write-int cpu-affinity port))) + (when (>= (protocol-minor v) 11) + (write-int (if reserve-space? 1 0) port)) + (let ((conn (%make-nix-server port + (protocol-major v) + (protocol-minor v) + (make-hash-table 100) + (make-hash-table 100)))) + (let loop ((done? (process-stderr conn))) + (or done? (process-stderr conn))) + conn))))))))) (define (close-connection server) "Close the connection to SERVER." diff --git a/tests/store.scm b/tests/store.scm index 38b8efce96..123ea8a787 100644 --- a/tests/store.scm +++ b/tests/store.scm @@ -48,6 +48,15 @@ (test-begin "store") +(test-equal "connection handshake error" + EPROTO + (let ((port (%make-void-port "rw"))) + (guard (c ((nix-connection-error? c) + (and (eq? port (nix-connection-error-file c)) + (nix-connection-error-code c)))) + (open-connection #f #:port port) + 'broken))) + (test-equal "store-path-hash-part" "283gqy39v3g9dxjy26rynl0zls82fmcg" (store-path-hash-part -- cgit v1.2.3 From bd8345777f5a48ee61656248655ebac71a09e926 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Fri, 9 Dec 2016 14:54:42 +0100 Subject: offload: Do not read ~/.ssh/known_hosts. * guix/scripts/offload.scm (open-ssh-session): Pass #:knownhosts to 'make-session'. --- guix/scripts/offload.scm | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'guix') diff --git a/guix/scripts/offload.scm b/guix/scripts/offload.scm index ebff11664d..f25cc5e7bb 100644 --- a/guix/scripts/offload.scm +++ b/guix/scripts/offload.scm @@ -177,6 +177,14 @@ private key from '~a': ~a") ;; #:log-verbosity 'protocol #:identity (build-machine-private-key machine) + ;; By default libssh reads ~/.ssh/known_hosts + ;; and uses that to adjust its choice of cipher + ;; suites, which changes the type of host key + ;; that the server sends (RSA vs. Ed25519, + ;; etc.). Opt for something reproducible and + ;; stateless instead. + #:knownhosts "/dev/null" + ;; We need lightweight compression when ;; exchanging full archives. #:compression -- cgit v1.2.3 From 2b513387cd63f82afc8fb6a076674b323e964b7f Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Fri, 9 Dec 2016 23:00:08 +0100 Subject: offload: Test each machine only once. * guix/scripts/offload.scm (check-machine-availability)[build-machine=?]: New procedure. Add call to 'delete-duplicates'. --- guix/scripts/offload.scm | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'guix') diff --git a/guix/scripts/offload.scm b/guix/scripts/offload.scm index f25cc5e7bb..f56220ff69 100644 --- a/guix/scripts/offload.scm +++ b/guix/scripts/offload.scm @@ -710,7 +710,14 @@ allowed on MACHINE. Return +∞ if MACHINE is unreachable." (define (check-machine-availability machine-file) "Check that each machine in MACHINE-FILE is usable as a build machine." - (let ((machines (build-machines machine-file))) + (define (build-machine=? m1 m2) + (and (string=? (build-machine-name m1) (build-machine-name m2)) + (= (build-machine-port m1) (build-machine-port m2)))) + + ;; A given build machine may appear several times (e.g., once for + ;; "x86_64-linux" and a second time for "i686-linux"); test them only once. + (let ((machines (delete-duplicates (build-machines machine-file) + build-machine=?))) (info (_ "testing ~a build machines defined in '~a'...~%") (length machines) machine-file) (let* ((names (map build-machine-name machines)) -- cgit v1.2.3 From 27991c97e64c95be4cae7f2b0a843565df329215 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Fri, 9 Dec 2016 23:12:06 +0100 Subject: offload: Allow testing machines that match a regexp. * guix/scripts/offload.scm (check-machine-availability): Add 'pred' parameter and honor it. (guix-offload): for the "test" sub-command, accept an extra 'regexp' parameter. Pass a second argument to 'check-machine-availability'. --- doc/guix.texi | 6 ++++++ guix/scripts/offload.scm | 25 ++++++++++++++++--------- 2 files changed, 22 insertions(+), 9 deletions(-) (limited to 'guix') diff --git a/doc/guix.texi b/doc/guix.texi index 71de73b953..0cb1bc7665 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -1005,6 +1005,12 @@ command line: # guix offload test machines-qualif.scm @end example +Last, you can test the subset of the machines whose name matches a +regular expression like this: + +@example +# guix offload test machines.scm '\.gnu\.org$' +@end example @node Invoking guix-daemon @section Invoking @command{guix-daemon} diff --git a/guix/scripts/offload.scm b/guix/scripts/offload.scm index f56220ff69..c98cf8c534 100644 --- a/guix/scripts/offload.scm +++ b/guix/scripts/offload.scm @@ -708,16 +708,18 @@ allowed on MACHINE. Return +∞ if MACHINE is unreachable." (leave (_ "failed to import '~a' from '~a'~%") item name))))) -(define (check-machine-availability machine-file) - "Check that each machine in MACHINE-FILE is usable as a build machine." +(define (check-machine-availability machine-file pred) + "Check that each machine matching PRED in MACHINE-FILE is usable as a build +machine." (define (build-machine=? m1 m2) (and (string=? (build-machine-name m1) (build-machine-name m2)) (= (build-machine-port m1) (build-machine-port m2)))) ;; A given build machine may appear several times (e.g., once for ;; "x86_64-linux" and a second time for "i686-linux"); test them only once. - (let ((machines (delete-duplicates (build-machines machine-file) - build-machine=?))) + (let ((machines (filter pred + (delete-duplicates (build-machines machine-file) + build-machine=?)))) (info (_ "testing ~a build machines defined in '~a'...~%") (length machines) machine-file) (let* ((names (map build-machine-name machines)) @@ -781,11 +783,16 @@ allowed on MACHINE. Return +∞ if MACHINE is unreachable." (loop (read-line))))))) (("test" rest ...) (with-error-handling - (let ((file (match rest - ((file) file) - (() %machine-file) - (_ (leave (_ "wrong number of arguments~%")))))) - (check-machine-availability (or file %machine-file))))) + (let-values (((file pred) + (match rest + ((file regexp) + (values file + (compose (cut string-match regexp <>) + build-machine-name))) + ((file) (values file (const #t))) + (() (values %machine-file (const #t))) + (_ (leave (_ "wrong number of arguments~%")))))) + (check-machine-availability (or file %machine-file) pred)))) (("--version") (show-version-and-exit "guix offload")) (("--help") -- cgit v1.2.3 From 63773200d7ac68fcaee6efd9ffe8ea7aa3fafa38 Mon Sep 17 00:00:00 2001 From: Eric Bavier Date: Sun, 4 Dec 2016 22:42:49 -0600 Subject: import: json: Silence json-fetch output. * guix/import/json.scm (json-fetch): Use http-fetch instead of url-fetch to avoid writing to stdout and a temporary file for each invocation. * guix/import/gem.scm (rubygems-fetch): Do not redirect json-fetch output to /dev/null. * guix/import/pypi.scm (pypi-fetch): Likewise. --- guix/import/gem.scm | 10 ++-------- guix/import/json.scm | 17 ++++++++++------- guix/import/pypi.scm | 10 ++-------- 3 files changed, 14 insertions(+), 23 deletions(-) (limited to 'guix') diff --git a/guix/import/gem.scm b/guix/import/gem.scm index 3d0c190656..3ad7facc7f 100644 --- a/guix/import/gem.scm +++ b/guix/import/gem.scm @@ -38,14 +38,8 @@ (define (rubygems-fetch name) "Return an alist representation of the RubyGems metadata for the package NAME, or #f on failure." - ;; XXX: We want to silence the download progress report, which is especially - ;; annoying for 'guix refresh', but we have to use a file port. - (call-with-output-file "/dev/null" - (lambda (null) - (with-error-to-port null - (lambda () - (json-fetch - (string-append "https://rubygems.org/api/v1/gems/" name ".json"))))))) + (json-fetch + (string-append "https://rubygems.org/api/v1/gems/" name ".json"))) (define (ruby-package-name name) "Given the NAME of a package on RubyGems, return a Guix-compliant name for diff --git a/guix/import/json.scm b/guix/import/json.scm index c3092a5a9d..5940f5e48f 100644 --- a/guix/import/json.scm +++ b/guix/import/json.scm @@ -1,6 +1,6 @@ ;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2014 David Thompson -;;; Copyright © 2015 Eric Bavier +;;; Copyright © 2015, 2016 Eric Bavier ;;; ;;; This file is part of GNU Guix. ;;; @@ -19,14 +19,17 @@ (define-module (guix import json) #:use-module (json) - #:use-module (guix utils) + #:use-module (guix http-client) #:use-module (guix import utils) + #:use-module (srfi srfi-34) #:export (json-fetch)) (define (json-fetch url) "Return an alist representation of the JSON resource URL, or #f on failure." - (call-with-temporary-output-file - (lambda (temp port) - (and (url-fetch url temp) - (hash-table->alist - (call-with-input-file temp json->scm)))))) + (guard (c ((and (http-get-error? c) + (= 404 (http-get-error-code c))) + #f)) ;"expected" if package is unknown + (let* ((port (http-fetch url)) + (result (hash-table->alist (json->scm port)))) + (close-port port) + result))) diff --git a/guix/import/pypi.scm b/guix/import/pypi.scm index 68153d5ab1..9794ff9757 100644 --- a/guix/import/pypi.scm +++ b/guix/import/pypi.scm @@ -51,14 +51,8 @@ (define (pypi-fetch name) "Return an alist representation of the PyPI metadata for the package NAME, or #f on failure." - ;; XXX: We want to silence the download progress report, which is especially - ;; annoying for 'guix refresh', but we have to use a file port. - (call-with-output-file "/dev/null" - (lambda (null) - (with-error-to-port null - (lambda () - (json-fetch (string-append "https://pypi.python.org/pypi/" - name "/json"))))))) + (json-fetch (string-append "https://pypi.python.org/pypi/" + name "/json"))) ;; For packages found on PyPI that lack a source distribution. (define-condition-type &missing-source-error &error -- cgit v1.2.3 From d391ad57d69facc25bba74d959421f7725cbf000 Mon Sep 17 00:00:00 2001 From: Eric Bavier Date: Mon, 12 Dec 2016 21:57:09 -0600 Subject: import: cpan: Move core-module? to top-level. * guix/import/cpan.scm (cpan-module->sexp): Move local core-module? procedure to ... (core-module?): ... here. --- guix/import/cpan.scm | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) (limited to 'guix') diff --git a/guix/import/cpan.scm b/guix/import/cpan.scm index d244969c9e..f3dd01bfea 100644 --- a/guix/import/cpan.scm +++ b/guix/import/cpan.scm @@ -116,6 +116,31 @@ if the original's domain was metacpan." (and (access? core X_OK) core)))) +(define core-module? + (let ((perl-version (package-version perl)) + (rx (make-regexp + (string-append "released with perl v?([0-9\\.]*)" + "(.*and removed from v?([0-9\\.]*))?")))) + (lambda (name) + (define (version-between? lower version upper) + (and (version>=? version lower) + (or (not upper) + (version>? upper version)))) + (and (force %corelist) + (parameterize ((current-error-port (%make-void-port "w"))) + (let* ((corelist (open-pipe* OPEN_READ (force %corelist) name))) + (let loop () + (let ((line (read-line corelist))) + (if (eof-object? line) + (begin (close-pipe corelist) #f) + (or (and=> (regexp-exec rx line) + (lambda (m) + (let ((first (match:substring m 1)) + (last (match:substring m 3))) + (version-between? + first perl-version last)))) + (loop))))))))))) + (define (cpan-module->sexp meta) "Return the `package' s-expression for a CPAN module from the metadata in META." @@ -132,31 +157,6 @@ META." ((? number? vrs) (number->string vrs)) ((? string? vrs) vrs))) - (define core-module? - (let ((perl-version (package-version perl)) - (rx (make-regexp - (string-append "released with perl v?([0-9\\.]*)" - "(.*and removed from v?([0-9\\.]*))?")))) - (lambda (name) - (define (version-between? lower version upper) - (and (version>=? version lower) - (or (not upper) - (version>? upper version)))) - (and (force %corelist) - (parameterize ((current-error-port (%make-void-port "w"))) - (let* ((corelist (open-pipe* OPEN_READ (force %corelist) name))) - (let loop () - (let ((line (read-line corelist))) - (if (eof-object? line) - (begin (close-pipe corelist) #f) - (or (and=> (regexp-exec rx line) - (lambda (m) - (let ((first (match:substring m 1)) - (last (match:substring m 3))) - (version-between? - first perl-version last)))) - (loop))))))))))) - (define (convert-inputs phases) ;; Convert phase dependencies into a list of name/variable pairs. (match (flatten -- cgit v1.2.3 From ff55fe559951b88bfd691b9dada3a0f26002c4cb Mon Sep 17 00:00:00 2001 From: Eric Bavier Date: Mon, 12 Dec 2016 21:59:27 -0600 Subject: import: cpan: Add updater. * guix/import/cpan.scm (module->dist-name): Fetch the field of interest. (cpan-fetch): Accept release name rather than module name. (fix-source-url): Rename to ... (cpan-source-url): ... this. Take metadata as parameter. (package->upstream-name, cpan-version, cpan-package?, latest-release): New procedures. (cpan-module->sexp): Use cpan-version and cpan-source-url. (%cpan-updater): New variable. * guix/scripts/refresh.scm (%updaters): Add %CPAN-UPDATER. --- guix/import/cpan.scm | 120 ++++++++++++++++++++++++++++++++++++++--------- guix/scripts/refresh.scm | 1 + 2 files changed, 100 insertions(+), 21 deletions(-) (limited to 'guix') diff --git a/guix/import/cpan.scm b/guix/import/cpan.scm index f3dd01bfea..b19d56ddcf 100644 --- a/guix/import/cpan.scm +++ b/guix/import/cpan.scm @@ -24,18 +24,23 @@ #:use-module ((ice-9 popen) #:select (open-pipe* close-pipe)) #:use-module ((ice-9 rdelim) #:select (read-line)) #:use-module (srfi srfi-1) + #:use-module (srfi srfi-26) #:use-module (json) #:use-module (guix hash) #:use-module (guix store) #:use-module (guix utils) #:use-module (guix base32) - #:use-module ((guix download) #:select (download-to-store)) - #:use-module (guix import utils) + #:use-module (guix ui) + #:use-module ((guix download) #:select (download-to-store url-fetch)) + #:use-module ((guix import utils) #:select (factorize-uri + flatten assoc-ref*)) #:use-module (guix import json) #:use-module (guix packages) + #:use-module (guix upstream) #:use-module (guix derivations) #:use-module (gnu packages perl) - #:export (cpan->guix-package)) + #:export (cpan->guix-package + %cpan-updater)) ;;; Commentary: ;;; @@ -84,28 +89,49 @@ module is distributed with 'Test::Simple', so (module->dist-name \"ok\") would return \"Test-Simple\"" (assoc-ref (json-fetch (string-append "https://api.metacpan.org/module/" - module)) + module + "?fields=distribution")) "distribution")) -(define (cpan-fetch module) +(define (package->upstream-name package) + "Return the CPAN name of PACKAGE." + (let* ((properties (package-properties package)) + (upstream-name (and=> properties + (cut assoc-ref <> 'upstream-name)))) + (or upstream-name + (match (package-source package) + ((? origin? origin) + (match (origin-uri origin) + ((or (? string? url) (url _ ...)) + (match (string-match (string-append "([^/]*)-v?[0-9\\.]+") url) + (#f #f) + (m (match:substring m 1)))) + (_ #f))) + (_ #f))))) + +(define (cpan-fetch name) "Return an alist representation of the CPAN metadata for the perl module MODULE, or #f on failure. MODULE should be e.g. \"Test::Script\"" ;; This API always returns the latest release of the module. - (json-fetch (string-append "https://api.metacpan.org/release/" - ;; XXX: The 'release' api requires the "release" - ;; name of the package. This substitution seems - ;; reasonably consistent across packages. - (module->name module)))) + (json-fetch (string-append "https://api.metacpan.org/release/" name))) (define (cpan-home name) (string-append "http://search.cpan.org/dist/" name)) -(define (fix-source-url download-url) - "Return a new download URL based on DOWNLOAD-URL which now uses our mirrors, -if the original's domain was metacpan." - (regexp-substitute/global #f "http[s]?://cpan.metacpan.org" download-url +(define (cpan-source-url meta) + "Return the download URL for a module's source tarball." + (regexp-substitute/global #f "http[s]?://cpan.metacpan.org" + (assoc-ref meta "download_url") 'pre "mirror://cpan" 'post)) +(define (cpan-version meta) + "Return the version number from META." + (match (assoc-ref meta "version") + ((? number? version) + ;; version is sometimes not quoted in the module json, so it gets + ;; imported into Guile as a number, so convert it to a string. + (number->string version)) + (version version))) (define %corelist (delay @@ -152,10 +178,8 @@ META." (string-downcase name) (string-append "perl-" (string-downcase name)))) - (define version - (match (assoc-ref meta "version") - ((? number? vrs) (number->string vrs)) - ((? string? vrs) vrs))) + (define version (cpan-version meta)) + (define source-url (cpan-source-url meta)) (define (convert-inputs phases) ;; Convert phase dependencies into a list of name/variable pairs. @@ -193,8 +217,6 @@ META." (list (list guix-name (list 'quasiquote inputs)))))) - (define source-url (fix-source-url (assoc-ref meta "download_url"))) - (let ((tarball (with-store store (download-to-store store source-url)))) `(package @@ -224,5 +246,61 @@ META." (define (cpan->guix-package module-name) "Fetch the metadata for PACKAGE-NAME from metacpan.org, and return the `package' s-expression corresponding to that package, or #f on failure." - (let ((module-meta (cpan-fetch module-name))) + (let ((module-meta (cpan-fetch (module->name module-name)))) (and=> module-meta cpan-module->sexp))) + +(define (cpan-package? package) + "Return #t if PACKAGE is a package from CPAN." + (define cpan-url? + (let ((cpan-rx (make-regexp (string-append "(" + "mirror://cpan" "|" + "https?://www.cpan.org" "|" + "https?://cpan.metacpan.org" + ")")))) + (lambda (url) + (regexp-exec cpan-rx url)))) + + (let ((source-url (and=> (package-source package) origin-uri)) + (fetch-method (and=> (package-source package) origin-method))) + (and (eq? fetch-method url-fetch) + (match source-url + ((? string?) + (cpan-url? source-url)) + ((source-url ...) + (any cpan-url? source-url)))))) + +(define (latest-release package) + "Return an for the latest release of PACKAGE." + (match (cpan-fetch (package->upstream-name package)) + (#f #f) + (meta + (let ((core-inputs + (match (package-direct-inputs package) + (((_ inputs _ ...) ...) + (filter-map (match-lambda + ((and (? package?) + (? cpan-package?) + (= package->upstream-name + (? core-module? name))) + name) + (else #f)) + inputs))))) + ;; Warn about inputs that are part of perl's core + (unless (null? core-inputs) + (for-each (lambda (module) + (warning (_ "input '~a' of ~a is in Perl core~%") + module (package-name package))) + core-inputs))) + (let ((version (cpan-version meta)) + (url (cpan-source-url meta))) + (upstream-source + (package (package-name package)) + (version version) + (urls url)))))) + +(define %cpan-updater + (upstream-updater + (name 'cpan) + (description "Updater for CPAN packages") + (pred cpan-package?) + (latest latest-release))) diff --git a/guix/scripts/refresh.scm b/guix/scripts/refresh.scm index 805e4543ec..f8fb3f80ca 100644 --- a/guix/scripts/refresh.scm +++ b/guix/scripts/refresh.scm @@ -206,6 +206,7 @@ unavailable optional dependencies such as Guile-JSON." %cran-updater %bioconductor-updater %hackage-updater + ((guix import cpan) => %cpan-updater) ((guix import pypi) => %pypi-updater) ((guix import gem) => %gem-updater) ((guix import github) => %github-updater))) -- cgit v1.2.3 From af72a21a698bce94f4526fe04c0a9a06c6c3da80 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Wed, 28 Sep 2016 16:17:07 +0200 Subject: upstream: Use first url when it lacks an extension. * guix/upstream.scm (package-update): Use a url from the list when it lacks an extension. --- guix/upstream.scm | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'guix') diff --git a/guix/upstream.scm b/guix/upstream.scm index 8685afd860..22187311ea 100644 --- a/guix/upstream.scm +++ b/guix/upstream.scm @@ -241,12 +241,16 @@ and 'interactive' (default)." ((archive-type) (match (and=> (package-source package) origin-uri) ((? string? uri) - (or (file-extension uri) "gz")) + (file-extension uri)) (_ "gz"))) ((url signature-url) (find2 (lambda (url sig-url) - (string-suffix? archive-type url)) + ;; Some URIs lack a file extension, like + ;; 'https://crates.io/???/0.1/download'. In that + ;; case, pick the first URL. + (or (not archive-type) + (string-suffix? archive-type url))) urls (or signature-urls (circular-list #f))))) (let ((tarball (download-tarball store url signature-url -- cgit v1.2.3 From 4b3cb7f4bc5d5a265731fe3ecc752a25968cad45 Mon Sep 17 00:00:00 2001 From: David Craven Date: Thu, 22 Sep 2016 14:58:31 +0200 Subject: build-system: Add cargo build system. * guix/build-system/cargo.scm: New file. * guix/build/cargo-build-system.scm: New file. * Makefile.am (MODULES): Add files. --- Makefile.am | 2 + guix/build-system/cargo.scm | 150 ++++++++++++++++++++++++++++++++++++++ guix/build/cargo-build-system.scm | 104 ++++++++++++++++++++++++++ 3 files changed, 256 insertions(+) create mode 100644 guix/build-system/cargo.scm create mode 100644 guix/build/cargo-build-system.scm (limited to 'guix') diff --git a/Makefile.am b/Makefile.am index b69468b327..5cb4261f4b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -61,6 +61,7 @@ MODULES = \ guix/zlib.scm \ guix/build-system.scm \ guix/build-system/ant.scm \ + guix/build-system/cargo.scm \ guix/build-system/cmake.scm \ guix/build-system/emacs.scm \ guix/build-system/asdf.scm \ @@ -83,6 +84,7 @@ MODULES = \ guix/ui.scm \ guix/build/ant-build-system.scm \ guix/build/download.scm \ + guix/build/cargo-build-system.scm \ guix/build/cmake-build-system.scm \ guix/build/emacs-build-system.scm \ guix/build/asdf-build-system.scm \ diff --git a/guix/build-system/cargo.scm b/guix/build-system/cargo.scm new file mode 100644 index 0000000000..8d835dda1d --- /dev/null +++ b/guix/build-system/cargo.scm @@ -0,0 +1,150 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2013, 2014, 2015, 2016 Ludovic Courtès +;;; Copyright © 2013 Andreas Enge +;;; Copyright © 2013 Nikita Karetnikov +;;; Copyright © 2016 David Craven +;;; +;;; 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 . + +(define-module (guix build-system cargo) + #:use-module (guix search-paths) + #:use-module (guix store) + #:use-module (guix utils) + #:use-module (guix derivations) + #:use-module (guix packages) + #:use-module (guix build-system) + #:use-module (guix build-system gnu) + #:use-module (ice-9 match) + #:use-module (srfi srfi-26) + #:export (cargo-build-system + crate-url + crate-url? + crate-uri)) + +(define crate-url "https://crates.io/api/v1/crates/") +(define crate-url? (cut string-prefix? crate-url <>)) + +(define (crate-uri name version) + "Return a URI string for the crate package hosted at crates.io corresponding +to NAME and VERSION." + (string-append crate-url name "/" version "/download")) + +(define (default-cargo) + "Return the default Cargo package." + ;; Lazily resolve the binding to avoid a circular dependency. + (let ((rust (resolve-interface '(gnu packages rust)))) + ;; FIXME: Package cargo and replace cargo-bootstrap with cargo. + (module-ref rust 'cargo-bootstrap))) + +(define (default-rustc) + "Return the default Rustc package." + ;; Lazily resolve the binding to avoid a circular dependency. + (let ((rust (resolve-interface '(gnu packages rust)))) + (module-ref rust 'rustc))) + +(define %cargo-build-system-modules + ;; Build-side modules imported by default. + `((guix build cargo-build-system) + ,@%gnu-build-system-modules)) + +(define* (cargo-build store name inputs + #:key + (tests? #t) + (test-target #f) + (configure-flags #f) + (phases '(@ (guix build cargo-build-system) + %standard-phases)) + (outputs '("out")) + (search-paths '()) + (system (%current-system)) + (guile #f) + (imported-modules %cargo-build-system-modules) + (modules '((guix build cargo-build-system) + (guix build utils)))) + "Build SOURCE using CARGO, and with INPUTS." + + (define builder + `(begin + (use-modules ,@modules) + (cargo-build #:name ,name + #:source ,(match (assoc-ref inputs "source") + (((? derivation? source)) + (derivation->output-path source)) + ((source) + source) + (source + source)) + #:system ,system + #:test-target ,test-target + #:tests? ,tests? + #:phases ,phases + #:outputs %outputs + #:search-paths ',(map search-path-specification->sexp + search-paths) + #:inputs %build-inputs))) + + (define guile-for-build + (match guile + ((? package?) + (package-derivation store guile system #:graft? #f)) + (#f ; the default + (let* ((distro (resolve-interface '(gnu packages commencement))) + (guile (module-ref distro 'guile-final))) + (package-derivation store guile system #:graft? #f))))) + + (build-expression->derivation store name builder + #:inputs inputs + #:system system + #:modules imported-modules + #:outputs outputs + #:guile-for-build guile-for-build)) + +(define* (lower name + #:key source inputs native-inputs outputs system target + (cargo (default-cargo)) + (rustc (default-rustc)) + #:allow-other-keys + #:rest arguments) + "Return a bag for NAME." + + (define private-keywords + '(#:source #:target #:cargo #:rustc #:inputs #:native-inputs)) + + (and (not target) ;; TODO: support cross-compilation + (bag + (name name) + (system system) + (target target) + (host-inputs `(,@(if source + `(("source" ,source)) + '()) + ,@inputs + + ;; Keep the standard inputs of 'gnu-build-system' + ,@(standard-packages))) + (build-inputs `(("cargo" ,cargo) + ("rustc" ,rustc) + ,@native-inputs)) + (outputs outputs) + (build cargo-build) + (arguments (strip-keyword-arguments private-keywords arguments))))) + +(define cargo-build-system + (build-system + (name 'cargo) + (description + "Cargo build system, to build Rust crates") + (lower lower))) diff --git a/guix/build/cargo-build-system.scm b/guix/build/cargo-build-system.scm new file mode 100644 index 0000000000..460d829b3c --- /dev/null +++ b/guix/build/cargo-build-system.scm @@ -0,0 +1,104 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2016 David Craven +;;; +;;; 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 . + +(define-module (guix build cargo-build-system) + #:use-module ((guix build gnu-build-system) #:prefix gnu:) + #:use-module (guix build utils) + #:use-module (ice-9 ftw) + #:use-module (ice-9 format) + #:use-module (ice-9 match) + #:use-module (srfi srfi-1) + #:use-module (srfi srfi-26) + #:export (%standard-phases + cargo-build)) + +;; Commentary: +;; +;; Builder-side code of the standard Rust package build procedure. +;; +;; Code: + +;; FIXME: Needs to be parsed from url not package name. +(define (package-name->crate-name name) + "Return the crate name of NAME." + (match (string-split name #\-) + (("rust" rest ...) + (string-join rest "-")) + (_ #f))) + +(define* (configure #:key inputs #:allow-other-keys) + "Replace Cargo.toml [dependencies] section with guix inputs." + (let ((port (open-file "Cargo.toml" "a" #:encoding "utf-8"))) + (format port "~%[replace]~%") + (for-each + (match-lambda + ((name . path) + (let ((crate (package-name->crate-name name))) + (when (and crate path) + (match (string-split (basename path) #\-) + ((_ ... version) + (format port "\"~a:~a\" = { path = \"~a/rustsrc\" }~%" + crate version path))))))) + inputs) + (close-port port)) + #t) + +(define* (build #:key (cargo-build-flags '("--release" "--frozen")) + #:allow-other-keys) + "Build a given Cargo package." + (zero? (apply system* `("cargo" "build" ,@cargo-build-flags)))) + +(define* (check #:key tests? #:allow-other-keys) + "Run tests for a given Cargo package." + (when tests? + (zero? (system* "cargo" "test")))) + +(define* (install #:key inputs outputs #:allow-other-keys) + "Install a given Cargo package." + (let* ((out (assoc-ref outputs "out")) + (src (assoc-ref inputs "source")) + (bin (string-append out "/bin")) + (rsrc (string-append out "/rustsrc"))) + (mkdir-p rsrc) + ;; Rust doesn't have a stable ABI yet. Because of this + ;; Cargo doesn't have a search path for binaries yet. + ;; Until this changes we are working around this by + ;; distributing crates as source and replacing + ;; references in Cargo.toml with store paths. + (copy-recursively "src" (string-append rsrc "/src")) + (install-file "Cargo.toml" rsrc) + ;; When the package includes executables we install + ;; it using cargo install. This fails when the crate + ;; doesn't contain an executable. + (system* "cargo" "install" "--root" bin) + #t)) + +(define %standard-phases + ;; 'configure' phase is not needed. + (modify-phases gnu:%standard-phases + (replace 'configure configure) + (replace 'build build) + (replace 'check check) + (replace 'install install))) + +(define* (cargo-build #:key inputs (phases %standard-phases) + #:allow-other-keys #:rest args) + "Build the given Cargo package, applying all of PHASES in order." + (apply gnu:gnu-build #:inputs inputs #:phases phases args)) + +;;; cargo-build-system.scm ends here -- cgit v1.2.3 From bb3f36ed4ca83f01eb422f9de9c404d53218b382 Mon Sep 17 00:00:00 2001 From: David Craven Date: Sat, 10 Dec 2016 18:43:39 +0100 Subject: import: utils: Add some utilities. * guix/import/utils.scm (maybe-inputs, maybe-native-inputs, package->definition): New variables. --- guix/import/utils.scm | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) (limited to 'guix') diff --git a/guix/import/utils.scm b/guix/import/utils.scm index 057c2d9c7d..f304da20e6 100644 --- a/guix/import/utils.scm +++ b/guix/import/utils.scm @@ -22,6 +22,7 @@ #:use-module (guix base32) #:use-module ((guix build download) #:prefix build:) #:use-module (guix hash) + #:use-module (guix http-client) #:use-module ((guix licenses) #:prefix license:) #:use-module (guix utils) #:use-module (ice-9 match) @@ -36,6 +37,10 @@ url-fetch guix-hash-url + maybe-inputs + maybe-native-inputs + package->definition + spdx-string->license license->symbol @@ -205,3 +210,32 @@ into a proper sentence and by using two spaces between sentences." ;; Use double spacing between sentences (regexp-substitute/global #f "\\. \\b" cleaned 'pre ". " 'post))) + +(define (package-names->package-inputs names) + (map (lambda (input) + (list input (list 'unquote (string->symbol input)))) + names)) + +(define (maybe-inputs package-names) + "Given a list of PACKAGE-NAMES, tries to generate the 'inputs' field of a +package definition." + (match (package-names->package-inputs package-names) + (() + '()) + ((package-inputs ...) + `((inputs (,'quasiquote ,package-inputs)))))) + +(define (maybe-native-inputs package-names) + "Given a list of PACKAGE-NAMES, tries to generate the 'inputs' field of a +package definition." + (match (package-names->package-inputs package-names) + (() + '()) + ((package-inputs ...) + `((native-inputs (,'quasiquote ,package-inputs)))))) + +(define (package->definition guix-package) + (match guix-package + (('package ('name (? string? name)) _ ...) + `(define-public ,(string->symbol name) + ,guix-package)))) -- cgit v1.2.3 From 3e0c036584b41bcc08a8c8e040295716108bb0b2 Mon Sep 17 00:00:00 2001 From: David Craven Date: Thu, 22 Sep 2016 11:40:58 +0200 Subject: import: Add importer for rust crates. * guix/import/crate.scm: New file. * guix/scripts/import/crate.scm: New file. * guix/scripts/import.scm (importers): Add crate importer. * tests/crate.scm: New file. * doc/guix.texi: Add crate importer to table. * Makefile.am (MODULES, SCM_TESTS): Add files. --- Makefile.am | 5 +- doc/guix.texi | 5 ++ guix/import/crate.scm | 125 ++++++++++++++++++++++++++++++++++++++++++ guix/scripts/import.scm | 2 +- guix/scripts/import/crate.scm | 94 +++++++++++++++++++++++++++++++ guix/scripts/refresh.scm | 1 + tests/crate.scm | 102 ++++++++++++++++++++++++++++++++++ 7 files changed, 332 insertions(+), 2 deletions(-) create mode 100644 guix/import/crate.scm create mode 100644 guix/scripts/import/crate.scm create mode 100644 tests/crate.scm (limited to 'guix') diff --git a/Makefile.am b/Makefile.am index 5cb4261f4b..84ff6642a2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -122,6 +122,7 @@ MODULES = \ guix/import/snix.scm \ guix/import/cabal.scm \ guix/import/cran.scm \ + guix/import/crate.scm \ guix/import/hackage.scm \ guix/import/elpa.scm \ guix/scripts.scm \ @@ -141,6 +142,7 @@ MODULES = \ guix/scripts/lint.scm \ guix/scripts/challenge.scm \ guix/scripts/import/cran.scm \ + guix/scripts/import/crate.scm \ guix/scripts/import/gnu.scm \ guix/scripts/import/nix.scm \ guix/scripts/import/hackage.scm \ @@ -282,7 +284,8 @@ if HAVE_GUILE_JSON SCM_TESTS += \ tests/pypi.scm \ tests/cpan.scm \ - tests/gem.scm + tests/gem.scm \ + tests/crate.scm endif diff --git a/doc/guix.texi b/doc/guix.texi index f1c5963f76..5db20ecdfa 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -5267,6 +5267,11 @@ signatures,, emacs, The GNU Emacs Manual}). identifier. @end itemize @end table + +@item crate +@cindex crate +Import metadata from the crates.io Rust package repository +@uref{https://crates.io, crates.io}. @end table The structure of the @command{guix import} code is modular. It would be diff --git a/guix/import/crate.scm b/guix/import/crate.scm new file mode 100644 index 0000000000..e78e3ad9ca --- /dev/null +++ b/guix/import/crate.scm @@ -0,0 +1,125 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2016 David Craven +;;; +;;; 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 . + +(define-module (guix import crate) + #:use-module (guix base32) + #:use-module (guix build-system cargo) + #:use-module ((guix download) #:prefix download:) + #:use-module (guix hash) + #:use-module (guix http-client) + #:use-module (guix import json) + #:use-module (guix import utils) + #:use-module ((guix licenses) #:prefix license:) + #:use-module (guix monads) + #:use-module (guix packages) + #:use-module (guix upstream) + #:use-module (guix utils) + #:use-module (ice-9 match) + #:use-module (ice-9 pretty-print) ; recursive + #:use-module (json) + #:use-module (srfi srfi-1) + #:use-module (srfi srfi-2) + #:use-module (srfi srfi-26) + #:export (crate->guix-package + guix-package->crate-name)) + +(define (crate-fetch crate-name callback) + "Fetch the metadata for CRATE-NAME from crates.io and call the callback." + + (define (crates->inputs crates) + (sort (map (cut assoc-ref <> "crate_id") crates) string-cilicense string) + (map spdx-string->license (string-split string #\/))) + + (define (crate-kind-predicate kind) + (lambda (dep) (string=? (assoc-ref dep "kind") kind))) + + (and-let* ((crate-json (json-fetch (string-append crate-url crate-name))) + (crate (assoc-ref crate-json "crate")) + (name (assoc-ref crate "name")) + (version (assoc-ref crate "max_version")) + (home-page (assoc-ref crate "homepage")) + (synopsis (assoc-ref crate "description")) + (description (assoc-ref crate "description")) + (license (string->license (assoc-ref crate "license"))) + (path (string-append "/" version "/dependencies")) + (deps-json (json-fetch (string-append crate-url name path))) + (deps (assoc-ref deps-json "dependencies")) + (input-crates (filter (crate-kind-predicate "normal") deps)) + (native-input-crates + (filter (lambda (dep) + (not ((crate-kind-predicate "normal") dep))) deps)) + (inputs (crates->inputs input-crates)) + (native-inputs (crates->inputs native-input-crates))) + (callback #:name name #:version version + #:inputs inputs #:native-inputs native-inputs + #:home-page home-page #:synopsis synopsis + #:description description #:license license))) + +(define* (make-crate-sexp #:key name version inputs native-inputs + home-page synopsis description license + #:allow-other-keys) + "Return the `package' s-expression for a rust package with the given NAME, +VERSION, INPUTS, NATIVE-INPUTS, HOME-PAGE, SYNOPSIS, DESCRIPTION, and LICENSE." + (let* ((port (http-fetch (crate-uri name version))) + (guix-name (crate-name->package-name name)) + (inputs (map crate-name->package-name inputs)) + (native-inputs (map crate-name->package-name native-inputs)) + (pkg `(package + (name ,guix-name) + (version ,version) + (source (origin + (method url-fetch) + (uri (crate-uri ,name version)) + (file-name (string-append name "-" version ".tar.gz")) + (sha256 + (base32 + ,(bytevector->nix-base32-string (port-sha256 port)))))) + (build-system cargo-build-system) + ,@(maybe-native-inputs native-inputs) + ,@(maybe-inputs inputs) + (home-page ,home-page) + (synopsis ,synopsis) + (description ,(beautify-description description)) + (license ,(match license + (() #f) + ((license) license) + (_ `(list ,@license))))))) + (close-port port) + pkg)) + +(define (crate->guix-package crate-name) + "Fetch the metadata for CRATE-NAME from crates.io, and return the +`package' s-expression corresponding to that package, or #f on failure." + (crate-fetch crate-name make-crate-sexp)) + +(define (guix-package->crate-name package) + "Return the crate name of PACKAGE." + (and-let* ((origin (package-source package)) + (uri (origin-uri origin)) + (crate-url? uri) + (len (string-length crate-url)) + (path (xsubstring uri len)) + (parts (string-split path #\/))) + (match parts + ((name _ ...) name)))) + +(define (crate-name->package-name name) + (string-append "rust-" (string-join (string-split name #\_) "-"))) + diff --git a/guix/scripts/import.scm b/guix/scripts/import.scm index e54744feca..c671686043 100644 --- a/guix/scripts/import.scm +++ b/guix/scripts/import.scm @@ -73,7 +73,7 @@ rather than \\n." ;;; Entry point. ;;; -(define importers '("gnu" "nix" "pypi" "cpan" "hackage" "elpa" "gem" "cran")) +(define importers '("gnu" "nix" "pypi" "cpan" "hackage" "elpa" "gem" "cran" "crate")) (define (resolve-importer name) (let ((module (resolve-interface diff --git a/guix/scripts/import/crate.scm b/guix/scripts/import/crate.scm new file mode 100644 index 0000000000..4337a0b623 --- /dev/null +++ b/guix/scripts/import/crate.scm @@ -0,0 +1,94 @@ + +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2014 David Thompson +;;; Copyright © 2016 David Craven +;;; +;;; 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 . + +(define-module (guix scripts import crate) + #:use-module (guix ui) + #:use-module (guix utils) + #:use-module (guix scripts) + #:use-module (guix import crate) + #: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-crate)) + + +;;; +;;; Command-line options. +;;; + +(define %default-options + '()) + +(define (show-help) + (display (_ "Usage: guix import crate PACKAGE-NAME +Import and convert the crate.io package for PACKAGE-NAME.\n")) + (display (_ " + -h, --help display this help and exit")) + (display (_ " + -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 crate"))) + %standard-import-options)) + + +;;; +;;; Entry point. +;;; + +(define (guix-import-crate . args) + (define (parse-options) + ;; Return the alist of option values. + (args-fold* args %options + (lambda (opt name arg result) + (leave (_ "~A: unrecognized option~%") name)) + (lambda (arg result) + (alist-cons 'argument arg result)) + %default-options)) + + (let* ((opts (parse-options)) + (args (filter-map (match-lambda + (('argument . value) + value) + (_ #f)) + (reverse opts)))) + (match args + ((package-name) + (let ((sexp (crate->guix-package package-name))) + (unless sexp + (leave (_ "failed to download meta-data for package '~a'~%") + package-name)) + sexp)) + (() + (leave (_ "too few arguments~%"))) + ((many ...) + (leave (_ "too many arguments~%")))))) diff --git a/guix/scripts/refresh.scm b/guix/scripts/refresh.scm index f8fb3f80ca..72f51cbff8 100644 --- a/guix/scripts/refresh.scm +++ b/guix/scripts/refresh.scm @@ -39,6 +39,7 @@ %kernel.org-updater)) #:use-module (guix import elpa) #:use-module (guix import cran) + #:use-module (guix import crate) #:use-module (guix import hackage) #:use-module (guix gnupg) #:use-module (gnu packages) diff --git a/tests/crate.scm b/tests/crate.scm new file mode 100644 index 0000000000..18d5f72a8c --- /dev/null +++ b/tests/crate.scm @@ -0,0 +1,102 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2014 David Thompson +;;; Copyright © 2016 David Craven +;;; +;;; 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 . + +(define-module (test-crate) + #:use-module (guix import crate) + #:use-module (guix base32) + #:use-module (guix build-system cargo) + #:use-module (guix hash) + #:use-module (guix tests) + #:use-module (ice-9 iconv) + #:use-module (ice-9 match) + #:use-module (srfi srfi-64)) + +(define test-crate + "{ + \"crate\": { + \"max_version\": \"1.0.0\", + \"name\": \"foo\", + \"license\": \"MIT/Apache-2.0\", + \"description\": \"summary\", + \"homepage\": \"http://example.com\", + } +}") + +(define test-dependencies + "{ + \"dependencies\": [ + { + \"crate_id\": \"bar\", + \"kind\": \"normal\", + } + ] +}") + +(define test-source-hash + "") + +(test-begin "crate") + +(test-equal "guix-package->crate-name" + "rustc-serialize" + (guix-package->crate-name + (dummy-package + "rust-rustc-serialize" + (source (dummy-origin + (uri (crate-uri "rustc-serialize" "1.0"))))))) + +(test-assert "crate->guix-package" + ;; Replace network resources with sample data. + (mock ((guix http-client) http-fetch + (lambda (url) + (match url + ("https://crates.io/api/v1/crates/foo" + (open-input-string test-crate)) + ("https://crates.io/api/v1/crates/foo/1.0.0/download" + (set! test-source-hash + (bytevector->nix-base32-string + (sha256 (string->bytevector "empty file\n" "utf-8")))) + (open-input-string "empty file\n")) + ("https://crates.io/api/v1/crates/foo/1.0.0/dependencies" + (open-input-string test-dependencies)) + (_ (error "Unexpected URL: " url))))) + (match (crate->guix-package "foo") + (('package + ('name "rust-foo") + ('version "1.0.0") + ('source ('origin + ('method 'url-fetch) + ('uri ('crate-uri "foo" 'version)) + ('file-name ('string-append 'name "-" 'version ".tar.gz")) + ('sha256 + ('base32 + (? string? hash))))) + ('build-system 'cargo-build-system) + ('inputs + ('quasiquote + (("rust-bar" ('unquote 'rust-bar))))) + ('home-page "http://example.com") + ('synopsis "summary") + ('description "summary") + ('license ('list 'license:expat 'license:asl2.0))) + (string=? test-source-hash hash)) + (x + (pk 'fail x #f))))) + +(test-end "crate") -- cgit v1.2.3 From 8ac529878640de632356895fbcaeeed6c1cb335e Mon Sep 17 00:00:00 2001 From: David Craven Date: Thu, 22 Sep 2016 11:40:58 +0200 Subject: import: Add updater for rust crates. * guix/import/crate.scm (crate-package?, latest-release, %crate-updater): New variables. * guix/scripts/refresh.scm (%updaters): Add crate updater. * doc/guix.texi: Add crate updater to table. --- doc/guix.texi | 2 ++ guix/import/crate.scm | 36 +++++++++++++++++++++++++++++++++++- guix/scripts/refresh.scm | 3 ++- 3 files changed, 39 insertions(+), 2 deletions(-) (limited to 'guix') diff --git a/doc/guix.texi b/doc/guix.texi index 5db20ecdfa..a5424b4e01 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -5396,6 +5396,8 @@ the updater for @uref{https://rubygems.org, RubyGems} packages. the updater for @uref{https://github.com, GitHub} packages. @item hackage the updater for @uref{https://hackage.haskell.org, Hackage} packages. +@item crate +the updater for @uref{https://crates.io, Crates} packages. @end table For instance, the following command only checks for updates of Emacs diff --git a/guix/import/crate.scm b/guix/import/crate.scm index e78e3ad9ca..3a19fc70cf 100644 --- a/guix/import/crate.scm +++ b/guix/import/crate.scm @@ -36,7 +36,8 @@ #:use-module (srfi srfi-2) #:use-module (srfi srfi-26) #:export (crate->guix-package - guix-package->crate-name)) + guix-package->crate-name + %crate-updater)) (define (crate-fetch crate-name callback) "Fetch the metadata for CRATE-NAME from crates.io and call the callback." @@ -123,3 +124,36 @@ VERSION, INPUTS, NATIVE-INPUTS, HOME-PAGE, SYNOPSIS, DESCRIPTION, and LICENSE." (define (crate-name->package-name name) (string-append "rust-" (string-join (string-split name #\_) "-"))) +;;; +;;; Updater +;;; + +(define (crate-package? package) + "Return true if PACKAGE is a Rust crate from crates.io." + (let ((source-url (and=> (package-source package) origin-uri)) + (fetch-method (and=> (package-source package) origin-method))) + (and (eq? fetch-method download:url-fetch) + (match source-url + ((? string?) + (crate-url? source-url)) + ((source-url ...) + (any crate-url? source-url)))))) + +(define (latest-release package) + "Return an for the latest release of PACKAGE." + (let* ((crate-name (guix-package->crate-name package)) + (callback (lambda* (#:key version #:allow-other-keys) version)) + (version (crate-fetch crate-name callback)) + (url (crate-uri crate-name version))) + (upstream-source + (package (package-name package)) + (version version) + (urls (list url))))) + +(define %crate-updater + (upstream-updater + (name 'crates) + (description "Updater for crates.io packages") + (pred crate-package?) + (latest latest-release))) + diff --git a/guix/scripts/refresh.scm b/guix/scripts/refresh.scm index 72f51cbff8..2a06405a14 100644 --- a/guix/scripts/refresh.scm +++ b/guix/scripts/refresh.scm @@ -210,7 +210,8 @@ unavailable optional dependencies such as Guile-JSON." ((guix import cpan) => %cpan-updater) ((guix import pypi) => %pypi-updater) ((guix import gem) => %gem-updater) - ((guix import github) => %github-updater))) + ((guix import github) => %github-updater) + ((guix import crate) => %crate-updater))) (define (lookup-updater-by-name name) "Return the updater called NAME." -- cgit v1.2.3 From 51377437a1e37c9d5f2e137528e9c278b252d781 Mon Sep 17 00:00:00 2001 From: Ricardo Wurmus Date: Thu, 20 Oct 2016 08:47:03 +0200 Subject: graph: Backend must have name and description. * guix/graph.scm (): Add fields "name" and "description". (%graphviz-backend): Provide values for name and description. (export-graph): Ignore name and description when matching backends. (graph-backend-name, graph-backend-description): New procedures. * tests/graph.scm (make-recording-backend): Initialize name and description fields of test graph-backend. --- guix/graph.scm | 20 +++++++++++++------- tests/graph.scm | 3 ++- 2 files changed, 15 insertions(+), 8 deletions(-) (limited to 'guix') diff --git a/guix/graph.scm b/guix/graph.scm index 735d340c2c..5cf98f0d54 100644 --- a/guix/graph.scm +++ b/guix/graph.scm @@ -44,6 +44,8 @@ %graphviz-backend graph-backend? graph-backend + graph-backend-name + graph-backend-description export-graph)) @@ -140,12 +142,14 @@ typically returned by 'node-edges' or 'node-back-edges'." ;;; (define-record-type - (graph-backend prologue epilogue node edge) + (graph-backend name description prologue epilogue node edge) graph-backend? - (prologue graph-backend-prologue) - (epilogue graph-backend-epilogue) - (node graph-backend-node) - (edge graph-backend-edge)) + (name graph-backend-name) + (description graph-backend-description) + (prologue graph-backend-prologue) + (epilogue graph-backend-epilogue) + (node graph-backend-node) + (edge graph-backend-edge)) (define %colors ;; See colortbl.h in Graphviz. @@ -170,7 +174,9 @@ typically returned by 'node-edges' or 'node-back-edges'." id1 id2 (pop-color id1))) (define %graphviz-backend - (graph-backend emit-prologue emit-epilogue + (graph-backend "graphviz" + "Generate graph in DOT format for use with Graphviz." + emit-prologue emit-epilogue emit-node emit-edge)) (define* (export-graph sinks port @@ -181,7 +187,7 @@ typically returned by 'node-edges' or 'node-back-edges'." given BACKEND. Use NODE-TYPE to traverse the DAG. When REVERSE-EDGES? is true, draw reverse arrows." (match backend - (($ emit-prologue emit-epilogue emit-node emit-edge) + (($ _ _ emit-prologue emit-epilogue emit-node emit-edge) (emit-prologue (node-type-name node-type) port) (match node-type diff --git a/tests/graph.scm b/tests/graph.scm index f2e441cee6..bc4d62fe50 100644 --- a/tests/graph.scm +++ b/tests/graph.scm @@ -57,7 +57,8 @@ edges." (define (return) (values (reverse nodes) (reverse edges))) - (values (graph-backend (const #t) (const #t) + (values (graph-backend "test" "This is the test backend." + (const #t) (const #t) record-node record-edge) return))) -- cgit v1.2.3 From 642339dc3fc6df33edd78f3cdc170c20a32a3c7d Mon Sep 17 00:00:00 2001 From: Ricardo Wurmus Date: Fri, 21 Oct 2016 23:59:00 +0200 Subject: graph: Add "list-backend" and "backend" options. * guix/graph.scm (%graph-backends): New variable. * guix/scripts/graph.scm (lookup-backend, list-backends): New procedures. (%options): Add options for "backend" and "list-backends". (show-help): Add help texts for "backend" and "list-backend" options. (%default-options): Add "backend" default. (guix-graph): Pass backend argument to "export-graph". * doc/guix.texi (Invoking guix graph): Document the new options. --- doc/guix.texi | 7 +++++++ guix/graph.scm | 10 ++++++++++ guix/scripts/graph.scm | 36 ++++++++++++++++++++++++++++++++++-- 3 files changed, 51 insertions(+), 2 deletions(-) (limited to 'guix') diff --git a/doc/guix.texi b/doc/guix.texi index a5424b4e01..83f52484e6 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -5827,6 +5827,13 @@ the values listed above. @item --list-types List the supported graph types. +@item --backend=@var{backend} +@itemx -b @var{backend} +Produce a graph using the selected @var{backend}. + +@item --list-backends +List the supported graph backends. + @item --expression=@var{expr} @itemx -e @var{expr} Consider the package @var{expr} evaluates to. diff --git a/guix/graph.scm b/guix/graph.scm index 5cf98f0d54..d2c1fa6c48 100644 --- a/guix/graph.scm +++ b/guix/graph.scm @@ -1,5 +1,6 @@ ;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2015, 2016 Ludovic Courtès +;;; Copyright © 2016 Ricardo Wurmus ;;; ;;; This file is part of GNU Guix. ;;; @@ -41,6 +42,7 @@ node-transitive-edges node-reachable-count + %graph-backends %graphviz-backend graph-backend? graph-backend @@ -179,6 +181,14 @@ typically returned by 'node-edges' or 'node-back-edges'." emit-prologue emit-epilogue emit-node emit-edge)) + +;;; +;;; Shared. +;;; + +(define %graph-backends + (list %graphviz-backend)) + (define* (export-graph sinks port #:key reverse-edges? node-type diff --git a/guix/scripts/graph.scm b/guix/scripts/graph.scm index 2f70d64c90..d96df5fbaf 100644 --- a/guix/scripts/graph.scm +++ b/guix/scripts/graph.scm @@ -337,6 +337,13 @@ substitutes." %node-types) (leave (_ "~a: unknown node type~%") name))) +(define (lookup-backend name) + "Return the graph backend called NAME. Raise an error if it is not found." + (or (find (lambda (backend) + (string=? (graph-backend-name backend) name)) + %graph-backends) + (leave (_ "~a: unknown backend~%") name))) + (define (list-node-types) "Print the available node types along with their synopsis." (display (_ "The available node types are:\n")) @@ -347,6 +354,16 @@ substitutes." (node-type-description type))) %node-types)) +(define (list-backends) + "Print the available backends along with their synopsis." + (display (_ "The available backend types are:\n")) + (newline) + (for-each (lambda (backend) + (format #t " - ~a: ~a~%" + (graph-backend-name backend) + (graph-backend-description backend))) + %graph-backends)) + ;;; ;;; Command-line options. @@ -361,6 +378,14 @@ substitutes." (lambda (opt name arg result) (list-node-types) (exit 0))) + (option '(#\b "backend") #t #f + (lambda (opt name arg result) + (alist-cons 'backend (lookup-backend arg) + result))) + (option '("list-backends") #f #f + (lambda (opt name arg result) + (list-backends) + (exit 0))) (option '(#\e "expression") #t #f (lambda (opt name arg result) (alist-cons 'expression arg result))) @@ -378,6 +403,10 @@ substitutes." (display (_ "Usage: guix graph PACKAGE... Emit a Graphviz (dot) representation of the dependencies of PACKAGE...\n")) (display (_ " + -b, --backend=TYPE produce a graph with the given backend TYPE")) + (display (_ " + --list-backends list the available graph backends")) + (display (_ " -t, --type=TYPE represent nodes of the given TYPE")) (display (_ " --list-types list the available graph types")) @@ -392,7 +421,8 @@ Emit a Graphviz (dot) representation of the dependencies of PACKAGE...\n")) (show-bug-report-information)) (define %default-options - `((node-type . ,%package-node-type))) + `((node-type . ,%package-node-type) + (backend . ,%graphviz-backend))) ;;; @@ -407,6 +437,7 @@ Emit a Graphviz (dot) representation of the dependencies of PACKAGE...\n")) (lambda (arg result) (alist-cons 'argument arg result)) %default-options)) + (backend (assoc-ref opts 'backend)) (type (assoc-ref opts 'node-type)) (items (filter-map (match-lambda (('argument . (? store-path? item)) @@ -429,7 +460,8 @@ Emit a Graphviz (dot) representation of the dependencies of PACKAGE...\n")) items))) (export-graph (concatenate nodes) (current-output-port) - #:node-type type))))))) + #:node-type type + #:backend backend))))))) #t) ;;; graph.scm ends here -- cgit v1.2.3 From 4d93f312f084c34a70cf7da3abe5f92a74d76861 Mon Sep 17 00:00:00 2001 From: Ricardo Wurmus Date: Sat, 22 Oct 2016 00:02:19 +0200 Subject: graph: Add d3js backend. * d3.v3.js, graph.js: New files. * Makefile.am (EXTRA_DIST): List them. * guix/graph.scm (%d3js-backend): New variable. (emit-d3js-prologue, emit-d3js-epilogue, emit-d3js-node, emit-d3js-edge): New procedures. (%graph-backends): Add %d3js-backend. --- Makefile.am | 2 + d3.v3.js | 9584 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ graph.js | 129 + guix/graph.scm | 51 +- 4 files changed, 9765 insertions(+), 1 deletion(-) create mode 100644 d3.v3.js create mode 100644 graph.js (limited to 'guix') diff --git a/Makefile.am b/Makefile.am index 84ff6642a2..de0de7189d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -382,6 +382,8 @@ EXTRA_DIST = \ build-aux/generate-authors.scm \ build-aux/test-driver.scm \ build-aux/run-system-tests.scm \ + d3.v3.js \ + graph.js \ srfi/srfi-37.scm.in \ srfi/srfi-64.scm \ srfi/srfi-64.upstream.scm \ diff --git a/d3.v3.js b/d3.v3.js new file mode 100644 index 0000000000..d234c4ec86 --- /dev/null +++ b/d3.v3.js @@ -0,0 +1,9584 @@ +// Downloaded from http://d3js.org/d3.v3.js +// +// Copyright 2010-2016 Mike Bostock +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of the author nor the names of contributors may be used to +// endorse or promote products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +!function() { + var d3 = { + version: "3.5.17" + }; + var d3_arraySlice = [].slice, d3_array = function(list) { + return d3_arraySlice.call(list); + }; + var d3_document = this.document; + function d3_documentElement(node) { + return node && (node.ownerDocument || node.document || node).documentElement; + } + function d3_window(node) { + return node && (node.ownerDocument && node.ownerDocument.defaultView || node.document && node || node.defaultView); + } + if (d3_document) { + try { + d3_array(d3_document.documentElement.childNodes)[0].nodeType; + } catch (e) { + d3_array = function(list) { + var i = list.length, array = new Array(i); + while (i--) array[i] = list[i]; + return array; + }; + } + } + if (!Date.now) Date.now = function() { + return +new Date(); + }; + if (d3_document) { + try { + d3_document.createElement("DIV").style.setProperty("opacity", 0, ""); + } catch (error) { + var d3_element_prototype = this.Element.prototype, d3_element_setAttribute = d3_element_prototype.setAttribute, d3_element_setAttributeNS = d3_element_prototype.setAttributeNS, d3_style_prototype = this.CSSStyleDeclaration.prototype, d3_style_setProperty = d3_style_prototype.setProperty; + d3_element_prototype.setAttribute = function(name, value) { + d3_element_setAttribute.call(this, name, value + ""); + }; + d3_element_prototype.setAttributeNS = function(space, local, value) { + d3_element_setAttributeNS.call(this, space, local, value + ""); + }; + d3_style_prototype.setProperty = function(name, value, priority) { + d3_style_setProperty.call(this, name, value + "", priority); + }; + } + } + d3.ascending = d3_ascending; + function d3_ascending(a, b) { + return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN; + } + d3.descending = function(a, b) { + return b < a ? -1 : b > a ? 1 : b >= a ? 0 : NaN; + }; + d3.min = function(array, f) { + var i = -1, n = array.length, a, b; + if (arguments.length === 1) { + while (++i < n) if ((b = array[i]) != null && b >= b) { + a = b; + break; + } + while (++i < n) if ((b = array[i]) != null && a > b) a = b; + } else { + while (++i < n) if ((b = f.call(array, array[i], i)) != null && b >= b) { + a = b; + break; + } + while (++i < n) if ((b = f.call(array, array[i], i)) != null && a > b) a = b; + } + return a; + }; + d3.max = function(array, f) { + var i = -1, n = array.length, a, b; + if (arguments.length === 1) { + while (++i < n) if ((b = array[i]) != null && b >= b) { + a = b; + break; + } + while (++i < n) if ((b = array[i]) != null && b > a) a = b; + } else { + while (++i < n) if ((b = f.call(array, array[i], i)) != null && b >= b) { + a = b; + break; + } + while (++i < n) if ((b = f.call(array, array[i], i)) != null && b > a) a = b; + } + return a; + }; + d3.extent = function(array, f) { + var i = -1, n = array.length, a, b, c; + if (arguments.length === 1) { + while (++i < n) if ((b = array[i]) != null && b >= b) { + a = c = b; + break; + } + while (++i < n) if ((b = array[i]) != null) { + if (a > b) a = b; + if (c < b) c = b; + } + } else { + while (++i < n) if ((b = f.call(array, array[i], i)) != null && b >= b) { + a = c = b; + break; + } + while (++i < n) if ((b = f.call(array, array[i], i)) != null) { + if (a > b) a = b; + if (c < b) c = b; + } + } + return [ a, c ]; + }; + function d3_number(x) { + return x === null ? NaN : +x; + } + function d3_numeric(x) { + return !isNaN(x); + } + d3.sum = function(array, f) { + var s = 0, n = array.length, a, i = -1; + if (arguments.length === 1) { + while (++i < n) if (d3_numeric(a = +array[i])) s += a; + } else { + while (++i < n) if (d3_numeric(a = +f.call(array, array[i], i))) s += a; + } + return s; + }; + d3.mean = function(array, f) { + var s = 0, n = array.length, a, i = -1, j = n; + if (arguments.length === 1) { + while (++i < n) if (d3_numeric(a = d3_number(array[i]))) s += a; else --j; + } else { + while (++i < n) if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) s += a; else --j; + } + if (j) return s / j; + }; + d3.quantile = function(values, p) { + var H = (values.length - 1) * p + 1, h = Math.floor(H), v = +values[h - 1], e = H - h; + return e ? v + e * (values[h] - v) : v; + }; + d3.median = function(array, f) { + var numbers = [], n = array.length, a, i = -1; + if (arguments.length === 1) { + while (++i < n) if (d3_numeric(a = d3_number(array[i]))) numbers.push(a); + } else { + while (++i < n) if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) numbers.push(a); + } + if (numbers.length) return d3.quantile(numbers.sort(d3_ascending), .5); + }; + d3.variance = function(array, f) { + var n = array.length, m = 0, a, d, s = 0, i = -1, j = 0; + if (arguments.length === 1) { + while (++i < n) { + if (d3_numeric(a = d3_number(array[i]))) { + d = a - m; + m += d / ++j; + s += d * (a - m); + } + } + } else { + while (++i < n) { + if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) { + d = a - m; + m += d / ++j; + s += d * (a - m); + } + } + } + if (j > 1) return s / (j - 1); + }; + d3.deviation = function() { + var v = d3.variance.apply(this, arguments); + return v ? Math.sqrt(v) : v; + }; + function d3_bisector(compare) { + return { + left: function(a, x, lo, hi) { + if (arguments.length < 3) lo = 0; + if (arguments.length < 4) hi = a.length; + while (lo < hi) { + var mid = lo + hi >>> 1; + if (compare(a[mid], x) < 0) lo = mid + 1; else hi = mid; + } + return lo; + }, + right: function(a, x, lo, hi) { + if (arguments.length < 3) lo = 0; + if (arguments.length < 4) hi = a.length; + while (lo < hi) { + var mid = lo + hi >>> 1; + if (compare(a[mid], x) > 0) hi = mid; else lo = mid + 1; + } + return lo; + } + }; + } + var d3_bisect = d3_bisector(d3_ascending); + d3.bisectLeft = d3_bisect.left; + d3.bisect = d3.bisectRight = d3_bisect.right; + d3.bisector = function(f) { + return d3_bisector(f.length === 1 ? function(d, x) { + return d3_ascending(f(d), x); + } : f); + }; + d3.shuffle = function(array, i0, i1) { + if ((m = arguments.length) < 3) { + i1 = array.length; + if (m < 2) i0 = 0; + } + var m = i1 - i0, t, i; + while (m) { + i = Math.random() * m-- | 0; + t = array[m + i0], array[m + i0] = array[i + i0], array[i + i0] = t; + } + return array; + }; + d3.permute = function(array, indexes) { + var i = indexes.length, permutes = new Array(i); + while (i--) permutes[i] = array[indexes[i]]; + return permutes; + }; + d3.pairs = function(array) { + var i = 0, n = array.length - 1, p0, p1 = array[0], pairs = new Array(n < 0 ? 0 : n); + while (i < n) pairs[i] = [ p0 = p1, p1 = array[++i] ]; + return pairs; + }; + d3.transpose = function(matrix) { + if (!(n = matrix.length)) return []; + for (var i = -1, m = d3.min(matrix, d3_transposeLength), transpose = new Array(m); ++i < m; ) { + for (var j = -1, n, row = transpose[i] = new Array(n); ++j < n; ) { + row[j] = matrix[j][i]; + } + } + return transpose; + }; + function d3_transposeLength(d) { + return d.length; + } + d3.zip = function() { + return d3.transpose(arguments); + }; + d3.keys = function(map) { + var keys = []; + for (var key in map) keys.push(key); + return keys; + }; + d3.values = function(map) { + var values = []; + for (var key in map) values.push(map[key]); + return values; + }; + d3.entries = function(map) { + var entries = []; + for (var key in map) entries.push({ + key: key, + value: map[key] + }); + return entries; + }; + d3.merge = function(arrays) { + var n = arrays.length, m, i = -1, j = 0, merged, array; + while (++i < n) j += arrays[i].length; + merged = new Array(j); + while (--n >= 0) { + array = arrays[n]; + m = array.length; + while (--m >= 0) { + merged[--j] = array[m]; + } + } + return merged; + }; + var abs = Math.abs; + d3.range = function(start, stop, step) { + if (arguments.length < 3) { + step = 1; + if (arguments.length < 2) { + stop = start; + start = 0; + } + } + if ((stop - start) / step === Infinity) throw new Error("infinite range"); + var range = [], k = d3_range_integerScale(abs(step)), i = -1, j; + start *= k, stop *= k, step *= k; + if (step < 0) while ((j = start + step * ++i) > stop) range.push(j / k); else while ((j = start + step * ++i) < stop) range.push(j / k); + return range; + }; + function d3_range_integerScale(x) { + var k = 1; + while (x * k % 1) k *= 10; + return k; + } + function d3_class(ctor, properties) { + for (var key in properties) { + Object.defineProperty(ctor.prototype, key, { + value: properties[key], + enumerable: false + }); + } + } + d3.map = function(object, f) { + var map = new d3_Map(); + if (object instanceof d3_Map) { + object.forEach(function(key, value) { + map.set(key, value); + }); + } else if (Array.isArray(object)) { + var i = -1, n = object.length, o; + if (arguments.length === 1) while (++i < n) map.set(i, object[i]); else while (++i < n) map.set(f.call(object, o = object[i], i), o); + } else { + for (var key in object) map.set(key, object[key]); + } + return map; + }; + function d3_Map() { + this._ = Object.create(null); + } + var d3_map_proto = "__proto__", d3_map_zero = "\x00"; + d3_class(d3_Map, { + has: d3_map_has, + get: function(key) { + return this._[d3_map_escape(key)]; + }, + set: function(key, value) { + return this._[d3_map_escape(key)] = value; + }, + remove: d3_map_remove, + keys: d3_map_keys, + values: function() { + var values = []; + for (var key in this._) values.push(this._[key]); + return values; + }, + entries: function() { + var entries = []; + for (var key in this._) entries.push({ + key: d3_map_unescape(key), + value: this._[key] + }); + return entries; + }, + size: d3_map_size, + empty: d3_map_empty, + forEach: function(f) { + for (var key in this._) f.call(this, d3_map_unescape(key), this._[key]); + } + }); + function d3_map_escape(key) { + return (key += "") === d3_map_proto || key[0] === d3_map_zero ? d3_map_zero + key : key; + } + function d3_map_unescape(key) { + return (key += "")[0] === d3_map_zero ? key.slice(1) : key; + } + function d3_map_has(key) { + return d3_map_escape(key) in this._; + } + function d3_map_remove(key) { + return (key = d3_map_escape(key)) in this._ && delete this._[key]; + } + function d3_map_keys() { + var keys = []; + for (var key in this._) keys.push(d3_map_unescape(key)); + return keys; + } + function d3_map_size() { + var size = 0; + for (var key in this._) ++size; + return size; + } + function d3_map_empty() { + for (var key in this._) return false; + return true; + } + d3.nest = function() { + var nest = {}, keys = [], sortKeys = [], sortValues, rollup; + function map(mapType, array, depth) { + if (depth >= keys.length) return rollup ? rollup.call(nest, array) : sortValues ? array.sort(sortValues) : array; + var i = -1, n = array.length, key = keys[depth++], keyValue, object, setter, valuesByKey = new d3_Map(), values; + while (++i < n) { + if (values = valuesByKey.get(keyValue = key(object = array[i]))) { + values.push(object); + } else { + valuesByKey.set(keyValue, [ object ]); + } + } + if (mapType) { + object = mapType(); + setter = function(keyValue, values) { + object.set(keyValue, map(mapType, values, depth)); + }; + } else { + object = {}; + setter = function(keyValue, values) { + object[keyValue] = map(mapType, values, depth); + }; + } + valuesByKey.forEach(setter); + return object; + } + function entries(map, depth) { + if (depth >= keys.length) return map; + var array = [], sortKey = sortKeys[depth++]; + map.forEach(function(key, keyMap) { + array.push({ + key: key, + values: entries(keyMap, depth) + }); + }); + return sortKey ? array.sort(function(a, b) { + return sortKey(a.key, b.key); + }) : array; + } + nest.map = function(array, mapType) { + return map(mapType, array, 0); + }; + nest.entries = function(array) { + return entries(map(d3.map, array, 0), 0); + }; + nest.key = function(d) { + keys.push(d); + return nest; + }; + nest.sortKeys = function(order) { + sortKeys[keys.length - 1] = order; + return nest; + }; + nest.sortValues = function(order) { + sortValues = order; + return nest; + }; + nest.rollup = function(f) { + rollup = f; + return nest; + }; + return nest; + }; + d3.set = function(array) { + var set = new d3_Set(); + if (array) for (var i = 0, n = array.length; i < n; ++i) set.add(array[i]); + return set; + }; + function d3_Set() { + this._ = Object.create(null); + } + d3_class(d3_Set, { + has: d3_map_has, + add: function(key) { + this._[d3_map_escape(key += "")] = true; + return key; + }, + remove: d3_map_remove, + values: d3_map_keys, + size: d3_map_size, + empty: d3_map_empty, + forEach: function(f) { + for (var key in this._) f.call(this, d3_map_unescape(key)); + } + }); + d3.behavior = {}; + function d3_identity(d) { + return d; + } + d3.rebind = function(target, source) { + var i = 1, n = arguments.length, method; + while (++i < n) target[method = arguments[i]] = d3_rebind(target, source, source[method]); + return target; + }; + function d3_rebind(target, source, method) { + return function() { + var value = method.apply(source, arguments); + return value === source ? target : value; + }; + } + function d3_vendorSymbol(object, name) { + if (name in object) return name; + name = name.charAt(0).toUpperCase() + name.slice(1); + for (var i = 0, n = d3_vendorPrefixes.length; i < n; ++i) { + var prefixName = d3_vendorPrefixes[i] + name; + if (prefixName in object) return prefixName; + } + } + var d3_vendorPrefixes = [ "webkit", "ms", "moz", "Moz", "o", "O" ]; + function d3_noop() {} + d3.dispatch = function() { + var dispatch = new d3_dispatch(), i = -1, n = arguments.length; + while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch); + return dispatch; + }; + function d3_dispatch() {} + d3_dispatch.prototype.on = function(type, listener) { + var i = type.indexOf("."), name = ""; + if (i >= 0) { + name = type.slice(i + 1); + type = type.slice(0, i); + } + if (type) return arguments.length < 2 ? this[type].on(name) : this[type].on(name, listener); + if (arguments.length === 2) { + if (listener == null) for (type in this) { + if (this.hasOwnProperty(type)) this[type].on(name, null); + } + return this; + } + }; + function d3_dispatch_event(dispatch) { + var listeners = [], listenerByName = new d3_Map(); + function event() { + var z = listeners, i = -1, n = z.length, l; + while (++i < n) if (l = z[i].on) l.apply(this, arguments); + return dispatch; + } + event.on = function(name, listener) { + var l = listenerByName.get(name), i; + if (arguments.length < 2) return l && l.on; + if (l) { + l.on = null; + listeners = listeners.slice(0, i = listeners.indexOf(l)).concat(listeners.slice(i + 1)); + listenerByName.remove(name); + } + if (listener) listeners.push(listenerByName.set(name, { + on: listener + })); + return dispatch; + }; + return event; + } + d3.event = null; + function d3_eventPreventDefault() { + d3.event.preventDefault(); + } + function d3_eventSource() { + var e = d3.event, s; + while (s = e.sourceEvent) e = s; + return e; + } + function d3_eventDispatch(target) { + var dispatch = new d3_dispatch(), i = 0, n = arguments.length; + while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch); + dispatch.of = function(thiz, argumentz) { + return function(e1) { + try { + var e0 = e1.sourceEvent = d3.event; + e1.target = target; + d3.event = e1; + dispatch[e1.type].apply(thiz, argumentz); + } finally { + d3.event = e0; + } + }; + }; + return dispatch; + } + d3.requote = function(s) { + return s.replace(d3_requote_re, "\\$&"); + }; + var d3_requote_re = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g; + var d3_subclass = {}.__proto__ ? function(object, prototype) { + object.__proto__ = prototype; + } : function(object, prototype) { + for (var property in prototype) object[property] = prototype[property]; + }; + function d3_selection(groups) { + d3_subclass(groups, d3_selectionPrototype); + return groups; + } + var d3_select = function(s, n) { + return n.querySelector(s); + }, d3_selectAll = function(s, n) { + return n.querySelectorAll(s); + }, d3_selectMatches = function(n, s) { + var d3_selectMatcher = n.matches || n[d3_vendorSymbol(n, "matchesSelector")]; + d3_selectMatches = function(n, s) { + return d3_selectMatcher.call(n, s); + }; + return d3_selectMatches(n, s); + }; + if (typeof Sizzle === "function") { + d3_select = function(s, n) { + return Sizzle(s, n)[0] || null; + }; + d3_selectAll = Sizzle; + d3_selectMatches = Sizzle.matchesSelector; + } + d3.selection = function() { + return d3.select(d3_document.documentElement); + }; + var d3_selectionPrototype = d3.selection.prototype = []; + d3_selectionPrototype.select = function(selector) { + var subgroups = [], subgroup, subnode, group, node; + selector = d3_selection_selector(selector); + for (var j = -1, m = this.length; ++j < m; ) { + subgroups.push(subgroup = []); + subgroup.parentNode = (group = this[j]).parentNode; + for (var i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) { + subgroup.push(subnode = selector.call(node, node.__data__, i, j)); + if (subnode && "__data__" in node) subnode.__data__ = node.__data__; + } else { + subgroup.push(null); + } + } + } + return d3_selection(subgroups); + }; + function d3_selection_selector(selector) { + return typeof selector === "function" ? selector : function() { + return d3_select(selector, this); + }; + } + d3_selectionPrototype.selectAll = function(selector) { + var subgroups = [], subgroup, node; + selector = d3_selection_selectorAll(selector); + for (var j = -1, m = this.length; ++j < m; ) { + for (var group = this[j], i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) { + subgroups.push(subgroup = d3_array(selector.call(node, node.__data__, i, j))); + subgroup.parentNode = node; + } + } + } + return d3_selection(subgroups); + }; + function d3_selection_selectorAll(selector) { + return typeof selector === "function" ? selector : function() { + return d3_selectAll(selector, this); + }; + } + var d3_nsXhtml = "http://www.w3.org/1999/xhtml"; + var d3_nsPrefix = { + svg: "http://www.w3.org/2000/svg", + xhtml: d3_nsXhtml, + xlink: "http://www.w3.org/1999/xlink", + xml: "http://www.w3.org/XML/1998/namespace", + xmlns: "http://www.w3.org/2000/xmlns/" + }; + d3.ns = { + prefix: d3_nsPrefix, + qualify: function(name) { + var i = name.indexOf(":"), prefix = name; + if (i >= 0 && (prefix = name.slice(0, i)) !== "xmlns") name = name.slice(i + 1); + return d3_nsPrefix.hasOwnProperty(prefix) ? { + space: d3_nsPrefix[prefix], + local: name + } : name; + } + }; + d3_selectionPrototype.attr = function(name, value) { + if (arguments.length < 2) { + if (typeof name === "string") { + var node = this.node(); + name = d3.ns.qualify(name); + return name.local ? node.getAttributeNS(name.space, name.local) : node.getAttribute(name); + } + for (value in name) this.each(d3_selection_attr(value, name[value])); + return this; + } + return this.each(d3_selection_attr(name, value)); + }; + function d3_selection_attr(name, value) { + name = d3.ns.qualify(name); + function attrNull() { + this.removeAttribute(name); + } + function attrNullNS() { + this.removeAttributeNS(name.space, name.local); + } + function attrConstant() { + this.setAttribute(name, value); + } + function attrConstantNS() { + this.setAttributeNS(name.space, name.local, value); + } + function attrFunction() { + var x = value.apply(this, arguments); + if (x == null) this.removeAttribute(name); else this.setAttribute(name, x); + } + function attrFunctionNS() { + var x = value.apply(this, arguments); + if (x == null) this.removeAttributeNS(name.space, name.local); else this.setAttributeNS(name.space, name.local, x); + } + return value == null ? name.local ? attrNullNS : attrNull : typeof value === "function" ? name.local ? attrFunctionNS : attrFunction : name.local ? attrConstantNS : attrConstant; + } + function d3_collapse(s) { + return s.trim().replace(/\s+/g, " "); + } + d3_selectionPrototype.classed = function(name, value) { + if (arguments.length < 2) { + if (typeof name === "string") { + var node = this.node(), n = (name = d3_selection_classes(name)).length, i = -1; + if (value = node.classList) { + while (++i < n) if (!value.contains(name[i])) return false; + } else { + value = node.getAttribute("class"); + while (++i < n) if (!d3_selection_classedRe(name[i]).test(value)) return false; + } + return true; + } + for (value in name) this.each(d3_selection_classed(value, name[value])); + return this; + } + return this.each(d3_selection_classed(name, value)); + }; + function d3_selection_classedRe(name) { + return new RegExp("(?:^|\\s+)" + d3.requote(name) + "(?:\\s+|$)", "g"); + } + function d3_selection_classes(name) { + return (name + "").trim().split(/^|\s+/); + } + function d3_selection_classed(name, value) { + name = d3_selection_classes(name).map(d3_selection_classedName); + var n = name.length; + function classedConstant() { + var i = -1; + while (++i < n) name[i](this, value); + } + function classedFunction() { + var i = -1, x = value.apply(this, arguments); + while (++i < n) name[i](this, x); + } + return typeof value === "function" ? classedFunction : classedConstant; + } + function d3_selection_classedName(name) { + var re = d3_selection_classedRe(name); + return function(node, value) { + if (c = node.classList) return value ? c.add(name) : c.remove(name); + var c = node.getAttribute("class") || ""; + if (value) { + re.lastIndex = 0; + if (!re.test(c)) node.setAttribute("class", d3_collapse(c + " " + name)); + } else { + node.setAttribute("class", d3_collapse(c.replace(re, " "))); + } + }; + } + d3_selectionPrototype.style = function(name, value, priority) { + var n = arguments.length; + if (n < 3) { + if (typeof name !== "string") { + if (n < 2) value = ""; + for (priority in name) this.each(d3_selection_style(priority, name[priority], value)); + return this; + } + if (n < 2) { + var node = this.node(); + return d3_window(node).getComputedStyle(node, null).getPropertyValue(name); + } + priority = ""; + } + return this.each(d3_selection_style(name, value, priority)); + }; + function d3_selection_style(name, value, priority) { + function styleNull() { + this.style.removeProperty(name); + } + function styleConstant() { + this.style.setProperty(name, value, priority); + } + function styleFunction() { + var x = value.apply(this, arguments); + if (x == null) this.style.removeProperty(name); else this.style.setProperty(name, x, priority); + } + return value == null ? styleNull : typeof value === "function" ? styleFunction : styleConstant; + } + d3_selectionPrototype.property = function(name, value) { + if (arguments.length < 2) { + if (typeof name === "string") return this.node()[name]; + for (value in name) this.each(d3_selection_property(value, name[value])); + return this; + } + return this.each(d3_selection_property(name, value)); + }; + function d3_selection_property(name, value) { + function propertyNull() { + delete this[name]; + } + function propertyConstant() { + this[name] = value; + } + function propertyFunction() { + var x = value.apply(this, arguments); + if (x == null) delete this[name]; else this[name] = x; + } + return value == null ? propertyNull : typeof value === "function" ? propertyFunction : propertyConstant; + } + d3_selectionPrototype.text = function(value) { + return arguments.length ? this.each(typeof value === "function" ? function() { + var v = value.apply(this, arguments); + this.textContent = v == null ? "" : v; + } : value == null ? function() { + this.textContent = ""; + } : function() { + this.textContent = value; + }) : this.node().textContent; + }; + d3_selectionPrototype.html = function(value) { + return arguments.length ? this.each(typeof value === "function" ? function() { + var v = value.apply(this, arguments); + this.innerHTML = v == null ? "" : v; + } : value == null ? function() { + this.innerHTML = ""; + } : function() { + this.innerHTML = value; + }) : this.node().innerHTML; + }; + d3_selectionPrototype.append = function(name) { + name = d3_selection_creator(name); + return this.select(function() { + return this.appendChild(name.apply(this, arguments)); + }); + }; + function d3_selection_creator(name) { + function create() { + var document = this.ownerDocument, namespace = this.namespaceURI; + return namespace === d3_nsXhtml && document.documentElement.namespaceURI === d3_nsXhtml ? document.createElement(name) : document.createElementNS(namespace, name); + } + function createNS() { + return this.ownerDocument.createElementNS(name.space, name.local); + } + return typeof name === "function" ? name : (name = d3.ns.qualify(name)).local ? createNS : create; + } + d3_selectionPrototype.insert = function(name, before) { + name = d3_selection_creator(name); + before = d3_selection_selector(before); + return this.select(function() { + return this.insertBefore(name.apply(this, arguments), before.apply(this, arguments) || null); + }); + }; + d3_selectionPrototype.remove = function() { + return this.each(d3_selectionRemove); + }; + function d3_selectionRemove() { + var parent = this.parentNode; + if (parent) parent.removeChild(this); + } + d3_selectionPrototype.data = function(value, key) { + var i = -1, n = this.length, group, node; + if (!arguments.length) { + value = new Array(n = (group = this[0]).length); + while (++i < n) { + if (node = group[i]) { + value[i] = node.__data__; + } + } + return value; + } + function bind(group, groupData) { + var i, n = group.length, m = groupData.length, n0 = Math.min(n, m), updateNodes = new Array(m), enterNodes = new Array(m), exitNodes = new Array(n), node, nodeData; + if (key) { + var nodeByKeyValue = new d3_Map(), keyValues = new Array(n), keyValue; + for (i = -1; ++i < n; ) { + if (node = group[i]) { + if (nodeByKeyValue.has(keyValue = key.call(node, node.__data__, i))) { + exitNodes[i] = node; + } else { + nodeByKeyValue.set(keyValue, node); + } + keyValues[i] = keyValue; + } + } + for (i = -1; ++i < m; ) { + if (!(node = nodeByKeyValue.get(keyValue = key.call(groupData, nodeData = groupData[i], i)))) { + enterNodes[i] = d3_selection_dataNode(nodeData); + } else if (node !== true) { + updateNodes[i] = node; + node.__data__ = nodeData; + } + nodeByKeyValue.set(keyValue, true); + } + for (i = -1; ++i < n; ) { + if (i in keyValues && nodeByKeyValue.get(keyValues[i]) !== true) { + exitNodes[i] = group[i]; + } + } + } else { + for (i = -1; ++i < n0; ) { + node = group[i]; + nodeData = groupData[i]; + if (node) { + node.__data__ = nodeData; + updateNodes[i] = node; + } else { + enterNodes[i] = d3_selection_dataNode(nodeData); + } + } + for (;i < m; ++i) { + enterNodes[i] = d3_selection_dataNode(groupData[i]); + } + for (;i < n; ++i) { + exitNodes[i] = group[i]; + } + } + enterNodes.update = updateNodes; + enterNodes.parentNode = updateNodes.parentNode = exitNodes.parentNode = group.parentNode; + enter.push(enterNodes); + update.push(updateNodes); + exit.push(exitNodes); + } + var enter = d3_selection_enter([]), update = d3_selection([]), exit = d3_selection([]); + if (typeof value === "function") { + while (++i < n) { + bind(group = this[i], value.call(group, group.parentNode.__data__, i)); + } + } else { + while (++i < n) { + bind(group = this[i], value); + } + } + update.enter = function() { + return enter; + }; + update.exit = function() { + return exit; + }; + return update; + }; + function d3_selection_dataNode(data) { + return { + __data__: data + }; + } + d3_selectionPrototype.datum = function(value) { + return arguments.length ? this.property("__data__", value) : this.property("__data__"); + }; + d3_selectionPrototype.filter = function(filter) { + var subgroups = [], subgroup, group, node; + if (typeof filter !== "function") filter = d3_selection_filter(filter); + for (var j = 0, m = this.length; j < m; j++) { + subgroups.push(subgroup = []); + subgroup.parentNode = (group = this[j]).parentNode; + for (var i = 0, n = group.length; i < n; i++) { + if ((node = group[i]) && filter.call(node, node.__data__, i, j)) { + subgroup.push(node); + } + } + } + return d3_selection(subgroups); + }; + function d3_selection_filter(selector) { + return function() { + return d3_selectMatches(this, selector); + }; + } + d3_selectionPrototype.order = function() { + for (var j = -1, m = this.length; ++j < m; ) { + for (var group = this[j], i = group.length - 1, next = group[i], node; --i >= 0; ) { + if (node = group[i]) { + if (next && next !== node.nextSibling) next.parentNode.insertBefore(node, next); + next = node; + } + } + } + return this; + }; + d3_selectionPrototype.sort = function(comparator) { + comparator = d3_selection_sortComparator.apply(this, arguments); + for (var j = -1, m = this.length; ++j < m; ) this[j].sort(comparator); + return this.order(); + }; + function d3_selection_sortComparator(comparator) { + if (!arguments.length) comparator = d3_ascending; + return function(a, b) { + return a && b ? comparator(a.__data__, b.__data__) : !a - !b; + }; + } + d3_selectionPrototype.each = function(callback) { + return d3_selection_each(this, function(node, i, j) { + callback.call(node, node.__data__, i, j); + }); + }; + function d3_selection_each(groups, callback) { + for (var j = 0, m = groups.length; j < m; j++) { + for (var group = groups[j], i = 0, n = group.length, node; i < n; i++) { + if (node = group[i]) callback(node, i, j); + } + } + return groups; + } + d3_selectionPrototype.call = function(callback) { + var args = d3_array(arguments); + callback.apply(args[0] = this, args); + return this; + }; + d3_selectionPrototype.empty = function() { + return !this.node(); + }; + d3_selectionPrototype.node = function() { + for (var j = 0, m = this.length; j < m; j++) { + for (var group = this[j], i = 0, n = group.length; i < n; i++) { + var node = group[i]; + if (node) return node; + } + } + return null; + }; + d3_selectionPrototype.size = function() { + var n = 0; + d3_selection_each(this, function() { + ++n; + }); + return n; + }; + function d3_selection_enter(selection) { + d3_subclass(selection, d3_selection_enterPrototype); + return selection; + } + var d3_selection_enterPrototype = []; + d3.selection.enter = d3_selection_enter; + d3.selection.enter.prototype = d3_selection_enterPrototype; + d3_selection_enterPrototype.append = d3_selectionPrototype.append; + d3_selection_enterPrototype.empty = d3_selectionPrototype.empty; + d3_selection_enterPrototype.node = d3_selectionPrototype.node; + d3_selection_enterPrototype.call = d3_selectionPrototype.call; + d3_selection_enterPrototype.size = d3_selectionPrototype.size; + d3_selection_enterPrototype.select = function(selector) { + var subgroups = [], subgroup, subnode, upgroup, group, node; + for (var j = -1, m = this.length; ++j < m; ) { + upgroup = (group = this[j]).update; + subgroups.push(subgroup = []); + subgroup.parentNode = group.parentNode; + for (var i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) { + subgroup.push(upgroup[i] = subnode = selector.call(group.parentNode, node.__data__, i, j)); + subnode.__data__ = node.__data__; + } else { + subgroup.push(null); + } + } + } + return d3_selection(subgroups); + }; + d3_selection_enterPrototype.insert = function(name, before) { + if (arguments.length < 2) before = d3_selection_enterInsertBefore(this); + return d3_selectionPrototype.insert.call(this, name, before); + }; + function d3_selection_enterInsertBefore(enter) { + var i0, j0; + return function(d, i, j) { + var group = enter[j].update, n = group.length, node; + if (j != j0) j0 = j, i0 = 0; + if (i >= i0) i0 = i + 1; + while (!(node = group[i0]) && ++i0 < n) ; + return node; + }; + } + d3.select = function(node) { + var group; + if (typeof node === "string") { + group = [ d3_select(node, d3_document) ]; + group.parentNode = d3_document.documentElement; + } else { + group = [ node ]; + group.parentNode = d3_documentElement(node); + } + return d3_selection([ group ]); + }; + d3.selectAll = function(nodes) { + var group; + if (typeof nodes === "string") { + group = d3_array(d3_selectAll(nodes, d3_document)); + group.parentNode = d3_document.documentElement; + } else { + group = d3_array(nodes); + group.parentNode = null; + } + return d3_selection([ group ]); + }; + d3_selectionPrototype.on = function(type, listener, capture) { + var n = arguments.length; + if (n < 3) { + if (typeof type !== "string") { + if (n < 2) listener = false; + for (capture in type) this.each(d3_selection_on(capture, type[capture], listener)); + return this; + } + if (n < 2) return (n = this.node()["__on" + type]) && n._; + capture = false; + } + return this.each(d3_selection_on(type, listener, capture)); + }; + function d3_selection_on(type, listener, capture) { + var name = "__on" + type, i = type.indexOf("."), wrap = d3_selection_onListener; + if (i > 0) type = type.slice(0, i); + var filter = d3_selection_onFilters.get(type); + if (filter) type = filter, wrap = d3_selection_onFilter; + function onRemove() { + var l = this[name]; + if (l) { + this.removeEventListener(type, l, l.$); + delete this[name]; + } + } + function onAdd() { + var l = wrap(listener, d3_array(arguments)); + onRemove.call(this); + this.addEventListener(type, this[name] = l, l.$ = capture); + l._ = listener; + } + function removeAll() { + var re = new RegExp("^__on([^.]+)" + d3.requote(type) + "$"), match; + for (var name in this) { + if (match = name.match(re)) { + var l = this[name]; + this.removeEventListener(match[1], l, l.$); + delete this[name]; + } + } + } + return i ? listener ? onAdd : onRemove : listener ? d3_noop : removeAll; + } + var d3_selection_onFilters = d3.map({ + mouseenter: "mouseover", + mouseleave: "mouseout" + }); + if (d3_document) { + d3_selection_onFilters.forEach(function(k) { + if ("on" + k in d3_document) d3_selection_onFilters.remove(k); + }); + } + function d3_selection_onListener(listener, argumentz) { + return function(e) { + var o = d3.event; + d3.event = e; + argumentz[0] = this.__data__; + try { + listener.apply(this, argumentz); + } finally { + d3.event = o; + } + }; + } + function d3_selection_onFilter(listener, argumentz) { + var l = d3_selection_onListener(listener, argumentz); + return function(e) { + var target = this, related = e.relatedTarget; + if (!related || related !== target && !(related.compareDocumentPosition(target) & 8)) { + l.call(target, e); + } + }; + } + var d3_event_dragSelect, d3_event_dragId = 0; + function d3_event_dragSuppress(node) { + var name = ".dragsuppress-" + ++d3_event_dragId, click = "click" + name, w = d3.select(d3_window(node)).on("touchmove" + name, d3_eventPreventDefault).on("dragstart" + name, d3_eventPreventDefault).on("selectstart" + name, d3_eventPreventDefault); + if (d3_event_dragSelect == null) { + d3_event_dragSelect = "onselectstart" in node ? false : d3_vendorSymbol(node.style, "userSelect"); + } + if (d3_event_dragSelect) { + var style = d3_documentElement(node).style, select = style[d3_event_dragSelect]; + style[d3_event_dragSelect] = "none"; + } + return function(suppressClick) { + w.on(name, null); + if (d3_event_dragSelect) style[d3_event_dragSelect] = select; + if (suppressClick) { + var off = function() { + w.on(click, null); + }; + w.on(click, function() { + d3_eventPreventDefault(); + off(); + }, true); + setTimeout(off, 0); + } + }; + } + d3.mouse = function(container) { + return d3_mousePoint(container, d3_eventSource()); + }; + var d3_mouse_bug44083 = this.navigator && /WebKit/.test(this.navigator.userAgent) ? -1 : 0; + function d3_mousePoint(container, e) { + if (e.changedTouches) e = e.changedTouches[0]; + var svg = container.ownerSVGElement || container; + if (svg.createSVGPoint) { + var point = svg.createSVGPoint(); + if (d3_mouse_bug44083 < 0) { + var window = d3_window(container); + if (window.scrollX || window.scrollY) { + svg = d3.select("body").append("svg").style({ + position: "absolute", + top: 0, + left: 0, + margin: 0, + padding: 0, + border: "none" + }, "important"); + var ctm = svg[0][0].getScreenCTM(); + d3_mouse_bug44083 = !(ctm.f || ctm.e); + svg.remove(); + } + } + if (d3_mouse_bug44083) point.x = e.pageX, point.y = e.pageY; else point.x = e.clientX, + point.y = e.clientY; + point = point.matrixTransform(container.getScreenCTM().inverse()); + return [ point.x, point.y ]; + } + var rect = container.getBoundingClientRect(); + return [ e.clientX - rect.left - container.clientLeft, e.clientY - rect.top - container.clientTop ]; + } + d3.touch = function(container, touches, identifier) { + if (arguments.length < 3) identifier = touches, touches = d3_eventSource().changedTouches; + if (touches) for (var i = 0, n = touches.length, touch; i < n; ++i) { + if ((touch = touches[i]).identifier === identifier) { + return d3_mousePoint(container, touch); + } + } + }; + d3.behavior.drag = function() { + var event = d3_eventDispatch(drag, "drag", "dragstart", "dragend"), origin = null, mousedown = dragstart(d3_noop, d3.mouse, d3_window, "mousemove", "mouseup"), touchstart = dragstart(d3_behavior_dragTouchId, d3.touch, d3_identity, "touchmove", "touchend"); + function drag() { + this.on("mousedown.drag", mousedown).on("touchstart.drag", touchstart); + } + function dragstart(id, position, subject, move, end) { + return function() { + var that = this, target = d3.event.target.correspondingElement || d3.event.target, parent = that.parentNode, dispatch = event.of(that, arguments), dragged = 0, dragId = id(), dragName = ".drag" + (dragId == null ? "" : "-" + dragId), dragOffset, dragSubject = d3.select(subject(target)).on(move + dragName, moved).on(end + dragName, ended), dragRestore = d3_event_dragSuppress(target), position0 = position(parent, dragId); + if (origin) { + dragOffset = origin.apply(that, arguments); + dragOffset = [ dragOffset.x - position0[0], dragOffset.y - position0[1] ]; + } else { + dragOffset = [ 0, 0 ]; + } + dispatch({ + type: "dragstart" + }); + function moved() { + var position1 = position(parent, dragId), dx, dy; + if (!position1) return; + dx = position1[0] - position0[0]; + dy = position1[1] - position0[1]; + dragged |= dx | dy; + position0 = position1; + dispatch({ + type: "drag", + x: position1[0] + dragOffset[0], + y: position1[1] + dragOffset[1], + dx: dx, + dy: dy + }); + } + function ended() { + if (!position(parent, dragId)) return; + dragSubject.on(move + dragName, null).on(end + dragName, null); + dragRestore(dragged); + dispatch({ + type: "dragend" + }); + } + }; + } + drag.origin = function(x) { + if (!arguments.length) return origin; + origin = x; + return drag; + }; + return d3.rebind(drag, event, "on"); + }; + function d3_behavior_dragTouchId() { + return d3.event.changedTouches[0].identifier; + } + d3.touches = function(container, touches) { + if (arguments.length < 2) touches = d3_eventSource().touches; + return touches ? d3_array(touches).map(function(touch) { + var point = d3_mousePoint(container, touch); + point.identifier = touch.identifier; + return point; + }) : []; + }; + var ε = 1e-6, ε2 = ε * ε, π = Math.PI, τ = 2 * π, τε = τ - ε, halfπ = π / 2, d3_radians = π / 180, d3_degrees = 180 / π; + function d3_sgn(x) { + return x > 0 ? 1 : x < 0 ? -1 : 0; + } + function d3_cross2d(a, b, c) { + return (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]); + } + function d3_acos(x) { + return x > 1 ? 0 : x < -1 ? π : Math.acos(x); + } + function d3_asin(x) { + return x > 1 ? halfπ : x < -1 ? -halfπ : Math.asin(x); + } + function d3_sinh(x) { + return ((x = Math.exp(x)) - 1 / x) / 2; + } + function d3_cosh(x) { + return ((x = Math.exp(x)) + 1 / x) / 2; + } + function d3_tanh(x) { + return ((x = Math.exp(2 * x)) - 1) / (x + 1); + } + function d3_haversin(x) { + return (x = Math.sin(x / 2)) * x; + } + var ρ = Math.SQRT2, ρ2 = 2, ρ4 = 4; + d3.interpolateZoom = function(p0, p1) { + var ux0 = p0[0], uy0 = p0[1], w0 = p0[2], ux1 = p1[0], uy1 = p1[1], w1 = p1[2], dx = ux1 - ux0, dy = uy1 - uy0, d2 = dx * dx + dy * dy, i, S; + if (d2 < ε2) { + S = Math.log(w1 / w0) / ρ; + i = function(t) { + return [ ux0 + t * dx, uy0 + t * dy, w0 * Math.exp(ρ * t * S) ]; + }; + } else { + var d1 = Math.sqrt(d2), b0 = (w1 * w1 - w0 * w0 + ρ4 * d2) / (2 * w0 * ρ2 * d1), b1 = (w1 * w1 - w0 * w0 - ρ4 * d2) / (2 * w1 * ρ2 * d1), r0 = Math.log(Math.sqrt(b0 * b0 + 1) - b0), r1 = Math.log(Math.sqrt(b1 * b1 + 1) - b1); + S = (r1 - r0) / ρ; + i = function(t) { + var s = t * S, coshr0 = d3_cosh(r0), u = w0 / (ρ2 * d1) * (coshr0 * d3_tanh(ρ * s + r0) - d3_sinh(r0)); + return [ ux0 + u * dx, uy0 + u * dy, w0 * coshr0 / d3_cosh(ρ * s + r0) ]; + }; + } + i.duration = S * 1e3; + return i; + }; + d3.behavior.zoom = function() { + var view = { + x: 0, + y: 0, + k: 1 + }, translate0, center0, center, size = [ 960, 500 ], scaleExtent = d3_behavior_zoomInfinity, duration = 250, zooming = 0, mousedown = "mousedown.zoom", mousemove = "mousemove.zoom", mouseup = "mouseup.zoom", mousewheelTimer, touchstart = "touchstart.zoom", touchtime, event = d3_eventDispatch(zoom, "zoomstart", "zoom", "zoomend"), x0, x1, y0, y1; + if (!d3_behavior_zoomWheel) { + d3_behavior_zoomWheel = "onwheel" in d3_document ? (d3_behavior_zoomDelta = function() { + return -d3.event.deltaY * (d3.event.deltaMode ? 120 : 1); + }, "wheel") : "onmousewheel" in d3_document ? (d3_behavior_zoomDelta = function() { + return d3.event.wheelDelta; + }, "mousewheel") : (d3_behavior_zoomDelta = function() { + return -d3.event.detail; + }, "MozMousePixelScroll"); + } + function zoom(g) { + g.on(mousedown, mousedowned).on(d3_behavior_zoomWheel + ".zoom", mousewheeled).on("dblclick.zoom", dblclicked).on(touchstart, touchstarted); + } + zoom.event = function(g) { + g.each(function() { + var dispatch = event.of(this, arguments), view1 = view; + if (d3_transitionInheritId) { + d3.select(this).transition().each("start.zoom", function() { + view = this.__chart__ || { + x: 0, + y: 0, + k: 1 + }; + zoomstarted(dispatch); + }).tween("zoom:zoom", function() { + var dx = size[0], dy = size[1], cx = center0 ? center0[0] : dx / 2, cy = center0 ? center0[1] : dy / 2, i = d3.interpolateZoom([ (cx - view.x) / view.k, (cy - view.y) / view.k, dx / view.k ], [ (cx - view1.x) / view1.k, (cy - view1.y) / view1.k, dx / view1.k ]); + return function(t) { + var l = i(t), k = dx / l[2]; + this.__chart__ = view = { + x: cx - l[0] * k, + y: cy - l[1] * k, + k: k + }; + zoomed(dispatch); + }; + }).each("interrupt.zoom", function() { + zoomended(dispatch); + }).each("end.zoom", function() { + zoomended(dispatch); + }); + } else { + this.__chart__ = view; + zoomstarted(dispatch); + zoomed(dispatch); + zoomended(dispatch); + } + }); + }; + zoom.translate = function(_) { + if (!arguments.length) return [ view.x, view.y ]; + view = { + x: +_[0], + y: +_[1], + k: view.k + }; + rescale(); + return zoom; + }; + zoom.scale = function(_) { + if (!arguments.length) return view.k; + view = { + x: view.x, + y: view.y, + k: null + }; + scaleTo(+_); + rescale(); + return zoom; + }; + zoom.scaleExtent = function(_) { + if (!arguments.length) return scaleExtent; + scaleExtent = _ == null ? d3_behavior_zoomInfinity : [ +_[0], +_[1] ]; + return zoom; + }; + zoom.center = function(_) { + if (!arguments.length) return center; + center = _ && [ +_[0], +_[1] ]; + return zoom; + }; + zoom.size = function(_) { + if (!arguments.length) return size; + size = _ && [ +_[0], +_[1] ]; + return zoom; + }; + zoom.duration = function(_) { + if (!arguments.length) return duration; + duration = +_; + return zoom; + }; + zoom.x = function(z) { + if (!arguments.length) return x1; + x1 = z; + x0 = z.copy(); + view = { + x: 0, + y: 0, + k: 1 + }; + return zoom; + }; + zoom.y = function(z) { + if (!arguments.length) return y1; + y1 = z; + y0 = z.copy(); + view = { + x: 0, + y: 0, + k: 1 + }; + return zoom; + }; + function location(p) { + return [ (p[0] - view.x) / view.k, (p[1] - view.y) / view.k ]; + } + function point(l) { + return [ l[0] * view.k + view.x, l[1] * view.k + view.y ]; + } + function scaleTo(s) { + view.k = Math.max(scaleExtent[0], Math.min(scaleExtent[1], s)); + } + function translateTo(p, l) { + l = point(l); + view.x += p[0] - l[0]; + view.y += p[1] - l[1]; + } + function zoomTo(that, p, l, k) { + that.__chart__ = { + x: view.x, + y: view.y, + k: view.k + }; + scaleTo(Math.pow(2, k)); + translateTo(center0 = p, l); + that = d3.select(that); + if (duration > 0) that = that.transition().duration(duration); + that.call(zoom.event); + } + function rescale() { + if (x1) x1.domain(x0.range().map(function(x) { + return (x - view.x) / view.k; + }).map(x0.invert)); + if (y1) y1.domain(y0.range().map(function(y) { + return (y - view.y) / view.k; + }).map(y0.invert)); + } + function zoomstarted(dispatch) { + if (!zooming++) dispatch({ + type: "zoomstart" + }); + } + function zoomed(dispatch) { + rescale(); + dispatch({ + type: "zoom", + scale: view.k, + translate: [ view.x, view.y ] + }); + } + function zoomended(dispatch) { + if (!--zooming) dispatch({ + type: "zoomend" + }), center0 = null; + } + function mousedowned() { + var that = this, dispatch = event.of(that, arguments), dragged = 0, subject = d3.select(d3_window(that)).on(mousemove, moved).on(mouseup, ended), location0 = location(d3.mouse(that)), dragRestore = d3_event_dragSuppress(that); + d3_selection_interrupt.call(that); + zoomstarted(dispatch); + function moved() { + dragged = 1; + translateTo(d3.mouse(that), location0); + zoomed(dispatch); + } + function ended() { + subject.on(mousemove, null).on(mouseup, null); + dragRestore(dragged); + zoomended(dispatch); + } + } + function touchstarted() { + var that = this, dispatch = event.of(that, arguments), locations0 = {}, distance0 = 0, scale0, zoomName = ".zoom-" + d3.event.changedTouches[0].identifier, touchmove = "touchmove" + zoomName, touchend = "touchend" + zoomName, targets = [], subject = d3.select(that), dragRestore = d3_event_dragSuppress(that); + started(); + zoomstarted(dispatch); + subject.on(mousedown, null).on(touchstart, started); + function relocate() { + var touches = d3.touches(that); + scale0 = view.k; + touches.forEach(function(t) { + if (t.identifier in locations0) locations0[t.identifier] = location(t); + }); + return touches; + } + function started() { + var target = d3.event.target; + d3.select(target).on(touchmove, moved).on(touchend, ended); + targets.push(target); + var changed = d3.event.changedTouches; + for (var i = 0, n = changed.length; i < n; ++i) { + locations0[changed[i].identifier] = null; + } + var touches = relocate(), now = Date.now(); + if (touches.length === 1) { + if (now - touchtime < 500) { + var p = touches[0]; + zoomTo(that, p, locations0[p.identifier], Math.floor(Math.log(view.k) / Math.LN2) + 1); + d3_eventPreventDefault(); + } + touchtime = now; + } else if (touches.length > 1) { + var p = touches[0], q = touches[1], dx = p[0] - q[0], dy = p[1] - q[1]; + distance0 = dx * dx + dy * dy; + } + } + function moved() { + var touches = d3.touches(that), p0, l0, p1, l1; + d3_selection_interrupt.call(that); + for (var i = 0, n = touches.length; i < n; ++i, l1 = null) { + p1 = touches[i]; + if (l1 = locations0[p1.identifier]) { + if (l0) break; + p0 = p1, l0 = l1; + } + } + if (l1) { + var distance1 = (distance1 = p1[0] - p0[0]) * distance1 + (distance1 = p1[1] - p0[1]) * distance1, scale1 = distance0 && Math.sqrt(distance1 / distance0); + p0 = [ (p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2 ]; + l0 = [ (l0[0] + l1[0]) / 2, (l0[1] + l1[1]) / 2 ]; + scaleTo(scale1 * scale0); + } + touchtime = null; + translateTo(p0, l0); + zoomed(dispatch); + } + function ended() { + if (d3.event.touches.length) { + var changed = d3.event.changedTouches; + for (var i = 0, n = changed.length; i < n; ++i) { + delete locations0[changed[i].identifier]; + } + for (var identifier in locations0) { + return void relocate(); + } + } + d3.selectAll(targets).on(zoomName, null); + subject.on(mousedown, mousedowned).on(touchstart, touchstarted); + dragRestore(); + zoomended(dispatch); + } + } + function mousewheeled() { + var dispatch = event.of(this, arguments); + if (mousewheelTimer) clearTimeout(mousewheelTimer); else d3_selection_interrupt.call(this), + translate0 = location(center0 = center || d3.mouse(this)), zoomstarted(dispatch); + mousewheelTimer = setTimeout(function() { + mousewheelTimer = null; + zoomended(dispatch); + }, 50); + d3_eventPreventDefault(); + scaleTo(Math.pow(2, d3_behavior_zoomDelta() * .002) * view.k); + translateTo(center0, translate0); + zoomed(dispatch); + } + function dblclicked() { + var p = d3.mouse(this), k = Math.log(view.k) / Math.LN2; + zoomTo(this, p, location(p), d3.event.shiftKey ? Math.ceil(k) - 1 : Math.floor(k) + 1); + } + return d3.rebind(zoom, event, "on"); + }; + var d3_behavior_zoomInfinity = [ 0, Infinity ], d3_behavior_zoomDelta, d3_behavior_zoomWheel; + d3.color = d3_color; + function d3_color() {} + d3_color.prototype.toString = function() { + return this.rgb() + ""; + }; + d3.hsl = d3_hsl; + function d3_hsl(h, s, l) { + return this instanceof d3_hsl ? void (this.h = +h, this.s = +s, this.l = +l) : arguments.length < 2 ? h instanceof d3_hsl ? new d3_hsl(h.h, h.s, h.l) : d3_rgb_parse("" + h, d3_rgb_hsl, d3_hsl) : new d3_hsl(h, s, l); + } + var d3_hslPrototype = d3_hsl.prototype = new d3_color(); + d3_hslPrototype.brighter = function(k) { + k = Math.pow(.7, arguments.length ? k : 1); + return new d3_hsl(this.h, this.s, this.l / k); + }; + d3_hslPrototype.darker = function(k) { + k = Math.pow(.7, arguments.length ? k : 1); + return new d3_hsl(this.h, this.s, k * this.l); + }; + d3_hslPrototype.rgb = function() { + return d3_hsl_rgb(this.h, this.s, this.l); + }; + function d3_hsl_rgb(h, s, l) { + var m1, m2; + h = isNaN(h) ? 0 : (h %= 360) < 0 ? h + 360 : h; + s = isNaN(s) ? 0 : s < 0 ? 0 : s > 1 ? 1 : s; + l = l < 0 ? 0 : l > 1 ? 1 : l; + m2 = l <= .5 ? l * (1 + s) : l + s - l * s; + m1 = 2 * l - m2; + function v(h) { + if (h > 360) h -= 360; else if (h < 0) h += 360; + if (h < 60) return m1 + (m2 - m1) * h / 60; + if (h < 180) return m2; + if (h < 240) return m1 + (m2 - m1) * (240 - h) / 60; + return m1; + } + function vv(h) { + return Math.round(v(h) * 255); + } + return new d3_rgb(vv(h + 120), vv(h), vv(h - 120)); + } + d3.hcl = d3_hcl; + function d3_hcl(h, c, l) { + return this instanceof d3_hcl ? void (this.h = +h, this.c = +c, this.l = +l) : arguments.length < 2 ? h instanceof d3_hcl ? new d3_hcl(h.h, h.c, h.l) : h instanceof d3_lab ? d3_lab_hcl(h.l, h.a, h.b) : d3_lab_hcl((h = d3_rgb_lab((h = d3.rgb(h)).r, h.g, h.b)).l, h.a, h.b) : new d3_hcl(h, c, l); + } + var d3_hclPrototype = d3_hcl.prototype = new d3_color(); + d3_hclPrototype.brighter = function(k) { + return new d3_hcl(this.h, this.c, Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1))); + }; + d3_hclPrototype.darker = function(k) { + return new d3_hcl(this.h, this.c, Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1))); + }; + d3_hclPrototype.rgb = function() { + return d3_hcl_lab(this.h, this.c, this.l).rgb(); + }; + function d3_hcl_lab(h, c, l) { + if (isNaN(h)) h = 0; + if (isNaN(c)) c = 0; + return new d3_lab(l, Math.cos(h *= d3_radians) * c, Math.sin(h) * c); + } + d3.lab = d3_lab; + function d3_lab(l, a, b) { + return this instanceof d3_lab ? void (this.l = +l, this.a = +a, this.b = +b) : arguments.length < 2 ? l instanceof d3_lab ? new d3_lab(l.l, l.a, l.b) : l instanceof d3_hcl ? d3_hcl_lab(l.h, l.c, l.l) : d3_rgb_lab((l = d3_rgb(l)).r, l.g, l.b) : new d3_lab(l, a, b); + } + var d3_lab_K = 18; + var d3_lab_X = .95047, d3_lab_Y = 1, d3_lab_Z = 1.08883; + var d3_labPrototype = d3_lab.prototype = new d3_color(); + d3_labPrototype.brighter = function(k) { + return new d3_lab(Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1)), this.a, this.b); + }; + d3_labPrototype.darker = function(k) { + return new d3_lab(Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1)), this.a, this.b); + }; + d3_labPrototype.rgb = function() { + return d3_lab_rgb(this.l, this.a, this.b); + }; + function d3_lab_rgb(l, a, b) { + var y = (l + 16) / 116, x = y + a / 500, z = y - b / 200; + x = d3_lab_xyz(x) * d3_lab_X; + y = d3_lab_xyz(y) * d3_lab_Y; + z = d3_lab_xyz(z) * d3_lab_Z; + return new d3_rgb(d3_xyz_rgb(3.2404542 * x - 1.5371385 * y - .4985314 * z), d3_xyz_rgb(-.969266 * x + 1.8760108 * y + .041556 * z), d3_xyz_rgb(.0556434 * x - .2040259 * y + 1.0572252 * z)); + } + function d3_lab_hcl(l, a, b) { + return l > 0 ? new d3_hcl(Math.atan2(b, a) * d3_degrees, Math.sqrt(a * a + b * b), l) : new d3_hcl(NaN, NaN, l); + } + function d3_lab_xyz(x) { + return x > .206893034 ? x * x * x : (x - 4 / 29) / 7.787037; + } + function d3_xyz_lab(x) { + return x > .008856 ? Math.pow(x, 1 / 3) : 7.787037 * x + 4 / 29; + } + function d3_xyz_rgb(r) { + return Math.round(255 * (r <= .00304 ? 12.92 * r : 1.055 * Math.pow(r, 1 / 2.4) - .055)); + } + d3.rgb = d3_rgb; + function d3_rgb(r, g, b) { + return this instanceof d3_rgb ? void (this.r = ~~r, this.g = ~~g, this.b = ~~b) : arguments.length < 2 ? r instanceof d3_rgb ? new d3_rgb(r.r, r.g, r.b) : d3_rgb_parse("" + r, d3_rgb, d3_hsl_rgb) : new d3_rgb(r, g, b); + } + function d3_rgbNumber(value) { + return new d3_rgb(value >> 16, value >> 8 & 255, value & 255); + } + function d3_rgbString(value) { + return d3_rgbNumber(value) + ""; + } + var d3_rgbPrototype = d3_rgb.prototype = new d3_color(); + d3_rgbPrototype.brighter = function(k) { + k = Math.pow(.7, arguments.length ? k : 1); + var r = this.r, g = this.g, b = this.b, i = 30; + if (!r && !g && !b) return new d3_rgb(i, i, i); + if (r && r < i) r = i; + if (g && g < i) g = i; + if (b && b < i) b = i; + return new d3_rgb(Math.min(255, r / k), Math.min(255, g / k), Math.min(255, b / k)); + }; + d3_rgbPrototype.darker = function(k) { + k = Math.pow(.7, arguments.length ? k : 1); + return new d3_rgb(k * this.r, k * this.g, k * this.b); + }; + d3_rgbPrototype.hsl = function() { + return d3_rgb_hsl(this.r, this.g, this.b); + }; + d3_rgbPrototype.toString = function() { + return "#" + d3_rgb_hex(this.r) + d3_rgb_hex(this.g) + d3_rgb_hex(this.b); + }; + function d3_rgb_hex(v) { + return v < 16 ? "0" + Math.max(0, v).toString(16) : Math.min(255, v).toString(16); + } + function d3_rgb_parse(format, rgb, hsl) { + var r = 0, g = 0, b = 0, m1, m2, color; + m1 = /([a-z]+)\((.*)\)/.exec(format = format.toLowerCase()); + if (m1) { + m2 = m1[2].split(","); + switch (m1[1]) { + case "hsl": + { + return hsl(parseFloat(m2[0]), parseFloat(m2[1]) / 100, parseFloat(m2[2]) / 100); + } + + case "rgb": + { + return rgb(d3_rgb_parseNumber(m2[0]), d3_rgb_parseNumber(m2[1]), d3_rgb_parseNumber(m2[2])); + } + } + } + if (color = d3_rgb_names.get(format)) { + return rgb(color.r, color.g, color.b); + } + if (format != null && format.charAt(0) === "#" && !isNaN(color = parseInt(format.slice(1), 16))) { + if (format.length === 4) { + r = (color & 3840) >> 4; + r = r >> 4 | r; + g = color & 240; + g = g >> 4 | g; + b = color & 15; + b = b << 4 | b; + } else if (format.length === 7) { + r = (color & 16711680) >> 16; + g = (color & 65280) >> 8; + b = color & 255; + } + } + return rgb(r, g, b); + } + function d3_rgb_hsl(r, g, b) { + var min = Math.min(r /= 255, g /= 255, b /= 255), max = Math.max(r, g, b), d = max - min, h, s, l = (max + min) / 2; + if (d) { + s = l < .5 ? d / (max + min) : d / (2 - max - min); + if (r == max) h = (g - b) / d + (g < b ? 6 : 0); else if (g == max) h = (b - r) / d + 2; else h = (r - g) / d + 4; + h *= 60; + } else { + h = NaN; + s = l > 0 && l < 1 ? 0 : h; + } + return new d3_hsl(h, s, l); + } + function d3_rgb_lab(r, g, b) { + r = d3_rgb_xyz(r); + g = d3_rgb_xyz(g); + b = d3_rgb_xyz(b); + var x = d3_xyz_lab((.4124564 * r + .3575761 * g + .1804375 * b) / d3_lab_X), y = d3_xyz_lab((.2126729 * r + .7151522 * g + .072175 * b) / d3_lab_Y), z = d3_xyz_lab((.0193339 * r + .119192 * g + .9503041 * b) / d3_lab_Z); + return d3_lab(116 * y - 16, 500 * (x - y), 200 * (y - z)); + } + function d3_rgb_xyz(r) { + return (r /= 255) <= .04045 ? r / 12.92 : Math.pow((r + .055) / 1.055, 2.4); + } + function d3_rgb_parseNumber(c) { + var f = parseFloat(c); + return c.charAt(c.length - 1) === "%" ? Math.round(f * 2.55) : f; + } + var d3_rgb_names = d3.map({ + aliceblue: 15792383, + antiquewhite: 16444375, + aqua: 65535, + aquamarine: 8388564, + azure: 15794175, + beige: 16119260, + bisque: 16770244, + black: 0, + blanchedalmond: 16772045, + blue: 255, + blueviolet: 9055202, + brown: 10824234, + burlywood: 14596231, + cadetblue: 6266528, + chartreuse: 8388352, + chocolate: 13789470, + coral: 16744272, + cornflowerblue: 6591981, + cornsilk: 16775388, + crimson: 14423100, + cyan: 65535, + darkblue: 139, + darkcyan: 35723, + darkgoldenrod: 12092939, + darkgray: 11119017, + darkgreen: 25600, + darkgrey: 11119017, + darkkhaki: 12433259, + darkmagenta: 9109643, + darkolivegreen: 5597999, + darkorange: 16747520, + darkorchid: 10040012, + darkred: 9109504, + darksalmon: 15308410, + darkseagreen: 9419919, + darkslateblue: 4734347, + darkslategray: 3100495, + darkslategrey: 3100495, + darkturquoise: 52945, + darkviolet: 9699539, + deeppink: 16716947, + deepskyblue: 49151, + dimgray: 6908265, + dimgrey: 6908265, + dodgerblue: 2003199, + firebrick: 11674146, + floralwhite: 16775920, + forestgreen: 2263842, + fuchsia: 16711935, + gainsboro: 14474460, + ghostwhite: 16316671, + gold: 16766720, + goldenrod: 14329120, + gray: 8421504, + green: 32768, + greenyellow: 11403055, + grey: 8421504, + honeydew: 15794160, + hotpink: 16738740, + indianred: 13458524, + indigo: 4915330, + ivory: 16777200, + khaki: 15787660, + lavender: 15132410, + lavenderblush: 16773365, + lawngreen: 8190976, + lemonchiffon: 16775885, + lightblue: 11393254, + lightcoral: 15761536, + lightcyan: 14745599, + lightgoldenrodyellow: 16448210, + lightgray: 13882323, + lightgreen: 9498256, + lightgrey: 13882323, + lightpink: 16758465, + lightsalmon: 16752762, + lightseagreen: 2142890, + lightskyblue: 8900346, + lightslategray: 7833753, + lightslategrey: 7833753, + lightsteelblue: 11584734, + lightyellow: 16777184, + lime: 65280, + limegreen: 3329330, + linen: 16445670, + magenta: 16711935, + maroon: 8388608, + mediumaquamarine: 6737322, + mediumblue: 205, + mediumorchid: 12211667, + mediumpurple: 9662683, + mediumseagreen: 3978097, + mediumslateblue: 8087790, + mediumspringgreen: 64154, + mediumturquoise: 4772300, + mediumvioletred: 13047173, + midnightblue: 1644912, + mintcream: 16121850, + mistyrose: 16770273, + moccasin: 16770229, + navajowhite: 16768685, + navy: 128, + oldlace: 16643558, + olive: 8421376, + olivedrab: 7048739, + orange: 16753920, + orangered: 16729344, + orchid: 14315734, + palegoldenrod: 15657130, + palegreen: 10025880, + paleturquoise: 11529966, + palevioletred: 14381203, + papayawhip: 16773077, + peachpuff: 16767673, + peru: 13468991, + pink: 16761035, + plum: 14524637, + powderblue: 11591910, + purple: 8388736, + rebeccapurple: 6697881, + red: 16711680, + rosybrown: 12357519, + royalblue: 4286945, + saddlebrown: 9127187, + salmon: 16416882, + sandybrown: 16032864, + seagreen: 3050327, + seashell: 16774638, + sienna: 10506797, + silver: 12632256, + skyblue: 8900331, + slateblue: 6970061, + slategray: 7372944, + slategrey: 7372944, + snow: 16775930, + springgreen: 65407, + steelblue: 4620980, + tan: 13808780, + teal: 32896, + thistle: 14204888, + tomato: 16737095, + turquoise: 4251856, + violet: 15631086, + wheat: 16113331, + white: 16777215, + whitesmoke: 16119285, + yellow: 16776960, + yellowgreen: 10145074 + }); + d3_rgb_names.forEach(function(key, value) { + d3_rgb_names.set(key, d3_rgbNumber(value)); + }); + function d3_functor(v) { + return typeof v === "function" ? v : function() { + return v; + }; + } + d3.functor = d3_functor; + d3.xhr = d3_xhrType(d3_identity); + function d3_xhrType(response) { + return function(url, mimeType, callback) { + if (arguments.length === 2 && typeof mimeType === "function") callback = mimeType, + mimeType = null; + return d3_xhr(url, mimeType, response, callback); + }; + } + function d3_xhr(url, mimeType, response, callback) { + var xhr = {}, dispatch = d3.dispatch("beforesend", "progress", "load", "error"), headers = {}, request = new XMLHttpRequest(), responseType = null; + if (this.XDomainRequest && !("withCredentials" in request) && /^(http(s)?:)?\/\//.test(url)) request = new XDomainRequest(); + "onload" in request ? request.onload = request.onerror = respond : request.onreadystatechange = function() { + request.readyState > 3 && respond(); + }; + function respond() { + var status = request.status, result; + if (!status && d3_xhrHasResponse(request) || status >= 200 && status < 300 || status === 304) { + try { + result = response.call(xhr, request); + } catch (e) { + dispatch.error.call(xhr, e); + return; + } + dispatch.load.call(xhr, result); + } else { + dispatch.error.call(xhr, request); + } + } + request.onprogress = function(event) { + var o = d3.event; + d3.event = event; + try { + dispatch.progress.call(xhr, request); + } finally { + d3.event = o; + } + }; + xhr.header = function(name, value) { + name = (name + "").toLowerCase(); + if (arguments.length < 2) return headers[name]; + if (value == null) delete headers[name]; else headers[name] = value + ""; + return xhr; + }; + xhr.mimeType = function(value) { + if (!arguments.length) return mimeType; + mimeType = value == null ? null : value + ""; + return xhr; + }; + xhr.responseType = function(value) { + if (!arguments.length) return responseType; + responseType = value; + return xhr; + }; + xhr.response = function(value) { + response = value; + return xhr; + }; + [ "get", "post" ].forEach(function(method) { + xhr[method] = function() { + return xhr.send.apply(xhr, [ method ].concat(d3_array(arguments))); + }; + }); + xhr.send = function(method, data, callback) { + if (arguments.length === 2 && typeof data === "function") callback = data, data = null; + request.open(method, url, true); + if (mimeType != null && !("accept" in headers)) headers["accept"] = mimeType + ",*/*"; + if (request.setRequestHeader) for (var name in headers) request.setRequestHeader(name, headers[name]); + if (mimeType != null && request.overrideMimeType) request.overrideMimeType(mimeType); + if (responseType != null) request.responseType = responseType; + if (callback != null) xhr.on("error", callback).on("load", function(request) { + callback(null, request); + }); + dispatch.beforesend.call(xhr, request); + request.send(data == null ? null : data); + return xhr; + }; + xhr.abort = function() { + request.abort(); + return xhr; + }; + d3.rebind(xhr, dispatch, "on"); + return callback == null ? xhr : xhr.get(d3_xhr_fixCallback(callback)); + } + function d3_xhr_fixCallback(callback) { + return callback.length === 1 ? function(error, request) { + callback(error == null ? request : null); + } : callback; + } + function d3_xhrHasResponse(request) { + var type = request.responseType; + return type && type !== "text" ? request.response : request.responseText; + } + d3.dsv = function(delimiter, mimeType) { + var reFormat = new RegExp('["' + delimiter + "\n]"), delimiterCode = delimiter.charCodeAt(0); + function dsv(url, row, callback) { + if (arguments.length < 3) callback = row, row = null; + var xhr = d3_xhr(url, mimeType, row == null ? response : typedResponse(row), callback); + xhr.row = function(_) { + return arguments.length ? xhr.response((row = _) == null ? response : typedResponse(_)) : row; + }; + return xhr; + } + function response(request) { + return dsv.parse(request.responseText); + } + function typedResponse(f) { + return function(request) { + return dsv.parse(request.responseText, f); + }; + } + dsv.parse = function(text, f) { + var o; + return dsv.parseRows(text, function(row, i) { + if (o) return o(row, i - 1); + var a = new Function("d", "return {" + row.map(function(name, i) { + return JSON.stringify(name) + ": d[" + i + "]"; + }).join(",") + "}"); + o = f ? function(row, i) { + return f(a(row), i); + } : a; + }); + }; + dsv.parseRows = function(text, f) { + var EOL = {}, EOF = {}, rows = [], N = text.length, I = 0, n = 0, t, eol; + function token() { + if (I >= N) return EOF; + if (eol) return eol = false, EOL; + var j = I; + if (text.charCodeAt(j) === 34) { + var i = j; + while (i++ < N) { + if (text.charCodeAt(i) === 34) { + if (text.charCodeAt(i + 1) !== 34) break; + ++i; + } + } + I = i + 2; + var c = text.charCodeAt(i + 1); + if (c === 13) { + eol = true; + if (text.charCodeAt(i + 2) === 10) ++I; + } else if (c === 10) { + eol = true; + } + return text.slice(j + 1, i).replace(/""/g, '"'); + } + while (I < N) { + var c = text.charCodeAt(I++), k = 1; + if (c === 10) eol = true; else if (c === 13) { + eol = true; + if (text.charCodeAt(I) === 10) ++I, ++k; + } else if (c !== delimiterCode) continue; + return text.slice(j, I - k); + } + return text.slice(j); + } + while ((t = token()) !== EOF) { + var a = []; + while (t !== EOL && t !== EOF) { + a.push(t); + t = token(); + } + if (f && (a = f(a, n++)) == null) continue; + rows.push(a); + } + return rows; + }; + dsv.format = function(rows) { + if (Array.isArray(rows[0])) return dsv.formatRows(rows); + var fieldSet = new d3_Set(), fields = []; + rows.forEach(function(row) { + for (var field in row) { + if (!fieldSet.has(field)) { + fields.push(fieldSet.add(field)); + } + } + }); + return [ fields.map(formatValue).join(delimiter) ].concat(rows.map(function(row) { + return fields.map(function(field) { + return formatValue(row[field]); + }).join(delimiter); + })).join("\n"); + }; + dsv.formatRows = function(rows) { + return rows.map(formatRow).join("\n"); + }; + function formatRow(row) { + return row.map(formatValue).join(delimiter); + } + function formatValue(text) { + return reFormat.test(text) ? '"' + text.replace(/\"/g, '""') + '"' : text; + } + return dsv; + }; + d3.csv = d3.dsv(",", "text/csv"); + d3.tsv = d3.dsv(" ", "text/tab-separated-values"); + var d3_timer_queueHead, d3_timer_queueTail, d3_timer_interval, d3_timer_timeout, d3_timer_frame = this[d3_vendorSymbol(this, "requestAnimationFrame")] || function(callback) { + setTimeout(callback, 17); + }; + d3.timer = function() { + d3_timer.apply(this, arguments); + }; + function d3_timer(callback, delay, then) { + var n = arguments.length; + if (n < 2) delay = 0; + if (n < 3) then = Date.now(); + var time = then + delay, timer = { + c: callback, + t: time, + n: null + }; + if (d3_timer_queueTail) d3_timer_queueTail.n = timer; else d3_timer_queueHead = timer; + d3_timer_queueTail = timer; + if (!d3_timer_interval) { + d3_timer_timeout = clearTimeout(d3_timer_timeout); + d3_timer_interval = 1; + d3_timer_frame(d3_timer_step); + } + return timer; + } + function d3_timer_step() { + var now = d3_timer_mark(), delay = d3_timer_sweep() - now; + if (delay > 24) { + if (isFinite(delay)) { + clearTimeout(d3_timer_timeout); + d3_timer_timeout = setTimeout(d3_timer_step, delay); + } + d3_timer_interval = 0; + } else { + d3_timer_interval = 1; + d3_timer_frame(d3_timer_step); + } + } + d3.timer.flush = function() { + d3_timer_mark(); + d3_timer_sweep(); + }; + function d3_timer_mark() { + var now = Date.now(), timer = d3_timer_queueHead; + while (timer) { + if (now >= timer.t && timer.c(now - timer.t)) timer.c = null; + timer = timer.n; + } + return now; + } + function d3_timer_sweep() { + var t0, t1 = d3_timer_queueHead, time = Infinity; + while (t1) { + if (t1.c) { + if (t1.t < time) time = t1.t; + t1 = (t0 = t1).n; + } else { + t1 = t0 ? t0.n = t1.n : d3_timer_queueHead = t1.n; + } + } + d3_timer_queueTail = t0; + return time; + } + function d3_format_precision(x, p) { + return p - (x ? Math.ceil(Math.log(x) / Math.LN10) : 1); + } + d3.round = function(x, n) { + return n ? Math.round(x * (n = Math.pow(10, n))) / n : Math.round(x); + }; + var d3_formatPrefixes = [ "y", "z", "a", "f", "p", "n", "µ", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y" ].map(d3_formatPrefix); + d3.formatPrefix = function(value, precision) { + var i = 0; + if (value = +value) { + if (value < 0) value *= -1; + if (precision) value = d3.round(value, d3_format_precision(value, precision)); + i = 1 + Math.floor(1e-12 + Math.log(value) / Math.LN10); + i = Math.max(-24, Math.min(24, Math.floor((i - 1) / 3) * 3)); + } + return d3_formatPrefixes[8 + i / 3]; + }; + function d3_formatPrefix(d, i) { + var k = Math.pow(10, abs(8 - i) * 3); + return { + scale: i > 8 ? function(d) { + return d / k; + } : function(d) { + return d * k; + }, + symbol: d + }; + } + function d3_locale_numberFormat(locale) { + var locale_decimal = locale.decimal, locale_thousands = locale.thousands, locale_grouping = locale.grouping, locale_currency = locale.currency, formatGroup = locale_grouping && locale_thousands ? function(value, width) { + var i = value.length, t = [], j = 0, g = locale_grouping[0], length = 0; + while (i > 0 && g > 0) { + if (length + g + 1 > width) g = Math.max(1, width - length); + t.push(value.substring(i -= g, i + g)); + if ((length += g + 1) > width) break; + g = locale_grouping[j = (j + 1) % locale_grouping.length]; + } + return t.reverse().join(locale_thousands); + } : d3_identity; + return function(specifier) { + var match = d3_format_re.exec(specifier), fill = match[1] || " ", align = match[2] || ">", sign = match[3] || "-", symbol = match[4] || "", zfill = match[5], width = +match[6], comma = match[7], precision = match[8], type = match[9], scale = 1, prefix = "", suffix = "", integer = false, exponent = true; + if (precision) precision = +precision.substring(1); + if (zfill || fill === "0" && align === "=") { + zfill = fill = "0"; + align = "="; + } + switch (type) { + case "n": + comma = true; + type = "g"; + break; + + case "%": + scale = 100; + suffix = "%"; + type = "f"; + break; + + case "p": + scale = 100; + suffix = "%"; + type = "r"; + break; + + case "b": + case "o": + case "x": + case "X": + if (symbol === "#") prefix = "0" + type.toLowerCase(); + + case "c": + exponent = false; + + case "d": + integer = true; + precision = 0; + break; + + case "s": + scale = -1; + type = "r"; + break; + } + if (symbol === "$") prefix = locale_currency[0], suffix = locale_currency[1]; + if (type == "r" && !precision) type = "g"; + if (precision != null) { + if (type == "g") precision = Math.max(1, Math.min(21, precision)); else if (type == "e" || type == "f") precision = Math.max(0, Math.min(20, precision)); + } + type = d3_format_types.get(type) || d3_format_typeDefault; + var zcomma = zfill && comma; + return function(value) { + var fullSuffix = suffix; + if (integer && value % 1) return ""; + var negative = value < 0 || value === 0 && 1 / value < 0 ? (value = -value, "-") : sign === "-" ? "" : sign; + if (scale < 0) { + var unit = d3.formatPrefix(value, precision); + value = unit.scale(value); + fullSuffix = unit.symbol + suffix; + } else { + value *= scale; + } + value = type(value, precision); + var i = value.lastIndexOf("."), before, after; + if (i < 0) { + var j = exponent ? value.lastIndexOf("e") : -1; + if (j < 0) before = value, after = ""; else before = value.substring(0, j), after = value.substring(j); + } else { + before = value.substring(0, i); + after = locale_decimal + value.substring(i + 1); + } + if (!zfill && comma) before = formatGroup(before, Infinity); + var length = prefix.length + before.length + after.length + (zcomma ? 0 : negative.length), padding = length < width ? new Array(length = width - length + 1).join(fill) : ""; + if (zcomma) before = formatGroup(padding + before, padding.length ? width - after.length : Infinity); + negative += prefix; + value = before + after; + return (align === "<" ? negative + value + padding : align === ">" ? padding + negative + value : align === "^" ? padding.substring(0, length >>= 1) + negative + value + padding.substring(length) : negative + (zcomma ? value : padding + value)) + fullSuffix; + }; + }; + } + var d3_format_re = /(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i; + var d3_format_types = d3.map({ + b: function(x) { + return x.toString(2); + }, + c: function(x) { + return String.fromCharCode(x); + }, + o: function(x) { + return x.toString(8); + }, + x: function(x) { + return x.toString(16); + }, + X: function(x) { + return x.toString(16).toUpperCase(); + }, + g: function(x, p) { + return x.toPrecision(p); + }, + e: function(x, p) { + return x.toExponential(p); + }, + f: function(x, p) { + return x.toFixed(p); + }, + r: function(x, p) { + return (x = d3.round(x, d3_format_precision(x, p))).toFixed(Math.max(0, Math.min(20, d3_format_precision(x * (1 + 1e-15), p)))); + } + }); + function d3_format_typeDefault(x) { + return x + ""; + } + var d3_time = d3.time = {}, d3_date = Date; + function d3_date_utc() { + this._ = new Date(arguments.length > 1 ? Date.UTC.apply(this, arguments) : arguments[0]); + } + d3_date_utc.prototype = { + getDate: function() { + return this._.getUTCDate(); + }, + getDay: function() { + return this._.getUTCDay(); + }, + getFullYear: function() { + return this._.getUTCFullYear(); + }, + getHours: function() { + return this._.getUTCHours(); + }, + getMilliseconds: function() { + return this._.getUTCMilliseconds(); + }, + getMinutes: function() { + return this._.getUTCMinutes(); + }, + getMonth: function() { + return this._.getUTCMonth(); + }, + getSeconds: function() { + return this._.getUTCSeconds(); + }, + getTime: function() { + return this._.getTime(); + }, + getTimezoneOffset: function() { + return 0; + }, + valueOf: function() { + return this._.valueOf(); + }, + setDate: function() { + d3_time_prototype.setUTCDate.apply(this._, arguments); + }, + setDay: function() { + d3_time_prototype.setUTCDay.apply(this._, arguments); + }, + setFullYear: function() { + d3_time_prototype.setUTCFullYear.apply(this._, arguments); + }, + setHours: function() { + d3_time_prototype.setUTCHours.apply(this._, arguments); + }, + setMilliseconds: function() { + d3_time_prototype.setUTCMilliseconds.apply(this._, arguments); + }, + setMinutes: function() { + d3_time_prototype.setUTCMinutes.apply(this._, arguments); + }, + setMonth: function() { + d3_time_prototype.setUTCMonth.apply(this._, arguments); + }, + setSeconds: function() { + d3_time_prototype.setUTCSeconds.apply(this._, arguments); + }, + setTime: function() { + d3_time_prototype.setTime.apply(this._, arguments); + } + }; + var d3_time_prototype = Date.prototype; + function d3_time_interval(local, step, number) { + function round(date) { + var d0 = local(date), d1 = offset(d0, 1); + return date - d0 < d1 - date ? d0 : d1; + } + function ceil(date) { + step(date = local(new d3_date(date - 1)), 1); + return date; + } + function offset(date, k) { + step(date = new d3_date(+date), k); + return date; + } + function range(t0, t1, dt) { + var time = ceil(t0), times = []; + if (dt > 1) { + while (time < t1) { + if (!(number(time) % dt)) times.push(new Date(+time)); + step(time, 1); + } + } else { + while (time < t1) times.push(new Date(+time)), step(time, 1); + } + return times; + } + function range_utc(t0, t1, dt) { + try { + d3_date = d3_date_utc; + var utc = new d3_date_utc(); + utc._ = t0; + return range(utc, t1, dt); + } finally { + d3_date = Date; + } + } + local.floor = local; + local.round = round; + local.ceil = ceil; + local.offset = offset; + local.range = range; + var utc = local.utc = d3_time_interval_utc(local); + utc.floor = utc; + utc.round = d3_time_interval_utc(round); + utc.ceil = d3_time_interval_utc(ceil); + utc.offset = d3_time_interval_utc(offset); + utc.range = range_utc; + return local; + } + function d3_time_interval_utc(method) { + return function(date, k) { + try { + d3_date = d3_date_utc; + var utc = new d3_date_utc(); + utc._ = date; + return method(utc, k)._; + } finally { + d3_date = Date; + } + }; + } + d3_time.year = d3_time_interval(function(date) { + date = d3_time.day(date); + date.setMonth(0, 1); + return date; + }, function(date, offset) { + date.setFullYear(date.getFullYear() + offset); + }, function(date) { + return date.getFullYear(); + }); + d3_time.years = d3_time.year.range; + d3_time.years.utc = d3_time.year.utc.range; + d3_time.day = d3_time_interval(function(date) { + var day = new d3_date(2e3, 0); + day.setFullYear(date.getFullYear(), date.getMonth(), date.getDate()); + return day; + }, function(date, offset) { + date.setDate(date.getDate() + offset); + }, function(date) { + return date.getDate() - 1; + }); + d3_time.days = d3_time.day.range; + d3_time.days.utc = d3_time.day.utc.range; + d3_time.dayOfYear = function(date) { + var year = d3_time.year(date); + return Math.floor((date - year - (date.getTimezoneOffset() - year.getTimezoneOffset()) * 6e4) / 864e5); + }; + [ "sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday" ].forEach(function(day, i) { + i = 7 - i; + var interval = d3_time[day] = d3_time_interval(function(date) { + (date = d3_time.day(date)).setDate(date.getDate() - (date.getDay() + i) % 7); + return date; + }, function(date, offset) { + date.setDate(date.getDate() + Math.floor(offset) * 7); + }, function(date) { + var day = d3_time.year(date).getDay(); + return Math.floor((d3_time.dayOfYear(date) + (day + i) % 7) / 7) - (day !== i); + }); + d3_time[day + "s"] = interval.range; + d3_time[day + "s"].utc = interval.utc.range; + d3_time[day + "OfYear"] = function(date) { + var day = d3_time.year(date).getDay(); + return Math.floor((d3_time.dayOfYear(date) + (day + i) % 7) / 7); + }; + }); + d3_time.week = d3_time.sunday; + d3_time.weeks = d3_time.sunday.range; + d3_time.weeks.utc = d3_time.sunday.utc.range; + d3_time.weekOfYear = d3_time.sundayOfYear; + function d3_locale_timeFormat(locale) { + var locale_dateTime = locale.dateTime, locale_date = locale.date, locale_time = locale.time, locale_periods = locale.periods, locale_days = locale.days, locale_shortDays = locale.shortDays, locale_months = locale.months, locale_shortMonths = locale.shortMonths; + function d3_time_format(template) { + var n = template.length; + function format(date) { + var string = [], i = -1, j = 0, c, p, f; + while (++i < n) { + if (template.charCodeAt(i) === 37) { + string.push(template.slice(j, i)); + if ((p = d3_time_formatPads[c = template.charAt(++i)]) != null) c = template.charAt(++i); + if (f = d3_time_formats[c]) c = f(date, p == null ? c === "e" ? " " : "0" : p); + string.push(c); + j = i + 1; + } + } + string.push(template.slice(j, i)); + return string.join(""); + } + format.parse = function(string) { + var d = { + y: 1900, + m: 0, + d: 1, + H: 0, + M: 0, + S: 0, + L: 0, + Z: null + }, i = d3_time_parse(d, template, string, 0); + if (i != string.length) return null; + if ("p" in d) d.H = d.H % 12 + d.p * 12; + var localZ = d.Z != null && d3_date !== d3_date_utc, date = new (localZ ? d3_date_utc : d3_date)(); + if ("j" in d) date.setFullYear(d.y, 0, d.j); else if ("W" in d || "U" in d) { + if (!("w" in d)) d.w = "W" in d ? 1 : 0; + date.setFullYear(d.y, 0, 1); + date.setFullYear(d.y, 0, "W" in d ? (d.w + 6) % 7 + d.W * 7 - (date.getDay() + 5) % 7 : d.w + d.U * 7 - (date.getDay() + 6) % 7); + } else date.setFullYear(d.y, d.m, d.d); + date.setHours(d.H + (d.Z / 100 | 0), d.M + d.Z % 100, d.S, d.L); + return localZ ? date._ : date; + }; + format.toString = function() { + return template; + }; + return format; + } + function d3_time_parse(date, template, string, j) { + var c, p, t, i = 0, n = template.length, m = string.length; + while (i < n) { + if (j >= m) return -1; + c = template.charCodeAt(i++); + if (c === 37) { + t = template.charAt(i++); + p = d3_time_parsers[t in d3_time_formatPads ? template.charAt(i++) : t]; + if (!p || (j = p(date, string, j)) < 0) return -1; + } else if (c != string.charCodeAt(j++)) { + return -1; + } + } + return j; + } + d3_time_format.utc = function(template) { + var local = d3_time_format(template); + function format(date) { + try { + d3_date = d3_date_utc; + var utc = new d3_date(); + utc._ = date; + return local(utc); + } finally { + d3_date = Date; + } + } + format.parse = function(string) { + try { + d3_date = d3_date_utc; + var date = local.parse(string); + return date && date._; + } finally { + d3_date = Date; + } + }; + format.toString = local.toString; + return format; + }; + d3_time_format.multi = d3_time_format.utc.multi = d3_time_formatMulti; + var d3_time_periodLookup = d3.map(), d3_time_dayRe = d3_time_formatRe(locale_days), d3_time_dayLookup = d3_time_formatLookup(locale_days), d3_time_dayAbbrevRe = d3_time_formatRe(locale_shortDays), d3_time_dayAbbrevLookup = d3_time_formatLookup(locale_shortDays), d3_time_monthRe = d3_time_formatRe(locale_months), d3_time_monthLookup = d3_time_formatLookup(locale_months), d3_time_monthAbbrevRe = d3_time_formatRe(locale_shortMonths), d3_time_monthAbbrevLookup = d3_time_formatLookup(locale_shortMonths); + locale_periods.forEach(function(p, i) { + d3_time_periodLookup.set(p.toLowerCase(), i); + }); + var d3_time_formats = { + a: function(d) { + return locale_shortDays[d.getDay()]; + }, + A: function(d) { + return locale_days[d.getDay()]; + }, + b: function(d) { + return locale_shortMonths[d.getMonth()]; + }, + B: function(d) { + return locale_months[d.getMonth()]; + }, + c: d3_time_format(locale_dateTime), + d: function(d, p) { + return d3_time_formatPad(d.getDate(), p, 2); + }, + e: function(d, p) { + return d3_time_formatPad(d.getDate(), p, 2); + }, + H: function(d, p) { + return d3_time_formatPad(d.getHours(), p, 2); + }, + I: function(d, p) { + return d3_time_formatPad(d.getHours() % 12 || 12, p, 2); + }, + j: function(d, p) { + return d3_time_formatPad(1 + d3_time.dayOfYear(d), p, 3); + }, + L: function(d, p) { + return d3_time_formatPad(d.getMilliseconds(), p, 3); + }, + m: function(d, p) { + return d3_time_formatPad(d.getMonth() + 1, p, 2); + }, + M: function(d, p) { + return d3_time_formatPad(d.getMinutes(), p, 2); + }, + p: function(d) { + return locale_periods[+(d.getHours() >= 12)]; + }, + S: function(d, p) { + return d3_time_formatPad(d.getSeconds(), p, 2); + }, + U: function(d, p) { + return d3_time_formatPad(d3_time.sundayOfYear(d), p, 2); + }, + w: function(d) { + return d.getDay(); + }, + W: function(d, p) { + return d3_time_formatPad(d3_time.mondayOfYear(d), p, 2); + }, + x: d3_time_format(locale_date), + X: d3_time_format(locale_time), + y: function(d, p) { + return d3_time_formatPad(d.getFullYear() % 100, p, 2); + }, + Y: function(d, p) { + return d3_time_formatPad(d.getFullYear() % 1e4, p, 4); + }, + Z: d3_time_zone, + "%": function() { + return "%"; + } + }; + var d3_time_parsers = { + a: d3_time_parseWeekdayAbbrev, + A: d3_time_parseWeekday, + b: d3_time_parseMonthAbbrev, + B: d3_time_parseMonth, + c: d3_time_parseLocaleFull, + d: d3_time_parseDay, + e: d3_time_parseDay, + H: d3_time_parseHour24, + I: d3_time_parseHour24, + j: d3_time_parseDayOfYear, + L: d3_time_parseMilliseconds, + m: d3_time_parseMonthNumber, + M: d3_time_parseMinutes, + p: d3_time_parseAmPm, + S: d3_time_parseSeconds, + U: d3_time_parseWeekNumberSunday, + w: d3_time_parseWeekdayNumber, + W: d3_time_parseWeekNumberMonday, + x: d3_time_parseLocaleDate, + X: d3_time_parseLocaleTime, + y: d3_time_parseYear, + Y: d3_time_parseFullYear, + Z: d3_time_parseZone, + "%": d3_time_parseLiteralPercent + }; + function d3_time_parseWeekdayAbbrev(date, string, i) { + d3_time_dayAbbrevRe.lastIndex = 0; + var n = d3_time_dayAbbrevRe.exec(string.slice(i)); + return n ? (date.w = d3_time_dayAbbrevLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; + } + function d3_time_parseWeekday(date, string, i) { + d3_time_dayRe.lastIndex = 0; + var n = d3_time_dayRe.exec(string.slice(i)); + return n ? (date.w = d3_time_dayLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; + } + function d3_time_parseMonthAbbrev(date, string, i) { + d3_time_monthAbbrevRe.lastIndex = 0; + var n = d3_time_monthAbbrevRe.exec(string.slice(i)); + return n ? (date.m = d3_time_monthAbbrevLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; + } + function d3_time_parseMonth(date, string, i) { + d3_time_monthRe.lastIndex = 0; + var n = d3_time_monthRe.exec(string.slice(i)); + return n ? (date.m = d3_time_monthLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; + } + function d3_time_parseLocaleFull(date, string, i) { + return d3_time_parse(date, d3_time_formats.c.toString(), string, i); + } + function d3_time_parseLocaleDate(date, string, i) { + return d3_time_parse(date, d3_time_formats.x.toString(), string, i); + } + function d3_time_parseLocaleTime(date, string, i) { + return d3_time_parse(date, d3_time_formats.X.toString(), string, i); + } + function d3_time_parseAmPm(date, string, i) { + var n = d3_time_periodLookup.get(string.slice(i, i += 2).toLowerCase()); + return n == null ? -1 : (date.p = n, i); + } + return d3_time_format; + } + var d3_time_formatPads = { + "-": "", + _: " ", + "0": "0" + }, d3_time_numberRe = /^\s*\d+/, d3_time_percentRe = /^%/; + function d3_time_formatPad(value, fill, width) { + var sign = value < 0 ? "-" : "", string = (sign ? -value : value) + "", length = string.length; + return sign + (length < width ? new Array(width - length + 1).join(fill) + string : string); + } + function d3_time_formatRe(names) { + return new RegExp("^(?:" + names.map(d3.requote).join("|") + ")", "i"); + } + function d3_time_formatLookup(names) { + var map = new d3_Map(), i = -1, n = names.length; + while (++i < n) map.set(names[i].toLowerCase(), i); + return map; + } + function d3_time_parseWeekdayNumber(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 1)); + return n ? (date.w = +n[0], i + n[0].length) : -1; + } + function d3_time_parseWeekNumberSunday(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i)); + return n ? (date.U = +n[0], i + n[0].length) : -1; + } + function d3_time_parseWeekNumberMonday(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i)); + return n ? (date.W = +n[0], i + n[0].length) : -1; + } + function d3_time_parseFullYear(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 4)); + return n ? (date.y = +n[0], i + n[0].length) : -1; + } + function d3_time_parseYear(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 2)); + return n ? (date.y = d3_time_expandYear(+n[0]), i + n[0].length) : -1; + } + function d3_time_parseZone(date, string, i) { + return /^[+-]\d{4}$/.test(string = string.slice(i, i + 5)) ? (date.Z = -string, + i + 5) : -1; + } + function d3_time_expandYear(d) { + return d + (d > 68 ? 1900 : 2e3); + } + function d3_time_parseMonthNumber(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 2)); + return n ? (date.m = n[0] - 1, i + n[0].length) : -1; + } + function d3_time_parseDay(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 2)); + return n ? (date.d = +n[0], i + n[0].length) : -1; + } + function d3_time_parseDayOfYear(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 3)); + return n ? (date.j = +n[0], i + n[0].length) : -1; + } + function d3_time_parseHour24(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 2)); + return n ? (date.H = +n[0], i + n[0].length) : -1; + } + function d3_time_parseMinutes(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 2)); + return n ? (date.M = +n[0], i + n[0].length) : -1; + } + function d3_time_parseSeconds(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 2)); + return n ? (date.S = +n[0], i + n[0].length) : -1; + } + function d3_time_parseMilliseconds(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 3)); + return n ? (date.L = +n[0], i + n[0].length) : -1; + } + function d3_time_zone(d) { + var z = d.getTimezoneOffset(), zs = z > 0 ? "-" : "+", zh = abs(z) / 60 | 0, zm = abs(z) % 60; + return zs + d3_time_formatPad(zh, "0", 2) + d3_time_formatPad(zm, "0", 2); + } + function d3_time_parseLiteralPercent(date, string, i) { + d3_time_percentRe.lastIndex = 0; + var n = d3_time_percentRe.exec(string.slice(i, i + 1)); + return n ? i + n[0].length : -1; + } + function d3_time_formatMulti(formats) { + var n = formats.length, i = -1; + while (++i < n) formats[i][0] = this(formats[i][0]); + return function(date) { + var i = 0, f = formats[i]; + while (!f[1](date)) f = formats[++i]; + return f[0](date); + }; + } + d3.locale = function(locale) { + return { + numberFormat: d3_locale_numberFormat(locale), + timeFormat: d3_locale_timeFormat(locale) + }; + }; + var d3_locale_enUS = d3.locale({ + decimal: ".", + thousands: ",", + grouping: [ 3 ], + currency: [ "$", "" ], + dateTime: "%a %b %e %X %Y", + date: "%m/%d/%Y", + time: "%H:%M:%S", + periods: [ "AM", "PM" ], + days: [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ], + shortDays: [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ], + months: [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ], + shortMonths: [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ] + }); + d3.format = d3_locale_enUS.numberFormat; + d3.geo = {}; + function d3_adder() {} + d3_adder.prototype = { + s: 0, + t: 0, + add: function(y) { + d3_adderSum(y, this.t, d3_adderTemp); + d3_adderSum(d3_adderTemp.s, this.s, this); + if (this.s) this.t += d3_adderTemp.t; else this.s = d3_adderTemp.t; + }, + reset: function() { + this.s = this.t = 0; + }, + valueOf: function() { + return this.s; + } + }; + var d3_adderTemp = new d3_adder(); + function d3_adderSum(a, b, o) { + var x = o.s = a + b, bv = x - a, av = x - bv; + o.t = a - av + (b - bv); + } + d3.geo.stream = function(object, listener) { + if (object && d3_geo_streamObjectType.hasOwnProperty(object.type)) { + d3_geo_streamObjectType[object.type](object, listener); + } else { + d3_geo_streamGeometry(object, listener); + } + }; + function d3_geo_streamGeometry(geometry, listener) { + if (geometry && d3_geo_streamGeometryType.hasOwnProperty(geometry.type)) { + d3_geo_streamGeometryType[geometry.type](geometry, listener); + } + } + var d3_geo_streamObjectType = { + Feature: function(feature, listener) { + d3_geo_streamGeometry(feature.geometry, listener); + }, + FeatureCollection: function(object, listener) { + var features = object.features, i = -1, n = features.length; + while (++i < n) d3_geo_streamGeometry(features[i].geometry, listener); + } + }; + var d3_geo_streamGeometryType = { + Sphere: function(object, listener) { + listener.sphere(); + }, + Point: function(object, listener) { + object = object.coordinates; + listener.point(object[0], object[1], object[2]); + }, + MultiPoint: function(object, listener) { + var coordinates = object.coordinates, i = -1, n = coordinates.length; + while (++i < n) object = coordinates[i], listener.point(object[0], object[1], object[2]); + }, + LineString: function(object, listener) { + d3_geo_streamLine(object.coordinates, listener, 0); + }, + MultiLineString: function(object, listener) { + var coordinates = object.coordinates, i = -1, n = coordinates.length; + while (++i < n) d3_geo_streamLine(coordinates[i], listener, 0); + }, + Polygon: function(object, listener) { + d3_geo_streamPolygon(object.coordinates, listener); + }, + MultiPolygon: function(object, listener) { + var coordinates = object.coordinates, i = -1, n = coordinates.length; + while (++i < n) d3_geo_streamPolygon(coordinates[i], listener); + }, + GeometryCollection: function(object, listener) { + var geometries = object.geometries, i = -1, n = geometries.length; + while (++i < n) d3_geo_streamGeometry(geometries[i], listener); + } + }; + function d3_geo_streamLine(coordinates, listener, closed) { + var i = -1, n = coordinates.length - closed, coordinate; + listener.lineStart(); + while (++i < n) coordinate = coordinates[i], listener.point(coordinate[0], coordinate[1], coordinate[2]); + listener.lineEnd(); + } + function d3_geo_streamPolygon(coordinates, listener) { + var i = -1, n = coordinates.length; + listener.polygonStart(); + while (++i < n) d3_geo_streamLine(coordinates[i], listener, 1); + listener.polygonEnd(); + } + d3.geo.area = function(object) { + d3_geo_areaSum = 0; + d3.geo.stream(object, d3_geo_area); + return d3_geo_areaSum; + }; + var d3_geo_areaSum, d3_geo_areaRingSum = new d3_adder(); + var d3_geo_area = { + sphere: function() { + d3_geo_areaSum += 4 * π; + }, + point: d3_noop, + lineStart: d3_noop, + lineEnd: d3_noop, + polygonStart: function() { + d3_geo_areaRingSum.reset(); + d3_geo_area.lineStart = d3_geo_areaRingStart; + }, + polygonEnd: function() { + var area = 2 * d3_geo_areaRingSum; + d3_geo_areaSum += area < 0 ? 4 * π + area : area; + d3_geo_area.lineStart = d3_geo_area.lineEnd = d3_geo_area.point = d3_noop; + } + }; + function d3_geo_areaRingStart() { + var λ00, φ00, λ0, cosφ0, sinφ0; + d3_geo_area.point = function(λ, φ) { + d3_geo_area.point = nextPoint; + λ0 = (λ00 = λ) * d3_radians, cosφ0 = Math.cos(φ = (φ00 = φ) * d3_radians / 2 + π / 4), + sinφ0 = Math.sin(φ); + }; + function nextPoint(λ, φ) { + λ *= d3_radians; + φ = φ * d3_radians / 2 + π / 4; + var dλ = λ - λ0, sdλ = dλ >= 0 ? 1 : -1, adλ = sdλ * dλ, cosφ = Math.cos(φ), sinφ = Math.sin(φ), k = sinφ0 * sinφ, u = cosφ0 * cosφ + k * Math.cos(adλ), v = k * sdλ * Math.sin(adλ); + d3_geo_areaRingSum.add(Math.atan2(v, u)); + λ0 = λ, cosφ0 = cosφ, sinφ0 = sinφ; + } + d3_geo_area.lineEnd = function() { + nextPoint(λ00, φ00); + }; + } + function d3_geo_cartesian(spherical) { + var λ = spherical[0], φ = spherical[1], cosφ = Math.cos(φ); + return [ cosφ * Math.cos(λ), cosφ * Math.sin(λ), Math.sin(φ) ]; + } + function d3_geo_cartesianDot(a, b) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; + } + function d3_geo_cartesianCross(a, b) { + return [ a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0] ]; + } + function d3_geo_cartesianAdd(a, b) { + a[0] += b[0]; + a[1] += b[1]; + a[2] += b[2]; + } + function d3_geo_cartesianScale(vector, k) { + return [ vector[0] * k, vector[1] * k, vector[2] * k ]; + } + function d3_geo_cartesianNormalize(d) { + var l = Math.sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]); + d[0] /= l; + d[1] /= l; + d[2] /= l; + } + function d3_geo_spherical(cartesian) { + return [ Math.atan2(cartesian[1], cartesian[0]), d3_asin(cartesian[2]) ]; + } + function d3_geo_sphericalEqual(a, b) { + return abs(a[0] - b[0]) < ε && abs(a[1] - b[1]) < ε; + } + d3.geo.bounds = function() { + var λ0, φ0, λ1, φ1, λ_, λ__, φ__, p0, dλSum, ranges, range; + var bound = { + point: point, + lineStart: lineStart, + lineEnd: lineEnd, + polygonStart: function() { + bound.point = ringPoint; + bound.lineStart = ringStart; + bound.lineEnd = ringEnd; + dλSum = 0; + d3_geo_area.polygonStart(); + }, + polygonEnd: function() { + d3_geo_area.polygonEnd(); + bound.point = point; + bound.lineStart = lineStart; + bound.lineEnd = lineEnd; + if (d3_geo_areaRingSum < 0) λ0 = -(λ1 = 180), φ0 = -(φ1 = 90); else if (dλSum > ε) φ1 = 90; else if (dλSum < -ε) φ0 = -90; + range[0] = λ0, range[1] = λ1; + } + }; + function point(λ, φ) { + ranges.push(range = [ λ0 = λ, λ1 = λ ]); + if (φ < φ0) φ0 = φ; + if (φ > φ1) φ1 = φ; + } + function linePoint(λ, φ) { + var p = d3_geo_cartesian([ λ * d3_radians, φ * d3_radians ]); + if (p0) { + var normal = d3_geo_cartesianCross(p0, p), equatorial = [ normal[1], -normal[0], 0 ], inflection = d3_geo_cartesianCross(equatorial, normal); + d3_geo_cartesianNormalize(inflection); + inflection = d3_geo_spherical(inflection); + var dλ = λ - λ_, s = dλ > 0 ? 1 : -1, λi = inflection[0] * d3_degrees * s, antimeridian = abs(dλ) > 180; + if (antimeridian ^ (s * λ_ < λi && λi < s * λ)) { + var φi = inflection[1] * d3_degrees; + if (φi > φ1) φ1 = φi; + } else if (λi = (λi + 360) % 360 - 180, antimeridian ^ (s * λ_ < λi && λi < s * λ)) { + var φi = -inflection[1] * d3_degrees; + if (φi < φ0) φ0 = φi; + } else { + if (φ < φ0) φ0 = φ; + if (φ > φ1) φ1 = φ; + } + if (antimeridian) { + if (λ < λ_) { + if (angle(λ0, λ) > angle(λ0, λ1)) λ1 = λ; + } else { + if (angle(λ, λ1) > angle(λ0, λ1)) λ0 = λ; + } + } else { + if (λ1 >= λ0) { + if (λ < λ0) λ0 = λ; + if (λ > λ1) λ1 = λ; + } else { + if (λ > λ_) { + if (angle(λ0, λ) > angle(λ0, λ1)) λ1 = λ; + } else { + if (angle(λ, λ1) > angle(λ0, λ1)) λ0 = λ; + } + } + } + } else { + point(λ, φ); + } + p0 = p, λ_ = λ; + } + function lineStart() { + bound.point = linePoint; + } + function lineEnd() { + range[0] = λ0, range[1] = λ1; + bound.point = point; + p0 = null; + } + function ringPoint(λ, φ) { + if (p0) { + var dλ = λ - λ_; + dλSum += abs(dλ) > 180 ? dλ + (dλ > 0 ? 360 : -360) : dλ; + } else λ__ = λ, φ__ = φ; + d3_geo_area.point(λ, φ); + linePoint(λ, φ); + } + function ringStart() { + d3_geo_area.lineStart(); + } + function ringEnd() { + ringPoint(λ__, φ__); + d3_geo_area.lineEnd(); + if (abs(dλSum) > ε) λ0 = -(λ1 = 180); + range[0] = λ0, range[1] = λ1; + p0 = null; + } + function angle(λ0, λ1) { + return (λ1 -= λ0) < 0 ? λ1 + 360 : λ1; + } + function compareRanges(a, b) { + return a[0] - b[0]; + } + function withinRange(x, range) { + return range[0] <= range[1] ? range[0] <= x && x <= range[1] : x < range[0] || range[1] < x; + } + return function(feature) { + φ1 = λ1 = -(λ0 = φ0 = Infinity); + ranges = []; + d3.geo.stream(feature, bound); + var n = ranges.length; + if (n) { + ranges.sort(compareRanges); + for (var i = 1, a = ranges[0], b, merged = [ a ]; i < n; ++i) { + b = ranges[i]; + if (withinRange(b[0], a) || withinRange(b[1], a)) { + if (angle(a[0], b[1]) > angle(a[0], a[1])) a[1] = b[1]; + if (angle(b[0], a[1]) > angle(a[0], a[1])) a[0] = b[0]; + } else { + merged.push(a = b); + } + } + var best = -Infinity, dλ; + for (var n = merged.length - 1, i = 0, a = merged[n], b; i <= n; a = b, ++i) { + b = merged[i]; + if ((dλ = angle(a[1], b[0])) > best) best = dλ, λ0 = b[0], λ1 = a[1]; + } + } + ranges = range = null; + return λ0 === Infinity || φ0 === Infinity ? [ [ NaN, NaN ], [ NaN, NaN ] ] : [ [ λ0, φ0 ], [ λ1, φ1 ] ]; + }; + }(); + d3.geo.centroid = function(object) { + d3_geo_centroidW0 = d3_geo_centroidW1 = d3_geo_centroidX0 = d3_geo_centroidY0 = d3_geo_centroidZ0 = d3_geo_centroidX1 = d3_geo_centroidY1 = d3_geo_centroidZ1 = d3_geo_centroidX2 = d3_geo_centroidY2 = d3_geo_centroidZ2 = 0; + d3.geo.stream(object, d3_geo_centroid); + var x = d3_geo_centroidX2, y = d3_geo_centroidY2, z = d3_geo_centroidZ2, m = x * x + y * y + z * z; + if (m < ε2) { + x = d3_geo_centroidX1, y = d3_geo_centroidY1, z = d3_geo_centroidZ1; + if (d3_geo_centroidW1 < ε) x = d3_geo_centroidX0, y = d3_geo_centroidY0, z = d3_geo_centroidZ0; + m = x * x + y * y + z * z; + if (m < ε2) return [ NaN, NaN ]; + } + return [ Math.atan2(y, x) * d3_degrees, d3_asin(z / Math.sqrt(m)) * d3_degrees ]; + }; + var d3_geo_centroidW0, d3_geo_centroidW1, d3_geo_centroidX0, d3_geo_centroidY0, d3_geo_centroidZ0, d3_geo_centroidX1, d3_geo_centroidY1, d3_geo_centroidZ1, d3_geo_centroidX2, d3_geo_centroidY2, d3_geo_centroidZ2; + var d3_geo_centroid = { + sphere: d3_noop, + point: d3_geo_centroidPoint, + lineStart: d3_geo_centroidLineStart, + lineEnd: d3_geo_centroidLineEnd, + polygonStart: function() { + d3_geo_centroid.lineStart = d3_geo_centroidRingStart; + }, + polygonEnd: function() { + d3_geo_centroid.lineStart = d3_geo_centroidLineStart; + } + }; + function d3_geo_centroidPoint(λ, φ) { + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians); + d3_geo_centroidPointXYZ(cosφ * Math.cos(λ), cosφ * Math.sin(λ), Math.sin(φ)); + } + function d3_geo_centroidPointXYZ(x, y, z) { + ++d3_geo_centroidW0; + d3_geo_centroidX0 += (x - d3_geo_centroidX0) / d3_geo_centroidW0; + d3_geo_centroidY0 += (y - d3_geo_centroidY0) / d3_geo_centroidW0; + d3_geo_centroidZ0 += (z - d3_geo_centroidZ0) / d3_geo_centroidW0; + } + function d3_geo_centroidLineStart() { + var x0, y0, z0; + d3_geo_centroid.point = function(λ, φ) { + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians); + x0 = cosφ * Math.cos(λ); + y0 = cosφ * Math.sin(λ); + z0 = Math.sin(φ); + d3_geo_centroid.point = nextPoint; + d3_geo_centroidPointXYZ(x0, y0, z0); + }; + function nextPoint(λ, φ) { + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians), x = cosφ * Math.cos(λ), y = cosφ * Math.sin(λ), z = Math.sin(φ), w = Math.atan2(Math.sqrt((w = y0 * z - z0 * y) * w + (w = z0 * x - x0 * z) * w + (w = x0 * y - y0 * x) * w), x0 * x + y0 * y + z0 * z); + d3_geo_centroidW1 += w; + d3_geo_centroidX1 += w * (x0 + (x0 = x)); + d3_geo_centroidY1 += w * (y0 + (y0 = y)); + d3_geo_centroidZ1 += w * (z0 + (z0 = z)); + d3_geo_centroidPointXYZ(x0, y0, z0); + } + } + function d3_geo_centroidLineEnd() { + d3_geo_centroid.point = d3_geo_centroidPoint; + } + function d3_geo_centroidRingStart() { + var λ00, φ00, x0, y0, z0; + d3_geo_centroid.point = function(λ, φ) { + λ00 = λ, φ00 = φ; + d3_geo_centroid.point = nextPoint; + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians); + x0 = cosφ * Math.cos(λ); + y0 = cosφ * Math.sin(λ); + z0 = Math.sin(φ); + d3_geo_centroidPointXYZ(x0, y0, z0); + }; + d3_geo_centroid.lineEnd = function() { + nextPoint(λ00, φ00); + d3_geo_centroid.lineEnd = d3_geo_centroidLineEnd; + d3_geo_centroid.point = d3_geo_centroidPoint; + }; + function nextPoint(λ, φ) { + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians), x = cosφ * Math.cos(λ), y = cosφ * Math.sin(λ), z = Math.sin(φ), cx = y0 * z - z0 * y, cy = z0 * x - x0 * z, cz = x0 * y - y0 * x, m = Math.sqrt(cx * cx + cy * cy + cz * cz), u = x0 * x + y0 * y + z0 * z, v = m && -d3_acos(u) / m, w = Math.atan2(m, u); + d3_geo_centroidX2 += v * cx; + d3_geo_centroidY2 += v * cy; + d3_geo_centroidZ2 += v * cz; + d3_geo_centroidW1 += w; + d3_geo_centroidX1 += w * (x0 + (x0 = x)); + d3_geo_centroidY1 += w * (y0 + (y0 = y)); + d3_geo_centroidZ1 += w * (z0 + (z0 = z)); + d3_geo_centroidPointXYZ(x0, y0, z0); + } + } + function d3_geo_compose(a, b) { + function compose(x, y) { + return x = a(x, y), b(x[0], x[1]); + } + if (a.invert && b.invert) compose.invert = function(x, y) { + return x = b.invert(x, y), x && a.invert(x[0], x[1]); + }; + return compose; + } + function d3_true() { + return true; + } + function d3_geo_clipPolygon(segments, compare, clipStartInside, interpolate, listener) { + var subject = [], clip = []; + segments.forEach(function(segment) { + if ((n = segment.length - 1) <= 0) return; + var n, p0 = segment[0], p1 = segment[n]; + if (d3_geo_sphericalEqual(p0, p1)) { + listener.lineStart(); + for (var i = 0; i < n; ++i) listener.point((p0 = segment[i])[0], p0[1]); + listener.lineEnd(); + return; + } + var a = new d3_geo_clipPolygonIntersection(p0, segment, null, true), b = new d3_geo_clipPolygonIntersection(p0, null, a, false); + a.o = b; + subject.push(a); + clip.push(b); + a = new d3_geo_clipPolygonIntersection(p1, segment, null, false); + b = new d3_geo_clipPolygonIntersection(p1, null, a, true); + a.o = b; + subject.push(a); + clip.push(b); + }); + clip.sort(compare); + d3_geo_clipPolygonLinkCircular(subject); + d3_geo_clipPolygonLinkCircular(clip); + if (!subject.length) return; + for (var i = 0, entry = clipStartInside, n = clip.length; i < n; ++i) { + clip[i].e = entry = !entry; + } + var start = subject[0], points, point; + while (1) { + var current = start, isSubject = true; + while (current.v) if ((current = current.n) === start) return; + points = current.z; + listener.lineStart(); + do { + current.v = current.o.v = true; + if (current.e) { + if (isSubject) { + for (var i = 0, n = points.length; i < n; ++i) listener.point((point = points[i])[0], point[1]); + } else { + interpolate(current.x, current.n.x, 1, listener); + } + current = current.n; + } else { + if (isSubject) { + points = current.p.z; + for (var i = points.length - 1; i >= 0; --i) listener.point((point = points[i])[0], point[1]); + } else { + interpolate(current.x, current.p.x, -1, listener); + } + current = current.p; + } + current = current.o; + points = current.z; + isSubject = !isSubject; + } while (!current.v); + listener.lineEnd(); + } + } + function d3_geo_clipPolygonLinkCircular(array) { + if (!(n = array.length)) return; + var n, i = 0, a = array[0], b; + while (++i < n) { + a.n = b = array[i]; + b.p = a; + a = b; + } + a.n = b = array[0]; + b.p = a; + } + function d3_geo_clipPolygonIntersection(point, points, other, entry) { + this.x = point; + this.z = points; + this.o = other; + this.e = entry; + this.v = false; + this.n = this.p = null; + } + function d3_geo_clip(pointVisible, clipLine, interpolate, clipStart) { + return function(rotate, listener) { + var line = clipLine(listener), rotatedClipStart = rotate.invert(clipStart[0], clipStart[1]); + var clip = { + point: point, + lineStart: lineStart, + lineEnd: lineEnd, + polygonStart: function() { + clip.point = pointRing; + clip.lineStart = ringStart; + clip.lineEnd = ringEnd; + segments = []; + polygon = []; + }, + polygonEnd: function() { + clip.point = point; + clip.lineStart = lineStart; + clip.lineEnd = lineEnd; + segments = d3.merge(segments); + var clipStartInside = d3_geo_pointInPolygon(rotatedClipStart, polygon); + if (segments.length) { + if (!polygonStarted) listener.polygonStart(), polygonStarted = true; + d3_geo_clipPolygon(segments, d3_geo_clipSort, clipStartInside, interpolate, listener); + } else if (clipStartInside) { + if (!polygonStarted) listener.polygonStart(), polygonStarted = true; + listener.lineStart(); + interpolate(null, null, 1, listener); + listener.lineEnd(); + } + if (polygonStarted) listener.polygonEnd(), polygonStarted = false; + segments = polygon = null; + }, + sphere: function() { + listener.polygonStart(); + listener.lineStart(); + interpolate(null, null, 1, listener); + listener.lineEnd(); + listener.polygonEnd(); + } + }; + function point(λ, φ) { + var point = rotate(λ, φ); + if (pointVisible(λ = point[0], φ = point[1])) listener.point(λ, φ); + } + function pointLine(λ, φ) { + var point = rotate(λ, φ); + line.point(point[0], point[1]); + } + function lineStart() { + clip.point = pointLine; + line.lineStart(); + } + function lineEnd() { + clip.point = point; + line.lineEnd(); + } + var segments; + var buffer = d3_geo_clipBufferListener(), ringListener = clipLine(buffer), polygonStarted = false, polygon, ring; + function pointRing(λ, φ) { + ring.push([ λ, φ ]); + var point = rotate(λ, φ); + ringListener.point(point[0], point[1]); + } + function ringStart() { + ringListener.lineStart(); + ring = []; + } + function ringEnd() { + pointRing(ring[0][0], ring[0][1]); + ringListener.lineEnd(); + var clean = ringListener.clean(), ringSegments = buffer.buffer(), segment, n = ringSegments.length; + ring.pop(); + polygon.push(ring); + ring = null; + if (!n) return; + if (clean & 1) { + segment = ringSegments[0]; + var n = segment.length - 1, i = -1, point; + if (n > 0) { + if (!polygonStarted) listener.polygonStart(), polygonStarted = true; + listener.lineStart(); + while (++i < n) listener.point((point = segment[i])[0], point[1]); + listener.lineEnd(); + } + return; + } + if (n > 1 && clean & 2) ringSegments.push(ringSegments.pop().concat(ringSegments.shift())); + segments.push(ringSegments.filter(d3_geo_clipSegmentLength1)); + } + return clip; + }; + } + function d3_geo_clipSegmentLength1(segment) { + return segment.length > 1; + } + function d3_geo_clipBufferListener() { + var lines = [], line; + return { + lineStart: function() { + lines.push(line = []); + }, + point: function(λ, φ) { + line.push([ λ, φ ]); + }, + lineEnd: d3_noop, + buffer: function() { + var buffer = lines; + lines = []; + line = null; + return buffer; + }, + rejoin: function() { + if (lines.length > 1) lines.push(lines.pop().concat(lines.shift())); + } + }; + } + function d3_geo_clipSort(a, b) { + return ((a = a.x)[0] < 0 ? a[1] - halfπ - ε : halfπ - a[1]) - ((b = b.x)[0] < 0 ? b[1] - halfπ - ε : halfπ - b[1]); + } + var d3_geo_clipAntimeridian = d3_geo_clip(d3_true, d3_geo_clipAntimeridianLine, d3_geo_clipAntimeridianInterpolate, [ -π, -π / 2 ]); + function d3_geo_clipAntimeridianLine(listener) { + var λ0 = NaN, φ0 = NaN, sλ0 = NaN, clean; + return { + lineStart: function() { + listener.lineStart(); + clean = 1; + }, + point: function(λ1, φ1) { + var sλ1 = λ1 > 0 ? π : -π, dλ = abs(λ1 - λ0); + if (abs(dλ - π) < ε) { + listener.point(λ0, φ0 = (φ0 + φ1) / 2 > 0 ? halfπ : -halfπ); + listener.point(sλ0, φ0); + listener.lineEnd(); + listener.lineStart(); + listener.point(sλ1, φ0); + listener.point(λ1, φ0); + clean = 0; + } else if (sλ0 !== sλ1 && dλ >= π) { + if (abs(λ0 - sλ0) < ε) λ0 -= sλ0 * ε; + if (abs(λ1 - sλ1) < ε) λ1 -= sλ1 * ε; + φ0 = d3_geo_clipAntimeridianIntersect(λ0, φ0, λ1, φ1); + listener.point(sλ0, φ0); + listener.lineEnd(); + listener.lineStart(); + listener.point(sλ1, φ0); + clean = 0; + } + listener.point(λ0 = λ1, φ0 = φ1); + sλ0 = sλ1; + }, + lineEnd: function() { + listener.lineEnd(); + λ0 = φ0 = NaN; + }, + clean: function() { + return 2 - clean; + } + }; + } + function d3_geo_clipAntimeridianIntersect(λ0, φ0, λ1, φ1) { + var cosφ0, cosφ1, sinλ0_λ1 = Math.sin(λ0 - λ1); + return abs(sinλ0_λ1) > ε ? Math.atan((Math.sin(φ0) * (cosφ1 = Math.cos(φ1)) * Math.sin(λ1) - Math.sin(φ1) * (cosφ0 = Math.cos(φ0)) * Math.sin(λ0)) / (cosφ0 * cosφ1 * sinλ0_λ1)) : (φ0 + φ1) / 2; + } + function d3_geo_clipAntimeridianInterpolate(from, to, direction, listener) { + var φ; + if (from == null) { + φ = direction * halfπ; + listener.point(-π, φ); + listener.point(0, φ); + listener.point(π, φ); + listener.point(π, 0); + listener.point(π, -φ); + listener.point(0, -φ); + listener.point(-π, -φ); + listener.point(-π, 0); + listener.point(-π, φ); + } else if (abs(from[0] - to[0]) > ε) { + var s = from[0] < to[0] ? π : -π; + φ = direction * s / 2; + listener.point(-s, φ); + listener.point(0, φ); + listener.point(s, φ); + } else { + listener.point(to[0], to[1]); + } + } + function d3_geo_pointInPolygon(point, polygon) { + var meridian = point[0], parallel = point[1], meridianNormal = [ Math.sin(meridian), -Math.cos(meridian), 0 ], polarAngle = 0, winding = 0; + d3_geo_areaRingSum.reset(); + for (var i = 0, n = polygon.length; i < n; ++i) { + var ring = polygon[i], m = ring.length; + if (!m) continue; + var point0 = ring[0], λ0 = point0[0], φ0 = point0[1] / 2 + π / 4, sinφ0 = Math.sin(φ0), cosφ0 = Math.cos(φ0), j = 1; + while (true) { + if (j === m) j = 0; + point = ring[j]; + var λ = point[0], φ = point[1] / 2 + π / 4, sinφ = Math.sin(φ), cosφ = Math.cos(φ), dλ = λ - λ0, sdλ = dλ >= 0 ? 1 : -1, adλ = sdλ * dλ, antimeridian = adλ > π, k = sinφ0 * sinφ; + d3_geo_areaRingSum.add(Math.atan2(k * sdλ * Math.sin(adλ), cosφ0 * cosφ + k * Math.cos(adλ))); + polarAngle += antimeridian ? dλ + sdλ * τ : dλ; + if (antimeridian ^ λ0 >= meridian ^ λ >= meridian) { + var arc = d3_geo_cartesianCross(d3_geo_cartesian(point0), d3_geo_cartesian(point)); + d3_geo_cartesianNormalize(arc); + var intersection = d3_geo_cartesianCross(meridianNormal, arc); + d3_geo_cartesianNormalize(intersection); + var φarc = (antimeridian ^ dλ >= 0 ? -1 : 1) * d3_asin(intersection[2]); + if (parallel > φarc || parallel === φarc && (arc[0] || arc[1])) { + winding += antimeridian ^ dλ >= 0 ? 1 : -1; + } + } + if (!j++) break; + λ0 = λ, sinφ0 = sinφ, cosφ0 = cosφ, point0 = point; + } + } + return (polarAngle < -ε || polarAngle < ε && d3_geo_areaRingSum < -ε) ^ winding & 1; + } + function d3_geo_clipCircle(radius) { + var cr = Math.cos(radius), smallRadius = cr > 0, notHemisphere = abs(cr) > ε, interpolate = d3_geo_circleInterpolate(radius, 6 * d3_radians); + return d3_geo_clip(visible, clipLine, interpolate, smallRadius ? [ 0, -radius ] : [ -π, radius - π ]); + function visible(λ, φ) { + return Math.cos(λ) * Math.cos(φ) > cr; + } + function clipLine(listener) { + var point0, c0, v0, v00, clean; + return { + lineStart: function() { + v00 = v0 = false; + clean = 1; + }, + point: function(λ, φ) { + var point1 = [ λ, φ ], point2, v = visible(λ, φ), c = smallRadius ? v ? 0 : code(λ, φ) : v ? code(λ + (λ < 0 ? π : -π), φ) : 0; + if (!point0 && (v00 = v0 = v)) listener.lineStart(); + if (v !== v0) { + point2 = intersect(point0, point1); + if (d3_geo_sphericalEqual(point0, point2) || d3_geo_sphericalEqual(point1, point2)) { + point1[0] += ε; + point1[1] += ε; + v = visible(point1[0], point1[1]); + } + } + if (v !== v0) { + clean = 0; + if (v) { + listener.lineStart(); + point2 = intersect(point1, point0); + listener.point(point2[0], point2[1]); + } else { + point2 = intersect(point0, point1); + listener.point(point2[0], point2[1]); + listener.lineEnd(); + } + point0 = point2; + } else if (notHemisphere && point0 && smallRadius ^ v) { + var t; + if (!(c & c0) && (t = intersect(point1, point0, true))) { + clean = 0; + if (smallRadius) { + listener.lineStart(); + listener.point(t[0][0], t[0][1]); + listener.point(t[1][0], t[1][1]); + listener.lineEnd(); + } else { + listener.point(t[1][0], t[1][1]); + listener.lineEnd(); + listener.lineStart(); + listener.point(t[0][0], t[0][1]); + } + } + } + if (v && (!point0 || !d3_geo_sphericalEqual(point0, point1))) { + listener.point(point1[0], point1[1]); + } + point0 = point1, v0 = v, c0 = c; + }, + lineEnd: function() { + if (v0) listener.lineEnd(); + point0 = null; + }, + clean: function() { + return clean | (v00 && v0) << 1; + } + }; + } + function intersect(a, b, two) { + var pa = d3_geo_cartesian(a), pb = d3_geo_cartesian(b); + var n1 = [ 1, 0, 0 ], n2 = d3_geo_cartesianCross(pa, pb), n2n2 = d3_geo_cartesianDot(n2, n2), n1n2 = n2[0], determinant = n2n2 - n1n2 * n1n2; + if (!determinant) return !two && a; + var c1 = cr * n2n2 / determinant, c2 = -cr * n1n2 / determinant, n1xn2 = d3_geo_cartesianCross(n1, n2), A = d3_geo_cartesianScale(n1, c1), B = d3_geo_cartesianScale(n2, c2); + d3_geo_cartesianAdd(A, B); + var u = n1xn2, w = d3_geo_cartesianDot(A, u), uu = d3_geo_cartesianDot(u, u), t2 = w * w - uu * (d3_geo_cartesianDot(A, A) - 1); + if (t2 < 0) return; + var t = Math.sqrt(t2), q = d3_geo_cartesianScale(u, (-w - t) / uu); + d3_geo_cartesianAdd(q, A); + q = d3_geo_spherical(q); + if (!two) return q; + var λ0 = a[0], λ1 = b[0], φ0 = a[1], φ1 = b[1], z; + if (λ1 < λ0) z = λ0, λ0 = λ1, λ1 = z; + var δλ = λ1 - λ0, polar = abs(δλ - π) < ε, meridian = polar || δλ < ε; + if (!polar && φ1 < φ0) z = φ0, φ0 = φ1, φ1 = z; + if (meridian ? polar ? φ0 + φ1 > 0 ^ q[1] < (abs(q[0] - λ0) < ε ? φ0 : φ1) : φ0 <= q[1] && q[1] <= φ1 : δλ > π ^ (λ0 <= q[0] && q[0] <= λ1)) { + var q1 = d3_geo_cartesianScale(u, (-w + t) / uu); + d3_geo_cartesianAdd(q1, A); + return [ q, d3_geo_spherical(q1) ]; + } + } + function code(λ, φ) { + var r = smallRadius ? radius : π - radius, code = 0; + if (λ < -r) code |= 1; else if (λ > r) code |= 2; + if (φ < -r) code |= 4; else if (φ > r) code |= 8; + return code; + } + } + function d3_geom_clipLine(x0, y0, x1, y1) { + return function(line) { + var a = line.a, b = line.b, ax = a.x, ay = a.y, bx = b.x, by = b.y, t0 = 0, t1 = 1, dx = bx - ax, dy = by - ay, r; + r = x0 - ax; + if (!dx && r > 0) return; + r /= dx; + if (dx < 0) { + if (r < t0) return; + if (r < t1) t1 = r; + } else if (dx > 0) { + if (r > t1) return; + if (r > t0) t0 = r; + } + r = x1 - ax; + if (!dx && r < 0) return; + r /= dx; + if (dx < 0) { + if (r > t1) return; + if (r > t0) t0 = r; + } else if (dx > 0) { + if (r < t0) return; + if (r < t1) t1 = r; + } + r = y0 - ay; + if (!dy && r > 0) return; + r /= dy; + if (dy < 0) { + if (r < t0) return; + if (r < t1) t1 = r; + } else if (dy > 0) { + if (r > t1) return; + if (r > t0) t0 = r; + } + r = y1 - ay; + if (!dy && r < 0) return; + r /= dy; + if (dy < 0) { + if (r > t1) return; + if (r > t0) t0 = r; + } else if (dy > 0) { + if (r < t0) return; + if (r < t1) t1 = r; + } + if (t0 > 0) line.a = { + x: ax + t0 * dx, + y: ay + t0 * dy + }; + if (t1 < 1) line.b = { + x: ax + t1 * dx, + y: ay + t1 * dy + }; + return line; + }; + } + var d3_geo_clipExtentMAX = 1e9; + d3.geo.clipExtent = function() { + var x0, y0, x1, y1, stream, clip, clipExtent = { + stream: function(output) { + if (stream) stream.valid = false; + stream = clip(output); + stream.valid = true; + return stream; + }, + extent: function(_) { + if (!arguments.length) return [ [ x0, y0 ], [ x1, y1 ] ]; + clip = d3_geo_clipExtent(x0 = +_[0][0], y0 = +_[0][1], x1 = +_[1][0], y1 = +_[1][1]); + if (stream) stream.valid = false, stream = null; + return clipExtent; + } + }; + return clipExtent.extent([ [ 0, 0 ], [ 960, 500 ] ]); + }; + function d3_geo_clipExtent(x0, y0, x1, y1) { + return function(listener) { + var listener_ = listener, bufferListener = d3_geo_clipBufferListener(), clipLine = d3_geom_clipLine(x0, y0, x1, y1), segments, polygon, ring; + var clip = { + point: point, + lineStart: lineStart, + lineEnd: lineEnd, + polygonStart: function() { + listener = bufferListener; + segments = []; + polygon = []; + clean = true; + }, + polygonEnd: function() { + listener = listener_; + segments = d3.merge(segments); + var clipStartInside = insidePolygon([ x0, y1 ]), inside = clean && clipStartInside, visible = segments.length; + if (inside || visible) { + listener.polygonStart(); + if (inside) { + listener.lineStart(); + interpolate(null, null, 1, listener); + listener.lineEnd(); + } + if (visible) { + d3_geo_clipPolygon(segments, compare, clipStartInside, interpolate, listener); + } + listener.polygonEnd(); + } + segments = polygon = ring = null; + } + }; + function insidePolygon(p) { + var wn = 0, n = polygon.length, y = p[1]; + for (var i = 0; i < n; ++i) { + for (var j = 1, v = polygon[i], m = v.length, a = v[0], b; j < m; ++j) { + b = v[j]; + if (a[1] <= y) { + if (b[1] > y && d3_cross2d(a, b, p) > 0) ++wn; + } else { + if (b[1] <= y && d3_cross2d(a, b, p) < 0) --wn; + } + a = b; + } + } + return wn !== 0; + } + function interpolate(from, to, direction, listener) { + var a = 0, a1 = 0; + if (from == null || (a = corner(from, direction)) !== (a1 = corner(to, direction)) || comparePoints(from, to) < 0 ^ direction > 0) { + do { + listener.point(a === 0 || a === 3 ? x0 : x1, a > 1 ? y1 : y0); + } while ((a = (a + direction + 4) % 4) !== a1); + } else { + listener.point(to[0], to[1]); + } + } + function pointVisible(x, y) { + return x0 <= x && x <= x1 && y0 <= y && y <= y1; + } + function point(x, y) { + if (pointVisible(x, y)) listener.point(x, y); + } + var x__, y__, v__, x_, y_, v_, first, clean; + function lineStart() { + clip.point = linePoint; + if (polygon) polygon.push(ring = []); + first = true; + v_ = false; + x_ = y_ = NaN; + } + function lineEnd() { + if (segments) { + linePoint(x__, y__); + if (v__ && v_) bufferListener.rejoin(); + segments.push(bufferListener.buffer()); + } + clip.point = point; + if (v_) listener.lineEnd(); + } + function linePoint(x, y) { + x = Math.max(-d3_geo_clipExtentMAX, Math.min(d3_geo_clipExtentMAX, x)); + y = Math.max(-d3_geo_clipExtentMAX, Math.min(d3_geo_clipExtentMAX, y)); + var v = pointVisible(x, y); + if (polygon) ring.push([ x, y ]); + if (first) { + x__ = x, y__ = y, v__ = v; + first = false; + if (v) { + listener.lineStart(); + listener.point(x, y); + } + } else { + if (v && v_) listener.point(x, y); else { + var l = { + a: { + x: x_, + y: y_ + }, + b: { + x: x, + y: y + } + }; + if (clipLine(l)) { + if (!v_) { + listener.lineStart(); + listener.point(l.a.x, l.a.y); + } + listener.point(l.b.x, l.b.y); + if (!v) listener.lineEnd(); + clean = false; + } else if (v) { + listener.lineStart(); + listener.point(x, y); + clean = false; + } + } + } + x_ = x, y_ = y, v_ = v; + } + return clip; + }; + function corner(p, direction) { + return abs(p[0] - x0) < ε ? direction > 0 ? 0 : 3 : abs(p[0] - x1) < ε ? direction > 0 ? 2 : 1 : abs(p[1] - y0) < ε ? direction > 0 ? 1 : 0 : direction > 0 ? 3 : 2; + } + function compare(a, b) { + return comparePoints(a.x, b.x); + } + function comparePoints(a, b) { + var ca = corner(a, 1), cb = corner(b, 1); + return ca !== cb ? ca - cb : ca === 0 ? b[1] - a[1] : ca === 1 ? a[0] - b[0] : ca === 2 ? a[1] - b[1] : b[0] - a[0]; + } + } + function d3_geo_conic(projectAt) { + var φ0 = 0, φ1 = π / 3, m = d3_geo_projectionMutator(projectAt), p = m(φ0, φ1); + p.parallels = function(_) { + if (!arguments.length) return [ φ0 / π * 180, φ1 / π * 180 ]; + return m(φ0 = _[0] * π / 180, φ1 = _[1] * π / 180); + }; + return p; + } + function d3_geo_conicEqualArea(φ0, φ1) { + var sinφ0 = Math.sin(φ0), n = (sinφ0 + Math.sin(φ1)) / 2, C = 1 + sinφ0 * (2 * n - sinφ0), ρ0 = Math.sqrt(C) / n; + function forward(λ, φ) { + var ρ = Math.sqrt(C - 2 * n * Math.sin(φ)) / n; + return [ ρ * Math.sin(λ *= n), ρ0 - ρ * Math.cos(λ) ]; + } + forward.invert = function(x, y) { + var ρ0_y = ρ0 - y; + return [ Math.atan2(x, ρ0_y) / n, d3_asin((C - (x * x + ρ0_y * ρ0_y) * n * n) / (2 * n)) ]; + }; + return forward; + } + (d3.geo.conicEqualArea = function() { + return d3_geo_conic(d3_geo_conicEqualArea); + }).raw = d3_geo_conicEqualArea; + d3.geo.albers = function() { + return d3.geo.conicEqualArea().rotate([ 96, 0 ]).center([ -.6, 38.7 ]).parallels([ 29.5, 45.5 ]).scale(1070); + }; + d3.geo.albersUsa = function() { + var lower48 = d3.geo.albers(); + var alaska = d3.geo.conicEqualArea().rotate([ 154, 0 ]).center([ -2, 58.5 ]).parallels([ 55, 65 ]); + var hawaii = d3.geo.conicEqualArea().rotate([ 157, 0 ]).center([ -3, 19.9 ]).parallels([ 8, 18 ]); + var point, pointStream = { + point: function(x, y) { + point = [ x, y ]; + } + }, lower48Point, alaskaPoint, hawaiiPoint; + function albersUsa(coordinates) { + var x = coordinates[0], y = coordinates[1]; + point = null; + (lower48Point(x, y), point) || (alaskaPoint(x, y), point) || hawaiiPoint(x, y); + return point; + } + albersUsa.invert = function(coordinates) { + var k = lower48.scale(), t = lower48.translate(), x = (coordinates[0] - t[0]) / k, y = (coordinates[1] - t[1]) / k; + return (y >= .12 && y < .234 && x >= -.425 && x < -.214 ? alaska : y >= .166 && y < .234 && x >= -.214 && x < -.115 ? hawaii : lower48).invert(coordinates); + }; + albersUsa.stream = function(stream) { + var lower48Stream = lower48.stream(stream), alaskaStream = alaska.stream(stream), hawaiiStream = hawaii.stream(stream); + return { + point: function(x, y) { + lower48Stream.point(x, y); + alaskaStream.point(x, y); + hawaiiStream.point(x, y); + }, + sphere: function() { + lower48Stream.sphere(); + alaskaStream.sphere(); + hawaiiStream.sphere(); + }, + lineStart: function() { + lower48Stream.lineStart(); + alaskaStream.lineStart(); + hawaiiStream.lineStart(); + }, + lineEnd: function() { + lower48Stream.lineEnd(); + alaskaStream.lineEnd(); + hawaiiStream.lineEnd(); + }, + polygonStart: function() { + lower48Stream.polygonStart(); + alaskaStream.polygonStart(); + hawaiiStream.polygonStart(); + }, + polygonEnd: function() { + lower48Stream.polygonEnd(); + alaskaStream.polygonEnd(); + hawaiiStream.polygonEnd(); + } + }; + }; + albersUsa.precision = function(_) { + if (!arguments.length) return lower48.precision(); + lower48.precision(_); + alaska.precision(_); + hawaii.precision(_); + return albersUsa; + }; + albersUsa.scale = function(_) { + if (!arguments.length) return lower48.scale(); + lower48.scale(_); + alaska.scale(_ * .35); + hawaii.scale(_); + return albersUsa.translate(lower48.translate()); + }; + albersUsa.translate = function(_) { + if (!arguments.length) return lower48.translate(); + var k = lower48.scale(), x = +_[0], y = +_[1]; + lower48Point = lower48.translate(_).clipExtent([ [ x - .455 * k, y - .238 * k ], [ x + .455 * k, y + .238 * k ] ]).stream(pointStream).point; + alaskaPoint = alaska.translate([ x - .307 * k, y + .201 * k ]).clipExtent([ [ x - .425 * k + ε, y + .12 * k + ε ], [ x - .214 * k - ε, y + .234 * k - ε ] ]).stream(pointStream).point; + hawaiiPoint = hawaii.translate([ x - .205 * k, y + .212 * k ]).clipExtent([ [ x - .214 * k + ε, y + .166 * k + ε ], [ x - .115 * k - ε, y + .234 * k - ε ] ]).stream(pointStream).point; + return albersUsa; + }; + return albersUsa.scale(1070); + }; + var d3_geo_pathAreaSum, d3_geo_pathAreaPolygon, d3_geo_pathArea = { + point: d3_noop, + lineStart: d3_noop, + lineEnd: d3_noop, + polygonStart: function() { + d3_geo_pathAreaPolygon = 0; + d3_geo_pathArea.lineStart = d3_geo_pathAreaRingStart; + }, + polygonEnd: function() { + d3_geo_pathArea.lineStart = d3_geo_pathArea.lineEnd = d3_geo_pathArea.point = d3_noop; + d3_geo_pathAreaSum += abs(d3_geo_pathAreaPolygon / 2); + } + }; + function d3_geo_pathAreaRingStart() { + var x00, y00, x0, y0; + d3_geo_pathArea.point = function(x, y) { + d3_geo_pathArea.point = nextPoint; + x00 = x0 = x, y00 = y0 = y; + }; + function nextPoint(x, y) { + d3_geo_pathAreaPolygon += y0 * x - x0 * y; + x0 = x, y0 = y; + } + d3_geo_pathArea.lineEnd = function() { + nextPoint(x00, y00); + }; + } + var d3_geo_pathBoundsX0, d3_geo_pathBoundsY0, d3_geo_pathBoundsX1, d3_geo_pathBoundsY1; + var d3_geo_pathBounds = { + point: d3_geo_pathBoundsPoint, + lineStart: d3_noop, + lineEnd: d3_noop, + polygonStart: d3_noop, + polygonEnd: d3_noop + }; + function d3_geo_pathBoundsPoint(x, y) { + if (x < d3_geo_pathBoundsX0) d3_geo_pathBoundsX0 = x; + if (x > d3_geo_pathBoundsX1) d3_geo_pathBoundsX1 = x; + if (y < d3_geo_pathBoundsY0) d3_geo_pathBoundsY0 = y; + if (y > d3_geo_pathBoundsY1) d3_geo_pathBoundsY1 = y; + } + function d3_geo_pathBuffer() { + var pointCircle = d3_geo_pathBufferCircle(4.5), buffer = []; + var stream = { + point: point, + lineStart: function() { + stream.point = pointLineStart; + }, + lineEnd: lineEnd, + polygonStart: function() { + stream.lineEnd = lineEndPolygon; + }, + polygonEnd: function() { + stream.lineEnd = lineEnd; + stream.point = point; + }, + pointRadius: function(_) { + pointCircle = d3_geo_pathBufferCircle(_); + return stream; + }, + result: function() { + if (buffer.length) { + var result = buffer.join(""); + buffer = []; + return result; + } + } + }; + function point(x, y) { + buffer.push("M", x, ",", y, pointCircle); + } + function pointLineStart(x, y) { + buffer.push("M", x, ",", y); + stream.point = pointLine; + } + function pointLine(x, y) { + buffer.push("L", x, ",", y); + } + function lineEnd() { + stream.point = point; + } + function lineEndPolygon() { + buffer.push("Z"); + } + return stream; + } + function d3_geo_pathBufferCircle(radius) { + return "m0," + radius + "a" + radius + "," + radius + " 0 1,1 0," + -2 * radius + "a" + radius + "," + radius + " 0 1,1 0," + 2 * radius + "z"; + } + var d3_geo_pathCentroid = { + point: d3_geo_pathCentroidPoint, + lineStart: d3_geo_pathCentroidLineStart, + lineEnd: d3_geo_pathCentroidLineEnd, + polygonStart: function() { + d3_geo_pathCentroid.lineStart = d3_geo_pathCentroidRingStart; + }, + polygonEnd: function() { + d3_geo_pathCentroid.point = d3_geo_pathCentroidPoint; + d3_geo_pathCentroid.lineStart = d3_geo_pathCentroidLineStart; + d3_geo_pathCentroid.lineEnd = d3_geo_pathCentroidLineEnd; + } + }; + function d3_geo_pathCentroidPoint(x, y) { + d3_geo_centroidX0 += x; + d3_geo_centroidY0 += y; + ++d3_geo_centroidZ0; + } + function d3_geo_pathCentroidLineStart() { + var x0, y0; + d3_geo_pathCentroid.point = function(x, y) { + d3_geo_pathCentroid.point = nextPoint; + d3_geo_pathCentroidPoint(x0 = x, y0 = y); + }; + function nextPoint(x, y) { + var dx = x - x0, dy = y - y0, z = Math.sqrt(dx * dx + dy * dy); + d3_geo_centroidX1 += z * (x0 + x) / 2; + d3_geo_centroidY1 += z * (y0 + y) / 2; + d3_geo_centroidZ1 += z; + d3_geo_pathCentroidPoint(x0 = x, y0 = y); + } + } + function d3_geo_pathCentroidLineEnd() { + d3_geo_pathCentroid.point = d3_geo_pathCentroidPoint; + } + function d3_geo_pathCentroidRingStart() { + var x00, y00, x0, y0; + d3_geo_pathCentroid.point = function(x, y) { + d3_geo_pathCentroid.point = nextPoint; + d3_geo_pathCentroidPoint(x00 = x0 = x, y00 = y0 = y); + }; + function nextPoint(x, y) { + var dx = x - x0, dy = y - y0, z = Math.sqrt(dx * dx + dy * dy); + d3_geo_centroidX1 += z * (x0 + x) / 2; + d3_geo_centroidY1 += z * (y0 + y) / 2; + d3_geo_centroidZ1 += z; + z = y0 * x - x0 * y; + d3_geo_centroidX2 += z * (x0 + x); + d3_geo_centroidY2 += z * (y0 + y); + d3_geo_centroidZ2 += z * 3; + d3_geo_pathCentroidPoint(x0 = x, y0 = y); + } + d3_geo_pathCentroid.lineEnd = function() { + nextPoint(x00, y00); + }; + } + function d3_geo_pathContext(context) { + var pointRadius = 4.5; + var stream = { + point: point, + lineStart: function() { + stream.point = pointLineStart; + }, + lineEnd: lineEnd, + polygonStart: function() { + stream.lineEnd = lineEndPolygon; + }, + polygonEnd: function() { + stream.lineEnd = lineEnd; + stream.point = point; + }, + pointRadius: function(_) { + pointRadius = _; + return stream; + }, + result: d3_noop + }; + function point(x, y) { + context.moveTo(x + pointRadius, y); + context.arc(x, y, pointRadius, 0, τ); + } + function pointLineStart(x, y) { + context.moveTo(x, y); + stream.point = pointLine; + } + function pointLine(x, y) { + context.lineTo(x, y); + } + function lineEnd() { + stream.point = point; + } + function lineEndPolygon() { + context.closePath(); + } + return stream; + } + function d3_geo_resample(project) { + var δ2 = .5, cosMinDistance = Math.cos(30 * d3_radians), maxDepth = 16; + function resample(stream) { + return (maxDepth ? resampleRecursive : resampleNone)(stream); + } + function resampleNone(stream) { + return d3_geo_transformPoint(stream, function(x, y) { + x = project(x, y); + stream.point(x[0], x[1]); + }); + } + function resampleRecursive(stream) { + var λ00, φ00, x00, y00, a00, b00, c00, λ0, x0, y0, a0, b0, c0; + var resample = { + point: point, + lineStart: lineStart, + lineEnd: lineEnd, + polygonStart: function() { + stream.polygonStart(); + resample.lineStart = ringStart; + }, + polygonEnd: function() { + stream.polygonEnd(); + resample.lineStart = lineStart; + } + }; + function point(x, y) { + x = project(x, y); + stream.point(x[0], x[1]); + } + function lineStart() { + x0 = NaN; + resample.point = linePoint; + stream.lineStart(); + } + function linePoint(λ, φ) { + var c = d3_geo_cartesian([ λ, φ ]), p = project(λ, φ); + resampleLineTo(x0, y0, λ0, a0, b0, c0, x0 = p[0], y0 = p[1], λ0 = λ, a0 = c[0], b0 = c[1], c0 = c[2], maxDepth, stream); + stream.point(x0, y0); + } + function lineEnd() { + resample.point = point; + stream.lineEnd(); + } + function ringStart() { + lineStart(); + resample.point = ringPoint; + resample.lineEnd = ringEnd; + } + function ringPoint(λ, φ) { + linePoint(λ00 = λ, φ00 = φ), x00 = x0, y00 = y0, a00 = a0, b00 = b0, c00 = c0; + resample.point = linePoint; + } + function ringEnd() { + resampleLineTo(x0, y0, λ0, a0, b0, c0, x00, y00, λ00, a00, b00, c00, maxDepth, stream); + resample.lineEnd = lineEnd; + lineEnd(); + } + return resample; + } + function resampleLineTo(x0, y0, λ0, a0, b0, c0, x1, y1, λ1, a1, b1, c1, depth, stream) { + var dx = x1 - x0, dy = y1 - y0, d2 = dx * dx + dy * dy; + if (d2 > 4 * δ2 && depth--) { + var a = a0 + a1, b = b0 + b1, c = c0 + c1, m = Math.sqrt(a * a + b * b + c * c), φ2 = Math.asin(c /= m), λ2 = abs(abs(c) - 1) < ε || abs(λ0 - λ1) < ε ? (λ0 + λ1) / 2 : Math.atan2(b, a), p = project(λ2, φ2), x2 = p[0], y2 = p[1], dx2 = x2 - x0, dy2 = y2 - y0, dz = dy * dx2 - dx * dy2; + if (dz * dz / d2 > δ2 || abs((dx * dx2 + dy * dy2) / d2 - .5) > .3 || a0 * a1 + b0 * b1 + c0 * c1 < cosMinDistance) { + resampleLineTo(x0, y0, λ0, a0, b0, c0, x2, y2, λ2, a /= m, b /= m, c, depth, stream); + stream.point(x2, y2); + resampleLineTo(x2, y2, λ2, a, b, c, x1, y1, λ1, a1, b1, c1, depth, stream); + } + } + } + resample.precision = function(_) { + if (!arguments.length) return Math.sqrt(δ2); + maxDepth = (δ2 = _ * _) > 0 && 16; + return resample; + }; + return resample; + } + d3.geo.path = function() { + var pointRadius = 4.5, projection, context, projectStream, contextStream, cacheStream; + function path(object) { + if (object) { + if (typeof pointRadius === "function") contextStream.pointRadius(+pointRadius.apply(this, arguments)); + if (!cacheStream || !cacheStream.valid) cacheStream = projectStream(contextStream); + d3.geo.stream(object, cacheStream); + } + return contextStream.result(); + } + path.area = function(object) { + d3_geo_pathAreaSum = 0; + d3.geo.stream(object, projectStream(d3_geo_pathArea)); + return d3_geo_pathAreaSum; + }; + path.centroid = function(object) { + d3_geo_centroidX0 = d3_geo_centroidY0 = d3_geo_centroidZ0 = d3_geo_centroidX1 = d3_geo_centroidY1 = d3_geo_centroidZ1 = d3_geo_centroidX2 = d3_geo_centroidY2 = d3_geo_centroidZ2 = 0; + d3.geo.stream(object, projectStream(d3_geo_pathCentroid)); + return d3_geo_centroidZ2 ? [ d3_geo_centroidX2 / d3_geo_centroidZ2, d3_geo_centroidY2 / d3_geo_centroidZ2 ] : d3_geo_centroidZ1 ? [ d3_geo_centroidX1 / d3_geo_centroidZ1, d3_geo_centroidY1 / d3_geo_centroidZ1 ] : d3_geo_centroidZ0 ? [ d3_geo_centroidX0 / d3_geo_centroidZ0, d3_geo_centroidY0 / d3_geo_centroidZ0 ] : [ NaN, NaN ]; + }; + path.bounds = function(object) { + d3_geo_pathBoundsX1 = d3_geo_pathBoundsY1 = -(d3_geo_pathBoundsX0 = d3_geo_pathBoundsY0 = Infinity); + d3.geo.stream(object, projectStream(d3_geo_pathBounds)); + return [ [ d3_geo_pathBoundsX0, d3_geo_pathBoundsY0 ], [ d3_geo_pathBoundsX1, d3_geo_pathBoundsY1 ] ]; + }; + path.projection = function(_) { + if (!arguments.length) return projection; + projectStream = (projection = _) ? _.stream || d3_geo_pathProjectStream(_) : d3_identity; + return reset(); + }; + path.context = function(_) { + if (!arguments.length) return context; + contextStream = (context = _) == null ? new d3_geo_pathBuffer() : new d3_geo_pathContext(_); + if (typeof pointRadius !== "function") contextStream.pointRadius(pointRadius); + return reset(); + }; + path.pointRadius = function(_) { + if (!arguments.length) return pointRadius; + pointRadius = typeof _ === "function" ? _ : (contextStream.pointRadius(+_), +_); + return path; + }; + function reset() { + cacheStream = null; + return path; + } + return path.projection(d3.geo.albersUsa()).context(null); + }; + function d3_geo_pathProjectStream(project) { + var resample = d3_geo_resample(function(x, y) { + return project([ x * d3_degrees, y * d3_degrees ]); + }); + return function(stream) { + return d3_geo_projectionRadians(resample(stream)); + }; + } + d3.geo.transform = function(methods) { + return { + stream: function(stream) { + var transform = new d3_geo_transform(stream); + for (var k in methods) transform[k] = methods[k]; + return transform; + } + }; + }; + function d3_geo_transform(stream) { + this.stream = stream; + } + d3_geo_transform.prototype = { + point: function(x, y) { + this.stream.point(x, y); + }, + sphere: function() { + this.stream.sphere(); + }, + lineStart: function() { + this.stream.lineStart(); + }, + lineEnd: function() { + this.stream.lineEnd(); + }, + polygonStart: function() { + this.stream.polygonStart(); + }, + polygonEnd: function() { + this.stream.polygonEnd(); + } + }; + function d3_geo_transformPoint(stream, point) { + return { + point: point, + sphere: function() { + stream.sphere(); + }, + lineStart: function() { + stream.lineStart(); + }, + lineEnd: function() { + stream.lineEnd(); + }, + polygonStart: function() { + stream.polygonStart(); + }, + polygonEnd: function() { + stream.polygonEnd(); + } + }; + } + d3.geo.projection = d3_geo_projection; + d3.geo.projectionMutator = d3_geo_projectionMutator; + function d3_geo_projection(project) { + return d3_geo_projectionMutator(function() { + return project; + })(); + } + function d3_geo_projectionMutator(projectAt) { + var project, rotate, projectRotate, projectResample = d3_geo_resample(function(x, y) { + x = project(x, y); + return [ x[0] * k + δx, δy - x[1] * k ]; + }), k = 150, x = 480, y = 250, λ = 0, φ = 0, δλ = 0, δφ = 0, δγ = 0, δx, δy, preclip = d3_geo_clipAntimeridian, postclip = d3_identity, clipAngle = null, clipExtent = null, stream; + function projection(point) { + point = projectRotate(point[0] * d3_radians, point[1] * d3_radians); + return [ point[0] * k + δx, δy - point[1] * k ]; + } + function invert(point) { + point = projectRotate.invert((point[0] - δx) / k, (δy - point[1]) / k); + return point && [ point[0] * d3_degrees, point[1] * d3_degrees ]; + } + projection.stream = function(output) { + if (stream) stream.valid = false; + stream = d3_geo_projectionRadians(preclip(rotate, projectResample(postclip(output)))); + stream.valid = true; + return stream; + }; + projection.clipAngle = function(_) { + if (!arguments.length) return clipAngle; + preclip = _ == null ? (clipAngle = _, d3_geo_clipAntimeridian) : d3_geo_clipCircle((clipAngle = +_) * d3_radians); + return invalidate(); + }; + projection.clipExtent = function(_) { + if (!arguments.length) return clipExtent; + clipExtent = _; + postclip = _ ? d3_geo_clipExtent(_[0][0], _[0][1], _[1][0], _[1][1]) : d3_identity; + return invalidate(); + }; + projection.scale = function(_) { + if (!arguments.length) return k; + k = +_; + return reset(); + }; + projection.translate = function(_) { + if (!arguments.length) return [ x, y ]; + x = +_[0]; + y = +_[1]; + return reset(); + }; + projection.center = function(_) { + if (!arguments.length) return [ λ * d3_degrees, φ * d3_degrees ]; + λ = _[0] % 360 * d3_radians; + φ = _[1] % 360 * d3_radians; + return reset(); + }; + projection.rotate = function(_) { + if (!arguments.length) return [ δλ * d3_degrees, δφ * d3_degrees, δγ * d3_degrees ]; + δλ = _[0] % 360 * d3_radians; + δφ = _[1] % 360 * d3_radians; + δγ = _.length > 2 ? _[2] % 360 * d3_radians : 0; + return reset(); + }; + d3.rebind(projection, projectResample, "precision"); + function reset() { + projectRotate = d3_geo_compose(rotate = d3_geo_rotation(δλ, δφ, δγ), project); + var center = project(λ, φ); + δx = x - center[0] * k; + δy = y + center[1] * k; + return invalidate(); + } + function invalidate() { + if (stream) stream.valid = false, stream = null; + return projection; + } + return function() { + project = projectAt.apply(this, arguments); + projection.invert = project.invert && invert; + return reset(); + }; + } + function d3_geo_projectionRadians(stream) { + return d3_geo_transformPoint(stream, function(x, y) { + stream.point(x * d3_radians, y * d3_radians); + }); + } + function d3_geo_equirectangular(λ, φ) { + return [ λ, φ ]; + } + (d3.geo.equirectangular = function() { + return d3_geo_projection(d3_geo_equirectangular); + }).raw = d3_geo_equirectangular.invert = d3_geo_equirectangular; + d3.geo.rotation = function(rotate) { + rotate = d3_geo_rotation(rotate[0] % 360 * d3_radians, rotate[1] * d3_radians, rotate.length > 2 ? rotate[2] * d3_radians : 0); + function forward(coordinates) { + coordinates = rotate(coordinates[0] * d3_radians, coordinates[1] * d3_radians); + return coordinates[0] *= d3_degrees, coordinates[1] *= d3_degrees, coordinates; + } + forward.invert = function(coordinates) { + coordinates = rotate.invert(coordinates[0] * d3_radians, coordinates[1] * d3_radians); + return coordinates[0] *= d3_degrees, coordinates[1] *= d3_degrees, coordinates; + }; + return forward; + }; + function d3_geo_identityRotation(λ, φ) { + return [ λ > π ? λ - τ : λ < -π ? λ + τ : λ, φ ]; + } + d3_geo_identityRotation.invert = d3_geo_equirectangular; + function d3_geo_rotation(δλ, δφ, δγ) { + return δλ ? δφ || δγ ? d3_geo_compose(d3_geo_rotationλ(δλ), d3_geo_rotationφγ(δφ, δγ)) : d3_geo_rotationλ(δλ) : δφ || δγ ? d3_geo_rotationφγ(δφ, δγ) : d3_geo_identityRotation; + } + function d3_geo_forwardRotationλ(δλ) { + return function(λ, φ) { + return λ += δλ, [ λ > π ? λ - τ : λ < -π ? λ + τ : λ, φ ]; + }; + } + function d3_geo_rotationλ(δλ) { + var rotation = d3_geo_forwardRotationλ(δλ); + rotation.invert = d3_geo_forwardRotationλ(-δλ); + return rotation; + } + function d3_geo_rotationφγ(δφ, δγ) { + var cosδφ = Math.cos(δφ), sinδφ = Math.sin(δφ), cosδγ = Math.cos(δγ), sinδγ = Math.sin(δγ); + function rotation(λ, φ) { + var cosφ = Math.cos(φ), x = Math.cos(λ) * cosφ, y = Math.sin(λ) * cosφ, z = Math.sin(φ), k = z * cosδφ + x * sinδφ; + return [ Math.atan2(y * cosδγ - k * sinδγ, x * cosδφ - z * sinδφ), d3_asin(k * cosδγ + y * sinδγ) ]; + } + rotation.invert = function(λ, φ) { + var cosφ = Math.cos(φ), x = Math.cos(λ) * cosφ, y = Math.sin(λ) * cosφ, z = Math.sin(φ), k = z * cosδγ - y * sinδγ; + return [ Math.atan2(y * cosδγ + z * sinδγ, x * cosδφ + k * sinδφ), d3_asin(k * cosδφ - x * sinδφ) ]; + }; + return rotation; + } + d3.geo.circle = function() { + var origin = [ 0, 0 ], angle, precision = 6, interpolate; + function circle() { + var center = typeof origin === "function" ? origin.apply(this, arguments) : origin, rotate = d3_geo_rotation(-center[0] * d3_radians, -center[1] * d3_radians, 0).invert, ring = []; + interpolate(null, null, 1, { + point: function(x, y) { + ring.push(x = rotate(x, y)); + x[0] *= d3_degrees, x[1] *= d3_degrees; + } + }); + return { + type: "Polygon", + coordinates: [ ring ] + }; + } + circle.origin = function(x) { + if (!arguments.length) return origin; + origin = x; + return circle; + }; + circle.angle = function(x) { + if (!arguments.length) return angle; + interpolate = d3_geo_circleInterpolate((angle = +x) * d3_radians, precision * d3_radians); + return circle; + }; + circle.precision = function(_) { + if (!arguments.length) return precision; + interpolate = d3_geo_circleInterpolate(angle * d3_radians, (precision = +_) * d3_radians); + return circle; + }; + return circle.angle(90); + }; + function d3_geo_circleInterpolate(radius, precision) { + var cr = Math.cos(radius), sr = Math.sin(radius); + return function(from, to, direction, listener) { + var step = direction * precision; + if (from != null) { + from = d3_geo_circleAngle(cr, from); + to = d3_geo_circleAngle(cr, to); + if (direction > 0 ? from < to : from > to) from += direction * τ; + } else { + from = radius + direction * τ; + to = radius - .5 * step; + } + for (var point, t = from; direction > 0 ? t > to : t < to; t -= step) { + listener.point((point = d3_geo_spherical([ cr, -sr * Math.cos(t), -sr * Math.sin(t) ]))[0], point[1]); + } + }; + } + function d3_geo_circleAngle(cr, point) { + var a = d3_geo_cartesian(point); + a[0] -= cr; + d3_geo_cartesianNormalize(a); + var angle = d3_acos(-a[1]); + return ((-a[2] < 0 ? -angle : angle) + 2 * Math.PI - ε) % (2 * Math.PI); + } + d3.geo.distance = function(a, b) { + var Δλ = (b[0] - a[0]) * d3_radians, φ0 = a[1] * d3_radians, φ1 = b[1] * d3_radians, sinΔλ = Math.sin(Δλ), cosΔλ = Math.cos(Δλ), sinφ0 = Math.sin(φ0), cosφ0 = Math.cos(φ0), sinφ1 = Math.sin(φ1), cosφ1 = Math.cos(φ1), t; + return Math.atan2(Math.sqrt((t = cosφ1 * sinΔλ) * t + (t = cosφ0 * sinφ1 - sinφ0 * cosφ1 * cosΔλ) * t), sinφ0 * sinφ1 + cosφ0 * cosφ1 * cosΔλ); + }; + d3.geo.graticule = function() { + var x1, x0, X1, X0, y1, y0, Y1, Y0, dx = 10, dy = dx, DX = 90, DY = 360, x, y, X, Y, precision = 2.5; + function graticule() { + return { + type: "MultiLineString", + coordinates: lines() + }; + } + function lines() { + return d3.range(Math.ceil(X0 / DX) * DX, X1, DX).map(X).concat(d3.range(Math.ceil(Y0 / DY) * DY, Y1, DY).map(Y)).concat(d3.range(Math.ceil(x0 / dx) * dx, x1, dx).filter(function(x) { + return abs(x % DX) > ε; + }).map(x)).concat(d3.range(Math.ceil(y0 / dy) * dy, y1, dy).filter(function(y) { + return abs(y % DY) > ε; + }).map(y)); + } + graticule.lines = function() { + return lines().map(function(coordinates) { + return { + type: "LineString", + coordinates: coordinates + }; + }); + }; + graticule.outline = function() { + return { + type: "Polygon", + coordinates: [ X(X0).concat(Y(Y1).slice(1), X(X1).reverse().slice(1), Y(Y0).reverse().slice(1)) ] + }; + }; + graticule.extent = function(_) { + if (!arguments.length) return graticule.minorExtent(); + return graticule.majorExtent(_).minorExtent(_); + }; + graticule.majorExtent = function(_) { + if (!arguments.length) return [ [ X0, Y0 ], [ X1, Y1 ] ]; + X0 = +_[0][0], X1 = +_[1][0]; + Y0 = +_[0][1], Y1 = +_[1][1]; + if (X0 > X1) _ = X0, X0 = X1, X1 = _; + if (Y0 > Y1) _ = Y0, Y0 = Y1, Y1 = _; + return graticule.precision(precision); + }; + graticule.minorExtent = function(_) { + if (!arguments.length) return [ [ x0, y0 ], [ x1, y1 ] ]; + x0 = +_[0][0], x1 = +_[1][0]; + y0 = +_[0][1], y1 = +_[1][1]; + if (x0 > x1) _ = x0, x0 = x1, x1 = _; + if (y0 > y1) _ = y0, y0 = y1, y1 = _; + return graticule.precision(precision); + }; + graticule.step = function(_) { + if (!arguments.length) return graticule.minorStep(); + return graticule.majorStep(_).minorStep(_); + }; + graticule.majorStep = function(_) { + if (!arguments.length) return [ DX, DY ]; + DX = +_[0], DY = +_[1]; + return graticule; + }; + graticule.minorStep = function(_) { + if (!arguments.length) return [ dx, dy ]; + dx = +_[0], dy = +_[1]; + return graticule; + }; + graticule.precision = function(_) { + if (!arguments.length) return precision; + precision = +_; + x = d3_geo_graticuleX(y0, y1, 90); + y = d3_geo_graticuleY(x0, x1, precision); + X = d3_geo_graticuleX(Y0, Y1, 90); + Y = d3_geo_graticuleY(X0, X1, precision); + return graticule; + }; + return graticule.majorExtent([ [ -180, -90 + ε ], [ 180, 90 - ε ] ]).minorExtent([ [ -180, -80 - ε ], [ 180, 80 + ε ] ]); + }; + function d3_geo_graticuleX(y0, y1, dy) { + var y = d3.range(y0, y1 - ε, dy).concat(y1); + return function(x) { + return y.map(function(y) { + return [ x, y ]; + }); + }; + } + function d3_geo_graticuleY(x0, x1, dx) { + var x = d3.range(x0, x1 - ε, dx).concat(x1); + return function(y) { + return x.map(function(x) { + return [ x, y ]; + }); + }; + } + function d3_source(d) { + return d.source; + } + function d3_target(d) { + return d.target; + } + d3.geo.greatArc = function() { + var source = d3_source, source_, target = d3_target, target_; + function greatArc() { + return { + type: "LineString", + coordinates: [ source_ || source.apply(this, arguments), target_ || target.apply(this, arguments) ] + }; + } + greatArc.distance = function() { + return d3.geo.distance(source_ || source.apply(this, arguments), target_ || target.apply(this, arguments)); + }; + greatArc.source = function(_) { + if (!arguments.length) return source; + source = _, source_ = typeof _ === "function" ? null : _; + return greatArc; + }; + greatArc.target = function(_) { + if (!arguments.length) return target; + target = _, target_ = typeof _ === "function" ? null : _; + return greatArc; + }; + greatArc.precision = function() { + return arguments.length ? greatArc : 0; + }; + return greatArc; + }; + d3.geo.interpolate = function(source, target) { + return d3_geo_interpolate(source[0] * d3_radians, source[1] * d3_radians, target[0] * d3_radians, target[1] * d3_radians); + }; + function d3_geo_interpolate(x0, y0, x1, y1) { + var cy0 = Math.cos(y0), sy0 = Math.sin(y0), cy1 = Math.cos(y1), sy1 = Math.sin(y1), kx0 = cy0 * Math.cos(x0), ky0 = cy0 * Math.sin(x0), kx1 = cy1 * Math.cos(x1), ky1 = cy1 * Math.sin(x1), d = 2 * Math.asin(Math.sqrt(d3_haversin(y1 - y0) + cy0 * cy1 * d3_haversin(x1 - x0))), k = 1 / Math.sin(d); + var interpolate = d ? function(t) { + var B = Math.sin(t *= d) * k, A = Math.sin(d - t) * k, x = A * kx0 + B * kx1, y = A * ky0 + B * ky1, z = A * sy0 + B * sy1; + return [ Math.atan2(y, x) * d3_degrees, Math.atan2(z, Math.sqrt(x * x + y * y)) * d3_degrees ]; + } : function() { + return [ x0 * d3_degrees, y0 * d3_degrees ]; + }; + interpolate.distance = d; + return interpolate; + } + d3.geo.length = function(object) { + d3_geo_lengthSum = 0; + d3.geo.stream(object, d3_geo_length); + return d3_geo_lengthSum; + }; + var d3_geo_lengthSum; + var d3_geo_length = { + sphere: d3_noop, + point: d3_noop, + lineStart: d3_geo_lengthLineStart, + lineEnd: d3_noop, + polygonStart: d3_noop, + polygonEnd: d3_noop + }; + function d3_geo_lengthLineStart() { + var λ0, sinφ0, cosφ0; + d3_geo_length.point = function(λ, φ) { + λ0 = λ * d3_radians, sinφ0 = Math.sin(φ *= d3_radians), cosφ0 = Math.cos(φ); + d3_geo_length.point = nextPoint; + }; + d3_geo_length.lineEnd = function() { + d3_geo_length.point = d3_geo_length.lineEnd = d3_noop; + }; + function nextPoint(λ, φ) { + var sinφ = Math.sin(φ *= d3_radians), cosφ = Math.cos(φ), t = abs((λ *= d3_radians) - λ0), cosΔλ = Math.cos(t); + d3_geo_lengthSum += Math.atan2(Math.sqrt((t = cosφ * Math.sin(t)) * t + (t = cosφ0 * sinφ - sinφ0 * cosφ * cosΔλ) * t), sinφ0 * sinφ + cosφ0 * cosφ * cosΔλ); + λ0 = λ, sinφ0 = sinφ, cosφ0 = cosφ; + } + } + function d3_geo_azimuthal(scale, angle) { + function azimuthal(λ, φ) { + var cosλ = Math.cos(λ), cosφ = Math.cos(φ), k = scale(cosλ * cosφ); + return [ k * cosφ * Math.sin(λ), k * Math.sin(φ) ]; + } + azimuthal.invert = function(x, y) { + var ρ = Math.sqrt(x * x + y * y), c = angle(ρ), sinc = Math.sin(c), cosc = Math.cos(c); + return [ Math.atan2(x * sinc, ρ * cosc), Math.asin(ρ && y * sinc / ρ) ]; + }; + return azimuthal; + } + var d3_geo_azimuthalEqualArea = d3_geo_azimuthal(function(cosλcosφ) { + return Math.sqrt(2 / (1 + cosλcosφ)); + }, function(ρ) { + return 2 * Math.asin(ρ / 2); + }); + (d3.geo.azimuthalEqualArea = function() { + return d3_geo_projection(d3_geo_azimuthalEqualArea); + }).raw = d3_geo_azimuthalEqualArea; + var d3_geo_azimuthalEquidistant = d3_geo_azimuthal(function(cosλcosφ) { + var c = Math.acos(cosλcosφ); + return c && c / Math.sin(c); + }, d3_identity); + (d3.geo.azimuthalEquidistant = function() { + return d3_geo_projection(d3_geo_azimuthalEquidistant); + }).raw = d3_geo_azimuthalEquidistant; + function d3_geo_conicConformal(φ0, φ1) { + var cosφ0 = Math.cos(φ0), t = function(φ) { + return Math.tan(π / 4 + φ / 2); + }, n = φ0 === φ1 ? Math.sin(φ0) : Math.log(cosφ0 / Math.cos(φ1)) / Math.log(t(φ1) / t(φ0)), F = cosφ0 * Math.pow(t(φ0), n) / n; + if (!n) return d3_geo_mercator; + function forward(λ, φ) { + if (F > 0) { + if (φ < -halfπ + ε) φ = -halfπ + ε; + } else { + if (φ > halfπ - ε) φ = halfπ - ε; + } + var ρ = F / Math.pow(t(φ), n); + return [ ρ * Math.sin(n * λ), F - ρ * Math.cos(n * λ) ]; + } + forward.invert = function(x, y) { + var ρ0_y = F - y, ρ = d3_sgn(n) * Math.sqrt(x * x + ρ0_y * ρ0_y); + return [ Math.atan2(x, ρ0_y) / n, 2 * Math.atan(Math.pow(F / ρ, 1 / n)) - halfπ ]; + }; + return forward; + } + (d3.geo.conicConformal = function() { + return d3_geo_conic(d3_geo_conicConformal); + }).raw = d3_geo_conicConformal; + function d3_geo_conicEquidistant(φ0, φ1) { + var cosφ0 = Math.cos(φ0), n = φ0 === φ1 ? Math.sin(φ0) : (cosφ0 - Math.cos(φ1)) / (φ1 - φ0), G = cosφ0 / n + φ0; + if (abs(n) < ε) return d3_geo_equirectangular; + function forward(λ, φ) { + var ρ = G - φ; + return [ ρ * Math.sin(n * λ), G - ρ * Math.cos(n * λ) ]; + } + forward.invert = function(x, y) { + var ρ0_y = G - y; + return [ Math.atan2(x, ρ0_y) / n, G - d3_sgn(n) * Math.sqrt(x * x + ρ0_y * ρ0_y) ]; + }; + return forward; + } + (d3.geo.conicEquidistant = function() { + return d3_geo_conic(d3_geo_conicEquidistant); + }).raw = d3_geo_conicEquidistant; + var d3_geo_gnomonic = d3_geo_azimuthal(function(cosλcosφ) { + return 1 / cosλcosφ; + }, Math.atan); + (d3.geo.gnomonic = function() { + return d3_geo_projection(d3_geo_gnomonic); + }).raw = d3_geo_gnomonic; + function d3_geo_mercator(λ, φ) { + return [ λ, Math.log(Math.tan(π / 4 + φ / 2)) ]; + } + d3_geo_mercator.invert = function(x, y) { + return [ x, 2 * Math.atan(Math.exp(y)) - halfπ ]; + }; + function d3_geo_mercatorProjection(project) { + var m = d3_geo_projection(project), scale = m.scale, translate = m.translate, clipExtent = m.clipExtent, clipAuto; + m.scale = function() { + var v = scale.apply(m, arguments); + return v === m ? clipAuto ? m.clipExtent(null) : m : v; + }; + m.translate = function() { + var v = translate.apply(m, arguments); + return v === m ? clipAuto ? m.clipExtent(null) : m : v; + }; + m.clipExtent = function(_) { + var v = clipExtent.apply(m, arguments); + if (v === m) { + if (clipAuto = _ == null) { + var k = π * scale(), t = translate(); + clipExtent([ [ t[0] - k, t[1] - k ], [ t[0] + k, t[1] + k ] ]); + } + } else if (clipAuto) { + v = null; + } + return v; + }; + return m.clipExtent(null); + } + (d3.geo.mercator = function() { + return d3_geo_mercatorProjection(d3_geo_mercator); + }).raw = d3_geo_mercator; + var d3_geo_orthographic = d3_geo_azimuthal(function() { + return 1; + }, Math.asin); + (d3.geo.orthographic = function() { + return d3_geo_projection(d3_geo_orthographic); + }).raw = d3_geo_orthographic; + var d3_geo_stereographic = d3_geo_azimuthal(function(cosλcosφ) { + return 1 / (1 + cosλcosφ); + }, function(ρ) { + return 2 * Math.atan(ρ); + }); + (d3.geo.stereographic = function() { + return d3_geo_projection(d3_geo_stereographic); + }).raw = d3_geo_stereographic; + function d3_geo_transverseMercator(λ, φ) { + return [ Math.log(Math.tan(π / 4 + φ / 2)), -λ ]; + } + d3_geo_transverseMercator.invert = function(x, y) { + return [ -y, 2 * Math.atan(Math.exp(x)) - halfπ ]; + }; + (d3.geo.transverseMercator = function() { + var projection = d3_geo_mercatorProjection(d3_geo_transverseMercator), center = projection.center, rotate = projection.rotate; + projection.center = function(_) { + return _ ? center([ -_[1], _[0] ]) : (_ = center(), [ _[1], -_[0] ]); + }; + projection.rotate = function(_) { + return _ ? rotate([ _[0], _[1], _.length > 2 ? _[2] + 90 : 90 ]) : (_ = rotate(), + [ _[0], _[1], _[2] - 90 ]); + }; + return rotate([ 0, 0, 90 ]); + }).raw = d3_geo_transverseMercator; + d3.geom = {}; + function d3_geom_pointX(d) { + return d[0]; + } + function d3_geom_pointY(d) { + return d[1]; + } + d3.geom.hull = function(vertices) { + var x = d3_geom_pointX, y = d3_geom_pointY; + if (arguments.length) return hull(vertices); + function hull(data) { + if (data.length < 3) return []; + var fx = d3_functor(x), fy = d3_functor(y), i, n = data.length, points = [], flippedPoints = []; + for (i = 0; i < n; i++) { + points.push([ +fx.call(this, data[i], i), +fy.call(this, data[i], i), i ]); + } + points.sort(d3_geom_hullOrder); + for (i = 0; i < n; i++) flippedPoints.push([ points[i][0], -points[i][1] ]); + var upper = d3_geom_hullUpper(points), lower = d3_geom_hullUpper(flippedPoints); + var skipLeft = lower[0] === upper[0], skipRight = lower[lower.length - 1] === upper[upper.length - 1], polygon = []; + for (i = upper.length - 1; i >= 0; --i) polygon.push(data[points[upper[i]][2]]); + for (i = +skipLeft; i < lower.length - skipRight; ++i) polygon.push(data[points[lower[i]][2]]); + return polygon; + } + hull.x = function(_) { + return arguments.length ? (x = _, hull) : x; + }; + hull.y = function(_) { + return arguments.length ? (y = _, hull) : y; + }; + return hull; + }; + function d3_geom_hullUpper(points) { + var n = points.length, hull = [ 0, 1 ], hs = 2; + for (var i = 2; i < n; i++) { + while (hs > 1 && d3_cross2d(points[hull[hs - 2]], points[hull[hs - 1]], points[i]) <= 0) --hs; + hull[hs++] = i; + } + return hull.slice(0, hs); + } + function d3_geom_hullOrder(a, b) { + return a[0] - b[0] || a[1] - b[1]; + } + d3.geom.polygon = function(coordinates) { + d3_subclass(coordinates, d3_geom_polygonPrototype); + return coordinates; + }; + var d3_geom_polygonPrototype = d3.geom.polygon.prototype = []; + d3_geom_polygonPrototype.area = function() { + var i = -1, n = this.length, a, b = this[n - 1], area = 0; + while (++i < n) { + a = b; + b = this[i]; + area += a[1] * b[0] - a[0] * b[1]; + } + return area * .5; + }; + d3_geom_polygonPrototype.centroid = function(k) { + var i = -1, n = this.length, x = 0, y = 0, a, b = this[n - 1], c; + if (!arguments.length) k = -1 / (6 * this.area()); + while (++i < n) { + a = b; + b = this[i]; + c = a[0] * b[1] - b[0] * a[1]; + x += (a[0] + b[0]) * c; + y += (a[1] + b[1]) * c; + } + return [ x * k, y * k ]; + }; + d3_geom_polygonPrototype.clip = function(subject) { + var input, closed = d3_geom_polygonClosed(subject), i = -1, n = this.length - d3_geom_polygonClosed(this), j, m, a = this[n - 1], b, c, d; + while (++i < n) { + input = subject.slice(); + subject.length = 0; + b = this[i]; + c = input[(m = input.length - closed) - 1]; + j = -1; + while (++j < m) { + d = input[j]; + if (d3_geom_polygonInside(d, a, b)) { + if (!d3_geom_polygonInside(c, a, b)) { + subject.push(d3_geom_polygonIntersect(c, d, a, b)); + } + subject.push(d); + } else if (d3_geom_polygonInside(c, a, b)) { + subject.push(d3_geom_polygonIntersect(c, d, a, b)); + } + c = d; + } + if (closed) subject.push(subject[0]); + a = b; + } + return subject; + }; + function d3_geom_polygonInside(p, a, b) { + return (b[0] - a[0]) * (p[1] - a[1]) < (b[1] - a[1]) * (p[0] - a[0]); + } + function d3_geom_polygonIntersect(c, d, a, b) { + var x1 = c[0], x3 = a[0], x21 = d[0] - x1, x43 = b[0] - x3, y1 = c[1], y3 = a[1], y21 = d[1] - y1, y43 = b[1] - y3, ua = (x43 * (y1 - y3) - y43 * (x1 - x3)) / (y43 * x21 - x43 * y21); + return [ x1 + ua * x21, y1 + ua * y21 ]; + } + function d3_geom_polygonClosed(coordinates) { + var a = coordinates[0], b = coordinates[coordinates.length - 1]; + return !(a[0] - b[0] || a[1] - b[1]); + } + var d3_geom_voronoiEdges, d3_geom_voronoiCells, d3_geom_voronoiBeaches, d3_geom_voronoiBeachPool = [], d3_geom_voronoiFirstCircle, d3_geom_voronoiCircles, d3_geom_voronoiCirclePool = []; + function d3_geom_voronoiBeach() { + d3_geom_voronoiRedBlackNode(this); + this.edge = this.site = this.circle = null; + } + function d3_geom_voronoiCreateBeach(site) { + var beach = d3_geom_voronoiBeachPool.pop() || new d3_geom_voronoiBeach(); + beach.site = site; + return beach; + } + function d3_geom_voronoiDetachBeach(beach) { + d3_geom_voronoiDetachCircle(beach); + d3_geom_voronoiBeaches.remove(beach); + d3_geom_voronoiBeachPool.push(beach); + d3_geom_voronoiRedBlackNode(beach); + } + function d3_geom_voronoiRemoveBeach(beach) { + var circle = beach.circle, x = circle.x, y = circle.cy, vertex = { + x: x, + y: y + }, previous = beach.P, next = beach.N, disappearing = [ beach ]; + d3_geom_voronoiDetachBeach(beach); + var lArc = previous; + while (lArc.circle && abs(x - lArc.circle.x) < ε && abs(y - lArc.circle.cy) < ε) { + previous = lArc.P; + disappearing.unshift(lArc); + d3_geom_voronoiDetachBeach(lArc); + lArc = previous; + } + disappearing.unshift(lArc); + d3_geom_voronoiDetachCircle(lArc); + var rArc = next; + while (rArc.circle && abs(x - rArc.circle.x) < ε && abs(y - rArc.circle.cy) < ε) { + next = rArc.N; + disappearing.push(rArc); + d3_geom_voronoiDetachBeach(rArc); + rArc = next; + } + disappearing.push(rArc); + d3_geom_voronoiDetachCircle(rArc); + var nArcs = disappearing.length, iArc; + for (iArc = 1; iArc < nArcs; ++iArc) { + rArc = disappearing[iArc]; + lArc = disappearing[iArc - 1]; + d3_geom_voronoiSetEdgeEnd(rArc.edge, lArc.site, rArc.site, vertex); + } + lArc = disappearing[0]; + rArc = disappearing[nArcs - 1]; + rArc.edge = d3_geom_voronoiCreateEdge(lArc.site, rArc.site, null, vertex); + d3_geom_voronoiAttachCircle(lArc); + d3_geom_voronoiAttachCircle(rArc); + } + function d3_geom_voronoiAddBeach(site) { + var x = site.x, directrix = site.y, lArc, rArc, dxl, dxr, node = d3_geom_voronoiBeaches._; + while (node) { + dxl = d3_geom_voronoiLeftBreakPoint(node, directrix) - x; + if (dxl > ε) node = node.L; else { + dxr = x - d3_geom_voronoiRightBreakPoint(node, directrix); + if (dxr > ε) { + if (!node.R) { + lArc = node; + break; + } + node = node.R; + } else { + if (dxl > -ε) { + lArc = node.P; + rArc = node; + } else if (dxr > -ε) { + lArc = node; + rArc = node.N; + } else { + lArc = rArc = node; + } + break; + } + } + } + var newArc = d3_geom_voronoiCreateBeach(site); + d3_geom_voronoiBeaches.insert(lArc, newArc); + if (!lArc && !rArc) return; + if (lArc === rArc) { + d3_geom_voronoiDetachCircle(lArc); + rArc = d3_geom_voronoiCreateBeach(lArc.site); + d3_geom_voronoiBeaches.insert(newArc, rArc); + newArc.edge = rArc.edge = d3_geom_voronoiCreateEdge(lArc.site, newArc.site); + d3_geom_voronoiAttachCircle(lArc); + d3_geom_voronoiAttachCircle(rArc); + return; + } + if (!rArc) { + newArc.edge = d3_geom_voronoiCreateEdge(lArc.site, newArc.site); + return; + } + d3_geom_voronoiDetachCircle(lArc); + d3_geom_voronoiDetachCircle(rArc); + var lSite = lArc.site, ax = lSite.x, ay = lSite.y, bx = site.x - ax, by = site.y - ay, rSite = rArc.site, cx = rSite.x - ax, cy = rSite.y - ay, d = 2 * (bx * cy - by * cx), hb = bx * bx + by * by, hc = cx * cx + cy * cy, vertex = { + x: (cy * hb - by * hc) / d + ax, + y: (bx * hc - cx * hb) / d + ay + }; + d3_geom_voronoiSetEdgeEnd(rArc.edge, lSite, rSite, vertex); + newArc.edge = d3_geom_voronoiCreateEdge(lSite, site, null, vertex); + rArc.edge = d3_geom_voronoiCreateEdge(site, rSite, null, vertex); + d3_geom_voronoiAttachCircle(lArc); + d3_geom_voronoiAttachCircle(rArc); + } + function d3_geom_voronoiLeftBreakPoint(arc, directrix) { + var site = arc.site, rfocx = site.x, rfocy = site.y, pby2 = rfocy - directrix; + if (!pby2) return rfocx; + var lArc = arc.P; + if (!lArc) return -Infinity; + site = lArc.site; + var lfocx = site.x, lfocy = site.y, plby2 = lfocy - directrix; + if (!plby2) return lfocx; + var hl = lfocx - rfocx, aby2 = 1 / pby2 - 1 / plby2, b = hl / plby2; + if (aby2) return (-b + Math.sqrt(b * b - 2 * aby2 * (hl * hl / (-2 * plby2) - lfocy + plby2 / 2 + rfocy - pby2 / 2))) / aby2 + rfocx; + return (rfocx + lfocx) / 2; + } + function d3_geom_voronoiRightBreakPoint(arc, directrix) { + var rArc = arc.N; + if (rArc) return d3_geom_voronoiLeftBreakPoint(rArc, directrix); + var site = arc.site; + return site.y === directrix ? site.x : Infinity; + } + function d3_geom_voronoiCell(site) { + this.site = site; + this.edges = []; + } + d3_geom_voronoiCell.prototype.prepare = function() { + var halfEdges = this.edges, iHalfEdge = halfEdges.length, edge; + while (iHalfEdge--) { + edge = halfEdges[iHalfEdge].edge; + if (!edge.b || !edge.a) halfEdges.splice(iHalfEdge, 1); + } + halfEdges.sort(d3_geom_voronoiHalfEdgeOrder); + return halfEdges.length; + }; + function d3_geom_voronoiCloseCells(extent) { + var x0 = extent[0][0], x1 = extent[1][0], y0 = extent[0][1], y1 = extent[1][1], x2, y2, x3, y3, cells = d3_geom_voronoiCells, iCell = cells.length, cell, iHalfEdge, halfEdges, nHalfEdges, start, end; + while (iCell--) { + cell = cells[iCell]; + if (!cell || !cell.prepare()) continue; + halfEdges = cell.edges; + nHalfEdges = halfEdges.length; + iHalfEdge = 0; + while (iHalfEdge < nHalfEdges) { + end = halfEdges[iHalfEdge].end(), x3 = end.x, y3 = end.y; + start = halfEdges[++iHalfEdge % nHalfEdges].start(), x2 = start.x, y2 = start.y; + if (abs(x3 - x2) > ε || abs(y3 - y2) > ε) { + halfEdges.splice(iHalfEdge, 0, new d3_geom_voronoiHalfEdge(d3_geom_voronoiCreateBorderEdge(cell.site, end, abs(x3 - x0) < ε && y1 - y3 > ε ? { + x: x0, + y: abs(x2 - x0) < ε ? y2 : y1 + } : abs(y3 - y1) < ε && x1 - x3 > ε ? { + x: abs(y2 - y1) < ε ? x2 : x1, + y: y1 + } : abs(x3 - x1) < ε && y3 - y0 > ε ? { + x: x1, + y: abs(x2 - x1) < ε ? y2 : y0 + } : abs(y3 - y0) < ε && x3 - x0 > ε ? { + x: abs(y2 - y0) < ε ? x2 : x0, + y: y0 + } : null), cell.site, null)); + ++nHalfEdges; + } + } + } + } + function d3_geom_voronoiHalfEdgeOrder(a, b) { + return b.angle - a.angle; + } + function d3_geom_voronoiCircle() { + d3_geom_voronoiRedBlackNode(this); + this.x = this.y = this.arc = this.site = this.cy = null; + } + function d3_geom_voronoiAttachCircle(arc) { + var lArc = arc.P, rArc = arc.N; + if (!lArc || !rArc) return; + var lSite = lArc.site, cSite = arc.site, rSite = rArc.site; + if (lSite === rSite) return; + var bx = cSite.x, by = cSite.y, ax = lSite.x - bx, ay = lSite.y - by, cx = rSite.x - bx, cy = rSite.y - by; + var d = 2 * (ax * cy - ay * cx); + if (d >= -ε2) return; + var ha = ax * ax + ay * ay, hc = cx * cx + cy * cy, x = (cy * ha - ay * hc) / d, y = (ax * hc - cx * ha) / d, cy = y + by; + var circle = d3_geom_voronoiCirclePool.pop() || new d3_geom_voronoiCircle(); + circle.arc = arc; + circle.site = cSite; + circle.x = x + bx; + circle.y = cy + Math.sqrt(x * x + y * y); + circle.cy = cy; + arc.circle = circle; + var before = null, node = d3_geom_voronoiCircles._; + while (node) { + if (circle.y < node.y || circle.y === node.y && circle.x <= node.x) { + if (node.L) node = node.L; else { + before = node.P; + break; + } + } else { + if (node.R) node = node.R; else { + before = node; + break; + } + } + } + d3_geom_voronoiCircles.insert(before, circle); + if (!before) d3_geom_voronoiFirstCircle = circle; + } + function d3_geom_voronoiDetachCircle(arc) { + var circle = arc.circle; + if (circle) { + if (!circle.P) d3_geom_voronoiFirstCircle = circle.N; + d3_geom_voronoiCircles.remove(circle); + d3_geom_voronoiCirclePool.push(circle); + d3_geom_voronoiRedBlackNode(circle); + arc.circle = null; + } + } + function d3_geom_voronoiClipEdges(extent) { + var edges = d3_geom_voronoiEdges, clip = d3_geom_clipLine(extent[0][0], extent[0][1], extent[1][0], extent[1][1]), i = edges.length, e; + while (i--) { + e = edges[i]; + if (!d3_geom_voronoiConnectEdge(e, extent) || !clip(e) || abs(e.a.x - e.b.x) < ε && abs(e.a.y - e.b.y) < ε) { + e.a = e.b = null; + edges.splice(i, 1); + } + } + } + function d3_geom_voronoiConnectEdge(edge, extent) { + var vb = edge.b; + if (vb) return true; + var va = edge.a, x0 = extent[0][0], x1 = extent[1][0], y0 = extent[0][1], y1 = extent[1][1], lSite = edge.l, rSite = edge.r, lx = lSite.x, ly = lSite.y, rx = rSite.x, ry = rSite.y, fx = (lx + rx) / 2, fy = (ly + ry) / 2, fm, fb; + if (ry === ly) { + if (fx < x0 || fx >= x1) return; + if (lx > rx) { + if (!va) va = { + x: fx, + y: y0 + }; else if (va.y >= y1) return; + vb = { + x: fx, + y: y1 + }; + } else { + if (!va) va = { + x: fx, + y: y1 + }; else if (va.y < y0) return; + vb = { + x: fx, + y: y0 + }; + } + } else { + fm = (lx - rx) / (ry - ly); + fb = fy - fm * fx; + if (fm < -1 || fm > 1) { + if (lx > rx) { + if (!va) va = { + x: (y0 - fb) / fm, + y: y0 + }; else if (va.y >= y1) return; + vb = { + x: (y1 - fb) / fm, + y: y1 + }; + } else { + if (!va) va = { + x: (y1 - fb) / fm, + y: y1 + }; else if (va.y < y0) return; + vb = { + x: (y0 - fb) / fm, + y: y0 + }; + } + } else { + if (ly < ry) { + if (!va) va = { + x: x0, + y: fm * x0 + fb + }; else if (va.x >= x1) return; + vb = { + x: x1, + y: fm * x1 + fb + }; + } else { + if (!va) va = { + x: x1, + y: fm * x1 + fb + }; else if (va.x < x0) return; + vb = { + x: x0, + y: fm * x0 + fb + }; + } + } + } + edge.a = va; + edge.b = vb; + return true; + } + function d3_geom_voronoiEdge(lSite, rSite) { + this.l = lSite; + this.r = rSite; + this.a = this.b = null; + } + function d3_geom_voronoiCreateEdge(lSite, rSite, va, vb) { + var edge = new d3_geom_voronoiEdge(lSite, rSite); + d3_geom_voronoiEdges.push(edge); + if (va) d3_geom_voronoiSetEdgeEnd(edge, lSite, rSite, va); + if (vb) d3_geom_voronoiSetEdgeEnd(edge, rSite, lSite, vb); + d3_geom_voronoiCells[lSite.i].edges.push(new d3_geom_voronoiHalfEdge(edge, lSite, rSite)); + d3_geom_voronoiCells[rSite.i].edges.push(new d3_geom_voronoiHalfEdge(edge, rSite, lSite)); + return edge; + } + function d3_geom_voronoiCreateBorderEdge(lSite, va, vb) { + var edge = new d3_geom_voronoiEdge(lSite, null); + edge.a = va; + edge.b = vb; + d3_geom_voronoiEdges.push(edge); + return edge; + } + function d3_geom_voronoiSetEdgeEnd(edge, lSite, rSite, vertex) { + if (!edge.a && !edge.b) { + edge.a = vertex; + edge.l = lSite; + edge.r = rSite; + } else if (edge.l === rSite) { + edge.b = vertex; + } else { + edge.a = vertex; + } + } + function d3_geom_voronoiHalfEdge(edge, lSite, rSite) { + var va = edge.a, vb = edge.b; + this.edge = edge; + this.site = lSite; + this.angle = rSite ? Math.atan2(rSite.y - lSite.y, rSite.x - lSite.x) : edge.l === lSite ? Math.atan2(vb.x - va.x, va.y - vb.y) : Math.atan2(va.x - vb.x, vb.y - va.y); + } + d3_geom_voronoiHalfEdge.prototype = { + start: function() { + return this.edge.l === this.site ? this.edge.a : this.edge.b; + }, + end: function() { + return this.edge.l === this.site ? this.edge.b : this.edge.a; + } + }; + function d3_geom_voronoiRedBlackTree() { + this._ = null; + } + function d3_geom_voronoiRedBlackNode(node) { + node.U = node.C = node.L = node.R = node.P = node.N = null; + } + d3_geom_voronoiRedBlackTree.prototype = { + insert: function(after, node) { + var parent, grandpa, uncle; + if (after) { + node.P = after; + node.N = after.N; + if (after.N) after.N.P = node; + after.N = node; + if (after.R) { + after = after.R; + while (after.L) after = after.L; + after.L = node; + } else { + after.R = node; + } + parent = after; + } else if (this._) { + after = d3_geom_voronoiRedBlackFirst(this._); + node.P = null; + node.N = after; + after.P = after.L = node; + parent = after; + } else { + node.P = node.N = null; + this._ = node; + parent = null; + } + node.L = node.R = null; + node.U = parent; + node.C = true; + after = node; + while (parent && parent.C) { + grandpa = parent.U; + if (parent === grandpa.L) { + uncle = grandpa.R; + if (uncle && uncle.C) { + parent.C = uncle.C = false; + grandpa.C = true; + after = grandpa; + } else { + if (after === parent.R) { + d3_geom_voronoiRedBlackRotateLeft(this, parent); + after = parent; + parent = after.U; + } + parent.C = false; + grandpa.C = true; + d3_geom_voronoiRedBlackRotateRight(this, grandpa); + } + } else { + uncle = grandpa.L; + if (uncle && uncle.C) { + parent.C = uncle.C = false; + grandpa.C = true; + after = grandpa; + } else { + if (after === parent.L) { + d3_geom_voronoiRedBlackRotateRight(this, parent); + after = parent; + parent = after.U; + } + parent.C = false; + grandpa.C = true; + d3_geom_voronoiRedBlackRotateLeft(this, grandpa); + } + } + parent = after.U; + } + this._.C = false; + }, + remove: function(node) { + if (node.N) node.N.P = node.P; + if (node.P) node.P.N = node.N; + node.N = node.P = null; + var parent = node.U, sibling, left = node.L, right = node.R, next, red; + if (!left) next = right; else if (!right) next = left; else next = d3_geom_voronoiRedBlackFirst(right); + if (parent) { + if (parent.L === node) parent.L = next; else parent.R = next; + } else { + this._ = next; + } + if (left && right) { + red = next.C; + next.C = node.C; + next.L = left; + left.U = next; + if (next !== right) { + parent = next.U; + next.U = node.U; + node = next.R; + parent.L = node; + next.R = right; + right.U = next; + } else { + next.U = parent; + parent = next; + node = next.R; + } + } else { + red = node.C; + node = next; + } + if (node) node.U = parent; + if (red) return; + if (node && node.C) { + node.C = false; + return; + } + do { + if (node === this._) break; + if (node === parent.L) { + sibling = parent.R; + if (sibling.C) { + sibling.C = false; + parent.C = true; + d3_geom_voronoiRedBlackRotateLeft(this, parent); + sibling = parent.R; + } + if (sibling.L && sibling.L.C || sibling.R && sibling.R.C) { + if (!sibling.R || !sibling.R.C) { + sibling.L.C = false; + sibling.C = true; + d3_geom_voronoiRedBlackRotateRight(this, sibling); + sibling = parent.R; + } + sibling.C = parent.C; + parent.C = sibling.R.C = false; + d3_geom_voronoiRedBlackRotateLeft(this, parent); + node = this._; + break; + } + } else { + sibling = parent.L; + if (sibling.C) { + sibling.C = false; + parent.C = true; + d3_geom_voronoiRedBlackRotateRight(this, parent); + sibling = parent.L; + } + if (sibling.L && sibling.L.C || sibling.R && sibling.R.C) { + if (!sibling.L || !sibling.L.C) { + sibling.R.C = false; + sibling.C = true; + d3_geom_voronoiRedBlackRotateLeft(this, sibling); + sibling = parent.L; + } + sibling.C = parent.C; + parent.C = sibling.L.C = false; + d3_geom_voronoiRedBlackRotateRight(this, parent); + node = this._; + break; + } + } + sibling.C = true; + node = parent; + parent = parent.U; + } while (!node.C); + if (node) node.C = false; + } + }; + function d3_geom_voronoiRedBlackRotateLeft(tree, node) { + var p = node, q = node.R, parent = p.U; + if (parent) { + if (parent.L === p) parent.L = q; else parent.R = q; + } else { + tree._ = q; + } + q.U = parent; + p.U = q; + p.R = q.L; + if (p.R) p.R.U = p; + q.L = p; + } + function d3_geom_voronoiRedBlackRotateRight(tree, node) { + var p = node, q = node.L, parent = p.U; + if (parent) { + if (parent.L === p) parent.L = q; else parent.R = q; + } else { + tree._ = q; + } + q.U = parent; + p.U = q; + p.L = q.R; + if (p.L) p.L.U = p; + q.R = p; + } + function d3_geom_voronoiRedBlackFirst(node) { + while (node.L) node = node.L; + return node; + } + function d3_geom_voronoi(sites, bbox) { + var site = sites.sort(d3_geom_voronoiVertexOrder).pop(), x0, y0, circle; + d3_geom_voronoiEdges = []; + d3_geom_voronoiCells = new Array(sites.length); + d3_geom_voronoiBeaches = new d3_geom_voronoiRedBlackTree(); + d3_geom_voronoiCircles = new d3_geom_voronoiRedBlackTree(); + while (true) { + circle = d3_geom_voronoiFirstCircle; + if (site && (!circle || site.y < circle.y || site.y === circle.y && site.x < circle.x)) { + if (site.x !== x0 || site.y !== y0) { + d3_geom_voronoiCells[site.i] = new d3_geom_voronoiCell(site); + d3_geom_voronoiAddBeach(site); + x0 = site.x, y0 = site.y; + } + site = sites.pop(); + } else if (circle) { + d3_geom_voronoiRemoveBeach(circle.arc); + } else { + break; + } + } + if (bbox) d3_geom_voronoiClipEdges(bbox), d3_geom_voronoiCloseCells(bbox); + var diagram = { + cells: d3_geom_voronoiCells, + edges: d3_geom_voronoiEdges + }; + d3_geom_voronoiBeaches = d3_geom_voronoiCircles = d3_geom_voronoiEdges = d3_geom_voronoiCells = null; + return diagram; + } + function d3_geom_voronoiVertexOrder(a, b) { + return b.y - a.y || b.x - a.x; + } + d3.geom.voronoi = function(points) { + var x = d3_geom_pointX, y = d3_geom_pointY, fx = x, fy = y, clipExtent = d3_geom_voronoiClipExtent; + if (points) return voronoi(points); + function voronoi(data) { + var polygons = new Array(data.length), x0 = clipExtent[0][0], y0 = clipExtent[0][1], x1 = clipExtent[1][0], y1 = clipExtent[1][1]; + d3_geom_voronoi(sites(data), clipExtent).cells.forEach(function(cell, i) { + var edges = cell.edges, site = cell.site, polygon = polygons[i] = edges.length ? edges.map(function(e) { + var s = e.start(); + return [ s.x, s.y ]; + }) : site.x >= x0 && site.x <= x1 && site.y >= y0 && site.y <= y1 ? [ [ x0, y1 ], [ x1, y1 ], [ x1, y0 ], [ x0, y0 ] ] : []; + polygon.point = data[i]; + }); + return polygons; + } + function sites(data) { + return data.map(function(d, i) { + return { + x: Math.round(fx(d, i) / ε) * ε, + y: Math.round(fy(d, i) / ε) * ε, + i: i + }; + }); + } + voronoi.links = function(data) { + return d3_geom_voronoi(sites(data)).edges.filter(function(edge) { + return edge.l && edge.r; + }).map(function(edge) { + return { + source: data[edge.l.i], + target: data[edge.r.i] + }; + }); + }; + voronoi.triangles = function(data) { + var triangles = []; + d3_geom_voronoi(sites(data)).cells.forEach(function(cell, i) { + var site = cell.site, edges = cell.edges.sort(d3_geom_voronoiHalfEdgeOrder), j = -1, m = edges.length, e0, s0, e1 = edges[m - 1].edge, s1 = e1.l === site ? e1.r : e1.l; + while (++j < m) { + e0 = e1; + s0 = s1; + e1 = edges[j].edge; + s1 = e1.l === site ? e1.r : e1.l; + if (i < s0.i && i < s1.i && d3_geom_voronoiTriangleArea(site, s0, s1) < 0) { + triangles.push([ data[i], data[s0.i], data[s1.i] ]); + } + } + }); + return triangles; + }; + voronoi.x = function(_) { + return arguments.length ? (fx = d3_functor(x = _), voronoi) : x; + }; + voronoi.y = function(_) { + return arguments.length ? (fy = d3_functor(y = _), voronoi) : y; + }; + voronoi.clipExtent = function(_) { + if (!arguments.length) return clipExtent === d3_geom_voronoiClipExtent ? null : clipExtent; + clipExtent = _ == null ? d3_geom_voronoiClipExtent : _; + return voronoi; + }; + voronoi.size = function(_) { + if (!arguments.length) return clipExtent === d3_geom_voronoiClipExtent ? null : clipExtent && clipExtent[1]; + return voronoi.clipExtent(_ && [ [ 0, 0 ], _ ]); + }; + return voronoi; + }; + var d3_geom_voronoiClipExtent = [ [ -1e6, -1e6 ], [ 1e6, 1e6 ] ]; + function d3_geom_voronoiTriangleArea(a, b, c) { + return (a.x - c.x) * (b.y - a.y) - (a.x - b.x) * (c.y - a.y); + } + d3.geom.delaunay = function(vertices) { + return d3.geom.voronoi().triangles(vertices); + }; + d3.geom.quadtree = function(points, x1, y1, x2, y2) { + var x = d3_geom_pointX, y = d3_geom_pointY, compat; + if (compat = arguments.length) { + x = d3_geom_quadtreeCompatX; + y = d3_geom_quadtreeCompatY; + if (compat === 3) { + y2 = y1; + x2 = x1; + y1 = x1 = 0; + } + return quadtree(points); + } + function quadtree(data) { + var d, fx = d3_functor(x), fy = d3_functor(y), xs, ys, i, n, x1_, y1_, x2_, y2_; + if (x1 != null) { + x1_ = x1, y1_ = y1, x2_ = x2, y2_ = y2; + } else { + x2_ = y2_ = -(x1_ = y1_ = Infinity); + xs = [], ys = []; + n = data.length; + if (compat) for (i = 0; i < n; ++i) { + d = data[i]; + if (d.x < x1_) x1_ = d.x; + if (d.y < y1_) y1_ = d.y; + if (d.x > x2_) x2_ = d.x; + if (d.y > y2_) y2_ = d.y; + xs.push(d.x); + ys.push(d.y); + } else for (i = 0; i < n; ++i) { + var x_ = +fx(d = data[i], i), y_ = +fy(d, i); + if (x_ < x1_) x1_ = x_; + if (y_ < y1_) y1_ = y_; + if (x_ > x2_) x2_ = x_; + if (y_ > y2_) y2_ = y_; + xs.push(x_); + ys.push(y_); + } + } + var dx = x2_ - x1_, dy = y2_ - y1_; + if (dx > dy) y2_ = y1_ + dx; else x2_ = x1_ + dy; + function insert(n, d, x, y, x1, y1, x2, y2) { + if (isNaN(x) || isNaN(y)) return; + if (n.leaf) { + var nx = n.x, ny = n.y; + if (nx != null) { + if (abs(nx - x) + abs(ny - y) < .01) { + insertChild(n, d, x, y, x1, y1, x2, y2); + } else { + var nPoint = n.point; + n.x = n.y = n.point = null; + insertChild(n, nPoint, nx, ny, x1, y1, x2, y2); + insertChild(n, d, x, y, x1, y1, x2, y2); + } + } else { + n.x = x, n.y = y, n.point = d; + } + } else { + insertChild(n, d, x, y, x1, y1, x2, y2); + } + } + function insertChild(n, d, x, y, x1, y1, x2, y2) { + var xm = (x1 + x2) * .5, ym = (y1 + y2) * .5, right = x >= xm, below = y >= ym, i = below << 1 | right; + n.leaf = false; + n = n.nodes[i] || (n.nodes[i] = d3_geom_quadtreeNode()); + if (right) x1 = xm; else x2 = xm; + if (below) y1 = ym; else y2 = ym; + insert(n, d, x, y, x1, y1, x2, y2); + } + var root = d3_geom_quadtreeNode(); + root.add = function(d) { + insert(root, d, +fx(d, ++i), +fy(d, i), x1_, y1_, x2_, y2_); + }; + root.visit = function(f) { + d3_geom_quadtreeVisit(f, root, x1_, y1_, x2_, y2_); + }; + root.find = function(point) { + return d3_geom_quadtreeFind(root, point[0], point[1], x1_, y1_, x2_, y2_); + }; + i = -1; + if (x1 == null) { + while (++i < n) { + insert(root, data[i], xs[i], ys[i], x1_, y1_, x2_, y2_); + } + --i; + } else data.forEach(root.add); + xs = ys = data = d = null; + return root; + } + quadtree.x = function(_) { + return arguments.length ? (x = _, quadtree) : x; + }; + quadtree.y = function(_) { + return arguments.length ? (y = _, quadtree) : y; + }; + quadtree.extent = function(_) { + if (!arguments.length) return x1 == null ? null : [ [ x1, y1 ], [ x2, y2 ] ]; + if (_ == null) x1 = y1 = x2 = y2 = null; else x1 = +_[0][0], y1 = +_[0][1], x2 = +_[1][0], + y2 = +_[1][1]; + return quadtree; + }; + quadtree.size = function(_) { + if (!arguments.length) return x1 == null ? null : [ x2 - x1, y2 - y1 ]; + if (_ == null) x1 = y1 = x2 = y2 = null; else x1 = y1 = 0, x2 = +_[0], y2 = +_[1]; + return quadtree; + }; + return quadtree; + }; + function d3_geom_quadtreeCompatX(d) { + return d.x; + } + function d3_geom_quadtreeCompatY(d) { + return d.y; + } + function d3_geom_quadtreeNode() { + return { + leaf: true, + nodes: [], + point: null, + x: null, + y: null + }; + } + function d3_geom_quadtreeVisit(f, node, x1, y1, x2, y2) { + if (!f(node, x1, y1, x2, y2)) { + var sx = (x1 + x2) * .5, sy = (y1 + y2) * .5, children = node.nodes; + if (children[0]) d3_geom_quadtreeVisit(f, children[0], x1, y1, sx, sy); + if (children[1]) d3_geom_quadtreeVisit(f, children[1], sx, y1, x2, sy); + if (children[2]) d3_geom_quadtreeVisit(f, children[2], x1, sy, sx, y2); + if (children[3]) d3_geom_quadtreeVisit(f, children[3], sx, sy, x2, y2); + } + } + function d3_geom_quadtreeFind(root, x, y, x0, y0, x3, y3) { + var minDistance2 = Infinity, closestPoint; + (function find(node, x1, y1, x2, y2) { + if (x1 > x3 || y1 > y3 || x2 < x0 || y2 < y0) return; + if (point = node.point) { + var point, dx = x - node.x, dy = y - node.y, distance2 = dx * dx + dy * dy; + if (distance2 < minDistance2) { + var distance = Math.sqrt(minDistance2 = distance2); + x0 = x - distance, y0 = y - distance; + x3 = x + distance, y3 = y + distance; + closestPoint = point; + } + } + var children = node.nodes, xm = (x1 + x2) * .5, ym = (y1 + y2) * .5, right = x >= xm, below = y >= ym; + for (var i = below << 1 | right, j = i + 4; i < j; ++i) { + if (node = children[i & 3]) switch (i & 3) { + case 0: + find(node, x1, y1, xm, ym); + break; + + case 1: + find(node, xm, y1, x2, ym); + break; + + case 2: + find(node, x1, ym, xm, y2); + break; + + case 3: + find(node, xm, ym, x2, y2); + break; + } + } + })(root, x0, y0, x3, y3); + return closestPoint; + } + d3.interpolateRgb = d3_interpolateRgb; + function d3_interpolateRgb(a, b) { + a = d3.rgb(a); + b = d3.rgb(b); + var ar = a.r, ag = a.g, ab = a.b, br = b.r - ar, bg = b.g - ag, bb = b.b - ab; + return function(t) { + return "#" + d3_rgb_hex(Math.round(ar + br * t)) + d3_rgb_hex(Math.round(ag + bg * t)) + d3_rgb_hex(Math.round(ab + bb * t)); + }; + } + d3.interpolateObject = d3_interpolateObject; + function d3_interpolateObject(a, b) { + var i = {}, c = {}, k; + for (k in a) { + if (k in b) { + i[k] = d3_interpolate(a[k], b[k]); + } else { + c[k] = a[k]; + } + } + for (k in b) { + if (!(k in a)) { + c[k] = b[k]; + } + } + return function(t) { + for (k in i) c[k] = i[k](t); + return c; + }; + } + d3.interpolateNumber = d3_interpolateNumber; + function d3_interpolateNumber(a, b) { + a = +a, b = +b; + return function(t) { + return a * (1 - t) + b * t; + }; + } + d3.interpolateString = d3_interpolateString; + function d3_interpolateString(a, b) { + var bi = d3_interpolate_numberA.lastIndex = d3_interpolate_numberB.lastIndex = 0, am, bm, bs, i = -1, s = [], q = []; + a = a + "", b = b + ""; + while ((am = d3_interpolate_numberA.exec(a)) && (bm = d3_interpolate_numberB.exec(b))) { + if ((bs = bm.index) > bi) { + bs = b.slice(bi, bs); + if (s[i]) s[i] += bs; else s[++i] = bs; + } + if ((am = am[0]) === (bm = bm[0])) { + if (s[i]) s[i] += bm; else s[++i] = bm; + } else { + s[++i] = null; + q.push({ + i: i, + x: d3_interpolateNumber(am, bm) + }); + } + bi = d3_interpolate_numberB.lastIndex; + } + if (bi < b.length) { + bs = b.slice(bi); + if (s[i]) s[i] += bs; else s[++i] = bs; + } + return s.length < 2 ? q[0] ? (b = q[0].x, function(t) { + return b(t) + ""; + }) : function() { + return b; + } : (b = q.length, function(t) { + for (var i = 0, o; i < b; ++i) s[(o = q[i]).i] = o.x(t); + return s.join(""); + }); + } + var d3_interpolate_numberA = /[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g, d3_interpolate_numberB = new RegExp(d3_interpolate_numberA.source, "g"); + d3.interpolate = d3_interpolate; + function d3_interpolate(a, b) { + var i = d3.interpolators.length, f; + while (--i >= 0 && !(f = d3.interpolators[i](a, b))) ; + return f; + } + d3.interpolators = [ function(a, b) { + var t = typeof b; + return (t === "string" ? d3_rgb_names.has(b.toLowerCase()) || /^(#|rgb\(|hsl\()/i.test(b) ? d3_interpolateRgb : d3_interpolateString : b instanceof d3_color ? d3_interpolateRgb : Array.isArray(b) ? d3_interpolateArray : t === "object" && isNaN(b) ? d3_interpolateObject : d3_interpolateNumber)(a, b); + } ]; + d3.interpolateArray = d3_interpolateArray; + function d3_interpolateArray(a, b) { + var x = [], c = [], na = a.length, nb = b.length, n0 = Math.min(a.length, b.length), i; + for (i = 0; i < n0; ++i) x.push(d3_interpolate(a[i], b[i])); + for (;i < na; ++i) c[i] = a[i]; + for (;i < nb; ++i) c[i] = b[i]; + return function(t) { + for (i = 0; i < n0; ++i) c[i] = x[i](t); + return c; + }; + } + var d3_ease_default = function() { + return d3_identity; + }; + var d3_ease = d3.map({ + linear: d3_ease_default, + poly: d3_ease_poly, + quad: function() { + return d3_ease_quad; + }, + cubic: function() { + return d3_ease_cubic; + }, + sin: function() { + return d3_ease_sin; + }, + exp: function() { + return d3_ease_exp; + }, + circle: function() { + return d3_ease_circle; + }, + elastic: d3_ease_elastic, + back: d3_ease_back, + bounce: function() { + return d3_ease_bounce; + } + }); + var d3_ease_mode = d3.map({ + "in": d3_identity, + out: d3_ease_reverse, + "in-out": d3_ease_reflect, + "out-in": function(f) { + return d3_ease_reflect(d3_ease_reverse(f)); + } + }); + d3.ease = function(name) { + var i = name.indexOf("-"), t = i >= 0 ? name.slice(0, i) : name, m = i >= 0 ? name.slice(i + 1) : "in"; + t = d3_ease.get(t) || d3_ease_default; + m = d3_ease_mode.get(m) || d3_identity; + return d3_ease_clamp(m(t.apply(null, d3_arraySlice.call(arguments, 1)))); + }; + function d3_ease_clamp(f) { + return function(t) { + return t <= 0 ? 0 : t >= 1 ? 1 : f(t); + }; + } + function d3_ease_reverse(f) { + return function(t) { + return 1 - f(1 - t); + }; + } + function d3_ease_reflect(f) { + return function(t) { + return .5 * (t < .5 ? f(2 * t) : 2 - f(2 - 2 * t)); + }; + } + function d3_ease_quad(t) { + return t * t; + } + function d3_ease_cubic(t) { + return t * t * t; + } + function d3_ease_cubicInOut(t) { + if (t <= 0) return 0; + if (t >= 1) return 1; + var t2 = t * t, t3 = t2 * t; + return 4 * (t < .5 ? t3 : 3 * (t - t2) + t3 - .75); + } + function d3_ease_poly(e) { + return function(t) { + return Math.pow(t, e); + }; + } + function d3_ease_sin(t) { + return 1 - Math.cos(t * halfπ); + } + function d3_ease_exp(t) { + return Math.pow(2, 10 * (t - 1)); + } + function d3_ease_circle(t) { + return 1 - Math.sqrt(1 - t * t); + } + function d3_ease_elastic(a, p) { + var s; + if (arguments.length < 2) p = .45; + if (arguments.length) s = p / τ * Math.asin(1 / a); else a = 1, s = p / 4; + return function(t) { + return 1 + a * Math.pow(2, -10 * t) * Math.sin((t - s) * τ / p); + }; + } + function d3_ease_back(s) { + if (!s) s = 1.70158; + return function(t) { + return t * t * ((s + 1) * t - s); + }; + } + function d3_ease_bounce(t) { + return t < 1 / 2.75 ? 7.5625 * t * t : t < 2 / 2.75 ? 7.5625 * (t -= 1.5 / 2.75) * t + .75 : t < 2.5 / 2.75 ? 7.5625 * (t -= 2.25 / 2.75) * t + .9375 : 7.5625 * (t -= 2.625 / 2.75) * t + .984375; + } + d3.interpolateHcl = d3_interpolateHcl; + function d3_interpolateHcl(a, b) { + a = d3.hcl(a); + b = d3.hcl(b); + var ah = a.h, ac = a.c, al = a.l, bh = b.h - ah, bc = b.c - ac, bl = b.l - al; + if (isNaN(bc)) bc = 0, ac = isNaN(ac) ? b.c : ac; + if (isNaN(bh)) bh = 0, ah = isNaN(ah) ? b.h : ah; else if (bh > 180) bh -= 360; else if (bh < -180) bh += 360; + return function(t) { + return d3_hcl_lab(ah + bh * t, ac + bc * t, al + bl * t) + ""; + }; + } + d3.interpolateHsl = d3_interpolateHsl; + function d3_interpolateHsl(a, b) { + a = d3.hsl(a); + b = d3.hsl(b); + var ah = a.h, as = a.s, al = a.l, bh = b.h - ah, bs = b.s - as, bl = b.l - al; + if (isNaN(bs)) bs = 0, as = isNaN(as) ? b.s : as; + if (isNaN(bh)) bh = 0, ah = isNaN(ah) ? b.h : ah; else if (bh > 180) bh -= 360; else if (bh < -180) bh += 360; + return function(t) { + return d3_hsl_rgb(ah + bh * t, as + bs * t, al + bl * t) + ""; + }; + } + d3.interpolateLab = d3_interpolateLab; + function d3_interpolateLab(a, b) { + a = d3.lab(a); + b = d3.lab(b); + var al = a.l, aa = a.a, ab = a.b, bl = b.l - al, ba = b.a - aa, bb = b.b - ab; + return function(t) { + return d3_lab_rgb(al + bl * t, aa + ba * t, ab + bb * t) + ""; + }; + } + d3.interpolateRound = d3_interpolateRound; + function d3_interpolateRound(a, b) { + b -= a; + return function(t) { + return Math.round(a + b * t); + }; + } + d3.transform = function(string) { + var g = d3_document.createElementNS(d3.ns.prefix.svg, "g"); + return (d3.transform = function(string) { + if (string != null) { + g.setAttribute("transform", string); + var t = g.transform.baseVal.consolidate(); + } + return new d3_transform(t ? t.matrix : d3_transformIdentity); + })(string); + }; + function d3_transform(m) { + var r0 = [ m.a, m.b ], r1 = [ m.c, m.d ], kx = d3_transformNormalize(r0), kz = d3_transformDot(r0, r1), ky = d3_transformNormalize(d3_transformCombine(r1, r0, -kz)) || 0; + if (r0[0] * r1[1] < r1[0] * r0[1]) { + r0[0] *= -1; + r0[1] *= -1; + kx *= -1; + kz *= -1; + } + this.rotate = (kx ? Math.atan2(r0[1], r0[0]) : Math.atan2(-r1[0], r1[1])) * d3_degrees; + this.translate = [ m.e, m.f ]; + this.scale = [ kx, ky ]; + this.skew = ky ? Math.atan2(kz, ky) * d3_degrees : 0; + } + d3_transform.prototype.toString = function() { + return "translate(" + this.translate + ")rotate(" + this.rotate + ")skewX(" + this.skew + ")scale(" + this.scale + ")"; + }; + function d3_transformDot(a, b) { + return a[0] * b[0] + a[1] * b[1]; + } + function d3_transformNormalize(a) { + var k = Math.sqrt(d3_transformDot(a, a)); + if (k) { + a[0] /= k; + a[1] /= k; + } + return k; + } + function d3_transformCombine(a, b, k) { + a[0] += k * b[0]; + a[1] += k * b[1]; + return a; + } + var d3_transformIdentity = { + a: 1, + b: 0, + c: 0, + d: 1, + e: 0, + f: 0 + }; + d3.interpolateTransform = d3_interpolateTransform; + function d3_interpolateTransformPop(s) { + return s.length ? s.pop() + "," : ""; + } + function d3_interpolateTranslate(ta, tb, s, q) { + if (ta[0] !== tb[0] || ta[1] !== tb[1]) { + var i = s.push("translate(", null, ",", null, ")"); + q.push({ + i: i - 4, + x: d3_interpolateNumber(ta[0], tb[0]) + }, { + i: i - 2, + x: d3_interpolateNumber(ta[1], tb[1]) + }); + } else if (tb[0] || tb[1]) { + s.push("translate(" + tb + ")"); + } + } + function d3_interpolateRotate(ra, rb, s, q) { + if (ra !== rb) { + if (ra - rb > 180) rb += 360; else if (rb - ra > 180) ra += 360; + q.push({ + i: s.push(d3_interpolateTransformPop(s) + "rotate(", null, ")") - 2, + x: d3_interpolateNumber(ra, rb) + }); + } else if (rb) { + s.push(d3_interpolateTransformPop(s) + "rotate(" + rb + ")"); + } + } + function d3_interpolateSkew(wa, wb, s, q) { + if (wa !== wb) { + q.push({ + i: s.push(d3_interpolateTransformPop(s) + "skewX(", null, ")") - 2, + x: d3_interpolateNumber(wa, wb) + }); + } else if (wb) { + s.push(d3_interpolateTransformPop(s) + "skewX(" + wb + ")"); + } + } + function d3_interpolateScale(ka, kb, s, q) { + if (ka[0] !== kb[0] || ka[1] !== kb[1]) { + var i = s.push(d3_interpolateTransformPop(s) + "scale(", null, ",", null, ")"); + q.push({ + i: i - 4, + x: d3_interpolateNumber(ka[0], kb[0]) + }, { + i: i - 2, + x: d3_interpolateNumber(ka[1], kb[1]) + }); + } else if (kb[0] !== 1 || kb[1] !== 1) { + s.push(d3_interpolateTransformPop(s) + "scale(" + kb + ")"); + } + } + function d3_interpolateTransform(a, b) { + var s = [], q = []; + a = d3.transform(a), b = d3.transform(b); + d3_interpolateTranslate(a.translate, b.translate, s, q); + d3_interpolateRotate(a.rotate, b.rotate, s, q); + d3_interpolateSkew(a.skew, b.skew, s, q); + d3_interpolateScale(a.scale, b.scale, s, q); + a = b = null; + return function(t) { + var i = -1, n = q.length, o; + while (++i < n) s[(o = q[i]).i] = o.x(t); + return s.join(""); + }; + } + function d3_uninterpolateNumber(a, b) { + b = (b -= a = +a) || 1 / b; + return function(x) { + return (x - a) / b; + }; + } + function d3_uninterpolateClamp(a, b) { + b = (b -= a = +a) || 1 / b; + return function(x) { + return Math.max(0, Math.min(1, (x - a) / b)); + }; + } + d3.layout = {}; + d3.layout.bundle = function() { + return function(links) { + var paths = [], i = -1, n = links.length; + while (++i < n) paths.push(d3_layout_bundlePath(links[i])); + return paths; + }; + }; + function d3_layout_bundlePath(link) { + var start = link.source, end = link.target, lca = d3_layout_bundleLeastCommonAncestor(start, end), points = [ start ]; + while (start !== lca) { + start = start.parent; + points.push(start); + } + var k = points.length; + while (end !== lca) { + points.splice(k, 0, end); + end = end.parent; + } + return points; + } + function d3_layout_bundleAncestors(node) { + var ancestors = [], parent = node.parent; + while (parent != null) { + ancestors.push(node); + node = parent; + parent = parent.parent; + } + ancestors.push(node); + return ancestors; + } + function d3_layout_bundleLeastCommonAncestor(a, b) { + if (a === b) return a; + var aNodes = d3_layout_bundleAncestors(a), bNodes = d3_layout_bundleAncestors(b), aNode = aNodes.pop(), bNode = bNodes.pop(), sharedNode = null; + while (aNode === bNode) { + sharedNode = aNode; + aNode = aNodes.pop(); + bNode = bNodes.pop(); + } + return sharedNode; + } + d3.layout.chord = function() { + var chord = {}, chords, groups, matrix, n, padding = 0, sortGroups, sortSubgroups, sortChords; + function relayout() { + var subgroups = {}, groupSums = [], groupIndex = d3.range(n), subgroupIndex = [], k, x, x0, i, j; + chords = []; + groups = []; + k = 0, i = -1; + while (++i < n) { + x = 0, j = -1; + while (++j < n) { + x += matrix[i][j]; + } + groupSums.push(x); + subgroupIndex.push(d3.range(n)); + k += x; + } + if (sortGroups) { + groupIndex.sort(function(a, b) { + return sortGroups(groupSums[a], groupSums[b]); + }); + } + if (sortSubgroups) { + subgroupIndex.forEach(function(d, i) { + d.sort(function(a, b) { + return sortSubgroups(matrix[i][a], matrix[i][b]); + }); + }); + } + k = (τ - padding * n) / k; + x = 0, i = -1; + while (++i < n) { + x0 = x, j = -1; + while (++j < n) { + var di = groupIndex[i], dj = subgroupIndex[di][j], v = matrix[di][dj], a0 = x, a1 = x += v * k; + subgroups[di + "-" + dj] = { + index: di, + subindex: dj, + startAngle: a0, + endAngle: a1, + value: v + }; + } + groups[di] = { + index: di, + startAngle: x0, + endAngle: x, + value: groupSums[di] + }; + x += padding; + } + i = -1; + while (++i < n) { + j = i - 1; + while (++j < n) { + var source = subgroups[i + "-" + j], target = subgroups[j + "-" + i]; + if (source.value || target.value) { + chords.push(source.value < target.value ? { + source: target, + target: source + } : { + source: source, + target: target + }); + } + } + } + if (sortChords) resort(); + } + function resort() { + chords.sort(function(a, b) { + return sortChords((a.source.value + a.target.value) / 2, (b.source.value + b.target.value) / 2); + }); + } + chord.matrix = function(x) { + if (!arguments.length) return matrix; + n = (matrix = x) && matrix.length; + chords = groups = null; + return chord; + }; + chord.padding = function(x) { + if (!arguments.length) return padding; + padding = x; + chords = groups = null; + return chord; + }; + chord.sortGroups = function(x) { + if (!arguments.length) return sortGroups; + sortGroups = x; + chords = groups = null; + return chord; + }; + chord.sortSubgroups = function(x) { + if (!arguments.length) return sortSubgroups; + sortSubgroups = x; + chords = null; + return chord; + }; + chord.sortChords = function(x) { + if (!arguments.length) return sortChords; + sortChords = x; + if (chords) resort(); + return chord; + }; + chord.chords = function() { + if (!chords) relayout(); + return chords; + }; + chord.groups = function() { + if (!groups) relayout(); + return groups; + }; + return chord; + }; + d3.layout.force = function() { + var force = {}, event = d3.dispatch("start", "tick", "end"), timer, size = [ 1, 1 ], drag, alpha, friction = .9, linkDistance = d3_layout_forceLinkDistance, linkStrength = d3_layout_forceLinkStrength, charge = -30, chargeDistance2 = d3_layout_forceChargeDistance2, gravity = .1, theta2 = .64, nodes = [], links = [], distances, strengths, charges; + function repulse(node) { + return function(quad, x1, _, x2) { + if (quad.point !== node) { + var dx = quad.cx - node.x, dy = quad.cy - node.y, dw = x2 - x1, dn = dx * dx + dy * dy; + if (dw * dw / theta2 < dn) { + if (dn < chargeDistance2) { + var k = quad.charge / dn; + node.px -= dx * k; + node.py -= dy * k; + } + return true; + } + if (quad.point && dn && dn < chargeDistance2) { + var k = quad.pointCharge / dn; + node.px -= dx * k; + node.py -= dy * k; + } + } + return !quad.charge; + }; + } + force.tick = function() { + if ((alpha *= .99) < .005) { + timer = null; + event.end({ + type: "end", + alpha: alpha = 0 + }); + return true; + } + var n = nodes.length, m = links.length, q, i, o, s, t, l, k, x, y; + for (i = 0; i < m; ++i) { + o = links[i]; + s = o.source; + t = o.target; + x = t.x - s.x; + y = t.y - s.y; + if (l = x * x + y * y) { + l = alpha * strengths[i] * ((l = Math.sqrt(l)) - distances[i]) / l; + x *= l; + y *= l; + t.x -= x * (k = s.weight + t.weight ? s.weight / (s.weight + t.weight) : .5); + t.y -= y * k; + s.x += x * (k = 1 - k); + s.y += y * k; + } + } + if (k = alpha * gravity) { + x = size[0] / 2; + y = size[1] / 2; + i = -1; + if (k) while (++i < n) { + o = nodes[i]; + o.x += (x - o.x) * k; + o.y += (y - o.y) * k; + } + } + if (charge) { + d3_layout_forceAccumulate(q = d3.geom.quadtree(nodes), alpha, charges); + i = -1; + while (++i < n) { + if (!(o = nodes[i]).fixed) { + q.visit(repulse(o)); + } + } + } + i = -1; + while (++i < n) { + o = nodes[i]; + if (o.fixed) { + o.x = o.px; + o.y = o.py; + } else { + o.x -= (o.px - (o.px = o.x)) * friction; + o.y -= (o.py - (o.py = o.y)) * friction; + } + } + event.tick({ + type: "tick", + alpha: alpha + }); + }; + force.nodes = function(x) { + if (!arguments.length) return nodes; + nodes = x; + return force; + }; + force.links = function(x) { + if (!arguments.length) return links; + links = x; + return force; + }; + force.size = function(x) { + if (!arguments.length) return size; + size = x; + return force; + }; + force.linkDistance = function(x) { + if (!arguments.length) return linkDistance; + linkDistance = typeof x === "function" ? x : +x; + return force; + }; + force.distance = force.linkDistance; + force.linkStrength = function(x) { + if (!arguments.length) return linkStrength; + linkStrength = typeof x === "function" ? x : +x; + return force; + }; + force.friction = function(x) { + if (!arguments.length) return friction; + friction = +x; + return force; + }; + force.charge = function(x) { + if (!arguments.length) return charge; + charge = typeof x === "function" ? x : +x; + return force; + }; + force.chargeDistance = function(x) { + if (!arguments.length) return Math.sqrt(chargeDistance2); + chargeDistance2 = x * x; + return force; + }; + force.gravity = function(x) { + if (!arguments.length) return gravity; + gravity = +x; + return force; + }; + force.theta = function(x) { + if (!arguments.length) return Math.sqrt(theta2); + theta2 = x * x; + return force; + }; + force.alpha = function(x) { + if (!arguments.length) return alpha; + x = +x; + if (alpha) { + if (x > 0) { + alpha = x; + } else { + timer.c = null, timer.t = NaN, timer = null; + event.end({ + type: "end", + alpha: alpha = 0 + }); + } + } else if (x > 0) { + event.start({ + type: "start", + alpha: alpha = x + }); + timer = d3_timer(force.tick); + } + return force; + }; + force.start = function() { + var i, n = nodes.length, m = links.length, w = size[0], h = size[1], neighbors, o; + for (i = 0; i < n; ++i) { + (o = nodes[i]).index = i; + o.weight = 0; + } + for (i = 0; i < m; ++i) { + o = links[i]; + if (typeof o.source == "number") o.source = nodes[o.source]; + if (typeof o.target == "number") o.target = nodes[o.target]; + ++o.source.weight; + ++o.target.weight; + } + for (i = 0; i < n; ++i) { + o = nodes[i]; + if (isNaN(o.x)) o.x = position("x", w); + if (isNaN(o.y)) o.y = position("y", h); + if (isNaN(o.px)) o.px = o.x; + if (isNaN(o.py)) o.py = o.y; + } + distances = []; + if (typeof linkDistance === "function") for (i = 0; i < m; ++i) distances[i] = +linkDistance.call(this, links[i], i); else for (i = 0; i < m; ++i) distances[i] = linkDistance; + strengths = []; + if (typeof linkStrength === "function") for (i = 0; i < m; ++i) strengths[i] = +linkStrength.call(this, links[i], i); else for (i = 0; i < m; ++i) strengths[i] = linkStrength; + charges = []; + if (typeof charge === "function") for (i = 0; i < n; ++i) charges[i] = +charge.call(this, nodes[i], i); else for (i = 0; i < n; ++i) charges[i] = charge; + function position(dimension, size) { + if (!neighbors) { + neighbors = new Array(n); + for (j = 0; j < n; ++j) { + neighbors[j] = []; + } + for (j = 0; j < m; ++j) { + var o = links[j]; + neighbors[o.source.index].push(o.target); + neighbors[o.target.index].push(o.source); + } + } + var candidates = neighbors[i], j = -1, l = candidates.length, x; + while (++j < l) if (!isNaN(x = candidates[j][dimension])) return x; + return Math.random() * size; + } + return force.resume(); + }; + force.resume = function() { + return force.alpha(.1); + }; + force.stop = function() { + return force.alpha(0); + }; + force.drag = function() { + if (!drag) drag = d3.behavior.drag().origin(d3_identity).on("dragstart.force", d3_layout_forceDragstart).on("drag.force", dragmove).on("dragend.force", d3_layout_forceDragend); + if (!arguments.length) return drag; + this.on("mouseover.force", d3_layout_forceMouseover).on("mouseout.force", d3_layout_forceMouseout).call(drag); + }; + function dragmove(d) { + d.px = d3.event.x, d.py = d3.event.y; + force.resume(); + } + return d3.rebind(force, event, "on"); + }; + function d3_layout_forceDragstart(d) { + d.fixed |= 2; + } + function d3_layout_forceDragend(d) { + d.fixed &= ~6; + } + function d3_layout_forceMouseover(d) { + d.fixed |= 4; + d.px = d.x, d.py = d.y; + } + function d3_layout_forceMouseout(d) { + d.fixed &= ~4; + } + function d3_layout_forceAccumulate(quad, alpha, charges) { + var cx = 0, cy = 0; + quad.charge = 0; + if (!quad.leaf) { + var nodes = quad.nodes, n = nodes.length, i = -1, c; + while (++i < n) { + c = nodes[i]; + if (c == null) continue; + d3_layout_forceAccumulate(c, alpha, charges); + quad.charge += c.charge; + cx += c.charge * c.cx; + cy += c.charge * c.cy; + } + } + if (quad.point) { + if (!quad.leaf) { + quad.point.x += Math.random() - .5; + quad.point.y += Math.random() - .5; + } + var k = alpha * charges[quad.point.index]; + quad.charge += quad.pointCharge = k; + cx += k * quad.point.x; + cy += k * quad.point.y; + } + quad.cx = cx / quad.charge; + quad.cy = cy / quad.charge; + } + var d3_layout_forceLinkDistance = 20, d3_layout_forceLinkStrength = 1, d3_layout_forceChargeDistance2 = Infinity; + d3.layout.hierarchy = function() { + var sort = d3_layout_hierarchySort, children = d3_layout_hierarchyChildren, value = d3_layout_hierarchyValue; + function hierarchy(root) { + var stack = [ root ], nodes = [], node; + root.depth = 0; + while ((node = stack.pop()) != null) { + nodes.push(node); + if ((childs = children.call(hierarchy, node, node.depth)) && (n = childs.length)) { + var n, childs, child; + while (--n >= 0) { + stack.push(child = childs[n]); + child.parent = node; + child.depth = node.depth + 1; + } + if (value) node.value = 0; + node.children = childs; + } else { + if (value) node.value = +value.call(hierarchy, node, node.depth) || 0; + delete node.children; + } + } + d3_layout_hierarchyVisitAfter(root, function(node) { + var childs, parent; + if (sort && (childs = node.children)) childs.sort(sort); + if (value && (parent = node.parent)) parent.value += node.value; + }); + return nodes; + } + hierarchy.sort = function(x) { + if (!arguments.length) return sort; + sort = x; + return hierarchy; + }; + hierarchy.children = function(x) { + if (!arguments.length) return children; + children = x; + return hierarchy; + }; + hierarchy.value = function(x) { + if (!arguments.length) return value; + value = x; + return hierarchy; + }; + hierarchy.revalue = function(root) { + if (value) { + d3_layout_hierarchyVisitBefore(root, function(node) { + if (node.children) node.value = 0; + }); + d3_layout_hierarchyVisitAfter(root, function(node) { + var parent; + if (!node.children) node.value = +value.call(hierarchy, node, node.depth) || 0; + if (parent = node.parent) parent.value += node.value; + }); + } + return root; + }; + return hierarchy; + }; + function d3_layout_hierarchyRebind(object, hierarchy) { + d3.rebind(object, hierarchy, "sort", "children", "value"); + object.nodes = object; + object.links = d3_layout_hierarchyLinks; + return object; + } + function d3_layout_hierarchyVisitBefore(node, callback) { + var nodes = [ node ]; + while ((node = nodes.pop()) != null) { + callback(node); + if ((children = node.children) && (n = children.length)) { + var n, children; + while (--n >= 0) nodes.push(children[n]); + } + } + } + function d3_layout_hierarchyVisitAfter(node, callback) { + var nodes = [ node ], nodes2 = []; + while ((node = nodes.pop()) != null) { + nodes2.push(node); + if ((children = node.children) && (n = children.length)) { + var i = -1, n, children; + while (++i < n) nodes.push(children[i]); + } + } + while ((node = nodes2.pop()) != null) { + callback(node); + } + } + function d3_layout_hierarchyChildren(d) { + return d.children; + } + function d3_layout_hierarchyValue(d) { + return d.value; + } + function d3_layout_hierarchySort(a, b) { + return b.value - a.value; + } + function d3_layout_hierarchyLinks(nodes) { + return d3.merge(nodes.map(function(parent) { + return (parent.children || []).map(function(child) { + return { + source: parent, + target: child + }; + }); + })); + } + d3.layout.partition = function() { + var hierarchy = d3.layout.hierarchy(), size = [ 1, 1 ]; + function position(node, x, dx, dy) { + var children = node.children; + node.x = x; + node.y = node.depth * dy; + node.dx = dx; + node.dy = dy; + if (children && (n = children.length)) { + var i = -1, n, c, d; + dx = node.value ? dx / node.value : 0; + while (++i < n) { + position(c = children[i], x, d = c.value * dx, dy); + x += d; + } + } + } + function depth(node) { + var children = node.children, d = 0; + if (children && (n = children.length)) { + var i = -1, n; + while (++i < n) d = Math.max(d, depth(children[i])); + } + return 1 + d; + } + function partition(d, i) { + var nodes = hierarchy.call(this, d, i); + position(nodes[0], 0, size[0], size[1] / depth(nodes[0])); + return nodes; + } + partition.size = function(x) { + if (!arguments.length) return size; + size = x; + return partition; + }; + return d3_layout_hierarchyRebind(partition, hierarchy); + }; + d3.layout.pie = function() { + var value = Number, sort = d3_layout_pieSortByValue, startAngle = 0, endAngle = τ, padAngle = 0; + function pie(data) { + var n = data.length, values = data.map(function(d, i) { + return +value.call(pie, d, i); + }), a = +(typeof startAngle === "function" ? startAngle.apply(this, arguments) : startAngle), da = (typeof endAngle === "function" ? endAngle.apply(this, arguments) : endAngle) - a, p = Math.min(Math.abs(da) / n, +(typeof padAngle === "function" ? padAngle.apply(this, arguments) : padAngle)), pa = p * (da < 0 ? -1 : 1), sum = d3.sum(values), k = sum ? (da - n * pa) / sum : 0, index = d3.range(n), arcs = [], v; + if (sort != null) index.sort(sort === d3_layout_pieSortByValue ? function(i, j) { + return values[j] - values[i]; + } : function(i, j) { + return sort(data[i], data[j]); + }); + index.forEach(function(i) { + arcs[i] = { + data: data[i], + value: v = values[i], + startAngle: a, + endAngle: a += v * k + pa, + padAngle: p + }; + }); + return arcs; + } + pie.value = function(_) { + if (!arguments.length) return value; + value = _; + return pie; + }; + pie.sort = function(_) { + if (!arguments.length) return sort; + sort = _; + return pie; + }; + pie.startAngle = function(_) { + if (!arguments.length) return startAngle; + startAngle = _; + return pie; + }; + pie.endAngle = function(_) { + if (!arguments.length) return endAngle; + endAngle = _; + return pie; + }; + pie.padAngle = function(_) { + if (!arguments.length) return padAngle; + padAngle = _; + return pie; + }; + return pie; + }; + var d3_layout_pieSortByValue = {}; + d3.layout.stack = function() { + var values = d3_identity, order = d3_layout_stackOrderDefault, offset = d3_layout_stackOffsetZero, out = d3_layout_stackOut, x = d3_layout_stackX, y = d3_layout_stackY; + function stack(data, index) { + if (!(n = data.length)) return data; + var series = data.map(function(d, i) { + return values.call(stack, d, i); + }); + var points = series.map(function(d) { + return d.map(function(v, i) { + return [ x.call(stack, v, i), y.call(stack, v, i) ]; + }); + }); + var orders = order.call(stack, points, index); + series = d3.permute(series, orders); + points = d3.permute(points, orders); + var offsets = offset.call(stack, points, index); + var m = series[0].length, n, i, j, o; + for (j = 0; j < m; ++j) { + out.call(stack, series[0][j], o = offsets[j], points[0][j][1]); + for (i = 1; i < n; ++i) { + out.call(stack, series[i][j], o += points[i - 1][j][1], points[i][j][1]); + } + } + return data; + } + stack.values = function(x) { + if (!arguments.length) return values; + values = x; + return stack; + }; + stack.order = function(x) { + if (!arguments.length) return order; + order = typeof x === "function" ? x : d3_layout_stackOrders.get(x) || d3_layout_stackOrderDefault; + return stack; + }; + stack.offset = function(x) { + if (!arguments.length) return offset; + offset = typeof x === "function" ? x : d3_layout_stackOffsets.get(x) || d3_layout_stackOffsetZero; + return stack; + }; + stack.x = function(z) { + if (!arguments.length) return x; + x = z; + return stack; + }; + stack.y = function(z) { + if (!arguments.length) return y; + y = z; + return stack; + }; + stack.out = function(z) { + if (!arguments.length) return out; + out = z; + return stack; + }; + return stack; + }; + function d3_layout_stackX(d) { + return d.x; + } + function d3_layout_stackY(d) { + return d.y; + } + function d3_layout_stackOut(d, y0, y) { + d.y0 = y0; + d.y = y; + } + var d3_layout_stackOrders = d3.map({ + "inside-out": function(data) { + var n = data.length, i, j, max = data.map(d3_layout_stackMaxIndex), sums = data.map(d3_layout_stackReduceSum), index = d3.range(n).sort(function(a, b) { + return max[a] - max[b]; + }), top = 0, bottom = 0, tops = [], bottoms = []; + for (i = 0; i < n; ++i) { + j = index[i]; + if (top < bottom) { + top += sums[j]; + tops.push(j); + } else { + bottom += sums[j]; + bottoms.push(j); + } + } + return bottoms.reverse().concat(tops); + }, + reverse: function(data) { + return d3.range(data.length).reverse(); + }, + "default": d3_layout_stackOrderDefault + }); + var d3_layout_stackOffsets = d3.map({ + silhouette: function(data) { + var n = data.length, m = data[0].length, sums = [], max = 0, i, j, o, y0 = []; + for (j = 0; j < m; ++j) { + for (i = 0, o = 0; i < n; i++) o += data[i][j][1]; + if (o > max) max = o; + sums.push(o); + } + for (j = 0; j < m; ++j) { + y0[j] = (max - sums[j]) / 2; + } + return y0; + }, + wiggle: function(data) { + var n = data.length, x = data[0], m = x.length, i, j, k, s1, s2, s3, dx, o, o0, y0 = []; + y0[0] = o = o0 = 0; + for (j = 1; j < m; ++j) { + for (i = 0, s1 = 0; i < n; ++i) s1 += data[i][j][1]; + for (i = 0, s2 = 0, dx = x[j][0] - x[j - 1][0]; i < n; ++i) { + for (k = 0, s3 = (data[i][j][1] - data[i][j - 1][1]) / (2 * dx); k < i; ++k) { + s3 += (data[k][j][1] - data[k][j - 1][1]) / dx; + } + s2 += s3 * data[i][j][1]; + } + y0[j] = o -= s1 ? s2 / s1 * dx : 0; + if (o < o0) o0 = o; + } + for (j = 0; j < m; ++j) y0[j] -= o0; + return y0; + }, + expand: function(data) { + var n = data.length, m = data[0].length, k = 1 / n, i, j, o, y0 = []; + for (j = 0; j < m; ++j) { + for (i = 0, o = 0; i < n; i++) o += data[i][j][1]; + if (o) for (i = 0; i < n; i++) data[i][j][1] /= o; else for (i = 0; i < n; i++) data[i][j][1] = k; + } + for (j = 0; j < m; ++j) y0[j] = 0; + return y0; + }, + zero: d3_layout_stackOffsetZero + }); + function d3_layout_stackOrderDefault(data) { + return d3.range(data.length); + } + function d3_layout_stackOffsetZero(data) { + var j = -1, m = data[0].length, y0 = []; + while (++j < m) y0[j] = 0; + return y0; + } + function d3_layout_stackMaxIndex(array) { + var i = 1, j = 0, v = array[0][1], k, n = array.length; + for (;i < n; ++i) { + if ((k = array[i][1]) > v) { + j = i; + v = k; + } + } + return j; + } + function d3_layout_stackReduceSum(d) { + return d.reduce(d3_layout_stackSum, 0); + } + function d3_layout_stackSum(p, d) { + return p + d[1]; + } + d3.layout.histogram = function() { + var frequency = true, valuer = Number, ranger = d3_layout_histogramRange, binner = d3_layout_histogramBinSturges; + function histogram(data, i) { + var bins = [], values = data.map(valuer, this), range = ranger.call(this, values, i), thresholds = binner.call(this, range, values, i), bin, i = -1, n = values.length, m = thresholds.length - 1, k = frequency ? 1 : 1 / n, x; + while (++i < m) { + bin = bins[i] = []; + bin.dx = thresholds[i + 1] - (bin.x = thresholds[i]); + bin.y = 0; + } + if (m > 0) { + i = -1; + while (++i < n) { + x = values[i]; + if (x >= range[0] && x <= range[1]) { + bin = bins[d3.bisect(thresholds, x, 1, m) - 1]; + bin.y += k; + bin.push(data[i]); + } + } + } + return bins; + } + histogram.value = function(x) { + if (!arguments.length) return valuer; + valuer = x; + return histogram; + }; + histogram.range = function(x) { + if (!arguments.length) return ranger; + ranger = d3_functor(x); + return histogram; + }; + histogram.bins = function(x) { + if (!arguments.length) return binner; + binner = typeof x === "number" ? function(range) { + return d3_layout_histogramBinFixed(range, x); + } : d3_functor(x); + return histogram; + }; + histogram.frequency = function(x) { + if (!arguments.length) return frequency; + frequency = !!x; + return histogram; + }; + return histogram; + }; + function d3_layout_histogramBinSturges(range, values) { + return d3_layout_histogramBinFixed(range, Math.ceil(Math.log(values.length) / Math.LN2 + 1)); + } + function d3_layout_histogramBinFixed(range, n) { + var x = -1, b = +range[0], m = (range[1] - b) / n, f = []; + while (++x <= n) f[x] = m * x + b; + return f; + } + function d3_layout_histogramRange(values) { + return [ d3.min(values), d3.max(values) ]; + } + d3.layout.pack = function() { + var hierarchy = d3.layout.hierarchy().sort(d3_layout_packSort), padding = 0, size = [ 1, 1 ], radius; + function pack(d, i) { + var nodes = hierarchy.call(this, d, i), root = nodes[0], w = size[0], h = size[1], r = radius == null ? Math.sqrt : typeof radius === "function" ? radius : function() { + return radius; + }; + root.x = root.y = 0; + d3_layout_hierarchyVisitAfter(root, function(d) { + d.r = +r(d.value); + }); + d3_layout_hierarchyVisitAfter(root, d3_layout_packSiblings); + if (padding) { + var dr = padding * (radius ? 1 : Math.max(2 * root.r / w, 2 * root.r / h)) / 2; + d3_layout_hierarchyVisitAfter(root, function(d) { + d.r += dr; + }); + d3_layout_hierarchyVisitAfter(root, d3_layout_packSiblings); + d3_layout_hierarchyVisitAfter(root, function(d) { + d.r -= dr; + }); + } + d3_layout_packTransform(root, w / 2, h / 2, radius ? 1 : 1 / Math.max(2 * root.r / w, 2 * root.r / h)); + return nodes; + } + pack.size = function(_) { + if (!arguments.length) return size; + size = _; + return pack; + }; + pack.radius = function(_) { + if (!arguments.length) return radius; + radius = _ == null || typeof _ === "function" ? _ : +_; + return pack; + }; + pack.padding = function(_) { + if (!arguments.length) return padding; + padding = +_; + return pack; + }; + return d3_layout_hierarchyRebind(pack, hierarchy); + }; + function d3_layout_packSort(a, b) { + return a.value - b.value; + } + function d3_layout_packInsert(a, b) { + var c = a._pack_next; + a._pack_next = b; + b._pack_prev = a; + b._pack_next = c; + c._pack_prev = b; + } + function d3_layout_packSplice(a, b) { + a._pack_next = b; + b._pack_prev = a; + } + function d3_layout_packIntersects(a, b) { + var dx = b.x - a.x, dy = b.y - a.y, dr = a.r + b.r; + return .999 * dr * dr > dx * dx + dy * dy; + } + function d3_layout_packSiblings(node) { + if (!(nodes = node.children) || !(n = nodes.length)) return; + var nodes, xMin = Infinity, xMax = -Infinity, yMin = Infinity, yMax = -Infinity, a, b, c, i, j, k, n; + function bound(node) { + xMin = Math.min(node.x - node.r, xMin); + xMax = Math.max(node.x + node.r, xMax); + yMin = Math.min(node.y - node.r, yMin); + yMax = Math.max(node.y + node.r, yMax); + } + nodes.forEach(d3_layout_packLink); + a = nodes[0]; + a.x = -a.r; + a.y = 0; + bound(a); + if (n > 1) { + b = nodes[1]; + b.x = b.r; + b.y = 0; + bound(b); + if (n > 2) { + c = nodes[2]; + d3_layout_packPlace(a, b, c); + bound(c); + d3_layout_packInsert(a, c); + a._pack_prev = c; + d3_layout_packInsert(c, b); + b = a._pack_next; + for (i = 3; i < n; i++) { + d3_layout_packPlace(a, b, c = nodes[i]); + var isect = 0, s1 = 1, s2 = 1; + for (j = b._pack_next; j !== b; j = j._pack_next, s1++) { + if (d3_layout_packIntersects(j, c)) { + isect = 1; + break; + } + } + if (isect == 1) { + for (k = a._pack_prev; k !== j._pack_prev; k = k._pack_prev, s2++) { + if (d3_layout_packIntersects(k, c)) { + break; + } + } + } + if (isect) { + if (s1 < s2 || s1 == s2 && b.r < a.r) d3_layout_packSplice(a, b = j); else d3_layout_packSplice(a = k, b); + i--; + } else { + d3_layout_packInsert(a, c); + b = c; + bound(c); + } + } + } + } + var cx = (xMin + xMax) / 2, cy = (yMin + yMax) / 2, cr = 0; + for (i = 0; i < n; i++) { + c = nodes[i]; + c.x -= cx; + c.y -= cy; + cr = Math.max(cr, c.r + Math.sqrt(c.x * c.x + c.y * c.y)); + } + node.r = cr; + nodes.forEach(d3_layout_packUnlink); + } + function d3_layout_packLink(node) { + node._pack_next = node._pack_prev = node; + } + function d3_layout_packUnlink(node) { + delete node._pack_next; + delete node._pack_prev; + } + function d3_layout_packTransform(node, x, y, k) { + var children = node.children; + node.x = x += k * node.x; + node.y = y += k * node.y; + node.r *= k; + if (children) { + var i = -1, n = children.length; + while (++i < n) d3_layout_packTransform(children[i], x, y, k); + } + } + function d3_layout_packPlace(a, b, c) { + var db = a.r + c.r, dx = b.x - a.x, dy = b.y - a.y; + if (db && (dx || dy)) { + var da = b.r + c.r, dc = dx * dx + dy * dy; + da *= da; + db *= db; + var x = .5 + (db - da) / (2 * dc), y = Math.sqrt(Math.max(0, 2 * da * (db + dc) - (db -= dc) * db - da * da)) / (2 * dc); + c.x = a.x + x * dx + y * dy; + c.y = a.y + x * dy - y * dx; + } else { + c.x = a.x + db; + c.y = a.y; + } + } + d3.layout.tree = function() { + var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ], nodeSize = null; + function tree(d, i) { + var nodes = hierarchy.call(this, d, i), root0 = nodes[0], root1 = wrapTree(root0); + d3_layout_hierarchyVisitAfter(root1, firstWalk), root1.parent.m = -root1.z; + d3_layout_hierarchyVisitBefore(root1, secondWalk); + if (nodeSize) d3_layout_hierarchyVisitBefore(root0, sizeNode); else { + var left = root0, right = root0, bottom = root0; + d3_layout_hierarchyVisitBefore(root0, function(node) { + if (node.x < left.x) left = node; + if (node.x > right.x) right = node; + if (node.depth > bottom.depth) bottom = node; + }); + var tx = separation(left, right) / 2 - left.x, kx = size[0] / (right.x + separation(right, left) / 2 + tx), ky = size[1] / (bottom.depth || 1); + d3_layout_hierarchyVisitBefore(root0, function(node) { + node.x = (node.x + tx) * kx; + node.y = node.depth * ky; + }); + } + return nodes; + } + function wrapTree(root0) { + var root1 = { + A: null, + children: [ root0 ] + }, queue = [ root1 ], node1; + while ((node1 = queue.pop()) != null) { + for (var children = node1.children, child, i = 0, n = children.length; i < n; ++i) { + queue.push((children[i] = child = { + _: children[i], + parent: node1, + children: (child = children[i].children) && child.slice() || [], + A: null, + a: null, + z: 0, + m: 0, + c: 0, + s: 0, + t: null, + i: i + }).a = child); + } + } + return root1.children[0]; + } + function firstWalk(v) { + var children = v.children, siblings = v.parent.children, w = v.i ? siblings[v.i - 1] : null; + if (children.length) { + d3_layout_treeShift(v); + var midpoint = (children[0].z + children[children.length - 1].z) / 2; + if (w) { + v.z = w.z + separation(v._, w._); + v.m = v.z - midpoint; + } else { + v.z = midpoint; + } + } else if (w) { + v.z = w.z + separation(v._, w._); + } + v.parent.A = apportion(v, w, v.parent.A || siblings[0]); + } + function secondWalk(v) { + v._.x = v.z + v.parent.m; + v.m += v.parent.m; + } + function apportion(v, w, ancestor) { + if (w) { + var vip = v, vop = v, vim = w, vom = vip.parent.children[0], sip = vip.m, sop = vop.m, sim = vim.m, som = vom.m, shift; + while (vim = d3_layout_treeRight(vim), vip = d3_layout_treeLeft(vip), vim && vip) { + vom = d3_layout_treeLeft(vom); + vop = d3_layout_treeRight(vop); + vop.a = v; + shift = vim.z + sim - vip.z - sip + separation(vim._, vip._); + if (shift > 0) { + d3_layout_treeMove(d3_layout_treeAncestor(vim, v, ancestor), v, shift); + sip += shift; + sop += shift; + } + sim += vim.m; + sip += vip.m; + som += vom.m; + sop += vop.m; + } + if (vim && !d3_layout_treeRight(vop)) { + vop.t = vim; + vop.m += sim - sop; + } + if (vip && !d3_layout_treeLeft(vom)) { + vom.t = vip; + vom.m += sip - som; + ancestor = v; + } + } + return ancestor; + } + function sizeNode(node) { + node.x *= size[0]; + node.y = node.depth * size[1]; + } + tree.separation = function(x) { + if (!arguments.length) return separation; + separation = x; + return tree; + }; + tree.size = function(x) { + if (!arguments.length) return nodeSize ? null : size; + nodeSize = (size = x) == null ? sizeNode : null; + return tree; + }; + tree.nodeSize = function(x) { + if (!arguments.length) return nodeSize ? size : null; + nodeSize = (size = x) == null ? null : sizeNode; + return tree; + }; + return d3_layout_hierarchyRebind(tree, hierarchy); + }; + function d3_layout_treeSeparation(a, b) { + return a.parent == b.parent ? 1 : 2; + } + function d3_layout_treeLeft(v) { + var children = v.children; + return children.length ? children[0] : v.t; + } + function d3_layout_treeRight(v) { + var children = v.children, n; + return (n = children.length) ? children[n - 1] : v.t; + } + function d3_layout_treeMove(wm, wp, shift) { + var change = shift / (wp.i - wm.i); + wp.c -= change; + wp.s += shift; + wm.c += change; + wp.z += shift; + wp.m += shift; + } + function d3_layout_treeShift(v) { + var shift = 0, change = 0, children = v.children, i = children.length, w; + while (--i >= 0) { + w = children[i]; + w.z += shift; + w.m += shift; + shift += w.s + (change += w.c); + } + } + function d3_layout_treeAncestor(vim, v, ancestor) { + return vim.a.parent === v.parent ? vim.a : ancestor; + } + d3.layout.cluster = function() { + var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ], nodeSize = false; + function cluster(d, i) { + var nodes = hierarchy.call(this, d, i), root = nodes[0], previousNode, x = 0; + d3_layout_hierarchyVisitAfter(root, function(node) { + var children = node.children; + if (children && children.length) { + node.x = d3_layout_clusterX(children); + node.y = d3_layout_clusterY(children); + } else { + node.x = previousNode ? x += separation(node, previousNode) : 0; + node.y = 0; + previousNode = node; + } + }); + var left = d3_layout_clusterLeft(root), right = d3_layout_clusterRight(root), x0 = left.x - separation(left, right) / 2, x1 = right.x + separation(right, left) / 2; + d3_layout_hierarchyVisitAfter(root, nodeSize ? function(node) { + node.x = (node.x - root.x) * size[0]; + node.y = (root.y - node.y) * size[1]; + } : function(node) { + node.x = (node.x - x0) / (x1 - x0) * size[0]; + node.y = (1 - (root.y ? node.y / root.y : 1)) * size[1]; + }); + return nodes; + } + cluster.separation = function(x) { + if (!arguments.length) return separation; + separation = x; + return cluster; + }; + cluster.size = function(x) { + if (!arguments.length) return nodeSize ? null : size; + nodeSize = (size = x) == null; + return cluster; + }; + cluster.nodeSize = function(x) { + if (!arguments.length) return nodeSize ? size : null; + nodeSize = (size = x) != null; + return cluster; + }; + return d3_layout_hierarchyRebind(cluster, hierarchy); + }; + function d3_layout_clusterY(children) { + return 1 + d3.max(children, function(child) { + return child.y; + }); + } + function d3_layout_clusterX(children) { + return children.reduce(function(x, child) { + return x + child.x; + }, 0) / children.length; + } + function d3_layout_clusterLeft(node) { + var children = node.children; + return children && children.length ? d3_layout_clusterLeft(children[0]) : node; + } + function d3_layout_clusterRight(node) { + var children = node.children, n; + return children && (n = children.length) ? d3_layout_clusterRight(children[n - 1]) : node; + } + d3.layout.treemap = function() { + var hierarchy = d3.layout.hierarchy(), round = Math.round, size = [ 1, 1 ], padding = null, pad = d3_layout_treemapPadNull, sticky = false, stickies, mode = "squarify", ratio = .5 * (1 + Math.sqrt(5)); + function scale(children, k) { + var i = -1, n = children.length, child, area; + while (++i < n) { + area = (child = children[i]).value * (k < 0 ? 0 : k); + child.area = isNaN(area) || area <= 0 ? 0 : area; + } + } + function squarify(node) { + var children = node.children; + if (children && children.length) { + var rect = pad(node), row = [], remaining = children.slice(), child, best = Infinity, score, u = mode === "slice" ? rect.dx : mode === "dice" ? rect.dy : mode === "slice-dice" ? node.depth & 1 ? rect.dy : rect.dx : Math.min(rect.dx, rect.dy), n; + scale(remaining, rect.dx * rect.dy / node.value); + row.area = 0; + while ((n = remaining.length) > 0) { + row.push(child = remaining[n - 1]); + row.area += child.area; + if (mode !== "squarify" || (score = worst(row, u)) <= best) { + remaining.pop(); + best = score; + } else { + row.area -= row.pop().area; + position(row, u, rect, false); + u = Math.min(rect.dx, rect.dy); + row.length = row.area = 0; + best = Infinity; + } + } + if (row.length) { + position(row, u, rect, true); + row.length = row.area = 0; + } + children.forEach(squarify); + } + } + function stickify(node) { + var children = node.children; + if (children && children.length) { + var rect = pad(node), remaining = children.slice(), child, row = []; + scale(remaining, rect.dx * rect.dy / node.value); + row.area = 0; + while (child = remaining.pop()) { + row.push(child); + row.area += child.area; + if (child.z != null) { + position(row, child.z ? rect.dx : rect.dy, rect, !remaining.length); + row.length = row.area = 0; + } + } + children.forEach(stickify); + } + } + function worst(row, u) { + var s = row.area, r, rmax = 0, rmin = Infinity, i = -1, n = row.length; + while (++i < n) { + if (!(r = row[i].area)) continue; + if (r < rmin) rmin = r; + if (r > rmax) rmax = r; + } + s *= s; + u *= u; + return s ? Math.max(u * rmax * ratio / s, s / (u * rmin * ratio)) : Infinity; + } + function position(row, u, rect, flush) { + var i = -1, n = row.length, x = rect.x, y = rect.y, v = u ? round(row.area / u) : 0, o; + if (u == rect.dx) { + if (flush || v > rect.dy) v = rect.dy; + while (++i < n) { + o = row[i]; + o.x = x; + o.y = y; + o.dy = v; + x += o.dx = Math.min(rect.x + rect.dx - x, v ? round(o.area / v) : 0); + } + o.z = true; + o.dx += rect.x + rect.dx - x; + rect.y += v; + rect.dy -= v; + } else { + if (flush || v > rect.dx) v = rect.dx; + while (++i < n) { + o = row[i]; + o.x = x; + o.y = y; + o.dx = v; + y += o.dy = Math.min(rect.y + rect.dy - y, v ? round(o.area / v) : 0); + } + o.z = false; + o.dy += rect.y + rect.dy - y; + rect.x += v; + rect.dx -= v; + } + } + function treemap(d) { + var nodes = stickies || hierarchy(d), root = nodes[0]; + root.x = root.y = 0; + if (root.value) root.dx = size[0], root.dy = size[1]; else root.dx = root.dy = 0; + if (stickies) hierarchy.revalue(root); + scale([ root ], root.dx * root.dy / root.value); + (stickies ? stickify : squarify)(root); + if (sticky) stickies = nodes; + return nodes; + } + treemap.size = function(x) { + if (!arguments.length) return size; + size = x; + return treemap; + }; + treemap.padding = function(x) { + if (!arguments.length) return padding; + function padFunction(node) { + var p = x.call(treemap, node, node.depth); + return p == null ? d3_layout_treemapPadNull(node) : d3_layout_treemapPad(node, typeof p === "number" ? [ p, p, p, p ] : p); + } + function padConstant(node) { + return d3_layout_treemapPad(node, x); + } + var type; + pad = (padding = x) == null ? d3_layout_treemapPadNull : (type = typeof x) === "function" ? padFunction : type === "number" ? (x = [ x, x, x, x ], + padConstant) : padConstant; + return treemap; + }; + treemap.round = function(x) { + if (!arguments.length) return round != Number; + round = x ? Math.round : Number; + return treemap; + }; + treemap.sticky = function(x) { + if (!arguments.length) return sticky; + sticky = x; + stickies = null; + return treemap; + }; + treemap.ratio = function(x) { + if (!arguments.length) return ratio; + ratio = x; + return treemap; + }; + treemap.mode = function(x) { + if (!arguments.length) return mode; + mode = x + ""; + return treemap; + }; + return d3_layout_hierarchyRebind(treemap, hierarchy); + }; + function d3_layout_treemapPadNull(node) { + return { + x: node.x, + y: node.y, + dx: node.dx, + dy: node.dy + }; + } + function d3_layout_treemapPad(node, padding) { + var x = node.x + padding[3], y = node.y + padding[0], dx = node.dx - padding[1] - padding[3], dy = node.dy - padding[0] - padding[2]; + if (dx < 0) { + x += dx / 2; + dx = 0; + } + if (dy < 0) { + y += dy / 2; + dy = 0; + } + return { + x: x, + y: y, + dx: dx, + dy: dy + }; + } + d3.random = { + normal: function(µ, σ) { + var n = arguments.length; + if (n < 2) σ = 1; + if (n < 1) µ = 0; + return function() { + var x, y, r; + do { + x = Math.random() * 2 - 1; + y = Math.random() * 2 - 1; + r = x * x + y * y; + } while (!r || r > 1); + return µ + σ * x * Math.sqrt(-2 * Math.log(r) / r); + }; + }, + logNormal: function() { + var random = d3.random.normal.apply(d3, arguments); + return function() { + return Math.exp(random()); + }; + }, + bates: function(m) { + var random = d3.random.irwinHall(m); + return function() { + return random() / m; + }; + }, + irwinHall: function(m) { + return function() { + for (var s = 0, j = 0; j < m; j++) s += Math.random(); + return s; + }; + } + }; + d3.scale = {}; + function d3_scaleExtent(domain) { + var start = domain[0], stop = domain[domain.length - 1]; + return start < stop ? [ start, stop ] : [ stop, start ]; + } + function d3_scaleRange(scale) { + return scale.rangeExtent ? scale.rangeExtent() : d3_scaleExtent(scale.range()); + } + function d3_scale_bilinear(domain, range, uninterpolate, interpolate) { + var u = uninterpolate(domain[0], domain[1]), i = interpolate(range[0], range[1]); + return function(x) { + return i(u(x)); + }; + } + function d3_scale_nice(domain, nice) { + var i0 = 0, i1 = domain.length - 1, x0 = domain[i0], x1 = domain[i1], dx; + if (x1 < x0) { + dx = i0, i0 = i1, i1 = dx; + dx = x0, x0 = x1, x1 = dx; + } + domain[i0] = nice.floor(x0); + domain[i1] = nice.ceil(x1); + return domain; + } + function d3_scale_niceStep(step) { + return step ? { + floor: function(x) { + return Math.floor(x / step) * step; + }, + ceil: function(x) { + return Math.ceil(x / step) * step; + } + } : d3_scale_niceIdentity; + } + var d3_scale_niceIdentity = { + floor: d3_identity, + ceil: d3_identity + }; + function d3_scale_polylinear(domain, range, uninterpolate, interpolate) { + var u = [], i = [], j = 0, k = Math.min(domain.length, range.length) - 1; + if (domain[k] < domain[0]) { + domain = domain.slice().reverse(); + range = range.slice().reverse(); + } + while (++j <= k) { + u.push(uninterpolate(domain[j - 1], domain[j])); + i.push(interpolate(range[j - 1], range[j])); + } + return function(x) { + var j = d3.bisect(domain, x, 1, k) - 1; + return i[j](u[j](x)); + }; + } + d3.scale.linear = function() { + return d3_scale_linear([ 0, 1 ], [ 0, 1 ], d3_interpolate, false); + }; + function d3_scale_linear(domain, range, interpolate, clamp) { + var output, input; + function rescale() { + var linear = Math.min(domain.length, range.length) > 2 ? d3_scale_polylinear : d3_scale_bilinear, uninterpolate = clamp ? d3_uninterpolateClamp : d3_uninterpolateNumber; + output = linear(domain, range, uninterpolate, interpolate); + input = linear(range, domain, uninterpolate, d3_interpolate); + return scale; + } + function scale(x) { + return output(x); + } + scale.invert = function(y) { + return input(y); + }; + scale.domain = function(x) { + if (!arguments.length) return domain; + domain = x.map(Number); + return rescale(); + }; + scale.range = function(x) { + if (!arguments.length) return range; + range = x; + return rescale(); + }; + scale.rangeRound = function(x) { + return scale.range(x).interpolate(d3_interpolateRound); + }; + scale.clamp = function(x) { + if (!arguments.length) return clamp; + clamp = x; + return rescale(); + }; + scale.interpolate = function(x) { + if (!arguments.length) return interpolate; + interpolate = x; + return rescale(); + }; + scale.ticks = function(m) { + return d3_scale_linearTicks(domain, m); + }; + scale.tickFormat = function(m, format) { + return d3_scale_linearTickFormat(domain, m, format); + }; + scale.nice = function(m) { + d3_scale_linearNice(domain, m); + return rescale(); + }; + scale.copy = function() { + return d3_scale_linear(domain, range, interpolate, clamp); + }; + return rescale(); + } + function d3_scale_linearRebind(scale, linear) { + return d3.rebind(scale, linear, "range", "rangeRound", "interpolate", "clamp"); + } + function d3_scale_linearNice(domain, m) { + d3_scale_nice(domain, d3_scale_niceStep(d3_scale_linearTickRange(domain, m)[2])); + d3_scale_nice(domain, d3_scale_niceStep(d3_scale_linearTickRange(domain, m)[2])); + return domain; + } + function d3_scale_linearTickRange(domain, m) { + if (m == null) m = 10; + var extent = d3_scaleExtent(domain), span = extent[1] - extent[0], step = Math.pow(10, Math.floor(Math.log(span / m) / Math.LN10)), err = m / span * step; + if (err <= .15) step *= 10; else if (err <= .35) step *= 5; else if (err <= .75) step *= 2; + extent[0] = Math.ceil(extent[0] / step) * step; + extent[1] = Math.floor(extent[1] / step) * step + step * .5; + extent[2] = step; + return extent; + } + function d3_scale_linearTicks(domain, m) { + return d3.range.apply(d3, d3_scale_linearTickRange(domain, m)); + } + function d3_scale_linearTickFormat(domain, m, format) { + var range = d3_scale_linearTickRange(domain, m); + if (format) { + var match = d3_format_re.exec(format); + match.shift(); + if (match[8] === "s") { + var prefix = d3.formatPrefix(Math.max(abs(range[0]), abs(range[1]))); + if (!match[7]) match[7] = "." + d3_scale_linearPrecision(prefix.scale(range[2])); + match[8] = "f"; + format = d3.format(match.join("")); + return function(d) { + return format(prefix.scale(d)) + prefix.symbol; + }; + } + if (!match[7]) match[7] = "." + d3_scale_linearFormatPrecision(match[8], range); + format = match.join(""); + } else { + format = ",." + d3_scale_linearPrecision(range[2]) + "f"; + } + return d3.format(format); + } + var d3_scale_linearFormatSignificant = { + s: 1, + g: 1, + p: 1, + r: 1, + e: 1 + }; + function d3_scale_linearPrecision(value) { + return -Math.floor(Math.log(value) / Math.LN10 + .01); + } + function d3_scale_linearFormatPrecision(type, range) { + var p = d3_scale_linearPrecision(range[2]); + return type in d3_scale_linearFormatSignificant ? Math.abs(p - d3_scale_linearPrecision(Math.max(abs(range[0]), abs(range[1])))) + +(type !== "e") : p - (type === "%") * 2; + } + d3.scale.log = function() { + return d3_scale_log(d3.scale.linear().domain([ 0, 1 ]), 10, true, [ 1, 10 ]); + }; + function d3_scale_log(linear, base, positive, domain) { + function log(x) { + return (positive ? Math.log(x < 0 ? 0 : x) : -Math.log(x > 0 ? 0 : -x)) / Math.log(base); + } + function pow(x) { + return positive ? Math.pow(base, x) : -Math.pow(base, -x); + } + function scale(x) { + return linear(log(x)); + } + scale.invert = function(x) { + return pow(linear.invert(x)); + }; + scale.domain = function(x) { + if (!arguments.length) return domain; + positive = x[0] >= 0; + linear.domain((domain = x.map(Number)).map(log)); + return scale; + }; + scale.base = function(_) { + if (!arguments.length) return base; + base = +_; + linear.domain(domain.map(log)); + return scale; + }; + scale.nice = function() { + var niced = d3_scale_nice(domain.map(log), positive ? Math : d3_scale_logNiceNegative); + linear.domain(niced); + domain = niced.map(pow); + return scale; + }; + scale.ticks = function() { + var extent = d3_scaleExtent(domain), ticks = [], u = extent[0], v = extent[1], i = Math.floor(log(u)), j = Math.ceil(log(v)), n = base % 1 ? 2 : base; + if (isFinite(j - i)) { + if (positive) { + for (;i < j; i++) for (var k = 1; k < n; k++) ticks.push(pow(i) * k); + ticks.push(pow(i)); + } else { + ticks.push(pow(i)); + for (;i++ < j; ) for (var k = n - 1; k > 0; k--) ticks.push(pow(i) * k); + } + for (i = 0; ticks[i] < u; i++) {} + for (j = ticks.length; ticks[j - 1] > v; j--) {} + ticks = ticks.slice(i, j); + } + return ticks; + }; + scale.tickFormat = function(n, format) { + if (!arguments.length) return d3_scale_logFormat; + if (arguments.length < 2) format = d3_scale_logFormat; else if (typeof format !== "function") format = d3.format(format); + var k = Math.max(1, base * n / scale.ticks().length); + return function(d) { + var i = d / pow(Math.round(log(d))); + if (i * base < base - .5) i *= base; + return i <= k ? format(d) : ""; + }; + }; + scale.copy = function() { + return d3_scale_log(linear.copy(), base, positive, domain); + }; + return d3_scale_linearRebind(scale, linear); + } + var d3_scale_logFormat = d3.format(".0e"), d3_scale_logNiceNegative = { + floor: function(x) { + return -Math.ceil(-x); + }, + ceil: function(x) { + return -Math.floor(-x); + } + }; + d3.scale.pow = function() { + return d3_scale_pow(d3.scale.linear(), 1, [ 0, 1 ]); + }; + function d3_scale_pow(linear, exponent, domain) { + var powp = d3_scale_powPow(exponent), powb = d3_scale_powPow(1 / exponent); + function scale(x) { + return linear(powp(x)); + } + scale.invert = function(x) { + return powb(linear.invert(x)); + }; + scale.domain = function(x) { + if (!arguments.length) return domain; + linear.domain((domain = x.map(Number)).map(powp)); + return scale; + }; + scale.ticks = function(m) { + return d3_scale_linearTicks(domain, m); + }; + scale.tickFormat = function(m, format) { + return d3_scale_linearTickFormat(domain, m, format); + }; + scale.nice = function(m) { + return scale.domain(d3_scale_linearNice(domain, m)); + }; + scale.exponent = function(x) { + if (!arguments.length) return exponent; + powp = d3_scale_powPow(exponent = x); + powb = d3_scale_powPow(1 / exponent); + linear.domain(domain.map(powp)); + return scale; + }; + scale.copy = function() { + return d3_scale_pow(linear.copy(), exponent, domain); + }; + return d3_scale_linearRebind(scale, linear); + } + function d3_scale_powPow(e) { + return function(x) { + return x < 0 ? -Math.pow(-x, e) : Math.pow(x, e); + }; + } + d3.scale.sqrt = function() { + return d3.scale.pow().exponent(.5); + }; + d3.scale.ordinal = function() { + return d3_scale_ordinal([], { + t: "range", + a: [ [] ] + }); + }; + function d3_scale_ordinal(domain, ranger) { + var index, range, rangeBand; + function scale(x) { + return range[((index.get(x) || (ranger.t === "range" ? index.set(x, domain.push(x)) : NaN)) - 1) % range.length]; + } + function steps(start, step) { + return d3.range(domain.length).map(function(i) { + return start + step * i; + }); + } + scale.domain = function(x) { + if (!arguments.length) return domain; + domain = []; + index = new d3_Map(); + var i = -1, n = x.length, xi; + while (++i < n) if (!index.has(xi = x[i])) index.set(xi, domain.push(xi)); + return scale[ranger.t].apply(scale, ranger.a); + }; + scale.range = function(x) { + if (!arguments.length) return range; + range = x; + rangeBand = 0; + ranger = { + t: "range", + a: arguments + }; + return scale; + }; + scale.rangePoints = function(x, padding) { + if (arguments.length < 2) padding = 0; + var start = x[0], stop = x[1], step = domain.length < 2 ? (start = (start + stop) / 2, + 0) : (stop - start) / (domain.length - 1 + padding); + range = steps(start + step * padding / 2, step); + rangeBand = 0; + ranger = { + t: "rangePoints", + a: arguments + }; + return scale; + }; + scale.rangeRoundPoints = function(x, padding) { + if (arguments.length < 2) padding = 0; + var start = x[0], stop = x[1], step = domain.length < 2 ? (start = stop = Math.round((start + stop) / 2), + 0) : (stop - start) / (domain.length - 1 + padding) | 0; + range = steps(start + Math.round(step * padding / 2 + (stop - start - (domain.length - 1 + padding) * step) / 2), step); + rangeBand = 0; + ranger = { + t: "rangeRoundPoints", + a: arguments + }; + return scale; + }; + scale.rangeBands = function(x, padding, outerPadding) { + if (arguments.length < 2) padding = 0; + if (arguments.length < 3) outerPadding = padding; + var reverse = x[1] < x[0], start = x[reverse - 0], stop = x[1 - reverse], step = (stop - start) / (domain.length - padding + 2 * outerPadding); + range = steps(start + step * outerPadding, step); + if (reverse) range.reverse(); + rangeBand = step * (1 - padding); + ranger = { + t: "rangeBands", + a: arguments + }; + return scale; + }; + scale.rangeRoundBands = function(x, padding, outerPadding) { + if (arguments.length < 2) padding = 0; + if (arguments.length < 3) outerPadding = padding; + var reverse = x[1] < x[0], start = x[reverse - 0], stop = x[1 - reverse], step = Math.floor((stop - start) / (domain.length - padding + 2 * outerPadding)); + range = steps(start + Math.round((stop - start - (domain.length - padding) * step) / 2), step); + if (reverse) range.reverse(); + rangeBand = Math.round(step * (1 - padding)); + ranger = { + t: "rangeRoundBands", + a: arguments + }; + return scale; + }; + scale.rangeBand = function() { + return rangeBand; + }; + scale.rangeExtent = function() { + return d3_scaleExtent(ranger.a[0]); + }; + scale.copy = function() { + return d3_scale_ordinal(domain, ranger); + }; + return scale.domain(domain); + } + d3.scale.category10 = function() { + return d3.scale.ordinal().range(d3_category10); + }; + d3.scale.category20 = function() { + return d3.scale.ordinal().range(d3_category20); + }; + d3.scale.category20b = function() { + return d3.scale.ordinal().range(d3_category20b); + }; + d3.scale.category20c = function() { + return d3.scale.ordinal().range(d3_category20c); + }; + var d3_category10 = [ 2062260, 16744206, 2924588, 14034728, 9725885, 9197131, 14907330, 8355711, 12369186, 1556175 ].map(d3_rgbString); + var d3_category20 = [ 2062260, 11454440, 16744206, 16759672, 2924588, 10018698, 14034728, 16750742, 9725885, 12955861, 9197131, 12885140, 14907330, 16234194, 8355711, 13092807, 12369186, 14408589, 1556175, 10410725 ].map(d3_rgbString); + var d3_category20b = [ 3750777, 5395619, 7040719, 10264286, 6519097, 9216594, 11915115, 13556636, 9202993, 12426809, 15186514, 15190932, 8666169, 11356490, 14049643, 15177372, 8077683, 10834324, 13528509, 14589654 ].map(d3_rgbString); + var d3_category20c = [ 3244733, 7057110, 10406625, 13032431, 15095053, 16616764, 16625259, 16634018, 3253076, 7652470, 10607003, 13101504, 7695281, 10394312, 12369372, 14342891, 6513507, 9868950, 12434877, 14277081 ].map(d3_rgbString); + d3.scale.quantile = function() { + return d3_scale_quantile([], []); + }; + function d3_scale_quantile(domain, range) { + var thresholds; + function rescale() { + var k = 0, q = range.length; + thresholds = []; + while (++k < q) thresholds[k - 1] = d3.quantile(domain, k / q); + return scale; + } + function scale(x) { + if (!isNaN(x = +x)) return range[d3.bisect(thresholds, x)]; + } + scale.domain = function(x) { + if (!arguments.length) return domain; + domain = x.map(d3_number).filter(d3_numeric).sort(d3_ascending); + return rescale(); + }; + scale.range = function(x) { + if (!arguments.length) return range; + range = x; + return rescale(); + }; + scale.quantiles = function() { + return thresholds; + }; + scale.invertExtent = function(y) { + y = range.indexOf(y); + return y < 0 ? [ NaN, NaN ] : [ y > 0 ? thresholds[y - 1] : domain[0], y < thresholds.length ? thresholds[y] : domain[domain.length - 1] ]; + }; + scale.copy = function() { + return d3_scale_quantile(domain, range); + }; + return rescale(); + } + d3.scale.quantize = function() { + return d3_scale_quantize(0, 1, [ 0, 1 ]); + }; + function d3_scale_quantize(x0, x1, range) { + var kx, i; + function scale(x) { + return range[Math.max(0, Math.min(i, Math.floor(kx * (x - x0))))]; + } + function rescale() { + kx = range.length / (x1 - x0); + i = range.length - 1; + return scale; + } + scale.domain = function(x) { + if (!arguments.length) return [ x0, x1 ]; + x0 = +x[0]; + x1 = +x[x.length - 1]; + return rescale(); + }; + scale.range = function(x) { + if (!arguments.length) return range; + range = x; + return rescale(); + }; + scale.invertExtent = function(y) { + y = range.indexOf(y); + y = y < 0 ? NaN : y / kx + x0; + return [ y, y + 1 / kx ]; + }; + scale.copy = function() { + return d3_scale_quantize(x0, x1, range); + }; + return rescale(); + } + d3.scale.threshold = function() { + return d3_scale_threshold([ .5 ], [ 0, 1 ]); + }; + function d3_scale_threshold(domain, range) { + function scale(x) { + if (x <= x) return range[d3.bisect(domain, x)]; + } + scale.domain = function(_) { + if (!arguments.length) return domain; + domain = _; + return scale; + }; + scale.range = function(_) { + if (!arguments.length) return range; + range = _; + return scale; + }; + scale.invertExtent = function(y) { + y = range.indexOf(y); + return [ domain[y - 1], domain[y] ]; + }; + scale.copy = function() { + return d3_scale_threshold(domain, range); + }; + return scale; + } + d3.scale.identity = function() { + return d3_scale_identity([ 0, 1 ]); + }; + function d3_scale_identity(domain) { + function identity(x) { + return +x; + } + identity.invert = identity; + identity.domain = identity.range = function(x) { + if (!arguments.length) return domain; + domain = x.map(identity); + return identity; + }; + identity.ticks = function(m) { + return d3_scale_linearTicks(domain, m); + }; + identity.tickFormat = function(m, format) { + return d3_scale_linearTickFormat(domain, m, format); + }; + identity.copy = function() { + return d3_scale_identity(domain); + }; + return identity; + } + d3.svg = {}; + function d3_zero() { + return 0; + } + d3.svg.arc = function() { + var innerRadius = d3_svg_arcInnerRadius, outerRadius = d3_svg_arcOuterRadius, cornerRadius = d3_zero, padRadius = d3_svg_arcAuto, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle, padAngle = d3_svg_arcPadAngle; + function arc() { + var r0 = Math.max(0, +innerRadius.apply(this, arguments)), r1 = Math.max(0, +outerRadius.apply(this, arguments)), a0 = startAngle.apply(this, arguments) - halfπ, a1 = endAngle.apply(this, arguments) - halfπ, da = Math.abs(a1 - a0), cw = a0 > a1 ? 0 : 1; + if (r1 < r0) rc = r1, r1 = r0, r0 = rc; + if (da >= τε) return circleSegment(r1, cw) + (r0 ? circleSegment(r0, 1 - cw) : "") + "Z"; + var rc, cr, rp, ap, p0 = 0, p1 = 0, x0, y0, x1, y1, x2, y2, x3, y3, path = []; + if (ap = (+padAngle.apply(this, arguments) || 0) / 2) { + rp = padRadius === d3_svg_arcAuto ? Math.sqrt(r0 * r0 + r1 * r1) : +padRadius.apply(this, arguments); + if (!cw) p1 *= -1; + if (r1) p1 = d3_asin(rp / r1 * Math.sin(ap)); + if (r0) p0 = d3_asin(rp / r0 * Math.sin(ap)); + } + if (r1) { + x0 = r1 * Math.cos(a0 + p1); + y0 = r1 * Math.sin(a0 + p1); + x1 = r1 * Math.cos(a1 - p1); + y1 = r1 * Math.sin(a1 - p1); + var l1 = Math.abs(a1 - a0 - 2 * p1) <= π ? 0 : 1; + if (p1 && d3_svg_arcSweep(x0, y0, x1, y1) === cw ^ l1) { + var h1 = (a0 + a1) / 2; + x0 = r1 * Math.cos(h1); + y0 = r1 * Math.sin(h1); + x1 = y1 = null; + } + } else { + x0 = y0 = 0; + } + if (r0) { + x2 = r0 * Math.cos(a1 - p0); + y2 = r0 * Math.sin(a1 - p0); + x3 = r0 * Math.cos(a0 + p0); + y3 = r0 * Math.sin(a0 + p0); + var l0 = Math.abs(a0 - a1 + 2 * p0) <= π ? 0 : 1; + if (p0 && d3_svg_arcSweep(x2, y2, x3, y3) === 1 - cw ^ l0) { + var h0 = (a0 + a1) / 2; + x2 = r0 * Math.cos(h0); + y2 = r0 * Math.sin(h0); + x3 = y3 = null; + } + } else { + x2 = y2 = 0; + } + if (da > ε && (rc = Math.min(Math.abs(r1 - r0) / 2, +cornerRadius.apply(this, arguments))) > .001) { + cr = r0 < r1 ^ cw ? 0 : 1; + var rc1 = rc, rc0 = rc; + if (da < π) { + var oc = x3 == null ? [ x2, y2 ] : x1 == null ? [ x0, y0 ] : d3_geom_polygonIntersect([ x0, y0 ], [ x3, y3 ], [ x1, y1 ], [ x2, y2 ]), ax = x0 - oc[0], ay = y0 - oc[1], bx = x1 - oc[0], by = y1 - oc[1], kc = 1 / Math.sin(Math.acos((ax * bx + ay * by) / (Math.sqrt(ax * ax + ay * ay) * Math.sqrt(bx * bx + by * by))) / 2), lc = Math.sqrt(oc[0] * oc[0] + oc[1] * oc[1]); + rc0 = Math.min(rc, (r0 - lc) / (kc - 1)); + rc1 = Math.min(rc, (r1 - lc) / (kc + 1)); + } + if (x1 != null) { + var t30 = d3_svg_arcCornerTangents(x3 == null ? [ x2, y2 ] : [ x3, y3 ], [ x0, y0 ], r1, rc1, cw), t12 = d3_svg_arcCornerTangents([ x1, y1 ], [ x2, y2 ], r1, rc1, cw); + if (rc === rc1) { + path.push("M", t30[0], "A", rc1, ",", rc1, " 0 0,", cr, " ", t30[1], "A", r1, ",", r1, " 0 ", 1 - cw ^ d3_svg_arcSweep(t30[1][0], t30[1][1], t12[1][0], t12[1][1]), ",", cw, " ", t12[1], "A", rc1, ",", rc1, " 0 0,", cr, " ", t12[0]); + } else { + path.push("M", t30[0], "A", rc1, ",", rc1, " 0 1,", cr, " ", t12[0]); + } + } else { + path.push("M", x0, ",", y0); + } + if (x3 != null) { + var t03 = d3_svg_arcCornerTangents([ x0, y0 ], [ x3, y3 ], r0, -rc0, cw), t21 = d3_svg_arcCornerTangents([ x2, y2 ], x1 == null ? [ x0, y0 ] : [ x1, y1 ], r0, -rc0, cw); + if (rc === rc0) { + path.push("L", t21[0], "A", rc0, ",", rc0, " 0 0,", cr, " ", t21[1], "A", r0, ",", r0, " 0 ", cw ^ d3_svg_arcSweep(t21[1][0], t21[1][1], t03[1][0], t03[1][1]), ",", 1 - cw, " ", t03[1], "A", rc0, ",", rc0, " 0 0,", cr, " ", t03[0]); + } else { + path.push("L", t21[0], "A", rc0, ",", rc0, " 0 0,", cr, " ", t03[0]); + } + } else { + path.push("L", x2, ",", y2); + } + } else { + path.push("M", x0, ",", y0); + if (x1 != null) path.push("A", r1, ",", r1, " 0 ", l1, ",", cw, " ", x1, ",", y1); + path.push("L", x2, ",", y2); + if (x3 != null) path.push("A", r0, ",", r0, " 0 ", l0, ",", 1 - cw, " ", x3, ",", y3); + } + path.push("Z"); + return path.join(""); + } + function circleSegment(r1, cw) { + return "M0," + r1 + "A" + r1 + "," + r1 + " 0 1," + cw + " 0," + -r1 + "A" + r1 + "," + r1 + " 0 1," + cw + " 0," + r1; + } + arc.innerRadius = function(v) { + if (!arguments.length) return innerRadius; + innerRadius = d3_functor(v); + return arc; + }; + arc.outerRadius = function(v) { + if (!arguments.length) return outerRadius; + outerRadius = d3_functor(v); + return arc; + }; + arc.cornerRadius = function(v) { + if (!arguments.length) return cornerRadius; + cornerRadius = d3_functor(v); + return arc; + }; + arc.padRadius = function(v) { + if (!arguments.length) return padRadius; + padRadius = v == d3_svg_arcAuto ? d3_svg_arcAuto : d3_functor(v); + return arc; + }; + arc.startAngle = function(v) { + if (!arguments.length) return startAngle; + startAngle = d3_functor(v); + return arc; + }; + arc.endAngle = function(v) { + if (!arguments.length) return endAngle; + endAngle = d3_functor(v); + return arc; + }; + arc.padAngle = function(v) { + if (!arguments.length) return padAngle; + padAngle = d3_functor(v); + return arc; + }; + arc.centroid = function() { + var r = (+innerRadius.apply(this, arguments) + +outerRadius.apply(this, arguments)) / 2, a = (+startAngle.apply(this, arguments) + +endAngle.apply(this, arguments)) / 2 - halfπ; + return [ Math.cos(a) * r, Math.sin(a) * r ]; + }; + return arc; + }; + var d3_svg_arcAuto = "auto"; + function d3_svg_arcInnerRadius(d) { + return d.innerRadius; + } + function d3_svg_arcOuterRadius(d) { + return d.outerRadius; + } + function d3_svg_arcStartAngle(d) { + return d.startAngle; + } + function d3_svg_arcEndAngle(d) { + return d.endAngle; + } + function d3_svg_arcPadAngle(d) { + return d && d.padAngle; + } + function d3_svg_arcSweep(x0, y0, x1, y1) { + return (x0 - x1) * y0 - (y0 - y1) * x0 > 0 ? 0 : 1; + } + function d3_svg_arcCornerTangents(p0, p1, r1, rc, cw) { + var x01 = p0[0] - p1[0], y01 = p0[1] - p1[1], lo = (cw ? rc : -rc) / Math.sqrt(x01 * x01 + y01 * y01), ox = lo * y01, oy = -lo * x01, x1 = p0[0] + ox, y1 = p0[1] + oy, x2 = p1[0] + ox, y2 = p1[1] + oy, x3 = (x1 + x2) / 2, y3 = (y1 + y2) / 2, dx = x2 - x1, dy = y2 - y1, d2 = dx * dx + dy * dy, r = r1 - rc, D = x1 * y2 - x2 * y1, d = (dy < 0 ? -1 : 1) * Math.sqrt(Math.max(0, r * r * d2 - D * D)), cx0 = (D * dy - dx * d) / d2, cy0 = (-D * dx - dy * d) / d2, cx1 = (D * dy + dx * d) / d2, cy1 = (-D * dx + dy * d) / d2, dx0 = cx0 - x3, dy0 = cy0 - y3, dx1 = cx1 - x3, dy1 = cy1 - y3; + if (dx0 * dx0 + dy0 * dy0 > dx1 * dx1 + dy1 * dy1) cx0 = cx1, cy0 = cy1; + return [ [ cx0 - ox, cy0 - oy ], [ cx0 * r1 / r, cy0 * r1 / r ] ]; + } + function d3_svg_line(projection) { + var x = d3_geom_pointX, y = d3_geom_pointY, defined = d3_true, interpolate = d3_svg_lineLinear, interpolateKey = interpolate.key, tension = .7; + function line(data) { + var segments = [], points = [], i = -1, n = data.length, d, fx = d3_functor(x), fy = d3_functor(y); + function segment() { + segments.push("M", interpolate(projection(points), tension)); + } + while (++i < n) { + if (defined.call(this, d = data[i], i)) { + points.push([ +fx.call(this, d, i), +fy.call(this, d, i) ]); + } else if (points.length) { + segment(); + points = []; + } + } + if (points.length) segment(); + return segments.length ? segments.join("") : null; + } + line.x = function(_) { + if (!arguments.length) return x; + x = _; + return line; + }; + line.y = function(_) { + if (!arguments.length) return y; + y = _; + return line; + }; + line.defined = function(_) { + if (!arguments.length) return defined; + defined = _; + return line; + }; + line.interpolate = function(_) { + if (!arguments.length) return interpolateKey; + if (typeof _ === "function") interpolateKey = interpolate = _; else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key; + return line; + }; + line.tension = function(_) { + if (!arguments.length) return tension; + tension = _; + return line; + }; + return line; + } + d3.svg.line = function() { + return d3_svg_line(d3_identity); + }; + var d3_svg_lineInterpolators = d3.map({ + linear: d3_svg_lineLinear, + "linear-closed": d3_svg_lineLinearClosed, + step: d3_svg_lineStep, + "step-before": d3_svg_lineStepBefore, + "step-after": d3_svg_lineStepAfter, + basis: d3_svg_lineBasis, + "basis-open": d3_svg_lineBasisOpen, + "basis-closed": d3_svg_lineBasisClosed, + bundle: d3_svg_lineBundle, + cardinal: d3_svg_lineCardinal, + "cardinal-open": d3_svg_lineCardinalOpen, + "cardinal-closed": d3_svg_lineCardinalClosed, + monotone: d3_svg_lineMonotone + }); + d3_svg_lineInterpolators.forEach(function(key, value) { + value.key = key; + value.closed = /-closed$/.test(key); + }); + function d3_svg_lineLinear(points) { + return points.length > 1 ? points.join("L") : points + "Z"; + } + function d3_svg_lineLinearClosed(points) { + return points.join("L") + "Z"; + } + function d3_svg_lineStep(points) { + var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ]; + while (++i < n) path.push("H", (p[0] + (p = points[i])[0]) / 2, "V", p[1]); + if (n > 1) path.push("H", p[0]); + return path.join(""); + } + function d3_svg_lineStepBefore(points) { + var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ]; + while (++i < n) path.push("V", (p = points[i])[1], "H", p[0]); + return path.join(""); + } + function d3_svg_lineStepAfter(points) { + var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ]; + while (++i < n) path.push("H", (p = points[i])[0], "V", p[1]); + return path.join(""); + } + function d3_svg_lineCardinalOpen(points, tension) { + return points.length < 4 ? d3_svg_lineLinear(points) : points[1] + d3_svg_lineHermite(points.slice(1, -1), d3_svg_lineCardinalTangents(points, tension)); + } + function d3_svg_lineCardinalClosed(points, tension) { + return points.length < 3 ? d3_svg_lineLinearClosed(points) : points[0] + d3_svg_lineHermite((points.push(points[0]), + points), d3_svg_lineCardinalTangents([ points[points.length - 2] ].concat(points, [ points[1] ]), tension)); + } + function d3_svg_lineCardinal(points, tension) { + return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite(points, d3_svg_lineCardinalTangents(points, tension)); + } + function d3_svg_lineHermite(points, tangents) { + if (tangents.length < 1 || points.length != tangents.length && points.length != tangents.length + 2) { + return d3_svg_lineLinear(points); + } + var quad = points.length != tangents.length, path = "", p0 = points[0], p = points[1], t0 = tangents[0], t = t0, pi = 1; + if (quad) { + path += "Q" + (p[0] - t0[0] * 2 / 3) + "," + (p[1] - t0[1] * 2 / 3) + "," + p[0] + "," + p[1]; + p0 = points[1]; + pi = 2; + } + if (tangents.length > 1) { + t = tangents[1]; + p = points[pi]; + pi++; + path += "C" + (p0[0] + t0[0]) + "," + (p0[1] + t0[1]) + "," + (p[0] - t[0]) + "," + (p[1] - t[1]) + "," + p[0] + "," + p[1]; + for (var i = 2; i < tangents.length; i++, pi++) { + p = points[pi]; + t = tangents[i]; + path += "S" + (p[0] - t[0]) + "," + (p[1] - t[1]) + "," + p[0] + "," + p[1]; + } + } + if (quad) { + var lp = points[pi]; + path += "Q" + (p[0] + t[0] * 2 / 3) + "," + (p[1] + t[1] * 2 / 3) + "," + lp[0] + "," + lp[1]; + } + return path; + } + function d3_svg_lineCardinalTangents(points, tension) { + var tangents = [], a = (1 - tension) / 2, p0, p1 = points[0], p2 = points[1], i = 1, n = points.length; + while (++i < n) { + p0 = p1; + p1 = p2; + p2 = points[i]; + tangents.push([ a * (p2[0] - p0[0]), a * (p2[1] - p0[1]) ]); + } + return tangents; + } + function d3_svg_lineBasis(points) { + if (points.length < 3) return d3_svg_lineLinear(points); + var i = 1, n = points.length, pi = points[0], x0 = pi[0], y0 = pi[1], px = [ x0, x0, x0, (pi = points[1])[0] ], py = [ y0, y0, y0, pi[1] ], path = [ x0, ",", y0, "L", d3_svg_lineDot4(d3_svg_lineBasisBezier3, px), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, py) ]; + points.push(points[n - 1]); + while (++i <= n) { + pi = points[i]; + px.shift(); + px.push(pi[0]); + py.shift(); + py.push(pi[1]); + d3_svg_lineBasisBezier(path, px, py); + } + points.pop(); + path.push("L", pi); + return path.join(""); + } + function d3_svg_lineBasisOpen(points) { + if (points.length < 4) return d3_svg_lineLinear(points); + var path = [], i = -1, n = points.length, pi, px = [ 0 ], py = [ 0 ]; + while (++i < 3) { + pi = points[i]; + px.push(pi[0]); + py.push(pi[1]); + } + path.push(d3_svg_lineDot4(d3_svg_lineBasisBezier3, px) + "," + d3_svg_lineDot4(d3_svg_lineBasisBezier3, py)); + --i; + while (++i < n) { + pi = points[i]; + px.shift(); + px.push(pi[0]); + py.shift(); + py.push(pi[1]); + d3_svg_lineBasisBezier(path, px, py); + } + return path.join(""); + } + function d3_svg_lineBasisClosed(points) { + var path, i = -1, n = points.length, m = n + 4, pi, px = [], py = []; + while (++i < 4) { + pi = points[i % n]; + px.push(pi[0]); + py.push(pi[1]); + } + path = [ d3_svg_lineDot4(d3_svg_lineBasisBezier3, px), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, py) ]; + --i; + while (++i < m) { + pi = points[i % n]; + px.shift(); + px.push(pi[0]); + py.shift(); + py.push(pi[1]); + d3_svg_lineBasisBezier(path, px, py); + } + return path.join(""); + } + function d3_svg_lineBundle(points, tension) { + var n = points.length - 1; + if (n) { + var x0 = points[0][0], y0 = points[0][1], dx = points[n][0] - x0, dy = points[n][1] - y0, i = -1, p, t; + while (++i <= n) { + p = points[i]; + t = i / n; + p[0] = tension * p[0] + (1 - tension) * (x0 + t * dx); + p[1] = tension * p[1] + (1 - tension) * (y0 + t * dy); + } + } + return d3_svg_lineBasis(points); + } + function d3_svg_lineDot4(a, b) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3]; + } + var d3_svg_lineBasisBezier1 = [ 0, 2 / 3, 1 / 3, 0 ], d3_svg_lineBasisBezier2 = [ 0, 1 / 3, 2 / 3, 0 ], d3_svg_lineBasisBezier3 = [ 0, 1 / 6, 2 / 3, 1 / 6 ]; + function d3_svg_lineBasisBezier(path, x, y) { + path.push("C", d3_svg_lineDot4(d3_svg_lineBasisBezier1, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier1, y), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, y), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, y)); + } + function d3_svg_lineSlope(p0, p1) { + return (p1[1] - p0[1]) / (p1[0] - p0[0]); + } + function d3_svg_lineFiniteDifferences(points) { + var i = 0, j = points.length - 1, m = [], p0 = points[0], p1 = points[1], d = m[0] = d3_svg_lineSlope(p0, p1); + while (++i < j) { + m[i] = (d + (d = d3_svg_lineSlope(p0 = p1, p1 = points[i + 1]))) / 2; + } + m[i] = d; + return m; + } + function d3_svg_lineMonotoneTangents(points) { + var tangents = [], d, a, b, s, m = d3_svg_lineFiniteDifferences(points), i = -1, j = points.length - 1; + while (++i < j) { + d = d3_svg_lineSlope(points[i], points[i + 1]); + if (abs(d) < ε) { + m[i] = m[i + 1] = 0; + } else { + a = m[i] / d; + b = m[i + 1] / d; + s = a * a + b * b; + if (s > 9) { + s = d * 3 / Math.sqrt(s); + m[i] = s * a; + m[i + 1] = s * b; + } + } + } + i = -1; + while (++i <= j) { + s = (points[Math.min(j, i + 1)][0] - points[Math.max(0, i - 1)][0]) / (6 * (1 + m[i] * m[i])); + tangents.push([ s || 0, m[i] * s || 0 ]); + } + return tangents; + } + function d3_svg_lineMonotone(points) { + return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite(points, d3_svg_lineMonotoneTangents(points)); + } + d3.svg.line.radial = function() { + var line = d3_svg_line(d3_svg_lineRadial); + line.radius = line.x, delete line.x; + line.angle = line.y, delete line.y; + return line; + }; + function d3_svg_lineRadial(points) { + var point, i = -1, n = points.length, r, a; + while (++i < n) { + point = points[i]; + r = point[0]; + a = point[1] - halfπ; + point[0] = r * Math.cos(a); + point[1] = r * Math.sin(a); + } + return points; + } + function d3_svg_area(projection) { + var x0 = d3_geom_pointX, x1 = d3_geom_pointX, y0 = 0, y1 = d3_geom_pointY, defined = d3_true, interpolate = d3_svg_lineLinear, interpolateKey = interpolate.key, interpolateReverse = interpolate, L = "L", tension = .7; + function area(data) { + var segments = [], points0 = [], points1 = [], i = -1, n = data.length, d, fx0 = d3_functor(x0), fy0 = d3_functor(y0), fx1 = x0 === x1 ? function() { + return x; + } : d3_functor(x1), fy1 = y0 === y1 ? function() { + return y; + } : d3_functor(y1), x, y; + function segment() { + segments.push("M", interpolate(projection(points1), tension), L, interpolateReverse(projection(points0.reverse()), tension), "Z"); + } + while (++i < n) { + if (defined.call(this, d = data[i], i)) { + points0.push([ x = +fx0.call(this, d, i), y = +fy0.call(this, d, i) ]); + points1.push([ +fx1.call(this, d, i), +fy1.call(this, d, i) ]); + } else if (points0.length) { + segment(); + points0 = []; + points1 = []; + } + } + if (points0.length) segment(); + return segments.length ? segments.join("") : null; + } + area.x = function(_) { + if (!arguments.length) return x1; + x0 = x1 = _; + return area; + }; + area.x0 = function(_) { + if (!arguments.length) return x0; + x0 = _; + return area; + }; + area.x1 = function(_) { + if (!arguments.length) return x1; + x1 = _; + return area; + }; + area.y = function(_) { + if (!arguments.length) return y1; + y0 = y1 = _; + return area; + }; + area.y0 = function(_) { + if (!arguments.length) return y0; + y0 = _; + return area; + }; + area.y1 = function(_) { + if (!arguments.length) return y1; + y1 = _; + return area; + }; + area.defined = function(_) { + if (!arguments.length) return defined; + defined = _; + return area; + }; + area.interpolate = function(_) { + if (!arguments.length) return interpolateKey; + if (typeof _ === "function") interpolateKey = interpolate = _; else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key; + interpolateReverse = interpolate.reverse || interpolate; + L = interpolate.closed ? "M" : "L"; + return area; + }; + area.tension = function(_) { + if (!arguments.length) return tension; + tension = _; + return area; + }; + return area; + } + d3_svg_lineStepBefore.reverse = d3_svg_lineStepAfter; + d3_svg_lineStepAfter.reverse = d3_svg_lineStepBefore; + d3.svg.area = function() { + return d3_svg_area(d3_identity); + }; + d3.svg.area.radial = function() { + var area = d3_svg_area(d3_svg_lineRadial); + area.radius = area.x, delete area.x; + area.innerRadius = area.x0, delete area.x0; + area.outerRadius = area.x1, delete area.x1; + area.angle = area.y, delete area.y; + area.startAngle = area.y0, delete area.y0; + area.endAngle = area.y1, delete area.y1; + return area; + }; + d3.svg.chord = function() { + var source = d3_source, target = d3_target, radius = d3_svg_chordRadius, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle; + function chord(d, i) { + var s = subgroup(this, source, d, i), t = subgroup(this, target, d, i); + return "M" + s.p0 + arc(s.r, s.p1, s.a1 - s.a0) + (equals(s, t) ? curve(s.r, s.p1, s.r, s.p0) : curve(s.r, s.p1, t.r, t.p0) + arc(t.r, t.p1, t.a1 - t.a0) + curve(t.r, t.p1, s.r, s.p0)) + "Z"; + } + function subgroup(self, f, d, i) { + var subgroup = f.call(self, d, i), r = radius.call(self, subgroup, i), a0 = startAngle.call(self, subgroup, i) - halfπ, a1 = endAngle.call(self, subgroup, i) - halfπ; + return { + r: r, + a0: a0, + a1: a1, + p0: [ r * Math.cos(a0), r * Math.sin(a0) ], + p1: [ r * Math.cos(a1), r * Math.sin(a1) ] + }; + } + function equals(a, b) { + return a.a0 == b.a0 && a.a1 == b.a1; + } + function arc(r, p, a) { + return "A" + r + "," + r + " 0 " + +(a > π) + ",1 " + p; + } + function curve(r0, p0, r1, p1) { + return "Q 0,0 " + p1; + } + chord.radius = function(v) { + if (!arguments.length) return radius; + radius = d3_functor(v); + return chord; + }; + chord.source = function(v) { + if (!arguments.length) return source; + source = d3_functor(v); + return chord; + }; + chord.target = function(v) { + if (!arguments.length) return target; + target = d3_functor(v); + return chord; + }; + chord.startAngle = function(v) { + if (!arguments.length) return startAngle; + startAngle = d3_functor(v); + return chord; + }; + chord.endAngle = function(v) { + if (!arguments.length) return endAngle; + endAngle = d3_functor(v); + return chord; + }; + return chord; + }; + function d3_svg_chordRadius(d) { + return d.radius; + } + d3.svg.diagonal = function() { + var source = d3_source, target = d3_target, projection = d3_svg_diagonalProjection; + function diagonal(d, i) { + var p0 = source.call(this, d, i), p3 = target.call(this, d, i), m = (p0.y + p3.y) / 2, p = [ p0, { + x: p0.x, + y: m + }, { + x: p3.x, + y: m + }, p3 ]; + p = p.map(projection); + return "M" + p[0] + "C" + p[1] + " " + p[2] + " " + p[3]; + } + diagonal.source = function(x) { + if (!arguments.length) return source; + source = d3_functor(x); + return diagonal; + }; + diagonal.target = function(x) { + if (!arguments.length) return target; + target = d3_functor(x); + return diagonal; + }; + diagonal.projection = function(x) { + if (!arguments.length) return projection; + projection = x; + return diagonal; + }; + return diagonal; + }; + function d3_svg_diagonalProjection(d) { + return [ d.x, d.y ]; + } + d3.svg.diagonal.radial = function() { + var diagonal = d3.svg.diagonal(), projection = d3_svg_diagonalProjection, projection_ = diagonal.projection; + diagonal.projection = function(x) { + return arguments.length ? projection_(d3_svg_diagonalRadialProjection(projection = x)) : projection; + }; + return diagonal; + }; + function d3_svg_diagonalRadialProjection(projection) { + return function() { + var d = projection.apply(this, arguments), r = d[0], a = d[1] - halfπ; + return [ r * Math.cos(a), r * Math.sin(a) ]; + }; + } + d3.svg.symbol = function() { + var type = d3_svg_symbolType, size = d3_svg_symbolSize; + function symbol(d, i) { + return (d3_svg_symbols.get(type.call(this, d, i)) || d3_svg_symbolCircle)(size.call(this, d, i)); + } + symbol.type = function(x) { + if (!arguments.length) return type; + type = d3_functor(x); + return symbol; + }; + symbol.size = function(x) { + if (!arguments.length) return size; + size = d3_functor(x); + return symbol; + }; + return symbol; + }; + function d3_svg_symbolSize() { + return 64; + } + function d3_svg_symbolType() { + return "circle"; + } + function d3_svg_symbolCircle(size) { + var r = Math.sqrt(size / π); + return "M0," + r + "A" + r + "," + r + " 0 1,1 0," + -r + "A" + r + "," + r + " 0 1,1 0," + r + "Z"; + } + var d3_svg_symbols = d3.map({ + circle: d3_svg_symbolCircle, + cross: function(size) { + var r = Math.sqrt(size / 5) / 2; + return "M" + -3 * r + "," + -r + "H" + -r + "V" + -3 * r + "H" + r + "V" + -r + "H" + 3 * r + "V" + r + "H" + r + "V" + 3 * r + "H" + -r + "V" + r + "H" + -3 * r + "Z"; + }, + diamond: function(size) { + var ry = Math.sqrt(size / (2 * d3_svg_symbolTan30)), rx = ry * d3_svg_symbolTan30; + return "M0," + -ry + "L" + rx + ",0" + " 0," + ry + " " + -rx + ",0" + "Z"; + }, + square: function(size) { + var r = Math.sqrt(size) / 2; + return "M" + -r + "," + -r + "L" + r + "," + -r + " " + r + "," + r + " " + -r + "," + r + "Z"; + }, + "triangle-down": function(size) { + var rx = Math.sqrt(size / d3_svg_symbolSqrt3), ry = rx * d3_svg_symbolSqrt3 / 2; + return "M0," + ry + "L" + rx + "," + -ry + " " + -rx + "," + -ry + "Z"; + }, + "triangle-up": function(size) { + var rx = Math.sqrt(size / d3_svg_symbolSqrt3), ry = rx * d3_svg_symbolSqrt3 / 2; + return "M0," + -ry + "L" + rx + "," + ry + " " + -rx + "," + ry + "Z"; + } + }); + d3.svg.symbolTypes = d3_svg_symbols.keys(); + var d3_svg_symbolSqrt3 = Math.sqrt(3), d3_svg_symbolTan30 = Math.tan(30 * d3_radians); + d3_selectionPrototype.transition = function(name) { + var id = d3_transitionInheritId || ++d3_transitionId, ns = d3_transitionNamespace(name), subgroups = [], subgroup, node, transition = d3_transitionInherit || { + time: Date.now(), + ease: d3_ease_cubicInOut, + delay: 0, + duration: 250 + }; + for (var j = -1, m = this.length; ++j < m; ) { + subgroups.push(subgroup = []); + for (var group = this[j], i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) d3_transitionNode(node, i, ns, id, transition); + subgroup.push(node); + } + } + return d3_transition(subgroups, ns, id); + }; + d3_selectionPrototype.interrupt = function(name) { + return this.each(name == null ? d3_selection_interrupt : d3_selection_interruptNS(d3_transitionNamespace(name))); + }; + var d3_selection_interrupt = d3_selection_interruptNS(d3_transitionNamespace()); + function d3_selection_interruptNS(ns) { + return function() { + var lock, activeId, active; + if ((lock = this[ns]) && (active = lock[activeId = lock.active])) { + active.timer.c = null; + active.timer.t = NaN; + if (--lock.count) delete lock[activeId]; else delete this[ns]; + lock.active += .5; + active.event && active.event.interrupt.call(this, this.__data__, active.index); + } + }; + } + function d3_transition(groups, ns, id) { + d3_subclass(groups, d3_transitionPrototype); + groups.namespace = ns; + groups.id = id; + return groups; + } + var d3_transitionPrototype = [], d3_transitionId = 0, d3_transitionInheritId, d3_transitionInherit; + d3_transitionPrototype.call = d3_selectionPrototype.call; + d3_transitionPrototype.empty = d3_selectionPrototype.empty; + d3_transitionPrototype.node = d3_selectionPrototype.node; + d3_transitionPrototype.size = d3_selectionPrototype.size; + d3.transition = function(selection, name) { + return selection && selection.transition ? d3_transitionInheritId ? selection.transition(name) : selection : d3.selection().transition(selection); + }; + d3.transition.prototype = d3_transitionPrototype; + d3_transitionPrototype.select = function(selector) { + var id = this.id, ns = this.namespace, subgroups = [], subgroup, subnode, node; + selector = d3_selection_selector(selector); + for (var j = -1, m = this.length; ++j < m; ) { + subgroups.push(subgroup = []); + for (var group = this[j], i = -1, n = group.length; ++i < n; ) { + if ((node = group[i]) && (subnode = selector.call(node, node.__data__, i, j))) { + if ("__data__" in node) subnode.__data__ = node.__data__; + d3_transitionNode(subnode, i, ns, id, node[ns][id]); + subgroup.push(subnode); + } else { + subgroup.push(null); + } + } + } + return d3_transition(subgroups, ns, id); + }; + d3_transitionPrototype.selectAll = function(selector) { + var id = this.id, ns = this.namespace, subgroups = [], subgroup, subnodes, node, subnode, transition; + selector = d3_selection_selectorAll(selector); + for (var j = -1, m = this.length; ++j < m; ) { + for (var group = this[j], i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) { + transition = node[ns][id]; + subnodes = selector.call(node, node.__data__, i, j); + subgroups.push(subgroup = []); + for (var k = -1, o = subnodes.length; ++k < o; ) { + if (subnode = subnodes[k]) d3_transitionNode(subnode, k, ns, id, transition); + subgroup.push(subnode); + } + } + } + } + return d3_transition(subgroups, ns, id); + }; + d3_transitionPrototype.filter = function(filter) { + var subgroups = [], subgroup, group, node; + if (typeof filter !== "function") filter = d3_selection_filter(filter); + for (var j = 0, m = this.length; j < m; j++) { + subgroups.push(subgroup = []); + for (var group = this[j], i = 0, n = group.length; i < n; i++) { + if ((node = group[i]) && filter.call(node, node.__data__, i, j)) { + subgroup.push(node); + } + } + } + return d3_transition(subgroups, this.namespace, this.id); + }; + d3_transitionPrototype.tween = function(name, tween) { + var id = this.id, ns = this.namespace; + if (arguments.length < 2) return this.node()[ns][id].tween.get(name); + return d3_selection_each(this, tween == null ? function(node) { + node[ns][id].tween.remove(name); + } : function(node) { + node[ns][id].tween.set(name, tween); + }); + }; + function d3_transition_tween(groups, name, value, tween) { + var id = groups.id, ns = groups.namespace; + return d3_selection_each(groups, typeof value === "function" ? function(node, i, j) { + node[ns][id].tween.set(name, tween(value.call(node, node.__data__, i, j))); + } : (value = tween(value), function(node) { + node[ns][id].tween.set(name, value); + })); + } + d3_transitionPrototype.attr = function(nameNS, value) { + if (arguments.length < 2) { + for (value in nameNS) this.attr(value, nameNS[value]); + return this; + } + var interpolate = nameNS == "transform" ? d3_interpolateTransform : d3_interpolate, name = d3.ns.qualify(nameNS); + function attrNull() { + this.removeAttribute(name); + } + function attrNullNS() { + this.removeAttributeNS(name.space, name.local); + } + function attrTween(b) { + return b == null ? attrNull : (b += "", function() { + var a = this.getAttribute(name), i; + return a !== b && (i = interpolate(a, b), function(t) { + this.setAttribute(name, i(t)); + }); + }); + } + function attrTweenNS(b) { + return b == null ? attrNullNS : (b += "", function() { + var a = this.getAttributeNS(name.space, name.local), i; + return a !== b && (i = interpolate(a, b), function(t) { + this.setAttributeNS(name.space, name.local, i(t)); + }); + }); + } + return d3_transition_tween(this, "attr." + nameNS, value, name.local ? attrTweenNS : attrTween); + }; + d3_transitionPrototype.attrTween = function(nameNS, tween) { + var name = d3.ns.qualify(nameNS); + function attrTween(d, i) { + var f = tween.call(this, d, i, this.getAttribute(name)); + return f && function(t) { + this.setAttribute(name, f(t)); + }; + } + function attrTweenNS(d, i) { + var f = tween.call(this, d, i, this.getAttributeNS(name.space, name.local)); + return f && function(t) { + this.setAttributeNS(name.space, name.local, f(t)); + }; + } + return this.tween("attr." + nameNS, name.local ? attrTweenNS : attrTween); + }; + d3_transitionPrototype.style = function(name, value, priority) { + var n = arguments.length; + if (n < 3) { + if (typeof name !== "string") { + if (n < 2) value = ""; + for (priority in name) this.style(priority, name[priority], value); + return this; + } + priority = ""; + } + function styleNull() { + this.style.removeProperty(name); + } + function styleString(b) { + return b == null ? styleNull : (b += "", function() { + var a = d3_window(this).getComputedStyle(this, null).getPropertyValue(name), i; + return a !== b && (i = d3_interpolate(a, b), function(t) { + this.style.setProperty(name, i(t), priority); + }); + }); + } + return d3_transition_tween(this, "style." + name, value, styleString); + }; + d3_transitionPrototype.styleTween = function(name, tween, priority) { + if (arguments.length < 3) priority = ""; + function styleTween(d, i) { + var f = tween.call(this, d, i, d3_window(this).getComputedStyle(this, null).getPropertyValue(name)); + return f && function(t) { + this.style.setProperty(name, f(t), priority); + }; + } + return this.tween("style." + name, styleTween); + }; + d3_transitionPrototype.text = function(value) { + return d3_transition_tween(this, "text", value, d3_transition_text); + }; + function d3_transition_text(b) { + if (b == null) b = ""; + return function() { + this.textContent = b; + }; + } + d3_transitionPrototype.remove = function() { + var ns = this.namespace; + return this.each("end.transition", function() { + var p; + if (this[ns].count < 2 && (p = this.parentNode)) p.removeChild(this); + }); + }; + d3_transitionPrototype.ease = function(value) { + var id = this.id, ns = this.namespace; + if (arguments.length < 1) return this.node()[ns][id].ease; + if (typeof value !== "function") value = d3.ease.apply(d3, arguments); + return d3_selection_each(this, function(node) { + node[ns][id].ease = value; + }); + }; + d3_transitionPrototype.delay = function(value) { + var id = this.id, ns = this.namespace; + if (arguments.length < 1) return this.node()[ns][id].delay; + return d3_selection_each(this, typeof value === "function" ? function(node, i, j) { + node[ns][id].delay = +value.call(node, node.__data__, i, j); + } : (value = +value, function(node) { + node[ns][id].delay = value; + })); + }; + d3_transitionPrototype.duration = function(value) { + var id = this.id, ns = this.namespace; + if (arguments.length < 1) return this.node()[ns][id].duration; + return d3_selection_each(this, typeof value === "function" ? function(node, i, j) { + node[ns][id].duration = Math.max(1, value.call(node, node.__data__, i, j)); + } : (value = Math.max(1, value), function(node) { + node[ns][id].duration = value; + })); + }; + d3_transitionPrototype.each = function(type, listener) { + var id = this.id, ns = this.namespace; + if (arguments.length < 2) { + var inherit = d3_transitionInherit, inheritId = d3_transitionInheritId; + try { + d3_transitionInheritId = id; + d3_selection_each(this, function(node, i, j) { + d3_transitionInherit = node[ns][id]; + type.call(node, node.__data__, i, j); + }); + } finally { + d3_transitionInherit = inherit; + d3_transitionInheritId = inheritId; + } + } else { + d3_selection_each(this, function(node) { + var transition = node[ns][id]; + (transition.event || (transition.event = d3.dispatch("start", "end", "interrupt"))).on(type, listener); + }); + } + return this; + }; + d3_transitionPrototype.transition = function() { + var id0 = this.id, id1 = ++d3_transitionId, ns = this.namespace, subgroups = [], subgroup, group, node, transition; + for (var j = 0, m = this.length; j < m; j++) { + subgroups.push(subgroup = []); + for (var group = this[j], i = 0, n = group.length; i < n; i++) { + if (node = group[i]) { + transition = node[ns][id0]; + d3_transitionNode(node, i, ns, id1, { + time: transition.time, + ease: transition.ease, + delay: transition.delay + transition.duration, + duration: transition.duration + }); + } + subgroup.push(node); + } + } + return d3_transition(subgroups, ns, id1); + }; + function d3_transitionNamespace(name) { + return name == null ? "__transition__" : "__transition_" + name + "__"; + } + function d3_transitionNode(node, i, ns, id, inherit) { + var lock = node[ns] || (node[ns] = { + active: 0, + count: 0 + }), transition = lock[id], time, timer, duration, ease, tweens; + function schedule(elapsed) { + var delay = transition.delay; + timer.t = delay + time; + if (delay <= elapsed) return start(elapsed - delay); + timer.c = start; + } + function start(elapsed) { + var activeId = lock.active, active = lock[activeId]; + if (active) { + active.timer.c = null; + active.timer.t = NaN; + --lock.count; + delete lock[activeId]; + active.event && active.event.interrupt.call(node, node.__data__, active.index); + } + for (var cancelId in lock) { + if (+cancelId < id) { + var cancel = lock[cancelId]; + cancel.timer.c = null; + cancel.timer.t = NaN; + --lock.count; + delete lock[cancelId]; + } + } + timer.c = tick; + d3_timer(function() { + if (timer.c && tick(elapsed || 1)) { + timer.c = null; + timer.t = NaN; + } + return 1; + }, 0, time); + lock.active = id; + transition.event && transition.event.start.call(node, node.__data__, i); + tweens = []; + transition.tween.forEach(function(key, value) { + if (value = value.call(node, node.__data__, i)) { + tweens.push(value); + } + }); + ease = transition.ease; + duration = transition.duration; + } + function tick(elapsed) { + var t = elapsed / duration, e = ease(t), n = tweens.length; + while (n > 0) { + tweens[--n].call(node, e); + } + if (t >= 1) { + transition.event && transition.event.end.call(node, node.__data__, i); + if (--lock.count) delete lock[id]; else delete node[ns]; + return 1; + } + } + if (!transition) { + time = inherit.time; + timer = d3_timer(schedule, 0, time); + transition = lock[id] = { + tween: new d3_Map(), + time: time, + timer: timer, + delay: inherit.delay, + duration: inherit.duration, + ease: inherit.ease, + index: i + }; + inherit = null; + ++lock.count; + } + } + d3.svg.axis = function() { + var scale = d3.scale.linear(), orient = d3_svg_axisDefaultOrient, innerTickSize = 6, outerTickSize = 6, tickPadding = 3, tickArguments_ = [ 10 ], tickValues = null, tickFormat_; + function axis(g) { + g.each(function() { + var g = d3.select(this); + var scale0 = this.__chart__ || scale, scale1 = this.__chart__ = scale.copy(); + var ticks = tickValues == null ? scale1.ticks ? scale1.ticks.apply(scale1, tickArguments_) : scale1.domain() : tickValues, tickFormat = tickFormat_ == null ? scale1.tickFormat ? scale1.tickFormat.apply(scale1, tickArguments_) : d3_identity : tickFormat_, tick = g.selectAll(".tick").data(ticks, scale1), tickEnter = tick.enter().insert("g", ".domain").attr("class", "tick").style("opacity", ε), tickExit = d3.transition(tick.exit()).style("opacity", ε).remove(), tickUpdate = d3.transition(tick.order()).style("opacity", 1), tickSpacing = Math.max(innerTickSize, 0) + tickPadding, tickTransform; + var range = d3_scaleRange(scale1), path = g.selectAll(".domain").data([ 0 ]), pathUpdate = (path.enter().append("path").attr("class", "domain"), + d3.transition(path)); + tickEnter.append("line"); + tickEnter.append("text"); + var lineEnter = tickEnter.select("line"), lineUpdate = tickUpdate.select("line"), text = tick.select("text").text(tickFormat), textEnter = tickEnter.select("text"), textUpdate = tickUpdate.select("text"), sign = orient === "top" || orient === "left" ? -1 : 1, x1, x2, y1, y2; + if (orient === "bottom" || orient === "top") { + tickTransform = d3_svg_axisX, x1 = "x", y1 = "y", x2 = "x2", y2 = "y2"; + text.attr("dy", sign < 0 ? "0em" : ".71em").style("text-anchor", "middle"); + pathUpdate.attr("d", "M" + range[0] + "," + sign * outerTickSize + "V0H" + range[1] + "V" + sign * outerTickSize); + } else { + tickTransform = d3_svg_axisY, x1 = "y", y1 = "x", x2 = "y2", y2 = "x2"; + text.attr("dy", ".32em").style("text-anchor", sign < 0 ? "end" : "start"); + pathUpdate.attr("d", "M" + sign * outerTickSize + "," + range[0] + "H0V" + range[1] + "H" + sign * outerTickSize); + } + lineEnter.attr(y2, sign * innerTickSize); + textEnter.attr(y1, sign * tickSpacing); + lineUpdate.attr(x2, 0).attr(y2, sign * innerTickSize); + textUpdate.attr(x1, 0).attr(y1, sign * tickSpacing); + if (scale1.rangeBand) { + var x = scale1, dx = x.rangeBand() / 2; + scale0 = scale1 = function(d) { + return x(d) + dx; + }; + } else if (scale0.rangeBand) { + scale0 = scale1; + } else { + tickExit.call(tickTransform, scale1, scale0); + } + tickEnter.call(tickTransform, scale0, scale1); + tickUpdate.call(tickTransform, scale1, scale1); + }); + } + axis.scale = function(x) { + if (!arguments.length) return scale; + scale = x; + return axis; + }; + axis.orient = function(x) { + if (!arguments.length) return orient; + orient = x in d3_svg_axisOrients ? x + "" : d3_svg_axisDefaultOrient; + return axis; + }; + axis.ticks = function() { + if (!arguments.length) return tickArguments_; + tickArguments_ = d3_array(arguments); + return axis; + }; + axis.tickValues = function(x) { + if (!arguments.length) return tickValues; + tickValues = x; + return axis; + }; + axis.tickFormat = function(x) { + if (!arguments.length) return tickFormat_; + tickFormat_ = x; + return axis; + }; + axis.tickSize = function(x) { + var n = arguments.length; + if (!n) return innerTickSize; + innerTickSize = +x; + outerTickSize = +arguments[n - 1]; + return axis; + }; + axis.innerTickSize = function(x) { + if (!arguments.length) return innerTickSize; + innerTickSize = +x; + return axis; + }; + axis.outerTickSize = function(x) { + if (!arguments.length) return outerTickSize; + outerTickSize = +x; + return axis; + }; + axis.tickPadding = function(x) { + if (!arguments.length) return tickPadding; + tickPadding = +x; + return axis; + }; + axis.tickSubdivide = function() { + return arguments.length && axis; + }; + return axis; + }; + var d3_svg_axisDefaultOrient = "bottom", d3_svg_axisOrients = { + top: 1, + right: 1, + bottom: 1, + left: 1 + }; + function d3_svg_axisX(selection, x0, x1) { + selection.attr("transform", function(d) { + var v0 = x0(d); + return "translate(" + (isFinite(v0) ? v0 : x1(d)) + ",0)"; + }); + } + function d3_svg_axisY(selection, y0, y1) { + selection.attr("transform", function(d) { + var v0 = y0(d); + return "translate(0," + (isFinite(v0) ? v0 : y1(d)) + ")"; + }); + } + d3.svg.brush = function() { + var event = d3_eventDispatch(brush, "brushstart", "brush", "brushend"), x = null, y = null, xExtent = [ 0, 0 ], yExtent = [ 0, 0 ], xExtentDomain, yExtentDomain, xClamp = true, yClamp = true, resizes = d3_svg_brushResizes[0]; + function brush(g) { + g.each(function() { + var g = d3.select(this).style("pointer-events", "all").style("-webkit-tap-highlight-color", "rgba(0,0,0,0)").on("mousedown.brush", brushstart).on("touchstart.brush", brushstart); + var background = g.selectAll(".background").data([ 0 ]); + background.enter().append("rect").attr("class", "background").style("visibility", "hidden").style("cursor", "crosshair"); + g.selectAll(".extent").data([ 0 ]).enter().append("rect").attr("class", "extent").style("cursor", "move"); + var resize = g.selectAll(".resize").data(resizes, d3_identity); + resize.exit().remove(); + resize.enter().append("g").attr("class", function(d) { + return "resize " + d; + }).style("cursor", function(d) { + return d3_svg_brushCursor[d]; + }).append("rect").attr("x", function(d) { + return /[ew]$/.test(d) ? -3 : null; + }).attr("y", function(d) { + return /^[ns]/.test(d) ? -3 : null; + }).attr("width", 6).attr("height", 6).style("visibility", "hidden"); + resize.style("display", brush.empty() ? "none" : null); + var gUpdate = d3.transition(g), backgroundUpdate = d3.transition(background), range; + if (x) { + range = d3_scaleRange(x); + backgroundUpdate.attr("x", range[0]).attr("width", range[1] - range[0]); + redrawX(gUpdate); + } + if (y) { + range = d3_scaleRange(y); + backgroundUpdate.attr("y", range[0]).attr("height", range[1] - range[0]); + redrawY(gUpdate); + } + redraw(gUpdate); + }); + } + brush.event = function(g) { + g.each(function() { + var event_ = event.of(this, arguments), extent1 = { + x: xExtent, + y: yExtent, + i: xExtentDomain, + j: yExtentDomain + }, extent0 = this.__chart__ || extent1; + this.__chart__ = extent1; + if (d3_transitionInheritId) { + d3.select(this).transition().each("start.brush", function() { + xExtentDomain = extent0.i; + yExtentDomain = extent0.j; + xExtent = extent0.x; + yExtent = extent0.y; + event_({ + type: "brushstart" + }); + }).tween("brush:brush", function() { + var xi = d3_interpolateArray(xExtent, extent1.x), yi = d3_interpolateArray(yExtent, extent1.y); + xExtentDomain = yExtentDomain = null; + return function(t) { + xExtent = extent1.x = xi(t); + yExtent = extent1.y = yi(t); + event_({ + type: "brush", + mode: "resize" + }); + }; + }).each("end.brush", function() { + xExtentDomain = extent1.i; + yExtentDomain = extent1.j; + event_({ + type: "brush", + mode: "resize" + }); + event_({ + type: "brushend" + }); + }); + } else { + event_({ + type: "brushstart" + }); + event_({ + type: "brush", + mode: "resize" + }); + event_({ + type: "brushend" + }); + } + }); + }; + function redraw(g) { + g.selectAll(".resize").attr("transform", function(d) { + return "translate(" + xExtent[+/e$/.test(d)] + "," + yExtent[+/^s/.test(d)] + ")"; + }); + } + function redrawX(g) { + g.select(".extent").attr("x", xExtent[0]); + g.selectAll(".extent,.n>rect,.s>rect").attr("width", xExtent[1] - xExtent[0]); + } + function redrawY(g) { + g.select(".extent").attr("y", yExtent[0]); + g.selectAll(".extent,.e>rect,.w>rect").attr("height", yExtent[1] - yExtent[0]); + } + function brushstart() { + var target = this, eventTarget = d3.select(d3.event.target), event_ = event.of(target, arguments), g = d3.select(target), resizing = eventTarget.datum(), resizingX = !/^(n|s)$/.test(resizing) && x, resizingY = !/^(e|w)$/.test(resizing) && y, dragging = eventTarget.classed("extent"), dragRestore = d3_event_dragSuppress(target), center, origin = d3.mouse(target), offset; + var w = d3.select(d3_window(target)).on("keydown.brush", keydown).on("keyup.brush", keyup); + if (d3.event.changedTouches) { + w.on("touchmove.brush", brushmove).on("touchend.brush", brushend); + } else { + w.on("mousemove.brush", brushmove).on("mouseup.brush", brushend); + } + g.interrupt().selectAll("*").interrupt(); + if (dragging) { + origin[0] = xExtent[0] - origin[0]; + origin[1] = yExtent[0] - origin[1]; + } else if (resizing) { + var ex = +/w$/.test(resizing), ey = +/^n/.test(resizing); + offset = [ xExtent[1 - ex] - origin[0], yExtent[1 - ey] - origin[1] ]; + origin[0] = xExtent[ex]; + origin[1] = yExtent[ey]; + } else if (d3.event.altKey) center = origin.slice(); + g.style("pointer-events", "none").selectAll(".resize").style("display", null); + d3.select("body").style("cursor", eventTarget.style("cursor")); + event_({ + type: "brushstart" + }); + brushmove(); + function keydown() { + if (d3.event.keyCode == 32) { + if (!dragging) { + center = null; + origin[0] -= xExtent[1]; + origin[1] -= yExtent[1]; + dragging = 2; + } + d3_eventPreventDefault(); + } + } + function keyup() { + if (d3.event.keyCode == 32 && dragging == 2) { + origin[0] += xExtent[1]; + origin[1] += yExtent[1]; + dragging = 0; + d3_eventPreventDefault(); + } + } + function brushmove() { + var point = d3.mouse(target), moved = false; + if (offset) { + point[0] += offset[0]; + point[1] += offset[1]; + } + if (!dragging) { + if (d3.event.altKey) { + if (!center) center = [ (xExtent[0] + xExtent[1]) / 2, (yExtent[0] + yExtent[1]) / 2 ]; + origin[0] = xExtent[+(point[0] < center[0])]; + origin[1] = yExtent[+(point[1] < center[1])]; + } else center = null; + } + if (resizingX && move1(point, x, 0)) { + redrawX(g); + moved = true; + } + if (resizingY && move1(point, y, 1)) { + redrawY(g); + moved = true; + } + if (moved) { + redraw(g); + event_({ + type: "brush", + mode: dragging ? "move" : "resize" + }); + } + } + function move1(point, scale, i) { + var range = d3_scaleRange(scale), r0 = range[0], r1 = range[1], position = origin[i], extent = i ? yExtent : xExtent, size = extent[1] - extent[0], min, max; + if (dragging) { + r0 -= position; + r1 -= size + position; + } + min = (i ? yClamp : xClamp) ? Math.max(r0, Math.min(r1, point[i])) : point[i]; + if (dragging) { + max = (min += position) + size; + } else { + if (center) position = Math.max(r0, Math.min(r1, 2 * center[i] - min)); + if (position < min) { + max = min; + min = position; + } else { + max = position; + } + } + if (extent[0] != min || extent[1] != max) { + if (i) yExtentDomain = null; else xExtentDomain = null; + extent[0] = min; + extent[1] = max; + return true; + } + } + function brushend() { + brushmove(); + g.style("pointer-events", "all").selectAll(".resize").style("display", brush.empty() ? "none" : null); + d3.select("body").style("cursor", null); + w.on("mousemove.brush", null).on("mouseup.brush", null).on("touchmove.brush", null).on("touchend.brush", null).on("keydown.brush", null).on("keyup.brush", null); + dragRestore(); + event_({ + type: "brushend" + }); + } + } + brush.x = function(z) { + if (!arguments.length) return x; + x = z; + resizes = d3_svg_brushResizes[!x << 1 | !y]; + return brush; + }; + brush.y = function(z) { + if (!arguments.length) return y; + y = z; + resizes = d3_svg_brushResizes[!x << 1 | !y]; + return brush; + }; + brush.clamp = function(z) { + if (!arguments.length) return x && y ? [ xClamp, yClamp ] : x ? xClamp : y ? yClamp : null; + if (x && y) xClamp = !!z[0], yClamp = !!z[1]; else if (x) xClamp = !!z; else if (y) yClamp = !!z; + return brush; + }; + brush.extent = function(z) { + var x0, x1, y0, y1, t; + if (!arguments.length) { + if (x) { + if (xExtentDomain) { + x0 = xExtentDomain[0], x1 = xExtentDomain[1]; + } else { + x0 = xExtent[0], x1 = xExtent[1]; + if (x.invert) x0 = x.invert(x0), x1 = x.invert(x1); + if (x1 < x0) t = x0, x0 = x1, x1 = t; + } + } + if (y) { + if (yExtentDomain) { + y0 = yExtentDomain[0], y1 = yExtentDomain[1]; + } else { + y0 = yExtent[0], y1 = yExtent[1]; + if (y.invert) y0 = y.invert(y0), y1 = y.invert(y1); + if (y1 < y0) t = y0, y0 = y1, y1 = t; + } + } + return x && y ? [ [ x0, y0 ], [ x1, y1 ] ] : x ? [ x0, x1 ] : y && [ y0, y1 ]; + } + if (x) { + x0 = z[0], x1 = z[1]; + if (y) x0 = x0[0], x1 = x1[0]; + xExtentDomain = [ x0, x1 ]; + if (x.invert) x0 = x(x0), x1 = x(x1); + if (x1 < x0) t = x0, x0 = x1, x1 = t; + if (x0 != xExtent[0] || x1 != xExtent[1]) xExtent = [ x0, x1 ]; + } + if (y) { + y0 = z[0], y1 = z[1]; + if (x) y0 = y0[1], y1 = y1[1]; + yExtentDomain = [ y0, y1 ]; + if (y.invert) y0 = y(y0), y1 = y(y1); + if (y1 < y0) t = y0, y0 = y1, y1 = t; + if (y0 != yExtent[0] || y1 != yExtent[1]) yExtent = [ y0, y1 ]; + } + return brush; + }; + brush.clear = function() { + if (!brush.empty()) { + xExtent = [ 0, 0 ], yExtent = [ 0, 0 ]; + xExtentDomain = yExtentDomain = null; + } + return brush; + }; + brush.empty = function() { + return !!x && xExtent[0] == xExtent[1] || !!y && yExtent[0] == yExtent[1]; + }; + return d3.rebind(brush, event, "on"); + }; + var d3_svg_brushCursor = { + n: "ns-resize", + e: "ew-resize", + s: "ns-resize", + w: "ew-resize", + nw: "nwse-resize", + ne: "nesw-resize", + se: "nwse-resize", + sw: "nesw-resize" + }; + var d3_svg_brushResizes = [ [ "n", "e", "s", "w", "nw", "ne", "se", "sw" ], [ "e", "w" ], [ "n", "s" ], [] ]; + var d3_time_format = d3_time.format = d3_locale_enUS.timeFormat; + var d3_time_formatUtc = d3_time_format.utc; + var d3_time_formatIso = d3_time_formatUtc("%Y-%m-%dT%H:%M:%S.%LZ"); + d3_time_format.iso = Date.prototype.toISOString && +new Date("2000-01-01T00:00:00.000Z") ? d3_time_formatIsoNative : d3_time_formatIso; + function d3_time_formatIsoNative(date) { + return date.toISOString(); + } + d3_time_formatIsoNative.parse = function(string) { + var date = new Date(string); + return isNaN(date) ? null : date; + }; + d3_time_formatIsoNative.toString = d3_time_formatIso.toString; + d3_time.second = d3_time_interval(function(date) { + return new d3_date(Math.floor(date / 1e3) * 1e3); + }, function(date, offset) { + date.setTime(date.getTime() + Math.floor(offset) * 1e3); + }, function(date) { + return date.getSeconds(); + }); + d3_time.seconds = d3_time.second.range; + d3_time.seconds.utc = d3_time.second.utc.range; + d3_time.minute = d3_time_interval(function(date) { + return new d3_date(Math.floor(date / 6e4) * 6e4); + }, function(date, offset) { + date.setTime(date.getTime() + Math.floor(offset) * 6e4); + }, function(date) { + return date.getMinutes(); + }); + d3_time.minutes = d3_time.minute.range; + d3_time.minutes.utc = d3_time.minute.utc.range; + d3_time.hour = d3_time_interval(function(date) { + var timezone = date.getTimezoneOffset() / 60; + return new d3_date((Math.floor(date / 36e5 - timezone) + timezone) * 36e5); + }, function(date, offset) { + date.setTime(date.getTime() + Math.floor(offset) * 36e5); + }, function(date) { + return date.getHours(); + }); + d3_time.hours = d3_time.hour.range; + d3_time.hours.utc = d3_time.hour.utc.range; + d3_time.month = d3_time_interval(function(date) { + date = d3_time.day(date); + date.setDate(1); + return date; + }, function(date, offset) { + date.setMonth(date.getMonth() + offset); + }, function(date) { + return date.getMonth(); + }); + d3_time.months = d3_time.month.range; + d3_time.months.utc = d3_time.month.utc.range; + function d3_time_scale(linear, methods, format) { + function scale(x) { + return linear(x); + } + scale.invert = function(x) { + return d3_time_scaleDate(linear.invert(x)); + }; + scale.domain = function(x) { + if (!arguments.length) return linear.domain().map(d3_time_scaleDate); + linear.domain(x); + return scale; + }; + function tickMethod(extent, count) { + var span = extent[1] - extent[0], target = span / count, i = d3.bisect(d3_time_scaleSteps, target); + return i == d3_time_scaleSteps.length ? [ methods.year, d3_scale_linearTickRange(extent.map(function(d) { + return d / 31536e6; + }), count)[2] ] : !i ? [ d3_time_scaleMilliseconds, d3_scale_linearTickRange(extent, count)[2] ] : methods[target / d3_time_scaleSteps[i - 1] < d3_time_scaleSteps[i] / target ? i - 1 : i]; + } + scale.nice = function(interval, skip) { + var domain = scale.domain(), extent = d3_scaleExtent(domain), method = interval == null ? tickMethod(extent, 10) : typeof interval === "number" && tickMethod(extent, interval); + if (method) interval = method[0], skip = method[1]; + function skipped(date) { + return !isNaN(date) && !interval.range(date, d3_time_scaleDate(+date + 1), skip).length; + } + return scale.domain(d3_scale_nice(domain, skip > 1 ? { + floor: function(date) { + while (skipped(date = interval.floor(date))) date = d3_time_scaleDate(date - 1); + return date; + }, + ceil: function(date) { + while (skipped(date = interval.ceil(date))) date = d3_time_scaleDate(+date + 1); + return date; + } + } : interval)); + }; + scale.ticks = function(interval, skip) { + var extent = d3_scaleExtent(scale.domain()), method = interval == null ? tickMethod(extent, 10) : typeof interval === "number" ? tickMethod(extent, interval) : !interval.range && [ { + range: interval + }, skip ]; + if (method) interval = method[0], skip = method[1]; + return interval.range(extent[0], d3_time_scaleDate(+extent[1] + 1), skip < 1 ? 1 : skip); + }; + scale.tickFormat = function() { + return format; + }; + scale.copy = function() { + return d3_time_scale(linear.copy(), methods, format); + }; + return d3_scale_linearRebind(scale, linear); + } + function d3_time_scaleDate(t) { + return new Date(t); + } + var d3_time_scaleSteps = [ 1e3, 5e3, 15e3, 3e4, 6e4, 3e5, 9e5, 18e5, 36e5, 108e5, 216e5, 432e5, 864e5, 1728e5, 6048e5, 2592e6, 7776e6, 31536e6 ]; + var d3_time_scaleLocalMethods = [ [ d3_time.second, 1 ], [ d3_time.second, 5 ], [ d3_time.second, 15 ], [ d3_time.second, 30 ], [ d3_time.minute, 1 ], [ d3_time.minute, 5 ], [ d3_time.minute, 15 ], [ d3_time.minute, 30 ], [ d3_time.hour, 1 ], [ d3_time.hour, 3 ], [ d3_time.hour, 6 ], [ d3_time.hour, 12 ], [ d3_time.day, 1 ], [ d3_time.day, 2 ], [ d3_time.week, 1 ], [ d3_time.month, 1 ], [ d3_time.month, 3 ], [ d3_time.year, 1 ] ]; + var d3_time_scaleLocalFormat = d3_time_format.multi([ [ ".%L", function(d) { + return d.getMilliseconds(); + } ], [ ":%S", function(d) { + return d.getSeconds(); + } ], [ "%I:%M", function(d) { + return d.getMinutes(); + } ], [ "%I %p", function(d) { + return d.getHours(); + } ], [ "%a %d", function(d) { + return d.getDay() && d.getDate() != 1; + } ], [ "%b %d", function(d) { + return d.getDate() != 1; + } ], [ "%B", function(d) { + return d.getMonth(); + } ], [ "%Y", d3_true ] ]); + var d3_time_scaleMilliseconds = { + range: function(start, stop, step) { + return d3.range(Math.ceil(start / step) * step, +stop, step).map(d3_time_scaleDate); + }, + floor: d3_identity, + ceil: d3_identity + }; + d3_time_scaleLocalMethods.year = d3_time.year; + d3_time.scale = function() { + return d3_time_scale(d3.scale.linear(), d3_time_scaleLocalMethods, d3_time_scaleLocalFormat); + }; + var d3_time_scaleUtcMethods = d3_time_scaleLocalMethods.map(function(m) { + return [ m[0].utc, m[1] ]; + }); + var d3_time_scaleUtcFormat = d3_time_formatUtc.multi([ [ ".%L", function(d) { + return d.getUTCMilliseconds(); + } ], [ ":%S", function(d) { + return d.getUTCSeconds(); + } ], [ "%I:%M", function(d) { + return d.getUTCMinutes(); + } ], [ "%I %p", function(d) { + return d.getUTCHours(); + } ], [ "%a %d", function(d) { + return d.getUTCDay() && d.getUTCDate() != 1; + } ], [ "%b %d", function(d) { + return d.getUTCDate() != 1; + } ], [ "%B", function(d) { + return d.getUTCMonth(); + } ], [ "%Y", d3_true ] ]); + d3_time_scaleUtcMethods.year = d3_time.year.utc; + d3_time.scale.utc = function() { + return d3_time_scale(d3.scale.linear(), d3_time_scaleUtcMethods, d3_time_scaleUtcFormat); + }; + d3.text = d3_xhrType(function(request) { + return request.responseText; + }); + d3.json = function(url, callback) { + return d3_xhr(url, "application/json", d3_json, callback); + }; + function d3_json(request) { + return JSON.parse(request.responseText); + } + d3.html = function(url, callback) { + return d3_xhr(url, "text/html", d3_html, callback); + }; + function d3_html(request) { + var range = d3_document.createRange(); + range.selectNode(d3_document.body); + return range.createContextualFragment(request.responseText); + } + d3.xml = d3_xhrType(function(request) { + return request.responseXML; + }); + if (typeof define === "function" && define.amd) this.d3 = d3, define(d3); else if (typeof module === "object" && module.exports) module.exports = d3; else this.d3 = d3; +}(); diff --git a/graph.js b/graph.js new file mode 100644 index 0000000000..ad8279395d --- /dev/null +++ b/graph.js @@ -0,0 +1,129 @@ +// GNU Guix --- Functional package management for GNU +// Copyright © 2016 Ricardo Wurmus +// +// 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 . + +var outerRadius = Math.max(nodeArray.length * 15, 500) / 2, + innerRadius = outerRadius - Math.min(nodeArray.length * 5, 200), + width = outerRadius * 2, + height = outerRadius * 2, + colors = d3.scale.category20c(), + matrix = []; + +function neighborsOf (node) { + return links.filter(function (e) { + return e.source === node; + }).map(function (e) { + return e.target; + }); +} + +function zoomed () { + zoomer.attr("transform", + "translate(" + d3.event.translate + ")" + + "scale(" + d3.event.scale + ")"); +} + +function fade (opacity, root) { + return function (g, i) { + root.selectAll("g path.chord") + .filter(function (d) { + return d.source.index != i && d.target.index != i; + }) + .transition() + .style("opacity", opacity); + }; +} + +// Now that we have all nodes in an object we can replace each reference +// with the actual node object. +links.forEach(function (link) { + link.target = nodes[link.target]; + link.source = nodes[link.source]; +}); + +// Construct a square matrix for package dependencies +nodeArray.forEach(function (d, index, arr) { + var source = index, + row = matrix[source]; + if (!row) { + row = matrix[source] = []; + for (var i = -1; ++i < arr.length;) row[i] = 0; + } + neighborsOf(d).forEach(function (d) { row[d.index]++; }); +}); + +// chord layout +var chord = d3.layout.chord() + .padding(0.01) + .sortSubgroups(d3.descending) + .sortChords(d3.descending) + .matrix(matrix); + +var arc = d3.svg.arc() + .innerRadius(innerRadius) + .outerRadius(innerRadius + 20); + +var zoom = d3.behavior.zoom() + .scaleExtent([0.1, 10]) + .on("zoom", zoomed); + +var svg = d3.select("body").append("svg") + .attr("width", "100%") + .attr("height", "100%") + .attr('viewBox', '0 0 ' + Math.min(width, height) + ' ' + Math.min(width, height)) + .attr('preserveAspectRatio', 'xMinYMin') + .call(zoom); + +var zoomer = svg.append("g"); + +var container = zoomer.append("g") + .attr("transform", "translate(" + outerRadius + "," + outerRadius + ")"); + +// Group for arcs and labels +var g = container.selectAll(".group") + .data(chord.groups) + .enter().append("g") + .attr("class", "group") + .on("mouseout", fade(1, container)) + .on("mouseover", fade(0.1, container)); + +// Draw one segment per package +g.append("path") + .style("fill", function (d) { return colors(d.index); }) + .style("stroke", function (d) { return colors(d.index); }) + .attr("d", arc); + +// Add circular labels +g.append("text") + .each(function (d) { d.angle = (d.startAngle + d.endAngle) / 2; }) + .attr("dy", ".35em") + .attr("transform", function (d) { + return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")" + + "translate(" + (innerRadius + 26) + ")" + + (d.angle > Math.PI ? "rotate(180)" : ""); + }) + .style("text-anchor", function (d) { return d.angle > Math.PI ? "end" : null; }) + .text(function (d) { return nodeArray[d.index].label; }); + +// Draw chords from source to target; color by source. +container.selectAll(".chord") + .data(chord.chords) + .enter().append("path") + .attr("class", "chord") + .style("stroke", function (d) { return d3.rgb(colors(d.source.index)).darker(); }) + .style("fill", function (d) { return colors(d.source.index); }) + .attr("d", d3.svg.chord().radius(innerRadius)); diff --git a/guix/graph.scm b/guix/graph.scm index d2c1fa6c48..7af2cd3b80 100644 --- a/guix/graph.scm +++ b/guix/graph.scm @@ -22,6 +22,7 @@ #:use-module (guix monads) #:use-module (guix records) #:use-module (guix sets) + #:use-module (rnrs io ports) #:use-module (srfi srfi-1) #:use-module (srfi srfi-9) #:use-module (srfi srfi-26) @@ -43,6 +44,7 @@ node-reachable-count %graph-backends + %d3js-backend %graphviz-backend graph-backend? graph-backend @@ -181,13 +183,60 @@ typically returned by 'node-edges' or 'node-back-edges'." emit-prologue emit-epilogue emit-node emit-edge)) + +;;; +;;; d3js export. +;;; + +(define (emit-d3js-prologue name port) + (format port "\ + + + + + + + + + " + (search-path %load-path "graph.js"))) + +(define (emit-d3js-node id label port) + (format port "\ +nodes[\"~a\"] = {\"id\": \"~a\", \"label\": \"~a\", \"index\": nodeArray.length}; +nodeArray.push(nodes[\"~a\"]);~%" + id id label id)) + +(define (emit-d3js-edge id1 id2 port) + (format port "links.push({\"source\": \"~a\", \"target\": \"~a\"});~%" + id1 id2)) + +(define %d3js-backend + (graph-backend "d3js" + "Generate chord diagrams with d3js." + emit-d3js-prologue emit-d3js-epilogue + emit-d3js-node emit-d3js-edge)) + ;;; ;;; Shared. ;;; (define %graph-backends - (list %graphviz-backend)) + (list %graphviz-backend + %d3js-backend)) (define* (export-graph sinks port #:key -- cgit v1.2.3 From 6976c68158e9c6bb264ca48041da5be1c24521cf Mon Sep 17 00:00:00 2001 From: David Craven Date: Thu, 15 Dec 2016 05:53:32 +0100 Subject: upstream: Determine archive type correctly. * guix/upstream.scm (package-update): Take the basename of the uri before trying to determine the extension. --- guix/upstream.scm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'guix') diff --git a/guix/upstream.scm b/guix/upstream.scm index 22187311ea..2334c4c0a6 100644 --- a/guix/upstream.scm +++ b/guix/upstream.scm @@ -241,7 +241,7 @@ and 'interactive' (default)." ((archive-type) (match (and=> (package-source package) origin-uri) ((? string? uri) - (file-extension uri)) + (file-extension (basename uri))) (_ "gz"))) ((url signature-url) -- cgit v1.2.3 From 99ec0cb397453802788e7095724dcfbca3ca1723 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Fri, 16 Dec 2016 17:38:39 +0100 Subject: refresh: Make dependency on (guix import crates) weak. Reported by Tobias Geerinckx-Rice . * guix/scripts/refresh.scm: Do not explicitly import (guix import crates). --- guix/scripts/refresh.scm | 1 - 1 file changed, 1 deletion(-) (limited to 'guix') diff --git a/guix/scripts/refresh.scm b/guix/scripts/refresh.scm index 2a06405a14..0dd7eee974 100644 --- a/guix/scripts/refresh.scm +++ b/guix/scripts/refresh.scm @@ -39,7 +39,6 @@ %kernel.org-updater)) #:use-module (guix import elpa) #:use-module (guix import cran) - #:use-module (guix import crate) #:use-module (guix import hackage) #:use-module (guix gnupg) #:use-module (gnu packages) -- cgit v1.2.3 From 580deec5b44d623e994e59ef07e9e0c5496762fd Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Fri, 16 Dec 2016 18:00:01 +0100 Subject: download: Protect against dangling symlinks in $SSL_CERT_DIR. Reported by Christopher Baines in . * guix/build/download.scm (make-credendials-with-ca-trust-files): Check whether FILE exists before calling 'set-certificate-credentials-x509-trust-file!'. --- guix/build/download.scm | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'guix') diff --git a/guix/build/download.scm b/guix/build/download.scm index 8e32b3d7ff..203338b527 100644 --- a/guix/build/download.scm +++ b/guix/build/download.scm @@ -289,9 +289,12 @@ DIRECTORY. Those authority certificates are checked when (string-suffix? ".pem" file))) '()))) (for-each (lambda (file) - (set-certificate-credentials-x509-trust-file! - cred (string-append directory "/" file) - x509-certificate-format/pem)) + (let ((file (string-append directory "/" file))) + ;; Protect against dangling symlinks. + (when (file-exists? file) + (set-certificate-credentials-x509-trust-file! + cred file + x509-certificate-format/pem)))) (or files '())) cred)) -- cgit v1.2.3 From 1af0860e8be81c01ad405c1226d6bc4516e62863 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Fri, 16 Dec 2016 18:01:08 +0100 Subject: profiles: Build profiles in a UTF-8 locale. Fixes . Reported by Christopher Baines . * guix/profiles.scm (profile-derivation)[glibc-utf8-locales]: New variable. [builder]: Use it to set 'LOCPATH'. --- guix/profiles.scm | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'guix') diff --git a/guix/profiles.scm b/guix/profiles.scm index 0b317ef51e..82d8b33c09 100644 --- a/guix/profiles.scm +++ b/guix/profiles.scm @@ -939,6 +939,10 @@ the monadic procedures listed in HOOKS--such as an Info 'dir' file, etc." extras) (manifest-inputs manifest))) + (define glibc-utf8-locales ;lazy reference + (module-ref (resolve-interface '(gnu packages base)) + 'glibc-utf8-locales)) + (define builder (with-imported-modules '((guix build profiles) (guix build union) @@ -953,6 +957,13 @@ the monadic procedures listed in HOOKS--such as an Info 'dir' file, etc." (setvbuf (current-output-port) _IOLBF) (setvbuf (current-error-port) _IOLBF) + ;; Some file names (e.g., in 'nss-certs') are UTF-8 encoded so + ;; install a UTF-8 locale. + (setenv "LOCPATH" + (string-append #+glibc-utf8-locales "/lib/locale/" + #+(package-version glibc-utf8-locales))) + (setlocale LC_ALL "en_US.utf8") + (define search-paths ;; Search paths of MANIFEST's packages, converted back to their ;; record form. -- cgit v1.2.3 From a6562c7e206650ff3d8e6764e60c59a768414bf7 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Sat, 17 Dec 2016 12:43:10 +0100 Subject: profiles: Remove dependency on 'glibc-utf8-locales' for tests. Commit 1af0860e8be81c01ad405c1226d6bc4516e62863 added a mandatory dependency on 'glibc-utf8-locales', which entails long rebuilds for tests. * guix/profiles.scm (profile-derivation): Add #:locales? parameter. Add 'set-utf8-locale' variable. Use it when LOCALES? is true. (link-to-empty-profile): Pass #:locales? #f. * guix/scripts/environment.scm (inputs->profile-derivation): Pass #:locales?. * guix/scripts/package.scm (build-and-use-profile): Likewise. * tests/packages.scm ("--search-paths with pattern"): Pass #:locales? #f. * tests/profiles.scm ("profile-derivation") ("profile-derivation, inputs", "profile-manifest, search-paths") ("etc/profile", "etc/profile when etc/ already exists"): ("etc/profile when etc/ is a symlink"): Likewise. --- guix/profiles.scm | 25 +++++++++++++++++-------- guix/scripts/environment.scm | 3 ++- guix/scripts/package.scm | 3 ++- tests/packages.scm | 3 ++- tests/profiles.scm | 18 ++++++++++++------ 5 files changed, 35 insertions(+), 17 deletions(-) (limited to 'guix') diff --git a/guix/profiles.scm b/guix/profiles.scm index 82d8b33c09..e7707b6543 100644 --- a/guix/profiles.scm +++ b/guix/profiles.scm @@ -919,10 +919,14 @@ files for the truetype fonts of the @var{manifest} entries." (define* (profile-derivation manifest #:key (hooks %default-profile-hooks) + (locales? #t) system) "Return a derivation that builds a profile (aka. 'user environment') with the given MANIFEST. The profile includes additional derivations returned by -the monadic procedures listed in HOOKS--such as an Info 'dir' file, etc." +the monadic procedures listed in HOOKS--such as an Info 'dir' file, etc. + +When LOCALES? is true, the build is performed under a UTF-8 locale; this adds +a dependency on the 'glibc-utf8-locales' package." (mlet %store-monad ((system (if system (return system) (current-system))) @@ -943,6 +947,15 @@ the monadic procedures listed in HOOKS--such as an Info 'dir' file, etc." (module-ref (resolve-interface '(gnu packages base)) 'glibc-utf8-locales)) + (define set-utf8-locale + ;; Some file names (e.g., in 'nss-certs') are UTF-8 encoded so + ;; install a UTF-8 locale. + #~(begin + (setenv "LOCPATH" + #$(file-append glibc-utf8-locales "/lib/locale/" + (package-version glibc-utf8-locales))) + (setlocale LC_ALL "en_US.utf8"))) + (define builder (with-imported-modules '((guix build profiles) (guix build union) @@ -957,12 +970,7 @@ the monadic procedures listed in HOOKS--such as an Info 'dir' file, etc." (setvbuf (current-output-port) _IOLBF) (setvbuf (current-error-port) _IOLBF) - ;; Some file names (e.g., in 'nss-certs') are UTF-8 encoded so - ;; install a UTF-8 locale. - (setenv "LOCPATH" - (string-append #+glibc-utf8-locales "/lib/locale/" - #+(package-version glibc-utf8-locales))) - (setlocale LC_ALL "en_US.utf8") + #+(if locales? set-utf8-locale #t) (define search-paths ;; Search paths of MANIFEST's packages, converted back to their @@ -1110,7 +1118,8 @@ case when generations have been deleted (there are \"holes\")." "Link GENERATION, a string, to the empty profile. An error is raised if that fails." (let* ((drv (run-with-store store - (profile-derivation (manifest '())))) + (profile-derivation (manifest '()) + #:locales? #f))) (prof (derivation->output-path drv "out"))) (build-derivations store (list drv)) (switch-symlinks generation prof))) diff --git a/guix/scripts/environment.scm b/guix/scripts/environment.scm index 6dea67ca22..7201d98fea 100644 --- a/guix/scripts/environment.scm +++ b/guix/scripts/environment.scm @@ -323,7 +323,8 @@ profile." #:system system #:hooks (if bootstrap? '() - %default-profile-hooks))) + %default-profile-hooks) + #:locales? (not bootstrap?))) (define requisites* (store-lift requisites)) diff --git a/guix/scripts/package.scm b/guix/scripts/package.scm index 96a22f6fab..90e7fa2298 100644 --- a/guix/scripts/package.scm +++ b/guix/scripts/package.scm @@ -200,7 +200,8 @@ specified in MANIFEST, a manifest object." (profile-derivation manifest #:hooks (if bootstrap? '() - %default-profile-hooks)))) + %default-profile-hooks) + #:locales? (not bootstrap?)))) (prof (derivation->output-path prof-drv))) (show-what-to-build store (list prof-drv) #:use-substitutes? use-substitutes? diff --git a/tests/packages.scm b/tests/packages.scm index 47e76b53e9..247f75cc43 100644 --- a/tests/packages.scm +++ b/tests/packages.scm @@ -968,7 +968,8 @@ (profile-derivation (manifest (map package->manifest-entry (list p1 p2))) - #:hooks '()) + #:hooks '() + #:locales? #f) #:guile-for-build (%guile-for-build)))) (build-derivations %store (list prof)) (string-match (format #f "^export XML_CATALOG_FILES=\"~a/xml/+bar/baz/catalog\\.xml\"\n" diff --git a/tests/profiles.scm b/tests/profiles.scm index f9c2f5499e..5536364889 100644 --- a/tests/profiles.scm +++ b/tests/profiles.scm @@ -195,7 +195,8 @@ ((entry -> (package->manifest-entry %bootstrap-guile)) (guile (package->derivation %bootstrap-guile)) (drv (profile-derivation (manifest (list entry)) - #:hooks '())) + #:hooks '() + #:locales? #f)) (profile -> (derivation->output-path drv)) (bindir -> (string-append profile "/bin")) (_ (built-derivations (list drv)))) @@ -207,7 +208,8 @@ (mlet* %store-monad ((entry -> (package->manifest-entry packages:glibc "debug")) (drv (profile-derivation (manifest (list entry)) - #:hooks '()))) + #:hooks '() + #:locales? #f))) (return (derivation-inputs drv)))) (test-assert "package->manifest-entry defaults to \"out\"" @@ -228,7 +230,8 @@ (package-native-search-paths packages:guile-2.0)))) (entry -> (package->manifest-entry guile)) (drv (profile-derivation (manifest (list entry)) - #:hooks '())) + #:hooks '() + #:locales? #f)) (profile -> (derivation->output-path drv))) (mbegin %store-monad (built-derivations (list drv)) @@ -259,7 +262,8 @@ (package-native-search-paths packages:guile-2.0)))) (entry -> (package->manifest-entry guile)) (drv (profile-derivation (manifest (list entry)) - #:hooks '())) + #:hooks '() + #:locales? #f)) (profile -> (derivation->output-path drv))) (mbegin %store-monad (built-derivations (list drv)) @@ -293,7 +297,8 @@ (display "foo!" port)))))))) (entry -> (package->manifest-entry thing)) (drv (profile-derivation (manifest (list entry)) - #:hooks '())) + #:hooks '() + #:locales? #f)) (profile -> (derivation->output-path drv))) (mbegin %store-monad (built-derivations (list drv)) @@ -321,7 +326,8 @@ (display "foo!" port)))))))) (entry -> (package->manifest-entry thing)) (drv (profile-derivation (manifest (list entry)) - #:hooks '())) + #:hooks '() + #:locales? #f)) (profile -> (derivation->output-path drv))) (mbegin %store-monad (built-derivations (list drv)) -- cgit v1.2.3 From 13f54d081b6922c65f7cb369ad24071e78ebba54 Mon Sep 17 00:00:00 2001 From: Ricardo Wurmus Date: Thu, 8 Dec 2016 14:35:20 +0100 Subject: import cran: Do not use "or later" licenses by default. * guix/import/cran.scm (string->license): Use exact license versions. --- guix/import/cran.scm | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'guix') diff --git a/guix/import/cran.scm b/guix/import/cran.scm index 3fb2e213b0..13f5f82a45 100644 --- a/guix/import/cran.scm +++ b/guix/import/cran.scm @@ -55,11 +55,11 @@ ("GPL" (list 'gpl2+ 'gpl3+)) ("GPL (>= 2)" 'gpl2+) ("GPL (>= 3)" 'gpl3+) - ("GPL-2" 'gpl2+) - ("GPL-3" 'gpl3+) - ("LGPL-2" 'lgpl2.0+) - ("LGPL-2.1" 'lgpl2.1+) - ("LGPL-3" 'lgpl3+) + ("GPL-2" 'gpl2) + ("GPL-3" 'gpl3) + ("LGPL-2" 'lgpl2.0) + ("LGPL-2.1" 'lgpl2.1) + ("LGPL-3" 'lgpl3) ("LGPL (>= 2)" 'lgpl2.0+) ("LGPL (>= 3)" 'lgpl3+) ("MIT" 'x11) -- cgit v1.2.3 From 741d68c20d1910252f34441c3df65a6f35771673 Mon Sep 17 00:00:00 2001 From: Ricardo Wurmus Date: Thu, 8 Dec 2016 14:36:27 +0100 Subject: import cran: Translate MIT to the Expat license. * guix/import/cran.scm (string->license): Translate "MIT" license to Expat license. --- guix/import/cran.scm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'guix') diff --git a/guix/import/cran.scm b/guix/import/cran.scm index 13f5f82a45..549b11c392 100644 --- a/guix/import/cran.scm +++ b/guix/import/cran.scm @@ -62,8 +62,8 @@ ("LGPL-3" 'lgpl3) ("LGPL (>= 2)" 'lgpl2.0+) ("LGPL (>= 3)" 'lgpl3+) - ("MIT" 'x11) - ("MIT + file LICENSE" 'x11) + ("MIT" 'expat) + ("MIT + file LICENSE" 'expat) ((x) (string->license x)) ((lst ...) `(list ,@(map string->license lst))) (_ #f))) -- cgit v1.2.3 From 576eda6d02c65cbbbd611691abb5f5ee9511afc6 Mon Sep 17 00:00:00 2001 From: Ricardo Wurmus Date: Thu, 8 Dec 2016 14:37:32 +0100 Subject: import cran: Handle BSD licenses with LICENSE file. * guix/import/cran.scm (string->license): Add cases for BSD licenses with LICENSE file. --- guix/import/cran.scm | 2 ++ 1 file changed, 2 insertions(+) (limited to 'guix') diff --git a/guix/import/cran.scm b/guix/import/cran.scm index 549b11c392..d2add6f633 100644 --- a/guix/import/cran.scm +++ b/guix/import/cran.scm @@ -51,7 +51,9 @@ ("Artistic-2.0" 'artistic2.0) ("Apache License 2.0" 'asl2.0) ("BSD_2_clause" 'bsd-2) + ("BSD_2_clause + file LICENSE" 'bsd-2) ("BSD_3_clause" 'bsd-3) + ("BSD_3_clause + file LICENSE" 'bsd-3) ("GPL" (list 'gpl2+ 'gpl3+)) ("GPL (>= 2)" 'gpl2+) ("GPL (>= 3)" 'gpl3+) -- cgit v1.2.3 From 2e5f32ce1b2d552527b8eb9a85a100a92d6d252a Mon Sep 17 00:00:00 2001 From: Ricardo Wurmus Date: Tue, 17 May 2016 15:06:44 +0200 Subject: import cran: Remove more invalid characters from package names. * guix/import/cran.scm (guix-name): Replace period and underscore with dash; always prepend package names with "r-". --- guix/import/cran.scm | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'guix') diff --git a/guix/import/cran.scm b/guix/import/cran.scm index d2add6f633..ee34eade94 100644 --- a/guix/import/cran.scm +++ b/guix/import/cran.scm @@ -152,9 +152,11 @@ empty list when the FIELD cannot be found." "Return the `package' s-expression for an R package published on REPOSITORY from the alist META, which was derived from the R package's DESCRIPTION file." (define (guix-name name) - (if (string-prefix? "r-" name) - (string-downcase name) - (string-append "r-" (string-downcase name)))) + (string-append "r-" (string-map (match-lambda + (#\_ #\-) + (#\. #\-) + (chr (char-downcase chr))) + name))) (let* ((base-url (case repository ((cran) %cran-url) -- cgit v1.2.3 From bfa0c752bc19810f7b4908c3d0f4a1dd7832c1b6 Mon Sep 17 00:00:00 2001 From: Ricardo Wurmus Date: Wed, 6 Jul 2016 12:42:38 +0200 Subject: import cran: Move guix-name to top-level. * guix/import/cran.scm (guix-name): Move to top-level. --- guix/import/cran.scm | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) (limited to 'guix') diff --git a/guix/import/cran.scm b/guix/import/cran.scm index ee34eade94..c98ab88aec 100644 --- a/guix/import/cran.scm +++ b/guix/import/cran.scm @@ -148,16 +148,17 @@ empty list when the FIELD cannot be found." (string-any char-set:whitespace item))) (map string-trim-both items)))))) +(define (guix-name name) + "Return a Guix package name for a given R package name." + (string-append "r-" (string-map (match-lambda + (#\_ #\-) + (#\. #\-) + (chr (char-downcase chr))) + name))) + (define (description->package repository meta) "Return the `package' s-expression for an R package published on REPOSITORY from the alist META, which was derived from the R package's DESCRIPTION file." - (define (guix-name name) - (string-append "r-" (string-map (match-lambda - (#\_ #\-) - (#\. #\-) - (chr (char-downcase chr))) - name))) - (let* ((base-url (case repository ((cran) %cran-url) ((bioconductor) %bioconductor-url))) -- cgit v1.2.3 From ad68f7fa58daeff542d2fba19f65928c1af5799b Mon Sep 17 00:00:00 2001 From: Ricardo Wurmus Date: Tue, 17 May 2016 15:17:54 +0200 Subject: import cran: description->package: Also return package dependencies. * guix/import/cran.scm (description->package): Return package dependencies in addition to generated package expression. --- guix/import/cran.scm | 58 ++++++++++++++++++++++++++++------------------------ 1 file changed, 31 insertions(+), 27 deletions(-) (limited to 'guix') diff --git a/guix/import/cran.scm b/guix/import/cran.scm index c98ab88aec..313e26b03d 100644 --- a/guix/import/cran.scm +++ b/guix/import/cran.scm @@ -23,6 +23,7 @@ #:use-module ((ice-9 rdelim) #:select (read-string)) #:use-module (srfi srfi-1) #:use-module (srfi srfi-26) + #:use-module (ice-9 receive) #:use-module (guix http-client) #:use-module (guix hash) #:use-module (guix store) @@ -179,33 +180,36 @@ from the alist META, which was derived from the R package's DESCRIPTION file." (_ #f))) (tarball (with-store store (download-to-store store source-url))) (sysdepends (map string-downcase (listify meta "SystemRequirements"))) - (propagate (map guix-name (lset-union equal? - (listify meta "Imports") - (listify meta "LinkingTo") - (delete "R" - (listify meta "Depends")))))) - `(package - (name ,(guix-name name)) - (version ,version) - (source (origin - (method url-fetch) - (uri (,(procedure-name uri-helper) ,name version)) - (sha256 - (base32 - ,(bytevector->nix-base32-string (file-sha256 tarball)))))) - ,@(if (not (equal? (string-append "r-" name) - (guix-name name))) - `((properties ,`(,'quasiquote ((,'upstream-name . ,name))))) - '()) - (build-system r-build-system) - ,@(maybe-inputs sysdepends) - ,@(maybe-inputs propagate 'propagated-inputs) - (home-page ,(if (string-null? home-page) - (string-append base-url name) - home-page)) - (synopsis ,synopsis) - (description ,(beautify-description (assoc-ref meta "Description"))) - (license ,license)))) + (propagate (lset-union equal? + (listify meta "Imports") + (listify meta "LinkingTo") + (delete "R" + (listify meta "Depends"))))) + (values + `(package + (name ,(guix-name name)) + (version ,version) + (source (origin + (method url-fetch) + (uri (,(procedure-name uri-helper) ,name version)) + (sha256 + (base32 + ,(bytevector->nix-base32-string (file-sha256 tarball)))))) + ,@(if (not (equal? (string-append "r-" name) + (guix-name name))) + `((properties ,`(,'quasiquote ((,'upstream-name . ,name))))) + '()) + (build-system r-build-system) + ,@(maybe-inputs sysdepends) + ,@(maybe-inputs (map guix-name propagate) 'propagated-inputs) + (home-page ,(if (string-null? home-page) + (string-append base-url name) + home-page)) + (synopsis ,synopsis) + (description ,(beautify-description (or (assoc-ref meta "Description") + ""))) + (license ,license)) + propagate))) (define* (cran->guix-package package-name #:optional (repo 'cran)) "Fetch the metadata for PACKAGE-NAME from REPO and return the `package' -- cgit v1.2.3 From b26abe4f148ea04145cb1f62122eb560b64a0139 Mon Sep 17 00:00:00 2001 From: Ricardo Wurmus Date: Tue, 17 May 2016 15:22:30 +0200 Subject: import cran: Ignore default R packages. * guix/import/cran.scm (default-r-packages): New variable. (description->package): Drop default R packages from list of inputs. --- guix/import/cran.scm | 44 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 5 deletions(-) (limited to 'guix') diff --git a/guix/import/cran.scm b/guix/import/cran.scm index 313e26b03d..09796e0159 100644 --- a/guix/import/cran.scm +++ b/guix/import/cran.scm @@ -149,6 +149,38 @@ empty list when the FIELD cannot be found." (string-any char-set:whitespace item))) (map string-trim-both items)))))) +(define default-r-packages + (list "KernSmooth" + "MASS" + "Matrix" + "base" + "boot" + "class" + "cluster" + "codetools" + "compiler" + "datasets" + "foreign" + "grDevices" + "graphics" + "grid" + "lattice" + "methods" + "mgcv" + "nlme" + "nnet" + "parallel" + "rpart" + "spatial" + "splines" + "stats" + "stats4" + "survival" + "tcltk" + "tools" + "translations" + "utils")) + (define (guix-name name) "Return a Guix package name for a given R package name." (string-append "r-" (string-map (match-lambda @@ -180,11 +212,13 @@ from the alist META, which was derived from the R package's DESCRIPTION file." (_ #f))) (tarball (with-store store (download-to-store store source-url))) (sysdepends (map string-downcase (listify meta "SystemRequirements"))) - (propagate (lset-union equal? - (listify meta "Imports") - (listify meta "LinkingTo") - (delete "R" - (listify meta "Depends"))))) + (propagate (filter (lambda (name) + (not (member name default-r-packages))) + (lset-union equal? + (listify meta "Imports") + (listify meta "LinkingTo") + (delete "R" + (listify meta "Depends")))))) (values `(package (name ,(guix-name name)) -- cgit v1.2.3 From 94e907b96252bda6bbf49552b89928f337aadcfd Mon Sep 17 00:00:00 2001 From: Ricardo Wurmus Date: Tue, 17 May 2016 16:38:17 +0200 Subject: import cran: Add recursive importer. * guix/import/cran.scm (recursive-import): New variable. (cran->guix-package): Memoize the procedure. --- guix/import/cran.scm | 78 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 71 insertions(+), 7 deletions(-) (limited to 'guix') diff --git a/guix/import/cran.scm b/guix/import/cran.scm index 09796e0159..123abfe7ea 100644 --- a/guix/import/cran.scm +++ b/guix/import/cran.scm @@ -23,7 +23,9 @@ #:use-module ((ice-9 rdelim) #:select (read-string)) #:use-module (srfi srfi-1) #:use-module (srfi srfi-26) + #:use-module (srfi srfi-41) #:use-module (ice-9 receive) + #:use-module (guix combinators) #:use-module (guix http-client) #:use-module (guix hash) #:use-module (guix store) @@ -33,8 +35,10 @@ #:use-module ((guix build-system r) #:select (cran-uri bioconductor-uri)) #:use-module (guix upstream) #:use-module (guix packages) + #:use-module (gnu packages) #:export (cran->guix-package bioconductor->guix-package + recursive-import %cran-updater %bioconductor-updater)) @@ -245,14 +249,74 @@ from the alist META, which was derived from the R package's DESCRIPTION file." (license ,license)) propagate))) -(define* (cran->guix-package package-name #:optional (repo 'cran)) - "Fetch the metadata for PACKAGE-NAME from REPO and return the `package' +(define cran->guix-package + (memoize + (lambda* (package-name #:optional (repo 'cran)) + "Fetch the metadata for PACKAGE-NAME from REPO and return the `package' s-expression corresponding to that package, or #f on failure." - (let* ((url (case repo - ((cran) %cran-url) - ((bioconductor) %bioconductor-svn-url))) - (module-meta (fetch-description url package-name))) - (and=> module-meta (cut description->package repo <>)))) + (let* ((url (case repo + ((cran) %cran-url) + ((bioconductor) %bioconductor-svn-url))) + (module-meta (fetch-description url package-name))) + (and=> module-meta (cut description->package repo <>)))))) + +(define* (recursive-import package-name #:optional (repo 'cran)) + "Generate a stream of package expressions for PACKAGE-NAME and all its +dependencies." + (receive (package . dependencies) + (cran->guix-package package-name repo) + (if (not package) + stream-null + + ;; Generate a lazy stream of package expressions for all unknown + ;; dependencies in the graph. + (let* ((make-state (lambda (queue done) + (cons queue done))) + (next (match-lambda + (((next . rest) . done) next))) + (imported (match-lambda + ((queue . done) done))) + (done? (match-lambda + ((queue . done) + (zero? (length queue))))) + (unknown? (lambda* (dependency #:optional (done '())) + (and (not (member dependency + done)) + (null? (find-packages-by-name + (guix-name dependency)))))) + (update (lambda (state new-queue) + (match state + (((head . tail) . done) + (make-state (lset-difference + equal? + (lset-union equal? new-queue tail) + done) + (cons head done))))))) + (stream-cons + package + (stream-unfold + ;; map: produce a stream element + (lambda (state) + (cran->guix-package (next state) repo)) + + ;; predicate + (compose not done?) + + ;; generator: update the queue + (lambda (state) + (receive (package . dependencies) + (cran->guix-package (next state) repo) + (if package + (update state (filter (cut unknown? <> + (cons (next state) + (imported state))) + (car dependencies))) + ;; TODO: Try the other archives before giving up + (update state (imported state))))) + + ;; initial state + (make-state (filter unknown? (car dependencies)) + (list package-name)))))))) ;;; -- cgit v1.2.3 From 64ce53eb5e8347e93574bf02e183d668c33e250c Mon Sep 17 00:00:00 2001 From: Ricardo Wurmus Date: Tue, 17 May 2016 16:41:13 +0200 Subject: import cran: Add "recursive" option. * guix/scripts/import/cran.scm: (%options): Add "recursive" option. (guix-import-cran): Handle "recursive" option. * doc/guix.texi (Invoking guix import): Document it. --- doc/guix.texi | 4 ++++ guix/scripts/import/cran.scm | 26 ++++++++++++++++++++------ 2 files changed, 24 insertions(+), 6 deletions(-) (limited to 'guix') diff --git a/doc/guix.texi b/doc/guix.texi index bd782cab03..5d3a1753bf 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -5148,6 +5148,10 @@ R package: guix import cran Cairo @end example +When @code{--recursive} is added, the importer will traverse the +dependency graph of the given upstream package recursively and generate +package expressions for all those packages that are not yet in Guix. + When @code{--archive=bioconductor} is added, metadata is imported from @uref{http://www.bioconductor.org/, Bioconductor}, a repository of R packages for for the analysis and comprehension of high-throughput diff --git a/guix/scripts/import/cran.scm b/guix/scripts/import/cran.scm index ace1123b90..66c660ae14 100644 --- a/guix/scripts/import/cran.scm +++ b/guix/scripts/import/cran.scm @@ -26,6 +26,7 @@ #:use-module (srfi srfi-1) #:use-module (srfi srfi-11) #:use-module (srfi srfi-37) + #:use-module (srfi srfi-41) #:use-module (ice-9 match) #:use-module (ice-9 format) #:export (guix-import-cran)) @@ -63,6 +64,9 @@ Import and convert the CRAN package for PACKAGE-NAME.\n")) (lambda (opt name arg result) (alist-cons 'repo (string->symbol arg) (alist-delete 'repo result)))) + (option '(#\r "recursive") #f #f + (lambda (opt name arg result) + (alist-cons 'recursive #t result))) %standard-import-options)) @@ -88,12 +92,22 @@ Import and convert the CRAN package for PACKAGE-NAME.\n")) (reverse opts)))) (match args ((package-name) - (let ((sexp (cran->guix-package package-name - (or (assoc-ref opts 'repo) 'cran)))) - (unless sexp - (leave (_ "failed to download description for package '~a'~%") - package-name)) - sexp)) + (if (assoc-ref opts 'recursive) + ;; Recursive import + (map (match-lambda + ((and ('package ('name name) . rest) pkg) + `(define-public ,(string->symbol name) + ,pkg)) + (_ #f)) + (stream->list (recursive-import package-name + (or (assoc-ref opts 'repo) 'cran)))) + ;; Single import + (let ((sexp (cran->guix-package package-name + (or (assoc-ref opts 'repo) 'cran)))) + (unless sexp + (leave (_ "failed to download description for package '~a'~%") + package-name)) + sexp))) (() (leave (_ "too few arguments~%"))) ((many ...) -- cgit v1.2.3 From d68ba5f483a42ea324ff31746fde7dcc41600463 Mon Sep 17 00:00:00 2001 From: Ricardo Wurmus Date: Tue, 17 May 2016 16:42:41 +0200 Subject: guix import: Print list of expressions. * guix/scripts/import.scm (guix-import): Print list of expressions. --- guix/scripts/import.scm | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) (limited to 'guix') diff --git a/guix/scripts/import.scm b/guix/scripts/import.scm index c671686043..4d07e0fd69 100644 --- a/guix/scripts/import.scm +++ b/guix/scripts/import.scm @@ -107,10 +107,17 @@ Run IMPORTER with ARGS.\n")) (show-version-and-exit "guix import")) ((importer args ...) (if (member importer importers) - (match (apply (resolve-importer importer) args) - ((and expr ('package _ ...)) - (pretty-print expr (newline-rewriting-port - (current-output-port)))) - (x - (leave (_ "'~a' import failed~%") importer))) + (let ((print (lambda (expr) + (pretty-print expr (newline-rewriting-port + (current-output-port)))))) + (match (apply (resolve-importer importer) args) + ((and expr ('package _ ...)) + (print expr)) + ((? list? expressions) + (for-each (lambda (expr) + (print expr) + (newline)) + expressions)) + (x + (leave (_ "'~a' import failed~%") importer)))) (leave (_ "~a: invalid importer~%") importer))))) -- cgit v1.2.3 From fdbc84b056683204c5832d3740d9b109f31f7cb9 Mon Sep 17 00:00:00 2001 From: Ricardo Wurmus Date: Sat, 17 Dec 2016 15:24:45 +0100 Subject: import cran: Handle HTTP errors. Fixes . * guix/import/cran.scm (fetch-description): Return #f in case of HTTP errors. --- guix/import/cran.scm | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'guix') diff --git a/guix/import/cran.scm b/guix/import/cran.scm index 123abfe7ea..463a25514e 100644 --- a/guix/import/cran.scm +++ b/guix/import/cran.scm @@ -23,8 +23,10 @@ #:use-module ((ice-9 rdelim) #:select (read-string)) #:use-module (srfi srfi-1) #:use-module (srfi srfi-26) + #:use-module (srfi srfi-34) #:use-module (srfi srfi-41) #:use-module (ice-9 receive) + #:use-module (web uri) #:use-module (guix combinators) #:use-module (guix http-client) #:use-module (guix hash) @@ -128,10 +130,18 @@ package definition." (define (fetch-description base-url name) "Return an alist of the contents of the DESCRIPTION file for the R package -NAME, or #f on failure. NAME is case-sensitive." +NAME, or #f in case of failure. NAME is case-sensitive." ;; This API always returns the latest release of the module. (let ((url (string-append base-url name "/DESCRIPTION"))) - (description->alist (read-string (http-fetch url))))) + (guard (c ((http-get-error? c) + (format (current-error-port) + "error: failed to retrieve package information \ +from ~s: ~a (~s)~%" + (uri->string (http-get-error-uri c)) + (http-get-error-code c) + (http-get-error-reason c)) + #f)) + (description->alist (read-string (http-fetch url)))))) (define (listify meta field) "Look up FIELD in the alist META. If FIELD contains a comma-separated -- cgit v1.2.3 From 5b14a7902c58d9fb7923f9e16871f549fbe59b6e Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Mon, 19 Dec 2016 17:06:12 +0100 Subject: gexp: Native inputs of nested gexps are properly accounted for. Previously, 'gexp-native-inputs' would not return the native inputs of nested gexps. For example, this: (gexp-native-inputs #~(foo #$#~(bar #+coreutils))) would return '(). * guix/gexp.scm (gexp-inputs)[add-reference-inputs]: In the non-recursive cases, check whether N? and NATIVE? are the same, and act accordingly. [native-input?]: Remove. Fold over all of (gexp-references exp). * tests/gexp.scm ("ungexp + ungexp-native, nested, special mixture"): New test. * tests/gexp.scm ("input list splicing + ungexp-native-splicing"): Pass #:native? #t to 'gexp-input'. --- guix/gexp.scm | 28 ++++++++++++---------------- tests/gexp.scm | 11 ++++++++++- 2 files changed, 22 insertions(+), 17 deletions(-) (limited to 'guix') diff --git a/guix/gexp.scm b/guix/gexp.scm index fd5dc49233..5021688ac7 100644 --- a/guix/gexp.scm +++ b/guix/gexp.scm @@ -678,32 +678,28 @@ references; otherwise, return only non-native references." (if (direct-store-path? str) (cons `(,str) result) result)) - (($ (? struct? thing) output) - (if (lookup-compiler thing) + (($ (? struct? thing) output n?) + (if (and (eqv? n? native?) (lookup-compiler thing)) ;; THING is a derivation, or a package, or an origin, etc. (cons `(,thing ,output) result) result)) (($ (lst ...) output n?) - (fold-right add-reference-inputs result - ;; XXX: For now, automatically convert LST to a list of - ;; gexp-inputs. - (map (match-lambda - ((? gexp-input? x) x) - (x (%gexp-input x "out" (or n? native?)))) - lst))) + (if (eqv? native? n?) + (fold-right add-reference-inputs result + ;; XXX: For now, automatically convert LST to a list of + ;; gexp-inputs. + (map (match-lambda + ((? gexp-input? x) x) + (x (%gexp-input x "out" (or n? native?)))) + lst)) + result)) (_ ;; Ignore references to other kinds of objects. result))) - (define (native-input? x) - (and (gexp-input? x) - (gexp-input-native? x))) - (fold-right add-reference-inputs '() - (if native? - (filter native-input? (gexp-references exp)) - (remove native-input? (gexp-references exp))))) + (gexp-references exp))) (define gexp-native-inputs (cut gexp-inputs <> #:native? #t)) diff --git a/tests/gexp.scm b/tests/gexp.scm index 354d28f014..797d5fa457 100644 --- a/tests/gexp.scm +++ b/tests/gexp.scm @@ -277,6 +277,14 @@ (ungexp %bootstrap-guile))))) (list (gexp-inputs exp) '<> (gexp-native-inputs exp)))) +(test-equal "ungexp + ungexp-native, nested, special mixture" + `(() <> ((,coreutils "out"))) + + ;; (gexp-native-inputs exp) used to return '(), wrongfully. + (let* ((foo (gexp (foo (ungexp-native coreutils)))) + (exp (gexp (bar (ungexp foo))))) + (list (gexp-inputs exp) '<> (gexp-native-inputs exp)))) + (test-assert "input list" (let ((exp (gexp (display '(ungexp (list %bootstrap-guile coreutils))))) @@ -327,7 +335,8 @@ `(list ,@(cons 5 outputs)))))) (test-assert "input list splicing + ungexp-native-splicing" - (let* ((inputs (list (gexp-input glibc "debug") %bootstrap-guile)) + (let* ((inputs (list (gexp-input glibc "debug" #:native? #t) + %bootstrap-guile)) (exp (gexp (list (ungexp-native-splicing (cons (+ 2 3) inputs)))))) (and (lset= equal? `((,glibc "debug") (,%bootstrap-guile "out")) -- cgit v1.2.3 From d343a60f93cecb789f3366bac1f068f281c38d9f Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Mon, 19 Dec 2016 17:13:21 +0100 Subject: gexp: Slightly simplify 'gexp-inputs'. * guix/gexp.scm (gexp-inputs)[add-reference-inputs]: Remove unneeded 'if' in the non-native nested gexp case. --- guix/gexp.scm | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'guix') diff --git a/guix/gexp.scm b/guix/gexp.scm index 5021688ac7..6f63afe6a6 100644 --- a/guix/gexp.scm +++ b/guix/gexp.scm @@ -669,11 +669,8 @@ references; otherwise, return only non-native references." result) result)) (($ (? gexp? exp) _ #f) - (if native? - (append (gexp-inputs exp #:native? #t) - result) - (append (gexp-inputs exp) - result))) + (append (gexp-inputs exp #:native? native?) + result)) (($ (? string? str)) (if (direct-store-path? str) (cons `(,str) result) -- cgit v1.2.3 From 4cb7786eff9b9188169ed2c2e5af53903962e7d1 Mon Sep 17 00:00:00 2001 From: Marius Bakke Date: Tue, 20 Dec 2016 12:44:45 +0100 Subject: import: pypi: Match new 'pypi-uri' domain in updater. * guix/import/pypi.scm (pypi-package?): Match pypi.io domain. --- guix/import/pypi.scm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'guix') diff --git a/guix/import/pypi.scm b/guix/import/pypi.scm index 9794ff9757..7cce0fc594 100644 --- a/guix/import/pypi.scm +++ b/guix/import/pypi.scm @@ -303,7 +303,8 @@ VERSION, SOURCE-URL, HOME-PAGE, SYNOPSIS, DESCRIPTION, and LICENSE." "Return true if PACKAGE is a Python package from PyPI." (define (pypi-url? url) - (string-prefix? "https://pypi.python.org/" url)) + (or (string-prefix? "https://pypi.python.org/" url) + (string-prefix? "https://pypi.io/packages" url))) (let ((source-url (and=> (package-source package) origin-uri)) (fetch-method (and=> (package-source package) origin-method))) -- cgit v1.2.3 From f943c317fb714075b455d4a30f631c8cb45732b4 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Tue, 20 Dec 2016 19:06:22 +0100 Subject: environment: Add '--root' option. * guix/scripts/environment.scm (show-help, %options): Add --root. (register-gc-root): New procedure. (guix-environment): Call 'register-gc-root' when OPTS has a 'gc-root' option. * doc/guix.texi (Invoking guix environment): Document it. * tests/guix-environment.sh: Add tests. --- doc/guix.texi | 15 +++++++++++++++ guix/scripts/environment.scm | 34 ++++++++++++++++++++++++++++++++-- tests/guix-environment.sh | 17 ++++++++++++++++- 3 files changed, 63 insertions(+), 3 deletions(-) (limited to 'guix') diff --git a/doc/guix.texi b/doc/guix.texi index 512b3ae9ce..69129d5835 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -5997,6 +5997,21 @@ The @code{--container} option requires Linux-libre 3.19 or newer. The available options are summarized below. @table @code +@item --root=@var{file} +@itemx -r @var{file} +@cindex persistent environment +@cindex garbage collector root, for environments +Make @var{file} a symlink to the profile for this environment, and +register it as a garbage collector root. + +This is useful if you want to protect your environment from garbage +collection, to make it ``persistent''. + +When this option is omitted, the environment is protected from garbage +collection only for the duration of the @command{guix environment} +session. This means that next time you recreate the same environment, +you could have to rebuild or re-download packages. + @item --expression=@var{expr} @itemx -e @var{expr} Create an environment for the package or list of packages that diff --git a/guix/scripts/environment.scm b/guix/scripts/environment.scm index 7201d98fea..1d3be6a84f 100644 --- a/guix/scripts/environment.scm +++ b/guix/scripts/environment.scm @@ -155,6 +155,9 @@ COMMAND or an interactive shell in that environment.\n")) (display (_ " -s, --system=SYSTEM attempt to build for SYSTEM--e.g., \"i686-linux\"")) (display (_ " + -r, --root=FILE make FILE a symlink to the result, and register it + as a garbage collector root")) + (display (_ " -C, --container run command within an isolated container")) (display (_ " -N, --network allow containers to access the network")) @@ -247,6 +250,9 @@ COMMAND or an interactive shell in that environment.\n")) (alist-cons 'file-system-mapping (specification->file-system-mapping arg #f) result))) + (option '(#\r "root") #t #f + (lambda (opt name arg result) + (alist-cons 'gc-root arg result))) (option '("bootstrap") #f #f (lambda (opt name arg result) (alist-cons 'bootstrap? #t result))) @@ -523,7 +529,26 @@ message if any test fails." (report-error (_ "cannot create container: /proc/self/setgroups does not exist\n")) (leave (_ "is your kernel version < 3.19?\n")))) -;; Entry point. +(define (register-gc-root target root) + "Make ROOT an indirect root to TARGET. This is procedure is idempotent." + (let* ((root (string-append (canonicalize-path (dirname root)) + "/" root))) + (catch 'system-error + (lambda () + (symlink target root) + ((store-lift add-indirect-root) root)) + (lambda args + (if (and (= EEXIST (system-error-errno args)) + (equal? (false-if-exception (readlink root)) target)) + (with-monad %store-monad + (return #t)) + (apply throw args)))))) + + +;;; +;;; Entry point. +;;; + (define (guix-environment . args) (with-error-handling (let* ((opts (parse-args args)) @@ -579,7 +604,9 @@ message if any test fails." system)) (prof-drv (inputs->profile-derivation inputs system bootstrap?)) - (profile -> (derivation->output-path prof-drv))) + (profile -> (derivation->output-path prof-drv)) + (gc-root -> (assoc-ref opts 'gc-root))) + ;; First build the inputs. This is necessary even for ;; --search-paths. Additionally, we might need to build bash for ;; a container. @@ -588,6 +615,9 @@ message if any test fails." (list prof-drv bash) (list prof-drv)) opts) + (mwhen gc-root + (register-gc-root profile gc-root)) + (cond ((assoc-ref opts 'dry-run?) (return #t)) diff --git a/tests/guix-environment.sh b/tests/guix-environment.sh index 68343520b0..2b3bbfe036 100644 --- a/tests/guix-environment.sh +++ b/tests/guix-environment.sh @@ -25,7 +25,8 @@ set -e guix environment --version tmpdir="t-guix-environment-$$" -trap 'rm -r "$tmpdir"' EXIT +gcroot="t-guix-environment-gc-root-$$" +trap 'rm -r "$tmpdir"; rm -f "$gcroot"' EXIT mkdir "$tmpdir" @@ -61,6 +62,20 @@ fi guix environment --bootstrap --ad-hoc guile-bootstrap --pure \ -- "$SHELL" -c 'test -f "$GUIX_ENVIRONMENT/bin/guile"' +# Make sure '-r' works as expected. +rm -f "$gcroot" +expected="`guix environment --bootstrap --ad-hoc guile-bootstrap \ + -- "$SHELL" -c 'echo $GUIX_ENVIRONMENT'`" +guix environment --bootstrap -r "$gcroot" --ad-hoc guile-bootstrap \ + -- guile -c 1 +test `readlink "$gcroot"` = "$expected" + +# Make sure '-r' is idempotent. +guix environment --bootstrap -r "$gcroot" --ad-hoc guile-bootstrap \ + -- guile -c 1 +test `readlink "$gcroot"` = "$expected" + + case "`uname -m`" in x86_64) # On x86_64, we should be able to create a 32-bit environment. -- cgit v1.2.3 From 62bd24db39a86f80242f923eb4cc2f18f3b02c67 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Tue, 20 Dec 2016 19:08:22 +0100 Subject: import: github: Use 'json-fetch'. * guix/import/github.scm (json-fetch*): Remove. (latest-released-version): Adjust accordingly. --- guix/import/github.scm | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) (limited to 'guix') diff --git a/guix/import/github.scm b/guix/import/github.scm index 0843ddeefd..01452b12e3 100644 --- a/guix/import/github.scm +++ b/guix/import/github.scm @@ -23,23 +23,12 @@ #:use-module (guix utils) #:use-module ((guix download) #:prefix download:) #:use-module (guix import utils) + #:use-module (guix import json) #:use-module (guix packages) #:use-module (guix upstream) #:use-module (web uri) #:export (%github-updater)) -(define (json-fetch* url) - "Return a list/hash representation of the JSON resource URL, or #f on -failure." - (call-with-output-file "/dev/null" - (lambda (null) - (with-error-to-port null - (lambda () - (call-with-temporary-output-file - (lambda (temp port) - (and (url-fetch url temp) - (call-with-input-file temp json->scm))))))))) - (define (find-extension url) "Return the extension of the archive e.g. '.tar.gz' given a URL, or false if none is recognized" @@ -136,7 +125,7 @@ the package e.g. 'bedtools2'. Return #f if there is no releases" "https://api.github.com/repos/" (github-user-slash-repository url) "/releases")) - (json (json-fetch* + (json (json-fetch (if token (string-append api-url "?access_token=" token) api-url)))) -- cgit v1.2.3 From 7c247809efe5d6a2a11617f41f45a2b6e8d6855f Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Wed, 21 Dec 2016 23:46:44 +0100 Subject: guix build: '--with-source' overrides the 'replacement' of a package. * guix/scripts/build.scm (package-with-source): Set 'replacement' to #f. * tests/scripts-build.scm ("options->transformation, with-source, replacement"): New test. --- guix/scripts/build.scm | 6 +++++- tests/scripts-build.scm | 13 +++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) (limited to 'guix') diff --git a/guix/scripts/build.scm b/guix/scripts/build.scm index 8c2c4902fc..ccb4c275fc 100644 --- a/guix/scripts/build.scm +++ b/guix/scripts/build.scm @@ -151,7 +151,11 @@ the new package's version number from URI." ;; Use #:recursive? #t to allow for directories. (source (download-to-store store uri - #:recursive? #t)))))) + #:recursive? #t)) + + ;; Override the replacement, otherwise '--with-source' would + ;; have no effect. + (replacement #f))))) ;;; diff --git a/tests/scripts-build.scm b/tests/scripts-build.scm index b324012806..a1f684c736 100644 --- a/tests/scripts-build.scm +++ b/tests/scripts-build.scm @@ -50,6 +50,19 @@ (add-to-store store "guix.scm" #t "sha256" s))))))) +(test-assert "options->transformation, with-source, replacement" + ;; Same, but this time the original package has a 'replacement' field. We + ;; expect that replacement to be set to #f in the new package. + (let* ((p (dummy-package "guix.scm" (replacement coreutils))) + (s (search-path %load-path "guix.scm")) + (t (options->transformation `((with-source . ,s))))) + (with-store store + (let ((new (t store p))) + (and (not (eq? new p)) + (string=? (package-source new) + (add-to-store store "guix.scm" #t "sha256" s)) + (not (package-replacement new))))))) + (test-assert "options->transformation, with-source, with version" ;; Our pseudo-package is called 'guix.scm' so the 'guix.scm-2.0' source ;; should be applicable, and its version should be extracted. -- cgit v1.2.3 From 36225d4feff2f5582c05239b49433627997d2f2a Mon Sep 17 00:00:00 2001 From: Carlo Zancanaro Date: Wed, 14 Dec 2016 12:31:12 +1100 Subject: import: elpa: Fix call-with-downloaded-file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * guix/import/elpa.scm (call-with-downloaded-file): Make function behaviour match documentation string. Signed-off-by: Ludovic Courtès --- guix/import/elpa.scm | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'guix') diff --git a/guix/import/elpa.scm b/guix/import/elpa.scm index 320a09e8c6..ec232cd8a3 100644 --- a/guix/import/elpa.scm +++ b/guix/import/elpa.scm @@ -89,7 +89,13 @@ NAMES (strings)." "Fetch URL, store the content in a temporary file and call PROC with that file. Returns the value returned by PROC. On error call ERROR-THUNK and return its value or leave if it's false." - (proc (http-fetch/cached (string->uri url)))) + (catch #t + (lambda () + (proc (http-fetch/cached (string->uri url)))) + (lambda (key . args) + (if error-thunk + (error-thunk) + (leave (_ "~A: download failed~%") url))))) (define (is-elpa-package? name elpa-pkg-spec) "Return true if the string NAME corresponds to the name of the package -- cgit v1.2.3 From 681a555b837d766848baacbed5750e9d30314709 Mon Sep 17 00:00:00 2001 From: Carlo Zancanaro Date: Wed, 14 Dec 2016 12:34:15 +1100 Subject: import: elpa: Import dependencies as propagated-inputs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * guix/import/elpa.scm (elpa-package->sexp): Import dependencies as propagated-inputs. Signed-off-by: Ludovic Courtès --- guix/import/elpa.scm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'guix') diff --git a/guix/import/elpa.scm b/guix/import/elpa.scm index ec232cd8a3..96cf5bbae6 100644 --- a/guix/import/elpa.scm +++ b/guix/import/elpa.scm @@ -228,7 +228,7 @@ type ''." (bytevector->nix-base32-string (file-sha256 tarball)) "failed to download package"))))) (build-system emacs-build-system) - ,@(maybe-inputs 'inputs dependencies) + ,@(maybe-inputs 'propagated-inputs dependencies) (home-page ,(elpa-package-home-page pkg)) (synopsis ,(elpa-package-synopsis pkg)) (description ,(elpa-package-description pkg)) -- cgit v1.2.3 From 987a29ba43cc8a2751eafe392906d240713c724e Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Fri, 30 Dec 2016 23:22:27 +0100 Subject: Add (guix ssh) module. * guix/scripts/offload.scm (connect-to-remote-daemon) (store-import-channel, store-export-channel, send-files) (retrieve-files): Move to (guix ssh). (nonce): Add optional 'name' parameter and use it. (retrieve-files*): New procedure. (transfer-and-offload): Use it instead of 'retrieve-files', and add first parameter to 'send-files'. (assert-node-can-import): Likewise. (assert-node-can-export): Use 'retrieve-files' instead of 'store-export-channel'. * guix/ssh.scm: New file. * configure.ac: Use 'GUIX_CHECK_GUILE_SSH' and define 'HAVE_GUILE_SSH' Automake conditional. * Makefile.am (MODULES) [HAVE_GUILE_SSH]: Add guix/ssh.scm. --- Makefile.am | 7 ++ configure.ac | 5 ++ guix/scripts/offload.scm | 154 ++++------------------------------- guix/ssh.scm | 204 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 233 insertions(+), 137 deletions(-) create mode 100644 guix/ssh.scm (limited to 'guix') diff --git a/Makefile.am b/Makefile.am index 97629f26e6..094d6e5108 100644 --- a/Makefile.am +++ b/Makefile.am @@ -170,6 +170,13 @@ MODULES += \ endif +if HAVE_GUILE_SSH + +MODULES += \ + guix/ssh.scm + +endif HAVE_GUILE_SSH + if BUILD_DAEMON_OFFLOAD MODULES += \ diff --git a/configure.ac b/configure.ac index c3173d60c5..676f600111 100644 --- a/configure.ac +++ b/configure.ac @@ -216,6 +216,11 @@ AC_MSG_CHECKING([for zlib's shared library name]) AC_MSG_RESULT([$LIBZ]) AC_SUBST([LIBZ]) +dnl Check for Guile-SSH, for the (guix ssh) module. +GUIX_CHECK_GUILE_SSH +AM_CONDITIONAL([HAVE_GUILE_SSH], + [test "x$guix_cv_have_recent_guile_ssh" = "xyes"]) + AC_CACHE_SAVE m4_include([config-daemon.ac]) diff --git a/guix/scripts/offload.scm b/guix/scripts/offload.scm index c98cf8c534..6a4ae28689 100644 --- a/guix/scripts/offload.scm +++ b/guix/scripts/offload.scm @@ -27,6 +27,7 @@ #:use-module (ssh version) #:use-module (guix config) #:use-module (guix records) + #:use-module (guix ssh) #:use-module (guix store) #:use-module (guix derivations) #:use-module ((guix serialization) @@ -221,53 +222,6 @@ instead of '~a' of type '~a'~%") (leave (_ "failed to connect to '~a': ~a~%") (build-machine-name machine) (get-error session)))))) -(define* (connect-to-remote-daemon session - #:optional - (socket-name "/var/guix/daemon-socket/socket")) - "Connect to the remote build daemon listening on SOCKET-NAME over SESSION, -an SSH session. Return a object." - (define redirect - ;; Code run in SESSION to redirect the remote process' stdin/stdout to the - ;; daemon's socket, à la socat. The SSH protocol supports forwarding to - ;; Unix-domain sockets but libssh doesn't have an API for that, hence this - ;; hack. - `(begin - (use-modules (ice-9 match) (rnrs io ports)) - - (let ((sock (socket AF_UNIX SOCK_STREAM 0)) - (stdin (current-input-port)) - (stdout (current-output-port))) - (setvbuf stdin _IONBF) - (setvbuf stdout _IONBF) - (connect sock AF_UNIX ,socket-name) - - (let loop () - (match (select (list stdin sock) '() (list stdin stdout sock)) - ((reads writes ()) - (when (memq stdin reads) - (match (get-bytevector-some stdin) - ((? eof-object?) - (primitive-exit 0)) - (bv - (put-bytevector sock bv)))) - (when (memq sock reads) - (match (get-bytevector-some sock) - ((? eof-object?) - (primitive-exit 0)) - (bv - (put-bytevector stdout bv)))) - (loop)) - (_ - (primitive-exit 1))))))) - - (let ((channel - (open-remote-pipe* session OPEN_BOTH - ;; Sort-of shell-quote REDIRECT. - "guile" "-c" - (object->string - (object->string redirect))))) - (open-connection #:port channel))) - ;;; ;;; Synchronization. @@ -382,8 +336,9 @@ MACHINE." ;; Protect DRV from garbage collection. (add-temp-root store (derivation-file-name drv)) - (send-files (cons (derivation-file-name drv) inputs) - store) + (with-store local + (send-files local (cons (derivation-file-name drv) inputs) store + #:log-port (current-output-port))) (format (current-error-port) "offloading '~a' to '~a'...~%" (derivation-file-name drv) (build-machine-name machine)) (format (current-error-port) "@ build-remote ~a ~a~%" @@ -401,93 +356,17 @@ MACHINE." (parameterize ((current-build-output-port (build-log-port))) (build-derivations store (list drv)))) - (retrieve-files outputs store) + (retrieve-files* outputs store) (format (current-error-port) "done with offloaded '~a'~%" (derivation-file-name drv))) -(define (store-import-channel session) - "Return an output port to which archives to be exported to SESSION's store -can be written." - ;; Using the 'import-paths' RPC on a remote store would be slow because it - ;; makes a round trip every time 32 KiB have been transferred. This - ;; procedure instead opens a separate channel to use the remote - ;; 'import-paths' procedure, which consumes all the data in a single round - ;; trip. - (define import - `(begin - (use-modules (guix)) - - (with-store store - (setvbuf (current-input-port) _IONBF) - (import-paths store (current-input-port))))) - - (open-remote-output-pipe session - (string-join - `("guile" "-c" - ,(object->string - (object->string import)))))) - -(define (store-export-channel session files) - "Return an input port from which an export of FILES from SESSION's store can -be read." - ;; Same as above: this is more efficient than calling 'export-paths' on a - ;; remote store. - (define export - `(begin - (use-modules (guix)) - - (with-store store - (setvbuf (current-output-port) _IONBF) - (export-paths store ',files (current-output-port))))) - - (open-remote-input-pipe session - (string-join - `("guile" "-c" - ,(object->string - (object->string export)))))) - -(define (send-files files remote) - "Send the subset of FILES that's missing to REMOTE, a remote store." - (with-store store - ;; Compute the subset of FILES missing on SESSION and send them. - (let* ((session (channel-get-session (nix-server-socket remote))) - (node (make-node session)) - (missing (node-eval node - `(begin - (use-modules (guix) - (srfi srfi-1) (srfi srfi-26)) - - (with-store store - (remove (cut valid-path? store <>) - ',files))))) - (count (length missing)) - (port (store-import-channel session))) - (format #t (N_ "sending ~a store item to '~a'...~%" - "sending ~a store items to '~a'...~%" count) - count (session-get session 'host)) - - ;; Send MISSING in topological order. - (export-paths store missing port) - - ;; Tell the remote process that we're done. (In theory the - ;; end-of-archive mark of 'export-paths' would be enough, but in - ;; practice it's not.) - (channel-send-eof port) - - ;; Wait for completion of the remote process. - (let ((result (zero? (channel-get-exit-status port)))) - (close-port port) - result)))) - -(define (retrieve-files files remote) - "Retrieve FILES from SESSION's store, and import them." - (let* ((session (channel-get-session (nix-server-socket remote))) - (host (session-get session 'host)) - (port (store-export-channel session files)) - (count (length files))) +(define (retrieve-files* files remote) + "Retrieve FILES from REMOTE and import them using 'restore-file-set'." + (let-values (((port count) + (file-retrieval-port files remote))) (format #t (N_ "retrieving ~a store item from '~a'...~%" "retrieving ~a store items from '~a'...~%" count) - count host) + count (remote-store-host remote)) ;; We cannot use the 'import-paths' RPC here because we already ;; hold the locks for FILES. @@ -677,8 +556,8 @@ allowed on MACHINE. Return +∞ if MACHINE is unreachable." (delay (seed->random-state (logxor (getpid) (car (gettimeofday)))))) -(define (nonce) - (string-append (gethostname) "-" +(define* (nonce #:optional (name (gethostname))) + (string-append name "-" (number->string (random 1000000 (force %random-state))))) (define (assert-node-can-import node name daemon-socket) @@ -687,7 +566,9 @@ allowed on MACHINE. Return +∞ if MACHINE is unreachable." (with-store store (let* ((item (add-text-to-store store "export-test" (nonce))) (remote (connect-to-remote-daemon session daemon-socket))) - (send-files (list item) remote) + (with-store local + (send-files local (list item) remote)) + (if (valid-path? remote item) (info (_ "'~a' successfully imported '~a'~%") name item) @@ -698,10 +579,9 @@ allowed on MACHINE. Return +∞ if MACHINE is unreachable." "Bail out if we cannot import signed archives from NODE." (let* ((session (node-session node)) (remote (connect-to-remote-daemon session daemon-socket)) - (item (add-text-to-store remote "import-test" (nonce))) - (port (store-export-channel session (list item)))) + (item (add-text-to-store remote "import-test" (nonce name)))) (with-store store - (if (and (import-paths store port) + (if (and (retrieve-files store (list item) remote) (valid-path? store item)) (info (_ "successfully imported '~a' from '~a'~%") item name) diff --git a/guix/ssh.scm b/guix/ssh.scm new file mode 100644 index 0000000000..e07d7612c6 --- /dev/null +++ b/guix/ssh.scm @@ -0,0 +1,204 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2016 Ludovic Courtès +;;; +;;; 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 . + +(define-module (guix ssh) + #:use-module (guix store) + #:autoload (guix ui) (N_) + #:use-module (ssh channel) + #:use-module (ssh popen) + #:use-module (ssh session) + #:use-module (ssh dist) + #:use-module (ssh dist node) + #:use-module (srfi srfi-11) + #:use-module (ice-9 match) + #:export (connect-to-remote-daemon + send-files + retrieve-files + remote-store-host + + file-retrieval-port)) + +;;; Commentary: +;;; +;;; This module provides tools to support communication with remote stores +;;; over SSH, using Guile-SSH. +;;; +;;; Code: + +(define* (connect-to-remote-daemon session + #:optional + (socket-name "/var/guix/daemon-socket/socket")) + "Connect to the remote build daemon listening on SOCKET-NAME over SESSION, +an SSH session. Return a object." + (define redirect + ;; Code run in SESSION to redirect the remote process' stdin/stdout to the + ;; daemon's socket, à la socat. The SSH protocol supports forwarding to + ;; Unix-domain sockets but libssh doesn't have an API for that, hence this + ;; hack. + `(begin + (use-modules (ice-9 match) (rnrs io ports)) + + (let ((sock (socket AF_UNIX SOCK_STREAM 0)) + (stdin (current-input-port)) + (stdout (current-output-port))) + (setvbuf stdin _IONBF) + (setvbuf stdout _IONBF) + (connect sock AF_UNIX ,socket-name) + + (let loop () + (match (select (list stdin sock) '() (list stdin stdout sock)) + ((reads writes ()) + (when (memq stdin reads) + (match (get-bytevector-some stdin) + ((? eof-object?) + (primitive-exit 0)) + (bv + (put-bytevector sock bv)))) + (when (memq sock reads) + (match (get-bytevector-some sock) + ((? eof-object?) + (primitive-exit 0)) + (bv + (put-bytevector stdout bv)))) + (loop)) + (_ + (primitive-exit 1))))))) + + (let ((channel + (open-remote-pipe* session OPEN_BOTH + ;; Sort-of shell-quote REDIRECT. + "guile" "-c" + (object->string + (object->string redirect))))) + (open-connection #:port channel))) + +(define (store-import-channel session) + "Return an output port to which archives to be exported to SESSION's store +can be written." + ;; Using the 'import-paths' RPC on a remote store would be slow because it + ;; makes a round trip every time 32 KiB have been transferred. This + ;; procedure instead opens a separate channel to use the remote + ;; 'import-paths' procedure, which consumes all the data in a single round + ;; trip. + (define import + `(begin + (use-modules (guix)) + + (with-store store + (setvbuf (current-input-port) _IONBF) + + ;; FIXME: Exceptions are silently swallowed. We should report them + ;; somehow. + (import-paths store (current-input-port))))) + + (open-remote-output-pipe session + (string-join + `("guile" "-c" + ,(object->string + (object->string import)))))) + +(define (store-export-channel session files) + "Return an input port from which an export of FILES from SESSION's store can +be read." + ;; Same as above: this is more efficient than calling 'export-paths' on a + ;; remote store. + (define export + `(begin + (use-modules (guix)) + + (with-store store + (setvbuf (current-output-port) _IONBF) + + ;; FIXME: Exceptions are silently swallowed. We should report them + ;; somehow. + (export-paths store ',files (current-output-port))))) + + (open-remote-input-pipe session + (string-join + `("guile" "-c" + ,(object->string + (object->string export)))))) + +(define* (send-files local files remote + #:key (log-port (current-error-port))) + "Send the subset of FILES from LOCAL (a local store) that's missing to +REMOTE, a remote store." + ;; Compute the subset of FILES missing on SESSION and send them. + (let* ((session (channel-get-session (nix-server-socket remote))) + (node (make-node session)) + (missing (node-eval node + `(begin + (use-modules (guix) + (srfi srfi-1) (srfi srfi-26)) + + (with-store store + (remove (cut valid-path? store <>) + ',files))))) + (count (length missing)) + (port (store-import-channel session))) + (format log-port (N_ "sending ~a store item to '~a'...~%" + "sending ~a store items to '~a'...~%" count) + count (session-get session 'host)) + + ;; Send MISSING in topological order. + (export-paths local missing port) + + ;; Tell the remote process that we're done. (In theory the end-of-archive + ;; mark of 'export-paths' would be enough, but in practice it's not.) + (channel-send-eof port) + + ;; Wait for completion of the remote process. + (let ((result (zero? (channel-get-exit-status port)))) + (close-port port) + result))) + +(define (remote-store-session remote) + "Return the SSH channel beneath REMOTE, a remote store as returned by +'connect-to-remote-daemon', or #f." + (channel-get-session (nix-server-socket remote))) + +(define (remote-store-host remote) + "Return the name of the host REMOTE is connected to, where REMOTE is a +remote store as returned by 'connect-to-remote-daemon'." + (match (remote-store-session remote) + (#f #f) + ((? session? session) + (session-get session 'host)))) + +(define (file-retrieval-port files remote) + "Return an input port from which to retrieve FILES (a list of store items) +from REMOTE, along with the number of items to retrieve (lower than or equal +to the length of FILES.)" + (values (store-export-channel (remote-store-session remote) files) + (length files))) + +(define* (retrieve-files local files remote + #:key (log-port (current-error-port))) + "Retrieve FILES from REMOTE and import them using the 'import-paths' RPC on +LOCAL." + (let-values (((port count) + (file-retrieval-port files remote))) + (format #t (N_ "retrieving ~a store item from '~a'...~%" + "retrieving ~a store items from '~a'...~%" count) + count (remote-store-host remote)) + + (let ((result (import-paths local port))) + (close-port port) + result))) + +;;; ssh.scm ends here -- cgit v1.2.3 From c0b2d08bf473ccc6a95e0ed38ce966befb83c0c1 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Sat, 31 Dec 2016 15:28:28 +0100 Subject: git-download: Use a single download script for all derivations. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit That way, we have only one /gnu/store/…-git-download instead of one /gnu/store/…-PACKAGE-checkout-builder for each package. This is a followup to ced200328ca6337ac446e4557c645629e7d7a997. * guix/git-download.scm (git-fetch)[build]: Get the URL, commit, and recursive parameter using 'getenv'. Pass #:script-name and #:env-vars arguments to 'gexp->derivation'. --- guix/git-download.scm | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) (limited to 'guix') diff --git a/guix/git-download.scm b/guix/git-download.scm index 9cc6dd3d94..81d1d4fbf6 100644 --- a/guix/git-download.scm +++ b/guix/git-download.scm @@ -82,14 +82,26 @@ HASH-ALGO (a symbol). Use NAME as the file name, or a generic name if #f." (((names dirs) ...) dirs))) - (git-fetch '#$(git-reference-url ref) - '#$(git-reference-commit ref) + (git-fetch (getenv "git url") (getenv "git commit") #$output - #:recursive? '#$(git-reference-recursive? ref) + #:recursive? (call-with-input-string + (getenv "git recursive?") + read) #:git-command (string-append #+git "/bin/git"))))) (mlet %store-monad ((guile (package->derivation guile system))) (gexp->derivation (or name "git-checkout") build + + ;; Use environment variables and a fixed script name so + ;; there's only one script in store for all the + ;; downloads. + #:script-name "git-download" + #:env-vars + `(("git url" . ,(git-reference-url ref)) + ("git commit" . ,(git-reference-commit ref)) + ("git recursive?" . ,(object->string + (git-reference-recursive? ref)))) + #:system system #:local-build? #t ;don't offload repo cloning #:hash-algo hash-algo -- cgit v1.2.3 From e9629e8221844081e95f7633a882b0a1e6fb45f6 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Sat, 31 Dec 2016 18:13:29 +0100 Subject: ssh: Allow transfers of complete closures. * guix/ssh.scm (store-export-channel, send-files) (file-retrieval-port, retrieve-files): Add #:recursive? parameter and honor it. --- guix/ssh.scm | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) (limited to 'guix') diff --git a/guix/ssh.scm b/guix/ssh.scm index e07d7612c6..e31ec53a4a 100644 --- a/guix/ssh.scm +++ b/guix/ssh.scm @@ -112,9 +112,10 @@ can be written." ,(object->string (object->string import)))))) -(define (store-export-channel session files) +(define* (store-export-channel session files + #:key recursive?) "Return an input port from which an export of FILES from SESSION's store can -be read." +be read. When RECURSIVE? is true, the closure of FILES is exported." ;; Same as above: this is more efficient than calling 'export-paths' on a ;; remote store. (define export @@ -126,7 +127,8 @@ be read." ;; FIXME: Exceptions are silently swallowed. We should report them ;; somehow. - (export-paths store ',files (current-output-port))))) + (export-paths store ',files (current-output-port) + #:recursive? ,recursive?)))) (open-remote-input-pipe session (string-join @@ -135,11 +137,14 @@ be read." (object->string export)))))) (define* (send-files local files remote - #:key (log-port (current-error-port))) + #:key + recursive? + (log-port (current-error-port))) "Send the subset of FILES from LOCAL (a local store) that's missing to -REMOTE, a remote store." +REMOTE, a remote store. When RECURSIVE? is true, send the closure of FILES." ;; Compute the subset of FILES missing on SESSION and send them. - (let* ((session (channel-get-session (nix-server-socket remote))) + (let* ((files (if recursive? (requisites local files) files)) + (session (channel-get-session (nix-server-socket remote))) (node (make-node session)) (missing (node-eval node `(begin @@ -180,19 +185,22 @@ remote store as returned by 'connect-to-remote-daemon'." ((? session? session) (session-get session 'host)))) -(define (file-retrieval-port files remote) +(define* (file-retrieval-port files remote + #:key recursive?) "Return an input port from which to retrieve FILES (a list of store items) from REMOTE, along with the number of items to retrieve (lower than or equal to the length of FILES.)" - (values (store-export-channel (remote-store-session remote) files) - (length files))) + (values (store-export-channel (remote-store-session remote) files + #:recursive? recursive?) + (length files))) ;XXX: inaccurate when RECURSIVE? is true (define* (retrieve-files local files remote - #:key (log-port (current-error-port))) + #:key recursive? (log-port (current-error-port))) "Retrieve FILES from REMOTE and import them using the 'import-paths' RPC on -LOCAL." +LOCAL. When RECURSIVE? is true, retrieve the closure of FILES." (let-values (((port count) - (file-retrieval-port files remote))) + (file-retrieval-port files remote + #:recursive? recursive?))) (format #t (N_ "retrieving ~a store item from '~a'...~%" "retrieving ~a store items from '~a'...~%" count) count (remote-store-host remote)) -- cgit v1.2.3 From 23973e4fbf1547da10a4d1e16c293192ee6a05ed Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Sat, 31 Dec 2016 18:32:15 +0100 Subject: ssh: 'send-files' returns the list of items sent. * guix/ssh.scm (send-files): Return MISSING. --- guix/ssh.scm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'guix') diff --git a/guix/ssh.scm b/guix/ssh.scm index e31ec53a4a..226c4fd97d 100644 --- a/guix/ssh.scm +++ b/guix/ssh.scm @@ -141,7 +141,8 @@ be read. When RECURSIVE? is true, the closure of FILES is exported." recursive? (log-port (current-error-port))) "Send the subset of FILES from LOCAL (a local store) that's missing to -REMOTE, a remote store. When RECURSIVE? is true, send the closure of FILES." +REMOTE, a remote store. When RECURSIVE? is true, send the closure of FILES. +Return the list of store items actually sent." ;; Compute the subset of FILES missing on SESSION and send them. (let* ((files (if recursive? (requisites local files) files)) (session (channel-get-session (nix-server-socket remote))) @@ -170,7 +171,7 @@ REMOTE, a remote store. When RECURSIVE? is true, send the closure of FILES." ;; Wait for completion of the remote process. (let ((result (zero? (channel-get-exit-status port)))) (close-port port) - result))) + missing))) (define (remote-store-session remote) "Return the SSH channel beneath REMOTE, a remote store as returned by -- cgit v1.2.3 From 13164a210224025384061a5d4c522fa1983c10b4 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Sat, 31 Dec 2016 18:34:17 +0100 Subject: ssh: 'retrieve-files' detects remote export failures. * guix/ssh.scm (retrieve-files): Call 'lookahead-u8' and raise a '&message' condition when it returns EOF. --- guix/ssh.scm | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'guix') diff --git a/guix/ssh.scm b/guix/ssh.scm index 226c4fd97d..3548243839 100644 --- a/guix/ssh.scm +++ b/guix/ssh.scm @@ -25,7 +25,10 @@ #:use-module (ssh dist) #:use-module (ssh dist node) #:use-module (srfi srfi-11) + #:use-module (srfi srfi-34) + #:use-module (srfi srfi-35) #:use-module (ice-9 match) + #:use-module (ice-9 binary-ports) #:export (connect-to-remote-daemon send-files retrieve-files @@ -205,6 +208,16 @@ LOCAL. When RECURSIVE? is true, retrieve the closure of FILES." (format #t (N_ "retrieving ~a store item from '~a'...~%" "retrieving ~a store items from '~a'...~%" count) count (remote-store-host remote)) + (when (eof-object? (lookahead-u8 port)) + ;; The failure could be because one of the requested store items is not + ;; valid on REMOTE, or because Guile or Guix is improperly installed. + ;; TODO: Improve error reporting. + (raise (condition + (&message + (message + (format #f + (_ "failed to retrieve store items from '~a'") + (remote-store-host remote))))))) (let ((result (import-paths local port))) (close-port port) -- cgit v1.2.3 From f11c444d440b68c3975c2dcaacb24fa3e0e09c7d Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Sat, 31 Dec 2016 18:19:56 +0100 Subject: Add 'guix copy'. * guix/scripts/copy.scm: New file. * guix/scripts/archive.scm (options->derivations+files): Export. * doc/guix.texi (Invoking guix copy): New node. * Makefile.am (MODULES) [HAVE_GUILE_SSH]: Add guix/scripts/copy.scm. * po/guix/POTFILES.in: Likewise. --- Makefile.am | 3 +- doc/guix.texi | 77 +++++++++++++++++- guix/scripts/archive.scm | 3 +- guix/scripts/copy.scm | 207 +++++++++++++++++++++++++++++++++++++++++++++++ po/guix/POTFILES.in | 1 + 5 files changed, 285 insertions(+), 6 deletions(-) create mode 100644 guix/scripts/copy.scm (limited to 'guix') diff --git a/Makefile.am b/Makefile.am index 094d6e5108..fb08a004b6 100644 --- a/Makefile.am +++ b/Makefile.am @@ -173,7 +173,8 @@ endif if HAVE_GUILE_SSH MODULES += \ - guix/ssh.scm + guix/ssh.scm \ + guix/scripts/copy.scm endif HAVE_GUILE_SSH diff --git a/doc/guix.texi b/doc/guix.texi index 8756061a46..42fb439668 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -145,12 +145,13 @@ Utilities * Invoking guix environment:: Setting up development environments. * Invoking guix publish:: Sharing substitutes. * Invoking guix challenge:: Challenging substitute servers. +* Invoking guix copy:: Copying to and from a remote store. * Invoking guix container:: Process isolation. Invoking @command{guix build} * Common Build Options:: Build options for most commands. -* Package Transformation Options:: Creating variants of packages. +* Package Transformation Options:: Creating variants of packages. * Additional Build Options:: Options specific to 'guix build'. GNU Distribution @@ -199,12 +200,14 @@ Services * Log Rotation:: The rottlog service. * Networking Services:: Network setup, SSH daemon, etc. * X Window:: Graphical display. +* Printing Services:: Local and remote printer support. * Desktop Services:: D-Bus and desktop services. * Database Services:: SQL databases. * Mail Services:: IMAP, POP3, SMTP, and all that. * Kerberos Services:: Kerberos services. * Web Services:: Web servers. * Network File System:: NFS related services. +* Continuous Integration:: The Cuirass service. * Miscellaneous Services:: Other services. Defining Services @@ -551,7 +554,8 @@ interest primarily for developers and not for casual users. @item @c Note: We need at least 0.10.2 for 'channel-send-eof'. -Support for build offloading (@pxref{Daemon Offload Setup}) depends on +Support for build offloading (@pxref{Daemon Offload Setup}) and +@command{guix copy} (@pxref{Invoking guix copy}) depends on @uref{https://github.com/artyom-poptsov/guile-ssh, Guile-SSH}, version 0.10.2 or later. @@ -2384,7 +2388,9 @@ However, note that, in both examples, all of @code{emacs} and the profile as well as all of their dependencies are transferred (due to @code{-r}), regardless of what is already available in the store on the target machine. The @code{--missing} option can help figure out which -items are missing from the target store. +items are missing from the target store. The @command{guix copy} +command simplifies and optimizes this whole process, so this is probably +what you should use in this case (@pxref{Invoking guix copy}). @cindex nar, archive format @cindex normalized archive (nar) @@ -4415,6 +4421,7 @@ the Scheme programming interface of Guix in a convenient way. * Invoking guix environment:: Setting up development environments. * Invoking guix publish:: Sharing substitutes. * Invoking guix challenge:: Challenging substitute servers. +* Invoking guix copy:: Copying to and from a remote store. * Invoking guix container:: Process isolation. @end menu @@ -4467,7 +4474,7 @@ described in the subsections below. @menu * Common Build Options:: Build options for most commands. -* Package Transformation Options:: Creating variants of packages. +* Package Transformation Options:: Creating variants of packages. * Additional Build Options:: Options specific to 'guix build'. @end menu @@ -6371,6 +6378,68 @@ URLs to compare to. @end table +@node Invoking guix copy +@section Invoking @command{guix copy} + +@cindex copy, of store items, over SSH +@cindex SSH, copy of store items +@cindex sharing store items across machines +@cindex transferring store items across machines +The @command{guix copy} command copies items from the store of one +machine to that of another machine over a secure shell (SSH) +connection@footnote{This command is available only when Guile-SSH was +found. @xref{Requirements}, for details.}. For example, the following +command copies the @code{coreutils} package, the user's profile, and all +their dependencies over to @var{host}, logged in as @var{user}: + +@example +guix copy --to=@var{user}@@@var{host} \ + coreutils `readlink -f ~/.guix-profile` +@end example + +If some of the items to be copied are already present on @var{host}, +they are not actually sent. + +The command below retrieves @code{libreoffice} and @code{gimp} from +@var{host}, assuming they are available there: + +@example +guix copy --from=@var{host} libreoffice gimp +@end example + +The SSH connection is established using the Guile-SSH client, which is +compatible with OpenSSH: it honors @file{~/.ssh/known_hosts} and +@file{~/.ssh/config}, and uses the SSH agent for authentication. + +The key used to sign items that are sent must be accepted by the remote +machine. Likewise, the key used by the remote machine to sign items you +are retrieving must be in @file{/etc/guix/acl} so it is accepted by your +own daemon. @xref{Invoking guix archive}, for more information about +store item authentication. + +The general syntax is: + +@example +guix copy [--to=@var{spec}|--from=@var{spec}] @var{items}@dots{} +@end example + +You must always specify one of the following options: + +@table @code +@item --to=@var{spec} +@itemx --from=@var{spec} +Specify the host to send to or receive from. @var{spec} must be an SSH +spec such as @code{example.org}, @code{charlie@@example.org}, or +@code{charlie@@example.org:2222}. +@end table + +The @var{items} can be either package names, such as @code{gimp}, or +store items, such as @file{/gnu/store/@dots{}-idutils-4.6}. + +When specifying the name of a package to send, it is first built if +needed, unless @option{--dry-run} was specified. Common build options +are supported (@pxref{Common Build Options}). + @node Invoking guix container @section Invoking @command{guix container} diff --git a/guix/scripts/archive.scm b/guix/scripts/archive.scm index 400353247c..7e432351ed 100644 --- a/guix/scripts/archive.scm +++ b/guix/scripts/archive.scm @@ -41,7 +41,8 @@ #:use-module (srfi srfi-26) #:use-module (srfi srfi-37) #:use-module (ice-9 binary-ports) - #:export (guix-archive)) + #:export (guix-archive + options->derivations+files)) ;;; diff --git a/guix/scripts/copy.scm b/guix/scripts/copy.scm new file mode 100644 index 0000000000..9ae204e6c6 --- /dev/null +++ b/guix/scripts/copy.scm @@ -0,0 +1,207 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2016 Ludovic Courtès +;;; +;;; 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 . + +(define-module (guix scripts copy) + #:use-module (guix ui) + #:use-module (guix scripts) + #:use-module (guix ssh) + #:use-module (guix store) + #:use-module (guix utils) + #:use-module (guix derivations) + #:use-module (guix scripts build) + #:use-module ((guix scripts archive) #:select (options->derivations+files)) + #:use-module (ssh session) + #:use-module (ssh auth) + #:use-module (ssh key) + #: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-copy)) + + +;;; +;;; Exchanging store items over SSH. +;;; + +(define %compression + "zlib@openssh.com,zlib") + +(define* (open-ssh-session host #:key user port) + "Open an SSH session for HOST and return it. When USER and PORT are #f, use +default values or whatever '~/.ssh/config' specifies; otherwise use them. +Throw an error on failure." + (let ((session (make-session #:user user + #:host host + #:port port + #:timeout 10 ;seconds + ;; #:log-verbosity 'protocol + + ;; We need lightweight compression when + ;; exchanging full archives. + #:compression %compression + #:compression-level 3))) + + ;; Honor ~/.ssh/config. + (session-parse-config! session) + + (match (connect! session) + ('ok + ;; Let the SSH agent authenticate us to the server. + (match (userauth-agent! session) + ('success + session) + (x + (disconnect! session) + (leave (_ "SSH authentication failed for '~a': ~a~%") + host (get-error session))))) + (x + ;; Connection failed or timeout expired. + (leave (_ "SSH connection to '~a' failed: ~a~%") + host (get-error session)))))) + +(define (ssh-spec->user+host+port spec) + "Parse SPEC, a string like \"user@host:port\" or just \"host\", and return +three values: the user name (or #f), the host name, and the TCP port +number (or #f) corresponding to SPEC." + (define tokens + (char-set #\@ #\:)) + + (match (string-tokenize spec (char-set-complement tokens)) + ((host) + (values #f host #f)) + ((left right) + (if (string-index spec #\@) + (values left right #f) + (values #f left (string->number right)))) + ((user host port) + (match (string->number port) + ((? integer? port) + (values user host port)) + (x + (leave (_ "~a: invalid TCP port number~%") port)))) + (x + (leave (_ "~a: invalid SSH specification~%") spec)))) + +(define (send-to-remote-host target opts) + "Send ITEMS to TARGET. ITEMS is a list of store items or package names; for ; +package names, build the underlying packages before sending them." + (with-store local + (set-build-options-from-command-line local opts) + (let-values (((user host port) + (ssh-spec->user+host+port target)) + ((drv items) + (options->derivations+files local opts))) + (show-what-to-build local drv + #:use-substitutes? (assoc-ref opts 'substitutes?) + #:dry-run? (assoc-ref opts 'dry-run?)) + + (and (or (assoc-ref opts 'dry-run?) + (build-derivations local drv)) + (let* ((session (open-ssh-session host #:user user #:port port)) + (sent (send-files local items + (connect-to-remote-daemon session) + #:recursive? #t))) + (format #t "~{~a~%~}" sent) + sent))))) + +(define (retrieve-from-remote-host source opts) + "Retrieve ITEMS from SOURCE." + (with-store local + (let*-values (((user host port) + (ssh-spec->user+host+port source)) + ((session) + (open-ssh-session host #:user user #:port port)) + ((remote) + (connect-to-remote-daemon session))) + (set-build-options-from-command-line local opts) + ;; TODO: Here we could to compute and build the derivations on REMOTE + ;; rather than on LOCAL (one-off offloading) but that is currently too + ;; slow due to the many RPC round trips. So we just assume that REMOTE + ;; contains ITEMS. + (let*-values (((drv items) + (options->derivations+files local opts)) + ((retrieved) + (retrieve-files local items remote #:recursive? #t))) + (format #t "~{~a~%~}" retrieved) + retrieved)))) + + +;;; +;;; Options. +;;; + +(define (show-help) + (display (_ "Usage: guix copy [OPTION]... ITEMS... +Copy ITEMS to or from the specified host over SSH.\n")) + (display (_ " + --to=HOST send ITEMS to HOST")) + (display (_ " + --from=HOST receive ITEMS from HOST")) + (newline) + (show-build-options-help) + (newline) + (display (_ " + -h, --help display this help and exit")) + (display (_ " + -V, --version display version information and exit")) + (newline) + (show-bug-report-information)) + +(define %options + ;; Specifications of the command-line options. + (cons* (option '("to") #t #f + (lambda (opt name arg result) + (alist-cons 'destination arg result))) + (option '("from") #t #f + (lambda (opt name arg result) + (alist-cons 'source arg result))) + (option '(#\h "help") #f #f + (lambda args + (show-help) + (exit 0))) + (option '(#\V "version") #f #f + (lambda args + (show-version-and-exit "guix copy"))) + (option '(#\s "system") #t #f + (lambda (opt name arg result) + (alist-cons 'system arg + (alist-delete 'system result eq?)))) + %standard-build-options)) + +(define %default-options + `((system . ,(%current-system)) + (substitutes? . #t) + (graft? . #t) + (max-silent-time . 3600) + (verbosity . 0))) + + +;;; +;;; Entry point. +;;; + +(define (guix-copy . args) + (with-error-handling + (let* ((opts (parse-command-line args %options (list %default-options))) + (source (assoc-ref opts 'source)) + (target (assoc-ref opts 'destination))) + (cond (target (send-to-remote-host target opts)) + (source (retrieve-from-remote-host source opts)) + (else (leave (_ "use '--to' or '--from'~%"))))))) diff --git a/po/guix/POTFILES.in b/po/guix/POTFILES.in index 27cc64929d..0a2eee8170 100644 --- a/po/guix/POTFILES.in +++ b/po/guix/POTFILES.in @@ -24,6 +24,7 @@ guix/scripts/edit.scm guix/scripts/size.scm guix/scripts/graph.scm guix/scripts/challenge.scm +guix/scripts/copy.scm guix/gnu-maintenance.scm guix/scripts/container.scm guix/scripts/container/exec.scm -- cgit v1.2.3 From dc77498c1c6e3aaef9aae1cac189e6d9db470952 Mon Sep 17 00:00:00 2001 From: David Craven Date: Sun, 1 Jan 2017 16:11:55 +0100 Subject: build-system: cargo: Make Cargo.toml writeable. * guix/build/cargo-build-system.scm (configure): Make sure Cargo.toml is writeable before attempting modification. Problem reported by Danny Milosavljevic . --- guix/build/cargo-build-system.scm | 2 ++ 1 file changed, 2 insertions(+) (limited to 'guix') diff --git a/guix/build/cargo-build-system.scm b/guix/build/cargo-build-system.scm index 460d829b3c..4fa29b4cd3 100644 --- a/guix/build/cargo-build-system.scm +++ b/guix/build/cargo-build-system.scm @@ -43,6 +43,8 @@ (define* (configure #:key inputs #:allow-other-keys) "Replace Cargo.toml [dependencies] section with guix inputs." + ;; Make sure Cargo.toml is writeable when the crate uses git-fetch. + (chmod "Cargo.toml" #o644) (let ((port (open-file "Cargo.toml" "a" #:encoding "utf-8"))) (format port "~%[replace]~%") (for-each -- cgit v1.2.3 From f53a5514e0e9535d2e7c668803e64b4aac17da2b Mon Sep 17 00:00:00 2001 From: David Craven Date: Sun, 1 Jan 2017 16:14:45 +0100 Subject: import: crate: Provide a default home-page value. * guix/import/crate.scm (make-crate-sexp): Provide a default home-page value. * tests/crate.scm (test-crate): Add repository field. Problem reported by ng0 . --- guix/import/crate.scm | 12 +++++++++--- tests/crate.scm | 1 + 2 files changed, 10 insertions(+), 3 deletions(-) (limited to 'guix') diff --git a/guix/import/crate.scm b/guix/import/crate.scm index 3a19fc70cf..33cc6104c5 100644 --- a/guix/import/crate.scm +++ b/guix/import/crate.scm @@ -55,7 +55,8 @@ (crate (assoc-ref crate-json "crate")) (name (assoc-ref crate "name")) (version (assoc-ref crate "max_version")) - (home-page (assoc-ref crate "homepage")) + (homepage (assoc-ref crate "homepage")) + (repository (assoc-ref crate "repository")) (synopsis (assoc-ref crate "description")) (description (assoc-ref crate "description")) (license (string->license (assoc-ref crate "license"))) @@ -67,7 +68,10 @@ (filter (lambda (dep) (not ((crate-kind-predicate "normal") dep))) deps)) (inputs (crates->inputs input-crates)) - (native-inputs (crates->inputs native-input-crates))) + (native-inputs (crates->inputs native-input-crates)) + (home-page (match homepage + (() repository) + (_ homepage)))) (callback #:name name #:version version #:inputs inputs #:native-inputs native-inputs #:home-page home-page #:synopsis synopsis @@ -95,7 +99,9 @@ VERSION, INPUTS, NATIVE-INPUTS, HOME-PAGE, SYNOPSIS, DESCRIPTION, and LICENSE." (build-system cargo-build-system) ,@(maybe-native-inputs native-inputs) ,@(maybe-inputs inputs) - (home-page ,home-page) + (home-page ,(match home-page + (() "") + (_ home-page))) (synopsis ,synopsis) (description ,(beautify-description description)) (license ,(match license diff --git a/tests/crate.scm b/tests/crate.scm index 18d5f72a8c..6f6fc2bc29 100644 --- a/tests/crate.scm +++ b/tests/crate.scm @@ -35,6 +35,7 @@ \"license\": \"MIT/Apache-2.0\", \"description\": \"summary\", \"homepage\": \"http://example.com\", + \"repository\": \"http://example.com\", } }") -- cgit v1.2.3 From f1d136957d0d5634e60e5389a046a917169cdb9e Mon Sep 17 00:00:00 2001 From: David Craven Date: Thu, 29 Dec 2016 16:29:24 +0100 Subject: build-system: cargo: Handle Cargo.lock file not present. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * guix/build-system/cargo.scm (cargo-build): Add src output. (private-keywords): Add #:outputs. * guix/build/cargo-build-system.scm (configure): Use /share/rust-source when replacing inputs. (build, check): Don't do anything when there isn't a Cargo.lock file present. (install): Install sources to src output. When a Cargo.lock file is present use cargo install to install binaries to out. * guix/import/crate.scm (make-crate-sexp): Importer uses the src output for crate inputs by default. * guix/import/utils.scm (package-names->package-inputs, maybe-inputs, maybe-native-inputs): Take an optional output argument. * tests/crate.scm (crate->guix-package test): Update. Problem reported by Francisco Gómez García . --- guix/build-system/cargo.scm | 4 ++-- guix/build/cargo-build-system.scm | 20 ++++++++++++-------- guix/import/crate.scm | 4 ++-- guix/import/utils.scm | 14 ++++++++------ tests/crate.scm | 2 +- 5 files changed, 25 insertions(+), 19 deletions(-) (limited to 'guix') diff --git a/guix/build-system/cargo.scm b/guix/build-system/cargo.scm index 8d835dda1d..ffc0afda3b 100644 --- a/guix/build-system/cargo.scm +++ b/guix/build-system/cargo.scm @@ -109,7 +109,7 @@ to NAME and VERSION." #:inputs inputs #:system system #:modules imported-modules - #:outputs outputs + #:outputs (cons "src" outputs) #:guile-for-build guile-for-build)) (define* (lower name @@ -121,7 +121,7 @@ to NAME and VERSION." "Return a bag for NAME." (define private-keywords - '(#:source #:target #:cargo #:rustc #:inputs #:native-inputs)) + '(#:source #:target #:cargo #:rustc #:inputs #:native-inputs #:outputs)) (and (not target) ;; TODO: support cross-compilation (bag diff --git a/guix/build/cargo-build-system.scm b/guix/build/cargo-build-system.scm index 4fa29b4cd3..7d656a8d58 100644 --- a/guix/build/cargo-build-system.scm +++ b/guix/build/cargo-build-system.scm @@ -54,7 +54,7 @@ (when (and crate path) (match (string-split (basename path) #\-) ((_ ... version) - (format port "\"~a:~a\" = { path = \"~a/rustsrc\" }~%" + (format port "\"~a:~a\" = { path = \"~a/share/rust-source\" }~%" crate version path))))))) inputs) (close-port port)) @@ -63,19 +63,22 @@ (define* (build #:key (cargo-build-flags '("--release" "--frozen")) #:allow-other-keys) "Build a given Cargo package." - (zero? (apply system* `("cargo" "build" ,@cargo-build-flags)))) + (if (file-exists? "Cargo.lock") + (zero? (apply system* `("cargo" "build" ,@cargo-build-flags))) + #t)) (define* (check #:key tests? #:allow-other-keys) "Run tests for a given Cargo package." - (when tests? - (zero? (system* "cargo" "test")))) + (if (and tests? (file-exists? "Cargo.lock")) + (zero? (system* "cargo" "test")) + #t)) (define* (install #:key inputs outputs #:allow-other-keys) "Install a given Cargo package." (let* ((out (assoc-ref outputs "out")) (src (assoc-ref inputs "source")) - (bin (string-append out "/bin")) - (rsrc (string-append out "/rustsrc"))) + (rsrc (string-append (assoc-ref outputs "src") + "/share/rust-source"))) (mkdir-p rsrc) ;; Rust doesn't have a stable ABI yet. Because of this ;; Cargo doesn't have a search path for binaries yet. @@ -87,8 +90,9 @@ ;; When the package includes executables we install ;; it using cargo install. This fails when the crate ;; doesn't contain an executable. - (system* "cargo" "install" "--root" bin) - #t)) + (if (file-exists? "Cargo.lock") + (system* "cargo" "install" "--root" out) + (mkdir out)))) (define %standard-phases ;; 'configure' phase is not needed. diff --git a/guix/import/crate.scm b/guix/import/crate.scm index 33cc6104c5..233a20e983 100644 --- a/guix/import/crate.scm +++ b/guix/import/crate.scm @@ -97,8 +97,8 @@ VERSION, INPUTS, NATIVE-INPUTS, HOME-PAGE, SYNOPSIS, DESCRIPTION, and LICENSE." (base32 ,(bytevector->nix-base32-string (port-sha256 port)))))) (build-system cargo-build-system) - ,@(maybe-native-inputs native-inputs) - ,@(maybe-inputs inputs) + ,@(maybe-native-inputs native-inputs "src") + ,@(maybe-inputs inputs "src") (home-page ,(match home-page (() "") (_ home-page))) diff --git a/guix/import/utils.scm b/guix/import/utils.scm index f304da20e6..be1980d08f 100644 --- a/guix/import/utils.scm +++ b/guix/import/utils.scm @@ -211,24 +211,26 @@ into a proper sentence and by using two spaces between sentences." (regexp-substitute/global #f "\\. \\b" cleaned 'pre ". " 'post))) -(define (package-names->package-inputs names) +(define* (package-names->package-inputs names #:optional (output #f)) (map (lambda (input) - (list input (list 'unquote (string->symbol input)))) + (cons* input (list 'unquote (string->symbol input)) + (or (and output (list output)) + '()))) names)) -(define (maybe-inputs package-names) +(define* (maybe-inputs package-names #:optional (output #f)) "Given a list of PACKAGE-NAMES, tries to generate the 'inputs' field of a package definition." - (match (package-names->package-inputs package-names) + (match (package-names->package-inputs package-names output) (() '()) ((package-inputs ...) `((inputs (,'quasiquote ,package-inputs)))))) -(define (maybe-native-inputs package-names) +(define* (maybe-native-inputs package-names #:optional (output #f)) "Given a list of PACKAGE-NAMES, tries to generate the 'inputs' field of a package definition." - (match (package-names->package-inputs package-names) + (match (package-names->package-inputs package-names output) (() '()) ((package-inputs ...) diff --git a/tests/crate.scm b/tests/crate.scm index 6f6fc2bc29..0bb344bb8a 100644 --- a/tests/crate.scm +++ b/tests/crate.scm @@ -91,7 +91,7 @@ ('build-system 'cargo-build-system) ('inputs ('quasiquote - (("rust-bar" ('unquote 'rust-bar))))) + (("rust-bar" ('unquote 'rust-bar) "src")))) ('home-page "http://example.com") ('synopsis "summary") ('description "summary") -- cgit v1.2.3 From 4a6e889feba7bb382bb9e356d15e8260a256ced4 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Sun, 1 Jan 2017 18:17:29 +0100 Subject: gexp: Properly report substitution errors. * guix/gexp.scm (gexp)[substitute-ungexp]: Wrap body in 'with-syntax' and pass EXP as the last argument to 'syntax-error'. [substitute-ungexp-splicing]: Pass EXP as the last argument to 'syntax-error'. --- guix/gexp.scm | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'guix') diff --git a/guix/gexp.scm b/guix/gexp.scm index 6f63afe6a6..79a7b18b09 100644 --- a/guix/gexp.scm +++ b/guix/gexp.scm @@ -1,5 +1,5 @@ ;;; GNU Guix --- Functional package management for GNU -;;; Copyright © 2014, 2015, 2016 Ludovic Courtès +;;; Copyright © 2014, 2015, 2016, 2017 Ludovic Courtès ;;; ;;; This file is part of GNU Guix. ;;; @@ -846,9 +846,9 @@ environment." (match (assoc exp substs) ((_ id) id) - (_ - #'(syntax-error "error: no 'ungexp' substitution" - #'ref)))) + (_ ;internal error + (with-syntax ((exp exp)) + #'(syntax-error "error: no 'ungexp' substitution" exp))))) (define (substitute-ungexp-splicing exp substs) (syntax-case exp () @@ -860,7 +860,7 @@ environment." #,(substitute-references #'(rest ...) substs)))) (_ #'(syntax-error "error: no 'ungexp-splicing' substitution" - #'ref)))))) + exp)))))) (define (substitute-references exp substs) ;; Return a variant of EXP where all the cars of SUBSTS have been -- cgit v1.2.3 From 5e2e4a51f93f98c35824e4a7f5a88274d1551b4c Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Sun, 1 Jan 2017 22:22:14 +0100 Subject: gexp: Support 'ungexp' forms in improper lists. * guix/gexp.scm (gexp)[collect-escapes, substitute-references]: Replace the (exp0 exp ...) patterns with (exp0 . exp) to match improper lists. Adjust clause bodies accordingly. * tests/gexp.scm ("one input package, dotted list"): New test. --- guix/gexp.scm | 8 ++++---- tests/gexp.scm | 12 +++++++++++- 2 files changed, 15 insertions(+), 5 deletions(-) (limited to 'guix') diff --git a/guix/gexp.scm b/guix/gexp.scm index 79a7b18b09..1f7fbef0a0 100644 --- a/guix/gexp.scm +++ b/guix/gexp.scm @@ -812,9 +812,9 @@ environment." (cons exp result)) ((ungexp-native-splicing _ ...) (cons exp result)) - ((exp0 exp ...) + ((exp0 . exp) (let ((result (loop #'exp0 result))) - (fold loop result #'(exp ...)))) + (loop #'exp result))) (_ result)))) @@ -875,9 +875,9 @@ environment." (substitute-ungexp-splicing exp substs)) (((ungexp-native-splicing _ ...) rest ...) (substitute-ungexp-splicing exp substs)) - ((exp0 exp ...) + ((exp0 . exp) #`(cons #,(substitute-references #'exp0 substs) - #,(substitute-references #'(exp ...) substs))) + #,(substitute-references #'exp substs))) (x #''x))) (syntax-case s (ungexp output) diff --git a/tests/gexp.scm b/tests/gexp.scm index 797d5fa457..baf78837ae 100644 --- a/tests/gexp.scm +++ b/tests/gexp.scm @@ -1,5 +1,5 @@ ;;; GNU Guix --- Functional package management for GNU -;;; Copyright © 2014, 2015, 2016 Ludovic Courtès +;;; Copyright © 2014, 2015, 2016, 2017 Ludovic Courtès ;;; ;;; This file is part of GNU Guix. ;;; @@ -92,6 +92,16 @@ (package-derivation %store coreutils))) (gexp->sexp* exp))))) +(test-assert "one input package, dotted list" + (let ((exp (gexp (coreutils . (ungexp coreutils))))) + (and (gexp? exp) + (match (gexp-inputs exp) + (((p "out")) + (eq? p coreutils))) + (equal? `(coreutils . ,(derivation->output-path + (package-derivation %store coreutils))) + (gexp->sexp* exp))))) + (test-assert "one input origin" (let ((exp (gexp (display (ungexp (package-source coreutils)))))) (and (gexp? exp) -- cgit v1.2.3 From fef494d27d52149812365e59d34e1ac3a97396c0 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Sun, 1 Jan 2017 22:37:14 +0100 Subject: gnu-maintenance: Update ViewVC URLs. * guix/gnu-maintenance.scm (%gnumaint-base-url): Change "/gnumaint" to "/womb/gnumaint". (%package-list-url, %package-description-url): Adjust accordingly. --- guix/gnu-maintenance.scm | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'guix') diff --git a/guix/gnu-maintenance.scm b/guix/gnu-maintenance.scm index 4d4bb452be..789724c8c0 100644 --- a/guix/gnu-maintenance.scm +++ b/guix/gnu-maintenance.scm @@ -1,5 +1,5 @@ ;;; GNU Guix --- Functional package management for GNU -;;; Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016 Ludovic Courtès +;;; Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017 Ludovic Courtès ;;; Copyright © 2012, 2013 Nikita Karetnikov ;;; ;;; This file is part of GNU Guix. @@ -76,17 +76,17 @@ ;;; (define %gnumaint-base-url - "http://cvs.savannah.gnu.org/viewvc/*checkout*/gnumaint/") + "http://cvs.savannah.gnu.org/viewvc/*checkout*/womb/gnumaint/") (define %package-list-url (string->uri - (string-append %gnumaint-base-url "gnupackages.txt?root=womb"))) + (string-append %gnumaint-base-url "gnupackages.txt"))) (define %package-description-url ;; This file contains package descriptions in recutils format. ;; See . (string->uri - (string-append %gnumaint-base-url "pkgblurbs.txt?root=womb"))) + (string-append %gnumaint-base-url "pkgblurbs.txt"))) (define-record-type* gnu-package-descriptor -- cgit v1.2.3 From a6e0ae4046911023044563bafd0aa8b662bb1de9 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Sun, 1 Jan 2017 23:38:07 +0100 Subject: syscalls: 'terminal-columns' swallows ENOSYS. * guix/build/syscalls.scm (terminal-columns): Catch ENOSYS. --- guix/build/syscalls.scm | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'guix') diff --git a/guix/build/syscalls.scm b/guix/build/syscalls.scm index 9386c0f5d0..2e37846ff0 100644 --- a/guix/build/syscalls.scm +++ b/guix/build/syscalls.scm @@ -1,5 +1,5 @@ ;;; GNU Guix --- Functional package management for GNU -;;; Copyright © 2014, 2015, 2016 Ludovic Courtès +;;; Copyright © 2014, 2015, 2016, 2017 Ludovic Courtès ;;; Copyright © 2015 David Thompson ;;; Copyright © 2015 Mark H Weaver ;;; @@ -1474,7 +1474,9 @@ always a positive integer." ;; ENOTTY is what we're after but 2012-and-earlier Linux versions ;; would return EINVAL instead in some cases: ;; . - (if (or (= errno ENOTTY) (= errno EINVAL)) + ;; Furthermore, some FUSE file systems like unionfs return ENOSYS for + ;; that ioctl. + (if (memv errno (list ENOTTY EINVAL ENOSYS)) (fall-back) (apply throw args)))))) -- cgit v1.2.3 From 74d862e8a24c06fc07d83d94b482613d1e47e37f Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Sun, 1 Jan 2017 23:38:52 +0100 Subject: ui: It's 2017 now! * guix/ui.scm (show-version-and-exit): Change year to 2017. --- guix/ui.scm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'guix') diff --git a/guix/ui.scm b/guix/ui.scm index cafb3c6705..dba41a0971 100644 --- a/guix/ui.scm +++ b/guix/ui.scm @@ -1,5 +1,5 @@ ;;; GNU Guix --- Functional package management for GNU -;;; Copyright © 2012, 2013, 2014, 2015, 2016 Ludovic Courtès +;;; Copyright © 2012, 2013, 2014, 2015, 2016, 2017 Ludovic Courtès ;;; Copyright © 2013 Mark H Weaver ;;; Copyright © 2013 Nikita Karetnikov ;;; Copyright © 2014 Cyril Roelandt @@ -306,7 +306,7 @@ exiting. ARGS is the list of arguments received by the 'throw' handler." "Display version information for COMMAND and `(exit 0)'." (simple-format #t "~a (~a) ~a~%" command %guix-package-name %guix-version) - (display (_ "Copyright (C) 2016 the Guix authors + (display (_ "Copyright (C) 2017 the Guix authors License GPLv3+: GNU GPL version 3 or later This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. -- cgit v1.2.3 From eeb05e87d3d424b2f80b7e1ecb945c088ac6e201 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Mon, 2 Jan 2017 15:23:21 +0100 Subject: guix hash: '-x' excludes '.git' even when it is a regular file. * guix/scripts/hash.scm (guix-hash)[vcs-file?]: Add case for '.git' regular files. --- guix/scripts/hash.scm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'guix') diff --git a/guix/scripts/hash.scm b/guix/scripts/hash.scm index a339a8556b..640b2417d2 100644 --- a/guix/scripts/hash.scm +++ b/guix/scripts/hash.scm @@ -1,5 +1,5 @@ ;;; GNU Guix --- Functional package management for GNU -;;; Copyright © 2012, 2013, 2014, 2016 Ludovic Courtès +;;; Copyright © 2012, 2013, 2014, 2016, 2017 Ludovic Courtès ;;; Copyright © 2013 Nikita Karetnikov ;;; Copyright © 2016 Jan Nieuwenhuizen ;;; @@ -116,6 +116,9 @@ and 'hexadecimal' can be used as well).\n")) (case (stat:type stat) ((directory) (member (basename file) '(".bzr" ".git" ".hg" ".svn" "CVS"))) + ((regular) + ;; Git sub-modules have a '.git' file that is a regular text file. + (string=? (basename file) ".git")) (else #f))) -- cgit v1.2.3 From d925cdc35f1ffe79f6e17b2284fa20ca3082b0fe Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Mon, 2 Jan 2017 23:13:46 +0100 Subject: ui: Keep the word "Copyright" untranslated. Suggested by John Darrington . * guix/ui.scm (show-version-and-exit): Make "Copyright (C) 2017" untranslatable, except "(C)" itself, like Gnulib's version-etc does. --- guix/ui.scm | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'guix') diff --git a/guix/ui.scm b/guix/ui.scm index dba41a0971..7d4c437354 100644 --- a/guix/ui.scm +++ b/guix/ui.scm @@ -306,7 +306,13 @@ exiting. ARGS is the list of arguments received by the 'throw' handler." "Display version information for COMMAND and `(exit 0)'." (simple-format #t "~a (~a) ~a~%" command %guix-package-name %guix-version) - (display (_ "Copyright (C) 2017 the Guix authors + (format #t "Copyright ~a 2017 ~a" + ;; TRANSLATORS: Translate "(C)" to the copyright symbol + ;; (C-in-a-circle), if this symbol is available in the user's + ;; locale. Otherwise, do not translate "(C)"; leave it as-is. */ + (_ "(C)") + (_ "the Guix authors\n")) + (display (_"\ License GPLv3+: GNU GPL version 3 or later This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. -- cgit v1.2.3 From 484437bd438ccaf7efa673edc26aee80d2fc7bf0 Mon Sep 17 00:00:00 2001 From: Manolis Ragkousis Date: Mon, 2 Jan 2017 14:05:31 +0200 Subject: guix: build: make-bootstrap: Copy libpthread_nonshared.a to the new system. * guix/build/make-bootstrap.scm (%libc-object-files-rx): Update regexp. --- guix/build/make-bootstrap.scm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'guix') diff --git a/guix/build/make-bootstrap.scm b/guix/build/make-bootstrap.scm index bc4c0e3d5f..21c78cc8f5 100644 --- a/guix/build/make-bootstrap.scm +++ b/guix/build/make-bootstrap.scm @@ -1,5 +1,5 @@ ;;; GNU Guix --- Functional package management for GNU -;;; Copyright © 2015 Manolis Fragkiskos Ragkousis +;;; Copyright © 2015, 2017 Manolis Fragkiskos Ragkousis ;;; Copyright © 2015 Ludovic Courtès ;;; ;;; This file is part of GNU Guix. @@ -64,7 +64,8 @@ when producing a bootstrap libc." #t)) (define %libc-object-files-rx "^(crt.*|ld.*|lib(c|m|dl|rt|pthread|nsl|\ -util).*\\.so(\\..*)?|lib(machuser|hurduser).so.*|libc(rt|)_nonshared\\.a)$") +util).*\\.so(\\..*)?|lib(machuser|hurduser).so.*|(libc(rt|)|libpthread)\ +_nonshared\\.a)$") (setvbuf (current-output-port) _IOLBF) (let* ((libdir (string-append output "/lib"))) -- cgit v1.2.3 From 3b7ccbe94c3e15a04f1809c5acc894799426235e Mon Sep 17 00:00:00 2001 From: David Craven Date: Thu, 29 Dec 2016 16:28:47 +0100 Subject: build-system: cargo: Use correct cargo. * gnu/packages/rust.scm (cargo-bootstrap): Make private. * guix/build-system/cargo.scm (default-cargo): Use cargo. --- gnu/packages/rust.scm | 4 +--- guix/build-system/cargo.scm | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) (limited to 'guix') diff --git a/gnu/packages/rust.scm b/gnu/packages/rust.scm index 08122923b6..97d0d7cb0d 100644 --- a/gnu/packages/rust.scm +++ b/gnu/packages/rust.scm @@ -122,9 +122,7 @@ which can in turn be used to build the final Rust compiler.") (license license:asl2.0))) -;; FIXME: Make private once cargo is packaged. Is currently used by the -;; cargo-build-system. -(define-public cargo-bootstrap +(define cargo-bootstrap (package (name "cargo-bootstrap") (version (cargo-version %rust-bootstrap-binaries-version)) diff --git a/guix/build-system/cargo.scm b/guix/build-system/cargo.scm index ffc0afda3b..3582f0e328 100644 --- a/guix/build-system/cargo.scm +++ b/guix/build-system/cargo.scm @@ -46,8 +46,7 @@ to NAME and VERSION." "Return the default Cargo package." ;; Lazily resolve the binding to avoid a circular dependency. (let ((rust (resolve-interface '(gnu packages rust)))) - ;; FIXME: Package cargo and replace cargo-bootstrap with cargo. - (module-ref rust 'cargo-bootstrap))) + (module-ref rust 'cargo))) (define (default-rustc) "Return the default Rustc package." -- cgit v1.2.3 From ee17a9e06e636400e3354796a42ac445dbcc8f96 Mon Sep 17 00:00:00 2001 From: David Craven Date: Mon, 2 Jan 2017 17:49:07 +0100 Subject: git-download: Add some helpers. * guix/git-download.scm (git-version, git-file-name): New variables. --- guix/git-download.scm | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'guix') diff --git a/guix/git-download.scm b/guix/git-download.scm index 81d1d4fbf6..fca44f552a 100644 --- a/guix/git-download.scm +++ b/guix/git-download.scm @@ -30,7 +30,9 @@ git-reference-commit git-reference-recursive? - git-fetch)) + git-fetch + git-version + git-file-name)) ;;; Commentary: ;;; @@ -110,4 +112,12 @@ HASH-ALGO (a symbol). Use NAME as the file name, or a generic name if #f." #:guile-for-build guile #:local-build? #t))) +(define (git-version version revision commit) + "Return the version string for packages using git-download." + (string-append version "-" revision "." (string-take commit 7))) + +(define (git-file-name name version) + "Return the file-name for packages using git-download." + (string-append name "-" version "-checkout")) + ;;; git-download.scm ends here -- cgit v1.2.3 From e6876cb9dc2c90c731abd8fef2c01c1a4ba8f59f Mon Sep 17 00:00:00 2001 From: Julien Lepiller Date: Thu, 22 Dec 2016 19:56:33 +0100 Subject: gnu: Add ocaml-build-system. * guix/build/ocaml-build-system.scm: New file. * guix/build-system/ocaml.scm: New file. * Makefile.am (MODULES): Add them. * gnu/packages/ocaml.scm (ocaml)[native-search-paths]: Adjuste OCAMLPATH. Signed-off-by: David Craven --- Makefile.am | 2 + gnu/packages/ocaml.scm | 2 +- guix/build-system/ocaml.scm | 181 ++++++++++++++++++++++++++++++++++++++ guix/build/ocaml-build-system.scm | 119 +++++++++++++++++++++++++ 4 files changed, 303 insertions(+), 1 deletion(-) create mode 100644 guix/build-system/ocaml.scm create mode 100644 guix/build/ocaml-build-system.scm (limited to 'guix') diff --git a/Makefile.am b/Makefile.am index fb08a004b6..1a66fff505 100644 --- a/Makefile.am +++ b/Makefile.am @@ -70,6 +70,7 @@ MODULES = \ guix/build-system/haskell.scm \ guix/build-system/perl.scm \ guix/build-system/python.scm \ + guix/build-system/ocaml.scm \ guix/build-system/waf.scm \ guix/build-system/r.scm \ guix/build-system/ruby.scm \ @@ -95,6 +96,7 @@ MODULES = \ guix/build/gnu-dist.scm \ guix/build/perl-build-system.scm \ guix/build/python-build-system.scm \ + guix/build/ocaml-build-system.scm \ guix/build/r-build-system.scm \ guix/build/ruby-build-system.scm \ guix/build/waf-build-system.scm \ diff --git a/gnu/packages/ocaml.scm b/gnu/packages/ocaml.scm index 35c782473c..88e95a8483 100644 --- a/gnu/packages/ocaml.scm +++ b/gnu/packages/ocaml.scm @@ -71,7 +71,7 @@ (native-search-paths (list (search-path-specification (variable "OCAMLPATH") - (files (list "lib/ocaml"))) + (files (list "lib/ocaml" "lib/ocaml/site-lib"))) (search-path-specification (variable "CAML_LD_LIBRARY_PATH") (files (list "lib/ocaml/site-lib/stubslibs"))))) diff --git a/guix/build-system/ocaml.scm b/guix/build-system/ocaml.scm new file mode 100644 index 0000000000..f4f57b5ad5 --- /dev/null +++ b/guix/build-system/ocaml.scm @@ -0,0 +1,181 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2016, 2017 Julien Lepiller +;;; +;;; 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 . + +(define-module (guix build-system ocaml) + #:use-module (guix store) + #:use-module (guix utils) + #:use-module (guix derivations) + #:use-module (guix search-paths) + #:use-module (guix build-system) + #:use-module (guix build-system gnu) + #:use-module (guix packages) + #:use-module (ice-9 match) + #:export (%ocaml-build-system-modules + ocaml-build + ocaml-build-system)) + +;; Commentary: +;; +;; Standard build procedure for packages using ocaml. This is implemented as an +;; extension of `gnu-build-system'. +;; +;; OCaml packages don't use a single standard for their build system. Some use +;; autotools, other use custom configure scripts with Makefiles, others use +;; oasis to generate the configure script and Makefile and lastly, some use +;; custom ocaml scripts. +;; +;; Each phase in the build system will try to figure out what the build system +;; is for that package. Most packages come with a custom configure script and +;; a Makefile that in turn call custom build tools. Packages built with oasis +;; will have a `setup.ml' file in the top directory, that can be used for all +;; phases. In that case the Makefile is here only to call that script. In case +;; the setup.ml do not work as expected, the @var{use-make} argument can be +;; used to ignore the setup.ml file and run make instead. +;; +;; Some packages use their own custom scripts, `pkg/pkg.ml' or +;; `pkg/build.ml'. They can be used here too. +;; +;; Code: + +(define %ocaml-build-system-modules + ;; Build-side modules imported by default. + `((guix build ocaml-build-system) + ,@%gnu-build-system-modules)) + +(define (default-ocaml) + "Return the default OCaml package." + + ;; Do not use `@' to avoid introducing circular dependencies. + (let ((module (resolve-interface '(gnu packages ocaml)))) + (module-ref module 'ocaml))) + +(define (default-findlib) + "Return the default OCaml-findlib package." + + ;; Do not use `@' to avoid introducing circular dependencies. + (let ((module (resolve-interface '(gnu packages ocaml)))) + (module-ref module 'ocaml-findlib))) + +(define* (lower name + #:key source inputs native-inputs outputs system target + (ocaml (default-ocaml)) + (findlib (default-findlib)) + #:allow-other-keys + #:rest arguments) + "Return a bag for NAME." + (define private-keywords + '(#:source #:target #:ocaml #:findlib #:inputs #:native-inputs)) + + (and (not target) ;XXX: no cross-compilation + (bag + (name name) + (system system) + (host-inputs `(,@(if source + `(("source" ,source)) + '()) + ,@inputs + + ;; Keep the standard inputs of 'gnu-build-system'. + ,@(standard-packages))) + (build-inputs `(("ocaml" ,ocaml) + ("findlib" ,findlib) + ,@native-inputs)) + (outputs outputs) + (build ocaml-build) + (arguments (strip-keyword-arguments private-keywords arguments))))) + +(define* (ocaml-build store name inputs + #:key (guile #f) + (outputs '("out")) (configure-flags ''()) + (search-paths '()) + (make-flags ''()) + (build-flags ''()) + (out-of-source? #t) + (use-make? #f) + (tests? #t) + (test-flags ''("--enable-tests")) + (test-target "test") + (install-target "install") + (validate-runpath? #t) + (patch-shebangs? #t) + (strip-binaries? #t) + (strip-flags ''("--strip-debug")) + (strip-directories ''("lib" "lib64" "libexec" + "bin" "sbin")) + (phases '(@ (guix build ocaml-build-system) + %standard-phases)) + (system (%current-system)) + (imported-modules %ocaml-build-system-modules) + (modules '((guix build ocaml-build-system) + (guix build utils)))) + "Build SOURCE using OCAML, and with INPUTS. This assumes that SOURCE +provides a 'setup.ml' file as its build system." + (define builder + `(begin + (use-modules ,@modules) + (ocaml-build #:source ,(match (assoc-ref inputs "source") + (((? derivation? source)) + (derivation->output-path source)) + ((source) + source) + (source + source)) + #:system ,system + #:outputs %outputs + #:inputs %build-inputs + #:search-paths ',(map search-path-specification->sexp + search-paths) + #:phases ,phases + #:configure-flags ,configure-flags + #:test-flags ,test-flags + #:make-flags ,make-flags + #:build-flags ,build-flags + #:out-of-source? ,out-of-source? + #:use-make? ,use-make? + #:tests? ,tests? + #:test-target ,test-target + #:install-target ,install-target + #:validate-runpath? ,validate-runpath? + #:patch-shebangs? ,patch-shebangs? + #:strip-binaries? ,strip-binaries? + #:strip-flags ,strip-flags + #:strip-directories ,strip-directories))) + + (define guile-for-build + (match guile + ((? package?) + (package-derivation store guile system #:graft? #f)) + (#f ; the default + (let* ((distro (resolve-interface '(gnu packages commencement))) + (guile (module-ref distro 'guile-final))) + (package-derivation store guile system #:graft? #f))))) + + (build-expression->derivation store name builder + #:system system + #:inputs inputs + #:modules imported-modules + #:outputs outputs + #:guile-for-build guile-for-build)) + +(define ocaml-build-system + (build-system + (name 'ocaml) + (description "The standard OCaml build system") + (lower lower))) + +;;; ocaml.scm ends here diff --git a/guix/build/ocaml-build-system.scm b/guix/build/ocaml-build-system.scm new file mode 100644 index 0000000000..f77251ca09 --- /dev/null +++ b/guix/build/ocaml-build-system.scm @@ -0,0 +1,119 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2016, 2017 Julien Lepiller +;;; +;;; 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 . + +(define-module (guix build ocaml-build-system) + #:use-module ((guix build gnu-build-system) #:prefix gnu:) + #:use-module (guix build utils) + #:use-module (ice-9 match) + #:export (%standard-phases + ocaml-build)) + +;; Commentary: +;; +;; Builder-side code of the standard ocaml build procedure. +;; +;; Code: + +(define* (ocaml-findlib-environment #:key outputs #:allow-other-keys) + (let* ((out (assoc-ref outputs "out"))) + (setenv "OCAMLFIND_DESTDIR" (string-append out "/lib/ocaml/site-lib")) + (setenv "OCAMLFIND_LDCONF" "ignore")) + #t) + +(define* (configure #:key outputs (configure-flags '()) + (test-flags '("--enable-tests")) tests? + #:allow-other-keys) + "Configure the given package." + (let* ((out (assoc-ref outputs "out"))) + (format #t "build directory: ~s~%" (getcwd)) + (if (file-exists? "setup.ml") + (let ((args `("-configure" + "--prefix" ,out + ,@(if tests? + test-flags + '()) + ,@configure-flags))) + (format #t "running 'setup.ml' with arguments ~s~%" args) + (zero? (apply system* "ocaml" "setup.ml" args))) + (let ((args `("-prefix" ,out ,@configure-flags))) + (format #t "running 'configure' with arguments ~s~%" args) + (zero? (apply system* "./configure" args)))))) + +(define* (build #:key inputs outputs (build-flags '()) (make-flags '()) + (use-make? #f) #:allow-other-keys) + "Build the given package." + (if (and (file-exists? "setup.ml") (not use-make?)) + (zero? (apply system* "ocaml" "setup.ml" "-build" build-flags)) + (if (file-exists? "Makefile") + (zero? (apply system* "make" make-flags)) + (let ((file (if (file-exists? "pkg/pkg.ml") "pkg/pkg.ml" "pkg/build.ml"))) + (zero? (apply system* "ocaml" "-I" + (string-append (assoc-ref inputs "findlib") + "/lib/ocaml/site-lib") + file build-flags)))))) + +(define* (check #:key inputs outputs (make-flags '()) (test-target "test") tests? + (use-make? #f) #:allow-other-keys) + "Install the given package." + (when tests? + (if (and (file-exists? "setup.ml") (not use-make?)) + (zero? (system* "ocaml" "setup.ml" (string-append "-" test-target))) + (if (file-exists? "Makefile") + (zero? (apply system* "make" test-target make-flags)) + (let ((file (if (file-exists? "pkg/pkg.ml") "pkg/pkg.ml" "pkg/build.ml"))) + (zero? (system* "ocaml" "-I" + (string-append (assoc-ref inputs "findlib") + "/lib/ocaml/site-lib") + file test-target))))))) + +(define* (install #:key outputs (build-flags '()) (make-flags '()) (use-make? #f) + (install-target "install") + #:allow-other-keys) + "Install the given package." + (let ((out (assoc-ref outputs "out"))) + (if (and (file-exists? "setup.ml") (not use-make?)) + (zero? (apply system* "ocaml" "setup.ml" + (string-append "-" install-target) build-flags)) + (if (file-exists? "Makefile") + (zero? (apply system* "make" install-target make-flags)) + (zero? (system* "opam-installer" "-i" (string-append "--prefix=" out) + (string-append "--libdir=" out "/lib/ocaml/site-lib"))))))) + +(define* (prepare-install #:key outputs #:allow-other-keys) + "Prepare for building the given package." + (mkdir-p (string-append (assoc-ref outputs "out") "/lib/ocaml/site-lib")) + (mkdir-p (string-append (assoc-ref outputs "out") "/bin"))) + +(define %standard-phases + ;; Everything is as with the GNU Build System except for the `configure' + ;; , `build', `check' and `install' phases. + (modify-phases gnu:%standard-phases + (add-before 'configure 'ocaml-findlib-environment + ocaml-findlib-environment) + (add-before 'install 'prepare-install prepare-install) + (replace 'configure configure) + (replace 'build build) + (replace 'check check) + (replace 'install install))) + +(define* (ocaml-build #:key inputs (phases %standard-phases) + #:allow-other-keys #:rest args) + "Build the given package, applying all of PHASES in order." + (apply gnu:gnu-build #:inputs inputs #:phases phases args)) + +;;; ocaml-build-system.scm ends here -- cgit v1.2.3 From d38bc9a9f6feefc465964531520fee5663a12f48 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Wed, 4 Jan 2017 10:43:08 +0100 Subject: grafts: Move caching to a new 'with-cache' macro. * guix/grafts.scm (with-cache): New macro. (cumulative-grafts)[return/cache]: Remove. Use 'with-cache' instead. --- guix/grafts.scm | 90 ++++++++++++++++++++++++++++++--------------------------- 1 file changed, 48 insertions(+), 42 deletions(-) (limited to 'guix') diff --git a/guix/grafts.scm b/guix/grafts.scm index dda7c1d235..2006d3908e 100644 --- a/guix/grafts.scm +++ b/guix/grafts.scm @@ -1,5 +1,5 @@ ;;; GNU Guix --- Functional package management for GNU -;;; Copyright © 2014, 2015, 2016 Ludovic Courtès +;;; Copyright © 2014, 2015, 2016, 2017 Ludovic Courtès ;;; ;;; This file is part of GNU Guix. ;;; @@ -214,6 +214,17 @@ available." (delete-duplicates (concatenate refs) string=?)) result)))))) +(define-syntax-rule (with-cache key exp ...) + "Cache the value of monadic expression EXP under KEY." + (mlet %state-monad ((cache (current-state))) + (match (vhash-assq key cache) + ((_ . result) ;cache hit + (return result)) + (#f ;cache miss + (mlet %state-monad ((result (begin exp ...))) + (set-current-state (vhash-consq key result cache)) + (return result)))))) + (define* (cumulative-grafts store drv grafts references #:key @@ -252,48 +263,39 @@ derivations to the corresponding set of grafts." #:system system)) (state-return grafts)))) - (define (return/cache cache value) - (mbegin %state-monad - (set-current-state (vhash-consq drv value cache)) - (return value))) - - (mlet %state-monad ((cache (current-state))) - (match (vhash-assq drv cache) - ((_ . grafts) ;hit + (with-cache drv + (match (non-self-references references drv outputs) + (() ;no dependencies (return grafts)) - (#f ;miss - (match (non-self-references references drv outputs) - (() ;no dependencies - (return/cache cache grafts)) - (deps ;one or more dependencies - (mlet %state-monad ((grafts (mapm %state-monad dependency-grafts deps))) - (let ((grafts (delete-duplicates (concatenate grafts) equal?))) - (match (filter (lambda (graft) - (member (graft-origin-file-name graft) deps)) - grafts) - (() - (return/cache cache grafts)) - ((applicable ..1) - ;; Use APPLICABLE, the subset of GRAFTS that is really - ;; applicable to DRV, to avoid creating several identical - ;; grafted variants of DRV. - (let* ((new (graft-derivation/shallow store drv applicable - #:guile guile - #:system system)) - - ;; Replace references to any of the outputs of DRV, - ;; even if that's more than needed. This is so that - ;; the result refers only to the outputs of NEW and - ;; not to those of DRV. - (grafts (append (map (lambda (output) - (graft - (origin drv) - (origin-output output) - (replacement new) - (replacement-output output))) - (derivation-output-names drv)) - grafts))) - (return/cache cache grafts)))))))))))) + (deps ;one or more dependencies + (mlet %state-monad ((grafts (mapm %state-monad dependency-grafts deps))) + (let ((grafts (delete-duplicates (concatenate grafts) equal?))) + (match (filter (lambda (graft) + (member (graft-origin-file-name graft) deps)) + grafts) + (() + (return grafts)) + ((applicable ..1) + ;; Use APPLICABLE, the subset of GRAFTS that is really + ;; applicable to DRV, to avoid creating several identical + ;; grafted variants of DRV. + (let* ((new (graft-derivation/shallow store drv applicable + #:guile guile + #:system system)) + + ;; Replace references to any of the outputs of DRV, + ;; even if that's more than needed. This is so that + ;; the result refers only to the outputs of NEW and + ;; not to those of DRV. + (grafts (append (map (lambda (output) + (graft + (origin drv) + (origin-output output) + (replacement new) + (replacement-output output))) + (derivation-output-names drv)) + grafts))) + (return grafts)))))))))) (define* (graft-derivation store drv grafts #:key (guile (%guile-for-build)) @@ -333,4 +335,8 @@ it otherwise. It returns the previous setting." (lambda (store) (values (%graft? enable?) store))) +;; Local Variables: +;; eval: (put 'with-cache 'scheme-indent-function 1) +;; End: + ;;; grafts.scm ends here -- cgit v1.2.3 From b96a0640a3ca128c0b9bf9acaef7b3b7a8bb1455 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Wed, 4 Jan 2017 16:16:17 +0100 Subject: graph: Add '%reverse-package-node-type'. * guix/scripts/graph.scm (%reverse-package-node-type): New variable. (%node-types): Add it. * tests/graph.scm ("reverse package DAG"): New test. * doc/guix.texi (Invoking guix refresh): Add cross-reference to "Invoking guix graph". (Invoking guix graph): Document 'reverse-package'. --- doc/guix.texi | 18 ++++++++++++++++++ guix/scripts/graph.scm | 21 +++++++++++++++++++++ tests/graph.scm | 14 +++++++++++++- 3 files changed, 52 insertions(+), 1 deletion(-) (limited to 'guix') diff --git a/doc/guix.texi b/doc/guix.texi index 3a9ebe8a63..adc7fefcae 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -5445,6 +5445,10 @@ end, display the fraction of packages covered by all these updaters. List top-level dependent packages that would need to be rebuilt as a result of upgrading one or more packages. +@xref{Invoking guix graph, the @code{reverse-package} type of +@command{guix graph}}, for information on how to visualize the list of +dependents of a package. + @end table Be aware that the @code{--list-dependent} option only @@ -5746,6 +5750,20 @@ This is the default type used in the example above. It shows the DAG of package objects, excluding implicit dependencies. It is concise, but filters out many details. +@item reverse-package +This shows the @emph{reverse} DAG of packages. For example: + +@example +guix graph --type=reverse-package ocaml +@end example + +... yields the graph of packages that depend on OCaml. + +Note that for core packages this can yield huge graphs. If all you want +is to know the number of packages that depend on a given package, use +@command{guix refresh --list-dependent} (@pxref{Invoking guix refresh, +@option{--list-dependent}}). + @item bag-emerged This is the package DAG, @emph{including} implicit inputs. diff --git a/guix/scripts/graph.scm b/guix/scripts/graph.scm index d96df5fbaf..79ce503a2e 100644 --- a/guix/scripts/graph.scm +++ b/guix/scripts/graph.scm @@ -37,6 +37,7 @@ #:use-module (srfi srfi-37) #:use-module (ice-9 match) #:export (%package-node-type + %reverse-package-node-type %bag-node-type %bag-with-origins-node-type %bag-emerged-node-type @@ -101,6 +102,25 @@ name." (label node-full-name) (edges (lift1 package-node-edges %store-monad)))) + +;;; +;;; Reverse package DAG. +;;; + +(define %reverse-package-node-type + ;; For this node type we first need to compute the list of packages and the + ;; list of back-edges. Since we want to do it only once, we use the + ;; promises below. + (let* ((packages (delay (fold-packages cons '()))) + (back-edges (delay (run-with-store #f ;store not actually needed + (node-back-edges %package-node-type + (force packages)))))) + (node-type + (inherit %package-node-type) + (name "reverse-package") + (description "the reverse DAG of packages") + (edges (lift1 (force back-edges) %store-monad))))) + ;;; ;;; Package DAG using bags. @@ -323,6 +343,7 @@ substitutes." (define %node-types ;; List of all the node types. (list %package-node-type + %reverse-package-node-type %bag-node-type %bag-with-origins-node-type %bag-emerged-node-type diff --git a/tests/graph.scm b/tests/graph.scm index bc4d62fe50..6431c482f7 100644 --- a/tests/graph.scm +++ b/tests/graph.scm @@ -1,5 +1,5 @@ ;;; GNU Guix --- Functional package management for GNU -;;; Copyright © 2015, 2016 Ludovic Courtès +;;; Copyright © 2015, 2016, 2017 Ludovic Courtès ;;; ;;; This file is part of GNU Guix. ;;; @@ -32,6 +32,7 @@ #:use-module (gnu packages) #:use-module (gnu packages base) #:use-module (gnu packages guile) + #:use-module (gnu packages libunistring) #:use-module (gnu packages bootstrap) #:use-module (ice-9 match) #:use-module (srfi srfi-1) @@ -92,6 +93,17 @@ edges." (list p3 p3 p2) (list p2 p1 p1)))))))) +(test-assert "reverse package DAG" + (let-values (((backend nodes+edges) (make-recording-backend))) + (run-with-store %store + (export-graph (list libunistring) 'port + #:node-type %reverse-package-node-type + #:backend backend)) + ;; We should see nothing more than these 3 packages. + (let-values (((nodes edges) (nodes+edges))) + (and (member (package->tuple guile-2.0) nodes) + (->bool (member (edge->tuple libunistring guile-2.0) edges)))))) + (test-assert "bag-emerged DAG" (let-values (((backend nodes+edges) (make-recording-backend))) (let* ((o (dummy-origin (method (lambda _ -- cgit v1.2.3 From 6d943e553d45280ec54f067a3e5e7f714fcd9ba1 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Thu, 5 Jan 2017 21:49:45 +0100 Subject: derivations: Micro-optimize 'write-derivation'. * guix/derivations.scm (write-derivation)[write-input]: Use 'display' instead of 'write' for PATH. Use 'simple-format' instead of 'format', and '~a' instead of '~s' for SYSTEM and BUILDER. --- guix/derivations.scm | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'guix') diff --git a/guix/derivations.scm b/guix/derivations.scm index 7ed9bd61d3..23ad58f914 100644 --- a/guix/derivations.scm +++ b/guix/derivations.scm @@ -1,5 +1,5 @@ ;;; GNU Guix --- Functional package management for GNU -;;; Copyright © 2012, 2013, 2014, 2015, 2016 Ludovic Courtès +;;; Copyright © 2012, 2013, 2014, 2015, 2016, 2017 Ludovic Courtès ;;; ;;; This file is part of GNU Guix. ;;; @@ -520,9 +520,9 @@ that form." (define (write-input input port) (match input (($ path sub-drvs) - (display "(" port) - (write path port) - (display "," port) + (display "(\"" port) + (display path port) + (display "\"," port) (write-string-list sub-drvs) (display ")" port)))) @@ -545,7 +545,7 @@ that form." (write-list inputs write-input port) (display "," port) (write-string-list sources) - (format port ",~s,~s," system builder) + (simple-format port ",\"~a\",\"~a\"," system builder) (write-string-list args) (display "," port) (write-list env-vars write-env-var port) -- cgit v1.2.3 From 76c31074c89239bda31b29e78e63e878b17a57f9 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Thu, 5 Jan 2017 23:40:59 +0100 Subject: derivations: Share a cache between 'derivation' and 'read-derivation'. This leads a 13% speedup on 'guix build libreoffice -d' and 18% on 'guix build gnome -d'. * guix/derivations.scm (%derivation-cache): New variable. (read-derivation): Use it instead of the private 'cache' variable. (derivation): Populate %DERIVATION-CACHE before returning. --- guix/derivations.scm | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) (limited to 'guix') diff --git a/guix/derivations.scm b/guix/derivations.scm index 23ad58f914..d5e4b5730b 100644 --- a/guix/derivations.scm +++ b/guix/derivations.scm @@ -453,19 +453,22 @@ one-argument procedure similar to that returned by 'substitution-oracle'." (loop (read drv-port) (cons (ununquote exp) result)))))) -(define read-derivation - (let ((cache (make-weak-value-hash-table 200))) - (lambda (drv-port) - "Read the derivation from DRV-PORT and return the corresponding +(define %derivation-cache + ;; Maps derivation file names to objects. + ;; XXX: This is redundant with 'atts-cache' in the store. + (make-weak-value-hash-table 200)) + +(define (read-derivation drv-port) + "Read the derivation from DRV-PORT and return the corresponding object." - ;; Memoize that operation because `%read-derivation' is quite expensive, - ;; and because the same argument is read more than 15 times on average - ;; during something like (package-derivation s gdb). - (let ((file (and=> (port-filename drv-port) basename))) - (or (and file (hash-ref cache file)) - (let ((drv (%read-derivation drv-port))) - (hash-set! cache file drv) - drv)))))) + ;; Memoize that operation because `%read-derivation' is quite expensive, + ;; and because the same argument is read more than 15 times on average + ;; during something like (package-derivation s gdb). + (let ((file (port-filename drv-port))) + (or (and file (hash-ref %derivation-cache file)) + (let ((drv (%read-derivation drv-port))) + (hash-set! %derivation-cache file drv) + drv)))) (define-inlinable (write-sequence lst write-item port) ;; Write each element of LST with WRITE-ITEM to PORT, separating them with a @@ -866,10 +869,12 @@ output should not be used." system builder args env-vars #f)) (drv (add-output-paths drv-masked))) - (let ((file (add-text-to-store store (string-append name ".drv") - (derivation->string drv) - (map derivation-input-path inputs)))) - (set-file-name drv file)))) + (let* ((file (add-text-to-store store (string-append name ".drv") + (derivation->string drv) + (map derivation-input-path inputs))) + (drv (set-file-name drv file))) + (hash-set! %derivation-cache file drv) + drv))) (define* (map-derivation store drv mapping #:key (system (%current-system))) -- cgit v1.2.3 From 03476a23ff2d4175b7d3c808726178f764359bec Mon Sep 17 00:00:00 2001 From: Ricardo Wurmus Date: Tue, 3 Jan 2017 16:20:15 +0100 Subject: guix: Add Docker image export. * guix/docker.scm: New file. * Makefile.am (MODULES): Register it. * guix/scripts/archive.scm (show-help, %options, guix-archive): Add support for "--format". * doc/guix.texi (Invoking guix archive): Document it. --- Makefile.am | 1 + doc/guix.texi | 18 ++++++- guix/docker.scm | 127 +++++++++++++++++++++++++++++++++++++++++++++++ guix/scripts/archive.scm | 17 ++++++- 4 files changed, 161 insertions(+), 2 deletions(-) create mode 100644 guix/docker.scm (limited to 'guix') diff --git a/Makefile.am b/Makefile.am index 1a66fff505..3e147df2e0 100644 --- a/Makefile.am +++ b/Makefile.am @@ -160,6 +160,7 @@ MODULES = \ if HAVE_GUILE_JSON MODULES += \ + guix/docker.scm \ guix/import/github.scm \ guix/import/json.scm \ guix/import/crate.scm \ diff --git a/doc/guix.texi b/doc/guix.texi index adc7fefcae..e52382e976 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -2394,7 +2394,7 @@ what you should use in this case (@pxref{Invoking guix copy}). @cindex nar, archive format @cindex normalized archive (nar) -Archives are stored in the ``normalized archive'' or ``nar'' format, which is +By default archives are stored in the ``normalized archive'' or ``nar'' format, which is comparable in spirit to `tar', but with differences that make it more appropriate for our purposes. First, rather than recording all Unix metadata for each file, the nar format only mentions @@ -2410,6 +2410,9 @@ verifies the signature and rejects the import in case of an invalid signature or if the signing key is not authorized. @c FIXME: Add xref to daemon doc about signatures. +Optionally, archives can be exported as a Docker image in the tar +archive format using @code{--format=docker}. + The main options are: @table @code @@ -2438,6 +2441,19 @@ Read a list of store file names from the standard input, one per line, and write on the standard output the subset of these files missing from the store. +@item -f +@item --format=@var{FMT} +@cindex docker, export +@cindex export format +Specify the export format. Acceptable arguments are @code{nar} and +@code{docker}. The default is the nar format. When the format is +@code{docker}, recursively export the specified store directory as a +Docker image in tar archive format, as specified in +@uref{https://github.com/docker/docker/blob/master/image/spec/v1.2.md, +version 1.2.0 of the Docker Image Specification}. Using +@code{--format=docker} implies @code{--recursive}. The generated +archive can be loaded by Docker using @command{docker load}. + @item --generate-key[=@var{parameters}] @cindex signing, archives Generate a new key pair for the daemon. This is a prerequisite before diff --git a/guix/docker.scm b/guix/docker.scm new file mode 100644 index 0000000000..dbe1e5351c --- /dev/null +++ b/guix/docker.scm @@ -0,0 +1,127 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2017 Ricardo Wurmus +;;; +;;; 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 . + +(define-module (guix docker) + #:use-module (guix hash) + #:use-module (guix store) + #:use-module (guix utils) + #:use-module ((guix build utils) + #:select (delete-file-recursively + with-directory-excursion)) + #:use-module (json) + #:use-module (rnrs bytevectors) + #:use-module (ice-9 match) + #:export (build-docker-image)) + +;; Generate a 256-bit identifier in hexadecimal encoding for the Docker image +;; containing the closure at PATH. +(define docker-id + (compose bytevector->base16-string sha256 string->utf8)) + +(define (layer-diff-id layer) + "Generate a layer DiffID for the given LAYER archive." + (string-append "sha256:" (bytevector->base16-string (file-sha256 layer)))) + +;; This is the semantic version of the JSON metadata schema according to +;; https://github.com/docker/docker/blob/master/image/spec/v1.2.md +;; It is NOT the version of the image specification. +(define schema-version "1.0") + +(define (image-description id time) + "Generate a simple image description." + `((id . ,id) + (created . ,time) + (container_config . #nil))) + +(define (generate-tag path) + "Generate an image tag for the given PATH." + (match (string-split (basename path) #\-) + ((hash name . rest) (string-append name ":" hash)))) + +(define (manifest path id) + "Generate a simple image manifest." + `(((Config . "config.json") + (RepoTags . (,(generate-tag path))) + (Layers . (,(string-append id "/layer.tar")))))) + +;; According to the specifications this is required for backwards +;; compatibility. It duplicates information provided by the manifest. +(define (repositories path id) + "Generate a repositories file referencing PATH and the image ID." + `((,(generate-tag path) . ((latest . ,id))))) + +;; See https://github.com/opencontainers/image-spec/blob/master/config.md +(define (config layer time arch) + "Generate a minimal image configuration for the given LAYER file." + ;; "architecture" must be values matching "platform.arch" in the + ;; runtime-spec at + ;; https://github.com/opencontainers/runtime-spec/blob/v1.0.0-rc2/config.md#platform + `((architecture . ,arch) + (comment . "Generated by GNU Guix") + (created . ,time) + (config . #nil) + (container_config . #nil) + (os . "linux") + (rootfs . ((type . "layers") + (diff_ids . (,(layer-diff-id layer))))))) + +(define* (build-docker-image path #:key system) + "Generate a Docker image archive from the given store PATH. The image +contains the closure of the given store item." + (let ((id (docker-id path)) + (time (strftime "%FT%TZ" (localtime (current-time)))) + (name (string-append (getcwd) + "/docker-image-" (basename path) ".tar")) + (arch (match system + ("x86_64-linux" "amd64") + ("i686-linux" "386") + ("armhf-linux" "arm") + ("mips64el-linux" "mips64le")))) + (and (call-with-temporary-directory + (lambda (directory) + (with-directory-excursion directory + ;; Add symlink from /bin to /gnu/store/.../bin + (symlink (string-append path "/bin") "bin") + + (mkdir id) + (with-directory-excursion id + (with-output-to-file "VERSION" + (lambda () (display schema-version))) + (with-output-to-file "json" + (lambda () (scm->json (image-description id time)))) + + ;; Wrap it up + (let ((items (with-store store + (requisites store (list path))))) + (and (zero? (apply system* "tar" "-cf" "layer.tar" + (cons "../bin" items))) + (delete-file "../bin")))) + + (with-output-to-file "config.json" + (lambda () + (scm->json (config (string-append id "/layer.tar") + time arch)))) + (with-output-to-file "manifest.json" + (lambda () + (scm->json (manifest path id)))) + (with-output-to-file "repositories" + (lambda () + (scm->json (repositories path id))))) + (and (zero? (system* "tar" "-C" directory "-cf" name ".")) + (begin (delete-file-recursively directory) #t)))) + name))) diff --git a/guix/scripts/archive.scm b/guix/scripts/archive.scm index 7e432351ed..6eba9e0008 100644 --- a/guix/scripts/archive.scm +++ b/guix/scripts/archive.scm @@ -1,5 +1,6 @@ ;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2013, 2014, 2015, 2016 Ludovic Courtès +;;; Copyright © 2017 Ricardo Wurmus ;;; ;;; This file is part of GNU Guix. ;;; @@ -30,6 +31,7 @@ #:use-module (guix ui) #:use-module (guix pki) #:use-module (guix pk-crypto) + #:use-module (guix docker) #:use-module (guix scripts) #:use-module (guix scripts build) #:use-module (gnu packages) @@ -62,6 +64,8 @@ Export/import one or more packages from/to the store.\n")) (display (_ " --export export the specified files/packages to stdout")) + (display (_ " + --format=FMT export files/packages in the specified format FMT")) (display (_ " -r, --recursive combined with '--export', include dependencies")) (display (_ " @@ -117,6 +121,9 @@ Export/import one or more packages from/to the store.\n")) (option '("export") #f #f (lambda (opt name arg result) (alist-cons 'export #t result))) + (option '(#\f "format") #t #f + (lambda (opt name arg result . rest) + (alist-cons 'format arg result))) (option '(#\r "recursive") #f #f (lambda (opt name arg result) (alist-cons 'export-recursive? #t result))) @@ -331,7 +338,15 @@ the input port." (else (with-store store (cond ((assoc-ref opts 'export) - (export-from-store store opts)) + (cond ((equal? (assoc-ref opts 'format) "docker") + (match (car opts) + (('argument . (? store-path? item)) + (format #t "~a\n" + (build-docker-image + item + #:system (assoc-ref opts 'system)))) + (_ (leave (_ "argument must be a direct store path~%"))))) + (_ (export-from-store store opts)))) ((assoc-ref opts 'import) (import-paths store (current-input-port))) ((assoc-ref opts 'missing) -- cgit v1.2.3