Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial POC for Buildroot Detectable #1076

Draft
wants to merge 11 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.synopsys.integration.detectable.detectable.executable.resolver;

import com.synopsys.integration.detectable.ExecutableTarget;
import com.synopsys.integration.detectable.detectable.exception.DetectableException;

public interface MakeResolver {
ExecutableTarget resolveMake() throws DetectableException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.synopsys.integration.detectable.detectables.buildroot;

public enum BuildrootDependencyType {
HOST
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.synopsys.integration.detectable.detectables.buildroot;

import com.synopsys.integration.bdio.graph.builder.MissingExternalIdException;
import com.synopsys.integration.common.util.finder.FileFinder;
import com.synopsys.integration.detectable.Detectable;
import com.synopsys.integration.detectable.DetectableEnvironment;
import com.synopsys.integration.detectable.ExecutableTarget;
import com.synopsys.integration.detectable.detectable.DetectableAccuracyType;
import com.synopsys.integration.detectable.detectable.Requirements;
import com.synopsys.integration.detectable.detectable.annotation.DetectableInfo;
import com.synopsys.integration.detectable.detectable.exception.DetectableException;
import com.synopsys.integration.detectable.detectable.executable.ExecutableFailedException;
import com.synopsys.integration.detectable.detectable.executable.resolver.MakeResolver;
import com.synopsys.integration.detectable.detectable.result.DetectableResult;
import com.synopsys.integration.detectable.extraction.Extraction;
import com.synopsys.integration.detectable.extraction.ExtractionEnvironment;
import com.synopsys.integration.executable.ExecutableRunnerException;

@DetectableInfo(name = "Buildroot", language = "various", forge = "Buildroot", accuracy = DetectableAccuracyType.HIGH, requirementsMarkdown = "Files: .confg, Makefile. Executable: make.")
public class BuildrootDetectable extends Detectable {
public static final String CONFIG_FILENAME = ".config";
public static final String MAKEFILE_FILENAME = "Makefile";

private final FileFinder fileFinder;
private final BuildrootExtractor buildrootExtractor;
private final MakeResolver makeResolver;

private ExecutableTarget makeExe;

public BuildrootDetectable(DetectableEnvironment environment, FileFinder fileFinder, BuildrootExtractor buildrootExtractor, MakeResolver makeResolver) {
super(environment);

this.fileFinder = fileFinder;
this.buildrootExtractor = buildrootExtractor;
this.makeResolver = makeResolver;
}

@Override
public DetectableResult applicable() {
Requirements requirements = new Requirements(fileFinder, environment);
requirements.file(CONFIG_FILENAME);
requirements.file(MAKEFILE_FILENAME);
return requirements.result();
}

@Override
public DetectableResult extractable() throws DetectableException {
Requirements requirements = new Requirements(fileFinder, environment);
makeExe = requirements.executable(makeResolver::resolveMake, "make");
return requirements.result();
}

@Override
public Extraction extract(ExtractionEnvironment extractionEnvironment) throws ExecutableRunnerException, MissingExternalIdException, ExecutableFailedException {
return buildrootExtractor.extract(makeExe, environment.getDirectory());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.synopsys.integration.detectable.detectables.buildroot;

import com.synopsys.integration.detectable.detectable.util.EnumListFilter;

public class BuildrootDetectableOptions {
private final EnumListFilter<BuildrootDependencyType> dependencyTypeFilter;

public BuildrootDetectableOptions(EnumListFilter<BuildrootDependencyType> dependencyTypeFilter) {
this.dependencyTypeFilter = dependencyTypeFilter;
}

public EnumListFilter<BuildrootDependencyType> getDependencyTypeFilter() {
return dependencyTypeFilter;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package com.synopsys.integration.detectable.detectables.buildroot;

import java.io.File;
import java.util.Map;

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

import com.google.gson.JsonParseException;
import com.synopsys.integration.bdio.graph.builder.LazyExternalIdDependencyGraphBuilder;
import com.synopsys.integration.bdio.graph.builder.LazyId;
import com.synopsys.integration.bdio.graph.builder.MissingExternalIdException;
import com.synopsys.integration.bdio.model.Forge;
import com.synopsys.integration.bdio.model.dependency.Dependency;
import com.synopsys.integration.bdio.model.dependency.DependencyFactory;
import com.synopsys.integration.bdio.model.externalid.ExternalIdFactory;
import com.synopsys.integration.detectable.ExecutableTarget;
import com.synopsys.integration.detectable.ExecutableUtils;
import com.synopsys.integration.detectable.detectable.codelocation.CodeLocation;
import com.synopsys.integration.detectable.detectable.executable.DetectableExecutableRunner;
import com.synopsys.integration.detectable.detectable.executable.ExecutableFailedException;
import com.synopsys.integration.detectable.detectables.buildroot.model.Parser;
import com.synopsys.integration.detectable.detectables.buildroot.model.ShowInfoComponent;
import com.synopsys.integration.detectable.extraction.Extraction;
import com.synopsys.integration.detectable.util.ToolVersionLogger;
import com.synopsys.integration.executable.ExecutableRunnerException;

public class BuildrootExtractor {
public static final Forge forge = new Forge("/", "buildroot");

private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final ExternalIdFactory externalIdFactory = new ExternalIdFactory();
private final DependencyFactory dependencyFactory = new DependencyFactory(externalIdFactory);
private final Parser parser = new Parser();
private final BuildrootDetectableOptions options;
private final DetectableExecutableRunner executableRunner;
private final ToolVersionLogger toolVersionLogger;

public BuildrootExtractor(BuildrootDetectableOptions options, DetectableExecutableRunner executableRunner, ToolVersionLogger toolVersionLogger) {
this.options = options;
this.executableRunner = executableRunner;
this.toolVersionLogger = toolVersionLogger;
}

public Extraction extract(ExecutableTarget makeExe, File workingDirectory) throws ExecutableRunnerException, MissingExternalIdException, ExecutableFailedException {
// log version of make
toolVersionLogger.log(workingDirectory, makeExe, "-v");

// log version of buildroot
toolVersionLogger.log(workingDirectory, makeExe, "print-version");

String output = executableRunner.executeSuccessfully(ExecutableUtils.createFromTarget(workingDirectory, makeExe, "show-info")).getStandardOutput();

Map<String, ShowInfoComponent> components;
try {
components = parser.parse(output);
} catch (JsonParseException e) {
return Extraction.failure("Unable to parse make show-info output");
}

LazyExternalIdDependencyGraphBuilder graph = new LazyExternalIdDependencyGraphBuilder();

for (ShowInfoComponent component : components.values()) {
String type = component.getType();
if (type.equals("rootfs") || type.equals("host") && options.getDependencyTypeFilter().shouldExclude(BuildrootDependencyType.HOST)) {
logger.trace("Skipping component of type: " + type);
continue;
}
logger.trace("Processing buildroot component: " + component.getName());

LazyId id = makeLazyId(component);

if (component.getReverseDependencies().size() == 0) {
graph.addChildToRoot(id);
} else {
for (String reverseDependency : component.getReverseDependencies()) {
LazyId reverseDependencyId = makeLazyId(components.get(reverseDependency));
graph.addChildWithParent(id, reverseDependencyId);
}
}

Dependency dependency = dependencyFactory.createNameVersionDependency(forge, component.getName(), component.getVersion());
graph.setDependencyInfo(id, component.getName(), component.getVersion(), dependency.getExternalId());
}

return Extraction.success(new CodeLocation(graph.build()));
}

private LazyId makeLazyId(ShowInfoComponent component) {
return LazyId.fromNameAndVersion(component.getName(), component.getVersion());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.synopsys.integration.detectable.detectables.buildroot.model;

import java.lang.reflect.Type;
import java.util.Map;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

public class Parser {

public Map<String, ShowInfoComponent> parse(String showInfoOutput) {
Gson gson = new Gson();
Type type = new TypeToken<Map<String, ShowInfoComponent>>() {}.getType();
return gson.fromJson(showInfoOutput, type);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.synopsys.integration.detectable.detectables.buildroot.model;

import java.util.List;

import com.google.gson.annotations.SerializedName;
import com.synopsys.integration.util.Stringable;

public class ShowInfoComponent extends Stringable {
private final String type;
private final String name;
private final String version;
private final List<String> dependencies;

@SerializedName("reverse_dependencies")
private final List<String> reverseDependencies;

public ShowInfoComponent(String type, String name, String version, List<String> dependencies, List<String> reverseDependencies) {
this.type = type;
this.name = name;
this.version = version;
this.dependencies = dependencies;
this.reverseDependencies = reverseDependencies;
}

public String getType() {
return type;
}

public String getName() {
return name;
}

public String getVersion() {
return version;
}

public List<String> getDependencies() {
return dependencies;
}

public List<String> getReverseDependencies() {
return reverseDependencies;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import com.synopsys.integration.detectable.detectable.executable.resolver.GradleResolver;
import com.synopsys.integration.detectable.detectable.executable.resolver.JavaResolver;
import com.synopsys.integration.detectable.detectable.executable.resolver.LernaResolver;
import com.synopsys.integration.detectable.detectable.executable.resolver.MakeResolver;
import com.synopsys.integration.detectable.detectable.executable.resolver.MavenResolver;
import com.synopsys.integration.detectable.detectable.executable.resolver.NpmResolver;
import com.synopsys.integration.detectable.detectable.executable.resolver.PearResolver;
Expand Down Expand Up @@ -59,6 +60,9 @@
import com.synopsys.integration.detectable.detectables.bitbake.parse.PwdOutputParser;
import com.synopsys.integration.detectable.detectables.bitbake.transform.BitbakeDependencyGraphTransformer;
import com.synopsys.integration.detectable.detectables.bitbake.transform.BitbakeGraphTransformer;
import com.synopsys.integration.detectable.detectables.buildroot.BuildrootDetectable;
import com.synopsys.integration.detectable.detectables.buildroot.BuildrootDetectableOptions;
import com.synopsys.integration.detectable.detectables.buildroot.BuildrootExtractor;
import com.synopsys.integration.detectable.detectables.cargo.CargoExtractor;
import com.synopsys.integration.detectable.detectables.cargo.CargoLockDetectable;
import com.synopsys.integration.detectable.detectables.cargo.parse.CargoDependencyLineParser;
Expand Down Expand Up @@ -347,6 +351,11 @@ public BitbakeDetectable createBitbakeDetectable(DetectableEnvironment environme
return new BitbakeDetectable(environment, fileFinder, bitbakeDetectableOptions, bitbakeExtractor, bashResolver);
}

public BuildrootDetectable createBuildrootDetectable(DetectableEnvironment environment, BuildrootDetectableOptions options, MakeResolver makeResolver) {
BuildrootExtractor buildrootExtractor = new BuildrootExtractor(options, executableRunner, toolVersionLogger);
return new BuildrootDetectable(environment, fileFinder, buildrootExtractor, makeResolver);
}

public CargoLockDetectable createCargoDetectable(DetectableEnvironment environment) {
CargoTomlParser cargoTomlParser = new CargoTomlParser();
CargoDependencyLineParser cargoDependencyLineParser = new CargoDependencyLineParser();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package com.synopsys.integration.detectable.detectables.buildroot.functional;

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.io.IOException;

import org.jetbrains.annotations.NotNull;

import com.synopsys.integration.bdio.model.externalid.ExternalId;
import com.synopsys.integration.bdio.model.externalid.ExternalIdFactory;
import com.synopsys.integration.detectable.Detectable;
import com.synopsys.integration.detectable.DetectableEnvironment;
import com.synopsys.integration.detectable.ExecutableTarget;
import com.synopsys.integration.detectable.detectable.util.EnumListFilter;
import com.synopsys.integration.detectable.detectables.buildroot.BuildrootDependencyType;
import com.synopsys.integration.detectable.detectables.buildroot.BuildrootDetectableOptions;
import com.synopsys.integration.detectable.detectables.buildroot.BuildrootExtractor;
import com.synopsys.integration.detectable.extraction.Extraction;
import com.synopsys.integration.detectable.functional.DetectableFunctionalTest;
import com.synopsys.integration.detectable.util.FunctionalTestFiles;
import com.synopsys.integration.detectable.util.graph.NameVersionGraphAssert;

public class BuildrootDetectableTest extends DetectableFunctionalTest {
public BuildrootDetectableTest() throws IOException {
super("buildroot");
}

@Override
protected void setup() throws IOException {
addFile(".config");
addFile("Makefile");

addExecutableOutput(
createStandardOutput(
FunctionalTestFiles.asString("/buildroot/make-show-info.json")
),
new String[] {
"make",
"show-info"
}
);
}

@Override
public @NotNull Detectable create(@NotNull DetectableEnvironment detectableEnvironment) {
return detectableFactory.createBuildrootDetectable(
detectableEnvironment,
new BuildrootDetectableOptions(
EnumListFilter.fromExcluded(BuildrootDependencyType.HOST)
),
() -> ExecutableTarget.forCommand("make")
);
}

@Override
public void assertExtraction(@NotNull Extraction extraction) {
assertEquals(1, extraction.getCodeLocations().size());

ExternalIdFactory factory = new ExternalIdFactory();
ExternalId busyboxId = factory.createNameVersionExternalId(BuildrootExtractor.forge, "busybox", "1.36.1");
ExternalId gccId = factory.createNameVersionExternalId(BuildrootExtractor.forge, "gcc-final", "12.3.0");

ExternalId glibcId = factory.createNameVersionExternalId(
BuildrootExtractor.forge,
"glibc",
"2.38-44-gd37c2b20a4787463d192b32041c3406c2bd91de0"
);
ExternalId linuxHeadersId = factory.createNameVersionExternalId(BuildrootExtractor.forge, "linux-headers", "6.6.18");

ExternalId libtoolId = factory.createNameVersionExternalId(BuildrootExtractor.forge, "libtool", "2.4.6");


NameVersionGraphAssert graphAssert = new NameVersionGraphAssert(
BuildrootExtractor.forge,
extraction.getCodeLocations().get(0).getDependencyGraph()
);

graphAssert.hasDependency(busyboxId);
graphAssert.hasDependency(gccId);
graphAssert.hasDependency(glibcId);
graphAssert.hasDependency(linuxHeadersId);

graphAssert.hasParentChildRelationship(glibcId, linuxHeadersId);

graphAssert.hasNoDependency(libtoolId);
}

}