]> git.saurik.com Git - apple/xnu.git/blame - SETUP/json_compilation_db/json_compilation_db.c
xnu-6153.11.26.tar.gz
[apple/xnu.git] / SETUP / json_compilation_db / json_compilation_db.c
CommitLineData
3e170ce0
A
1/*
2 * Copyright (c) 2013 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24/*
25 * json_compilation_db is a helper tool that takes a compiler invocation, and
26 * appends it in JSON format to the specified database.
27 */
28
29#include <stdio.h>
30#include <stdlib.h>
31#include <unistd.h>
32#include <string.h>
33#include <stdbool.h>
34#include <errno.h>
35#include <err.h>
36#include <sysexits.h>
37
38#include <sys/stat.h>
39#include <sys/fcntl.h>
40#include <sys/param.h>
41
42void usage(void);
43char *escape_string(const char *);
44
45/*
46 * We support appending to two databases.
47 *
48 * 0-byte: ""
49 *
50 * or
51 *
52 * "["
53 * "{"
54 * " ..."
55 * "}"
56 * "]"
57 */
58
0a7de745
A
59int
60main(int argc, char * argv[])
3e170ce0
A
61{
62 struct stat sb;
63 int ret;
64 int dstfd;
65 FILE *dst = NULL;
66 const char *json_output = NULL;
67 const char *cwd = NULL;
68 const char *input_file = NULL;
69 char start[2];
70 size_t read_bytes;
71 int i;
72 size_t input_file_len;
73
74 if (argc < 5) {
75 usage();
76 }
77
78 json_output = argv[1];
79 cwd = argv[2];
80 input_file = argv[3];
81
82 argv += 4;
83 argc -= 4;
84
85 input_file_len = strlen(input_file);
0a7de745
A
86 if (!(input_file_len > 2 && 0 == strcmp(".c", input_file + input_file_len - 2)) &&
87 !(input_file_len > 3 && 0 == strcmp(".cp", input_file + input_file_len - 3)) &&
88 !(input_file_len > 4 && 0 == strcmp(".cpp", input_file + input_file_len - 4))) {
3e170ce0
A
89 /* Not a C/C++ file, just skip it */
90 return 0;
91 }
92
93 dstfd = open(json_output, O_RDWR | O_CREAT | O_EXLOCK, DEFFILEMODE);
0a7de745 94 if (dstfd < 0) {
3e170ce0 95 err(EX_NOINPUT, "open(%s)", json_output);
0a7de745 96 }
3e170ce0
A
97
98 ret = fstat(dstfd, &sb);
0a7de745 99 if (ret < 0) {
3e170ce0 100 err(EX_NOINPUT, "fstat(%s)", json_output);
0a7de745 101 }
3e170ce0 102
0a7de745 103 if (!S_ISREG(sb.st_mode)) {
3e170ce0 104 err(EX_USAGE, "%s is not a regular file", json_output);
0a7de745 105 }
3e170ce0
A
106
107 dst = fdopen(dstfd, "w+");
0a7de745 108 if (dst == NULL) {
3e170ce0 109 err(EX_UNAVAILABLE, "fdopen");
0a7de745 110 }
3e170ce0 111
0a7de745
A
112 read_bytes = fread(start, sizeof(start[0]), sizeof(start) / sizeof(start[0]), dst);
113 if ((read_bytes != sizeof(start)) || (0 != memcmp(start, "[\n", sizeof(start) / sizeof(start[0])))) {
3e170ce0
A
114 /* no JSON start, we don't really care why */
115 ret = fseeko(dst, 0, SEEK_SET);
0a7de745 116 if (ret < 0) {
3e170ce0 117 err(EX_UNAVAILABLE, "fseeko");
0a7de745 118 }
3e170ce0
A
119
120 ret = fputs("[", dst);
0a7de745 121 if (ret < 0) {
3e170ce0 122 err(EX_UNAVAILABLE, "fputs");
0a7de745 123 }
3e170ce0
A
124 } else {
125 /* has at least two bytes at the start. Seek to 3 bytes before the end */
126 ret = fseeko(dst, -3, SEEK_END);
0a7de745 127 if (ret < 0) {
3e170ce0 128 err(EX_UNAVAILABLE, "fseeko");
0a7de745 129 }
3e170ce0
A
130
131 ret = fputs(",", dst);
0a7de745 132 if (ret < 0) {
3e170ce0 133 err(EX_UNAVAILABLE, "fputs");
0a7de745 134 }
3e170ce0
A
135 }
136
137 fprintf(dst, "\n");
138 fprintf(dst, "{\n");
139 fprintf(dst, " \"directory\": \"%s\",\n", cwd);
140 fprintf(dst, " \"file\": \"%s\",\n", input_file);
141 fprintf(dst, " \"command\": \"");
0a7de745 142 for (i = 0; i < argc; i++) {
3e170ce0 143 bool needs_escape = strchr(argv[i], '\\') || strchr(argv[i], '"') || strchr(argv[i], ' ');
0a7de745 144
3e170ce0
A
145 if (needs_escape) {
146 char *escaped_string = escape_string(argv[i]);
147 fprintf(dst, "%s\\\"%s\\\"", i == 0 ? "" : " ", escaped_string);
148 free(escaped_string);
149 } else {
150 fprintf(dst, "%s%s", i == 0 ? "" : " ", argv[i]);
151 }
152 }
153 fprintf(dst, "\"\n");
154 fprintf(dst, "}\n");
155 fprintf(dst, "]\n");
156
157 ret = fclose(dst);
0a7de745 158 if (ret < 0) {
3e170ce0 159 err(EX_UNAVAILABLE, "fclose");
0a7de745 160 }
3e170ce0
A
161
162 return 0;
163}
164
0a7de745
A
165void
166usage(void)
3e170ce0
A
167{
168 fprintf(stderr, "Usage: %s <json_output> <cwd> <input_file> <compiler> [<invocation> ...]\n", getprogname());
169 exit(EX_USAGE);
170}
171
172/*
173 * A valid JSON string can't contain \ or ", so we look for these in our argv[] array (which
174 * our parent shell would have done shell metacharacter evaluation on, and escape just these.
175 * The entire string is put in \" escaped quotes to handle spaces that are valid JSON
176 * but should be used for grouping when running the compiler for real.
177 */
178char *
179escape_string(const char *input)
180{
181 size_t len = strlen(input);
182 size_t i, j;
183 char *output = malloc(len * 4 + 1);
184
0a7de745 185 for (i = 0, j = 0; i < len; i++) {
3e170ce0
A
186 char ch = input[i];
187
188 if (ch == '\\' || ch == '"') {
189 output[j++] = '\\';
190 output[j++] = '\\'; /* output \\ in JSON, which the final shell will see as \ */
191 output[j++] = '\\'; /* escape \ or ", which the final shell will see and pass to the compiler */
192 }
193 output[j++] = ch;
194 }
195
196 output[j] = '\0';
197
198 return output;
199}