xref: /aosp_15_r20/external/bazel-skylib/rules/directory/private/glob.bzl (revision bcb5dc7965af6ee42bf2f21341a2ec00233a8c8a)
1*bcb5dc79SHONG Yifan# Copyright 2024 The Bazel Authors. All rights reserved.
2*bcb5dc79SHONG Yifan#
3*bcb5dc79SHONG Yifan# Licensed under the Apache License, Version 2.0 (the "License");
4*bcb5dc79SHONG Yifan# you may not use this file except in compliance with the License.
5*bcb5dc79SHONG Yifan# You may obtain a copy of the License at
6*bcb5dc79SHONG Yifan#
7*bcb5dc79SHONG Yifan#    http://www.apache.org/licenses/LICENSE-2.0
8*bcb5dc79SHONG Yifan#
9*bcb5dc79SHONG Yifan# Unless required by applicable law or agreed to in writing, software
10*bcb5dc79SHONG Yifan# distributed under the License is distributed on an "AS IS" BASIS,
11*bcb5dc79SHONG Yifan# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*bcb5dc79SHONG Yifan# See the License for the specific language governing permissions and
13*bcb5dc79SHONG Yifan# limitations under the License.
14*bcb5dc79SHONG Yifan
15*bcb5dc79SHONG Yifan"""Skylib module containing glob operations on directories."""
16*bcb5dc79SHONG Yifan
17*bcb5dc79SHONG Yifan_NO_GLOB_MATCHES = "{glob} failed to match any files in {dir}"
18*bcb5dc79SHONG Yifan
19*bcb5dc79SHONG Yifandef transitive_entries(directory):
20*bcb5dc79SHONG Yifan    """Returns the files and directories contained within a directory transitively.
21*bcb5dc79SHONG Yifan
22*bcb5dc79SHONG Yifan    Args:
23*bcb5dc79SHONG Yifan        directory: (DirectoryInfo) The directory to look at
24*bcb5dc79SHONG Yifan
25*bcb5dc79SHONG Yifan    Returns:
26*bcb5dc79SHONG Yifan        List[Either[DirectoryInfo, File]] The entries contained within.
27*bcb5dc79SHONG Yifan    """
28*bcb5dc79SHONG Yifan    entries = [directory]
29*bcb5dc79SHONG Yifan    stack = [directory]
30*bcb5dc79SHONG Yifan    for _ in range(99999999):
31*bcb5dc79SHONG Yifan        if not stack:
32*bcb5dc79SHONG Yifan            return entries
33*bcb5dc79SHONG Yifan        d = stack.pop()
34*bcb5dc79SHONG Yifan        for entry in d.entries.values():
35*bcb5dc79SHONG Yifan            entries.append(entry)
36*bcb5dc79SHONG Yifan            if type(entry) != "File":
37*bcb5dc79SHONG Yifan                stack.append(entry)
38*bcb5dc79SHONG Yifan
39*bcb5dc79SHONG Yifan    fail("Should never get to here")
40*bcb5dc79SHONG Yifan
41*bcb5dc79SHONG Yifandef directory_glob_chunk(directory, chunk):
42*bcb5dc79SHONG Yifan    """Given a directory and a chunk of a glob, returns possible candidates.
43*bcb5dc79SHONG Yifan
44*bcb5dc79SHONG Yifan    Args:
45*bcb5dc79SHONG Yifan        directory: (DirectoryInfo) The directory to look relative from.
46*bcb5dc79SHONG Yifan        chunk: (string) A chunk of a glob to look at.
47*bcb5dc79SHONG Yifan
48*bcb5dc79SHONG Yifan    Returns:
49*bcb5dc79SHONG Yifan        List[Either[DirectoryInfo, File]]] The candidate next entries for the chunk.
50*bcb5dc79SHONG Yifan    """
51*bcb5dc79SHONG Yifan    if chunk == "*":
52*bcb5dc79SHONG Yifan        return directory.entries.values()
53*bcb5dc79SHONG Yifan    elif chunk == "**":
54*bcb5dc79SHONG Yifan        return transitive_entries(directory)
55*bcb5dc79SHONG Yifan    elif "*" not in chunk:
56*bcb5dc79SHONG Yifan        if chunk in directory.entries:
57*bcb5dc79SHONG Yifan            return [directory.entries[chunk]]
58*bcb5dc79SHONG Yifan        else:
59*bcb5dc79SHONG Yifan            return []
60*bcb5dc79SHONG Yifan    elif chunk.count("*") > 2:
61*bcb5dc79SHONG Yifan        fail("glob chunks with more than two asterixes are unsupported. Got", chunk)
62*bcb5dc79SHONG Yifan
63*bcb5dc79SHONG Yifan    if chunk.count("*") == 2:
64*bcb5dc79SHONG Yifan        left, middle, right = chunk.split("*")
65*bcb5dc79SHONG Yifan    else:
66*bcb5dc79SHONG Yifan        middle = ""
67*bcb5dc79SHONG Yifan        left, right = chunk.split("*")
68*bcb5dc79SHONG Yifan    entries = []
69*bcb5dc79SHONG Yifan    for name, entry in directory.entries.items():
70*bcb5dc79SHONG Yifan        if name.startswith(left) and name.endswith(right) and len(left) + len(right) <= len(name) and middle in name[len(left):len(name) - len(right)]:
71*bcb5dc79SHONG Yifan            entries.append(entry)
72*bcb5dc79SHONG Yifan    return entries
73*bcb5dc79SHONG Yifan
74*bcb5dc79SHONG Yifandef directory_single_glob(directory, glob):
75*bcb5dc79SHONG Yifan    """Calculates all files that are matched by a glob on a directory.
76*bcb5dc79SHONG Yifan
77*bcb5dc79SHONG Yifan    Args:
78*bcb5dc79SHONG Yifan        directory: (DirectoryInfo) The directory to look relative from.
79*bcb5dc79SHONG Yifan        glob: (string) A glob to match.
80*bcb5dc79SHONG Yifan
81*bcb5dc79SHONG Yifan    Returns:
82*bcb5dc79SHONG Yifan        List[File] A list of files that match.
83*bcb5dc79SHONG Yifan    """
84*bcb5dc79SHONG Yifan
85*bcb5dc79SHONG Yifan    # Treat a glob as a nondeterministic finite state automata. We can be in
86*bcb5dc79SHONG Yifan    # multiple places at the one time.
87*bcb5dc79SHONG Yifan    candidate_dirs = [directory]
88*bcb5dc79SHONG Yifan    candidate_files = []
89*bcb5dc79SHONG Yifan    for chunk in glob.split("/"):
90*bcb5dc79SHONG Yifan        next_candidate_dirs = {}
91*bcb5dc79SHONG Yifan        candidate_files = {}
92*bcb5dc79SHONG Yifan        for candidate in candidate_dirs:
93*bcb5dc79SHONG Yifan            for e in directory_glob_chunk(candidate, chunk):
94*bcb5dc79SHONG Yifan                if type(e) == "File":
95*bcb5dc79SHONG Yifan                    candidate_files[e] = None
96*bcb5dc79SHONG Yifan                else:
97*bcb5dc79SHONG Yifan                    next_candidate_dirs[e.human_readable] = e
98*bcb5dc79SHONG Yifan        candidate_dirs = next_candidate_dirs.values()
99*bcb5dc79SHONG Yifan
100*bcb5dc79SHONG Yifan    return list(candidate_files)
101*bcb5dc79SHONG Yifan
102*bcb5dc79SHONG Yifandef glob(directory, include, exclude = [], allow_empty = False):
103*bcb5dc79SHONG Yifan    """native.glob, but for DirectoryInfo.
104*bcb5dc79SHONG Yifan
105*bcb5dc79SHONG Yifan    Args:
106*bcb5dc79SHONG Yifan        directory: (DirectoryInfo) The directory to look relative from.
107*bcb5dc79SHONG Yifan        include: (List[string]) A list of globs to match.
108*bcb5dc79SHONG Yifan        exclude: (List[string]) A list of globs to exclude.
109*bcb5dc79SHONG Yifan        allow_empty: (bool) Whether to allow a glob to not match any files.
110*bcb5dc79SHONG Yifan
111*bcb5dc79SHONG Yifan    Returns:
112*bcb5dc79SHONG Yifan        depset[File] A set of files that match.
113*bcb5dc79SHONG Yifan    """
114*bcb5dc79SHONG Yifan    include_files = []
115*bcb5dc79SHONG Yifan    for g in include:
116*bcb5dc79SHONG Yifan        matches = directory_single_glob(directory, g)
117*bcb5dc79SHONG Yifan        if not matches and not allow_empty:
118*bcb5dc79SHONG Yifan            fail(_NO_GLOB_MATCHES.format(
119*bcb5dc79SHONG Yifan                glob = repr(g),
120*bcb5dc79SHONG Yifan                dir = directory.human_readable,
121*bcb5dc79SHONG Yifan            ))
122*bcb5dc79SHONG Yifan        include_files.extend(matches)
123*bcb5dc79SHONG Yifan
124*bcb5dc79SHONG Yifan    if not exclude:
125*bcb5dc79SHONG Yifan        return depset(include_files)
126*bcb5dc79SHONG Yifan
127*bcb5dc79SHONG Yifan    include_files = {k: None for k in include_files}
128*bcb5dc79SHONG Yifan    for g in exclude:
129*bcb5dc79SHONG Yifan        matches = directory_single_glob(directory, g)
130*bcb5dc79SHONG Yifan        if not matches and not allow_empty:
131*bcb5dc79SHONG Yifan            fail(_NO_GLOB_MATCHES.format(
132*bcb5dc79SHONG Yifan                glob = repr(g),
133*bcb5dc79SHONG Yifan                dir = directory.human_readable,
134*bcb5dc79SHONG Yifan            ))
135*bcb5dc79SHONG Yifan        for f in matches:
136*bcb5dc79SHONG Yifan            include_files.pop(f, None)
137*bcb5dc79SHONG Yifan    return depset(include_files.keys())
138