use of org.lflang.util.LFCommand in project lingua-franca by lf-lang.
the class GeneratorCommandFactory method createCommand.
/**
* Create a LFCommand instance from a given command, an argument list and a directory.
* <p>
* This will check first if the command can actually be found and executed. If the command is not found, null is
* returned. In addition, an error message will be shown if failOnError is true. Otherwise, a warning will be
* displayed.
*
* @param cmd the command to look up
* @param args a list of arguments
* @param dir the directory to execute the command in. If null, this will default to the CWD
* @param failOnError If true, an error is shown if the command cannot be found. Otherwise, only a warning is
* displayed.
* @return an LFCommand object or null if the command could not be found
* @see LFCommand#get(String, List, boolean, Path)
*/
public LFCommand createCommand(String cmd, List<String> args, Path dir, boolean failOnError) {
assert cmd != null && args != null;
if (dir == null) {
dir = Paths.get("");
}
LFCommand command = LFCommand.get(cmd, args, quiet, dir);
if (command != null) {
command.setEnvironmentVariable("LF_CURRENT_WORKING_DIRECTORY", dir.toString());
command.setEnvironmentVariable("LF_SOURCE_DIRECTORY", fileConfig.srcPath.toString());
command.setEnvironmentVariable("LF_SOURCE_GEN_DIRECTORY", fileConfig.getSrcGenPath().toString());
command.setEnvironmentVariable("LF_BIN_DIRECTORY", fileConfig.binPath.toString());
} else {
final String message = "The command " + cmd + " could not be found in the current working directory or in your PATH. " + "Make sure that your PATH variable includes the directory where " + cmd + " is installed. " + "You can set PATH in ~/.bash_profile on Linux or Mac.";
if (failOnError) {
errorReporter.reportError(message);
} else {
errorReporter.reportWarning(message);
}
}
return command;
}
use of org.lflang.util.LFCommand in project lingua-franca by lf-lang.
the class PythonValidator method getPossibleStrategies.
@Override
protected Collection<ValidationStrategy> getPossibleStrategies() {
return List.of(new ValidationStrategy() {
@Override
public LFCommand getCommand(Path generatedFile) {
return LFCommand.get("python3", List.of("-c", "import compileall; compileall.compile_dir('.', quiet=1)"), true, fileConfig.getSrcGenPkgPath());
}
@Override
public Strategy getErrorReportingStrategy() {
return (a, b, c) -> {
};
}
@Override
public Strategy getOutputReportingStrategy() {
return (String validationOutput, ErrorReporter errorReporter, Map<Path, CodeMap> map) -> {
String[] lines = (validationOutput + "\n\n\n").lines().toArray(String[]::new);
for (int i = 0; i < lines.length - 3; i++) {
if (!tryReportTypical(lines, i)) {
tryReportAlternative(lines, i);
}
}
};
}
/**
* Try to report a typical error message from the Python compiler.
*
* @param lines The lines of output from the compiler.
* @param i The current index at which a message may start. Guaranteed to be less
* than
* {@code lines.length - 3}.
* @return Whether an error message was reported.
*/
private boolean tryReportTypical(String[] lines, int i) {
Matcher main = DIAGNOSTIC_MESSAGE_PATTERN.matcher(lines[i]);
Matcher messageMatcher = MESSAGE.matcher(lines[i + 3]);
String message = messageMatcher.matches() ? messageMatcher.group() : "Syntax Error";
if (main.matches()) {
int line = Integer.parseInt(main.group("line"));
CodeMap map = codeMaps.get(fileConfig.getSrcGenPkgPath().resolve(Path.of(main.group("path"))).normalize());
// Column is just a placeholder.
Position genPosition = Position.fromOneBased(line, Integer.MAX_VALUE);
if (map == null) {
// Undesirable fallback
errorReporter.report(null, DiagnosticSeverity.Error, message, 1);
} else {
for (Path lfFile : map.lfSourcePaths()) {
Position lfPosition = map.adjusted(lfFile, genPosition);
// TODO: We could be more precise than just getting the right line, but the way the output
// is formatted (with leading whitespace possibly trimmed) does not make it easy.
errorReporter.report(lfFile, DiagnosticSeverity.Error, message, lfPosition.getOneBasedLine());
}
}
return true;
}
return false;
}
/**
* Try to report an alternative error message from the Python compiler.
*
* @param lines The lines of output from the compiler.
* @param i The current index at which a message may start.
*/
private void tryReportAlternative(String[] lines, int i) {
Matcher main = ALT_DIAGNOSTIC_MESSAGE_PATTERN.matcher(lines[i]);
if (main.matches()) {
int line = Integer.parseInt(main.group("line"));
Iterable<CodeMap> relevantMaps = codeMaps.keySet().stream().filter(p -> main.group().contains(p.getFileName().toString())).map(codeMaps::get)::iterator;
for (CodeMap map : relevantMaps) {
// There should almost always be exactly one of these
for (Path lfFile : map.lfSourcePaths()) {
errorReporter.report(lfFile, DiagnosticSeverity.Error, main.group().replace("*** ", "").replace("Sorry: ", ""), map.adjusted(lfFile, Position.fromOneBased(line, 1)).getOneBasedLine());
}
}
}
}
@Override
public boolean isFullBatch() {
return true;
}
@Override
public int getPriority() {
return 0;
}
}, new ValidationStrategy() {
@Override
public LFCommand getCommand(Path generatedFile) {
return LFCommand.get("pylint", List.of("--output-format=json", generatedFile.getFileName().toString()), true, fileConfig.getSrcGenPath());
}
@Override
public Strategy getErrorReportingStrategy() {
return (a, b, c) -> {
};
}
@Override
public Strategy getOutputReportingStrategy() {
return (validationOutput, errorReporter, codeMaps) -> {
try {
for (PylintMessage message : mapper.readValue(validationOutput, PylintMessage[].class)) {
if (shouldIgnore(message))
continue;
CodeMap map = codeMaps.get(message.getPath(fileConfig.getSrcGenPath()));
if (map != null) {
for (Path lfFile : map.lfSourcePaths()) {
Function<Position, Position> adjust = p -> map.adjusted(lfFile, p);
String humanMessage = DiagnosticReporting.messageOf(message.message, message.getPath(fileConfig.getSrcGenPath()), message.getStart());
Position lfStart = adjust.apply(message.getStart());
Position lfEnd = adjust.apply(message.getEnd());
bestEffortReport(errorReporter, adjust, lfStart, lfEnd, lfFile, message.getSeverity(), humanMessage);
}
}
}
} catch (JsonProcessingException e) {
errorReporter.reportWarning("Failed to parse linter output. The Lingua Franca code generator is tested with Pylint " + "version 2.12.2. Consider updating PyLint if you have an older version.");
e.printStackTrace();
}
};
}
/**
* Return whether the given message should be ignored.
* @param message A Pylint message that is a candidate to be reported.
* @return whether {@code message} should be reported.
*/
private boolean shouldIgnore(PylintMessage message) {
// Code generation does not preserve whitespace, so this check is unreliable.
if (message.symbol.equals("trailing-whitespace") || message.symbol.equals("line-too-long"))
return true;
// This filters out Pylint messages concerning missing members in types defined by protocol buffers.
// FIXME: Make this unnecessary, perhaps using https://github.com/nelfin/pylint-protobuf.
Matcher matcher = PylintNoNamePattern.matcher(message.message);
return message.symbol.equals("no-member") && matcher.matches() && protoNames.contains(matcher.group("name"));
}
/**
* Make a best-effort attempt to place the diagnostic on the correct line.
*/
private void bestEffortReport(ErrorReporter errorReporter, Function<Position, Position> adjust, Position lfStart, Position lfEnd, Path file, DiagnosticSeverity severity, String humanMessage) {
if (!lfEnd.equals(Position.ORIGIN) && !lfStart.equals(Position.ORIGIN)) {
// Ideal case
errorReporter.report(file, severity, humanMessage, lfStart, lfEnd);
} else {
// Fallback: Try to report on the correct line, or failing that, just line 1.
if (lfStart.equals(Position.ORIGIN))
lfStart = adjust.apply(Position.fromZeroBased(lfStart.getZeroBasedLine(), Integer.MAX_VALUE));
// FIXME: It might be better to improve style of generated code instead of quietly returning here.
if (lfStart.equals(Position.ORIGIN) && severity != DiagnosticSeverity.Error)
return;
errorReporter.report(file, severity, humanMessage, lfStart.getOneBasedLine());
}
}
@Override
public boolean isFullBatch() {
return false;
}
@Override
public int getPriority() {
return 1;
}
});
}
use of org.lflang.util.LFCommand in project lingua-franca by lf-lang.
the class CCmakeCompiler method compileCmakeCommand.
/**
* Return a command to compile the specified C file using CMake.
* This produces a C-specific compile command.
*
* @param fileToCompile The C filename without the .c extension.
* @param noBinary If true, the compiler will create a .o output instead of a binary.
* If false, the compile command will produce a binary.
*/
public LFCommand compileCmakeCommand(String fileToCompile, boolean noBinary) {
// Set the build directory to be "build"
Path buildPath = fileConfig.getSrcGenPath().resolve("build");
List<String> arguments = new ArrayList<String>();
arguments.addAll(List.of("-DCMAKE_INSTALL_PREFIX=" + FileUtil.toUnixString(fileConfig.getOutPath()), "-DCMAKE_INSTALL_BINDIR=" + FileUtil.toUnixString(fileConfig.getOutPath().relativize(fileConfig.binPath)), FileUtil.toUnixString(fileConfig.getSrcGenPath())));
if (GeneratorUtils.isHostWindows()) {
arguments.add("-DCMAKE_SYSTEM_VERSION=\"10.0\"");
}
LFCommand command = commandFactory.createCommand("cmake", arguments, buildPath);
if (command == null) {
errorReporter.reportError("The C/CCpp target requires CMAKE >= 3.5 to compile the generated code. " + "Auto-compiling can be disabled using the \"no-compile: true\" target property.");
}
return command;
}
use of org.lflang.util.LFCommand in project lingua-franca by lf-lang.
the class CCompiler method compileCCommand.
/**
* Return a command to compile the specified C file using a native compiler
* (generally gcc unless overridden by the user).
* This produces a C specific compile command.
*
* @param fileToCompile The C filename without the .c extension.
* @param noBinary If true, the compiler will create a .o output instead of a binary.
* If false, the compile command will produce a binary.
*/
public LFCommand compileCCommand(String fileToCompile, boolean noBinary) {
String cFilename = getTargetFileName(fileToCompile, CppMode);
Path relativeSrcPath = fileConfig.getOutPath().relativize(fileConfig.getSrcGenPath().resolve(Paths.get(cFilename)));
Path relativeBinPath = fileConfig.getOutPath().relativize(fileConfig.binPath.resolve(Paths.get(fileToCompile)));
// NOTE: we assume that any C compiler takes Unix paths as arguments.
String relSrcPathString = FileUtil.toUnixString(relativeSrcPath);
String relBinPathString = FileUtil.toUnixString(relativeBinPath);
// If there is no main reactor, then generate a .o file not an executable.
if (noBinary) {
relBinPathString += ".o";
}
ArrayList<String> compileArgs = new ArrayList<String>();
compileArgs.add(relSrcPathString);
for (String file : targetConfig.compileAdditionalSources) {
var relativePath = fileConfig.getOutPath().relativize(fileConfig.getSrcGenPath().resolve(Paths.get(file)));
compileArgs.add(FileUtil.toUnixString(relativePath));
}
compileArgs.addAll(targetConfig.compileLibraries);
// Add compile definitions
targetConfig.compileDefinitions.forEach((key, value) -> {
compileArgs.add("-D" + key + "=" + value);
});
// If threaded computation is requested, add a -pthread option.
if (targetConfig.threads != 0 || targetConfig.tracing != null) {
compileArgs.add("-pthread");
// If the LF program itself is threaded or if tracing is enabled, we need to define
// NUMBER_OF_WORKERS so that platform-specific C files will contain the appropriate functions
compileArgs.add("-DNUMBER_OF_WORKERS=" + targetConfig.threads);
}
// Finally, add the compiler flags in target parameters (if any)
compileArgs.addAll(targetConfig.compilerFlags);
// using a target property or Args line flag.
if (!compileArgs.contains("-o")) {
compileArgs.add("-o");
compileArgs.add(relBinPathString);
}
// Then again, I think this only makes sense when we can do linking.
if (noBinary) {
// FIXME: revisit
compileArgs.add("-c");
}
LFCommand command = commandFactory.createCommand(targetConfig.compiler, compileArgs, fileConfig.getOutPath());
if (command == null) {
errorReporter.reportError("The C/CCpp target requires GCC >= 7 to compile the generated code. " + "Auto-compiling can be disabled using the \"no-compile: true\" target property.");
}
return command;
}
use of org.lflang.util.LFCommand in project lingua-franca by lf-lang.
the class CCompiler method runCCompiler.
/**
* Run the C compiler by directly invoking a C compiler (gcc by default).
*
* @param file The source file to compile without the .c extension.
* @param noBinary If true, the compiler will create a .o output instead of a binary.
* If false, the compile command will produce a binary.
* @param generator An instance of GenratorBase, only used to report error line numbers
* in the Eclipse IDE.
*
* @return true if compilation succeeds, false otherwise.
*/
public boolean runCCompiler(String file, boolean noBinary, GeneratorBase generator, LFGeneratorContext context) throws IOException {
if (noBinary && context.getMode() == LFGeneratorContext.Mode.STANDALONE) {
errorReporter.reportError("Did not output executable; no main reactor found.");
}
LFCommand compile = compileCCommand(file, noBinary);
if (compile == null) {
return false;
}
int returnCode = compile.run(context.getCancelIndicator());
if (returnCode != 0 && context.getMode() == LFGeneratorContext.Mode.STANDALONE) {
errorReporter.reportError(targetConfig.compiler + " returns error code " + returnCode);
}
// But we still want to mark the IDE.
if (compile.getErrors().toString().length() > 0 && context.getMode() != LFGeneratorContext.Mode.STANDALONE) {
generator.reportCommandErrors(compile.getErrors().toString());
}
if (returnCode == 0 && compile.getErrors().toString().length() == 0) {
System.out.println("SUCCESS: Compiling generated code for " + fileConfig.name + " finished with no errors.");
}
return (returnCode == 0);
}
Aggregations