HHH-18485 - Documentation symlinks on release

This commit is contained in:
Steve Ebersole 2024-08-14 07:54:42 -05:00
parent 1718e884cc
commit 8fbd201151
7 changed files with 296 additions and 56 deletions

View File

@ -7,6 +7,7 @@
package org.hibernate.orm;
import java.io.Serializable;
import java.util.Objects;
/**
* Major/family version component pair
@ -45,6 +46,23 @@ public class ReleaseFamilyIdentifier implements Comparable<ReleaseFamilyIdentifi
return majorVersion + "." + familyVersion;
}
@Override
public boolean equals(Object o) {
if ( this == o ) {
return true;
}
if ( o == null || getClass() != o.getClass() ) {
return false;
}
ReleaseFamilyIdentifier that = (ReleaseFamilyIdentifier) o;
return majorVersion == that.majorVersion && familyVersion == that.familyVersion;
}
@Override
public int hashCode() {
return Objects.hash( majorVersion, familyVersion );
}
public boolean newerThan(ReleaseFamilyIdentifier other) {
return compareTo( other ) > 0;
}

View File

@ -20,7 +20,8 @@ import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
/**
* Models the published doc descriptor file
* Helper for {@linkplain #loadProject() loading} the project documentation descriptor and
* {@linkplain #storeProject storing} it to file.
*
* @author Steve Ebersole
*/
@ -46,6 +47,9 @@ public class DescriptorAccess {
}
}
/**
* Store the descriptor to file
*/
public static void storeProject(ProjectDocumentationDescriptor project, File jsonFile) {
prepareJsonFile( jsonFile );

View File

@ -25,12 +25,21 @@ import org.hibernate.orm.ReleaseFamilyIdentifier;
public class DocumentationPublishing {
public static final String DSL_NAME = "documentationPublishing";
public static final String RSYNC_SERVER = "filemgmt-prod-sync.jboss.org";
public static final String SFTP_SERVER = "filemgmt-prod.jboss.org";
public static final String DOC_SERVER_BASE_DIR = "/docs_htdocs/hibernate";
public static final String DESCRIPTOR_FILE = "doc-pub/orm.json";
private final Project project;
private final DirectoryProperty stagingDirectory;
private final Property<String> docServerUrl;
private final Property<String> docDescriptorUploadUrl;
private final Property<String> rsyncDocServer;
private final Property<String> sftpDocServer;
private final Property<String> serverBaseDir;
private final RegularFileProperty updatedJsonFile;
private final ReleaseFamilyIdentifier releaseFamilyIdentifier;
@ -43,18 +52,22 @@ public class DocumentationPublishing {
.directoryProperty()
.convention( project.getLayout().getBuildDirectory().dir( "documentation" ) );
docServerUrl = project.getObjects()
.property( String.class )
.convention( "filemgmt-prod-sync.jboss.org:/docs_htdocs/hibernate/orm" );
docDescriptorUploadUrl = project.getObjects()
rsyncDocServer = project.getObjects()
.property( String.class )
.convention( "filemgmt-prod-sync.jboss.org:/docs_htdocs/hibernate/_outdated-content/orm.json" );
.convention( RSYNC_SERVER );
sftpDocServer = project.getObjects()
.property( String.class )
.convention( SFTP_SERVER );
serverBaseDir = project.getObjects()
.property( String.class )
.convention( DOC_SERVER_BASE_DIR );
updatedJsonFile = project.getObjects()
.fileProperty()
.convention( project.getLayout().getBuildDirectory().file( "doc-pub/orm.json" ) );
.convention( project.getLayout().getBuildDirectory().file( DESCRIPTOR_FILE ) );
releaseFamilyIdentifier = ReleaseFamilyIdentifier.parse( project.getVersion().toString() );
}
@ -63,24 +76,22 @@ public class DocumentationPublishing {
return releaseFamilyIdentifier;
}
public Property<String> getDocServerUrl() {
return docServerUrl;
public Property<String> getRsyncDocServer() {
return rsyncDocServer;
}
public Property<String> getSftpDocServer() {
return sftpDocServer;
}
public Property<String> getServerBaseDir() {
return serverBaseDir;
}
public DirectoryProperty getStagingDirectory() {
return stagingDirectory;
}
/**
* Where to upload the {@link #getUpdatedJsonFile() documentation descriptor}
*/
public Property<String> getDocDescriptorUploadUrl() {
return docDescriptorUploadUrl;
}
/**
* THe ORM documentation descriptor
*/
public Provider<RegularFile> getUpdatedJsonFile() {
return updatedJsonFile;
}

View File

@ -6,8 +6,11 @@
*/
package org.hibernate.orm.docs;
import java.util.Locale;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.TaskProvider;
import static org.hibernate.orm.docs.DocumentationPublishing.DSL_NAME;
@ -17,10 +20,12 @@ import static org.hibernate.orm.docs.PublishTask.UPLOAD_TASK_NAME;
/**
* Plugin for helping with publishing documentation to the doc server - <ul>
* <li>Publishes a config extension ({@link DocumentationPublishing}) under {@value DocumentationPublishing#DSL_NAME}</li>
* <li>Creates a task ({@value PublishTask#UPLOAD_TASK_NAME}) to upload the documentation to the doc server</li>
* <li>Creates a task ({@value GenerateDescriptorTask#GEN_DESC_TASK_NAME}) to create the "published doc descriptor" (JSON) file</li>
* <li>Creates a task ({@value PublishDescriptorTask#UPLOAD_DESC_TASK_NAME}) to upload the "published doc descriptor" (JSON) file to the doc server</li>
* <li>Publishes a {@link DocumentationPublishing DSL extension} under {@value DocumentationPublishing#DSL_NAME}</li>
* <li>Creates a task to upload the documentation to the doc server - {@value PublishTask#UPLOAD_TASK_NAME}</li>
* <li>Creates a task to create the doc descriptor (JSON) file - {@value GenerateDescriptorTask#GEN_DESC_TASK_NAME}</li>
* <li>Creates a task to upload the doc descriptor (JSON) file to the doc server - {@value PublishDescriptorTask#UPLOAD_DESC_TASK_NAME}</li>
* <li>Creates a task to update symlinks on the doc server - {@value UpdateSymLinksTask#SYMLINKS_TASK_NAME}</li>
* <li>Creates a task to upload the migration guide to the doc server - {@value PublishMigrationGuide#NAME}</li>
* </ul>
*
* @author Steve Ebersole
@ -31,6 +36,7 @@ public class DocumentationPublishingPlugin implements Plugin<Project> {
final DocumentationPublishing docPubDsl = project.getExtensions().create( DSL_NAME, DocumentationPublishing.class );
final boolean isSnapshot = project.getVersion().toString().endsWith( "-SNAPSHOT" );
final boolean isFinal = project.getVersion().toString().endsWith( ".Final" );
final TaskProvider<GenerateDescriptorTask> generateDescriptorTask = project.getTasks().register(
GEN_DESC_TASK_NAME,
@ -38,6 +44,8 @@ public class DocumentationPublishingPlugin implements Plugin<Project> {
(task) -> {
task.getCurrentlyBuildingFamily().convention( docPubDsl.getReleaseFamilyIdentifier() );
task.getJsonFile().convention( docPubDsl.getUpdatedJsonFile() );
task.onlyIf( (t) -> isFinal );
}
);
@ -45,22 +53,11 @@ public class DocumentationPublishingPlugin implements Plugin<Project> {
UPLOAD_DESC_TASK_NAME,
PublishDescriptorTask.class,
(task) -> {
task.getDocDescriptorUploadUrl().convention( docPubDsl.getDocDescriptorUploadUrl() );
task.getDocDescriptorUploadUrl().convention( defaultDescriptorUploadUrl( docPubDsl ) );
task.getJsonFile().convention( docPubDsl.getUpdatedJsonFile() );
task.dependsOn( generateDescriptorTask );
task.onlyIf( (t) -> !isSnapshot && generateDescriptorTask.get().getDidWork() );
}
);
//noinspection unused
final TaskProvider<PublishMigrationGuide> publishMigrationGuideTask = project.getTasks().register(
PublishMigrationGuide.NAME,
PublishMigrationGuide.class,
(task) -> {
task.getCurrentlyBuildingFamily().convention( docPubDsl.getReleaseFamilyIdentifier() );
task.getDocServerUrl().convention( docPubDsl.getDocServerUrl() );
task.getMigrationGuideDirectory().convention( project.getLayout().getBuildDirectory().dir( "documentation/migration-guide" ) );
task.onlyIf( (t) -> generateDescriptorTask.get().getDidWork() && generateDescriptorTask.get().needsUpload() );
}
);
@ -70,12 +67,48 @@ public class DocumentationPublishingPlugin implements Plugin<Project> {
PublishTask.class,
(task) -> {
task.getBuildingFamily().convention( docPubDsl.getReleaseFamilyIdentifier() );
task.getDocServerUrl().convention( docPubDsl.getDocServerUrl() );
task.getStagingDirectory().convention( docPubDsl.getStagingDirectory() );
task.getDocServerUrl().convention( defaultDocUploadUrl( docPubDsl ) );
task.dependsOn( uploadDescriptorTask );
task.onlyIf( (t) -> !isSnapshot );
}
);
//noinspection unused
final TaskProvider<UpdateSymLinksTask> symLinkTask = project.getTasks().register(
UpdateSymLinksTask.SYMLINKS_TASK_NAME,
UpdateSymLinksTask.class,
(task) -> {
task.getBuildingFamily().convention( docPubDsl.getReleaseFamilyIdentifier() );
task.getSftpDocServer().convention( docPubDsl.getSftpDocServer() );
task.getServerBaseDir().convention( docPubDsl.getServerBaseDir() );
task.dependsOn( generateDescriptorTask );
task.dependsOn( uploadTask );
task.onlyIf( (t) -> generateDescriptorTask.get().getDidWork() && generateDescriptorTask.get().needsSymLinkUpdate() );
}
);
//noinspection unused
final TaskProvider<PublishMigrationGuide> publishMigrationGuideTask = project.getTasks().register(
PublishMigrationGuide.NAME,
PublishMigrationGuide.class,
(task) -> {
task.getCurrentlyBuildingFamily().convention( docPubDsl.getReleaseFamilyIdentifier() );
task.getDocServerUrl().convention( defaultDocUploadUrl( docPubDsl ) );
task.getMigrationGuideDirectory().convention( project.getLayout().getBuildDirectory().dir( "documentation/migration-guide" ) );
}
);
}
private Provider<String> defaultDescriptorUploadUrl(DocumentationPublishing dsl) {
return dsl.getRsyncDocServer()
.map( (server) -> String.format( Locale.ROOT, "%s:%s/_outdated-content/orm.json", server, dsl.getServerBaseDir().get() ) );
}
private Provider<String> defaultDocUploadUrl(DocumentationPublishing dsl) {
return dsl.getRsyncDocServer()
.map( (server) -> String.format( Locale.ROOT, "%s:%s/orm", server, dsl.getServerBaseDir().get() ) );
}
}

View File

@ -7,6 +7,9 @@
package org.hibernate.orm.docs;
import java.io.File;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.gradle.api.DefaultTask;
import org.gradle.api.file.RegularFileProperty;
@ -18,19 +21,25 @@ import org.gradle.api.tasks.TaskAction;
import org.hibernate.orm.ReleaseFamilyIdentifier;
/**
* Task for creating the JSON "documentation descriptor" for ORM
*
* @author Steve Ebersole
*/
public abstract class GenerateDescriptorTask extends DefaultTask {
public static final String GEN_DESC_TASK_NAME = "generateDocumentationDescriptor";
private final RegularFileProperty jsonFile;
private final Property<ReleaseFamilyIdentifier> currentlyBuildingFamily;
private final RegularFileProperty jsonFile;
private boolean needsUpload;
private boolean needsSymLinkUpdate;
public GenerateDescriptorTask() {
setGroup( "documentation" );
setDescription( "Generates the documentation publication descriptor (JSON)" );
jsonFile = getProject().getObjects().fileProperty();
currentlyBuildingFamily = getProject().getObjects().property( ReleaseFamilyIdentifier.class );
jsonFile = getProject().getObjects().fileProperty();
}
@Input
@ -43,14 +52,47 @@ public abstract class GenerateDescriptorTask extends DefaultTask {
return jsonFile;
}
/**
* Whether we determined, during {@linkplain #generateDescriptor}, that uploading the
* doc descriptor was needed.
*
* @see PublishDescriptorTask
*/
public boolean needsUpload() {
return getDidWork() && needsUpload;
}
/**
* Whether we determined, during {@linkplain #generateDescriptor}, that updating the
* doc server symlinks was needed.
*
* @see UpdateSymLinksTask
*/
public boolean needsSymLinkUpdate() {
return getDidWork() && needsSymLinkUpdate;
}
@TaskAction
public void generateDescriptor() {
final ProjectDocumentationDescriptor descriptor = DescriptorAccess.loadProject();
final boolean isFinal = getProject().getVersion().toString().endsWith( ".Final" );
final Set<ReleaseFamilyIdentifier> processedReleases = new HashSet<>();
ReleaseFamilyIdentifier newest = null;
boolean foundCurrentRelease = false;
for ( ReleaseFamilyDocumentation releaseFamily : descriptor.getReleaseFamilies() ) {
final Iterator<ReleaseFamilyDocumentation> itr = descriptor.getReleaseFamilies().iterator();
while ( itr.hasNext() ) {
final ReleaseFamilyDocumentation releaseFamily = itr.next();
// NOTE: sometimes releases get duplicated in the descriptor...
// let's clean those up if we run across them
if ( !processedReleases.add( releaseFamily.getName() ) ) {
itr.remove();
needsUpload = true;
continue;
}
if ( newest == null
|| releaseFamily.getName().newerThan( newest ) ) {
newest = releaseFamily.getName();
@ -61,27 +103,31 @@ public abstract class GenerateDescriptorTask extends DefaultTask {
}
}
if ( ! foundCurrentRelease ) {
final ReleaseFamilyDocumentation newEntry = new ReleaseFamilyDocumentation();
newEntry.setName( currentlyBuildingFamily.get() );
descriptor.addReleaseFamily( newEntry );
setDidWork( true );
}
if ( isFinal ) {
// we are releasing a Final - possibly do some other things
// we only want to update "stable" to `currentlyBuildingFamily` when-
// 1. we are currently building a Final
// 2. currentlyBuildingFamily is the newest
if ( !foundCurrentRelease ) {
// this release is not yet tracked in the descriptor - add it
final ReleaseFamilyDocumentation newEntry = new ReleaseFamilyDocumentation();
newEntry.setName( currentlyBuildingFamily.get() );
descriptor.addReleaseFamily( newEntry );
setDidWork( true );
needsUpload = true;
}
if ( currentlyBuildingFamily.get().newerThan( newest ) ) {
descriptor.setStableFamily( currentlyBuildingFamily.get() );
setDidWork( true );
if ( currentlyBuildingFamily.get().newerThan( newest ) ) {
// this release is newer than any currently tracked in the descriptor
descriptor.setStableFamily( currentlyBuildingFamily.get() );
setDidWork( true );
needsSymLinkUpdate = true;
}
}
DescriptorAccess.storeProject( descriptor, 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 File jsonFile = new File( "/tmp/hibernate-orm-build/doc-pub/orm.json" );
final ProjectDocumentationDescriptor projectDoc = DescriptorAccess.loadProject();
DescriptorAccess.storeProject( projectDoc, jsonFile );
}

View File

@ -64,8 +64,9 @@ public abstract class PublishTask extends DefaultTask {
getProject().getLogger().lifecycle( "Uploading documentation `{}` -> `{}`", stagingDirPath, url );
final ExecResult result = getProject().exec( (exec) -> {
exec.executable( "rsync" );
exec.args("--port=2222", "-avz", "--links", "--delete", stagingDirPathContent, url );
exec.args("-avz", "--delete", stagingDirPathContent, url );
} );
getProject().getLogger().lifecycle( "Done uploading documentation - {}", result.getExitValue() == 0 ? "success" : "failure" );
setDidWork( true );
}
}

View File

@ -0,0 +1,127 @@
/*
* 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.BufferedReader;
import java.io.Closeable;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Locale;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import org.gradle.api.DefaultTask;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.TaskAction;
import org.hibernate.orm.ReleaseFamilyIdentifier;
import static org.hibernate.orm.docs.DocumentationPublishing.DOC_SERVER_BASE_DIR;
import static org.hibernate.orm.docs.DocumentationPublishing.SFTP_SERVER;
/**
* Updates the "current" and "stable" symlinks on the doc server
*
* @author Steve Ebersole
*/
public class UpdateSymLinksTask extends DefaultTask {
public static final String SYMLINKS_TASK_NAME = "updateDocSymLinks";
private final Property<String> sftpDocServer;
private final Property<String> serverBaseDir;
private final Property<ReleaseFamilyIdentifier> buildingFamily;
public UpdateSymLinksTask() {
setGroup( "documentation" );
setDescription( "Updates the 'current' and 'stable' symlinks on the documentation server" );
buildingFamily = getProject().getObjects().property( ReleaseFamilyIdentifier.class );
sftpDocServer = getProject().getObjects().property( String.class );
serverBaseDir = getProject().getObjects().property( String.class );
}
public Property<ReleaseFamilyIdentifier> getBuildingFamily() {
return buildingFamily;
}
public Property<String> getSftpDocServer() {
return sftpDocServer;
}
public Property<String> getServerBaseDir() {
return serverBaseDir;
}
@TaskAction
public void updateSymLinks() throws Exception {
updateSymLinks( buildingFamily.get().toExternalForm(), sftpDocServer.get(), serverBaseDir.get() );
}
private static void updateSymLinks(String releaseName, String sftpServer, String serverBaseDir) throws Exception {
final File commandFile = createCommandFile( releaseName, serverBaseDir );
System.out.println( "SFTP command file : " + commandFile.getAbsolutePath() );
final Process sftpProcess = new ProcessBuilder()
.command( "sh", "sftp", "-b", commandFile.getAbsolutePath(), sftpServer )
.redirectInput( ProcessBuilder.Redirect.INHERIT )
.start();
ExecutorService service = Executors.newFixedThreadPool( 2 );
try ( InputStream is = sftpProcess.getInputStream(); InputStream es = sftpProcess.getErrorStream();
Closeable pool = service::shutdownNow ) {
service.submit( () -> drain( is, System.out::println ) );
service.submit( () -> drain( es, System.err::println ) );
service.shutdown();
final boolean isFinished = sftpProcess.waitFor( 15, TimeUnit.SECONDS );
if ( !isFinished ) {
System.out.println( "Forcibly ending sftp" );
sftpProcess.destroyForcibly();
}
}
}
private static void drain(InputStream stream, Consumer<String> consumer) {
try (InputStreamReader in = new InputStreamReader( stream, StandardCharsets.UTF_8 );
BufferedReader bufferedReader = new BufferedReader( in ); ) {
bufferedReader.lines().forEach( consumer );
}
catch (IOException e) {
throw new RuntimeException( e );
}
}
private static File createCommandFile(String releaseName, String serverBaseDir) throws IOException {
final File commandFile = File.createTempFile( "hibernate-orm-release-doc-symlink-" + releaseName, "-cmd.txt" );
try (FileWriter commandFileWriter = new FileWriter( commandFile )) {
commandFileWriter.write( "cd " + serverBaseDir + "/stable\n" );
commandFileWriter.write( "rm orm\n" );
commandFileWriter.write( String.format( Locale.ROOT, "ln -s ../orm/%s orm\n", releaseName ) );
commandFileWriter.write( "cd " + serverBaseDir + "/orm\n" );
commandFileWriter.write( "rm current\n" );
commandFileWriter.write( String.format( Locale.ROOT, "ln -s %s current\n", releaseName ) );
commandFileWriter.flush();
}
return commandFile;
}
public static void main(String[] args) throws Exception {
System.out.println( "Starting UpdateSymLinksTask" );
updateSymLinks( "6.6", SFTP_SERVER, DOC_SERVER_BASE_DIR );
}
}