的Android通過添加構建R.java
文件任務做到這一點。就像Android使它看起來複制一樣簡單,行爲需要一點努力。你可以創建自己的gradle任務來完成相同的任務。您將需要一個創建和擴展以及任務的gradle插件。擴展將被用於追蹤在build.gradle
插件添加應該創建擴展價值觀和任務
// create DSL model
target.extensions.create('buildConfig', BuildConfigModel)
// create task to generate file
def buildConfigTask = target.tasks.create('generateBuildConfig', BuildConfigTask)
target.tasks.findByName('clean').configure {
delete new File("$project.projectDir/src/main", "generated-sources")
}
// this task must always run... it's never `upToDate`
buildConfigTask.outputs.upToDateWhen { false }
// java compiler depends on this task... allows us to reference generated code from
// human written code
target.tasks.getByName('compileJava').dependsOn buildConfigTask
這裏是你如何使用你的任務是增加產生的源文件
project.configure(project, { Project configuredProject ->
// compilerJava task {@link JavaCompile}
def compileJava = configuredProject.compileJava
// add the created java file to source path so it gets compiled
compileJava.source(project.buildConfig.createBuildConfig(project, compileJava))
})
然後我們的擴展會看起來像這樣
package com.jbirdvegas.q41680813;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
import org.gradle.api.GradleException;
import org.gradle.api.Project;
import org.gradle.api.tasks.compile.JavaCompile;
import javax.lang.model.element.Modifier;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* Handles creating the BuildConfig.java from a module project
* <p>
* Warning... Keep this in java, gradle doesn't have the streams api we are using
*/
public class BuildConfigModel {
/**
* Final java file output path pattern for {@link String#format(String, Object...) String#format} formatting of
* the file's path. Directory structure will be created if needed
*/
private static final String OUTPUT_PATH_FORMAT = "%s/src/main/generated-sources/%s/%s/BuildConfig.java";
/**
* List of DSL supplied {@link BuildValue buildConfig#add}
*/
private List<BuildValue> mBuildValues = new ArrayList<>();
/**
* Required... do not remove
*/
public BuildConfigModel() {
}
/**
* Create a new field to the project's generated `BuildConfig.java`'s inner class for each type
*
* @param clazz Type of value to be written (will be grouped by clazz)
* @param name field name to be created
* @param value value to be assigned to field's name
*/
@SuppressWarnings({"unused", "WeakerAccess"})
public void add(Class clazz, String name, Object value) {
mBuildValues.add(new BuildValue(clazz, name, value));
}
/**
* Create `BuildConfig.java` and add it to the {@link JavaCompile#source(Object...)} compileJava#source(Object...)}
*
* @param project module to generate BuildConfig for
* @param javaCompile project's `compileJava` task
* @return generated `BuildConfig.java` file
*/
public File createBuildConfig(Project project, JavaCompile javaCompile) {
File buildConfigFile = getBuildConfigFile(project);
createJavaClass(project, buildConfigFile);
javaCompile.source(buildConfigFile);
return buildConfigFile;
}
/**
* Destination file for given project's `BuildConfig.java`
*
* @param project module to generate BuildConfig for
* @return File representing the destination of the created `BuildConfig.java`
*/
@SuppressWarnings("WeakerAccess")
public File getBuildConfigFile(Project project) {
return project.file(String.format(OUTPUT_PATH_FORMAT,
project.getProjectDir().getAbsolutePath(),
project.getGroup().toString().replaceAll("\\.", "/"),
project.getName()));
}
/**
* Create `BuildConfig.java` with a few default values and any values supplied
* to the `buildConfig`'s {@link #add(Class, String, Object) add} method.
* <p>
* Default BuildConfig fields will be generated by {@link #getDefaultFields}
* <p>
* Fields added via {@link #add(Class, String, Object) add} method will be grouped into inner classes
* named <pre>{@code Class#getSimpleName().toLowerCase() + "s"}</pre>
*
* @param project module to generate BuildConfig for
* @param buildConfigFile File representing the destination of the BuildConfig.java output
*/
@SuppressWarnings("WeakerAccess")
public void createJavaClass(Project project, File buildConfigFile) {
//noinspection unchecked
Collections.sort(mBuildValues);
// group our configs by their types into a map of groups
Map<Class, List<BuildValue>> groupedConfigs = mBuildValues.stream()
// put the values in groups based on the simple name of the class in lowercase
.collect(Collectors.groupingBy(BuildValue::getValueType));
// create the fully qualified class that will contain our build settings
TypeSpec.Builder buildConfigJavaBuilder = TypeSpec.classBuilder("BuildConfig")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
// note for javadoc
.addJavadoc("$S\n", "DO NOT MODIFY; this class is written automatically by the compiler")
// replace public constructor with private
.addMethod(createPrivateConstructor());
// add any fields that will be in all BuildConfig classes
buildConfigJavaBuilder.addFields(getDefaultFields(project));
groupedConfigs.forEach((aClass, buildValues) -> {
// create the inner class
String safeInnerClassName = aClass.getSimpleName().toLowerCase() + 's';
TypeSpec.Builder innerClass = TypeSpec.classBuilder(safeInnerClassName)
.addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
// make a constructor that's private since all the members of this class are public static final
.addMethod(createPrivateConstructor());
// for each inner class type create a field
// each object type gets it's own inner class
//noinspection SimplifyStreamApiCallChains
buildValues.stream().forEachOrdered(buildValue -> {
// create the requested field in the class
FieldSpec fieldSpec = FieldSpec.builder(buildValue.clazz, buildValue.name)
.addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
.initializer(CodeBlock.of(getStringFormatter(buildValue.clazz), buildValue.value))
.build();
// add the field to the inner class
innerClass.addField(fieldSpec);
});
// add the inner class to the fully qualified class
buildConfigJavaBuilder.addType(innerClass.build());
});
// determine the package name from project.group + '.' + project.name
String packageName = project.getGroup() + "." + project.getName();
// create a java file writer
JavaFile.Builder builder = JavaFile.builder(packageName, buildConfigJavaBuilder.build());
// don't import java.lang.* it's redundant
builder.skipJavaLangImports(true);
// use four spaces for indent instead of default two spaces
builder.indent(" ");
// create the file in memory
JavaFile javaFile = builder.build();
// ensure file structure
if (!buildConfigFile.getParentFile().exists() && !buildConfigFile.getParentFile().mkdirs()) {
throw new GradleException("Failed to create directory structure for " + buildConfigFile.getAbsolutePath());
}
// write BuildConfig.java to location
try (FileWriter writer = new FileWriter(buildConfigFile)) {
javaFile.writeTo(writer);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Strings require being treated specially in order to be encapsulated in quotes correctly
* All other classes are treated as literals... We may want to handle more {@link java.lang.reflect.Type Type}
*
* @param clazz Class formatter is needed for
* @return "$S" if the class is a {@link String} else a literal formatter is returned "$L"
*/
private String getStringFormatter(Class clazz) {
switch (clazz.getSimpleName().toLowerCase()) {
case "string":
// causes the formatter used to wrap the value in quotes correctly
case "date":
// date objects are serialized to a string
return "$S";
case "long":
return "$LL";
case "double":
return "$LD";
case "float":
return "$LF";
default:
// for the reset use literal
return "$L";
}
}
/**
* get project added build values
*
* @return List of build values added by project's `buildConfig` closure
*/
@SuppressWarnings("unused")
public List<BuildValue> collectBuildValues() {
return mBuildValues;
}
/**
* Make a private constructor for the class. Default is public but our classes only contain
* <pre>{@code public static final {@link Object}}</pre> so public constructors are redundant
*
* @return private constructor method
*/
private MethodSpec createPrivateConstructor() {
return MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build();
}
/**
* Create default field references
*
* @param project module to generate BuildConfig for
* @return List of fields to write to generated BuildConfig
*/
private List<FieldSpec> getDefaultFields(Project project) {
List<FieldSpec> fields = new ArrayList<>();
// set current version
fields.add(FieldSpec.builder(String.class, "version")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
.initializer(CodeBlock.of(getStringFormatter(String.class), project.getVersion()))
.build());
return fields;
}
class BuildValue implements Comparable {
/**
* Type of field's value
*/
/* package */ Class clazz;
/**
* Field name
*/
/* package */ String name;
/**
* Field's value Value must be able to be serialized as a string
*/
/* package */ Object value;
/* package */ BuildValue(Class clazz, String name, Object value) {
this.clazz = clazz;
this.name = name;
this.value = value;
}
/* package */ Class getValueType() {
return clazz;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof BuildValue)) return false;
BuildValue that = (BuildValue) o;
if (clazz != null ? !clazz.equals(that.clazz) : that.clazz != null) return false;
if (name != null ? !name.equals(that.name) : that.name != null) return false;
return value != null ? value.equals(that.value) : that.value == null;
}
@Override
public int hashCode() {
int result = clazz != null ? clazz.hashCode() : 0;
result = 31 * result + (name != null ? name.hashCode() : 0);
result = 31 * result + (value != null ? value.hashCode() : 0);
return result;
}
@Override
public int compareTo(Object o) {
return (name != null && o != null) ? name.compareTo(o.toString()) : -1;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("BuildValue{");
sb.append("class=").append(clazz.getCanonicalName());
sb.append(", name='").append(name).append('\'');
sb.append(", value=").append(value);
sb.append('}');
return sb.toString();
}
}
}
這裏默認插件將創建BuildConfig.java
與version
默認的字段,但您可以添加自己的價值觀也
buildConfig {
add Boolean, 'myKey', false
}
然後在運行時,你可以用BuildConfig.value
獲得參考的價值和上面的例子中,你也具有BuildConfig.myKey
類型的Boolean
類型。
編輯:我的示例使用常規的插件類,任務類,但是擴展,BuildConfigModel
,是用Java編寫。我所有的消息來源都位於src/main/groovy
哇。感謝您的詳細回覆。今天我無法做到這一點,但我會盡快實施。我自己手動設置版本信息似乎更容易,但是我的項目列表正在增長,所以從長遠來看,設置自動化解決方案將是值得的。 –