diff mbox series

[14/16] package/pkg-cargo: add support for unlocked packages

Message ID 1f9ee4bc875b559abde9a4746ab55fa4ca62dc85.1717360788.git.yann.morin.1998@free.fr
State Changes Requested
Headers show
Series support/download: allow downloading unlocked cargo packages (branch yem/cargo-unchained) | expand

Commit Message

Yann E. MORIN June 2, 2024, 8:39 p.m. UTC
Signed-off-by: Yann E. MORIN <yann.morin.1998@free.fr>
---
 docs/manual/adding-packages-cargo.adoc | 22 ++++++++++++++++++++++
 package/pkg-cargo.mk                   | 15 ++++++++++++++-
 support/download/cargo-post-process    | 25 ++++++++++++++++++++-----
 3 files changed, 56 insertions(+), 6 deletions(-)

Comments

James Hilliard June 2, 2024, 8:53 p.m. UTC | #1
On Sun, Jun 2, 2024 at 2:42 PM Yann E. MORIN <yann.morin.1998@free.fr> wrote:
>
> Signed-off-by: Yann E. MORIN <yann.morin.1998@free.fr>
> ---
>  docs/manual/adding-packages-cargo.adoc | 22 ++++++++++++++++++++++
>  package/pkg-cargo.mk                   | 15 ++++++++++++++-
>  support/download/cargo-post-process    | 25 ++++++++++++++++++++-----
>  3 files changed, 56 insertions(+), 6 deletions(-)
>
> diff --git a/docs/manual/adding-packages-cargo.adoc b/docs/manual/adding-packages-cargo.adoc
> index 8a2292e7b6..0ed529a770 100644
> --- a/docs/manual/adding-packages-cargo.adoc
> +++ b/docs/manual/adding-packages-cargo.adoc
> @@ -82,12 +82,17 @@ typical packages will therefore only use a few of them.
>    environment of +cargo+ invocations. It used at both build and
>    installation time
>
> +* +FOO_CARGO_LOCKED+ can be set to +YES+ (the default) or +NO+, to
> +  specify whether the source tree has been cargo-locked or not. See
> +  below for xref:cargo-vendoring[more details].
> +
>  * +FOO_CARGO_BUILD_OPTS+ can be used to pass additional options to
>    +cargo+ at build time.
>
>  * +FOO_CARGO_INSTALL_OPTS+ can be used to pass additional options to
>    +cargo+ at install time.
>
> +[[cargo-vendoring]]
>  A crate can depend on other libraries from crates.io or git
>  repositories, listed in its +Cargo.toml+ file. Buildroot automatically
>  takes care of downloading such dependencies as part of the download
> @@ -95,3 +100,20 @@ step of packages that use the +cargo-package+ infrastructure. Such
>  dependencies are then kept together with the package source code in
>  the tarball cached in Buildroot's +DL_DIR+. This is referred to as
>  _vendoring_. Vendoring allows for a completely off-line build.
> +
> +When a package source tree contains a +Cargo.lock+ file, the package has
> +been _locked_: cargo did store the versions for the complete dependency
> +chain in the +Cargo.lock+ file; that is then reused by cargo during the
> +vendoring, which yields a reproducible dependency chain, and thus a
> +reproducible tarball; therefore, it is possible to compute a hash for
> +that archive. If a package was however not locked, then the versions for
> +the dependencies are unknown, the dependency chain is not reproducible,
> +and the archive is then not reproducible, which implies there can be no
> +hash for that archive.

Hmm, for these cases shouldn't we generate and commit the lockfile to
the buildroot package so that we can then generate a deterministic archive
and archive hash?

I think we want to have the ability to make these packages deterministic
even if upstream fails to provide a Cargo.lock file.

> +
> +Packages are normally locked, so Buildroot will by default instruct
> +cargo to atttempt a locked vendoring; for a package that was not locked,
> +this would fail; in that case, +FOO_CARGO_LOCKED+ must be set to +NO+ so
> +Buildroot does not request cargo to attempt a locked vendoring. It is an
> +error to set +FOO_CARGO_LOCKED+ to +NO+ for a locked package, as it is
> +to set it to +YES+ (or not set it at all) on an unlocked package.
> diff --git a/package/pkg-cargo.mk b/package/pkg-cargo.mk
> index 41dfcbd096..90e4facf52 100644
> --- a/package/pkg-cargo.mk
> +++ b/package/pkg-cargo.mk
> @@ -195,6 +195,18 @@ ifneq ($$($(2)_SUBDIR),)
>  $(2)_DOWNLOAD_POST_PROCESS_OPTS += -m$$($(2)_SUBDIR)/Cargo.toml
>  endif
>
> +ifndef $(2)_CARGO_LOCKED
> + ifdef $(3)_CARGO_LOCKED
> +  $(2)_CARGO_LOCKED = $$($(3)_CARGO_LOCKED)
> + else
> +  $(2)_CARGO_LOCKED = YES
> + endif
> +endif
> +
> +ifeq ($$($(2)_CARGO_LOCKED),NO)
> +$(2)_DOWNLOAD_POST_PROCESS_OPTS += -u
> +endif
> +
>  # Because we append vendored info, we can't rely on the values being empty
>  # once we eventually get into the generic-package infra. So, we duplicate
>  # the heuristics here
> @@ -225,7 +237,8 @@ endif
>  #    dependencies should have been built by the download post
>  #    process logic
>  #  * --locked to force cargo to use the Cargo.lock file, which ensures
> -#    that a fixed set of dependency versions is used
> +#    that cargo does not update the dependencies we got during the
> +#    vendoring, at download time
>
>  #
>  # Build step. Only define it if not already defined by the package .mk
> diff --git a/support/download/cargo-post-process b/support/download/cargo-post-process
> index 12198051a4..880a800600 100755
> --- a/support/download/cargo-post-process
> +++ b/support/download/cargo-post-process
> @@ -11,11 +11,13 @@ if [ "${BR_CARGO_MANIFEST_PATH}" ]; then
>  fi
>
>  manifest=Cargo.toml
> -while getopts "n:o:m:" OPT; do
> +locked=true
> +while getopts "n:o:m:u" OPT; do
>      case "${OPT}" in
>      o)  output="${OPTARG}";;
>      n)  base_name="${OPTARG}";;
>      m)  manifest="${OPTARG}";;
> +    u)  locked=false;;
>      :)  error "option '%s' expects a mandatory argument\n" "${OPTARG}";;
>      \?) error "unknown option '%s'\n" "${OPTARG}";;
>      esac
> @@ -26,19 +28,32 @@ if tar tf "${output}" | grep -q "^[^/]*/VENDOR" ; then
>      exit 0
>  fi
>
> +declare -a vendor_opts
> +vendor_opts=( --manifest-path "${manifest}" )
> +if ${locked}; then
> +    vendor_opts+=( --locked )
> +fi
> +
>  post_process_unpack "${base_name}" "${output}"
>
>  # Do the Cargo vendoring
>  pushd "${base_name}" > /dev/null
>
> +if [ -e Cargo.lock ] && ! ${locked}; then
> +    printf 'Unlocked vendoring was requested, but source tree has a Cargo.lock\n'
> +    exit 1
> +elif [ ! -e Cargo.lock ] && ${locked}; then
> +    # Although cargo itself would check for that, and error out in such
> +    # a case, the error message can be confusing, so do it ourselves
> +    printf 'Locked vendoring was requested, but source tree has no Cargo.lock\n'
> +    exit 1
> +fi >&2
> +
>  # Create the local .cargo/config with vendor info
>  mkdir -p .cargo/
>  mkdir -p "${CARGO_HOME}"
>  flock "${CARGO_HOME}"/.br-lock \
> -cargo vendor \
> -    --manifest-path "${manifest}" \
> -    --locked VENDOR \
> -    > .cargo/config
> +cargo vendor "${vendor_opts[@]}" VENDOR > .cargo/config
>
>  # "cargo vendor' outputs on stderr a message directing to add some data
>  # to the project's .cargo/config.toml, data that it outputs on stdout.
> --
> 2.45.1
>
> _______________________________________________
> buildroot mailing list
> buildroot@buildroot.org
> https://lists.buildroot.org/mailman/listinfo/buildroot
Yann E. MORIN June 2, 2024, 9:15 p.m. UTC | #2
James, All,

On 2024-06-02 14:53 -0600, James Hilliard spake thusly:
> On Sun, Jun 2, 2024 at 2:42 PM Yann E. MORIN <yann.morin.1998@free.fr> wrote:
[--SNIP--]
> > +When a package source tree contains a +Cargo.lock+ file, the package has
> > +been _locked_: cargo did store the versions for the complete dependency
> > +chain in the +Cargo.lock+ file; that is then reused by cargo during the
> > +vendoring, which yields a reproducible dependency chain, and thus a
> > +reproducible tarball; therefore, it is possible to compute a hash for
> > +that archive. If a package was however not locked, then the versions for
> > +the dependencies are unknown, the dependency chain is not reproducible,
> > +and the archive is then not reproducible, which implies there can be no
> > +hash for that archive.
> 
> Hmm, for these cases shouldn't we generate and commit the lockfile to
> the buildroot package so that we can then generate a deterministic archive
> and archive hash?

Thanks for the quick feedback! :-)
That's a good idea, indeed!

Instead of introducing the -u option, we'd do something like:

In foo.mk:

    FOO_CARGO_LOCKED = buildroot  (or whatever nmeans "not uptream")

in pkg-cargo.mk:

    $(2)_CARGO_LOCKED ?= upstream  (or anything that means "OK!!!")

in pkg-download.mk:

    dl-wrapper \
        [...] \
        $(if $(filter buildroot,$($(PKG)_CARGO_LOCKED)),-L $($(PKG)_PKGDIR)/Cargo.lock \
        [...]

and in cargo-post-process (pseudo code):

    if option -L is given:
        copy -L arg to ./Cargo.lock
    endif

    [ -f Cargo.lock ] || die 'No way!'

Was that what you had in mind?

> I think we want to have the ability to make these packages deterministic
> even if upstream fails to provide a Cargo.lock file.

Yes, the lack of reproducibility was really bothering me. But if we can
easily create a Cargo.lock without too much issue, then your suggestion
would be great!

Regards,
Yann E. MORIN.
James Hilliard June 2, 2024, 9:25 p.m. UTC | #3
On Sun, Jun 2, 2024 at 3:15 PM Yann E. MORIN <yann.morin.1998@free.fr> wrote:
>
> James, All,
>
> On 2024-06-02 14:53 -0600, James Hilliard spake thusly:
> > On Sun, Jun 2, 2024 at 2:42 PM Yann E. MORIN <yann.morin.1998@free.fr> wrote:
> [--SNIP--]
> > > +When a package source tree contains a +Cargo.lock+ file, the package has
> > > +been _locked_: cargo did store the versions for the complete dependency
> > > +chain in the +Cargo.lock+ file; that is then reused by cargo during the
> > > +vendoring, which yields a reproducible dependency chain, and thus a
> > > +reproducible tarball; therefore, it is possible to compute a hash for
> > > +that archive. If a package was however not locked, then the versions for
> > > +the dependencies are unknown, the dependency chain is not reproducible,
> > > +and the archive is then not reproducible, which implies there can be no
> > > +hash for that archive.
> >
> > Hmm, for these cases shouldn't we generate and commit the lockfile to
> > the buildroot package so that we can then generate a deterministic archive
> > and archive hash?
>
> Thanks for the quick feedback! :-)
> That's a good idea, indeed!
>
> Instead of introducing the -u option, we'd do something like:
>
> In foo.mk:
>
>     FOO_CARGO_LOCKED = buildroot  (or whatever nmeans "not uptream")
>
> in pkg-cargo.mk:
>
>     $(2)_CARGO_LOCKED ?= upstream  (or anything that means "OK!!!")
>
> in pkg-download.mk:
>
>     dl-wrapper \
>         [...] \
>         $(if $(filter buildroot,$($(PKG)_CARGO_LOCKED)),-L $($(PKG)_PKGDIR)/Cargo.lock \
>         [...]
>
> and in cargo-post-process (pseudo code):
>
>     if option -L is given:
>         copy -L arg to ./Cargo.lock
>     endif
>
>     [ -f Cargo.lock ] || die 'No way!'
>
> Was that what you had in mind?

Yeah, something along those lines, one thing that might be a bit tricky is
that we probably need to incorporate the Cargo.lock file hash or something
like that in the archive name, so that way if the Cargo.lock file needs to be
regenerated the archive name will also be appropriately changed at the same
time.

>
> > I think we want to have the ability to make these packages deterministic
> > even if upstream fails to provide a Cargo.lock file.
>
> Yes, the lack of reproducibility was really bothering me. But if we can
> easily create a Cargo.lock without too much issue, then your suggestion
> would be great!
>
> Regards,
> Yann E. MORIN.
>
> --
> .-----------------.--------------------.------------------.--------------------.
> |  Yann E. MORIN  | Real-Time Embedded | /"\ ASCII RIBBON | Erics' conspiracy: |
> | +33 662 376 056 | Software  Designer | \ / CAMPAIGN     |  ___               |
> | +33 561 099 427 `------------.-------:  X  AGAINST      |  \e/  There is no  |
> | http://ymorin.is-a-geek.org/ | _/*\_ | / \ HTML MAIL    |   v   conspiracy.  |
> '------------------------------^-------^------------------^--------------------'
Yann E. MORIN June 3, 2024, 11:52 a.m. UTC | #4
James, All,

On 2024-06-02 15:25 -0600, James Hilliard spake thusly:
> On Sun, Jun 2, 2024 at 3:15 PM Yann E. MORIN <yann.morin.1998@free.fr> wrote:
> > On 2024-06-02 14:53 -0600, James Hilliard spake thusly:
> > > On Sun, Jun 2, 2024 at 2:42 PM Yann E. MORIN <yann.morin.1998@free.fr> wrote:
[--SNIP--]
> > > > +When a package source tree contains a +Cargo.lock+ file, the package has
> > > > +been _locked_: cargo did store the versions for the complete dependency
> > > > +chain in the +Cargo.lock+ file; that is then reused by cargo during the
> > > > +vendoring, which yields a reproducible dependency chain, and thus a
> > > > +reproducible tarball; therefore, it is possible to compute a hash for
> > > > +that archive. If a package was however not locked, then the versions for
> > > > +the dependencies are unknown, the dependency chain is not reproducible,
> > > > +and the archive is then not reproducible, which implies there can be no
> > > > +hash for that archive.
> > > Hmm, for these cases shouldn't we generate and commit the lockfile to
> > > the buildroot package so that we can then generate a deterministic archive
> > > and archive hash?
[--SNIP--]
> Yeah, something along those lines, one thing that might be a bit tricky is
> that we probably need to incorporate the Cargo.lock file hash or something
> like that in the archive name, so that way if the Cargo.lock file needs to be
> regenerated the archive name will also be appropriately changed at the same
> time.

Hmmm... Indeed, this would cause quite some grief if we would change the
Cargo.lock...

However, I don't see an easy solution to this issue... :-/

Well, I do have an idea, and it would definitely work, but it would be
*excessively* ugly... I'll try to implement it, but as an additional
change on-top of the existing series, because I am not sure it would br
meaningful in the end... Let's see...

Regards,
Yann E. MORIN.
James Hilliard June 3, 2024, 5:48 p.m. UTC | #5
On Mon, Jun 3, 2024 at 5:52 AM Yann E. MORIN <yann.morin.1998@free.fr> wrote:
>
> James, All,
>
> On 2024-06-02 15:25 -0600, James Hilliard spake thusly:
> > On Sun, Jun 2, 2024 at 3:15 PM Yann E. MORIN <yann.morin.1998@free.fr> wrote:
> > > On 2024-06-02 14:53 -0600, James Hilliard spake thusly:
> > > > On Sun, Jun 2, 2024 at 2:42 PM Yann E. MORIN <yann.morin.1998@free.fr> wrote:
> [--SNIP--]
> > > > > +When a package source tree contains a +Cargo.lock+ file, the package has
> > > > > +been _locked_: cargo did store the versions for the complete dependency
> > > > > +chain in the +Cargo.lock+ file; that is then reused by cargo during the
> > > > > +vendoring, which yields a reproducible dependency chain, and thus a
> > > > > +reproducible tarball; therefore, it is possible to compute a hash for
> > > > > +that archive. If a package was however not locked, then the versions for
> > > > > +the dependencies are unknown, the dependency chain is not reproducible,
> > > > > +and the archive is then not reproducible, which implies there can be no
> > > > > +hash for that archive.
> > > > Hmm, for these cases shouldn't we generate and commit the lockfile to
> > > > the buildroot package so that we can then generate a deterministic archive
> > > > and archive hash?
> [--SNIP--]
> > Yeah, something along those lines, one thing that might be a bit tricky is
> > that we probably need to incorporate the Cargo.lock file hash or something
> > like that in the archive name, so that way if the Cargo.lock file needs to be
> > regenerated the archive name will also be appropriately changed at the same
> > time.
>
> Hmmm... Indeed, this would cause quite some grief if we would change the
> Cargo.lock...
>
> However, I don't see an easy solution to this issue... :-/
>
> Well, I do have an idea, and it would definitely work, but it would be
> *excessively* ugly... I'll try to implement it, but as an additional
> change on-top of the existing series, because I am not sure it would br
> meaningful in the end... Let's see...

Probably it would be good enough to say take the first 7 characters of the
sha256 hash(same length as a git shorthash) of the Cargo.lock file and
incorporate that into the archive filename appended to the package version.

>
> Regards,
> Yann E. MORIN.
>
> --
> .-----------------.--------------------.------------------.--------------------.
> |  Yann E. MORIN  | Real-Time Embedded | /"\ ASCII RIBBON | Erics' conspiracy: |
> | +33 662 376 056 | Software  Designer | \ / CAMPAIGN     |  ___               |
> | +33 561 099 427 `------------.-------:  X  AGAINST      |  \e/  There is no  |
> | http://ymorin.is-a-geek.org/ | _/*\_ | / \ HTML MAIL    |   v   conspiracy.  |
> '------------------------------^-------^------------------^--------------------'
Yann E. MORIN June 3, 2024, 6:43 p.m. UTC | #6
James, All,

On 2024-06-03 11:48 -0600, James Hilliard spake thusly:
> On Mon, Jun 3, 2024 at 5:52 AM Yann E. MORIN <yann.morin.1998@free.fr> wrote:
> > Hmmm... Indeed, this would cause quite some grief if we would change the
> > Cargo.lock...
> > However, I don't see an easy solution to this issue... :-/
> > Well, I do have an idea, and it would definitely work, but it would be
> > *excessively* ugly... I'll try to implement it, but as an additional
> > change on-top of the existing series, because I am not sure it would br
> > meaningful in the end... Let's see...
> Probably it would be good enough to say take the first 7 characters of the
> sha256 hash(same length as a git shorthash) of the Cargo.lock file and
> incorporate that into the archive filename appended to the package version.

The problem with short hashes, is that they are much less unique than
full hashes. We've excluded using short hashes wherever we had the
choice (see for example the recent git export-subst attribute, where we
replace the short-hash placeholder with the full-length hash). So I
would refrain from using a short-hash anywhere else as well.

There is a simpler solution, though, which is to rely on the .mk to set
the version and be done with it:

 1. it is very seldom that a cargo package is not locked, so we don't
    need anything too complex;
 2. for unlocked packages, we'll very seldom update our Cargo.lock, so
    we don't need anything too fancy;
 3. if one forgets to update the version when updating Cargo.lock,
    there will be a hash mismatch on download anyway, so we'll notice.

So that's what I've gone with. It requires a few tricks and tweaks here
and there, but this is already working since a few hours ago, but I got
distracted and did not have time to finish the work (and the commit
logs); maybe later tonight, or tomorrow...

Regards,
Yann E. MORIN.
James Hilliard June 3, 2024, 6:55 p.m. UTC | #7
On Mon, Jun 3, 2024 at 12:43 PM Yann E. MORIN <yann.morin.1998@free.fr> wrote:
>
> James, All,
>
> On 2024-06-03 11:48 -0600, James Hilliard spake thusly:
> > On Mon, Jun 3, 2024 at 5:52 AM Yann E. MORIN <yann.morin.1998@free.fr> wrote:
> > > Hmmm... Indeed, this would cause quite some grief if we would change the
> > > Cargo.lock...
> > > However, I don't see an easy solution to this issue... :-/
> > > Well, I do have an idea, and it would definitely work, but it would be
> > > *excessively* ugly... I'll try to implement it, but as an additional
> > > change on-top of the existing series, because I am not sure it would br
> > > meaningful in the end... Let's see...
> > Probably it would be good enough to say take the first 7 characters of the
> > sha256 hash(same length as a git shorthash) of the Cargo.lock file and
> > incorporate that into the archive filename appended to the package version.
>
> The problem with short hashes, is that they are much less unique than
> full hashes. We've excluded using short hashes wherever we had the
> choice (see for example the recent git export-subst attribute, where we
> replace the short-hash placeholder with the full-length hash). So I
> would refrain from using a short-hash anywhere else as well.

Well the short hash would only need to be unique per package version so
I think it would work fine in this case.

>
> There is a simpler solution, though, which is to rely on the .mk to set
> the version and be done with it:
>
>  1. it is very seldom that a cargo package is not locked, so we don't
>     need anything too complex;
>  2. for unlocked packages, we'll very seldom update our Cargo.lock, so
>     we don't need anything too fancy;
>  3. if one forgets to update the version when updating Cargo.lock,
>     there will be a hash mismatch on download anyway, so we'll notice.

Ok, but how would we handle a case where we need to update the
Cargo.lock but are unable to update the version(i.e. if the package does
not have a newer version).

Are you thinking of something like setting the Cargo.lock version in the .mk
and incorporating that in the archive somehow?

>
> So that's what I've gone with. It requires a few tricks and tweaks here
> and there, but this is already working since a few hours ago, but I got
> distracted and did not have time to finish the work (and the commit
> logs); maybe later tonight, or tomorrow...
>
> Regards,
> Yann E. MORIN.
>
> --
> .-----------------.--------------------.------------------.--------------------.
> |  Yann E. MORIN  | Real-Time Embedded | /"\ ASCII RIBBON | Erics' conspiracy: |
> | +33 662 376 056 | Software  Designer | \ / CAMPAIGN     |  ___               |
> | +33 561 099 427 `------------.-------:  X  AGAINST      |  \e/  There is no  |
> | http://ymorin.is-a-geek.org/ | _/*\_ | / \ HTML MAIL    |   v   conspiracy.  |
> '------------------------------^-------^------------------^--------------------'
Yann E. MORIN June 3, 2024, 7:45 p.m. UTC | #8
James, All,

On 2024-06-03 12:55 -0600, James Hilliard spake thusly:
> On Mon, Jun 3, 2024 at 12:43 PM Yann E. MORIN <yann.morin.1998@free.fr> wrote:
[--SNIP--]
> > The problem with short hashes, is that they are much less unique than
> > full hashes. [--SNIP--]
> Well the short hash would only need to be unique per package version so
> I think it would work fine in this case.

"All will be fine" I shouted at him as he was falling down the lava
lake... ;-)

Yes, you are right, that it should not cause any issue in practice.
However, getting the hash (short or full) means spawning a sub-shell;
it is not very great... Especially compared to the simpler solution,
below...

> > There is a simpler solution, though, which is to rely on the .mk to set
> > the version and be done with it:
> >
> >  1. it is very seldom that a cargo package is not locked, so we don't
> >     need anything too complex;
> >  2. for unlocked packages, we'll very seldom update our Cargo.lock, so
> >     we don't need anything too fancy;
> >  3. if one forgets to update the version when updating Cargo.lock,
> >     there will be a hash mismatch on download anyway, so we'll notice.
> 
> Ok, but how would we handle a case where we need to update the
> Cargo.lock but are unable to update the version(i.e. if the package does
> not have a newer version).

I was not speaking about the version of the package, but that of the
archive, like we have -git4, -cargo2, etc...

> Are you thinking of something like setting the Cargo.lock version in the .mk
> and incorporating that in the archive somehow?

Yes, exactly that: FOO_CARGO_LOCK_VERSION = N

And then the infra can append a -lockN suffix when it constructs the
archive filename.

Damn, you got me to reveal my cunning plan in advance! ;-)

Regards,
Yann E. MORIN.
diff mbox series

Patch

diff --git a/docs/manual/adding-packages-cargo.adoc b/docs/manual/adding-packages-cargo.adoc
index 8a2292e7b6..0ed529a770 100644
--- a/docs/manual/adding-packages-cargo.adoc
+++ b/docs/manual/adding-packages-cargo.adoc
@@ -82,12 +82,17 @@  typical packages will therefore only use a few of them.
   environment of +cargo+ invocations. It used at both build and
   installation time
 
+* +FOO_CARGO_LOCKED+ can be set to +YES+ (the default) or +NO+, to
+  specify whether the source tree has been cargo-locked or not. See
+  below for xref:cargo-vendoring[more details].
+
 * +FOO_CARGO_BUILD_OPTS+ can be used to pass additional options to
   +cargo+ at build time.
 
 * +FOO_CARGO_INSTALL_OPTS+ can be used to pass additional options to
   +cargo+ at install time.
 
+[[cargo-vendoring]]
 A crate can depend on other libraries from crates.io or git
 repositories, listed in its +Cargo.toml+ file. Buildroot automatically
 takes care of downloading such dependencies as part of the download
@@ -95,3 +100,20 @@  step of packages that use the +cargo-package+ infrastructure. Such
 dependencies are then kept together with the package source code in
 the tarball cached in Buildroot's +DL_DIR+. This is referred to as
 _vendoring_. Vendoring allows for a completely off-line build.
+
+When a package source tree contains a +Cargo.lock+ file, the package has
+been _locked_: cargo did store the versions for the complete dependency
+chain in the +Cargo.lock+ file; that is then reused by cargo during the
+vendoring, which yields a reproducible dependency chain, and thus a
+reproducible tarball; therefore, it is possible to compute a hash for
+that archive. If a package was however not locked, then the versions for
+the dependencies are unknown, the dependency chain is not reproducible,
+and the archive is then not reproducible, which implies there can be no
+hash for that archive.
+
+Packages are normally locked, so Buildroot will by default instruct
+cargo to atttempt a locked vendoring; for a package that was not locked,
+this would fail; in that case, +FOO_CARGO_LOCKED+ must be set to +NO+ so
+Buildroot does not request cargo to attempt a locked vendoring. It is an
+error to set +FOO_CARGO_LOCKED+ to +NO+ for a locked package, as it is
+to set it to +YES+ (or not set it at all) on an unlocked package.
diff --git a/package/pkg-cargo.mk b/package/pkg-cargo.mk
index 41dfcbd096..90e4facf52 100644
--- a/package/pkg-cargo.mk
+++ b/package/pkg-cargo.mk
@@ -195,6 +195,18 @@  ifneq ($$($(2)_SUBDIR),)
 $(2)_DOWNLOAD_POST_PROCESS_OPTS += -m$$($(2)_SUBDIR)/Cargo.toml
 endif
 
+ifndef $(2)_CARGO_LOCKED
+ ifdef $(3)_CARGO_LOCKED
+  $(2)_CARGO_LOCKED = $$($(3)_CARGO_LOCKED)
+ else
+  $(2)_CARGO_LOCKED = YES
+ endif
+endif
+
+ifeq ($$($(2)_CARGO_LOCKED),NO)
+$(2)_DOWNLOAD_POST_PROCESS_OPTS += -u
+endif
+
 # Because we append vendored info, we can't rely on the values being empty
 # once we eventually get into the generic-package infra. So, we duplicate
 # the heuristics here
@@ -225,7 +237,8 @@  endif
 #    dependencies should have been built by the download post
 #    process logic
 #  * --locked to force cargo to use the Cargo.lock file, which ensures
-#    that a fixed set of dependency versions is used
+#    that cargo does not update the dependencies we got during the
+#    vendoring, at download time
 
 #
 # Build step. Only define it if not already defined by the package .mk
diff --git a/support/download/cargo-post-process b/support/download/cargo-post-process
index 12198051a4..880a800600 100755
--- a/support/download/cargo-post-process
+++ b/support/download/cargo-post-process
@@ -11,11 +11,13 @@  if [ "${BR_CARGO_MANIFEST_PATH}" ]; then
 fi
 
 manifest=Cargo.toml
-while getopts "n:o:m:" OPT; do
+locked=true
+while getopts "n:o:m:u" OPT; do
     case "${OPT}" in
     o)  output="${OPTARG}";;
     n)  base_name="${OPTARG}";;
     m)  manifest="${OPTARG}";;
+    u)  locked=false;;
     :)  error "option '%s' expects a mandatory argument\n" "${OPTARG}";;
     \?) error "unknown option '%s'\n" "${OPTARG}";;
     esac
@@ -26,19 +28,32 @@  if tar tf "${output}" | grep -q "^[^/]*/VENDOR" ; then
     exit 0
 fi
 
+declare -a vendor_opts
+vendor_opts=( --manifest-path "${manifest}" )
+if ${locked}; then
+    vendor_opts+=( --locked )
+fi
+
 post_process_unpack "${base_name}" "${output}"
 
 # Do the Cargo vendoring
 pushd "${base_name}" > /dev/null
 
+if [ -e Cargo.lock ] && ! ${locked}; then
+    printf 'Unlocked vendoring was requested, but source tree has a Cargo.lock\n'
+    exit 1
+elif [ ! -e Cargo.lock ] && ${locked}; then
+    # Although cargo itself would check for that, and error out in such
+    # a case, the error message can be confusing, so do it ourselves
+    printf 'Locked vendoring was requested, but source tree has no Cargo.lock\n'
+    exit 1
+fi >&2
+
 # Create the local .cargo/config with vendor info
 mkdir -p .cargo/
 mkdir -p "${CARGO_HOME}"
 flock "${CARGO_HOME}"/.br-lock \
-cargo vendor \
-    --manifest-path "${manifest}" \
-    --locked VENDOR \
-    > .cargo/config
+cargo vendor "${vendor_opts[@]}" VENDOR > .cargo/config
 
 # "cargo vendor' outputs on stderr a message directing to add some data
 # to the project's .cargo/config.toml, data that it outputs on stdout.