Open philmcardle opened 7 years ago
Thanks Phil for the feature request. It is a limitation with the Visual Cpp toolchain. Your current workaround is the way to go for now, until we improve the model.
In my above comment, I suggested that I would workaround this by coercing Gradle to only configure one C++ toolchain at a time, using pre-defined / command-line project properties, but in researching GH 906 I stumbled across setToolChain on InstallExecutable, CreateStaticLibrary, AbstractNativeCompileTask, and AbstractLinkTask and I'm wondering whether I could instead configure both my vs2013 and vs2015 toolchains:
apply plugin: 'cpp'
model {
toolChains {
vs2015(VisualCpp) {
installDir = 'C:/Program Files (x86)/Microsoft Visual Studio 14.0'
}
vs2013(VisualCpp) {
installDir = 'C:/Program Files (x86)/Microsoft Visual Studio 12.0'
}
}
platforms {
windows_x64_vs2015 {
architecture 'x86_64'
operatingSystem 'windows'
ext.toolChain = 'vs2015'
}
windows_x64_vs2013 {
architecture 'x86_64'
operatingSystem 'windows'
ext.toolChain = 'vs2013'
}
}
components {
TestProject(NativeLibrarySpec) {
targetPlatform 'windows_x64_vs2015'
targetPlatform 'windows_x64_vs2013'
}
}
}
..and then call this setToolChain method with the different VS toolchains above on each of the tasks configured for each platform?
e.g. compileTestProjectWindows_x64_vs2013SharedLibraryTestProjectCpp, compileTestProjectWindows_x64_vs2015SharedLibraryTestProjectCpp and so on.
I'm going to try this later, but, just talking out loud in case a) it works b) it's something that's likely to be unsupported later and I should avoid.
Okay, the above doesn't work.
I have the usual issue with competing configuration being applied at different times - much of the configuration that depends on knowing which toolchain will be used to run a given task is applied before I can change the toolchains on the tasks.
Going by options.txt (and the subsequent output), the compile tasks seem to be completely unaffected, and the link tasks are partially affected, so I end up calling a vs2013 linker on code compiled with vs2015. I can provide example code if you're curious, but, you probably saw it coming π
I can't tell how well any other tasks were affected, in this scenario, but, not workable.
@lacasseio (and apologies for the tag) Is there any way I can get to call setToolChain before the rest of the configuration? (assuming that my idea is otherwise viable)
Thanks @philmcardle for the awesome work you are doing on this and no worries for the tag. Since linker tasks are created after the component container is closed, I would suggest mutating the binaries matching your platform for each component like the following instead of changing the AbstractLinkTask
task:
import org.gradle.nativeplatform.internal.NativeBinarySpecInternal
apply plugin: "cpp"
model {
components {
main(NativeExecutableSpec)
withType(NativeComponentSpec) { component ->
component.binaries.afterEach(NativeBinarySpecInternal) { binary ->
binary.setToolChain(null)
}
}
}
}
Give it a try and I would be very curious to know if this work.
Hahaha, that works π
I didn't need the use of NativeBinarySpecInternal, so I'd be curious to know what that would have been for.
I'm able to do this across my entire multi-project build with the following code:
model {
platforms {
windows_x64_vs2015 {
architecture 'x86_64'
operatingSystem 'windows'
ext.toolChain = 'vs2015'
}
windows_x64_vs2013 {
architecture 'x86_64'
operatingSystem 'windows'
ext.toolChain = 'vs2013'
}
}
components {
withType(NativeComponentSpec) {
targetPlatform 'windows_x64_vs2015'
targetPlatform 'windows_x64_vs2013'
binaries {
afterEach { binary ->
if (binary.name =~ /vs2013/) {
binary.setToolChain(toolChains.vs2013)
}
}
}
}
}
}
I've included my custom platforms to give the code a little more context. I should also call setToolChain
explicitly for vs2015 rather than relying on it defaulting to it by way of the order of defined toolchains, but, as a proof of concept π
I have more than one BuildType defined, so my binary names are of the form:
windows_x64_vs2013DebugSharedLibrary
windows_x64_vs2015DebugSharedLibrary
..and so on.
I've confirmed that all the compiler and linker commands are correct for all targets and additionally that my prebuilt library code alluded to in gradle/gradle#823 continues to work fine (which, you would expect - but sometimes the simultaneous configuration makes this hilarious). I've included it here for reference, and for anyone else considering a similar approach:
repositories {
libs(PrebuiltLibraries) {
GoogleTest {
headers.srcDir "$rootProject.projectDir/Dependencies/GoogleTest-1.8.0/googletest/include"
binaries.withType(StaticLibraryBinary) {
def googleTestBuildType
switch (buildType) {
case buildTypes.Debug:
googleTestBuildType = 'debug'
break
case buildTypes.Release:
googleTestBuildType = 'release'
break
}
// Bug Workaround - https://github.com/gradle/gradle/issues/823
// Check for the existence of our custom property before trying to use it in configuration
if (targetPlatform.hasProperty('toolChain')) {
def googleTestCompiler = targetPlatform.toolChain
def googleTestLibName
switch (targetPlatform.operatingSystem) {
case { it.isWindows() }:
googleTestLibName = 'gtest.lib'
break
case { it.isLinux() } :
googleTestLibName = 'libgtest.a'
break
}
staticLibraryFile = file("$rootProject.projectDir/Dependencies/GoogleTest-1.8.0/googletest/lib/${googleTestCompiler}/${googleTestBuildType}/${googleTestLibName}")
}
}
}
}
}
Additionally, the toolChain
changes are automatically propagated to the respective GoogleTest testSuites, though I have no idea which code is handling that I am grateful nonetheless π
Thanks for posting your solution here Phil. It will be very helpful for everyone until we get a change to improve this specific scenario.
I referenced NativeBinarySpecInternal
in my example because setToolChain
is a method of that class. There are several reasons writing Gradle code that way which is more verbose:
DefaultSharedLibraryBinarySpec
, DefaultStaticLibraryBinarySpec
and DefaultNativeExecutableSpec
). Those classes implement both the public and internal API of Gradle. Even if the API specify SharedLibraryBinarySpec
, Groovy ignore that and tries to call the method you want on the object. Sometimes that method is available in the internal API and everything will work without been able to find the public documentation. It causes confusion to the less experienced user.binaries
contain can contain anything that is a BinarySpec
. Future development, either by the Gradle team or by your team, may decide to add a custom type to a container. That custom type may not have setToolChain
which may break your code unexpectedly. Filtering helps been explicit in the outcome of your configuration.I hope this answer your question. Again, thanks a lot for posting your solution.
I didn't even realise it was internal, but I can see now that it's not on the javadoc pages (and I can see that I looked for this exact method before).
I'll amend my own code to use that, as you suggest, thanks.
I don't know what the appropriate way to solve this is, so a bit of a scattershot for the title, sorry.
Expected Behavior
Gradle should supporting targeting different versions of the Microsoft VC++ compiler with the same flexibility as it supports separately targeting VisualCpp, Gcc, Clang and others right now.
Current Behavior
Gradle treats all versions of the VC++ compiler as equivalent, when they're not in fact cross-compatible, requiring native developers to go to great lengths to separately target each version of VS for the build steps, and when sourcing the dependencies.
Context
I'm not the first person to report this on the forums, but I posted this topic most recently, for context: https://discuss.gradle.org/t/native-trying-to-constrain-which-platforms-a-toolchain-supports/20424
At the moment, I have a project which has to be built for Windows x64 using VS2013, Windows x64 using VS2015 and Linux x64 using gcc, with prebuilt dependencies having been built for each of these already. It will likely expand to more later.
I'll work around it by coercing Gradle to only configure one C++ toolchain at a time, using pre-defined / command-line project properties. Not ideal, and a betrayal of one of Gradle's most useful features, but I can't see another way right now.