2017-01-16 2 views
0

Я привык к Android Studio и разрабатываю проекты для Android.Добавление информации о версии в имя файла jar с использованием IntelliJ

В Android Studio я поставил это в файле build.gradle:

defaultConfig { 
    applicationId "com.domain.myapp" 
    minSdkVersion 19 
    targetSdkVersion 19 
    versionCode 1 
    versionName "1.0" 
    setProperty("archivesBaseName", "myapp.$versionName.$versionCode") 
    signingConfig signingConfigs.config 
} 
buildTypes { 
    release { 
     minifyEnabled false 
     proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 
     applicationVariants.all { variant -> 
      variant.outputs.each { output -> 
       def newName = output.outputFile.name 
       newName = newName.replace("-release", "") 
       output.outputFile = new File(output.outputFile.parent, newName) 
      } 
     } 
     signingConfig signingConfigs.config 
    } 
    debug { 
     signingConfig signingConfigs.config 
    } 
} 

Когда я строю я получаю myapp.1.0.1.apk, прекрасно работает.

Теперь я разрабатываю проект java-проекта с использованием IntelliJ, а не Android Studio.

Как я могу выполнить одно и то же? Я нахожу редкую информацию ...

ответ

0

Android делает это, добавляя задачу, которая создает файл R.java. Так же просто, как Android заставляет его воспроизводить поведение, требует немного усилий. Вы можете создать свою собственную задачу градации, чтобы выполнить ее. Вам понадобится плагин 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.

Редактировать: мой пример использует groovy для класса плагина и класса задачи, однако расширение, BuildConfigModel, написано в java. Все мои источники размещены в src/main/groovy

+0

Wow. Благодарим вас за подробный ответ. Я не смогу дойти до этого сегодня, но я скоро посмотрю на это. Кажется, проще просто вручную установить информацию о версии, но список моих проектов растет, поэтому в конечном итоге будет полезно настроить автоматическое решение. –

Смежные вопросы