1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package vogar.tasks;
18
19import java.io.File;
20import java.io.FileOutputStream;
21import java.io.IOException;
22import java.io.OutputStream;
23import java.util.Collections;
24import java.util.HashSet;
25import java.util.Properties;
26import java.util.Set;
27import java.util.regex.Pattern;
28
29import com.google.common.collect.Sets;
30
31import vogar.Action;
32import vogar.Classpath;
33import vogar.Driver;
34import vogar.Mode;
35import vogar.Outcome;
36import vogar.Result;
37import vogar.Run;
38import vogar.TestProperties;
39import vogar.commands.Command;
40import vogar.commands.CommandFailedException;
41import vogar.commands.Jack;
42import vogar.commands.Javac;
43
44/**
45 * Compiles classes for the given action and makes them ready for execution.
46 */
47public final class BuildActionTask extends Task {
48    private static final Pattern JAVA_SOURCE_PATTERN = Pattern.compile("\\/(\\w)+\\.java$");
49
50    private final Action action;
51    private final Run run;
52    private final Driver driver;
53    private final File outputFile;
54
55    public BuildActionTask(Run run, Action action, Driver driver, File outputFile) {
56        super("build " + action.getName());
57        this.run = run;
58        this.action = action;
59        this.driver = driver;
60        this.outputFile = outputFile;
61    }
62
63    @Override protected Result execute() throws Exception {
64        try {
65            if (run.useJack) {
66                compileWithJack(action, outputFile);
67            } else {
68                compile(action, outputFile);
69            }
70            return Result.SUCCESS;
71        } catch (CommandFailedException e) {
72            driver.addEarlyResult(new Outcome(action.getName(), Result.COMPILE_FAILED,
73                    e.getOutputLines()));
74            return Result.COMPILE_FAILED;
75        } catch (IOException e) {
76            driver.addEarlyResult(new Outcome(action.getName(), Result.ERROR, e));
77            return Result.ERROR;
78        }
79    }
80
81    /**
82     * Returns the .jar file containing the action's compiled classes.
83     *
84     * @throws CommandFailedException if javac fails
85     */
86    private void compile(Action action, File jar) throws IOException {
87        File classesDir = run.localFile(action, "classes");
88        run.mkdir.mkdirs(classesDir);
89        createJarMetadataFiles(action, classesDir);
90
91        Set<File> sourceFiles = new HashSet<File>();
92        File javaFile = action.getJavaFile();
93        Javac javac = new Javac(run.log, run.javaPath("javac"));
94        if (run.debugging) {
95            javac.debug();
96        }
97        if (javaFile != null) {
98            if (!JAVA_SOURCE_PATTERN.matcher(javaFile.toString()).find()) {
99                throw new CommandFailedException(Collections.<String>emptyList(),
100                        Collections.singletonList("Cannot compile: " + javaFile));
101            }
102            sourceFiles.add(javaFile);
103            Classpath sourceDirs = Classpath.of(action.getSourcePath());
104            sourceDirs.addAll(run.sourcepath);
105            javac.sourcepath(sourceDirs.getElements());
106        }
107        if (!sourceFiles.isEmpty()) {
108            if (!run.buildClasspath.isEmpty()) {
109                javac.bootClasspath(run.buildClasspath);
110            }
111            javac.classpath(run.classpath)
112                    .destination(classesDir)
113                    .javaVersion(run.language.getJavacSourceAndTarget())
114                    .extra(run.javacArgs)
115                    .compile(sourceFiles);
116        }
117
118        new Command(run.log, run.javaPath("jar"), "cvfM", jar.getPath(),
119                "-C", classesDir.getPath(), "./").execute();
120    }
121
122
123
124    /**
125     * Compile sources using the Jack compiler.
126     */
127    private void compileWithJack(Action action, File jackFile) throws IOException {
128        // Create a folder for resources.
129        File resourcesDir = run.localFile(action, "resources");
130        run.mkdir.mkdirs(resourcesDir);
131        createJarMetadataFiles(action, resourcesDir);
132
133        File javaFile = action.getJavaFile();
134        Jack compiler = Jack.getJackCommand(run.log);
135
136        if (run.debugging) {
137            compiler.setDebug();
138        }
139        compiler.sourceVersion(run.language.getJackSourceVersion());
140        compiler.minApiLevel(String.valueOf(run.language.getJackMinApilevel()));
141        Set<File> sourceFiles = Sets.newHashSet();
142
143        // Add the source files to be compiled.
144        // The javac compiler supports the -sourcepath directive although jack
145        // does not have this (see b/22382563) so for now only the files given
146        // are actually compiled.
147        if (javaFile != null) {
148            if (!JAVA_SOURCE_PATTERN.matcher(javaFile.toString()).find()) {
149                throw new CommandFailedException(Collections.<String>emptyList(),
150                        Collections.singletonList("There is no source to compile here: "
151                                + javaFile));
152            }
153            sourceFiles.add(javaFile);
154        }
155
156        // Compile if there is anything to compile.
157        if (!sourceFiles.isEmpty()) {
158            if (!run.buildClasspath.isEmpty()) {
159                compiler.setClassPath(run.buildClasspath.toString() + ":"
160                        + run.classpath.toString());
161            }
162        }
163
164        compiler.outputJack(jackFile.getPath())
165                .importResource(resourcesDir.getPath())
166                .compile(sourceFiles);
167    }
168
169    /**
170     * Writes files to {@code classesDir} to be included in the .jar file for
171     * {@code action}.
172     */
173    private void createJarMetadataFiles(Action action, File classesDir) throws IOException {
174        OutputStream propertiesOut
175                = new FileOutputStream(new File(classesDir, TestProperties.FILE));
176        Properties properties = new Properties();
177        fillInProperties(properties, action);
178        properties.store(propertiesOut, "generated by " + Mode.class.getName());
179        propertiesOut.close();
180    }
181
182    /**
183     * Fill in properties for running in this mode
184     */
185    private void fillInProperties(Properties properties, Action action) {
186        properties.setProperty(TestProperties.TEST_CLASS_OR_PACKAGE, action.getTargetClass());
187        properties.setProperty(TestProperties.QUALIFIED_NAME, action.getName());
188        properties.setProperty(TestProperties.MONITOR_PORT, Integer.toString(run.firstMonitorPort));
189        properties.setProperty(TestProperties.TIMEOUT, Integer.toString(run.timeoutSeconds));
190        properties.setProperty(TestProperties.PROFILE, Boolean.toString(run.profile));
191        properties.setProperty(TestProperties.PROFILE_DEPTH, Integer.toString(run.profileDepth));
192        properties.setProperty(TestProperties.PROFILE_INTERVAL,
193                Integer.toString(run.profileInterval));
194        properties.setProperty(TestProperties.PROFILE_FILE, run.profileFile.getName());
195        properties.setProperty(TestProperties.PROFILE_THREAD_GROUP,
196                Boolean.toString(run.profileThreadGroup));
197        properties.setProperty(TestProperties.TEST_ONLY, Boolean.toString(run.testOnly));
198    }
199}
200