/*
* Copyright 2011 the original author or authors.
*
* Licensed 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.
*/
package org.gradle.plugins.signing;
import groovy.lang.Closure;
import org.gradle.api.Action;
import org.gradle.api.InvalidUserDataException;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.artifacts.PublishArtifact;
import org.gradle.api.artifacts.maven.MavenDeployment;
import org.gradle.api.internal.ConventionMapping;
import org.gradle.api.internal.IConventionAware;
import org.gradle.api.internal.project.ProjectInternal;
import org.gradle.internal.reflect.Instantiator;
import org.gradle.plugins.signing.signatory.Signatory;
import org.gradle.plugins.signing.signatory.SignatoryProvider;
import org.gradle.plugins.signing.signatory.pgp.PgpSignatoryProvider;
import org.gradle.plugins.signing.type.DefaultSignatureTypeProvider;
import org.gradle.plugins.signing.type.SignatureType;
import org.gradle.plugins.signing.type.SignatureTypeProvider;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import static org.codehaus.groovy.runtime.StringGroovyMethods.capitalize;
import static org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation.castToBoolean;
import static org.gradle.util.GUtil.uncheckedCall;
/**
* The global signing configuration for a project.
*/
public class SigningExtension {
/**
* The name of the configuration that all signature artifacts will be placed into ("signatures")
*/
public static final String DEFAULT_CONFIGURATION_NAME = "signatures";
/**
* The project that the settings are for
*/
private final Project project;
/**
* The configuration that signature artifacts will be placed into.
*
* <p>Changing this will not affect any signing already configured.</p>
*/
private Configuration configuration;
private Object required = true;
/**
* The provider of signature types.
*/
private SignatureTypeProvider signatureTypes;
/**
* The provider of signatories.
*/
private SignatoryProvider signatories;
/**
* Configures the signing settings for the given project.
*/
public SigningExtension(Project project) {
this.project = project;
this.configuration = getDefaultConfiguration();
this.signatureTypes = createSignatureTypeProvider();
this.signatories = createSignatoryProvider();
project.getTasks().withType(Sign.class, new Action<Sign>() {
@Override
public void execute(Sign task) {
addSignatureSpecConventions(task);
}
});
}
public final Project getProject() {
return project;
}
/**
* Whether or not this task should fail if no signatory or signature type are configured at generation time.
* @since 4.0
*/
public void setRequired(boolean required) {
this.required = required;
}
/**
* Whether or not this task should fail if no signatory or signature type are configured at generation time.
*
* If {@code required} is a {@link Callable}, it will be stored and "called" on demand (i.e. when {@link #isRequired()} is called) and the return value will be interpreting according to the Groovy
* Truth. For example:
*
* <pre>
* signing {
* required = { gradle.taskGraph.hasTask("uploadArchives") }
* }
* </pre>
*
* Because the task graph is not known until Gradle starts executing, we must use defer the decision. We can do this via using a {@link Closure} (which is a {@link Callable}).
*
* For any other type, the value will be stored and evaluated on demand according to the Groovy Truth.
*
* <pre>
* signing {
* required = false
* }
* </pre>
*/
public void setRequired(Object required) {
this.required = required;
}
/**
* Whether or not this task should fail if no signatory or signature type are configured at generation time.
*
* <p>Defaults to {@code true}.</p>
*
* @see #setRequired(Object)
*/
public boolean isRequired() {
return castToBoolean(force(required));
}
/**
* Provides the configuration that signature artifacts are added to. Called once during construction.
*/
protected Configuration getDefaultConfiguration() {
ConfigurationContainer configurations = project.getConfigurations();
Configuration configuration = configurations.findByName(DEFAULT_CONFIGURATION_NAME);
return configuration != null
? configuration
: configurations.create(DEFAULT_CONFIGURATION_NAME);
}
/**
* Provides the signature type provider. Called once during construction.
*/
protected SignatureTypeProvider createSignatureTypeProvider() {
return new DefaultSignatureTypeProvider();
}
/**
* Provides the signatory provider. Called once during construction.
*/
protected SignatoryProvider createSignatoryProvider() {
return new PgpSignatoryProvider();
}
/**
* Configures the signatory provider (delegating to its {@link SignatoryProvider#configure(SigningExtension, Closure) configure method}).
*
* @param closure the signatory provider configuration DSL
* @return the configured signatory provider
*/
public SignatoryProvider signatories(Closure closure) {
signatories.configure(this, closure);
return signatories;
}
/**
* The signatory that will be used for signing when an explicit signatory has not been specified.
*
* <p>Delegates to the signatory provider's default signatory.</p>
*/
public Signatory getSignatory() {
return signatories.getDefaultSignatory(project);
}
/**
* The signature type that will be used for signing files when an explicit signature type has not been specified.
*
* <p>Delegates to the signature type provider's default type.</p>
*/
public SignatureType getSignatureType() {
return signatureTypes.getDefaultType();
}
public void setSignatureTypes(SignatureTypeProvider signatureTypes) {
this.signatureTypes = signatureTypes;
}
public SignatureTypeProvider getSignatureTypes() {
return signatureTypes;
}
public void setSignatories(SignatoryProvider signatories) {
this.signatories = signatories;
}
public void setConfiguration(Configuration configuration) {
this.configuration = configuration;
}
/**
* The configuration that signature artifacts are added to.
*/
public Configuration getConfiguration() {
return configuration;
}
/**
* Adds conventions to the given spec, using this settings object's default signatory and signature type as the default signatory and signature type for the spec.
*/
protected void addSignatureSpecConventions(SignatureSpec spec) {
if (!(spec instanceof IConventionAware)) {
throw new InvalidUserDataException("Cannot add conventions to signature spec \'" + String.valueOf(spec) + "\' as it is not convention aware");
}
ConventionMapping conventionMapping = ((IConventionAware) spec).getConventionMapping();
conventionMapping.map("signatory", new Callable<Signatory>() {
public Signatory call() {
return getSignatory();
}
});
conventionMapping.map("signatureType", new Callable<SignatureType>() {
public SignatureType call() {
return getSignatureType();
}
});
conventionMapping.map("required", new Callable<Boolean>() {
public Boolean call() {
return isRequired();
}
});
}
/**
* Creates signing tasks that depend on and sign the "archive" produced by the given tasks.
*
* <p>The created tasks will be named "sign<i><input task name capitalized></i>". That is, given a task with the name "jar" the created task will be named "signJar". <p> If the task is not
* an {@link org.gradle.api.tasks.bundling.AbstractArchiveTask}, an {@link InvalidUserDataException} will be thrown.</p> <p> The signature artifact for the created task is added to the {@link
* #getConfiguration() for this settings object}.
*
* @param tasks The tasks whose archives are to be signed
* @return the created tasks.
*/
public List<Sign> sign(Task... tasks) {
List<Sign> result = new ArrayList<Sign>(tasks.length);
for (final Task taskToSign : tasks) {
result.add(
createSignTaskFor(taskToSign.getName(), new Action<Sign>() {
public void execute(Sign task) {
task.sign(taskToSign);
}
})
);
}
return result;
}
/**
* Creates signing tasks that sign {@link Configuration#getAllArtifacts() all of the artifacts} of the given configurations.
*
* <p>The created tasks will be named "sign<i><configuration name capitalized></i>". That is, given a configuration with the name "archives" the created task will be named "signArchives".
*
* The signature artifact for the created task is added to the {@link #getConfiguration() for this settings object}.
*
* @param configurations The configurations whose archives are to be signed
* @return the created tasks.
*/
public List<Sign> sign(Configuration... configurations) {
List<Sign> result = new ArrayList<Sign>(configurations.length);
for (final Configuration configurationToSign : configurations) {
result.add(
createSignTaskFor(configurationToSign.getName(), new Action<Sign>() {
public void execute(Sign task) {
task.sign(configurationToSign);
}
})
);
}
return result;
}
private Sign createSignTaskFor(final CharSequence name, Action<Sign> taskConfiguration) {
Sign signTask = project.getTasks().create("sign" + capitalize(name), Sign.class, taskConfiguration);
addSignaturesToConfiguration(signTask, getConfiguration());
return signTask;
}
protected Object addSignaturesToConfiguration(Sign task, final Configuration configuration) {
task.getSignatures().all(new Action<Signature>() {
public void execute(Signature sig) {
configuration.getArtifacts().add(sig);
}
});
return task.getSignatures().whenObjectRemoved(new Action<Signature>() {
public void execute(Signature sig) {
configuration.getArtifacts().remove(sig);
}
});
}
/**
* Digitally signs the publish artifacts, generating signature files alongside them.
*
* <p>The project's default signatory and default signature type from the {@link SigningExtension signing settings} will be used to generate the signature. The returned {@link SignOperation sign
* operation} gives access to the created signature files. <p> If there is no configured default signatory available, the sign operation will fail.
*
* @param publishArtifacts The publish artifacts to sign
* @return The executed {@link SignOperation sign operation}
*/
public SignOperation sign(final PublishArtifact... publishArtifacts) {
return doSignOperation(new Action<SignOperation>() {
public void execute(SignOperation operation) {
operation.sign(publishArtifacts);
}
});
}
/**
* Digitally signs the files, generating signature files alongside them.
*
* <p>The project's default signatory and default signature type from the {@link SigningExtension signing settings} will be used to generate the signature. The returned {@link SignOperation sign
* operation} gives access to the created signature files. <p> If there is no configured default signatory available, the sign operation will fail.
*
* @param files The files to sign.
* @return The executed {@link SignOperation sign operation}.
*/
public SignOperation sign(final File... files) {
return doSignOperation(new Action<SignOperation>() {
public void execute(SignOperation operation) {
operation.sign(files);
}
});
}
/**
* Digitally signs the files, generating signature files alongside them.
*
* <p>The project's default signatory and default signature type from the {@link SigningExtension signing settings} will be used to generate the signature. The returned {@link SignOperation sign
* operation} gives access to the created signature files. <p> If there is no configured default signatory available, the sign operation will fail.
*
* @param classifier The classifier to assign to the created signature artifacts.
* @param files The publish artifacts to sign.
* @return The executed {@link SignOperation sign operation}.
*/
public SignOperation sign(final String classifier, final File... files) {
return doSignOperation(new Action<SignOperation>() {
public void execute(SignOperation operation) {
operation.sign(classifier, files);
}
});
}
/**
* Creates a new {@link SignOperation sign operation} using the given closure to configure it before executing it.
*
* <p>The project's default signatory and default signature type from the {@link SigningExtension signing settings} will be used to generate the signature. The returned {@link SignOperation sign
* operation} gives access to the created signature files. <p> If there is no configured default signatory available (and one is not explicitly specified in this operation's configuration), the
* sign operation will fail.
*
* @param closure The configuration of the {@link SignOperation sign operation}.
* @return The executed {@link SignOperation sign operation}.
*/
public SignOperation sign(Closure closure) {
return doSignOperation(closure);
}
/**
* Signs the POM artifact for the given Maven deployment.
*
* <p>You can use this method to sign the generated POM when publishing to a Maven repository with the Maven plugin. </p>
* <pre autoTested=''>
* uploadArchives {
* repositories {
* mavenDeployer {
* beforeDeployment { MavenDeployment deployment ->
* signing.signPom(deployment)
* }
* }
* }
* }
* </pre>
* <p>You can optionally provide a configuration closure to fine tune the {@link SignOperation sign
* operation} for the POM.</p> <p> If {@link #isRequired()} is false and the signature cannot be generated (e.g. no configured signatory), this method will silently do nothing. That is, a
* signature for the POM file will not be uploaded.
* <p>
* <b>Note:</b> Signing the generated POM file generated by the Maven Publishing plugin is currently not supported. Future versions of Gradle might add this functionality.
*
* @param mavenDeployment The deployment to sign the POM of
* @param closure the configuration of the underlying {@link SignOperation sign operation} for the POM (optional)
* @return the generated signature artifact
*/
public Signature signPom(final MavenDeployment mavenDeployment, final Closure closure) {
SignOperation signOperation = doSignOperation(new Action<SignOperation>() {
public void execute(SignOperation so) {
so.sign(mavenDeployment.getPomArtifact());
so.configure(closure);
}
});
Signature pomSignature = signOperation.getSingleSignature();
if (!pomSignature.getFile().exists()) {
// This means that the signature was not required and we couldn't generate the signature
// (most likely project.required == false and there is no signatory)
// So just noop
return null;
}
// Have to alter the "type" of the artifact to match what is published
// See https://issues.gradle.org/browse/GRADLE-1589
pomSignature.setType("pom." + pomSignature.getSignatureType().getExtension());
mavenDeployment.addArtifact(pomSignature);
return pomSignature;
}
/**
* Signs the POM artifact for the given Maven deployment.
*
* <p>You can use this method to sign the generated POM when publishing to a Maven repository with the Maven plugin. </p>
* <pre autoTested=''>
* uploadArchives {
* repositories {
* mavenDeployer {
* beforeDeployment { MavenDeployment deployment ->
* signing.signPom(deployment)
* }
* }
* }
* }
* </pre>
* <p>You can optionally provide a configuration closure to fine tune the {@link SignOperation sign
* operation} for the POM.</p> <p> If {@link #isRequired()} is false and the signature cannot be generated (e.g. no configured signatory), this method will silently do nothing. That is, a
* signature for the POM file will not be uploaded.
* <p>
* <b>Note:</b> Signing the generated POM file generated by the Maven Publishing plugin is currently not supported. Future versions of Gradle might add this functionality.
*
* @param mavenDeployment The deployment to sign the POM of
* @return the generated signature artifact
*/
public Signature signPom(MavenDeployment mavenDeployment) {
return signPom(mavenDeployment, null);
}
protected SignOperation doSignOperation(final Closure setup) {
return doSignOperation(new Action<SignOperation>() {
@Override
public void execute(SignOperation operation) {
operation.configure(setup);
}
});
}
protected SignOperation doSignOperation(Action<SignOperation> setup) {
SignOperation operation = instantiator().newInstance(SignOperation.class);
addSignatureSpecConventions(operation);
setup.execute(operation);
operation.execute();
return operation;
}
private Instantiator instantiator() {
return ((ProjectInternal) project).getServices().get(Instantiator.class);
}
public SignatoryProvider getSignatories() {
return signatories;
}
private Object force(Object maybeCallable) {
return maybeCallable instanceof Callable
? uncheckedCall((Callable) maybeCallable)
: maybeCallable;
}
}