gradle / gradle-native

The home of Gradle's support for natively compiled languages
https://blog.gradle.org/introducing-the-new-cpp-plugins
Apache License 2.0
93 stars 8 forks source link

Support for C++ source compatibility #399

Open lacasseio opened 6 years ago

lacasseio commented 6 years ago

As with the Swift language, source compatibility should also be supported for C++ language.

Expected Behaviors

Users can specify the C++ language (C++98, C++03, C++11, C++14, C++17, etc.) on the component to compile.

Open Questions

Test Cases

k-mack commented 6 years ago

This would be a real nice-to-have. I hope you won't mind me chiming in on those open questions.

Since Swift is a language and a compiler (well, they share the same name), it seems that doing this for C++ will be more complex than how it is done in SwiftSourceCompatibility. It would seem there would be a layer between the compiler and the language version to help make the decision about what dialect option should be used. For example, there would be some type of construct, say GccVersionLanguageSupport, that would verify the source compatibility with a specific version of the compiler. This could be queried with a VersionNumber and a CppSourceCompatibility to return the dialect option that should be used when invoking the compiler.

Thinking out loud, it may look something like below. There would need to be some interfaces to bridge the API between the GCC, Clang, and MSVC implementations.

public enum CppSourceCompatibility {
    Cpp98, Cpp98Extended, // "Extended" meaning use the compiler's language extensions
    Cpp03, Cpp03Extended, // Users should understand that extensions are compiler-dependent
    Cpp11, Cpp11Extended,
    Cpp14, Cpp14Extended,
    Cpp17, Cpp17Extended
}

public class GccVersionLanguageSupport {
    public static String getLanguageStandardOption(VersionNumber version, CppSourceCompatibility dialect) {
        // Modeled after gcc documentation; clang and msvc should be just as straightforward
        switch (dialect) {
        case Cpp17: Cpp17Extended:
            // Still waiting for gcc to support "-std=c++17" and deprecate "-std=c++1z"
            if (version.getMajor() >= 5) {
                return dialect == Cpp17 ? "-std=c++1z" : "-std=gnu++1z"
            }
            break;
        case Cpp14: Cpp14Extended:
            // gcc 5 and later is c++14 complete
            // gcc 4.8-4.9 has experimental support for c++14 (compatibility not guaranteed between versions so ABI is also not guaranteed)
            if (version.getMajor() >= 5) {
                return dialect == Cpp14 ? "-std=c++14" : "-std=gnu++14"
            } else if (version.getMajor() >= 4 && (version.getMinor() == 8 || version.getMinor() == 9)) {
                return dialect == Cpp14 ? "-std=c++1y" : "-std=gnu++1y"
            }
            break;
        // rest of cases...
        }
        return null; // i.e., the provided version of the compiler does not support the provided dialect
    }
}

Clang uses the same "-std=..." options, so the logic would be similar. With respect to MSVC extensions, I think the logic above would only need to decide whether or not to use "/Za," "/Ze," or "/Zc" (if using VS 2017). It would be challenging to model each compiler's specific language standard extension individually as each compiler and version may offer different extensions. It may suffice to just support the user in adding additional arguments to the compiler for further customization of the dialect.

The compiler-specific, dialect-related preprocessor macros could be queried in a similar fashion. I think the main ones would be __STDC__, __STDCVERSION__, and \_cplusplus. Although, MSVC isn't very helpful since it hasn't kept up-to-date with the __cplusplus macro value.

lacasseio commented 6 years ago

@k-mack Your inputs are warmly welcomed on any subject. Native modeling should solve real use case and having the users show their typical use cases is helpful for us.

At the moment, the source compatibility is solved per language. We will move toward a cross-cutting solution to include all ecosystem. C++ is indeed a complex ecosystem to solve. We discussed that resolving all those dimensions is similar to how dependencies are resolved. We may move into a generic solution in the future to allow matching rules to be specified. It's especially crucial for target systems with custom compilers (e.g., forks of GCC) that may introduce yet other variance in the source dialect and target ABI.

ice1000 commented 6 years ago

This feature is important to me. Any update?

adammurdoch commented 6 years ago

@ice1000 No update at the moment. Do you have some more details of what exactly you need? e.g. which tool chains are you using? Which C++ versions do you need to target? Which operating systems does it need to work on? etc.

ice1000 commented 6 years ago

I'm developing cross-compiler C++ projects, and I want a convenient way to let the compiler use C++17.

Currently, I add a command line flag directly (say, 3 ifs to detect compiler). I want a more elegant way.

adammurdoch commented 6 years ago

Thanks for the details.

A simple way that we could start adding support for C++ versions might be just a way to specify a default C++ version for the whole application/library, with the choice being "C++17" or "compiler default".

To keep things simple to start with we would not bother modelling source that handles multiple C++ versions or compiler specific dialects and so on. They can happen later.

ice1000 commented 6 years ago

We need 98, 03, 11, 14, 17, 2a for C++ and 98, 11 for C

lacasseio commented 6 years ago

One thing to consider with for this issue which is still an open question for me is the dialect/extension for each source compatibility. For example, you have -std=c++11 and -std=gnu++11. You get the same issue with Microsoft compilers. If we see them as extensions (as they are often referred to) then we model the standard source compatibility and deal with extensions as a separate concept.

It becomes interesting when a source compatibility is only half implemented such as VS2013, not implementing constexpr. If you don't use them then VS2013 is a valid compiler to use but if you do, then VS2013 shouldn't be selected. Basically, under a standard, there are features that you may or may not use that can define if you can or can't use a compiler.

Let's not get into custom compiled toolchain that disables some feature of the language just because. Those are rare occurrences, but shouldn't lock ourselves in with an implementation.

lacasseio commented 6 years ago

Thinking about this a bit more, we can start just as we did with the operating system where you are targetting the system in general without all the complexity of service pack or patches. This would mean that source compatibility of C++11 means you are targetting all feature of C++11. This would mean VS2013 wouldn't be selected. Then we can enhance the concept by having specific features for each feature of the language where you can explicitly you aren't requiring constexpr feature. This will allow Gradle to choose VS2013 in this case. Much later, we could allow custom source compatibility with user-defined rules for selecting valid toolchains.

@gradle/native What do we think about this?

ice1000 commented 6 years ago

@akemimadoka

akemimadoka commented 6 years ago

I think it's a good idea which users should be able to explicitly specify features which they need, consider using list at C++ compiler support for selecting proper toolchain, it can also be easily implemented by using feature test macros at Feature Test Recommendations.

eaaltonen commented 6 years ago

Our use case comes in two parts:

  1. cross platform development
  2. final platform specific application

Cross Platform Library Development

We want to develop a library part so that we can compile and test it on at least (Windows, Visual Studio) and (Linux, GCC) ⇒ no language extensions allowed

The language features idea sounds pretty cool, but for our case we would be fine by specifying a Cpp14, lenient language compatiblity, as we can solve the language features issue by running lots of Continuous Integration.

Platform Specific Application

The libraries will then be used in platform specific applications, so extensions may be used. Some of them must also be cross compiled.

lacasseio commented 6 years ago

At the moment, the source compatibility would be implemented like the target machine's architecture and operating system. A typed, Named, interface would be used instead of enum (in later steps). A factory would allow creating common compatibility object like C++11, C++14, etc. Known supported type by Gradle would work as expected. For other kinds of support such as going into the weed of the support matrix, you would be able to provide your own instance of the interface and register a transformer on the tool chain to map the DSL object to the correct flag. We could add a registration for probing the tool to find out if the feature is supported. As a first step, we would consciously ignore the language extensions. Overriding the compiler flag, this should be done the same way we will solve overriding the compiler flag for optimized and debuggable.

@k-mack Your initial design is pretty close to what we may accept as a first step for this feature. Do you have some free time to help with this? I can see the steps as follow:

k-mack commented 6 years ago

@lacasseio I'm on holiday this week, but I'll spend time next week getting a PR together.

lacasseio commented 5 years ago

The work have started by @k-mack, thanks a lot! I left some comment on the code.

For the open questions: 1- Let's stick to choosing the right toolchain base on the tool chain version like you are already doing. As for the partially implemented source standard, we could assume them to be valid full-fledged standard and model them further in later work. 2- I agree we should use whatever is the default for the tool chain selected when nothing is specified. However, once the tool chain is selected, the C++ source compatibility field should be filled with the right value, just like in Swift. 3- With regards to your change where it needs to know what compiler type, we could add compiler implementation type for the Clang type and simply have the type added to the GccCompilerArgsTransformer class.

Does that make sense?

k-mack commented 5 years ago

@lacasseio I polished up the work and added some unit and integration tests. I think source compatibility should ultimately be added into the variant structure for a C++ component like is done for target machines, but that could probably be done with a subsequent PR.