{"id":1002,"date":"2019-11-06T08:57:52","date_gmt":"2019-11-06T07:57:52","guid":{"rendered":"https:\/\/blogs.gentoo.org\/mgorny\/?p=1002"},"modified":"2019-11-10T18:31:43","modified_gmt":"2019-11-10T17:31:43","slug":"gentoo-eclass-design-pitfalls","status":"publish","type":"post","link":"https:\/\/blogs.gentoo.org\/mgorny\/2019\/11\/06\/gentoo-eclass-design-pitfalls\/","title":{"rendered":"Gentoo eclass design pitfalls"},"content":{"rendered":"<p>I have written my share of&nbsp;eclasses, and&nbsp;I have made my share of&nbsp;mistakes.  Designing good eclasses is a&nbsp;non-trivial problem, and&nbsp;there are many pitfalls you should be&nbsp;watching for.  In&nbsp;this post, I would like to&nbsp;highlight three of&nbsp;them.<!--more--><\/p>\n<h2>Not all metadata variables are combined<\/h2>\n<p>PMS provides a&nbsp;convenient feature for eclass writers: cumulative handling of&nbsp;metadata variables.  Quoting the&nbsp;relevant passage:<\/p>\n<blockquote><p>The <kbd>IUSE<\/kbd>, <kbd>REQUIRED_USE<\/kbd>, <kbd>DEPEND<\/kbd>, <kbd>BDEPEND<\/kbd>, <kbd>RDEPEND<\/kbd> and&nbsp;<kbd>PDEPEND<\/kbd> variables are handled specially when set by an eclass. They must be accumulated across eclasses, appending the value set by each eclass to the resulting value after the previous one is loaded. Then the eclass-defined value is appended to that defined by the ebuild. [\u2026]<\/p>\n<p><cite><a rel=\"external\" href=\"https:\/\/projects.gentoo.org\/pms\/7\/pms.html#x1-10600010.2\">Package Manager Specification (30th April 2018), 10.2 Eclass-defined Metadata Keys<\/a><\/cite><\/p><\/blockquote>\n<p>That&#8217;s really handy!  However, the&nbsp;important thing that&#8217;s not&nbsp;obvious from this description is that <em>not all metadata variables<\/em> work this way.  The&nbsp;following multi-value variables don&#8217;t: <kbd>HOMEPAGE<\/kbd>, <kbd>SRC_URI<\/kbd>, <kbd>LICENSE<\/kbd>, <kbd>KEYWORDS<\/kbd>, <kbd>PROPERTIES<\/kbd> and&nbsp;<kbd>RESTRICT<\/kbd>.  Surely, some of&nbsp;them are not&nbsp;supposed to&nbsp;be&nbsp;set in&nbsp;eclasses but&nbsp;e.g.&nbsp;the&nbsp;last two are confusing.<\/p>\n<p>This means that technically you need to&nbsp;append when defining them, e.g.:<\/p>\n<pre><code># my.eclass\nRESTRICT+=\" !test? ( test )\"<\/code><\/pre>\n<p>However, that&#8217;s not&nbsp;the&nbsp;biggest problem.  The&nbsp;real issue is that those variables are normally set in&nbsp;ebuilds <em>after inherit<\/em>, so you actually need to&nbsp;make sure that all ebuilds append to&nbsp;them.  For&nbsp;example, the&nbsp;ebuild needs to do:<\/p>\n<pre><code># my-1.ebuild\ninherit my\nRESTRICT+=\" bindist\"<\/code><\/pre>\n<p>Therefore, this design is&nbsp;prone to&nbsp;mistakes at&nbsp;ebuild level.  I&#8217;m going to&nbsp;discuss an&nbsp;alternative solution below.<\/p>\n<h2>Declarative vs functional<\/h2>\n<p>It is common to&nbsp;use declarative style in&nbsp;eclasses \u2014 create a&nbsp;bunch of&nbsp;variables that ebuilds can use to&nbsp;control the&nbsp;eclass behavior.  However, this style has two significant disadvantages.<\/p>\n<p>Firstly, it is prone to&nbsp;typos.  If&nbsp;someone recalls the&nbsp;variable name wrong, and&nbsp;its effects are not&nbsp;explicitly visible, it is very easy to&nbsp;commit an&nbsp;ebuild with a&nbsp;silly bug.  If&nbsp;the&nbsp;effects are&nbsp;visible, it can still give you some quality debugging headache.<\/p>\n<p>Secondly, in&nbsp;order to&nbsp;affect global scope, the&nbsp;variables need to be&nbsp;set before <kbd>inherit<\/kbd>.  This is not&nbsp;trivially enforced, and&nbsp;it is easy to&nbsp;miss that the&nbsp;variable doesn&#8217;t work (or&nbsp;partially misbehaves) when set too&nbsp;late.<\/p>\n<p>The&nbsp;alternative is to&nbsp;use functional style, especially for&nbsp;affecting global scope variables.  Instead of&nbsp;immediately editing variables in&nbsp;global scope and&nbsp;expecting ebuilds to&nbsp;control the&nbsp;behavior via&nbsp;variables, give them a&nbsp;function to do it:<\/p>\n<pre><code># my.eclass\nmy_enable_pytest() {\n  IUSE+=\" test\"\n  RESTRICT+=\" !test? ( test )\"\n  BDEPEND+=\" test? ( dev-python\/pytest[${PYTHON_USEDEP}] )\"\n  python_test() {\n    pytest -vv || die\n  }\n}<\/code><\/pre>\n<p>Note that this function is&nbsp;evaluated in&nbsp;ebuild context, so all variables need appending.  Its main advantage is that it works independently of&nbsp;where in&nbsp;ebuild it&#8217;s called (but&nbsp;if&nbsp;you call it early, remember to&nbsp;append!), and&nbsp;in&nbsp;case of&nbsp;typo you get an&nbsp;explicit error.  Example use in&nbsp;ebuild:<\/p>\n<pre><code># my-1.ebuild\ninherit my\nIUSE=\"randomstuff\"\nRDEPEND=\"randomstuff? ( dev-libs\/random )\"\nmy_enable_pytest<\/code><\/pre>\n<h2>Think what phases to&nbsp;export<\/h2>\n<p>Exporting phase functions is often a&nbsp;matter of&nbsp;convenience.  However, doing it&nbsp;poorly can cause ebuild writers more pain than if&nbsp;they weren&#8217;t exported in&nbsp;the&nbsp;first place.  An&nbsp;example of&nbsp;this is&nbsp;<a rel=\"external\" href=\"https:\/\/gitweb.gentoo.org\/repo\/gentoo.git\/tree\/eclass\/vala.eclass?id=bf316094d6b29542ffcc8cadc7f90a546eee070f\">vala.eclass<\/a> as&nbsp;of&nbsp;today.  It wrongly exports dysfunctional <kbd>src_prepare()<\/kbd>, and&nbsp;all ebuilds have to&nbsp;redefine it anyway.<\/p>\n<p>It is often a&nbsp;good idea to&nbsp;consider how your eclass is&nbsp;going to be&nbsp;used.  If&nbsp;there are&nbsp;both use cases for&nbsp;having the&nbsp;phases exported and&nbsp;for&nbsp;providing utility functions without any&nbsp;phases, it is probably a&nbsp;good idea to&nbsp;split the&nbsp;eclass in&nbsp;two: into <kbd>-utils<\/kbd> eclass that just provides the&nbsp;functions, and&nbsp;main eclass that combines them with phase functions.  A&nbsp;good examples today are <kbd>xdg<\/kbd> and&nbsp;<kbd>xdg-utils<\/kbd> eclasses.<\/p>\n<p>When you do&nbsp;need to&nbsp;export phases, it is wortwhile to&nbsp;consider how different eclasses are going to be&nbsp;combined.  Generally, a&nbsp;few eclass types could be listed:<\/p>\n<ul>\n<li>Unpacking (fetching) eclasses; e.g.&nbsp;<kbd>git-r3<\/kbd> with <kbd>src_unpack()<\/kbd>,<\/li>\n<li>Build system eclasses; e.g.&nbsp;<kbd>cmake-utils<\/kbd>, <kbd>src_prepare()<\/kbd> through <kbd>src_install()<\/kbd>,<\/li>\n<li>Post-install eclasses; e.g.&nbsp;<kbd>xdg<\/kbd>, <kbd>pkg_*inst()<\/kbd>, <kbd>pkg_*rm()<\/kbd>,<\/li>\n<li>Build environment setup eclasses; e.g.&nbsp;<kbd>python-single-r1<\/kbd>, <kbd>pkg_setup()<\/kbd>.<\/li>\n<\/ul>\n<p>Generally, it&#8217;s best to&nbsp;fit your eclass into as&nbsp;few of&nbsp;those as&nbsp;possible.  If&nbsp;you do that, there&#8217;s a&nbsp;good chance that the&nbsp;ebuild author would be&nbsp;able to&nbsp;combine multiple eclasses easily:<\/p>\n<pre><code># my-1.ebuild\nPYTHON_COMPAT=( python3_7 )\ninherit cmake-utils git-r3 python-single-r1<\/code><\/pre>\n<p>Note that since each of&nbsp;those eclasses uses a&nbsp;different phase function set to do&nbsp;its work, they combine just fine!  The&nbsp;inherit order is also irrelevant.  If&nbsp;we e.g.&nbsp;need to&nbsp;add <kbd>llvm<\/kbd> to the&nbsp;list, we just have to&nbsp;redefine <kbd>pkg_setup()<\/kbd>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I have written my share of&nbsp;eclasses, and&nbsp;I have made my share of&nbsp;mistakes. Designing good eclasses is a&nbsp;non-trivial problem, and&nbsp;there are many pitfalls you should be&nbsp;watching for. In&nbsp;this post, I would like to&nbsp;highlight three of&nbsp;them.<\/p>\n","protected":false},"author":137,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"jetpack_publicize_message":"","jetpack_is_tweetstorm":false,"jetpack_publicize_feature_enabled":true},"categories":[11],"tags":[],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/blogs.gentoo.org\/mgorny\/wp-json\/wp\/v2\/posts\/1002"}],"collection":[{"href":"https:\/\/blogs.gentoo.org\/mgorny\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.gentoo.org\/mgorny\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.gentoo.org\/mgorny\/wp-json\/wp\/v2\/users\/137"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.gentoo.org\/mgorny\/wp-json\/wp\/v2\/comments?post=1002"}],"version-history":[{"count":23,"href":"https:\/\/blogs.gentoo.org\/mgorny\/wp-json\/wp\/v2\/posts\/1002\/revisions"}],"predecessor-version":[{"id":1026,"href":"https:\/\/blogs.gentoo.org\/mgorny\/wp-json\/wp\/v2\/posts\/1002\/revisions\/1026"}],"wp:attachment":[{"href":"https:\/\/blogs.gentoo.org\/mgorny\/wp-json\/wp\/v2\/media?parent=1002"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.gentoo.org\/mgorny\/wp-json\/wp\/v2\/categories?post=1002"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.gentoo.org\/mgorny\/wp-json\/wp\/v2\/tags?post=1002"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}