Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.blackduck.integration.detectable.detectables.meson;

import com.blackduck.integration.bdio.model.Forge;

public class MesonConstants {
public static final Forge MESON_FORGE = new Forge("/", "meson");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.blackduck.integration.detectable.detectables.meson;

import java.io.File;

import com.blackduck.integration.common.util.finder.FileFinder;
import com.blackduck.integration.detectable.Detectable;
import com.blackduck.integration.detectable.DetectableEnvironment;
import com.blackduck.integration.detectable.detectable.DetectableAccuracyType;
import com.blackduck.integration.detectable.detectable.Requirements;
import com.blackduck.integration.detectable.detectable.annotation.DetectableInfo;
import com.blackduck.integration.detectable.detectable.result.DetectableResult;
import com.blackduck.integration.detectable.detectable.result.FilesNotFoundDetectableResult;
import com.blackduck.integration.detectable.detectable.result.PassedDetectableResult;
import com.blackduck.integration.detectable.extraction.Extraction;
import com.blackduck.integration.detectable.extraction.ExtractionEnvironment;

@DetectableInfo(name = "Meson", language = "C/C++", forge = "meson", accuracy = DetectableAccuracyType.HIGH, requirementsMarkdown = "Files: meson.build, intro-projectinfo.json, intro-dependencies.json")
public class MesonDetectable extends Detectable {
public static final String MESON_BUILD_FILENAME = "meson.build";
private static final String INTROSPECT_PROJECT_FILENAME = "intro-projectinfo.json";
private static final String INTROSPECT_DEPENDENCIES_FILENAME = "intro-dependencies.json";

private final FileFinder fileFinder;
private final MesonExtractor mesonExtractor;

private File projectInfoFile;
private File dependenciesFile;

public MesonDetectable(DetectableEnvironment environment, FileFinder fileFinder, MesonExtractor mesonExtractor) {
super(environment);
this.fileFinder = fileFinder;
this.mesonExtractor = mesonExtractor;
}

@Override
public DetectableResult applicable() {
Requirements requirements = new Requirements(fileFinder, environment);
requirements.file(MESON_BUILD_FILENAME);
if (requirements.isAlreadyFailed()) {
return requirements.result();
}
projectInfoFile = fileFinder.findFile(environment.getDirectory(), INTROSPECT_PROJECT_FILENAME, false, 2);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I originally made only "meson.build" the file that makes detect.jar aware that this is a meson project and only then check if the introspects exist.

If the "meson setup builddir" has been not been run first, the fileFinder will not find the introspect files. "Buildless" makes me feel "meson setup" is not needed. Instead of reading introspect files created by meson, it is also possible to read the dependencies straight from the meson.build file, the example I added to the testcases are simple

deps = [dependency('boost'),dependency('libcurl'),]

But meson itself has multiple ways to determine dependencies
https://github.com/mesonbuild/meson/blob/master/mesonbuild/dependencies/base.py#L111

Other detectables try to look for the binary and run the application if found. It is possible to run "meson setup bd_temp" and than read the introspect_files that are freshly created in the bd_temp directory.

dependenciesFile = fileFinder.findFile(environment.getDirectory(), INTROSPECT_DEPENDENCIES_FILENAME, false, 2);
if (projectInfoFile == null || dependenciesFile == null) {
return new FilesNotFoundDetectableResult(INTROSPECT_PROJECT_FILENAME, INTROSPECT_DEPENDENCIES_FILENAME);
}
requirements.explainFile(projectInfoFile);
requirements.explainFile(dependenciesFile);
return requirements.result();
}

@Override
public DetectableResult extractable() {
return new PassedDetectableResult();
}

@Override
public Extraction extract(ExtractionEnvironment extractionEnvironment) {
return mesonExtractor.extract(projectInfoFile, dependenciesFile);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.blackduck.integration.detectable.detectables.meson;

import java.io.BufferedReader;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.blackduck.integration.bdio.graph.DependencyGraph;
import com.blackduck.integration.detectable.detectable.codelocation.CodeLocation;
import com.blackduck.integration.detectable.detectables.meson.parse.MesonDependencyFileParser;
import com.blackduck.integration.detectable.detectables.meson.parse.MesonProjectFileParser;
import com.blackduck.integration.detectable.extraction.Extraction;
import com.blackduck.integration.util.NameVersion;
import com.google.gson.Gson;

public class MesonExtractor {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final MesonProjectFileParser mesonProjectFileParser;
private final MesonDependencyFileParser mesonDependencyFileParser;
private final Gson gson;

public MesonExtractor(
MesonProjectFileParser mesonProjectFileParser,
MesonDependencyFileParser mesonDependencyFileParser,
Gson gson
) {
this.mesonProjectFileParser = mesonProjectFileParser;
this.mesonDependencyFileParser = mesonDependencyFileParser;
this.gson = gson;
}

public Extraction extract(File projectInfoFile, File dependenciesFile) {
try {
logger.debug("Parsing Meson project info: {}", projectInfoFile.getAbsolutePath());
NameVersion nameVersion = determineProjectNameVersion(projectInfoFile);

logger.debug("Parsing Meson dependencies: {}", dependenciesFile.getAbsolutePath());
try (BufferedReader reader = Files.newBufferedReader(dependenciesFile.toPath(), StandardCharsets.UTF_8)) {
DependencyGraph dependencyGraph = mesonDependencyFileParser.parseProjectDependencies(gson, reader);
CodeLocation codeLocation = new CodeLocation(dependencyGraph);

return new Extraction.Builder()
.success(codeLocation)
.projectName(nameVersion.getName())
.projectVersion(nameVersion.getVersion())
.build();
}

} catch (Exception e) {
logger.error("Failed to extract Meson dependencies", e);
return new Extraction.Builder().exception(e).build();
}
}

private NameVersion determineProjectNameVersion(File projectInfoFile) {
final String defaultProjectName = "";
final String defaultProjectVersion = "";

try (BufferedReader reader = Files.newBufferedReader(projectInfoFile.toPath(), StandardCharsets.UTF_8)) {
return mesonProjectFileParser.getProjectNameVersion(gson, reader, defaultProjectName, defaultProjectVersion);
} catch (Exception e) {
logger.warn("Failed to parse Meson introspect, using defaults", e);
}

return new NameVersion(defaultProjectName, defaultProjectVersion);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package com.blackduck.integration.detectable.detectables.meson.parse;

import java.io.BufferedReader;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.blackduck.integration.bdio.graph.BasicDependencyGraph;
import com.blackduck.integration.bdio.graph.DependencyGraph;
import com.blackduck.integration.bdio.model.dependency.Dependency;
import com.blackduck.integration.bdio.model.externalid.ExternalId;
import com.blackduck.integration.bdio.model.externalid.ExternalIdFactory;
import com.blackduck.integration.detectable.detectables.meson.MesonConstants;
import com.google.gson.Gson;

public class MesonDependencyFileParser {
private static final String INTERNAL_DEPENDENCY_TYPE = "internal";

private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final ExternalIdFactory externalIdFactory;

public MesonDependencyFileParser(ExternalIdFactory externalIdFactory) {
this.externalIdFactory = externalIdFactory;
}

public DependencyGraph parseProjectDependencies(Gson gson, BufferedReader reader) {
DependencyGraph graph = new BasicDependencyGraph();

try {
MesonDependency[] dependencies = gson.fromJson(reader, MesonDependency[].class);

if (dependencies == null) {
logger.warn("Failed to parse Meson dependencies - gson returned null");
return graph;
}

logger.debug("Found {} Meson dependencies", dependencies.length);

for (MesonDependency dep : dependencies) {
if (!INTERNAL_DEPENDENCY_TYPE.equals(dep.getType())
&& StringUtils.isNotBlank(dep.getName())
&& StringUtils.isNotBlank(dep.getVersion())) {

ExternalId dependencyExternalId = externalIdFactory.createNameVersionExternalId(MesonConstants.MESON_FORGE, dep.getName(), dep.getVersion());
Dependency dependency = new Dependency(dep.getName(), dep.getVersion(), dependencyExternalId);
logger.trace("Adding dependency: {}", dependency.getExternalId());
graph.addDirectDependency(dependency);
} else {
logger.debug("Skipping dependency - name: '{}', type: '{}', version: '{}'", dep.getName(), dep.getType(), dep.getVersion());
}
}
} catch (Exception e) {
logger.warn("Failed to parse Meson dependency JSON", e);
}

return graph;
}

private static class MesonDependency {
private String name;
private String type;
private String version;

public String getName() {
return name;
}

public String getType() {
return type;
}

public String getVersion() {
return version;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.blackduck.integration.detectable.detectables.meson.parse;

import java.io.BufferedReader;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.blackduck.integration.util.NameVersion;
import com.google.gson.Gson;

public class MesonProjectFileParser {
private final Logger logger = LoggerFactory.getLogger(this.getClass());

public MesonProjectFileParser() {
}

public NameVersion getProjectNameVersion(Gson gson, BufferedReader reader, String defaultProjectName,
String defaultProjectVersion) {
try {
MesonProjectInfo projectInfo = gson.fromJson(reader, MesonProjectInfo.class);

String projectName = defaultProjectName;
String projectVersion = defaultProjectVersion;

if (StringUtils.isNotBlank(projectInfo.getDescriptiveName())) {
projectName = projectInfo.getDescriptiveName();
logger.debug("Extracted project name: {}", projectName);
}

if (StringUtils.isNotBlank(projectInfo.getVersion())) {
projectVersion = projectInfo.getVersion();
logger.debug("Extracted project version: {}", projectVersion);
}

return new NameVersion(projectName, projectVersion);
} catch (Exception e) {
logger.warn("Failed to parse Meson project JSON, using defaults", e);
return new NameVersion(defaultProjectName, defaultProjectVersion);
}
}

private static class MesonProjectInfo {
private String version;
private String descriptive_name;

public String getVersion() {
return version;
}

public String getDescriptiveName() {
return descriptive_name;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,10 @@
import com.blackduck.integration.detectable.detectables.maven.cli.MavenPomDetectable;
import com.blackduck.integration.detectable.detectables.maven.cli.MavenPomWrapperDetectable;
import com.blackduck.integration.detectable.detectables.maven.parsing.MavenProjectInspectorDetectable;
import com.blackduck.integration.detectable.detectables.meson.MesonDetectable;
import com.blackduck.integration.detectable.detectables.meson.MesonExtractor;
import com.blackduck.integration.detectable.detectables.meson.parse.MesonDependencyFileParser;
import com.blackduck.integration.detectable.detectables.meson.parse.MesonProjectFileParser;
import com.blackduck.integration.detectable.detectables.npm.cli.NpmCliDetectable;
import com.blackduck.integration.detectable.detectables.npm.cli.NpmCliExtractor;
import com.blackduck.integration.detectable.detectables.npm.cli.NpmCliExtractorOptions;
Expand Down Expand Up @@ -396,6 +400,13 @@ public ClangDetectable createClangDetectable(DetectableEnvironment environment,
);
}

public MesonDetectable createMesonDetectable(DetectableEnvironment environment) {
MesonProjectFileParser mesonProjectFileParser = new MesonProjectFileParser();
MesonDependencyFileParser mesonDependencyFileParser = new MesonDependencyFileParser(externalIdFactory);
MesonExtractor mesonExtractor = new MesonExtractor(mesonProjectFileParser, mesonDependencyFileParser, gson);
return new MesonDetectable(environment, fileFinder, mesonExtractor);
}

public ComposerLockDetectable createComposerDetectable(DetectableEnvironment environment, ComposerLockDetectableOptions composerLockDetectableOptions) {
PackagistParser packagistParser = new PackagistParser(externalIdFactory, composerLockDetectableOptions.getDependencyTypeFilter());
ComposerLockExtractor composerLockExtractor = new ComposerLockExtractor(packagistParser);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.blackduck.integration.detectable.detectables.meson.functional;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;

import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Assertions;

import com.blackduck.integration.detectable.Detectable;
import com.blackduck.integration.detectable.DetectableEnvironment;
import com.blackduck.integration.detectable.extraction.Extraction;
import com.blackduck.integration.detectable.detectables.meson.MesonConstants;
import com.blackduck.integration.detectable.functional.DetectableFunctionalTest;
import com.blackduck.integration.detectable.util.graph.NameVersionGraphAssert;

public class MesonDependencyFileParserTest extends DetectableFunctionalTest {
public MesonDependencyFileParserTest() throws IOException {
super("meson");
}

@Override
protected void setup() throws IOException {
Path root = addDirectory(Paths.get("."));
Path builddir = addDirectory(Paths.get("builddir/meson-info"));
addFile(
root.resolve("meson.build"),
"project('hello','cpp',default_options: ['cpp_std=c++20', 'default_library=shared'],version : '0.1',)",
"deps = [dependency('boost'),dependency('libcurl'),]",
"exe = executable('hello', 'hello.cpp', install : true)"
);
addFile(
builddir.resolve("intro-projectinfo.json"),
"{\"version\": \"0.1\", \"descriptive_name\": \"hello\", \"subproject_dir\": \"subprojects\", \"subprojects\": []}"
);
addFile(builddir.resolve("intro-dependencies.json"),
"[{\"name\": \"boost\", \"type\": \"system\", \"version\": \"1.83.0\", \"compile_args\": [\"-I/usr/include\", \"-DBOOST_ALL_NO_LIB\"], \"link_args\": [], \"include_directories\": [], \"sources\": [], \"extra_files\": [], \"dependencies\": [], \"depends\": [], \"meson_variables\": []}, {\"name\": \"libcurl\", \"type\": \"pkgconfig\", \"version\": \"8.5.0\", \"compile_args\": [\"-I/usr/include/x86_64-linux-gnu\"], \"link_args\": [\"/usr/lib/x86_64-linux-gnu/libcurl.so\"], \"include_directories\": [], \"sources\": [], \"extra_files\": [], \"dependencies\": [], \"depends\": [], \"meson_variables\": []}]"
);
}

@NotNull
@Override
public Detectable create(@NotNull DetectableEnvironment detectableEnvironment) {
return detectableFactory.createMesonDetectable(detectableEnvironment);
}

@Override
public void assertExtraction(@NotNull Extraction extraction) {
Assertions.assertEquals(1, extraction.getCodeLocations().size());
NameVersionGraphAssert graphAssert = new NameVersionGraphAssert(MesonConstants.MESON_FORGE, extraction.getCodeLocations().get(0).getDependencyGraph());
graphAssert.hasRootSize(2);
graphAssert.hasRootDependency("boost", "1.83.0");
graphAssert.hasRootDependency("libcurl", "8.5.0");
}
}
Loading