Add AccessWidenerValidator (#518)
* Add AccessWidenerValidator * Move to task * Review feedback
This commit is contained in:
		
							parent
							
								
									0248bacc16
								
							
						
					
					
						commit
						a10307464e
					
				
					 5 changed files with 138 additions and 2 deletions
				
			
		|  | @ -39,13 +39,13 @@ import org.gradle.api.artifacts.ExternalModuleDependency; | |||
| import org.gradle.api.artifacts.repositories.MavenArtifactRepository; | ||||
| 
 | ||||
| import net.fabricmc.loom.LoomGradleExtension; | ||||
| import net.fabricmc.loom.LoomRepositoryPlugin; | ||||
| import net.fabricmc.loom.build.ModCompileRemapper; | ||||
| import net.fabricmc.loom.configuration.DependencyProvider.DependencyInfo; | ||||
| import net.fabricmc.loom.configuration.mods.ModProcessor; | ||||
| import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl; | ||||
| import net.fabricmc.loom.util.Constants; | ||||
| import net.fabricmc.loom.util.SourceRemapper; | ||||
| import net.fabricmc.loom.LoomRepositoryPlugin; | ||||
| 
 | ||||
| public class LoomDependencyManager { | ||||
| 	private static class ProviderList { | ||||
|  |  | |||
|  | @ -57,6 +57,13 @@ public final class LoomTasks { | |||
| 		tasks.register("downloadAssets", DownloadAssetsTask.class, t -> t.setDescription("Downloads required assets for Fabric.")); | ||||
| 		tasks.register("remapSourcesJar", RemapSourcesJarTask.class, t -> t.setDescription("Remaps the project sources jar to intermediary names.")); | ||||
| 
 | ||||
| 		tasks.getByName("check").dependsOn( | ||||
| 				tasks.register("validateAccessWidener", ValidateAccessWidenerTask.class, t -> { | ||||
| 					t.setDescription("Validate all the rules in the access widener against the Minecraft jar"); | ||||
| 					t.setGroup("verification"); | ||||
| 				}) | ||||
| 		); | ||||
| 
 | ||||
| 		registerIDETasks(tasks); | ||||
| 		registerRunTasks(tasks, project); | ||||
| 		registerDecompileTasks(tasks, project); | ||||
|  |  | |||
|  | @ -0,0 +1,109 @@ | |||
| /* | ||||
|  * This file is part of fabric-loom, licensed under the MIT License (MIT). | ||||
|  * | ||||
|  * Copyright (c) 2021 FabricMC | ||||
|  * | ||||
|  * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|  * of this software and associated documentation files (the "Software"), to deal | ||||
|  * in the Software without restriction, including without limitation the rights | ||||
|  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|  * copies of the Software, and to permit persons to whom the Software is | ||||
|  * furnished to do so, subject to the following conditions: | ||||
|  * | ||||
|  * The above copyright notice and this permission notice shall be included in all | ||||
|  * copies or substantial portions of the Software. | ||||
|  * | ||||
|  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|  * SOFTWARE. | ||||
|  */ | ||||
| 
 | ||||
| package net.fabricmc.loom.task; | ||||
| 
 | ||||
| import java.io.BufferedReader; | ||||
| import java.io.IOException; | ||||
| import java.io.UncheckedIOException; | ||||
| import java.nio.charset.StandardCharsets; | ||||
| import java.nio.file.Files; | ||||
| 
 | ||||
| import javax.inject.Inject; | ||||
| 
 | ||||
| import org.gradle.api.DefaultTask; | ||||
| import org.gradle.api.file.RegularFileProperty; | ||||
| import org.gradle.api.tasks.InputFile; | ||||
| import org.gradle.api.tasks.SkipWhenEmpty; | ||||
| import org.gradle.api.tasks.TaskAction; | ||||
| 
 | ||||
| import net.fabricmc.accesswidener.AccessWidenerFormatException; | ||||
| import net.fabricmc.accesswidener.AccessWidenerReader; | ||||
| import net.fabricmc.accesswidener.AccessWidenerVisitor; | ||||
| import net.fabricmc.loom.LoomGradleExtension; | ||||
| import net.fabricmc.tinyremapper.TinyRemapper; | ||||
| import net.fabricmc.tinyremapper.api.TrEnvironment; | ||||
| 
 | ||||
| public abstract class ValidateAccessWidenerTask extends DefaultTask { | ||||
| 	@SkipWhenEmpty | ||||
| 	@InputFile | ||||
| 	public abstract RegularFileProperty getAccessWidener(); | ||||
| 
 | ||||
| 	@InputFile | ||||
| 	public abstract RegularFileProperty getTargetJar(); | ||||
| 
 | ||||
| 	@Inject | ||||
| 	public ValidateAccessWidenerTask() { | ||||
| 		final LoomGradleExtension extension = LoomGradleExtension.get(getProject()); | ||||
| 
 | ||||
| 		getAccessWidener().convention(extension.getAccessWidenerPath()).finalizeValueOnRead(); | ||||
| 		getTargetJar().convention(getProject().getObjects().fileProperty().fileValue(extension.getMinecraftMappedProvider().getMappedJar())).finalizeValueOnRead(); | ||||
| 	} | ||||
| 
 | ||||
| 	@TaskAction | ||||
| 	public void run() { | ||||
| 		final TinyRemapper tinyRemapper = TinyRemapper.newRemapper().build(); | ||||
| 		tinyRemapper.readClassPath(getTargetJar().get().getAsFile().toPath()); | ||||
| 
 | ||||
| 		final AccessWidenerValidator validator = new AccessWidenerValidator(tinyRemapper.getEnvironment()); | ||||
| 		final AccessWidenerReader accessWidenerReader = new AccessWidenerReader(validator); | ||||
| 
 | ||||
| 		try (BufferedReader reader = Files.newBufferedReader(getAccessWidener().get().getAsFile().toPath(), StandardCharsets.UTF_8)) { | ||||
| 			accessWidenerReader.read(reader, "named"); | ||||
| 		} catch (AccessWidenerFormatException e) { | ||||
| 			getProject().getLogger().error("Failed to validate access-widener file {} on line {}: {}", getAccessWidener().get().getAsFile().getName(), e.getLineNumber(), e.getMessage()); | ||||
| 			throw e; | ||||
| 		} catch (IOException e) { | ||||
| 			throw new UncheckedIOException("Failed to read access widener", e); | ||||
| 		} finally { | ||||
| 			tinyRemapper.finish(); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Validates that all entries in an access-widner file relate to a class/method/field in the mc jar. | ||||
| 	 */ | ||||
| 	private static record AccessWidenerValidator(TrEnvironment environment) implements AccessWidenerVisitor { | ||||
| 		@Override | ||||
| 		public void visitClass(String name, AccessWidenerReader.AccessType access, boolean transitive) { | ||||
| 			if (environment().getClass(name) == null) { | ||||
| 				throw new RuntimeException("Could not find class (%s)".formatted(name)); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		@Override | ||||
| 		public void visitMethod(String owner, String name, String descriptor, AccessWidenerReader.AccessType access, boolean transitive) { | ||||
| 			if (environment().getMethod(owner, name, descriptor) == null) { | ||||
| 				throw new RuntimeException("Could not find method (%s%s) in class (%s)".formatted(name, descriptor, owner)); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		@Override | ||||
| 		public void visitField(String owner, String name, String descriptor, AccessWidenerReader.AccessType access, boolean transitive) { | ||||
| 			if (environment().getField(owner, name, descriptor) == null) { | ||||
| 				throw new RuntimeException("Could not find field (%s%s) in class (%s)".formatted(name, descriptor, owner)); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | @ -65,4 +65,24 @@ class AccessWidenerTest extends Specification implements GradleProjectTestTrait | |||
| 		where: | ||||
| 			version << STANDARD_TEST_VERSIONS | ||||
| 	} | ||||
| 
 | ||||
| 	@Unroll | ||||
| 	def "invalid (#awLine)"() { | ||||
| 		setup: | ||||
| 			def gradle = gradleProject(project: "accesswidener", version: version) | ||||
| 			new File(gradle.projectDir, "src/main/resources/modid.accesswidener").append(awLine) | ||||
| 			def errorPrefix = "Failed to validate access-widener file modid.accesswidener on line 10: java.lang.RuntimeException: " | ||||
| 
 | ||||
| 		when: | ||||
| 			def result = gradle.run(task: "check", expectFailure: true) | ||||
| 
 | ||||
| 		then: | ||||
| 			result.output.contains(errorPrefix + error) | ||||
| 
 | ||||
| 		where: | ||||
| 			awLine 																					| error																									| version | ||||
| 			'accessible\tclass\tnet/minecraft/DoesntExists'											| "Could not find class (net/minecraft/DoesntExists)"													| DEFAULT_GRADLE | ||||
| 			'accessible\tfield\tnet/minecraft/screen/slot/Slot\tabc\tI'								| "Could not find field (abcI) in class (net/minecraft/screen/slot/Slot)"								| DEFAULT_GRADLE | ||||
| 			'accessible\tmethod\tnet/minecraft/client/main/Main\tmain\t([Ljava/lang/NotAString;)V'	| "Could not find method (main([Ljava/lang/NotAString;)V) in class (net/minecraft/client/main/Main)"	| DEFAULT_GRADLE | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -156,7 +156,7 @@ trait GradleProjectTestTrait { | |||
| 
 | ||||
|             runner.withArguments(args as String[]) | ||||
| 
 | ||||
|             return runner.build() | ||||
|             return options.expectFailure ? runner.buildAndFail() : runner.build() | ||||
|         } | ||||
| 
 | ||||
|         private GradleRunner getRunner() { | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue