Commit d03cad90 by 王涛55

init

parents
#Android generated
bin
gen
#Eclipse
.project
.classpath
.settings
4Admin/4Admin.apk
4Admin/proguard_logs
#IntelliJ IDEA
.idea
*.iml
classes
out
production
#Maven
target
release.properties
pom.xml.*
#Command line
local.properties
proguard-project.txt
# Android Studio
.gradle
local.properties
.idea/workspace.xml
.idea/libraries
.DS_Store
build
# Log
*.log
# python
*.pyc
code_analysis/hbt/dist/
code_analysis/hbt/hbt.egg-info/
apply plugin: 'android-aspectjx'
aspectjx {
// //织入遍历符合条件的库
// includeJarFilter 'universal-image-loader', 'AspectJX-Demo/library'
//排除包含‘universal-image-loader’的库
// excludeJarFilter 'universal-image-loader'
// excludeJarFilter 'org.apache.httpcomponents'
// excludeJarFilter '.jar'
}
/*
* Copyright (C) 2017 贵阳货车帮科技有限公司
*/
// Top-level build file where you can add configuration options common to all sub-projects/modules.
apply from: file("${rootDir}/BuildScript/global_config.gradle")
apply from: file("${rootDir}/BuildScript/global_function.gradle")
buildscript {
repositories {
jcenter()
maven {
// Android Team 开发的公共库稳定发布版本
name 'wlqq-releases'
url 'http://nexus.56qq.cn:81/content/repositories/wlqq-releases'
}
maven {
// Android Team 开发的公共库开发快照版本
name 'wlqq-snapshots'
url 'http://nexus.56qq.cn:81/content/repositories/wlqq-snapshots'
}
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
classpath 'com.tencent.bugly:symtabfileuploader:1.3.9'
classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.7.1'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
classpath 'org.aspectj:aspectjtools:1.8.9'
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:1.0.10'
classpath 'de.undercouch:gradle-download-task:3.2.0'
classpath 'com.vanniktech:gradle-android-apk-size-plugin:0.3.0'
classpath 'com.vanniktech:gradle-android-junit-jacoco-plugin:0.10.0'
classpath 'com.wlqq.android:gradle-plugin-exclude:1.3.0'
classpath 'com.wlqq.android:gradle-plugin-replace:1.0.0-SNAPSHOT'
classpath 'com.wlqq.android:gradle-plugin-debugger:1.0.0'
}
}
allprojects {
repositories {
maven {
// JCenter 代理仓库
url 'http://nexus.56qq.cn:81/content/repositories/bintray'
}
jcenter()
maven { url "https://www.jitpack.io" }
maven {
// 第三方没有提供 maven 仓库的 SDK jar 包
url 'http://nexus.56qq.cn:81/content/repositories/thirdparty'
}
maven {
// Android Team 开发的公共库稳定发布版本
name 'wlqq-releases'
url 'http://nexus.56qq.cn:81/content/repositories/wlqq-releases'
}
maven {
// Android Team 开发的公共库开发快照版本
name 'wlqq-snapshots'
url 'http://nexus.56qq.cn:81/content/repositories/wlqq-snapshots'
}
}
}
subprojects { subProject ->
// fix : 编码GBK的不可映射字符
// gradle 版本 2.0 以下的用户请将 JavaCompile 改为 Compile
subProject.tasks.withType(JavaCompile) {
options.encoding = 'UTF-8'
}
plugins.withId('com.android.application') {
subProject.apply from: file("${project.rootDir}/BuildScript/code_analysis/findbugs.gradle")
}
plugins.withId('com.android.library') {
subProject.apply from: file("${project.rootDir}/BuildScript/code_analysis/findbugs.gradle")
}
plugins.withId('java') {
subProject.apply from: file("${project.rootDir}/BuildScript/code_analysis/findbugs-java.gradle")
}
apply from: file("${project.rootDir}/BuildScript/code_analysis/pmd.gradle")
apply from: file("${project.rootDir}/BuildScript/code_analysis/checkstyle.gradle")
afterEvaluate { project ->
if (project.hasProperty("android")) {
android {
useLibrary 'org.apache.http.legacy'
compileSdkVersion COMPILE_SDK_VERSION
buildToolsVersion BUILD_TOOLS_VERSION
defaultConfig {
minSdkVersion MIN_SDK_VERSION
}
signingConfigs {
release {
// 如果要支持最新版的系统 Android 7.0
// 这一行必须加,否则安装时会提示没有签名
// 作用是只使用旧版签名,禁用 V2 版签名模式
// 详见:https://github.com/mcxiaoke/packer-ng-plugin/blob/master/compatibility.md
v2SigningEnabled false
}
}
lintOptions {
checkReleaseBuilds false
abortOnError false
xmlReport true
htmlReport false
lintConfig file("${project.rootDir}/BuildScript/code_analysis/lint.xml")
}
packagingOptions {
exclude 'META-INF/LGPL2.1'
exclude 'META-INF/LICENSE'
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/NOTICE'
exclude 'META-INF/NOTICE.txt'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
}
}
}
}
class VersionConfig {
String versionName
int versionCode
@Override
public String toString() {
return """\
VersionConfig{
versionName='$versionName',
versionCode=$versionCode
}"""
}
}
/**
* 基于语意化版本配置生成版本信息
*
* @see <a href="https://www.tapd.cn/20032611/markdown_wikis/view/#1120032611001009351">Android 端产品版本命名方案</a>
*
* @param major 产品大改版,比如:司机版从 4.x 到 5.x。取值范围 1..99
* @param middle 增加了大的功能模块(二手车、停车场)。取值范围 0..99
* @param minor 原功能模块的小改进。取值范围 0..99
* @param branch 用于验证技术方案的支线版本(例如司机版 TAB 动态化运营),取值范围 0..9。若大于零,说明是支线版本
* @param patch 紧急封包(hotfix 或打包新版本的插件),取值范围 0..9 。若大于零,说明是紧急封包版本
* @return
*/
VersionConfig generateVersionConfig(int major, int middle, int minor, int branch, int patch) {
if (major < 1 || major > 99) {
throw new GradleException("major expected in 1..99, but actual: ${major}")
}
if (middle < 0 || middle > 99) {
throw new GradleException("middle expected in 0..99, but actual: ${middle}")
}
if (minor < 0 || minor > 99) {
throw new GradleException("minor expected in 0..99, but actual: ${minor}")
}
if (branch < 0 || branch > 9) {
throw new GradleException("branch expected in 0..9, but actual: ${branch}")
}
if (patch < 0 || patch > 9) {
throw new GradleException("patch expected in 0..9, but actual: ${patch}")
}
String versionName
int versionCode
if (patch > 0) {
versionName = "$major.$middle.$minor.$branch.$patch"
} else if (branch > 0) {
versionName = "$major.$middle.$minor.$branch"
} else {
versionName = "$major.$middle.$minor"
}
versionCode = major * 1000000 + middle * 10000 + minor * 100 + branch * 10 + patch
return new VersionConfig('versionName': versionName, 'versionCode': versionCode)
}
def android_support_version = '25.3.1'
def android_test_support_version = '0.5'
def espresso_version = '2.2.2'
ext {
gradleDependencies = [
"support-v4" : "com.android.support:support-v4:${android_support_version}",
"cardview-v7" : "com.android.support:cardview-v7:${android_support_version}",
"appcompat-v7" : "com.android.support:appcompat-v7:${android_support_version}",
"recyclerview-v7" : "com.android.support:recyclerview-v7:${android_support_version}",
"support-annotations" : "com.android.support:support-annotations:${android_support_version}",
"awesomelog" : "io.github.shaobin0604:awesomelog:1.0.1",
"BaseRecyclerViewAdapterHelper": "com.github.CymChad:BaseRecyclerViewAdapterHelper:v1.9.7",
"base-adapter-helper" : "com.joanzapata.android:base-adapter-helper:1.1.11",
"protobuf-java" : "com.google.protobuf:protobuf-java:2.4.1",
"atsl-runner" : "com.android.support.test:runner:${android_test_support_version}",
"atsl-rules" : "com.android.support.test:rules:${android_test_support_version}",
"espresso-core" : "com.android.support.test.espresso:espresso-core:${espresso_version}",
"espresso-contrib" : "com.android.support.test.espresso:espresso-contrib:${espresso_version}",
"uiautomator-v18" : "com.android.support.test.uiautomator:uiautomator-v18:2.1.2",
]
}
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE module PUBLIC
"-//Puppy Crawl//DTD Check Configuration 1.3//EN"
"http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
<module name="Checker">
<property name="severity" value="warning" />
<!-- 文件长度不超过2000行,default2000行 -->
<module name="FileLength" />
<module name="FileTabCharacter" />
<module name="SuppressWarningsFilter" />
<module name="TreeWalker">
<!-- Checks for whitespace -->
<!-- See http://checkstyle.sf.net/config_whitespace.html -->
<module name="Indentation">
<property name="arrayInitIndent" value="8" />
<property name="lineWrappingIndentation" value="8" />
</module>
<module name="GenericWhitespace" />
<module name="EmptyForIteratorPad" />
<module name="MethodParamPad" />
<module name="NoWhitespaceAfter" />
<module name="NoWhitespaceBefore" />
<module name="OperatorWrap" />
<module name="ParenPad" />
<module name="TypecastParenPad" />
<module name="WhitespaceAfter" />
<module name="WhitespaceAround" />
<!-- Checks for blocks. You know, those {}'s -->
<!-- See http://checkstyle.sf.net/config_blocks.html -->
<!-- 检查左侧大括号 左侧大括号必须放在前一行代码的行尾 -->
<module name="LeftCurly">
<property name="severity" value="warning" />
<message key="line.previous" value="左侧大括号必须放在前一行代码的行尾,不计入到100个字符内" />
</module>
<!-- 对关键字else、try和catch的右侧大括号放置位置进行检查 -->
<module name="RightCurly">
<property name="severity" value="warning" />
<!--与下一语句放在同一行 -->
<property name="option" value="same" />
</module>
<module name="NeedBraces">
<property name="tokens" value="LITERAL_DO, LITERAL_IF, LITERAL_ELSE, LITERAL_FOR, LITERAL_WHILE" />
</module>
<!--注解设置-->
<module name="AnnotationUseStyle">
<!--注解的参数样式 忽略-->
<property name="elementStyle" value="ignore" />
<!--是否在数组元素后尾随逗号 忽略 -->
<property name="trailingArrayComma" value="ignore" />
<!--检查是否保留结束括号 忽略 -->
<property name="closingParens" value="ignore" />
</module>
<!-- 命名规范 -->
<!-- 类,接口的命名,匹配规则默认:(^[A-Z][a-zA-Z0-9]*$),必须以大写字母开始 -->
<module name="TypeName" />
<!-- 变量的检查 匹配规则 非static的member是 m开头,然后是大写字母 再是其他-->
<module name="MemberName">
<property name="format" value="^m[A-Z][a-zA-Z0-9]*$" />
<property name="applyToPublic" value="false" />
</module>
<!-- 方法名的检查 匹配规则默认^[a-z][a-zA-Z0-9]*$ 范围:方法名 命名为小写-->
<module name="MethodName" />
<!-- 方法的参数名 匹配规则默认^[a-z][a-zA-Z0-9]*$ 范围:方法中的参数名 命名为小写-->
<module name="ParameterName " />
<!-- 常量名的检查 匹配规则默认^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$ 范围:常量(static , final 字段) 命名为大写-->
<module name="ConstantName" />
<!-- static 非final的变量 -->
<module name="StaticVariableName">
<property name="format" value="^s[A-Z][a-zA-Z0-9]*$" />
</module>
<!-- 限制导入.*包的检查 -->
<module name="AvoidStarImport">
<!-- 实例;import java.util.*;.-->
<property name="allowClassImports" value="false" />
<!-- 实例 ;import static org.junit.Assert.*;-->
<property name="allowStaticMemberImports" value="true" />
</module>
<!-- 限制导入多余的包,例如java.lang.String -->
<module name="RedundantImport" />
<!-- 限制导入未使用过的类 -->
<module name="UnusedImports">
<property name="processJavadoc" value="true" />
</module>
<!--代码规范-->
<!-- 每行不超过120-->
<module name="LineLength">
<property name="max" value="120" />
<!--
Ignore Javadoc comments since references to other elements (@see
tag, etc.) may get rather long and should not be truncated.
-->
<property name="ignorePattern" value="^ *\* *[^ ]+$"/>
</module>
<!-- 即制方法和构造函数行数不超过多少行,默认160行(不包括空行) -->
<module name="MethodLength">
<property name="countEmpty" value="false" />
<property name="tokens" value="METHOD_DEF,CTOR_DEF" />
<property name="max" value="160" />
</module>
<!-- 不能出现大块空白区域 -->
<module name="GenericWhitespace" />
<!-- 检查是否有未初始化的循环变量 -->
<module name="EmptyForInitializerPad" />
<!-- 不许出现空语句 int a = 0; //正常 ; // 这里就是一个空的语句 -->
<module name="EmptyStatement" />
<!-- 不能容忍魔法数,范围 double,int 忽略0,1 -->
<module name="MagicNumber">
<property name="tokens" value="NUM_DOUBLE, NUM_FLOAT" />
<property name="ignoreNumbers" value="0,1" />
<property name="ignoreAnnotation" value="true" />
</module>
<!-- String的比较不能用!= 和 == -->
<module name="StringLiteralEquality" />
<!-- 限制for循环最多嵌套3层 -->
<module name="NestedForDepth">
<property name="max" value="3" />
</module>
<!-- if最多嵌套5层 -->
<module name="NestedIfDepth">
<property name="max" value="5" />
</module>
<!-- 同一行不能有多个声明 -->
<module name="MultipleVariableDeclarations" />
<!-- 检查数组声明风格 只能为java 例如: String[] args -->
<module name="ArrayTypeStyle" />
<!--避免 null.equals("sss")情况-->
<module name="EqualsAvoidNull" />
<!-- 异常抛出数量定义 -->
<module name="ThrowsCount">
<metadata name="net.sf.eclipsecs.core.comment" value="最大异常抛出个数" />
<property name="max" value="3" />
</module>
<!-- 参数个数定义 最多7个
<module name="ParameterNumber">
<property name="max" value="7" />
</module>
-->
<!--try catch 异常处理数量 1-->
<module name="NestedTryDepth ">
<property name="max" value="1" />
</module>
<module name="SuppressWarningsHolder" />
</module>
</module>
\ No newline at end of file
apply plugin: 'checkstyle'
checkstyle {
toolVersion '7.8.1'
showViolations true
}
task checkstyle(type: Checkstyle, group: 'Verification', description: 'Runs code style checks') {
source 'src'
include '**/*.java'
exclude '**/gen/**'
exclude '**/R.java'
exclude '**/BuildConfig.java'
exclude '**/com/wlqq/http/**'
exclude '**/com/wlqq/securityhttp/**'
// 测试代码
exclude '**/test/**'
// PhantomLib:PhantomCore 中使用的第三方开源库代码 https://github.com/jOOQ/jOOR
exclude '**/org/joor/**'
// PhantomLib:PhantomCore 使用的 android framework Stub 类
exclude '**/android/content/pm/**'
// PhantomLib:PhantomCore 中用于反射 android framework 类中的工具类
exclude '**/mirror/**'
// PhantomLib:dexmaker 中使用的第三方开源库代码 https://github.com/linkedin/dexmaker
exclude '**/com/android/dx/**'
exclude '**/com/google/dexmaker/**'
configFile = file("${project.rootDir}/BuildScript/code_analysis/checkstyle-config.xml")
// empty classpath
classpath = files()
ignoreFailures true
reports {
xml.enabled true
html.enabled !xml.enabled
}
}
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2016 - 2017 贵阳货车帮科技有限公司 -->
<FindBugsFilter>
<!-- http://stackoverflow.com/questions/7568579/eclipsefindbugs-exclude-filter-files-doesnt-work -->
<Match>
<Class name="~.*\.R\$.*"/>
</Match>
<Match>
<Class name="~.*\.Manifest\$.*"/>
</Match>
<!-- All bugs in test classes, except for JUnit-specific bugs -->
<Match>
<Class name="~.*\.*Test"/>
<Not>
<Bug code="IJU"/>
</Not>
</Match>
<Match>
<Bug code="UwF"/>
</Match>
<!-- PhantomLib:dexmaker 中使用的第三方开源库代码 https://github.com/linkedin/dexmaker -->
<Match>
<Package name="~^com\.android\.dx.*"/>
</Match>
<Match>
<Package name="~^com\.google\.dexmaker.*"/>
</Match>
<!-- PhantomLib:PhantomCore 中使用的第三方开源库代码 https://github.com/jOOQ/jOOR -->
<Match>
<Package name="org.joor"/>
</Match>
<!-- PhantomLib:PhantomCore 使用的 Android Framework 中的隐藏类 -->
<Match>
<Package name="~^android\.content\.pm.*"/>
</Match>
<!-- PhantomLib:PhantomCore 中用于方便反射 Android Framework 的类-->
<Match>
<Package name="~^mirror.*"/>
</Match>
<!-- WLUtils 中引入的apache开源库中的代码 https://github.com/apache -->
<Match>
<Package name="~^com\.wlqq\.utils.*\.thirdparty.*"/>
</Match>
<!--WLUtils 获取应用渠道中引入的第三方代码 https://github.com/mcxiaoke/packer-ng-plugin -->
<Match>
<Class name="~^com\.wlqq\.utils\.PackerNg.*"/>
</Match>
<!--UniversalLogReporter 忽略DiskLruCache -->
<Match>
<Class name="com.wlqq.ulreporter.cache.filecache.DiskLruCache" />
</Match>
<!-- WLHttp 中的原始http请求忽略检查 -->
<Match>
<Package name="~^com\.wlqq\.http.*"/>
</Match>
<Match>
<Package name="~^com\.wlqq\.securityhttp.*"/>
</Match>
</FindBugsFilter>
\ No newline at end of file
/*
* Copyright (C) 2017 贵阳货车帮科技有限公司
*/
apply plugin: 'findbugs'
findbugs {
// do not exit, when finding issues
ignoreFailures = true
reportLevel = "medium"
effort = "max"
}
task findbugs(type: FindBugs, dependsOn: 'assemble', group: 'Verification',
description: 'Inspect java bytecode for bugs') {
excludeFilter = new File("${project.rootDir}/BuildScript/code_analysis/findbugs-filter.xml")
// check all code, include code of testcase
classes = files("$projectDir.absolutePath/build/classes")
source = 'src'
include '**/*.java'
exclude '**/gen/**'
classpath = files()
reports {
xml.enabled = true
html.enabled = !xml.enabled
}
}
\ No newline at end of file
apply plugin: 'findbugs'
findbugs {
// do not exit, when finding issues
ignoreFailures = true
reportLevel = "medium"
effort = "max"
}
task findbugs(type: FindBugs, group: 'Verification',
description: 'Inspect java bytecode for bugs') {
excludeFilter = new File("${project.rootDir}/BuildScript/code_analysis/findbugs-filter.xml")
// check all code, include code of testcase
classes = fileTree("build/intermediates/classes/")
source = fileTree('src/')
classpath = files()
reports {
xml.enabled = true
html.enabled = !xml.enabled
}
}
\ No newline at end of file
Copyright (c) 2016 The Python Packaging Authority (PyPA)
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# Include the license file
include LICENSE.txt
# Include the data files
recursive-include data *
package com.wlqq.data.net;
import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import com.raizlabs.android.dbflow.annotation.NotNull;
import com.wlqq.data.request.LoginParams;
import com.wlqq.data.response.HttpResponse;
import com.wlqq.data.utils.Preconditions;
import com.wlqq.encrypt.BaseDESDecryptor;
import com.wlqq.encrypt.BaseDESEncryptor;
import com.wlqq.login.model.Session;
import com.wlqq.utils.LogUtil;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import io.reactivex.Observable;
import io.reactivex.functions.Function;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
public class GasApiImpl implements IGasApi {
private final int CLIENT_TIMEOUT = 60; //60 seconds
private IGasService mGasService;
public GasApiImpl(@NotNull String baseUrl, @NotNull BaseDESEncryptor encryptor,
@NotNull BaseDESDecryptor decryptor){
Preconditions.checkNotNull(baseUrl);
Preconditions.checkNotNull(encryptor);
Preconditions.checkNotNull(decryptor);
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(
new HttpLoggingInterceptor.Logger() {
@Override
public void log(String message) {
LogUtil.d("hcbLog", message);
}
});
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient().newBuilder()
.addInterceptor(interceptor)
.readTimeout(CLIENT_TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(CLIENT_TIMEOUT, TimeUnit.SECONDS)
.connectTimeout(CLIENT_TIMEOUT, TimeUnit.SECONDS)
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.client(client)
.addConverterFactory(new EncryptDecryptConverterFactory(encryptor, decryptor))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
mGasService = retrofit.create(IGasService.class);
}
private static class ResponseStatusChecker<T> implements Function<HttpResponse<T>, T> {
@Override
public T apply(HttpResponse<T> response) throws Exception {
if (response.isStatusOK()) {
return response.content;
}
throw new ResponseStatusErrorException(response.errorMsg);
}
}
@Override
public Observable<Session> login(@NotNull Map<String, String> headers,
@NotNull LoginParams params) {
Preconditions.checkNotNull(headers);
Preconditions.checkNotNull(params);
return mGasService.login(headers, params).map(new ResponseStatusChecker<Session>());
}
}
/*
* Copyright (C) 2017 贵阳货车帮科技有限公司
*/
package com.wlqq.data.net;
import com.raizlabs.android.dbflow.annotation.NotNull;
import com.wlqq.data.request.LoginParams;
import com.wlqq.login.model.Session;
import java.util.Map;
import io.reactivex.Observable;
/**
* Interfaces of gas station.
*/
public interface IGasApi {
/**
* Get an {@link Observable} which will emit a {@link Session}.
*/
Observable<Session> login(@NotNull Map<String, String> headers, @NotNull LoginParams params);
void logout();
}
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from jira import JIRA, JIRAError
from rbtools.utils.commands import get_review_request
from rbtools.api.client import RBClient
import subprocess
import re
import os
import platform
from sourcefile import SourceFile
SUCCESS = ''
class Action(object):
subject_re = r'^\s*(feat|fix|docs|style|refactor|chore)?\s*\((.*)\)\s*[:|:]\s*(.+)\n'
new_line_re = r'\n'
body_re = r'((?:.+\n)+)'
affect_re = r'^([测试影响|影响].*)'
commit_re = subject_re + new_line_re + body_re + new_line_re + affect_re
rename_re = r'^\s*R\d+\s+.+[\.java|\.xml]\s+(.+(\.java|\.xml))\s*$'
update_re = r'^\s*[M|A]\s+(.+(\.java|\.xml))\s*$'
GROUP_INDEX_FILE_NAME = 1
STATUS_SUBMITTED = 'submitted'
def __init__(self, config, ctx, commit_message=''):
self.config = config
self.context = ctx
self.commit_message = commit_message or subprocess.check_output('git log --format=%B -n 1', shell=True).strip()
def get_issue_id(self):
return re.compile(self.subject_re).match(self.commit_message).group(2)
def get_review_request_by_id(self, request_id):
client = RBClient(self.config.reviewboard_url)
return get_review_request(request_id, client.get_root())
def get_review_url(self, lines=None):
if not lines:
lines = self.commit_message.split('\n')
review_request_pattern = re.compile(r'^\s*%s/r/(\d+)/\s*$' % self.config.reviewboard_url)
for line in lines:
result = review_request_pattern.match(line)
if result:
return line, result.group(1)
else:
print(u'[WARN]: 没有解析到reviewboard url')
return None, None
def get_committed(self):
commit_log = subprocess.check_output('git log --name-status --pretty=format:"" -n 1', shell=True)
return [SourceFile.create(os.path.dirname(f), os.path.basename(f)) for f in
self.get_commit_file_list(commit_log.split('\n'))]
def get_commit_file_list(self, lines):
files = list()
rename_pattern = re.compile(self.rename_re)
update_pattern = re.compile(self.update_re)
for line in lines:
result = rename_pattern.match(line)
if result:
files.append(result.group(self.GROUP_INDEX_FILE_NAME))
continue
result = update_pattern.match(line)
if result:
files.append(result.group(self.GROUP_INDEX_FILE_NAME))
return files
class WorkspaceCleanAction(Action):
def do(self):
if self.config.clean_check and \
'' != subprocess.check_output('git status --porcelain --untracked-files=no', shell=True):
return u'WARNING: 有未commit的文件。请commit后再发送review request'
return SUCCESS
class CommitCheckAction(Action):
help_text = u'''
commit message格式不符合要求
示例:
>>>>>>
feat(ANDROID_INFRA-74): seperate registration from identity verification
原来的两个接口实现不动,但是标为Deprecated
1. /mobile/consignor/register/quick: doRegister()
2. /mobile/consignor/real-name-authentication: doRealNameCertify()
新增两个接口:
1. /mobile/consignor/register/quick: doQuickRegister()
2. /mobile/consignor/identity-auth-request: submitIdentityVerifyRequest()
测试影响: 注册和验证流程
>>>>>>
完整例子请参考: http://confluence.56qq.cn/pages/viewpage.action?pageId=2263806
'''
error_message = u'commit message格式不符合要求'
def do(self):
if not self.config.commit_message_check:
return ''
if not self.commit_message:
return u'commit message为空'
if re.compile(self.commit_re, re.MULTILINE).match(self.commit_message):
return SUCCESS
print(self.help_text)
return self.error_message
class LandCheckAction(Action):
def do(self):
review_url, request_id = self.get_review_url()
if not request_id:
return u'未能查询到review request id'
print(u'开始检查review request是否能够提交: ' + request_id)
review_request = self.get_review_request_by_id(request_id)
if not review_request.approved:
print(review_request.approval_failure)
return u'reviewboard检查失败'
if not self.exist_remote_branch():
return u'不存在的远程分支。请检查分支名。'
return SUCCESS
def exist_remote_branch(self):
remote_name = 'remotes/origin/{}'.format(self.context.params['branch'])
for line in subprocess.check_output('git branch -a', shell=True).split('\n'):
if line.strip() == remote_name:
return True
else:
return False
class PushAction(Action):
def do(self):
return SUCCESS if subprocess.call('git push origin HEAD:' + self.context.params['branch'], shell=True) == 0 \
else u'push失败'
class JiraUpdateAction(Action):
def do(self):
authed_jira = JIRA(server=self.config.jira_url, basic_auth=(self.config.jira_user, self.config.jira_password))
issue = authed_jira.issue(self.get_issue_id())
authed_jira.add_comment(issue, self.commit_message)
# '4' 代表状态是 开发中
try:
authed_jira.transition_issue(issue, transition='4')
except JIRAError:
# 当jira状态已经为“开发中”时,这里transition会抛异常。ignore it
pass
return SUCCESS
class CloseReviewboardAction(Action):
def do(self):
commit_id = subprocess.check_output('git log --format="%H" -n 1', shell=True)
description = 'hbt closed: {}'.format(commit_id)
_, request_id = self.get_review_url()
if not request_id:
return u'未能查询到review request id'
review_request = self.get_review_request_by_id(request_id)
if review_request.status == self.STATUS_SUBMITTED:
return u'Review request {} is already submitted.'.format(request_id)
review_request.update(status=self.STATUS_SUBMITTED, description=description)
return SUCCESS
class CopyrightUpdateAction(Action):
def do(self):
for f in self.get_committed():
f.add_copyright()
return SUCCESS
class AmendMessageAction(Action):
def do(self):
return SUCCESS if subprocess.call('git commit -a --amend --no-edit', shell=True) == 0 else \
u'更新commit message失败'
class StyleCheckAction(Action):
CYGWIN_SYSTEM_PREFIX = 'CYGWIN'
CHECKSTYLE_JAR_FILE_NAME = 'checkstyle-all.jar'
CHECKSTYLE_CONFIG_FILE_NAME = 'checkstyle-config.xml'
def do(self):
return SUCCESS if self.config.checkstyle or self.style_errors() == 0 else\
'\nWARNING: You must fix the errors and warnings first, then post review again'
def style_errors(self):
hbt_dir = self.config.hbt_dir
if platform.system().startswith(self.CYGWIN_SYSTEM_PREFIX):
# change to cygwin path
hbt_dir = subprocess.check_output('cygpath -m "' + self.config.hbt_dir + '"', shell=True).strip()
jar_file = os.path.join(hbt_dir, self.CHECKSTYLE_JAR_FILE_NAME)
config_file = os.path.join(hbt_dir, self.CHECKSTYLE_CONFIG_FILE_NAME)
# check code command
command = 'java -jar ' + jar_file + ' -c ' + config_file
for f in self.get_committed():
if f.check_style(command) != 0:
return 1
return 0
class RBTAction(Action):
review_request_re = r'^\s*%s/r/(\d+)/\s*$'
CHECKSTYLE_TAG = '[checked_4_0_2]'
def do(self):
rbt_command = 'rbt %s %s' % (self.context.info_name.encode('ascii', 'ignore'), ' '.join(arg for arg in self.context.params['rbt_args']))
summary = self.commit_message.split('\n')[0].replace('"', '\\\"')
if summary:
rbt_command += ' --summary "%s %s"' % (self.CHECKSTYLE_TAG, summary)
post_message = subprocess.check_output(rbt_command, shell=True)
review_url, request_id = self.get_review_url(post_message.split('\n'))
if not review_url:
return
new_message = self.update_commit_message(review_url).replace('"', '\\\"')
# update review board url to commit message
subprocess.call('git commit --amend -m "%s"' % new_message, shell=True)
return SUCCESS
def update_commit_message(self, review_url):
lines = []
has_updated = False
for line in self.commit_message.split('\n'):
if re.compile(self.review_request_re % self.config.reviewboard_url).match(line):
lines.append(review_url)
has_updated = True
else:
lines.append(line)
if not has_updated:
lines.append(review_url)
lines.append('%s/browse/%s' % (self.config.jira_url, self.get_issue_id()))
return '\n'.join(lines)
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE module PUBLIC
"-//Puppy Crawl//DTD Check Configuration 1.3//EN"
"http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
<module name="Checker">
<property name="severity" value="warning" />
<!-- 文件长度不超过2000行,default2000行 -->
<module name="FileLength" />
<module name="FileTabCharacter" />
<module name="SuppressWarningsFilter" />
<module name="TreeWalker">
<!-- Checks for whitespace -->
<!-- See http://checkstyle.sf.net/config_whitespace.html -->
<module name="Indentation">
<property name="arrayInitIndent" value="8" />
<property name="lineWrappingIndentation" value="8" />
</module>
<module name="GenericWhitespace" />
<module name="EmptyForIteratorPad" />
<module name="MethodParamPad" />
<module name="NoWhitespaceAfter" />
<module name="NoWhitespaceBefore" />
<module name="OperatorWrap" />
<module name="ParenPad" />
<module name="TypecastParenPad" />
<module name="WhitespaceAfter" />
<module name="WhitespaceAround" />
<!-- Checks for blocks. You know, those {}'s -->
<!-- See http://checkstyle.sf.net/config_blocks.html -->
<!-- 检查左侧大括号 左侧大括号必须放在前一行代码的行尾 -->
<module name="LeftCurly">
<property name="severity" value="warning" />
<message key="line.previous" value="左侧大括号必须放在前一行代码的行尾,不计入到100个字符内" />
</module>
<!-- 对关键字else、try和catch的右侧大括号放置位置进行检查 -->
<module name="RightCurly">
<property name="severity" value="warning" />
<!--与下一语句放在同一行 -->
<property name="option" value="same" />
</module>
<module name="NeedBraces">
<property name="tokens" value="LITERAL_DO, LITERAL_IF, LITERAL_ELSE, LITERAL_FOR, LITERAL_WHILE" />
</module>
<!--注解设置-->
<module name="AnnotationUseStyle">
<!--注解的参数样式 忽略-->
<property name="elementStyle" value="ignore" />
<!--是否在数组元素后尾随逗号 忽略 -->
<property name="trailingArrayComma" value="ignore" />
<!--检查是否保留结束括号 忽略 -->
<property name="closingParens" value="ignore" />
</module>
<!-- 命名规范 -->
<!-- 类,接口的命名,匹配规则默认:(^[A-Z][a-zA-Z0-9]*$),必须以大写字母开始 -->
<module name="TypeName" />
<!-- 变量的检查 匹配规则 非static的member是 m开头,然后是大写字母 再是其他-->
<module name="MemberName">
<property name="format" value="^m[A-Z][a-zA-Z0-9]*$" />
<property name="applyToPublic" value="false" />
</module>
<!-- 方法名的检查 匹配规则默认^[a-z][a-zA-Z0-9]*$ 范围:方法名 命名为小写-->
<module name="MethodName" />
<!-- 方法的参数名 匹配规则默认^[a-z][a-zA-Z0-9]*$ 范围:方法中的参数名 命名为小写-->
<module name="ParameterName " />
<!-- 常量名的检查 匹配规则默认^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$ 范围:常量(static , final 字段) 命名为大写-->
<module name="ConstantName" />
<!-- static 非final的变量 -->
<module name="StaticVariableName">
<property name="format" value="^s[A-Z][a-zA-Z0-9]*$" />
</module>
<!-- 限制导入.*包的检查 -->
<module name="AvoidStarImport">
<!-- 实例;import java.util.*;.-->
<property name="allowClassImports" value="false" />
<!-- 实例 ;import static org.junit.Assert.*;-->
<property name="allowStaticMemberImports" value="true" />
</module>
<!-- 限制导入多余的包,例如java.lang.String -->
<module name="RedundantImport" />
<!-- 限制导入未使用过的类 -->
<module name="UnusedImports">
<property name="processJavadoc" value="true" />
</module>
<!--代码规范-->
<!-- 每行不超过120-->
<module name="LineLength">
<property name="max" value="120" />
<!--
Ignore Javadoc comments since references to other elements (@see
tag, etc.) may get rather long and should not be truncated.
-->
<property name="ignorePattern" value="^ *\* *[^ ]+$"/>
</module>
<!-- 即制方法和构造函数行数不超过多少行,默认160行(不包括空行) -->
<module name="MethodLength">
<property name="countEmpty" value="false" />
<property name="tokens" value="METHOD_DEF,CTOR_DEF" />
<property name="max" value="160" />
</module>
<!-- 不能出现大块空白区域 -->
<module name="GenericWhitespace" />
<!-- 检查是否有未初始化的循环变量 -->
<module name="EmptyForInitializerPad" />
<!-- 不许出现空语句 int a = 0; //正常 ; // 这里就是一个空的语句 -->
<module name="EmptyStatement" />
<!-- 不能容忍魔法数,范围 double,int 忽略0,1 -->
<module name="MagicNumber">
<property name="tokens" value="NUM_DOUBLE, NUM_FLOAT" />
<property name="ignoreNumbers" value="0,1" />
<property name="ignoreAnnotation" value="true" />
</module>
<!-- String的比较不能用!= 和 == -->
<module name="StringLiteralEquality" />
<!-- 限制for循环最多嵌套3层 -->
<module name="NestedForDepth">
<property name="max" value="3" />
</module>
<!-- if最多嵌套5层 -->
<module name="NestedIfDepth">
<property name="max" value="5" />
</module>
<!-- 同一行不能有多个声明 -->
<module name="MultipleVariableDeclarations" />
<!-- 检查数组声明风格 只能为java 例如: String[] args -->
<module name="ArrayTypeStyle" />
<!--避免 null.equals("sss")情况-->
<module name="EqualsAvoidNull" />
<!-- 异常抛出数量定义 -->
<module name="ThrowsCount">
<metadata name="net.sf.eclipsecs.core.comment" value="最大异常抛出个数" />
<property name="max" value="3" />
</module>
<!-- 参数个数定义 最多7个
<module name="ParameterNumber">
<property name="max" value="7" />
</module>
-->
<!--try catch 异常处理数量 1-->
<module name="NestedTryDepth ">
<property name="max" value="1" />
</module>
<module name="SuppressWarningsHolder" />
</module>
</module>
\ No newline at end of file
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from os.path import expanduser, abspath, dirname, join
import os
import io
import json
import six
DEFAULT_CONFIG_LOCATION = expanduser("~/.hbtrc")
DEFAULT_CONFIG = {
'jira_url': 'http://jira.56qq.cn',
'reviewboard_url': 'http://reviewboard.56qq.com',
'clean_check': True,
'commit_message_check': True,
'checkstyle': True,
'hbt_dir': dirname(abspath(__file__)),
'jira_user': '',
'jira_password': ''
}
class InvalidConfigError(ValueError):
"""Raised if an invalid configuration is encountered."""
def __init__(self, message):
super(InvalidConfigError, self).__init__(message)
class HBTConfig(object):
def __init__(self, filename=None):
if filename is None and os.path.isfile(DEFAULT_CONFIG_LOCATION):
filename = DEFAULT_CONFIG_LOCATION
self.override(DEFAULT_CONFIG)
if filename is not None:
try:
with io.open(filename, encoding='utf-8') as f:
file_config = json.loads(f.read())
except ValueError as e:
raise InvalidConfigError("Failed to read configuration file '{}'. Error: {}".format(filename, e))
self.override(file_config)
if not (self.jira_user and self.jira_password and self.hbt_dir):
raise InvalidConfigError(u"jira_user, jira_password, hbt_dir不能为空")
# noinspection PyCompatibility
def make_unicode(self, config):
if six.PY2:
# Sometimes (depending on the source of the config value) an argument will be str instead of unicode
# to unify that and ease further usage of the config, we convert everything to unicode
for k, v in config.items():
if type(v) is str:
config[k] = unicode(v, "utf-8")
return config
def override(self, config):
# abs_path_config = self.make_unicode(self.make_paths_absolute(config, ["path", "response_log"]))
self.__dict__.update(config)
def make_paths_absolute(self, config, keys):
abs_path_config = dict(config)
for key in keys:
if key in abs_path_config and abs_path_config[key] is not None and not os.path.isabs(abs_path_config[key]):
abs_path_config[key] = join(os.getcwd(), abs_path_config[key])
return abs_path_config
if __name__ == '__main__':
c = HBTConfig()
print c
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import click
import os
from sourcefile import SourceFile
from config import HBTConfig
import pipeline
RBT_COMMAND_NAME = 'rbt'
CONTEXT_SETTINGS = dict(token_normalize_func=lambda x: RBT_COMMAND_NAME)
config = HBTConfig()
class AliasedGroup(click.Group):
def get_command(self, ctx, cmd_name):
return click.Group.get_command(self, ctx, cmd_name) or click.Group.get_command(self, ctx, RBT_COMMAND_NAME)
@click.command(cls=AliasedGroup)
def cli():
pass
@click.command()
@click.argument('source_dir')
def copyright(source_dir):
to_parse = list()
for root, dirs, files in os.walk(source_dir):
for file in files:
source_file = SourceFile.create(root, file)
if source_file:
to_parse.append(source_file)
[(click.echo(f.name), f.add_copyright()) for f in to_parse]
@click.command()
@click.argument('branch')
@click.pass_context
def land(ctx, branch):
import pipeline
pipeline.Pipeline.create_pipeline(config, ctx, pipeline.profile_land).run()
@click.command(context_settings=dict(ignore_unknown_options=True,))
@click.argument('rbt_args', nargs=-1, type=click.UNPROCESSED)
@click.pass_context
def rbt(ctx, rbt_args):
pipeline.Pipeline.create_pipeline(config, ctx, pipeline.profile_rbt).run()
def main():
cli.add_command(copyright)
cli.add_command(land)
cli.add_command(rbt)
cli()
if __name__ == '__main__':
main()
{
"jira_url": "http://jira.56qq.cn",
"reviewboard_url": "http://reviewboard.56qq.com",
"clean_check": true,
"commit_message_check": true,
"checkstyle": true,
"jira_user": "user",
"jira_password": "password",
"hbt_dir": "/a/b/c"
}
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
from actions import *
class InvalidCommitError(BaseException):
"""Raised if an invalid configuration is encountered."""
def __init__(self, message):
super(InvalidCommitError, self).__init__(message)
profile_land = 'land'
profile_rbt = 'rbt'
pipeline_land = [WorkspaceCleanAction, CommitCheckAction, LandCheckAction, PushAction, JiraUpdateAction, CloseReviewboardAction]
pipeline_rbt = [WorkspaceCleanAction, CommitCheckAction, CopyrightUpdateAction, AmendMessageAction, StyleCheckAction, RBTAction]
profiles = {
profile_land: pipeline_land,
profile_rbt: pipeline_rbt
}
class Pipeline(object):
def __init__(self, config, ctx, actions_class):
self.queue = []
commit_message = subprocess.check_output('git log --format=%B -n 1', shell=True).strip()
for action in actions_class:
self.queue.append(action(config, ctx, commit_message))
def run(self):
for action in self.queue:
try:
err = action.do()
if err:
print(err)
sys.exit(1)
except Exception as e:
print('unknown error:', e.message)
sys.exit(1)
@staticmethod
def create_pipeline(config, ctx, profile):
return Pipeline(config, ctx, profiles.get(profile))
if __name__ == '__main__':
subject_re = r'^\s*(feat|fix|docs|style|refactor|chore)?\s*\((.*)\)\s*[:|:]\s*(.+)\n'
new_line_re = r'\n'
body_re = r'((?:.+\n)+)'
affect_re = r'^([测试影响|影响].*)\n'
msg = """feat(ANDROID_INFRA-57): 修改copyright后自动commit文件。消除手动commit的困扰。
扫描被修改的文件,发现copyright不对的,进行修改。然后自动提交commit相关改动。
扫描被修改的文件,发现copyright不对的,进行修改。然后自动提交commit相关改动。
扫描被修改的文件,发现copyright不对的,进行修改。然后自动提交commit相关改动。
测试影响: copyright 相关测试
rev
"""
# print re.compile(subject_re + new_line_re + body_re + new_line_re + affect_re, re.MULTILINE).match(msg).group(2)
print (re.compile(subject_re, re.MULTILINE).match(msg).group(2))
pass
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import subprocess
import re
import time
from jira import JIRA, JIRAError
from rbtools.utils.commands import get_review_request
from rbtools.api.client import RBClient
class SourceFile(object):
COPYRIGHT_TEMPLATE = '%s'
TYPE_JAVA = 'java'
TYPE_XML = 'xml'
TYPE_OTHERS = 'others'
LEVEL_ERROR = 'ERROR'
LEVEL_WARN = 'WARN'
BODY_PATTERN = None
def __init__(self, path, name):
self.path = path
self.name = name
self.abs_path = os.path.join(path, name)
self.created = self.get_create_year()
self.current = self.get_current_year()
@staticmethod
def create(path, name):
if name.endswith(SourceFile.TYPE_JAVA):
return JavaFile(path, name)
elif name.endswith(SourceFile.TYPE_XML):
return XmlFile(path, name)
else:
return None
def check_style(self, command):
return 0
def get_level_count(self, lines, level):
return len(re.compile(r"\[%s\]" % level).findall(lines))
def add_copyright(self):
with open(self.abs_path) as f:
lines = self.remove_leading_comment(f.readlines())
with open(self.abs_path, 'w') as f:
f.write(self.get_copyright_text())
f.writelines(lines)
def get_create_year(self):
command = 'git log --follow --format=%ai --reverse -- {} | head -1'.format(self.abs_path)
# output example: 2015-11-10 15:44:29 +0800
date_format = subprocess.check_output(command, shell=True)
if len(date_format) == 0:
return self.get_current_year()
return date_format.split('-')[0]
def get_current_year(self):
return time.strftime('%Y', time.localtime(time.time()))
def remove_leading_comment(self, lines):
idx = 0
for idx, line in enumerate(lines):
if self.BODY_PATTERN.match(line):
break
return lines[idx:]
def get_copyright_text(self):
return self.COPYRIGHT_TEMPLATE % \
(self.current if self.created == self.current else
'%s - %s' % (self.created, self.current))
class JavaFile(SourceFile):
COPYRIGHT_TEMPLATE = """\
/*
* Copyright (C) %s 贵阳货车帮科技有限公司
*/\n
"""
BODY_PATTERN = re.compile(r'^package.+$')
def check_style(self, command):
result = subprocess.check_output(command + ' ' + self.abs_path, shell=True)
return self.get_level_count(result, self.LEVEL_ERROR) + self.get_level_count(result, self.LEVEL_WARN)
class XmlFile(SourceFile):
COPYRIGHT_TEMPLATE = """\
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) %s 贵阳货车帮科技有限公司 -->\n"""
BODY_PATTERN = re.compile(r'^<(!DOCTYPE|[a-zA-Z]).+$')
class WorkSpace(object):
SUBJECT_PATTERN = r'^\s*(feat|fix|docs|style|refactor|chore)?\s*\((.*)\)\s*[:|:]\s*(.+)$'
AFFECT_PATTERN = r'^[测试影响|影响]'
REVIEW_REQUEST_PATTERN = r'^\s*%s/r/(\d+)/\s*$'
RENAME_PATTERN = r'^\s*R\d+\s+.+[\.java|\.xml]\s+(.+[\.java|\.xml])\s*$'
ADD_OR_MODIFY_PATTERN = r'^\s*[M|A]\s+(.+[\.java|\.xml])\s*$'
GROUP_INDEX_FILE_NAME = 1
STATUS_SUBMITTED = 'submitted'
def __init__(self, config):
self.config = config
self.issue_id = None
self.commit_message = subprocess.check_output('git log --format=%B -n 1', shell=True).strip().split('\n')
self.review_url, self.request_id = self.get_review_url()
def is_clean(self):
return self.config.clean_check and \
'' != subprocess.check_output('git status --porcelain --untracked-files=no', shell=True)
def get_review_url(self):
pattern = re.compile(self.REVIEW_REQUEST_PATTERN % self.config.reviewboard_url)
for line in self.commit_message:
result = pattern.match(line)
if result:
return line, result.group(1)
else:
print('[WARN]: 没有解析到reviewboard url')
return None, None
def check_commit_message(self):
if not self.config.commit_message_check:
return ''
if not self.commit_message:
return u'commit message为空'
result = self.check_subject(self.commit_message[0])
if result:
return result
if not self.config.strict:
# 非严格模式,不检查body和测试影响
return ''
result = self.check_affect(self.commit_message)
if result:
return result
result, reason = self.check_body(self.commit_message[1:-1])
if not result:
return False, reason
return True, ''
def check_subject(self, subject):
result = re.compile(self.SUBJECT_PATTERN).match(subject)
reason = u'第一行为subject,格式为 <type>(<issue_id>): <subject>'
if result:
self.issue_id = result.group(2)
reason = ''
return reason
def check_body(self, lines):
if len(lines) < 3:
return False, u"commit message缺少<subject>, <body>, <affect>元素之一"
if len(lines) > 1 and lines[1].strip():
return False, u"标题的下面应该为空行"
if lines[-2].strip():
return False, u'"测试影响"与body之间需要空行'
if lines[1].strip():
return False, u'subject与body之间需要空行'
return True, ''
def check_affect(self, lines):
pattern = re.compile(self.AFFECT_PATTERN)
for idx, line in enumerate(lines):
if pattern.match(line):
break
else:
return u'没有"测试影响"描述'
if lines[idx - 1].strip():
return u'"测试影响"与body之间需要空行'
if idx < 4:
return u'缺少<body>'
return ''
def get_commit_message(self):
self.commit_message = subprocess.check_output('git log --format=%B -n 1', shell=True).strip().split('\n')
return self.commit_message
def check_land_request(self):
if not self.request_id:
return u'未能查询到review request id'
print(u'开始检查review request是否能够提交: ' + self.request_id)
review_request = self.get_review_request_by_id()
if not review_request.approved:
return review_request.approval_failure
return ''
def get_review_request_by_id(self):
client = RBClient(self.config.reviewboard_url)
return get_review_request(self.request_id, client.get_root())
def exist_remote_branch(self, name):
lines = subprocess.check_output('git branch -a', shell=True).split('\n')
remote_name = 'remotes/origin/{}'.format(name)
for line in lines:
if line.strip() == remote_name:
return True
else:
return False
def push_change(self, branch_name):
return subprocess.call('git push origin HEAD:' + branch_name, shell=True)
def update_jira_comment(self):
jira_user = subprocess.check_output('git config jira.user', shell=True).strip() or self.config.user
jira_password = subprocess.check_output('git config jira.pwd', shell=True).strip() or self.config.password
authed_jira = JIRA(server=(self.config.jira_url), basic_auth=(jira_user, jira_password))
if not self.issue_id:
return False
issue = authed_jira.issue(self.issue_id)
authed_jira.add_comment(issue, '\n'.join(self.commit_message))
# '4' 代表状态是 开发中
try:
authed_jira.transition_issue(issue, transition='4')
except JIRAError:
# 当jira状态已经为“开发中”时,这里transition会抛异常。ignore it
return False
return True
def close_review_request(self):
commit_id = subprocess.check_output('git log --format="%H" -n 1', shell=True)
description = 'hbt closed: {}'.format(commit_id)
review_request = self.get_review_request_by_id()
if review_request.status == self.STATUS_SUBMITTED:
print('Review request {} is already submitted.'.format(self.request_id))
return
review_request.update(status=self.STATUS_SUBMITTED, description=description)
def get_commit_file_list(self, lines):
files = list()
rename_pattern = re.compile(self.RENAME_PATTERN)
update_pattern = re.compile(self.ADD_OR_MODIFY_PATTERN)
for line in lines:
result = rename_pattern.match(line)
if result:
files.append(result.group(self.GROUP_INDEX_FILE_NAME))
continue
result = update_pattern.match(line)
if result:
files.append(result.group(self.GROUP_INDEX_FILE_NAME))
return files
def committed_files(self):
commit_log = subprocess.check_output('git log --name-status --pretty=format:"" -n 1', shell=True)
return [SourceFile.create(os.path.dirname(f), os.path.basename(f))
for f in self.get_commit_file_list(commit_log.split('\n'))]
def ammend_message(self):
subprocess.call('git commit -a --amend --no-edit', shell=True)
if __name__ == '__main__':
java = SourceFile.create('.', 'TestNoWarn.java')
print(java.path, java.name)
java.add_copyright()
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import unittest
from config import HBTConfig
from actions import CommitCheckAction, Action
from sourcefile import SourceFile, JavaFile, XmlFile
JAVA_CONTENT = """\
package com.wlqq.data.net;
import com.raizlabs.android.dbflow.annotation.NotNull;
import com.wlqq.data.request.LoginParams;
import com.wlqq.login.model.Session;
import java.util.Map;
import io.reactivex.Observable;
/**
* Interfaces of gas station.
*/
public interface IGasApi {
/**
* Get an {@link Observable} which will emit a {@link Session}.
*/
Observable<Session> login(@NotNull Map<String, String> headers, @NotNull LoginParams params);
void logout();
}
"""
XML_CONTENT = """\
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright (C) 2017 贵阳货车帮科技有限公司 -->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wlqq.dms.api.build.dao.BuildLogPatternDao">
<sql id="column">
uuid,
pattern,
error_code AS errorCode,
error_message AS errorMessage,
create_user AS createUser,
create_time AS createTime,
update_user AS updateUser,
update_time AS updateTime
</sql>
<select id="list" resultType="com.wlqq.dms.api.build.domain.BuildLogPattern">
SELECT * from dms_build_log_pattern
</select>
</mapper>
"""
def split_file_path(p):
return [os.path.dirname(p), os.path.basename(p)]
class Hbt2TestCase(unittest.TestCase):
java_file_path = 'TestJava.java'
xml_file_path = 'testXml.xml'
def setUp(self):
with open(self.java_file_path, 'w') as f:
f.write(JAVA_CONTENT)
with open(self.xml_file_path, 'w') as f:
f.write(XML_CONTENT)
def tearDown(self):
for p in [self.java_file_path, self.xml_file_path]:
if os.path.exists(p):
os.remove(p)
def test_config(self):
default_config = HBTConfig('hbtrc')
self.assertEqual(default_config.jira_url, "http://jira.56qq.cn")
self.assertEqual(default_config.reviewboard_url, "http://reviewboard.56qq.com")
self.assertEqual(default_config.clean_check, True)
self.assertEqual(default_config.commit_message_check, True)
self.assertEqual(default_config.checkstyle, True)
self.assertEqual(default_config.jira_user, "user")
self.assertEqual(default_config.jira_password, "password")
self.assertEqual(default_config.hbt_dir, "/a/b/c")
def test_commited_files(self):
commited_log = """
M .gitignore
A doc/dms_20171017.sql
M src/main/java/com/wlqq/dms/api/login/controller/LoginController.java
M src/main/java/com/wlqq/dms/api/login/vo/LoginResponseVO.java
M src/main/java/com/wlqq/dms/api/user/controller/UserController.java
M src/main/java/com/wlqq/dms/api/user/dao/UserDao.java
M src/main/java/com/wlqq/dms/api/user/domain/User.java
M src/main/java/com/wlqq/dms/api/user/service/UserService.java
M src/main/java/com/wlqq/dms/api/user/service/impl/UserServiceImpl.java
A src/main/java/com/wlqq/dms/api/user/vo/UserApiKeyUpdateVO.java
M src/main/resources/mapper/user/UserDaoMapper.xml
"""
files = Action(None, None).get_commit_file_list(commited_log.split('\n'))
self.assertEqual(len(files), 9)
self.assertFalse(filter(lambda x: not x.endswith('.java') and not x.endswith('.xml'), files))
rename_log = """R100 code_analysis/hbt/scripts/test.java code_analysis/hbt/scripts/test1.java"""
files = Action(None, None).get_commit_file_list(rename_log.split('\n'))
print(files)
def test_sourcefile_op(self):
sf = SourceFile.create(*split_file_path(self.java_file_path))
self.assertTrue(isinstance(sf, JavaFile))
sf.add_copyright()
with open(self.java_file_path) as f:
content = f.read()
self.assertTrue(content.startswith(sf.get_copyright_text()))
sf = SourceFile.create(*split_file_path(self.xml_file_path))
self.assertTrue(isinstance(sf, XmlFile))
sf.add_copyright()
with open(self.xml_file_path) as f:
content = f.read()
self.assertTrue(content.startswith(sf.get_copyright_text()))
def test_commit_message(self):
config = HBTConfig('hbtrc')
config.commit_message_check = True
valid_messages = [
"""\
fix(ANDROID_INFRA-57): summary
body
测试影响\
""",
"""\
fix(ANDROID_INFRA-57): summary
body
影响:启动页面
http://reviewboard.56qq.com/r/3889/
http://jira.56qq.cn/browse/ANDROID_INFRA-96\
""",
"""\
fix(ANDROID_INFRA-57): summary
body
测试影响
""",
"""\
feat(ANDROID_INFRA-57): summary
body
测试影响
""",
"""\
docs(ANDROID_INFRA-57): summary
body
测试影响
""",
]
action = CommitCheckAction(config, None)
for msg in valid_messages:
action.commit_message = msg
self.assertFalse(action.do())
invalid_messages = [
"""\
unknownfeat(ANDROID_INFRA-57): summary
body
测试影响
""",
"""\
unknownfeat(ANDROID_INFRA-57): summary
测试影响
""",
"""\
unknownfeat(ANDROID_INFRA-57): summary
测试影响
""",
"""\
unknownfeat(ANDROID_INFRA-57): summary
测试影响
""",
"""\
unknownfeat(ANDROID_INFRA-57): summary
这里是正文
""",
"""\
这里是正文
测试影响
""",
"""\
feat ANDROID_INFRA-57 summary
测试影响
"""
]
for msg in invalid_messages:
action.commit_message = msg
self.assertTrue(action.do())
def test_review_url(self):
config = HBTConfig('hbtrc')
request_id = '3889'
review_url = 'http://reviewboard.56qq.com/r/%s/' % request_id
commit_message = """\
fix(ANDROID_INFRA-57): summary
body
影响:启动页面
http://reviewboard.56qq.com/r/3889/
http://jira.56qq.cn/browse/ANDROID_INFRA-96
"""
action = Action(config, None, commit_message)
self.assertEqual((review_url, request_id), action.get_review_url())
commit_message = """\
fix(ANDROID_INFRA-57): summary
body
影响:启动页面
"""
action = Action(config, None, commit_message)
self.assertEqual((None, None), action.get_review_url())
def test_commit_file_list(self):
commit_javas = """\
M src/main/java/com/wlqq/checkout/CheckOutActivity.java
D src/main/java/com/wlqq/shift/activity/FixShiftSuccessActivity.java
A src/main/java/com/wlqq/shift/activity/UnConfirmedShiftActivity.java
M src/main/res/values/values.xml
D src/main/res/values/colors.xml
A src/main/res/values/strings.xml
R100 debug.java test.java
R109 src.xml dest.xml
"""
self.assertEqual(6, len(Action(None, None, None).get_commit_file_list(commit_javas.split('\n'))))
[bdist_wheel]
# This flag says that the code is written to work on both Python 2 and Python
# 3. If at all possible, it is good practice to do this. If you cannot, you
# will need to generate wheels for each Python version that you support.
universal=1
#! /usr/bin/env python
# -*- encoding: utf-8 -*-
#
# Copyright (C) 2017 贵阳货车帮科技有限公司
#
from setuptools import setup, find_packages
setup(
name = "hbt",
version = "4.0.2",
keywords = ("pip", "rbt"),
description = "rbt wrapper",
long_description = "rbt wrapper for review board",
license = "MIT Licence",
author = "Bergkamp.Zhou",
author_email = "zxhmilu0811@gmail.com",
packages = find_packages(),
platforms = "any",
install_requires = ['RBTools>=0.7.9', 'jira>=1.0.10', 'click>=6.7'],
package_data={
'': ['checkstyle-all.jar', 'checkstyle-config.xml'],
},
scripts = [],
entry_points = {
'console_scripts': [
'hbt = scripts.hbt:main',
'hbt2 = scripts.hbt2:main'
]
}
)
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2017 贵阳货车帮科技有限公司 -->
<lint>
<issue id="all">
<ignore path="build" />
</issue>
<!-- Incomplete translation -->
<issue id="MissingTranslation" severity="ignore" />
<!-- Using left/right instead of start/end attributes -->
<issue id="RtlHardcoded" severity="ignore"/>
<!-- Image without contentDescription -->
<issue id="ContentDescription" severity="ignore"/>
<!-- Accessibility in Custom Views -->
<issue id="ClickableViewAccessibility" severity="ignore"/>
<!-- Padding and margin symmetry -->
<issue id="RtlSymmetry" severity="ignore"/>
<!-- Padding and margin symmetry -->
<issue id="MissingRegistered" severity="ignore"/>
<!-- 忽略原因 :
* 目前 UI 并没有考虑系统大字体
* 我们重要的信息展示界面已经实现了应用内字体调整
* 很多第三方应用,比如 微信、美团 都没有跟随系统字体大小 -->
<issue id="SpUsage" severity="ignore"/>
<!-- 忽略原因 :
* 没有必要为所有分辨率创建不同尺寸的图片,Android 自己会自动进行缩放
* 同时考虑到包体积 -->
<issue id="IconMissingDensityFolder" severity="ignore"/>
<issue id="IconDensities" severity="ignore"/>
<!-- 忽略原因:国内环境不太有支持Google搜索跳转到此App的需要 -->
<issue id="GoogleAppIndexingWarning" severity="ignore"/>
</lint>
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2017 贵阳货车帮科技有限公司 -->
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Android Application Rules"
xmlns="http://pmd.sf.net/ruleset/1.0.0" xsi:noNamespaceSchemaLocation="http://pmd.sf.net/ruleset_xml_schema.xsd"
xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd">
<description>Custom ruleset for ribot Android application</description>
<exclude-pattern>.*/R.java</exclude-pattern>
<exclude-pattern>.*/gen/.*</exclude-pattern>
<!-- PhantomLib:dexmaker 中使用的第三方开源库代码 https://github.com/linkedin/dexmaker -->
<exclude-pattern>.*/com/android/dx/.*</exclude-pattern>
<exclude-pattern>.*/com/google/dexmaker/.*</exclude-pattern>
<!-- PhantomLib:PhantomCore 中使用的第三方开源库代码 https://github.com/jOOQ/jOOR -->
<exclude-pattern>.*/org/joor/.*</exclude-pattern>
<!-- PhantomLib:PhantomCore 中用于方便反射 Android Framework 的类-->
<exclude-pattern>.*/mirror/.*</exclude-pattern>
<!-- 早年间引入的第三方源代码 -->
<exclude-pattern>.*/com/wlqq/utils/thirdparty/.*</exclude-pattern>
<!-- WLUtils 中引入的apache开源库中的代码 https://github.com/apache -->
<exclude-pattern>.*/com/wlqq/utils/.*/third.*</exclude-pattern>
<exclude-pattern>.*/com/wlqq/http/MySSLSocketFactory.java</exclude-pattern>
<!--WLUtils 获取应用渠道中引入的第三方代码 https://github.com/mcxiaoke/packer-ng-plugin -->
<exclude-pattern>.*/com/wlqq/utils/PackerNg.java</exclude-pattern>
<!--WLHttp 忽略老http模块代码检查 -->
<exclude-pattern>.*/com/wlqq/http/.*</exclude-pattern>
<exclude-pattern>.*/com/wlqq/securityhttp/.*</exclude-pattern>
<!-- UniversalLogReporter 中引入的第三方代码 https://android.googlesource.com/platform/libcore/+/android-4.1
.1_r1/luni/src/main/java/libcore/io/DiskLruCache.java-->
<exclude-pattern>.*/com/wlqq/ulreporter/cache/filecache/DiskLruCache.java</exclude-pattern>
<rule ref="rulesets/java/android.xml" />
<rule ref="rulesets/java/unusedcode.xml" />
<rule ref="rulesets/java/strings.xml" />
<rule ref="rulesets/java/basic.xml" />
<rule ref="rulesets/java/braces.xml" />
<rule ref="rulesets/java/design.xml">
<exclude name="GodClass" />
</rule>
</ruleset>
\ No newline at end of file
apply plugin: 'pmd'
pmd {
// do not exist, when finding issues
ignoreFailures = true
}
task pmd(type: Pmd, group: 'Verification', description: 'Inspect sourcecode for bugs') {
ruleSetFiles = files("${project.rootDir}/BuildScript/code_analysis/pmd-config.xml")
// 增加 ruleSets = [] 是为了解决 pmd-config.xml 中的 exclude-pattern 没有全部生效的问题
// 参考 https://stackoverflow.com/questions/32247190/pmd-exclude-pattern-with-gradle
ruleSets = []
source 'src'
include '**/*.java'
exclude '**/gen/**'
exclude '**/test/**'
exclude '**/androidTest/**'
reports {
xml.enabled = true
html.enabled = !xml.enabled
}
}
\ No newline at end of file
File added
apply plugin: 'de.undercouch.download'
import java.util.regex.Pattern
class Globals {
static final String DOWNLOAD_URL_PREFIX = 'http://10.2.1.117:9999'
static final String RELEASE_BUILD_DOWNLOAD_URL_PREFIX = DOWNLOAD_URL_PREFIX + '/release_build'
static final String DAILY_BUILD_DOWNLOAD_URL_PREFIX = DOWNLOAD_URL_PREFIX + '/test_build'
static final String BUILTIN_PLUGIN_LIST_CSV_FILE = 'builtin_plugin_list.csv'
}
/**
* 删除参数指定目录下的匹配文件名前缀的文件
*
* @param dir 目录名
* @param apkFileNamePrefix APK 文件前缀名
* @return
*/
static def deletePluginApkFile(String dir, String apkFileNamePrefix) {
new File(dir).eachFile() { file ->
if (file.name.startsWith(apkFileNamePrefix)) {
file.delete()
}
}
}
/**
* 获取插件指定版本<b>稳定版</b>下载链接
*
* @param remoteDir 远程服务器上插件 APK 所在目录名
* @param packageName 插件包名
* @param versionName 插件版本名
* @return 下载链接
*/
static def getStableVersionUrl(String remoteDir, String packageName, String versionName) {
return "${Globals.RELEASE_BUILD_DOWNLOAD_URL_PREFIX}/${remoteDir}/${packageName}_${versionName}.apk"
}
/**
* 获取插件指定版本<b>快照版</b>下载链接
*
* @param remoteDir 远程服务器上插件 APK 所在目录名
* @param packageName 插件包名
* @param versionName 插件版本名
* @return 下载链接
*/
static def getSnapshotVersionUrl(String remoteDir, String packageName, String versionName) {
return "${Globals.DAILY_BUILD_DOWNLOAD_URL_PREFIX}/${remoteDir}/${packageName}_${versionName}.apk"
}
/**
* 获取插件最新的<b>快照版</b>下载链接
*
* @param remoteDir
* @return 下载链接
*/
static def getLatestSnapshotVersionUrl(String remoteDir) {
def filename = "${Globals.DAILY_BUILD_DOWNLOAD_URL_PREFIX}/${remoteDir}/filename.txt".toURL().text
return "${Globals.DAILY_BUILD_DOWNLOAD_URL_PREFIX}/${remoteDir}/${filename}"
}
/**
* 下载插件指定版本<b>稳定版</b>到指定的本地目录
*
* @param remoteDir 远程服务器上插件 APK 所在目录名
* @param packageName 插件包名
* @param versionName 插件版本名
* @param localDir 本地存储目录
* @return
*/
def downloadPluginStableVersion(String remoteDir, String packageName, String versionName, String localDir) {
def pluginDir = localDir
deletePluginApkFile(pluginDir, packageName)
download {
src getStableVersionUrl(remoteDir, packageName, versionName)
dest pluginDir
}
}
/**
* 下载插件指定版本<b>快照版</b>到指定的本地目录
*
* @param remoteDir 远程服务器上插件 APK 所在目录名
* @param packageName 插件包名
* @param versionName 插件版本名
* @param localDir 本地存储目录
* @return
*/
def downloadPluginSnapshotVersion(String remoteDir, String packageName, String versionName, String localDir) {
println "[downloadPluginSnapshotVersion] ${packageName}:${versionName}"
def pluginDir = localDir
deletePluginApkFile(pluginDir, packageName)
def url = null
if ('+' == versionName) {
url = getLatestSnapshotVersionUrl(remoteDir)
} else {
url = getSnapshotVersionUrl(remoteDir, packageName, versionName)
}
download {
src url
dest pluginDir
}
}
/**
* 生成内置插件列表描述文件 <code>assets/builtin_plugin_list.csv</code>
*
* 在 assets 目录下匹配符合插件命名规范的 APK 文件 <code>${package_name}_${version_name}.apk</code> ,生成对应
* 的内置插件描述信息
* <ul>
* <li>filename</li>
* <li>packageName</li>
* <li>versionName</li>
* <li>versionCode</li>
* </ul>
* 写入 csv 文件 <code>assets/builtin_plugin_list.csv</code>
* <pre>
* csv file, line format: ${filename},${packageName},${versionName},${versionCode}
* e.g. com.wlqq.phantom.plugin.wallet_2.1.9.apk,com.wlqq.phantom.plugin.wallet,2.1.9,20109
* </pre>
* @param assetsDirPath assets dir path
* @return
*/
def genBuiltinPluginList(String assetsDirPath) {
final File assetsDir = file(assetsDirPath)
final Pattern pattern = Pattern.compile(/(^[a-z]+(\.[a-z][a-z0-9]*)*)_(\d+\.\d+\.\d+)\.apk$/)
def result = assetsDir.list().inject([]) { list, filename ->
def matcher = pattern.matcher(filename)
if (matcher.matches()) {
def packageName = matcher.group(1)
def versionName = matcher.group(3)
def versionCode = versionName.split(/\./).inject(0) { acc, val ->
acc * 100 + val.toInteger()
}
list << new Tuple(filename, packageName, versionName, versionCode)
} else {
list
}
}
File propertiesFile = new File(assetsDir, Globals.BUILTIN_PLUGIN_LIST_CSV_FILE)
if (propertiesFile.exists()) {
propertiesFile.delete()
}
propertiesFile.createNewFile()
propertiesFile.withPrintWriter('utf-8') { writer ->
println "[genBuiltinPluginList] ====== BEGIN: DUMP BUILT-IN PLUGIN LIST ======"
result.each {
println it.toString()
writer.println(it.join(','))
}
println "[genBuiltinPluginList] ====== END: DUMP BUILT-IN PLUGIN LIST ======"
}
}
// 导出方法 downloadPluginStableVersion 供其他 gradle 脚本调用
project.ext.downloadPluginStableVersion = this.&downloadPluginStableVersion
// 导出方法 downloadPluginSnapshotVersion 供其他 gradle 脚本调用
project.ext.downloadPluginSnapshotVersion = this.&downloadPluginSnapshotVersion
// 导出方法 genBuiltinPluginList 供其他 gradle 脚本调用
project.ext.genBuiltinPluginList = this.&genBuiltinPluginList
\ No newline at end of file
/*
* Copyright (C) 2017 贵阳货车帮科技有限公司
*/
/**
* 全局的版本定义
* 1. 构建工具链版本
* 2. 第三方依赖库版本
*/
ext {
// android build tools version
COMPILE_SDK_VERSION = 25
BUILD_TOOLS_VERSION = '25.0.3'
MIN_SDK_VERSION = 14
// gradle maven dependencies version
ANDROID_SUPPORT_LIB_VERSION = '25.3.1'
ANDROID_MULTIDEX_VERSION = '1.0.1'
JUNIT_VERSION = '4.12'
ANDROID_TEST_SUPPORT_VERSION = '0.5'
ESPRESSO_VERSION = '2.2.2'
UIAUTOMATOR_VERSION = '2.1.2'
GSON_VERSION = '2.8.1'
COMMONS_IO_VERSION = '1.4'
COMMONS_LANG_VERSION = '2.5'
HTTP_MIME_VERSION = '4.1.1'
JAVA_BASE64_VERSION = '1.3.1'
// 极光推送
JPUSH_VERSION = '2.1.3'
// 高徳地图
AMAP_MAP3D_VERSION = '4.1.1'
AMAP_LOCATION_VERSION = '3.3.0'
AMAP_SEARCH_VERSION = '5.0.0'
AWESOMELOG_VERSION = '1.0.1'
PROTOBUF_JAVA_VERSION = '2.4.1'
// 二维码生成/扫描 ZXing https://github.com/zxing/zxing
ZXING_VERSION = '3.1.0'
// 数据库 ORM 框架 DBFlow https://github.com/Raizlabs/DBFlow
DBFLOW_VERSION = '3.0.0'
// 内存泄漏检测 LeakCanary https://github.com/square/leakcanary
LEAK_CANARY_VERSION = '1.5'
// mockito
MOCKITO_CORE = '2.8.47'
// assertj
ASSERTJ_CORE = '3.8.0'
ASSERTJ_ANDROID = '1.1.1'
// Robolectric
ROBOLECTRIC_VERSION = '3.3.2'
//Okhttp
OKHTTP3_VERSION = '3.9.0'
//retrofit
RETROFIT2_VERSION = '2.3.0'
//rxjava
RXJVA_VERSION = '2.1.3'
RXANDROID_VERSION = '2.0.1'
// http://nexus.56qq.cn:81/content/repositories/wlqq-releases/com/wlqq/android/ImageLoader/
IMAGE_LOADER_VERSION = '1.0.0'
// http://nexus.56qq.cn:81/content/repositories/wlqq-snapshots/com/wlqq/android/phantom-compiler/
PHANTOM_COMPILER_VERSION = '0.5.0-SNAPSHOT'
//powermock
PROWEMOCK_VERSIN = '1.7.3'
// https://mvnrepository.com/artifact/org.json/json
ORG_JSON_VERSION = '20160810'
// https://mvnrepository.com/artifact/com.google.code.findbugs/findbugs-annotations
FINDBUGS_ANNOTATIONS_VERSION = '3.0.1'
// 宿主共享公共库最小版本,为了向前兼容,这个版本通常不需要修改
MIN_ANDROID_SUPPORT_LIB_VERSION = '25.3.1'
MIN_GSON_VERSION = '2.8.1'
MIN_COMMONS_IO_VERSION = '1.4'
MIN_COMMONS_LANG_VERSION = '2.5'
MIN_HTTP_MIME_VERSION = '4.1.1'
MIN_JAVA_BASE64_VERSION = '1.3.1'
MIN_IMAGE_LOADER_VERSION = '1.0.0'
}
/**
* 获取 Jenkins Job Build Number,若没有在 Jenkins 编译,则返回零
*
* @return Jenkins Job Build Number,若没有在 Jenkins 编译,则返回零
*/
def static getJenkinsBuildNumber() {
def env = System.env
if (env == null || !env.containsKey('BUILD_NUMBER')) {
return 0
}
return env.BUILD_NUMBER
}
/**
* 是否在 Jenkins 上编译
*
* @return true 表示在 Jenkins 上编译;否则 false
*/
def static isBuildOnJenkins() {
return getJenkinsBuildNumber() > 0
}
// 将方法导出为全局方法
ext {
getJenkinsBuildNumber = this.&getJenkinsBuildNumber
isBuildOnJenkins = this.&isBuildOnJenkins
}
\ No newline at end of file
#Mon Sep 07 18:13:31 CST 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=http\://10.2.1.117:9999/software/gradle/gradle-3.3-all.zip
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >&-
APP_HOME="`pwd -P`"
cd "$SAVED" >&-
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
/*
* Copyright (C) 2017 贵阳货车帮科技有限公司
*/
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
/**
* 对dex进行加解密,需要加解密的dex有插件包中的dex和优化生成的odex/oat
*/
public class PluginSecurityUtils {
private static final String ENCRYPT_FILE = "classes.pro";
//RSA一次加密明文最大长度是117, 0x70 + 5 == 117
private static final int ENCRYPTION_DATA_LEN = 0x70 + 5;
private static byte[] sDataBuf = new byte[ENCRYPTION_DATA_LEN];
private static final String sPublicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDaDK7Dsymtn279tD0lY1ROoD6Cysj/J/87rreeyrWOobvjPeTBUNOI+BM6rWzvE1z6q+gCv9RxzBdiHayz5kDgwaMTmmRujh/rDKOA8gWyFxPCiwbMb9UbHW/6dW5JiFDxYApES6Z0EnXa5JjAogyUqhoTFUfQEpKXKtCETsOt6wIDAQAB";
private static final String sPrivateKey = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBANoMrsOzKa2fbv20PSVjVE6gPoLKyP8n/zuut57KtY6hu+M95MFQ04j4EzqtbO8TXPqr6AK/1HHMF2IdrLPmQODBoxOaZG6OH+sMo4DyBbIXE8KLBsxv1Rsdb/p1bkmIUPFgCkRLpnQSddrkmMCiDJSqGhMVR9ASkpcq0IROw63rAgMBAAECgYEAtZPgKQnWiEX/WHgyfyMDPuKEi8gwrwJwshOhxzMJE/itDOQqzazwKtxirvdigoh+YiSrdTanxAfv5P3PJpR0qusIEJwhpKOACfKFQw3NZJc+3YZpQKvQDXUH2lIWMPC1TNiM6/CK4IIwLFdCau+HKDZhbU07YF8RKTWk/pUy9BECQQD/SB+19JJAVzIDPi6l/4vROlcI4+HMp8US0B+Cp/SOTmDK8u68XOzSTCvbl2V7fu/SuRH2vJWio4WuNq6fLlaDAkEA2qm9rg9i2C/6p1ClfhicUX6QqHrfixrxNmgmhG0W6JPxo+kTdackaR/TbdNcpuNkj5VWGp+1yyrSsSah6aXueQJAD0D0BB9VDdsn9eGlT+3xINNnl/Rl7aCoufMNrvTyO/6a8gWKFl9HF1nN1RU6zyJKmkDMvf2Ow2UZ+8rwrDpMmQJAM/BjxMeU1AM5h6qpVLVl1Bm7JLnjBXjF2QWOOKBs85vIpwWDAMxN4saTgx/UfzO+PDjdtf8/wF2QpFGn3gbzCQJARyKxMoJFz7WYccR+J4+AyLPE2k82ocl0lxz/nggNAONyg3HH+JRBn+ejgBzZvgwy64Ibph3gs8/tKPzmCSwRXA==";
public static void encryptDex(String dexFilePath) throws Exception {
encryptionFile(dexFilePath, ENCRYPT_FILE, sPublicKey);
cleanBufData();
}
/**
* 被加密的dex头部需要填充数据,这里直接从dex文件末尾ENCRYPTION_DATA_LEN * 2处取出一块数据填充到头部,
* 填充的数据以dey开始
*/
private static void generateData(String dexFilePath) throws FileNotFoundException, IOException {
if (sDataBuf[0] == 'd' && sDataBuf[1] == 'e' && sDataBuf[2] == 'y') {
return;
}
sDataBuf[0] = 'd';
sDataBuf[1] = 'e';
sDataBuf[2] = 'y';
File dexFile = new File(dexFilePath);
RandomAccessFile raf = new RandomAccessFile(dexFile, "r");
//从dex文件末尾 ENCRYPTION_DATA_LEN * 2取ENCRYPTION_DATA_LEN - 3长度数据作为填充数据。
long readPosition = dexFile.length() - ENCRYPTION_DATA_LEN * 2;
raf.seek(readPosition);
raf.read(sDataBuf, 3, ENCRYPTION_DATA_LEN - 3);
raf.close();
}
/**
* dex加密完成后需要清空保存在内存中的填充数据,
* 因为这是静态变量,不清空,加密其他插件可能会被用到
*/
private static void cleanBufData() {
for (int i = 0; i < ENCRYPTION_DATA_LEN; i++) {
sDataBuf[i] = '0';
}
}
public static void decryptDex(String dexFilePath) throws Exception {
decryptionFile(dexFilePath, ENCRYPT_FILE, sPrivateKey);
}
/**
* 对dex进行加密。加密分为两种情况:
* 1. 如果classes.pro文件存在,则表示dex之前是加密过的,这个时候只需要替换dex头部ENCRYPTION_DATA_LEN长度的数据。
* 2. 如果classes.pro文件不存在, 则需要在加密dex的同时生成classes.pro文件
*
* @param encryptionFilePath 被加密的dex文件路径
* @param keyFileName 这里为classes.pro文件
* @param key 加密公钥
*
* @throws Exception 加密失败抛出异常
*/
private static void encryptionFile(String encryptionFilePath, String keyFileName, String key) throws Exception {
File enFile = new File(encryptionFilePath);
File keyFile = new File(enFile.getParentFile(), keyFileName);
byte[] dataBuf = new byte[ENCRYPTION_DATA_LEN];
generateData(encryptionFilePath);
if (keyFile.exists()) {
//加密文件classes.pro存在,重新加密只需要修改dex文件头部
RandomAccessFile raf = new RandomAccessFile(enFile, "rw");
raf.seek(0);
raf.write(sDataBuf);
raf.close();
return;
}
FileInputStream enFileInputStream = new FileInputStream(enFile);
enFileInputStream.read(dataBuf);
enFileInputStream.close();
byte[] enData = RSAUtil.encryptByPublicKey(key, dataBuf);
FileOutputStream keyFileOutputStream = new FileOutputStream(keyFile);
keyFileOutputStream.write(enData);
keyFileOutputStream.close();
RandomAccessFile raf = new RandomAccessFile(enFile, "rw");
raf.seek(0);
raf.write(sDataBuf);
raf.close();
}
/**
* 对dex进行解密。解密时先读取被加密dex头部ENCRYPTION_DATA_LEN长度的数据缓存起来,方便重新加密时使用,然后将
* 解密出来的正确数据写入dex文件头部,还原dex。
*
* @param decryptionFilePath 被加密多的dex文件
* @param keyFileName 这里为加密数据保存文件classes.pro
* @param key 解密私钥
* @throws Exception 解密失败抛出异常
*/
private static void decryptionFile(String decryptionFilePath, String keyFileName, String key) throws Exception {
File deFile = new File(decryptionFilePath);
File keyFile = new File(deFile.getParentFile(), keyFileName);
byte[] keyBuf = new byte[(int) keyFile.length()];
FileInputStream keyFileInputStream = new FileInputStream(keyFile);
keyFileInputStream.read(keyBuf);
keyFileInputStream.close();
byte[] normalData = RSAUtil.decryptByPrivateKey(key, keyBuf);
RandomAccessFile raf = new RandomAccessFile(deFile, "rw");
raf.seek(0);
raf.read(sDataBuf);
raf.seek(0);
raf.write(normalData);
raf.close();
}
}
/*
* Copyright (C) 2017 贵阳货车帮科技有限公司
*/
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
/**
* RSA 加解密工具类
*/
public class RSAUtil {
public static final String KEY_ALGORITHM = "RSA";
public static final String KEY_ALGORITHM_CIPHER = "RSA/ECB/PKCS1Padding";
/**
* RSA 私钥加密。
*
* @param key 私钥。
* @param data 待加密数据。
* @return 密文数据。如果加密失败返回null。
* @throws InvalidKeySpecException 当传入的key不是一个合法的RSA私钥时抛异常。
* @throws NullPointerException key 或 data为null时抛异常
*/
public static byte[] encryptByPrivateKey(String key, byte[] data) throws InvalidKeySpecException {
Key privateKey = generatePrivateKey(key.decodeBase64());
return doFinal(Cipher.ENCRYPT_MODE, privateKey, data);
}
/**
* RSA 私钥解密。
*
* @param key 私钥。
* @param data 待解密数据。
* @return 解密后的数据。如果解密失败返回null。
* @throws InvalidKeySpecException 当传入的key不是一个合法的RSA私钥时抛异常。
* @throws NullPointerException key 或 data为null时抛异常
*/
public static byte[] decryptByPrivateKey(String key, byte[] data) throws InvalidKeySpecException {
Key privateKey = generatePrivateKey(key.decodeBase64());
return doFinal(Cipher.DECRYPT_MODE, privateKey, data);
}
/**
* RSA 公钥加密。
*
* @param key 公钥。
* @param data 待加密数据。
* @return 加密后的数据。如果加密失败返回null。
* @throws InvalidKeySpecException 当传入的key不是一个合法的RSA公钥时抛异常。
* @throws NullPointerException key 或 data为null时抛异常
*/
public static byte[] encryptByPublicKey(String key, byte[] data) throws InvalidKeySpecException {
Key publicKey = generatePublicKey(key.decodeBase64());
return doFinal(Cipher.ENCRYPT_MODE, publicKey, data);
}
/**
* RSA 公钥解密。
*
* @param key 公钥。
* @param data 待解密数据。
* @return 解密后的数据。如果解密失败返回null。
* @throws InvalidKeySpecException 当传入的key不是一个合法的RSA公钥时抛异常。
* @throws NullPointerException key 或 data为null时抛异常
*/
public static byte[] decryptByPublicKey(String key, byte[] data) throws InvalidKeySpecException {
Key publicKey = generatePublicKey(key.decodeBase64());
return doFinal(Cipher.DECRYPT_MODE, publicKey, data);
}
private static byte[] doFinal(int model, Key key, byte[] data) {
if (key == null) {
return null;
}
try {
Cipher cipher = Cipher.getInstance(KEY_ALGORITHM_CIPHER);
cipher.init(model, key);
return cipher.doFinal(data);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
}
return null;
}
private static Key generatePrivateKey(byte[] key) throws InvalidKeySpecException {
// 获取私钥
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(key);
try {
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
return keyFactory.generatePrivate(keySpec);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
private static Key generatePublicKey(byte[] key) throws InvalidKeySpecException {
// 获取公钥
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(key);
try {
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
return keyFactory.generatePublic(keySpec);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
}
/**
* 加密插件apk
*/
afterEvaluate{
assembleDebug.doLast {
encryptPlugin()
}
assembleRelease.doLast {
encryptPlugin()
}
}
def encryptPlugin() {
File dstApkFile = null
project.android.applicationVariants.all { variant ->
if (null != dstApkFile) {
return
}
variant.outputs.each { output ->
if (output.outputFile.exists()) {
dstApkFile = output.outputFile
return
}
}
}
unpackage(dstApkFile)
repackagePlugin.execute()
resign(dstApkFile)
}
//将加密后的插件压缩成apk
task repackagePlugin(type : Zip) {
archiveName = 'unsign.apk'
from("${buildDir.absolutePath}${File.separator}outputs${File.separator}apk${File.separator}tmp${File.separator}"){
into "/"
}
}
def unpackage(File outApkFile) {
println "unpackage:${outApkFile}"
def workDir = outApkFile.getParent() + File.separator
def tmpDir = workDir + 'tmp'
project.copy {
from(zipTree(outApkFile))
into tmpDir
}
def assetsDir = file("${tmpDir}${File.separator}assets")
if (!assetsDir.exists()) {
assetsDir.mkdirs()
}
//将classes.dex移到asstes目录
File dstDexFile = new File("${assetsDir}${File.separator}classes.dex")
delete(dstDexFile)
GFileUtils.moveFile(new File("${tmpDir}${File.separator}classes.dex"),
dstDexFile)
GFileUtils.deleteDirectory(new File("${tmpDir}${File.separator}META-INF"))
//加密dex
encryptDex(dstDexFile)
}
//加密dex文件
def encryptDex(File dex) {
GroovyClassLoader classLoader = new GroovyClassLoader()
classLoader.parseClass(file("${rootDir}/BuildScript/phantom_plugin_encryption/RSAUtil.java"))
def cls = classLoader.parseClass(file("${rootDir}/BuildScript/phantom_plugin_encryption/PluginSecurityUtils.java"))
cls.encryptDex(dex.absolutePath)
}
//对加密后的apk签名
def resign(File outApkFile) {
def dstDir = outApkFile.getParentFile()
def signConfig = project.android.signingConfigs.debug
def signCmd = 'jarsigner -verbose' +
' -digestalg SHA1' +
' -sigalg SHA1withRSA' +
' -keystore '+ signConfig.storeFile.absolutePath +
' -storepass '+ signConfig.storePassword +
' -keypass '+ signConfig.keyPassword +
' -signedjar '+ "${dstDir.absolutePath}${File.separator}resigned.apk" +
" ${buildDir}${File.separator}distributions${File.separator}unsign.apk " +
signConfig.keyAlias
//重新签名
Process process = signCmd.execute()
println process.text
Properties properties = new Properties()
InputStream inputStream = project.rootProject.file("${rootDir}${File.separator}local.properties").newDataInputStream() ;
properties.load( inputStream )
delete(file("${dstDir.absolutePath}${File.separator}${outApkFile.name}"))
//zipalign
def alignCmdPath = "${properties.getProperty('sdk.dir')}${File.separator}build-tools${File.separator}${project.android.buildToolsVersion}${File.separator}zipalign"
def zipalignCmd = "${alignCmdPath} -v 4 " + "${dstDir.absolutePath}${File.separator}resigned.apk " + "${dstDir.absolutePath}${File.separator}${outApkFile.name}"
def resultTxt = zipalignCmd.execute().text
println resultTxt
delete(file("${dstDir.absolutePath}${File.separator}resigned.apk"))
delete(file("${dstDir.absolutePath}${File.separator}tmp"))
}
\ No newline at end of file
import javax.imageio.ImageIO
import java.awt.Color
import java.awt.Font
import java.awt.FontMetrics
import java.awt.Graphics2D
import java.awt.RenderingHints
import java.awt.image.BufferedImage
def static void generateTextImage(String text,
String textColor,
String fontFace,
int fontSize,
String outputImageFilePath) {
BufferedImage image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
// Represents an image with 8-bit RGBA color components packed into integer pixels.
Graphics2D graphics2d = image.createGraphics();
Font font = new Font(fontFace, Font.PLAIN, fontSize);
graphics2d.setFont(font);
FontMetrics fontMetrics = graphics2d.getFontMetrics();
int width = fontMetrics.stringWidth(text);
int height = fontMetrics.getHeight();
graphics2d.dispose();
image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
graphics2d = image.createGraphics();
setRenderingHint(graphics2d);
graphics2d.setFont(font);
fontMetrics = graphics2d.getFontMetrics();
graphics2d.setColor(Color.decode(textColor));
graphics2d.drawString(text, 0, fontMetrics.getAscent());
graphics2d.dispose();
ImageIO.write(image, "png", new File(outputImageFilePath));
}
def static void setRenderingHint(Graphics2D graphics2d) {
graphics2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
graphics2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
graphics2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
graphics2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
graphics2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
graphics2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
graphics2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
graphics2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
graphics2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
}
class TextImage {
String text
String textColor
String fontFace
int fontSize
String outputImageFilePath
}
project.extensions.create('textImage', TextImage)
task generateTextImage << {
GroovyClassLoader classLoader = new GroovyClassLoader()
def cls = classLoader.parseClass(file("${rootDir}/BuildScript/plugins/textimage/TextImageCreator.groovy"))
cls.generateTextImage(textImage.text, textImage.textColor,
textImage.fontFace, textImage.fontSize, textImage.outputImageFilePath);
}
preBuild.dependsOn generateTextImage
Set<String> findDependencies(String type) {
Set<String> dependenceLibs = []
rootProject.allprojects.each {
try {
it.configurations.getByName(type)
} catch (UnknownConfigurationException e) {
return;
}
Iterator<Dependency> iterator = it.configurations.getByName(type).dependencies.iterator()
while (iterator.hasNext()) {
Dependency dependency = iterator.next()
if (null != dependency.group && !"unspecified".equals(dependency.version)) {
dependenceLibs.add("${dependency.group}:${dependency.name}:${dependency.version}")
}
}
}
return dependenceLibs
}
/**
* 生成依赖库版本描述文件
*
* @param dependenceLibs 依赖库集合,集合元素以依赖库 maven GAV 格式描述描述
* @param outputDir 文件输出目录
* @param fileName 文件名
*/
void createPropertiesFile(Set<String> dependenceLibs, String outputDir, String fileName) {
File dir = new File(outputDir)
if (dir.isFile()) {
throw new GradleException("[createPropertiesFile] the specified dir: ${outputDir} must not be a file")
}
if (!dir.exists() && !dir.mkdirs()) {
throw new GradleException("[createPropertiesFile] create dir: ${outputDir} failed")
}
File propertiesFile = new File(outputDir, fileName)
propertiesFile.withWriter('UTF-8') { writer ->
println "[createPropertiesFile: ${fileName}] ====== BEGIN DUMP ======"
dependenceLibs.each {
// write to file
writer.writeLine(it)
// dump to console
println it
}
println "[createPropertiesFile: ${fileName}] ====== END DUMP ======"
}
}
void createCompileProperties(String outputDir) {
createPropertiesFile(findDependencies('compile'), outputDir, 'compile_dependencies.txt')
}
/**
* Create lib requirements file declared by plugin
* <ul>
* <li>provided_dependencies.txt for Phantom version lower than 0.6.0</li>
* <li>provided_dependencies_v2.txt for Phantom version greater or equal than 0.6.0</li>
* </ul>
*
* @param outputDir the output directory
*
* @see <a href="http://git.56qq.com/Android-Team/WLExcludeClasses/blob/master/wl-plugin-exclude/src/main/groovy/com/wlqq/gradle/plugin/exclude/ExcludeClasses.groovy">ExcludeConfig</a>
*/
void createProvidedProperties(String outputDir) {
def providedLibs = findDependencies('provided')
def providedLibsV2 = new HashSet(providedLibs)
def excludes = project.extensions.findByName("wlExclude")
if (null != excludes) {
def compileLibs = findDependencies('compile')
// excludeLib type : ExcludeConfig
for (def excludeLib : excludes.excludeLibs) {
compileLibs.each { compileLib ->
if (compileLib == excludeLib.name) {
if (!isVersionRequirementValid(excludeLib.versionRequirement)) {
throw new GradleException("version requirement invalid: '${excludeLib.versionRequirement}'")
}
providedLibs.add("${excludeLib.groupId}:${excludeLib.artifactId}:${stripOperator(excludeLib.versionRequirement)}")
providedLibsV2.add("${excludeLib.groupId}:${excludeLib.artifactId}:${excludeLib.versionRequirement}")
}
}
}
}
createPropertiesFile(providedLibs, outputDir, 'provided_dependencies.txt')
createPropertiesFile(providedLibsV2, outputDir, 'provided_dependencies_v2.txt')
}
static boolean isVersionRequirementValid(String versionRequirement) {
// 合法的 versionRequirement 声明为 4.12 或 >=4.12
return versionRequirement ==~ /(?:>=)?(:?\d+\.)*\d+/
}
static String stripOperator(String versionRequirement) {
return versionRequirement.replaceFirst(">=", "")
}
//生成compile_dependencies.txt属性文件
//调用方式project.ext.createCompileProperties()
project.ext.createCompileProperties = this.&createCompileProperties
//生成provided_dependencies.txt属性文件
//调用方式project.ext.createProvidedProperties()
project.ext.createProvidedProperties = this.&createProvidedProperties
\ No newline at end of file
/**
* 该脚本创建了用于上传构件(jar/aar,源代码,Javadoc 文档)的任务 <code>uploadArchives</code>
* 默认上传到 snapshots 仓库,若指定可选参数 <code>release</code> 则上传到 releases 仓库
* <p>
* 上传构件需要的 Nexus username 和 password 需要以 gradle properties 的方式指定
* </p>
*/
apply plugin: 'maven'
def nexusUrl = 'http://nexus.56qq.cn:81'
/**
* 获取 Nexus username, 建议配置到 global gradle properties (~/.gradle/gradle.properties)
*
* @return Nexus username
*/
def getRepositoryUsername() {
return hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : ""
}
/**
* 获取 Nexus password, 建议配置到 global gradle properties (~/.gradle/gradle.properties)
*
* @return Nexus password
*/
def getRepositoryPassword() {
return hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : ""
}
/**
* 是否上传到 releases 仓库,通过 gradle 命令行参数 <code>-Prelease</code> 指定
*
* @return true 上传到 releases 仓库,否则 false
*/
def isRelease() {
return hasProperty('release')
}
afterEvaluate { project ->
uploadArchives {
repositories {
mavenDeployer {
pom.name = project.name
pom.groupId = 'com.wlqq.android' // Android Team 统一使用 Group Id: com.wlqq.android
pom.artifactId = project.name // ArtifactId 为 project 的名字
if (isRelease()) {
pom.version = android.defaultConfig.versionName
} else {
pom.version = android.defaultConfig.versionName + '-SNAPSHOT'
}
// 稳定发布版仓库
repository(url: "${nexusUrl}/content/repositories/wlqq-releases") {
authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
}
// 开发快照版仓库
snapshotRepository(url: "${nexusUrl}/content/repositories/wlqq-snapshots") {
authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
}
}
}
}
// 生成 Javadoc
task androidJavadocs(type: Javadoc) {
source = android.sourceSets.main.java.srcDirs
classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
failOnError false
}
// 打包 Javadoc
task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
classifier = 'javadoc'
from androidJavadocs.destinationDir
}
// 打包源代码
task androidSourcesJar(type: Jar) {
classifier = 'sources'
from android.sourceSets.main.java.sourceFiles
}
artifacts {
archives androidSourcesJar
}
}
\ No newline at end of file
/**
* 该脚本创建了用于上传 java 项目生成的构件(jar/源代码/Javadoc)的任务 <code>uploadArchives</code>
* <p>
* 默认上传到 snapshots 仓库,若指定可选参数 <code>release</code> 则上传到 releases 仓库
* </p>
*
* <p>
* <b>注意</b>
* <ul>
* <li>上传构件需要的 Nexus username 和 password 需要在 <code>gradle.properties</code> 中定义</li>
* <li>项目的 POM_ARTIFACT_ID 和 POM_VERSION_NAME 需要在 <code>gradle.properties</code> 中定义</li>
* </ul>
* </p>
*/
apply plugin: 'maven'
def nexusUrl = 'http://nexus.56qq.cn:81'
/**
* 获取 Nexus username, 建议配置到 global gradle properties (~/.gradle/gradle.properties)
*
* @return Nexus username
*/
def getRepositoryUsername() {
return hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : ""
}
/**
* 获取 Nexus password, 建议配置到 global gradle properties (~/.gradle/gradle.properties)
*
* @return Nexus password
*/
def getRepositoryPassword() {
return hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : ""
}
/**
* 是否上传到 releases 仓库,通过 gradle 命令行参数 <code>-Prelease</code> 指定
*
* @return true 上传到 releases 仓库,否则 false
*/
def isRelease() {
return hasProperty('release')
}
afterEvaluate { project ->
uploadArchives {
repositories {
mavenDeployer {
pom.name = project.name
// Android Team 统一使用 Group Id: com.wlqq.android
pom.groupId = 'com.wlqq.android'
// POM_ARTIFACT_ID 需要在 apply 该脚本的模块的 gradle.properties 中定义
pom.artifactId = POM_ARTIFACT_ID
// POM_VERSION_NAME 需要在 apply 该脚本的模块的 gradle.properties 中定义
if (isRelease()) {
pom.version = POM_VERSION_NAME
} else {
pom.version = "${POM_VERSION_NAME}-SNAPSHOT"
}
// 稳定发布版仓库
repository(url: "${nexusUrl}/content/repositories/wlqq-releases") {
authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
}
// 开发快照版仓库
snapshotRepository(url: "${nexusUrl}/content/repositories/wlqq-snapshots") {
authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
}
}
}
}
}
// 打包源代码
task sourcesJar(type: Jar) {
classifier = 'sources'
from sourceSets.main.allJava
}
artifacts {
archives sourcesJar
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment