package com.prezi.haxe.gradle; import com.google.common.base.Predicate; import com.google.common.base.Strings; import com.google.common.collect.Iterables; import org.gradle.api.Action; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.Dependency; import org.gradle.api.artifacts.ProjectDependency; import org.gradle.api.artifacts.PublishArtifact; import org.gradle.api.file.CopySpec; import org.gradle.api.file.FileCollection; import org.gradle.api.file.FileTree; import org.gradle.api.file.FileVisitDetails; import org.gradle.api.file.FileVisitor; import org.gradle.api.internal.file.FileOperations; import org.gradle.api.internal.project.ProjectInternal; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.Map; import java.util.Set; import java.util.jar.Manifest; public class HaxelibDependencyExtractor { public HaxelibDependencyExtractor(Project project) { this.project = project; } public void extractDependenciesFrom(FileCollection classPath, Set<File> sourcePath, Set<File> resourcePath, Map<String, File> embeds) { if (classPath instanceof Configuration) { for (Configuration config : ((Configuration) classPath).getHierarchy()) { extractDependenciesFromInternal(config, sourcePath, resourcePath, embeds); } } else { for (File file : classPath.getFiles()) { extractFile(file.getName(), file, sourcePath, resourcePath, embeds); } } } private void extractDependenciesFromInternal(Configuration configuration, Set<File> sourcePath, Set<File> resourcePath, Map<String, File> embeds) { for (Dependency dependency : configuration.getAllDependencies()) { if (dependency instanceof ProjectDependency) { ProjectDependency projectDependency = (ProjectDependency) dependency; Configuration dependentConfiguration = projectDependency.getProjectConfiguration(); extractDependenciesFromInternal(dependentConfiguration, sourcePath, resourcePath, embeds); Iterable<PublishArtifact> harArtifacts = Iterables.filter(dependentConfiguration.getAllArtifacts(), new Predicate<PublishArtifact>() { @Override public boolean apply(PublishArtifact artifact) { return artifact.getType().equals(Har.DEFAULT_EXTENSION); } }); for (PublishArtifact artifact : harArtifacts) { String libName = artifact.getName() + (!Strings.isNullOrEmpty(artifact.getClassifier()) ? "-" + artifact.getClassifier() : ""); extractFile(libName, artifact.getFile(), sourcePath, resourcePath, embeds); } } else { for (File file : configuration.files(dependency)) { extractFile(file.getName(), file, sourcePath, resourcePath, embeds); } } } } private void extractFile(final String libName, final File file, Set<File> sourcePath, Set<File> resourcePath, Map<String, File> embeddedResources) { final File targetPath = project.file(String.valueOf(project.getBuildDir()) + "/" + EXTRACTED_HAXELIBS_DIR + "/" + libName); project.getLogger().debug("Extracting Haxe library file: {} into {}", file, targetPath); final FileTree zip = project.zipTree(file); ((ProjectInternal) project).getServices().get(FileOperations.class).sync(new Action<CopySpec>() { @Override public void execute(CopySpec copySpec) { copySpec.from(zip); copySpec.into(targetPath); } }); HaxelibTypeDetector typeDetector = new HaxelibTypeDetector(targetPath); zip.visit(typeDetector); final File libraryRoot = typeDetector.getLibraryRoot(); final Manifest manifest = typeDetector.getManifest(); final HaxelibType type = typeDetector.getType(); if (type == null) { project.getLogger().warn("Unsupported library type: " + file); } else { switch (type) { case VERSION_1_0: File sources = new File(libraryRoot, "sources"); File resources = new File(libraryRoot, "resources"); File embedded = new File(libraryRoot, "embedded"); if (sources.exists()) { project.getLogger().debug("Prezi Haxelib 1.0, adding sources at {}", sources); sourcePath.add(sources); } if (resources.exists()) { project.getLogger().debug("Prezi Haxelib 1.0, adding resources at {}", resources); resourcePath.add(resources); } if (embedded.exists()) { project.getLogger().debug("Prezi Haxelib 1.0, adding embedded resources at {}", embedded); resourcePath.add(embedded); embeddedResources.putAll(EmbeddedResourceEncoding.decode(manifest.getMainAttributes().getValue(Har.MANIFEST_ATTR_EMBEDDED_RESOURCES), embedded)); } break; case HAXELIB: project.getLogger().debug("Official Haxelib, adding root at {}", libraryRoot); sourcePath.add(libraryRoot); break; } } } private static final String EXTRACTED_HAXELIBS_DIR = "haxelibs"; private final Project project; private static class HaxelibTypeDetector implements FileVisitor { private File libraryRoot; private Manifest manifest; private HaxelibType type; public HaxelibTypeDetector(File targetPath) { this.libraryRoot = targetPath; } @Override public void visitDir(FileVisitDetails details) { } @Override public void visitFile(FileVisitDetails details) { String path = details.getPath(); if (path.startsWith("/")) { path = path.substring(1); } if (path.equals("META-INF/MANIFEST.MF")) { try { FileInputStream fis = new FileInputStream(details.getFile()); try { manifest = new Manifest(fis); if ("1.0".equals(manifest.getMainAttributes().getValue(Har.MANIFEST_ATTR_LIBRARY_VERSION))) { type = HaxelibType.VERSION_1_0; details.stopVisiting(); } } finally { fis.close(); } } catch (IOException ex) { throw new RuntimeException(ex); } } else if ((details.getName().equals("haxelib.json") || details.getName().equals("haxelib.xml")) && details.getRelativePath().getParent() != null) { type = HaxelibType.HAXELIB; libraryRoot = details.getRelativePath().getParent().getFile(libraryRoot); details.stopVisiting(); } } public File getLibraryRoot() { return libraryRoot; } public Manifest getManifest() { return manifest; } public HaxelibType getType() { return type; } } }