Improved support for documentation publishing

This commit is contained in:
Steve Ebersole 2022-04-07 08:48:17 -05:00
parent e0a99f25ce
commit 47a3d3d56d
12 changed files with 700 additions and 12 deletions

View File

@ -17,6 +17,10 @@ buildDir = "target"
dependencies {
implementation gradleApi()
implementation 'org.jboss:jandex:2.4.2.Final'
implementation 'org.apache.httpcomponents:httpclient:4.5.13'
implementation 'jakarta.json.bind:jakarta.json.bind-api:2.0.0'
implementation 'jakarta.json:jakarta.json-api:2.0.1'
implementation 'org.eclipse:yasson:2.0.4'
}
tasks.compileJava {
@ -47,6 +51,10 @@ gradlePlugin {
id = 'org.hibernate.orm.build.reports'
implementationClass = 'org.hibernate.orm.post.ReportGenerationPlugin'
}
docPubPlugin {
id = 'org.hibernate.orm.build.doc-pub'
implementationClass = 'org.hibernate.orm.docs.DocumentationPublishingPlugin'
}
}
}

View File

@ -0,0 +1,94 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.orm;
/**
* Major/family version component pair
*
* @author Steve Ebersole
*/
public class ReleaseFamilyIdentifier implements Comparable<ReleaseFamilyIdentifier> {
private final int majorVersion;
private final int familyVersion;
public ReleaseFamilyIdentifier(int majorVersion, int familyVersion) {
this.majorVersion = majorVersion;
this.familyVersion = familyVersion;
}
/**
* The {@code 5} in {@code 5.6}, or the {@code 6} in {@code 6.0}
*/
public int getMajorVersion() {
return majorVersion;
}
/**
* The {@code 6} in {@code 5.6}, or the {@code 0} in {@code 6.0}
*/
public int getFamilyVersion() {
return familyVersion;
}
@Override
public String toString() {
return "ReleaseFamilyIdentifier( " + majorVersion + " . " + familyVersion + " )";
}
public String toExternalForm() {
return majorVersion + "." + familyVersion;
}
public boolean newerThan(ReleaseFamilyIdentifier other) {
return compareTo( other ) > 0;
}
public boolean olderThan(ReleaseFamilyIdentifier other) {
return compareTo( other ) < 0;
}
public static ReleaseFamilyIdentifier parse(String version) {
assert version != null && !version.isEmpty();
final int delimiter = version.indexOf( '.' );
assert delimiter > 0;
final int secondDelimiter = version.indexOf( '.', delimiter + 1 );
assert secondDelimiter < 0 || secondDelimiter > delimiter;
final String majorString = version.substring( 0, delimiter );
final String familyString;
if ( secondDelimiter < 0 ) {
familyString = version.substring( delimiter + 1 );
}
else {
familyString = version.substring( delimiter + 1, secondDelimiter );
}
final int major = Integer.parseInt( majorString );
final int family = Integer.parseInt( familyString );
return new ReleaseFamilyIdentifier( major, family );
}
@Override
public int compareTo(ReleaseFamilyIdentifier other) {
if ( this == other ) {
return 0;
}
if ( this.majorVersion < other.majorVersion ) {
return -1;
}
if ( this.majorVersion > other.majorVersion ) {
return 1;
}
return Integer.compare( this.familyVersion, other.familyVersion );
}
}

View File

@ -0,0 +1,82 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.orm.docs;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import jakarta.json.bind.Jsonb;
import jakarta.json.bind.JsonbBuilder;
import jakarta.json.bind.JsonbConfig;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
/**
* Basically a DAO for the doc pub descriptor
*
* @author Steve Ebersole
*/
public class DescriptorAccess {
public static final String DETAILS_URL = "https://docs.jboss.org/hibernate/_outdated-content/orm.json";
/**
* Load the descriptor
* @return
*/
public static ProjectDocumentation loadProject() {
try ( final CloseableHttpClient httpClient = HttpClientBuilder.create().build() ) {
final HttpGet request = new HttpGet( DETAILS_URL );
try ( final CloseableHttpResponse response = httpClient.execute( request ) ) {
final HttpEntity responseEntity = response.getEntity();
final Jsonb jsonb = JsonbBuilder.create( new JsonbConfig().withFormatting( true ) );
return jsonb.fromJson( responseEntity.getContent(), ProjectDocumentation.class );
}
}
catch (IOException e) {
throw new RuntimeException( "Unable to create HttpClient", e );
}
}
public static void storeProject(ProjectDocumentation project, File jsonFile) {
prepareJsonFile( jsonFile );
final Jsonb jsonb = JsonbBuilder.create( new JsonbConfig().withFormatting( true ) );
try ( final FileWriter writer = new FileWriter( jsonFile ) ) {
jsonb.toJson( project, writer );
}
catch (IOException e) {
throw new RuntimeException( "Unable to open write for JSON file : " + jsonFile.getPath(), e );
}
}
private static void prepareJsonFile(File jsonFile) {
if ( jsonFile.exists() ) {
final boolean deleted = jsonFile.delete();
assert deleted;
}
if ( ! jsonFile.getParentFile().exists() ) {
final boolean dirsMade = jsonFile.getParentFile().mkdirs();
assert dirsMade;
}
try {
final boolean created = jsonFile.createNewFile();
assert created;
}
catch (IOException e) {
throw new RuntimeException( "Unable to create JSON file : `" + jsonFile.getPath() + "`", e );
}
}
}

View File

@ -0,0 +1,78 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.orm.docs;
import javax.inject.Inject;
import org.gradle.api.Project;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.file.RegularFile;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.hibernate.orm.ReleaseFamilyIdentifier;
/**
* Gradle DSL extension for configuring documentation publishing
*
* @author Steve Ebersole
*/
public class DocumentationPublishing {
private final Project project;
private final DirectoryProperty stagingDirectory;
private final Property<String> docServerUrl;
private final RegularFileProperty updatedJsonFile;
private final ReleaseFamilyIdentifier releaseFamilyIdentifier;
@Inject
public DocumentationPublishing(Project project) {
this.project = project;
stagingDirectory = project.getObjects()
.directoryProperty()
.convention( project.getLayout().getBuildDirectory().dir( "documentation" ) );
docServerUrl = project.getObjects()
.property( String.class )
.convention( "filemgmt.jboss.org:/docs_htdocs/hibernate/orm" );
updatedJsonFile = project.getObjects()
.fileProperty()
.convention( project.getLayout().getBuildDirectory().file( "doc-pub/orm.json" ) );
// todo : pull HibernateOrmVersion out of `gradle/basic-information.gradle` and use here
// for now, just parse the project version
releaseFamilyIdentifier = ReleaseFamilyIdentifier.parse( project.getVersion().toString() );
}
public ReleaseFamilyIdentifier getReleaseFamilyIdentifier() {
return releaseFamilyIdentifier;
}
public Provider<RegularFile> getUpdatedJsonFile() {
return updatedJsonFile;
}
public Property<String> getDocServerUrl() {
return docServerUrl;
}
public DirectoryProperty getStagingDirectory() {
return stagingDirectory;
}
public void setUpdatedJsonFile(Object ref) {
updatedJsonFile.fileValue( project.file( ref ) );
}
public void updatedJsonFile(Object ref) {
updatedJsonFile.fileValue( project.file( ref ) );
}
}

View File

@ -0,0 +1,58 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.orm.docs;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
/**
* Plugin for helping with publishing documentation to the doc server - <ul>
* <li>Publishes a config extension ({@link DocumentationPublishing}) under {@value #EXT}</li>
* <li>Creates a task ({@value #UPLOAD_TASK}) to upload the documentation to the doc server</li>
* <li>Creates a task ({@value #GEN_DESC_TASK}) to create the "published doc descriptor" (JSON) file</li>
* <li>Creates a task ({@value #UPLOAD_DESC_TASK}) to upload the "published doc descriptor" (JSON) file to the doc server</li>
* </ul>
*
* @author Steve Ebersole
*/
public class DocumentationPublishingPlugin implements Plugin<Project> {
public static final String EXT = "documentationPublishing";
public static final String UPLOAD_TASK = "uploadDocumentation";
public static final String GEN_DESC_TASK = "generatorDocumentationDescriptor";
public static final String UPLOAD_DESC_TASK = "uploadDocumentationDescriptor";
@Override
public void apply(Project project) {
final DocumentationPublishing docPubDsl = project.getExtensions().create( EXT, DocumentationPublishing.class );
final PublishTask uploadTask = project.getTasks().create(
UPLOAD_TASK,
PublishTask.class,
docPubDsl
);
final GenerateDescriptorTask generateDescriptorTask = project.getTasks().create(
GEN_DESC_TASK,
GenerateDescriptorTask.class,
docPubDsl
);
final PublishDescriptorTask uploadDescriptorTask = project.getTasks().create(
UPLOAD_DESC_TASK,
PublishDescriptorTask.class,
docPubDsl
);
// todo - incorporate HibernateVersion from `gradle/base-information.gradle`
final boolean isSnapshot = project.getVersion().toString().endsWith( "-SNAPSHOT" );
uploadTask.onlyIf( (task) -> !isSnapshot );
uploadDescriptorTask.onlyIf( (task) -> !isSnapshot && generateDescriptorTask.getDidWork() );
uploadTask.dependsOn( uploadDescriptorTask );
uploadDescriptorTask.dependsOn( generateDescriptorTask );
}
}

View File

@ -0,0 +1,82 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.orm.docs;
import java.io.File;
import javax.inject.Inject;
import org.gradle.api.DefaultTask;
import org.gradle.api.file.RegularFile;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;
import org.hibernate.orm.ReleaseFamilyIdentifier;
/**
* @author Steve Ebersole
*/
public abstract class GenerateDescriptorTask extends DefaultTask {
private final Provider<RegularFile> jsonFile;
private final ReleaseFamilyIdentifier currentlyBuildingFamily;
@Inject
public GenerateDescriptorTask(DocumentationPublishing config) {
setGroup( "Release" );
setDescription( "Generates the documentation publication descriptor (JSON)" );
jsonFile = config.getUpdatedJsonFile();
currentlyBuildingFamily = config.getReleaseFamilyIdentifier();
}
@OutputFile
public Provider<RegularFile> getJsonFile() {
return jsonFile;
}
@TaskAction
public void generateDescriptor() {
final ProjectDocumentation projectDoc = DescriptorAccess.loadProject();
ReleaseFamilyIdentifier newest = null;
boolean foundCurrentRelease = false;
for ( ReleaseFamilyDocumentation releaseFamily : projectDoc.getReleaseFamilies() ) {
if ( newest == null
|| releaseFamily.getName().newerThan( newest ) ) {
newest = releaseFamily.getName();
}
if ( releaseFamily.getName().equals( currentlyBuildingFamily ) ) {
foundCurrentRelease = true;
}
}
if ( ! foundCurrentRelease ) {
final ReleaseFamilyDocumentation newEntry = new ReleaseFamilyDocumentation();
newEntry.setName( currentlyBuildingFamily );
setDidWork( true );
}
// we only want to update "stable" to `currentlyBuildingFamily` when-
// 1. we are currently building a Final
// 2. currentlyBuildingFamily is the newest
if ( currentlyBuildingFamily.newerThan( newest ) ) {
projectDoc.setStableFamily( currentlyBuildingFamily );
setDidWork( true );
}
DescriptorAccess.storeProject( projectDoc, jsonFile.get().getAsFile() );
}
public static void main(String... args) {
final File jsonFile = new File( "/home/sebersole/projects/hibernate-orm/6.0/hibernate-orm-build/target/doc-pub/orm.json" );
final ProjectDocumentation projectDoc = DescriptorAccess.loadProject();
DescriptorAccess.storeProject( projectDoc, jsonFile );
}
}

View File

@ -0,0 +1,81 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.orm.docs;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
import org.hibernate.orm.ReleaseFamilyIdentifier;
import jakarta.json.bind.annotation.JsonbProperty;
import jakarta.json.bind.annotation.JsonbPropertyOrder;
import jakarta.json.bind.annotation.JsonbTypeAdapter;
/**
* Binding for the doc-pub descriptor (JSON) file
*
* @author Steve Ebersole
*/
@JsonbPropertyOrder( {"name", "stableFamily", "singlePageDetails", "multiPageDetails", "releaseFamilies" } )
public class ProjectDocumentation {
@JsonbProperty( "project" )
private String name;
@JsonbProperty( "stable" )
@JsonbTypeAdapter( ReleaseFamilyIdentifierMarshalling.class )
private ReleaseFamilyIdentifier stableFamily;
@JsonbProperty( "versions" )
private List<ReleaseFamilyDocumentation> releaseFamilies;
@JsonbProperty( "multi" )
private Map<String,String> multiPageDetails;
@JsonbProperty( "single" )
private Map<String,String> singlePageDetails;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public ReleaseFamilyIdentifier getStableFamily() {
return stableFamily;
}
public void setStableFamily(ReleaseFamilyIdentifier stableFamily) {
this.stableFamily = stableFamily;
}
public List<ReleaseFamilyDocumentation> getReleaseFamilies() {
return releaseFamilies;
}
public void setReleaseFamilies(List<ReleaseFamilyDocumentation> releaseFamilies) {
this.releaseFamilies = releaseFamilies;
}
public Map<String, String> getMultiPageDetails() {
return multiPageDetails;
}
public void setMultiPageDetails(Map<String, String> multiPageDetails) {
this.multiPageDetails = multiPageDetails;
}
public Map<String, String> getSinglePageDetails() {
return singlePageDetails;
}
public void setSinglePageDetails(Map<String, String> singlePageDetails) {
this.singlePageDetails = singlePageDetails;
}
}

View File

@ -0,0 +1,59 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.orm.docs;
import javax.inject.Inject;
import org.gradle.api.DefaultTask;
import org.gradle.api.file.RegularFile;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.SkipWhenEmpty;
import org.gradle.api.tasks.TaskAction;
/**
* @author Steve Ebersole
*/
public abstract class PublishDescriptorTask extends DefaultTask {
private final Provider<String> docServerUrl;
private final Provider<RegularFile> jsonFile;
@Inject
public PublishDescriptorTask(DocumentationPublishing config) {
setGroup( "Release" );
setDescription( "Publishes the documentation publication descriptor (JSON)" );
docServerUrl = config.getDocServerUrl();
jsonFile = config.getUpdatedJsonFile();
}
@InputFile
@SkipWhenEmpty
public Provider<RegularFile> getJsonFile() {
return jsonFile;
}
@Input
public Provider<String> getDocServerUrl() {
return docServerUrl;
}
@TaskAction
public void uploadDescriptor() {
final String base = docServerUrl.get();
final String normalizedBase = base.endsWith( "/" ) ? base : base + "/";
final String url = normalizedBase + "_outdated-content/orm.json";
final String jsonPath = jsonFile.get().getAsFile().getAbsolutePath();
getProject().exec( (exec) -> {
exec.executable( "scp" );
exec.args( jsonPath, url );
} );
}
}

View File

@ -0,0 +1,65 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.orm.docs;
import javax.inject.Inject;
import org.gradle.api.DefaultTask;
import org.gradle.api.file.Directory;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputDirectory;
import org.gradle.api.tasks.TaskAction;
import org.gradle.process.ExecResult;
import org.hibernate.orm.ReleaseFamilyIdentifier;
/**
* @author Steve Ebersole
*/
public abstract class PublishTask extends DefaultTask {
private final ReleaseFamilyIdentifier buildingFamily;
private final Provider<String> docServerUrl;
private final Provider<Directory> stagingDirectory;
@Inject
public PublishTask(DocumentationPublishing config) {
setGroup( "Release" );
setDescription( "Publish documentation to the doc server" );
buildingFamily = config.getReleaseFamilyIdentifier();
stagingDirectory = config.getStagingDirectory();
docServerUrl = config.getDocServerUrl();
}
@Input
public Provider<String> getDocServerUrl() {
return docServerUrl;
}
@InputDirectory
public Provider<Directory> getStagingDirectory() {
return stagingDirectory;
}
@TaskAction
public void uploadDocumentation() {
final String releaseFamily = buildingFamily.toExternalForm();
final String base = docServerUrl.get();
final String normalizedBase = base.endsWith( "/" ) ? base : base + "/";
final String url = normalizedBase + "orm/" + releaseFamily;
final String stagingDirPath = stagingDirectory.get().getAsFile().getAbsolutePath();
getProject().getLogger().lifecycle( "Uploading documentation `{}` -> `{}`", stagingDirPath, url );
final ExecResult result = getProject().exec( (exec) -> {
exec.executable( "rsync" );
exec.args( "-avz", "--links", "--protocol=28", stagingDirPath, url );
} );
getProject().getLogger().lifecycle( "Done uploading documentation - {}", result.getExitValue() == 0 ? "success" : "failure" );
}
}

View File

@ -0,0 +1,60 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.orm.docs;
import java.util.HashMap;
import java.util.Map;
import org.hibernate.orm.ReleaseFamilyIdentifier;
import jakarta.json.bind.annotation.JsonbProperty;
import jakarta.json.bind.annotation.JsonbPropertyOrder;
import jakarta.json.bind.annotation.JsonbTypeAdapter;
/**
* Binding for the doc-pub descriptor (JSON) related to a specific release family.
*
* @see ProjectDocumentation
*
* @author Steve Ebersole
*/
@JsonbPropertyOrder( { "name", "redirects" } )
public class ReleaseFamilyDocumentation {
@JsonbProperty( "version" )
@JsonbTypeAdapter( ReleaseFamilyIdentifierMarshalling.class )
private ReleaseFamilyIdentifier name;
private Map<String,String> redirects;
public ReleaseFamilyDocumentation() {
}
/**
* The release family, e.g. `6.0` or `5.6`
*/
public ReleaseFamilyIdentifier getName() {
return name;
}
public void setName(ReleaseFamilyIdentifier name) {
this.name = name;
}
public Map<String, String> getRedirects() {
return redirects;
}
public void setRedirects(Map<String, String> redirects) {
this.redirects = redirects;
}
public void redirect(String from, String to) {
if ( redirects == null ) {
redirects = new HashMap<>();
}
redirects.put( from, to );
}
}

View File

@ -0,0 +1,31 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.orm.docs;
import org.hibernate.orm.ReleaseFamilyIdentifier;
import jakarta.json.Json;
import jakarta.json.JsonString;
import jakarta.json.JsonValue;
import jakarta.json.bind.adapter.JsonbAdapter;
/**
* @author Steve Ebersole
*/
public class ReleaseFamilyIdentifierMarshalling implements JsonbAdapter<ReleaseFamilyIdentifier, JsonValue> {
@Override
public JsonValue adaptToJson(ReleaseFamilyIdentifier obj) throws Exception {
return Json.createValue( obj.toExternalForm() );
}
@Override
public ReleaseFamilyIdentifier adaptFromJson(JsonValue obj) throws Exception {
assert obj.getValueType() == JsonValue.ValueType.STRING;
final JsonString jsonString = (JsonString) obj;
return ReleaseFamilyIdentifier.parse( jsonString.getString() );
}
}

View File

@ -12,6 +12,7 @@ import groovy.json.JsonSlurper
apply from: rootProject.file( 'gradle/module.gradle' )
apply from: rootProject.file( 'gradle/libraries.gradle' )
apply plugin: 'org.hibernate.orm.build.doc-pub'
apply plugin: 'idea'
idea.module {
@ -325,20 +326,9 @@ task assembleDocumentation {
// into projectTemplateStagingDir
//}
/**
* Upload the documentation to the JBoss doc server
*/
task uploadDocumentation(type:Exec) {
group 'Release'
description 'Uploads documentation to the JBoss doc server'
tasks.uploadDocumentation {
dependsOn assembleDocumentation
final String url = "filemgmt.jboss.org:/docs_htdocs/hibernate/orm/${rootProject.ormVersion.family}";
executable 'rsync'
args '-avz', '--links', '--protocol=28', "${buildDir}/documentation/", url
doFirst {
if ( rootProject.ormVersion.isSnapshot ) {
logger.error( "Cannot perform upload of SNAPSHOT documentation" );