Search code examples
mavenmigrationsonatypejcenter

How can I migrate JCenter artifact to the Sonatype Maven repository?


The JCenter Maven repository will be down in some month.

https://jfrog.com/blog/into-the-sunset-bintray-jcenter-gocenter-and-chartcenter/?utm_source=mkto&utm_medium=email&utm_campaign=bintray-sunset&utm_content=global-02-2021

How can I migrate all artifact to Sonatype before it will be deleted.


Solution

  • I have solve this with multiple steps:

    • First I have written a Java program to download all the artifacts from my JCenter account.
    • Then I have write a Gradle script with the Maven plugin. This script use archiveBaseName, version and the directory of the artifacts as parameters. The script and maven plugin add all the missing things like the PGP signature.
    • Then I write a Java program that iterate over the downloaded artifacts (or a part of it) and call the Gradle script with the parameters archiveBaseName, version and the directory with the artifacts in a loop.
    • On the Sonatype GUI I deploy the uploaded files. Multiple version at once.

    The follow code snippet which I have used will not work out of the box because it used internal private API. But it can help as start point. Of course you need an sonatype account and a gpg ring file. The secret properties are saved in the gradle.properties. You need also to customize the group name that the company used in all 3 files.

    Downloader

    package tool;
    
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.net.URL;
    import java.nio.charset.StandardCharsets;
    import java.util.ArrayList;
    import java.util.List;
    
    import javax.annotation.Nonnull;
    
    import com.inet.lib.util.IOFunctions;
    
    public class DownloadJCenter {
    
        // this path will be cut on save
        private static String basePath;
    
        private static File   root;
    
        public static void main( String[] args ) throws IOException {
            basePath = "/";
            URL startURL = new URL( "https://jcenter.bintray.com/com/company/" );
            root = new File( "jcenter" );
            load( startURL );
        }
    
        /**
         * Load the URL iterative
         * 
         * @param parent the URL to load
         * @throws IOException if any error occur
         */
        static void load( @Nonnull URL parent ) throws IOException {
            System.err.println( parent );
            InputStream input = IOFunctions.openStreamSupportingRedirect( parent, 5000 );
            String content = IOFunctions.readString( input, StandardCharsets.UTF_8 );
            List<URL> urls = extractUrls( parent, content );
            for( URL url : urls ) {
                String path = url.getPath();
                if( path.endsWith( "/" ) ) {
                    load( url );
                } else {
                    input = IOFunctions.openStreamSupportingRedirect( url, 5000 );
                    byte[] bytes = IOFunctions.readBytes( input );
                    File file = new File( root, path.substring( basePath.length() ) );
                    file.getParentFile().mkdirs();
                    try (FileOutputStream fos = new FileOutputStream( file )) {
                        fos.write( bytes );
                    }
                }
            }
        }
    
        /**
         * Extract the URLs from the content of page
         * 
         * @param parent the parent URL for relative URLs
         * @param content the page content
         * @return list of found URLs
         * @throws IOException if any error occur
         */
        @Nonnull
        static List<URL> extractUrls( URL parent, @Nonnull String content ) throws IOException {
            ArrayList<URL> result = new ArrayList<>();
            int idx = 0;
            while( true ) {
                int idx1 = content.indexOf( "href=\"", idx );
                if( idx1 < 0 ) {
                    break;
                }
                idx1 += 6;
                int idx2 = content.indexOf( "\"", idx1 );
                String urlStr = content.substring( idx1, idx2 );
                if( !urlStr.startsWith( ".." ) ) {
                    result.add( new URL( parent, urlStr ) );
                }
                idx = idx2;
            }
            return result;
        }
    }
    

    Gradle Launcher

    package tool;
    
    import java.io.File;
    import java.io.IOException;
    import java.lang.ProcessBuilder.Redirect;
    import java.util.ArrayList;
    import java.util.HashMap;
    
    import com.inet.error.ErrorCode;
    import com.inet.lib.util.IOFunctions;
    import com.inet.shared.utils.Version;
    
    /**
     * Deploy to the Sonatype server.
     */
    public class DeploySonatype {
    
        private static final String gradle = "C:/Users/...../gradle-6.7.1/bin/gradle.bat";
    
        public static void main( String[] args ) throws IOException {
            File root = new File( "jcenter/com/company" );
            for( File file : root.listFiles() ) {
                if( file.isDirectory() ) {
                    String archivesBaseName = file.getName();
                    deployLibrary( archivesBaseName, file );
                }
            }
        }
    
        static void deployLibrary( String archivesBaseName, File libraryDir ) throws IOException {
            HashMap<Version,File> versionDirs = new HashMap<>();
            for( File file : libraryDir.listFiles() ) {
                if( file.isDirectory() ) {
                    versionDirs.put( new Version( file.getName()), file );
                }
            }
    
            ArrayList<Version> versions = new ArrayList( versionDirs.keySet() );
            versions.sort( null );// old versions first
    
            for( Version version : versions ) {
                File dir = versionDirs.get( version );
                deployVersion( archivesBaseName, version, dir );
            }
        }
    
        static void deployVersion( String archivesBaseName, Version version, File dir ) throws IOException {
            System.err.println( archivesBaseName + " " + version + " " + dir );
    
            File script = IOFunctions.getFile( DeploySonatype.class.getResource( "deploy.gradle" ) );
            IOFunctions.deleteDir( new File( script.getParent(), "build" ) ); // clean from previous run
    
            ArrayList<String> command = new ArrayList<>();
            command.add( gradle );
            command.add( "-b" );
            command.add( "\"" + script.getPath() + "\"" );
            command.add( "--stacktrace" );
            command.add( "-ParchivesBaseName=" + archivesBaseName );
            command.add( "-Pversion=" + version );
            command.add( "-PartifactDir=" + dir.getAbsolutePath() );
    
            command.add( "uploadArchives" );
    
            System.err.println( command );
    
            ProcessBuilder processBuilder = new ProcessBuilder( command );
            processBuilder.redirectOutput( Redirect.INHERIT );
            processBuilder.redirectError( Redirect.INHERIT );
            processBuilder.environment().put( "JAVA_HOME", System.getProperty( "java.home" ) );
            Process start = processBuilder.start();
            try {
                int exitValue = start.waitFor();
                if( exitValue != 0 ) {
                    throw new IOException( "Exit Value: " + exitValue );
                }
            } catch( InterruptedException ex ) {
                ErrorCode.throwAny( ex );
            }
        }
    }
    

    deploy.gradle

    /****************************************
     * Deploy to Maven
     ****************************************/
    apply plugin: 'maven'
    apply plugin: 'signing'
    
    group = 'com.company'
    println archivesBaseName + "/" + version + "  -> " + artifactDir // come from Java as parameter
    
    task copyArtifact(type: Copy) {
        from file( artifactDir )
        into file("$buildDir/artifacts" )
    }
    
    task setupArchives {
        dependsOn copyArtifact
        doLast {
            artifacts {
                fileTree( dir: file( "$buildDir/artifacts" ) ).each {
                    archives file: it
                    println "\t" + it
                }
            }
            signing {
                if (project.hasProperty("signing.keyId") ){
                    sign configurations.archives
                }
            }
        }
    }
    
    uploadArchives {
        dependsOn setupArchives
    
        repositories {
            mavenDeployer {
                beforeDeployment { MavenDeployment deployment ->
                    signing {
                        fileTree( dir: file( "$buildDir/artifacts" ) ).each {
                            sign it
                            println "sign: " + it
                        }
                    }
                }
    
                if (project.hasProperty("ossrhUsername") ){
                    repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
                        authentication(userName: project["ossrhUsername"], password: project["ossrhPassword"])
                    }
                }
             }
        }
    }