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