[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Re: build profile syntax ideas



Hi,

TLDR: Guillem is right, I'm wrong. Please leave everything as it is. :)

I came to the conclusion that the meaning/semantics of the new syntax can best
be summarized as:

 - by default no build profile is set
 - multiple build profiles can be activated at the same time
 - the restriction syntax is an ordered list of statements that govern whether
   or not a build dependency is kept
 - an exclamation mark in front means: "drop build dependency if the profile
   indicated by the following statement is set"
 - no exclamation mark in front means: "keep build dependency if the profile
   indicated by the following statement is set"
 - processing is done from left to right
 - the first statement for which a profile is set, applies the rule and stops
   further evaluation of other items in the list
 - if the list has been processed and for no statement a profile was set, keep
   the build dependency if at least one statement in the list has an
   exclamation mark in front, otherwise drop the build dependency

The following should only be consumed with enough time at hand and might not
make any sense at all. It does to me so I'm posting it in the hope it might be
found useful by someone. Otherwise the above actually says it all. The below
gives some reason of why this makes sense by linking the meaning of the syntax
to how it is done in Gentoo with USE flags.

Quoting Guillem Jover (2014-02-08 02:02:21)
> > In your test cases I see that for "dep3 <!profile.stage1 profile.notest>"
> > with profiles stage1 and notest both activated, dep3 does not get picked.
> 
> Right (although I rearranged it so that it gets picked now, and I guess
> that could be considered cheating :), I should probably add another case
> demonstrating it being dropped.

Yes, I think that would make things clearer. With your solution, order of the
qualifiers matters. With DEB_BUILD_PROFILE="stage1 notest":

this would be dropped:

	dep3 <!profile.stage1 profile.notest>

but this would get picked:

	dep3 <profile.notest !profile.stage1>

I find it very hard to visualize the logical reason for that in my head. Let me
express in words what both of the above mean.

The first: drop dep3 if we build with stage1. If we dont build with stage1,
then also check if we build with notest. If we dont build with notest then drop
dep3, otherwise keep it.

The second: keep dep3 if we build with notest. If we dont build with notest,
then also check if we build with stage1. If we do build with stage1 then drop
dep3, otherwise keep it.

> Sure. The issue with the disjunction is that adding a new negated restriction
> is not safe anymore, in the common case of building with just one profile,
> and no mixed positive/negative restrictions. What tripped me, was the dep1 in
> the 2/4 and 3/4 cases.
> 
> Say, you don't want a specific dependency on profile.stage1, you have:
> 
>   dep1 <!profile.stage1>
> 
> it gets reduced fine if only profile.stage1 is specified, but then if
> you decide you don't want it either on profile.other, then:
> 
>   dep1 <!profile.stage1 !profile.other>
> 
> will make dep1 suddenly start appearing on profile.stage1.
> 
> This means, that adding negations to an existing negated restriction is
> not safe any longer, because it has side effects, and to get the old
> behaviour you need to know what other restrictions are in place for
> each dependency where profile.stage1 appears so that they can be
> specified as part of the build. This seems highly undesirable to me.
> 
> If this analysis is wrong, I'd like to hear, or if you have a solution
> to that specific problem, I'd gladly revert to a nicer implementation
> that could support also positive/negative restrictions nicely.

I see what you mean.

So lets do it the other way round and lets make a table on what we can probably
agree upon would intuitively be the right meaning:

                       ""       "stage1" "notest" "stage1 notest"
foo <!profile.stage1>  keep     drop     keep     drop
foo <profile.stage1>   drop     keep     drop     keep

Is the above meaning okay with everybody? Here the more difficult ones:

                                      ""       "stage1" "notest" "stage1 notest"
foo <!profile.stage1 !profile.notest> keep     drop     drop     drop
foo <profile.stage1 profile.notest>   drop     keep     keep     keep

If I didnt miss anything, then the above tables match exactly the current
implementation in dpkg by Guillem. It should probably stay that way. It must be
noted that my implementation did not result in the above tables and thus
introduces confusion.

And here the tricky one:

	dep3 <!profile.stage1 profile.notest>

When reading that out loud, what does it say: drop if we build with stage1 and
keep if we build with notest.

I'm at a loss what this should mean even for the most simple setup of "no
profile specified at all". When no profile is specified then surely "notest" is
not specified so we should drop it. But with no profile "stage1" is also not
specified so we should not not drop it (i.e keep it).

While pondering about this I remembered Gentoo and its USE flags. Surely it
would help to see how they solve this. In gentoo it's the other way round (in
both: meaning and ordering).  First they do write which condition to check for
and then the dependency to install like this:

	jpeg? ( media-libs/jpeg )

Meaning: if we build with the USE flag jpeg set to true, then depend on
media-libs/jpeg. This can also be be negated:

	!nophysfs? ( dev-games/physfs ) 

Meaning: if we build with the USE flag nophysfs set to false, then depend on
dev-games/physfs. It can also be (infinitely) nested:

	jpeg? ( !nophysfs? ( dev-games/physfs ) )

So first the USE flag jpeg is checked and if it's true, then nophysfs is
checked and upon that result, depend on dev-games/physfs or not. If jpeg was
set to false, then no further check would be done and no dependency on
dev-games/physfs would be added.

Lets translate the last example to build profiles:

	physfs <!profile.nojpeg profile.physfs>

Notice the translation of jpeg->nojpeg and nophysfs->physfs. This is because
build profiles work in the opposite direction compared to USE flags.

So if the build profile "nojpeg" was given, then the dependency on physfs would
be dropped and no further check would be done. If "nojpeg" was not listed, then
a check for "physfs" would be done. If physfs was set, then the dependency
would be added and if not, it would be dropped.

I hope you can see how this allows to interpret the list of mixed positive and
negative build profiles as it is done by Gentoo USE flags? Just the other way
round because an enabled USE flag means the addition of a dependency and an
enabled build profile means its removal. The '!' works the other way round in
build profiles compared to USE flags. For build profiles, an exclamation mark
means "drop if this is set". For USE flags an exclamation mark means "drop if
this is not set".

Lets see if this rule still fulfills above tables. I translate stage1->full and
notest->test.

Debian:
	dep3 <!profile.stage1>
Gentoo:
	full? ( dep3 )

If a full build is done, depend on dep3. Both agree on that.

Debian:
	dep3 <profile.stage1>
Gentoo:
	!full? ( dep3 )

If no full build is done, depend on dep3. Correct. Now it becomes hairy:

Debian:
	dep3 <!profile.stage1 !profile.notest>
Gentoo:
	full? ( test? ( dep3 ) )

In the table above we agreed that dep3 should be dropped once either stage1 or
notest or both are activated. The Gentoo statement means that dep3 should only
be included if *both* full and test USE flags are set. If either or both of
them are false, the dependency on dep3 is dropped. I hope you can see how this
makes those statements equivalent?

Debian:
	dep3 <profile.stage1 profile.notest>
Gentoo:
	!full? ( !test? ( dep3 ) )

In the table above we agreed that dep3 should be kept only once either stage1 or
notest or both are activated. The Gentoo statement means that dep3 should only
be included if *both* full and test use flags are *not* set. If either or both
of them are true, then the dependency is dropped. I hope this makes sense?

And now to the tricky mixed ones:

Debian:
	dep3 <!profile.stage1 profile.notest>
Gentoo:
	full? ( !test? ( dep3 ) )

Lets recall how I described the Debian statement initially: Drop dep3 if we
build with stage1. If we dont build with stage1, then also check if we build
with notest. If we dont build with notest then drop dep3, otherwise keep it.

Lets do the same for the Gentoo statement: Drop dep3 if we dont do a full build
(no need to check test). If we dont do a full build, then also check if we
build with test. If we dont build with test then add dep3, otherwise drop it.

I hope this still all makes sense. The last part can equivalently be applied to
the reverse situation.

So at least for me, the build profile syntax is now abundantly clear. Order
matters and it is important that it does. Maybe this was actually helpful for
somebody to understand why it makes sense to treat things as they are now
treated and I hope I didnt waste anybodies time.

cheers, josch


Reply to: