1 /* 2 * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 import org.gradle.api.DefaultTask 27 import org.gradle.api.tasks.InputFiles 28 import org.gradle.api.tasks.Optional 29 import org.gradle.api.tasks.OutputDirectory 30 import org.gradle.api.tasks.TaskAction 31 import org.gradle.api.tasks.util.PatternFilterable 32 import org.gradle.api.tasks.util.PatternSet 33 34 import java.util.concurrent.ConcurrentHashMap 35 import java.util.concurrent.CountDownLatch 36 import java.util.concurrent.ExecutorService 37 import java.util.concurrent.Executors 38 import java.util.concurrent.Future 39 40 class NativeCompileTask extends DefaultTask { 41 @Optional String matches; // regex for matching input files 42 List<String> params = new ArrayList<String>(); 43 List sourceRoots = new ArrayList(); 44 @OutputDirectory File output; 45 @InputFiles List<File> allFiles = []; 46 private final PatternFilterable patternSet = new PatternSet(); 47 48 public NativeCompileTask source(Object... sources) { 49 for (Object source : sources) { 50 if (source instanceof Collection) { 51 sourceRoots.addAll((Collection)source); 52 } else { 53 sourceRoots.add(source); 54 } 55 } 56 updateFiles(); 57 return this; 58 } 59 60 public NativeCompileTask include(String... includes) { 61 patternSet.include(includes); 62 return this; 63 } 64 65 public NativeCompileTask include(Iterable<String> includes) { 66 patternSet.include(includes); 67 return this; 68 } 69 70 private void updateFiles() { 71 // Combine the different source roots into a single List<File> based on all files in each source root 72 allFiles.clear(); 73 sourceRoots.each { 74 def file = project.file(it); 75 if (file && file.exists()) { 76 allFiles += file.isDirectory() ? file.listFiles() : file; 77 } 78 } 79 } 80 81 @TaskAction void compile() { 82 // Get the existing native-dependencies file from build/dependency-cache and load its contents into 83 // memory. If the file doesn't exist, then we will just have an empty dependency map. 84 final Map<String, Map> dependencies = new ConcurrentHashMap<>(); 85 final File nativeDependenciesFile = project.file("$project.buildDir/dependency-cache/native-dependencies"); 86 if (nativeDependenciesFile.exists()) { 87 nativeDependenciesFile.splitEachLine("\t", { strings -> 88 try { 89 dependencies.put(strings[0], ["DATE":Long.parseLong(strings[1]), "SIZE":Long.parseLong(strings[2])]); 90 } catch (Exception e) { 91 // Might fail due to a corrupt native-dependencies file, in which case, we'll just not 92 // do anything which will cause the native code to execute again 93 } 94 }); 95 } 96 97 project.mkdir(output); 98 99 // Recompute the allFiles list as the input can come from auto-generated 100 // content (HSLS files, for example) which might have changed since 101 // the task was configured (i.e. when source() was called). 102 updateFiles(); 103 def source = project.files(allFiles); 104 boolean forceCompile = false; 105 final Set<File> files = new HashSet<File>(); 106 source.each { File file -> 107 final Map fileData = dependencies.get(file.toString()); 108 final boolean isModified = fileData == null || 109 !fileData["DATE"].equals(file.lastModified()) || 110 !fileData["SIZE"].equals(file.length()); 111 112 if (matches == null || file.name.matches(matches)) { 113 // If the source file is not listed in dependencies, then we must compile it. 114 // If the target file(s) (.rc or .cur in the case of resources, .pdb or .obj for sources) 115 // do not exist, then compile. 116 // If the source file date or size differs from dependencies, then compile it. 117 if (isModified) { 118 files += file; 119 } else { 120 final File outputFile = outputFile(file); 121 if (!outputFile.exists()) { 122 files += file; 123 } 124 } 125 } else { 126 // This file can be header file or some other type of resource file. 127 // Force all source files to be compile. 128 if (isModified) { 129 forceCompile = true; 130 //let the iterator finish to update dependencies map 131 } 132 } 133 if (isModified) { 134 dependencies.put(file.toString(), ["DATE":file.lastModified(), "SIZE":file.length()]); 135 } 136 } 137 if (forceCompile) { 138 files += matches == null ? source.files : source.filter{it.name.matches(matches)}.files; 139 } 140 141 project.logger.info("Compiling native files: $files"); 142 final ExecutorService executor = Executors.newFixedThreadPool(Integer.parseInt(project.NUM_COMPILE_THREADS.toString())); 143 final CountDownLatch latch = new CountDownLatch(files.size()); 144 List futures = new ArrayList<Future>(); 145 files.each { File sourceFile -> 146 futures.add(executor.submit(new Runnable() { 147 @Override public void run() { 148 try { 149 final File outputFile = outputFile(sourceFile); 150 doCompile(sourceFile, outputFile) 151 } finally { 152 latch.countDown(); 153 } 154 } 155 })); 156 } 157 latch.await(); 158 // Looking for whether an exception occurred while executing any of the futures. 159 // By calling "get()" on each future an exception will be thrown if one had occurred 160 // on the background thread. 161 futures.each {it.get();} 162 163 // Update the native-dependencies file 164 if (nativeDependenciesFile.exists()) nativeDependenciesFile.delete(); 165 nativeDependenciesFile.getParentFile().mkdirs(); 166 nativeDependenciesFile.createNewFile(); 167 dependencies.each { key, value -> 168 nativeDependenciesFile << key << "\t" << value["DATE"] << "\t" << value["SIZE"] << "\n"; 169 } 170 } 171 172 protected void doCompile(File sourceFile, File outputFile){ } 173 protected File outputFile(File sourceFile) { return null; } 174 } 175