-
Notifications
You must be signed in to change notification settings - Fork 10
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
Style change proposal: target and file lists should never be put on one line #9
Comments
I'll have to think a little bit longer about your proposals but here is my view on the topic and I hope you'll challenge the points you find weak. On proposal nr. 1I find this proposal quite reasonable since what you're proposing is to lean more towards readability of diffs at little to no cost to readability of the code as it's viewed in the file editor. While this would slightly break my assumption about limited configuration would you be happy with extra parameter "list-expansion (temporary name, I don't how to call it better)" with these strategies: target_link_libraries(foo PUBLIC bar)
target_link_libraries(foo PUBLIC bar baz)
target_link_libraries(foo PUBLIC bar PRIVATE baz)
target_link_libraries(
foo
PUBLIC bar
PRIVATE
example::some_util
external::some_lib
external::another_lib
Boost::Boost
)
target_link_libraries(foo PUBLIC bar)
target_link_libraries(
foo
PUBLIC
bar
baz
)
target_link_libraries(
foo
PUBLIC
bar
PRIVATE
baz
)
target_link_libraries(
foo
PUBLIC
bar
PRIVATE
example::some_util
external::some_lib
external::another_lib
Boost::Boost
)
target_link_libraries(
foo
PUBLIC
bar
)
target_link_libraries(
foo
PUBLIC
bar
baz
)
target_link_libraries(
foo
PUBLIC
bar
PRIVATE
baz
)
target_link_libraries(
foo
PUBLIC
bar
PRIVATE
example::some_util
external::some_lib
external::another_lib
Boost::Boost
) On proposal nr. 2I'm against that proposal due to these reasons:
target_link_libraries(
"foo", # if it was named argument it would be `target="foo"`
public=["bar"],
private=[
"example::some_util",
"external::some_lib",
"external::another_lib",
"Boost::Boost",
],
)
add_library(
"foo",
kind="SHARED" # note that cmake doesn't call this option `kind` but I suppose this would be API in python
sources=["one.cpp", "two.cpp"] # again lack of keyword in cmake
)
Current state (lines of focus are shown here with symbol | | |
| | |
| | |
target_link_libraries(
| |foobar
| |PRIVATE
| | |example::some_util
| | |external::some_lib
| | |external::another_lib
| | |Boost::Boost
) |
| |
| |
| |
add_library(
| |foo
| |SHARED
| |source_one.cpp
| |source_two.cpp
) After the proposal: | | | |
| | | |
| | | |
target_link_libraries(|foobar
| |PRIVATE
| | |example::some_util
| | |external::some_lib
| | |external::another_lib
| | |Boost::Boost
) |
| | | |
| | | |
| | | |
add_library(|foo |SHARED
| |source_one.cpp
| |source_two.cpp
)
# before
add_library(lib SHARED
source_one.cpp
source_two.cpp
)
# after
add_library(lib SHARED)
target_sources(
lib
PRIVATE # I've assumed your preference to expanding lists
source_one.cpp
source_two.cpp
) |
Another note: initially when I started implementing the formatter I didn't know how to call this principle for structured and repeatable visual cues which I call now "lines of focus" but I've been inspired by the term from this presentation by Kevlin Henney where Kevlin Henney calls this property "lines of attention". |
Thank you for your quick and detailed reply! I haven't watched Kevlin's talk (yet), but tried to react to all your thoughts regarding prop. nr. 2. On proposal nr. 1: if you're willing to add a configuration option, I'd be absolutely delighted. I especially like that it has three options, not just two. On proposal nr. 2:
Isn't there already some special casing? I don't think the
True, although most commands that are similar are named
I think "function that has a single positional argument, i.e. it uses
I wouldn't deal with them, because if the "object" argument is named, then there's already no readability issue, and if it's an unnamed non-first object argument, then the signature is simply bad IMO. (Which is the case for many builtin functions, but that's the beauty of CMake.)
I think I get what you're talking about here, although I don't feel like it introduces a separate line of focus: my eyes parse
True, although this would incur training my team on using All in all, I still feel like prop. 2 would improve readability, but if you feel like it would require too much work / special casing / etc., I can easily live without it, especially since I have multiple possible workarounds:
So, if you're still not convinced of prop. 2, I'd still be really happy with the acceptance/implementation of prop. 1 only. (Regardless of whether the default style is changed or an option is introduced.) Proposal 2 was more of a combination of "while we're talking about these functions" and "try and make |
Question that popped into my head while writing my previous answer: |
No hard deadlines but I'll try make something for you next week once I find enough time to work a little bit with implementation. You're right about |
Wow, thanks, that would be great! I can test the prototype whenever you need.
Yeah, I have no idea how to format
For a human, this grouping/indenting/commenting style improves readability, but good luck figuring it out automatically. I think the best solution in |
@petamas Please be invited to test out this version which introduces To see the expected effects you can compare how files are formatted under two strategies here (for the sake of demonstration line length is 50 instead of usual 80): |
Thanks, I'll take a look tomorrow! |
@BlankSpruce
It should be formatted this way in either of those cases to support merges on addition:
Or am I missing something? |
The I'd argue that the way you expect formatting in the new example fits the description only for I'm wondering whether we could formulate somewhat simple rule that would consistently (within reason since CMake language isn't exactly friendly here) describe desired formatting that wouldn't be something along the lines "and if the command is called THIS and the argument section is THAT then do [...]". |
I agree with the name change, I just did not know which option you implemented under that name because it kinda fits both. :)
In this comment, you formatted both I'd also say that the original issue (frequently changing lists introducing unnecessary conflicts) applies to this example too.
All my proposals will make decisions on the function call level, not the individual list argument level i.e. either all provided list arguments are allowed to be inlined, or none of them are. (Based on your example formatting, this already seems to be the case, i.e. when you expanded one list argument, you expanded all of them. I like this because it makes the arguments consistent with each other.) Proposal A: always expend lists for function calls with more than one provided list argument, or with a provided list argument containing more than one element
Proposal B: always expend lists for function calls with a provided list argument containing more than one element I feel like the rationale for the third bullet point in proposal A is weaker than the other two, so I tried to make the rule less constraining, allowing (but not mandating) a bit more inlining.
What do you think? Personally, I prefer proposal A, but if you find it too restrictive, I can live with proposal B. (I was more swamped today than I expected, so I couldn't run the testing version on our code yet, sorry.) |
It seems that proposal A is quite nice but I need to make sure I understand it well. Could you show how you would format examples for the made-up command described below? Assume whatever line length you want but please state what it is for the sake of completion. Signature:
Code:
|
Before I show my formatting, I want to note some stuff:
Harry Potter and the Philosopher's StoneDepending on
As you can see, line length only ever impacts the one-value arguments, because proposal A forces BOTH lists to be expanded, because there are two of them ( The Shape of WaterSimilarly to Harry Potter, I'd expect one of these three:
Again, two lists were provided in the call -> both lists must be expanded. ParasiteI'd expect this to be formatted like this, with the usual variations around
Everything Everywhere All at OnceAgain, same principle (two list -> expand lists) with varying
The King's SpeechI'd expect one of these three:
Naturally, the first option would need Everything Everywhere All at Once (again)I want to highlight this specific case you did not ask about, but which I had a problem with for the previous implementation (and we have talked about it, I just want to make sure there's no misunderstanding): I'd expect this to be formatted this way because of the second half of proposal A ("expand lists ... with a provided list argument containing more than one element"), with the usual variations around
In particular, I'd like these to be disallowed:
The Shape of Water (again)Similarly, I'd like to talk about a "stripped" version of this example, where only one director and cast member are provided: I'd expect this to be formatted this way (with the usual
Note that even though both the
and this for
All other examples would be formatted exactly the same way with proposals A and B because all of them either involve lists with two or more items, triggering expansion in both proposals, or (in case of The King's Speech) not triggering expansion in any of them. Again, the handling of |
Excellent work. It seems that my initial understanding was correct. I think these should be the formatting principles for your proposed style:
## line_length: 100
# 2 "items"
try_to_win_best_picture_academy_award("Edge of Tomorrow" "Live Die Repeat")
# 3 "items", 2 titles and 1 DIRECTORS "item"
try_to_win_best_picture_academy_award(
"Edge of Tomorrow"
"Live Die Repeat"
DIRECTORS
"Doug Liman"
)
# 2 "items", 1 title and 1 DIRECTORS "item"
try_to_win_best_picture_academy_award("The Shape of Water" DIRECTORS "Guillermo del Toro")
# line_length: 80
try_to_win_best_picture_academy_award(
"The Shape of Water"
DIRECTORS
"Guillermo del Toro"
)
## line_length: 100
# 2 "items", 1 title and 1 GENRE
try_to_win_best_picture_academy_award("Everything Everywhere All at Once" YEAR 2022)
# 3 "items", 1 title, 1 GENRE and 1 YEAR
try_to_win_best_picture_academy_award(
"Everything Everywhere All at Once"
GENRE "Absurdist comedy-drama"
YEAR 2022
)
# line_length: 30 (kind of absurd but you get the point)
try_to_win_best_picture_academy_award(
"Everything Everywhere All at Once"
YEAR 2022
)
try_to_win_best_picture_academy_award(
"Everything Everywhere All at Once"
GENRE
"Absurdist comedy-drama"
YEAR 2022
)
## line_length: 100
try_to_win_best_picture_academy_award("The Shape of Water" DIRECTORS "Guillermo del Toro")
try_to_win_best_picture_academy_award(
"Everything Everywhere All at Once"
DIRECTORS
"Daniel Scheinert"
"Daniel Kwan"
)
## line_length: 80
try_to_win_best_picture_academy_award(
"The Shape of Water"
DIRECTORS
"Guillermo del Toro"
)
try_to_win_best_picture_academy_award(
"Everything Everywhere All at Once"
DIRECTORS
"Daniel Scheinert"
"Daniel Kwan"
)
## line_length: 40
# CAST and DIRECTORS have "multi value" semantics, GENRE and YEAR have "one value" semantics
try_to_win_best_picture_academy_award(
"Everything Everywhere All at Once"
DIRECTORS
"Daniel Scheinert"
"Daniel Kwan"
CAST
"Michelle Yeoh"
GENRE "Absurdist comedy-drama"
YEAR 2022
) This should lead to the following in the usual codebase: ## line_length: 80
add_library(foo SHARED)
add_library(
foo
SHARED
EXCLUDE_FROM_ALL
)
add_library(
foo
SHARED
a.cpp
b.cpp
c.cpp
)
target_link_library(foo PUBLIC bar)
target_link_library(
foo
PUBLIC
bar
baz
)
target_link_library(
foo
PUBLIC
bar
PRIVATE
baz
) I think with this set of rules I'll be able to cook something up soon.
Basically the rule is: once you've expanded there's no going back. Hence the last layout is the one it produces even if it violates line length limit. |
@petamas |
Sorry for disappearing, I'll test & reply tomorrow. |
Hi, |
Not a problem. Take your time. :) |
(I'll write a separate comment about why I'm not 100% sure the approach described above matches what I need.) Hi, I've tried testing it, but something isn't right. I've installed the package in a venv from your branch:
Then, I ran it this way:
Result was:
This differs significantly from |
You've done everything correctly. Somehow it doesn't work with arguments passed in command line. I'll push the correction soon. |
I've pushed code to the same branch. It should work now. I'm eager to see your comments about this style. |
Note: this has been written before I checked the output Re: #9 (comment) First of all, what do you consider an item? In my mind, there are the following types of items:
I'd consider each of these 5 argument types an "item", i.e.
Why is this requirement needed? Neither of my proposals (A and B) says anything about the number of keyworded items with option or one value semantics, or single-value positional items. They are only addressing the specific formatting issues related to keyworded items with multi value semantics, and to multi-value positional items. The difference is that proposal B forces expansion of the command if any of these kinds of items have at least two values assigned to them, while proposal A forces expansion on the mere presence of two "multi-value semantics" items in addition to the situations expanded by proposal B. On more concrete examples:
These two together are basically equivalent to what I intended proposal B to be, at least for keyworded items with multi-value semantics. I'd propose dropping the "keyworded" part, and extend these rules to positional items with multi-value semantics too. (I'm fine with only implementing this for builtins, as detecting multi-value positional items may be nontrivial for user-defined functions.)
This is a useful clarification, but I think it is not a change compared to how gersemi works by default. (Or is it?) In conclusion, I'd drop the "command invocation might be inlined with at most 2 "items"" rule, and make the other three apply to positional multi-value arguments of builtin functions. Next comment will cite examples from our codebase that I found problematic with the current implementation of |
(It is implied by my last paragraph, but not said explicitly: with your last fix it works as you intended, so I could run it on the codebase, and collect examples for my next comment). |
First impression is that I'm probably fine with dropping the rule That being said I'd like to see your perspective on yet another example. I wonder if this would "feel right" regardless of the fact that you could easily reason it is consistent with the rules: set(OPTIONS LIBRARY_TYPE)
set(ONE_VALUE_KEYWORDS LIBRARY_NAME)
set(MULTI_VALUE_KEYWORDS DEPENDENCIES)
cmake_parse_arguments(PREFIX "${OPTIONS}" "${ONE_VALUE_KEYWORDS}" "${MULTI_VALUE_KEYWORDS}" ${ARGN})
add_library(${PREFIX_LIBRARY_NAME} ${PREFIX_LIBRARY_TYPE})
target_link_library(${PREFIX_LIBRARY_NAME} PRIVATE ${PREFIX_DEPENDENCIES} Boost::boost) # adding boost for the sake of example This invocation of With set(OPTIONS LIBRARY_TYPE)
set(ONE_VALUE_KEYWORDS LIBRARY_NAME)
set(MULTI_VALUE_KEYWORDS DEPENDENCIES)
cmake_parse_arguments(PREFIX "${OPTIONS}" "${ONE_VALUE_KEYWORDS}" "${MULTI_VALUE_KEYWORDS}" ${ARGN})
add_library(${PREFIX_LIBRARY_NAME} ${PREFIX_LIBRARY_TYPE})
target_link_library(
${PREFIX_LIBRARY_NAME}
PRIVATE
${PREFIX_DEPENDENCIES}
Boost::boost
) # adding boost for the sake of example Example like that made me think that perhaps counting positional arguments should participate into "let's expand now" functionality. |
Personally, I'm fine with this formatting - while a function with five positional arguments is bad interface design in general, I don't feel that it should be expanded unless the line gets too long. Also, specifically the |
As promised, I collected a bunch of examples from our codebase where the command invocation might be inlined with at most 2 "items" rule needlessly expands the line:
I noticed that you nest properties as if they were keyworded arguments when they all part of a
However, this is not the case for
This becomes even messier-looking if the property has multiple values (this is not supported by
I'd prefer these to be formatted this way if
Or I'd allow a slightly terser syntax too:
Or, in case of the middle one, I'd also allow inlining the whole call if the line length limit allows it:
I understand this would involve some special-casing around Note that this is much less of a concern on my side, it is just something I've spotted; these calls are rare enough in our codebase that I'm fine with wrapping them / leaving their formatting as gersemi does them. Our codebase managed to crash
This seem to be present in the "stock" version of I noticed that long It seems like the "stock" version behaves the same, so this has nothing to do with It seems like whatever special-casing you have for the |
Could it be made a recognized distinction? If not, do you have an idea for handling |
It's not necessary, I'll fix it without an issue. I'm guessing you use variables to introduce options which is something I haven't seen yet.
No, it's just the name + parenthesis fits into 4 characters, exact same amount as indentation and I remember vividly that I made decision to treat it like that. Not very good example of consistency, isn't it?
Yes, it can be made (and I think I should have done it earlier), it'll just take a little more time. Nice observation about |
Yeah, it is in a file I haven't ever seen, and in a function that is used a single time, so is not a regular thing. :D We have some weird stuff lurking around in the dark corners of our codebase...
Well, I actually like the result, but it's not very consistent, yeah. :D I also understand why you made the decision, the 4-wide line followed by a 4-wide indent would have looked weird.
Thanks!
Happy to help! Feel free to ping me if you need more examples / wanna validate expected layouts with a second set of eyeballs. |
Introduced strategies: - 'favour-inlining' delays expansion of the code as long as possible - 'favour-expansion' starts expansion when list of arguments has more than two items or keyworded item has more than one element Fix all the issues found out in discussion for issue #9
I've been busy lately hence the delay. Here's the third attempt. Let's hope that third time's the charm indeed. All the issues mentioned in your comments, @petamas, are implemented there. |
Hey, don't apologize, you're the one doing me a favour. :) Thank you for your work so far! I'm also quite busy this week, I have a bunch of deadlines, but I'll take the new version for a spin early next week. |
Sorry for disappearing again - I did test the new version last week, but did not have the time to type up my findings until now. In general, the expansion algorithm seems to work as intended, there are only minor troubles with the handling of specific commands. As before, I'll be using Control-flow statementsIn case of certain control flow statements and other "language" commands, Given the following:
the output is this:
I assume this happens because Similarly,
gets turned into
I also assume something similar is the cause for changing this:
to this:
I think these should be special-cased to be exempt from the Unrecognized functionsIt seems to me that when
gets turned into this:
I think both should be kept as one-liners, because they're under the limit (120 characters), and there are no multi-valued arguments present. Unrecognized argumentsIt seems like
gets turned into this:
, but
|
Brief answers:
|
Thanks for the follow-up!
I can accept this formatting as it is, the description is quite long usually anyways, triggering expansion, and nesting makes sense, it was just surprising to see first, is all. So from my PoV, changing this can be even dropped if you don't have time / don't want to add special handling to it. (Most of our cache variables are |
Not really funny thing about:
It turns out that unless I make some heuristic about |
Introduced strategies: - 'favour-inlining' delays expansion of the code as long as possible - 'favour-expansion' starts expansion when list of arguments has more than two items or keyworded item has more than one element Fix all the issues found out in discussion for issue #9
Here's the fourth attempt. That single |
@petamas : Thank you! I'm still testing & evaluating in general, but wanted to notify you that this crashes gersemi:
I'm simply running |
Introduced strategies: - 'favour-inlining' delays expansion of the code as long as possible - 'favour-expansion' starts expansion when list of arguments has more than two items or keyworded item has more than one element Fix all the issues found out in discussion for issue #9
@petamas I've updated the branch to fix that error. |
@BlankSpruce: I only managed to find a single (very minor) inconsistency:
vs
Is this because you can only supply one directory, while (in theory) you can supply multiple targets? I'm fine with it as-is, just want to understand. Apart from that, this latest version is perfect for my goals, thanks for implementing it for us! I plan to integrate the released version into our codebase in May, and refer back if any issues come up over time. Thanks again! |
That's exactly the reason. The signature allows that:
With that concluding major milestone I'll try to release 0.9 version today that has this new style available. For any further issues after the release just open new issue. |
Will do, and thanks again! It's really cool that you were willing to work through this with me, I cannot express enough how grateful I am. |
Introduced strategies: - 'favour-inlining' delays expansion of the code as long as possible - 'favour-expansion' starts expansion when list of arguments has more than two items or keyworded item has more than one element Fix all the issues found out in discussion for issue #9
First of all, I was really happy when I found
gersemi
, it looks pretty great, and it is maintained, unlikecmake-format
, yay! I personally prefer less opinionated formatters, but the default style ofgersemi
fits our use case fairly well. However, there is one specific case where I feel like it could be improved, so I'm proposing a style enhancement.The README shows this example of formatting:
I'd argue that
target_link_libraries()
and similar functions should always be formatted this way:add_library()
should also always be formatted this way:The two key changes:
PUBLIC
section would've fit on one line, every dependency was broken into a separate line.Rationale for nr. 1:
gersemi
to break stuff into multiple lines, then removing something else may trigger it to put everything in one line again, causing unnecessarily large diffs.PUBLIC
andPRIVATE
section, it's more pleasing aesthetically for them to be formatted consistently.Rationale for nr. 2:
object.func(arg1, arg2)
instead offunc(object, arg1, arg2)
.) I'd consider thetarget_whatever()
functions to be "methods" on the target object, and express this by putting them on the same line.add_library()
/add_executable()
without any flags, there is no clear separation between the target name and the list of files; putting the target name next to the function call and the files in separate lines with different indenting makes it more distinct.STATIC
/SHARED
,EXCLUDE_FROM_ALL
, etc.), those would look out of place "mixed in" with the file list.I'd apply the above changes to
add_library()
,add_executable()
,target_link_libraries()
,target_sources()
,target_precompile_headers()
, and possiblytarget_include_directories()
,target_link_directories()
,target_compile_definitions()
,target_compile_features()
,target_compile_options()
,target_link_options()
.What do you think? Nr. 1 is the really important part for me for the 5 highlighted functions, the other functions and nr. 2 is more of a "if we're already talking about this" thing. I'm also torn on whether function calls with only one provided list argument containing one value (eg.
target_include_directories(Foo PUBLIC include)
) should warrant an exception to the "always multiline" rule.The text was updated successfully, but these errors were encountered: