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