buildtools/src/main/java/org/spigotmc/builder/Builder.java
2024-03-10 09:17:15 +11:00

1308 lines
51 KiB
Java

package org.spigotmc.builder;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import com.google.common.collect.ObjectArrays;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;
import com.google.common.io.ByteStreams;
import com.google.common.io.CharStreams;
import com.google.common.io.Files;
import com.google.common.io.Resources;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import difflib.DiffUtils;
import difflib.Patch;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.lang.management.ManagementFactory;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystem;
import java.nio.file.FileSystemException;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.EnumSet;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import joptsimple.OptionSet;
import lombok.RequiredArgsConstructor;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.output.TeeOutputStream;
import org.apache.commons.lang3.time.DurationFormatUtils;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.ResetCommand;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.lib.GpgConfig;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.transport.FetchResult;
import org.eclipse.jgit.transport.RefSpec;
import org.spigotmc.mapper.MapUtil;
import org.spigotmc.utils.Constants;
import org.spigotmc.utils.Flags;
public class Builder
{
public static File CWD = new File( "." );
public static final String LOG_FILE = Constants.LOG_FILE;
private static final boolean IS_WINDOWS = System.getProperty( "os.name" ).startsWith( "Windows" );
private static final boolean AUTOCRLF = !"\n".equals( System.getProperty( "line.separator" ) );
private static boolean dontUpdate;
private static List<Compile> compile;
private static boolean generateSource;
private static boolean generateDocs;
private static boolean dev;
private static boolean remapped;
private static List<PullRequest> pullRequests;
private static String applyPatchesShell = "sh";
private static boolean didClone = false;
//
private static BuildInfo buildInfo = BuildInfo.DEV;
//
private static File msysDir;
private static File maven;
public static void startBuilder(String[] args, OptionSet options) throws Exception
{
long start = System.nanoTime();
System.out.println( Arrays.toString( args ) );
// May be null
String buildVersion = Builder.class.getPackage().getImplementationVersion();
int buildNumber = -1;
if ( buildVersion != null )
{
String[] split = buildVersion.split( "-" );
if ( split.length == 4 )
{
try
{
buildNumber = Integer.parseInt( split[3] );
} catch ( NumberFormatException ignored )
{
}
}
}
System.out.println( "Loading BuildTools version: " + buildVersion + " (#" + buildNumber + ")" );
System.out.println( "Java Version: " + JavaVersion.getCurrentVersion() );
System.out.println( "Current Path: " + CWD.getCanonicalPath() );
if ( CWD.getAbsolutePath().contains( "'" ) || CWD.getAbsolutePath().contains( "#" ) || CWD.getAbsolutePath().contains( "~" ) || CWD.getAbsolutePath().contains( "(" ) || CWD.getAbsolutePath().contains( ")" ) )
{
System.err.println( Constants.SPECIAL_CHARACTERS_WARNING );
System.exit( 1 );
}
if ( options.has( Flags.HELP_FLAG ) )
{
Flags.PARSER.printHelpOn( System.out );
System.exit( 0 );
}
if ( options.has( Flags.DISABLE_CERT_FLAG ) )
{
disableHttpsCertificateCheck();
}
dontUpdate = options.has( Flags.DONT_UPDATE_FLAG );
generateSource = options.has( Flags.GENERATE_SOURCE_FLAG );
generateDocs = options.has( Flags.GENERATE_DOCS_FLAG );
dev = options.has( Flags.DEV_FLAG );
// Experimental implies dev but with different refs
if ( options.has( Flags.EXPERIMENTAL_FLAG ) )
{
dev = true;
buildInfo = BuildInfo.EXPERIMENTAL;
}
remapped = options.has( Flags.REMAPPED_FLAG );
compile = options.valuesOf( Flags.TO_COMPILE_FLAG );
pullRequests = options.valuesOf( Flags.BUILD_PULL_REQUEST_FLAG );
validatedPullRequestsOptions();
if ( options.has( Flags.SKIP_COMPILE_FLAG ) )
{
compile = Collections.singletonList( Compile.NONE );
System.err.println( "--skip-compile is deprecated, please use --compile NONE" );
}
if ( ( dev || dontUpdate ) && options.has( Flags.JENKINS_VERSION_FLAG ) )
{
System.err.println( "Using --dev or --dont-update with --rev makes no sense, exiting." );
System.exit( 1 );
}
if ( compile.isEmpty() && !pullRequests.isEmpty() )
{
compile = new ArrayList<>();
if ( getPullRequest( Repository.BUKKIT ) != null || getPullRequest( Repository.CRAFTBUKKIT ) != null )
{
compile.add( Compile.CRAFTBUKKIT );
}
if ( getPullRequest( Repository.SPIGOT ) != null )
{
compile.add( Compile.SPIGOT );
}
}
try
{
runProcess( CWD, "sh", "-c", "exit" );
} catch ( Exception ex )
{
if ( IS_WINDOWS )
{
String gitVersion = "PortableGit-2.30.0-" + ( System.getProperty( "os.arch" ).endsWith( "64" ) ? "64" : "32" ) + "-bit";
// https://github.com/git-for-windows/git/releases/tag/v2.30.0.windows.1
String gitHash = System.getProperty( "os.arch" ).endsWith( "64" ) ? "6497e30fc6141e3c27af6cc3a081861043a7666dd54f395d47184e8eb75f5d61" : "b3768c64b6afa082043659c56acb4c3483df6b6e884fdc7e3c769f7e7e99a3a8";
msysDir = new File( gitVersion, "PortableGit" );
if ( !msysDir.isDirectory() )
{
System.out.println( "*** Could not find PortableGit installation, downloading. ***" );
String gitName = gitVersion + ".7z.exe";
File gitInstall = new File( gitVersion, gitName );
gitInstall.deleteOnExit();
gitInstall.getParentFile().mkdirs();
if ( !gitInstall.exists() )
{
download( "https://github.com/git-for-windows/git/releases/download/v2.30.0.windows.1/" + gitName, gitInstall, HashFormat.SHA256, gitHash );
}
System.out.println( "Extracting downloaded git install" );
// yes to all, silent, don't run. Only -y seems to work
runProcess( gitInstall.getParentFile(), gitInstall.getAbsolutePath(), "-y", "-gm2", "-nr" );
gitInstall.delete();
}
System.out.println( "*** Using downloaded git " + msysDir + " ***" );
System.out.println( "*** Please note that this is a beta feature, so if it does not work please also try a manual install of git from https://git-for-windows.github.io/ ***" );
} else
{
System.out.println( "You must run this jar through bash (msysgit)" );
System.exit( 1 );
}
}
try
{
runProcess( CWD, "git", "--version" );
} catch ( Exception ex )
{
System.out.println( "Could not successfully run git. Please ensure it is installed and functioning. " + ex.getMessage() );
System.exit( 1 );
}
try
{
runProcess( CWD, "java", "-version" );
} catch ( Exception ex )
{
System.out.println( "Could not successfully run Java." + ex.getMessage() );
System.exit( 1 );
}
if ( !dontUpdate && !dev )
{
String askedVersion = options.valueOf( Flags.JENKINS_VERSION_FLAG );
System.out.println( "Attempting to build version: '" + askedVersion + "' use --rev <version> to override" );
String verInfo;
try
{
verInfo = get( "https://hub.spigotmc.org/versions/" + askedVersion + ".json" );
} catch ( IOException ex )
{
System.err.println( "Could not get version " + askedVersion + " does it exist? Try another version or use 'latest'" );
ex.printStackTrace();
System.exit( 1 );
return;
}
System.out.println( "Found version" );
System.out.println( verInfo );
buildInfo = new Gson().fromJson( verInfo, BuildInfo.class );
if ( buildNumber != -1 && buildInfo.getToolsVersion() != -1 && buildNumber < buildInfo.getToolsVersion() )
{
System.err.println( "**** Your BuildTools is out of date and will not build the requested version. Please grab a new copy from https://www.spigotmc.org/go/buildtools-dl" );
System.exit( 1 );
}
if ( !options.has( Flags.DISABLE_JAVA_CHECK_FLAG ) )
{
if ( buildInfo.getJavaVersions() == null )
{
buildInfo.setJavaVersions( new int[]
{
JavaVersion.JAVA_7.getVersion(), JavaVersion.JAVA_8.getVersion()
} );
}
Preconditions.checkArgument( buildInfo.getJavaVersions().length == 2, "Expected only two Java versions, got %s", JavaVersion.printVersions( buildInfo.getJavaVersions() ) );
JavaVersion curVersion = JavaVersion.getCurrentVersion();
JavaVersion minVersion = JavaVersion.getByVersion( buildInfo.getJavaVersions()[0] );
JavaVersion maxVersion = JavaVersion.getByVersion( buildInfo.getJavaVersions()[1] );
if ( curVersion.getVersion() < minVersion.getVersion() || curVersion.getVersion() > maxVersion.getVersion() )
{
System.err.println( "*** The version you have requested to build requires Java versions between " + JavaVersion.printVersions( buildInfo.getJavaVersions() ) + ", but you are using " + curVersion );
System.err.println( "*** Please rerun BuildTools using an appropriate Java version. For obvious reasons outdated MC versions do not support Java versions that did not exist at their release." );
System.exit( 1 );
}
}
}
File workDir = new File( "work" );
workDir.mkdir();
File bukkit = new File( "Bukkit" );
if ( !bukkit.exists() || !containsGit( bukkit ) )
{
clone( "https://hub.spigotmc.org/stash/scm/spigot/bukkit.git", bukkit );
}
File craftBukkit = new File( "CraftBukkit" );
if ( !craftBukkit.exists() || !containsGit( craftBukkit ) )
{
clone( "https://hub.spigotmc.org/stash/scm/spigot/craftbukkit.git", craftBukkit );
}
File spigot = new File( "Spigot" );
if ( !spigot.exists() || !containsGit( spigot ) )
{
clone( "https://hub.spigotmc.org/stash/scm/spigot/spigot.git", spigot );
}
File buildData = new File( "BuildData" );
if ( !buildData.exists() || !containsGit( buildData ) )
{
clone( "https://hub.spigotmc.org/stash/scm/spigot/builddata.git", buildData );
}
String m2Home = System.getenv( "M2_HOME" );
if ( m2Home == null || !( maven = new File( m2Home ) ).exists() )
{
maven = new File( Constants.MAVEN_FOLDER );
if ( !maven.exists() )
{
System.out.println( "Maven does not exist, downloading. Please wait." );
File mvnTemp = new File( Constants.MAVEN_FILE );
mvnTemp.deleteOnExit();
download( Constants.MAVEN_DOWNLOAD, mvnTemp, HashFormat.SHA512, Constants.MAVEN_HASH );
unzip( mvnTemp, new File( "." ) );
mvnTemp.delete();
}
}
Git bukkitGit = Git.open( bukkit );
Git craftBukkitGit = Git.open( craftBukkit );
Git spigotGit = Git.open( spigot );
Git buildGit = Git.open( buildData );
if ( !dontUpdate )
{
boolean buildDataChanged = pull( buildGit, buildInfo.getRefs().getBuildData(), null );
boolean bukkitChanged = pull( bukkitGit, buildInfo.getRefs().getBukkit(), getPullRequest( Repository.BUKKIT ) );
boolean craftBukkitChanged = pull( craftBukkitGit, buildInfo.getRefs().getCraftBukkit(), getPullRequest( Repository.CRAFTBUKKIT ) );
boolean spigotChanged = pull( spigotGit, buildInfo.getRefs().getSpigot(), getPullRequest( Repository.SPIGOT ) );
// Checks if any of the 4 repositories have been updated via a fetch, the --compile-if-changed flag is set and none of the repositories were cloned in this run.
if ( !buildDataChanged && !bukkitChanged && !craftBukkitChanged && !spigotChanged && options.has( Flags.COMPILE_IF_CHANGED_FLAG ) && !didClone )
{
System.out.println( "*** No changes detected in any of the repositories!" );
System.out.println( "*** Exiting due to the --compile-if-changed" );
System.exit( 2 );
}
}
VersionInfo versionInfo = new Gson().fromJson(
Files.asCharSource( new File( "BuildData/info.json" ), StandardCharsets.UTF_8 ).read(),
VersionInfo.class
);
// Default to 1.8 builds.
if ( versionInfo == null )
{
versionInfo = new VersionInfo( "1.8", "bukkit-1.8.at", "bukkit-1.8-cl.csrg", "bukkit-1.8-members.csrg", "package.srg", null );
}
System.out.println( "Attempting to build Minecraft with details: " + versionInfo );
if ( buildNumber != -1 && versionInfo.getToolsVersion() != -1 && buildNumber < versionInfo.getToolsVersion() )
{
System.err.println( "" );
System.err.println( "**** Your BuildTools is out of date and will not build the requested version. Please grab a new copy from https://www.spigotmc.org/go/buildtools-dl" );
System.exit( 1 );
}
File vanillaJar = new File( workDir, "minecraft_server." + versionInfo.getMinecraftVersion() + ".jar" );
File embeddedVanillaJar = new File( workDir, "server-" + versionInfo.getMinecraftVersion() + ".jar" );
if ( !checkHash( vanillaJar, versionInfo ) )
{
if ( versionInfo.getServerUrl() != null )
{
download( versionInfo.getServerUrl(), vanillaJar, HashFormat.MD5, versionInfo.getMinecraftHash() );
} else
{
download( getServerVanillaUrl( versionInfo.getMinecraftVersion() ), vanillaJar, HashFormat.MD5, versionInfo.getMinecraftHash() );
}
}
try ( JarFile jar = new JarFile( vanillaJar ) )
{
ZipEntry entry = jar.getEntry( "META-INF/versions/" + versionInfo.getMinecraftVersion() + "/server-" + versionInfo.getMinecraftVersion() + ".jar" );
if ( entry != null )
{
if ( !checkHash( embeddedVanillaJar, HashFormat.SHA256, versionInfo.getMinecraftHash() ) )
{
try ( InputStream is = jar.getInputStream( entry ) )
{
byte[] embedded = ByteStreams.toByteArray( is );
if ( embedded != null )
{
Files.write( embedded, embeddedVanillaJar );
}
}
try ( FileSystem zipfs = FileSystems.newFileSystem( embeddedVanillaJar.toPath(), (ClassLoader) null ) )
{
java.nio.file.Files.delete( zipfs.getPath( "/META-INF/MOJANGCS.RSA" ) );
java.nio.file.Files.delete( zipfs.getPath( "/META-INF/MOJANGCS.SF" ) );
}
}
vanillaJar = embeddedVanillaJar;
}
}
if ( versionInfo.getServerUrl() == null )
{
// Legacy versions can also specify a specific shell to build with which has to be bash-compatible
applyPatchesShell = System.getenv().get( "SHELL" );
if ( applyPatchesShell == null || applyPatchesShell.trim().isEmpty() )
{
applyPatchesShell = "bash";
}
}
Iterable<RevCommit> mappings = buildGit.log()
.addPath( "mappings/" )
.setMaxCount( 1 ).call();
Hasher mappingsHash = HashFormat.MD5.getHash().newHasher();
for ( RevCommit rev : mappings )
{
mappingsHash.putString( rev.getName(), StandardCharsets.UTF_8 );
}
String mappingsVersion = mappingsHash.hash().toString().substring( 24 ); // Last 8 chars
File finalMappedJar = new File( workDir, "mapped." + mappingsVersion + ".jar" );
if ( !finalMappedJar.exists() )
{
System.out.println( "Final mapped jar: " + finalMappedJar + " does not exist, creating (please wait)!" );
File classMappings = new File( "BuildData/mappings/" + versionInfo.getClassMappings() );
File memberMappings = new File( "BuildData/mappings/" + versionInfo.getMemberMappings() );
File fieldMappings = new File( workDir, "bukkit-" + mappingsVersion + "-fields.csrg" );
if ( versionInfo.getMappingsUrl() != null )
{
File mojangMappings = new File( workDir, "minecraft_server." + versionInfo.getMinecraftVersion() + ".txt" );
if ( !mojangMappings.exists() )
{
download( versionInfo.getMappingsUrl(), mojangMappings );
}
MapUtil mapUtil = new MapUtil();
mapUtil.loadBuk( classMappings );
if ( !memberMappings.exists() )
{
memberMappings = new File( workDir, "bukkit-" + mappingsVersion + "-members.csrg" );
mapUtil.makeFieldMaps( mojangMappings, memberMappings, true );
} else if ( !fieldMappings.exists() )
{
mapUtil.makeFieldMaps( mojangMappings, fieldMappings, false );
}
// 1.17+
if ( memberMappings.exists() )
{
runMavenInstall( CWD, "install:install-file", "-Dfile=" + memberMappings, "-Dpackaging=csrg", "-DgroupId=org.spigotmc",
"-DartifactId=minecraft-server", "-Dversion=" + versionInfo.getSpigotVersion(), "-Dclassifier=maps-spigot-members", "-DgeneratePom=false" );
}
// 1.17
if ( fieldMappings.exists() )
{
runMavenInstall( CWD, "install:install-file", "-Dfile=" + fieldMappings, "-Dpackaging=csrg", "-DgroupId=org.spigotmc",
"-DartifactId=minecraft-server", "-Dversion=" + versionInfo.getSpigotVersion(), "-Dclassifier=maps-spigot-fields", "-DgeneratePom=false" );
File combinedMappings = new File( workDir, "bukkit-" + mappingsVersion + "-combined.csrg" );
if ( !combinedMappings.exists() )
{
mapUtil.makeCombinedMaps( combinedMappings, memberMappings );
}
runMavenInstall( CWD, "install:install-file", "-Dfile=" + combinedMappings, "-Dpackaging=csrg", "-DgroupId=org.spigotmc",
"-DartifactId=minecraft-server", "-Dversion=" + versionInfo.getSpigotVersion(), "-Dclassifier=maps-spigot", "-DgeneratePom=false" );
} else
{
// 1.18+
runMavenInstall( CWD, "install:install-file", "-Dfile=" + classMappings, "-Dpackaging=csrg", "-DgroupId=org.spigotmc",
"-DartifactId=minecraft-server", "-Dversion=" + versionInfo.getSpigotVersion(), "-Dclassifier=maps-spigot", "-DgeneratePom=false" );
}
// 1.17+
runMavenInstall( CWD, "install:install-file", "-Dfile=" + mojangMappings, "-Dpackaging=txt", "-DgroupId=org.spigotmc",
"-DartifactId=minecraft-server", "-Dversion=" + versionInfo.getSpigotVersion(), "-Dclassifier=maps-mojang", "-DgeneratePom=false" );
}
File clMappedJar = new File( finalMappedJar + "-cl" );
File mMappedJar = new File( finalMappedJar + "-m" );
if ( versionInfo.getClassMapCommand() == null )
{
versionInfo.setClassMapCommand( "java -jar BuildData/bin/SpecialSource-2.jar map -i {0} -m {1} -o {2}" );
}
runProcess( CWD, MessageFormat.format( versionInfo.getClassMapCommand(), vanillaJar.getPath(), classMappings.getPath(), clMappedJar.getPath() ).split( " " ) );
if ( versionInfo.getMemberMapCommand() == null )
{
versionInfo.setMemberMapCommand( "java -jar BuildData/bin/SpecialSource-2.jar map -i {0} -m {1} -o {2}" );
}
runProcess( CWD, MessageFormat.format( versionInfo.getMemberMapCommand(), clMappedJar.getPath(),
memberMappings.getPath(), mMappedJar.getPath() ).split( " " ) );
if ( versionInfo.getFinalMapCommand() == null )
{
versionInfo.setFinalMapCommand( "java -jar BuildData/bin/SpecialSource.jar --kill-lvt -i {0} --access-transformer {1} -m {2} -o {3}" );
}
runProcess( CWD, MessageFormat.format( versionInfo.getFinalMapCommand(), mMappedJar.getPath(), "BuildData/mappings/" + versionInfo.getAccessTransforms(),
( versionInfo.getPackageMappings() == null ) ? fieldMappings.getPath() : "BuildData/mappings/" + versionInfo.getPackageMappings(), finalMappedJar.getPath() ).split( " " ) );
}
runMavenInstall( CWD, "install:install-file", "-Dfile=" + finalMappedJar, "-Dpackaging=jar", "-DgroupId=org.spigotmc",
"-DartifactId=minecraft-server", "-Dversion=" + ( versionInfo.getSpigotVersion() != null ? versionInfo.getSpigotVersion() : versionInfo.getMinecraftVersion() + "-SNAPSHOT" ) );
File decompileDir = new File( workDir, "decompile-" + mappingsVersion );
if ( !decompileDir.exists() )
{
decompileDir.mkdir();
File clazzDir = new File( decompileDir, "classes" );
unzip( finalMappedJar, clazzDir, (input) -> input.startsWith( "net/minecraft" ) );
if ( versionInfo.getDecompileCommand() == null )
{
versionInfo.setDecompileCommand( "java -jar BuildData/bin/fernflower.jar -dgs=1 -hdc=0 -rbr=0 -asc=1 -udv=0 {0} {1}" );
}
runProcess( CWD, MessageFormat.format( versionInfo.getDecompileCommand(), clazzDir.getPath(), decompileDir.getPath() ).split( " " ) );
}
try
{
File latestLink = new File( workDir, "decompile-latest" );
latestLink.delete();
java.nio.file.Files.createSymbolicLink( latestLink.toPath(), decompileDir.getParentFile().toPath().relativize( decompileDir.toPath() ) );
} catch ( UnsupportedOperationException ex )
{
// Ignore if not possible
} catch ( FileSystemException ex )
{
// Not running as admin on Windows
} catch ( IOException ex )
{
System.out.println( "Did not create decompile-latest link " + ex.getMessage() );
}
System.out.println( "Applying CraftBukkit Patches" );
File nmsDir = new File( craftBukkit, "src/main/java/net" );
if ( nmsDir.exists() )
{
System.out.println( "Backing up NMS dir" );
FileUtils.moveDirectory( nmsDir, new File( workDir, "nms.old." + System.currentTimeMillis() ) );
}
Path patchDir = new File( craftBukkit, "nms-patches" ).toPath();
java.nio.file.Files.walk( patchDir ).filter( java.nio.file.Files::isRegularFile ).forEach( (path) ->
{
File file = path.toFile();
if ( !file.getName().endsWith( ".patch" ) )
{
return;
}
String relativeName = patchDir.relativize( path ).toString().replace( ".patch", ".java" );
String targetFile = ( relativeName.contains( File.separator ) ) ? relativeName : "net/minecraft/server/" + relativeName;
File clean = new File( decompileDir, targetFile );
File t = new File( nmsDir.getParentFile(), targetFile );
t.getParentFile().mkdirs();
System.out.println( "Patching " + relativeName );
try
{
List<String> readFile = Files.readLines( file, StandardCharsets.UTF_8 );
// Manually append prelude if it is not found in the first few lines.
boolean preludeFound = false;
for ( int i = 0; i < Math.min( 3, readFile.size() ); i++ )
{
if ( readFile.get( i ).startsWith( "+++" ) )
{
preludeFound = true;
break;
}
}
if ( !preludeFound )
{
readFile.add( 0, "+++" );
}
Patch parsedPatch = DiffUtils.parseUnifiedDiff( readFile );
List<?> modifiedLines = DiffUtils.patch( Files.readLines( clean, StandardCharsets.UTF_8 ), parsedPatch );
try ( BufferedWriter bw = new BufferedWriter( new FileWriter( t ) ) )
{
for ( Object line : modifiedLines )
{
bw.write( (String) line );
bw.newLine();
}
}
} catch ( Exception ex )
{
throw new RuntimeException( "Error patching " + relativeName, ex );
}
} );
File tmpNms = new File( craftBukkit, "tmp-nms" );
FileUtils.copyDirectory( nmsDir, tmpNms );
craftBukkitGit.branchDelete().setBranchNames( "patched" ).setForce( true ).call();
craftBukkitGit.checkout().setCreateBranch( true ).setForceRefUpdate( true ).setName( "patched" ).call();
craftBukkitGit.add().addFilepattern( "src/main/java/net/" ).call();
craftBukkitGit.commit().setGpgConfig( new GpgConfig( null, null, null ) ).setSign( false ).setMessage( "CraftBukkit $ " + new Date() ).call();
PullRequest craftBukkitPullRequest = getPullRequest( Repository.CRAFTBUKKIT );
craftBukkitGit.checkout().setName( ( craftBukkitPullRequest == null ) ? buildInfo.getRefs().getCraftBukkit() : "origin/pr/" + craftBukkitPullRequest.getId() ).call();
FileUtils.moveDirectory( tmpNms, nmsDir );
if ( versionInfo.getToolsVersion() < 93 )
{
File spigotApi = new File( spigot, "Bukkit" );
if ( !spigotApi.exists() )
{
clone( "file://" + bukkit.getAbsolutePath(), spigotApi );
}
File spigotServer = new File( spigot, "CraftBukkit" );
if ( !spigotServer.exists() )
{
clone( "file://" + craftBukkit.getAbsolutePath(), spigotServer );
}
}
// Git spigotApiGit = Git.open( spigotApi );
// Git spigotServerGit = Git.open( spigotServer );
if ( compile == null || compile.isEmpty() )
{
if ( dev )
{
compile = Arrays.asList( Compile.CRAFTBUKKIT, Compile.SPIGOT );
} else
{
compile = Collections.singletonList( Compile.SPIGOT );
}
}
if ( compile.contains( Compile.CRAFTBUKKIT ) )
{
System.out.println( "Compiling Bukkit" );
runMavenAPI( bukkit, "clean", "install" );
if ( generateDocs )
{
runMavenAPI( bukkit, "javadoc:jar" );
}
if ( generateSource )
{
runMavenAPI( bukkit, "source:jar" );
}
System.out.println( "Compiling CraftBukkit" );
runMavenServer( craftBukkit, "clean", "install" );
}
try
{
if ( compile.contains( Compile.SPIGOT ) )
{
runProcess( spigot, applyPatchesShell, "applyPatches.sh" );
System.out.println( "*** Spigot patches applied!" );
System.out.println( "Compiling Spigot & Spigot-API" );
runMavenServer( spigot, "clean", "install" );
File spigotApi = new File( spigot, "Spigot-API" );
if ( generateDocs )
{
runMavenAPI( spigotApi, "javadoc:jar" );
}
if ( generateSource )
{
runMavenAPI( spigotApi, "source:jar" );
}
}
} catch ( Exception ex )
{
System.err.println( "Error compiling Spigot. Please check the wiki for FAQs." );
System.err.println( "If this does not resolve your issue then please pastebin the entire " + LOG_FILE + " file when seeking support." );
ex.printStackTrace();
System.exit( 1 );
}
for ( int i = 0; i < 35; i++ )
{
System.out.println( " " );
}
long end = System.nanoTime();
long duration = TimeUnit.NANOSECONDS.toMillis( end - start );
if ( duration / 1000F >= 60 )
{
System.out.println( "Total Time: " + DurationFormatUtils.formatDurationWords( duration, true, true ) );
} else
{
System.out.println( "Total Time: " + DurationFormatUtils.formatDuration( duration, "s.SS' seconds'" ) );
}
System.out.println();
System.out.println( "Success! Everything completed successfully. Copying final .jar files now." );
String fileExtension = ".jar";
String snapshot = "-SNAPSHOT";
String experimental = "-experimental";
String base = ( versionInfo.getSpigotVersion() != null ) ? "-" + versionInfo.getSpigotVersion() : "";
String bootstrap = ( versionInfo.getToolsVersion() >= 138 ) ? "-bootstrap" : "";
String suffix = base + bootstrap + fileExtension;
String finalName = "spigot-" + versionInfo.getMinecraftVersion() + fileExtension;
if ( options.has( Flags.EXPERIMENTAL_FLAG ) )
{
suffix = versionInfo.getMinecraftVersion() + experimental + snapshot + bootstrap + fileExtension;
finalName = "spigot-" + versionInfo.getMinecraftVersion() + experimental + fileExtension;
}
if ( Flags.OUTPUT_NAME_FLAG.value( options ) != null )
{
finalName = Flags.OUTPUT_NAME_FLAG.value( options );
}
if ( compile.contains( Compile.CRAFTBUKKIT ) )
{
copyJar( "CraftBukkit/target", "craftbukkit", suffix, new File( Flags.OUTPUT_DIR_FLAG.value( options ), "craftbukkit-" + versionInfo.getMinecraftVersion() + ".jar" ) );
}
if ( compile.contains( Compile.SPIGOT ) )
{
copyJar( "Spigot/Spigot-Server/target", "spigot", suffix, new File( Flags.OUTPUT_DIR_FLAG.value( options ), finalName ) );
}
System.exit( 0 );
}
private static boolean checkHash(File vanillaJar, VersionInfo versionInfo) throws IOException
{
if ( versionInfo.getShaServerHash() != null )
{
return checkHash( vanillaJar, HashFormat.SHA1, versionInfo.getShaServerHash() );
} else if ( versionInfo.getMinecraftHash() != null )
{
return checkHash( vanillaJar, HashFormat.MD5, versionInfo.getMinecraftHash() );
} else
{
return vanillaJar.isFile();
}
}
private static boolean checkHash(File vanillaJar, HashFormat hashFormat, String goodHash) throws IOException
{
if ( !vanillaJar.isFile() )
{
return false;
}
if ( dev )
{
return true;
}
String hash = Files.asByteSource( vanillaJar ).hash( hashFormat.getHash() ).toString();
boolean result = hash.equals( goodHash );
if ( !result )
{
System.err.println( "**** Warning, Minecraft jar hash of " + hash + " does not match stored hash of " + goodHash );
return false;
} else
{
System.out.println( "Found good Minecraft hash (" + hash + ")" );
return true;
}
}
public static String get(String url) throws IOException
{
URLConnection con = new URL( url ).openConnection();
con.setConnectTimeout( Constants.GENERAL_TIMEOUT_MS );
con.setReadTimeout( Constants.GENERAL_TIMEOUT_MS );
try ( InputStreamReader r = new InputStreamReader( con.getInputStream() ) )
{
return CharStreams.toString( r );
}
}
public static void copyJar(String path, final String jarPrefix, final String jarSuffix, File outJar) throws Exception
{
File[] files = new File( path ).listFiles( (dir, name) -> name.startsWith( jarPrefix ) && name.endsWith( jarSuffix ) );
if ( !outJar.getParentFile().isDirectory() )
{
outJar.getParentFile().mkdirs();
}
for ( File file : files )
{
System.out.println( "Copying " + file.getName() + " to " + outJar.getCanonicalPath() );
Files.copy( file, outJar );
System.out.println( " - Saved as " + outJar );
}
}
public static boolean pull(Git repo, String ref, PullRequest pullRequest) throws Exception
{
System.out.println( "Pulling updates for " + repo.getRepository().getDirectory() );
try
{
repo.reset().setRef( "origin/master" ).setMode( ResetCommand.ResetType.HARD ).call();
} catch ( JGitInternalException ex )
{
System.err.println( "*** Warning, could not find origin/master ref, but continuing anyway." );
System.err.println( "*** If further errors occur please delete " + repo.getRepository().getDirectory().getParent() + " and retry." );
}
FetchResult result;
if ( pullRequest != null )
{
result = repo.fetch().setRefSpecs( new RefSpec( "+refs/pull-requests/" + pullRequest.getId() + "/from:refs/remotes/origin/pr/" + pullRequest.getId() ) ).call();
} else
{
result = repo.fetch().call();
}
System.out.println( "Successfully fetched updates!" );
if ( pullRequest != null )
{
repo.checkout().setName( "origin/pr/" + pullRequest.getId() ).setForced( true ).call();
} else
{
repo.reset().setRef( ref ).setMode( ResetCommand.ResetType.HARD ).call();
}
if ( ref.equals( "master" ) )
{
repo.reset().setRef( "origin/master" ).setMode( ResetCommand.ResetType.HARD ).call();
}
System.out.println( "Checked out: " + ref );
// Return true if fetch changed any tracking refs.
return !result.getTrackingRefUpdates().isEmpty();
}
public static PullRequest getPullRequest(Repository repository)
{
for ( PullRequest request : pullRequests )
{
if ( request.getRepository() == repository )
{
return request;
}
}
return null;
}
public static void validatedPullRequestsOptions()
{
Set<Repository> repositories = EnumSet.noneOf( Repository.class );
for ( PullRequest pullRequest : pullRequests )
{
if ( !repositories.add( pullRequest.getRepository() ) )
{
throw new RuntimeException( "Pull request option for repository " + pullRequest.getRepository() + " is present multiple times. Only one per repository can be specified." );
}
}
}
private static int runMavenInstall(File workDir, String... command) throws Exception
{
return runMaven0( workDir, false, false, command );
}
private static int runMavenAPI(File workDir, String... command) throws Exception
{
return runMaven0( workDir, dev, false, command );
}
private static int runMavenServer(File workDir, String... command) throws Exception
{
return runMaven0( workDir, dev, remapped, command );
}
private static int runMaven0(File workDir, boolean dev, boolean remapped, String... command) throws Exception
{
List<String> args = new LinkedList<>();
if ( IS_WINDOWS )
{
args.add( maven.getAbsolutePath() + "/bin/mvn.cmd" );
} else
{
args.add( "sh" );
args.add( maven.getAbsolutePath() + "/bin/mvn" );
}
args.add( "-Dbt.name=" + buildInfo.getName() );
if ( dev )
{
args.add( "-P" );
args.add( "development" );
}
if ( remapped )
{
args.add( "-P" );
args.add( "remapped" );
}
args.addAll( Arrays.asList( command ) );
return runProcess( workDir, args.toArray( new String[ args.size() ] ) );
}
public static int runProcess(File workDir, String... command) throws Exception
{
if ( command[0].equals( "java" ) )
{
command[0] = System.getProperty( "java.home" ) + "/bin/" + command[0];
}
if ( msysDir != null )
{
if ( "bash".equals( command[0] ) )
{
command[0] = "git-bash";
}
// BUILDTOOLS-594, etc: Many broken systems lack cmd.exe in PATH for unknown reasons (user error?)
String cmd = System.getenv( "ComSpec" );
if ( cmd == null )
{
// Hopefully nothing messes up both PATH and ComSpec (what a broken system)
cmd = "cmd.exe";
}
String[] shim = new String[]
{
cmd, "/D", "/C"
};
command = ObjectArrays.concat( shim, command, String.class );
}
return runProcess0( workDir, command );
}
private static int runProcess0(File workDir, String... command) throws Exception
{
Preconditions.checkArgument( workDir != null, "workDir" );
Preconditions.checkArgument( command != null && command.length > 0, "Invalid command" );
ProcessBuilder pb = new ProcessBuilder( command );
pb.directory( workDir );
pb.environment().put( "JAVA_HOME", System.getProperty( "java.home" ) );
pb.environment().put( "GIT_COMMITTER_NAME", "BuildTools" );
pb.environment().put( "GIT_COMMITTER_EMAIL", "unconfigured@null.spigotmc.org" );
pb.environment().remove( "M2_HOME" ); // Just let maven figure this out from where it is invoked
if ( !pb.environment().containsKey( "MAVEN_OPTS" ) )
{
pb.environment().put( "MAVEN_OPTS", "-Xmx1024M" );
}
if ( !pb.environment().containsKey( "_JAVA_OPTIONS" ) )
{
String javaOptions = null;
for ( String arg : ManagementFactory.getRuntimeMXBean().getInputArguments() )
{
if ( arg.startsWith( "-Xmx" ) )
{
javaOptions = arg;
break;
}
}
if ( javaOptions != null )
{
pb.environment().put( "_JAVA_OPTIONS", javaOptions );
}
}
if ( IS_WINDOWS )
{
String pathEnv = null;
for ( String key : pb.environment().keySet() )
{
if ( key.equalsIgnoreCase( "path" ) )
{
pathEnv = key;
}
}
if ( pathEnv == null )
{
throw new IllegalStateException( "Could not find path variable!" );
}
if ( msysDir != null )
{
String path = msysDir.getAbsolutePath() + ";" + new File( msysDir, "bin" ).getAbsolutePath() + ";" + pb.environment().get( pathEnv );
pb.environment().put( pathEnv, path );
}
String path = pb.environment().get( pathEnv );
// Not strictly correct, but least likely to be a false positive
if ( !path.contains( "C:\\WINDOWS\\system32;" ) )
{
path = System.getenv( "SystemRoot" ) + "\\system32;" + path;
pb.environment().put( pathEnv, path );
}
}
final Process ps = pb.start();
new Thread( new StreamRedirector( ps.getInputStream(), System.out ), "System.out redirector" ).start();
new Thread( new StreamRedirector( ps.getErrorStream(), System.err ), "System.err redirector" ).start();
int status = ps.waitFor();
if ( status != 0 )
{
throw new RuntimeException( "Error running command, return status !=0: " + Arrays.toString( command ) );
}
return status;
}
@RequiredArgsConstructor
private static class StreamRedirector implements Runnable
{
private final InputStream in;
private final PrintStream out;
@Override
public void run()
{
try ( BufferedReader br = new BufferedReader( new InputStreamReader( in ) ) )
{
String line;
while ( ( line = br.readLine() ) != null )
{
out.println( line );
}
} catch ( IOException ex )
{
throw new RuntimeException( ex );
}
}
}
public static void unzip(File zipFile, File targetFolder) throws IOException
{
unzip( zipFile, targetFolder, null );
}
public static void unzip(File zipFile, File targetFolder, Predicate<String> filter) throws IOException
{
targetFolder.mkdir();
try ( ZipFile zip = new ZipFile( zipFile ) )
{
for ( Enumeration<? extends ZipEntry> entries = zip.entries(); entries.hasMoreElements(); )
{
ZipEntry entry = entries.nextElement();
if ( filter != null )
{
if ( !filter.test( entry.getName() ) )
{
continue;
}
}
File outFile = new File( targetFolder, entry.getName() );
if ( entry.isDirectory() )
{
outFile.mkdirs();
continue;
}
if ( outFile.getParentFile() != null )
{
outFile.getParentFile().mkdirs();
}
try ( InputStream is = zip.getInputStream( entry ); OutputStream os = new FileOutputStream( outFile ); )
{
ByteStreams.copy( is, os );
}
System.out.println( "Extracted: " + outFile );
}
}
}
public static void clone(String url, File target) throws GitAPIException, IOException
{
System.out.println( "Starting clone of " + url + " to " + target );
try ( Git result = Git.cloneRepository().setURI( url ).setDirectory( target ).call() )
{
StoredConfig config = result.getRepository().getConfig();
config.setBoolean( "core", null, "autocrlf", AUTOCRLF );
config.save();
didClone = true;
System.out.println( "Cloned git repository " + url + " to " + target.getAbsolutePath() + ". Current HEAD: " + commitHash( result ) );
}
}
public static String commitHash(Git repo) throws GitAPIException
{
return Iterables.getOnlyElement( repo.log().setMaxCount( 1 ).call() ).getName();
}
public static File download(String url, File target) throws IOException
{
return download( url, target, HashFormat.SHA1, "!" );
}
public static File download(String url, File target, HashFormat hashFormat, String goodHash) throws IOException
{
String shaHash = VersionInfo.hashFromUrl( url );
if ( shaHash != null )
{
hashFormat = HashFormat.SHA1;
goodHash = shaHash;
}
System.out.println( "Starting download of " + url );
byte[] bytes = Resources.toByteArray( new URL( url ) );
String hash = hashFormat.getHash().hashBytes( bytes ).toString();
System.out.println( "Downloaded file: " + target + " with hash: " + hash );
if ( !dev && goodHash != null && !goodHash.equals( hash ) )
{
throw new IllegalStateException( "Downloaded file: " + target + " did not match expected hash: " + goodHash );
}
Files.write( bytes, target );
return target;
}
public static String getServerVanillaUrl(String version) throws Exception
{
Gson gson = new Gson();
String responseManifest = get( "https://launchermeta.mojang.com/mc/game/version_manifest.json" );
JsonObject manifest = gson.fromJson( responseManifest, JsonObject.class );
JsonArray manifestVersions = manifest.getAsJsonArray( "versions" );
for ( JsonElement manifestVersionElement : manifestVersions )
{
if ( manifestVersionElement.isJsonObject() )
{
JsonObject manifestVersion = manifestVersionElement.getAsJsonObject();
if ( manifestVersion.get( "id" ).getAsString().equals( version ) )
{
String urlVersionData = manifestVersion.get( "url" ).getAsString();
String responseVersionData = get( urlVersionData );
JsonObject versionData = gson.fromJson( responseVersionData, JsonObject.class );
return versionData.getAsJsonObject( "downloads" ).getAsJsonObject( "server" ).get( "url" ).getAsString();
}
}
}
throw new RuntimeException( "Error cannot get the URL for legacy server version " + version );
}
public static void disableHttpsCertificateCheck()
{
// This globally disables certificate checking
// http://stackoverflow.com/questions/19723415/java-overriding-function-to-disable-ssl-certificate-check
try
{
TrustManager[] trustAllCerts = new TrustManager[]
{
new X509TrustManager()
{
@Override
public X509Certificate[] getAcceptedIssuers()
{
return null;
}
@Override
public void checkClientTrusted(X509Certificate[] certs, String authType)
{
}
@Override
public void checkServerTrusted(X509Certificate[] certs, String authType)
{
}
}
};
// Trust SSL certs
SSLContext sc = SSLContext.getInstance( "SSL" );
sc.init( null, trustAllCerts, new SecureRandom() );
HttpsURLConnection.setDefaultSSLSocketFactory( sc.getSocketFactory() );
// Trust host names
HttpsURLConnection.setDefaultHostnameVerifier( (hostname, session) -> true );
} catch ( NoSuchAlgorithmException | KeyManagementException ex )
{
System.out.println( "Failed to disable https certificate check" );
ex.printStackTrace( System.err );
}
}
public static void logOutput(OutputStream defaultOut, OutputStream defaultError)
{
try
{
final OutputStream logOut = new BufferedOutputStream( new FileOutputStream( new File( CWD, LOG_FILE ) ) );
Runtime.getRuntime().addShutdownHook( new Thread()
{
@Override
public void run()
{
System.setOut( new PrintStream( new FileOutputStream( FileDescriptor.out ) ) );
System.setErr( new PrintStream( new FileOutputStream( FileDescriptor.err ) ) );
try
{
logOut.close();
} catch ( IOException ex )
{
// We're shutting the jvm down anyway.
}
}
} );
System.setOut( new PrintStream( new TeeOutputStream( defaultOut, logOut ) ) );
System.setErr( new PrintStream( new TeeOutputStream( defaultError, logOut ) ) );
} catch ( FileNotFoundException ex )
{
System.err.println( "Failed to create log file: " + LOG_FILE );
}
}
public enum HashFormat
{
MD5
{
@Override
@SuppressWarnings("deprecation")
public HashFunction getHash()
{
return Hashing.md5();
}
}, SHA1
{
@Override
@SuppressWarnings("deprecation")
public HashFunction getHash()
{
return Hashing.sha1();
}
}, SHA256
{
@Override
public HashFunction getHash()
{
return Hashing.sha256();
}
}, SHA512
{
@Override
public HashFunction getHash()
{
return Hashing.sha512();
}
};
public abstract HashFunction getHash();
}
private static boolean containsGit(File file)
{
return new File( file, ".git" ).isDirectory();
}
}