1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
|
// Copyright (c) 2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Helper tool that is built and run during a build to pull strings from
// the GRD files and generate the InfoPlist.strings files needed for
// Mac OS X app bundles.
#import <Foundation/Foundation.h>
#include <stdio.h>
#include <unistd.h>
#include "base/data_pack.h"
#include "base/file_path.h"
#include "base/mac/scoped_nsautorelease_pool.h"
#include "base/scoped_ptr.h"
#include "base/string_piece.h"
#include "base/string_util.h"
#include "grit/chromium_strings.h"
namespace {
NSString* ApplicationVersionString(const char* version_file_path) {
NSError* error = nil;
NSString* path_string = [NSString stringWithUTF8String:version_file_path];
NSString* version_file =
[NSString stringWithContentsOfFile:path_string
encoding:NSUTF8StringEncoding
error:&error];
if (!version_file || error) {
fprintf(stderr, "Failed to load version file: %s\n",
[[error description] UTF8String]);
return nil;
}
int major = 0, minor = 0, build = 0, patch = 0;
NSScanner* scanner = [NSScanner scannerWithString:version_file];
if ([scanner scanString:@"MAJOR=" intoString:nil] &&
[scanner scanInt:&major] &&
[scanner scanString:@"MINOR=" intoString:nil] &&
[scanner scanInt:&minor] &&
[scanner scanString:@"BUILD=" intoString:nil] &&
[scanner scanInt:&build] &&
[scanner scanString:@"PATCH=" intoString:nil] &&
[scanner scanInt:&patch]) {
return [NSString stringWithFormat:@"%d.%d.%d.%d",
major, minor, build, patch];
}
fprintf(stderr, "Failed to parse version file\n");
return nil;
}
base::DataPack* LoadResourceDataPack(const char* dir_path,
const char* branding_strings_name,
const char* locale_name) {
base::DataPack* resource_pack = NULL;
NSString* resource_path = [NSString stringWithFormat:@"%s/%s_%s.pak",
dir_path, branding_strings_name, locale_name];
if (resource_path) {
FilePath resources_pak_path([resource_path fileSystemRepresentation]);
resource_pack = new base::DataPack;
bool success = resource_pack->Load(resources_pak_path);
if (!success) {
delete resource_pack;
resource_pack = NULL;
}
}
return resource_pack;
}
NSString* LoadStringFromDataPack(base::DataPack* data_pack,
const char* data_pack_lang,
uint32_t resource_id,
const char* resource_id_str) {
NSString* result = nil;
base::StringPiece data;
if (data_pack->GetStringPiece(resource_id, &data)) {
// Data pack encodes strings as UTF16.
result =
[[[NSString alloc] initWithBytes:data.data()
length:data.length()
encoding:NSUTF16LittleEndianStringEncoding]
autorelease];
}
if (!result) {
fprintf(stderr, "ERROR: failed to load string %s for lang %s\n",
resource_id_str, data_pack_lang);
exit(1);
}
return result;
}
// Escape quotes, newlines, etc so there are no errors when the strings file
// is parsed.
NSString* EscapeForStringsFileValue(NSString* str) {
NSMutableString* worker = [NSMutableString stringWithString:str];
// Since this is a build tool, we don't really worry about making this
// the most efficient code.
// Backslash first since we need to do it before we put in all the others
[worker replaceOccurrencesOfString:@"\\"
withString:@"\\\\"
options:NSLiteralSearch
range:NSMakeRange(0, [worker length])];
// Now the rest of them.
[worker replaceOccurrencesOfString:@"\n"
withString:@"\\n"
options:NSLiteralSearch
range:NSMakeRange(0, [worker length])];
[worker replaceOccurrencesOfString:@"\r"
withString:@"\\r"
options:NSLiteralSearch
range:NSMakeRange(0, [worker length])];
[worker replaceOccurrencesOfString:@"\t"
withString:@"\\t"
options:NSLiteralSearch
range:NSMakeRange(0, [worker length])];
[worker replaceOccurrencesOfString:@"\""
withString:@"\\\""
options:NSLiteralSearch
range:NSMakeRange(0, [worker length])];
return [[worker copy] autorelease];
}
// The valid types for the -t arg
const char* kAppType_Main = "main"; // Main app
const char* kAppType_Helper = "helper"; // Helper app
} // namespace
int main(int argc, char* const argv[]) {
base::mac::ScopedNSAutoreleasePool autorelease_pool;
const char* version_file_path = NULL;
const char* grit_output_dir = NULL;
const char* branding_strings_name = NULL;
const char* output_dir = NULL;
const char* app_type = kAppType_Main;
// Process the args
int ch;
while ((ch = getopt(argc, argv, "t:v:g:b:o:")) != -1) {
switch (ch) {
case 't':
app_type = optarg;
break;
case 'v':
version_file_path = optarg;
break;
case 'g':
grit_output_dir = optarg;
break;
case 'b':
branding_strings_name = optarg;
break;
case 'o':
output_dir = optarg;
break;
default:
fprintf(stderr, "ERROR: bad command line arg\n");
exit(1);
break;
}
}
argc -= optind;
argv += optind;
#define CHECK_ARG(a, b) \
do { \
if ((a)) { \
fprintf(stderr, "ERROR: " b "\n"); \
exit(1); \
} \
} while (false)
// Check our args
CHECK_ARG(!version_file_path, "Missing VERSION file path");
CHECK_ARG(!grit_output_dir, "Missing grit output dir path");
CHECK_ARG(!output_dir, "Missing path to write InfoPlist.strings files");
CHECK_ARG(!branding_strings_name, "Missing branding strings file name");
CHECK_ARG(argc == 0, "Missing language list");
CHECK_ARG((strcmp(app_type, kAppType_Main) != 0 &&
strcmp(app_type, kAppType_Helper) != 0),
"Unknown app type");
char* const* lang_list = argv;
int lang_list_count = argc;
// Parse the version file and build our string
NSString* version_string = ApplicationVersionString(version_file_path);
if (!version_string) {
fprintf(stderr, "ERROR: failed to get a version string");
exit(1);
}
NSFileManager* fm = [NSFileManager defaultManager];
for (int loop = 0; loop < lang_list_count; ++loop) {
const char* cur_lang = lang_list[loop];
// Open the branded string pak file
scoped_ptr<base::DataPack> branded_data_pack(
LoadResourceDataPack(grit_output_dir,
branding_strings_name,
cur_lang));
if (branded_data_pack.get() == NULL) {
fprintf(stderr, "ERROR: Failed to load branded pak for language: %s\n",
cur_lang);
exit(1);
}
uint32_t name_id = IDS_PRODUCT_NAME;
const char* name_id_str = "IDS_PRODUCT_NAME";
uint32_t short_name_id = IDS_SHORT_PRODUCT_NAME;
const char* short_name_id_str = "IDS_SHORT_PRODUCT_NAME";
if (strcmp(app_type, kAppType_Helper) == 0) {
name_id = IDS_HELPER_NAME;
name_id_str = "IDS_HELPER_NAME";
short_name_id = IDS_SHORT_HELPER_NAME;
short_name_id_str = "IDS_SHORT_HELPER_NAME";
}
// Fetch the strings
NSString* name =
LoadStringFromDataPack(branded_data_pack.get(), cur_lang,
name_id, name_id_str);
NSString* short_name =
LoadStringFromDataPack(branded_data_pack.get(), cur_lang,
short_name_id, short_name_id_str);
NSString* copyright =
LoadStringFromDataPack(branded_data_pack.get(), cur_lang,
IDS_ABOUT_VERSION_COPYRIGHT,
"IDS_ABOUT_VERSION_COPYRIGHT");
// For now, assume this is ok for all languages. If we need to, this could
// be moved into generated_resources.grd and fetched.
NSString *get_info = [NSString stringWithFormat:@"%@ %@, %@",
name, version_string, copyright];
// Generate the InfoPlist.strings file contents
NSString* strings_file_contents_string =
[NSString stringWithFormat:
@"CFBundleDisplayName = \"%@\";\n"
@"CFBundleGetInfoString = \"%@\";\n"
@"CFBundleName = \"%@\";\n"
@"NSHumanReadableCopyright = \"%@\";\n",
EscapeForStringsFileValue(name),
EscapeForStringsFileValue(get_info),
EscapeForStringsFileValue(short_name),
EscapeForStringsFileValue(copyright)];
// We set up Xcode projects expecting strings files to be UTF8, so make
// sure we write the data in that form. When Xcode copies them it will
// put them final runtime encoding.
NSData* strings_file_contents_utf8 =
[strings_file_contents_string dataUsingEncoding:NSUTF8StringEncoding];
if ([strings_file_contents_utf8 length] == 0) {
fprintf(stderr, "ERROR: failed to get the utf8 encoding of the strings "
"file for language: %s\n", cur_lang);
exit(1);
}
// For Cocoa to find the locale at runtime, it needs to use '_' instead of
// '-' (http://crbug.com/20441). Also, 'en-US' should be represented
// simply as 'en' (http://crbug.com/19165, http://crbug.com/25578).
NSString* cur_lang_ns = [NSString stringWithUTF8String:cur_lang];
if ([cur_lang_ns isEqualToString:@"en-US"]) {
cur_lang_ns = @"en";
}
cur_lang_ns = [cur_lang_ns stringByReplacingOccurrencesOfString:@"-"
withString:@"_"];
// Make sure the lproj we write to exists
NSString *lproj_name = [NSString stringWithFormat:@"%@.lproj", cur_lang_ns];
NSString *output_path =
[[NSString stringWithUTF8String:output_dir]
stringByAppendingPathComponent:lproj_name];
NSError* error = nil;
if (![fm fileExistsAtPath:output_path] &&
![fm createDirectoryAtPath:output_path
withIntermediateDirectories:YES
attributes:nil
error:&error]) {
fprintf(stderr, "ERROR: '%s' didn't exist or we failed to create it\n",
[output_path UTF8String]);
exit(1);
}
// Write out the file
output_path =
[output_path stringByAppendingPathComponent:@"InfoPlist.strings"];
if (![strings_file_contents_utf8 writeToFile:output_path
atomically:YES]) {
fprintf(stderr, "ERROR: Failed to write out '%s'\n",
[output_path UTF8String]);
exit(1);
}
}
return 0;
}
|