[CMake] Scope of a macro

Robert Dailey rcdailey at gmail.com
Wed Mar 4 19:17:17 EST 2009


On Wed, Mar 4, 2009 at 3:59 PM, Michael Wild <themiwi at gmail.com> wrote:

>
> On 4. Mar, 2009, at 20:58, Robert Dailey wrote:
>
>> Is CMake not general-purpose as well?
>>
>
> No, it's not. It's neither a system programing language nor would you
> consider it for anything else than creating a build system.


But even domain-specific programming languages, like CMake, can benefit from
some language features that are in a general purpose language. The domain in
which the language lies is not important. Whether I'm coding in CMake or
C++, I need my tools to make my job as easy as possible. I'm human, I make
mistakes, I expect my tools to find as many of those mistakes as possible.
Granted, tools can't find all of the problems, but there are problems it can
find ahead of time and to deliberately not implement that time-saving
functionality is wrong.

 I'm not asking CMake to become C++,
>> however there are some common language features that are useful.
>>
>
> I don't claim that your proposal is not useful, I just don't see that it
> would add enough value to justify the effort.


It saves people frustration and debugging. You will also evolve as a human
being.


>  Note that
>> there are already concepts in CMake that respect directory scope. There
>> are
>> properties specific to directories, for example. Another example is
>> include_directories(). Include directories added by a parent are available
>> to children and children can append to this list, however siblings do not
>> inherit each others' include directories.
>>
>
> That's because in these cases name-scope is absolutely crucial for the
> correctness of the build. They directly affect the generated build system
> and the resulting compiler/tools-invocations. The same is not true with the
> scope of macro/function names.


Mistakenly calling a macro which is defined in a location intended not to be
accessible to parent directories also affects the generated build system. If
I'm calling the wrong function, I'm potentially setting the wrong build
options and depending on the severity of the issue it may go unnoticed.


>  "Good enough" is relative. Each person has their own standards for "Good
>> enough", so there is no one true path here that will make everyone happy.
>>
>
> "Good enough" as a programming concept means "does it do the job". There's
> always room for better design, more efficient implementation etc. But at the
> end of the day it has to fulfill the requirements, be useable and
> maintainable. As soon as it does the job reasonably, it can be considered
> "good enough". As you point out, what exactly "good enough" means is not
> always clear.


What you are talking about is "functional". However, functional is not
always acceptable nor is it always production worthy. A prototype of an
airplane game can be considered "functional", however it is by no means
acceptable as a production-quality application. Therefore, "Good enough"
doesn't always cut it, and it sounds lazy.

If your job was to create a calculator app that does nothing more than
divide 2 numbers, your definition of good enough would be to have the app
take 2 arbitrary entities, perform division on it, and print the result.
Doing it beyond functional would be to check if both of those entities are
numbers. You would also check to see that you are not dividing by zero. In
the former case, you would get a crash and the user would be confused as to
what went wrong. The usability of the application is in jeopardy. However,
in the latter you are more than just "functional", you are exceptional. The
user can get detailed error dialogs explaining exactly what the problem is
and how to fix it. This makes the lives of everyone who uses the calculator
app better.

Fullfilling the requirements is something that CMake does indeed do
(ignoring bugs), however by only doing the minimum required effort possible
to make the application "Good enough" (functional), you compromise the
usability of the software. What would take me less than 5 seconds to find
with a human-readable error message in CMake ("ERROR: Using a function or
macro that doesn't exist") quickly becomes a task that requires extensive
debugging, effort, and time. The problem may even go unnoticed and my
resulting build system may behave in erratic ways because the incorrect
macro was being called due to simple programmer error (Maybe I forgot to
replace a function call somewhere after an iteration of refactoring).

Human beings aren't perfect, and as programmers we often make silly
mistakes. We create tools like CMake and the C++ compiler not not only
provide some function, such as building an application or a build system,
but to also make the process of doing so as effortless as possible. We also
expect our tools to take some of the error-proneness out of fat-fingering
things ourselves. The less responsibility the programmer has, usually the
better off the process of development becomes.

There is absolutely no excuse whatsoever for the behavior I'm experiencing.
Using the excuse that CMake is functional has nothing to do with this
problem. The fact that you are defending this problem seems lazy to me. This
issue makes CMake less usable and also makes refactoring a pain. You also
compromise the maintainability of CMake scripts. I can have the best design
in the world and make every effort to hide implementation details from other
things that dont' care about it, but when you deliberately make everything
globally accessible when I have no control over that behavior you make my
code unmaintainable.

 Code documentation does not prevent programmer error. It is error prone to
>> have macros or functions available to the parent directory in which they
>> were defined. What if I was going through and doing some refactoring and I
>> renamed a macro somewhere and also put it a bit further down in the
>> directory tree. Other places that call it will still work and CMake will
>> not
>> give me an error message.
>>
>
> If you are sloppy, such errors can occur. But on the other hand, please
> also consider that it can be very useful to have global macro/function
> names. I will give an example below.


I'm not sloppy, I'm a human being. Programmer derives from the "Human Being"
class. You aren't perfect, so why should CMake assume that? CMake is
obviously putting responsibility on the programmer that is unnecessary. If
we were all perfect, then "functional" would be an acceptable determination
of a production worthy application. CMake should create me a build system,
but also help me in any way possible to make performing that task as easy,
effortless, and as non-error-prone as possible.

Iterative development is a common approach to software. This also applies to
CMake. CMake is complex enough that the code can go stale. Requirements can
change, just like they do in software. What if the directory structure for
projects change? What if a project is removed or added? CMake will
constantly change as the projects change, and with that you perform
refactoring. We've all made the mistake of typing a number wrong, or hitting
an extra key that we did not mean to. We've also all been victims of copy
and paste errors. All of these errors are mistakes typical human beings
make, since we are not perfect. CMake should make every effort to find these
issues for us at the earliest point possible. When I forget to rename a
function, I don't want CMake to accept that because it was defined at some
leaf directory.

Given how most of the directory related properties work, the scope of macros
and functions is inconsistent. It is a common assumption by many programmers
that when I define a macro in CMake in a sub-directory that it will not be
seen or be accessible by any parents.

Also, if you want global macros or functions, why not use include()? If you
don't want to do that, find your root CMakeLists.txt and implement the macro
or function there, and every single child directory in the entire hierarchy
will inherit that particular function or macro and have complete access to
it.

When you go the route of having every single function accessible in a global
manner regardless of where it was defined, you quickly make CMake unreadable
and unmaintainable. When I'm a new employee at a company and I'm trying to
review the CMake build system to try to find what macros or functions I can
call, I'm going to be forced to look at *all* CMake files in the entire
hierarchy instead of going from parent to parent. It becomes extremely
difficult to find all of the macros you can use when they are all scattered
about and all globally accessible.

You also destroy the ability to create 2 macros with the same name. For
example, suppose you have two macros named create_project(). Both will have
completely different implementations. One is defined in c:\foo\project1 and
the other in c:\foo\project2. I now have to arbitrarily give each of them
different names when the same name for both would have been perfectly
acceptable and reasonable.

Again, I do agree to a certain degree. However, it's impossible to prevent
> all errors.


We're not talking about making CMake perfect. We're talking about making
CMake solve problems we know can be solved. We know that it's wrong for
macros, when defined, to become "globally accessible" regardless of
directory depth. I've already established that it is error prone and I have
given many examples to back it up. We know the problem can be solved, and
the only reason we're having this discussion is because you feel it should
not be solved because the only thing that matters is that CMake be
"functional".


>  Again, this is "good enough" for *you*. Obviously not everyone will agree
>> with your outlook on how CMake should work, and the same goes for my
>> opinion
>> on it. I also don't like the mentality most open source projects take of
>> "If
>> you don't like it, implement it yourself". I've actually implemented
>> features that made sense in other open source projects before, only for
>> their authors to arrogantly refuse to apply them because of their personal
>> bias towards it being "good enough".
>>
>
> I understand the frustration, I know it. But then, you have to realize that
> it is ultimately the owners/maintainers of a project who decide what goes in
> and what not. Sometimes it is also difficult for them to keep their project
> on track and not have it evolve into a unmaintainable feature-monster.
> However, good news about open source projects is, that as a last resort you
> can still fork them...


Yes it does get a bit frustrating sometimes. Open source projects right now
are very black & white as far as feature requests go. You either have to
become an active developer on the project to get features implemented, or
you dont' get them implemented at all. In this case, I consider this issue a
bug and not a feature. It's very hard to justify to people at times that a
particular change makes sense and that when it is done it will benefit the
community as a whole. I have a family, a job, and other things going on and
have no time whatsoever to contribute to this project. If I could, I would.
However, doing it myself doesn't help.

All developers have to agree that a particular feature or bug fix makes
sense. If each developer has the "I'm going to do what I want to do"
mentality, you end up creating crappy software. You all have to work as a
team and carefully determine your requirements and what contracts you will
fulfill. So even if I decided to implement this myself in CMake, you
obviously would still have issues or disagreements with the bug fix. I don't
see software development in such a selfish way. The whole reason for me
discussing this issue with you guys is to not just ask you to do it for me,
but to see if the community would find some use in it. If a majority of
people agree that the current behavior is broken and could be fixed, then it
should be fixed.


> I think the best step to take in this case is to call a vote from the
>> community. Let's see how many people would actually like to see child
>> macros
>> not available to their parent directories. I think this is a fundamental
>> concept shared by many languages and makes perfect logical sense.
>>
>> A lot of times I organize projects with a common directory hierarchy in
>> groups. For example, I may have projects structured like this:
>>
>> foo/components/A
>> foo/components/B
>> foo/components/C
>>
>> I can have the foo/components/CMakeLists.txt file define a macro called
>> define_component(). This macro would take advantage of the fact that it
>> knows a little bit about the hierarchical pattern of the directory
>> structure
>> for all component projects A, B, and C. However, this macro is completely
>> useless in a directory called foo/libraries, for example.
>>
>
> Well, then I propose you call that macro A_define_component(). Using this
> naming scheme it becomes very clear what the intent and scope of this macro
> is. Using good and consistent naming conventions greatly help avoiding
> errors.


No, that's not a naming convention, that's name mangling. It's arbitrary and
foolish. The very fact that you've appended arbitrary suffixes/prefixes to
the name implies that there is a fundamental need for a packaging concept,
like namespaces. define_component() is a perfect name and is ambiguous to
which specific component it is defining. This is the very essence of
reusable code. define_component() is reusable, A_define_component() is not.
There's no reason to have 1 macro per component as you suggest.


> Now to the counter-example, where global macros are useful. Let's say that
> A is an application providing a plugin-architecture. B and C are plugins for
> A. To make things easier, foo/components/A/CMakeLists.txt defines a macro,
> say add_A_plugin(), allowing you to pass it some parameters, such as the
> name and the sources of the plugin. The macro then sets things up
> appropriately to create a plugin for A. With your approach one would be
> forced to define the macro either in foo/CMakeLists.txt or
> foo/components/CMakeLists.txt where it doesn't belong.
>
> I do know that the above example is a bit contrived and that such a macro
> probably should go into a AUse.cmake file. But still, I think one can see
> the usefulness of global macro/function names.


The example is a bit hard to work with but here is the way I would do it:

I'd have the following structure:
foo/A
foo/A/plugins/B
foo/A/plugins/C

I would define the add_plugin() macro into foo/A/CMakeLists.txt and call
that macro from projects B and C (they both would have CMakeLists.txt
files). None of this requires any global macros and both B and C inherit the
macro.

I fail to see a need for global macros as you explain them. As I said
before, if you want global macros you have 2 choices. You can either use
includes or define the macro at the root CMakeLists.txt file.


>  Having said that, why should the macro defined in
>> foo/components/CMakeLists.txt be in any way accessible in
>> foo/libraries/CMakeLists.txt? It is error prone and causes suble bugs in
>> CMake that can take a long time to track down.
>>
>
> As I said above, if foo/components/CMakeLists.txt provides some macros
> which other parts of the build system should use, it is very useful.


The problem is that foo/components/CMakeLists.txt would *not* define any
macros useful to other parts of the build system above itself. It is only
useful to its own sub-tree. If you are defining macros in such a way you are
doing something evil and CMake is allowing it. If I had something so useful
on such a global scale as that, I would most certainly either use include()
or define that macro in the highest directory as necessary (up to the root
CMakeLists.txt file).
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://www.cmake.org/pipermail/cmake/attachments/20090304/95d4c9ac/attachment-0001.htm>


More information about the CMake mailing list