Class: Drupid::Updater
Overview
Helper class to build or update a Drupal installation.
Defined Under Namespace
Classes: AbstractAction, DeleteAction, InstallLibraryAction, InstallProjectAction, Log, MoveAction, UpdateLibraryAction, UpdateProjectAction
Instance Attribute Summary collapse
-
#log ⇒ Object
readonly
The updater’s log.
-
#makefile ⇒ Object
readonly
A Drupid::Makefile object.
-
#platform ⇒ Object
readonly
A Drupid::Platform object.
-
#site ⇒ Object
readonly
(For multisite platforms) the site to be synchronized.
Instance Method Summary collapse
-
#apply_changes(options = {}) ⇒ Object
Applies any pending changes.
-
#exclude(project_list) ⇒ Object
Adds the given list of project names to the exclusion set of this updater.
-
#excluded ⇒ Object
Returns a list of names of projects that must be considered as processed when synchronizing.
-
#excluded?(project_name) ⇒ Boolean
Returns true if the given project is in the exclusion set of this updater; returns false otherwise.
- #get_drupal ⇒ Object
-
#initialize(makefile, platform, options = { :site => nil, :force => false }) ⇒ Updater
constructor
Creates a new updater for a given makefile and a given platform.
-
#pending_actions? ⇒ Boolean
Returns true if there are actions that have not been applied to the platform (including actions in derivative builds); returns false otherwise.
-
#prepare_derivative_build(project) ⇒ Object
Enqueues a derivative build based on the specified project (which is typically, but not necessarily, an installation profile).
-
#sync(options = {}) ⇒ Object
Tries to reconcile the makefile with the platform by resolving unmet dependencies and determining which projects must be installed, upgraded, downgraded, moved or removed.
-
#sync_drupal_core ⇒ Object
Synchronizes Drupal core.
-
#sync_libraries ⇒ Object
Synchronizes libraries between the makefile and the platform.
- #sync_library(lib) ⇒ Object
-
#sync_project(project) ⇒ Object
Performs the necessary synchronization actions for the given project.
-
#sync_projects(options = {}) ⇒ Object
Synchronizes projects between the makefile and the platform.
-
#updatedb ⇒ Object
Updates Drupal’s database using
drush updatedb
.
Methods included from Utils
#blah, #bzr, #compare_paths, #curl, #cvs, #debug, #dont_debug, #git, #hg, #ignore_interrupts, #interactive_shell, #odie, #ofail, #ohai, #owarn, #runBabyRun, #svn, #tempdir, #uncompress, #which, #writeFile
Constructor Details
#initialize(makefile, platform, options = { :site => nil, :force => false }) ⇒ Updater
Creates a new updater for a given makefile and a given platform. For multisite platforms, optionally specify a site to synchronize.
39 40 41 42 43 44 45 46 47 48 49 50 |
# File 'lib/drupid/updater.rb', line 39 def initialize(makefile, platform, = { :site => nil, :force => false }) @makefile = makefile @platform = platform @site = [:site] @log = Log.new @force_changes = [:force] # @libraries_paths = Array.new @core_projects = Array.new @derivative_builds = Array.new @excluded_projects = Hash.new end |
Instance Attribute Details
#log ⇒ Object (readonly)
The updater’s log.
35 36 37 |
# File 'lib/drupid/updater.rb', line 35 def log @log end |
#makefile ⇒ Object (readonly)
A Drupid::Makefile object.
29 30 31 |
# File 'lib/drupid/updater.rb', line 29 def makefile @makefile end |
#platform ⇒ Object (readonly)
A Drupid::Platform object.
31 32 33 |
# File 'lib/drupid/updater.rb', line 31 def platform @platform end |
#site ⇒ Object (readonly)
(For multisite platforms) the site to be synchronized.
33 34 35 |
# File 'lib/drupid/updater.rb', line 33 def site @site end |
Instance Method Details
#apply_changes(options = {}) ⇒ Object
Applies any pending changes. This is the method that actually modifies the platform. Note that applying changes may be destructive (projects may be upgraded, downgraded, deleted from the platform, moved and/or patched). Always backup your site before calling this method! If :force is set to true, changes are applied even if there are errors.
See also: Drupid::Updater.sync
Options: force, no_lockfile
352 353 354 355 356 357 358 |
# File 'lib/drupid/updater.rb', line 352 def apply_changes( = {}) raise "No changes can be applied because there are errors." if log.errors? and not [:force] log.apply_pending_actions @derivative_builds.each { |updater| updater.apply_changes(.merge(:no_lockfile => true)) } @log.clear @derivative_builds.clear end |
#exclude(project_list) ⇒ Object
Adds the given list of project names to the exclusion set of this updater.
63 64 65 |
# File 'lib/drupid/updater.rb', line 63 def exclude(project_list) project_list.each { |p| @excluded_projects[p] = true } end |
#excluded ⇒ Object
Returns a list of names of projects that must be considered as processed when synchronizing. This always include all Drupal core projects, but other projects may be added.
Requires: this updater’s platform must have been analyzed (see Drupid::Platform.analyze).
58 59 60 |
# File 'lib/drupid/updater.rb', line 58 def excluded @excluded_projects.keys end |
#excluded?(project_name) ⇒ Boolean
Returns true if the given project is in the exclusion set of this updater; returns false otherwise.
69 70 71 |
# File 'lib/drupid/updater.rb', line 69 def excluded?(project_name) @excluded_projects.has_key?(project_name) end |
#get_drupal ⇒ Object
152 153 154 155 156 157 158 159 160 161 162 163 164 |
# File 'lib/drupid/updater.rb', line 152 def get_drupal drupal = @makefile.drupal_project unless drupal # Nothing to do owarn 'No Drupal project specified.' return false end return false unless _fetch_and_patch(drupal) # Extract information about core projects, which must not be synchronized temp_platform = Platform.new(drupal.local_path) temp_platform.analyze @core_projects = temp_platform.core_project_names return true end |
#pending_actions? ⇒ Boolean
Returns true if there are actions that have not been applied to the platform (including actions in derivative builds); returns false otherwise.
76 77 78 79 80 81 82 |
# File 'lib/drupid/updater.rb', line 76 def pending_actions? return true if @log.pending_actions? @derivative_builds.each do |d| return true if d.pending_actions? end return false end |
#prepare_derivative_build(project) ⇒ Object
Enqueues a derivative build based on the specified project (which is typically, but not necessarily, an installation profile). Does nothing if the project does not contain any makefile whose name coincides with the name of the project.
88 89 90 91 92 93 94 95 96 97 98 99 100 |
# File 'lib/drupid/updater.rb', line 88 def prepare_derivative_build(project) mf = project.makefile return false if mf.nil? debug "Preparing derivative build for #{mf.basename}" submake = Makefile.new(mf) subplatform = Platform.new(@platform.local_path) subplatform.contrib_path = @platform.dest_path(project) new_updater = Updater.new(submake, subplatform, site) new_updater.exclude(project.extensions) new_updater.exclude(@platform.profiles) @derivative_builds << new_updater return true end |
#sync(options = {}) ⇒ Object
Tries to reconcile the makefile with the platform by resolving unmet dependencies and determining which projects must be installed, upgraded, downgraded, moved or removed. This method does not return anything. The result of the synchronization can be inspected by accessing Drupid::Updater#log.
This method does not modify the platform at all, it only preflights changes and caches the needed stuff locally. For changes to be applied, Drupid::Updater#apply_changes must be invoked after this method has been invoked.
If :nofollow is set to true, then this method does not try to resolve missing dependencies: it only checks the projects that are explicitly mentioned in the makefile. If :nocore is set to true, only contrib projects are synchronized; otherwise, Drupal core is synchronized, too.
See also: Drupid::Updater#apply_changes
Options: nofollow, nocore, nolibs
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/drupid/updater.rb', line 122 def sync( = {}) @log.clear @platform.analyze # These paths are needed because Drupal allows libraries to be installed # inside modules. Hence, we must ignore them when synchronizing those modules. @makefile.each_library do |l| @libraries_paths << @platform.local_path + @platform.contrib_path + l.target_path end # We always need a local copy of Drupal core (either the version specified # in the makefile or the latest version), even if we are not going to # synchronize the core, in order to extract the list of core projects. if get_drupal if [:nocore] blah "Skipping core" else sync_drupal_core end else return end sync_projects() sync_libraries unless [:nolibs] # Process derivative builds @derivative_builds.each do |updater| updater.sync(.merge(:nocore => true)) @log.merge(updater.log) end return end |
#sync_drupal_core ⇒ Object
Synchronizes Drupal core. Returns true if the synchronization is successful; returns false otherwise.
169 170 171 172 173 174 175 176 |
# File 'lib/drupid/updater.rb', line 169 def sync_drupal_core if @platform.drupal_project _compare_versions @makefile.drupal_project, @platform.drupal_project else log.action(InstallProjectAction.new(@platform, @makefile.drupal_project)) end return true end |
#sync_libraries ⇒ Object
Synchronizes libraries between the makefile and the platform.
290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 |
# File 'lib/drupid/updater.rb', line 290 def sync_libraries debug 'Syncing libraries' processed_paths = [] @makefile.each_library do |lib| sync_library(lib) processed_paths << lib.target_path end # Determine libraries that should be deleted from the 'libraries' folder. # The above is a bit of an overstatement, as it is basically impossible # to detect a "library" in a reliable way. What we actually do is just # deleting "spurious" paths inside the 'libraries' folder. # Note also that Drupid is not smart enough to find libraries installed # inside modules or themes. Pathname.glob(@platform.libraries_path.to_s + '/**/*').each do |p| next unless p.directory? q = p.relative_path_from(@platform.local_path + @platform.contrib_path) # If q is not a prefix of any processed path, or viceversa, delete it if processed_paths.find_all { |pp| pp.fnmatch(q.to_s + '*') or q.fnmatch(pp.to_s + '*') }.empty? l = Library.new(p.basename) l.local_path = p log.action(DeleteAction.new(@platform, l)) # Do not need to delete subdirectories processed_paths << q end end end |
#sync_library(lib) ⇒ Object
317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 |
# File 'lib/drupid/updater.rb', line 317 def sync_library(lib) return false unless _fetch_and_patch(lib) platform_lib = Library.new(lib.name) relpath = @platform.contrib_path + lib.target_path libpath = @platform.local_path + relpath platform_lib.local_path = libpath if platform_lib.exist? begin diff = lib.file_level_compare_with platform_lib rescue => ex odie "Failed to verify the integrity of library #{lib.extended_name}: #{ex}" end if diff.empty? log.notice("#{Tty.white}[OK]#{Tty.reset} #{lib.extended_name} (#{relpath})") else log.action(UpdateLibraryAction.new(platform, lib)) log.notice(diff.join("\n")) end else log.action(InstallLibraryAction.new(platform, lib)) end return true end |
#sync_project(project) ⇒ Object
Performs the necessary synchronization actions for the given project. Returns true if the dependencies of the given project must be synchronized, too; returns false otherwise.
234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 |
# File 'lib/drupid/updater.rb', line 234 def sync_project(project) return false unless _fetch_and_patch(project) # Does this project contains a makefile? If so, enqueue a derivative build. has_makefile = prepare_derivative_build(project) # Ignore libraries that may be installed inside this project pp = @platform.local_path + @platform.dest_path(project) @libraries_paths.each do |lp| if lp.fnmatch?(pp.to_s + '/*') project.ignore_path(lp.relative_path_from(pp)) @log.notice("Ignoring #{project.ignore_paths.last} inside #{project.extended_name}") end end # Does the project exist in the platform? If so, compare the two. if @platform.has_project?(project.name) platform_project = @platform.get_project(project.name) # Fix project location new_path = @platform.dest_path(project) if @platform.local_path + new_path != platform_project.local_path log.action(MoveAction.new(@platform, platform_project, new_path)) if (@platform.local_path + new_path).exist? if @force_changes owarn "Overwriting existing path: #{new_path}" else log.error("#{new_path} already exists. Use --force to overwrite.") end end end # Compare versions and log suitable actions _compare_versions project, platform_project else # If analyzing the platform does not allow us to detect the project (e.g., Fusion), # we try to see if the directory exists where it is supposed to be. proj_path = @platform.local_path + @platform.dest_path(project) if proj_path.exist? begin platform_project = PlatformProject.new(@platform, proj_path) _compare_versions project, platform_project rescue => ex log.action(UpdateProjectAction.new(@platform, project)) if @force_changes owarn "Overwriting existing path: #{proj_path}" else log.error("#{proj_path} exists, but was not recognized as a project (use --force to overwrite it): #{ex}") end end else # new project log.action(InstallProjectAction.new(@platform, project)) end end return (not has_makefile) end |
#sync_projects(options = {}) ⇒ Object
Synchronizes projects between the makefile and the platform.
Options: nofollow
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 |
# File 'lib/drupid/updater.rb', line 181 def sync_projects( = {}) exclude(@core_projects) # Skip core projects processed = Array.new(excluded) # List of names of processed projects dep_queue = Array.new # Queue of Drupid::Project objects whose dependencies must be checked. This is always a subset of processed. @makefile.each_project do |makefile_project| dep_queue << makefile_project if sync_project(makefile_project) processed += makefile_project.extensions end unless [:nofollow] # Recursively get dependent projects. # An invariant is that each project in the dependency queue has been processed # and cached locally. Hence, it has a version and its path points to the # cached copy. while not dep_queue.empty? do project = dep_queue.shift project.dependencies.each do |dependent_project_name| unless processed.include?(dependent_project_name) debug "Queue dependency: #{dependent_project_name} <- #{project.extended_name}" new_project = Project.new(dependent_project_name, project.core) dep_queue << new_project if sync_project(new_project) @makefile.add_project(new_project) processed += new_project.extensions end end end end # Determine projects that should be deleted pending_delete = @platform.project_names - processed pending_delete.each do |p| proj = platform.get_project(p) log.action(DeleteAction.new(platform, proj)) if which('drush').nil? if @force_changes owarn "Forcing deletion." else log.error "#{proj.extended_name}: use --force to force deletion or install Drush >=6.0." end elsif proj.installed?(site) if @force_changes owarn "Deleting an installed/enabled project." else log.error "#{proj.extended_name} cannot be deleted because it is installed" end end end end |
#updatedb ⇒ Object
Updates Drupal’s database using drush updatedb
. If #site is defined, then updates only the specified site; otherwise, iterates over all Platform#site_names and updates each one in turn.
Returns true upon success, false otherwise.
365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 |
# File 'lib/drupid/updater.rb', line 365 def updatedb ohai "Updating Drupal database..." blah "Platform: #{self.platform.local_path}" res = true site_list = (self.site) ? [self.site] : self.platform.site_names site_list.each do |s| site_path = self.platform.sites_path + s debug "Site path: #{site_path}" unless site_path.exist? debug "Skipping #{site_path} because it does not exist." next end blah "Updating site: #{s}" res = Drush.updatedb(site_path) && res end return res end |