├── .builds ├── alpine.yml ├── debian.yml ├── freebsd.yml └── openbsd.yml ├── .clang-format ├── .github └── workflows │ └── build.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── arg.h ├── build.c ├── build.h ├── deps.c ├── deps.h ├── env.c ├── env.h ├── graph.c ├── graph.h ├── htab.c ├── htab.h ├── log.c ├── log.h ├── os-posix.c ├── os.h ├── parse.c ├── parse.h ├── samu.1 ├── samu.c ├── scan.c ├── scan.h ├── tool.c ├── tool.h ├── tree.c ├── tree.h ├── util.c └── util.h /.builds/alpine.yml: -------------------------------------------------------------------------------- 1 | image: alpine/edge 2 | sources: 3 | - https://git.sr.ht/~mcf/samurai 4 | tasks: 5 | - build: make -C samurai 6 | -------------------------------------------------------------------------------- /.builds/debian.yml: -------------------------------------------------------------------------------- 1 | image: debian/stable 2 | sources: 3 | - https://git.sr.ht/~mcf/samurai 4 | tasks: 5 | - build: make -C samurai 6 | -------------------------------------------------------------------------------- /.builds/freebsd.yml: -------------------------------------------------------------------------------- 1 | image: freebsd/latest 2 | sources: 3 | - https://git.sr.ht/~mcf/samurai 4 | tasks: 5 | - build: make -C samurai CC=cc 6 | -------------------------------------------------------------------------------- /.builds/openbsd.yml: -------------------------------------------------------------------------------- 1 | image: openbsd/latest 2 | sources: 3 | - https://git.sr.ht/~mcf/samurai 4 | tasks: 5 | - build: make -C samurai LDLIBS= 6 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | # clang-format isn't authoritative for the style decisions in this project, but 2 | # these rules mostly align with the suggested style, and clang-format can be 3 | # used fix any potential style-mistakes. 4 | AlignEscapedNewlines: DontAlign 5 | AlwaysBreakAfterDefinitionReturnType: All 6 | BreakBeforeBraces: Linux 7 | ColumnLimit: 0 8 | IndentWidth: 8 9 | SortIncludes: false 10 | TabWidth: 8 11 | UseTab: ForIndentation 12 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | strategy: 8 | matrix: 9 | os: [ubuntu-latest, macOS-latest] 10 | runs-on: ${{ matrix.os }} 11 | steps: 12 | - uses: actions/checkout@v1 13 | - name: make 14 | run: make 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | .*.sw* 3 | /samu 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2017-2021 Michael Forney 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any purpose 4 | with or without fee is hereby granted, provided that the above copyright notice 5 | and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 8 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 9 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 10 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS 11 | OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 12 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 13 | THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- 15 | Parts of samurai were written using ninja source code as a reference, which 16 | is licensed under the Apache License, Version 2.0. 17 | 18 | Copyright 2011-2015 Google Inc. All Rights Reserved. 19 | 20 | Apache License 21 | Version 2.0, January 2010 22 | http://www.apache.org/licenses/ 23 | 24 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 25 | 26 | 1. Definitions. 27 | 28 | "License" shall mean the terms and conditions for use, reproduction, 29 | and distribution as defined by Sections 1 through 9 of this document. 30 | 31 | "Licensor" shall mean the copyright owner or entity authorized by 32 | the copyright owner that is granting the License. 33 | 34 | "Legal Entity" shall mean the union of the acting entity and all 35 | other entities that control, are controlled by, or are under common 36 | control with that entity. For the purposes of this definition, 37 | "control" means (i) the power, direct or indirect, to cause the 38 | direction or management of such entity, whether by contract or 39 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 40 | outstanding shares, or (iii) beneficial ownership of such entity. 41 | 42 | "You" (or "Your") shall mean an individual or Legal Entity 43 | exercising permissions granted by this License. 44 | 45 | "Source" form shall mean the preferred form for making modifications, 46 | including but not limited to software source code, documentation 47 | source, and configuration files. 48 | 49 | "Object" form shall mean any form resulting from mechanical 50 | transformation or translation of a Source form, including but 51 | not limited to compiled object code, generated documentation, 52 | and conversions to other media types. 53 | 54 | "Work" shall mean the work of authorship, whether in Source or 55 | Object form, made available under the License, as indicated by a 56 | copyright notice that is included in or attached to the work 57 | (an example is provided in the Appendix below). 58 | 59 | "Derivative Works" shall mean any work, whether in Source or Object 60 | form, that is based on (or derived from) the Work and for which the 61 | editorial revisions, annotations, elaborations, or other modifications 62 | represent, as a whole, an original work of authorship. For the purposes 63 | of this License, Derivative Works shall not include works that remain 64 | separable from, or merely link (or bind by name) to the interfaces of, 65 | the Work and Derivative Works thereof. 66 | 67 | "Contribution" shall mean any work of authorship, including 68 | the original version of the Work and any modifications or additions 69 | to that Work or Derivative Works thereof, that is intentionally 70 | submitted to Licensor for inclusion in the Work by the copyright owner 71 | or by an individual or Legal Entity authorized to submit on behalf of 72 | the copyright owner. For the purposes of this definition, "submitted" 73 | means any form of electronic, verbal, or written communication sent 74 | to the Licensor or its representatives, including but not limited to 75 | communication on electronic mailing lists, source code control systems, 76 | and issue tracking systems that are managed by, or on behalf of, the 77 | Licensor for the purpose of discussing and improving the Work, but 78 | excluding communication that is conspicuously marked or otherwise 79 | designated in writing by the copyright owner as "Not a Contribution." 80 | 81 | "Contributor" shall mean Licensor and any individual or Legal Entity 82 | on behalf of whom a Contribution has been received by Licensor and 83 | subsequently incorporated within the Work. 84 | 85 | 2. Grant of Copyright License. Subject to the terms and conditions of 86 | this License, each Contributor hereby grants to You a perpetual, 87 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 88 | copyright license to reproduce, prepare Derivative Works of, 89 | publicly display, publicly perform, sublicense, and distribute the 90 | Work and such Derivative Works in Source or Object form. 91 | 92 | 3. Grant of Patent License. Subject to the terms and conditions of 93 | this License, each Contributor hereby grants to You a perpetual, 94 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 95 | (except as stated in this section) patent license to make, have made, 96 | use, offer to sell, sell, import, and otherwise transfer the Work, 97 | where such license applies only to those patent claims licensable 98 | by such Contributor that are necessarily infringed by their 99 | Contribution(s) alone or by combination of their Contribution(s) 100 | with the Work to which such Contribution(s) was submitted. If You 101 | institute patent litigation against any entity (including a 102 | cross-claim or counterclaim in a lawsuit) alleging that the Work 103 | or a Contribution incorporated within the Work constitutes direct 104 | or contributory patent infringement, then any patent licenses 105 | granted to You under this License for that Work shall terminate 106 | as of the date such litigation is filed. 107 | 108 | 4. Redistribution. You may reproduce and distribute copies of the 109 | Work or Derivative Works thereof in any medium, with or without 110 | modifications, and in Source or Object form, provided that You 111 | meet the following conditions: 112 | 113 | (a) You must give any other recipients of the Work or 114 | Derivative Works a copy of this License; and 115 | 116 | (b) You must cause any modified files to carry prominent notices 117 | stating that You changed the files; and 118 | 119 | (c) You must retain, in the Source form of any Derivative Works 120 | that You distribute, all copyright, patent, trademark, and 121 | attribution notices from the Source form of the Work, 122 | excluding those notices that do not pertain to any part of 123 | the Derivative Works; and 124 | 125 | (d) If the Work includes a "NOTICE" text file as part of its 126 | distribution, then any Derivative Works that You distribute must 127 | include a readable copy of the attribution notices contained 128 | within such NOTICE file, excluding those notices that do not 129 | pertain to any part of the Derivative Works, in at least one 130 | of the following places: within a NOTICE text file distributed 131 | as part of the Derivative Works; within the Source form or 132 | documentation, if provided along with the Derivative Works; or, 133 | within a display generated by the Derivative Works, if and 134 | wherever such third-party notices normally appear. The contents 135 | of the NOTICE file are for informational purposes only and 136 | do not modify the License. You may add Your own attribution 137 | notices within Derivative Works that You distribute, alongside 138 | or as an addendum to the NOTICE text from the Work, provided 139 | that such additional attribution notices cannot be construed 140 | as modifying the License. 141 | 142 | You may add Your own copyright statement to Your modifications and 143 | may provide additional or different license terms and conditions 144 | for use, reproduction, or distribution of Your modifications, or 145 | for any such Derivative Works as a whole, provided Your use, 146 | reproduction, and distribution of the Work otherwise complies with 147 | the conditions stated in this License. 148 | 149 | 5. Submission of Contributions. Unless You explicitly state otherwise, 150 | any Contribution intentionally submitted for inclusion in the Work 151 | by You to the Licensor shall be under the terms and conditions of 152 | this License, without any additional terms or conditions. 153 | Notwithstanding the above, nothing herein shall supersede or modify 154 | the terms of any separate license agreement you may have executed 155 | with Licensor regarding such Contributions. 156 | 157 | 6. Trademarks. This License does not grant permission to use the trade 158 | names, trademarks, service marks, or product names of the Licensor, 159 | except as required for reasonable and customary use in describing the 160 | origin of the Work and reproducing the content of the NOTICE file. 161 | 162 | 7. Disclaimer of Warranty. Unless required by applicable law or 163 | agreed to in writing, Licensor provides the Work (and each 164 | Contributor provides its Contributions) on an "AS IS" BASIS, 165 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 166 | implied, including, without limitation, any warranties or conditions 167 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 168 | PARTICULAR PURPOSE. You are solely responsible for determining the 169 | appropriateness of using or redistributing the Work and assume any 170 | risks associated with Your exercise of permissions under this License. 171 | 172 | 8. Limitation of Liability. In no event and under no legal theory, 173 | whether in tort (including negligence), contract, or otherwise, 174 | unless required by applicable law (such as deliberate and grossly 175 | negligent acts) or agreed to in writing, shall any Contributor be 176 | liable to You for damages, including any direct, indirect, special, 177 | incidental, or consequential damages of any character arising as a 178 | result of this License or out of the use or inability to use the 179 | Work (including but not limited to damages for loss of goodwill, 180 | work stoppage, computer failure or malfunction, or any and all 181 | other commercial damages or losses), even if such Contributor 182 | has been advised of the possibility of such damages. 183 | 184 | 9. Accepting Warranty or Additional Liability. While redistributing 185 | the Work or Derivative Works thereof, You may choose to offer, 186 | and charge a fee for, acceptance of support, warranty, indemnity, 187 | or other liability obligations and/or rights consistent with this 188 | License. However, in accepting such obligations, You may act only 189 | on Your own behalf and on Your sole responsibility, not on behalf 190 | of any other Contributor, and only if You agree to indemnify, 191 | defend, and hold each Contributor harmless for any liability 192 | incurred by, or claims asserted against, such Contributor by reason 193 | of your accepting any such warranty or additional liability. 194 | 195 | END OF TERMS AND CONDITIONS 196 | 197 | APPENDIX: How to apply the Apache License to your work. 198 | 199 | To apply the Apache License to your work, attach the following 200 | boilerplate notice, with the fields enclosed by brackets "[]" 201 | replaced with your own identifying information. (Don't include 202 | the brackets!) The text should be enclosed in the appropriate 203 | comment syntax for the file format. We also recommend that a 204 | file or class name and description of purpose be included on the 205 | same "printed page" as the copyright notice for easier 206 | identification within third-party archives. 207 | 208 | Copyright [yyyy] [name of copyright owner] 209 | 210 | Licensed under the Apache License, Version 2.0 (the "License"); 211 | you may not use this file except in compliance with the License. 212 | You may obtain a copy of the License at 213 | 214 | http://www.apache.org/licenses/LICENSE-2.0 215 | 216 | Unless required by applicable law or agreed to in writing, software 217 | distributed under the License is distributed on an "AS IS" BASIS, 218 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 219 | See the License for the specific language governing permissions and 220 | limitations under the License. 221 | -------------------------------------------------------------------------------- 222 | htab.c and htab.h were initially copied from util/htab.c and util/util.h from 223 | Myrddin, which is licensed under the MIT license. 224 | 225 | Copyright (c) 2013 Ori Bernstein 226 | 227 | Permission is hereby granted, free of charge, to any person obtaining a copy 228 | of this software and associated documentation files (the "Software"), to 229 | deal in the Software without restriction, including without limitation the 230 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 231 | sell copies of the Software, and to permit persons to whom the Software is 232 | furnished to do so, subject to the following conditions: 233 | 234 | The above copyright notice and this permission notice shall be included in 235 | all copies or substantial portions of the Software. 236 | 237 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 238 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 239 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 240 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 241 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 242 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 243 | IN THE SOFTWARE. 244 | -------------------------------------------------------------------------------- 245 | tree.c is based on tsearch_avl.c from musl 246 | 247 | Copyright © 2005-2014 Rich Felker, et al. 248 | 249 | Permission is hereby granted, free of charge, to any person obtaining 250 | a copy of this software and associated documentation files (the 251 | "Software"), to deal in the Software without restriction, including 252 | without limitation the rights to use, copy, modify, merge, publish, 253 | distribute, sublicense, and/or sell copies of the Software, and to 254 | permit persons to whom the Software is furnished to do so, subject to 255 | the following conditions: 256 | 257 | The above copyright notice and this permission notice shall be 258 | included in all copies or substantial portions of the Software. 259 | 260 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 261 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 262 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 263 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 264 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 265 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 266 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 267 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .POSIX: 2 | .PHONY: all install clean 3 | 4 | OS=posix 5 | PREFIX=/usr/local 6 | BINDIR=$(PREFIX)/bin 7 | MANDIR=$(PREFIX)/share/man 8 | ALL_CFLAGS=$(CFLAGS) -std=c99 -Wall -Wextra -Wshadow -Wmissing-prototypes -Wpedantic -Wno-unused-parameter 9 | LDLIBS=-lrt 10 | OBJ=\ 11 | build.o\ 12 | deps.o\ 13 | env.o\ 14 | graph.o\ 15 | htab.o\ 16 | log.o\ 17 | parse.o\ 18 | samu.o\ 19 | scan.o\ 20 | tool.o\ 21 | tree.o\ 22 | util.o\ 23 | os-$(OS).o 24 | HDR=\ 25 | arg.h\ 26 | build.h\ 27 | deps.h\ 28 | env.h\ 29 | graph.h\ 30 | htab.h\ 31 | log.h\ 32 | os.h\ 33 | parse.h\ 34 | scan.h\ 35 | tool.h\ 36 | tree.h\ 37 | util.h 38 | 39 | all: samu 40 | 41 | .c.o: 42 | $(CC) $(ALL_CFLAGS) -c -o $@ $< 43 | 44 | samu: $(OBJ) 45 | $(CC) $(LDFLAGS) -o $@ $(OBJ) $(LDLIBS) 46 | 47 | $(OBJ): $(HDR) 48 | 49 | install: samu samu.1 50 | mkdir -p $(DESTDIR)$(BINDIR) 51 | cp samu $(DESTDIR)$(BINDIR)/ 52 | mkdir -p $(DESTDIR)$(MANDIR)/man1 53 | cp samu.1 $(DESTDIR)$(MANDIR)/man1/ 54 | 55 | clean: 56 | rm -f samu $(OBJ) 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # samurai 2 | 3 | [![builds.sr.ht status](https://builds.sr.ht/~mcf/samurai.svg)](https://builds.sr.ht/~mcf/samurai) 4 | [![GitHub build status](https://github.com/michaelforney/samurai/workflows/build/badge.svg)](https://github.com/michaelforney/samurai/actions) 5 | 6 | samurai is a ninja-compatible build tool written in C99 with a focus on 7 | simplicity, speed, and portability. 8 | 9 | ## Status 10 | 11 | samurai implements the ninja build language through version 1.9.0 except 12 | for MSVC dependency handling (`deps = msvc`). It uses the same format 13 | for `.ninja_log` and `.ninja_deps` as ninja, currently version 5 and 4 14 | respectively. 15 | 16 | It is feature-complete and supports most of the same options as ninja. 17 | 18 | ## Requirements 19 | 20 | samurai requires various POSIX.1-2008 interfaces. 21 | 22 | Scheduling jobs based on load average requires the non-standard 23 | `getloadavg` function. This feature can be enabled by defining 24 | `HAVE_GETLOADAVG` in your `CFLAGS`, along with any other necessary 25 | definitions for your platform. 26 | 27 | ## Differences from ninja 28 | 29 | samurai tries to match ninja behavior as much as possible, but there 30 | are several cases where it is slightly different: 31 | 32 | - samurai uses the [variable lookup order] documented in the ninja manual, 33 | while ninja has a quirk ([ninja-build/ninja#1516]) that if the build 34 | edge has no variable bindings, the variable is looked up in file scope 35 | *before* the rule-level variables. 36 | - samurai schedules jobs using a stack, so the last scheduled job is 37 | the first to execute, while ninja schedules jobs based on the pointer 38 | value of the edge structure (they are stored in a `std::set`), 39 | so the first to execute depends on the address returned by `malloc`. 40 | This may result in build failures due to insufficiently specified 41 | dependencies in the project's build system. 42 | - samurai does not post-process the job output in any way, so if it 43 | includes escape sequences they will be preserved, while ninja strips 44 | escape sequences if standard output is not a terminal. Some build 45 | systems, like meson, force color output from gcc by default using 46 | `-fdiagnostics-color=always`, so if you plan to save the output to a 47 | log, you should pass `-Db_colorout=auto` to meson. 48 | - samurai follows the [POSIX Utility Syntax Guidelines], in particular 49 | guideline 9, so it requires that any command-line options precede 50 | the operands. It does not do GNU-style argument permutation. 51 | 52 | [ninja-build/ninja#1516]: https://github.com/ninja-build/ninja/issues/1516 53 | [variable lookup order]: https://ninja-build.org/manual.html#ref_scope 54 | [POSIX Utility Syntax Guidelines]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html#tag_12_02 55 | -------------------------------------------------------------------------------- /arg.h: -------------------------------------------------------------------------------- 1 | extern const char *argv0; 2 | 3 | #define ARGBEGIN \ 4 | for (;;) { \ 5 | if (argc > 0) \ 6 | ++argv, --argc; \ 7 | if (argc == 0 || (*argv)[0] != '-') \ 8 | break; \ 9 | if ((*argv)[1] == '-' && !(*argv)[2]) { \ 10 | ++argv, --argc; \ 11 | break; \ 12 | } \ 13 | for (char *opt_ = &(*argv)[1], done_ = 0; !done_ && *opt_; ++opt_) { \ 14 | switch (*opt_) 15 | 16 | #define ARGEND \ 17 | } \ 18 | } 19 | 20 | #define EARGF(x) \ 21 | (done_ = 1, *++opt_ ? opt_ : argv[1] ? --argc, *++argv : ((x), abort(), (char *)0)) 22 | -------------------------------------------------------------------------------- /build.c: -------------------------------------------------------------------------------- 1 | #define _POSIX_C_SOURCE 200809L 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 "build.h" 15 | #include "deps.h" 16 | #include "env.h" 17 | #include "graph.h" 18 | #include "log.h" 19 | #include "os.h" 20 | #include "util.h" 21 | 22 | struct job { 23 | struct string *cmd; 24 | struct edge *edge; 25 | struct buffer buf; 26 | size_t next; 27 | pid_t pid; 28 | int fd; 29 | bool failed; 30 | }; 31 | 32 | struct buildoptions buildopts = {.maxfail = 1}; 33 | static struct edge *work; 34 | static size_t nstarted, nfinished, ntotal; 35 | static bool consoleused; 36 | static struct timespec starttime; 37 | 38 | void 39 | buildreset(void) 40 | { 41 | struct edge *e; 42 | 43 | for (e = alledges; e; e = e->allnext) 44 | e->flags &= ~FLAG_WORK; 45 | } 46 | 47 | /* returns whether n1 is newer than n2, or false if n1 is NULL */ 48 | static bool 49 | isnewer(struct node *n1, struct node *n2) 50 | { 51 | return n1 && n1->mtime > n2->mtime; 52 | } 53 | 54 | /* returns whether this output node is dirty in relation to the newest input */ 55 | static bool 56 | isdirty(struct node *n, struct node *newest, bool generator, bool restat) 57 | { 58 | struct edge *e; 59 | 60 | e = n->gen; 61 | if (e->rule == &phonyrule) { 62 | if (e->nin > 0 || n->mtime != MTIME_MISSING) 63 | return false; 64 | if (buildopts.explain) 65 | warn("explain %s: phony and no inputs", n->path->s); 66 | return true; 67 | } 68 | if (n->mtime == MTIME_MISSING) { 69 | if (buildopts.explain) 70 | warn("explain %s: missing", n->path->s); 71 | return true; 72 | } 73 | if (isnewer(newest, n) && (!restat || n->logmtime == MTIME_MISSING)) { 74 | if (buildopts.explain) { 75 | warn("explain %s: older than input '%s': %" PRId64 " vs %" PRId64, 76 | n->path->s, newest->path->s, n->mtime, newest->mtime); 77 | } 78 | return true; 79 | } 80 | if (n->logmtime == MTIME_MISSING) { 81 | if (!generator) { 82 | if (buildopts.explain) 83 | warn("explain %s: no record in .ninja_log", n->path->s); 84 | return true; 85 | } 86 | } else if (newest && n->logmtime < newest->mtime) { 87 | if (buildopts.explain) { 88 | warn("explain %s: recorded mtime is older than input '%s': %" PRId64 " vs %" PRId64, 89 | n->path->s, newest->path->s, n->logmtime, newest->mtime); 90 | } 91 | return true; 92 | } 93 | if (generator) 94 | return false; 95 | edgehash(e); 96 | if (e->hash == n->hash) 97 | return false; 98 | if (buildopts.explain) 99 | warn("explain %s: command line changed", n->path->s); 100 | return true; 101 | } 102 | 103 | /* add an edge to the work queue */ 104 | static void 105 | queue(struct edge *e) 106 | { 107 | struct edge **front = &work; 108 | 109 | if (e->pool && e->rule != &phonyrule) { 110 | if (e->pool->numjobs == e->pool->maxjobs) 111 | front = &e->pool->work; 112 | else 113 | ++e->pool->numjobs; 114 | } 115 | e->worknext = *front; 116 | *front = e; 117 | } 118 | 119 | void 120 | buildadd(struct node *n) 121 | { 122 | struct edge *e; 123 | struct node *newest; 124 | size_t i; 125 | bool generator, restat; 126 | 127 | e = n->gen; 128 | if (!e) { 129 | if (n->mtime == MTIME_UNKNOWN) 130 | nodestat(n); 131 | if (n->mtime == MTIME_MISSING) 132 | fatal("file is missing and not created by any action: '%s'", n->path->s); 133 | n->dirty = false; 134 | return; 135 | } 136 | if (e->flags & FLAG_CYCLE) 137 | fatal("dependency cycle involving '%s'", n->path->s); 138 | if (e->flags & FLAG_WORK) 139 | return; 140 | e->flags |= FLAG_CYCLE | FLAG_WORK; 141 | for (i = 0; i < e->nout; ++i) { 142 | n = e->out[i]; 143 | n->dirty = false; 144 | if (n->mtime == MTIME_UNKNOWN) 145 | nodestat(n); 146 | } 147 | depsload(e); 148 | e->nblock = 0; 149 | newest = NULL; 150 | for (i = 0; i < e->nin; ++i) { 151 | n = e->in[i]; 152 | buildadd(n); 153 | if (i < e->inorderidx) { 154 | if (n->dirty) 155 | e->flags |= FLAG_DIRTY_IN; 156 | if (n->mtime != MTIME_MISSING && !isnewer(newest, n)) 157 | newest = n; 158 | } 159 | if (n->dirty || (n->gen && n->gen->nblock > 0)) 160 | ++e->nblock; 161 | } 162 | /* all outputs are dirty if any are older than the newest input */ 163 | generator = edgevar(e, "generator", true); 164 | restat = edgevar(e, "restat", true); 165 | for (i = 0; i < e->nout && !(e->flags & FLAG_DIRTY_OUT); ++i) { 166 | n = e->out[i]; 167 | if (isdirty(n, newest, generator, restat)) { 168 | n->dirty = true; 169 | e->flags |= FLAG_DIRTY_OUT; 170 | } 171 | } 172 | if (e->flags & FLAG_DIRTY) { 173 | for (i = 0; i < e->nout; ++i) { 174 | n = e->out[i]; 175 | if (buildopts.explain && !n->dirty) { 176 | if (e->flags & FLAG_DIRTY_IN) 177 | warn("explain %s: input is dirty", n->path->s); 178 | else if (e->flags & FLAG_DIRTY_OUT) 179 | warn("explain %s: output of generating action is dirty", n->path->s); 180 | } 181 | n->dirty = true; 182 | } 183 | } 184 | if (!(e->flags & FLAG_DIRTY_OUT)) 185 | e->nprune = e->nblock; 186 | if (e->flags & FLAG_DIRTY) { 187 | if (e->nblock == 0) 188 | queue(e); 189 | if (e->rule != &phonyrule) 190 | ++ntotal; 191 | } 192 | e->flags &= ~FLAG_CYCLE; 193 | } 194 | 195 | static size_t 196 | formatstatus(char *buf, size_t len) 197 | { 198 | const char *fmt; 199 | size_t ret = 0; 200 | int n; 201 | struct timespec endtime; 202 | 203 | for (fmt = buildopts.statusfmt; *fmt; ++fmt) { 204 | if (*fmt != '%' || *++fmt == '%') { 205 | if (len > 1) { 206 | *buf++ = *fmt; 207 | --len; 208 | } 209 | ++ret; 210 | continue; 211 | } 212 | n = 0; 213 | switch (*fmt) { 214 | case 's': 215 | n = snprintf(buf, len, "%zu", nstarted); 216 | break; 217 | case 'f': 218 | n = snprintf(buf, len, "%zu", nfinished); 219 | break; 220 | case 't': 221 | n = snprintf(buf, len, "%zu", ntotal); 222 | break; 223 | case 'r': 224 | n = snprintf(buf, len, "%zu", nstarted - nfinished); 225 | break; 226 | case 'u': 227 | n = snprintf(buf, len, "%zu", ntotal - nstarted); 228 | break; 229 | case 'p': 230 | n = snprintf(buf, len, "%3zu%%", 100 * nfinished / ntotal); 231 | break; 232 | case 'o': 233 | if (clock_gettime(CLOCK_MONOTONIC, &endtime) != 0) { 234 | warn("clock_gettime:"); 235 | break; 236 | } 237 | n = snprintf(buf, len, "%.1f", nfinished / ((endtime.tv_sec - starttime.tv_sec) + 0.000000001 * (endtime.tv_nsec - starttime.tv_nsec))); 238 | break; 239 | case 'e': 240 | if (clock_gettime(CLOCK_MONOTONIC, &endtime) != 0) { 241 | warn("clock_gettime:"); 242 | break; 243 | } 244 | n = snprintf(buf, len, "%.3f", (endtime.tv_sec - starttime.tv_sec) + 0.000000001 * (endtime.tv_nsec - starttime.tv_nsec)); 245 | break; 246 | default: 247 | fatal("unknown placeholder '%%%c' in $NINJA_STATUS", *fmt); 248 | continue; /* unreachable, but avoids warning */ 249 | } 250 | if (n < 0) 251 | fatal("snprintf:"); 252 | ret += n; 253 | if ((size_t)n > len) 254 | n = len; 255 | buf += n; 256 | len -= n; 257 | } 258 | if (len > 0) 259 | *buf = '\0'; 260 | return ret; 261 | } 262 | 263 | static void 264 | printstatus(struct edge *e, struct string *cmd) 265 | { 266 | struct string *description; 267 | char status[256]; 268 | 269 | description = buildopts.verbose ? NULL : edgevar(e, "description", true); 270 | if (!description || description->n == 0) 271 | description = cmd; 272 | formatstatus(status, sizeof(status)); 273 | fputs(status, stdout); 274 | puts(description->s); 275 | } 276 | 277 | static int 278 | jobstart(struct job *j, struct edge *e) 279 | { 280 | extern char **environ; 281 | size_t i; 282 | struct node *n; 283 | struct string *rspfile, *content; 284 | int fd[2]; 285 | posix_spawn_file_actions_t actions; 286 | char *argv[] = {"/bin/sh", "-c", NULL, NULL}; 287 | 288 | ++nstarted; 289 | for (i = 0; i < e->nout; ++i) { 290 | n = e->out[i]; 291 | if (n->mtime == MTIME_MISSING) { 292 | if (osmkdirs(n->path, true) < 0) 293 | goto err0; 294 | } 295 | } 296 | rspfile = edgevar(e, "rspfile", false); 297 | if (rspfile) { 298 | content = edgevar(e, "rspfile_content", true); 299 | if (writefile(rspfile->s, content) < 0) 300 | goto err0; 301 | } 302 | 303 | if (pipe(fd) < 0) { 304 | warn("pipe:"); 305 | goto err1; 306 | } 307 | j->edge = e; 308 | j->cmd = edgevar(e, "command", true); 309 | j->fd = fd[0]; 310 | argv[2] = j->cmd->s; 311 | 312 | if (!consoleused) 313 | printstatus(e, j->cmd); 314 | 315 | if ((errno = posix_spawn_file_actions_init(&actions))) { 316 | warn("posix_spawn_file_actions_init:"); 317 | goto err2; 318 | } 319 | if ((errno = posix_spawn_file_actions_addclose(&actions, fd[0]))) { 320 | warn("posix_spawn_file_actions_addclose:"); 321 | goto err3; 322 | } 323 | if (e->pool != &consolepool) { 324 | if ((errno = posix_spawn_file_actions_addopen(&actions, 0, "/dev/null", O_RDONLY, 0))) { 325 | warn("posix_spawn_file_actions_addopen:"); 326 | goto err3; 327 | } 328 | if ((errno = posix_spawn_file_actions_adddup2(&actions, fd[1], 1))) { 329 | warn("posix_spawn_file_actions_adddup2:"); 330 | goto err3; 331 | } 332 | if ((errno = posix_spawn_file_actions_adddup2(&actions, fd[1], 2))) { 333 | warn("posix_spawn_file_actions_adddup2:"); 334 | goto err3; 335 | } 336 | if ((errno = posix_spawn_file_actions_addclose(&actions, fd[1]))) { 337 | warn("posix_spawn_file_actions_addclose:"); 338 | goto err3; 339 | } 340 | } 341 | if ((errno = posix_spawn(&j->pid, argv[0], &actions, NULL, argv, environ))) { 342 | warn("posix_spawn %s:", j->cmd->s); 343 | goto err3; 344 | } 345 | posix_spawn_file_actions_destroy(&actions); 346 | close(fd[1]); 347 | j->failed = false; 348 | if (e->pool == &consolepool) 349 | consoleused = true; 350 | 351 | return j->fd; 352 | 353 | err3: 354 | posix_spawn_file_actions_destroy(&actions); 355 | err2: 356 | close(fd[0]); 357 | close(fd[1]); 358 | err1: 359 | if (rspfile && !buildopts.keeprsp) 360 | remove(rspfile->s); 361 | err0: 362 | return -1; 363 | } 364 | 365 | static void 366 | nodedone(struct node *n, bool prune) 367 | { 368 | struct edge *e; 369 | size_t i, j; 370 | 371 | for (i = 0; i < n->nuse; ++i) { 372 | e = n->use[i]; 373 | /* skip edges not used in this build */ 374 | if (!(e->flags & FLAG_WORK)) 375 | continue; 376 | if (!(e->flags & (prune ? FLAG_DIRTY_OUT : FLAG_DIRTY)) && --e->nprune == 0) { 377 | /* either edge was clean (possible with order-only 378 | * inputs), or all its blocking inputs were pruned, so 379 | * its outputs can be pruned as well */ 380 | for (j = 0; j < e->nout; ++j) 381 | nodedone(e->out[j], true); 382 | if (e->flags & FLAG_DIRTY && e->rule != &phonyrule) 383 | --ntotal; 384 | } else if (--e->nblock == 0) { 385 | queue(e); 386 | } 387 | } 388 | } 389 | 390 | static bool 391 | shouldprune(struct edge *e, struct node *n, int64_t old) 392 | { 393 | struct node *in, *newest; 394 | size_t i; 395 | 396 | if (old != n->mtime) 397 | return false; 398 | newest = NULL; 399 | for (i = 0; i < e->inorderidx; ++i) { 400 | in = e->in[i]; 401 | nodestat(in); 402 | if (in->mtime != MTIME_MISSING && !isnewer(newest, in)) 403 | newest = in; 404 | } 405 | if (newest) 406 | n->logmtime = newest->mtime; 407 | 408 | return true; 409 | } 410 | 411 | static void 412 | edgedone(struct edge *e) 413 | { 414 | struct node *n; 415 | size_t i; 416 | struct string *rspfile; 417 | bool restat; 418 | int64_t old; 419 | 420 | restat = edgevar(e, "restat", true); 421 | for (i = 0; i < e->nout; ++i) { 422 | n = e->out[i]; 423 | old = n->mtime; 424 | nodestat(n); 425 | n->logmtime = n->mtime == MTIME_MISSING ? 0 : n->mtime; 426 | nodedone(n, restat && shouldprune(e, n, old)); 427 | } 428 | rspfile = edgevar(e, "rspfile", false); 429 | if (rspfile && !buildopts.keeprsp) 430 | remove(rspfile->s); 431 | edgehash(e); 432 | depsrecord(e); 433 | for (i = 0; i < e->nout; ++i) { 434 | n = e->out[i]; 435 | n->hash = e->hash; 436 | logrecord(n); 437 | } 438 | } 439 | 440 | static void 441 | jobdone(struct job *j) 442 | { 443 | int status; 444 | struct edge *e, *new; 445 | struct pool *p; 446 | 447 | ++nfinished; 448 | if (waitpid(j->pid, &status, 0) < 0) { 449 | warn("waitpid %d:", j->pid); 450 | j->failed = true; 451 | } else if (WIFEXITED(status)) { 452 | if (WEXITSTATUS(status) != 0) { 453 | warn("job failed with status %d: %s", WEXITSTATUS(status), j->cmd->s); 454 | j->failed = true; 455 | } 456 | } else if (WIFSIGNALED(status)) { 457 | warn("job terminated due to signal %d: %s", WTERMSIG(status), j->cmd->s); 458 | j->failed = true; 459 | } else { 460 | /* cannot happen according to POSIX */ 461 | warn("job status unknown: %s", j->cmd->s); 462 | j->failed = true; 463 | } 464 | close(j->fd); 465 | if (j->buf.len && (!consoleused || j->failed)) 466 | fwrite(j->buf.data, 1, j->buf.len, stdout); 467 | j->buf.len = 0; 468 | e = j->edge; 469 | if (e->pool) { 470 | p = e->pool; 471 | 472 | if (p == &consolepool) 473 | consoleused = false; 474 | /* move edge from pool queue to main work queue */ 475 | if (p->work) { 476 | new = p->work; 477 | p->work = p->work->worknext; 478 | new->worknext = work; 479 | work = new; 480 | } else { 481 | --p->numjobs; 482 | } 483 | } 484 | if (!j->failed) 485 | edgedone(e); 486 | } 487 | 488 | /* returns whether a job still has work to do. if not, sets j->failed */ 489 | static bool 490 | jobwork(struct job *j) 491 | { 492 | char *newdata; 493 | size_t newcap; 494 | ssize_t n; 495 | 496 | if (j->buf.cap - j->buf.len < BUFSIZ / 2) { 497 | newcap = j->buf.cap + BUFSIZ; 498 | newdata = realloc(j->buf.data, newcap); 499 | if (!newdata) { 500 | warn("realloc:"); 501 | goto kill; 502 | } 503 | j->buf.cap = newcap; 504 | j->buf.data = newdata; 505 | } 506 | n = read(j->fd, j->buf.data + j->buf.len, j->buf.cap - j->buf.len); 507 | if (n > 0) { 508 | j->buf.len += n; 509 | return true; 510 | } 511 | if (n == 0) 512 | goto done; 513 | warn("read:"); 514 | 515 | kill: 516 | kill(j->pid, SIGTERM); 517 | j->failed = true; 518 | done: 519 | jobdone(j); 520 | 521 | return false; 522 | } 523 | 524 | /* queries the system load average */ 525 | static double 526 | queryload(void) 527 | { 528 | #ifdef HAVE_GETLOADAVG 529 | double load; 530 | 531 | if (getloadavg(&load, 1) == -1) { 532 | warn("getloadavg:"); 533 | load = 100.0; 534 | } 535 | 536 | return load; 537 | #else 538 | return 0; 539 | #endif 540 | } 541 | 542 | void 543 | build(void) 544 | { 545 | struct job *jobs = NULL; 546 | struct pollfd *fds = NULL; 547 | size_t i, next = 0, jobslen = 0, maxjobs = buildopts.maxjobs, numjobs = 0, numfail = 0; 548 | struct edge *e; 549 | 550 | if (ntotal == 0) { 551 | warn("nothing to do"); 552 | return; 553 | } 554 | 555 | clock_gettime(CLOCK_MONOTONIC, &starttime); 556 | formatstatus(NULL, 0); 557 | 558 | nstarted = 0; 559 | for (;;) { 560 | /* limit number of of jobs based on load */ 561 | if (buildopts.maxload) 562 | maxjobs = queryload() > buildopts.maxload ? 1 : buildopts.maxjobs; 563 | /* start ready edges */ 564 | while (work && numjobs < maxjobs && numfail < buildopts.maxfail) { 565 | e = work; 566 | work = work->worknext; 567 | if (e->rule != &phonyrule && buildopts.dryrun) { 568 | ++nstarted; 569 | printstatus(e, edgevar(e, "command", true)); 570 | ++nfinished; 571 | } 572 | if (e->rule == &phonyrule || buildopts.dryrun) { 573 | for (i = 0; i < e->nout; ++i) 574 | nodedone(e->out[i], false); 575 | continue; 576 | } 577 | if (next == jobslen) { 578 | jobslen = jobslen ? jobslen * 2 : 8; 579 | if (jobslen > buildopts.maxjobs) 580 | jobslen = buildopts.maxjobs; 581 | jobs = xreallocarray(jobs, jobslen, sizeof(jobs[0])); 582 | fds = xreallocarray(fds, jobslen, sizeof(fds[0])); 583 | for (i = next; i < jobslen; ++i) { 584 | jobs[i].buf.data = NULL; 585 | jobs[i].buf.len = 0; 586 | jobs[i].buf.cap = 0; 587 | jobs[i].next = i + 1; 588 | fds[i].fd = -1; 589 | fds[i].events = POLLIN; 590 | } 591 | } 592 | fds[next].fd = jobstart(&jobs[next], e); 593 | if (fds[next].fd < 0) { 594 | warn("job failed to start"); 595 | ++numfail; 596 | } else { 597 | next = jobs[next].next; 598 | ++numjobs; 599 | } 600 | } 601 | if (numjobs == 0) 602 | break; 603 | if (poll(fds, jobslen, 5000) < 0) 604 | fatal("poll:"); 605 | for (i = 0; i < jobslen; ++i) { 606 | if (!fds[i].revents || jobwork(&jobs[i])) 607 | continue; 608 | --numjobs; 609 | jobs[i].next = next; 610 | fds[i].fd = -1; 611 | next = i; 612 | if (jobs[i].failed) 613 | ++numfail; 614 | } 615 | } 616 | for (i = 0; i < jobslen; ++i) 617 | free(jobs[i].buf.data); 618 | free(jobs); 619 | free(fds); 620 | if (numfail > 0) { 621 | if (numfail < buildopts.maxfail) 622 | fatal("cannot make progress due to previous errors"); 623 | else if (numfail > 1) 624 | fatal("subcommands failed"); 625 | else 626 | fatal("subcommand failed"); 627 | } 628 | ntotal = 0; /* reset in case we just rebuilt the manifest */ 629 | } 630 | -------------------------------------------------------------------------------- /build.h: -------------------------------------------------------------------------------- 1 | struct node; 2 | 3 | struct buildoptions { 4 | size_t maxjobs, maxfail; 5 | _Bool verbose, explain, keepdepfile, keeprsp, dryrun; 6 | const char *statusfmt; 7 | double maxload; 8 | }; 9 | 10 | extern struct buildoptions buildopts; 11 | 12 | /* reset state, so a new build can be executed */ 13 | void buildreset(void); 14 | /* schedule a particular target to be built */ 15 | void buildadd(struct node *); 16 | /* execute rules to build the scheduled targets */ 17 | void build(void); 18 | -------------------------------------------------------------------------------- /deps.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "build.h" 9 | #include "deps.h" 10 | #include "env.h" 11 | #include "graph.h" 12 | #include "util.h" 13 | 14 | /* 15 | .ninja_deps file format 16 | 17 | The header identifying the format is the string "# ninjadeps\n", followed by a 18 | 4-byte integer specifying the format version. After this is a series of binary 19 | records. All integers in .ninja_deps are written in system byte-order. 20 | 21 | A record starts with a 4-byte integer indicating the record type and size. If 22 | the high bit is set, then it is a dependency record. Otherwise, it is a node 23 | record. In either case, the remaining 31 bits specify the size in bytes of the 24 | rest of the record. The size must be a multiple of 4, and no larger than than 25 | 2^19. 26 | 27 | Node records are given in incrementing ID order, and must be given before any 28 | dependency record that refers to it. The last 4-byte integer in the record is 29 | used as a checksum to prevent corruption. Counting from 0, the n-th node record 30 | (specifying the node with ID n) will have a checksum of ~n (bitwise negation of 31 | n). The remaining bytes of the record specify the path of the node, padded with 32 | NUL bytes to the next 4-byte boundary (start of the checksum value). 33 | 34 | A dependency record contains a list of dependencies for the edge that built a 35 | particular node. The first 4-byte integer is the node ID. The second and third 36 | 4-byte integers are the low and high 32-bits of the UNIX mtime (in nanoseconds) 37 | of the node when it was built. Following this is a sequence of 4-byte integers 38 | specifying the IDs of the dependency nodes for this edge, which will have been 39 | specified previously in node records. 40 | */ 41 | 42 | /* maximum record size (in bytes) */ 43 | #define MAX_RECORD_SIZE (1 << 19) 44 | 45 | struct nodearray { 46 | struct node **node; 47 | size_t len; 48 | }; 49 | 50 | struct entry { 51 | struct node *node; 52 | struct nodearray deps; 53 | int64_t mtime; 54 | }; 55 | 56 | static const char depsname[] = ".ninja_deps"; 57 | static const char depstmpname[] = ".ninja_deps.tmp"; 58 | static const char depsheader[] = "# ninjadeps\n"; 59 | static const uint32_t depsver = 4; 60 | static FILE *depsfile; 61 | static struct entry *entries; 62 | static size_t entrieslen, entriescap; 63 | 64 | static void 65 | depswrite(const void *p, size_t n, size_t m) 66 | { 67 | if (fwrite(p, n, m, depsfile) != m) 68 | fatal("deps log write:"); 69 | } 70 | 71 | static bool 72 | recordid(struct node *n) 73 | { 74 | uint32_t sz, chk; 75 | 76 | if (n->id != -1) 77 | return false; 78 | if (entrieslen == INT32_MAX) 79 | fatal("too many nodes"); 80 | n->id = entrieslen++; 81 | sz = (n->path->n + 7) & ~3; 82 | if (sz + 4 >= MAX_RECORD_SIZE) 83 | fatal("ID record too large"); 84 | depswrite(&sz, 4, 1); 85 | depswrite(n->path->s, 1, n->path->n); 86 | depswrite((char[4]){0}, 1, sz - n->path->n - 4); 87 | chk = ~n->id; 88 | depswrite(&chk, 4, 1); 89 | 90 | return true; 91 | } 92 | 93 | static void 94 | recorddeps(struct node *out, struct nodearray *deps, int64_t mtime) 95 | { 96 | uint32_t sz, m; 97 | size_t i; 98 | 99 | sz = 12 + deps->len * 4; 100 | if (sz + 4 >= MAX_RECORD_SIZE) 101 | fatal("deps record too large"); 102 | sz |= 0x80000000; 103 | depswrite(&sz, 4, 1); 104 | depswrite(&out->id, 4, 1); 105 | m = mtime & 0xffffffff; 106 | depswrite(&m, 4, 1); 107 | m = (mtime >> 32) & 0xffffffff; 108 | depswrite(&m, 4, 1); 109 | for (i = 0; i < deps->len; ++i) 110 | depswrite(&deps->node[i]->id, 4, 1); 111 | } 112 | 113 | void 114 | depsinit(const char *builddir) 115 | { 116 | char *depspath = (char *)depsname, *depstmppath = (char *)depstmpname; 117 | uint32_t *buf, cap, ver, sz, id; 118 | size_t len, i, j, nrecord; 119 | bool isdep; 120 | struct string *path; 121 | struct node *n; 122 | struct edge *e; 123 | struct entry *entry, *oldentries; 124 | 125 | /* XXX: when ninja hits a bad record, it truncates the log to the last 126 | * good record. perhaps we should do the same. */ 127 | 128 | if (depsfile) 129 | fclose(depsfile); 130 | entrieslen = 0; 131 | cap = BUFSIZ; 132 | buf = xmalloc(cap); 133 | if (builddir) 134 | xasprintf(&depspath, "%s/%s", builddir, depsname); 135 | depsfile = fopen(depspath, "r+"); 136 | if (!depsfile) { 137 | if (errno != ENOENT) 138 | fatal("open %s:", depspath); 139 | goto rewrite; 140 | } 141 | if (!fgets((char *)buf, sizeof(depsheader), depsfile)) 142 | goto rewrite; 143 | if (strcmp((char *)buf, depsheader) != 0) { 144 | warn("invalid deps log header"); 145 | goto rewrite; 146 | } 147 | if (fread(&ver, sizeof(ver), 1, depsfile) != 1) { 148 | warn(ferror(depsfile) ? "deps log read:" : "deps log truncated"); 149 | goto rewrite; 150 | } 151 | if (ver != depsver) { 152 | warn("unknown deps log version"); 153 | goto rewrite; 154 | } 155 | for (nrecord = 0;; ++nrecord) { 156 | if (fread(&sz, sizeof(sz), 1, depsfile) != 1) 157 | break; 158 | isdep = sz & 0x80000000; 159 | sz &= 0x7fffffff; 160 | if (sz > MAX_RECORD_SIZE) { 161 | warn("deps record too large"); 162 | goto rewrite; 163 | } 164 | if (sz > cap) { 165 | do cap *= 2; 166 | while (sz > cap); 167 | free(buf); 168 | buf = xmalloc(cap); 169 | } 170 | if (fread(buf, sz, 1, depsfile) != 1) { 171 | warn(ferror(depsfile) ? "deps log read:" : "deps log truncated"); 172 | goto rewrite; 173 | } 174 | if (sz % 4) { 175 | warn("invalid size, must be multiple of 4: %" PRIu32, sz); 176 | goto rewrite; 177 | } 178 | if (isdep) { 179 | if (sz < 12) { 180 | warn("invalid size, must be at least 12: %" PRIu32, sz); 181 | goto rewrite; 182 | } 183 | sz -= 12; 184 | id = buf[0]; 185 | if (id >= entrieslen) { 186 | warn("invalid node ID: %" PRIu32, id); 187 | goto rewrite; 188 | } 189 | entry = &entries[id]; 190 | entry->mtime = (int64_t)buf[2] << 32 | buf[1]; 191 | e = entry->node->gen; 192 | if (!e || !edgevar(e, "deps", true)) 193 | continue; 194 | sz /= 4; 195 | free(entry->deps.node); 196 | entry->deps.len = sz; 197 | entry->deps.node = xreallocarray(NULL, sz, sizeof(n)); 198 | for (i = 0; i < sz; ++i) { 199 | id = buf[3 + i]; 200 | if (id >= entrieslen) { 201 | warn("invalid node ID: %" PRIu32, id); 202 | goto rewrite; 203 | } 204 | entry->deps.node[i] = entries[id].node; 205 | } 206 | } else { 207 | if (sz <= 4) { 208 | warn("invalid size, must be greater than 4: %" PRIu32, sz); 209 | goto rewrite; 210 | } 211 | if (entrieslen != ~buf[sz / 4 - 1]) { 212 | warn("corrupt deps log, bad checksum"); 213 | goto rewrite; 214 | } 215 | if (entrieslen == INT32_MAX) { 216 | warn("too many nodes in deps log"); 217 | goto rewrite; 218 | } 219 | len = sz - 4; 220 | while (((char *)buf)[len - 1] == '\0') 221 | --len; 222 | path = mkstr(len); 223 | memcpy(path->s, buf, len); 224 | path->s[len] = '\0'; 225 | 226 | n = mknode(path); 227 | if (entrieslen >= entriescap) { 228 | entriescap = entriescap ? entriescap * 2 : 1024; 229 | entries = xreallocarray(entries, entriescap, sizeof(entries[0])); 230 | } 231 | n->id = entrieslen; 232 | entries[entrieslen++] = (struct entry){.node = n}; 233 | } 234 | } 235 | if (ferror(depsfile)) { 236 | warn("deps log read:"); 237 | goto rewrite; 238 | } 239 | if (nrecord <= 1000 || nrecord < 3 * entrieslen) { 240 | if (builddir) 241 | free(depspath); 242 | free(buf); 243 | return; 244 | } 245 | 246 | rewrite: 247 | free(buf); 248 | if (depsfile) 249 | fclose(depsfile); 250 | if (builddir) 251 | xasprintf(&depstmppath, "%s/%s", builddir, depstmpname); 252 | depsfile = fopen(depstmppath, "w"); 253 | if (!depsfile) 254 | fatal("open %s:", depstmppath); 255 | depswrite(depsheader, 1, sizeof(depsheader) - 1); 256 | depswrite(&depsver, 1, sizeof(depsver)); 257 | 258 | /* reset ID for all current entries */ 259 | for (i = 0; i < entrieslen; ++i) 260 | entries[i].node->id = -1; 261 | /* save a temporary copy of the old entries */ 262 | oldentries = xreallocarray(NULL, entrieslen, sizeof(entries[0])); 263 | memcpy(oldentries, entries, entrieslen * sizeof(entries[0])); 264 | 265 | len = entrieslen; 266 | entrieslen = 0; 267 | for (i = 0; i < len; ++i) { 268 | entry = &oldentries[i]; 269 | if (!entry->deps.len) 270 | continue; 271 | recordid(entry->node); 272 | entries[entry->node->id] = *entry; 273 | for (j = 0; j < entry->deps.len; ++j) 274 | recordid(entry->deps.node[j]); 275 | recorddeps(entry->node, &entry->deps, entry->mtime); 276 | } 277 | free(oldentries); 278 | fflush(depsfile); 279 | if (ferror(depsfile)) 280 | fatal("deps log write failed"); 281 | if (rename(depstmppath, depspath) < 0) 282 | fatal("deps log rename:"); 283 | if (builddir) { 284 | free(depstmppath); 285 | free(depspath); 286 | } 287 | } 288 | 289 | void 290 | depsclose(void) 291 | { 292 | fflush(depsfile); 293 | if (ferror(depsfile)) 294 | fatal("deps log write failed"); 295 | fclose(depsfile); 296 | } 297 | 298 | static struct nodearray * 299 | depsparse(const char *name, bool allowmissing) 300 | { 301 | static struct buffer buf; 302 | static struct nodearray deps; 303 | static size_t depscap; 304 | struct string *in, *out = NULL; 305 | FILE *f; 306 | int c, n; 307 | bool sawcolon; 308 | 309 | deps.len = 0; 310 | f = fopen(name, "r"); 311 | if (!f) { 312 | if (errno == ENOENT && allowmissing) 313 | return &deps; 314 | return NULL; 315 | } 316 | sawcolon = false; 317 | buf.len = 0; 318 | c = getc(f); 319 | for (;;) { 320 | /* TODO: this parser needs to be rewritten to be made simpler */ 321 | while (isalnum(c) || strchr("$+,-./@\\_", c)) { 322 | switch (c) { 323 | case '\\': 324 | /* handle the crazy escaping generated by clang and gcc */ 325 | n = 0; 326 | do { 327 | c = getc(f); 328 | if (++n % 2 == 0) 329 | bufadd(&buf, '\\'); 330 | } while (c == '\\'); 331 | if ((c == ' ' || c == '\t') && n % 2 != 0) 332 | break; 333 | for (; n > 2; n -= 2) 334 | bufadd(&buf, '\\'); 335 | switch (c) { 336 | case '#': break; 337 | case '\n': c = ' '; continue; 338 | default: bufadd(&buf, '\\'); continue; 339 | } 340 | break; 341 | case '$': 342 | c = getc(f); 343 | if (c != '$') { 344 | warn("bad depfile '%s': contains variable reference", name); 345 | goto err; 346 | } 347 | break; 348 | } 349 | bufadd(&buf, c); 350 | c = getc(f); 351 | } 352 | if (sawcolon) { 353 | if (!isspace(c) && c != EOF) { 354 | warn("bad depfile '%s': '%c' is not a valid target character", name, c); 355 | goto err; 356 | } 357 | if (buf.len > 0) { 358 | if (deps.len == depscap) { 359 | depscap = deps.node ? depscap * 2 : 32; 360 | deps.node = xreallocarray(deps.node, depscap, sizeof(deps.node[0])); 361 | } 362 | in = mkstr(buf.len); 363 | memcpy(in->s, buf.data, buf.len); 364 | in->s[buf.len] = '\0'; 365 | deps.node[deps.len++] = mknode(in); 366 | } 367 | if (c == '\n') { 368 | sawcolon = false; 369 | do c = getc(f); 370 | while (c == '\n'); 371 | } 372 | if (c == EOF) 373 | break; 374 | } else { 375 | while (isblank(c)) 376 | c = getc(f); 377 | if (c == EOF) 378 | break; 379 | if (c != ':') { 380 | warn("bad depfile '%s': expected ':', saw '%c'", name, c); 381 | goto err; 382 | } 383 | if (!out) { 384 | out = mkstr(buf.len); 385 | memcpy(out->s, buf.data, buf.len); 386 | out->s[buf.len] = '\0'; 387 | } else if (out->n != buf.len || memcmp(buf.data, out->s, buf.len) != 0) { 388 | warn("bad depfile '%s': multiple outputs: %.*s != %s", name, (int)buf.len, buf.data, out->s); 389 | goto err; 390 | } 391 | sawcolon = true; 392 | c = getc(f); 393 | } 394 | buf.len = 0; 395 | for (;;) { 396 | if (c == '\\') { 397 | if (getc(f) != '\n') { 398 | warn("bad depfile '%s': '\\' only allowed before newline", name); 399 | goto err; 400 | } 401 | } else if (!isblank(c)) { 402 | break; 403 | } 404 | c = getc(f); 405 | } 406 | } 407 | if (ferror(f)) { 408 | warn("depfile read '%s':", name); 409 | goto err; 410 | } 411 | fclose(f); 412 | return &deps; 413 | 414 | err: 415 | fclose(f); 416 | return NULL; 417 | } 418 | 419 | void 420 | depsload(struct edge *e) 421 | { 422 | struct string *deptype, *depfile; 423 | struct nodearray *deps = NULL; 424 | struct node *n; 425 | 426 | if (e->flags & FLAG_DEPS) 427 | return; 428 | e->flags |= FLAG_DEPS; 429 | n = e->out[0]; 430 | deptype = edgevar(e, "deps", true); 431 | if (deptype) { 432 | if (n->id != -1 && n->mtime <= entries[n->id].mtime) 433 | deps = &entries[n->id].deps; 434 | else if (buildopts.explain) 435 | warn("explain %s: missing or outdated record in .ninja_deps", n->path->s); 436 | } else { 437 | depfile = edgevar(e, "depfile", false); 438 | if (!depfile) 439 | return; 440 | deps = depsparse(depfile->s, false); 441 | if (buildopts.explain && !deps) 442 | warn("explain %s: missing or invalid depfile", n->path->s); 443 | } 444 | if (deps) { 445 | edgeadddeps(e, deps->node, deps->len); 446 | } else { 447 | n->dirty = true; 448 | e->flags |= FLAG_DIRTY_OUT; 449 | } 450 | } 451 | 452 | void 453 | depsrecord(struct edge *e) 454 | { 455 | struct string *deptype, *depfile; 456 | struct nodearray *deps; 457 | struct node *out, *n; 458 | struct entry *entry; 459 | size_t i; 460 | bool update; 461 | 462 | deptype = edgevar(e, "deps", true); 463 | if (!deptype || deptype->n == 0) 464 | return; 465 | if (strcmp(deptype->s, "gcc") != 0) { 466 | warn("unsuported deps type: %s", deptype->s); 467 | return; 468 | } 469 | depfile = edgevar(e, "depfile", false); 470 | if (!depfile || depfile->n == 0) { 471 | warn("deps but no depfile"); 472 | return; 473 | } 474 | out = e->out[0]; 475 | deps = depsparse(depfile->s, true); 476 | if (!buildopts.keepdepfile) 477 | remove(depfile->s); 478 | if (!deps) 479 | return; 480 | update = false; 481 | entry = NULL; 482 | if (recordid(out)) { 483 | update = true; 484 | } else { 485 | entry = &entries[out->id]; 486 | if (entry->mtime != out->mtime || entry->deps.len != deps->len) 487 | update = true; 488 | for (i = 0; i < deps->len && !update; ++i) { 489 | if (entry->deps.node[i] != deps->node[i]) 490 | update = true; 491 | } 492 | } 493 | for (i = 0; i < deps->len; ++i) { 494 | n = deps->node[i]; 495 | if (recordid(n)) 496 | update = true; 497 | } 498 | if (update) { 499 | recorddeps(out, deps, out->mtime); 500 | if (fflush(depsfile) < 0) 501 | fatal("deps log flush:"); 502 | } 503 | } 504 | -------------------------------------------------------------------------------- /deps.h: -------------------------------------------------------------------------------- 1 | struct edge; 2 | 3 | void depsinit(const char *); 4 | void depsclose(void); 5 | void depsload(struct edge *); 6 | void depsrecord(struct edge *); 7 | -------------------------------------------------------------------------------- /env.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "env.h" 5 | #include "graph.h" 6 | #include "tree.h" 7 | #include "util.h" 8 | 9 | struct environment { 10 | struct environment *parent; 11 | struct treenode *bindings; 12 | struct treenode *rules; 13 | struct environment *allnext; 14 | }; 15 | 16 | struct environment *rootenv; 17 | struct rule phonyrule = {.name = "phony"}; 18 | struct pool consolepool = {.name = "console", .maxjobs = 1}; 19 | static struct treenode *pools; 20 | static struct environment *allenvs; 21 | 22 | static void addpool(struct pool *); 23 | static void delpool(void *); 24 | static void delrule(void *); 25 | 26 | void 27 | envinit(void) 28 | { 29 | struct environment *env; 30 | 31 | /* free old environments and pools in case we rebuilt the manifest */ 32 | while (allenvs) { 33 | env = allenvs; 34 | allenvs = env->allnext; 35 | deltree(env->bindings, free, free); 36 | deltree(env->rules, NULL, delrule); 37 | free(env); 38 | } 39 | deltree(pools, NULL, delpool); 40 | 41 | rootenv = mkenv(NULL); 42 | envaddrule(rootenv, &phonyrule); 43 | pools = NULL; 44 | addpool(&consolepool); 45 | } 46 | 47 | static void 48 | addvar(struct treenode **tree, char *var, void *val) 49 | { 50 | char *old; 51 | 52 | old = treeinsert(tree, var, val); 53 | if (old) 54 | free(old); 55 | } 56 | 57 | struct environment * 58 | mkenv(struct environment *parent) 59 | { 60 | struct environment *env; 61 | 62 | env = xmalloc(sizeof(*env)); 63 | env->parent = parent; 64 | env->bindings = NULL; 65 | env->rules = NULL; 66 | env->allnext = allenvs; 67 | allenvs = env; 68 | 69 | return env; 70 | } 71 | 72 | struct string * 73 | envvar(struct environment *env, char *var) 74 | { 75 | struct treenode *n; 76 | 77 | do { 78 | n = treefind(env->bindings, var); 79 | if (n) 80 | return n->value; 81 | env = env->parent; 82 | } while (env); 83 | 84 | return NULL; 85 | } 86 | 87 | void 88 | envaddvar(struct environment *env, char *var, struct string *val) 89 | { 90 | addvar(&env->bindings, var, val); 91 | } 92 | 93 | static struct string * 94 | merge(struct evalstring *str, size_t n) 95 | { 96 | struct string *result; 97 | struct evalstring *p; 98 | char *s; 99 | 100 | result = mkstr(n); 101 | s = result->s; 102 | for (p = str; p; p = p->next) { 103 | if (!p->str) 104 | continue; 105 | memcpy(s, p->str->s, p->str->n); 106 | s += p->str->n; 107 | } 108 | *s = '\0'; 109 | 110 | return result; 111 | } 112 | 113 | struct string * 114 | enveval(struct environment *env, struct evalstring *str) 115 | { 116 | size_t n; 117 | struct evalstring *p; 118 | struct string *res; 119 | 120 | n = 0; 121 | for (p = str; p; p = p->next) { 122 | if (p->var) 123 | p->str = envvar(env, p->var); 124 | if (p->str) 125 | n += p->str->n; 126 | } 127 | res = merge(str, n); 128 | delevalstr(str); 129 | 130 | return res; 131 | } 132 | 133 | void 134 | envaddrule(struct environment *env, struct rule *r) 135 | { 136 | if (treeinsert(&env->rules, r->name, r)) 137 | fatal("rule '%s' redefined", r->name); 138 | } 139 | 140 | struct rule * 141 | envrule(struct environment *env, char *name) 142 | { 143 | struct treenode *n; 144 | 145 | do { 146 | n = treefind(env->rules, name); 147 | if (n) 148 | return n->value; 149 | env = env->parent; 150 | } while (env); 151 | 152 | return NULL; 153 | } 154 | 155 | static struct string * 156 | pathlist(struct node **nodes, size_t n, char sep, bool escape) 157 | { 158 | size_t i, len; 159 | struct string *path, *result; 160 | char *s; 161 | 162 | if (n == 0) 163 | return NULL; 164 | if (n == 1) 165 | return nodepath(nodes[0], escape); 166 | for (i = 0, len = 0; i < n; ++i) 167 | len += nodepath(nodes[i], escape)->n; 168 | result = mkstr(len + n - 1); 169 | s = result->s; 170 | for (i = 0; i < n; ++i) { 171 | path = nodepath(nodes[i], escape); 172 | memcpy(s, path->s, path->n); 173 | s += path->n; 174 | *s++ = sep; 175 | } 176 | *--s = '\0'; 177 | 178 | return result; 179 | } 180 | 181 | struct rule * 182 | mkrule(char *name) 183 | { 184 | struct rule *r; 185 | 186 | r = xmalloc(sizeof(*r)); 187 | r->name = name; 188 | r->bindings = NULL; 189 | 190 | return r; 191 | } 192 | 193 | static void 194 | delrule(void *ptr) 195 | { 196 | struct rule *r = ptr; 197 | 198 | if (r == &phonyrule) 199 | return; 200 | deltree(r->bindings, free, delevalstr); 201 | free(r->name); 202 | free(r); 203 | } 204 | 205 | void 206 | ruleaddvar(struct rule *r, char *var, struct evalstring *val) 207 | { 208 | addvar(&r->bindings, var, val); 209 | } 210 | 211 | struct string * 212 | edgevar(struct edge *e, char *var, bool escape) 213 | { 214 | static void *const cycle = (void *)&cycle; 215 | struct evalstring *str, *p; 216 | struct treenode *n; 217 | size_t len; 218 | 219 | if (strcmp(var, "in") == 0) 220 | return pathlist(e->in, e->inimpidx, ' ', escape); 221 | if (strcmp(var, "in_newline") == 0) 222 | return pathlist(e->in, e->inimpidx, '\n', escape); 223 | if (strcmp(var, "out") == 0) 224 | return pathlist(e->out, e->outimpidx, ' ', escape); 225 | n = treefind(e->env->bindings, var); 226 | if (n) 227 | return n->value; 228 | n = treefind(e->rule->bindings, var); 229 | if (!n) 230 | return envvar(e->env->parent, var); 231 | if (n->value == cycle) 232 | fatal("cycle in rule variable involving '%s'", var); 233 | str = n->value; 234 | n->value = cycle; 235 | len = 0; 236 | for (p = str; p; p = p->next) { 237 | if (p->var) 238 | p->str = edgevar(e, p->var, escape); 239 | if (p->str) 240 | len += p->str->n; 241 | } 242 | n->value = str; 243 | return merge(str, len); 244 | } 245 | 246 | static void 247 | addpool(struct pool *p) 248 | { 249 | if (treeinsert(&pools, p->name, p)) 250 | fatal("pool '%s' redefined", p->name); 251 | } 252 | 253 | struct pool * 254 | mkpool(char *name) 255 | { 256 | struct pool *p; 257 | 258 | p = xmalloc(sizeof(*p)); 259 | p->name = name; 260 | p->numjobs = 0; 261 | p->maxjobs = 0; 262 | p->work = NULL; 263 | addpool(p); 264 | 265 | return p; 266 | } 267 | 268 | static void 269 | delpool(void *ptr) 270 | { 271 | struct pool *p = ptr; 272 | 273 | if (p == &consolepool) 274 | return; 275 | free(p->name); 276 | free(p); 277 | } 278 | 279 | struct pool * 280 | poolget(char *name) 281 | { 282 | struct treenode *n; 283 | 284 | n = treefind(pools, name); 285 | if (!n) 286 | fatal("unknown pool '%s'", name); 287 | 288 | return n->value; 289 | } 290 | -------------------------------------------------------------------------------- /env.h: -------------------------------------------------------------------------------- 1 | struct evalstring; 2 | struct string; 3 | 4 | struct rule { 5 | char *name; 6 | struct treenode *bindings; 7 | }; 8 | 9 | struct pool { 10 | char *name; 11 | int numjobs, maxjobs; 12 | 13 | /* a queue of ready edges blocked by the pool's capacity */ 14 | struct edge *work; 15 | }; 16 | 17 | void envinit(void); 18 | 19 | /* create a new environment with an optional parent */ 20 | struct environment *mkenv(struct environment *); 21 | /* search environment and its parents for a variable, returning the value or NULL if not found */ 22 | struct string *envvar(struct environment *, char *); 23 | /* add to environment a variable and its value, replacing the old value if there is one */ 24 | void envaddvar(struct environment *, char *, struct string *); 25 | /* evaluate an unevaluated string within an environment, returning the result */ 26 | struct string *enveval(struct environment *, struct evalstring *); 27 | /* search an environment and its parents for a rule, returning the rule or NULL if not found */ 28 | struct rule *envrule(struct environment *, char *); 29 | /* add a rule to an environment, or fail if the rule already exists */ 30 | void envaddrule(struct environment *, struct rule *); 31 | 32 | /* create a new rule with the given name */ 33 | struct rule *mkrule(char *); 34 | /* add to rule a variable and its value */ 35 | void ruleaddvar(struct rule *, char *, struct evalstring *); 36 | 37 | /* create a new pool with the given name */ 38 | struct pool *mkpool(char *); 39 | /* lookup a pool by name, or fail if it does not exist */ 40 | struct pool *poolget(char *); 41 | 42 | /* evaluate and return an edge's variable, optionally shell-escaped */ 43 | struct string *edgevar(struct edge *, char *, _Bool); 44 | 45 | extern struct environment *rootenv; 46 | extern struct rule phonyrule; 47 | extern struct pool consolepool; 48 | -------------------------------------------------------------------------------- /graph.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "env.h" 7 | #include "graph.h" 8 | #include "htab.h" 9 | #include "os.h" 10 | #include "util.h" 11 | 12 | static struct hashtable *allnodes; 13 | struct edge *alledges; 14 | 15 | static void 16 | delnode(void *p) 17 | { 18 | struct node *n = p; 19 | 20 | if (n->shellpath != n->path) 21 | free(n->shellpath); 22 | free(n->use); 23 | free(n->path); 24 | free(n); 25 | } 26 | 27 | void 28 | graphinit(void) 29 | { 30 | struct edge *e; 31 | 32 | /* delete old nodes and edges in case we rebuilt the manifest */ 33 | delhtab(allnodes, delnode); 34 | while (alledges) { 35 | e = alledges; 36 | alledges = e->allnext; 37 | free(e->out); 38 | free(e->in); 39 | free(e); 40 | } 41 | allnodes = mkhtab(1024); 42 | } 43 | 44 | struct node * 45 | mknode(struct string *path) 46 | { 47 | void **v; 48 | struct node *n; 49 | struct hashtablekey k; 50 | 51 | htabkey(&k, path->s, path->n); 52 | v = htabput(allnodes, &k); 53 | if (*v) { 54 | free(path); 55 | return *v; 56 | } 57 | n = xmalloc(sizeof(*n)); 58 | n->path = path; 59 | n->shellpath = NULL; 60 | n->gen = NULL; 61 | n->use = NULL; 62 | n->nuse = 0; 63 | n->mtime = MTIME_UNKNOWN; 64 | n->logmtime = MTIME_MISSING; 65 | n->hash = 0; 66 | n->id = -1; 67 | *v = n; 68 | 69 | return n; 70 | } 71 | 72 | struct node * 73 | nodeget(const char *path, size_t len) 74 | { 75 | struct hashtablekey k; 76 | 77 | if (!len) 78 | len = strlen(path); 79 | htabkey(&k, path, len); 80 | return htabget(allnodes, &k); 81 | } 82 | 83 | void 84 | nodestat(struct node *n) 85 | { 86 | n->mtime = osmtime(n->path->s); 87 | } 88 | 89 | struct string * 90 | nodepath(struct node *n, bool escape) 91 | { 92 | char *s, *d; 93 | int nquote; 94 | 95 | if (!escape) 96 | return n->path; 97 | if (n->shellpath) 98 | return n->shellpath; 99 | escape = false; 100 | nquote = 0; 101 | for (s = n->path->s; *s; ++s) { 102 | if (!isalnum(*(unsigned char *)s) && !strchr("_+-./", *s)) 103 | escape = true; 104 | if (*s == '\'') 105 | ++nquote; 106 | } 107 | if (escape) { 108 | n->shellpath = mkstr(n->path->n + 2 + 3 * nquote); 109 | d = n->shellpath->s; 110 | *d++ = '\''; 111 | for (s = n->path->s; *s; ++s) { 112 | *d++ = *s; 113 | if (*s == '\'') { 114 | *d++ = '\\'; 115 | *d++ = '\''; 116 | *d++ = '\''; 117 | } 118 | } 119 | *d++ = '\''; 120 | } else { 121 | n->shellpath = n->path; 122 | } 123 | return n->shellpath; 124 | } 125 | 126 | void 127 | nodeuse(struct node *n, struct edge *e) 128 | { 129 | /* allocate in powers of two */ 130 | if (!(n->nuse & (n->nuse - 1))) 131 | n->use = xreallocarray(n->use, n->nuse ? n->nuse * 2 : 1, sizeof(e)); 132 | n->use[n->nuse++] = e; 133 | } 134 | 135 | struct edge * 136 | mkedge(struct environment *parent) 137 | { 138 | struct edge *e; 139 | 140 | e = xmalloc(sizeof(*e)); 141 | e->env = mkenv(parent); 142 | e->pool = NULL; 143 | e->out = NULL; 144 | e->nout = 0; 145 | e->in = NULL; 146 | e->nin = 0; 147 | e->flags = 0; 148 | e->allnext = alledges; 149 | alledges = e; 150 | 151 | return e; 152 | } 153 | 154 | void 155 | edgehash(struct edge *e) 156 | { 157 | static const char sep[] = ";rspfile="; 158 | struct string *cmd, *rsp, *s; 159 | 160 | if (e->flags & FLAG_HASH) 161 | return; 162 | e->flags |= FLAG_HASH; 163 | cmd = edgevar(e, "command", true); 164 | if (!cmd) 165 | fatal("rule '%s' has no command", e->rule->name); 166 | rsp = edgevar(e, "rspfile_content", true); 167 | if (rsp && rsp->n > 0) { 168 | s = mkstr(cmd->n + sizeof(sep) - 1 + rsp->n); 169 | memcpy(s->s, cmd->s, cmd->n); 170 | memcpy(s->s + cmd->n, sep, sizeof(sep) - 1); 171 | memcpy(s->s + cmd->n + sizeof(sep) - 1, rsp->s, rsp->n); 172 | s->s[s->n] = '\0'; 173 | e->hash = murmurhash64a(s->s, s->n); 174 | free(s); 175 | } else { 176 | e->hash = murmurhash64a(cmd->s, cmd->n); 177 | } 178 | } 179 | 180 | static struct edge * 181 | mkphony(struct node *n) 182 | { 183 | struct edge *e; 184 | 185 | e = mkedge(rootenv); 186 | e->rule = &phonyrule; 187 | e->inimpidx = 0; 188 | e->inorderidx = 0; 189 | e->outimpidx = 1; 190 | e->nout = 1; 191 | e->out = xmalloc(sizeof(n)); 192 | e->out[0] = n; 193 | 194 | return e; 195 | } 196 | 197 | void 198 | edgeadddeps(struct edge *e, struct node **deps, size_t ndeps) 199 | { 200 | struct node **order, *n; 201 | size_t norder, i; 202 | 203 | for (i = 0; i < ndeps; ++i) { 204 | n = deps[i]; 205 | if (!n->gen) 206 | n->gen = mkphony(n); 207 | nodeuse(n, e); 208 | } 209 | e->in = xreallocarray(e->in, e->nin + ndeps, sizeof(e->in[0])); 210 | order = e->in + e->inorderidx; 211 | norder = e->nin - e->inorderidx; 212 | memmove(order + ndeps, order, norder * sizeof(e->in[0])); 213 | memcpy(order, deps, ndeps * sizeof(e->in[0])); 214 | e->inorderidx += ndeps; 215 | e->nin += ndeps; 216 | } 217 | -------------------------------------------------------------------------------- /graph.h: -------------------------------------------------------------------------------- 1 | #include /* for uint64_t */ 2 | 3 | /* set in the tv_nsec field of a node's mtime */ 4 | enum { 5 | /* we haven't stat the file yet */ 6 | MTIME_UNKNOWN = -1, 7 | /* the file does not exist */ 8 | MTIME_MISSING = -2, 9 | }; 10 | 11 | struct node { 12 | /* shellpath is the escaped shell path, and is populated as needed by nodepath */ 13 | struct string *path, *shellpath; 14 | 15 | /* modification time of file (in nanoseconds) and build log entry (in seconds) */ 16 | int64_t mtime, logmtime; 17 | 18 | /* generating edge and dependent edges */ 19 | struct edge *gen, **use; 20 | size_t nuse; 21 | 22 | /* command hash used to build this output, read from build log */ 23 | uint64_t hash; 24 | 25 | /* ID for .ninja_deps. -1 if not present in log. */ 26 | int32_t id; 27 | 28 | /* does the node need to be rebuilt */ 29 | _Bool dirty; 30 | }; 31 | 32 | /* build rule, i.e., edge between inputs and outputs */ 33 | struct edge { 34 | struct rule *rule; 35 | struct pool *pool; 36 | struct environment *env; 37 | 38 | /* input and output nodes */ 39 | struct node **out, **in; 40 | size_t nout, nin; 41 | 42 | /* index of first implicit output */ 43 | size_t outimpidx; 44 | /* index of first implicit and order-only input */ 45 | size_t inimpidx, inorderidx; 46 | 47 | /* command hash */ 48 | uint64_t hash; 49 | 50 | /* how many inputs need to be rebuilt or pruned before this edge is ready */ 51 | size_t nblock; 52 | /* how many inputs need to be pruned before all outputs can be pruned */ 53 | size_t nprune; 54 | 55 | enum { 56 | FLAG_WORK = 1 << 0, /* scheduled for build */ 57 | FLAG_HASH = 1 << 1, /* calculated the command hash */ 58 | FLAG_DIRTY_IN = 1 << 3, /* dirty input */ 59 | FLAG_DIRTY_OUT = 1 << 4, /* missing or outdated output */ 60 | FLAG_DIRTY = FLAG_DIRTY_IN | FLAG_DIRTY_OUT, 61 | FLAG_CYCLE = 1 << 5, /* used for cycle detection */ 62 | FLAG_DEPS = 1 << 6, /* dependencies loaded */ 63 | } flags; 64 | 65 | /* used to coordinate ready work in build() */ 66 | struct edge *worknext; 67 | /* used for alledges linked list */ 68 | struct edge *allnext; 69 | }; 70 | 71 | void graphinit(void); 72 | 73 | /* create a new node or return existing node */ 74 | struct node *mknode(struct string *); 75 | /* lookup a node by name; returns NULL if it does not exist */ 76 | struct node *nodeget(const char *, size_t); 77 | /* update the mtime field of a node */ 78 | void nodestat(struct node *); 79 | /* get a node's path, possibly escaped for the shell */ 80 | struct string *nodepath(struct node *, _Bool); 81 | /* record the usage of a node by an edge */ 82 | void nodeuse(struct node *, struct edge *); 83 | 84 | /* create a new edge with the given parent environment */ 85 | struct edge *mkedge(struct environment *parent); 86 | /* compute the murmurhash64a of an edge command and store it in the hash field */ 87 | void edgehash(struct edge *); 88 | /* add dependencies from $depfile or .ninja_deps as implicit inputs */ 89 | void edgeadddeps(struct edge *e, struct node **deps, size_t ndeps); 90 | 91 | /* a single linked list of all edges, valid up until build() */ 92 | extern struct edge *alledges; 93 | -------------------------------------------------------------------------------- /htab.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "util.h" 7 | #include "htab.h" 8 | 9 | struct hashtable { 10 | size_t len, cap; 11 | struct hashtablekey *keys; 12 | void **vals; 13 | }; 14 | 15 | void 16 | htabkey(struct hashtablekey *k, const char *s, size_t n) 17 | { 18 | k->str = s; 19 | k->len = n; 20 | k->hash = murmurhash64a(s, n); 21 | } 22 | 23 | struct hashtable * 24 | mkhtab(size_t cap) 25 | { 26 | struct hashtable *h; 27 | size_t i; 28 | 29 | assert(!(cap & (cap - 1))); 30 | h = xmalloc(sizeof(*h)); 31 | h->len = 0; 32 | h->cap = cap; 33 | h->keys = xreallocarray(NULL, cap, sizeof(h->keys[0])); 34 | h->vals = xreallocarray(NULL, cap, sizeof(h->vals[0])); 35 | for (i = 0; i < cap; ++i) 36 | h->keys[i].str = NULL; 37 | 38 | return h; 39 | } 40 | 41 | void 42 | delhtab(struct hashtable *h, void del(void *)) 43 | { 44 | size_t i; 45 | 46 | if (!h) 47 | return; 48 | if (del) { 49 | for (i = 0; i < h->cap; ++i) { 50 | if (h->keys[i].str) 51 | del(h->vals[i]); 52 | } 53 | } 54 | free(h->keys); 55 | free(h->vals); 56 | free(h); 57 | } 58 | 59 | static bool 60 | keyequal(struct hashtablekey *k1, struct hashtablekey *k2) 61 | { 62 | if (k1->hash != k2->hash || k1->len != k2->len) 63 | return false; 64 | return memcmp(k1->str, k2->str, k1->len) == 0; 65 | } 66 | 67 | static size_t 68 | keyindex(struct hashtable *h, struct hashtablekey *k) 69 | { 70 | size_t i; 71 | 72 | i = k->hash & (h->cap - 1); 73 | while (h->keys[i].str && !keyequal(&h->keys[i], k)) 74 | i = (i + 1) & (h->cap - 1); 75 | return i; 76 | } 77 | 78 | void ** 79 | htabput(struct hashtable *h, struct hashtablekey *k) 80 | { 81 | struct hashtablekey *oldkeys; 82 | void **oldvals; 83 | size_t i, j, oldcap; 84 | 85 | if (h->cap / 2 < h->len) { 86 | oldkeys = h->keys; 87 | oldvals = h->vals; 88 | oldcap = h->cap; 89 | h->cap *= 2; 90 | h->keys = xreallocarray(NULL, h->cap, sizeof(h->keys[0])); 91 | h->vals = xreallocarray(NULL, h->cap, sizeof(h->vals[0])); 92 | for (i = 0; i < h->cap; ++i) 93 | h->keys[i].str = NULL; 94 | for (i = 0; i < oldcap; ++i) { 95 | if (oldkeys[i].str) { 96 | j = keyindex(h, &oldkeys[i]); 97 | h->keys[j] = oldkeys[i]; 98 | h->vals[j] = oldvals[i]; 99 | } 100 | } 101 | free(oldkeys); 102 | free(oldvals); 103 | } 104 | i = keyindex(h, k); 105 | if (!h->keys[i].str) { 106 | h->keys[i] = *k; 107 | h->vals[i] = NULL; 108 | ++h->len; 109 | } 110 | 111 | return &h->vals[i]; 112 | } 113 | 114 | void * 115 | htabget(struct hashtable *h, struct hashtablekey *k) 116 | { 117 | size_t i; 118 | 119 | i = keyindex(h, k); 120 | return h->keys[i].str ? h->vals[i] : NULL; 121 | } 122 | 123 | uint64_t 124 | murmurhash64a(const void *ptr, size_t len) 125 | { 126 | const uint64_t seed = 0xdecafbaddecafbadull; 127 | const uint64_t m = 0xc6a4a7935bd1e995ull; 128 | uint64_t h, k, n; 129 | const uint8_t *p, *end; 130 | int r = 47; 131 | 132 | h = seed ^ (len * m); 133 | n = len & ~0x7ull; 134 | end = ptr; 135 | end += n; 136 | for (p = ptr; p != end; p += 8) { 137 | memcpy(&k, p, sizeof(k)); 138 | 139 | k *= m; 140 | k ^= k >> r; 141 | k *= m; 142 | 143 | h ^= k; 144 | h *= m; 145 | } 146 | 147 | switch (len & 0x7) { 148 | case 7: h ^= (uint64_t)p[6] << 48; /* fallthrough */ 149 | case 6: h ^= (uint64_t)p[5] << 40; /* fallthrough */ 150 | case 5: h ^= (uint64_t)p[4] << 32; /* fallthrough */ 151 | case 4: h ^= (uint64_t)p[3] << 24; /* fallthrough */ 152 | case 3: h ^= (uint64_t)p[2] << 16; /* fallthrough */ 153 | case 2: h ^= (uint64_t)p[1] << 8; /* fallthrough */ 154 | case 1: h ^= (uint64_t)p[0]; 155 | h *= m; 156 | } 157 | 158 | h ^= h >> r; 159 | h *= m; 160 | h ^= h >> r; 161 | 162 | return h; 163 | } 164 | -------------------------------------------------------------------------------- /htab.h: -------------------------------------------------------------------------------- 1 | #include /* for uint64_t */ 2 | 3 | struct hashtablekey { 4 | uint64_t hash; 5 | const char *str; 6 | size_t len; 7 | }; 8 | 9 | void htabkey(struct hashtablekey *, const char *, size_t); 10 | 11 | struct hashtable *mkhtab(size_t); 12 | void delhtab(struct hashtable *, void(void *)); 13 | void **htabput(struct hashtable *, struct hashtablekey *); 14 | void *htabget(struct hashtable *, struct hashtablekey *); 15 | 16 | uint64_t murmurhash64a(const void *, size_t); 17 | -------------------------------------------------------------------------------- /log.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "graph.h" 7 | #include "log.h" 8 | #include "util.h" 9 | 10 | static FILE *logfile; 11 | static const char *logname = ".ninja_log"; 12 | static const char *logtmpname = ".ninja_log.tmp"; 13 | static const char *logfmt = "# ninja log v%d\n"; 14 | static const int logver = 5; 15 | 16 | static char * 17 | nextfield(char **end) 18 | { 19 | char *s = *end; 20 | 21 | if (!*s) { 22 | warn("corrupt build log: missing field"); 23 | return NULL; 24 | } 25 | *end += strcspn(*end, "\t\n"); 26 | if (**end) 27 | *(*end)++ = '\0'; 28 | 29 | return s; 30 | } 31 | 32 | void 33 | loginit(const char *builddir) 34 | { 35 | int ver; 36 | char *logpath = (char *)logname, *logtmppath = (char *)logtmpname, *p, *s; 37 | size_t nline, nentry, i; 38 | struct edge *e; 39 | struct node *n; 40 | int64_t mtime; 41 | struct buffer buf = {0}; 42 | 43 | nline = 0; 44 | nentry = 0; 45 | 46 | if (logfile) 47 | fclose(logfile); 48 | if (builddir) 49 | xasprintf(&logpath, "%s/%s", builddir, logname); 50 | logfile = fopen(logpath, "r+"); 51 | if (!logfile) { 52 | if (errno != ENOENT) 53 | fatal("open %s:", logpath); 54 | goto rewrite; 55 | } 56 | setvbuf(logfile, NULL, _IOLBF, 0); 57 | if (fscanf(logfile, logfmt, &ver) < 1) 58 | goto rewrite; 59 | if (ver != logver) 60 | goto rewrite; 61 | 62 | for (;;) { 63 | if (buf.cap - buf.len < BUFSIZ) { 64 | buf.cap = buf.cap ? buf.cap * 2 : BUFSIZ; 65 | buf.data = xreallocarray(buf.data, buf.cap, 1); 66 | } 67 | buf.data[buf.cap - 2] = '\0'; 68 | if (!fgets(buf.data + buf.len, buf.cap - buf.len, logfile)) 69 | break; 70 | if (buf.data[buf.cap - 2] && buf.data[buf.cap - 2] != '\n') { 71 | buf.len = buf.cap - 1; 72 | continue; 73 | } 74 | ++nline; 75 | p = buf.data; 76 | buf.len = 0; 77 | if (!nextfield(&p)) /* start time */ 78 | continue; 79 | if (!nextfield(&p)) /* end time */ 80 | continue; 81 | s = nextfield(&p); /* mtime (used for restat) */ 82 | if (!s) 83 | continue; 84 | mtime = strtoll(s, &s, 10); 85 | if (*s) { 86 | warn("corrupt build log: invalid mtime"); 87 | continue; 88 | } 89 | s = nextfield(&p); /* output path */ 90 | if (!s) 91 | continue; 92 | n = nodeget(s, 0); 93 | if (!n || !n->gen) 94 | continue; 95 | if (n->logmtime == MTIME_MISSING) 96 | ++nentry; 97 | n->logmtime = mtime; 98 | s = nextfield(&p); /* command hash */ 99 | if (!s) 100 | continue; 101 | n->hash = strtoull(s, &s, 16); 102 | if (*s) { 103 | warn("corrupt build log: invalid hash for '%s'", n->path->s); 104 | continue; 105 | } 106 | } 107 | free(buf.data); 108 | if (ferror(logfile)) { 109 | warn("build log read:"); 110 | goto rewrite; 111 | } 112 | if (nline <= 100 || nline <= 3 * nentry) { 113 | if (builddir) 114 | free(logpath); 115 | return; 116 | } 117 | 118 | rewrite: 119 | if (logfile) 120 | fclose(logfile); 121 | if (builddir) 122 | xasprintf(&logtmppath, "%s/%s", builddir, logtmpname); 123 | logfile = fopen(logtmppath, "w"); 124 | if (!logfile) 125 | fatal("open %s:", logtmppath); 126 | setvbuf(logfile, NULL, _IOLBF, 0); 127 | fprintf(logfile, logfmt, logver); 128 | if (nentry > 0) { 129 | for (e = alledges; e; e = e->allnext) { 130 | for (i = 0; i < e->nout; ++i) { 131 | n = e->out[i]; 132 | if (!n->hash) 133 | continue; 134 | logrecord(n); 135 | } 136 | } 137 | } 138 | fflush(logfile); 139 | if (ferror(logfile)) 140 | fatal("build log write failed"); 141 | if (rename(logtmppath, logpath) < 0) 142 | fatal("build log rename:"); 143 | if (builddir) { 144 | free(logpath); 145 | free(logtmppath); 146 | } 147 | } 148 | 149 | void 150 | logclose(void) 151 | { 152 | fflush(logfile); 153 | if (ferror(logfile)) 154 | fatal("build log write failed"); 155 | fclose(logfile); 156 | } 157 | 158 | void 159 | logrecord(struct node *n) 160 | { 161 | fprintf(logfile, "0\t0\t%" PRId64 "\t%s\t%" PRIx64 "\n", n->logmtime, n->path->s, n->hash); 162 | } 163 | -------------------------------------------------------------------------------- /log.h: -------------------------------------------------------------------------------- 1 | struct node; 2 | 3 | void loginit(const char *); 4 | void logclose(void); 5 | void logrecord(struct node *); 6 | -------------------------------------------------------------------------------- /os-posix.c: -------------------------------------------------------------------------------- 1 | #define _POSIX_C_SOURCE 200809L 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "graph.h" 8 | #include "os.h" 9 | #include "util.h" 10 | 11 | void 12 | osgetcwd(char *buf, size_t len) 13 | { 14 | if (!getcwd(buf, len)) 15 | fatal("getcwd:"); 16 | } 17 | 18 | void 19 | oschdir(const char *dir) 20 | { 21 | if (chdir(dir) < 0) 22 | fatal("chdir %s:", dir); 23 | } 24 | 25 | int 26 | osmkdirs(struct string *path, bool parent) 27 | { 28 | int ret; 29 | struct stat st; 30 | char *s, *end; 31 | 32 | ret = 0; 33 | end = path->s + path->n; 34 | for (s = end - parent; s > path->s; --s) { 35 | if (*s != '/' && *s) 36 | continue; 37 | *s = '\0'; 38 | if (stat(path->s, &st) == 0) 39 | break; 40 | if (errno != ENOENT) { 41 | warn("stat %s:", path->s); 42 | ret = -1; 43 | break; 44 | } 45 | } 46 | if (s > path->s && s < end) 47 | *s = '/'; 48 | while (++s <= end - parent) { 49 | if (*s != '\0') 50 | continue; 51 | if (ret == 0 && mkdir(path->s, 0777) < 0 && errno != EEXIST) { 52 | warn("mkdir %s:", path->s); 53 | ret = -1; 54 | } 55 | if (s < end) 56 | *s = '/'; 57 | } 58 | 59 | return ret; 60 | } 61 | 62 | int64_t 63 | osmtime(const char *name) 64 | { 65 | struct stat st; 66 | 67 | if (stat(name, &st) < 0) { 68 | if (errno != ENOENT) 69 | fatal("stat %s:", name); 70 | return MTIME_MISSING; 71 | } else { 72 | #ifdef __APPLE__ 73 | return (int64_t)st.st_mtime * 1000000000 + st.st_mtimensec; 74 | /* 75 | Illumos hides the members of st_mtim when you define _POSIX_C_SOURCE 76 | since it has not been updated to support POSIX.1-2008: 77 | https://www.illumos.org/issues/13327 78 | */ 79 | #elif defined(__sun) 80 | return (int64_t)st.st_mtim.__tv_sec * 1000000000 + st.st_mtim.__tv_nsec; 81 | #else 82 | return (int64_t)st.st_mtim.tv_sec * 1000000000 + st.st_mtim.tv_nsec; 83 | #endif 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /os.h: -------------------------------------------------------------------------------- 1 | struct string; 2 | 3 | void osgetcwd(char *, size_t); 4 | /* changes the working directory to the given path */ 5 | void oschdir(const char *); 6 | /* creates all the parent directories of the given path */ 7 | int osmkdirs(struct string *, _Bool); 8 | /* queries the mtime of a file in nanoseconds since the UNIX epoch */ 9 | int64_t osmtime(const char *); 10 | -------------------------------------------------------------------------------- /parse.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "env.h" 6 | #include "graph.h" 7 | #include "parse.h" 8 | #include "scan.h" 9 | #include "util.h" 10 | 11 | struct parseoptions parseopts; 12 | static struct node **deftarg; 13 | static size_t ndeftarg; 14 | 15 | void 16 | parseinit(void) 17 | { 18 | free(deftarg); 19 | deftarg = NULL; 20 | ndeftarg = 0; 21 | } 22 | 23 | static void 24 | parselet(struct scanner *s, struct evalstring **val) 25 | { 26 | scanchar(s, '='); 27 | *val = scanstring(s, false); 28 | scannewline(s); 29 | } 30 | 31 | static void 32 | parserule(struct scanner *s, struct environment *env) 33 | { 34 | struct rule *r; 35 | char *var; 36 | struct evalstring *val; 37 | bool hascommand = false, hasrspfile = false, hasrspcontent = false; 38 | 39 | r = mkrule(scanname(s)); 40 | scannewline(s); 41 | while (scanindent(s)) { 42 | var = scanname(s); 43 | parselet(s, &val); 44 | ruleaddvar(r, var, val); 45 | if (!val) 46 | continue; 47 | if (strcmp(var, "command") == 0) 48 | hascommand = true; 49 | else if (strcmp(var, "rspfile") == 0) 50 | hasrspfile = true; 51 | else if (strcmp(var, "rspfile_content") == 0) 52 | hasrspcontent = true; 53 | } 54 | if (!hascommand) 55 | fatal("rule '%s' has no command", r->name); 56 | if (hasrspfile != hasrspcontent) 57 | fatal("rule '%s' has rspfile and no rspfile_content or vice versa", r->name); 58 | envaddrule(env, r); 59 | } 60 | 61 | static void 62 | parseedge(struct scanner *s, struct environment *env) 63 | { 64 | struct edge *e; 65 | struct evalstring *str, **path; 66 | char *name; 67 | struct string *val; 68 | struct node *n; 69 | size_t i; 70 | int p; 71 | 72 | e = mkedge(env); 73 | 74 | scanpaths(s); 75 | e->outimpidx = npaths; 76 | if (scanpipe(s, 1)) 77 | scanpaths(s); 78 | e->nout = npaths; 79 | if (e->nout == 0) 80 | scanerror(s, "expected output path"); 81 | scanchar(s, ':'); 82 | name = scanname(s); 83 | e->rule = envrule(env, name); 84 | if (!e->rule) 85 | fatal("undefined rule '%s'", name); 86 | free(name); 87 | scanpaths(s); 88 | e->inimpidx = npaths - e->nout; 89 | p = scanpipe(s, 1 | 2); 90 | if (p == 1) { 91 | scanpaths(s); 92 | p = scanpipe(s, 2); 93 | } 94 | e->inorderidx = npaths - e->nout; 95 | if (p == 2) 96 | scanpaths(s); 97 | e->nin = npaths - e->nout; 98 | scannewline(s); 99 | while (scanindent(s)) { 100 | name = scanname(s); 101 | parselet(s, &str); 102 | val = enveval(env, str); 103 | envaddvar(e->env, name, val); 104 | } 105 | 106 | e->out = xreallocarray(NULL, e->nout, sizeof(e->out[0])); 107 | for (i = 0, path = paths; i < e->nout; ++path) { 108 | val = enveval(e->env, *path); 109 | canonpath(val); 110 | n = mknode(val); 111 | if (n->gen) { 112 | if (!parseopts.dupbuildwarn) 113 | fatal("multiple rules generate '%s'", n->path->s); 114 | warn("multiple rules generate '%s'", n->path->s); 115 | --e->nout; 116 | if (i < e->outimpidx) 117 | --e->outimpidx; 118 | } else { 119 | n->gen = e; 120 | e->out[i] = n; 121 | ++i; 122 | } 123 | } 124 | e->in = xreallocarray(NULL, e->nin, sizeof(e->in[0])); 125 | for (i = 0; i < e->nin; ++i, ++path) { 126 | val = enveval(e->env, *path); 127 | canonpath(val); 128 | n = mknode(val); 129 | e->in[i] = n; 130 | nodeuse(n, e); 131 | } 132 | npaths = 0; 133 | 134 | val = edgevar(e, "pool", true); 135 | if (val) 136 | e->pool = poolget(val->s); 137 | } 138 | 139 | static void 140 | parseinclude(struct scanner *s, struct environment *env, bool newscope) 141 | { 142 | struct evalstring *str; 143 | struct string *path; 144 | 145 | str = scanstring(s, true); 146 | if (!str) 147 | scanerror(s, "expected include path"); 148 | scannewline(s); 149 | path = enveval(env, str); 150 | 151 | if (newscope) 152 | env = mkenv(env); 153 | parse(path->s, env); 154 | free(path); 155 | } 156 | 157 | static void 158 | parsedefault(struct scanner *s, struct environment *env) 159 | { 160 | struct string *path; 161 | struct node *n; 162 | size_t i; 163 | 164 | scanpaths(s); 165 | deftarg = xreallocarray(deftarg, ndeftarg + npaths, sizeof(*deftarg)); 166 | for (i = 0; i < npaths; ++i) { 167 | path = enveval(env, paths[i]); 168 | canonpath(path); 169 | n = nodeget(path->s, path->n); 170 | if (!n) 171 | fatal("unknown target '%s'", path->s); 172 | free(path); 173 | deftarg[ndeftarg++] = n; 174 | } 175 | scannewline(s); 176 | npaths = 0; 177 | } 178 | 179 | static void 180 | parsepool(struct scanner *s, struct environment *env) 181 | { 182 | struct pool *p; 183 | struct evalstring *val; 184 | struct string *str; 185 | char *var, *end; 186 | 187 | p = mkpool(scanname(s)); 188 | scannewline(s); 189 | while (scanindent(s)) { 190 | var = scanname(s); 191 | parselet(s, &val); 192 | if (strcmp(var, "depth") == 0) { 193 | str = enveval(env, val); 194 | p->maxjobs = strtol(str->s, &end, 10); 195 | if (*end) 196 | fatal("invalid pool depth '%s'", str->s); 197 | free(str); 198 | } else { 199 | fatal("unexpected pool variable '%s'", var); 200 | } 201 | } 202 | if (!p->maxjobs) 203 | fatal("pool '%s' has no depth", p->name); 204 | } 205 | 206 | static void 207 | checkversion(const char *ver) 208 | { 209 | int major, minor = 0; 210 | 211 | if (sscanf(ver, "%d.%d", &major, &minor) < 1) 212 | fatal("invalid ninja_required_version"); 213 | if (major > ninjamajor || (major == ninjamajor && minor > ninjaminor)) 214 | fatal("ninja_required_version %s is newer than %d.%d", ver, ninjamajor, ninjaminor); 215 | } 216 | 217 | void 218 | parse(const char *name, struct environment *env) 219 | { 220 | struct scanner s; 221 | char *var; 222 | struct string *val; 223 | struct evalstring *str; 224 | 225 | scaninit(&s, name); 226 | for (;;) { 227 | switch (scankeyword(&s, &var)) { 228 | case RULE: 229 | parserule(&s, env); 230 | break; 231 | case BUILD: 232 | parseedge(&s, env); 233 | break; 234 | case INCLUDE: 235 | parseinclude(&s, env, false); 236 | break; 237 | case SUBNINJA: 238 | parseinclude(&s, env, true); 239 | break; 240 | case DEFAULT: 241 | parsedefault(&s, env); 242 | break; 243 | case POOL: 244 | parsepool(&s, env); 245 | break; 246 | case VARIABLE: 247 | parselet(&s, &str); 248 | val = enveval(env, str); 249 | if (strcmp(var, "ninja_required_version") == 0) 250 | checkversion(val->s); 251 | envaddvar(env, var, val); 252 | break; 253 | case EOF: 254 | scanclose(&s); 255 | return; 256 | } 257 | } 258 | } 259 | 260 | void 261 | defaultnodes(void fn(struct node *)) 262 | { 263 | struct edge *e; 264 | struct node *n; 265 | size_t i; 266 | 267 | if (ndeftarg > 0) { 268 | for (i = 0; i < ndeftarg; ++i) 269 | fn(deftarg[i]); 270 | } else { 271 | /* by default build all nodes which are not used by any edges */ 272 | for (e = alledges; e; e = e->allnext) { 273 | for (i = 0; i < e->nout; ++i) { 274 | n = e->out[i]; 275 | if (n->nuse == 0) 276 | fn(n); 277 | } 278 | } 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /parse.h: -------------------------------------------------------------------------------- 1 | struct environment; 2 | struct node; 3 | 4 | struct parseoptions { 5 | _Bool dupbuildwarn; 6 | }; 7 | 8 | void parseinit(void); 9 | void parse(const char *, struct environment *); 10 | 11 | extern struct parseoptions parseopts; 12 | 13 | /* supported ninja version */ 14 | enum { 15 | ninjamajor = 1, 16 | ninjaminor = 9, 17 | }; 18 | 19 | /* execute a function with all default nodes */ 20 | void defaultnodes(void(struct node *)); 21 | -------------------------------------------------------------------------------- /samu.1: -------------------------------------------------------------------------------- 1 | .Dd December 29, 2024 2 | .Dt SAMU 1 3 | .Os 4 | .Sh NAME 5 | .Nm samu 6 | .Nd ninja-compatible build tool 7 | .Sh SYNOPSIS 8 | .Nm 9 | .Op Fl C Ar dir 10 | .Op Fl d Ar debugflag 11 | .Op Fl f Ar buildfile 12 | .Op Fl j Ar maxjobs 13 | .Op Fl k Ar maxfail 14 | .Op Fl l Ar maxload 15 | .Op Fl w Ar warnflag=action 16 | .Op Fl nv 17 | .Op Ar target... 18 | .Nm 19 | .Op Fl C Ar dir 20 | .Op Fl f Ar buildfile 21 | .Fl t Cm clean 22 | .Op Fl gr 23 | .Op Ar target... 24 | .Nm 25 | .Op Fl C Ar dir 26 | .Op Fl f Ar buildfile 27 | .Fl t Cm compdb 28 | .Op Fl x 29 | .Op Ar rule... 30 | .Nm 31 | .Op Fl C Ar dir 32 | .Op Fl f Ar buildfile 33 | .Fl t Cm commands 34 | .Op Ar target... 35 | .Nm 36 | .Op Fl C Ar dir 37 | .Op Fl f Ar buildfile 38 | .Fl t Cm graph 39 | .Op Ar target... 40 | .Nm 41 | .Op Fl C Ar dir 42 | .Op Fl f Ar buildfile 43 | .Fl t Cm query 44 | .Op Ar target... 45 | .Nm 46 | .Op Fl C Ar dir 47 | .Op Fl f Ar buildfile 48 | .Fl t Cm targets 49 | .Op Cm depth Op Ar maxdepth 50 | .Nm 51 | .Op Fl C Ar dir 52 | .Op Fl f Ar buildfile 53 | .Fl t Cm targets 54 | .Cm rule 55 | .Op Ar rulename 56 | .Nm 57 | .Op Fl C Ar dir 58 | .Op Fl f Ar buildfile 59 | .Fl t Cm targets 60 | .Cm all 61 | .Sh DESCRIPTION 62 | .Nm 63 | loads a build manifest from 64 | .Ar buildfile 65 | and rebuilds 66 | .Ar targets 67 | if they are out of date. 68 | If no targets are specified, the manifest's default targets are built. 69 | If there are no default targets, the leaf nodes of the dependency graph are 70 | built (outputs that have no consuming action). 71 | .Pp 72 | Targets are out of date if they are older than the generating action's newest 73 | input, they have no entry in the build log, or if the command to build the 74 | target differs from what was used previously. 75 | Targets built with 76 | .Cm generator 77 | rules are not rebuilt if the command changes. 78 | .Pp 79 | If the 80 | .Cm clean 81 | tool is used, the targets are cleaned instead. 82 | When a target is cleaned, it is removed and are all of its inputs are cleaned. 83 | Targets with no generating actions are never removed. 84 | Targets built with 85 | .Sy generator 86 | rules are only removed if 87 | .Fl g 88 | is specified. 89 | .Pp 90 | If the 91 | .Cm commands 92 | tool is used, a list of shell commands used to build the specified 93 | (or default) targets is printed. 94 | .Pp 95 | If the 96 | .Cm compdb 97 | tool is used, a compilation database 98 | .Pq Lk https://clang.llvm.org/docs/JSONCompilationDatabase.html 99 | is printed instead. 100 | .Pp 101 | If the 102 | .Cm graph 103 | tool is used, a graphviz dot file is printed instead. 104 | .Pp 105 | If the 106 | .Cm query 107 | tool is used, the inputs and outputs of the targets are printed instead. 108 | .Pp 109 | If the 110 | .Cm targets 111 | tool is used, a list of targets will be displayed, either by rule or by depth. 112 | The first argument determines how the targets will be displayed: 113 | .Bl -tag -width Ds 114 | .It Cm depth 115 | An indented tree of targets will be displayed, starting by the root targets 116 | (those with no dependent targets), up to the depth specified by 117 | .Ar maxdepth 118 | (defaults to 1). 119 | .It Cm rule 120 | If 121 | .Ar rulename 122 | is given, a list of targets using the given rule will be displayed. 123 | Otherwise, a list of source files will be displayed. 124 | .It Cm all 125 | A list of all targets will be displayed. 126 | .El 127 | .Sh OPTIONS 128 | .Bl -tag -width Ds 129 | .It Fl C 130 | Switch working directory to 131 | .Ar dir 132 | before building. 133 | .It Fl d 134 | Enable a debugging option. 135 | This flag may be specified multiple times to enable multiple debugging options. 136 | Possible values for 137 | .Ar debugflag 138 | are: 139 | .Bl -tag -width keepdepfile 140 | .It Cm explain 141 | Print messages explaining why outputs were dirty. 142 | .It Cm keepdepfile 143 | Don't remove $depfile after it was parsed. 144 | .It Cm keeprsp 145 | Don't remove $rspfile after job completion or failure. 146 | .El 147 | .It Fl f 148 | Load manifest from 149 | .Ar buildfile 150 | instead of build.ninja. 151 | .It Fl j 152 | Run up to 153 | .Ar maxjobs 154 | jobs in parallel (default based on number of CPUs). 155 | If zero, allow unlimited concurrent jobs. 156 | .It Fl k 157 | Allow up to 158 | .Ar maxfail 159 | job failures. 160 | If negative or zero, allow any number of job failures. 161 | .It Fl l 162 | Do not spawn new jobs if the system load percentage is greater than 163 | .Ar maxload . 164 | If zero, spawn jobs as soon as possible. 165 | .It Fl n 166 | Do not actually execute the commands or update the log. 167 | .It Fl v 168 | Print full commands instead of just 169 | .Sy description . 170 | .It Fl w 171 | Control how certain errors are handled. 172 | If 173 | .Ar warnflag 174 | is 175 | .Cm dupbuild , 176 | .Ar action 177 | specifies what to do if an output is listed in multiple build edges. 178 | If 179 | .Ar action 180 | is 181 | .Cm err , 182 | treat it like an error and fail. 183 | If 184 | .Ar action 185 | is 186 | .Cm warn , 187 | only print a diagnostic message. 188 | .It Fl t 189 | Instead of building, invoke a subtool. 190 | All arguments and flags after the subtool are interpreted by the subtool. 191 | The currently supported subtools are 192 | .Cm clean , 193 | .Cm commands , 194 | .Cm compdb , 195 | .Cm graph , 196 | .Cm query , 197 | and 198 | .Cm targets . 199 | .It Fl g 200 | When cleaning, also clean outputs of 201 | .Sy generator 202 | actions. 203 | .It Fl r 204 | .Ar targets 205 | are interpreted as rules, and all outputs of actions with the specified rule 206 | types will be cleaned instead. 207 | .It Fl x 208 | When printing a compilation database, replace @$rspfile with $rspfile_content (with in place of ). 209 | .El 210 | .Sh ENVIRONMENT 211 | .Bl -tag -width NINJA_STATUS 212 | .It Ev SAMUFLAGS 213 | Options for 214 | .Nm 215 | that get applied before those specified on the command-line. 216 | The only options allowed in 217 | .Ev SAMUFLAGS 218 | are 219 | .Fl v , 220 | .Fl j 221 | and 222 | .Fl l . 223 | .It Ev NINJA_STATUS 224 | The status output printed to the left of each rule description, using printf-like conversion specifiers. 225 | If unset, the default is "[%s/%t] ". 226 | .Pp 227 | Available conversion specifiers: 228 | .Bl -tag -width Ds 229 | .It Cm %s 230 | Number of started jobs. 231 | .It Cm %f 232 | Number of finished jobs. 233 | .It Cm %t 234 | Total number of jobs. 235 | .It Cm %r 236 | Number of running jobs. 237 | .It Cm %u 238 | Number of remaining jobs. 239 | .It Cm %p 240 | Percentage of completed jobs. 241 | .It Cm %o 242 | Rate of finished jobs per second (to 1 decimal place). 243 | .It Cm %e 244 | Elapsed time in seconds (to 3 decimal places). 245 | .It Cm %% 246 | The '%' character. 247 | .El 248 | .El 249 | .Sh SEE ALSO 250 | .Xr make 1 251 | -------------------------------------------------------------------------------- /samu.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "arg.h" 7 | #include "build.h" 8 | #include "deps.h" 9 | #include "env.h" 10 | #include "graph.h" 11 | #include "log.h" 12 | #include "os.h" 13 | #include "parse.h" 14 | #include "tool.h" 15 | #include "util.h" 16 | 17 | const char *argv0; 18 | 19 | static void 20 | usage(void) 21 | { 22 | fprintf(stderr, "usage: %s [-C dir] [-f buildfile] [-j maxjobs] [-k maxfail] [-l maxload] [-n]\n", argv0); 23 | exit(2); 24 | } 25 | 26 | static char * 27 | getbuilddir(void) 28 | { 29 | struct string *builddir; 30 | 31 | builddir = envvar(rootenv, "builddir"); 32 | if (!builddir) 33 | return NULL; 34 | if (osmkdirs(builddir, false) < 0) 35 | exit(1); 36 | return builddir->s; 37 | } 38 | 39 | static void 40 | debugflag(const char *flag) 41 | { 42 | if (strcmp(flag, "explain") == 0) 43 | buildopts.explain = true; 44 | else if (strcmp(flag, "keepdepfile") == 0) 45 | buildopts.keepdepfile = true; 46 | else if (strcmp(flag, "keeprsp") == 0) 47 | buildopts.keeprsp = true; 48 | else 49 | fatal("unknown debug flag '%s'", flag); 50 | } 51 | 52 | static void 53 | loadflag(const char *flag) 54 | { 55 | #ifdef HAVE_GETLOADAVG 56 | double value; 57 | char *end; 58 | errno = 0; 59 | 60 | value = strtod(flag, &end); 61 | if (*end || value < 0 || errno != 0) 62 | fatal("invalid -l parameter"); 63 | buildopts.maxload = value; 64 | #else 65 | warn("job scheduling based on load average is not supported"); 66 | #endif 67 | } 68 | 69 | static void 70 | warnflag(const char *flag) 71 | { 72 | if (strcmp(flag, "dupbuild=err") == 0) 73 | parseopts.dupbuildwarn = false; 74 | else if (strcmp(flag, "dupbuild=warn") == 0) 75 | parseopts.dupbuildwarn = true; 76 | else 77 | fatal("unknown warning flag '%s'", flag); 78 | } 79 | 80 | static void 81 | jobsflag(const char *flag) 82 | { 83 | long num; 84 | char *end; 85 | 86 | num = strtol(flag, &end, 10); 87 | if (*end || num < 0) 88 | fatal("invalid -j parameter"); 89 | buildopts.maxjobs = num > 0 ? num : -1; 90 | } 91 | 92 | static void 93 | parseenvargs(char *env) 94 | { 95 | char *arg, *argvbuf[64], **argv = argvbuf; 96 | int argc; 97 | 98 | if (!env) 99 | return; 100 | env = xmemdup(env, strlen(env) + 1); 101 | argc = 1; 102 | argv[0] = NULL; 103 | arg = strtok(env, " "); 104 | while (arg) { 105 | if ((size_t)argc >= LEN(argvbuf) - 1) 106 | fatal("too many arguments in SAMUFLAGS"); 107 | argv[argc++] = arg; 108 | arg = strtok(NULL, " "); 109 | } 110 | argv[argc] = NULL; 111 | 112 | ARGBEGIN { 113 | case 'j': 114 | jobsflag(EARGF(usage())); 115 | break; 116 | case 'v': 117 | buildopts.verbose = true; 118 | break; 119 | case 'l': 120 | loadflag(EARGF(usage())); 121 | break; 122 | default: 123 | fatal("invalid option in SAMUFLAGS"); 124 | } ARGEND 125 | 126 | free(env); 127 | } 128 | 129 | static const char * 130 | progname(const char *arg, const char *def) 131 | { 132 | const char *slash; 133 | 134 | if (!arg) 135 | return def; 136 | slash = strrchr(arg, '/'); 137 | return slash ? slash + 1 : arg; 138 | } 139 | 140 | int 141 | main(int argc, char *argv[]) 142 | { 143 | char *builddir, *manifest = "build.ninja", *end, *arg; 144 | const struct tool *tool = NULL; 145 | struct node *n; 146 | long num; 147 | int tries; 148 | 149 | argv0 = progname(argv[0], "samu"); 150 | parseenvargs(getenv("SAMUFLAGS")); 151 | ARGBEGIN { 152 | case '-': 153 | arg = EARGF(usage()); 154 | if (strcmp(arg, "version") == 0) { 155 | printf("%d.%d.0\n", ninjamajor, ninjaminor); 156 | return 0; 157 | } else if (strcmp(arg, "verbose") == 0) { 158 | buildopts.verbose = true; 159 | } else { 160 | usage(); 161 | } 162 | break; 163 | case 'C': 164 | arg = EARGF(usage()); 165 | warn("entering directory '%s'", arg); 166 | oschdir(arg); 167 | break; 168 | case 'd': 169 | debugflag(EARGF(usage())); 170 | break; 171 | case 'f': 172 | manifest = EARGF(usage()); 173 | break; 174 | case 'j': 175 | jobsflag(EARGF(usage())); 176 | break; 177 | case 'k': 178 | num = strtol(EARGF(usage()), &end, 10); 179 | if (*end) 180 | fatal("invalid -k parameter"); 181 | buildopts.maxfail = num > 0 ? num : -1; 182 | break; 183 | case 'l': 184 | loadflag(EARGF(usage())); 185 | break; 186 | case 'n': 187 | buildopts.dryrun = true; 188 | break; 189 | case 't': 190 | tool = toolget(EARGF(usage())); 191 | goto argdone; 192 | case 'v': 193 | buildopts.verbose = true; 194 | break; 195 | case 'w': 196 | warnflag(EARGF(usage())); 197 | break; 198 | default: 199 | usage(); 200 | } ARGEND 201 | argdone: 202 | if (!buildopts.maxjobs) { 203 | #ifdef _SC_NPROCESSORS_ONLN 204 | long nproc = sysconf(_SC_NPROCESSORS_ONLN); 205 | switch (nproc) { 206 | case -1: case 0: case 1: 207 | buildopts.maxjobs = 2; 208 | break; 209 | case 2: 210 | buildopts.maxjobs = 3; 211 | break; 212 | default: 213 | buildopts.maxjobs = nproc + 2; 214 | break; 215 | } 216 | #else 217 | buildopts.maxjobs = 2; 218 | #endif 219 | } 220 | 221 | buildopts.statusfmt = getenv("NINJA_STATUS"); 222 | if (!buildopts.statusfmt) 223 | buildopts.statusfmt = "[%s/%t] "; 224 | 225 | setvbuf(stdout, NULL, _IOLBF, 0); 226 | 227 | tries = 0; 228 | retry: 229 | /* (re-)initialize global graph, environment, and parse structures */ 230 | graphinit(); 231 | envinit(); 232 | parseinit(); 233 | 234 | /* parse the manifest */ 235 | parse(manifest, rootenv); 236 | 237 | if (tool) 238 | return tool->run(argc, argv); 239 | 240 | /* load the build log */ 241 | builddir = getbuilddir(); 242 | loginit(builddir); 243 | depsinit(builddir); 244 | 245 | /* rebuild the manifest if it's dirty */ 246 | n = nodeget(manifest, 0); 247 | if (n && n->gen) { 248 | buildadd(n); 249 | if (n->dirty) { 250 | build(); 251 | if (n->gen->flags & FLAG_DIRTY_OUT || n->gen->nprune > 0) { 252 | if (++tries > 100) 253 | fatal("manifest '%s' dirty after 100 tries", manifest); 254 | if (!buildopts.dryrun) 255 | goto retry; 256 | } 257 | /* manifest was pruned; reset state, then continue with build */ 258 | buildreset(); 259 | } 260 | } 261 | 262 | /* finally, build any specified targets or the default targets */ 263 | if (argc) { 264 | for (; *argv; ++argv) { 265 | n = nodeget(*argv, 0); 266 | if (!n) 267 | fatal("unknown target '%s'", *argv); 268 | buildadd(n); 269 | } 270 | } else { 271 | defaultnodes(buildadd); 272 | } 273 | build(); 274 | logclose(); 275 | depsclose(); 276 | 277 | return 0; 278 | } 279 | -------------------------------------------------------------------------------- /scan.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "scan.h" 8 | #include "util.h" 9 | 10 | struct evalstring **paths; 11 | size_t npaths; 12 | static struct buffer buf; 13 | 14 | void 15 | scaninit(struct scanner *s, const char *path) 16 | { 17 | s->path = path; 18 | s->line = 1; 19 | s->col = 1; 20 | s->f = fopen(path, "r"); 21 | if (!s->f) 22 | fatal("open %s:", path); 23 | s->chr = getc(s->f); 24 | } 25 | 26 | void 27 | scanclose(struct scanner *s) 28 | { 29 | fclose(s->f); 30 | } 31 | 32 | void 33 | scanerror(struct scanner *s, const char *fmt, ...) 34 | { 35 | extern const char *argv0; 36 | va_list ap; 37 | 38 | fprintf(stderr, "%s: %s:%d:%d: ", argv0, s->path, s->line, s->col); 39 | va_start(ap, fmt); 40 | vfprintf(stderr, fmt, ap); 41 | va_end(ap); 42 | putc('\n', stderr); 43 | exit(1); 44 | } 45 | 46 | static int 47 | next(struct scanner *s) 48 | { 49 | if (s->chr == '\n') { 50 | ++s->line; 51 | s->col = 1; 52 | } else { 53 | ++s->col; 54 | } 55 | s->chr = getc(s->f); 56 | 57 | return s->chr; 58 | } 59 | 60 | static int 61 | issimplevar(int c) 62 | { 63 | return isalnum(c) || c == '_' || c == '-'; 64 | } 65 | 66 | static int 67 | isvar(int c) 68 | { 69 | return issimplevar(c) || c == '.'; 70 | } 71 | 72 | static bool 73 | newline(struct scanner *s) 74 | { 75 | switch (s->chr) { 76 | case '\r': 77 | if (next(s) != '\n') 78 | scanerror(s, "expected '\\n' after '\\r'"); 79 | /* fallthrough */ 80 | case '\n': 81 | next(s); 82 | return true; 83 | } 84 | return false; 85 | } 86 | 87 | static bool 88 | singlespace(struct scanner *s) 89 | { 90 | switch (s->chr) { 91 | case '$': 92 | next(s); 93 | if (newline(s)) 94 | return true; 95 | ungetc(s->chr, s->f); 96 | s->chr = '$'; 97 | return false; 98 | case ' ': 99 | next(s); 100 | return true; 101 | } 102 | return false; 103 | } 104 | 105 | static bool 106 | space(struct scanner *s) 107 | { 108 | if (!singlespace(s)) 109 | return false; 110 | while (singlespace(s)) 111 | ; 112 | return true; 113 | } 114 | 115 | static bool 116 | comment(struct scanner *s) 117 | { 118 | if (s->chr != '#') 119 | return false; 120 | do next(s); 121 | while (!newline(s)); 122 | return true; 123 | } 124 | 125 | static void 126 | name(struct scanner *s) 127 | { 128 | buf.len = 0; 129 | while (isvar(s->chr)) { 130 | bufadd(&buf, s->chr); 131 | next(s); 132 | } 133 | if (!buf.len) 134 | scanerror(s, "expected name"); 135 | bufadd(&buf, '\0'); 136 | space(s); 137 | } 138 | 139 | int 140 | scankeyword(struct scanner *s, char **var) 141 | { 142 | /* must stay in sorted order */ 143 | static const struct { 144 | const char *name; 145 | int value; 146 | } keywords[] = { 147 | {"build", BUILD}, 148 | {"default", DEFAULT}, 149 | {"include", INCLUDE}, 150 | {"pool", POOL}, 151 | {"rule", RULE}, 152 | {"subninja", SUBNINJA}, 153 | }; 154 | int low = 0, high = LEN(keywords) - 1, mid, cmp; 155 | 156 | for (;;) { 157 | switch (s->chr) { 158 | case ' ': 159 | space(s); 160 | if (!comment(s) && !newline(s)) 161 | scanerror(s, "unexpected indent"); 162 | break; 163 | case '#': 164 | comment(s); 165 | break; 166 | case '\r': 167 | case '\n': 168 | newline(s); 169 | break; 170 | case EOF: 171 | return EOF; 172 | default: 173 | name(s); 174 | while (low <= high) { 175 | mid = (low + high) / 2; 176 | cmp = strcmp(buf.data, keywords[mid].name); 177 | if (cmp == 0) 178 | return keywords[mid].value; 179 | if (cmp < 0) 180 | high = mid - 1; 181 | else 182 | low = mid + 1; 183 | } 184 | *var = xmemdup(buf.data, buf.len); 185 | return VARIABLE; 186 | } 187 | } 188 | } 189 | 190 | char * 191 | scanname(struct scanner *s) 192 | { 193 | name(s); 194 | return xmemdup(buf.data, buf.len); 195 | } 196 | 197 | static void 198 | addstringpart(struct evalstring ***end, bool var) 199 | { 200 | struct evalstring *p; 201 | 202 | p = xmalloc(sizeof(*p)); 203 | p->next = NULL; 204 | **end = p; 205 | if (var) { 206 | bufadd(&buf, '\0'); 207 | p->var = xmemdup(buf.data, buf.len); 208 | } else { 209 | p->var = NULL; 210 | p->str = mkstr(buf.len); 211 | memcpy(p->str->s, buf.data, buf.len); 212 | p->str->s[buf.len] = '\0'; 213 | } 214 | *end = &p->next; 215 | buf.len = 0; 216 | } 217 | 218 | static void 219 | escape(struct scanner *s, struct evalstring ***end) 220 | { 221 | switch (s->chr) { 222 | case '$': 223 | case ' ': 224 | case ':': 225 | bufadd(&buf, s->chr); 226 | next(s); 227 | break; 228 | case '{': 229 | if (buf.len > 0) 230 | addstringpart(end, false); 231 | while (isvar(next(s))) 232 | bufadd(&buf, s->chr); 233 | if (s->chr != '}') 234 | scanerror(s, "invalid variable name"); 235 | next(s); 236 | addstringpart(end, true); 237 | break; 238 | case '\r': 239 | case '\n': 240 | newline(s); 241 | space(s); 242 | break; 243 | default: 244 | if (buf.len > 0) 245 | addstringpart(end, false); 246 | while (issimplevar(s->chr)) { 247 | bufadd(&buf, s->chr); 248 | next(s); 249 | } 250 | if (!buf.len) 251 | scanerror(s, "invalid $ escape"); 252 | addstringpart(end, true); 253 | } 254 | } 255 | 256 | struct evalstring * 257 | scanstring(struct scanner *s, bool path) 258 | { 259 | struct evalstring *str = NULL, **end = &str; 260 | 261 | buf.len = 0; 262 | for (;;) { 263 | switch (s->chr) { 264 | case '$': 265 | next(s); 266 | escape(s, &end); 267 | break; 268 | case ':': 269 | case '|': 270 | case ' ': 271 | if (path) 272 | goto out; 273 | /* fallthrough */ 274 | default: 275 | bufadd(&buf, s->chr); 276 | next(s); 277 | break; 278 | case '\r': 279 | case '\n': 280 | case EOF: 281 | goto out; 282 | } 283 | } 284 | out: 285 | if (buf.len > 0) 286 | addstringpart(&end, 0); 287 | if (path) 288 | space(s); 289 | return str; 290 | } 291 | 292 | void 293 | scanpaths(struct scanner *s) 294 | { 295 | static size_t max; 296 | struct evalstring *str; 297 | 298 | while ((str = scanstring(s, true))) { 299 | if (npaths == max) { 300 | max = max ? max * 2 : 32; 301 | paths = xreallocarray(paths, max, sizeof(paths[0])); 302 | } 303 | paths[npaths++] = str; 304 | } 305 | } 306 | 307 | void 308 | scanchar(struct scanner *s, int c) 309 | { 310 | if (s->chr != c) 311 | scanerror(s, "expected '%c'", c); 312 | next(s); 313 | space(s); 314 | } 315 | 316 | int 317 | scanpipe(struct scanner *s, int n) 318 | { 319 | if (s->chr != '|') 320 | return 0; 321 | next(s); 322 | if (s->chr != '|') { 323 | if (!(n & 1)) 324 | scanerror(s, "expected '||'"); 325 | space(s); 326 | return 1; 327 | } 328 | if (!(n & 2)) 329 | scanerror(s, "unexpected '||'"); 330 | next(s); 331 | space(s); 332 | return 2; 333 | } 334 | 335 | bool 336 | scanindent(struct scanner *s) 337 | { 338 | bool indent; 339 | 340 | for (;;) { 341 | indent = space(s); 342 | if (!comment(s)) 343 | return indent && !newline(s); 344 | } 345 | } 346 | 347 | void 348 | scannewline(struct scanner *s) 349 | { 350 | if (!newline(s)) 351 | scanerror(s, "expected newline"); 352 | } 353 | -------------------------------------------------------------------------------- /scan.h: -------------------------------------------------------------------------------- 1 | enum token { 2 | BUILD, 3 | DEFAULT, 4 | INCLUDE, 5 | POOL, 6 | RULE, 7 | SUBNINJA, 8 | VARIABLE, 9 | }; 10 | 11 | struct scanner { 12 | FILE *f; 13 | const char *path; 14 | int chr, line, col; 15 | }; 16 | 17 | extern struct evalstring **paths; 18 | extern size_t npaths; 19 | 20 | void scaninit(struct scanner *, const char *); 21 | void scanclose(struct scanner *); 22 | 23 | void scanerror(struct scanner *, const char *, ...); 24 | int scankeyword(struct scanner *, char **); 25 | char *scanname(struct scanner *); 26 | struct evalstring *scanstring(struct scanner *, _Bool); 27 | void scanpaths(struct scanner *); 28 | void scanchar(struct scanner *, int); 29 | int scanpipe(struct scanner *, int); 30 | _Bool scanindent(struct scanner *); 31 | void scannewline(struct scanner *); 32 | -------------------------------------------------------------------------------- /tool.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "arg.h" 8 | #include "env.h" 9 | #include "graph.h" 10 | #include "os.h" 11 | #include "parse.h" 12 | #include "tool.h" 13 | #include "util.h" 14 | 15 | static int 16 | cleanpath(struct string *path) 17 | { 18 | if (path) { 19 | if (remove(path->s) == 0) { 20 | printf("remove %s\n", path->s); 21 | } else if (errno != ENOENT) { 22 | warn("remove %s:", path->s); 23 | return -1; 24 | } 25 | } 26 | 27 | return 0; 28 | } 29 | 30 | static int 31 | cleanedge(struct edge *e) 32 | { 33 | int ret = 0; 34 | size_t i; 35 | 36 | for (i = 0; i < e->nout; ++i) { 37 | if (cleanpath(e->out[i]->path) < 0) 38 | ret = -1; 39 | } 40 | if (cleanpath(edgevar(e, "rspfile", false)) < 0) 41 | ret = -1; 42 | if (cleanpath(edgevar(e, "depfile", false)) < 0) 43 | ret = -1; 44 | 45 | return ret; 46 | } 47 | 48 | static int 49 | cleantarget(struct node *n) 50 | { 51 | int ret = 0; 52 | size_t i; 53 | 54 | if (!n->gen || n->gen->rule == &phonyrule) 55 | return 0; 56 | if (cleanpath(n->path) < 0) 57 | ret = -1; 58 | for (i = 0; i < n->gen->nin; ++i) { 59 | if (cleantarget(n->gen->in[i]) < 0) 60 | ret = -1; 61 | } 62 | 63 | return ret; 64 | } 65 | 66 | static int 67 | clean(int argc, char *argv[]) 68 | { 69 | int ret = 0; 70 | bool cleangen = false, cleanrule = false; 71 | struct edge *e; 72 | struct node *n; 73 | struct rule *r; 74 | 75 | ARGBEGIN { 76 | case 'g': 77 | cleangen = true; 78 | break; 79 | case 'r': 80 | cleanrule = true; 81 | break; 82 | default: 83 | fprintf(stderr, "usage: %s ... -t clean [-gr] [targets...]\n", argv0); 84 | return 2; 85 | } ARGEND 86 | 87 | if (cleanrule) { 88 | if (!argc) 89 | fatal("expected a rule to clean"); 90 | for (; *argv; ++argv) { 91 | r = envrule(rootenv, *argv); 92 | if (!r) { 93 | warn("unknown rule '%s'", *argv); 94 | ret = 1; 95 | continue; 96 | } 97 | for (e = alledges; e; e = e->allnext) { 98 | if (e->rule != r) 99 | continue; 100 | if (cleanedge(e) < 0) 101 | ret = 1; 102 | } 103 | } 104 | } else if (argc > 0) { 105 | for (; *argv; ++argv) { 106 | n = nodeget(*argv, 0); 107 | if (!n) { 108 | warn("unknown target '%s'", *argv); 109 | ret = 1; 110 | continue; 111 | } 112 | if (cleantarget(n) < 0) 113 | ret = 1; 114 | } 115 | } else { 116 | for (e = alledges; e; e = e->allnext) { 117 | if (e->rule == &phonyrule) 118 | continue; 119 | if (!cleangen && edgevar(e, "generator", true)) 120 | continue; 121 | if (cleanedge(e) < 0) 122 | ret = 1; 123 | } 124 | } 125 | 126 | return ret; 127 | } 128 | 129 | /* depth-first traversal */ 130 | static void 131 | targetcommands(struct node *n) 132 | { 133 | struct edge *e = n->gen; 134 | struct string *command; 135 | size_t i; 136 | 137 | if (!e || (e->flags & FLAG_WORK)) 138 | return; 139 | e->flags |= FLAG_WORK; 140 | for (i = 0; i < e->nin; ++i) 141 | targetcommands(e->in[i]); 142 | command = edgevar(e, "command", true); 143 | if (command && command->n) 144 | puts(command->s); 145 | } 146 | 147 | static int 148 | commands(int argc, char *argv[]) 149 | { 150 | struct node *n; 151 | 152 | if (argc > 1) { 153 | while (*++argv) { 154 | n = nodeget(*argv, 0); 155 | if (!n) 156 | fatal("unknown target '%s'", *argv); 157 | targetcommands(n); 158 | } 159 | } else { 160 | defaultnodes(targetcommands); 161 | } 162 | 163 | if (fflush(stdout) || ferror(stdout)) 164 | fatal("write failed"); 165 | 166 | return 0; 167 | } 168 | 169 | static void 170 | printquoted(const char *s, size_t n, bool join) 171 | { 172 | size_t i; 173 | char c; 174 | 175 | for (i = 0; i < n; ++i) { 176 | c = s[i]; 177 | switch (c) { 178 | case '"': 179 | case '\\': 180 | putchar('\\'); 181 | break; 182 | case '\n': 183 | if (join) 184 | c = ' '; 185 | break; 186 | case '\0': 187 | return; 188 | } 189 | putchar(c); 190 | } 191 | } 192 | 193 | static int 194 | compdb(int argc, char *argv[]) 195 | { 196 | char dir[1024], *p; 197 | struct edge *e; 198 | struct string *cmd, *rspfile, *content; 199 | bool expandrsp = false, first = true; 200 | int i; 201 | size_t off; 202 | 203 | ARGBEGIN { 204 | case 'x': 205 | expandrsp = true; 206 | break; 207 | default: 208 | fprintf(stderr, "usage: %s ... -t compdb [-x] [rules...]\n", argv0); 209 | return 2; 210 | } ARGEND 211 | 212 | osgetcwd(dir, sizeof(dir)); 213 | 214 | putchar('['); 215 | for (e = alledges; e; e = e->allnext) { 216 | if (e->nin == 0) 217 | continue; 218 | for (i = 0; i < argc; ++i) { 219 | if (strcmp(e->rule->name, argv[i]) == 0) { 220 | if (first) 221 | first = false; 222 | else 223 | putchar(','); 224 | 225 | printf("\n {\n \"directory\": \""); 226 | printquoted(dir, -1, false); 227 | 228 | printf("\",\n \"command\": \""); 229 | cmd = edgevar(e, "command", true); 230 | rspfile = expandrsp ? edgevar(e, "rspfile", true) : NULL; 231 | p = rspfile ? strstr(cmd->s, rspfile->s) : NULL; 232 | if (!p || p == cmd->s || p[-1] != '@') { 233 | printquoted(cmd->s, cmd->n, false); 234 | } else { 235 | off = p - cmd->s; 236 | printquoted(cmd->s, off - 1, false); 237 | content = edgevar(e, "rspfile_content", true); 238 | printquoted(content->s, content->n, true); 239 | off += rspfile->n; 240 | printquoted(cmd->s + off, cmd->n - off, false); 241 | } 242 | 243 | printf("\",\n \"file\": \""); 244 | printquoted(e->in[0]->path->s, -1, false); 245 | 246 | printf("\",\n \"output\": \""); 247 | printquoted(e->out[0]->path->s, -1, false); 248 | 249 | printf("\"\n }"); 250 | break; 251 | } 252 | } 253 | } 254 | puts("\n]"); 255 | 256 | if (fflush(stdout) || ferror(stdout)) 257 | fatal("write failed"); 258 | 259 | return 0; 260 | } 261 | 262 | static void 263 | graphnode(struct node *n) 264 | { 265 | struct edge *e = n->gen; 266 | size_t i; 267 | const char *style; 268 | 269 | printf("\"%p\" [label=\"", (void *)n); 270 | printquoted(n->path->s, n->path->n, false); 271 | printf("\"]\n"); 272 | 273 | if (!e || (e->flags & FLAG_WORK)) 274 | return; 275 | e->flags |= FLAG_WORK; 276 | 277 | for (i = 0; i < e->nin; ++i) 278 | graphnode(e->in[i]); 279 | 280 | if (e->nin == 1 && e->nout == 1) { 281 | printf("\"%p\" -> \"%p\" [label=\"%s\"]\n", (void *)e->in[0], (void *)e->out[0], e->rule->name); 282 | } else { 283 | printf("\"%p\" [label=\"%s\", shape=ellipse]\n", (void *)e, e->rule->name); 284 | for (i = 0; i < e->nout; ++i) 285 | printf("\"%p\" -> \"%p\"\n", (void *)e, (void *)e->out[i]); 286 | for (i = 0; i < e->nin; ++i) { 287 | style = i >= e->inorderidx ? " style=dotted" : ""; 288 | printf("\"%p\" -> \"%p\" [arrowhead=none%s]\n", (void *)e->in[i], (void *)e, style); 289 | } 290 | } 291 | } 292 | 293 | static int 294 | graph(int argc, char *argv[]) 295 | { 296 | struct node *n; 297 | 298 | puts("digraph ninja {"); 299 | puts("rankdir=\"LR\""); 300 | puts("node [fontsize=10, shape=box, height=0.25]"); 301 | puts("edge [fontsize=10]"); 302 | 303 | if (argc > 1) { 304 | while (*++argv) { 305 | n = nodeget(*argv, 0); 306 | if (!n) 307 | fatal("unknown target '%s'", *argv); 308 | graphnode(n); 309 | } 310 | } else { 311 | defaultnodes(graphnode); 312 | } 313 | 314 | puts("}"); 315 | 316 | if (fflush(stdout) || ferror(stdout)) 317 | fatal("write failed"); 318 | 319 | return 0; 320 | } 321 | 322 | static int 323 | query(int argc, char *argv[]) 324 | { 325 | struct node *n; 326 | struct edge *e; 327 | char *path; 328 | int i; 329 | size_t j, k; 330 | 331 | if (argc == 1) { 332 | fprintf(stderr, "usage: %s ... -t query target...\n", argv0); 333 | exit(2); 334 | } 335 | for (i = 1; i < argc; ++i) { 336 | path = argv[i]; 337 | n = nodeget(path, 0); 338 | if (!n) 339 | fatal("unknown target '%s'", path); 340 | printf("%s:\n", argv[i]); 341 | e = n->gen; 342 | if (e) { 343 | printf(" input: %s\n", e->rule->name); 344 | for (j = 0; j < e->nin; ++j) 345 | printf(" %s\n", e->in[j]->path->s); 346 | } 347 | puts(" outputs:"); 348 | for (j = 0; j < n->nuse; ++j) { 349 | e = n->use[j]; 350 | for (k = 0; k < e->nout; ++k) 351 | printf(" %s\n", e->out[k]->path->s); 352 | } 353 | } 354 | 355 | return 0; 356 | } 357 | 358 | static void 359 | targetsdepth(struct node *n, size_t depth, size_t indent) 360 | { 361 | struct edge *e = n->gen; 362 | size_t i; 363 | 364 | for (i = 0; i < indent; ++i) 365 | printf(" "); 366 | if (e) { 367 | printf("%s: %s\n", n->path->s, e->rule->name); 368 | if (depth != 1) { 369 | for (i = 0; i < e->nin; ++i) 370 | targetsdepth(e->in[i], depth - 1, indent + 1); 371 | } 372 | } else { 373 | puts(n->path->s); 374 | } 375 | } 376 | 377 | static void 378 | targetsusage(void) 379 | { 380 | fprintf(stderr, 381 | "usage: %s ... -t targets [depth [maxdepth]]\n" 382 | " %s ... -t targets rule [rulename]\n" 383 | " %s ... -t targets all\n", 384 | argv0, argv0, argv0); 385 | exit(2); 386 | } 387 | 388 | static int 389 | targets(int argc, char *argv[]) 390 | { 391 | struct edge *e; 392 | size_t depth = 1, i; 393 | char *end, *mode, *name; 394 | 395 | if (argc > 3) 396 | targetsusage(); 397 | mode = argv[1]; 398 | if (!mode || strcmp(mode, "depth") == 0) { 399 | if (argc == 3) { 400 | depth = strtol(argv[2], &end, 10); 401 | if (*end) 402 | targetsusage(); 403 | } 404 | for (e = alledges; e; e = e->allnext) { 405 | for (i = 0; i < e->nout; ++i) { 406 | if (e->out[i]->nuse == 0) 407 | targetsdepth(e->out[i], depth, 0); 408 | } 409 | } 410 | } else if (strcmp(mode, "rule") == 0) { 411 | name = argv[2]; 412 | for (e = alledges; e; e = e->allnext) { 413 | if (!name) { 414 | for (i = 0; i < e->nin; ++i) { 415 | if (!e->in[i]->gen) 416 | puts(e->in[i]->path->s); 417 | } 418 | } else if (strcmp(e->rule->name, name) == 0) { 419 | for (i = 0; i < e->nout; ++i) 420 | puts(e->out[i]->path->s); 421 | } 422 | } 423 | } else if (strcmp(mode, "all") == 0 && argc == 2) { 424 | for (e = alledges; e; e = e->allnext) { 425 | for (i = 0; i < e->nout; ++i) 426 | printf("%s: %s\n", e->out[i]->path->s, e->rule->name); 427 | } 428 | } else { 429 | targetsusage(); 430 | } 431 | 432 | if (fflush(stdout) || ferror(stdout)) 433 | fatal("write failed"); 434 | 435 | return 0; 436 | } 437 | 438 | static const struct tool tools[] = { 439 | {"clean", clean}, 440 | {"commands", commands}, 441 | {"compdb", compdb}, 442 | {"graph", graph}, 443 | {"query", query}, 444 | {"targets", targets}, 445 | }; 446 | 447 | const struct tool * 448 | toolget(const char *name) 449 | { 450 | const struct tool *t; 451 | size_t i; 452 | 453 | t = NULL; 454 | for (i = 0; i < LEN(tools); ++i) { 455 | if (strcmp(name, tools[i].name) == 0) { 456 | t = &tools[i]; 457 | break; 458 | } 459 | } 460 | if (!t) 461 | fatal("unknown tool '%s'", name); 462 | 463 | return t; 464 | } 465 | -------------------------------------------------------------------------------- /tool.h: -------------------------------------------------------------------------------- 1 | struct tool { 2 | const char *name; 3 | int (*run)(int, char *[]); 4 | }; 5 | 6 | const struct tool *toolget(const char *); 7 | -------------------------------------------------------------------------------- /tree.c: -------------------------------------------------------------------------------- 1 | /* Based on musl's src/search/tsearch.c, by Szabolcs Nagy. 2 | * See LICENSE file for copyright details. */ 3 | #include 4 | #include 5 | #include "tree.h" 6 | #include "util.h" 7 | 8 | #define MAXH (sizeof(void *) * 8 * 3 / 2) 9 | 10 | void 11 | deltree(struct treenode *n, void delkey(void *), void delval(void *)) 12 | { 13 | if (!n) 14 | return; 15 | if (delkey) 16 | delkey(n->key); 17 | if (delval) 18 | delval(n->value); 19 | deltree(n->child[0], delkey, delval); 20 | deltree(n->child[1], delkey, delval); 21 | free(n); 22 | } 23 | 24 | static inline int 25 | height(struct treenode *n) 26 | { 27 | return n ? n->height : 0; 28 | } 29 | 30 | static int 31 | rot(struct treenode **p, struct treenode *x, int dir /* deeper side */) 32 | { 33 | struct treenode *y = x->child[dir]; 34 | struct treenode *z = y->child[!dir]; 35 | int hx = x->height; 36 | int hz = height(z); 37 | 38 | if (hz > height(y->child[dir])) { 39 | /* 40 | * x 41 | * / \ dir z 42 | * A y / \ 43 | * / \ --> x y 44 | * z D /| |\ 45 | * / \ A B C D 46 | * B C 47 | */ 48 | x->child[dir] = z->child[!dir]; 49 | y->child[!dir] = z->child[dir]; 50 | z->child[!dir] = x; 51 | z->child[dir] = y; 52 | x->height = hz; 53 | y->height = hz; 54 | z->height = hz + 1; 55 | } else { 56 | /* 57 | * x y 58 | * / \ / \ 59 | * A y --> x D 60 | * / \ / \ 61 | * z D A z 62 | */ 63 | x->child[dir] = z; 64 | y->child[!dir] = x; 65 | x->height = hz + 1; 66 | y->height = hz + 2; 67 | z = y; 68 | } 69 | *p = z; 70 | return z->height - hx; 71 | } 72 | 73 | static int 74 | balance(struct treenode **p) 75 | { 76 | struct treenode *n = *p; 77 | int h0 = height(n->child[0]); 78 | int h1 = height(n->child[1]); 79 | 80 | if (h0 - h1 + 1u < 3u) { 81 | int old = n->height; 82 | n->height = h0 < h1 ? h1 + 1 : h0 + 1; 83 | return n->height - old; 84 | } 85 | return rot(p, n, h0 < h1); 86 | } 87 | 88 | struct treenode * 89 | treefind(struct treenode *n, const char *key) 90 | { 91 | int c; 92 | 93 | while (n) { 94 | c = strcmp(key, n->key); 95 | if (c == 0) 96 | return n; 97 | n = n->child[c > 0]; 98 | } 99 | return NULL; 100 | } 101 | 102 | void * 103 | treeinsert(struct treenode **rootp, char *key, void *value) 104 | { 105 | struct treenode **a[MAXH], *n = *rootp, *r; 106 | void *old; 107 | int i = 0, c; 108 | 109 | a[i++] = rootp; 110 | while (n) { 111 | c = strcmp(key, n->key); 112 | if (c == 0) { 113 | old = n->value; 114 | n->value = value; 115 | return old; 116 | } 117 | a[i++] = &n->child[c > 0]; 118 | n = n->child[c > 0]; 119 | } 120 | r = xmalloc(sizeof(*r)); 121 | r->key = key; 122 | r->value = value; 123 | r->child[0] = r->child[1] = NULL; 124 | r->height = 1; 125 | /* insert new node, rebalance ancestors. */ 126 | *a[--i] = r; 127 | while (i && balance(a[--i])) 128 | ; 129 | return NULL; 130 | } 131 | -------------------------------------------------------------------------------- /tree.h: -------------------------------------------------------------------------------- 1 | /* binary tree node, such that keys are sorted lexicographically for fast lookup */ 2 | struct treenode { 3 | char *key; 4 | void *value; 5 | struct treenode *child[2]; 6 | int height; 7 | }; 8 | 9 | /* free a tree and its children recursively, free keys and values with a function */ 10 | void deltree(struct treenode *, void(void *), void(void *)); 11 | /* search a binary tree for a key, return the key's value or NULL */ 12 | struct treenode *treefind(struct treenode *, const char *); 13 | /* insert into a binary tree a key and a value, replace and return the old value if the key already exists */ 14 | void *treeinsert(struct treenode **, char *, void *); 15 | -------------------------------------------------------------------------------- /util.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "os.h" 9 | #include "util.h" 10 | 11 | extern const char *argv0; 12 | 13 | static void 14 | vwarn(const char *fmt, va_list ap) 15 | { 16 | fprintf(stderr, "%s: ", argv0); 17 | vfprintf(stderr, fmt, ap); 18 | if (fmt[0] && fmt[strlen(fmt) - 1] == ':') { 19 | putc(' ', stderr); 20 | perror(NULL); 21 | } else { 22 | putc('\n', stderr); 23 | } 24 | } 25 | 26 | void 27 | warn(const char *fmt, ...) 28 | { 29 | va_list ap; 30 | 31 | va_start(ap, fmt); 32 | vwarn(fmt, ap); 33 | va_end(ap); 34 | } 35 | 36 | void 37 | fatal(const char *fmt, ...) 38 | { 39 | va_list ap; 40 | 41 | va_start(ap, fmt); 42 | vwarn(fmt, ap); 43 | va_end(ap); 44 | exit(1); 45 | } 46 | 47 | void * 48 | xmalloc(size_t n) 49 | { 50 | void *p; 51 | 52 | p = malloc(n); 53 | if (!p) 54 | fatal("malloc:"); 55 | 56 | return p; 57 | } 58 | 59 | static void * 60 | reallocarray_(void *p, size_t n, size_t m) 61 | { 62 | if (m && n > SIZE_MAX / m) { 63 | errno = ENOMEM; 64 | return NULL; 65 | } 66 | return realloc(p, n * m); 67 | } 68 | 69 | void * 70 | xreallocarray(void *p, size_t n, size_t m) 71 | { 72 | p = reallocarray_(p, n, m); 73 | if (!p) 74 | fatal("reallocarray:"); 75 | 76 | return p; 77 | } 78 | 79 | char * 80 | xmemdup(const char *s, size_t n) 81 | { 82 | char *p; 83 | 84 | p = xmalloc(n); 85 | memcpy(p, s, n); 86 | 87 | return p; 88 | } 89 | 90 | int 91 | xasprintf(char **s, const char *fmt, ...) 92 | { 93 | va_list ap; 94 | int ret; 95 | size_t n; 96 | 97 | va_start(ap, fmt); 98 | ret = vsnprintf(NULL, 0, fmt, ap); 99 | va_end(ap); 100 | if (ret < 0) 101 | fatal("vsnprintf:"); 102 | n = ret + 1; 103 | *s = xmalloc(n); 104 | va_start(ap, fmt); 105 | ret = vsnprintf(*s, n, fmt, ap); 106 | va_end(ap); 107 | if (ret < 0 || (size_t)ret >= n) 108 | fatal("vsnprintf:"); 109 | 110 | return ret; 111 | } 112 | 113 | void 114 | bufadd(struct buffer *buf, char c) 115 | { 116 | if (buf->len >= buf->cap) { 117 | buf->cap = buf->cap ? buf->cap * 2 : 1 << 8; 118 | buf->data = realloc(buf->data, buf->cap); 119 | if (!buf->data) 120 | fatal("realloc:"); 121 | } 122 | buf->data[buf->len++] = c; 123 | } 124 | 125 | struct string * 126 | mkstr(size_t n) 127 | { 128 | struct string *str; 129 | 130 | str = xmalloc(sizeof(*str) + n + 1); 131 | str->n = n; 132 | 133 | return str; 134 | } 135 | 136 | void 137 | delevalstr(void *ptr) 138 | { 139 | struct evalstring *str = ptr, *p; 140 | 141 | while (str) { 142 | p = str; 143 | str = str->next; 144 | if (p->var) 145 | free(p->var); 146 | else 147 | free(p->str); 148 | free(p); 149 | } 150 | } 151 | 152 | void 153 | canonpath(struct string *path) 154 | { 155 | char *component[60]; 156 | int n; 157 | char *s, *d, *end; 158 | 159 | if (path->n == 0) 160 | fatal("empty path"); 161 | s = d = path->s; 162 | end = path->s + path->n; 163 | n = 0; 164 | if (*s == '/') { 165 | ++s; 166 | ++d; 167 | } 168 | while (s < end) { 169 | switch (s[0]) { 170 | case '/': 171 | ++s; 172 | continue; 173 | case '.': 174 | switch (s[1]) { 175 | case '\0': case '/': 176 | s += 2; 177 | continue; 178 | case '.': 179 | if (s[2] != '/' && s[2] != '\0') 180 | break; 181 | if (n > 0) { 182 | d = component[--n]; 183 | } else { 184 | *d++ = s[0]; 185 | *d++ = s[1]; 186 | *d++ = s[2]; 187 | } 188 | s += 3; 189 | continue; 190 | } 191 | } 192 | if (n == LEN(component)) 193 | fatal("path has too many components: %s", path->s); 194 | component[n++] = d; 195 | while (*s != '/' && *s != '\0') 196 | *d++ = *s++; 197 | *d++ = *s++; 198 | } 199 | if (d == path->s) { 200 | *d++ = '.'; 201 | *d = '\0'; 202 | } else { 203 | *--d = '\0'; 204 | } 205 | path->n = d - path->s; 206 | } 207 | 208 | int 209 | writefile(const char *name, struct string *s) 210 | { 211 | FILE *f; 212 | int ret; 213 | 214 | f = fopen(name, "w"); 215 | if (!f) { 216 | warn("open %s:", name); 217 | return -1; 218 | } 219 | ret = 0; 220 | if (s && (fwrite(s->s, 1, s->n, f) != s->n || fflush(f) != 0)) { 221 | warn("write %s:", name); 222 | ret = -1; 223 | } 224 | fclose(f); 225 | 226 | return ret; 227 | } 228 | -------------------------------------------------------------------------------- /util.h: -------------------------------------------------------------------------------- 1 | struct buffer { 2 | char *data; 3 | size_t len, cap; 4 | }; 5 | 6 | struct string { 7 | size_t n; 8 | char s[]; 9 | }; 10 | 11 | /* an unevaluated string */ 12 | struct evalstring { 13 | char *var; 14 | struct string *str; 15 | struct evalstring *next; 16 | }; 17 | 18 | #define LEN(a) (sizeof(a) / sizeof((a)[0])) 19 | 20 | void warn(const char *, ...); 21 | void fatal(const char *, ...); 22 | 23 | void *xmalloc(size_t); 24 | void *xreallocarray(void *, size_t, size_t); 25 | char *xmemdup(const char *, size_t); 26 | int xasprintf(char **, const char *, ...); 27 | 28 | /* append a byte to a buffer */ 29 | void bufadd(struct buffer *buf, char c); 30 | 31 | /* allocates a new string with length n. n + 1 bytes are allocated for 32 | * s, but not initialized. */ 33 | struct string *mkstr(size_t n); 34 | 35 | /* delete an unevaluated string */ 36 | void delevalstr(void *); 37 | 38 | /* canonicalizes the given path by removing duplicate slashes, and 39 | * folding '/.' and 'foo/..' */ 40 | void canonpath(struct string *); 41 | /* write a new file with the given name and contents */ 42 | int writefile(const char *, struct string *); 43 | --------------------------------------------------------------------------------