[{"id":2176,"web_url":"https://patchwork.libcamera.org/comment/2176/","msgid":"<e152b555-9da3-279a-a314-f67cc063c48e@ideasonboard.com>","date":"2019-07-05T11:18:31","subject":"Re: [libcamera-devel] [PATCH] libcamera: Rework automatic version\n\tgeneration to avoid rebuilds","submitter":{"id":4,"url":"https://patchwork.libcamera.org/api/people/4/","name":"Kieran Bingham","email":"kieran.bingham@ideasonboard.com"},"content":"Hi Laurent,\n\nOn 05/07/2019 09:29, Laurent Pinchart wrote:\n> Commit b817bcec6b53 (\"libcamera: Auto generate version information\")\n> generates version information in order to automatically include it\n> various locations (Sphinx and Doxygen documentation, libcamera::version\n> variable available at runtime, and version.h available at compile time).\n> Unfortunately this causes lots of unnecessary rebuilds when modifying\n> the git tree state, which hinders development.\n\nDid you perform any measurements here?\n\nYou are right, that due to the version string changing, any time the\ngit-version returns a different string, extra steps are required to\nre-link that information.\n\nfor testing below, the following command is used to show all actual\ntasks performed, and remove parallelisation from the equation:\n\n   /usr/bin/time -v ninja -j1 -v -d explain -d stats\n\n\nA previous no-op rebuild which did not consider the version string took:\n (taken at 021af795c298d10317c2931dc5ba05d887c49ab6)\n\n\tCommand being timed: \"ninja -j1 -v -d explain -d stats\"\n\tUser time (seconds): 0.01\n\tSystem time (seconds): 0.01\n\tPercent of CPU this job got: 100%\n\tElapsed (wall clock) time (h:mm:ss or m:ss): 0:00.02\n\n\nCertainly fast, - because it's a full no-op.\n\n\nWith my series, A no-op rebuild with the same version string (taken at\n0de1a9f318ecf9f3d069b8a4a4a02ef0391cfb04) takes:\n\n\tCommand being timed: \"ninja -j1 -v -d explain -d stats\"\n\tUser time (seconds): 0.16\n\tSystem time (seconds): 0.02\n\tPercent of CPU this job got: 101%\n\tElapsed (wall clock) time (h:mm:ss or m:ss): 0:00.18\n\n\nSlower, yes, but not IMO a severe penalty or development hindrance.\n\nThe only executed command is:\n\n[1/37] meson --internal vcstagger version.h.in version.h v0.0  @VCS_TAG@\n'(.*)' utils/gen-version.sh\n\n\n\nA 'no-op-version-changed' (again at 0de1a9f, which excludes this patch)\ntakes:\n\n\t# 'git commit --amend' executed to change the git-version\n           without any code change\n\n\tCommand being timed: \"ninja -j1 -v -d explain -d stats\"\n\tUser time (seconds): 2.81\n\tSystem time (seconds): 0.34\n\tPercent of CPU this job got: 103%\n\tElapsed (wall clock) time (h:mm:ss or m:ss): 0:03.05\n\n\n7 commands were executed: <output reduced for brevity here>\n\n[1/37] meson --internal vcstagger version.h.in version.h v0.0  @VCS_TAG@\n'(.*)' utils/gen-version.sh\n[2/37] c++ -c src/libcamera/control_types.cpp # An autogenerated file\n[3/37] c++ -c camera_manager.cpp # Due to version str\n[4/37] c++ -o src/libcamera/libcamera.so # Relink libcamera.so\n[5/37] meson --internal symbolextractor libcamera.so.symbols\n[6/7] c++ -c ../src/qcam/main_window.cpp\n[7/7] c++ -o src/qcam/qcam # Regenerated because it references the\nversion.h which includes the direct string which has changed.\n\n\nWhen run without the -j1 I have an 8-core CPU, so 3.05 linear seconds\nbecames 1.87 elapsed. Perhaps this is still noticeable on slower build\nmachines. Of course - this is only if the *git version* changes, so some\ndevelopment change has likely occurred and other objects are probably\nbeing rebuilt too.\n\n\nBut nothing there seems unreasonable, perhaps excepting the\nre-generation of the control_types.cpp.\n\nWith *this* patch,\n\na 'no-op' rebuild with the same version string takes:\n\n\tCommand being timed: \"ninja -j1 -v -d explain -d stats\"\n\tUser time (seconds): 0.17\n\tSystem time (seconds): 0.02\n\tPercent of CPU this job got: 99%\n\tElapsed (wall clock) time (h:mm:ss or m:ss): 0:00.20\n\nand has indeed generated exactly one command generation: <cmd edited for\nbrevity>\n\n[1/36] meson --internal vcstagger version.cpp.in version.cpp v0.0\n@VCS_TAG@ '(.*)' gen-version.sh\n\n\nNot that there was one less check performed internally, so this is now\n[1/36] where before it was [1/37]\n\n\nand a 'no-op-version-changed' rebuild takes:\n\n\t# git commit --amend; edit message, quit to generate new sha1.\n\n\tCommand being timed: \"ninja -j1 -v -d explain -d stats\"\n\tUser time (seconds): 0.94\n\tSystem time (seconds): 0.19\n\tPercent of CPU this job got: 98%\n\tElapsed (wall clock) time (h:mm:ss or m:ss): 0:01.16\n\n\nso 3.05 elapsed time for non-parallel build is down to 1.16. Certainly\ncan't fault that as an improvement. For parallel builds, this patch\nbrings Elapsed time down to 1.07 from 1.87.\n\nAgain for completeness, here's the commands which were now executed:\n\n\n\n[1/36] meson --internal vcstagger version.cpp.in\nsrc/libcamera/version.cpp v0.0 src/libcamera @VCS_TAG@ '(.*)'\nsrc/libcamera/gen-version.sh /home/linuxembedded/iob/libcamera/libcamera\n[2/36] c++ -c src/libcamera/version.cpp # Our autogenerated version.cpp\n[3/36] c++ -o src/libcamera/libcamera.so # Re-link\n[4/36] meson --internal symbolextractor libcamera.so.symbols\n[5/5] /usr/bin/doxygen Documentation/Doxyfile\n\n\n\n> The problem is caused by the generated version.h being listed as a\n> dependency for the whole libcamera. This is required as meson (to the\n> best of my knowledge) doesn't provide a way to explicitly specify the\n> dependency of a single object file (camera_manager.o in this case, as\n\nSingle objects determine their dependencies through the compiler parsing\ntheir includes, so that tells them what needs to be rebuilt - but\ndoesn't link to the vcs_tag object indeed. Perhaps this is a limitation\nin meson that could be updated, but I don't think it's necessary.\n\nThe dependencies are checked in sequence to ensure that the version tag\nis updated before it is used. As far as I can see, no full rebuild\noccurs, (with the exception of the controls file which is also\nauto-generated).\n\n\n> camera_manager.cpp is the only consumer of the generated version string)\n> on the custom target used to generate version.h. The dependency can't be\n> automatically detected at build time, like dependencies on normal\n> headers that are generated by parsing the source, because the version.h\n> header may not exist yet. The build could then fail in a racy way.\n\nCorrect, the normal dependency generation can't queue the execution of\nthe vcs_tag command, but it does mean that only objects which include\nversion.h directly are rebuilt, which I think is as expected?\n\n\n> This change attempts at solving the issue by generating a version.cpp\n> instead of a version.h. This minimises the number of files that need to\n> be rebuild when then git tree state changes, while retaining the main\n> purpose of the original automatic version generation, the ability to\n> access the git-based version string at runtime.\n\nI'm fine with this, it's more akin to what I originally proposed.\n\nBy removing the version string from the header, it means that qcam does\nnot get rebuilt each time as it now only gets the runtime version string.\n\n\n> The Sphinx and Doxygen documentation however lose git-based version\n> information, to prevent a full documentation rebuild for every commit.\n> We may want to investigate how to preserve detailed version information\n> there, as well as how to make it available in a header file for\n> applications. For the time being, this commit should be a good\n> compromise to avoid unnecessary recompilation, and additional features\n> can be built on top of it after taking the time to consider and test\n> them carefully.\n\nHaving the build information directly linked into the documentation was\na key point of my series. I wanted the documentation to specify the\nversion that it represents.\n\nI agree that there is a meson issue there that because the version given\nto Doxygen is only generated at configure time it could get stale.\n\nSo there's a policy point to consider there, As anything which generates\nthe version for public consumption should do a full build - we can\nensure that it does do any necessary reconfiguration stage. In fact I\nbelieve you can \"ninja reconfigure\" without requiring a full rebuild too...\n\n\n> \n> Fixes: b817bcec6b53 (\"libcamera: Auto generate version information\")\n\n'Fixes' seems a strong choice to me.\nI don't seem to have the same concerns as you regarding b817, but lets\nmove on.\n\n\nAcked-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n\n\n\n> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> ---\n>  Documentation/Doxyfile.in                     |  4 ++-\n>  meson.build                 |  8 +-----\n>  {version.h.in => version.h} |  4 ---\n>  meson.build                                   |  4 +--\n>  src/libcamera/camera_manager.cpp              |  5 ----\n>  {utils => src/libcamera}/gen-version.sh       |  0\n>  src/libcamera/meson.build                     |  8 +++++-\n>  src/libcamera/version.cpp.in                  | 25 +++++++++++++++++++\n>  8 files changed, 37 insertions(+), 21 deletions(-)\n>  rename {version.h.in => version.h} (79%)\n>  rename {utils => src/libcamera}/gen-version.sh (100%)\n>  create mode 100644 src/libcamera/version.cpp.in\n> \n> diff --git a/Documentation/Doxyfile.in b/Documentation/Doxyfile.in\n> index cad85ff979f8..ed111909b603 100644\n> --- a/Documentation/Doxyfile.in\n> +++ b/Documentation/Doxyfile.in\n> @@ -791,7 +791,9 @@ WARN_LOGFILE           =\n>  # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING\n>  # Note: If this tag is empty the current directory is searched.\n>  \n> -INPUT                  = \"@TOP_SRCDIR@/include/libcamera\" \"@TOP_SRCDIR@/src/libcamera\"\n> +INPUT                  = \"@TOP_SRCDIR@/include/libcamera\" \\\n> +\t\t\t \"@TOP_SRCDIR@/src/libcamera\" \\\n> +\t\t\t \"@TOP_BUILDDIR@/src/libcamera\"\n>  \n>  # This tag can be used to specify the character encoding of the source files\n>  # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses\n> diff --git a/meson.build b/meson.build\n> index 6f81f1117318..bafa103d141b 100644\n> --- a/meson.build\n> +++ b/meson.build\n> @@ -14,15 +14,9 @@ libcamera_api = files([\n>      'signal.h',\n>      'stream.h',\n>      'timer.h',\n> +    'version.h',\n>  ])\n>  \n> -gen_version = join_paths(meson.source_root(), 'utils', 'gen-version.sh')\n> -\n> -version_h = vcs_tag(command : [gen_version, meson.current_source_dir()],\n> -                    input : 'version.h.in',\n> -                    output : 'version.h',\n> -                    fallback : 'v0.0')\n> -\n>  gen_header = files('gen-header.sh')\n>  \n>  libcamera_h = custom_target('gen-header',\n> diff --git a/version.h.in b/version.h\n> similarity index 79%\n> rename from version.h.in\n> rename to version.h\n> index e49b36962aed..75d57d48af0d 100644\n> --- a/version.h.in\n> +++ b/version.h\n> @@ -3,16 +3,12 @@\n>   * Copyright (C) 2019, Google Inc.\n>   *\n>   * version.h - Library version information\n> - *\n> - * This file is auto-generated. Do not edit.\n>   */\n>  #ifndef __LIBCAMERA_VERSION_H__\n>  #define __LIBCAMERA_VERSION_H__\n>  \n>  #include <string>\n>  \n> -#define LIBCAMERA_VERSION \"@VCS_TAG@\"\n> -\n>  namespace libcamera {\n>  \n>  extern const std::string version;\n> diff --git a/meson.build b/meson.build\n> index 342b3cc76a93..271b538bdae8 100644\n> --- a/meson.build\n> +++ b/meson.build\n> @@ -1,8 +1,6 @@\n>  project('libcamera', 'c', 'cpp',\n>      meson_version : '>= 0.40',\n> -    version : run_command('utils/gen-version.sh',\n> -                          '@0@'.format(meson.source_root()),\n> -                          check : true).stdout().strip(),\n> +    version : 'v0.0',\n\nI'm unhappy that this patch changes the project version, but you seem to\nbe very keen to remove this, so I concede.\n\nIn the long run, I'd perhaps envisage having an option on gen-version\nwhich prepares a 'simple' version, which will return the latest known\nrelease without extra labels or commit specific information, that is\nprovided to the meson.project_version.\n\n\n>      default_options : [\n>          'werror=true',\n>          'warning_level=2',\n> diff --git a/src/libcamera/camera_manager.cpp b/src/libcamera/camera_manager.cpp\n> index c5da46b4062d..69503c6e8726 100644\n> --- a/src/libcamera/camera_manager.cpp\n> +++ b/src/libcamera/camera_manager.cpp\n> @@ -26,11 +26,6 @@ namespace libcamera {\n>  \n>  LOG_DEFINE_CATEGORY(Camera)\n>  \n> -/**\n> - * \\brief The library global version string\n> - */\n> -const std::string version(LIBCAMERA_VERSION);\n> -\n>  /**\n>   * \\class CameraManager\n>   * \\brief Provide access and manage all cameras in the system\n> diff --git a/utils/gen-version.sh b/src/libcamera/gen-version.sh\n> similarity index 100%\n> rename from utils/gen-version.sh\n> rename to src/libcamera/gen-version.sh\n> diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build\n> index 336f4f066fac..c35ecb988d72 100644\n> --- a/src/libcamera/meson.build\n> +++ b/src/libcamera/meson.build\n> @@ -79,8 +79,14 @@ control_types_cpp = custom_target('control_types_cpp',\n>  \n>  libcamera_sources += control_types_cpp\n>  \n> +version_cpp = vcs_tag(command : ['gen-version.sh', meson.source_root()],\n> +                      input : 'version.cpp.in',\n> +                      output : 'version.cpp',\n> +                      fallback : meson.project_version())\n> +\n> +libcamera_sources += version_cpp\n> +\n>  libcamera_deps = [\n> -    declare_dependency(sources : version_h),\n>      cc.find_library('dl'),\n>      libudev,\n>  ]\n> diff --git a/src/libcamera/version.cpp.in b/src/libcamera/version.cpp.in\n> new file mode 100644\n> index 000000000000..055dc0b5c5ad\n> --- /dev/null\n> +++ b/src/libcamera/version.cpp.in\n> @@ -0,0 +1,25 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2019, Google Inc.\n> + *\n> + * version.cpp - libcamera version\n> + *\n> + * This file is auto-generated. Do not edit.\n> + */\n> +\n> +#include <libcamera/version.h>\n> +\n> +/**\n> + * \\file version.h\n> + * \\brief libcamera version\n> + */\n> +\n> +namespace libcamera {\n> +\n> +/**\n> + * \\var libcamera::version\n> + * \\brief The library global version string\n> + */\n> +const std::string version(\"@VCS_TAG@\");\n> +\n> +} /* namespace libcamera */\n>","headers":{"Return-Path":"<kieran.bingham@ideasonboard.com>","Received":["from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 0DAC660C23\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri,  5 Jul 2019 13:18:35 +0200 (CEST)","from [192.168.0.20]\n\t(cpc89242-aztw30-2-0-cust488.18-1.cable.virginm.net [86.31.129.233])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 4A44024B;\n\tFri,  5 Jul 2019 13:18:34 +0200 (CEST)"],"DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1562325514;\n\tbh=pABZA0OKTydVb7ir7hce60OmlzrWWdrCvlKhS+Q3mGg=;\n\th=Reply-To:Subject:To:References:From:Date:In-Reply-To:From;\n\tb=AWAbrDb9JRjMaDo+Htwy60rOwwOpTjOL5PN4iK38OPM24OTfSQiMjQb8TK8mpjGWl\n\tf39wwISCmexa8LQpImzAhw9n/szbbbjGzJWDRxiTBFztVDKRNmR9CMU4CKC08pFQeX\n\tnhueylsgddhfFr/MvQhVgu6JbsThDUBR1dtmGd74=","Reply-To":"kieran.bingham@ideasonboard.com","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>,\n\tlibcamera-devel@lists.libcamera.org","References":"<20190705082916.13412-1-laurent.pinchart@ideasonboard.com>","From":"Kieran Bingham <kieran.bingham@ideasonboard.com>","Openpgp":"preference=signencrypt","Autocrypt":"addr=kieran.bingham@ideasonboard.com; keydata=\n\t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\n\tsbsRB8KNNvVXBOVNwko86rQqF9drZuw=","Organization":"Ideas on Board","Message-ID":"<e152b555-9da3-279a-a314-f67cc063c48e@ideasonboard.com>","Date":"Fri, 5 Jul 2019 12:18:31 +0100","User-Agent":"Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101\n\tThunderbird/60.7.1","MIME-Version":"1.0","In-Reply-To":"<20190705082916.13412-1-laurent.pinchart@ideasonboard.com>","Content-Type":"text/plain; charset=utf-8","Content-Language":"en-GB","Content-Transfer-Encoding":"8bit","Subject":"Re: [libcamera-devel] [PATCH] libcamera: Rework automatic version\n\tgeneration to avoid rebuilds","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.23","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","X-List-Received-Date":"Fri, 05 Jul 2019 11:18:35 -0000"}},{"id":2177,"web_url":"https://patchwork.libcamera.org/comment/2177/","msgid":"<20190705125934.GD4994@pendragon.ideasonboard.com>","date":"2019-07-05T12:59:34","subject":"Re: [libcamera-devel] [PATCH] libcamera: Rework automatic version\n\tgeneration to avoid rebuilds","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"Hi Kieran,\n\nOn Fri, Jul 05, 2019 at 12:18:31PM +0100, Kieran Bingham wrote:\n> On 05/07/2019 09:29, Laurent Pinchart wrote:\n> > Commit b817bcec6b53 (\"libcamera: Auto generate version information\")\n> > generates version information in order to automatically include it\n> > various locations (Sphinx and Doxygen documentation, libcamera::version\n> > variable available at runtime, and version.h available at compile time).\n> > Unfortunately this causes lots of unnecessary rebuilds when modifying\n> > the git tree state, which hinders development.\n> \n> Did you perform any measurements here?\n\nOnly subjective measurement when developing on top of the automatic\nversion generation. I've now measured the actual time. The test case is\na moving between a base commit and a commit directly on top that only\ntouches a .cpp file (thread.cpp in this case, which I still have to\npost). Running plain 'time ninja' on my machine (2 cores with\nhyperthreading), I went from\n\n[35/35] Linking target test/timer.\n\nreal    0m1.737s\nuser    0m3.342s\nsys     0m0.584s\n\nto\n\n[39/39] Linking target src/qcam/qcam.\n\nreal    0m3.376s\nuser    0m8.669s\nsys     0m0.898s\n\nThe increase is most probably not as dramatic as it felt when I realised\nit was an undesired side effect I hadn't foreseen, but I would still\navoid increasing build time if possible.\n\n> You are right, that due to the version string changing, any time the\n> git-version returns a different string, extra steps are required to\n> re-link that information.\n> \n> for testing below, the following command is used to show all actual\n> tasks performed, and remove parallelisation from the equation:\n> \n>    /usr/bin/time -v ninja -j1 -v -d explain -d stats\n> \n> \n> A previous no-op rebuild which did not consider the version string took:\n>  (taken at 021af795c298d10317c2931dc5ba05d887c49ab6)\n> \n> \tCommand being timed: \"ninja -j1 -v -d explain -d stats\"\n> \tUser time (seconds): 0.01\n> \tSystem time (seconds): 0.01\n> \tPercent of CPU this job got: 100%\n> \tElapsed (wall clock) time (h:mm:ss or m:ss): 0:00.02\n> \n> \n> Certainly fast, - because it's a full no-op.\n> \n> \n> With my series, A no-op rebuild with the same version string (taken at\n> 0de1a9f318ecf9f3d069b8a4a4a02ef0391cfb04) takes:\n> \n> \tCommand being timed: \"ninja -j1 -v -d explain -d stats\"\n> \tUser time (seconds): 0.16\n> \tSystem time (seconds): 0.02\n> \tPercent of CPU this job got: 101%\n> \tElapsed (wall clock) time (h:mm:ss or m:ss): 0:00.18\n> \n> \n> Slower, yes, but not IMO a severe penalty or development hindrance.\n> \n> The only executed command is:\n> \n> [1/37] meson --internal vcstagger version.h.in version.h v0.0  @VCS_TAG@\n> '(.*)' utils/gen-version.sh\n> \n> \n> \n> A 'no-op-version-changed' (again at 0de1a9f, which excludes this patch)\n> takes:\n> \n> \t# 'git commit --amend' executed to change the git-version\n>            without any code change\n> \n> \tCommand being timed: \"ninja -j1 -v -d explain -d stats\"\n> \tUser time (seconds): 2.81\n> \tSystem time (seconds): 0.34\n> \tPercent of CPU this job got: 103%\n> \tElapsed (wall clock) time (h:mm:ss or m:ss): 0:03.05\n> \n> \n> 7 commands were executed: <output reduced for brevity here>\n> \n> [1/37] meson --internal vcstagger version.h.in version.h v0.0  @VCS_TAG@\n> '(.*)' utils/gen-version.sh\n> [2/37] c++ -c src/libcamera/control_types.cpp # An autogenerated file\n> [3/37] c++ -c camera_manager.cpp # Due to version str\n> [4/37] c++ -o src/libcamera/libcamera.so # Relink libcamera.so\n> [5/37] meson --internal symbolextractor libcamera.so.symbols\n> [6/7] c++ -c ../src/qcam/main_window.cpp\n> [7/7] c++ -o src/qcam/qcam # Regenerated because it references the\n> version.h which includes the direct string which has changed.\n> \n> \n> When run without the -j1 I have an 8-core CPU, so 3.05 linear seconds\n> becames 1.87 elapsed. Perhaps this is still noticeable on slower build\n> machines. Of course - this is only if the *git version* changes, so some\n> development change has likely occurred and other objects are probably\n> being rebuilt too.\n> \n> \n> But nothing there seems unreasonable, perhaps excepting the\n> re-generation of the control_types.cpp.\n> \n> With *this* patch,\n> \n> a 'no-op' rebuild with the same version string takes:\n> \n> \tCommand being timed: \"ninja -j1 -v -d explain -d stats\"\n> \tUser time (seconds): 0.17\n> \tSystem time (seconds): 0.02\n> \tPercent of CPU this job got: 99%\n> \tElapsed (wall clock) time (h:mm:ss or m:ss): 0:00.20\n> \n> and has indeed generated exactly one command generation: <cmd edited for\n> brevity>\n> \n> [1/36] meson --internal vcstagger version.cpp.in version.cpp v0.0\n> @VCS_TAG@ '(.*)' gen-version.sh\n> \n> \n> Not that there was one less check performed internally, so this is now\n> [1/36] where before it was [1/37]\n> \n> \n> and a 'no-op-version-changed' rebuild takes:\n> \n> \t# git commit --amend; edit message, quit to generate new sha1.\n> \n> \tCommand being timed: \"ninja -j1 -v -d explain -d stats\"\n> \tUser time (seconds): 0.94\n> \tSystem time (seconds): 0.19\n> \tPercent of CPU this job got: 98%\n> \tElapsed (wall clock) time (h:mm:ss or m:ss): 0:01.16\n> \n> \n> so 3.05 elapsed time for non-parallel build is down to 1.16. Certainly\n> can't fault that as an improvement. For parallel builds, this patch\n> brings Elapsed time down to 1.07 from 1.87.\n> \n> Again for completeness, here's the commands which were now executed:\n> \n> [1/36] meson --internal vcstagger version.cpp.in\n> src/libcamera/version.cpp v0.0 src/libcamera @VCS_TAG@ '(.*)'\n> src/libcamera/gen-version.sh /home/linuxembedded/iob/libcamera/libcamera\n> [2/36] c++ -c src/libcamera/version.cpp # Our autogenerated version.cpp\n> [3/36] c++ -o src/libcamera/libcamera.so # Re-link\n> [4/36] meson --internal symbolextractor libcamera.so.symbols\n> [5/5] /usr/bin/doxygen Documentation/Doxyfile\n> \n> > The problem is caused by the generated version.h being listed as a\n> > dependency for the whole libcamera. This is required as meson (to the\n> > best of my knowledge) doesn't provide a way to explicitly specify the\n> > dependency of a single object file (camera_manager.o in this case, as\n> \n> Single objects determine their dependencies through the compiler parsing\n> their includes, so that tells them what needs to be rebuilt - but\n> doesn't link to the vcs_tag object indeed. Perhaps this is a limitation\n> in meson that could be updated, but I don't think it's necessary.\n\nI also believe it's a limitation of meson, yes. They may be a way to\ndeclare additional dependencies at the object level, but I haven't found\none.\n\n> The dependencies are checked in sequence to ensure that the version tag\n> is updated before it is used. As far as I can see, no full rebuild\n> occurs, (with the exception of the controls file which is also\n> auto-generated).\n\nI'm not sure to follow you here. What ensures that the version tag is\nupdated before it is used ?\n\n> > camera_manager.cpp is the only consumer of the generated version string)\n> > on the custom target used to generate version.h. The dependency can't be\n> > automatically detected at build time, like dependencies on normal\n> > headers that are generated by parsing the source, because the version.h\n> > header may not exist yet. The build could then fail in a racy way.\n> \n> Correct, the normal dependency generation can't queue the execution of\n> the vcs_tag command, but it does mean that only objects which include\n> version.h directly are rebuilt, which I think is as expected?\n\nThe problem, which I encountered before when generating libcamera.h and\ncontrols, is that the missing dependency can cause a build failure due\nto a race condition. Initially version.h doesn't exist, so the automatic\nsource dependency generation mechanism will not pick it up, and will not\ncause it to be built. If it gets build before the source files that\ninclude it, it's all fine. Otherwise compilation will fail as the header\ndoesn't exist. This can cause a transient build failure too, as the\nversion.h generation could run in parallel to the build of a file using\nit. Re-running ninja will then work.\n\n> > This change attempts at solving the issue by generating a version.cpp\n> > instead of a version.h. This minimises the number of files that need to\n> > be rebuild when then git tree state changes, while retaining the main\n> > purpose of the original automatic version generation, the ability to\n> > access the git-based version string at runtime.\n> \n> I'm fine with this, it's more akin to what I originally proposed.\n> \n> By removing the version string from the header, it means that qcam does\n> not get rebuilt each time as it now only gets the runtime version string.\n> \n> > The Sphinx and Doxygen documentation however lose git-based version\n> > information, to prevent a full documentation rebuild for every commit.\n> > We may want to investigate how to preserve detailed version information\n> > there, as well as how to make it available in a header file for\n> > applications. For the time being, this commit should be a good\n> > compromise to avoid unnecessary recompilation, and additional features\n> > can be built on top of it after taking the time to consider and test\n> > them carefully.\n> \n> Having the build information directly linked into the documentation was\n> a key point of my series. I wanted the documentation to specify the\n> version that it represents.\n\nI would like that too, I actually think it's more important than\ndisplaying the version number in qcam (and is of similar importance to\ndisplaying it in the libcamera log). I would however make a distinction\nbetween the Sphinx and the Doxygen documentation, I think it's more\nimportant for the latter than the former (at least at this time).\n\n> I agree that there is a meson issue there that because the version given\n> to Doxygen is only generated at configure time it could get stale.\n> \n> So there's a policy point to consider there, As anything which generates\n> the version for public consumption should do a full build - we can\n> ensure that it does do any necessary reconfiguration stage. In fact I\n> believe you can \"ninja reconfigure\" without requiring a full rebuild too...\n\nRegarding documentation, I think having version information is useful,\nbut I would like to know what use cases you care about. The\ndocumentation we currently publish on libcamera.org is certainly not\nbased on a stable branch, but on the other hand I don't think anyone\nexpects it to be. When we'll start creating releases for libcamera I\nforesee a need to have multiple versions of the API documentation on the\nwebsite, one for each release (possibly with a link named latest\npointing to the latest one). We can also keep publishing a nightly\nbuild, and that's probably the only one for which I can think of a full\nversion string being useful. For that we will do a full build in any\ncase, so the correct version will be included.\n\nI have a feeling that vcs_tag() is currently a half-baked feature in\nmeson, without any best practice rules on how to use it, and in general\non how to generate and use version information (this part isn't specific\nto meson). That's why I believe we should take the time required to\nthink about what we need. In particular, we expose no version\ninformation that can be used at build time (the preprocessor can't\nreally make use of a version string in a meaningful way), so I think we\nshould expose the classic LIBCAMERA_VERSION_MAJOR,\nLIBCAMERA_VERSION_MINOR and LIBCAMERA_VERSION_PATCH in a header file\ngenerated at meson configure time, without including any git SHA1 or\npatch count in there. A \"plain\" version string can easily be generated\nfrom that, and a \"git\" version string should be exposed through a\nruntime API only (if at all) and be printed to the libcamera log (that\npart I believe is important for debugging purpose).\n\n> > Fixes: b817bcec6b53 (\"libcamera: Auto generate version information\")\n> \n> 'Fixes' seems a strong choice to me.\n> I don't seem to have the same concerns as you regarding b817, but lets\n> move on.\n> \n> Acked-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n> \n> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> > ---\n> >  Documentation/Doxyfile.in                     |  4 ++-\n> >  meson.build                 |  8 +-----\n> >  {version.h.in => version.h} |  4 ---\n> >  meson.build                                   |  4 +--\n> >  src/libcamera/camera_manager.cpp              |  5 ----\n> >  {utils => src/libcamera}/gen-version.sh       |  0\n> >  src/libcamera/meson.build                     |  8 +++++-\n> >  src/libcamera/version.cpp.in                  | 25 +++++++++++++++++++\n> >  8 files changed, 37 insertions(+), 21 deletions(-)\n> >  rename {version.h.in => version.h} (79%)\n> >  rename {utils => src/libcamera}/gen-version.sh (100%)\n> >  create mode 100644 src/libcamera/version.cpp.in\n> > \n> > diff --git a/Documentation/Doxyfile.in b/Documentation/Doxyfile.in\n> > index cad85ff979f8..ed111909b603 100644\n> > --- a/Documentation/Doxyfile.in\n> > +++ b/Documentation/Doxyfile.in\n> > @@ -791,7 +791,9 @@ WARN_LOGFILE           =\n> >  # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING\n> >  # Note: If this tag is empty the current directory is searched.\n> >  \n> > -INPUT                  = \"@TOP_SRCDIR@/include/libcamera\" \"@TOP_SRCDIR@/src/libcamera\"\n> > +INPUT                  = \"@TOP_SRCDIR@/include/libcamera\" \\\n> > +\t\t\t \"@TOP_SRCDIR@/src/libcamera\" \\\n> > +\t\t\t \"@TOP_BUILDDIR@/src/libcamera\"\n> >  \n> >  # This tag can be used to specify the character encoding of the source files\n> >  # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses\n> > diff --git a/meson.build b/meson.build\n> > index 6f81f1117318..bafa103d141b 100644\n> > --- a/meson.build\n> > +++ b/meson.build\n> > @@ -14,15 +14,9 @@ libcamera_api = files([\n> >      'signal.h',\n> >      'stream.h',\n> >      'timer.h',\n> > +    'version.h',\n> >  ])\n> >  \n> > -gen_version = join_paths(meson.source_root(), 'utils', 'gen-version.sh')\n> > -\n> > -version_h = vcs_tag(command : [gen_version, meson.current_source_dir()],\n> > -                    input : 'version.h.in',\n> > -                    output : 'version.h',\n> > -                    fallback : 'v0.0')\n> > -\n> >  gen_header = files('gen-header.sh')\n> >  \n> >  libcamera_h = custom_target('gen-header',\n> > diff --git a/version.h.in b/version.h\n> > similarity index 79%\n> > rename from version.h.in\n> > rename to version.h\n> > index e49b36962aed..75d57d48af0d 100644\n> > --- a/version.h.in\n> > +++ b/version.h\n> > @@ -3,16 +3,12 @@\n> >   * Copyright (C) 2019, Google Inc.\n> >   *\n> >   * version.h - Library version information\n> > - *\n> > - * This file is auto-generated. Do not edit.\n> >   */\n> >  #ifndef __LIBCAMERA_VERSION_H__\n> >  #define __LIBCAMERA_VERSION_H__\n> >  \n> >  #include <string>\n> >  \n> > -#define LIBCAMERA_VERSION \"@VCS_TAG@\"\n> > -\n> >  namespace libcamera {\n> >  \n> >  extern const std::string version;\n> > diff --git a/meson.build b/meson.build\n> > index 342b3cc76a93..271b538bdae8 100644\n> > --- a/meson.build\n> > +++ b/meson.build\n> > @@ -1,8 +1,6 @@\n> >  project('libcamera', 'c', 'cpp',\n> >      meson_version : '>= 0.40',\n> > -    version : run_command('utils/gen-version.sh',\n> > -                          '@0@'.format(meson.source_root()),\n> > -                          check : true).stdout().strip(),\n> > +    version : 'v0.0',\n> \n> I'm unhappy that this patch changes the project version, but you seem to\n> be very keen to remove this, so I concede.\n\nI've thought about it since our last discussion on this topic, and from\nmy side I have the following requirements for the base version number:\n\n- It shall not be stored in multiple files (canonical source)\n- For tarball releases, tt shall be stored in a file in the root\n  directory of the project\n- Under git control, if it is stored in a file, it shall be stored in\n  the same file as for the tarball releases\n\nmeson.build is an obvious candidate. If we decide to store the version\nnumber in a file under git control, it should be meson.build. The\nadvantage in my opinion is that it gives an easy way to quickly check\nthe base version.\n\nHowever, I'm not completely opposed to storing the version number in a\nfile not under git control, which would be generated when creating a\ntarball release. There are pros and cons for this, and I think I could\nbe convinced if most of the concerns can be addressed.\n\nSo going back to storing the version information in meson.build in this\npatch is not an attempt to set this in stone in the long term, but\nreally an interim measure.\n\n> In the long run, I'd perhaps envisage having an option on gen-version\n> which prepares a 'simple' version, which will return the latest known\n> release without extra labels or commit specific information, that is\n> provided to the meson.project_version.\n> \n> >      default_options : [\n> >          'werror=true',\n> >          'warning_level=2',\n> > diff --git a/src/libcamera/camera_manager.cpp b/src/libcamera/camera_manager.cpp\n> > index c5da46b4062d..69503c6e8726 100644\n> > --- a/src/libcamera/camera_manager.cpp\n> > +++ b/src/libcamera/camera_manager.cpp\n> > @@ -26,11 +26,6 @@ namespace libcamera {\n> >  \n> >  LOG_DEFINE_CATEGORY(Camera)\n> >  \n> > -/**\n> > - * \\brief The library global version string\n> > - */\n> > -const std::string version(LIBCAMERA_VERSION);\n> > -\n> >  /**\n> >   * \\class CameraManager\n> >   * \\brief Provide access and manage all cameras in the system\n> > diff --git a/utils/gen-version.sh b/src/libcamera/gen-version.sh\n> > similarity index 100%\n> > rename from utils/gen-version.sh\n> > rename to src/libcamera/gen-version.sh\n> > diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build\n> > index 336f4f066fac..c35ecb988d72 100644\n> > --- a/src/libcamera/meson.build\n> > +++ b/src/libcamera/meson.build\n> > @@ -79,8 +79,14 @@ control_types_cpp = custom_target('control_types_cpp',\n> >  \n> >  libcamera_sources += control_types_cpp\n> >  \n> > +version_cpp = vcs_tag(command : ['gen-version.sh', meson.source_root()],\n> > +                      input : 'version.cpp.in',\n> > +                      output : 'version.cpp',\n> > +                      fallback : meson.project_version())\n> > +\n> > +libcamera_sources += version_cpp\n> > +\n> >  libcamera_deps = [\n> > -    declare_dependency(sources : version_h),\n> >      cc.find_library('dl'),\n> >      libudev,\n> >  ]\n> > diff --git a/src/libcamera/version.cpp.in b/src/libcamera/version.cpp.in\n> > new file mode 100644\n> > index 000000000000..055dc0b5c5ad\n> > --- /dev/null\n> > +++ b/src/libcamera/version.cpp.in\n> > @@ -0,0 +1,25 @@\n> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> > +/*\n> > + * Copyright (C) 2019, Google Inc.\n> > + *\n> > + * version.cpp - libcamera version\n> > + *\n> > + * This file is auto-generated. Do not edit.\n> > + */\n> > +\n> > +#include <libcamera/version.h>\n> > +\n> > +/**\n> > + * \\file version.h\n> > + * \\brief libcamera version\n> > + */\n> > +\n> > +namespace libcamera {\n> > +\n> > +/**\n> > + * \\var libcamera::version\n> > + * \\brief The library global version string\n> > + */\n> > +const std::string version(\"@VCS_TAG@\");\n> > +\n> > +} /* namespace libcamera */","headers":{"Return-Path":"<laurent.pinchart@ideasonboard.com>","Received":["from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id A2FE360C23\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri,  5 Jul 2019 14:59:55 +0200 (CEST)","from pendragon.ideasonboard.com\n\t(dfj612yhrgyx302h3jwwy-3.rev.dnainternet.fi\n\t[IPv6:2001:14ba:21f5:5b00:ce28:277f:58d7:3ca4])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 0A51424B;\n\tFri,  5 Jul 2019 14:59:54 +0200 (CEST)"],"DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1562331595;\n\tbh=SMxxkNxEmg5I6wUgzyY0rabXAnkgrYc589SCNajQmbo=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=VY57yua3CFMj/I2AxrC1QFJZJY4nnNAAHaAIhe1GvHZ0AlqlBeUTOtaMG/UqWfs7A\n\toTwudEkANSWLeZWWMoaVg3WjwV2+OdUzRsW+Pq2KR4Nh28wwzfNcBmAqbFdLMgO1Fk\n\tfhJy7T0ccpvwVEkl50BwOBTwuZhNQ5LsU15uWy3w=","Date":"Fri, 5 Jul 2019 15:59:34 +0300","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"Kieran Bingham <kieran.bingham@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Message-ID":"<20190705125934.GD4994@pendragon.ideasonboard.com>","References":"<20190705082916.13412-1-laurent.pinchart@ideasonboard.com>\n\t<e152b555-9da3-279a-a314-f67cc063c48e@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<e152b555-9da3-279a-a314-f67cc063c48e@ideasonboard.com>","User-Agent":"Mutt/1.10.1 (2018-07-13)","Subject":"Re: [libcamera-devel] [PATCH] libcamera: Rework automatic version\n\tgeneration to avoid rebuilds","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.23","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","X-List-Received-Date":"Fri, 05 Jul 2019 12:59:55 -0000"}}]