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

Docker support for C++ target #2322

Merged
merged 14 commits into from
Jun 19, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -233,4 +233,12 @@ public static DockerGenerator dockerGeneratorFactory(LFGeneratorContext context)
throw new IllegalArgumentException("No Docker support for " + target + " yet.");
};
}

/**
* Convert an argument list, starting with the command to execute, into a string that can be
* executed by a POSIX-compliant shell.
*/
public static String argListToCommand(List<String> args) {
return args.stream().map(it -> "\"" + it + "\"").collect(Collectors.joining(" "));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@ protected String generateCopyOfExecutable() {
return String.join(
"\n",
super.generateCopyOfExecutable(),
"COPY --from=builder /lingua-franca/%s/src-gen ./src-gen"
.formatted(lfModuleName, lfModuleName, lfModuleName));
"COPY --from=builder /lingua-franca/%s/src-gen ./src-gen".formatted(lfModuleName));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* Generates the docker file related code for the Typescript target.
*
* @author Hou Seng Wong
* @author Marten Lohstroh
*/
public class TSDockerGenerator extends DockerGenerator {

Expand Down
1 change: 1 addition & 0 deletions core/src/main/java/org/lflang/target/Target.java
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,7 @@ public void initialize(TargetConfig config) {
BuildTypeProperty.INSTANCE,
CmakeIncludeProperty.INSTANCE,
CompilerProperty.INSTANCE,
DockerProperty.INSTANCE,
ExportDependencyGraphProperty.INSTANCE,
ExportToYamlProperty.INSTANCE,
ExternalRuntimePathProperty.INSTANCE,
Expand Down
56 changes: 45 additions & 11 deletions core/src/main/kotlin/org/lflang/generator/cpp/CppGenerator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,16 @@
package org.lflang.generator.cpp

import org.eclipse.emf.ecore.resource.Resource
import org.lflang.target.Target
import org.lflang.generator.*
import org.lflang.generator.GeneratorUtils.canGenerate
import org.lflang.generator.LFGeneratorContext.Mode
import org.lflang.generator.docker.DockerGenerator
import org.lflang.isGeneric
import org.lflang.scoping.LFGlobalScopeProvider
import org.lflang.target.Target
import org.lflang.target.property.*
import org.lflang.util.FileUtil
import java.io.IOException
import java.nio.file.Files
import java.nio.file.Path

Expand All @@ -59,6 +60,7 @@ class CppGenerator(
const val MINIMUM_CMAKE_VERSION = "3.5"

const val CPP_VERSION = "20"

}

override fun doGenerate(resource: Resource, context: LFGeneratorContext) {
Expand All @@ -67,8 +69,7 @@ class CppGenerator(
if (!canGenerate(errorsOccurred(), mainDef, messageReporter, context)) return

// create a platform-specific generator
val platformGenerator: CppPlatformGenerator =
if (targetConfig.get(Ros2Property.INSTANCE)) CppRos2Generator(this) else CppStandaloneGenerator(this)
val platformGenerator: CppPlatformGenerator = getPlatformGenerator()

// generate all core files
generateFiles(platformGenerator.srcGenPath, getAllImportedResources(resource))
Expand All @@ -83,7 +84,6 @@ class CppGenerator(
context.reportProgress(
"Code generation complete. Validating generated code...", IntegratedBuilder.GENERATED_PERCENT_PROGRESS
)

if (platformGenerator.doCompile(context)) {
CppValidator(fileConfig, messageReporter, codeMaps).doValidate(context)
context.finish(GeneratorResult.GENERATED_NO_EXECUTABLE.apply(context, codeMaps))
Expand All @@ -94,14 +94,48 @@ class CppGenerator(
context.reportProgress(
"Code generation complete. Compiling...", IntegratedBuilder.GENERATED_PERCENT_PROGRESS
)
if (platformGenerator.doCompile(context)) {
context.finish(GeneratorResult.Status.COMPILED, codeMaps)
if (targetConfig.get(DockerProperty.INSTANCE).enabled) {
copySrcGenBaseDirIntoDockerDir()
buildUsingDocker()
} else {
context.unsuccessfulFinish()
if (platformGenerator.doCompile(context)) {
context.finish(GeneratorResult.Status.COMPILED, codeMaps)
} else {
context.unsuccessfulFinish()
}
}
}
}

/**
* Copy the contents of the entire src-gen directory to a nested src-gen directory next to the generated Dockerfile.
*/
private fun copySrcGenBaseDirIntoDockerDir() {
lhstrh marked this conversation as resolved.
Show resolved Hide resolved
FileUtil.deleteDirectory(context.fileConfig.srcGenPath.resolve("src-gen"))
try {
// We need to copy in two steps via a temporary directory, as the target directory
// is located within the source directory. Without the temporary directory, copying
// fails as we modify the source while writing the target.
val tempDir = Files.createTempDirectory(context.fileConfig.outPath, "src-gen-directory")
try {
FileUtil.copyDirectoryContents(context.fileConfig.srcGenBasePath, tempDir, false)
FileUtil.copyDirectoryContents(tempDir, context.fileConfig.srcGenPath.resolve("src-gen"), false)
} catch (e: IOException) {
context.errorReporter.nowhere()
.error("Failed to copy sources to make them accessible to Docker: " + if (e.message == null) "No cause given" else e.message)
e.printStackTrace()
} finally {
FileUtil.deleteDirectory(tempDir)
}
if (errorsOccurred()) {
return
}
} catch (e: IOException) {
context.errorReporter.nowhere().error("Failed to create temporary directory.")
e.printStackTrace()
}
}

private fun fetchReactorCpp(version: String) {
val libPath = fileConfig.srcGenBasePath.resolve("reactor-cpp-$version")
// abort if the directory already exists
Expand Down Expand Up @@ -184,11 +218,11 @@ class CppGenerator(
}
}

private fun getPlatformGenerator() = if (targetConfig.get(Ros2Property.INSTANCE)) CppRos2Generator(this) else CppStandaloneGenerator(this)

override fun getTarget() = Target.CPP

override fun getTargetTypes(): TargetTypes = CppTypes
override fun getDockerGenerator(context: LFGeneratorContext?): DockerGenerator {
TODO("Not yet implemented")
}
}

override fun getDockerGenerator(context: LFGeneratorContext?): DockerGenerator = getPlatformGenerator().getDockerGenerator(context)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import org.lflang.MessageReporter
import org.lflang.target.TargetConfig
import org.lflang.generator.GeneratorCommandFactory
import org.lflang.generator.LFGeneratorContext
import org.lflang.generator.docker.DockerGenerator
import org.lflang.target.property.BuildTypeProperty
import org.lflang.target.property.LoggingProperty
import org.lflang.target.property.NoRuntimeValidationProperty
Expand Down Expand Up @@ -37,4 +38,6 @@ abstract class CppPlatformGenerator(protected val generator: CppGenerator) {
"-DREACTOR_CPP_LOG_LEVEL=${targetConfig.get(LoggingProperty.INSTANCE).severity}",
"-DLF_SRC_PKG_PATH=${fileConfig.srcPkgPath}",
)

abstract fun getDockerGenerator(context: LFGeneratorContext?): DockerGenerator
}
67 changes: 55 additions & 12 deletions core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Generator.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package org.lflang.generator.cpp

import org.lflang.generator.LFGeneratorContext
import org.lflang.generator.docker.DockerGenerator
import org.lflang.target.property.DockerProperty
import org.lflang.util.FileUtil
import java.nio.file.Path

Expand All @@ -12,6 +14,10 @@ class CppRos2Generator(generator: CppGenerator) : CppPlatformGenerator(generator
private val nodeGenerator = CppRos2NodeGenerator(mainReactor, targetConfig, fileConfig);
private val packageGenerator = CppRos2PackageGenerator(generator, nodeGenerator.nodeName)

companion object {
const val DEFAULT_BASE_IMAGE: String = "ros:rolling-ros-base"
}

override fun generatePlatformFiles() {
FileUtil.writeToFile(
nodeGenerator.generateHeader(),
Expand All @@ -30,7 +36,11 @@ class CppRos2Generator(generator: CppGenerator) : CppPlatformGenerator(generator
packagePath.resolve("CMakeLists.txt"),
true
)
val scriptPath = fileConfig.binPath.resolve(fileConfig.name);
val scriptPath =
if (targetConfig.get(DockerProperty.INSTANCE).enabled)
fileConfig.srcGenPath.resolve("bin").resolve(fileConfig.name)
else
fileConfig.binPath.resolve(fileConfig.name)
FileUtil.writeToFile(packageGenerator.generateBinScript(), scriptPath)
scriptPath.toFile().setExecutable(true);
}
Expand All @@ -46,24 +56,57 @@ class CppRos2Generator(generator: CppGenerator) : CppPlatformGenerator(generator
)
return false
}

val colconCommand = commandFactory.createCommand(
"colcon", listOf(
"colcon", colconArgs(), fileConfig.outPath)
val returnCode = colconCommand?.run(context.cancelIndicator)
if (returnCode != 0 && !messageReporter.errorsOccurred) {
// If errors occurred but none were reported, then the following message is the best we can do.
messageReporter.nowhere().error("colcon failed with error code $returnCode")
}

return !messageReporter.errorsOccurred
}

private fun colconArgs(): List<String> {
return listOf(
"build",
"--packages-select",
fileConfig.name,
packageGenerator.reactorCppName,
"--cmake-args",
"-DLF_REACTOR_CPP_SUFFIX=${packageGenerator.reactorCppSuffix}",
) + cmakeArgs,
fileConfig.outPath
)
val returnCode = colconCommand?.run(context.cancelIndicator);
if (returnCode != 0 && !messageReporter.errorsOccurred) {
// If errors occurred but none were reported, then the following message is the best we can do.
messageReporter.nowhere().error("colcon failed with error code $returnCode")
}
) + cmakeArgs
}

return !messageReporter.errorsOccurred
inner class CppDockerGenerator(context: LFGeneratorContext?) : DockerGenerator(context) {
override fun generateCopyForSources() =
"""
COPY src-gen src-gen
COPY bin bin
""".trimIndent()

override fun defaultImage(): String = DEFAULT_BASE_IMAGE

override fun generateRunForInstallingDeps(): String = ""

override fun defaultEntryPoint(): List<String> = listOf("./bin/" + fileConfig.name)

override fun generateCopyOfExecutable(): String =
"""
${super.generateCopyOfExecutable()}
COPY --from=builder lingua-franca/${fileConfig.name}/install install
""".trimIndent()

override fun defaultBuildCommands(): List<String> {
val commands = listOf(
listOf(".", "/opt/ros/rolling/setup.sh"),
listOf("mkdir", "-p", "build"),
listOf("colcon") + colconArgs(),
)
return commands.map { argListToCommand(it) }
}
}

override fun getDockerGenerator(context: LFGeneratorContext?): DockerGenerator = CppDockerGenerator(context)

}
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ class CppRos2PackageGenerator(generator: CppGenerator, private val nodeName: Str
return """
|#!/bin/bash
|script_dir="$S(dirname -- "$S(readlink -f -- "${S}0")")"
|source "$S{script_dir}/$relPath/install/setup.sh"
|source "$S{script_dir}/$relPath/install/setup.bash"
|ros2 run ${fileConfig.name} ${fileConfig.name}_exe
""".trimMargin()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ class CppStandaloneCmakeGenerator(private val targetConfig: TargetConfig, privat
|cmake_minimum_required(VERSION 3.5)
|project(${fileConfig.name} VERSION 0.0.0 LANGUAGES CXX)
|
|option(REACTOR_CPP_LINK_EXECINFO "Link against execinfo" OFF)
cmnrd marked this conversation as resolved.
Show resolved Hide resolved
|
|${if (targetConfig.get(ExternalRuntimePathProperty.INSTANCE) != null) "find_package(reactor-cpp PATHS ${targetConfig.get(ExternalRuntimePathProperty.INSTANCE)})" else ""}
|
|set(LF_MAIN_TARGET ${fileConfig.name})
Expand All @@ -167,6 +169,10 @@ class CppStandaloneCmakeGenerator(private val targetConfig: TargetConfig, privat
|)
|target_link_libraries($S{LF_MAIN_TARGET} $reactorCppTarget)
|
|if(REACTOR_CPP_LINK_EXECINFO)
| target_link_libraries($S{LF_MAIN_TARGET} execinfo)
|endif()
|
|if(MSVC)
| target_compile_options($S{LF_MAIN_TARGET} PRIVATE /W4)
|else()
Expand Down
Loading
Loading