├── .gitattributes ├── README.md ├── .gitignore ├── LICENSE ├── unpack_sdks.sh ├── tools └── gen_headers_source.zig ├── src ├── headers.c └── main.zig └── COPYING.unpack_sdks /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fetch-them-macos-headers 2 | 3 | A deprecated project that was used to collect macOS system header files. 4 | 5 | The new workflow is here: https://github.com/ziglang/zig/wiki/Updating-libc#darwin 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This file is for zig-specific build artifacts. 2 | # If you have OS-specific or editor-specific files to ignore, 3 | # such as *.swp or .DS_Store, put those in your global 4 | # ~/.gitignore and put this in your ~/.gitconfig: 5 | # 6 | # [core] 7 | # excludesfile = ~/.gitignore 8 | # 9 | # Cheers! 10 | # -andrewrk 11 | 12 | .zig-cache/ 13 | zig-out/ 14 | /release/ 15 | /debug/ 16 | /build/ 17 | /build-*/ 18 | /docgen_tmp/ 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Jakub Konka 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /unpack_sdks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | BUILD_DIR="$1" 5 | CLT_TMP_DIR="$BUILD_DIR/apple-clts" 6 | 7 | mkdir -p "$CLT_TMP_DIR" 8 | 9 | # extract mounted "Command Line Developer Tools" installers 10 | for d in "/Volumes/Command Line Developer Tools"*; do 11 | [ -d "$d" ] || continue 12 | ID= 13 | DISK_IMAGE="$(hdiutil info | grep -B20 "$d" | grep image-path | 14 | awk '{print $3 $4 $5 $6 $7}' || true)" 15 | if [ -f "$DISK_IMAGE" ]; then 16 | ID=$(shasum "$DISK_IMAGE" | cut -d' ' -f1) 17 | else 18 | ID=$(shasum <<< "$d" | cut -d' ' -f1) 19 | fi 20 | SUBDIR="$CLT_TMP_DIR/$ID" 21 | if [ -f "$SUBDIR/processed.mark" ]; then 22 | echo "skipping already-processed $(relpath "$SUBDIR")" 23 | continue 24 | fi 25 | echo "extracting $(relpath "$d")" 26 | mkdir -p "$SUBDIR" 27 | pushd "$SUBDIR" 28 | pkg="$(echo "$d/Command Line Tools"*.pkg)" 29 | if [ -f "$pkg" ]; then 30 | # extra .pkg wrapper 31 | echo " xar -xf $pkg" 32 | xar -xf "$pkg" 33 | else 34 | # pre 10.14 SDKs didn't have that wrapper 35 | echo " cp" "$d"/*_SDK_macOS*.pkg "." 36 | cp -a "$d"/*_SDK_macOS*.pkg . 37 | fi 38 | for f in *_mac*_SDK.pkg *_SDK_macOS*.pkg; do 39 | [ -e "$f" ] || continue 40 | payload_file="$f/Payload" 41 | if [ -f "$f" ]; then 42 | echo " xar -xf $f" 43 | xar -xf "$f" 44 | payload_file="Payload" 45 | fi 46 | echo " pbzx -n $payload_file | cpio -i" 47 | pbzx -n "$payload_file" | cpio -i 2>/dev/null & 48 | done 49 | printf " ..." ; wait ; echo 50 | rm -rf Payload Bom PackageInfo Distribution Resources *.pkg 51 | popd 52 | touch "$SUBDIR/processed.mark" 53 | done 54 | 55 | -------------------------------------------------------------------------------- /tools/gen_headers_source.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const assert = std.debug.assert; 3 | 4 | const Allocator = std.mem.Allocator; 5 | 6 | var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){}; 7 | const gpa = general_purpose_allocator.allocator(); 8 | 9 | const usage = 10 | \\gen_headers_source [dir] 11 | \\ 12 | \\General Options: 13 | \\-h, --help Print this help and exit 14 | ; 15 | 16 | fn info(comptime format: []const u8, args: anytype) void { 17 | const msg = std.fmt.allocPrint(gpa, "info: " ++ format ++ "\n", args) catch return; 18 | std.io.getStdOut().writeAll(msg) catch {}; 19 | } 20 | 21 | fn fatal(comptime format: []const u8, args: anytype) noreturn { 22 | ret: { 23 | const msg = std.fmt.allocPrint(gpa, "fatal: " ++ format ++ "\n", args) catch break :ret; 24 | std.io.getStdErr().writeAll(msg) catch {}; 25 | } 26 | std.process.exit(1); 27 | } 28 | 29 | pub fn main() anyerror!void { 30 | var arena_allocator = std.heap.ArenaAllocator.init(gpa); 31 | defer arena_allocator.deinit(); 32 | const arena = arena_allocator.allocator(); 33 | 34 | const args = try std.process.argsAlloc(arena); 35 | if (args.len == 1) fatal("no command or option specified", .{}); 36 | 37 | var positionals = std.ArrayList([]const u8).init(arena); 38 | 39 | for (args[1..]) |arg| { 40 | if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) { 41 | return info(usage, .{}); 42 | } else try positionals.append(arg); 43 | } 44 | 45 | if (positionals.items.len != 1) fatal("expected one positional argument: [dir]", .{}); 46 | 47 | var dir = try std.fs.cwd().openDir(positionals.items[0], .{ .no_follow = true }); 48 | defer dir.close(); 49 | var paths = std.ArrayList([]const u8).init(arena); 50 | try findHeaders(arena, dir, "", &paths); 51 | 52 | const SortFn = struct { 53 | pub fn lessThan(ctx: void, lhs: []const u8, rhs: []const u8) bool { 54 | _ = ctx; 55 | return std.mem.lessThan(u8, lhs, rhs); 56 | } 57 | }; 58 | 59 | std.mem.sort([]const u8, paths.items, {}, SortFn.lessThan); 60 | 61 | const stdout = std.io.getStdOut().writer(); 62 | try stdout.writeAll("#define _XOPEN_SOURCE\n"); 63 | for (paths.items) |path| { 64 | try stdout.print("#include <{s}>\n", .{path}); 65 | } 66 | try stdout.writeAll( 67 | \\int main(int argc, char **argv) { 68 | \\ return 0; 69 | \\} 70 | ); 71 | } 72 | 73 | fn findHeaders( 74 | arena: Allocator, 75 | dir: std.fs.Dir, 76 | prefix: []const u8, 77 | paths: *std.ArrayList([]const u8), 78 | ) anyerror!void { 79 | var it = dir.iterate(); 80 | while (try it.next()) |entry| { 81 | switch (entry.kind) { 82 | .directory => { 83 | const path = try std.fs.path.join(arena, &.{ prefix, entry.name }); 84 | var subdir = try dir.openDir(entry.name, .{ .no_follow = true }); 85 | defer subdir.close(); 86 | try findHeaders(arena, subdir, path, paths); 87 | }, 88 | .file, .sym_link => { 89 | const ext = std.fs.path.extension(entry.name); 90 | if (!std.mem.eql(u8, ext, ".h")) continue; 91 | const path = try std.fs.path.join(arena, &.{ prefix, entry.name }); 92 | try paths.append(path); 93 | }, 94 | else => {}, 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/headers.c: -------------------------------------------------------------------------------- 1 | // Source: https://en.wikipedia.org/wiki/C_standard_library#Header_files 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | // Source: https://en.wikipedia.org/wiki/C_standard_library#BSD_libc 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | // Source: https://en.wikipedia.org/wiki/C_POSIX_library 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | #include 59 | #include 60 | #include 61 | #include 62 | #include 63 | #include 64 | /* #include - not found on macos catalina */ 65 | #include 66 | #include 67 | #include 68 | #include 69 | #include 70 | #include 71 | #include 72 | #include 73 | #include 74 | #include 75 | #include 76 | #include 77 | #include 78 | #include 79 | #include 80 | #include 81 | #include 82 | #include 83 | #include 84 | #include 85 | #include 86 | #include 87 | #include 88 | #include 89 | #include 90 | #include 91 | /* #include - not found on macos catalina */ 92 | #include 93 | #include 94 | #include 95 | #include 96 | #include 97 | #include 98 | #include 99 | #include 100 | #include 101 | #include 102 | #include 103 | #include 104 | #include 105 | #include 106 | #include 107 | #include 108 | #include 109 | #include 110 | #include 111 | #include 112 | #include 113 | #include 114 | #include 115 | #include 116 | /* #include - not found on macos catalina */ 117 | #include 118 | #include 119 | #include 120 | #include 121 | #include 122 | #include 123 | #include 124 | 125 | // macOS system headers 126 | #include 127 | #include 128 | #include 129 | #include 130 | #include 131 | #include 132 | #include 133 | #include 134 | #include 135 | #include 136 | #include 137 | #include 138 | #include 139 | 140 | // Depended on by libcxx 141 | #include 142 | #include 143 | #include 144 | #include 145 | #include 146 | #include 147 | #include 148 | #include 149 | #include 150 | #include 151 | #include 152 | #include 153 | #include 154 | #include 155 | 156 | // Depended on by LLVM 157 | #include 158 | #include 159 | #include 160 | 161 | // Depended on by several frameworks 162 | #include 163 | #include 164 | #include 165 | #include 166 | #include 167 | #include 168 | #include 169 | #include 170 | #include 171 | #include 172 | #include 173 | #include 174 | #include 175 | #include 176 | #include 177 | #include 178 | #include 179 | #include 180 | #include 181 | 182 | #include 183 | #include 184 | #include 185 | #include 186 | #include 187 | 188 | // Depended on by libuv 189 | #include 190 | #include 191 | #include 192 | #include 193 | 194 | // Depended on by sqlite-amalgamation 195 | #include 196 | #include 197 | 198 | // Provided by macOS LibC 199 | #include 200 | #include 201 | 202 | #define _XOPEN_SOURCE 203 | #include 204 | 205 | int main(int argc, char **argv) { 206 | return 0; 207 | } 208 | -------------------------------------------------------------------------------- /COPYING.unpack_sdks: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2021 Rasmus Andersson 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | 204 | -------------------------------------------------------------------------------- /src/main.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const fs = std.fs; 3 | const io = std.io; 4 | const mem = std.mem; 5 | const process = std.process; 6 | const assert = std.debug.assert; 7 | const tmpDir = std.testing.tmpDir; 8 | 9 | const Allocator = mem.Allocator; 10 | const Blake3 = std.crypto.hash.Blake3; 11 | const OsTag = std.Target.Os.Tag; 12 | 13 | var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){}; 14 | const gpa = general_purpose_allocator.allocator(); 15 | 16 | const Arch = enum { 17 | any, 18 | aarch64, 19 | x86_64, 20 | }; 21 | 22 | const Abi = enum { any, none }; 23 | 24 | const OsVer = enum(u32) { 25 | any = 0, 26 | catalina = 10, 27 | big_sur = 11, 28 | monterey = 12, 29 | ventura = 13, 30 | sonoma = 14, 31 | sequoia = 15, 32 | }; 33 | 34 | const Target = struct { 35 | arch: Arch, 36 | os: OsTag = .macos, 37 | os_ver: OsVer, 38 | abi: Abi = .none, 39 | 40 | fn hash(a: Target) u32 { 41 | var hasher = std.hash.Wyhash.init(0); 42 | std.hash.autoHash(&hasher, a.arch); 43 | std.hash.autoHash(&hasher, a.os); 44 | std.hash.autoHash(&hasher, a.os_ver); 45 | std.hash.autoHash(&hasher, a.abi); 46 | return @as(u32, @truncate(hasher.final())); 47 | } 48 | 49 | fn eql(a: Target, b: Target) bool { 50 | return a.arch == b.arch and 51 | a.os == b.os and 52 | a.os_ver == b.os_ver and 53 | a.abi == b.abi; 54 | } 55 | 56 | fn name(self: Target, allocator: Allocator) ![]const u8 { 57 | return std.fmt.allocPrint(allocator, "{s}-{s}-{s}", .{ 58 | @tagName(self.arch), 59 | @tagName(self.os), 60 | @tagName(self.abi), 61 | }); 62 | } 63 | 64 | fn fullName(self: Target, allocator: Allocator) ![]const u8 { 65 | if (self.os_ver == .any) return self.name(allocator); 66 | return std.fmt.allocPrint(allocator, "{s}-{s}.{d}-{s}", .{ 67 | @tagName(self.arch), 68 | @tagName(self.os), 69 | @intFromEnum(self.os_ver), 70 | @tagName(self.abi), 71 | }); 72 | } 73 | }; 74 | 75 | const targets = [_]Target{ 76 | Target{ 77 | .arch = .any, 78 | .abi = .any, 79 | .os_ver = .any, 80 | }, 81 | Target{ 82 | .arch = .aarch64, 83 | .os_ver = .any, 84 | }, 85 | Target{ 86 | .arch = .x86_64, 87 | .os_ver = .any, 88 | }, 89 | Target{ 90 | .arch = .x86_64, 91 | .os_ver = .catalina, 92 | }, 93 | Target{ 94 | .arch = .x86_64, 95 | .os_ver = .big_sur, 96 | }, 97 | Target{ 98 | .arch = .x86_64, 99 | .os_ver = .monterey, 100 | }, 101 | Target{ 102 | .arch = .x86_64, 103 | .os_ver = .ventura, 104 | }, 105 | Target{ 106 | .arch = .x86_64, 107 | .os_ver = .sonoma, 108 | }, 109 | Target{ 110 | .arch = .x86_64, 111 | .os_ver = .sequoia, 112 | }, 113 | Target{ 114 | .arch = .aarch64, 115 | .os_ver = .big_sur, 116 | }, 117 | Target{ 118 | .arch = .aarch64, 119 | .os_ver = .monterey, 120 | }, 121 | Target{ 122 | .arch = .aarch64, 123 | .os_ver = .ventura, 124 | }, 125 | Target{ 126 | .arch = .aarch64, 127 | .os_ver = .sonoma, 128 | }, 129 | Target{ 130 | .arch = .aarch64, 131 | .os_ver = .sequoia, 132 | }, 133 | }; 134 | 135 | const headers_source_prefix: []const u8 = "headers"; 136 | 137 | const Contents = struct { 138 | bytes: []const u8, 139 | hit_count: usize, 140 | hash: []const u8, 141 | is_generic: bool, 142 | 143 | fn hitCountLessThan(context: void, lhs: *const Contents, rhs: *const Contents) bool { 144 | _ = context; 145 | return lhs.hit_count < rhs.hit_count; 146 | } 147 | }; 148 | 149 | const TargetToHashContext = struct { 150 | pub fn hash(self: @This(), target: Target) u32 { 151 | _ = self; 152 | return target.hash(); 153 | } 154 | pub fn eql(self: @This(), a: Target, b: Target, b_index: usize) bool { 155 | _ = self; 156 | _ = b_index; 157 | return a.eql(b); 158 | } 159 | }; 160 | const TargetToHash = std.ArrayHashMap(Target, []const u8, TargetToHashContext, true); 161 | 162 | const HashToContents = std.StringHashMap(Contents); 163 | const PathTable = std.StringHashMap(*TargetToHash); 164 | 165 | /// The don't-dedup-list contains file paths with known problematic headers 166 | /// which while contain the same contents between architectures, should not be 167 | /// deduped since they contain includes, etc. which are relative and thus cannot be separated 168 | /// into a shared include dir such as `any-macos-any`. 169 | const dont_dedup_list = &[_][]const u8{ 170 | "libkern/OSAtomic.h", 171 | "libkern/OSAtomicDeprecated.h", 172 | "libkern/OSSpinLockDeprecated.h", 173 | "libkern/OSAtomicQueue.h", 174 | }; 175 | 176 | fn generateDontDedupMap(arena: Allocator) !std.StringHashMap(void) { 177 | var map = std.StringHashMap(void).init(arena); 178 | try map.ensureTotalCapacity(dont_dedup_list.len); 179 | for (dont_dedup_list) |path| { 180 | map.putAssumeCapacityNoClobber(path, {}); 181 | } 182 | return map; 183 | } 184 | 185 | const usage = 186 | \\fetch_them_macos_headers fetch 187 | \\fetch_them_macos_headers dedup 188 | \\ 189 | \\Commands: 190 | \\ fetch Fetch libc headers into headers/-macos. dir 191 | \\ dedup Generate deduplicated dirs into a given path 192 | \\ 193 | \\General Options: 194 | \\-h, --help Print this help and exit 195 | ; 196 | 197 | pub fn main() anyerror!void { 198 | var arena = std.heap.ArenaAllocator.init(gpa); 199 | defer arena.deinit(); 200 | 201 | const all_args = try std.process.argsAlloc(arena.allocator()); 202 | const args = all_args[1..]; 203 | if (args.len == 0) fatal("no command or option specified", .{}); 204 | 205 | const cmd = args[0]; 206 | if (mem.eql(u8, cmd, "--help") or mem.eql(u8, cmd, "-h")) { 207 | return info(usage, .{}); 208 | } else if (mem.eql(u8, cmd, "dedup")) { 209 | return dedup(arena.allocator(), args[1..]); 210 | } else if (mem.eql(u8, cmd, "fetch")) { 211 | return fetch(arena.allocator(), args[1..]); 212 | } else fatal("unknown command or option: {s}", .{cmd}); 213 | } 214 | 215 | const ArgsIterator = struct { 216 | args: []const []const u8, 217 | i: usize = 0, 218 | 219 | fn next(it: *@This()) ?[]const u8 { 220 | if (it.i >= it.args.len) { 221 | return null; 222 | } 223 | defer it.i += 1; 224 | return it.args[it.i]; 225 | } 226 | 227 | fn nextOrFatal(it: *@This()) []const u8 { 228 | const arg = it.next() orelse fatal("expected parameter after '{s}'", .{it.args[it.i - 1]}); 229 | return arg; 230 | } 231 | }; 232 | 233 | fn info(comptime format: []const u8, args: anytype) void { 234 | const msg = std.fmt.allocPrint(gpa, "info: " ++ format ++ "\n", args) catch return; 235 | std.io.getStdOut().writeAll(msg) catch {}; 236 | } 237 | 238 | fn fatal(comptime format: []const u8, args: anytype) noreturn { 239 | ret: { 240 | const msg = std.fmt.allocPrint(gpa, "fatal: " ++ format ++ "\n", args) catch break :ret; 241 | std.io.getStdErr().writeAll(msg) catch {}; 242 | } 243 | std.process.exit(1); 244 | } 245 | 246 | const fetch_usage = 247 | \\fetch_them_macos_headers fetch 248 | \\ 249 | \\Options: 250 | \\ --sysroot Path to macOS SDK 251 | \\ 252 | \\General Options: 253 | \\-h, --help Print this help and exit 254 | ; 255 | 256 | fn fetch(arena: Allocator, args: []const []const u8) !void { 257 | var argv = std.ArrayList([]const u8).init(arena); 258 | var sysroot: ?[]const u8 = null; 259 | 260 | var args_iter = ArgsIterator{ .args = args }; 261 | while (args_iter.next()) |arg| { 262 | if (mem.eql(u8, arg, "--help") or mem.eql(u8, arg, "-h")) { 263 | return info(fetch_usage, .{}); 264 | } else if (mem.eql(u8, arg, "--sysroot")) { 265 | sysroot = args_iter.nextOrFatal(); 266 | } else try argv.append(arg); 267 | } 268 | 269 | const sysroot_path = sysroot orelse blk: { 270 | const target = try std.zig.system.resolveTargetQuery(.{}); 271 | break :blk std.zig.system.darwin.getSdk(arena, target) orelse 272 | fatal("no SDK found; you can provide one explicitly with '--sysroot' flag", .{}); 273 | }; 274 | 275 | var sdk_dir = try std.fs.cwd().openDir(sysroot_path, .{}); 276 | defer sdk_dir.close(); 277 | const sdk_info = try sdk_dir.readFileAlloc(arena, "SDKSettings.json", std.math.maxInt(u32)); 278 | 279 | const parsed_json = try std.json.parseFromSlice(struct { 280 | DefaultProperties: struct { MACOSX_DEPLOYMENT_TARGET: []const u8 }, 281 | }, arena, sdk_info, .{ .ignore_unknown_fields = true }); 282 | 283 | const version = Version.parse(parsed_json.value.DefaultProperties.MACOSX_DEPLOYMENT_TARGET) orelse 284 | fatal("don't know how to parse SDK version: {s}", .{ 285 | parsed_json.value.DefaultProperties.MACOSX_DEPLOYMENT_TARGET, 286 | }); 287 | const os_ver: OsVer = switch (version.major) { 288 | 10 => .catalina, 289 | 11 => .big_sur, 290 | 12 => .monterey, 291 | 13 => .ventura, 292 | 14 => .sonoma, 293 | 15 => .sequoia, 294 | else => unreachable, 295 | }; 296 | info("found SDK deployment target macOS {} aka '{s}'", .{ version, @tagName(os_ver) }); 297 | 298 | var tmp = tmpDir(.{}); 299 | defer tmp.cleanup(); 300 | 301 | for (&[_]Arch{ .aarch64, .x86_64 }) |arch| { 302 | const target: Target = .{ 303 | .arch = arch, 304 | .os_ver = os_ver, 305 | }; 306 | try fetchTarget(arena, argv.items, sysroot_path, target, version, tmp); 307 | } 308 | } 309 | 310 | fn fetchTarget( 311 | arena: Allocator, 312 | args: []const []const u8, 313 | sysroot: []const u8, 314 | target: Target, 315 | ver: Version, 316 | tmp: std.testing.TmpDir, 317 | ) !void { 318 | const tmp_filename = "headers"; 319 | const headers_list_filename = "headers.o.d"; 320 | const tmp_path = try tmp.dir.realpathAlloc(arena, "."); 321 | const tmp_file_path = try fs.path.join(arena, &[_][]const u8{ tmp_path, tmp_filename }); 322 | const headers_list_path = try fs.path.join(arena, &[_][]const u8{ tmp_path, headers_list_filename }); 323 | 324 | const macos_version = try std.fmt.allocPrint(arena, "-mmacosx-version-min={d}.{d}", .{ 325 | ver.major, 326 | ver.minor, 327 | }); 328 | 329 | var cc_argv = std.ArrayList([]const u8).init(arena); 330 | try cc_argv.appendSlice(&[_][]const u8{ 331 | "cc", 332 | "-arch", 333 | switch (target.arch) { 334 | .x86_64 => "x86_64", 335 | .aarch64 => "arm64", 336 | else => unreachable, 337 | }, 338 | macos_version, 339 | "-isysroot", 340 | sysroot, 341 | "-iwithsysroot", 342 | "/usr/include", 343 | "-o", 344 | tmp_file_path, 345 | "src/headers.c", 346 | "-MD", 347 | "-MV", 348 | "-MF", 349 | headers_list_path, 350 | }); 351 | try cc_argv.appendSlice(args); 352 | 353 | // TODO instead of calling `cc` as a child process here, 354 | // hook in directly to `zig cc` API. 355 | const res = try std.process.Child.run(.{ 356 | .allocator = arena, 357 | .argv = cc_argv.items, 358 | }); 359 | 360 | if (res.stderr.len != 0) { 361 | std.log.err("{s}", .{res.stderr}); 362 | } 363 | 364 | // Read in the contents of `upgrade.o.d` 365 | const headers_list_file = try tmp.dir.openFile(headers_list_filename, .{}); 366 | defer headers_list_file.close(); 367 | 368 | var headers_dir = fs.cwd().openDir(headers_source_prefix, .{}) catch |err| switch (err) { 369 | error.FileNotFound, 370 | error.NotDir, 371 | => fatal("path '{s}' not found or not a directory. Did you accidentally delete it?", .{ 372 | headers_source_prefix, 373 | }), 374 | else => return err, 375 | }; 376 | defer headers_dir.close(); 377 | 378 | const dest_path = try target.fullName(arena); 379 | try headers_dir.deleteTree(dest_path); 380 | 381 | var dest_dir = try headers_dir.makeOpenPath(dest_path, .{}); 382 | var dirs = std.StringHashMap(fs.Dir).init(arena); 383 | try dirs.putNoClobber(".", dest_dir); 384 | 385 | const headers_list_str = try headers_list_file.reader().readAllAlloc(arena, std.math.maxInt(usize)); 386 | const prefix = "/usr/include"; 387 | 388 | var it = mem.splitScalar(u8, headers_list_str, '\n'); 389 | while (it.next()) |line| { 390 | if (mem.lastIndexOf(u8, line, "clang") != null) continue; 391 | if (mem.lastIndexOf(u8, line, prefix[0..])) |idx| { 392 | const out_rel_path = line[idx + prefix.len + 1 ..]; 393 | const out_rel_path_stripped = mem.trim(u8, out_rel_path, " \\"); 394 | const dirname = fs.path.dirname(out_rel_path_stripped) orelse "."; 395 | const maybe_dir = try dirs.getOrPut(dirname); 396 | if (!maybe_dir.found_existing) { 397 | maybe_dir.value_ptr.* = try dest_dir.makeOpenPath(dirname, .{}); 398 | } 399 | const basename = fs.path.basename(out_rel_path_stripped); 400 | 401 | const line_stripped = mem.trim(u8, line, " \\"); 402 | const abs_dirname = fs.path.dirname(line_stripped).?; 403 | var orig_subdir = try fs.cwd().openDir(abs_dirname, .{}); 404 | defer orig_subdir.close(); 405 | 406 | try orig_subdir.copyFile(basename, maybe_dir.value_ptr.*, basename, .{}); 407 | } 408 | } 409 | 410 | var dir_it = dirs.iterator(); 411 | while (dir_it.next()) |entry| { 412 | entry.value_ptr.close(); 413 | } 414 | } 415 | 416 | const dedup_usage = 417 | \\fetch_them_macos_headers dedup [path] 418 | \\ 419 | \\General Options: 420 | \\-h, --help Print this help and exit 421 | ; 422 | 423 | /// Dedups libs headers assuming the following layered structure: 424 | /// layer 1: x86_64-macos.10 x86_64-macos.11 x86_64-macos.12 aarch64-macos.11 aarch64-macos.12 425 | /// layer 2: any-macos.10 any-macos.11 any-macos.12 426 | /// layer 3: any-macos 427 | /// 428 | /// The first layer consists of headers specific to a CPU architecture AND macOS version. The second 429 | /// layer consists of headers common to a macOS version across CPU architectures, and the final 430 | /// layer consists of headers common to all libc headers. 431 | fn dedup(arena: Allocator, args: []const []const u8) !void { 432 | var path: ?[]const u8 = null; 433 | var args_iter = ArgsIterator{ .args = args }; 434 | while (args_iter.next()) |arg| { 435 | if (mem.eql(u8, arg, "--help") or mem.eql(u8, arg, "-h")) { 436 | return info(dedup_usage, .{}); 437 | } else { 438 | if (path != null) fatal("too many arguments", .{}); 439 | path = arg; 440 | } 441 | } 442 | 443 | const dest_path = path orelse fatal("no destination path specified", .{}); 444 | var dest_dir = fs.cwd().makeOpenPath(dest_path, .{}) catch |err| switch (err) { 445 | error.NotDir => fatal("path '{s}' not a directory", .{dest_path}), 446 | else => return err, 447 | }; 448 | defer dest_dir.close(); 449 | 450 | var dont_dedup_map = try generateDontDedupMap(arena); 451 | var layer_2_targets = std.ArrayList(TargetWithPrefix).init(arena); 452 | 453 | for (&[_]OsVer{ .catalina, .big_sur, .monterey, .ventura, .sonoma, .sequoia }) |os_ver| { 454 | var layer_1_targets = std.ArrayList(TargetWithPrefix).init(arena); 455 | 456 | for (targets) |target| { 457 | if (target.os_ver != os_ver) continue; 458 | try layer_1_targets.append(.{ 459 | .prefix = headers_source_prefix, 460 | .target = target, 461 | }); 462 | } 463 | 464 | if (layer_1_targets.items.len < 2) { 465 | try layer_2_targets.appendSlice(layer_1_targets.items); 466 | continue; 467 | } 468 | 469 | const layer_2_target = try dedupDirs(arena, .{ 470 | .os_ver = os_ver, 471 | .dest_path = dest_path, 472 | .dest_dir = dest_dir, 473 | .targets = layer_1_targets.items, 474 | .dont_dedup_map = &dont_dedup_map, 475 | }); 476 | try layer_2_targets.append(layer_2_target); 477 | } 478 | 479 | const layer_3_target = try dedupDirs(arena, .{ 480 | .os_ver = .any, 481 | .dest_path = dest_path, 482 | .dest_dir = dest_dir, 483 | .targets = layer_2_targets.items, 484 | .dont_dedup_map = &dont_dedup_map, 485 | }); 486 | assert(layer_3_target.target.eql(targets[0])); 487 | } 488 | 489 | const TargetWithPrefix = struct { 490 | prefix: []const u8, 491 | target: Target, 492 | }; 493 | 494 | const DedupDirsArgs = struct { 495 | os_ver: OsVer, 496 | dest_path: []const u8, 497 | dest_dir: fs.Dir, 498 | targets: []const TargetWithPrefix, 499 | dont_dedup_map: *const std.StringHashMap(void), 500 | }; 501 | 502 | fn dedupDirs(arena: Allocator, args: DedupDirsArgs) !TargetWithPrefix { 503 | var tmp = tmpDir(.{ .iterate = true }); 504 | defer tmp.cleanup(); 505 | 506 | var path_table = PathTable.init(arena); 507 | var hash_to_contents = HashToContents.init(arena); 508 | 509 | var savings = FindResult{}; 510 | for (args.targets) |target| { 511 | const res = try findDuplicates(target.target, arena, target.prefix, &path_table, &hash_to_contents); 512 | savings.max_bytes_saved += res.max_bytes_saved; 513 | savings.total_bytes += res.total_bytes; 514 | } 515 | 516 | info("summary: {} could be reduced to {}", .{ 517 | std.fmt.fmtIntSizeBin(savings.total_bytes), 518 | std.fmt.fmtIntSizeBin(savings.total_bytes - savings.max_bytes_saved), 519 | }); 520 | 521 | const output_target = Target{ 522 | .arch = .any, 523 | .abi = .any, 524 | .os_ver = args.os_ver, 525 | }; 526 | const common_name = try output_target.fullName(arena); 527 | 528 | var missed_opportunity_bytes: usize = 0; 529 | // Iterate path_table. For each path, put all the hashes into a list. Sort by hit_count. 530 | // The hash with the highest hit_count gets to be the "generic" one. Everybody else 531 | // gets their header in a separate arch directory. 532 | var path_it = path_table.iterator(); 533 | while (path_it.next()) |path_kv| { 534 | if (!args.dont_dedup_map.contains(path_kv.key_ptr.*)) { 535 | var contents_list = std.ArrayList(*Contents).init(arena); 536 | { 537 | var hash_it = path_kv.value_ptr.*.iterator(); 538 | while (hash_it.next()) |hash_kv| { 539 | const contents = &hash_to_contents.getEntry(hash_kv.value_ptr.*).?.value_ptr.*; 540 | try contents_list.append(contents); 541 | } 542 | } 543 | std.mem.sort(*Contents, contents_list.items, {}, Contents.hitCountLessThan); 544 | const best_contents = contents_list.popOrNull().?; 545 | if (best_contents.hit_count > 1) { 546 | // Put it in `any-macos-none`. 547 | const full_path = try fs.path.join(arena, &[_][]const u8{ common_name, path_kv.key_ptr.* }); 548 | try tmp.dir.makePath(fs.path.dirname(full_path).?); 549 | try tmp.dir.writeFile(.{ .sub_path = full_path, .data = best_contents.bytes }); 550 | best_contents.is_generic = true; 551 | while (contents_list.popOrNull()) |contender| { 552 | if (contender.hit_count > 1) { 553 | const this_missed_bytes = contender.hit_count * contender.bytes.len; 554 | missed_opportunity_bytes += this_missed_bytes; 555 | info("Missed opportunity ({}): {s}", .{ 556 | std.fmt.fmtIntSizeBin(this_missed_bytes), 557 | path_kv.key_ptr.*, 558 | }); 559 | } else break; 560 | } 561 | } 562 | } 563 | var hash_it = path_kv.value_ptr.*.iterator(); 564 | while (hash_it.next()) |hash_kv| { 565 | const contents = &hash_to_contents.getEntry(hash_kv.value_ptr.*).?.value_ptr.*; 566 | if (contents.is_generic) continue; 567 | 568 | const target = hash_kv.key_ptr.*; 569 | const target_name = try target.fullName(arena); 570 | const full_path = try fs.path.join(arena, &[_][]const u8{ target_name, path_kv.key_ptr.* }); 571 | try tmp.dir.makePath(fs.path.dirname(full_path).?); 572 | try tmp.dir.writeFile(.{ .sub_path = full_path, .data = contents.bytes }); 573 | } 574 | } 575 | 576 | for (args.targets) |target| { 577 | const target_name = try target.target.fullName(arena); 578 | try args.dest_dir.deleteTree(target_name); 579 | } 580 | try args.dest_dir.deleteTree(common_name); 581 | 582 | var tmp_it = tmp.dir.iterate(); 583 | while (try tmp_it.next()) |entry| { 584 | switch (entry.kind) { 585 | .directory => { 586 | const sub_dir = try tmp.dir.openDir(entry.name, .{ .iterate = true }); 587 | const dest_sub_dir = try args.dest_dir.makeOpenPath(entry.name, .{}); 588 | try copyDirAll(sub_dir, dest_sub_dir); 589 | }, 590 | else => info("unexpected file format: not a directory: '{s}'", .{entry.name}), 591 | } 592 | } 593 | 594 | return TargetWithPrefix{ 595 | .prefix = args.dest_path, 596 | .target = output_target, 597 | }; 598 | } 599 | 600 | const FindResult = struct { 601 | max_bytes_saved: usize = 0, 602 | total_bytes: usize = 0, 603 | }; 604 | 605 | fn findDuplicates( 606 | target: Target, 607 | arena: Allocator, 608 | dest_path: []const u8, 609 | path_table: *PathTable, 610 | hash_to_contents: *HashToContents, 611 | ) !FindResult { 612 | var result = FindResult{}; 613 | 614 | const target_name = try target.fullName(arena); 615 | const target_include_dir = try fs.path.join(arena, &[_][]const u8{ dest_path, target_name }); 616 | var dir_stack = std.ArrayList([]const u8).init(arena); 617 | try dir_stack.append(target_include_dir); 618 | 619 | while (dir_stack.popOrNull()) |full_dir_name| { 620 | var dir = fs.cwd().openDir(full_dir_name, .{ .iterate = true }) catch |err| switch (err) { 621 | error.FileNotFound => break, 622 | error.AccessDenied => break, 623 | else => return err, 624 | }; 625 | defer dir.close(); 626 | 627 | var dir_it = dir.iterate(); 628 | 629 | while (try dir_it.next()) |entry| { 630 | const full_path = try fs.path.join(arena, &[_][]const u8{ full_dir_name, entry.name }); 631 | switch (entry.kind) { 632 | .directory => try dir_stack.append(full_path), 633 | .file => { 634 | const rel_path = try fs.path.relative(arena, target_include_dir, full_path); 635 | const max_size = 2 * 1024 * 1024 * 1024; 636 | const raw_bytes = try fs.cwd().readFileAlloc(arena, full_path, max_size); 637 | const trimmed = mem.trim(u8, raw_bytes, " \r\n\t"); 638 | result.total_bytes += raw_bytes.len; 639 | const hash = try arena.alloc(u8, 32); 640 | var hasher = Blake3.init(.{}); 641 | hasher.update(rel_path); 642 | hasher.update(trimmed); 643 | hasher.final(hash); 644 | const gop = try hash_to_contents.getOrPut(hash); 645 | if (gop.found_existing) { 646 | result.max_bytes_saved += raw_bytes.len; 647 | gop.value_ptr.hit_count += 1; 648 | info("duplicate: {s} {s} ({})", .{ 649 | target_name, 650 | rel_path, 651 | std.fmt.fmtIntSizeBin(raw_bytes.len), 652 | }); 653 | } else { 654 | gop.value_ptr.* = Contents{ 655 | .bytes = trimmed, 656 | .hit_count = 1, 657 | .hash = hash, 658 | .is_generic = false, 659 | }; 660 | } 661 | const path_gop = try path_table.getOrPut(rel_path); 662 | const target_to_hash = if (path_gop.found_existing) path_gop.value_ptr.* else blk: { 663 | const ptr = try arena.create(TargetToHash); 664 | ptr.* = TargetToHash.init(arena); 665 | path_gop.value_ptr.* = ptr; 666 | break :blk ptr; 667 | }; 668 | try target_to_hash.putNoClobber(target, hash); 669 | }, 670 | else => info("unexpected file: {s}", .{full_path}), 671 | } 672 | } 673 | } 674 | 675 | return result; 676 | } 677 | 678 | fn copyDirAll(source: fs.Dir, dest: fs.Dir) anyerror!void { 679 | var it = source.iterate(); 680 | while (try it.next()) |next| { 681 | switch (next.kind) { 682 | .directory => { 683 | var sub_dir = try dest.makeOpenPath(next.name, .{}); 684 | var sub_source = try source.openDir(next.name, .{ .iterate = true }); 685 | defer { 686 | sub_dir.close(); 687 | sub_source.close(); 688 | } 689 | try copyDirAll(sub_source, sub_dir); 690 | }, 691 | .file => { 692 | var source_file = try source.openFile(next.name, .{}); 693 | var dest_file = try dest.createFile(next.name, .{}); 694 | defer { 695 | source_file.close(); 696 | dest_file.close(); 697 | } 698 | const stat = try source_file.stat(); 699 | const ncopied = try source_file.copyRangeAll(0, dest_file, 0, stat.size); 700 | assert(ncopied == stat.size); 701 | }, 702 | else => |kind| info("unexpected file kind '{s}' will be ignored", .{@tagName(kind)}), 703 | } 704 | } 705 | } 706 | 707 | const Version = struct { 708 | major: u16, 709 | minor: u8, 710 | patch: u8, 711 | 712 | fn parse(raw: []const u8) ?Version { 713 | var parsed: [3]u16 = [_]u16{0} ** 3; 714 | var count: usize = 0; 715 | var it = std.mem.splitAny(u8, raw, "."); 716 | while (it.next()) |comp| { 717 | if (count >= 3) return null; 718 | parsed[count] = std.fmt.parseInt(u16, comp, 10) catch return null; 719 | count += 1; 720 | } 721 | if (count == 0) return null; 722 | const major = parsed[0]; 723 | const minor = std.math.cast(u8, parsed[1]) orelse return null; 724 | const patch = std.math.cast(u8, parsed[2]) orelse return null; 725 | return .{ .major = major, .minor = minor, .patch = patch }; 726 | } 727 | 728 | pub fn format( 729 | v: Version, 730 | comptime unused_fmt_string: []const u8, 731 | options: std.fmt.FormatOptions, 732 | writer: anytype, 733 | ) !void { 734 | _ = unused_fmt_string; 735 | _ = options; 736 | try writer.print("{d}.{d}.{d}", .{ v.major, v.minor, v.patch }); 737 | } 738 | }; 739 | --------------------------------------------------------------------------------