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

Bugfixes in handling of target properties across imports #2232

Merged
merged 16 commits into from
Jun 30, 2024
Merged
Show file tree
Hide file tree
Changes from 15 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
@@ -1,20 +1,10 @@
package org.lflang.federated.generator;

import static org.lflang.ast.ASTUtils.convertToEmptyListIfNull;

import java.nio.file.Path;
import java.util.List;
import java.util.Objects;
import org.eclipse.emf.ecore.resource.Resource;
import org.lflang.MessageReporter;
import org.lflang.ast.ASTUtils;
import org.lflang.generator.GeneratorUtils;
import org.lflang.generator.LFGeneratorContext;
import org.lflang.lf.KeyValuePair;
import org.lflang.target.Target;
import org.lflang.target.TargetConfig;
import org.lflang.target.property.FileListProperty;
import org.lflang.util.FileUtil;

/**
* Subclass of TargetConfig with a specialized constructor for creating configurations for
Expand Down Expand Up @@ -43,7 +33,7 @@ public FederateTargetConfig(LFGeneratorContext context, Resource federateResourc
load(federationResource, reporter);

// Load properties from the federate file
mergeImportedConfig(federateResource, federationResource, reporter);
mergeImportedConfig(federateResource, federationResource, p -> p.loadFromFederate(), reporter);

// Load properties from the generator context
load(context.getArgs(), reporter);
Expand All @@ -52,66 +42,4 @@ public FederateTargetConfig(LFGeneratorContext context, Resource federateResourc

this.validate(reporter);
}

/**
* If the federate that target configuration applies to is imported, merge target properties
* declared in the file that it was imported from.
*
* @param federateResource The resource where the class of the federate is specified.
* @param mainResource The resource in which the federation (i.e., main reactor) is specified.
* @param messageReporter An error reporter to use when problems are encountered.
*/
private void mergeImportedConfig(
Resource federateResource, Resource mainResource, MessageReporter messageReporter) {
// If the federate is imported, then update the configuration based on target properties
// in the imported file.
if (!federateResource.equals(mainResource)) {
var importedTargetDecl = GeneratorUtils.findTargetDecl(federateResource);
var targetProperties = importedTargetDecl.getConfig();
if (targetProperties != null) {
// Merge properties
update(
this,
convertToEmptyListIfNull(targetProperties.getPairs()),
getRelativePath(mainResource, federateResource),
messageReporter);
}
}
}

private Path getRelativePath(Resource source, Resource target) {
return FileUtil.toPath(source.getURI())
.getParent()
.relativize(FileUtil.toPath(target.getURI()).getParent());
}

/**
* Update the given configuration using the given target properties.
*
* @param config The configuration object to update.
* @param pairs AST node that holds all the target properties.
* @param relativePath The path from the main resource to the resource from which the new
* properties originate.
*/
public void update(
TargetConfig config, List<KeyValuePair> pairs, Path relativePath, MessageReporter err) {
pairs.forEach(
pair -> {
var p = config.forName(pair.getName());
if (p.isPresent()) {
var value = pair.getValue();
var property = p.get();
if (property instanceof FileListProperty fileListProperty) {
var files =
ASTUtils.elementToListOfStrings(value).stream()
.map(relativePath::resolve) // assume all paths are relative
.map(Objects::toString)
.toList();
fileListProperty.update(config, files);
} else {
p.get().update(this, pair, err);
}
}
});
}
}
70 changes: 29 additions & 41 deletions core/src/main/java/org/lflang/generator/GeneratorBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
Expand Down Expand Up @@ -80,18 +79,12 @@
*/
public abstract class GeneratorBase extends AbstractLFValidator {

////////////////////////////////////////////
//// Public fields.

/** The main (top-level) reactor instance. */
public ReactorInstance main;

/** An error reporter for reporting any errors or warnings during the code generation */
public MessageReporter messageReporter;

////////////////////////////////////////////
//// Protected fields.

/** The current target configuration. */
protected final TargetConfig targetConfig;

Expand Down Expand Up @@ -125,9 +118,6 @@ public Instantiation getMainDef() {
*/
protected List<Reactor> reactors = new ArrayList<>();

/** The set of resources referenced reactor classes reside in. */
protected Set<LFResource> resources = new LinkedHashSet<>(); // FIXME: Why do we need this?

/**
* Graph that tracks dependencies between instantiations. This is a graph where each node is a
* Reactor (not a ReactorInstance) and an arc from Reactor A to Reactor B means that B contains an
Expand All @@ -147,9 +137,6 @@ public Instantiation getMainDef() {
/** Indicates whether the program has any watchdogs. This is used to check for support. */
public boolean hasWatchdogs = false;

// //////////////////////////////////////////
// // Private fields.

/** A list ot AST transformations to apply before code generation */
private final List<AstTransformation> astTransformations = new ArrayList<>();

Expand All @@ -170,8 +157,26 @@ protected void registerTransformation(AstTransformation transformation) {
astTransformations.add(transformation);
}

// //////////////////////////////////////////
// // Code generation functions to override for a concrete code generator.
/**
* If the given reactor is defined in another file, process its target properties so that they are
* reflected in the target configuration.
*/
private void loadTargetProperties(Resource resource) {
var mainFileConfig = this.context.getFileConfig();
if (resource != mainFileConfig.resource) {
this.context
.getTargetConfig()
.mergeImportedConfig(
LFGenerator.createFileConfig(
resource,
mainFileConfig.getSrcGenBasePath(),
mainFileConfig.useHierarchicalBin)
.resource,
mainFileConfig.resource,
p -> p.loadFromImport(),
this.messageReporter);
}
}

/**
* Generate code from the Lingua Franca model contained by the specified resource.
Expand Down Expand Up @@ -218,46 +223,29 @@ public void doGenerate(Resource resource, LFGeneratorContext context) {
}
}

// Process target files. Copy each of them into the src-gen dir.
// FIXME: Should we do this here? This doesn't make sense for federates the way it is
// done here.
copyUserFiles(this.targetConfig, context.getFileConfig());

// Collect reactors and create an instantiation graph.
// These are needed to figure out which resources we need
// to validate, which happens in setResources().
setReactorsAndInstantiationGraph(context.getMode());

List<Resource> allResources = GeneratorUtils.getResources(reactors);
resources.addAll(
allResources
.stream() // FIXME: This filter reproduces the behavior of the method it replaces. But
// why must it be so complicated? Why are we worried about weird corner cases
// like this?
.filter(
it ->
!Objects.equal(it, context.getFileConfig().resource)
|| mainDef != null && it == mainDef.getReactorClass().eResource())
.map(
it ->
GeneratorUtils.getLFResource(
it, context.getFileConfig().getSrcGenBasePath(), context, messageReporter))
.toList());
Set<Resource> allResources = GeneratorUtils.getResources(reactors);

GeneratorUtils.accommodatePhysicalActionsIfPresent(
allResources,
getTarget().setsKeepAliveOptionAutomatically(),
targetConfig,
messageReporter);
// FIXME: Should the GeneratorBase pull in {@code files} from imported
// resources?

// Load target properties for all resources.
allResources.forEach(r -> loadTargetProperties(r));

for (AstTransformation transformation : astTransformations) {
transformation.applyTransformation(reactors);
}

// Transform connections that reside in mutually exclusive modes and are otherwise conflicting
// This should be done before creating the instantiation graph
transformConflictingConnectionsInModalReactors();
transformConflictingConnectionsInModalReactors(allResources);

// Invoke these functions a second time because transformations
// may have introduced new reactors!
Expand Down Expand Up @@ -426,9 +414,9 @@ protected void checkWatchdogSupport(boolean isSupported) {
* Finds and transforms connections into forwarding reactions iff the connections have the same
* destination as other connections or reaction in mutually exclusive modes.
*/
private void transformConflictingConnectionsInModalReactors() {
for (LFResource r : resources) {
var transform = ASTUtils.findConflictingConnectionsInModalReactors(r.eResource);
private void transformConflictingConnectionsInModalReactors(Set<Resource> resources) {
for (Resource r : resources) {
var transform = ASTUtils.findConflictingConnectionsInModalReactors(r);
if (!transform.isEmpty()) {
var factory = LfFactory.eINSTANCE;
for (Connection connection : transform) {
Expand Down
64 changes: 21 additions & 43 deletions core/src/main/java/org/lflang/generator/GeneratorUtils.java
Original file line number Diff line number Diff line change
@@ -1,24 +1,19 @@
package org.lflang.generator;

import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.LinkedHashSet;
import java.util.Set;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.xtext.xbase.lib.IteratorExtensions;
import org.lflang.FileConfig;
import org.lflang.MessageReporter;
import org.lflang.ast.ASTUtils;
import org.lflang.generator.LFGeneratorContext.Mode;
import org.lflang.lf.Action;
import org.lflang.lf.ActionOrigin;
import org.lflang.lf.ImportedReactor;
import org.lflang.lf.Instantiation;
import org.lflang.lf.KeyValuePair;
import org.lflang.lf.KeyValuePairs;
import org.lflang.lf.Reactor;
import org.lflang.lf.TargetDecl;
import org.lflang.target.TargetConfig;
Expand All @@ -45,7 +40,7 @@ public static TargetDecl findTargetDecl(Resource resource) {
* targetConfig}. This is a helper function for setTargetConfig. It should not be used elsewhere.
*/
public static void accommodatePhysicalActionsIfPresent(
List<Resource> resources,
Set<Resource> resources,
boolean setsKeepAliveOptionAutomatically,
TargetConfig targetConfig,
MessageReporter messageReporter) {
Expand Down Expand Up @@ -84,50 +79,33 @@ public static <T> Iterable<T> findAll(Resource resource, Class<T> nodeType) {
}

/**
* Return the resources that provide the given reactors.
* Return the resources that provide the given reactors and their ancestors.
*
* @param reactors The reactors for which to find containing resources.
* @return the resources that provide the given reactors.
*/
public static List<Resource> getResources(Iterable<Reactor> reactors) {
HashSet<Resource> visited = new HashSet<>();
List<Resource> resources = new ArrayList<>();
public static Set<Resource> getResources(Iterable<Reactor> reactors) {
Set<Resource> visited = new LinkedHashSet<>();
for (Reactor r : reactors) {
Resource resource = r.eResource();
if (!visited.contains(resource)) {
visited.add(resource);
resources.add(resource);
if (!visited.contains(r.eResource())) {
addInheritedResources(r, visited);
}
}
return resources;
return visited;
}

/**
* Return the {@code LFResource} representation of the given resource.
*
* @param resource The {@code Resource} to be represented as an {@code LFResource}
* @param srcGenBasePath The root directory for any generated sources associated with the
* resource.
* @param context The generator invocation context.
* @param messageReporter An error message acceptor.
* @return the {@code LFResource} representation of the given resource.
*/
public static LFResource getLFResource(
Resource resource,
Path srcGenBasePath,
LFGeneratorContext context,
MessageReporter messageReporter) {
var target = ASTUtils.targetDecl(resource);
KeyValuePairs config = target.getConfig();
var targetConfig = new TargetConfig(resource, context.getArgs(), messageReporter);
if (config != null) {
List<KeyValuePair> pairs = config.getPairs();
targetConfig.load(pairs != null ? pairs : List.of(), messageReporter);
/** Collect all resources associated with reactor through class inheritance. */
private static void addInheritedResources(Reactor reactor, Set<Resource> resources) {
resources.add(reactor.eResource());
for (var s : reactor.getSuperClasses()) {
if (!resources.contains(s)) {
if (s instanceof ImportedReactor i) {
addInheritedResources(i.getReactorClass(), resources);
} else if (s instanceof Reactor r) {
addInheritedResources(r, resources);
}
}
}
FileConfig fc =
LFGenerator.createFileConfig(
resource, srcGenBasePath, context.getFileConfig().useHierarchicalBin);
return new LFResource(resource, fc, targetConfig);
}

/**
Expand Down
47 changes: 0 additions & 47 deletions core/src/main/java/org/lflang/generator/LFResource.java

This file was deleted.

Loading
Loading