-
Notifications
You must be signed in to change notification settings - Fork 4.9k
/
dependencies.clj
104 lines (86 loc) · 4.32 KB
/
dependencies.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
(ns metabase.plugins.dependencies
(:require [clojure.tools.logging :as log]
[metabase.plugins.classloader :as classloader]
[metabase.util :as u]
[metabase.util.i18n :refer [trs]]))
(def ^:private plugins-with-unsatisfied-deps
(atom #{}))
(defn- dependency-type [{classname :class, plugin :plugin}]
(cond
classname :class
plugin :plugin
:else :unknown))
(defmulti ^:private dependency-satisfied?
{:arglists '([initialized-plugin-names info dependency])}
(fn [_ _ dep] (dependency-type dep)))
(defmethod dependency-satisfied? :default [_ {{plugin-name :name} :info} dep]
(log/error
(u/format-color 'red
(trs "Plugin {0} declares a dependency that Metabase does not understand: {1}" plugin-name dep))
(trs "Refer to the plugin manifest reference for a complete list of valid plugin dependencies:")
"https://github.com/metabase/metabase/wiki/Metabase-Plugin-Manifest-Reference")
false)
(defonce ^:private already-logged (atom #{}))
(defn log-once
"Log a message a single time, such as warning that a plugin cannot be initialized because of required dependencies.
Subsequent calls with duplicate messages are automatically ignored."
{:style/indent 1}
([message]
(log-once nil message))
([plugin-name-or-nil message]
(let [k [plugin-name-or-nil message]]
(when-not (contains? @already-logged k)
(swap! already-logged conj k)
(log/info message)))))
(defn- warn-about-required-dependencies [plugin-name message]
(log-once plugin-name
(str (u/format-color 'red (trs "Metabase cannot initialize plugin {0} due to required dependencies." plugin-name))
" "
message)))
(defmethod dependency-satisfied? :class
[_ {{plugin-name :name} :info} {^String classname :class, message :message, :as dep}]
(try
(Class/forName classname false (classloader/the-classloader))
(catch ClassNotFoundException _
(warn-about-required-dependencies plugin-name (or message (trs "Class not found: {0}" classname)))
false)))
(defmethod dependency-satisfied? :plugin
[initialized-plugin-names {{plugin-name :name} :info, :as info} {dep-plugin-name :plugin}]
(log-once plugin-name (trs "Plugin ''{0}'' depends on plugin ''{1}''" plugin-name dep-plugin-name))
((set initialized-plugin-names) dep-plugin-name))
(defn- all-dependencies-satisfied?*
[initialized-plugin-names {:keys [dependencies], {plugin-name :name} :info, :as info}]
(let [dep-satisfied? (fn [dep]
(u/prog1 (dependency-satisfied? initialized-plugin-names info dep)
(log-once plugin-name
(trs "{0} dependency {1} satisfied? {2}" plugin-name (dissoc dep :message) (boolean <>)))))]
(every? dep-satisfied? dependencies)))
(defn all-dependencies-satisfied?
"Check whether all dependencies are satisfied for a plugin; return truthy if all are; otherwise log explanations about
why they are not, and return falsey.
For plugins that *might* have their dependencies satisfied in the near future"
[initialized-plugin-names info]
(or
(all-dependencies-satisfied?* initialized-plugin-names info)
(do
(swap! plugins-with-unsatisfied-deps conj info)
(log-once (u/format-color 'yellow
(trs "Plugins with unsatisfied deps: {0}" (mapv (comp :name :info) @plugins-with-unsatisfied-deps))))
false)))
(defn- remove-plugins-with-satisfied-deps [plugins initialized-plugin-names ready-for-init-atom]
;; since `remove-plugins-with-satisfied-deps` could theoretically be called multiple times we need to reset the atom
;; used to return the plugins ready for init so we don't accidentally include something in there twice etc.
(reset! ready-for-init-atom nil)
(set
(for [info plugins
:let [ready? (when (all-dependencies-satisfied?* initialized-plugin-names info)
(swap! ready-for-init-atom conj info))]
:when (not ready?)]
info)))
(defn update-unsatisfied-deps!
"Updates internal list of plugins that still have unmet dependencies; returns sequence of plugin infos for all plugins
that are now ready for initialization."
[initialized-plugin-names]
(let [ready-for-init (atom nil)]
(swap! plugins-with-unsatisfied-deps remove-plugins-with-satisfied-deps initialized-plugin-names ready-for-init)
@ready-for-init))