如何将 JCenter 工件迁移到 Sonatype Maven 存储库?

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

JCenter Maven 存储库将在一个月后关闭。

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

如何在删除所有工件之前将其迁移到 Sonatype。

我已经通过多个步骤解决了这个问题:

  • 首先,我编写了一个 Java 程序来从我的 JCenter 帐户下载所有工件。
  • 然后我用 Maven 插件写了一个 Gradle 脚本。此脚本使用 archiveBaseName、版本和工件目录作为参数。脚本和 maven 插件添加了所有缺失的东西,比如 PGP 签名。
  • 然后我编写了一个 Java 程序来遍历下载的工件(或其中的一部分)并使用参数 archiveBaseName、版本和包含工件的目录调用 Gradle 脚本一个循环。
  • 在 Sonatype GUI 上,我部署了上传的文件。一次多个版本。

我使用的以下代码片段无法开箱即用,因为它使用了内部私有 API。但它可以作为起点。当然你需要一个sonatype账号和一个gpg ring文件。秘密属性保存在 gradle.properties 中。您还需要自定义公司在所有 3 个文件中使用的组名称。

下载器

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 启动器

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"])
                }
            }
         }
    }
}