Class: SC::Target

Inherits:
HashStruct show all
Defined in:
lib/sproutcore/models/target.rb

Overview

Defines a build target in a project. A build target is a component that you might want to build separately such as an application or library.

Targets make require other targets through their dependencies property. The property should name the other targets using either a relative or absolute target name. An absolute target name begins with a forward slash (i.e. /sproutcore), where a relative begins with the target name itself.

When you create a target you must pass at least the following two parameters:

target_name:: the name of the target, must start with a forward-slash
target_type:: a task namespace that can be used to invoke tasks for 
  building the manifest and other commands

In addition to the above required parameters, you may also pass options. These options will be simply set on the target and may be used by the build tasks. At least two options, however, have special meaning and they should be set at some point before the task is used:

project:: This should point to the project the owns the target.  If you
  create a target using the Project#add_target method, this will be set
  for you.
source_root::  This should contain the absolute path to the source 
  directory for the target.  If you do not set this option, then the
  Target will not be able to load a Buildfile you define.  It is not 
  strictly necessary to set this option however, as some targets may be
  entirely synthesized from other sources.

Constant Summary collapse

LONG_LANGUAGE_MAP =

LANGUAGE HELPER METHODS

{ :english => :en, :french => :fr, :german => :de, :japanese => :ja, :spanish => :es, :italian => :it }
SHORT_LANGUAGE_MAP =
{ :en => :english, :fr => :french, :de => :german, :ja => :japanese, :es => :spanish, :it => :italian }

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from HashStruct

#[], #[]=, #deep_clone, #has_options?, #merge, #merge!, #method_missing, #to_hash

Constructor Details

#initialize(target_name, target_type, opts = {}) ⇒ Target

Returns a new instance of Target.



42
43
44
45
46
47
48
49
50
51
52
# File 'lib/sproutcore/models/target.rb', line 42

def initialize(target_name, target_type, opts={})
  # Add target_name & type of options so they can be enumerated as part 
  # of the hash
  opts = opts.merge :target_name => target_name, 
                    :target_type => target_type
                    
  # Remove the project since we do not want to enumerate that as part of
  # the hash
  @project = opts.delete(:project)
  super(opts)
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method in the class SC::HashStruct

Instance Attribute Details

#projectObject (readonly)

Returns the value of attribute project.



54
55
56
# File 'lib/sproutcore/models/target.rb', line 54

def project
  @project
end

Instance Method Details

#_expand_required_targets(opts, seen) ⇒ Object



201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/sproutcore/models/target.rb', line 201

def _expand_required_targets(opts, seen)
  ret = []
  required_targets(opts).each do |target|
    next if seen.include?(target)
    seen << target # avoid loading again
    
    # add required targets, if not already seend...
    target._expand_required_targets(opts, seen).each do |required|
      ret << required
      seen << required
    end
    
    # then add my own target...
    ret << target
  end
  return ret.uniq.compact
end

#build_docs!(opts = {}) ⇒ Object

Creates all of the documentation for the target.

Options

:build_root:: the root path to place built documentation
:language::   the language to build.  defaults to preferred lang
:required::   include required targets.  defaults to true


421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
# File 'lib/sproutcore/models/target.rb', line 421

def build_docs!(opts ={})

  build_root   = opts[:build_root] || nil
  language     = opts[:language] || self.config.preferred_language || :en
  logger       = opts[:logger] || SC.logger
  template_name = opts[:template] || 'jsdoc'
  use_required = opts[:required]
  use_required = true if use_required.nil? 
  
  # collect targets to build
  doc_targets = [self]
  doc_targets += self.expand_required_targets if use_required

  # convert targets to manifests so we can get alll files
  doc_manifests = doc_targets.map do |target| 
    target.manifest_for(:language => language)
  end

  # Collect all source entries, in the order they should be loaded
  file_list = []
  doc_manifests.each do |manifest|
    entry = manifest.build!.entry_for('javascript.js')
    next if entry.nil?

    # loop over entries, collecting their source entries until we get
    # back to the original source files.  Since we expand these entries
    # in their proper load order this should give us something suitable
    # to hand to jsdoc.
    entries = entry.ordered_entries || entry.source_entries
    while entries && entries.size>0
      new_entries = []
      entries.each do |cur_entry|
        sources = cur_entry.ordered_entries || cur_entry.source_entries
        if sources
          new_entries += sources
        elsif entry.filename =~ /\.js$/
          file_list << cur_entry.source_path
        end
      end
      entries = new_entries
    end
  end

  file_list = file_list.uniq # remove duplicates

  logger.info "Building #{target_name} docs at #{build_root}"
  FileUtils.mkdir_p(build_root)

  # Prepare jsdoc opts
  jsdoc_root    = SC::PATH / 'vendor' / 'jsdoc'
  jar_path      = jsdoc_root / 'jsrun.jar'
  runjs_path    = jsdoc_root / 'app' / 'run.js'
  
  # look for a directory matching the template name
  cur_project = self.project
  has_template = false
  while cur_project 
    template_path = cur_project.project_root / 'doc_templates' / template_name
    has_template = File.directory?(template_path)
    cur_project = has_template ? nil : cur_project.parent_project
  end

  if !has_template
    cur_project = self.project
    has_template = false
    while cur_project 
      template_path = cur_project.project_root / template_name
      has_template = File.directory?(template_path)
      cur_project = has_template ? nil : cur_project.parent_project
    end
  end
  throw("could not find template named #{template_name}") if !has_template

  # wrap files in quotes...
  # Note: using -server gives an approx. 25% speed boost over -client 
  # (the default)
  js_doc_cmd = %(java -server -Djsdoc.dir="#{jsdoc_root}" -jar "#{jar_path}" "#{runjs_path}" -t="#{template_path}" -d="#{build_root}" "#{ file_list * '" "' }" -v)

  logger.info "File Manifest:\r\n"
  file_list.each { |file_path| logger.info(file_path) }
  
  puts "Generating docs for #{self.target_name}\r\nPlease be patient this could take awhile..."
  
  # use pipe so that we can immediately log output as it happens
  IO.popen(js_doc_cmd) do |pipe|
    while line = pipe.gets
      logger.info line.sub(/\n$/,'')
    end
  end

  puts "Finished."
end

#buildfileObject

Returns the buildfile for this target. The buildfile is a clone of the parent target buildfile with any buildfile found in the current target’s source_root merged over top of it.

Returns

Buildfile


95
96
97
# File 'lib/sproutcore/models/target.rb', line 95

def buildfile 
  @buildfile ||= parent_target.buildfile.dup.for_target(self).load!(self.source_root)
end

#compute_build_number(seen = nil, opts = {}) ⇒ Object

Computes a unique build number for this target. The build number is gauranteed to change anytime the contents of any source file changes or anytime the build number of a required target changes. Although this will generate long strings, it will automatically ensure that resources are properly cached when deployed.

Note that this method does NOT set the build_number on the receiver; it just calculates a value and returns it. You usually call this method just to set the build_number on the target.

Returns

A build number string


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
288
289
290
291
292
293
294
295
# File 'lib/sproutcore/models/target.rb', line 244

def compute_build_number(seen=nil, opts = {})

  # reset cache if forced to recompute
  build_number = nil if opts[:force]
  
  # Look for a global build_numbers hash and try that
  if (build_numbers = config.build_numbers)
    build_number = build_numbers[target_name.to_s] || build_numbers[target_name.to_sym]
  end

  # Otherwise, use config build number specifically for this target, if 
  # specified
  build_number ||= config.build_number 

  # Otherwise, actually compute a build number. 
  if build_number.nil?
    require 'digest/md5'

    # Computes the build number based on the contents of the
    # files.  It is not as fast as using an mtime, but it will remain
    # constant from one machine to the next so it can be used when
    # deploying across multiple build machines, etc.
    digests = Dir.glob(File.join(source_root, '**', '*')).map do |path|
      allowed = File.exists?(path) && !File.directory?(path)
      allowed = allowed && !target_directory?(path)
      allowed ? Digest::SHA1.hexdigest(File.read(path)) : nil
    end
    digests.compact!

    # Get all required targets and add in their build number.
    # Note the "seen" variable passed here will avoid circular references
    # causing infinite loops.  Normally this should not be necessary, but
    # we put this here to gaurd against misconfigured projects
    seen ||= []
    
    _targets = required_targets(:theme => true).sort do |a,b|
      (a.target_name||'').to_s <=> (b.target_name||'').to_s
    end
    
    _targets.each do |ct|
      next if seen.include?(ct)
      ct.prepare!
      seen << ct
      digests << ct.compute_build_number(seen, :force => true)
    end

    # Finally digest the complete string - tada! build number
    build_number = Digest::SHA1.hexdigest(digests.join)
  end
  
  return build_number.to_s
end

#configObject

Returns the config for the current project. The config is computed by taking the merged config settings from the build file given the current build mode, then merging any environmental configs (set in SC::env) over the top.

This is the config hash you should use to control how items are built.



105
106
107
# File 'lib/sproutcore/models/target.rb', line 105

def config
  return @config ||= buildfile.config_for(target_name, SC.build_mode).merge(SC.env)
end

#expand_required_targets(opts = {}) ⇒ Object

Returns the expanded list of required targets, ordered as they actually need to be loaded.

This method takes the same options as required_targets to select the targets to include.

Returns

Array of targets


197
198
199
# File 'lib/sproutcore/models/target.rb', line 197

def expand_required_targets(opts ={})
  _expand_required_targets(opts, [self])
end

#inspectObject



56
57
58
# File 'lib/sproutcore/models/target.rb', line 56

def inspect
  "SC::Target(#{target_name})"
end

#installed_languagesObject

Returns the language codes for any languages actually found in this target.



382
383
384
385
386
387
388
389
# File 'lib/sproutcore/models/target.rb', line 382

def installed_languages
  ret = Dir.glob(File.join(source_root, '*.lproj')).map do |path|
    next unless path =~ /\/([^\/]+)\.lproj/
    (LONG_LANGUAGE_MAP[$1.downcase.to_sym] || $1).to_s
  end
  ret << config.preferred_language.to_s if config.preferred_language
  ret.compact.uniq.sort { |a,b| a.downcase <=> b.downcase }.map { |l| l.to_sym }
end

#loads_theme?Boolean

Returns true if this target can load a theme. Default returns true only if the target_type == :app, but you can override this by setting the value yourself.

Returns:

  • (Boolean)


183
184
185
186
# File 'lib/sproutcore/models/target.rb', line 183

def loads_theme?;
  ret = self[:loads_theme]
  ret.nil? ? (target_type == :app) : ret
end

#lproj_for(language_code) ⇒ Object

Returns project-relative path to the lproj directory for the named short language code



393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
# File 'lib/sproutcore/models/target.rb', line 393

def lproj_for(language_code)
  
  # try code as passed
  ret = "#{language_code}.lproj"
  return ret if File.directory? File.join(source_root, ret)
  
  # try long language too...
  language_code = SHORT_LANGUAGE_MAP[language_code.to_s.downcase.to_sym]
  if language_code
    ret = "#{language_code}.lproj"
    return ret if File.directory? File.join(source_root, ret)
  end
  
  # None found
  return nil
end

#manifest_for(variation = {}) ⇒ Object

Returns the manifest matching the variation options. If no matching manifest can be found, a new manifest will be created and prepared.



307
308
309
310
311
312
313
314
# File 'lib/sproutcore/models/target.rb', line 307

def manifest_for(variation={})
  ret = manifests.find { |m| m.has_options?(variation) }
  if ret.nil?
    ret = Manifest.new(self, variation)
    @manifests << ret
  end
  return ret 
end

#manifestsObject

An array of manifests for the target. You can retrieve the manifest for a particular variation using the method manifest_for().



303
# File 'lib/sproutcore/models/target.rb', line 303

def manifests; @manifests ||= []; end

#parent_targetObject

Finds the parent target for this target. The parent target is the target with a higher-level of hierarchy.

For example, the parent of ‘sproutcore/foundation’ is ‘sproutcore’

If your target is a top-level target, then this will return the project itself. If your target does not yet belong to a project, this will return nil



329
330
331
332
333
334
335
336
337
338
# File 'lib/sproutcore/models/target.rb', line 329

def parent_target
  return @parent_target unless @parent_target.nil?
  return nil if project.nil?
  tname = target_name
  while @parent_target.nil?
    tname = tname.to_s.sub(/\/[^\/]+$/,'')
    @parent_target = (tname.size>0) ? project.target_for(tname) : project
  end
  @parent_target
end

#prepare!Object

Invoke this method to make sure the basic paths for the target have been prepared. This method is called on the target before it is returned from any call target_for(). It will invoke the target:prepare build task.

You can call this method as often as you want; it will only execute once.

Returns

Target


70
71
72
73
74
75
76
77
78
79
# File 'lib/sproutcore/models/target.rb', line 70

def prepare!
  if !@is_prepared
    @is_prepared = true
    if buildfile.task_defined? 'target:prepare'
      buildfile.invoke 'target:prepare', 
        :target => self, :project => self.project, :config => self.config       
    end
  end
  return self
end

#prepared?Boolean

Used for unit testing

Returns:

  • (Boolean)


82
# File 'lib/sproutcore/models/target.rb', line 82

def prepared?; @is_prepared || false; end

#reload_config!Object

Clears the cached config, reloading it from the buildfile again. This is mostly used for unit testing.



111
112
113
114
115
116
# File 'lib/sproutcore/models/target.rb', line 111

def reload_config!
  @config= nil
  @is_prepared = false
  prepare!
  return self
end

#required_targets(opts = {}) ⇒ Object

Returns all of the targets required by this target. This will use the “required” config, resolving the target names using target_for().

You may pass some additional options in which will select the set of targets you want returned.

Options

:debug:: if true, config.debug_required will be included
:test::  if true, config.test_required will also be included

Returns

Array of Targets


136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/sproutcore/models/target.rb', line 136

def required_targets(opts={})
  
  # compute cache key for these options
  key = [:debug, :test, :theme].map do |k| 
    opts[k] ? k : nil 
  end
  key = key.compact.join('.')
  
  # Return cache value if found
  ret = (@required_targets ||= {})[key]
  return ret unless ret.nil?
  
  # else compute return value, respecting options
  ret = [config.required]
  if opts[:debug] && config.debug_required
    ret << config.debug_required 
  end
  if opts[:test] && config.test_required
    ret << config.test_required
  end 
  if opts[:theme] && self.loads_theme? && config.theme
    # verify theme is a theme target type - note that if no matching
    # target is found, we'll just let this go through so the standard
    # not found warning can show.
    t = target_for(config.theme)
    if t && t.target_type != :theme
      SC.logger.warn "Target #{config.theme} was set as theme for #{target_name} but it is not a theme."
    else
      ret << config.theme
    end
  end
  
  ret = ret.flatten.compact.map do |n| 
    if (t = target_for(n)).nil? 
      SC.logger.warn "Could not find target #{n} that is required by #{target_name}"
    end
    t
  end
  ret = ret.compact.uniq

  @required_targets[key] = ret
  return ret 
end

#target_directory?(path, root_path = nil) ⇒ Boolean

Returns true if the passed path appears to be a target directory according to the target’s current config.

Returns:

  • (Boolean)


221
222
223
224
225
226
227
228
229
# File 'lib/sproutcore/models/target.rb', line 221

def target_directory?(path, root_path=nil)
  root_path = self.source_root if root_path.nil?
  @target_names ||= self.config.target_types.keys
  path = path.to_s.sub /^#{Regexp.escape root_path}\//, ''
  @target_names.each do |name|
    return true if path =~ /^#{Regexp.escape name.to_s}/
  end
  return false
end

#target_for(target_name) ⇒ Object

Find a target relative to the receiver target. If you pass a regular target name, this method will start by looking for direct children of the receiver, then it will move up the hierarchy, looking in the parent target and on up until it reaches the top-level library.

This ‘scoped’ search model allows you to next targets and then to properly reference them.

To absolutely reference a target (i.e. to name its part beginning from the project root, begin with a forward-slash.)

Params

target_name:: the name of the target

Returns

found target or nil if no match could be found


357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
# File 'lib/sproutcore/models/target.rb', line 357

def target_for(target_name)
  if target_name =~ /^\// # absolute target
    ret = project.target_for(target_name)
  else # relative target...
    
    # look for any targets that are children of this target
    ret = project.target_for([self.target_name, target_name].join('/'))
    
    # Ask my parent target to look for the target, and so on.
    if ret.nil?
      ret = parent_target.nil? ? project.target_for(target_name) : parent_target.target_for(target_name)
    end
  end
  return ret 
end