org.sonar.plugins.visualstudio.VisualStudioProjectBuilder.java Source code

Java tutorial

Introduction

Here is the source code for org.sonar.plugins.visualstudio.VisualStudioProjectBuilder.java

Source

/*
 * Analysis Bootstrapper for Visual Studio Projects
 * Copyright (C) 2014 SonarSource
 * dev@sonar.codehaus.org
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
 */
package org.sonar.plugins.visualstudio;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableSet;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.batch.bootstrap.ProjectBuilder;
import org.sonar.api.batch.bootstrap.ProjectDefinition;
import org.sonar.api.config.Settings;
import org.sonar.api.utils.SonarException;

import javax.annotation.Nullable;

import java.io.File;
import java.io.IOException;
import java.text.Normalizer;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.regex.PatternSyntaxException;

public class VisualStudioProjectBuilder extends ProjectBuilder {

    private static final String SONAR_MODULES_PROPERTY_KEY = "sonar.modules";
    private static final String WEB_APPLICATION_PROJECT_TYPE_GUID = "{349C5851-65DF-11DA-9384-00065B846F21}";
    private static final Logger LOG = LoggerFactory.getLogger(VisualStudioProjectBuilder.class);

    private final Settings settings;

    public VisualStudioProjectBuilder(Settings settings) {
        this.settings = settings;
    }

    @Override
    public void build(Context context) {
        build(context, new VisualStudioAssemblyLocator(settings));
    }

    public void build(Context context, VisualStudioAssemblyLocator assemblyLocator) {
        ProjectDefinition sonarProject = context.projectReactor().getRoot();

        if (!settings.getBoolean(VisualStudioPlugin.VISUAL_STUDIO_ENABLE_PROPERTY_KEY)) {
            LOG.info("To enable the analysis bootstraper for Visual Studio projects, set the property \""
                    + VisualStudioPlugin.VISUAL_STUDIO_ENABLE_PROPERTY_KEY + "\" to \"true\"");
            return;
        }

        File solutionFile = getSolutionFile(sonarProject.getBaseDir());
        if (solutionFile == null) {
            LOG.info("No Visual Studio solution file found.");
            return;
        }

        LOG.info("Using the following Visual Studio solution: " + solutionFile.getAbsolutePath());

        if (settings.hasKey(SONAR_MODULES_PROPERTY_KEY)) {
            throw new SonarException("Do not use the Visual Studio bootstrapper and set the \""
                    + SONAR_MODULES_PROPERTY_KEY + "\" property at the same time.");
        }

        sonarProject.resetSourceDirs();

        Set<String> skippedProjects = skippedProjectsByNames();
        boolean hasModules = false;

        VisualStudioSolution solution = new VisualStudioSolutionParser().parse(solutionFile);
        VisualStudioProjectParser projectParser = new VisualStudioProjectParser();
        for (VisualStudioSolutionProject solutionProject : solution.projects()) {
            if (!isSupportedProjectType(solutionProject)) {
                logSkippedProject(solutionProject,
                        "because its project type is unsupported: " + solutionProject.path());
            } else if (skippedProjects.contains(solutionProject.name())) {
                logSkippedProject(solutionProject, "because it is listed in the property \""
                        + VisualStudioPlugin.VISUAL_STUDIO_OLD_SKIPPED_PROJECTS + "\".");
            } else if (isSkippedProjectByPattern(solutionProject.name())) {
                logSkippedProject(solutionProject, "because it matches the property \""
                        + VisualStudioPlugin.VISUAL_STUDIO_SKIPPED_PROJECT_PATTERN + "\".");
            } else {
                File projectFile = relativePathFile(solutionFile.getParentFile(), solutionProject.path());
                if (!projectFile.isFile()) {
                    LOG.warn("Unable to find the Visual Studio project file " + projectFile.getAbsolutePath());
                } else {
                    VisualStudioProject project = projectParser.parse(projectFile);
                    File assembly = assemblyLocator.locateAssembly(solutionProject.name(), projectFile, project);
                    if (skipNotBuildProjects() && assembly == null) {
                        logSkippedProject(solutionProject, "because it is not built and \""
                                + VisualStudioPlugin.VISUAL_STUDIO_SKIP_IF_NOT_BUILT + "\" is set.");
                    } else {
                        hasModules = true;
                        buildModule(sonarProject, solutionProject.name(), projectFile, project, assembly,
                                solutionFile);
                    }
                }
            }
        }

        Preconditions.checkState(hasModules, "No Visual Studio projects were found.");
    }

    private static void logSkippedProject(VisualStudioSolutionProject solutionProject, String reason) {
        LOG.info("Skipping the project \"" + solutionProject.name() + "\" " + reason);
    }

    private boolean skipNotBuildProjects() {
        return settings.getBoolean(VisualStudioPlugin.VISUAL_STUDIO_SKIP_IF_NOT_BUILT);
    }

    private boolean isSupportedProjectType(VisualStudioSolutionProject project) {
        String path = project.path().toLowerCase();
        return path.endsWith(".csproj") || path.endsWith(".vbproj") || path.endsWith(".vcxproj");
    }

    private void buildModule(ProjectDefinition solutionProject, String projectName, File projectFile,
            VisualStudioProject project, @Nullable File assembly, File solutionFile) {
        String escapedProjectName = escapeProjectName(projectName);

        ProjectDefinition module = ProjectDefinition.create()
                .setKey(projectKey(solutionProject.getKey()) + ":" + escapedProjectName).setName(projectName);
        solutionProject.addSubProject(module);

        module.setBaseDir(projectFile.getParentFile());
        module.setWorkDir(new File(solutionProject.getWorkDir(),
                solutionProject.getKey().replace(':', '_') + "_" + escapedProjectName));

        boolean isTestProject = isTestProject(projectName);
        LOG.info("Adding the Visual Studio " + (isTestProject ? "test " : "") + "project: " + projectName + "... "
                + projectFile.getAbsolutePath());

        if (isTestProject) {
            module.setTestDirs(projectFile.getParentFile());
        } else {
            module.setSourceDirs(projectFile.getParentFile());
        }

        for (String filePath : project.files()) {
            File file = relativePathFile(projectFile.getParentFile(), filePath);
            if (!file.isFile()) {
                LOG.warn("Cannot find the file " + file.getAbsolutePath() + " of project " + projectName);
            } else if (!isInSourceDir(file, projectFile.getParentFile())) {
                LOG.warn("Skipping the file " + file.getAbsolutePath() + " of project " + projectName
                        + " located outside of the source directory.");
            } else {
                if (isTestProject) {
                    module.addTestFiles(file.getAbsolutePath());
                } else {
                    module.addSourceFiles(file);
                }
            }
        }

        forwardModuleProperties(module);
        setFxCopProperties(module, projectFile, project, assembly);
        setReSharperProperties(module, projectName, solutionFile);
        setStyleCopProperties(module, projectFile);
    }

    private void forwardModuleProperties(ProjectDefinition module) {
        for (Map.Entry<String, String> entry : settings.getProperties().entrySet()) {
            if (entry.getKey().startsWith(module.getName() + ".")) {
                module.setProperty(entry.getKey().substring(module.getName().length() + 1), entry.getValue());
            }
        }
    }

    private void setFxCopProperties(ProjectDefinition module, File projectFile, VisualStudioProject project,
            @Nullable File assembly) {
        if (assembly == null) {
            return;
        }

        if (isWebApplication(project)) {
            module.setProperty("sonar.cs.fxcop.aspnet", "true");
        }

        module.setProperty("sonar.cs.fxcop.assembly", assembly.getAbsolutePath());
        module.setProperty("sonar.vbnet.fxcop.assembly", assembly.getAbsolutePath());
    }

    private boolean isWebApplication(VisualStudioProject project) {
        return project.projectTypeGuids() != null
                && project.projectTypeGuids().toUpperCase().contains(WEB_APPLICATION_PROJECT_TYPE_GUID);
    }

    private void setReSharperProperties(ProjectDefinition module, String projectName, File solutionFile) {
        module.setProperty("sonar.resharper.solutionFile", solutionFile.getAbsolutePath());
        module.setProperty("sonar.resharper.projectName", projectName);
    }

    private void setStyleCopProperties(ProjectDefinition module, File projectFile) {
        module.setProperty("sonar.stylecop.projectFilePath", projectFile.getAbsolutePath());
    }

    private static boolean isInSourceDir(File file, File folder) {
        try {
            return file.getCanonicalPath().replace('\\', '/')
                    .startsWith(folder.getCanonicalPath().replace('\\', '/') + "/");
        } catch (IOException e) {
            throw Throwables.propagate(e);
        }
    }

    @Nullable
    private File getSolutionFile(File projectBaseDir) {
        File result;

        String solutionPath = settings.getString(VisualStudioPlugin.VISUAL_STUDIO_SOLUTION_PROPERTY_KEY);
        if (!Strings.nullToEmpty(solutionPath).isEmpty()) {
            result = new File(projectBaseDir, solutionPath);
        } else {
            Collection<File> solutionFiles = FileUtils.listFiles(projectBaseDir, new String[] { "sln" }, false);
            if (solutionFiles.isEmpty()) {
                result = null;
            } else if (solutionFiles.size() == 1) {
                result = solutionFiles.iterator().next();
            } else {
                throw new SonarException("Found several .sln files in " + projectBaseDir.getAbsolutePath()
                        + ". Please set \"" + VisualStudioPlugin.VISUAL_STUDIO_SOLUTION_PROPERTY_KEY
                        + "\" to explicitly tell which one to use.");
            }
        }

        return result;
    }

    private static File relativePathFile(File file, String relativePath) {
        return new File(file, relativePath.replace('\\', '/'));
    }

    private String projectKey(String projectKey) {
        if ("unsafe"
                .equals(settings.getString(VisualStudioPlugin.VISUAL_STUDIO_PROJECT_KEY_STRATEGY_PROPERTY_KEY))) {
            int i = projectKey.indexOf(':');

            if (i == -1) {
                LOG.warn("Unset the deprecated uncessary property \""
                        + VisualStudioPlugin.VISUAL_STUDIO_PROJECT_KEY_STRATEGY_PROPERTY_KEY
                        + "\" used to analyze this project. "
                        + "This property support will soon be removed, and unsetting it will *NOT* affect this particular project.");
            } else {
                String unsafeProjectKey = projectKey.substring(0, i);

                LOG.warn("Unset the deprecated uncessary property \""
                        + VisualStudioPlugin.VISUAL_STUDIO_PROJECT_KEY_STRATEGY_PROPERTY_KEY
                        + "\" used to analyze this project. "
                        + "You will need to update the project key from the unsafe \"" + unsafeProjectKey
                        + "\" value to \"" + projectKey + "\".");

                return unsafeProjectKey;
            }
        }

        return projectKey;
    }

    @VisibleForTesting
    static String escapeProjectName(String projectName) {
        String escaped = Normalizer.normalize(projectName, Normalizer.Form.NFD);
        escaped = escaped.replaceAll("\\p{M}", "");
        escaped = escaped.replace(' ', '_');
        escaped = escaped.replace('+', '_');
        return escaped;
    }

    private boolean isTestProject(String projectName) {
        return matchesPropertyRegex(VisualStudioPlugin.VISUAL_STUDIO_TEST_PROJECT_PATTERN, projectName);
    }

    private boolean isSkippedProjectByPattern(String projectName) {
        return matchesPropertyRegex(VisualStudioPlugin.VISUAL_STUDIO_SKIPPED_PROJECT_PATTERN, projectName);
    }

    private boolean matchesPropertyRegex(String propertyKey, String value) {
        String pattern = settings.getString(propertyKey);
        try {
            return pattern != null && value.matches(pattern);
        } catch (PatternSyntaxException e) {
            LOG.error("The syntax of the regular expression of the \"" + propertyKey + "\" property is invalid: "
                    + pattern);
            throw Throwables.propagate(e);
        }
    }

    private Set<String> skippedProjectsByNames() {
        String skippedProjects = settings.getString(VisualStudioPlugin.VISUAL_STUDIO_OLD_SKIPPED_PROJECTS);
        if (skippedProjects == null) {
            return ImmutableSet.of();
        }

        LOG.warn("Replace the deprecated property \"" + VisualStudioPlugin.VISUAL_STUDIO_OLD_SKIPPED_PROJECTS
                + "\" by the new \"" + VisualStudioPlugin.VISUAL_STUDIO_SKIPPED_PROJECT_PATTERN + "\".");

        return ImmutableSet.<String>builder().addAll(Splitter.on(',').omitEmptyStrings().split(skippedProjects))
                .build();
    }

}