All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jfrog.tasks.GenerateDepTrees Maven / Gradle / Ivy

There is a newer version: 3.0.2
Show newest version
package com.jfrog.tasks;

import com.jfrog.GradleDepTreeResults;
import com.jfrog.GradleDependencyNode;
import com.jfrog.Utils;
import org.gradle.api.DefaultTask;
import org.gradle.api.GradleException;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.tasks.*;

import javax.annotation.Nonnull;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;

import static com.jfrog.GradleDependencyTreeUtils.addConfiguration;

/**
 * Represents the generateDepTrees Gradle task.
 *
 * @author yahavi
 **/
@CacheableTask
public class GenerateDepTrees extends DefaultTask {
    public static final String OUTPUT_FILE_PROPERTY = "com.jfrog.depsTreeOutputFile";
    public static final String TASK_NAME = "generateDepTrees";
    public static final String INCLUDE_ALL_BUILD_FILES = "com.jfrog.includeAllBuildFiles";

    private final Path pluginOutputDir = Paths.get(getProject().getRootProject().getBuildDir().getPath(), "gradle-dep-tree");

    public GenerateDepTrees() {
        // Disables executing this task on subprojects
        setImpliesSubProjects(true);
        setOnlyIf(element -> {
            if (System.getProperty(OUTPUT_FILE_PROPERTY) == null) {
                throw new GradleException("'" + OUTPUT_FILE_PROPERTY + "' system property is mandatory");
            }
            return true;
        });
    }

    @Internal
    @Override
    @Nonnull
    public String getName() {
        return TASK_NAME;
    }

    /**
     * This method is used by Gradle, to decide whether this task is up-to-date or should be running.
     * If the build.gradle of the project or a build.gradle file of a parent project was changed, the cache should be invalidated.
     *
     * @return a list of the build.gradle files of the project and its parents.
     */
    @InputFiles
    @Classpath
    public List getInputFile() {
        List inputFiles = new ArrayList<>();
        for (Project project = getProject(); project != null; project = project.getParent()) {
            inputFiles.add(project.getBuildFile());
        }
        return inputFiles;
    }

    /**
     * This method is used by Gradle, to decide whether this task is up-to-date or should be running.
     * If an output file is missing, the task will be executed.
     *
     * @return a list of the output files of the task.
     */
    @OutputFiles
    public List getOutputFiles() {
        List outputFiles = new ArrayList<>();
        for (Project project : getRelatedProjects()) {
            outputFiles.add(getProjectOutputFile(project));
        }
        return outputFiles;
    }

    @TaskAction
    void generateDepTrees() throws IOException {
        createOutputDir();
        for (Project project : getRelatedProjects()) {
            GradleDepTreeResults results = createProjectDependencyTree(project);
            // Write output to file
            Utils.saveToFileAsJson(getProjectOutputFile(project), results);
        }
    }

    /**
     * Write the summary to the stdout after the task finished.
     *
     * @return task dependency.
     */
    @Internal
    @Override
    @Nonnull
    public TaskDependency getFinalizedBy() {
        String outputFile = System.getProperty(OUTPUT_FILE_PROPERTY);
        try (FileWriter writer = new FileWriter(outputFile, false)) {
            for (File file : getOutputFiles()) {
                writer.append(file.getAbsolutePath()).append(System.lineSeparator());
            }
            writer.flush();
        } catch (IOException e) {
            throw new GradleException("File '" + outputFile + "' is not writable", e);
        }
        return super.getFinalizedBy();
    }

    /**
     * Related projects:
     * - The current running project.
     * - Subprojects that don't contain build.gradle file - this is needed to allow running this task concurrently on
     * build.gradle files. The user should be allowed to run "gradle generateDepTrees" on each one of the
     * build.gradle files in his/her project.
     *
     * @return list of related projects.
     */
    private List getRelatedProjects() {
        List relatedProjects = new ArrayList<>();
        relatedProjects.add(getProject());
        boolean includeAllBuildFiles = Boolean.parseBoolean(System.getProperty(INCLUDE_ALL_BUILD_FILES));

        for (Project project : getProject().getSubprojects()) {
            if (includeAllBuildFiles || !project.getBuildFile().exists()) {
                relatedProjects.add(project);
            }
        }
        return relatedProjects;
    }

    /**
     * Get the output file of the project. The output files are list of files under ${buildDir}/gradle-dep-tree
     * directory. The files are generated in the end of the "generateDepTrees" task, for each one of the related
     * projects. To support special characters, the name of the output file is a base64 encoding of the project name.
     *
     * @param project - The current Gradle project
     * @return the output file of the project.
     */
    private File getProjectOutputFile(Project project) {
        return project.file(pluginOutputDir.resolve(Base64.getEncoder().encodeToString(project.getName().getBytes(StandardCharsets.UTF_8))));
    }

    /**
     * Create "${buildDir}/gradle-dep-tree" directory.
     *
     * @throws IOException in case of any I/O error.
     */
    private void createOutputDir() throws IOException {
        Files.createDirectories(pluginOutputDir);
    }

    /**
     * Generate the dependency tree for all project's configurations.
     *
     * @param project - The Gradle project
     * @return a result object containing the root of the tree, the nodes and the relations between them.
     */
    private GradleDepTreeResults createProjectDependencyTree(Project project) {
        String rootId = getProjectModuleId(project);
        GradleDependencyNode root = new GradleDependencyNode();
        Map nodes = new HashMap<>();
        nodes.put(rootId, root);

        for (Configuration configuration : project.getConfigurations()) {
            addConfiguration(root, configuration, nodes);
        }
        return new GradleDepTreeResults(rootId, nodes);
    }

    private String getProjectModuleId(Project project) {
        final String unspecifiedIdPart = "unspecified";
        String group = project.getGroup().toString().isEmpty() ? unspecifiedIdPart : project.getGroup().toString();
        String name = project.getName().isEmpty() ? unspecifiedIdPart : project.getName();
        String version = project.getVersion().toString().isEmpty() ? unspecifiedIdPart : project.getVersion().toString();
        return String.join(":", group, name, version);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy