Skip to content

Instantly share code, notes, and snippets.

@JuryA
Forked from nmattia/default.nix
Created September 8, 2024 13:19

Revisions

  1. @nmattia nmattia revised this gist Nov 1, 2021. 1 changed file with 25 additions and 0 deletions.
    25 changes: 25 additions & 0 deletions default.nix
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,5 @@
    # MIT License, see below
    #
    # These are some helpers for figuring out the derivations attributes of runtime
    # dependencies of a derivation, in particular the function `runtimeReport`. At
    # the bottom of the file you can see it used on `hello`. Spoiler: glibc is a
    @@ -190,3 +192,26 @@ cinfo = drv: runCommandNoCC "${drv.name}-cinfo"
    '';

    in runtimeReport pkgs.hello

    # MIT License
    #
    # Copyright (c) 2021 Nicolas Mattia
    #
    # Permission is hereby granted, free of charge, to any person obtaining a copy
    # of this software and associated documentation files (the "Software"), to deal
    # in the Software without restriction, including without limitation the rights
    # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    # copies of the Software, and to permit persons to whom the Software is
    # furnished to do so, subject to the following conditions:
    #
    # The above copyright notice and this permission notice shall be included in all
    # copies or substantial portions of the Software.
    #
    # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    # SOFTWARE.
    #
  2. @nmattia nmattia revised this gist Oct 7, 2019. 1 changed file with 7 additions and 8 deletions.
    15 changes: 7 additions & 8 deletions default.nix
    Original file line number Diff line number Diff line change
    @@ -1,10 +1,10 @@
    # These are some helpers for figuring out the derivations attributes of a
    # derivation's runtime dependencies.
    # These are some helpers for figuring out the derivations attributes of runtime
    # dependencies of a derivation, in particular the function `runtimeReport`. At
    # the bottom of the file you can see it used on `hello`. Spoiler: glibc is a
    # runtime dependency.
    # For more info see
    #
    # Example:
    # let pkgs = import <nixpkgs> {}; in runtimeReport pkgs.hello
    #
    # Spoiler: glibc is a runtime dependency. # TODO: link to article
    # https://nmattia.com/posts/2019-10-08-runtime-dependencies.html

    # Let's call these "imports". They're functions used throughout the code.
    # Nothing interesting here.
    @@ -84,7 +84,6 @@ runtimeReport = drv:
    echo " ---------------------------------"
    echo
    echo "runtime dependencies of ${drv.name}:"
    # TODO: note: you can use `buildtimeReports` as well
    cat ${buildtimeReports drv} |\
    jq -r --slurpfile runtime ${cinfo drv} \
    ' # First, we strip away (path-)duplicates.
    @@ -190,4 +189,4 @@ cinfo = drv: runCommandNoCC "${drv.name}-cinfo"
    jq -c 'map(select( length > 0 ))' > $out
    '';

    in runtimeReport pkgs.hello
    in runtimeReport pkgs.hello
  3. @nmattia nmattia created this gist Oct 7, 2019.
    193 changes: 193 additions & 0 deletions default.nix
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,193 @@
    # These are some helpers for figuring out the derivations attributes of a
    # derivation's runtime dependencies.
    #
    # Example:
    # let pkgs = import <nixpkgs> {}; in runtimeReport pkgs.hello
    #
    # Spoiler: glibc is a runtime dependency. # TODO: link to article

    # Let's call these "imports". They're functions used throughout the code.
    # Nothing interesting here.
    with rec
    { pkgs = import <nixpkgs> {};
    inherit (pkgs)
    closureInfo
    runCommandNoCC
    writeText
    jq
    ;
    inherit (pkgs.lib)
    concatLists
    concatMap
    concatStringsSep
    filter
    isAttrs
    isDerivation
    isList
    isString
    mapAttrsToList
    ;
    inherit (builtins)
    genericClosure
    hasAttr
    toJSON
    typeOf
    unsafeDiscardStringContext
    ;
    };

    let

    # Create a "runtime report" of the runtime dependencies of `drv`. A "runtime
    # report" is made up of smaller "dependency reports". A "dependency report" is
    # a string describing the dependency, made from the dependency's derivation
    # attributes. Here we use `mkReport` to make the report of any particular
    # dependency.
    #
    # NOTE: we use the following terms:
    #
    # * "buildtime" to mean basically any derivation involved in the build of
    # `drv`.
    # * "buildtime-only" for the "buildtime" dependencies that _are not_
    # referenced anymore by `drv`'s store entry.
    # * "runtime" for the rest.
    #
    # The "runtime report" is created in two steps:
    #
    # * Generate reports for all the _buildtime_ dependencies with
    # `buildtimeReports`.
    # * Filter out the reports for buildtime-only dependencies.
    #
    # Most of the "buildtime" reports won't even be used, because most buildtime
    # dependencies are buildtime-only dependencies. However Nix does not give us a
    # way of retrieving the derivation attributes of runtime dependencies, but we
    # can twist its arm to:
    #
    # * Give us the store paths of runtime dependencies (see `cinfo`).
    # * Give us the derivation attributes of all the buildtime dependencies (see
    # `buildtimeDerivations`).
    #
    # Here's the hack: `buildtimeReports` tags the reports with the (expected)
    # store path of the "buildtime" dependency, which we cross check against the
    # list of runtime store paths. If it's a match, we keep it. Otherwise, we
    # discard it.
    runtimeReport = drv:
    runCommandNoCC "${drv.name}-report" { buildInputs = [ jq ]; }
    # XXX: This is to avoid IFD
    ''
    (
    echo " ---------------------------------"
    echo " | OFFICIAL REPORT |"
    echo " | requested by: the lawyers |"
    echo " | written by: yours truly |"
    echo " | TOP SECRET - TOP SECRET |"
    echo " ---------------------------------"
    echo
    echo "runtime dependencies of ${drv.name}:"
    # TODO: note: you can use `buildtimeReports` as well
    cat ${buildtimeReports drv} |\
    jq -r --slurpfile runtime ${cinfo drv} \
    ' # First, we strip away (path-)duplicates.
    unique_by(.path)
    # Then we map over each build-time derivation and use `select()`
    # to keep only the ones that show up in $runtime
    | map( # this little beauty checks if "obj.path" is in "runtime"
    select(. as $obj | $runtime | any(.[] | . == $obj.path))
    | .report)
    | .[]'
    ) > $out
    '';

    # Creates reports for all of `drv`'s buildtime dependencies. Each element in
    # the list has two fields:
    #
    # * path = "/nix/store/..."
    # * report = "some report based on the dependency's derivation attributes"
    buildtimeReports = drv: writeText "${drv.name}-runtime" ( toJSON (
    map (obj:
    # unsafe: optimization to avoid downloading unused deps
    { # XXX: we discard the context of the dependencies' store paths because
    # they're only ever used for lookup. This matters when fetching a
    # prebuilt final report -- there's no point downloading all of `drv`'s
    # buildtime dependencies.
    path = unsafeDiscardStringContext obj.key;
    report = mkReport obj.drv;
    }
    )
    (buildtimeDerivations drv) # the heavy lifting is done somewhere else
    ));

    # Returns a list of all of `drv0`'s inputs, a.k.a. buildtime dependencies.
    # Elements in the list has two fields:
    #
    # * key: the store path of the input.
    # * drv: the actual derivation object.
    #
    # There are no guarantees that this will find _all_ inputs, but it works well
    # enough in practice.
    #
    buildtimeDerivations = drv0:
    let
    # We include all the outputs because they each have different outPaths
    drvOutputs = drv:
    # XXX: some derivations, like stdenv, don't have "outputs"
    if hasAttr "outputs" drv
    then map (output: drv.${output}) drv.outputs
    else [ drv ];

    # Recurse into the derivation attributes to find new derivations
    drvDeps = attrs:
    mapAttrsToList (k: v:
    if isDerivation v then (drvOutputs v)
    else if isList v
    then concatMap drvOutputs (filter isDerivation v)
    else []
    ) attrs;
    in
    # Walk through the whole DAG of dependencies, using the `outPath` as an
    # index for the elements.
    let wrap = drv: { key = drv.outPath ; inherit drv; }; in genericClosure
    { startSet = map wrap (drvOutputs drv0) ;
    operator = obj: map wrap
    ( concatLists (drvDeps obj.drv.drvAttrs) ) ;
    };

    # make a report. Would could also output a json object and process everything
    # later on.
    mkReport = drv:
    let
    license =
    if hasAttr "meta" drv && hasAttr "license" drv.meta then
    if isList drv.meta.license then
    concatStringsSep ", " (
    map renderLicense drv.meta.license)
    else renderLicense drv.meta.license
    else "no license";

    maintainer =
    if hasAttr "meta" drv && hasAttr "maintainers" drv.meta
    then concatStringsSep ", " (map (m: m.name) drv.meta.maintainers)
    else "nobody";

    in " - ${drv.name} (${license}) maintained by ${maintainer}";

    # Basically pretty prints a license
    renderLicense = license:
    if isAttrs license then license.shortName
    else if isString license then license
    else abort "no idea how to handle license of type ${typeOf license}";

    # This is a wrapper around nixpkgs' `closureInfo`. It produces a JSON file
    # containing a list of the store paths of `drv`'s runtime dependencies.
    cinfo = drv: runCommandNoCC "${drv.name}-cinfo"
    { buildInputs = [ jq ]; }
    # NOTE: we avoid IFD here as well
    ''
    cat ${closureInfo { rootPaths = [ drv ]; }}/store-paths |\
    grep -v "^$" |\
    jq -R -s -c 'split("\n")' |\
    jq -c 'map(select( length > 0 ))' > $out
    '';

    in runtimeReport pkgs.hello