Import 'buildr/custom_pom' addon to make it easier to build POMs for projects publishing to Maven Central.
Project: http://git-wip-us.apache.org/repos/asf/buildr/repo Commit: http://git-wip-us.apache.org/repos/asf/buildr/commit/baa269bb Tree: http://git-wip-us.apache.org/repos/asf/buildr/tree/baa269bb Diff: http://git-wip-us.apache.org/repos/asf/buildr/diff/baa269bb Branch: refs/heads/master Commit: baa269bb3243a7615e05bb95f6c799f112adafec Parents: 54a832d Author: Peter Donald <[email protected]> Authored: Sun May 11 11:10:35 2014 +1000 Committer: Peter Donald <[email protected]> Committed: Sun May 11 11:10:35 2014 +1000 ---------------------------------------------------------------------- CHANGELOG | 2 + addon/buildr/custom_pom.rb | 283 +++++++++++++++++++++++++++++++++++++ doc/more_stuff.textile | 72 ++++++++++ spec/addon/custom_pom_spec.rb | 149 +++++++++++++++++++ 4 files changed, 506 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/buildr/blob/baa269bb/CHANGELOG ---------------------------------------------------------------------- diff --git a/CHANGELOG b/CHANGELOG index 3f4a4ce..4aaae82 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ 1.4.17 (Pending) +* Added: Import 'buildr/custom_pom' addon to make it easier to + build POMs for projects publishing to Maven Central. * Added: Add flag to allow non portable extensions in wsgen addon. * Fixed: Avoid nil dereference bug in GWT addon when running GWT in a project that has no source directory. http://git-wip-us.apache.org/repos/asf/buildr/blob/baa269bb/addon/buildr/custom_pom.rb ---------------------------------------------------------------------- diff --git a/addon/buildr/custom_pom.rb b/addon/buildr/custom_pom.rb new file mode 100644 index 0000000..76b51d8 --- /dev/null +++ b/addon/buildr/custom_pom.rb @@ -0,0 +1,283 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +module Buildr + class CustomPom + Developer = Struct.new(:id, :name, :email, :roles) + + # Specify the name of the project + attr_writer :name + + # Retrieve the name of the project, defaulting to the project description or the name if not specified + def name + @name || @buildr_project.comment || @buildr_project.name + end + + # Specify a project description + attr_writer :description + + # Retrieve the project description, defaulting to the name if not specified + def description + @description || name + end + + # Property for the projects url + attr_accessor :url + + # Return the map of licenses for project + def licenses + @licenses ||= {} + end + + # Add Apache2 to the list of licenses + def add_apache_v2_license + self.licenses['The Apache Software License, Version 2.0'] = 'http://www.apache.org/licenses/LICENSE-2.0.txt' + end + + def add_bsd_2_license + self.licenses['The BSD 2-Clause License'] = 'http://opensource.org/licenses/BSD-2-Clause' + end + + def add_bsd_3_license + self.licenses['The BSD 3-Clause License'] = 'http://opensource.org/licenses/BSD-3-Clause' + end + + def add_cddl_v1_license + self.licenses['Common Development and Distribution License (CDDL-1.0)'] = 'http://opensource.org/licenses/CDDL-1.0' + end + + def add_epl_v1_license + self.licenses['Eclipse Public License - v 1.0'] = 'http://www.eclipse.org/legal/epl-v10.html' + end + + def add_gpl_v1_license + self.licenses['GNU General Public License (GPL) version 1.0'] = 'http://www.gnu.org/licenses/gpl-1.0.html' + end + + def add_gpl_v2_license + self.licenses['GNU General Public License (GPL) version 2.0'] = 'http://www.gnu.org/licenses/gpl-2.0.html' + end + + def add_gpl_v3_license + self.licenses['GNU General Public License (GPL) version 3.0'] = 'http://www.gnu.org/licenses/gpl-3.0.html' + end + + def add_lgpl_v2_license + self.licenses['GNU General Lesser Public License (LGPL) version 2.1'] = 'http://www.gnu.org/licenses/lgpl-2.1.html' + end + + def add_lgpl_v3_license + self.licenses['GNU General Lesser Public License (LGPL) version 3.0'] = 'http://www.gnu.org/licenses/lgpl-3.0.html' + end + + def add_mit_license + self.licenses['The MIT License'] = 'http://opensource.org/licenses/MIT' + end + + + attr_accessor :scm_url + attr_accessor :scm_connection + attr_accessor :scm_developer_connection + + attr_accessor :issues_url + attr_accessor :issues_system + + # Add a project like add_github_project('realityforge/gwt-appcache') + def add_github_project(project_spec) + git_url = "[email protected]:#{project_spec}.git" + self.scm_connection = self.scm_developer_connection = "scm:git:#{git_url}" + self.scm_url = git_url + web_url = "https://github.com/#{project_spec}" + self.url = web_url + self.issues_url = "#{web_url}/issues" + self.issues_system = 'GitHub Issues' + end + + def developers + @developers ||= [] + end + + def add_developer(id, name = nil, email = nil, roles = nil) + self.developers << Developer.new(id, name, email, roles) + end + + def provided_dependencies + @provided_dependencies ||= [] + end + + def provided_dependencies=(provided_dependencies) + @provided_dependencies = provided_dependencies + end + + def runtime_dependencies + @runtime_dependencies ||= [] + end + + def runtime_dependencies=(runtime_dependencies) + @runtime_dependencies = runtime_dependencies + end + + def optional_dependencies + @optional_dependencies ||= [] + end + + def optional_dependencies=(optional_dependencies) + @optional_dependencies = optional_dependencies + end + + protected + + def associate_project(buildr_project) + @buildr_project = buildr_project + end + + def self.pom_xml(project, package) + Proc.new do + xml = Builder::XmlMarkup.new(:indent => 2) + xml.instruct! + xml.project('xmlns' => 'http://maven.apache.org/POM/4.0.0', + 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + 'xsi:schemaLocation' => 'http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd') do + xml.modelVersion '4.0.0' + xml.parent do + xml.groupId 'org.sonatype.oss' + xml.artifactId 'oss-parent' + xml.version '7' + end + xml.groupId project.group + xml.artifactId project.id + xml.version project.version + xml.packaging package.type.to_s + xml.classifier package.classifier if package.classifier + + xml.name project.pom.name if project.pom.name + xml.description project.pom.description if project.pom.description + xml.url project.pom.url if project.pom.url + + xml.licenses do + project.pom.licenses.each_pair do |name, url| + xml.license do + xml.name name + xml.url url + xml.distribution 'repo' + end + end + end + + if project.pom.scm_url || project.pom.scm_connection || project.pom.scm_developer_connection + xml.scm do + xml.connection project.pom.scm_connection if project.pom.scm_connection + xml.developerConnection project.pom.scm_developer_connection if project.pom.scm_developer_connection + xml.url project.pom.scm_url if project.pom.scm_url + end + end + + if project.pom.issues_url + xml.issueManagement do + xml.url project.pom.issues_url + xml.system project.pom.issues_system if project.pom.issues_system + end + end + + xml.developers do + project.pom.developers.each do |developer| + xml.developer do + xml.id developer.id + xml.name developer.name if developer.name + xml.email developer.email if developer.email + if developer.roles + xml.roles do + developer.roles.each do |role| + xml.role role + end + end + end + end + end + end + + xml.dependencies do + provided_deps = Buildr.artifacts(project.pom.provided_dependencies).collect { |d| d.to_s } + runtime_deps = Buildr.artifacts(project.pom.runtime_dependencies).collect { |d| d.to_s } + optional_deps = Buildr.artifacts(project.pom.optional_dependencies).collect { |d| d.to_s } + deps = + Buildr.artifacts(project.compile.dependencies). + select { |d| d.is_a?(Artifact) }. + collect do |d| + f = d.to_s + scope = provided_deps.include?(f) ? 'provided' : + runtime_deps.include?(f) ? 'runtime' : + 'compile' + d.to_hash.merge(:scope => scope, :optional => optional_deps.include?(f)) + end + Buildr.artifacts(project.test.compile.dependencies). + select { |d| d.is_a?(Artifact) && !project.compile.dependencies.include?(d) }.collect { |d| d.to_hash.merge(:scope => 'test') } + deps.each do |dependency| + xml.dependency do + xml.groupId dependency[:group] + xml.artifactId dependency[:id] + xml.version dependency[:version] + xml.scope dependency[:scope] unless dependency[:scope] == 'compile' + xml.optional true if dependency[:optional] + end + end + end + end + end + end + end +end + +module Buildr + class Project #:nodoc: + def pom + unless @pom + @pom = parent ? parent.pom.dup : Buildr::CustomPom.new + @pom.send :associate_project, self + end + @pom + end + end +end + +module Buildr + module Package + alias :old_package :package + + def package(*args) + package = old_package(*args) + class << package + def pom + unless @pom || classifier + pom_filename = Util.replace_extension(name, 'pom') + spec = {:group => group, :id => id, :version => version, :type => :pom} + @pom = Buildr.artifact(spec, pom_filename) + buildr_project = Buildr.project(self.scope.join(':')) + @pom.content Buildr::CustomPom.pom_xml(buildr_project, self) + end + @pom + end + end + package.instance_variable_set('@pom', nil) + package.enhance([package.pom.to_s]) if package.type.to_s == 'jar' && !package.classifier + package + end + end + + module ActsAsArtifact + def pom_xml + self.pom.content + end + end +end http://git-wip-us.apache.org/repos/asf/buildr/blob/baa269bb/doc/more_stuff.textile ---------------------------------------------------------------------- diff --git a/doc/more_stuff.textile b/doc/more_stuff.textile index 1fdeebe..057ddcd 100644 --- a/doc/more_stuff.textile +++ b/doc/more_stuff.textile @@ -391,6 +391,78 @@ $ buildr --generate /path/to/my_project This creates a basic buildfile with a main project called 'my_project'. The buildfile contains a skeleton for compiling the Eclipse projects. If you want to automate dependency tracking via OSGi have a look at the "buildr4osgi":http://oss.intalio.com/buildr4osgi/ project. Support for building Eclipse RCP applications, running PDE tests and P2-sites is currently lacking in Buildr. +h2(#maven_central). Releasing to Maven Central + +Many opensource projects release their artifacts to Maven Central. To release a library to Maven Central, the project needs to provide several elements for each library; + +* the jar artifact, +* the sources artifact, +* the javadocs artifact, +* a pom that supplies fields required by Maven Central, and +* gpg signatures for every file supplied. + +Buildr has built-in support for the artifacts and can easily sign the artifacts using the 'buildr/gpg' addon. However it has not always been easy to generate a pom in the required format until the 'buildr/custom_pom' became available. + +Below is an extremely verbose example of a project that provides all the elements required to publish to Maven Central. + +{% highlight sh %} +# Include addon to generate GPG Signatures +require 'buildr/gpg' +# Include addon to generate custom pom +require 'buildr/custom_pom' + +define 'myproject' do + project.group = 'org.myproject' + project.version = '1.0' + + pom.licenses['The Apache Software License, Version 2.0'] = 'http://www.apache.org/licenses/LICENSE-2.0.txt' + pom.scm_connection = pom.scm_developer_connection = 'scm:git:[email protected]:jbloggs/myproject' + pom.scm_url = '[email protected]:jbloggs/myproject' + pom.url = 'https://github.com/jbloggs/myproject' + pom.issues_url = 'https://github.com/jbloggs/myproject/issues' + pom.issues_system = 'GitHub Issues' + pom.add_developer('jbloggs', 'Joe Bloggs', '[email protected]', ['Project Lead']) + pom.provided_dependencies.concat [:javax_servlet] + pom.optional_dependencies.concat [:optional_api] + + compile.with :javax_servlet, :some_api, :optional_api + + test.with :mockito + + package(:jar) + package(:sources) + package(:javadoc) +end +{% endhighlight %} + +That example is however, extremely verbose and there is a number of helper methods been added to the 'buildr/custom_pom' addon to simplify common scenarios. It would be more common to see the addon used in the following manner; + +{% highlight sh %} +require 'buildr/gpg' +require 'buildr/custom_pom' + +define 'myproject' do + project.group = 'org.myproject' + project.version = '1.0' + + pom.add_apache2_license + pom.add_github_project('jbloggs/myproject') + pom.add_developer('jbloggs', 'Joe Bloggs') + pom.provided_dependencies.concat [:javax_servlet] + pom.optional_dependencies.concat [:optional_api] + + compile.with :javax_servlet, :optional_api + + test.with :mockito + + package(:jar) + package(:sources) + package(:javadoc) +end +{% endhighlight %} + +If there are other common scenarios useful for opensource developers, feel free to make a request on buildr mailing list to provide simplified helper methods. + h2(#idea). IntelliJ IDEA If you use IntelliJ IDEA, you can generate project files by issuing: http://git-wip-us.apache.org/repos/asf/buildr/blob/baa269bb/spec/addon/custom_pom_spec.rb ---------------------------------------------------------------------- diff --git a/spec/addon/custom_pom_spec.rb b/spec/addon/custom_pom_spec.rb new file mode 100644 index 0000000..e201ad1 --- /dev/null +++ b/spec/addon/custom_pom_spec.rb @@ -0,0 +1,149 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + + +require File.expand_path('../spec_helpers', File.dirname(__FILE__)) +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'xpath_matchers')) + +Sandbox.require_optional_extension 'buildr/custom_pom' + +describe Buildr::CustomPom do + + def xml_document(filename) + File.should be_exist(filename) + REXML::Document.new(File.read(filename)) + end + + def project_pom_xml(project) + xml_document(project.packages[0].pom.to_s) + end + + def verify_license(pom_xml, name, url) + pom_xml.should match_xpath("/project/licenses/license/url[../name/text() = '#{name}']", url) + end + + def dependency_xpath(artifact_id) + "/project/dependencies/dependency[artifactId/text() = '#{artifact_id}']" + end + + def verify_dependency_group(pom_xml, artifact_id, group) + pom_xml.should match_xpath("#{dependency_xpath(artifact_id)}/groupId", group) + end + + def verify_dependency_version(pom_xml, artifact_id, version) + pom_xml.should match_xpath("#{dependency_xpath(artifact_id)}/version", version) + end + + def verify_dependency_scope(pom_xml, artifact_id, scope) + pom_xml.should match_xpath("#{dependency_xpath(artifact_id)}/scope", scope) + end + + def verify_dependency_optional(pom_xml, artifact_id, optional) + pom_xml.should match_xpath("#{dependency_xpath(artifact_id)}/optional", optional) + end + + def verify_dependency(pom_xml, artifact_id, group, version, scope, optional) + verify_dependency_group(pom_xml, artifact_id, group) + verify_dependency_version(pom_xml, artifact_id, version) + verify_dependency_scope(pom_xml, artifact_id, scope) + verify_dependency_optional(pom_xml, artifact_id, optional) + end + + describe "with explicitly specified pom details" do + before do + ['id-provided', 'id-optional', 'id-runtime', 'id-test'].each do |artifact_id| + artifact("group:#{artifact_id}:jar:1.0") do |t| + mkdir_p File.dirname(t.to_s) + Zip::ZipOutputStream.open t.to_s do |zip| + zip.put_next_entry 'empty.txt' + end + end + end + write 'src/main/java/Example.java', "public class Example {}" + + @foo = define 'foo' do + project.group = 'org.myproject' + project.version = '1.0' + + pom.licenses['The Apache Software License, Version 2.0'] = 'http://www.apache.org/licenses/LICENSE-2.0.txt' + pom.licenses['GNU General Public License (GPL) version 3.0'] = 'http://www.gnu.org/licenses/gpl-3.0.html' + pom.scm_connection = pom.scm_developer_connection = 'scm:git:[email protected]:jbloggs/myproject' + pom.scm_url = '[email protected]:jbloggs/myproject' + pom.url = 'https://github.com/jbloggs/myproject' + pom.issues_url = 'https://github.com/jbloggs/myproject/issues' + pom.issues_system = 'GitHub Issues' + pom.add_developer('jbloggs', 'Joe Bloggs', '[email protected]', ['Project Lead']) + pom.provided_dependencies = ['group:id-provided:jar:1.0'] + pom.optional_dependencies = ['group:id-optional:jar:1.0'] + + compile.with 'group:id-runtime:jar:1.0', 'group:id-optional:jar:1.0', 'group:id-provided:jar:1.0' + + test.with 'group:id-test:jar:1.0' + + package(:jar) + end + task('package').invoke + @pom_xml = project_pom_xml(@foo) + #$stderr.puts @pom_xml.to_s + end + + it "has correct static metadata" do + @pom_xml.should match_xpath("/project/modelVersion", '4.0.0') + @pom_xml.should match_xpath("/project/parent/groupId", 'org.sonatype.oss') + @pom_xml.should match_xpath("/project/parent/artifactId", 'oss-parent') + @pom_xml.should match_xpath("/project/parent/version", '7') + end + + it "has correct project level metadata" do + @pom_xml.should match_xpath("/project/groupId", 'org.myproject') + @pom_xml.should match_xpath("/project/artifactId", 'foo') + @pom_xml.should match_xpath("/project/version", '1.0') + @pom_xml.should match_xpath("/project/packaging", 'jar') + @pom_xml.should match_xpath("/project/name", 'foo') + @pom_xml.should match_xpath("/project/description", 'foo') + @pom_xml.should match_xpath("/project/url", 'https://github.com/jbloggs/myproject') + end + + it "has correct scm details" do + @pom_xml.should match_xpath("/project/scm/connection", 'scm:git:[email protected]:jbloggs/myproject') + @pom_xml.should match_xpath("/project/scm/developerConnection", 'scm:git:[email protected]:jbloggs/myproject') + @pom_xml.should match_xpath("/project/scm/url", '[email protected]:jbloggs/myproject') + end + + it "has correct issueManagement details" do + @pom_xml.should match_xpath("/project/issueManagement/url", 'https://github.com/jbloggs/myproject/issues') + @pom_xml.should match_xpath("/project/issueManagement/system", 'GitHub Issues') + end + + it "has correct developers details" do + @pom_xml.should match_xpath("/project/developers/developer/id", 'jbloggs') + @pom_xml.should match_xpath("/project/developers/developer/name", 'Joe Bloggs') + @pom_xml.should match_xpath("/project/developers/developer/email", '[email protected]') + @pom_xml.should match_xpath("/project/developers/developer/roles/role", 'Project Lead') + end + + it "has correct license details" do + verify_license(@pom_xml, 'The Apache Software License, Version 2.0', 'http://www.apache.org/licenses/LICENSE-2.0.txt') + verify_license(@pom_xml, 'GNU General Public License (GPL) version 3.0', 'http://www.gnu.org/licenses/gpl-3.0.html') + end + + it "has correct dependency details" do + verify_dependency(@pom_xml, 'id-runtime', 'group', '1.0', nil, nil) + verify_dependency(@pom_xml, 'id-optional', 'group', '1.0', nil, 'true') + verify_dependency(@pom_xml, 'id-provided', 'group', '1.0', 'provided', nil) + verify_dependency(@pom_xml, 'id-test', 'group', '1.0', 'test', nil) + end + end +end
