-
Notifications
You must be signed in to change notification settings - Fork 362
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
Extract NativeSourcePlugin
#2572
base: main
Are you sure you want to change the base?
Extract NativeSourcePlugin
#2572
Conversation
This reverts commit 8a51945.
This reverts commit 045d714.
|
||
} | ||
|
||
case object RustSourcePlugin extends NativeSourcePlugin { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I kept this here for now, but I guess the idea is this can move into another repo/library? :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that would be the main goal. In my option, the perfect solution
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I agree :) It can be complicated to modify the CI in the primary repo to accomodate rust's special requirements. I think all the rust integration can live very naturally in another repo: a compiler plugin, some std library interop like you suggested, etc.
trait NativeSourcePlugin { | ||
|
||
def extensions: Seq[String] | ||
|
||
def compile(config: Config, path: Path): Option[CompilationContext] | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the new interface for compiling native sources.
|
||
} | ||
|
||
sealed abstract class ClangSourcePlugin extends NativeSourcePlugin { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I hope this is not over-engineered 😅
/** Assembler file extension: ".S" */ | ||
val assemblerExt = ".S" | ||
|
||
/** List of source patterns used: ".c, .cpp, .S" */ | ||
val extensions = Seq(cExt, assemblerExt) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wasn't sure where .S
should go, so I snuck it in here.
Also need to fix that comment :)
@@ -56,6 +56,8 @@ sealed trait NativeConfig { | |||
*/ | |||
def embedResources: Boolean | |||
|
|||
def nativeSourcePlugins: Seq[NativeSourcePlugin] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the NativeConfig
the right place to keep these?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that could be the only way we could use to pass extensions without the usage of reflection.
I can imagine the following scenario:
We can define an sbt plugin, which could automatically populate some fields of nativeConfig,
object ScalaNativeRustIntegrationPlugin extends AutoPlugin {
override def requires: Plugins = plugins.ScalaNativePlugin
object autoImport {
val rustIntegrationConfig = taskKey[RustPLuginConfig](???)
val rustIntegrationPlugin = taskKey[RustIntegarionPlugin](???)
case class RustPluginConfig()
class RustIntegarionPlugin(config: RustPluginConfig) extends NativeSourcePlugin
}
}
and then in users code
val foo = project.in(file("foo")))
.enablePlugins(ScalaNativeRustIntegrationPlugin)
.settings(
rustIntegrationConfig := ???,
nativeConfig ~= _.withNativeSourceCompilerPlugins(rustIntegrationPlugin.value)
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, actually we can make it even easier for the user :)
object ScalaNativeRustIntegrationPlugin extends AutoPlugin {
override def requires: Plugins = plugins.ScalaNativePlugin
object autoImport {
val rustIntegrationConfig = settingKey[RustPluginConfig](???)
case class RustPluginConfig()
}
override def projectSettings = Seq(
nativeConfig := {
val plugin = new RustSourceCompilerPlugin(rustIntegrationConfig.value)
nativeConfig.value.withNativeSourceCompilerPlugin(plugin)
}
)
}
Then user code is just:
val foo = project.in(file("foo"))
.enablePlugins(ScalaNativeRustIntegrationPlugin)
.settings(
rustIntegrationConfig := ???,
)
def rustc: Path = discover("rustc", "RUSTC") | ||
def cargo: Path = discover("cargo", "CARGO_HOME") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the rust integration is extracted, I guess these should be moved/removed as well.
nativeSourcePlugins = | ||
Seq(CSourcePlugin, CppSourcePlugin, RustSourcePlugin) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I put them all here for now, for backwards-compatibility. Maybe in future it should be empty, and default C/CPP plugins are populated elsewhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we can use a similar strategy as with linktimeProperties - the configurable part would include only custom plugins, while the core ones (C, Cpp,Rust?) would be always part of the core repo and always enabled.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You made another comment:
Maybe, if in the future someone would like to introduce better Cpp integration we could add settings to nativeConfig allowing for disabling our internal implementations.
I think instead of adding more settings, we should keep the C and Cpp plugins as part of the configurable list instead of force-enabling them. They can be included by default but then they will be easy to remove/replace. They should definitely remain part of the core repo always :)
I think the Rust makes sense as another repo.
): Seq[CompilationResult] = { | ||
import NativeSourcePlugin._ | ||
|
||
val plugins = LlSourcePlugin +: config.compilerConfig.nativeSourcePlugins |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIUC the .ll
files are part of the core functionality—Scala Native will not work without them. So it seems that it should not be optional.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great job on this topic, I really like the separation of compilation logic depending on the source.
I don't have today more time to spend on it, but it looks promising.
I wonder if in the final implementation we can merge some ideas from #2325 eg. filtering sources.
|
||
def withNativeSourcePlugins(plugins: Seq[NativeSourcePlugin]): NativeConfig | ||
|
||
def withNativeSourcePlugin(plugin: NativeSourcePlugin): NativeConfig = | ||
withNativeSourcePlugins(nativeSourcePlugins :+ plugin) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can merge these two by using variadic arguments
def withNativeSourcePlugins(plugins: Seq[NativeSourcePlugin]): NativeConfig | |
def withNativeSourcePlugin(plugin: NativeSourcePlugin): NativeConfig = | |
withNativeSourcePlugins(nativeSourcePlugins :+ plugin) | |
def withNativeSourcePlugins(plugins: NativeSourcePlugin*): NativeConfig = | |
withNativeSourcePlugins(nativeSourcePlugins ++ plugins) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, I wrote these with different purpose.
withNativeSourcePlugin
appends a single plugin to your list of plugins.
withNativeSourcePlugins
replaces the entire list of plugins. Otherwise there is no way to remove and/or change the order.
I guess it needs better names :)
I have some more thoughts on this topic, in the other comments.
nativeSourcePlugins = | ||
Seq(CSourcePlugin, CppSourcePlugin, RustSourcePlugin) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we can use a similar strategy as with linktimeProperties - the configurable part would include only custom plugins, while the core ones (C, Cpp,Rust?) would be always part of the core repo and always enabled.
@@ -232,6 +248,7 @@ object NativeConfig { | |||
| - optimize: $optimize | |||
| - linktimeProperties: $listLinktimeProperties | |||
| - embedResources: $embedResources | |||
| - nativeSourcePlugins:${nativeSourcePlugins.mkString(",")} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe plugins should define their names (similarly to compiler phases), this way the output would be more friendly.
We can also wrap it into array and add space delimiter for better readability
| - nativeSourcePlugins:${nativeSourcePlugins.mkString(",")} | |
| - nativeSourcePlugins:${nativeSourcePlugins.mkString("[", ", ", "]")} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this should be improved 👍
|
||
} | ||
|
||
case object RustSourcePlugin extends NativeSourcePlugin { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that would be the main goal. In my option, the perfect solution
|
||
import NativeSourcePlugin._ | ||
|
||
trait NativeSourcePlugin { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think about a bit longer name?
trait NativeSourcePlugin { | |
trait NativeSourcesCompilerPlugin { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that seems good 👍
): Seq[CompilationResult] = { | ||
import NativeSourcePlugin._ | ||
|
||
val plugins = LlSourcePlugin +: config.compilerConfig.nativeSourcePlugins |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to the previous comment - if we would only define additional plugins in NativeConfig we should here define also CLang and Cpp plugins. Maybe, if in the future someone would like to introduce better Cpp integration we could add settings to nativeConfig allowing for disabling our internal implementations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See #2572 (comment).
@@ -56,6 +56,8 @@ sealed trait NativeConfig { | |||
*/ | |||
def embedResources: Boolean | |||
|
|||
def nativeSourcePlugins: Seq[NativeSourcePlugin] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that could be the only way we could use to pass extensions without the usage of reflection.
I can imagine the following scenario:
We can define an sbt plugin, which could automatically populate some fields of nativeConfig,
object ScalaNativeRustIntegrationPlugin extends AutoPlugin {
override def requires: Plugins = plugins.ScalaNativePlugin
object autoImport {
val rustIntegrationConfig = taskKey[RustPLuginConfig](???)
val rustIntegrationPlugin = taskKey[RustIntegarionPlugin](???)
case class RustPluginConfig()
class RustIntegarionPlugin(config: RustPluginConfig) extends NativeSourcePlugin
}
}
and then in users code
val foo = project.in(file("foo")))
.enablePlugins(ScalaNativeRustIntegrationPlugin)
.settings(
rustIntegrationConfig := ???,
nativeConfig ~= _.withNativeSourceCompilerPlugins(rustIntegrationPlugin.value)
)
val inpath = path.toAbsolutePath() | ||
|
||
val compiler = plugins | ||
.find(p => p.extensions.exists(ext => inpath.toString.endsWith(ext))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the future, we might think about improvement for that, eg. building a Map (extension -> impl)
val inpath = path.toAbsolutePath() | ||
|
||
val compiler = plugins | ||
.find(p => p.extensions.exists(ext => inpath.toString.endsWith(ext))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, we should check if we don't have clashes between plugins when two or more of them would use the same set of extensions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably a good idea :) I was thinking there can be some priority/precedence based on their ordering, but that has got limited use-case and potential for confusion probably :)
val regexExtensions = srcExtensions.mkString("""(\""", """|\""", ")") | ||
private def jarSrcRegex(plugins: Seq[NativeSourcePlugin]): String = { | ||
val regexExtensions = | ||
srcExtensions(plugins).mkString("""(\""", """|\""", ")") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are a lot of plugins passing here, maybe we move all of this method to a single Impl
taking and plugins list. This might allow for some optimizations also. In most cases, we would probably create at most 2 instances per run, (1 instance per findNativeLibs and findNativePaths call)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yeah, that will be much better 😆
I haven't had enough time yet to look at this PR. I don't think they are super related. Some of the parts of the build plugin concept had to do with filtering for windows that is now done in a different way and also conditional linking solved some of the things that were needed. I will put a comment on #2325 as an update. |
| - dump: $dump | ||
| - optimize: $optimize | ||
| - linktimeProperties: $listLinktimeProperties | ||
| - embedResources: $embedResources |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removing the extra space here could reduce the diff,
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I forgot I renamed the config in question. Actually we need more spaces 😆
case Mode.ReleaseFull => "-O3" | ||
} | ||
|
||
def compile(config: Config, inpath: Path): Option[CompilationContext] = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you explain why this returns an option because in the C/C++ plugin it seems to be not needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good question. I'm actually not 100% sure, this was the existing method signature. The rust compiler does return None
in some instances. It seems to indicate that there is nothing to be done. But I'm not sure what differentiates it from returning a CompilationContext
with an empty command. @WojciechMazur will probably know :)
scala-native/tools/src/main/scala/scala/scalanative/build/NativeSourcesCompilerPlugin.scala
Line 183 in 653dfab
if (!shouldBuildCrate) None |
compilationOutput.filter(_.isInstanceOf[ObjectFile]) ++ | ||
compilationOutput.filter(_.isInstanceOf[Library]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you use partition
here to avoid 2 transits?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a few small things - cosmetic. Will look more later.
jarPattern.matcher(name).matches() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Spurious change?
private def readDir(path: Path): Option[Path] = | ||
private def readDir( | ||
path: Path | ||
): Option[Path] = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here too?
s"glob:${srcPathPattern(path)}**{", | ||
",", | ||
"}" | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here?
private def destSrcPattern( | ||
workdir: Path, | ||
destPath: Path | ||
): String = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And here?
This PR branches off of #2566 to try extracting out a plugin for native sources as suggested in #2566 (comment).