buildr 0.15.0 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,15 @@
1
+ 0.16 (3/7/2007)
2
+ * Added: zip.include :as=> to include file under specified name.
3
+ * Added: zip.merge to include the (expanded) contents of one zip file in another.
4
+ * Added: experimental test task using JUnit and JMock.
5
+ * Changed: project.to_s returns name, projects returns sorted by name.
6
+ * Changed: project definition now executed using project's base directory
7
+ as the current directory.
8
+ * Fixed: artifact test cases and minor code cleanup.
9
+ * Fixed: attempts to download artifact even if created by task.
10
+ * Fixed: release task now deletes old tagged copy and reports SVN usage.
11
+ * Fixed: OpenJPA not including target directory in classpath.
12
+
1
13
  0.15 (2/28/2007)
2
14
  * Fixed: tasks fail unless deployment server specified.
3
15
  * Changed: deploy method executes deployment, instead of returning a task.
@@ -1,13 +1,17 @@
1
- # Remember that all these need to be Gem dependencies.
2
- gem "facets"
3
- gem "builder"
4
- gem "net-ssh"
5
- gem "net-sftp"
6
- gem "rubyzip"
7
- gem "highline"
1
+ # returning(obj) and with(obj)
2
+ require "facet/kernel/with"
3
+ # &:symbol goodness.
4
+ require "facet/symbol/to_proc"
5
+ # blank? on string and nil
6
+ require "facet/string/blank"
7
+ require "facet/nilclass/blank"
8
+ # What it says.
9
+ require "facet/module/alias_method_chain"
10
+ require "facet/string/starts_with"
11
+
8
12
 
9
13
  module Buildr
10
- VERSION = "0.15.0"
14
+ VERSION = "0.16.0"
11
15
  end
12
16
 
13
17
  $LOAD_PATH.unshift File.join(File.dirname(__FILE__))
@@ -23,6 +27,7 @@ require "tasks/zip"
23
27
 
24
28
  require "java/java"
25
29
  require "java/compile"
30
+ require "java/test"
26
31
  require "java/javacc"
27
32
  require "java/openjpa"
28
33
  require "java/packaging"
@@ -1,25 +1,27 @@
1
1
  module Buildr
2
2
 
3
3
  # This module gives you a way to access the individual properties of an
4
- # artifact: id, group, type and version. It can also return the artifact
5
- # specification.
4
+ # artifact: id, group, type, classifier and version. It also provides other
5
+ # methods commonly used on an artifact, specifically #to_hash and #to_spec.
6
6
  module ActsAsArtifact
7
7
 
8
+ ARTIFACT_ATTRIBUTES = [:group, :id, :type, :classifier, :version]
9
+
8
10
  class << self
9
11
  def included(mod)
10
12
  mod.extend self
11
13
  end
12
14
  end
13
15
 
14
- # The artifact id.
16
+ # The artifact identifier.
15
17
  attr_reader :id
16
- # The group id.
18
+ # The group identifier.
17
19
  attr_reader :group
18
20
  # The file type.
19
21
  attr_reader :type
20
22
  # The version number.
21
23
  attr_reader :version
22
- # Optional classifier.
24
+ # Optional artifact classifier.
23
25
  attr_reader :classifier
24
26
 
25
27
  # Returns the artifact specification as a hash.
@@ -31,28 +33,20 @@ module Buildr
31
33
 
32
34
  # Returns the artifact specification, in the structure:
33
35
  # <group>:<artifact>:<type>:<version>
36
+ # or
37
+ # <group>:<artifact>:<type>:<classifier><:<version>
34
38
  def to_spec()
35
39
  classifier.blank? ? "#{group}:#{id}:#{type}:#{version}" : "#{group}:#{id}:#{type}:#{classifier}:#{version}"
36
40
  end
37
41
 
38
42
  # Apply specification to this artifact.
39
43
  def apply_spec(spec)
40
- case spec
41
- when String
42
- @group, @id, @type, @version, *rest = spec.split(":")
43
- unless rest.empty?
44
- @classifier, @version = @version, rest.shift
45
- fail "Expecting project:id:type:version or project:id:type:classifier:version, found #{spec}" unless rest.empty?
46
- end
47
- when Hash
48
- [:group, :id, :type, :version, :classifier].each { |key| instance_variable_set("@#{key}", spec[key]) }
49
- @type ||= DEFAULT_FILE_TYPE
50
- else
51
- raise ArgumentError, "Expecting a string or a hash"
52
- end
44
+ spec = Artifact.to_hash(spec)
45
+ ARTIFACT_ATTRIBUTES.each { |key| instance_variable_set("@#{key}", spec[key]) }
53
46
  self
54
47
  end
55
48
 
49
+ # Convenience method that returns a POM artifact.
56
50
  def pom()
57
51
  return self if type.to_s == "pom"
58
52
  artifact(:group=>group, :id=>id, :version=>version, :type=>"pom", :classifier=>classifier)
@@ -61,7 +55,11 @@ module Buildr
61
55
  end
62
56
 
63
57
 
64
- # Use the artifact and artifacts method to create artifact tasks.
58
+ # The Artifact task maps to an artifact file in the local repository
59
+ # and knows how to download the file from a remote repository.
60
+ #
61
+ # The task will only download the file if it does not exist. You can
62
+ # enhance the task to create the artifact yourself.
65
63
  class Artifact < Rake::FileCreationTask
66
64
 
67
65
  # The default file type for artifacts, if not specified.
@@ -71,64 +69,67 @@ module Buildr
71
69
 
72
70
  class << self
73
71
 
74
- # Lookup an artifact task based on its specification.
72
+ # Lookup a previously registered artifact task based on the
73
+ # artifact specification (string or hash).
75
74
  def lookup(spec)
76
75
  @artifacts ||= {}
77
- @artifacts[hash_to_spec(spec)]
76
+ @artifacts[to_spec(spec)]
78
77
  end
79
78
 
80
- # Register a task as an artifact. Returns the task when calling
81
- # #artifacts. For example, a project will use this to register the
82
- # packages it creates.
83
- def register(task)
79
+ # Register an artifact task(s) for later lookup (see #lookup).
80
+ def register(*tasks)
84
81
  @artifacts ||= {}
85
- if task.respond_to?(:to_hash)
86
- @artifacts[hash_to_spec(task.to_hash)] = task
87
- else
88
- fail "Can only call with an artifact task"
89
- end
82
+ fail "You can only register an artifact task, strings and hashes are just not good enough" unless
83
+ tasks.all? { |task| task.respond_to?(:to_spec) && task.respond_to?(:invoke) }
84
+ tasks.each { |task| @artifacts[task.to_spec] = task }
85
+ tasks
90
86
  end
91
87
 
92
88
  # Turn a spec into a hash. This method accepts a string, hash or any object
93
- # that responds to the method to_spec. There are several reasons you'll want
94
- # to use this method:
95
- # * You can pass it anything that could possibly be a spec, and get a hash.
96
- # * It will check that your spec includes the group identifier, artifact
97
- # identifier and version number, and set the file type, if not specified.
89
+ # that responds to the method to_spec. There are several reasons to use this
90
+ # method:
91
+ # * You can pass anything that could possibly be a spec, and get a hash.
92
+ # * It will check that the spec includes the group identifier, artifact
93
+ # identifier and version number and set the file type, if missing.
98
94
  # * It will always return a new specs hash.
99
- # * Calling to_s on the hash will return a spec string.
100
95
  #
101
96
  # :nodoc:
102
- def spec_to_hash(spec)
103
- return spec_to_hash(spec.to_spec) if spec.respond_to?(:to_spec)
104
- if String === spec
105
- group, id, type, version, *rest = spec.split(":")
106
- unless rest.empty?
107
- classifier, version = version, rest.shift
108
- fail "Expecting project:id:type:version or project:id:type:classifier:version, found #{spec}" unless rest.empty?
109
- end
110
- fail "Missing file type for #{spec}" if type.blank?
111
- spec_to_hash :group=>group, :id=>id, :type=>type, :version=>version, :classifier=>classifier
112
- elsif Hash === spec
113
- spec = spec.clone
97
+ def to_hash(spec)
98
+ if spec.respond_to?(:to_spec)
99
+ to_hash spec.to_spec
100
+ elsif Hash === spec
101
+ # Sanitize the hash and check it's valid.
102
+ spec = ARTIFACT_ATTRIBUTES.inject({}) { |h, k| h[k] = spec[k] ; h }
114
103
  fail "Missing group identifier for #{spec.inspect}" if spec[:group].blank?
115
104
  fail "Missing artifact identifier for #{spec.inspect}" if spec[:id].blank?
116
105
  fail "Missing version for #{spec.inspect}" if spec[:version].blank?
117
106
  spec[:type] = DEFAULT_FILE_TYPE if spec[:type].blank?
118
107
  spec
108
+ elsif String === spec
109
+ group, id, type, version, *rest = spec.split(":")
110
+ unless rest.empty?
111
+ # Optional classifier comes before version.
112
+ classifier, version = version, rest.shift
113
+ fail "Expecting <project:id:type:version> or <project:id:type:classifier:version>, found <#{spec}>" unless rest.empty?
114
+ end
115
+ to_hash :group=>group, :id=>id, :type=>type, :version=>version, :classifier=>classifier
119
116
  else
120
- fail "Spec must be a string, hash or object that responds to to_spec"
117
+ fail "Expecting a String, Hash or object that responds to to_spec"
121
118
  end
122
119
  end
123
120
 
124
- # Convert a hash back to a spec string.
125
- def hash_to_spec(hash)
121
+ # Convert a hash back to a spec string. This method accepts
122
+ # a string, hash or any object that responds to to_spec.
123
+ # :nodoc:
124
+ def to_spec(hash)
125
+ hash = to_hash(hash) unless Hash === hash
126
126
  version = ":#{hash[:version]}" unless hash[:version].blank?
127
127
  classifier = ":#{hash[:classifier]}" unless hash[:classifier].blank?
128
128
  "#{hash[:group]}:#{hash[:id]}:#{hash[:type] || DEFAULT_FILE_TYPE}#{classifier}#{version}"
129
129
  end
130
130
 
131
131
  # Convert a hash to a file name.
132
+ # :nodoc:
132
133
  def hash_to_file_name(hash)
133
134
  version = "-#{hash[:version]}" unless hash[:version].blank?
134
135
  classifier = "-#{hash[:classifier]}" unless hash[:classifier].blank?
@@ -137,30 +138,32 @@ module Buildr
137
138
 
138
139
  end
139
140
 
140
- def initialize(*args)
141
+ def execute()
142
+ # Default behavior: download the artifact from one of the remote
143
+ # repositories if the file does not exist. But this default behavior
144
+ # is counter useful if the artifact knows how to build itself
145
+ # (e.g. download from a different location), so don't perform it
146
+ # if the task found a different way to create the artifact.
141
147
  super
142
- enhance do |task|
143
- # Download the artifact form one of the remote repositories if the
144
- # file does not exist in the local repository, and no other behavior
145
- # specified. If this task has been enhanced, delegate to the other
146
- # enhancers. We don't know if this is the first or other enhancement,
147
- # but we only need to know it's not the only one.
148
- if @actions.size == 1
149
- repositories.download(to_spec)
150
- end
148
+ unless Rake.application.options.dryrun || File.exist?(name)
149
+ repositories.download(to_spec)
151
150
  end
152
151
  end
153
152
 
154
153
  end
155
154
 
156
155
 
157
- # Singleton object for specifying the local, remote and release repositories.
156
+ # Holds the path to the local repository, URLs for remote repositories, and
157
+ # settings for the deployment repository.
158
158
  class Repositories
159
159
  include Singleton
160
160
 
161
161
  # Returns the path to the local repository.
162
162
  #
163
163
  # The default path is .m2/repository relative to the home directory.
164
+ # You can change the location of the local repository by using a symbol
165
+ # link or by setting a different path. If you set a different path, do it
166
+ # in the buildr.rb file instead of the Rakefile.
164
167
  def local()
165
168
  @local ||= ENV["local_repo"] || File.join(ENV["HOME"], ".m2", "repository")
166
169
  end
@@ -176,7 +179,7 @@ module Buildr
176
179
  # locate :group=>"log4j", :id=>"log4j", :version=>"1.1"
177
180
  # => ~/.m2/repository/log4j/log4j/1.1/log4j-1.1.jar
178
181
  def locate(spec)
179
- spec = Artifact.spec_to_hash(spec) unless Hash === spec
182
+ spec = Artifact.to_hash(spec)
180
183
  File.join(local, spec[:group].split("."), spec[:id], spec[:version], Artifact.hash_to_file_name(spec))
181
184
  end
182
185
 
@@ -210,10 +213,10 @@ module Buildr
210
213
  # and store it in the local repository. Returns the path if downloaded,
211
214
  # otherwise raises an exception.
212
215
  def download(spec)
213
- spec = Artifact.spec_to_hash(spec) unless Hash === spec
216
+ spec = Artifact.to_hash(spec) unless Hash === spec
214
217
  path = locate(spec)
215
218
 
216
- puts "Downloading #{Artifact.hash_to_spec(spec)}" if Rake.application.options.trace
219
+ puts "Downloading #{Artifact.to_spec(spec)}" if Rake.application.options.trace
217
220
  return path if remote.any? do |repo_id, repo_url|
218
221
  begin
219
222
  rel_path = spec[:group].gsub(".", "/") +
@@ -232,13 +235,16 @@ module Buildr
232
235
  false
233
236
  end
234
237
  end
235
- fail "Failed to download #{Artifact.hash_to_spec(spec)}, tried the following repositories:\n#{repositories.remote.values.join("\n")}"
238
+ fail "Failed to download #{Artifact.to_spec(spec)}, tried the following repositories:\n#{repositories.remote.values.join("\n")}"
236
239
  end
237
240
 
238
- # Specifies deployment target for all artifacts generated by this project.
241
+ # Specifies the deployment repository. Accepts a hash with the different
242
+ # repository settings (e.g. url, username, password). Anything else is
243
+ # interepted as the URL.
239
244
  #
240
245
  # For example:
241
- # repositories.deploy_to = "sftp://example.com/var/www/maven/"
246
+ # repositories.deploy_to = { :url=>"sftp://example.com/var/www/maven/",
247
+ # :username="john", :password=>"secret" }
242
248
  # or:
243
249
  # repositories.deploy_to = "sftp://john:secret@example.com/var/www/maven/"
244
250
  def deploy_to=(options)
@@ -246,13 +252,21 @@ module Buildr
246
252
  @deploy_to = options
247
253
  end
248
254
 
255
+ # Returns the current deployment repository configuration. This is a more
256
+ # convenient way to specify deployment in the Rakefile, and override it
257
+ # locally. For example:
258
+ # # Rakefile
259
+ # repositories.deploy_to[:url] ||= "sftp://example.com"
260
+ # # buildr.rb
261
+ # repositories.deploy_to[:url] = "sftp://acme.org"
249
262
  def deploy_to()
250
- @deploy_to || {}
263
+ @deploy_to ||= {}
251
264
  end
252
265
 
253
266
  end
254
267
 
255
- # Returns a global object for setting local and remote repositories.
268
+
269
+ # Returns a global object for setting local, remote and deploy repositories.
256
270
  # See Repositories.
257
271
  def repositories()
258
272
  Repositories.instance
@@ -276,14 +290,13 @@ module Buildr
276
290
  # download(artifact("dojo:dojo-widget:zip:2.0")=>
277
291
  # "http://download.dojotoolkit.org/release-2.0/dojo-2.0-widget.zip")
278
292
  def artifact(spec, &block)
279
- spec = Artifact.spec_to_hash(spec)
293
+ spec = Artifact.to_hash(spec)
280
294
  unless task = Artifact.lookup(spec)
281
295
  task = Artifact.define_task(repositories.locate(spec))
282
296
  Artifact.register(task)
283
297
  end
284
298
  task.apply_spec spec
285
- task.enhance(&block) if block_given?
286
- task
299
+ task.enhance &block
287
300
  end
288
301
 
289
302
  # Creates multiple artifacts from a set of specifications and returns
@@ -320,7 +333,7 @@ module Buildr
320
333
  case spec
321
334
  when Hash
322
335
  set |= [artifact(spec)]
323
- when /:.*:.*:/
336
+ when /:/
324
337
  set |= [artifact(spec)]
325
338
  when String
326
339
  set |= [file(spec)]
@@ -331,7 +344,7 @@ module Buildr
331
344
  when Array
332
345
  set |= artifacts(*spec)
333
346
  else
334
- fail "Invalid artifact specification: #{spec || 'nil'}"
347
+ fail "Invalid artifact specification: #{spec.to_s || 'nil'}"
335
348
  end
336
349
  set
337
350
  end
@@ -1,25 +1,82 @@
1
+ require "open3"
2
+
3
+
1
4
  module Buildr
2
5
 
6
+ BUILD_TASKS = {
7
+ :build =>"Build the project",
8
+ :clean =>"Clean files generated during a build",
9
+ :package =>"Create packages",
10
+ :install =>"Install packages created by the project",
11
+ :uninstall=>"Remove previously installed packages",
12
+ :deploy =>"Deploy packages created by the project"
13
+ }
14
+
15
+ # Handles the build and clean tasks.
16
+ BUILD_TASKS.each { |name, comment| LocalDirectoryTask.define_task(name).add_comment(comment) }
17
+
18
+ Project.on_define do |project|
19
+ BUILD_TASKS.each { |name, comment| project.recursive_task name }
20
+ end
21
+
22
+ class Project
23
+ def build(*args, &block)
24
+ returning(@build_task ||= task("build")) do |task|
25
+ task.enhance args, &block
26
+ end
27
+ end
28
+
29
+ def clean(*args, &block)
30
+ returning(@clean_task ||= task("clean")) do |task|
31
+ task.enhance args, &block
32
+ end
33
+ end
34
+ end
35
+
36
+ Project.on_define do |project|
37
+ project.build
38
+ project.clean
39
+ end
40
+
41
+
3
42
  class ReleaseTask < Rake::Task
4
43
 
5
44
  VERSION_NUMBER_PATTERN = /VERSION_NUMBER\s*=\s*(["'])(.*)\1/
6
45
  NEXT_VERSION_PATTERN = /NEXT_VERSION\s*=\s*(["'])(.*)\1/
7
46
 
47
+ class << self
48
+ def svn_ignores()
49
+ @ignores = (@ignores || []).map { |pat| pat.is_a?(Regexp) ? pat : Regexp.new("^.*\s+#{Regexp.escape pat}$") }
50
+ end
51
+ end
52
+
8
53
  def initialize(*args)
9
54
  super
10
55
  enhance do |task|
11
56
  # Make sure we don't have anything uncommitted in SVN.
12
- fail "Uncommitted SVN files violate the First Principle Of Release!" unless
13
- svn("status").empty?
14
- # Load the Rakefile and find the version numbers.
57
+ check_status
58
+ # Update current version to next version before deploying.
15
59
  next_ver = update_version
16
- # Run the deployment externally using the new version number.
60
+ # Run the deployment externally using the new version number
61
+ # (from the modified Rakefile).
17
62
  sh "rake deploy"
63
+ # Update the next version as well to the next increment and commit.
18
64
  update_next_version next_ver
19
- tag_repository
65
+ # Tag the repository for this release.
66
+ tag_repository next_ver
67
+ # Update the next version to end with -SNAPSHOT.
68
+ update_version_to_snapshot next_ver
20
69
  end
21
70
  end
22
71
 
72
+ def check_status()
73
+ ignores = ReleaseTask.svn_ignores
74
+ status = svn("status", "--ignore-externals", :verbose=>false).
75
+ reject { |line| line =~ /^X\s/ || ignores.any? { |pat| line =~ pat } }
76
+ fail "Uncommitted SVN files violate the First Principle Of Release!\n#{status}" unless
77
+ status.empty?
78
+ end
79
+
23
80
  # Change the Rakefile and update the current version number to the
24
81
  # next version number (VERSION_NUMBER = NEXT_VERSION). We need this
25
82
  # before making a release with the next version. Return the next version.
@@ -54,54 +111,43 @@ module Buildr
54
111
  File.open(Rake.application.rakefile, "w") { |file| file.write rakefile }
55
112
 
56
113
  # Commit new version number.
57
- svn "commit", "-m", "Changed release number to #{version}"
114
+ svn "commit", "-m", "Changed version number to #{version}", Rake.application.rakefile
58
115
  end
59
116
 
60
117
  # Create a tag in the SVN repository.
61
- def tag_repository()
118
+ def tag_repository(version)
62
119
  # Copy to tag.
63
120
  cur_url = svn("info").scan(/URL: (.*)/)[0][0]
64
- new_url = cur_url.sub(/trunk$/, "tags/#{cur_ver}")
65
- svn "copy", cur_url, new_url
121
+ new_url = cur_url.sub(/trunk$/, "tags/#{version}")
122
+ svn "remove", new_url, "-m", "Removing old copy" rescue nil
123
+ svn "copy", cur_url, new_url, "-m", "Release #{version}"
66
124
  end
67
125
 
68
- def svn(*args)
69
- stdin, stdout, stderr = Open3.popen3("svn", *args)
70
- stdin.close
71
- error = stderr.read
72
- fail error unless error.empty?
73
- stdout.read
126
+ def update_version_to_snapshot(version)
127
+ version += "-SNAPSHOT"
128
+ rakefile = File.read(Rake.application.rakefile)
129
+ rakefile.gsub!(VERSION_NUMBER_PATTERN) { |ver| ver.sub(/(["']).*\1/, %Q{"#{version}"}) }
130
+ File.open(Rake.application.rakefile, "w") { |file| file.write rakefile }
131
+ # Commit new version number.
132
+ svn "commit", "-m", "Changed version number to #{version}", Rake.application.rakefile
74
133
  end
75
- end
76
134
 
77
- # Handles the build and clean tasks.
78
- desc "Clean all projects"
79
- LocalDirectoryTask.define_task("clean")
80
- desc "Build all projects"
81
- LocalDirectoryTask.define_task("build")
82
- desc "Make a release"
83
- ReleaseTask.define_task "release"
84
-
85
- class Project
86
- def build(*args, &block)
87
- returning(@build_task ||= recursive_task("build")) do |task|
88
- task.enhance args, &block
135
+ def svn(*args)
136
+ if Hash === args.last
137
+ options = args.pop
138
+ else
139
+ options = { :verbose=>verbose }
89
140
  end
90
- end
91
-
92
- def clean(*args, &block)
93
- returning(@clean_task ||= recursive_task("clean")) do |task|
94
- task.enhance args, &block
141
+ puts ["svn", *args].join(" ") if options[:verbose]
142
+ Open3.popen3("svn", *args) do |stdin, stdout, stderr|
143
+ stdin.close
144
+ error = stderr.read
145
+ fail error unless error.empty?
146
+ returning(stdout.read) { |output| puts output if Rake.application.options.trace }
95
147
  end
96
148
  end
97
149
  end
98
150
 
99
- Project.on_create do |project|
100
- desc "Clean all files generated during the build process"
101
- project.clean
102
-
103
- desc "Build this project"
104
- project.build
105
- end
106
-
151
+ desc "Make a release"
152
+ ReleaseTask.define_task "release"
107
153
  end