1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package com.github.maven_nar.cpptasks.compiler;
21
22 import java.io.File;
23 import java.io.IOException;
24 import java.util.Collections;
25 import java.util.Enumeration;
26 import java.util.Vector;
27 import java.util.ArrayList;
28
29 import org.apache.tools.ant.BuildException;
30 import org.apache.tools.ant.types.Environment;
31 import org.apache.commons.io.FilenameUtils;
32
33 import com.github.maven_nar.cpptasks.CCTask;
34 import com.github.maven_nar.cpptasks.CUtil;
35 import com.github.maven_nar.cpptasks.CompilerDef;
36 import com.github.maven_nar.cpptasks.OptimizationEnum;
37 import com.github.maven_nar.cpptasks.ProcessorDef;
38 import com.github.maven_nar.cpptasks.ProcessorParam;
39 import com.github.maven_nar.cpptasks.TargetDef;
40 import com.github.maven_nar.cpptasks.VersionInfo;
41 import com.github.maven_nar.cpptasks.types.CommandLineArgument;
42 import com.github.maven_nar.cpptasks.types.UndefineArgument;
43 import com.google.common.collect.ObjectArrays;
44 import org.apache.tools.ant.util.FileUtils;
45
46
47
48
49
50
51
52 public abstract class CommandLineCompiler extends AbstractCompiler {
53
54 private static final String CCACHE_CMD = "ccache";
55 private String command;
56 private String prefix;
57 private final Environment env;
58 private String identifier;
59 private final String identifierArg;
60 private final boolean libtool;
61 private final CommandLineCompiler libtoolCompiler;
62 private final boolean newEnvironment;
63
64 protected CommandLineCompiler(final String command, final String identifierArg, final String[] sourceExtensions,
65 final String[] headerExtensions,
66 final String outputSuffix, final boolean libtool, final CommandLineCompiler libtoolCompiler,
67 final boolean newEnvironment, final Environment env) {
68 super(sourceExtensions, headerExtensions, outputSuffix);
69 this.command = command;
70 if (libtool && libtoolCompiler != null) {
71 throw new java.lang.IllegalArgumentException("libtoolCompiler should be null when libtool is true");
72 }
73 this.libtool = libtool;
74 this.libtoolCompiler = libtoolCompiler;
75 this.identifierArg = identifierArg;
76 this.newEnvironment = newEnvironment;
77 this.env = env;
78 }
79
80 abstract protected void addImpliedArgs(Vector<String> args, boolean debug, boolean multithreaded, boolean exceptions,
81 LinkType linkType, Boolean rtti, OptimizationEnum optimization);
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100 protected void addIncludes(final String baseDirPath, final File[] includeDirs, final Vector<String> args,
101 final Vector<String> relativeArgs, final StringBuffer includePathId, final boolean isSystem) {
102 for (final File includeDir : includeDirs) {
103 args.addElement(getIncludeDirSwitch(includeDir.getAbsolutePath(), isSystem));
104 if (relativeArgs != null) {
105 final String relative = CUtil.getRelativePath(baseDirPath, includeDir);
106 relativeArgs.addElement(getIncludeDirSwitch(relative, isSystem));
107 if (includePathId != null) {
108 if (includePathId.length() == 0) {
109 includePathId.append("/I");
110 } else {
111 includePathId.append(" /I");
112 }
113 includePathId.append(relative);
114 }
115 }
116 }
117 }
118
119 abstract protected void addWarningSwitch(Vector<String> args, int warnings);
120
121 protected void buildDefineArguments(final CompilerDef[] defs, final Vector<String> args) {
122
123
124
125 UndefineArgument[] merged = defs[0].getActiveDefines();
126 for (int i = 1; i < defs.length; i++) {
127
128
129
130 merged = UndefineArgument.merge(defs[i].getActiveDefines(), merged);
131 }
132 final StringBuffer buf = new StringBuffer(30);
133 for (final UndefineArgument current : merged) {
134 buf.setLength(0);
135 if (current.isDefine()) {
136 getDefineSwitch(buf, current.getName(), current.getValue());
137 } else {
138 getUndefineSwitch(buf, current.getName());
139 }
140 args.addElement(buf.toString());
141 }
142 }
143
144 @Override
145 public String[] getOutputFileNames(final String inputFile, final VersionInfo versionInfo) {
146
147
148
149 if (bid(inputFile) > 1) {
150 final String baseName = getBaseOutputName(inputFile);
151 final File standardisedFile = new File(inputFile);
152 try {
153 return new String[] {
154 baseName + FilenameUtils.EXTENSION_SEPARATOR + Integer.toHexString(standardisedFile.getCanonicalPath().hashCode()) + getOutputSuffix()
155 };
156 } catch (IOException e) {
157 throw new BuildException("Source file not found", e);
158 }
159 }
160 return new String[0];
161 }
162
163
164
165
166
167 public void compile(final CCTask task, final File outputDir, final String[] sourceFiles, String[] args,
168 final String[] endArgs, final boolean relentless, final CommandLineCompilerConfiguration config,
169 final ProgressMonitor monitor) throws BuildException {
170 BuildException exc = null;
171
172
173
174 String command = getCommandWithPath(config);
175 if (config.isUseCcache()) {
176
177
178 final String compilerCommand = command;
179 command = CCACHE_CMD;
180 args = ObjectArrays.concat(compilerCommand, args);
181 }
182 int baseLength = command.length() + args.length + endArgs.length;
183 if (this.libtool) {
184 baseLength += 8;
185 }
186 for (final String arg : args) {
187 baseLength += arg.length();
188 }
189 for (final String endArg : endArgs) {
190 baseLength += endArg.length();
191 }
192 if (baseLength > getMaximumCommandLength()) {
193 throw new BuildException("Command line is over maximum length without specifying source file");
194 }
195
196
197
198 final int maxInputFilesPerCommand = getMaximumInputFilesPerCommand();
199 final int argumentCountPerInputFile = getArgumentCountPerInputFile();
200 for (int sourceIndex = 0; sourceIndex < sourceFiles.length;) {
201 int cmdLength = baseLength;
202 int firstFileNextExec;
203 for (firstFileNextExec = sourceIndex; firstFileNextExec < sourceFiles.length
204 && firstFileNextExec - sourceIndex < maxInputFilesPerCommand; firstFileNextExec++) {
205 cmdLength += getTotalArgumentLengthForInputFile(outputDir, sourceFiles[firstFileNextExec]);
206 if (cmdLength >= getMaximumCommandLength()) {
207 break;
208 }
209 }
210 if (firstFileNextExec == sourceIndex) {
211 throw new BuildException("Extremely long file name, can't fit on command line");
212 }
213
214 ArrayList<String> commandlinePrefix = new ArrayList<>();
215 if (this.libtool) {
216 commandlinePrefix.add("libtool");
217 }
218 commandlinePrefix.add(command);
219 Collections.addAll(commandlinePrefix, args);
220
221 int retval = 0;
222 for (int j = sourceIndex; j < firstFileNextExec; j++) {
223 ArrayList<String> commandlineSuffix = new ArrayList<>();
224
225 for (int k = 0; k < argumentCountPerInputFile; k++) {
226 commandlineSuffix.add(getInputFileArgument(outputDir, sourceFiles[j], k));
227 }
228 Collections.addAll(commandlineSuffix, endArgs);
229
230 ArrayList<String> commandline = new ArrayList<>(commandlinePrefix);
231 commandline.addAll(commandlineSuffix);
232 final int ret = runCommand(task, workDir,
233 commandline.toArray(new String[commandline.size()]));
234 if (ret != 0) { retval = ret; }
235 }
236 if (monitor != null) {
237 final String[] fileNames = new String[firstFileNextExec - sourceIndex];
238
239 System.arraycopy(sourceFiles, sourceIndex + 0, fileNames, 0, fileNames.length);
240 monitor.progress(fileNames);
241 }
242
243
244
245
246 if (retval != 0 && exc == null) {
247
248
249
250 exc = new BuildException(getCommandWithPath(config) + " failed with return code " + retval, task.getLocation());
251
252
253
254
255 if (!relentless) {
256 throw exc;
257 }
258 }
259 sourceIndex = firstFileNextExec;
260 }
261
262
263
264 if (exc != null) {
265 throw exc;
266 }
267 }
268
269 @Override
270 protected CompilerConfiguration createConfiguration(final CCTask task, final LinkType linkType,
271 final ProcessorDef[] baseDefs, final CompilerDef specificDef, final TargetDef targetPlatform,
272 final VersionInfo versionInfo) {
273
274 this.prefix = specificDef.getCompilerPrefix();
275 this.objDir = task.getObjdir();
276 final Vector<String> args = new Vector<>();
277 final CompilerDef[] defaultProviders = new CompilerDef[baseDefs.length + 1];
278 for (int i = 0; i < baseDefs.length; i++) {
279 defaultProviders[i + 1] = (CompilerDef) baseDefs[i];
280 }
281 defaultProviders[0] = specificDef;
282 final Vector<CommandLineArgument> cmdArgs = new Vector<>();
283
284
285
286
287 CommandLineArgument[] commandArgs;
288 for (int i = defaultProviders.length - 1; i >= 0; i--) {
289 commandArgs = defaultProviders[i].getActiveProcessorArgs();
290 for (final CommandLineArgument commandArg : commandArgs) {
291 if (commandArg.getLocation() == 0) {
292 String arg = commandArg.getValue();
293 if (isWindows() && arg.matches(".*[ \"].*")) {
294
295 arg = "\"" + arg.replaceAll("[\\\\\"]", "\\\\$0") + "\"";
296 }
297 args.addElement(arg);
298 } else {
299 cmdArgs.addElement(commandArg);
300 }
301 }
302 }
303 final Vector<ProcessorParam> params = new Vector<>();
304
305
306
307 ProcessorParam[] paramArray;
308 for (int i = defaultProviders.length - 1; i >= 0; i--) {
309 paramArray = defaultProviders[i].getActiveProcessorParams();
310 Collections.addAll(params, paramArray);
311 }
312 paramArray = params.toArray(new ProcessorParam[params.size()]);
313
314 if (specificDef.isClearDefaultOptions() == false) {
315 final boolean multithreaded = specificDef.getMultithreaded(defaultProviders, 1);
316 final boolean debug = specificDef.getDebug(baseDefs, 0);
317 final boolean exceptions = specificDef.getExceptions(defaultProviders, 1);
318 final Boolean rtti = specificDef.getRtti(defaultProviders, 1);
319 final OptimizationEnum optimization = specificDef.getOptimization(defaultProviders, 1);
320 this.addImpliedArgs(args, debug, multithreaded, exceptions, linkType, rtti, optimization);
321 }
322
323
324
325
326 buildDefineArguments(defaultProviders, args);
327 final int warnings = specificDef.getWarnings(defaultProviders, 0);
328 addWarningSwitch(args, warnings);
329 Enumeration<CommandLineArgument> argEnum = cmdArgs.elements();
330 int endCount = 0;
331 while (argEnum.hasMoreElements()) {
332 final CommandLineArgument arg = argEnum.nextElement();
333 switch (arg.getLocation()) {
334 case 1:
335 args.addElement(arg.getValue());
336 break;
337 case 2:
338 endCount++;
339 break;
340 }
341 }
342 final String[] endArgs = new String[endCount];
343 argEnum = cmdArgs.elements();
344 int index = 0;
345 while (argEnum.hasMoreElements()) {
346 final CommandLineArgument arg = argEnum.nextElement();
347 if (arg.getLocation() == 2) {
348 endArgs[index++] = arg.getValue();
349 }
350 }
351
352
353
354
355
356 final Vector<String> relativeArgs = (Vector) args.clone();
357
358
359
360 final StringBuffer includePathIdentifier = new StringBuffer();
361 final File baseDir = specificDef.getProject().getBaseDir();
362 String baseDirPath;
363 try {
364 baseDirPath = baseDir.getCanonicalPath();
365 } catch (final IOException ex) {
366 baseDirPath = baseDir.toString();
367 }
368 final Vector<String> includePath = new Vector<>();
369 final Vector<String> sysIncludePath = new Vector<>();
370 for (int i = defaultProviders.length - 1; i >= 0; i--) {
371 String[] incPath = defaultProviders[i].getActiveIncludePaths();
372 for (final String element : incPath) {
373 includePath.addElement(element);
374 }
375 incPath = defaultProviders[i].getActiveSysIncludePaths();
376 for (final String element : incPath) {
377 sysIncludePath.addElement(element);
378 }
379 }
380 final File[] incPath = new File[includePath.size()];
381 for (int i = 0; i < includePath.size(); i++) {
382 incPath[i] = new File(includePath.elementAt(i));
383 }
384 final File[] sysIncPath = new File[sysIncludePath.size()];
385 for (int i = 0; i < sysIncludePath.size(); i++) {
386 sysIncPath[i] = new File(sysIncludePath.elementAt(i));
387 }
388 addIncludes(baseDirPath, incPath, args, relativeArgs, includePathIdentifier, false);
389 addIncludes(baseDirPath, sysIncPath, args, null, null, true);
390 final StringBuffer buf = new StringBuffer(getIdentifier());
391 for (int i = 0; i < relativeArgs.size(); i++) {
392 buf.append(' ');
393 buf.append(relativeArgs.elementAt(i));
394 }
395 for (final String endArg : endArgs) {
396 buf.append(' ');
397 buf.append(endArg);
398 }
399 final String configId = buf.toString();
400 final String[] argArray = new String[args.size()];
401 args.copyInto(argArray);
402 final boolean rebuild = specificDef.getRebuild(baseDefs, 0);
403 final File[] envIncludePath = getEnvironmentIncludePath();
404 final String path = specificDef.getToolPath();
405
406 CommandLineCompiler compiler = this;
407 Environment environment = specificDef.getEnv();
408 if (environment == null) {
409 for (final ProcessorDef baseDef : baseDefs) {
410 environment = baseDef.getEnv();
411 if (environment != null) {
412 compiler = (CommandLineCompiler) compiler.changeEnvironment(baseDef.isNewEnvironment(), environment);
413 }
414 }
415 } else {
416 compiler = (CommandLineCompiler) compiler.changeEnvironment(specificDef.isNewEnvironment(), environment);
417 }
418 return new CommandLineCompilerConfiguration(compiler, configId, incPath, sysIncPath, envIncludePath,
419 includePathIdentifier.toString(), argArray, paramArray, rebuild, endArgs, path, specificDef.getCcache());
420 }
421
422 protected int getArgumentCountPerInputFile() {
423 return 1;
424 }
425
426 protected final String getCommand() {
427 if (this.prefix != null && (!this.prefix.isEmpty())) {
428 return this.prefix + this.command;
429 } else {
430 return this.command;
431 }
432 }
433
434 public String getCommandWithPath(final CommandLineCompilerConfiguration config) {
435 if (config.getCommandPath() != null) {
436 final File command = new File(config.getCommandPath(), this.getCommand());
437 try {
438 return command.getCanonicalPath();
439 } catch (final IOException e) {
440 e.printStackTrace();
441 return command.getAbsolutePath();
442 }
443 } else {
444 return this.getCommand();
445 }
446 }
447
448 abstract protected void getDefineSwitch(StringBuffer buffer, String define, String value);
449
450 protected abstract File[] getEnvironmentIncludePath();
451
452 @Override
453 public String getIdentifier() {
454 if (this.identifier == null) {
455 if (this.identifierArg == null) {
456 this.identifier = getIdentifier(new String[] {
457 this.getCommand()
458 }, this.getCommand());
459 } else {
460 this.identifier = getIdentifier(new String[] {
461 this.getCommand(), this.identifierArg
462 }, this.getCommand());
463 }
464 }
465 return this.identifier;
466 }
467
468 abstract protected String getIncludeDirSwitch(String source);
469
470
471
472
473
474
475
476
477
478
479
480
481
482 protected String getIncludeDirSwitch(final String source, final boolean isSystem) {
483 return getIncludeDirSwitch(source);
484 }
485
486 protected String getInputFileArgument(final File outputDir, final String filename, final int index) {
487
488
489
490 String relative="";
491 String inputFile;
492 try {
493 relative = FileUtils.getRelativePath(workDir, new File(filename));
494 } catch (Exception ex) {
495 }
496 if (relative.isEmpty()) {
497 inputFile = filename;
498 } else {
499 inputFile = relative;
500 }
501 if (inputFile.indexOf(' ') >= 0) {
502 final String buf = "\"" + inputFile +
503 "\"";
504 return buf;
505 }
506 return inputFile;
507 }
508
509 protected final boolean getLibtool() {
510 return this.libtool;
511 }
512
513
514
515
516
517
518 public final CommandLineCompiler getLibtoolCompiler() {
519 if (this.libtoolCompiler != null) {
520 return this.libtoolCompiler;
521 }
522 return this;
523 }
524
525 abstract public int getMaximumCommandLength();
526
527 protected int getMaximumInputFilesPerCommand() {
528 return Integer.MAX_VALUE;
529 }
530
531
532
533
534
535
536
537
538
539
540 protected int getTotalArgumentLengthForInputFile(final File outputDir, final String inputFile) {
541 final int argumentCountPerInputFile = getArgumentCountPerInputFile();
542 int len=0;
543 for (int k = 0; k < argumentCountPerInputFile; k++) {
544 len+=getInputFileArgument(outputDir, inputFile, k).length();
545 }
546 return len + argumentCountPerInputFile;
547 }
548
549 abstract protected void getUndefineSwitch(StringBuffer buffer, String define);
550
551
552
553
554
555 protected int runCommand(final CCTask task, final File workingDir, final String[] cmdline) throws BuildException {
556 return CUtil.runCommand(task, workingDir, cmdline, this.newEnvironment, this.env);
557 }
558
559 protected final void setCommand(final String command) {
560 this.command = command;
561 }
562 }