···9 _fileFilter
10 _printFileset
11 _intersection
12+ _difference
13 ;
1415 inherit (builtins)
···366 ];
367 in
368 _intersection
369+ (elemAt filesets 0)
370+ (elemAt filesets 1);
371+372+ /*
373+ The file set containing all files from the first file set that are not in the second file set.
374+ See also [Difference (set theory)](https://en.wikipedia.org/wiki/Complement_(set_theory)#Relative_complement).
375+376+ The given file sets are evaluated as lazily as possible,
377+ with the first argument being evaluated first if needed.
378+379+ Type:
380+ union :: FileSet -> FileSet -> FileSet
381+382+ Example:
383+ # Create a file set containing all files from the current directory,
384+ # except ones under ./tests
385+ difference ./. ./tests
386+387+ let
388+ # A set of Nix-related files
389+ nixFiles = unions [ ./default.nix ./nix ./tests/default.nix ];
390+ in
391+ # Create a file set containing all files under ./tests, except ones in `nixFiles`,
392+ # meaning only without ./tests/default.nix
393+ difference ./tests nixFiles
394+ */
395+ difference =
396+ # The positive file set.
397+ # The result can only contain files that are also in this file set.
398+ #
399+ # This argument can also be a path,
400+ # which gets [implicitly coerced to a file set](#sec-fileset-path-coercion).
401+ positive:
402+ # The negative file set.
403+ # The result will never contain files that are also in this file set.
404+ #
405+ # This argument can also be a path,
406+ # which gets [implicitly coerced to a file set](#sec-fileset-path-coercion).
407+ negative:
408+ let
409+ filesets = _coerceMany "lib.fileset.difference" [
410+ {
411+ context = "first argument (positive set)";
412+ value = positive;
413+ }
414+ {
415+ context = "second argument (negative set)";
416+ value = negative;
417+ }
418+ ];
419+ in
420+ _difference
421 (elemAt filesets 0)
422 (elemAt filesets 1);
423
+80
lib/fileset/internal.nix
···651 # In all other cases it's the rhs
652 rhs;
65300000000000000000000000000000000000000000000000000000000000000000000000000000000654 _fileFilter = predicate: fileset:
655 let
656 recurse = path: tree:
···651 # In all other cases it's the rhs
652 rhs;
653654+ # Compute the set difference between two file sets.
655+ # The filesets must already be coerced and validated to be in the same filesystem root.
656+ # Type: Fileset -> Fileset -> Fileset
657+ _difference = positive: negative:
658+ let
659+ # The common base components prefix, e.g.
660+ # (/foo/bar, /foo/bar/baz) -> /foo/bar
661+ # (/foo/bar, /foo/baz) -> /foo
662+ commonBaseComponentsLength =
663+ # TODO: Have a `lib.lists.commonPrefixLength` function such that we don't need the list allocation from commonPrefix here
664+ length (
665+ commonPrefix
666+ positive._internalBaseComponents
667+ negative._internalBaseComponents
668+ );
669+670+ # We need filesetTree's with the same base to be able to compute the difference between them
671+ # This here is the filesetTree from the negative file set, but for a base path that matches the positive file set.
672+ # Examples:
673+ # For `difference /foo /foo/bar`, `negativeTreeWithPositiveBase = { bar = "directory"; }`
674+ # because under the base path of `/foo`, only `bar` from the negative file set is included
675+ # For `difference /foo/bar /foo`, `negativeTreeWithPositiveBase = "directory"`
676+ # because under the base path of `/foo/bar`, everything from the negative file set is included
677+ # For `difference /foo /bar`, `negativeTreeWithPositiveBase = null`
678+ # because under the base path of `/foo`, nothing from the negative file set is included
679+ negativeTreeWithPositiveBase =
680+ if commonBaseComponentsLength == length positive._internalBaseComponents then
681+ # The common prefix is the same as the positive base path, so the second path is equal or longer.
682+ # We need to _shorten_ the negative filesetTree to the same base path as the positive one
683+ # E.g. for `difference /foo /foo/bar` the common prefix is /foo, equal to the positive file set's base
684+ # So we need to shorten the base of the tree for the negative argument from /foo/bar to just /foo
685+ _shortenTreeBase positive._internalBaseComponents negative
686+ else if commonBaseComponentsLength == length negative._internalBaseComponents then
687+ # The common prefix is the same as the negative base path, so the first path is longer.
688+ # We need to lengthen the negative filesetTree to the same base path as the positive one.
689+ # E.g. for `difference /foo/bar /foo` the common prefix is /foo, equal to the negative file set's base
690+ # So we need to lengthen the base of the tree for the negative argument from /foo to /foo/bar
691+ _lengthenTreeBase positive._internalBaseComponents negative
692+ else
693+ # The common prefix is neither the first nor the second path.
694+ # This means there's no overlap between the two file sets,
695+ # and nothing from the negative argument should get removed from the positive one
696+ # E.g for `difference /foo /bar`, we remove nothing to get the same as `/foo`
697+ null;
698+699+ resultingTree =
700+ _differenceTree
701+ positive._internalBase
702+ positive._internalTree
703+ negativeTreeWithPositiveBase;
704+ in
705+ # If the first file set is empty, we can never have any files in the result
706+ if positive._internalIsEmptyWithoutBase then
707+ _emptyWithoutBase
708+ # If the second file set is empty, nothing gets removed, so the result is just the first file set
709+ else if negative._internalIsEmptyWithoutBase then
710+ positive
711+ else
712+ # We use the positive file set base for the result,
713+ # because only files from the positive side may be included,
714+ # which is what base path is for
715+ _create positive._internalBase resultingTree;
716+717+ # Computes the set difference of two filesetTree's
718+ # Type: Path -> filesetTree -> filesetTree
719+ _differenceTree = path: lhs: rhs:
720+ # If the lhs doesn't have any files, or the right hand side includes all files
721+ if lhs == null || isString rhs then
722+ # The result will always be empty
723+ null
724+ # If the right hand side has no files
725+ else if rhs == null then
726+ # The result is always the left hand side, because nothing gets removed
727+ lhs
728+ else
729+ # Otherwise we always have two attribute sets to recurse into
730+ mapAttrs (name: lhsValue:
731+ _differenceTree (path + "/${name}") lhsValue (rhs.${name} or null)
732+ ) (_directoryEntries path lhs);
733+734 _fileFilter = predicate: fileset:
735 let
736 recurse = path: tree: