diff options
author | Boris Kolpackov <boris@codesynthesis.com> | 2023-11-02 08:32:03 +0200 |
---|---|---|
committer | Boris Kolpackov <boris@codesynthesis.com> | 2023-11-02 08:32:03 +0200 |
commit | f401d46208e5921c2ce471a7928805ba5946be07 (patch) | |
tree | dcd2448d06b83a877f504219a7a5763a59abb7b5 | |
parent | 26402aa6b4bfa0be174723d8137c37b8506650fd (diff) |
Further work on packaging guide
-rw-r--r-- | doc/packaging.cli | 479 |
1 files changed, 450 insertions, 29 deletions
diff --git a/doc/packaging.cli b/doc/packaging.cli index 96c2861..3ccd4b6 100644 --- a/doc/packaging.cli +++ b/doc/packaging.cli @@ -29,6 +29,9 @@ repository. For additional information, including documentation for individual \h1#intro|Introduction| +@@ Assume read through toolchain introduction and build system introduction. +Also, ideally, have some experience using \c{build2} in your own projects. + The aim of this guide is to ease the convertion of third-party C/C++ projects to the \c{build2} build system and publishing them to the \l{https://cppget.org cppget.org} package repository by codifying the best @@ -245,16 +248,34 @@ for <name>} line, where \c{<name>} is the project name.| \N|Since this is your personal repository, you can do the initial work directly in \c{master}/\c{main} or in a separate branch, it's up to you.| +As a running example, let's assume we want to package a library called \c{foo} +whose upstream repository is at \c{https://github.com/<upstream>/foo.git}. We +have created its package repository at +\c{https://github.com/<personal>/foo.git} (with the \c{build2 package for foo} +description) and can now clone it: + +\ +$ git clone https://github.com/<personal>/foo.git +\ -\h2#core-repo-init|Initialize package repository with \c{bdep new -t empty}| -From the repository root directory, run: +\h2#core-repo-init|Initialize package repository with \c{bdep new --type empty}| + +Change to the root directory of the package repository that you have clonned +on the previous step and run (continuing with our \c{foo} example): \ -bdep new -t empty +$ cd foo +$ bdep new --type empty +$ tree . +./ +├── .gitattributes +├── .gitignore +├── README.md +└── repositories.manifest \ -This command will create a number of files, including: +This command creates a number of files in the root of the repository: \dl| @@ -282,9 +303,9 @@ inside for details).|| Next add and commit these files: \ -git add . -git status -git commit -m \"Initialize repository\" +$ git add . +$ git status +$ git commit -m \"Initialize repository\" \ \N|In these guidelines we will be using the package repository setup that is @@ -328,10 +349,10 @@ purpose and all the relevant commands will be provided and explained, in case you are not familiar with this \c{git} mechanism.| Given the upstream repository URL, to add it as a submodule, run the following -command from the package repository root: +command from the package repository root (continuing with our \c{foo} example): \ -git submodule add https://github.com/.../<project>.git upstream +$ git submodule add https://github.com/<upstream>/foo.git upstream \ \N|You should prefer \c{https://} over \c{git://} for the upstream repository @@ -348,17 +369,17 @@ to update the \c{upstream} submodule to point to this release commit, run the following command: \ -cd upstream -git checkout vX.Y.Z -cd .. +$ cd upstream +$ git checkout vX.Y.Z +$ cd .. \ Then add and commit these changes: \ -git add . -git status -git commit -m \"Add upstream submodule\" +$ git add . +$ git status +$ git commit -m \"Add upstream submodule\" \ Now we have all the upstream source code for the release that we are @@ -543,15 +564,405 @@ to use, \l{https://build2.org/community.xhtml#help get in touch} to discuss the alternatives. It can be quite painful to change these things after you have completed the remaining packaging steps. +Continuing with our \c{foo} example, we will follow the recommendation and +call the library package \c{libfoo}. -@@ Where do we overlay the source code? -====================================================================== +\h2#core-package-struct|Decide on the package source code layout| + +Another aspect we need to decide on is the source code layout inside the +package. Here we want to stay as close to upstream layout as possible unless +there are valid reasons to deviate. This has the best chance of giving us a +build without any compile errors since the header inclusion in the project can +be sensitive to this layout. This also makes it easier for upstream to adopt +the \c{build2} build. + +Sometimes, however, there are good reasons for deviating from upstream, +especially in cases where upstream is clearly following bad practices, for +example including generically-named public headers without the library +subdirectory. If you do decide to change the layout, it's usually less +disruptive (to the build) to rearrange things at the outer levels than at the +inner. For example, it should normally be possible to move/rename the +top-level \c{tests/} directory or to place the library source directory into a +subdirectory. + +Our overall plan for the package is to create the initial layout and +\c{buildfile} templates automatically using \l{bdep-new(1)} in the +\c{--package} mode, then tweak \c{buildfile}s if necessary, and finally +\"fill\" the package with upstream source code using symlinks. + +The main rationale for using \l{bdep-new(1)} instead of doing everything by +hand is that there are many nuances in getting the build right and +auto-generated \c{buildfile}s had years of refinement and fine-tuning. The +familiar structure also makes it easier for others to understand your build, +for example while reviewing your package submission. + +The \l{bdep-new(1)} command supports a wide variety of +\l{bdep-new.xhtml#src-layout source layouts}. While it may take a bit of time +to understand the customization points necessary to achieve the desired layout +for your first package, this will pay off in spades when you work on +converting subsequent packages. + +And so the focus of the following steps is to iteratively discover the +\l{bdep-new(1)} command line that best approximates the upstream layout. The +recommended procedure is as follows: + +\ol| + +\li|Study the upstream source layout and existing build system.| + +\li|Craft and execute the \l{bdep-new(1)} command line necessary to achieve +the upstream layout.| + +\li|Study the auto-generated \c{buildfile}s for things that don't fit and need +to change. But don't rush to start manually editing the result. First get an +overview of the required changes and then check if it's possible to achieve +these changes automatically using one of \l{bdep-new(1)} sub-options. If +that's the case, delete the package subdirectory, and restart from step #2.|| + +This and the following two sections discuss each of these steps in more detail +and also look at some examples. + +The first step above is to study the upstream project in order to understand +where the various parts are (headers, sources, etc.) and how they are built. +Things that can help here include: + +\ul| + +\li|Read through the existing build system definitions.| + +\li|Try to build the project using the existing build system.| + +\li|Try to install the project using the existing build system.| + +\li|Look into the Debian package contents to see if there are any differences + with regards to the installation locations.|| + +For libraries, the first key pieces of information we need to find is how the +public headers are included and where they are installed. The two common +\i{good} practices is to either include the public headers with a library name +as a subdirectory, for example, \c{#include\ <foo/util.h>}, or to include the +library name into each public header name, for example, \c{#include\ +<foo_util.h>} or \c{#include\ <foo.h>} (in the last example the header name is +the library name itself, which is also fairly common). Unfortunately, there is +also a fairly common \i{bad} practice: having generically named headers (such +as \c{util.h}) included without the library subdirectory. + +\N|The reason this is a bad practice is that libraries that have such headers +cannot coexist, neither in the same build nor when installed. See +\l{intro#proj-struct Canonical Project Structure} for background and details. +See \l{#howto-bad-inclusion-practice How do I deal with bad header inclusion +practice} if you encounter such a case.| + +Where should we look to get this information? While the library source files +sound like a natural place, oftentimes they include own headers with the +\c{\"\"} style inclusion, either because the headers are in the same directory +or because the library build arranges for them to be found this way with +additional header search paths. As a result, a better place to look could be +library's examples and/or tests. Some libraries also describe which headers +they provide and how to include them in their documentation. + +The way public headers are included normally determines where they are +installed. If they are included with a subdirectory, then they are normally +installed into the same subdirectory in, say, \c{/usr/include/}. Continuing +with the above example, a header that is included as \c{<foo/util.h>} would +normally be installed as \c{/usr/include/foo/util.h}. On the other hand, if +the library name is part of the header name, then the headers are usually (but +not always) installed directly into, say, \c{/usr/include/}, for example as +\c{/usr/include/foo_util.h}. + +\N|While these are the commonly used installation schemes, there are +deviations. In particular, in both cases upstream may choose to add an +additional subdirectory when installing (so the above examples we instead +end up with, say, \c{/usr/include/sub/foo/util.h} and +\c{/usr/include/sub/foo_util.h}). See \l{#howto-extra-header-install-subdir +How do I handle extra header installation subdirectory} if you encounter such +a case.| + +The inclusion scheme would normally be recreated in the upstream source code +layout. In particular, if upstream includes public headers with a +subdirectory, then this subdirectory would normally also be present in the +upstream layout so that such a header can be included form the upstream +codebase directly. As an example, let's say we determined that public headers +of \c{libfoo} are included with the \c{foo/} subdirectory, such as +\c{<foo/util.hpp>}. One of the typical upstream layouts for such a library +would look like this: + +\ +$ tree upstream/ +upstream/ +├── include/ +│ └── foo/ +│ └── util.hpp +└── src/ + ├── priv.hpp + └── util.cpp +\ + +Notice how the \c{util.hpp} header is in the \c{foo/} subdirectory rather +than in \c{include/} directly. + +The second key pieces of information we need to find is whether and, if so, +how the public headers and sources are split. For instance, in the above +example, we can see that public headers go into \c{include/} while sources and +private headers go into \c{src/}. But they could also be combined in the same +directory, for example, as in the following layout: + +\ +upstream/ +└── foo/ + ├── priv.hpp + ├── util.cpp + └── util.hpp +\ + +\N|In multi-package projects, for example, those that provide both a library and +an executable, you would also want to understand how the sources are split +between the packages.| + +If the headers and sources are split into different directories, then the +source directory may or may not have the include subdirectory, similar to the +header directory. In the above split layout the \c{src/} directory doesn't +contain the include subdirectory (\c{foo/}) while the following layout does: + +\ +upstream/ +├── include/ +│ └── foo/ +│ └── util.hpp +└── src/ + └── foo/ + ├── priv.hpp + └── util.cpp +\ + +With the understanding of these key properties of upstream layout you should +be in a good position to start crafting the \l{bdep-new(1)} command line that +recreates it. + + +\h2#core-package-craft-cmd|Craft \c{bdep new} command line to create package| + +The recommened procedure for this step is to read through the \c{bdep-new}'s +\l{bdep-new.xhtml#src-layout SOURCE LAYOUT} section (which contains a large +number of examples) while experimenting with various options in an attempt to +create the desired layout. If the layout you've got isn't quite right yet, +simply remove the package directory along with the \c{packages.manifest} file +and try again. + +Let's illustrate this approach on the original example of the split layout: + +\ +upstream/ +├── include/ +│ └── foo/ +│ └── util.hpp +└── src/ + ├── priv.hpp + └── util.cpp +\ + +We know it's split, so let's start with that and see what we get. Remember, +our \c{foo} package repository that we have clonned and initialized earlier +looks like this: +\ +$ tree foo/ +foo/ +├── .gitattributes +├── .gitignore +├── README.md +└── repositories.manifest +\ + +Now we create the \c{libfoo} package inside: + +\ +$ cd foo +$ bdep new --package --lang c++ --type lib,split libfoo +$ tree libfoo/ +libfoo/ +├── include/ +│ └── libfoo/ +│ └── foo.hxx +└── src/ + └── libfoo/ + └── foo.cxx +\ + +The outer structure looks right, but inside \c{include/} and \c{src/} things +are a bit off. Specifically, the include subdirectory should be \c{foo/}, not +\c{libfoo/}, there shouldn't be one inside \c{src/}, and the file extensions +don't match upstream. All this can be easily tweaked, however: + +\ +$ rm -r libfoo/ packages.manifest +$ bdep new --package \ + --lang c++,cpp \ + --type lib,split,subdir=foo,no-subdir-source \ + libfoo +$ tree libfoo/ +libfoo/ +├── include/ +│ └── foo/ +│ └── foo.hpp +└── src/ + └── foo.cpp +\ + +The other \c{bdep-new} sub-options (see the \l{bdep-new(1)} man page for the +complete list) that you will likely want to use when packaging a third-party +project include: + +\dl| + +\li|\n\c{no-version} + +Omit the auto-generated version header. Usually upstream will provided its own +equivalent to this functionality. + +\N|Note that even if upstream doesn't provide any version information, it's +not a good idea to try to rectify this by providing your own version header +since upstream may add it in a future version and you may end up with a +conflict. Instead, work with the project maintainer to rectify this in +upstream.|| + +\li|\n\c{no-symexport}\n\c{auto-symexport} + +The \c{no-symexport} sub-option suppresses the generation of the DLL symbol +exporting header. This is an appropriate option if upstream provides its +own symbol exporting arrangements. + +The \c{auto-symexport} sub-option enables automatic DLL symbol exporting +support (see \l{b##cc-auto-symexport Automatic DLL Symbol Exporting} for +background). This is an appropriate option if upstream relies on similar +support in the existing build system. It is also recommended that you give +this functionality a try even if upstream does not support building +shared libraries on Windows.| + +\li|\n\c{binless} + +Create a header-only library template. See \l{#dont-header-only Don't make +library header-only if it can be compiled} and +\l{https://github.com/build2/HOWTO/blob/master/entries/make-header-only-library.md +How do I make a header-only C/C++ library?}|| + +Continuing with our \c{libfoo} example, assuming upstream provides own symbol +exporting, the final \c{bdep-new} command line would be: + +\ +$ bdep new --package \ + --lang c++,cpp \ + --type lib,split,subdir=foo,no-subdir-source,no-version,no-symexport \ + libfoo +\ + +Let's also get a more complete view of what it generates: + +\ +$ tree libfoo/ +libfoo/ +├── build/ +│ └── ... +├── include/ +│ └── foo/ +│ ├── buildfile +│ └── foo.hpp +├── src/ +│ ├── buildfile +│ └── foo.cpp +├── tests/ +│ ├── build/ +│ │ └── ... +│ ├── basics/ +│ │ ├── buildfile +│ │ └── driver.cpp +│ └── buildfile +├── buildfile +├── manifest +└── README.md +\ + +\h2#core-package-review|Review and test auto-genetated \c{buildfile} templates| + +Once the overall layout looks right, the next step is to take a closer look at +the generated \c{buildfile}s to make sure that overall they match the upstrem +build. Of particular interest are the header and source directory +\c{buildfile}s (\c{libfoo/include/buildifle} and \c{libfoo/src/buildifle} in +the above listing) which define how the library is built and installed. + +Here we are focusing on the macro-level differences that are easier to change +by tweaking the \c{bdep-new} command line rather than manually. For example, +if we look at the generated source directory \c{buildfile} and realize it +builds a \"binful\" library (that is, a library that includes source files and +therefore produces library binaries) while the upsteam library is header-only, +it is much easier to fix this by re-running \c{bdep-new} with the \c{binless} +sub-option than by changing the \c{buildfile} manually. + +\N|Don't be tempted to start making manual changes at this stage even if you +cannot see anything else that can be fixed with a \c{bdep-new} re-run. This +is still a dry-run and we will recreate the package one more time in the +following section before starting manual adjustments.| + +Besides examining the generated \c{buildfile}s, it's also a good idea to +build, test, and install the generated package to make sure everything ends up +where you expected and matches upstream where necessary. In particular, make +sure public headers are installed into the same location as upstream. + +\N|The \c{bdep-new}-generated library is a simple \"Hello, World!\" example +that can nevertheless be built, tested, and installed. The idea here is to +verify it matches upstream using the generated source files before replacing +them with the upstream source file symlinks.| + +Note that at this stage its easiest to build, test, and install in source +directly sidestepping the \c{bdep} initialization of the package (which you +would have to de-initalize before you can re-run \c{bdep-new}). Continue +with the above example, the recommended sequence of commands would be: + +\ +$ cd libfoo +$ b update +$ b test +$ b install config.install.root=/tmp/install +$ b clean +\ +Let's also briefly discuss other subdirectories and files found in the +\c{bdep-new}-generated \c{libfoo} package. +The \c{build/} subdirectory is the standard \c{build2} place for project-wide +build system information (see \l{b#intro-proj-struct Project Structure} for +details). We will look close at its contents in the following steps. +In the root directory of our package we find the root \c{buildfile} and +package \c{manifest}. We will be tweaking both in the following steps. There +is also \c{README.md} which we will replace with the upstream symlink. +The \c{tests/} subdirectory is the standard \c{build2} tests subproject (see +\l{b#intro-operations-test Testing} for details). While you can suppress its +generation with the \c{no-tests} \c{bdep-new} sub-option, we recommend that +you keep it and use it as a starting point for porting upstream tests or, if +upstream doesn't provide any, for a basic \"smoke test\" (@@ ref HOWTO). + +\N|You can easily add/remove/rename this \c{tests/} subproject. The only place +where it is mentioned explicitly and where you will need to make changes is +the root \c{buildfile}. In pacticular, if upstream provides examples that you +wish to port, it is recommended that you use a copy of the generated +\c{tests/} subproject as a starting point (not forgeting to add the +corresponding entry in the root \c{buildfile}).| + +@@ We can actually do the final creation here (with symlinking, etc). + +@@ Adding additional subdirectories to avoid symlinking individual files. + Ideally would want bdep-new mode. + +@@ Pre-symlink README, LICENSE, PACKAGE-README.md? +@@ Auto-detect the license? +@@ Where do we overlay the source code? + +@@ The Don't write buildfiles by hand entry is now duplicate/redundant. + +====================================================================== \h1#dont-do|What Not to Do| @@ -617,18 +1028,8 @@ time for manual customizations. These would normally include: \h#dont-change-upstream|Don't change upstream source code layout| It's a good idea to stay as close to the upstream's source code layout as -possible. This has the best chance of giving us a build without any compile -errors since the header inclusion in the project can be sensitive to this -layout. This also makes it easier for upstream to adopt the \c{build2} -build. - -Sometimes, however, there are good reasons for deviating from upstream, -especially in cases where upstream is clearly following bad practices, for -example installing generically-named headers without a library prefix. If you -do decide to change the layout, it's usually less disruptive (to the build) to -rearrange things at the outer levels than at the inner. For example, it should -normally be possible to move/rename the top-level \c{tests/} directory or to -place the library source directory into a subdirectory. +possible. For background and rationale, see \l{#core-package-struct Decide on +the package source code layout}. \h#dont-forget-update-manifest|Don't forget to update \c{manifest} values| @@ -825,6 +1226,26 @@ $ bdep new -t lib,prefix=libigl-core,no-subdir,no-version libigl-core \h1#howto|Packaging HOWTO| +@@ howto make smoke test (and fix ref) + +\h#howto-bad-inclusion-practice|How do I deal with bad header inclusion practice| + +This sections explains how to deal with libraries that include their public, +generically-named headers without a library name as directory prefix. Such +libraries cannot coexist, neither in the same build nor when installed. For +background and details, see \l{intro#proj-struct Canonical Project Structure}. + +@@ TODO + + +\h#howto-extra-header-install-subdir|How do I handle extra header installation subdirectory| + +This sections explains how to handle an additional header installation +subdirectory. + +@@ TODO + + \h1#faq|Packaging FAQ| \h#faq-publish-stage|Where to publish if package requires staged toolchain?| |