| | 28 | |
|---|
| | 29 | # Used internally by Capistrano to track which recipes have been loaded |
|---|
| | 30 | # via require, so that they may be successfully reloaded when require |
|---|
| | 31 | # is called again. |
|---|
| | 32 | def recipes_per_feature |
|---|
| | 33 | @recipes_per_feature ||= {} |
|---|
| | 34 | end |
|---|
| | 35 | |
|---|
| | 36 | # Used internally to determine what the current "feature" being |
|---|
| | 37 | # required is. This is used to track which files load which recipes |
|---|
| | 38 | # via require. |
|---|
| | 39 | def current_feature |
|---|
| | 40 | Thread.current[:capistrano_current_feature] |
|---|
| | 41 | end |
|---|
| | 42 | |
|---|
| | 43 | # Used internally to specify the current file being required, so that |
|---|
| | 44 | # any recipes loaded by that file can be remembered. This allows |
|---|
| | 45 | # recipes loaded via require to be correctly reloaded in different |
|---|
| | 46 | # Configuration instances in the same Ruby instance. |
|---|
| | 47 | def current_feature=(feature) |
|---|
| | 48 | Thread.current[:capistrano_current_feature] = feature |
|---|
| | 49 | end |
|---|
| | 108 | # |
|---|
| | 109 | # This is a bit more complicated than an initial review would seem to |
|---|
| | 110 | # necessitate, but the use case that complicates things is this: An |
|---|
| | 111 | # advanced user wants to embed capistrano, and needs to instantiate |
|---|
| | 112 | # more than one capistrano configuration at a time. They also want each |
|---|
| | 113 | # configuration to require a third-party capistrano extension. Using a |
|---|
| | 114 | # naive require implementation, this would allow the first configuration |
|---|
| | 115 | # to successfully load the third-party extension, but the require would |
|---|
| | 116 | # fail for the second configuration because the extension has already |
|---|
| | 117 | # been loaded. |
|---|
| | 118 | # |
|---|
| | 119 | # To work around this, we do a few things: |
|---|
| | 120 | # |
|---|
| | 121 | # 1. Each time a 'require' is invoked inside of a capistrano recipe, |
|---|
| | 122 | # we remember the arguments (see "current_feature"). |
|---|
| | 123 | # 2. Each time a 'load' is invoked inside of a capistrano recipe, and |
|---|
| | 124 | # "current_feature" is not nil (meaning we are inside of a pending |
|---|
| | 125 | # require) we remember the options (see "remember_load" and |
|---|
| | 126 | # "recipes_per_feature"). |
|---|
| | 127 | # 3. Each time a 'require' is invoked inside of a capistrano recipe, |
|---|
| | 128 | # we check to see if this particular configuration has ever seen these |
|---|
| | 129 | # arguments to require (see @loaded_features), and if not, we proceed |
|---|
| | 130 | # as if the file had never been required. If the superclass' require |
|---|
| | 131 | # returns false (meaning, potentially, that the file has already been |
|---|
| | 132 | # required), then we look in the recipes_per_feature collection and |
|---|
| | 133 | # load any remembered recipes from there. |
|---|
| | 134 | # |
|---|
| | 135 | # It's kind of a bear, but it works, and works transparently. Note that |
|---|
| | 136 | # a simpler implementation would just muck with $", allowing files to be |
|---|
| | 137 | # required multiple times, but that will cause warnings (and possibly |
|---|
| | 138 | # errors) if the file to be required contains constant definitions and |
|---|
| | 139 | # such, alongside (or instead of) capistrano recipe definitions. |
|---|
| 84 | | original, self.class.instance = self.class.instance, self |
|---|
| 85 | | super |
|---|
| 86 | | ensure |
|---|
| 87 | | # restore the original, so that require's can be nested |
|---|
| 88 | | self.class.instance = original |
|---|
| | 141 | # look to see if this specific configuration instance has ever seen |
|---|
| | 142 | # these arguments to require before |
|---|
| | 143 | if !@loaded_features.include?(args) |
|---|
| | 144 | @loaded_features << args |
|---|
| | 145 | |
|---|
| | 146 | begin |
|---|
| | 147 | original_instance, self.class.instance = self.class.instance, self |
|---|
| | 148 | original_feature, self.class.current_feature = self.class.current_feature, args |
|---|
| | 149 | |
|---|
| | 150 | result = super |
|---|
| | 151 | if !result # file has been required previously, load up the remembered recipes |
|---|
| | 152 | list = self.class.recipes_per_feature[args] || [] |
|---|
| | 153 | list.each { |options| load(options.merge(:reloading => true)) } |
|---|
| | 154 | end |
|---|
| | 155 | |
|---|
| | 156 | return result |
|---|
| | 157 | ensure |
|---|
| | 158 | # restore the original, so that require's can be nested |
|---|
| | 159 | self.class.instance = original_instance |
|---|
| | 160 | self.class.current_feature = original_feature |
|---|
| | 161 | end |
|---|
| | 162 | else |
|---|
| | 163 | return false |
|---|
| | 164 | end |
|---|