├── README.md ├── doc └── intro.md ├── .gitignore ├── deps.edn ├── CONTRIBUTING.md ├── .github └── workflows │ └── test.yml ├── src ├── main │ ├── dotnet │ │ └── packager │ │ │ └── clojure.tools.namespace.csproj │ └── clojure │ │ └── clojure │ │ └── tools │ │ └── namespace │ │ ├── reload.clj │ │ ├── parse.cljc │ │ ├── track.cljc │ │ ├── file.clj │ │ ├── dependency.cljc │ │ ├── dir.clj │ │ ├── repl.clj │ │ └── find.clj └── test │ └── clojure │ └── clojure │ └── tools │ └── namespace │ ├── track_test.clj │ ├── parse_test1.clj │ ├── dir_test.clj │ ├── repl_test.clj │ ├── find_test.clj │ ├── reload_test.clj │ ├── file_test.clj │ ├── test_helpers.clj │ ├── parse_test.clj │ └── dependency_test.clj ├── project.clj ├── CHANGES.md └── epl.html /README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clojure/clr.tools.namespace/master/README.md -------------------------------------------------------------------------------- /doc/intro.md: -------------------------------------------------------------------------------- 1 | # Introduction to clojure.tools.namespace 2 | 3 | TODO: write [great documentation](http://jacobian.org/writing/great-documentation/what-to-write/) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | pom.xml 2 | /lib/ 3 | /classes/ 4 | /targets/ 5 | /target 6 | .cpcache 7 | .idea/ 8 | *.iml 9 | /classes 10 | /checkouts 11 | *.jar 12 | *.class 13 | *.dll 14 | *.pdb 15 | *.exe 16 | .lein-deps-sum 17 | .lein-failures 18 | .lein-plugins 19 | .vs 20 | 21 | #Visual Studio artifacts 22 | bin 23 | obj 24 | *.user 25 | *.suo 26 | *.nupkg -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src/main/clojure"] 2 | :deps 3 | {io.github.clojure/clr.tools.reader {:git/tag "v1.5.2" :git/sha "1a7a8e9"}} 4 | 5 | :aliases 6 | {:test 7 | {:extra-paths ["src/test/clojure"] 8 | :extra-deps {io.github.dmiller/test-runner {:git/sha "c055ea13d19c6a9b9632aa2370fcc2215c8043c3"}} 9 | ;; :main-opts ["-m" "cognitect.test-runner" "-d" "src/test/clojure"] 10 | :exec-fn cognitect.test-runner.api/test 11 | :exec-args {:dirs ["src/test/clojure"]}}}} 12 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | This is a [Clojure contrib] project. 2 | 3 | Under the Clojure contrib [guidelines], this project cannot accept 4 | pull requests. All patches must be submitted via [JIRA]. 5 | 6 | See [Contributing] on the Clojure website for 7 | more information on how to contribute. 8 | 9 | [Clojure contrib]: https://clojure.org/community/contrib_libs 10 | [Contributing]: https://clojure.org/community/contributing 11 | [JIRA]: https://clojure.atlassian.net/browse/TNS 12 | [guidelines]: https://clojure.org/community/contrib_howto -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | on: [push, pull_request] 3 | jobs: 4 | test: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - name: Checkout 8 | uses: actions/checkout@v3 9 | 10 | - name: Prepare dotnet 11 | uses: xt0rted/setup-dotnet@v1.5.0 12 | 13 | - name: Prepare Clojure CLR 14 | run: | 15 | dotnet tool install --global Clojure.Main --version 1.12.0-alpha10 16 | dotnet tool install --global Clojure.Cljr --version 0.1.0-alpha5 17 | - name: Run cljr tests 18 | run: cljr -X:test -------------------------------------------------------------------------------- /src/main/dotnet/packager/clojure.tools.namespace.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0;netstandard2.1 5 | 6 | 7 | 8 | clojure.tools.namespace 9 | clojure.tools 10 | clojure.tools.namespace 11 | clojure.tools.namespace 12 | clojure.tools.namespace 13 | ClojureCLR Contributors 14 | Tools for managing namespaces in ClojureCLR. 15 | Copyright © Rich Hickey, ClojureCLR Contributors 2025 16 | EPL-1.0 17 | https://github.com/clojure/clr.tools.namesapce 18 | ClojureCLR contributors 19 | Clojure;ClojureCLR 20 | 1.5.5 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/test/clojure/clojure/tools/namespace/track_test.clj: -------------------------------------------------------------------------------- 1 | (ns clojure.tools.namespace.track-test 2 | (:refer-clojure :exclude (remove)) 3 | (:use clojure.test 4 | clojure.tools.namespace.track)) 5 | 6 | (deftest t-tracker 7 | (let [x (tracker)] 8 | (is (empty? x)) 9 | (is (map? x)))) 10 | 11 | (defn- equal-dep-map? [x load unload dependencies depenendents] 12 | (= x {:clojure.tools.namespace.track/load load 13 | :clojure.tools.namespace.track/unload unload 14 | :clojure.tools.namespace.track/deps (clojure.tools.namespace.dependency.MapDependencyGraph. dependencies depenendents)})) 15 | 16 | 17 | (deftest t-add 18 | (let [y (add (tracker) '{ a #{b} b #{c d}}) 19 | w (add y '{a #{c} b #{e} d #{f g}})] 20 | (is (equal-dep-map? y '(b a) '(a b) '{b #{c d}, a #{b}} '{d #{b}, c #{b}, b #{a}})) ;;; ordering diff on unload: '(b a) 21 | (is (equal-dep-map? w '(a b d) '(a b d) '{d #{f g}, b #{e}, a #{c}} '{g #{d}, f #{d}, e #{b}, d #{b}, c #{b a}, b #{a}})))) ;; ordering diff on unload: '(a d b)) 22 | 23 | (deftest t-remove 24 | (let [y (add (tracker) '{ a #{b} b #{c d}}) 25 | z (remove y '(d)) 26 | w (remove y '(q))] 27 | #_(is (equal-dep-map? z '(b a) '(a b d) '{b #{c d}, a #{b}} '{d #{b}, c #{b}, b #{a}})) ;; with the TNS-6 change, shouldn't this test change? 28 | (is (equal-dep-map? z '(b a) '(a b d) '{b #{c}, a #{b}} '{c #{b}, b #{a}})) 29 | (is (= y w)))) 30 | 31 | -------------------------------------------------------------------------------- /src/test/clojure/clojure/tools/namespace/parse_test1.clj: -------------------------------------------------------------------------------- 1 | (ns clojure.tools.namespace.parse-test1 2 | (:use clojure.test 3 | clojure.tools.namespace.parse)) 4 | ;;; DM: I don't know where this came from. Does not appear to be part of the current or historical c.t.namespace code 5 | (deftest t-comment? 6 | (testing "is a comment" 7 | (are [x] (comment? x) 8 | '(comment) 9 | '(comment a b c))) 10 | (testing "not a comment" 11 | (are [x] (not (comment? x)) 12 | 7 13 | () 14 | '(a b c) 15 | 'comment 16 | '[comment a b]))) 17 | 18 | (deftest t-ns-decl? 19 | (testing "is an ns" 20 | (are [x] (ns-decl? x) 21 | '(ns) 22 | '(ns a b c))) 23 | (testing "not an ns" 24 | (are [x] (not (ns-decl? x)) 25 | 7 26 | () 27 | '(a b c) 28 | 'ns 29 | '[ns a b]))) 30 | 31 | (defn- read-ns-decl-from-string [s] 32 | (with-open [ptr (clojure.lang.PushbackTextReader. (System.IO.StringReader. s))] 33 | (read-ns-decl ptr))) 34 | 35 | (deftest t-read-ns-decl 36 | (are [v s] (= v (read-ns-decl-from-string s)) 37 | nil "" 38 | nil "(comment a b c)" 39 | nil "(a) (b) (c)" 40 | '(ns a) "(a) (ns a)" 41 | '(ns a) "(ns a) (comment (ns b))" 42 | '(ns a) "(comment (ns b)) (comment (ns c)) (ns a) (other things)")) 43 | 44 | (deftest t-deps-from-ns-decl 45 | (are [deps form] (= deps (deps-from-ns-decl form)) 46 | #{'clojure.set 47 | 'clojure.zip} '(ns a (:require (clojure zip [set :as s]))) 48 | #{'clojure.set} '(ns a (:require clojure.set)) 49 | #{'clojure.set 50 | 'clojure.zip} '(ns a (:require clojure.set) (:use clojure.zip) (:refer clojure.whatever)))) 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject org.clojure.clr/tools.namespace "1.5.5" 2 | :description "Port of clojure.org/tools.namespace to ClojureCLR" 3 | :url "https://github.com/clojure/clr.tools.namespace" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :dependencies [[org.clojure.clr/tools.reader "1.5.2"]] 7 | :source-paths ["src/main/clojure"] 8 | :test-paths ["src/test/clojure"] 9 | :min-lein-version "2.0.0" 10 | :plugins [[lein-clr "0.2.0"]] 11 | :deploy-repositories [["clojars" {:url "https://clojars.org/repo/" 12 | :sign-releases false}]] 13 | :clr {:cmd-templates {:clj-exe [#_"mono" [CLJCLR15_40 %1]] 14 | :clj-dep [#_"mono" ["target/clr/clj/Debug 4.0" %1]] 15 | :clj-url "https://github.com/downloads/clojure/clojure-clr/clojure-clr-1.4.0-Debug-4.0.zip" 16 | :clj-zip "clojure-clr-1.4.0-Debug-4.0.zip" 17 | :curl ["curl" "--insecure" "-f" "-L" "-o" %1 %2] 18 | :nuget-ver [#_"mono" [*PATH "nuget.exe"] "install" %1 "-Version" %2] 19 | :nuget-any [#_"mono" [*PATH "nuget.exe"] "install" %1] 20 | :unzip ["unzip" "-d" %1 %2] 21 | :wget ["wget" "--no-check-certificate" "--no-clobber" "-O" %1 %2]} 22 | ;; for automatic download/unzip of ClojureCLR, 23 | ;; 1. make sure you have curl or wget installed and on PATH, 24 | ;; 2. uncomment deps in :deps-cmds, and 25 | ;; 3. use :clj-dep instead of :clj-exe in :main-cmd and :compile-cmd 26 | :deps-cmds [; [:wget :clj-zip :clj-url] ; edit to use :curl instead of :wget 27 | ; [:unzip "../clj" :clj-zip] 28 | ] 29 | :main-cmd [:clj-exe "Clojure.Main.exe"] 30 | :compile-cmd [:clj-exe "Clojure.Compile.exe"]}) -------------------------------------------------------------------------------- /src/test/clojure/clojure/tools/namespace/dir_test.clj: -------------------------------------------------------------------------------- 1 | (ns clojure.tools.namespace.dir-test 2 | (:require [clojure.test :refer [deftest is]] 3 | [clojure.tools.namespace.test-helpers :as help] 4 | [clojure.tools.namespace.dir :as dir]) 5 | #_(:import 6 | (java.io File))) 7 | ;;; I don't know what the equivalent test would be for .Net. 8 | #_(defn- make-symbolic-link 9 | "Reflectively calls java.nio.file.Files/createSymbolicLink on two 10 | java.io.File arguments, to avoid a compile-time dependency on 11 | java.nio.file.Files. Returns a java.io.File." 12 | [^File link ^File target] 13 | (let [path-class (Class/forName "java.nio.file.Path") 14 | attr-class (Class/forName "java.nio.file.attribute.FileAttribute") 15 | attr-array (make-array attr-class 0) 16 | attr-array-class (.getClass attr-array) 17 | to-path (.getMethod java.io.File "toPath" (into-array Class [])) 18 | to-file (.getMethod path-class "toFile" (into-array Class [])) 19 | create-link (.getMethod (Class/forName "java.nio.file.Files") 20 | "createSymbolicLink" 21 | (into-array Class [path-class path-class attr-array-class])) 22 | link-path (.invoke to-path link (object-array 0)) 23 | target-path (.invoke to-path target (object-array 0)) 24 | link (.invoke create-link path-class (object-array [link-path target-path attr-array]))] 25 | (.invoke to-file link (object-array 0)))) 26 | 27 | ;; Only run this test on Java 1.7+, where java.nio.file.Files is available. 28 | #_(when (try (Class/forName "java.nio.file.Files") 29 | (catch ClassNotFoundException _ false)) 30 | (deftest t-scan-by-canonical-path 31 | (let [dir (help/create-temp-dir "t-scan-by-canonical-path") 32 | main-clj (help/create-source dir 'example.main :clj '[example.one]) 33 | one-cljc (help/create-source dir 'example.one :clj) 34 | other-dir (help/create-temp-dir "t-scan-by-canonical-path-other") 35 | link (File. other-dir "link")] 36 | (make-symbolic-link link dir) 37 | (is (= (::dir/files (dir/scan-dirs {} [dir])) 38 | (::dir/files (dir/scan-dirs {} [link]))))))) -------------------------------------------------------------------------------- /src/main/clojure/clojure/tools/namespace/reload.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Stuart Sierra, 2012. All rights reserved. The use and 2 | ;; distribution terms for this software are covered by the Eclipse 3 | ;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ;; which can be found in the file epl-v10.html at the root of this 5 | ;; distribution. By using this software in any fashion, you are 6 | ;; agreeing to be bound by the terms of this license. You must not 7 | ;; remove this notice, or any other, from this software. 8 | 9 | (ns ^{:author "Stuart Sierra, modified for ClojureCLR by David Miller" 10 | :doc "Force reloading namespaces on demand or through a 11 | dependency tracker"} 12 | clojure.tools.namespace.reload 13 | (:require [clojure.tools.namespace.track :as track])) 14 | 15 | (defn remove-lib 16 | "Remove lib's namespace and remove lib from the set of loaded libs." 17 | [lib] 18 | (remove-ns lib) 19 | (dosync (alter @#'clojure.core/*loaded-libs* disj lib))) 20 | 21 | (defn track-reload-one 22 | "Executes the next pending unload/reload operation in the dependency 23 | tracker. Returns the updated dependency tracker. If reloading caused 24 | an error, it is captured as ::error and the namespace which caused 25 | the error is ::error-ns." 26 | [tracker] 27 | (let [{unload ::track/unload, load ::track/load} tracker] 28 | (cond 29 | (seq unload) 30 | (let [n (first unload)] 31 | (remove-lib n) 32 | (update-in tracker [::track/unload] rest)) 33 | (seq load) 34 | (let [n (first load)] 35 | (try (require n :reload) 36 | (update-in tracker [::track/load] rest) 37 | (catch Exception t ;;; Throwable 38 | (assoc tracker 39 | ::error t ::error-ns n ::track/unload load)))) 40 | :else 41 | tracker))) 42 | 43 | (defn track-reload 44 | "Executes all pending unload/reload operations on dependency tracker 45 | until either an error is encountered or there are no more pending 46 | operations." 47 | [tracker] 48 | (loop [tracker (dissoc tracker ::error ::error-ns)] 49 | (let [{error ::error, unload ::track/unload, load ::track/load} tracker] 50 | (if (and (not error) 51 | (or (seq load) (seq unload))) 52 | (recur (track-reload-one tracker)) 53 | tracker)))) -------------------------------------------------------------------------------- /src/test/clojure/clojure/tools/namespace/repl_test.clj: -------------------------------------------------------------------------------- 1 | (ns clojure.tools.namespace.repl-test 2 | (:require [clojure.test :refer [deftest is use-fixtures]] 3 | [clojure.tools.namespace.dir :as dir] 4 | [clojure.tools.namespace.find :as find] 5 | [clojure.tools.namespace.repl :as repl] 6 | [clojure.tools.namespace.test-helpers :as help])) 7 | 8 | ;; Tests contributed by Brandon Correa 9 | 10 | (defn reset-repl! [] 11 | (repl/clear) 12 | (repl/set-refresh-dirs)) 13 | 14 | (defn reset-repl-fixture [test-fn] 15 | (reset-repl!) 16 | (test-fn) 17 | (reset-repl!)) 18 | 19 | (use-fixtures :each reset-repl-fixture) 20 | 21 | (defn current-time-millis [] 22 | (long 23 | (/ (- (.-Ticks DateTime/UtcNow) 24 | (.-Ticks DateTime/UnixEpoch)) 25 | TimeSpan/TicksPerMillisecond))) 26 | 27 | (deftest t-repl-scan-time-component 28 | (let [before (current-time-millis) 29 | scan (repl/scan {:platform find/clj}) 30 | after (current-time-millis) 31 | time (::dir/time scan)] 32 | (is (<= before time after)) 33 | (is (integer? (::dir/time scan))))) 34 | 35 | (deftest t-repl-scan-twice 36 | (let [dir (help/create-temp-dir "t-repl-scan") 37 | other-dir (help/create-temp-dir "t-repl-scan-other") 38 | main-clj (help/create-source dir 'example.main :clj '[example.one]) 39 | one-cljc (help/create-source dir 'example.one :clj) 40 | _ (repl/set-refresh-dirs dir other-dir) 41 | scan-1 (repl/scan {:platform find/clj}) 42 | scan-2 (repl/scan {:platform find/clj}) 43 | paths-1 (map str (::dir/files scan-1)) 44 | paths-2 (map str (::dir/files scan-2)) 45 | paths (set paths-1)] 46 | (is (= 2 (count paths-1))) 47 | (is (= paths-1 paths-2)) 48 | (is (contains? paths (str main-clj))) 49 | (is (contains? paths (str one-cljc))))) 50 | 51 | (deftest t-repl-scan-after-file-modified 52 | (let [dir (help/create-temp-dir "t-repl-scan-after-file-modified") 53 | main-clj (help/create-source dir 'example.main :clj) 54 | _ (repl/set-refresh-dirs dir) 55 | scan-1 (repl/scan {:platform find/clj}) 56 | _ (System.IO.File/SetLastWriteTimeUtc (.-FullName main-clj) DateTime/UtcNow) 57 | scan-2 (repl/scan {:platform find/clj}) 58 | paths-1 (map str (::dir/files scan-1)) 59 | paths-2 (map str (::dir/files scan-2)) 60 | paths (set paths-1)] 61 | (is (= 1 (count paths-1))) 62 | (is (= paths-1 paths-2)) 63 | (is (contains? paths (str main-clj))))) -------------------------------------------------------------------------------- /src/test/clojure/clojure/tools/namespace/find_test.clj: -------------------------------------------------------------------------------- 1 | (ns clojure.tools.namespace.find-test 2 | (:require [clojure.test :refer [deftest is]] 3 | [clojure.tools.namespace.test-helpers :as help] 4 | [clojure.tools.namespace.find :as find]) 5 | ) ;;; (:import (java.io File)) 6 | 7 | (deftest t-find-clj-and-cljc-files 8 | "main.clj depends on one.cljc which depends on two.clj. 9 | two.cljs also exists but should not be returned" 10 | (let [dir (help/create-temp-dir "t-find-clj-and-cljc-files") 11 | main-clj (help/create-source dir 'example.main :clj '[example.one]) 12 | one-cljc (help/create-source dir 'example.one :cljc '[example.two]) 13 | two-clj (help/create-source dir 'example.two :clj) 14 | two-cljs (help/create-source dir 'example.two :cljs)] 15 | (is (help/same-files? 16 | [main-clj one-cljc two-clj] 17 | (find/find-sources-in-dir dir))))) 18 | 19 | (deftest t-find-cljs-and-cljc-files 20 | "main.cljs depends on one.cljc which depends on two.cljs. 21 | two.clj also exists but should not be returned" 22 | (let [dir (help/create-temp-dir "t-find-cljs-and-cljc-files") 23 | main-cljs (help/create-source dir 'example.main :cljs '[example.one]) 24 | one-cljc (help/create-source dir 'example.one :cljc '[example.two]) 25 | two-clj (help/create-source dir 'example.two :clj) 26 | two-cljs (help/create-source dir 'example.two :cljs)] 27 | (is (help/same-files? 28 | [main-cljs one-cljc two-cljs] 29 | (find/find-sources-in-dir dir find/cljs))))) 30 | 31 | (deftest t-find-ns-decl-meta 32 | (let [dir (help/create-temp-dir "t-find-clj-and-cljc-files") 33 | main-clj (help/create-source dir 'example.main :clj '[example.one]) 34 | one-cljc (help/create-source dir 'example.one :cljc '[example.two]) 35 | two-clj (help/create-source dir 'example.two :clj) 36 | two-cljs (help/create-source dir 'example.two :cljs) 37 | headless-clj (help/create-headless-source dir 'example.headless :clj)] 38 | (is (every? #{(.Name ^System.IO.DirectoryInfo dir)} ;; .getName ^java.io.File 39 | (map #(-> % second meta :dir) 40 | (find/find-ns-decls [dir])))))) 41 | 42 | ;;; DM: added 43 | (deftest t-find-cljr-and-cljc-files 44 | "main.cljr depends on one.cljc which depends on two.cljr. 45 | two.clj also exists but should not be returned" 46 | (let [dir (help/create-temp-dir "t-find-cljr-and-cljc-files") 47 | main-cljr (help/create-source dir 'example.main :cljr '[example.one]) 48 | one-cljc (help/create-source dir 'example.one :cljc '[example.two]) 49 | two-cljs (help/create-source dir 'example.two :cljs) 50 | two-cljr (help/create-source dir 'example.two :cljr)] 51 | (is (help/same-files? 52 | [main-cljr one-cljc two-cljr] 53 | (find/find-sources-in-dir dir find/cljr))))) -------------------------------------------------------------------------------- /src/test/clojure/clojure/tools/namespace/reload_test.clj: -------------------------------------------------------------------------------- 1 | (ns clojure.tools.namespace.reload-test 2 | (:use clojure.test 3 | clojure.tools.namespace.reload)) 4 | 5 | (deftest t-remove-lib 6 | (with-bindings {#'clojure.core/*loaded-libs* (ref '#{a.b c.d})} 7 | (create-ns 'a.b) 8 | (is (find-ns 'a.b)) 9 | (is (some #{'a.b} @@#'clojure.core/*loaded-libs*)) 10 | (remove-lib 'a.b) 11 | (is (not (find-ns 'a.b))) 12 | (is (= @@#'clojure.core/*loaded-libs* '#{c.d})))) 13 | 14 | (defn- create-tracker [load unload] 15 | {:clojure.tools.namespace.track/load load 16 | :clojure.tools.namespace.track/unload unload 17 | :clojure.tools.namespace.track/deps (clojure.tools.namespace.dependency.MapDependencyGraph. {} {})}) 18 | 19 | (defmacro with-req-rem [reqfn remfn & body] 20 | `(with-redefs [clojure.core/require ~reqfn 21 | clojure.core/remove-ns ~remfn] 22 | ~@body)) 23 | 24 | 25 | (defmacro with-recording [& body] 26 | `(let [rems# (atom []) 27 | reqs# (atom [])] 28 | (with-req-rem 29 | (fn [& args#] (swap! reqs# conj args#)) 30 | (fn [lib#] (swap! rems# conj lib#)) 31 | (let [result# (do ~@body) ] 32 | [result# @rems# @reqs#])))) 33 | 34 | 35 | 36 | (defn- do-reload-one [load unload] 37 | (with-recording 38 | (track-reload-one (create-tracker load unload)))) 39 | 40 | 41 | (defn- do-reload-one-with-load-error [load] 42 | (with-req-rem 43 | (fn [& args] (throw (Exception.))) 44 | identity 45 | (track-reload-one (create-tracker load nil)))) 46 | 47 | (defn- do-reload [load unload] 48 | (with-recording 49 | (track-reload (create-tracker load unload)))) 50 | 51 | (defn- do-reload-with-load-error [load error-sym] 52 | (with-req-rem 53 | (fn [lib & args] (when (= lib error-sym) (throw (Exception.)))) 54 | identity 55 | (track-reload (create-tracker load nil)))) 56 | 57 | 58 | (defn- equal-dep-map? [x load unload] 59 | (and (= (:clojure.tools.namespace.track/load x) load) 60 | (= (:clojure.tools.namespace.track/unload x) unload))) 61 | 62 | 63 | (deftest t-track-reload-one 64 | (testing "unload" 65 | (let [[new-tracker rems reqs] (do-reload-one '(a b) '(c d))] 66 | (is (equal-dep-map? new-tracker '(a b) '(d))) 67 | (is (= rems '[c])) 68 | (is (empty? reqs)))) 69 | (testing "load" 70 | (let [[new-tracker rems reqs] (do-reload-one '(a b) '())] 71 | (is (equal-dep-map? new-tracker '(b) '())) 72 | (is (empty? rems)) 73 | (is (= reqs '[(a :reload)])))) 74 | (testing "load with error" 75 | (let [new-tracker (do-reload-one-with-load-error '(a b))] 76 | (is (equal-dep-map? new-tracker '(a b) '(a b))) 77 | (is (instance? Exception (:clojure.tools.namespace.reload/error new-tracker))) 78 | (is (= (:clojure.tools.namespace.reload/error-ns new-tracker) 'a))))) 79 | 80 | (deftest t-track-reload 81 | (testing "normal" 82 | (let [[new-tracker rems reqs] (do-reload '(a b) '(c d))] 83 | (is (equal-dep-map? new-tracker '() '())) 84 | (is (= rems '[c d])) 85 | (is (= reqs '[(a :reload) (b :reload)])))) 86 | (testing "load with error" 87 | (let [new-tracker (do-reload-with-load-error '(a b c) 'b)] 88 | (is (equal-dep-map? new-tracker '(b c) '(b c))) 89 | (is (instance? Exception (:clojure.tools.namespace.reload/error new-tracker))) 90 | (is (= (:clojure.tools.namespace.reload/error-ns new-tracker) 'b))))) 91 | 92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /src/test/clojure/clojure/tools/namespace/file_test.clj: -------------------------------------------------------------------------------- 1 | (ns clojure.tools.namespace.file-test 2 | (:require [clojure.test :refer [deftest is]] 3 | [clojure.tools.namespace.dependency :as dep] 4 | [clojure.tools.namespace.file :as file] 5 | [clojure.tools.namespace.test-helpers :as help] 6 | [clojure.tools.namespace.track :as track]) 7 | (:import (System.IO FileInfo))) 8 | 9 | ;; Tests compliments of Brandon Correa 10 | 11 | (deftest t-add-no-files 12 | (let [tracker (file/add-files (track/tracker) nil)] 13 | (is (= (dep/->MapDependencyGraph {} {}) (::track/deps tracker))) 14 | (is (= {} (::file/filemap tracker))) 15 | (is (= '() (::track/unload tracker))) 16 | (is (= '() (::track/load tracker))))) 17 | 18 | (deftest t-add-one-file 19 | (let [dir (help/create-temp-dir "t-add-one-file") 20 | one-clj (help/create-source dir 'example.one :clj) 21 | tracker (file/add-files (track/tracker) [one-clj])] 22 | (is (= (dep/->MapDependencyGraph {} {}) (::track/deps tracker))) 23 | (is (= {one-clj 'example.one} (::file/filemap tracker))) 24 | (is (= (list 'example.one) (::track/unload tracker))) 25 | (is (= (list 'example.one) (::track/load tracker))))) 26 | 27 | (deftest t-add-file-with-dependency 28 | (let [dir (help/create-temp-dir "t-add-file-with-dependency") 29 | main-clj (help/create-source dir 'example.main :clj '[example.one]) 30 | tracker (file/add-files (track/tracker) [main-clj])] 31 | (is (= {'example.main #{'example.one}} (:dependencies (::track/deps tracker)))) 32 | (is (= {'example.one #{'example.main}} (:dependents (::track/deps tracker)))) 33 | (is (= {main-clj 'example.main} (::file/filemap tracker))) 34 | (is (= (list 'example.main) (::track/unload tracker))) 35 | (is (= (list 'example.main) (::track/load tracker))))) 36 | 37 | (deftest t-add-file-that-already-exists 38 | (let [dir (help/create-temp-dir "t-add-file-that-already-exists") 39 | file-ref-1 (help/create-source dir 'example.main :clj) 40 | file-ref-2 (FileInfo. (.-FullName file-ref-1)) 41 | tracker (-> (track/tracker) 42 | (file/add-files [file-ref-1]) 43 | (file/add-files [file-ref-2]))] 44 | (is (= {} (:dependencies (::track/deps tracker)))) 45 | (is (= {} (:dependents (::track/deps tracker)))) 46 | (is (= {file-ref-2 'example.main} (::file/filemap tracker))) 47 | (is (= (list 'example.main) (::track/unload tracker))) 48 | (is (= (list 'example.main) (::track/load tracker))))) 49 | 50 | (deftest t-add-file-that-already-exists-in-the-same-call 51 | (let [dir (help/create-temp-dir "t-add-file-that-already-exists-in-the-same-call") 52 | file-ref-1 (help/create-source dir 'example.main :clj) 53 | file-ref-2 (FileInfo. (.-FullName file-ref-1)) 54 | tracker (-> (track/tracker) 55 | (file/add-files [file-ref-1 file-ref-2]))] 56 | (is (= {} (:dependencies (::track/deps tracker)))) 57 | (is (= {} (:dependents (::track/deps tracker)))) 58 | (is (= {file-ref-2 'example.main} (::file/filemap tracker))) 59 | (is (= (list 'example.main) (::track/unload tracker))) 60 | (is (= (list 'example.main) (::track/load tracker))))) 61 | 62 | (deftest t-remove-no-files-from-empty-tracker 63 | (let [tracker (file/remove-files {} nil)] 64 | (is (= (dep/->MapDependencyGraph {} {}) (::track/deps tracker))) 65 | (is (nil? (::file/filemap tracker))) 66 | (is (= '() (::track/unload tracker))) 67 | (is (= '() (::track/load tracker))))) 68 | 69 | (deftest t-remove-file-with-dependency-from-filemap 70 | (let [dir (help/create-temp-dir "t-remove-file-with-dependency-from-filemap") 71 | file-ref-1 (help/create-source dir 'example.main :clj '[example.one]) 72 | file-ref-2 (FileInfo. (.-FullName file-ref-1)) 73 | tracker (-> (track/tracker) 74 | (file/add-files [file-ref-1]) 75 | (file/remove-files [file-ref-2]))] 76 | (is (= {} (:dependencies (::track/deps tracker)))) 77 | (is (= {'example.one #{}} (:dependents (::track/deps tracker)))) 78 | (is (= {} (::file/filemap tracker))) 79 | (is (= (list 'example.main) (::track/unload tracker))) 80 | (is (= (list) (::track/load tracker))))) -------------------------------------------------------------------------------- /src/main/clojure/clojure/tools/namespace/parse.cljc: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Stuart Sierra, 2012. All rights reserved. The use and 2 | ;; distribution terms for this software are covered by the Eclipse 3 | ;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ;; which can be found in the file epl-v10.html at the root of this 5 | ;; distribution. By using this software in any fashion, you are 6 | ;; agreeing to be bound by the terms of this license. You must not 7 | ;; remove this notice, or any other, from this software. 8 | 9 | (ns ^{:author "Stuart Sierra, modified for ClojureCLR by David Miller" 10 | :doc "Parse Clojure namespace (ns) declarations and extract 11 | dependencies."} 12 | clojure.tools.namespace.parse 13 | (:require #?(:clj [clojure.tools.reader :as reader] :cljr [clojure.tools.reader :as reader] ;; DM: Added :cljr entry 14 | :cljs [cljs.tools.reader :as reader]) 15 | [clojure.set :as set])) 16 | 17 | (defn comment? 18 | "Returns true if form is a (comment ...)" 19 | [form] 20 | (and (list? form) (= 'comment (first form)))) 21 | 22 | (defn ns-decl? 23 | "Returns true if form is a (ns ...) declaration." 24 | [form] 25 | (and (list? form) (= 'ns (first form)))) 26 | 27 | (def clj-read-opts 28 | "Map of options for tools.reader/read allowing reader conditionals 29 | with the :clj feature enabled." 30 | {:read-cond :allow 31 | :features #{:clj}}) 32 | 33 | (def cljs-read-opts 34 | "Map of options for tools.reader/read allowing reader conditionals 35 | with the :cljs feature enabled." 36 | {:read-cond :allow 37 | :features #{:cljs}}) 38 | 39 | ;; DM: Added 40 | (def cljr-read-opts 41 | "Map of options for tools.reader/read allowing reader conditionals 42 | with the :cljr feature enabled." 43 | {:read-cond :allow 44 | :features #{:cljr}}) 45 | 46 | (defn read-ns-decl 47 | "Attempts to read a (ns ...) declaration from a reader, and returns 48 | the unevaluated form. Returns the first top-level ns form found. 49 | Returns nil if ns declaration cannot be found. Throws exception on 50 | invalid syntax. 51 | 52 | Note that read can execute code (controlled by 53 | tools.reader/*read-eval*), and as such should be used only with 54 | trusted sources. read-opts is passed through to tools.reader/read, 55 | defaults to clj-read-opts" 56 | ([rdr] 57 | (read-ns-decl rdr nil)) 58 | ([rdr read-opts] 59 | (let [opts (assoc (or read-opts cljr-read-opts) ;;; clj-read-opts 60 | :eof ::eof)] 61 | (loop [] 62 | (let [form (reader/read opts rdr)] 63 | (cond 64 | (ns-decl? form) form 65 | (= ::eof form) nil 66 | :else (recur))))))) 67 | 68 | 69 | ;;; Parsing dependencies 70 | 71 | (defn- prefix-spec? 72 | "Returns true if form represents a libspec prefix list like 73 | (prefix name1 name1) or [com.example.prefix [name1 :as name1]]" 74 | [form] 75 | (and (sequential? form) ; should be a list, but often is not 76 | (symbol? (first form)) 77 | (not-any? keyword? form) 78 | (< 1 (count form)))) ; not a bare vector like [foo] 79 | 80 | (defn- option-spec? 81 | "Returns true if form represents a libspec vector containing optional 82 | keyword arguments like [namespace :as alias] or 83 | [namespace :refer (x y)] or just [namespace]" 84 | [form] 85 | (and (sequential? form) ; should be a vector, but often is not 86 | (or (symbol? (first form)) 87 | (string? (first form))) 88 | (or (keyword? (second form)) ; vector like [foo :as f] 89 | (= 1 (count form))))) ; bare vector like [foo] 90 | 91 | (defn- deps-from-libspec [prefix form] 92 | (cond (prefix-spec? form) 93 | (mapcat (fn [f] (deps-from-libspec 94 | (symbol (str (when prefix (str prefix ".")) 95 | (first form))) 96 | f)) 97 | (rest form)) 98 | (option-spec? form) 99 | (when-not (= :as-alias (second form)) 100 | (deps-from-libspec prefix (first form))) 101 | (symbol? form) 102 | (list (symbol (str (when prefix (str prefix ".")) form))) 103 | (keyword? form) ; Some people write (:require ... :reload-all) 104 | nil 105 | (string? form) ; NPM dep, ignore 106 | nil 107 | :else 108 | (throw (ex-info "Unparsable namespace form" 109 | {:reason ::unparsable-ns-form 110 | :form form})))) 111 | 112 | (def ^:private ns-clause-head-names 113 | "Set of symbol/keyword names which can appear as the head of a 114 | clause in the ns form." 115 | #{"use" "require" "require-macros"}) 116 | 117 | (def ^:private ns-clause-heads 118 | "Set of all symbols and keywords which can appear at the head of a 119 | dependency clause in the ns form." 120 | (set (mapcat (fn [name] (list (keyword name) 121 | (symbol name))) 122 | ns-clause-head-names))) 123 | 124 | (defn- deps-from-ns-form [form] 125 | (when (and (sequential? form) ; should be list but sometimes is not 126 | (contains? ns-clause-heads (first form))) 127 | (mapcat #(deps-from-libspec nil %) (rest form)))) 128 | 129 | (defn name-from-ns-decl 130 | "Given an (ns...) declaration form (unevaluated), returns the name 131 | of the namespace as a symbol." 132 | [decl] 133 | (second decl)) 134 | 135 | (defn deps-from-ns-decl 136 | "Given an (ns...) declaration form (unevaluated), returns a set of 137 | symbols naming the dependencies of that namespace. Handles :use and 138 | :require clauses but not :load." 139 | [decl] 140 | (set (mapcat deps-from-ns-form decl))) -------------------------------------------------------------------------------- /src/test/clojure/clojure/tools/namespace/test_helpers.clj: -------------------------------------------------------------------------------- 1 | (ns clojure.tools.namespace.test-helpers 2 | "Utilities to help with testing files and namespaces." 3 | (:require [clojure.clr.io :as io] ;;; clojure.java.io 4 | [clojure.string :as string]) 5 | (:import (System.IO Path File FileInfo DirectoryInfo TextWriter Directory))) ;;; (java.io Closeable File Writer PrintWriter) 6 | 7 | (defn create-temp-dir 8 | "Creates and returns a new temporary directory java.io.File." 9 | [name] 10 | (let [temp-filename (Path/Combine (Path/GetTempPath) name) ;;; temp-file (File/createTempFile name nil) 11 | _ (when (or (File/Exists temp-filename)(Directory/Exists temp-filename)) 12 | (Directory/Delete temp-filename true)) ;;; (.delete temp-file) 13 | dir (Directory/CreateDirectory temp-filename)] ;;; (.mkdirs temp-file) 14 | (println "Temporary directory" (Path/GetFullPath temp-filename)) ;;; (.getAbsolutePath temp-file) 15 | dir)) ;;; temp-file 16 | 17 | (defn- write-contents 18 | "Writes contents into writer. Strings are written as-is via println, 19 | other types written as by prn." 20 | [^TextWriter writer contents] ;;; ^Writer 21 | {:pre [(sequential? contents)]} 22 | (binding [*out* writer] ;;; (PrintWriter. writer) 23 | (doseq [content contents] 24 | (if (string? content) 25 | (println content) 26 | (prn content)) 27 | (newline)))) 28 | 29 | ;;; DM: Added 30 | (defn- coerce-to-file-path 31 | "Creates a path (string) from a vector of strings and possible a beginning DirectoryInfo" 32 | [path] 33 | (let [coerce (fn [x] 34 | (cond 35 | (instance? DirectoryInfo x) (.FullName ^DirectoryInfo x) 36 | (instance? FileInfo x) (.FullName ^FileInfo x) 37 | (instance? String x) x 38 | :otherwise (str x)))] 39 | (Path/Combine (into-array String (map coerce path))))) 40 | ;;; DM: 41 | 42 | (defn create-file 43 | "Creates a file from a vector of path elements. Writes contents into 44 | the file. Elements of contents may be data, written via prn, or 45 | strings, written directly." 46 | [path contents] 47 | {:pre [(vector? path)]} 48 | (let [path (coerce-to-file-path path) ;;; ^File file (apply io/file path) 49 | ^FileInfo fi (FileInfo. path)] ;;; (when-let [parent (.getParentFile file)] 50 | (when-not (.Exists fi) (Directory/CreateDirectory (.. fi Directory FullName))) ;;; (.mkdirs parent)) 51 | (with-open [wtr (io/text-writer fi)] ;;; io/writer file 52 | (write-contents wtr contents)) 53 | fi)) ;;; file 54 | 55 | (defn- sym->path 56 | "Converts a symbol name into a vector of path parts, not including 57 | file extension." 58 | [symbol] 59 | {:pre [(symbol? symbol)] 60 | :post [(vector? %)]} 61 | (-> (name symbol) 62 | (string/replace \- \_) 63 | (string/split #"\."))) 64 | 65 | (defn- source-path 66 | "Returns a vector of path components for namespace named sym, 67 | with given file extension (keyword)." 68 | [sym extension] 69 | (let [path (sym->path sym) 70 | basename (peek path) 71 | filename (str basename \. (name extension))] 72 | (conj (pop path) filename))) 73 | 74 | (defn create-source 75 | "Creates a file at the correct path under base-dir for a namespace 76 | named sym, with file extension (keyword), containing a ns 77 | declaration which :require's the dependencies (symbols). Optional 78 | contents written after the ns declaration as by write-contents." 79 | ([base-dir sym extension] 80 | (create-source base-dir sym extension nil nil)) 81 | ([base-dir sym extension dependencies] 82 | (create-source base-dir sym extension dependencies nil)) 83 | ([base-dir sym extension dependencies contents] 84 | (let [full-path (into [base-dir] (source-path sym extension)) 85 | ns-decl (if (seq dependencies) 86 | (list 'ns sym (list* :require dependencies)) 87 | (list 'ns sym))] 88 | (create-file full-path (into [ns-decl] contents))))) 89 | 90 | (defn create-headless-source 91 | "Creates a file at the correct path under base-dir for a file that 92 | declares in-ns for namespace named sym, with file extension (keyword) 93 | and will also require the dependencies (symbols). Optional contents 94 | written after the ns declaration as by write-contents." 95 | ([base-dir sym extension] 96 | (create-headless-source base-dir sym extension nil nil)) 97 | ([base-dir sym extension dependencies] 98 | (create-headless-source base-dir sym extension dependencies nil)) 99 | ([base-dir sym extension dependencies contents] 100 | (let [full-path (into [base-dir] (source-path sym extension)) 101 | ins-decl (list 'in-ns (list 'quote sym)) 102 | deps-decl (when (seq dependencies) 103 | (map #(list 'require `(quote ~%)) dependencies))] 104 | (create-file full-path (filter identity (concat [ins-decl] deps-decl contents)))))) 105 | 106 | (defn same-files? 107 | "True if files-a and files-b contain the same canonical File's, 108 | regardless of order." 109 | [files-a files-b] 110 | (= (sort (map #(.FullName ^FileInfo %) files-a)) ;;; .getCanonicalPath ^File 111 | (sort (map #(.FullName ^FileInfo %) files-b)))) ;;; .getCanonicalPath ^File 112 | -------------------------------------------------------------------------------- /src/main/clojure/clojure/tools/namespace/track.cljc: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Stuart Sierra, 2012. All rights reserved. The use and 2 | ;; distribution terms for this software are covered by the Eclipse 3 | ;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ;; which can be found in the file epl-v10.html at the root of this 5 | ;; distribution. By using this software in any fashion, you are 6 | ;; agreeing to be bound by the terms of this license. You must not 7 | ;; remove this notice, or any other, from this software. 8 | 9 | (ns ^{:author "Stuart Sierra" 10 | :doc "Dependency tracker which can compute which namespaces need to be 11 | reloaded after files have changed. This is the low-level 12 | implementation that requires you to find the namespace dependencies 13 | yourself: most uses will interact with the wrappers in 14 | clojure.tools.namespace.file and clojure.tools.namespace.dir or the 15 | public API in clojure.tools.namespace.repl."} 16 | clojure.tools.namespace.track 17 | (:refer-clojure :exclude (remove)) 18 | (:require [clojure.set :as set] 19 | [clojure.tools.namespace.dependency :as dep])) 20 | 21 | (defn- remove-deps [deps names] 22 | (reduce dep/remove-node deps names)) 23 | 24 | (defn- add-deps [deps depmap] 25 | (reduce (fn [ds [name dependencies]] 26 | (reduce (fn [g dep] (dep/depend g name dep)) 27 | ds dependencies)) 28 | deps depmap)) 29 | 30 | (defn- update-deps [deps depmap] 31 | (-> deps 32 | (remove-deps (keys depmap)) 33 | (add-deps depmap))) 34 | 35 | (defn- affected-namespaces [deps names] 36 | (set/union (set names) 37 | (dep/transitive-dependents-set deps names))) 38 | 39 | (defn add 40 | "Returns an updated dependency tracker with new/updated namespaces. 41 | 42 | Depmap is a map describing the new or modified namespaces. Keys in 43 | the map are namespace names (symbols). Values in the map are sets of 44 | symbols naming the direct dependencies of each namespace. For 45 | example, assuming these ns declarations: 46 | 47 | (ns alpha (:require beta)) 48 | (ns beta (:require gamma delta)) 49 | 50 | the depmap would look like this: 51 | 52 | {alpha #{beta} 53 | beta #{gamma delta}} 54 | 55 | After adding new/updated namespaces, the dependency tracker will 56 | have two lists associated with the following keys: 57 | 58 | :clojure.tools.namespace.track/unload 59 | is the list of namespaces that need to be removed 60 | 61 | :clojure.tools.namespace.track/load 62 | is the list of namespaces that need to be reloaded 63 | 64 | To reload namespaces in the correct order, first remove/unload all 65 | namespaces in the 'unload' list, then (re)load all namespaces in the 66 | 'load' list. The clojure.tools.namespace.reload namespace has 67 | functions to do this." 68 | [tracker depmap] 69 | (let [{load ::load 70 | unload ::unload 71 | deps ::deps 72 | :or {load (), unload (), deps (dep/graph)}} tracker 73 | new-deps (update-deps deps depmap) 74 | changed (affected-namespaces new-deps (keys depmap)) 75 | ;; With a new tracker, old dependencies are empty, so 76 | ;; unload order will be undefined unless we use new 77 | ;; dependencies (TNS-20). 78 | old-deps (if (empty? (dep/nodes deps)) new-deps deps)] 79 | (assoc tracker 80 | ::deps new-deps 81 | ::unload (distinct 82 | (concat (reverse (sort (dep/topo-comparator old-deps) changed)) 83 | unload)) 84 | ::load (distinct 85 | (concat (sort (dep/topo-comparator new-deps) changed) 86 | load))))) 87 | 88 | (defn remove 89 | "Returns an updated dependency tracker from which the namespaces 90 | (symbols) have been removed. The ::unload and ::load lists are 91 | populated as with 'add'." 92 | [tracker names] 93 | (let [{load ::load 94 | unload ::unload 95 | deps ::deps 96 | :or {load (), unload (), deps (dep/graph)}} tracker 97 | known (set (dep/nodes deps)) 98 | removed-names (filter known names) 99 | new-deps (reduce dep/remove-all deps removed-names) 100 | changed (affected-namespaces deps removed-names)] 101 | (assoc tracker 102 | ::deps new-deps 103 | ::unload (distinct 104 | (concat (reverse (sort (dep/topo-comparator deps) changed)) 105 | unload)) 106 | ::load (distinct 107 | (filter (complement (set removed-names)) 108 | (concat (sort (dep/topo-comparator new-deps) changed) 109 | load)))))) 110 | 111 | (defn tracker 112 | "Returns a new, empty dependency tracker" 113 | [] 114 | {}) 115 | 116 | (comment 117 | ;; Structure of the namespace tracker map. Documented for reference 118 | ;; only: This is not a public API. 119 | 120 | {;; Dependency graph of namespace names (symbols) as defined in 121 | ;; clojure.tools.namespace.dependency/graph 122 | :clojure.tools.namespace.track/deps {} 123 | 124 | ;; Ordered list of namespace names (symbols) that need to be 125 | ;; removed to bring the running system into agreement with the 126 | ;; source files. 127 | :clojure.tools.namespace.track/unload () 128 | 129 | ;; Ordered list of namespace names (symbols) that need to be 130 | ;; (re)loaded to bring the running system into agreement with the 131 | ;; source files. 132 | :clojure.tools.namespace.track/load () 133 | 134 | ;; Added by clojure.tools.namespace.file: Map from source files 135 | ;; (java.io.File) to the names (symbols) of namespaces they 136 | ;; represent. 137 | :clojure.tools.namespace.file/filemap {} 138 | 139 | ;; Added by clojure.tools.namespace.dir: Set of source files 140 | ;; (java.io.File) which have been seen by this dependency tracker; 141 | ;; used to determine when files have been deleted. 142 | :clojure.tools.namespace.dir/files #{} 143 | 144 | ;; Added by clojure.tools.namespace.dir: Instant when the 145 | ;; directories were last scanned, as returned by 146 | ;; System/currentTimeMillis. 147 | :clojure.tools.namespace.dir/time 1405201862262}) -------------------------------------------------------------------------------- /src/main/clojure/clojure/tools/namespace/file.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Stuart Sierra, 2012. All rights reserved. The use and 2 | ;; distribution terms for this software are covered by the Eclipse 3 | ;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ;; which can be found in the file epl-v10.html at the root of this 5 | ;; distribution. By using this software in any fashion, you are 6 | ;; agreeing to be bound by the terms of this license. You must not 7 | ;; remove this notice, or any other, from this software. 8 | 9 | (ns ^{:author "Stuart Sierra, modified for ClojureCLR by David Miller" 10 | :doc "Read and track namespace information from files"} 11 | clojure.tools.namespace.file 12 | (:require [clojure.clr.io :as io] ;;; clojure.java.io 13 | [clojure.tools.namespace.parse :as parse] 14 | [clojure.tools.namespace.track :as track]) 15 | (:import (clojure.lang PushbackTextReader))) ;;; (java.io PushbackReader))) 16 | 17 | (defn read-file-ns-decl 18 | "Attempts to read a (ns ...) declaration from file, and returns the 19 | unevaluated form. Returns nil if ns declaration cannot be found. 20 | read-opts is passed through to tools.reader/read." 21 | ([file] 22 | (read-file-ns-decl file nil)) 23 | ([file read-opts] 24 | (with-open [rdr (PushbackTextReader. (io/text-reader file))] ;;; PushbackReader. io/reader 25 | (parse/read-ns-decl rdr read-opts)))) 26 | 27 | (declare is-file? is-directory?) 28 | 29 | (defn file-with-extension? 30 | "Returns true if the java.io.File represents a file whose name ends 31 | with one of the Strings in extensions." 32 | {:added "0.3.0"} 33 | [^System.IO.FileInfo file extensions] ;;; ^java.io.File 34 | (and (is-file? file) ;;; (.isFile file) java.io.File conflates regular files and directors. Not so with FileInfo 35 | (let [extn (.Extension file)] ;;; name (.getName file) 36 | (some #(= extn %) extensions)))) ;;; #(.endsWith name %) 37 | 38 | (def ^{:added "0.3.0"} 39 | clojure-extensions 40 | "File extensions for Clojure (JVM) files." 41 | (list ".clj" ".cljc")) 42 | 43 | (def ^{:added "0.3.0"} 44 | clojurescript-extensions 45 | "File extensions for ClojureScript files." 46 | (list ".cljs" ".cljc")) 47 | 48 | (def ^{:added "0.3.0"} 49 | clojure-clr-extensions 50 | "File extensions for Clojure (CLR) files." 51 | (list ".cljr" ".cljc" ".clj" )) 52 | 53 | (defn clojure-file? 54 | "Returns true if the java.io.File represents a file which will be 55 | read by the Clojure (JVM) compiler." 56 | [^System.IO.FileSystemInfo file] ;;; java.io.File 57 | (file-with-extension? file clojure-extensions)) 58 | 59 | (defn clojurescript-file? 60 | "Returns true if the java.io.File represents a file which will be 61 | read by the Clojure (JVM) compiler." 62 | [^System.IO.FileSystemInfo file] ;;; java.io.File 63 | (file-with-extension? file clojurescript-extensions)) 64 | 65 | (defn clojure-clr-file? 66 | "Returns true if the java.io.File represents a file which will be 67 | read by the Clojure (JVM) compiler." 68 | [^System.IO.FileSystemInfo file] ;;; java.io.File 69 | (file-with-extension? file clojure-clr-extensions)) 70 | 71 | ;; Dealing with FileInfo.Equals is reference-based, not structuaral -- via Brandon Correa 72 | 73 | (defn- files= [file-1 file-2] 74 | (= (.-FullName file-1) (.-FullName file-2))) 75 | 76 | (defn some-file [coll file] 77 | (reduce #(when (files= file %2) (reduced %2)) nil coll)) 78 | 79 | (defn into-files [files others] 80 | (into files (remove #(some-file files %) others))) 81 | 82 | (defn- dissoc-files [m files] 83 | (when m 84 | (select-keys m (remove #(some-file files %) (keys m))))) 85 | 86 | (defn- get-file [filemap file] 87 | (reduce #(when (files= file (first %2)) (reduced (second %2))) nil filemap)) 88 | 89 | (defn- files->symbols [tracker files] 90 | (let [filemap (::filemap tracker {})] 91 | (keep #(get-file filemap %) files))) 92 | 93 | (defn- merge-file-map [m other] 94 | (merge (dissoc-files m (keys other)) other)) 95 | 96 | (defn- distinct-files [files] 97 | (reduce #(-> (disj %1 (some-file %1 %2)) 98 | (conj %2)) #{} files)) 99 | 100 | ;; 101 | 102 | ;;; Dependency tracker 103 | 104 | (defn- files-and-deps [files read-opts] 105 | (reduce (fn [m file] 106 | (if-let [decl (read-file-ns-decl file read-opts)] 107 | (let [deps (parse/deps-from-ns-decl decl) 108 | name (parse/name-from-ns-decl decl)] 109 | (-> m 110 | (assoc-in [:depmap name] deps) 111 | (assoc-in [:filemap file] name))) 112 | m)) 113 | {} (distinct-files files))) ;;; files 114 | 115 | 116 | (def ^:private merge-map (fnil merge-file-map {})) ;;; (fnil merge {}) 117 | 118 | (defn add-files 119 | "Reads ns declarations from files; returns an updated dependency 120 | tracker with those files added. read-opts is passed through to 121 | tools.reader." 122 | ([tracker files] 123 | (add-files tracker files nil)) 124 | ([tracker files read-opts] 125 | (let [{:keys [depmap filemap]} (files-and-deps files read-opts)] 126 | (-> tracker 127 | (track/add depmap) 128 | (update ::filemap merge-map filemap))))) ;;; (update-in [::filemap] merge-map filemap) 129 | 130 | (defn remove-files 131 | "Returns an updated dependency tracker with files removed. The files 132 | must have been previously added with add-files." 133 | [tracker files] 134 | (-> tracker 135 | (track/remove (files->symbols tracker files)) ;;; (track/remove (keep (::filemap tracker {}) files)) 136 | (update ::filemap dissoc-files files))) ;;; (update-in [::filemap] #(apply dissoc % files)) 137 | 138 | ;;; Added 139 | 140 | (defn is-file? [^System.IO.FileSystemInfo file] 141 | (not= (enum-and (.Attributes file) System.IO.FileAttributes/Directory) System.IO.FileAttributes/Directory)) 142 | 143 | (defn is-directory? [^System.IO.FileSystemInfo file] 144 | (= (enum-and (.Attributes file) System.IO.FileAttributes/Directory) System.IO.FileAttributes/Directory)) 145 | -------------------------------------------------------------------------------- /src/main/clojure/clojure/tools/namespace/dependency.cljc: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Stuart Sierra, 2012. All rights reserved. The use and 2 | ;; distribution terms for this software are covered by the Eclipse 3 | ;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ;; which can be found in the file epl-v10.html at the root of this 5 | ;; distribution. By using this software in any fashion, you are 6 | ;; agreeing to be bound by the terms of this license. You must not 7 | ;; remove this notice, or any other, from this software. 8 | 9 | (ns ^{:author "Stuart Sierra, modified for ClojureCLR by David Miller" 10 | :doc "Bidirectional graphs of dependencies and dependent objects."} 11 | clojure.tools.namespace.dependency 12 | (:require [clojure.set :as set])) 13 | 14 | (defprotocol DependencyGraph 15 | (immediate-dependencies [graph node] 16 | "Returns the set of immediate dependencies of node.") 17 | (immediate-dependents [graph node] 18 | "Returns the set of immediate dependents of node.") 19 | (transitive-dependencies [graph node] 20 | "Returns the set of all things which node depends on, directly or 21 | transitively.") 22 | (transitive-dependencies-set [graph node-set] 23 | "Returns the set of all things which any node in node-set depends 24 | on, directly or transitively.") 25 | (transitive-dependents [graph node] 26 | "Returns the set of all things which depend upon node, directly or 27 | transitively.") 28 | (transitive-dependents-set [graph node-set] 29 | "Returns the set of all things which depend upon any node in 30 | node-set, directly or transitively.") 31 | (nodes [graph] 32 | "Returns the set of all nodes in graph.")) 33 | 34 | (defprotocol DependencyGraphUpdate 35 | (depend [graph node dep] 36 | "Returns a new graph with a dependency from node to dep (\"node depends 37 | on dep\"). Forbids circular dependencies.") 38 | (remove-edge [graph node dep] 39 | "Returns a new graph with the dependency from node to dep removed.") 40 | (remove-all [graph node] 41 | "Returns a new dependency graph with all references to node removed.") 42 | (remove-node [graph node] 43 | "Removes the node from the dependency graph without removing it as a 44 | dependency of other nodes. That is, removes all outgoing edges from 45 | node.")) 46 | 47 | (defn- remove-from-map [amap x] 48 | (reduce (fn [m [k vs]] 49 | (assoc m k (disj vs x))) 50 | {} (dissoc amap x))) 51 | 52 | (defn- transitive 53 | "Recursively expands the set of dependency relationships starting 54 | at (get neighbors x), for each x in node-set" 55 | [neighbors node-set] 56 | (loop [unexpanded (mapcat neighbors node-set) 57 | expanded #{}] 58 | (if-let [[node & more] (seq unexpanded)] 59 | (if (contains? expanded node) 60 | (recur more expanded) 61 | (recur (concat more (neighbors node)) 62 | (conj expanded node))) 63 | expanded))) 64 | 65 | (declare depends?) 66 | 67 | (def set-conj (fnil conj #{})) 68 | 69 | (defrecord MapDependencyGraph [dependencies dependents] 70 | DependencyGraph 71 | (immediate-dependencies [graph node] 72 | (get dependencies node #{})) 73 | (immediate-dependents [graph node] 74 | (get dependents node #{})) 75 | (transitive-dependencies [graph node] 76 | (transitive dependencies #{node})) 77 | (transitive-dependencies-set [graph node-set] 78 | (transitive dependencies node-set)) 79 | (transitive-dependents [graph node] 80 | (transitive dependents #{node})) 81 | (transitive-dependents-set [graph node-set] 82 | (transitive dependents node-set)) 83 | (nodes [graph] 84 | (clojure.set/union (set (keys dependencies)) 85 | (set (keys dependents)))) 86 | DependencyGraphUpdate 87 | (depend [graph node dep] 88 | (when (or (= node dep) (depends? graph dep node)) 89 | (throw (ex-info (str "Circular dependency between " 90 | (pr-str node) " and " (pr-str dep)) 91 | {:reason ::circular-dependency 92 | :node node 93 | :dependency dep}))) 94 | (MapDependencyGraph. 95 | (update-in dependencies [node] set-conj dep) 96 | (update-in dependents [dep] set-conj node))) 97 | (remove-edge [graph node dep] 98 | (MapDependencyGraph. 99 | (update-in dependencies [node] disj dep) 100 | (update-in dependents [dep] disj node))) 101 | (remove-all [graph node] 102 | (MapDependencyGraph. 103 | (remove-from-map dependencies node) 104 | (remove-from-map dependents node))) 105 | (remove-node [graph node] 106 | (MapDependencyGraph. 107 | (dissoc dependencies node) 108 | dependents))) 109 | 110 | (defn graph "Returns a new, empty, dependency graph." [] 111 | (->MapDependencyGraph {} {})) 112 | 113 | (defn depends? 114 | "True if x is directly or transitively dependent on y." 115 | [graph x y] 116 | (contains? (transitive-dependencies graph x) y)) 117 | 118 | (defn dependent? 119 | "True if y is a dependent of x." 120 | [graph x y] 121 | (contains? (transitive-dependents graph x) y)) 122 | 123 | (defn topo-sort 124 | "Returns a topologically-sorted list of nodes in graph." 125 | [graph] 126 | (loop [sorted () 127 | g graph 128 | todo (set (filter #(empty? (immediate-dependents graph %)) 129 | (nodes graph)))] 130 | (if (empty? todo) 131 | sorted 132 | (let [[node & more] (seq todo) 133 | deps (immediate-dependencies g node) 134 | [add g'] (loop [deps deps 135 | g g 136 | add #{}] 137 | (if (seq deps) 138 | (let [d (first deps) 139 | g' (remove-edge g node d)] 140 | (if (empty? (immediate-dependents g' d)) 141 | (recur (rest deps) g' (conj add d)) 142 | (recur (rest deps) g' add))) 143 | [add g]))] 144 | (recur (cons node sorted) 145 | (remove-node g' node) 146 | (clojure.set/union (set more) (set add))))))) 147 | 148 | (def ^:private max-number 149 | #?(:clj Long/MAX_VALUE :cljr Int64/MaxValue ;;; DM: Added :cljr entry 150 | :cljs js/Number.MAX_VALUE)) 151 | 152 | (defn topo-comparator 153 | "Returns a comparator fn which produces a topological sort based on 154 | the dependencies in graph. Nodes not present in the graph will sort 155 | after nodes in the graph." 156 | [graph] 157 | (let [pos (zipmap (topo-sort graph) (range))] 158 | (fn [a b] 159 | (compare (get pos a max-number) 160 | (get pos b max-number))))) 161 | -------------------------------------------------------------------------------- /src/main/clojure/clojure/tools/namespace/dir.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Stuart Sierra, 2012. All rights reserved. The use and 2 | ;; distribution terms for this software are covered by the Eclipse 3 | ;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ;; which can be found in the file epl-v10.html at the root of this 5 | ;; distribution. By using this software in any fashion, you are 6 | ;; agreeing to be bound by the terms of this license. You must not 7 | ;; remove this notice, or any other, from this software. 8 | 9 | (ns ^{:author "Stuart Sierra, modified for ClojureCLR by David Miller" 10 | :doc "Track namespace dependencies and changes by monitoring 11 | file-modification timestamps"} 12 | clojure.tools.namespace.dir 13 | (:require [clojure.tools.namespace.file :as file] 14 | [clojure.tools.namespace.find :as find] 15 | [clojure.tools.namespace.track :as track] 16 | ;;;[clojure.java.classpath :refer [classpath-directories]] 17 | [clojure.clr.io :as io] ;;; clojure.java.io 18 | [clojure.set :as set] 19 | [clojure.string :as string]) 20 | (:import (System.IO DirectoryInfo FileSystemInfo Path) (System.Text.RegularExpressions Regex))) ;;; (java.io File) (java.util.regex Pattern) 21 | 22 | (declare make-dir-info) 23 | 24 | (defn- find-files [dirs platform] 25 | (->> dirs 26 | (map make-dir-info) ;;; (map io/file) 27 | ;;; ;;; I have no idea if this is necessary. (map #(.getCanonicalFile ^File %)) 28 | (filter #(.Exists ^DirectoryInfo %)) ;;; #(.exists ^File %) 29 | (mapcat #(find/find-sources-in-dir % platform)) 30 | )) ;;; ditto: (map #(.getCanonicalFile ^File %)) 31 | 32 | (defn- milliseconds-since-epoch [^DateTime time] 33 | (long 34 | (/ (- (.-Ticks time) 35 | (.-Ticks DateTime/UnixEpoch)) 36 | TimeSpan/TicksPerMillisecond))) 37 | 38 | (defn- modified-files [tracker files] 39 | (filter #(< (::time tracker 0) (milliseconds-since-epoch (.-LastWriteTimeUtc ^FileSystemInfo %))) files)) ;;; #(< (::time tracker 0) (.lastModified ^File %)) 40 | 41 | (defn- deleted-files [tracker files] 42 | (set (remove #(file/some-file files %) (::files tracker #{})))) ;;; (set/difference (::files tracker #{}) (set files)) 43 | 44 | (defn- update-files [tracker deleted modified {:keys [read-opts]}] 45 | (let [now (milliseconds-since-epoch DateTime/UtcNow)] ;;; (System/currentTimeMillis) 46 | (-> tracker 47 | (update-in [::files] #(if % (apply disj % deleted) #{})) 48 | (file/remove-files deleted) 49 | (update-in [::files] file/into-files modified) ;;; (update-in [::files] into modified) 50 | (file/add-files modified read-opts) 51 | (assoc ::time now)))) 52 | 53 | (defn- dirs-on-classpath [] ;;; This has been replaced in JVM by a call to clojure.java.classpath/classpath-directories. We don't have that, so we're leaving this in 54 | (filter file/is-directory? ;;; #(.isDirectory ^File %) 55 | (map #(DirectoryInfo. ^String %) ;;; #(File. ^String %) 56 | (string/split 57 | (Environment/GetEnvironmentVariable "CLOJURE_LOAD_PATH") ;;; (System/getProperty "java.class.path") 58 | (Regex. (str "\\" System.IO.Path/PathSeparator)))))) ;;; (Pattern/compile (Pattern/quote File/pathSeparator)))))) 59 | 60 | (defn scan-files 61 | "Scans files to find those which have changed since the last time 62 | 'scan-files' was run; updates the dependency tracker with 63 | new/changed/deleted files. 64 | 65 | files is the collection of files to scan. 66 | 67 | Optional third argument is map of options: 68 | 69 | :platform Either clj (default) or cljs, both defined in 70 | clojure.tools.namespace.find, controls reader options for 71 | parsing files. 72 | 73 | :add-all? If true, assumes all extant files are modified regardless 74 | of filesystem timestamps." 75 | {:added "0.3.0"} 76 | ([tracker files] (scan-files tracker files nil)) 77 | ([tracker files {:keys [platform add-all?]}] 78 | (let [deleted (seq (deleted-files tracker files)) 79 | modified (if add-all? 80 | files 81 | (seq (modified-files tracker files)))] 82 | (if (or deleted modified) 83 | (update-files tracker deleted modified platform) 84 | tracker)))) 85 | 86 | (defn scan-dirs 87 | "Scans directories for files which have changed since the last time 88 | 'scan-dirs' or 'scan-files' was run; updates the dependency tracker 89 | with new/changed/deleted files. 90 | 91 | dirs is the collection of directories to scan, defaults to all 92 | directories on Clojure's classpath. 93 | 94 | Optional third argument is map of options: 95 | 96 | :platform Either clj (default) or cljs, both defined in 97 | clojure.tools.namespace.find, controls file extensions 98 | and reader options. 99 | 100 | :add-all? If true, assumes all extant files are modified regardless 101 | of filesystem timestamps." 102 | {:added "0.3.0"} 103 | ([tracker] (scan-dirs tracker nil nil)) 104 | ([tracker dirs] (scan-dirs tracker dirs nil)) 105 | ([tracker dirs {:keys [platform add-all?] :as options}] 106 | (let [ds (or (seq dirs) (dirs-on-classpath))] ;;; (classpath-directories) 107 | (scan-files tracker (find-files ds platform) options)))) 108 | 109 | (defn scan 110 | "DEPRECATED: replaced by scan-dirs. 111 | 112 | Scans directories for Clojure (.clj, .cljc) source files which have 113 | changed since the last time 'scan' was run; update the dependency 114 | tracker with new/changed/deleted files. 115 | 116 | If no dirs given, defaults to all directories on the classpath." 117 | {:added "0.2.0" 118 | :deprecated "0.3.0"} 119 | [tracker & dirs] 120 | (scan-dirs tracker dirs {:platform find/cljr})) ;;; find/clj -- is this correct? 121 | 122 | (defn scan-all 123 | "DEPRECATED: replaced by scan-dirs. 124 | 125 | Scans directories for all Clojure source files and updates the 126 | dependency tracker to reload files. If no dirs given, defaults to 127 | all directories on the classpath." 128 | {:added "0.2.0" 129 | :deprecated "0.3.0"} 130 | [tracker & dirs] 131 | (scan-dirs tracker dirs {:platform find/cljr :add-all? true})) ;;; find/clj -- is this correct? 132 | 133 | ;;; ADDED 134 | 135 | (defn- make-dir-info 136 | ^DirectoryInfo [x] 137 | (cond 138 | (instance? DirectoryInfo x) x 139 | (string? x) (DirectoryInfo. ^String x) 140 | :default (DirectoryInfo. (str x)))) 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /src/test/clojure/clojure/tools/namespace/parse_test.clj: -------------------------------------------------------------------------------- 1 | (ns clojure.tools.namespace.parse-test 2 | (:use [clojure.test :only (deftest is)] 3 | [clojure.tools.namespace.parse :only (deps-from-ns-decl 4 | read-ns-decl)])) 5 | 6 | (def ns-decl-prefix-list 7 | '(ns com.example.one 8 | (:require (com.example two 9 | [three :as three] 10 | [four :refer (a b)]) 11 | (com.example.sub [five :as five] 12 | six)) 13 | (:use (com.example seven 14 | [eight :as eight] 15 | (nine :only (c d)) 16 | [ten])))) 17 | 18 | ;; Some people like to write prefix lists as vectors, not lists. The 19 | ;; use/require functions accept this form. 20 | (def ns-decl-prefix-list-as-vector 21 | '(ns com.example.one 22 | (:require [com.example 23 | two 24 | [three :as three] 25 | [four :refer (a b)]] 26 | [com.example.sub 27 | [five :as five] 28 | six 29 | [eleven :as-alias eleven]]) 30 | (:use [com.example 31 | seven 32 | [eight :as eight] 33 | (nine :only (c d)) 34 | [ten]]))) 35 | 36 | (def ns-decl-prefix-list-clauses-as-vectors 37 | "Sometimes people even write the clauses inside ns as vectors, which 38 | clojure.core/ns allows. See TNS-21." 39 | '(ns com.example.one 40 | [:require [com.example 41 | two 42 | [three :as three] 43 | [four :refer (a b)]] 44 | [com.example.sub 45 | [five :as five] 46 | six]] 47 | [:use [com.example 48 | seven 49 | [eight :as eight] 50 | (nine :only (c d)) 51 | [ten]]])) 52 | 53 | (def deps-from-prefix-list 54 | '#{com.example.two 55 | com.example.three 56 | com.example.four 57 | com.example.sub.five 58 | com.example.sub.six 59 | com.example.seven 60 | com.example.eight 61 | com.example.nine 62 | com.example.ten}) 63 | 64 | (deftest t-prefix-list 65 | (is (= deps-from-prefix-list 66 | (deps-from-ns-decl ns-decl-prefix-list)))) 67 | 68 | (deftest t-prefix-list-as-vector 69 | (is (= deps-from-prefix-list 70 | (deps-from-ns-decl ns-decl-prefix-list-as-vector)))) 71 | 72 | (deftest t-prefix-list-clauses-as-vectors 73 | (is (= deps-from-prefix-list 74 | (deps-from-ns-decl ns-decl-prefix-list-clauses-as-vectors)))) 75 | 76 | (deftest t-no-deps-returns-empty-set 77 | (is (= #{} (deps-from-ns-decl '(ns com.example.one))))) 78 | 79 | (def multiple-ns-decls 80 | '((ns one) 81 | (ns two (:require one)) 82 | (ns three (:require [one :as o] [two :as t])))) 83 | 84 | (def multiple-ns-decls-string 85 | " (println \"Code before first ns\") 86 | (ns one) 87 | (println \"Some code\") 88 | (defn hello-world [] \"Hello, World!\") 89 | (ns two (:require one)) 90 | (println \"Some more code\") 91 | (ns three (:require [one :as o] [two :as t]))") 92 | 93 | (deftest t-read-multiple-ns-decls 94 | (with-open [rdr (clojure.lang.PushbackTextReader. ;;; java.io.PushbackReader. 95 | (System.IO.StringReader. multiple-ns-decls-string))] ;;; java.io.StringReader. 96 | (is (= multiple-ns-decls 97 | (take-while identity (repeatedly #(read-ns-decl rdr))))))) 98 | 99 | (def ns-docstring-example 100 | "The example ns declaration used in the docstring of clojure.core/ns" 101 | '(ns foo.bar 102 | (:refer-clojure :exclude [ancestors printf]) 103 | (:require (clojure.contrib sql combinatorics)) 104 | (:use (my.lib this that)) 105 | (:import (java.util Date Timer Random) 106 | (java.sql Connection Statement)))) 107 | 108 | (def deps-from-ns-docstring-example 109 | '#{clojure.contrib.sql 110 | clojure.contrib.combinatorics 111 | my.lib.this 112 | my.lib.that}) 113 | 114 | (deftest t-ns-docstring-example 115 | (is (= deps-from-ns-docstring-example 116 | (deps-from-ns-decl ns-docstring-example)))) 117 | 118 | (def require-docstring-example 119 | "The example ns declaration used in the docstring of 120 | clojure.core/require" 121 | '(ns (:require (clojure zip [set :as s])))) 122 | 123 | (def deps-from-require-docstring-example 124 | '#{clojure.zip 125 | clojure.set}) 126 | 127 | (deftest t-require-docstring-example 128 | (is (= deps-from-require-docstring-example 129 | (deps-from-ns-decl require-docstring-example)))) 130 | 131 | (def multiple-clauses 132 | "Example showing more than one :require or :use clause in one ns 133 | declaration, which clojure.core/ns allows." 134 | '(ns foo.bar 135 | (:require com.example.one) 136 | (:import java.io.File) 137 | (:require (com.example two three)) 138 | (:use (com.example [four :only [x]])) 139 | (:use (com.example (five :only [x]))))) 140 | 141 | (def deps-from-multiple-clauses 142 | '#{com.example.one 143 | com.example.two 144 | com.example.three 145 | com.example.four 146 | com.example.five}) 147 | 148 | (deftest t-deps-from-multiple-clauses 149 | (is (= deps-from-multiple-clauses 150 | (deps-from-ns-decl multiple-clauses)))) 151 | 152 | (def clauses-without-keywords 153 | "Example of require/use clauses with symbols instead of keywords, 154 | which clojure.core/ns allows." 155 | '(ns foo.bar 156 | (require com.example.one) 157 | (import java.io.File) 158 | (use (com.example (prefixes (two :only [x]) 159 | three))))) 160 | 161 | (def deps-from-clauses-without-keywords 162 | '#{com.example.one 163 | com.example.prefixes.two 164 | com.example.prefixes.three}) 165 | 166 | (deftest t-clauses-without-keywords 167 | (is (= deps-from-clauses-without-keywords 168 | (deps-from-ns-decl clauses-without-keywords)))) 169 | 170 | (def reader-conditionals-string 171 | "(ns com.examples.one 172 | (:require #?(:cljr clojure.string ;;; :clj 173 | :cljs goog.string)))") 174 | 175 | (defn str->ns-decl [^String s] 176 | (-> s 177 | System.IO.StringReader. ;;; java.io.StringReader. 178 | clojure.lang.PushbackTextReader. ;;; java.io.PushbackReader. 179 | read-ns-decl)) 180 | 181 | (deftest t-reader-conditionals 182 | (when (resolve 'clojure.core/reader-conditional?) 183 | (let [actual (-> reader-conditionals-string 184 | str->ns-decl 185 | deps-from-ns-decl)] 186 | (is (= #{'clojure.string} actual))))) 187 | 188 | (def cljs-ns-with-npm-dependency 189 | "(ns com.examples.one 190 | (:require [\"foobar\"] [baz]))") 191 | 192 | (deftest cljs-string-dependency 193 | (let [actual (-> cljs-ns-with-npm-dependency 194 | str->ns-decl 195 | deps-from-ns-decl)] 196 | (is (= #{'baz} actual)))) 197 | 198 | (def cljs-ns-with-require-macros 199 | "(ns com.examples.one 200 | (:require-macros [org.macros :refer [my-macro]]))") 201 | 202 | (deftest cljs-require-macros 203 | (let [actual (-> cljs-ns-with-require-macros 204 | str->ns-decl 205 | deps-from-ns-decl)] 206 | (is (= #{'org.macros} actual)))) -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # Change Log for tools.namespace 2 | 3 | 4 | ## 0.3.x series 5 | 6 | ### Version 0.3.0-alpha1 on 14-Aug-2015 7 | 8 | * **Partial ClojureScript Support** [TNS-35] 9 | 10 | * Platform-neutral namespaces were converted to conditional-read 11 | (`.cljc`) files: c.t.n.dependency, c.t.n.track, and 12 | c.t.n.parse. These namespaces can be used in ClojureScript 13 | programs. 14 | 15 | * Added support for finding, parsing, and analyzing 16 | ClojureScript source files (`.cljs` and `.cljc`) in 17 | c.t.n.file, c.t.n.parse, c.t.n.dir, and c.t.n.find with 18 | optional "platform" arguments. These namespaces still only 19 | **run** on the Clojure(JVM) platform. 20 | 21 | * Reloading and interactive features remain Clojure(JVM) only 22 | for now: c.t.n.move, c.t.n.reload, and c.t.n.repl 23 | 24 | * Uses [tools.reader] for platform-independent parsing and 25 | conditional-reader support. 26 | 27 | * Minimum Clojure version is 1.7.0 28 | 29 | * Breaking change: `c.t.n.parse/read-ns-decl` no longer returns 30 | `nil` on syntax errors. Instead, exceptions are allowed to 31 | propagate up from tools.reader. This change only affects code 32 | which calls `read-ns-decl` directly. c.t.n.file and c.t.n.find 33 | will catch and ignore reader exeptions when trying to read 34 | namespace declarations. 35 | 36 | * Enhancement [TNS-36]: Use java.classpath for better JVM classpath 37 | resolution 38 | 39 | * Possible breaking change: parse/read-ns-decl does not capture 40 | reader errors 41 | 42 | * Some definitions deprecated; see source code or Var metadata for 43 | details. 44 | 45 | 46 | 47 | ## 0.2.x series 48 | 49 | ### Version 0.2.11 on 19-Jun-2015 50 | 51 | * [TNS-34] Allow reader conditionals in parsed source files 52 | 53 | ### Version 0.2.10 on 26-Feb-2015 54 | 55 | * Widen existing functions to handle both clj and cljc files in 56 | advance of reader conditional support in Clojure 1.7. 57 | 58 | ### Version 0.2.9 on 31-Jan-2015 59 | 60 | * Fix [TNS-20]: Undefined 'unload' order after namespaces are first 61 | added to an new, empty tracker. 62 | 63 | * Improvement [TNS-21]: Support `ns` clauses which use vectors 64 | instead of lists for clauses, contrary to docs. 65 | 66 | * Improvement [TNS-32]: Support `ns` clauses which use symbols as 67 | clause heads instead of keywords, contrary to docs. 68 | 69 | ### Version 0.2.8 on 19-Dec-2014 70 | 71 | * Improvement [TNS-31]: Specific error message when `:after` symbol 72 | passed to `refresh` cannot be resolved. 73 | 74 | * Fix [TNS-26]: namespace alias recovery after failed reload did not 75 | work due to local binding shadowing global Var 76 | 77 | ### Version 0.2.7 on 19-Sept-2014 78 | 79 | * [Revert bad commit](https://github.com/clojure/tools.namespace/commit/27194f2edfe3f5f9e1343f993beca4b43f0bafe8) 80 | mistakenly included in 0.2.6 which could cause the tracker's 81 | 'unload' order to be incorrect. See discussion at [TNS-20]. 82 | 83 | ### **BROKEN** Version 0.2.6 on 7-Sept-2014 **DO NOT USE** 84 | 85 | * `clojure.tools.namespace.parse/read-ns-decl` asserts that its 86 | argument is a PushbackReader, instead of silently returning nil 87 | 88 | * Fix [TNS-22]: broken `clojure.string/replace` with Windows path 89 | separator 90 | 91 | ### Version 0.2.5 on 15-Jul-2014 92 | 93 | * New `clojure.tools.namespace.repl/clear` empties the state of the 94 | REPL dependency tracker. This can help repair the dependency 95 | tracker after a failed load or a circular dependency error. 96 | 97 | * Enhancement [TNS-19]: `deps-from-ns-decl` should return an empty 98 | set instead of nil. This may be a breaking change for some but 99 | is consistent with the original docstring. 100 | 101 | * Enhancement [TNS-18]: Compute transitive dependencies in linear time. 102 | 103 | * Enhancement [TNS-17]: The `ns` form doesn't need to be the first 104 | top-level form in a file. 105 | 106 | * Fix [TNS-16]: Don't depend on specific hash ordering in tests. 107 | Exposed by new hash functions in Clojure 1.6.0. 108 | 109 | * Fix [TNS-15]: Handle spaces in classpath directories (old 110 | `clojure.tools.namespace`) 111 | 112 | * Fix [TNS-12]: Duplicate definition of `jar-file?` 113 | 114 | ### Version 0.2.4 on 19-Jul-2013 115 | 116 | * Fix [TNS-10]: Forbid circular dependency when a namespace depends 117 | on itself 118 | 119 | * Fix [TNS-9] and [TNS-11]: support other prefix-list forms 120 | 121 | * Fix [TNS-8]: In `move-ns`, do not modify files whose contents does 122 | not change 123 | 124 | ### Version 0.2.3 on 01-Apr-2013 125 | 126 | * New: Attempt recovery of aliases/refers in REPL after error 127 | 128 | ### Version 0.2.2 on 14-Dec-2012 129 | 130 | * New: Add `:after` option to `refresh` 131 | 132 | * New: Add `clojure.tools.namespace.move` 133 | 134 | * Fix [TNS-4], reflection warnings 135 | 136 | ### Version 0.2.1 on 26-Oct-2012 137 | 138 | * Fix: Restore deprecated 0.1.x APIs in `clojure.tools.namespace` 139 | 140 | * Fix [TNS-3], actually use `refresh-dirs` 141 | 142 | ### **BROKEN** Version 0.2.0 on 05-Oct-2012 **DO NOT USE** 143 | 144 | * Not recommended for use: this release introduced breaking API 145 | changes (renaming core namespaces and functions) without 146 | backwards-compatibility. Applications with dependencies on both 147 | the 0.2.x and 0.1.x APIs cannot use this version. 148 | 149 | * New dependency tracking & reloading features 150 | 151 | * Eliminate dependency on [java.classpath] 152 | 153 | 154 | 155 | ## 0.1.x series 156 | 157 | ### Version 0.1.3 on 24-Apr-2012 158 | 159 | * Fix [TNS-1] Workaround for Clojure 1.2 reader bug 160 | 161 | ### Version 0.1.2 on 10-Feb-2012 162 | 163 | * Fix: Eliminate reflection warnings 164 | 165 | ### Version 0.1.1 on 18-May-2011 166 | 167 | ### Version 0.1.0 on 24-Apr-2011 168 | 169 | * Source-compatible with clojure.contrib.find-namespaces in old 170 | clojure-contrib 1.2.0 171 | 172 | [TNS-1]: http://dev.clojure.org/jira/browse/TNS-1 173 | [TNS-3]: http://dev.clojure.org/jira/browse/TNS-3 174 | [TNS-4]: http://dev.clojure.org/jira/browse/TNS-4 175 | [TNS-8]: http://dev.clojure.org/jira/browse/TNS-8 176 | [TNS-9]: http://dev.clojure.org/jira/browse/TNS-9 177 | [TNS-10]: http://dev.clojure.org/jira/browse/TNS-10 178 | [TNS-11]: http://dev.clojure.org/jira/browse/TNS-11 179 | [TNS-12]: http://dev.clojure.org/jira/browse/TNS-12 180 | [TNS-15]: http://dev.clojure.org/jira/browse/TNS-15 181 | [TNS-16]: http://dev.clojure.org/jira/browse/TNS-16 182 | [TNS-17]: http://dev.clojure.org/jira/browse/TNS-17 183 | [TNS-18]: http://dev.clojure.org/jira/browse/TNS-18 184 | [TNS-19]: http://dev.clojure.org/jira/browse/TNS-19 185 | [TNS-20]: http://dev.clojure.org/jira/browse/TNS-20 186 | [TNS-21]: http://dev.clojure.org/jira/browse/TNS-21 187 | [TNS-22]: http://dev.clojure.org/jira/browse/TNS-22 188 | [TNS-23]: http://dev.clojure.org/jira/browse/TNS-23 189 | [TNS-24]: http://dev.clojure.org/jira/browse/TNS-24 190 | [TNS-25]: http://dev.clojure.org/jira/browse/TNS-25 191 | [TNS-26]: http://dev.clojure.org/jira/browse/TNS-26 192 | [TNS-27]: http://dev.clojure.org/jira/browse/TNS-27 193 | [TNS-28]: http://dev.clojure.org/jira/browse/TNS-28 194 | [TNS-29]: http://dev.clojure.org/jira/browse/TNS-29 195 | [TNS-30]: http://dev.clojure.org/jira/browse/TNS-30 196 | [TNS-31]: http://dev.clojure.org/jira/browse/TNS-31 197 | [TNS-32]: http://dev.clojure.org/jira/browse/TNS-32 198 | [TNS-33]: http://dev.clojure.org/jira/browse/TNS-33 199 | [TNS-34]: http://dev.clojure.org/jira/browse/TNS-34 200 | [TNS-35]: http://dev.clojure.org/jira/browse/TNS-35 201 | [TNS-36]: http://dev.clojure.org/jira/browse/TNS-36 202 | [java.classpath]: https://github.com/clojure/java.classpath 203 | [tools.reader]: https://github.com/clojure/tools.reader 204 | [JEP 122]: http://openjdk.java.net/jeps/122 -------------------------------------------------------------------------------- /src/main/clojure/clojure/tools/namespace/repl.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Stuart Sierra, 2012. All rights reserved. The use and 2 | ;; distribution terms for this software are covered by the Eclipse 3 | ;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ;; which can be found in the file epl-v10.html at the root of this 5 | ;; distribution. By using this software in any fashion, you are 6 | ;; agreeing to be bound by the terms of this license. You must not 7 | ;; remove this notice, or any other, from this software. 8 | 9 | (ns ^{:author "Stuart Sierra" 10 | :doc "REPL utilities for working with namespaces"} 11 | clojure.tools.namespace.repl 12 | (:require [clojure.tools.namespace.track :as track] 13 | [clojure.tools.namespace.dir :as dir] 14 | [clojure.tools.namespace.find :as find] 15 | [clojure.tools.namespace.reload :as reload])) 16 | 17 | (defonce refresh-tracker (track/tracker)) 18 | 19 | (defonce refresh-dirs []) 20 | 21 | (defn- print-and-return [tracker] 22 | (if-let [e (::reload/error tracker)] 23 | (do (when (thread-bound? #'*e) 24 | (set! *e e)) 25 | (prn :error-while-loading (::reload/error-ns tracker)) 26 | e) 27 | :ok)) 28 | 29 | (defn- print-pending-reloads [tracker] 30 | (prn :reloading (::track/load tracker))) 31 | 32 | (defn- load-disabled? [sym] 33 | (false? (::load (meta (find-ns sym))))) 34 | 35 | (defn- unload-disabled? [sym] 36 | (or (false? (::unload (meta (find-ns sym)))) 37 | (load-disabled? sym))) 38 | 39 | (defn- remove-disabled [tracker] 40 | (-> tracker 41 | (update-in [::track/unload] #(remove unload-disabled? %)) 42 | (update-in [::track/load] #(remove load-disabled? %)))) 43 | 44 | (defn- referred 45 | "Given a Namespace object, returns a map of symbols describing the 46 | Vars it refers from other namespaces, in the following form: 47 | 48 | {other-namespace-name {symbol-in-other-ns symbol-in-this-ns}}" 49 | [ns] 50 | (reduce (fn [m [sym var]] 51 | (let [ns-name (ns-name (:ns (meta var))) 52 | var-name (:name (meta var))] 53 | (assoc-in m [ns-name var-name] sym))) 54 | {} 55 | (ns-refers ns))) 56 | 57 | (defn- aliased 58 | "Given a namespace object, returns a map of symbols describing its 59 | aliases, in the following form: 60 | 61 | {alias-symbol namespace-name}" 62 | [ns] 63 | (reduce (fn [m [alias n]] (assoc m alias (ns-name n))) 64 | {} (ns-aliases ns))) 65 | 66 | (defn- recover-ns 67 | "Given the maps returned by 'referred' and 'aliased', attempts to 68 | restore as many bindings as possible into the current namespace. Any 69 | bindings to namespaces or Vars which do not currently exist will be 70 | ignored." 71 | [refers aliases] 72 | (doseq [[ns-name symbol-map] refers] 73 | (when-let [ns (find-ns ns-name)] 74 | (doseq [[source-name target-name] symbol-map] 75 | (when (ns-resolve ns source-name) 76 | (if (= source-name target-name) 77 | (refer ns-name :only (list source-name)) 78 | (refer ns-name :only () :rename {source-name target-name})))))) 79 | (doseq [[alias-sym ns-name] aliases] 80 | (when (find-ns ns-name) 81 | (alias alias-sym ns-name)))) 82 | 83 | (defn scan 84 | "Scans directories for files which have changed since the last time 85 | 'scan' or 'refresh' was run; updates the dependency tracker 86 | with new/changed/deleted files. 87 | 88 | Optional argument is map of options: 89 | 90 | :platform Either clj (default) or cljs, both defined in 91 | clojure.tools.namespace.find, controls file extensions 92 | and reader options. 93 | 94 | :add-all? If true, assumes all extant files are modified regardless 95 | of filesystem timestamps. 96 | 97 | Returns map with keys: 98 | 99 | ::track/unload list of namespace symbols that will be unloaded 100 | ::track/load list of namespace symbols that will be loaded" 101 | ([] 102 | (scan nil)) 103 | ([options] 104 | (alter-var-root #'refresh-tracker 105 | #(-> % 106 | (dir/scan-dirs refresh-dirs options) 107 | (remove-disabled))))) 108 | 109 | (defn refresh-scanned 110 | "Reloads namespaces in dependency order. Does not scan directories again, 111 | expected to be used after 'scan'. 112 | 113 | Returns :ok or an error; sets the latest exception to 114 | clojure.core/*e (if *e is thread-bound). 115 | 116 | The directories to be scanned are controlled by 'set-refresh-dirs'; 117 | defaults to all directories on the Java classpath. 118 | 119 | Options are key-value pairs. Valid options are: 120 | 121 | :after Namespace-qualified symbol naming a zero-argument 122 | function to be invoked after a successful refresh. This 123 | symbol will be resolved *after* all namespaces have 124 | been reloaded." 125 | [& options] 126 | (let [{:keys [after]} options] 127 | (when after 128 | (assert (symbol? after) ":after value must be a symbol") 129 | (assert (namespace after) 130 | ":after value must be a namespace-qualified symbol")) 131 | (let [current-ns-name (ns-name *ns*) 132 | current-ns-refers (referred *ns*) 133 | current-ns-aliases (aliased *ns*)] 134 | (print-pending-reloads refresh-tracker) 135 | (alter-var-root #'refresh-tracker reload/track-reload) 136 | (in-ns current-ns-name) 137 | (let [result (print-and-return refresh-tracker)] 138 | (if (= :ok result) 139 | (if after 140 | (if-let [after (ns-resolve *ns* after)] 141 | (after) 142 | (throw (Exception. 143 | (str "Cannot resolve :after symbol " after)))) 144 | result) 145 | ;; There was an error, recover as much as we can: 146 | (do (when-not (or (false? (::unload (meta *ns*))) 147 | (false? (::load (meta *ns*)))) 148 | (recover-ns current-ns-refers current-ns-aliases)) 149 | ;; Return the Exception to the REPL: 150 | result)))))) 151 | 152 | (defn disable-unload! 153 | "Adds metadata to namespace (or *ns* if unspecified) telling 154 | 'refresh' not to unload it. The namespace may still be reloaded, it 155 | just won't be removed first. 156 | 157 | Warning: Aliases to reloaded namespaces will break." 158 | ([] (disable-unload! *ns*)) 159 | ([namespace] (alter-meta! namespace assoc ::unload false))) 160 | 161 | (defn disable-reload! 162 | "Adds metadata to namespace (or *ns* if unspecified) telling 163 | 'refresh' not to load it. Implies disable-unload! also. 164 | 165 | Warning: Aliases to reloaded namespaces will break." 166 | ([] (disable-reload! *ns*)) 167 | ([namespace] (alter-meta! namespace assoc ::load false))) 168 | 169 | (defn refresh 170 | "Scans source code directories for files which have changed (since 171 | the last time this function was run) and reloads them in dependency 172 | order. Returns :ok or an error; sets the latest exception to 173 | clojure.core/*e (if *e is thread-bound). 174 | 175 | The directories to be scanned are controlled by 'set-refresh-dirs'; 176 | defaults to all directories on the Java classpath. 177 | 178 | Options are key-value pairs. Valid options are: 179 | 180 | :after Namespace-qualified symbol naming a zero-argument 181 | function to be invoked after a successful refresh. This 182 | symbol will be resolved *after* all namespaces have 183 | been reloaded." 184 | [& options] 185 | (let [{:keys [after]} options] 186 | (scan {:platform find/clj}) 187 | (apply refresh-scanned options))) 188 | 189 | (defn refresh-all 190 | "Scans source code directories for all Clojure source files and 191 | reloads them in dependency order. 192 | 193 | The directories to be scanned are controlled by 'set-refresh-dirs'; 194 | defaults to all directories on the Java classpath. 195 | 196 | Options are key-value pairs. Valid options are: 197 | 198 | :after Namespace-qualified symbol naming a zero-argument 199 | function to be invoked after a successful refresh. This 200 | symbol will be resolved *after* all namespaces have 201 | been reloaded." 202 | [& options] 203 | (let [{:keys [after]} options] 204 | (scan {:platform find/clj 205 | :add-all? true}) 206 | (apply refresh-scanned options))) 207 | 208 | (defn set-refresh-dirs 209 | "Sets the directories which are scanned by 'refresh'. Supports the 210 | same types as clojure.java.io/file." 211 | [& dirs] 212 | (alter-var-root #'refresh-dirs (constantly dirs))) 213 | 214 | (defn clear 215 | "Clears all state from the namespace/file tracker. This may help 216 | repair the namespace tracker when it gets into an inconsistent 217 | state, without restarting the Clojure process. The next call to 218 | 'refresh' will reload all source files, but may not completely 219 | remove stale code from deleted files." 220 | [] 221 | (alter-var-root #'refresh-tracker (constantly (track/tracker)))) -------------------------------------------------------------------------------- /src/test/clojure/clojure/tools/namespace/dependency_test.clj: -------------------------------------------------------------------------------- 1 | (ns clojure.tools.namespace.dependency-test 2 | (:use clojure.test 3 | clojure.tools.namespace.dependency)) 4 | 5 | ;; building a graph like: 6 | ;; 7 | ;; :a 8 | ;; / | 9 | ;; :b | 10 | ;; \ | 11 | ;; :c 12 | ;; | 13 | ;; :d 14 | ;; 15 | (def g1 (-> (graph) 16 | (depend :b :a) ; "B depends on A" 17 | (depend :c :b) ; "C depends on B" 18 | (depend :c :a) ; "C depends on A" 19 | (depend :d :c))) ; "D depends on C" 20 | 21 | ;; 'one 'five 22 | ;; | | 23 | ;; 'two | 24 | ;; / \ | 25 | ;; / \ | 26 | ;; / \ / 27 | ;; 'three 'four 28 | ;; | / 29 | ;; 'six / 30 | ;; | / 31 | ;; | / 32 | ;; | / 33 | ;; 'seven 34 | ;; 35 | (def g2 (-> (graph) 36 | (depend 'two 'one) 37 | (depend 'three 'two) 38 | (depend 'four 'two) 39 | (depend 'four 'five) 40 | (depend 'six 'three) 41 | (depend 'seven 'six) 42 | (depend 'seven 'four))) 43 | 44 | ;; :level0 45 | ;; / | | \ 46 | ;; ----- | | ----- 47 | ;; / | | \ 48 | ;; :level1a :level1b :level1c :level1d 49 | ;; \ | | / 50 | ;; ----- | | ----- 51 | ;; \ | | / 52 | ;; :level2 53 | ;; / | | \ 54 | ;; ----- | | ----- 55 | ;; / | | \ 56 | ;; :level3a :level3b :level3c :level3d 57 | ;; \ | | / 58 | ;; ----- | | ----- 59 | ;; \ | | / 60 | ;; :level4 61 | ;; 62 | ;; ... and so on in a repeating pattern like that, up to :level26 63 | 64 | (def g3 (-> (graph) 65 | (depend :level1a :level0) 66 | (depend :level1b :level0) 67 | (depend :level1c :level0) 68 | (depend :level1d :level0) 69 | (depend :level2 :level1a) 70 | (depend :level2 :level1b) 71 | (depend :level2 :level1c) 72 | (depend :level2 :level1d) 73 | 74 | (depend :level3a :level2) 75 | (depend :level3b :level2) 76 | (depend :level3c :level2) 77 | (depend :level3d :level2) 78 | (depend :level4 :level3a) 79 | (depend :level4 :level3b) 80 | (depend :level4 :level3c) 81 | (depend :level4 :level3d) 82 | 83 | (depend :level5a :level4) 84 | (depend :level5b :level4) 85 | (depend :level5c :level4) 86 | (depend :level5d :level4) 87 | (depend :level6 :level5a) 88 | (depend :level6 :level5b) 89 | (depend :level6 :level5c) 90 | (depend :level6 :level5d) 91 | 92 | (depend :level7a :level6) 93 | (depend :level7b :level6) 94 | (depend :level7c :level6) 95 | (depend :level7d :level6) 96 | (depend :level8 :level7a) 97 | (depend :level8 :level7b) 98 | (depend :level8 :level7c) 99 | (depend :level8 :level7d) 100 | 101 | (depend :level9a :level8) 102 | (depend :level9b :level8) 103 | (depend :level9c :level8) 104 | (depend :level9d :level8) 105 | (depend :level10 :level9a) 106 | (depend :level10 :level9b) 107 | (depend :level10 :level9c) 108 | (depend :level10 :level9d) 109 | 110 | (depend :level11a :level10) 111 | (depend :level11b :level10) 112 | (depend :level11c :level10) 113 | (depend :level11d :level10) 114 | (depend :level12 :level11a) 115 | (depend :level12 :level11b) 116 | (depend :level12 :level11c) 117 | (depend :level12 :level11d) 118 | 119 | (depend :level13a :level12) 120 | (depend :level13b :level12) 121 | (depend :level13c :level12) 122 | (depend :level13d :level12) 123 | (depend :level14 :level13a) 124 | (depend :level14 :level13b) 125 | (depend :level14 :level13c) 126 | (depend :level14 :level13d) 127 | 128 | (depend :level15a :level14) 129 | (depend :level15b :level14) 130 | (depend :level15c :level14) 131 | (depend :level15d :level14) 132 | (depend :level16 :level15a) 133 | (depend :level16 :level15b) 134 | (depend :level16 :level15c) 135 | (depend :level16 :level15d) 136 | 137 | (depend :level17a :level16) 138 | (depend :level17b :level16) 139 | (depend :level17c :level16) 140 | (depend :level17d :level16) 141 | (depend :level18 :level17a) 142 | (depend :level18 :level17b) 143 | (depend :level18 :level17c) 144 | (depend :level18 :level17d) 145 | 146 | (depend :level19a :level18) 147 | (depend :level19b :level18) 148 | (depend :level19c :level18) 149 | (depend :level19d :level18) 150 | (depend :level20 :level19a) 151 | (depend :level20 :level19b) 152 | (depend :level20 :level19c) 153 | (depend :level20 :level19d) 154 | 155 | (depend :level21a :level20) 156 | (depend :level21b :level20) 157 | (depend :level21c :level20) 158 | (depend :level21d :level20) 159 | (depend :level22 :level21a) 160 | (depend :level22 :level21b) 161 | (depend :level22 :level21c) 162 | (depend :level22 :level21d) 163 | 164 | (depend :level23a :level22) 165 | (depend :level23b :level22) 166 | (depend :level23c :level22) 167 | (depend :level23d :level22) 168 | (depend :level24 :level23a) 169 | (depend :level24 :level23b) 170 | (depend :level24 :level23c) 171 | (depend :level24 :level23d) 172 | 173 | (depend :level25a :level24) 174 | (depend :level25b :level24) 175 | (depend :level25c :level24) 176 | (depend :level25d :level24) 177 | (depend :level26 :level25a) 178 | (depend :level26 :level25b) 179 | (depend :level26 :level25c) 180 | (depend :level26 :level25d))) 181 | 182 | (deftest t-transitive-dependencies 183 | (is (= #{:a :c :b} 184 | (transitive-dependencies g1 :d))) 185 | (is (= '#{two four six one five three} 186 | (transitive-dependencies g2 'seven)))) 187 | 188 | (deftest t-transitive-dependencies-deep 189 | (is (= #{:level0 190 | :level1a :level1b :level1c :level1d 191 | :level2 192 | :level3a :level3b :level3c :level3d 193 | :level4 194 | :level5a :level5b :level5c :level5d 195 | :level6 196 | :level7a :level7b :level7c :level7d 197 | :level8 198 | :level9a :level9b :level9c :level9d 199 | :level10 200 | :level11a :level11b :level11c :level11d 201 | :level12 202 | :level13a :level13b :level13c :level13d 203 | :level14 204 | :level15a :level15b :level15c :level15d 205 | :level16 206 | :level17a :level17b :level17c :level17d 207 | :level18 208 | :level19a :level19b :level19c :level19d 209 | :level20 210 | :level21a :level21b :level21c :level21d 211 | :level22 212 | :level23a :level23b :level23c :level23d} 213 | (transitive-dependencies g3 :level24))) 214 | (is (= #{:level0 215 | :level1a :level1b :level1c :level1d 216 | :level2 217 | :level3a :level3b :level3c :level3d 218 | :level4 219 | :level5a :level5b :level5c :level5d 220 | :level6 221 | :level7a :level7b :level7c :level7d 222 | :level8 223 | :level9a :level9b :level9c :level9d 224 | :level10 225 | :level11a :level11b :level11c :level11d 226 | :level12 227 | :level13a :level13b :level13c :level13d 228 | :level14 229 | :level15a :level15b :level15c :level15d 230 | :level16 231 | :level17a :level17b :level17c :level17d 232 | :level18 233 | :level19a :level19b :level19c :level19d 234 | :level20 235 | :level21a :level21b :level21c :level21d 236 | :level22 237 | :level23a :level23b :level23c :level23d 238 | :level24 239 | :level25a :level25b :level25c :level25d} 240 | (transitive-dependencies g3 :level26)))) 241 | 242 | 243 | (deftest t-transitive-dependents 244 | (is (= '#{four seven} 245 | (transitive-dependents g2 'five))) 246 | (is (= '#{four seven six three} 247 | (transitive-dependents g2 'two)))) 248 | 249 | (defn- before? 250 | "True if x comes before y in an ordered collection." 251 | [coll x y] 252 | (loop [[item & more] (seq coll)] 253 | (cond (nil? item) true ; end of the seq 254 | (= x item) true ; x comes first 255 | (= y item) false 256 | :else (recur more)))) 257 | 258 | (deftest t-before 259 | (is (true? (before? [:a :b :c :d] :a :b))) 260 | (is (true? (before? [:a :b :c :d] :b :c))) 261 | (is (false? (before? [:a :b :c :d] :d :c))) 262 | (is (false? (before? [:a :b :c :d] :c :a)))) 263 | 264 | (deftest t-topo-comparator-1 265 | (let [sorted (sort (topo-comparator g1) [:d :a :b :foo])] 266 | (are [x y] (before? sorted x y) 267 | :a :b 268 | :a :d 269 | :a :foo 270 | :b :d 271 | :b :foo 272 | :d :foo))) 273 | 274 | (deftest t-topo-comparator-2 275 | (let [sorted (sort (topo-comparator g2) '[three seven nine eight five])] 276 | (are [x y] (before? sorted x y) 277 | 'three 'seven 278 | 'three 'eight 279 | 'three 'nine 280 | 'five 'eight 281 | 'five 'nine 282 | 'seven 'eight 283 | 'seven 'nine))) 284 | 285 | (deftest t-topo-sort 286 | (let [sorted (topo-sort g2)] 287 | (are [x y] (before? sorted x y) 288 | 'one 'two 289 | 'one 'three 290 | 'one 'four 291 | 'one 'six 292 | 'one 'seven 293 | 'two 'three 294 | 'two 'four 295 | 'two 'six 296 | 'two 'seven 297 | 'three 'six 298 | 'three 'seven 299 | 'four 'seven 300 | 'five 'four 301 | 'five 'seven 302 | 'six 'seven))) 303 | 304 | (deftest t-no-cycles 305 | (is (thrown? Exception 306 | (-> (graph) 307 | (depend :a :b) 308 | (depend :b :c) 309 | (depend :c :a))))) 310 | 311 | (deftest t-no-self-cycles 312 | (is (thrown? Exception 313 | (-> (graph) 314 | (depend :a :b) 315 | (depend :a :a))))) -------------------------------------------------------------------------------- /src/main/clojure/clojure/tools/namespace/find.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Stuart Sierra, 2012. All rights reserved. The use and 2 | ;; distribution terms for this software are covered by the Eclipse 3 | ;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ;; which can be found in the file epl-v10.html at the root of this 5 | ;; distribution. By using this software in any fashion, you are 6 | ;; agreeing to be bound by the terms of this license. You must not 7 | ;; remove this notice, or any other, from this software. 8 | 9 | (ns 10 | ^{:author "Stuart Sierra, modified for ClojureCLR by David Miller", 11 | :doc "Search for namespace declarations in directories and JAR files."} 12 | clojure.tools.namespace.find 13 | (:require ;;; Doesn't exist for us: [clojure.java.classpath :as classpath] 14 | [clojure.clr.io :as io] ;;; clojure.java.io 15 | [clojure.set :as set] 16 | [clojure.tools.namespace.file :as file] 17 | [clojure.tools.namespace.parse :as parse]) 18 | (:import (System.IO TextReader ;;; (java.io File FileReader BufferedReader PushbackReader 19 | FileInfo DirectoryInfo FileSystemInfo) ;;; InputStreamReader) 20 | (clojure.lang PushbackTextReader))) ;;; (java.util.jar JarFile JarEntry))) 21 | 22 | (set! *warn-on-reflection* true) 23 | 24 | (def ^{:added "0.3.0"} 25 | clj 26 | "Platform definition of file extensions and reader options for 27 | Clojure (.clj and .cljc) source files." 28 | {:read-opts parse/clj-read-opts 29 | :extensions file/clojure-extensions}) 30 | 31 | (def ^{:added "0.3.0"} 32 | cljs 33 | "Platform definition of file extensions and reader options for 34 | ClojureScript (.cljs and .cljc) source files." 35 | {:read-opts parse/cljs-read-opts 36 | :extensions file/clojurescript-extensions}) 37 | 38 | (def ^{:added "0.3.0"} 39 | cljr 40 | "Platform definition of file extensions and reader options for 41 | ClojureCLR (.cljr and .cljc) source files." 42 | {:read-opts parse/cljr-read-opts 43 | :extensions file/clojure-clr-extensions}) 44 | 45 | (defmacro ^:private ignore-reader-exception 46 | "If body throws an exception caused by a syntax error (from 47 | tools.reader), returns nil. Rethrows other exceptions." 48 | [& body] 49 | `(try ~@body 50 | (catch Exception e# 51 | (if (= :reader-exception (:type (ex-data e#))) 52 | nil 53 | (throw e#))))) 54 | 55 | ;;; Finding namespaces in a directory tree 56 | 57 | (defn- sort-files-breadth-first 58 | [files] 59 | (sort-by #(.FullName ^FileSystemInfo %) files)) ;#(.getAbsolutePath ^File %) 60 | 61 | (defn find-sources-in-dir 62 | "Searches recursively under dir for source files. Returns a sequence 63 | of File objects, in breadth-first sort order. 64 | 65 | Optional second argument is either clj (default) or cljs, both 66 | defined in clojure.tools.namespace.find." 67 | {:added "0.3.0"} 68 | ([dir] 69 | (find-sources-in-dir dir nil)) 70 | ([^DirectoryInfo dir platform] ;;; ^File 71 | (let [{:keys [extensions]} (or platform clj)] 72 | (->> (file-seq dir) 73 | (filter #(file/file-with-extension? % extensions)) 74 | sort-files-breadth-first)))) 75 | 76 | (defn find-clojure-sources-in-dir 77 | "DEPRECATED: replaced by find-sources-in-dir 78 | 79 | Searches recursively under dir for Clojure source files (.clj, .cljc). 80 | Returns a sequence of File objects, in breadth-first sort order." ;;; SystemFileInfo 81 | {:added "0.2.0" 82 | :deprecated "0.3.0"} 83 | [^FileSystemInfo dir] ;;; ^File 84 | (find-sources-in-dir dir clj)) 85 | 86 | (defn find-ns-decls-in-dir 87 | "Searches dir recursively for (ns ...) declarations in Clojure 88 | source files; returns the unevaluated ns declarations. 89 | The symbol name in the returned ns declaration will contain metadata 90 | for the corresponding directory and located file within at keys 91 | :dir and :file. 92 | 93 | Optional second argument platform is either clj (default) or cljs, 94 | both defined in clojure.tools.namespace.find." 95 | {:added "0.2.0"} 96 | ([dir] (find-ns-decls-in-dir dir nil)) 97 | ([dir platform] 98 | (keep #(ignore-reader-exception 99 | (let [[_ nom & more :as decl] (file/read-file-ns-decl % (:read-opts platform))] 100 | (when (and decl nom (symbol? nom)) 101 | (list* 'ns (with-meta nom 102 | {:dir (.Name ^DirectoryInfo dir) :file (.Name ^FileInfo %)}) ;; .getName ^java.io.File x 2 103 | more)))) 104 | (find-sources-in-dir dir platform)))) 105 | 106 | (defn find-namespaces-in-dir 107 | "Searches dir recursively for (ns ...) declarations in Clojure 108 | source files; returns the symbol names of the declared namespaces. 109 | 110 | Optional second argument platform is either clj (default) or cljs, 111 | both defined in clojure.tools.namespace.find." 112 | {:added "0.3.0"} 113 | ([dir] (find-namespaces-in-dir dir nil)) 114 | ([dir platform] 115 | (map parse/name-from-ns-decl (find-ns-decls-in-dir dir platform)))) 116 | 117 | ;;; Finding namespaces in JAR files 118 | 119 | (defn- ends-with-extension 120 | [^String filename extensions] 121 | (some #(.EndsWith filename ^String %) extensions)) ;;; .endsWith, add type hint 122 | 123 | (defn sources-in-jar 124 | "Returns a sequence of source file names found in the JAR file. 125 | 126 | Optional second argument platform is either clj (default) or cljs, 127 | both defined in clojure.tools.namespace.find." 128 | {:added "0.3.0"} 129 | ([jar-file] 130 | (sources-in-jar jar-file nil)) 131 | ([jar-file platform] ;;; [^JarFile jar-file platform] 132 | nil)) ;;; (let [{:keys [extensions]} (or platform clj)] 133 | ;;; (filter #(ends-with-extension % extensions) 134 | ;;; (classpath/filenames-in-jar jar-file))) 135 | 136 | (defn clojure-sources-in-jar 137 | "DEPRECATED: replaced by sources-in-jar 138 | 139 | Returns a sequence of filenames ending in .clj or .cljc found in the 140 | JAR file." 141 | {:added "0.2.0" 142 | :deprecated "0.3.0"} 143 | [jar-file] 144 | (sources-in-jar jar-file clj)) 145 | 146 | (defn read-ns-decl-from-jarfile-entry 147 | "Attempts to read a (ns ...) declaration from the named entry in the 148 | JAR file, and returns the unevaluated form. Returns nil if read 149 | fails due to invalid syntax or if a ns declaration cannot be found. 150 | The symbol name in the returned ns declaration will contain metadata 151 | for the corresponding jar filename and located file within at keys 152 | :jar and :file. 153 | 154 | Optional third argument platform is either clj (default) or cljs, 155 | both defined in clojure.tools.namespace.find." 156 | ([jarfile entry-name] 157 | (read-ns-decl-from-jarfile-entry jarfile entry-name nil)) 158 | ([jarfile ^String entry-name platorm] ;;; [^JarFile jarfile ^String entry-name platform] 159 | nil)) ;;; (let [{:keys [read-opts]} (or platform clj)] 160 | ;;; (with-open [rdr (PushbackReader. 161 | ;;; (io/reader 162 | ;;; (.getInputStream jarfile (.getEntry jarfile entry-name))))] 163 | ;;; (ignore-reader-exception 164 | ;;; (let [[_ nom & more :as decl] (parse/read-ns-decl rdr read-opts)] 165 | ;;; (when (and decl nom (symbol? nom)) 166 | ;;; (list* 'ns (with-meta nom 167 | ;;; {:jar (.getName ^JarFile jarfile) :file entry-name}) 168 | ;;; more)))))))) 169 | (defn find-ns-decls-in-jarfile 170 | "Searches the JAR file for source files containing (ns ...) 171 | declarations; returns the unevaluated ns declarations. 172 | 173 | Optional second argument platform is either clj (default) or cljs, 174 | both defined in clojure.tools.namespace.find." 175 | ([jarfile] 176 | (find-ns-decls-in-jarfile jarfile nil)) 177 | ([jarfile platform] ;;; ^JarFile 178 | (keep #(read-ns-decl-from-jarfile-entry jarfile % platform) 179 | (sources-in-jar jarfile platform)))) 180 | 181 | (defn find-namespaces-in-jarfile 182 | "Searches the JAR file for platform source files containing (ns ...) 183 | declarations. Returns a sequence of the symbol names of the 184 | declared namespaces. 185 | 186 | Optional second argument platform is either clj (default) or cljs, 187 | both defined in clojure.tools.namespace.find." 188 | ([jarfile] 189 | (find-namespaces-in-jarfile jarfile nil)) 190 | ([jarfile platform] ;;; ^JarFile 191 | (map parse/name-from-ns-decl (find-ns-decls-in-jarfile jarfile platform)))) 192 | 193 | 194 | ;;; Finding namespaces 195 | 196 | (defn find-ns-decls 197 | "Searches a sequence of java.io.File objects (both directories and 198 | JAR files) for platform source files containing (ns...) 199 | declarations. Returns a sequence of the unevaluated ns declaration 200 | forms. Use with clojure.java.classpath to search Clojure's 201 | classpath. 202 | 203 | Optional second argument platform is either clj (default) or cljs, 204 | both defined in clojure.tools.namespace.find." 205 | ([files] 206 | (find-ns-decls files nil)) 207 | ([files platform] 208 | (concat 209 | (mapcat #(find-ns-decls-in-dir % platform) 210 | (filter file/is-directory? files)) ;;; #(.isDirectory ^File %) 211 | ;;;(mapcat #(find-ns-decls-in-jarfile % platform) 212 | ;;; (map #(JarFile. (io/file %)) 213 | ))) ;;; (filter classpath/jar-file? files))) 214 | 215 | (defn find-namespaces 216 | "Searches a sequence of java.io.File objects (both directories and 217 | JAR files) for platform source files containing (ns...) 218 | declarations. Returns a sequence of the symbol names of the declared 219 | namespaces. Use with clojure.java.classpath to search Clojure's 220 | classpath. 221 | 222 | Optional second argument platform is either clj (default) or cljs, 223 | both defined in clojure.tools.namespace.find." 224 | ([files] 225 | (find-namespaces files nil)) 226 | ([files platform] 227 | (map parse/name-from-ns-decl (find-ns-decls files platform)))) -------------------------------------------------------------------------------- /epl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Eclipse Public License - Version 1.0 8 | 25 | 26 | 27 | 28 | 29 | 30 |

Eclipse Public License - v 1.0

31 | 32 |

THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE 33 | PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR 34 | DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS 35 | AGREEMENT.

36 | 37 |

1. DEFINITIONS

38 | 39 |

"Contribution" means:

40 | 41 |

a) in the case of the initial Contributor, the initial 42 | code and documentation distributed under this Agreement, and

43 |

b) in the case of each subsequent Contributor:

44 |

i) changes to the Program, and

45 |

ii) additions to the Program;

46 |

where such changes and/or additions to the Program 47 | originate from and are distributed by that particular Contributor. A 48 | Contribution 'originates' from a Contributor if it was added to the 49 | Program by such Contributor itself or anyone acting on such 50 | Contributor's behalf. Contributions do not include additions to the 51 | Program which: (i) are separate modules of software distributed in 52 | conjunction with the Program under their own license agreement, and (ii) 53 | are not derivative works of the Program.

54 | 55 |

"Contributor" means any person or entity that distributes 56 | the Program.

57 | 58 |

"Licensed Patents" mean patent claims licensable by a 59 | Contributor which are necessarily infringed by the use or sale of its 60 | Contribution alone or when combined with the Program.

61 | 62 |

"Program" means the Contributions distributed in accordance 63 | with this Agreement.

64 | 65 |

"Recipient" means anyone who receives the Program under 66 | this Agreement, including all Contributors.

67 | 68 |

2. GRANT OF RIGHTS

69 | 70 |

a) Subject to the terms of this Agreement, each 71 | Contributor hereby grants Recipient a non-exclusive, worldwide, 72 | royalty-free copyright license to reproduce, prepare derivative works 73 | of, publicly display, publicly perform, distribute and sublicense the 74 | Contribution of such Contributor, if any, and such derivative works, in 75 | source code and object code form.

76 | 77 |

b) Subject to the terms of this Agreement, each 78 | Contributor hereby grants Recipient a non-exclusive, worldwide, 79 | royalty-free patent license under Licensed Patents to make, use, sell, 80 | offer to sell, import and otherwise transfer the Contribution of such 81 | Contributor, if any, in source code and object code form. This patent 82 | license shall apply to the combination of the Contribution and the 83 | Program if, at the time the Contribution is added by the Contributor, 84 | such addition of the Contribution causes such combination to be covered 85 | by the Licensed Patents. The patent license shall not apply to any other 86 | combinations which include the Contribution. No hardware per se is 87 | licensed hereunder.

88 | 89 |

c) Recipient understands that although each Contributor 90 | grants the licenses to its Contributions set forth herein, no assurances 91 | are provided by any Contributor that the Program does not infringe the 92 | patent or other intellectual property rights of any other entity. Each 93 | Contributor disclaims any liability to Recipient for claims brought by 94 | any other entity based on infringement of intellectual property rights 95 | or otherwise. As a condition to exercising the rights and licenses 96 | granted hereunder, each Recipient hereby assumes sole responsibility to 97 | secure any other intellectual property rights needed, if any. For 98 | example, if a third party patent license is required to allow Recipient 99 | to distribute the Program, it is Recipient's responsibility to acquire 100 | that license before distributing the Program.

101 | 102 |

d) Each Contributor represents that to its knowledge it 103 | has sufficient copyright rights in its Contribution, if any, to grant 104 | the copyright license set forth in this Agreement.

105 | 106 |

3. REQUIREMENTS

107 | 108 |

A Contributor may choose to distribute the Program in object code 109 | form under its own license agreement, provided that:

110 | 111 |

a) it complies with the terms and conditions of this 112 | Agreement; and

113 | 114 |

b) its license agreement:

115 | 116 |

i) effectively disclaims on behalf of all Contributors 117 | all warranties and conditions, express and implied, including warranties 118 | or conditions of title and non-infringement, and implied warranties or 119 | conditions of merchantability and fitness for a particular purpose;

120 | 121 |

ii) effectively excludes on behalf of all Contributors 122 | all liability for damages, including direct, indirect, special, 123 | incidental and consequential damages, such as lost profits;

124 | 125 |

iii) states that any provisions which differ from this 126 | Agreement are offered by that Contributor alone and not by any other 127 | party; and

128 | 129 |

iv) states that source code for the Program is available 130 | from such Contributor, and informs licensees how to obtain it in a 131 | reasonable manner on or through a medium customarily used for software 132 | exchange.

133 | 134 |

When the Program is made available in source code form:

135 | 136 |

a) it must be made available under this Agreement; and

137 | 138 |

b) a copy of this Agreement must be included with each 139 | copy of the Program.

140 | 141 |

Contributors may not remove or alter any copyright notices contained 142 | within the Program.

143 | 144 |

Each Contributor must identify itself as the originator of its 145 | Contribution, if any, in a manner that reasonably allows subsequent 146 | Recipients to identify the originator of the Contribution.

147 | 148 |

4. COMMERCIAL DISTRIBUTION

149 | 150 |

Commercial distributors of software may accept certain 151 | responsibilities with respect to end users, business partners and the 152 | like. While this license is intended to facilitate the commercial use of 153 | the Program, the Contributor who includes the Program in a commercial 154 | product offering should do so in a manner which does not create 155 | potential liability for other Contributors. Therefore, if a Contributor 156 | includes the Program in a commercial product offering, such Contributor 157 | ("Commercial Contributor") hereby agrees to defend and 158 | indemnify every other Contributor ("Indemnified Contributor") 159 | against any losses, damages and costs (collectively "Losses") 160 | arising from claims, lawsuits and other legal actions brought by a third 161 | party against the Indemnified Contributor to the extent caused by the 162 | acts or omissions of such Commercial Contributor in connection with its 163 | distribution of the Program in a commercial product offering. The 164 | obligations in this section do not apply to any claims or Losses 165 | relating to any actual or alleged intellectual property infringement. In 166 | order to qualify, an Indemnified Contributor must: a) promptly notify 167 | the Commercial Contributor in writing of such claim, and b) allow the 168 | Commercial Contributor to control, and cooperate with the Commercial 169 | Contributor in, the defense and any related settlement negotiations. The 170 | Indemnified Contributor may participate in any such claim at its own 171 | expense.

172 | 173 |

For example, a Contributor might include the Program in a commercial 174 | product offering, Product X. That Contributor is then a Commercial 175 | Contributor. If that Commercial Contributor then makes performance 176 | claims, or offers warranties related to Product X, those performance 177 | claims and warranties are such Commercial Contributor's responsibility 178 | alone. Under this section, the Commercial Contributor would have to 179 | defend claims against the other Contributors related to those 180 | performance claims and warranties, and if a court requires any other 181 | Contributor to pay any damages as a result, the Commercial Contributor 182 | must pay those damages.

183 | 184 |

5. NO WARRANTY

185 | 186 |

EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS 187 | PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 188 | OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, 189 | ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY 190 | OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely 191 | responsible for determining the appropriateness of using and 192 | distributing the Program and assumes all risks associated with its 193 | exercise of rights under this Agreement , including but not limited to 194 | the risks and costs of program errors, compliance with applicable laws, 195 | damage to or loss of data, programs or equipment, and unavailability or 196 | interruption of operations.

197 | 198 |

6. DISCLAIMER OF LIABILITY

199 | 200 |

EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT 201 | NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, 202 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING 203 | WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF 204 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 205 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR 206 | DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED 207 | HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

208 | 209 |

7. GENERAL

210 | 211 |

If any provision of this Agreement is invalid or unenforceable under 212 | applicable law, it shall not affect the validity or enforceability of 213 | the remainder of the terms of this Agreement, and without further action 214 | by the parties hereto, such provision shall be reformed to the minimum 215 | extent necessary to make such provision valid and enforceable.

216 | 217 |

If Recipient institutes patent litigation against any entity 218 | (including a cross-claim or counterclaim in a lawsuit) alleging that the 219 | Program itself (excluding combinations of the Program with other 220 | software or hardware) infringes such Recipient's patent(s), then such 221 | Recipient's rights granted under Section 2(b) shall terminate as of the 222 | date such litigation is filed.

223 | 224 |

All Recipient's rights under this Agreement shall terminate if it 225 | fails to comply with any of the material terms or conditions of this 226 | Agreement and does not cure such failure in a reasonable period of time 227 | after becoming aware of such noncompliance. If all Recipient's rights 228 | under this Agreement terminate, Recipient agrees to cease use and 229 | distribution of the Program as soon as reasonably practicable. However, 230 | Recipient's obligations under this Agreement and any licenses granted by 231 | Recipient relating to the Program shall continue and survive.

232 | 233 |

Everyone is permitted to copy and distribute copies of this 234 | Agreement, but in order to avoid inconsistency the Agreement is 235 | copyrighted and may only be modified in the following manner. The 236 | Agreement Steward reserves the right to publish new versions (including 237 | revisions) of this Agreement from time to time. No one other than the 238 | Agreement Steward has the right to modify this Agreement. The Eclipse 239 | Foundation is the initial Agreement Steward. The Eclipse Foundation may 240 | assign the responsibility to serve as the Agreement Steward to a 241 | suitable separate entity. Each new version of the Agreement will be 242 | given a distinguishing version number. The Program (including 243 | Contributions) may always be distributed subject to the version of the 244 | Agreement under which it was received. In addition, after a new version 245 | of the Agreement is published, Contributor may elect to distribute the 246 | Program (including its Contributions) under the new version. Except as 247 | expressly stated in Sections 2(a) and 2(b) above, Recipient receives no 248 | rights or licenses to the intellectual property of any Contributor under 249 | this Agreement, whether expressly, by implication, estoppel or 250 | otherwise. All rights in the Program not expressly granted under this 251 | Agreement are reserved.

252 | 253 |

This Agreement is governed by the laws of the State of New York and 254 | the intellectual property laws of the United States of America. No party 255 | to this Agreement will bring a legal action under this Agreement more 256 | than one year after the cause of action arose. Each party waives its 257 | rights to a jury trial in any resulting litigation.

258 | 259 | 260 | 261 | --------------------------------------------------------------------------------