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

C++20 modules are in: discussing a sane (experimental) design for Meson #5024

Open
germandiagogomez opened this issue Mar 6, 2019 · 130 comments

Comments

@germandiagogomez
Copy link
Contributor

Hello everyone.

I am particularly interested in this topic. CMkae guys already started something:https://www.reddit.com/r/cpp/comments/axnwiz/cmake_gcc_module_proofofconcept/

Though I do not know the proposal well enough to propose a particular design myself, I think it would be a good idea to kick off discussion on strategies and module mapping, at least in the context of gcc, now that Modules have been voted in.

I would propose that this thread output is an initial design proposal to give a first try on implementation with the main high level details and strategies:

  • file mapping handling
  • module scanning
  • ninja (and others later) rules to generate...
@dcbaker
Copy link
Member

dcbaker commented Mar 6, 2019

I'm not sure I have a good enough grasp on how C++20 modules are supposed to work, does anyone have a link to a good overview of them?

@jpakkane
Copy link
Member

jpakkane commented Mar 6, 2019

There isn't one. There are three different implementation that are different. The standardisation committee has promised to create a technical specification on how this "should work" but no-one has to actually follow that, though they are strongly recommended to.

@dcbaker
Copy link
Member

dcbaker commented Mar 7, 2019

Sigh, I love committee hand waving. Does GCC or Clang have any documents on how their implementation is supposed to work?

I'm dreading trying to get this information out of the ICC guys.

@germandiagogomez
Copy link
Contributor Author

germandiagogomez commented Mar 11, 2019

Food for thought about how to organize things, this will be a series I guess: https://vector-of-bool.github.io/2019/03/10/modules-1.html

@germandiagogomez
Copy link
Contributor Author

germandiagogomez commented Mar 20, 2019

First attemp at module mapping: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1484r1.pdf

GNU Make modules support prototype: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1602r0.pdf

@jpakkane this seems to be the start of some reasonable mapping and a proof of concept implementation of scanning dependencies?

@andreaskem
Copy link

Hi,

some links about modules in GCC and Clang:
https://gcc.gnu.org/wiki/cxx-modules
https://clang.llvm.org/docs/Modules.html

This will also affect e.g., ninja:
ninja-build/ninja#1521

It might be worth keeping in mind that e.g., Fortran has had modules (and submodules) for a while. CMake handles those quite well (ninja still needs some patches, I think).

@dcbaker
Copy link
Member

dcbaker commented Mar 27, 2019

Unless the ninja patches go upstream I don't think we can rely on ninja for this. We'll also have to figure out what XCode and VS are going to do about modules, I don't know if it's better to rely on ninja doing it for us if VS or XCode makes us implement this ourselves. (It doesn't seem unlikely that msbuild will rely on VS explicitly declaring the module relationship in the XML.

Meson already handles fortran modules and submodules I think, without using anything from ninja. @scivision has done a lot of work in that area.

@jhasse
Copy link
Contributor

jhasse commented Apr 1, 2019

Unless the ninja patches go upstream I don't think we can rely on ninja for this.

FYI: It's very very likely that those Ninja patches will go upstream soon (I'm planning to review the PR this month) and be released with 1.10.

@mathstuf
Copy link
Contributor

mathstuf commented Apr 5, 2019

Hi, I'm the author of the Reddit post linked in the description and the CMake C++20 module support, so I can answer any questions you might have. I have a repository with C++ module use cases that I'd be happy to have meson build support added to.

Meson already handles fortran modules and submodules

Does it support generated fortran sources though? That's basically what requires the ninja patches we made. I'll be adding a C++ example for this next week to the above mentioned repository.

@mathstuf
Copy link
Contributor

mathstuf commented Apr 5, 2019

There isn't one. There are three different implementation that are different. The standardisation committee has promised to create a technical specification on how this "should work" but no-one has to actually follow that, though they are strongly recommended to.

I've gotten verbal confirmation that the major compiler vendors would be fine with providing information specified in this thread (continued here and here). I have the patch for GCC. Clang is next on my list, and it sounds like MSVC will as well. EDG said they'd have to follow whatever the other three do anyways, so we get them and all their backends by their other-compiler emulation. If you have any input on the format specified there, feel free to drop me a line.

@GunpowderGuy
Copy link

any progress ? does the meson team know when we should we expect this feature to be released ?
i would like to try experimental support as soon as it is available

@scivision
Copy link
Member

Just to add another CMake perspective, with details of how CMakes implement Fortran modules and submodules and thoughts how they'd do C++
https://mathstuf.fedorapeople.org/fortran-modules/fortran-modules.html

@mathstuf
Copy link
Contributor

I'll also note that the required Ninja features have landed and will be included in the 1.10 release.

@mensinda
Copy link
Member

Here are my thoughts on supporting C++20 modules in Meson:

The main problem that has to be solved with modules is the mapping of module names/identifiers to source files and the resolution of dependencies. There are multiple proposals on how such resolution mechanisms can be implemented. They range from an on-demand compiler, build system IPC mechanism and batch compilation to scanning the source files during the build step.

The main problem I see with these proposals is that they all (some more than others) depend on additional functionality in the compiler. In an ideal world, the required feature would be implemented by every compiler with the same logic, same limitations, same JSON format, etc. However, this might not necessarily be the case. As a result, code that compiles perfectly fine with GCC and Clang might fail to compile with ICC. In the (granted unlikely) worst-case scenario, a module handler for every compiler has to be written and maintained.

Even if there is an interface that is supported by most compilers, a fallback solution is still required for the remaining compilers. This would also leave us with two module mechanisms to maintain. Additionally, there would also be no guarantee that code that works with the "official" mechanism would also work with the "fallback" mechanism.

My solution: Let the build system do the entire dependency management by only providing the "fallback" solution. At least for the initial support until there is a universally accepted solution.

Since depending on the compiler in any way doesn't work, both batch compilation and IPC are out. This leaves us with scanning the source code with a builtin/shared tool. I am not going to repeat what is already listed there. I want to argue why I think that this scanner shouldn't be implemented in the compiler.

Naturally, this scanning step can not cover all possible macro/module interactions. If such an import statement is encountered where it does not know what to do (unknown macro in #if, etc.) an error is produced, and the build is aborted.

This artificially restricts what you can do with C++20 modules (module lookup is implementation-defined anyway, so this should be fine). While this restricts what developers can do initially, it would greatly increase the portabilety of the code since every compiler that supports modules should support explicit module mapping.

That being said, there is already a relatively small preprocessor implementation in python, writing one for the scanning tool shouldn't be that hard. So something like this could be supported out of the box:

#define FOO fooMod
#define BAR(x) FOO ## : ## x
export module FOO;
import BAR(bar);

And expanding the subset of module declarations that are supported later on is always possible.

The only real problem is dealing with the automatic #include conversion by the compiler. However, it should be possible to work around this by having a whitelist of compilers where the rules for automatic #include conversion can be retrieved. For all other compilers, this feature is disabled.

Another advantage would be that we have full control over the scanning tool and can remove and add features as we please. With a direct compiler integration, we would have to ask all the compiler vendors to change something.

Of course, writing such a scanner wouldn't be trivial to build and would ideally be used by multiple build systems (maybe some Meson CMake cooperation?). I would be personally happy to contribute some code towards this if there is a chance that this approach would be used in Meson.

These are my thoughts on this issue, so please correct me if I got anything wrong or missed something.

@mathstuf
Copy link
Contributor

mathstuf commented Aug 25, 2019 via email

@mensinda
Copy link
Member

See clang-scan-deps (on the phone, can get a link tomorrow) for such a tool.

I wasn't aware that there is already a project. Less work for us then :)

Emulating the compilers is hard though, so I think that is probably best at first. Such a tool would be useful for faster scanning if it proves to be too slow.

My idea was specifically not to emulate the compiler, rather provide a tool that works for 90%-99% of use cases and print an error for the rest. Granted, restricting the user is not very user-friendly, but it would guarantee that the code works with every setup.

This is not even the surface of the corner cases :) . Please join #modules and #sg15_tooling on the cpplang slack to discuss with other stakeholders.

Sure, my point was (primarily) to give an error and abort the build if such an edge case is discovered. Then support for these edge cases might be added later.

I have already gotten verbal confirmation that the JSON format is ok with GCC (I have a patch), Clang developers, and MSVC. When I asked EDG developers, they said they have to implement whatever the other three do anyways. I don't think we'll have issues with compiler support (it's really easy anyways compared to having modules at all). Flag spelling may differ, but GCC and Clang will likely be the same.

That's why we're working with ISO where all the implementors are present :) .

If this format becomes the standard for dependency scanning and every compiler supports it, then this would be the best-case scenario, and my main issue for relying on the compiler is resolved :)

In this case, the only remaining issue would be speed. Scanning for dependencies should be nearly instant (I haven't tested any compiler implementation yet, so I don't know about the current performance). It would also be really useful if the compiler would support scanning multiple files at once. This could reduce the process spawning overhead, especially on Windows,

@mathstuf
Copy link
Contributor

Sorry, got busy and didn't circle back on this. Some links:

My idea was specifically not to emulate the compiler, rather provide a tool that works for 90%-99% of use cases and print an error for the rest. Granted, restricting the user is not very user-friendly, but it would guarantee that the code works with every setup.

Well, the fundamental problem seems to be something like this:

#if __has_feature(frobnitz)
import frobnitz;
#else
import fallback.frobnitz;
#endif

The preprocessor definitions are easy to get, but the feature set is not so easy. __has_attribute also is likely in the same bucket.

In this case, the only remaining issue would be speed. Scanning for dependencies should be nearly instant (I haven't tested any compiler implementation yet, so I don't know about the current performance). It would also be really useful if the compiler would support scanning multiple files at once. This could reduce the process spawning overhead, especially on Windows,

Our prior paper (which missed mailings, but is available here) showed how per-source, per-target, and whole-project scanning is possible and isomorphic in terms of build correctness (the difference is mainly in incremental build work reductions). I think clang-scan-deps is likely to support batch scanning, but maybe not right away.

@mensinda
Copy link
Member

Well, the fundamental problem seems to be something like this:

#if __has_feature(frobnitz)
import frobnitz;
#else
import fallback.frobnitz;
#endif

Even clang-scan-deps would have problems with this. There is no way any tool could reliably detect this for all compilers and compiler flags. That's why I would propose to specifically disallow such constructs by producing a hard error in the scanning step (with some helpful message on how to work around it, if possible). I am the first one to admit that this isn't user-friendly, but these cases should be the exception and not the norm.

Also, one can work around this in meson (and CMake) by using configure_file() with data from the compiler methods to generate a config.h:

#pragma once
#define HAS_FEATURE_frobnitz 1

Then your original case can be rewritten as:

#include "config.h"
#if HAS_FEATURE_frobnitz
import frobnitz;
#else
import fallback.frobnitz;
#endif

I will repeat that I am fully aware that this adds additional burden on the user and that not all (presumably valid) use cases can be solved like this one. However, in my opinion, the benefits outweigh the costs here. Such a scanner would be limited by design but blazing fast and easy to maintain. Additionally, I would argue that defining all HAS_FEATURE_*, etc. in a separate config.h generated by the build system is preferable anyway because it makes the code easier to understand.

This scanner would ideally be fairly compact and have no external dependencies. This is because it is even lower on the software stack than build systems like meson and CMake (but not ninja). So only the C++ or python standard library would be allowed as well as a custom build.py to bootstrap the project.

That's also why I don't particularly like the idea of a clang-scan-deps, exactly because it depends on LLVM (LLVM is built with CMake, so there would also be a circular dependency 😃). Just installing the LLVM toolchain to have support for module scanning seems overkill for me. On Windows with VS and if you are only using GCC on Linux, you don't need and/or want LLVM. Especially if you want to keep a docker image small and would only need clang-scan-deps for module detection.

PS: How do you actually use clang-scan-deps? I managed to install it from llvm-git, but I can't figure out how to scan stuff.

@mathstuf
Copy link
Contributor

I will repeat that I am fully aware that this adds additional burden on the user and that not all (presumably valid) use cases can be solved like this one. However, in my opinion, the benefits outweigh the costs here. Such a scanner would be limited by design but blazing fast and easy to maintain. Additionally, I would argue that defining all HAS_FEATURE_*, etc. in a separate config.h generated by the build system is preferable anyway because it makes the code easier to understand.

The compiler is likely to also be useful as a scanner (GCC is at least). Just probably not as fast as one would like. An option to say "I don't do silly things" to allow using clang-scan-deps seems reasonable though. That also nicely removes the dep cycle you're worried about.

(LLVM is built with CMake, so there would also be a circular dependency)

Well, CMake is unlikely to use modules itself until…oh 2029 probably based on the speed of C++11 adoption of our target compilers, so that part is not a cycle :) .

PS: How do you actually use clang-scan-deps? I managed to install it from llvm-git, but I can't figure out how to scan stuff.

That, I'm not sure. You'll have to ask Michael Spencer (one of its developers).

@Bigcheese
Copy link

Our prior paper (which missed mailings, but is available here) showed how per-source, per-target, and whole-project scanning is possible and isomorphic in terms of build correctness (the difference is mainly in incremental build work reductions). I think clang-scan-deps is likely to support batch scanning, but maybe not right away.

clang-scan-deps is designed for batch processing. It doesn't even have an interface for scanning a single file (other than providing it a batch of a single file).

@Bigcheese
Copy link

PS: How do you actually use clang-scan-deps? I managed to install it from llvm-git, but I can't figure out how to scan stuff.

clang-scan-deps expects a compilation database. It needs a full command line to know how to preprocess each file.

$ clang-scan-deps --compilation-database=db.json

Upstream clang-scan-deps doesn't currently support reporting modules deps yet.

@germandiagogomez
Copy link
Contributor Author

@jpakkane Ninja v 1.10 seems to have initial support for modules...? ninja-build/ninja#1521

@flajann2
Copy link

flajann2 commented May 3, 2020

So, as of today, where do we stand with meson support for C++20 modules? I am willing to ditch CMake for Meson at this point if that support is there. And it is unclear where or how the Ninja support factors in with Meson.

CMake supports Fortran, but no indication as far as I know as to when they will port that functionality over to C++20.

@jpakkane
Copy link
Member

jpakkane commented May 3, 2020

The module support is not implemented yet. The compiler toolchains do not support it that well yet either (at least last I looked, maybe it has changed). Once Ninja and toolchains have the necessary bits (and they are sufficiently stable) we'll add module support.

@mathstuf
Copy link
Contributor

mathstuf commented May 3, 2020

FWIW, ninja does have the necessary bits merged now (and released in 1.10). Compilers still need a spec to write against for dependency info (at least for CMake's approach), but that's mostly on me to work on for SG15. CMake would then need some CMake-side changes for usage requirements related to modules.

@germandiagogomez
Copy link
Contributor Author

This is an implementation of http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1184r1.pdf

Module mapper implementation relicensed and freely available:

 - http://lists.llvm.org/pipermail/cfe-dev/2020-May/065487.html

@germandiagogomez
Copy link
Contributor Author

libcody, build system/compiler communication has been added to GCC. Might be relevant to Meson?

https://github.com/urnathan/libcody

@mathstuf
Copy link
Contributor

mathstuf commented Jun 9, 2020

If meson wants to run something during the build and monitor that communication, it can help. But AFAIK, ninja has no way of specifying such a tool to communicate with, so you're likely left with the compiler sending off a message to some blocking process which then starts/communicates with the necessary tool (and all the locking fun that sounds like it involves).

While that is a solution to building modules, I don't find it a particularly scalable one (you don't know what is needed until the compiler has started and now you're consuming memory, process slots, and doing IPC to figure out what work to do next). It's probably fine for small projects which are writing their makefiles by hand though, but once there's a complicated dependency graph, I only forsee contention.

@mathstuf
Copy link
Contributor

we have a scanner that is okayish for fortran,

Note that I suggest first porting your Fortran scanner to use P1689 as a "our P1689 handling works". It is a future goal to interact with the Fortran standards committee about P1689 as well.

we need a way to determine whether a C++ source should be treated as a module. VS and Clang use (different) specific extensions, gcc does not

VS has opinions; MSVC goes by the -interface and -internalPartition flags. GCC and Clang have -x c++-module.

@torokati44
Copy link
Contributor

@dcbaker:
I'm really glad to see someone interested in working on this!

We need to scan each C++ file to determine if it uses import/export -

  • we have a scanner that is okayish for fortran,
  • P1689 is a standard way to communicate this, clang-scan-deps can generate this for clang; gcc and msvc do this directly

I think the current scanner can also do C++, even if it's fairly primitive still:

def scan_cpp_file(self, fname: str) -> None:
fpath = pathlib.Path(fname)
for line in fpath.read_text(encoding='utf-8', errors='ignore').split('\n'):
import_match = CPP_IMPORT_RE.match(line)
export_match = CPP_EXPORT_RE.match(line)
if import_match:
needed = import_match.group(1)
if fname in self.needs:
self.needs[fname].append(needed)
else:
self.needs[fname] = [needed]
if export_match:
exported_module = export_match.group(1)
if exported_module in self.provided_by:
raise RuntimeError(f'Multiple files provide module {exported_module}.')
self.sources_with_exports.append(fname)
self.provided_by[exported_module] = fname
self.exports[fname] = exported_module

I wouldn't mind Meson keeping this direction - I have the impression that this project was always more "practical and pragmatic" than "prepared for all theoretical scenarios". Depending on clang-scan-deps or custom compiler-specific logic just to be able to say "Hey, we're doing standard P1689 things!" may not be worth it. But this is just my two euro-cents.

And especially with https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1857r1.html, a simple scanner script (maybe running just a preprocessor before it, but maybe not even that) should suffice in most scenarios.

@mathstuf:

GCC and Clang have -x c++-module.

Unfortunately, GCC does not. Here's a snippet of man g++:

       -x language
           Specify  explicitly  the  language for the following input files (rather than letting the compiler choose a default based on the file name suffix).  This
           option applies to all following input files until the next -x option.  Possible values for language are:

                   c  c-header  cpp-output
                   c++  c++-header  c++-system-header c++-user-header c++-cpp-output
                   objective-c  objective-c-header  objective-c-cpp-output
                   objective-c++ objective-c++-header objective-c++-cpp-output
                   assembler  assembler-with-cpp
                   ada
                   d
                   f77  f77-cpp-input f95  f95-cpp-input
                   go

What GCC does have in exchange, is '-fmodule-mapper=|@g++-mapper-server -r '"$ROOT" and libcody.
I don't like this, I think it's an unnecessary overcomplication, and Clang's simple BMI file name rules are way more easily usable.

@mathstuf
Copy link
Contributor

Oh, I forgot the GCC details…but you can just write a static module mapper; there's no need to do libcody protocol logic at all. It's basically a fancy response file at that point (just without having to deal with command line escaping rules :) ).

p1857r1

There is also P3034R0. However, these only really help with export detection. import detection can use arbitrary preprocessor logic. It's really something best left to the compiler (or a tool that can emulate it faithfully like clang-scan-deps).

I suspect Meson will more readily be able to do batch scanning than CMake given the lack of per-source flags interfering, but external projects are (almost certainly) going to have crazy logic around import statements that will have to be dealt with.

@dcbaker
Copy link
Member

dcbaker commented Nov 17, 2023

Note that I suggest first porting your Fortran scanner to use P1689 as a "our P1689 handling works". It is a future goal to interact with the Fortran standards committee about P1689 as well.

This is likely future work, the scanner we have seems to be working, and we support a bunch of fortran compilers that are likely never going to grow P1689 support (they're old and not getting updates or have been replaced). All that to say I don't think we'll ever be able to get rid of the hand rolled scanner, but we can probably support P1689 for newer compilers.

So it sounds like for gcc their plan is "we will do that right thing depending on what's in the file", clang and msvc give us more power over that?

@dcbaker
Copy link
Member

dcbaker commented Nov 17, 2023

Aslo, @mathstuf do you know which revision of p1869 the various compilers implement? are the all implementing r5?

@intractabilis
Copy link

I think the current scanner can also do C++, even if it's fairly primitive still:

The best practice is to rely on reasonable defaults provided by the compiler. In this case, compiler-provided scanner. This corresponds to the original Meson philosophy set by Jussi.

@intractabilis
Copy link

are the all implementing r5?

Clang and GCC, yes.

@mathstuf
Copy link
Contributor

This is likely future work, the scanner we have seems to be working, and we support a bunch of fortran compilers that are likely never going to grow P1689 support

I'm not saying to wait for Fortran compiler support, but to use P1689 for your scanner output and consumption instead of whatever format you use between the scanner and dyndep output (assuming that there is one; if it is somehow one thing, there's no need to communicate via P1689).

do you know which revision of p1869 the various compilers implement? are the all implementing r5?

MSVC is also using r5.

@dcbaker
Copy link
Member

dcbaker commented Nov 17, 2023

I'm not saying to wait for Fortran compiler support, but to use P1689 for your scanner output and consumption instead of whatever format you use between the scanner and dyndep output (assuming that there is one; if it is somehow one thing, there's no need to communicate via P1689).

Ah, that makes sense. Right now our tool just reads fortran files and dumps a dyndep directly. Which works in simple cases, but this reminds me that it's actually broken when using modules across targets...

@mathstuf
Copy link
Contributor

Yes, I believe it is difficult to have just a scanner that also does the collation step (see this paper that I've probably linked to before in this issue) because the scanner needs to communicate between scans (though one could also have a project-wide scan-and-collate as a single thing, this doesn't work if one supports sources which produce or consume modules generated from tools which produce or consume modules themselves). Target-granularity seems the best middle ground of performance and corner cases if one needs to discover "phases" of targets across the graph at generate time.

@AlyamanMas
Copy link

Any updates on this?

@dcbaker
Copy link
Member

dcbaker commented Jan 9, 2024

I have some patches out for Fortran that are groundwork, there are some really annoying to solve because the compiler story is a mess

@deepbluev7
Copy link
Contributor

Small update on the C++ side, module declarations now shouldn't be macros: https://isocpp.org/files/papers/P3034R1.html (Defect Report, so will also apply to C++20 afaik).

@eli-schwartz
Copy link
Member

Yup:

20:03 <elibrokeit> I am incredibly surprised it took so many years for it to finally be forbidden :P
20:03 <elibrokeit> but as I recall that was still a proposal albeit one extremely likely to be accepted
20:04 <sam_> it's final now
20:04 <sam_> as of like 2 days ago or something
20:04 <pinskia> sam_ you beat me to saying that
20:04 <sam_> :)
20:04 <jwakely> as of saturday
20:05 <jwakely> last saturday

@vinniefalco
Copy link

This was opened in 2019 what is the status?

@dcbaker
Copy link
Member

dcbaker commented Apr 3, 2024

The current status is that basic cases should mostly work. I have been working on fixing up our scanner and how we do scanning to get faster and more accurate results see here, mainly aiming at Fortran since they've hod modules longer, and C++ and Fortran modules have many of the same design decisions.

@alexjwilliams
Copy link

alexjwilliams commented Apr 4, 2024

The current status is that basic cases should mostly work.

Is there a minimal example somewhere that I can explore?

@dcbaker
Copy link
Member

dcbaker commented Apr 4, 2024

For C++, in the Meson repo you can look at `test cases/unit/85 cpp modules"

This will only work with GCC or MSVC, we don't have support for Clang yet.

@lissom
Copy link

lissom commented May 12, 2024

Support for clang would be great, as they appear to have the most operational modules. Maybe not a few years ago when the example was done, but now. i.e. https://github.com/infiniflow/infinity/tree/main

@ChuanqiXu9
Copy link

So it may be better to mark the status of meson in https://arewemodulesyet.org/tools/ as partial instead of ✅ since it can't work with clang

@JohnyMarley
Copy link

Hello!
Are modules now fully support with GCC?

@rtgiskard
Copy link

rtgiskard commented Jun 11, 2024

Support for clang would be great, as they appear to have the most operational modules. Maybe not a few years ago when the example was done, but now. i.e. https://github.com/infiniflow/infinity/tree/main

Project with cpp module, very good reference! I'll try modules from now on


Failed to get start with meson ..

@lissom
Copy link

lissom commented Jun 12, 2024

Support for clang would be great, as they appear to have the most operational modules. Maybe not a few years ago when the example was done, but now. i.e. https://github.com/infiniflow/infinity/tree/main

Project with cpp module, very good reference! I'll try modules from now on

Failed to get start with meson ..

Meson doesn't support this, it would be great if it did (it's CMake). The reason I linked it is that it's a fully working production C++ modules project, so there is no longer the chicken and egg problem. Knowing that a compiler supports them and there is a reference project, it should be easier for Meson to implement with something concrete to look at. (A lot of the discourse around modules seems to boil down to the other guy doesn't support this so how can I. With the other guy being the build system or the compiler, take your pick.)

@ilobilo
Copy link

ilobilo commented Jul 26, 2024

👀

@mattpetters
Copy link

mattpetters commented Oct 3, 2024

For any future travelers...definitely not first class support but I was able to hack together something workable on my project.
On a Macbook Pro but my system clang was behind so that's why I went with the llvm from homebrew.
Apologies if this is not 100% clean, spent the past few days reading clang docs and meson docs and new to both lol

project('cpp-modules-ex', 'cpp',
        version : '0.1',
        default_options : ['cpp_std=c++20'])

cpp = meson.get_compiler('cpp')

add_project_link_arguments('-stdlib=libc++', language : 'cpp')

# Compile module interface units
# -fmodule-output lets us do the whole precompilation and object file in one go instead of two steps (.pcm -> .o) with --precompile
# clang recommends .cppm for the module extension btw (and says it may not work otherwise)
feature_obj = custom_target('feature_obj',
                               output : ['feature.o', 'feature.pcm'],
                               input : 'modules/feature.cppm',
                               command : [cpp.cmd_array(), '-std=c++20', '-c', '@INPUT@',
                                          '-fmodule-output=@OUTPUT1@', '-o', '@OUTPUT0@'])

build_args = ['-fprebuilt-module-path=.', '-std=c++20', '-stdlib=libc++', '-I/opt/homebrew/opt/llvm/include']

linker_args = ['-L/opt/homebrew/opt/llvm/lib', '-L/opt/homebrew/opt/llvm/lib/c++', '-lc++', '-Wl,-rpath,/opt/homebrew/opt/llvm/lib/c++']

# Build the final executable
executable(meson.project_name(),
           'main.cpp',
           feature_obj,
           cpp_args : build_args,
           link_args : linker_args,
           install_rpath : '/opt/homebrew/opt/llvm/lib/c++',
           dependencies : declare_dependency(sources : feature_obj))

for a project structure like:

tree -L 2
.
├── README.md
├── build
├── main.cpp
├── meson-native-clang
├── meson.build
├── modules
│   ├── feature.cppm
│   ├── feature_impl.cpp

meson-native-clang (may not be necessary but been throwing the kitchen sink at this)

[binaries]
c = '/opt/homebrew/opt/llvm/bin/clang'
cpp = '/opt/homebrew/opt/llvm/bin/clang++'

[built-in options]
cpp_args = ['-I/opt/homebrew/opt/llvm/include']
cpp_link_args = ['-L/opt/homebrew/opt/llvm/lib', '-L/opt/homebrew/opt/llvm/lib/c++', '-Wl,-rpath,/opt/homebrew/opt/llvm/lib/c++']

@mathstuf
Copy link
Contributor

mathstuf commented Oct 4, 2024

Just an FYI, this (using -fprebuilt-module-path=<static-path>) is the "modules are like includes" strategy which fails in a few ways:

  • the module might be provided by a library not depended upon by the current library (parallel build hazard as well)
  • the module might be leftover from a previous build and no longer part of the build graph
  • the module might be private to the library providing it and not for use in any given context
  • BMI compatibility is not considered (when debug and release are in the same build graph, how to separate them? static and shared?)

These are why the build system (IMO) must be involved in hooking up dependencies rather than "just look for things on disk".

@mattpetters
Copy link

Just an FYI, this (using -fprebuilt-module-path=<static-path>) is the "modules are like includes" strategy which fails in a few ways:

  • the module might be provided by a library not depended upon by the current library (parallel build hazard as well)
  • the module might be leftover from a previous build and no longer part of the build graph
  • the module might be private to the library providing it and not for use in any given context
  • BMI compatibility is not considered (when debug and release are in the same build graph, how to separate them? static and shared?)

These are why the build system (IMO) must be involved in hooking up dependencies rather than "just look for things on disk".

Ah I see. That actually makes sense now why first class support for this stuff is such a big task. Thanks for the education 🙏

@mathstuf
Copy link
Contributor

mathstuf commented Oct 4, 2024

Just to break it down, here's what CMake does (in some more detail than the clouds and boxes in this paper provide:

  1. scanning sources needs to happen (build or configure time; build supports generated sources and getting "reconfigure" out of the main development loop)
  2. these files are likely to be in P1689R5 format
  3. once scanning for a target is complete (build or configure time), a tool (provided by meson for meson) reads in the following information:
  • the P1689 files for the target
  • results of this tool on dependent targets (*)
  • metadata about the target from the configure step
  1. this tool then takes the information and, for each TU:
  • writes out the dependency information to a "module map" file that each compilation then reads
  • writes out a file for "how to use modules provided by this target" for consuming targets to use (see (*) above)
  • write out any install-time information for how to install the generated BMI files and/or ship module metadata to installation artifacts (e.g., CPS files are likely to grow a "provides modules X, Y, and Z" field some day)
  1. other things this tool can do:
  • detect cycles and say "no" better than ninja: cycle detected (a TODO for CMake)
  • warn when a module is requested that is known but not usable (e.g., made by a target not linked) (CMake just lets the compiler say "module X not found" today)

Complications:

  • Target A might need to provide BMIs for different consuming targets B and C if their flags differ "meaningfully") (see https://gitlab.kitware.com/cmake/cmake/-/issues/25539 for CMake's task to do this for in-project targets)
  • The set of flags that are "meaningful" are…vast (I'm thinking of doing a "these flags are ok to differ by" list rather than the other way around
  • I recommend providing docs or a tool to help debug this stuff when it inevitably goes sideways (e.g., "bundle up CMakeFiles/target.dir/*.{ddi,json,modmap} files" or something)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests