Search in sources :

Example 1 with LFCommand

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;
}
Also used : LFCommand(org.lflang.util.LFCommand)

Example 2 with LFCommand

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;
        }
    });
}
Also used : Path(java.nio.file.Path) JsonProperty(com.fasterxml.jackson.annotation.JsonProperty) Strategy(org.lflang.generator.DiagnosticReporting.Strategy) ErrorReporter(org.lflang.ErrorReporter) Function(java.util.function.Function) DeserializationFeature(com.fasterxml.jackson.databind.DeserializationFeature) Matcher(java.util.regex.Matcher) Map(java.util.Map) Path(java.nio.file.Path) DiagnosticSeverity(org.eclipse.lsp4j.DiagnosticSeverity) Position(org.lflang.generator.Position) ImmutableSet(com.google.common.collect.ImmutableSet) ImmutableMap(com.google.common.collect.ImmutableMap) Collection(java.util.Collection) Validator(org.lflang.generator.Validator) ObjectMapper(com.fasterxml.jackson.databind.ObjectMapper) Set(java.util.Set) Include(com.fasterxml.jackson.annotation.JsonInclude.Include) JsonProcessingException(com.fasterxml.jackson.core.JsonProcessingException) LFCommand(org.lflang.util.LFCommand) DiagnosticReporting(org.lflang.generator.DiagnosticReporting) List(java.util.List) CodeMap(org.lflang.generator.CodeMap) ValidationStrategy(org.lflang.generator.ValidationStrategy) JsonInclude(com.fasterxml.jackson.annotation.JsonInclude) Pattern(java.util.regex.Pattern) FileConfig(org.lflang.FileConfig) Matcher(java.util.regex.Matcher) Position(org.lflang.generator.Position) ValidationStrategy(org.lflang.generator.ValidationStrategy) LFCommand(org.lflang.util.LFCommand) ErrorReporter(org.lflang.ErrorReporter) CodeMap(org.lflang.generator.CodeMap) DiagnosticSeverity(org.eclipse.lsp4j.DiagnosticSeverity) Strategy(org.lflang.generator.DiagnosticReporting.Strategy) ValidationStrategy(org.lflang.generator.ValidationStrategy) Map(java.util.Map) ImmutableMap(com.google.common.collect.ImmutableMap) CodeMap(org.lflang.generator.CodeMap) JsonProcessingException(com.fasterxml.jackson.core.JsonProcessingException)

Example 3 with LFCommand

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;
}
Also used : Path(java.nio.file.Path) LFCommand(org.lflang.util.LFCommand) ArrayList(java.util.ArrayList)

Example 4 with LFCommand

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;
}
Also used : Path(java.nio.file.Path) LFCommand(org.lflang.util.LFCommand) ArrayList(java.util.ArrayList)

Example 5 with LFCommand

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);
}
Also used : LFCommand(org.lflang.util.LFCommand)

Aggregations

LFCommand (org.lflang.util.LFCommand)12 Path (java.nio.file.Path)7 ArrayList (java.util.ArrayList)4 ImmutableMap (com.google.common.collect.ImmutableMap)2 Collection (java.util.Collection)2 List (java.util.List)2 Map (java.util.Map)2 ErrorReporter (org.lflang.ErrorReporter)2 JsonInclude (com.fasterxml.jackson.annotation.JsonInclude)1 Include (com.fasterxml.jackson.annotation.JsonInclude.Include)1 JsonProperty (com.fasterxml.jackson.annotation.JsonProperty)1 JsonProcessingException (com.fasterxml.jackson.core.JsonProcessingException)1 DeserializationFeature (com.fasterxml.jackson.databind.DeserializationFeature)1 ObjectMapper (com.fasterxml.jackson.databind.ObjectMapper)1 ImmutableSet (com.google.common.collect.ImmutableSet)1 Comparator (java.util.Comparator)1 Set (java.util.Set)1 Callable (java.util.concurrent.Callable)1 CompletableFuture (java.util.concurrent.CompletableFuture)1 ExecutionException (java.util.concurrent.ExecutionException)1