1 package com.github.maven_nar;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.util.ArrayList;
6 import java.util.Arrays;
7 import java.util.Collections;
8 import java.util.Comparator;
9 import java.util.LinkedHashSet;
10 import java.util.List;
11 import java.util.ListIterator;
12 import java.util.Map.Entry;
13 import java.util.Set;
14 import java.util.regex.Matcher;
15 import java.util.regex.Pattern;
16
17 import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
18 import org.apache.maven.plugin.MojoExecutionException;
19 import org.apache.maven.plugin.MojoFailureException;
20 import org.apache.maven.plugins.annotations.Parameter;
21 import org.apache.tools.ant.types.Environment.Variable;
22 import org.codehaus.plexus.util.StringUtils;
23
24 import com.github.maven_nar.cpptasks.CCTask;
25 import com.github.maven_nar.cpptasks.CompilerDef;
26 import com.github.maven_nar.cpptasks.LinkerDef;
27 import com.github.maven_nar.cpptasks.types.SystemIncludePath;
28 import com.google.common.collect.Sets;
29
30 public class Msvc {
31
32 @Parameter
33 private File home;
34
35 private AbstractNarMojo mojo;
36
37 private final Set<String> paths = new LinkedHashSet<>();
38
39 @Parameter
40 private String version;
41
42 @Parameter
43 private File windowsSdkHome;
44
45 @Parameter
46 private String windowsSdkVersion;
47
48 @Parameter
49 private String tempPath;
50
51 private File windowsHome;
52 private String toolPathWindowsSDK;
53 private String toolPathLinker;
54 private List<File> sdkIncludes = new ArrayList<>();
55 private List<File> sdkLibs = new ArrayList<>();
56 private Set<String> libsRequired = Sets.newHashSet("ucrt", "um", "shared", "winrt");
57
58 private boolean addIncludePath(final CCTask task, final File home, final String subDirectory)
59 throws MojoExecutionException {
60 if (home == null) {
61 return false;
62 }
63 final File file = new File(home, subDirectory);
64 if (file.exists())
65 return addIncludePathToTask(task, file);
66
67 return false;
68 }
69
70 private boolean addIncludePathToTask(final CCTask task, final File file)
71 throws MojoExecutionException {
72 try {
73 final SystemIncludePath includePath = task.createSysIncludePath();
74 final String fullPath = file.getCanonicalPath();
75 includePath.setPath(fullPath);
76 return true;
77 } catch (final IOException e) {
78 throw new MojoExecutionException("Unable to add system include: " + file.getAbsolutePath(), e);
79 }
80 }
81
82 private boolean addPath(final File home, final String path) {
83 if (home != null) {
84 final File directory = new File(home, path);
85 if (directory.exists()) {
86 try {
87 final String fullPath = directory.getCanonicalPath();
88 this.paths.add(fullPath);
89 return true;
90 } catch (final IOException e) {
91 throw new IllegalArgumentException("Unable to get path: " + directory, e);
92 }
93 }
94 }
95 return false;
96 }
97
98 public void configureCCTask(final CCTask task) throws MojoExecutionException {
99 if (OS.WINDOWS.equals(mojo.getOS())
100 && "msvc".equalsIgnoreCase(mojo.getLinker().getName())) {
101 addIncludePath(task, this.home, "VC/include");
102 addIncludePath(task, this.home, "VC/atlmfc/include");
103 if (compareVersion(this.windowsSdkVersion, "7.1A") <= 0) {
104 addIncludePath(task, this.windowsSdkHome, "include");
105 } else {
106 for (File sdkInclude : sdkIncludes)
107 addIncludePathToTask(task, sdkInclude);
108
109 }
110 task.addEnv(getPathVariable());
111
112
113
114 Variable envVariable = new Variable();
115
116
117
118
119
120 envVariable.setKey("SystemRoot");
121 envVariable.setValue(this.windowsHome.getAbsolutePath());
122 task.addEnv(envVariable);
123
124 envVariable = new Variable();
125 envVariable.setKey("TMP");
126 envVariable.setValue(getTempPath());
127 task.addEnv(envVariable);
128 }
129 }
130
131 public void configureLinker(final LinkerDef linker) throws MojoExecutionException {
132 final String os = mojo.getOS();
133 if (os.equals(OS.WINDOWS) && "msvc".equalsIgnoreCase(mojo.getLinker().getName())) {
134 final String arch = mojo.getArchitecture();
135
136
137 if ("x86".equals(arch)) {
138 linker.addLibraryDirectory(this.home, "VC/lib");
139 linker.addLibraryDirectory(this.home, "VC/atlmfc/lib");
140 } else {
141 linker.addLibraryDirectory(this.home, "VC/lib/" + arch);
142 linker.addLibraryDirectory(this.home, "VC/atlmfc/lib/" + arch);
143 }
144
145 String sdkArch = arch;
146 if ("amd64".equals(arch)) {
147 sdkArch = "x64";
148 }
149
150 if (compareVersion(this.windowsSdkVersion, "8.0") < 0) {
151 if ("x86".equals(arch)) {
152 linker.addLibraryDirectory(this.windowsSdkHome, "lib");
153 } else {
154 linker.addLibraryDirectory(this.windowsSdkHome, "lib/" + sdkArch);
155 }
156 } else
157 for (File sdkLib : sdkLibs)
158 linker.addLibraryDirectory(sdkLib, sdkArch);
159
160 }
161 }
162
163 private String getTempPath(){
164 if( null == tempPath ){
165 tempPath = System.getenv("TMP");
166 if( null == tempPath )
167 tempPath = System.getenv("TEMP");
168 if( null == tempPath )
169 tempPath = "C:\\Temp";
170 }
171 return tempPath;
172 }
173
174 public Variable getPathVariable() {
175 if (this.paths.isEmpty()) {
176 return null;
177 }
178 final Variable pathVariable = new Variable();
179 pathVariable.setKey("PATH");
180 pathVariable.setValue(StringUtils.join(this.paths.iterator(), File.pathSeparator));
181 return pathVariable;
182 }
183
184 public String getVersion() {
185 return this.version;
186 }
187
188 public String getWindowsSdkVersion() {
189 return this.windowsSdkVersion;
190 }
191
192 private void init() throws MojoFailureException, MojoExecutionException {
193 final String mojoOs = this.mojo.getOS();
194 if (NarUtil.isWindows() && OS.WINDOWS.equals(mojoOs)) {
195 windowsHome = new File(System.getenv("SystemRoot"));
196 initVisualStudio();
197 initWindowsSdk();
198 initPath();
199 } else {
200 this.version = "";
201 this.windowsSdkVersion = "";
202 }
203 }
204
205 private void initPath() throws MojoExecutionException {
206 final String mojoArchitecture = this.mojo.getArchitecture();
207 final String osArchitecture = NarUtil.getArchitecture(null);
208
209
210 final boolean matchMojo = false;
211
212
213
214
215
216
217
218 if (!osArchitecture.equals(mojoArchitecture) && !matchMojo) {
219 if (!addPath(this.home, "VC/bin/" + osArchitecture + "_" + mojoArchitecture)) {
220 throw new MojoExecutionException("Unable to find compiler for architecture " + mojoArchitecture + ".\n"
221 + new File(this.home, "VC/bin/" + osArchitecture + "_" + mojoArchitecture));
222 }
223 toolPathLinker = new File(this.home, "VC/bin/" + osArchitecture + "_" + mojoArchitecture).getAbsolutePath();
224 }
225 if (null == toolPathLinker) {
226 if ("amd64".equals(mojoArchitecture))
227 toolPathLinker = new File(this.home, "VC/bin/amd64").getAbsolutePath();
228 else
229 toolPathLinker = new File(this.home, "VC/bin").getAbsolutePath();
230 }
231 if ("amd64".equals(osArchitecture) && !matchMojo) {
232 addPath(this.home, "VC/bin/amd64");
233 } else {
234 addPath(this.home, "VC/bin");
235 }
236 addPath(this.home, "VC/VCPackages");
237 addPath(this.home, "Common7/Tools");
238 addPath(this.home, "Common7/IDE");
239
240
241 if (compareVersion(this.windowsSdkVersion, "7.1A") <= 0) {
242 if ("amd64".equals(osArchitecture) && !matchMojo) {
243 addPath(this.windowsSdkHome, "bin/x64");
244 }
245 addPath(this.windowsSdkHome, "bin");
246 } else {
247 if ("amd64".equals(osArchitecture) && !matchMojo) {
248 addPath(this.windowsSdkHome, "bin/x64");
249 }
250 addPath(this.windowsSdkHome, "bin/x86");
251 }
252 if ("amd64".equals(mojoArchitecture)) {
253 toolPathWindowsSDK = new File(this.windowsSdkHome, "bin/x64").getAbsolutePath();
254 } else if (compareVersion(this.windowsSdkVersion, "7.1A") <= 0) {
255 toolPathWindowsSDK = new File(this.windowsSdkHome, "bin").getAbsolutePath();
256 } else {
257 toolPathWindowsSDK = new File(this.windowsSdkHome, "bin/x86").getAbsolutePath();
258 }
259
260
261 addPath(this.windowsHome, "System32");
262 addPath(this.windowsHome, "");
263 addPath(this.windowsHome, "System32/wbem");
264 }
265
266 private void initVisualStudio() throws MojoFailureException, MojoExecutionException {
267 mojo.getLog().debug(" -- Searching for usable VisualStudio ");
268 if (this.version != null && this.version.trim().length() > 1) {
269 String internalVersion;
270 Pattern r = Pattern.compile("(\\d+)\\.*(\\d)");
271 Matcher matcher = r.matcher(this.version);
272 if (matcher.find()) {
273 internalVersion = matcher.group(1) + matcher.group(2);
274 this.version = matcher.group(1) + "." + matcher.group(2);
275 } else {
276 throw new MojoExecutionException("msvc.version must be the internal version in the form 10.0 or 120");
277 }
278 if (this.home == null) {
279 final String commontToolsVar = System.getenv("VS" + internalVersion + "COMNTOOLS");
280 if (commontToolsVar != null && commontToolsVar.trim().length() > 0) {
281 final File commonToolsDirectory = new File(commontToolsVar);
282 if (commonToolsDirectory.exists()) {
283 this.home = commonToolsDirectory.getParentFile().getParentFile();
284 }
285 }
286
287
288 }
289 mojo.getLog()
290 .debug(String.format(" VisualStudio %1s (%2s) found %3s ", this.version, internalVersion, this.home));
291 } else {
292 this.version = "";
293 for (final Entry<String, String> entry : System.getenv().entrySet()) {
294 final String key = entry.getKey();
295 final String value = entry.getValue();
296 final Pattern versionPattern = Pattern.compile("VS(\\d+)(\\d)COMNTOOLS");
297 final Matcher matcher = versionPattern.matcher(key);
298 if (matcher.matches()) {
299 final String version = matcher.group(1) + "." + matcher.group(2);
300 if (versionStringComparator.compare(version, this.version) > 0) {
301 final File commonToolsDirectory = new File(value);
302 if (commonToolsDirectory.exists()) {
303 this.version = version;
304 this.home = commonToolsDirectory.getParentFile().getParentFile();
305 mojo.getLog().debug(
306 String.format(" VisualStudio %1s (%2s) found %3s ", this.version,
307 matcher.group(1) + matcher.group(2), this.home));
308 }
309 }
310 }
311 }
312 if (this.version.length() == 0) {
313 final TextStream out = new StringTextStream();
314 final TextStream err = new StringTextStream();
315 final TextStream dbg = new StringTextStream();
316
317 NarUtil.runCommand("link", new String[] {
318 "/?"
319 }, null, null, out, err, dbg, null, true);
320 final Pattern p = Pattern.compile("(\\d+\\.\\d+)\\.\\d+(\\.\\d+)?");
321 final Matcher m = p.matcher(out.toString());
322 if (m.find()) {
323 this.version = m.group(1);
324 mojo.getLog().debug(
325 String.format(" VisualStudio Not found but link runs and reports version %1s (%2s)", this.version,
326 m.group(0)));
327 } else {
328 throw new MojoExecutionException(
329 "msvc.version not specified and no VS<Version>COMNTOOLS environment variable can be found");
330 }
331 }
332 }
333 }
334
335 private final Comparator<String> versionStringComparator = new Comparator<String>() {
336 @Override
337 public int compare(String o1, String o2) {
338 DefaultArtifactVersion version1 = new DefaultArtifactVersion(o1);
339 DefaultArtifactVersion version2 = new DefaultArtifactVersion(o2);
340 return version1.compareTo(version2);
341 }
342 };
343
344 private final Comparator<File> versionComparator = new Comparator<File>() {
345 @Override
346 public int compare(File o1, File o2) {
347
348
349 String firstDir = o2.getName(), secondDir = o1.getName();
350 if (firstDir.charAt(0) == 'v') {
351 firstDir = firstDir.substring(1, firstDir.length() - 1);
352 secondDir = secondDir.substring(1, secondDir.length() - 1);
353 }
354
355 String[] firstVersionString = firstDir.split("\\."), secondVersionString = secondDir.split("\\.");
356
357 int maxIdx = Math.min(firstVersionString.length, secondVersionString.length);
358 int deltaVer;
359 try {
360 for (int i = 0; i < maxIdx; i++)
361 if ((deltaVer = Integer.parseInt(firstVersionString[i]) - Integer.parseInt(secondVersionString[i])) != 0)
362 return deltaVer;
363
364 } catch (NumberFormatException e) {
365 return firstDir.compareTo(secondDir);
366 }
367 if (firstVersionString.length > maxIdx)
368 return 1;
369 else if (secondVersionString.length > maxIdx)
370 return -1;
371 return 0;
372 }
373 };
374 private boolean foundSDK = false;
375 private void initWindowsSdk() throws MojoExecutionException {
376 if(this.windowsSdkVersion != null && this.windowsSdkVersion.trim().equals(""))
377 this.windowsSdkVersion = null;
378
379 mojo.getLog().debug(" -- Searching for usable WindowSDK ");
380
381 for (final File directory : Arrays.asList(
382 new File("C:/Program Files (x86)/Windows Kits"),
383 new File("C:/Program Files (x86)/Microsoft SDKs/Windows"),
384 new File("C:/Program Files/Windows Kits"),
385 new File("C:/Program Files/Microsoft SDKs/Windows") )) {
386 if (directory.exists()) {
387 final File[] kitDirectories = directory.listFiles();
388 Arrays.sort(kitDirectories, versionComparator);
389 if (kitDirectories != null) {
390 for (final File kitDirectory : kitDirectories) {
391
392 if (new File(kitDirectory, "Include").exists()) {
393
394 String kitVersion = kitDirectory.getName();
395 if (kitVersion.charAt(0) == 'v') {
396 kitVersion = kitVersion.substring(1);
397 }
398 if (this.windowsSdkVersion!=null && compareVersion(kitVersion,this.windowsSdkVersion)>0)
399 continue;
400 mojo.getLog()
401 .debug(String.format(" WindowSDK %1s found %2s", kitVersion, kitDirectory.getAbsolutePath()));
402 if (kitVersion.matches("\\d+\\.\\d+?[A-Z]?")) {
403
404 legacySDK(kitDirectory);
405 } else if (kitVersion.matches("\\d+?")) {
406
407 addNewSDKLibraries(kitDirectory);
408 }
409 }
410 }
411 if (libsRequired.size() == 0)
412 break;
413 }
414 }
415 }
416 if (!foundSDK)
417 throw new MojoExecutionException("msvc.windowsSdkVersion not specified and versions cannot be found");
418 mojo.getLog().debug(String.format(" Using WindowSDK %1s found %2s", this.windowsSdkVersion, this.windowsSdkHome));
419 }
420
421 private void addNewSDKLibraries(final File kitDirectory) {
422
423 List<File> kitVersionDirectories = Arrays.asList(new File(kitDirectory, "Include").listFiles());
424 Collections.sort(kitVersionDirectories, versionComparator);
425 ListIterator<File> kitVersionDirectoriesIt = kitVersionDirectories.listIterator();
426 File kitVersionDirectory = null;
427 while (kitVersionDirectoriesIt.hasNext() && (kitVersionDirectory = kitVersionDirectoriesIt.next()) != null) {
428 if (new File(kitVersionDirectory, "ucrt").exists()) {
429 break;
430 }
431 }
432
433 if (kitVersionDirectory != null) {
434 String version = kitVersionDirectory.getName();
435 mojo.getLog().debug(String.format(" Latest Win %1s KitDir at %2s", kitVersionDirectory.getName(), kitVersionDirectory.getAbsolutePath()));
436
437 File includeDir = new File(kitDirectory, "Include/" + version);
438 File libDir = new File(kitDirectory, "Lib/" + version);
439 addSDKLibs(includeDir, libDir);
440 }
441 }
442
443 private void setKit(File home) {
444 if (!foundSDK) {
445 if(this.windowsSdkVersion==null)
446 this.windowsSdkVersion = home.getName();
447 if(this.windowsSdkHome==null)
448 this.windowsSdkHome = home;
449 foundSDK=true;
450 }
451 }
452
453 private void legacySDK(final File kitDirectory) {
454 File includeDir = new File(kitDirectory, "Include");
455 File libDir = new File(kitDirectory, "Lib");
456 if(includeDir.exists() && libDir.exists()){
457 File usableLibDir = null;
458 for( final File libSubDir : libDir.listFiles()){
459 final File um = new File(libSubDir,"um");
460 if( um.exists())
461 usableLibDir = libSubDir;
462 }
463 if( null == usableLibDir )
464 usableLibDir = libDir.listFiles()[0];
465
466 addSDKLibs(includeDir, usableLibDir);
467 setKit(kitDirectory);
468 }
469 }
470
471 private void addSDKLibs(File includeDir, File libdir) {
472 final File[] libs = includeDir.listFiles();
473 for (final File libIncludeDir : libs) {
474
475 if (libsRequired.remove(libIncludeDir.getName())) {
476 mojo.getLog().debug(String.format(" Using directory %1s for library %2s", libIncludeDir.getAbsolutePath(), libIncludeDir.getName()));
477 sdkIncludes.add(libIncludeDir);
478 sdkLibs.add(new File(libdir, libIncludeDir.getName()));
479 }
480 }
481 }
482
483 public void setMojo(final AbstractNarMojo mojo) throws MojoFailureException, MojoExecutionException {
484 if (mojo != this.mojo) {
485 this.mojo = mojo;
486 init();
487 }
488 }
489
490 @Override
491 public String toString() {
492 return this.home + "\n" + this.windowsSdkHome;
493 }
494
495 public String getToolPath() {
496 return this.toolPathLinker;
497 }
498
499 public String getSDKToolPath() {
500 return this.toolPathWindowsSDK;
501 }
502
503 public void setToolPath(CompilerDef compilerDef, String name) {
504 if ("res".equals(name) || "mc".equals(name) || "idl".equals(name)) {
505 compilerDef.setToolPath(this.toolPathWindowsSDK);
506 } else {
507 compilerDef.setToolPath(this.toolPathLinker);
508 }
509 }
510
511 public int compareVersion(Object o1, Object o2) {
512 String version1 = (String) o1;
513 String version2 = (String) o2;
514
515 VersionTokenizer tokenizer1 = new VersionTokenizer(version1);
516 VersionTokenizer tokenizer2 = new VersionTokenizer(version2);
517
518 int number1 = 0, number2 = 0;
519 String suffix1 = "", suffix2 = "";
520
521 while (tokenizer1.MoveNext()) {
522 if (!tokenizer2.MoveNext()) {
523 do {
524 number1 = tokenizer1.getNumber();
525 suffix1 = tokenizer1.getSuffix();
526 if (number1 != 0 || suffix1.length() != 0) {
527
528 return 1;
529 }
530 } while (tokenizer1.MoveNext());
531
532
533 return 0;
534 }
535
536 number1 = tokenizer1.getNumber();
537 suffix1 = tokenizer1.getSuffix();
538 number2 = tokenizer2.getNumber();
539 suffix2 = tokenizer2.getSuffix();
540
541 if (number1 < number2) {
542
543 return -1;
544 }
545 if (number1 > number2) {
546
547 return 1;
548 }
549
550 boolean empty1 = suffix1.length() == 0;
551 boolean empty2 = suffix2.length() == 0;
552
553 if (empty1 && empty2)
554 continue;
555 if (empty1)
556 return 1;
557 if (empty2)
558 return -1;
559
560
561 int result = suffix1.compareTo(suffix2);
562 if (result != 0)
563 return result;
564
565 }
566 if (tokenizer2.MoveNext()) {
567 do {
568 number2 = tokenizer2.getNumber();
569 suffix2 = tokenizer2.getSuffix();
570 if (number2 != 0 || suffix2.length() != 0) {
571
572 return -1;
573 }
574 } while (tokenizer2.MoveNext());
575
576
577 return 0;
578 }
579 return 0;
580 }
581
582
583 class VersionTokenizer {
584 private final String _versionString;
585 private final int _length;
586
587 private int _position;
588 private int _number;
589 private String _suffix;
590 private boolean _hasValue;
591
592 public int getNumber() {
593 return _number;
594 }
595
596 public String getSuffix() {
597 return _suffix;
598 }
599
600 public boolean hasValue() {
601 return _hasValue;
602 }
603
604 public VersionTokenizer(String versionString) {
605 if (versionString == null)
606 throw new IllegalArgumentException("versionString is null");
607
608 _versionString = versionString;
609 _length = versionString.length();
610 }
611
612 public boolean MoveNext() {
613 _number = 0;
614 _suffix = "";
615 _hasValue = false;
616
617
618 if (_position >= _length)
619 return false;
620
621 _hasValue = true;
622
623 while (_position < _length) {
624 char c = _versionString.charAt(_position);
625 if (c < '0' || c > '9')
626 break;
627 _number = _number * 10 + (c - '0');
628 _position++;
629 }
630
631 int suffixStart = _position;
632
633 while (_position < _length) {
634 char c = _versionString.charAt(_position);
635 if (c == '.')
636 break;
637 _position++;
638 }
639
640 _suffix = _versionString.substring(suffixStart, _position);
641
642 if (_position < _length)
643 _position++;
644
645 return true;
646 }
647 }
648 }